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