text_style.dart 13.9 KB
Newer Older
1 2 3 4
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'dart:ui' as ui show ParagraphStyle, TextStyle, lerpDouble;
6

7
import 'basic_types.dart';
8

Florian Loitsch's avatar
Florian Loitsch committed
9
/// An immutable style in which paint text.
10
class TextStyle {
11
  /// Creates a text style.
12
  const TextStyle({
13
    this.inherit: true,
14 15 16 17
    this.color,
    this.fontFamily,
    this.fontSize,
    this.fontWeight,
18
    this.fontStyle,
19
    this.letterSpacing,
20
    this.wordSpacing,
21
    this.textBaseline,
22 23 24 25 26 27
    this.height,
    this.decoration,
    this.decorationColor,
    this.decorationStyle
  });

28
  /// Whether null values are replaced with their value in an ancestor text style (e.g., in a [TextSpan] tree).
29 30
  final bool inherit;

31
  /// The color to use when painting the text.
32
  final Color color;
33

34
  /// The name of the font to use when painting the text (e.g., Roboto).
35
  final String fontFamily;
36

37
  /// The size of glyphs (in logical pixels) to use when painting the text.
38 39 40 41
  ///
  /// During painting, the [fontSize] is multiplied by the current
  /// `textScaleFactor` to let users make it easier to read text by increasing
  /// its size.
42 43
  final double fontSize;

44
  /// The typeface thickness to use when painting the text (e.g., bold).
45
  final FontWeight fontWeight;
46

47
  /// The typeface variant to use when drawing the letters (e.g., italics).
48
  final FontStyle fontStyle;
49

50
  /// The amount of space (in logical pixels) to add between each letter.
51 52
  final double letterSpacing;

53
  /// The amount of space (in logical pixels) to add at each sequence of white-space (i.e. between each word).
54 55
  final double wordSpacing;

56
  /// 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.
57
  final TextBaseline textBaseline;
58

59 60 61
  /// The height of this text span, as a multiple of the font size.
  ///
  /// If applied to the root [TextSpan], this value sets the line height, which
62 63
  /// is the minimum distance between subsequent text baselines, as multiple of
  /// the font size.
64 65
  final double height;

66
  /// The decorations to paint near the text (e.g., an underline).
67
  final TextDecoration decoration;
68

69
  /// The color in which to paint the text decorations.
70
  final Color decorationColor;
71

72
  /// The style in which to paint the text decorations (e.g., dashed).
73 74
  final TextDecorationStyle decorationStyle;

75
  /// Creates a copy of this text style but with the given fields replaced with the new values.
76 77 78 79 80
  TextStyle copyWith({
    Color color,
    String fontFamily,
    double fontSize,
    FontWeight fontWeight,
81
    FontStyle fontStyle,
82
    double letterSpacing,
83
    double wordSpacing,
84
    TextBaseline textBaseline,
85
    double height,
86
    TextDecoration decoration,
87 88 89 90
    Color decorationColor,
    TextDecorationStyle decorationStyle
  }) {
    return new TextStyle(
91
      inherit: inherit,
Ian Hickson's avatar
Ian Hickson committed
92 93 94 95 96 97 98 99 100 101 102 103
      color: color ?? this.color,
      fontFamily: fontFamily ?? this.fontFamily,
      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,
      decoration: decoration ?? this.decoration,
      decorationColor: decorationColor ?? this.decorationColor,
      decorationStyle: decorationStyle ?? this.decorationStyle
104 105 106
    );
  }

Ian Hickson's avatar
Ian Hickson committed
107 108 109
  /// Creates a copy of this text style but with the numeric fields multiplied
  /// by the given factors and then incremented by the given deltas.
  ///
110 111
  /// 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
112 113 114 115 116 117 118 119 120 121 122
  ///
  /// 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].
  ///
  /// The arguments must not be null.
  ///
  /// If the underlying values are null, then the corresponding factors and/or
  /// deltas must not be specified.
  ///
123
  /// The non-numeric fields can be controlled using the corresponding arguments.
Ian Hickson's avatar
Ian Hickson committed
124
  TextStyle apply({
125 126
    Color color,
    String fontFamily,
Ian Hickson's avatar
Ian Hickson committed
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
    double fontSizeFactor: 1.0,
    double fontSizeDelta: 0.0,
    int fontWeightDelta: 0,
    double letterSpacingFactor: 1.0,
    double letterSpacingDelta: 0.0,
    double wordSpacingFactor: 1.0,
    double wordSpacingDelta: 0.0,
    double heightFactor: 1.0,
    double heightDelta: 0.0,
  }) {
    assert(fontSizeFactor != null);
    assert(fontSizeDelta != null);
    assert(fontSize != null || (fontSizeFactor == 1.0 && fontSizeDelta == 0.0));
    assert(fontWeightDelta != null);
    assert(fontWeight != null || fontWeightDelta == 0.0);
    assert(letterSpacingFactor != null);
    assert(letterSpacingDelta != null);
    assert(letterSpacing != null || (letterSpacingFactor == 1.0 && letterSpacingDelta == 0.0));
    assert(wordSpacingFactor != null);
    assert(wordSpacingDelta != null);
    assert(wordSpacing != null || (wordSpacingFactor == 1.0 && wordSpacingDelta == 0.0));
    assert(heightFactor != null);
    assert(heightDelta != null);
    assert(heightFactor != null || (heightFactor == 1.0 && heightDelta == 0.0));
    return new TextStyle(
      inherit: inherit,
153 154
      color: color ?? this.color,
      fontFamily: fontFamily ?? this.fontFamily,
Ian Hickson's avatar
Ian Hickson committed
155 156 157 158 159 160 161 162 163 164 165 166 167
      fontSize: fontSize == null ? null : fontSize * fontSizeFactor + fontSizeDelta,
      fontWeight: fontWeight == null ? null : FontWeight.values[(fontWeight.index + fontWeightDelta).clamp(0, FontWeight.values.length - 1)],
      fontStyle: fontStyle,
      letterSpacing: letterSpacing == null ? null : letterSpacing * letterSpacingFactor + letterSpacingDelta,
      wordSpacing: wordSpacing == null ? null : wordSpacing * wordSpacingFactor + wordSpacingDelta,
      textBaseline: textBaseline,
      height: height == null ? null : height * heightFactor + heightDelta,
      decoration: decoration,
      decorationColor: decorationColor,
      decorationStyle: decorationStyle,
    );
  }

168
  /// Returns a new text style that matches this text style but with some values
169 170
  /// replaced by the non-null parameters of the given text style. If the given
  /// text style is null, simply returns this text style.
171
  TextStyle merge(TextStyle other) {
172 173 174
    if (other == null)
      return this;
    assert(other.inherit);
175 176 177 178 179
    return copyWith(
      color: other.color,
      fontFamily: other.fontFamily,
      fontSize: other.fontSize,
      fontWeight: other.fontWeight,
180
      fontStyle: other.fontStyle,
181
      letterSpacing: other.letterSpacing,
182
      wordSpacing: other.wordSpacing,
183
      textBaseline: other.textBaseline,
184 185 186 187 188 189 190
      height: other.height,
      decoration: other.decoration,
      decorationColor: other.decorationColor,
      decorationStyle: other.decorationStyle
    );
  }

191 192 193 194 195 196 197 198 199 200
  /// Interpolate between two text styles.
  ///
  /// This will not work well if the styles don't set the same fields.
  static TextStyle lerp(TextStyle begin, TextStyle end, double t) {
    assert(begin.inherit == end.inherit);
    return new TextStyle(
      inherit: end.inherit,
      color: Color.lerp(begin.color, end.color, t),
      fontFamily: t < 0.5 ? begin.fontFamily : end.fontFamily,
      fontSize: ui.lerpDouble(begin.fontSize ?? end.fontSize, end.fontSize ?? begin.fontSize, t),
Ian Hickson's avatar
Ian Hickson committed
201
      fontWeight: FontWeight.lerp(begin.fontWeight, end.fontWeight, t),
202 203 204 205 206 207 208 209 210 211
      fontStyle: t < 0.5 ? begin.fontStyle : end.fontStyle,
      letterSpacing: ui.lerpDouble(begin.letterSpacing ?? end.letterSpacing, end.letterSpacing ?? begin.letterSpacing, t),
      wordSpacing: ui.lerpDouble(begin.wordSpacing ?? end.wordSpacing, end.wordSpacing ?? begin.wordSpacing, t),
      textBaseline: t < 0.5 ? begin.textBaseline : end.textBaseline,
      height: ui.lerpDouble(begin.height ?? end.height, end.height ?? begin.height, t),
      decoration: t < 0.5 ? begin.decoration : end.decoration,
      decorationColor: Color.lerp(begin.decorationColor, end.decorationColor, t),
      decorationStyle: t < 0.5 ? begin.decorationStyle : end.decorationStyle
    );
  }
212

213
  /// The style information for text runs, encoded for use by `dart:ui`.
214
  ui.TextStyle getTextStyle({ double textScaleFactor: 1.0 }) {
215 216 217 218 219 220 221
    return new ui.TextStyle(
      color: color,
      decoration: decoration,
      decorationColor: decorationColor,
      decorationStyle: decorationStyle,
      fontWeight: fontWeight,
      fontStyle: fontStyle,
222
      textBaseline: textBaseline,
223
      fontFamily: fontFamily,
224
      fontSize: fontSize == null ? null : fontSize * textScaleFactor,
225 226
      letterSpacing: letterSpacing,
      wordSpacing: wordSpacing,
Adam Barth's avatar
Adam Barth committed
227
      height: height
228 229 230
    );
  }

231
  /// The style information for paragraphs, encoded for use by `dart:ui`.
232 233 234 235
  ui.ParagraphStyle getParagraphStyle({
      TextAlign textAlign,
      double textScaleFactor: 1.0,
      String ellipsis,
236
      int maxLines,
237
   }) {
238
    return new ui.ParagraphStyle(
239
      textAlign: textAlign,
Adam Barth's avatar
Adam Barth committed
240 241 242
      fontWeight: fontWeight,
      fontStyle: fontStyle,
      fontFamily: fontFamily,
243
      fontSize: fontSize == null ? null : fontSize * textScaleFactor,
244
      lineHeight: height,
245
      maxLines: maxLines,
246
      ellipsis: ellipsis,
247 248
    );
  }
249

250
  @override
Hixie's avatar
Hixie committed
251
  bool operator ==(dynamic other) {
252 253
    if (identical(this, other))
      return true;
Hixie's avatar
Hixie committed
254 255 256
    if (other is! TextStyle)
      return false;
    final TextStyle typedOther = other;
257 258
    return inherit == typedOther.inherit &&
           color == typedOther.color &&
Hixie's avatar
Hixie committed
259 260 261 262
           fontFamily == typedOther.fontFamily &&
           fontSize == typedOther.fontSize &&
           fontWeight == typedOther.fontWeight &&
           fontStyle == typedOther.fontStyle &&
263
           letterSpacing == typedOther.letterSpacing &&
264
           wordSpacing == typedOther.wordSpacing &&
Hixie's avatar
Hixie committed
265
           textBaseline == typedOther.textBaseline &&
266
           height == typedOther.height &&
Hixie's avatar
Hixie committed
267 268 269
           decoration == typedOther.decoration &&
           decorationColor == typedOther.decorationColor &&
           decorationStyle == typedOther.decorationStyle;
270 271
  }

272
  @override
273
  int get hashCode {
274
    return hashValues(
275
      inherit,
276 277 278 279 280 281
      color,
      fontFamily,
      fontSize,
      fontWeight,
      fontStyle,
      letterSpacing,
282
      wordSpacing,
283
      textBaseline,
284
      height,
285 286 287 288
      decoration,
      decorationColor,
      decorationStyle
    );
289 290
  }

291
  @override
292
  String toString([String prefix = '']) {
293
    final List<String> result = <String>[];
294
    result.add('${prefix}inherit: $inherit');
295 296 297
    if (color != null)
      result.add('${prefix}color: $color');
    if (fontFamily != null)
Hixie's avatar
Hixie committed
298
      result.add('${prefix}family: "$fontFamily"');
299
    if (fontSize != null)
Hixie's avatar
Hixie committed
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341
      result.add('${prefix}size: $fontSize');
    if (fontWeight != null) {
      switch (fontWeight) {
        case FontWeight.w100:
          result.add('${prefix}weight: 100');
          break;
        case FontWeight.w200:
          result.add('${prefix}weight: 200');
          break;
        case FontWeight.w300:
          result.add('${prefix}weight: 300');
          break;
        case FontWeight.w400:
          result.add('${prefix}weight: 400');
          break;
        case FontWeight.w500:
          result.add('${prefix}weight: 500');
          break;
        case FontWeight.w600:
          result.add('${prefix}weight: 600');
          break;
        case FontWeight.w700:
          result.add('${prefix}weight: 700');
          break;
        case FontWeight.w800:
          result.add('${prefix}weight: 800');
          break;
        case FontWeight.w900:
          result.add('${prefix}weight: 900');
          break;
      }
    }
    if (fontStyle != null) {
      switch (fontStyle) {
        case FontStyle.normal:
          result.add('${prefix}style: normal');
          break;
        case FontStyle.italic:
          result.add('${prefix}style: italic');
          break;
      }
    }
342
    if (letterSpacing != null)
343 344 345
      result.add('${prefix}letterSpacing: ${letterSpacing}x');
    if (wordSpacing != null)
      result.add('${prefix}wordSpacing: ${wordSpacing}x');
Hixie's avatar
Hixie committed
346 347 348 349 350 351 352 353 354 355
    if (textBaseline != null) {
      switch (textBaseline) {
        case TextBaseline.alphabetic:
          result.add('${prefix}baseline: alphabetic');
          break;
        case TextBaseline.ideographic:
          result.add('${prefix}baseline: ideographic');
          break;
      }
    }
356 357
    if (height != null)
      result.add('${prefix}height: ${height}x');
Hixie's avatar
Hixie committed
358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389
    if (decoration != null || decorationColor != null || decorationStyle != null) {
      String decorationDescription = '${prefix}decoration: ';
      bool haveDecorationDescription = false;
      if (decorationStyle != null) {
        switch (decorationStyle) {
          case TextDecorationStyle.solid:
            decorationDescription += 'solid';
            break;
          case TextDecorationStyle.double:
            decorationDescription += 'double';
            break;
          case TextDecorationStyle.dotted:
            decorationDescription += 'dotted';
            break;
          case TextDecorationStyle.dashed:
            decorationDescription += 'dashed';
            break;
          case TextDecorationStyle.wavy:
            decorationDescription += 'wavy';
            break;
        }
        haveDecorationDescription = true;
      }
      if (decorationColor != null) {
        if (haveDecorationDescription)
          decorationDescription += ' ';
        decorationDescription += '$decorationColor';
        haveDecorationDescription = true;
      }
      if (decoration != null) {
        if (haveDecorationDescription)
          decorationDescription += ' ';
390
        decorationDescription += '$decoration';
Hixie's avatar
Hixie committed
391 392 393 394 395
        haveDecorationDescription = true;
      }
      assert(haveDecorationDescription);
      result.add(decorationDescription);
    }
396
    if (result.isEmpty)
Hixie's avatar
Hixie committed
397
      return '$prefix<no style specified>';
398 399 400
    return result.join('\n');
  }
}