Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

[web] Separate text fragmenting from layout #34085

Merged
merged 45 commits into from
Oct 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
714db1f
[web] Refactor line breaker to resemble how v8BreakIterator works
mdebbar Jun 15, 2022
a775489
[web] Bidi fragmenter
mdebbar Jun 22, 2022
9ea6cc1
[web] Layout fragmenter
mdebbar Jun 23, 2022
08646b4
refactor layout_service to use the new fragmenter
mdebbar Jun 28, 2022
b36dd47
Merge branch 'main' into fragmenting_line_breaks
mdebbar Jun 28, 2022
3a7845b
more changes
mdebbar Jun 29, 2022
02006a6
more changes and test fixes
mdebbar Jun 30, 2022
971b940
Merge branch 'main' into fragmenting_line_breaks
mdebbar Aug 24, 2022
690db62
Merge branch 'main' into fragmenting_line_breaks
mdebbar Aug 29, 2022
76bb549
Merge branch 'main' into fragmenting_line_breaks
mdebbar Aug 30, 2022
696932b
fix some analyzer warnings
mdebbar Aug 30, 2022
9c58c23
fragmenters don't depend on CanvasParagraph anymore
mdebbar Aug 30, 2022
0004dc5
start using fragments instead of boxes everywhere
mdebbar Aug 31, 2022
ba3ab0e
Merge branch 'main' into fragmenting_line_breaks
mdebbar Sep 1, 2022
e84ad11
Merge branch 'main' into fragmenting_line_breaks
mdebbar Sep 12, 2022
e37f31f
Merge branch 'main' into fragmenting_line_breaks
mdebbar Sep 13, 2022
6331513
Merge branch 'main' into fragmenting_line_breaks
mdebbar Sep 13, 2022
5e26f40
fix some tests
mdebbar Sep 15, 2022
3a7c968
Merge branch 'main' into fragmenting_line_breaks
mdebbar Sep 15, 2022
137ce54
multiple changes
mdebbar Sep 15, 2022
1671dd1
fix text boxes
mdebbar Sep 20, 2022
74f6a15
use 'late' for LayoutFragment properties
mdebbar Sep 20, 2022
4aac9ca
Merge branch 'main' into fragmenting_line_breaks
mdebbar Sep 20, 2022
2265821
fix positionForOffset
mdebbar Sep 20, 2022
23850a7
Fix rtl positioning issue
mdebbar Sep 20, 2022
df29a50
Merge branch 'main' into fragmenting_line_breaks
mdebbar Sep 21, 2022
4be8e7d
fix some directionality issues
mdebbar Sep 21, 2022
01e6638
fix more directionality issues
mdebbar Sep 26, 2022
8f2dc1b
Merge branch 'main' into fragmenting_line_breaks
mdebbar Sep 26, 2022
c12d923
fix align justify
mdebbar Sep 26, 2022
4cd56e3
clearer variable names
mdebbar Sep 27, 2022
4f920a2
make some goldens clearer
mdebbar Sep 27, 2022
10031be
handle empty string + tests
mdebbar Sep 28, 2022
6dcd79a
Merge branch 'main' into fragmenting_line_breaks
mdebbar Sep 28, 2022
3cc3fea
better handling of empty paragraphs
mdebbar Sep 29, 2022
443ce94
Merge branch 'main' into fragmenting_line_breaks
mdebbar Sep 29, 2022
30804ee
fix surrogate handling in line breaker
mdebbar Sep 30, 2022
13d5cbe
minor improvements
mdebbar Sep 30, 2022
274653b
more improvements
mdebbar Oct 3, 2022
5dafc9e
Merge branch 'main' into fragmenting_line_breaks
mdebbar Oct 3, 2022
802828b
fix trailing newlines issue
mdebbar Oct 13, 2022
ea30370
Merge branch 'main' into fragmenting_line_breaks
mdebbar Oct 13, 2022
479287e
Merge branch 'main' into fragmenting_line_breaks
mdebbar Oct 14, 2022
977177e
Merge branch 'main' into fragmenting_line_breaks
mdebbar Oct 17, 2022
086b420
add handling of mashriqi digits + other review comments
mdebbar Oct 17, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -1924,6 +1924,8 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/svg.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/test_embedding.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/text/canvas_paragraph.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/text/font_collection.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/text/fragmenter.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/text/layout_fragmenter.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/text/layout_service.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/text/line_break_properties.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/text/line_breaker.dart
Expand Down
2 changes: 2 additions & 0 deletions lib/web_ui/lib/src/engine.dart
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ export 'engine/svg.dart';
export 'engine/test_embedding.dart';
export 'engine/text/canvas_paragraph.dart';
export 'engine/text/font_collection.dart';
export 'engine/text/fragmenter.dart';
export 'engine/text/layout_fragmenter.dart';
export 'engine/text/layout_service.dart';
export 'engine/text/line_break_properties.dart';
export 'engine/text/line_breaker.dart';
Expand Down
2 changes: 2 additions & 0 deletions lib/web_ui/lib/src/engine/dom.dart
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,8 @@ extension DomCanvasRenderingContext2DExtension on DomCanvasRenderingContext2D {
external set fillStyle(Object? style);
external String get font;
external set font(String value);
external String get direction;
external set direction(String value);
external set lineWidth(num? value);
external set strokeStyle(Object? value);
external Object? get strokeStyle;
Expand Down
6 changes: 4 additions & 2 deletions lib/web_ui/lib/src/engine/html/bitmap_canvas.dart
Original file line number Diff line number Diff line change
Expand Up @@ -913,9 +913,11 @@ class BitmapCanvas extends EngineCanvas {
_cachedLastCssFont = null;
}

void setCssFont(String cssFont) {
void setCssFont(String cssFont, ui.TextDirection textDirection) {
final DomCanvasRenderingContext2D ctx = _canvasPool.context;
ctx.direction = textDirection == ui.TextDirection.ltr ? 'ltr' : 'rtl';

if (cssFont != _cachedLastCssFont) {
final DomCanvasRenderingContext2D ctx = _canvasPool.context;
ctx.font = cssFont;
_cachedLastCssFont = cssFont;
}
Expand Down
148 changes: 90 additions & 58 deletions lib/web_ui/lib/src/engine/text/canvas_paragraph.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@ import '../embedder.dart';
import '../html/bitmap_canvas.dart';
import '../profiler.dart';
import '../util.dart';
import 'layout_fragmenter.dart';
import 'layout_service.dart';
import 'paint_service.dart';
import 'paragraph.dart';
import 'word_breaker.dart';

const ui.Color _defaultTextColor = ui.Color(0xFFFF0000);

final String _placeholderChar = String.fromCharCode(0xFFFC);

/// A paragraph made up of a flat list of text spans and placeholders.
///
/// [CanvasParagraph] doesn't use a DOM element to represent the structure of
Expand All @@ -32,7 +35,7 @@ class CanvasParagraph implements ui.Paragraph {
required this.plainText,
required this.placeholderCount,
required this.canDrawOnCanvas,
});
}) : assert(spans.isNotEmpty);

