// 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;

/// Whether to slant the glyphs in the font
enum FontStyle {
  /// Use the upright glyphs
  normal,

  /// Use glyphs designed for slanting
  italic,
}

/// The thickness of the glyphs used to draw the text
class FontWeight {
  const FontWeight._(this.index);

  /// The encoded integer value of this font weight.
  final int index;

  /// Thin, the least thick
  static const FontWeight w100 = FontWeight._(0);

  /// Extra-light
  static const FontWeight w200 = FontWeight._(1);

  /// Light
  static const FontWeight w300 = FontWeight._(2);

  /// Normal / regular / plain
  static const FontWeight w400 = FontWeight._(3);

  /// Medium
  static const FontWeight w500 = FontWeight._(4);

  /// Semi-bold
  static const FontWeight w600 = FontWeight._(5);

  /// Bold
  static const FontWeight w700 = FontWeight._(6);

  /// Extra-bold
  static const FontWeight w800 = FontWeight._(7);

  /// Black, the most thick
  static const FontWeight w900 = FontWeight._(8);

  /// The default font weight.
  static const FontWeight normal = w400;

  /// A commonly used font weight that is heavier than normal.
  static const FontWeight bold = w700;

  /// A list of all the font weights.
  static const List<FontWeight> values = <FontWeight>[
    w100, w200, w300, w400, w500, w600, w700, w800, w900
  ];

  /// Linearly interpolates between two font weights.
  ///
  /// Rather than using fractional weights, the interpolation rounds to the
  /// nearest weight.
  ///
  /// If both `a` and `b` are null, then this method will return null. Otherwise,
  /// any null values for `a` or `b` are interpreted as equivalent to [normal]
  /// (also known as [w400]).
  ///
  /// The `t` argument represents position on the timeline, with 0.0 meaning
  /// that the interpolation has not started, returning `a` (or something
  /// equivalent to `a`), 1.0 meaning that the interpolation has finished,
  /// returning `b` (or something equivalent to `b`), and values in between
  /// meaning that the interpolation is at the relevant point on the timeline
  /// between `a` and `b`. The interpolation can be extrapolated beyond 0.0 and
  /// 1.0, so negative values and values greater than 1.0 are valid (and can
  /// easily be generated by curves such as [Curves.elasticInOut]). The result
  /// is clamped to the range [w100]–[w900].
  ///
  /// Values for `t` are usually obtained from an [Animation<double>], such as
  /// an [AnimationController].
  static FontWeight lerp(FontWeight a, FontWeight b, double t) {
    assert(t != null);
    if (a == null && b == null)
      return null;
    return values[lerpDouble(a?.index ?? normal.index, b?.index ?? normal.index, t).round().clamp(0, 8)];
  }

  @override
  String toString() {
    return const <int, String>{
      0: 'FontWeight.w100',
      1: 'FontWeight.w200',
      2: 'FontWeight.w300',
      3: 'FontWeight.w400',
      4: 'FontWeight.w500',
      5: 'FontWeight.w600',
      6: 'FontWeight.w700',
      7: 'FontWeight.w800',
      8: 'FontWeight.w900',
    }[index];
  }
}

/// A feature tag and value that affect the selection of glyphs in a font.
///
/// {@tool sample}
///
/// This example shows usage of several OpenType font features, including
/// Small Caps (smcp), old-style figures, fractional ligatures and stylistic
/// sets.
///
/// ```dart
///class TypePage extends StatelessWidget {
///  // The Cardo, Milonga and Raleway Dots fonts can be downloaded from
///  // Google Fonts (https://www.google.com/fonts).
///
///  final titleStyle = TextStyle(
///    fontSize: 18,
///    fontFeatures: [FontFeature.enable('smcp')],
///    color: Colors.blueGrey[600],
///  );
///
///  @override
///  Widget build(BuildContext context) {
///    return Scaffold(
///      body: Center(
///        child: Column(
///          mainAxisAlignment: MainAxisAlignment.center,
///          children: <Widget>[
///            Spacer(flex: 5),
///            Text('regular numbers have their place:', style: titleStyle),
///            Text('The 1972 cup final was a 1-1 draw.',
///                style: TextStyle(
///                  fontFamily: 'Cardo',
///                  fontSize: 24,
///                )),
///            Spacer(),
///            Text('but old-style figures blend well with lower case:',
///                style: titleStyle),
///            Text('The 1972 cup final was a 1-1 draw.',
///                style: TextStyle(
///                    fontFamily: 'Cardo',
///                    fontSize: 24,
///                    fontFeatures: [FontFeature.oldstyleFigures()])),
///            Spacer(),
///            Divider(),
///            Spacer(),
///            Text('fractions look better with a custom ligature:',
///                style: titleStyle),
///            Text('Add 1/2 tsp of flour and stir.',
///                style: TextStyle(
///                    fontFamily: 'Milonga',
///                    fontSize: 24,
///                    fontFeatures: [FontFeature.enable('frac')])),
///            Spacer(),
///            Divider(),
///            Spacer(),
///            Text('multiple stylistic sets in one font:', style: titleStyle),
///            Text('Raleway Dots',
///                style: TextStyle(fontFamily: 'Raleway Dots', fontSize: 48)),
///            Text('Raleway Dots',
///                style: TextStyle(
///                  fontFeatures: [FontFeature.stylisticSet(1)],
///                  fontFamily: 'Raleway Dots',
///                  fontSize: 48,
///                )),
///            Spacer(flex: 5),
///          ],
///        ),
///      ),
///    );
///  }
///}
/// ```
/// {@end-tool}
class FontFeature {
  /// Creates a [FontFeature] object, which can be added to a [TextStyle] to
  /// change how the engine selects glyphs when rendering text.
  ///
  /// `feature` is the four-character tag that identifies the feature.
  /// These tags are specified by font formats such as OpenType.
  ///
  /// `value` is the value that the feature will be set to.  The behavior
  /// of the value depends on the specific feature.  Many features are
  /// flags whose value can be 1 (when enabled) or 0 (when disabled).
  ///
  /// See <https://docs.microsoft.com/en-us/typography/opentype/spec/featuretags>
  const FontFeature(this.feature, [ this.value = 1 ]) : assert(feature != null), assert(feature.length == 4), assert(value != null), assert(value >= 0);

  /// 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);

  /// Randomize the alternate forms used in text.
  ///
  /// 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.
  ///
  /// See also:
  ///
  ///  * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_pt#rand>
  const FontFeature.randomize() : feature = 'rand', value = 1;

  /// Select a stylistic set.
  ///
  /// Fonts may have up to 20 stylistic sets, numbered 1 through 20.
  ///
  /// See also:
  ///
  ///  * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_pt#ssxx>
  factory FontFeature.stylisticSet(int value) {
    assert(value >= 1);
    assert(value <= 20);
    return FontFeature('ss${value.toString().padLeft(2, "0")}');
  }

  /// Use the slashed 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].
  ///
  /// See also:
  ///
  ///  * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_uz#zero>
  const FontFeature.slashedZero() : feature = 'zero', value = 1;

  /// Use oldstyle figures.
  ///
  /// 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.
  ///
  /// This overrides [FontFeature.slashedZero].
  ///
  /// See also:
  ///
  ///  * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_ko#onum>
  const FontFeature.oldstyleFigures() : feature = 'onum', value = 1;

  /// Use proportional (varying width) figures.
  ///
  /// 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.
  ///
  /// See also:
  ///
  ///  * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_pt#pnum>
  const FontFeature.proportionalFigures() : feature = 'pnum', value = 1;

