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