blob: 6a67606bfee751059598b4323a2dafc1d56af9d5 [file] [log] [blame]
/*
* Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Apple Inc.
* All rights reserved.
* Copyright (C) 2008, 2010 Nokia Corporation and/or its subsidiary(-ies)
* Copyright (C) 2007 Alp Toker <alp@atoker.com>
* Copyright (C) 2008 Eric Seidel <eric@webkit.org>
* Copyright (C) 2008 Dirk Schulze <krit@webkit.org>
* Copyright (C) 2010 Torch Mobile (Beijing) Co. Ltd. All rights reserved.
* Copyright (C) 2012, 2013 Intel Corporation. All rights reserved.
* Copyright (C) 2012, 2013 Adobe Systems Incorporated. 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 THE COPYRIGHT HOLDER "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 THE COPYRIGHT HOLDER 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/modules/canvas/canvas2d/canvas_path.h"
#include "base/numerics/safe_conversions.h"
#include "third_party/blink/renderer/core/geometry/dom_point.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/geometry/float_rect.h"
#include "third_party/blink/renderer/platform/transforms/affine_transform.h"
#include "third_party/blink/renderer/platform/wtf/math_extras.h"
namespace blink {
// TODO(crbug.com/940846): Consider using double-type without casting and
// DoublePoint & DoubleRect instead of FloatPoint & FloatRect.
void CanvasPath::closePath() {
if (path_.IsEmpty())
return;
path_.CloseSubpath();
}
void CanvasPath::moveTo(double double_x, double double_y) {
float x = base::saturated_cast<float>(double_x);
float y = base::saturated_cast<float>(double_y);
if (!std::isfinite(x) || !std::isfinite(y))
return;
if (!IsTransformInvertible()) {
path_.MoveTo(GetTransform().MapPoint(FloatPoint(x, y)));
return;
}
path_.MoveTo(FloatPoint(x, y));
}
void CanvasPath::lineTo(double double_x, double double_y) {
float x = base::saturated_cast<float>(double_x);
float y = base::saturated_cast<float>(double_y);
if (!std::isfinite(x) || !std::isfinite(y))
return;
FloatPoint p1 = FloatPoint(x, y);
if (!IsTransformInvertible()) {
p1 = GetTransform().MapPoint(p1);
}
if (!path_.HasCurrentPoint())
path_.MoveTo(p1);
path_.AddLineTo(p1);
}
void CanvasPath::quadraticCurveTo(double double_cpx,
double double_cpy,
double double_x,
double double_y) {
float cpx = base::saturated_cast<float>(double_cpx);
float cpy = base::saturated_cast<float>(double_cpy);
float x = base::saturated_cast<float>(double_x);
float y = base::saturated_cast<float>(double_y);
if (!std::isfinite(cpx) || !std::isfinite(cpy) || !std::isfinite(x) ||
!std::isfinite(y))
return;
FloatPoint p1 = FloatPoint(x, y);
FloatPoint cp = FloatPoint(cpx, cpy);
if (!IsTransformInvertible()) {
p1 = GetTransform().MapPoint(p1);
cp = GetTransform().MapPoint(cp);
}
if (!path_.HasCurrentPoint())
path_.MoveTo(FloatPoint(cpx, cpy));
path_.AddQuadCurveTo(cp, p1);
}
void CanvasPath::bezierCurveTo(double double_cp1x,
double double_cp1y,
double double_cp2x,
double double_cp2y,
double double_x,
double double_y) {
float cp1x = base::saturated_cast<float>(double_cp1x);
float cp1y = base::saturated_cast<float>(double_cp1y);
float cp2x = base::saturated_cast<float>(double_cp2x);
float cp2y = base::saturated_cast<float>(double_cp2y);
float x = base::saturated_cast<float>(double_x);
float y = base::saturated_cast<float>(double_y);
if (!std::isfinite(cp1x) || !std::isfinite(cp1y) || !std::isfinite(cp2x) ||
!std::isfinite(cp2y) || !std::isfinite(x) || !std::isfinite(y))
return;
FloatPoint p1 = FloatPoint(x, y);
FloatPoint cp1 = FloatPoint(cp1x, cp1y);
FloatPoint cp2 = FloatPoint(cp2x, cp2y);
if (!IsTransformInvertible()) {
p1 = GetTransform().MapPoint(p1);
cp1 = GetTransform().MapPoint(cp1);
cp2 = GetTransform().MapPoint(cp2);
}
if (!path_.HasCurrentPoint())
path_.MoveTo(FloatPoint(cp1x, cp1y));
path_.AddBezierCurveTo(cp1, cp2, p1);
}
void CanvasPath::arcTo(double double_x1,
double double_y1,
double double_x2,
double double_y2,
double double_r,
ExceptionState& exception_state) {
float x1 = base::saturated_cast<float>(double_x1);
float y1 = base::saturated_cast<float>(double_y1);
float x2 = base::saturated_cast<float>(double_x2);
float y2 = base::saturated_cast<float>(double_y2);
float r = base::saturated_cast<float>(double_r);
if (!std::isfinite(x1) || !std::isfinite(y1) || !std::isfinite(x2) ||
!std::isfinite(y2) || !std::isfinite(r))
return;
if (r < 0) {
exception_state.ThrowDOMException(
DOMExceptionCode::kIndexSizeError,
"The radius provided (" + String::Number(r) + ") is negative.");
return;
}
FloatPoint p1 = FloatPoint(x1, y1);
FloatPoint p2 = FloatPoint(x2, y2);
if (!IsTransformInvertible()) {
p1 = GetTransform().MapPoint(p1);
p2 = GetTransform().MapPoint(p2);
}
if (!path_.HasCurrentPoint())
path_.MoveTo(p1);
else if (p1 == path_.CurrentPoint() || p1 == p2 || !r)
lineTo(x1, y1);
else
path_.AddArcTo(p1, p2, r);
}
namespace {
float AdjustEndAngle(float start_angle, float end_angle, bool anticlockwise) {
float new_end_angle = end_angle;
/* http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-arc
* If the anticlockwise argument is false and endAngle-startAngle is equal
* to or greater than 2pi, or,
* if the anticlockwise argument is true and startAngle-endAngle is equal to
* or greater than 2pi,
* then the arc is the whole circumference of this ellipse, and the point at
* startAngle along this circle's circumference, measured in radians clockwise
* from the ellipse's semi-major axis, acts as both the start point and the
* end point.
*/
if (!anticlockwise && end_angle - start_angle >= kTwoPiFloat) {
new_end_angle = start_angle + kTwoPiFloat;
} else if (anticlockwise && start_angle - end_angle >= kTwoPiFloat) {
new_end_angle = start_angle - kTwoPiFloat;
/*
* Otherwise, the arc is the path along the circumference of this ellipse
* from the start point to the end point, going anti-clockwise if the
* anticlockwise argument is true, and clockwise otherwise.
* Since the points are on the ellipse, as opposed to being simply angles
* from zero, the arc can never cover an angle greater than 2pi radians.
*/
/* NOTE: When startAngle = 0, endAngle = 2Pi and anticlockwise = true, the
* spec does not indicate clearly.
* We draw the entire circle, because some web sites use arc(x, y, r, 0,
* 2*Math.PI, true) to draw circle.
* We preserve backward-compatibility.
*/
} else if (!anticlockwise && start_angle > end_angle) {
new_end_angle = start_angle +
(kTwoPiFloat - fmodf(start_angle - end_angle, kTwoPiFloat));
} else if (anticlockwise && start_angle < end_angle) {
new_end_angle = start_angle -
(kTwoPiFloat - fmodf(end_angle - start_angle, kTwoPiFloat));
}
DCHECK(EllipseIsRenderable(start_angle, new_end_angle));
DCHECK((anticlockwise && (start_angle >= new_end_angle)) ||
(!anticlockwise && (new_end_angle >= start_angle)));
return new_end_angle;
}
inline void LineToFloatPoint(CanvasPath* path, const FloatPoint& p) {
path->lineTo(p.X(), p.Y());
}
inline FloatPoint GetPointOnEllipse(float radius_x,
float radius_y,
float theta) {
return FloatPoint(radius_x * cosf(theta), radius_y * sinf(theta));
}
void CanonicalizeAngle(float* start_angle, float* end_angle) {
// Make 0 <= startAngle < 2*PI
float new_start_angle = fmodf(*start_angle, kTwoPiFloat);
if (new_start_angle < 0) {
new_start_angle += kTwoPiFloat;
// Check for possible catastrophic cancellation in cases where
// newStartAngle was a tiny negative number (c.f. crbug.com/503422)
if (new_start_angle >= kTwoPiFloat)
new_start_angle -= kTwoPiFloat;
}
float delta = new_start_angle - *start_angle;
*start_angle = new_start_angle;
*end_angle = *end_angle + delta;
DCHECK_GE(new_start_angle, 0);
DCHECK_LT(new_start_angle, kTwoPiFloat);
}
/*
* degenerateEllipse() handles a degenerated ellipse using several lines.
*
* Let's see a following example: line to ellipse to line.
* _--^\
* ( )
* -----( )
* )
* /--------
*
* If radiusX becomes zero, the ellipse of the example is degenerated.
* _
* // P
* //
* -----//
* /
* /--------
*
* To draw the above example, need to get P that is a local maximum point.
* Angles for P are 0.5Pi and 1.5Pi in the ellipse coordinates.
*
* If radiusY becomes zero, the result is as follows.
* -----__
* --_
* ----------
* ``P
* Angles for P are 0 and Pi in the ellipse coordinates.
*
* To handle both cases, degenerateEllipse() lines to start angle, local maximum
* points(every 0.5Pi), and end angle.
* NOTE: Before ellipse() calls this function, adjustEndAngle() is called, so
* endAngle - startAngle must be equal to or less than 2Pi.
*/
void DegenerateEllipse(CanvasPath* path,
float x,
float y,
float radius_x,
float radius_y,
float rotation,
float start_angle,
float end_angle,
bool anticlockwise) {
DCHECK(EllipseIsRenderable(start_angle, end_angle));
DCHECK_GE(start_angle, 0);
DCHECK_LT(start_angle, kTwoPiFloat);
DCHECK((anticlockwise && (start_angle - end_angle) >= 0) ||
(!anticlockwise && (end_angle - start_angle) >= 0));
FloatPoint center(x, y);
AffineTransform rotation_matrix;
rotation_matrix.RotateRadians(rotation);
// First, if the object's path has any subpaths, then the method must add a
// straight line from the last point in the subpath to the start point of the
// arc.
LineToFloatPoint(path, center + rotation_matrix.MapPoint(GetPointOnEllipse(
radius_x, radius_y, start_angle)));
if ((!radius_x && !radius_y) || start_angle == end_angle)
return;
if (!anticlockwise) {
// start_angle - fmodf(start_angle, kPiOverTwoFloat) + kPiOverTwoFloat is
// the one of (0, 0.5Pi, Pi, 1.5Pi, 2Pi) that is the closest to start_angle
// on the clockwise direction.
for (float angle = start_angle - fmodf(start_angle, kPiOverTwoFloat) +
kPiOverTwoFloat;
angle < end_angle; angle += kPiOverTwoFloat) {
LineToFloatPoint(
path, center + rotation_matrix.MapPoint(
GetPointOnEllipse(radius_x, radius_y, angle)));
}
} else {
for (float angle = start_angle - fmodf(start_angle, kPiOverTwoFloat);
angle > end_angle; angle -= kPiOverTwoFloat) {
LineToFloatPoint(
path, center + rotation_matrix.MapPoint(
GetPointOnEllipse(radius_x, radius_y, angle)));
}
}
LineToFloatPoint(path, center + rotation_matrix.MapPoint(GetPointOnEllipse(
radius_x, radius_y, end_angle)));
}
} // namespace
void CanvasPath::arc(double double_x,
double double_y,
double double_radius,
double double_start_angle,
double double_end_angle,
bool anticlockwise,
ExceptionState& exception_state) {
float x = base::saturated_cast<float>(double_x);
float y = base::saturated_cast<float>(double_y);
float radius = base::saturated_cast<float>(double_radius);
float start_angle = base::saturated_cast<float>(double_start_angle);
float end_angle = base::saturated_cast<float>(double_end_angle);
if (!std::isfinite(x) || !std::isfinite(y) || !std::isfinite(radius) ||
!std::isfinite(start_angle) || !std::isfinite(end_angle))
return;
if (radius < 0) {
exception_state.ThrowDOMException(
DOMExceptionCode::kIndexSizeError,
"The radius provided (" + String::Number(radius) + ") is negative.");
return;
}
if (!IsTransformInvertible())
return;
if (!radius || start_angle == end_angle) {
// The arc is empty but we still need to draw the connecting line.
lineTo(x + radius * cosf(start_angle), y + radius * sinf(start_angle));
return;
}
CanonicalizeAngle(&start_angle, &end_angle);
path_.AddArc(FloatPoint(x, y), radius, start_angle,
AdjustEndAngle(start_angle, end_angle, anticlockwise));
}
void CanvasPath::ellipse(double double_x,
double double_y,
double double_radius_x,
double double_radius_y,
double double_rotation,
double double_start_angle,
double double_end_angle,
bool anticlockwise,
ExceptionState& exception_state) {
float x = base::saturated_cast<float>(double_x);
float y = base::saturated_cast<float>(double_y);
float radius_x = base::saturated_cast<float>(double_radius_x);
float radius_y = base::saturated_cast<float>(double_radius_y);
float rotation = base::saturated_cast<float>(double_rotation);
float start_angle = base::saturated_cast<float>(double_start_angle);
float end_angle = base::saturated_cast<float>(double_end_angle);
if (!std::isfinite(x) || !std::isfinite(y) || !std::isfinite(radius_x) ||
!std::isfinite(radius_y) || !std::isfinite(rotation) ||
!std::isfinite(start_angle) || !std::isfinite(end_angle))
return;
if (radius_x < 0) {
exception_state.ThrowDOMException(DOMExceptionCode::kIndexSizeError,
"The major-axis radius provided (" +
String::Number(radius_x) +
") is negative.");
return;
}
if (radius_y < 0) {
exception_state.ThrowDOMException(DOMExceptionCode::kIndexSizeError,
"The minor-axis radius provided (" +
String::Number(radius_y) +
") is negative.");
return;
}
if (!IsTransformInvertible())
return;
CanonicalizeAngle(&start_angle, &end_angle);
float adjusted_end_angle =
AdjustEndAngle(start_angle, end_angle, anticlockwise);
if (!radius_x || !radius_y || start_angle == adjusted_end_angle) {
// The ellipse is empty but we still need to draw the connecting line to
// start point.
DegenerateEllipse(this, x, y, radius_x, radius_y, rotation, start_angle,
adjusted_end_angle, anticlockwise);
return;
}
path_.AddEllipse(FloatPoint(x, y), radius_x, radius_y, rotation, start_angle,
adjusted_end_angle);
}
void CanvasPath::rect(double double_x,
double double_y,
double double_width,
double double_height) {
float x = base::saturated_cast<float>(double_x);
float y = base::saturated_cast<float>(double_y);
float width = base::saturated_cast<float>(double_width);
float height = base::saturated_cast<float>(double_height);
if (!IsTransformInvertible())
return;
if (!std::isfinite(x) || !std::isfinite(y) || !std::isfinite(width) ||
!std::isfinite(height))
return;
path_.AddRect(FloatRect(x, y, width, height));
}
void CanvasPath::roundRect(double double_x,
double double_y,
double double_width,
double double_height,
const HeapVector<DoubleOrDOMPoint, 0> radii,
ExceptionState& exception_state) {
const int num_radii = radii.size();
if (num_radii < 1 || num_radii > 4) {
exception_state.ThrowDOMException(
DOMExceptionCode::kIndexSizeError,
String::Number(num_radii) +
" radii provided. Between one and four radii are necessary.");
}
float x = base::saturated_cast<float>(double_x);
float y = base::saturated_cast<float>(double_y);
float width = base::saturated_cast<float>(double_width);
float height = base::saturated_cast<float>(double_height);
if (!IsTransformInvertible())
return;
if (!std::isfinite(x) || !std::isfinite(y) || !std::isfinite(width) ||
!std::isfinite(height))
return;
FloatRect rect = FloatRect(x, y, width, height);
FloatSize r[num_radii];
for (int i = 0; i < num_radii; ++i) {
if (radii[i].IsDouble()) {
float a = base::saturated_cast<float>(radii[i].GetAsDouble());
r[i] = FloatSize(a, a);
} else { // This radius is a DOMPoint
DOMPoint* p = radii[i].GetAsDOMPoint();
r[i] = FloatSize(base::saturated_cast<float>(p->x()),
base::saturated_cast<float>(p->y()));
}
}
// Order of arguments here is so that this function behaves the same as the
// CSS border-radius property
switch (num_radii) {
case 1:
path_.AddRoundedRect(rect, r[0]);
break;
case 2:
path_.AddRoundedRect(rect, r[0], r[1], r[1], r[0]);
break;
case 3:
path_.AddRoundedRect(rect, r[0], r[1], r[1], r[2]);
break;
case 4:
path_.AddRoundedRect(rect, r[0], r[1], r[3], r[2]);
}
}
} // namespace blink