]> gitweb.michael.orlitzky.com - libsvgtiny.git/blob - svgtiny.c
ab6b6dd5ea30f282320fa33bcccd5fff437a5be3
[libsvgtiny.git] / svgtiny.c
1 /*
2 * This file is part of Libsvgtiny
3 * Licensed under the MIT License,
4 * http://opensource.org/licenses/mit-license.php
5 * Copyright 2008 James Bursa <james@semichrome.net>
6 */
7
8 #define _GNU_SOURCE /* for strndup */
9 #include <assert.h>
10 #include <math.h>
11 #include <setjmp.h>
12 #include <stdbool.h>
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <libxml/parser.h>
17 #include <libxml/debugXML.h>
18 #include "svgtiny.h"
19 #include "svgtiny_internal.h"
20
21
22 static svgtiny_code svgtiny_parse_svg(xmlNode *svg,
23 struct svgtiny_parse_state state);
24 static svgtiny_code svgtiny_parse_path(xmlNode *path,
25 struct svgtiny_parse_state state);
26 static svgtiny_code svgtiny_parse_rect(xmlNode *rect,
27 struct svgtiny_parse_state state);
28 static svgtiny_code svgtiny_parse_circle(xmlNode *circle,
29 struct svgtiny_parse_state state);
30 static svgtiny_code svgtiny_parse_line(xmlNode *line,
31 struct svgtiny_parse_state state);
32 static svgtiny_code svgtiny_parse_poly(xmlNode *poly,
33 struct svgtiny_parse_state state, bool polygon);
34 static svgtiny_code svgtiny_parse_text(xmlNode *text,
35 struct svgtiny_parse_state state);
36 static void svgtiny_parse_position_attributes(const xmlNode *node,
37 const struct svgtiny_parse_state state,
38 float *x, float *y, float *width, float *height);
39 static 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;
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 } else {
399 /*LOG(("parse failed at \"%s\"", s));*/
400 break;
401 }
402 }
403
404 xmlFree(path_d);
405
406 return svgtiny_add_path(p, i, &state);
407 }
408
409
410 /**
411 * Parse a <rect> element node.
412 *
413 * http://www.w3.org/TR/SVG11/shapes#RectElement
414 */
415
416 svgtiny_code svgtiny_parse_rect(xmlNode *rect,
417 struct svgtiny_parse_state state)
418 {
419 float x, y, width, height;
420
421 svgtiny_parse_position_attributes(rect, state,
422 &x, &y, &width, &height);
423 svgtiny_parse_paint_attributes(rect, &state);
424 svgtiny_parse_transform_attributes(rect, &state);
425
426 float *p = malloc(13 * sizeof p[0]);
427 if (!p)
428 return svgtiny_OUT_OF_MEMORY;
429
430 p[0] = svgtiny_PATH_MOVE;
431 p[1] = x;
432 p[2] = y;
433 p[3] = svgtiny_PATH_LINE;
434 p[4] = x + width;
435 p[5] = y;
436 p[6] = svgtiny_PATH_LINE;
437 p[7] = x + width;
438 p[8] = y + height;
439 p[9] = svgtiny_PATH_LINE;
440 p[10] = x;
441 p[11] = y + height;
442 p[12] = svgtiny_PATH_CLOSE;
443
444 return svgtiny_add_path(p, 13, &state);
445 }
446
447
448 /**
449 * Parse a <circle> element node.
450 */
451
452 svgtiny_code svgtiny_parse_circle(xmlNode *circle,
453 struct svgtiny_parse_state state)
454 {
455 float x = 0, y = 0, r = 0;
456 const float kappa = 0.5522847498;
457
458 for (xmlAttr *attr = circle->properties; attr; attr = attr->next) {
459 const char *name = (const char *) attr->name;
460 const char *content = (const char *) attr->children->content;
461 if (strcmp(name, "cx") == 0)
462 x = svgtiny_parse_length(content,
463 state.viewport_width, state);
464 else if (strcmp(name, "cy") == 0)
465 y = svgtiny_parse_length(content,
466 state.viewport_height, state);
467 else if (strcmp(name, "r") == 0)
468 r = svgtiny_parse_length(content,
469 state.viewport_width, state);
470 }
471 svgtiny_parse_paint_attributes(circle, &state);
472 svgtiny_parse_transform_attributes(circle, &state);
473
474 float *p = malloc(32 * sizeof p[0]);
475 if (!p)
476 return svgtiny_OUT_OF_MEMORY;
477
478 p[0] = svgtiny_PATH_MOVE;
479 p[1] = x - r;
480 p[2] = y;
481 p[3] = svgtiny_PATH_BEZIER;
482 p[4] = x - r;
483 p[5] = y + r * kappa;
484 p[6] = x - r * kappa;
485 p[7] = y + r;
486 p[8] = x;
487 p[9] = y + r;
488 p[10] = svgtiny_PATH_BEZIER;
489 p[11] = x + r * kappa;
490 p[12] = y + r;
491 p[13] = x + r;
492 p[14] = y + r * kappa;
493 p[15] = x + r;
494 p[16] = y;
495 p[17] = svgtiny_PATH_BEZIER;
496 p[18] = x + r;
497 p[19] = y - r * kappa;
498 p[20] = x + r * kappa;
499 p[21] = y - r;
500 p[22] = x;
501 p[23] = y - r;
502 p[24] = svgtiny_PATH_BEZIER;
503 p[25] = x - r * kappa;
504 p[26] = y - r;
505 p[27] = x - r;
506 p[28] = y - r * kappa;
507 p[29] = x - r;
508 p[30] = y;
509 p[31] = svgtiny_PATH_CLOSE;
510
511 return svgtiny_add_path(p, 32, &state);
512 }
513
514
515 /**
516 * Parse a <line> element node.
517 */
518
519 svgtiny_code svgtiny_parse_line(xmlNode *line,
520 struct svgtiny_parse_state state)
521 {
522 float x1 = 0, y1 = 0, x2 = 0, y2 = 0;
523
524 for (xmlAttr *attr = line->properties; attr; attr = attr->next) {
525 const char *name = (const char *) attr->name;
526 const char *content = (const char *) attr->children->content;
527 if (strcmp(name, "x1") == 0)
528 x1 = svgtiny_parse_length(content,
529 state.viewport_width, state);
530 else if (strcmp(name, "y1") == 0)
531 y1 = svgtiny_parse_length(content,
532 state.viewport_height, state);
533 else if (strcmp(name, "x2") == 0)
534 x2 = svgtiny_parse_length(content,
535 state.viewport_width, state);
536 else if (strcmp(name, "y2") == 0)
537 y2 = svgtiny_parse_length(content,
538 state.viewport_height, state);
539 }
540 svgtiny_parse_paint_attributes(line, &state);
541 svgtiny_parse_transform_attributes(line, &state);
542
543 float *p = malloc(7 * sizeof p[0]);
544 if (!p)
545 return svgtiny_OUT_OF_MEMORY;
546
547 p[0] = svgtiny_PATH_MOVE;
548 p[1] = x1;
549 p[2] = y1;
550 p[3] = svgtiny_PATH_LINE;
551 p[4] = x2;
552 p[5] = y2;
553 p[6] = svgtiny_PATH_CLOSE;
554
555 return svgtiny_add_path(p, 7, &state);
556 }
557
558
559 /**
560 * Parse a <polyline> or <polygon> element node.
561 *
562 * http://www.w3.org/TR/SVG11/shapes#PolylineElement
563 * http://www.w3.org/TR/SVG11/shapes#PolygonElement
564 */
565
566 svgtiny_code svgtiny_parse_poly(xmlNode *poly,
567 struct svgtiny_parse_state state, bool polygon)
568 {
569 char *s, *points;
570
571 svgtiny_parse_paint_attributes(poly, &state);
572 svgtiny_parse_transform_attributes(poly, &state);
573
574 /* read points attribute */
575 s = points = (char *) xmlGetProp(poly, (const xmlChar *) "points");
576 if (!s) {
577 state.diagram->error_line = poly->line;
578 state.diagram->error_message =
579 "polyline/polygon: missing points attribute";
580 return svgtiny_SVG_ERROR;
581 }
582
583 /* allocate space for path: it will never have more elements than s */
584 float *p = malloc(sizeof p[0] * strlen(s));
585 if (!p) {
586 xmlFree(points);
587 return svgtiny_OUT_OF_MEMORY;
588 }
589
590 /* parse s and build path */
591 for (unsigned int i = 0; s[i]; i++)
592 if (s[i] == ',')
593 s[i] = ' ';
594 unsigned int i = 0;
595 while (*s) {
596 float x, y;
597 int n;
598
599 if (sscanf(s, "%f %f %n", &x, &y, &n) == 2) {
600 if (i == 0)
601 p[i++] = svgtiny_PATH_MOVE;
602 else
603 p[i++] = svgtiny_PATH_LINE;
604 p[i++] = x;
605 p[i++] = y;
606 s += n;
607 } else {
608 break;
609 }
610 }
611 if (polygon)
612 p[i++] = svgtiny_PATH_CLOSE;
613
614 xmlFree(points);
615
616 return svgtiny_add_path(p, i, &state);
617 }
618
619
620 /**
621 * Parse a <text> or <tspan> element node.
622 */
623
624 svgtiny_code svgtiny_parse_text(xmlNode *text,
625 struct svgtiny_parse_state state)
626 {
627 float x, y, width, height;
628
629 svgtiny_parse_position_attributes(text, state,
630 &x, &y, &width, &height);
631 svgtiny_parse_font_attributes(text, &state);
632 svgtiny_parse_transform_attributes(text, &state);
633
634 float px = state.ctm.a * x + state.ctm.c * y + state.ctm.e;
635 float py = state.ctm.b * x + state.ctm.d * y + state.ctm.f;
636 /* state.ctm.e = px - state.origin_x; */
637 /* state.ctm.f = py - state.origin_y; */
638
639 /*struct css_style style = state.style;
640 style.font_size.value.length.value *= state.ctm.a;*/
641
642 for (xmlNode *child = text->children; child; child = child->next) {
643 svgtiny_code code = svgtiny_OK;
644
645 if (child->type == XML_TEXT_NODE) {
646 struct svgtiny_shape *shape = svgtiny_add_shape(&state);
647 if (!shape)
648 return svgtiny_OUT_OF_MEMORY;
649 shape->text = strdup((const char *) child->content);
650 shape->text_x = px;
651 shape->text_y = py;
652 state.diagram->shape_count++;
653
654 } else if (child->type == XML_ELEMENT_NODE &&
655 strcmp((const char *) child->name,
656 "tspan") == 0) {
657 code = svgtiny_parse_text(child, state);
658 }
659
660 if (!code != svgtiny_OK)
661 return code;
662 }
663
664 return svgtiny_OK;
665 }
666
667
668 /**
669 * Parse x, y, width, and height attributes, if present.
670 */
671
672 void svgtiny_parse_position_attributes(const xmlNode *node,
673 const struct svgtiny_parse_state state,
674 float *x, float *y, float *width, float *height)
675 {
676 *x = 0;
677 *y = 0;
678 *width = state.viewport_width;
679 *height = state.viewport_height;
680
681 for (xmlAttr *attr = node->properties; attr; attr = attr->next) {
682 const char *name = (const char *) attr->name;
683 const char *content = (const char *) attr->children->content;
684 if (strcmp(name, "x") == 0)
685 *x = svgtiny_parse_length(content,
686 state.viewport_width, state);
687 else if (strcmp(name, "y") == 0)
688 *y = svgtiny_parse_length(content,
689 state.viewport_height, state);
690 else if (strcmp(name, "width") == 0)
691 *width = svgtiny_parse_length(content,
692 state.viewport_width, state);
693 else if (strcmp(name, "height") == 0)
694 *height = svgtiny_parse_length(content,
695 state.viewport_height, state);
696 }
697 }
698
699
700 /**
701 * Parse a length as a number of pixels.
702 */
703
704 float svgtiny_parse_length(const char *s, int viewport_size,
705 const struct svgtiny_parse_state state)
706 {
707 int num_length = strspn(s, "0123456789+-.");
708 const char *unit = s + num_length;
709 float n = atof((const char *) s);
710 float font_size = 20; /*css_len2px(&state.style.font_size.value.length, 0);*/
711
712 if (unit[0] == 0) {
713 return n;
714 } else if (unit[0] == '%') {
715 return n / 100.0 * viewport_size;
716 } else if (unit[0] == 'e' && unit[1] == 'm') {
717 return n * font_size;
718 } else if (unit[0] == 'e' && unit[1] == 'x') {
719 return n / 2.0 * font_size;
720 } else if (unit[0] == 'p' && unit[1] == 'x') {
721 return n;
722 } else if (unit[0] == 'p' && unit[1] == 't') {
723 return n * 1.25;
724 } else if (unit[0] == 'p' && unit[1] == 'c') {
725 return n * 15.0;
726 } else if (unit[0] == 'm' && unit[1] == 'm') {
727 return n * 3.543307;
728 } else if (unit[0] == 'c' && unit[1] == 'm') {
729 return n * 35.43307;
730 } else if (unit[0] == 'i' && unit[1] == 'n') {
731 return n * 90;
732 }
733
734 return 0;
735 }
736
737
738 /**
739 * Parse paint attributes, if present.
740 */
741
742 void svgtiny_parse_paint_attributes(const xmlNode *node,
743 struct svgtiny_parse_state *state)
744 {
745 for (const xmlAttr *attr = node->properties; attr; attr = attr->next) {
746 const char *name = (const char *) attr->name;
747 const char *content = (const char *) attr->children->content;
748 if (strcmp(name, "fill") == 0)
749 svgtiny_parse_color(content, &state->fill, state);
750 else if (strcmp(name, "stroke") == 0)
751 svgtiny_parse_color(content, &state->stroke, state);
752 else if (strcmp(name, "stroke-width") == 0)
753 state->stroke_width = svgtiny_parse_length(content,
754 state->viewport_width, *state);
755 else if (strcmp(name, "style") == 0) {
756 const char *style = (const char *)
757 attr->children->content;
758 const char *s;
759 char *value;
760 if ((s = strstr(style, "fill:"))) {
761 s += 5;
762 while (*s == ' ')
763 s++;
764 value = strndup(s, strcspn(s, "; "));
765 svgtiny_parse_color(value, &state->fill, state);
766 free(value);
767 }
768 if ((s = strstr(style, "stroke:"))) {
769 s += 7;
770 while (*s == ' ')
771 s++;
772 value = strndup(s, strcspn(s, "; "));
773 svgtiny_parse_color(value, &state->stroke, state);
774 free(value);
775 }
776 if ((s = strstr(style, "stroke-width:"))) {
777 s += 13;
778 while (*s == ' ')
779 s++;
780 state->stroke_width = svgtiny_parse_length(s,
781 state->viewport_width, *state);
782 }
783 }
784 }
785 }
786
787
788 /**
789 * Parse a colour.
790 */
791
792 void svgtiny_parse_color(const char *s, svgtiny_colour *c,
793 struct svgtiny_parse_state *state)
794 {
795 unsigned int r, g, b;
796 float rf, gf, bf;
797 size_t len = strlen(s);
798 char *id = 0, *rparen;
799
800 if (len == 4 && s[0] == '#') {
801 if (sscanf(s + 1, "%1x%1x%1x", &r, &g, &b) == 3)
802 *c = svgtiny_RGB(r | r << 4, g | g << 4, b | b << 4);
803
804 } else if (len == 7 && s[0] == '#') {
805 if (sscanf(s + 1, "%2x%2x%2x", &r, &g, &b) == 3)
806 *c = svgtiny_RGB(r, g, b);
807
808 } else if (10 <= len && s[0] == 'r' && s[1] == 'g' && s[2] == 'b' &&
809 s[3] == '(' && s[len - 1] == ')') {
810 if (sscanf(s + 4, "%i,%i,%i", &r, &g, &b) == 3)
811 *c = svgtiny_RGB(r, g, b);
812 else if (sscanf(s + 4, "%f%%,%f%%,%f%%", &rf, &gf, &bf) == 3) {
813 b = bf * 255 / 100;
814 g = gf * 255 / 100;
815 r = rf * 255 / 100;
816 *c = svgtiny_RGB(r, g, b);
817 }
818
819 } else if (len == 4 && strcmp(s, "none") == 0) {
820 *c = svgtiny_TRANSPARENT;
821
822 } else if (5 < len && s[0] == 'u' && s[1] == 'r' && s[2] == 'l' &&
823 s[3] == '(') {
824 if (s[4] == '#') {
825 id = strdup(s + 5);
826 if (!id)
827 return;
828 rparen = strchr(id, ')');
829 if (rparen)
830 *rparen = 0;
831 svgtiny_find_gradient(id, state);
832 free(id);
833 fprintf(stderr, "linear_gradient_stop_count %i\n",
834 state->linear_gradient_stop_count);
835 if (state->linear_gradient_stop_count == 0)
836 *c = svgtiny_TRANSPARENT;
837 else if (state->linear_gradient_stop_count == 1)
838 *c = state->gradient_stop[0].color;
839 else
840 *c = svgtiny_LINEAR_GRADIENT;
841 }
842
843 } else {
844 const struct svgtiny_named_color *named_color;
845 named_color = svgtiny_color_lookup(s, strlen(s));
846 if (named_color)
847 *c = named_color->color;
848 }
849 }
850
851
852 /**
853 * Parse font attributes, if present.
854 */
855
856 void svgtiny_parse_font_attributes(const xmlNode *node,
857 struct svgtiny_parse_state *state)
858 {
859 for (const xmlAttr *attr = node->properties; attr; attr = attr->next) {
860 if (strcmp((const char *) attr->name, "font-size") == 0) {
861 /*if (css_parse_length(
862 (const char *) attr->children->content,
863 &state->style.font_size.value.length,
864 true, true)) {
865 state->style.font_size.size =
866 CSS_FONT_SIZE_LENGTH;
867 }*/
868 }
869 }
870 }
871
872
873 /**
874 * Parse transform attributes, if present.
875 *
876 * http://www.w3.org/TR/SVG11/coords#TransformAttribute
877 */
878
879 void svgtiny_parse_transform_attributes(xmlNode *node,
880 struct svgtiny_parse_state *state)
881 {
882 char *transform;
883
884 /* parse transform */
885 transform = (char *) xmlGetProp(node, (const xmlChar *) "transform");
886 if (transform) {
887 svgtiny_parse_transform(transform, &state->ctm.a, &state->ctm.b,
888 &state->ctm.c, &state->ctm.d,
889 &state->ctm.e, &state->ctm.f);
890 xmlFree(transform);
891 }
892 }
893
894
895 /**
896 * Parse a transform string.
897 */
898
899 void svgtiny_parse_transform(char *s, float *ma, float *mb,
900 float *mc, float *md, float *me, float *mf)
901 {
902 float a, b, c, d, e, f;
903 float za, zb, zc, zd, ze, zf;
904 float angle, x, y;
905 int n;
906
907 for (unsigned int i = 0; s[i]; i++)
908 if (s[i] == ',')
909 s[i] = ' ';
910
911 while (*s) {
912 a = d = 1;
913 b = c = 0;
914 e = f = 0;
915 if (sscanf(s, "matrix (%f %f %f %f %f %f) %n",
916 &a, &b, &c, &d, &e, &f, &n) == 6)
917 ;
918 else if (sscanf(s, "translate (%f %f) %n",
919 &e, &f, &n) == 2)
920 ;
921 else if (sscanf(s, "translate (%f) %n",
922 &e, &n) == 1)
923 ;
924 else if (sscanf(s, "scale (%f %f) %n",
925 &a, &d, &n) == 2)
926 ;
927 else if (sscanf(s, "scale (%f) %n",
928 &a, &n) == 1)
929 d = a;
930 else if (sscanf(s, "rotate (%f %f %f) %n",
931 &angle, &x, &y, &n) == 3) {
932 angle = angle / 180 * M_PI;
933 a = cos(angle);
934 b = sin(angle);
935 c = -sin(angle);
936 d = cos(angle);
937 e = -x * cos(angle) + y * sin(angle) + x;
938 f = -x * sin(angle) - y * cos(angle) + y;
939 } else if (sscanf(s, "rotate (%f) %n",
940 &angle, &n) == 1) {
941 angle = angle / 180 * M_PI;
942 a = cos(angle);
943 b = sin(angle);
944 c = -sin(angle);
945 d = cos(angle);
946 } else if (sscanf(s, "skewX (%f) %n",
947 &angle, &n) == 1) {
948 angle = angle / 180 * M_PI;
949 c = tan(angle);
950 } else if (sscanf(s, "skewY (%f) %n",
951 &angle, &n) == 1) {
952 angle = angle / 180 * M_PI;
953 b = tan(angle);
954 } else
955 break;
956 za = *ma * a + *mc * b;
957 zb = *mb * a + *md * b;
958 zc = *ma * c + *mc * d;
959 zd = *mb * c + *md * d;
960 ze = *ma * e + *mc * f + *me;
961 zf = *mb * e + *md * f + *mf;
962 *ma = za;
963 *mb = zb;
964 *mc = zc;
965 *md = zd;
966 *me = ze;
967 *mf = zf;
968 s += n;
969 }
970 }
971
972
973 /**
974 * Add a path to the svgtiny_diagram.
975 */
976
977 svgtiny_code svgtiny_add_path(float *p, unsigned int n,
978 struct svgtiny_parse_state *state)
979 {
980 if (state->fill == svgtiny_LINEAR_GRADIENT)
981 return svgtiny_add_path_linear_gradient(p, n, state);
982
983 svgtiny_transform_path(p, n, state);
984
985 struct svgtiny_shape *shape = svgtiny_add_shape(state);
986 if (!shape) {
987 free(p);
988 return svgtiny_OUT_OF_MEMORY;
989 }
990 shape->path = p;
991 shape->path_length = n;
992 state->diagram->shape_count++;
993
994 return svgtiny_OK;
995 }
996
997
998 /**
999 * Add a svgtiny_shape to the svgtiny_diagram.
1000 */
1001
1002 struct svgtiny_shape *svgtiny_add_shape(struct svgtiny_parse_state *state)
1003 {
1004 struct svgtiny_shape *shape = realloc(state->diagram->shape,
1005 (state->diagram->shape_count + 1) *
1006 sizeof (state->diagram->shape[0]));
1007 if (!shape)
1008 return 0;
1009 state->diagram->shape = shape;
1010
1011 shape += state->diagram->shape_count;
1012 shape->path = 0;
1013 shape->path_length = 0;
1014 shape->text = 0;
1015 shape->fill = state->fill;
1016 shape->stroke = state->stroke;
1017 shape->stroke_width = state->stroke_width *
1018 (state->ctm.a + state->ctm.d) / 2;
1019
1020 return shape;
1021 }
1022
1023
1024 /**
1025 * Apply the current transformation matrix to a path.
1026 */
1027
1028 void svgtiny_transform_path(float *p, unsigned int n,
1029 struct svgtiny_parse_state *state)
1030 {
1031 for (unsigned int j = 0; j != n; ) {
1032 unsigned int points = 0;
1033 switch ((int) p[j]) {
1034 case svgtiny_PATH_MOVE:
1035 case svgtiny_PATH_LINE:
1036 points = 1;
1037 break;
1038 case svgtiny_PATH_CLOSE:
1039 points = 0;
1040 break;
1041 case svgtiny_PATH_BEZIER:
1042 points = 3;
1043 break;
1044 default:
1045 assert(0);
1046 }
1047 j++;
1048 for (unsigned int k = 0; k != points; k++) {
1049 float x0 = p[j], y0 = p[j + 1];
1050 float x = state->ctm.a * x0 + state->ctm.c * y0 +
1051 state->ctm.e;
1052 float y = state->ctm.b * x0 + state->ctm.d * y0 +
1053 state->ctm.f;
1054 p[j] = x;
1055 p[j + 1] = y;
1056 j += 2;
1057 }
1058 }
1059 }
1060
1061
1062 /**
1063 * Free all memory used by a diagram.
1064 */
1065
1066 void svgtiny_free(struct svgtiny_diagram *svg)
1067 {
1068 assert(svg);
1069
1070 for (unsigned int i = 0; i != svg->shape_count; i++) {
1071 free(svg->shape[i].path);
1072 free(svg->shape[i].text);
1073 }
1074
1075 free(svg->shape);
1076
1077 free(svg);
1078 }
1079