Skip to content

Commit 521d478

Browse files
astreetfacebook-github-bot
authored andcommitted
Add flag for caching text truncation
Reviewed By: adityasharat Differential Revision: D75162864 fbshipit-source-id: 92f0d918326e84e146172769977acdd2f73c38fe
1 parent a6e671f commit 521d478

3 files changed

Lines changed: 79 additions & 9 deletions

File tree

litho-rendercore-text/src/main/java/com/facebook/rendercore/text/RichTextPrimitive.kt

Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.facebook.rendercore.text
1818

19+
import android.graphics.Rect
1920
import androidx.core.util.component1
2021
import androidx.core.util.component2
2122
import com.facebook.rendercore.RenderCoreConfig
@@ -36,10 +37,12 @@ fun RichTextPrimitive(
3637
id: Long,
3738
text: CharSequence,
3839
style: TextStyle,
39-
usePerformantTruncation: Boolean = RenderCoreConfig.usePerformantTruncation
40+
usePerformantTruncation: Boolean = RenderCoreConfig.usePerformantTruncation,
41+
useTruncationCaching: Boolean = RenderCoreConfig.useTruncationCaching,
4042
): Primitive {
4143
return Primitive(
42-
layoutBehavior = RichTextLayoutBehavior(text, style, usePerformantTruncation),
44+
layoutBehavior =
45+
RichTextLayoutBehavior(text, style, usePerformantTruncation, useTruncationCaching),
4346
mountBehavior =
4447
MountBehavior(
4548
id = id,
@@ -50,8 +53,8 @@ fun RichTextPrimitive(
5053
) { c ->
5154
RCTextView(c)
5255
}) {
53-
bindWithLayoutData<TextLayout>(Unit) { content, textLayout ->
54-
content.mount(textLayout)
56+
bindWithLayoutData<RichTextLayoutData>(Unit) { content, layoutData ->
57+
content.mount(layoutData.textLayout)
5558
onUnbind { content.unmount() }
5659
}
5760
})
@@ -60,9 +63,26 @@ fun RichTextPrimitive(
6063
private class RichTextLayoutBehavior(
6164
val text: CharSequence,
6265
val style: TextStyle,
63-
val usePerformantTruncation: Boolean
66+
val usePerformantTruncation: Boolean,
67+
val useTruncationCaching: Boolean,
6468
) : LayoutBehavior {
6569
override fun LayoutScope.layout(sizeConstraints: SizeConstraints): PrimitiveLayoutResult {
70+
val previousRichTextLayoutData = previousLayoutData as? RichTextLayoutData
71+
if (useTruncationCaching &&
72+
previousRichTextLayoutData != null &&
73+
canReuseTextLayout(previousRichTextLayoutData, text, style, sizeConstraints)) {
74+
return PrimitiveLayoutResult(
75+
width = max(previousRichTextLayoutData.size.width(), sizeConstraints.minWidth),
76+
height = max(previousRichTextLayoutData.size.height(), sizeConstraints.minHeight),
77+
// TODO: This should copy the textLayout with new clickable spans
78+
layoutData =
79+
RichTextLayoutData(
80+
previousRichTextLayoutData.textLayout,
81+
previousRichTextLayoutData.size,
82+
style,
83+
sizeConstraints))
84+
}
85+
6686
val (size, textLayout) =
6787
layout(
6888
androidContext,
@@ -71,14 +91,55 @@ private class RichTextLayoutBehavior(
7191
text,
7292
style,
7393
usePerformantTruncation)
74-
7594
return PrimitiveLayoutResult(
7695
width = max(size.width(), sizeConstraints.minWidth),
7796
height = max(size.height(), sizeConstraints.minHeight),
78-
layoutData = textLayout)
97+
layoutData = RichTextLayoutData(textLayout, size, style, sizeConstraints))
98+
}
99+
100+
private fun canReuseTextLayout(
101+
previousLayoutData: RichTextLayoutData,
102+
text: CharSequence,
103+
style: TextStyle,
104+
sizeConstraints: SizeConstraints
105+
): Boolean {
106+
return previousLayoutData.textLayout.isExplicitlyTruncated &&
107+
previousLayoutData.sizeConstraints == sizeConstraints &&
108+
hasEquivalentStyle(style, previousLayoutData.style) &&
109+
hasEquivalentTruncatedText(previousLayoutData.textLayout, text, style)
110+
}
111+
112+
// TODO: this should be moved to TextStyle and emcompass all properties
113+
fun hasEquivalentStyle(style1: TextStyle, style2: TextStyle): Boolean {
114+
return style1.textSize == style2.textSize &&
115+
style1.textColor == style2.textColor &&
116+
style1.textDirection == style2.textDirection &&
117+
style1.textStyle == style2.textStyle &&
118+
style1.truncationStyle == style2.truncationStyle
119+
}
120+
121+
private fun hasEquivalentTruncatedText(
122+
textLayout: TextLayout,
123+
text: CharSequence,
124+
style: TextStyle
125+
): Boolean {
126+
if (!textLayout.isExplicitlyTruncated) {
127+
return false
128+
}
129+
val textLayoutText = textLayout.layout.text
130+
val suffix = style.customEllipsisText ?: ""
131+
return textLayoutText.endsWith(suffix) &&
132+
text.startsWith(textLayoutText.subSequence(0, textLayoutText.length - suffix.length))
79133
}
80134
}
81135

136+
private class RichTextLayoutData(
137+
val textLayout: TextLayout,
138+
val size: Rect,
139+
val style: TextStyle,
140+
val sizeConstraints: SizeConstraints
141+
)
142+
82143
object RichTextPrimitiveConfig {
83144
val canPreallocation: Boolean = true
84145
val poolSize: Int = 10

litho-rendercore/src/main/java/com/facebook/rendercore/RenderCoreConfig.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,7 @@ object RenderCoreConfig {
5050

5151
/** Flag to use getOffsetForAdvance in TextMeasurementUtils to prevent ANRs. */
5252
@JvmField var usePerformantTruncation: Boolean = false
53+
54+
/** Flag to re-use compatible TextLayouts in truncation scenarios. */
55+
@JvmField var useTruncationCaching: Boolean = false
5356
}

litho-widget/src/main/java/com/facebook/litho/widget/RichText.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,21 @@ class RichText(
2929
val text: CharSequence,
3030
val textStyle: TextStyle? = null,
3131
private val style: Style? = null,
32-
private val usePerformantTruncation: Boolean = RenderCoreConfig.usePerformantTruncation
32+
private val usePerformantTruncation: Boolean = RenderCoreConfig.usePerformantTruncation,
33+
private val useTruncationCaching: Boolean = RenderCoreConfig.useTruncationCaching
3334
) : PrimitiveComponent() {
3435
override fun PrimitiveComponentScope.render(): LithoPrimitive {
3536
val resolvedTextStyle =
3637
useCached(textStyle) {
3738
textStyle ?: TextStyle.createDefaultConfiguredTextStyle(androidContext)
3839
}
3940
return LithoPrimitive(
40-
RichTextPrimitive(createPrimitiveId(), text, resolvedTextStyle, usePerformantTruncation),
41+
RichTextPrimitive(
42+
createPrimitiveId(),
43+
text,
44+
resolvedTextStyle,
45+
usePerformantTruncation,
46+
useTruncationCaching),
4147
style = style)
4248
}
4349
}

0 commit comments

Comments
 (0)