1 /*
<lambda>null2  * Copyright 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.compose.foundation.text
18 
19 import androidx.compose.foundation.text.modifiers.SelectableTextAnnotatedStringElement
20 import androidx.compose.foundation.text.modifiers.SelectionController
21 import androidx.compose.foundation.text.modifiers.TextAnnotatedStringElement
22 import androidx.compose.foundation.text.modifiers.TextAnnotatedStringNode
23 import androidx.compose.foundation.text.modifiers.TextStringSimpleElement
24 import androidx.compose.foundation.text.modifiers.hasLinks
25 import androidx.compose.foundation.text.selection.LocalSelectionRegistrar
26 import androidx.compose.foundation.text.selection.LocalTextSelectionColors
27 import androidx.compose.foundation.text.selection.SelectionRegistrar
28 import androidx.compose.foundation.text.selection.hasSelection
29 import androidx.compose.runtime.Composable
30 import androidx.compose.runtime.MutableState
31 import androidx.compose.runtime.NonRestartableComposable
32 import androidx.compose.runtime.getValue
33 import androidx.compose.runtime.mutableStateOf
34 import androidx.compose.runtime.remember
35 import androidx.compose.runtime.saveable.Saver
36 import androidx.compose.runtime.saveable.rememberSaveable
37 import androidx.compose.runtime.setValue
38 import androidx.compose.ui.Modifier
39 import androidx.compose.ui.geometry.Rect
40 import androidx.compose.ui.graphics.ColorProducer
41 import androidx.compose.ui.layout.Layout
42 import androidx.compose.ui.layout.Measurable
43 import androidx.compose.ui.layout.MeasurePolicy
44 import androidx.compose.ui.layout.MeasureResult
45 import androidx.compose.ui.layout.MeasureScope
46 import androidx.compose.ui.layout.Placeable
47 import androidx.compose.ui.platform.LocalFontFamilyResolver
48 import androidx.compose.ui.text.AnnotatedString
49 import androidx.compose.ui.text.Placeholder
50 import androidx.compose.ui.text.TextLayoutResult
51 import androidx.compose.ui.text.TextStyle
52 import androidx.compose.ui.text.font.FontFamily
53 import androidx.compose.ui.text.style.TextOverflow
54 import androidx.compose.ui.unit.Constraints
55 import androidx.compose.ui.unit.Constraints.Companion.fitPrioritizingWidth
56 import androidx.compose.ui.unit.IntOffset
57 import androidx.compose.ui.util.fastFilter
58 import androidx.compose.ui.util.fastForEach
59 import androidx.compose.ui.util.fastMapIndexedNotNull
60 import androidx.compose.ui.util.fastRoundToInt
61 import kotlin.math.floor
62 
63 /**
64  * Basic element that displays text and provides semantics / accessibility information. Typically
65  * you will instead want to use [androidx.compose.material.Text], which is a higher level Text
66  * element that contains semantics and consumes style information from a theme.
67  *
68  * @param text The text to be displayed.
69  * @param modifier [Modifier] to apply to this layout node.
70  * @param style Style configuration for the text such as color, font, line height etc.
71  * @param onTextLayout Callback that is executed when a new text layout is calculated. A
72  *   [TextLayoutResult] object that callback provides contains paragraph information, size of the
73  *   text, baselines and other details. The callback can be used to add additional decoration or
74  *   functionality to the text. For example, to draw selection around the text.
75  * @param overflow How visual overflow should be handled.
76  * @param softWrap Whether the text should break at soft line breaks. If false, the glyphs in the
77  *   text will be positioned as if there was unlimited horizontal space. If [softWrap] is false,
78  *   [overflow] and TextAlign may have unexpected effects.
79  * @param maxLines An optional maximum number of lines for the text to span, wrapping if necessary.
80  *   If the text exceeds the given number of lines, it will be truncated according to [overflow] and
81  *   [softWrap]. It is required that 1 <= [minLines] <= [maxLines].
82  * @param minLines The minimum height in terms of minimum number of visible lines. It is required
83  *   that 1 <= [minLines] <= [maxLines].
84  * @param color Overrides the text color provided in [style]
85  * @param autoSize Enable auto sizing for this text composable. Finds the biggest font size that
86  *   fits in the available space and lays the text out with this size. This performs multiple layout
87  *   passes and can be slower than using a fixed font size. This takes precedence over sizes defined
88  *   through [style]. See [TextAutoSize] and
89  *   [androidx.compose.foundation.samples.TextAutoSizeBasicTextSample].
90  */
91 @Composable
92 fun BasicText(
93     text: String,
94     modifier: Modifier = Modifier,
95     style: TextStyle = TextStyle.Default,
96     onTextLayout: ((TextLayoutResult) -> Unit)? = null,
97     overflow: TextOverflow = TextOverflow.Clip,
98     softWrap: Boolean = true,
99     maxLines: Int = Int.MAX_VALUE,
100     minLines: Int = 1,
101     color: ColorProducer? = null,
102     autoSize: TextAutoSize? = null
103 ) {
104     validateMinMaxLines(minLines = minLines, maxLines = maxLines)
105     val selectionRegistrar = LocalSelectionRegistrar.current
106     val selectionController =
107         if (selectionRegistrar != null) {
108             val backgroundSelectionColor = LocalTextSelectionColors.current.backgroundColor
109             val selectableId =
110                 rememberSaveable(selectionRegistrar, saver = selectionIdSaver(selectionRegistrar)) {
111                     selectionRegistrar.nextSelectableId()
112                 }
113             remember(selectableId, selectionRegistrar, backgroundSelectionColor) {
114                 SelectionController(selectableId, selectionRegistrar, backgroundSelectionColor)
115             }
116         } else {
117             null
118         }
119 
120     val fontFamilyResolver = LocalFontFamilyResolver.current
121 
122     BackgroundTextMeasurement(text = text, style = style, fontFamilyResolver = fontFamilyResolver)
123 
124     val finalModifier =
125         if (selectionController != null || onTextLayout != null || autoSize != null) {
126             modifier.textModifier(
127                 AnnotatedString(text = text),
128                 style = style,
129                 onTextLayout = onTextLayout,
130                 overflow = overflow,
131                 softWrap = softWrap,
132                 maxLines = maxLines,
133                 minLines = minLines,
134                 fontFamilyResolver = LocalFontFamilyResolver.current,
135                 placeholders = null,
136                 onPlaceholderLayout = null,
137                 selectionController = selectionController,
138                 color = color,
139                 onShowTranslation = null,
140                 autoSize = autoSize
141             )
142         } else {
143             modifier then
144                 TextStringSimpleElement(
145                     text = text,
146                     style = style,
147                     fontFamilyResolver = fontFamilyResolver,
148                     overflow = overflow,
149                     softWrap = softWrap,
150                     maxLines = maxLines,
151                     minLines = minLines,
152                     color = color
153                 )
154         }
155     Layout(finalModifier, EmptyMeasurePolicy)
156 }
157 
158 /**
159  * Basic element that displays text and provides semantics / accessibility information. Typically
160  * you will instead want to use [androidx.compose.material.Text], which is a higher level Text
161  * element that contains semantics and consumes style information from a theme.
162  *
163  * @param text The text to be displayed.
164  * @param modifier [Modifier] to apply to this layout node.
165  * @param style Style configuration for the text such as color, font, line height etc.
166  * @param onTextLayout Callback that is executed when a new text layout is calculated. A
167  *   [TextLayoutResult] object that callback provides contains paragraph information, size of the
168  *   text, baselines and other details. The callback can be used to add additional decoration or
169  *   functionality to the text. For example, to draw selection around the text.
170  * @param overflow How visual overflow should be handled.
171  * @param softWrap Whether the text should break at soft line breaks. If false, the glyphs in the
172  *   text will be positioned as if there was unlimited horizontal space. If [softWrap] is false,
173  *   [overflow] and TextAlign may have unexpected effects.
174  * @param maxLines An optional maximum number of lines for the text to span, wrapping if necessary.
175  *   If the text exceeds the given number of lines, it will be truncated according to [overflow] and
176  *   [softWrap]. It is required that 1 <= [minLines] <= [maxLines].
177  * @param minLines The minimum height in terms of minimum number of visible lines. It is required
178  *   that 1 <= [minLines] <= [maxLines].
179  * @param inlineContent A map store composables that replaces certain ranges of the text. It's used
180  *   to insert composables into text layout. Check [InlineTextContent] for more information.
181  * @param color Overrides the text color provided in [style]
182  * @param autoSize Enable auto sizing for this text composable. Finds the biggest font size that
183  *   fits in the available space and lays the text out with this size. This performs multiple layout
184  *   passes and can be slower than using a fixed font size. This takes precedence over sizes defined
185  *   through [style]. See [TextAutoSize] and
186  *   [androidx.compose.foundation.samples.TextAutoSizeBasicTextSample].
187  */
188 @Composable
BasicTextnull189 fun BasicText(
190     text: AnnotatedString,
191     modifier: Modifier = Modifier,
192     style: TextStyle = TextStyle.Default,
193     onTextLayout: ((TextLayoutResult) -> Unit)? = null,
194     overflow: TextOverflow = TextOverflow.Clip,
195     softWrap: Boolean = true,
196     maxLines: Int = Int.MAX_VALUE,
197     minLines: Int = 1,
198     inlineContent: Map<String, InlineTextContent> = mapOf(),
199     color: ColorProducer? = null,
200     autoSize: TextAutoSize? = null
201 ) {
202     validateMinMaxLines(minLines = minLines, maxLines = maxLines)
203     val selectionRegistrar = LocalSelectionRegistrar.current
204     val selectionController =
205         if (selectionRegistrar != null) {
206             val backgroundSelectionColor = LocalTextSelectionColors.current.backgroundColor
207             val selectableId =
208                 rememberSaveable(selectionRegistrar, saver = selectionIdSaver(selectionRegistrar)) {
209                     selectionRegistrar.nextSelectableId()
210                 }
211             remember(selectableId, selectionRegistrar, backgroundSelectionColor) {
212                 SelectionController(selectableId, selectionRegistrar, backgroundSelectionColor)
213             }
214         } else {
215             null
216         }
217     val hasInlineContent = text.hasInlineContent()
218     val hasLinks = text.hasLinks()
219 
220     val fontFamilyResolver = LocalFontFamilyResolver.current
221 
222     if (!hasInlineContent && !hasLinks) {
223         BackgroundTextMeasurement(
224             text = text,
225             style = style,
226             fontFamilyResolver = fontFamilyResolver,
227             placeholders = null
228         )
229 
230         // this is the same as text: String, use all the early exits
231         Layout(
232             modifier =
233                 modifier.textModifier(
234                     text = text,
235                     style = style,
236                     onTextLayout = onTextLayout,
237                     overflow = overflow,
238                     softWrap = softWrap,
239                     maxLines = maxLines,
240                     minLines = minLines,
241                     fontFamilyResolver = fontFamilyResolver,
242                     placeholders = null,
243                     onPlaceholderLayout = null,
244                     selectionController = selectionController,
245                     color = color,
246                     onShowTranslation = null,
247                     autoSize = autoSize
248                 ),
249             EmptyMeasurePolicy
250         )
251     } else {
252         // takes into account text substitution (for translation) that is happening inside the
253         // TextAnnotatedStringNode
254         var displayedText by remember(text) { mutableStateOf(text) }
255 
256         LayoutWithLinksAndInlineContent(
257             modifier = modifier,
258             text = displayedText,
259             onTextLayout = onTextLayout,
260             hasInlineContent = hasInlineContent,
261             inlineContent = inlineContent,
262             style = style,
263             overflow = overflow,
264             softWrap = softWrap,
265             maxLines = maxLines,
266             minLines = minLines,
267             fontFamilyResolver = fontFamilyResolver,
268             selectionController = selectionController,
269             color = color,
270             onShowTranslation = { substitutionValue ->
271                 displayedText =
272                     if (substitutionValue.isShowingSubstitution) {
273                         substitutionValue.substitution
274                     } else {
275                         substitutionValue.original
276                     }
277             },
278             autoSize = autoSize
279         )
280     }
281 }
282 
283 /**
284  * Basic element that displays text and provides semantics / accessibility information. Typically
285  * you will instead want to use [androidx.compose.material.Text], which is a higher level Text
286  * element that contains semantics and consumes style information from a theme.
287  *
288  * @param text The text to be displayed.
289  * @param modifier [Modifier] to apply to this layout node.
290  * @param style Style configuration for the text such as color, font, line height etc.
291  * @param onTextLayout Callback that is executed when a new text layout is calculated. A
292  *   [TextLayoutResult] object that callback provides contains paragraph information, size of the
293  *   text, baselines and other details. The callback can be used to add additional decoration or
294  *   functionality to the text. For example, to draw selection around the text.
295  * @param overflow How visual overflow should be handled.
296  * @param softWrap Whether the text should break at soft line breaks. If false, the glyphs in the
297  *   text will be positioned as if there was unlimited horizontal space. If [softWrap] is false,
298  *   [overflow] and TextAlign may have unexpected effects.
299  * @param maxLines An optional maximum number of lines for the text to span, wrapping if necessary.
300  *   If the text exceeds the given number of lines, it will be truncated according to [overflow] and
301  *   [softWrap]. It is required that 1 <= [minLines] <= [maxLines].
302  * @param minLines The minimum height in terms of minimum number of visible lines. It is required
303  *   that 1 <= [minLines] <= [maxLines].
304  * @param color Overrides the text color provided in [style]
305  */
306 @Deprecated("Maintained for binary compatibility", level = DeprecationLevel.HIDDEN)
307 @Composable
BasicTextnull308 fun BasicText(
309     text: String,
310     modifier: Modifier = Modifier,
311     style: TextStyle = TextStyle.Default,
312     onTextLayout: ((TextLayoutResult) -> Unit)? = null,
313     overflow: TextOverflow = TextOverflow.Clip,
314     softWrap: Boolean = true,
315     maxLines: Int = Int.MAX_VALUE,
316     minLines: Int = 1,
317     color: ColorProducer? = null
318 ) {
319     BasicText(text, modifier, style, onTextLayout, overflow, softWrap, maxLines, minLines, color)
320 }
321 
322 /**
323  * Basic element that displays text and provides semantics / accessibility information. Typically
324  * you will instead want to use [androidx.compose.material.Text], which is a higher level Text
325  * element that contains semantics and consumes style information from a theme.
326  *
327  * @param text The text to be displayed.
328  * @param modifier [Modifier] to apply to this layout node.
329  * @param style Style configuration for the text such as color, font, line height etc.
330  * @param onTextLayout Callback that is executed when a new text layout is calculated. A
331  *   [TextLayoutResult] object that callback provides contains paragraph information, size of the
332  *   text, baselines and other details. The callback can be used to add additional decoration or
333  *   functionality to the text. For example, to draw selection around the text.
334  * @param overflow How visual overflow should be handled.
335  * @param softWrap Whether the text should break at soft line breaks. If false, the glyphs in the
336  *   text will be positioned as if there was unlimited horizontal space. If [softWrap] is false,
337  *   [overflow] and TextAlign may have unexpected effects.
338  * @param maxLines An optional maximum number of lines for the text to span, wrapping if necessary.
339  *   If the text exceeds the given number of lines, it will be truncated according to [overflow] and
340  *   [softWrap]. It is required that 1 <= [minLines] <= [maxLines].
341  * @param minLines The minimum height in terms of minimum number of visible lines. It is required
342  *   that 1 <= [minLines] <= [maxLines].
343  * @param inlineContent A map store composables that replaces certain ranges of the text. It's used
344  *   to insert composables into text layout. Check [InlineTextContent] for more information.
345  * @param color Overrides the text color provided in [style]
346  */
347 @Deprecated("Maintained for binary compatibility", level = DeprecationLevel.HIDDEN)
348 @Composable
BasicTextnull349 fun BasicText(
350     text: AnnotatedString,
351     modifier: Modifier = Modifier,
352     style: TextStyle = TextStyle.Default,
353     onTextLayout: ((TextLayoutResult) -> Unit)? = null,
354     overflow: TextOverflow = TextOverflow.Clip,
355     softWrap: Boolean = true,
356     maxLines: Int = Int.MAX_VALUE,
357     minLines: Int = 1,
358     inlineContent: Map<String, InlineTextContent> = mapOf(),
359     color: ColorProducer? = null
360 ) {
361     BasicText(
362         text,
363         modifier,
364         style,
365         onTextLayout,
366         overflow,
367         softWrap,
368         maxLines,
369         minLines,
370         inlineContent,
371         color
372     )
373 }
374 
375 @Deprecated("Maintained for binary compatibility", level = DeprecationLevel.HIDDEN)
376 @Composable
BasicTextnull377 fun BasicText(
378     text: String,
379     modifier: Modifier = Modifier,
380     style: TextStyle = TextStyle.Default,
381     onTextLayout: ((TextLayoutResult) -> Unit)? = null,
382     overflow: TextOverflow = TextOverflow.Clip,
383     softWrap: Boolean = true,
384     maxLines: Int = Int.MAX_VALUE
385 ) {
386     BasicText(
387         text = text,
388         modifier = modifier,
389         style = style,
390         onTextLayout = onTextLayout,
391         overflow = overflow,
392         softWrap = softWrap,
393         minLines = 1,
394         maxLines = maxLines
395     )
396 }
397 
398 @Deprecated("Maintained for binary compatibility", level = DeprecationLevel.HIDDEN)
399 @Composable
BasicTextnull400 fun BasicText(
401     text: AnnotatedString,
402     modifier: Modifier = Modifier,
403     style: TextStyle = TextStyle.Default,
404     onTextLayout: ((TextLayoutResult) -> Unit)? = null,
405     overflow: TextOverflow = TextOverflow.Clip,
406     softWrap: Boolean = true,
407     maxLines: Int = Int.MAX_VALUE,
408     inlineContent: Map<String, InlineTextContent> = mapOf(),
409 ) {
410     BasicText(
411         text = text,
412         modifier = modifier,
413         style = style,
414         onTextLayout = onTextLayout,
415         overflow = overflow,
416         softWrap = softWrap,
417         maxLines = maxLines,
418         minLines = 1,
419         inlineContent = inlineContent
420     )
421 }
422 
423 @Deprecated("Maintained for binary compat", level = DeprecationLevel.HIDDEN)
424 @Composable
BasicTextnull425 fun BasicText(
426     text: String,
427     modifier: Modifier = Modifier,
428     style: TextStyle = TextStyle.Default,
429     onTextLayout: ((TextLayoutResult) -> Unit)? = null,
430     overflow: TextOverflow = TextOverflow.Clip,
431     softWrap: Boolean = true,
432     maxLines: Int = Int.MAX_VALUE,
433     minLines: Int = 1
434 ) = BasicText(text, modifier, style, onTextLayout, overflow, softWrap, maxLines, minLines)
435 
436 @Deprecated("Maintained for binary compat", level = DeprecationLevel.HIDDEN)
437 @Composable
438 fun BasicText(
439     text: AnnotatedString,
440     modifier: Modifier = Modifier,
441     style: TextStyle = TextStyle.Default,
442     onTextLayout: ((TextLayoutResult) -> Unit)? = null,
443     overflow: TextOverflow = TextOverflow.Clip,
444     softWrap: Boolean = true,
445     maxLines: Int = Int.MAX_VALUE,
446     minLines: Int = 1,
447     inlineContent: Map<String, InlineTextContent> = mapOf()
448 ) =
449     BasicText(
450         text = text,
451         modifier = modifier,
452         style = style,
453         onTextLayout = onTextLayout,
454         overflow = overflow,
455         softWrap = softWrap,
456         maxLines = maxLines,
457         minLines = minLines,
458         inlineContent = inlineContent
459     )
460 
461 /** A custom saver that won't save if no selection is active. */
462 private fun selectionIdSaver(selectionRegistrar: SelectionRegistrar?) =
463     Saver<Long, Long>(
464         save = { if (selectionRegistrar.hasSelection(it)) it else null },
<lambda>null465         restore = { it }
466     )
467 
468 private object EmptyMeasurePolicy : MeasurePolicy {
<lambda>null469     private val placementBlock: Placeable.PlacementScope.() -> Unit = {}
470 
measurenull471     override fun MeasureScope.measure(
472         measurables: List<Measurable>,
473         constraints: Constraints
474     ): MeasureResult {
475         return layout(constraints.maxWidth, constraints.maxHeight, placementBlock = placementBlock)
476     }
477 }
478 
479 /** Measure policy for inline content and links */
480 private class TextMeasurePolicy(
481     private val shouldMeasureLinks: () -> Boolean,
482     private val placements: () -> List<Rect?>?
483 ) : MeasurePolicy {
measurenull484     override fun MeasureScope.measure(
485         measurables: List<Measurable>,
486         constraints: Constraints
487     ): MeasureResult {
488         // inline content
489         val inlineContentMeasurables =
490             measurables.fastFilter { it.parentData !is TextRangeLayoutModifier }
491         val inlineContentToPlace =
492             placements()?.fastMapIndexedNotNull { index, rect ->
493                 // PlaceholderRect will be null if it's ellipsized. In that case, the corresponding
494                 // inline children won't be measured or placed.
495                 rect?.let {
496                     Pair(
497                         inlineContentMeasurables[index].measure(
498                             Constraints(
499                                 maxWidth = floor(it.width).toInt(),
500                                 maxHeight = floor(it.height).toInt()
501                             )
502                         ),
503                         IntOffset(it.left.fastRoundToInt(), it.top.fastRoundToInt())
504                     )
505                 }
506             }
507 
508         // links
509         val linksMeasurables = measurables.fastFilter { it.parentData is TextRangeLayoutModifier }
510         val linksToPlace =
511             measureWithTextRangeMeasureConstraints(
512                 measurables = linksMeasurables,
513                 shouldMeasureLinks = shouldMeasureLinks
514             )
515 
516         return layout(constraints.maxWidth, constraints.maxHeight) {
517             // inline content
518             inlineContentToPlace?.fastForEach { (placeable, position) -> placeable.place(position) }
519             // links
520             linksToPlace?.fastForEach { (placeable, measureResult) ->
521                 placeable.place(measureResult?.invoke() ?: IntOffset.Zero)
522             }
523         }
524     }
525 }
526 
527 /** Measure policy for links only */
528 private class LinksTextMeasurePolicy(private val shouldMeasureLinks: () -> Boolean) :
529     MeasurePolicy {
measurenull530     override fun MeasureScope.measure(
531         measurables: List<Measurable>,
532         constraints: Constraints
533     ): MeasureResult {
534         return layout(constraints.maxWidth, constraints.maxHeight) {
535             val linksToPlace =
536                 measureWithTextRangeMeasureConstraints(
537                     measurables = measurables,
538                     shouldMeasureLinks = shouldMeasureLinks
539                 )
540             linksToPlace?.fastForEach { (placeable, measureResult) ->
541                 placeable.place(measureResult?.invoke() ?: IntOffset.Zero)
542             }
543         }
544     }
545 }
546 
measureWithTextRangeMeasureConstraintsnull547 private fun measureWithTextRangeMeasureConstraints(
548     measurables: List<Measurable>,
549     shouldMeasureLinks: () -> Boolean,
550 ): List<Pair<Placeable, (() -> IntOffset)?>>? {
551     return if (shouldMeasureLinks()) {
552         val textRangeLayoutMeasureScope = TextRangeLayoutMeasureScope()
553         measurables.fastMapIndexedNotNull { _, measurable ->
554             val rangeMeasurePolicy =
555                 (measurable.parentData as TextRangeLayoutModifier).measurePolicy
556             val rangeMeasureResult =
557                 with(rangeMeasurePolicy) { textRangeLayoutMeasureScope.measure() }
558             val placeable =
559                 measurable.measure(
560                     fitPrioritizingWidth(
561                         minWidth = rangeMeasureResult.width,
562                         maxWidth = rangeMeasureResult.width,
563                         minHeight = rangeMeasureResult.height,
564                         maxHeight = rangeMeasureResult.height
565                     )
566                 )
567             Pair(placeable, rangeMeasureResult.place)
568         }
569     } else {
570         null
571     }
572 }
573 
textModifiernull574 private fun Modifier.textModifier(
575     text: AnnotatedString,
576     style: TextStyle,
577     onTextLayout: ((TextLayoutResult) -> Unit)?,
578     overflow: TextOverflow,
579     softWrap: Boolean,
580     maxLines: Int,
581     minLines: Int,
582     fontFamilyResolver: FontFamily.Resolver,
583     placeholders: List<AnnotatedString.Range<Placeholder>>?,
584     onPlaceholderLayout: ((List<Rect?>) -> Unit)?,
585     selectionController: SelectionController?,
586     color: ColorProducer?,
587     onShowTranslation: ((TextAnnotatedStringNode.TextSubstitutionValue) -> Unit)?,
588     autoSize: TextAutoSize?,
589 ): Modifier {
590     if (selectionController == null) {
591         val staticTextModifier =
592             TextAnnotatedStringElement(
593                 text,
594                 style,
595                 fontFamilyResolver,
596                 onTextLayout,
597                 overflow,
598                 softWrap,
599                 maxLines,
600                 minLines,
601                 placeholders,
602                 onPlaceholderLayout,
603                 null,
604                 color,
605                 autoSize,
606                 onShowTranslation
607             )
608         return this then Modifier /* selection position */ then staticTextModifier
609     } else {
610         val selectableTextModifier =
611             SelectableTextAnnotatedStringElement(
612                 text,
613                 style,
614                 fontFamilyResolver,
615                 onTextLayout,
616                 overflow,
617                 softWrap,
618                 maxLines,
619                 minLines,
620                 placeholders,
621                 onPlaceholderLayout,
622                 selectionController,
623                 color,
624                 autoSize
625             )
626         return this then selectionController.modifier then selectableTextModifier
627     }
628 }
629 
630 @Composable
LayoutWithLinksAndInlineContentnull631 private fun LayoutWithLinksAndInlineContent(
632     modifier: Modifier,
633     text: AnnotatedString,
634     onTextLayout: ((TextLayoutResult) -> Unit)?,
635     hasInlineContent: Boolean,
636     inlineContent: Map<String, InlineTextContent> = mapOf(),
637     style: TextStyle,
638     overflow: TextOverflow,
639     softWrap: Boolean,
640     maxLines: Int,
641     minLines: Int,
642     fontFamilyResolver: FontFamily.Resolver,
643     selectionController: SelectionController?,
644     color: ColorProducer?,
645     onShowTranslation: ((TextAnnotatedStringNode.TextSubstitutionValue) -> Unit)?,
646     autoSize: TextAutoSize?
647 ) {
648 
649     val textScope =
650         if (text.hasLinks()) {
651             remember(text) { TextLinkScope(text) }
652         } else null
653 
654     // only adds additional span styles to the existing link annotations, doesn't semantically
655     // change the text
656     val styledText: () -> AnnotatedString =
657         if (text.hasLinks()) {
658             remember(text, textScope) { { textScope?.applyAnnotators() ?: text } }
659         } else {
660             { text }
661         }
662 
663     // do the inline content allocs
664     val (placeholders, inlineComposables) =
665         if (hasInlineContent) {
666             text.resolveInlineContent(inlineContent = inlineContent)
667         } else Pair(null, null)
668 
669     val measuredPlaceholderPositions =
670         if (hasInlineContent) {
671             remember<MutableState<List<Rect?>?>> { mutableStateOf(null) }
672         } else null
673 
674     val onPlaceholderLayout: ((List<Rect?>) -> Unit)? =
675         if (hasInlineContent) {
676             { measuredPlaceholderPositions?.value = it }
677         } else null
678 
679     BackgroundTextMeasurement(
680         text = text,
681         style = style,
682         fontFamilyResolver = fontFamilyResolver,
683         placeholders = placeholders
684     )
685 
686     Layout(
687         content = {
688             textScope?.LinksComposables()
689             inlineComposables?.let { InlineChildren(text = text, inlineContents = it) }
690         },
691         modifier =
692             modifier.textModifier(
693                 text = styledText(),
694                 style = style,
695                 onTextLayout = {
696                     textScope?.textLayoutResult = it
697                     onTextLayout?.invoke(it)
698                 },
699                 overflow = overflow,
700                 softWrap = softWrap,
701                 maxLines = maxLines,
702                 minLines = minLines,
703                 fontFamilyResolver = fontFamilyResolver,
704                 placeholders = placeholders,
705                 onPlaceholderLayout = onPlaceholderLayout,
706                 selectionController = selectionController,
707                 color = color,
708                 onShowTranslation = onShowTranslation,
709                 autoSize = autoSize,
710             ),
711         measurePolicy =
712             if (!hasInlineContent) {
713                 LinksTextMeasurePolicy(
714                     shouldMeasureLinks = { textScope?.let { it.shouldMeasureLinks() } ?: false }
715                 )
716             } else {
717                 TextMeasurePolicy(
718                     shouldMeasureLinks = { textScope?.let { it.shouldMeasureLinks() } ?: false },
719                     placements = { measuredPlaceholderPositions?.value }
720                 )
721             }
722     )
723 }
724 
725 /**
726  * This function pre-measures the text on Android platform to warm the platform text layout cache in
727  * a background thread before the actual text layout begins.
728  *
729  * @return An optional callback to cancel the background task from proceeding. If the measurement
730  *   starts on the main thread, there is no point in starting this task in the background thread.
731  */
732 @Composable
733 @NonRestartableComposable
BackgroundTextMeasurementnull734 internal expect fun BackgroundTextMeasurement(
735     text: String,
736     style: TextStyle,
737     fontFamilyResolver: FontFamily.Resolver
738 )
739 
740 /**
741  * This function pre-measures the text on Android platform to warm the platform text layout cache in
742  * a background thread before the actual text layout begins.
743  *
744  * @return An optional callback to cancel the background task from proceeding. If the measurement
745  *   starts on the main thread, there is no point in starting this task in the background thread.
746  */
747 @Composable
748 @NonRestartableComposable
749 internal expect fun BackgroundTextMeasurement(
750     text: AnnotatedString,
751     style: TextStyle,
752     fontFamilyResolver: FontFamily.Resolver,
753     placeholders: List<AnnotatedString.Range<Placeholder>>?
754 )
755