]> gitweb.michael.orlitzky.com - libsvgtiny.git/blob - src/svgtiny.c
5a6e98808210e8a261e2d9bec322ce7aa7f9706c
[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 value = strndup(s, strcspn(s, "; "));
921 state->stroke_width = svgtiny_parse_length(value,
922 state->viewport_width, *state);
923 free(value);
924 }
925 }
926 }
927 }
928
929
930 /**
931 * Parse a colour.
932 */
933
934 void svgtiny_parse_color(const char *s, svgtiny_colour *c,
935 struct svgtiny_parse_state *state)
936 {
937 unsigned int r, g, b;
938 float rf, gf, bf;
939 size_t len = strlen(s);
940 char *id = 0, *rparen;
941
942 if (len == 4 && s[0] == '#') {
943 if (sscanf(s + 1, "%1x%1x%1x", &r, &g, &b) == 3)
944 *c = svgtiny_RGB(r | r << 4, g | g << 4, b | b << 4);
945
946 } else if (len == 7 && s[0] == '#') {
947 if (sscanf(s + 1, "%2x%2x%2x", &r, &g, &b) == 3)
948 *c = svgtiny_RGB(r, g, b);
949
950 } else if (10 <= len && s[0] == 'r' && s[1] == 'g' && s[2] == 'b' &&
951 s[3] == '(' && s[len - 1] == ')') {
952 if (sscanf(s + 4, "%u,%u,%u", &r, &g, &b) == 3)
953 *c = svgtiny_RGB(r, g, b);
954 else if (sscanf(s + 4, "%f%%,%f%%,%f%%", &rf, &gf, &bf) == 3) {
955 b = bf * 255 / 100;
956 g = gf * 255 / 100;
957 r = rf * 255 / 100;
958 *c = svgtiny_RGB(r, g, b);
959 }
960
961 } else if (len == 4 && strcmp(s, "none") == 0) {
962 *c = svgtiny_TRANSPARENT;
963
964 } else if (5 < len && s[0] == 'u' && s[1] == 'r' && s[2] == 'l' &&
965 s[3] == '(') {
966 if (s[4] == '#') {
967 id = strdup(s + 5);
968 if (!id)
969 return;
970 rparen = strchr(id, ')');
971 if (rparen)
972 *rparen = 0;
973 svgtiny_find_gradient(id, state);
974 free(id);
975 fprintf(stderr, "linear_gradient_stop_count %i\n",
976 state->linear_gradient_stop_count);
977 if (state->linear_gradient_stop_count == 0)
978 *c = svgtiny_TRANSPARENT;
979 else if (state->linear_gradient_stop_count == 1)
980 *c = state->gradient_stop[0].color;
981 else
982 *c = svgtiny_LINEAR_GRADIENT;
983 }
984
985 } else {
986 const struct svgtiny_named_color *named_color;
987 named_color = svgtiny_color_lookup(s, strlen(s));
988 if (named_color)
989 *c = named_color->color;
990 }
991 }
992
993
994 /**
995 * Parse font attributes, if present.
996 */
997
998 void svgtiny_parse_font_attributes(const xmlNode *node,
999 struct svgtiny_parse_state *state)
1000 {
1001 const xmlAttr *attr;
1002
1003 UNUSED(state);
1004
1005 for (attr = node->properties; attr; attr = attr->next) {
1006 if (strcmp((const char *) attr->name, "font-size") == 0) {
1007 /*if (css_parse_length(
1008 (const char *) attr->children->content,
1009 &state->style.font_size.value.length,
1010 true, true)) {
1011 state->style.font_size.size =
1012 CSS_FONT_SIZE_LENGTH;
1013 }*/
1014 }
1015 }
1016 }
1017
1018
1019 /**
1020 * Parse transform attributes, if present.
1021 *
1022 * http://www.w3.org/TR/SVG11/coords#TransformAttribute
1023 */
1024
1025 void svgtiny_parse_transform_attributes(xmlNode *node,
1026 struct svgtiny_parse_state *state)
1027 {
1028 char *transform;
1029
1030 /* parse transform */
1031 transform = (char *) xmlGetProp(node, (const xmlChar *) "transform");
1032 if (transform) {
1033 svgtiny_parse_transform(transform, &state->ctm.a, &state->ctm.b,
1034 &state->ctm.c, &state->ctm.d,
1035 &state->ctm.e, &state->ctm.f);
1036 xmlFree(transform);
1037 }
1038 }
1039
1040
1041 /**
1042 * Parse a transform string.
1043 */
1044
1045 void svgtiny_parse_transform(char *s, float *ma, float *mb,
1046 float *mc, float *md, float *me, float *mf)
1047 {
1048 float a, b, c, d, e, f;
1049 float za, zb, zc, zd, ze, zf;
1050 float angle, x, y;
1051 int n;
1052 unsigned int i;
1053
1054 for (i = 0; s[i]; i++)
1055 if (s[i] == ',')
1056 s[i] = ' ';
1057
1058 while (*s) {
1059 a = d = 1;
1060 b = c = 0;
1061 e = f = 0;
1062 if (sscanf(s, "matrix (%f %f %f %f %f %f) %n",
1063 &a, &b, &c, &d, &e, &f, &n) == 6)
1064 ;
1065 else if (sscanf(s, "translate (%f %f) %n",
1066 &e, &f, &n) == 2)
1067 ;
1068 else if (sscanf(s, "translate (%f) %n",
1069 &e, &n) == 1)
1070 ;
1071 else if (sscanf(s, "scale (%f %f) %n",
1072 &a, &d, &n) == 2)
1073 ;
1074 else if (sscanf(s, "scale (%f) %n",
1075 &a, &n) == 1)
1076 d = a;
1077 else if (sscanf(s, "rotate (%f %f %f) %n",
1078 &angle, &x, &y, &n) == 3) {
1079 angle = angle / 180 * M_PI;
1080 a = cos(angle);
1081 b = sin(angle);
1082 c = -sin(angle);
1083 d = cos(angle);
1084 e = -x * cos(angle) + y * sin(angle) + x;
1085 f = -x * sin(angle) - y * cos(angle) + y;
1086 } else if (sscanf(s, "rotate (%f) %n",
1087 &angle, &n) == 1) {
1088 angle = angle / 180 * M_PI;
1089 a = cos(angle);
1090 b = sin(angle);
1091 c = -sin(angle);
1092 d = cos(angle);
1093 } else if (sscanf(s, "skewX (%f) %n",
1094 &angle, &n) == 1) {
1095 angle = angle / 180 * M_PI;
1096 c = tan(angle);
1097 } else if (sscanf(s, "skewY (%f) %n",
1098 &angle, &n) == 1) {
1099 angle = angle / 180 * M_PI;
1100 b = tan(angle);
1101 } else
1102 break;
1103 za = *ma * a + *mc * b;
1104 zb = *mb * a + *md * b;
1105 zc = *ma * c + *mc * d;
1106 zd = *mb * c + *md * d;
1107 ze = *ma * e + *mc * f + *me;
1108 zf = *mb * e + *md * f + *mf;
1109 *ma = za;
1110 *mb = zb;
1111 *mc = zc;
1112 *md = zd;
1113 *me = ze;
1114 *mf = zf;
1115 s += n;
1116 }
1117 }
1118
1119
1120 /**
1121 * Add a path to the svgtiny_diagram.
1122 */
1123
1124 svgtiny_code svgtiny_add_path(float *p, unsigned int n,
1125 struct svgtiny_parse_state *state)
1126 {
1127 struct svgtiny_shape *shape;
1128
1129 if (state->fill == svgtiny_LINEAR_GRADIENT)
1130 return svgtiny_add_path_linear_gradient(p, n, state);
1131
1132 svgtiny_transform_path(p, n, state);
1133
1134 shape = svgtiny_add_shape(state);
1135 if (!shape) {
1136 free(p);
1137 return svgtiny_OUT_OF_MEMORY;
1138 }
1139 shape->path = p;
1140 shape->path_length = n;
1141 state->diagram->shape_count++;
1142
1143 return svgtiny_OK;
1144 }
1145
1146
1147 /**
1148 * Add a svgtiny_shape to the svgtiny_diagram.
1149 */
1150
1151 struct svgtiny_shape *svgtiny_add_shape(struct svgtiny_parse_state *state)
1152 {
1153 struct svgtiny_shape *shape = realloc(state->diagram->shape,
1154 (state->diagram->shape_count + 1) *
1155 sizeof (state->diagram->shape[0]));
1156 if (!shape)
1157 return 0;
1158 state->diagram->shape = shape;
1159
1160 shape += state->diagram->shape_count;
1161 shape->path = 0;
1162 shape->path_length = 0;
1163 shape->text = 0;
1164 shape->fill = state->fill;
1165 shape->stroke = state->stroke;
1166 shape->stroke_width = lroundf((float) state->stroke_width *
1167 (state->ctm.a + state->ctm.d) / 2.0);
1168 if (0 < state->stroke_width && shape->stroke_width == 0)
1169 shape->stroke_width = 1;
1170
1171 return shape;
1172 }
1173
1174
1175 /**
1176 * Apply the current transformation matrix to a path.
1177 */
1178
1179 void svgtiny_transform_path(float *p, unsigned int n,
1180 struct svgtiny_parse_state *state)
1181 {
1182 unsigned int j;
1183
1184 for (j = 0; j != n; ) {
1185 unsigned int points = 0;
1186 unsigned int k;
1187 switch ((int) p[j]) {
1188 case svgtiny_PATH_MOVE:
1189 case svgtiny_PATH_LINE:
1190 points = 1;
1191 break;
1192 case svgtiny_PATH_CLOSE:
1193 points = 0;
1194 break;
1195 case svgtiny_PATH_BEZIER:
1196 points = 3;
1197 break;
1198 default:
1199 assert(0);
1200 }
1201 j++;
1202 for (k = 0; k != points; k++) {
1203 float x0 = p[j], y0 = p[j + 1];
1204 float x = state->ctm.a * x0 + state->ctm.c * y0 +
1205 state->ctm.e;
1206 float y = state->ctm.b * x0 + state->ctm.d * y0 +
1207 state->ctm.f;
1208 p[j] = x;
1209 p[j + 1] = y;
1210 j += 2;
1211 }
1212 }
1213 }
1214
1215
1216 /**
1217 * Free all memory used by a diagram.
1218 */
1219
1220 void svgtiny_free(struct svgtiny_diagram *svg)
1221 {
1222 unsigned int i;
1223 assert(svg);
1224
1225 for (i = 0; i != svg->shape_count; i++) {
1226 free(svg->shape[i].path);
1227 free(svg->shape[i].text);
1228 }
1229
1230 free(svg->shape);
1231
1232 free(svg);
1233 }
1234