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