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