3575 lines
132 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.12
part of dart.ui;
/// Whether to slant the glyphs in the font
enum FontStyle {
/// Use the upright glyphs
normal,
/// Use glyphs designed for slanting
italic,
}
/// The thickness of the glyphs used to draw the text
class FontWeight {
const FontWeight._(this.index);
/// The encoded integer value of this font weight.
final int index;
/// Thin, the least thick
static const FontWeight w100 = FontWeight._(0);
/// Extra-light
static const FontWeight w200 = FontWeight._(1);
/// Light
static const FontWeight w300 = FontWeight._(2);
/// Normal / regular / plain
static const FontWeight w400 = FontWeight._(3);
/// Medium
static const FontWeight w500 = FontWeight._(4);
/// Semi-bold
static const FontWeight w600 = FontWeight._(5);
/// Bold
static const FontWeight w700 = FontWeight._(6);
/// Extra-bold
static const FontWeight w800 = FontWeight._(7);
/// Black, the most thick
static const FontWeight w900 = FontWeight._(8);
/// The default font weight.
static const FontWeight normal = w400;
/// A commonly used font weight that is heavier than normal.
static const FontWeight bold = w700;
/// A list of all the font weights.
static const List<FontWeight> values = <FontWeight>[
w100, w200, w300, w400, w500, w600, w700, w800, w900
];
/// Linearly interpolates between two font weights.
///
/// Rather than using fractional weights, the interpolation rounds to the
/// nearest weight.
///
/// If both `a` and `b` are null, then this method will return null. Otherwise,
/// any null values for `a` or `b` are interpreted as equivalent to [normal]
/// (also known as [w400]).
///
/// The `t` argument represents position on the timeline, with 0.0 meaning
/// that the interpolation has not started, returning `a` (or something
/// equivalent to `a`), 1.0 meaning that the interpolation has finished,
/// returning `b` (or something equivalent to `b`), and values in between
/// meaning that the interpolation is at the relevant point on the timeline
/// between `a` and `b`. The interpolation can be extrapolated beyond 0.0 and
/// 1.0, so negative values and values greater than 1.0 are valid (and can
/// easily be generated by curves such as [Curves.elasticInOut]). The result
/// is clamped to the range [w100][w900].
///
/// Values for `t` are usually obtained from an [Animation<double>], such as
/// an [AnimationController].
static FontWeight? lerp(FontWeight? a, FontWeight? b, double t) {
assert(t != null);
if (a == null && b == null)
return null;
return values[_lerpInt((a ?? normal).index, (b ?? normal).index, t).round().clamp(0, 8)];
}
@override
String toString() {
return const <int, String>{
0: 'FontWeight.w100',
1: 'FontWeight.w200',
2: 'FontWeight.w300',
3: 'FontWeight.w400',
4: 'FontWeight.w500',
5: 'FontWeight.w600',
6: 'FontWeight.w700',
7: 'FontWeight.w800',
8: 'FontWeight.w900',
}[index]!;
}
}
/// A feature tag and value that affect the selection of glyphs in a font.
///
/// Different fonts support different features. Consider using a tool
/// such as <https://wakamaifondue.com/> to examine your fonts to
/// determine what features are available.
///
/// {@tool sample --template=stateless_widget_material}
///
/// This example shows usage of several OpenType font features,
/// including Small Caps (selected manually using the "smcp" code),
/// old-style figures, fractional ligatures, and stylistic sets.
///
/// ```dart dartImports
/// import 'dart:ui';
/// ```
///
/// ```dart preamble
/// final TextStyle titleStyle = TextStyle(
/// fontSize: 18,
/// fontFeatures: const <FontFeature>[FontFeature.enable('smcp')],
/// color: Colors.blueGrey[600],
/// );
/// ```
///
/// ```dart
/// Widget build(BuildContext context) {
/// // The Cardo, Milonga and Raleway Dots fonts can be downloaded from
/// // Google Fonts (https://www.google.com/fonts).
/// return Scaffold(
/// body: Center(
/// child: Column(
/// mainAxisAlignment: MainAxisAlignment.center,
/// children: <Widget>[
/// const Spacer(flex: 5),
/// Text('regular numbers have their place:', style: titleStyle),
/// const Text('The 1972 cup final was a 1-1 draw.',
/// style: TextStyle(
/// fontFamily: 'Cardo',
/// fontSize: 24,
/// )),
/// const Spacer(),
/// Text('but old-style figures blend well with lower case:',
/// style: titleStyle),
/// const Text('The 1972 cup final was a 1-1 draw.',
/// style: TextStyle(
/// fontFamily: 'Cardo',
/// fontSize: 24,
/// fontFeatures: const <FontFeature>[FontFeature.oldstyleFigures()])),
/// const Spacer(),
/// const Divider(),
/// const Spacer(),
/// Text('fractions look better with a custom ligature:',
/// style: titleStyle),
/// const Text('Add 1/2 tsp of flour and stir.',
/// style: TextStyle(
/// fontFamily: 'Milonga',
/// fontSize: 24,
/// fontFeatures: <FontFeature>[FontFeature.alternativeFractions()])),
/// const Spacer(),
/// const Divider(),
/// const Spacer(),
/// Text('multiple stylistic sets in one font:', style: titleStyle),
/// const Text('Raleway Dots',
/// style: TextStyle(fontFamily: 'Raleway Dots', fontSize: 48)),
/// Text('Raleway Dots',
/// style: TextStyle(
/// fontFeatures: <FontFeature>[FontFeature.stylisticSet(1)],
/// fontFamily: 'Raleway Dots',
/// fontSize: 48,
/// )),
/// const Spacer(flex: 5),
/// ],
/// ),
/// ),
/// );
/// }
/// ```
/// {@end-tool}
///
/// See also:
///
/// * <https://en.wikipedia.org/wiki/List_of_typographic_features>,
/// Wikipedia's description of these typographic features.
///
/// * <https://docs.microsoft.com/en-us/typography/opentype/spec/featuretags>,
/// Microsoft's registry of these features.
class FontFeature {
/// Creates a [FontFeature] object, which can be added to a [TextStyle] to
/// change how the engine selects glyphs when rendering text.
///
/// `feature` is the four-character tag that identifies the feature.
/// These tags are specified by font formats such as OpenType.
///
/// `value` is the value that the feature will be set to. The behavior
/// of the value depends on the specific feature. Many features are
/// flags whose value can be 1 (when enabled) or 0 (when disabled).
///
/// See <https://docs.microsoft.com/en-us/typography/opentype/spec/featuretags>
const FontFeature(
this.feature,
[ this.value = 1 ]
) : assert(feature != null),
assert(feature.length == 4, 'Feature tag must be exactly four characters long.'),
assert(value != null),
assert(value >= 0, 'Feature value must be zero or a positive integer.');
/// Create a [FontFeature] object that enables the feature with the given tag.
const FontFeature.enable(String feature) : this(feature, 1);
/// Create a [FontFeature] object that disables the feature with the given tag.
const FontFeature.disable(String feature) : this(feature, 0);
// Features below should be alphabetic by feature tag. This makes it
// easier to determine when a feature is missing so that we avoid
// adding duplicates.
//
// The full list is extremely long, and many of the features are
// language-specific, or indeed force-enabled for particular locales
// by HarfBuzz, so we don't even attempt to be comprehensive here.
// Features listed below are those we deemed "interesting enough" to
// have their own constructor, mostly on the basis of whether we
// could find a font where the feature had a useful effect that
// could be demonstrated.
// Start of feature tag list.
// ------------------------------------------------------------------------
// aalt
/// Access alternative glyphs. (`aalt`)
///
/// This feature selects the given glyph variant for glyphs in the span.
///
/// {@tool sample --template=stateless_widget}
///
/// The Raleway font supports several alternate glyphs. The code
/// below shows how specific glyphs can be selected. With `aalt` set
/// to zero, the default, the normal glyphs are used. With a
/// non-zero value, Raleway substitutes small caps for lower case
/// letters. With value 2, the lowercase "a" changes to a stemless
/// "a", whereas the lowercase "t" changes to a vertical bar instead
/// of having a curve. By targeting specific letters in the text
/// (using [Text.rich]), the desired rendering for each glyph can be
/// achieved.
///
/// ```dart dartImports
/// import 'dart:ui';
/// ```
///
/// ```dart
/// Widget build(BuildContext context) {
/// // The Raleway font can be downloaded from Google Fonts (https://www.google.com/fonts).
/// return const Text(
/// 'The infamous Tuna Torture.',
/// style: TextStyle(
/// fontFamily: 'Raleway',
/// fontFeatures: <FontFeature>[
/// FontFeature.alternative(1), // or 2, or 3, or...
/// ],
/// ),
/// );
/// }
/// ```
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_aalt.png)
/// {@end-tool}
///
/// See also:
///
/// * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_ae#aalt>
const FontFeature.alternative(this.value) : feature = 'aalt';
// afrc
/// Use alternative ligatures to represent fractions. (`afrc`)
///
/// When this feature is enabled (and the font supports it),
/// sequences of digits separated by U+002F SOLIDUS character (/) or
/// U+2044 FRACTION SLASH () are replaced by ligatures that
/// represent the corresponding fraction. These ligatures may differ
/// from those used by the [FontFeature.fractions] feature.
///
/// This feature overrides all other features.
///
/// {@tool sample --template=stateless_widget}
///
/// The Ubuntu Mono font supports the `afrc` feature. It causes digits
/// before slashes to become superscripted and digits after slashes to become
/// subscripted. This contrasts to the effect seen with [FontFeature.fractions].
///
/// ```dart dartImports
/// import 'dart:ui';
/// ```
///
/// ```dart
/// Widget build(BuildContext context) {
/// // The Ubuntu Mono font can be downloaded from Google Fonts (https://www.google.com/fonts).
/// return const Text(
/// 'Fractions: 1/2 2/3 3/4 4/5',
/// style: TextStyle(
/// fontFamily: 'Ubuntu Mono',
/// fontFeatures: <FontFeature>[
/// FontFeature.alternativeFractions(),
/// ],
/// ),
/// );
/// }
/// ```
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_afrc.png)
/// {@end-tool}
///
/// See also:
///
/// * [FontFeature.fractions], which has a similar (but different) effect.
/// * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_ae#afrc>
const FontFeature.alternativeFractions() : feature = 'afrc', value = 1;
// calt
/// Enable contextual alternates. (`calt`)
///
/// With this feature enabled, specific glyphs may be replaced by
/// alternatives based on nearby text.
///
/// {@tool sample --template=stateless_widget}
///
/// The Barriecito font supports the `calt` feature. It causes some
/// letters in close proximity to other instances of themselves to
/// use different glyphs, to give the appearance of more variation
/// in the glyphs, rather than having each letter always use a
/// particular glyph.
///
/// ```dart dartImports
/// import 'dart:ui';
/// ```
///
/// ```dart
/// Widget build(BuildContext context) {
/// // The Barriecito font can be downloaded from Google Fonts (https://www.google.com/fonts).
/// return const Text(
/// 'Ooohh, we weren\'t going to tell him that.',
/// style: TextStyle(
/// fontFamily: 'Barriecito',
/// fontFeatures: <FontFeature>[
/// FontFeature.contextualAlternates(),
/// ],
/// ),
/// );
/// }
/// ```
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_calt.png)
/// {@end-tool}
///
/// See also:
///
/// * [FontFeature.randomize], which is more a rarely supported but more
/// powerful way to get a similar effect.
/// * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_ae#calt>
const FontFeature.contextualAlternates() : feature = 'calt', value = 1;
// case
/// Enable case-sensitive forms. (`case`)
///
/// Some glyphs, for example parentheses or operators, are typically
/// designed to fit nicely with mixed case, or even predominantly
/// lowercase, text. When these glyphs are placed near strings of
/// capital letters, they appear a little off-center.
///
/// This feature, when supported by the font, causes these glyphs to
/// be shifted slightly, or otherwise adjusted, so as to form a more
/// aethestically pleasing combination with capital letters.
///
/// {@tool sample --template=stateless_widget}
///
/// The Piazzolla font supports the `case` feature. It causes
/// parentheses, brackets, braces, guillemets, slashes, bullets, and
/// some other glyphs (not shown below) to be shifted up slightly so
/// that capital letters appear centered in comparison. When the
/// feature is disabled, those glyphs are optimized for use with
/// lowercase letters, and so capital letters appear to ride higher
/// relative to the punctuation marks.
///
/// The difference is very subtle. It may be most obvious when
/// examining the square brackets compared to the capital A.
///
/// ```dart dartImports
/// import 'dart:ui';
/// ```
///
/// ```dart
/// Widget build(BuildContext context) {
/// // The Piazzolla font can be downloaded from Google Fonts (https://www.google.com/fonts).
/// return const Text(
/// '(A) [A] {A} «A» A/B A•B',
/// style: TextStyle(
/// fontFamily: 'Piazzolla',
/// fontFeatures: <FontFeature>[
/// FontFeature.caseSensitiveForms(),
/// ],
/// ),
/// );
/// }
/// ```
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_case.png)
/// {@end-tool}
///
/// See also:
///
/// * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_ae#case>
const FontFeature.caseSensitiveForms() : feature = 'case', value = 1;
// cvXX
/// Select a character variant. (`cv01` through `cv99`)
///
/// Fonts may have up to 99 character variant sets, numbered 1
/// through 99, each of which can be independently enabled or
/// disabled.
///
/// Related character variants are typically grouped into stylistic
/// sets, controlled by the [FontFeature.stylisticSet] feature
/// (`ssXX`).
///
/// {@tool sample --template=stateless_widget}
///
/// The Source Code Pro font supports the `cvXX` feature for several
/// characters. In the example below, variants 1 (`cv01`), 2
/// (`cv02`), and 4 (`cv04`) are selected. Variant 1 changes the
/// rendering of the "a" character, variant 2 changes the lowercase
/// "g" character, and variant 4 changes the lowercase "i" and "l"
/// characters. There are also variants (not shown here) that
/// control the rendering of various greek characters such as beta
/// and theta.
///
/// Notably, this can be contrasted with the stylistic sets, where
/// the set which affects the "a" character also affects beta, and
/// the set which affects the "g" character also affects theta and
/// delta.
///
/// ```dart dartImports
/// import 'dart:ui';
/// ```
///
/// ```dart
/// Widget build(BuildContext context) {
/// // The Source Code Pro font can be downloaded from Google Fonts (https://www.google.com/fonts).
/// return Text(
/// 'aáâ β gǵĝ θб Iiíî Ll',
/// style: TextStyle(
/// fontFamily: 'Source Code Pro',
/// fontFeatures: <FontFeature>[
/// FontFeature.characterVariant(1),
/// FontFeature.characterVariant(2),
/// FontFeature.characterVariant(4),
/// ],
/// ),
/// );
/// }
/// ```
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_cvXX.png)
/// {@end-tool}
///
/// See also:
///
/// * [FontFeature.stylisticSet], which allows for groups of characters
/// variants to be selected at once, as opposed to individual character variants.
/// * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_ae#cv01-cv99>
factory FontFeature.characterVariant(int value) {
assert(value >= 1);
assert(value <= 20);
return FontFeature('cv${value.toString().padLeft(2, "0")}');
}
// dnom
/// Display digits as denominators. (`dnom`)
///
/// This is typically used automatically by the font rendering
/// system as part of the implementation of `frac` for the denominator
/// part of fractions (see [FontFeature.fractions]).
///
/// {@tool sample --template=stateless_widget}
///
/// The Piazzolla font supports the `dnom` feature. It causes
/// the digits to be rendered smaller and near the bottom of the EM box.
///
/// ```dart dartImports
/// import 'dart:ui';
/// ```
///
/// ```dart
/// Widget build(BuildContext context) {
/// // The Piazzolla font can be downloaded from Google Fonts (https://www.google.com/fonts).
/// return const Text(
/// 'Fractions: 1/2 2/3 3/4 4/5',
/// style: TextStyle(
/// fontFamily: 'Piazzolla',
/// fontFeatures: <FontFeature>[
/// FontFeature.denominator(),
/// ],
/// ),
/// );
/// }
/// ```
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_dnom.png)
/// {@end-tool}
///
/// See also:
///
/// * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_ae#dnom>
const FontFeature.denominator() : feature = 'dnom', value = 1;
// frac
/// Use ligatures to represent fractions. (`afrc`)
///
/// When this feature is enabled (and the font supports it),
/// sequences of digits separated by U+002F SOLIDUS character (/) or
/// U+2044 FRACTION SLASH () are replaced by ligatures that
/// represent the corresponding fraction.
///
/// This feature may imply the [FontFeature.numerator] and
/// [FontFeature.denominator] features.
///
/// {@tool sample --template=stateless_widget}
///
/// The Ubuntu Mono font supports the `frac` feature. It causes
/// digits around slashes to be turned into dedicated fraction
/// glpyhs. This contrasts to the effect seen with
/// [FontFeature.alternativeFractions].
///
/// ```dart dartImports
/// import 'dart:ui';
/// ```
///
/// ```dart
/// Widget build(BuildContext context) {
/// // The Ubuntu Mono font can be downloaded from Google Fonts (https://www.google.com/fonts).
/// return const Text(
/// 'Fractions: 1/2 2/3 3/4 4/5',
/// style: TextStyle(
/// fontFamily: 'Ubuntu Mono',
/// fontFeatures: <FontFeature>[
/// FontFeature.fractions(),
/// ],
/// ),
/// );
/// }
/// ```
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_frac.png)
/// {@end-tool}
///
/// See also:
///
/// * [FontFeature.alternativeFractions], which has a similar (but different) effect.
/// * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_fj#frac>
const FontFeature.fractions() : feature = 'frac', value = 1;
// hist
/// Use historical forms. (`hist`)
///
/// Some fonts have alteratives for letters whose forms have changed
/// through the ages. In the Latin alphabet, this is common for
/// example with the long-form "s" or the Fraktur "k". This feature enables
/// those alternative glyphs.
///
/// This does not enable legacy ligatures, only single-character alternatives.
/// To enable historical ligatures, use [FontFeature.historicalLigatures].
///
/// This feature may override other glyph-substitution features.
///
/// {@tool sample --template=stateless_widget}
///
/// The Cardo font supports the `hist` feature specifically for the
/// letter "s": it changes occurrences of that letter for the glyph
/// used by U+017F LATIN SMALL LETTER LONG S.
///
/// ```dart dartImports
/// import 'dart:ui';
/// ```
///
/// ```dart
/// Widget build(BuildContext context) {
/// // The Cardo font can be downloaded from Google Fonts (https://www.google.com/fonts).
/// return const Text(
/// 'VIBRANT fish assisted his business.',
/// style: TextStyle(
/// fontFamily: 'Sorts Mill Goudy',
/// fontFeatures: <FontFeature>[
/// FontFeature.historicalForms(), // Enables "hist".
/// // Use FontFeature.historicalLigatures() to enable "hlig" as well.
/// ],
/// ),
/// );
/// }
/// ```
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_historical.png)
/// {@end-tool}
///
/// See also:
///
/// * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_fj#hist>
const FontFeature.historicalForms() : feature = 'hist', value = 1;
// hlig
/// Use historical ligatures. (`hlig`)
///
/// Some fonts support ligatures that have fallen out of favor today,
/// but were historically in common use. This feature enables those
/// ligatures.
///
/// For example, the "long s" glyph was historically typeset with
/// characters such as "t" and "h" as a single ligature.
///
/// This does not enable the legacy forms, only ligatures. See
/// [FontFeature.historicalForms] to enable single characters to be
/// replaced with their historical alternatives. Combining both is
/// usually desired since the ligatures typically apply specifically
/// to characters that have historical forms as well. For example,
/// the historical forms feature might replace the "s" character
/// with the "long s" (ſ) character, while the historical ligatures
/// feature might specifically apply to cases where "long s" is
/// followed by other characters such as "t". In such cases, without
/// the historical forms being enabled, the ligatures would only
/// apply when the "long s" is used explicitly.
///
/// This feature may override other glyph-substitution features.
///
/// {@tool sample --template=stateless_widget}
///
/// The Cardo font supports the `hlig` feature. It has legacy
/// ligatures for "VI" and "NT", and various ligatures involving the
/// "long s". In the example below, both historical forms (`hist 1`)
/// and historical ligatures (`hlig 1`) are enabled, so, for
/// instance, "fish" becomes "fiſh" which is then rendered using a
/// ligature for the last two characters.
///
/// Similarly, the word "business" is turned into "buſineſſ" by
/// `hist`, and the `ſi` and `ſſ` pairs are ligated by `hlig`.
/// Observe in particular the position of the dot of the "i" in
/// "business" in the various combinations of these features.
///
/// ```dart dartImports
/// import 'dart:ui';
/// ```
///
/// ```dart
/// Widget build(BuildContext context) {
/// // The Cardo font can be downloaded from Google Fonts (https://www.google.com/fonts).
/// return const Text(
/// 'VIBRANT fish assisted his business.',
/// style: TextStyle(
/// fontFamily: 'Sorts Mill Goudy',
/// fontFeatures: <FontFeature>[
/// FontFeature.historicalForms(), // Enables "hist".
/// FontFeature.historicalLigatures() // Enables "hlig".
/// ],
/// ),
/// );
/// }
/// ```
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_historical.png)
/// {@end-tool}
///
/// See also:
///
/// * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_fj#hlig>
const FontFeature.historicalLigatures() : feature = 'hlig', value = 1;
// lnum
/// Use lining figures. (`lnum`)
///
/// Some fonts have digits that, like lowercase latin letters, have
/// both descenders and ascenders. In some situations, especially in
/// conjunction with capital letters, this leads to an aesthetically
/// questionable irregularity. Lining figures, on the other hand,
/// have a uniform height, and align with the baseline and the
/// height of capital letters. Conceptually, they can be thought of
/// as "capital digits".
///
/// This feature may conflict with [FontFeature.oldstyleFigures].
///
/// {@tool sample --template=stateless_widget}
///
/// The Sorts Mill Goudy font supports the `lnum` feature. It causes
/// digits to fit more seamlessly with capital letters.
///
/// ```dart dartImports
/// import 'dart:ui';
/// ```
///
/// ```dart
/// Widget build(BuildContext context) {
/// // The Sorts Mill Goudy font can be downloaded from Google Fonts (https://www.google.com/fonts).
/// return const Text(
/// 'CALL 311-555-2368 NOW!',
/// style: TextStyle(
/// fontFamily: 'Sorts Mill Goudy',
/// fontFeatures: <FontFeature>[
/// FontFeature.liningFigures(),
/// ],
/// ),
/// );
/// }
/// ```
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_lnum.png)
/// {@end-tool}
///
/// See also:
///
/// * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_ko#lnum>
const FontFeature.liningFigures() : feature = 'lnum', value = 1;
// locl
/// Use locale-specific glyphs. (`locl`)
///
/// Some characters, most notably those in the Unicode Han
/// Unification blocks, vary in presentation based on the locale in
/// use. For example, the ideograph for "grass" (U+8349, 草) has a
/// broken top line in Traditional Chinese, but a solid top line in
/// Simplified Chinese, Japanese, Korean, and Vietnamese. This kind
/// of variation also exists with other alphabets, for example
/// Cyrilic characters as used in the Bulgarian and Serbian
/// alphabets vary from their Russian counterparts.
///
/// A particular font may default to the forms for the locale for
/// which it was constructed, but still support alternative forms
/// for other locales. When this feature is enabled, the locale (as
/// specified using [painting.TextStyle.locale], for instance) is
/// used to determine which glyphs to use when locale-specific
/// alternatives exist. Disabling this feature causes the font
/// rendering to ignore locale information and only use the default
/// glyphs.
///
/// This feature is enabled by default. Using
/// `FontFeature.localeAware(enable: false)` disables the
/// locale-awareness. (So does not specifying the locale in the
/// first place, of course.)
///
/// {@tool sample --template=stateless_widget}
///
/// The Noto Sans CJK font supports the `locl` feature for CJK characters.
/// In this example, the `localeAware` feature is not explicitly used, as it is
/// enabled by default. This example instead shows how to set the locale,
/// thus demonstrating how Noto Sans adapts the glyph shapes to the locale.
///
/// ```dart dartImports
/// import 'dart:ui';
/// ```
///
/// ```dart
/// Widget build(BuildContext context) {
/// // The Noto family of fonts can be downloaded from Google Fonts (https://www.google.com/fonts).
/// return const Text(
/// '次 化 刃 直 入 令',
/// locale: const Locale('zh', 'CN'), // or Locale('ja'), Locale('ko'), Locale('zh', 'TW'), etc
/// style: TextStyle(
/// fontFamily: 'Noto Sans',
/// ),
/// );
/// }
/// ```
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_locl.png)
/// {@end-tool}
///
/// See also:
///
/// * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_ko#locl>
/// * <https://en.wikipedia.org/wiki/Han_unification>
/// * <https://en.wikipedia.org/wiki/Cyrillic_script>
const FontFeature.localeAware({ bool enable = true }) : feature = 'locl', value = enable ? 1 : 0;
// nalt
/// Display alternative glyphs for numerals (alternate annotation forms). (`nalt`)
///
/// Replaces glyphs used in numbering lists (e.g. 1, 2, 3...; or a, b, c...) with notational
/// variants that might be more typographically interesting.
///
/// Fonts sometimes support multiple alternatives, and the argument
/// selects the set to use (a positive integer, or 0 to disable the
/// feature). The default set if none is specified is 1.
///
/// {@tool sample --template=stateless_widget}
///
/// The Gothic A1 font supports several notational variant sets via
/// the `nalt` feature.
///
/// Set 1 changes the spacing of the glyphs. Set 2 parenthesizes the
/// latin letters and reduces the numerals to subscripts. Set 3
/// circles the glyphs. Set 4 parenthesizes the digits. Set 5 uses
/// reverse-video circles for the digits. Set 7 superscripts the
/// digits.
///
/// The code below shows how to select set 3.
///
/// ```dart dartImports
/// import 'dart:ui';
/// ```
///
/// ```dart
/// Widget build(BuildContext context) {
/// // The Gothic A1 font can be downloaded from Google Fonts (https://www.google.com/fonts).
/// return const Text(
/// 'abc 123',
/// style: TextStyle(
/// fontFamily: 'Gothic A1',
/// fontFeatures: <FontFeature>[
/// FontFeature.notationalForms(3), // circled letters and digits
/// ],
/// ),
/// );
/// }
/// ```
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_nalt.png)
/// {@end-tool}
///
/// See also:
///
/// * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_ko#nalt>
const FontFeature.notationalForms([this.value = 1]) : feature = 'nalt', assert(value >= 0);
// numr
/// Display digits as numerators. (`numr`)
///
/// This is typically used automatically by the font rendering
/// system as part of the implementation of `frac` for the numerator
/// part of fractions (see [FontFeature.fractions]).
///
/// {@tool sample --template=stateless_widget}
///
/// The Piazzolla font supports the `numr` feature. It causes
/// the digits to be rendered smaller and near the top of the EM box.
///
/// ```dart dartImports
/// import 'dart:ui';
/// ```
///
/// ```dart
/// Widget build(BuildContext context) {
/// // The Piazzolla font can be downloaded from Google Fonts (https://www.google.com/fonts).
/// return const Text(
/// 'Fractions: 1/2 2/3 3/4 4/5',
/// style: TextStyle(
/// fontFamily: 'Piazzolla',
/// fontFeatures: <FontFeature>[
/// FontFeature.numerators(),
/// ],
/// ),
/// );
/// }
/// ```
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_numr.png)
/// {@end-tool}
///
/// See also:
///
/// * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_ko#numr>
const FontFeature.numerators() : feature = 'numr', value = 1;
// onum
/// Use oldstyle figures. (`onum`)
///
/// Some fonts have variants of the figures (e.g. the digit 9) that,
/// when this feature is enabled, render with descenders under the
/// baseline instead of being entirely above the baseline. If the
/// default digits are lining figures, this allows the selection of
/// digits that fit better with mixed case (uppercase and lowercase)
/// text.
///
/// This overrides [FontFeature.slashedZero] and may conflict with
/// [FontFeature.liningFigures].
///
/// {@tool sample --template=stateless_widget}
///
/// The Piazzolla font supports the `onum` feature. It causes
/// digits to extend below the baseline.
///
/// ```dart dartImports
/// import 'dart:ui';
/// ```
///
/// ```dart
/// Widget build(BuildContext context) {
/// // The Piazzolla font can be downloaded from Google Fonts (https://www.google.com/fonts).
/// return const Text(
/// 'Call 311-555-2368 now!',
/// style: TextStyle(
/// fontFamily: 'Piazzolla',
/// fontFeatures: <FontFeature>[
/// FontFeature.oldstyleFigures(),
/// ],
/// ),
/// );
/// }
/// ```
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_onum.png)
/// {@end-tool}
///
/// See also:
///
/// * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_ko#onum>
/// * <https://en.wikipedia.org/wiki/Text_figures>
const FontFeature.oldstyleFigures() : feature = 'onum', value = 1;
// ordn
/// Use ordinal forms for alphabetic glyphs. (`ordn`)
///
/// Some fonts have variants of the alphabetic glyphs intended for
/// use after numbers when expressing ordinals, as in "1st", "2nd",
/// "3rd". This feature enables those alternative glyphs.
///
/// This may override other features that substitute glyphs.
///
/// {@tool sample --template=stateless_widget}
///
/// The Piazzolla font supports the `ordn` feature. It causes
/// alphabetic glyphs to become smaller and superscripted.
///
/// ```dart dartImports
/// import 'dart:ui';
/// ```
///
/// ```dart
/// Widget build(BuildContext context) {
/// // The Piazzolla font can be downloaded from Google Fonts (https://www.google.com/fonts).
/// return const Text(
/// '1st, 2nd, 3rd, 4th...',
/// style: TextStyle(
/// fontFamily: 'Piazzolla',
/// fontFeatures: <FontFeature>[
/// FontFeature.ordinalForms(),
/// ],
/// ),
/// );
/// }
/// ```
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_ordn.png)
/// {@end-tool}
///
/// See also:
///
/// * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_ko#ordn>
const FontFeature.ordinalForms() : feature = 'ordn', value = 1;
// pnum
/// Use proportional (varying width) figures. (`pnum`)
///
/// For fonts that have both proportional and tabular (monospace) figures,
/// this enables the proportional figures.
///
/// This is mutually exclusive with [FontFeature.tabularFigures].
///
/// The default behavior varies from font to font.
///
/// {@tool sample --template=stateless_widget}
///
/// The Kufam font supports the `pnum` feature. It causes the digits
/// to become proportionally-sized, rather than all being the same
/// width. In this font this is especially noticeable with the digit
/// "1": normally, the 1 has very noticeable serifs in this
/// sans-serif font, but with the proportionally figures enabled,
/// the digit becomes much narrower.
///
/// ```dart dartImports
/// import 'dart:ui';
/// ```
///
/// ```dart
/// Widget build(BuildContext context) {
/// // The Kufam font can be downloaded from Google Fonts (https://www.google.com/fonts).
/// return const Text(
/// 'Call 311-555-2368 now!',
/// style: TextStyle(
/// fontFamily: 'Kufam',
/// fontFeatures: <FontFeature>[
/// FontFeature.proportionalFigures(),
/// ],
/// ),
/// );
/// }
/// ```
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_pnum.png)
/// {@end-tool}
///
/// See also:
///
/// * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_pt#pnum>
const FontFeature.proportionalFigures() : feature = 'pnum', value = 1;
// rand
/// Randomize the alternate forms used in text. (`rand`)
///
/// For example, this can be used with suitably-prepared handwriting fonts to
/// vary the forms used for each character, so that, for instance, the word
/// "cross-section" would be rendered with two different "c"s, two different "o"s,
/// and three different "s"s.
///
/// Contextual alternates ([FontFeature.contextualAlternates])
/// provide a similar effect in some fonts, without using
/// randomness.
///
/// See also:
///
/// * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_pt#rand>
const FontFeature.randomize() : feature = 'rand', value = 1;
// salt
/// Enable stylistic alternates. (`salt`)
///
/// Some fonts have alternative forms that are not tied to a
/// particular purpose (such as being historical forms, or
/// contextually relevant alternatives, or ligatures, etc). This
/// font feature enables these purely stylistic alternatives.
///
/// This may override other features that substitute glyphs.
///
/// {@tool sample --template=stateless_widget}
///
/// The Source Code Pro font supports the `salt` feature. It causes
/// some glyphs to be rendered differently, for example the "a" and
/// "g" glyphs change from their typographically common
/// double-storey forms to simpler single-storey forms, the dollar
/// sign's line changes from discontinuous to continuous (and is
/// angled), and the "0" rendering changes from a center dot to a
/// slash.
///
/// ```dart dartImports
/// import 'dart:ui';
/// ```
///
/// ```dart
/// Widget build(BuildContext context) {
/// // The Source Code Pro font can be downloaded from Google Fonts (https://www.google.com/fonts).
/// return const Text(
/// r'Agile Game - $100 initial bet',
/// style: TextStyle(
/// fontFamily: 'Source Code Pro',
/// fontFeatures: <FontFeature>[
/// FontFeature.stylisticAlternates(),
/// ],
/// ),
/// );
/// }
/// ```
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_salt.png)
/// {@end-tool}
///
/// See also:
///
/// * [FontFeature.contextualAlternates], which is enables alternates specific to certain contexts.
/// * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_pt#salt>
const FontFeature.stylisticAlternates() : feature = 'salt', value = 1;
// sinf
/// Use scientific inferiors. (`sinf`)
///
/// Some fonts have variants of the figures (e.g. the digit 2) that,
/// when this feature is enabled, render in a manner more
/// appropriate for subscripted digits ("inferiors") used in
/// scientific contexts, e.g. the subscripts in chemical formulae.
///
/// This may override other features that substitute glyphs.
///
/// {@tool sample --template=stateless_widget}
///
/// The Piazzolla font supports the `sinf` feature. It causes
/// digits to be smaller and subscripted.
///
/// ```dart dartImports
/// import 'dart:ui';
/// ```
///
/// ```dart
/// Widget build(BuildContext context) {
/// // The Piazzolla font can be downloaded from Google Fonts (https://www.google.com/fonts).
/// return const Text(
/// 'C8H10N4O2',
/// style: TextStyle(
/// fontFamily: 'Piazzolla',
/// fontFeatures: <FontFeature>[
/// FontFeature.scientificInferiors(),
/// ],
/// ),
/// );
/// }
/// ```
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_sinf.png)
/// {@end-tool}
///
/// See also:
///
/// * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_pt#sinf>
const FontFeature.scientificInferiors() : feature = 'sinf', value = 1;
// ssXX
/// Select a stylistic set. (`ss01` through `ss20`)
///
/// Fonts may have up to 20 stylistic sets, numbered 1 through 20,
/// each of which can be independently enabled or disabled.
///
/// For more fine-grained control, in some fonts individual
/// character variants can also be controlled by the
/// [FontFeature.characterVariant] feature (`cvXX`).
///
/// {@tool sample --template=stateless_widget}
///
/// The Source Code Pro font supports the `ssXX` feature for several
/// sets. In the example below, stylistic sets 2 (`ss02`), 3
/// (`ss03`), and 4 (`ss04`) are selected. Stylistic set 2 changes
/// the rendering of the "a" character and the beta character,
/// stylistic set 3 changes the lowercase "g", theta, and delta
/// characters, and stylistic set 4 changes the lowercase "i" and
/// "l" characters.
///
/// This font also supports character variants (see
/// [FontFeature.characterVariant]).
///
/// ```dart dartImports
/// import 'dart:ui';
/// ```
///
/// ```dart
/// Widget build(BuildContext context) {
/// // The Source Code Pro font can be downloaded from Google Fonts (https://www.google.com/fonts).
/// return Text(
/// 'aáâ β gǵĝ θб Iiíî Ll',
/// style: TextStyle(
/// fontFamily: 'Source Code Pro',
/// fontFeatures: <FontFeature>[
/// FontFeature.stylisticSet(2),
/// FontFeature.stylisticSet(3),
/// FontFeature.stylisticSet(4),
/// ],
/// ),
/// );
/// }
/// ```
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_ssXX_1.png)
/// {@end-tool}
///
/// {@tool sample --template=stateless_widget}
///
/// The Piazzolla font supports the `ssXX` feature for for more
/// elaborate stylistic effects. Set 1 turns some Latin characters
/// into Roman numerals, set 2 enables some ASCII characters to be
/// used to create pretty arrows, and so forth.
///
/// _These_ stylistic sets do _not_ correspond to character variants.
///
/// ```dart dartImports
/// import 'dart:ui';
/// ```
///
/// ```dart
/// Widget build(BuildContext context) {
/// // The Piazzolla font can be downloaded from Google Fonts (https://www.google.com/fonts).
/// return Text(
/// '-> MCMXCVII <-', // 1997
/// style: TextStyle(
/// fontFamily: 'Piazzolla',
/// fontFeatures: <FontFeature>[
/// FontFeature.stylisticSet(1),
/// FontFeature.stylisticSet(2),
/// ],
/// ),
/// );
/// }
/// ```
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_ssXX_2.png)
/// {@end-tool}
///
/// See also:
///
/// * [FontFeature.characterVariant], which allows for individual character
/// variants to be selected, as opposed to entire sets.
/// * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_pt#ssxx>
factory FontFeature.stylisticSet(int value) {
assert(value >= 1);
assert(value <= 20);
return FontFeature('ss${value.toString().padLeft(2, "0")}');
}
// subs
/// Enable subscripts. (`subs`)
///
/// This feature causes some fonts to change some glyphs to their subscripted form.
///
/// It typically does not affect all glyphs, and so is not appropriate for generally causing
/// all text to be subscripted.
///
/// This may override other features that substitute glyphs.
///
/// {@tool sample --template=stateless_widget}
///
/// The Piazzolla font supports the `subs` feature. It causes
/// digits to be smaller and subscripted.
///
/// ```dart dartImports
/// import 'dart:ui';
/// ```
///
/// ```dart
/// Widget build(BuildContext context) {
/// // The Piazzolla font can be downloaded from Google Fonts (https://www.google.com/fonts).
/// return const Text(
/// 'Line from x1,y1 to x2,y2',
/// style: TextStyle(
/// fontFamily: 'Piazzolla',
/// fontFeatures: <FontFeature>[
/// FontFeature.subscripts(),
/// ],
/// ),
/// );
/// }
/// ```
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_subs.png)
/// {@end-tool}
///
/// See also:
///
/// * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_pt#subs>
/// * [FontFeature.scientificInferiors], which is similar but intended specifically for
/// subscripts used in scientific contexts.
/// * [FontFeature.superscripts], which is similar but for subscripting.
const FontFeature.subscripts() : feature = 'subs', value = 1;
// sups
/// Enable superscripts. (`sups`)
///
/// This feature causes some fonts to change some glyphs to their
/// superscripted form. This may be more than just changing their
/// position. For example, digits might change to lining figures
/// (see [FontFeature.liningFigures]) in addition to being raised
/// and shrunk.
///
/// It typically does not affect all glyphs, and so is not
/// appropriate for generally causing all text to be superscripted.
///
/// This may override other features that substitute glyphs.
///
/// {@tool sample --template=stateless_widget}
///
/// The Sorts Mill Goudy font supports the `sups` feature. It causes
/// digits to be smaller, superscripted, and changes them to lining
/// figures (so they are all the same height).
///
/// ```dart dartImports
/// import 'dart:ui';
/// ```
///
/// ```dart
/// Widget build(BuildContext context) {
/// // The Sorts Mill Goudy font can be downloaded from Google Fonts (https://www.google.com/fonts).
/// return const Text(
/// 'The isotope 238U decays to 206Pb',
/// style: TextStyle(
/// fontFamily: 'Sorts Mill Goudy',
/// fontFeatures: <FontFeature>[
/// FontFeature.superscripts(),
/// ],
/// ),
/// );
/// }
/// ```
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_sups.png)
/// {@end-tool}
///
/// See also:
///
/// * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_pt#sups>
/// * [FontFeature.subscripts], which is similar but for subscripting.
const FontFeature.superscripts() : feature = 'sups', value = 1;
// swsh
/// Enable swash glyphs. (`swsh`)
///
/// Some fonts have beautiful flourishes on some characters. These
/// come in many forms, such as exaggerated serifs, long tails, long
/// entry strokes, or other forms of decorative extensions to the
/// base character.
///
/// This feature enables the rendering of these flourishes. Some
/// fonts have many swashes per character; the argument, if
/// specified, selects which swash to use (0 disables them
/// altogether).
///
/// Some fonts have an absurd number of alternative swashes. For
/// example, Adobe's Poetica famously has 63 different ampersand
/// forms available through this feature!
///
/// {@tool sample --template=stateless_widget}
///
/// The BioRhyme Expanded font supports the `swsh` feature specifically
/// for the capital "Q" and "R" glyphs and the ampersand.
///
/// ```dart dartImports
/// import 'dart:ui';
/// ```
///
/// ```dart
/// Widget build(BuildContext context) {
/// // The BioRhyme Expanded font can be downloaded from Google Fonts (https://www.google.com/fonts).
/// return const Text(
/// 'Queer & Romantic',
/// style: TextStyle(
/// fontFamily: 'BioRhyme Expanded',
/// fontFeatures: <FontFeature>[
/// FontFeature.swash(),
/// ],
/// ),
/// );
/// }
/// ```
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_swsh.png)
/// {@end-tool}
///
/// See also:
///
/// * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_pt#swsh>
/// * <https://en.wikipedia.org/wiki/Swash_(typography)>
const FontFeature.swash([this.value = 1]) : feature = 'swsh', assert(value >= 0);
// tnum
/// Use tabular (monospace) figures. (`tnum`)
///
/// For fonts that have both proportional (varying width) and tabular figures,
/// this enables the tabular figures. Tabular figures are monospaced (all the
/// same width), so that they align in tables of figures.
///
/// This is mutually exclusive with [FontFeature.proportionalFigures].
///
/// The default behavior varies from font to font.
///
/// {@tool sample --template=stateless_widget}
///
/// The Piazzolla font supports the `tnum` feature. It causes the
/// digits to become uniformally-sized, rather than having variable
/// widths. In this font this is especially noticeable with the
/// digit "1"; with tabular figures enabled, the "1" digit is more
/// widely spaced.
///
/// ```dart dartImports
/// import 'dart:ui';
/// ```
///
/// ```dart
/// Widget build(BuildContext context) {
/// // The Piazzolla font can be downloaded from Google Fonts (https://www.google.com/fonts).
/// return const Text(
/// 'Call 311-555-2368 now!',
/// style: TextStyle(
/// fontFamily: 'Piazzolla',
/// fontFeatures: <FontFeature>[
/// FontFeature.tabularFigures(),
/// ],
/// ),
/// );
/// }
/// ```
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_tnum.png)
/// {@end-tool}
///
/// See also:
///
/// * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_pt#tnum>
const FontFeature.tabularFigures() : feature = 'tnum', value = 1;
// zero
/// Use the slashed zero. (`zero`)
///
/// Some fonts contain both a circular zero and a zero with a slash. This
/// enables the use of the latter form.
///
/// This is overridden by [FontFeature.oldstyleFigures].
///
/// {@tool sample --template=stateless_widget}
///
/// The Source Code Pro font supports the `zero` feature. It causes the
/// zero digit to be drawn with a slash rather than the default rendering,
/// which in this case has a dot through the zero rather than a slash.
///
/// ```dart dartImports
/// import 'dart:ui';
/// ```
///
/// ```dart
/// Widget build(BuildContext context) {
/// // The Source Code Pro font can be downloaded from Google Fonts (https://www.google.com/fonts).
/// return const Text(
/// 'One million is: 1,000,000.00',
/// style: TextStyle(
/// fontFamily: 'Source Code Pro',
/// fontFeatures: <FontFeature>[
/// FontFeature.slashedZero(),
/// ],
/// ),
/// );
/// }
/// ```
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_zero.png)
/// {@end-tool}
///
/// See also:
///
/// * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_uz#zero>
const FontFeature.slashedZero() : feature = 'zero', value = 1;
// ------------------------------------------------------------------------
// End of feature tags list.
/// The tag that identifies the effect of this feature. Must consist of 4
/// ASCII characters (typically lowercase letters).
///
/// These features are defined in a registry maintained by Microsoft:
/// <https://docs.microsoft.com/en-us/typography/opentype/spec/featuretags>
final String feature;
/// The value assigned to this feature.
///
/// Must be a positive integer. Many features are Boolean values that accept
/// values of either 0 (feature is disabled) or 1 (feature is enabled). Other
/// features have a bound range of values (which may be documented in these
/// API docs for features that have dedicated constructors, and are generally
/// documented in the official registry). In some cases the precise supported
/// range depends on the font.
///
/// See also:
///
/// * <https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist>
final int value;
static const int _kEncodedSize = 8;
void _encode(ByteData byteData) {
assert(feature.codeUnits.every((int c) => c >= 0x20 && c <= 0x7F));
for (int i = 0; i < 4; i++) {
byteData.setUint8(i, feature.codeUnitAt(i));
}
byteData.setInt32(4, value, _kFakeHostEndian);
}
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType)
return false;
return other is FontFeature
&& other.feature == feature
&& other.value == value;
}
@override
int get hashCode => hashValues(feature, value);
@override
String toString() => "FontFeature('$feature', $value)";
}
/// Whether and how to align text horizontally.
// The order of this enum must match the order of the values in RenderStyleConstants.h's ETextAlign.
enum TextAlign {
/// Align the text on the left edge of the container.
left,
/// Align the text on the right edge of the container.
right,
/// Align the text in the center of the container.
center,
/// Stretch lines of text that end with a soft line break to fill the width of
/// the container.
///
/// Lines that end with hard line breaks are aligned towards the [start] edge.
justify,
/// Align the text on the leading edge of the container.
///
/// For left-to-right text ([TextDirection.ltr]), this is the left edge.
///
/// For right-to-left text ([TextDirection.rtl]), this is the right edge.
start,
/// Align the text on the trailing edge of the container.
///
/// For left-to-right text ([TextDirection.ltr]), this is the right edge.
///
/// For right-to-left text ([TextDirection.rtl]), this is the left edge.
end,
}
/// A horizontal line used for aligning text.
enum TextBaseline {
/// The horizontal line used to align the bottom of glyphs for alphabetic characters.
alphabetic,
/// The horizontal line used to align ideographic characters.
ideographic,
}
/// A linear decoration to draw near the text.
class TextDecoration {
const TextDecoration._(this._mask);
/// Creates a decoration that paints the union of all the given decorations.
factory TextDecoration.combine(List<TextDecoration> decorations) {
int mask = 0;
for (final TextDecoration decoration in decorations)
mask |= decoration._mask;
return TextDecoration._(mask);
}
final int _mask;
/// Whether this decoration will paint at least as much decoration as the given decoration.
bool contains(TextDecoration other) {
return (_mask | other._mask) == _mask;
}
/// Do not draw a decoration
static const TextDecoration none = TextDecoration._(0x0);
/// Draw a line underneath each line of text
static const TextDecoration underline = TextDecoration._(0x1);
/// Draw a line above each line of text
static const TextDecoration overline = TextDecoration._(0x2);
/// Draw a line through each line of text
static const TextDecoration lineThrough = TextDecoration._(0x4);
@override
bool operator ==(Object other) {
return other is TextDecoration
&& other._mask == _mask;
}
@override
int get hashCode => _mask.hashCode;
@override
String toString() {
if (_mask == 0)
return 'TextDecoration.none';
final List<String> values = <String>[];
if (_mask & underline._mask != 0)
values.add('underline');
if (_mask & overline._mask != 0)
values.add('overline');
if (_mask & lineThrough._mask != 0)
values.add('lineThrough');
if (values.length == 1)
return 'TextDecoration.${values[0]}';
return 'TextDecoration.combine([${values.join(", ")}])';
}
}
/// The style in which to draw a text decoration
enum TextDecorationStyle {
/// Draw a solid line
solid,
/// Draw two lines
double,
/// Draw a dotted line
dotted,
/// Draw a dashed line
dashed,
/// Draw a sinusoidal line
wavy
}
/// {@macro dart.ui.textLeadingDistribution}
enum TextLeadingDistribution {
/// Distributes the [leading](https://en.wikipedia.org/wiki/Leading)
/// of the text proportionally above and below the text, to the font's
/// ascent/discent ratio.
///
/// {@template dart.ui.leading}
/// The leading of a text run is defined as
/// `TextStyle.height * TextStyle.fontSize - TextStyle.fontSize`. When
/// [TextStyle.height] is not set, the text run uses the leading specified by
/// the font instead.
/// {@endtemplate}
proportional,
/// Distributes the ["leading"](https://en.wikipedia.org/wiki/Leading)
/// of the text evenly above and below the text (i.e. evenly above the
/// font's ascender and below the descender).
///
/// {@macro dart.ui.leading}
///
/// The leading can become negative when [TextStyle.height] is smaller than
/// 1.0.
///
/// This is the default strategy used by CSS, known as
/// ["half-leading"](https://www.w3.org/TR/css-inline-3/#half-leading).
even,
}
/// {@template dart.ui.textHeightBehavior}
/// Defines how to apply [TextStyle.height] over and under text.
///
/// [applyHeightToFirstAscent] and [applyHeightToLastDescent] represent whether
/// the [TextStyle.height] modifier will be applied to the corresponding metric.
/// By default both properties are true, and [TextStyle.height] is applied as
/// normal. When set to false, the font's default ascent will be used.
///
/// [leadingDistribution] determines how the [leading] is distributed over and
/// under text. This property applies before [applyHeightToFirstAscent] and
/// [applyHeightToLastDescent].
///
/// {@endtemplate}
class TextHeightBehavior {
/// Creates a new TextHeightBehavior object.
///
/// * applyHeightToFirstAscent: When true, the [TextStyle.height] modifier
/// will be applied to the ascent of the first line. When false, the font's
/// default ascent will be used.
/// * applyHeightToLastDescent: When true, the [TextStyle.height] modifier
/// will be applied to the descent of the last line. When false, the font's
/// default descent will be used.
/// * leadingDistribution: How the [leading] is distributed over and under
/// text.
///
/// All properties default to true (height modifications applied as normal).
const TextHeightBehavior({
this.applyHeightToFirstAscent = true,
this.applyHeightToLastDescent = true,
this.leadingDistribution = TextLeadingDistribution.proportional,
});
/// Creates a new TextHeightBehavior object from an encoded form.
///
/// See [_encode] for the creation of the encoded form.
const TextHeightBehavior._fromEncoded(int encoded, this.leadingDistribution)
: applyHeightToFirstAscent = (encoded & 0x1) == 0,
applyHeightToLastDescent = (encoded & 0x2) == 0;
/// Whether to apply the [TextStyle.height] modifier to the ascent of the first
/// line in the paragraph.
///
/// When true, the [TextStyle.height] modifier will be applied to to the ascent
/// of the first line. When false, the font's default ascent will be used and
/// the [TextStyle.height] will have no effect on the ascent of the first line.
///
/// This property only has effect if a non-null [TextStyle.height] is specified.
///
/// Defaults to true (height modifications applied as normal).
final bool applyHeightToFirstAscent;
/// Whether to apply the [TextStyle.height] modifier to the descent of the last
/// line in the paragraph.
///
/// When true, the [TextStyle.height] modifier will be applied to to the descent
/// of the last line. When false, the font's default descent will be used and
/// the [TextStyle.height] will have no effect on the descent of the last line.
///
/// This property only has effect if a non-null [TextStyle.height] is specified.
///
/// Defaults to true (height modifications applied as normal).
final bool applyHeightToLastDescent;
/// {@template dart.ui.textLeadingDistribution}
/// How the ["leading"](https://en.wikipedia.org/wiki/Leading) is distributed
/// over and under the text.
///
/// Does not affect layout when [TextStyle.height] is not specified. The
/// leading can become negative, for example, when [TextLeadingDistribution.even]
/// is used with a [TextStyle.height] much smaller than 1.0.
/// {@endtemplate}
///
/// Defaults to [TextLeadingDistribution.proportional],
final TextLeadingDistribution leadingDistribution;
/// Returns an encoded int representation of this object (excluding
/// [leadingDistribution]).
int _encode() {
return (applyHeightToFirstAscent ? 0 : 1 << 0)
| (applyHeightToLastDescent ? 0 : 1 << 1);
}
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType)
return false;
return other is TextHeightBehavior
&& other.applyHeightToFirstAscent == applyHeightToFirstAscent
&& other.applyHeightToLastDescent == applyHeightToLastDescent
&& other.leadingDistribution == leadingDistribution;
}
@override
int get hashCode {
return hashValues(
applyHeightToFirstAscent,
applyHeightToLastDescent,
leadingDistribution.index,
);
}
@override
String toString() {
return 'TextHeightBehavior('
'applyHeightToFirstAscent: $applyHeightToFirstAscent, '
'applyHeightToLastDescent: $applyHeightToLastDescent, '
'leadingDistribution: $leadingDistribution'
')';
}
}
/// Determines if lists [a] and [b] are deep equivalent.
///
/// Returns true if the lists are both null, or if they are both non-null, have
/// the same length, and contain the same elements in the same order. Returns
/// false otherwise.
bool _listEquals<T>(List<T>? a, List<T>? b) {
if (a == null)
return b == null;
if (b == null || a.length != b.length)
return false;
for (int index = 0; index < a.length; index += 1) {
if (a[index] != b[index])
return false;
}
return true;
}
// This encoding must match the C++ version of ParagraphBuilder::pushStyle.
//
// The encoded array buffer has 8 elements.
//
// - Element 0: A bit field where the ith bit indicates whether the ith element
// has a non-null value. Bits 8 to 12 indicate whether |fontFamily|,
// |fontSize|, |letterSpacing|, |wordSpacing|, and |height| are non-null,
// respectively. Bit 0 indicates the [TextLeadingDistribution] of the text
// style.
//
// - Element 1: The |color| in ARGB with 8 bits per channel.
//
// - Element 2: A bit field indicating which text decorations are present in
// the |textDecoration| list. The ith bit is set if there's a TextDecoration
// with enum index i in the list.
//
// - Element 3: The |decorationColor| in ARGB with 8 bits per channel.
//
// - Element 4: The bit field of the |decorationStyle|.
//
// - Element 5: The index of the |fontWeight|.
//
// - Element 6: The enum index of the |fontStyle|.
//
// - Element 7: The enum index of the |textBaseline|.
//
Int32List _encodeTextStyle(
Color? color,
TextDecoration? decoration,
Color? decorationColor,
TextDecorationStyle? decorationStyle,
double? decorationThickness,
FontWeight? fontWeight,
FontStyle? fontStyle,
TextBaseline? textBaseline,
String? fontFamily,
List<String>? fontFamilyFallback,
double? fontSize,
double? letterSpacing,
double? wordSpacing,
double? height,
Locale? locale,
Paint? background,
Paint? foreground,
List<Shadow>? shadows,
List<FontFeature>? fontFeatures,
) {
final Int32List result = Int32List(9);
// The 0th bit of result[0] is reserved for leadingDistribution.
if (color != null) {
result[0] |= 1 << 1;
result[1] = color.value;
}
if (decoration != null) {
result[0] |= 1 << 2;
result[2] = decoration._mask;
}
if (decorationColor != null) {
result[0] |= 1 << 3;
result[3] = decorationColor.value;
}
if (decorationStyle != null) {
result[0] |= 1 << 4;
result[4] = decorationStyle.index;
}
if (fontWeight != null) {
result[0] |= 1 << 5;
result[5] = fontWeight.index;
}
if (fontStyle != null) {
result[0] |= 1 << 6;
result[6] = fontStyle.index;
}
if (textBaseline != null) {
result[0] |= 1 << 7;
result[7] = textBaseline.index;
}
if (decorationThickness != null) {
result[0] |= 1 << 8;
}
if (fontFamily != null || (fontFamilyFallback != null && fontFamilyFallback.isNotEmpty)) {
result[0] |= 1 << 9;
// Passed separately to native.
}
if (fontSize != null) {
result[0] |= 1 << 10;
// Passed separately to native.
}
if (letterSpacing != null) {
result[0] |= 1 << 11;
// Passed separately to native.
}
if (wordSpacing != null) {
result[0] |= 1 << 12;
// Passed separately to native.
}
if (height != null) {
result[0] |= 1 << 13;
// Passed separately to native.
}
if (locale != null) {
result[0] |= 1 << 14;
// Passed separately to native.
}
if (background != null) {
result[0] |= 1 << 15;
// Passed separately to native.
}
if (foreground != null) {
result[0] |= 1 << 16;
// Passed separately to native.
}
if (shadows != null) {
result[0] |= 1 << 17;
// Passed separately to native.
}
if (fontFeatures != null) {
result[0] |= 1 << 18;
// Passed separately to native.
}
return result;
}
/// An opaque object that determines the size, position, and rendering of text.
///
/// See also:
///
/// * [TextStyle](https://api.flutter.dev/flutter/painting/TextStyle-class.html), the class in the [painting] library.
///
class TextStyle {
/// Creates a new TextStyle object.
///
/// * `color`: The color to use when painting the text. If this is specified, `foreground` must be null.
/// * `decoration`: The decorations to paint near the text (e.g., an underline).
/// * `decorationColor`: The color in which to paint the text decorations.
/// * `decorationStyle`: The style in which to paint the text decorations (e.g., dashed).
/// * `decorationThickness`: The thickness of the decoration as a muliplier on the thickness specified by the font.
/// * `fontWeight`: The typeface thickness to use when painting the text (e.g., bold).
/// * `fontStyle`: The typeface variant to use when drawing the letters (e.g., italics).
/// * `fontFamily`: The name of the font to use when painting the text (e.g., Roboto). If a `fontFamilyFallback` is
/// provided and `fontFamily` is not, then the first font family in `fontFamilyFallback` will take the position of
/// the preferred font family. When a higher priority font cannot be found or does not contain a glyph, a lower
/// priority font will be used.
/// * `fontFamilyFallback`: An ordered list of the names of the fonts to fallback on when a glyph cannot
/// be found in a higher priority font. When the `fontFamily` is null, the first font family in this list
/// is used as the preferred font. Internally, the 'fontFamily` is concatenated to the front of this list.
/// When no font family is provided through 'fontFamilyFallback' (null or empty) or `fontFamily`, then the
/// platform default font will be used.
/// * `fontSize`: The size of glyphs (in logical pixels) to use when painting the text.
/// * `letterSpacing`: The amount of space (in logical pixels) to add between each letter.
/// * `wordSpacing`: The amount of space (in logical pixels) to add at each sequence of white-space (i.e. between each word).
/// * `textBaseline`: 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.
/// * `height`: The height of this text span, as a multiplier of the font size. Omitting `height` will allow the line height
/// to take the height as defined by the font, which may not be exactly the height of the fontSize.
/// * `leadingDistribution`: When `height` is specified, how the extra vertical space should be distributed over and under the text. Defaults
/// to the paragraph's [TextHeightBehavior] if left unspecified.
/// * `locale`: The locale used to select region-specific glyphs.
/// * `background`: The paint drawn as a background for the text.
/// * `foreground`: The paint used to draw the text. If this is specified, `color` must be null.
/// * `fontFeatures`: The font features that should be applied to the text.
TextStyle({
Color? color,
TextDecoration? decoration,
Color? decorationColor,
TextDecorationStyle? decorationStyle,
double? decorationThickness,
FontWeight? fontWeight,
FontStyle? fontStyle,
TextBaseline? textBaseline,
String? fontFamily,
List<String>? fontFamilyFallback,
double? fontSize,
double? letterSpacing,
double? wordSpacing,
double? height,
TextLeadingDistribution? leadingDistribution,
Locale? locale,
Paint? background,
Paint? foreground,
List<Shadow>? shadows,
List<FontFeature>? fontFeatures,
}) : assert(color == null || foreground == null,
'Cannot provide both a color and a foreground\n'
'The color argument is just a shorthand for "foreground: Paint()..color = color".'
),
_encoded = _encodeTextStyle(
color,
decoration,
decorationColor,
decorationStyle,
decorationThickness,
fontWeight,
fontStyle,
textBaseline,
fontFamily,
fontFamilyFallback,
fontSize,
letterSpacing,
wordSpacing,
height,
locale,
background,
foreground,
shadows,
fontFeatures,
),
_leadingDistribution = leadingDistribution,
_fontFamily = fontFamily ?? '',
_fontFamilyFallback = fontFamilyFallback,
_fontSize = fontSize,
_letterSpacing = letterSpacing,
_wordSpacing = wordSpacing,
_height = height,
_decorationThickness = decorationThickness,
_locale = locale,
_background = background,
_foreground = foreground,
_shadows = shadows,
_fontFeatures = fontFeatures;
final Int32List _encoded;
final String _fontFamily;
final List<String>? _fontFamilyFallback;
final double? _fontSize;
final double? _letterSpacing;
final double? _wordSpacing;
final double? _height;
final double? _decorationThickness;
final Locale? _locale;
final Paint? _background;
final Paint? _foreground;
final List<Shadow>? _shadows;
final List<FontFeature>? _fontFeatures;
final TextLeadingDistribution? _leadingDistribution;
@override
bool operator ==(Object other) {
if (identical(this, other))
return true;
return other is TextStyle
&& other._leadingDistribution == _leadingDistribution
&& other._fontFamily == _fontFamily
&& other._fontSize == _fontSize
&& other._letterSpacing == _letterSpacing
&& other._wordSpacing == _wordSpacing
&& other._height == _height
&& other._decorationThickness == _decorationThickness
&& other._locale == _locale
&& other._background == _background
&& other._foreground == _foreground
&& _listEquals<int>(other._encoded, _encoded)
&& _listEquals<Shadow>(other._shadows, _shadows)
&& _listEquals<String>(other._fontFamilyFallback, _fontFamilyFallback)
&& _listEquals<FontFeature>(other._fontFeatures, _fontFeatures);
}
@override
int get hashCode => hashValues(hashList(_encoded), _leadingDistribution, _fontFamily, _fontFamilyFallback, _fontSize, _letterSpacing, _wordSpacing, _height, _locale, _background, _foreground, hashList(_shadows), _decorationThickness, hashList(_fontFeatures));
@override
String toString() {
return 'TextStyle('
'color: ${ _encoded[0] & 0x00002 == 0x00002 ? Color(_encoded[1]) : "unspecified"}, '
'decoration: ${ _encoded[0] & 0x00004 == 0x00004 ? TextDecoration._(_encoded[2]) : "unspecified"}, '
'decorationColor: ${ _encoded[0] & 0x00008 == 0x00008 ? Color(_encoded[3]) : "unspecified"}, '
'decorationStyle: ${ _encoded[0] & 0x00010 == 0x00010 ? TextDecorationStyle.values[_encoded[4]] : "unspecified"}, '
// The decorationThickness is not in encoded order in order to keep it near the other decoration properties.
'decorationThickness: ${_encoded[0] & 0x00100 == 0x00100 ? _decorationThickness : "unspecified"}, '
'fontWeight: ${ _encoded[0] & 0x00020 == 0x00020 ? FontWeight.values[_encoded[5]] : "unspecified"}, '
'fontStyle: ${ _encoded[0] & 0x00040 == 0x00040 ? FontStyle.values[_encoded[6]] : "unspecified"}, '
'textBaseline: ${ _encoded[0] & 0x00080 == 0x00080 ? TextBaseline.values[_encoded[7]] : "unspecified"}, '
'fontFamily: ${ _encoded[0] & 0x00200 == 0x00200
&& _fontFamily != '' ? _fontFamily : "unspecified"}, '
'fontFamilyFallback: ${ _encoded[0] & 0x00200 == 0x00200
&& _fontFamilyFallback != null
&& _fontFamilyFallback!.isNotEmpty ? _fontFamilyFallback : "unspecified"}, '
'fontSize: ${ _encoded[0] & 0x00400 == 0x00400 ? _fontSize : "unspecified"}, '
'letterSpacing: ${ _encoded[0] & 0x00800 == 0x00800 ? "${_letterSpacing}x" : "unspecified"}, '
'wordSpacing: ${ _encoded[0] & 0x01000 == 0x01000 ? "${_wordSpacing}x" : "unspecified"}, '
'height: ${ _encoded[0] & 0x02000 == 0x02000 ? "${_height}x" : "unspecified"}, '
'leadingDistribution: ${_leadingDistribution ?? "unspecified"}, '
'locale: ${ _encoded[0] & 0x04000 == 0x04000 ? _locale : "unspecified"}, '
'background: ${ _encoded[0] & 0x08000 == 0x08000 ? _background : "unspecified"}, '
'foreground: ${ _encoded[0] & 0x10000 == 0x10000 ? _foreground : "unspecified"}, '
'shadows: ${ _encoded[0] & 0x20000 == 0x20000 ? _shadows : "unspecified"}, '
'fontFeatures: ${ _encoded[0] & 0x40000 == 0x40000 ? _fontFeatures : "unspecified"}'
')';
}
}
// This encoding must match the C++ version ParagraphBuilder::build.
//
// The encoded array buffer has 6 elements.
//
// - Element 0: A bit mask indicating which fields are non-null.
// Bit 0 is unused. Bits 1-n are set if the corresponding index in the
// encoded array is non-null. The remaining bits represent fields that
// are passed separately from the array.
//
// - Element 1: The enum index of the |textAlign|.
//
// - Element 2: The enum index of the |textDirection|.
//
// - Element 3: The index of the |fontWeight|.
//
// - Element 4: The enum index of the |fontStyle|.
//
// - Element 5: The value of |maxLines|.
//
// - Element 6: The encoded value of |textHeightBehavior|, except its leading
// distribution.
Int32List _encodeParagraphStyle(
TextAlign? textAlign,
TextDirection? textDirection,
int? maxLines,
String? fontFamily,
double? fontSize,
double? height,
TextHeightBehavior? textHeightBehavior,
FontWeight? fontWeight,
FontStyle? fontStyle,
StrutStyle? strutStyle,
String? ellipsis,
Locale? locale,
) {
final Int32List result = Int32List(7); // also update paragraph_builder.cc
if (textAlign != null) {
result[0] |= 1 << 1;
result[1] = textAlign.index;
}
if (textDirection != null) {
result[0] |= 1 << 2;
result[2] = textDirection.index;
}
if (fontWeight != null) {
result[0] |= 1 << 3;
result[3] = fontWeight.index;
}
if (fontStyle != null) {
result[0] |= 1 << 4;
result[4] = fontStyle.index;
}
if (maxLines != null) {
result[0] |= 1 << 5;
result[5] = maxLines;
}
if (textHeightBehavior != null) {
result[0] |= 1 << 6;
result[6] = textHeightBehavior._encode();
}
if (fontFamily != null) {
result[0] |= 1 << 7;
// Passed separately to native.
}
if (fontSize != null) {
result[0] |= 1 << 8;
// Passed separately to native.
}
if (height != null) {
result[0] |= 1 << 9;
// Passed separately to native.
}
if (strutStyle != null) {
result[0] |= 1 << 10;
// Passed separately to native.
}
if (ellipsis != null) {
result[0] |= 1 << 11;
// Passed separately to native.
}
if (locale != null) {
result[0] |= 1 << 12;
// Passed separately to native.
}
return result;
}
/// An opaque object that determines the configuration used by
/// [ParagraphBuilder] to position lines within a [Paragraph] of text.
class ParagraphStyle {
/// Creates a new ParagraphStyle object.
///
/// * `textAlign`: The alignment of the text within the lines of the
/// paragraph. If the last line is ellipsized (see `ellipsis` below), the
/// alignment is applied to that line after it has been truncated but before
/// the ellipsis has been added.
// See: https://github.com/flutter/flutter/issues/9819
///
/// * `textDirection`: The directionality of the text, left-to-right (e.g.
/// Norwegian) or right-to-left (e.g. Hebrew). This controls the overall
/// directionality of the paragraph, as well as the meaning of
/// [TextAlign.start] and [TextAlign.end] in the `textAlign` field.
///
/// * `maxLines`: The maximum number of lines painted. Lines beyond this
/// number are silently dropped. For example, if `maxLines` is 1, then only
/// one line is rendered. If `maxLines` is null, but `ellipsis` is not null,
/// then lines after the first one that overflows the width constraints are
/// dropped. The width constraints are those set in the
/// [ParagraphConstraints] object passed to the [Paragraph.layout] method.
///
/// * `fontFamily`: The name of the font family to apply when painting the text,
/// in the absence of a `textStyle` being attached to the span.
///
/// * `fontSize`: The fallback size of glyphs (in logical pixels) to
/// use when painting the text. This is used when there is no [TextStyle].
///
/// * `height`: The fallback height of the spans as a multiplier of the font
/// size. The fallback height is used when no height is provided through
/// [TextStyle.height]. Omitting `height` here and in [TextStyle] will allow
/// the line height to take the height as defined by the font, which may not
/// be exactly the height of the `fontSize`.
///
/// * `textHeightBehavior`: Specifies how the `height` multiplier is
/// applied to ascent of the first line and the descent of the last line.
///
/// * `leadingDistribution`: Specifies how the extra vertical space added by
/// the `height` multiplier should be distributed over and under the text.
/// Defaults to [TextLeadingDistribution.proportional].
///
/// * `fontWeight`: The typeface thickness to use when painting the text
/// (e.g., bold).
///
/// * `fontStyle`: The typeface variant to use when drawing the letters (e.g.,
/// italics).
///
/// * `strutStyle`: The properties of the strut. Strut defines a set of minimum
/// vertical line height related metrics and can be used to obtain more
/// advanced line spacing behavior.
///
/// * `ellipsis`: String used to ellipsize overflowing text. If `maxLines` is
/// not null, then the `ellipsis`, if any, is applied to the last rendered
/// line, if that line overflows the width constraints. If `maxLines` is
/// null, then the `ellipsis` is applied to the first line that overflows
/// the width constraints, and subsequent lines are dropped. The width
/// constraints are those set in the [ParagraphConstraints] object passed to
/// the [Paragraph.layout] method. The empty string and the null value are
/// considered equivalent and turn off this behavior.
///
/// * `locale`: The locale used to select region-specific glyphs.
ParagraphStyle({
TextAlign? textAlign,
TextDirection? textDirection,
int? maxLines,
String? fontFamily,
double? fontSize,
double? height,
TextHeightBehavior? textHeightBehavior,
FontWeight? fontWeight,
FontStyle? fontStyle,
StrutStyle? strutStyle,
String? ellipsis,
Locale? locale,
}) : _encoded = _encodeParagraphStyle(
textAlign,
textDirection,
maxLines,
fontFamily,
fontSize,
height,
textHeightBehavior,
fontWeight,
fontStyle,
strutStyle,
ellipsis,
locale,
),
_fontFamily = fontFamily,
_fontSize = fontSize,
_height = height,
_strutStyle = strutStyle,
_ellipsis = ellipsis,
_locale = locale,
_leadingDistribution = textHeightBehavior?.leadingDistribution ?? TextLeadingDistribution.proportional;
final Int32List _encoded;
final String? _fontFamily;
final double? _fontSize;
final double? _height;
final StrutStyle? _strutStyle;
final String? _ellipsis;
final Locale? _locale;
final TextLeadingDistribution _leadingDistribution;
@override
bool operator ==(Object other) {
if (identical(this, other))
return true;
if (other.runtimeType != runtimeType)
return false;
return other is ParagraphStyle
&& other._fontFamily == _fontFamily
&& other._fontSize == _fontSize
&& other._height == _height
&& other._strutStyle == _strutStyle
&& other._ellipsis == _ellipsis
&& other._locale == _locale
&& other._leadingDistribution == _leadingDistribution
&& _listEquals<int>(other._encoded, _encoded);
}
@override
int get hashCode => hashValues(hashList(_encoded), _fontFamily, _fontSize, _height, _ellipsis, _locale, _leadingDistribution);
@override
String toString() {
return 'ParagraphStyle('
'textAlign: ${ _encoded[0] & 0x002 == 0x002 ? TextAlign.values[_encoded[1]] : "unspecified"}, '
'textDirection: ${ _encoded[0] & 0x004 == 0x004 ? TextDirection.values[_encoded[2]] : "unspecified"}, '
'fontWeight: ${ _encoded[0] & 0x008 == 0x008 ? FontWeight.values[_encoded[3]] : "unspecified"}, '
'fontStyle: ${ _encoded[0] & 0x010 == 0x010 ? FontStyle.values[_encoded[4]] : "unspecified"}, '
'maxLines: ${ _encoded[0] & 0x020 == 0x020 ? _encoded[5] : "unspecified"}, '
'textHeightBehavior: ${
_encoded[0] & 0x040 == 0x040 ?
TextHeightBehavior._fromEncoded(_encoded[6], _leadingDistribution).toString() : "unspecified"}, '
'fontFamily: ${ _encoded[0] & 0x080 == 0x080 ? _fontFamily : "unspecified"}, '
'fontSize: ${ _encoded[0] & 0x100 == 0x100 ? _fontSize : "unspecified"}, '
'height: ${ _encoded[0] & 0x200 == 0x200 ? "${_height}x" : "unspecified"}, '
'ellipsis: ${ _encoded[0] & 0x400 == 0x400 ? "\"$_ellipsis\"" : "unspecified"}, '
'locale: ${ _encoded[0] & 0x800 == 0x800 ? _locale : "unspecified"}'
')';
}
}
// Serialize strut properties into ByteData. This encoding errs towards
// compactness. The first 8 bits is a bitmask that records which properties
// are null. The rest of the values are encoded in the same order encountered
// in the bitmask. The final returned value truncates any unused bytes
// at the end. For ease of decoding, all 8 bit ints are stored before any 32 bit
// ints.
//
// We serialize this more thoroughly than ParagraphStyle because it is
// much more likely that the strut is empty/null and we wish to add
// minimal overhead for non-strut cases.
ByteData _encodeStrut(
String? fontFamily,
List<String>? fontFamilyFallback,
double? fontSize,
double? height,
TextLeadingDistribution? leadingDistribution,
double? leading,
FontWeight? fontWeight,
FontStyle? fontStyle,
bool? forceStrutHeight) {
if (fontFamily == null &&
fontSize == null &&
height == null &&
leadingDistribution == null &&
leading == null &&
fontWeight == null &&
fontStyle == null &&
forceStrutHeight == null)
return ByteData(0);
final ByteData data = ByteData(16); // Max size is 16 bytes
int bitmask = 0; // 8 bit mask
int byteCount = 1;
if (fontWeight != null) {
bitmask |= 1 << 0;
data.setInt8(byteCount, fontWeight.index);
byteCount += 1;
}
if (fontStyle != null) {
bitmask |= 1 << 1;
data.setInt8(byteCount, fontStyle.index);
byteCount += 1;
}
if (fontFamily != null || (fontFamilyFallback != null && fontFamilyFallback.isNotEmpty)){
bitmask |= 1 << 2;
// passed separately to native
}
// The 3rd bit (0-indexed) is reserved for leadingDistribution.
if (fontSize != null) {
bitmask |= 1 << 4;
data.setFloat32(byteCount, fontSize, _kFakeHostEndian);
byteCount += 4;
}
if (height != null) {
bitmask |= 1 << 5;
data.setFloat32(byteCount, height, _kFakeHostEndian);
byteCount += 4;
}
if (leading != null) {
bitmask |= 1 << 6;
data.setFloat32(byteCount, leading, _kFakeHostEndian);
byteCount += 4;
}
if (forceStrutHeight ?? false) {
bitmask |= 1 << 7;
}
data.setInt8(0, bitmask);
assert(byteCount <= 16);
assert(bitmask >> 8 == 0, 'strut bitmask overflow: $bitmask');
return ByteData.view(data.buffer, 0, byteCount);
}
/// See also:
///
/// * [StrutStyle](https://api.flutter.dev/flutter/painting/StrutStyle-class.html), the class in the [painting] library.
///
class StrutStyle {
/// Creates a new StrutStyle object.
///
/// * `fontFamily`: The name of the font to use when painting the text (e.g.,
/// Roboto).
///
/// * `fontFamilyFallback`: An ordered list of font family names that will be
/// searched for when the font in `fontFamily` cannot be found.
///
/// * `fontSize`: The size of glyphs (in logical pixels) to use when painting
/// the text.
///
/// * `height`: The minimum height of the line boxes, as a multiplier of the
/// font size. The lines of the paragraph will be at least
/// `(height + leading) * fontSize` tall when `fontSize` is not null. Omitting
/// `height` will allow the minimum line height to take the height as defined
/// by the font, which may not be exactly the height of the `fontSize`. When
/// `fontSize` is null, there is no minimum line height. Tall glyphs due to
/// baseline alignment or large [TextStyle.fontSize] may cause the actual line
/// height after layout to be taller than specified here. The `fontSize` must
/// be provided for this property to take effect.
///
/// * `leading`: The minimum amount of leading between lines as a multiple of
/// the font size. `fontSize` must be provided for this property to take
/// effect. The leading added by this property is distributed evenly over
/// and under the text, regardless of `leadingDistribution`.
///
/// * `leadingDistribution`: how the extra vertical space added by the
/// `height` multiplier should be distributed over and under the text,
/// independent of `leading` (which is always distributed evenly over and
/// under text). Defaults to the paragraph's [TextHeightBehavior]'s leading
/// distribution.
///
/// * `fontWeight`: The typeface thickness to use when painting the text
/// (e.g., bold).
///
/// * `fontStyle`: The typeface variant to use when drawing the letters (e.g.,
/// italics).
///
/// * `forceStrutHeight`: When true, the paragraph will force all lines to be exactly
/// `(height + leading) * fontSize` tall from baseline to baseline.
/// [TextStyle] is no longer able to influence the line height, and any tall
/// glyphs may overlap with lines above. If a `fontFamily` is specified, the
/// total ascent of the first line will be the min of the `Ascent + half-leading`
/// of the `fontFamily` and `(height + leading) * fontSize`. Otherwise, it
/// will be determined by the Ascent + half-leading of the first text.
StrutStyle({
String? fontFamily,
List<String>? fontFamilyFallback,
double? fontSize,
double? height,
TextLeadingDistribution? leadingDistribution,
double? leading,
FontWeight? fontWeight,
FontStyle? fontStyle,
bool? forceStrutHeight,
}) : _encoded = _encodeStrut(
fontFamily,
fontFamilyFallback,
fontSize,
height,
leadingDistribution,
leading,
fontWeight,
fontStyle,
forceStrutHeight,
),
_leadingDistribution = leadingDistribution,
_fontFamily = fontFamily,
_fontFamilyFallback = fontFamilyFallback;
final ByteData _encoded; // Most of the data for strut is encoded.
final String? _fontFamily;
final List<String>? _fontFamilyFallback;
final TextLeadingDistribution? _leadingDistribution;
bool get _enabled => _encoded.lengthInBytes > 0;
@override
bool operator ==(Object other) {
if (identical(this, other))
return true;
if (other.runtimeType != runtimeType)
return false;
return other is StrutStyle
&& other._fontFamily == _fontFamily
&& other._leadingDistribution == _leadingDistribution
&& _listEquals<String>(other._fontFamilyFallback, _fontFamilyFallback)
&& _listEquals<int>(other._encoded.buffer.asInt8List(), _encoded.buffer.asInt8List());
}
@override
int get hashCode => hashValues(hashList(_encoded.buffer.asInt8List()), _fontFamily, _leadingDistribution);
}
/// A direction in which text flows.
///
/// Some languages are written from the left to the right (for example, English,
/// Tamil, or Chinese), while others are written from the right to the left (for
/// example Aramaic, Hebrew, or Urdu). Some are also written in a mixture, for
/// example Arabic is mostly written right-to-left, with numerals written
/// left-to-right.
///
/// The text direction must be provided to APIs that render text or lay out
/// boxes horizontally, so that they can determine which direction to start in:
/// either right-to-left, [TextDirection.rtl]; or left-to-right,
/// [TextDirection.ltr].
///
/// ## Design discussion
///
/// Flutter is designed to address the needs of applications written in any of
/// the world's currently-used languages, whether they use a right-to-left or
/// left-to-right writing direction. Flutter does not support other writing
/// modes, such as vertical text or boustrophedon text, as these are rarely used
/// in computer programs.
///
/// It is common when developing user interface frameworks to pick a default
/// text direction — typically left-to-right, the direction most familiar to the
/// engineers working on the framework — because this simplifies the development
/// of applications on the platform. Unfortunately, this frequently results in
/// the platform having unexpected left-to-right biases or assumptions, as
/// engineers will typically miss places where they need to support
/// right-to-left text. This then results in bugs that only manifest in
/// right-to-left environments.
///
/// In an effort to minimize the extent to which Flutter experiences this
/// category of issues, the lowest levels of the Flutter framework do not have a
/// default text reading direction. Any time a reading direction is necessary,
/// for example when text is to be displayed, or when a
/// writing-direction-dependent value is to be interpreted, the reading
/// direction must be explicitly specified. Where possible, such as in `switch`
/// statements, the right-to-left case is listed first, to avoid the impression
/// that it is an afterthought.
///
/// At the higher levels (specifically starting at the widgets library), an
/// ambient [Directionality] is introduced, which provides a default. Thus, for
/// instance, a [Text] widget in the scope of a [MaterialApp] widget does not
/// need to be given an explicit writing direction. The [Directionality.of]
/// static method can be used to obtain the ambient text direction for a
/// particular [BuildContext].
///
/// ### Known left-to-right biases in Flutter
///
/// Despite the design intent described above, certain left-to-right biases have
/// nonetheless crept into Flutter's design. These include:
///
/// * The [Canvas] origin is at the top left, and the x-axis increases in a
/// left-to-right direction.
///
/// * The default localization in the widgets and material libraries is
/// American English, which is left-to-right.
///
/// ### Visual properties vs directional properties
///
/// Many classes in the Flutter framework are offered in two versions, a
/// visually-oriented variant, and a text-direction-dependent variant. For
/// example, [EdgeInsets] is described in terms of top, left, right, and bottom,
/// while [EdgeInsetsDirectional] is described in terms of top, start, end, and
/// bottom, where start and end correspond to right and left in right-to-left
/// text and left and right in left-to-right text.
///
/// There are distinct use cases for each of these variants.
///
/// Text-direction-dependent variants are useful when developing user interfaces
/// that should "flip" with the text direction. For example, a paragraph of text
/// in English will typically be left-aligned and a quote will be indented from
/// the left, while in Arabic it will be right-aligned and indented from the
/// right. Both of these cases are described by the direction-dependent
/// [TextAlign.start] and [EdgeInsetsDirectional.start].
///
/// In contrast, the visual variants are useful when the text direction is known
/// and not affected by the reading direction. For example, an application
/// giving driving directions might show a "turn left" arrow on the left and a
/// "turn right" arrow on the right — and would do so whether the application
/// was localized to French (left-to-right) or Hebrew (right-to-left).
///
/// In practice, it is also expected that many developers will only be
/// targeting one language, and in that case it may be simpler to think in
/// visual terms.
// The order of this enum must match the order of the values in TextDirection.h's TextDirection.
enum TextDirection {
/// The text flows from right to left (e.g. Arabic, Hebrew).
rtl,
/// The text flows from left to right (e.g., English, French).
ltr,
}
/// A rectangle enclosing a run of text.
///
/// This is similar to [Rect] but includes an inherent [TextDirection].
@pragma('vm:entry-point')
class TextBox {
/// Creates an object that describes a box containing text.
const TextBox.fromLTRBD(
this.left,
this.top,
this.right,
this.bottom,
this.direction,
);
/// The left edge of the text box, irrespective of direction.
///
/// To get the leading edge (which may depend on the [direction]), consider [start].
final double left;
/// The top edge of the text box.
final double top;
/// The right edge of the text box, irrespective of direction.
///
/// To get the trailing edge (which may depend on the [direction]), consider [end].
final double right;
/// The bottom edge of the text box.
final double bottom;
/// The direction in which text inside this box flows.
final TextDirection direction;
/// Returns a rect of the same size as this box.
Rect toRect() => Rect.fromLTRB(left, top, right, bottom);
/// The [left] edge of the box for left-to-right text; the [right] edge of the box for right-to-left text.
///
/// See also:
///
/// * [direction], which specifies the text direction.
double get start {
return (direction == TextDirection.ltr) ? left : right;
}
/// The [right] edge of the box for left-to-right text; the [left] edge of the box for right-to-left text.
///
/// See also:
///
/// * [direction], which specifies the text direction.
double get end {
return (direction == TextDirection.ltr) ? right : left;
}
@override
bool operator ==(Object other) {
if (identical(this, other))
return true;
if (other.runtimeType != runtimeType)
return false;
return other is TextBox
&& other.left == left
&& other.top == top
&& other.right == right
&& other.bottom == bottom
&& other.direction == direction;
}
@override
int get hashCode => hashValues(left, top, right, bottom, direction);
@override
String toString() => 'TextBox.fromLTRBD(${left.toStringAsFixed(1)}, ${top.toStringAsFixed(1)}, ${right.toStringAsFixed(1)}, ${bottom.toStringAsFixed(1)}, $direction)';
}
/// A way to disambiguate a [TextPosition] when its offset could match two
/// different locations in the rendered string.
///
/// For example, at an offset where the rendered text wraps, there are two
/// visual positions that the offset could represent: one prior to the line
/// break (at the end of the first line) and one after the line break (at the
/// start of the second line). A text affinity disambiguates between these two
/// cases.
///
/// This affects only line breaks caused by wrapping, not explicit newline
/// characters. For newline characters, the position is fully specified by the
/// offset alone, and there is no ambiguity.
///
/// [TextAffinity] also affects bidirectional text at the interface between LTR
/// and RTL text. Consider the following string, where the lowercase letters
/// will be displayed as LTR and the uppercase letters RTL: "helloHELLO". When
/// rendered, the string would appear visually as "helloOLLEH". An offset of 5
/// would be ambiguous without a corresponding [TextAffinity]. Looking at the
/// string in code, the offset represents the position just after the "o" and
/// just before the "H". When rendered, this offset could be either in the
/// middle of the string to the right of the "o" or at the end of the string to
/// the right of the "H".
enum TextAffinity {
/// The position has affinity for the upstream side of the text position, i.e.
/// in the direction of the beginning of the string.
///
/// In the example of an offset at the place where text is wrapping, upstream
/// indicates the end of the first line.
///
/// In the bidirectional text example "helloHELLO", an offset of 5 with
/// [TextAffinity] upstream would appear in the middle of the rendered text,
/// just to the right of the "o". See the definition of [TextAffinity] for the
/// full example.
upstream,
/// The position has affinity for the downstream side of the text position,
/// i.e. in the direction of the end of the string.
///
/// In the example of an offset at the place where text is wrapping,
/// downstream indicates the beginning of the second line.
///
/// In the bidirectional text example "helloHELLO", an offset of 5 with
/// [TextAffinity] downstream would appear at the end of the rendered text,
/// just to the right of the "H". See the definition of [TextAffinity] for the
/// full example.
downstream,
}
/// A position in a string of text.
///
/// A TextPosition can be used to locate a position in a string in code (using
/// the [offset] property), and it can also be used to locate the same position
/// visually in a rendered string of text (using [offset] and, when needed to
/// resolve ambiguity, [affinity]).
///
/// The location of an offset in a rendered string is ambiguous in two cases.
/// One happens when rendered text is forced to wrap. In this case, the offset
/// where the wrap occurs could visually appear either at the end of the first
/// line or the beginning of the second line. The second way is with
/// bidirectional text. An offset at the interface between two different text
/// directions could have one of two locations in the rendered text.
///
/// See the documentation for [TextAffinity] for more information on how
/// TextAffinity disambiguates situations like these.
class TextPosition {
/// Creates an object representing a particular position in a string.
///
/// The arguments must not be null (so the [offset] argument is required).
const TextPosition({
required this.offset,
this.affinity = TextAffinity.downstream,
}) : assert(offset != null),
assert(affinity != null);
/// The index of the character that immediately follows the position in the
/// string representation of the text.
///
/// For example, given the string `'Hello'`, offset 0 represents the cursor
/// being before the `H`, while offset 5 represents the cursor being just
/// after the `o`.
final int offset;
/// Disambiguates cases where the position in the string given by [offset]
/// could represent two different visual positions in the rendered text. For
/// example, this can happen when text is forced to wrap, or when one string
/// of text is rendered with multiple text directions.
///
/// See the documentation for [TextAffinity] for more information on how
/// TextAffinity disambiguates situations like these.
final TextAffinity affinity;
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType)
return false;
return other is TextPosition
&& other.offset == offset
&& other.affinity == affinity;
}
@override
int get hashCode => hashValues(offset, affinity);
@override
String toString() {
return 'TextPosition(offset: $offset, affinity: $affinity)';
}
}
/// A range of characters in a string of text.
class TextRange {
/// Creates a text range.
///
/// The [start] and [end] arguments must not be null. Both the [start] and
/// [end] must either be greater than or equal to zero or both exactly -1.
///
/// The text included in the range includes the character at [start], but not
/// the one at [end].
///
/// Instead of creating an empty text range, consider using the [empty]
/// constant.
const TextRange({
required this.start,
required this.end,
}) : assert(start != null && start >= -1),
assert(end != null && end >= -1);
/// A text range that starts and ends at offset.
///
/// The [offset] argument must be non-null and greater than or equal to -1.
const TextRange.collapsed(int offset)
: assert(offset != null && offset >= -1),
start = offset,
end = offset;
/// A text range that contains nothing and is not in the text.
static const TextRange empty = TextRange(start: -1, end: -1);
/// The index of the first character in the range.
///
/// If [start] and [end] are both -1, the text range is empty.
final int start;
/// The next index after the characters in this range.
///
/// If [start] and [end] are both -1, the text range is empty.
final int end;
/// Whether this range represents a valid position in the text.
bool get isValid => start >= 0 && end >= 0;
/// Whether this range is empty (but still potentially placed inside the text).
bool get isCollapsed => start == end;
/// Whether the start of this range precedes the end.
bool get isNormalized => end >= start;
/// The text before this range.
String textBefore(String text) {
assert(isNormalized);
return text.substring(0, start);
}
/// The text after this range.
String textAfter(String text) {
assert(isNormalized);
return text.substring(end);
}
/// The text inside this range.
String textInside(String text) {
assert(isNormalized);
return text.substring(start, end);
}
@override
bool operator ==(Object other) {
if (identical(this, other))
return true;
return other is TextRange
&& other.start == start
&& other.end == end;
}
@override
int get hashCode => hashValues(
start.hashCode,
end.hashCode,
);
@override
String toString() => 'TextRange(start: $start, end: $end)';
}
/// Layout constraints for [Paragraph] objects.
///
/// Instances of this class are typically used with [Paragraph.layout].
///
/// The only constraint that can be specified is the [width]. See the discussion
/// at [width] for more details.
class ParagraphConstraints {
/// Creates constraints for laying out a paragraph.
///
/// The [width] argument must not be null.
const ParagraphConstraints({
required this.width,
}) : assert(width != null);
/// The width the paragraph should use whey computing the positions of glyphs.
///
/// If possible, the paragraph will select a soft line break prior to reaching
/// this width. If no soft line break is available, the paragraph will select
/// a hard line break prior to reaching this width. If that would force a line
/// break without any characters having been placed (i.e. if the next
/// character to be laid out does not fit within the given width constraint)
/// then the next character is allowed to overflow the width constraint and a
/// forced line break is placed after it (even if an explicit line break
/// follows).
///
/// The width influences how ellipses are applied. See the discussion at
/// [new ParagraphStyle] for more details.
///
/// This width is also used to position glyphs according to the [TextAlign]
/// alignment described in the [ParagraphStyle] used when building the
/// [Paragraph] with a [ParagraphBuilder].
final double width;
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType)
return false;
return other is ParagraphConstraints
&& other.width == width;
}
@override
int get hashCode => width.hashCode;
@override
String toString() => 'ParagraphConstraints(width: $width)';
}
/// Defines various ways to vertically bound the boxes returned by
/// [Paragraph.getBoxesForRange].
///
/// See [BoxWidthStyle] for a similar property to control width.
enum BoxHeightStyle {
/// Provide tight bounding boxes that fit heights per run. This style may result
/// in uneven bounding boxes that do not nicely connect with adjacent boxes.
tight,
/// The height of the boxes will be the maximum height of all runs in the
/// line. All boxes in the same line will be the same height.
///
/// This does not guarantee that the boxes will cover the entire vertical height of the line
/// when there is additional line spacing.
///
/// See [BoxHeightStyle.includeLineSpacingTop], [BoxHeightStyle.includeLineSpacingMiddle],
/// and [BoxHeightStyle.includeLineSpacingBottom] for styles that will cover
/// the entire line.
max,
/// Extends the top and bottom edge of the bounds to fully cover any line
/// spacing.
///
/// The top and bottom of each box will cover half of the
/// space above and half of the space below the line.
///
/// {@template dart.ui.boxHeightStyle.includeLineSpacing}
/// The top edge of each line should be the same as the bottom edge
/// of the line above. There should be no gaps in vertical coverage given any
/// amount of line spacing. Line spacing is not included above the first line
/// and below the last line due to no additional space present there.
/// {@endtemplate}
includeLineSpacingMiddle,
/// Extends the top edge of the bounds to fully cover any line spacing.
///
/// The line spacing will be added to the top of the box.
///
/// {@macro dart.ui.boxHeightStyle.includeLineSpacing}
includeLineSpacingTop,
/// Extends the bottom edge of the bounds to fully cover any line spacing.
///
/// The line spacing will be added to the bottom of the box.
///
/// {@macro dart.ui.boxHeightStyle.includeLineSpacing}
includeLineSpacingBottom,
/// Calculate box heights based on the metrics of this paragraph's [StrutStyle].
///
/// Boxes based on the strut will have consistent heights throughout the
/// entire paragraph. The top edge of each line will align with the bottom
/// edge of the previous line. It is possible for glyphs to extend outside
/// these boxes.
strut,
}
/// Defines various ways to horizontally bound the boxes returned by
/// [Paragraph.getBoxesForRange].
///
/// See [BoxHeightStyle] for a similar property to control height.
enum BoxWidthStyle {
/// Provide tight bounding boxes that fit widths to the runs of each line
/// independently.
tight,
/// Adds up to two additional boxes as needed at the beginning and/or end
/// of each line so that the widths of the boxes in line are the same width
/// as the widest line in the paragraph.
///
/// The additional boxes on each line are only added when the relevant box
/// at the relevant edge of that line does not span the maximum width of
/// the paragraph.
max,
}
/// Where to vertically align the placeholder relative to the surrounding text.
///
/// Used by [ParagraphBuilder.addPlaceholder].
enum PlaceholderAlignment {
/// Match the baseline of the placeholder with the baseline.
///
/// The [TextBaseline] to use must be specified and non-null when using this
/// alignment mode.
baseline,
/// Align the bottom edge of the placeholder with the baseline such that the
/// placeholder sits on top of the baseline.
///
/// The [TextBaseline] to use must be specified and non-null when using this
/// alignment mode.
aboveBaseline,
/// Align the top edge of the placeholder with the baseline specified
/// such that the placeholder hangs below the baseline.
///
/// The [TextBaseline] to use must be specified and non-null when using this
/// alignment mode.
belowBaseline,
/// Align the top edge of the placeholder with the top edge of the text.
///
/// When the placeholder is very tall, the extra space will hang from
/// the top and extend through the bottom of the line.
top,
/// Align the bottom edge of the placeholder with the bottom edge of the text.
///
/// When the placeholder is very tall, the extra space will rise from the
/// bottom and extend through the top of the line.
bottom,
/// Align the middle of the placeholder with the middle of the text.
///
/// When the placeholder is very tall, the extra space will grow equally
/// from the top and bottom of the line.
middle,
}
/// [LineMetrics] stores the measurements and statistics of a single line in the
/// paragraph.
///
/// The measurements here are for the line as a whole, and represent the maximum
/// extent of the line instead of per-run or per-glyph metrics. For more detailed
/// metrics, see [TextBox] and [Paragraph.getBoxesForRange].
///
/// [LineMetrics] should be obtained directly from the [Paragraph.computeLineMetrics]
/// method.
class LineMetrics {
/// Creates a [LineMetrics] object with only the specified values.
LineMetrics({
required this.hardBreak,
required this.ascent,
required this.descent,
required this.unscaledAscent,
required this.height,
required this.width,
required this.left,
required this.baseline,
required this.lineNumber,
});
/// True if this line ends with an explicit line break (e.g. '\n') or is the end
/// of the paragraph. False otherwise.
final bool hardBreak;
/// The rise from the [baseline] as calculated from the font and style for this line.
///
/// This is the final computed ascent and can be impacted by the strut, height, scaling,
/// as well as outlying runs that are very tall.
///
/// The [ascent] is provided as a positive value, even though it is typically defined
/// in fonts as negative. This is to ensure the signage of operations with these
/// metrics directly reflects the intended signage of the value. For example,
/// the y coordinate of the top edge of the line is `baseline - ascent`.
final double ascent;
/// The drop from the [baseline] as calculated from the font and style for this line.
///
/// This is the final computed ascent and can be impacted by the strut, height, scaling,
/// as well as outlying runs that are very tall.
///
/// The y coordinate of the bottom edge of the line is `baseline + descent`.
final double descent;
/// The rise from the [baseline] as calculated from the font and style for this line
/// ignoring the [TextStyle.height].
///
/// The [unscaledAscent] is provided as a positive value, even though it is typically
/// defined in fonts as negative. This is to ensure the signage of operations with
/// these metrics directly reflects the intended signage of the value.
final double unscaledAscent;
/// Total height of the line from the top edge to the bottom edge.
///
/// This is equivalent to `round(ascent + descent)`. This value is provided
/// separately due to rounding causing sub-pixel differences from the unrounded
/// values.
final double height;
/// Width of the line from the left edge of the leftmost glyph to the right
/// edge of the rightmost glyph.
///
/// This is not the same as the width of the pargraph.
///
/// See also:
///
/// * [Paragraph.width], the max width passed in during layout.
/// * [Paragraph.longestLine], the width of the longest line in the paragraph.
final double width;
/// The x coordinate of left edge of the line.
///
/// The right edge can be obtained with `left + width`.
final double left;
/// The y coordinate of the baseline for this line from the top of the paragraph.
///
/// The bottom edge of the paragraph up to and including this line may be obtained
/// through `baseline + descent`.
final double baseline;
/// The number of this line in the overall paragraph, with the first line being
/// index zero.
///
/// For example, the first line is line 0, second line is line 1.
final int lineNumber;
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) {
return false;
}
return other is LineMetrics
&& other.hardBreak == hardBreak
&& other.ascent == ascent
&& other.descent == descent
&& other.unscaledAscent == unscaledAscent
&& other.height == height
&& other.width == width
&& other.left == left
&& other.baseline == baseline
&& other.lineNumber == lineNumber;
}
@override
int get hashCode => hashValues(hardBreak, ascent, descent, unscaledAscent, height, width, left, baseline, lineNumber);
@override
String toString() {
return 'LineMetrics(hardBreak: $hardBreak, '
'ascent: $ascent, '
'descent: $descent, '
'unscaledAscent: $unscaledAscent, '
'height: $height, '
'width: $width, '
'left: $left, '
'baseline: $baseline, '
'lineNumber: $lineNumber)';
}
}
/// A paragraph of text.
///
/// A paragraph retains the size and position of each glyph in the text and can
/// be efficiently resized and painted.
///
/// To create a [Paragraph] object, use a [ParagraphBuilder].
///
/// Paragraphs can be displayed on a [Canvas] using the [Canvas.drawParagraph]
/// method.
@pragma('vm:entry-point')
class Paragraph extends NativeFieldWrapperClass2 {
/// This class is created by the engine, and should not be instantiated
/// or extended directly.
///
/// To create a [Paragraph] object, use a [ParagraphBuilder].
@pragma('vm:entry-point')
Paragraph._();
/// The amount of horizontal space this paragraph occupies.
///
/// Valid only after [layout] has been called.
double get width native 'Paragraph_width';
/// The amount of vertical space this paragraph occupies.
///
/// Valid only after [layout] has been called.
double get height native 'Paragraph_height';
/// The distance from the left edge of the leftmost glyph to the right edge of
/// the rightmost glyph in the paragraph.
///
/// Valid only after [layout] has been called.
double get longestLine native 'Paragraph_longestLine';
/// The minimum width that this paragraph could be without failing to paint
/// its contents within itself.
///
/// Valid only after [layout] has been called.
double get minIntrinsicWidth native 'Paragraph_minIntrinsicWidth';
/// Returns the smallest width beyond which increasing the width never
/// decreases the height.
///
/// Valid only after [layout] has been called.
double get maxIntrinsicWidth native 'Paragraph_maxIntrinsicWidth';
/// The distance from the top of the paragraph to the alphabetic
/// baseline of the first line, in logical pixels.
double get alphabeticBaseline native 'Paragraph_alphabeticBaseline';
/// The distance from the top of the paragraph to the ideographic
/// baseline of the first line, in logical pixels.
double get ideographicBaseline native 'Paragraph_ideographicBaseline';
/// True if there is more vertical content, but the text was truncated, either
/// because we reached `maxLines` lines of text or because the `maxLines` was
/// null, `ellipsis` was not null, and one of the lines exceeded the width
/// constraint.
///
/// See the discussion of the `maxLines` and `ellipsis` arguments at
/// [new ParagraphStyle].
bool get didExceedMaxLines native 'Paragraph_didExceedMaxLines';
/// Computes the size and position of each glyph in the paragraph.
///
/// The [ParagraphConstraints] control how wide the text is allowed to be.
void layout(ParagraphConstraints constraints) => _layout(constraints.width);
void _layout(double width) native 'Paragraph_layout';
List<TextBox> _decodeTextBoxes(Float32List encoded) {
final int count = encoded.length ~/ 5;
final List<TextBox> boxes = <TextBox>[];
int position = 0;
for (int index = 0; index < count; index += 1) {
boxes.add(TextBox.fromLTRBD(
encoded[position++],
encoded[position++],
encoded[position++],
encoded[position++],
TextDirection.values[encoded[position++].toInt()],
));
}
return boxes;
}
/// Returns a list of text boxes that enclose the given text range.
///
/// The [boxHeightStyle] and [boxWidthStyle] parameters allow customization
/// of how the boxes are bound vertically and horizontally. Both style
/// parameters default to the tight option, which will provide close-fitting
/// boxes and will not account for any line spacing.
///
/// Coordinates of the TextBox are relative to the upper-left corner of the paragraph,
/// where positive y values indicate down.
///
/// The [boxHeightStyle] and [boxWidthStyle] parameters must not be null.
///
/// See [BoxHeightStyle] and [BoxWidthStyle] for full descriptions of each option.
List<TextBox> getBoxesForRange(int start, int end, {BoxHeightStyle boxHeightStyle = BoxHeightStyle.tight, BoxWidthStyle boxWidthStyle = BoxWidthStyle.tight}) {
assert(boxHeightStyle != null);
assert(boxWidthStyle != null);
return _decodeTextBoxes(_getBoxesForRange(start, end, boxHeightStyle.index, boxWidthStyle.index));
}
// See paragraph.cc for the layout of this return value.
Float32List _getBoxesForRange(int start, int end, int boxHeightStyle, int boxWidthStyle) native 'Paragraph_getRectsForRange';
/// Returns a list of text boxes that enclose all placeholders in the paragraph.
///
/// The order of the boxes are in the same order as passed in through
/// [ParagraphBuilder.addPlaceholder].
///
/// Coordinates of the [TextBox] are relative to the upper-left corner of the paragraph,
/// where positive y values indicate down.
List<TextBox> getBoxesForPlaceholders() {
return _decodeTextBoxes(_getBoxesForPlaceholders());
}
Float32List _getBoxesForPlaceholders() native 'Paragraph_getRectsForPlaceholders';
/// Returns the text position closest to the given offset.
TextPosition getPositionForOffset(Offset offset) {
final List<int> encoded = _getPositionForOffset(offset.dx, offset.dy);
return TextPosition(offset: encoded[0], affinity: TextAffinity.values[encoded[1]]);
}
List<int> _getPositionForOffset(double dx, double dy) native 'Paragraph_getPositionForOffset';
/// Returns the [TextRange] of the word at the given [TextPosition].
///
/// Characters not part of a word, such as spaces, symbols, and punctuation,
/// have word breaks on both sides. In such cases, this method will return
/// [offset, offset+1]. Word boundaries are defined more precisely in Unicode
/// Standard Annex #29 http://www.unicode.org/reports/tr29/#Word_Boundaries
TextRange getWordBoundary(TextPosition position) {
final List<int> boundary = _getWordBoundary(position.offset);
return TextRange(start: boundary[0], end: boundary[1]);
}
List<int> _getWordBoundary(int offset) native 'Paragraph_getWordBoundary';
/// Returns the [TextRange] of the line at the given [TextPosition].
///
/// The newline (if any) is returned as part of the range.
///
/// Not valid until after layout.
///
/// This can potentially be expensive, since it needs to compute the line
/// metrics, so use it sparingly.
TextRange getLineBoundary(TextPosition position) {
final List<int> boundary = _getLineBoundary(position.offset);
return TextRange(start: boundary[0], end: boundary[1]);
}
List<int> _getLineBoundary(int offset) native 'Paragraph_getLineBoundary';
// Redirecting the paint function in this way solves some dependency problems
// in the C++ code. If we straighten out the C++ dependencies, we can remove
// this indirection.
void _paint(Canvas canvas, double x, double y) native 'Paragraph_paint';
/// Returns the full list of [LineMetrics] that describe in detail the various
/// metrics of each laid out line.
///
/// Not valid until after layout.
///
/// This can potentially return a large amount of data, so it is not recommended
/// to repeatedly call this. Instead, cache the results.
List<LineMetrics> computeLineMetrics() {
final Float64List encoded = _computeLineMetrics();
final int count = encoded.length ~/ 9;
int position = 0;
final List<LineMetrics> metrics = <LineMetrics>[
for (int index = 0; index < count; index += 1)
LineMetrics(
hardBreak: encoded[position++] != 0,
ascent: encoded[position++],
descent: encoded[position++],
unscaledAscent: encoded[position++],
height: encoded[position++],
width: encoded[position++],
left: encoded[position++],
baseline: encoded[position++],
lineNumber: encoded[position++].toInt(),
)
];
return metrics;
}
Float64List _computeLineMetrics() native 'Paragraph_computeLineMetrics';
}
/// Builds a [Paragraph] containing text with the given styling information.
///
/// To set the paragraph's alignment, truncation, and ellipsizing behavior, pass
/// an appropriately-configured [ParagraphStyle] object to the
/// [new ParagraphBuilder] constructor.
///
/// Then, call combinations of [pushStyle], [addText], and [pop] to add styled
/// text to the object.
///
/// Finally, call [build] to obtain the constructed [Paragraph] object. After
/// this point, the builder is no longer usable.
///
/// After constructing a [Paragraph], call [Paragraph.layout] on it and then
/// paint it with [Canvas.drawParagraph].
class ParagraphBuilder extends NativeFieldWrapperClass2 {
/// Creates a new [ParagraphBuilder] object, which is used to create a
/// [Paragraph].
@pragma('vm:entry-point')
ParagraphBuilder(ParagraphStyle style)
: _defaultLeadingDistribution = style._leadingDistribution {
List<String>? strutFontFamilies;
final StrutStyle? strutStyle = style._strutStyle;
final ByteData? encodedStrutStyle;
if (strutStyle != null && strutStyle._enabled) {
final String? fontFamily = strutStyle._fontFamily;
strutFontFamilies = <String>[
if (fontFamily != null) fontFamily,
...?strutStyle._fontFamilyFallback,
];
assert(TextLeadingDistribution.values.length <= 2);
final TextLeadingDistribution leadingDistribution = strutStyle._leadingDistribution
?? style._leadingDistribution;
encodedStrutStyle = strutStyle._encoded;
int bitmask = encodedStrutStyle.getInt8(0);
bitmask |= (leadingDistribution.index) << 3;
encodedStrutStyle.setInt8(0, bitmask);
} else {
encodedStrutStyle = null;
}
_constructor(
style._encoded,
encodedStrutStyle,
style._fontFamily,
strutFontFamilies,
style._fontSize,
style._height,
style._ellipsis,
_encodeLocale(style._locale)
);
}
void _constructor(
Int32List encoded,
ByteData? strutData,
String? fontFamily,
List<dynamic>? strutFontFamily,
double? fontSize,
double? height,
String? ellipsis,
String locale
) native 'ParagraphBuilder_constructor';
/// The number of placeholders currently in the paragraph.
int get placeholderCount => _placeholderCount;
int _placeholderCount = 0;
/// The scales of the placeholders in the paragraph.
List<double> get placeholderScales => _placeholderScales;
List<double> _placeholderScales = <double>[];
final TextLeadingDistribution _defaultLeadingDistribution;
/// Applies the given style to the added text until [pop] is called.
///
/// See [pop] for details.
void pushStyle(TextStyle style) {
final List<String> fullFontFamilies = <String>[];
fullFontFamilies.add(style._fontFamily);
if (style._fontFamilyFallback != null)
fullFontFamilies.addAll(style._fontFamilyFallback!);
final Int32List encoded = style._encoded;
final TextLeadingDistribution finalLeadingDistribution = style._leadingDistribution ?? _defaultLeadingDistribution;
// ensure the enum can be represented using 1 bit.
assert(TextLeadingDistribution.values.length <= 2);
// Use the leading distribution from the paragraph's style if it's not
// explicitly set in `style`.
encoded[0] |= finalLeadingDistribution.index << 0;
ByteData? encodedFontFeatures;
final List<FontFeature>? fontFeatures = style._fontFeatures;
if (fontFeatures != null) {
encodedFontFeatures = ByteData(fontFeatures.length * FontFeature._kEncodedSize);
int byteOffset = 0;
for (final FontFeature feature in fontFeatures) {
feature._encode(ByteData.view(encodedFontFeatures.buffer, byteOffset, FontFeature._kEncodedSize));
byteOffset += FontFeature._kEncodedSize;
}
}
_pushStyle(
encoded,
fullFontFamilies,
style._fontSize,
style._letterSpacing,
style._wordSpacing,
style._height,
style._decorationThickness,
_encodeLocale(style._locale),
style._background?._objects,
style._background?._data,
style._foreground?._objects,
style._foreground?._data,
Shadow._encodeShadows(style._shadows),
encodedFontFeatures,
);
}
void _pushStyle(
Int32List encoded,
List<dynamic> fontFamilies,
double? fontSize,
double? letterSpacing,
double? wordSpacing,
double? height,
double? decorationThickness,
String locale,
List<dynamic>? backgroundObjects,
ByteData? backgroundData,
List<dynamic>? foregroundObjects,
ByteData? foregroundData,
ByteData shadowsData,
ByteData? fontFeaturesData,
) native 'ParagraphBuilder_pushStyle';
static String _encodeLocale(Locale? locale) => locale?.toString() ?? '';
/// Ends the effect of the most recent call to [pushStyle].
///
/// Internally, the paragraph builder maintains a stack of text styles. Text
/// added to the paragraph is affected by all the styles in the stack. Calling
/// [pop] removes the topmost style in the stack, leaving the remaining styles
/// in effect.
void pop() native 'ParagraphBuilder_pop';
/// Adds the given text to the paragraph.
///
/// The text will be styled according to the current stack of text styles.
void addText(String text) {
final String? error = _addText(text);
if (error != null)
throw ArgumentError(error);
}
String? _addText(String text) native 'ParagraphBuilder_addText';
/// Adds an inline placeholder space to the paragraph.
///
/// The paragraph will contain a rectangular space with no text of the dimensions
/// specified.
///
/// The `width` and `height` parameters specify the size of the placeholder rectangle.
///
/// The `alignment` parameter specifies how the placeholder rectangle will be vertically
/// aligned with the surrounding text. When [PlaceholderAlignment.baseline],
/// [PlaceholderAlignment.aboveBaseline], and [PlaceholderAlignment.belowBaseline]
/// alignment modes are used, the baseline needs to be set with the `baseline`.
/// When using [PlaceholderAlignment.baseline], `baselineOffset` indicates the distance
/// of the baseline down from the top of of the rectangle. The default `baselineOffset`
/// is the `height`.
///
/// Examples:
///
/// * For a 30x50 placeholder with the bottom edge aligned with the bottom of the text, use:
/// `addPlaceholder(30, 50, PlaceholderAlignment.bottom);`
/// * For a 30x50 placeholder that is vertically centered around the text, use:
/// `addPlaceholder(30, 50, PlaceholderAlignment.middle);`.
/// * For a 30x50 placeholder that sits completely on top of the alphabetic baseline, use:
/// `addPlaceholder(30, 50, PlaceholderAlignment.aboveBaseline, baseline: TextBaseline.alphabetic)`.
/// * For a 30x50 placeholder with 40 pixels above and 10 pixels below the alphabetic baseline, use:
/// `addPlaceholder(30, 50, PlaceholderAlignment.baseline, baseline: TextBaseline.alphabetic, baselineOffset: 40)`.
///
/// Lines are permitted to break around each placeholder.
///
/// Decorations will be drawn based on the font defined in the most recently
/// pushed [TextStyle]. The decorations are drawn as if unicode text were present
/// in the placeholder space, and will draw the same regardless of the height and
/// alignment of the placeholder. To hide or manually adjust decorations to fit,
/// a text style with the desired decoration behavior should be pushed before
/// adding a placeholder.
///
/// Any decorations drawn through a placeholder will exist on the same canvas/layer
/// as the text. This means any content drawn on top of the space reserved by
/// the placeholder will be drawn over the decoration, possibly obscuring the
/// decoration.
///
/// Placeholders are represented by a unicode 0xFFFC "object replacement character"
/// in the text buffer. For each placeholder, one object replacement character is
/// added on to the text buffer.
///
/// The `scale` parameter will scale the `width` and `height` by the specified amount,
/// and keep track of the scale. The scales of placeholders added can be accessed
/// through [placeholderScales]. This is primarily used for accessibility scaling.
void addPlaceholder(double width, double height, PlaceholderAlignment alignment, {
double scale = 1.0,
double? baselineOffset,
TextBaseline? baseline,
}) {
// Require a baseline to be specified if using a baseline-based alignment.
assert(!(alignment == PlaceholderAlignment.aboveBaseline ||
alignment == PlaceholderAlignment.belowBaseline ||
alignment == PlaceholderAlignment.baseline) || baseline != null);
// Default the baselineOffset to height if null. This will place the placeholder
// fully above the baseline, similar to [PlaceholderAlignment.aboveBaseline].
baselineOffset = baselineOffset ?? height;
_addPlaceholder(width * scale, height * scale, alignment.index, baselineOffset * scale, baseline?.index);
_placeholderCount++;
_placeholderScales.add(scale);
}
String? _addPlaceholder(double width, double height, int alignment, double baselineOffset, int? baseline) native 'ParagraphBuilder_addPlaceholder';
/// Applies the given paragraph style and returns a [Paragraph] containing the
/// added text and associated styling.
///
/// After calling this function, the paragraph builder object is invalid and
/// cannot be used further.
Paragraph build() {
final Paragraph paragraph = Paragraph._();
_build(paragraph);
return paragraph;
}
void _build(Paragraph outParagraph) native 'ParagraphBuilder_build';
}
/// Loads a font from a buffer and makes it available for rendering text.
///
/// * `list`: A list of bytes containing the font file.
/// * `fontFamily`: The family name used to identify the font in text styles.
/// If this is not provided, then the family name will be extracted from the font file.
Future<void> loadFontFromList(Uint8List list, {String? fontFamily}) {
return _futurize(
(_Callback<void> callback) {
_loadFontFromList(list, callback, fontFamily);
}
).then((_) => _sendFontChangeMessage());
}
final ByteData _fontChangeMessage = utf8.encoder.convert(
json.encode(<String, dynamic>{'type': 'fontsChange'})
).buffer.asByteData();
FutureOr<void> _sendFontChangeMessage() async {
const String kSystemChannelName = 'flutter/system';
if (PlatformDispatcher.instance.onPlatformMessage != null) {
_invoke3<String, ByteData?, PlatformMessageResponseCallback>(
PlatformDispatcher.instance.onPlatformMessage,
PlatformDispatcher.instance._onPlatformMessageZone,
kSystemChannelName,
_fontChangeMessage,
(ByteData? responseData) { },
);
} else {
channelBuffers.push(kSystemChannelName, _fontChangeMessage, (ByteData? responseData) { });
}
}
// TODO(gspencergoog): remove this template block once the framework templates
// are renamed to not reference it.
/// {@template flutter.dart:ui.textHeightBehavior}
/// Defines how the paragraph will apply [TextStyle.height] to the ascent of the
/// first line and descent of the last line.
///
/// Each boolean value represents whether the [TextStyle.height] modifier will
/// be applied to the corresponding metric. By default, all properties are true,
/// and [TextStyle.height] is applied as normal. When set to false, the font's
/// default ascent will be used.
/// {@endtemplate}
void _loadFontFromList(Uint8List list, _Callback<void> callback, String? fontFamily) native 'loadFontFromList';