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