diff --git a/lib/ui/text.dart b/lib/ui/text.dart index 19b06a906e878..fa5092cac172f 100644 --- a/lib/ui/text.dart +++ b/lib/ui/text.dart @@ -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,28 +871,32 @@ Int32List _encodeParagraphStyle( result[0] |= 1 << 5; result[5] = maxLines; } - if (fontFamily != null) { + if (textHeightBehavior != null) { result[0] |= 1 << 6; + result[6] = textHeightBehavior.encode(); + } + if (fontFamily != null) { + result[0] |= 1 << 7; // Passed separately to native. } if (fontSize != null) { - result[0] |= 1 << 7; + result[0] |= 1 << 8; // Passed separately to native. } if (height != null) { - result[0] |= 1 << 8; + result[0] |= 1 << 9; // Passed separately to native. } if (strutStyle != null) { - result[0] |= 1 << 9; + result[0] |= 1 << 10; // Passed separately to native. } if (ellipsis != null) { - result[0] |= 1 << 10; + result[0] |= 1 << 11; // Passed separately to native. } if (locale != null) { - result[0] |= 1 << 11; + 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"}' ')'; } } diff --git a/lib/ui/text/paragraph_builder.cc b/lib/ui/text/paragraph_builder.cc index 8c858e6953ba9..51739fc2cf3fd 100644 --- a/lib/ui/text/paragraph_builder.cc +++ b/lib/ui/text/paragraph_builder.cc @@ -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); } diff --git a/lib/web_ui/lib/src/engine/compositor/text.dart b/lib/web_ui/lib/src/engine/compositor/text.dart index 06f6805b88020..a9cf6578f5c96 100644 --- a/lib/web_ui/lib/src/engine/compositor/text.dart +++ b/lib/web_ui/lib/src/engine/compositor/text.dart @@ -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; } diff --git a/lib/web_ui/lib/src/engine/text/paragraph.dart b/lib/web_ui/lib/src/engine/text/paragraph.dart index b4c5912c7946e..80ba65dbd8bdb 100644 --- a/lib/web_ui/lib/src/engine/text/paragraph.dart +++ b/lib/web_ui/lib/src/engine/text/paragraph.dart @@ -457,6 +457,7 @@ class EngineParagraphStyle implements ui.ParagraphStyle { String fontFamily, double fontSize, double height, + ui.TextHeightBehavior textHeightBehavior, ui.FontWeight fontWeight, ui.FontStyle fontStyle, ui.StrutStyle strutStyle, @@ -470,6 +471,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, @@ -483,6 +485,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; @@ -536,6 +539,7 @@ class EngineParagraphStyle implements ui.ParagraphStyle { _fontFamily == typedOther._fontFamily || _fontSize == typedOther._fontSize || _height == typedOther._height || + _textHeightBehavior == typedOther._textHeightBehavior || _ellipsis == typedOther._ellipsis || _locale == typedOther._locale; } @@ -556,6 +560,7 @@ class EngineParagraphStyle implements ui.ParagraphStyle { 'fontFamily: ${_fontFamily ?? "unspecified"}, ' 'fontSize: ${_fontSize != null ? _fontSize.toStringAsFixed(1) : "unspecified"}, ' 'height: ${_height != null ? "${_height.toStringAsFixed(1)}x" : "unspecified"}, ' + 'textHeightBehavior: ${_textHeightBehavior ?? "unspecified"}, ' 'ellipsis: ${_ellipsis != null ? "\"$_ellipsis\"" : "unspecified"}, ' 'locale: ${_locale ?? "unspecified"}' ')'; diff --git a/lib/web_ui/lib/src/ui/text.dart b/lib/web_ui/lib/src/ui/text.dart index 6b57d37f2f168..b479db85c51c7 100644 --- a/lib/web_ui/lib/src/ui/text.dart +++ b/lib/web_ui/lib/src/ui/text.dart @@ -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, diff --git a/testing/dart/text_test.dart b/testing/dart/text_test.dart index df1f9a8384af3..2ca0d726f9fdb 100644 --- a/testing/dart/text_test.dart +++ b/testing/dart/text_test.dart @@ -26,6 +26,56 @@ void main() { expect(FontWeight.lerp(FontWeight.w400, null, 1), equals(FontWeight.w400)); }); }); + + 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); diff --git a/third_party/txt/src/txt/paragraph_style.h b/third_party/txt/src/txt/paragraph_style.h index 9ca5fae3ca9c4..46ea7a1e7c1a8 100644 --- a/third_party/txt/src/txt/paragraph_style.h +++ b/third_party/txt/src/txt/paragraph_style.h @@ -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 diff --git a/third_party/txt/src/txt/paragraph_txt.cc b/third_party/txt/src/txt/paragraph_txt.cc index 4b28234e83876..0554091ceeb72 100644 --- a/third_party/txt/src/txt/paragraph_txt.cc +++ b/third_party/txt/src/txt/paragraph_txt.cc @@ -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); diff --git a/third_party/txt/src/txt/paragraph_txt.h b/third_party/txt/src/txt/paragraph_txt.h index 7cb1cce73f8e2..bb95e6e7a0053 100644 --- a/third_party/txt/src/txt/paragraph_txt.h +++ b/third_party/txt/src/txt/paragraph_txt.h @@ -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 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, diff --git a/third_party/txt/tests/paragraph_unittests.cc b/third_party/txt/tests/paragraph_unittests.cc index 057273eddf9c7..6122be579929b 100644 --- a/third_party/txt/tests/paragraph_unittests.cc +++ b/third_party/txt/tests/paragraph_unittests.cc @@ -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(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 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