From b4c9a226cd64c156ea791da20184bf841029bbc9 Mon Sep 17 00:00:00 2001 From: LongCatIsLooong <31859944+LongCatIsLooong@users.noreply.github.com> Date: Mon, 26 Aug 2024 16:34:17 -0700 Subject: [PATCH] fix `getFullHeightForCaret` when strut is disabled. (#154039) Fixes https://github.com/flutter/flutter/issues/150638 --- .../lib/src/painting/text_painter.dart | 29 +++++++++++++++---- .../test/painting/text_painter_test.dart | 22 ++++++++++++++ 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/packages/flutter/lib/src/painting/text_painter.dart b/packages/flutter/lib/src/painting/text_painter.dart index 9fbeeefe4fe..a4774dcd928 100644 --- a/packages/flutter/lib/src/painting/text_painter.dart +++ b/packages/flutter/lib/src/painting/text_painter.dart @@ -371,6 +371,7 @@ class _TextLayout { final double baseline = lineMetrics.baseline; final double dx; + final double height; late final ui.GlyphInfo? lastGlyph = _paragraph.getGlyphInfoAt(rawString.length - 1); // TODO(LongCatIsLooong): handle the case where maxLine is set to non-null // and the last line ends with trailing whitespaces. @@ -381,13 +382,15 @@ class _TextLayout { TextDirection.ltr => glyphBounds.right, TextDirection.rtl => glyphBounds.left, }; + height = glyphBounds.height; } else { dx = switch (writingDirection) { TextDirection.ltr => lineMetrics.left + lineMetrics.width, TextDirection.rtl => lineMetrics.left, }; + height = lineMetrics.height; } - return _LineCaretMetrics(offset: Offset(dx, baseline), writingDirection: writingDirection); + return _LineCaretMetrics(offset: Offset(dx, baseline), writingDirection: writingDirection, height: height); } double _contentWidthFor(double minWidth, double maxWidth, TextWidthBasis widthBasis) { @@ -501,8 +504,8 @@ class _TextPainterLayoutCacheWithOffset { /// The _CaretMetrics for carets located in a non-empty paragraph. Such carets /// are anchored to the trailing edge or the leading edge of a glyph, or a /// ligature component. -final class _LineCaretMetrics { - const _LineCaretMetrics({required this.offset, required this.writingDirection}); +class _LineCaretMetrics { + const _LineCaretMetrics({required this.offset, required this.writingDirection, required this.height}); /// The offset from the top left corner of the paragraph to the caret's top /// start location. final Offset offset; @@ -512,10 +515,13 @@ final class _LineCaretMetrics { /// right of [offset]. final TextDirection writingDirection; + /// The recommended height of the caret. + final double height; + _LineCaretMetrics shift(Offset offset) { return offset == Offset.zero ? this - : _LineCaretMetrics(offset: offset + this.offset, writingDirection: writingDirection); + : _LineCaretMetrics(offset: offset + this.offset, writingDirection: writingDirection, height: height); } } @@ -1367,6 +1373,16 @@ class TextPainter { /// /// Valid only after [layout] has been called. double getFullHeightForCaret(TextPosition position, Rect caretPrototype) { + // The if condition is derived from + // https://github.com/google/skia/blob/0086a17e0d4cc676cf88cae671ba5ee967eb7241/modules/skparagraph/src/TextLine.cpp#L1244-L1246 + // which is set here: + // https://github.com/flutter/engine/blob/a821b8790c9fd0e095013cd5bd1f20273bc1ee47/third_party/txt/src/skia/paragraph_builder_skia.cc#L134 + if (strutStyle == null || strutStyle == StrutStyle.disabled || strutStyle?.fontSize == 0.0) { + final double? heightFromCaretMetrics = _computeCaretMetrics(position)?.height; + if (heightFromCaretMetrics != null) { + return heightFromCaretMetrics; + } + } final TextBox textBox = _getOrCreateLayoutTemplate().getBoxesForRange(0, 1, boxHeightStyle: ui.BoxHeightStyle.strut).single; return textBox.toRect().height; } @@ -1450,7 +1466,8 @@ class TextPainter { if (glyphInfo == null) { // If the glyph isn't laid out, then the position points to a character - // that is not laid out. Use the EOT caret. + // that is not laid out (the part of text is invisible due to maxLines or + // infinite paragraph x offset). Use the EOT caret. // TODO(LongCatIsLooong): assert when an invalid position is given. final ui.Paragraph template = _getOrCreateLayoutTemplate(); assert(template.numberOfLines == 1); @@ -1487,6 +1504,7 @@ class TextPainter { metrics = _LineCaretMetrics( offset: Offset(anchorToLeft ? box.left : box.right, box.top), writingDirection: box.direction, + height: box.bottom - box.top, ); } else { // Fall back to glyphInfo. This should only happen when using the HTML renderer. @@ -1499,6 +1517,7 @@ class TextPainter { metrics = _LineCaretMetrics( offset: Offset(dx, graphemeBounds.top), writingDirection: glyphInfo.writingDirection, + height: graphemeBounds.height, ); } diff --git a/packages/flutter/test/painting/text_painter_test.dart b/packages/flutter/test/painting/text_painter_test.dart index b8293455ae8..8c4fdf7a606 100644 --- a/packages/flutter/test/painting/text_painter_test.dart +++ b/packages/flutter/test/painting/text_painter_test.dart @@ -857,6 +857,28 @@ void main() { expect(caretOffset.dy, 0.0); painter.dispose(); }); + + test('caret height reflects run height if strut is disabled', () { + const TextSpan span = TextSpan(text: 'M', style: TextStyle(fontSize: 128), children: [ + TextSpan(text: 'M', style: TextStyle(fontSize: 32)), + TextSpan(text: 'M', style: TextStyle(fontSize: 64)), + ]); + final TextPainter painter = TextPainter() + ..textDirection = TextDirection.ltr + ..text = span + ..layout(); + + expect(painter.getFullHeightForCaret(const TextPosition(offset: 0, affinity: ui.TextAffinity.upstream), Rect.zero), 128.0); + expect(painter.getFullHeightForCaret(const TextPosition(offset: 0), Rect.zero), 128.0); + expect(painter.getFullHeightForCaret(const TextPosition(offset: 1, affinity: ui.TextAffinity.upstream), Rect.zero), 128.0); + expect(painter.getFullHeightForCaret(const TextPosition(offset: 1), Rect.zero), 32.0); + expect(painter.getFullHeightForCaret(const TextPosition(offset: 2, affinity: ui.TextAffinity.upstream), Rect.zero), 32.0); + expect(painter.getFullHeightForCaret(const TextPosition(offset: 2), Rect.zero), 64.0); + expect(painter.getFullHeightForCaret(const TextPosition(offset: 3, affinity: ui.TextAffinity.upstream), Rect.zero), 64.0); + expect(painter.getFullHeightForCaret(const TextPosition(offset: 3), Rect.zero), 128.0); + + painter.dispose(); + }); }); test('TextPainter error test', () {