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/. */
7 #include "SVGPathDataParser.h"
9 #include "mozilla/gfx/Point.h"
10 #include "SVGDataParser.h"
11 #include "SVGContentUtils.h"
12 #include "SVGPathData.h"
13 #include "SVGPathSegUtils.h"
15 using namespace mozilla::dom::SVGPathSeg_Binding
;
16 using namespace mozilla::gfx
;
20 static inline char16_t
ToUpper(char16_t aCh
) {
21 return aCh
>= 'a' && aCh
<= 'z' ? aCh
- 'a' + 'A' : aCh
;
24 bool SVGPathDataParser::Parse() {
25 mPathSegList
->Clear();
29 //----------------------------------------------------------------------
31 bool SVGPathDataParser::ParseCoordPair(float& aX
, float& aY
) {
32 return SVGContentUtils::ParseNumber(mIter
, mEnd
, aX
) && SkipCommaWsp() &&
33 SVGContentUtils::ParseNumber(mIter
, mEnd
, aY
);
36 bool SVGPathDataParser::ParseFlag(bool& aFlag
) {
37 if (mIter
== mEnd
|| (*mIter
!= '0' && *mIter
!= '1')) {
40 aFlag
= (*mIter
== '1');
46 //----------------------------------------------------------------------
48 bool SVGPathDataParser::ParsePath() {
50 if (!ParseSubPath()) {
58 //----------------------------------------------------------------------
60 bool SVGPathDataParser::ParseSubPath() {
61 return ParseMoveto() && ParseSubPathElements();
64 bool SVGPathDataParser::ParseSubPathElements() {
65 while (SkipWsp() && !IsStartOfSubPath()) {
66 char16_t commandType
= ToUpper(*mIter
);
68 // Upper case commands have absolute co-ordinates,
69 // lower case commands have relative co-ordinates.
70 bool absCoords
= commandType
== *mIter
;
75 if (!ParseSubPathElement(commandType
, absCoords
)) {
82 bool SVGPathDataParser::ParseSubPathElement(char16_t aCommandType
,
84 switch (aCommandType
) {
86 return ParseClosePath();
88 return ParseLineto(aAbsCoords
);
90 return ParseHorizontalLineto(aAbsCoords
);
92 return ParseVerticalLineto(aAbsCoords
);
94 return ParseCurveto(aAbsCoords
);
96 return ParseSmoothCurveto(aAbsCoords
);
98 return ParseQuadBezierCurveto(aAbsCoords
);
100 return ParseSmoothQuadBezierCurveto(aAbsCoords
);
102 return ParseEllipticalArc(aAbsCoords
);
107 bool SVGPathDataParser::IsStartOfSubPath() const {
108 return *mIter
== 'm' || *mIter
== 'M';
111 //----------------------------------------------------------------------
113 bool SVGPathDataParser::ParseMoveto() {
114 if (!IsStartOfSubPath()) {
118 bool absCoords
= (*mIter
== 'M');
124 if (!ParseCoordPair(x
, y
)) {
128 if (NS_FAILED(mPathSegList
->AppendSeg(
129 absCoords
? PATHSEG_MOVETO_ABS
: PATHSEG_MOVETO_REL
, x
, y
))) {
133 if (!SkipWsp() || IsAlpha(*mIter
)) {
134 // End of data, or start of a new command
140 // Per SVG 1.1 Section 8.3.2
141 // If a moveto is followed by multiple pairs of coordinates,
142 // the subsequent pairs are treated as implicit lineto commands
143 return ParseLineto(absCoords
);
146 //----------------------------------------------------------------------
148 bool SVGPathDataParser::ParseClosePath() {
149 return NS_SUCCEEDED(mPathSegList
->AppendSeg(PATHSEG_CLOSEPATH
));
152 //----------------------------------------------------------------------
154 bool SVGPathDataParser::ParseLineto(bool aAbsCoords
) {
157 if (!ParseCoordPair(x
, y
)) {
161 if (NS_FAILED(mPathSegList
->AppendSeg(
162 aAbsCoords
? PATHSEG_LINETO_ABS
: PATHSEG_LINETO_REL
, x
, y
))) {
166 if (!SkipWsp() || IsAlpha(*mIter
)) {
167 // End of data, or start of a new command
174 //----------------------------------------------------------------------
176 bool SVGPathDataParser::ParseHorizontalLineto(bool aAbsCoords
) {
179 if (!SVGContentUtils::ParseNumber(mIter
, mEnd
, x
)) {
183 if (NS_FAILED(mPathSegList
->AppendSeg(aAbsCoords
184 ? PATHSEG_LINETO_HORIZONTAL_ABS
185 : PATHSEG_LINETO_HORIZONTAL_REL
,
190 if (!SkipWsp() || IsAlpha(*mIter
)) {
191 // End of data, or start of a new command
198 //----------------------------------------------------------------------
200 bool SVGPathDataParser::ParseVerticalLineto(bool aAbsCoords
) {
203 if (!SVGContentUtils::ParseNumber(mIter
, mEnd
, y
)) {
207 if (NS_FAILED(mPathSegList
->AppendSeg(aAbsCoords
208 ? PATHSEG_LINETO_VERTICAL_ABS
209 : PATHSEG_LINETO_VERTICAL_REL
,
214 if (!SkipWsp() || IsAlpha(*mIter
)) {
215 // End of data, or start of a new command
222 //----------------------------------------------------------------------
224 bool SVGPathDataParser::ParseCurveto(bool aAbsCoords
) {
226 float x1
, y1
, x2
, y2
, x
, y
;
228 if (!(ParseCoordPair(x1
, y1
) && SkipCommaWsp() && ParseCoordPair(x2
, y2
) &&
229 SkipCommaWsp() && ParseCoordPair(x
, y
))) {
233 if (NS_FAILED(mPathSegList
->AppendSeg(
234 aAbsCoords
? PATHSEG_CURVETO_CUBIC_ABS
: PATHSEG_CURVETO_CUBIC_REL
,
235 x1
, y1
, x2
, y2
, x
, y
))) {
239 if (!SkipWsp() || IsAlpha(*mIter
)) {
240 // End of data, or start of a new command
247 //----------------------------------------------------------------------
249 bool SVGPathDataParser::ParseSmoothCurveto(bool aAbsCoords
) {
252 if (!(ParseCoordPair(x2
, y2
) && SkipCommaWsp() && ParseCoordPair(x
, y
))) {
256 if (NS_FAILED(mPathSegList
->AppendSeg(
257 aAbsCoords
? PATHSEG_CURVETO_CUBIC_SMOOTH_ABS
258 : PATHSEG_CURVETO_CUBIC_SMOOTH_REL
,
263 if (!SkipWsp() || IsAlpha(*mIter
)) {
264 // End of data, or start of a new command
271 //----------------------------------------------------------------------
273 bool SVGPathDataParser::ParseQuadBezierCurveto(bool aAbsCoords
) {
276 if (!(ParseCoordPair(x1
, y1
) && SkipCommaWsp() && ParseCoordPair(x
, y
))) {
280 if (NS_FAILED(mPathSegList
->AppendSeg(aAbsCoords
281 ? PATHSEG_CURVETO_QUADRATIC_ABS
282 : PATHSEG_CURVETO_QUADRATIC_REL
,
287 if (!SkipWsp() || IsAlpha(*mIter
)) {
288 // Start of a new command
295 //----------------------------------------------------------------------
297 bool SVGPathDataParser::ParseSmoothQuadBezierCurveto(bool aAbsCoords
) {
300 if (!ParseCoordPair(x
, y
)) {
304 if (NS_FAILED(mPathSegList
->AppendSeg(
305 aAbsCoords
? PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS
306 : PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL
,
311 if (!SkipWsp() || IsAlpha(*mIter
)) {
312 // End of data, or start of a new command
319 //----------------------------------------------------------------------
321 bool SVGPathDataParser::ParseEllipticalArc(bool aAbsCoords
) {
323 float r1
, r2
, angle
, x
, y
;
324 bool largeArcFlag
, sweepFlag
;
326 if (!(SVGContentUtils::ParseNumber(mIter
, mEnd
, r1
) && SkipCommaWsp() &&
327 SVGContentUtils::ParseNumber(mIter
, mEnd
, r2
) && SkipCommaWsp() &&
328 SVGContentUtils::ParseNumber(mIter
, mEnd
, angle
) && SkipCommaWsp() &&
329 ParseFlag(largeArcFlag
) && SkipCommaWsp() && ParseFlag(sweepFlag
) &&
330 SkipCommaWsp() && ParseCoordPair(x
, y
))) {
334 // We can only pass floats after 'type', and per the SVG spec for arc,
335 // non-zero args are treated at 'true'.
336 if (NS_FAILED(mPathSegList
->AppendSeg(
337 aAbsCoords
? PATHSEG_ARC_ABS
: PATHSEG_ARC_REL
, r1
, r2
, angle
,
338 largeArcFlag
? 1.0f
: 0.0f
, sweepFlag
? 1.0f
: 0.0f
, x
, y
))) {
342 if (!SkipWsp() || IsAlpha(*mIter
)) {
343 // End of data, or start of a new command
350 //-----------------------------------------------------------------------
352 static double CalcVectorAngle(double ux
, double uy
, double vx
, double vy
) {
353 double ta
= atan2(uy
, ux
);
354 double tb
= atan2(vy
, vx
);
355 if (tb
>= ta
) return tb
- ta
;
356 return 2 * M_PI
- (ta
- tb
);
359 SVGArcConverter::SVGArcConverter(const Point
& from
, const Point
& to
,
360 const Point
& radii
, double angle
,
361 bool largeArcFlag
, bool sweepFlag
) {
362 MOZ_ASSERT(radii
.x
!= 0.0f
&& radii
.y
!= 0.0f
, "Bad radii");
364 const double radPerDeg
= M_PI
/ 180.0;
372 // Convert to center parameterization as shown in
373 // http://www.w3.org/TR/SVG/implnote.html
377 mSinPhi
= sin(angle
* radPerDeg
);
378 mCosPhi
= cos(angle
* radPerDeg
);
381 mCosPhi
* (from
.x
- to
.x
) / 2.0 + mSinPhi
* (from
.y
- to
.y
) / 2.0;
383 -mSinPhi
* (from
.x
- to
.x
) / 2.0 + mCosPhi
* (from
.y
- to
.y
) / 2.0;
386 double numerator
= mRx
* mRx
* mRy
* mRy
- mRx
* mRx
* y1dash
* y1dash
-
387 mRy
* mRy
* x1dash
* x1dash
;
389 if (numerator
< 0.0) {
390 // If mRx , mRy and are such that there is no solution (basically,
391 // the ellipse is not big enough to reach from 'from' to 'to'
392 // then the ellipse is scaled up uniformly until there is
393 // exactly one solution (until the ellipse is just big enough).
395 // -> find factor s, such that numerator' with mRx'=s*mRx and
396 // mRy'=s*mRy becomes 0 :
397 double s
= sqrt(1.0 - numerator
/ (mRx
* mRx
* mRy
* mRy
));
404 root
= (largeArcFlag
== sweepFlag
? -1.0 : 1.0) *
406 (mRx
* mRx
* y1dash
* y1dash
+ mRy
* mRy
* x1dash
* x1dash
));
409 double cxdash
= root
* mRx
* y1dash
/ mRy
;
410 double cydash
= -root
* mRy
* x1dash
/ mRx
;
412 mC
.x
= mCosPhi
* cxdash
- mSinPhi
* cydash
+ (from
.x
+ to
.x
) / 2.0;
413 mC
.y
= mSinPhi
* cxdash
+ mCosPhi
* cydash
+ (from
.y
+ to
.y
) / 2.0;
414 mTheta
= CalcVectorAngle(1.0, 0.0, (x1dash
- cxdash
) / mRx
,
415 (y1dash
- cydash
) / mRy
);
417 CalcVectorAngle((x1dash
- cxdash
) / mRx
, (y1dash
- cydash
) / mRy
,
418 (-x1dash
- cxdash
) / mRx
, (-y1dash
- cydash
) / mRy
);
419 if (!sweepFlag
&& dtheta
> 0)
420 dtheta
-= 2.0 * M_PI
;
421 else if (sweepFlag
&& dtheta
< 0)
422 dtheta
+= 2.0 * M_PI
;
424 // Convert into cubic bezier segments <= 90deg
425 mNumSegs
= static_cast<int>(ceil(fabs(dtheta
/ (M_PI
/ 2.0))));
426 mDelta
= dtheta
/ mNumSegs
;
427 mT
= 8.0 / 3.0 * sin(mDelta
/ 4.0) * sin(mDelta
/ 4.0) / sin(mDelta
/ 2.0);
432 bool SVGArcConverter::GetNextSegment(Point
* cp1
, Point
* cp2
, Point
* to
) {
433 if (mSegIndex
== mNumSegs
) {
437 double cosTheta1
= cos(mTheta
);
438 double sinTheta1
= sin(mTheta
);
439 double theta2
= mTheta
+ mDelta
;
440 double cosTheta2
= cos(theta2
);
441 double sinTheta2
= sin(theta2
);
443 // a) calculate endpoint of the segment:
444 to
->x
= mCosPhi
* mRx
* cosTheta2
- mSinPhi
* mRy
* sinTheta2
+ mC
.x
;
445 to
->y
= mSinPhi
* mRx
* cosTheta2
+ mCosPhi
* mRy
* sinTheta2
+ mC
.y
;
447 // b) calculate gradients at start/end points of segment:
449 mFrom
.x
+ mT
* (-mCosPhi
* mRx
* sinTheta1
- mSinPhi
* mRy
* cosTheta1
);
451 mFrom
.y
+ mT
* (-mSinPhi
* mRx
* sinTheta1
+ mCosPhi
* mRy
* cosTheta1
);
453 cp2
->x
= to
->x
+ mT
* (mCosPhi
* mRx
* sinTheta2
+ mSinPhi
* mRy
* cosTheta2
);
454 cp2
->y
= to
->y
+ mT
* (mSinPhi
* mRx
* sinTheta2
- mCosPhi
* mRy
* cosTheta2
);
464 } // namespace mozilla