typography.dart 18.5 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 'package:flutter/foundation.dart';
6
import 'package:flutter/painting.dart';
7 8

import 'colors.dart';
9

10 11
// TODO(eseidel): Font weights are supposed to be language relative.
// TODO(jackson): Baseline should be language relative.
12
// TODO(ianh): These values are for English-like text.
13 14 15 16 17 18 19 20 21 22

/// Material design text theme.
///
/// Definitions for the various typographical styles found in material design
/// (e.g., headline, caption). Rather than creating a [TextTheme] directly,
/// you can obtain an instance as [Typography.black] or [Typography.white].
///
/// To obtain the current text theme, call [Theme.of] with the current
/// [BuildContext] and read the [ThemeData.textTheme] property.
///
23 24 25 26 27 28 29 30 31 32
/// The following image [from the material design
/// specification](https://material.io/guidelines/style/typography.html#typography-styles)
/// shows the recommended styles for each of the properties of a [TextTheme].
/// This image uses the `Roboto` font, which is the font used on Android. On
/// iOS, the [San Francisco
/// font](https://developer.apple.com/ios/human-interface-guidelines/visual-design/typography/)
/// is automatically used instead.
///
/// ![To see the image, visit the typography site referenced below.](https://storage.googleapis.com/material-design/publish/material_v_11/assets/0Bzhp5Z4wHba3alhXZ2pPWGk3Zjg/style_typography_styles_scale.png)
///
33 34
/// See also:
///
35 36 37
///  * [Typography], the class that generates [TextTheme]s appropriate for a platform.
///  * [Theme], for other aspects of a material design application that can be
///    globally adjusted, such as the color scheme.
38
///  * <http://material.google.com/style/typography.html>
39
@immutable
40
class TextTheme {
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
  /// Create a text theme that uses the given values.
  ///
  /// Rather than creating a new text theme, consider using [Typography.black]
  /// or [Typography.white], which implement the typography styles in the
  /// material design specification:
  ///
  /// <https://material.google.com/style/typography.html#typography-styles>
  ///
  /// If you do decide to create your own text theme, consider using one of
  /// those predefined themes as a starting point for [copyWith] or [apply].
  const TextTheme({
    this.display4,
    this.display3,
    this.display2,
    this.display1,
    this.headline,
    this.title,
    this.subhead,
    this.body2,
    this.body1,
    this.caption,
62
    this.button,
63
  });
64

65
  const TextTheme._blackMountainView()
66 67 68 69 70 71 72 73 74 75 76
    : display4 = const TextStyle(fontFamily: 'Roboto',         inherit: false, fontSize: 112.0, fontWeight: FontWeight.w100, color: Colors.black54, textBaseline: TextBaseline.alphabetic),
      display3 = const TextStyle(fontFamily: 'Roboto',         inherit: false, fontSize:  56.0, fontWeight: FontWeight.w400, color: Colors.black54, textBaseline: TextBaseline.alphabetic),
      display2 = const TextStyle(fontFamily: 'Roboto',         inherit: false, fontSize:  45.0, fontWeight: FontWeight.w400, color: Colors.black54, textBaseline: TextBaseline.alphabetic),
      display1 = const TextStyle(fontFamily: 'Roboto',         inherit: false, fontSize:  34.0, fontWeight: FontWeight.w400, color: Colors.black54, textBaseline: TextBaseline.alphabetic),
      headline = const TextStyle(fontFamily: 'Roboto',         inherit: false, fontSize:  24.0, fontWeight: FontWeight.w400, color: Colors.black87, textBaseline: TextBaseline.alphabetic),
      title    = const TextStyle(fontFamily: 'Roboto',         inherit: false, fontSize:  20.0, fontWeight: FontWeight.w500, color: Colors.black87, textBaseline: TextBaseline.alphabetic),
      subhead  = const TextStyle(fontFamily: 'Roboto',         inherit: false, fontSize:  16.0, fontWeight: FontWeight.w400, color: Colors.black87, textBaseline: TextBaseline.alphabetic),
      body2    = const TextStyle(fontFamily: 'Roboto',         inherit: false, fontSize:  14.0, fontWeight: FontWeight.w500, color: Colors.black87, textBaseline: TextBaseline.alphabetic),
      body1    = const TextStyle(fontFamily: 'Roboto',         inherit: false, fontSize:  14.0, fontWeight: FontWeight.w400, color: Colors.black87, textBaseline: TextBaseline.alphabetic),
      caption  = const TextStyle(fontFamily: 'Roboto',         inherit: false, fontSize:  12.0, fontWeight: FontWeight.w400, color: Colors.black54, textBaseline: TextBaseline.alphabetic),
      button   = const TextStyle(fontFamily: 'Roboto',         inherit: false, fontSize:  14.0, fontWeight: FontWeight.w500, color: Colors.black87, textBaseline: TextBaseline.alphabetic);
77

78
  const TextTheme._whiteMountainView()
79 80 81 82 83 84 85 86 87 88 89
    : display4 = const TextStyle(fontFamily: 'Roboto',         inherit: false, fontSize: 112.0, fontWeight: FontWeight.w100, color: Colors.white70, textBaseline: TextBaseline.alphabetic),
      display3 = const TextStyle(fontFamily: 'Roboto',         inherit: false, fontSize:  56.0, fontWeight: FontWeight.w400, color: Colors.white70, textBaseline: TextBaseline.alphabetic),
      display2 = const TextStyle(fontFamily: 'Roboto',         inherit: false, fontSize:  45.0, fontWeight: FontWeight.w400, color: Colors.white70, textBaseline: TextBaseline.alphabetic),
      display1 = const TextStyle(fontFamily: 'Roboto',         inherit: false, fontSize:  34.0, fontWeight: FontWeight.w400, color: Colors.white70, textBaseline: TextBaseline.alphabetic),
      headline = const TextStyle(fontFamily: 'Roboto',         inherit: false, fontSize:  24.0, fontWeight: FontWeight.w400, color: Colors.white,   textBaseline: TextBaseline.alphabetic),
      title    = const TextStyle(fontFamily: 'Roboto',         inherit: false, fontSize:  20.0, fontWeight: FontWeight.w500, color: Colors.white,   textBaseline: TextBaseline.alphabetic),
      subhead  = const TextStyle(fontFamily: 'Roboto',         inherit: false, fontSize:  16.0, fontWeight: FontWeight.w400, color: Colors.white,   textBaseline: TextBaseline.alphabetic),
      body2    = const TextStyle(fontFamily: 'Roboto',         inherit: false, fontSize:  14.0, fontWeight: FontWeight.w500, color: Colors.white,   textBaseline: TextBaseline.alphabetic),
      body1    = const TextStyle(fontFamily: 'Roboto',         inherit: false, fontSize:  14.0, fontWeight: FontWeight.w400, color: Colors.white,   textBaseline: TextBaseline.alphabetic),
      caption  = const TextStyle(fontFamily: 'Roboto',         inherit: false, fontSize:  12.0, fontWeight: FontWeight.w400, color: Colors.white70, textBaseline: TextBaseline.alphabetic),
      button   = const TextStyle(fontFamily: 'Roboto',         inherit: false, fontSize:  14.0, fontWeight: FontWeight.w500, color: Colors.white,   textBaseline: TextBaseline.alphabetic);
90

91
  const TextTheme._blackCupertino()
92 93 94 95 96 97
    : display4 = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize: 112.0, fontWeight: FontWeight.w100, color: Colors.black54, textBaseline: TextBaseline.alphabetic),
      display3 = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize:  56.0, fontWeight: FontWeight.w400, color: Colors.black54, textBaseline: TextBaseline.alphabetic),
      display2 = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize:  45.0, fontWeight: FontWeight.w400, color: Colors.black54, textBaseline: TextBaseline.alphabetic),
      display1 = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize:  34.0, fontWeight: FontWeight.w400, color: Colors.black54, textBaseline: TextBaseline.alphabetic),
      headline = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize:  24.0, fontWeight: FontWeight.w400, color: Colors.black87, textBaseline: TextBaseline.alphabetic),
      title    = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize:  20.0, fontWeight: FontWeight.w500, color: Colors.black87, textBaseline: TextBaseline.alphabetic),
