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