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