text_style.dart 63.8 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6 7 8 9 10 11 12 13 14
import 'dart:ui' as ui show
  FontFeature,
  FontVariation,
  ParagraphStyle,
  Shadow,
  StrutStyle,
  TextHeightBehavior,
  TextLeadingDistribution,
  TextStyle,
  lerpDouble;
15

16 17
import 'package:flutter/foundation.dart';

18
import 'basic_types.dart';
19
import 'colors.dart';
20
import 'strut_style.dart';
21
import 'text_painter.dart';
22

23 24
const String _kDefaultDebugLabel = 'unknown';

25
const String _kColorForegroundWarning = 'Cannot provide both a color and a foreground\n'
26
    'The color argument is just a shorthand for "foreground: Paint()..color = color".';
27 28

const String _kColorBackgroundWarning = 'Cannot provide both a backgroundColor and a background\n'
29
    'The backgroundColor argument is just a shorthand for "background: Paint()..color = color".';
30

31 32 33 34 35
// The default font size if none is specified. This should be kept in
// sync with the default values in text_painter.dart, as well as the
// defaults set in the engine (eg, LibTxt's text_style.h, paragraph_style.h).
const double _kDefaultFontSize = 14.0;

36
// Examples can assume:
37
// late BuildContext context;
38

39
/// An immutable style describing how to format and paint text.
40
///
41 42
/// {@youtube 560 315 https://www.youtube.com/watch?v=1z6YP7YmvwA}
///
43
/// ### Bold
44
///
45
/// {@tool snippet}
46 47 48 49
/// Here, a single line of text in a [Text] widget is given a specific style
/// override. The style is mixed with the ambient [DefaultTextStyle] by the
/// [Text] widget.
///
50
/// ![Applying the style in this way creates bold text.](https://flutter.github.io/assets-for-api-docs/assets/painting/text_style_bold.png)
51
///
52
/// ```dart
53
/// const Text(
54
///   'No, we need bold strokes. We need this plan.',
55
///   style: TextStyle(fontWeight: FontWeight.bold),
56 57
/// )
/// ```
58
/// {@end-tool}
59
///
60
/// ### Italics
61
///
62
/// {@tool snippet}
63 64
/// As in the previous example, the [Text] widget is given a specific style
/// override which is implicitly mixed with the ambient [DefaultTextStyle].
65
///
66
/// ![This results in italicized text.](https://flutter.github.io/assets-for-api-docs/assets/painting/text_style_italics.png)
67
///
68
/// ```dart
69
/// const Text(
70
///   "Welcome to the present, we're running a real nation.",
71
///   style: TextStyle(fontStyle: FontStyle.italic),
72 73
/// )
/// ```
74
/// {@end-tool}
75
///
76
/// ### Opacity and Color
77
///
78 79 80 81 82 83 84 85 86
/// Each line here is progressively more opaque. The base color is
/// [material.Colors.black], and [Color.withOpacity] is used to create a
/// derivative color with the desired opacity. The root [TextSpan] for this
/// [RichText] widget is explicitly given the ambient [DefaultTextStyle], since
/// [RichText] does not do that automatically. The inner [TextStyle] objects are
/// implicitly mixed with the parent [TextSpan]'s [TextSpan.style].
///
/// If [color] is specified, [foreground] must be null and vice versa. [color] is
/// treated as a shorthand for `Paint()..color = color`.
87
///
88 89 90 91
/// If [backgroundColor] is specified, [background] must be null and vice versa.
/// The [backgroundColor] is treated as a shorthand for
/// `background: Paint()..color = backgroundColor`.
///
92
/// ![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)
93
///
94
/// ```dart
95 96 97
/// RichText(
///   text: TextSpan(
///     style: DefaultTextStyle.of(context).style,
98
///     children: <TextSpan>[
99
///       TextSpan(
100
///         text: "You don't have the votes.\n",
101
///         style: TextStyle(color: Colors.black.withOpacity(0.6)),
102
///       ),
103
///       TextSpan(
104
///         text: "You don't have the votes!\n",
105
///         style: TextStyle(color: Colors.black.withOpacity(0.8)),
106
///       ),
107
///       TextSpan(
108
///         text: "You're gonna need congressional approval and you don't have the votes!\n",
109
///         style: TextStyle(color: Colors.black.withOpacity(1.0)),
110 111 112 113 114 115
///       ),
///     ],
///   ),
/// )
/// ```
///
116
/// ### Size
117
///
118
/// {@tool snippet}
119 120 121
/// In this example, the ambient [DefaultTextStyle] is explicitly manipulated to
/// obtain a [TextStyle] that doubles the default font size.
///
122
/// ![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)
123
///
124
/// ```dart
125
/// Text(
126
///   "These are wise words, enterprising men quote 'em.",
127 128 129
///   style: DefaultTextStyle.of(context).style.apply(fontSizeFactor: 2.0),
/// )
/// ```
130
/// {@end-tool}
131
///
132
/// ### Line height
133
///
134 135 136 137 138
/// By default, text will layout with line height as defined by the font.
/// Font-metrics defined line height may be taller or shorter than the font size.
/// The [height] property allows manual adjustment of the height of the line as
/// a multiple of [fontSize]. For most fonts, setting [height] to 1.0 is not
/// the same as omitting or setting height to null. The following diagram
139
/// illustrates the difference between the font-metrics-defined line height and
140 141
/// the line height produced with `height: 1.0` (also known as the EM-square):
///
142
/// ![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)
143
///
144
/// {@tool snippet}
145
/// The [height] property can be used to change the line height. Here, the line
146
/// height is set to 5 times the font size, so that the text is very spaced out.
147 148
/// Since the `fontSize` is set to 10, the final height of the line is
/// 50 pixels.
149 150
///
/// ```dart
151
/// const Text(
152 153
///   'Ladies and gentlemen, you coulda been anywhere in the world tonight, but you’re here with us in New York City.',
///   style: TextStyle(height: 5, fontSize: 10),
154 155
/// )
/// ```
156
/// {@end-tool}
157
///
158 159
/// Examples of the resulting heights from different values of `TextStyle.height`:
///
160
/// ![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)
161
///
162 163
/// See [StrutStyle] for further control of line height at the paragraph level.
///
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
/// ### Leading Distribution and Trimming
///
/// [Leading](https://en.wikipedia.org/wiki/Leading) is the vertical space
/// between glyphs from adjacent lines. Quantitatively, it is the line height
/// (see the previous section) subtracted by the font's ascent and descent.
/// It's possible to have a negative `Leading` if [height] is sufficiently
/// small.
///
/// When the [height] multiplier is null, `leading` and how it is distributed
/// is up to the font's
/// [metrics](https://en.wikipedia.org/wiki/Typeface#Font_metrics).
/// When the [height] multiplier is specified, the exact behavior can be
/// configured via [leadingDistribution] and [TextPainter.textHeightBehavior].
///
/// ![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)
///
/// Above is a side-by-side comparison of different [leadingDistribution] and
/// [TextPainter.textHeightBehavior] combinations.
///
///  * Configuration 1: The default. [leadingDistribution] is set to [TextLeadingDistribution.proportional].
///  * Configuration 2: same as Configuration 1, except [TextHeightBehavior.applyHeightToFirstAscent] is set to false.
///  * Configuration 3: [leadingDistribution] is set to [TextLeadingDistribution.even].
///  * Configuration 4: same as Configuration 3, except [TextHeightBehavior.applyHeightToLastDescent] is set to false.
///
/// The [leadingDistribution] property controls how leading is distributed over
/// and under the text. With [TextLeadingDistribution.proportional]
/// (Configuration 1), `Top Leading : Bottom Leading = Font Ascent : Font
/// Descent`, which also means the alphabetic baseline divides the line height
/// into 2 parts proportional to the font's ascent and descent. With
/// [TextLeadingDistribution.even] (Configuration 3), `Top Leading` equals
/// `Bottom Leading`, and the glyphs are roughly centered within the allotted
/// line height.
///
/// The [TextPainter.textHeightBehavior] is a property that controls leading at
/// the paragraph level. The `applyHeightToFirstAscent` property is applied
/// **after** [height] and [leadingDistribution]. Setting it to false trims the
/// "Top Leading" of the text box to match the font's ascent if it's on the
/// first line (see Configuration 2). Similarly setting
/// `applyHeightToLastDescent` to false reduces "Bottom Leading" to 0 for the
/// last line of text (Configuration 4).
///
205
/// ### Wavy red underline with black text
206
///
207
/// {@tool snippet}
208 209 210 211 212
/// Styles can be combined. In this example, the misspelled word is drawn in
/// black text and underlined with a wavy red line to indicate a spelling error.
/// (The remainder is styled according to the Flutter default text styles, not
/// the ambient [DefaultTextStyle], since no explicit style is given and
/// [RichText] does not automatically use the ambient [DefaultTextStyle].)
213
///
214
/// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/text_style_wavy_red_underline.png)
215
///
216
/// ```dart
217
/// RichText(
218
///   text: const TextSpan(
219
///     text: "Don't tax the South ",
220
///     children: <TextSpan>[
221
///       TextSpan(
222
///         text: 'cuz',
223
///         style: TextStyle(
224 225 226 227 228 229
///           color: Colors.black,
///           decoration: TextDecoration.underline,
///           decorationColor: Colors.red,
///           decorationStyle: TextDecorationStyle.wavy,
///         ),
///       ),
230
///       TextSpan(
231 232 233 234 235 236
///         text: ' we got it made in the shade',
///       ),
///     ],
///   ),
/// )
/// ```
237
/// {@end-tool}
238
///
239
/// ### Borders and stroke (Foreground)
240
///
241
/// {@tool snippet}
242 243 244 245
/// To create bordered text, a [Paint] with [Paint.style] set to [PaintingStyle.stroke]
/// should be provided as a [foreground] paint. The following example uses a [Stack]
/// to produce a stroke and fill effect.
///
246
/// ![](https://flutter.github.io/assets-for-api-docs/assets/widgets/text_border.png)
247 248 249 250 251 252 253 254 255 256 257 258
///
/// ```dart
/// Stack(
///   children: <Widget>[
///     // Stroked text as border.
///     Text(
///       'Greetings, planet!',
///       style: TextStyle(
///         fontSize: 40,
///         foreground: Paint()
///           ..style = PaintingStyle.stroke
///           ..strokeWidth = 6
259
///           ..color = Colors.blue[700]!,
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274
///       ),
///     ),
///     // Solid text as fill.
///     Text(
///       'Greetings, planet!',
///       style: TextStyle(
///         fontSize: 40,
///         color: Colors.grey[300],
///       ),
///     ),
///   ],
/// )
/// ```
/// {@end-tool}
///
275
/// ### Gradients (Foreground)
276
///
277
/// {@tool snippet}
278 279 280 281
/// The [foreground] property also allows effects such as gradients to be
/// applied to the text. Here we provide a [Paint] with a [ui.Gradient]
/// shader.
///
282
/// ![](https://flutter.github.io/assets-for-api-docs/assets/widgets/text_gradient.png)
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302
///
/// ```dart
/// Text(
///   'Greetings, planet!',
///   style: TextStyle(
///     fontSize: 40,
///     foreground: Paint()
///       ..shader = ui.Gradient.linear(
///         const Offset(0, 20),
///         const Offset(150, 20),
///         <Color>[
///           Colors.red,
///           Colors.yellow,
///         ],
///       )
///   ),
/// )
/// ```
/// {@end-tool}
///
303
/// ### Custom Fonts
304 305 306
///
/// Custom fonts can be declared in the `pubspec.yaml` file as shown below:
///
307
/// ```yaml
308 309 310 311 312 313 314 315 316 317 318 319 320 321
/// flutter:
///   fonts:
///     - family: Raleway
///       fonts:
///         - asset: fonts/Raleway-Regular.ttf
///         - asset: fonts/Raleway-Medium.ttf
///           weight: 500
///         - asset: assets/fonts/Raleway-SemiBold.ttf
///           weight: 600
///      - family: Schyler
///        fonts:
///          - asset: fonts/Schyler-Regular.ttf
///          - asset: fonts/Schyler-Italic.ttf
///            style: italic
322
/// ```
323 324 325
///
/// The `family` property determines the name of the font, which you can use in
/// the [fontFamily] argument. The `asset` property is a path to the font file,
326
/// relative to the `pubspec.yaml` file. The `weight` property specifies the
327 328
/// weight of the glyph outlines in the file as an integer multiple of 100
/// between 100 and 900. This corresponds to the [FontWeight] class and can be
329
/// used in the [fontWeight] argument. The `style` property specifies whether the
330 331 332 333 334 335
/// outlines in the file are `italic` or `normal`. These values correspond to
/// the [FontStyle] class and can be used in the [fontStyle] argument.
///
/// To select a custom font, create [TextStyle] using the [fontFamily]
/// argument as shown in the example below:
///
336
/// {@tool snippet}
337
/// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/text_style_custom_fonts.png)
338
///
339 340 341
/// ```dart
/// const TextStyle(fontFamily: 'Raleway')
/// ```
342
/// {@end-tool}
343
///
344
/// To use a font family defined in a package, the `package` argument must be
345 346 347 348 349 350 351 352
/// provided. For instance, suppose the font declaration above is in the
/// `pubspec.yaml` of a package named `my_package` which the app depends on.
/// Then creating the TextStyle is done as follows:
///
/// ```dart
/// const TextStyle(fontFamily: 'Raleway', package: 'my_package')
/// ```
///
353 354
/// If the package internally uses the font it defines, it should still specify
/// the `package` argument when creating the text style as in the example above.
355
///
356 357 358 359
/// A package can also provide font files without declaring a font in its
/// `pubspec.yaml`. These files should then be in the `lib/` folder of the
/// package. The font files will not automatically be bundled in the app, instead
/// the app can use these selectively when declaring a font. Suppose a package
360
/// named `my_package` has:
361
///
362
///     lib/fonts/Raleway-Medium.ttf
363 364 365
///
/// Then the app can declare a font like in the example below:
///
366
/// ```yaml
367 368 369 370 371 372 373
/// flutter:
///   fonts:
///     - family: Raleway
///       fonts:
///         - asset: assets/fonts/Raleway-Regular.ttf
///         - asset: packages/my_package/fonts/Raleway-Medium.ttf
///           weight: 500
374
/// ```
375 376 377
///
/// The `lib/` is implied, so it should not be included in the asset path.
///
378
/// In this case, since the app locally defines the font, the TextStyle is
379 380
/// created without the `package` argument:
///
381
/// {@tool snippet}
382
/// ```dart
383 384
/// const TextStyle(fontFamily: 'Raleway')
/// ```
385 386
/// {@end-tool}
///
387 388
/// #### Supported font formats
///
389
/// Font formats currently supported by Flutter:
390 391 392 393 394 395 396
///
///  * `.ttc`
///  * `.ttf`
///  * `.otf`
///
/// Flutter does not support `.woff` and `.woff2` fonts for all platforms.
///
397 398 399 400 401 402 403 404 405 406 407 408
/// ### Custom Font Fallback
///
/// A custom [fontFamilyFallback] list can be provided. The list should be an
/// ordered list of strings of font family names in the order they will be attempted.
///
/// The fonts in [fontFamilyFallback] will be used only if the requested glyph is
/// not present in the [fontFamily].
///
/// The fallback order is:
///
///  * [fontFamily]
///  * [fontFamilyFallback] in order of first to last.
409
///  * System fallback fonts which will vary depending on platform.
410 411 412 413 414 415 416
///
/// The glyph used will always be the first matching version in fallback order.
///
/// The [fontFamilyFallback] property is commonly used to specify different font
/// families for multilingual text spans as well as separate fonts for glyphs such
/// as emojis.
///
417
/// {@tool snippet}
418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433
/// In the following example, any glyphs not present in the font `Raleway` will be attempted
/// to be resolved with `Noto Sans CJK SC`, and then with `Noto Color Emoji`:
///
/// ```dart
/// const TextStyle(
///   fontFamily: 'Raleway',
///   fontFamilyFallback: <String>[
///     'Noto Sans CJK SC',
///     'Noto Color Emoji',
///   ],
/// )
/// ```
/// {@end-tool}
///
/// If all custom fallback font families are exhausted and no match was found
/// or no custom fallback was provided, the platform font fallback will be used.
434
///
435 436
/// ### Inconsistent platform fonts
///
437 438 439 440 441 442 443 444 445 446
/// By default, fonts differ depending on the platform.
///
///  * The default font-family for `Android`,`Fuchsia` and `Linux` is `Roboto`.
///  * The default font-family for `iOS` is `.SF UI Display`/`.SF UI Text`.
///  * The default font-family for `MacOS` is `.AppleSystemUIFont`.
///  * The default font-family for `Windows` is `Segoe UI`.
//
// The implementation of these defaults can be found in:
// /packages/flutter/lib/src/material/typography.dart
///
447 448 449 450 451 452 453 454 455 456 457 458
/// Since Flutter's font discovery for default fonts depends on the fonts present
/// on the device, it is not safe to assume all default fonts will be available or
/// consistent across devices.
///
/// A known example of this is that Samsung devices ship with a CJK font that has
/// smaller line spacing than the Android default. This results in Samsung devices
/// displaying more tightly spaced text than on other Android devices when no
/// custom font is specified.
///
/// To avoid this, a custom font should be specified if absolute font consistency
/// is required for your application.
///
459 460
/// See also:
///
461
///  * [Text], the widget for showing text in a single style.
462 463
///  * [DefaultTextStyle], the widget that specifies the default text styles for
///    [Text] widgets, configured using a [TextStyle].
464
///  * [RichText], the widget for showing a paragraph of mix-style text.
465
///  * [TextSpan], the class that wraps a [TextStyle] for the purposes of
466 467
///    passing it to a [RichText].
///  * [TextStyle](https://api.flutter.dev/flutter/dart-ui/TextStyle-class.html), the class in the [dart:ui] library.
468 469
///  * Cookbook: [Use a custom font](https://flutter.dev/docs/cookbook/design/fonts)
///  * Cookbook: [Use themes to share colors and font styles](https://flutter.dev/docs/cookbook/design/themes)
470
@immutable
471
class TextStyle with Diagnosticable {
472
  /// Creates a text style.
473 474 475 476
  ///
  /// The `package` argument must be non-null if the font family is defined in a
  /// package. It is combined with the `fontFamily` argument to set the
  /// [fontFamily] property.
477
  const TextStyle({
478
    this.inherit = true,
479
    this.color,
480
    this.backgroundColor,
481 482
    this.fontSize,
    this.fontWeight,
483
    this.fontStyle,
484
    this.letterSpacing,
485
    this.wordSpacing,
486
    this.textBaseline,
487
    this.height,
488
    this.leadingDistribution,
489
    this.locale,
490
    this.foreground,
491
    this.background,
492
    this.shadows,
493
    this.fontFeatures,
494
    this.fontVariations,
495 496
    this.decoration,
    this.decorationColor,
497
    this.decorationStyle,
498
    this.decorationThickness,
499
    this.debugLabel,
500 501 502
    String? fontFamily,
    List<String>? fontFamilyFallback,
    String? package,
503
    this.overflow,
504
  }) : fontFamily = package == null ? fontFamily : 'packages/$package/$fontFamily',
505 506
       _fontFamilyFallback = fontFamilyFallback,
       _package = package,
507 508
       assert(color == null || foreground == null, _kColorForegroundWarning),
       assert(backgroundColor == null || background == null, _kColorBackgroundWarning);
509

510

511 512
  /// Whether null values in this [TextStyle] can be replaced with their value
  /// in another [TextStyle] using [merge].
513
  ///
514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531
  /// The [merge] operation is not commutative: the [inherit] value of the
  /// method argument decides whether the two [TextStyle]s can be combined
  /// together. If it is false, the method argument [TextStyle] will be returned.
  /// Otherwise, the combining is allowed, and the returned [TextStyle] inherits
  /// the [inherit] value from the method receiver.
  ///
  /// This property has no effect on [TextSpan]'s text style cascading: in a
  /// [TextSpan] tree, a [TextSpan]'s text style can be combined with that of an
  /// ancestor [TextSpan] if it has unspecified fields, regardless of its
  /// [inherit] value.
  ///
  /// Properties that don't have explicit values or other default values to fall
  /// back to will revert to the defaults: white in color, a font size of 14
  /// pixels, in a sans-serif font face.
  ///
  /// See also:
  ///  * [TextStyle.merge], which can be used to combine properties from two
  ///    [TextStyle]s.
532 533
  final bool inherit;

534
  /// The color to use when painting the text.
535
  ///
536
  /// If [foreground] is specified, this value must be null. The [color] property
537
  /// is shorthand for `Paint()..color = color`.
538
  ///
539 540 541
  /// In [merge], [apply], and [lerp], conflicts between [color] and [foreground]
  /// specification are resolved in [foreground]'s favor - i.e. if [foreground] is
  /// specified in one place, it will dominate [color] in another.
542
  final Color? color;
543

544 545 546 547 548 549 550 551 552
  /// The color to use as the background for the text.
  ///
  /// If [background] is specified, this value must be null. The
  /// [backgroundColor] property is shorthand for
  /// `background: Paint()..color = backgroundColor`.
  ///
  /// In [merge], [apply], and [lerp], conflicts between [backgroundColor] and [background]
  /// specification are resolved in [background]'s favor - i.e. if [background] is
  /// specified in one place, it will dominate [color] in another.
553
  final Color? backgroundColor;
554

555 556 557
  /// The name of the font to use when painting the text (e.g., Roboto).
  ///
  /// If the font is defined in a package, this will be prefixed with
558 559 560
  /// 'packages/package_name/' (e.g. 'packages/cool_fonts/Roboto'). The
  /// prefixing is done by the constructor when the `package` argument is
  /// provided.
561 562 563 564 565 566 567
  ///
  /// The value provided in [fontFamily] will act as the preferred/first font
  /// family that glyphs are looked for in, followed in order by the font families
  /// in [fontFamilyFallback]. When [fontFamily] is null or not provided, the
  /// first value in [fontFamilyFallback] acts as the preferred/first font
  /// family. When neither is provided, then the default platform font will
  /// be used.
568
  final String? fontFamily;
569

570 571 572 573 574 575 576 577 578 579
  /// The ordered list of font families to fall back on when a glyph cannot be
  /// found in a higher priority font family.
  ///
  /// The value provided in [fontFamily] will act as the preferred/first font
  /// family that glyphs are looked for in, followed in order by the font families
  /// in [fontFamilyFallback]. If all font families are exhausted and no match
  /// was found, the default platform font family will be used instead.
  ///
  /// When [fontFamily] is null or not provided, the first value in [fontFamilyFallback]
  /// acts as the preferred/first font family. When neither is provided, then
580
  /// the default platform font will be used. Providing an empty list or null
581 582 583 584 585 586 587 588 589 590
  /// for this property is the same as omitting it.
  ///
  /// For example, if a glyph is not found in [fontFamily], then each font family
  /// in [fontFamilyFallback] will be searched in order until it is found. If it
  /// is not found, then a box will be drawn in its place.
  ///
  /// If the font is defined in a package, each font family in the list will be
  /// prefixed with 'packages/package_name/' (e.g. 'packages/cool_fonts/Roboto').
  /// The package name should be provided by the `package` argument in the
  /// constructor.
591
  List<String>? get fontFamilyFallback => _package == null ? _fontFamilyFallback : _fontFamilyFallback?.map((String str) => 'packages/$_package/$str').toList();
592
  final List<String>? _fontFamilyFallback;
593 594 595