  /// Use tabular (monospace) figures.
  ///
  /// For fonts that have both proportional (varying width) and tabular figures,
  /// this enables the tabular figures.
  ///
  /// This is mutually exclusive with [FontFeature.proportionalFigures].
  ///
  /// The default behavior varies from font to font.
  ///
  /// See also:
  ///
  ///  * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_pt#tnum>
  const FontFeature.tabularFigures() : feature = 'tnum', value = 1;

  /// The tag that identifies the effect of this feature.  Must consist of 4
  /// ASCII characters (typically lowercase letters).
  ///
  /// See <https://docs.microsoft.com/en-us/typography/opentype/spec/featuretags>
  final String feature;

  /// The value assigned to this feature.
  ///
  /// Must be a positive integer.  Many features are Boolean values that accept
  /// values of either 0 (feature is disabled) or 1 (feature is enabled).
  final int value;

  static const int _kEncodedSize = 8;

  void _encode(ByteData byteData) {
    assert(feature.codeUnits.every((int c) => c >= 0x20 && c <= 0x7F));
    for (int i = 0; i < 4; i++) {
      byteData.setUint8(i, feature.codeUnitAt(i));
    }
    byteData.setInt32(4, value, _kFakeHostEndian);
  }

  @override
  bool operator ==(dynamic other) {
    if (identical(this, other))
      return true;
    if (other.runtimeType != runtimeType)
      return false;
    final FontFeature typedOther = other;
    return feature == typedOther.feature
           && value == typedOther.value;
  }

  @override
  int get hashCode => hashValues(feature, value);

  @override
  String toString() => 'FontFeature($feature, $value)';
}

/// Whether and how to align text horizontally.
// The order of this enum must match the order of the values in RenderStyleConstants.h's ETextAlign.
enum TextAlign {
  /// Align the text on the left edge of the container.
  left,

  /// Align the text on the right edge of the container.
  right,

  /// Align the text in the center of the container.
  center,

  /// Stretch lines of text that end with a soft line break to fill the width of
  /// the container.
  ///
  /// Lines that end with hard line breaks are aligned towards the [start] edge.
  justify,

  /// Align the text on the leading edge of the container.
  ///
  /// For left-to-right text ([TextDirection.ltr]), this is the left edge.
  ///
  /// For right-to-left text ([TextDirection.rtl]), this is the right edge.
  start,

  /// Align the text on the trailing edge of the container.
  ///
  /// For left-to-right text ([TextDirection.ltr]), this is the right edge.
  ///
  /// For right-to-left text ([TextDirection.rtl]), this is the left edge.
  end,
}

/// A horizontal line used for aligning text.
enum TextBaseline {
  /// The horizontal line used to align the bottom of glyphs for alphabetic characters.
  alphabetic,

  /// The horizontal line used to align ideographic characters.
  ideographic,
}

/// A linear decoration to draw near the text.
class TextDecoration {
  const TextDecoration._(this._mask);

  /// Creates a decoration that paints the union of all the given decorations.
  factory TextDecoration.combine(List<TextDecoration> decorations) {
    int mask = 0;
    for (TextDecoration decoration in decorations)
      mask |= decoration._mask;
    return TextDecoration._(mask);
  }

  final int _mask;

  /// Whether this decoration will paint at least as much decoration as the given decoration.
  bool contains(TextDecoration other) {
    return (_mask | other._mask) == _mask;
  }

  /// Do not draw a decoration
  static const TextDecoration none = TextDecoration._(0x0);

  /// Draw a line underneath each line of text
  static const TextDecoration underline = TextDecoration._(0x1);

  /// Draw a line above each line of text
  static const TextDecoration overline = TextDecoration._(0x2);

  /// Draw a line through each line of text
  static const TextDecoration lineThrough = TextDecoration._(0x4);

  @override
  bool operator ==(dynamic other) {
    if (other is! TextDecoration)
      return false;
    final TextDecoration typedOther = other;
    return _mask == typedOther._mask;
  }

  @override
  int get hashCode => _mask.hashCode;

  @override
  String toString() {
    if (_mask == 0)
      return 'TextDecoration.none';
    final List<String> values = <String>[];
    if (_mask & underline._mask != 0)
      values.add('underline');
    if (_mask & overline._mask != 0)
      values.add('overline');
    if (_mask & lineThrough._mask != 0)
      values.add('lineThrough');
    if (values.length == 1)
      return 'TextDecoration.${values[0]}';
    return 'TextDecoration.combine([${values.join(", ")}])';
  }
}

/// The style in which to draw a text decoration
enum TextDecorationStyle {
  /// Draw a solid line
  solid,

  /// Draw two lines
  double,

  /// Draw a dotted line
  dotted,

  /// Draw a dashed line
  dashed,

  /// Draw a sinusoidal line
  wavy
}

/// Determines if lists [a] and [b] are deep equivalent.
///
/// Returns true if the lists are both null, or if they are both non-null, have
/// the same length, and contain the same elements in the same order. Returns
/// false otherwise.
bool _listEquals<T>(List<T> a, List<T> b) {
  if (a == null)
    return b == null;
  if (b == null || a.length != b.length)
    return false;
  for (int index = 0; index < a.length; index += 1) {
    if (a[index] != b[index])
      return false;
  }
  return true;
}

// This encoding must match the C++ version of ParagraphBuilder::pushStyle.
//
// The encoded array buffer has 8 elements.
//
//  - Element 0: A bit field where the ith bit indicates whether the ith element
//    has a non-null value. Bits 8 to 12 indicate whether |fontFamily|,
//    |fontSize|, |letterSpacing|, |wordSpacing|, and |height| are non-null,
//    respectively. Bit 0 is unused.
//
//  - Element 1: The |color| in ARGB with 8 bits per channel.
//
//  - Element 2: A bit field indicating which text decorations are present in
//    the |textDecoration| list. The ith bit is set if there's a TextDecoration
//    with enum index i in the list.
//
//  - Element 3: The |decorationColor| in ARGB with 8 bits per channel.
//
//  - Element 4: The bit field of the |decorationStyle|.
//
//  - Element 5: The index of the |fontWeight|.
//
//  - Element 6: The enum index of the |fontStyle|.
//
//  - Element 7: The enum index of the |textBaseline|.
//
Int32List _encodeTextStyle(
  Color color,
  TextDecoration decoration,
  Color decorationColor,
  TextDecorationStyle decorationStyle,
  double decorationThickness,
  FontWeight fontWeight,
  FontStyle fontStyle,
  TextBaseline textBaseline,
  String fontFamily,
  List<String> fontFamilyFallback,
  double fontSize,
  double letterSpacing,
  double wordSpacing,
  double height,
  Locale locale,
  Paint background,
  Paint foreground,
  List<Shadow> shadows,
  List<FontFeature> fontFeatures,
) {
  final Int32List result = Int32List(8);
  if (color != null) {
    result[0] |= 1 << 1;
    result[1] = color.value;
  }
  if (decoration != null) {
    result[0] |= 1 << 2;
    result[2] = decoration._mask;
  }
  if (decorationColor != null) {
    result[0] |= 1 << 3;
    result[3] = decorationColor.value;
  }
  if (decorationStyle != null) {
    result[0] |= 1 << 4;
    result[4] = decorationStyle.index;
  }
  if (fontWeight != null) {
    result[0] |= 1 << 5;
    result[5] = fontWeight.index;
  }
  if (fontStyle != null) {
    result[0] |= 1 << 6;
    result[6] = fontStyle.index;
  }
  if (textBaseline != null) {
    result[0] |= 1 << 7;
    result[7] = textBaseline.index;
  }
  if (decorationThickness != null) {
    result[0] |= 1 << 8;
  }
  if (fontFamily != null || (fontFamilyFallback != null && fontFamilyFallback.isNotEmpty)) {
    result[0] |= 1 << 9;
    // Passed separately to native.
  }
  if (fontSize != null) {
    result[0] |= 1 << 10;
    // Passed separately to native.
  }
  if (letterSpacing != null) {
    result[0] |= 1 << 11;
    // Passed separately to native.
  }
  if (wordSpacing != null) {
    result[0] |= 1 << 12;
    // Passed separately to native.
  }
  if (height != null) {
    result[0] |= 1 << 13;
    // Passed separately to native.
  }
  if (locale != null) {
    result[0] |= 1 << 14;
    // Passed separately to native.
  }
  if (background != null) {
    result[0] |= 1 << 15;
    // Passed separately to native.
  }
  if (foreground != null) {
    result[0] |= 1 << 16;
    // Passed separately to native.
  }
  if (shadows != null) {
    result[0] |= 1 << 17;
    // Passed separately to native.
  }
  if (fontFeatures != null) {
    result[0] |= 1 << 18;
    // Passed separately to native.
  }
  return result;
}

