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