mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
527 lines
18 KiB
C++
527 lines
18 KiB
C++
/*
|
|
* 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 "flutter/sky/engine/platform/graphics/Path.h"
|
|
|
|
#include <math.h>
|
|
#include "flutter/sky/engine/platform/geometry/FloatPoint.h"
|
|
#include "flutter/sky/engine/platform/geometry/FloatRect.h"
|
|
#include "flutter/sky/engine/platform/graphics/GraphicsContext.h"
|
|
#include "flutter/sky/engine/platform/graphics/skia/SkiaUtils.h"
|
|
#include "flutter/sky/engine/platform/transforms/AffineTransform.h"
|
|
#include "flutter/sky/engine/wtf/MathExtras.h"
|
|
#include "third_party/skia/include/pathops/SkPathOps.h"
|
|
|
|
namespace blink {
|
|
|
|
Path::Path() : m_path() {}
|
|
|
|
Path::Path(const Path& other) {
|
|
m_path = SkPath(other.m_path);
|
|
}
|
|
|
|
Path::~Path() {}
|
|
|
|
Path& Path::operator=(const Path& other) {
|
|
m_path = SkPath(other.m_path);
|
|
return *this;
|
|
}
|
|
|
|
bool Path::operator==(const Path& other) const {
|
|
return m_path == other.m_path;
|
|
}
|
|
|
|
bool Path::contains(const FloatPoint& point, WindRule rule) const {
|
|
return SkPathContainsPoint(m_path, point,
|
|
static_cast<SkPath::FillType>(rule));
|
|
}
|
|
|
|
bool Path::strokeContains(const FloatPoint& point,
|
|
const StrokeData& strokeData) const {
|
|
SkPaint paint;
|
|
strokeData.setupPaint(&paint);
|
|
SkPath strokePath;
|
|
paint.getFillPath(m_path, &strokePath);
|
|
|
|
return SkPathContainsPoint(strokePath, point, SkPath::kWinding_FillType);
|
|
}
|
|
|
|
FloatRect Path::boundingRect() const {
|
|
return m_path.getBounds();
|
|
}
|
|
|
|
FloatRect Path::strokeBoundingRect(const StrokeData& strokeData) const {
|
|
SkPaint paint;
|
|
strokeData.setupPaint(&paint);
|
|
SkPath boundingPath;
|
|
paint.getFillPath(m_path, &boundingPath);
|
|
|
|
return boundingPath.getBounds();
|
|
}
|
|
|
|
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(m_path);
|
|
SkPoint pts[4];
|
|
PathElement pathElement;
|
|
FloatPoint pathPoints[3];
|
|
|
|
for (;;) {
|
|
switch (iter.next(pts)) {
|
|
case SkPath::kMove_Verb:
|
|
pathElement.type = PathElementMoveToPoint;
|
|
pathElement.points = convertPathPoints(pathPoints, &pts[0], 1);
|
|
break;
|
|
case SkPath::kLine_Verb:
|
|
pathElement.type = PathElementAddLineToPoint;
|
|
pathElement.points = convertPathPoints(pathPoints, &pts[1], 1);
|
|
break;
|
|
case SkPath::kQuad_Verb:
|
|
pathElement.type = PathElementAddQuadCurveToPoint;
|
|
pathElement.points = convertPathPoints(pathPoints, &pts[1], 2);
|
|
break;
|
|
case SkPath::kCubic_Verb:
|
|
pathElement.type = PathElementAddCurveToPoint;
|
|
pathElement.points = convertPathPoints(pathPoints, &pts[1], 3);
|
|
break;
|
|
case SkPath::kClose_Verb:
|
|
pathElement.type = PathElementCloseSubpath;
|
|
pathElement.points = convertPathPoints(pathPoints, 0, 0);
|
|
break;
|
|
case SkPath::kDone_Verb:
|
|
return;
|
|
default: // place-holder for kConic_Verb, when that lands from skia
|
|
break;
|
|
}
|
|
function(info, &pathElement);
|
|
}
|
|
}
|
|
|
|
void Path::transform(const AffineTransform& xform) {
|
|
m_path.transform(affineTransformToSkMatrix(xform));
|
|
}
|
|
|
|
float Path::length() const {
|
|
SkScalar length = 0;
|
|
SkPathMeasure measure(m_path, false);
|
|
|
|
do {
|
|
length += measure.getLength();
|
|
} while (measure.nextContour());
|
|
|
|
return SkScalarToFloat(length);
|
|
}
|
|
|
|
FloatPoint Path::pointAtLength(float length, bool& ok) const {
|
|
FloatPoint point;
|
|
float normal;
|
|
ok = pointAndNormalAtLength(length, point, normal);
|
|
return point;
|
|
}
|
|
|
|
float Path::normalAngleAtLength(float length, bool& ok) const {
|
|
FloatPoint point;
|
|
float normal;
|
|
ok = pointAndNormalAtLength(length, point, normal);
|
|
return normal;
|
|
}
|
|
|
|
static bool calculatePointAndNormalOnPath(SkPathMeasure& measure,
|
|
SkScalar length,
|
|
FloatPoint& point,
|
|
float& normalAngle,
|
|
SkScalar* accumulatedLength = 0) {
|
|
do {
|
|
SkScalar contourLength = measure.getLength();
|
|
if (length <= contourLength) {
|
|
SkVector tangent;
|
|
SkPoint position;
|
|
|
|
if (measure.getPosTan(length, &position, &tangent)) {
|
|
normalAngle =
|
|
rad2deg(SkScalarToFloat(SkScalarATan2(tangent.fY, tangent.fX)));
|
|
point = FloatPoint(SkScalarToFloat(position.fX),
|
|
SkScalarToFloat(position.fY));
|
|
return true;
|
|
}
|
|
}
|
|
length -= contourLength;
|
|
if (accumulatedLength)
|
|
*accumulatedLength += contourLength;
|
|
} while (measure.nextContour());
|
|
return false;
|
|
}
|
|
|
|
bool Path::pointAndNormalAtLength(float length,
|
|
FloatPoint& point,
|
|
float& normal) const {
|
|
SkPathMeasure measure(m_path, false);
|
|
|
|
if (calculatePointAndNormalOnPath(measure, WebCoreFloatToSkScalar(length),
|
|
point, normal))
|
|
return true;
|
|
|
|
normal = 0;
|
|
point = FloatPoint(0, 0);
|
|
return false;
|
|
}
|
|
|
|
Path::PositionCalculator::PositionCalculator(const Path& path)
|
|
: m_path(path.skPath()),
|
|
m_pathMeasure(path.skPath(), false),
|
|
m_accumulatedLength(0) {}
|
|
|
|
bool Path::PositionCalculator::pointAndNormalAtLength(float length,
|
|
FloatPoint& point,
|
|
float& normalAngle) {
|
|
SkScalar skLength = WebCoreFloatToSkScalar(length);
|
|
if (skLength >= 0) {
|
|
if (skLength < m_accumulatedLength) {
|
|
// Reset path measurer to rewind (and restart from 0).
|
|
m_pathMeasure.setPath(&m_path, false);
|
|
m_accumulatedLength = 0;
|
|
} else {
|
|
skLength -= m_accumulatedLength;
|
|
}
|
|
|
|
if (calculatePointAndNormalOnPath(m_pathMeasure, skLength, point,
|
|
normalAngle, &m_accumulatedLength))
|
|
return true;
|
|
}
|
|
|
|
normalAngle = 0;
|
|
point = FloatPoint(0, 0);
|
|
return false;
|
|
}
|
|
|
|
void Path::clear() {
|
|
m_path.reset();
|
|
}
|
|
|
|
bool Path::isEmpty() const {
|
|
return m_path.isEmpty();
|
|
}
|
|
|
|
bool Path::hasCurrentPoint() const {
|
|
return m_path.getPoints(0, 0);
|
|
}
|
|
|
|
FloatPoint Path::currentPoint() const {
|
|
if (m_path.countPoints() > 0) {
|
|
SkPoint skResult;
|
|
m_path.getLastPt(&skResult);
|
|
FloatPoint result;
|
|
result.setX(SkScalarToFloat(skResult.fX));
|
|
result.setY(SkScalarToFloat(skResult.fY));
|
|
return result;
|
|
}
|
|
|
|
// FIXME: Why does this return quietNaN? Other ports return 0,0.
|
|
float quietNaN = std::numeric_limits<float>::quiet_NaN();
|
|
return FloatPoint(quietNaN, quietNaN);
|
|
}
|
|
|
|
WindRule Path::windRule() const {
|
|
return m_path.getFillType() == SkPath::kEvenOdd_FillType ? RULE_EVENODD
|
|
: RULE_NONZERO;
|
|
}
|
|
|
|
void Path::setWindRule(const WindRule rule) {
|
|
m_path.setFillType(WebCoreWindRuleToSkFillType(rule));
|
|
}
|
|
|
|
void Path::moveTo(const FloatPoint& point) {
|
|
m_path.moveTo(point.data());
|
|
}
|
|
|
|
void Path::addLineTo(const FloatPoint& point) {
|
|
m_path.lineTo(point.data());
|
|
}
|
|
|
|
void Path::addQuadCurveTo(const FloatPoint& cp, const FloatPoint& ep) {
|
|
m_path.quadTo(cp.data(), ep.data());
|
|
}
|
|
|
|
void Path::addBezierCurveTo(const FloatPoint& p1,
|
|
const FloatPoint& p2,
|
|
const FloatPoint& ep) {
|
|
m_path.cubicTo(p1.data(), p2.data(), ep.data());
|
|
}
|
|
|
|
void Path::addArcTo(const FloatPoint& p1, const FloatPoint& p2, float radius) {
|
|
m_path.arcTo(p1.data(), p2.data(), WebCoreFloatToSkScalar(radius));
|
|
}
|
|
|
|
void Path::closeSubpath() {
|
|
m_path.close();
|
|
}
|
|
|
|
void Path::addEllipse(const FloatPoint& p,
|
|
float radiusX,
|
|
float radiusY,
|
|
float startAngle,
|
|
float endAngle,
|
|
bool anticlockwise) {
|
|
ASSERT(ellipseIsRenderable(startAngle, endAngle));
|
|
ASSERT(startAngle >= 0 && startAngle < twoPiFloat);
|
|
ASSERT((anticlockwise && (startAngle - endAngle) >= 0) ||
|
|
(!anticlockwise && (endAngle - startAngle) >= 0));
|
|
|
|
SkScalar cx = WebCoreFloatToSkScalar(p.x());
|
|
SkScalar cy = WebCoreFloatToSkScalar(p.y());
|
|
SkScalar radiusXScalar = WebCoreFloatToSkScalar(radiusX);
|
|
SkScalar radiusYScalar = WebCoreFloatToSkScalar(radiusY);
|
|
|
|
SkRect oval;
|
|
oval.set(cx - radiusXScalar, cy - radiusYScalar, cx + radiusXScalar,
|
|
cy + radiusYScalar);
|
|
|
|
float sweep = endAngle - startAngle;
|
|
SkScalar startDegrees = WebCoreFloatToSkScalar(startAngle * 180 / piFloat);
|
|
SkScalar sweepDegrees = WebCoreFloatToSkScalar(sweep * 180 / piFloat);
|
|
SkScalar s360 = SkIntToScalar(360);
|
|
|
|
// We can't use SkPath::addOval(), because addOval() makes 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(sweepDegrees, s360)) {
|
|
// SkPath::arcTo can't handle the sweepAngle that is equal to or greater
|
|
// than 2Pi.
|
|
m_path.arcTo(oval, startDegrees, s180, false);
|
|
m_path.arcTo(oval, startDegrees + s180, s180, false);
|
|
return;
|
|
}
|
|
if (SkScalarNearlyEqual(sweepDegrees, -s360)) {
|
|
m_path.arcTo(oval, startDegrees, -s180, false);
|
|
m_path.arcTo(oval, startDegrees - s180, -s180, false);
|
|
return;
|
|
}
|
|
|
|
m_path.arcTo(oval, startDegrees, sweepDegrees, false);
|
|
}
|
|
|
|
void Path::addArc(const FloatPoint& p,
|
|
float radius,
|
|
float startAngle,
|
|
float endAngle,
|
|
bool anticlockwise) {
|
|
addEllipse(p, radius, radius, startAngle, endAngle, anticlockwise);
|
|
}
|
|
|
|
void Path::addRect(const FloatRect& rect) {
|
|
m_path.addRect(rect);
|
|
}
|
|
|
|
void Path::addEllipse(const FloatPoint& p,
|
|
float radiusX,
|
|
float radiusY,
|
|
float rotation,
|
|
float startAngle,
|
|
float endAngle,
|
|
bool anticlockwise) {
|
|
ASSERT(ellipseIsRenderable(startAngle, endAngle));
|
|
ASSERT(startAngle >= 0 && startAngle < twoPiFloat);
|
|
ASSERT((anticlockwise && (startAngle - endAngle) >= 0) ||
|
|
(!anticlockwise && (endAngle - startAngle) >= 0));
|
|
|
|
if (!rotation) {
|
|
addEllipse(FloatPoint(p.x(), p.y()), radiusX, radiusY, startAngle, endAngle,
|
|
anticlockwise);
|
|
return;
|
|
}
|
|
|
|
// Add an arc after the relevant transform.
|
|
AffineTransform ellipseTransform =
|
|
AffineTransform::translation(p.x(), p.y()).rotateRadians(rotation);
|
|
ASSERT(ellipseTransform.isInvertible());
|
|
AffineTransform inverseEllipseTransform = ellipseTransform.inverse();
|
|
transform(inverseEllipseTransform);
|
|
addEllipse(FloatPoint::zero(), radiusX, radiusY, startAngle, endAngle,
|
|
anticlockwise);
|
|
transform(ellipseTransform);
|
|
}
|
|
|
|
void Path::addEllipse(const FloatRect& rect) {
|
|
m_path.addOval(rect);
|
|
}
|
|
|
|
void Path::addRoundedRect(const RoundedRect& r) {
|
|
addRoundedRect(r.rect(), r.radii().topLeft(), r.radii().topRight(),
|
|
r.radii().bottomLeft(), r.radii().bottomRight());
|
|
}
|
|
|
|
void Path::addRoundedRect(const FloatRect& rect,
|
|
const FloatSize& roundingRadii) {
|
|
if (rect.isEmpty())
|
|
return;
|
|
|
|
FloatSize radius(roundingRadii);
|
|
FloatSize halfSize(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() > halfSize.width())
|
|
radius.setWidth(halfSize.width());
|
|
|
|
if (radius.height() > halfSize.height())
|
|
radius.setHeight(halfSize.height());
|
|
|
|
addPathForRoundedRect(rect, radius, radius, radius, radius);
|
|
}
|
|
|
|
void Path::addRoundedRect(const FloatRect& rect,
|
|
const FloatSize& topLeftRadius,
|
|
const FloatSize& topRightRadius,
|
|
const FloatSize& bottomLeftRadius,
|
|
const FloatSize& bottomRightRadius) {
|
|
if (rect.isEmpty())
|
|
return;
|
|
|
|
if (rect.width() < topLeftRadius.width() + topRightRadius.width() ||
|
|
rect.width() < bottomLeftRadius.width() + bottomRightRadius.width() ||
|
|
rect.height() < topLeftRadius.height() + bottomLeftRadius.height() ||
|
|
rect.height() < topRightRadius.height() + bottomRightRadius.height()) {
|
|
// If all the radii cannot be accommodated, return a rect.
|
|
addRect(rect);
|
|
return;
|
|
}
|
|
|
|
addPathForRoundedRect(rect, topLeftRadius, topRightRadius, bottomLeftRadius,
|
|
bottomRightRadius);
|
|
}
|
|
|
|
void Path::addPathForRoundedRect(const FloatRect& rect,
|
|
const FloatSize& topLeftRadius,
|
|
const FloatSize& topRightRadius,
|
|
const FloatSize& bottomLeftRadius,
|
|
const FloatSize& bottomRightRadius) {
|
|
addBeziersForRoundedRect(rect, topLeftRadius, topRightRadius,
|
|
bottomLeftRadius, bottomRightRadius);
|
|
}
|
|
|
|
// Approximation of control point positions on a bezier to simulate a quarter of
|
|
// a circle. This is 1-kappa, where kappa = 4 * (sqrt(2) - 1) / 3
|
|
static const float gCircleControlPoint = 0.447715f;
|
|
|
|
void Path::addBeziersForRoundedRect(const FloatRect& rect,
|
|
const FloatSize& topLeftRadius,
|
|
const FloatSize& topRightRadius,
|
|
const FloatSize& bottomLeftRadius,
|
|
const FloatSize& bottomRightRadius) {
|
|
moveTo(FloatPoint(rect.x() + topLeftRadius.width(), rect.y()));
|
|
|
|
addLineTo(FloatPoint(rect.maxX() - topRightRadius.width(), rect.y()));
|
|
if (topRightRadius.width() > 0 || topRightRadius.height() > 0)
|
|
addBezierCurveTo(
|
|
FloatPoint(rect.maxX() - topRightRadius.width() * gCircleControlPoint,
|
|
rect.y()),
|
|
FloatPoint(rect.maxX(),
|
|
rect.y() + topRightRadius.height() * gCircleControlPoint),
|
|
FloatPoint(rect.maxX(), rect.y() + topRightRadius.height()));
|
|
addLineTo(FloatPoint(rect.maxX(), rect.maxY() - bottomRightRadius.height()));
|
|
if (bottomRightRadius.width() > 0 || bottomRightRadius.height() > 0)
|
|
addBezierCurveTo(
|
|
FloatPoint(rect.maxX(), rect.maxY() - bottomRightRadius.height() *
|
|
gCircleControlPoint),
|
|
FloatPoint(
|
|
rect.maxX() - bottomRightRadius.width() * gCircleControlPoint,
|
|
rect.maxY()),
|
|
FloatPoint(rect.maxX() - bottomRightRadius.width(), rect.maxY()));
|
|
addLineTo(FloatPoint(rect.x() + bottomLeftRadius.width(), rect.maxY()));
|
|
if (bottomLeftRadius.width() > 0 || bottomLeftRadius.height() > 0)
|
|
addBezierCurveTo(
|
|
FloatPoint(rect.x() + bottomLeftRadius.width() * gCircleControlPoint,
|
|
rect.maxY()),
|
|
FloatPoint(rect.x(), rect.maxY() - bottomLeftRadius.height() *
|
|
gCircleControlPoint),
|
|
FloatPoint(rect.x(), rect.maxY() - bottomLeftRadius.height()));
|
|
addLineTo(FloatPoint(rect.x(), rect.y() + topLeftRadius.height()));
|
|
if (topLeftRadius.width() > 0 || topLeftRadius.height() > 0)
|
|
addBezierCurveTo(
|
|
FloatPoint(rect.x(),
|
|
rect.y() + topLeftRadius.height() * gCircleControlPoint),
|
|
FloatPoint(rect.x() + topLeftRadius.width() * gCircleControlPoint,
|
|
rect.y()),
|
|
FloatPoint(rect.x() + topLeftRadius.width(), rect.y()));
|
|
|
|
closeSubpath();
|
|
}
|
|
|
|
void Path::addPath(const Path& src, const AffineTransform& transform) {
|
|
m_path.addPath(src.skPath(), affineTransformToSkMatrix(transform));
|
|
}
|
|
|
|
void Path::translate(const FloatSize& size) {
|
|
m_path.offset(WebCoreFloatToSkScalar(size.width()),
|
|
WebCoreFloatToSkScalar(size.height()));
|
|
}
|
|
|
|
bool Path::subtractPath(const Path& other) {
|
|
return Op(m_path, other.m_path, kDifference_SkPathOp, &m_path);
|
|
}
|
|
|
|
bool Path::intersectPath(const Path& other) {
|
|
return Op(m_path, other.m_path, kIntersect_SkPathOp, &m_path);
|
|
}
|
|
|
|
bool Path::unionPath(const Path& other) {
|
|
return Op(m_path, other.m_path, kUnion_SkPathOp, &m_path);
|
|
}
|
|
|
|
#if ENABLE(ASSERT)
|
|
bool ellipseIsRenderable(float startAngle, float endAngle) {
|
|
return (std::abs(endAngle - startAngle) < twoPiFloat) ||
|
|
WebCoreFloatNearlyEqual(std::abs(endAngle - startAngle), twoPiFloat);
|
|
}
|
|
#endif
|
|
|
|
} // namespace blink
|