98 99 100 101 102
      subhead  = const TextStyle(fontFamily: '.SF UI Text',    inherit: false, fontSize:  16.0, fontWeight: FontWeight.w400, color: Colors.black87, textBaseline: TextBaseline.alphabetic),
      body2    = const TextStyle(fontFamily: '.SF UI Text',    inherit: false, fontSize:  14.0, fontWeight: FontWeight.w500, color: Colors.black87, textBaseline: TextBaseline.alphabetic),
      body1    = const TextStyle(fontFamily: '.SF UI Text',    inherit: false, fontSize:  14.0, fontWeight: FontWeight.w400, color: Colors.black87, textBaseline: TextBaseline.alphabetic),
      caption  = const TextStyle(fontFamily: '.SF UI Text',    inherit: false, fontSize:  12.0, fontWeight: FontWeight.w400, color: Colors.black54, textBaseline: TextBaseline.alphabetic),
      button   = const TextStyle(fontFamily: '.SF UI Text',    inherit: false, fontSize:  14.0, fontWeight: FontWeight.w500, color: Colors.black87, textBaseline: TextBaseline.alphabetic);
103 104

  const TextTheme._whiteCupertino()
105 106 107 108 109 110
    : display4 = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize: 112.0, fontWeight: FontWeight.w100, color: Colors.white70, textBaseline: TextBaseline.alphabetic),
      display3 = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize:  56.0, fontWeight: FontWeight.w400, color: Colors.white70, textBaseline: TextBaseline.alphabetic),
      display2 = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize:  45.0, fontWeight: FontWeight.w400, color: Colors.white70, textBaseline: TextBaseline.alphabetic),
      display1 = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize:  34.0, fontWeight: FontWeight.w400, color: Colors.white70, textBaseline: TextBaseline.alphabetic),
      headline = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize:  24.0, fontWeight: FontWeight.w400, color: Colors.white,   textBaseline: TextBaseline.alphabetic),
      title    = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize:  20.0, fontWeight: FontWeight.w500, color: Colors.white,   textBaseline: TextBaseline.alphabetic),
