diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/text/canvas_paragraph.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/text/canvas_paragraph.dart index de266ad59db..0134a3d4c42 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/text/canvas_paragraph.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/text/canvas_paragraph.dart @@ -18,6 +18,7 @@ class CanvasParagraph implements EngineParagraph { CanvasParagraph( this.spans, { required this.paragraphStyle, + required this.plainText, required this.placeholderCount, }); @@ -27,6 +28,9 @@ class CanvasParagraph implements EngineParagraph { /// General styling information for this paragraph. final EngineParagraphStyle paragraphStyle; + /// The full textual content of the paragraph. + final String plainText; + /// The number of placeholders in this paragraph. final int placeholderCount; @@ -108,26 +112,8 @@ class CanvasParagraph implements EngineParagraph { // 3. Paint the text. } - String? _cachedPlainText; - @override - String toPlainText() { - final String? plainText = _cachedPlainText; - if (plainText == null) { - return _cachedPlainText ??= _computePlainText(); - } - return plainText; - } - - String _computePlainText() { - final StringBuffer buffer = StringBuffer(); - for (final ParagraphSpan span in spans) { - if (span is FlatTextSpan) { - buffer.write(span.text); - } - } - return buffer.toString(); - } + String toPlainText() => plainText; html.HtmlElement? _cachedDomElement; @@ -269,14 +255,28 @@ abstract class ParagraphSpan { /// does, we flatten the structure and resolve/merge all the styles from parent /// nodes. class FlatTextSpan extends ParagraphSpan { - /// Creates a [FlatTextSpan] with the given [text] and [style]. - const FlatTextSpan({required this.text, required this.style}); - - /// The textual content of the span. - final String text; + /// Creates a [FlatTextSpan] with the given [style], representing the span of + /// text in the range between [start] and [end]. + FlatTextSpan({ + required this.style, + required this.start, + required this.end, + }); /// The resolved style of the span. final EngineTextStyle style; + + /// The index of the beginning of the range of text represented by this span. + final int start; + + /// The index of the end of the range of text represented by this span. + final int end; + + String textOf(CanvasParagraph paragraph) { + final String text = paragraph.toPlainText(); + assert(end <= text.length); + return text.substring(start, end); + } } /// Represents a node in the tree of text styles pushed to [ui.ParagraphBuilder]. @@ -511,6 +511,7 @@ class CanvasParagraphBuilder implements ui.ParagraphBuilder { : _paragraphStyle = style, _rootStyleNode = RootStyleNode(style); + final StringBuffer _plainTextBuffer = StringBuffer(); final EngineParagraphStyle _paragraphStyle; final List _spans = []; @@ -573,8 +574,12 @@ class CanvasParagraphBuilder implements ui.ParagraphBuilder { @override void addText(String text) { - _spans - .add(FlatTextSpan(text: text, style: _currentStyleNode.resolveStyle())); + final EngineTextStyle style = _currentStyleNode.resolveStyle(); + final int start = _plainTextBuffer.length; + _plainTextBuffer.write(text); + final int end = _plainTextBuffer.length; + + _spans.add(FlatTextSpan(style: style, start: start, end: end)); } @override @@ -582,6 +587,7 @@ class CanvasParagraphBuilder implements ui.ParagraphBuilder { return CanvasParagraph( _spans, paragraphStyle: _paragraphStyle, + plainText: _plainTextBuffer.toString(), placeholderCount: _placeholderCount, ); } diff --git a/engine/src/flutter/lib/web_ui/test/text/canvas_paragraph_builder_test.dart b/engine/src/flutter/lib/web_ui/test/text/canvas_paragraph_builder_test.dart index 2f354a6b35c..ecacd78b41d 100644 --- a/engine/src/flutter/lib/web_ui/test/text/canvas_paragraph_builder_test.dart +++ b/engine/src/flutter/lib/web_ui/test/text/canvas_paragraph_builder_test.dart @@ -34,7 +34,7 @@ void testMain() { final ParagraphSpan span = paragraph.spans.single; expect(span, isA()); final FlatTextSpan textSpan = span as FlatTextSpan; - expect(textSpan.text, 'Hello'); + expect(textSpan.textOf(paragraph), 'Hello'); expect(textSpan.style, TextStyle(fontSize: 13.0)); }); @@ -56,7 +56,7 @@ void testMain() { expect(paragraph.spans, hasLength(1)); final FlatTextSpan span = paragraph.spans.single as FlatTextSpan; - expect(span.text, 'Hello'); + expect(span.textOf(paragraph), 'Hello'); expect( span.style, TextStyle( @@ -84,7 +84,7 @@ void testMain() { expect(paragraph.spans, hasLength(2)); final FlatTextSpan hello = paragraph.spans.first as FlatTextSpan; - expect(hello.text, 'Hello'); + expect(hello.textOf(paragraph), 'Hello'); expect( hello.style, TextStyle( @@ -94,7 +94,7 @@ void testMain() { ); final FlatTextSpan world = paragraph.spans.last as FlatTextSpan; - expect(world.text, ' world'); + expect(world.textOf(paragraph), ' world'); expect( world.style, TextStyle( @@ -122,14 +122,14 @@ void testMain() { expect(paragraph.spans, hasLength(3)); final FlatTextSpan hello = paragraph.spans[0] as FlatTextSpan; - expect(hello.text, 'Hello'); + expect(hello.textOf(paragraph), 'Hello'); expect( hello.style, TextStyle(fontSize: 13.0, fontWeight: FontWeight.bold, height: 2.0), ); final FlatTextSpan world = paragraph.spans[1] as FlatTextSpan; - expect(world.text, ' world'); + expect(world.textOf(paragraph), ' world'); expect( world.style, TextStyle( @@ -140,7 +140,7 @@ void testMain() { ); final FlatTextSpan bang = paragraph.spans[2] as FlatTextSpan; - expect(bang.text, '!'); + expect(bang.textOf(paragraph), '!'); expect( bang.style, TextStyle(