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); - }); -}