From a0e868f1b32deb00eb9d31ed45448400b248b76a Mon Sep 17 00:00:00 2001 From: Alexander Belokon <39626151+abelokon0711@users.noreply.github.com> Date: Tue, 23 Nov 2021 10:58:56 +0100 Subject: [PATCH 1/6] Evaluating TextStyle 'foreground' property and adding CSS text stroke. --- lib/web_ui/lib/src/engine/text/paragraph.dart | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/web_ui/lib/src/engine/text/paragraph.dart b/lib/web_ui/lib/src/engine/text/paragraph.dart index 78829587a5df6..48e4f115c2262 100644 --- a/lib/web_ui/lib/src/engine/text/paragraph.dart +++ b/lib/web_ui/lib/src/engine/text/paragraph.dart @@ -699,6 +699,11 @@ void applyTextStyleToElement({ if (color != null) { cssStyle.color = colorToCssString(color); } + final ui.Paint? foreground = style.foreground; + if (foreground != null) { + cssStyle.textStroke = + '${foreground.strokeWidth}px ${colorToCssString(foreground.color)}'; + } final ui.Color? background = style.background?.color; if (background != null) { cssStyle.backgroundColor = colorToCssString(background); From 3319eccdf4c3367a112a764c4794a3d3e8e4c224 Mon Sep 17 00:00:00 2001 From: Alexander Belokon <39626151+abelokon0711@users.noreply.github.com> Date: Tue, 23 Nov 2021 15:11:44 +0100 Subject: [PATCH 2/6] Adding tests and support for drawing text stroke on bitmap canvas --- .../lib/src/engine/html/bitmap_canvas.dart | 8 ++++- .../lib/src/engine/text/paint_service.dart | 7 +++-- lib/web_ui/lib/src/engine/text/paragraph.dart | 7 ++--- .../html/paragraph/general_golden_test.dart | 31 +++++++++++++++++++ 4 files changed, 45 insertions(+), 8 deletions(-) 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 e856684c14e10..cd4dcfd36c8e4 100644 --- a/lib/web_ui/lib/src/engine/html/bitmap_canvas.dart +++ b/lib/web_ui/lib/src/engine/html/bitmap_canvas.dart @@ -916,7 +916,7 @@ class BitmapCanvas extends EngineCanvas { /// /// The text is drawn starting at coordinates ([x], [y]). It uses the current /// font set by the most recent call to [setCssFont]. - void fillText(String text, double x, double y, {List? shadows}) { + void fillText(String text, double x, double y, {List? shadows, bool paintStroke = false}) { final html.CanvasRenderingContext2D ctx = _canvasPool.context; if (shadows != null) { ctx.save(); @@ -927,10 +927,16 @@ class BitmapCanvas extends EngineCanvas { ctx.shadowOffsetY = shadow.offset.dy; ctx.fillText(text, x, y); + if (paintStroke) { + ctx.strokeText(text, x, y); + } } ctx.restore(); } ctx.fillText(text, x, y); + if (paintStroke) { + ctx.strokeText(text, x, y); + } } @override diff --git a/lib/web_ui/lib/src/engine/text/paint_service.dart b/lib/web_ui/lib/src/engine/text/paint_service.dart index 2d57b5e13ff2b..0dd50ae1ae72d 100644 --- a/lib/web_ui/lib/src/engine/text/paint_service.dart +++ b/lib/web_ui/lib/src/engine/text/paint_service.dart @@ -93,6 +93,7 @@ class TextPaintService { _applySpanStyleToCanvas(span, canvas); final double x = offset.dx + line.left + box.left; final double y = offset.dy + line.baseline; + final bool paintStroke = span.style.foreground?.strokeWidth != null; // Don't paint the text for space-only boxes. This is just an // optimization, it doesn't have any effect on the output. @@ -103,7 +104,7 @@ class TextPaintService { ); final double? letterSpacing = span.style.letterSpacing; if (letterSpacing == null || letterSpacing == 0.0) { - canvas.fillText(text, x, y, shadows: span.style.shadows); + canvas.fillText(text, x, y, shadows: span.style.shadows, paintStroke: paintStroke); } else { // TODO(mdebbar): Implement letter-spacing on canvas more efficiently: // https://github.com/flutter/flutter/issues/51234 @@ -112,7 +113,7 @@ class TextPaintService { for (int i = 0; i < len; i++) { final String char = text[i]; canvas.fillText(char, charX.roundToDouble(), y, - shadows: span.style.shadows); + shadows: span.style.shadows, paintStroke: paintStroke); charX += letterSpacing + canvas.measureText(char).width!; } } @@ -122,7 +123,7 @@ class TextPaintService { final String? ellipsis = line.ellipsis; if (ellipsis != null && box == line.boxes.last) { final double x = offset.dx + line.left + box.right; - canvas.fillText(ellipsis, x, y); + canvas.fillText(ellipsis, x, y, paintStroke: paintStroke); } canvas.tearDownPaint(); diff --git a/lib/web_ui/lib/src/engine/text/paragraph.dart b/lib/web_ui/lib/src/engine/text/paragraph.dart index 48e4f115c2262..e60c73a4c9ffb 100644 --- a/lib/web_ui/lib/src/engine/text/paragraph.dart +++ b/lib/web_ui/lib/src/engine/text/paragraph.dart @@ -699,10 +699,9 @@ void applyTextStyleToElement({ if (color != null) { cssStyle.color = colorToCssString(color); } - final ui.Paint? foreground = style.foreground; - if (foreground != null) { - cssStyle.textStroke = - '${foreground.strokeWidth}px ${colorToCssString(foreground.color)}'; + final double? strokeWidth = style.foreground?.strokeWidth; + if (strokeWidth != null) { + cssStyle.textStroke = '${strokeWidth}px ${colorToCssString(color)}'; } final ui.Color? background = style.background?.color; if (background != null) { diff --git a/lib/web_ui/test/html/paragraph/general_golden_test.dart b/lib/web_ui/test/html/paragraph/general_golden_test.dart index 261c452a075ac..59468eae9afcd 100644 --- a/lib/web_ui/test/html/paragraph/general_golden_test.dart +++ b/lib/web_ui/test/html/paragraph/general_golden_test.dart @@ -502,4 +502,35 @@ Future testMain() async { testBackgroundStyle(canvas); return takeScreenshot(canvas, bounds, 'canvas_paragraph_background_style_dom'); }); + + void testForegroundStyle(EngineCanvas canvas) { + final CanvasParagraph paragraph = rich( + EngineParagraphStyle(fontFamily: 'Roboto', fontSize: 40.0), + (CanvasParagraphBuilder builder) { + builder.pushStyle(EngineTextStyle.only(foreground: Paint()..color = blue)); + builder.addText('Lorem'); + builder.pushStyle(EngineTextStyle.only(foreground: Paint()..color = red..strokeWidth = 2.0)); + builder.addText('ipsum\ndo'); + builder.pop(); + builder.pushStyle(EngineTextStyle.only(foreground: Paint()..color = green..strokeWidth = 4.0)); + builder.addText('lor sit'); + }, + ); + paragraph.layout(constrain(double.infinity)); + canvas.drawParagraph(paragraph, Offset.zero); + } + + test('foreground style', () { + const Rect bounds = Rect.fromLTWH(0, 0, 300, 200); + final BitmapCanvas canvas = BitmapCanvas(bounds, RenderStrategy()); + testForegroundStyle(canvas); + return takeScreenshot(canvas, bounds, 'canvas_paragraph_foreground_style'); + }); + + test('foreground style (DOM)', () { + const Rect bounds = Rect.fromLTWH(0, 0, 300, 200); + final DomCanvas canvas = DomCanvas(domRenderer.createElement('flt-picture')); + testForegroundStyle(canvas); + return takeScreenshot(canvas, bounds, 'canvas_paragraph_foreground_style_dom'); + }); } From 8f8d59539cb7c99b8f5916b8aad76e9e02d572a2 Mon Sep 17 00:00:00 2001 From: Alexander Belokon <39626151+abelokon0711@users.noreply.github.com> Date: Tue, 23 Nov 2021 15:21:59 +0100 Subject: [PATCH 3/6] Adjust formatting --- lib/web_ui/lib/src/engine/html/bitmap_canvas.dart | 1 + 1 file changed, 1 insertion(+) 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 cd4dcfd36c8e4..32521691f79dd 100644 --- a/lib/web_ui/lib/src/engine/html/bitmap_canvas.dart +++ b/lib/web_ui/lib/src/engine/html/bitmap_canvas.dart @@ -933,6 +933,7 @@ class BitmapCanvas extends EngineCanvas { } ctx.restore(); } + ctx.fillText(text, x, y); if (paintStroke) { ctx.strokeText(text, x, y); From dd92cc5fd56b311a1238967aa2b5191f51aac6f1 Mon Sep 17 00:00:00 2001 From: Alexander Belokon <39626151+abelokon0711@users.noreply.github.com> Date: Thu, 2 Dec 2021 14:22:08 +0100 Subject: [PATCH 4/6] Just draw text stroke if PaintingStyle.stroke is set --- .../lib/src/engine/html/bitmap_canvas.dart | 12 +++++++----- .../lib/src/engine/text/paint_service.dart | 11 ++++++----- lib/web_ui/lib/src/engine/text/paragraph.dart | 15 ++++++++++----- .../html/paragraph/general_golden_test.dart | 17 ++++++++++++----- 4 files changed, 35 insertions(+), 20 deletions(-) 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 32521691f79dd..dfc220bdfbf57 100644 --- a/lib/web_ui/lib/src/engine/html/bitmap_canvas.dart +++ b/lib/web_ui/lib/src/engine/html/bitmap_canvas.dart @@ -916,7 +916,7 @@ class BitmapCanvas extends EngineCanvas { /// /// The text is drawn starting at coordinates ([x], [y]). It uses the current /// font set by the most recent call to [setCssFont]. - void fillText(String text, double x, double y, {List? shadows, bool paintStroke = false}) { + void drawText(String text, double x, double y, {ui.PaintingStyle? style, List? shadows}) { final html.CanvasRenderingContext2D ctx = _canvasPool.context; if (shadows != null) { ctx.save(); @@ -926,17 +926,19 @@ class BitmapCanvas extends EngineCanvas { ctx.shadowOffsetX = shadow.offset.dx; ctx.shadowOffsetY = shadow.offset.dy; - ctx.fillText(text, x, y); - if (paintStroke) { + if (style == ui.PaintingStyle.stroke) { ctx.strokeText(text, x, y); + } else { + ctx.fillText(text, x, y); } } ctx.restore(); } - ctx.fillText(text, x, y); - if (paintStroke) { + if (style == ui.PaintingStyle.stroke) { ctx.strokeText(text, x, y); + } else { + ctx.fillText(text, x, y); } } diff --git a/lib/web_ui/lib/src/engine/text/paint_service.dart b/lib/web_ui/lib/src/engine/text/paint_service.dart index 0dd50ae1ae72d..524c6a12db4ce 100644 --- a/lib/web_ui/lib/src/engine/text/paint_service.dart +++ b/lib/web_ui/lib/src/engine/text/paint_service.dart @@ -93,7 +93,6 @@ class TextPaintService { _applySpanStyleToCanvas(span, canvas); final double x = offset.dx + line.left + box.left; final double y = offset.dy + line.baseline; - final bool paintStroke = span.style.foreground?.strokeWidth != null; // Don't paint the text for space-only boxes. This is just an // optimization, it doesn't have any effect on the output. @@ -104,7 +103,8 @@ class TextPaintService { ); final double? letterSpacing = span.style.letterSpacing; if (letterSpacing == null || letterSpacing == 0.0) { - canvas.fillText(text, x, y, shadows: span.style.shadows, paintStroke: paintStroke); + canvas.drawText(text, x, y, + style: span.style.foreground?.style, shadows: span.style.shadows); } else { // TODO(mdebbar): Implement letter-spacing on canvas more efficiently: // https://github.com/flutter/flutter/issues/51234 @@ -112,8 +112,9 @@ class TextPaintService { final int len = text.length; for (int i = 0; i < len; i++) { final String char = text[i]; - canvas.fillText(char, charX.roundToDouble(), y, - shadows: span.style.shadows, paintStroke: paintStroke); + canvas.drawText(char, charX.roundToDouble(), y, + style: span.style.foreground?.style, + shadows: span.style.shadows); charX += letterSpacing + canvas.measureText(char).width!; } } @@ -123,7 +124,7 @@ class TextPaintService { final String? ellipsis = line.ellipsis; if (ellipsis != null && box == line.boxes.last) { final double x = offset.dx + line.left + box.right; - canvas.fillText(ellipsis, x, y, paintStroke: paintStroke); + canvas.drawText(ellipsis, x, y, style: span.style.foreground?.style); } canvas.tearDownPaint(); diff --git a/lib/web_ui/lib/src/engine/text/paragraph.dart b/lib/web_ui/lib/src/engine/text/paragraph.dart index e60c73a4c9ffb..1695c6174f07c 100644 --- a/lib/web_ui/lib/src/engine/text/paragraph.dart +++ b/lib/web_ui/lib/src/engine/text/paragraph.dart @@ -696,13 +696,18 @@ void applyTextStyleToElement({ final html.CssStyleDeclaration cssStyle = element.style; final ui.Color? color = style.foreground?.color ?? style.color; - if (color != null) { + if (style.foreground?.style == ui.PaintingStyle.stroke) { + // Use transparent text color to achieve same visual output as in canvas. + cssStyle.color = colorToCssString(const ui.Color(0x00)); + // Use hairline (device pixel when strokeWidth is not specified). + final double? strokeWidth = style.foreground?.strokeWidth; + final double adaptedWidth = strokeWidth != null && strokeWidth > 0 + ? strokeWidth + : 1.0 / ui.window.devicePixelRatio; + cssStyle.textStroke = '${adaptedWidth}px ${colorToCssString(color)}'; + } else if (color != null) { cssStyle.color = colorToCssString(color); } - final double? strokeWidth = style.foreground?.strokeWidth; - if (strokeWidth != null) { - cssStyle.textStroke = '${strokeWidth}px ${colorToCssString(color)}'; - } final ui.Color? background = style.background?.color; if (background != null) { cssStyle.backgroundColor = colorToCssString(background); diff --git a/lib/web_ui/test/html/paragraph/general_golden_test.dart b/lib/web_ui/test/html/paragraph/general_golden_test.dart index 59468eae9afcd..734d8eb65f2f4 100644 --- a/lib/web_ui/test/html/paragraph/general_golden_test.dart +++ b/lib/web_ui/test/html/paragraph/general_golden_test.dart @@ -507,13 +507,20 @@ Future testMain() async { final CanvasParagraph paragraph = rich( EngineParagraphStyle(fontFamily: 'Roboto', fontSize: 40.0), (CanvasParagraphBuilder builder) { - builder.pushStyle(EngineTextStyle.only(foreground: Paint()..color = blue)); + builder.pushStyle(EngineTextStyle.only(color: blue)); builder.addText('Lorem'); - builder.pushStyle(EngineTextStyle.only(foreground: Paint()..color = red..strokeWidth = 2.0)); - builder.addText('ipsum\ndo'); builder.pop(); - builder.pushStyle(EngineTextStyle.only(foreground: Paint()..color = green..strokeWidth = 4.0)); - builder.addText('lor sit'); + builder.pushStyle(EngineTextStyle.only(foreground: Paint()..color = red..style = PaintingStyle.stroke)); + builder.addText('ipsum\n'); + builder.pop(); + builder.pushStyle(EngineTextStyle.only(foreground: Paint()..color = blue..style = PaintingStyle.stroke..strokeWidth = 0.0)); + builder.addText('dolor'); + builder.pop(); + builder.pushStyle(EngineTextStyle.only(foreground: Paint()..color = green..style = PaintingStyle.stroke..strokeWidth = 2.0)); + builder.addText('sit\n'); + builder.pop(); + builder.pushStyle(EngineTextStyle.only(foreground: Paint()..color = yellow..style = PaintingStyle.stroke..strokeWidth = 4.0)); + builder.addText('amet'); }, ); paragraph.layout(constrain(double.infinity)); From 34f14fd7e20f45d031c1b3815cd58a4705a96375 Mon Sep 17 00:00:00 2001 From: Alexander Belokon <39626151+abelokon0711@users.noreply.github.com> Date: Fri, 3 Dec 2021 00:15:55 +0100 Subject: [PATCH 5/6] Add code explanation --- lib/web_ui/lib/src/engine/text/paragraph.dart | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/web_ui/lib/src/engine/text/paragraph.dart b/lib/web_ui/lib/src/engine/text/paragraph.dart index 1695c6174f07c..6c7187adb91d9 100644 --- a/lib/web_ui/lib/src/engine/text/paragraph.dart +++ b/lib/web_ui/lib/src/engine/text/paragraph.dart @@ -697,8 +697,13 @@ void applyTextStyleToElement({ final ui.Color? color = style.foreground?.color ?? style.color; if (style.foreground?.style == ui.PaintingStyle.stroke) { - // Use transparent text color to achieve same visual output as in canvas. - cssStyle.color = colorToCssString(const ui.Color(0x00)); + // When comparing the outputs of the Bitmap Canvas and the DOM + // implementation, we have found, that we need to set the background color + // of the text to transparent to achieve the same effect as in the Bitmap + // Canvas and the Skia Engine where only the text stroke is painted. + // If we don't set it here to transparent, the text will inherit the color + // of it's parent element. + cssStyle.color = 'transparent'; // Use hairline (device pixel when strokeWidth is not specified). final double? strokeWidth = style.foreground?.strokeWidth; final double adaptedWidth = strokeWidth != null && strokeWidth > 0 From f2f22c4622c8cc2d60b7161aafb3af443a91cf00 Mon Sep 17 00:00:00 2001 From: Alexander Belokon <39626151+abelokon0711@users.noreply.github.com> Date: Sat, 4 Dec 2021 00:40:34 +0100 Subject: [PATCH 6/6] Update goldens_lock.yaml --- lib/web_ui/dev/goldens_lock.yaml | 2 +- lib/web_ui/lib/src/engine/text/paragraph.dart | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/web_ui/dev/goldens_lock.yaml b/lib/web_ui/dev/goldens_lock.yaml index b2ba801a9d4c5..75c3c349ca9ea 100644 --- a/lib/web_ui/dev/goldens_lock.yaml +++ b/lib/web_ui/dev/goldens_lock.yaml @@ -1,2 +1,2 @@ repository: https://github.com/flutter/goldens.git -revision: 0dd2e82050422c05e9daf652fa4267fb7c01f260 +revision: 0e62a287b5109053bc3e59dd8fc832f488d904c9 diff --git a/lib/web_ui/lib/src/engine/text/paragraph.dart b/lib/web_ui/lib/src/engine/text/paragraph.dart index 6c7187adb91d9..a74657951bd2e 100644 --- a/lib/web_ui/lib/src/engine/text/paragraph.dart +++ b/lib/web_ui/lib/src/engine/text/paragraph.dart @@ -700,8 +700,8 @@ void applyTextStyleToElement({ // When comparing the outputs of the Bitmap Canvas and the DOM // implementation, we have found, that we need to set the background color // of the text to transparent to achieve the same effect as in the Bitmap - // Canvas and the Skia Engine where only the text stroke is painted. - // If we don't set it here to transparent, the text will inherit the color + // Canvas and the Skia Engine where only the text stroke is painted. + // If we don't set it here to transparent, the text will inherit the color // of it's parent element. cssStyle.color = 'transparent'; // Use hairline (device pixel when strokeWidth is not specified).