| /* |
| * Copyright (C) Research In Motion Limited 2009-2010. All rights reserved. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #include "config.h" |
| #include "Path.h" |
| |
| #include "AffineTransform.h" |
| #include "FloatRect.h" |
| #include "GraphicsContext.h" |
| #include "NotImplemented.h" |
| #include "PainterOpenVG.h" |
| #include "PlatformPathOpenVG.h" |
| #include "StrokeStyleApplier.h" |
| #include "VGUtils.h" |
| |
| #include <openvg.h> |
| #include <wtf/MathExtras.h> |
| #include <wtf/text/WTFString.h> |
| |
| #define WEBKIT_VG_PATH_CAPABILITIES VG_PATH_CAPABILITY_ALL |
| |
| #define FUZZY_COMPARE(number, reference, delta) \ |
| (number >= (reference - delta) && number <= (reference + delta)) |
| |
| namespace WebCore { |
| |
| PlatformPathOpenVG::PlatformPathOpenVG() |
| : SharedResourceOpenVG() |
| { |
| createPath(); |
| } |
| |
| PlatformPathOpenVG::PlatformPathOpenVG(const PlatformPathOpenVG& other) |
| : SharedResourceOpenVG() |
| , m_currentPoint(other.m_currentPoint) |
| , m_subpathStartPoint(other.m_subpathStartPoint) |
| { |
| createPath(); |
| // makeCompatibleContextCurrent() is called by createPath(), so not necessary here. |
| vgAppendPath(m_vgPath, other.m_vgPath); |
| ASSERT_VG_NO_ERROR(); |
| } |
| |
| PlatformPathOpenVG& PlatformPathOpenVG::operator=(const PlatformPathOpenVG& other) |
| { |
| if (&other != this) { |
| clear(); |
| // makeCompatibleContextCurrent() is called by clear(), so not necessary here. |
| vgAppendPath(m_vgPath, other.m_vgPath); |
| ASSERT_VG_NO_ERROR(); |
| } |
| return *this; |
| } |
| |
| PlatformPathOpenVG::~PlatformPathOpenVG() |
| { |
| makeCompatibleContextCurrent(); |
| |
| vgDestroyPath(m_vgPath); |
| ASSERT_VG_NO_ERROR(); |
| } |
| |
| void PlatformPathOpenVG::clear() |
| { |
| makeCompatibleContextCurrent(); |
| |
| vgClearPath(m_vgPath, WEBKIT_VG_PATH_CAPABILITIES); |
| ASSERT_VG_NO_ERROR(); |
| |
| m_subpathStartPoint.setX(0); |
| m_subpathStartPoint.setY(0); |
| m_currentPoint = m_subpathStartPoint; |
| } |
| |
| void PlatformPathOpenVG::createPath() |
| { |
| makeSharedContextCurrent(); |
| |
| m_vgPath = vgCreatePath( |
| VG_PATH_FORMAT_STANDARD, VG_PATH_DATATYPE_F, |
| 1.0 /* scale */, 0.0 /* bias */, |
| 0 /* expected number of segments */, |
| 0 /* expected number of total coordinates */, |
| WEBKIT_VG_PATH_CAPABILITIES); |
| ASSERT_VG_NO_ERROR(); |
| } |
| |
| |
| Path::Path() |
| { |
| m_path = new PlatformPathOpenVG(); |
| } |
| |
| Path::~Path() |
| { |
| delete m_path; |
| } |
| |
| Path::Path(const Path& other) |
| { |
| m_path = new PlatformPathOpenVG(*(other.m_path)); |
| } |
| |
| Path& Path::operator=(const Path& other) |
| { |
| *m_path = *(other.m_path); |
| return *this; |
| } |
| |
| FloatPoint Path::currentPoint() const |
| { |
| // FIXME: is this the way to return the current point of the subpath? |
| return m_currentPoint; |
| } |
| |
| |
| bool Path::contains(const FloatPoint& point, WindRule rule) const |
| { |
| notImplemented(); |
| |
| // OpenVG has no path-contains function, so for now we approximate by |
| // using the bounding rect of the path. |
| return boundingRect().contains(point); |
| } |
| |
| bool Path::strokeContains(StrokeStyleApplier* applier, const FloatPoint& point) const |
| { |
| notImplemented(); |
| |
| // OpenVG has no path-contains function, so for now we approximate by |
| // using the stroke bounding rect of the path. |
| return (const_cast<Path*>(this))->strokeBoundingRect().contains(point); |
| } |
| |
| void Path::translate(const FloatSize& size) |
| { |
| AffineTransform transformation; |
| transformation.translate(size.width(), size.height()); |
| transform(transformation); |
| } |
| |
| FloatRect Path::boundingRect() const |
| { |
| VGfloat minX; |
| VGfloat minY; |
| VGfloat width; |
| VGfloat height; |
| |
| m_path->makeCompatibleContextCurrent(); |
| vgPathBounds(m_path->vgPath(), &minX, &minY, &width, &height); |
| ASSERT_VG_NO_ERROR(); |
| |
| return FloatRect(FloatPoint(minX, minY), FloatSize(width, height)); |
| } |
| |
| FloatRect Path::strokeBoundingRect(StrokeStyleApplier* applier) const |
| { |
| notImplemented(); |
| |
| // vgPathBounds() ignores stroke parameters, and we don't currently have |
| // an approximation that takes stroke parameters into account. |
| return boundingRect(); |
| } |
| |
| void Path::moveTo(const FloatPoint& point) |
| { |
| static const VGubyte pathSegments[] = { VG_MOVE_TO_ABS }; |
| const VGfloat pathData[] = { point.x(), point.y() }; |
| |
| m_path->makeCompatibleContextCurrent(); |
| vgAppendPathData(m_path->vgPath(), 1, pathSegments, pathData); |
| ASSERT_VG_NO_ERROR(); |
| |
| m_path->m_currentPoint = m_path->m_subpathStartPoint = point; |
| } |
| |
| void Path::addLineTo(const FloatPoint& point) |
| { |
| static const VGubyte pathSegments[] = { VG_LINE_TO_ABS }; |
| const VGfloat pathData[] = { point.x(), point.y() }; |
| |
| m_path->makeCompatibleContextCurrent(); |
| vgAppendPathData(m_path->vgPath(), 1, pathSegments, pathData); |
| ASSERT_VG_NO_ERROR(); |
| |
| m_path->m_currentPoint = point; |
| } |
| |
| void Path::addQuadCurveTo(const FloatPoint& controlPoint, const FloatPoint& endPoint) |
| { |
| static const VGubyte pathSegments[] = { VG_QUAD_TO_ABS }; |
| const VGfloat pathData[] = { controlPoint.x(), controlPoint.y(), endPoint.x(), endPoint.y() }; |
| |
| m_path->makeCompatibleContextCurrent(); |
| vgAppendPathData(m_path->vgPath(), 1, pathSegments, pathData); |
| ASSERT_VG_NO_ERROR(); |
| |
| m_path->m_currentPoint = endPoint; |
| } |
| |
| void Path::addBezierCurveTo(const FloatPoint& controlPoint1, const FloatPoint& controlPoint2, const FloatPoint& endPoint) |
| { |
| static const VGubyte pathSegments[] = { VG_CUBIC_TO_ABS }; |
| const VGfloat pathData[] = { controlPoint1.x(), controlPoint1.y(), controlPoint2.x(), controlPoint2.y(), endPoint.x(), endPoint.y() }; |
| |
| m_path->makeCompatibleContextCurrent(); |
| vgAppendPathData(m_path->vgPath(), 1, pathSegments, pathData); |
| ASSERT_VG_NO_ERROR(); |
| |
| m_path->m_currentPoint = endPoint; |
| } |
| |
| void Path::addArcTo(const FloatPoint& point1, const FloatPoint& point2, float radius) |
| { |
| // See http://philip.html5.org/tests/canvas/suite/tests/spec.html#arcto. |
| |
| const FloatPoint& point0 = m_path->m_currentPoint; |
| if (!radius || point0 == point1 || point1 == point2) { |
| addLineTo(point1); |
| return; |
| } |
| |
| FloatSize v01 = point0 - point1; |
| FloatSize v21 = point2 - point1; |
| |
| // sin(A - B) = sin(A) * cos(B) - sin(B) * cos(A) |
| double cross = v01.width() * v21.height() - v01.height() * v21.width(); |
| |
| if (fabs(cross) < 1E-10) { |
| // on one line |
| addLineTo(point1); |
| return; |
| } |
| |
| double d01 = hypot(v01.width(), v01.height()); |
| double d21 = hypot(v21.width(), v21.height()); |
| double angle = (piDouble - fabs(asin(cross / (d01 * d21)))) * 0.5; |
| double span = radius * tan(angle); |
| double rate = span / d01; |
| FloatPoint startPoint = FloatPoint(point1.x() + v01.width() * rate, |
| point1.y() + v01.height() * rate); |
| rate = span / d21; |
| FloatPoint endPoint = FloatPoint(point1.x() + v21.width() * rate, |
| point1.y() + v21.height() * rate); |
| |
| // Fa: large arc flag, makes the difference between SCWARC_TO and LCWARC_TO |
| // respectively SCCWARC_TO and LCCWARC_TO arcs. We always use small |
| // arcs for arcTo(), as the arc is defined as the "shortest arc" of the |
| // circle specified in HTML 5. |
| |
| // Fs: sweep flag, specifying whether the arc is drawn in increasing (true) |
| // or decreasing (0) direction. |
| const bool anticlockwise = cross < 0; |
| |
| // Translate the large arc and sweep flags into an OpenVG segment command. |
| const VGubyte segmentCommand = anticlockwise ? VG_SCCWARC_TO_ABS : VG_SCWARC_TO_ABS; |
| |
| const VGubyte pathSegments[] = { |
| VG_LINE_TO_ABS, |
| segmentCommand |
| }; |
| const VGfloat pathData[] = { |
| startPoint.x(), startPoint.y(), |
| radius, radius, 0, endPoint.x(), endPoint.y() |
| }; |
| |
| m_path->makeCompatibleContextCurrent(); |
| vgAppendPathData(m_path->vgPath(), 2, pathSegments, pathData); |
| ASSERT_VG_NO_ERROR(); |
| |
| m_path->m_currentPoint = endPoint; |
| } |
| |
| void Path::closeSubpath() |
| { |
| static const VGubyte pathSegments[] = { VG_CLOSE_PATH }; |
| // pathData must not be 0, but certain compilers also don't create |
| // zero-size arrays. So let's use a random aligned value (sizeof(VGfloat)), |
| // it won't be accessed anyways as VG_CLOSE_PATH doesn't take coordinates. |
| static const VGfloat* pathData = reinterpret_cast<VGfloat*>(sizeof(VGfloat)); |
| |
| m_path->makeCompatibleContextCurrent(); |
| vgAppendPathData(m_path->vgPath(), 1, pathSegments, pathData); |
| ASSERT_VG_NO_ERROR(); |
| |
| m_path->m_currentPoint = m_path->m_subpathStartPoint; |
| } |
| |
| void Path::addArc(const FloatPoint& center, float radius, float startAngle, float endAngle, bool anticlockwise) |
| { |
| // The OpenVG spec says nothing about inf as radius or start/end angle. |
| // WebKit seems to pass those (e.g. https://bugs.webkit.org/show_bug.cgi?id=16449), |
| // so abort instead of risking undefined behavior. |
| if (!isfinite(radius) || !isfinite(startAngle) || !isfinite(endAngle)) |
| return; |
| |
| // For some reason, the HTML 5 spec defines the angle as going clockwise |
| // from the positive X axis instead of going standard anticlockwise. |
| // So let's make it a proper angle in order to keep sanity. |
| startAngle = fmod((2.0 * piDouble) - startAngle, 2.0 * piDouble); |
| endAngle = fmod((2.0 * piDouble) - endAngle, 2.0 * piDouble); |
| |
| // Make it so that endAngle > startAngle. fmod() above takes care of |
| // keeping the difference below 360 degrees. |
| if (endAngle <= startAngle) |
| endAngle += 2.0 * piDouble; |
| |
| const VGfloat angleDelta = anticlockwise |
| ? (endAngle - startAngle) |
| : (startAngle - endAngle + (2.0 * piDouble)); |
| |
| // OpenVG uses endpoint parameterization while this method receives its |
| // values in center parameterization. It lacks an ellipse rotation |
| // parameter so we use 0 for that, and also the radius is only a single |
| // value which makes for rh == rv. In order to convert from endpoint to |
| // center parameterization, we use the formulas from the OpenVG/SVG specs: |
| |
| // (x,y) = (cos rot, -sin rot; sin rot, -cos rot) * (rh * cos angle, rv * sin angle) + (center.x, center.y) |
| // rot is 0, which simplifies this a bit: |
| // (x,y) = (1, 0; 0, -1) * (rh * cos angle, rv * sin angle) + (center.x, center.y) |
| // = (1 * rh * cos angle + 0 * rv * sin angle, 0 * rh * cos angle + -1 * rv * sin angle) + (center.x, center.y) |
| // = (rh * cos angle, -rv * sin angle) + (center.x, center.y) |
| // (Set angle = {startAngle, endAngle} to retrieve the respective endpoints.) |
| |
| const VGfloat startX = radius * cos(startAngle) + center.x(); |
| const VGfloat startY = -radius * sin(startAngle) + center.y(); |
| const VGfloat endX = radius * cos(endAngle) + center.x(); |
| const VGfloat endY = -radius * sin(endAngle) + center.y(); |
| |
| // Fa: large arc flag, makes the difference between SCWARC_TO and LCWARC_TO |
| // respectively SCCWARC_TO and LCCWARC_TO arcs. |
| const bool largeArc = (angleDelta > piDouble); |
| |
| // Fs: sweep flag, specifying whether the arc is drawn in increasing (true) |
| // or decreasing (0) direction. No need to calculate this value, as it |
| // we already get it passed as a parameter (Fs == !anticlockwise). |
| |
| // Translate the large arc and sweep flags into an OpenVG segment command. |
| // As OpenVG thinks of everything upside down, we need to reverse the |
| // anticlockwise parameter in order to get the specified rotation. |
| const VGubyte segmentCommand = !anticlockwise |
| ? (largeArc ? VG_LCCWARC_TO_ABS : VG_SCCWARC_TO_ABS) |
| : (largeArc ? VG_LCWARC_TO_ABS : VG_SCWARC_TO_ABS); |
| |
| // So now, we've got all the parameters in endpoint parameterization format |
| // as OpenVG requires it. Which means we can just pass it like this. |
| const VGubyte pathSegments[] = { |
| hasCurrentPoint() ? VG_LINE_TO_ABS : VG_MOVE_TO_ABS, |
| segmentCommand |
| }; |
| const VGfloat pathData[] = { |
| startX, startY, |
| radius, radius, 0, endX, endY |
| }; |
| |
| m_path->makeCompatibleContextCurrent(); |
| vgAppendPathData(m_path->vgPath(), 2, pathSegments, pathData); |
| ASSERT_VG_NO_ERROR(); |
| |
| m_path->m_currentPoint.setX(endX); |
| m_path->m_currentPoint.setY(endY); |
| } |
| |
| void Path::addRect(const FloatRect& rect) |
| { |
| static const VGubyte pathSegments[] = { |
| VG_MOVE_TO_ABS, |
| VG_HLINE_TO_REL, |
| VG_VLINE_TO_REL, |
| VG_HLINE_TO_REL, |
| VG_CLOSE_PATH |
| }; |
| const VGfloat pathData[] = { |
| rect.x(), rect.y(), |
| rect.width(), |
| rect.height(), |
| -rect.width() |
| }; |
| |
| m_path->makeCompatibleContextCurrent(); |
| vgAppendPathData(m_path->vgPath(), 5, pathSegments, pathData); |
| ASSERT_VG_NO_ERROR(); |
| |
| m_path->m_currentPoint = m_path->m_subpathStartPoint = rect.location(); |
| } |
| |
| void Path::addEllipse(const FloatRect& rect) |
| { |
| static const VGubyte pathSegments[] = { |
| VG_MOVE_TO_ABS, |
| VG_SCCWARC_TO_REL, |
| VG_SCCWARC_TO_REL, |
| VG_CLOSE_PATH |
| }; |
| const VGfloat pathData[] = { |
| rect.x() + rect.width() / 2.0, rect.y(), |
| rect.width() / 2.0, rect.height() / 2.0, 0, 0, rect.height(), |
| rect.width() / 2.0, rect.height() / 2.0, 0, 0, -rect.height() |
| }; |
| |
| m_path->makeCompatibleContextCurrent(); |
| vgAppendPathData(m_path->vgPath(), 4, pathSegments, pathData); |
| ASSERT_VG_NO_ERROR(); |
| } |
| |
| void Path::clear() |
| { |
| m_path->clear(); |
| } |
| |
| bool Path::isEmpty() const |
| { |
| m_path->makeCompatibleContextCurrent(); |
| return !vgGetParameteri(m_path->vgPath(), VG_PATH_NUM_SEGMENTS); |
| } |
| |
| bool Path::hasCurrentPoint() const |
| { |
| m_path->makeCompatibleContextCurrent(); |
| return vgGetParameteri(m_path->vgPath(), VG_PATH_NUM_SEGMENTS) > 0; |
| } |
| |
| void Path::apply(void* info, PathApplierFunction function) const |
| { |
| // OpenVG provides no means to retrieve path segment information. |
| // This is *very* unfortunate, we might need to store the segments in |
| // memory if we want to implement this function properly. |
| // See http://www.khronos.org/message_boards/viewtopic.php?f=6&t=1887 |
| notImplemented(); |
| } |
| |
| void Path::transform(const AffineTransform& transformation) |
| { |
| PlatformPathOpenVG* dst = new PlatformPathOpenVG(); |
| // dst->makeCompatibleContextCurrent() is called by the platform path |
| // constructor, therefore not necessary to call it again here. |
| PainterOpenVG::transformPath(dst->vgPath(), m_path->vgPath(), transformation); |
| delete m_path; |
| m_path = dst; |
| |
| m_path->m_currentPoint = transformation.mapPoint(m_path->m_currentPoint); |
| m_path->m_subpathStartPoint = transformation.mapPoint(m_path->m_subpathStartPoint); |
| } |
| |
| |
| // Path::length(), Path::pointAtLength() and Path::normalAngleAtLength() are |
| // reimplemented here instead of in Path.cpp, because OpenVG has its own |
| // functions and Path::apply() doesn't really work as long as we rely on VGPath |
| // as primary path storage. |
| |
| float Path::length() const |
| { |
| m_path->makeCompatibleContextCurrent(); |
| VGfloat length = vgPathLength(m_path->vgPath(), 0, vgGetParameteri(m_path->vgPath(), VG_PATH_NUM_SEGMENTS)); |
| ASSERT_VG_NO_ERROR(); |
| return length; |
| } |
| |
| FloatPoint Path::pointAtLength(float length, bool& ok) const |
| { |
| VGfloat x = 0, y = 0; |
| m_path->makeCompatibleContextCurrent(); |
| |
| vgPointAlongPath(m_path->vgPath(), 0, vgGetParameteri(m_path->vgPath(), VG_PATH_NUM_SEGMENTS), |
| length, &x, &y, 0, 0); |
| ok = (vgGetError() == VG_NO_ERROR); |
| return FloatPoint(x, y); |
| } |
| |
| float Path::normalAngleAtLength(float length, bool& ok) const |
| { |
| VGfloat tangentX, tangentY; |
| m_path->makeCompatibleContextCurrent(); |
| |
| vgPointAlongPath(m_path->vgPath(), 0, vgGetParameteri(m_path->vgPath(), VG_PATH_NUM_SEGMENTS), |
| length, 0, 0, &tangentX, &tangentY); |
| ok = (vgGetError() == VG_NO_ERROR); |
| return atan2f(tangentY, tangentX) * 180.0 / piFloat; // convert to degrees |
| } |
| |
| } |