2 * Copyright (C) 2003, 2006 Apple Computer, Inc. All rights reserved.
3 * 2006 Rob Buis <buis@kde.org>
4 * Copyright (C) 2007 Eric Seidel <eric@webkit.org>
5 * Copyright (C) 2013 Google Inc. All rights reserved.
6 * Copyright (C) 2013 Intel Corporation. All rights reserved.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
17 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
21 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
25 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 #include "platform/graphics/Path.h"
34 #include "platform/geometry/FloatPoint.h"
35 #include "platform/geometry/FloatRect.h"
36 #include "platform/graphics/GraphicsContext.h"
37 #include "platform/graphics/skia/SkiaUtils.h"
38 #include "platform/transforms/AffineTransform.h"
39 #include "third_party/skia/include/pathops/SkPathOps.h"
40 #include "wtf/MathExtras.h"
49 Path::Path(const Path& other)
51 m_path = SkPath(other.m_path);
58 Path& Path::operator=(const Path& other)
60 m_path = SkPath(other.m_path);
64 bool Path::operator==(const Path& other) const
66 return m_path == other.m_path;
69 bool Path::contains(const FloatPoint& point, WindRule rule) const
71 return SkPathContainsPoint(m_path, point, rule == RULE_NONZERO ? SkPath::kWinding_FillType : SkPath::kEvenOdd_FillType);
74 bool Path::strokeContains(const FloatPoint& point, const StrokeData& strokeData) const
77 strokeData.setupPaint(&paint);
79 paint.getFillPath(m_path, &strokePath);
81 return SkPathContainsPoint(strokePath, point, SkPath::kWinding_FillType);
84 FloatRect Path::boundingRect() const
86 return m_path.getBounds();
89 FloatRect Path::strokeBoundingRect(const StrokeData& strokeData) const
92 strokeData.setupPaint(&paint);
94 paint.getFillPath(m_path, &boundingPath);
96 return boundingPath.getBounds();
99 static FloatPoint* convertPathPoints(FloatPoint dst[], const SkPoint src[], int count)
101 for (int i = 0; i < count; i++) {
102 dst[i].setX(SkScalarToFloat(src[i].fX));
103 dst[i].setY(SkScalarToFloat(src[i].fY));
108 void Path::apply(void* info, PathApplierFunction function) const
110 SkPath::RawIter iter(m_path);
112 PathElement pathElement;
113 FloatPoint pathPoints[3];
116 switch (iter.next(pts)) {
117 case SkPath::kMove_Verb:
118 pathElement.type = PathElementMoveToPoint;
119 pathElement.points = convertPathPoints(pathPoints, &pts[0], 1);
121 case SkPath::kLine_Verb:
122 pathElement.type = PathElementAddLineToPoint;
123 pathElement.points = convertPathPoints(pathPoints, &pts[1], 1);
125 case SkPath::kQuad_Verb:
126 pathElement.type = PathElementAddQuadCurveToPoint;
127 pathElement.points = convertPathPoints(pathPoints, &pts[1], 2);
129 case SkPath::kCubic_Verb:
130 pathElement.type = PathElementAddCurveToPoint;
131 pathElement.points = convertPathPoints(pathPoints, &pts[1], 3);
133 case SkPath::kClose_Verb:
134 pathElement.type = PathElementCloseSubpath;
135 pathElement.points = convertPathPoints(pathPoints, 0, 0);
137 case SkPath::kDone_Verb:
139 default: // place-holder for kConic_Verb, when that lands from skia
142 function(info, &pathElement);
146 void Path::transform(const AffineTransform& xform)
148 m_path.transform(affineTransformToSkMatrix(xform));
151 float Path::length() const
154 SkPathMeasure measure(m_path, false);
157 length += measure.getLength();
158 } while (measure.nextContour());
160 return SkScalarToFloat(length);
163 FloatPoint Path::pointAtLength(float length, bool& ok) const
167 ok = pointAndNormalAtLength(length, point, normal);
171 float Path::normalAngleAtLength(float length, bool& ok) const
175 ok = pointAndNormalAtLength(length, point, normal);
179 static bool calculatePointAndNormalOnPath(SkPathMeasure& measure, SkScalar length, FloatPoint& point, float& normalAngle, SkScalar* accumulatedLength = 0)
182 SkScalar contourLength = measure.getLength();
183 if (length <= contourLength) {
187 if (measure.getPosTan(length, &position, &tangent)) {
188 normalAngle = rad2deg(SkScalarToFloat(SkScalarATan2(tangent.fY, tangent.fX)));
189 point = FloatPoint(SkScalarToFloat(position.fX), SkScalarToFloat(position.fY));
193 length -= contourLength;
194 if (accumulatedLength)
195 *accumulatedLength += contourLength;
196 } while (measure.nextContour());
200 bool Path::pointAndNormalAtLength(float length, FloatPoint& point, float& normal) const
202 SkPathMeasure measure(m_path, false);
204 if (calculatePointAndNormalOnPath(measure, WebCoreFloatToSkScalar(length), point, normal))
208 point = FloatPoint(0, 0);
212 Path::PositionCalculator::PositionCalculator(const Path& path)
213 : m_path(path.skPath())
214 , m_pathMeasure(path.skPath(), false)
215 , m_accumulatedLength(0)
219 bool Path::PositionCalculator::pointAndNormalAtLength(float length, FloatPoint& point, float& normalAngle)
221 SkScalar skLength = WebCoreFloatToSkScalar(length);
223 if (skLength < m_accumulatedLength) {
224 // Reset path measurer to rewind (and restart from 0).
225 m_pathMeasure.setPath(&m_path, false);
226 m_accumulatedLength = 0;
228 skLength -= m_accumulatedLength;
231 if (calculatePointAndNormalOnPath(m_pathMeasure, skLength, point, normalAngle, &m_accumulatedLength))
236 point = FloatPoint(0, 0);
245 bool Path::isEmpty() const
247 return m_path.isEmpty();
250 bool Path::hasCurrentPoint() const
252 return m_path.getPoints(0, 0);
255 FloatPoint Path::currentPoint() const
257 if (m_path.countPoints() > 0) {
259 m_path.getLastPt(&skResult);
261 result.setX(SkScalarToFloat(skResult.fX));
262 result.setY(SkScalarToFloat(skResult.fY));
266 // FIXME: Why does this return quietNaN? Other ports return 0,0.
267 float quietNaN = std::numeric_limits<float>::quiet_NaN();
268 return FloatPoint(quietNaN, quietNaN);
271 WindRule Path::windRule() const
273 return m_path.getFillType() == SkPath::kEvenOdd_FillType
278 void Path::setWindRule(const WindRule rule)
280 m_path.setFillType(rule == RULE_EVENODD
281 ? SkPath::kEvenOdd_FillType
282 : SkPath::kWinding_FillType);
285 void Path::moveTo(const FloatPoint& point)
287 m_path.moveTo(point);
290 void Path::addLineTo(const FloatPoint& point)
292 m_path.lineTo(point);
295 void Path::addQuadCurveTo(const FloatPoint& cp, const FloatPoint& ep)
297 m_path.quadTo(cp, ep);
300 void Path::addBezierCurveTo(const FloatPoint& p1, const FloatPoint& p2, const FloatPoint& ep)
302 m_path.cubicTo(p1, p2, ep);
305 void Path::addArcTo(const FloatPoint& p1, const FloatPoint& p2, float radius)
307 m_path.arcTo(p1, p2, WebCoreFloatToSkScalar(radius));
310 void Path::closeSubpath()
315 void Path::addEllipse(const FloatPoint& p, float radiusX, float radiusY, float startAngle, float endAngle, bool anticlockwise)
317 ASSERT(ellipseIsRenderable(startAngle, endAngle));
318 ASSERT(startAngle >= 0 && startAngle < 2 * piFloat);
319 ASSERT((anticlockwise && (startAngle - endAngle) >= 0) || (!anticlockwise && (endAngle - startAngle) >= 0));
321 SkScalar cx = WebCoreFloatToSkScalar(p.x());
322 SkScalar cy = WebCoreFloatToSkScalar(p.y());
323 SkScalar radiusXScalar = WebCoreFloatToSkScalar(radiusX);
324 SkScalar radiusYScalar = WebCoreFloatToSkScalar(radiusY);
327 oval.set(cx - radiusXScalar, cy - radiusYScalar, cx + radiusXScalar, cy + radiusYScalar);
329 float sweep = endAngle - startAngle;
330 SkScalar startDegrees = WebCoreFloatToSkScalar(startAngle * 180 / piFloat);
331 SkScalar sweepDegrees = WebCoreFloatToSkScalar(sweep * 180 / piFloat);
332 SkScalar s360 = SkIntToScalar(360);
334 // We can't use SkPath::addOval(), because addOval() makes new sub-path. addOval() calls moveTo() and close() internally.
336 // Use s180, not s360, because SkPath::arcTo(oval, angle, s360, false) draws nothing.
337 SkScalar s180 = SkIntToScalar(180);
338 if (SkScalarNearlyEqual(sweepDegrees, s360)) {
339 // SkPath::arcTo can't handle the sweepAngle that is equal to or greater than 2Pi.
340 m_path.arcTo(oval, startDegrees, s180, false);
341 m_path.arcTo(oval, startDegrees + s180, s180, false);
344 if (SkScalarNearlyEqual(sweepDegrees, -s360)) {
345 m_path.arcTo(oval, startDegrees, -s180, false);
346 m_path.arcTo(oval, startDegrees - s180, -s180, false);
350 m_path.arcTo(oval, startDegrees, sweepDegrees, false);
353 void Path::addArc(const FloatPoint& p, float radius, float startAngle, float endAngle, bool anticlockwise)
355 addEllipse(p, radius, radius, startAngle, endAngle, anticlockwise);
358 void Path::addRect(const FloatRect& rect)
360 m_path.addRect(rect);
363 void Path::addEllipse(const FloatPoint& p, float radiusX, float radiusY, float rotation, float startAngle, float endAngle, bool anticlockwise)
365 ASSERT(ellipseIsRenderable(startAngle, endAngle));
366 ASSERT(startAngle >= 0 && startAngle < 2 * piFloat);
367 ASSERT((anticlockwise && (startAngle - endAngle) >= 0) || (!anticlockwise && (endAngle - startAngle) >= 0));
370 addEllipse(FloatPoint(p.x(), p.y()), radiusX, radiusY, startAngle, endAngle, anticlockwise);
374 // Add an arc after the relevant transform.
375 AffineTransform ellipseTransform = AffineTransform::translation(p.x(), p.y()).rotate(rad2deg(rotation));
376 ASSERT(ellipseTransform.isInvertible());
377 AffineTransform inverseEllipseTransform = ellipseTransform.inverse();
378 transform(inverseEllipseTransform);
379 addEllipse(FloatPoint::zero(), radiusX, radiusY, startAngle, endAngle, anticlockwise);
380 transform(ellipseTransform);
383 void Path::addEllipse(const FloatRect& rect)
385 m_path.addOval(rect);
388 void Path::addRoundedRect(const RoundedRect& r)
390 addRoundedRect(r.rect(), r.radii().topLeft(), r.radii().topRight(), r.radii().bottomLeft(), r.radii().bottomRight());
393 void Path::addRoundedRect(const FloatRect& rect, const FloatSize& roundingRadii)
398 FloatSize radius(roundingRadii);
399 FloatSize halfSize(rect.width() / 2, rect.height() / 2);
401 // Apply the SVG corner radius constraints, per the rect section of the SVG shapes spec: if
402 // one of rx,ry is negative, then the other corner radius value is used. If both values are
403 // negative then rx = ry = 0. If rx is greater than half of the width of the rectangle
404 // then set rx to half of the width; ry is handled similarly.
406 if (radius.width() < 0)
407 radius.setWidth((radius.height() < 0) ? 0 : radius.height());
409 if (radius.height() < 0)
410 radius.setHeight(radius.width());
412 if (radius.width() > halfSize.width())
413 radius.setWidth(halfSize.width());
415 if (radius.height() > halfSize.height())
416 radius.setHeight(halfSize.height());
418 addPathForRoundedRect(rect, radius, radius, radius, radius);
421 void Path::addRoundedRect(const FloatRect& rect, const FloatSize& topLeftRadius, const FloatSize& topRightRadius, const FloatSize& bottomLeftRadius, const FloatSize& bottomRightRadius)
426 if (rect.width() < topLeftRadius.width() + topRightRadius.width()
427 || rect.width() < bottomLeftRadius.width() + bottomRightRadius.width()
428 || rect.height() < topLeftRadius.height() + bottomLeftRadius.height()
429 || rect.height() < topRightRadius.height() + bottomRightRadius.height()) {
430 // If all the radii cannot be accommodated, return a rect.
435 addPathForRoundedRect(rect, topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius);
438 void Path::addPathForRoundedRect(const FloatRect& rect, const FloatSize& topLeftRadius, const FloatSize& topRightRadius, const FloatSize& bottomLeftRadius, const FloatSize& bottomRightRadius)
440 addBeziersForRoundedRect(rect, topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius);
443 // Approximation of control point positions on a bezier to simulate a quarter of a circle.
444 // This is 1-kappa, where kappa = 4 * (sqrt(2) - 1) / 3
445 static const float gCircleControlPoint = 0.447715f;
447 void Path::addBeziersForRoundedRect(const FloatRect& rect, const FloatSize& topLeftRadius, const FloatSize& topRightRadius, const FloatSize& bottomLeftRadius, const FloatSize& bottomRightRadius)
449 moveTo(FloatPoint(rect.x() + topLeftRadius.width(), rect.y()));
451 addLineTo(FloatPoint(rect.maxX() - topRightRadius.width(), rect.y()));
452 if (topRightRadius.width() > 0 || topRightRadius.height() > 0)
453 addBezierCurveTo(FloatPoint(rect.maxX() - topRightRadius.width() * gCircleControlPoint, rect.y()),
454 FloatPoint(rect.maxX(), rect.y() + topRightRadius.height() * gCircleControlPoint),
455 FloatPoint(rect.maxX(), rect.y() + topRightRadius.height()));
456 addLineTo(FloatPoint(rect.maxX(), rect.maxY() - bottomRightRadius.height()));
457 if (bottomRightRadius.width() > 0 || bottomRightRadius.height() > 0)
458 addBezierCurveTo(FloatPoint(rect.maxX(), rect.maxY() - bottomRightRadius.height() * gCircleControlPoint),
459 FloatPoint(rect.maxX() - bottomRightRadius.width() * gCircleControlPoint, rect.maxY()),
460 FloatPoint(rect.maxX() - bottomRightRadius.width(), rect.maxY()));
461 addLineTo(FloatPoint(rect.x() + bottomLeftRadius.width(), rect.maxY()));
462 if (bottomLeftRadius.width() > 0 || bottomLeftRadius.height() > 0)
463 addBezierCurveTo(FloatPoint(rect.x() + bottomLeftRadius.width() * gCircleControlPoint, rect.maxY()),
464 FloatPoint(rect.x(), rect.maxY() - bottomLeftRadius.height() * gCircleControlPoint),
465 FloatPoint(rect.x(), rect.maxY() - bottomLeftRadius.height()));
466 addLineTo(FloatPoint(rect.x(), rect.y() + topLeftRadius.height()));
467 if (topLeftRadius.width() > 0 || topLeftRadius.height() > 0)
468 addBezierCurveTo(FloatPoint(rect.x(), rect.y() + topLeftRadius.height() * gCircleControlPoint),
469 FloatPoint(rect.x() + topLeftRadius.width() * gCircleControlPoint, rect.y()),
470 FloatPoint(rect.x() + topLeftRadius.width(), rect.y()));
475 void Path::translate(const FloatSize& size)
477 m_path.offset(WebCoreFloatToSkScalar(size.width()), WebCoreFloatToSkScalar(size.height()));
480 bool Path::unionPath(const Path& other)
482 return Op(m_path, other.m_path, kUnion_PathOp, &m_path);
486 bool ellipseIsRenderable(float startAngle, float endAngle)
488 return (std::abs(endAngle - startAngle) < 2 * piFloat)
489 || WebCoreFloatNearlyEqual(std::abs(endAngle - startAngle), 2 * piFloat);