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