]> gitweb.michael.orlitzky.com - libsvgtiny.git/blob - svgtiny.c
Add install target.
[libsvgtiny.git] / 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 James Bursa <james@semichrome.net>
6 */
7
8 #define _GNU_SOURCE /* for strndup */
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 #include <libxml/parser.h>
17 #include <libxml/debugXML.h>
18 #include "svgtiny.h"
19
20
21 struct svgtiny_parse_state {
22 struct svgtiny_diagram *diagram;
23 xmlDoc *document;
24
25 float viewport_width;
26 float viewport_height;
27
28 /* current transformation matrix */
29 struct {
30 float a, b, c, d, e, f;
31 } ctm;
32
33 /*struct css_style style;*/
34
35 /* paint attributes */
36 svgtiny_colour fill;
37 svgtiny_colour stroke;
38 int stroke_width;
39 };
40
41
42 static bool svgtiny_parse_svg(xmlNode *svg, struct svgtiny_parse_state state);
43 static bool svgtiny_parse_path(xmlNode *path, struct svgtiny_parse_state state);
44 static bool svgtiny_parse_rect(xmlNode *rect, struct svgtiny_parse_state state);
45 static bool svgtiny_parse_circle(xmlNode *circle,
46 struct svgtiny_parse_state state);
47 static bool svgtiny_parse_line(xmlNode *line, struct svgtiny_parse_state state);
48 static bool svgtiny_parse_poly(xmlNode *poly, struct svgtiny_parse_state state,
49 bool polygon);
50 static bool svgtiny_parse_text(xmlNode *text, struct svgtiny_parse_state state);
51 static void svgtiny_parse_position_attributes(const xmlNode *node,
52 const struct svgtiny_parse_state state,
53 float *x, float *y, float *width, float *height);
54 static float svgtiny_parse_length(const char *s, int viewport_size,
55 const struct svgtiny_parse_state state);
56 static void svgtiny_parse_paint_attributes(const xmlNode *node,
57 struct svgtiny_parse_state *state);
58 static void svgtiny_parse_color(const char *s, svgtiny_colour *c,
59 struct svgtiny_parse_state *state);
60 static void svgtiny_parse_font_attributes(const xmlNode *node,
61 struct svgtiny_parse_state *state);
62 static void svgtiny_parse_transform_attributes(xmlNode *node,
63 struct svgtiny_parse_state *state);
64 static struct svgtiny_shape *svgtiny_add_shape(
65 struct svgtiny_parse_state *state);
66 static void svgtiny_transform_path(float *p, unsigned int n,
67 struct svgtiny_parse_state *state);
68
69
70 struct svgtiny_diagram *svgtiny_create(void)
71 {
72 struct svgtiny_diagram *diagram;
73
74 diagram = malloc(sizeof *diagram);
75 if (!diagram)
76 return 0;
77
78 diagram->shape = 0;
79 diagram->shape_count = 0;
80
81 return diagram;
82 }
83
84
85 svgtiny_code svgtiny_parse(struct svgtiny_diagram *diagram,
86 const char *buffer, size_t size, const char *url,
87 int viewport_width, int viewport_height)
88 {
89 xmlDoc *document;
90 xmlNode *svg;
91 struct svgtiny_parse_state state;
92
93 assert(diagram);
94 assert(buffer);
95 assert(url);
96
97 /* parse XML to tree */
98 document = xmlReadMemory(buffer, size, url, 0,
99 XML_PARSE_NONET | XML_PARSE_COMPACT |
100 XML_PARSE_DTDVALID /* needed for xmlGetID to work */);
101 if (!document)
102 return svgtiny_LIBXML_ERROR;
103
104 /*xmlDebugDumpDocument(stderr, document);*/
105
106 /* find root <svg> element */
107 svg = xmlDocGetRootElement(document);
108 if (!svg)
109 return svgtiny_NOT_SVG;
110 if (strcmp((const char *) svg->name, "svg") != 0)
111 return svgtiny_NOT_SVG;
112
113 /* get graphic dimensions */
114 float x, y, width, height;
115 state.diagram = diagram;
116 state.document = document;
117 state.viewport_width = viewport_width;
118 state.viewport_height = viewport_height;
119 svgtiny_parse_position_attributes(svg, state, &x, &y, &width, &height);
120 diagram->width = width;
121 diagram->height = height;
122
123 state.viewport_width = width;
124 state.viewport_height = height;
125 state.ctm.a = 1; /*(float) viewport_width / (float) width;*/
126 state.ctm.b = 0;
127 state.ctm.c = 0;
128 state.ctm.d = 1; /*(float) viewport_height / (float) height;*/
129 state.ctm.e = 0; /*x;*/
130 state.ctm.f = 0; /*y;*/
131 /*state.style = css_base_style;
132 state.style.font_size.value.length.value = option_font_size * 0.1;*/
133 state.fill = 0x000000;
134 state.stroke = svgtiny_TRANSPARENT;
135 state.stroke_width = 1;
136
137 svgtiny_parse_svg(svg, state);
138
139 xmlFreeDoc(document);
140
141 return svgtiny_OK;
142 }
143
144
145 /**
146 * Parse a <svg> or <g> element node.
147 */
148
149 bool svgtiny_parse_svg(xmlNode *svg, struct svgtiny_parse_state state)
150 {
151 float x, y, width, height;
152
153 svgtiny_parse_position_attributes(svg, state, &x, &y, &width, &height);
154 svgtiny_parse_paint_attributes(svg, &state);
155 svgtiny_parse_font_attributes(svg, &state);
156
157 /* parse viewBox */
158 xmlAttr *view_box = xmlHasProp(svg, (const xmlChar *) "viewBox");
159 if (view_box) {
160 const char *s = (const char *) view_box->children->content;
161 float min_x, min_y, vwidth, vheight;
162 if (sscanf(s, "%f,%f,%f,%f",
163 &min_x, &min_y, &vwidth, &vheight) == 4 ||
164 sscanf(s, "%f %f %f %f",
165 &min_x, &min_y, &vwidth, &vheight) == 4) {
166 state.ctm.a = (float) state.viewport_width / vwidth;
167 state.ctm.d = (float) state.viewport_height / vheight;
168 state.ctm.e += -min_x * state.ctm.a;
169 state.ctm.f += -min_y * state.ctm.d;
170 }
171 }
172
173 svgtiny_parse_transform_attributes(svg, &state);
174
175 for (xmlNode *child = svg->children; child; child = child->next) {
176 bool ok = true;
177
178 if (child->type == XML_ELEMENT_NODE) {
179 const char *name = (const char *) child->name;
180 if (strcmp(name, "svg") == 0)
181 ok = svgtiny_parse_svg(child, state);
182 else if (strcmp(name, "g") == 0)
183 ok = svgtiny_parse_svg(child, state);
184 else if (strcmp(name, "a") == 0)
185 ok = svgtiny_parse_svg(child, state);
186 else if (strcmp(name, "path") == 0)
187 ok = svgtiny_parse_path(child, state);
188 else if (strcmp(name, "rect") == 0)
189 ok = svgtiny_parse_rect(child, state);
190 else if (strcmp(name, "circle") == 0)
191 ok = svgtiny_parse_circle(child, state);
192 else if (strcmp(name, "line") == 0)
193 ok = svgtiny_parse_line(child, state);
194 else if (strcmp(name, "polyline") == 0)
195 ok = svgtiny_parse_poly(child, state, false);
196 else if (strcmp(name, "polygon") == 0)
197 ok = svgtiny_parse_poly(child, state, true);
198 else if (strcmp(name, "text") == 0)
199 ok = svgtiny_parse_text(child, state);
200 }
201
202 if (!ok)
203 return false;
204 }
205
206 return true;
207 }
208
209
210 /**
211 * Parse a <path> element node.
212 *
213 * http://www.w3.org/TR/SVG11/paths#PathElement
214 */
215
216 bool svgtiny_parse_path(xmlNode *path, struct svgtiny_parse_state state)
217 {
218 char *s, *path_d;
219
220 svgtiny_parse_paint_attributes(path, &state);
221 svgtiny_parse_transform_attributes(path, &state);
222
223 /* read d attribute */
224 s = path_d = (char *) xmlGetProp(path, (const xmlChar *) "d");
225 if (!s) {
226 /*LOG(("path missing d attribute"));*/
227 return false;
228 }
229
230 /* allocate space for path: it will never have more elements than d */
231 float *p = malloc(sizeof p[0] * strlen(s));
232 if (!p) {
233 /*LOG(("out of memory"));*/
234 return false;
235 }
236
237 /* parse d and build path */
238 for (unsigned int i = 0; s[i]; i++)
239 if (s[i] == ',')
240 s[i] = ' ';
241 unsigned int i = 0;
242 float last_x = 0, last_y = 0;
243 float last_cubic_x = 0, last_cubic_y = 0;
244 float last_quad_x = 0, last_quad_y = 0;
245 while (*s) {
246 char command[2];
247 int plot_command;
248 float x, y, x1, y1, x2, y2;
249 int n;
250
251 /* moveto (M, m), lineto (L, l) (2 arguments) */
252 if (sscanf(s, " %1[MmLl] %f %f %n", command, &x, &y, &n) == 3) {
253 /*LOG(("moveto or lineto"));*/
254 if (*command == 'M' || *command == 'm')
255 plot_command = svgtiny_PATH_MOVE;
256 else
257 plot_command = svgtiny_PATH_LINE;
258 do {
259 p[i++] = plot_command;
260 if ('a' <= *command) {
261 x += last_x;
262 y += last_y;
263 }
264 p[i++] = last_cubic_x = last_quad_x = last_x
265 = x;
266 p[i++] = last_cubic_y = last_quad_y = last_y
267 = y;
268 s += n;
269 plot_command = svgtiny_PATH_LINE;
270 } while (sscanf(s, "%f %f %n", &x, &y, &n) == 2);
271
272 /* closepath (Z, z) (no arguments) */
273 } else if (sscanf(s, " %1[Zz] %n", command, &n) == 1) {
274 /*LOG(("closepath"));*/
275 p[i++] = svgtiny_PATH_CLOSE;
276 s += n;
277
278 /* horizontal lineto (H, h) (1 argument) */
279 } else if (sscanf(s, " %1[Hh] %f %n", command, &x, &n) == 2) {
280 /*LOG(("horizontal lineto"));*/
281 do {
282 p[i++] = svgtiny_PATH_LINE;
283 if (*command == 'h')
284 x += last_x;
285 p[i++] = last_cubic_x = last_quad_x = last_x
286 = x;
287 p[i++] = last_cubic_y = last_quad_y = last_y;
288 s += n;
289 } while (sscanf(s, "%f %n", &x, &n) == 1);
290
291 /* vertical lineto (V, v) (1 argument) */
292 } else if (sscanf(s, " %1[Vv] %f %n", command, &y, &n) == 2) {
293 /*LOG(("vertical lineto"));*/
294 do {
295 p[i++] = svgtiny_PATH_LINE;
296 if (*command == 'v')
297 y += last_y;
298 p[i++] = last_cubic_x = last_quad_x = last_x;
299 p[i++] = last_cubic_y = last_quad_y = last_y
300 = y;
301 s += n;
302 } while (sscanf(s, "%f %n", &x, &n) == 1);
303
304 /* curveto (C, c) (6 arguments) */
305 } else if (sscanf(s, " %1[Cc] %f %f %f %f %f %f %n", command,
306 &x1, &y1, &x2, &y2, &x, &y, &n) == 7) {
307 /*LOG(("curveto"));*/
308 do {
309 p[i++] = svgtiny_PATH_BEZIER;
310 if (*command == 'c') {
311 x1 += last_x;
312 y1 += last_y;
313 x2 += last_x;
314 y2 += last_y;
315 x += last_x;
316 y += last_y;
317 }
318 p[i++] = x1;
319 p[i++] = y1;
320 p[i++] = last_cubic_x = x2;
321 p[i++] = last_cubic_y = y2;
322 p[i++] = last_quad_x = last_x = x;
323 p[i++] = last_quad_y = last_y = y;
324 s += n;
325 } while (sscanf(s, "%f %f %f %f %f %f %n",
326 &x1, &y1, &x2, &y2, &x, &y, &n) == 6);
327
328 /* shorthand/smooth curveto (S, s) (4 arguments) */
329 } else if (sscanf(s, " %1[Ss] %f %f %f %f %n", command,
330 &x2, &y2, &x, &y, &n) == 5) {
331 /*LOG(("shorthand/smooth curveto"));*/
332 do {
333 p[i++] = svgtiny_PATH_BEZIER;
334 x1 = last_x + (last_x - last_cubic_x);
335 y1 = last_y + (last_y - last_cubic_y);
336 if (*command == 's') {
337 x2 += last_x;
338 y2 += last_y;
339 x += last_x;
340 y += last_y;
341 }
342 p[i++] = x1;
343 p[i++] = y1;
344 p[i++] = last_cubic_x = x2;
345 p[i++] = last_cubic_y = y2;
346 p[i++] = last_quad_x = last_x = x;
347 p[i++] = last_quad_y = last_y = y;
348 s += n;
349 } while (sscanf(s, "%f %f %f %f %n",
350 &x2, &y2, &x, &y, &n) == 4);
351
352 /* quadratic Bezier curveto (Q, q) (4 arguments) */
353 } else if (sscanf(s, " %1[Qq] %f %f %f %f %n", command,
354 &x1, &y1, &x, &y, &n) == 5) {
355 /*LOG(("quadratic Bezier curveto"));*/
356 do {
357 p[i++] = svgtiny_PATH_BEZIER;
358 last_quad_x = x1;
359 last_quad_y = y1;
360 if (*command == 'q') {
361 x1 += last_x;
362 y1 += last_y;
363 x += last_x;
364 y += last_y;
365 }
366 p[i++] = 1./3 * last_x + 2./3 * x1;
367 p[i++] = 1./3 * last_y + 2./3 * y1;
368 p[i++] = 2./3 * x1 + 1./3 * x;
369 p[i++] = 2./3 * y1 + 1./3 * y;
370 p[i++] = last_cubic_x = last_x = x;
371 p[i++] = last_cubic_y = last_y = y;
372 s += n;
373 } while (sscanf(s, "%f %f %f %f %n",
374 &x1, &y1, &x, &y, &n) == 4);
375
376 /* shorthand/smooth quadratic Bezier curveto (T, t)
377 (2 arguments) */
378 } else if (sscanf(s, " %1[Tt] %f %f %n", command,
379 &x, &y, &n) == 3) {
380 /*LOG(("shorthand/smooth quadratic Bezier curveto"));*/
381 do {
382 p[i++] = svgtiny_PATH_BEZIER;
383 x1 = last_x + (last_x - last_quad_x);
384 y1 = last_y + (last_y - last_quad_y);
385 last_quad_x = x1;
386 last_quad_y = y1;
387 if (*command == 't') {
388 x1 += last_x;
389 y1 += last_y;
390 x += last_x;
391 y += last_y;
392 }
393 p[i++] = 1./3 * last_x + 2./3 * x1;
394 p[i++] = 1./3 * last_y + 2./3 * y1;
395 p[i++] = 2./3 * x1 + 1./3 * x;
396 p[i++] = 2./3 * y1 + 1./3 * y;
397 p[i++] = last_cubic_x = last_x = x;
398 p[i++] = last_cubic_y = last_y = y;
399 s += n;
400 } while (sscanf(s, "%f %f %n",
401 &x, &y, &n) == 2);
402
403 } else {
404 /*LOG(("parse failed at \"%s\"", s));*/
405 break;
406 }
407 }
408
409 xmlFree(path_d);
410
411 svgtiny_transform_path(p, i, &state);
412
413 struct svgtiny_shape *shape = svgtiny_add_shape(&state);
414 if (!shape) {
415 free(p);
416 return false;
417 }
418 shape->path = p;
419 shape->path_length = i;
420 state.diagram->shape_count++;
421
422 return true;
423 }
424
425
426 /**
427 * Parse a <rect> element node.
428 *
429 * http://www.w3.org/TR/SVG11/shapes#RectElement
430 */
431
432 bool svgtiny_parse_rect(xmlNode *rect, struct svgtiny_parse_state state)
433 {
434 float x, y, width, height;
435
436 svgtiny_parse_position_attributes(rect, state,
437 &x, &y, &width, &height);
438 svgtiny_parse_paint_attributes(rect, &state);
439 svgtiny_parse_transform_attributes(rect, &state);
440
441 float *p = malloc(13 * sizeof p[0]);
442 if (!p)
443 return false;
444
445 p[0] = svgtiny_PATH_MOVE;
446 p[1] = x;
447 p[2] = y;
448 p[3] = svgtiny_PATH_LINE;
449 p[4] = x + width;
450 p[5] = y;
451 p[6] = svgtiny_PATH_LINE;
452 p[7] = x + width;
453 p[8] = y + height;
454 p[9] = svgtiny_PATH_LINE;
455 p[10] = x;
456 p[11] = y + height;
457 p[12] = svgtiny_PATH_CLOSE;
458
459 svgtiny_transform_path(p, 13, &state);
460
461 struct svgtiny_shape *shape = svgtiny_add_shape(&state);
462 if (!shape) {
463 free(p);
464 return false;
465 }
466 shape->path = p;
467 shape->path_length = 13;
468 state.diagram->shape_count++;
469
470 return true;
471 }
472
473
474 /**
475 * Parse a <circle> element node.
476 */
477
478 bool svgtiny_parse_circle(xmlNode *circle, struct svgtiny_parse_state state)
479 {
480 float x = 0, y = 0, r = 0;
481 const float kappa = 0.5522847498;
482
483 for (xmlAttr *attr = circle->properties; attr; attr = attr->next) {
484 const char *name = (const char *) attr->name;
485 const char *content = (const char *) attr->children->content;
486 if (strcmp(name, "cx") == 0)
487 x = svgtiny_parse_length(content,
488 state.viewport_width, state);
489 else if (strcmp(name, "cy") == 0)
490 y = svgtiny_parse_length(content,
491 state.viewport_height, state);
492 else if (strcmp(name, "r") == 0)
493 r = svgtiny_parse_length(content,
494 state.viewport_width, state);
495 }
496 svgtiny_parse_paint_attributes(circle, &state);
497 svgtiny_parse_transform_attributes(circle, &state);
498
499 float *p = malloc(32 * sizeof p[0]);
500 if (!p)
501 return false;
502
503 p[0] = svgtiny_PATH_MOVE;
504 p[1] = x - r;
505 p[2] = y;
506 p[3] = svgtiny_PATH_BEZIER;
507 p[4] = x - r;
508 p[5] = y + r * kappa;
509 p[6] = x - r * kappa;
510 p[7] = y + r;
511 p[8] = x;
512 p[9] = y + r;
513 p[10] = svgtiny_PATH_BEZIER;
514 p[11] = x + r * kappa;
515 p[12] = y + r;
516 p[13] = x + r;
517 p[14] = y + r * kappa;
518 p[15] = x + r;
519 p[16] = y;
520 p[17] = svgtiny_PATH_BEZIER;
521 p[18] = x + r;
522 p[19] = y - r * kappa;
523 p[20] = x + r * kappa;
524 p[21] = y - r;
525 p[22] = x;
526 p[23] = y - r;
527 p[24] = svgtiny_PATH_BEZIER;
528 p[25] = x - r * kappa;
529 p[26] = y - r;
530 p[27] = x - r;
531 p[28] = y - r * kappa;
532 p[29] = x - r;
533 p[30] = y;
534 p[31] = svgtiny_PATH_CLOSE;
535
536 svgtiny_transform_path(p, 32, &state);
537
538 struct svgtiny_shape *shape = svgtiny_add_shape(&state);
539 if (!shape) {
540 free(p);
541 return false;
542 }
543 shape->path = p;
544 shape->path_length = 32;
545 state.diagram->shape_count++;
546
547 return true;
548 }
549
550
551 /**
552 * Parse a <line> element node.
553 */
554
555 bool svgtiny_parse_line(xmlNode *line, struct svgtiny_parse_state state)
556 {
557 float x1 = 0, y1 = 0, x2 = 0, y2 = 0;
558
559 for (xmlAttr *attr = line->properties; attr; attr = attr->next) {
560 const char *name = (const char *) attr->name;
561 const char *content = (const char *) attr->children->content;
562 if (strcmp(name, "x1") == 0)
563 x1 = svgtiny_parse_length(content,
564 state.viewport_width, state);
565 else if (strcmp(name, "y1") == 0)
566 y1 = svgtiny_parse_length(content,
567 state.viewport_height, state);
568 else if (strcmp(name, "x2") == 0)
569 x2 = svgtiny_parse_length(content,
570 state.viewport_width, state);
571 else if (strcmp(name, "y2") == 0)
572 y2 = svgtiny_parse_length(content,
573 state.viewport_height, state);
574 }
575 svgtiny_parse_paint_attributes(line, &state);
576 svgtiny_parse_transform_attributes(line, &state);
577
578 float *p = malloc(7 * sizeof p[0]);
579 if (!p)
580 return false;
581
582 p[0] = svgtiny_PATH_MOVE;
583 p[1] = x1;
584 p[2] = y1;
585 p[3] = svgtiny_PATH_LINE;
586 p[4] = x2;
587 p[5] = y2;
588 p[6] = svgtiny_PATH_CLOSE;
589
590 svgtiny_transform_path(p, 7, &state);
591
592 struct svgtiny_shape *shape = svgtiny_add_shape(&state);
593 if (!shape) {
594 free(p);
595 return false;
596 }
597 shape->path = p;
598 shape->path_length = 7;
599 state.diagram->shape_count++;
600
601 return true;
602 }
603
604
605 /**
606 * Parse a <polyline> or <polygon> element node.
607 *
608 * http://www.w3.org/TR/SVG11/shapes#PolylineElement
609 * http://www.w3.org/TR/SVG11/shapes#PolygonElement
610 */
611
612 bool svgtiny_parse_poly(xmlNode *poly, struct svgtiny_parse_state state,
613 bool polygon)
614 {
615 char *s, *points;
616
617 svgtiny_parse_paint_attributes(poly, &state);
618 svgtiny_parse_transform_attributes(poly, &state);
619
620 /* read d attribute */
621 s = points = (char *) xmlGetProp(poly, (const xmlChar *) "points");
622 if (!s) {
623 /*LOG(("poly missing d attribute"));*/
624 return false;
625 }
626
627 /* allocate space for path: it will never have more elements than s */
628 float *p = malloc(sizeof p[0] * strlen(s));
629 if (!p) {
630 /*LOG(("out of memory"));*/
631 return false;
632 }
633
634 /* parse s and build path */
635 for (unsigned int i = 0; s[i]; i++)
636 if (s[i] == ',')
637 s[i] = ' ';
638 unsigned int i = 0;
639 while (*s) {
640 float x, y;
641 int n;
642
643 if (sscanf(s, "%f %f %n", &x, &y, &n) == 2) {
644 if (i == 0)
645 p[i++] = svgtiny_PATH_MOVE;
646 else
647 p[i++] = svgtiny_PATH_LINE;
648 p[i++] = x;
649 p[i++] = y;
650 s += n;
651 } else {
652 break;
653 }
654 }
655 if (polygon)
656 p[i++] = svgtiny_PATH_CLOSE;
657
658 xmlFree(points);
659
660 svgtiny_transform_path(p, i, &state);
661
662 struct svgtiny_shape *shape = svgtiny_add_shape(&state);
663 if (!shape) {
664 free(p);
665 return false;
666 }
667 shape->path = p;
668 shape->path_length = i;
669 state.diagram->shape_count++;
670
671 return true;
672 }
673
674
675 /**
676 * Parse a <text> or <tspan> element node.
677 */
678
679 bool svgtiny_parse_text(xmlNode *text, struct svgtiny_parse_state state)
680 {
681 float x, y, width, height;
682
683 svgtiny_parse_position_attributes(text, state,
684 &x, &y, &width, &height);
685 svgtiny_parse_font_attributes(text, &state);
686 svgtiny_parse_transform_attributes(text, &state);
687
688 float px = state.ctm.a * x + state.ctm.c * y + state.ctm.e;
689 float py = state.ctm.b * x + state.ctm.d * y + state.ctm.f;
690 /* state.ctm.e = px - state.origin_x; */
691 /* state.ctm.f = py - state.origin_y; */
692
693 /*struct css_style style = state.style;
694 style.font_size.value.length.value *= state.ctm.a;*/
695
696 for (xmlNode *child = text->children; child; child = child->next) {
697 bool ok = true;
698
699 if (child->type == XML_TEXT_NODE) {
700 struct svgtiny_shape *shape = svgtiny_add_shape(&state);
701 if (!shape)
702 return false;
703 shape->text = strdup((const char *) child->content);
704 shape->text_x = px;
705 shape->text_y = py;
706 state.diagram->shape_count++;
707
708 } else if (child->type == XML_ELEMENT_NODE &&
709 strcmp((const char *) child->name,
710 "tspan") == 0) {
711 ok = svgtiny_parse_text(child, state);
712 }
713
714 if (!ok)
715 return false;
716 }
717
718 return true;
719 }
720
721
722 /**
723 * Parse x, y, width, and height attributes, if present.
724 */
725
726 void svgtiny_parse_position_attributes(const xmlNode *node,
727 const struct svgtiny_parse_state state,
728 float *x, float *y, float *width, float *height)
729 {
730 *x = 0;
731 *y = 0;
732 *width = state.viewport_width;
733 *height = state.viewport_height;
734
735 for (xmlAttr *attr = node->properties; attr; attr = attr->next) {
736 const char *name = (const char *) attr->name;
737 const char *content = (const char *) attr->children->content;
738 if (strcmp(name, "x") == 0)
739 *x = svgtiny_parse_length(content,
740 state.viewport_width, state);
741 else if (strcmp(name, "y") == 0)
742 *y = svgtiny_parse_length(content,
743 state.viewport_height, state);
744 else if (strcmp(name, "width") == 0)
745 *width = svgtiny_parse_length(content,
746 state.viewport_width, state);
747 else if (strcmp(name, "height") == 0)
748 *height = svgtiny_parse_length(content,
749 state.viewport_height, state);
750 }
751 }
752
753
754 /**
755 * Parse a length as a number of pixels.
756 */
757
758 float svgtiny_parse_length(const char *s, int viewport_size,
759 const struct svgtiny_parse_state state)
760 {
761 int num_length = strspn(s, "0123456789+-.");
762 const char *unit = s + num_length;
763 float n = atof((const char *) s);
764 float font_size = 20; /*css_len2px(&state.style.font_size.value.length, 0);*/
765
766 if (unit[0] == 0) {
767 return n;
768 } else if (unit[0] == '%') {
769 return n / 100.0 * viewport_size;
770 } else if (unit[0] == 'e' && unit[1] == 'm') {
771 return n * font_size;
772 } else if (unit[0] == 'e' && unit[1] == 'x') {
773 return n / 2.0 * font_size;
774 } else if (unit[0] == 'p' && unit[1] == 'x') {
775 return n;
776 } else if (unit[0] == 'p' && unit[1] == 't') {
777 return n * 1.25;
778 } else if (unit[0] == 'p' && unit[1] == 'c') {
779 return n * 15.0;
780 } else if (unit[0] == 'm' && unit[1] == 'm') {
781 return n * 3.543307;
782 } else if (unit[0] == 'c' && unit[1] == 'm') {
783 return n * 35.43307;
784 } else if (unit[0] == 'i' && unit[1] == 'n') {
785 return n * 90;
786 }
787
788 return 0;
789 }
790
791
792 /**
793 * Parse paint attributes, if present.
794 */
795
796 void svgtiny_parse_paint_attributes(const xmlNode *node,
797 struct svgtiny_parse_state *state)
798 {
799 for (const xmlAttr *attr = node->properties; attr; attr = attr->next) {
800 const char *name = (const char *) attr->name;
801 const char *content = (const char *) attr->children->content;
802 if (strcmp(name, "fill") == 0)
803 svgtiny_parse_color(content, &state->fill, state);
804 else if (strcmp(name, "stroke") == 0)
805 svgtiny_parse_color(content, &state->stroke, state);
806 else if (strcmp(name, "stroke-width") == 0)
807 state->stroke_width = svgtiny_parse_length(content,
808 state->viewport_width, *state);
809 else if (strcmp(name, "style") == 0) {
810 const char *style = (const char *)
811 attr->children->content;
812 const char *s;
813 char *value;
814 if ((s = strstr(style, "fill:"))) {
815 s += 5;
816 while (*s == ' ')
817 s++;
818 value = strndup(s, strcspn(s, "; "));
819 svgtiny_parse_color(value, &state->fill, state);
820 free(value);
821 }
822 if ((s = strstr(style, "stroke:"))) {
823 s += 7;
824 while (*s == ' ')
825 s++;
826 value = strndup(s, strcspn(s, "; "));
827 svgtiny_parse_color(value, &state->stroke, state);
828 free(value);
829 }
830 if ((s = strstr(style, "stroke-width:"))) {
831 s += 13;
832 while (*s == ' ')
833 s++;
834 state->stroke_width = svgtiny_parse_length(s,
835 state->viewport_width, *state);
836 }
837 }
838 }
839 }
840
841
842 /**
843 * Parse a colour.
844 */
845
846 void svgtiny_parse_color(const char *s, svgtiny_colour *c,
847 struct svgtiny_parse_state *state)
848 {
849 unsigned int r, g, b;
850 float rf, gf, bf;
851 size_t len = strlen(s);
852 char *id = 0, *rparen;
853 xmlAttr *id_attr;
854
855 if (len == 4 && s[0] == '#') {
856 if (sscanf(s + 1, "%1x%1x%1x", &r, &g, &b) == 3)
857 *c = svgtiny_RGB(r | r << 4, g | g << 4, b | b << 4);
858
859 } else if (len == 7 && s[0] == '#') {
860 if (sscanf(s + 1, "%2x%2x%2x", &r, &g, &b) == 3)
861 *c = svgtiny_RGB(r, g, b);
862
863 } else if (10 <= len && s[0] == 'r' && s[1] == 'g' && s[2] == 'b' &&
864 s[3] == '(' && s[len - 1] == ')') {
865 if (sscanf(s + 4, "%i,%i,%i", &r, &g, &b) == 3)
866 *c = svgtiny_RGB(r, g, b);
867 else if (sscanf(s + 4, "%f%%,%f%%,%f%%", &rf, &gf, &bf) == 3) {
868 b = bf * 255 / 100;
869 g = gf * 255 / 100;
870 r = rf * 255 / 100;
871 *c = svgtiny_RGB(r, g, b);
872 }
873
874 } else if (len == 4 && strcmp(s, "none") == 0) {
875 *c = svgtiny_TRANSPARENT;
876
877 } else if (5 < len && s[0] == 'u' && s[1] == 'r' && s[2] == 'l' &&
878 s[3] == '(') {
879 if (s[4] == '#') {
880 id = strdup(s + 5);
881 if (!id)
882 return;
883 rparen = strchr(id, ')');
884 if (rparen)
885 *rparen = 0;
886 id_attr = xmlGetID(state->document,
887 (const xmlChar *) id);
888 if (!id_attr) {
889 fprintf(stderr, "id \"%s\" not found\n", id);
890 free(id);
891 return;
892 }
893 fprintf(stderr, "id \"%s\" at %p\n", id, id_attr);
894 free(id);
895 }
896
897 } else {
898 const struct svgtiny_named_color *named_color;
899 named_color = svgtiny_color_lookup(s, strlen(s));
900 if (named_color)
901 *c = named_color->color;
902 }
903 }
904
905
906 /**
907 * Parse font attributes, if present.
908 */
909
910 void svgtiny_parse_font_attributes(const xmlNode *node,
911 struct svgtiny_parse_state *state)
912 {
913 for (const xmlAttr *attr = node->properties; attr; attr = attr->next) {
914 if (strcmp((const char *) attr->name, "font-size") == 0) {
915 /*if (css_parse_length(
916 (const char *) attr->children->content,
917 &state->style.font_size.value.length,
918 true, true)) {
919 state->style.font_size.size =
920 CSS_FONT_SIZE_LENGTH;
921 }*/
922 }
923 }
924 }
925
926
927 /**
928 * Parse transform attributes, if present.
929 *
930 * http://www.w3.org/TR/SVG11/coords#TransformAttribute
931 */
932
933 void svgtiny_parse_transform_attributes(xmlNode *node,
934 struct svgtiny_parse_state *state)
935 {
936 char *transform, *s;
937 float a, b, c, d, e, f;
938 float ctm_a, ctm_b, ctm_c, ctm_d, ctm_e, ctm_f;
939 float angle, x, y;
940 int n;
941
942 /* parse transform */
943 s = transform = (char *) xmlGetProp(node,
944 (const xmlChar *) "transform");
945 if (transform) {
946 for (unsigned int i = 0; transform[i]; i++)
947 if (transform[i] == ',')
948 transform[i] = ' ';
949
950 while (*s) {
951 a = d = 1;
952 b = c = 0;
953 e = f = 0;
954 if (sscanf(s, "matrix (%f %f %f %f %f %f) %n",
955 &a, &b, &c, &d, &e, &f, &n) == 6)
956 ;
957 else if (sscanf(s, "translate (%f %f) %n",
958 &e, &f, &n) == 2)
959 ;
960 else if (sscanf(s, "translate (%f) %n",
961 &e, &n) == 1)
962 ;
963 else if (sscanf(s, "scale (%f %f) %n",
964 &a, &d, &n) == 2)
965 ;
966 else if (sscanf(s, "scale (%f) %n",
967 &a, &n) == 1)
968 d = a;
969 else if (sscanf(s, "rotate (%f %f %f) %n",
970 &angle, &x, &y, &n) == 3) {
971 angle = angle / 180 * M_PI;
972 a = cos(angle);
973 b = sin(angle);
974 c = -sin(angle);
975 d = cos(angle);
976 e = -x * cos(angle) + y * sin(angle) + x;
977 f = -x * sin(angle) - y * cos(angle) + y;
978 } else if (sscanf(s, "rotate (%f) %n",
979 &angle, &n) == 1) {
980 angle = angle / 180 * M_PI;
981 a = cos(angle);
982 b = sin(angle);
983 c = -sin(angle);
984 d = cos(angle);
985 } else if (sscanf(s, "skewX (%f) %n",
986 &angle, &n) == 1) {
987 angle = angle / 180 * M_PI;
988 c = tan(angle);
989 } else if (sscanf(s, "skewY (%f) %n",
990 &angle, &n) == 1) {
991 angle = angle / 180 * M_PI;
992 b = tan(angle);
993 } else
994 break;
995 ctm_a = state->ctm.a * a + state->ctm.c * b;
996 ctm_b = state->ctm.b * a + state->ctm.d * b;
997 ctm_c = state->ctm.a * c + state->ctm.c * d;
998 ctm_d = state->ctm.b * c + state->ctm.d * d;
999 ctm_e = state->ctm.a * e + state->ctm.c * f +
1000 state->ctm.e;
1001 ctm_f = state->ctm.b * e + state->ctm.d * f +
1002 state->ctm.f;
1003 state->ctm.a = ctm_a;
1004 state->ctm.b = ctm_b;
1005 state->ctm.c = ctm_c;
1006 state->ctm.d = ctm_d;
1007 state->ctm.e = ctm_e;
1008 state->ctm.f = ctm_f;
1009 s += n;
1010 }
1011
1012 xmlFree(transform);
1013 }
1014 }
1015
1016
1017 /**
1018 * Add a svgtiny_shape to the svgtiny_diagram.
1019 */
1020
1021 struct svgtiny_shape *svgtiny_add_shape(struct svgtiny_parse_state *state)
1022 {
1023 struct svgtiny_shape *shape = realloc(state->diagram->shape,
1024 (state->diagram->shape_count + 1) *
1025 sizeof (state->diagram->shape[0]));
1026 if (!shape)
1027 return 0;
1028 state->diagram->shape = shape;
1029
1030 shape += state->diagram->shape_count;
1031 shape->path = 0;
1032 shape->path_length = 0;
1033 shape->text = 0;
1034 shape->fill = state->fill;
1035 shape->stroke = state->stroke;
1036 shape->stroke_width = state->stroke_width;
1037
1038 return shape;
1039 }
1040
1041
1042 void svgtiny_transform_path(float *p, unsigned int n,
1043 struct svgtiny_parse_state *state)
1044 {
1045 for (unsigned int j = 0; j != n; ) {
1046 unsigned int points = 0;
1047 switch ((int) p[j]) {
1048 case svgtiny_PATH_MOVE:
1049 case svgtiny_PATH_LINE:
1050 points = 1;
1051 break;
1052 case svgtiny_PATH_CLOSE:
1053 points = 0;
1054 break;
1055 case svgtiny_PATH_BEZIER:
1056 points = 3;
1057 break;
1058 default:
1059 assert(0);
1060 }
1061 j++;
1062 for (unsigned int k = 0; k != points; k++) {
1063 float x0 = p[j], y0 = p[j + 1];
1064 float x = state->ctm.a * x0 + state->ctm.c * y0 +
1065 state->ctm.e;
1066 float y = state->ctm.b * x0 + state->ctm.d * y0 +
1067 state->ctm.f;
1068 p[j] = x;
1069 p[j + 1] = y;
1070 j += 2;
1071 }
1072 }
1073 }
1074
1075
1076 void svgtiny_free(struct svgtiny_diagram *svg)
1077 {
1078 assert(svg);
1079
1080 for (unsigned int i = 0; i != svg->shape_count; i++) {
1081 free(svg->shape[i].path);
1082 free(svg->shape[i].text);
1083 }
1084
1085 free(svg->shape);
1086
1087 free(svg);
1088 }
1089