blob: 5a345b476164a7058c80c560b72701767dec7616 [file] [log] [blame]
/*
* Copyright (C) 2003, 2006 Apple Computer, Inc. All rights reserved.
* 2006 Rob Buis <buis@kde.org>
* Copyright (C) 2007 Eric Seidel <eric@webkit.org>
* 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/geometry/path.h"
#include <math.h>
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <optional>
#include "third_party/blink/renderer/platform/geometry/path_builder.h"
#include "third_party/blink/renderer/platform/geometry/skia_geometry_utils.h"
#include "third_party/blink/renderer/platform/geometry/stroke_data.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/core/SkPathBuilder.h"
#include "third_party/skia/include/pathops/SkPathOps.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/geometry/quad_f.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/geometry/skia_conversions.h"
namespace blink {
namespace {
bool PathQuadIntersection(const SkPath& path, const gfx::QuadF& quad) {
const SkPath quad_path =
SkPathBuilder()
.moveTo(gfx::PointFToSkPoint(ClampNonFiniteToZero(quad.p1())))
.lineTo(gfx::PointFToSkPoint(ClampNonFiniteToZero(quad.p2())))
.lineTo(gfx::PointFToSkPoint(ClampNonFiniteToZero(quad.p3())))
.lineTo(gfx::PointFToSkPoint(ClampNonFiniteToZero(quad.p4())))
.close()
.detach();
const auto intersection = Op(path, quad_path, kIntersect_SkPathOp);
return intersection && !intersection->isEmpty();
}
} // namespace
Path::Path() = default;
Path::Path(const Path& other) = default;
Path::Path(const SkPath& other) : path_(other) {}
Path::~Path() = default;
Path& Path::operator=(const Path&) = default;
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 gfx::PointF& point) const {
if (!std::isfinite(point.x()) || !std::isfinite(point.y())) {
return false;
}
return path_.contains(point.x(), point.y());
}
bool Path::Contains(const gfx::PointF& point, WindRule rule) const {
if (!std::isfinite(point.x()) || !std::isfinite(point.y())) {
return false;
}
const float x = point.x();
const float y = point.y();
const 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);
}
bool Path::Intersects(const gfx::QuadF& quad) const {
return PathQuadIntersection(path_, quad);
}
bool Path::Intersects(const gfx::QuadF& quad, WindRule rule) const {
SkPathFillType fill_type = WebCoreWindRuleToSkFillType(rule);
if (path_.getFillType() != fill_type) {
SkPath tmp(path_);
tmp.setFillType(fill_type);
return PathQuadIntersection(tmp, quad);
}
return PathQuadIntersection(path_, quad);
}
SkPath Path::StrokePath(const StrokeData& stroke_data,
const AffineTransform& transform) const {
float stroke_precision = ClampTo<float>(
sqrt(std::max(transform.XScaleSquared(), transform.YScaleSquared())));
return StrokePath(stroke_data, stroke_precision);
}
SkPath Path::StrokePath(const StrokeData& stroke_data,
float stroke_precision) const {
cc::PaintFlags flags;
stroke_data.SetupPaint(&flags);
SkPath stroke_path;
flags.getFillPath(path_, &stroke_path, nullptr, stroke_precision);
return stroke_path;
}
bool Path::StrokeContains(const gfx::PointF& 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(point.x(), point.y());
}
gfx::RectF Path::TightBoundingRect() const {
return gfx::SkRectToRectF(path_.computeTightBounds());
}
gfx::RectF Path::BoundingRect() const {
return gfx::SkRectToRectF(path_.getBounds());
}
gfx::RectF Path::StrokeBoundingRect(const StrokeData& stroke_data) const {
// Skia stroke resolution scale for reduced-precision requirements.
constexpr float kStrokePrecision = 0.3f;
return gfx::SkRectToRectF(
StrokePath(stroke_data, kStrokePrecision).computeTightBounds());
}
static base::span<gfx::PointF> ConvertPathPoints(
std::array<gfx::PointF, 3>& dst,
base::span<const SkPoint> src) {
for (size_t i = 0; i < src.size(); ++i) {
dst[i] = gfx::SkPointToPointF(src[i]);
}
return base::span(dst).first(src.size());
}
void Path::Apply(void* info, PathApplierFunction function) const {
SkPath::RawIter iter(path_);
std::array<SkPoint, 4> pts;
std::array<gfx::PointF, 3> path_points;
PathElement path_element;
for (;;) {
switch (iter.next(pts.data())) {
case SkPath::kMove_Verb:
path_element.type = kPathElementMoveToPoint;
path_element.points =
ConvertPathPoints(path_points, base::span(pts).first(1u));
break;
case SkPath::kLine_Verb:
path_element.type = kPathElementAddLineToPoint;
path_element.points =
ConvertPathPoints(path_points, base::span(pts).subspan<1, 1>());
break;
case SkPath::kQuad_Verb:
path_element.type = kPathElementAddQuadCurveToPoint;
path_element.points =
ConvertPathPoints(path_points, base::span(pts).subspan<1, 2>());
break;
case SkPath::kCubic_Verb:
path_element.type = kPathElementAddCurveToPoint;
path_element.points =
ConvertPathPoints(path_points, base::span(pts).subspan<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;
std::array<SkPoint, 1 + 2 * kQuadCount> quads;
SkPath::ConvertConicToQuads(pts[0], pts[1], pts[2], iter.conicWeight(),
quads.data(), kPow2);
path_element.type = kPathElementAddQuadCurveToPoint;
for (unsigned i = 0; i < kQuadCount; ++i) {
path_element.points = ConvertPathPoints(
path_points, base::span(quads).subspan(1 + 2 * i, 2u));
function(info, path_element);
}
continue;
}
case SkPath::kClose_Verb:
path_element.type = kPathElementCloseSubpath;
path_element.points = ConvertPathPoints(path_points, {});
break;
case SkPath::kDone_Verb:
return;
}
function(info, path_element);
}
}
float Path::length() const {
float length = 0;
SkPathMeasure measure(path_, false);
do {
length += measure.getLength();
} while (measure.nextContour());
return length;
}
gfx::PointF Path::PointAtLength(float length) const {
return PointAndNormalAtLength(length).point;
}
static std::optional<PointAndTangent> CalculatePointAndNormalOnPath(
SkPathMeasure& measure,
float& contour_start,
float length) {
do {
const float contour_end = contour_start + measure.getLength();
if (length <= contour_end) {
SkVector tangent;
SkPoint position;
const float pos_in_contour = length - contour_start;
if (measure.getPosTan(pos_in_contour, &position, &tangent)) {
PointAndTangent result;
result.point = gfx::SkPointToPointF(position);
result.tangent_in_degrees =
Rad2deg(SkScalarATan2(tangent.fY, tangent.fX));
return result;
}
}
contour_start = contour_end;
} while (measure.nextContour());
return std::nullopt;
}
PointAndTangent Path::PointAndNormalAtLength(float length) const {
SkPathMeasure measure(path_, false);
float start = 0;
if (std::optional<PointAndTangent> result = CalculatePointAndNormalOnPath(
measure, start, ClampNonFiniteToZero(length))) {
return *result;
}
return {gfx::SkPointToPointF(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) {
length = ClampNonFiniteToZero(length);
if (length >= 0) {
if (length < accumulated_length_) {
// Reset path measurer to rewind (and restart from 0).
path_measure_.setPath(&path_, false);
accumulated_length_ = 0;
}
std::optional<PointAndTangent> result = CalculatePointAndNormalOnPath(
path_measure_, accumulated_length_, length);
if (result) {
return *result;
}
}
return {gfx::SkPointToPointF(path_.getPoint(0)), 0};
}
bool Path::IsEmpty() const {
return path_.isEmpty();
}
bool Path::IsClosed() const {
return path_.isLastContourClosed();
}
bool Path::IsLine() const {
return path_.isLine(nullptr);
}
Path Path::MakeRect(const gfx::RectF& rect) {
return PathBuilder().AddRect(rect).Finalize();
}
Path Path::MakeRect(const gfx::PointF& origin,
const gfx::PointF& opposite_point) {
return PathBuilder().AddRect(origin, opposite_point).Finalize();
}
Path Path::MakeContouredRect(const ContouredRect& crect) {
return PathBuilder().AddContouredRect(crect).Finalize();
}
Path Path::MakeRoundedRect(const FloatRoundedRect& rrect) {
return PathBuilder().AddRoundedRect(rrect).Finalize();
}
Path Path::MakeEllipse(const gfx::PointF& center,
float radius_x,
float radius_y) {
return PathBuilder().AddEllipse(center, radius_x, radius_y).Finalize();
}
bool EllipseIsRenderable(float start_angle, float end_angle) {
const float abs_sweep = std::abs(end_angle - start_angle);
return (abs_sweep < kTwoPiFloat) ||
WebCoreFloatNearlyEqual(abs_sweep, kTwoPiFloat);
}
} // namespace blink