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