2006-12-03 Dimitris Glezos <dimitris@glezos.com>
[dia.git] / lib / dia_svg.c
blob760578309a03223d65024f225c065df711b727ad
1 /* dia -- an diagram creation/manipulation program
2 * Copyright (C) 1998 Alexander Larsson
4 * dia_svg.c -- Refactoring by Hans Breuer from :
6 * Custom Objects -- objects defined in XML rather than C.
7 * Copyright (C) 1999 James Henstridge.
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24 #include "config.h"
26 #include <string.h>
27 #include <stdlib.h>
29 #include <pango/pango-attributes.h>
31 #include "dia_svg.h"
33 /** Initialize a style object from another style object or defaults.
34 * @param gs An SVG style object to initialize.
35 * @param parent_style An SVG style object to copy values from, or NULL,
36 * in which case defaults will be used.
38 void
39 dia_svg_style_init(DiaSvgStyle *gs, DiaSvgStyle *parent_style)
41 g_return_if_fail (gs);
42 gs->stroke = parent_style ? parent_style->stroke : (-1);
43 gs->line_width = parent_style ? parent_style->line_width : 0.0;
44 gs->linestyle = parent_style ? parent_style->linestyle : LINESTYLE_SOLID;
45 gs->dashlength = parent_style ? parent_style->dashlength : 1;
46 gs->fill = parent_style ? parent_style->fill : (-1);
47 gs->linecap = parent_style ? parent_style->linecap : DIA_SVG_LINECAPS_DEFAULT;
48 gs->linejoin = parent_style ? parent_style->linejoin : DIA_SVG_LINEJOIN_DEFAULT;
49 gs->linestyle = parent_style ? parent_style->linestyle : DIA_SVG_LINESTYLE_DEFAULT;
50 gs->font = (parent_style && parent_style->font) ? dia_font_ref(parent_style->font) : NULL;
51 gs->font_height = parent_style ? parent_style->font_height : 0.8;
52 gs->alignment = parent_style ? parent_style->alignment : ALIGN_LEFT;
55 /** Copy style values from one SVG style object to another.
56 * @param dest SVG style object to copy to.
57 * @param src SVG style object to copy from.
59 void
60 dia_svg_style_copy(DiaSvgStyle *dest, DiaSvgStyle *src)
62 g_return_if_fail (dest && src);
64 dest->stroke = src->stroke;
65 dest->line_width = src->line_width;
66 dest->linestyle = src->linestyle;
67 dest->dashlength = src->dashlength;
68 dest->fill = src->fill;
69 if (dest->font)
70 dia_font_unref (dest->font);
71 dest->font = src->font ? dia_font_ref(src->font) : NULL;
72 dest->font_height = src->font_height;
73 dest->alignment = src->alignment;
76 /** Parse an SVG color description.
77 * @param color A place to store the color information (0RGB)
78 * @param str An SVG color description string to parse.
79 * @return TRUE if parsing was successful.
80 * Shouldn't we use an actual Dia Color object as return value?
81 * Would require that the DiaSvgStyle object uses that, too. If we did that,
82 * we could even return the color object directly, and we would be able to use
83 * >8 bits per channel.
84 * But we would not be able to handle named colors anymore ...
86 static gboolean
87 _parse_color(gint32 *color, const char *str)
89 if (str[0] == '#')
90 *color = strtol(str+1, NULL, 16) & 0xffffff;
91 else if (0 == strncmp(str, "none", 4))
92 *color = DIA_SVG_COLOUR_NONE;
93 else if (0 == strncmp(str, "foreground", 10) || 0 == strncmp(str, "fg", 2) ||
94 0 == strncmp(str, "inverse", 7))
95 *color = DIA_SVG_COLOUR_FOREGROUND;
96 else if (0 == strncmp(str, "background", 10) || 0 == strncmp(str, "bg", 2) ||
97 0 == strncmp(str, "default", 7))
98 *color = DIA_SVG_COLOUR_BACKGROUND;
99 else if (0 == strcmp(str, "text"))
100 *color = DIA_SVG_COLOUR_TEXT;
101 else if (0 == strncmp(str, "rgb(", 4)) {
102 int r = 0, g = 0, b = 0;
103 if (3 == sscanf (str+4, "%d,%d,%d", &r, &g, &b))
104 *color = ((r<<16) & 0xFF0000) | ((g<<8) & 0xFF00) | (b & 0xFF);
105 else
106 return FALSE;
107 } else {
108 /* Pango needs null terminated strings, so we just use it as a fallback */
109 PangoColor pc;
110 char* se = strchr (str, ';');
112 if (!se) {
113 if (pango_color_parse (&pc, str))
114 *color = ((pc.red >> 8) << 16) | ((pc.green >> 8) << 8) | (pc.blue >> 8);
115 else
116 return FALSE;
117 } else {
118 gchar* sz = g_strndup (str, se - str);
119 gboolean ret = pango_color_parse (&pc, str);
121 if (ret)
122 *color = ((pc.red >> 8) << 16) | ((pc.green >> 8) << 8) | (pc.blue >> 8);
123 g_free (sz);
124 return ret;
127 return TRUE;
130 enum
132 FONT_NAME_LENGTH_MAX = 40
135 /** This function not only parses the style attribute of the given node
136 * it also extracts some of the style properties directly.
137 * @param node An XML node to parse a style from.
138 * @param s The SVG style object to fill out. This should previously be
139 * initialized to some default values.
140 * @bug This function is way too long (213 lines). So dont touch it :)
142 void
143 dia_svg_parse_style(xmlNodePtr node, DiaSvgStyle *s)
145 xmlChar *str;
146 gchar temp[FONT_NAME_LENGTH_MAX+1]; /* font-family names will be limited to 40 characters */
147 int i = 0;
148 gboolean over = FALSE;
149 char *family = NULL, *style = NULL, *weight = NULL;
151 str = xmlGetProp(node, "style");
153 if (str) {
154 gchar *ptr = (gchar *)str;
155 while (ptr[0] != '\0') {
156 /* skip white space at start */
157 while (ptr[0] != '\0' && g_ascii_isspace(ptr[0])) ptr++;
158 if (ptr[0] == '\0') break;
160 if (!strncmp("font-family:", ptr, 12)) {
161 ptr += 12;
162 while ((ptr[0] != '\0') && g_ascii_isspace(ptr[0])) ptr++;
163 i = 0; over = FALSE;
164 while (ptr[0] != '\0' && ptr[0] != ';' && !over) {
165 if (i < FONT_NAME_LENGTH_MAX) {
166 temp[i] = ptr[0];
167 } else over = TRUE;
168 i++;
169 ptr++;
171 temp[i] = '\0';
173 if (!over) family = g_strdup(temp);
174 } else if (!strncmp("font-weight:", ptr, 12)) {
175 ptr += 12;
176 while ((ptr[0] != '\0') && g_ascii_isspace(ptr[0])) ptr++;
177 i = 0; over = FALSE;
178 while (ptr[0] != '\0' && ptr[0] != ';' && !over) {
179 if (i < FONT_NAME_LENGTH_MAX) {
180 temp[i] = ptr[0];
181 } else over = TRUE;
182 i++;
183 ptr++;
185 temp[i] = '\0';
187 if (!over) weight = g_strdup(temp);
188 } else if (!strncmp("font-style:", ptr, 11)) {
189 ptr += 11;
190 while ((ptr[0] != '\0') && g_ascii_isspace(ptr[0])) ptr++;
191 i = 0; over = FALSE;
192 while (ptr[0] != '\0' && ptr[0] != ';' && !over) {
193 if (i < FONT_NAME_LENGTH_MAX) {
194 temp[i] = ptr[0];
195 } else over = TRUE;
196 i++;
197 ptr++;
199 temp[i] = '\0';
201 if (!over) style = g_strdup(temp);
202 } else if (!strncmp("font-size:", ptr, 10)) {
203 ptr += 10;
204 while ((ptr[0] != '\0') && g_ascii_isspace(ptr[0])) ptr++;
205 i = 0; over = FALSE;
206 while (ptr[0] != '\0' && ptr[0] != ';' && !over) {
207 if (i < FONT_NAME_LENGTH_MAX) {
208 temp[i] = ptr[0];
209 } else over = TRUE;
210 i++;
211 ptr++;
213 temp[i] = '\0';
215 if (!over) {
216 s->font_height = g_ascii_strtod(temp, NULL);
218 } else if (!strncmp("text-anchor:", ptr, 12)) {
219 ptr += 12;
220 while ((ptr[0] != '\0') && g_ascii_isspace(ptr[0])) ptr++;
221 if (!strncmp(ptr, "start", 5))
222 s->alignment = ALIGN_LEFT;
223 else if (!strncmp(ptr, "end", 3))
224 s->alignment = ALIGN_RIGHT;
225 else if (!strncmp(ptr, "middle", 6))
226 s->alignment = ALIGN_CENTER;
228 } else if (!strncmp("stroke-width:", ptr, 13)) {
229 ptr += 13;
230 s->line_width = g_ascii_strtod(ptr, &ptr);
231 } else if (!strncmp("stroke:", ptr, 7)) {
232 ptr += 7;
233 while ((ptr[0] != '\0') && g_ascii_isspace(ptr[0])) ptr++;
234 if (ptr[0] == '\0') break;
236 _parse_color (&s->stroke, ptr);
237 } else if (!strncmp("fill:", ptr, 5)) {
238 ptr += 5;
239 while (ptr[0] != '\0' && g_ascii_isspace(ptr[0])) ptr++;
240 if (ptr[0] == '\0') break;
242 _parse_color (&s->fill, ptr);
243 } else if (!strncmp("stroke-linecap:", ptr, 15)) {
244 ptr += 15;
245 while (ptr[0] != '\0' && g_ascii_isspace(ptr[0])) ptr++;
246 if (ptr[0] == '\0') break;
248 if (!strncmp(ptr, "butt", 4))
249 s->linecap = LINECAPS_BUTT;
250 else if (!strncmp(ptr, "round", 5))
251 s->linecap = LINECAPS_ROUND;
252 else if (!strncmp(ptr, "square", 6) || !strncmp(ptr, "projecting", 10))
253 s->linecap = LINECAPS_PROJECTING;
254 else if (!strncmp(ptr, "default", 7))
255 s->linecap = DIA_SVG_LINECAPS_DEFAULT;
256 } else if (!strncmp("stroke-linejoin:", ptr, 16)) {
257 ptr += 16;
258 while (ptr[0] != '\0' && g_ascii_isspace(ptr[0])) ptr++;
259 if (ptr[0] == '\0') break;
261 if (!strncmp(ptr, "miter", 5))
262 s->linejoin = LINEJOIN_MITER;
263 else if (!strncmp(ptr, "round", 5))
264 s->linejoin = LINEJOIN_ROUND;
265 else if (!strncmp(ptr, "bevel", 5))
266 s->linejoin = LINEJOIN_BEVEL;
267 else if (!strncmp(ptr, "default", 7))
268 s->linejoin = DIA_SVG_LINEJOIN_DEFAULT;
269 } else if (!strncmp("stroke-pattern:", ptr, 15)) {
270 ptr += 15;
271 while (ptr[0] != '\0' && g_ascii_isspace(ptr[0])) ptr++;
272 if (ptr[0] == '\0') break;
274 if (!strncmp(ptr, "solid", 5))
275 s->linestyle = LINESTYLE_SOLID;
276 else if (!strncmp(ptr, "dashed", 6))
277 s->linestyle = LINESTYLE_DASHED;
278 else if (!strncmp(ptr, "dash-dot", 8))
279 s->linestyle = LINESTYLE_DASH_DOT;
280 else if (!strncmp(ptr, "dash-dot-dot", 12))
281 s->linestyle = LINESTYLE_DASH_DOT_DOT;
282 else if (!strncmp(ptr, "dotted", 6))
283 s->linestyle = LINESTYLE_DOTTED;
284 else if (!strncmp(ptr, "default", 7))
285 s->linestyle = DIA_SVG_LINESTYLE_DEFAULT;
286 /* XXX: deal with a real pattern */
287 } else if (!strncmp("stroke-dashlength:", ptr, 18)) {
288 ptr += 18;
289 while (ptr[0] != '\0' && g_ascii_isspace(ptr[0])) ptr++;
290 if (ptr[0] == '\0') break;
292 if (!strncmp(ptr, "default", 7))
293 s->dashlength = 1.0;
294 else {
295 s->dashlength = g_ascii_strtod(ptr, &ptr);
297 } else if (!strncmp("stroke-dasharray:", ptr, 17)) {
298 /* FIXME? do we need to read an array here (not clear from
299 * Dia's usage); do we need to set the linestyle depending
300 * on the array's size ? --hb
302 s->linestyle = LINESTYLE_DASHED;
303 ptr += 17;
304 while (ptr[0] != '\0' && g_ascii_isspace(ptr[0])) ptr++;
305 if (ptr[0] == '\0') break;
307 if (!strncmp(ptr, "default", 7))
308 s->dashlength = 1.0;
309 else {
310 s->dashlength = g_ascii_strtod(ptr, &ptr);
314 /* skip up to the next attribute */
315 while (ptr[0] != '\0' && ptr[0] != ';' && ptr[0] != '\n') ptr++;
316 if (ptr[0] != '\0') ptr++;
318 xmlFree(str);
321 /* ugly svg variations, it is allowed to give style properties without
322 * the style attribute, i.e. direct attributes
324 str = xmlGetProp(node, "fill");
325 if (str) {
326 _parse_color (&s->fill, str);
327 xmlFree(str);
329 str = xmlGetProp(node, "stroke");
330 if (str) {
331 _parse_color (&s->stroke, str);
332 xmlFree(str);
334 str = xmlGetProp(node, "stroke-width");
335 if (str) {
336 s->line_width = g_ascii_strtod(str, NULL);
337 xmlFree(str);
340 if (family || style || weight) {
341 if (s->font)
342 dia_font_unref (s->font);
343 s->font = dia_font_new_from_style(DIA_FONT_SANS,s->font_height/*bogus*/);
344 if (family) {
345 dia_font_set_any_family(s->font,family);
346 g_free(family);
348 if (style) {
349 dia_font_set_slant_from_string(s->font,style);
350 g_free(style);
352 if (weight) {
353 dia_font_set_weight_from_string(s->font,weight);
354 g_free(weight);
359 /** Parse a SVG description of an arc segment.
360 * Code stolen from (and adapted)
361 * http://www.inkscape.org/doc/doxygen/html/svg-path_8cpp.php#a7
362 * which may have got it from rsvg, hope it is correct ;)
363 * @param points
364 * @param xc
365 * @param yc
366 * @param th0
367 * @param th1
368 * @param rx
369 * @param ry
370 * @param x_axis_rotation
371 * @param last_p2
372 * If you want the description of the algorithm read the SVG specs.
374 static void
375 _path_arc_segment(GArray* points,
376 real xc, real yc,
377 real th0, real th1,
378 real rx, real ry,
379 real x_axis_rotation,
380 Point *last_p2)
382 BezPoint bez;
383 real sin_th, cos_th;
384 real a00, a01, a10, a11;
385 real x1, y1, x2, y2, x3, y3;
386 real t;
387 real th_half;
389 sin_th = sin (x_axis_rotation * (M_PI / 180.0));
390 cos_th = cos (x_axis_rotation * (M_PI / 180.0));
391 /* inverse transform compared with rsvg_path_arc */
392 a00 = cos_th * rx;
393 a01 = -sin_th * ry;
394 a10 = sin_th * rx;
395 a11 = cos_th * ry;
397 th_half = 0.5 * (th1 - th0);
398 t = (8.0 / 3.0) * sin(th_half * 0.5) * sin(th_half * 0.5) / sin(th_half);
399 x1 = xc + cos (th0) - t * sin (th0);
400 y1 = yc + sin (th0) + t * cos (th0);
401 x3 = xc + cos (th1);
402 y3 = yc + sin (th1);
403 x2 = x3 + t * sin (th1);
404 y2 = y3 - t * cos (th1);
406 bez.type = BEZ_CURVE_TO;
407 bez.p1.x = a00 * x1 + a01 * y1;
408 bez.p1.y = a10 * x1 + a11 * y1;
409 bez.p2.x = a00 * x2 + a01 * y2;
410 bez.p2.y = a10 * x2 + a11 * y2;
411 bez.p3.x = a00 * x3 + a01 * y3;
412 bez.p3.y = a10 * x3 + a11 * y3;
414 *last_p2 = bez.p2;
416 g_array_append_val(points, bez);
419 /** Parse an SVG description of a full arc.
420 * @param points
421 * @param cpx
422 * @param cpy
423 * @param rx
424 * @param ry
425 * @param x_axis_rotation
426 * @param large_arc_flag
427 * @param sweep_flag
428 * @param x
429 * @param y
430 * @param last_p2
431 * @bug Also here don't know what the parameters mean.
433 static void
434 _path_arc(GArray *points, double cpx, double cpy,
435 double rx, double ry, double x_axis_rotation,
436 int large_arc_flag, int sweep_flag,
437 double x, double y,
438 Point *last_p2)
440 double sin_th, cos_th;
441 double a00, a01, a10, a11;
442 double x0, y0, x1, y1, xc, yc;
443 double d, sfactor, sfactor_sq;
444 double th0, th1, th_arc;
445 double px, py, pl;
446 int i, n_segs;
448 sin_th = sin (x_axis_rotation * (M_PI / 180.0));
449 cos_th = cos (x_axis_rotation * (M_PI / 180.0));
452 * Correction of out-of-range radii as described in Appendix F.6.6:
454 * 1. Ensure radii are non-zero (Done?).
455 * 2. Ensure that radii are positive.
456 * 3. Ensure that radii are large enough.
458 if(rx < 0.0) rx = -rx;
459 if(ry < 0.0) ry = -ry;
461 px = cos_th * (cpx - x) * 0.5 + sin_th * (cpy - y) * 0.5;
462 py = cos_th * (cpy - y) * 0.5 - sin_th * (cpx - x) * 0.5;
463 pl = (px * px) / (rx * rx) + (py * py) / (ry * ry);
465 if(pl > 1.0)
467 pl = sqrt(pl);
468 rx *= pl;
469 ry *= pl;
472 /* Proceed with computations as described in Appendix F.6.5 */
474 a00 = cos_th / rx;
475 a01 = sin_th / rx;
476 a10 = -sin_th / ry;
477 a11 = cos_th / ry;
478 x0 = a00 * cpx + a01 * cpy;
479 y0 = a10 * cpx + a11 * cpy;
480 x1 = a00 * x + a01 * y;
481 y1 = a10 * x + a11 * y;
482 /* (x0, y0) is current point in transformed coordinate space.
483 (x1, y1) is new point in transformed coordinate space.
485 The arc fits a unit-radius circle in this space.
487 d = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0);
488 sfactor_sq = 1.0 / d - 0.25;
489 if (sfactor_sq < 0) sfactor_sq = 0;
490 sfactor = sqrt (sfactor_sq);
491 if (sweep_flag == large_arc_flag) sfactor = -sfactor;
492 xc = 0.5 * (x0 + x1) - sfactor * (y1 - y0);
493 yc = 0.5 * (y0 + y1) + sfactor * (x1 - x0);
494 /* (xc, yc) is center of the circle. */
496 th0 = atan2 (y0 - yc, x0 - xc);
497 th1 = atan2 (y1 - yc, x1 - xc);
499 th_arc = th1 - th0;
500 if (th_arc < 0 && sweep_flag)
501 th_arc += 2 * M_PI;
502 else if (th_arc > 0 && !sweep_flag)
503 th_arc -= 2 * M_PI;
505 n_segs = (int) ceil (fabs (th_arc / (M_PI * 0.5 + 0.001)));
507 for (i = 0; i < n_segs; i++) {
508 _path_arc_segment(points, xc, yc,
509 th0 + i * th_arc / n_segs,
510 th0 + (i + 1) * th_arc / n_segs,
511 rx, ry, x_axis_rotation,
512 last_p2);
516 /* routine to chomp off the start of the string */
517 #define path_chomp(path) while (path[0]!='\0'&&strchr(" \t\n\r,", path[0])) path++
519 /** Takes SVG path content and converts it in an array of BezPoint.
521 * SVG pathes can contain multiple MOVE_TO commands while Dia's bezier
522 * object can only contain one so you may need to call this function
523 * multiple times.
525 * @param path_str A string describing an SVG path.
526 * @param unparsed The position in `path_str' where parsing ended, or NULL if
527 * the string was completely parsed. This should be used for
528 * calling the function until it is fully parsed.
529 * @param closed Whether the path was closed.
530 * @returns Array of BezPoint objects, or NULL if an error occurred.
531 * The caller is responsible for freeing the array.
532 * @bug This function is way too long (324 lines). So dont touch it. please!
533 * Shouldn't we try to turn straight lines, simple arc, polylines and
534 * zigzaglines into their appropriate objects? Could either be done by
535 * returning an object or by having functions that try parsing as
536 * specific simple paths.
537 * NOPE: Dia is capable to handle beziers and the file has given us some so WHY should be break it in to pieces ???
539 GArray*
540 dia_svg_parse_path(const gchar *path_str, gchar **unparsed, gboolean *closed)
542 enum {
543 PATH_MOVE, PATH_LINE, PATH_HLINE, PATH_VLINE, PATH_CURVE,
544 PATH_SMOOTHCURVE, PATH_ARC, PATH_CLOSE } last_type = PATH_MOVE;
545 Point last_open = {0.0, 0.0};
546 Point last_point = {0.0, 0.0};
547 Point last_control = {0.0, 0.0};
548 gboolean last_relative = FALSE;
549 GArray *points;
550 BezPoint bez;
551 gchar *path = (gchar *)path_str;
552 gboolean need_next_element = FALSE;
554 *closed = FALSE;
555 *unparsed = NULL;
557 points = g_array_new(FALSE, FALSE, sizeof(BezPoint));
558 g_array_set_size(points, 0);
560 path_chomp(path);
561 while (path[0] != '\0') {
562 #ifdef DEBUG_CUSTOM
563 g_print("Path: %s\n", path);
564 #endif
565 /* check for a new command */
566 switch (path[0]) {
567 case 'M':
568 if (points->len > 0) {
569 need_next_element = TRUE;
570 goto MORETOPARSE;
572 path++;
573 path_chomp(path);
574 last_type = PATH_MOVE;
575 last_relative = FALSE;
576 break;
577 case 'm':
578 if (points->len > 0)
579 goto MORETOPARSE;
580 path++;
581 path_chomp(path);
582 last_type = PATH_MOVE;
583 last_relative = TRUE;
584 break;
585 case 'L':
586 path++;
587 path_chomp(path);
588 last_type = PATH_LINE;
589 last_relative = FALSE;
590 break;
591 case 'l':
592 path++;
593 path_chomp(path);
594 last_type = PATH_LINE;
595 last_relative = TRUE;
596 break;
597 case 'H':
598 path++;
599 path_chomp(path);
600 last_type = PATH_HLINE;
601 last_relative = FALSE;
602 break;
603 case 'h':
604 path++;
605 path_chomp(path);
606 last_type = PATH_HLINE;
607 last_relative = TRUE;
608 break;
609 case 'V':
610 path++;
611 path_chomp(path);
612 last_type = PATH_VLINE;
613 last_relative = FALSE;
614 break;
615 case 'v':
616 path++;
617 path_chomp(path);
618 last_type = PATH_VLINE;
619 last_relative = TRUE;
620 break;
621 case 'C':
622 path++;
623 path_chomp(path);
624 last_type = PATH_CURVE;
625 last_relative = FALSE;
626 break;
627 case 'c':
628 path++;
629 path_chomp(path);
630 last_type = PATH_CURVE;
631 last_relative = TRUE;
632 break;
633 case 'S':
634 path++;
635 path_chomp(path);
636 last_type = PATH_SMOOTHCURVE;
637 last_relative = FALSE;
638 break;
639 case 's':
640 path++;
641 path_chomp(path);
642 last_type = PATH_SMOOTHCURVE;
643 last_relative = TRUE;
644 break;
645 case 'Z':
646 case 'z':
647 path++;
648 path_chomp(path);
649 last_type = PATH_CLOSE;
650 last_relative = FALSE;
651 break;
652 case 'A':
653 path++;
654 path_chomp(path);
655 last_type = PATH_ARC;
656 last_relative = FALSE;
657 break;
658 case 'a':
659 path++;
660 path_chomp(path);
661 last_type = PATH_ARC;
662 last_relative = TRUE;
663 break;
664 case '0':
665 case '1':
666 case '2':
667 case '3':
668 case '4':
669 case '5':
670 case '6':
671 case '7':
672 case '8':
673 case '9':
674 case '.':
675 case '+':
676 case '-':
677 if (last_type == PATH_CLOSE) {
678 g_warning("parse_path: argument given for implicite close path");
679 /* consume one number so we don't fall into an infinite loop */
680 while (path != '\0' && strchr("0123456789.+-", path[0])) path++;
681 path_chomp(path);
682 *closed = TRUE;
683 need_next_element = TRUE;
684 goto MORETOPARSE;
686 break;
687 default:
688 g_warning("unsupported path code '%c'", path[0]);
689 path++;
690 path_chomp(path);
691 break;
694 /* actually parse the path component */
695 switch (last_type) {
696 case PATH_MOVE:
697 bez.type = BEZ_MOVE_TO;
698 bez.p1.x = g_ascii_strtod(path, &path);
699 path_chomp(path);
700 bez.p1.y = g_ascii_strtod(path, &path);
701 path_chomp(path);
702 if (last_relative) {
703 bez.p1.x += last_point.x;
704 bez.p1.y += last_point.y;
706 last_point = bez.p1;
707 last_control = bez.p1;
708 last_open = bez.p1;
709 g_array_append_val(points, bez);
710 break;
711 case PATH_LINE:
712 bez.type = BEZ_LINE_TO;
713 bez.p1.x = g_ascii_strtod(path, &path);
714 path_chomp(path);
715 bez.p1.y = g_ascii_strtod(path, &path);
716 path_chomp(path);
717 if (last_relative) {
718 bez.p1.x += last_point.x;
719 bez.p1.y += last_point.y;
721 last_point = bez.p1;
722 last_control = bez.p1;
724 g_array_append_val(points, bez);
725 break;
726 case PATH_HLINE:
727 bez.type = BEZ_LINE_TO;
728 bez.p1.x = g_ascii_strtod(path, &path);
729 path_chomp(path);
730 bez.p1.y = last_point.y;
731 if (last_relative)
732 bez.p1.x += last_point.x;
733 last_point = bez.p1;
734 last_control = bez.p1;
736 g_array_append_val(points, bez);
737 break;
738 case PATH_VLINE:
739 bez.type = BEZ_LINE_TO;
740 bez.p1.x = last_point.x;
741 bez.p1.y = g_ascii_strtod(path, &path);
742 path_chomp(path);
743 if (last_relative)
744 bez.p1.y += last_point.y;
745 last_point = bez.p1;
746 last_control = bez.p1;
748 g_array_append_val(points, bez);
749 break;
750 case PATH_CURVE:
751 bez.type = BEZ_CURVE_TO;
752 bez.p1.x = g_ascii_strtod(path, &path);
753 path_chomp(path);
754 bez.p1.y = g_ascii_strtod(path, &path);
755 path_chomp(path);
756 bez.p2.x = g_ascii_strtod(path, &path);
757 path_chomp(path);
758 bez.p2.y = g_ascii_strtod(path, &path);
759 path_chomp(path);
760 bez.p3.x = g_ascii_strtod(path, &path);
761 path_chomp(path);
762 bez.p3.y = g_ascii_strtod(path, &path);
763 path_chomp(path);
764 if (last_relative) {
765 bez.p1.x += last_point.x;
766 bez.p1.y += last_point.y;
767 bez.p2.x += last_point.x;
768 bez.p2.y += last_point.y;
769 bez.p3.x += last_point.x;
770 bez.p3.y += last_point.y;
772 last_point = bez.p3;
773 last_control = bez.p2;
775 g_array_append_val(points, bez);
776 break;
777 case PATH_SMOOTHCURVE:
778 bez.type = BEZ_CURVE_TO;
779 bez.p1.x = 2 * last_point.x - last_control.x;
780 bez.p1.y = 2 * last_point.y - last_control.y;
781 bez.p2.x = g_ascii_strtod(path, &path);
782 path_chomp(path);
783 bez.p2.y = g_ascii_strtod(path, &path);
784 path_chomp(path);
785 bez.p3.x = g_ascii_strtod(path, &path);
786 path_chomp(path);
787 bez.p3.y = g_ascii_strtod(path, &path);
788 path_chomp(path);
789 if (last_relative) {
790 bez.p2.x += last_point.x;
791 bez.p2.y += last_point.y;
792 bez.p3.x += last_point.x;
793 bez.p3.y += last_point.y;
795 last_point = bez.p3;
796 last_control = bez.p2;
798 g_array_append_val(points, bez);
799 break;
800 case PATH_ARC :
802 real rx, ry;
803 real xrot;
804 int largearc, sweep;
805 Point dest, dest_c;
807 rx = g_ascii_strtod(path, &path);
808 path_chomp(path);
809 ry = g_ascii_strtod(path, &path);
810 path_chomp(path);
811 xrot = g_ascii_strtod(path, &path);
812 path_chomp(path);
814 largearc = (int)g_ascii_strtod(path, &path);
815 path_chomp(path);
816 sweep = (int)g_ascii_strtod(path, &path);
817 path_chomp(path);
819 dest.x = g_ascii_strtod(path, &path);
820 path_chomp(path);
821 dest.y = g_ascii_strtod(path, &path);
822 path_chomp(path);
824 if (last_relative) {
825 dest.x += last_point.x;
826 dest.y += last_point.y;
829 _path_arc (points, last_point.x, last_point.y,
830 rx, ry, xrot, largearc, sweep, dest.x, dest.y,
831 &dest_c);
832 last_point = dest;
833 last_control = dest_c;
835 break;
836 case PATH_CLOSE:
837 /* close the path with a line */
838 if (last_open.x != last_point.x || last_open.y != last_point.y) {
839 bez.type = BEZ_LINE_TO;
840 bez.p1 = last_open;
841 g_array_append_val(points, bez);
843 *closed = TRUE;
844 need_next_element = TRUE;
846 /* get rid of any ignorable characters */
847 path_chomp(path);
848 MORETOPARSE:
849 if (need_next_element) {
850 /* check if there really is mor to be parsed */
851 if (path[0] != 0)
852 *unparsed = path;
853 break; /* while */
857 /* avoid returning an array with only one point (I'd say the exporter
858 * producing such is rather broken, but *our* bezier creation code
859 * would crash on it.
861 if (points->len < 2) {
862 g_array_set_size(points, 0);
864 return points;