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.
29 #include <pango/pango-attributes.h>
34 dia_svg_style_init (DiaSvgStyle
*gs
, DiaSvgStyle
*parent_style
)
36 g_return_if_fail (gs
);
37 gs
->stroke
= parent_style
? parent_style
->stroke
: (-1);
38 gs
->line_width
= parent_style
? parent_style
->line_width
: 0.0;
39 gs
->linestyle
= parent_style
? parent_style
->linestyle
: LINESTYLE_SOLID
;
40 gs
->dashlength
= parent_style
? parent_style
->dashlength
: 1;
41 gs
->fill
= parent_style
? parent_style
->fill
: (-1);
42 gs
->font
= (parent_style
&& parent_style
->font
) ? dia_font_ref(parent_style
->font
) : NULL
;
43 gs
->font_height
= parent_style
? parent_style
->font_height
: 0.8;
44 gs
->alignment
= parent_style
? parent_style
->alignment
: ALIGN_LEFT
;
48 dia_svg_style_copy (DiaSvgStyle
*dest
, DiaSvgStyle
*src
)
50 g_return_if_fail (dest
&& src
);
52 dest
->stroke
= src
->stroke
;
53 dest
->line_width
= src
->line_width
;
54 dest
->linestyle
= src
->linestyle
;
55 dest
->dashlength
= src
->dashlength
;
56 dest
->fill
= src
->fill
;
58 dia_font_unref (dest
->font
);
59 dest
->font
= src
->font
? dia_font_ref(src
->font
) : NULL
;
60 dest
->font_height
= src
->font_height
;
61 dest
->alignment
= src
->alignment
;
65 _parse_color (gint32
*color
, const char *str
)
68 *color
= strtol(str
+1, NULL
, 16) & 0xffffff;
69 else if (0 == strncmp(str
, "none", 4))
70 *color
= DIA_SVG_COLOUR_NONE
;
71 else if (0 == strncmp(str
, "foreground", 10) || 0 == strncmp(str
, "fg", 2) ||
72 0 == strncmp(str
, "inverse", 7))
73 *color
= DIA_SVG_COLOUR_FOREGROUND
;
74 else if (0 == strncmp(str
, "background", 10) || 0 == strncmp(str
, "bg", 2) ||
75 0 == strncmp(str
, "default", 7))
76 *color
= DIA_SVG_COLOUR_BACKGROUND
;
77 else if (0 == strcmp(str
, "text"))
78 *color
= DIA_SVG_COLOUR_TEXT
;
79 else if (0 == strncmp(str
, "rgb(", 4)) {
80 int r
= 0, g
= 0, b
= 0;
81 if (3 == sscanf (str
+4, "%d,%d,%d", &r
, &g
, &b
))
82 *color
= ((r
<<16) & 0xFF0000) | ((g
<<8) & 0xFF00) | (b
& 0xFF);
86 /* Pango needs null terminated strings, so we just use it as a fallback */
88 char* se
= strchr (str
, ';');
91 if (pango_color_parse (&pc
, str
))
92 *color
= ((pc
.red
>> 8) << 16) | ((pc
.green
>> 8) << 8) | (pc
.blue
>> 8);
96 gchar
* sz
= g_strndup (str
, se
- str
);
97 gboolean ret
= pango_color_parse (&pc
, str
);
100 *color
= ((pc
.red
>> 8) << 16) | ((pc
.green
>> 8) << 8) | (pc
.blue
>> 8);
110 FONT_NAME_LENGTH_MAX
= 40
114 * This function not only parses the style attribute of the given node
115 * it also extracts some of the style properties directly.
118 dia_svg_parse_style(xmlNodePtr node
, DiaSvgStyle
*s
)
121 gchar temp
[FONT_NAME_LENGTH_MAX
+1]; /* font-family names will be limited to 40 characters */
123 gboolean over
= FALSE
;
124 char *family
= NULL
, *style
= NULL
, *weight
= NULL
;
126 str
= xmlGetProp(node
, "style");
129 gchar
*ptr
= (gchar
*)str
;
130 while (ptr
[0] != '\0') {
131 /* skip white space at start */
132 while (ptr
[0] != '\0' && g_ascii_isspace(ptr
[0])) ptr
++;
133 if (ptr
[0] == '\0') break;
135 if (!strncmp("font-family:", ptr
, 12)) {
137 while ((ptr
[0] != '\0') && g_ascii_isspace(ptr
[0])) ptr
++;
139 while (ptr
[0] != '\0' && ptr
[0] != ';' && !over
) {
140 if (i
< FONT_NAME_LENGTH_MAX
) {
148 if (!over
) family
= g_strdup(temp
);
149 } else if (!strncmp("font-weight:", ptr
, 12)) {
151 while ((ptr
[0] != '\0') && g_ascii_isspace(ptr
[0])) ptr
++;
153 while (ptr
[0] != '\0' && ptr
[0] != ';' && !over
) {
154 if (i
< FONT_NAME_LENGTH_MAX
) {
162 if (!over
) weight
= g_strdup(temp
);
163 } else if (!strncmp("font-style:", ptr
, 11)) {
165 while ((ptr
[0] != '\0') && g_ascii_isspace(ptr
[0])) ptr
++;
167 while (ptr
[0] != '\0' && ptr
[0] != ';' && !over
) {
168 if (i
< FONT_NAME_LENGTH_MAX
) {
176 if (!over
) style
= g_strdup(temp
);
177 } else if (!strncmp("font-size:", ptr
, 10)) {
179 while ((ptr
[0] != '\0') && g_ascii_isspace(ptr
[0])) ptr
++;
181 while (ptr
[0] != '\0' && ptr
[0] != ';' && !over
) {
182 if (i
< FONT_NAME_LENGTH_MAX
) {
191 s
->font_height
= g_ascii_strtod(temp
, NULL
);
193 } else if (!strncmp("text-anchor:", ptr
, 12)) {
195 while ((ptr
[0] != '\0') && g_ascii_isspace(ptr
[0])) ptr
++;
196 if (!strncmp(ptr
, "start", 5))
197 s
->alignment
= ALIGN_LEFT
;
198 else if (!strncmp(ptr
, "end", 3))
199 s
->alignment
= ALIGN_RIGHT
;
200 else if (!strncmp(ptr
, "middle", 6))
201 s
->alignment
= ALIGN_CENTER
;
203 } else if (!strncmp("stroke-width:", ptr
, 13)) {
205 s
->line_width
= g_ascii_strtod(ptr
, &ptr
);
206 } else if (!strncmp("stroke:", ptr
, 7)) {
208 while ((ptr
[0] != '\0') && g_ascii_isspace(ptr
[0])) ptr
++;
209 if (ptr
[0] == '\0') break;
211 _parse_color (&s
->stroke
, ptr
);
212 } else if (!strncmp("fill:", ptr
, 5)) {
214 while (ptr
[0] != '\0' && g_ascii_isspace(ptr
[0])) ptr
++;
215 if (ptr
[0] == '\0') break;
217 _parse_color (&s
->fill
, ptr
);
218 } else if (!strncmp("stroke-linecap:", ptr
, 15)) {
220 while (ptr
[0] != '\0' && g_ascii_isspace(ptr
[0])) ptr
++;
221 if (ptr
[0] == '\0') break;
223 if (!strncmp(ptr
, "butt", 4))
224 s
->linecap
= LINECAPS_BUTT
;
225 else if (!strncmp(ptr
, "round", 5))
226 s
->linecap
= LINECAPS_ROUND
;
227 else if (!strncmp(ptr
, "square", 6) || !strncmp(ptr
, "projecting", 10))
228 s
->linecap
= LINECAPS_PROJECTING
;
229 else if (!strncmp(ptr
, "default", 7))
230 s
->linecap
= DIA_SVG_LINECAPS_DEFAULT
;
231 } else if (!strncmp("stroke-linejoin:", ptr
, 16)) {
233 while (ptr
[0] != '\0' && g_ascii_isspace(ptr
[0])) ptr
++;
234 if (ptr
[0] == '\0') break;
236 if (!strncmp(ptr
, "miter", 5))
237 s
->linejoin
= LINEJOIN_MITER
;
238 else if (!strncmp(ptr
, "round", 5))
239 s
->linejoin
= LINEJOIN_ROUND
;
240 else if (!strncmp(ptr
, "bevel", 5))
241 s
->linejoin
= LINEJOIN_BEVEL
;
242 else if (!strncmp(ptr
, "default", 7))
243 s
->linejoin
= DIA_SVG_LINEJOIN_DEFAULT
;
244 } else if (!strncmp("stroke-pattern:", ptr
, 15)) {
246 while (ptr
[0] != '\0' && g_ascii_isspace(ptr
[0])) ptr
++;
247 if (ptr
[0] == '\0') break;
249 if (!strncmp(ptr
, "solid", 5))
250 s
->linestyle
= LINESTYLE_SOLID
;
251 else if (!strncmp(ptr
, "dashed", 6))
252 s
->linestyle
= LINESTYLE_DASHED
;
253 else if (!strncmp(ptr
, "dash-dot", 8))
254 s
->linestyle
= LINESTYLE_DASH_DOT
;
255 else if (!strncmp(ptr
, "dash-dot-dot", 12))
256 s
->linestyle
= LINESTYLE_DASH_DOT_DOT
;
257 else if (!strncmp(ptr
, "dotted", 6))
258 s
->linestyle
= LINESTYLE_DOTTED
;
259 else if (!strncmp(ptr
, "default", 7))
260 s
->linestyle
= DIA_SVG_LINESTYLE_DEFAULT
;
261 /* XXX: deal with a real pattern */
262 } else if (!strncmp("stroke-dashlength:", ptr
, 18)) {
264 while (ptr
[0] != '\0' && g_ascii_isspace(ptr
[0])) ptr
++;
265 if (ptr
[0] == '\0') break;
267 if (!strncmp(ptr
, "default", 7))
270 s
->dashlength
= g_ascii_strtod(ptr
, &ptr
);
272 } else if (!strncmp("stroke-dasharray:", ptr
, 17)) {
273 /* FIXME? do we need to read an array here (not clear from
274 * Dia's usage); do we need to set the linestyle depending
275 * on the array's size ? --hb
277 s
->linestyle
= LINESTYLE_DASHED
;
279 while (ptr
[0] != '\0' && g_ascii_isspace(ptr
[0])) ptr
++;
280 if (ptr
[0] == '\0') break;
282 if (!strncmp(ptr
, "default", 7))
285 s
->dashlength
= g_ascii_strtod(ptr
, &ptr
);
289 /* skip up to the next attribute */
290 while (ptr
[0] != '\0' && ptr
[0] != ';' && ptr
[0] != '\n') ptr
++;
291 if (ptr
[0] != '\0') ptr
++;
296 /* ugly svg variations, it is allowed to give style properties without
297 * the style attribute, i.e. direct attributes
299 str
= xmlGetProp(node
, "fill");
301 _parse_color (&s
->fill
, str
);
304 str
= xmlGetProp(node
, "stroke");
306 _parse_color (&s
->stroke
, str
);
309 str
= xmlGetProp(node
, "stroke-width");
311 s
->line_width
= g_ascii_strtod(str
, NULL
);
315 if (family
|| style
|| weight
) {
317 dia_font_unref (s
->font
);
318 s
->font
= dia_font_new_from_style(DIA_FONT_SANS
,s
->font_height
/*bogus*/);
320 dia_font_set_any_family(s
->font
,family
);
324 dia_font_set_slant_from_string(s
->font
,style
);
328 dia_font_set_weight_from_string(s
->font
,weight
);
335 * Code stolen from (and adapted)
336 * http://www.inkscape.org/doc/doxygen/html/svg-path_8cpp.php#a7
337 * which may have got it from rsvg, hop it is correct ;)
340 _path_arc_segment (GArray
* points
,
344 real x_axis_rotation
,
349 real a00
, a01
, a10
, a11
;
350 real x1
, y1
, x2
, y2
, x3
, y3
;
354 sin_th
= sin (x_axis_rotation
* (M_PI
/ 180.0));
355 cos_th
= cos (x_axis_rotation
* (M_PI
/ 180.0));
356 /* inverse transform compared with rsvg_path_arc */
362 th_half
= 0.5 * (th1
- th0
);
363 t
= (8.0 / 3.0) * sin(th_half
* 0.5) * sin(th_half
* 0.5) / sin(th_half
);
364 x1
= xc
+ cos (th0
) - t
* sin (th0
);
365 y1
= yc
+ sin (th0
) + t
* cos (th0
);
368 x2
= x3
+ t
* sin (th1
);
369 y2
= y3
- t
* cos (th1
);
371 bez
.type
= BEZ_CURVE_TO
;
372 bez
.p1
.x
= a00
* x1
+ a01
* y1
;
373 bez
.p1
.y
= a10
* x1
+ a11
* y1
;
374 bez
.p2
.x
= a00
* x2
+ a01
* y2
;
375 bez
.p2
.y
= a10
* x2
+ a11
* y2
;
376 bez
.p3
.x
= a00
* x3
+ a01
* y3
;
377 bez
.p3
.y
= a10
* x3
+ a11
* y3
;
381 g_array_append_val(points
, bez
);
385 _path_arc (GArray
*points
, double cpx
, double cpy
,
386 double rx
, double ry
, double x_axis_rotation
,
387 int large_arc_flag
, int sweep_flag
,
391 double sin_th
, cos_th
;
392 double a00
, a01
, a10
, a11
;
393 double x0
, y0
, x1
, y1
, xc
, yc
;
394 double d
, sfactor
, sfactor_sq
;
395 double th0
, th1
, th_arc
;
399 sin_th
= sin (x_axis_rotation
* (M_PI
/ 180.0));
400 cos_th
= cos (x_axis_rotation
* (M_PI
/ 180.0));
403 * Correction of out-of-range radii as described in Appendix F.6.6:
405 * 1. Ensure radii are non-zero (Done?).
406 * 2. Ensure that radii are positive.
407 * 3. Ensure that radii are large enough.
410 if(rx
< 0.0) rx
= -rx
;
411 if(ry
< 0.0) ry
= -ry
;
413 px
= cos_th
* (cpx
- x
) * 0.5 + sin_th
* (cpy
- y
) * 0.5;
414 py
= cos_th
* (cpy
- y
) * 0.5 - sin_th
* (cpx
- x
) * 0.5;
415 pl
= (px
* px
) / (rx
* rx
) + (py
* py
) / (ry
* ry
);
424 /* Proceed with computations as described in Appendix F.6.5 */
430 x0
= a00
* cpx
+ a01
* cpy
;
431 y0
= a10
* cpx
+ a11
* cpy
;
432 x1
= a00
* x
+ a01
* y
;
433 y1
= a10
* x
+ a11
* y
;
434 /* (x0, y0) is current point in transformed coordinate space.
435 (x1, y1) is new point in transformed coordinate space.
437 The arc fits a unit-radius circle in this space.
439 d
= (x1
- x0
) * (x1
- x0
) + (y1
- y0
) * (y1
- y0
);
440 sfactor_sq
= 1.0 / d
- 0.25;
441 if (sfactor_sq
< 0) sfactor_sq
= 0;
442 sfactor
= sqrt (sfactor_sq
);
443 if (sweep_flag
== large_arc_flag
) sfactor
= -sfactor
;
444 xc
= 0.5 * (x0
+ x1
) - sfactor
* (y1
- y0
);
445 yc
= 0.5 * (y0
+ y1
) + sfactor
* (x1
- x0
);
446 /* (xc, yc) is center of the circle. */
448 th0
= atan2 (y0
- yc
, x0
- xc
);
449 th1
= atan2 (y1
- yc
, x1
- xc
);
452 if (th_arc
< 0 && sweep_flag
)
454 else if (th_arc
> 0 && !sweep_flag
)
457 n_segs
= (int) ceil (fabs (th_arc
/ (M_PI
* 0.5 + 0.001)));
459 for (i
= 0; i
< n_segs
; i
++) {
460 _path_arc_segment(points
, xc
, yc
,
461 th0
+ i
* th_arc
/ n_segs
,
462 th0
+ (i
+ 1) * th_arc
/ n_segs
,
463 rx
, ry
, x_axis_rotation
,
468 /* routine to chomp off the start of the string */
469 #define path_chomp(path) while (path[0]!='\0'&&strchr(" \t\n\r,", path[0])) path++
472 * Takes SVG path content and converts it in an array of BezPoint.
473 * The caller is responsible to free/consume the returned array.
474 * Returns NULL on error.
476 * SVG pathes can contain multiple MOVE_TO commands while Dia's bezier
477 * object can only contain one so you may need to call this function
481 dia_svg_parse_path(const gchar
*path_str
, gchar
**unparsed
, gboolean
*closed
)
484 PATH_MOVE
, PATH_LINE
, PATH_HLINE
, PATH_VLINE
, PATH_CURVE
,
485 PATH_SMOOTHCURVE
, PATH_ARC
, PATH_CLOSE
} last_type
= PATH_MOVE
;
486 Point last_open
= {0.0, 0.0};
487 Point last_point
= {0.0, 0.0};
488 Point last_control
= {0.0, 0.0};
489 gboolean last_relative
= FALSE
;
492 gchar
*path
= (gchar
*)path_str
;
493 gboolean need_next_element
= FALSE
;
498 points
= g_array_new(FALSE
, FALSE
, sizeof(BezPoint
));
499 g_array_set_size(points
, 0);
502 while (path
[0] != '\0') {
504 g_print("Path: %s\n", path
);
506 /* check for a new command */
509 if (points
->len
> 0) {
510 need_next_element
= TRUE
;
515 last_type
= PATH_MOVE
;
516 last_relative
= FALSE
;
523 last_type
= PATH_MOVE
;
524 last_relative
= TRUE
;
529 last_type
= PATH_LINE
;
530 last_relative
= FALSE
;
535 last_type
= PATH_LINE
;
536 last_relative
= TRUE
;
541 last_type
= PATH_HLINE
;
542 last_relative
= FALSE
;
547 last_type
= PATH_HLINE
;
548 last_relative
= TRUE
;
553 last_type
= PATH_VLINE
;
554 last_relative
= FALSE
;
559 last_type
= PATH_VLINE
;
560 last_relative
= TRUE
;
565 last_type
= PATH_CURVE
;
566 last_relative
= FALSE
;
571 last_type
= PATH_CURVE
;
572 last_relative
= TRUE
;
577 last_type
= PATH_SMOOTHCURVE
;
578 last_relative
= FALSE
;
583 last_type
= PATH_SMOOTHCURVE
;
584 last_relative
= TRUE
;
590 last_type
= PATH_CLOSE
;
591 last_relative
= FALSE
;
596 last_type
= PATH_ARC
;
597 last_relative
= FALSE
;
602 last_type
= PATH_ARC
;
603 last_relative
= TRUE
;
618 if (last_type
== PATH_CLOSE
) {
619 g_warning("parse_path: argument given for implicite close path");
620 /* consume one number so we don't fall into an infinite loop */
621 while (path
!= '\0' && strchr("0123456789.+-", path
[0])) path
++;
624 need_next_element
= TRUE
;
629 g_warning("unsupported path code '%c'", path
[0]);
635 /* actually parse the path component */
638 bez
.type
= BEZ_MOVE_TO
;
639 bez
.p1
.x
= g_ascii_strtod(path
, &path
);
641 bez
.p1
.y
= g_ascii_strtod(path
, &path
);
644 bez
.p1
.x
+= last_point
.x
;
645 bez
.p1
.y
+= last_point
.y
;
648 last_control
= bez
.p1
;
650 g_array_append_val(points
, bez
);
653 bez
.type
= BEZ_LINE_TO
;
654 bez
.p1
.x
= g_ascii_strtod(path
, &path
);
656 bez
.p1
.y
= g_ascii_strtod(path
, &path
);
659 bez
.p1
.x
+= last_point
.x
;
660 bez
.p1
.y
+= last_point
.y
;
663 last_control
= bez
.p1
;
665 g_array_append_val(points
, bez
);
668 bez
.type
= BEZ_LINE_TO
;
669 bez
.p1
.x
= g_ascii_strtod(path
, &path
);
671 bez
.p1
.y
= last_point
.y
;
673 bez
.p1
.x
+= last_point
.x
;
675 last_control
= bez
.p1
;
677 g_array_append_val(points
, bez
);
680 bez
.type
= BEZ_LINE_TO
;
681 bez
.p1
.x
= last_point
.x
;
682 bez
.p1
.y
= g_ascii_strtod(path
, &path
);
685 bez
.p1
.y
+= last_point
.y
;
687 last_control
= bez
.p1
;
689 g_array_append_val(points
, bez
);
692 bez
.type
= BEZ_CURVE_TO
;
693 bez
.p1
.x
= g_ascii_strtod(path
, &path
);
695 bez
.p1
.y
= g_ascii_strtod(path
, &path
);
697 bez
.p2
.x
= g_ascii_strtod(path
, &path
);
699 bez
.p2
.y
= g_ascii_strtod(path
, &path
);
701 bez
.p3
.x
= g_ascii_strtod(path
, &path
);
703 bez
.p3
.y
= g_ascii_strtod(path
, &path
);
706 bez
.p1
.x
+= last_point
.x
;
707 bez
.p1
.y
+= last_point
.y
;
708 bez
.p2
.x
+= last_point
.x
;
709 bez
.p2
.y
+= last_point
.y
;
710 bez
.p3
.x
+= last_point
.x
;
711 bez
.p3
.y
+= last_point
.y
;
714 last_control
= bez
.p2
;
716 g_array_append_val(points
, bez
);
718 case PATH_SMOOTHCURVE
:
719 bez
.type
= BEZ_CURVE_TO
;
720 bez
.p1
.x
= 2 * last_point
.x
- last_control
.x
;
721 bez
.p1
.y
= 2 * last_point
.y
- last_control
.y
;
722 bez
.p2
.x
= g_ascii_strtod(path
, &path
);
724 bez
.p2
.y
= g_ascii_strtod(path
, &path
);
726 bez
.p3
.x
= g_ascii_strtod(path
, &path
);
728 bez
.p3
.y
= g_ascii_strtod(path
, &path
);
731 bez
.p2
.x
+= last_point
.x
;
732 bez
.p2
.y
+= last_point
.y
;
733 bez
.p3
.x
+= last_point
.x
;
734 bez
.p3
.y
+= last_point
.y
;
737 last_control
= bez
.p2
;
739 g_array_append_val(points
, bez
);
748 rx
= g_ascii_strtod(path
, &path
);
750 ry
= g_ascii_strtod(path
, &path
);
752 xrot
= g_ascii_strtod(path
, &path
);
755 largearc
= (int)g_ascii_strtod(path
, &path
);
757 sweep
= (int)g_ascii_strtod(path
, &path
);
760 dest
.x
= g_ascii_strtod(path
, &path
);
762 dest
.y
= g_ascii_strtod(path
, &path
);
766 dest
.x
+= last_point
.x
;
767 dest
.y
+= last_point
.y
;
770 _path_arc (points
, last_point
.x
, last_point
.y
,
771 rx
, ry
, xrot
, largearc
, sweep
, dest
.x
, dest
.y
,
774 last_control
= dest_c
;
778 /* close the path with a line */
779 if (last_open
.x
!= last_point
.x
|| last_open
.y
!= last_point
.y
) {
780 bez
.type
= BEZ_LINE_TO
;
782 g_array_append_val(points
, bez
);
785 need_next_element
= TRUE
;
787 /* get rid of any ignorable characters */
790 if (need_next_element
) {
791 /* check if there really is mor to be parsed */
798 /* avoid returning an array with only one point (I'd say the exporter
799 * producing such is rather broken, but *our* bezier creation code
802 if (points
->len
< 2) {
803 g_array_set_size(points
, 0);