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