111 112 113 114 115
      subhead  = const TextStyle(fontFamily: '.SF UI Text',    inherit: false, fontSize:  16.0, fontWeight: FontWeight.w400, color: Colors.white,   textBaseline: TextBaseline.alphabetic),
      body2    = const TextStyle(fontFamily: '.SF UI Text',    inherit: false, fontSize:  14.0, fontWeight: FontWeight.w500, color: Colors.white,   textBaseline: TextBaseline.alphabetic),
      body1    = const TextStyle(fontFamily: '.SF UI Text',    inherit: false, fontSize:  14.0, fontWeight: FontWeight.w400, color: Colors.white,   textBaseline: TextBaseline.alphabetic),
      caption  = const TextStyle(fontFamily: '.SF UI Text',    inherit: false, fontSize:  12.0, fontWeight: FontWeight.w400, color: Colors.white70, textBaseline: TextBaseline.alphabetic),
      button   = const TextStyle(fontFamily: '.SF UI Text',    inherit: false, fontSize:  14.0, fontWeight: FontWeight.w500, color: Colors.white,   textBaseline: TextBaseline.alphabetic);
116

117 118 119
  /// Extremely large text.
  ///
  /// The font size is 112 pixels.
120
  final TextStyle display4;
121 122 123

  /// Very, very large text.
  ///
124
  /// Used for the date in the dialog shown by [showDatePicker].
125
  final TextStyle display3;
126 127

  /// Very large text.
128
  final TextStyle display2;
129 130

  /// Large text.
131
  final TextStyle display1;
132

133 134
  /// Used for large text in dialogs (e.g., the month and year in the dialog
  /// shown by [showDatePicker]).
135
  final TextStyle headline;
136

137 138
  /// Used for the primary text in app bars and dialogs (e.g., [AppBar.title]
  /// and [AlertDialog.title]).
