Unverified Commit 8c009aa6 authored by LongCatIsLooong's avatar LongCatIsLooong Committed by GitHub

Add TextLeadingDistribution to TextStyle (#77001)

parent 9f47adc4
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
/// painting boxes. /// painting boxes.
library painting; library painting;
export 'dart:ui' show Shadow, PlaceholderAlignment, TextHeightBehavior; export 'dart:ui' show Shadow, PlaceholderAlignment, TextHeightBehavior, TextLeadingDistribution;
export 'src/painting/alignment.dart'; export 'src/painting/alignment.dart';
export 'src/painting/basic_types.dart'; export 'src/painting/basic_types.dart';
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:ui' show TextLeadingDistribution;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
...@@ -300,6 +301,7 @@ class StrutStyle with Diagnosticable { ...@@ -300,6 +301,7 @@ class StrutStyle with Diagnosticable {
List<String>? fontFamilyFallback, List<String>? fontFamilyFallback,
this.fontSize, this.fontSize,
this.height, this.height,
this.leadingDistribution,
this.leading, this.leading,
this.fontWeight, this.fontWeight,
this.fontStyle, this.fontStyle,
...@@ -338,6 +340,7 @@ class StrutStyle with Diagnosticable { ...@@ -338,6 +340,7 @@ class StrutStyle with Diagnosticable {
List<String>? fontFamilyFallback, List<String>? fontFamilyFallback,
double? fontSize, double? fontSize,
double? height, double? height,
TextLeadingDistribution? leadingDistribution,
this.leading, // TextStyle does not have an equivalent (yet). this.leading, // TextStyle does not have an equivalent (yet).
FontWeight? fontWeight, FontWeight? fontWeight,
FontStyle? fontStyle, FontStyle? fontStyle,
...@@ -351,6 +354,7 @@ class StrutStyle with Diagnosticable { ...@@ -351,6 +354,7 @@ class StrutStyle with Diagnosticable {
fontFamily = fontFamily != null ? (package == null ? fontFamily : 'packages/$package/$fontFamily') : textStyle.fontFamily, fontFamily = fontFamily != null ? (package == null ? fontFamily : 'packages/$package/$fontFamily') : textStyle.fontFamily,
_fontFamilyFallback = fontFamilyFallback ?? textStyle.fontFamilyFallback, _fontFamilyFallback = fontFamilyFallback ?? textStyle.fontFamilyFallback,
height = height ?? textStyle.height, height = height ?? textStyle.height,
leadingDistribution = leadingDistribution ?? textStyle.leadingDistribution,
fontSize = fontSize ?? textStyle.fontSize, fontSize = fontSize ?? textStyle.fontSize,
fontWeight = fontWeight ?? textStyle.fontWeight, fontWeight = fontWeight ?? textStyle.fontWeight,
fontStyle = fontStyle ?? textStyle.fontStyle, fontStyle = fontStyle ?? textStyle.fontStyle,
...@@ -420,26 +424,21 @@ class StrutStyle with Diagnosticable { ...@@ -420,26 +424,21 @@ class StrutStyle with Diagnosticable {
/// The default fontSize is 14 logical pixels. /// The default fontSize is 14 logical pixels.
final double? fontSize; final double? fontSize;
/// The multiple of [fontSize] to multiply the ascent and descent by where /// The minimum height of the strut, as a multiple of [fontSize].
/// `ascent + descent = fontSize`.
/// ///
/// Ascent is the spacing above the baseline and descent is the spacing below /// When [height] is omitted or null, then the strut's height will be the sum
/// the baseline. /// of the strut's font-defined ascent, its font-defined descent, and its
/// [leading]. The font's combined ascent and descent may be taller or shorter
/// than the [fontSize].
/// ///
/// When [height] is omitted or null, then the font defined ascent and descent /// When [height] is provided, the line's EM-square ascent and descent (which
/// will be used. The font's combined ascent and descent may be taller or /// sums to [fontSize]) will be scaled by [height] to achieve a final strut
/// shorter than the [fontSize]. When [height] is provided, the line's EM-square /// height of `fontSize * height + fontSize * leading` logical pixels. The
/// ascent and descent (which sums to [fontSize]) will be scaled by [height] to /// following diagram illustrates the differences between the font metrics
/// achieve a final line height of `fontSize * height + fontSize * leading` /// defined height and the EM-square height:
/// logical pixels. The following diagram illustrates the differences between
/// the font metrics defined height and the EM-square height:
/// ///
/// ![Text height diagram](https://flutter.github.io/assets-for-api-docs/assets/painting/text_height_diagram.png) /// ![Text height diagram](https://flutter.github.io/assets-for-api-docs/assets/painting/text_height_diagram.png)
/// ///
/// The [height] will impact the spacing above and below the baseline differently
/// depending on the ratios between the font's ascent and descent. This property is
/// separate from the leading multiplier, which is controlled through [leading].
///
/// The ratio of ascent:descent with [height] specified is the same as the /// The ratio of ascent:descent with [height] specified is the same as the
/// font metrics defined ascent:descent ratio when [height] is null or omitted. /// font metrics defined ascent:descent ratio when [height] is null or omitted.
/// ///
...@@ -448,6 +447,22 @@ class StrutStyle with Diagnosticable { ...@@ -448,6 +447,22 @@ class StrutStyle with Diagnosticable {
/// The default height is null. /// The default height is null.
final double? height; final double? height;
/// How the vertical space added by the [height] multiplier should be
/// distributed over and under the strut.
///
/// When a non-null [height] is specified, after accommodating the imaginary
/// strut glyph, the remaining vertical space from the allotted
/// `fontSize * height` logical pixels will be distributed over and under the
/// strut, according to the [leadingDistribution] property.
///
/// The additional leading introduced by the [leading] property applies
/// independently of [leadingDistribution]: it will always be distributed
/// evenly over and under the strut, regardless of [leadingDistribution].
///
/// Defaults to null, which defers to the paragraph's
/// `ParagraphStyle.textHeightBehavior`'s `leadingDistribution`.
final TextLeadingDistribution? leadingDistribution;
/// The typeface thickness to use when calculating the strut (e.g., bold). /// The typeface thickness to use when calculating the strut (e.g., bold).
/// ///
/// The default fontWeight is [FontWeight.w400]. /// The default fontWeight is [FontWeight.w400].
...@@ -458,12 +473,13 @@ class StrutStyle with Diagnosticable { ...@@ -458,12 +473,13 @@ class StrutStyle with Diagnosticable {
/// The default fontStyle is [FontStyle.normal]. /// The default fontStyle is [FontStyle.normal].
final FontStyle? fontStyle; final FontStyle? fontStyle;
/// The custom leading to apply to the strut as a multiple of [fontSize]. /// The additional leading to apply to the strut as a multiple of [fontSize],
/// indepdent of [height] and [leadingDistribution].
/// ///
/// Leading is additional spacing between lines. Half of the leading is added /// Leading is additional spacing between lines. Half of the leading is added
/// to the top and the other half to the bottom of the line. This differs /// to the top and the other half to the bottom of the line. This differs
/// from [height] since the spacing is equally distributed above and below the /// from [height] since the spacing is always equally distributed above and
/// baseline. /// below the baseline, regardless of [leadingDistribution].
/// ///
/// The default leading is null, which will use the font-specified leading. /// The default leading is null, which will use the font-specified leading.
final double? leading; final double? leading;
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:ui' as ui show ParagraphStyle, TextStyle, StrutStyle, lerpDouble, Shadow, FontFeature, TextHeightBehavior; import 'dart:ui' as ui show ParagraphStyle, TextStyle, StrutStyle, lerpDouble, Shadow, FontFeature, TextHeightBehavior, TextLeadingDistribution;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
...@@ -425,6 +425,7 @@ class TextStyle with Diagnosticable { ...@@ -425,6 +425,7 @@ class TextStyle with Diagnosticable {
this.wordSpacing, this.wordSpacing,
this.textBaseline, this.textBaseline,
this.height, this.height,
this.leadingDistribution,
this.locale, this.locale,
this.foreground, this.foreground,
this.background, this.background,
...@@ -567,9 +568,25 @@ class TextStyle with Diagnosticable { ...@@ -567,9 +568,25 @@ class TextStyle with Diagnosticable {
/// ///
/// ![Since the explicit line height is applied as a scale factor on the font-metrics-defined line height, the gap above the text grows faster, as the height grows, than the gap below the text.](https://flutter.github.io/assets-for-api-docs/assets/painting/text_height_comparison_diagram.png) /// ![Since the explicit line height is applied as a scale factor on the font-metrics-defined line height, the gap above the text grows faster, as the height grows, than the gap below the text.](https://flutter.github.io/assets-for-api-docs/assets/painting/text_height_comparison_diagram.png)
/// ///
/// See [StrutStyle] for further control of line height at the paragraph level. /// See [StrutStyle] and [TextHeightBehavior] for further control of line
/// height at the paragraph level.
final double? height; final double? height;
/// How the vertical space added by the [height] multiplier should be
/// distributed over and under the text.
///
/// When a non-null [height] is specified, after accommodating the glyphs of
/// the text, the remaining vertical space from the allotted line height will
/// be distributed over and under the text, according to the
/// [leadingDistribution] property.
///
/// When [height] is null, [leadingDistribution] does not affect the text
/// layout.
///
/// Defaults to null, which defers to the paragraph's
/// `ParagraphStyle.textHeightBehavior`'s `leadingDistribution`.
final ui.TextLeadingDistribution? leadingDistribution;
/// The locale used to select region-specific glyphs. /// The locale used to select region-specific glyphs.
/// ///
/// This property is rarely set. Typically the locale used to select /// This property is rarely set. Typically the locale used to select
...@@ -721,6 +738,7 @@ class TextStyle with Diagnosticable { ...@@ -721,6 +738,7 @@ class TextStyle with Diagnosticable {
double? wordSpacing, double? wordSpacing,
TextBaseline? textBaseline, TextBaseline? textBaseline,
double? height, double? height,
ui.TextLeadingDistribution? leadingDistribution,
Locale? locale, Locale? locale,
Paint? foreground, Paint? foreground,
Paint? background, Paint? background,
...@@ -753,6 +771,7 @@ class TextStyle with Diagnosticable { ...@@ -753,6 +771,7 @@ class TextStyle with Diagnosticable {
wordSpacing: wordSpacing ?? this.wordSpacing, wordSpacing: wordSpacing ?? this.wordSpacing,
textBaseline: textBaseline ?? this.textBaseline, textBaseline: textBaseline ?? this.textBaseline,
height: height ?? this.height, height: height ?? this.height,
leadingDistribution: leadingDistribution ?? this.leadingDistribution,
locale: locale ?? this.locale, locale: locale ?? this.locale,
foreground: foreground ?? this.foreground, foreground: foreground ?? this.foreground,
background: background ?? this.background, background: background ?? this.background,
...@@ -816,6 +835,7 @@ class TextStyle with Diagnosticable { ...@@ -816,6 +835,7 @@ class TextStyle with Diagnosticable {
double heightFactor = 1.0, double heightFactor = 1.0,
double heightDelta = 0.0, double heightDelta = 0.0,
TextBaseline? textBaseline, TextBaseline? textBaseline,
ui.TextLeadingDistribution? leadingDistribution,
Locale? locale, Locale? locale,
List<ui.Shadow>? shadows, List<ui.Shadow>? shadows,
List<ui.FontFeature>? fontFeatures, List<ui.FontFeature>? fontFeatures,
...@@ -857,6 +877,7 @@ class TextStyle with Diagnosticable { ...@@ -857,6 +877,7 @@ class TextStyle with Diagnosticable {
wordSpacing: wordSpacing == null ? null : wordSpacing! * wordSpacingFactor + wordSpacingDelta, wordSpacing: wordSpacing == null ? null : wordSpacing! * wordSpacingFactor + wordSpacingDelta,
textBaseline: textBaseline ?? this.textBaseline, textBaseline: textBaseline ?? this.textBaseline,
height: height == null ? null : height! * heightFactor + heightDelta, height: height == null ? null : height! * heightFactor + heightDelta,
leadingDistribution: leadingDistribution ?? this.leadingDistribution,
locale: locale ?? this.locale, locale: locale ?? this.locale,
foreground: foreground, foreground: foreground,
background: background, background: background,
...@@ -916,6 +937,7 @@ class TextStyle with Diagnosticable { ...@@ -916,6 +937,7 @@ class TextStyle with Diagnosticable {
wordSpacing: other.wordSpacing, wordSpacing: other.wordSpacing,
textBaseline: other.textBaseline, textBaseline: other.textBaseline,
height: other.height, height: other.height,
leadingDistribution: other.leadingDistribution,
locale: other.locale, locale: other.locale,
foreground: other.foreground, foreground: other.foreground,
background: other.background, background: other.background,
...@@ -969,6 +991,7 @@ class TextStyle with Diagnosticable { ...@@ -969,6 +991,7 @@ class TextStyle with Diagnosticable {
wordSpacing: t < 0.5 ? null : b.wordSpacing, wordSpacing: t < 0.5 ? null : b.wordSpacing,
textBaseline: t < 0.5 ? null : b.textBaseline, textBaseline: t < 0.5 ? null : b.textBaseline,
height: t < 0.5 ? null : b.height, height: t < 0.5 ? null : b.height,
leadingDistribution: t < 0.5 ? null : b.leadingDistribution,
locale: t < 0.5 ? null : b.locale, locale: t < 0.5 ? null : b.locale,
foreground: t < 0.5 ? null : b.foreground, foreground: t < 0.5 ? null : b.foreground,
background: t < 0.5 ? null : b.background, background: t < 0.5 ? null : b.background,
...@@ -996,6 +1019,7 @@ class TextStyle with Diagnosticable { ...@@ -996,6 +1019,7 @@ class TextStyle with Diagnosticable {
wordSpacing: t < 0.5 ? a.wordSpacing : null, wordSpacing: t < 0.5 ? a.wordSpacing : null,
textBaseline: t < 0.5 ? a.textBaseline : null, textBaseline: t < 0.5 ? a.textBaseline : null,
height: t < 0.5 ? a.height : null, height: t < 0.5 ? a.height : null,
leadingDistribution: t < 0.5 ? a.leadingDistribution : null,
locale: t < 0.5 ? a.locale : null, locale: t < 0.5 ? a.locale : null,
foreground: t < 0.5 ? a.foreground : null, foreground: t < 0.5 ? a.foreground : null,
background: t < 0.5 ? a.background : null, background: t < 0.5 ? a.background : null,
...@@ -1022,6 +1046,7 @@ class TextStyle with Diagnosticable { ...@@ -1022,6 +1046,7 @@ class TextStyle with Diagnosticable {
wordSpacing: ui.lerpDouble(a.wordSpacing ?? b.wordSpacing, b.wordSpacing ?? a.wordSpacing, t), wordSpacing: ui.lerpDouble(a.wordSpacing ?? b.wordSpacing, b.wordSpacing ?? a.wordSpacing, t),
textBaseline: t < 0.5 ? a.textBaseline : b.textBaseline, textBaseline: t < 0.5 ? a.textBaseline : b.textBaseline,
height: ui.lerpDouble(a.height ?? b.height, b.height ?? a.height, t), height: ui.lerpDouble(a.height ?? b.height, b.height ?? a.height, t),
leadingDistribution: t < 0.5 ? a.leadingDistribution : b.leadingDistribution,
locale: t < 0.5 ? a.locale : b.locale, locale: t < 0.5 ? a.locale : b.locale,
foreground: (a.foreground != null || b.foreground != null) foreground: (a.foreground != null || b.foreground != null)
? t < 0.5 ? t < 0.5
...@@ -1054,6 +1079,7 @@ class TextStyle with Diagnosticable { ...@@ -1054,6 +1079,7 @@ class TextStyle with Diagnosticable {
fontWeight: fontWeight, fontWeight: fontWeight,
fontStyle: fontStyle, fontStyle: fontStyle,
textBaseline: textBaseline, textBaseline: textBaseline,
leadingDistribution: leadingDistribution,
fontFamily: fontFamily, fontFamily: fontFamily,
fontFamilyFallback: fontFamilyFallback, fontFamilyFallback: fontFamilyFallback,
fontSize: fontSize == null ? null : fontSize! * textScaleFactor, fontSize: fontSize == null ? null : fontSize! * textScaleFactor,
...@@ -1096,6 +1122,9 @@ class TextStyle with Diagnosticable { ...@@ -1096,6 +1122,9 @@ class TextStyle with Diagnosticable {
}) { }) {
assert(textScaleFactor != null); assert(textScaleFactor != null);
assert(maxLines == null || maxLines > 0); assert(maxLines == null || maxLines > 0);
final ui.TextLeadingDistribution? leadingDistribution = this.leadingDistribution;
final ui.TextHeightBehavior? effectiveTextHeightBehavior = textHeightBehavior
?? (leadingDistribution == null ? null : ui.TextHeightBehavior(leadingDistribution: leadingDistribution));
return ui.ParagraphStyle( return ui.ParagraphStyle(
textAlign: textAlign, textAlign: textAlign,
textDirection: textDirection, textDirection: textDirection,
...@@ -1106,7 +1135,7 @@ class TextStyle with Diagnosticable { ...@@ -1106,7 +1135,7 @@ class TextStyle with Diagnosticable {
fontFamily: fontFamily ?? this.fontFamily, fontFamily: fontFamily ?? this.fontFamily,
fontSize: (fontSize ?? this.fontSize ?? _kDefaultFontSize) * textScaleFactor, fontSize: (fontSize ?? this.fontSize ?? _kDefaultFontSize) * textScaleFactor,
height: height ?? this.height, height: height ?? this.height,
textHeightBehavior: textHeightBehavior, textHeightBehavior: effectiveTextHeightBehavior,
strutStyle: strutStyle == null ? null : ui.StrutStyle( strutStyle: strutStyle == null ? null : ui.StrutStyle(
fontFamily: strutStyle.fontFamily, fontFamily: strutStyle.fontFamily,
fontFamilyFallback: strutStyle.fontFamilyFallback, fontFamilyFallback: strutStyle.fontFamilyFallback,
...@@ -1141,6 +1170,7 @@ class TextStyle with Diagnosticable { ...@@ -1141,6 +1170,7 @@ class TextStyle with Diagnosticable {
wordSpacing != other.wordSpacing || wordSpacing != other.wordSpacing ||
textBaseline != other.textBaseline || textBaseline != other.textBaseline ||
height != other.height || height != other.height ||
leadingDistribution != other.leadingDistribution ||
locale != other.locale || locale != other.locale ||
foreground != other.foreground || foreground != other.foreground ||
background != other.background || background != other.background ||
...@@ -1176,6 +1206,7 @@ class TextStyle with Diagnosticable { ...@@ -1176,6 +1206,7 @@ class TextStyle with Diagnosticable {
&& other.wordSpacing == wordSpacing && other.wordSpacing == wordSpacing
&& other.textBaseline == textBaseline && other.textBaseline == textBaseline
&& other.height == height && other.height == height
&& other.leadingDistribution == leadingDistribution
&& other.locale == locale && other.locale == locale
&& other.foreground == foreground && other.foreground == foreground
&& other.background == background && other.background == background
...@@ -1190,7 +1221,7 @@ class TextStyle with Diagnosticable { ...@@ -1190,7 +1221,7 @@ class TextStyle with Diagnosticable {
@override @override
int get hashCode { int get hashCode {
return hashValues( return hashList(<Object?>[
inherit, inherit,
color, color,
backgroundColor, backgroundColor,
...@@ -1202,6 +1233,7 @@ class TextStyle with Diagnosticable { ...@@ -1202,6 +1233,7 @@ class TextStyle with Diagnosticable {
wordSpacing, wordSpacing,
textBaseline, textBaseline,
height, height,
leadingDistribution,
locale, locale,
foreground, foreground,
background, background,
...@@ -1211,7 +1243,7 @@ class TextStyle with Diagnosticable { ...@@ -1211,7 +1243,7 @@ class TextStyle with Diagnosticable {
hashList(shadows), hashList(shadows),
hashList(fontFeatures), hashList(fontFeatures),
hashList(fontFamilyFallback), hashList(fontFamilyFallback),
); ]);
} }
@override @override
...@@ -1248,6 +1280,7 @@ class TextStyle with Diagnosticable { ...@@ -1248,6 +1280,7 @@ class TextStyle with Diagnosticable {
styles.add(DoubleProperty('${prefix}wordSpacing', wordSpacing, defaultValue: null)); styles.add(DoubleProperty('${prefix}wordSpacing', wordSpacing, defaultValue: null));
styles.add(EnumProperty<TextBaseline>('${prefix}baseline', textBaseline, defaultValue: null)); styles.add(EnumProperty<TextBaseline>('${prefix}baseline', textBaseline, defaultValue: null));
styles.add(DoubleProperty('${prefix}height', height, unit: 'x', defaultValue: null)); styles.add(DoubleProperty('${prefix}height', height, unit: 'x', defaultValue: null));
styles.add(EnumProperty<ui.TextLeadingDistribution>('${prefix}leadingDistribution', leadingDistribution, defaultValue: null));
styles.add(DiagnosticsProperty<Locale>('${prefix}locale', locale, defaultValue: null)); styles.add(DiagnosticsProperty<Locale>('${prefix}locale', locale, defaultValue: null));
styles.add(DiagnosticsProperty<Paint>('${prefix}foreground', foreground, defaultValue: null)); styles.add(DiagnosticsProperty<Paint>('${prefix}foreground', foreground, defaultValue: null));
styles.add(DiagnosticsProperty<Paint>('${prefix}background', background, defaultValue: null)); styles.add(DiagnosticsProperty<Paint>('${prefix}background', background, defaultValue: null));
......
...@@ -725,6 +725,8 @@ class _TextStyleProxy implements TextStyle { ...@@ -725,6 +725,8 @@ class _TextStyleProxy implements TextStyle {
@override @override
double? get height => _delegate.height; double? get height => _delegate.height;
@override @override
TextLeadingDistribution? get leadingDistribution => _delegate.leadingDistribution;
@override
Locale? get locale => _delegate.locale; Locale? get locale => _delegate.locale;
@override @override
ui.Paint? get foreground => _delegate.foreground; ui.Paint? get foreground => _delegate.foreground;
...@@ -778,6 +780,7 @@ class _TextStyleProxy implements TextStyle { ...@@ -778,6 +780,7 @@ class _TextStyleProxy implements TextStyle {
double wordSpacingDelta = 0.0, double wordSpacingDelta = 0.0,
double heightFactor = 1.0, double heightFactor = 1.0,
double heightDelta = 0.0, double heightDelta = 0.0,
TextLeadingDistribution? leadingDistribution,
TextBaseline? textBaseline, TextBaseline? textBaseline,
Locale? locale, Locale? locale,
List<ui.Shadow>? shadows, List<ui.Shadow>? shadows,
...@@ -805,6 +808,7 @@ class _TextStyleProxy implements TextStyle { ...@@ -805,6 +808,7 @@ class _TextStyleProxy implements TextStyle {
double? wordSpacing, double? wordSpacing,
TextBaseline? textBaseline, TextBaseline? textBaseline,
double? height, double? height,
TextLeadingDistribution? leadingDistribution,
Locale? locale, Locale? locale,
ui.Paint? foreground, ui.Paint? foreground,
ui.Paint? background, ui.Paint? background,
......
...@@ -832,6 +832,126 @@ void main() { ...@@ -832,6 +832,126 @@ void main() {
)!; )!;
expect(caretHeight, 50.0); expect(caretHeight, 50.0);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/56308 }, skip: isBrowser); // https://github.com/flutter/flutter/issues/56308
group('TextPainter line-height', () {
test('half-leading', (){
const TextStyle style = TextStyle(
height: 20,
fontSize: 1,
leadingDistribution: TextLeadingDistribution.even,
);
final TextPainter painter = TextPainter()
..textDirection = TextDirection.ltr
..text = const TextSpan(text: 'A', style: style)
..layout();
final Rect glyphBox = painter.getBoxesForSelection(
const TextSelection(baseOffset: 0, extentOffset: 1),
).first.toRect();
final RelativeRect insets = RelativeRect.fromSize(glyphBox, painter.size);
// The glyph box is centered.
expect(insets.top, insets.bottom);
// The glyph box is exactly 1 logical pixel high.
expect(insets.top, (20 - 1) / 2);
});
test('half-leading with small height', (){
const TextStyle style = TextStyle(
height: 0.1,
fontSize: 10,
leadingDistribution: TextLeadingDistribution.even,
);
final TextPainter painter = TextPainter()
..textDirection = TextDirection.ltr
..text = const TextSpan(text: 'A', style: style)
..layout();
final Rect glyphBox = painter.getBoxesForSelection(
const TextSelection(baseOffset: 0, extentOffset: 1),
).first.toRect();
final RelativeRect insets = RelativeRect.fromSize(glyphBox, painter.size);
// The glyph box is still centered.
expect(insets.top, insets.bottom);
// The glyph box is exactly 10 logical pixel high (the height multiplier
// does not scale the glyph). Negative leading.
expect(insets.top, (1 - 10) / 2);
});
test('half-leading with leading trim', (){
const TextStyle style = TextStyle(
height: 0.1,
fontSize: 10,
leadingDistribution: TextLeadingDistribution.even,
);
final TextPainter painter = TextPainter()
..textDirection = TextDirection.ltr
..text = const TextSpan(text: 'A', style: style)
..textHeightBehavior = const TextHeightBehavior(
applyHeightToFirstAscent: false,
applyHeightToLastDescent: false,
leadingDistribution: TextLeadingDistribution.proportional,
)
..layout();
final Rect glyphBox = painter.getBoxesForSelection(
const TextSelection(baseOffset: 0, extentOffset: 1),
).first.toRect();
expect(painter.size, glyphBox.size);
// The glyph box is still centered.
expect(glyphBox.topLeft, Offset.zero);
});
test('TextLeadingDistribution falls back to paragraph style', (){
const TextStyle style = TextStyle(height: 20, fontSize: 1);
final TextPainter painter = TextPainter()
..textDirection = TextDirection.ltr
..text = const TextSpan(text: 'A', style: style)
..textHeightBehavior = const TextHeightBehavior(
leadingDistribution: TextLeadingDistribution.even,
)
..layout();
final Rect glyphBox = painter.getBoxesForSelection(
const TextSelection(baseOffset: 0, extentOffset: 1),
).first.toRect();
// Still uses half-leading.
final RelativeRect insets = RelativeRect.fromSize(glyphBox, painter.size);
expect(insets.top, insets.bottom);
expect(insets.top, (20 - 1) / 2);
});
test('TextLeadingDistribution does nothing if height multiplier is null', (){
const TextStyle style = TextStyle(fontSize: 1);
final TextPainter painter = TextPainter()
..textDirection = TextDirection.ltr
..text = const TextSpan(text: 'A', style: style)
..textHeightBehavior = const TextHeightBehavior(
leadingDistribution: TextLeadingDistribution.even,
)
..layout();
final Rect glyphBox = painter.getBoxesForSelection(
const TextSelection(baseOffset: 0, extentOffset: 1),
).first.toRect();
painter.textHeightBehavior = const TextHeightBehavior(
leadingDistribution: TextLeadingDistribution.proportional,
);
painter.layout();
final Rect newGlyphBox = painter.getBoxesForSelection(
const TextSelection(baseOffset: 0, extentOffset: 1),
).first.toRect();
expect(glyphBox, newGlyphBox);
});
}, skip: isBrowser);
} }
class MockCanvas extends Fake implements Canvas { class MockCanvas extends Fake implements Canvas {
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
import 'dart:ui' as ui show TextStyle, ParagraphStyle, FontFeature, Shadow; import 'dart:ui' as ui show TextStyle, ParagraphStyle, FontFeature, Shadow;
import 'package:flutter/painting.dart'; import 'package:flutter/painting.dart';
import 'package:flutter/src/foundation/constants.dart';
import '../flutter_test_alternative.dart'; import '../flutter_test_alternative.dart';
// This matcher verifies ui.TextStyle.toString (from dart:ui) reports a superset // This matcher verifies ui.TextStyle.toString (from dart:ui) reports a superset
...@@ -29,6 +30,9 @@ class _DartUiTextStyleToStringMatcher extends Matcher { ...@@ -29,6 +30,9 @@ class _DartUiTextStyleToStringMatcher extends Matcher {
_propertyToString('letterSpacing', textStyle.letterSpacing), _propertyToString('letterSpacing', textStyle.letterSpacing),
_propertyToString('wordSpacing', textStyle.wordSpacing), _propertyToString('wordSpacing', textStyle.wordSpacing),
_propertyToString('height', textStyle.height), _propertyToString('height', textStyle.height),
// TODO(LongCatIsLooong): web support for
// https://github.com/flutter/flutter/issues/72521
if (!kIsWeb) _propertyToString('leadingDistribution', textStyle.leadingDistribution),
_propertyToString('locale', textStyle.locale), _propertyToString('locale', textStyle.locale),
_propertyToString('background', textStyle.background), _propertyToString('background', textStyle.background),
_propertyToString('foreground', textStyle.foreground), _propertyToString('foreground', textStyle.foreground),
...@@ -115,7 +119,11 @@ void main() { ...@@ -115,7 +119,11 @@ void main() {
equals('TextStyle(inherit: false, size: 10.0, weight: 800, height: 123.0x)'), equals('TextStyle(inherit: false, size: 10.0, weight: 800, height: 123.0x)'),
); );
final TextStyle s2 = s1.copyWith(color: const Color(0xFF00FF00), height: 100.0); final TextStyle s2 = s1.copyWith(
color: const Color(0xFF00FF00),
height: 100.0,
leadingDistribution: TextLeadingDistribution.even,
);
expect(s1.fontFamily, isNull); expect(s1.fontFamily, isNull);
expect(s1.fontSize, 10.0); expect(s1.fontSize, 10.0);
expect(s1.fontWeight, FontWeight.w800); expect(s1.fontWeight, FontWeight.w800);
...@@ -126,11 +134,12 @@ void main() { ...@@ -126,11 +134,12 @@ void main() {
expect(s2.fontWeight, FontWeight.w800); expect(s2.fontWeight, FontWeight.w800);
expect(s2.height, 100.0); expect(s2.height, 100.0);
expect(s2.color, const Color(0xFF00FF00)); expect(s2.color, const Color(0xFF00FF00));
expect(s2.leadingDistribution, TextLeadingDistribution.even);
expect(s2, isNot(equals(s1))); expect(s2, isNot(equals(s1)));
expect( expect(
s2.toString(), s2.toString(),
equals( equals(
'TextStyle(inherit: true, color: Color(0xff00ff00), size: 10.0, weight: 800, height: 100.0x)', 'TextStyle(inherit: true, color: Color(0xff00ff00), size: 10.0, weight: 800, height: 100.0x, leadingDistribution: even)',
), ),
); );
...@@ -162,6 +171,7 @@ void main() { ...@@ -162,6 +171,7 @@ void main() {
expect(s2.fontWeight, FontWeight.w800); expect(s2.fontWeight, FontWeight.w800);
expect(s2.height, 100.0); expect(s2.height, 100.0);
expect(s2.color, const Color(0xFF00FF00)); expect(s2.color, const Color(0xFF00FF00));
expect(s2.leadingDistribution, TextLeadingDistribution.even);
expect(s2, isNot(equals(s1))); expect(s2, isNot(equals(s1)));
expect(s2, isNot(equals(s4))); expect(s2, isNot(equals(s4)));
expect(s4.fontFamily, isNull); expect(s4.fontFamily, isNull);
...@@ -169,6 +179,7 @@ void main() { ...@@ -169,6 +179,7 @@ void main() {
expect(s4.fontWeight, FontWeight.w800); expect(s4.fontWeight, FontWeight.w800);
expect(s4.height, 123.0); expect(s4.height, 123.0);
expect(s4.color, const Color(0xFF00FF00)); expect(s4.color, const Color(0xFF00FF00));
expect(s4.leadingDistribution, TextLeadingDistribution.even);
final TextStyle s5 = TextStyle.lerp(s1, s3, 0.25)!; final TextStyle s5 = TextStyle.lerp(s1, s3, 0.25)!;
expect(s1.fontFamily, isNull); expect(s1.fontFamily, isNull);
...@@ -247,16 +258,21 @@ void main() { ...@@ -247,16 +258,21 @@ void main() {
expect(ts5, equals(ui.TextStyle(fontWeight: FontWeight.w700, fontSize: 12.0, height: 123.0))); expect(ts5, equals(ui.TextStyle(fontWeight: FontWeight.w700, fontSize: 12.0, height: 123.0)));
expect(ts5, matchesToStringOf(s5)); expect(ts5, matchesToStringOf(s5));
final ui.TextStyle ts2 = s2.getTextStyle(); final ui.TextStyle ts2 = s2.getTextStyle();
expect(ts2, equals(ui.TextStyle(color: const Color(0xFF00FF00), fontWeight: FontWeight.w800, fontSize: 10.0, height: 100.0))); expect(ts2, equals(ui.TextStyle(color: const Color(0xFF00FF00), fontWeight: FontWeight.w800, fontSize: 10.0, height: 100.0, leadingDistribution: TextLeadingDistribution.even)));
expect(ts2, matchesToStringOf(s2)); expect(ts2, matchesToStringOf(s2));
final ui.ParagraphStyle ps2 = s2.getParagraphStyle(textAlign: TextAlign.center); final ui.ParagraphStyle ps2 = s2.getParagraphStyle(textAlign: TextAlign.center);
expect(ps2, equals(ui.ParagraphStyle(textAlign: TextAlign.center, fontWeight: FontWeight.w800, fontSize: 10.0, height: 100.0))); expect(
ps2,
equals(ui.ParagraphStyle(textAlign: TextAlign.center, fontWeight: FontWeight.w800, fontSize: 10.0, height: 100.0, textHeightBehavior: const TextHeightBehavior(leadingDistribution: TextLeadingDistribution.even))),
);
final ui.ParagraphStyle ps5 = s5.getParagraphStyle(); final ui.ParagraphStyle ps5 = s5.getParagraphStyle();
expect(ps5, equals(ui.ParagraphStyle(fontWeight: FontWeight.w700, fontSize: 12.0, height: 123.0))); expect(
ps5,
equals(ui.ParagraphStyle(fontWeight: FontWeight.w700, fontSize: 12.0, height: 123.0)),
);
}); });
test('TextStyle with text direction', () { test('TextStyle with text direction', () {
final ui.ParagraphStyle ps6 = const TextStyle().getParagraphStyle(textDirection: TextDirection.ltr); final ui.ParagraphStyle ps6 = const TextStyle().getParagraphStyle(textDirection: TextDirection.ltr);
expect(ps6, equals(ui.ParagraphStyle(textDirection: TextDirection.ltr, fontSize: 14.0))); expect(ps6, equals(ui.ParagraphStyle(textDirection: TextDirection.ltr, fontSize: 14.0)));
...@@ -342,6 +358,11 @@ void main() { ...@@ -342,6 +358,11 @@ void main() {
const TextStyle b = TextStyle(fontFamilyFallback: <String>['Noto'], shadows: <ui.Shadow>[ui.Shadow()], fontFeatures: <ui.FontFeature>[ui.FontFeature('abcd')]); const TextStyle b = TextStyle(fontFamilyFallback: <String>['Noto'], shadows: <ui.Shadow>[ui.Shadow()], fontFeatures: <ui.FontFeature>[ui.FontFeature('abcd')]);
expect(a.hashCode, a.hashCode); expect(a.hashCode, a.hashCode);
expect(a.hashCode, isNot(equals(b.hashCode))); expect(a.hashCode, isNot(equals(b.hashCode)));
const TextStyle c = TextStyle(leadingDistribution: TextLeadingDistribution.even);
const TextStyle d = TextStyle(leadingDistribution: TextLeadingDistribution.proportional);
expect(c.hashCode, c.hashCode);
expect(c.hashCode, isNot(d.hashCode));
}); });
test('TextStyle foreground and color combos', () { test('TextStyle foreground and color combos', () {
...@@ -450,7 +471,14 @@ void main() { ...@@ -450,7 +471,14 @@ void main() {
}); });
test('TextStyle apply', () { test('TextStyle apply', () {
const TextStyle style = TextStyle(fontSize: 10, shadows: <ui.Shadow>[], fontStyle: FontStyle.normal, fontFeatures: <ui.FontFeature>[], textBaseline: TextBaseline.alphabetic); const TextStyle style = TextStyle(
fontSize: 10,
shadows: <ui.Shadow>[],
fontStyle: FontStyle.normal,
fontFeatures: <ui.FontFeature>[],
textBaseline: TextBaseline.alphabetic,
leadingDistribution: TextLeadingDistribution.even,
);
expect(style.apply().shadows, const <ui.Shadow>[]); expect(style.apply().shadows, const <ui.Shadow>[]);
expect(style.apply(shadows: const <ui.Shadow>[ui.Shadow(blurRadius: 2.0)]).shadows, const <ui.Shadow>[ui.Shadow(blurRadius: 2.0)]); expect(style.apply(shadows: const <ui.Shadow>[ui.Shadow(blurRadius: 2.0)]).shadows, const <ui.Shadow>[ui.Shadow(blurRadius: 2.0)]);
expect(style.apply().fontStyle, FontStyle.normal); expect(style.apply().fontStyle, FontStyle.normal);
...@@ -461,5 +489,10 @@ void main() { ...@@ -461,5 +489,10 @@ void main() {
expect(style.apply(fontFeatures: const <ui.FontFeature>[ui.FontFeature.enable('test')]).fontFeatures, const <ui.FontFeature>[ui.FontFeature.enable('test')]); expect(style.apply(fontFeatures: const <ui.FontFeature>[ui.FontFeature.enable('test')]).fontFeatures, const <ui.FontFeature>[ui.FontFeature.enable('test')]);
expect(style.apply().textBaseline, TextBaseline.alphabetic); expect(style.apply().textBaseline, TextBaseline.alphabetic);
expect(style.apply(textBaseline: TextBaseline.ideographic).textBaseline, TextBaseline.ideographic); expect(style.apply(textBaseline: TextBaseline.ideographic).textBaseline, TextBaseline.ideographic);
expect(style.apply().leadingDistribution, TextLeadingDistribution.even);
expect(
style.apply(leadingDistribution: TextLeadingDistribution.proportional).leadingDistribution,
TextLeadingDistribution.proportional,
);
}); });
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment