2011-03-08 Andreas Kling <kling@webkit.org>
[WebKit-https.git] / Source / WebCore / platform / graphics / openvg / PathOpenVG.cpp
1 /*
2  * Copyright (C) Research In Motion Limited 2009-2010. All rights reserved.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public License
15  * along with this library; see the file COPYING.LIB.  If not, write to
16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 #include "config.h"
21 #include "Path.h"
22
23 #include "AffineTransform.h"
24 #include "FloatRect.h"
25 #include "GraphicsContext.h"
26 #include "NotImplemented.h"
27 #include "PainterOpenVG.h"
28 #include "PlatformPathOpenVG.h"
29 #include "PlatformString.h"
30 #include "StrokeStyleApplier.h"
31 #include "VGUtils.h"
32
33 #include <openvg.h>
34 #include <wtf/MathExtras.h>
35
36 #define WEBKIT_VG_PATH_CAPABILITIES VG_PATH_CAPABILITY_ALL
37
38 #define FUZZY_COMPARE(number, reference, delta) \
39     (number >= (reference - delta) && number <= (reference + delta))
40
41 namespace WebCore {
42
43 PlatformPathOpenVG::PlatformPathOpenVG()
44     : SharedResourceOpenVG()
45 {
46     createPath();
47 }
48
49 PlatformPathOpenVG::PlatformPathOpenVG(const PlatformPathOpenVG& other)
50     : SharedResourceOpenVG()
51     , m_currentPoint(other.m_currentPoint)
52     , m_subpathStartPoint(other.m_subpathStartPoint)
53 {
54     createPath();
55     // makeCompatibleContextCurrent() is called by createPath(), so not necessary here.
56     vgAppendPath(m_vgPath, other.m_vgPath);
57     ASSERT_VG_NO_ERROR();
58 }
59
60 PlatformPathOpenVG& PlatformPathOpenVG::operator=(const PlatformPathOpenVG& other)
61 {
62     if (&other != this) {
63         clear();
64         // makeCompatibleContextCurrent() is called by clear(), so not necessary here.
65         vgAppendPath(m_vgPath, other.m_vgPath);
66         ASSERT_VG_NO_ERROR();
67     }
68     return *this;
69 }
70
71 PlatformPathOpenVG::~PlatformPathOpenVG()
72 {
73     makeCompatibleContextCurrent();
74
75     vgDestroyPath(m_vgPath);
76     ASSERT_VG_NO_ERROR();
77 }
78
79 void PlatformPathOpenVG::clear()
80 {
81     makeCompatibleContextCurrent();
82
83     vgClearPath(m_vgPath, WEBKIT_VG_PATH_CAPABILITIES);
84     ASSERT_VG_NO_ERROR();
85
86     m_subpathStartPoint.setX(0);
87     m_subpathStartPoint.setY(0);
88     m_currentPoint = m_subpathStartPoint;
89 }
90
91 void PlatformPathOpenVG::createPath()
92 {
93     makeSharedContextCurrent();
94
95     m_vgPath = vgCreatePath(
96         VG_PATH_FORMAT_STANDARD, VG_PATH_DATATYPE_F,
97         1.0 /* scale */, 0.0 /* bias */,
98         0 /* expected number of segments */,
99         0 /* expected number of total coordinates */,
100         WEBKIT_VG_PATH_CAPABILITIES);
101     ASSERT_VG_NO_ERROR();
102 }
103
104
105 Path::Path()
106 {
107     m_path = new PlatformPathOpenVG();
108 }
109
110 Path::~Path()
111 {
112     delete m_path;
113 }
114
115 Path::Path(const Path& other)
116 {
117     m_path = new PlatformPathOpenVG(*(other.m_path));
118 }
119
120 Path& Path::operator=(const Path& other)
121 {
122     *m_path = *(other.m_path);
123     return *this;
124 }
125
126 FloatPoint Path::currentPoint() const 
127 {
128     // FIXME: is this the way to return the current point of the subpath?
129     return m_currentPoint;
130 }
131
132
133 bool Path::contains(const FloatPoint& point, WindRule rule) const
134 {
135     notImplemented();
136
137     // OpenVG has no path-contains function, so for now we approximate by
138     // using the bounding rect of the path.
139     return boundingRect().contains(point);
140 }
141
142 bool Path::strokeContains(StrokeStyleApplier* applier, const FloatPoint& point) const
143 {
144     notImplemented();
145
146     // OpenVG has no path-contains function, so for now we approximate by
147     // using the stroke bounding rect of the path.
148     return (const_cast<Path*>(this))->strokeBoundingRect().contains(point);
149 }
150
151 void Path::translate(const FloatSize& size)
152 {
153     AffineTransform transformation;
154     transformation.translate(size.width(), size.height());
155     transform(transformation);
156 }
157
158 FloatRect Path::boundingRect() const
159 {
160     VGfloat minX;
161     VGfloat minY;
162     VGfloat width;
163     VGfloat height;
164
165     m_path->makeCompatibleContextCurrent();
166     vgPathBounds(m_path->vgPath(), &minX, &minY, &width, &height);
167     ASSERT_VG_NO_ERROR();
168
169     return FloatRect(FloatPoint(minX, minY), FloatSize(width, height));
170 }
171
172 FloatRect Path::strokeBoundingRect(StrokeStyleApplier* applier) const
173 {
174     notImplemented();
175
176     // vgPathBounds() ignores stroke parameters, and we don't currently have
177     // an approximation that takes stroke parameters into account.
178     return boundingRect();
179 }
180
181 void Path::moveTo(const FloatPoint& point)
182 {
183     static const VGubyte pathSegments[] = { VG_MOVE_TO_ABS };
184     const VGfloat pathData[] = { point.x(), point.y() };
185
186     m_path->makeCompatibleContextCurrent();
187     vgAppendPathData(m_path->vgPath(), 1, pathSegments, pathData);
188     ASSERT_VG_NO_ERROR();
189
190     m_path->m_currentPoint = m_path->m_subpathStartPoint = point;
191 }
192
193 void Path::addLineTo(const FloatPoint& point)
194 {
195     static const VGubyte pathSegments[] = { VG_LINE_TO_ABS };
196     const VGfloat pathData[] = { point.x(), point.y() };
197
198     m_path->makeCompatibleContextCurrent();
199     vgAppendPathData(m_path->vgPath(), 1, pathSegments, pathData);
200     ASSERT_VG_NO_ERROR();
201
202     m_path->m_currentPoint = point;
203 }
204
205 void Path::addQuadCurveTo(const FloatPoint& controlPoint, const FloatPoint& endPoint)
206 {
207     static const VGubyte pathSegments[] = { VG_QUAD_TO_ABS };
208     const VGfloat pathData[] = { controlPoint.x(), controlPoint.y(), endPoint.x(), endPoint.y() };
209
210     m_path->makeCompatibleContextCurrent();
211     vgAppendPathData(m_path->vgPath(), 1, pathSegments, pathData);
212     ASSERT_VG_NO_ERROR();
213
214     m_path->m_currentPoint = endPoint;
215 }
216
217 void Path::addBezierCurveTo(const FloatPoint& controlPoint1, const FloatPoint& controlPoint2, const FloatPoint& endPoint)
218 {
219     static const VGubyte pathSegments[] = { VG_CUBIC_TO_ABS };
220     const VGfloat pathData[] = { controlPoint1.x(), controlPoint1.y(), controlPoint2.x(), controlPoint2.y(), endPoint.x(), endPoint.y() };
221
222     m_path->makeCompatibleContextCurrent();
223     vgAppendPathData(m_path->vgPath(), 1, pathSegments, pathData);
224     ASSERT_VG_NO_ERROR();
225
226     m_path->m_currentPoint = endPoint;
227 }
228
229 void Path::addArcTo(const FloatPoint& point1, const FloatPoint& point2, float radius)
230 {
231     // See http://philip.html5.org/tests/canvas/suite/tests/spec.html#arcto.
232
233     const FloatPoint& point0 = m_path->m_currentPoint;
234     if (!radius || point0 == point1 || point1 == point2) {
235         addLineTo(point1);
236         return;
237     }
238
239     FloatSize v01 = point0 - point1;
240     FloatSize v21 = point2 - point1;
241
242     // sin(A - B) = sin(A) * cos(B) - sin(B) * cos(A)
243     double cross = v01.width() * v21.height() - v01.height() * v21.width();
244
245     if (fabs(cross) < 1E-10) {
246         // on one line
247         addLineTo(point1);
248         return;
249     }
250
251     double d01 = hypot(v01.width(), v01.height());
252     double d21 = hypot(v21.width(), v21.height());
253     double angle = (piDouble - fabs(asin(cross / (d01 * d21)))) * 0.5;
254     double span = radius * tan(angle);
255     double rate = span / d01;
256     FloatPoint startPoint = FloatPoint(point1.x() + v01.width() * rate,
257                                        point1.y() + v01.height() * rate);
258     rate = span / d21;
259     FloatPoint endPoint = FloatPoint(point1.x() + v21.width() * rate,
260                                      point1.y() + v21.height() * rate);
261
262     // Fa: large arc flag, makes the difference between SCWARC_TO and LCWARC_TO
263     //     respectively SCCWARC_TO and LCCWARC_TO arcs. We always use small
264     //     arcs for arcTo(), as the arc is defined as the "shortest arc" of the
265     //     circle specified in HTML 5.
266
267     // Fs: sweep flag, specifying whether the arc is drawn in increasing (true)
268     //     or decreasing (0) direction.
269     const bool anticlockwise = cross < 0;
270
271     // Translate the large arc and sweep flags into an OpenVG segment command.
272     const VGubyte segmentCommand = anticlockwise ? VG_SCCWARC_TO_ABS : VG_SCWARC_TO_ABS;
273
274     const VGubyte pathSegments[] = {
275         VG_LINE_TO_ABS,
276         segmentCommand
277     };
278     const VGfloat pathData[] = {
279         startPoint.x(), startPoint.y(),
280         radius, radius, 0, endPoint.x(), endPoint.y()
281     };
282
283     m_path->makeCompatibleContextCurrent();
284     vgAppendPathData(m_path->vgPath(), 2, pathSegments, pathData);
285     ASSERT_VG_NO_ERROR();
286
287     m_path->m_currentPoint = endPoint;
288 }
289
290 void Path::closeSubpath()
291 {
292     static const VGubyte pathSegments[] = { VG_CLOSE_PATH };
293     // pathData must not be 0, but certain compilers also don't create
294     // zero-size arrays. So let's use a random aligned value (sizeof(VGfloat)),
295     // it won't be accessed anyways as VG_CLOSE_PATH doesn't take coordinates.
296     static const VGfloat* pathData = reinterpret_cast<VGfloat*>(sizeof(VGfloat));
297
298     m_path->makeCompatibleContextCurrent();
299     vgAppendPathData(m_path->vgPath(), 1, pathSegments, pathData);
300     ASSERT_VG_NO_ERROR();
301
302     m_path->m_currentPoint = m_path->m_subpathStartPoint;
303 }
304
305 void Path::addArc(const FloatPoint& center, float radius, float startAngle, float endAngle, bool anticlockwise)
306 {
307     // The OpenVG spec says nothing about inf as radius or start/end angle.
308     // WebKit seems to pass those (e.g. https://bugs.webkit.org/show_bug.cgi?id=16449),
309     // so abort instead of risking undefined behavior.
310     if (!isfinite(radius) || !isfinite(startAngle) || !isfinite(endAngle))
311         return;
312
313     // For some reason, the HTML 5 spec defines the angle as going clockwise
314     // from the positive X axis instead of going standard anticlockwise.
315     // So let's make it a proper angle in order to keep sanity.
316     startAngle = fmod((2.0 * piDouble) - startAngle, 2.0 * piDouble);
317     endAngle = fmod((2.0 * piDouble) - endAngle, 2.0 * piDouble);
318
319     // Make it so that endAngle > startAngle. fmod() above takes care of
320     // keeping the difference below 360 degrees.
321     if (endAngle <= startAngle)
322         endAngle += 2.0 * piDouble;
323
324     const VGfloat angleDelta = anticlockwise
325         ? (endAngle - startAngle)
326         : (startAngle - endAngle + (2.0 * piDouble));
327
328     // OpenVG uses endpoint parameterization while this method receives its
329     // values in center parameterization. It lacks an ellipse rotation
330     // parameter so we use 0 for that, and also the radius is only a single
331     // value which makes for rh == rv. In order to convert from endpoint to
332     // center parameterization, we use the formulas from the OpenVG/SVG specs:
333
334     // (x,y) = (cos rot, -sin rot; sin rot, -cos rot) * (rh * cos angle, rv * sin angle) + (center.x, center.y)
335     // rot is 0, which simplifies this a bit:
336     // (x,y) = (1, 0; 0, -1) * (rh * cos angle, rv * sin angle) + (center.x, center.y)
337     //       = (1 * rh * cos angle + 0 * rv * sin angle, 0 * rh * cos angle + -1 * rv * sin angle) + (center.x, center.y)
338     //       = (rh * cos angle, -rv * sin angle) + (center.x, center.y)
339     // (Set angle = {startAngle, endAngle} to retrieve the respective endpoints.)
340
341     const VGfloat startX = radius * cos(startAngle) + center.x();
342     const VGfloat startY = -radius * sin(startAngle) + center.y();
343     const VGfloat endX = radius * cos(endAngle) + center.x();
344     const VGfloat endY = -radius * sin(endAngle) + center.y();
345
346     // Fa: large arc flag, makes the difference between SCWARC_TO and LCWARC_TO
347     //     respectively SCCWARC_TO and LCCWARC_TO arcs.
348     const bool largeArc = (angleDelta > piDouble);
349
350     // Fs: sweep flag, specifying whether the arc is drawn in increasing (true)
351     //     or decreasing (0) direction. No need to calculate this value, as it
352     //     we already get it passed as a parameter (Fs == !anticlockwise).
353
354     // Translate the large arc and sweep flags into an OpenVG segment command.
355     // As OpenVG thinks of everything upside down, we need to reverse the
356     // anticlockwise parameter in order to get the specified rotation.
357     const VGubyte segmentCommand = !anticlockwise
358         ? (largeArc ? VG_LCCWARC_TO_ABS : VG_SCCWARC_TO_ABS)
359         : (largeArc ? VG_LCWARC_TO_ABS : VG_SCWARC_TO_ABS);
360
361     // So now, we've got all the parameters in endpoint parameterization format
362     // as OpenVG requires it. Which means we can just pass it like this.
363     const VGubyte pathSegments[] = {
364         hasCurrentPoint() ? VG_LINE_TO_ABS : VG_MOVE_TO_ABS,
365         segmentCommand
366     };
367     const VGfloat pathData[] = {
368         startX, startY,
369         radius, radius, 0, endX, endY
370     };
371
372     m_path->makeCompatibleContextCurrent();
373     vgAppendPathData(m_path->vgPath(), 2, pathSegments, pathData);
374     ASSERT_VG_NO_ERROR();
375
376     m_path->m_currentPoint.setX(endX);
377     m_path->m_currentPoint.setY(endY);
378 }
379
380 void Path::addRect(const FloatRect& rect)
381 {
382     static const VGubyte pathSegments[] = {
383         VG_MOVE_TO_ABS,
384         VG_HLINE_TO_REL,
385         VG_VLINE_TO_REL,
386         VG_HLINE_TO_REL,
387         VG_CLOSE_PATH
388     };
389     const VGfloat pathData[] = {
390         rect.x(), rect.y(),
391         rect.width(),
392         rect.height(),
393         -rect.width()
394     };
395
396     m_path->makeCompatibleContextCurrent();
397     vgAppendPathData(m_path->vgPath(), 5, pathSegments, pathData);
398     ASSERT_VG_NO_ERROR();
399
400     m_path->m_currentPoint = m_path->m_subpathStartPoint = rect.location();
401 }
402
403 void Path::addEllipse(const FloatRect& rect)
404 {
405     static const VGubyte pathSegments[] = {
406         VG_MOVE_TO_ABS,
407         VG_SCCWARC_TO_REL,
408         VG_SCCWARC_TO_REL,
409         VG_CLOSE_PATH
410     };
411     const VGfloat pathData[] = {
412         rect.x() + rect.width() / 2.0, rect.y(),
413         rect.width() / 2.0, rect.height() / 2.0, 0, 0, rect.height(),
414         rect.width() / 2.0, rect.height() / 2.0, 0, 0, -rect.height()
415     };
416
417     m_path->makeCompatibleContextCurrent();
418     vgAppendPathData(m_path->vgPath(), 4, pathSegments, pathData);
419     ASSERT_VG_NO_ERROR();
420 }
421
422 void Path::clear()
423 {
424     m_path->clear();
425 }
426
427 bool Path::isEmpty() const
428 {
429     m_path->makeCompatibleContextCurrent();
430     return !vgGetParameteri(m_path->vgPath(), VG_PATH_NUM_SEGMENTS);
431 }
432
433 bool Path::hasCurrentPoint() const
434 {
435     m_path->makeCompatibleContextCurrent();
436     return vgGetParameteri(m_path->vgPath(), VG_PATH_NUM_SEGMENTS) > 0;
437 }
438
439 void Path::apply(void* info, PathApplierFunction function) const
440 {
441     // OpenVG provides no means to retrieve path segment information.
442     // This is *very* unfortunate, we might need to store the segments in
443     // memory if we want to implement this function properly.
444     // See http://www.khronos.org/message_boards/viewtopic.php?f=6&t=1887
445     notImplemented();
446 }
447
448 void Path::transform(const AffineTransform& transformation)
449 {
450     PlatformPathOpenVG* dst = new PlatformPathOpenVG();
451     // dst->makeCompatibleContextCurrent() is called by the platform path
452     // constructor, therefore not necessary to call it again here.
453     PainterOpenVG::transformPath(dst->vgPath(), m_path->vgPath(), transformation);
454     delete m_path;
455     m_path = dst;
456
457     m_path->m_currentPoint = transformation.mapPoint(m_path->m_currentPoint);
458     m_path->m_subpathStartPoint = transformation.mapPoint(m_path->m_subpathStartPoint);
459 }
460
461
462 // Path::length(), Path::pointAtLength() and Path::normalAngleAtLength() are
463 // reimplemented here instead of in Path.cpp, because OpenVG has its own
464 // functions and Path::apply() doesn't really work as long as we rely on VGPath
465 // as primary path storage.
466
467 float Path::length() const
468 {
469     m_path->makeCompatibleContextCurrent();
470     VGfloat length = vgPathLength(m_path->vgPath(), 0, vgGetParameteri(m_path->vgPath(), VG_PATH_NUM_SEGMENTS));
471     ASSERT_VG_NO_ERROR();
472     return length;
473 }
474
475 FloatPoint Path::pointAtLength(float length, bool& ok) const
476 {
477     VGfloat x = 0, y = 0;
478     m_path->makeCompatibleContextCurrent();
479
480     vgPointAlongPath(m_path->vgPath(), 0, vgGetParameteri(m_path->vgPath(), VG_PATH_NUM_SEGMENTS),
481                      length, &x, &y, 0, 0);
482     ok = (vgGetError() == VG_NO_ERROR);
483     return FloatPoint(x, y);
484 }
485
486 float Path::normalAngleAtLength(float length, bool& ok) const
487 {
488     VGfloat tangentX, tangentY;
489     m_path->makeCompatibleContextCurrent();
490
491     vgPointAlongPath(m_path->vgPath(), 0, vgGetParameteri(m_path->vgPath(), VG_PATH_NUM_SEGMENTS),
492                      length, 0, 0, &tangentX, &tangentY);
493     ok = (vgGetError() == VG_NO_ERROR);
494     return atan2f(tangentY, tangentX) * 180.0 / piFloat; // convert to degrees
495 }
496
497 }