139
  final TextStyle title;
140

141
  /// Used for the primary text in lists (e.g., [ListTile.title]).
142
  final TextStyle subhead;
143 144

  /// Used for emphasizing text that would otherwise be [body1].
145
  final TextStyle body2;
146 147

  /// Used for the default text style for [Material].
148
  final TextStyle body1;
149

Ian Hickson's avatar
Ian Hickson committed
150
  /// Used for auxillary text associated with images.
151
  final TextStyle caption;
152 153

  /// Used for text on [RaisedButton] and [FlatButton].
154 155
  final TextStyle button;

156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
  /// Creates a copy of this text theme but with the given fields replaced with
  /// the new values.
  ///
  /// Consider using [Typography.black] or [Typography.white], which implement
  /// the typography styles in the material design specification, as a starting
  /// point.
  TextTheme copyWith({
    TextStyle display4,
    TextStyle display3,
    TextStyle display2,
    TextStyle display1,
    TextStyle headline,
    TextStyle title,
    TextStyle subhead,
    TextStyle body2,
    TextStyle body1,
    TextStyle caption,
    TextStyle button
  }) {
    return new TextTheme(
      display4: display4 ?? this.display4,
      display3: display3 ?? this.display3,
      display2: display2 ?? this.display2,
      display1: display1 ?? this.display1,
      headline: headline ?? this.headline,
      title: title ?? this.title,
      subhead: subhead ?? this.subhead,
      body2: body2 ?? this.body2,
      body1: body1 ?? this.body1,
      caption: caption ?? this.caption,
186
      button: button ?? this.button,
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
    );
  }

  /// Creates a copy of this text theme but with the given field replaced in
  /// each of the individual text styles.
  ///
  /// The `displayColor` is applied to [display4], [display3], [display2],
  /// [display1], and [caption]. The `bodyColor` is applied to the remaining
  /// text styles.
  ///
  /// Consider using [Typography.black] or [Typography.white], which implement
  /// the typography styles in the material design specification, as a starting
  /// point.
  TextTheme apply({
    String fontFamily,
    double fontSizeFactor: 1.0,
    double fontSizeDelta: 0.0,
    Color displayColor,
    Color bodyColor
  }) {
    return new TextTheme(
      display4: display4.apply(
        color: displayColor,
        fontFamily: fontFamily,
        fontSizeFactor: fontSizeFactor,
212
        fontSizeDelta: fontSizeDelta,
213 214 215 216 217
      ),
      display3: display3.apply(
        color: displayColor,
        fontFamily: fontFamily,
        fontSizeFactor: fontSizeFactor,
218
        fontSizeDelta: fontSizeDelta,
219 220 221 222 223
      ),
      display2: display2.apply(
        color: displayColor,
        fontFamily: fontFamily,
        fontSizeFactor: fontSizeFactor,
224
        fontSizeDelta: fontSizeDelta,
225 226 227 228 229
      ),
      display1: display1.apply(
        color: displayColor,
        fontFamily: fontFamily,
        fontSizeFactor: fontSizeFactor,
230
        fontSizeDelta: fontSizeDelta,
231 232 233 234 235
      ),
      headline: headline.apply(
        color: bodyColor,
        fontFamily: fontFamily,
        fontSizeFactor: fontSizeFactor,
236
        fontSizeDelta: fontSizeDelta,
237 238 239 240 241
      ),
      title: title.apply(
        color: bodyColor,
        fontFamily: fontFamily,
        fontSizeFactor: fontSizeFactor,
242
        fontSizeDelta: fontSizeDelta,
243 244 245 246 247
      ),
      subhead: subhead.apply(
        color: bodyColor,
        fontFamily: fontFamily,
        fontSizeFactor: fontSizeFactor,
248
        fontSizeDelta: fontSizeDelta,
249 250 251 252 253
      ),
      body2: body2.apply(
        color: bodyColor,
        fontFamily: fontFamily,
        fontSizeFactor: fontSizeFactor,
254
        fontSizeDelta: fontSizeDelta,
255 256 257 258 259
      ),
      body1: body1.apply(
        color: bodyColor,
        fontFamily: fontFamily,
        fontSizeFactor: fontSizeFactor,
260
        fontSizeDelta: fontSizeDelta,
261 262 263 264 265
      ),
      caption: caption.apply(
        color: displayColor,
        fontFamily: fontFamily,
        fontSizeFactor: fontSizeFactor,
266
        fontSizeDelta: fontSizeDelta,
267 268 269 270 271
      ),
      button: button.apply(
        color: bodyColor,
        fontFamily: fontFamily,
        fontSizeFactor: fontSizeFactor,
272
        fontSizeDelta: fontSizeDelta,
273 274 275 276
      ),
    );
  }

