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.graphics.Brush
22 import androidx.compose.ui.graphics.Color
23 import androidx.compose.ui.graphics.Shadow
24 import androidx.compose.ui.graphics.drawscope.DrawStyle
25 import androidx.compose.ui.graphics.drawscope.Fill
26 import androidx.compose.ui.graphics.isSpecified
27 import androidx.compose.ui.graphics.lerp
28 import androidx.compose.ui.graphics.takeOrElse
29 import androidx.compose.ui.text.font.FontFamily
30 import androidx.compose.ui.text.font.FontStyle
31 import androidx.compose.ui.text.font.FontSynthesis
32 import androidx.compose.ui.text.font.FontWeight
33 import androidx.compose.ui.text.font.lerp
34 import androidx.compose.ui.text.intl.LocaleList
35 import androidx.compose.ui.text.style.BaselineShift
36 import androidx.compose.ui.text.style.TextDecoration
37 import androidx.compose.ui.text.style.TextForegroundStyle
38 import androidx.compose.ui.text.style.TextGeometricTransform
39 import androidx.compose.ui.text.style.lerp
40 import androidx.compose.ui.unit.TextUnit
41 import androidx.compose.ui.unit.isSpecified
42 import androidx.compose.ui.unit.isUnspecified
43 import androidx.compose.ui.unit.lerp
44 import androidx.compose.ui.unit.sp
45 
46 /** The default font size if none is specified. */
47 private val DefaultFontSize = 14.sp
48 private val DefaultLetterSpacing = 0.sp
49 private val DefaultBackgroundColor = Color.Transparent
50 // TODO(nona): Introduce TextUnit.Original for representing "do not change the original result".
51 //  Need to distinguish from Inherit.
52 private val DefaultColor = Color.Black
53 private val DefaultColorForegroundStyle = TextForegroundStyle.from(DefaultColor)
54 
55 /**
56  * Styling configuration for a text span. This configuration only allows character level styling, in
57  * order to set paragraph level styling such as line height, or text alignment please see
58  * [ParagraphStyle].
59  *
60  * @sample androidx.compose.ui.text.samples.SpanStyleSample
61  * @sample androidx.compose.ui.text.samples.AnnotatedStringBuilderSample
62  * @param fontSize The size of glyphs (in logical pixels) to use when painting the text. This may be
63  *   [TextUnit.Unspecified] for inheriting from another [SpanStyle].
64  * @param fontWeight The typeface thickness to use when painting the text (e.g., bold).
65  * @param fontStyle The typeface variant to use when drawing the letters (e.g., italic).
66  * @param fontSynthesis Whether to synthesize font weight and/or style when the requested weight or
67  *   style cannot be found in the provided font family.
68  * @param fontFamily The font family to be used when rendering the text.
69  * @param fontFeatureSettings The advanced typography settings provided by font. The format is the
70  *   same as the CSS font-feature-settings attribute:
71  *   https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop
72  * @param letterSpacing The amount of space (in em) to add between each letter.
73  * @param baselineShift The amount by which the text is shifted up from the current baseline.
74  * @param textGeometricTransform The geometric transformation applied the text.
75  * @param localeList The locale list used to select region-specific glyphs.
76  * @param background The background color for the text.
77  * @param textDecoration The decorations to paint on the text (e.g., an underline).
78  * @param shadow The shadow effect applied on the text.
79  * @param platformStyle Platform specific [SpanStyle] parameters.
80  * @param drawStyle Drawing style of text, whether fill in the text while drawing or stroke around
81  *   the edges.
82  * @see AnnotatedString
83  * @see TextStyle
84  * @see ParagraphStyle
85  */
86 @Immutable
87 class SpanStyle
88 internal constructor(
89     // The fill to draw text, a unified representation of Color and Brush.
90     internal val textForegroundStyle: TextForegroundStyle,
91     val fontSize: TextUnit = TextUnit.Unspecified,
92     val fontWeight: FontWeight? = null,
93     val fontStyle: FontStyle? = null,
94     val fontSynthesis: FontSynthesis? = null,
95     val fontFamily: FontFamily? = null,
96     val fontFeatureSettings: String? = null,
97     val letterSpacing: TextUnit = TextUnit.Unspecified,
98     val baselineShift: BaselineShift? = null,
99     val textGeometricTransform: TextGeometricTransform? = null,
100     val localeList: LocaleList? = null,
101     val background: Color = Color.Unspecified,
102     val textDecoration: TextDecoration? = null,
103     val shadow: Shadow? = null,
104     val platformStyle: PlatformSpanStyle? = null,
105     val drawStyle: DrawStyle? = null
106 ) : AnnotatedString.Annotation {
107 
108     /**
109      * Styling configuration for a text span. This configuration only allows character level
110      * styling, in order to set paragraph level styling such as line height, or text alignment
111      * please see [ParagraphStyle].
112      *
113      * @sample androidx.compose.ui.text.samples.SpanStyleSample
114      * @sample androidx.compose.ui.text.samples.AnnotatedStringBuilderSample
115      * @param color The text color.
116      * @param fontSize The size of glyphs (in logical pixels) to use when painting the text. This
117      *   may be [TextUnit.Unspecified] for inheriting from another [SpanStyle].
118      * @param fontWeight The typeface thickness to use when painting the text (e.g., bold).
119      * @param fontStyle The typeface variant to use when drawing the letters (e.g., italic).
120      * @param fontSynthesis Whether to synthesize font weight and/or style when the requested weight
121      *   or style cannot be found in the provided font family.
122      * @param fontFamily The font family to be used when rendering the text.
123      * @param fontFeatureSettings The advanced typography settings provided by font. The format is
124      *   the same as the CSS font-feature-settings attribute:
125      *   https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop
126      * @param letterSpacing The amount of space (in em) to add between each letter.
127      * @param baselineShift The amount by which the text is shifted up from the current baseline.
128      * @param textGeometricTransform The geometric transformation applied the text.
129      * @param localeList The locale list used to select region-specific glyphs.
130      * @param background The background color for the text.
131      * @param textDecoration The decorations to paint on the text (e.g., an underline).
132      * @param shadow The shadow effect applied on the text.
133      * @see AnnotatedString
134      * @see TextStyle
135      * @see ParagraphStyle
136      */
137     @Deprecated(
138         "SpanStyle constructors that do not take new stable parameters " +
139             "like PlatformStyle, DrawStyle are deprecated. Please use the new stable " +
140             "constructor.",
141         level = DeprecationLevel.HIDDEN
142     )
143     constructor(
144         color: Color = Color.Unspecified,
145         fontSize: TextUnit = TextUnit.Unspecified,
146         fontWeight: FontWeight? = null,
147         fontStyle: FontStyle? = null,
148         fontSynthesis: FontSynthesis? = null,
149         fontFamily: FontFamily? = null,
150         fontFeatureSettings: String? = null,
151         letterSpacing: TextUnit = TextUnit.Unspecified,
152         baselineShift: BaselineShift? = null,
153         textGeometricTransform: TextGeometricTransform? = null,
154         localeList: LocaleList? = null,
155         background: Color = Color.Unspecified,
156         textDecoration: TextDecoration? = null,
157         shadow: Shadow? = null
158     ) : this(
159         textForegroundStyle = TextForegroundStyle.from(color),
160         fontSize = fontSize,
161         fontWeight = fontWeight,
162         fontStyle = fontStyle,
163         fontSynthesis = fontSynthesis,
164         fontFamily = fontFamily,
165         fontFeatureSettings = fontFeatureSettings,
166         letterSpacing = letterSpacing,
167         baselineShift = baselineShift,
168         textGeometricTransform = textGeometricTransform,
169         localeList = localeList,
170         background = background,
171         textDecoration = textDecoration,
172         shadow = shadow,
173         platformStyle = null
174     )
175 
176     /**
177      * Styling configuration for a text span. This configuration only allows character level
178      * styling, in order to set paragraph level styling such as line height, or text alignment
179      * please see [ParagraphStyle].
180      *
181      * @sample androidx.compose.ui.text.samples.SpanStyleSample
182      * @sample androidx.compose.ui.text.samples.AnnotatedStringBuilderSample
183      * @param color The color to draw the text.
184      * @param fontSize The size of glyphs (in logical pixels) to use when painting the text. This
185      *   may be [TextUnit.Unspecified] for inheriting from another [SpanStyle].
186      * @param fontWeight The typeface thickness to use when painting the text (e.g., bold).
187      * @param fontStyle The typeface variant to use when drawing the letters (e.g., italic).
188      * @param fontSynthesis Whether to synthesize font weight and/or style when the requested weight
189      *   or style cannot be found in the provided font family.
190      * @param fontFamily The font family to be used when rendering the text.
191      * @param fontFeatureSettings The advanced typography settings provided by font. The format is
192      *   the same as the CSS font-feature-settings attribute:
193      *   https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop
194      * @param letterSpacing The amount of space (in em) to add between each letter.
195      * @param baselineShift The amount by which the text is shifted up from the current baseline.
196      * @param textGeometricTransform The geometric transformation applied the text.
197      * @param localeList The locale list used to select region-specific glyphs.
198      * @param background The background color for the text.
199      * @param textDecoration The decorations to paint on the text (e.g., an underline).
200      * @param shadow The shadow effect applied on the text.
201      * @param platformStyle Platform specific [SpanStyle] parameters.
202      * @see AnnotatedString
203      * @see TextStyle
204      * @see ParagraphStyle
205      */
206     @Deprecated(
207         "SpanStyle constructors that do not take new stable parameters " +
208             "like PlatformStyle, DrawStyle are deprecated. Please use the new stable " +
209             "constructor.",
210         level = DeprecationLevel.HIDDEN
211     )
212     constructor(
213         color: Color = Color.Unspecified,
214         fontSize: TextUnit = TextUnit.Unspecified,
215         fontWeight: FontWeight? = null,
216         fontStyle: FontStyle? = null,
217         fontSynthesis: FontSynthesis? = null,
218         fontFamily: FontFamily? = null,
219         fontFeatureSettings: String? = null,
220         letterSpacing: TextUnit = TextUnit.Unspecified,
221         baselineShift: BaselineShift? = null,
222         textGeometricTransform: TextGeometricTransform? = null,
223         localeList: LocaleList? = null,
224         background: Color = Color.Unspecified,
225         textDecoration: TextDecoration? = null,
226         shadow: Shadow? = null,
227         platformStyle: PlatformSpanStyle? = null
228     ) : this(
229         textForegroundStyle = TextForegroundStyle.from(color),
230         fontSize = fontSize,
231         fontWeight = fontWeight,
232         fontStyle = fontStyle,
233         fontSynthesis = fontSynthesis,
234         fontFamily = fontFamily,
235         fontFeatureSettings = fontFeatureSettings,
236         letterSpacing = letterSpacing,
237         baselineShift = baselineShift,
238         textGeometricTransform = textGeometricTransform,
239         localeList = localeList,
240         background = background,
241         textDecoration = textDecoration,
242         shadow = shadow,
243         platformStyle = platformStyle
244     )
245 
246     /**
247      * Styling configuration for a text span. This configuration only allows character level
248      * styling, in order to set paragraph level styling such as line height, or text alignment
249      * please see [ParagraphStyle].
250      *
251      * @sample androidx.compose.ui.text.samples.SpanStyleSample
252      * @sample androidx.compose.ui.text.samples.AnnotatedStringBuilderSample
253      * @param color The color to draw the text.
254      * @param fontSize The size of glyphs (in logical pixels) to use when painting the text. This
255      *   may be [TextUnit.Unspecified] for inheriting from another [SpanStyle].
256      * @param fontWeight The typeface thickness to use when painting the text (e.g., bold).
257      * @param fontStyle The typeface variant to use when drawing the letters (e.g., italic).
258      * @param fontSynthesis Whether to synthesize font weight and/or style when the requested weight
259      *   or style cannot be found in the provided font family.
260      * @param fontFamily The font family to be used when rendering the text.
261      * @param fontFeatureSettings The advanced typography settings provided by font. The format is
262      *   the same as the CSS font-feature-settings attribute:
263      *   https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop
264      * @param letterSpacing The amount of space (in em) to add between each letter.
265      * @param baselineShift The amount by which the text is shifted up from the current baseline.
266      * @param textGeometricTransform The geometric transformation applied the text.
267      * @param localeList The locale list used to select region-specific glyphs.
268      * @param background The background color for the text.
269      * @param textDecoration The decorations to paint on the text (e.g., an underline).
270      * @param shadow The shadow effect applied on the text.
271      * @param platformStyle Platform specific [SpanStyle] parameters.
272      * @param drawStyle Drawing style of text, whether fill in the text while drawing or stroke
273      *   around the edges.
274      * @see AnnotatedString
275      * @see TextStyle
276      * @see ParagraphStyle
277      */
278     constructor(
279         color: Color = Color.Unspecified,
280         fontSize: TextUnit = TextUnit.Unspecified,
281         fontWeight: FontWeight? = null,
282         fontStyle: FontStyle? = null,
283         fontSynthesis: FontSynthesis? = null,
284         fontFamily: FontFamily? = null,
285         fontFeatureSettings: String? = null,
286         letterSpacing: TextUnit = TextUnit.Unspecified,
287         baselineShift: BaselineShift? = null,
288         textGeometricTransform: TextGeometricTransform? = null,
289         localeList: LocaleList? = null,
290         background: Color = Color.Unspecified,
291         textDecoration: TextDecoration? = null,
292         shadow: Shadow? = null,
293         platformStyle: PlatformSpanStyle? = null,
294         drawStyle: DrawStyle? = null
295     ) : this(
296         textForegroundStyle = TextForegroundStyle.from(color),
297         fontSize = fontSize,
298         fontWeight = fontWeight,
299         fontStyle = fontStyle,
300         fontSynthesis = fontSynthesis,
301         fontFamily = fontFamily,
302         fontFeatureSettings = fontFeatureSettings,
303         letterSpacing = letterSpacing,
304         baselineShift = baselineShift,
305         textGeometricTransform = textGeometricTransform,
306         localeList = localeList,
307         background = background,
308         textDecoration = textDecoration,
309         shadow = shadow,
310         platformStyle = platformStyle,
311         drawStyle = drawStyle
312     )
313 
314     /**
315      * Styling configuration for a text span. This configuration only allows character level
316      * styling, in order to set paragraph level styling such as line height, or text alignment
317      * please see [ParagraphStyle].
318      *
319      * @sample androidx.compose.ui.text.samples.SpanStyleBrushSample
320      * @sample androidx.compose.ui.text.samples.AnnotatedStringBuilderSample
321      * @param brush The brush to use when painting the text. If brush is given as null, it will be
322      *   treated as unspecified. It is equivalent to calling the alternative color constructor with
323      *   [Color.Unspecified]
324      * @param alpha Opacity to be applied to [brush] from 0.0f to 1.0f representing fully
325      *   transparent to fully opaque respectively.
326      * @param fontSize The size of glyphs (in logical pixels) to use when painting the text. This
327      *   may be [TextUnit.Unspecified] for inheriting from another [SpanStyle].
328      * @param fontWeight The typeface thickness to use when painting the text (e.g., bold).
329      * @param fontStyle The typeface variant to use when drawing the letters (e.g., italic).
330      * @param fontSynthesis Whether to synthesize font weight and/or style when the requested weight
331      *   or style cannot be found in the provided font family.
332      * @param fontFamily The font family to be used when rendering the text.
333      * @param fontFeatureSettings The advanced typography settings provided by font. The format is
334      *   the same as the CSS font-feature-settings attribute:
335      *   https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop
336      * @param letterSpacing The amount of space (in em) to add between each letter.
337      * @param baselineShift The amount by which the text is shifted up from the current baseline.
338      * @param textGeometricTransform The geometric transformation applied the text.
339      * @param localeList The locale list used to select region-specific glyphs.
340      * @param background The background color for the text.
341      * @param textDecoration The decorations to paint on the text (e.g., an underline).
342      * @param shadow The shadow effect applied on the text.
343      * @param platformStyle Platform specific [SpanStyle] parameters.
344      * @param drawStyle Drawing style of text, whether fill in the text while drawing or stroke
345      *   around the edges.
346      * @see AnnotatedString
347      * @see TextStyle
348      * @see ParagraphStyle
349      */
350     constructor(
351         brush: Brush?,
352         alpha: Float = Float.NaN,
353         fontSize: TextUnit = TextUnit.Unspecified,
354         fontWeight: FontWeight? = null,
355         fontStyle: FontStyle? = null,
356         fontSynthesis: FontSynthesis? = null,
357         fontFamily: FontFamily? = null,
358         fontFeatureSettings: String? = null,
359         letterSpacing: TextUnit = TextUnit.Unspecified,
360         baselineShift: BaselineShift? = null,
361         textGeometricTransform: TextGeometricTransform? = null,
362         localeList: LocaleList? = null,
363         background: Color = Color.Unspecified,
364         textDecoration: TextDecoration? = null,
365         shadow: Shadow? = null,
366         platformStyle: PlatformSpanStyle? = null,
367         drawStyle: DrawStyle? = null
368     ) : this(
369         textForegroundStyle = TextForegroundStyle.from(brush, alpha),
370         fontSize = fontSize,
371         fontWeight = fontWeight,
372         fontStyle = fontStyle,
373         fontSynthesis = fontSynthesis,
374         fontFamily = fontFamily,
375         fontFeatureSettings = fontFeatureSettings,
376         letterSpacing = letterSpacing,
377         baselineShift = baselineShift,
378         textGeometricTransform = textGeometricTransform,
379         localeList = localeList,
380         background = background,
381         textDecoration = textDecoration,
382         shadow = shadow,
383         platformStyle = platformStyle,
384         drawStyle = drawStyle
385     )
386 
387     /** Color to draw text. */
388     val color: Color
389         get() = this.textForegroundStyle.color
390 
391     /** Brush to draw text. If not null, overrides [color]. */
392     val brush: Brush?
393         get() = this.textForegroundStyle.brush
394 
395     /**
396      * Opacity of text. This value is either provided along side Brush, or via alpha channel in
397      * color.
398      */
399     val alpha: Float
400         get() = this.textForegroundStyle.alpha
401 
402     /**
403      * Returns a new span style that is a combination of this style and the given [other] style.
404      *
405      * [other] span style's null or inherit properties are replaced with the non-null properties of
406      * this span style. Another way to think of it is that the "missing" properties of the [other]
407      * style are _filled_ by the properties of this style.
408      *
409      * If the given span style is null, returns this span style.
410      */
411     @Stable
mergenull412     fun merge(other: SpanStyle? = null): SpanStyle {
413         if (other == null) return this
414         return fastMerge(
415             color = other.textForegroundStyle.color,
416             brush = other.textForegroundStyle.brush,
417             alpha = other.textForegroundStyle.alpha,
418             fontSize = other.fontSize,
419             fontWeight = other.fontWeight,
420             fontStyle = other.fontStyle,
421             fontSynthesis = other.fontSynthesis,
422             fontFamily = other.fontFamily,
423             fontFeatureSettings = other.fontFeatureSettings,
424             letterSpacing = other.letterSpacing,
425             baselineShift = other.baselineShift,
426             textGeometricTransform = other.textGeometricTransform,
427             localeList = other.localeList,
428             background = other.background,
429             textDecoration = other.textDecoration,
430             shadow = other.shadow,
431             platformStyle = other.platformStyle,
432             drawStyle = other.drawStyle
433         )
434     }
435 
436     /** Plus operator overload that applies a [merge]. */
plusnull437     @Stable operator fun plus(other: SpanStyle): SpanStyle = this.merge(other)
438 
439     @Deprecated(
440         "SpanStyle copy constructors that do not take new stable parameters " +
441             "like PlatformStyle, DrawStyle are deprecated. Please use the new stable " +
442             "copy constructor.",
443         level = DeprecationLevel.HIDDEN
444     )
445     fun copy(
446         color: Color = this.color,
447         fontSize: TextUnit = this.fontSize,
448         fontWeight: FontWeight? = this.fontWeight,
449         fontStyle: FontStyle? = this.fontStyle,
450         fontSynthesis: FontSynthesis? = this.fontSynthesis,
451         fontFamily: FontFamily? = this.fontFamily,
452         fontFeatureSettings: String? = this.fontFeatureSettings,
453         letterSpacing: TextUnit = this.letterSpacing,
454         baselineShift: BaselineShift? = this.baselineShift,
455         textGeometricTransform: TextGeometricTransform? = this.textGeometricTransform,
456         localeList: LocaleList? = this.localeList,
457         background: Color = this.background,
458         textDecoration: TextDecoration? = this.textDecoration,
459         shadow: Shadow? = this.shadow
460     ): SpanStyle {
461         return SpanStyle(
462             textForegroundStyle =
463                 if (color == this.color) {
464                     textForegroundStyle
465                 } else {
466                     TextForegroundStyle.from(color)
467                 },
468             fontSize = fontSize,
469             fontWeight = fontWeight,
470             fontStyle = fontStyle,
471             fontSynthesis = fontSynthesis,
472             fontFamily = fontFamily,
473             fontFeatureSettings = fontFeatureSettings,
474             letterSpacing = letterSpacing,
475             baselineShift = baselineShift,
476             textGeometricTransform = textGeometricTransform,
477             localeList = localeList,
478             background = background,
479             textDecoration = textDecoration,
480             shadow = shadow,
481             platformStyle = this.platformStyle,
482             drawStyle = this.drawStyle
483         )
484     }
485 
486     @Deprecated(
487         "SpanStyle copy constructors that do not take new stable parameters " +
488             "like PlatformStyle, DrawStyle are deprecated. Please use the new stable " +
489             "copy constructor.",
490         level = DeprecationLevel.HIDDEN
491     )
copynull492     fun copy(
493         color: Color = this.color,
494         fontSize: TextUnit = this.fontSize,
495         fontWeight: FontWeight? = this.fontWeight,
496         fontStyle: FontStyle? = this.fontStyle,
497         fontSynthesis: FontSynthesis? = this.fontSynthesis,
498         fontFamily: FontFamily? = this.fontFamily,
499         fontFeatureSettings: String? = this.fontFeatureSettings,
500         letterSpacing: TextUnit = this.letterSpacing,
501         baselineShift: BaselineShift? = this.baselineShift,
502         textGeometricTransform: TextGeometricTransform? = this.textGeometricTransform,
503         localeList: LocaleList? = this.localeList,
504         background: Color = this.background,
505         textDecoration: TextDecoration? = this.textDecoration,
506         shadow: Shadow? = this.shadow,
507         platformStyle: PlatformSpanStyle? = this.platformStyle
508     ): SpanStyle {
509         return SpanStyle(
510             textForegroundStyle =
511                 if (color == this.color) {
512                     textForegroundStyle
513                 } else {
514                     TextForegroundStyle.from(color)
515                 },
516             fontSize = fontSize,
517             fontWeight = fontWeight,
518             fontStyle = fontStyle,
519             fontSynthesis = fontSynthesis,
520             fontFamily = fontFamily,
521             fontFeatureSettings = fontFeatureSettings,
522             letterSpacing = letterSpacing,
523             baselineShift = baselineShift,
524             textGeometricTransform = textGeometricTransform,
525             localeList = localeList,
526             background = background,
527             textDecoration = textDecoration,
528             shadow = shadow,
529             platformStyle = platformStyle
530         )
531     }
532 
copynull533     fun copy(
534         color: Color = this.color,
535         fontSize: TextUnit = this.fontSize,
536         fontWeight: FontWeight? = this.fontWeight,
537         fontStyle: FontStyle? = this.fontStyle,
538         fontSynthesis: FontSynthesis? = this.fontSynthesis,
539         fontFamily: FontFamily? = this.fontFamily,
540         fontFeatureSettings: String? = this.fontFeatureSettings,
541         letterSpacing: TextUnit = this.letterSpacing,
542         baselineShift: BaselineShift? = this.baselineShift,
543         textGeometricTransform: TextGeometricTransform? = this.textGeometricTransform,
544         localeList: LocaleList? = this.localeList,
545         background: Color = this.background,
546         textDecoration: TextDecoration? = this.textDecoration,
547         shadow: Shadow? = this.shadow,
548         platformStyle: PlatformSpanStyle? = this.platformStyle,
549         drawStyle: DrawStyle? = this.drawStyle
550     ): SpanStyle {
551         return SpanStyle(
552             textForegroundStyle =
553                 if (color == this.color) {
554                     textForegroundStyle
555                 } else {
556                     TextForegroundStyle.from(color)
557                 },
558             fontSize = fontSize,
559             fontWeight = fontWeight,
560             fontStyle = fontStyle,
561             fontSynthesis = fontSynthesis,
562             fontFamily = fontFamily,
563             fontFeatureSettings = fontFeatureSettings,
564             letterSpacing = letterSpacing,
565             baselineShift = baselineShift,
566             textGeometricTransform = textGeometricTransform,
567             localeList = localeList,
568             background = background,
569             textDecoration = textDecoration,
570             shadow = shadow,
571             platformStyle = platformStyle,
572             drawStyle = drawStyle
573         )
574     }
575 
copynull576     fun copy(
577         brush: Brush?,
578         alpha: Float = this.alpha,
579         fontSize: TextUnit = this.fontSize,
580         fontWeight: FontWeight? = this.fontWeight,
581         fontStyle: FontStyle? = this.fontStyle,
582         fontSynthesis: FontSynthesis? = this.fontSynthesis,
583         fontFamily: FontFamily? = this.fontFamily,
584         fontFeatureSettings: String? = this.fontFeatureSettings,
585         letterSpacing: TextUnit = this.letterSpacing,
586         baselineShift: BaselineShift? = this.baselineShift,
587         textGeometricTransform: TextGeometricTransform? = this.textGeometricTransform,
588         localeList: LocaleList? = this.localeList,
589         background: Color = this.background,
590         textDecoration: TextDecoration? = this.textDecoration,
591         shadow: Shadow? = this.shadow,
592         platformStyle: PlatformSpanStyle? = this.platformStyle,
593         drawStyle: DrawStyle? = this.drawStyle
594     ): SpanStyle {
595         return SpanStyle(
596             textForegroundStyle = TextForegroundStyle.from(brush, alpha),
597             fontSize = fontSize,
598             fontWeight = fontWeight,
599             fontStyle = fontStyle,
600             fontSynthesis = fontSynthesis,
601             fontFamily = fontFamily,
602             fontFeatureSettings = fontFeatureSettings,
603             letterSpacing = letterSpacing,
604             baselineShift = baselineShift,
605             textGeometricTransform = textGeometricTransform,
606             localeList = localeList,
607             background = background,
608             textDecoration = textDecoration,
609             shadow = shadow,
610             platformStyle = platformStyle,
611             drawStyle = drawStyle
612         )
613     }
614 
equalsnull615     override fun equals(other: Any?): Boolean {
616         if (this === other) return true
617         if (other !is SpanStyle) return false
618         return hasSameLayoutAffectingAttributes(other) && hasSameNonLayoutAttributes(other)
619     }
620 
hasSameLayoutAffectingAttributesnull621     internal fun hasSameLayoutAffectingAttributes(other: SpanStyle): Boolean {
622         if (this === other) return true
623         if (fontSize != other.fontSize) return false
624         if (fontWeight != other.fontWeight) return false
625         if (fontStyle != other.fontStyle) return false
626         if (fontSynthesis != other.fontSynthesis) return false
627         if (fontFamily != other.fontFamily) return false
628         if (fontFeatureSettings != other.fontFeatureSettings) return false
629         if (letterSpacing != other.letterSpacing) return false
630         if (baselineShift != other.baselineShift) return false
631         if (textGeometricTransform != other.textGeometricTransform) return false
632         if (localeList != other.localeList) return false
633         if (background != other.background) return false
634         if (platformStyle != other.platformStyle) return false
635         return true
636     }
637 
hasSameNonLayoutAttributesnull638     internal fun hasSameNonLayoutAttributes(other: SpanStyle): Boolean {
639         if (textForegroundStyle != other.textForegroundStyle) return false
640         if (textDecoration != other.textDecoration) return false
641         if (shadow != other.shadow) return false
642         if (drawStyle != other.drawStyle) return false
643         return true
644     }
645 
hashCodenull646     override fun hashCode(): Int {
647         var result = color.hashCode()
648         result = 31 * result + brush.hashCode()
649         result = 31 * result + alpha.hashCode()
650         result = 31 * result + fontSize.hashCode()
651         result = 31 * result + (fontWeight?.hashCode() ?: 0)
652         result = 31 * result + (fontStyle?.hashCode() ?: 0)
653         result = 31 * result + (fontSynthesis?.hashCode() ?: 0)
654         result = 31 * result + (fontFamily?.hashCode() ?: 0)
655         result = 31 * result + (fontFeatureSettings?.hashCode() ?: 0)
656         result = 31 * result + letterSpacing.hashCode()
657         result = 31 * result + (baselineShift?.hashCode() ?: 0)
658         result = 31 * result + (textGeometricTransform?.hashCode() ?: 0)
659         result = 31 * result + (localeList?.hashCode() ?: 0)
660         result = 31 * result + background.hashCode()
661         result = 31 * result + (textDecoration?.hashCode() ?: 0)
662         result = 31 * result + (shadow?.hashCode() ?: 0)
663         result = 31 * result + (platformStyle?.hashCode() ?: 0)
664         result = 31 * result + (drawStyle?.hashCode() ?: 0)
665         return result
666     }
667 
hashCodeLayoutAffectingAttributesnull668     internal fun hashCodeLayoutAffectingAttributes(): Int {
669         var result = fontSize.hashCode()
670         result = 31 * result + (fontWeight?.hashCode() ?: 0)
671         result = 31 * result + (fontStyle?.hashCode() ?: 0)
672         result = 31 * result + (fontSynthesis?.hashCode() ?: 0)
673         result = 31 * result + (fontFamily?.hashCode() ?: 0)
674         result = 31 * result + (fontFeatureSettings?.hashCode() ?: 0)
675         result = 31 * result + letterSpacing.hashCode()
676         result = 31 * result + (baselineShift?.hashCode() ?: 0)
677         result = 31 * result + (textGeometricTransform?.hashCode() ?: 0)
678         result = 31 * result + (localeList?.hashCode() ?: 0)
679         result = 31 * result + background.hashCode()
680         result = 31 * result + (platformStyle?.hashCode() ?: 0)
681         return result
682     }
683 
toStringnull684     override fun toString(): String {
685         return "SpanStyle(" +
686             "color=$color, " +
687             "brush=$brush, " +
688             "alpha=$alpha, " +
689             "fontSize=$fontSize, " +
690             "fontWeight=$fontWeight, " +
691             "fontStyle=$fontStyle, " +
692             "fontSynthesis=$fontSynthesis, " +
693             "fontFamily=$fontFamily, " +
694             "fontFeatureSettings=$fontFeatureSettings, " +
695             "letterSpacing=$letterSpacing, " +
696             "baselineShift=$baselineShift, " +
697             "textGeometricTransform=$textGeometricTransform, " +
698             "localeList=$localeList, " +
699             "background=$background, " +
700             "textDecoration=$textDecoration, " +
701             "shadow=$shadow, " +
702             "platformStyle=$platformStyle, " +
703             "drawStyle=$drawStyle" +
704             ")"
705     }
706 }
707 
708 /**
709  * @param a An sp value. Maybe [TextUnit.Unspecified]
710  * @param b An sp value. Maybe [TextUnit.Unspecified]
711  */
lerpTextUnitInheritablenull712 internal fun lerpTextUnitInheritable(a: TextUnit, b: TextUnit, t: Float): TextUnit {
713     if (a.isUnspecified || b.isUnspecified) return lerpDiscrete(a, b, t)
714     return lerp(a, b, t)
715 }
716 
717 /**
718  * Lerp between two values that cannot be transitioned. Returns [a] if [fraction] is smaller than
719  * 0.5 otherwise [b].
720  */
lerpDiscretenull721 internal fun <T> lerpDiscrete(a: T, b: T, fraction: Float): T = if (fraction < 0.5) a else b
722 
723 /**
724  * Interpolate between two span styles.
725  *
726  * This will not work well if the styles don't set the same fields.
727  *
728  * The [fraction] argument represents position on the timeline, with 0.0 meaning that the
729  * interpolation has not started, returning [start] (or something equivalent to [start]), 1.0
730  * meaning that the interpolation has finished, returning [stop] (or something equivalent to
731  * [stop]), and values in between meaning that the interpolation is at the relevant point on the
732  * timeline between [start] and [stop]. The interpolation can be extrapolated beyond 0.0 and 1.0, so
733  * negative values and values greater than 1.0 are valid.
734  */
735 fun lerp(start: SpanStyle, stop: SpanStyle, fraction: Float): SpanStyle {
736     return SpanStyle(
737         textForegroundStyle = lerp(start.textForegroundStyle, stop.textForegroundStyle, fraction),
738         fontFamily = lerpDiscrete(start.fontFamily, stop.fontFamily, fraction),
739         fontSize = lerpTextUnitInheritable(start.fontSize, stop.fontSize, fraction),
740         fontWeight =
741             lerp(
742                 start.fontWeight ?: FontWeight.Normal,
743                 stop.fontWeight ?: FontWeight.Normal,
744                 fraction
745             ),
746         fontStyle = lerpDiscrete(start.fontStyle, stop.fontStyle, fraction),
747         fontSynthesis = lerpDiscrete(start.fontSynthesis, stop.fontSynthesis, fraction),
748         fontFeatureSettings =
749             lerpDiscrete(start.fontFeatureSettings, stop.fontFeatureSettings, fraction),
750         letterSpacing = lerpTextUnitInheritable(start.letterSpacing, stop.letterSpacing, fraction),
751         baselineShift =
752             lerp(
753                 start.baselineShift ?: BaselineShift(0f),
754                 stop.baselineShift ?: BaselineShift(0f),
755                 fraction
756             ),
757         textGeometricTransform =
758             lerp(
759                 start.textGeometricTransform ?: TextGeometricTransform.None,
760                 stop.textGeometricTransform ?: TextGeometricTransform.None,
761                 fraction
762             ),
763         localeList = lerpDiscrete(start.localeList, stop.localeList, fraction),
764         background = lerp(start.background, stop.background, fraction),
765         textDecoration = lerpDiscrete(start.textDecoration, stop.textDecoration, fraction),
766         shadow = lerp(start.shadow ?: Shadow(), stop.shadow ?: Shadow(), fraction),
767         platformStyle = lerpPlatformStyle(start.platformStyle, stop.platformStyle, fraction),
768         drawStyle = lerpDiscrete(start.drawStyle, stop.drawStyle, fraction)
769     )
770 }
771 
lerpPlatformStylenull772 private fun lerpPlatformStyle(
773     start: PlatformSpanStyle?,
774     stop: PlatformSpanStyle?,
775     fraction: Float
776 ): PlatformSpanStyle? {
777     if (start == null && stop == null) return null
778     val startNonNull = start ?: PlatformSpanStyle.Default
779     val stopNonNull = stop ?: PlatformSpanStyle.Default
780     return lerp(startNonNull, stopNonNull, fraction)
781 }
782 
resolveSpanStyleDefaultsnull783 internal fun resolveSpanStyleDefaults(style: SpanStyle) =
784     SpanStyle(
785         textForegroundStyle = style.textForegroundStyle.takeOrElse { DefaultColorForegroundStyle },
786         fontSize = if (style.fontSize.isUnspecified) DefaultFontSize else style.fontSize,
787         fontWeight = style.fontWeight ?: FontWeight.Normal,
788         fontStyle = style.fontStyle ?: FontStyle.Normal,
789         fontSynthesis = style.fontSynthesis ?: FontSynthesis.All,
790         fontFamily = style.fontFamily ?: FontFamily.Default,
791         fontFeatureSettings = style.fontFeatureSettings ?: "",
792         letterSpacing =
793             if (style.letterSpacing.isUnspecified) {
794                 DefaultLetterSpacing
795             } else {
796                 style.letterSpacing
797             },
798         baselineShift = style.baselineShift ?: BaselineShift.None,
799         textGeometricTransform = style.textGeometricTransform ?: TextGeometricTransform.None,
800         localeList = style.localeList ?: LocaleList.current,
<lambda>null801         background = style.background.takeOrElse { DefaultBackgroundColor },
802         textDecoration = style.textDecoration ?: TextDecoration.None,
803         shadow = style.shadow ?: Shadow.None,
804         platformStyle = style.platformStyle,
805         drawStyle = style.drawStyle ?: Fill
806     )
807 
fastMergenull808 internal fun SpanStyle.fastMerge(
809     color: Color,
810     brush: Brush?,
811     alpha: Float,
812     fontSize: TextUnit,
813     fontWeight: FontWeight?,
814     fontStyle: FontStyle?,
815     fontSynthesis: FontSynthesis?,
816     fontFamily: FontFamily?,
817     fontFeatureSettings: String?,
818     letterSpacing: TextUnit,
819     baselineShift: BaselineShift?,
820     textGeometricTransform: TextGeometricTransform?,
821     localeList: LocaleList?,
822     background: Color,
823     textDecoration: TextDecoration?,
824     shadow: Shadow?,
825     platformStyle: PlatformSpanStyle?,
826     drawStyle: DrawStyle?
827 ): SpanStyle {
828     // prioritize the parameters to Text in diffs here
829     /**
830      * color: Color fontSize: TextUnit fontStyle: FontStyle? fontWeight: FontWeight? fontFamily:
831      * FontFamily? letterSpacing: TextUnit textDecoration: TextDecoration? textAlign: TextAlign?
832      * lineHeight: TextUnit
833      */
834 
835     // any new vals should do a pre-merge check here
836     val requiresAlloc =
837         fontSize.isSpecified && fontSize != this.fontSize ||
838             brush == null && color.isSpecified && color != textForegroundStyle.color ||
839             fontStyle != null && fontStyle != this.fontStyle ||
840             fontWeight != null && fontWeight != this.fontWeight ||
841             // ref check for font-family, since we don't want to compare lists in fast path
842             fontFamily != null && fontFamily !== this.fontFamily ||
843             letterSpacing.isSpecified && letterSpacing != this.letterSpacing ||
844             textDecoration != null && textDecoration != this.textDecoration ||
845             // then compare the remaining params, for potential non-Text merges
846             brush != textForegroundStyle.brush ||
847             brush != null && alpha != this.textForegroundStyle.alpha ||
848             fontSynthesis != null && fontSynthesis != this.fontSynthesis ||
849             fontFeatureSettings != null && fontFeatureSettings != this.fontFeatureSettings ||
850             baselineShift != null && baselineShift != this.baselineShift ||
851             textGeometricTransform != null &&
852                 textGeometricTransform != this.textGeometricTransform ||
853             localeList != null && localeList != this.localeList ||
854             background.isSpecified && background != this.background ||
855             shadow != null && shadow != this.shadow ||
856             platformStyle != null && platformStyle != this.platformStyle ||
857             drawStyle != null && drawStyle != this.drawStyle
858 
859     if (!requiresAlloc) {
860         // we're done
861         return this
862     }
863 
864     val otherTextForegroundStyle =
865         if (brush != null) {
866             TextForegroundStyle.from(brush, alpha)
867         } else {
868             TextForegroundStyle.from(color)
869         }
870 
871     return SpanStyle(
872         textForegroundStyle = textForegroundStyle.merge(otherTextForegroundStyle),
873         fontFamily = fontFamily ?: this.fontFamily,
874         fontSize = if (!fontSize.isUnspecified) fontSize else this.fontSize,
875         fontWeight = fontWeight ?: this.fontWeight,
876         fontStyle = fontStyle ?: this.fontStyle,
877         fontSynthesis = fontSynthesis ?: this.fontSynthesis,
878         fontFeatureSettings = fontFeatureSettings ?: this.fontFeatureSettings,
879         letterSpacing =
880             if (!letterSpacing.isUnspecified) {
881                 letterSpacing
882             } else {
883                 this.letterSpacing
884             },
885         baselineShift = baselineShift ?: this.baselineShift,
886         textGeometricTransform = textGeometricTransform ?: this.textGeometricTransform,
887         localeList = localeList ?: this.localeList,
888         background = background.takeOrElse { this.background },
889         textDecoration = textDecoration ?: this.textDecoration,
890         shadow = shadow ?: this.shadow,
891         platformStyle = mergePlatformStyle(platformStyle),
892         drawStyle = drawStyle ?: this.drawStyle
893     )
894 }
895 
mergePlatformStylenull896 private fun SpanStyle.mergePlatformStyle(other: PlatformSpanStyle?): PlatformSpanStyle? {
897     if (platformStyle == null) return other
898     if (other == null) return platformStyle
899     return platformStyle.merge(other)
900 }
901