/// An opaque object that determines the size, position, and rendering of text.
///
/// See also:
///
///  * [TextStyle](https://api.flutter.dev/flutter/painting/TextStyle-class.html), the class in the [painting] library.
///
class TextStyle {
  /// Creates a new TextStyle object.
  ///
  /// * `color`: The color to use when painting the text. If this is specified, `foreground` must be null.
  /// * `decoration`: The decorations to paint near the text (e.g., an underline).
  /// * `decorationColor`: The color in which to paint the text decorations.
  /// * `decorationStyle`: The style in which to paint the text decorations (e.g., dashed).
  /// * `decorationThickness`: The thickness of the decoration as a muliplier on the thickness specified by the font.
  /// * `fontWeight`: The typeface thickness to use when painting the text (e.g., bold).
  /// * `fontStyle`: The typeface variant to use when drawing the letters (e.g., italics).
  /// * `fontFamily`: The name of the font to use when painting the text (e.g., Roboto). If a `fontFamilyFallback` is
  ///   provided and `fontFamily` is not, then the first font family in `fontFamilyFallback` will take the position of
  ///   the preferred font family. When a higher priority font cannot be found or does not contain a glyph, a lower
  ///   priority font will be used.
  /// * `fontFamilyFallback`: An ordered list of the names of the fonts to fallback on when a glyph cannot
  ///   be found in a higher priority font. When the `fontFamily` is null, the first font family in this list
  ///   is used as the preferred font. Internally, the 'fontFamily` is concatenated to the front of this list.
  ///   When no font family is provided through 'fontFamilyFallback' (null or empty) or `fontFamily`, then the
  ///   platform default font will be used.
  /// * `fontSize`: The size of glyphs (in logical pixels) to use when painting the text.
  /// * `letterSpacing`: The amount of space (in logical pixels) to add between each letter.
  /// * `wordSpacing`: The amount of space (in logical pixels) to add at each sequence of white-space (i.e. between each word).
  /// * `textBaseline`: The common baseline that should be aligned between this text span and its parent text span, or, for the root text spans, with the line box.
  /// * `height`: The height of this text span, as a multiplier of the font size. Omitting `height` will allow the line height
  ///   to take the height as defined by the font, which may not be exactly the height of the fontSize.
  /// * `locale`: The locale used to select region-specific glyphs.
  /// * `background`: The paint drawn as a background for the text.
  /// * `foreground`: The paint used to draw the text. If this is specified, `color` must be null.
  /// * `fontFeatures`: The font features that should be applied to the text.
  TextStyle({
    Color color,
    TextDecoration decoration,
    Color decorationColor,
    TextDecorationStyle decorationStyle,
    double decorationThickness,
    FontWeight fontWeight,
    FontStyle fontStyle,
    TextBaseline textBaseline,
    String fontFamily,
    List<String> fontFamilyFallback,
    double fontSize,
    double letterSpacing,
    double wordSpacing,
    double height,
    Locale locale,
    Paint background,
    Paint foreground,
    List<Shadow> shadows,
    List<FontFeature> fontFeatures,
  }) : assert(color == null || foreground == null,
         'Cannot provide both a color and a foreground\n'
         'The color argument is just a shorthand for "foreground: Paint()..color = color".'
       ),
       _encoded = _encodeTextStyle(
         color,
         decoration,
         decorationColor,
         decorationStyle,
         decorationThickness,
         fontWeight,
         fontStyle,
         textBaseline,
         fontFamily,
         fontFamilyFallback,
         fontSize,
         letterSpacing,
         wordSpacing,
         height,
         locale,
         background,
         foreground,
         shadows,
         fontFeatures,
       ),
       _fontFamily = fontFamily ?? '',
       _fontFamilyFallback = fontFamilyFallback,
       _fontSize = fontSize,
       _letterSpacing = letterSpacing,
       _wordSpacing = wordSpacing,
       _height = height,
       _decorationThickness = decorationThickness,
       _locale = locale,
       _background = background,
       _foreground = foreground,
       _shadows = shadows,
       _fontFeatures = fontFeatures;

  final Int32List _encoded;
  final String _fontFamily;
  final List<String> _fontFamilyFallback;
  final double _fontSize;
  final double _letterSpacing;
  final double _wordSpacing;
  final double _height;
  final double _decorationThickness;
  final Locale _locale;
  final Paint _background;
  final Paint _foreground;
  final List<Shadow> _shadows;
  final List<FontFeature> _fontFeatures;

  @override
  bool operator ==(dynamic other) {
    if (identical(this, other))
      return true;
    if (other is! TextStyle)
      return false;
    final TextStyle typedOther = other;
    if (_fontFamily != typedOther._fontFamily ||
        _fontSize != typedOther._fontSize ||
        _letterSpacing != typedOther._letterSpacing ||
        _wordSpacing != typedOther._wordSpacing ||
        _height != typedOther._height ||
        _decorationThickness != typedOther._decorationThickness ||
        _locale != typedOther._locale ||
        _background != typedOther._background ||
        _foreground != typedOther._foreground)
     return false;
    for (int index = 0; index < _encoded.length; index += 1) {
      if (_encoded[index] != typedOther._encoded[index])
        return false;
    }
    if (!_listEquals<Shadow>(_shadows, typedOther._shadows))
      return false;
    if (!_listEquals<String>(_fontFamilyFallback, typedOther._fontFamilyFallback))
      return false;
    if (!_listEquals<FontFeature>(_fontFeatures, typedOther._fontFeatures))
      return false;
    return true;
  }

  @override
  int get hashCode => hashValues(hashList(_encoded), _fontFamily, _fontFamilyFallback, _fontSize, _letterSpacing, _wordSpacing, _height, _locale, _background, _foreground, hashList(_shadows), _decorationThickness, hashList(_fontFeatures));