  // This is stored in order to prefix the fontFamilies in _fontFamilyFallback
  // in the [fontFamilyFallback] getter.
596
  final String? _package;
597

598
  /// The size of glyphs (in logical pixels) to use when painting the text.
599 600 601 602
  ///
  /// During painting, the [fontSize] is multiplied by the current
  /// `textScaleFactor` to let users make it easier to read text by increasing
  /// its size.
603 604 605
  ///
  /// [getParagraphStyle] will default to 14 logical pixels if the font size
  /// isn't specified here.
606
  final double? fontSize;
607

608
  /// The typeface thickness to use when painting the text (e.g., bold).
609
  final FontWeight? fontWeight;
610

611
  /// The typeface variant to use when drawing the letters (e.g., italics).
612
  final FontStyle? fontStyle;
613

614
  /// The amount of space (in logical pixels) to add between each letter.
xster's avatar
xster committed
615
  /// A negative value can be used to bring the letters closer.
616
  final double? letterSpacing;
617

618 619 620
  /// The amount of space (in logical pixels) to add at each sequence of
  /// white-space (i.e. between each word). A negative value can be used to
  /// bring the words closer.
621
  final double? wordSpacing;
622

623 624
  /// The common baseline that should be aligned between this text span and its
  /// parent text span, or, for the root text spans, with the line box.
625
  final TextBaseline? textBaseline;
626

627 628
  /// The height of this text span, as a multiple of the font size.
  ///
629 630 631 632 633 634 635 636 637 638 639 640 641
  /// When [height] is null or omitted, the line height will be determined
  /// by the font's metrics directly, which may differ from the fontSize.
  /// When [height] is non-null, the line height of the span of text will be a
  /// multiple of [fontSize] and be exactly `fontSize * height` logical pixels
  /// tall.
  ///
  /// For most fonts, setting [height] to 1.0 is not the same as omitting or
  /// setting height to null because the [fontSize] sets the height of the EM-square,
  /// which is different than the font provided metrics for line height. The
  /// following diagram illustrates the difference between the font-metrics
  /// defined line height and the line height produced with `height: 1.0`
  /// (which forms the upper and lower edges of the EM-square):
  ///
642
  /// ![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)
643 644 645
  ///
  /// Examples of the resulting line heights from different values of `TextStyle.height`:
  ///
646
  /// ![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)
647
  ///
648 649
  /// See [StrutStyle] and [TextHeightBehavior] for further control of line
  /// height at the paragraph level.
650
  final double? height;
651

652 653 654 655 656 657
  /// How the vertical space added by the [height] multiplier should be
  /// distributed over and under the text.
  ///
  /// When a non-null [height] is specified, after accommodating the glyphs of
  /// the text, the remaining vertical space from the allotted line height will
  /// be distributed over and under the text, according to the
658 659
  /// [leadingDistribution] property. See the [TextStyle] class's documentation
  /// for an example.
660 661 662 663 664
  ///
  /// When [height] is null, [leadingDistribution] does not affect the text
  /// layout.
  ///
  /// Defaults to null, which defers to the paragraph's
665
  /// `ParagraphStyle.textHeightBehavior`'s [leadingDistribution].
666 667
  final ui.TextLeadingDistribution? leadingDistribution;

668
  /// The locale used to select region-specific glyphs.
669 670 671 672
  ///
  /// This property is rarely set. Typically the locale used to select
  /// region-specific glyphs is defined by the text widget's [BuildContext]
  /// using `Localizations.localeOf(context)`. For example [RichText] defines
673 674 675
  /// its locale this way. However, a rich text widget's [TextSpan]s could
  /// specify text styles with different explicit locales in order to select
  /// different region-specific glyphs for each text span.
676
  final Locale? locale;
677

678 679 680 681 682 683
  /// The paint drawn as a foreground for the text.
  ///
  /// The value should ideally be cached and reused each time if multiple text
  /// styles are created with the same paint settings. Otherwise, each time it
  /// will appear like the style changed, which will result in unnecessary
  /// updates all the way through the framework.
684
  ///
685
  /// If [color] is specified, this value must be null. The [color] property
686
  /// is shorthand for `Paint()..color = color`.
687
  ///
688 689 690
  /// In [merge], [apply], and [lerp], conflicts between [color] and [foreground]
  /// specification are resolved in [foreground]'s favor - i.e. if [foreground] is
  /// specified in one place, it will dominate [color] in another.
691
  final Paint? foreground;
692

693 694 695 696 697 698
  /// The paint drawn as a background for the text.
  ///
  /// The value should ideally be cached and reused each time if multiple text
  /// styles are created with the same paint settings. Otherwise, each time it
  /// will appear like the style changed, which will result in unnecessary
  /// updates all the way through the framework.
699 700 701 702 703 704 705 706 707
  ///
  /// If [backgroundColor] is specified, this value must be null. The
  /// [backgroundColor] property is shorthand for
  /// `background: Paint()..color = backgroundColor`.
  ///
  /// In [merge], [apply], and [lerp], conflicts between [backgroundColor] and
  /// [background] specification are resolved in [background]'s favor - i.e. if
  /// [background] is specified in one place, it will dominate [backgroundColor]
  /// in another.
708
  final Paint? background;
709

710
  /// The decorations to paint near the text (e.g., an underline).
711 712
  ///
  /// Multiple decorations can be applied using [TextDecoration.combine].
713
  final TextDecoration? decoration;
714

715
  /// The color in which to paint the text decorations.
716
  final Color? decorationColor;
717

718
  /// The style in which to paint the text decorations (e.g., dashed).
719
  final TextDecorationStyle? decorationStyle;
720

721
  /// The thickness of the decoration stroke as a multiplier of the thickness
722 723 724 725 726 727 728 729
  /// defined by the font.
  ///
  /// The font provides a base stroke width for [decoration]s which scales off
  /// of the [fontSize]. This property may be used to achieve a thinner or
  /// thicker decoration stroke, without changing the [fontSize]. For example,
  /// a [decorationThickness] of 2.0 will draw a decoration twice as thick as
  /// the font defined decoration thickness.
  ///
730
  /// {@tool snippet}
731 732 733 734
  /// To achieve a bolded strike-through, we can apply a thicker stroke for the
  /// decoration.
  ///
  /// ```dart
735
  /// const Text(
736 737 738 739 740 741 742 743 744
  ///   'This has a very BOLD strike through!',
  ///   style: TextStyle(
  ///     decoration: TextDecoration.lineThrough,
  ///     decorationThickness: 2.85,
  ///   ),
  /// )
  /// ```
  /// {@end-tool}
  ///
745
  /// {@tool snippet}
746 747 748 749
  /// We can apply a very thin and subtle wavy underline (perhaps, when words
  /// are misspelled) by using a [decorationThickness] < 1.0.
  ///
  /// ```dart
750
  /// const Text(
751 752 753 754 755 756 757 758 759 760 761 762 763
  ///   'oopsIforgottousespaces!',
  ///   style: TextStyle(
  ///     decoration: TextDecoration.underline,
  ///     decorationStyle: TextDecorationStyle.wavy,
  ///     decorationColor: Colors.red,
  ///     decorationThickness: 0.5,
  ///   ),
  /// )
  /// ```
  /// {@end-tool}
  ///
  /// The default [decorationThickness] is 1.0, which will use the font's base
  /// stroke thickness/width.
764
  final double? decorationThickness;
765

766 767 768 769 770 771 772 773 774 775 776
  /// A human-readable description of this text style.
  ///
  /// This property is maintained only in debug builds.
  ///
  /// When merging ([merge]), copying ([copyWith]), modifying using [apply], or
  /// interpolating ([lerp]), the label of the resulting style is marked with
  /// the debug labels of the original styles. This helps figuring out where a
  /// particular text style came from.
  ///
  /// This property is not considered when comparing text styles using `==` or
  /// [compareTo], and it does not affect [hashCode].
777
  final String? debugLabel;
778

779 780 781 782 783 784 785
  /// A list of [Shadow]s that will be painted underneath the text.
  ///
  /// Multiple shadows are supported to replicate lighting from multiple light
  /// sources.
  ///
  /// Shadows must be in the same order for [TextStyle] to be considered as
  /// equivalent as order produces differing transparency.
786
  final List<ui.Shadow>? shadows;
787

788 789 790
  /// A list of [FontFeature]s that affect how the font selects glyphs.
  ///
  /// Some fonts support multiple variants of how a given character can be
791
  /// rendered. For example, a font might provide both proportional and
792
  /// tabular numbers, or it might offer versions of the zero digit with
793
  /// and without slashes. [FontFeature]s can be used to select which of
794
  /// these variants will be used for rendering.
795
  final List<ui.FontFeature>? fontFeatures;
796

797 798 799
  /// A list of [FontVariation]s that affect how a variable font is rendered.
  ///
  /// Some fonts are variable fonts that can generate multiple font faces based
800
  /// on the values of customizable attributes. For example, a variable font
801 802 803 804 805 806
  /// may have a weight axis that can be set to a value between 1 and 1000.
  /// [FontVariation]s can be used to select the values of these design axes.
  ///
  /// For example, to control the weight axis of the Roboto Slab variable font
  /// (https://fonts.google.com/specimen/Roboto+Slab):
  /// ```dart
807
  /// const TextStyle(
808
  ///   fontFamily: 'RobotoSlab',
809
  ///   fontVariations: <ui.FontVariation>[ui.FontVariation('wght', 900.0)]
810 811 812 813
  /// )
  /// ```
  final List<ui.FontVariation>? fontVariations;

814 815 816
  /// How visual text overflow should be handled.
  final TextOverflow? overflow;

817 818 819
  // Return the original value of fontFamily, without the additional
  // "packages/$_package/" prefix.
  String? get _fontFamily {
820
    if (_package != null) {
821
      final String fontFamilyPrefix = 'packages/$_package/';
822 823
      assert(fontFamily?.startsWith(fontFamilyPrefix) ?? true);
      return fontFamily?.substring(fontFamilyPrefix.length);
824 825 826 827
    }
    return fontFamily;
  }

828 829
  /// Creates a copy of this text style but with the given fields replaced with
  /// the new values.
830
  ///
831 832
  /// One of [color] or [foreground] must be null, and if this has [foreground]
  /// specified it will be given preference over any color parameter.
833 834 835 836
  ///
  /// One of [backgroundColor] or [background] must be null, and if this has
  /// [background] specified it will be given preference over any
  /// backgroundColor parameter.
837
  TextStyle copyWith({
838 839 840 841 842 843 844 845 846 847
    bool? inherit,
    Color? color,
    Color? backgroundColor,
    double? fontSize,
    FontWeight? fontWeight,
    FontStyle? fontStyle,
    double? letterSpacing,
    double? wordSpacing,
    TextBaseline? textBaseline,
    double? height,
848
    ui.TextLeadingDistribution? leadingDistribution,
849 850 851 852 853
    Locale? locale,
    Paint? foreground,
    Paint? background,
    List<ui.Shadow>? shadows,
    List<ui.FontFeature>? fontFeatures,
854
    List<ui.FontVariation>? fontVariations,
855 856 857 858 859
    TextDecoration? decoration,
    Color? decorationColor,
    TextDecorationStyle? decorationStyle,
    double? decorationThickness,
    String? debugLabel,
860 861 862
    String? fontFamily,
    List<String>? fontFamilyFallback,
    String? package,
863
    TextOverflow? overflow,
864
  }) {
865
    assert(color == null || foreground == null, _kColorForegroundWarning);
866
    assert(backgroundColor == null || background == null, _kColorBackgroundWarning);
867
    String? newDebugLabel;
868
    assert(() {
869
      if (this.debugLabel != null) {
870
        newDebugLabel = debugLabel ?? '(${this.debugLabel}).copyWith';
871
      }
872 873
      return true;
    }());
874

875
    return TextStyle(
876
      inherit: inherit ?? this.inherit,
877
      color: this.foreground == null && foreground == null ? color ?? this.color : null,
878
      backgroundColor: this.background == null && background == null ? backgroundColor ?? this.backgroundColor : null,
Ian Hickson's avatar
Ian Hickson committed
879 880 881 882 883 884 885
      fontSize: fontSize ?? this.fontSize,
      fontWeight: fontWeight ?? this.fontWeight,
      fontStyle: fontStyle ?? this.fontStyle,
      letterSpacing: letterSpacing ?? this.letterSpacing,
      wordSpacing: wordSpacing ?? this.wordSpacing,
      textBaseline: textBaseline ?? this.textBaseline,
      height: height ?? this.height,
886
      leadingDistribution: leadingDistribution ?? this.leadingDistribution,
887
      locale: locale ?? this.locale,
888
      foreground: foreground ?? this.foreground,
889
      background: background ?? this.background,
890
      shadows: shadows ?? this.shadows,
891
      fontFeatures: fontFeatures ?? this.fontFeatures,
892
      fontVariations: fontVariations ?? this.fontVariations,
Ian Hickson's avatar
Ian Hickson committed
893 894
      decoration: decoration ?? this.decoration,
      decorationColor: decorationColor ?? this.decorationColor,
895
      decorationStyle: decorationStyle ?? this.decorationStyle,
896
      decorationThickness: decorationThickness ?? this.decorationThickness,
897
      debugLabel: newDebugLabel,
898
      fontFamily: fontFamily ?? _fontFamily,
899
      fontFamilyFallback: fontFamilyFallback ?? _fontFamilyFallback,
900
      package: package ?? _package,
901
      overflow: overflow ?? this.overflow,
902 903 904
    );
  }

905 906 907 908 909
  /// Creates a copy of this text style replacing or altering the specified
  /// properties.
  ///
  /// The non-numeric properties [color], [fontFamily], [decoration],
  /// [decorationColor] and [decorationStyle] are replaced with the new values.
910
  ///
911 912 913
  /// [foreground] will be given preference over [color] if it is not null and
  /// [background] will be given preference over [backgroundColor] if it is not
  /// null.
914 915 916
  ///
  /// The numeric properties are multiplied by the given factors and then
  /// incremented by the given deltas.
Ian Hickson's avatar
Ian Hickson committed
917
  ///
918 919
  /// For example, `style.apply(fontSizeFactor: 2.0, fontSizeDelta: 1.0)` would
  /// return a [TextStyle] whose [fontSize] is `style.fontSize * 2.0 + 1.0`.
Ian Hickson's avatar
Ian Hickson committed
920 921 922 923 924 925
  ///
  /// For the [fontWeight], the delta is applied to the [FontWeight] enum index
  /// values, so that for instance `style.apply(fontWeightDelta: -2)` when
  /// applied to a `style` whose [fontWeight] is [FontWeight.w500] will return a
  /// [TextStyle] with a [FontWeight.w300].
  ///
926
  /// The numeric arguments must not be null.
Ian Hickson's avatar
Ian Hickson committed
927 928 929
  ///
  /// If the underlying values are null, then the corresponding factors and/or
  /// deltas must not be specified.
930
  ///
931
  /// If [foreground] is specified on this object, then applying [color] here
932 933
  /// will have no effect and if [background] is specified on this object, then
  /// applying [backgroundColor] here will have no effect either.
Ian Hickson's avatar
Ian Hickson committed
934
  TextStyle apply({
935 936 937 938 939
    Color? color,
    Color? backgroundColor,
    TextDecoration? decoration,
    Color? decorationColor,
    TextDecorationStyle? decorationStyle,
940 941
    double decorationThicknessFactor = 1.0,
    double decorationThicknessDelta = 0.0,
942 943
    String? fontFamily,
    List<String>? fontFamilyFallback,
944 945 946
    double fontSizeFactor = 1.0,
    double fontSizeDelta = 0.0,
    int fontWeightDelta = 0,
947
    FontStyle? fontStyle,
948 949 950 951 952 953
    double letterSpacingFactor = 1.0,
    double letterSpacingDelta = 0.0,
    double wordSpacingFactor = 1.0,
    double wordSpacingDelta = 0.0,
    double heightFactor = 1.0,
    double heightDelta = 0.0,
954
    TextBaseline? textBaseline,
955
    ui.TextLeadingDistribution? leadingDistribution,
956 957 958
    Locale? locale,
    List<ui.Shadow>? shadows,
    List<ui.FontFeature>? fontFeatures,
959
    List<ui.FontVariation>? fontVariations,
960
    String? package,
961
    TextOverflow? overflow,
Ian Hickson's avatar
Ian Hickson committed
962 963 964 965 966
  }) {
    assert(fontSize != null || (fontSizeFactor == 1.0 && fontSizeDelta == 0.0));
    assert(fontWeight != null || fontWeightDelta == 0.0);
    assert(letterSpacing != null || (letterSpacingFactor == 1.0 && letterSpacingDelta == 0.0));
    assert(wordSpacing != null || (wordSpacingFactor == 1.0 && wordSpacingDelta == 0.0));
967
    assert(decorationThickness != null || (decorationThicknessFactor == 1.0 && decorationThicknessDelta == 0.0));
968

969
    String? modifiedDebugLabel;
970
    assert(() {
971
      if (debugLabel != null) {
972
        modifiedDebugLabel = '($debugLabel).apply';
973
      }
974 975 976
      return true;
    }());

977
    return TextStyle(
Ian Hickson's avatar
Ian Hickson committed
978
      inherit: inherit,
979
      color: foreground == null ? color ?? this.color : null,
980
      backgroundColor: background == null ? backgroundColor ?? this.backgroundColor : null,
981
      fontFamily: fontFamily ?? _fontFamily,
982
      fontFamilyFallback: fontFamilyFallback ?? _fontFamilyFallback,
983
      fontSize: fontSize == null ? null : fontSize! * fontSizeFactor + fontSizeDelta,
984
      fontWeight: fontWeight == null ? null : FontWeight.values[(fontWeight!.index + fontWeightDelta).clamp(0, FontWeight.values.length - 1)], // ignore_clamp_double_lint
985
      fontStyle: fontStyle ?? this.fontStyle,
986 987
      letterSpacing: letterSpacing == null ? null : letterSpacing! * letterSpacingFactor + letterSpacingDelta,
      wordSpacing: wordSpacing == null ? null : wordSpacing! * wordSpacingFactor + wordSpacingDelta,
988
      textBaseline: textBaseline ?? this.textBaseline,
989
      height: height == null ? null : height! * heightFactor + heightDelta,
990
      leadingDistribution: leadingDistribution ?? this.leadingDistribution,
991
      locale: locale ?? this.locale,
992
      foreground: foreground,
993
      background: background,
994 995
      shadows: shadows ?? this.shadows,
      fontFeatures: fontFeatures ?? this.fontFeatures,
996
      fontVariations: fontVariations ?? this.fontVariations,
997 998 999
      decoration: decoration ?? this.decoration,
      decorationColor: decorationColor ?? this.decorationColor,
      decorationStyle: decorationStyle ?? this.decorationStyle,
1000
      decorationThickness: decorationThickness == null ? null : decorationThickness! * decorationThicknessFactor + decorationThicknessDelta,
1001
      overflow: overflow ?? this.overflow,
1002
      package: package ?? _package,
1003
      debugLabel: modifiedDebugLabel,
Ian Hickson's avatar
Ian Hickson committed
1004 1005 1006
    );
  }

1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020
  /// Returns a new text style that is a combination of this style and the given
  /// [other] style.
  ///
  /// If the given [other] text style has its [TextStyle.inherit] set to true,
  /// its null properties are replaced with the non-null properties of this text
  /// style. The [other] style _inherits_ the properties of this style. Another
  /// way to think of it is that the "missing" properties of the [other] style
  /// are _filled_ by the properties of this style.
  ///
  /// If the given [other] text style has its [TextStyle.inherit] set to false,
  /// returns the given [other] style unchanged. The [other] style does not
  /// inherit properties of this style.
  ///
  /// If the given text style is null, returns this text style.
1021
  ///
1022 1023
  /// One of [color] or [foreground] must be null, and if this or `other` has
  /// [foreground] specified it will be given preference over any color parameter.
1024
  ///
1025
  /// Similarly, one of [backgroundColor] or [background] must be null, and if
1026 1027
  /// this or `other` has [background] specified it will be given preference
  /// over any backgroundColor parameter.
1028
  TextStyle merge(TextStyle? other) {
1029
    if (other == null) {
1030
      return this;
1031 1032
    }
    if (!other.inherit) {
1033
      return other;
1034
    }
1035

1036
    String? mergedDebugLabel;
1037
    assert(() {
1038
      if (other.debugLabel != null || debugLabel != null) {
1039
        mergedDebugLabel = '(${debugLabel ?? _kDefaultDebugLabel}).merge(${other.debugLabel ?? _kDefaultDebugLabel})';
1040
      }
1041 1042 1043
      return true;
    }());

1044 1045
    return copyWith(
      color: other.color,
1046
      backgroundColor: other.backgroundColor,
1047 1048
      fontSize: other.fontSize,
      fontWeight: other.fontWeight,
1049
      fontStyle: other.fontStyle,
1050
      letterSpacing: other.letterSpacing,
1051
      wordSpacing: other.wordSpacing,
1052
      textBaseline: other.textBaseline,
1053
      height: other.height,
1054
      leadingDistribution: other.leadingDistribution,
1055
      locale: other.locale,
1056
      foreground: other.foreground,
1057
      background: other.background,
1058
      shadows: other.shadows,
1059
      fontFeatures: other.fontFeatures,
1060
      fontVariations: other.fontVariations,
1061 1062
      decoration: other.decoration,
      decorationColor: other.decorationColor,
1063
      decorationStyle: other.decorationStyle,
1064
      decorationThickness: other.decorationThickness,
1065
      debugLabel: mergedDebugLabel,
1066
      fontFamily: other._fontFamily,
1067
      fontFamilyFallback: other._fontFamilyFallback,
1068 1069
      package: other._package,
      overflow: other.overflow,
1070 1071 1072
    );
  }

1073
  /// Interpolate between two text styles for animated transitions.
1074
  ///
1075 1076 1077 1078 1079
  /// Interpolation will not work well if the styles don't specify the same fields.
  /// When this happens, to keep the interpolated transition smooth, the
  /// implementation uses the non-null value throughout the transition for
  /// lerpable fields such as colors (for example, if one [TextStyle] specified
  /// `fontSize` but the other didn't, the returned [TextStyle] will use the
Lioness100's avatar
Lioness100 committed
1080
  /// `fontSize` from the [TextStyle] that specified it, regardless of the `t`
1081 1082 1083 1084 1085
  /// value).
  ///
  /// This method throws when the given [TextStyle]s don't have the same
  /// [inherit] value and a lerpable field is missing from both [TextStyle]s,
  /// as that could result in jumpy transitions.
1086
  ///
1087
  /// {@macro dart.ui.shadow.lerp}
1088
  ///
1089 1090 1091
  /// If [foreground] is specified on either of `a` or `b`, both will be treated
  /// as if they have a [foreground] paint (creating a new [Paint] if necessary
  /// based on the [color] property).
1092 1093 1094 1095
  ///
  /// If [background] is specified on either of `a` or `b`, both will be treated
  /// as if they have a [background] paint (creating a new [Paint] if necessary
  /// based on the [backgroundColor] property).
1096
  static TextStyle? lerp(TextStyle? a, TextStyle? b, double t) {
1097 1098
    if (identical(a, b)) {
      return a;
1099
    }
1100
    String? lerpDebugLabel;
1101
    assert(() {
1102
      lerpDebugLabel = 'lerp(${a?.debugLabel ?? _kDefaultDebugLabel}${t.toStringAsFixed(1)}${b?.debugLabel ?? _kDefaultDebugLabel})';
1103 1104 1105
      return true;
    }());

1106
    if (a == null) {
1107
      return TextStyle(
1108
        inherit: b!.inherit,
1109
        color: Color.lerp(null, b.color, t),
1110
        backgroundColor: Color.lerp(null, b.backgroundColor, t),
1111 1112 1113 1114 1115 1116 1117
        fontSize: t < 0.5 ? null : b.fontSize,
        fontWeight: FontWeight.lerp(null, b.fontWeight, t),
        fontStyle: t < 0.5 ? null : b.fontStyle,
        letterSpacing: t < 0.5 ? null : b.letterSpacing,
        wordSpacing: t < 0.5 ? null : b.wordSpacing,
        textBaseline: t < 0.5 ? null : b.textBaseline,
        height: t < 0.5 ? null : b.height,
1118
        leadingDistribution: t < 0.5 ? null : b.leadingDistribution,
1119
        locale: t < 0.5 ? null : b.locale,
1120
        foreground: t < 0.5 ? null : b.foreground,
1121
        background: t < 0.5 ? null : b.background,
1122
        shadows: t < 0.5 ? null : b.shadows,
1123
        fontFeatures: t < 0.5 ? null : b.fontFeatures,
1124
        fontVariations: t < 0.5 ? null : b.fontVariations,
1125
        decoration: t < 0.5 ? null : b.decoration,
1126 1127
        decorationColor: Color.lerp(null, b.decorationColor, t),
        decorationStyle: t < 0.5 ? null : b.decorationStyle,
1128
        decorationThickness: t < 0.5 ? null : b.decorationThickness,
1129
        debugLabel: lerpDebugLabel,
1130
        fontFamily: t < 0.5 ? null : b._fontFamily,
1131
        fontFamilyFallback: t < 0.5 ? null : b._fontFamilyFallback,
1132 1133
        package: t < 0.5 ? null : b._package,
        overflow: t < 0.5 ? null : b.overflow,
1134 1135 1136 1137
      );
    }

    if (b == null) {
1138
      return TextStyle(
1139 1140
        inherit: a.inherit,
        color: Color.lerp(a.color, null, t),
1141
        backgroundColor: Color.lerp(null, a.backgroundColor, t),
1142 1143 1144 1145 1146 1147 1148
        fontSize: t < 0.5 ? a.fontSize : null,
        fontWeight: FontWeight.lerp(a.fontWeight, null, t),
        fontStyle: t < 0.5 ? a.fontStyle : null,
        letterSpacing: t < 0.5 ? a.letterSpacing : null,
        wordSpacing: t < 0.5 ? a.wordSpacing : null,
        textBaseline: t < 0.5 ? a.textBaseline : null,
        height: t < 0.5 ? a.height : null,
1149
        leadingDistribution: t < 0.5 ? a.leadingDistribution : null,
1150
        locale: t < 0.5 ? a.locale : null,
1151
        foreground: t < 0.5 ? a.foreground : null,
1152
        background: t < 0.5 ? a.background : null,
1153
        shadows: t < 0.5 ? a.shadows : null,
1154
        fontFeatures: t < 0.5 ? a.fontFeatures : null,
1155
        fontVariations: t < 0.5 ? a.fontVariations : null,
1156 1157 1158
        decoration: t < 0.5 ? a.decoration : null,
        decorationColor: Color.lerp(a.decorationColor, null, t),
        decorationStyle: t < 0.5 ? a.decorationStyle : null,
1159
        decorationThickness: t < 0.5 ? a.decorationThickness : null,
1160
        debugLabel: lerpDebugLabel,
1161
        fontFamily: t < 0.5 ? a._fontFamily : null,
1162
        fontFamilyFallback: t < 0.5 ? a._fontFamilyFallback : null,
1163 1164
        package: t < 0.5 ? a._package : null,
        overflow: t < 0.5 ? a.overflow : null,
1165 1166 1167
      );
    }

1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229
    assert(() {
      if (a.inherit == b.inherit) {
        return true;
      }

      final List<String> nullFields = <String>[
        if (a.foreground == null && b.foreground == null && a.color == null && b.color == null) 'color',
        if (a.background == null && b.background == null && a.backgroundColor == null && b.backgroundColor == null) 'backgroundColor',
        if (a.fontSize == null && b.fontSize == null) 'fontSize',
        if (a.letterSpacing == null && b.letterSpacing == null) 'letterSpacing',
        if (a.wordSpacing == null && b.wordSpacing == null) 'wordSpacing',
        if (a.height == null && b.height == null) 'height',
        if (a.decorationColor == null && b.decorationColor == null) 'decorationColor',
        if (a.decorationThickness == null && b.decorationThickness == null) 'decorationThickness',
      ];
      if (nullFields.isEmpty) {
        return true;
      }

      throw FlutterError.fromParts(<DiagnosticsNode>[
        ErrorSummary('Failed to interpolate TextStyles with different inherit values.'),
        ErrorSpacer(),
        ErrorDescription('The TextStyles being interpolated were:'),
        a.toDiagnosticsNode(name: 'from', style: DiagnosticsTreeStyle.singleLine),
        b.toDiagnosticsNode(name: 'to', style: DiagnosticsTreeStyle.singleLine),
        ErrorDescription(
          'The following fields are unspecified in both TextStyles:\n'
          '${nullFields.map((String name) => '"$name"').join(', ')}.\n'
          'When "inherit" changes during the transition, these fields may '
          'observe abrupt value changes as a result, causing "jump"s in the '
          'transition.'
        ),
        ErrorSpacer(),
        ErrorHint(
          'In general, TextStyle.lerp only works well when both TextStyles have '
          'the same "inherit" value, and specify the same fields.',
        ),
        ErrorHint(
          'If the TextStyles were directly created by you, consider bringing '
          'them to parity to ensure a smooth transition.'
        ),
        ErrorSpacer(),
        ErrorHint(
          'If one of the TextStyles being lerped is significantly more elaborate '
          'than the other, and has "inherited" set to false, it is often because '
          'it is merged with another TextStyle before being lerped. Comparing '
          'the "debugLabel"s of the two TextStyles may help identify if that was '
          'the case.'
        ),
        ErrorHint(
          'For example, you may see this error message when trying to lerp '
          'between "ThemeData()" and "Theme.of(context)". This is because '
          'TextStyles from "Theme.of(context)" are merged with TextStyles from '
          'another theme and thus are more elaborate than the TextStyles from '
          '"ThemeData()" (which is reflected in their "debugLabel"s -- '
          'TextStyles from "Theme.of(context)" should have labels in the form of '
          '"(<A TextStyle>).merge(<Another TextStyle>)"). It is recommended to '
          'only lerp ThemeData with matching TextStyles.'
        ),
      ]);
    }());

1230
    return TextStyle(
1231
      inherit: t < 0.5 ? a.inherit : b.inherit,
1232
      color: a.foreground == null && b.foreground == null ? Color.lerp(a.color, b.color, t) : null,
1233
      backgroundColor: a.background == null && b.background == null ? Color.lerp(a.backgroundColor, b.backgroundColor, t) : null,
1234 1235 1236 1237 1238 1239 1240
      fontSize: ui.lerpDouble(a.fontSize ?? b.fontSize, b.fontSize ?? a.fontSize, t),
      fontWeight: FontWeight.lerp(a.fontWeight, b.fontWeight, t),
      fontStyle: t < 0.5 ? a.fontStyle : b.fontStyle,
      letterSpacing: ui.lerpDouble(a.letterSpacing ?? b.letterSpacing, b.letterSpacing ?? a.letterSpacing, t),
      wordSpacing: ui.lerpDouble(a.wordSpacing ?? b.wordSpacing, b.wordSpacing ?? a.wordSpacing, t),
      textBaseline: t < 0.5 ? a.textBaseline : b.textBaseline,
      height: ui.lerpDouble(a.height ?? b.height, b.height ?? a.height, t),
1241
      leadingDistribution: t < 0.5 ? a.leadingDistribution : b.leadingDistribution,
1242
      locale: t < 0.5 ? a.locale : b.locale,
1243
      foreground: (a.foreground != null || b.foreground != null)
1244
        ? t < 0.5
1245 1246
          ? a.foreground ?? (Paint()..color = a.color!)
          : b.foreground ?? (Paint()..color = b.color!)
1247
        : null,
1248 1249
      background: (a.background != null || b.background != null)
        ? t < 0.5
1250 1251
          ? a.background ?? (Paint()..color = a.backgroundColor!)
          : b.background ?? (Paint()..color = b.backgroundColor!)
1252
        : null,
1253
      shadows: t < 0.5 ? a.shadows : b.shadows,
1254
      fontFeatures: t < 0.5 ? a.fontFeatures : b.fontFeatures,
1255
      fontVariations: t < 0.5 ? a.fontVariations : b.fontVariations,
1256 1257 1258
      decoration: t < 0.5 ? a.decoration : b.decoration,
      decorationColor: Color.lerp(a.decorationColor, b.decorationColor, t),
      decorationStyle: t < 0.5 ? a.decorationStyle : b.decorationStyle,
1259
      decorationThickness: ui.lerpDouble(a.decorationThickness ?? b.decorationThickness, b.decorationThickness ?? a.decorationThickness, t),
1260
      debugLabel: lerpDebugLabel,
1261
      fontFamily: t < 0.5 ? a._fontFamily : b._fontFamily,
1262
      fontFamilyFallback: t < 0.5 ? a._fontFamilyFallback : b._fontFamilyFallback,
1263 1264
      package: t < 0.5 ? a._package : b._package,
      overflow: t < 0.5 ? a.overflow : b.overflow,
1265 1266
    );
  }
1267

1268
  /// The style information for text runs, encoded for use by `dart:ui`.
1269
  ui.TextStyle getTextStyle({ double textScaleFactor = 1.0 }) {
1270
    return ui.TextStyle(
1271 1272 1273 1274
      color: color,
      decoration: decoration,
      decorationColor: decorationColor,
      decorationStyle: decorationStyle,
1275
      decorationThickness: decorationThickness,
1276 1277
      fontWeight: fontWeight,
      fontStyle: fontStyle,
1278
      textBaseline: textBaseline,
1279
      leadingDistribution: leadingDistribution,
1280
      fontFamily: fontFamily,
1281
      fontFamilyFallback: fontFamilyFallback,
1282
      fontSize: fontSize == null ? null : fontSize! * textScaleFactor,
1283 1284
      letterSpacing: letterSpacing,
      wordSpacing: wordSpacing,
1285 1286
      height: height,
      locale: locale,
1287
      foreground: foreground,
1288
      background: background ?? (backgroundColor != null
1289
        ? (Paint()..color = backgroundColor!)
1290 1291
        : null
      ),
1292
      shadows: shadows,
1293
      fontFeatures: fontFeatures,
1294
      fontVariations: fontVariations,
1295 1296 1297
    );
  }

1298
  /// The style information for paragraphs, encoded for use by `dart:ui`.
1299 1300 1301 1302
  ///
  /// The `textScaleFactor` argument must not be null. If omitted, it defaults
  /// to 1.0. The other arguments may be null. The `maxLines` argument, if
  /// specified and non-null, must be greater than zero.
1303 1304 1305
  ///
  /// If the font size on this style isn't set, it will default to 14 logical
  /// pixels.
1306
  ui.ParagraphStyle getParagraphStyle({
1307 1308
    TextAlign? textAlign,
    TextDirection? textDirection,
1309
    double textScaleFactor = 1.0,
1310 1311 1312 1313 1314 1315 1316 1317 1318 1319
    String? ellipsis,
    int? maxLines,
    ui.TextHeightBehavior? textHeightBehavior,
    Locale? locale,
    String? fontFamily,
    double? fontSize,
    FontWeight? fontWeight,
    FontStyle? fontStyle,
    double? height,
    StrutStyle? strutStyle,
1320 1321
  }) {
    assert(maxLines == null || maxLines > 0);
1322 1323 1324
    final ui.TextLeadingDistribution? leadingDistribution = this.leadingDistribution;
    final ui.TextHeightBehavior? effectiveTextHeightBehavior = textHeightBehavior
      ?? (leadingDistribution == null ? null : ui.TextHeightBehavior(leadingDistribution: leadingDistribution));
1325
    return ui.ParagraphStyle(
1326
      textAlign: textAlign,
Ian Hickson's avatar
Ian Hickson committed
1327
      textDirection: textDirection,
1328
      // Here, we establish the contents of this TextStyle as the paragraph's default font
1329 1330 1331 1332
      // unless an override is passed in.
      fontWeight: fontWeight ?? this.fontWeight,
      fontStyle: fontStyle ?? this.fontStyle,
      fontFamily: fontFamily ?? this.fontFamily,
1333
      fontSize: (fontSize ?? this.fontSize ?? _kDefaultFontSize) * textScaleFactor,
1334
      height: height ?? this.height,
1335
      textHeightBehavior: effectiveTextHeightBehavior,
1336 1337 1338
      strutStyle: strutStyle == null ? null : ui.StrutStyle(
        fontFamily: strutStyle.fontFamily,
        fontFamilyFallback: strutStyle.fontFamilyFallback,
1339
        fontSize: strutStyle.fontSize == null ? null : strutStyle.fontSize! * textScaleFactor,
1340 1341 1342 1343 1344 1345
        height: strutStyle.height,
        leading: strutStyle.leading,
        fontWeight: strutStyle.fontWeight,
        fontStyle: strutStyle.fontStyle,
        forceStrutHeight: strutStyle.forceStrutHeight,
      ),
1346
      maxLines: maxLines,
1347
      ellipsis: ellipsis,
1348
      locale: locale,
1349 1350
    );
  }
1351

1352 1353 1354 1355 1356 1357 1358
  /// Describe the difference between this style and another, in terms of how
  /// much damage it will make to the rendering.
  ///
  /// See also:
  ///
  ///  * [TextSpan.compareTo], which does the same thing for entire [TextSpan]s.
  RenderComparison compareTo(TextStyle other) {
1359
    if (identical(this, other)) {
1360
      return RenderComparison.identical;
1361
    }
1362 1363 1364 1365 1366 1367 1368 1369
    if (inherit != other.inherit ||
        fontFamily != other.fontFamily ||
        fontSize != other.fontSize ||
        fontWeight != other.fontWeight ||
        fontStyle != other.fontStyle ||
        letterSpacing != other.letterSpacing ||
        wordSpacing != other.wordSpacing ||
        textBaseline != other.textBaseline ||
1370
        height != other.height ||
1371
        leadingDistribution != other.leadingDistribution ||
1372
        locale != other.locale ||
1373
        foreground != other.foreground ||
1374
        background != other.background ||
1375
        !listEquals(shadows, other.shadows) ||
1376
        !listEquals(fontFeatures, other.fontFeatures) ||
1377
        !listEquals(fontVariations, other.fontVariations) ||
1378
        !listEquals(fontFamilyFallback, other.fontFamilyFallback) ||
1379
        overflow != other.overflow) {
1380
      return RenderComparison.layout;
1381
    }
1382
    if (color != other.color ||
1383
        backgroundColor != other.backgroundColor ||
1384 1385
        decoration != other.decoration ||
        decorationColor != other.decorationColor ||
1386
        decorationStyle != other.decorationStyle ||
1387
        decorationThickness != other.decorationThickness) {
1388
      return RenderComparison.paint;
1389
    }
1390 1391 1392
    return RenderComparison.identical;
  }

1393
  @override
1394
  bool operator ==(Object other) {
1395
    if (identical(this, other)) {
1396
      return true;
1397 1398
    }
    if (other.runtimeType != runtimeType) {
Hixie's avatar
Hixie committed
1399
      return false;
1400
    }
1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411
    return other is TextStyle
        && other.inherit == inherit
        && other.color == color
        && other.backgroundColor == backgroundColor
        && other.fontSize == fontSize
        && other.fontWeight == fontWeight
        && other.fontStyle == fontStyle
        && other.letterSpacing == letterSpacing
        && other.wordSpacing == wordSpacing
        && other.textBaseline == textBaseline
        && other.height == height
1412
        && other.leadingDistribution == leadingDistribution
1413 1414 1415
        && other.locale == locale
        && other.foreground == foreground
        && other.background == background
1416 1417
        && listEquals(other.shadows, shadows)
        && listEquals(other.fontFeatures, fontFeatures)
1418
        && listEquals(other.fontVariations, fontVariations)
1419 1420 1421 1422
        && other.decoration == decoration
        && other.decorationColor == decorationColor
        && other.decorationStyle == decorationStyle
        && other.decorationThickness == decorationThickness
1423
        && other.fontFamily == fontFamily
1424
        && listEquals(other.fontFamilyFallback, fontFamilyFallback)
1425
        && other._package == _package
1426
        && other.overflow == overflow;
1427 1428
  }

1429
  @override
1430 1431 1432
  int get hashCode {
    final List<String>? fontFamilyFallback = this.fontFamilyFallback;
    final int fontHash = Object.hash(
1433
      decorationStyle,
1434 1435
      decorationThickness,
      fontFamily,
1436
      fontFamilyFallback == null ? null : Object.hashAll(fontFamilyFallback),
1437
      _package,
1438
      overflow,
1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466
    );

    final List<ui.Shadow>? shadows = this.shadows;
    final List<ui.FontFeature>? fontFeatures = this.fontFeatures;
    final List<ui.FontVariation>? fontVariations = this.fontVariations;
    return Object.hash(
      inherit,
      color,
      backgroundColor,
      fontSize,
      fontWeight,
      fontStyle,
      letterSpacing,
      wordSpacing,
      textBaseline,
      height,
      leadingDistribution,
      locale,
      foreground,
      background,
      shadows == null ? null : Object.hashAll(shadows),
      fontFeatures == null ? null : Object.hashAll(fontFeatures),
      fontVariations == null ? null : Object.hashAll(fontVariations),
      decoration,
      decorationColor,
      fontHash,
    );
  }
1467

1468
  @override
1469
  String toStringShort() => objectRuntimeType(this, 'TextStyle');
1470 1471

