diff --git a/lib/web_ui/dev/goldens_lock.yaml b/lib/web_ui/dev/goldens_lock.yaml index c848de7900609aab82747bcbf4950e8fd13dc565..3ddfa4a9ca48c2a5ef34bf1075edc0517898b200 100644 --- a/lib/web_ui/dev/goldens_lock.yaml +++ b/lib/web_ui/dev/goldens_lock.yaml @@ -1,2 +1,2 @@ repository: https://github.com/flutter/goldens.git -revision: d4599aade933fe5c9c74d4c4acc08649ab6146bc +revision: bb55871d3803337053f7200b8690a4c1322e82ea diff --git a/lib/web_ui/lib/src/engine/text/layout_service.dart b/lib/web_ui/lib/src/engine/text/layout_service.dart index 745f93ae0b15c0ccae3e383cace26b7d7b40b8d1..5a47175da03e0bdd0222493e879cf6a5e9881ab9 100644 --- a/lib/web_ui/lib/src/engine/text/layout_service.dart +++ b/lib/web_ui/lib/src/engine/text/layout_service.dart @@ -78,6 +78,7 @@ class TextLayoutService { final Spanometer spanometer = Spanometer(paragraph, context); int spanIndex = 0; + ParagraphSpan span = paragraph.spans[0]; LineBuilder currentLine = LineBuilder.first(paragraph, spanometer, maxWidth: constraints.width); @@ -85,33 +86,28 @@ class TextLayoutService { // statements (e.g. when we reach `endOfText`, when ellipsis has been // appended). while (true) { - // ************************** // - // *** HANDLE END OF TEXT *** // - // ************************** // - - // All spans have been consumed. - final bool reachedEnd = spanIndex == spanCount; - if (reachedEnd) { - // In some cases, we need to extend the line to the end of text and - // build it: - // - // 1. Line is not empty. This could happen when the last span is a - // placeholder. - // - // 2. We haven't reached `LineBreakType.endOfText` yet. This could - // happen when the last character is a new line. - if (currentLine.isNotEmpty || currentLine.end.type != LineBreakType.endOfText) { - currentLine.extendToEndOfText(); + // *********************************************** // + // *** HANDLE HARD LINE BREAKS AND END OF TEXT *** // + // *********************************************** // + + if (currentLine.end.isHard) { + if (currentLine.isNotEmpty) { lines.add(currentLine.build()); + if (currentLine.end.type != LineBreakType.endOfText) { + currentLine = currentLine.nextLine(); + } + } + + if (currentLine.end.type == LineBreakType.endOfText) { + break; } - break; } // ********************************* // // *** THE MAIN MEASUREMENT PART *** // // ********************************* // - final ParagraphSpan span = paragraph.spans[spanIndex]; + final isLastSpan = spanIndex == spanCount - 1; if (span is PlaceholderSpan) { if (currentLine.widthIncludingSpace + span.width <= constraints.width) { @@ -125,7 +121,11 @@ class TextLayoutService { } currentLine.addPlaceholder(span); } - spanIndex++; + + if (isLastSpan) { + lines.add(currentLine.build()); + break; + } } else if (span is FlatTextSpan) { spanometer.currentSpan = span; final LineBreakResult nextBreak = currentLine.findNextBreak(span.end); @@ -138,10 +138,6 @@ class TextLayoutService { // The line can extend to `nextBreak` without overflowing. currentLine.extendTo(nextBreak); - if (nextBreak.type == LineBreakType.mandatory) { - lines.add(currentLine.build()); - currentLine = currentLine.nextLine(); - } } else { // The chunk of text can't fit into the current line. final bool isLastLine = @@ -169,12 +165,6 @@ class TextLayoutService { currentLine = currentLine.nextLine(); } } - - // Only go to the next span if we've reached the end of this span. - if (currentLine.end.index >= span.end) { - currentLine.createBox(); - ++spanIndex; - } } else { throw UnimplementedError('Unknown span type: ${span.runtimeType}'); } @@ -182,6 +172,16 @@ class TextLayoutService { if (lines.length == maxLines) { break; } + + // ********************************************* // + // *** ADVANCE TO THE NEXT SPAN IF NECESSARY *** // + // ********************************************* // + + // Only go to the next span if we've reached the end of this span. + if (currentLine.end.index >= span.end && spanIndex < spanCount - 1) { + currentLine.createBox(); + span = paragraph.spans[++spanIndex]; + } } // ************************************************** // @@ -205,15 +205,13 @@ class TextLayoutService { // ******************************** // spanIndex = 0; + span = paragraph.spans[0]; currentLine = LineBuilder.first(paragraph, spanometer, maxWidth: constraints.width); - while (spanIndex < spanCount) { - final ParagraphSpan span = paragraph.spans[spanIndex]; - + while (currentLine.end.type != LineBreakType.endOfText) { if (span is PlaceholderSpan) { currentLine.addPlaceholder(span); - spanIndex++; } else if (span is FlatTextSpan) { spanometer.currentSpan = span; final LineBreakResult nextBreak = currentLine.findNextBreak(span.end); @@ -221,11 +219,6 @@ class TextLayoutService { // For the purpose of max intrinsic width, we don't care if the line // fits within the constraints or not. So we always extend it. currentLine.extendTo(nextBreak); - - // Only go to the next span if we've reached the end of this span. - if (currentLine.end.index >= span.end) { - spanIndex++; - } } final double widthOfLastSegment = currentLine.lastSegment.width; @@ -238,9 +231,19 @@ class TextLayoutService { maxIntrinsicWidth = currentLine.widthIncludingSpace; } - if (currentLine.end.type == LineBreakType.mandatory) { + if (currentLine.end.isHard) { currentLine = currentLine.nextLine(); } + + // Only go to the next span if we've reached the end of this span. + if (currentLine.end.index >= span.end) { + if (spanIndex < spanCount - 1) { + span = paragraph.spans[++spanIndex]; + } else { + // We reached the end of the last span in the paragraph. + break; + } + } } } @@ -773,23 +776,6 @@ class LineBuilder { _addSegment(_createSegment(newEnd)); } - void extendToEndOfText() { - final LineBreakResult endOfText = LineBreakResult.sameIndex( - paragraph.toPlainText().length, - LineBreakType.endOfText, - ); - - // The spanometer may not be ready in some cases. E.g. when the paragraph - // is made up of only placeholders and no text. - if (spanometer.isReady) { - ascent = math.max(ascent, spanometer.ascent); - descent = math.max(descent, spanometer.descent); - _addSegment(_createSegment(endOfText)); - } else { - end = endOfText; - } - } - void addPlaceholder(PlaceholderSpan placeholder) { // Increase the line's height to fit the placeholder, if necessary. final double ascent, descent; @@ -1038,7 +1024,7 @@ class LineBuilder { final LineBreakResult boxEnd = end; // Avoid creating empty boxes. This could happen when the end of a span // coincides with the end of a line. In this case, `createBox` is called twice. - if (boxStart.index == boxEnd.index) { + if (boxStart == boxEnd) { return; } @@ -1164,9 +1150,6 @@ class Spanometer { } } - /// Whether the spanometer is ready to take measurements. - bool get isReady => _currentSpan != null; - /// The distance from the top of the current span to the alphabetic baseline. double get ascent => _currentRuler!.alphabeticBaseline; diff --git a/lib/web_ui/test/golden_tests/engine/canvas_paragraph/placeholders_test.dart b/lib/web_ui/test/golden_tests/engine/canvas_paragraph/placeholders_test.dart index a1764aa33ddd82c935c0dda6aa9b96b3960e4c69..45440b03dc3a98a658967385bc2b7887ab8628e6 100644 --- a/lib/web_ui/test/golden_tests/engine/canvas_paragraph/placeholders_test.dart +++ b/lib/web_ui/test/golden_tests/engine/canvas_paragraph/placeholders_test.dart @@ -50,7 +50,9 @@ void testMain() async { canvas.drawParagraph(paragraph, offset); // Then fill the placeholders. - fillPlaceholder(canvas, offset, paragraph); + final TextBox placeholderBox = paragraph.getBoxesForPlaceholders().single; + final SurfacePaint redPaint = Paint()..color = red; + canvas.drawRect(placeholderBox.toRect().shift(offset), redPaint.paintData); offset = offset.translate(0.0, paragraph.height + 30.0); } @@ -84,7 +86,9 @@ void testMain() async { canvas.drawParagraph(paragraph, offset); // Then fill the placeholders. - fillPlaceholder(canvas, offset, paragraph); + final TextBox placeholderBox = paragraph.getBoxesForPlaceholders().single; + final SurfacePaint redPaint = Paint()..color = red; + canvas.drawRect(placeholderBox.toRect().shift(offset), redPaint.paintData); offset = offset.translate(0.0, paragraph.height + 30.0); } @@ -118,89 +122,13 @@ void testMain() async { canvas.drawParagraph(paragraph, offset); // Then fill the placeholders. - fillPlaceholder(canvas, offset, paragraph); + final TextBox placeholderBox = paragraph.getBoxesForPlaceholders().single; + final SurfacePaint redPaint = Paint()..color = red; + canvas.drawRect(placeholderBox.toRect().shift(offset), redPaint.paintData); offset = offset.translate(0.0, paragraph.height + 30.0); } return takeScreenshot(canvas, bounds, 'canvas_paragraph_placeholders_align_dom'); }); - - test('draws paragraphs starting or ending with a placeholder', () { - const Rect bounds = Rect.fromLTWH(0, 0, 420, 300); - final canvas = BitmapCanvas(bounds, RenderStrategy()); - - Offset offset = Offset(10, 10); - - // First paragraph with a placeholder at the beginning. - final CanvasParagraph paragraph1 = rich( - ParagraphStyle(fontFamily: 'Roboto', fontSize: 24.0, textAlign: TextAlign.center), - (builder) { - builder.addPlaceholder(80.0, 50.0, PlaceholderAlignment.baseline, baseline: TextBaseline.alphabetic); - builder.pushStyle(TextStyle(color: black)); - builder.addText(' Lorem ipsum.'); - }, - )..layout(constrain(400.0)); - - // Draw the paragraph. - canvas.drawParagraph(paragraph1, offset); - fillPlaceholder(canvas, offset, paragraph1); - surroundParagraph(canvas, offset, paragraph1); - - offset = offset.translate(0.0, paragraph1.height + 30.0); - - // Second paragraph with a placeholder at the end. - final CanvasParagraph paragraph2 = rich( - ParagraphStyle(fontFamily: 'Roboto', fontSize: 24.0, textAlign: TextAlign.center), - (builder) { - builder.pushStyle(TextStyle(color: black)); - builder.addText('Lorem ipsum '); - builder.addPlaceholder(80.0, 50.0, PlaceholderAlignment.baseline, baseline: TextBaseline.alphabetic); - }, - )..layout(constrain(400.0)); - - // Draw the paragraph. - canvas.drawParagraph(paragraph2, offset); - fillPlaceholder(canvas, offset, paragraph2); - surroundParagraph(canvas, offset, paragraph2); - - offset = offset.translate(0.0, paragraph2.height + 30.0); - - // Third paragraph with a placeholder alone in the second line. - final CanvasParagraph paragraph3 = rich( - ParagraphStyle(fontFamily: 'Roboto', fontSize: 24.0, textAlign: TextAlign.center), - (builder) { - builder.pushStyle(TextStyle(color: black)); - builder.addText('Lorem ipsum '); - builder.addPlaceholder(80.0, 50.0, PlaceholderAlignment.baseline, baseline: TextBaseline.alphabetic); - }, - )..layout(constrain(200.0)); - - // Draw the paragraph. - canvas.drawParagraph(paragraph3, offset); - fillPlaceholder(canvas, offset, paragraph3); - surroundParagraph(canvas, offset, paragraph3); - - return takeScreenshot(canvas, bounds, 'canvas_paragraph_placeholders_start_and_end'); - }); -} - -void surroundParagraph( - EngineCanvas canvas, - Offset offset, - CanvasParagraph paragraph, -) { - final Rect rect = offset & Size(paragraph.width, paragraph.height); - final SurfacePaint paint = Paint()..color = blue..style = PaintingStyle.stroke; - canvas.drawRect(rect, paint.paintData); -} - -void fillPlaceholder( - EngineCanvas canvas, - Offset offset, - CanvasParagraph paragraph, -) { - final TextBox placeholderBox = paragraph.getBoxesForPlaceholders().single; - final SurfacePaint paint = Paint()..color = red; - canvas.drawRect(placeholderBox.toRect().shift(offset), paint.paintData); } diff --git a/lib/web_ui/test/text/layout_service_rich_test.dart b/lib/web_ui/test/text/layout_service_rich_test.dart index c7786429e7b216a4863fb275d613637baf690a29..5978658c21390de73572451af0899b49ea333493 100644 --- a/lib/web_ui/test/text/layout_service_rich_test.dart +++ b/lib/web_ui/test/text/layout_service_rich_test.dart @@ -164,26 +164,7 @@ void testMain() async { expect(paragraph.minIntrinsicWidth, 300.0); expect(paragraph.height, 50.0); expectLines(paragraph, [ - l('', 0, 0, hardBreak: true, width: 300.0, left: 100.0), - ]); - }); - - test('correct maxIntrinsicWidth when paragraph ends with placeholder', () { - final EngineParagraphStyle paragraphStyle = EngineParagraphStyle( - fontFamily: 'ahem', - fontSize: 10, - textAlign: ui.TextAlign.center, - ); - final CanvasParagraph paragraph = rich(paragraphStyle, (builder) { - builder.addText('abcd'); - builder.addPlaceholder(300.0, 50.0, ui.PlaceholderAlignment.bottom); - })..layout(constrain(400.0)); - - expect(paragraph.maxIntrinsicWidth, 340.0); - expect(paragraph.minIntrinsicWidth, 300.0); - expect(paragraph.height, 50.0); - expectLines(paragraph, [ - l('abcd', 0, 4, hardBreak: true, width: 340.0, left: 30.0), + l('', 0, 0, hardBreak: false, width: 300.0, left: 100.0), ]); }); }