  @override
  String toString() {
    return 'TextStyle('
             'color: ${              _encoded[0] & 0x00002 == 0x00002  ? Color(_encoded[1])                  : "unspecified"}, '
             'decoration: ${         _encoded[0] & 0x00004 == 0x00004  ? TextDecoration._(_encoded[2])       : "unspecified"}, '
             'decorationColor: ${    _encoded[0] & 0x00008 == 0x00008  ? Color(_encoded[3])                  : "unspecified"}, '
             'decorationStyle: ${    _encoded[0] & 0x00010 == 0x00010  ? TextDecorationStyle.values[_encoded[4]] : "unspecified"}, '
             // The decorationThickness is not in encoded order in order to keep it near the other decoration properties.
             'decorationThickness: ${_encoded[0] & 0x00100 == 0x00100  ? _decorationThickness                    : "unspecified"}, '
             'fontWeight: ${         _encoded[0] & 0x00020 == 0x00020  ? FontWeight.values[_encoded[5]]          : "unspecified"}, '
             'fontStyle: ${          _encoded[0] & 0x00040 == 0x00040  ? FontStyle.values[_encoded[6]]           : "unspecified"}, '
             'textBaseline: ${       _encoded[0] & 0x00080 == 0x00080  ? TextBaseline.values[_encoded[7]]        : "unspecified"}, '
             'fontFamily: ${         _encoded[0] & 0x00200 == 0x00200
                                     && _fontFamily != null            ? _fontFamily                             : "unspecified"}, '
             'fontFamilyFallback: ${ _encoded[0] & 0x00200 == 0x00200
                                     && _fontFamilyFallback != null
                                     && _fontFamilyFallback.isNotEmpty ? _fontFamilyFallback                     : "unspecified"}, '
             'fontSize: ${           _encoded[0] & 0x00400 == 0x00400  ? _fontSize                               : "unspecified"}, '
             'letterSpacing: ${      _encoded[0] & 0x00800 == 0x00800  ? "${_letterSpacing}x"                    : "unspecified"}, '
             'wordSpacing: ${        _encoded[0] & 0x01000 == 0x01000  ? "${_wordSpacing}x"                      : "unspecified"}, '
             'height: ${             _encoded[0] & 0x02000 == 0x02000  ? "${_height}x"                           : "unspecified"}, '
             'locale: ${             _encoded[0] & 0x04000 == 0x04000  ? _locale                                 : "unspecified"}, '
             'background: ${         _encoded[0] & 0x08000 == 0x08000  ? _background                             : "unspecified"}, '
             'foreground: ${         _encoded[0] & 0x10000 == 0x10000  ? _foreground                             : "unspecified"}, '
             'shadows: ${            _encoded[0] & 0x20000 == 0x20000  ? _shadows                                : "unspecified"}, '
             'fontFeatures: ${       _encoded[0] & 0x40000 == 0x40000  ? _fontFeatures                           : "unspecified"}'
           ')';
  }
}

// This encoding must match the C++ version ParagraphBuilder::build.
//
// The encoded array buffer has 6 elements.
//
//  - Element 0: A bit mask indicating which fields are non-null.
//    Bit 0 is unused. Bits 1-n are set if the corresponding index in the
//    encoded array is non-null.  The remaining bits represent fields that
//    are passed separately from the array.
//
//  - Element 1: The enum index of the |textAlign|.
//
//  - Element 2: The enum index of the |textDirection|.
//
//  - Element 3: The index of the |fontWeight|.
//
//  - Element 4: The enum index of the |fontStyle|.
//
//  - Element 5: The value of |maxLines|.
//
Int32List _encodeParagraphStyle(
  TextAlign textAlign,
  TextDirection textDirection,
  int maxLines,
  String fontFamily,
  double fontSize,
  double height,
  FontWeight fontWeight,
  FontStyle fontStyle,
  StrutStyle strutStyle,
  String ellipsis,
  Locale locale,
) {
  final Int32List result = Int32List(6); // also update paragraph_builder.cc
  if (textAlign != null) {
    result[0] |= 1 << 1;
    result[1] = textAlign.index;
  }
  if (textDirection != null) {
    result[0] |= 1 << 2;
    result[2] = textDirection.index;
  }
  if (fontWeight != null) {
    result[0] |= 1 << 3;
    result[3] = fontWeight.index;
  }
  if (fontStyle != null) {
    result[0] |= 1 << 4;
    result[4] = fontStyle.index;
  }
  if (maxLines != null) {
    result[0] |= 1 << 5;
    result[5] = maxLines;
  }
  if (fontFamily != null) {
    result[0] |= 1 << 6;
    // Passed separately to native.
  }
  if (fontSize != null) {
    result[0] |= 1 << 7;
    // Passed separately to native.
  }
  if (height != null) {
    result[0] |= 1 << 8;
    // Passed separately to native.
  }
  if (strutStyle != null) {
    result[0] |= 1 << 9;
    // Passed separately to native.
  }
  if (ellipsis != null) {
    result[0] |= 1 << 10;
    // Passed separately to native.
  }
  if (locale != null) {
    result[0] |= 1 << 11;
    // Passed separately to native.
  }
  return result;
}

