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.
This commit is contained in:
parent
496ddc580c
commit
c6cc3cdeda
@ -1 +1 @@
|
||||
15f2b92cce916982b7dd8ce658bbf2a465c06ba4
|
||||
e7eb1c8bf65531195fc76ba96c8fc8478ac5f554
|
||||
|
@ -1 +1 @@
|
||||
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';
|
||||
|
478
packages/flutter/lib/src/painting/strut_style.dart
Normal file
478
packages/flutter/lib/src/painting/strut_style.dart
Normal file
@ -0,0 +1,478 @@
|
||||
// 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.
|
||||
///
|
||||
/// 
|
||||
///
|
||||
/// ```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.
|
||||
///
|
||||
/// 
|
||||
///
|
||||
/// ```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);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user