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..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 @@ -1,8 +1,5 @@ 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 @@ -11,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 @@ -35,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 @@ -95,29 +92,15 @@ 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 { - RecursiveRenderMarkdownAst( - astNode = astNode, - contentOverride = contentOverride, - inlineContentOverride = inlineContentOverride, - richTextRenderOptions = richTextRenderOptions, - richTextDecorations = richTextDecorations, - markdownAnimationState = markdownAnimationState, - astNodeComposer = astBlockNodeComposer, - ) - } + RecursiveRenderMarkdownAst( + astNode = astNode, + contentOverride = contentOverride, + inlineContentOverride = inlineContentOverride, + richTextRenderOptions = richTextRenderOptions, + richTextDecorations = richTextDecorations, + markdownAnimationState = markdownAnimationState, + astNodeComposer = astBlockNodeComposer, + ) } /** @@ -345,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( + enableRtlCompatibility = richTextRenderOptions.enableRtlCompatibility, + contentDirection = compatibilityDirection, + ), textDirection = compatibilityDirection.toCompatibilityTextDirection(), textAlign = compatibilityDirection.toCompatibilityTextAlign(), ) @@ -356,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( + enableRtlCompatibility = richTextRenderOptions.enableRtlCompatibility, + 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 80a5602a..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 @@ -8,8 +8,8 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.IntSize 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.toCompatibilityTextAlign import com.halilibo.richtext.markdown.rtl.toCompatibilityTextDirection import com.halilibo.richtext.markdown.node.AstBlockQuote import com.halilibo.richtext.markdown.node.AstCode @@ -88,17 +88,15 @@ internal fun RichTextScope.MarkdownRichText( Text( text = richText, - modifier = if (compatibilityDirection != null) { - modifier.fillMaxWidth() - } else { - modifier - }, + modifier = modifier.fillMaxWidthForRtlCompatibility( + enableRtlCompatibility = richTextRenderOptions.enableRtlCompatibility, + contentDirection = compatibilityDirection, + ), isLeafText = astNode.isLastInTree(), renderOptions = richTextRenderOptions, sharedAnimationState = markdownAnimationState, decorations = richTextDecorations, - textAlign = LocalCompatibilityTextAlignOverride.current - ?: compatibilityDirection.toCompatibilityTextAlign(), + textAlign = LocalCompatibilityTextAlignOverride.current, textDirection = textDirection, ) } 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..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 @@ -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 @@ -78,6 +83,25 @@ internal fun TextDirection?.toCompatibilityTextDirection(): TextDirection? = whe else -> null } +@Composable +internal fun Modifier.fillMaxWidthForRtlCompatibility( + enableRtlCompatibility: Boolean, + contentDirection: TextDirection?, +): Modifier = if ( + 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..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 @@ -106,7 +106,14 @@ public interface BlockQuoteGutter { val contentsConstraints = constraints .offset(horizontal = -gutterWidth) .let { - if (shouldFillWidthForRtlCompatibility(richTextRenderOptions, gutterDirection) && it.hasBoundedWidth) { + if ( + shouldFillWidthForRtlCompatibility( + enableRtlCompatibility = richTextRenderOptions.enableRtlCompatibility, + 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..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 @@ -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,8 @@ 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.rtl.toCompatibilityDirection import com.halilibo.richtext.ui.string.MarkdownAnimationState import com.halilibo.richtext.ui.string.RichTextRenderOptions @@ -72,6 +72,8 @@ internal fun CodeBlockStyle.resolveDefaults() = CodeBlockStyle( textAlign: TextAlign? = null, textDirection: TextDirection? = null, ) { + val compatibilityDirection = textAlign.toCompatibilityDirection() + CodeBlock( wordWrap = wordWrap, markdownAnimationState = markdownAnimationState, @@ -82,11 +84,10 @@ internal fun CodeBlockStyle.resolveDefaults() = CodeBlockStyle( ) { Text( text = text, - modifier = if (textAlign != null) { - Modifier.fillMaxWidth() - } else { - Modifier - }, + modifier = Modifier.fillMaxWidthForRtlCompatibility( + enableRtlCompatibility = richTextRenderOptions.enableRtlCompatibility, + contentDirection = compatibilityDirection, + ), textAlign = textAlign, textDirection = textDirection, ) @@ -115,6 +116,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 +125,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( + enableRtlCompatibility = richTextRenderOptions.enableRtlCompatibility, + contentDirection = compatibilityDirection, + ) + ) .then(blockModifier) .padding(blockPadding) ) { 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..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 @@ -324,10 +324,9 @@ private val LocalOrderedMarkerTextDirection = compositionLocalOf val listWidth = if ( - richTextRenderOptions.enableRtlCompatibility && constraints.hasBoundedWidth && constraints.maxWidth != Constraints.Infinity && - shouldFillWidthForRtlCompatibility(richTextRenderOptions, markerDirection) + shouldFillWidthForRtlCompatibility(enableRtlCompatibility, markerDirection, layoutDirection) ) { constraints.maxWidth } else { @@ -348,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 267fe5a0..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 @@ -1,13 +1,34 @@ 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.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?, -): Boolean = renderOptions.enableRtlCompatibility && contentDirection != null + layoutDirection: LayoutDirection, +): Boolean = enableRtlCompatibility && contentDirection.isOppositeOf(layoutDirection) + +@Composable +internal fun Modifier.fillMaxWidthForRtlCompatibility( + enableRtlCompatibility: Boolean, + contentDirection: TextDirection?, +): Modifier = if ( + shouldFillWidthForRtlCompatibility( + enableRtlCompatibility = enableRtlCompatibility, + contentDirection = contentDirection, + layoutDirection = LocalLayoutDirection.current, + ) +) { + fillMaxWidth() +} else { + this +} internal fun resolveRtlCompatibleLayoutDirection( contentDirection: TextDirection?, @@ -17,3 +38,15 @@ internal fun resolveRtlCompatibleLayoutDirection( TextDirection.Rtl -> LayoutDirection.Rtl 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 + else -> false +}