1 /*
2 * Copyright (C) 2002, 2003 The Karbon Developers
3 * Copyright (C) 2006 Alexander Kellett <lypanov@kde.org>
4 * Copyright (C) 2006, 2007 Rob Buis <buis@kde.org>
5 * Copyright (C) 2007, 2009 Apple Inc. All rights reserved.
6 * Copyright (C) Research In Motion Limited 2010. All rights reserved.
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Library General Public License for more details.
17 *
18 * You should have received a copy of the GNU Library General Public License
19 * along with this library; see the file COPYING.LIB. If not, write to
20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
22 */
23
24 #include "config.h"
25
26 #if ENABLE(SVG)
27 #include "SVGPathParser.h"
28
29 #include "AffineTransform.h"
30 #include <wtf/MathExtras.h>
31
32 static const float gOneOverThree = 1 / 3.f;
33
34 namespace WebCore {
35
SVGPathParser()36 SVGPathParser::SVGPathParser()
37 : m_consumer(0)
38 {
39 }
40
parseClosePathSegment()41 void SVGPathParser::parseClosePathSegment()
42 {
43 // Reset m_currentPoint for the next path.
44 if (m_pathParsingMode == NormalizedParsing)
45 m_currentPoint = m_subPathPoint;
46 m_closePath = true;
47 m_consumer->closePath();
48 }
49
parseMoveToSegment()50 bool SVGPathParser::parseMoveToSegment()
51 {
52 FloatPoint targetPoint;
53 if (!m_source->parseMoveToSegment(targetPoint))
54 return false;
55
56 if (m_pathParsingMode == NormalizedParsing) {
57 if (m_mode == RelativeCoordinates)
58 m_currentPoint += targetPoint;
59 else
60 m_currentPoint = targetPoint;
61 m_subPathPoint = m_currentPoint;
62 m_consumer->moveTo(m_currentPoint, m_closePath, AbsoluteCoordinates);
63 } else
64 m_consumer->moveTo(targetPoint, m_closePath, m_mode);
65 m_closePath = false;
66 return true;
67 }
68
parseLineToSegment()69 bool SVGPathParser::parseLineToSegment()
70 {
71 FloatPoint targetPoint;
72 if (!m_source->parseLineToSegment(targetPoint))
73 return false;
74
75 if (m_pathParsingMode == NormalizedParsing) {
76 if (m_mode == RelativeCoordinates)
77 m_currentPoint += targetPoint;
78 else
79 m_currentPoint = targetPoint;
80 m_consumer->lineTo(m_currentPoint, AbsoluteCoordinates);
81 } else
82 m_consumer->lineTo(targetPoint, m_mode);
83 return true;
84 }
85
parseLineToHorizontalSegment()86 bool SVGPathParser::parseLineToHorizontalSegment()
87 {
88 float toX;
89 if (!m_source->parseLineToHorizontalSegment(toX))
90 return false;
91
92 if (m_pathParsingMode == NormalizedParsing) {
93 if (m_mode == RelativeCoordinates)
94 m_currentPoint.move(toX, 0);
95 else
96 m_currentPoint.setX(toX);
97 m_consumer->lineTo(m_currentPoint, AbsoluteCoordinates);
98 } else
99 m_consumer->lineToHorizontal(toX, m_mode);
100 return true;
101 }
102
parseLineToVerticalSegment()103 bool SVGPathParser::parseLineToVerticalSegment()
104 {
105 float toY;
106 if (!m_source->parseLineToVerticalSegment(toY))
107 return false;
108
109 if (m_pathParsingMode == NormalizedParsing) {
110 if (m_mode == RelativeCoordinates)
111 m_currentPoint.move(0, toY);
112 else
113 m_currentPoint.setY(toY);
114 m_consumer->lineTo(m_currentPoint, AbsoluteCoordinates);
115 } else
116 m_consumer->lineToVertical(toY, m_mode);
117 return true;
118 }
119
parseCurveToCubicSegment()120 bool SVGPathParser::parseCurveToCubicSegment()
121 {
122 FloatPoint point1;
123 FloatPoint point2;
124 FloatPoint targetPoint;
125 if (!m_source->parseCurveToCubicSegment(point1, point2, targetPoint))
126 return false;
127
128 if (m_pathParsingMode == NormalizedParsing) {
129 if (m_mode == RelativeCoordinates) {
130 point1 += m_currentPoint;
131 point2 += m_currentPoint;
132 targetPoint += m_currentPoint;
133 }
134 m_consumer->curveToCubic(point1, point2, targetPoint, AbsoluteCoordinates);
135
136 m_controlPoint = point2;
137 m_currentPoint = targetPoint;
138 } else
139 m_consumer->curveToCubic(point1, point2, targetPoint, m_mode);
140 return true;
141 }
142
parseCurveToCubicSmoothSegment()143 bool SVGPathParser::parseCurveToCubicSmoothSegment()
144 {
145 FloatPoint point2;
146 FloatPoint targetPoint;
147 if (!m_source->parseCurveToCubicSmoothSegment(point2, targetPoint))
148 return false;
149
150 if (m_lastCommand != PathSegCurveToCubicAbs
151 && m_lastCommand != PathSegCurveToCubicRel
152 && m_lastCommand != PathSegCurveToCubicSmoothAbs
153 && m_lastCommand != PathSegCurveToCubicSmoothRel)
154 m_controlPoint = m_currentPoint;
155
156 if (m_pathParsingMode == NormalizedParsing) {
157 FloatPoint point1 = m_currentPoint;
158 point1.scale(2, 2);
159 point1.move(-m_controlPoint.x(), -m_controlPoint.y());
160 if (m_mode == RelativeCoordinates) {
161 point2 += m_currentPoint;
162 targetPoint += m_currentPoint;
163 }
164
165 m_consumer->curveToCubic(point1, point2, targetPoint, AbsoluteCoordinates);
166
167 m_controlPoint = point2;
168 m_currentPoint = targetPoint;
169 } else
170 m_consumer->curveToCubicSmooth(point2, targetPoint, m_mode);
171 return true;
172 }
173
parseCurveToQuadraticSegment()174 bool SVGPathParser::parseCurveToQuadraticSegment()
175 {
176 FloatPoint point1;
177 FloatPoint targetPoint;
178 if (!m_source->parseCurveToQuadraticSegment(point1, targetPoint))
179 return false;
180
181 if (m_pathParsingMode == NormalizedParsing) {
182 m_controlPoint = point1;
183 FloatPoint point1 = m_currentPoint;
184 point1.move(2 * m_controlPoint.x(), 2 * m_controlPoint.y());
185 FloatPoint point2(targetPoint.x() + 2 * m_controlPoint.x(), targetPoint.y() + 2 * m_controlPoint.y());
186 if (m_mode == RelativeCoordinates) {
187 point1.move(2 * m_currentPoint.x(), 2 * m_currentPoint.y());
188 point2.move(3 * m_currentPoint.x(), 3 * m_currentPoint.y());
189 targetPoint += m_currentPoint;
190 }
191 point1.scale(gOneOverThree, gOneOverThree);
192 point2.scale(gOneOverThree, gOneOverThree);
193
194 m_consumer->curveToCubic(point1, point2, targetPoint, AbsoluteCoordinates);
195
196 if (m_mode == RelativeCoordinates)
197 m_controlPoint += m_currentPoint;
198 m_currentPoint = targetPoint;
199 } else
200 m_consumer->curveToQuadratic(point1, targetPoint, m_mode);
201 return true;
202 }
203
parseCurveToQuadraticSmoothSegment()204 bool SVGPathParser::parseCurveToQuadraticSmoothSegment()
205 {
206 FloatPoint targetPoint;
207 if (!m_source->parseCurveToQuadraticSmoothSegment(targetPoint))
208 return false;
209
210 if (m_lastCommand != PathSegCurveToQuadraticAbs
211 && m_lastCommand != PathSegCurveToQuadraticRel
212 && m_lastCommand != PathSegCurveToQuadraticSmoothAbs
213 && m_lastCommand != PathSegCurveToQuadraticSmoothRel)
214 m_controlPoint = m_currentPoint;
215
216 if (m_pathParsingMode == NormalizedParsing) {
217 FloatPoint cubicPoint = m_currentPoint;
218 cubicPoint.scale(2, 2);
219 cubicPoint.move(-m_controlPoint.x(), -m_controlPoint.y());
220 FloatPoint point1(m_currentPoint.x() + 2 * cubicPoint.x(), m_currentPoint.y() + 2 * cubicPoint.y());
221 FloatPoint point2(targetPoint.x() + 2 * cubicPoint.x(), targetPoint.y() + 2 * cubicPoint.y());
222 if (m_mode == RelativeCoordinates) {
223 point2 += m_currentPoint;
224 targetPoint += m_currentPoint;
225 }
226 point1.scale(gOneOverThree, gOneOverThree);
227 point2.scale(gOneOverThree, gOneOverThree);
228
229 m_consumer->curveToCubic(point1, point2, targetPoint, AbsoluteCoordinates);
230
231 m_controlPoint = cubicPoint;
232 m_currentPoint = targetPoint;
233 } else
234 m_consumer->curveToQuadraticSmooth(targetPoint, m_mode);
235 return true;
236 }
237
parseArcToSegment()238 bool SVGPathParser::parseArcToSegment()
239 {
240 float rx;
241 float ry;
242 float angle;
243 bool largeArc;
244 bool sweep;
245 FloatPoint targetPoint;
246 if (!m_source->parseArcToSegment(rx, ry, angle, largeArc, sweep, targetPoint))
247 return false;
248
249 // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto") joining the endpoints.
250 // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
251 rx = fabsf(rx);
252 ry = fabsf(ry);
253 if (!rx || !ry) {
254 if (m_pathParsingMode == NormalizedParsing) {
255 if (m_mode == RelativeCoordinates)
256 m_currentPoint += targetPoint;
257 else
258 m_currentPoint = targetPoint;
259 m_consumer->lineTo(m_currentPoint, AbsoluteCoordinates);
260 } else
261 m_consumer->lineTo(targetPoint, m_mode);
262 return true;
263 }
264
265 if (m_pathParsingMode == NormalizedParsing) {
266 FloatPoint point1 = m_currentPoint;
267 if (m_mode == RelativeCoordinates)
268 targetPoint += m_currentPoint;
269 m_currentPoint = targetPoint;
270 return decomposeArcToCubic(angle, rx, ry, point1, targetPoint, largeArc, sweep);
271 }
272 m_consumer->arcTo(rx, ry, angle, largeArc, sweep, targetPoint, m_mode);
273 return true;
274 }
275
parsePathDataFromSource(PathParsingMode pathParsingMode)276 bool SVGPathParser::parsePathDataFromSource(PathParsingMode pathParsingMode)
277 {
278 ASSERT(m_source);
279 ASSERT(m_consumer);
280
281 m_pathParsingMode = pathParsingMode;
282
283 m_controlPoint = FloatPoint();
284 m_currentPoint = FloatPoint();
285 m_subPathPoint = FloatPoint();
286 m_closePath = true;
287
288 // Skip any leading spaces.
289 if (!m_source->moveToNextToken())
290 return false;
291
292 SVGPathSegType command;
293 m_source->parseSVGSegmentType(command);
294 m_lastCommand = PathSegUnknown;
295
296 // Path must start with moveto.
297 if (command != PathSegMoveToAbs && command != PathSegMoveToRel)
298 return false;
299
300 while (true) {
301 // Skip spaces between command and first coordinate.
302 m_source->moveToNextToken();
303 m_mode = AbsoluteCoordinates;
304 switch (command) {
305 case PathSegMoveToRel:
306 m_mode = RelativeCoordinates;
307 case PathSegMoveToAbs:
308 if (!parseMoveToSegment())
309 return false;
310 break;
311 case PathSegLineToRel:
312 m_mode = RelativeCoordinates;
313 case PathSegLineToAbs:
314 if (!parseLineToSegment())
315 return false;
316 break;
317 case PathSegLineToHorizontalRel:
318 m_mode = RelativeCoordinates;
319 case PathSegLineToHorizontalAbs:
320 if (!parseLineToHorizontalSegment())
321 return false;
322 break;
323 case PathSegLineToVerticalRel:
324 m_mode = RelativeCoordinates;
325 case PathSegLineToVerticalAbs:
326 if (!parseLineToVerticalSegment())
327 return false;
328 break;
329 case PathSegClosePath:
330 parseClosePathSegment();
331 break;
332 case PathSegCurveToCubicRel:
333 m_mode = RelativeCoordinates;
334 case PathSegCurveToCubicAbs:
335 if (!parseCurveToCubicSegment())
336 return false;
337 break;
338 case PathSegCurveToCubicSmoothRel:
339 m_mode = RelativeCoordinates;
340 case PathSegCurveToCubicSmoothAbs:
341 if (!parseCurveToCubicSmoothSegment())
342 return false;
343 break;
344 case PathSegCurveToQuadraticRel:
345 m_mode = RelativeCoordinates;
346 case PathSegCurveToQuadraticAbs:
347 if (!parseCurveToQuadraticSegment())
348 return false;
349 break;
350 case PathSegCurveToQuadraticSmoothRel:
351 m_mode = RelativeCoordinates;
352 case PathSegCurveToQuadraticSmoothAbs:
353 if (!parseCurveToQuadraticSmoothSegment())
354 return false;
355 break;
356 case PathSegArcRel:
357 m_mode = RelativeCoordinates;
358 case PathSegArcAbs:
359 if (!parseArcToSegment())
360 return false;
361 break;
362 default:
363 return false;
364 }
365 if (!m_consumer->continueConsuming())
366 return true;
367
368 m_lastCommand = command;
369
370 if (!m_source->hasMoreData())
371 return true;
372
373 command = m_source->nextCommand(command);
374
375 if (m_lastCommand != PathSegCurveToCubicAbs
376 && m_lastCommand != PathSegCurveToCubicRel
377 && m_lastCommand != PathSegCurveToCubicSmoothAbs
378 && m_lastCommand != PathSegCurveToCubicSmoothRel
379 && m_lastCommand != PathSegCurveToQuadraticAbs
380 && m_lastCommand != PathSegCurveToQuadraticRel
381 && m_lastCommand != PathSegCurveToQuadraticSmoothAbs
382 && m_lastCommand != PathSegCurveToQuadraticSmoothRel)
383 m_controlPoint = m_currentPoint;
384
385 m_consumer->incrementPathSegmentCount();
386 }
387
388 return false;
389 }
390
cleanup()391 void SVGPathParser::cleanup()
392 {
393 ASSERT(m_source);
394 ASSERT(m_consumer);
395
396 m_consumer->cleanup();
397 m_source = 0;
398 m_consumer = 0;
399 }
400
401 // This works by converting the SVG arc to "simple" beziers.
402 // Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
403 // See also SVG implementation notes: http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
decomposeArcToCubic(float angle,float rx,float ry,FloatPoint & point1,FloatPoint & point2,bool largeArcFlag,bool sweepFlag)404 bool SVGPathParser::decomposeArcToCubic(float angle, float rx, float ry, FloatPoint& point1, FloatPoint& point2, bool largeArcFlag, bool sweepFlag)
405 {
406 FloatSize midPointDistance = point1 - point2;
407 midPointDistance.scale(0.5f);
408
409 AffineTransform pointTransform;
410 pointTransform.rotate(-angle);
411
412 FloatPoint transformedMidPoint = pointTransform.mapPoint(FloatPoint(midPointDistance.width(), midPointDistance.height()));
413 float squareRx = rx * rx;
414 float squareRy = ry * ry;
415 float squareX = transformedMidPoint.x() * transformedMidPoint.x();
416 float squareY = transformedMidPoint.y() * transformedMidPoint.y();
417
418 // Check if the radii are big enough to draw the arc, scale radii if not.
419 // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
420 float radiiScale = squareX / squareRx + squareY / squareRy;
421 if (radiiScale > 1) {
422 rx *= sqrtf(radiiScale);
423 ry *= sqrtf(radiiScale);
424 }
425
426 pointTransform.makeIdentity();
427 pointTransform.scale(1 / rx, 1 / ry);
428 pointTransform.rotate(-angle);
429
430 point1 = pointTransform.mapPoint(point1);
431 point2 = pointTransform.mapPoint(point2);
432 FloatSize delta = point2 - point1;
433
434 float d = delta.width() * delta.width() + delta.height() * delta.height();
435 float scaleFactorSquared = std::max(1 / d - 0.25f, 0.f);
436
437 float scaleFactor = sqrtf(scaleFactorSquared);
438 if (sweepFlag == largeArcFlag)
439 scaleFactor = -scaleFactor;
440
441 delta.scale(scaleFactor);
442 FloatPoint centerPoint = FloatPoint(0.5f * (point1.x() + point2.x()) - delta.height(),
443 0.5f * (point1.y() + point2.y()) + delta.width());
444
445 float theta1 = atan2f(point1.y() - centerPoint.y(), point1.x() - centerPoint.x());
446 float theta2 = atan2f(point2.y() - centerPoint.y(), point2.x() - centerPoint.x());
447
448 float thetaArc = theta2 - theta1;
449 if (thetaArc < 0 && sweepFlag)
450 thetaArc += 2 * piFloat;
451 else if (thetaArc > 0 && !sweepFlag)
452 thetaArc -= 2 * piFloat;
453
454 pointTransform.makeIdentity();
455 pointTransform.rotate(angle);
456 pointTransform.scale(rx, ry);
457
458 // Some results of atan2 on some platform implementations are not exact enough. So that we get more
459 // cubic curves than expected here. Adding 0.001f reduces the count of sgements to the correct count.
460 int segments = ceilf(fabsf(thetaArc / (piOverTwoFloat + 0.001f)));
461 for (int i = 0; i < segments; ++i) {
462 float startTheta = theta1 + i * thetaArc / segments;
463 float endTheta = theta1 + (i + 1) * thetaArc / segments;
464
465 float t = (8 / 6.f) * tanf(0.25f * (endTheta - startTheta));
466 if (!isfinite(t))
467 return false;
468 float sinStartTheta = sinf(startTheta);
469 float cosStartTheta = cosf(startTheta);
470 float sinEndTheta = sinf(endTheta);
471 float cosEndTheta = cosf(endTheta);
472
473 point1 = FloatPoint(cosStartTheta - t * sinStartTheta, sinStartTheta + t * cosStartTheta);
474 point1.move(centerPoint.x(), centerPoint.y());
475 FloatPoint targetPoint = FloatPoint(cosEndTheta, sinEndTheta);
476 targetPoint.move(centerPoint.x(), centerPoint.y());
477 point2 = targetPoint;
478 point2.move(t * sinEndTheta, -t * cosEndTheta);
479
480 m_consumer->curveToCubic(pointTransform.mapPoint(point1), pointTransform.mapPoint(point2),
481 pointTransform.mapPoint(targetPoint), AbsoluteCoordinates);
482 }
483 return true;
484 }
485
486 }
487
488 #endif // ENABLE(SVG)
489