Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "nsSVGPathDataParser.h"
8 :
9 : #include "mozilla/gfx/Point.h"
10 : #include "nsSVGDataParser.h"
11 : #include "SVGContentUtils.h"
12 : #include "SVGPathData.h"
13 : #include "SVGPathSegUtils.h"
14 :
15 : using namespace mozilla;
16 : using namespace mozilla::gfx;
17 :
18 642 : static inline char16_t ToUpper(char16_t aCh)
19 : {
20 642 : return aCh >= 'a' && aCh <= 'z' ? aCh - 'a' + 'A' : aCh;
21 : }
22 :
23 : bool
24 44 : nsSVGPathDataParser::Parse()
25 : {
26 44 : mPathSegList->Clear();
27 44 : return ParsePath();
28 : }
29 :
30 : //----------------------------------------------------------------------
31 :
32 : bool
33 1292 : nsSVGPathDataParser::ParseCoordPair(float& aX, float& aY)
34 : {
35 2584 : return SVGContentUtils::ParseNumber(mIter, mEnd, aX) &&
36 2584 : SkipCommaWsp() &&
37 2584 : SVGContentUtils::ParseNumber(mIter, mEnd, aY);
38 : }
39 :
40 : bool
41 224 : nsSVGPathDataParser::ParseFlag(bool& aFlag)
42 : {
43 224 : if (mIter == mEnd || (*mIter != '0' && *mIter != '1')) {
44 0 : return false;
45 : }
46 224 : aFlag = (*mIter == '1');
47 :
48 224 : ++mIter;
49 224 : return true;
50 : }
51 :
52 : //----------------------------------------------------------------------
53 :
54 : bool
55 132 : nsSVGPathDataParser::ParsePath()
56 : {
57 220 : while (SkipWsp()) {
58 88 : if (!ParseSubPath()) {
59 0 : return false;
60 : }
61 : }
62 :
63 44 : return true;
64 : }
65 :
66 : //----------------------------------------------------------------------
67 :
68 : bool
69 88 : nsSVGPathDataParser::ParseSubPath()
70 : {
71 88 : return ParseMoveto() && ParseSubPathElements();
72 : }
73 :
74 : bool
75 730 : nsSVGPathDataParser::ParseSubPathElements()
76 : {
77 1372 : while (SkipWsp() && !IsStartOfSubPath()) {
78 642 : char16_t commandType = ToUpper(*mIter);
79 :
80 : // Upper case commands have absolute co-ordinates,
81 : // lower case commands have relative co-ordinates.
82 642 : bool absCoords = commandType == *mIter;
83 :
84 642 : ++mIter;
85 642 : SkipWsp();
86 :
87 642 : if (!ParseSubPathElement(commandType, absCoords)) {
88 0 : return false;
89 : }
90 : }
91 88 : return true;
92 : }
93 :
94 : bool
95 642 : nsSVGPathDataParser::ParseSubPathElement(char16_t aCommandType,
96 : bool aAbsCoords)
97 : {
98 642 : switch (aCommandType) {
99 : case 'Z':
100 83 : return ParseClosePath();
101 : case 'L':
102 209 : return ParseLineto(aAbsCoords);
103 : case 'H':
104 31 : return ParseHorizontalLineto(aAbsCoords);
105 : case 'V':
106 27 : return ParseVerticalLineto(aAbsCoords);
107 : case 'C':
108 173 : return ParseCurveto(aAbsCoords);
109 : case 'S':
110 28 : return ParseSmoothCurveto(aAbsCoords);
111 : case 'Q':
112 1 : return ParseQuadBezierCurveto(aAbsCoords);
113 : case 'T':
114 0 : return ParseSmoothQuadBezierCurveto(aAbsCoords);
115 : case 'A':
116 90 : return ParseEllipticalArc(aAbsCoords);
117 : }
118 0 : return false;
119 : }
120 :
121 : bool
122 774 : nsSVGPathDataParser::IsStartOfSubPath() const
123 : {
124 774 : return *mIter == 'm' || *mIter == 'M';
125 : }
126 :
127 : //----------------------------------------------------------------------
128 :
129 : bool
130 88 : nsSVGPathDataParser::ParseMoveto()
131 : {
132 88 : if (!IsStartOfSubPath()) {
133 0 : return false;
134 : }
135 :
136 88 : bool absCoords = (*mIter == 'M');
137 :
138 88 : ++mIter;
139 88 : SkipWsp();
140 :
141 : float x, y;
142 88 : if (!ParseCoordPair(x, y)) {
143 0 : return false;
144 : }
145 :
146 88 : if (NS_FAILED(mPathSegList->AppendSeg(
147 : absCoords ? PATHSEG_MOVETO_ABS : PATHSEG_MOVETO_REL,
148 : x, y))) {
149 0 : return false;
150 : }
151 :
152 88 : if (!SkipWsp() || IsAlpha(*mIter)) {
153 : // End of data, or start of a new command
154 82 : return true;
155 : }
156 :
157 6 : SkipCommaWsp();
158 :
159 : // Per SVG 1.1 Section 8.3.2
160 : // If a moveto is followed by multiple pairs of coordinates,
161 : // the subsequent pairs are treated as implicit lineto commands
162 6 : return ParseLineto(absCoords);
163 : }
164 :
165 : //----------------------------------------------------------------------
166 :
167 : bool
168 83 : nsSVGPathDataParser::ParseClosePath()
169 : {
170 83 : return NS_SUCCEEDED(mPathSegList->AppendSeg(PATHSEG_CLOSEPATH));
171 : }
172 :
173 : //----------------------------------------------------------------------
174 :
175 : bool
176 267 : nsSVGPathDataParser::ParseLineto(bool aAbsCoords)
177 : {
178 : while (true) {
179 : float x, y;
180 267 : if (!ParseCoordPair(x, y)) {
181 215 : return false;
182 : }
183 :
184 267 : if (NS_FAILED(mPathSegList->AppendSeg(
185 : aAbsCoords ? PATHSEG_LINETO_ABS : PATHSEG_LINETO_REL,
186 : x, y))) {
187 0 : return false;
188 : }
189 :
190 267 : if (!SkipWsp() || IsAlpha(*mIter)) {
191 : // End of data, or start of a new command
192 215 : return true;
193 : }
194 52 : SkipCommaWsp();
195 52 : }
196 : }
197 :
198 : //----------------------------------------------------------------------
199 :
200 : bool
201 31 : nsSVGPathDataParser::ParseHorizontalLineto(bool aAbsCoords)
202 : {
203 : while (true) {
204 : float x;
205 31 : if (!SVGContentUtils::ParseNumber(mIter, mEnd, x)) {
206 31 : return false;
207 : }
208 :
209 31 : if (NS_FAILED(mPathSegList->AppendSeg(
210 : aAbsCoords ? PATHSEG_LINETO_HORIZONTAL_ABS : PATHSEG_LINETO_HORIZONTAL_REL,
211 : x))) {
212 0 : return false;
213 : }
214 :
215 31 : if (!SkipWsp() || IsAlpha(*mIter)) {
216 : // End of data, or start of a new command
217 31 : return true;
218 : }
219 0 : SkipCommaWsp();
220 0 : }
221 : }
222 :
223 : //----------------------------------------------------------------------
224 :
225 : bool
226 27 : nsSVGPathDataParser::ParseVerticalLineto(bool aAbsCoords)
227 : {
228 : while (true) {
229 : float y;
230 27 : if (!SVGContentUtils::ParseNumber(mIter, mEnd, y)) {
231 27 : return false;
232 : }
233 :
234 27 : if (NS_FAILED(mPathSegList->AppendSeg(
235 : aAbsCoords ? PATHSEG_LINETO_VERTICAL_ABS : PATHSEG_LINETO_VERTICAL_REL,
236 : y))) {
237 0 : return false;
238 : }
239 :
240 27 : if (!SkipWsp() || IsAlpha(*mIter)) {
241 : // End of data, or start of a new command
242 27 : return true;
243 : }
244 0 : SkipCommaWsp();
245 0 : }
246 : }
247 :
248 : //----------------------------------------------------------------------
249 :
250 : bool
251 253 : nsSVGPathDataParser::ParseCurveto(bool aAbsCoords)
252 : {
253 : while (true) {
254 : float x1, y1, x2, y2, x, y;
255 :
256 759 : if (!(ParseCoordPair(x1, y1) &&
257 506 : SkipCommaWsp() &&
258 506 : ParseCoordPair(x2, y2) &&
259 253 : SkipCommaWsp() &&
260 253 : ParseCoordPair(x, y))) {
261 173 : return false;
262 : }
263 :
264 253 : if (NS_FAILED(mPathSegList->AppendSeg(
265 : aAbsCoords ? PATHSEG_CURVETO_CUBIC_ABS : PATHSEG_CURVETO_CUBIC_REL,
266 : x1, y1, x2, y2, x, y))) {
267 0 : return false;
268 : }
269 :
270 253 : if (!SkipWsp() || IsAlpha(*mIter)) {
271 : // End of data, or start of a new command
272 173 : return true;
273 : }
274 80 : SkipCommaWsp();
275 80 : }
276 : }
277 :
278 : //----------------------------------------------------------------------
279 :
280 : bool
281 32 : nsSVGPathDataParser::ParseSmoothCurveto(bool aAbsCoords)
282 : {
283 : while (true) {
284 : float x2, y2, x, y;
285 96 : if (!(ParseCoordPair(x2, y2) &&
286 32 : SkipCommaWsp() &&
287 32 : ParseCoordPair(x, y))) {
288 28 : return false;
289 : }
290 :
291 32 : if (NS_FAILED(mPathSegList->AppendSeg(
292 : aAbsCoords ? PATHSEG_CURVETO_CUBIC_SMOOTH_ABS : PATHSEG_CURVETO_CUBIC_SMOOTH_REL,
293 : x2, y2, x, y))) {
294 0 : return false;
295 : }
296 :
297 32 : if (!SkipWsp() || IsAlpha(*mIter)) {
298 : // End of data, or start of a new command
299 28 : return true;
300 : }
301 4 : SkipCommaWsp();
302 4 : }
303 : }
304 :
305 : //----------------------------------------------------------------------
306 :
307 : bool
308 1 : nsSVGPathDataParser::ParseQuadBezierCurveto(bool aAbsCoords)
309 : {
310 : while (true) {
311 : float x1, y1, x, y;
312 3 : if (!(ParseCoordPair(x1, y1) &&
313 1 : SkipCommaWsp() &&
314 1 : ParseCoordPair(x, y))) {
315 1 : return false;
316 : }
317 :
318 1 : if (NS_FAILED(mPathSegList->AppendSeg(
319 : aAbsCoords ? PATHSEG_CURVETO_QUADRATIC_ABS : PATHSEG_CURVETO_QUADRATIC_REL,
320 : x1, y1, x, y))) {
321 0 : return false;
322 : }
323 :
324 1 : if (!SkipWsp() || IsAlpha(*mIter)) {
325 : // Start of a new command
326 1 : return true;
327 : }
328 0 : SkipCommaWsp();
329 0 : }
330 : }
331 :
332 : //----------------------------------------------------------------------
333 :
334 : bool
335 0 : nsSVGPathDataParser::ParseSmoothQuadBezierCurveto(bool aAbsCoords)
336 : {
337 : while (true) {
338 : float x, y;
339 0 : if (!ParseCoordPair(x, y)) {
340 0 : return false;
341 : }
342 :
343 0 : if (NS_FAILED(mPathSegList->AppendSeg(
344 : aAbsCoords ? PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS : PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL,
345 : x, y))) {
346 0 : return false;
347 : }
348 :
349 0 : if (!SkipWsp() || IsAlpha(*mIter)) {
350 : // End of data, or start of a new command
351 0 : return true;
352 : }
353 0 : SkipCommaWsp();
354 0 : }
355 : }
356 :
357 : //----------------------------------------------------------------------
358 :
359 : bool
360 112 : nsSVGPathDataParser::ParseEllipticalArc(bool aAbsCoords)
361 : {
362 : while (true) {
363 : float r1, r2, angle, x, y;
364 : bool largeArcFlag, sweepFlag;
365 :
366 336 : if (!(SVGContentUtils::ParseNumber(mIter, mEnd, r1) &&
367 224 : SkipCommaWsp() &&
368 224 : SVGContentUtils::ParseNumber(mIter, mEnd, r2) &&
369 224 : SkipCommaWsp() &&
370 224 : SVGContentUtils::ParseNumber(mIter, mEnd, angle)&&
371 224 : SkipCommaWsp() &&
372 224 : ParseFlag(largeArcFlag) &&
373 224 : SkipCommaWsp() &&
374 224 : ParseFlag(sweepFlag) &&
375 112 : SkipCommaWsp() &&
376 112 : ParseCoordPair(x, y))) {
377 90 : return false;
378 : }
379 :
380 : // We can only pass floats after 'type', and per the SVG spec for arc,
381 : // non-zero args are treated at 'true'.
382 112 : if (NS_FAILED(mPathSegList->AppendSeg(
383 : aAbsCoords ? PATHSEG_ARC_ABS : PATHSEG_ARC_REL,
384 : r1, r2, angle,
385 : largeArcFlag ? 1.0f : 0.0f,
386 : sweepFlag ? 1.0f : 0.0f,
387 : x, y))) {
388 0 : return false;
389 : }
390 :
391 112 : if (!SkipWsp() || IsAlpha(*mIter)) {
392 : // End of data, or start of a new command
393 90 : return true;
394 : }
395 22 : SkipCommaWsp();
396 22 : }
397 : }
398 :
399 : //-----------------------------------------------------------------------
400 :
401 :
402 :
403 :
404 : static double
405 208 : CalcVectorAngle(double ux, double uy, double vx, double vy)
406 : {
407 208 : double ta = atan2(uy, ux);
408 208 : double tb = atan2(vy, vx);
409 208 : if (tb >= ta)
410 102 : return tb-ta;
411 106 : return 2 * M_PI - (ta-tb);
412 : }
413 :
414 :
415 104 : nsSVGArcConverter::nsSVGArcConverter(const Point& from,
416 : const Point& to,
417 : const Point& radii,
418 : double angle,
419 : bool largeArcFlag,
420 104 : bool sweepFlag)
421 : {
422 104 : const double radPerDeg = M_PI/180.0;
423 104 : mSegIndex = 0;
424 :
425 104 : if (from == to) {
426 0 : mNumSegs = 0;
427 0 : return;
428 : }
429 :
430 : // Convert to center parameterization as shown in
431 : // http://www.w3.org/TR/SVG/implnote.html
432 104 : mRx = fabs(radii.x);
433 104 : mRy = fabs(radii.y);
434 :
435 104 : mSinPhi = sin(angle*radPerDeg);
436 104 : mCosPhi = cos(angle*radPerDeg);
437 :
438 104 : double x1dash = mCosPhi * (from.x-to.x)/2.0 + mSinPhi * (from.y-to.y)/2.0;
439 104 : double y1dash = -mSinPhi * (from.x-to.x)/2.0 + mCosPhi * (from.y-to.y)/2.0;
440 :
441 : double root;
442 104 : double numerator = mRx*mRx*mRy*mRy - mRx*mRx*y1dash*y1dash -
443 104 : mRy*mRy*x1dash*x1dash;
444 :
445 104 : if (numerator < 0.0) {
446 : // If mRx , mRy and are such that there is no solution (basically,
447 : // the ellipse is not big enough to reach from 'from' to 'to'
448 : // then the ellipse is scaled up uniformly until there is
449 : // exactly one solution (until the ellipse is just big enough).
450 :
451 : // -> find factor s, such that numerator' with mRx'=s*mRx and
452 : // mRy'=s*mRy becomes 0 :
453 0 : double s = sqrt(1.0 - numerator/(mRx*mRx*mRy*mRy));
454 :
455 0 : mRx *= s;
456 0 : mRy *= s;
457 0 : root = 0.0;
458 :
459 : }
460 : else {
461 208 : root = (largeArcFlag == sweepFlag ? -1.0 : 1.0) *
462 104 : sqrt( numerator/(mRx*mRx*y1dash*y1dash + mRy*mRy*x1dash*x1dash) );
463 : }
464 :
465 104 : double cxdash = root*mRx*y1dash/mRy;
466 104 : double cydash = -root*mRy*x1dash/mRx;
467 :
468 104 : mC.x = mCosPhi * cxdash - mSinPhi * cydash + (from.x+to.x)/2.0;
469 104 : mC.y = mSinPhi * cxdash + mCosPhi * cydash + (from.y+to.y)/2.0;
470 104 : mTheta = CalcVectorAngle(1.0, 0.0, (x1dash-cxdash)/mRx, (y1dash-cydash)/mRy);
471 208 : double dtheta = CalcVectorAngle((x1dash-cxdash)/mRx, (y1dash-cydash)/mRy,
472 312 : (-x1dash-cxdash)/mRx, (-y1dash-cydash)/mRy);
473 104 : if (!sweepFlag && dtheta>0)
474 64 : dtheta -= 2.0*M_PI;
475 40 : else if (sweepFlag && dtheta<0)
476 0 : dtheta += 2.0*M_PI;
477 :
478 : // Convert into cubic bezier segments <= 90deg
479 104 : mNumSegs = static_cast<int>(ceil(fabs(dtheta/(M_PI/2.0))));
480 104 : mDelta = dtheta/mNumSegs;
481 104 : mT = 8.0/3.0 * sin(mDelta/4.0) * sin(mDelta/4.0) / sin(mDelta/2.0);
482 :
483 104 : mFrom = from;
484 : }
485 :
486 : bool
487 252 : nsSVGArcConverter::GetNextSegment(Point* cp1, Point* cp2, Point* to)
488 : {
489 252 : if (mSegIndex == mNumSegs) {
490 104 : return false;
491 : }
492 :
493 148 : double cosTheta1 = cos(mTheta);
494 148 : double sinTheta1 = sin(mTheta);
495 148 : double theta2 = mTheta + mDelta;
496 148 : double cosTheta2 = cos(theta2);
497 148 : double sinTheta2 = sin(theta2);
498 :
499 : // a) calculate endpoint of the segment:
500 148 : to->x = mCosPhi * mRx*cosTheta2 - mSinPhi * mRy*sinTheta2 + mC.x;
501 148 : to->y = mSinPhi * mRx*cosTheta2 + mCosPhi * mRy*sinTheta2 + mC.y;
502 :
503 : // b) calculate gradients at start/end points of segment:
504 148 : cp1->x = mFrom.x + mT * ( - mCosPhi * mRx*sinTheta1 - mSinPhi * mRy*cosTheta1);
505 148 : cp1->y = mFrom.y + mT * ( - mSinPhi * mRx*sinTheta1 + mCosPhi * mRy*cosTheta1);
506 :
507 148 : cp2->x = to->x + mT * ( mCosPhi * mRx*sinTheta2 + mSinPhi * mRy*cosTheta2);
508 148 : cp2->y = to->y + mT * ( mSinPhi * mRx*sinTheta2 - mCosPhi * mRy*cosTheta2);
509 :
510 : // do next segment
511 148 : mTheta = theta2;
512 148 : mFrom = *to;
513 148 : ++mSegIndex;
514 :
515 148 : return true;
516 : }
|