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
83 changes: 25 additions & 58 deletions lib/web_ui/lib/src/engine/bitmap_canvas.dart
Original file line number Diff line number Diff line change
Expand Up @@ -771,70 +771,37 @@ class BitmapCanvas extends EngineCanvas {
_childOverdraw = true;
}

void _drawTextLine(
ParagraphGeometricStyle style,
EngineLineMetrics line,
double x,
double y,
) {
html.CanvasRenderingContext2D ctx = _canvasPool.context;
x += line.left;
final double? letterSpacing = style.letterSpacing;
if (letterSpacing == null || letterSpacing == 0.0) {
ctx.fillText(line.displayText!, x, y);
} else {
// When letter-spacing is set, we go through a more expensive code path
// that renders each character separately with the correct spacing
// between them.
//
// We are drawing letter spacing like the web does it, by adding the
// spacing after each letter. This is different from Flutter which puts
// the spacing around each letter i.e. for a 10px letter spacing, Flutter
// would put 5px before each letter and 5px after it, but on the web, we
// put no spacing before the letter and 10px after it. This is how the DOM
// does it.
final int len = line.displayText!.length;
for (int i = 0; i < len; i++) {
final String char = line.displayText![i];
ctx.fillText(char, x, y);
x += letterSpacing + ctx.measureText(char).width!;
}
void setFontFromParagraphStyle(ParagraphGeometricStyle style) {
if (style != _cachedLastStyle) {
html.CanvasRenderingContext2D ctx = _canvasPool.context;
ctx.font = style.cssFontString;
_cachedLastStyle = style;
}
}

/// Measures the given [text] and returns a [html.TextMetrics] object that
/// contains information about the measurement.
///
/// The text is measured using the font set by the most recent call to
/// [setFontFromParagraphStyle].
html.TextMetrics measureText(String text) {
return _canvasPool.context.measureText(text);
}

/// Draws text to the canvas starting at coordinate ([x], [y]).
///
/// The text is drawn starting at coordinates ([x], [y]). It uses the current
/// font set by the most recent call to [setFontFromParagraphStyle].
void fillText(String text, double x, double y) {
_canvasPool.context.fillText(text, x, y);
}

@override
void drawParagraph(EngineParagraph paragraph, ui.Offset offset) {
assert(paragraph._isLaidOut);
final ParagraphGeometricStyle style = paragraph._geometricStyle;

if (paragraph._drawOnCanvas && _childOverdraw == false) {
// !Do not move this assignment above this if clause since, accessing
// context will generate extra <canvas> tags.
final List<EngineLineMetrics> lines =
paragraph._measurementResult!.lines!;

final SurfacePaintData? backgroundPaint =
paragraph._background?.paintData;
if (backgroundPaint != null) {
final ui.Rect rect = ui.Rect.fromLTWH(
offset.dx, offset.dy, paragraph.width, paragraph.height);
drawRect(rect, backgroundPaint);
}

if (style != _cachedLastStyle) {
html.CanvasRenderingContext2D ctx = _canvasPool.context;
ctx.font = style.cssFontString;
_cachedLastStyle = style;
}
_setUpPaint(paragraph._paint!.paintData, null);
double y = offset.dy + paragraph.alphabeticBaseline;
final int len = lines.length;
for (int i = 0; i < len; i++) {
_drawTextLine(style, lines[i], offset.dx, y);
y += paragraph._lineHeight;
}
_tearDownPaint();
assert(paragraph.isLaidOut);

if (paragraph.drawOnCanvas && _childOverdraw == false) {
paragraph.paint(this, offset);
return;
}

Expand Down
26 changes: 5 additions & 21 deletions lib/web_ui/lib/src/engine/engine_canvas.dart
Original file line number Diff line number Diff line change
Expand Up @@ -251,35 +251,19 @@ html.Element _drawParagraphElement(
ui.Offset offset, {
Matrix4? transform,
}) {
assert(paragraph._isLaidOut);
assert(paragraph.isLaidOut);

final html.Element paragraphElement = paragraph._paragraphElement.clone(true) as html.Element;

final html.CssStyleDeclaration paragraphStyle = paragraphElement.style;
paragraphStyle
..position = 'absolute'
..whiteSpace = 'pre-wrap'
..overflowWrap = 'break-word'
..overflow = 'hidden'
..height = '${paragraph.height}px'
..width = '${paragraph.width}px';
final html.HtmlElement paragraphElement = paragraph.toDomElement();
paragraphElement.style
..height = '${paragraph.height}px'
..width = '${paragraph.width}px';

if (transform != null) {
setElementTransform(
paragraphElement,
transformWithOffset(transform, offset).storage,
);
}

final ParagraphGeometricStyle style = paragraph._geometricStyle;

// TODO(flutter_web): https://github.com/flutter/flutter/issues/33223
if (style.ellipsis != null &&
(style.maxLines == null || style.maxLines == 1)) {
paragraphStyle
..whiteSpace = 'pre'
..textOverflow = 'ellipsis';
}
return paragraphElement;
}

Expand Down
4 changes: 2 additions & 2 deletions lib/web_ui/lib/src/engine/html/recording_canvas.dart
Original file line number Diff line number Diff line change
Expand Up @@ -522,13 +522,13 @@ class RecordingCanvas {
void drawParagraph(ui.Paragraph paragraph, ui.Offset offset) {
assert(!_recordingEnded);
final EngineParagraph engineParagraph = paragraph as EngineParagraph;
if (!engineParagraph._isLaidOut) {
if (!engineParagraph.isLaidOut) {
// Ignore non-laid out paragraphs. This matches Flutter's behavior.
return;
}

_didDraw = true;
if (engineParagraph._geometricStyle.ellipsis != null) {
if (engineParagraph.hasArbitraryPaint) {
_hasArbitraryPaint = true;
}
final double left = offset.dx;
Expand Down
91 changes: 88 additions & 3 deletions lib/web_ui/lib/src/engine/text/paragraph.dart
Original file line number Diff line number Diff line change
Expand Up @@ -335,9 +335,94 @@ class EngineParagraph implements ui.Paragraph {
}
}

bool get hasArbitraryPaint => _geometricStyle.ellipsis != null;

void paint(BitmapCanvas canvas, ui.Offset offset) {
assert(drawOnCanvas);
assert(isLaidOut);

// Paint the background first.
final SurfacePaint? background = _background;
if (background != null) {
final ui.Rect rect = ui.Rect.fromLTWH(offset.dx, offset.dy, width, height);
canvas.drawRect(rect, background.paintData);
}

final List<EngineLineMetrics> lines = _measurementResult!.lines!;
canvas.setFontFromParagraphStyle(_geometricStyle);

// Then paint the text.
canvas._setUpPaint(_paint!.paintData, null);
double y = offset.dy + alphabeticBaseline;
final int len = lines.length;
for (int i = 0; i < len; i++) {
_paintLine(canvas, lines[i], offset.dx, y);
y += _lineHeight;
}
canvas._tearDownPaint();
}

void _paintLine(
BitmapCanvas canvas,
EngineLineMetrics line,
double x,
double y,
) {
x += line.left;
final double? letterSpacing = _geometricStyle.letterSpacing;
if (letterSpacing == null || letterSpacing == 0.0) {
canvas.fillText(line.displayText!, x, y);
} else {
// When letter-spacing is set, we go through a more expensive code path
// that renders each character separately with the correct spacing
// between them.
//
// We are drawing letter spacing like the web does it, by adding the
// spacing after each letter. This is different from Flutter which puts
// the spacing around each letter i.e. for a 10px letter spacing, Flutter
// would put 5px before each letter and 5px after it, but on the web, we
// put no spacing before the letter and 10px after it. This is how the DOM
// does it.
//
// TODO(mdebbar): Implement letter-spacing on canvas more efficiently:
// https://github.com/flutter/flutter/issues/51234
final int len = line.displayText!.length;
for (int i = 0; i < len; i++) {
final String char = line.displayText![i];
canvas.fillText(char, x, y);
x += letterSpacing + canvas.measureText(char).width!;
}
}
}

html.HtmlElement toDomElement() {
assert(isLaidOut);

final html.HtmlElement paragraphElement =
_paragraphElement.clone(true) as html.HtmlElement;

final html.CssStyleDeclaration paragraphStyle = paragraphElement.style;
paragraphStyle
..position = 'absolute'
..whiteSpace = 'pre-wrap'
..overflowWrap = 'break-word'
..overflow = 'hidden';

final ParagraphGeometricStyle style = _geometricStyle;

// TODO(flutter_web): https://github.com/flutter/flutter/issues/33223
if (style.ellipsis != null &&
(style.maxLines == null || style.maxLines == 1)) {
paragraphStyle
..whiteSpace = 'pre'
..textOverflow = 'ellipsis';
}
return paragraphElement;
}

@override
List<ui.TextBox> getBoxesForPlaceholders() {
assert(_isLaidOut);
assert(isLaidOut);
return _measurementResult!.placeholderBoxes;
}

Expand All @@ -351,7 +436,7 @@ class EngineParagraph implements ui.Paragraph {
/// - Paragraphs that contain decorations.
/// - Paragraphs that have a non-null word-spacing.
/// - Paragraphs with a background.
bool get _drawOnCanvas {
bool get drawOnCanvas {
if (!_hasLineMetrics) {
return false;
}
Expand All @@ -370,7 +455,7 @@ class EngineParagraph implements ui.Paragraph {
}

/// Whether this paragraph has been laid out.
bool get _isLaidOut => _measurementResult != null;
bool get isLaidOut => _measurementResult != null;

/// Asserts that the properties used to measure paragraph layout are the same
/// as the properties of this paragraphs root style.
Expand Down