  /// Adds all properties prefixing property names with the optional `prefix`.
1472
  @override
1473
  void debugFillProperties(DiagnosticPropertiesBuilder properties, { String prefix = '' }) {
1474
    super.debugFillProperties(properties);
1475
    if (debugLabel != null) {
1476
      properties.add(MessageProperty('${prefix}debugLabel', debugLabel!));
1477
    }
1478 1479 1480 1481 1482 1483 1484
    final List<DiagnosticsNode> styles = <DiagnosticsNode>[
      ColorProperty('${prefix}color', color, defaultValue: null),
      ColorProperty('${prefix}backgroundColor', backgroundColor, defaultValue: null),
      StringProperty('${prefix}family', fontFamily, defaultValue: null, quoted: false),
      IterableProperty<String>('${prefix}familyFallback', fontFamilyFallback, defaultValue: null),
      DoubleProperty('${prefix}size', fontSize, defaultValue: null),
    ];
1485
    String? weightDescription;
Hixie's avatar
Hixie committed
1486
    if (fontWeight != null) {
1487
      weightDescription = '${fontWeight!.index + 1}00';
Hixie's avatar
Hixie committed
1488
    }
1489 1490 1491
    // TODO(jacobr): switch this to use enumProperty which will either cause the
    // weight description to change to w600 from 600 or require existing
    // enumProperty to handle this special case.
1492
    styles.add(DiagnosticsProperty<FontWeight>(
1493 1494 1495 1496 1497
      '${prefix}weight',
      fontWeight,
      description: weightDescription,
      defaultValue: null,
    ));
1498 1499 1500 1501 1502
    styles.add(EnumProperty<FontStyle>('${prefix}style', fontStyle, defaultValue: null));
    styles.add(DoubleProperty('${prefix}letterSpacing', letterSpacing, defaultValue: null));
    styles.add(DoubleProperty('${prefix}wordSpacing', wordSpacing, defaultValue: null));
    styles.add(EnumProperty<TextBaseline>('${prefix}baseline', textBaseline, defaultValue: null));
    styles.add(DoubleProperty('${prefix}height', height, unit: 'x', defaultValue: null));
1503
    styles.add(EnumProperty<ui.TextLeadingDistribution>('${prefix}leadingDistribution', leadingDistribution, defaultValue: null));
1504 1505 1506
    styles.add(DiagnosticsProperty<Locale>('${prefix}locale', locale, defaultValue: null));
    styles.add(DiagnosticsProperty<Paint>('${prefix}foreground', foreground, defaultValue: null));
    styles.add(DiagnosticsProperty<Paint>('${prefix}background', background, defaultValue: null));
1507
    if (decoration != null || decorationColor != null || decorationStyle != null || decorationThickness != null) {
1508
      final List<String> decorationDescription = <String>[];
1509
      if (decorationStyle != null) {
1510
        decorationDescription.add(decorationStyle!.name);
1511
      }
1512 1513 1514

      // Hide decorationColor from the default text view as it is shown in the
      // terse decoration summary as well.
1515
      styles.add(ColorProperty('${prefix}decorationColor', decorationColor, defaultValue: null, level: DiagnosticLevel.fine));
1516

1517
      if (decorationColor != null) {
1518
        decorationDescription.add('$decorationColor');
1519
      }
1520 1521 1522 1523

      // Intentionally collide with the property 'decoration' added below.
      // Tools that show hidden properties could choose the first property
      // matching the name to disambiguate.
1524
      styles.add(DiagnosticsProperty<TextDecoration>('${prefix}decoration', decoration, defaultValue: null, level: DiagnosticLevel.hidden));
1525
      if (decoration != null) {
1526
        decorationDescription.add('$decoration');
1527
      }
1528
      assert(decorationDescription.isNotEmpty);
1529
      styles.add(MessageProperty('${prefix}decoration', decorationDescription.join(' ')));
1530
      styles.add(DoubleProperty('${prefix}decorationThickness', decorationThickness, unit: 'x', defaultValue: null));
Hixie's avatar
Hixie committed
1531
    }
1532

1533
    final bool styleSpecified = styles.any((DiagnosticsNode n) => !n.isFiltered(DiagnosticLevel.info));
1534
    properties.add(DiagnosticsProperty<bool>('${prefix}inherit', inherit, level: (!styleSpecified && inherit) ? DiagnosticLevel.fine : DiagnosticLevel.info));
1535
    styles.forEach(properties.add);
1536

1537
    if (!styleSpecified) {
1538
      properties.add(FlagProperty('inherit', value: inherit, ifTrue: '$prefix<all styles inherited>', ifFalse: '$prefix<no style specified>'));
1539
    }
1540 1541

    styles.add(EnumProperty<TextOverflow>('${prefix}overflow', overflow, defaultValue: null));
1542 1543
  }
}