Skip to content

Commit 2003a19

Browse files
mdebbarchaselatta
authored andcommitted
[web] Put the paragraph painting logic in the Paragraph class (flutter#22239)
1 parent 29c6ddb commit 2003a19

File tree

4 files changed

+120
-84
lines changed

4 files changed

+120
-84
lines changed

lib/web_ui/lib/src/engine/bitmap_canvas.dart

Lines changed: 25 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -777,70 +777,37 @@ class BitmapCanvas extends EngineCanvas {
777777
_childOverdraw = true;
778778
}
779779

780-
void _drawTextLine(
781-
ParagraphGeometricStyle style,
782-
EngineLineMetrics line,
783-
double x,
784-
double y,
785-
) {
786-
html.CanvasRenderingContext2D ctx = _canvasPool.context;
787-
x += line.left;
788-
final double? letterSpacing = style.letterSpacing;
789-
if (letterSpacing == null || letterSpacing == 0.0) {
790-
ctx.fillText(line.displayText!, x, y);
791-
} else {
792-
// When letter-spacing is set, we go through a more expensive code path
793-
// that renders each character separately with the correct spacing
794-
// between them.
795-
//
796-
// We are drawing letter spacing like the web does it, by adding the
797-
// spacing after each letter. This is different from Flutter which puts
798-
// the spacing around each letter i.e. for a 10px letter spacing, Flutter
799-
// would put 5px before each letter and 5px after it, but on the web, we
800-
// put no spacing before the letter and 10px after it. This is how the DOM
801-
// does it.
802-
final int len = line.displayText!.length;
803-
for (int i = 0; i < len; i++) {
804-
final String char = line.displayText![i];
805-
ctx.fillText(char, x, y);
806-
x += letterSpacing + ctx.measureText(char).width!;
807-
}
780+
void setFontFromParagraphStyle(ParagraphGeometricStyle style) {
781+
if (style != _cachedLastStyle) {
782+
html.CanvasRenderingContext2D ctx = _canvasPool.context;
783+
ctx.font = style.cssFontString;
784+
_cachedLastStyle = style;
808785
}
809786
}
810787

788+
/// Measures the given [text] and returns a [html.TextMetrics] object that
789+
/// contains information about the measurement.
790+
///
791+
/// The text is measured using the font set by the most recent call to
792+
/// [setFontFromParagraphStyle].
793+
html.TextMetrics measureText(String text) {
794+
return _canvasPool.context.measureText(text);
795+
}
796+
797+
/// Draws text to the canvas starting at coordinate ([x], [y]).
798+
///
799+
/// The text is drawn starting at coordinates ([x], [y]). It uses the current
800+
/// font set by the most recent call to [setFontFromParagraphStyle].
801+
void fillText(String text, double x, double y) {
802+
_canvasPool.context.fillText(text, x, y);
803+
}
804+
811805
@override
812806
void drawParagraph(EngineParagraph paragraph, ui.Offset offset) {
813-
assert(paragraph._isLaidOut);
814-
final ParagraphGeometricStyle style = paragraph._geometricStyle;
815-
816-
if (paragraph._drawOnCanvas && _childOverdraw == false) {
817-
// !Do not move this assignment above this if clause since, accessing
818-
// context will generate extra <canvas> tags.
819-
final List<EngineLineMetrics> lines =
820-
paragraph._measurementResult!.lines!;
821-
822-
final SurfacePaintData? backgroundPaint =
823-
paragraph._background?.paintData;
824-
if (backgroundPaint != null) {
825-
final ui.Rect rect = ui.Rect.fromLTWH(
826-
offset.dx, offset.dy, paragraph.width, paragraph.height);
827-
drawRect(rect, backgroundPaint);
828-
}
829-
830-
if (style != _cachedLastStyle) {
831-
html.CanvasRenderingContext2D ctx = _canvasPool.context;
832-
ctx.font = style.cssFontString;
833-
_cachedLastStyle = style;
834-
}
835-
_setUpPaint(paragraph._paint!.paintData, null);
836-
double y = offset.dy + paragraph.alphabeticBaseline;
837-
final int len = lines.length;
838-
for (int i = 0; i < len; i++) {
839-
_drawTextLine(style, lines[i], offset.dx, y);
840-
y += paragraph._lineHeight;
841-
}
842-
_tearDownPaint();
807+
assert(paragraph.isLaidOut);
843808

809+
if (paragraph.drawOnCanvas && _childOverdraw == false) {
810+
paragraph.paint(this, offset);
844811
return;
845812
}
846813

lib/web_ui/lib/src/engine/engine_canvas.dart

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -251,35 +251,19 @@ html.Element _drawParagraphElement(
251251
ui.Offset offset, {
252252
Matrix4? transform,
253253
}) {
254-
assert(paragraph._isLaidOut);
254+
assert(paragraph.isLaidOut);
255255

256-
final html.Element paragraphElement = paragraph._paragraphElement.clone(true) as html.Element;
257-
258-
final html.CssStyleDeclaration paragraphStyle = paragraphElement.style;
259-
paragraphStyle
260-
..position = 'absolute'
261-
..whiteSpace = 'pre-wrap'
262-
..overflowWrap = 'break-word'
263-
..overflow = 'hidden'
264-
..height = '${paragraph.height}px'
265-
..width = '${paragraph.width}px';
256+
final html.HtmlElement paragraphElement = paragraph.toDomElement();
257+
paragraphElement.style
258+
..height = '${paragraph.height}px'
259+
..width = '${paragraph.width}px';
266260

267261
if (transform != null) {
268262
setElementTransform(
269263
paragraphElement,
270264
transformWithOffset(transform, offset).storage,
271265
);
272266
}
273-
274-
final ParagraphGeometricStyle style = paragraph._geometricStyle;
275-
276-
// TODO(flutter_web): https://github.com/flutter/flutter/issues/33223
277-
if (style.ellipsis != null &&
278-
(style.maxLines == null || style.maxLines == 1)) {
279-
paragraphStyle
280-
..whiteSpace = 'pre'
281-
..textOverflow = 'ellipsis';
282-
}
283267
return paragraphElement;
284268
}
285269

lib/web_ui/lib/src/engine/html/recording_canvas.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -522,13 +522,13 @@ class RecordingCanvas {
522522
void drawParagraph(ui.Paragraph paragraph, ui.Offset offset) {
523523
assert(!_recordingEnded);
524524
final EngineParagraph engineParagraph = paragraph as EngineParagraph;
525-
if (!engineParagraph._isLaidOut) {
525+
if (!engineParagraph.isLaidOut) {
526526
// Ignore non-laid out paragraphs. This matches Flutter's behavior.
527527
return;
528528
}
529529

530530
_didDraw = true;
531-
if (engineParagraph._geometricStyle.ellipsis != null) {
531+
if (engineParagraph.hasArbitraryPaint) {
532532
_hasArbitraryPaint = true;
533533
}
534534
final double left = offset.dx;

lib/web_ui/lib/src/engine/text/paragraph.dart

Lines changed: 88 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -335,9 +335,94 @@ class EngineParagraph implements ui.Paragraph {
335335
}
336336
}
337337

338+
bool get hasArbitraryPaint => _geometricStyle.ellipsis != null;
339+
340+
void paint(BitmapCanvas canvas, ui.Offset offset) {
341+
assert(drawOnCanvas);
342+
assert(isLaidOut);
343+
344+
// Paint the background first.
345+
final SurfacePaint? background = _background;
346+
if (background != null) {
347+
final ui.Rect rect = ui.Rect.fromLTWH(offset.dx, offset.dy, width, height);
348+
canvas.drawRect(rect, background.paintData);
349+
}
350+
351+
final List<EngineLineMetrics> lines = _measurementResult!.lines!;
352+
canvas.setFontFromParagraphStyle(_geometricStyle);
353+
354+
// Then paint the text.
355+
canvas._setUpPaint(_paint!.paintData, null);
356+
double y = offset.dy + alphabeticBaseline;
357+
final int len = lines.length;
358+
for (int i = 0; i < len; i++) {
359+
_paintLine(canvas, lines[i], offset.dx, y);
360+
y += _lineHeight;
361+
}
362+
canvas._tearDownPaint();
363+
}
364+
365+
void _paintLine(
366+
BitmapCanvas canvas,
367+
EngineLineMetrics line,
368+
double x,
369+
double y,
370+
) {
371+
x += line.left;
372+
final double? letterSpacing = _geometricStyle.letterSpacing;
373+
if (letterSpacing == null || letterSpacing == 0.0) {
374+
canvas.fillText(line.displayText!, x, y);
375+
} else {
376+
// When letter-spacing is set, we go through a more expensive code path
377+
// that renders each character separately with the correct spacing
378+
// between them.
379+
//
380+
// We are drawing letter spacing like the web does it, by adding the
381+
// spacing after each letter. This is different from Flutter which puts
382+
// the spacing around each letter i.e. for a 10px letter spacing, Flutter
383+
// would put 5px before each letter and 5px after it, but on the web, we
384+
// put no spacing before the letter and 10px after it. This is how the DOM
385+
// does it.
386+
//
387+
// TODO(mdebbar): Implement letter-spacing on canvas more efficiently:
388+
// https://github.com/flutter/flutter/issues/51234
389+
final int len = line.displayText!.length;
390+
for (int i = 0; i < len; i++) {
391+
final String char = line.displayText![i];
392+
canvas.fillText(char, x, y);
393+
x += letterSpacing + canvas.measureText(char).width!;
394+
}
395+
}
396+
}
397+
398+
html.HtmlElement toDomElement() {
399+
assert(isLaidOut);
400+
401+
final html.HtmlElement paragraphElement =
402+
_paragraphElement.clone(true) as html.HtmlElement;
403+
404+
final html.CssStyleDeclaration paragraphStyle = paragraphElement.style;
405+
paragraphStyle
406+
..position = 'absolute'
407+
..whiteSpace = 'pre-wrap'
408+
..overflowWrap = 'break-word'
409+
..overflow = 'hidden';
410+
411+
final ParagraphGeometricStyle style = _geometricStyle;
412+
413+
// TODO(flutter_web): https://github.com/flutter/flutter/issues/33223
414+
if (style.ellipsis != null &&
415+
(style.maxLines == null || style.maxLines == 1)) {
416+
paragraphStyle
417+
..whiteSpace = 'pre'
418+
..textOverflow = 'ellipsis';
419+
}
420+
return paragraphElement;
421+
}
422+
338423
@override
339424
List<ui.TextBox> getBoxesForPlaceholders() {
340-
assert(_isLaidOut);
425+
assert(isLaidOut);
341426
return _measurementResult!.placeholderBoxes;
342427
}
343428

@@ -351,7 +436,7 @@ class EngineParagraph implements ui.Paragraph {
351436
/// - Paragraphs that contain decorations.
352437
/// - Paragraphs that have a non-null word-spacing.
353438
/// - Paragraphs with a background.
354-
bool get _drawOnCanvas {
439+
bool get drawOnCanvas {
355440
if (!_hasLineMetrics) {
356441
return false;
357442
}
@@ -370,7 +455,7 @@ class EngineParagraph implements ui.Paragraph {
370455
}
371456

372457
/// Whether this paragraph has been laid out.
373-
bool get _isLaidOut => _measurementResult != null;
458+
bool get isLaidOut => _measurementResult != null;
374459

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

0 commit comments

Comments
 (0)