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