mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Reland "Engine/LibTxt/dart:ui impl of TextHeightBehavior #15087" (flutter/engine#16155)
This commit is contained in:
parent
3235e60fb2
commit
20ab007c56
@ -429,6 +429,94 @@ enum TextDecorationStyle {
|
||||
wavy
|
||||
}
|
||||
|
||||
/// {@template flutter.dart:ui.textHeightBehavior}
|
||||
/// Defines how the paragraph will apply [TextStyle.height] to the ascent of the
|
||||
/// first line and descent of the last line.
|
||||
///
|
||||
/// Each boolean value represents whether the [TextStyle.height] modifier will
|
||||
/// be applied to the corresponding metric. By default, all properties are true,
|
||||
/// and [TextStyle.height] is applied as normal. When set to false, the font's
|
||||
/// default ascent will be used.
|
||||
/// {@endtemplate}
|
||||
class TextHeightBehavior {
|
||||
|
||||
/// Creates a new TextHeightBehavior object.
|
||||
///
|
||||
/// * applyHeightToFirstAscent: When true, the [TextStyle.height] modifier
|
||||
/// will be applied to the ascent of the first line. When false, the font's
|
||||
/// default ascent will be used.
|
||||
/// * applyHeightToLastDescent: When true, the [TextStyle.height] modifier
|
||||
/// will be applied to the descent of the last line. When false, the font's
|
||||
/// default descent will be used.
|
||||
///
|
||||
/// All properties default to true (height modifications applied as normal).
|
||||
const TextHeightBehavior({
|
||||
this.applyHeightToFirstAscent = true,
|
||||
this.applyHeightToLastDescent = true,
|
||||
});
|
||||
|
||||
/// Creates a new TextHeightBehavior object from an encoded form.
|
||||
///
|
||||
/// See [encode] for the creation of the encoded form.
|
||||
const TextHeightBehavior.fromEncoded(int encoded) : applyHeightToFirstAscent = (encoded & 0x1) == 0,
|
||||
applyHeightToLastDescent = (encoded & 0x2) == 0;
|
||||
|
||||
|
||||
/// Whether to apply the [TextStyle.height] modifier to the ascent of the first
|
||||
/// line in the paragraph.
|
||||
///
|
||||
/// When true, the [TextStyle.height] modifier will be applied to to the ascent
|
||||
/// of the first line. When false, the font's default ascent will be used and
|
||||
/// the [TextStyle.height] will have no effect on the ascent of the first line.
|
||||
///
|
||||
/// This property only has effect if a non-null [TextStyle.height] is specified.
|
||||
///
|
||||
/// Defaults to true (height modifications applied as normal).
|
||||
final bool applyHeightToFirstAscent;
|
||||
|
||||
/// Whether to apply the [TextStyle.height] modifier to the descent of the last
|
||||
/// line in the paragraph.
|
||||
///
|
||||
/// When true, the [TextStyle.height] modifier will be applied to to the descent
|
||||
/// of the last line. When false, the font's default descent will be used and
|
||||
/// the [TextStyle.height] will have no effect on the descent of the last line.
|
||||
///
|
||||
/// This property only has effect if a non-null [TextStyle.height] is specified.
|
||||
///
|
||||
/// Defaults to true (height modifications applied as normal).
|
||||
final bool applyHeightToLastDescent;
|
||||
|
||||
/// Returns an encoded int representation of this object.
|
||||
int encode() {
|
||||
return (applyHeightToFirstAscent ? 0 : 1 << 0) | (applyHeightToLastDescent ? 0 : 1 << 1);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
if (other.runtimeType != runtimeType)
|
||||
return false;
|
||||
return other is TextHeightBehavior
|
||||
&& other.applyHeightToFirstAscent == applyHeightToFirstAscent
|
||||
&& other.applyHeightToLastDescent == applyHeightToLastDescent;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return hashValues(
|
||||
applyHeightToFirstAscent,
|
||||
applyHeightToLastDescent,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'TextHeightBehavior('
|
||||
'applyHeightToFirstAscent: $applyHeightToFirstAscent, '
|
||||
'applyHeightToLastDescent: $applyHeightToLastDescent'
|
||||
')';
|
||||
}
|
||||
}
|
||||
|
||||
/// Determines if lists [a] and [b] are deep equivalent.
|
||||
///
|
||||
/// Returns true if the lists are both null, or if they are both non-null, have
|
||||
@ -746,6 +834,8 @@ class TextStyle {
|
||||
//
|
||||
// - Element 5: The value of |maxLines|.
|
||||
//
|
||||
// - Element 6: The encoded value of |textHeightBehavior|.
|
||||
//
|
||||
Int32List _encodeParagraphStyle(
|
||||
TextAlign textAlign,
|
||||
TextDirection textDirection,
|
||||
@ -753,13 +843,14 @@ Int32List _encodeParagraphStyle(
|
||||
String fontFamily,
|
||||
double fontSize,
|
||||
double height,
|
||||
TextHeightBehavior textHeightBehavior,
|
||||
FontWeight fontWeight,
|
||||
FontStyle fontStyle,
|
||||
StrutStyle strutStyle,
|
||||
String ellipsis,
|
||||
Locale locale,
|
||||
) {
|
||||
final Int32List result = Int32List(6); // also update paragraph_builder.cc
|
||||
final Int32List result = Int32List(7); // also update paragraph_builder.cc
|
||||
if (textAlign != null) {
|
||||
result[0] |= 1 << 1;
|
||||
result[1] = textAlign.index;
|
||||
@ -780,30 +871,34 @@ Int32List _encodeParagraphStyle(
|
||||
result[0] |= 1 << 5;
|
||||
result[5] = maxLines;
|
||||
}
|
||||
if (fontFamily != null) {
|
||||
if (textHeightBehavior != null) {
|
||||
result[0] |= 1 << 6;
|
||||
// Passed separately to native.
|
||||
result[6] = textHeightBehavior.encode();
|
||||
}
|
||||
if (fontSize != null) {
|
||||
if (fontFamily != null) {
|
||||
result[0] |= 1 << 7;
|
||||
// Passed separately to native.
|
||||
}
|
||||
if (height != null) {
|
||||
if (fontSize != null) {
|
||||
result[0] |= 1 << 8;
|
||||
// Passed separately to native.
|
||||
}
|
||||
if (strutStyle != null) {
|
||||
if (height != null) {
|
||||
result[0] |= 1 << 9;
|
||||
// Passed separately to native.
|
||||
}
|
||||
if (ellipsis != null) {
|
||||
if (strutStyle != null) {
|
||||
result[0] |= 1 << 10;
|
||||
// Passed separately to native.
|
||||
}
|
||||
if (locale != null) {
|
||||
if (ellipsis != null) {
|
||||
result[0] |= 1 << 11;
|
||||
// Passed separately to native.
|
||||
}
|
||||
if (locale != null) {
|
||||
result[0] |= 1 << 12;
|
||||
// Passed separately to native.
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -842,6 +937,9 @@ class ParagraphStyle {
|
||||
/// the line height to take the height as defined by the font, which may not
|
||||
/// be exactly the height of the `fontSize`.
|
||||
///
|
||||
/// * `textHeightBehavior`: Specifies how the `height` multiplier is
|
||||
/// applied to ascent of the first line and the descent of the last line.
|
||||
///
|
||||
/// * `fontWeight`: The typeface thickness to use when painting the text
|
||||
/// (e.g., bold).
|
||||
///
|
||||
@ -869,6 +967,7 @@ class ParagraphStyle {
|
||||
String fontFamily,
|
||||
double fontSize,
|
||||
double height,
|
||||
TextHeightBehavior textHeightBehavior,
|
||||
FontWeight fontWeight,
|
||||
FontStyle fontStyle,
|
||||
StrutStyle strutStyle,
|
||||
@ -881,6 +980,7 @@ class ParagraphStyle {
|
||||
fontFamily,
|
||||
fontSize,
|
||||
height,
|
||||
textHeightBehavior,
|
||||
fontWeight,
|
||||
fontStyle,
|
||||
strutStyle,
|
||||
@ -929,11 +1029,14 @@ class ParagraphStyle {
|
||||
'fontWeight: ${ _encoded[0] & 0x008 == 0x008 ? FontWeight.values[_encoded[3]] : "unspecified"}, '
|
||||
'fontStyle: ${ _encoded[0] & 0x010 == 0x010 ? FontStyle.values[_encoded[4]] : "unspecified"}, '
|
||||
'maxLines: ${ _encoded[0] & 0x020 == 0x020 ? _encoded[5] : "unspecified"}, '
|
||||
'fontFamily: ${ _encoded[0] & 0x040 == 0x040 ? _fontFamily : "unspecified"}, '
|
||||
'fontSize: ${ _encoded[0] & 0x080 == 0x080 ? _fontSize : "unspecified"}, '
|
||||
'height: ${ _encoded[0] & 0x100 == 0x100 ? "${_height}x" : "unspecified"}, '
|
||||
'ellipsis: ${ _encoded[0] & 0x200 == 0x200 ? "\"$_ellipsis\"" : "unspecified"}, '
|
||||
'locale: ${ _encoded[0] & 0x400 == 0x400 ? _locale : "unspecified"}'
|
||||
'textHeightBehavior: ${
|
||||
_encoded[0] & 0x040 == 0x040 ?
|
||||
TextHeightBehavior.fromEncoded(_encoded[6]).toString() : "unspecified"}, '
|
||||
'fontFamily: ${ _encoded[0] & 0x080 == 0x080 ? _fontFamily : "unspecified"}, '
|
||||
'fontSize: ${ _encoded[0] & 0x100 == 0x100 ? _fontSize : "unspecified"}, '
|
||||
'height: ${ _encoded[0] & 0x200 == 0x200 ? "${_height}x" : "unspecified"}, '
|
||||
'ellipsis: ${ _encoded[0] & 0x400 == 0x400 ? "\"$_ellipsis\"" : "unspecified"}, '
|
||||
'locale: ${ _encoded[0] & 0x800 == 0x800 ? _locale : "unspecified"}'
|
||||
')';
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,12 +75,13 @@ const int psTextDirectionIndex = 2;
|
||||
const int psFontWeightIndex = 3;
|
||||
const int psFontStyleIndex = 4;
|
||||
const int psMaxLinesIndex = 5;
|
||||
const int psFontFamilyIndex = 6;
|
||||
const int psFontSizeIndex = 7;
|
||||
const int psHeightIndex = 8;
|
||||
const int psStrutStyleIndex = 9;
|
||||
const int psEllipsisIndex = 10;
|
||||
const int psLocaleIndex = 11;
|
||||
const int psTextHeightBehaviorIndex = 6;
|
||||
const int psFontFamilyIndex = 7;
|
||||
const int psFontSizeIndex = 8;
|
||||
const int psHeightIndex = 9;
|
||||
const int psStrutStyleIndex = 10;
|
||||
const int psEllipsisIndex = 11;
|
||||
const int psLocaleIndex = 12;
|
||||
|
||||
const int psTextAlignMask = 1 << psTextAlignIndex;
|
||||
const int psTextDirectionMask = 1 << psTextDirectionIndex;
|
||||
@ -90,6 +91,7 @@ const int psMaxLinesMask = 1 << psMaxLinesIndex;
|
||||
const int psFontFamilyMask = 1 << psFontFamilyIndex;
|
||||
const int psFontSizeMask = 1 << psFontSizeIndex;
|
||||
const int psHeightMask = 1 << psHeightIndex;
|
||||
const int psTextHeightBehaviorMask = 1 << psTextHeightBehaviorIndex;
|
||||
const int psStrutStyleMask = 1 << psStrutStyleIndex;
|
||||
const int psEllipsisMask = 1 << psEllipsisIndex;
|
||||
const int psLocaleMask = 1 << psLocaleIndex;
|
||||
@ -265,6 +267,10 @@ ParagraphBuilder::ParagraphBuilder(
|
||||
style.has_height_override = true;
|
||||
}
|
||||
|
||||
if (mask & psTextHeightBehaviorMask) {
|
||||
style.text_height_behavior = encoded[psTextHeightBehaviorIndex];
|
||||
}
|
||||
|
||||
if (mask & psStrutStyleMask) {
|
||||
decodeStrut(strutData, strutFontFamilies, style);
|
||||
}
|
||||
|
||||
@ -25,6 +25,7 @@ class SkParagraphStyle implements ui.ParagraphStyle {
|
||||
String fontFamily,
|
||||
double fontSize,
|
||||
double height,
|
||||
ui.TextHeightBehavior textHeightBehavior,
|
||||
ui.FontWeight fontWeight,
|
||||
ui.FontStyle fontStyle,
|
||||
ui.StrutStyle strutStyle,
|
||||
@ -38,6 +39,7 @@ class SkParagraphStyle implements ui.ParagraphStyle {
|
||||
fontFamily,
|
||||
fontSize,
|
||||
height,
|
||||
textHeightBehavior,
|
||||
fontWeight,
|
||||
fontStyle,
|
||||
ellipsis,
|
||||
@ -85,6 +87,7 @@ class SkParagraphStyle implements ui.ParagraphStyle {
|
||||
String fontFamily,
|
||||
double fontSize,
|
||||
double height,
|
||||
ui.TextHeightBehavior textHeightBehavior,
|
||||
ui.FontWeight fontWeight,
|
||||
ui.FontStyle fontStyle,
|
||||
String ellipsis,
|
||||
@ -129,6 +132,10 @@ class SkParagraphStyle implements ui.ParagraphStyle {
|
||||
skParagraphStyle['heightMultiplier'] = height;
|
||||
}
|
||||
|
||||
if (textHeightBehavior != null) {
|
||||
skParagraphStyle['textHeightBehavior'] = textHeightBehavior.encode();
|
||||
}
|
||||
|
||||
if (maxLines != null) {
|
||||
skParagraphStyle['maxLines'] = maxLines;
|
||||
}
|
||||
|
||||
@ -468,6 +468,7 @@ class EngineParagraphStyle implements ui.ParagraphStyle {
|
||||
String fontFamily,
|
||||
double fontSize,
|
||||
double height,
|
||||
ui.TextHeightBehavior textHeightBehavior,
|
||||
ui.FontWeight fontWeight,
|
||||
ui.FontStyle fontStyle,
|
||||
ui.StrutStyle strutStyle,
|
||||
@ -481,6 +482,7 @@ class EngineParagraphStyle implements ui.ParagraphStyle {
|
||||
_fontFamily = fontFamily,
|
||||
_fontSize = fontSize,
|
||||
_height = height,
|
||||
_textHeightBehavior = textHeightBehavior,
|
||||
// TODO(b/128317744): add support for strut style.
|
||||
_strutStyle = strutStyle,
|
||||
_ellipsis = ellipsis,
|
||||
@ -494,6 +496,7 @@ class EngineParagraphStyle implements ui.ParagraphStyle {
|
||||
final String _fontFamily;
|
||||
final double _fontSize;
|
||||
final double _height;
|
||||
final ui.TextHeightBehavior _textHeightBehavior;
|
||||
final EngineStrutStyle _strutStyle;
|
||||
final String _ellipsis;
|
||||
final ui.Locale _locale;
|
||||
@ -547,13 +550,27 @@ class EngineParagraphStyle implements ui.ParagraphStyle {
|
||||
_fontFamily == typedOther._fontFamily ||
|
||||
_fontSize == typedOther._fontSize ||
|
||||
_height == typedOther._height ||
|
||||
_textHeightBehavior == typedOther._textHeightBehavior ||
|
||||
_ellipsis == typedOther._ellipsis ||
|
||||
_locale == typedOther._locale;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
ui.hashValues(_fontFamily, _fontSize, _height, _ellipsis, _locale);
|
||||
int get hashCode {
|
||||
return ui.hashValues(
|
||||
_textAlign,
|
||||
_textDirection,
|
||||
_fontWeight,
|
||||
_fontStyle,
|
||||
_maxLines,
|
||||
_fontFamily,
|
||||
_fontSize,
|
||||
_height,
|
||||
_textHeightBehavior,
|
||||
_ellipsis,
|
||||
_locale
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
@ -564,6 +581,7 @@ class EngineParagraphStyle implements ui.ParagraphStyle {
|
||||
'fontWeight: ${_fontWeight ?? "unspecified"}, '
|
||||
'fontStyle: ${_fontStyle ?? "unspecified"}, '
|
||||
'maxLines: ${_maxLines ?? "unspecified"}, '
|
||||
'textHeightBehavior: ${_textHeightBehavior ?? "unspecified"}, '
|
||||
'fontFamily: ${_fontFamily ?? "unspecified"}, '
|
||||
'fontSize: ${_fontSize != null ? _fontSize.toStringAsFixed(1) : "unspecified"}, '
|
||||
'height: ${_height != null ? "${_height.toStringAsFixed(1)}x" : "unspecified"}, '
|
||||
|
||||
@ -424,6 +424,92 @@ enum TextDecorationStyle {
|
||||
wavy
|
||||
}
|
||||
|
||||
/// Defines how the paragraph will apply [TextStyle.height] the ascent of the
|
||||
/// first line and descent of the last line.
|
||||
///
|
||||
/// The boolean value represents whether the [TextStyle.height] modifier will
|
||||
/// be applied to the corresponding metric. By default, all properties are true,
|
||||
/// and [TextStyle.height] is applied as normal. When set to false, the font's
|
||||
/// default ascent will be used.
|
||||
class TextHeightBehavior {
|
||||
|
||||
/// Creates a new TextHeightBehavior object.
|
||||
///
|
||||
/// * applyHeightToFirstAscent: When true, the [TextStyle.height] modifier
|
||||
/// will be applied to the ascent of the first line. When false, the font's
|
||||
/// default ascent will be used.
|
||||
/// * applyHeightToLastDescent: When true, the [TextStyle.height] modifier
|
||||
/// will be applied to the descent of the last line. When false, the font's
|
||||
/// default descent will be used.
|
||||
///
|
||||
/// All properties default to true (height modifications applied as normal).
|
||||
const TextHeightBehavior({
|
||||
this.applyHeightToFirstAscent = true,
|
||||
this.applyHeightToLastDescent = true,
|
||||
});
|
||||
|
||||
/// Creates a new TextHeightBehavior object from an encoded form.
|
||||
///
|
||||
/// See [encode] for the creation of the encoded form.
|
||||
const TextHeightBehavior.fromEncoded(int encoded) : applyHeightToFirstAscent = (encoded & 0x1) == 0,
|
||||
applyHeightToLastDescent = (encoded & 0x2) == 0;
|
||||
|
||||
|
||||
/// Whether to apply the [TextStyle.height] modifier to the ascent of the first
|
||||
/// line in the paragraph.
|
||||
///
|
||||
/// When true, the [TextStyle.height] modifier will be applied to to the ascent
|
||||
/// of the first line. When false, the font's default ascent will be used and
|
||||
/// the [TextStyle.height] will have no effect on the ascent of the first line.
|
||||
///
|
||||
/// This property only has effect if a non-null [TextStyle.height] is specified.
|
||||
///
|
||||
/// Defaults to true (height modifications applied as normal).
|
||||
final bool applyHeightToFirstAscent;
|
||||
|
||||
/// Whether to apply the [TextStyle.height] modifier to the descent of the last
|
||||
/// line in the paragraph.
|
||||
///
|
||||
/// When true, the [TextStyle.height] modifier will be applied to to the descent
|
||||
/// of the last line. When false, the font's default descent will be used and
|
||||
/// the [TextStyle.height] will have no effect on the descent of the last line.
|
||||
///
|
||||
/// This property only has effect if a non-null [TextStyle.height] is specified.
|
||||
///
|
||||
/// Defaults to true (height modifications applied as normal).
|
||||
final bool applyHeightToLastDescent;
|
||||
|
||||
/// Returns an encoded int representation of this object.
|
||||
int encode() {
|
||||
return (applyHeightToFirstAscent ? 0 : 1 << 0) | (applyHeightToLastDescent ? 0 : 1 << 1);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
if (other.runtimeType != runtimeType)
|
||||
return false;
|
||||
return other is TextHeightBehavior
|
||||
&& other.applyHeightToFirstAscent == applyHeightToFirstAscent
|
||||
&& other.applyHeightToLastDescent == applyHeightToLastDescent;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return hashValues(
|
||||
applyHeightToFirstAscent,
|
||||
applyHeightToLastDescent,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'TextHeightBehavior('
|
||||
'applyHeightToFirstAscent: $applyHeightToFirstAscent, '
|
||||
'applyHeightToLastDescent: $applyHeightToLastDescent'
|
||||
')';
|
||||
}
|
||||
}
|
||||
|
||||
/// An opaque object that determines the size, position, and rendering of text.
|
||||
abstract class TextStyle {
|
||||
/// Creates a new TextStyle object.
|
||||
@ -584,6 +670,7 @@ abstract class ParagraphStyle {
|
||||
String fontFamily,
|
||||
double fontSize,
|
||||
double height,
|
||||
TextHeightBehavior textHeightBehavior,
|
||||
FontWeight fontWeight,
|
||||
FontStyle fontStyle,
|
||||
StrutStyle strutStyle,
|
||||
@ -598,6 +685,7 @@ abstract class ParagraphStyle {
|
||||
fontFamily: fontFamily,
|
||||
fontSize: fontSize,
|
||||
height: height,
|
||||
textHeightBehavior: textHeightBehavior,
|
||||
fontWeight: fontWeight,
|
||||
fontStyle: fontStyle,
|
||||
strutStyle: strutStyle,
|
||||
@ -612,6 +700,7 @@ abstract class ParagraphStyle {
|
||||
fontFamily: fontFamily,
|
||||
fontSize: fontSize,
|
||||
height: height,
|
||||
textHeightBehavior: textHeightBehavior,
|
||||
fontWeight: fontWeight,
|
||||
fontStyle: fontStyle,
|
||||
strutStyle: strutStyle,
|
||||
|
||||
@ -26,6 +26,70 @@ void main() {
|
||||
expect(FontWeight.lerp(FontWeight.w400, null, 1), equals(FontWeight.w400));
|
||||
});
|
||||
});
|
||||
|
||||
group('ParagraphStyle', () {
|
||||
final ParagraphStyle ps0 = ParagraphStyle(textDirection: TextDirection.ltr, fontSize: 14.0);
|
||||
final ParagraphStyle ps1 = ParagraphStyle(textDirection: TextDirection.rtl, fontSize: 14.0);
|
||||
final ParagraphStyle ps2 = ParagraphStyle(textAlign: TextAlign.center, fontWeight: FontWeight.w800, fontSize: 10.0, height: 100.0);
|
||||
final ParagraphStyle ps3 = ParagraphStyle(fontWeight: FontWeight.w700, fontSize: 12.0, height: 123.0);
|
||||
|
||||
test('toString works', () {
|
||||
expect(ps0.toString(), equals('ParagraphStyle(textAlign: unspecified, textDirection: TextDirection.ltr, fontWeight: unspecified, fontStyle: unspecified, maxLines: unspecified, textHeightBehavior: unspecified, fontFamily: unspecified, fontSize: 14.0, height: unspecified, ellipsis: unspecified, locale: unspecified)'));
|
||||
expect(ps1.toString(), equals('ParagraphStyle(textAlign: unspecified, textDirection: TextDirection.rtl, fontWeight: unspecified, fontStyle: unspecified, maxLines: unspecified, textHeightBehavior: unspecified, fontFamily: unspecified, fontSize: 14.0, height: unspecified, ellipsis: unspecified, locale: unspecified)'));
|
||||
expect(ps2.toString(), equals('ParagraphStyle(textAlign: TextAlign.center, textDirection: unspecified, fontWeight: FontWeight.w800, fontStyle: unspecified, maxLines: unspecified, textHeightBehavior: unspecified, fontFamily: unspecified, fontSize: 10.0, height: 100.0x, ellipsis: unspecified, locale: unspecified)'));
|
||||
expect(ps3.toString(), equals('ParagraphStyle(textAlign: unspecified, textDirection: unspecified, fontWeight: FontWeight.w700, fontStyle: unspecified, maxLines: unspecified, textHeightBehavior: unspecified, fontFamily: unspecified, fontSize: 12.0, height: 123.0x, ellipsis: unspecified, locale: unspecified)'));
|
||||
});
|
||||
});
|
||||
|
||||
group('TextHeightBehavior', () {
|
||||
const TextHeightBehavior behavior0 = TextHeightBehavior();
|
||||
const TextHeightBehavior behavior1 = TextHeightBehavior(
|
||||
applyHeightToFirstAscent: false,
|
||||
applyHeightToLastDescent: false
|
||||
);
|
||||
const TextHeightBehavior behavior2 = TextHeightBehavior(
|
||||
applyHeightToFirstAscent: false,
|
||||
);
|
||||
const TextHeightBehavior behavior3 = TextHeightBehavior(
|
||||
applyHeightToLastDescent: false
|
||||
);
|
||||
|
||||
test('default constructor works', () {
|
||||
expect(behavior0.applyHeightToFirstAscent, equals(true));
|
||||
expect(behavior0.applyHeightToLastDescent, equals(true));
|
||||
|
||||
expect(behavior1.applyHeightToFirstAscent, equals(false));
|
||||
expect(behavior1.applyHeightToLastDescent, equals(false));
|
||||
|
||||
expect(behavior2.applyHeightToFirstAscent, equals(false));
|
||||
expect(behavior2.applyHeightToLastDescent, equals(true));
|
||||
|
||||
expect(behavior3.applyHeightToFirstAscent, equals(true));
|
||||
expect(behavior3.applyHeightToLastDescent, equals(false));
|
||||
});
|
||||
|
||||
test('encode works', () {
|
||||
expect(behavior0.encode(), equals(0));
|
||||
expect(behavior1.encode(), equals(3));
|
||||
expect(behavior2.encode(), equals(1));
|
||||
expect(behavior3.encode(), equals(2));
|
||||
});
|
||||
|
||||
test('decode works', () {
|
||||
expect(const TextHeightBehavior.fromEncoded(0), equals(behavior0));
|
||||
expect(const TextHeightBehavior.fromEncoded(3), equals(behavior1));
|
||||
expect(const TextHeightBehavior.fromEncoded(1), equals(behavior2));
|
||||
expect(const TextHeightBehavior.fromEncoded(2), equals(behavior3));
|
||||
});
|
||||
|
||||
test('toString works', () {
|
||||
expect(behavior0.toString(), equals('TextHeightBehavior(applyHeightToFirstAscent: true, applyHeightToLastDescent: true)'));
|
||||
expect(behavior1.toString(), equals('TextHeightBehavior(applyHeightToFirstAscent: false, applyHeightToLastDescent: false)'));
|
||||
expect(behavior2.toString(), equals('TextHeightBehavior(applyHeightToFirstAscent: false, applyHeightToLastDescent: true)'));
|
||||
expect(behavior3.toString(), equals('TextHeightBehavior(applyHeightToFirstAscent: true, applyHeightToLastDescent: false)'));
|
||||
});
|
||||
});
|
||||
|
||||
group('TextRange', () {
|
||||
test('empty ranges are correct', () {
|
||||
const TextRange range = TextRange(start: -1, end: -1);
|
||||
@ -81,6 +145,7 @@ void main() {
|
||||
expect(const TextRange(start: 0, end: 5).textInside('hello'), equals('hello'));
|
||||
});
|
||||
});
|
||||
|
||||
group('loadFontFromList', () {
|
||||
test('will send platform message after font is loaded', () async {
|
||||
final PlatformMessageCallback oldHandler = window.onPlatformMessage;
|
||||
|
||||
@ -41,6 +41,25 @@ enum class TextDirection {
|
||||
ltr,
|
||||
};
|
||||
|
||||
// Allows disabling height adjustments to first line's ascent and the
|
||||
// last line's descent. If disabled, the line will use the default font
|
||||
// metric provided ascent/descent and ParagraphStyle.height will not take
|
||||
// effect.
|
||||
//
|
||||
// The default behavior is kAll where height adjustments are enabled for all
|
||||
// lines.
|
||||
//
|
||||
// Multiple behaviors can be applied at once with a bitwise | operator. For
|
||||
// example, disabling first ascent and last descent can achieved with:
|
||||
//
|
||||
// (kDisableFirstAscent | kDisableLastDescent).
|
||||
enum TextHeightBehavior {
|
||||
kAll = 0x0,
|
||||
kDisableFirstAscent = 0x1,
|
||||
kDisableLastDescent = 0x2,
|
||||
kDisableAll = 0x1 | 0x2,
|
||||
};
|
||||
|
||||
class ParagraphStyle {
|
||||
public:
|
||||
// Default TextStyle. Used in GetTextStyle() to obtain the base TextStyle to
|
||||
@ -50,6 +69,7 @@ class ParagraphStyle {
|
||||
std::string font_family = "";
|
||||
double font_size = 14;
|
||||
double height = 1;
|
||||
size_t text_height_behavior = TextHeightBehavior::kAll;
|
||||
bool has_height_override = false;
|
||||
|
||||
// Strut properties. strut_enabled must be set to true for the rest of the
|
||||
|
||||
@ -1113,7 +1113,8 @@ void ParagraphTxt::Layout(double width) {
|
||||
for (const PaintRecord& paint_record : paint_records) {
|
||||
UpdateLineMetrics(paint_record.metrics(), paint_record.style(),
|
||||
max_ascent, max_descent, max_unscaled_ascent,
|
||||
paint_record.GetPlaceholderRun());
|
||||
paint_record.GetPlaceholderRun(), line_number,
|
||||
line_limit);
|
||||
}
|
||||
|
||||
// If no fonts were actually rendered, then compute a baseline based on the
|
||||
@ -1125,7 +1126,7 @@ void ParagraphTxt::Layout(double width) {
|
||||
font.setSize(style.font_size);
|
||||
font.getMetrics(&metrics);
|
||||
UpdateLineMetrics(metrics, style, max_ascent, max_descent,
|
||||
max_unscaled_ascent, nullptr);
|
||||
max_unscaled_ascent, nullptr, line_number, line_limit);
|
||||
}
|
||||
|
||||
// Calculate the baselines. This is only done on the first line.
|
||||
@ -1181,7 +1182,9 @@ void ParagraphTxt::UpdateLineMetrics(const SkFontMetrics& metrics,
|
||||
double& max_ascent,
|
||||
double& max_descent,
|
||||
double& max_unscaled_ascent,
|
||||
PlaceholderRun* placeholder_run) {
|
||||
PlaceholderRun* placeholder_run,
|
||||
size_t line_number,
|
||||
size_t line_limit) {
|
||||
if (!strut_.force_strut) {
|
||||
double ascent;
|
||||
double descent;
|
||||
@ -1242,6 +1245,21 @@ void ParagraphTxt::UpdateLineMetrics(const SkFontMetrics& metrics,
|
||||
ascent = (-metrics.fAscent + metrics.fLeading / 2);
|
||||
descent = (metrics.fDescent + metrics.fLeading / 2);
|
||||
}
|
||||
|
||||
// Account for text_height_behavior in paragraph_style_.
|
||||
//
|
||||
// Disable first line ascent modifications.
|
||||
if (line_number == 0 && paragraph_style_.text_height_behavior &
|
||||
TextHeightBehavior::kDisableFirstAscent) {
|
||||
ascent = -metrics.fAscent;
|
||||
}
|
||||
// Disable last line descent modifications.
|
||||
if (line_number == line_limit - 1 &&
|
||||
paragraph_style_.text_height_behavior &
|
||||
TextHeightBehavior::kDisableLastDescent) {
|
||||
descent = metrics.fDescent;
|
||||
}
|
||||
|
||||
ComputePlaceholder(placeholder_run, ascent, descent);
|
||||
|
||||
max_ascent = std::max(ascent, max_ascent);
|
||||
|
||||
@ -162,6 +162,7 @@ class ParagraphTxt : public Paragraph {
|
||||
FRIEND_TEST(ParagraphTest, FontFeaturesParagraph);
|
||||
FRIEND_TEST(ParagraphTest, GetGlyphPositionAtCoordinateSegfault);
|
||||
FRIEND_TEST(ParagraphTest, KhmerLineBreaker);
|
||||
FRIEND_TEST(ParagraphTest, TextHeightBehaviorRectsParagraph);
|
||||
|
||||
// Starting data to layout.
|
||||
std::vector<uint16_t> text_;
|
||||
@ -368,7 +369,10 @@ class ParagraphTxt : public Paragraph {
|
||||
double& max_ascent,
|
||||
double& max_descent,
|
||||
double& max_unscaled_ascent,
|
||||
PlaceholderRun* placeholder_run);
|
||||
PlaceholderRun* placeholder_run,
|
||||
size_t line_number,
|
||||
size_t line_limit);
|
||||
|
||||
// Calculate the starting X offset of a line based on the line's width and
|
||||
// alignment.
|
||||
double GetLineXOffset(double line_total_advance,
|
||||
|
||||
@ -5877,4 +5877,102 @@ TEST_F(ParagraphTest, KhmerLineBreaker) {
|
||||
ASSERT_TRUE(Snapshot());
|
||||
}
|
||||
|
||||
TEST_F(ParagraphTest, TextHeightBehaviorRectsParagraph) {
|
||||
// clang-format off
|
||||
const char* text =
|
||||
"line1\nline2\nline3";
|
||||
// clang-format on
|
||||
auto icu_text = icu::UnicodeString::fromUTF8(text);
|
||||
std::u16string u16_text(icu_text.getBuffer(),
|
||||
icu_text.getBuffer() + icu_text.length());
|
||||
|
||||
txt::ParagraphStyle paragraph_style;
|
||||
paragraph_style.text_height_behavior =
|
||||
txt::TextHeightBehavior::kDisableFirstAscent |
|
||||
txt::TextHeightBehavior::kDisableLastDescent;
|
||||
|
||||
txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());
|
||||
|
||||
txt::TextStyle text_style;
|
||||
text_style.color = SK_ColorBLACK;
|
||||
text_style.font_families = std::vector<std::string>(1, "Roboto");
|
||||
text_style.font_size = 30;
|
||||
text_style.height = 5;
|
||||
text_style.has_height_override = true;
|
||||
builder.PushStyle(text_style);
|
||||
builder.AddText(u16_text);
|
||||
|
||||
builder.Pop();
|
||||
|
||||
auto paragraph = BuildParagraph(builder);
|
||||
paragraph->Layout(GetTestCanvasWidth() - 300);
|
||||
|
||||
paragraph->Paint(GetCanvas(), 0, 0);
|
||||
|
||||
for (size_t i = 0; i < u16_text.length(); i++) {
|
||||
ASSERT_EQ(paragraph->text_[i], u16_text[i]);
|
||||
}
|
||||
|
||||
ASSERT_EQ(paragraph->records_.size(), 3ull);
|
||||
|
||||
SkPaint paint;
|
||||
paint.setStyle(SkPaint::kStroke_Style);
|
||||
paint.setAntiAlias(true);
|
||||
paint.setStrokeWidth(1);
|
||||
|
||||
// Tests for GetRectsForRange()
|
||||
Paragraph::RectHeightStyle rect_height_style =
|
||||
Paragraph::RectHeightStyle::kMax;
|
||||
Paragraph::RectWidthStyle rect_width_style =
|
||||
Paragraph::RectWidthStyle::kTight;
|
||||
paint.setColor(SK_ColorRED);
|
||||
std::vector<txt::Paragraph::TextBox> boxes =
|
||||
paragraph->GetRectsForRange(0, 0, rect_height_style, rect_width_style);
|
||||
for (size_t i = 0; i < boxes.size(); ++i) {
|
||||
GetCanvas()->drawRect(boxes[i].rect, paint);
|
||||
}
|
||||
EXPECT_EQ(boxes.size(), 0ull);
|
||||
|
||||
// First line. Shorter due to disabled height modifications on first ascent.
|
||||
boxes =
|
||||
paragraph->GetRectsForRange(0, 3, rect_height_style, rect_width_style);
|
||||
for (size_t i = 0; i < boxes.size(); ++i) {
|
||||
GetCanvas()->drawRect(boxes[i].rect, paint);
|
||||
}
|
||||
EXPECT_EQ(boxes.size(), 1ull);
|
||||
EXPECT_FLOAT_EQ(boxes[0].rect.left(), 0);
|
||||
EXPECT_FLOAT_EQ(boxes[0].rect.right(), 31.117188);
|
||||
EXPECT_FLOAT_EQ(boxes[0].rect.top(), -0.08203125);
|
||||
EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 59);
|
||||
EXPECT_FLOAT_EQ(boxes[0].rect.bottom() - boxes[0].rect.top(), 59.082031);
|
||||
|
||||
// Second line. Normal.
|
||||
boxes =
|
||||
paragraph->GetRectsForRange(6, 10, rect_height_style, rect_width_style);
|
||||
for (size_t i = 0; i < boxes.size(); ++i) {
|
||||
GetCanvas()->drawRect(boxes[i].rect, paint);
|
||||
}
|
||||
EXPECT_EQ(boxes.size(), 1ull);
|
||||
EXPECT_FLOAT_EQ(boxes[0].rect.left(), 0);
|
||||
EXPECT_FLOAT_EQ(boxes[0].rect.right(), 47.011719);
|
||||
EXPECT_FLOAT_EQ(boxes[0].rect.top(), 59);
|
||||
EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 209);
|
||||
EXPECT_FLOAT_EQ(boxes[0].rect.bottom() - boxes[0].rect.top(), 150);
|
||||
|
||||
// Third line. Shorter due to disabled height modifications on last descent
|
||||
boxes =
|
||||
paragraph->GetRectsForRange(12, 17, rect_height_style, rect_width_style);
|
||||
for (size_t i = 0; i < boxes.size(); ++i) {
|
||||
GetCanvas()->drawRect(boxes[i].rect, paint);
|
||||
}
|
||||
EXPECT_EQ(boxes.size(), 1ull);
|
||||
EXPECT_FLOAT_EQ(boxes[0].rect.left(), 0);
|
||||
EXPECT_FLOAT_EQ(boxes[0].rect.right(), 63.859375);
|
||||
EXPECT_FLOAT_EQ(boxes[0].rect.top(), 208.92578);
|
||||
EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 335);
|
||||
EXPECT_FLOAT_EQ(boxes[0].rect.bottom() - boxes[0].rect.top(), 126.07422);
|
||||
|
||||
ASSERT_TRUE(Snapshot());
|
||||
}
|
||||
|
||||
} // namespace txt
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user