]> gitweb.michael.orlitzky.com - libsvgtiny.git/blob - src/svgtiny.c
Fix reallocation of path buffer length to cope with differng path lengths
[libsvgtiny.git] / src / svgtiny.c
1 /*
2 * This file is part of Libsvgtiny
3 * Licensed under the MIT License,
4 * http://opensource.org/licenses/mit-license.php
5 * Copyright 2008-2009 James Bursa <james@semichrome.net>
6 * Copyright 2012 Daniel Silverstone <dsilvers@netsurf-browser.org>
7 */
8
9 #include <assert.h>
10 #include <math.h>
11 #include <setjmp.h>
12 #include <stdbool.h>
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <string.h>
16
17 #include <dom/dom.h>
18 #include <dom/bindings/xml/xmlparser.h>
19
20 #include "svgtiny.h"
21 #include "svgtiny_internal.h"
22
23 #ifndef M_PI
24 #define M_PI 3.14159265358979323846
25 #endif
26
27 #define KAPPA 0.5522847498
28
29 static svgtiny_code svgtiny_parse_svg(dom_element *svg,
30 struct svgtiny_parse_state state);
31 static svgtiny_code svgtiny_parse_path(dom_element *path,
32 struct svgtiny_parse_state state);
33 static svgtiny_code svgtiny_parse_rect(dom_element *rect,
34 struct svgtiny_parse_state state);
35 static svgtiny_code svgtiny_parse_circle(dom_element *circle,
36 struct svgtiny_parse_state state);
37 static svgtiny_code svgtiny_parse_ellipse(dom_element *ellipse,
38 struct svgtiny_parse_state state);
39 static svgtiny_code svgtiny_parse_line(dom_element *line,
40 struct svgtiny_parse_state state);
41 static svgtiny_code svgtiny_parse_poly(dom_element *poly,
42 struct svgtiny_parse_state state, bool polygon);
43 static svgtiny_code svgtiny_parse_text(dom_element *text,
44 struct svgtiny_parse_state state);
45 static void svgtiny_parse_position_attributes(dom_element *node,
46 const struct svgtiny_parse_state state,
47 float *x, float *y, float *width, float *height);
48 static void svgtiny_parse_paint_attributes(dom_element *node,
49 struct svgtiny_parse_state *state);
50 static void svgtiny_parse_font_attributes(dom_element *node,
51 struct svgtiny_parse_state *state);
52 static void svgtiny_parse_transform_attributes(dom_element *node,
53 struct svgtiny_parse_state *state);
54 static svgtiny_code svgtiny_add_path(float *p, unsigned int n,
55 struct svgtiny_parse_state *state);
56 static void _svgtiny_parse_color(const char *s, svgtiny_colour *c,
57 struct svgtiny_parse_state *state);
58
59 /**
60 * Set the local externally-stored parts of a parse state.
61 * Call this in functions that made a new state on the stack.
62 * Doesn't make own copy of global state, such as the interned string list.
63 */
64 static void svgtiny_setup_state_local(struct svgtiny_parse_state *state)
65 {
66 if (state->gradient_x1 != NULL) {
67 dom_string_ref(state->gradient_x1);
68 }
69 if (state->gradient_y1 != NULL) {
70 dom_string_ref(state->gradient_y1);
71 }
72 if (state->gradient_x2 != NULL) {
73 dom_string_ref(state->gradient_x2);
74 }
75 if (state->gradient_y2 != NULL) {
76 dom_string_ref(state->gradient_y2);
77 }
78 }
79
80 /**
81 * Cleanup the local externally-stored parts of a parse state.
82 * Call this in functions that made a new state on the stack.
83 * Doesn't cleanup global state, such as the interned string list.
84 */
85 static void svgtiny_cleanup_state_local(struct svgtiny_parse_state *state)
86 {
87 if (state->gradient_x1 != NULL) {
88 dom_string_unref(state->gradient_x1);
89 state->gradient_x1 = NULL;
90 }
91 if (state->gradient_y1 != NULL) {
92 dom_string_unref(state->gradient_y1);
93 state->gradient_y1 = NULL;
94 }
95 if (state->gradient_x2 != NULL) {
96 dom_string_unref(state->gradient_x2);
97 state->gradient_x2 = NULL;
98 }
99 if (state->gradient_y2 != NULL) {
100 dom_string_unref(state->gradient_y2);
101 state->gradient_y2 = NULL;
102 }
103 }
104
105
106 /**
107 * Create a new svgtiny_diagram structure.
108 */
109
110 struct svgtiny_diagram *svgtiny_create(void)
111 {
112 struct svgtiny_diagram *diagram;
113
114 diagram = calloc(sizeof(*diagram), 1);
115 if (!diagram)
116 return 0;
117
118 return diagram;
119 free(diagram);
120 return NULL;
121 }
122
123 static void ignore_msg(uint32_t severity, void *ctx, const char *msg, ...)
124 {
125 UNUSED(severity);
126 UNUSED(ctx);
127 UNUSED(msg);
128 }
129
130 /**
131 * Parse a block of memory into a svgtiny_diagram.
132 */
133
134 svgtiny_code svgtiny_parse(struct svgtiny_diagram *diagram,
135 const char *buffer, size_t size, const char *url,
136 int viewport_width, int viewport_height)
137 {
138 dom_document *document;
139 dom_exception exc;
140 dom_xml_parser *parser;
141 dom_xml_error err;
142 dom_element *svg;
143 dom_string *svg_name;
144 lwc_string *svg_name_lwc;
145 struct svgtiny_parse_state state;
146 float x, y, width, height;
147 svgtiny_code code;
148
149 assert(diagram);
150 assert(buffer);
151 assert(url);
152
153 UNUSED(url);
154
155 state.gradient_x1 = NULL;
156 state.gradient_y1 = NULL;
157 state.gradient_x2 = NULL;
158 state.gradient_y2 = NULL;
159
160 parser = dom_xml_parser_create(NULL, NULL,
161 ignore_msg, NULL, &document);
162
163 if (parser == NULL)
164 return svgtiny_LIBDOM_ERROR;
165
166 err = dom_xml_parser_parse_chunk(parser, (uint8_t *)buffer, size);
167 if (err != DOM_XML_OK) {
168 dom_node_unref(document);
169 dom_xml_parser_destroy(parser);
170 return svgtiny_LIBDOM_ERROR;
171 }
172
173 err = dom_xml_parser_completed(parser);
174 if (err != DOM_XML_OK) {
175 dom_node_unref(document);
176 dom_xml_parser_destroy(parser);
177 return svgtiny_LIBDOM_ERROR;
178 }
179
180 /* We're done parsing, drop the parser.
181 * We now own the document entirely.
182 */
183 dom_xml_parser_destroy(parser);
184
185 /* find root <svg> element */
186 exc = dom_document_get_document_element(document, &svg);
187 if (exc != DOM_NO_ERR) {
188 dom_node_unref(document);
189 return svgtiny_LIBDOM_ERROR;
190 }
191 exc = dom_node_get_node_name(svg, &svg_name);
192 if (exc != DOM_NO_ERR) {
193 dom_node_unref(svg);
194 dom_node_unref(document);
195 return svgtiny_LIBDOM_ERROR;
196 }
197 if (lwc_intern_string("svg", 3 /* SLEN("svg") */,
198 &svg_name_lwc) != lwc_error_ok) {
199 dom_string_unref(svg_name);
200 dom_node_unref(svg);
201 dom_node_unref(document);
202 return svgtiny_LIBDOM_ERROR;
203 }
204 if (!dom_string_caseless_lwc_isequal(svg_name, svg_name_lwc)) {
205 lwc_string_unref(svg_name_lwc);
206 dom_string_unref(svg_name);
207 dom_node_unref(svg);
208 dom_node_unref(document);
209 return svgtiny_NOT_SVG;
210 }
211
212 lwc_string_unref(svg_name_lwc);
213 dom_string_unref(svg_name);
214
215 /* get graphic dimensions */
216 memset(&state, 0, sizeof(state));
217 state.diagram = diagram;
218 state.document = document;
219 state.viewport_width = viewport_width;
220 state.viewport_height = viewport_height;
221
222 #define SVGTINY_STRING_ACTION2(s,n) \
223 if (dom_string_create_interned((const uint8_t *) #n, \
224 strlen(#n), &state.interned_##s) \
225 != DOM_NO_ERR) { \
226 code = svgtiny_LIBDOM_ERROR; \
227 goto cleanup; \
228 }
229 #include "svgtiny_strings.h"
230 #undef SVGTINY_STRING_ACTION2
231
232 svgtiny_parse_position_attributes(svg, state, &x, &y, &width, &height);
233 diagram->width = width;
234 diagram->height = height;
235
236 /* set up parsing state */
237 state.viewport_width = width;
238 state.viewport_height = height;
239 state.ctm.a = 1; /*(float) viewport_width / (float) width;*/
240 state.ctm.b = 0;
241 state.ctm.c = 0;
242 state.ctm.d = 1; /*(float) viewport_height / (float) height;*/
243 state.ctm.e = 0; /*x;*/
244 state.ctm.f = 0; /*y;*/
245 /*state.style = css_base_style;
246 state.style.font_size.value.length.value = option_font_size * 0.1;*/
247 state.fill = 0x000000;
248 state.stroke = svgtiny_TRANSPARENT;
249 state.stroke_width = 1;
250 state.linear_gradient_stop_count = 0;
251
252 /* parse tree */
253 code = svgtiny_parse_svg(svg, state);
254
255 dom_node_unref(svg);
256 dom_node_unref(document);
257
258 cleanup:
259 svgtiny_cleanup_state_local(&state);
260 #define SVGTINY_STRING_ACTION2(s,n) \
261 if (state.interned_##s != NULL) \
262 dom_string_unref(state.interned_##s);
263 #include "svgtiny_strings.h"
264 #undef SVGTINY_STRING_ACTION2
265 return code;
266 }
267
268
269 /**
270 * Parse a <svg> or <g> element node.
271 */
272
273 svgtiny_code svgtiny_parse_svg(dom_element *svg,
274 struct svgtiny_parse_state state)
275 {
276 float x, y, width, height;
277 dom_string *view_box;
278 dom_element *child;
279 dom_exception exc;
280
281 svgtiny_setup_state_local(&state);
282
283 svgtiny_parse_position_attributes(svg, state, &x, &y, &width, &height);
284 svgtiny_parse_paint_attributes(svg, &state);
285 svgtiny_parse_font_attributes(svg, &state);
286
287 exc = dom_element_get_attribute(svg, state.interned_viewBox,
288 &view_box);
289 if (exc != DOM_NO_ERR) {
290 svgtiny_cleanup_state_local(&state);
291 return svgtiny_LIBDOM_ERROR;
292 }
293
294 if (view_box) {
295 char *s = strndup(dom_string_data(view_box),
296 dom_string_byte_length(view_box));
297 float min_x, min_y, vwidth, vheight;
298 if (sscanf(s, "%f,%f,%f,%f",
299 &min_x, &min_y, &vwidth, &vheight) == 4 ||
300 sscanf(s, "%f %f %f %f",
301 &min_x, &min_y, &vwidth, &vheight) == 4) {
302 state.ctm.a = (float) state.viewport_width / vwidth;
303 state.ctm.d = (float) state.viewport_height / vheight;
304 state.ctm.e += -min_x * state.ctm.a;
305 state.ctm.f += -min_y * state.ctm.d;
306 }
307 free(s);
308 dom_string_unref(view_box);
309 }
310
311 svgtiny_parse_transform_attributes(svg, &state);
312
313 exc = dom_node_get_first_child(svg, (dom_node **) (void *) &child);
314 if (exc != DOM_NO_ERR) {
315 svgtiny_cleanup_state_local(&state);
316 return svgtiny_LIBDOM_ERROR;
317 }
318 while (child != NULL) {
319 dom_element *next;
320 dom_node_type nodetype;
321 svgtiny_code code = svgtiny_OK;
322
323 exc = dom_node_get_node_type(child, &nodetype);
324 if (exc != DOM_NO_ERR) {
325 dom_node_unref(child);
326 return svgtiny_LIBDOM_ERROR;
327 }
328 if (nodetype == DOM_ELEMENT_NODE) {
329 dom_string *nodename;
330 exc = dom_node_get_node_name(child, &nodename);
331 if (exc != DOM_NO_ERR) {
332 dom_node_unref(child);
333 svgtiny_cleanup_state_local(&state);
334 return svgtiny_LIBDOM_ERROR;
335 }
336 if (dom_string_caseless_isequal(state.interned_svg,
337 nodename))
338 code = svgtiny_parse_svg(child, state);
339 else if (dom_string_caseless_isequal(state.interned_g,
340 nodename))
341 code = svgtiny_parse_svg(child, state);
342 else if (dom_string_caseless_isequal(state.interned_a,
343 nodename))
344 code = svgtiny_parse_svg(child, state);
345 else if (dom_string_caseless_isequal(state.interned_path,
346 nodename))
347 code = svgtiny_parse_path(child, state);
348 else if (dom_string_caseless_isequal(state.interned_rect,
349 nodename))
350 code = svgtiny_parse_rect(child, state);
351 else if (dom_string_caseless_isequal(state.interned_circle,
352 nodename))
353 code = svgtiny_parse_circle(child, state);
354 else if (dom_string_caseless_isequal(state.interned_ellipse,
355 nodename))
356 code = svgtiny_parse_ellipse(child, state);
357 else if (dom_string_caseless_isequal(state.interned_line,
358 nodename))
359 code = svgtiny_parse_line(child, state);
360 else if (dom_string_caseless_isequal(state.interned_polyline,
361 nodename))
362 code = svgtiny_parse_poly(child, state, false);
363 else if (dom_string_caseless_isequal(state.interned_polygon,
364 nodename))
365 code = svgtiny_parse_poly(child, state, true);
366 else if (dom_string_caseless_isequal(state.interned_text,
367 nodename))
368 code = svgtiny_parse_text(child, state);
369 dom_string_unref(nodename);
370 }
371 if (code != svgtiny_OK) {
372 dom_node_unref(child);
373 svgtiny_cleanup_state_local(&state);
374 return code;
375 }
376 exc = dom_node_get_next_sibling(child,
377 (dom_node **) (void *) &next);
378 dom_node_unref(child);
379 if (exc != DOM_NO_ERR) {
380 svgtiny_cleanup_state_local(&state);
381 return svgtiny_LIBDOM_ERROR;
382 }
383 child = next;
384 }
385
386 svgtiny_cleanup_state_local(&state);
387 return svgtiny_OK;
388 }
389
390
391
392 /**
393 * Parse a <path> element node.
394 *
395 * http://www.w3.org/TR/SVG11/paths#PathElement
396 */
397
398 svgtiny_code svgtiny_parse_path(dom_element *path,
399 struct svgtiny_parse_state state)
400 {
401 svgtiny_code err;
402 dom_string *path_d_str;
403 dom_exception exc;
404 char *s, *path_d;
405 float *p; /* path elemets */
406 unsigned int palloc; /* number of path elements allocated */
407 unsigned int i;
408 float last_x = 0, last_y = 0;
409 float last_cubic_x = 0, last_cubic_y = 0;
410 float last_quad_x = 0, last_quad_y = 0;
411 float subpath_first_x = 0, subpath_first_y = 0;
412
413 svgtiny_setup_state_local(&state);
414
415 svgtiny_parse_paint_attributes(path, &state);
416 svgtiny_parse_transform_attributes(path, &state);
417
418 /* read d attribute */
419 exc = dom_element_get_attribute(path, state.interned_d, &path_d_str);
420 if (exc != DOM_NO_ERR) {
421 state.diagram->error_line = -1; /* path->line; */
422 state.diagram->error_message = "path: error retrieving d attribute";
423 svgtiny_cleanup_state_local(&state);
424 return svgtiny_SVG_ERROR;
425 }
426
427 if (path_d_str == NULL) {
428 state.diagram->error_line = -1; /* path->line; */
429 state.diagram->error_message = "path: missing d attribute";
430 svgtiny_cleanup_state_local(&state);
431 return svgtiny_SVG_ERROR;
432 }
433
434 /* empty path is permitted it just disables the path */
435 palloc = dom_string_byte_length(path_d_str);
436 if (palloc == 0) {
437 svgtiny_cleanup_state_local(&state);
438 return svgtiny_OK;
439 }
440
441 /* local copy of the path data allowing in-place modification */
442 s = path_d = strndup(dom_string_data(path_d_str), palloc);
443 dom_string_unref(path_d_str);
444 if (s == NULL) {
445 svgtiny_cleanup_state_local(&state);
446 return svgtiny_OUT_OF_MEMORY;
447 }
448
449 /* ensure path element allocation is sensibly bounded */
450 if (palloc < 8) {
451 palloc = 8;
452 } else if (palloc > 64) {
453 palloc = palloc / 2;
454 }
455
456 /* allocate initial space for path elements */
457 p = malloc(sizeof p[0] * palloc);
458 if (p == NULL) {
459 free(path_d);
460 svgtiny_cleanup_state_local(&state);
461 return svgtiny_OUT_OF_MEMORY;
462 }
463
464 /* parse d and build path */
465 for (i = 0; s[i]; i++)
466 if (s[i] == ',')
467 s[i] = ' ';
468 i = 0;
469 while (*s) {
470 char command[2];
471 int plot_command;
472 float x, y, x1, y1, x2, y2, rx, ry, rotation, large_arc, sweep;
473 int n;
474
475 /* Ensure there is sufficient space for path elements */
476 #define ALLOC_PATH_ELEMENTS(NUM_ELEMENTS) \
477 do { \
478 if ((palloc - i) < NUM_ELEMENTS) { \
479 float *tp; \
480 palloc = (palloc * 2) + (palloc / 2); \
481 tp = realloc(p, sizeof p[0] * palloc); \
482 if (tp == NULL) { \
483 free(p); \
484 free(path_d); \
485 svgtiny_cleanup_state_local(&state); \
486 return svgtiny_OUT_OF_MEMORY; \
487 } \
488 p = tp; \
489 } \
490 } while(0)
491
492
493 /* moveto (M, m), lineto (L, l) (2 arguments) */
494 if (sscanf(s, " %1[MmLl] %f %f %n", command, &x, &y, &n) == 3) {
495 /*LOG(("moveto or lineto"));*/
496 if (*command == 'M' || *command == 'm')
497 plot_command = svgtiny_PATH_MOVE;
498 else
499 plot_command = svgtiny_PATH_LINE;
500 do {
501 ALLOC_PATH_ELEMENTS(3);
502 p[i++] = plot_command;
503 if ('a' <= *command) {
504 x += last_x;
505 y += last_y;
506 }
507 if (plot_command == svgtiny_PATH_MOVE) {
508 subpath_first_x = x;
509 subpath_first_y = y;
510 }
511 p[i++] = last_cubic_x = last_quad_x = last_x
512 = x;
513 p[i++] = last_cubic_y = last_quad_y = last_y
514 = y;
515 s += n;
516 plot_command = svgtiny_PATH_LINE;
517 } while (sscanf(s, "%f %f %n", &x, &y, &n) == 2);
518
519 /* closepath (Z, z) (no arguments) */
520 } else if (sscanf(s, " %1[Zz] %n", command, &n) == 1) {
521 /*LOG(("closepath"));*/
522 ALLOC_PATH_ELEMENTS(1);
523
524 p[i++] = svgtiny_PATH_CLOSE;
525 s += n;
526 last_cubic_x = last_quad_x = last_x = subpath_first_x;
527 last_cubic_y = last_quad_y = last_y = subpath_first_y;
528
529 /* horizontal lineto (H, h) (1 argument) */
530 } else if (sscanf(s, " %1[Hh] %f %n", command, &x, &n) == 2) {
531 /*LOG(("horizontal lineto"));*/
532 do {
533 ALLOC_PATH_ELEMENTS(3);
534
535 p[i++] = svgtiny_PATH_LINE;
536 if (*command == 'h')
537 x += last_x;
538 p[i++] = last_cubic_x = last_quad_x = last_x
539 = x;
540 p[i++] = last_cubic_y = last_quad_y = last_y;
541 s += n;
542 } while (sscanf(s, "%f %n", &x, &n) == 1);
543
544 /* vertical lineto (V, v) (1 argument) */
545 } else if (sscanf(s, " %1[Vv] %f %n", command, &y, &n) == 2) {
546 /*LOG(("vertical lineto"));*/
547 do {
548 ALLOC_PATH_ELEMENTS(3);
549
550 p[i++] = svgtiny_PATH_LINE;
551 if (*command == 'v')
552 y += last_y;
553 p[i++] = last_cubic_x = last_quad_x = last_x;
554 p[i++] = last_cubic_y = last_quad_y = last_y
555 = y;
556 s += n;
557 } while (sscanf(s, "%f %n", &x, &n) == 1);
558
559 /* curveto (C, c) (6 arguments) */
560 } else if (sscanf(s, " %1[Cc] %f %f %f %f %f %f %n", command,
561 &x1, &y1, &x2, &y2, &x, &y, &n) == 7) {
562 /*LOG(("curveto"));*/
563 do {
564 ALLOC_PATH_ELEMENTS(7);
565
566 p[i++] = svgtiny_PATH_BEZIER;
567 if (*command == 'c') {
568 x1 += last_x;
569 y1 += last_y;
570 x2 += last_x;
571 y2 += last_y;
572 x += last_x;
573 y += last_y;
574 }
575 p[i++] = x1;
576 p[i++] = y1;
577 p[i++] = last_cubic_x = x2;
578 p[i++] = last_cubic_y = y2;
579 p[i++] = last_quad_x = last_x = x;
580 p[i++] = last_quad_y = last_y = y;
581 s += n;
582 } while (sscanf(s, "%f %f %f %f %f %f %n",
583 &x1, &y1, &x2, &y2, &x, &y, &n) == 6);
584
585 /* shorthand/smooth curveto (S, s) (4 arguments) */
586 } else if (sscanf(s, " %1[Ss] %f %f %f %f %n", command,
587 &x2, &y2, &x, &y, &n) == 5) {
588 /*LOG(("shorthand/smooth curveto"));*/
589 do {
590 ALLOC_PATH_ELEMENTS(7);
591
592 p[i++] = svgtiny_PATH_BEZIER;
593 x1 = last_x + (last_x - last_cubic_x);
594 y1 = last_y + (last_y - last_cubic_y);
595 if (*command == 's') {
596 x2 += last_x;
597 y2 += last_y;
598 x += last_x;
599 y += last_y;
600 }
601 p[i++] = x1;
602 p[i++] = y1;
603 p[i++] = last_cubic_x = x2;
604 p[i++] = last_cubic_y = y2;
605 p[i++] = last_quad_x = last_x = x;
606 p[i++] = last_quad_y = last_y = y;
607 s += n;
608 } while (sscanf(s, "%f %f %f %f %n",
609 &x2, &y2, &x, &y, &n) == 4);
610
611 /* quadratic Bezier curveto (Q, q) (4 arguments) */
612 } else if (sscanf(s, " %1[Qq] %f %f %f %f %n", command,
613 &x1, &y1, &x, &y, &n) == 5) {
614 /*LOG(("quadratic Bezier curveto"));*/
615 do {
616 ALLOC_PATH_ELEMENTS(7);
617
618 p[i++] = svgtiny_PATH_BEZIER;
619 last_quad_x = x1;
620 last_quad_y = y1;
621 if (*command == 'q') {
622 x1 += last_x;
623 y1 += last_y;
624 x += last_x;
625 y += last_y;
626 }
627 p[i++] = 1./3 * last_x + 2./3 * x1;
628 p[i++] = 1./3 * last_y + 2./3 * y1;
629 p[i++] = 2./3 * x1 + 1./3 * x;
630 p[i++] = 2./3 * y1 + 1./3 * y;
631 p[i++] = last_cubic_x = last_x = x;
632 p[i++] = last_cubic_y = last_y = y;
633 s += n;
634 } while (sscanf(s, "%f %f %f %f %n",
635 &x1, &y1, &x, &y, &n) == 4);
636
637 /* shorthand/smooth quadratic Bezier curveto (T, t)
638 (2 arguments) */
639 } else if (sscanf(s, " %1[Tt] %f %f %n", command,
640 &x, &y, &n) == 3) {
641 /*LOG(("shorthand/smooth quadratic Bezier curveto"));*/
642 do {
643 ALLOC_PATH_ELEMENTS(7);
644
645 p[i++] = svgtiny_PATH_BEZIER;
646 x1 = last_x + (last_x - last_quad_x);
647 y1 = last_y + (last_y - last_quad_y);
648 last_quad_x = x1;
649 last_quad_y = y1;
650 if (*command == 't') {
651 x1 += last_x;
652 y1 += last_y;
653 x += last_x;
654 y += last_y;
655 }
656 p[i++] = 1./3 * last_x + 2./3 * x1;
657 p[i++] = 1./3 * last_y + 2./3 * y1;
658 p[i++] = 2./3 * x1 + 1./3 * x;
659 p[i++] = 2./3 * y1 + 1./3 * y;
660 p[i++] = last_cubic_x = last_x = x;
661 p[i++] = last_cubic_y = last_y = y;
662 s += n;
663 } while (sscanf(s, "%f %f %n",
664 &x, &y, &n) == 2);
665
666 /* elliptical arc (A, a) (7 arguments) */
667 } else if (sscanf(s, " %1[Aa] %f %f %f %f %f %f %f %n", command,
668 &rx, &ry, &rotation, &large_arc, &sweep,
669 &x, &y, &n) == 8) {
670 do {
671 ALLOC_PATH_ELEMENTS(3);
672
673 p[i++] = svgtiny_PATH_LINE;
674 if (*command == 'a') {
675 x += last_x;
676 y += last_y;
677 }
678 p[i++] = last_cubic_x = last_quad_x = last_x
679 = x;
680 p[i++] = last_cubic_y = last_quad_y = last_y
681 = y;
682 s += n;
683 } while (sscanf(s, "%f %f %f %f %f %f %f %n",
684 &rx, &ry, &rotation, &large_arc, &sweep,
685 &x, &y, &n) == 7);
686
687 } else {
688 fprintf(stderr, "parse failed at \"%s\"\n", s);
689 break;
690 }
691 }
692
693 free(path_d);
694
695 if (i <= 4) {
696 /* no real segments in path */
697 free(p);
698 svgtiny_cleanup_state_local(&state);
699 return svgtiny_OK;
700 }
701
702 /* resize path element array to not be over allocated */
703 if (palloc != i) {
704 float *tp;
705
706 /* try the resize, if it fails just continue to use the old
707 * allocation
708 */
709 tp = realloc(p, sizeof p[0] * i);
710 if (tp != NULL) {
711 p = tp;
712 }
713 }
714
715 err = svgtiny_add_path(p, i, &state);
716
717 svgtiny_cleanup_state_local(&state);
718
719 return err;
720 }
721
722
723 /**
724 * Parse a <rect> element node.
725 *
726 * http://www.w3.org/TR/SVG11/shapes#RectElement
727 */
728
729 svgtiny_code svgtiny_parse_rect(dom_element *rect,
730 struct svgtiny_parse_state state)
731 {
732 svgtiny_code err;
733 float x, y, width, height;
734 float *p;
735
736 svgtiny_setup_state_local(&state);
737
738 svgtiny_parse_position_attributes(rect, state,
739 &x, &y, &width, &height);
740 svgtiny_parse_paint_attributes(rect, &state);
741 svgtiny_parse_transform_attributes(rect, &state);
742
743 p = malloc(13 * sizeof p[0]);
744 if (!p) {
745 svgtiny_cleanup_state_local(&state);
746 return svgtiny_OUT_OF_MEMORY;
747 }
748
749 p[0] = svgtiny_PATH_MOVE;
750 p[1] = x;
751 p[2] = y;
752 p[3] = svgtiny_PATH_LINE;
753 p[4] = x + width;
754 p[5] = y;
755 p[6] = svgtiny_PATH_LINE;
756 p[7] = x + width;
757 p[8] = y + height;
758 p[9] = svgtiny_PATH_LINE;
759 p[10] = x;
760 p[11] = y + height;
761 p[12] = svgtiny_PATH_CLOSE;
762
763 err = svgtiny_add_path(p, 13, &state);
764
765 svgtiny_cleanup_state_local(&state);
766
767 return err;
768 }
769
770
771 /**
772 * Parse a <circle> element node.
773 */
774
775 svgtiny_code svgtiny_parse_circle(dom_element *circle,
776 struct svgtiny_parse_state state)
777 {
778 svgtiny_code err;
779 float x = 0, y = 0, r = -1;
780 float *p;
781 dom_string *attr;
782 dom_exception exc;
783
784 svgtiny_setup_state_local(&state);
785
786 exc = dom_element_get_attribute(circle, state.interned_cx, &attr);
787 if (exc != DOM_NO_ERR) {
788 svgtiny_cleanup_state_local(&state);
789 return svgtiny_LIBDOM_ERROR;
790 }
791 if (attr != NULL) {
792 x = svgtiny_parse_length(attr, state.viewport_width, state);
793 }
794 dom_string_unref(attr);
795
796 exc = dom_element_get_attribute(circle, state.interned_cy, &attr);
797 if (exc != DOM_NO_ERR) {
798 svgtiny_cleanup_state_local(&state);
799 return svgtiny_LIBDOM_ERROR;
800 }
801 if (attr != NULL) {
802 y = svgtiny_parse_length(attr, state.viewport_height, state);
803 }
804 dom_string_unref(attr);
805
806 exc = dom_element_get_attribute(circle, state.interned_r, &attr);
807 if (exc != DOM_NO_ERR) {
808 svgtiny_cleanup_state_local(&state);
809 return svgtiny_LIBDOM_ERROR;
810 }
811 if (attr != NULL) {
812 r = svgtiny_parse_length(attr, state.viewport_width, state);
813 }
814 dom_string_unref(attr);
815
816 svgtiny_parse_paint_attributes(circle, &state);
817 svgtiny_parse_transform_attributes(circle, &state);
818
819 if (r < 0) {
820 state.diagram->error_line = -1; /* circle->line; */
821 state.diagram->error_message = "circle: r missing or negative";
822 svgtiny_cleanup_state_local(&state);
823 return svgtiny_SVG_ERROR;
824 }
825 if (r == 0) {
826 svgtiny_cleanup_state_local(&state);
827 return svgtiny_OK;
828 }
829
830 p = malloc(32 * sizeof p[0]);
831 if (!p) {
832 svgtiny_cleanup_state_local(&state);
833 return svgtiny_OUT_OF_MEMORY;
834 }
835
836 p[0] = svgtiny_PATH_MOVE;
837 p[1] = x + r;
838 p[2] = y;
839 p[3] = svgtiny_PATH_BEZIER;
840 p[4] = x + r;
841 p[5] = y + r * KAPPA;
842 p[6] = x + r * KAPPA;
843 p[7] = y + r;
844 p[8] = x;
845 p[9] = y + r;
846 p[10] = svgtiny_PATH_BEZIER;
847 p[11] = x - r * KAPPA;
848 p[12] = y + r;
849 p[13] = x - r;
850 p[14] = y + r * KAPPA;
851 p[15] = x - r;
852 p[16] = y;
853 p[17] = svgtiny_PATH_BEZIER;
854 p[18] = x - r;
855 p[19] = y - r * KAPPA;
856 p[20] = x - r * KAPPA;
857 p[21] = y - r;
858 p[22] = x;
859 p[23] = y - r;
860 p[24] = svgtiny_PATH_BEZIER;
861 p[25] = x + r * KAPPA;
862 p[26] = y - r;
863 p[27] = x + r;
864 p[28] = y - r * KAPPA;
865 p[29] = x + r;
866 p[30] = y;
867 p[31] = svgtiny_PATH_CLOSE;
868
869 err = svgtiny_add_path(p, 32, &state);
870
871 svgtiny_cleanup_state_local(&state);
872
873 return err;
874 }
875
876
877 /**
878 * Parse an <ellipse> element node.
879 */
880
881 svgtiny_code svgtiny_parse_ellipse(dom_element *ellipse,
882 struct svgtiny_parse_state state)
883 {
884 svgtiny_code err;
885 float x = 0, y = 0, rx = -1, ry = -1;
886 float *p;
887 dom_string *attr;
888 dom_exception exc;
889
890 svgtiny_setup_state_local(&state);
891
892 exc = dom_element_get_attribute(ellipse, state.interned_cx, &attr);
893 if (exc != DOM_NO_ERR) {
894 svgtiny_cleanup_state_local(&state);
895 return svgtiny_LIBDOM_ERROR;
896 }
897 if (attr != NULL) {
898 x = svgtiny_parse_length(attr, state.viewport_width, state);
899 }
900 dom_string_unref(attr);
901
902 exc = dom_element_get_attribute(ellipse, state.interned_cy, &attr);
903 if (exc != DOM_NO_ERR) {
904 svgtiny_cleanup_state_local(&state);
905 return svgtiny_LIBDOM_ERROR;
906 }
907 if (attr != NULL) {
908 y = svgtiny_parse_length(attr, state.viewport_height, state);
909 }
910 dom_string_unref(attr);
911
912 exc = dom_element_get_attribute(ellipse, state.interned_rx, &attr);
913 if (exc != DOM_NO_ERR) {
914 svgtiny_cleanup_state_local(&state);
915 return svgtiny_LIBDOM_ERROR;
916 }
917 if (attr != NULL) {
918 rx = svgtiny_parse_length(attr, state.viewport_width, state);
919 }
920 dom_string_unref(attr);
921
922 exc = dom_element_get_attribute(ellipse, state.interned_ry, &attr);
923 if (exc != DOM_NO_ERR) {
924 svgtiny_cleanup_state_local(&state);
925 return svgtiny_LIBDOM_ERROR;
926 }
927 if (attr != NULL) {
928 ry = svgtiny_parse_length(attr, state.viewport_width, state);
929 }
930 dom_string_unref(attr);
931
932 svgtiny_parse_paint_attributes(ellipse, &state);
933 svgtiny_parse_transform_attributes(ellipse, &state);
934
935 if (rx < 0 || ry < 0) {
936 state.diagram->error_line = -1; /* ellipse->line; */
937 state.diagram->error_message = "ellipse: rx or ry missing "
938 "or negative";
939 svgtiny_cleanup_state_local(&state);
940 return svgtiny_SVG_ERROR;
941 }
942 if (rx == 0 || ry == 0) {
943 svgtiny_cleanup_state_local(&state);
944 return svgtiny_OK;
945 }
946
947 p = malloc(32 * sizeof p[0]);
948 if (!p) {
949 svgtiny_cleanup_state_local(&state);
950 return svgtiny_OUT_OF_MEMORY;
951 }
952
953 p[0] = svgtiny_PATH_MOVE;
954 p[1] = x + rx;
955 p[2] = y;
956 p[3] = svgtiny_PATH_BEZIER;
957 p[4] = x + rx;
958 p[5] = y + ry * KAPPA;
959 p[6] = x + rx * KAPPA;
960 p[7] = y + ry;
961 p[8] = x;
962 p[9] = y + ry;
963 p[10] = svgtiny_PATH_BEZIER;
964 p[11] = x - rx * KAPPA;
965 p[12] = y + ry;
966 p[13] = x - rx;
967 p[14] = y + ry * KAPPA;
968 p[15] = x - rx;
969 p[16] = y;
970 p[17] = svgtiny_PATH_BEZIER;
971 p[18] = x - rx;
972 p[19] = y - ry * KAPPA;
973 p[20] = x - rx * KAPPA;
974 p[21] = y - ry;
975 p[22] = x;
976 p[23] = y - ry;
977 p[24] = svgtiny_PATH_BEZIER;
978 p[25] = x + rx * KAPPA;
979 p[26] = y - ry;
980 p[27] = x + rx;
981 p[28] = y - ry * KAPPA;
982 p[29] = x + rx;
983 p[30] = y;
984 p[31] = svgtiny_PATH_CLOSE;
985
986 err = svgtiny_add_path(p, 32, &state);
987
988 svgtiny_cleanup_state_local(&state);
989
990 return err;
991 }
992
993
994 /**
995 * Parse a <line> element node.
996 */
997
998 svgtiny_code svgtiny_parse_line(dom_element *line,
999 struct svgtiny_parse_state state)
1000 {
1001 svgtiny_code err;
1002 float x1 = 0, y1 = 0, x2 = 0, y2 = 0;
1003 float *p;
1004 dom_string *attr;
1005 dom_exception exc;
1006
1007 svgtiny_setup_state_local(&state);
1008
1009 exc = dom_element_get_attribute(line, state.interned_x1, &attr);
1010 if (exc != DOM_NO_ERR) {
1011 svgtiny_cleanup_state_local(&state);
1012 return svgtiny_LIBDOM_ERROR;
1013 }
1014 if (attr != NULL) {
1015 x1 = svgtiny_parse_length(attr, state.viewport_width, state);
1016 }
1017 dom_string_unref(attr);
1018
1019 exc = dom_element_get_attribute(line, state.interned_y1, &attr);
1020 if (exc != DOM_NO_ERR) {
1021 svgtiny_cleanup_state_local(&state);
1022 return svgtiny_LIBDOM_ERROR;
1023 }
1024 if (attr != NULL) {
1025 y1 = svgtiny_parse_length(attr, state.viewport_height, state);
1026 }
1027 dom_string_unref(attr);
1028
1029 exc = dom_element_get_attribute(line, state.interned_x2, &attr);
1030 if (exc != DOM_NO_ERR) {
1031 svgtiny_cleanup_state_local(&state);
1032 return svgtiny_LIBDOM_ERROR;
1033 }
1034 if (attr != NULL) {
1035 x2 = svgtiny_parse_length(attr, state.viewport_width, state);
1036 }
1037 dom_string_unref(attr);
1038
1039 exc = dom_element_get_attribute(line, state.interned_y2, &attr);
1040 if (exc != DOM_NO_ERR) {
1041 svgtiny_cleanup_state_local(&state);
1042 return svgtiny_LIBDOM_ERROR;
1043 }
1044 if (attr != NULL) {
1045 y2 = svgtiny_parse_length(attr, state.viewport_height, state);
1046 }
1047 dom_string_unref(attr);
1048
1049 svgtiny_parse_paint_attributes(line, &state);
1050 svgtiny_parse_transform_attributes(line, &state);
1051
1052 p = malloc(7 * sizeof p[0]);
1053 if (!p) {
1054 svgtiny_cleanup_state_local(&state);
1055 return svgtiny_OUT_OF_MEMORY;
1056 }
1057
1058 p[0] = svgtiny_PATH_MOVE;
1059 p[1] = x1;
1060 p[2] = y1;
1061 p[3] = svgtiny_PATH_LINE;
1062 p[4] = x2;
1063 p[5] = y2;
1064 p[6] = svgtiny_PATH_CLOSE;
1065
1066 err = svgtiny_add_path(p, 7, &state);
1067
1068 svgtiny_cleanup_state_local(&state);
1069
1070 return err;
1071 }
1072
1073
1074 /**
1075 * Parse a <polyline> or <polygon> element node.
1076 *
1077 * http://www.w3.org/TR/SVG11/shapes#PolylineElement
1078 * http://www.w3.org/TR/SVG11/shapes#PolygonElement
1079 */
1080
1081 svgtiny_code svgtiny_parse_poly(dom_element *poly,
1082 struct svgtiny_parse_state state, bool polygon)
1083 {
1084 svgtiny_code err;
1085 dom_string *points_str;
1086 dom_exception exc;
1087 char *s, *points;
1088 float *p;
1089 unsigned int i;
1090
1091 svgtiny_setup_state_local(&state);
1092
1093 svgtiny_parse_paint_attributes(poly, &state);
1094 svgtiny_parse_transform_attributes(poly, &state);
1095
1096 exc = dom_element_get_attribute(poly, state.interned_points,
1097 &points_str);
1098 if (exc != DOM_NO_ERR) {
1099 svgtiny_cleanup_state_local(&state);
1100 return svgtiny_LIBDOM_ERROR;
1101 }
1102
1103 if (points_str == NULL) {
1104 state.diagram->error_line = -1; /* poly->line; */
1105 state.diagram->error_message =
1106 "polyline/polygon: missing points attribute";
1107 svgtiny_cleanup_state_local(&state);
1108 return svgtiny_SVG_ERROR;
1109 }
1110
1111 s = points = strndup(dom_string_data(points_str),
1112 dom_string_byte_length(points_str));
1113 dom_string_unref(points_str);
1114 /* read points attribute */
1115 if (s == NULL) {
1116 svgtiny_cleanup_state_local(&state);
1117 return svgtiny_OUT_OF_MEMORY;
1118 }
1119 /* allocate space for path: it will never have more elements than s */
1120 p = malloc(sizeof p[0] * strlen(s));
1121 if (!p) {
1122 free(points);
1123 svgtiny_cleanup_state_local(&state);
1124 return svgtiny_OUT_OF_MEMORY;
1125 }
1126
1127 /* parse s and build path */
1128 for (i = 0; s[i]; i++)
1129 if (s[i] == ',')
1130 s[i] = ' ';
1131 i = 0;
1132 while (*s) {
1133 float x, y;
1134 int n;
1135
1136 if (sscanf(s, "%f %f %n", &x, &y, &n) == 2) {
1137 if (i == 0)
1138 p[i++] = svgtiny_PATH_MOVE;
1139 else
1140 p[i++] = svgtiny_PATH_LINE;
1141 p[i++] = x;
1142 p[i++] = y;
1143 s += n;
1144 } else {
1145 break;
1146 }
1147 }
1148 if (polygon)
1149 p[i++] = svgtiny_PATH_CLOSE;
1150
1151 free(points);
1152
1153 err = svgtiny_add_path(p, i, &state);
1154
1155 svgtiny_cleanup_state_local(&state);
1156
1157 return err;
1158 }
1159
1160
1161 /**
1162 * Parse a <text> or <tspan> element node.
1163 */
1164
1165 svgtiny_code svgtiny_parse_text(dom_element *text,
1166 struct svgtiny_parse_state state)
1167 {
1168 float x, y, width, height;
1169 float px, py;
1170 dom_node *child;
1171 dom_exception exc;
1172
1173 svgtiny_setup_state_local(&state);
1174
1175 svgtiny_parse_position_attributes(text, state,
1176 &x, &y, &width, &height);
1177 svgtiny_parse_font_attributes(text, &state);
1178 svgtiny_parse_transform_attributes(text, &state);
1179
1180 px = state.ctm.a * x + state.ctm.c * y + state.ctm.e;
1181 py = state.ctm.b * x + state.ctm.d * y + state.ctm.f;
1182 /* state.ctm.e = px - state.origin_x; */
1183 /* state.ctm.f = py - state.origin_y; */
1184
1185 /*struct css_style style = state.style;
1186 style.font_size.value.length.value *= state.ctm.a;*/
1187
1188 exc = dom_node_get_first_child(text, &child);
1189 if (exc != DOM_NO_ERR) {
1190 return svgtiny_LIBDOM_ERROR;
1191 svgtiny_cleanup_state_local(&state);
1192 }
1193 while (child != NULL) {
1194 dom_node *next;
1195 dom_node_type nodetype;
1196 svgtiny_code code = svgtiny_OK;
1197
1198 exc = dom_node_get_node_type(child, &nodetype);
1199 if (exc != DOM_NO_ERR) {
1200 dom_node_unref(child);
1201 svgtiny_cleanup_state_local(&state);
1202 return svgtiny_LIBDOM_ERROR;
1203 }
1204 if (nodetype == DOM_ELEMENT_NODE) {
1205 dom_string *nodename;
1206 exc = dom_node_get_node_name(child, &nodename);
1207 if (exc != DOM_NO_ERR) {
1208 dom_node_unref(child);
1209 svgtiny_cleanup_state_local(&state);
1210 return svgtiny_LIBDOM_ERROR;
1211 }
1212 if (dom_string_caseless_isequal(nodename,
1213 state.interned_tspan))
1214 code = svgtiny_parse_text((dom_element *)child,
1215 state);
1216 dom_string_unref(nodename);
1217 } else if (nodetype == DOM_TEXT_NODE) {
1218 struct svgtiny_shape *shape = svgtiny_add_shape(&state);
1219 dom_string *content;
1220 if (shape == NULL) {
1221 dom_node_unref(child);
1222 svgtiny_cleanup_state_local(&state);
1223 return svgtiny_OUT_OF_MEMORY;
1224 }
1225 exc = dom_text_get_whole_text(child, &content);
1226 if (exc != DOM_NO_ERR) {
1227 dom_node_unref(child);
1228 svgtiny_cleanup_state_local(&state);
1229 return svgtiny_LIBDOM_ERROR;
1230 }
1231 if (content != NULL) {
1232 shape->text = strndup(dom_string_data(content),
1233 dom_string_byte_length(content));
1234 dom_string_unref(content);
1235 } else {
1236 shape->text = strdup("");
1237 }
1238 shape->text_x = px;
1239 shape->text_y = py;
1240 state.diagram->shape_count++;
1241 }
1242
1243 if (code != svgtiny_OK) {
1244 dom_node_unref(child);
1245 svgtiny_cleanup_state_local(&state);
1246 return code;
1247 }
1248 exc = dom_node_get_next_sibling(child, &next);
1249 dom_node_unref(child);
1250 if (exc != DOM_NO_ERR) {
1251 svgtiny_cleanup_state_local(&state);
1252 return svgtiny_LIBDOM_ERROR;
1253 }
1254 child = next;
1255 }
1256
1257 svgtiny_cleanup_state_local(&state);
1258
1259 return svgtiny_OK;
1260 }
1261
1262
1263 /**
1264 * Parse x, y, width, and height attributes, if present.
1265 */
1266
1267 void svgtiny_parse_position_attributes(dom_element *node,
1268 const struct svgtiny_parse_state state,
1269 float *x, float *y, float *width, float *height)
1270 {
1271 dom_string *attr;
1272 dom_exception exc;
1273
1274 *x = 0;
1275 *y = 0;
1276 *width = state.viewport_width;
1277 *height = state.viewport_height;
1278
1279 exc = dom_element_get_attribute(node, state.interned_x, &attr);
1280 if (exc == DOM_NO_ERR && attr != NULL) {
1281 *x = svgtiny_parse_length(attr, state.viewport_width, state);
1282 dom_string_unref(attr);
1283 }
1284
1285 exc = dom_element_get_attribute(node, state.interned_y, &attr);
1286 if (exc == DOM_NO_ERR && attr != NULL) {
1287 *y = svgtiny_parse_length(attr, state.viewport_height, state);
1288 dom_string_unref(attr);
1289 }
1290
1291 exc = dom_element_get_attribute(node, state.interned_width, &attr);
1292 if (exc == DOM_NO_ERR && attr != NULL) {
1293 *width = svgtiny_parse_length(attr, state.viewport_width,
1294 state);
1295 dom_string_unref(attr);
1296 }
1297
1298 exc = dom_element_get_attribute(node, state.interned_height, &attr);
1299 if (exc == DOM_NO_ERR && attr != NULL) {
1300 *height = svgtiny_parse_length(attr, state.viewport_height,
1301 state);
1302 dom_string_unref(attr);
1303 }
1304 }
1305
1306
1307 /**
1308 * Parse a length as a number of pixels.
1309 */
1310
1311 static float _svgtiny_parse_length(const char *s, int viewport_size,
1312 const struct svgtiny_parse_state state)
1313 {
1314 int num_length = strspn(s, "0123456789+-.");
1315 const char *unit = s + num_length;
1316 float n = atof((const char *) s);
1317 float font_size = 20; /*css_len2px(&state.style.font_size.value.length, 0);*/
1318
1319 UNUSED(state);
1320
1321 if (unit[0] == 0) {
1322 return n;
1323 } else if (unit[0] == '%') {
1324 return n / 100.0 * viewport_size;
1325 } else if (unit[0] == 'e' && unit[1] == 'm') {
1326 return n * font_size;
1327 } else if (unit[0] == 'e' && unit[1] == 'x') {
1328 return n / 2.0 * font_size;
1329 } else if (unit[0] == 'p' && unit[1] == 'x') {
1330 return n;
1331 } else if (unit[0] == 'p' && unit[1] == 't') {
1332 return n * 1.25;
1333 } else if (unit[0] == 'p' && unit[1] == 'c') {
1334 return n * 15.0;
1335 } else if (unit[0] == 'm' && unit[1] == 'm') {
1336 return n * 3.543307;
1337 } else if (unit[0] == 'c' && unit[1] == 'm') {
1338 return n * 35.43307;
1339 } else if (unit[0] == 'i' && unit[1] == 'n') {
1340 return n * 90;
1341 }
1342
1343 return 0;
1344 }
1345
1346 float svgtiny_parse_length(dom_string *s, int viewport_size,
1347 const struct svgtiny_parse_state state)
1348 {
1349 char *ss = strndup(dom_string_data(s), dom_string_byte_length(s));
1350 float ret = _svgtiny_parse_length(ss, viewport_size, state);
1351 free(ss);
1352 return ret;
1353 }
1354
1355 /**
1356 * Parse paint attributes, if present.
1357 */
1358
1359 void svgtiny_parse_paint_attributes(dom_element *node,
1360 struct svgtiny_parse_state *state)
1361 {
1362 dom_string *attr;
1363 dom_exception exc;
1364
1365 exc = dom_element_get_attribute(node, state->interned_fill, &attr);
1366 if (exc == DOM_NO_ERR && attr != NULL) {
1367 svgtiny_parse_color(attr, &state->fill, state);
1368 dom_string_unref(attr);
1369 }
1370
1371 exc = dom_element_get_attribute(node, state->interned_stroke, &attr);
1372 if (exc == DOM_NO_ERR && attr != NULL) {
1373 svgtiny_parse_color(attr, &state->stroke, state);
1374 dom_string_unref(attr);
1375 }
1376
1377 exc = dom_element_get_attribute(node, state->interned_stroke_width, &attr);
1378 if (exc == DOM_NO_ERR && attr != NULL) {
1379 state->stroke_width = svgtiny_parse_length(attr,
1380 state->viewport_width, *state);
1381 dom_string_unref(attr);
1382 }
1383
1384 exc = dom_element_get_attribute(node, state->interned_style, &attr);
1385 if (exc == DOM_NO_ERR && attr != NULL) {
1386 char *style = strndup(dom_string_data(attr),
1387 dom_string_byte_length(attr));
1388 const char *s;
1389 char *value;
1390 if ((s = strstr(style, "fill:"))) {
1391 s += 5;
1392 while (*s == ' ')
1393 s++;
1394 value = strndup(s, strcspn(s, "; "));
1395 _svgtiny_parse_color(value, &state->fill, state);
1396 free(value);
1397 }
1398 if ((s = strstr(style, "stroke:"))) {
1399 s += 7;
1400 while (*s == ' ')
1401 s++;
1402 value = strndup(s, strcspn(s, "; "));
1403 _svgtiny_parse_color(value, &state->stroke, state);
1404 free(value);
1405 }
1406 if ((s = strstr(style, "stroke-width:"))) {
1407 s += 13;
1408 while (*s == ' ')
1409 s++;
1410 value = strndup(s, strcspn(s, "; "));
1411 state->stroke_width = _svgtiny_parse_length(value,
1412 state->viewport_width, *state);
1413 free(value);
1414 }
1415 free(style);
1416 dom_string_unref(attr);
1417 }
1418 }
1419
1420
1421 /**
1422 * Parse a colour.
1423 */
1424
1425 static void _svgtiny_parse_color(const char *s, svgtiny_colour *c,
1426 struct svgtiny_parse_state *state)
1427 {
1428 unsigned int r, g, b;
1429 float rf, gf, bf;
1430 size_t len = strlen(s);
1431 char *id = 0, *rparen;
1432
1433 if (len == 4 && s[0] == '#') {
1434 if (sscanf(s + 1, "%1x%1x%1x", &r, &g, &b) == 3)
1435 *c = svgtiny_RGB(r | r << 4, g | g << 4, b | b << 4);
1436
1437 } else if (len == 7 && s[0] == '#') {
1438 if (sscanf(s + 1, "%2x%2x%2x", &r, &g, &b) == 3)
1439 *c = svgtiny_RGB(r, g, b);
1440
1441 } else if (10 <= len && s[0] == 'r' && s[1] == 'g' && s[2] == 'b' &&
1442 s[3] == '(' && s[len - 1] == ')') {
1443 if (sscanf(s + 4, "%u,%u,%u", &r, &g, &b) == 3)
1444 *c = svgtiny_RGB(r, g, b);
1445 else if (sscanf(s + 4, "%f%%,%f%%,%f%%", &rf, &gf, &bf) == 3) {
1446 b = bf * 255 / 100;
1447 g = gf * 255 / 100;
1448 r = rf * 255 / 100;
1449 *c = svgtiny_RGB(r, g, b);
1450 }
1451
1452 } else if (len == 4 && strcmp(s, "none") == 0) {
1453 *c = svgtiny_TRANSPARENT;
1454
1455 } else if (5 < len && s[0] == 'u' && s[1] == 'r' && s[2] == 'l' &&
1456 s[3] == '(') {
1457 if (s[4] == '#') {
1458 id = strdup(s + 5);
1459 if (!id)
1460 return;
1461 rparen = strchr(id, ')');
1462 if (rparen)
1463 *rparen = 0;
1464 svgtiny_find_gradient(id, state);
1465 free(id);
1466 if (state->linear_gradient_stop_count == 0)
1467 *c = svgtiny_TRANSPARENT;
1468 else if (state->linear_gradient_stop_count == 1)
1469 *c = state->gradient_stop[0].color;
1470 else
1471 *c = svgtiny_LINEAR_GRADIENT;
1472 }
1473
1474 } else {
1475 const struct svgtiny_named_color *named_color;
1476 named_color = svgtiny_color_lookup(s, strlen(s));
1477 if (named_color)
1478 *c = named_color->color;
1479 }
1480 }
1481
1482 void svgtiny_parse_color(dom_string *s, svgtiny_colour *c,
1483 struct svgtiny_parse_state *state)
1484 {
1485 char *ss = strndup(dom_string_data(s), dom_string_byte_length(s));
1486 _svgtiny_parse_color(ss, c, state);
1487 free(ss);
1488 }
1489
1490 /**
1491 * Parse font attributes, if present.
1492 */
1493
1494 void svgtiny_parse_font_attributes(dom_element *node,
1495 struct svgtiny_parse_state *state)
1496 {
1497 /* TODO: Implement this, it never used to be */
1498 UNUSED(node);
1499 UNUSED(state);
1500 #ifdef WRITTEN_THIS_PROPERLY
1501 const xmlAttr *attr;
1502
1503 UNUSED(state);
1504
1505 for (attr = node->properties; attr; attr = attr->next) {
1506 if (strcmp((const char *) attr->name, "font-size") == 0) {
1507 /*if (css_parse_length(
1508 (const char *) attr->children->content,
1509 &state->style.font_size.value.length,
1510 true, true)) {
1511 state->style.font_size.size =
1512 CSS_FONT_SIZE_LENGTH;
1513 }*/
1514 }
1515 }
1516 #endif
1517 }
1518
1519
1520 /**
1521 * Parse transform attributes, if present.
1522 *
1523 * http://www.w3.org/TR/SVG11/coords#TransformAttribute
1524 */
1525
1526 void svgtiny_parse_transform_attributes(dom_element *node,
1527 struct svgtiny_parse_state *state)
1528 {
1529 char *transform;
1530 dom_string *attr;
1531 dom_exception exc;
1532
1533 exc = dom_element_get_attribute(node, state->interned_transform,
1534 &attr);
1535 if (exc == DOM_NO_ERR && attr != NULL) {
1536 transform = strndup(dom_string_data(attr),
1537 dom_string_byte_length(attr));
1538 svgtiny_parse_transform(transform, &state->ctm.a, &state->ctm.b,
1539 &state->ctm.c, &state->ctm.d,
1540 &state->ctm.e, &state->ctm.f);
1541 free(transform);
1542 dom_string_unref(attr);
1543 }
1544 }
1545
1546
1547 /**
1548 * Parse a transform string.
1549 */
1550
1551 void svgtiny_parse_transform(char *s, float *ma, float *mb,
1552 float *mc, float *md, float *me, float *mf)
1553 {
1554 float a, b, c, d, e, f;
1555 float za, zb, zc, zd, ze, zf;
1556 float angle, x, y;
1557 int n;
1558 unsigned int i;
1559
1560 for (i = 0; s[i]; i++)
1561 if (s[i] == ',')
1562 s[i] = ' ';
1563
1564 while (*s) {
1565 a = d = 1;
1566 b = c = 0;
1567 e = f = 0;
1568 if ((sscanf(s, " matrix (%f %f %f %f %f %f ) %n",
1569 &a, &b, &c, &d, &e, &f, &n) == 6) && (n > 0))
1570 ;
1571 else if ((sscanf(s, " translate (%f %f ) %n",
1572 &e, &f, &n) == 2) && (n > 0))
1573 ;
1574 else if ((sscanf(s, " translate (%f ) %n",
1575 &e, &n) == 1) && (n > 0))
1576 ;
1577 else if ((sscanf(s, " scale (%f %f ) %n",
1578 &a, &d, &n) == 2) && (n > 0))
1579 ;
1580 else if ((sscanf(s, " scale (%f ) %n",
1581 &a, &n) == 1) && (n > 0))
1582 d = a;
1583 else if ((sscanf(s, " rotate (%f %f %f ) %n",
1584 &angle, &x, &y, &n) == 3) && (n > 0)) {
1585 angle = angle / 180 * M_PI;
1586 a = cos(angle);
1587 b = sin(angle);
1588 c = -sin(angle);
1589 d = cos(angle);
1590 e = -x * cos(angle) + y * sin(angle) + x;
1591 f = -x * sin(angle) - y * cos(angle) + y;
1592 } else if ((sscanf(s, " rotate (%f ) %n",
1593 &angle, &n) == 1) && (n > 0)) {
1594 angle = angle / 180 * M_PI;
1595 a = cos(angle);
1596 b = sin(angle);
1597 c = -sin(angle);
1598 d = cos(angle);
1599 } else if ((sscanf(s, " skewX (%f ) %n",
1600 &angle, &n) == 1) && (n > 0)) {
1601 angle = angle / 180 * M_PI;
1602 c = tan(angle);
1603 } else if ((sscanf(s, " skewY (%f ) %n",
1604 &angle, &n) == 1) && (n > 0)) {
1605 angle = angle / 180 * M_PI;
1606 b = tan(angle);
1607 } else
1608 break;
1609 za = *ma * a + *mc * b;
1610 zb = *mb * a + *md * b;
1611 zc = *ma * c + *mc * d;
1612 zd = *mb * c + *md * d;
1613 ze = *ma * e + *mc * f + *me;
1614 zf = *mb * e + *md * f + *mf;
1615 *ma = za;
1616 *mb = zb;
1617 *mc = zc;
1618 *md = zd;
1619 *me = ze;
1620 *mf = zf;
1621 s += n;
1622 }
1623 }
1624
1625
1626 /**
1627 * Add a path to the svgtiny_diagram.
1628 */
1629
1630 svgtiny_code svgtiny_add_path(float *p, unsigned int n,
1631 struct svgtiny_parse_state *state)
1632 {
1633 struct svgtiny_shape *shape;
1634
1635 if (state->fill == svgtiny_LINEAR_GRADIENT)
1636 return svgtiny_add_path_linear_gradient(p, n, state);
1637
1638 svgtiny_transform_path(p, n, state);
1639
1640 shape = svgtiny_add_shape(state);
1641 if (!shape) {
1642 free(p);
1643 return svgtiny_OUT_OF_MEMORY;
1644 }
1645 shape->path = p;
1646 shape->path_length = n;
1647 state->diagram->shape_count++;
1648
1649 return svgtiny_OK;
1650 }
1651
1652
1653 /**
1654 * Add a svgtiny_shape to the svgtiny_diagram.
1655 */
1656
1657 struct svgtiny_shape *svgtiny_add_shape(struct svgtiny_parse_state *state)
1658 {
1659 struct svgtiny_shape *shape = realloc(state->diagram->shape,
1660 (state->diagram->shape_count + 1) *
1661 sizeof (state->diagram->shape[0]));
1662 if (!shape)
1663 return 0;
1664 state->diagram->shape = shape;
1665
1666 shape += state->diagram->shape_count;
1667 shape->path = 0;
1668 shape->path_length = 0;
1669 shape->text = 0;
1670 shape->fill = state->fill;
1671 shape->stroke = state->stroke;
1672 shape->stroke_width = lroundf((float) state->stroke_width *
1673 (state->ctm.a + state->ctm.d) / 2.0);
1674 if (0 < state->stroke_width && shape->stroke_width == 0)
1675 shape->stroke_width = 1;
1676
1677 return shape;
1678 }
1679
1680
1681 /**
1682 * Apply the current transformation matrix to a path.
1683 */
1684
1685 void svgtiny_transform_path(float *p, unsigned int n,
1686 struct svgtiny_parse_state *state)
1687 {
1688 unsigned int j;
1689
1690 for (j = 0; j != n; ) {
1691 unsigned int points = 0;
1692 unsigned int k;
1693 switch ((int) p[j]) {
1694 case svgtiny_PATH_MOVE:
1695 case svgtiny_PATH_LINE:
1696 points = 1;
1697 break;
1698 case svgtiny_PATH_CLOSE:
1699 points = 0;
1700 break;
1701 case svgtiny_PATH_BEZIER:
1702 points = 3;
1703 break;
1704 default:
1705 assert(0);
1706 }
1707 j++;
1708 for (k = 0; k != points; k++) {
1709 float x0 = p[j], y0 = p[j + 1];
1710 float x = state->ctm.a * x0 + state->ctm.c * y0 +
1711 state->ctm.e;
1712 float y = state->ctm.b * x0 + state->ctm.d * y0 +
1713 state->ctm.f;
1714 p[j] = x;
1715 p[j + 1] = y;
1716 j += 2;
1717 }
1718 }
1719 }
1720
1721
1722 /**
1723 * Free all memory used by a diagram.
1724 */
1725
1726 void svgtiny_free(struct svgtiny_diagram *svg)
1727 {
1728 unsigned int i;
1729 assert(svg);
1730
1731 for (i = 0; i != svg->shape_count; i++) {
1732 free(svg->shape[i].path);
1733 free(svg->shape[i].text);
1734 }
1735
1736 free(svg->shape);
1737
1738 free(svg);
1739 }
1740
1741 #ifndef HAVE_STRNDUP
1742 char *svgtiny_strndup(const char *s, size_t n)
1743 {
1744 size_t len;
1745 char *s2;
1746
1747 for (len = 0; len != n && s[len]; len++)
1748 continue;
1749
1750 s2 = malloc(len + 1);
1751 if (s2 == NULL)
1752 return NULL;
1753
1754 memcpy(s2, s, len);
1755 s2[len] = '\0';
1756
1757 return s2;
1758 }
1759 #endif
1760