277
  /// Linearly interpolate between two text themes.
278
  static TextTheme lerp(TextTheme begin, TextTheme end, double t) {
279 280 281 282 283 284 285 286 287 288 289
    return new TextTheme(
      display4: TextStyle.lerp(begin.display4, end.display4, t),
      display3: TextStyle.lerp(begin.display3, end.display3, t),
      display2: TextStyle.lerp(begin.display2, end.display2, t),
      display1: TextStyle.lerp(begin.display1, end.display1, t),
      headline: TextStyle.lerp(begin.headline, end.headline, t),
      title: TextStyle.lerp(begin.title, end.title, t),
      subhead: TextStyle.lerp(begin.subhead, end.subhead, t),
      body2: TextStyle.lerp(begin.body2, end.body2, t),
      body1: TextStyle.lerp(begin.body1, end.body1, t),
      caption: TextStyle.lerp(begin.caption, end.caption, t),
290
      button: TextStyle.lerp(begin.button, end.button, t),
291 292
    );
  }
293 294 295 296 297

  @override
  bool operator ==(dynamic other) {
    if (identical(this, other))
      return true;
298
    if (other.runtimeType != runtimeType)
299 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
      return false;
    final TextTheme typedOther = other;
    return display4 == typedOther.display4 &&
           display3 == typedOther.display3 &&
           display2 == typedOther.display2 &&
           display1 == typedOther.display1 &&
           headline == typedOther.headline &&
           title == typedOther.title &&
           subhead == typedOther.subhead &&
           body2 == typedOther.body2 &&
           body1 == typedOther.body1 &&
           caption == typedOther.caption &&
           button == typedOther.button;
  }

  @override
  int get hashCode {
    return hashValues(
      display4,
      display3,
      display2,
      display1,
      headline,
      title,
      subhead,
      body2,
      body1,
      caption,
      button,
    );
  }
330
}
331

332 333
/// The two material design text themes.
///
334 335 336 337
/// Material design defines two text themes: [black] and [white]. The black
/// text theme, which uses dark glyphs, is used on light backgrounds in light
/// themes. The white text theme, which uses light glyphs, is used in dark
/// themes and on dark backgrounds in light themes.
338 339 340 341 342 343
///
/// To obtain the current text theme, call [Theme.of] with the current
/// [BuildContext] and read the [ThemeData.textTheme] property.
///
/// See also:
///
344 345 346
///  * [TextTheme], which shows what the text styles in a theme look like.
///  * [Theme], for other aspects of a material design application that can be
///    globally adjusted, such as the color scheme.
347
///  * <http://material.google.com/style/typography.html>
348
class Typography {
349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368
  /// Creates the default typography for the specified platform.
  factory Typography({ @required TargetPlatform platform }) {
    assert(platform != null);
    switch (platform) {
      case TargetPlatform.android:
      case TargetPlatform.fuchsia:
        return const Typography._(
            const TextTheme._blackMountainView(),
            const TextTheme._whiteMountainView(),
        );
      case TargetPlatform.iOS:
        return const Typography._(
            const TextTheme._blackCupertino(),
            const TextTheme._whiteCupertino(),
        );
    }
    return null;
  }

  const Typography._(this.black, this.white);
369

370
  /// A material design text theme with dark glyphs.
371
  final TextTheme black;
372

373
  /// A material design text theme with light glyphs.
374
  final TextTheme white;
375
}