Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 1 addition & 1 deletion lib/web_ui/dev/goldens_lock.yaml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
repository: https://github.com/flutter/goldens.git
revision: b85f9093e6bc6d4e7cbb7f97491667c143c4a360
revision: 4c3d34f19045a1df10757e5b3cb6c9ace9a6038c
45 changes: 35 additions & 10 deletions lib/web_ui/lib/src/engine/text/canvas_paragraph.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class CanvasParagraph implements EngineParagraph {
required this.paragraphStyle,
required this.plainText,
required this.placeholderCount,
required this.drawOnCanvas,
});

/// The flat list of spans that make up this paragraph.
Expand All @@ -34,14 +35,17 @@ class CanvasParagraph implements EngineParagraph {
/// The number of placeholders in this paragraph.
final int placeholderCount;

@override
final bool drawOnCanvas;

@override
double get width => _layoutService.width;

@override
double get height => _layoutService.height;

@override
double get longestLine => _layoutService.longestLine;
double get longestLine => _layoutService.longestLine?.width ?? 0.0;

@override
double get minIntrinsicWidth => _layoutService.minIntrinsicWidth;
Expand Down Expand Up @@ -124,6 +128,14 @@ class CanvasParagraph implements EngineParagraph {
return domElement.clone(true) as html.HtmlElement;
}

double _getParagraphAlignOffset() {
final EngineLineMetrics? longestLine = _layoutService.longestLine;
if (longestLine != null) {
return longestLine.left;
}
return 0.0;
}

html.HtmlElement _createDomElement() {
final html.HtmlElement rootElement =
domRenderer.createElement('p') as html.HtmlElement;
Expand All @@ -137,6 +149,11 @@ class CanvasParagraph implements EngineParagraph {
// to insert our own <BR> breaks based on layout results.
..whiteSpace = 'pre';

final double alignOffset = _getParagraphAlignOffset();
if (alignOffset != 0.0) {
cssStyle.marginLeft = '${alignOffset}px';
}

if (paragraphStyle._maxLines != null || paragraphStyle._ellipsis != null) {
cssStyle
..overflowY = 'hidden'
Expand Down Expand Up @@ -199,15 +216,6 @@ class CanvasParagraph implements EngineParagraph {
return _layoutService.getBoxesForPlaceholders();
}

// TODO(mdebbar): Check for child spans if any has styles that can't be drawn
// on a canvas. e.g:
// - decoration
// - word-spacing
// - shadows (may be possible? https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowBlur)
// - font features
@override
final bool drawOnCanvas = true;

@override
List<ui.TextBox> getBoxesForRange(
int start,
Expand Down Expand Up @@ -599,13 +607,29 @@ class CanvasParagraphBuilder implements ui.ParagraphBuilder {
}
}

bool _drawOnCanvas = true;

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

if (_drawOnCanvas) {
final ui.TextDecoration? decoration = style._decoration;
if (decoration != null && decoration != ui.TextDecoration.none) {
_drawOnCanvas = false;
}
}

if (_drawOnCanvas) {
final List<ui.FontFeature>? fontFeatures = style._fontFeatures;
if (fontFeatures != null && fontFeatures.isNotEmpty) {
_drawOnCanvas = false;
}
}

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

Expand All @@ -616,6 +640,7 @@ class CanvasParagraphBuilder implements ui.ParagraphBuilder {
paragraphStyle: _paragraphStyle,
plainText: _plainTextBuffer.toString(),
placeholderCount: _placeholderCount,
drawOnCanvas: _drawOnCanvas,
);
}
}
9 changes: 5 additions & 4 deletions lib/web_ui/lib/src/engine/text/layout_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class TextLayoutService {

double height = 0.0;

double longestLine = 0.0;
EngineLineMetrics? longestLine;

double minIntrinsicWidth = 0.0;

Expand Down Expand Up @@ -65,7 +65,7 @@ class TextLayoutService {
// Reset results from previous layout.
width = constraints.width;
height = 0.0;
longestLine = 0.0;
longestLine = null;
minIntrinsicWidth = 0.0;
maxIntrinsicWidth = 0.0;
didExceedMaxLines = false;
Expand Down Expand Up @@ -187,8 +187,9 @@ class TextLayoutService {
alphabeticBaseline = line.baseline;
ideographicBaseline = alphabeticBaseline * _baselineRatioHack;
}
if (longestLine < line.width) {
longestLine = line.width;
final double longestLineWidth = longestLine?.width ?? 0.0;
if (longestLineWidth < line.width) {
longestLine = line;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,47 @@ void testMain() async {
return takeScreenshot(canvas, bounds, 'canvas_paragraph_align');
});

test('respects alignment in DOM mode', () {
final canvas = DomCanvas(domRenderer.createElement('flt-picture'));

Offset offset = Offset.zero;
CanvasParagraph paragraph;

void build(CanvasParagraphBuilder builder) {
builder.pushStyle(EngineTextStyle.only(color: black));
builder.addText('Lorem ');
builder.pushStyle(EngineTextStyle.only(color: blue));
builder.addText('ipsum ');
builder.pushStyle(EngineTextStyle.only(color: green));
builder.addText('dolor ');
builder.pushStyle(EngineTextStyle.only(color: red));
builder.addText('sit');
}

paragraph = rich(
ParagraphStyle(fontFamily: 'Roboto', textAlign: TextAlign.left),
build,
)..layout(constrain(100.0));
canvas.drawParagraph(paragraph, offset);
offset = offset.translate(0, paragraph.height + 10);

paragraph = rich(
ParagraphStyle(fontFamily: 'Roboto', textAlign: TextAlign.center),
build,
)..layout(constrain(100.0));
canvas.drawParagraph(paragraph, offset);
offset = offset.translate(0, paragraph.height + 10);

paragraph = rich(
ParagraphStyle(fontFamily: 'Roboto', textAlign: TextAlign.right),
build,
)..layout(constrain(100.0));
canvas.drawParagraph(paragraph, offset);
offset = offset.translate(0, paragraph.height + 10);

return takeScreenshot(canvas, bounds, 'canvas_paragraph_align_dom');
});

test('paints spans with varying heights/baselines', () {
final canvas = BitmapCanvas(bounds, RenderStrategy());

Expand Down Expand Up @@ -165,4 +206,37 @@ void testMain() async {

return takeScreenshot(canvas, bounds, 'canvas_paragraph_letter_spacing');
});

test('draws text decorations', () {
final canvas = BitmapCanvas(bounds, RenderStrategy());
final List<TextDecorationStyle> decorationStyles = <TextDecorationStyle>[
TextDecorationStyle.solid,
TextDecorationStyle.double,
TextDecorationStyle.dotted,
TextDecorationStyle.dashed,
TextDecorationStyle.wavy,
];

final CanvasParagraph paragraph = rich(
ParagraphStyle(fontFamily: 'Roboto'),
(builder) {
for (TextDecorationStyle decorationStyle in decorationStyles) {
builder.pushStyle(EngineTextStyle.only(
color: const Color.fromRGBO(50, 50, 255, 1.0),
decoration: TextDecoration.underline,
decorationStyle: decorationStyle,
decorationColor: red,
fontFamily: 'Roboto',
fontSize: 30,
));
builder.addText('Hello World');
builder.pop();
builder.addText(' ');
}
},
)..layout(constrain(double.infinity));

canvas.drawParagraph(paragraph, Offset.zero);
return takeScreenshot(canvas, bounds, 'canvas_paragraph_decoration');
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,40 @@ void testMain() async {

return takeScreenshot(canvas, bounds, 'canvas_paragraph_placeholders_align');
});

test('draws paragraphs with placeholders and text align in DOM mode', () {
final canvas = DomCanvas(domRenderer.createElement('flt-picture'));

const List<TextAlign> aligns = <TextAlign>[
TextAlign.left,
TextAlign.center,
TextAlign.right,
];

Offset offset = Offset.zero;
for (TextAlign align in aligns) {
final CanvasParagraph paragraph = rich(
ParagraphStyle(fontFamily: 'Roboto', fontSize: 14.0, textAlign: align),
(builder) {
builder.pushStyle(TextStyle(color: black));
builder.addText('Lorem');
builder.addPlaceholder(80.0, 50.0, PlaceholderAlignment.bottom);
builder.pushStyle(TextStyle(color: blue));
builder.addText('ipsum.');
},
)..layout(constrain(200.0));

// Draw the paragraph.
canvas.drawParagraph(paragraph, offset);

// Then fill the placeholders.
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');
});
}