// 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. part of dart.ui; /// A [TextStyle.height] value that indicates the text span should take /// the height defined by the font, which may not be exactly the height of /// [TextStyle.fontSize]. // To change the sentinel value, search for "kTextHeightNone" in the source code. const double kTextHeightNone = 0.0; /// Whether to use the italic type variation of glyphs in the font. /// /// Some modern fonts allow this to be selected in a more fine-grained manner. /// See [FontVariation.italic] for details. /// /// Italic type is distinct from slanted glyphs. To control the slant of a /// glyph, consider the [FontVariation.slant] font feature. enum FontStyle { /// Use the upright ("Roman") glyphs. normal, /// Use glyphs that have a more pronounced angle and typically a cursive style /// ("italic type"). italic, } /// The thickness of the glyphs used to draw the text. /// /// Values must be in the range 1..1000. /// /// Fonts are typically weighted on a 9-point scale, which, for historical /// reasons, uses the names 100 to 900. In Flutter, these are named `w100` to /// `w900` and have the following conventional meanings: /// /// * [w100]: Thin, the thinnest font weight. /// /// * [w200]: Extra light. /// /// * [w300]: Light. /// /// * [w400]: Normal. The constant [FontWeight.normal] is an alias for this value. /// /// * [w500]: Medium. /// /// * [w600]: Semi-bold. /// /// * [w700]: Bold. The constant [FontWeight.bold] is an alias for this value. /// /// * [w800]: Extra-bold. /// /// * [w900]: Black, the thickest font weight. /// /// For example, the font named "Roboto Medium" is typically exposed as a font /// with the name "Roboto" and the weight [FontWeight.w500]. /// /// Some modern fonts allow the weight to be adjusted in arbitrary increments. /// When using these fonts, applications can specify [FontWeight] instances /// constructed using values other than the predefined values. For these fonts, /// [FontWeight] will set the value of the `wght` axis (producing the same /// results as explicitly setting that attribute using [FontVariation.weight]). class FontWeight { /// Creates a [FontWeight] object, which can be added to a [TextStyle] to /// select the thickness of a font's glyphs. const FontWeight(this.value) : assert(value >= 1, 'Font weight must be between 1 and 1000'), assert(value <= 1000, 'Font weight must be between 1 and 1000'); /// The encoded integer value of this font weight. @Deprecated('Use value, which is more precise.') int get index => (value ~/ 100 - 1).clamp(0, 8); /// The thickness value of this font weight. final int value; /// Thin, the least thick. static const FontWeight w100 = FontWeight(100); /// Extra-light. static const FontWeight w200 = FontWeight(200); /// Light. static const FontWeight w300 = FontWeight(300); /// Normal / regular / plain. static const FontWeight w400 = FontWeight(400); /// Medium. static const FontWeight w500 = FontWeight(500); /// Semi-bold. static const FontWeight w600 = FontWeight(600); /// Bold. static const FontWeight w700 = FontWeight(700); /// Extra-bold. static const FontWeight w800 = FontWeight(800); /// Black, the most thick. static const FontWeight w900 = FontWeight(900); /// 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 values = [ w100, w200, w300, w400, w500, w600, w700, w800, w900, ]; @override bool operator ==(Object other) { if (other.runtimeType != runtimeType) { return false; } return other is FontWeight && other.value == value; } @override int get hashCode => value; /// Linearly interpolates between two font weights. /// /// 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], such as /// an [AnimationController]. static FontWeight? lerp(FontWeight? a, FontWeight? b, double t) { if (a == null && b == null) { return null; } return FontWeight( _lerpInt((a ?? normal).value, (b ?? normal).value, t).round().clamp(100, 900), ); } @override String toString() { if (value % 100 != 0) { return 'FontWeight($value)'; } return const { 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 to examine your fonts to /// determine what features are available. /// /// {@tool sample} /// 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. /// /// ** See code in examples/api/lib/ui/text/font_feature.0.dart ** /// {@end-tool} /// /// Some fonts also support continuous font variations; see the [FontVariation] /// class. /// /// See also: /// /// * , /// Wikipedia's description of these typographic features. /// /// * , /// 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 const FontFeature(this.feature, [this.value = 1]) : assert(feature.length == 4, 'Feature tag must be exactly four characters long.'), 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. // ------------------------------------------------------------------------ /// Access alternative glyphs. (`aalt`) /// /// This feature selects the given glyph variant for glyphs in the span. /// /// {@tool sample} /// 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 [widgets.Text.rich]), the desired rendering for each glyph can be /// achieved. /// /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_aalt.png) /// /// ** See code in examples/api/lib/ui/text/font_feature.font_feature_alternative.0.dart ** /// {@end-tool} /// /// See also: /// /// * const FontFeature.alternative(this.value) : feature = 'aalt'; /// 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} /// 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]. /// /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_afrc.png) /// /// ** See code in examples/api/lib/ui/text/font_feature.font_feature_alternative_fractions.0.dart ** /// {@end-tool} /// /// See also: /// /// * [FontFeature.fractions], which has a similar (but different) effect. /// * const FontFeature.alternativeFractions() : feature = 'afrc', value = 1; /// Enable contextual alternates. (`calt`) /// /// With this feature enabled, specific glyphs may be replaced by /// alternatives based on nearby text. /// /// {@tool sample} /// 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. /// /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_calt.png) /// /// ** See code in examples/api/lib/ui/text/font_feature.font_feature_contextual_alternates.0.dart ** /// {@end-tool} /// /// See also: /// /// * [FontFeature.randomize], which is more a rarely supported but more /// powerful way to get a similar effect. /// * const FontFeature.contextualAlternates() : feature = 'calt', value = 1; /// 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 /// aesthetically pleasing combination with capital letters. /// /// {@tool sample} /// 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. /// /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_case.png) /// /// ** See code in examples/api/lib/ui/text/font_feature.font_feature_case_sensitive_forms.0.dart ** /// {@end-tool} /// /// See also: /// /// * const FontFeature.caseSensitiveForms() : feature = 'case', value = 1; /// 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} /// 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. /// /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_cvXX.png) /// /// ** See code in examples/api/lib/ui/text/font_feature.font_feature_character_variant.0.dart ** /// {@end-tool} /// /// See also: /// /// * [FontFeature.stylisticSet], which allows for groups of characters /// variants to be selected at once, as opposed to individual character variants. /// * factory FontFeature.characterVariant(int value) { assert(value >= 1); assert(value <= 99); return FontFeature('cv${value.toString().padLeft(2, "0")}'); } /// 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} /// The Piazzolla font supports the `dnom` feature. It causes /// the digits to be rendered smaller and near the bottom of the EM box. /// /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_dnom.png) /// /// ** See code in examples/api/lib/ui/text/font_feature.font_feature_denominator.0.dart ** /// {@end-tool} /// /// See also: /// /// * const FontFeature.denominator() : feature = 'dnom', value = 1; /// 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.numerators] and /// [FontFeature.denominator] features. /// /// {@tool sample} /// The Ubuntu Mono font supports the `frac` feature. It causes /// digits around slashes to be turned into dedicated fraction /// glyphs. This contrasts to the effect seen with /// [FontFeature.alternativeFractions]. /// /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_frac.png) /// /// ** See code in examples/api/lib/ui/text/font_feature.font_feature_fractions.0.dart ** /// {@end-tool} /// /// See also: /// /// * [FontFeature.alternativeFractions], which has a similar (but different) effect. /// * const FontFeature.fractions() : feature = 'frac', value = 1; /// Use historical forms. (`hist`) /// /// Some fonts have alternatives 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} /// 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. /// /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_historical.png) /// /// ** See code in examples/api/lib/ui/text/font_feature.font_feature_historical_forms.0.dart ** /// {@end-tool} /// /// See also: /// /// * const FontFeature.historicalForms() : feature = 'hist', value = 1; /// 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} /// 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. /// /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_historical.png) /// /// ** See code in examples/api/lib/ui/text/font_feature.font_feature_historical_ligatures.0.dart ** /// {@end-tool} /// /// See also: /// /// * const FontFeature.historicalLigatures() : feature = 'hlig', value = 1; /// 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} /// The Sorts Mill Goudy font supports the `lnum` feature. It causes /// digits to fit more seamlessly with capital letters. /// /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_lnum.png) /// /// ** See code in examples/api/lib/ui/text/font_feature.font_feature_lining_figures.0.dart ** /// {@end-tool} /// /// See also: /// /// * const FontFeature.liningFigures() : feature = 'lnum', value = 1; /// 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 /// Cyrillic 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} /// 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. /// /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_locl.png) /// /// ** See code in examples/api/lib/ui/text/font_feature.font_feature_locale_aware.0.dart ** /// {@end-tool} /// /// See also: /// /// * /// * /// * const FontFeature.localeAware({bool enable = true}) : feature = 'locl', value = enable ? 1 : 0; /// 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} /// 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. /// /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_nalt.png) /// /// ** See code in examples/api/lib/ui/text/font_feature.font_feature_notational_forms.0.dart ** /// {@end-tool} /// /// See also: /// /// * const FontFeature.notationalForms([this.value = 1]) : feature = 'nalt', assert(value >= 0); /// 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} /// The Piazzolla font supports the `numr` feature. It causes /// the digits to be rendered smaller and near the top of the EM box. /// /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_numr.png) /// /// ** See code in examples/api/lib/ui/text/font_feature.font_feature_numerators.0.dart ** /// {@end-tool} /// /// See also: /// /// * const FontFeature.numerators() : feature = 'numr', value = 1; /// Use old style 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} /// The Piazzolla font supports the `onum` feature. It causes /// digits to extend below the baseline. /// /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_onum.png) /// /// ** See code in examples/api/lib/ui/text/font_feature.font_feature_oldstyle_figures.0.dart ** /// {@end-tool} /// /// See also: /// /// * /// * const FontFeature.oldstyleFigures() : feature = 'onum', value = 1; /// 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} /// The Piazzolla font supports the `ordn` feature. It causes /// alphabetic glyphs to become smaller and superscripted. /// /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_ordn.png) /// /// ** See code in examples/api/lib/ui/text/font_feature.font_feature_ordinal_forms.0.dart ** /// {@end-tool} /// /// See also: /// /// * const FontFeature.ordinalForms() : feature = 'ordn', value = 1; /// 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} /// 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. /// /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_pnum.png) /// /// ** See code in examples/api/lib/ui/text/font_feature.font_feature_proportional_figures.0.dart ** /// {@end-tool} /// /// See also: /// /// * const FontFeature.proportionalFigures() : feature = 'pnum', value = 1; /// 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: /// /// * const FontFeature.randomize() : feature = 'rand', value = 1; /// 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} /// 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. /// /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_salt.png) /// /// ** See code in examples/api/lib/ui/text/font_feature.font_feature_stylistic_alternates.0.dart ** /// {@end-tool} /// /// See also: /// /// * [FontFeature.contextualAlternates], which is enables alternates specific to certain contexts. /// * const FontFeature.stylisticAlternates() : feature = 'salt', value = 1; /// 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} /// The Piazzolla font supports the `sinf` feature. It causes /// digits to be smaller and subscripted. /// /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_sinf.png) /// /// ** See code in examples/api/lib/ui/text/font_feature.font_feature_scientific_inferiors.0.dart ** /// {@end-tool} /// /// See also: /// /// * const FontFeature.scientificInferiors() : feature = 'sinf', value = 1; /// 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} /// 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]). /// /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_ssXX_1.png) /// /// ** See code in examples/api/lib/ui/text/font_feature.font_feature_stylistic_set.0.dart ** /// {@end-tool} /// /// {@tool sample} /// The Piazzolla font supports the `ssXX` feature 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. /// /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_ssXX_2.png) /// /// ** See code in examples/api/lib/ui/text/font_feature.font_feature_stylistic_set.1.dart ** /// {@end-tool} /// /// See also: /// /// * [FontFeature.characterVariant], which allows for individual character /// variants to be selected, as opposed to entire sets. /// * factory FontFeature.stylisticSet(int value) { assert(value >= 1); assert(value <= 20); return FontFeature('ss${value.toString().padLeft(2, "0")}'); } /// 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} /// The Piazzolla font supports the `subs` feature. It causes /// digits to be smaller and subscripted. /// /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_subs.png) /// /// ** See code in examples/api/lib/ui/text/font_feature.font_feature_subscripts.0.dart ** /// {@end-tool} /// /// See also: /// /// * /// * [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; /// 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} /// 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). /// /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_sups.png) /// /// ** See code in examples/api/lib/ui/text/font_feature.font_feature_superscripts.0.dart ** /// {@end-tool} /// /// See also: /// /// * /// * [FontFeature.subscripts], which is similar but for subscripting. const FontFeature.superscripts() : feature = 'sups', value = 1; /// 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} /// The BioRhyme Expanded font supports the `swsh` feature specifically /// for the capital "Q" and "R" glyphs and the ampersand. /// /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_swsh.png) /// /// ** See code in examples/api/lib/ui/text/font_feature.font_feature_swash.0.dart ** /// {@end-tool} /// /// See also: /// /// * /// * const FontFeature.swash([this.value = 1]) : feature = 'swsh', assert(value >= 0); /// 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} /// The Piazzolla font supports the `tnum` feature. It causes the /// digits to become uniformly-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. /// /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_tnum.png) /// /// ** See code in examples/api/lib/ui/text/font_feature.font_feature_tabular_figures.0.dart ** /// {@end-tool} /// /// See also: /// /// * const FontFeature.tabularFigures() : feature = 'tnum', value = 1; /// 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} /// 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. /// /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/font_feature_zero.png) /// /// ** See code in examples/api/lib/ui/text/font_feature.font_feature_slashed_zero.0.dart ** /// {@end-tool} /// /// See also: /// /// * 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: /// 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: /// /// * final int value; static const int _kEncodedSize = 8; void _encode(ByteData byteData) { assert(feature.codeUnits.every((int c) => c >= 0x20 && c <= 0x7F)); for (var 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 => Object.hash(feature, value); @override String toString() => "FontFeature('$feature', $value)"; } /// An axis tag and value that can be used to customize variable fonts. /// /// Some fonts are variable fonts that can generate a range of different /// font faces by altering the values of the font's design axes. /// /// For example: /// /// ```dart /// const TextStyle(fontVariations: [ui.FontVariation('slnt', -5.0)]) /// ``` /// /// Font variations are distinct from font features, as exposed by the /// [FontFeature] class. Where features can be enabled or disabled in a discrete /// manner, font variations provide a continuous axis of control. /// /// See also: /// /// * , /// which lists registered axis tags. /// /// * , /// an overview of the font variations technology. class FontVariation { /// Creates a [FontVariation] object, which can be added to a [TextStyle] to /// change the variable attributes of a font. /// /// `axis` is the four-character tag that identifies the design axis. /// OpenType lists the [currently registered axis /// tags](https://docs.microsoft.com/en-us/typography/opentype/spec/dvaraxisreg). /// /// `value` is the value that the axis will be set to. The behavior /// depends on how the font implements the axis. const FontVariation(this.axis, this.value) : assert(axis.length == 4, 'Axis tag must be exactly four characters long.'), assert( value >= -32768.0 && value < 32768.0, 'Value must be representable as a signed 16.16 fixed-point number, i.e. it must be in this range: -32768.0 ≤ value < 32768.0', ); // Constructors below should be alphabetic by axis tag. This makes it easier // to determine when an axis is missing so that we avoid adding duplicates. // Start of axis tag list. // ------------------------------------------------------------------------ /// Variable font style. (`ital`) /// /// Varies the style of glyphs in the font between normal and italic. /// /// Values must in the range 0.0 (meaning normal, or Roman, as in /// [FontStyle.normal]) to 1.0 (meaning fully italic, as in /// [FontStyle.italic]). /// /// This is distinct from [FontVariation.slant], which leans the characters /// without changing the font style. /// /// See also: /// /// * const FontVariation.italic(this.value) : assert(value >= 0.0), assert(value <= 1.0), axis = 'ital'; /// Optical size optimization. (`opzs`) /// /// Changes the rendering of the font to be optimized for the given text size. /// Normally, the optical size of the font will be derived from the font size. /// /// This feature could be used when the text represents a particular physical /// font size, for example text in the representation of a hardcopy magazine, /// which does not correspond to the actual font size being used to render the /// text. By setting the optical size explicitly, font variations that might /// be applied as the text is zoomed will be fixed at the size being /// represented by the text. /// /// This feature could also be used to smooth animations. If a font varies its /// rendering as the font size is adjusted, it may appear to "quiver" (or, one /// might even say, "flutter") if the font size is animated. By setting a /// fixed optical size, the rendering can be fixed to one particular style as /// the text size animates. /// /// Values must be greater than zero, and are interpreted as points. A point /// is 1/72 of an inch, or 1.333 logical pixels (96/72). /// /// See also: /// /// * const FontVariation.opticalSize(this.value) : assert(value > 0.0), axis = 'opsz'; /// Variable font width. (`slnt`) /// /// Varies the slant of glyphs in the font. /// /// Values must be greater than -90.0 and less than +90.0, and represents the /// angle in _counter-clockwise_ degrees relative to "normal", at 0.0. /// /// For example, to lean the glyphs forward by 45 degrees, one would use /// `FontVariation.slant(-45.0)`. /// /// This is distinct from [FontVariation.italic], in that slant leans the /// characters without changing the font style. /// /// See also: /// /// * const FontVariation.slant(this.value) : assert(value > -90.0), assert(value < 90.0), axis = 'slnt'; /// Variable font width. (`wdth`) /// /// Varies the width of glyphs in the font. /// /// Values must be greater than zero, with no upper limit. 100.0 represents /// the "normal" width. Smaller values are "condensed", greater values are /// "extended". /// /// See also: /// /// * const FontVariation.width(this.value) : assert(value >= 0.0), axis = 'wdth'; /// Variable font weight. (`wght`) /// /// Applications should avoid using this and should instead declare font /// weight by specifying a [FontWeight], which will implicitly set this /// attribute. However, if a value is provided for this attribute, then it /// will override the [FontWeight]. /// /// Varies the stroke thickness of the font. /// /// Values must be in the range 1..1000, and are to be interpreted in a manner /// consistent with the values of [FontWeight]. For instance, `400` is the /// "normal" weight, and `700` is "bold". /// /// See also: /// /// * const FontVariation.weight(this.value) : assert(value >= 1), assert(value <= 1000), axis = 'wght'; // ------------------------------------------------------------------------ // End of axis tags list. /// The tag that identifies the design axis. /// /// An axis tag must consist of 4 ASCII characters. final String axis; /// The value assigned to this design axis. /// /// The range of usable values depends on the specification of the axis. /// /// While this property is represented as a [double] in this API /// ([binary64](https://en.wikipedia.org/wiki/Double-precision_floating-point_format)), /// fonts use the fixed-point 16.16 format to represent the value of font /// variations. This means that the actual range is -32768.0 to approximately /// 32767.999985 and in principle the smallest increment between two values is /// approximately 0.000015 (1/65536). /// /// Unfortunately for technical reasons the value is first converted to the /// [binary32 floating point /// format](https://en.wikipedia.org/wiki/Single-precision_floating-point_format), /// which only has 24 bits of precision. This means that for values outside /// the range -256.0 to 256.0, the smallest increment is larger than what is /// technically supported by OpenType. At the extreme edge of the range, the /// smallest increment is only approximately ±0.002. final double value; static const int _kEncodedSize = 8; void _encode(ByteData byteData) { assert(axis.codeUnits.every((int c) => c >= 0x20 && c <= 0x7F)); for (var i = 0; i < 4; i++) { byteData.setUint8(i, axis.codeUnitAt(i)); } byteData.setFloat32(4, value, _kFakeHostEndian); } @override bool operator ==(Object other) { if (other.runtimeType != runtimeType) { return false; } return other is FontVariation && other.axis == axis && other.value == value; } @override int get hashCode => Object.hash(axis, value); /// Linearly interpolates between two font variations. /// /// If the two variations have different axis tags, the interpolation switches /// abruptly from one to the other at t=0.5. Otherwise, the value is /// interpolated (see [lerpDouble]. /// /// The value is not clamped to the valid values of the axis tag, but it is /// clamped to the valid range of font variations values in general (the range /// of signed 16.16 fixed point numbers). /// /// 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]). /// /// Values for `t` are usually obtained from an [Animation], such as /// an [AnimationController]. static FontVariation? lerp(FontVariation? a, FontVariation? b, double t) { if (a?.axis != b?.axis || (a == null && b == null)) { return t < 0.5 ? a : b; } return FontVariation( a!.axis, clampDouble(lerpDouble(a.value, b!.value, t)!, -32768.0, 32768.0 - 1.0 / 65536.0), ); } @override String toString() => "FontVariation('$axis', $value)"; } /// The measurements of a character (or a sequence of visually connected /// characters) within a paragraph. /// /// See also: /// /// * [Paragraph.getGlyphInfoAt], which finds the [GlyphInfo] associated with /// a code unit in the text. /// * [Paragraph.getClosestGlyphInfoForOffset], which finds the [GlyphInfo] of /// the glyph(s) onscreen that's closest to the given [Offset]. final class GlyphInfo { /// Creates a [GlyphInfo] with the specified values. GlyphInfo( this.graphemeClusterLayoutBounds, this.graphemeClusterCodeUnitRange, this.writingDirection, ); GlyphInfo._( double left, double top, double right, double bottom, int graphemeStart, int graphemeEnd, bool isLTR, ) : graphemeClusterLayoutBounds = Rect.fromLTRB(left, top, right, bottom), graphemeClusterCodeUnitRange = TextRange(start: graphemeStart, end: graphemeEnd), writingDirection = isLTR ? TextDirection.ltr : TextDirection.rtl; /// The layout bounding rect of the associated character, in the paragraph's /// coordinates. /// /// This is **not** a tight bounding box that encloses the character's outline. /// The vertical extent reported is derived from the font metrics (instead of /// glyph metrics), and the horizontal extent is the horizontal advance of the /// character. final Rect graphemeClusterLayoutBounds; /// The UTF-16 range of the associated character in the text. final TextRange graphemeClusterCodeUnitRange; /// The writing direction within the [GlyphInfo]. final TextDirection writingDirection; @override bool operator ==(Object other) { if (identical(this, other)) { return true; } return other is GlyphInfo && graphemeClusterLayoutBounds == other.graphemeClusterLayoutBounds && graphemeClusterCodeUnitRange == other.graphemeClusterCodeUnitRange && writingDirection == other.writingDirection; } @override int get hashCode => Object.hash(graphemeClusterLayoutBounds, graphemeClusterCodeUnitRange, writingDirection); @override String toString() => 'Glyph($graphemeClusterLayoutBounds, textRange: $graphemeClusterCodeUnitRange, direction: $writingDirection)'; } /// 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. /// /// This baseline is often used for alphabetical scripts like Latin, Greek, /// Cyrillic, etc. /// /// Characters with descenders (like 'p', 'g', or 'y') extend below this line. alphabetic, /// The horizontal line used to align ideographic characters. /// /// This baseline is often used for scripts with uniform square heights, /// like Chinese, Japanese, Korean, etc. 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 decorations) { var mask = 0; for (final 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 values = []; 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/descent 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. /// /// [TextHeightBehavior.applyHeightToFirstAscent] and /// [TextHeightBehavior.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. /// /// [TextHeightBehavior.leadingDistribution] determines how the /// leading is distributed over and under text. This /// property applies before [TextHeightBehavior.applyHeightToFirstAscent] and /// [TextHeightBehavior.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 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 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 Object.hash( 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(List? a, List? b) { if (a == null) { return b == null; } if (b == null || a.length != b.length) { return false; } for (var 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? fontFamilyFallback, double? fontSize, double? letterSpacing, double? wordSpacing, double? height, Locale? locale, Paint? background, Paint? foreground, List? shadows, List? fontFeatures, List? fontVariations, ) { final 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.value; } 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. } if (fontVariations != null) { result[0] |= 1 << 19; // 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 multiplier 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. Setting the `height` to `kTextHeightNone` 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 set to a non-null that is not `kTextHeightNone`, 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. /// * `fontVariations`: The font variations 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? fontFamilyFallback, double? fontSize, double? letterSpacing, double? wordSpacing, double? height, TextLeadingDistribution? leadingDistribution, Locale? locale, Paint? background, Paint? foreground, List? shadows, List? fontFeatures, List? fontVariations, }) : 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, fontVariations, ), _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, _fontVariations = fontVariations; final Int32List _encoded; final String _fontFamily; final List? _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? _shadows; final List? _fontFeatures; final List? _fontVariations; 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(other._encoded, _encoded) && _listEquals(other._shadows, _shadows) && _listEquals(other._fontFamilyFallback, _fontFamilyFallback) && _listEquals(other._fontFeatures, _fontFeatures) && _listEquals(other._fontVariations, _fontVariations); } @override int get hashCode { final List? shadows = _shadows; final List? fontFeatures = _fontFeatures; final List? fontVariations = _fontVariations; return Object.hash( Object.hashAll(_encoded), _leadingDistribution, _fontFamily, _fontFamilyFallback, _fontSize, _letterSpacing, _wordSpacing, _height, _locale, _background, _foreground, shadows == null ? null : Object.hashAll(shadows), _decorationThickness, fontFeatures == null ? null : Object.hashAll(fontFeatures), fontVariations == null ? null : Object.hashAll(fontVariations), ); } @override String toString() { final List? fontFamilyFallback = _fontFamilyFallback; final heightText = _encoded[0] & 0x02000 == 0x02000 ? (_height == kTextHeightNone ? 'kTextHeightNone' : '${_height}x') : 'unspecified'; 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(_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: $heightText, ' '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"}, ' 'fontVariations: ${_encoded[0] & 0x80000 == 0x80000 ? _fontVariations : "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 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.value; } 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. } // Paragraph styles are unique in a paragraph, there is no inheriting so // height == null and height == kTextHeightNone are semantically equivalent. if (height != null && height != kTextHeightNone) { 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 (or setting it to /// [kTextHeightNone]) 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(other._encoded, _encoded); } @override int get hashCode => Object.hash( Object.hashAll(_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(_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"}, ' 'strutStyle: ${_encoded[0] & 0x400 == 0x400 ? _strutStyle : "unspecified"}, ' 'ellipsis: ${_encoded[0] & 0x800 == 0x800 ? '"$_ellipsis"' : "unspecified"}, ' 'locale: ${_encoded[0] & 0x1000 == 0x1000 ? _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 integers are stored before any 32 bit integers. // // 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? fontFamilyFallback, double? fontSize, double? height, TextLeadingDistribution? leadingDistribution, double? leading, FontWeight? fontWeight, FontStyle? fontStyle, bool? forceStrutHeight, ) { // Strut styles are unique in a paragraph, there is no inheriting so // height == null and height == kTextHeightNone are semantically equivalent. final bool hasHeightOverride = height != null && height != kTextHeightNone; if (fontFamily == null && fontSize == null && !hasHeightOverride && leadingDistribution == null && leading == null && fontWeight == null && fontStyle == null && forceStrutHeight == null) { return ByteData(0); } final data = ByteData(24); // Max size is 24 bytes var bitmask = 0; var byteCount = 4; if (fontWeight != null) { bitmask |= 1 << 0; data.setInt32(byteCount, fontWeight.value, _kFakeHostEndian); byteCount += 4; } if (fontStyle != null) { bitmask |= 1 << 1; data.setInt32(byteCount, fontStyle.index, _kFakeHostEndian); byteCount += 4; } 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 (hasHeightOverride) { 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.setInt32(0, bitmask, _kFakeHostEndian); assert(byteCount <= 24); assert(bitmask >> 32 == 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` (or setting it to [kTextHeightNone]) 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? 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? _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(other._fontFamilyFallback, _fontFamilyFallback) && _listEquals(other._encoded.buffer.asInt8List(), _encoded.buffer.asInt8List()); } @override int get hashCode => Object.hash(Object.hashAll(_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 [widgets.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]. 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 => Object.hash(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 describe a caret position in between /// characters. The [offset] points to the position between `offset - 1` and /// `offset` characters of the string, and the [affinity] is used to describe /// which character this position affiliates with. /// /// One use case is 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}); /// 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 => Object.hash(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 >= -1), assert(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 >= -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 => Object.hash(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}); /// 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 /// [ParagraphStyle.new] 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, }); LineMetrics._( this.hardBreak, this.ascent, this.descent, this.unscaledAscent, this.height, this.width, this.left, this.baseline, 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 => Object.hash( 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. abstract class Paragraph { /// The amount of horizontal space this paragraph occupies. /// /// Valid only after [layout] has been called. double get width; /// The amount of vertical space this paragraph occupies. /// /// Valid only after [layout] has been called. double get 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; /// 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; /// Returns the smallest width beyond which increasing the width never /// decreases the height. /// /// Valid only after [layout] has been called. double get maxIntrinsicWidth; /// The distance from the top of the paragraph to the alphabetic /// baseline of the first line, in logical pixels. double get alphabeticBaseline; /// The distance from the top of the paragraph to the ideographic /// baseline of the first line, in logical pixels. double get 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 /// [ParagraphStyle.new]. bool get 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); /// 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 getBoxesForRange( int start, int end, { BoxHeightStyle boxHeightStyle = BoxHeightStyle.tight, BoxWidthStyle boxWidthStyle = BoxWidthStyle.tight, }); /// 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 getBoxesForPlaceholders(); /// Returns the text position closest to the given offset. /// /// This method always returns a [TextPosition] for any given [offset], even /// when the [offset] is not close to any text, or when the paragraph is empty. /// This is useful for determining the text to select when the user drags the /// text selection handle. /// /// See also: /// /// * [getClosestGlyphInfoForOffset], which returns more information about /// the closest character to an [Offset]. TextPosition getPositionForOffset(Offset offset); /// Returns the [GlyphInfo] of the glyph closest to the given `offset` in the /// paragraph coordinate system, or null if if the text is empty, or is /// entirely clipped or ellipsized away. /// /// This method first finds the line closest to `offset.dy`, and then returns /// the [GlyphInfo] of the closest glyph(s) within that line. GlyphInfo? getClosestGlyphInfoForOffset(Offset offset); /// Returns the [GlyphInfo] located at the given UTF-16 `codeUnitOffset` in /// the paragraph, or null if the given `codeUnitOffset` is out of the visible /// lines or is ellipsized. GlyphInfo? getGlyphInfoAt(int codeUnitOffset); /// 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 /// /// The [TextPosition] is treated as caret position, its [TextPosition.affinity] /// is used to determine which character this position points to. For example, /// the word boundary at `TextPosition(offset: 5, affinity: TextPosition.upstream)` /// of the `string = 'Hello word'` will return range (0, 5) because the position /// points to the character 'o' instead of the space. TextRange getWordBoundary(TextPosition position); /// 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); /// 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 computeLineMetrics(); /// Returns the [LineMetrics] for the line at `lineNumber`, or null if the /// given `lineNumber` is greater than or equal to [numberOfLines]. LineMetrics? getLineMetricsAt(int lineNumber); /// The total number of visible lines in the paragraph. /// /// Returns a non-negative number. If `maxLines` is non-null, the value of /// [numberOfLines] never exceeds `maxLines`. int get numberOfLines; /// Returns the line number of the line that contains the code unit that /// `codeUnitOffset` points to. /// /// This method returns null if the given `codeUnitOffset` is out of bounds, or /// is logically after the last visible codepoint. This includes the case where /// its codepoint belongs to a visible line, but the text layout library /// replaced it with an ellipsis. /// /// If the target code unit points to a control character that introduces /// mandatory line breaks (most notably the line feed character `LF`, typically /// represented in strings as the escape sequence "\n"), to conform to /// [the unicode rules](https://unicode.org/reports/tr14/#LB4), the control /// character itself is always considered to be at the end of "current" line /// rather than the beginning of the new line. int? getLineNumberAt(int codeUnitOffset); /// Release the resources used by this object. The object is no longer usable /// after this method is called. void dispose(); /// Whether this reference to the underlying picture is [dispose]d. /// /// This only returns a valid value if asserts are enabled, and must not be /// used otherwise. bool get debugDisposed; } base class _NativeParagraph extends NativeFieldWrapperClass1 implements Paragraph { /// This class is created by the engine, and should not be instantiated /// or extended directly. /// /// To create a [Paragraph] object, use a [ParagraphBuilder]. _NativeParagraph._(); bool _needsLayout = true; @override @Native)>(symbol: 'Paragraph::width', isLeaf: true) external double get width; @override @Native)>(symbol: 'Paragraph::height', isLeaf: true) external double get height; @override @Native)>(symbol: 'Paragraph::longestLine', isLeaf: true) external double get longestLine; @override @Native)>(symbol: 'Paragraph::minIntrinsicWidth', isLeaf: true) external double get minIntrinsicWidth; @override @Native)>(symbol: 'Paragraph::maxIntrinsicWidth', isLeaf: true) external double get maxIntrinsicWidth; @override @Native)>(symbol: 'Paragraph::alphabeticBaseline', isLeaf: true) external double get alphabeticBaseline; @override @Native)>(symbol: 'Paragraph::ideographicBaseline', isLeaf: true) external double get ideographicBaseline; @override @Native)>(symbol: 'Paragraph::didExceedMaxLines', isLeaf: true) external bool get didExceedMaxLines; @override void layout(ParagraphConstraints constraints) { _layout(constraints.width); assert(() { _needsLayout = false; return true; }()); } @Native, Double)>(symbol: 'Paragraph::layout', isLeaf: true) external void _layout(double width); List _decodeTextBoxes(Float32List encoded) { final int count = encoded.length ~/ 5; final boxes = []; var position = 0; for (var 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; } @override List getBoxesForRange( int start, int end, { BoxHeightStyle boxHeightStyle = BoxHeightStyle.tight, BoxWidthStyle boxWidthStyle = BoxWidthStyle.tight, }) { return _decodeTextBoxes( _getBoxesForRange(start, end, boxHeightStyle.index, boxWidthStyle.index), ); } // See paragraph.cc for the layout of this return value. @Native, Uint32, Uint32, Uint32, Uint32)>( symbol: 'Paragraph::getRectsForRange', ) external Float32List _getBoxesForRange(int start, int end, int boxHeightStyle, int boxWidthStyle); @override List getBoxesForPlaceholders() { return _decodeTextBoxes(_getBoxesForPlaceholders()); } @Native)>(symbol: 'Paragraph::getRectsForPlaceholders') external Float32List _getBoxesForPlaceholders(); @override TextPosition getPositionForOffset(Offset offset) { final List encoded = _getPositionForOffset(offset.dx, offset.dy); return TextPosition(offset: encoded[0], affinity: TextAffinity.values[encoded[1]]); } @Native, Double, Double)>(symbol: 'Paragraph::getPositionForOffset') external List _getPositionForOffset(double dx, double dy); @override GlyphInfo? getGlyphInfoAt(int codeUnitOffset) => _getGlyphInfoAt(codeUnitOffset, GlyphInfo._); @Native, Uint32, Handle)>(symbol: 'Paragraph::getGlyphInfoAt') external GlyphInfo? _getGlyphInfoAt(int codeUnitOffset, Function constructor); @override GlyphInfo? getClosestGlyphInfoForOffset(Offset offset) => _getClosestGlyphInfoForOffset(offset.dx, offset.dy, GlyphInfo._); @Native, Double, Double, Handle)>( symbol: 'Paragraph::getClosestGlyphInfo', ) external GlyphInfo? _getClosestGlyphInfoForOffset(double dx, double dy, Function constructor); @override TextRange getWordBoundary(TextPosition position) { final int characterPosition; switch (position.affinity) { case TextAffinity.upstream: characterPosition = position.offset - 1; case TextAffinity.downstream: characterPosition = position.offset; } final List boundary = _getWordBoundary(characterPosition); return TextRange(start: boundary[0], end: boundary[1]); } @Native, Uint32)>(symbol: 'Paragraph::getWordBoundary') external List _getWordBoundary(int offset); @override TextRange getLineBoundary(TextPosition position) { final List boundary = _getLineBoundary(position.offset); final line = TextRange(start: boundary[0], end: boundary[1]); final List nextBoundary = _getLineBoundary(position.offset + 1); final nextLine = TextRange(start: nextBoundary[0], end: nextBoundary[1]); // If there is no next line, because we're at the end of the field, return line. if (!nextLine.isValid) { return line; } // _getLineBoundary only considers the offset and assumes that the // TextAffinity is upstream. In the case that TextPosition is just after a // word wrap (downstream), we need to return the line for the next offset. if (position.affinity == TextAffinity.downstream && line != nextLine && position.offset == line.end && line.end == nextLine.start) { return TextRange(start: nextBoundary[0], end: nextBoundary[1]); } return line; } @Native, Uint32)>(symbol: 'Paragraph::getLineBoundary') external List _getLineBoundary(int offset); // 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. @Native, Pointer, Double, Double)>(symbol: 'Paragraph::paint') external void _paint(_NativeCanvas canvas, double x, double y); @override List computeLineMetrics() { final Float64List encoded = _computeLineMetrics(); final int count = encoded.length ~/ 9; var position = 0; final metrics = [ 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; } @Native)>(symbol: 'Paragraph::computeLineMetrics') external Float64List _computeLineMetrics(); @override LineMetrics? getLineMetricsAt(int lineNumber) => _getLineMetricsAt(lineNumber, LineMetrics._); @Native, Uint32, Handle)>(symbol: 'Paragraph::getLineMetricsAt') external LineMetrics? _getLineMetricsAt(int lineNumber, Function constructor); @override @Native)>(symbol: 'Paragraph::getNumberOfLines') external int get numberOfLines; @override int? getLineNumberAt(int codeUnitOffset) { final int lineNumber = _getLineNumber(codeUnitOffset); return lineNumber < 0 ? null : lineNumber; } @Native, Uint32)>(symbol: 'Paragraph::getLineNumberAt') external int _getLineNumber(int codeUnitOffset); @override void dispose() { assert(!_disposed); assert(() { _disposed = true; return true; }()); _dispose(); } /// This can't be a leaf call because the native function calls Dart API /// (Dart_SetNativeInstanceField). @Native)>(symbol: 'Paragraph::dispose') external void _dispose(); bool _disposed = false; @override bool get debugDisposed { bool? disposed; assert(() { disposed = _disposed; return true; }()); return disposed ?? (throw StateError( '$runtimeType.debugDisposed is only available when asserts are enabled.', )); } @override String toString() { String? result; assert(() { if (_disposed && _needsLayout) { result = 'Paragraph(DISPOSED while dirty)'; } if (_disposed && !_needsLayout) { result = 'Paragraph(DISPOSED)'; } return true; }()); if (result != null) { return result!; } if (_needsLayout) { return 'Paragraph(dirty)'; } return 'Paragraph()'; } } /// 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 /// [ParagraphBuilder.new] 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]. abstract class ParagraphBuilder { /// Creates a new [ParagraphBuilder] object, which is used to create a /// [Paragraph]. factory ParagraphBuilder(ParagraphStyle style) = _NativeParagraphBuilder; /// The number of placeholders currently in the paragraph. int get placeholderCount; /// The scales of the placeholders in the paragraph. List get placeholderScales; /// Applies the given style to the added text until [pop] is called. /// /// See [pop] for details. void pushStyle(TextStyle style); /// 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(); /// Adds the given text to the paragraph. /// /// The text will be styled according to the current stack of text styles. void addText(String text); /// 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 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, }); /// 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(); } base class _NativeParagraphBuilder extends NativeFieldWrapperClass1 implements ParagraphBuilder { _NativeParagraphBuilder(ParagraphStyle style) : _defaultLeadingDistribution = style._leadingDistribution { List? strutFontFamilies; final StrutStyle? strutStyle = style._strutStyle; final ByteData? encodedStrutStyle; if (strutStyle != null && strutStyle._enabled) { final String? fontFamily = strutStyle._fontFamily; strutFontFamilies = [?fontFamily, ...?strutStyle._fontFamilyFallback]; assert(TextLeadingDistribution.values.length <= 2); final TextLeadingDistribution leadingDistribution = strutStyle._leadingDistribution ?? style._leadingDistribution; encodedStrutStyle = strutStyle._encoded; int bitmask = encodedStrutStyle.getInt32(0, _kFakeHostEndian); bitmask |= (leadingDistribution.index) << 3; encodedStrutStyle.setInt32(0, bitmask, _kFakeHostEndian); } else { encodedStrutStyle = null; } _constructor( style._encoded, encodedStrutStyle, style._fontFamily ?? '', strutFontFamilies, style._fontSize ?? 0, style._height ?? 0, style._ellipsis ?? '', _encodeLocale(style._locale), ); } @Native( symbol: 'ParagraphBuilder::Create', ) external void _constructor( Int32List encoded, ByteData? strutData, String fontFamily, List? strutFontFamily, double fontSize, double height, String ellipsis, String locale, ); @override int get placeholderCount => _placeholderCount; int _placeholderCount = 0; @override List get placeholderScales => _placeholderScales; final List _placeholderScales = []; final TextLeadingDistribution _defaultLeadingDistribution; @override void pushStyle(TextStyle style) { final fullFontFamilies = []; fullFontFamilies.add(style._fontFamily); final List? fontFamilyFallback = style._fontFamilyFallback; if (fontFamilyFallback != null) { fullFontFamilies.addAll(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? fontFeatures = style._fontFeatures; if (fontFeatures != null) { encodedFontFeatures = ByteData(fontFeatures.length * FontFeature._kEncodedSize); var byteOffset = 0; for (final FontFeature feature in fontFeatures) { feature._encode( ByteData.view(encodedFontFeatures.buffer, byteOffset, FontFeature._kEncodedSize), ); byteOffset += FontFeature._kEncodedSize; } } ByteData? encodedFontVariations; final List? fontVariations = style._fontVariations; if (fontVariations != null) { encodedFontVariations = ByteData(fontVariations.length * FontVariation._kEncodedSize); var byteOffset = 0; for (final FontVariation variation in fontVariations) { variation._encode( ByteData.view(encodedFontVariations.buffer, byteOffset, FontVariation._kEncodedSize), ); byteOffset += FontVariation._kEncodedSize; } } _pushStyle( encoded, fullFontFamilies, style._fontSize ?? 0, style._letterSpacing ?? 0, style._wordSpacing ?? 0, style._height ?? 0, style._decorationThickness ?? 0, _encodeLocale(style._locale), style._background?._objects, style._background?._data, style._foreground?._objects, style._foreground?._data, Shadow._encodeShadows(style._shadows), encodedFontFeatures, encodedFontVariations, ); } @Native< Void Function( Pointer, Handle, Handle, Double, Double, Double, Double, Double, Handle, Handle, Handle, Handle, Handle, Handle, Handle, Handle, ) >(symbol: 'ParagraphBuilder::pushStyle') external void _pushStyle( Int32List encoded, List fontFamilies, double fontSize, double letterSpacing, double wordSpacing, double height, double decorationThickness, String locale, List? backgroundObjects, ByteData? backgroundData, List? foregroundObjects, ByteData? foregroundData, ByteData shadowsData, ByteData? fontFeaturesData, ByteData? fontVariationsData, ); static String _encodeLocale(Locale? locale) => locale?.toString() ?? ''; @override @Native)>(symbol: 'ParagraphBuilder::pop', isLeaf: true) external void pop(); @override void addText(String text) { final String? error = _addText(text); if (error != null) { throw ArgumentError(error); } } @Native, Handle)>(symbol: 'ParagraphBuilder::addText') external String? _addText(String text); @override 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 ?? TextBaseline.alphabetic).index, ); _placeholderCount++; _placeholderScales.add(scale); } @Native, Double, Double, Uint32, Double, Uint32)>( symbol: 'ParagraphBuilder::addPlaceholder', ) external void _addPlaceholder( double width, double height, int alignment, double baselineOffset, int baseline, ); @override Paragraph build() { final paragraph = _NativeParagraph._(); _build(paragraph); return paragraph; } @Native, Handle)>(symbol: 'ParagraphBuilder::build') external void _build(_NativeParagraph outParagraph); @override String toString() => 'ParagraphBuilder'; } /// 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 loadFontFromList(Uint8List list, {String? fontFamily}) { return _futurize((_Callback callback) { _loadFontFromList(list, callback, fontFamily ?? ''); return null; }).then((_) => _sendFontChangeMessage()); } final ByteData _fontChangeMessage = utf8 .encode(json.encode({'type': 'fontsChange'})) .buffer .asByteData(); FutureOr _sendFontChangeMessage() async { const kSystemChannelName = 'flutter/system'; if (PlatformDispatcher.instance.onPlatformMessage != null) { _invoke3( PlatformDispatcher.instance.onPlatformMessage, PlatformDispatcher.instance._onPlatformMessageZone, kSystemChannelName, _fontChangeMessage, (ByteData? responseData) {}, ); } else { channelBuffers.push(kSystemChannelName, _fontChangeMessage, (ByteData? responseData) {}); } } @Native(symbol: 'FontCollection::LoadFontFromList') external void _loadFontFromList(Uint8List list, _Callback callback, String fontFamily);