]> gitweb.michael.orlitzky.com - libsvgtiny.git/blob - src/svgtiny.c
63c7cc2bf70d26bc9e21f16e17ab2fb9a58a5160
[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(const 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(const dom_element *node,
49 struct svgtiny_parse_state *state);
50 static void svgtiny_parse_font_attributes(const 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
57
58 /**
59 * Create a new svgtiny_diagram structure.
60 */
61
62 struct svgtiny_diagram *svgtiny_create(void)
63 {
64 struct svgtiny_diagram *diagram;
65
66 diagram = calloc(sizeof(*diagram), 1);
67 if (!diagram)
68 return 0;
69
70 return diagram;
71 free(diagram);
72 return NULL;
73 }
74
75 static void ignore_msg(uint32_t severity, void *ctx, const char *msg, ...)
76 {
77 UNUSED(severity);
78 UNUSED(ctx);
79 UNUSED(msg);
80 }
81
82 /**
83 * Parse a block of memory into a svgtiny_diagram.
84 */
85
86 svgtiny_code svgtiny_parse(struct svgtiny_diagram *diagram,
87 const char *buffer, size_t size, const char *url,
88 int viewport_width, int viewport_height)
89 {
90 dom_document *document;
91 dom_exception exc;
92 dom_xml_parser *parser;
93 dom_xml_error err;
94 dom_element *svg;
95 dom_string *svg_name;
96 lwc_string *svg_name_lwc;
97 struct svgtiny_parse_state state;
98 float x, y, width, height;
99 svgtiny_code code;
100
101 assert(diagram);
102 assert(buffer);
103 assert(url);
104
105 UNUSED(url);
106
107 parser = dom_xml_parser_create(NULL, NULL,
108 ignore_msg, NULL, &document);
109
110 if (parser == NULL)
111 return svgtiny_LIBDOM_ERROR;
112
113 err = dom_xml_parser_parse_chunk(parser, (uint8_t *)buffer, size);
114 if (err != DOM_XML_OK) {
115 dom_node_unref(document);
116 dom_xml_parser_destroy(parser);
117 return svgtiny_LIBDOM_ERROR;
118 }
119
120 err = dom_xml_parser_completed(parser);
121 if (err != DOM_XML_OK) {
122 dom_node_unref(document);
123 dom_xml_parser_destroy(parser);
124 return svgtiny_LIBDOM_ERROR;
125 }
126
127 /* We're done parsing, drop the parser.
128 * We now own the document entirely.
129 */
130 dom_xml_parser_destroy(parser);
131
132 /* find root <svg> element */
133 exc = dom_document_get_document_element(document, &svg);
134 if (exc != DOM_NO_ERR) {
135 dom_node_unref(document);
136 return svgtiny_LIBDOM_ERROR;
137 }
138 exc = dom_node_get_node_name(svg, &svg_name);
139 if (exc != DOM_NO_ERR) {
140 dom_node_unref(svg);
141 dom_node_unref(document);
142 return svgtiny_LIBDOM_ERROR;
143 }
144 if (lwc_intern_string("svg", 3 /* SLEN("svg") */,
145 &svg_name_lwc) != lwc_error_ok) {
146 dom_string_unref(svg_name);
147 dom_node_unref(svg);
148 dom_node_unref(document);
149 return svgtiny_LIBDOM_ERROR;
150 }
151 if (!dom_string_caseless_lwc_isequal(svg_name, svg_name_lwc)) {
152 lwc_string_unref(svg_name_lwc);
153 dom_string_unref(svg_name);
154 dom_node_unref(svg);
155 dom_node_unref(document);
156 return svgtiny_NOT_SVG;
157 }
158
159 lwc_string_unref(svg_name_lwc);
160 dom_string_unref(svg_name);
161
162 /* get graphic dimensions */
163 state.diagram = diagram;
164 state.document = document;
165 state.viewport_width = viewport_width;
166 state.viewport_height = viewport_height;
167
168 #define SVGTINY_STRING_ACTION(s) \
169 if (dom_string_create_interned((const uint8_t *) #s, \
170 strlen(#s), &state.interned_##s) \
171 != DOM_NO_ERR) { \
172 code = svgtiny_LIBDOM_ERROR; \
173 goto cleanup; \
174 }
175 #include "svgtiny_strings.h"
176 #undef SVGTINY_STRING_ACTION
177
178 svgtiny_parse_position_attributes(svg, state, &x, &y, &width, &height);
179 diagram->width = width;
180 diagram->height = height;
181
182 /* set up parsing state */
183 state.viewport_width = width;
184 state.viewport_height = height;
185 state.ctm.a = 1; /*(float) viewport_width / (float) width;*/
186 state.ctm.b = 0;
187 state.ctm.c = 0;
188 state.ctm.d = 1; /*(float) viewport_height / (float) height;*/
189 state.ctm.e = 0; /*x;*/
190 state.ctm.f = 0; /*y;*/
191 /*state.style = css_base_style;
192 state.style.font_size.value.length.value = option_font_size * 0.1;*/
193 state.fill = 0x000000;
194 state.stroke = svgtiny_TRANSPARENT;
195 state.stroke_width = 1;
196 state.linear_gradient_stop_count = 0;
197
198 /* parse tree */
199 code = svgtiny_parse_svg(svg, state);
200
201 dom_node_unref(svg);
202 dom_node_unref(document);
203
204 cleanup:
205 #define SVGTINY_STRING_ACTION(s) \
206 if (state.interned_##s != NULL) \
207 dom_string_unref(state.interned_##s);
208 //#include "svgtiny_strings.h"
209 #undef SVGTINY_STRING_ACTION
210 return code;
211 }
212
213
214 /**
215 * Parse a <svg> or <g> element node.
216 */
217
218 svgtiny_code svgtiny_parse_svg(dom_element *svg,
219 struct svgtiny_parse_state state)
220 {
221 float x, y, width, height;
222 dom_string *view_box;
223 dom_element *child;
224 dom_exception exc;
225
226 svgtiny_parse_position_attributes(svg, state, &x, &y, &width, &height);
227 svgtiny_parse_paint_attributes(svg, &state);
228 svgtiny_parse_font_attributes(svg, &state);
229
230 exc = dom_element_get_attribute(svg, state.interned_viewBox,
231 &view_box);
232 if (exc != DOM_NO_ERR) {
233 return svgtiny_LIBDOM_ERROR;
234 }
235
236 if (view_box) {
237 char *s = strndup(dom_string_data(view_box),
238 dom_string_length(view_box));
239 float min_x, min_y, vwidth, vheight;
240 if (sscanf(s, "%f,%f,%f,%f",
241 &min_x, &min_y, &vwidth, &vheight) == 4 ||
242 sscanf(s, "%f %f %f %f",
243 &min_x, &min_y, &vwidth, &vheight) == 4) {
244 state.ctm.a = (float) state.viewport_width / vwidth;
245 state.ctm.d = (float) state.viewport_height / vheight;
246 state.ctm.e += -min_x * state.ctm.a;
247 state.ctm.f += -min_y * state.ctm.d;
248 }
249 free(s);
250 dom_string_unref(view_box);
251 }
252
253 svgtiny_parse_transform_attributes(svg, &state);
254
255 exc = dom_node_get_first_child(svg, &child);
256 if (exc != DOM_NO_ERR) {
257 return svgtiny_LIBDOM_ERROR;
258 }
259 while (child != NULL) {
260 dom_element *next;
261 dom_node_type nodetype;
262 svgtiny_code code = svgtiny_OK;
263
264 exc = dom_node_get_node_type(child, &nodetype);
265 if (exc != DOM_NO_ERR) {
266 dom_node_unref(child);
267 return svgtiny_LIBDOM_ERROR;
268 }
269 if (nodetype == DOM_ELEMENT_NODE) {
270 dom_string *nodename;
271 exc = dom_node_get_node_name(child, &nodename);
272 if (exc != DOM_NO_ERR) {
273 dom_node_unref(child);
274 return svgtiny_LIBDOM_ERROR;
275 }
276 if (dom_string_caseless_isequal(state.interned_svg,
277 nodename))
278 code = svgtiny_parse_svg(child, state);
279 else if (dom_string_caseless_isequal(state.interned_g,
280 nodename))
281 code = svgtiny_parse_svg(child, state);
282 else if (dom_string_caseless_isequal(state.interned_a,
283 nodename))
284 code = svgtiny_parse_svg(child, state);
285 else if (dom_string_caseless_isequal(state.interned_path,
286 nodename))
287 code = svgtiny_parse_path(child, state);
288 else if (dom_string_caseless_isequal(state.interned_rect,
289 nodename))
290 code = svgtiny_parse_rect(child, state);
291 else if (dom_string_caseless_isequal(state.interned_circle,
292 nodename))
293 code = svgtiny_parse_circle(child, state);
294 else if (dom_string_caseless_isequal(state.interned_ellipse,
295 nodename))
296 code = svgtiny_parse_ellipse(child, state);
297 else if (dom_string_caseless_isequal(state.interned_line,
298 nodename))
299 code = svgtiny_parse_line(child, state);
300 else if (dom_string_caseless_isequal(state.interned_polyline,
301 nodename))
302 code = svgtiny_parse_poly(child, state, false);
303 else if (dom_string_caseless_isequal(state.interned_polygon,
304 nodename))
305 code = svgtiny_parse_poly(child, state, true);
306 else if (dom_string_caseless_isequal(state.interned_text,
307 nodename))
308 code = svgtiny_parse_text(child, state);
309 dom_string_unref(nodename);
310 }
311 if (code != svgtiny_OK) {
312 dom_node_unref(child);
313 return code;
314 }
315 exc = dom_node_get_next_sibling(child, &next);
316 dom_node_unref(child);
317 if (exc != DOM_NO_ERR) {
318 return svgtiny_LIBDOM_ERROR;
319 }
320 child = next;
321 }
322
323 return svgtiny_OK;
324 }
325
326
327
328 /**
329 * Parse a <path> element node.
330 *
331 * http://www.w3.org/TR/SVG11/paths#PathElement
332 */
333
334 svgtiny_code svgtiny_parse_path(dom_element *path,
335 struct svgtiny_parse_state state)
336 {
337 char *s, *path_d;
338 float *p;
339 unsigned int i;
340 float last_x = 0, last_y = 0;
341 float last_cubic_x = 0, last_cubic_y = 0;
342 float last_quad_x = 0, last_quad_y = 0;
343
344 svgtiny_parse_paint_attributes(path, &state);
345 svgtiny_parse_transform_attributes(path, &state);
346
347 /* read d attribute */
348 s = path_d = (char *) xmlGetProp(path, (const xmlChar *) "d");
349 if (!s) {
350 state.diagram->error_line = path->line;
351 state.diagram->error_message = "path: missing d attribute";
352 return svgtiny_SVG_ERROR;
353 }
354
355 /* allocate space for path: it will never have more elements than d */
356 p = malloc(sizeof p[0] * strlen(s));
357 if (!p)
358 return svgtiny_OUT_OF_MEMORY;
359
360 /* parse d and build path */
361 for (i = 0; s[i]; i++)
362 if (s[i] == ',')
363 s[i] = ' ';
364 i = 0;
365 while (*s) {
366 char command[2];
367 int plot_command;
368 float x, y, x1, y1, x2, y2, rx, ry, rotation, large_arc, sweep;
369 int n;
370
371 /* moveto (M, m), lineto (L, l) (2 arguments) */
372 if (sscanf(s, " %1[MmLl] %f %f %n", command, &x, &y, &n) == 3) {
373 /*LOG(("moveto or lineto"));*/
374 if (*command == 'M' || *command == 'm')
375 plot_command = svgtiny_PATH_MOVE;
376 else
377 plot_command = svgtiny_PATH_LINE;
378 do {
379 p[i++] = plot_command;
380 if ('a' <= *command) {
381 x += last_x;
382 y += last_y;
383 }
384 p[i++] = last_cubic_x = last_quad_x = last_x
385 = x;
386 p[i++] = last_cubic_y = last_quad_y = last_y
387 = y;
388 s += n;
389 plot_command = svgtiny_PATH_LINE;
390 } while (sscanf(s, "%f %f %n", &x, &y, &n) == 2);
391
392 /* closepath (Z, z) (no arguments) */
393 } else if (sscanf(s, " %1[Zz] %n", command, &n) == 1) {
394 /*LOG(("closepath"));*/
395 p[i++] = svgtiny_PATH_CLOSE;
396 s += n;
397
398 /* horizontal lineto (H, h) (1 argument) */
399 } else if (sscanf(s, " %1[Hh] %f %n", command, &x, &n) == 2) {
400 /*LOG(("horizontal lineto"));*/
401 do {
402 p[i++] = svgtiny_PATH_LINE;
403 if (*command == 'h')
404 x += last_x;
405 p[i++] = last_cubic_x = last_quad_x = last_x
406 = x;
407 p[i++] = last_cubic_y = last_quad_y = last_y;
408 s += n;
409 } while (sscanf(s, "%f %n", &x, &n) == 1);
410
411 /* vertical lineto (V, v) (1 argument) */
412 } else if (sscanf(s, " %1[Vv] %f %n", command, &y, &n) == 2) {
413 /*LOG(("vertical lineto"));*/
414 do {
415 p[i++] = svgtiny_PATH_LINE;
416 if (*command == 'v')
417 y += last_y;
418 p[i++] = last_cubic_x = last_quad_x = last_x;
419 p[i++] = last_cubic_y = last_quad_y = last_y
420 = y;
421 s += n;
422 } while (sscanf(s, "%f %n", &x, &n) == 1);
423
424 /* curveto (C, c) (6 arguments) */
425 } else if (sscanf(s, " %1[Cc] %f %f %f %f %f %f %n", command,
426 &x1, &y1, &x2, &y2, &x, &y, &n) == 7) {
427 /*LOG(("curveto"));*/
428 do {
429 p[i++] = svgtiny_PATH_BEZIER;
430 if (*command == 'c') {
431 x1 += last_x;
432 y1 += last_y;
433 x2 += last_x;
434 y2 += last_y;
435 x += last_x;
436 y += last_y;
437 }
438 p[i++] = x1;
439 p[i++] = y1;
440 p[i++] = last_cubic_x = x2;
441 p[i++] = last_cubic_y = y2;
442 p[i++] = last_quad_x = last_x = x;
443 p[i++] = last_quad_y = last_y = y;
444 s += n;
445 } while (sscanf(s, "%f %f %f %f %f %f %n",
446 &x1, &y1, &x2, &y2, &x, &y, &n) == 6);
447
448 /* shorthand/smooth curveto (S, s) (4 arguments) */
449 } else if (sscanf(s, " %1[Ss] %f %f %f %f %n", command,
450 &x2, &y2, &x, &y, &n) == 5) {
451 /*LOG(("shorthand/smooth curveto"));*/
452 do {
453 p[i++] = svgtiny_PATH_BEZIER;
454 x1 = last_x + (last_x - last_cubic_x);
455 y1 = last_y + (last_y - last_cubic_y);
456 if (*command == 's') {
457 x2 += last_x;
458 y2 += last_y;
459 x += last_x;
460 y += last_y;
461 }
462 p[i++] = x1;
463 p[i++] = y1;
464 p[i++] = last_cubic_x = x2;
465 p[i++] = last_cubic_y = y2;
466 p[i++] = last_quad_x = last_x = x;
467 p[i++] = last_quad_y = last_y = y;
468 s += n;
469 } while (sscanf(s, "%f %f %f %f %n",
470 &x2, &y2, &x, &y, &n) == 4);
471
472 /* quadratic Bezier curveto (Q, q) (4 arguments) */
473 } else if (sscanf(s, " %1[Qq] %f %f %f %f %n", command,
474 &x1, &y1, &x, &y, &n) == 5) {
475 /*LOG(("quadratic Bezier curveto"));*/
476 do {
477 p[i++] = svgtiny_PATH_BEZIER;
478 last_quad_x = x1;
479 last_quad_y = y1;
480 if (*command == 'q') {
481 x1 += last_x;
482 y1 += last_y;
483 x += last_x;
484 y += last_y;
485 }
486 p[i++] = 1./3 * last_x + 2./3 * x1;
487 p[i++] = 1./3 * last_y + 2./3 * y1;
488 p[i++] = 2./3 * x1 + 1./3 * x;
489 p[i++] = 2./3 * y1 + 1./3 * y;
490 p[i++] = last_cubic_x = last_x = x;
491 p[i++] = last_cubic_y = last_y = y;
492 s += n;
493 } while (sscanf(s, "%f %f %f %f %n",
494 &x1, &y1, &x, &y, &n) == 4);
495
496 /* shorthand/smooth quadratic Bezier curveto (T, t)
497 (2 arguments) */
498 } else if (sscanf(s, " %1[Tt] %f %f %n", command,
499 &x, &y, &n) == 3) {
500 /*LOG(("shorthand/smooth quadratic Bezier curveto"));*/
501 do {
502 p[i++] = svgtiny_PATH_BEZIER;
503 x1 = last_x + (last_x - last_quad_x);
504 y1 = last_y + (last_y - last_quad_y);
505 last_quad_x = x1;
506 last_quad_y = y1;
507 if (*command == 't') {
508 x1 += last_x;
509 y1 += last_y;
510 x += last_x;
511 y += last_y;
512 }
513 p[i++] = 1./3 * last_x + 2./3 * x1;
514 p[i++] = 1./3 * last_y + 2./3 * y1;
515 p[i++] = 2./3 * x1 + 1./3 * x;
516 p[i++] = 2./3 * y1 + 1./3 * y;
517 p[i++] = last_cubic_x = last_x = x;
518 p[i++] = last_cubic_y = last_y = y;
519 s += n;
520 } while (sscanf(s, "%f %f %n",
521 &x, &y, &n) == 2);
522
523 /* elliptical arc (A, a) (7 arguments) */
524 } else if (sscanf(s, " %1[Aa] %f %f %f %f %f %f %f %n", command,
525 &rx, &ry, &rotation, &large_arc, &sweep,
526 &x, &y, &n) == 8) {
527 do {
528 p[i++] = svgtiny_PATH_LINE;
529 if (*command == 'a') {
530 x += last_x;
531 y += last_y;
532 }
533 p[i++] = last_cubic_x = last_quad_x = last_x
534 = x;
535 p[i++] = last_cubic_y = last_quad_y = last_y
536 = y;
537 s += n;
538 } while (sscanf(s, "%f %f %f %f %f %f %f %n",
539 &rx, &ry, &rotation, &large_arc, &sweep,
540 &x, &y, &n) == 7);
541
542 } else {
543 fprintf(stderr, "parse failed at \"%s\"\n", s);
544 break;
545 }
546 }
547
548 xmlFree(path_d);
549
550 if (i <= 4) {
551 /* no real segments in path */
552 free(p);
553 return svgtiny_OK;
554 }
555
556 return svgtiny_add_path(p, i, &state);
557 }
558
559
560 /**
561 * Parse a <rect> element node.
562 *
563 * http://www.w3.org/TR/SVG11/shapes#RectElement
564 */
565
566 svgtiny_code svgtiny_parse_rect(dom_element *rect,
567 struct svgtiny_parse_state state)
568 {
569 float x, y, width, height;
570 float *p;
571
572 svgtiny_parse_position_attributes(rect, state,
573 &x, &y, &width, &height);
574 svgtiny_parse_paint_attributes(rect, &state);
575 svgtiny_parse_transform_attributes(rect, &state);
576
577 p = malloc(13 * sizeof p[0]);
578 if (!p)
579 return svgtiny_OUT_OF_MEMORY;
580
581 p[0] = svgtiny_PATH_MOVE;
582 p[1] = x;
583 p[2] = y;
584 p[3] = svgtiny_PATH_LINE;
585 p[4] = x + width;
586 p[5] = y;
587 p[6] = svgtiny_PATH_LINE;
588 p[7] = x + width;
589 p[8] = y + height;
590 p[9] = svgtiny_PATH_LINE;
591 p[10] = x;
592 p[11] = y + height;
593 p[12] = svgtiny_PATH_CLOSE;
594
595 return svgtiny_add_path(p, 13, &state);
596 }
597
598
599 /**
600 * Parse a <circle> element node.
601 */
602
603 svgtiny_code svgtiny_parse_circle(dom_element *circle,
604 struct svgtiny_parse_state state)
605 {
606 float x = 0, y = 0, r = -1;
607 float *p;
608 xmlAttr *attr;
609
610 for (attr = circle->properties; attr; attr = attr->next) {
611 const char *name = (const char *) attr->name;
612 const char *content = (const char *) attr->children->content;
613 if (strcmp(name, "cx") == 0)
614 x = svgtiny_parse_length(content,
615 state.viewport_width, state);
616 else if (strcmp(name, "cy") == 0)
617 y = svgtiny_parse_length(content,
618 state.viewport_height, state);
619 else if (strcmp(name, "r") == 0)
620 r = svgtiny_parse_length(content,
621 state.viewport_width, state);
622 }
623 svgtiny_parse_paint_attributes(circle, &state);
624 svgtiny_parse_transform_attributes(circle, &state);
625
626 if (r < 0) {
627 state.diagram->error_line = circle->line;
628 state.diagram->error_message = "circle: r missing or negative";
629 return svgtiny_SVG_ERROR;
630 }
631 if (r == 0)
632 return svgtiny_OK;
633
634 p = malloc(32 * sizeof p[0]);
635 if (!p)
636 return svgtiny_OUT_OF_MEMORY;
637
638 p[0] = svgtiny_PATH_MOVE;
639 p[1] = x + r;
640 p[2] = y;
641 p[3] = svgtiny_PATH_BEZIER;
642 p[4] = x + r;
643 p[5] = y + r * KAPPA;
644 p[6] = x + r * KAPPA;
645 p[7] = y + r;
646 p[8] = x;
647 p[9] = y + r;
648 p[10] = svgtiny_PATH_BEZIER;
649 p[11] = x - r * KAPPA;
650 p[12] = y + r;
651 p[13] = x - r;
652 p[14] = y + r * KAPPA;
653 p[15] = x - r;
654 p[16] = y;
655 p[17] = svgtiny_PATH_BEZIER;
656 p[18] = x - r;
657 p[19] = y - r * KAPPA;
658 p[20] = x - r * KAPPA;
659 p[21] = y - r;
660 p[22] = x;
661 p[23] = y - r;
662 p[24] = svgtiny_PATH_BEZIER;
663 p[25] = x + r * KAPPA;
664 p[26] = y - r;
665 p[27] = x + r;
666 p[28] = y - r * KAPPA;
667 p[29] = x + r;
668 p[30] = y;
669 p[31] = svgtiny_PATH_CLOSE;
670
671 return svgtiny_add_path(p, 32, &state);
672 }
673
674
675 /**
676 * Parse an <ellipse> element node.
677 */
678
679 svgtiny_code svgtiny_parse_ellipse(dom_element *ellipse,
680 struct svgtiny_parse_state state)
681 {
682 float x = 0, y = 0, rx = -1, ry = -1;
683 float *p;
684 xmlAttr *attr;
685
686 for (attr = ellipse->properties; attr; attr = attr->next) {
687 const char *name = (const char *) attr->name;
688 const char *content = (const char *) attr->children->content;
689 if (strcmp(name, "cx") == 0)
690 x = svgtiny_parse_length(content,
691 state.viewport_width, state);
692 else if (strcmp(name, "cy") == 0)
693 y = svgtiny_parse_length(content,
694 state.viewport_height, state);
695 else if (strcmp(name, "rx") == 0)
696 rx = svgtiny_parse_length(content,
697 state.viewport_width, state);
698 else if (strcmp(name, "ry") == 0)
699 ry = svgtiny_parse_length(content,
700 state.viewport_width, state);
701 }
702 svgtiny_parse_paint_attributes(ellipse, &state);
703 svgtiny_parse_transform_attributes(ellipse, &state);
704
705 if (rx < 0 || ry < 0) {
706 state.diagram->error_line = ellipse->line;
707 state.diagram->error_message = "ellipse: rx or ry missing "
708 "or negative";
709 return svgtiny_SVG_ERROR;
710 }
711 if (rx == 0 || ry == 0)
712 return svgtiny_OK;
713
714 p = malloc(32 * sizeof p[0]);
715 if (!p)
716 return svgtiny_OUT_OF_MEMORY;
717
718 p[0] = svgtiny_PATH_MOVE;
719 p[1] = x + rx;
720 p[2] = y;
721 p[3] = svgtiny_PATH_BEZIER;
722 p[4] = x + rx;
723 p[5] = y + ry * KAPPA;
724 p[6] = x + rx * KAPPA;
725 p[7] = y + ry;
726 p[8] = x;
727 p[9] = y + ry;
728 p[10] = svgtiny_PATH_BEZIER;
729 p[11] = x - rx * KAPPA;
730 p[12] = y + ry;
731 p[13] = x - rx;
732 p[14] = y + ry * KAPPA;
733 p[15] = x - rx;
734 p[16] = y;
735 p[17] = svgtiny_PATH_BEZIER;
736 p[18] = x - rx;
737 p[19] = y - ry * KAPPA;
738 p[20] = x - rx * KAPPA;
739 p[21] = y - ry;
740 p[22] = x;
741 p[23] = y - ry;
742 p[24] = svgtiny_PATH_BEZIER;
743 p[25] = x + rx * KAPPA;
744 p[26] = y - ry;
745 p[27] = x + rx;
746 p[28] = y - ry * KAPPA;
747 p[29] = x + rx;
748 p[30] = y;
749 p[31] = svgtiny_PATH_CLOSE;
750
751 return svgtiny_add_path(p, 32, &state);
752 }
753
754
755 /**
756 * Parse a <line> element node.
757 */
758
759 svgtiny_code svgtiny_parse_line(dom_element *line,
760 struct svgtiny_parse_state state)
761 {
762 float x1 = 0, y1 = 0, x2 = 0, y2 = 0;
763 float *p;
764 xmlAttr *attr;
765
766 for (attr = line->properties; attr; attr = attr->next) {
767 const char *name = (const char *) attr->name;
768 const char *content = (const char *) attr->children->content;
769 if (strcmp(name, "x1") == 0)
770 x1 = svgtiny_parse_length(content,
771 state.viewport_width, state);
772 else if (strcmp(name, "y1") == 0)
773 y1 = svgtiny_parse_length(content,
774 state.viewport_height, state);
775 else if (strcmp(name, "x2") == 0)
776 x2 = svgtiny_parse_length(content,
777 state.viewport_width, state);
778 else if (strcmp(name, "y2") == 0)
779 y2 = svgtiny_parse_length(content,
780 state.viewport_height, state);
781 }
782 svgtiny_parse_paint_attributes(line, &state);
783 svgtiny_parse_transform_attributes(line, &state);
784
785 p = malloc(7 * sizeof p[0]);
786 if (!p)
787 return svgtiny_OUT_OF_MEMORY;
788
789 p[0] = svgtiny_PATH_MOVE;
790 p[1] = x1;
791 p[2] = y1;
792 p[3] = svgtiny_PATH_LINE;
793 p[4] = x2;
794 p[5] = y2;
795 p[6] = svgtiny_PATH_CLOSE;
796
797 return svgtiny_add_path(p, 7, &state);
798 }
799
800
801 /**
802 * Parse a <polyline> or <polygon> element node.
803 *
804 * http://www.w3.org/TR/SVG11/shapes#PolylineElement
805 * http://www.w3.org/TR/SVG11/shapes#PolygonElement
806 */
807
808 svgtiny_code svgtiny_parse_poly(dom_element *poly,
809 struct svgtiny_parse_state state, bool polygon)
810 {
811 char *s, *points;
812 float *p;
813 unsigned int i;
814
815 svgtiny_parse_paint_attributes(poly, &state);
816 svgtiny_parse_transform_attributes(poly, &state);
817
818 /* read points attribute */
819 s = points = (char *) xmlGetProp(poly, (const xmlChar *) "points");
820 if (!s) {
821 state.diagram->error_line = poly->line;
822 state.diagram->error_message =
823 "polyline/polygon: missing points attribute";
824 return svgtiny_SVG_ERROR;
825 }
826
827 /* allocate space for path: it will never have more elements than s */
828 p = malloc(sizeof p[0] * strlen(s));
829 if (!p) {
830 xmlFree(points);
831 return svgtiny_OUT_OF_MEMORY;
832 }
833
834 /* parse s and build path */
835 for (i = 0; s[i]; i++)
836 if (s[i] == ',')
837 s[i] = ' ';
838 i = 0;
839 while (*s) {
840 float x, y;
841 int n;
842
843 if (sscanf(s, "%f %f %n", &x, &y, &n) == 2) {
844 if (i == 0)
845 p[i++] = svgtiny_PATH_MOVE;
846 else
847 p[i++] = svgtiny_PATH_LINE;
848 p[i++] = x;
849 p[i++] = y;
850 s += n;
851 } else {
852 break;
853 }
854 }
855 if (polygon)
856 p[i++] = svgtiny_PATH_CLOSE;
857
858 xmlFree(points);
859
860 return svgtiny_add_path(p, i, &state);
861 }
862
863
864 /**
865 * Parse a <text> or <tspan> element node.
866 */
867
868 svgtiny_code svgtiny_parse_text(dom_element *text,
869 struct svgtiny_parse_state state)
870 {
871 float x, y, width, height;
872 float px, py;
873 dom_element *child;
874
875 svgtiny_parse_position_attributes(text, state,
876 &x, &y, &width, &height);
877 svgtiny_parse_font_attributes(text, &state);
878 svgtiny_parse_transform_attributes(text, &state);
879
880 px = state.ctm.a * x + state.ctm.c * y + state.ctm.e;
881 py = state.ctm.b * x + state.ctm.d * y + state.ctm.f;
882 /* state.ctm.e = px - state.origin_x; */
883 /* state.ctm.f = py - state.origin_y; */
884
885 /*struct css_style style = state.style;
886 style.font_size.value.length.value *= state.ctm.a;*/
887
888 for (child = text->children; child; child = child->next) {
889 svgtiny_code code = svgtiny_OK;
890
891 if (child->type == XML_TEXT_NODE) {
892 struct svgtiny_shape *shape = svgtiny_add_shape(&state);
893 if (!shape)
894 return svgtiny_OUT_OF_MEMORY;
895 shape->text = strdup((const char *) child->content);
896 shape->text_x = px;
897 shape->text_y = py;
898 state.diagram->shape_count++;
899
900 } else if (child->type == XML_ELEMENT_NODE &&
901 strcmp((const char *) child->name,
902 "tspan") == 0) {
903 code = svgtiny_parse_text(child, state);
904 }
905
906 if (!code != svgtiny_OK)
907 return code;
908 }
909
910 return svgtiny_OK;
911 }
912
913
914 /**
915 * Parse x, y, width, and height attributes, if present.
916 */
917
918 void svgtiny_parse_position_attributes(const dom_element *node,
919 const struct svgtiny_parse_state state,
920 float *x, float *y, float *width, float *height)
921 {
922 xmlAttr *attr;
923
924 *x = 0;
925 *y = 0;
926 *width = state.viewport_width;
927 *height = state.viewport_height;
928
929 for (attr = node->properties; attr; attr = attr->next) {
930 const char *name = (const char *) attr->name;
931 const char *content = (const char *) attr->children->content;
932 if (strcmp(name, "x") == 0)
933 *x = svgtiny_parse_length(content,
934 state.viewport_width, state);
935 else if (strcmp(name, "y") == 0)
936 *y = svgtiny_parse_length(content,
937 state.viewport_height, state);
938 else if (strcmp(name, "width") == 0)
939 *width = svgtiny_parse_length(content,
940 state.viewport_width, state);
941 else if (strcmp(name, "height") == 0)
942 *height = svgtiny_parse_length(content,
943 state.viewport_height, state);
944 }
945 }
946
947
948 /**
949 * Parse a length as a number of pixels.
950 */
951
952 float svgtiny_parse_length(const char *s, int viewport_size,
953 const struct svgtiny_parse_state state)
954 {
955 int num_length = strspn(s, "0123456789+-.");
956 const char *unit = s + num_length;
957 float n = atof((const char *) s);
958 float font_size = 20; /*css_len2px(&state.style.font_size.value.length, 0);*/
959
960 UNUSED(state);
961
962 if (unit[0] == 0) {
963 return n;
964 } else if (unit[0] == '%') {
965 return n / 100.0 * viewport_size;
966 } else if (unit[0] == 'e' && unit[1] == 'm') {
967 return n * font_size;
968 } else if (unit[0] == 'e' && unit[1] == 'x') {
969 return n / 2.0 * font_size;
970 } else if (unit[0] == 'p' && unit[1] == 'x') {
971 return n;
972 } else if (unit[0] == 'p' && unit[1] == 't') {
973 return n * 1.25;
974 } else if (unit[0] == 'p' && unit[1] == 'c') {
975 return n * 15.0;
976 } else if (unit[0] == 'm' && unit[1] == 'm') {
977 return n * 3.543307;
978 } else if (unit[0] == 'c' && unit[1] == 'm') {
979 return n * 35.43307;
980 } else if (unit[0] == 'i' && unit[1] == 'n') {
981 return n * 90;
982 }
983
984 return 0;
985 }
986
987
988 /**
989 * Parse paint attributes, if present.
990 */
991
992 void svgtiny_parse_paint_attributes(const dom_element *node,
993 struct svgtiny_parse_state *state)
994 {
995 const xmlAttr *attr;
996
997 for (attr = node->properties; attr; attr = attr->next) {
998 const char *name = (const char *) attr->name;
999 const char *content = (const char *) attr->children->content;
1000 if (strcmp(name, "fill") == 0)
1001 svgtiny_parse_color(content, &state->fill, state);
1002 else if (strcmp(name, "stroke") == 0)
1003 svgtiny_parse_color(content, &state->stroke, state);
1004 else if (strcmp(name, "stroke-width") == 0)
1005 state->stroke_width = svgtiny_parse_length(content,
1006 state->viewport_width, *state);
1007 else if (strcmp(name, "style") == 0) {
1008 const char *style = (const char *)
1009 attr->children->content;
1010 const char *s;
1011 char *value;
1012 if ((s = strstr(style, "fill:"))) {
1013 s += 5;
1014 while (*s == ' ')
1015 s++;
1016 value = strndup(s, strcspn(s, "; "));
1017 svgtiny_parse_color(value, &state->fill, state);
1018 free(value);
1019 }
1020 if ((s = strstr(style, "stroke:"))) {
1021 s += 7;
1022 while (*s == ' ')
1023 s++;
1024 value = strndup(s, strcspn(s, "; "));
1025 svgtiny_parse_color(value, &state->stroke, state);
1026 free(value);
1027 }
1028 if ((s = strstr(style, "stroke-width:"))) {
1029 s += 13;
1030 while (*s == ' ')
1031 s++;
1032 value = strndup(s, strcspn(s, "; "));
1033 state->stroke_width = svgtiny_parse_length(value,
1034 state->viewport_width, *state);
1035 free(value);
1036 }
1037 }
1038 }
1039 }
1040
1041
1042 /**
1043 * Parse a colour.
1044 */
1045
1046 void svgtiny_parse_color(const char *s, svgtiny_colour *c,
1047 struct svgtiny_parse_state *state)
1048 {
1049 unsigned int r, g, b;
1050 float rf, gf, bf;
1051 size_t len = strlen(s);
1052 char *id = 0, *rparen;
1053
1054 if (len == 4 && s[0] == '#') {
1055 if (sscanf(s + 1, "%1x%1x%1x", &r, &g, &b) == 3)
1056 *c = svgtiny_RGB(r | r << 4, g | g << 4, b | b << 4);
1057
1058 } else if (len == 7 && s[0] == '#') {
1059 if (sscanf(s + 1, "%2x%2x%2x", &r, &g, &b) == 3)
1060 *c = svgtiny_RGB(r, g, b);
1061
1062 } else if (10 <= len && s[0] == 'r' && s[1] == 'g' && s[2] == 'b' &&
1063 s[3] == '(' && s[len - 1] == ')') {
1064 if (sscanf(s + 4, "%u,%u,%u", &r, &g, &b) == 3)
1065 *c = svgtiny_RGB(r, g, b);
1066 else if (sscanf(s + 4, "%f%%,%f%%,%f%%", &rf, &gf, &bf) == 3) {
1067 b = bf * 255 / 100;
1068 g = gf * 255 / 100;
1069 r = rf * 255 / 100;
1070 *c = svgtiny_RGB(r, g, b);
1071 }
1072
1073 } else if (len == 4 && strcmp(s, "none") == 0) {
1074 *c = svgtiny_TRANSPARENT;
1075
1076 } else if (5 < len && s[0] == 'u' && s[1] == 'r' && s[2] == 'l' &&
1077 s[3] == '(') {
1078 if (s[4] == '#') {
1079 id = strdup(s + 5);
1080 if (!id)
1081 return;
1082 rparen = strchr(id, ')');
1083 if (rparen)
1084 *rparen = 0;
1085 svgtiny_find_gradient(id, state);
1086 free(id);
1087 fprintf(stderr, "linear_gradient_stop_count %i\n",
1088 state->linear_gradient_stop_count);
1089 if (state->linear_gradient_stop_count == 0)
1090 *c = svgtiny_TRANSPARENT;
1091 else if (state->linear_gradient_stop_count == 1)
1092 *c = state->gradient_stop[0].color;
1093 else
1094 *c = svgtiny_LINEAR_GRADIENT;
1095 }
1096
1097 } else {
1098 const struct svgtiny_named_color *named_color;
1099 named_color = svgtiny_color_lookup(s, strlen(s));
1100 if (named_color)
1101 *c = named_color->color;
1102 }
1103 }
1104
1105
1106 /**
1107 * Parse font attributes, if present.
1108 */
1109
1110 void svgtiny_parse_font_attributes(const dom_element *node,
1111 struct svgtiny_parse_state *state)
1112 {
1113 const xmlAttr *attr;
1114
1115 UNUSED(state);
1116
1117 for (attr = node->properties; attr; attr = attr->next) {
1118 if (strcmp((const char *) attr->name, "font-size") == 0) {
1119 /*if (css_parse_length(
1120 (const char *) attr->children->content,
1121 &state->style.font_size.value.length,
1122 true, true)) {
1123 state->style.font_size.size =
1124 CSS_FONT_SIZE_LENGTH;
1125 }*/
1126 }
1127 }
1128 }
1129
1130
1131 /**
1132 * Parse transform attributes, if present.
1133 *
1134 * http://www.w3.org/TR/SVG11/coords#TransformAttribute
1135 */
1136
1137 void svgtiny_parse_transform_attributes(dom_element *node,
1138 struct svgtiny_parse_state *state)
1139 {
1140 char *transform;
1141
1142 /* parse transform */
1143 transform = (char *) xmlGetProp(node, (const xmlChar *) "transform");
1144 if (transform) {
1145 svgtiny_parse_transform(transform, &state->ctm.a, &state->ctm.b,
1146 &state->ctm.c, &state->ctm.d,
1147 &state->ctm.e, &state->ctm.f);
1148 xmlFree(transform);
1149 }
1150 }
1151
1152
1153 /**
1154 * Parse a transform string.
1155 */
1156
1157 void svgtiny_parse_transform(char *s, float *ma, float *mb,
1158 float *mc, float *md, float *me, float *mf)
1159 {
1160 float a, b, c, d, e, f;
1161 float za, zb, zc, zd, ze, zf;
1162 float angle, x, y;
1163 int n;
1164 unsigned int i;
1165
1166 for (i = 0; s[i]; i++)
1167 if (s[i] == ',')
1168 s[i] = ' ';
1169
1170 while (*s) {
1171 a = d = 1;
1172 b = c = 0;
1173 e = f = 0;
1174 if (sscanf(s, "matrix (%f %f %f %f %f %f) %n",
1175 &a, &b, &c, &d, &e, &f, &n) == 6)
1176 ;
1177 else if (sscanf(s, "translate (%f %f) %n",
1178 &e, &f, &n) == 2)
1179 ;
1180 else if (sscanf(s, "translate (%f) %n",
1181 &e, &n) == 1)
1182 ;
1183 else if (sscanf(s, "scale (%f %f) %n",
1184 &a, &d, &n) == 2)
1185 ;
1186 else if (sscanf(s, "scale (%f) %n",
1187 &a, &n) == 1)
1188 d = a;
1189 else if (sscanf(s, "rotate (%f %f %f) %n",
1190 &angle, &x, &y, &n) == 3) {
1191 angle = angle / 180 * M_PI;
1192 a = cos(angle);
1193 b = sin(angle);
1194 c = -sin(angle);
1195 d = cos(angle);
1196 e = -x * cos(angle) + y * sin(angle) + x;
1197 f = -x * sin(angle) - y * cos(angle) + y;
1198 } else if (sscanf(s, "rotate (%f) %n",
1199 &angle, &n) == 1) {
1200 angle = angle / 180 * M_PI;
1201 a = cos(angle);
1202 b = sin(angle);
1203 c = -sin(angle);
1204 d = cos(angle);
1205 } else if (sscanf(s, "skewX (%f) %n",
1206 &angle, &n) == 1) {
1207 angle = angle / 180 * M_PI;
1208 c = tan(angle);
1209 } else if (sscanf(s, "skewY (%f) %n",
1210 &angle, &n) == 1) {
1211 angle = angle / 180 * M_PI;
1212 b = tan(angle);
1213 } else
1214 break;
1215 za = *ma * a + *mc * b;
1216 zb = *mb * a + *md * b;
1217 zc = *ma * c + *mc * d;
1218 zd = *mb * c + *md * d;
1219 ze = *ma * e + *mc * f + *me;
1220 zf = *mb * e + *md * f + *mf;
1221 *ma = za;
1222 *mb = zb;
1223 *mc = zc;
1224 *md = zd;
1225 *me = ze;
1226 *mf = zf;
1227 s += n;
1228 }
1229 }
1230
1231
1232 /**
1233 * Add a path to the svgtiny_diagram.
1234 */
1235
1236 svgtiny_code svgtiny_add_path(float *p, unsigned int n,
1237 struct svgtiny_parse_state *state)
1238 {
1239 struct svgtiny_shape *shape;
1240
1241 if (state->fill == svgtiny_LINEAR_GRADIENT)
1242 return svgtiny_add_path_linear_gradient(p, n, state);
1243
1244 svgtiny_transform_path(p, n, state);
1245
1246 shape = svgtiny_add_shape(state);
1247 if (!shape) {
1248 free(p);
1249 return svgtiny_OUT_OF_MEMORY;
1250 }
1251 shape->path = p;
1252 shape->path_length = n;
1253 state->diagram->shape_count++;
1254
1255 return svgtiny_OK;
1256 }
1257
1258
1259 /**
1260 * Add a svgtiny_shape to the svgtiny_diagram.
1261 */
1262
1263 struct svgtiny_shape *svgtiny_add_shape(struct svgtiny_parse_state *state)
1264 {
1265 struct svgtiny_shape *shape = realloc(state->diagram->shape,
1266 (state->diagram->shape_count + 1) *
1267 sizeof (state->diagram->shape[0]));
1268 if (!shape)
1269 return 0;
1270 state->diagram->shape = shape;
1271
1272 shape += state->diagram->shape_count;
1273 shape->path = 0;
1274 shape->path_length = 0;
1275 shape->text = 0;
1276 shape->fill = state->fill;
1277 shape->stroke = state->stroke;
1278 shape->stroke_width = lroundf((float) state->stroke_width *
1279 (state->ctm.a + state->ctm.d) / 2.0);
1280 if (0 < state->stroke_width && shape->stroke_width == 0)
1281 shape->stroke_width = 1;
1282
1283 return shape;
1284 }
1285
1286
1287 /**
1288 * Apply the current transformation matrix to a path.
1289 */
1290
1291 void svgtiny_transform_path(float *p, unsigned int n,
1292 struct svgtiny_parse_state *state)
1293 {
1294 unsigned int j;
1295
1296 for (j = 0; j != n; ) {
1297 unsigned int points = 0;
1298 unsigned int k;
1299 switch ((int) p[j]) {
1300 case svgtiny_PATH_MOVE:
1301 case svgtiny_PATH_LINE:
1302 points = 1;
1303 break;
1304 case svgtiny_PATH_CLOSE:
1305 points = 0;
1306 break;
1307 case svgtiny_PATH_BEZIER:
1308 points = 3;
1309 break;
1310 default:
1311 assert(0);
1312 }
1313 j++;
1314 for (k = 0; k != points; k++) {
1315 float x0 = p[j], y0 = p[j + 1];
1316 float x = state->ctm.a * x0 + state->ctm.c * y0 +
1317 state->ctm.e;
1318 float y = state->ctm.b * x0 + state->ctm.d * y0 +
1319 state->ctm.f;
1320 p[j] = x;
1321 p[j + 1] = y;
1322 j += 2;
1323 }
1324 }
1325 }
1326
1327
1328 /**
1329 * Free all memory used by a diagram.
1330 */
1331
1332 void svgtiny_free(struct svgtiny_diagram *svg)
1333 {
1334 unsigned int i;
1335 assert(svg);
1336
1337 for (i = 0; i != svg->shape_count; i++) {
1338 free(svg->shape[i].path);
1339 free(svg->shape[i].text);
1340 }
1341
1342 free(svg->shape);
1343
1344 free(svg);
1345 }
1346
1347 #ifndef HAVE_STRNDUP
1348 char *svgtiny_strndup(const char *s, size_t n)
1349 {
1350 size_t len;
1351 char *s2;
1352
1353 for (len = 0; len != n && s[len]; len++)
1354 continue;
1355
1356 s2 = malloc(len + 1);
1357 if (s2 == NULL)
1358 return NULL;
1359
1360 memcpy(s2, s, len);
1361 s2[len] = '\0';
1362
1363 return s2;
1364 }
1365 #endif
1366