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