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