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