text_style.dart 11.1 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 gyphs (in logical pixels) to use when painting the text.
38 39
  final double fontSize;

40
  /// The typeface thickness to use when painting the text (e.g., bold).
41
  final FontWeight fontWeight;
42

43
  /// The typeface variant to use when drawing the letters (e.g., italics).
44
  final FontStyle fontStyle;
45

46
  /// The amount of space (in logical pixels) to add between each letter.
47 48
  final double letterSpacing;

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

52
  /// The baseline to use for aligning the text.
53
  final TextBaseline textBaseline;
54

55 56 57
  /// 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
58 59
  /// is the minimum distance between subsequent text baselines, as multiple of
  /// the font size.
60 61
  final double height;

62
  /// The decorations to paint near the text (e.g., an underline).
63
  final TextDecoration decoration;
64

65
  /// The color in which to paint the text decorations.
66
  final Color decorationColor;
67

68
  /// The style in which to paint the text decorations (e.g., dashed).
69 70
  final TextDecorationStyle decorationStyle;

71
  /// Creates a copy of this text style but with the given fields replaced with the new values.
72 73 74 75 76
  TextStyle copyWith({
    Color color,
    String fontFamily,
    double fontSize,
    FontWeight fontWeight,
77
    FontStyle fontStyle,
78
    double letterSpacing,
79
    double wordSpacing,
80
    TextBaseline textBaseline,
81
    double height,
82
    TextDecoration decoration,
83 84 85 86
    Color decorationColor,
    TextDecorationStyle decorationStyle
  }) {
    return new TextStyle(
87
      inherit: inherit,
88 89 90 91
      color: color != null ? color : this.color,
      fontFamily: fontFamily != null ? fontFamily : this.fontFamily,
      fontSize: fontSize != null ? fontSize : this.fontSize,
      fontWeight: fontWeight != null ? fontWeight : this.fontWeight,
92
      fontStyle: fontStyle != null ? fontStyle : this.fontStyle,
93
      letterSpacing: letterSpacing != null ? letterSpacing : this.letterSpacing,
94
      wordSpacing: wordSpacing != null ? wordSpacing : this.wordSpacing,
95
      textBaseline: textBaseline != null ? textBaseline : this.textBaseline,
96 97 98 99 100 101 102
      height: height != null ? height : this.height,
      decoration: decoration != null ? decoration : this.decoration,
      decorationColor: decorationColor != null ? decorationColor : this.decorationColor,
      decorationStyle: decorationStyle != null ? decorationStyle : this.decorationStyle
    );
  }

103
  /// Returns a new text style that matches this text style but with some values
104 105
  /// replaced by the non-null parameters of the given text style. If the given
  /// text style is null, simply returns this text style.
106
  TextStyle merge(TextStyle other) {
107 108 109
    if (other == null)
      return this;
    assert(other.inherit);
110 111 112 113 114
    return copyWith(
      color: other.color,
      fontFamily: other.fontFamily,
      fontSize: other.fontSize,
      fontWeight: other.fontWeight,
115
      fontStyle: other.fontStyle,
116
      letterSpacing: other.letterSpacing,
117
      wordSpacing: other.wordSpacing,
118
      textBaseline: other.textBaseline,
119 120 121 122 123 124 125
      height: other.height,
      decoration: other.decoration,
      decorationColor: other.decorationColor,
      decorationStyle: other.decorationStyle
    );
  }

126 127 128 129 130 131 132 133 134 135 136
  /// 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),
      // TODO(ianh): Replace next line with "fontWeight: FontWeight.lerp(begin.fontWeight, end.fontWeight, t)," once engine is revved
137
      fontWeight: FontWeight.values[ui.lerpDouble(begin?.fontWeight?.index ?? FontWeight.normal.index, end?.fontWeight?.index ?? FontWeight.normal.index, t.clamp(0.0, 1.0)).round()],
138 139 140 141 142 143 144 145 146 147
      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
    );
  }
148

149
  /// The style information for text runs, encoded for use by `dart:ui`.
