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 1 commit
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
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');
});
}