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