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