From ea4663fd8848921670280847ba64740ebb4b908c Mon Sep 17 00:00:00 2001 From: Omer Strulovich Date: Thu, 9 Apr 2026 16:56:52 -0400 Subject: [PATCH 1/9] Avoid intrinsic sizing for RTL markdown documents Co-authored-by: Codex --- .../sample/RtlCompatibilityBehaviorSample.kt | 12 ++- .../richtext/markdown/BasicMarkdown.kt | 81 +++++++++++++++---- 2 files changed, 74 insertions(+), 19 deletions(-) diff --git a/android-sample/src/main/java/com/zachklipp/richtext/sample/RtlCompatibilityBehaviorSample.kt b/android-sample/src/main/java/com/zachklipp/richtext/sample/RtlCompatibilityBehaviorSample.kt index 952a2c93..728d5e56 100644 --- a/android-sample/src/main/java/com/zachklipp/richtext/sample/RtlCompatibilityBehaviorSample.kt +++ b/android-sample/src/main/java/com/zachklipp/richtext/sample/RtlCompatibilityBehaviorSample.kt @@ -100,8 +100,9 @@ private fun TableBehaviorPreview() { BehaviorPreviewSurface { BehaviorPreviewColumn { BehaviorSection( - title = "Markdown table renders in RTL layout", + title = "Markdown table renders with RTL compatibility enabled", markdown = tableMarkdown, + richTextRenderOptions = RichTextRenderOptions(enableRtlCompatibility = true), ) } } @@ -138,6 +139,7 @@ private fun BehaviorPreviewColumn( private fun BehaviorSection( title: String, markdown: String, + richTextRenderOptions: RichTextRenderOptions = RichTextRenderOptions(), ) { Column( modifier = Modifier.fillMaxWidth(), @@ -147,18 +149,22 @@ private fun BehaviorSection( text = title, style = MaterialTheme.typography.labelLarge, ) - BehaviorMarkdown(markdown = markdown) + BehaviorMarkdown( + markdown = markdown, + richTextRenderOptions = richTextRenderOptions, + ) } } @Composable private fun BehaviorMarkdown( markdown: String, + richTextRenderOptions: RichTextRenderOptions, ) { RichText(modifier = Modifier.fillMaxWidth()) { Markdown( content = markdown, - richtextRenderOptions = RichTextRenderOptions(), + richtextRenderOptions = richTextRenderOptions, ) } } diff --git a/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/BasicMarkdown.kt b/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/BasicMarkdown.kt index 5ff1b7bf..ba61adad 100644 --- a/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/BasicMarkdown.kt +++ b/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/BasicMarkdown.kt @@ -1,13 +1,12 @@ package com.halilibo.richtext.markdown -import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.width import androidx.compose.foundation.text.BasicText import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.SubcomposeLayout import androidx.compose.ui.semantics.heading import androidx.compose.ui.semantics.semantics import com.halilibo.richtext.markdown.rtl.LocalCompatibilityTextAlignOverride @@ -95,29 +94,79 @@ public fun RichTextScope.BasicMarkdown( ) { val markdownAnimationState = remember { MarkdownAnimationState() } - if (richTextRenderOptions.enableRtlCompatibility && astNode.type is AstDocument) { - BasicRichText(modifier = Modifier.width(IntrinsicSize.Max)) { - RecursiveRenderMarkdownAst( - astNode = astNode, - contentOverride = contentOverride, - inlineContentOverride = inlineContentOverride, - richTextRenderOptions = richTextRenderOptions, - richTextDecorations = richTextDecorations, - markdownAnimationState = markdownAnimationState, - astNodeComposer = astBlockNodeComposer, - ) - } - } else { + @Composable + fun RenderMarkdown(renderOptions: RichTextRenderOptions) { RecursiveRenderMarkdownAst( astNode = astNode, contentOverride = contentOverride, inlineContentOverride = inlineContentOverride, - richTextRenderOptions = richTextRenderOptions, + richTextRenderOptions = renderOptions, richTextDecorations = richTextDecorations, markdownAnimationState = markdownAnimationState, astNodeComposer = astBlockNodeComposer, ) } + + if (richTextRenderOptions.enableRtlCompatibility && astNode.type is AstDocument) { + val measureRenderOptions = remember(richTextRenderOptions) { + richTextRenderOptions.copy( + animate = false, + enableRtlCompatibility = false, + ) + } + RtlCompatibilityDocument( + measureContent = { + BasicRichText { + RenderMarkdown(measureRenderOptions) + } + }, + content = { + BasicRichText { + RenderMarkdown(richTextRenderOptions) + } + }, + ) + } else { + RenderMarkdown(richTextRenderOptions) + } +} + +@Composable +private fun RtlCompatibilityDocument( + measureContent: @Composable () -> Unit, + content: @Composable () -> Unit, +) { + SubcomposeLayout { constraints -> + val measureConstraints = constraints.copy(minWidth = 0, minHeight = 0) + val measuredWidth = subcompose(RtlCompatibilityDocumentSlot.Measure, measureContent) + .map { measurable -> measurable.measure(measureConstraints) } + .maxOfOrNull { placeable -> placeable.width } + ?.coerceIn(constraints.minWidth, constraints.maxWidth) + ?: constraints.minWidth + + val contentConstraints = constraints.copy( + minWidth = measuredWidth, + maxWidth = measuredWidth, + minHeight = 0, + ) + val contentPlaceables = subcompose(RtlCompatibilityDocumentSlot.Content, content) + .map { measurable -> measurable.measure(contentConstraints) } + val height = contentPlaceables + .maxOfOrNull { placeable -> placeable.height } + ?.coerceIn(constraints.minHeight, constraints.maxHeight) + ?: constraints.minHeight + + layout(measuredWidth, height) { + contentPlaceables.forEach { placeable -> + placeable.placeRelative(0, 0) + } + } + } +} + +private enum class RtlCompatibilityDocumentSlot { + Measure, + Content, } /** From 65687e60fc347d2de42e216315d7409535279a38 Mon Sep 17 00:00:00 2001 From: Omer Strulovich Date: Fri, 10 Apr 2026 08:38:33 -0400 Subject: [PATCH 2/9] Fix RTL compatibility previews Enable the RTL compatibility samples to exercise compatibility mode, keep HTML block alignment explicit in compatibility mode, and avoid forcing paragraph alignment unless a containing block needs a shared side. Co-authored-by: Codex --- .../sample/RtlCompatibilityBehaviorSample.kt | 5 +++-- .../richtext/sample/RtlCompatibilitySample.kt | 4 +++- .../com/halilibo/richtext/markdown/BasicMarkdown.kt | 12 ++++++++---- .../halilibo/richtext/markdown/MarkdownRichText.kt | 3 +-- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/android-sample/src/main/java/com/zachklipp/richtext/sample/RtlCompatibilityBehaviorSample.kt b/android-sample/src/main/java/com/zachklipp/richtext/sample/RtlCompatibilityBehaviorSample.kt index 728d5e56..421ab478 100644 --- a/android-sample/src/main/java/com/zachklipp/richtext/sample/RtlCompatibilityBehaviorSample.kt +++ b/android-sample/src/main/java/com/zachklipp/richtext/sample/RtlCompatibilityBehaviorSample.kt @@ -102,7 +102,6 @@ private fun TableBehaviorPreview() { BehaviorSection( title = "Markdown table renders with RTL compatibility enabled", markdown = tableMarkdown, - richTextRenderOptions = RichTextRenderOptions(enableRtlCompatibility = true), ) } } @@ -139,7 +138,7 @@ private fun BehaviorPreviewColumn( private fun BehaviorSection( title: String, markdown: String, - richTextRenderOptions: RichTextRenderOptions = RichTextRenderOptions(), + richTextRenderOptions: RichTextRenderOptions = rtlCompatibilityOptions, ) { Column( modifier = Modifier.fillMaxWidth(), @@ -169,6 +168,8 @@ private fun BehaviorMarkdown( } } +private val rtlCompatibilityOptions = RichTextRenderOptions(enableRtlCompatibility = true) + private val englishStartingQuoteMarkdown = """ > English opens this quote. > אחר כך מופיעה עברית. diff --git a/android-sample/src/main/java/com/zachklipp/richtext/sample/RtlCompatibilitySample.kt b/android-sample/src/main/java/com/zachklipp/richtext/sample/RtlCompatibilitySample.kt index 993e1b58..9bdd2596 100644 --- a/android-sample/src/main/java/com/zachklipp/richtext/sample/RtlCompatibilitySample.kt +++ b/android-sample/src/main/java/com/zachklipp/richtext/sample/RtlCompatibilitySample.kt @@ -117,12 +117,14 @@ private fun MarkdownCaseContent( RichText(modifier = Modifier.fillMaxWidth()) { Markdown( content = markdown, - richtextRenderOptions = RichTextRenderOptions(), + richtextRenderOptions = rtlCompatibilityOptions, ) } } } +private val rtlCompatibilityOptions = RichTextRenderOptions(enableRtlCompatibility = true) + private data class PreviewVariant( val localeLabel: String, val layoutDirection: LayoutDirection, diff --git a/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/BasicMarkdown.kt b/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/BasicMarkdown.kt index ba61adad..b4a6beed 100644 --- a/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/BasicMarkdown.kt +++ b/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/BasicMarkdown.kt @@ -412,11 +412,15 @@ private val DefaultAstNodeComposer = object : AstBlockNodeComposer { } is AstHtmlBlock -> { - Text(text = richTextString { - appendInlineContent(content = InlineContent { - HtmlBlock(astNodeType.literal) + if (richTextRenderOptions.enableRtlCompatibility) { + HtmlBlock(astNodeType.literal) + } else { + Text(text = richTextString { + appendInlineContent(content = InlineContent { + HtmlBlock(astNodeType.literal) + }) }) - }) + } } is AstLinkReferenceDefinition -> { diff --git a/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/MarkdownRichText.kt b/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/MarkdownRichText.kt index 80a5602a..b84a7a64 100644 --- a/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/MarkdownRichText.kt +++ b/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/MarkdownRichText.kt @@ -97,8 +97,7 @@ internal fun RichTextScope.MarkdownRichText( renderOptions = richTextRenderOptions, sharedAnimationState = markdownAnimationState, decorations = richTextDecorations, - textAlign = LocalCompatibilityTextAlignOverride.current - ?: compatibilityDirection.toCompatibilityTextAlign(), + textAlign = LocalCompatibilityTextAlignOverride.current, textDirection = textDirection, ) } From 8157cc48b7531012610635c66b25d8bc1eab60ed Mon Sep 17 00:00:00 2001 From: Omer Strulovich Date: Fri, 10 Apr 2026 08:52:48 -0400 Subject: [PATCH 3/9] Use bounded width for RTL documents Skip the compatibility measurement slot when the parent already supplies a bounded max width, and keep the non-intrinsic measurement fallback only for unbounded layouts. Co-authored-by: Codex --- .../richtext/markdown/BasicMarkdown.kt | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/BasicMarkdown.kt b/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/BasicMarkdown.kt index b4a6beed..42be78da 100644 --- a/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/BasicMarkdown.kt +++ b/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/BasicMarkdown.kt @@ -108,14 +108,14 @@ public fun RichTextScope.BasicMarkdown( } if (richTextRenderOptions.enableRtlCompatibility && astNode.type is AstDocument) { - val measureRenderOptions = remember(richTextRenderOptions) { - richTextRenderOptions.copy( - animate = false, - enableRtlCompatibility = false, - ) - } RtlCompatibilityDocument( measureContent = { + val measureRenderOptions = remember(richTextRenderOptions) { + richTextRenderOptions.copy( + animate = false, + enableRtlCompatibility = false, + ) + } BasicRichText { RenderMarkdown(measureRenderOptions) } @@ -137,12 +137,16 @@ private fun RtlCompatibilityDocument( content: @Composable () -> Unit, ) { SubcomposeLayout { constraints -> - val measureConstraints = constraints.copy(minWidth = 0, minHeight = 0) - val measuredWidth = subcompose(RtlCompatibilityDocumentSlot.Measure, measureContent) - .map { measurable -> measurable.measure(measureConstraints) } - .maxOfOrNull { placeable -> placeable.width } - ?.coerceIn(constraints.minWidth, constraints.maxWidth) - ?: constraints.minWidth + val measuredWidth = if (constraints.hasBoundedWidth) { + constraints.maxWidth + } else { + val measureConstraints = constraints.copy(minWidth = 0, minHeight = 0) + subcompose(RtlCompatibilityDocumentSlot.Measure, measureContent) + .map { measurable -> measurable.measure(measureConstraints) } + .maxOfOrNull { placeable -> placeable.width } + ?.coerceIn(constraints.minWidth, constraints.maxWidth) + ?: constraints.minWidth + } val contentConstraints = constraints.copy( minWidth = measuredWidth, From 29e928453cfe7c21f4a67245c02672719516fec3 Mon Sep 17 00:00:00 2001 From: Omer Strulovich Date: Fri, 10 Apr 2026 09:10:56 -0400 Subject: [PATCH 4/9] Remove RTL document sizing wrapper Render markdown once in compatibility mode and leave direction handling to the existing block-level paragraph, quote, list, and code paths. This removes both intrinsic sizing and the document-level max-width wrapper. Co-authored-by: Codex --- .../richtext/markdown/BasicMarkdown.kt | 88 ++----------------- 1 file changed, 9 insertions(+), 79 deletions(-) diff --git a/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/BasicMarkdown.kt b/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/BasicMarkdown.kt index 42be78da..43b807ce 100644 --- a/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/BasicMarkdown.kt +++ b/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/BasicMarkdown.kt @@ -6,7 +6,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.layout.SubcomposeLayout import androidx.compose.ui.semantics.heading import androidx.compose.ui.semantics.semantics import com.halilibo.richtext.markdown.rtl.LocalCompatibilityTextAlignOverride @@ -34,7 +33,6 @@ import com.halilibo.richtext.markdown.node.AstTableRow import com.halilibo.richtext.markdown.node.AstText import com.halilibo.richtext.markdown.node.AstThematicBreak import com.halilibo.richtext.markdown.node.AstUnorderedList -import com.halilibo.richtext.ui.BasicRichText import com.halilibo.richtext.ui.BlockQuote import com.halilibo.richtext.ui.CodeBlock import com.halilibo.richtext.ui.FormattedList @@ -94,83 +92,15 @@ public fun RichTextScope.BasicMarkdown( ) { val markdownAnimationState = remember { MarkdownAnimationState() } - @Composable - fun RenderMarkdown(renderOptions: RichTextRenderOptions) { - RecursiveRenderMarkdownAst( - astNode = astNode, - contentOverride = contentOverride, - inlineContentOverride = inlineContentOverride, - richTextRenderOptions = renderOptions, - richTextDecorations = richTextDecorations, - markdownAnimationState = markdownAnimationState, - astNodeComposer = astBlockNodeComposer, - ) - } - - if (richTextRenderOptions.enableRtlCompatibility && astNode.type is AstDocument) { - RtlCompatibilityDocument( - measureContent = { - val measureRenderOptions = remember(richTextRenderOptions) { - richTextRenderOptions.copy( - animate = false, - enableRtlCompatibility = false, - ) - } - BasicRichText { - RenderMarkdown(measureRenderOptions) - } - }, - content = { - BasicRichText { - RenderMarkdown(richTextRenderOptions) - } - }, - ) - } else { - RenderMarkdown(richTextRenderOptions) - } -} - -@Composable -private fun RtlCompatibilityDocument( - measureContent: @Composable () -> Unit, - content: @Composable () -> Unit, -) { - SubcomposeLayout { constraints -> - val measuredWidth = if (constraints.hasBoundedWidth) { - constraints.maxWidth - } else { - val measureConstraints = constraints.copy(minWidth = 0, minHeight = 0) - subcompose(RtlCompatibilityDocumentSlot.Measure, measureContent) - .map { measurable -> measurable.measure(measureConstraints) } - .maxOfOrNull { placeable -> placeable.width } - ?.coerceIn(constraints.minWidth, constraints.maxWidth) - ?: constraints.minWidth - } - - val contentConstraints = constraints.copy( - minWidth = measuredWidth, - maxWidth = measuredWidth, - minHeight = 0, - ) - val contentPlaceables = subcompose(RtlCompatibilityDocumentSlot.Content, content) - .map { measurable -> measurable.measure(contentConstraints) } - val height = contentPlaceables - .maxOfOrNull { placeable -> placeable.height } - ?.coerceIn(constraints.minHeight, constraints.maxHeight) - ?: constraints.minHeight - - layout(measuredWidth, height) { - contentPlaceables.forEach { placeable -> - placeable.placeRelative(0, 0) - } - } - } -} - -private enum class RtlCompatibilityDocumentSlot { - Measure, - Content, + RecursiveRenderMarkdownAst( + astNode = astNode, + contentOverride = contentOverride, + inlineContentOverride = inlineContentOverride, + richTextRenderOptions = richTextRenderOptions, + richTextDecorations = richTextDecorations, + markdownAnimationState = markdownAnimationState, + astNodeComposer = astBlockNodeComposer, + ) } /** From 02f93268f1b034adf24ffe42cb7c58455f961c4d Mon Sep 17 00:00:00 2001 From: Omer Strulovich Date: Fri, 10 Apr 2026 14:34:35 -0400 Subject: [PATCH 5/9] Fill RTL markdown text only across direction boundaries Codex: Avoid expanding same-direction markdown text while still giving opposite-direction paragraphs a parent-width line box for far-edge placement. Co-authored-by: Codex --- .../halilibo/richtext/markdown/MarkdownRichText.kt | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/MarkdownRichText.kt b/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/MarkdownRichText.kt index b84a7a64..70fe2d94 100644 --- a/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/MarkdownRichText.kt +++ b/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/MarkdownRichText.kt @@ -5,11 +5,13 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.text.style.TextDirection import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import com.halilibo.richtext.markdown.rtl.LocalCompatibilityTextAlignOverride import com.halilibo.richtext.markdown.rtl.firstStrongTextDirection -import com.halilibo.richtext.markdown.rtl.toCompatibilityTextAlign import com.halilibo.richtext.markdown.rtl.toCompatibilityTextDirection import com.halilibo.richtext.markdown.node.AstBlockQuote import com.halilibo.richtext.markdown.node.AstCode @@ -85,10 +87,12 @@ internal fun RichTextScope.MarkdownRichText( null } val textDirection = compatibilityDirection.toCompatibilityTextDirection() + val layoutDirection = LocalLayoutDirection.current + val shouldFillWidth = compatibilityDirection.isOppositeOf(layoutDirection) Text( text = richText, - modifier = if (compatibilityDirection != null) { + modifier = if (shouldFillWidth) { modifier.fillMaxWidth() } else { modifier @@ -102,6 +106,12 @@ internal fun RichTextScope.MarkdownRichText( ) } +private fun TextDirection?.isOppositeOf(layoutDirection: LayoutDirection): Boolean = when (this) { + TextDirection.Ltr -> layoutDirection == LayoutDirection.Rtl + TextDirection.Rtl -> layoutDirection == LayoutDirection.Ltr + else -> false +} + private fun AstNode?.isLastInTree(): Boolean = this?.links?.parent == null || (links.next == null && links.parent.isLastInTree()) From dcaec91df3d556a1bbde12fce3085afcc60ad848 Mon Sep 17 00:00:00 2001 From: Omer Strulovich Date: Fri, 10 Apr 2026 16:17:18 -0400 Subject: [PATCH 6/9] Use RTL width helper across blocks Codex: Applies the RTL compatibility width helper to paragraph, list, quote, and code block width expansion points while keeping the helper internal to each module boundary. Co-authored-by: Codex --- .../richtext/markdown/BasicMarkdown.kt | 12 +++++-- .../richtext/markdown/MarkdownRichText.kt | 21 +++-------- .../richtext/markdown/rtl/RtlCompatibility.kt | 25 +++++++++++++ .../com/halilibo/richtext/ui/BlockQuote.kt | 9 ++++- .../com/halilibo/richtext/ui/CodeBlock.kt | 35 +++++++++++-------- .../com/halilibo/richtext/ui/FormattedList.kt | 3 +- .../richtext/ui/rtl/RtlCompatibility.kt | 29 ++++++++++++++- 7 files changed, 96 insertions(+), 38 deletions(-) diff --git a/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/BasicMarkdown.kt b/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/BasicMarkdown.kt index 43b807ce..b60e2284 100644 --- a/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/BasicMarkdown.kt +++ b/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/BasicMarkdown.kt @@ -1,6 +1,5 @@ package com.halilibo.richtext.markdown -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.text.BasicText import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider @@ -9,6 +8,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.semantics.heading import androidx.compose.ui.semantics.semantics import com.halilibo.richtext.markdown.rtl.LocalCompatibilityTextAlignOverride +import com.halilibo.richtext.markdown.rtl.fillMaxWidthForRtlCompatibility import com.halilibo.richtext.markdown.rtl.firstStrongTextDirectionInFirstLine import com.halilibo.richtext.markdown.rtl.toCompatibilityTextAlign import com.halilibo.richtext.markdown.rtl.toCompatibilityTextDirection @@ -328,7 +328,10 @@ private val DefaultAstNodeComposer = object : AstBlockNodeComposer { text = astNodeType.literal.trim(), markdownAnimationState = markdownAnimationState, richTextRenderOptions = richTextRenderOptions, - modifier = if (compatibilityDirection != null) Modifier.fillMaxWidth() else Modifier, + modifier = Modifier.fillMaxWidthForRtlCompatibility( + renderOptions = richTextRenderOptions, + contentDirection = compatibilityDirection, + ), textDirection = compatibilityDirection.toCompatibilityTextDirection(), textAlign = compatibilityDirection.toCompatibilityTextAlign(), ) @@ -339,7 +342,10 @@ private val DefaultAstNodeComposer = object : AstBlockNodeComposer { text = astNodeType.literal.trim(), markdownAnimationState = markdownAnimationState, richTextRenderOptions = richTextRenderOptions, - modifier = if (compatibilityDirection != null) Modifier.fillMaxWidth() else Modifier, + modifier = Modifier.fillMaxWidthForRtlCompatibility( + renderOptions = richTextRenderOptions, + contentDirection = compatibilityDirection, + ), textDirection = compatibilityDirection.toCompatibilityTextDirection(), textAlign = compatibilityDirection.toCompatibilityTextAlign(), ) diff --git a/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/MarkdownRichText.kt b/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/MarkdownRichText.kt index 70fe2d94..e9b174e7 100644 --- a/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/MarkdownRichText.kt +++ b/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/MarkdownRichText.kt @@ -5,12 +5,10 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalLayoutDirection -import androidx.compose.ui.text.style.TextDirection import androidx.compose.ui.unit.IntSize -import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import com.halilibo.richtext.markdown.rtl.LocalCompatibilityTextAlignOverride +import com.halilibo.richtext.markdown.rtl.fillMaxWidthForRtlCompatibility import com.halilibo.richtext.markdown.rtl.firstStrongTextDirection import com.halilibo.richtext.markdown.rtl.toCompatibilityTextDirection import com.halilibo.richtext.markdown.node.AstBlockQuote @@ -87,16 +85,13 @@ internal fun RichTextScope.MarkdownRichText( null } val textDirection = compatibilityDirection.toCompatibilityTextDirection() - val layoutDirection = LocalLayoutDirection.current - val shouldFillWidth = compatibilityDirection.isOppositeOf(layoutDirection) Text( text = richText, - modifier = if (shouldFillWidth) { - modifier.fillMaxWidth() - } else { - modifier - }, + modifier = modifier.fillMaxWidthForRtlCompatibility( + renderOptions = richTextRenderOptions, + contentDirection = compatibilityDirection, + ), isLeafText = astNode.isLastInTree(), renderOptions = richTextRenderOptions, sharedAnimationState = markdownAnimationState, @@ -106,12 +101,6 @@ internal fun RichTextScope.MarkdownRichText( ) } -private fun TextDirection?.isOppositeOf(layoutDirection: LayoutDirection): Boolean = when (this) { - TextDirection.Ltr -> layoutDirection == LayoutDirection.Rtl - TextDirection.Rtl -> layoutDirection == LayoutDirection.Ltr - else -> false -} - private fun AstNode?.isLastInTree(): Boolean = this?.links?.parent == null || (links.next == null && links.parent.isLastInTree()) diff --git a/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/rtl/RtlCompatibility.kt b/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/rtl/RtlCompatibility.kt index 82c71c1d..8ade2c83 100644 --- a/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/rtl/RtlCompatibility.kt +++ b/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/rtl/RtlCompatibility.kt @@ -1,8 +1,13 @@ package com.halilibo.richtext.markdown.rtl +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable import androidx.compose.runtime.compositionLocalOf +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextDirection +import androidx.compose.ui.unit.LayoutDirection import com.halilibo.richtext.markdown.childrenSequence import com.halilibo.richtext.markdown.node.AstCode import com.halilibo.richtext.markdown.node.AstFencedCodeBlock @@ -13,6 +18,7 @@ import com.halilibo.richtext.markdown.node.AstIndentedCodeBlock import com.halilibo.richtext.markdown.node.AstNode import com.halilibo.richtext.markdown.node.AstSoftLineBreak import com.halilibo.richtext.markdown.node.AstText +import com.halilibo.richtext.ui.string.RichTextRenderOptions import kotlin.text.CharDirectionality /** @@ -78,6 +84,25 @@ internal fun TextDirection?.toCompatibilityTextDirection(): TextDirection? = whe else -> null } +@Composable +internal fun Modifier.fillMaxWidthForRtlCompatibility( + renderOptions: RichTextRenderOptions, + contentDirection: TextDirection?, +): Modifier = if ( + renderOptions.enableRtlCompatibility && + contentDirection.isOppositeOf(LocalLayoutDirection.current) +) { + fillMaxWidth() +} else { + this +} + +private fun TextDirection?.isOppositeOf(layoutDirection: LayoutDirection): Boolean = when (this) { + TextDirection.Ltr -> layoutDirection == LayoutDirection.Rtl + TextDirection.Rtl -> layoutDirection == LayoutDirection.Ltr + else -> false +} + /** * Scans text for the first strong bidi character and returns its direction. * diff --git a/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/BlockQuote.kt b/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/BlockQuote.kt index df022009..c6b6cd2f 100644 --- a/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/BlockQuote.kt +++ b/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/BlockQuote.kt @@ -106,7 +106,14 @@ public interface BlockQuoteGutter { val contentsConstraints = constraints .offset(horizontal = -gutterWidth) .let { - if (shouldFillWidthForRtlCompatibility(richTextRenderOptions, gutterDirection) && it.hasBoundedWidth) { + if ( + shouldFillWidthForRtlCompatibility( + renderOptions = richTextRenderOptions, + contentDirection = gutterDirection, + layoutDirection = layoutDirection, + ) && + it.hasBoundedWidth + ) { it.copy(minWidth = it.maxWidth) } else { it diff --git a/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/CodeBlock.kt b/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/CodeBlock.kt index 82805186..89318897 100644 --- a/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/CodeBlock.kt +++ b/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/CodeBlock.kt @@ -2,7 +2,6 @@ package com.halilibo.richtext.ui import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable @@ -17,7 +16,7 @@ import androidx.compose.ui.text.style.TextDirection import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.sp -import com.halilibo.richtext.ui.rtl.shouldFillWidthForRtlCompatibility +import com.halilibo.richtext.ui.rtl.fillMaxWidthForRtlCompatibility import com.halilibo.richtext.ui.string.MarkdownAnimationState import com.halilibo.richtext.ui.string.RichTextRenderOptions @@ -72,6 +71,8 @@ internal fun CodeBlockStyle.resolveDefaults() = CodeBlockStyle( textAlign: TextAlign? = null, textDirection: TextDirection? = null, ) { + val compatibilityDirection = textAlign.toCompatibilityDirection() + CodeBlock( wordWrap = wordWrap, markdownAnimationState = markdownAnimationState, @@ -82,11 +83,10 @@ internal fun CodeBlockStyle.resolveDefaults() = CodeBlockStyle( ) { Text( text = text, - modifier = if (textAlign != null) { - Modifier.fillMaxWidth() - } else { - Modifier - }, + modifier = Modifier.fillMaxWidthForRtlCompatibility( + renderOptions = richTextRenderOptions, + contentDirection = compatibilityDirection, + ), textAlign = textAlign, textDirection = textDirection, ) @@ -115,6 +115,7 @@ internal fun CodeBlockStyle.resolveDefaults() = CodeBlockStyle( codeBlockStyle.padding!!.toDp() } val resolvedWordWrap = wordWrap ?: codeBlockStyle.wordWrap!! + val compatibilityDirection = textAlign.toCompatibilityDirection() val alpha = rememberMarkdownFade(richTextRenderOptions, markdownAnimationState) CodeBlockLayout( @@ -123,14 +124,12 @@ internal fun CodeBlockStyle.resolveDefaults() = CodeBlockStyle( Box( modifier = layoutModifier .graphicsLayer{ this.alpha = alpha.value } - .then(modifier) - .let { - if (richTextRenderOptions.enableRtlCompatibility && textAlign != null) { - it.fillMaxWidth() - } else { - it - } - } + .then( + modifier.fillMaxWidthForRtlCompatibility( + renderOptions = richTextRenderOptions, + contentDirection = compatibilityDirection, + ) + ) .then(blockModifier) .padding(blockPadding) ) { @@ -149,3 +148,9 @@ internal expect fun RichTextScope.CodeBlockLayout( wordWrap: Boolean, children: @Composable RichTextScope.(Modifier) -> Unit ) + +private fun TextAlign?.toCompatibilityDirection(): TextDirection? = when (this) { + TextAlign.Left -> TextDirection.Ltr + TextAlign.Right -> TextDirection.Rtl + else -> null +} diff --git a/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/FormattedList.kt b/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/FormattedList.kt index 3ee5dece..3d68aa2b 100644 --- a/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/FormattedList.kt +++ b/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/FormattedList.kt @@ -324,10 +324,9 @@ private val LocalOrderedMarkerTextDirection = compositionLocalOf val listWidth = if ( - richTextRenderOptions.enableRtlCompatibility && constraints.hasBoundedWidth && constraints.maxWidth != Constraints.Infinity && - shouldFillWidthForRtlCompatibility(richTextRenderOptions, markerDirection) + shouldFillWidthForRtlCompatibility(richTextRenderOptions, markerDirection, layoutDirection) ) { constraints.maxWidth } else { diff --git a/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/rtl/RtlCompatibility.kt b/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/rtl/RtlCompatibility.kt index 267fe5a0..90c72538 100644 --- a/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/rtl/RtlCompatibility.kt +++ b/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/rtl/RtlCompatibility.kt @@ -1,5 +1,9 @@ package com.halilibo.richtext.ui.rtl +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.text.style.TextDirection import androidx.compose.ui.unit.LayoutDirection import com.halilibo.richtext.ui.string.RichTextRenderOptions @@ -7,7 +11,24 @@ import com.halilibo.richtext.ui.string.RichTextRenderOptions internal fun shouldFillWidthForRtlCompatibility( renderOptions: RichTextRenderOptions, contentDirection: TextDirection?, -): Boolean = renderOptions.enableRtlCompatibility && contentDirection != null + layoutDirection: LayoutDirection, +): Boolean = renderOptions.enableRtlCompatibility && contentDirection.isOppositeOf(layoutDirection) + +@Composable +internal fun Modifier.fillMaxWidthForRtlCompatibility( + renderOptions: RichTextRenderOptions, + contentDirection: TextDirection?, +): Modifier = if ( + shouldFillWidthForRtlCompatibility( + renderOptions = renderOptions, + contentDirection = contentDirection, + layoutDirection = LocalLayoutDirection.current, + ) +) { + fillMaxWidth() +} else { + this +} internal fun resolveRtlCompatibleLayoutDirection( contentDirection: TextDirection?, @@ -17,3 +38,9 @@ internal fun resolveRtlCompatibleLayoutDirection( TextDirection.Rtl -> LayoutDirection.Rtl else -> systemDirection } + +private fun TextDirection?.isOppositeOf(layoutDirection: LayoutDirection): Boolean = when (this) { + TextDirection.Ltr -> layoutDirection == LayoutDirection.Rtl + TextDirection.Rtl -> layoutDirection == LayoutDirection.Ltr + else -> false +} From f6b6511edfd9de9c5e5bee921e419551be3566a7 Mon Sep 17 00:00:00 2001 From: Omer Strulovich Date: Fri, 10 Apr 2026 16:57:54 -0400 Subject: [PATCH 7/9] Use boolean for RTL width helpers Codex: Pass the RTL compatibility flag directly to width helpers and keep UI RTL conversion helpers grouped in the UI RTL file. Co-authored-by: Codex --- .../halilibo/richtext/markdown/BasicMarkdown.kt | 4 ++-- .../richtext/markdown/MarkdownRichText.kt | 2 +- .../richtext/markdown/rtl/RtlCompatibility.kt | 5 ++--- .../com/halilibo/richtext/ui/BlockQuote.kt | 2 +- .../kotlin/com/halilibo/richtext/ui/CodeBlock.kt | 11 +++-------- .../com/halilibo/richtext/ui/FormattedList.kt | 8 ++++---- .../halilibo/richtext/ui/rtl/RtlCompatibility.kt | 16 +++++++++++----- 7 files changed, 24 insertions(+), 24 deletions(-) diff --git a/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/BasicMarkdown.kt b/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/BasicMarkdown.kt index b60e2284..b532dcd7 100644 --- a/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/BasicMarkdown.kt +++ b/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/BasicMarkdown.kt @@ -329,7 +329,7 @@ private val DefaultAstNodeComposer = object : AstBlockNodeComposer { markdownAnimationState = markdownAnimationState, richTextRenderOptions = richTextRenderOptions, modifier = Modifier.fillMaxWidthForRtlCompatibility( - renderOptions = richTextRenderOptions, + enableRtlCompatibility = richTextRenderOptions.enableRtlCompatibility, contentDirection = compatibilityDirection, ), textDirection = compatibilityDirection.toCompatibilityTextDirection(), @@ -343,7 +343,7 @@ private val DefaultAstNodeComposer = object : AstBlockNodeComposer { markdownAnimationState = markdownAnimationState, richTextRenderOptions = richTextRenderOptions, modifier = Modifier.fillMaxWidthForRtlCompatibility( - renderOptions = richTextRenderOptions, + enableRtlCompatibility = richTextRenderOptions.enableRtlCompatibility, contentDirection = compatibilityDirection, ), textDirection = compatibilityDirection.toCompatibilityTextDirection(), diff --git a/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/MarkdownRichText.kt b/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/MarkdownRichText.kt index e9b174e7..16219770 100644 --- a/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/MarkdownRichText.kt +++ b/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/MarkdownRichText.kt @@ -89,7 +89,7 @@ internal fun RichTextScope.MarkdownRichText( Text( text = richText, modifier = modifier.fillMaxWidthForRtlCompatibility( - renderOptions = richTextRenderOptions, + enableRtlCompatibility = richTextRenderOptions.enableRtlCompatibility, contentDirection = compatibilityDirection, ), isLeafText = astNode.isLastInTree(), diff --git a/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/rtl/RtlCompatibility.kt b/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/rtl/RtlCompatibility.kt index 8ade2c83..fe7c3aba 100644 --- a/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/rtl/RtlCompatibility.kt +++ b/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/rtl/RtlCompatibility.kt @@ -18,7 +18,6 @@ import com.halilibo.richtext.markdown.node.AstIndentedCodeBlock import com.halilibo.richtext.markdown.node.AstNode import com.halilibo.richtext.markdown.node.AstSoftLineBreak import com.halilibo.richtext.markdown.node.AstText -import com.halilibo.richtext.ui.string.RichTextRenderOptions import kotlin.text.CharDirectionality /** @@ -86,10 +85,10 @@ internal fun TextDirection?.toCompatibilityTextDirection(): TextDirection? = whe @Composable internal fun Modifier.fillMaxWidthForRtlCompatibility( - renderOptions: RichTextRenderOptions, + enableRtlCompatibility: Boolean, contentDirection: TextDirection?, ): Modifier = if ( - renderOptions.enableRtlCompatibility && + enableRtlCompatibility && contentDirection.isOppositeOf(LocalLayoutDirection.current) ) { fillMaxWidth() diff --git a/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/BlockQuote.kt b/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/BlockQuote.kt index c6b6cd2f..8741ce1d 100644 --- a/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/BlockQuote.kt +++ b/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/BlockQuote.kt @@ -108,7 +108,7 @@ public interface BlockQuoteGutter { .let { if ( shouldFillWidthForRtlCompatibility( - renderOptions = richTextRenderOptions, + enableRtlCompatibility = richTextRenderOptions.enableRtlCompatibility, contentDirection = gutterDirection, layoutDirection = layoutDirection, ) && diff --git a/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/CodeBlock.kt b/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/CodeBlock.kt index 89318897..e4f21547 100644 --- a/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/CodeBlock.kt +++ b/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/CodeBlock.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.sp import com.halilibo.richtext.ui.rtl.fillMaxWidthForRtlCompatibility +import com.halilibo.richtext.ui.rtl.toCompatibilityDirection import com.halilibo.richtext.ui.string.MarkdownAnimationState import com.halilibo.richtext.ui.string.RichTextRenderOptions @@ -84,7 +85,7 @@ internal fun CodeBlockStyle.resolveDefaults() = CodeBlockStyle( Text( text = text, modifier = Modifier.fillMaxWidthForRtlCompatibility( - renderOptions = richTextRenderOptions, + enableRtlCompatibility = richTextRenderOptions.enableRtlCompatibility, contentDirection = compatibilityDirection, ), textAlign = textAlign, @@ -126,7 +127,7 @@ internal fun CodeBlockStyle.resolveDefaults() = CodeBlockStyle( .graphicsLayer{ this.alpha = alpha.value } .then( modifier.fillMaxWidthForRtlCompatibility( - renderOptions = richTextRenderOptions, + enableRtlCompatibility = richTextRenderOptions.enableRtlCompatibility, contentDirection = compatibilityDirection, ) ) @@ -148,9 +149,3 @@ internal expect fun RichTextScope.CodeBlockLayout( wordWrap: Boolean, children: @Composable RichTextScope.(Modifier) -> Unit ) - -private fun TextAlign?.toCompatibilityDirection(): TextDirection? = when (this) { - TextAlign.Left -> TextDirection.Ltr - TextAlign.Right -> TextDirection.Rtl - else -> null -} diff --git a/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/FormattedList.kt b/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/FormattedList.kt index 3d68aa2b..13edca7e 100644 --- a/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/FormattedList.kt +++ b/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/FormattedList.kt @@ -241,7 +241,7 @@ private val LocalOrderedMarkerTextDirection = compositionLocalOf count = items.size, itemSpacing = itemSpacing, prefixPadding = PaddingValues(start = markerIndent, end = contentsIndent), - richTextRenderOptions = richTextRenderOptions, + enableRtlCompatibility = richTextRenderOptions.enableRtlCompatibility, markerDirection = markerDirection, prefixForIndex = { index -> val alpha = rememberMarkdownFade(richTextRenderOptions, markdownAnimationState) @@ -272,7 +272,7 @@ private val LocalOrderedMarkerTextDirection = compositionLocalOf count: Int, itemSpacing: Dp, prefixPadding: PaddingValues, - richTextRenderOptions: RichTextRenderOptions, + enableRtlCompatibility: Boolean, markerDirection: TextDirection?, prefixForIndex: @Composable (index: Int) -> Unit, itemForIndex: @Composable (index: Int) -> Unit @@ -326,7 +326,7 @@ private val LocalOrderedMarkerTextDirection = compositionLocalOf if ( constraints.hasBoundedWidth && constraints.maxWidth != Constraints.Infinity && - shouldFillWidthForRtlCompatibility(richTextRenderOptions, markerDirection, layoutDirection) + shouldFillWidthForRtlCompatibility(enableRtlCompatibility, markerDirection, layoutDirection) ) { constraints.maxWidth } else { @@ -347,7 +347,7 @@ private val LocalOrderedMarkerTextDirection = compositionLocalOf val prefix = prefixPlaceables[i] val item = itemPlaceables[i] val rowHeight = max(prefix.height, item.height) + itemSpacing.roundToPx() - if (richTextRenderOptions.enableRtlCompatibility && markerDirection != null) { + if (enableRtlCompatibility && markerDirection != null) { if (markerOnRight) { item.place(0, y) prefix.place(listWidth - prefix.width, y) diff --git a/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/rtl/RtlCompatibility.kt b/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/rtl/RtlCompatibility.kt index 90c72538..18c0e672 100644 --- a/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/rtl/RtlCompatibility.kt +++ b/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/rtl/RtlCompatibility.kt @@ -4,23 +4,23 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextDirection import androidx.compose.ui.unit.LayoutDirection -import com.halilibo.richtext.ui.string.RichTextRenderOptions internal fun shouldFillWidthForRtlCompatibility( - renderOptions: RichTextRenderOptions, + enableRtlCompatibility: Boolean, contentDirection: TextDirection?, layoutDirection: LayoutDirection, -): Boolean = renderOptions.enableRtlCompatibility && contentDirection.isOppositeOf(layoutDirection) +): Boolean = enableRtlCompatibility && contentDirection.isOppositeOf(layoutDirection) @Composable internal fun Modifier.fillMaxWidthForRtlCompatibility( - renderOptions: RichTextRenderOptions, + enableRtlCompatibility: Boolean, contentDirection: TextDirection?, ): Modifier = if ( shouldFillWidthForRtlCompatibility( - renderOptions = renderOptions, + enableRtlCompatibility = enableRtlCompatibility, contentDirection = contentDirection, layoutDirection = LocalLayoutDirection.current, ) @@ -39,6 +39,12 @@ internal fun resolveRtlCompatibleLayoutDirection( else -> systemDirection } +internal fun TextAlign?.toCompatibilityDirection(): TextDirection? = when (this) { + TextAlign.Left -> TextDirection.Ltr + TextAlign.Right -> TextDirection.Rtl + else -> null +} + private fun TextDirection?.isOppositeOf(layoutDirection: LayoutDirection): Boolean = when (this) { TextDirection.Ltr -> layoutDirection == LayoutDirection.Rtl TextDirection.Rtl -> layoutDirection == LayoutDirection.Ltr From 057651d37d3ad45f3b302d8712c92e8b7a7b1cc0 Mon Sep 17 00:00:00 2001 From: Omer Strulovich Date: Fri, 10 Apr 2026 22:12:06 -0400 Subject: [PATCH 8/9] Remove RTL sample changes from compat PR Co-authored-by: Codex --- .../sample/RtlCompatibilityBehaviorSample.kt | 13 +++---------- .../richtext/sample/RtlCompatibilitySample.kt | 4 +--- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/android-sample/src/main/java/com/zachklipp/richtext/sample/RtlCompatibilityBehaviorSample.kt b/android-sample/src/main/java/com/zachklipp/richtext/sample/RtlCompatibilityBehaviorSample.kt index 421ab478..952a2c93 100644 --- a/android-sample/src/main/java/com/zachklipp/richtext/sample/RtlCompatibilityBehaviorSample.kt +++ b/android-sample/src/main/java/com/zachklipp/richtext/sample/RtlCompatibilityBehaviorSample.kt @@ -100,7 +100,7 @@ private fun TableBehaviorPreview() { BehaviorPreviewSurface { BehaviorPreviewColumn { BehaviorSection( - title = "Markdown table renders with RTL compatibility enabled", + title = "Markdown table renders in RTL layout", markdown = tableMarkdown, ) } @@ -138,7 +138,6 @@ private fun BehaviorPreviewColumn( private fun BehaviorSection( title: String, markdown: String, - richTextRenderOptions: RichTextRenderOptions = rtlCompatibilityOptions, ) { Column( modifier = Modifier.fillMaxWidth(), @@ -148,28 +147,22 @@ private fun BehaviorSection( text = title, style = MaterialTheme.typography.labelLarge, ) - BehaviorMarkdown( - markdown = markdown, - richTextRenderOptions = richTextRenderOptions, - ) + BehaviorMarkdown(markdown = markdown) } } @Composable private fun BehaviorMarkdown( markdown: String, - richTextRenderOptions: RichTextRenderOptions, ) { RichText(modifier = Modifier.fillMaxWidth()) { Markdown( content = markdown, - richtextRenderOptions = richTextRenderOptions, + richtextRenderOptions = RichTextRenderOptions(), ) } } -private val rtlCompatibilityOptions = RichTextRenderOptions(enableRtlCompatibility = true) - private val englishStartingQuoteMarkdown = """ > English opens this quote. > אחר כך מופיעה עברית. diff --git a/android-sample/src/main/java/com/zachklipp/richtext/sample/RtlCompatibilitySample.kt b/android-sample/src/main/java/com/zachklipp/richtext/sample/RtlCompatibilitySample.kt index 9bdd2596..993e1b58 100644 --- a/android-sample/src/main/java/com/zachklipp/richtext/sample/RtlCompatibilitySample.kt +++ b/android-sample/src/main/java/com/zachklipp/richtext/sample/RtlCompatibilitySample.kt @@ -117,14 +117,12 @@ private fun MarkdownCaseContent( RichText(modifier = Modifier.fillMaxWidth()) { Markdown( content = markdown, - richtextRenderOptions = rtlCompatibilityOptions, + richtextRenderOptions = RichTextRenderOptions(), ) } } } -private val rtlCompatibilityOptions = RichTextRenderOptions(enableRtlCompatibility = true) - private data class PreviewVariant( val localeLabel: String, val layoutDirection: LayoutDirection, From 025811376f86eb6203bf2db4dc12326af5a6ad9e Mon Sep 17 00:00:00 2001 From: Omer Strulovich Date: Fri, 10 Apr 2026 22:29:29 -0400 Subject: [PATCH 9/9] Remove HTML block RTL compat branch Co-authored-by: Codex --- .../com/halilibo/richtext/markdown/BasicMarkdown.kt | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/BasicMarkdown.kt b/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/BasicMarkdown.kt index b532dcd7..6e600abe 100644 --- a/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/BasicMarkdown.kt +++ b/richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/BasicMarkdown.kt @@ -352,15 +352,11 @@ private val DefaultAstNodeComposer = object : AstBlockNodeComposer { } is AstHtmlBlock -> { - if (richTextRenderOptions.enableRtlCompatibility) { - HtmlBlock(astNodeType.literal) - } else { - Text(text = richTextString { - appendInlineContent(content = InlineContent { - HtmlBlock(astNodeType.literal) - }) + Text(text = richTextString { + appendInlineContent(content = InlineContent { + HtmlBlock(astNodeType.literal) }) - } + }) } is AstLinkReferenceDefinition -> {