/// An opaque object that determines the configuration used by
/// [ParagraphBuilder] to position lines within a [Paragraph] of text.
class ParagraphStyle {
  /// Creates a new ParagraphStyle object.
  ///
  /// * `textAlign`: The alignment of the text within the lines of the
  ///   paragraph. If the last line is ellipsized (see `ellipsis` below), the
  ///   alignment is applied to that line after it has been truncated but before
  ///   the ellipsis has been added.
   //   See: https://github.com/flutter/flutter/issues/9819
  ///
  /// * `textDirection`: The directionality of the text, left-to-right (e.g.
  ///   Norwegian) or right-to-left (e.g. Hebrew). This controls the overall
  ///   directionality of the paragraph, as well as the meaning of
  ///   [TextAlign.start] and [TextAlign.end] in the `textAlign` field.
  ///
  /// * `maxLines`: The maximum number of lines painted. Lines beyond this
  ///   number are silently dropped. For example, if `maxLines` is 1, then only
  ///   one line is rendered. If `maxLines` is null, but `ellipsis` is not null,
  ///   then lines after the first one that overflows the width constraints are
  ///   dropped. The width constraints are those set in the
  ///   [ParagraphConstraints] object passed to the [Paragraph.layout] method.
  ///
  /// * `fontFamily`: The name of the font family to apply when painting the text,
  ///   in the absence of a `textStyle` being attached to the span.
  ///
  /// * `fontSize`: The fallback size of glyphs (in logical pixels) to
  ///   use when painting the text. This is used when there is no [TextStyle].
  ///
  /// * `height`: The fallback height of the spans as a multiplier of the font
  ///   size. The fallback height is used when no height is provided through
  ///   [TextStyle.height]. Omitting `height` here and in [TextStyle] will allow
  ///   the line height to take the height as defined by the font, which may not
  ///   be exactly the height of the `fontSize`.
  ///
  /// * `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,
    FontWeight fontWeight,
    FontStyle fontStyle,
    StrutStyle strutStyle,
    String ellipsis,
    Locale locale,
  }) : _encoded = _encodeParagraphStyle(
         textAlign,
         textDirection,
         maxLines,
         fontFamily,
         fontSize,
         height,
         fontWeight,
         fontStyle,
         strutStyle,
         ellipsis,
         locale,
       ),
       _fontFamily = fontFamily,
       _fontSize = fontSize,
       _height = height,
       _strutStyle = strutStyle,
       _ellipsis = ellipsis,
       _locale = locale;

  final Int32List _encoded;
  final String _fontFamily;
  final double _fontSize;
  final double _height;
  final StrutStyle _strutStyle;
  final String _ellipsis;
  final Locale _locale;

  @override
  bool operator ==(dynamic other) {
    if (identical(this, other))
      return true;
    if (other.runtimeType != runtimeType)
      return false;
    final ParagraphStyle typedOther = other;
    if (_fontFamily != typedOther._fontFamily ||
        _fontSize != typedOther._fontSize ||
        _height != typedOther._height ||
        _strutStyle != typedOther._strutStyle ||
        _ellipsis != typedOther._ellipsis ||
        _locale != typedOther._locale)
     return false;
    for (int index = 0; index < _encoded.length; index += 1) {
      if (_encoded[index] != typedOther._encoded[index])
        return false;
    }
    return true;
  }

  @override
  int get hashCode => hashValues(hashList(_encoded), _fontFamily, _fontSize, _height, _ellipsis, _locale);

  @override
  String toString() {
    return 'ParagraphStyle('
             'textAlign: ${     _encoded[0] & 0x002 == 0x002 ? TextAlign.values[_encoded[1]]     : "unspecified"}, '
             'textDirection: ${ _encoded[0] & 0x004 == 0x004 ? TextDirection.values[_encoded[2]] : "unspecified"}, '
             'fontWeight: ${    _encoded[0] & 0x008 == 0x008 ? FontWeight.values[_encoded[3]]    : "unspecified"}, '
             'fontStyle: ${     _encoded[0] & 0x010 == 0x010 ? FontStyle.values[_encoded[4]]     : "unspecified"}, '
             'maxLines: ${      _encoded[0] & 0x020 == 0x020 ? _encoded[5]                       : "unspecified"}, '
             'fontFamily: ${    _encoded[0] & 0x040 == 0x040 ? _fontFamily                       : "unspecified"}, '
             'fontSize: ${      _encoded[0] & 0x080 == 0x080 ? _fontSize                         : "unspecified"}, '
             'height: ${        _encoded[0] & 0x100 == 0x100 ? "${_height}x"                     : "unspecified"}, '
             'ellipsis: ${      _encoded[0] & 0x200 == 0x200 ? "\"$_ellipsis\""                  : "unspecified"}, '
             'locale: ${        _encoded[0] & 0x400 == 0x400 ? _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.
//
// We serialize this more thoroughly than ParagraphStyle because it is
// much more likely that the strut is empty/null and we wish to add
// minimal overhead for non-strut cases.
ByteData _encodeStrut(
  String fontFamily,
  List<String> fontFamilyFallback,
  double fontSize,
  double height,
  double leading,
  FontWeight fontWeight,
  FontStyle fontStyle,
  bool forceStrutHeight) {
  if (fontFamily == null &&
    fontSize == null &&
    height == null &&
    leading == null &&
    fontWeight == null &&
    fontStyle == null &&
    forceStrutHeight == null)
    return ByteData(0);

  final ByteData data = ByteData(15); // Max size is 15 bytes
  int bitmask = 0; // 8 bit mask
  int byteCount = 1;
  if (fontWeight != null) {
    bitmask |= 1 << 0;
    data.setInt8(byteCount, fontWeight.index);
    byteCount += 1;
  }
  if (fontStyle != null) {
    bitmask |= 1 << 1;
    data.setInt8(byteCount, fontStyle.index);
    byteCount += 1;
  }
  if (fontFamily != null || (fontFamilyFallback != null && fontFamilyFallback.isNotEmpty)){
    bitmask |= 1 << 2;
    // passed separately to native
  }
  if (fontSize != null) {
    bitmask |= 1 << 3;
    data.setFloat32(byteCount, fontSize, _kFakeHostEndian);
    byteCount += 4;
  }
  if (height != null) {
    bitmask |= 1 << 4;
    data.setFloat32(byteCount, height, _kFakeHostEndian);
    byteCount += 4;
  }
  if (leading != null) {
    bitmask |= 1 << 5;
    data.setFloat32(byteCount, leading, _kFakeHostEndian);
    byteCount += 4;
  }
  if (forceStrutHeight != null) {
    bitmask |= 1 << 6;
    // We store this boolean directly in the bitmask since there is
    // extra space in the 16 bit int.
    bitmask |= (forceStrutHeight ? 1 : 0) << 7;
  }

  data.setInt8(0, bitmask);

  return ByteData.view(data.buffer, 0,  byteCount);
}

/// See also:
///
///  * [StrutStyle](https://api.flutter.dev/flutter/painting/StrutStyle-class.html), the class in the [painting] library.
///
class StrutStyle {
  /// Creates a new StrutStyle object.
  ///
  /// * `fontFamily`: The name of the font to use when painting the text (e.g.,
  ///   Roboto).
  ///
  /// * `fontFamilyFallback`: An ordered list of font family names that will be
  ///    searched for when the font in `fontFamily` cannot be found.
  ///
  /// * `fontSize`: The size of glyphs (in logical pixels) to use when painting
  ///   the text.
  ///
  /// * `height`: The minimum height of the line boxes, as a multiplier of the
  ///   font size. The lines of the paragraph will be at least
  ///   `(height + leading) * fontSize` tall when `fontSize` is not null. Omitting
  ///   `height` will allow the minimum line height to take the height as defined
  ///   by the font, which may not be exactly the height of the `fontSize`. When
  ///   `fontSize` is null, there is no minimum line height. Tall glyphs due to
  ///   baseline alignment or large [TextStyle.fontSize] may cause the actual line
  ///   height after layout to be taller than specified here. The `fontSize` must
  ///   be provided for this property to take effect.
  ///
  /// * `leading`: The minimum amount of leading between lines as a multiple of
  ///   the font size. `fontSize` must be provided for this property to take effect.
  ///
  /// * `fontWeight`: The typeface thickness to use when painting the text
  ///   (e.g., bold).
  ///
  /// * `fontStyle`: The typeface variant to use when drawing the letters (e.g.,
  ///   italics).
  ///
  /// * `forceStrutHeight`: When true, the paragraph will force all lines to be exactly
  ///   `(height + leading) * fontSize` tall from baseline to baseline.
  ///   [TextStyle] is no longer able to influence the line height, and any tall
  ///   glyphs may overlap with lines above. If a `fontFamily` is specified, the
  ///   total ascent of the first line will be the min of the `Ascent + half-leading`
  ///   of the `fontFamily` and `(height + leading) * fontSize`. Otherwise, it
  ///   will be determined by the Ascent + half-leading of the first text.
  StrutStyle({
    String fontFamily,
    List<String> fontFamilyFallback,
    double fontSize,
    double height,
    double leading,
    FontWeight fontWeight,
    FontStyle fontStyle,
    bool forceStrutHeight,
  }) : _encoded = _encodeStrut(
         fontFamily,
         fontFamilyFallback,
         fontSize,
         height,
         leading,
         fontWeight,
         fontStyle,
         forceStrutHeight,
       ),
       _fontFamily = fontFamily,
       _fontFamilyFallback = fontFamilyFallback;

  final ByteData _encoded; // Most of the data for strut is encoded.
  final String _fontFamily;
  final List<String> _fontFamilyFallback;


  @override
  bool operator ==(dynamic other) {
    if (identical(this, other))
      return true;
    if (other.runtimeType != runtimeType)
      return false;
    final StrutStyle typedOther = other;
    if (_fontFamily != typedOther._fontFamily)
     return false;
    final Int8List encodedList = _encoded.buffer.asInt8List();
    final Int8List otherEncodedList = typedOther._encoded.buffer.asInt8List();
    for (int index = 0; index < _encoded.lengthInBytes; index += 1) {
      if (encodedList[index] != otherEncodedList[index])
        return false;
    }
    if (!_listEquals<String>(_fontFamilyFallback, typedOther._fontFamilyFallback))
      return false;
    return true;
  }

  @override
  int get hashCode => hashValues(hashList(_encoded.buffer.asInt8List()), _fontFamily);

}

/// A direction in which text flows.
///
/// Some languages are written from the left to the right (for example, English,
/// Tamil, or Chinese), while others are written from the right to the left (for
/// example Aramaic, Hebrew, or Urdu). Some are also written in a mixture, for
/// example Arabic is mostly written right-to-left, with numerals written
/// left-to-right.
///
/// The text direction must be provided to APIs that render text or lay out
/// boxes horizontally, so that they can determine which direction to start in:
/// either right-to-left, [TextDirection.rtl]; or left-to-right,
/// [TextDirection.ltr].
///
/// ## Design discussion
///
/// Flutter is designed to address the needs of applications written in any of
/// the world's currently-used languages, whether they use a right-to-left or
/// left-to-right writing direction. Flutter does not support other writing
/// modes, such as vertical text or boustrophedon text, as these are rarely used
/// in computer programs.
///
/// It is common when developing user interface frameworks to pick a default
/// text direction — typically left-to-right, the direction most familiar to the
/// engineers working on the framework — because this simplifies the development
/// of applications on the platform. Unfortunately, this frequently results in
/// the platform having unexpected left-to-right biases or assumptions, as
/// engineers will typically miss places where they need to support
/// right-to-left text. This then results in bugs that only manifest in
/// right-to-left environments.
///
/// In an effort to minimize the extent to which Flutter experiences this
/// category of issues, the lowest levels of the Flutter framework do not have a
/// default text reading direction. Any time a reading direction is necessary,
/// for example when text is to be displayed, or when a
/// writing-direction-dependent value is to be interpreted, the reading
/// direction must be explicitly specified. Where possible, such as in `switch`
/// statements, the right-to-left case is listed first, to avoid the impression
/// that it is an afterthought.
///
/// At the higher levels (specifically starting at the widgets library), an
/// ambient [Directionality] is introduced, which provides a default. Thus, for
/// instance, a [Text] widget in the scope of a [MaterialApp] widget does not
/// need to be given an explicit writing direction. The [Directionality.of]
/// static method can be used to obtain the ambient text direction for a
/// particular [BuildContext].
///
/// ### Known left-to-right biases in Flutter
///
/// Despite the design intent described above, certain left-to-right biases have
/// nonetheless crept into Flutter's design. These include:
///
///  * The [Canvas] origin is at the top left, and the x-axis increases in a
///    left-to-right direction.
///
///  * The default localization in the widgets and material libraries is
///    American English, which is left-to-right.
///
/// ### Visual properties vs directional properties
///
/// Many classes in the Flutter framework are offered in two versions, a
/// visually-oriented variant, and a text-direction-dependent variant. For
/// example, [EdgeInsets] is described in terms of top, left, right, and bottom,
/// while [EdgeInsetsDirectional] is described in terms of top, start, end, and
/// bottom, where start and end correspond to right and left in right-to-left
/// text and left and right in left-to-right text.
///
/// There are distinct use cases for each of these variants.
///
/// Text-direction-dependent variants are useful when developing user interfaces
/// that should "flip" with the text direction. For example, a paragraph of text
/// in English will typically be left-aligned and a quote will be indented from
/// the left, while in Arabic it will be right-aligned and indented from the
/// right. Both of these cases are described by the direction-dependent
/// [TextAlign.start] and [EdgeInsetsDirectional.start].
///
/// In contrast, the visual variants are useful when the text direction is known
/// and not affected by the reading direction. For example, an application
/// giving driving directions might show a "turn left" arrow on the left and a
/// "turn right" arrow on the right — and would do so whether the application
/// was localized to French (left-to-right) or Hebrew (right-to-left).
///
/// In practice, it is also expected that many developers will only be
/// targeting one language, and in that case it may be simpler to think in
/// visual terms.
// The order of this enum must match the order of the values in TextDirection.h's TextDirection.
enum TextDirection {
  /// The text flows from right to left (e.g. Arabic, Hebrew).
  rtl,

  /// The text flows from left to right (e.g., English, French).
  ltr,
}

/// A rectangle enclosing a run of text.
///
/// This is similar to [Rect] but includes an inherent [TextDirection].
@pragma('vm:entry-point')
class TextBox {
  /// Creates an object that describes a box containing text.
  const TextBox.fromLTRBD(
    this.left,
    this.top,
    this.right,
    this.bottom,
    this.direction,
  );

  @pragma('vm:entry-point')
  TextBox._(
    this.left,
    this.top,
    this.right,
    this.bottom,
    int directionIndex,
  ) : direction = TextDirection.values[directionIndex];

  /// 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 ==(dynamic other) {
    if (identical(this, other))
      return true;
    if (other.runtimeType != runtimeType)
      return false;
    final TextBox typedOther = other;
    return typedOther.left == left
        && typedOther.top == top
        && typedOther.right == right
        && typedOther.bottom == bottom
        && typedOther.direction == direction;
  }

  @override
  int get hashCode => hashValues(left, top, right, bottom, direction);

  @override
  String toString() => 'TextBox.fromLTRBD(${left.toStringAsFixed(1)}, ${top.toStringAsFixed(1)}, ${right.toStringAsFixed(1)}, ${bottom.toStringAsFixed(1)}, $direction)';
}

