Unverified Commit c6cc3cde authored by Gary Qian's avatar Gary Qian Committed by GitHub

Integrate Strut: Add StrutStyle, expose Strut API, wire up strut with dart:ui,...

Integrate Strut: Add StrutStyle, expose Strut API, wire up strut with dart:ui, Roll engine 31a7f4d..e7eb1c8 (7 commits) (#26332)

Includes a breaking change to dart:ui ParagraphStyle where lineHeight is renamed to height for consistency with TextStyle.
parent 496ddc58
15f2b92cce916982b7dd8ce658bbf2a465c06ba4
e7eb1c8bf65531195fc76ba96c8fc8478ac5f554
46a3d26acbb1b0d72b6b02c30f03b9dbda7d5bdf
cbd3fa445868962b7e910e498791755c988e9890
......@@ -51,6 +51,7 @@ export 'src/painting/paint_utilities.dart';
export 'src/painting/rounded_rectangle_border.dart';
export 'src/painting/shape_decoration.dart';
export 'src/painting/stadium_border.dart';
export 'src/painting/strut_style.dart';
export 'src/painting/superellipse_shape.dart';
export 'src/painting/text_painter.dart';
export 'src/painting/text_span.dart';
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/foundation.dart';
import 'basic_types.dart';
/// Defines the strut, which sets the minimum height a line can be
/// relative to the baseline. Strut applies to all lines in the pararaph.
///
/// Strut is a feature that allows minimum line heights to be set. The effect is as
/// if a zero width space was included at the beginning of each line in the
/// paragraph. This imaginary space is 'shaped' according the properties defined
/// in this class.
///
/// No lines may be shorter than the strut. The ascent and descent of the strut
/// are calculated, and any laid out text that has a shorter ascent or descent than
/// the strut's ascent or descent will take the ascent and descent of the strut.
/// Text with ascents or descents larger than the strut's ascent or descent will lay
/// out as normal and extend past the strut.
///
/// Strut is defined independently from any text content or [TextStyle]s.
///
/// The vertical components of strut are as follows:
///
/// * `leading * fontSize / 2` or half the font leading if `leading` is undefined (half leading)
/// * `ascent * height`
/// * `descent * height`
/// * `leading * fontSize / 2` or half the font leading if `leading` is undefined (half leading)
///
/// The sum of these four values is the total height of the line.
///
/// The `ascent + descent` is equivalent to the [fontSize]. Ascent is the font's
/// spacing above the baseline without leading and descent is the spacing below the
/// baseline without leading. Leading is split evenly between the top and bottom.
/// The values for `ascent` and `descent` are provided by the font named by
/// [fontFamily]. If no [fontFamily] or [fontFamilyFallback] is provided, then the
/// platform's default family will be used.
///
/// Each line's spacing above the baseline will be at least as tall as the half
/// leading plus ascent. Each line's spacing below the baseline will be at least as
/// tall as the half leading plus descent.
///
/// ### Fields and their default values.
// ///////////////////////////////////////////////////////////////////////////
// The defaults are noted here for convenience. The actual place where they //
// are defined is in the engine paragraph_style.h of LibTxt. The values here//
// should be updated should it change in the engine. The engine specifies //
// the defaults in order to reduce the amount of data we pass to native as //
// strut will usually be unspecified. //
// ///////////////////////////////////////////////////////////////////////////
///
/// Omitted or null properties will take the default values specified below:
///
/// * [fontFamily]: the name of the font to use when calcualting the strut (e.g., Roboto).
/// No glyphs from the font will be drawn and the font will be used purely for metrics.
///
/// * [fontFamilyFallback]: an ordered list of font family names that will be searched for when
/// the font in [fontFamily] cannot be found. When all specified font families have been
/// exhausted an no match was found, the default platform font will be used.
///
/// * [fontSize]: the size of the ascent plus descent in logical pixels. This is also
/// used as the basis of the custom leading caluclation. This value cannot
/// be negative.
/// Default is 14 logical pixels.
///
/// * [height]: the multiple of [fontSize] to multiply the ascent and descent by.
/// 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].
/// Default is 1.0.
///
/// * [leading]: the custom leading to apply to the strut as a multiple of [fontSize].
/// 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 height. This differs
/// from [height] since the spacing is equally distributed above and below the
/// baseline.
/// Default is `null`, which will use the font-specified leading.
///
/// * [fontWeight]: the typeface thickness to use when calculating the strut (e.g., bold).
/// Default is [FontWeight.w400].
///
/// * [fontStyle]: the typeface variant to use when calculating the strut (e.g., italic).
/// Default is [FontStyle.normal].
///
/// * [forceStrutHeight]: when true, all lines will be laid out with the height of the
/// strut. All line and run-specific metrics will be ignored/overridden and only strut
/// metrics will be used instead. This property guarantees uniform line spacing, however
/// text in adjacent lines may overlap. This property should be enabled with caution as
/// it bypasses a large portion of the vertical layout system.
/// The default value is false.
///
/// ### Examples
///
/// {@tool sample}
/// In this simple case, the text will be rendered at font size 10, however, the vertical
/// height of each line will be the strut height (Roboto in font size 30 * 1.5) as the text
/// itself is shorter than the strut.
///
/// ```dart
/// const Text(
/// 'Hello, world!\nSecond line!',
/// style: TextStyle(
/// fontSize: 10,
/// fontFamily: 'Raleway',
/// ),
/// strutStyle: StrutStyle(
/// fontFamily: 'Roboto',
/// fontSize: 30,
/// height: 1.5,
/// ),
/// ),
/// ```
/// {@end-tool}
///
/// {@tool sample}
/// Here, strut is used to absorb the additional line height in the second line.
/// The strut [height] was defined as 1.5 (the default font size is 14), which
/// caused all lines to be laid out taller than without strut. This extra space was
/// able to accomodate the larger font size of `Second line!` without causing the line
/// height to change for the second line only. All lines in this example are thus the
/// same height (`14 * 1.5`).
///
/// ```dart
/// const Text.rich(
/// TextSpan(
/// text: 'First line!\n',
/// style: TextStyle(
/// fontSize: 14,
/// fontFamily: 'Roboto'
/// ),
/// children: <TextSpan>[
/// TextSpan(
/// text: 'Second line!\n',
/// style: TextStyle(
/// fontSize: 16,
/// fontFamily: 'Roboto',
/// ),
/// ),
/// TextSpan(
/// text: 'Third line!\n',
/// style: TextStyle(
/// fontSize: 14,
/// fontFamily: 'Roboto',
/// ),
/// ),
/// ],
/// ),
/// strutStyle: StrutStyle(
/// fontFamily: 'Roboto',
/// height: 1.5,
/// ),
/// ),
/// ```
/// {@end-tool}
///
/// {@tool sample}
/// Here, strut is used to enable strange and overlapping text to achieve unique
/// effects. The `M`s in lines 2 and 3 are able to extend above their lines and
/// fill empty space in lines above. The [forceStrutHeight] is enabled and functions
/// as a 'grid' for the glyphs to draw on.
///
/// ![The result of the example below.](https://flutter.github.io/assets-for-api-docs/assets/painting/strut_force_example.png)
///
/// ```dart
/// const Text.rich(
/// TextSpan(
/// text: '--------- ---------\n',
/// style: TextStyle(
/// fontSize: 14,
/// fontFamily: 'Roboto',
/// ),
/// children: <TextSpan>[
/// TextSpan(
/// text: '^^^M^^^\n',
/// style: TextStyle(
/// fontSize: 30,
/// fontFamily: 'Roboto',
/// ),
/// ),
/// TextSpan(
/// text: 'M------M\n',
/// style: TextStyle(
/// fontSize: 30,
/// fontFamily: 'Roboto',
/// ),
/// ),
/// ],
/// ),
/// strutStyle: StrutStyle(
/// fontFamily: 'Roboto',
/// fontSize: 14,
/// height: 1,
/// forceStrutHeight: true,
/// ),
/// ),
/// ```
/// {@end-tool}
///
/// {@tool sample}
/// This example uses forceStrutHeight to create a 'drop cap' for the 'T' in 'The'.
/// By locking the line heights to the metrics of the 14pt serif font, we are able
/// to lay out a large 37pt 'T' on the second line to take up space on both the first
/// and second lines.
///
/// ![The result of the example below.](https://flutter.github.io/assets-for-api-docs/assets/painting/strut_force_example_2.png)
///
/// ```dart
/// Text.rich(
/// TextSpan(
/// text: '  he candle flickered\n',
/// style: TextStyle(
/// fontSize: 14,
/// fontFamily: 'Serif'
/// ),
/// children: <TextSpan>[
/// TextSpan(
/// text: 'T',
/// style: TextStyle(
/// fontSize: 37,
/// fontFamily: 'Serif'
/// ),
/// ),
/// TextSpan(
/// text: 'in the moonlight as\n',
/// style: TextStyle(
/// fontSize: 14,
/// fontFamily: 'Serif'
/// ),
/// ),
/// TextSpan(
/// text: 'Dash the bird fluttered\n',
/// style: TextStyle(
/// fontSize: 14,
/// fontFamily: 'Serif'
/// ),
/// ),
/// TextSpan(
/// text: 'off into the distance.',
/// style: TextStyle(
/// fontSize: 14,
/// fontFamily: 'Serif'
/// ),
/// ),
/// ],
/// ),
/// strutStyle: StrutStyle(
/// fontFamily: 'Serif',
/// fontSize: 14,
/// forceStrutHeight: true,
/// ),
/// ),
/// ```
/// {@end-tool}
///
@immutable
class StrutStyle extends Diagnosticable {
/// Creates a strut style.
///
/// The `package` argument must be non-null if the font family is defined in a
/// package. It is combined with the `fontFamily` argument to set the
/// [fontFamily] property.
const StrutStyle({
String fontFamily,
List<String> fontFamilyFallback,
this.fontSize,
this.height,
this.leading,
this.fontWeight,
this.fontStyle,
this.forceStrutHeight,
this.debugLabel,
String package,
}) : fontFamily = package == null ? fontFamily : 'packages/$package/$fontFamily',
_fontFamilyFallback = fontFamilyFallback,
_package = package,
assert(fontSize == null || fontSize > 0),
assert(leading == null || leading >= 0),
assert(package == null || (package != null && (fontFamily != null || fontFamilyFallback != null)));
/// The name of the font to use when calcualting the strut (e.g., Roboto). If the
/// font is defined in a package, this will be prefixed with
/// 'packages/package_name/' (e.g. 'packages/cool_fonts/Roboto'). The
/// prefixing is done by the constructor when the `package` argument is
/// provided.
///
/// The value provided in [fontFamily] will act as the preferred/first font
/// family that will be searched for, followed in order by the font families
/// in [fontFamilyFallback]. If all font families are exhausted and no match
/// was found, the default platform font family will be used instead. Unlike
/// [TextStyle.fontFamilyFallback], the font does not need to contain the
/// desired glyphs to match.
final String fontFamily;
/// The ordered list of font families to fall back on when a higher priority
/// font family cannot be found.
///
/// The value provided in [fontFamily] will act as the preferred/first font
/// family that will be searched for, followed in order by the font families
/// in [fontFamilyFallback]. If all font families are exhausted and no match
/// was found, the default platform font family will be used instead. Unlike
/// [TextStyle.fontFamilyFallback], the font does not need to contain the
/// desired glyphs to match.
///
/// When [fontFamily] is null or not provided, the first value in [fontFamilyFallback]
/// acts as the preferred/first font family. When neither is provided, then
/// the default platform font will be used. Providing and empty list or null
/// for this property is the same as omitting it.
///
/// If the font is defined in a package, each font family in the list will be
/// prefixed with 'packages/package_name/' (e.g. 'packages/cool_fonts/Roboto').
/// The package name should be provided by the `package` argument in the
/// constructor.
List<String> get fontFamilyFallback {
if (_package != null && _fontFamilyFallback != null)
return _fontFamilyFallback.map((String family) => 'packages/$_package/$family').toList();
return _fontFamilyFallback;
}
final List<String> _fontFamilyFallback;
// This is stored in order to prefix the fontFamilies in _fontFamilyFallback
// in the [fontFamilyFallback] getter.
final String _package;
/// The size of text (in logical pixels) to use when obtaining metrics from the font.
///
/// The [fontSize] is used to get the base set of metrics that are then used to calculated
/// the metrics of strut. The height and leading are expressed as a multiple of
/// [fontSize].
///
/// The default fontSize is 14 logical pixels.
final double fontSize;
/// The multiple of [fontSize] to multiply the ascent and descent by where `ascent + descent = fontSize`.
///
/// Ascent is the spacing above the baseline and descent is the spacing below the baseline.
///
/// 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 default height is 1.0.
final double height;
/// The typeface thickness to use when calculating the strut (e.g., bold).
///
/// The default fontWeight is [FontWeight.w400].
final FontWeight fontWeight;
/// The typeface variant to use when calculating the strut (e.g., italics).
///
/// The default fontStyle is [FontStyle.normal].
final FontStyle fontStyle;
/// The custom leading to apply to the strut as a multiple of [fontSize].
///
/// 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
/// from [height] since the spacing is equally distributed above and below the
/// baseline.
///
/// The default leading is null, which will use the font-specified leading.
final double leading;
/// Whether the strut height should be forced.
///
/// When true, all lines will be laid out with the height of the
/// strut. All line and run-specific metrics will be ignored/overridden and only strut
/// metrics will be used instead. This property guarantees uniform line spacing, however
/// text in adjacent lines may overlap.
///
/// This property should be enabled with caution as
/// it bypasses a large portion of the vertical layout system.
///
/// This is equivalent to setting [TextStyle.height] to zero for all [TextStyle]s
/// in the paragraph. Since the height of each line is calculated as a max of the
/// metrics of each run of text, zero height [TextStyle]s cause the minimums
/// defined by strut to always manifest, resulting in all lines having the height
/// of the strut.
///
/// The default is false.
final bool forceStrutHeight;
/// A human-readable description of this strut style.
///
/// This property is maintained only in debug builds.
///
/// This property is not considered when comparing strut styles using `==` or
/// [compareTo], and it does not affect [hashCode].
final String debugLabel;
/// Describe the difference between this style and another, in terms of how
/// much damage it will make to the rendering.
///
/// See also:
///
/// * [TextSpan.compareTo], which does the same thing for entire [TextSpan]s.
RenderComparison compareTo(StrutStyle other) {
if (identical(this, other))
return RenderComparison.identical;
if (fontFamily != other.fontFamily ||
fontSize != other.fontSize ||
fontWeight != other.fontWeight ||
fontStyle != other.fontStyle ||
height != other.height ||
leading != other.leading ||
forceStrutHeight != other.forceStrutHeight ||
!listEquals(fontFamilyFallback, other.fontFamilyFallback))
return RenderComparison.layout;
return RenderComparison.identical;
}
@override
bool operator ==(dynamic other) {
if (identical(this, other))
return true;
if (other.runtimeType != runtimeType)
return false;
final StrutStyle typedOther = other;
return fontFamily == typedOther.fontFamily &&
fontSize == typedOther.fontSize &&
fontWeight == typedOther.fontWeight &&
fontStyle == typedOther.fontStyle &&
height == typedOther.height &&
leading == typedOther.leading &&
forceStrutHeight == typedOther.forceStrutHeight;
}
@override
int get hashCode {
return hashValues(
fontFamily,
fontSize,
fontWeight,
fontStyle,
height,
leading,
forceStrutHeight,
);
}
/// Adds all properties prefixing property names with the optional `prefix`.
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties, { String prefix = '' }) {
super.debugFillProperties(properties);
if (debugLabel != null)
properties.add(MessageProperty('${prefix}debugLabel', debugLabel));
final List<DiagnosticsNode> styles = <DiagnosticsNode>[];
styles.add(StringProperty('${prefix}family', fontFamily, defaultValue: null, quoted: false));
styles.add(IterableProperty<String>('${prefix}familyFallback', fontFamilyFallback, defaultValue: null));
styles.add(DoubleProperty('${prefix}size', fontSize, defaultValue: null));
String weightDescription;
if (fontWeight != null) {
weightDescription = '${fontWeight.index + 1}00';
}
// TODO(jacobr): switch this to use enumProperty which will either cause the
// weight description to change to w600 from 600 or require existing
// enumProperty to handle this special case.
styles.add(DiagnosticsProperty<FontWeight>(
'${prefix}weight',
fontWeight,
description: weightDescription,
defaultValue: null,
));
styles.add(EnumProperty<FontStyle>('${prefix}style', fontStyle, defaultValue: null));
styles.add(DoubleProperty('${prefix}height', height, unit: 'x', defaultValue: null));
styles.add(FlagProperty('${prefix}forceStrutHeight', value: forceStrutHeight, defaultValue: null));
final bool styleSpecified = styles.any((DiagnosticsNode n) => !n.isFiltered(DiagnosticLevel.info));
styles.forEach(properties.add);
if (!styleSpecified)
properties.add(FlagProperty('forceStrutHeight', value: forceStrutHeight, ifTrue: '$prefix<strut height forced>', ifFalse: '$prefix<strut height normal>'));
}
}
......@@ -10,6 +10,7 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/services.dart';
import 'basic_types.dart';
import 'strut_style.dart';
import 'text_span.dart';
export 'package:flutter/services.dart' show TextRange, TextSelection;
......@@ -48,6 +49,7 @@ class TextPainter {
int maxLines,
String ellipsis,
Locale locale,
StrutStyle strutStyle,
}) : assert(text == null || text.debugAssertIsValid()),
assert(textAlign != null),
assert(textScaleFactor != null),
......@@ -58,7 +60,8 @@ class TextPainter {
_textScaleFactor = textScaleFactor,
_maxLines = maxLines,
_ellipsis = ellipsis,
_locale = locale;
_locale = locale,
_strutStyle = strutStyle;
ui.Paragraph _paragraph;
bool _needsLayout = true;
......@@ -198,6 +201,29 @@ class TextPainter {
_needsLayout = true;
}
/// {@template flutter.painting.textPainter.strutStyle}
/// The strut style to use. Strut style defines the strut, which sets minimum
/// vertical layout metrics.
///
/// Omitting or providing null will disable strut.
///
/// Omitting or providing null for any properties of [StrutStyle] will result in
/// default values being used. It is highly recommended to at least specify a
/// [fontSize].
///
/// See [StrutStyle] for details.
/// {@endtemplate}
StrutStyle get strutStyle => _strutStyle;
StrutStyle _strutStyle;
set strutStyle(StrutStyle value) {
if (_strutStyle == value)
return;
_strutStyle = value;
_paragraph = null;
_needsLayout = true;
}
ui.Paragraph _layoutTemplate;
ui.ParagraphStyle _createParagraphStyle([TextDirection defaultTextDirection]) {
......@@ -212,6 +238,7 @@ class TextPainter {
maxLines: _maxLines,
ellipsis: _ellipsis,
locale: _locale,
strutStyle: _strutStyle,
) ?? ui.ParagraphStyle(
textAlign: textAlign,
textDirection: textDirection ?? defaultTextDirection,
......
......@@ -2,11 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui' as ui show ParagraphStyle, TextStyle, lerpDouble, Shadow;
import 'dart:ui' as ui show ParagraphStyle, TextStyle, StrutStyle, lerpDouble, Shadow;
import 'package:flutter/foundation.dart';
import 'basic_types.dart';
import 'strut_style.dart';
const String _kDefaultDebugLabel = 'unknown';
......@@ -812,17 +813,35 @@ class TextStyle extends Diagnosticable {
String ellipsis,
int maxLines,
Locale locale,
String fontFamily,
double fontSize,
FontWeight fontWeight,
FontStyle fontStyle,
double height,
StrutStyle strutStyle,
}) {
assert(textScaleFactor != null);
assert(maxLines == null || maxLines > 0);
return ui.ParagraphStyle(
textAlign: textAlign,
textDirection: textDirection,
fontWeight: fontWeight,
fontStyle: fontStyle,
fontFamily: fontFamily,
fontSize: (fontSize ?? _defaultFontSize) * textScaleFactor,
lineHeight: height,
// Here, we stablish the contents of this TextStyle as the paragraph's default font
// unless an override is passed in.
fontWeight: fontWeight ?? this.fontWeight,
fontStyle: fontStyle ?? this.fontStyle,
fontFamily: fontFamily ?? this.fontFamily,
fontSize: (fontSize ?? this.fontSize ?? _defaultFontSize) * textScaleFactor,
height: height ?? this.height,
strutStyle: strutStyle == null ? null : ui.StrutStyle(
fontFamily: strutStyle.fontFamily,
fontFamilyFallback: strutStyle.fontFamilyFallback,
fontSize: strutStyle.fontSize,
height: strutStyle.height,
leading: strutStyle.leading,
fontWeight: strutStyle.fontWeight,
fontStyle: strutStyle.fontStyle,
forceStrutHeight: strutStyle.forceStrutHeight,
),
maxLines: maxLines,
ellipsis: ellipsis,
locale: locale,
......@@ -928,35 +947,7 @@ class TextStyle extends Diagnosticable {
styles.add(DoubleProperty('${prefix}size', fontSize, defaultValue: null));
String weightDescription;
if (fontWeight != null) {
switch (fontWeight) {
case FontWeight.w100:
weightDescription = '100';
break;
case FontWeight.w200:
weightDescription = '200';
break;
case FontWeight.w300:
weightDescription = '300';
break;
case FontWeight.w400:
weightDescription = '400';
break;
case FontWeight.w500:
weightDescription = '500';
break;
case FontWeight.w600:
weightDescription = '600';
break;
case FontWeight.w700:
weightDescription = '700';
break;
case FontWeight.w800:
weightDescription = '800';
break;
case FontWeight.w900:
weightDescription = '900';
break;
}
weightDescription = '${fontWeight.index + 1}00';
}
// TODO(jacobr): switch this to use enumProperty which will either cause the
// weight description to change to w600 from 600 or require existing
......
......@@ -95,7 +95,7 @@ class RenderErrorBox extends RenderBox {
/// The paragraph style to use when painting [RenderErrorBox] objects.
static ui.ParagraphStyle paragraphStyle = ui.ParagraphStyle(
lineHeight: 1.0,
height: 1.0,
);
@override
......
......@@ -6,6 +6,7 @@ import 'dart:ui' as ui show Gradient, Shader, TextBox;
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/painting.dart';
import 'package:flutter/semantics.dart';
import 'package:flutter/services.dart';
......@@ -45,6 +46,7 @@ class RenderParagraph extends RenderBox {
double textScaleFactor = 1.0,
int maxLines,
Locale locale,
StrutStyle strutStyle,
}) : assert(text != null),
assert(text.debugAssertIsValid()),
assert(textAlign != null),
......@@ -63,6 +65,7 @@ class RenderParagraph extends RenderBox {
maxLines: maxLines,
ellipsis: overflow == TextOverflow.ellipsis ? _kEllipsis : null,
locale: locale,
strutStyle: strutStyle,
);
final TextPainter _textPainter;
......@@ -194,6 +197,17 @@ class RenderParagraph extends RenderBox {
markNeedsLayout();
}
/// {@macro flutter.painting.textPainter.strutStyle}
StrutStyle get strutStyle => _textPainter.strutStyle;
/// The value may be null.
set strutStyle(StrutStyle value) {
if (_textPainter.strutStyle == value)
return;
_textPainter.strutStyle = value;
_overflowShader = null;
markNeedsLayout();
}
void _layoutText({ double minWidth = 0.0, double maxWidth = double.infinity }) {
final bool widthMatters = softWrap || overflow == TextOverflow.ellipsis;
_textPainter.layout(minWidth: minWidth, maxWidth: widthMatters ? maxWidth : double.infinity);
......
......@@ -4599,6 +4599,7 @@ class RichText extends LeafRenderObjectWidget {
this.textScaleFactor = 1.0,
this.maxLines,
this.locale,
this.strutStyle,
}) : assert(text != null),
assert(textAlign != null),
assert(softWrap != null),
......@@ -4660,6 +4661,9 @@ class RichText extends LeafRenderObjectWidget {
/// See [RenderParagraph.locale] for more information.
final Locale locale;
/// {@macro flutter.painting.textPainter.strutStyle}
final StrutStyle strutStyle;
@override
RenderParagraph createRenderObject(BuildContext context) {
assert(textDirection != null || debugCheckHasDirectionality(context));
......@@ -4670,6 +4674,7 @@ class RichText extends LeafRenderObjectWidget {
overflow: overflow,
textScaleFactor: textScaleFactor,
maxLines: maxLines,
strutStyle: strutStyle,
locale: locale ?? Localizations.localeOf(context, nullOk: true),
);
}
......@@ -4685,6 +4690,7 @@ class RichText extends LeafRenderObjectWidget {
..overflow = overflow
..textScaleFactor = textScaleFactor
..maxLines = maxLines
..strutStyle = strutStyle
..locale = locale ?? Localizations.localeOf(context, nullOk: true);
}
......
......@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'package:flutter/foundation.dart';
import 'package:flutter/painting.dart';
import 'basic.dart';
import 'framework.dart';
......@@ -224,6 +225,7 @@ class Text extends StatelessWidget {
const Text(this.data, {
Key key,
this.style,
this.strutStyle,
this.textAlign,
this.textDirection,
this.locale,
......@@ -240,6 +242,7 @@ class Text extends StatelessWidget {
const Text.rich(this.textSpan, {
Key key,
this.style,
this.strutStyle,
this.textAlign,
this.textDirection,
this.locale,
......@@ -269,6 +272,9 @@ class Text extends StatelessWidget {
/// replace the closest enclosing [DefaultTextStyle].
final TextStyle style;
/// {@macro flutter.painting.textPainter.strutStyle}
final StrutStyle strutStyle;
/// How the text should be aligned horizontally.
final TextAlign textAlign;
......@@ -356,6 +362,7 @@ class Text extends StatelessWidget {
overflow: overflow ?? defaultTextStyle.overflow,
textScaleFactor: textScaleFactor ?? MediaQuery.textScaleFactorOf(context),
maxLines: maxLines ?? defaultTextStyle.maxLines,
strutStyle: strutStyle,
text: TextSpan(
style: effectiveTextStyle,
text: data,
......
......@@ -720,7 +720,7 @@ class _TextStyleProxy implements TextStyle {
}
@override
ui.ParagraphStyle getParagraphStyle({TextAlign textAlign, TextDirection textDirection, double textScaleFactor = 1.0, String ellipsis, int maxLines, Locale locale}) {
ui.ParagraphStyle getParagraphStyle({TextAlign textAlign, TextDirection textDirection, double textScaleFactor = 1.0, String ellipsis, int maxLines, Locale locale, String fontFamily, double fontSize, FontWeight fontWeight, FontStyle fontStyle, double height, StrutStyle strutStyle}) {
throw UnimplementedError();
}
......
......@@ -169,22 +169,22 @@ void main() {
expect(ts2.toString(), 'TextStyle(color: Color(0xff00ff00), decoration: unspecified, decorationColor: unspecified, decorationStyle: unspecified, fontWeight: FontWeight.w800, fontStyle: unspecified, textBaseline: unspecified, fontFamily: unspecified, fontFamilyFallback: unspecified, fontSize: 10.0, letterSpacing: unspecified, wordSpacing: unspecified, height: 100.0x, locale: unspecified, background: unspecified, foreground: unspecified, shadows: unspecified)');
final ui.ParagraphStyle ps2 = s2.getParagraphStyle(textAlign: TextAlign.center);
expect(ps2, equals(ui.ParagraphStyle(textAlign: TextAlign.center, fontWeight: FontWeight.w800, fontSize: 10.0, lineHeight: 100.0)));
expect(ps2.toString(), 'ParagraphStyle(textAlign: TextAlign.center, textDirection: unspecified, fontWeight: FontWeight.w800, fontStyle: unspecified, maxLines: unspecified, fontFamily: unspecified, fontSize: 10.0, lineHeight: 100.0x, ellipsis: unspecified, locale: unspecified)');
expect(ps2, equals(ui.ParagraphStyle(textAlign: TextAlign.center, fontWeight: FontWeight.w800, fontSize: 10.0, height: 100.0)));
expect(ps2.toString(), 'ParagraphStyle(textAlign: TextAlign.center, textDirection: unspecified, fontWeight: FontWeight.w800, fontStyle: unspecified, maxLines: unspecified, fontFamily: unspecified, fontSize: 10.0, height: 100.0x, ellipsis: unspecified, locale: unspecified)');
final ui.ParagraphStyle ps5 = s5.getParagraphStyle();
expect(ps5, equals(ui.ParagraphStyle(fontWeight: FontWeight.w700, fontSize: 12.0, lineHeight: 123.0)));
expect(ps5.toString(), 'ParagraphStyle(textAlign: unspecified, textDirection: unspecified, fontWeight: FontWeight.w700, fontStyle: unspecified, maxLines: unspecified, fontFamily: unspecified, fontSize: 12.0, lineHeight: 123.0x, ellipsis: unspecified, locale: unspecified)');
expect(ps5, equals(ui.ParagraphStyle(fontWeight: FontWeight.w700, fontSize: 12.0, height: 123.0)));
expect(ps5.toString(), 'ParagraphStyle(textAlign: unspecified, textDirection: unspecified, fontWeight: FontWeight.w700, fontStyle: unspecified, maxLines: unspecified, fontFamily: unspecified, fontSize: 12.0, height: 123.0x, ellipsis: unspecified, locale: unspecified)');
});
test('TextStyle with text direction', () {
final ui.ParagraphStyle ps6 = const TextStyle().getParagraphStyle(textDirection: TextDirection.ltr);
expect(ps6, equals(ui.ParagraphStyle(textDirection: TextDirection.ltr, fontSize: 14.0)));
expect(ps6.toString(), 'ParagraphStyle(textAlign: unspecified, textDirection: TextDirection.ltr, fontWeight: unspecified, fontStyle: unspecified, maxLines: unspecified, fontFamily: unspecified, fontSize: 14.0, lineHeight: unspecified, ellipsis: unspecified, locale: unspecified)');
expect(ps6.toString(), 'ParagraphStyle(textAlign: unspecified, textDirection: TextDirection.ltr, fontWeight: unspecified, fontStyle: unspecified, maxLines: unspecified, fontFamily: unspecified, fontSize: 14.0, height: unspecified, ellipsis: unspecified, locale: unspecified)');
final ui.ParagraphStyle ps7 = const TextStyle().getParagraphStyle(textDirection: TextDirection.rtl);
expect(ps7, equals(ui.ParagraphStyle(textDirection: TextDirection.rtl, fontSize: 14.0)));
expect(ps7.toString(), 'ParagraphStyle(textAlign: unspecified, textDirection: TextDirection.rtl, fontWeight: unspecified, fontStyle: unspecified, maxLines: unspecified, fontFamily: unspecified, fontSize: 14.0, lineHeight: unspecified, ellipsis: unspecified, locale: unspecified)');
expect(ps7.toString(), 'ParagraphStyle(textAlign: unspecified, textDirection: TextDirection.rtl, fontWeight: unspecified, fontStyle: unspecified, maxLines: unspecified, fontFamily: unspecified, fontSize: 14.0, height: unspecified, ellipsis: unspecified, locale: unspecified)');
});
test('TextStyle using package font', () {
......
......@@ -220,4 +220,221 @@ void main() {
matchesGoldenFile('text_golden.Fade.1.png'),
);
}, skip: !Platform.isLinux);
testWidgets('Default Strut text', (WidgetTester tester) async {
await tester.pumpWidget(
Center(
child: RepaintBoundary(
child: Container(
width: 200.0,
height: 100.0,
decoration: const BoxDecoration(
color: Color(0xff00ff00),
),
child: const Text('Hello\nLine 2\nLine 3',
textDirection: TextDirection.ltr,
style: TextStyle(),
strutStyle: StrutStyle(),
),
),
),
),
);
await expectLater(
find.byType(Container),
matchesGoldenFile('text_golden.StrutDefault.png'),
);
}, skip: !Platform.isLinux);
testWidgets('Strut text 1', (WidgetTester tester) async {
await tester.pumpWidget(
Center(
child: RepaintBoundary(
child: Container(
width: 200.0,
height: 100.0,
decoration: const BoxDecoration(
color: Color(0xff00ff00),
),
child: const Text('Hello\nLine2\nLine3',
textDirection: TextDirection.ltr,
style: TextStyle(),
strutStyle: StrutStyle(
height: 1.5,
),
),
),
),
),
);
await expectLater(
find.byType(Container),
matchesGoldenFile('text_golden.Strut.1.png'),
);
}, skip: !Platform.isLinux);
testWidgets('Strut text 2', (WidgetTester tester) async {
await tester.pumpWidget(
Center(
child: RepaintBoundary(
child: Container(
width: 200.0,
height: 100.0,
decoration: const BoxDecoration(
color: Color(0xff00ff00),
),
child: const Text('Hello\nLine 2\nLine 3',
textDirection: TextDirection.ltr,
style: TextStyle(),
strutStyle: StrutStyle(
height: 1.5,
fontSize: 14,
),
),
),
),
),
);
await expectLater(
find.byType(Container),
matchesGoldenFile('text_golden.Strut.2.png'),
);
}, skip: !Platform.isLinux);
testWidgets('Strut text rich', (WidgetTester tester) async {
await tester.pumpWidget(
Center(
child: RepaintBoundary(
child: Container(
width: 200.0,
height: 150.0,
decoration: const BoxDecoration(
color: Color(0xff00ff00),
),
child: const Text.rich(
TextSpan(
text: 'Hello\n',
style: TextStyle(
color: Colors.red,
fontSize: 30
),
children: <TextSpan>[
TextSpan(
text: 'Second line!\n',
style: TextStyle(
fontSize: 5,
color: Colors.blue,
),
),
TextSpan(
text: 'Third line!\n',
style: TextStyle(
fontSize: 25,
color: Colors.white,
),
),
],
),
textDirection: TextDirection.ltr,
strutStyle: StrutStyle(
fontSize: 14,
height: 1.1,
leading: 0.1,
),
),
),
),
),
);
await expectLater(
find.byType(Container),
matchesGoldenFile('text_golden.Strut.3.png'),
);
}, skip: !Platform.isLinux);
testWidgets('Strut text font fallback', (WidgetTester tester) async {
// Font Fallback
await tester.pumpWidget(
Center(
child: RepaintBoundary(
child: Container(
width: 200.0,
height: 100.0,
decoration: const BoxDecoration(
color: Color(0xff00ff00),
),
child: const Text('Hello\nLine 2\nLine 3',
textDirection: TextDirection.ltr,
style: TextStyle(),
strutStyle: StrutStyle(
fontFamily: 'FakeFont 1',
fontFamilyFallback: <String>[
'FakeFont 2',
'EvilFont 3',
'Nice Font 4',
'ahem'
],
fontSize: 14,
),
),
),
),
),
);
await expectLater(
find.byType(Container),
matchesGoldenFile('text_golden.Strut.4.png'),
);
}, skip: !Platform.isLinux);
testWidgets('Strut text rich forceStrutHeight', (WidgetTester tester) async {
await tester.pumpWidget(
Center(
child: RepaintBoundary(
child: Container(
width: 200.0,
height: 100.0,
decoration: const BoxDecoration(
color: Color(0xff00ff00),
),
child: const Text.rich(
TextSpan(
text: 'Hello\n',
style: TextStyle(
color: Colors.red,
fontSize: 30
),
children: <TextSpan>[
TextSpan(
text: 'Second line!\n',
style: TextStyle(
fontSize: 9,
color: Colors.blue,
),
),
TextSpan(
text: 'Third line!\n',
style: TextStyle(
fontSize: 27,
color: Colors.white,
),
),
],
),
textDirection: TextDirection.ltr,
strutStyle: StrutStyle(
fontSize: 14,
height: 1.1,
forceStrutHeight: true,
),
),
),
),
),
);
await expectLater(
find.byType(Container),
matchesGoldenFile('text_golden.StrutForce.1.png'),
);
}, skip: !Platform.isLinux);
}
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