diff --git a/lib/web_ui/lib/src/engine/recording_canvas.dart b/lib/web_ui/lib/src/engine/recording_canvas.dart index db9a4927cb7f5..2608a6e30de62 100644 --- a/lib/web_ui/lib/src/engine/recording_canvas.dart +++ b/lib/web_ui/lib/src/engine/recording_canvas.dart @@ -7,8 +7,16 @@ part of engine; /// Enable this to print every command applied by a canvas. const bool _debugDumpPaintCommands = false; -// Similar to [Offset.distance] -double _getDistance(double x, double y) => math.sqrt(x * x + y * y); +// Returns the squared length of the x, y (of a border radius) +// It normalizes x, y values before working with them, by +// assuming anything < 0 to be 0, because flutter may pass +// negative radii (which Skia assumes to be 0), see: +// https://skia.org/user/api/SkRRect_Reference#SkRRect_inset +double _measureBorderRadius(double x, double y) { + double clampedX = x < 0 ? 0 : x; + double clampedY = y < 0 ? 0 : y; + return clampedX * clampedX + clampedY * clampedY; +} /// Records canvas commands to be applied to a [EngineCanvas]. /// @@ -231,8 +239,8 @@ class RecordingCanvas { } void drawDRRect(ui.RRect outer, ui.RRect inner, ui.Paint paint) { - // Ensure inner is fully contained within outer, by comparing its - // defining points (including its border radius) + // Check the inner bounds are contained within the outer bounds + // see: https://cs.chromium.org/chromium/src/third_party/skia/src/core/SkCanvas.cpp?l=1787-1789 ui.Rect innerRect = inner.outerRect; ui.Rect outerRect = outer.outerRect; if (outerRect == innerRect || outerRect.intersect(innerRect) != innerRect) { @@ -243,15 +251,15 @@ class RecordingCanvas { final ui.RRect scaledOuter = outer.scaleRadii(); final ui.RRect scaledInner = inner.scaleRadii(); - final double outerTl = _getDistance(scaledOuter.tlRadiusX, scaledOuter.tlRadiusY); - final double outerTr = _getDistance(scaledOuter.trRadiusX, scaledOuter.trRadiusY); - final double outerBl = _getDistance(scaledOuter.blRadiusX, scaledOuter.blRadiusY); - final double outerBr = _getDistance(scaledOuter.brRadiusX, scaledOuter.brRadiusY); + final double outerTl = _measureBorderRadius(scaledOuter.tlRadiusX, scaledOuter.tlRadiusY); + final double outerTr = _measureBorderRadius(scaledOuter.trRadiusX, scaledOuter.trRadiusY); + final double outerBl = _measureBorderRadius(scaledOuter.blRadiusX, scaledOuter.blRadiusY); + final double outerBr = _measureBorderRadius(scaledOuter.brRadiusX, scaledOuter.brRadiusY); - final double innerTl = _getDistance(scaledInner.tlRadiusX, scaledInner.tlRadiusY); - final double innerTr = _getDistance(scaledInner.trRadiusX, scaledInner.trRadiusY); - final double innerBl = _getDistance(scaledInner.blRadiusX, scaledInner.blRadiusY); - final double innerBr = _getDistance(scaledInner.brRadiusX, scaledInner.brRadiusY); + final double innerTl = _measureBorderRadius(scaledInner.tlRadiusX, scaledInner.tlRadiusY); + final double innerTr = _measureBorderRadius(scaledInner.trRadiusX, scaledInner.trRadiusY); + final double innerBl = _measureBorderRadius(scaledInner.blRadiusX, scaledInner.blRadiusY); + final double innerBr = _measureBorderRadius(scaledInner.brRadiusX, scaledInner.brRadiusY); if (innerTl > outerTl || innerTr > outerTr || innerBl > outerBl || innerBr > outerBr) { return; // Some inner radius is overlapping some outer radius diff --git a/lib/web_ui/test/engine/recording_canvas_test.dart b/lib/web_ui/test/engine/recording_canvas_test.dart index 6b5495fe13f9e..402a93e71f570 100644 --- a/lib/web_ui/test/engine/recording_canvas_test.dart +++ b/lib/web_ui/test/engine/recording_canvas_test.dart @@ -54,6 +54,27 @@ void main() { expect(mockCanvas.methodCallLog.length, equals(0)); }); + test('negative corners in inner RRect get passed through to draw', () { + // This comes from github issue #40728 + final RRect outer = RRect.fromRectAndCorners(const Rect.fromLTWH(0, 0, 88, 48), + topLeft: Radius.circular(6), bottomLeft: Radius.circular(6)); + final RRect inner = outer.deflate(1); + + // If these assertions fail, check [_measureBorderRadius] in recording_canvas.dart + expect(inner.brRadius, equals(Radius.circular(-1))); + expect(inner.trRadius, equals(Radius.circular(-1))); + + underTest.drawDRRect(outer, inner, somePaint); + underTest.apply(mockCanvas); + + // Expect to draw, even when inner has negative radii (which get ignored by canvas) + _expectDrawCall(mockCanvas, { + 'outer': outer, + 'inner': inner, + 'paint': somePaint.webOnlyPaintData, + }); + }); + test('preserve old scuba test behavior', () { final RRect outer = RRect.fromRectAndCorners(const Rect.fromLTRB(10, 20, 30, 40)); final RRect inner = RRect.fromRectAndCorners(const Rect.fromLTRB(12, 22, 28, 38));