/// A way to disambiguate a [TextPosition] when its offset could match two
/// different locations in the rendered string.
///
/// For example, at an offset where the rendered text wraps, there are two
/// visual positions that the offset could represent: one prior to the line
/// break (at the end of the first line) and one after the line break (at the
/// start of the second line). A text affinity disambiguates between these two
/// cases.
///
/// This affects only line breaks caused by wrapping, not explicit newline
/// characters. For newline characters, the position is fully specified by the
/// offset alone, and there is no ambiguity.
///
/// [TextAffinity] also affects bidirectional text at the interface between LTR
/// and RTL text. Consider the following string, where the lowercase letters
/// will be displayed as LTR and the uppercase letters RTL: "helloHELLO".  When
/// rendered, the string would appear visually as "helloOLLEH".  An offset of 5
/// would be ambiguous without a corresponding [TextAffinity].  Looking at the
/// string in code, the offset represents the position just after the "o" and
/// just before the "H".  When rendered, this offset could be either in the
/// middle of the string to the right of the "o" or at the end of the string to
/// the right of the "H".
enum TextAffinity {
  /// The position has affinity for the upstream side of the text position, i.e.
  /// in the direction of the beginning of the string.
  ///
  /// In the example of an offset at the place where text is wrapping, upstream
  /// indicates the end of the first line.
  ///
  /// In the bidirectional text example "helloHELLO", an offset of 5 with
  /// [TextAffinity] upstream would appear in the middle of the rendered text,
  /// just to the right of the "o". See the definition of [TextAffinity] for the
  /// full example.
  upstream,

  /// The position has affinity for the downstream side of the text position,
  /// i.e. in the direction of the end of the string.
  ///
  /// In the example of an offset at the place where text is wrapping,
  /// downstream indicates the beginning of the second line.
  ///
  /// In the bidirectional text example "helloHELLO", an offset of 5 with
  /// [TextAffinity] downstream would appear at the end of the rendered text,
  /// just to the right of the "H". See the definition of [TextAffinity] for the
  /// full example.
  downstream,
}

/// A position in a string of text.
///
/// A TextPosition can be used to locate a position in a string in code (using
/// the [offset] property), and it can also be used to locate the same position
/// visually in a rendered string of text (using [offset] and, when needed to
/// resolve ambiguity, [affinity]).
///
/// The location of an offset in a rendered string is ambiguous in two cases.
/// One happens when rendered text is forced to wrap. In this case, the offset
/// where the wrap occurs could visually appear either at the end of the first
/// line or the beginning of the second line. The second way is with
/// bidirectional text.  An offset at the interface between two different text
/// directions could have one of two locations in the rendered text.
///
/// See the documentation for [TextAffinity] for more information on how
/// TextAffinity disambiguates situations like these.
class TextPosition {
  /// Creates an object representing a particular position in a string.
  ///
  /// The arguments must not be null (so the [offset] argument is required).
  const TextPosition({
    this.offset,
    this.affinity = TextAffinity.downstream,
  }) : assert(offset != null),
       assert(affinity != null);

  /// The index of the character that immediately follows the position in the
  /// string representation of the text.
  ///
  /// For example, given the string `'Hello'`, offset 0 represents the cursor
  /// being before the `H`, while offset 5 represents the cursor being just
  /// after the `o`.
  final int offset;

  /// Disambiguates cases where the position in the string given by [offset]
  /// could represent two different visual positions in the rendered text. For
  /// example, this can happen when text is forced to wrap, or when one string
  /// of text is rendered with multiple text directions.
  ///
  /// See the documentation for [TextAffinity] for more information on how
  /// TextAffinity disambiguates situations like these.
  final TextAffinity affinity;