150 151 152 153 154 155 156 157 158
  ui.TextStyle get textStyle {
    return new ui.TextStyle(
      color: color,
      decoration: decoration,
      decorationColor: decorationColor,
      decorationStyle: decorationStyle,
      fontWeight: fontWeight,
      fontStyle: fontStyle,
      fontFamily: fontFamily,
159
      fontSize: fontSize,
160 161
      letterSpacing: letterSpacing,
      wordSpacing: wordSpacing,
Adam Barth's avatar
Adam Barth committed
162
      height: height
163 164 165
    );
  }

166
  /// The style information for paragraphs, encoded for use by `dart:ui`.
167
  ui.ParagraphStyle getParagraphStyle({ TextAlign textAlign }) {
168
    return new ui.ParagraphStyle(
169
      textAlign: textAlign,
170
      textBaseline: textBaseline,
Adam Barth's avatar
Adam Barth committed
171 172 173 174
      fontWeight: fontWeight,
      fontStyle: fontStyle,
      fontFamily: fontFamily,
      fontSize: fontSize,
175 176 177
      lineHeight: height
    );
  }
178

179
  @override
Hixie's avatar
Hixie committed
180
  bool operator ==(dynamic other) {
181 182
    if (identical(this, other))
      return true;
Hixie's avatar
Hixie committed
183 184 185
    if (other is! TextStyle)
      return false;
    final TextStyle typedOther = other;
186 187
    return inherit == typedOther.inherit &&
           color == typedOther.color &&
Hixie's avatar
Hixie committed
188 189 190 191
           fontFamily == typedOther.fontFamily &&
           fontSize == typedOther.fontSize &&
           fontWeight == typedOther.fontWeight &&
           fontStyle == typedOther.fontStyle &&
192
           letterSpacing == typedOther.letterSpacing &&
193
           wordSpacing == typedOther.wordSpacing &&
Hixie's avatar
Hixie committed
194
           textBaseline == typedOther.textBaseline &&
195
           height == typedOther.height &&
Hixie's avatar
Hixie committed
196 197 198
           decoration == typedOther.decoration &&
           decorationColor == typedOther.decorationColor &&
           decorationStyle == typedOther.decorationStyle;
199 200
  }

201
  @override
202
  int get hashCode {
203
    return hashValues(
204
      inherit,
205 206 207 208 209 210
      color,
      fontFamily,
      fontSize,
      fontWeight,
      fontStyle,
      letterSpacing,
211
      wordSpacing,
212
      textBaseline,
213
      height,
214 215 216 217
      decoration,
      decorationColor,
      decorationStyle
    );
218 219
  }

220
  @override
221
  String toString([String prefix = '']) {
Hixie's avatar
Hixie committed
222
    List<String> result = <String>[];
223
    result.add('${prefix}inherit: $inherit');
224 225 226
    if (color != null)
      result.add('${prefix}color: $color');
    if (fontFamily != null)
Hixie's avatar
Hixie committed
227
      result.add('${prefix}family: "$fontFamily"');
228
    if (fontSize != null)
Hixie's avatar
Hixie committed
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
      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;
      }
    }
271
    if (letterSpacing != null)
272 273 274
      result.add('${prefix}letterSpacing: ${letterSpacing}x');
    if (wordSpacing != null)
      result.add('${prefix}wordSpacing: ${wordSpacing}x');
Hixie's avatar
Hixie committed
275 276 277 278 279 280 281 282 283 284
    if (textBaseline != null) {
      switch (textBaseline) {
        case TextBaseline.alphabetic:
          result.add('${prefix}baseline: alphabetic');
          break;
        case TextBaseline.ideographic:
          result.add('${prefix}baseline: ideographic');
          break;
      }
    }
285 286
    if (height != null)
      result.add('${prefix}height: ${height}x');
Hixie's avatar
Hixie committed
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
    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 += ' ';
319
        decorationDescription += '$decoration';
Hixie's avatar
Hixie committed
320 321 322 323 324
        haveDecorationDescription = true;
      }
      assert(haveDecorationDescription);
      result.add(decorationDescription);
    }
325
    if (result.isEmpty)
Hixie's avatar
Hixie committed
326
      return '$prefix<no style specified>';
327 328 329
    return result.join('\n');
  }
}