Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit d762142

Browse files
authored
reland fix canvas drawLine bugs (#38949)
* fix canvas drawLine bugs * removed unecessary params from pathToSvgElement Co-authored-by: alanwutang11 <[email protected]>
1 parent 7d71ee3 commit d762142

File tree

4 files changed

+88
-25
lines changed

4 files changed

+88
-25
lines changed

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

+1-17
Original file line numberDiff line numberDiff line change
@@ -538,20 +538,6 @@ class BitmapCanvas extends EngineCanvas {
538538
if (_useDomForRenderingFill(paint)) {
539539
final Matrix4 transform = _canvasPool.currentTransform;
540540
final SurfacePath surfacePath = path as SurfacePath;
541-
final ui.Rect? pathAsLine = surfacePath.toStraightLine();
542-
if (pathAsLine != null) {
543-
ui.Rect rect = (pathAsLine.top == pathAsLine.bottom)
544-
? ui.Rect.fromLTWH(
545-
pathAsLine.left, pathAsLine.top, pathAsLine.width, 1)
546-
: ui.Rect.fromLTWH(
547-
pathAsLine.left, pathAsLine.top, 1, pathAsLine.height);
548-
549-
rect = adjustRectForDom(rect, paint);
550-
final DomHTMLElement element = buildDrawRectElement(
551-
rect, paint, 'draw-rect', _canvasPool.currentTransform);
552-
_drawElement(element, rect.topLeft, paint);
553-
return;
554-
}
555541

556542
final ui.Rect? pathAsRect = surfacePath.toRect();
557543
if (pathAsRect != null) {
@@ -563,9 +549,7 @@ class BitmapCanvas extends EngineCanvas {
563549
drawRRect(pathAsRRect, paint);
564550
return;
565551
}
566-
final ui.Rect pathBounds = surfacePath.getBounds();
567-
final DomElement svgElm = pathToSvgElement(
568-
surfacePath, paint, '${pathBounds.right}', '${pathBounds.bottom}');
552+
final DomElement svgElm = pathToSvgElement(surfacePath, paint);
569553
if (!_canvasPool.isClipped) {
570554
final DomCSSStyleDeclaration style = svgElm.style;
571555
style.position = 'absolute';

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

+1-3
Original file line numberDiff line numberDiff line change
@@ -392,9 +392,7 @@ class PersistedPhysicalShape extends PersistedContainerSurface
392392
path,
393393
SurfacePaintData()
394394
..style = ui.PaintingStyle.fill
395-
..color = color.value,
396-
'${pathBounds2.right}',
397-
'${pathBounds2.bottom}');
395+
..color = color.value);
398396

399397
/// Render element behind the clipped content.
400398
rootElement!.insertBefore(_svgElement!, childContainer);

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

+6-5
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import '../svg.dart';
1414
import '../text/canvas_paragraph.dart';
1515
import '../util.dart';
1616
import '../vector_math.dart';
17+
import 'bitmap_canvas.dart';
1718
import 'painting.dart';
1819
import 'path/path.dart';
1920
import 'path/path_to_svg.dart';
@@ -333,14 +334,11 @@ String _borderStrokeToCssUnit(double value) {
333334
return '${value.toStringAsFixed(3)}px';
334335
}
335336

336-
SVGSVGElement pathToSvgElement(
337-
SurfacePath path, SurfacePaintData paint, String width, String height) {
337+
SVGSVGElement pathToSvgElement(SurfacePath path, SurfacePaintData paint) {
338338
// In Firefox some SVG typed attributes are returned as null without a
339339
// setter. So we use strings here.
340340
final SVGSVGElement root = createSVGSVGElement()
341-
..setAttribute('width', '${width}px')
342-
..setAttribute('height', '${height}px')
343-
..setAttribute('viewBox', '0 0 $width $height');
341+
..setAttribute('overflow', 'visible');
344342

345343
final SVGPathElement svgPath = createSVGPathElement();
346344
root.append(svgPath);
@@ -350,6 +348,9 @@ SVGSVGElement pathToSvgElement(
350348
paint.strokeWidth != null)) {
351349
svgPath.setAttribute('stroke', colorValueToCssString(paint.color)!);
352350
svgPath.setAttribute('stroke-width', '${paint.strokeWidth ?? 1.0}');
351+
if (paint.strokeCap != null) {
352+
svgPath.setAttribute('stroke-linecap', '${stringForStrokeCap(paint.strokeCap)}');
353+
}
353354
svgPath.setAttribute('fill', 'none');
354355
} else if (paint.color != null) {
355356
svgPath.setAttribute('fill', colorValueToCssString(paint.color)!);

lib/web_ui/test/html/drawing/canvas_lines_golden_test.dart

+80
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,17 @@ Future<void> testMain() async {
1717
const Rect region = Rect.fromLTWH(0, 0, 300, 300);
1818

1919
late BitmapCanvas canvas;
20+
late BitmapCanvas domCanvas;
2021

2122
setUp(() {
2223
canvas = BitmapCanvas(region, RenderStrategy());
24+
// setting isInsideSvgFilterTree true forces use of DOM canvas
25+
domCanvas = BitmapCanvas(region, RenderStrategy()..isInsideSvgFilterTree = true);
2326
});
2427

2528
tearDown(() {
2629
canvas.rootElement.remove();
30+
domCanvas.rootElement.remove();
2731
});
2832

2933
test('draws lines with varying strokeWidth', () async {
@@ -33,6 +37,75 @@ Future<void> testMain() async {
3337
domDocument.body!.append(canvas.rootElement);
3438
await matchGoldenFile('canvas_lines_thickness.png', region: region);
3539
});
40+
test('draws lines with varying strokeWidth with dom canvas', () async {
41+
42+
paintLines(domCanvas);
43+
44+
domDocument.body!.append(domCanvas.rootElement);
45+
await matchGoldenFile('canvas_lines_thickness_dom_canvas.png', region: region);
46+
});
47+
test('draws lines with negative Offset values with dom canvas', () async {
48+
// test rendering lines correctly with negative offset when using DOM
49+
final SurfacePaintData paintWithStyle = SurfacePaintData()
50+
..color = 0xFFE91E63 // Colors.pink
51+
..style = PaintingStyle.stroke
52+
..strokeWidth = 16
53+
..strokeCap = StrokeCap.round;
54+
55+
// canvas.drawLine ignores paint.style (defaults to fill) according to api docs.
56+
// expect lines are rendered the same regardless of the set paint.style
57+
final SurfacePaintData paintWithoutStyle = SurfacePaintData()
58+
..color = 0xFF4CAF50 // Colors.green
59+
..strokeWidth = 16
60+
..strokeCap = StrokeCap.round;
61+
62+
// test vertical, horizontal, and diagonal lines
63+
final List<Offset> points = <Offset>[
64+
const Offset(-25, 50), const Offset(45, 50),
65+
const Offset(100, -25), const Offset(100, 200),
66+
const Offset(-150, -145), const Offset(100, 200),
67+
];
68+
final List<Offset> shiftedPoints = points.map((Offset point) => point.translate(20, 20)).toList();
69+
70+
paintLinesFromPoints(domCanvas, paintWithStyle, points);
71+
paintLinesFromPoints(domCanvas, paintWithoutStyle, shiftedPoints);
72+
73+
domDocument.body!.append(domCanvas.rootElement);
74+
await matchGoldenFile('canvas_lines_with_negative_offset.png', region: region);
75+
});
76+
77+
test('drawLines method respects strokeCap with dom canvas', () async {
78+
final SurfacePaintData paintStrokeCapRound = SurfacePaintData()
79+
..color = 0xFFE91E63 // Colors.pink
80+
..strokeWidth = 16
81+
..strokeCap = StrokeCap.round;
82+
83+
final SurfacePaintData paintStrokeCapSquare = SurfacePaintData()
84+
..color = 0xFF4CAF50 // Colors.green
85+
..strokeWidth = 16
86+
..strokeCap = StrokeCap.square;
87+
88+
final SurfacePaintData paintStrokeCapButt = SurfacePaintData()
89+
..color = 0xFFFF9800 // Colors.orange
90+
..strokeWidth = 16
91+
..strokeCap = StrokeCap.butt;
92+
93+
// test vertical, horizontal, and diagonal lines
94+
final List<Offset> points = <Offset>[
95+
const Offset(5, 50), const Offset(45, 50),
96+
const Offset(100, 5), const Offset(100, 200),
97+
const Offset(5, 10), const Offset(100, 200),
98+
];
99+
final List<Offset> shiftedPoints = points.map((Offset point) => point.translate(50, 50)).toList();
100+
final List<Offset> twiceShiftedPoints = shiftedPoints.map((Offset point) => point.translate(50, 50)).toList();
101+
102+
paintLinesFromPoints(domCanvas, paintStrokeCapRound, points);
103+
paintLinesFromPoints(domCanvas, paintStrokeCapSquare, shiftedPoints);
104+
paintLinesFromPoints(domCanvas, paintStrokeCapButt, twiceShiftedPoints);
105+
106+
domDocument.body!.append(domCanvas.rootElement);
107+
await matchGoldenFile('canvas_lines_with_strokeCap.png', region: region);
108+
});
36109
}
37110

38111
void paintLines(BitmapCanvas canvas) {
@@ -70,3 +143,10 @@ void paintLines(BitmapCanvas canvas) {
70143
paint3.color = 0xFFFF9800; // Colors.orange;
71144
canvas.drawLine(const Offset(50, 70), const Offset(150, 70), paint3);
72145
}
146+
147+
void paintLinesFromPoints(BitmapCanvas canvas, SurfacePaintData paint, List<Offset> points) {
148+
// points list contains pairs of Offset points, so for loop step is 2
149+
for (int i = 0; i < points.length - 1; i += 2) {
150+
canvas.drawLine(points[i], points[i + 1], paint);
151+
}
152+
}

0 commit comments

Comments
 (0)