  @override
  bool operator ==(dynamic other) {
    if (other.runtimeType != runtimeType)
      return false;
    final TextPosition typedOther = other;
    return typedOther.offset == offset
        && typedOther.affinity == affinity;
  }

  @override
  int get hashCode => hashValues(offset, affinity);

  @override
  String toString() {
    return 'TextPosition(offset: $offset, affinity: $affinity)';
  }
}

/// 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({
    this.width,
  }) : assert(width != null);

  /// The width the paragraph should use whey computing the positions of glyphs.
  ///
  /// If possible, the paragraph will select a soft line break prior to reaching
  /// this width. If no soft line break is available, the paragraph will select
  /// a hard line break prior to reaching this width. If that would force a line
  /// break without any characters having been placed (i.e. if the next
  /// character to be laid out does not fit within the given width constraint)
  /// then the next character is allowed to overflow the width constraint and a
  /// forced line break is placed after it (even if an explicit line break
  /// follows).
  ///
  /// The width influences how ellipses are applied. See the discussion at
  /// [new ParagraphStyle] for more details.
  ///
  /// This width is also used to position glyphs according to the [TextAlign]
  /// alignment described in the [ParagraphStyle] used when building the
  /// [Paragraph] with a [ParagraphBuilder].
  final double width;

  @override
  bool operator ==(dynamic other) {
    if (other.runtimeType != runtimeType)
      return false;
    final ParagraphConstraints typedOther = other;
    return typedOther.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].
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 [RectHeightStyle.includeLineSpacingTop], [RectHeightStyle.includeLineSpacingMiddle],
  /// and [RectHeightStyle.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 flutter.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 flutter.dart:ui.rectHeightStyle.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 flutter.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].
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,
}

/// A paragraph of text.
///
/// A paragraph retains the size and position of each glyph in the text and can
/// be efficiently resized and painted.
///
/// To create a [Paragraph] object, use a [ParagraphBuilder].
///
/// Paragraphs can be displayed on a [Canvas] using the [Canvas.drawParagraph]
/// method.
@pragma('vm:entry-point')
class Paragraph extends NativeFieldWrapperClass2 {
  /// This class is created by the engine, and should not be instantiated
  /// or extended directly.
  ///
  /// To create a [Paragraph] object, use a [ParagraphBuilder].
  @pragma('vm:entry-point')
  Paragraph._();

  /// The amount of horizontal space this paragraph occupies.
  ///
  /// Valid only after [layout] has been called.
  double get width native 'Paragraph_width';

  /// The amount of vertical space this paragraph occupies.
  ///
  /// Valid only after [layout] has been called.
  double get height native 'Paragraph_height';

  /// The distance from the left edge of the leftmost glyph to the right edge of
  /// the rightmost glyph in the paragraph.
  ///
  /// Valid only after [layout] has been called.
  double get longestLine native 'Paragraph_longestLine';

  /// The minimum width that this paragraph could be without failing to paint
  /// its contents within itself.
  ///
  /// Valid only after [layout] has been called.
  double get minIntrinsicWidth native 'Paragraph_minIntrinsicWidth';

  /// Returns the smallest width beyond which increasing the width never
  /// decreases the height.
  ///
  /// Valid only after [layout] has been called.
  double get maxIntrinsicWidth native 'Paragraph_maxIntrinsicWidth';

  /// The distance from the top of the paragraph to the alphabetic
  /// baseline of the first line, in logical pixels.
  double get alphabeticBaseline native 'Paragraph_alphabeticBaseline';

  /// The distance from the top of the paragraph to the ideographic
  /// baseline of the first line, in logical pixels.
  double get ideographicBaseline native 'Paragraph_ideographicBaseline';

  /// True if there is more vertical content, but the text was truncated, either
  /// because we reached `maxLines` lines of text or because the `maxLines` was
  /// null, `ellipsis` was not null, and one of the lines exceeded the width
  /// constraint.
  ///
  /// See the discussion of the `maxLines` and `ellipsis` arguments at
  /// [new ParagraphStyle].
  bool get didExceedMaxLines native 'Paragraph_didExceedMaxLines';

  /// Computes the size and position of each glyph in the paragraph.
  ///
  /// The [ParagraphConstraints] control how wide the text is allowed to be.
  void layout(ParagraphConstraints constraints) => _layout(constraints.width);
  void _layout(double width) native 'Paragraph_layout';

  /// Returns a list of text boxes that enclose the given text range.
  ///
  /// The [boxHeightStyle] and [boxWidthStyle] parameters allow customization
  /// of how the boxes are bound vertically and horizontally. Both style
  /// parameters default to the tight option, which will provide close-fitting
  /// boxes and will not account for any line spacing.
  ///
  /// Coordinates of the TextBox are relative to the upper-left corner of the paragraph,
  /// where positive y values indicate down.
  ///
  /// The [boxHeightStyle] and [boxWidthStyle] parameters must not be null.
  ///
  /// See [BoxHeightStyle] and [BoxWidthStyle] for full descriptions of each option.
  List<TextBox> getBoxesForRange(int start, int end, {BoxHeightStyle boxHeightStyle = BoxHeightStyle.tight, BoxWidthStyle boxWidthStyle = BoxWidthStyle.tight}) {
    assert(boxHeightStyle != null);
    assert(boxWidthStyle != null);
    return _getBoxesForRange(start, end, boxHeightStyle.index, boxWidthStyle.index);
  }
  List<TextBox> _getBoxesForRange(int start, int end, int boxHeightStyle, int boxWidthStyle) native 'Paragraph_getRectsForRange';

  /// Returns a list of text boxes that enclose all placeholders in the paragraph.
  ///
  /// The order of the boxes are in the same order as passed in through [addPlaceholder].
  ///
  /// Coordinates of the [TextBox] are relative to the upper-left corner of the paragraph,
  /// where positive y values indicate down.
  List<TextBox> getBoxesForPlaceholders() native 'Paragraph_getRectsForPlaceholders';

  /// Returns the text position closest to the given offset.
  TextPosition getPositionForOffset(Offset offset) {
    final List<int> encoded = _getPositionForOffset(offset.dx, offset.dy);
    return TextPosition(offset: encoded[0], affinity: TextAffinity.values[encoded[1]]);
  }
  List<int> _getPositionForOffset(double dx, double dy) native 'Paragraph_getPositionForOffset';

  /// Returns the [start, end] of the word at the given offset. 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
  List<int> getWordBoundary(int offset) native 'Paragraph_getWordBoundary';

  // Redirecting the paint function in this way solves some dependency problems
  // in the C++ code. If we straighten out the C++ dependencies, we can remove
  // this indirection.
  void _paint(Canvas canvas, double x, double y) native 'Paragraph_paint';
}

/// Builds a [Paragraph] containing text with the given styling information.
///
/// To set the paragraph's alignment, truncation, and ellipsizing behavior, pass
/// an appropriately-configured [ParagraphStyle] object to the
/// [new ParagraphBuilder] constructor.
///
/// Then, call combinations of [pushStyle], [addText], and [pop] to add styled
/// text to the object.
///
/// Finally, call [build] to obtain the constructed [Paragraph] object. After
/// this point, the builder is no longer usable.
///
/// After constructing a [Paragraph], call [Paragraph.layout] on it and then
/// paint it with [Canvas.drawParagraph].
class ParagraphBuilder extends NativeFieldWrapperClass2 {

