1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/// @docImport 'package:flutter/material.dart';
6library;
7
8import 'dart:collection';
9import 'dart:ui'
10 as ui
11 show ParagraphStyle, Shadow, StrutStyle, TextStyle, kTextHeightNone, lerpDouble;
12
13import 'package:flutter/foundation.dart';
14
15import 'basic_types.dart';
16import 'colors.dart';
17import 'strut_style.dart';
18import 'text_painter.dart';
19import 'text_scaler.dart';
20
21const String _kDefaultDebugLabel = 'unknown';
22
23const String _kColorForegroundWarning =
24 'Cannot provide both a color and a foreground\n'
25 'The color argument is just a shorthand for "foreground: Paint()..color = color".';
26
27const String _kColorBackgroundWarning =
28 'Cannot provide both a backgroundColor and a background\n'
29 'The backgroundColor argument is just a shorthand for "background: Paint()..color = color".';
30
31// Examples can assume:
32// late BuildContext context;
33
34/// An immutable style describing how to format and paint text.
35///
36/// {@youtube 560 315 https://www.youtube.com/watch?v=1z6YP7YmvwA}
37///
38/// ### Bold
39///
40/// {@tool snippet}
41/// Here, a single line of text in a [Text] widget is given a specific style
42/// override. The style is mixed with the ambient [DefaultTextStyle] by the
43/// [Text] widget.
44///
45/// ![Applying the style in this way creates bold text.](https://flutter.github.io/assets-for-api-docs/assets/painting/text_style_bold.png)
46///
47/// ```dart
48/// const Text(
49/// 'No, we need bold strokes. We need this plan.',
50/// style: TextStyle(fontWeight: FontWeight.bold),
51/// )
52/// ```
53/// {@end-tool}
54///
55/// ### Italics
56///
57/// {@tool snippet}
58/// As in the previous example, the [Text] widget is given a specific style
59/// override which is implicitly mixed with the ambient [DefaultTextStyle].
60///
61/// ![This results in italicized text.](https://flutter.github.io/assets-for-api-docs/assets/painting/text_style_italics.png)
62///
63/// ```dart
64/// const Text(
65/// "Welcome to the present, we're running a real nation.",
66/// style: TextStyle(fontStyle: FontStyle.italic),
67/// )
68/// ```
69/// {@end-tool}
70///
71/// ### Opacity and Color
72///
73/// Each line here is progressively more opaque. The base color is
74/// [Colors.black], and [Color.withOpacity] is used to create a
75/// derivative color with the desired opacity. The root [TextSpan] for this
76/// [RichText] widget is explicitly given the ambient [DefaultTextStyle], since
77/// [RichText] does not do that automatically. The inner [TextStyle] objects are
78/// implicitly mixed with the parent [TextSpan]'s [TextSpan.style].
79///
80/// If [color] is specified, [foreground] must be null and vice versa. [color] is
81/// treated as a shorthand for `Paint()..color = color`.
82///
83/// If [backgroundColor] is specified, [background] must be null and vice versa.
84/// The [backgroundColor] is treated as a shorthand for
85/// `background: Paint()..color = backgroundColor`.
86///
87/// ![This results in three lines of text that go from lighter to darker in color.](https://flutter.github.io/assets-for-api-docs/assets/painting/text_style_opacity_and_color.png)
88///
89/// ```dart
90/// RichText(
91/// text: TextSpan(
92/// style: DefaultTextStyle.of(context).style,
93/// children: <TextSpan>[
94/// TextSpan(
95/// text: "You don't have the votes.\n",
96/// style: TextStyle(color: Colors.black.withOpacity(0.6)),
97/// ),
98/// TextSpan(
99/// text: "You don't have the votes!\n",
100/// style: TextStyle(color: Colors.black.withOpacity(0.8)),
101/// ),
102/// TextSpan(
103/// text: "You're gonna need congressional approval and you don't have the votes!\n",
104/// style: TextStyle(color: Colors.black.withOpacity(1.0)),
105/// ),
106/// ],
107/// ),
108/// )
109/// ```
110///
111/// ### Size
112///
113/// {@tool snippet}
114/// In this example, the ambient [DefaultTextStyle] is explicitly manipulated to
115/// obtain a [TextStyle] that doubles the default font size.
116///
117/// ![This results in text that is twice as large as normal.](https://flutter.github.io/assets-for-api-docs/assets/painting/text_style_size.png)
118///
119/// ```dart
120/// Text(
121/// "These are wise words, enterprising men quote 'em.",
122/// style: DefaultTextStyle.of(context).style.apply(fontSizeFactor: 2.0),
123/// )
124/// ```
125/// {@end-tool}
126///
127/// ### Line height
128///
129/// By default, text will layout with line height as defined by the font.
130/// Font-metrics defined line height may be taller or shorter than the font size.
131/// The [height] property allows manual adjustment of the height of the line as
132/// a multiple of [fontSize]. For most fonts, setting [height] to 1.0 is not
133/// the same as omitting or setting height to null. The following diagram
134/// illustrates the difference between the font-metrics-defined line height and
135/// the line height produced with `height: 1.0` (also known as the EM-square):
136///
137/// ![With the font-metrics-defined line height, there is space between lines appropriate for the font, whereas the EM-square is only the height required to hold most of the characters.](https://flutter.github.io/assets-for-api-docs/assets/painting/text_height_diagram.png)
138///
139/// {@tool snippet}
140/// The [height] property can be used to change the line height. Here, the line
141/// height is set to 5 times the font size, so that the text is very spaced out.
142/// Since the `fontSize` is set to 10, the final height of the line is
143/// 50 pixels.
144///
145/// ```dart
146/// const Text(
147/// 'Ladies and gentlemen, you coulda been anywhere in the world tonight, but you’re here with us in New York City.',
148/// style: TextStyle(height: 5, fontSize: 10),
149/// )
150/// ```
151/// {@end-tool}
152///
153/// Examples of the resulting heights from different values of `TextStyle.height`:
154///
155/// ![Since the explicit line height is applied as a scale factor on the font-metrics-defined line height, the gap above the text grows faster, as the height grows, than the gap below the text.](https://flutter.github.io/assets-for-api-docs/assets/painting/text_height_comparison_diagram.png)
156///
157/// See [StrutStyle] for further control of line height at the paragraph level.
158///
159/// ### Leading Distribution and Trimming
160///
161/// [Leading](https://en.wikipedia.org/wiki/Leading) is the vertical space
162/// between glyphs from adjacent lines. Quantitatively, it is the line height
163/// (see the previous section) subtracted by the font's ascent and descent.
164/// It's possible to have a negative `Leading` if [height] is sufficiently
165/// small.
166///
167/// When the [height] multiplier is null, `leading` and how it is distributed
168/// is up to the font's
169/// [metrics](https://en.wikipedia.org/wiki/Typeface#Font_metrics).
170/// When the [height] multiplier is specified, the exact behavior can be
171/// configured via [leadingDistribution] and [TextPainter.textHeightBehavior].
172///
173/// ![In configuration 1 the line height is divided by the alphabetic baseline proportionally to the font's ascent and descent, in configuration 3 the glyphs are roughly centered within the line height, configuration 2 is similar to configuration 1 except the Text Top guide on the same line as the font's ascent](https://flutter.github.io/assets-for-api-docs/assets/painting/text_height_breakdown.png)
174///
175/// Above is a side-by-side comparison of different [leadingDistribution] and
176/// [TextPainter.textHeightBehavior] combinations.
177///
178/// * Configuration 1: The default. [leadingDistribution] is set to [TextLeadingDistribution.proportional].
179/// * Configuration 2: same as Configuration 1, except [TextHeightBehavior.applyHeightToFirstAscent] is set to false.
180/// * Configuration 3: [leadingDistribution] is set to [TextLeadingDistribution.even].
181/// * Configuration 4: same as Configuration 3, except [TextHeightBehavior.applyHeightToLastDescent] is set to false.
182///
183/// The [leadingDistribution] property controls how leading is distributed over
184/// and under the text. With [TextLeadingDistribution.proportional]
185/// (Configuration 1), `Top Leading : Bottom Leading = Font Ascent : Font
186/// Descent`, which also means the alphabetic baseline divides the line height
187/// into 2 parts proportional to the font's ascent and descent. With
188/// [TextLeadingDistribution.even] (Configuration 3), `Top Leading` equals
189/// `Bottom Leading`, and the glyphs are roughly centered within the allotted
190/// line height.
191///
192/// The [TextPainter.textHeightBehavior] is a property that controls leading at
193/// the paragraph level. The `applyHeightToFirstAscent` property is applied
194/// **after** [height] and [leadingDistribution]. Setting it to false trims the
195/// "Top Leading" of the text box to match the font's ascent if it's on the
196/// first line (see Configuration 2). Similarly setting
197/// `applyHeightToLastDescent` to false reduces "Bottom Leading" to 0 for the
198/// last line of text (Configuration 4).
199///
200/// ### Wavy red underline with black text
201///
202/// {@tool snippet}
203/// Styles can be combined. In this example, the misspelled word is drawn in
204/// black text and underlined with a wavy red line to indicate a spelling error.
205/// (The remainder is styled according to the Flutter default text styles, not
206/// the ambient [DefaultTextStyle], since no explicit style is given and
207/// [RichText] does not automatically use the ambient [DefaultTextStyle].)
208///
209/// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/text_style_wavy_red_underline.png)
210///
211/// ```dart
212/// RichText(
213/// text: const TextSpan(
214/// text: "Don't tax the South ",
215/// children: <TextSpan>[
216/// TextSpan(
217/// text: 'cuz',
218/// style: TextStyle(
219/// color: Colors.black,
220/// decoration: TextDecoration.underline,
221/// decorationColor: Colors.red,
222/// decorationStyle: TextDecorationStyle.wavy,
223/// ),
224/// ),
225/// TextSpan(
226/// text: ' we got it made in the shade',
227/// ),
228/// ],
229/// ),
230/// )
231/// ```
232/// {@end-tool}
233///
234/// ### Borders and stroke (Foreground)
235///
236/// {@tool snippet}
237/// To create bordered text, a [Paint] with [Paint.style] set to [PaintingStyle.stroke]
238/// should be provided as a [foreground] paint. The following example uses a [Stack]
239/// to produce a stroke and fill effect.
240///
241/// ![](https://flutter.github.io/assets-for-api-docs/assets/widgets/text_border.png)
242///
243/// ```dart
244/// Stack(
245/// children: <Widget>[
246/// // Stroked text as border.
247/// Text(
248/// 'Greetings, planet!',
249/// style: TextStyle(
250/// fontSize: 40,
251/// foreground: Paint()
252/// ..style = PaintingStyle.stroke
253/// ..strokeWidth = 6
254/// ..color = Colors.blue[700]!,
255/// ),
256/// ),
257/// // Solid text as fill.
258/// Text(
259/// 'Greetings, planet!',
260/// style: TextStyle(
261/// fontSize: 40,
262/// color: Colors.grey[300],
263/// ),
264/// ),
265/// ],
266/// )
267/// ```
268/// {@end-tool}
269///
270/// ### Gradients (Foreground)
271///
272/// {@tool snippet}
273/// The [foreground] property also allows effects such as gradients to be
274/// applied to the text. Here we provide a [Paint] with a [Gradient]
275/// shader.
276///
277/// ![](https://flutter.github.io/assets-for-api-docs/assets/widgets/text_gradient.png)
278///
279/// ```dart
280/// Text(
281/// 'Greetings, planet!',
282/// style: TextStyle(
283/// fontSize: 40,
284/// foreground: Paint()
285/// ..shader = ui.Gradient.linear(
286/// const Offset(0, 20),
287/// const Offset(150, 20),
288/// <Color>[
289/// Colors.red,
290/// Colors.yellow,
291/// ],
292/// )
293/// ),
294/// )
295/// ```
296/// {@end-tool}
297///
298/// ### Custom Fonts
299///
300/// Custom fonts can be declared in the `pubspec.yaml` file as shown below:
301///
302/// ```yaml
303/// flutter:
304/// fonts:
305/// - family: Raleway
306/// fonts:
307/// - asset: fonts/Raleway-Regular.ttf
308/// - asset: fonts/Raleway-Medium.ttf
309/// weight: 500
310/// - asset: assets/fonts/Raleway-SemiBold.ttf
311/// weight: 600
312/// - family: Schyler
313/// fonts:
314/// - asset: fonts/Schyler-Regular.ttf
315/// - asset: fonts/Schyler-Italic.ttf
316/// style: italic
317/// ```
318///
319/// The `family` property determines the name of the font, which you can use in
320/// the [fontFamily] argument. The `asset` property is a path to the font file,
321/// relative to the `pubspec.yaml` file. The `weight` property specifies the
322/// weight of the glyph outlines in the file as an integer multiple of 100
323/// between 100 and 900. This corresponds to the [FontWeight] class and can be
324/// used in the [fontWeight] argument. The `style` property specifies whether the
325/// outlines in the file are `italic` or `normal`. These values correspond to
326/// the [FontStyle] class and can be used in the [fontStyle] argument.
327///
328/// To select a custom font, create [TextStyle] using the [fontFamily]
329/// argument as shown in the example below:
330///
331/// {@tool snippet}
332/// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/text_style_custom_fonts.png)
333///
334/// ```dart
335/// const TextStyle(fontFamily: 'Raleway')
336/// ```
337/// {@end-tool}
338///
339/// To use a font family defined in a package, the `package` argument must be
340/// provided. For instance, suppose the font declaration above is in the
341/// `pubspec.yaml` of a package named `my_package` which the app depends on.
342/// Then creating the TextStyle is done as follows:
343///
344/// ```dart
345/// const TextStyle(fontFamily: 'Raleway', package: 'my_package')
346/// ```
347///
348/// If the package internally uses the font it defines, it should still specify
349/// the `package` argument when creating the text style as in the example above.
350///
351/// A package can also provide font files without declaring a font in its
352/// `pubspec.yaml`. These files should then be in the `lib/` folder of the
353/// package. The font files will not automatically be bundled in the app, instead
354/// the app can use these selectively when declaring a font. Suppose a package
355/// named `my_package` has:
356///
357/// lib/fonts/Raleway-Medium.ttf
358///
359/// Then the app can declare a font like in the example below:
360///
361/// ```yaml
362/// flutter:
363/// fonts:
364/// - family: Raleway
365/// fonts:
366/// - asset: assets/fonts/Raleway-Regular.ttf
367/// - asset: packages/my_package/fonts/Raleway-Medium.ttf
368/// weight: 500
369/// ```
370///
371/// The `lib/` is implied, so it should not be included in the asset path.
372///
373/// In this case, since the app locally defines the font, the TextStyle is
374/// created without the `package` argument:
375///
376/// {@tool snippet}
377/// ```dart
378/// const TextStyle(fontFamily: 'Raleway')
379/// ```
380/// {@end-tool}
381///
382/// #### Supported font formats
383///
384/// Font formats currently supported by Flutter:
385///
386/// * `.ttc`
387/// * `.ttf`
388/// * `.otf`
389///
390/// Flutter does not support `.woff` and `.woff2` fonts for all platforms.
391///
392/// ### Custom Font Fallback
393///
394/// A custom [fontFamilyFallback] list can be provided. The list should be an
395/// ordered list of strings of font family names in the order they will be attempted.
396///
397/// The fonts in [fontFamilyFallback] will be used only if the requested glyph is
398/// not present in the [fontFamily].
399///
400/// The fallback order is:
401///
402/// * [fontFamily]
403/// * [fontFamilyFallback] in order of first to last.
404/// * System fallback fonts which will vary depending on platform.
405///
406/// The glyph used will always be the first matching version in fallback order.
407///
408/// The [fontFamilyFallback] property is commonly used to specify different font
409/// families for multilingual text spans as well as separate fonts for glyphs such
410/// as emojis.
411///
412/// {@tool snippet}
413/// In the following example, any glyphs not present in the font `Raleway` will be attempted
414/// to be resolved with `Noto Sans CJK SC`, and then with `Noto Color Emoji`:
415///
416/// ```dart
417/// const TextStyle(
418/// fontFamily: 'Raleway',
419/// fontFamilyFallback: <String>[
420/// 'Noto Sans CJK SC',
421/// 'Noto Color Emoji',
422/// ],
423/// )
424/// ```
425/// {@end-tool}
426///
427/// If all custom fallback font families are exhausted and no match was found
428/// or no custom fallback was provided, the platform font fallback will be used.
429///
430/// ### Inconsistent platform fonts
431///
432/// By default, fonts differ depending on the platform.
433///
434/// * The default font-family for `Android`,`Fuchsia` and `Linux` is `Roboto`.
435/// * The default font-family for `iOS` is `SF Pro Display`/`SF Pro Text`.
436/// * The default font-family for `MacOS` is `.AppleSystemUIFont`.
437/// * The default font-family for `Windows` is `Segoe UI`.
438//
439// The implementation of these defaults can be found in:
440// /packages/flutter/lib/src/material/typography.dart
441///
442/// Since Flutter's font discovery for default fonts depends on the fonts present
443/// on the device, it is not safe to assume all default fonts will be available or
444/// consistent across devices.
445///
446/// A known example of this is that Samsung devices ship with a CJK font that has
447/// smaller line spacing than the Android default. This results in Samsung devices
448/// displaying more tightly spaced text than on other Android devices when no
449/// custom font is specified.
450///
451/// To avoid this, a custom font should be specified if absolute font consistency
452/// is required for your application.
453///
454/// See also:
455///
456/// * [Text], the widget for showing text in a single style.
457/// * [DefaultTextStyle], the widget that specifies the default text styles for
458/// [Text] widgets, configured using a [TextStyle].
459/// * [RichText], the widget for showing a paragraph of mix-style text.
460/// * [TextSpan], the class that wraps a [TextStyle] for the purposes of
461/// passing it to a [RichText].
462/// * [TextStyle](https://api.flutter.dev/flutter/dart-ui/TextStyle-class.html), the class in the [dart:ui] library.
463/// * Cookbook: [Use a custom font](https://docs.flutter.dev/cookbook/design/fonts)
464/// * Cookbook: [Use themes to share colors and font styles](https://docs.flutter.dev/cookbook/design/themes)
465@immutable
466class TextStyle with Diagnosticable {
467 /// Creates a text style.
468 ///
469 /// The `package` argument must be non-null if the font family is defined in a
470 /// package. It is combined with the `fontFamily` argument to set the
471 /// [fontFamily] property.
472 ///
473 /// On Apple devices the strings 'CupertinoSystemText' and
474 /// 'CupertinoSystemDisplay' are used in [fontFamily] as proxies for the
475 /// Apple system fonts. They currently redirect to the equivalent of SF Pro
476 /// Text and SF Pro Display respectively. 'CupertinoSystemText' is designed
477 /// for fonts below 20 point size, and 'CupertinoSystemDisplay' is recommended
478 /// for sizes 20 and above. When used on non-Apple platforms, these strings
479 /// will return the regular fallback font family instead.
480 const TextStyle({
481 this.inherit = true,
482 this.color,
483 this.backgroundColor,
484 this.fontSize,
485 this.fontWeight,
486 this.fontStyle,
487 this.letterSpacing,
488 this.wordSpacing,
489 this.textBaseline,
490 this.height,
491 this.leadingDistribution,
492 this.locale,
493 this.foreground,
494 this.background,
495 this.shadows,
496 this.fontFeatures,
497 this.fontVariations,
498 this.decoration,
499 this.decorationColor,
500 this.decorationStyle,
501 this.decorationThickness,
502 this.debugLabel,
503 String? fontFamily,
504 List<String>? fontFamilyFallback,
505 String? package,
506 this.overflow,
507 }) : fontFamily = package == null ? fontFamily : 'packages/$package/$fontFamily',
508 _fontFamilyFallback = fontFamilyFallback,
509 _package = package,
510 assert(color == null || foreground == null, _kColorForegroundWarning),
511 assert(backgroundColor == null || background == null, _kColorBackgroundWarning);
512
513 /// Whether null values in this [TextStyle] can be replaced with their value
514 /// in another [TextStyle] using [merge].
515 ///
516 /// The [merge] operation is not commutative: the [inherit] value of the
517 /// method argument decides whether the two [TextStyle]s can be combined
518 /// together. If it is false, the method argument [TextStyle] will be returned.
519 /// Otherwise, the combining is allowed, and the returned [TextStyle] inherits
520 /// the [inherit] value from the method receiver.
521 ///
522 /// This property does not affect the text style inheritance in an [InlineSpan]
523 /// tree: an [InlineSpan]'s text style is merged with that of an ancestor
524 /// [InlineSpan] if it has unspecified fields, regardless of its [inherit]
525 /// value.
526 ///
527 /// Properties that don't have explicit values or other default values to fall
528 /// back to will revert to the defaults: white in color, a font size of 14
529 /// pixels, in a sans-serif font face.
530 ///
531 /// See also:
532 /// * [TextStyle.merge], which can be used to combine properties from two
533 /// [TextStyle]s.
534 final bool inherit;
535
536 /// The color to use when painting the text.
537 ///
538 /// If [foreground] is specified, this value must be null. The [color] property
539 /// is shorthand for `Paint()..color = color`.
540 ///
541 /// In [merge], [apply], and [lerp], conflicts between [color] and [foreground]
542 /// specification are resolved in [foreground]'s favor - i.e. if [foreground] is
543 /// specified in one place, it will dominate [color] in another.
544 final Color? color;
545
546 /// The color to use as the background for the text.
547 ///
548 /// If [background] is specified, this value must be null. The
549 /// [backgroundColor] property is shorthand for
550 /// `background: Paint()..color = backgroundColor`.
551 ///
552 /// In [merge], [apply], and [lerp], conflicts between [backgroundColor] and [background]
553 /// specification are resolved in [background]'s favor - i.e. if [background] is
554 /// specified in one place, it will dominate [color] in another.
555 final Color? backgroundColor;
556
557 /// The name of the font to use when painting the text (e.g., Roboto).
558 ///
559 /// If the font is defined in a package, this will be prefixed with
560 /// 'packages/package_name/' (e.g. 'packages/cool_fonts/Roboto'). The
561 /// prefixing is done by the constructor when the `package` argument is
562 /// provided.
563 ///
564 /// The value provided in [fontFamily] will act as the preferred/first font
565 /// family that glyphs are looked for in, followed in order by the font families
566 /// in [fontFamilyFallback]. When [fontFamily] is null or not provided, the
567 /// first value in [fontFamilyFallback] acts as the preferred/first font
568 /// family. When neither is provided, then the default platform font will
569 /// be used.
570 ///
571 /// When running on Apple devices, the strings 'CupertinoSystemText' and
572 /// 'CupertinoSystemDisplay' are used as proxies for the Apple system fonts.
573 /// They currently redirect to the equivalent of SF Pro Text and SF Pro Display
574 /// respectively. 'CupertinoSystemText' is designed for fonts below 20 point
575 /// size, and 'CupertinoSystemDisplay' is recommended for sizes 20 and above.
576 /// When used on non-Apple platforms, these strings will return the regular
577 /// fallback font family instead.
578 final String? fontFamily;
579
580 /// The ordered list of font families to fall back on when a glyph cannot be
581 /// found in a higher priority font family.
582 ///
583 /// The value provided in [fontFamily] will act as the preferred/first font
584 /// family that glyphs are looked for in, followed in order by the font families
585 /// in [fontFamilyFallback]. If all font families are exhausted and no match
586 /// was found, the default platform font family will be used instead.
587 ///
588 /// When [fontFamily] is null or not provided, the first value in [fontFamilyFallback]
589 /// acts as the preferred/first font family. When neither is provided, then
590 /// the default platform font will be used. Providing an empty list or null
591 /// for this property is the same as omitting it.
592 ///
593 /// For example, if a glyph is not found in [fontFamily], then each font family
594 /// in [fontFamilyFallback] will be searched in order until it is found. If it
595 /// is not found, then a box will be drawn in its place.
596 ///
597 /// If the font is defined in a package, each font family in the list will be
598 /// prefixed with 'packages/package_name/' (e.g. 'packages/cool_fonts/Roboto').
599 /// The package name should be provided by the `package` argument in the
600 /// constructor.
601 List<String>? get fontFamilyFallback => _package == null
602 ? _fontFamilyFallback
603 : _fontFamilyFallback?.map((String str) => 'packages/$_package/$str').toList();
604 final List<String>? _fontFamilyFallback;
605
606 // This is stored in order to prefix the fontFamilies in _fontFamilyFallback
607 // in the [fontFamilyFallback] getter.
608 final String? _package;
609
610 /// The size of fonts (in logical pixels) to use when painting the text.
611 ///
612 /// The value specified matches the dimension of the
613 /// [em square](https://fonts.google.com/knowledge/glossary/em) of the
614 /// underlying font, and more often then not isn't exactly the height or the
615 /// width of glyphs in the font.
616 ///
617 /// During painting, the [fontSize] is multiplied by the current
618 /// `textScaleFactor` to let users make it easier to read text by increasing
619 /// its size.
620 ///
621 /// The [getParagraphStyle] method defaults to 14 logical pixels if [fontSize]
622 /// is set to null.
623 final double? fontSize;
624
625 /// The typeface thickness to use when painting the text (e.g., bold).
626 final FontWeight? fontWeight;
627
628 /// The typeface variant to use when drawing the letters (e.g., italics).
629 final FontStyle? fontStyle;
630
631 /// The amount of space (in logical pixels) to add between each letter.
632 /// A negative value can be used to bring the letters closer.
633 final double? letterSpacing;
634
635 /// The amount of space (in logical pixels) to add at each sequence of
636 /// white-space (i.e. between each word). A negative value can be used to
637 /// bring the words closer.
638 final double? wordSpacing;
639
640 /// The common baseline that should be aligned between this text span and its
641 /// parent text span, or, for the root text spans, with the line box.
642 final TextBaseline? textBaseline;
643
644 /// The height of this text span, as a multiple of the font size.
645 ///
646 /// When [height] is [kTextHeightNone], the line height will be determined by
647 /// the font's metrics directly, which may differ from the fontSize. Otherwise
648 /// the line height of the span of text will be a multiple of [fontSize],
649 /// and be exactly `fontSize * height` logical pixels tall.
650 ///
651 /// For most fonts, setting [height] to 1.0 is not the same as setting height
652 /// to [kTextHeightNone] because the [fontSize] sets the height of the EM-square,
653 /// which is different than the font provided metrics for line height. The
654 /// following diagram illustrates the difference between the font-metrics
655 /// defined line height and the line height produced with `height: 1.0`
656 /// (which forms the upper and lower edges of the EM-square):
657 ///
658 /// ![With the font-metrics-defined line height, there is space between lines appropriate for the font, whereas the EM-square is only the height required to hold most of the characters.](https://flutter.github.io/assets-for-api-docs/assets/painting/text_height_diagram.png)
659 ///
660 /// Examples of the resulting line heights from different values of `TextStyle.height`:
661 ///
662 /// ![Since the explicit line height is applied as a scale factor on the font-metrics-defined line height, the gap above the text grows faster, as the height grows, than the gap below the text.](https://flutter.github.io/assets-for-api-docs/assets/painting/text_height_comparison_diagram.png)
663 ///
664 /// See [StrutStyle] and [TextHeightBehavior] for further control of line
665 /// height at the paragraph level.
666 final double? height;
667
668 /// How the vertical space added by the [height] multiplier should be
669 /// distributed over and under the text.
670 ///
671 /// When a non-null [height] is specified, after accommodating the glyphs of
672 /// the text, the remaining vertical space from the allotted line height will
673 /// be distributed over and under the text, according to the
674 /// [leadingDistribution] property. See the [TextStyle] class's documentation
675 /// for an example.
676 ///
677 /// When [height] is null, [leadingDistribution] does not affect the text
678 /// layout.
679 ///
680 /// Defaults to null, which defers to the paragraph's
681 /// `ParagraphStyle.textHeightBehavior`'s [leadingDistribution].
682 final TextLeadingDistribution? leadingDistribution;
683
684 /// The locale used to select region-specific glyphs.
685 ///
686 /// This property is rarely set. Typically the locale used to select
687 /// region-specific glyphs is defined by the text widget's [BuildContext]
688 /// using `Localizations.localeOf(context)`. For example [RichText] defines
689 /// its locale this way. However, a rich text widget's [TextSpan]s could
690 /// specify text styles with different explicit locales in order to select
691 /// different region-specific glyphs for each text span.
692 final Locale? locale;
693
694 /// The paint drawn as a foreground for the text.
695 ///
696 /// The value should ideally be cached and reused each time if multiple text
697 /// styles are created with the same paint settings. Otherwise, each time it
698 /// will appear like the style changed, which will result in unnecessary
699 /// updates all the way through the framework.
700 ///
701 /// If [color] is specified, this value must be null. The [color] property
702 /// is shorthand for `Paint()..color = color`.
703 ///
704 /// In [merge], [apply], and [lerp], conflicts between [color] and [foreground]
705 /// specification are resolved in [foreground]'s favor - i.e. if [foreground] is
706 /// specified in one place, it will dominate [color] in another.
707 final Paint? foreground;
708
709 /// The paint drawn as a background for the text.
710 ///
711 /// The value should ideally be cached and reused each time if multiple text
712 /// styles are created with the same paint settings. Otherwise, each time it
713 /// will appear like the style changed, which will result in unnecessary
714 /// updates all the way through the framework.
715 ///
716 /// If [backgroundColor] is specified, this value must be null. The
717 /// [backgroundColor] property is shorthand for
718 /// `background: Paint()..color = backgroundColor`.
719 ///
720 /// In [merge], [apply], and [lerp], conflicts between [backgroundColor] and
721 /// [background] specification are resolved in [background]'s favor - i.e. if
722 /// [background] is specified in one place, it will dominate [backgroundColor]
723 /// in another.
724 final Paint? background;
725
726 /// The decorations to paint near the text (e.g., an underline).
727 ///
728 /// Multiple decorations can be applied using [TextDecoration.combine].
729 final TextDecoration? decoration;
730
731 /// The color in which to paint the text decorations.
732 final Color? decorationColor;
733
734 /// The style in which to paint the text decorations (e.g., dashed).
735 final TextDecorationStyle? decorationStyle;
736
737 /// The thickness of the decoration stroke as a multiplier of the thickness
738 /// defined by the font.
739 ///
740 /// The font provides a base stroke width for [decoration]s which scales off
741 /// of the [fontSize]. This property may be used to achieve a thinner or
742 /// thicker decoration stroke, without changing the [fontSize]. For example,
743 /// a [decorationThickness] of 2.0 will draw a decoration twice as thick as
744 /// the font defined decoration thickness.
745 ///
746 /// {@tool snippet}
747 /// To achieve a bolded strike-through, we can apply a thicker stroke for the
748 /// decoration.
749 ///
750 /// ```dart
751 /// const Text(
752 /// 'This has a very BOLD strike through!',
753 /// style: TextStyle(
754 /// decoration: TextDecoration.lineThrough,
755 /// decorationThickness: 2.85,
756 /// ),
757 /// )
758 /// ```
759 /// {@end-tool}
760 ///
761 /// {@tool snippet}
762 /// We can apply a very thin and subtle wavy underline (perhaps, when words
763 /// are misspelled) by using a [decorationThickness] < 1.0.
764 ///
765 /// ```dart
766 /// const Text(
767 /// 'oopsIforgottousespaces!',
768 /// style: TextStyle(
769 /// decoration: TextDecoration.underline,
770 /// decorationStyle: TextDecorationStyle.wavy,
771 /// decorationColor: Colors.red,
772 /// decorationThickness: 0.5,
773 /// ),
774 /// )
775 /// ```
776 /// {@end-tool}
777 ///
778 /// The default [decorationThickness] is 1.0, which will use the font's base
779 /// stroke thickness/width.
780 final double? decorationThickness;
781
782 /// A human-readable description of this text style.
783 ///
784 /// This property is maintained only in debug builds.
785 ///
786 /// When merging ([merge]), copying ([copyWith]), modifying using [apply], or
787 /// interpolating ([lerp]), the label of the resulting style is marked with
788 /// the debug labels of the original styles. This helps figuring out where a
789 /// particular text style came from.
790 ///
791 /// This property is not considered when comparing text styles using `==` or
792 /// [compareTo], and it does not affect [hashCode].
793 final String? debugLabel;
794
795 /// A list of [Shadow]s that will be painted underneath the text.
796 ///
797 /// Multiple shadows are supported to replicate lighting from multiple light
798 /// sources.
799 ///
800 /// Shadows must be in the same order for [TextStyle] to be considered as
801 /// equivalent as order produces differing transparency.
802 final List<Shadow>? shadows;
803
804 /// A list of [FontFeature]s that affect how the font selects glyphs.
805 ///
806 /// Some fonts support multiple variants of how a given character can be
807 /// rendered. For example, a font might provide both proportional and
808 /// tabular numbers, or it might offer versions of the zero digit with
809 /// and without slashes. [FontFeature]s can be used to select which of
810 /// these variants will be used for rendering.
811 ///
812 /// Font features are not interpolated by [lerp].
813 ///
814 /// See also:
815 ///
816 /// * [fontVariations], for font features that have continuous parameters.
817 final List<FontFeature>? fontFeatures;
818
819 /// A list of [FontVariation]s that affect how a variable font is rendered.
820 ///
821 /// Some fonts are variable fonts that can generate multiple font faces based
822 /// on the values of customizable attributes. For example, a variable font
823 /// may have a weight axis that can be set to a value between 1 and 1000.
824 /// [FontVariation]s can be used to select the values of these design axes.
825 ///
826 /// For example, to control the weight axis of the Roboto Slab variable font
827 /// (https://fonts.google.com/specimen/Roboto+Slab):
828 /// ```dart
829 /// const TextStyle(
830 /// fontFamily: 'RobotoSlab',
831 /// fontVariations: <FontVariation>[FontVariation('wght', 900.0)]
832 /// )
833 /// ```
834 ///
835 /// Font variations can be interpolated via [lerp]. This is fastest when the
836 /// same font variation axes are specified, in the same order, in both
837 /// [TextStyle] objects. See [lerpFontVariations].
838 ///
839 /// See also:
840 ///
841 /// * [fontFeatures], for font variations that have discrete values.
842 final List<FontVariation>? fontVariations;
843
844 /// How visual text overflow should be handled.
845 final TextOverflow? overflow;
846
847 // Return the original value of fontFamily, without the additional
848 // "packages/$_package/" prefix.
849 String? get _fontFamily {
850 if (_package != null) {
851 final String fontFamilyPrefix = 'packages/$_package/';
852 assert(fontFamily?.startsWith(fontFamilyPrefix) ?? true);
853 return fontFamily?.substring(fontFamilyPrefix.length);
854 }
855 return fontFamily;
856 }
857
858 /// Creates a copy of this text style but with the given fields replaced with
859 /// the new values.
860 ///
861 /// One of [color] or [foreground] must be null, and if this has [foreground]
862 /// specified it will be given preference over any color parameter.
863 ///
864 /// One of [backgroundColor] or [background] must be null, and if this has
865 /// [background] specified it will be given preference over any
866 /// backgroundColor parameter.
867 TextStyle copyWith({
868 bool? inherit,
869 Color? color,
870 Color? backgroundColor,
871 double? fontSize,
872 FontWeight? fontWeight,
873 FontStyle? fontStyle,
874 double? letterSpacing,
875 double? wordSpacing,
876 TextBaseline? textBaseline,
877 double? height,
878 TextLeadingDistribution? leadingDistribution,
879 Locale? locale,
880 Paint? foreground,
881 Paint? background,
882 List<Shadow>? shadows,
883 List<FontFeature>? fontFeatures,
884 List<FontVariation>? fontVariations,
885 TextDecoration? decoration,
886 Color? decorationColor,
887 TextDecorationStyle? decorationStyle,
888 double? decorationThickness,
889 String? debugLabel,
890 String? fontFamily,
891 List<String>? fontFamilyFallback,
892 String? package,
893 TextOverflow? overflow,
894 }) {
895 assert(color == null || foreground == null, _kColorForegroundWarning);
896 assert(backgroundColor == null || background == null, _kColorBackgroundWarning);
897 String? newDebugLabel;
898 assert(() {
899 if (debugLabel != null) {
900 newDebugLabel = debugLabel;
901 } else if (this.debugLabel != null) {
902 newDebugLabel = '(${this.debugLabel}).copyWith';
903 }
904 return true;
905 }());
906
907 return TextStyle(
908 inherit: inherit ?? this.inherit,
909 color: this.foreground == null && foreground == null ? color ?? this.color : null,
910 backgroundColor: this.background == null && background == null
911 ? backgroundColor ?? this.backgroundColor
912 : null,
913 fontSize: fontSize ?? this.fontSize,
914 fontWeight: fontWeight ?? this.fontWeight,
915 fontStyle: fontStyle ?? this.fontStyle,
916 letterSpacing: letterSpacing ?? this.letterSpacing,
917 wordSpacing: wordSpacing ?? this.wordSpacing,
918 textBaseline: textBaseline ?? this.textBaseline,
919 height: height ?? this.height,
920 leadingDistribution: leadingDistribution ?? this.leadingDistribution,
921 locale: locale ?? this.locale,
922 foreground: foreground ?? this.foreground,
923 background: background ?? this.background,
924 shadows: shadows ?? this.shadows,
925 fontFeatures: fontFeatures ?? this.fontFeatures,
926 fontVariations: fontVariations ?? this.fontVariations,
927 decoration: decoration ?? this.decoration,
928 decorationColor: decorationColor ?? this.decorationColor,
929 decorationStyle: decorationStyle ?? this.decorationStyle,
930 decorationThickness: decorationThickness ?? this.decorationThickness,
931 debugLabel: newDebugLabel,
932 fontFamily: fontFamily ?? _fontFamily,
933 fontFamilyFallback: fontFamilyFallback ?? _fontFamilyFallback,
934 package: package ?? _package,
935 overflow: overflow ?? this.overflow,
936 );
937 }
938
939 /// Creates a copy of this text style replacing or altering the specified
940 /// properties.
941 ///
942 /// The non-numeric properties [color], [fontFamily], [decoration],
943 /// [decorationColor] and [decorationStyle] are replaced with the new values.
944 ///
945 /// [foreground] will be given preference over [color] if it is not null and
946 /// [background] will be given preference over [backgroundColor] if it is not
947 /// null.
948 ///
949 /// The numeric properties are multiplied by the given factors and then
950 /// incremented by the given deltas.
951 ///
952 /// For example, `style.apply(fontSizeFactor: 2.0, fontSizeDelta: 1.0)` would
953 /// return a [TextStyle] whose [fontSize] is `style.fontSize * 2.0 + 1.0`.
954 ///
955 /// For the [fontWeight], the delta is applied to the [FontWeight] enum index
956 /// values, so that for instance `style.apply(fontWeightDelta: -2)` when
957 /// applied to a `style` whose [fontWeight] is [FontWeight.w500] will return a
958 /// [TextStyle] with a [FontWeight.w300].
959 ///
960 /// If the underlying values are null, then the corresponding factors and/or
961 /// deltas must not be specified. Additionally, if [height] is [kTextHeightNone]
962 /// it will not be modified by this method.
963 ///
964 /// If [foreground] is specified on this object, then applying [color] here
965 /// will have no effect and if [background] is specified on this object, then
966 /// applying [backgroundColor] here will have no effect either.
967 TextStyle apply({
968 Color? color,
969 Color? backgroundColor,
970 TextDecoration? decoration,
971 Color? decorationColor,
972 TextDecorationStyle? decorationStyle,
973 double decorationThicknessFactor = 1.0,
974 double decorationThicknessDelta = 0.0,
975 String? fontFamily,
976 List<String>? fontFamilyFallback,
977 double fontSizeFactor = 1.0,
978 double fontSizeDelta = 0.0,
979 int fontWeightDelta = 0,
980 FontStyle? fontStyle,
981 double letterSpacingFactor = 1.0,
982 double letterSpacingDelta = 0.0,
983 double wordSpacingFactor = 1.0,
984 double wordSpacingDelta = 0.0,
985 double heightFactor = 1.0,
986 double heightDelta = 0.0,
987 TextBaseline? textBaseline,
988 TextLeadingDistribution? leadingDistribution,
989 Locale? locale,
990 List<Shadow>? shadows,
991 List<FontFeature>? fontFeatures,
992 List<FontVariation>? fontVariations,
993 String? package,
994 TextOverflow? overflow,
995 }) {
996 assert(fontSize != null || (fontSizeFactor == 1.0 && fontSizeDelta == 0.0));
997 assert(fontWeight != null || fontWeightDelta == 0.0);
998 assert(letterSpacing != null || (letterSpacingFactor == 1.0 && letterSpacingDelta == 0.0));
999 assert(wordSpacing != null || (wordSpacingFactor == 1.0 && wordSpacingDelta == 0.0));
1000 assert(
1001 decorationThickness != null ||
1002 (decorationThicknessFactor == 1.0 && decorationThicknessDelta == 0.0),
1003 );
1004
1005 String? modifiedDebugLabel;
1006 assert(() {
1007 if (debugLabel != null) {
1008 modifiedDebugLabel = '($debugLabel).apply';
1009 }
1010 return true;
1011 }());
1012
1013 return TextStyle(
1014 inherit: inherit,
1015 color: foreground == null ? color ?? this.color : null,
1016 backgroundColor: background == null ? backgroundColor ?? this.backgroundColor : null,
1017 fontFamily: fontFamily ?? _fontFamily,
1018 fontFamilyFallback: fontFamilyFallback ?? _fontFamilyFallback,
1019 fontSize: fontSize == null ? null : fontSize! * fontSizeFactor + fontSizeDelta,
1020 fontWeight: fontWeight == null
1021 ? null
1022 : FontWeight.values[(fontWeight!.index + fontWeightDelta).clamp(
1023 0,
1024 FontWeight.values.length - 1,
1025 )],
1026 fontStyle: fontStyle ?? this.fontStyle,
1027 letterSpacing: letterSpacing == null
1028 ? null
1029 : letterSpacing! * letterSpacingFactor + letterSpacingDelta,
1030 wordSpacing: wordSpacing == null ? null : wordSpacing! * wordSpacingFactor + wordSpacingDelta,
1031 textBaseline: textBaseline ?? this.textBaseline,
1032 height: (height == null || height == ui.kTextHeightNone)
1033 ? height
1034 : height! * heightFactor + heightDelta,
1035 leadingDistribution: leadingDistribution ?? this.leadingDistribution,
1036 locale: locale ?? this.locale,
1037 foreground: foreground,
1038 background: background,
1039 shadows: shadows ?? this.shadows,
1040 fontFeatures: fontFeatures ?? this.fontFeatures,
1041 fontVariations: fontVariations ?? this.fontVariations,
1042 decoration: decoration ?? this.decoration,
1043 decorationColor: decorationColor ?? this.decorationColor,
1044 decorationStyle: decorationStyle ?? this.decorationStyle,
1045 decorationThickness: decorationThickness == null
1046 ? null
1047 : decorationThickness! * decorationThicknessFactor + decorationThicknessDelta,
1048 overflow: overflow ?? this.overflow,
1049 package: package ?? _package,
1050 debugLabel: modifiedDebugLabel,
1051 );
1052 }
1053
1054 /// Returns a new text style that is a combination of this style and the given
1055 /// [other] style.
1056 ///
1057 /// If the given [other] text style has its [TextStyle.inherit] set to true,
1058 /// its null properties are replaced with the non-null properties of this text
1059 /// style. The [other] style _inherits_ the properties of this style. Another
1060 /// way to think of it is that the "missing" properties of the [other] style
1061 /// are _filled_ by the properties of this style.
1062 ///
1063 /// If the given [other] text style has its [TextStyle.inherit] set to false,
1064 /// returns the given [other] style unchanged. The [other] style does not
1065 /// inherit properties of this style.
1066 ///
1067 /// If the given text style is null, returns this text style.
1068 ///
1069 /// One of [color] or [foreground] must be null, and if this or `other` has
1070 /// [foreground] specified it will be given preference over any color parameter.
1071 ///
1072 /// Similarly, one of [backgroundColor] or [background] must be null, and if
1073 /// this or `other` has [background] specified it will be given preference
1074 /// over any backgroundColor parameter.
1075 TextStyle merge(TextStyle? other) {
1076 if (other == null) {
1077 return this;
1078 }
1079 if (!other.inherit) {
1080 return other;
1081 }
1082
1083 String? mergedDebugLabel;
1084 assert(() {
1085 if (other.debugLabel != null || debugLabel != null) {
1086 mergedDebugLabel =
1087 '(${debugLabel ?? _kDefaultDebugLabel}).merge(${other.debugLabel ?? _kDefaultDebugLabel})';
1088 }
1089 return true;
1090 }());
1091
1092 return copyWith(
1093 color: other.color,
1094 backgroundColor: other.backgroundColor,
1095 fontSize: other.fontSize,
1096 fontWeight: other.fontWeight,
1097 fontStyle: other.fontStyle,
1098 letterSpacing: other.letterSpacing,
1099 wordSpacing: other.wordSpacing,
1100 textBaseline: other.textBaseline,
1101 height: other.height,
1102 leadingDistribution: other.leadingDistribution,
1103 locale: other.locale,
1104 foreground: other.foreground,
1105 background: other.background,
1106 shadows: other.shadows,
1107 fontFeatures: other.fontFeatures,
1108 fontVariations: other.fontVariations,
1109 decoration: other.decoration,
1110 decorationColor: other.decorationColor,
1111 decorationStyle: other.decorationStyle,
1112 decorationThickness: other.decorationThickness,
1113 debugLabel: mergedDebugLabel,
1114 fontFamily: other._fontFamily,
1115 fontFamilyFallback: other._fontFamilyFallback,
1116 package: other._package,
1117 overflow: other.overflow,
1118 );
1119 }
1120
1121 /// Interpolate between two text styles for animated transitions.
1122 ///
1123 /// Interpolation will not work well if the styles don't specify the same fields.
1124 /// When this happens, to keep the interpolated transition smooth, the
1125 /// implementation uses the non-null value throughout the transition for
1126 /// lerpable fields such as colors (for example, if one [TextStyle] specified
1127 /// `fontSize` but the other didn't, the returned [TextStyle] will use the
1128 /// `fontSize` from the [TextStyle] that specified it, regardless of the `t`
1129 /// value).
1130 ///
1131 /// This method throws when the given [TextStyle]s don't have the same
1132 /// [inherit] value and a lerpable field is missing from both [TextStyle]s,
1133 /// as that could result in jumpy transitions.
1134 ///
1135 /// {@macro dart.ui.shadow.lerp}
1136 ///
1137 /// If [foreground] is specified on either of `a` or `b`, both will be treated
1138 /// as if they have a [foreground] paint (creating a new [Paint] if necessary
1139 /// based on the [color] property).
1140 ///
1141 /// If [background] is specified on either of `a` or `b`, both will be treated
1142 /// as if they have a [background] paint (creating a new [Paint] if necessary
1143 /// based on the [backgroundColor] property).
1144 static TextStyle? lerp(TextStyle? a, TextStyle? b, double t) {
1145 if (identical(a, b)) {
1146 return a;
1147 }
1148 String? lerpDebugLabel;
1149 assert(() {
1150 lerpDebugLabel =
1151 'lerp(${a?.debugLabel ?? _kDefaultDebugLabel} ⎯${t.toStringAsFixed(1)}→ ${b?.debugLabel ?? _kDefaultDebugLabel})';
1152 return true;
1153 }());
1154
1155 if (a == null) {
1156 return TextStyle(
1157 inherit: b!.inherit,
1158 color: Color.lerp(null, b.color, t),
1159 backgroundColor: Color.lerp(null, b.backgroundColor, t),
1160 fontSize: t < 0.5 ? null : b.fontSize,
1161 fontWeight: FontWeight.lerp(null, b.fontWeight, t),
1162 fontStyle: t < 0.5 ? null : b.fontStyle,
1163 letterSpacing: t < 0.5 ? null : b.letterSpacing,
1164 wordSpacing: t < 0.5 ? null : b.wordSpacing,
1165 textBaseline: t < 0.5 ? null : b.textBaseline,
1166 height: t < 0.5 ? null : b.height,
1167 leadingDistribution: t < 0.5 ? null : b.leadingDistribution,
1168 locale: t < 0.5 ? null : b.locale,
1169 foreground: t < 0.5 ? null : b.foreground,
1170 background: t < 0.5 ? null : b.background,
1171 shadows: t < 0.5 ? null : b.shadows,
1172 fontFeatures: t < 0.5 ? null : b.fontFeatures,
1173 fontVariations: lerpFontVariations(null, b.fontVariations, t),
1174 decoration: t < 0.5 ? null : b.decoration,
1175 decorationColor: Color.lerp(null, b.decorationColor, t),
1176 decorationStyle: t < 0.5 ? null : b.decorationStyle,
1177 decorationThickness: t < 0.5 ? null : b.decorationThickness,
1178 debugLabel: lerpDebugLabel,
1179 fontFamily: t < 0.5 ? null : b._fontFamily,
1180 fontFamilyFallback: t < 0.5 ? null : b._fontFamilyFallback,
1181 package: t < 0.5 ? null : b._package,
1182 overflow: t < 0.5 ? null : b.overflow,
1183 );
1184 }
1185
1186 if (b == null) {
1187 return TextStyle(
1188 inherit: a.inherit,
1189 color: Color.lerp(a.color, null, t),
1190 backgroundColor: Color.lerp(null, a.backgroundColor, t),
1191 fontSize: t < 0.5 ? a.fontSize : null,
1192 fontWeight: FontWeight.lerp(a.fontWeight, null, t),
1193 fontStyle: t < 0.5 ? a.fontStyle : null,
1194 letterSpacing: t < 0.5 ? a.letterSpacing : null,
1195 wordSpacing: t < 0.5 ? a.wordSpacing : null,
1196 textBaseline: t < 0.5 ? a.textBaseline : null,
1197 height: t < 0.5 ? a.height : null,
1198 leadingDistribution: t < 0.5 ? a.leadingDistribution : null,
1199 locale: t < 0.5 ? a.locale : null,
1200 foreground: t < 0.5 ? a.foreground : null,
1201 background: t < 0.5 ? a.background : null,
1202 shadows: t < 0.5 ? a.shadows : null,
1203 fontFeatures: t < 0.5 ? a.fontFeatures : null,
1204 fontVariations: lerpFontVariations(a.fontVariations, null, t),
1205 decoration: t < 0.5 ? a.decoration : null,
1206 decorationColor: Color.lerp(a.decorationColor, null, t),
1207 decorationStyle: t < 0.5 ? a.decorationStyle : null,
1208 decorationThickness: t < 0.5 ? a.decorationThickness : null,
1209 debugLabel: lerpDebugLabel,
1210 fontFamily: t < 0.5 ? a._fontFamily : null,
1211 fontFamilyFallback: t < 0.5 ? a._fontFamilyFallback : null,
1212 package: t < 0.5 ? a._package : null,
1213 overflow: t < 0.5 ? a.overflow : null,
1214 );
1215 }
1216
1217 assert(() {
1218 if (a.inherit == b.inherit) {
1219 return true;
1220 }
1221
1222 final List<String> nullFields = <String>[
1223 if (a.foreground == null && b.foreground == null && a.color == null && b.color == null)
1224 'color',
1225 if (a.background == null &&
1226 b.background == null &&
1227 a.backgroundColor == null &&
1228 b.backgroundColor == null)
1229 'backgroundColor',
1230 if (a.fontSize == null && b.fontSize == null) 'fontSize',
1231 if (a.letterSpacing == null && b.letterSpacing == null) 'letterSpacing',
1232 if (a.wordSpacing == null && b.wordSpacing == null) 'wordSpacing',
1233 if (a.height == null && b.height == null) 'height',
1234 if (a.decorationColor == null && b.decorationColor == null) 'decorationColor',
1235 if (a.decorationThickness == null && b.decorationThickness == null) 'decorationThickness',
1236 ];
1237 if (nullFields.isEmpty) {
1238 return true;
1239 }
1240
1241 throw FlutterError.fromParts(<DiagnosticsNode>[
1242 ErrorSummary('Failed to interpolate TextStyles with different inherit values.'),
1243 ErrorSpacer(),
1244 ErrorDescription('The TextStyles being interpolated were:'),
1245 a.toDiagnosticsNode(name: 'from', style: DiagnosticsTreeStyle.singleLine),
1246 b.toDiagnosticsNode(name: 'to', style: DiagnosticsTreeStyle.singleLine),
1247 ErrorDescription(
1248 'The following fields are unspecified in both TextStyles:\n'
1249 '${nullFields.map((String name) => '"$name"').join(', ')}.\n'
1250 'When "inherit" changes during the transition, these fields may '
1251 'observe abrupt value changes as a result, causing "jump"s in the '
1252 'transition.',
1253 ),
1254 ErrorSpacer(),
1255 ErrorHint(
1256 'In general, TextStyle.lerp only works well when both TextStyles have '
1257 'the same "inherit" value, and specify the same fields.',
1258 ),
1259 ErrorHint(
1260 'If the TextStyles were directly created by you, consider bringing '
1261 'them to parity to ensure a smooth transition.',
1262 ),
1263 ErrorSpacer(),
1264 ErrorHint(
1265 'If one of the TextStyles being lerped is significantly more elaborate '
1266 'than the other, and has "inherited" set to false, it is often because '
1267 'it is merged with another TextStyle before being lerped. Comparing '
1268 'the "debugLabel"s of the two TextStyles may help identify if that was '
1269 'the case.',
1270 ),
1271 ErrorHint(
1272 'For example, you may see this error message when trying to lerp '
1273 'between "ThemeData()" and "Theme.of(context)". This is because '
1274 'TextStyles from "Theme.of(context)" are merged with TextStyles from '
1275 'another theme and thus are more elaborate than the TextStyles from '
1276 '"ThemeData()" (which is reflected in their "debugLabel"s -- '
1277 'TextStyles from "Theme.of(context)" should have labels in the form of '
1278 '"(<A TextStyle>).merge(<Another TextStyle>)"). It is recommended to '
1279 'only lerp ThemeData with matching TextStyles.',
1280 ),
1281 ]);
1282 }());
1283
1284 return TextStyle(
1285 inherit: t < 0.5 ? a.inherit : b.inherit,
1286 color: a.foreground == null && b.foreground == null ? Color.lerp(a.color, b.color, t) : null,
1287 backgroundColor: a.background == null && b.background == null
1288 ? Color.lerp(a.backgroundColor, b.backgroundColor, t)
1289 : null,
1290 fontSize: ui.lerpDouble(a.fontSize ?? b.fontSize, b.fontSize ?? a.fontSize, t),
1291 fontWeight: FontWeight.lerp(a.fontWeight, b.fontWeight, t),
1292 fontStyle: t < 0.5 ? a.fontStyle : b.fontStyle,
1293 letterSpacing: ui.lerpDouble(
1294 a.letterSpacing ?? b.letterSpacing,
1295 b.letterSpacing ?? a.letterSpacing,
1296 t,
1297 ),
1298 wordSpacing: ui.lerpDouble(a.wordSpacing ?? b.wordSpacing, b.wordSpacing ?? a.wordSpacing, t),
1299 textBaseline: t < 0.5 ? a.textBaseline : b.textBaseline,
1300 height: ui.lerpDouble(a.height ?? b.height, b.height ?? a.height, t),
1301 leadingDistribution: t < 0.5 ? a.leadingDistribution : b.leadingDistribution,
1302 locale: t < 0.5 ? a.locale : b.locale,
1303 foreground: (a.foreground != null || b.foreground != null)
1304 ? t < 0.5
1305 ? a.foreground ?? (Paint()..color = a.color!)
1306 : b.foreground ?? (Paint()..color = b.color!)
1307 : null,
1308 background: (a.background != null || b.background != null)
1309 ? t < 0.5
1310 ? a.background ?? (Paint()..color = a.backgroundColor!)
1311 : b.background ?? (Paint()..color = b.backgroundColor!)
1312 : null,
1313 shadows: ui.Shadow.lerpList(a.shadows, b.shadows, t),
1314 fontFeatures: t < 0.5 ? a.fontFeatures : b.fontFeatures,
1315 fontVariations: lerpFontVariations(a.fontVariations, b.fontVariations, t),
1316 decoration: t < 0.5 ? a.decoration : b.decoration,
1317 decorationColor: Color.lerp(a.decorationColor, b.decorationColor, t),
1318 decorationStyle: t < 0.5 ? a.decorationStyle : b.decorationStyle,
1319 decorationThickness: ui.lerpDouble(
1320 a.decorationThickness ?? b.decorationThickness,
1321 b.decorationThickness ?? a.decorationThickness,
1322 t,
1323 ),
1324 debugLabel: lerpDebugLabel,
1325 fontFamily: t < 0.5 ? a._fontFamily : b._fontFamily,
1326 fontFamilyFallback: t < 0.5 ? a._fontFamilyFallback : b._fontFamilyFallback,
1327 package: t < 0.5 ? a._package : b._package,
1328 overflow: t < 0.5 ? a.overflow : b.overflow,
1329 );
1330 }
1331
1332 /// The style information for text runs, encoded for use by `dart:ui`.
1333 ui.TextStyle getTextStyle({
1334 @Deprecated(
1335 'Use textScaler instead. '
1336 'Use of textScaleFactor was deprecated in preparation for the upcoming nonlinear text scaling support. '
1337 'This feature was deprecated after v3.12.0-2.0.pre.',
1338 )
1339 double textScaleFactor = 1.0,
1340 TextScaler textScaler = TextScaler.noScaling,
1341 }) {
1342 assert(
1343 identical(textScaler, TextScaler.noScaling) || textScaleFactor == 1.0,
1344 'textScaleFactor is deprecated and cannot be specified when textScaler is specified.',
1345 );
1346 final double? fontSize = switch (this.fontSize) {
1347 null => null,
1348 final double size when textScaler == TextScaler.noScaling => size * textScaleFactor,
1349 final double size => textScaler.scale(size),
1350 };
1351 return ui.TextStyle(
1352 color: color,
1353 decoration: decoration,
1354 decorationColor: decorationColor,
1355 decorationStyle: decorationStyle,
1356 decorationThickness: decorationThickness,
1357 fontWeight: fontWeight,
1358 fontStyle: fontStyle,
1359 textBaseline: textBaseline,
1360 leadingDistribution: leadingDistribution,
1361 fontFamily: fontFamily,
1362 fontFamilyFallback: fontFamilyFallback,
1363 fontSize: fontSize,
1364 letterSpacing: letterSpacing,
1365 wordSpacing: wordSpacing,
1366 height: height,
1367 locale: locale,
1368 foreground: foreground,
1369 background: switch ((background, backgroundColor)) {
1370 (final Paint paint, _) => paint,
1371 (_, final Color color) => Paint()..color = color,
1372 _ => null,
1373 },
1374 shadows: shadows,
1375 fontFeatures: fontFeatures,
1376 fontVariations: fontVariations,
1377 );
1378 }
1379
1380 /// The style information for paragraphs, encoded for use by `dart:ui`.
1381 ///
1382 /// If the `textScaleFactor` argument is omitted, it defaults to one. The
1383 /// other arguments may be null. The `maxLines` argument, if specified and
1384 /// non-null, must be greater than zero.
1385 ///
1386 /// If the font size on this style isn't set, it will default to 14 logical
1387 /// pixels.
1388 ui.ParagraphStyle getParagraphStyle({
1389 TextAlign? textAlign,
1390 TextDirection? textDirection,
1391 TextScaler textScaler = TextScaler.noScaling,
1392 String? ellipsis,
1393 int? maxLines,
1394 TextHeightBehavior? textHeightBehavior,
1395 Locale? locale,
1396 String? fontFamily,
1397 double? fontSize,
1398 FontWeight? fontWeight,
1399 FontStyle? fontStyle,
1400 double? height,
1401 StrutStyle? strutStyle,
1402 }) {
1403 assert(maxLines == null || maxLines > 0);
1404 final TextLeadingDistribution? leadingDistribution = this.leadingDistribution;
1405 final TextHeightBehavior? effectiveTextHeightBehavior =
1406 textHeightBehavior ??
1407 (leadingDistribution == null
1408 ? null
1409 : TextHeightBehavior(leadingDistribution: leadingDistribution));
1410
1411 return ui.ParagraphStyle(
1412 textAlign: textAlign,
1413 textDirection: textDirection,
1414 // Here, we establish the contents of this TextStyle as the paragraph's default font
1415 // unless an override is passed in.
1416 fontWeight: fontWeight ?? this.fontWeight,
1417 fontStyle: fontStyle ?? this.fontStyle,
1418 fontFamily: fontFamily ?? this.fontFamily,
1419 fontSize: textScaler.scale(fontSize ?? this.fontSize ?? kDefaultFontSize),
1420 height: height ?? this.height,
1421 textHeightBehavior: effectiveTextHeightBehavior,
1422 strutStyle: strutStyle == null
1423 ? null
1424 : ui.StrutStyle(
1425 fontFamily: strutStyle.fontFamily,
1426 fontFamilyFallback: strutStyle.fontFamilyFallback,
1427 fontSize: switch (strutStyle.fontSize) {
1428 null => null,
1429 final double unscaled => textScaler.scale(unscaled),
1430 },
1431 height: strutStyle.height,
1432 leading: strutStyle.leading,
1433 leadingDistribution: strutStyle.leadingDistribution,
1434 fontWeight: strutStyle.fontWeight,
1435 fontStyle: strutStyle.fontStyle,
1436 forceStrutHeight: strutStyle.forceStrutHeight,
1437 ),
1438 maxLines: maxLines,
1439 ellipsis: ellipsis,
1440 locale: locale,
1441 );
1442 }
1443
1444 /// Describe the difference between this style and another, in terms of how
1445 /// much damage it will make to the rendering.
1446 ///
1447 /// See also:
1448 ///
1449 /// * [TextSpan.compareTo], which does the same thing for entire [TextSpan]s.
1450 RenderComparison compareTo(TextStyle other) {
1451 if (identical(this, other)) {
1452 return RenderComparison.identical;
1453 }
1454 if (inherit != other.inherit ||
1455 fontFamily != other.fontFamily ||
1456 fontSize != other.fontSize ||
1457 fontWeight != other.fontWeight ||
1458 fontStyle != other.fontStyle ||
1459 letterSpacing != other.letterSpacing ||
1460 wordSpacing != other.wordSpacing ||
1461 textBaseline != other.textBaseline ||
1462 height != other.height ||
1463 leadingDistribution != other.leadingDistribution ||
1464 locale != other.locale ||
1465 foreground != other.foreground ||
1466 background != other.background ||
1467 !listEquals(shadows, other.shadows) ||
1468 !listEquals(fontFeatures, other.fontFeatures) ||
1469 !listEquals(fontVariations, other.fontVariations) ||
1470 !listEquals(fontFamilyFallback, other.fontFamilyFallback) ||
1471 overflow != other.overflow) {
1472 return RenderComparison.layout;
1473 }
1474 if (color != other.color ||
1475 backgroundColor != other.backgroundColor ||
1476 decoration != other.decoration ||
1477 decorationColor != other.decorationColor ||
1478 decorationStyle != other.decorationStyle ||
1479 decorationThickness != other.decorationThickness) {
1480 return RenderComparison.paint;
1481 }
1482 return RenderComparison.identical;
1483 }
1484
1485 @override
1486 bool operator ==(Object other) {
1487 if (identical(this, other)) {
1488 return true;
1489 }
1490 if (other.runtimeType != runtimeType) {
1491 return false;
1492 }
1493 return other is TextStyle &&
1494 other.inherit == inherit &&
1495 other.color == color &&
1496 other.backgroundColor == backgroundColor &&
1497 other.fontSize == fontSize &&
1498 other.fontWeight == fontWeight &&
1499 other.fontStyle == fontStyle &&
1500 other.letterSpacing == letterSpacing &&
1501 other.wordSpacing == wordSpacing &&
1502 other.textBaseline == textBaseline &&
1503 other.height == height &&
1504 other.leadingDistribution == leadingDistribution &&
1505 other.locale == locale &&
1506 other.foreground == foreground &&
1507 other.background == background &&
1508 listEquals(other.shadows, shadows) &&
1509 listEquals(other.fontFeatures, fontFeatures) &&
1510 listEquals(other.fontVariations, fontVariations) &&
1511 other.decoration == decoration &&
1512 other.decorationColor == decorationColor &&
1513 other.decorationStyle == decorationStyle &&
1514 other.decorationThickness == decorationThickness &&
1515 other.fontFamily == fontFamily &&
1516 listEquals(other.fontFamilyFallback, fontFamilyFallback) &&
1517 other._package == _package &&
1518 other.overflow == overflow;
1519 }
1520
1521 @override
1522 int get hashCode {
1523 final List<String>? fontFamilyFallback = this.fontFamilyFallback;
1524 final int fontHash = Object.hash(
1525 decorationStyle,
1526 decorationThickness,
1527 fontFamily,
1528 fontFamilyFallback == null ? null : Object.hashAll(fontFamilyFallback),
1529 _package,
1530 overflow,
1531 );
1532
1533 final List<Shadow>? shadows = this.shadows;
1534 final List<FontFeature>? fontFeatures = this.fontFeatures;
1535 final List<FontVariation>? fontVariations = this.fontVariations;
1536 return Object.hash(
1537 inherit,
1538 color,
1539 backgroundColor,
1540 fontSize,
1541 fontWeight,
1542 fontStyle,
1543 letterSpacing,
1544 wordSpacing,
1545 textBaseline,
1546 height,
1547 leadingDistribution,
1548 locale,
1549 foreground,
1550 background,
1551 shadows == null ? null : Object.hashAll(shadows),
1552 fontFeatures == null ? null : Object.hashAll(fontFeatures),
1553 fontVariations == null ? null : Object.hashAll(fontVariations),
1554 decoration,
1555 decorationColor,
1556 fontHash,
1557 );
1558 }
1559
1560 @override
1561 String toStringShort() => objectRuntimeType(this, 'TextStyle');
1562
1563 /// Adds all properties prefixing property names with the optional `prefix`.
1564 @override
1565 void debugFillProperties(DiagnosticPropertiesBuilder properties, {String prefix = ''}) {
1566 super.debugFillProperties(properties);
1567 if (debugLabel != null) {
1568 properties.add(MessageProperty('${prefix}debugLabel', debugLabel!));
1569 }
1570 final List<DiagnosticsNode> styles = <DiagnosticsNode>[
1571 ColorProperty('${prefix}color', color, defaultValue: null),
1572 ColorProperty('${prefix}backgroundColor', backgroundColor, defaultValue: null),
1573 StringProperty('${prefix}family', fontFamily, defaultValue: null, quoted: false),
1574 IterableProperty<String>('${prefix}familyFallback', fontFamilyFallback, defaultValue: null),
1575 DoubleProperty('${prefix}size', fontSize, defaultValue: null),
1576 ];
1577 String? weightDescription;
1578 if (fontWeight != null) {
1579 weightDescription = '${fontWeight!.index + 1}00';
1580 }
1581 // TODO(jacobr): switch this to use enumProperty which will either cause the
1582 // weight description to change to w600 from 600 or require existing
1583 // enumProperty to handle this special case.
1584 styles.add(
1585 DiagnosticsProperty<FontWeight>(
1586 '${prefix}weight',
1587 fontWeight,
1588 description: weightDescription,
1589 defaultValue: null,
1590 ),
1591 );
1592 styles.add(EnumProperty<FontStyle>('${prefix}style', fontStyle, defaultValue: null));
1593 styles.add(DoubleProperty('${prefix}letterSpacing', letterSpacing, defaultValue: null));
1594 styles.add(DoubleProperty('${prefix}wordSpacing', wordSpacing, defaultValue: null));
1595 styles.add(EnumProperty<TextBaseline>('${prefix}baseline', textBaseline, defaultValue: null));
1596 styles.add(DoubleProperty('${prefix}height', height, unit: 'x', defaultValue: null));
1597 styles.add(
1598 EnumProperty<TextLeadingDistribution>(
1599 '${prefix}leadingDistribution',
1600 leadingDistribution,
1601 defaultValue: null,
1602 ),
1603 );
1604 styles.add(DiagnosticsProperty<Locale>('${prefix}locale', locale, defaultValue: null));
1605 styles.add(DiagnosticsProperty<Paint>('${prefix}foreground', foreground, defaultValue: null));
1606 styles.add(DiagnosticsProperty<Paint>('${prefix}background', background, defaultValue: null));
1607 if (decoration != null ||
1608 decorationColor != null ||
1609 decorationStyle != null ||
1610 decorationThickness != null) {
1611 final List<String> decorationDescription = <String>[];
1612 if (decorationStyle != null) {
1613 decorationDescription.add(decorationStyle!.name);
1614 }
1615
1616 // Hide decorationColor from the default text view as it is shown in the
1617 // terse decoration summary as well.
1618 styles.add(
1619 ColorProperty(
1620 '${prefix}decorationColor',
1621 decorationColor,
1622 defaultValue: null,
1623 level: DiagnosticLevel.fine,
1624 ),
1625 );
1626
1627 if (decorationColor != null) {
1628 decorationDescription.add('$decorationColor');
1629 }
1630
1631 // Intentionally collide with the property 'decoration' added below.
1632 // Tools that show hidden properties could choose the first property
1633 // matching the name to disambiguate.
1634 styles.add(
1635 DiagnosticsProperty<TextDecoration>(
1636 '${prefix}decoration',
1637 decoration,
1638 defaultValue: null,
1639 level: DiagnosticLevel.hidden,
1640 ),
1641 );
1642 if (decoration != null) {
1643 decorationDescription.add('$decoration');
1644 }
1645 assert(decorationDescription.isNotEmpty);
1646 styles.add(MessageProperty('${prefix}decoration', decorationDescription.join(' ')));
1647 styles.add(
1648 DoubleProperty(
1649 '${prefix}decorationThickness',
1650 decorationThickness,
1651 unit: 'x',
1652 defaultValue: null,
1653 ),
1654 );
1655 }
1656
1657 final bool styleSpecified = styles.any(
1658 (DiagnosticsNode n) => !n.isFiltered(DiagnosticLevel.info),
1659 );
1660 properties.add(
1661 DiagnosticsProperty<bool>(
1662 '${prefix}inherit',
1663 inherit,
1664 level: (!styleSpecified && inherit) ? DiagnosticLevel.fine : DiagnosticLevel.info,
1665 ),
1666 );
1667 styles.forEach(properties.add);
1668
1669 if (!styleSpecified) {
1670 properties.add(
1671 FlagProperty(
1672 'inherit',
1673 value: inherit,
1674 ifTrue: '$prefix<all styles inherited>',
1675 ifFalse: '$prefix<no style specified>',
1676 ),
1677 );
1678 }
1679
1680 styles.add(EnumProperty<TextOverflow>('${prefix}overflow', overflow, defaultValue: null));
1681 }
1682}
1683
1684/// Interpolate between two lists of [FontVariation] objects.
1685///
1686/// Variations are paired by axis, and interpolated using [FontVariation.lerp].
1687///
1688/// Entries that are only present in one list are animated using a step-function
1689/// at t=0.5 that enables or disables the variation. This can be jarring and
1690/// largely defeats the point of animating font variations. For best results,
1691/// specify the same axes in both lists, and for best performance, specify them
1692/// in the same order.
1693///
1694/// ## Performance details
1695///
1696/// This algorithm is O(N), but the constant factor varies based on the input,
1697/// and that is probably more important (because typically N is going to be
1698/// tiny, like 1 or 2; at the time of writing, there are only about five defined
1699/// axes that fonts typically use!).
1700///
1701/// It is fastest when the lists contain the same axes ([FontVariation.axis]) in
1702/// the same order. The result is again in the same order, and no attempt is
1703/// made to detect or remove duplicates in this process. This is, by far, the
1704/// recommended way to use this algorithm.
1705///
1706/// When the order of items in the two input lists vary, the constant factor
1707/// increases substantially, as it involves creating two maps and a set,
1708/// inserting every variation in both lists into the maps and the set, and then
1709/// iterating over them to recreate the list.
1710///
1711/// In this case, the resulting order is arbitrary. Duplicates are dropped; in
1712/// each list, the last [FontVariation] for any particular axis is the one used
1713/// to compute the value for that axis. Values that only appear on one side are
1714/// interpolated using [FontVariation.lerp] against a null value, and resulting
1715/// null values are omitted from the resulting list.
1716///
1717/// When the lists begin with matching pairs of axes, the fast algorithm is used
1718/// up to the point where the lists diverge, and the more expensive algorithm
1719/// is used on the remaining entries.
1720///
1721/// See also:
1722///
1723/// * [TextStyle.lerp], which uses this function to handle
1724/// [TextStyle.fontVariations].
1725List<FontVariation>? lerpFontVariations(List<FontVariation>? a, List<FontVariation>? b, double t) {
1726 if (t == 0.0) {
1727 return a;
1728 }
1729 if (t == 1.0) {
1730 return b;
1731 }
1732 if (a == null || a.isEmpty || b == null || b.isEmpty) {
1733 // If one side is empty, that means anything on the other
1734 // side will use the null-to-something lerp, which is to
1735 // say, a step function at t=0.5.
1736 return t < 0.5 ? a : b;
1737 }
1738 assert(a.isNotEmpty && b.isNotEmpty);
1739 final List<FontVariation> result = <FontVariation>[];
1740 // First, try the efficient O(N) solution in the event that
1741 // the lists are compatible.
1742 int index = 0;
1743 final int minLength = a.length < b.length ? a.length : b.length;
1744 for (; index < minLength; index += 1) {
1745 if (a[index].axis != b[index].axis) {
1746 // The lists aren't compatible.
1747 break;
1748 }
1749 result.add(FontVariation.lerp(a[index], b[index], t)!);
1750 }
1751 final int maxLength = a.length > b.length ? a.length : b.length;
1752 if (index < maxLength) {
1753 // If we get here, we have found some case where we cannot
1754 // use the efficient approach.
1755 final Set<String> axes = HashSet<String>();
1756 final Map<String, FontVariation> aVariations = HashMap<String, FontVariation>();
1757 for (int indexA = index; indexA < a.length; indexA += 1) {
1758 aVariations[a[indexA].axis] = a[indexA];
1759 axes.add(a[indexA].axis);
1760 }
1761 final Map<String, FontVariation> bVariations = HashMap<String, FontVariation>();
1762 for (int indexB = index; indexB < b.length; indexB += 1) {
1763 bVariations[b[indexB].axis] = b[indexB];
1764 axes.add(b[indexB].axis);
1765 }
1766 for (final String axis in axes) {
1767 final FontVariation? variation = FontVariation.lerp(aVariations[axis], bVariations[axis], t);
1768 if (variation != null) {
1769 result.add(variation);
1770 }
1771 }
1772 }
1773 return result;
1774}
1775