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

Commit 80c090d

Browse files
authored
Fix path bounds for multiple rects. Implement winding rules (#18165)
1 parent 805a887 commit 80c090d

File tree

6 files changed

+127
-24
lines changed

6 files changed

+127
-24
lines changed

lib/web_ui/dev/goldens_lock.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
repository: https://github.com/flutter/goldens.git
2-
revision: 790616cbfb269fe17d44840ce52ec187fff5f9a7
2+
revision: 0c1a793bfd49e30fccdd7ad64329a114a9cb32f7

lib/web_ui/lib/src/engine/canvas_pool.dart

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -501,8 +501,14 @@ class _CanvasPool extends _SaveStackTracking {
501501
/// 'Runs' the given [path] by applying all of its commands to the canvas.
502502
void _runPath(html.CanvasRenderingContext2D ctx, SurfacePath path) {
503503
ctx.beginPath();
504-
for (Subpath subpath in path.subpaths) {
505-
for (PathCommand command in subpath.commands) {
504+
final List<Subpath> subpaths = path.subpaths;
505+
final int subpathCount = subpaths.length;
506+
for (int subPathIndex = 0; subPathIndex < subpathCount; subPathIndex++) {
507+
final Subpath subpath = subpaths[subPathIndex];
508+
final List<PathCommand> commands = subpath.commands;
509+
final int commandCount = commands.length;
510+
for (int c = 0; c < commandCount; c++) {
511+
final PathCommand command = commands[c];
506512
switch (command.type) {
507513
case PathCommandTypes.bezierCurveTo:
508514
final BezierCurveTo curve = command;
@@ -587,7 +593,7 @@ class _CanvasPool extends _SaveStackTracking {
587593

588594
void drawPath(ui.Path path, ui.PaintingStyle style) {
589595
_runPath(context, path);
590-
contextHandle.paint(style);
596+
contextHandle.paintPath(style, path.fillType);
591597
}
592598

593599
void drawShadow(ui.Path path, ui.Color color, double elevation,
@@ -756,6 +762,18 @@ class ContextStateHandle {
756762
}
757763
}
758764

765+
void paintPath(ui.PaintingStyle style, ui.PathFillType pathFillType) {
766+
if (style == ui.PaintingStyle.stroke) {
767+
context.stroke();
768+
} else {
769+
if (pathFillType == ui.PathFillType.nonZero) {
770+
context.fill();
771+
} else {
772+
context.fill('evenodd');
773+
}
774+
}
775+
}
776+
759777
void reset() {
760778
context.fillStyle = '';
761779
// Read back fillStyle/strokeStyle values from context so that input such

lib/web_ui/lib/src/engine/surface/path.dart

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,22 +1005,22 @@ class SurfacePath implements ui.Path {
10051005
break;
10061006
case PathCommandTypes.rect:
10071007
final RectCommand cmd = op;
1008-
left = cmd.x;
1008+
minX = cmd.x;
10091009
double width = cmd.width;
10101010
if (cmd.width < 0) {
1011-
left -= width;
1011+
minX -= width;
10121012
width = -width;
10131013
}
1014-
double top = cmd.y;
1014+
minY = cmd.y;
10151015
double height = cmd.height;
10161016
if (cmd.height < 0) {
1017-
top -= height;
1017+
minY -= height;
10181018
height = -height;
10191019
}
1020-
curX = minX = left;
1021-
maxX = left + width;
1022-
curY = minY = top;
1023-
maxY = top + height;
1020+
curX = minX;
1021+
maxX = minX + width;
1022+
curY = minY;
1023+
maxY = minY + height;
10241024
break;
10251025
case PathCommandTypes.rRect:
10261026
final RRectCommand cmd = op;

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

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -440,19 +440,22 @@ class RecordingCanvas {
440440
return;
441441
}
442442
}
443-
_hasArbitraryPaint = true;
444-
_didDraw = true;
445-
ui.Rect pathBounds = path.getBounds();
446-
final double paintSpread = _getPaintSpread(paint);
447-
if (paintSpread != 0.0) {
448-
pathBounds = pathBounds.inflate(paintSpread);
443+
SurfacePath sPath = path;
444+
if (sPath.subpaths.isNotEmpty) {
445+
_hasArbitraryPaint = true;
446+
_didDraw = true;
447+
ui.Rect pathBounds = sPath.getBounds();
448+
final double paintSpread = _getPaintSpread(paint);
449+
if (paintSpread != 0.0) {
450+
pathBounds = pathBounds.inflate(paintSpread);
451+
}
452+
// Clone path so it can be reused for subsequent draw calls.
453+
final ui.Path clone = SurfacePath._shallowCopy(path);
454+
final PaintDrawPath command = PaintDrawPath(clone, paint.paintData);
455+
_paintBounds.grow(pathBounds, command);
456+
clone.fillType = sPath.fillType;
457+
_commands.add(command);
449458
}
450-
// Clone path so it can be reused for subsequent draw calls.
451-
final ui.Path clone = SurfacePath._shallowCopy(path);
452-
final PaintDrawPath command = PaintDrawPath(clone, paint.paintData);
453-
_paintBounds.grow(pathBounds, command);
454-
clone.fillType = path.fillType;
455-
_commands.add(command);
456459
}
457460

458461
void drawImage(ui.Image image, ui.Offset offset, SurfacePaint paint) {
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
// @dart = 2.6
6+
import 'dart:html' as html;
7+
8+
import 'package:ui/src/engine.dart';
9+
import 'package:ui/ui.dart';
10+
import 'package:test/test.dart';
11+
12+
import 'package:web_engine_tester/golden_tester.dart';
13+
14+
void main() async {
15+
final Rect region = Rect.fromLTWH(0, 0, 500, 500);
16+
17+
BitmapCanvas canvas;
18+
19+
setUp(() {
20+
canvas = BitmapCanvas(region);
21+
});
22+
23+
tearDown(() {
24+
canvas.rootElement.remove();
25+
});
26+
27+
test('draws paths using nonzero and evenodd winding rules', () async {
28+
paintPaths(canvas);
29+
html.document.body.append(canvas.rootElement);
30+
await matchGoldenFile('canvas_path_winding.png', region: region);
31+
});
32+
33+
}
34+
35+
void paintPaths(BitmapCanvas canvas) {
36+
canvas.drawRect(Rect.fromLTRB(0, 0, 500, 500),
37+
SurfacePaintData()
38+
..color = Color(0xFFFFFFFF)
39+
..style = PaintingStyle.fill); // white
40+
41+
SurfacePaint paintFill = SurfacePaint()
42+
..style = PaintingStyle.fill
43+
..color = Color(0xFF00B0FF);
44+
SurfacePaint paintStroke = SurfacePaint()
45+
..style = PaintingStyle.stroke
46+
..strokeWidth = 2
47+
..color = Color(0xFFE00000);
48+
Path path1 = Path()
49+
..fillType = PathFillType.evenOdd
50+
..moveTo(50, 0)
51+
..lineTo(21, 90)
52+
..lineTo(98, 35)
53+
..lineTo(2, 35)
54+
..lineTo(79, 90)
55+
..close()
56+
..addRect(Rect.fromLTWH(20, 100, 200, 50))
57+
..addRect(Rect.fromLTWH(40, 120, 160, 10));
58+
Path path2 = Path()
59+
..fillType = PathFillType.nonZero
60+
..moveTo(50, 200)
61+
..lineTo(21, 290)
62+
..lineTo(98, 235)
63+
..lineTo(2, 235)
64+
..lineTo(79, 290)
65+
..close()
66+
..addRect(Rect.fromLTWH(20, 300, 200, 50))
67+
..addRect(Rect.fromLTWH(40, 320, 160, 10));
68+
canvas.drawPath(path1, paintFill.paintData);
69+
canvas.drawPath(path2, paintFill.paintData);
70+
canvas.drawPath(path1, paintStroke.paintData);
71+
canvas.drawPath(path2, paintStroke.paintData);
72+
}

lib/web_ui/test/path_test.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,16 @@ void main() {
128128
expect(path.getBounds(), const Rect.fromLTRB(50, 60, 50, 60));
129129
});
130130

131+
test('Should compute bounds for multiple addRect calls', () {
132+
final Path emptyPath = Path();
133+
expect(emptyPath.getBounds(), Rect.zero);
134+
135+
final SurfacePath path = SurfacePath();
136+
path.addRect(Rect.fromLTWH(0, 0, 270, 45));
137+
path.addRect(Rect.fromLTWH(134.5, 0, 1, 45));
138+
expect(path.getBounds(), const Rect.fromLTRB(0, 0, 270, 45));
139+
});
140+
131141
test('Should compute bounds for lines', () {
132142
final SurfacePath path = SurfacePath();
133143
path.moveTo(25, 30);

0 commit comments

Comments
 (0)