  /// Creates a new [ParagraphBuilder] object, which is used to create a
  /// [Paragraph].
  @pragma('vm:entry-point')
  ParagraphBuilder(ParagraphStyle style) {
    _placeholderCount = 0;
    List<String> strutFontFamilies;
    if (style._strutStyle != null) {
      strutFontFamilies = <String>[];
      if (style._strutStyle._fontFamily != null)
        strutFontFamilies.add(style._strutStyle._fontFamily);
      if (style._strutStyle._fontFamilyFallback != null)
        strutFontFamilies.addAll(style._strutStyle._fontFamilyFallback);
    }
    _constructor(
      style._encoded,
      style._strutStyle?._encoded,
      style._fontFamily,
      strutFontFamilies,
      style._fontSize,
      style._height,
      style._ellipsis,
      _encodeLocale(style._locale)
    );
  }

  void _constructor(
    Int32List encoded,
    ByteData strutData,
    String fontFamily,
    List<dynamic> strutFontFamily,
    double fontSize,
    double height,
    String ellipsis,
    String locale
  ) native 'ParagraphBuilder_constructor';

  /// The number of placeholders currently in the paragraph.
  int get placeholderCount => _placeholderCount;
  int _placeholderCount;

  /// The scales of the placeholders in the paragraph.
  List<double> get placeholderScales => _placeholderScales;
  List<double> _placeholderScales = <double>[];

  /// Applies the given style to the added text until [pop] is called.
  ///
  /// See [pop] for details.
  void pushStyle(TextStyle style) {
    final List<String> fullFontFamilies = <String>[];
    if (style._fontFamily != null)
      fullFontFamilies.add(style._fontFamily);
    if (style._fontFamilyFallback != null)
    fullFontFamilies.addAll(style._fontFamilyFallback);

    ByteData encodedFontFeatures;
    if (style._fontFeatures != null) {
      encodedFontFeatures = ByteData(style._fontFeatures.length * FontFeature._kEncodedSize);
      int byteOffset = 0;
      for (FontFeature feature in style._fontFeatures) {
        feature._encode(ByteData.view(encodedFontFeatures.buffer, byteOffset, FontFeature._kEncodedSize));
        byteOffset += FontFeature._kEncodedSize;
      }
    }

    _pushStyle(
      style._encoded,
      fullFontFamilies,
      style._fontSize,
      style._letterSpacing,
      style._wordSpacing,
      style._height,
      style._decorationThickness,
      _encodeLocale(style._locale),
      style._background?._objects,
      style._background?._data,
      style._foreground?._objects,
      style._foreground?._data,
      Shadow._encodeShadows(style._shadows),
      encodedFontFeatures,
    );
  }

  void _pushStyle(
    Int32List encoded,
    List<dynamic> fontFamilies,
    double fontSize,
    double letterSpacing,
    double wordSpacing,
    double height,
    double decorationThickness,
    String locale,
    List<dynamic> backgroundObjects,
    ByteData backgroundData,
    List<dynamic> foregroundObjects,
    ByteData foregroundData,
    ByteData shadowsData,
    ByteData fontFeaturesData,
  ) native 'ParagraphBuilder_pushStyle';

  static String _encodeLocale(Locale locale) => locale?.toString() ?? '';

  /// Ends the effect of the most recent call to [pushStyle].
  ///
  /// Internally, the paragraph builder maintains a stack of text styles. Text
  /// added to the paragraph is affected by all the styles in the stack. Calling
  /// [pop] removes the topmost style in the stack, leaving the remaining styles
  /// in effect.
  void pop() native 'ParagraphBuilder_pop';

  /// Adds the given text to the paragraph.
  ///
  /// The text will be styled according to the current stack of text styles.
  void addText(String text) {
    final String error = _addText(text);
    if (error != null)
      throw ArgumentError(error);
  }
  String _addText(String text) native 'ParagraphBuilder_addText';

  /// Adds an inline placeholder space to the paragraph.
  ///
  /// The paragraph will contain a rectangular space with no text of the dimensions
  /// specified.
  ///
  /// The `width` and `height` parameters specify the size of the placeholder rectangle.
  ///
  /// The `alignment` parameter specifies how the placeholder rectangle will be vertically
  /// aligned with the surrounding text. When [PlaceholderAlignment.baseline],
  /// [PlaceholderAlignment.aboveBaseline], and [PlaceholderAlignment.belowBaseline]
  /// alignment modes are used, the baseline needs to be set with the `baseline`.
  /// When using [PlaceholderAlignment.baseline], `baselineOffset` indicates the distance
  /// of the baseline down from the top of of the rectangle. The default `baselineOffset`
  /// is the `height`.
  ///
  /// Examples:
  ///
  /// * For a 30x50 placeholder with the bottom edge aligned with the bottom of the text, use:
  /// `addPlaceholder(30, 50, PlaceholderAlignment.bottom);`
  /// * For a 30x50 placeholder that is vertically centered around the text, use:
  /// `addPlaceholder(30, 50, PlaceholderAlignment.middle);`.
  /// * For a 30x50 placeholder that sits completely on top of the alphabetic baseline, use:
  /// `addPlaceholder(30, 50, PlaceholderAlignment.aboveBaseline, baseline: TextBaseline.alphabetic)`.
  /// * For a 30x50 placeholder with 40 pixels above and 10 pixels below the alphabetic baseline, use:
  /// `addPlaceholder(30, 50, PlaceholderAlignment.baseline, baseline: TextBaseline.alphabetic, baselineOffset: 40)`.
  ///
  /// Lines are permitted to break around each placeholder.
  ///
  /// Decorations will be drawn based on the font defined in the most recently
  /// pushed [TextStyle]. The decorations are drawn as if unicode text were present
  /// in the placeholder space, and will draw the same regardless of the height and
  /// alignment of the placeholder. To hide or manually adjust decorations to fit,
  /// a text style with the desired decoration behavior should be pushed before
  /// adding a placeholder.
  ///
  /// Any decorations drawn through a placeholder will exist on the same canvas/layer
  /// as the text. This means any content drawn on top of the space reserved by
  /// the placeholder will be drawn over the decoration, possibly obscuring the
  /// decoration.
  ///
  /// Placeholders are represented by a unicode 0xFFFC "object replacement character"
  /// in the text buffer. For each placeholder, one object replacement character is
  /// added on to the text buffer.
  ///
  /// The `scale` parameter will scale the `width` and `height` by the specified amount,
  /// and keep track of the scale. The scales of placeholders added can be accessed
  /// through [placeholderScales]. This is primarily used for acessibility scaling.
  void addPlaceholder(double width, double height, PlaceholderAlignment alignment, {
    double scale = 1.0,
    double baselineOffset,
    TextBaseline baseline,
  }) {
    // Require a baseline to be specified if using a baseline-based alignment.
    assert((alignment == PlaceholderAlignment.aboveBaseline ||
            alignment == PlaceholderAlignment.belowBaseline ||
            alignment == PlaceholderAlignment.baseline) ? baseline != null : true);
    // 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 == null ? height : baselineOffset) * scale, baseline == null ? null : baseline.index);
    _placeholderCount++;
    _placeholderScales.add(scale);
  }
  String _addPlaceholder(double width, double height, int alignment, double baselineOffset, int baseline) native 'ParagraphBuilder_addPlaceholder';

  /// Applies the given paragraph style and returns a [Paragraph] containing the
  /// added text and associated styling.
  ///
  /// After calling this function, the paragraph builder object is invalid and
  /// cannot be used further.
  Paragraph build() native 'ParagraphBuilder_build';
}

/// Loads a font from a buffer and makes it available for rendering text.
///
/// * `list`: A list of bytes containing the font file.
/// * `fontFamily`: The family name used to identify the font in text styles.
///  If this is not provided, then the family name will be extracted from the font file.
Future<void> loadFontFromList(Uint8List list, {String fontFamily}) {
  return _futurize(
    (_Callback<void> callback) => _loadFontFromList(list, callback, fontFamily)
  );
}

String _loadFontFromList(Uint8List list, _Callback<void> callback, String fontFamily) native 'loadFontFromList';