/* * Copyright (C) 2003, 2006 Apple Computer, Inc. All rights reserved. * 2006 Rob Buis * Copyright (C) 2007 Eric Seidel * Copyright (C) 2013 Google Inc. All rights reserved. * Copyright (C) 2013 Intel Corporation. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "third_party/blink/renderer/platform/graphics/path.h" #include #include "third_party/blink/renderer/platform/geometry/float_point.h" #include "third_party/blink/renderer/platform/geometry/float_rect.h" #include "third_party/blink/renderer/platform/graphics/graphics_context.h" #include "third_party/blink/renderer/platform/graphics/skia/skia_utils.h" #include "third_party/blink/renderer/platform/transforms/affine_transform.h" #include "third_party/blink/renderer/platform/wtf/math_extras.h" #include "third_party/skia/include/pathops/SkPathOps.h" namespace blink { Path::Path() : path_() {} Path::Path(const Path& other) : path_(other.path_) {} Path::Path(const SkPath& other) : path_(other) {} Path::~Path() = default; Path& Path::operator=(const Path& other) { path_ = other.path_; return *this; } Path& Path::operator=(const SkPath& other) { path_ = other; return *this; } bool Path::operator==(const Path& other) const { return path_ == other.path_; } bool Path::Contains(const FloatPoint& point) const { if (!std::isfinite(point.X()) || !std::isfinite(point.Y())) return false; return path_.contains(SkScalar(point.X()), SkScalar(point.Y())); } bool Path::Contains(const FloatPoint& point, WindRule rule) const { if (!std::isfinite(point.X()) || !std::isfinite(point.Y())) return false; SkScalar x = point.X(); SkScalar y = point.Y(); SkPathFillType fill_type = WebCoreWindRuleToSkFillType(rule); if (path_.getFillType() != fill_type) { SkPath tmp(path_); tmp.setFillType(fill_type); return tmp.contains(x, y); } return path_.contains(x, y); } SkPath Path::StrokePath(const StrokeData& stroke_data, const AffineTransform& transform) const { float stroke_precision = clampTo( sqrt(std::max(transform.XScaleSquared(), transform.YScaleSquared()))); return StrokePath(stroke_data, stroke_precision); } SkPath Path::StrokePath(const StrokeData& stroke_data, float stroke_precision) const { PaintFlags flags; stroke_data.SetupPaint(&flags); SkPath stroke_path; flags.getFillPath(path_, &stroke_path, nullptr, stroke_precision); return stroke_path; } bool Path::StrokeContains(const FloatPoint& point, const StrokeData& stroke_data, const AffineTransform& transform) const { if (!std::isfinite(point.X()) || !std::isfinite(point.Y())) return false; return StrokePath(stroke_data, transform) .contains(SkScalar(point.X()), SkScalar(point.Y())); } FloatRect Path::TightBoundingRect() const { return path_.computeTightBounds(); } FloatRect Path::BoundingRect() const { return path_.getBounds(); } FloatRect Path::StrokeBoundingRect(const StrokeData& stroke_data) const { // Skia stroke resolution scale for reduced-precision requirements. constexpr float kStrokePrecision = 0.3f; return StrokePath(stroke_data, kStrokePrecision).computeTightBounds(); } static FloatPoint* ConvertPathPoints(FloatPoint dst[], const SkPoint src[], int count) { for (int i = 0; i < count; i++) { dst[i].SetX(SkScalarToFloat(src[i].fX)); dst[i].SetY(SkScalarToFloat(src[i].fY)); } return dst; } void Path::Apply(void* info, PathApplierFunction function) const { SkPath::RawIter iter(path_); SkPoint pts[4]; PathElement path_element; FloatPoint path_points[3]; for (;;) { switch (iter.next(pts)) { case SkPath::kMove_Verb: path_element.type = kPathElementMoveToPoint; path_element.points = ConvertPathPoints(path_points, &pts[0], 1); break; case SkPath::kLine_Verb: path_element.type = kPathElementAddLineToPoint; path_element.points = ConvertPathPoints(path_points, &pts[1], 1); break; case SkPath::kQuad_Verb: path_element.type = kPathElementAddQuadCurveToPoint; path_element.points = ConvertPathPoints(path_points, &pts[1], 2); break; case SkPath::kCubic_Verb: path_element.type = kPathElementAddCurveToPoint; path_element.points = ConvertPathPoints(path_points, &pts[1], 3); break; case SkPath::kConic_Verb: { // Approximate with quads. Use two for now, increase if more precision // is needed. const int kPow2 = 1; const unsigned kQuadCount = 1 << kPow2; SkPoint quads[1 + 2 * kQuadCount]; SkPath::ConvertConicToQuads(pts[0], pts[1], pts[2], iter.conicWeight(), quads, kPow2); path_element.type = kPathElementAddQuadCurveToPoint; for (unsigned i = 0; i < kQuadCount; ++i) { path_element.points = ConvertPathPoints(path_points, &quads[1 + 2 * i], 2); function(info, &path_element); } continue; } case SkPath::kClose_Verb: path_element.type = kPathElementCloseSubpath; path_element.points = ConvertPathPoints(path_points, nullptr, 0); break; case SkPath::kDone_Verb: return; } function(info, &path_element); } } void Path::Transform(const AffineTransform& xform) { path_.transform(AffineTransformToSkMatrix(xform)); } void Path::Transform(const TransformationMatrix& transformation_matrix) { path_.transform(TransformationMatrixToSkMatrix(transformation_matrix)); } float Path::length() const { SkScalar length = 0; SkPathMeasure measure(path_, false); do { length += measure.getLength(); } while (measure.nextContour()); return SkScalarToFloat(length); } FloatPoint Path::PointAtLength(float length) const { return PointAndNormalAtLength(length).point; } static base::Optional CalculatePointAndNormalOnPath( SkPathMeasure& measure, SkScalar& contour_start, SkScalar length) { do { SkScalar contour_end = contour_start + measure.getLength(); if (length <= contour_end) { SkVector tangent; SkPoint position; SkScalar pos_in_contour = length - contour_start; if (measure.getPosTan(pos_in_contour, &position, &tangent)) { PointAndTangent result; result.point = FloatPoint(position); result.tangent_in_degrees = rad2deg(SkScalarToFloat(SkScalarATan2(tangent.fY, tangent.fX))); return result; } } contour_start = contour_end; } while (measure.nextContour()); return base::nullopt; } PointAndTangent Path::PointAndNormalAtLength(float length) const { SkPathMeasure measure(path_, false); SkScalar start = 0; if (base::Optional result = CalculatePointAndNormalOnPath( measure, start, WebCoreFloatToSkScalar(length))) return *result; return {FloatPoint(path_.getPoint(0)), 0}; } Path::PositionCalculator::PositionCalculator(const Path& path) : path_(path.GetSkPath()), path_measure_(path.GetSkPath(), false), accumulated_length_(0) {} PointAndTangent Path::PositionCalculator::PointAndNormalAtLength(float length) { SkScalar sk_length = WebCoreFloatToSkScalar(length); if (sk_length >= 0) { if (sk_length < accumulated_length_) { // Reset path measurer to rewind (and restart from 0). path_measure_.setPath(&path_, false); accumulated_length_ = 0; } base::Optional result = CalculatePointAndNormalOnPath( path_measure_, accumulated_length_, sk_length); if (result) return *result; } return {FloatPoint(path_.getPoint(0)), 0}; } void Path::Clear() { path_.reset(); } bool Path::IsEmpty() const { return path_.isEmpty(); } bool Path::IsClosed() const { return path_.isLastContourClosed(); } void Path::SetIsVolatile(bool is_volatile) { path_.setIsVolatile(is_volatile); } bool Path::HasCurrentPoint() const { return path_.getPoints(nullptr, 0); } FloatPoint Path::CurrentPoint() const { if (path_.countPoints() > 0) { SkPoint sk_result; path_.getLastPt(&sk_result); FloatPoint result; result.SetX(SkScalarToFloat(sk_result.fX)); result.SetY(SkScalarToFloat(sk_result.fY)); return result; } // FIXME: Why does this return quietNaN? Other ports return 0,0. float quiet_na_n = std::numeric_limits::quiet_NaN(); return FloatPoint(quiet_na_n, quiet_na_n); } void Path::SetWindRule(const WindRule rule) { path_.setFillType(WebCoreWindRuleToSkFillType(rule)); } void Path::MoveTo(const FloatPoint& point) { path_.moveTo(FloatPointToSkPoint(point)); } void Path::AddLineTo(const FloatPoint& point) { path_.lineTo(FloatPointToSkPoint(point)); } void Path::AddQuadCurveTo(const FloatPoint& cp, const FloatPoint& ep) { path_.quadTo(FloatPointToSkPoint(cp), FloatPointToSkPoint(ep)); } void Path::AddBezierCurveTo(const FloatPoint& p1, const FloatPoint& p2, const FloatPoint& ep) { path_.cubicTo(FloatPointToSkPoint(p1), FloatPointToSkPoint(p2), FloatPointToSkPoint(ep)); } void Path::AddArcTo(const FloatPoint& p1, const FloatPoint& p2, float radius) { path_.arcTo(FloatPointToSkPoint(p1), FloatPointToSkPoint(p2), WebCoreFloatToSkScalar(radius)); } void Path::AddArcTo(const FloatPoint& p, const FloatSize& r, float x_rotate, bool large_arc, bool sweep) { path_.arcTo(WebCoreFloatToSkScalar(r.Width()), WebCoreFloatToSkScalar(r.Height()), WebCoreFloatToSkScalar(x_rotate), large_arc ? SkPath::kLarge_ArcSize : SkPath::kSmall_ArcSize, sweep ? SkPathDirection::kCW : SkPathDirection::kCCW, WebCoreFloatToSkScalar(p.X()), WebCoreFloatToSkScalar(p.Y())); } void Path::CloseSubpath() { path_.close(); } void Path::AddEllipse(const FloatPoint& p, float radius_x, float radius_y, float start_angle, float end_angle) { DCHECK(EllipseIsRenderable(start_angle, end_angle)); DCHECK_GE(start_angle, 0); DCHECK_LT(start_angle, kTwoPiFloat); SkScalar cx = WebCoreFloatToSkScalar(p.X()); SkScalar cy = WebCoreFloatToSkScalar(p.Y()); SkScalar radius_x_scalar = WebCoreFloatToSkScalar(radius_x); SkScalar radius_y_scalar = WebCoreFloatToSkScalar(radius_y); SkRect oval; oval.setLTRB(cx - radius_x_scalar, cy - radius_y_scalar, cx + radius_x_scalar, cy + radius_y_scalar); float sweep = end_angle - start_angle; SkScalar start_degrees = WebCoreFloatToSkScalar(start_angle * 180 / kPiFloat); SkScalar sweep_degrees = WebCoreFloatToSkScalar(sweep * 180 / kPiFloat); SkScalar s360 = SkIntToScalar(360); // We can't use SkPath::addOval(), because addOval() makes a new sub-path. // addOval() calls moveTo() and close() internally. // Use s180, not s360, because SkPath::arcTo(oval, angle, s360, false) draws // nothing. SkScalar s180 = SkIntToScalar(180); if (SkScalarNearlyEqual(sweep_degrees, s360)) { // SkPath::arcTo can't handle the sweepAngle that is equal to or greater // than 2Pi. path_.arcTo(oval, start_degrees, s180, false); path_.arcTo(oval, start_degrees + s180, s180, false); return; } if (SkScalarNearlyEqual(sweep_degrees, -s360)) { path_.arcTo(oval, start_degrees, -s180, false); path_.arcTo(oval, start_degrees - s180, -s180, false); return; } path_.arcTo(oval, start_degrees, sweep_degrees, false); } void Path::AddArc(const FloatPoint& p, float radius, float start_angle, float end_angle) { AddEllipse(p, radius, radius, start_angle, end_angle); } void Path::AddRect(const FloatRect& rect) { // Start at upper-left, add clock-wise. path_.addRect(rect, SkPathDirection::kCW, 0); } void Path::AddEllipse(const FloatPoint& p, float radius_x, float radius_y, float rotation, float start_angle, float end_angle) { DCHECK(EllipseIsRenderable(start_angle, end_angle)); DCHECK_GE(start_angle, 0); DCHECK_LT(start_angle, kTwoPiFloat); if (!rotation) { AddEllipse(FloatPoint(p.X(), p.Y()), radius_x, radius_y, start_angle, end_angle); return; } // Add an arc after the relevant transform. AffineTransform ellipse_transform = AffineTransform::Translation(p.X(), p.Y()).RotateRadians(rotation); DCHECK(ellipse_transform.IsInvertible()); AffineTransform inverse_ellipse_transform = ellipse_transform.Inverse(); Transform(inverse_ellipse_transform); AddEllipse(FloatPoint::Zero(), radius_x, radius_y, start_angle, end_angle); Transform(ellipse_transform); } void Path::AddEllipse(const FloatRect& rect) { // Start at 3 o'clock, add clock-wise. path_.addOval(rect, SkPathDirection::kCW, 1); } void Path::AddRoundedRect(const FloatRoundedRect& r) { AddRoundedRect(r.Rect(), r.GetRadii().TopLeft(), r.GetRadii().TopRight(), r.GetRadii().BottomLeft(), r.GetRadii().BottomRight()); } void Path::AddRoundedRect(const FloatRect& rect, const FloatSize& rounding_radii) { if (rect.IsEmpty()) return; FloatSize radius(rounding_radii); FloatSize half_size(rect.Width() / 2, rect.Height() / 2); // Apply the SVG corner radius constraints, per the rect section of the SVG // shapes spec: if one of rx,ry is negative, then the other corner radius // value is used. If both values are negative then rx = ry = 0. If rx is // greater than half of the width of the rectangle then set rx to half of the // width; ry is handled similarly. if (radius.Width() < 0) radius.SetWidth((radius.Height() < 0) ? 0 : radius.Height()); if (radius.Height() < 0) radius.SetHeight(radius.Width()); if (radius.Width() > half_size.Width()) radius.SetWidth(half_size.Width()); if (radius.Height() > half_size.Height()) radius.SetHeight(half_size.Height()); AddPathForRoundedRect(rect, radius, radius, radius, radius); } void Path::AddRoundedRect(const FloatRect& rect, const FloatSize& top_left_radius, const FloatSize& top_right_radius, const FloatSize& bottom_left_radius, const FloatSize& bottom_right_radius) { if (rect.IsEmpty()) return; if (rect.Width() < top_left_radius.Width() + top_right_radius.Width() || rect.Width() < bottom_left_radius.Width() + bottom_right_radius.Width() || rect.Height() < top_left_radius.Height() + bottom_left_radius.Height() || rect.Height() < top_right_radius.Height() + bottom_right_radius.Height()) { // If all the radii cannot be accommodated, return a rect. // FIXME: Is this an error scenario, given that it appears the code in // FloatRoundedRect::constrainRadii() should be always called first? Should // we assert that this code is not reached? This fallback is very bad, since // it means that radii that are just barely too big due to rounding or // snapping will get completely ignored. AddRect(rect); return; } AddPathForRoundedRect(rect, top_left_radius, top_right_radius, bottom_left_radius, bottom_right_radius); } void Path::AddPathForRoundedRect(const FloatRect& rect, const FloatSize& top_left_radius, const FloatSize& top_right_radius, const FloatSize& bottom_left_radius, const FloatSize& bottom_right_radius) { // Start at upper-left (after corner radii), add clock-wise. path_.addRRect(FloatRoundedRect(rect, top_left_radius, top_right_radius, bottom_left_radius, bottom_right_radius), SkPathDirection::kCW, 0); } void Path::AddPath(const Path& src, const AffineTransform& transform) { path_.addPath(src.GetSkPath(), AffineTransformToSkMatrix(transform)); } void Path::Translate(const FloatSize& size) { path_.offset(WebCoreFloatToSkScalar(size.Width()), WebCoreFloatToSkScalar(size.Height())); } bool Path::SubtractPath(const Path& other) { return Op(path_, other.path_, kDifference_SkPathOp, &path_); } bool Path::UnionPath(const Path& other) { return Op(path_, other.path_, kUnion_SkPathOp, &path_); } bool Path::IntersectPath(const Path& other) { return Op(path_, other.path_, kIntersect_SkPathOp, &path_); } bool EllipseIsRenderable(float start_angle, float end_angle) { return (std::abs(end_angle - start_angle) < kTwoPiFloat) || WebCoreFloatNearlyEqual(std::abs(end_angle - start_angle), kTwoPiFloat); } } // namespace blink