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