diff --git a/lib/web_ui/lib/src/engine/canvas_pool.dart b/lib/web_ui/lib/src/engine/canvas_pool.dart
index 6528fae7eb346..7bf1b3890349b 100644
--- a/lib/web_ui/lib/src/engine/canvas_pool.dart
+++ b/lib/web_ui/lib/src/engine/canvas_pool.dart
@@ -76,13 +76,6 @@ class CanvasPool extends _SaveStackTracking {
translate(transform.dx, transform.dy);
}
- /// Returns true if no canvas has been allocated yet.
- bool get isEmpty => _canvas == null;
-
- /// Returns true if a canvas has been allocated for use.
- bool get isNotEmpty => _canvas != null;
-
-
/// Returns [CanvasRenderingContext2D] api to draw into this canvas.
html.CanvasRenderingContext2D get context {
html.CanvasRenderingContext2D? ctx = _context;
@@ -106,12 +99,28 @@ class CanvasPool extends _SaveStackTracking {
return _contextHandle!;
}
- /// Prevents active canvas to be used for rendering and prepares a new
- /// canvas allocation on next drawing request that will require one.
+ /// Returns true if a canvas is currently available for drawing.
+ ///
+ /// Calling [contextHandle] or, transitively, any of the `draw*` methods while
+ /// this returns true will reuse the existing canvas. Otherwise, a new canvas
+ /// will be allocated.
+ ///
+ /// Previously allocated and closed canvases (see [closeCanvas]) are not
+ /// considered by this getter.
+ bool get hasCanvas => _canvas != null;
+
+ /// Stops the currently available canvas from receiving any further drawing
+ /// commands.
+ ///
+ /// After calling this method, a subsequent call to [contextHandle] or,
+ /// transitively, any of the `draw*` methods will cause a new canvas to be
+ /// allocated.
///
- /// Saves current canvas so we can dispose
- /// and replay the clip/transform stack on top of new canvas.
- void closeCurrentCanvas() {
+ /// The closed canvas becomes an "active" canvas, that is a canvas that's used
+ /// to render picture content in the current frame. Active canvases may be
+ /// reused in other pictures if their contents are no longer needed for this
+ /// picture.
+ void closeCanvas() {
assert(_rootElement != null);
// Place clean copy of current canvas with context stack restored and paint
// reset into pool.
diff --git a/lib/web_ui/lib/src/engine/html/bitmap_canvas.dart b/lib/web_ui/lib/src/engine/html/bitmap_canvas.dart
index 9b7bb4a011282..8b9677d94fcb6 100644
--- a/lib/web_ui/lib/src/engine/html/bitmap_canvas.dart
+++ b/lib/web_ui/lib/src/engine/html/bitmap_canvas.dart
@@ -370,7 +370,7 @@ class BitmapCanvas extends EngineCanvas {
_renderStrategy.isInsideSvgFilterTree ||
(_preserveImageData == false && _contains3dTransform) ||
(_childOverdraw &&
- _canvasPool.isEmpty &&
+ !_canvasPool.hasCanvas &&
paint.maskFilter == null &&
paint.shader == null &&
paint.style != ui.PaintingStyle.stroke);
@@ -384,7 +384,7 @@ class BitmapCanvas extends EngineCanvas {
((_childOverdraw ||
_renderStrategy.hasImageElements ||
_renderStrategy.hasParagraphs) &&
- _canvasPool.isEmpty &&
+ !_canvasPool.hasCanvas &&
paint.maskFilter == null &&
paint.shader == null);
@@ -469,7 +469,7 @@ class BitmapCanvas extends EngineCanvas {
element.style.mixBlendMode = blendModeToCssMixBlendMode(blendMode) ?? '';
}
// Switch to preferring DOM from now on, and close the current canvas.
- _closeCurrentCanvas();
+ _closeCanvas();
}
@override
@@ -626,7 +626,7 @@ class BitmapCanvas extends EngineCanvas {
_applyTargetSize(
imageElement, image.width.toDouble(), image.height.toDouble());
}
- _closeCurrentCanvas();
+ _closeCanvas();
}
html.ImageElement _reuseOrCreateImage(HtmlImage htmlImage) {
@@ -770,7 +770,7 @@ class BitmapCanvas extends EngineCanvas {
restore();
}
}
- _closeCurrentCanvas();
+ _closeCanvas();
}
void _applyTargetSize(
@@ -882,8 +882,8 @@ class BitmapCanvas extends EngineCanvas {
// |---
// Any drawing operations after these tags should allocate a new canvas,
// instead of drawing into earlier canvas.
- void _closeCurrentCanvas() {
- _canvasPool.closeCurrentCanvas();
+ void _closeCanvas() {
+ _canvasPool.closeCanvas();
_childOverdraw = true;
_cachedLastCssFont = null;
}
@@ -939,16 +939,24 @@ class BitmapCanvas extends EngineCanvas {
void drawParagraph(CanvasParagraph paragraph, ui.Offset offset) {
assert(paragraph.isLaidOut);
- /// - paragraph.drawOnCanvas checks that the text styling doesn't include
- /// features that prevent text from being rendered correctly using canvas.
- /// - _childOverdraw check prevents sandwitching multiple canvas elements
- /// when we have alternating paragraphs and other drawing commands that are
- /// suitable for canvas.
- /// - To make sure an svg filter is applied correctly to paragraph we
- /// check isInsideSvgFilterTree to make sure dom node doesn't have any
- /// parents that apply one.
- if (paragraph.drawOnCanvas && _childOverdraw == false &&
- !_renderStrategy.isInsideSvgFilterTree) {
+ // Normally, text is composited as a plain HTML
tag. However, if a
+ // bitmap canvas was used for a preceding drawing command, then it's more
+ // efficient to continue compositing into the existing canvas, if possible.
+ // Whether it's possible to composite a paragraph into a 2D canvas depends
+ // on the following:
+ final bool canCompositeIntoBitmapCanvas =
+ // Cannot composite if the paragraph cannot be drawn into bitmap canvas
+ // in the first place.
+ paragraph.canDrawOnCanvas &&
+ // Cannot composite if there's no bitmap canvas to composite into.
+ // Creating a new bitmap canvas just to draw text doesn't make sense.
+ _canvasPool.hasCanvas &&
+ !_childOverdraw &&
+ // Bitmap canvas introduces correctness issues in the presence of SVG
+ // filters, so prefer plain HTML in this case.
+ !_renderStrategy.isInsideSvgFilterTree;
+
+ if (canCompositeIntoBitmapCanvas) {
paragraph.paint(this, offset);
return;
}
@@ -977,7 +985,7 @@ class BitmapCanvas extends EngineCanvas {
paragraphElement.style
..left = '0px'
..top = '0px';
- _closeCurrentCanvas();
+ _closeCanvas();
}
/// Draws vertices on a gl context.
diff --git a/lib/web_ui/lib/src/engine/text/canvas_paragraph.dart b/lib/web_ui/lib/src/engine/text/canvas_paragraph.dart
index 64c7bf30558fb..77f8f4ca00354 100644
--- a/lib/web_ui/lib/src/engine/text/canvas_paragraph.dart
+++ b/lib/web_ui/lib/src/engine/text/canvas_paragraph.dart
@@ -31,7 +31,7 @@ class CanvasParagraph implements ui.Paragraph {
required this.paragraphStyle,
required this.plainText,
required this.placeholderCount,
- required this.drawOnCanvas,
+ required this.canDrawOnCanvas,
});
/// The flat list of spans that make up this paragraph.
@@ -47,7 +47,10 @@ class CanvasParagraph implements ui.Paragraph {
final int placeholderCount;
/// Whether this paragraph can be drawn on a bitmap canvas.
- final bool drawOnCanvas;
+ ///
+ /// Some text features cannot be rendered into a 2D canvas and must use HTML,
+ /// such as font features and text decorations.
+ final bool canDrawOnCanvas;
@override
double get width => _layoutService.width;
@@ -623,7 +626,7 @@ class CanvasParagraphBuilder implements ui.ParagraphBuilder {
}
}
- bool _drawOnCanvas = true;
+ bool _canDrawOnCanvas = true;
@override
void addText(String text) {
@@ -632,24 +635,24 @@ class CanvasParagraphBuilder implements ui.ParagraphBuilder {
_plainTextBuffer.write(text);
final int end = _plainTextBuffer.length;
- if (_drawOnCanvas) {
+ if (_canDrawOnCanvas) {
final ui.TextDecoration? decoration = style.decoration;
if (decoration != null && decoration != ui.TextDecoration.none) {
- _drawOnCanvas = false;
+ _canDrawOnCanvas = false;
}
}
- if (_drawOnCanvas) {
+ if (_canDrawOnCanvas) {
final List