@@ -13,38 +13,39 @@ import androidx.compose.ui.node.LayoutNode
1313import androidx.compose.ui.text.TextLayoutResult
1414import kotlin.math.roundToInt
1515
16- internal class ComposeTextLayout (
17- internal val layout : TextLayoutResult ,
18- private val hasFillModifier : Boolean ,
19- ) : TextLayout {
16+ internal class ComposeTextLayout (internal val layout : TextLayoutResult ) : TextLayout {
2017 override val lineCount: Int
2118 get() = layout.lineCount
2219
2320 override val dominantTextColor: Int?
2421 get() = null
2522
26- override fun getPrimaryHorizontal (line : Int , offset : Int ): Float {
27- val horizontalPos = layout.getHorizontalPosition(offset, usePrimaryDirection = true )
28- // when there's no `fill` modifier on a Text composable, compose still thinks that there's
29- // one and wrongly calculates horizontal position relative to node's start, not text's start
30- // for some reason. This is only the case for single-line text (multiline works fien).
31- // So we subtract line's left to get the correct position
32- return if (! hasFillModifier && lineCount == 1 ) {
33- horizontalPos - layout.getLineLeft(line)
34- } else {
35- horizontalPos
23+ /* *
24+ * The paragraph may be laid out with a wider width (constraint maxWidth) than the actual node
25+ * (layout result size). When that happens, getLineLeft/getLineRight return positions in the
26+ * paragraph coordinate system, which don't match the node's bounds. In that case, text alignment
27+ * has no visible effect, so we fall back to using line width starting from x=0.
28+ */
29+ private val paragraphWidthExceedsNode: Boolean
30+ get() = layout.multiParagraph.width > layout.size.width
31+
32+ override fun getLineLeft (line : Int ): Float {
33+ if (paragraphWidthExceedsNode) {
34+ return 0f
3635 }
36+ return layout.getLineLeft(line)
3737 }
3838
39- override fun getEllipsisCount (line : Int ): Int = if (layout.isLineEllipsized(line)) 1 else 0
40-
41- override fun getLineVisibleEnd (line : Int ): Int = layout.getLineEnd(line, visibleEnd = true )
39+ override fun getLineRight (line : Int ): Float {
40+ if (paragraphWidthExceedsNode) {
41+ return layout.multiParagraph.getLineWidth(line)
42+ }
43+ return layout.getLineRight(line)
44+ }
4245
4346 override fun getLineTop (line : Int ): Int = layout.getLineTop(line).roundToInt()
4447
4548 override fun getLineBottom (line : Int ): Int = layout.getLineBottom(line).roundToInt()
46-
47- override fun getLineStart (line : Int ): Int = layout.getLineStart(line)
4849}
4950
5051// TODO: probably most of the below we can do via bytecode instrumentation and speed up at runtime
@@ -92,46 +93,31 @@ internal fun Painter.isMaskable(): Boolean {
9293 ! className.contains(" Brush" )
9394}
9495
95- internal data class TextAttributes (val color : Color ? , val hasFillModifier : Boolean )
96-
9796/* *
9897 * This method is necessary to mask text in Compose.
9998 *
10099 * We heuristically look up for classes that have a [Text] modifier, usually they all have a `Text`
101100 * string in their name, e.g. TextStringSimpleElement or TextAnnotatedStringElement. We then get the
102101 * color from the modifier, to be able to mask it with the correct color.
103102 *
104- * We also look up for classes that have a [Fill] modifier, usually they all have a `Fill` string in
105- * their name, e.g. FillElement. This is necessary to workaround a Compose bug where single-line
106- * text composable without a `fill` modifier still thinks that there's one and wrongly calculates
107- * horizontal position.
108- *
109103 * We also add special proguard rules to keep the `Text` class names and their `color` member.
110104 */
111- internal fun LayoutNode.findTextAttributes (): TextAttributes {
105+ internal fun LayoutNode.findTextColor (): Color ? {
112106 val modifierInfos = getModifierInfo()
113- var color: Color ? = null
114- var hasFillModifier = false
115107 for (index in modifierInfos.indices) {
116108 val modifier = modifierInfos[index].modifier
117109 val modifierClassName = modifier::class .java.name
118110 if (modifierClassName.contains(" Text" )) {
119- color =
120- try {
121- (modifier::class
122- .java
123- .getDeclaredField(" color" )
124- .apply { isAccessible = true }
125- .get(modifier) as ? ColorProducer )
126- ?.invoke()
127- } catch (e: Throwable ) {
128- null
129- }
130- } else if (modifierClassName.contains(" Fill" )) {
131- hasFillModifier = true
111+ return try {
112+ (modifier::class .java.getDeclaredField(" color" ).apply { isAccessible = true }.get(modifier)
113+ as ? ColorProducer )
114+ ?.invoke()
115+ } catch (e: Throwable ) {
116+ null
117+ }
132118 }
133119 }
134- return TextAttributes (color, hasFillModifier)
120+ return null
135121}
136122
137123/* *
0 commit comments