@@ -75,14 +75,16 @@ class DomCanvas extends EngineCanvas with SaveElementStackTracking {
7575
7676 @override
7777 void drawRect (ui.Rect rect, SurfacePaintData paint) {
78+ rect = adjustRectForDom (rect, paint);
7879 currentElement.append (
7980 buildDrawRectElement (rect, paint, 'draw-rect' , currentTransform));
8081 }
8182
8283 @override
8384 void drawRRect (ui.RRect rrect, SurfacePaintData paint) {
85+ final ui.Rect outerRect = adjustRectForDom (rrect.outerRect, paint);
8486 final DomElement element = buildDrawRectElement (
85- rrect. outerRect, paint, 'draw-rrect' , currentTransform);
87+ outerRect, paint, 'draw-rrect' , currentTransform);
8688 applyRRectBorderRadius (element.style, rrect);
8789 currentElement.append (element);
8890 }
@@ -160,8 +162,77 @@ ui.Color blurColor(ui.Color color, double sigma) {
160162 return ui.Color ((reducedAlpha & 0xff ) << 24 | (color.value & 0x00ffffff ));
161163}
162164
165+ /// When drawing a shape (rect, rrect, circle, etc) in DOM/CSS, the [rect] given
166+ /// by Flutter needs to be adjusted to what DOM/CSS expect.
167+ ///
168+ /// This method takes Flutter's [rect] and produces a new rect that can be used
169+ /// to generate the correct CSS properties to match Flutter's expectations.
170+ ///
171+ ///
172+ /// Here's what Flutter's given [rect] and [paint.strokeWidth] represent:
173+ ///
174+ /// top-left ↓
175+ /// ┌──↓──────────────────────┐
176+ /// →→→→x x │←←
177+ /// │ ┌───────────────┐ │ |
178+ /// │ │ │ │ |
179+ /// │ │ │ │ | height
180+ /// │ │ │ │ |
181+ /// │ └───────────────┘ │ |
182+ /// │ x x │←←
183+ /// └─────────────────────────┘
184+ /// stroke-width ↑----↑ ↑
185+ /// ↑-------------------↑ width
186+ ///
187+ ///
188+ ///
189+ /// In the DOM/CSS, here's how the coordinates should look like:
190+ ///
191+ /// top-left ↓
192+ /// →→x─────────────────────────┐
193+ /// │ │
194+ /// │ x───────────────x │←←
195+ /// │ │ │ │ |
196+ /// │ │ │ │ | height
197+ /// │ │ │ │ |
198+ /// │ x───────────────x │←←
199+ /// │ │
200+ /// └─────────────────────────┘
201+ /// border-width ↑----↑ ↑
202+ /// ↑---------------↑ width
203+ ///
204+ /// As shown in the drawing above, the width/height don't start at the top-left
205+ /// coordinates. Instead, they start from the inner top-left (inside the border).
206+ ui.Rect adjustRectForDom (ui.Rect rect, SurfacePaintData paint) {
207+ double left = math.min (rect.left, rect.right);
208+ double top = math.min (rect.top, rect.bottom);
209+ double width = rect.width.abs ();
210+ double height = rect.height.abs ();
211+
212+ final bool isStroke = paint.style == ui.PaintingStyle .stroke;
213+ final double strokeWidth = paint.strokeWidth ?? 0.0 ;
214+ if (isStroke && strokeWidth > 0.0 ) {
215+ left -= strokeWidth / 2.0 ;
216+ top -= strokeWidth / 2.0 ;
217+
218+ // width and height shouldn't go below zero.
219+ width = math.max (0 , width - strokeWidth);
220+ height = math.max (0 , height - strokeWidth);
221+ }
222+
223+ if (left != rect.left ||
224+ top != rect.top ||
225+ width != rect.width ||
226+ height != rect.height) {
227+ return ui.Rect .fromLTWH (left, top, width, height);
228+ }
229+ return rect;
230+ }
231+
163232DomHTMLElement buildDrawRectElement (
164233 ui.Rect rect, SurfacePaintData paint, String tagName, Matrix4 transform) {
234+ assert (rect.left <= rect.right);
235+ assert (rect.top <= rect.bottom);
165236 final DomHTMLElement rectangle = domDocument.createElement (tagName) as
166237 DomHTMLElement ;
167238 assert (() {
@@ -172,26 +243,11 @@ DomHTMLElement buildDrawRectElement(
172243 String effectiveTransform;
173244 final bool isStroke = paint.style == ui.PaintingStyle .stroke;
174245 final double strokeWidth = paint.strokeWidth ?? 0.0 ;
175- final double left = math.min (rect.left, rect.right);
176- final double right = math.max (rect.left, rect.right);
177- final double top = math.min (rect.top, rect.bottom);
178- final double bottom = math.max (rect.top, rect.bottom);
179246 if (transform.isIdentity ()) {
180- if (isStroke) {
181- effectiveTransform =
182- 'translate(${left - (strokeWidth / 2.0 )}px, ${top - (strokeWidth / 2.0 )}px)' ;
183- } else {
184- effectiveTransform = 'translate(${left }px, ${top }px)' ;
185- }
247+ effectiveTransform = 'translate(${rect .left }px, ${rect .top }px)' ;
186248 } else {
187- // Clone to avoid mutating _transform.
188- final Matrix4 translated = transform.clone ();
189- if (isStroke) {
190- translated.translate (
191- left - (strokeWidth / 2.0 ), top - (strokeWidth / 2.0 ));
192- } else {
193- translated.translate (left, top);
194- }
249+ // Clone to avoid mutating `transform`.
250+ final Matrix4 translated = transform.clone ()..translate (rect.left, rect.top);
195251 effectiveTransform = matrix4ToCssTransform (translated);
196252 }
197253 final DomCSSStyleDeclaration style = rectangle.style;
@@ -216,15 +272,14 @@ DomHTMLElement buildDrawRectElement(
216272 }
217273 }
218274
275+ style
276+ ..width = '${rect .width }px'
277+ ..height = '${rect .height }px' ;
278+
219279 if (isStroke) {
220- style
221- ..width = '${right - left - strokeWidth }px'
222- ..height = '${bottom - top - strokeWidth }px'
223- ..border = '${_borderStrokeToCssUnit (strokeWidth )} solid $cssColor ' ;
280+ style.border = '${_borderStrokeToCssUnit (strokeWidth )} solid $cssColor ' ;
224281 } else {
225282 style
226- ..width = '${right - left }px'
227- ..height = '${bottom - top }px'
228283 ..backgroundColor = cssColor
229284 ..backgroundImage = _getBackgroundImageCssValue (paint.shader, rect);
230285 }
0 commit comments