/// The flat list of spans that make up this paragraph.
final List<ParagraphSpan> spans;
Expand Down Expand Up @@ -168,38 +171,28 @@ class CanvasParagraph implements ui.Paragraph {

// 2. Append all spans to the paragraph.

DomHTMLElement? lastSpanElement;
for (int i = 0; i < lines.length; i++) {
final ParagraphLine line = lines[i];
final List<RangeBox> boxes = line.boxes;
final StringBuffer buffer = StringBuffer();

int j = 0;
while (j < boxes.length) {
final RangeBox box = boxes[j++];

if (box is SpanBox) {
lastSpanElement = domDocument.createElement('flt-span') as
DomHTMLElement;
applyTextStyleToElement(
element: lastSpanElement,
style: box.span.style,
isSpan: true,
);
_positionSpanElement(lastSpanElement, line, box);
lastSpanElement.appendText(box.toText());
rootElement.append(lastSpanElement);
buffer.write(box.toText());
} else if (box is PlaceholderBox) {
lastSpanElement = null;
} else {
throw UnimplementedError('Unknown box type: ${box.runtimeType}');
for (final LayoutFragment fragment in line.fragments) {
if (fragment.isPlaceholder) {
continue;
}

final String text = fragment.getText(this);
if (text.isEmpty) {
continue;
}
}

final String? ellipsis = line.ellipsis;
if (ellipsis != null) {
(lastSpanElement ?? rootElement).appendText(ellipsis);
final DomHTMLElement spanElement = domDocument.createElement('flt-span') as DomHTMLElement;
applyTextStyleToElement(
element: spanElement,
style: fragment.style,
isSpan: true,
);
_positionSpanElement(spanElement, line, fragment);

spanElement.appendText(text);
rootElement.append(spanElement);
}
}

Expand Down Expand Up @@ -283,8 +276,8 @@ class CanvasParagraph implements ui.Paragraph {
}
}

void _positionSpanElement(DomElement element, ParagraphLine line, RangeBox box) {
final ui.Rect boxRect = box.toTextBox(line, forPainting: true).toRect();
void _positionSpanElement(DomElement element, ParagraphLine line, LayoutFragment fragment) {
final ui.Rect boxRect = fragment.toPaintingTextBox().toRect();
element.style
..position = 'absolute'
..top = '${boxRect.top}px'
Expand All @@ -304,6 +297,9 @@ abstract class ParagraphSpan {

/// The index of the end of the range of text represented by this span.
int get end;

/// The resolved style of the span.
EngineTextStyle get style;
}

/// Represent a span of text in the paragraph.
Expand All @@ -323,7 +319,7 @@ class FlatTextSpan implements ParagraphSpan {
required this.end,
});

/// The resolved style of the span.
@override
final EngineTextStyle style;

@override
Expand All @@ -341,14 +337,24 @@ class FlatTextSpan implements ParagraphSpan {

class PlaceholderSpan extends ParagraphPlaceholder implements ParagraphSpan {
PlaceholderSpan(
int index,
super.width,
super.height,
super.alignment, {
required super.baselineOffset,
required super.baseline,
}) : start = index,
end = index;
this.style,
this.start,
this.end,
double width,
double height,
ui.PlaceholderAlignment alignment, {
required double baselineOffset,
required ui.TextBaseline baseline,
}) : super(
width,
height,
alignment,
baselineOffset: baselineOffset,
baseline: baseline,
);

@override
final EngineTextStyle style;

@override
final int start;
Expand Down Expand Up @@ -624,10 +630,19 @@ class CanvasParagraphBuilder implements ui.ParagraphBuilder {
alignment == ui.PlaceholderAlignment.belowBaseline ||
alignment == ui.PlaceholderAlignment.baseline) || baseline != null);

final int start = _plainTextBuffer.length;
_plainTextBuffer.write(_placeholderChar);
final int end = _plainTextBuffer.length;

final EngineTextStyle style = _currentStyleNode.resolveStyle();
_updateCanDrawOnCanvas(style);

_placeholderCount++;
_placeholderScales.add(scale);
_spans.add(PlaceholderSpan(
_plainTextBuffer.length,
style,
start,
end,
width * scale,
height * scale,
alignment,
Expand All @@ -652,37 +667,54 @@ class CanvasParagraphBuilder implements ui.ParagraphBuilder {

@override
void addText(String text) {
final EngineTextStyle style = _currentStyleNode.resolveStyle();
final int start = _plainTextBuffer.length;
_plainTextBuffer.write(text);
final int end = _plainTextBuffer.length;

if (_canDrawOnCanvas) {
final ui.TextDecoration? decoration = style.decoration;
if (decoration != null && decoration != ui.TextDecoration.none) {
_canDrawOnCanvas = false;
}
final EngineTextStyle style = _currentStyleNode.resolveStyle();
_updateCanDrawOnCanvas(style);

_spans.add(FlatTextSpan(style: style, start: start, end: end));
}

void _updateCanDrawOnCanvas(EngineTextStyle style) {
if (!_canDrawOnCanvas) {
return;
}

if (_canDrawOnCanvas) {
final List<ui.FontFeature>? fontFeatures = style.fontFeatures;
if (fontFeatures != null && fontFeatures.isNotEmpty) {
_canDrawOnCanvas = false;
}
final ui.TextDecoration? decoration = style.decoration;
if (decoration != null && decoration != ui.TextDecoration.none) {
_canDrawOnCanvas = false;
return;
}

if (_canDrawOnCanvas) {
final List<ui.FontVariation>? fontVariations = style.fontVariations;
if (fontVariations != null && fontVariations.isNotEmpty) {
_canDrawOnCanvas = false;
}
final List<ui.FontFeature>? fontFeatures = style.fontFeatures;
if (fontFeatures != null && fontFeatures.isNotEmpty) {
_canDrawOnCanvas = false;
return;
}

_spans.add(FlatTextSpan(style: style, start: start, end: end));
final List<ui.FontVariation>? fontVariations = style.fontVariations;
if (fontVariations != null && fontVariations.isNotEmpty) {
_canDrawOnCanvas = false;
return;
}
}

@override
CanvasParagraph build() {
if (_spans.isEmpty) {
// In case `addText` and `addPlaceholder` were never called.
//
// We want the paragraph to always have a non-empty list of spans to match
// the expectations of the [LayoutFragmenter].
_spans.add(FlatTextSpan(
style: _rootStyleNode.resolveStyle(),
start: 0,
end: 0,
));
}

return CanvasParagraph(
_spans,
paragraphStyle: _paragraphStyle,
Expand Down
34 changes: 34 additions & 0 deletions lib/web_ui/lib/src/engine/text/fragmenter.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/// Splits [text] into a list of [TextFragment]s.
///
/// Various subclasses can perform the fragmenting based on their own criteria.
///
/// See:
///
/// - [LineBreakFragmenter]: Fragments text based on line break opportunities.
/// - [BidiFragmenter]: Fragments text based on directionality.
abstract class TextFragmenter {
const TextFragmenter(this.text);

/// The text to be fragmented.
final String text;

/// Performs the fragmenting of [text] and returns a list of [TextFragment]s.
List<TextFragment> fragment();
}

/// Represents a fragment produced by [TextFragmenter].
abstract class TextFragment {
const TextFragment(this.start, this.end);

final int start;
final int end;

/// Whether this fragment's range overlaps with the range from [start] to [end].
bool overlapsWith(int start, int end) {
return start < this.end && this.start < end;
}
}
Loading