diff --git a/lib/web_ui/lib/src/engine/html/path/path.dart b/lib/web_ui/lib/src/engine/html/path/path.dart
index a917964d4e1d5..3eb52f8ea0fd3 100644
--- a/lib/web_ui/lib/src/engine/html/path/path.dart
+++ b/lib/web_ui/lib/src/engine/html/path/path.dart
@@ -1007,80 +1007,42 @@ class SurfacePath implements ui.Path {
_addOval(bounds, direction, startIndex ~/ 2);
} else {
final double weight = SPath.scalarRoot2Over2;
- final double left = bounds.left;
- final double right = bounds.right;
- final double top = bounds.top;
- final double bottom = bounds.bottom;
+ double left = bounds.left;
+ double right = bounds.right;
+ double top = bounds.top;
+ double bottom = bounds.bottom;
final double width = right - left;
final double height = bottom - top;
// Proportionally scale down all radii to fit. Find the minimum ratio
// of a side and the radii on that side (for all four sides) and use
// that to scale down _all_ the radii. This algorithm is from the
// W3 spec (http://www.w3.org/TR/css3-background/) section 5.5
- double tlRadiusX = math.max(0, rrect.tlRadiusX);
- double trRadiusX = math.max(0, rrect.trRadiusX);
- double blRadiusX = math.max(0, rrect.blRadiusX);
- double brRadiusX = math.max(0, rrect.brRadiusX);
- double tlRadiusY = math.max(0, rrect.tlRadiusY);
- double trRadiusY = math.max(0, rrect.trRadiusY);
- double blRadiusY = math.max(0, rrect.blRadiusY);
- double brRadiusY = math.max(0, rrect.brRadiusY);
+ final double tlRadiusX = math.max(0, rrect.tlRadiusX);
+ final double trRadiusX = math.max(0, rrect.trRadiusX);
+ final double blRadiusX = math.max(0, rrect.blRadiusX);
+ final double brRadiusX = math.max(0, rrect.brRadiusX);
+ final double tlRadiusY = math.max(0, rrect.tlRadiusY);
+ final double trRadiusY = math.max(0, rrect.trRadiusY);
+ final double blRadiusY = math.max(0, rrect.blRadiusY);
+ final double brRadiusY = math.max(0, rrect.brRadiusY);
double scale = _computeMinScale(tlRadiusX, trRadiusX, width, 1.0);
scale = _computeMinScale(blRadiusX, brRadiusX, width, scale);
scale = _computeMinScale(tlRadiusY, trRadiusY, height, scale);
scale = _computeMinScale(blRadiusY, brRadiusY, height, scale);
- if (scale != 1.0) {
- tlRadiusX = scale * tlRadiusX;
- trRadiusX = scale * trRadiusX;
- blRadiusX = scale * blRadiusX;
- brRadiusX = scale * brRadiusX;
- tlRadiusY = scale * tlRadiusY;
- trRadiusY = scale * trRadiusY;
- blRadiusY = scale * blRadiusY;
- brRadiusY = scale * brRadiusY;
- }
-
- // Whether we had to alter the rrect parameters for correctness.
- final bool isRRectCorrected =
- tlRadiusX != rrect.tlRadiusX ||
- trRadiusX != rrect.trRadiusX ||
- blRadiusX != rrect.blRadiusX ||
- brRadiusX != rrect.brRadiusX ||
- tlRadiusY != rrect.tlRadiusY ||
- trRadiusY != rrect.trRadiusY ||
- blRadiusY != rrect.blRadiusY ||
- brRadiusY != rrect.brRadiusY;
-
- // Expand the rrect into a series of path ops.
- moveTo(left, bottom - blRadiusY);
- lineTo(left, top + tlRadiusY);
- conicTo(left, top, left + tlRadiusX, top, weight);
- lineTo(right - trRadiusX, top);
- conicTo(right, top, right, top + trRadiusY, weight);
- lineTo(right, bottom - brRadiusY);
- conicTo(right, bottom, right - brRadiusX, bottom, weight);
- lineTo(left + blRadiusX, bottom);
- conicTo(left, bottom, left, bottom - blRadiusY, weight);
+ // Inlined version of:
+ moveTo(left, bottom - scale * blRadiusY);
+ lineTo(left, top + scale * tlRadiusY);
+ conicTo(left, top, left + scale * tlRadiusX, top, weight);
+ lineTo(right - scale * trRadiusX, top);
+ conicTo(right, top, right, top + scale * trRadiusY, weight);
+ lineTo(right, bottom - scale * brRadiusY);
+ conicTo(right, bottom, right - scale * brRadiusX, bottom, weight);
+ lineTo(left + scale * blRadiusX, bottom);
+ conicTo(left, bottom, left, bottom - scale * blRadiusY, weight);
close();
// SkAutoDisableDirectionCheck.
_firstDirection = isRRect ? direction : SPathDirection.kUnknown;
-
- // No need to duplicate the rrect if we know the path is not made of a
- // single rrect, or if the original rrect was consistent and didn't have
- // to be corrected.
- if (isRRect && isRRectCorrected) {
- rrect = ui.RRect.fromLTRBAndCorners(
- left,
- top,
- right,
- bottom,
- topLeft: ui.Radius.elliptical(tlRadiusX, tlRadiusY),
- topRight: ui.Radius.elliptical(trRadiusX, trRadiusY),
- bottomLeft: ui.Radius.elliptical(blRadiusX, blRadiusY),
- bottomRight: ui.Radius.elliptical(brRadiusX, brRadiusY),
- );
- }
pathRef.setIsRRect(
isRRect, direction == SPathDirection.kCCW, startIndex % 8, rrect);
}
diff --git a/lib/web_ui/lib/src/engine/html/path/path_ref.dart b/lib/web_ui/lib/src/engine/html/path/path_ref.dart
index 334f1dcc47061..e4ded5a2fe3ce 100644
--- a/lib/web_ui/lib/src/engine/html/path/path_ref.dart
+++ b/lib/web_ui/lib/src/engine/html/path/path_ref.dart
@@ -61,9 +61,9 @@ class PathRef {
fBoundsIsDirty = true; // this also invalidates fIsFinite
fSegmentMask = 0;
fIsOval = false;
- rrectRepresentation = null;
+ fIsRRect = false;
fIsRect = false;
- // The next two values don't matter unless fIsOval is true or rrectRepresentation is not null.
+ // The next two values don't matter unless fIsOval or fIsRRect are true.
fRRectOrOvalIsCCW = false;
fRRectOrOvalStartIdx = 0xAC;
assert(() {
@@ -104,7 +104,7 @@ class PathRef {
}
fSegmentMask = ref.fSegmentMask;
fIsOval = ref.fIsOval;
- rrectRepresentation = ref.rrectRepresentation;
+ fIsRRect = ref.fIsRRect;
fIsRect = ref.fIsRect;
fRRectOrOvalIsCCW = ref.fRRectOrOvalIsCCW;
fRRectOrOvalStartIdx = ref.fRRectOrOvalStartIdx;
@@ -153,9 +153,9 @@ class PathRef {
int get isOval => fIsOval ? fRRectOrOvalStartIdx : -1;
bool get isOvalCCW => fRRectOrOvalIsCCW;
- int get isRRect => rrectRepresentation != null ? fRRectOrOvalStartIdx : -1;
+ int get isRRect => fIsRRect ? fRRectOrOvalStartIdx : -1;
int get isRect => fIsRect ? fRRectOrOvalStartIdx : -1;
- ui.RRect? getRRect() => rrectRepresentation;
+ ui.RRect? getRRect() => fIsRRect ? _getRRect() : null;
ui.Rect? getRect() {
/// Use _detectRect() for detection if explicity addRect was used (fIsRect) or
/// it is a potential due to moveTo + 3 lineTo verbs.
@@ -227,6 +227,70 @@ class PathRef {
return null;
}
+ /// Reconstructs RRect from path commands.
+ ///
+ /// Expect 4 Conics and lines between.
+ /// Use conic points to calculate corner radius.
+ ui.RRect _getRRect() {
+ ui.Rect bounds = getBounds();
+ // Radii x,y of 4 corners
+ final List radii = [];
+ final PathRefIterator iter = PathRefIterator(this);
+ final Float32List pts = Float32List(PathRefIterator.kMaxBufferSize);
+ int verb = iter.next(pts);
+ assert(SPath.kMoveVerb == verb);
+ int cornerIndex = 0;
+ while ((verb = iter.next(pts)) != SPath.kDoneVerb) {
+ if (SPath.kConicVerb == verb) {
+ final double controlPx = pts[2];
+ final double controlPy = pts[3];
+ double vector1_0x = controlPx - pts[0];
+ double vector1_0y = controlPy - pts[1];
+ double vector2_1x = pts[4] - pts[2];
+ double vector2_1y = pts[5] - pts[3];
+ double dx, dy;
+ // Depending on the corner we have control point at same
+ // horizontal position as startpoint or same vertical position.
+ // The location delta of control point specifies corner radius.
+ if (vector1_0x != 0.0) {
+ // For CW : Top right or bottom left corners.
+ assert(vector2_1x == 0.0 && vector1_0y == 0.0);
+ dx = vector1_0x.abs();
+ dy = vector2_1y.abs();
+ } else if (vector1_0y != 0.0) {
+ assert(vector2_1x == 0.0 || vector2_1y == 0.0);
+ dx = vector2_1x.abs();
+ dy = vector1_0y.abs();
+ } else {
+ assert(vector2_1y == 0.0);
+ dx = vector1_0x.abs();
+ dy = vector1_0y.abs();
+ }
+ if (assertionsEnabled) {
+ final int checkCornerIndex = _nearlyEqual(controlPx, bounds.left)
+ ? (_nearlyEqual(controlPy, bounds.top)
+ ? _Corner.kUpperLeft
+ : _Corner.kLowerLeft)
+ : (_nearlyEqual(controlPy, bounds.top)
+ ? _Corner.kUpperRight
+ : _Corner.kLowerRight);
+ assert(checkCornerIndex == cornerIndex);
+ }
+ radii.add(ui.Radius.elliptical(dx, dy));
+ ++cornerIndex;
+ } else {
+ assert((verb == SPath.kLineVerb &&
+ ((pts[2] - pts[0]) == 0 || (pts[3] - pts[1]) == 0)) ||
+ verb == SPath.kCloseVerb);
+ }
+ }
+ return ui.RRect.fromRectAndCorners(bounds,
+ topLeft: radii[_Corner.kUpperLeft],
+ topRight: radii[_Corner.kUpperRight],
+ bottomRight: radii[_Corner.kLowerRight],
+ bottomLeft: radii[_Corner.kLowerLeft]);
+ }
+
bool operator ==(Object other) {
if (identical(this, other)) {
return true;
@@ -330,7 +394,7 @@ class PathRef {
}
fSegmentMask = source.fSegmentMask;
fIsOval = source.fIsOval;
- rrectRepresentation = source.rrectRepresentation;
+ fIsRRect = source.fIsRRect;
fIsRect = source.fIsRect;
fRRectOrOvalIsCCW = source.fRRectOrOvalIsCCW;
fRRectOrOvalStartIdx = source.fRRectOrOvalStartIdx;
@@ -363,7 +427,7 @@ class PathRef {
}
fSegmentMask = ref.fSegmentMask;
fIsOval = ref.fIsOval;
- rrectRepresentation = ref.rrectRepresentation;
+ fIsRRect = ref.fIsRRect;
fIsRect = ref.fIsRect;
fRRectOrOvalIsCCW = ref.fRRectOrOvalIsCCW;
fRRectOrOvalStartIdx = ref.fRRectOrOvalStartIdx;
@@ -714,7 +778,7 @@ class PathRef {
/// points are added.
void startEdit() {
fIsOval = false;
- rrectRepresentation = null;
+ fIsRRect = false;
fIsRect = false;
cachedBounds = null;
fBoundsIsDirty = true;
@@ -727,11 +791,7 @@ class PathRef {
}
void setIsRRect(bool isRRect, bool isCCW, int start, ui.RRect rrect) {
- if (isRRect) {
- rrectRepresentation = rrect;
- } else {
- rrectRepresentation = null;
- }
+ fIsRRect = isRRect;
fRRectOrOvalIsCCW = isCCW;
fRRectOrOvalStartIdx = start;
}
@@ -753,11 +813,7 @@ class PathRef {
bool fIsFinite = true; // only meaningful if bounds are valid
bool fIsOval = false;
-
- /// If the path is made of a single `RRect`, this field contains the original
- /// `RRect` that was added to the path.
- ui.RRect? rrectRepresentation;
-
+ bool fIsRRect = false;
bool fIsRect = false;
// Both the circle and rrect special cases have a notion of direction and starting point
// The next two variables store that information for either.
@@ -766,9 +822,9 @@ class PathRef {
int fSegmentMask = 0;
bool get isValid {
- if (fIsOval || rrectRepresentation != null) {
+ if (fIsOval || fIsRRect) {
// Currently we don't allow both of these to be set.
- if (fIsOval == (rrectRepresentation != null)) {
+ if (fIsOval == fIsRRect) {
return false;
}
if (fIsOval) {
@@ -782,7 +838,7 @@ class PathRef {
}
}
if (fIsRect) {
- if (fIsOval || (rrectRepresentation != null)) {
+ if (fIsOval || fIsRRect) {
return false;
}
if (fRRectOrOvalStartIdx >= 4) {
@@ -1008,3 +1064,10 @@ class PathRefIterator {
? pathRef._fVerbs[_verbIndex]
: SPath.kDoneVerb;
}
+
+class _Corner {
+ static const int kUpperLeft = 0;
+ static const int kUpperRight = 1;
+ static const int kLowerRight = 2;
+ static const int kLowerLeft = 3;
+}
diff --git a/lib/web_ui/test/engine/surface/path/path_ref_test.dart b/lib/web_ui/test/engine/surface/path/path_ref_test.dart
deleted file mode 100644
index 09c9802f8ae46..0000000000000
--- a/lib/web_ui/test/engine/surface/path/path_ref_test.dart
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright 2013 The Flutter Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// @dart=2.12
-
-import 'package:ui/src/engine.dart';
-import 'package:test/bootstrap/browser.dart';
-import 'package:test/test.dart';
-import 'package:ui/ui.dart';
-
-void main() {
- internalBootstrapBrowserTest(() => testMain);
-}
-
-void testMain() {
- test('PathRef.getRRect with radius', () {
- final SurfacePath path = SurfacePath();
- final RRect rrect = RRect.fromLTRBR(0, 0, 10, 10, Radius.circular(2));
- path.addRRect(rrect);
- expect(path.toRoundedRect(), rrect);
- });
-
- test('PathRef.getRRect with radius larger than rect', () {
- final SurfacePath path = SurfacePath();
- final RRect rrect = RRect.fromLTRBR(0, 0, 10, 10, Radius.circular(20));
- path.addRRect(rrect);
- expect(
- path.toRoundedRect(),
- // Path.addRRect will correct the radius to fit the dimensions, so when
- // extracting the rrect out of the path we don't get the original.
- RRect.fromLTRBR(0, 0, 10, 10, Radius.circular(5)),
- );
- });
-
- test('PathRef.getRRect with zero radius', () {
- final SurfacePath path = SurfacePath();
- final RRect rrect = RRect.fromLTRBR(0, 0, 10, 10, Radius.circular(0));
- path.addRRect(rrect);
- expect(path.toRoundedRect(), isNull);
- expect(path.toRect(), rrect.outerRect);
- });
-
- test('PathRef.getRRect elliptical', () {
- final SurfacePath path = SurfacePath();
- final RRect rrect = RRect.fromLTRBR(0, 0, 10, 10, Radius.elliptical(2, 4));
- path.addRRect(rrect);
- expect(path.toRoundedRect(), rrect);
- });
-
- test('PathRef.getRRect elliptical zero x', () {
- final SurfacePath path = SurfacePath();
- final RRect rrect = RRect.fromLTRBR(0, 0, 10, 10, Radius.elliptical(0, 3));
- path.addRRect(rrect);
- expect(path.toRoundedRect(), isNull);
- expect(path.toRect(), rrect.outerRect);
- });
-
- test('PathRef.getRRect elliptical zero y', () {
- final SurfacePath path = SurfacePath();
- final RRect rrect = RRect.fromLTRBR(0, 0, 10, 10, Radius.elliptical(3, 0));
- path.addRRect(rrect);
- expect(path.toRoundedRect(), isNull);
- expect(path.toRect(), rrect.outerRect);
- });
-
- test('PathRef.getRRect with nearly zero corner', () {
- final SurfacePath path = SurfacePath();
- final RRect original = RRect.fromLTRBR(0, 0, 10, 10, Radius.elliptical(0.00000001, 5));
- path.addRRect(original);
- expect(path.toRoundedRect(), original);
- });
-}