svgpathparser.cpp
1/* This file is part of the KDE project
2 Copyright (C) 2002, The Karbon Developers
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 "svgpathparser.h"
21#include <tqstring.h>
22#include <math.h>
23#include <kdebug.h>
24
25// parses the coord into number and forwards to the next token
26const char *
27SVGPathParser::getCoord( const char *ptr, double &number )
28{
29 int integer, exponent;
30 double decimal, frac;
31 int sign, expsign;
32
33 exponent = 0;
34 integer = 0;
35 frac = 1.0;
36 decimal = 0;
37 sign = 1;
38 expsign = 1;
39
40 // read the sign
41 if(*ptr == '+')
42 ptr++;
43 else if(*ptr == '-')
44 {
45 ptr++;
46 sign = -1;
47 }
48
49 // read the integer part
50 while(*ptr != '\0' && *ptr >= '0' && *ptr <= '9')
51 integer = (integer * 10) + *(ptr++) - '0';
52 if(*ptr == '.') // read the decimals
53 {
54 ptr++;
55 while(*ptr != '\0' && *ptr >= '0' && *ptr <= '9')
56 decimal += (*(ptr++) - '0') * (frac *= 0.1);
57 }
58
59 if(*ptr == 'e' || *ptr == 'E') // read the exponent part
60 {
61 ptr++;
62
63 // read the sign of the exponent
64 if(*ptr == '+')
65 ptr++;
66 else if(*ptr == '-')
67 {
68 ptr++;
69 expsign = -1;
70 }
71
72 exponent = 0;
73 while(*ptr != '\0' && *ptr >= '0' && *ptr <= '9')
74 {
75 exponent *= 10;
76 exponent += *ptr - '0';
77 ptr++;
78 }
79 }
80 number = integer + decimal;
81 number *= sign * pow( (double)10, double( expsign * exponent ) );
82
83 // skip the following space
84 if(*ptr == ' ')
85 ptr++;
86
87 return ptr;
88}
89
90void
91SVGPathParser::parseSVG( const TQString &s, bool process )
92{
93 if( !s.isEmpty() )
94 {
95 TQString d = s;
96 d = d.replace( ',', ' ' );
97 d = d.simplifyWhiteSpace();
98
99 const char *ptr = d.latin1();
100 const char *end = d.latin1() + d.length() + 1;
101
102 double contrlx, contrly, curx, cury, subpathx, subpathy, tox, toy, x1, y1, x2, y2, xc, yc;
103 double px1, py1, px2, py2, px3, py3;
104 bool relative;
105 char command = *(ptr++), lastCommand = ' ';
106
107 subpathx = subpathy = curx = cury = contrlx = contrly = 0.0;
108 while( ptr < end )
109 {
110 if( *ptr == ' ' )
111 ptr++;
112
113 relative = false;
114
115 //std::cout << "Command : " << command << std::endl;
116 switch( command )
117 {
118 case 'm':
119 relative = true;
120 case 'M':
121 {
122 ptr = getCoord( ptr, tox );
123 ptr = getCoord( ptr, toy );
124
125 if( process )
126 {
127 subpathx = curx = relative ? curx + tox : tox;
128 subpathy = cury = relative ? cury + toy : toy;
129
130 svgMoveTo( curx, cury );
131 }
132 else
133 svgMoveTo( tox, toy, !relative );
134 break;
135 }
136 case 'l':
137 relative = true;
138 case 'L':
139 {
140 ptr = getCoord( ptr, tox );
141 ptr = getCoord( ptr, toy );
142
143 if( process )
144 {
145 curx = relative ? curx + tox : tox;
146 cury = relative ? cury + toy : toy;
147
148 svgLineTo( curx, cury );
149 }
150 else
151 svgLineTo( tox, toy, !relative );
152 break;
153 }
154 case 'h':
155 {
156 ptr = getCoord( ptr, tox );
157 if( process )
158 {
159 curx = curx + tox;
160 svgLineTo( curx, cury );
161 }
162 else
163 svgLineToHorizontal( tox, false );
164 break;
165 }
166 case 'H':
167 {
168 ptr = getCoord( ptr, tox );
169 if( process )
170 {
171 curx = tox;
172 svgLineTo( curx, cury );
173 }
174 else
175 svgLineToHorizontal( tox );
176 break;
177 }
178 case 'v':
179 {
180 ptr = getCoord( ptr, toy );
181 if( process )
182 {
183 cury = cury + toy;
184 svgLineTo( curx, cury );
185 }
186 else
187 svgLineToVertical( toy, false );
188 break;
189 }
190 case 'V':
191 {
192 ptr = getCoord( ptr, toy );
193 if( process )
194 {
195 cury = toy;
196 svgLineTo( curx, cury );
197 }
198 else
199 svgLineToVertical( toy );
200 break;
201 }
202 case 'z':
203 case 'Z':
204 {
205 // reset curx, cury for next path
206 if( process )
207 {
208 curx = subpathx;
209 cury = subpathy;
210 }
211 svgClosePath();
212 break;
213 }
214 case 'c':
215 relative = true;
216 case 'C':
217 {
218 ptr = getCoord( ptr, x1 );
219 ptr = getCoord( ptr, y1 );
220 ptr = getCoord( ptr, x2 );
221 ptr = getCoord( ptr, y2 );
222 ptr = getCoord( ptr, tox );
223 ptr = getCoord( ptr, toy );
224
225 if( process )
226 {
227 px1 = relative ? curx + x1 : x1;
228 py1 = relative ? cury + y1 : y1;
229 px2 = relative ? curx + x2 : x2;
230 py2 = relative ? cury + y2 : y2;
231 px3 = relative ? curx + tox : tox;
232 py3 = relative ? cury + toy : toy;
233
234 svgCurveToCubic( px1, py1, px2, py2, px3, py3 );
235
236 contrlx = relative ? curx + x2 : x2;
237 contrly = relative ? cury + y2 : y2;
238 curx = relative ? curx + tox : tox;
239 cury = relative ? cury + toy : toy;
240 }
241 else
242 svgCurveToCubic( x1, y1, x2, y2, tox, toy, !relative );
243
244 break;
245 }
246 case 's':
247 relative = true;
248 case 'S':
249 {
250 ptr = getCoord( ptr, x2 );
251 ptr = getCoord( ptr, y2 );
252 ptr = getCoord( ptr, tox );
253 ptr = getCoord( ptr, toy );
254 if( !( lastCommand == 'c' || lastCommand == 'C' ||
255 lastCommand == 's' || lastCommand == 'S' ) )
256 {
257 contrlx = curx;
258 contrly = cury;
259 }
260
261
262 if( process )
263 {
264 px1 = 2 * curx - contrlx;
265 py1 = 2 * cury - contrly;
266 px2 = relative ? curx + x2 : x2;
267 py2 = relative ? cury + y2 : y2;
268 px3 = relative ? curx + tox : tox;
269 py3 = relative ? cury + toy : toy;
270
271 svgCurveToCubic( px1, py1, px2, py2, px3, py3 );
272
273 contrlx = relative ? curx + x2 : x2;
274 contrly = relative ? cury + y2 : y2;
275 curx = relative ? curx + tox : tox;
276 cury = relative ? cury + toy : toy;
277 }
278 else
279 svgCurveToCubicSmooth( x2, y2, tox, toy, !relative );
280 break;
281 }
282 case 'q':
283 relative = true;
284 case 'Q':
285 {
286 ptr = getCoord( ptr, x1 );
287 ptr = getCoord( ptr, y1 );
288 ptr = getCoord( ptr, tox );
289 ptr = getCoord( ptr, toy );
290
291 if( process )
292 {
293 px1 = relative ? (curx + 2 * (x1 + curx)) * (1.0 / 3.0) : (curx + 2 * x1) * (1.0 / 3.0);
294 py1 = relative ? (cury + 2 * (y1 + cury)) * (1.0 / 3.0) : (cury + 2 * y1) * (1.0 / 3.0);
295 px2 = relative ? ((curx + tox) + 2 * (x1 + curx)) * (1.0 / 3.0) : (tox + 2 * x1) * (1.0 / 3.0);
296 py2 = relative ? ((cury + toy) + 2 * (y1 + cury)) * (1.0 / 3.0) : (toy + 2 * y1) * (1.0 / 3.0);
297 px3 = relative ? curx + tox : tox;
298 py3 = relative ? cury + toy : toy;
299
300 svgCurveToCubic( px1, py1, px2, py2, px3, py3 );
301
302 contrlx = relative ? curx + x1 : x1;
303 contrly = relative ? cury + y1 : y1;
304 curx = relative ? curx + tox : tox;
305 cury = relative ? cury + toy : toy;
306 }
307 else
308 svgCurveToQuadratic( x1, y1, tox, toy, !relative );
309 break;
310 }
311 case 't':
312 relative = true;
313 case 'T':
314 {
315 ptr = getCoord(ptr, tox);
316 ptr = getCoord(ptr, toy);
317 if( !( lastCommand == 'q' || lastCommand == 'Q' ||
318 lastCommand == 't' || lastCommand == 'T' ) )
319 {
320 contrlx = curx;
321 contrly = cury;
322 }
323
324 if( process )
325 {
326 xc = 2 * curx - contrlx;
327 yc = 2 * cury - contrly;
328
329 px1 = (curx + 2 * xc) * (1.0 / 3.0);
330 py1 = (cury + 2 * yc) * (1.0 / 3.0);
331 px2 = relative ? ((curx + tox) + 2 * xc) * (1.0 / 3.0) : (tox + 2 * xc) * (1.0 / 3.0);
332 py2 = relative ? ((cury + toy) + 2 * yc) * (1.0 / 3.0) : (toy + 2 * yc) * (1.0 / 3.0);
333 px3 = relative ? curx + tox : tox;
334 py3 = relative ? cury + toy : toy;
335
336 svgCurveToCubic( px1, py1, px2, py2, px3, py3 );
337
338 contrlx = xc;
339 contrly = yc;
340 curx = relative ? curx + tox : tox;
341 cury = relative ? cury + toy : toy;
342 }
343 else
344 svgCurveToQuadraticSmooth( tox, toy, !relative );
345 break;
346 }
347 case 'a':
348 relative = true;
349 case 'A':
350 {
351 bool largeArc, sweep;
352 double angle, rx, ry;
353 ptr = getCoord( ptr, rx );
354 ptr = getCoord( ptr, ry );
355 ptr = getCoord( ptr, angle );
356 ptr = getCoord( ptr, tox );
357 largeArc = tox == 1;
358 ptr = getCoord( ptr, tox );
359 sweep = tox == 1;
360 ptr = getCoord( ptr, tox );
361 ptr = getCoord( ptr, toy );
362
363 // Spec: radii are nonnegative numbers
364 rx = fabs(rx);
365 ry = fabs(ry);
366
367 if( process )
368 calculateArc( relative, curx, cury, angle, tox, toy, rx, ry, largeArc, sweep );
369 else
370 svgArcTo( tox, toy, rx, ry, angle, largeArc, sweep, !relative );
371 }
372 default:
373 {
374 // when svg parser is used for a parsing an odf path an unknown command
375 // can be encountered, so we stop parsing here
376 kdDebug() << "SVGPathParser::parseSVG(): unknown command \"" << command << "\"" << endl;
377 return;
378 }
379 }
380
381 lastCommand = command;
382
383 if(*ptr == '+' || *ptr == '-' || (*ptr >= '0' && *ptr <= '9'))
384 {
385 // there are still coords in this command
386 if(command == 'M')
387 command = 'L';
388 else if(command == 'm')
389 command = 'l';
390 }
391 else
392 command = *(ptr++);
393
394 if( lastCommand != 'C' && lastCommand != 'c' &&
395 lastCommand != 'S' && lastCommand != 's' &&
396 lastCommand != 'Q' && lastCommand != 'q' &&
397 lastCommand != 'T' && lastCommand != 't')
398 {
399 contrlx = curx;
400 contrly = cury;
401 }
402 }
403 }
404}
405
406// This works by converting the SVG arc to "simple" beziers.
407// For each bezier found a svgToCurve call is done.
408// Adapted from Niko's code in tdelibs/tdecore/svgicons.
409// Maybe this can serve in some shared lib? (Rob)
410void
411SVGPathParser::calculateArc(bool relative, double &curx, double &cury, double angle, double x, double y, double r1, double r2, bool largeArcFlag, bool sweepFlag)
412{
413 double sin_th, cos_th;
414 double a00, a01, a10, a11;
415 double x0, y0, x1, y1, xc, yc;
416 double d, sfactor, sfactor_sq;
417 double th0, th1, th_arc;
418 int i, n_segs;
419
420 sin_th = sin(angle * (M_PI / 180.0));
421 cos_th = cos(angle * (M_PI / 180.0));
422
423 double dx;
424
425 if(!relative)
426 dx = (curx - x) / 2.0;
427 else
428 dx = -x / 2.0;
429
430 double dy;
431
432 if(!relative)
433 dy = (cury - y) / 2.0;
434 else
435 dy = -y / 2.0;
436
437 double _x1 = cos_th * dx + sin_th * dy;
438 double _y1 = -sin_th * dx + cos_th * dy;
439 double Pr1 = r1 * r1;
440 double Pr2 = r2 * r2;
441 double Px = _x1 * _x1;
442 double Py = _y1 * _y1;
443
444 // Spec : check if radii are large enough
445 double check = Px / Pr1 + Py / Pr2;
446 if(check > 1)
447 {
448 r1 = r1 * sqrt(check);
449 r2 = r2 * sqrt(check);
450 }
451
452 a00 = cos_th / r1;
453 a01 = sin_th / r1;
454 a10 = -sin_th / r2;
455 a11 = cos_th / r2;
456
457 x0 = a00 * curx + a01 * cury;
458 y0 = a10 * curx + a11 * cury;
459
460 if(!relative)
461 x1 = a00 * x + a01 * y;
462 else
463 x1 = a00 * (curx + x) + a01 * (cury + y);
464
465 if(!relative)
466 y1 = a10 * x + a11 * y;
467 else
468 y1 = a10 * (curx + x) + a11 * (cury + y);
469
470 /* (x0, y0) is current point in transformed coordinate space.
471 (x1, y1) is new point in transformed coordinate space.
472
473 The arc fits a unit-radius circle in this space.
474 */
475
476 d = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0);
477
478 sfactor_sq = 1.0 / d - 0.25;
479
480 if(sfactor_sq < 0)
481 sfactor_sq = 0;
482
483 sfactor = sqrt(sfactor_sq);
484
485 if(sweepFlag == largeArcFlag)
486 sfactor = -sfactor;
487
488 xc = 0.5 * (x0 + x1) - sfactor * (y1 - y0);
489 yc = 0.5 * (y0 + y1) + sfactor * (x1 - x0);
490
491 /* (xc, yc) is center of the circle. */
492 th0 = atan2(y0 - yc, x0 - xc);
493 th1 = atan2(y1 - yc, x1 - xc);
494
495 th_arc = th1 - th0;
496 if(th_arc < 0 && sweepFlag)
497 th_arc += 2 * M_PI;
498 else if(th_arc > 0 && !sweepFlag)
499 th_arc -= 2 * M_PI;
500
501 n_segs = (int) (int) ceil(fabs(th_arc / (M_PI * 0.5 + 0.001)));
502
503 for(i = 0; i < n_segs; i++)
504 {
505 {
506 double sin_th, cos_th;
507 double a00, a01, a10, a11;
508 double x1, y1, x2, y2, x3, y3;
509 double t;
510 double th_half;
511
512 double _th0 = th0 + i * th_arc / n_segs;
513 double _th1 = th0 + (i + 1) * th_arc / n_segs;
514
515 sin_th = sin(angle * (M_PI / 180.0));
516 cos_th = cos(angle * (M_PI / 180.0));
517
518 /* inverse transform compared with rsvg_path_arc */
519 a00 = cos_th * r1;
520 a01 = -sin_th * r2;
521 a10 = sin_th * r1;
522 a11 = cos_th * r2;
523
524 th_half = 0.5 * (_th1 - _th0);
525 t = (8.0 / 3.0) * sin(th_half * 0.5) * sin(th_half * 0.5) / sin(th_half);
526 x1 = xc + cos(_th0) - t * sin(_th0);
527 y1 = yc + sin(_th0) + t * cos(_th0);
528 x3 = xc + cos(_th1);
529 y3 = yc + sin(_th1);
530 x2 = x3 + t * sin(_th1);
531 y2 = y3 - t * cos(_th1);
532
533 svgCurveToCubic( a00 * x1 + a01 * y1, a10 * x1 + a11 * y1, a00 * x2 + a01 * y2, a10 * x2 + a11 * y2, a00 * x3 + a01 * y3, a10 * x3 + a11 * y3 );
534 }
535 }
536
537 if(!relative)
538 curx = x;
539 else
540 curx += x;
541
542 if(!relative)
543 cury = y;
544 else
545 cury += y;
546}
547
548void
549SVGPathParser::svgLineToHorizontal( double, bool )
550{
551}
552
553void
554SVGPathParser::svgLineToVertical( double, bool )
555{
556}
557
558void
559SVGPathParser::svgCurveToCubicSmooth( double, double, double, double, bool )
560{
561}
562
563void
564SVGPathParser::svgCurveToQuadratic( double, double, double, double, bool )
565{
566}
567
568void
569SVGPathParser::svgCurveToQuadraticSmooth( double, double, bool )
570{
571}
572
573void
574SVGPathParser::svgArcTo( double, double, double, double, double, bool, bool, bool )
575{
576}
577