1 /*
2 * Copyright 2019 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.ui.text
18
19 import androidx.compose.runtime.Immutable
20 import androidx.compose.runtime.Stable
21 import androidx.compose.ui.text.internal.checkPrecondition
22 import androidx.compose.ui.text.style.Hyphens
23 import androidx.compose.ui.text.style.LineBreak
24 import androidx.compose.ui.text.style.LineHeightStyle
25 import androidx.compose.ui.text.style.TextAlign
26 import androidx.compose.ui.text.style.TextDirection
27 import androidx.compose.ui.text.style.TextIndent
28 import androidx.compose.ui.text.style.TextMotion
29 import androidx.compose.ui.text.style.lerp
30 import androidx.compose.ui.unit.LayoutDirection
31 import androidx.compose.ui.unit.TextUnit
32 import androidx.compose.ui.unit.isSpecified
33 import androidx.compose.ui.unit.isUnspecified
34 import kotlin.jvm.JvmName
35
36 private val DefaultLineHeight = TextUnit.Unspecified
37
38 /**
39 * Paragraph styling configuration for a paragraph. The difference between [SpanStyle] and
40 * `ParagraphStyle` is that, `ParagraphStyle` can be applied to a whole [Paragraph] while
41 * [SpanStyle] can be applied at the character level. Once a portion of the text is marked with a
42 * `ParagraphStyle`, that portion will be separated from the remaining as if a line feed character
43 * was added.
44 *
45 * @sample androidx.compose.ui.text.samples.ParagraphStyleSample
46 * @sample androidx.compose.ui.text.samples.ParagraphStyleAnnotatedStringsSample
47 * @param textAlign The alignment of the text within the lines of the paragraph.
48 * @param textDirection The algorithm to be used to resolve the final text direction: Left To Right
49 * or Right To Left.
50 * @param lineHeight Line height for the [Paragraph] in [TextUnit] unit, e.g. SP or EM.
51 * @param textIndent The indentation of the paragraph.
52 * @param platformStyle Platform specific [ParagraphStyle] parameters.
53 * @param lineHeightStyle the configuration for line height such as vertical alignment of the line,
54 * whether to apply additional space as a result of line height to top of first line top and
55 * bottom of last line. The configuration is applied only when a [lineHeight] is defined. When
56 * null, [LineHeightStyle.Default] is used.
57 * @param lineBreak The line breaking configuration for the text.
58 * @param hyphens The configuration of hyphenation.
59 * @param textMotion Text character placement, whether to optimize for animated or static text.
60 * @see Paragraph
61 * @see AnnotatedString
62 * @see SpanStyle
63 * @see TextStyle
64 */
65 @Immutable
66 class ParagraphStyle(
67 val textAlign: TextAlign = TextAlign.Unspecified,
68 val textDirection: TextDirection = TextDirection.Unspecified,
69 val lineHeight: TextUnit = TextUnit.Unspecified,
70 val textIndent: TextIndent? = null,
71 val platformStyle: PlatformParagraphStyle? = null,
72 val lineHeightStyle: LineHeightStyle? = null,
73 val lineBreak: LineBreak = LineBreak.Unspecified,
74 val hyphens: Hyphens = Hyphens.Unspecified,
75 val textMotion: TextMotion? = null
76 ) : AnnotatedString.Annotation {
77 @Deprecated("Kept for backwards compatibility.", level = DeprecationLevel.WARNING)
78 @get:JvmName("getTextAlign-buA522U") // b/320819734
79 @Suppress("unused", "RedundantNullableReturnType", "PropertyName")
80 val deprecated_boxing_textAlign: TextAlign?
81 get() = this.textAlign
82
83 @Deprecated("Kept for backwards compatibility.", level = DeprecationLevel.WARNING)
84 @get:JvmName("getTextDirection-mmuk1to") // b/320819734
85 @Suppress("unused", "RedundantNullableReturnType", "PropertyName")
86 val deprecated_boxing_textDirection: TextDirection?
87 get() = this.textDirection
88
89 @Deprecated("Kept for backwards compatibility.", level = DeprecationLevel.WARNING)
90 @get:JvmName("getHyphens-EaSxIns") // b/320819734
91 @Suppress("unused", "RedundantNullableReturnType", "PropertyName")
92 val deprecated_boxing_hyphens: Hyphens?
93 get() = this.hyphens
94
95 @Deprecated("Kept for backwards compatibility.", level = DeprecationLevel.WARNING)
96 @get:JvmName("getLineBreak-LgCVezo") // b/320819734
97 @Suppress("unused", "RedundantNullableReturnType", "PropertyName")
98 val deprecated_boxing_lineBreak: LineBreak?
99 get() = this.lineBreak
100
101 @Deprecated(
102 "ParagraphStyle constructors that take nullable TextAlign, " +
103 "TextDirection, LineBreak, and Hyphens are deprecated. Please use a new constructor " +
104 "where these parameters are non-nullable. Null value has been replaced by a special " +
105 "Unspecified object for performance reason.",
106 level = DeprecationLevel.HIDDEN
107 )
108 constructor(
109 textAlign: TextAlign? = null,
110 textDirection: TextDirection? = null,
111 lineHeight: TextUnit = TextUnit.Unspecified,
112 textIndent: TextIndent? = null,
113 platformStyle: PlatformParagraphStyle? = null,
114 lineHeightStyle: LineHeightStyle? = null,
115 lineBreak: LineBreak? = null,
116 hyphens: Hyphens? = null,
117 textMotion: TextMotion? = null
118 ) : this(
119 textAlign = textAlign ?: TextAlign.Unspecified,
120 textDirection = textDirection ?: TextDirection.Unspecified,
121 lineHeight = lineHeight,
122 textIndent = textIndent,
123 platformStyle = platformStyle,
124 lineHeightStyle = lineHeightStyle,
125 lineBreak = lineBreak ?: LineBreak.Unspecified,
126 hyphens = hyphens ?: Hyphens.Unspecified,
127 textMotion = textMotion
128 )
129
130 @Deprecated(
131 "ParagraphStyle constructors that do not take new stable parameters " +
132 "like LineHeightStyle, LineBreak, Hyphens are deprecated. Please use the new stable " +
133 "constructor.",
134 level = DeprecationLevel.HIDDEN
135 )
136 constructor(
137 textAlign: TextAlign? = null,
138 textDirection: TextDirection? = null,
139 lineHeight: TextUnit = TextUnit.Unspecified,
140 textIndent: TextIndent? = null
141 ) : this(
142 textAlign = textAlign ?: TextAlign.Unspecified,
143 textDirection = textDirection ?: TextDirection.Unspecified,
144 lineHeight = lineHeight,
145 textIndent = textIndent,
146 platformStyle = null,
147 lineHeightStyle = null,
148 lineBreak = LineBreak.Unspecified,
149 hyphens = Hyphens.Unspecified,
150 textMotion = null
151 )
152
153 @Deprecated(
154 "ParagraphStyle constructors that do not take new stable parameters " +
155 "like LineHeightStyle, LineBreak, Hyphens are deprecated. Please use the new stable " +
156 "constructors.",
157 level = DeprecationLevel.HIDDEN
158 )
159 constructor(
160 textAlign: TextAlign? = null,
161 textDirection: TextDirection? = null,
162 lineHeight: TextUnit = TextUnit.Unspecified,
163 textIndent: TextIndent? = null,
164 platformStyle: PlatformParagraphStyle? = null,
165 lineHeightStyle: LineHeightStyle? = null
166 ) : this(
167 textAlign = textAlign ?: TextAlign.Unspecified,
168 textDirection = textDirection ?: TextDirection.Unspecified,
169 lineHeight = lineHeight,
170 textIndent = textIndent,
171 platformStyle = platformStyle,
172 lineHeightStyle = lineHeightStyle,
173 lineBreak = LineBreak.Unspecified,
174 hyphens = Hyphens.Unspecified,
175 textMotion = null
176 )
177
178 @Deprecated(
179 "ParagraphStyle constructors that do not take new stable parameters " +
180 "like LineBreak, Hyphens, TextMotion are deprecated. Please use the new stable " +
181 "constructors.",
182 level = DeprecationLevel.HIDDEN
183 )
184 constructor(
185 textAlign: TextAlign? = null,
186 textDirection: TextDirection? = null,
187 lineHeight: TextUnit = TextUnit.Unspecified,
188 textIndent: TextIndent? = null,
189 platformStyle: PlatformParagraphStyle? = null,
190 lineHeightStyle: LineHeightStyle? = null,
191 lineBreak: LineBreak? = null,
192 hyphens: Hyphens? = null
193 ) : this(
194 textAlign = textAlign ?: TextAlign.Unspecified,
195 textDirection = textDirection ?: TextDirection.Unspecified,
196 lineHeight = lineHeight,
197 textIndent = textIndent,
198 platformStyle = platformStyle,
199 lineHeightStyle = lineHeightStyle,
200 lineBreak = lineBreak ?: LineBreak.Unspecified,
201 hyphens = hyphens ?: Hyphens.Unspecified,
202 textMotion = null
203 )
204
205 init {
206 if (lineHeight != TextUnit.Unspecified) {
207 // Since we are checking if it's negative, no need to convert Sp into Px at this point.
<lambda>null208 checkPrecondition(lineHeight.value >= 0f) {
209 "lineHeight can't be negative (${lineHeight.value})"
210 }
211 }
212 }
213
214 /**
215 * Returns a new paragraph style that is a combination of this style and the given [other]
216 * style.
217 *
218 * If the given paragraph style is null, returns this paragraph style.
219 */
220 @Stable
mergenull221 fun merge(other: ParagraphStyle? = null): ParagraphStyle {
222 if (other == null) return this
223
224 return fastMerge(
225 textAlign = other.textAlign,
226 textDirection = other.textDirection,
227 lineHeight = other.lineHeight,
228 textIndent = other.textIndent,
229 platformStyle = other.platformStyle,
230 lineHeightStyle = other.lineHeightStyle,
231 lineBreak = other.lineBreak,
232 hyphens = other.hyphens,
233 textMotion = other.textMotion
234 )
235 }
236
237 /** Plus operator overload that applies a [merge]. */
plusnull238 @Stable operator fun plus(other: ParagraphStyle): ParagraphStyle = this.merge(other)
239
240 @Deprecated(
241 "ParagraphStyle copy constructors that do not take new stable parameters " +
242 "like LineHeightStyle, LineBreak, Hyphens are deprecated. Please use the new stable " +
243 "copy constructor.",
244 level = DeprecationLevel.HIDDEN
245 )
246 fun copy(
247 textAlign: TextAlign? = this.textAlign,
248 textDirection: TextDirection? = this.textDirection,
249 lineHeight: TextUnit = this.lineHeight,
250 textIndent: TextIndent? = this.textIndent
251 ): ParagraphStyle {
252 return ParagraphStyle(
253 textAlign = textAlign ?: TextAlign.Unspecified,
254 textDirection = textDirection ?: TextDirection.Unspecified,
255 lineHeight = lineHeight,
256 textIndent = textIndent,
257 platformStyle = this.platformStyle,
258 lineHeightStyle = this.lineHeightStyle,
259 lineBreak = this.lineBreak,
260 hyphens = this.hyphens,
261 textMotion = this.textMotion
262 )
263 }
264
265 @Deprecated(
266 "ParagraphStyle copy constructors that do not take new stable parameters " +
267 "like LineHeightStyle, LineBreak, Hyphens are deprecated. Please use the new stable " +
268 "copy constructor.",
269 level = DeprecationLevel.HIDDEN
270 )
copynull271 fun copy(
272 textAlign: TextAlign? = this.textAlign,
273 textDirection: TextDirection? = this.textDirection,
274 lineHeight: TextUnit = this.lineHeight,
275 textIndent: TextIndent? = this.textIndent,
276 platformStyle: PlatformParagraphStyle? = this.platformStyle,
277 lineHeightStyle: LineHeightStyle? = this.lineHeightStyle
278 ): ParagraphStyle {
279 return ParagraphStyle(
280 textAlign = textAlign ?: TextAlign.Unspecified,
281 textDirection = textDirection ?: TextDirection.Unspecified,
282 lineHeight = lineHeight,
283 textIndent = textIndent,
284 platformStyle = platformStyle,
285 lineHeightStyle = lineHeightStyle,
286 lineBreak = this.lineBreak,
287 hyphens = this.hyphens,
288 textMotion = this.textMotion
289 )
290 }
291
292 @Deprecated(
293 "ParagraphStyle copy constructors that do not take new stable parameters " +
294 "like LineBreak, Hyphens, TextMotion are deprecated. Please use the new stable " +
295 "copy constructor.",
296 level = DeprecationLevel.HIDDEN
297 )
copynull298 fun copy(
299 textAlign: TextAlign? = this.textAlign,
300 textDirection: TextDirection? = this.textDirection,
301 lineHeight: TextUnit = this.lineHeight,
302 textIndent: TextIndent? = this.textIndent,
303 platformStyle: PlatformParagraphStyle? = this.platformStyle,
304 lineHeightStyle: LineHeightStyle? = this.lineHeightStyle,
305 lineBreak: LineBreak? = this.lineBreak,
306 hyphens: Hyphens? = this.hyphens
307 ): ParagraphStyle {
308 return ParagraphStyle(
309 textAlign = textAlign ?: TextAlign.Unspecified,
310 textDirection = textDirection ?: TextDirection.Unspecified,
311 lineHeight = lineHeight,
312 textIndent = textIndent,
313 platformStyle = platformStyle,
314 lineHeightStyle = lineHeightStyle,
315 lineBreak = lineBreak ?: LineBreak.Unspecified,
316 hyphens = hyphens ?: Hyphens.Unspecified,
317 textMotion = this.textMotion
318 )
319 }
320
321 @Deprecated(
322 "ParagraphStyle copy constructors that take nullable TextAlign, " +
323 "TextDirection, LineBreak, and Hyphens are deprecated. Please use a new constructor " +
324 "where these parameters are non-nullable. Null value has been replaced by a special " +
325 "Unspecified object for performance reason.",
326 level = DeprecationLevel.HIDDEN
327 )
copynull328 fun copy(
329 textAlign: TextAlign? = this.textAlign,
330 textDirection: TextDirection? = this.textDirection,
331 lineHeight: TextUnit = this.lineHeight,
332 textIndent: TextIndent? = this.textIndent,
333 platformStyle: PlatformParagraphStyle? = this.platformStyle,
334 lineHeightStyle: LineHeightStyle? = this.lineHeightStyle,
335 lineBreak: LineBreak? = this.lineBreak,
336 hyphens: Hyphens? = this.hyphens,
337 textMotion: TextMotion? = this.textMotion
338 ): ParagraphStyle {
339 return ParagraphStyle(
340 textAlign = textAlign ?: TextAlign.Unspecified,
341 textDirection = textDirection ?: TextDirection.Unspecified,
342 lineHeight = lineHeight,
343 textIndent = textIndent,
344 platformStyle = platformStyle,
345 lineHeightStyle = lineHeightStyle,
346 lineBreak = lineBreak ?: LineBreak.Unspecified,
347 hyphens = hyphens ?: Hyphens.Unspecified,
348 textMotion = textMotion
349 )
350 }
351
copynull352 fun copy(
353 textAlign: TextAlign = this.textAlign,
354 textDirection: TextDirection = this.textDirection,
355 lineHeight: TextUnit = this.lineHeight,
356 textIndent: TextIndent? = this.textIndent,
357 platformStyle: PlatformParagraphStyle? = this.platformStyle,
358 lineHeightStyle: LineHeightStyle? = this.lineHeightStyle,
359 lineBreak: LineBreak = this.lineBreak,
360 hyphens: Hyphens = this.hyphens,
361 textMotion: TextMotion? = this.textMotion
362 ): ParagraphStyle {
363 return ParagraphStyle(
364 textAlign = textAlign,
365 textDirection = textDirection,
366 lineHeight = lineHeight,
367 textIndent = textIndent,
368 platformStyle = platformStyle,
369 lineHeightStyle = lineHeightStyle,
370 lineBreak = lineBreak,
371 hyphens = hyphens,
372 textMotion = textMotion
373 )
374 }
375
equalsnull376 override fun equals(other: Any?): Boolean {
377 if (this === other) return true
378 if (other !is ParagraphStyle) return false
379
380 if (textAlign != other.textAlign) return false
381 if (textDirection != other.textDirection) return false
382 if (lineHeight != other.lineHeight) return false
383 if (textIndent != other.textIndent) return false
384 if (platformStyle != other.platformStyle) return false
385 if (lineHeightStyle != other.lineHeightStyle) return false
386 if (lineBreak != other.lineBreak) return false
387 if (hyphens != other.hyphens) return false
388 if (textMotion != other.textMotion) return false
389
390 return true
391 }
392
hashCodenull393 override fun hashCode(): Int {
394 var result = textAlign.hashCode()
395 result = 31 * result + textDirection.hashCode()
396 result = 31 * result + lineHeight.hashCode()
397 result = 31 * result + (textIndent?.hashCode() ?: 0)
398 result = 31 * result + (platformStyle?.hashCode() ?: 0)
399 result = 31 * result + (lineHeightStyle?.hashCode() ?: 0)
400 result = 31 * result + lineBreak.hashCode()
401 result = 31 * result + hyphens.hashCode()
402 result = 31 * result + (textMotion?.hashCode() ?: 0)
403 return result
404 }
405
toStringnull406 override fun toString(): String {
407 return "ParagraphStyle(" +
408 "textAlign=$textAlign, " +
409 "textDirection=$textDirection, " +
410 "lineHeight=$lineHeight, " +
411 "textIndent=$textIndent, " +
412 "platformStyle=$platformStyle, " +
413 "lineHeightStyle=$lineHeightStyle, " +
414 "lineBreak=$lineBreak, " +
415 "hyphens=$hyphens, " +
416 "textMotion=$textMotion" +
417 ")"
418 }
419 }
420
421 /**
422 * Interpolate between two [ParagraphStyle]s.
423 *
424 * This will not work well if the styles don't set the same fields.
425 *
426 * The [fraction] argument represents position on the timeline, with 0.0 meaning that the
427 * interpolation has not started, returning [start] (or something equivalent to [start]), 1.0
428 * meaning that the interpolation has finished, returning [stop] (or something equivalent to
429 * [stop]), and values in between meaning that the interpolation is at the relevant point on the
430 * timeline between [start] and [stop]. The interpolation can be extrapolated beyond 0.0 and 1.0, so
431 * negative values and values greater than 1.0 are valid.
432 */
433 @Stable
lerpnull434 fun lerp(start: ParagraphStyle, stop: ParagraphStyle, fraction: Float): ParagraphStyle {
435 return ParagraphStyle(
436 textAlign = lerpDiscrete(start.textAlign, stop.textAlign, fraction),
437 textDirection = lerpDiscrete(start.textDirection, stop.textDirection, fraction),
438 lineHeight = lerpTextUnitInheritable(start.lineHeight, stop.lineHeight, fraction),
439 textIndent =
440 lerp(start.textIndent ?: TextIndent.None, stop.textIndent ?: TextIndent.None, fraction),
441 platformStyle = lerpPlatformStyle(start.platformStyle, stop.platformStyle, fraction),
442 lineHeightStyle = lerpDiscrete(start.lineHeightStyle, stop.lineHeightStyle, fraction),
443 lineBreak = lerpDiscrete(start.lineBreak, stop.lineBreak, fraction),
444 hyphens = lerpDiscrete(start.hyphens, stop.hyphens, fraction),
445 textMotion = lerpDiscrete(start.textMotion, stop.textMotion, fraction)
446 )
447 }
448
lerpPlatformStylenull449 private fun lerpPlatformStyle(
450 start: PlatformParagraphStyle?,
451 stop: PlatformParagraphStyle?,
452 fraction: Float
453 ): PlatformParagraphStyle? {
454 if (start == null && stop == null) return null
455 val startNonNull = start ?: PlatformParagraphStyle.Default
456 val stopNonNull = stop ?: PlatformParagraphStyle.Default
457 return lerp(startNonNull, stopNonNull, fraction)
458 }
459
resolveParagraphStyleDefaultsnull460 internal fun resolveParagraphStyleDefaults(style: ParagraphStyle, direction: LayoutDirection) =
461 ParagraphStyle(
462 textAlign =
463 if (style.textAlign == TextAlign.Unspecified) TextAlign.Start else style.textAlign,
464 textDirection = resolveTextDirection(direction, style.textDirection),
465 lineHeight = if (style.lineHeight.isUnspecified) DefaultLineHeight else style.lineHeight,
466 textIndent = style.textIndent ?: TextIndent.None,
467 platformStyle = style.platformStyle,
468 lineHeightStyle = style.lineHeightStyle,
469 lineBreak =
470 if (style.lineBreak == LineBreak.Unspecified) LineBreak.Simple else style.lineBreak,
471 hyphens = if (style.hyphens == Hyphens.Unspecified) Hyphens.None else style.hyphens,
472 textMotion = style.textMotion ?: TextMotion.Static
473 )
474
475 internal fun ParagraphStyle.fastMerge(
476 textAlign: TextAlign,
477 textDirection: TextDirection,
478 lineHeight: TextUnit,
479 textIndent: TextIndent?,
480 platformStyle: PlatformParagraphStyle?,
481 lineHeightStyle: LineHeightStyle?,
482 lineBreak: LineBreak,
483 hyphens: Hyphens,
484 textMotion: TextMotion?
485 ): ParagraphStyle {
486 // prioritize the parameters to Text in diffs here
487 /** textAlign: TextAlign? lineHeight: TextUnit */
488
489 // any new vals should do a pre-merge check here
490 val requiresAlloc =
491 textAlign != TextAlign.Unspecified && textAlign != this.textAlign ||
492 lineHeight.isSpecified && lineHeight != this.lineHeight ||
493 textIndent != null && textIndent != this.textIndent ||
494 textDirection != TextDirection.Unspecified && textDirection != this.textDirection ||
495 platformStyle != null && platformStyle != this.platformStyle ||
496 lineHeightStyle != null && lineHeightStyle != this.lineHeightStyle ||
497 lineBreak != LineBreak.Unspecified && lineBreak != this.lineBreak ||
498 hyphens != Hyphens.Unspecified && hyphens != this.hyphens ||
499 textMotion != null && textMotion != this.textMotion
500
501 if (!requiresAlloc) {
502 return this
503 }
504
505 return ParagraphStyle(
506 lineHeight =
507 if (lineHeight.isUnspecified) {
508 this.lineHeight
509 } else {
510 lineHeight
511 },
512 textIndent = textIndent ?: this.textIndent,
513 textAlign = if (textAlign != TextAlign.Unspecified) textAlign else this.textAlign,
514 textDirection =
515 if (textDirection != TextDirection.Unspecified) textDirection else this.textDirection,
516 platformStyle = mergePlatformStyle(platformStyle),
517 lineHeightStyle = lineHeightStyle ?: this.lineHeightStyle,
518 lineBreak = if (lineBreak != LineBreak.Unspecified) lineBreak else this.lineBreak,
519 hyphens = if (hyphens != Hyphens.Unspecified) hyphens else this.hyphens,
520 textMotion = textMotion ?: this.textMotion
521 )
522 }
523
mergePlatformStylenull524 private fun ParagraphStyle.mergePlatformStyle(
525 other: PlatformParagraphStyle?
526 ): PlatformParagraphStyle? {
527 if (platformStyle == null) return other
528 if (other == null) return platformStyle
529 return platformStyle.merge(other)
530 }
531