]> gitweb.michael.orlitzky.com - libsvgtiny.git/blob - src/svgtiny.c
README: document the new svgtiny_LIBCSS_ERROR code
[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 * Copyright 2012 Daniel Silverstone <dsilvers@netsurf-browser.org>
7 */
8
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
17 #include <dom/dom.h>
18 #include <dom/bindings/xml/xmlparser.h>
19
20 #include "svgtiny.h"
21 #include "svgtiny_internal.h"
22
23 /* Source file generated by `gperf`. */
24 #include "autogenerated_colors.c"
25
26 #define TAU 6.28318530717958647692
27
28 #ifndef M_PI
29 #define M_PI 3.14159265358979323846
30 #endif
31
32 #ifndef M_PI_2
33 #define M_PI_2 1.57079632679489661923
34 #endif
35
36 #define KAPPA 0.5522847498
37
38 #define degToRad(angleInDegrees) ((angleInDegrees) * M_PI / 180.0)
39 #define radToDeg(angleInRadians) ((angleInRadians) * 180.0 / M_PI)
40
41 static svgtiny_code svgtiny_parse_svg(dom_element *svg,
42 struct svgtiny_parse_state state);
43 static svgtiny_code svgtiny_parse_path(dom_element *path,
44 struct svgtiny_parse_state state);
45 static svgtiny_code svgtiny_parse_rect(dom_element *rect,
46 struct svgtiny_parse_state state);
47 static svgtiny_code svgtiny_parse_circle(dom_element *circle,
48 struct svgtiny_parse_state state);
49 static svgtiny_code svgtiny_parse_ellipse(dom_element *ellipse,
50 struct svgtiny_parse_state state);
51 static svgtiny_code svgtiny_parse_line(dom_element *line,
52 struct svgtiny_parse_state state);
53 static svgtiny_code svgtiny_parse_poly(dom_element *poly,
54 struct svgtiny_parse_state state, bool polygon);
55 static svgtiny_code svgtiny_parse_text(dom_element *text,
56 struct svgtiny_parse_state state);
57 static void svgtiny_parse_position_attributes(dom_element *node,
58 const struct svgtiny_parse_state state,
59 float *x, float *y, float *width, float *height);
60 static void svgtiny_parse_paint_attributes(dom_element *node,
61 struct svgtiny_parse_state *state);
62 static void svgtiny_parse_font_attributes(dom_element *node,
63 struct svgtiny_parse_state *state);
64 static void svgtiny_parse_transform_attributes(dom_element *node,
65 struct svgtiny_parse_state *state);
66 static svgtiny_code svgtiny_add_path(float *p, unsigned int n,
67 struct svgtiny_parse_state *state);
68 static void _svgtiny_parse_color(const char *s, svgtiny_colour *c,
69 struct svgtiny_parse_state_gradient *grad,
70 struct svgtiny_parse_state *state);
71
72 /**
73 * rotate midpoint vector
74 */
75 static void
76 rotate_midpoint_vector(float ax, float ay,
77 float bx, float by,
78 double radangle,
79 double *x_out, double *y_out)
80 {
81 double dx2; /* midpoint x coordinate */
82 double dy2; /* midpoint y coordinate */
83 double cosangle; /* cosine of rotation angle */
84 double sinangle; /* sine of rotation angle */
85
86 /* compute the sin and cos of the angle */
87 cosangle = cos(radangle);
88 sinangle = sin(radangle);
89
90 /* compute the midpoint between start and end points */
91 dx2 = (ax - bx) / 2.0;
92 dy2 = (ay - by) / 2.0;
93
94 /* rotate vector to remove angle */
95 *x_out = ((cosangle * dx2) + (sinangle * dy2));
96 *y_out = ((-sinangle * dx2) + (cosangle * dy2));
97 }
98
99
100 /**
101 * ensure the arc radii are large enough and scale as appropriate
102 *
103 * the radii need to be large enough if they are not they must be
104 * adjusted. This allows for elimination of differences between
105 * implementations especialy with rounding.
106 */
107 static void
108 ensure_radii_scale(double x1_sq, double y1_sq,
109 float *rx, float *ry,
110 double *rx_sq, double *ry_sq)
111 {
112 double radiisum;
113 double radiiscale;
114
115 /* set radii square values */
116 (*rx_sq) = (*rx) * (*rx);
117 (*ry_sq) = (*ry) * (*ry);
118
119 radiisum = (x1_sq / (*rx_sq)) + (y1_sq / (*ry_sq));
120 if (radiisum > 0.99999) {
121 /* need to scale radii */
122 radiiscale = sqrt(radiisum) * 1.00001;
123 *rx = (float)(radiiscale * (*rx));
124 *ry = (float)(radiiscale * (*ry));
125 /* update squares too */
126 (*rx_sq) = (*rx) * (*rx);
127 (*ry_sq) = (*ry) * (*ry);
128 }
129 }
130
131
132 /**
133 * compute the transformed centre point
134 */
135 static void
136 compute_transformed_centre_point(double sign, float rx, float ry,
137 double rx_sq, double ry_sq,
138 double x1, double y1,
139 double x1_sq, double y1_sq,
140 double *cx1, double *cy1)
141 {
142 double sq;
143 double coef;
144 sq = ((rx_sq * ry_sq) - (rx_sq * y1_sq) - (ry_sq * x1_sq)) /
145 ((rx_sq * y1_sq) + (ry_sq * x1_sq));
146 sq = (sq < 0) ? 0 : sq;
147
148 coef = (sign * sqrt(sq));
149
150 *cx1 = coef * ((rx * y1) / ry);
151 *cy1 = coef * -((ry * x1) / rx);
152 }
153
154
155 /**
156 * compute untransformed centre point
157 *
158 * \param ax The first point x coordinate
159 * \param ay The first point y coordinate
160 * \param bx The second point x coordinate
161 * \param ay The second point y coordinate
162 */
163 static void
164 compute_centre_point(float ax, float ay,
165 float bx, float by,
166 double cx1, double cy1,
167 double radangle,
168 double *x_out, double *y_out)
169 {
170 double sx2;
171 double sy2;
172 double cosangle; /* cosine of rotation angle */
173 double sinangle; /* sine of rotation angle */
174
175 /* compute the sin and cos of the angle */
176 cosangle = cos(radangle);
177 sinangle = sin(radangle);
178
179 sx2 = (ax + bx) / 2.0;
180 sy2 = (ay + by) / 2.0;
181
182 *x_out = sx2 + (cosangle * cx1 - sinangle * cy1);
183 *y_out = sy2 + (sinangle * cx1 + cosangle * cy1);
184 }
185
186
187 /**
188 * compute the angle start and extent
189 */
190 static void
191 compute_angle_start_extent(float rx, float ry,
192 double x1, double y1,
193 double cx1, double cy1,
194 double *start, double *extent)
195 {
196 double sign;
197 double ux;
198 double uy;
199 double vx;
200 double vy;
201 double p, n;
202 double actmp;
203
204 /*
205 * Angle betwen two vectors is +/- acos( u.v / len(u) * len(v))
206 * Where:
207 * '.' is the dot product.
208 * +/- is calculated from the sign of the cross product (u x v)
209 */
210
211 ux = (x1 - cx1) / rx;
212 uy = (y1 - cy1) / ry;
213 vx = (-x1 - cx1) / rx;
214 vy = (-y1 - cy1) / ry;
215
216 /* compute the start angle */
217 /* The angle between (ux, uy) and the 0 angle */
218
219 /* len(u) * len(1,0) == len(u) */
220 n = sqrt((ux * ux) + (uy * uy));
221 /* u.v == (ux,uy).(1,0) == (1 * ux) + (0 * uy) == ux */
222 p = ux;
223 /* u x v == (1 * uy - ux * 0) == uy */
224 sign = (uy < 0) ? -1.0 : 1.0;
225 /* (p >= n) so safe */
226 *start = sign * acos(p / n);
227
228 /* compute the extent angle */
229 n = sqrt(((ux * ux) + (uy * uy)) * ((vx * vx) + (vy * vy)));
230 p = (ux * vx) + (uy * vy);
231 sign = ((ux * vy) - (uy * vx) < 0) ? -1.0f : 1.0f;
232
233 /* arc cos must operate between -1 and 1 */
234 actmp = p / n;
235 if (actmp < -1.0) {
236 *extent = sign * M_PI;
237 } else if (actmp > 1.0) {
238 *extent = 0;
239 } else {
240 *extent = sign * acos(actmp);
241 }
242 }
243
244
245 /**
246 * converts a circle centered unit circle arc to a series of bezier curves
247 *
248 * Each bezier is stored as six values of three pairs of coordinates
249 *
250 * The beziers are stored without their start point as that is assumed
251 * to be the preceding elements end point.
252 *
253 * \param start The start angle of the arc (in radians)
254 * \param extent The size of the arc (in radians)
255 * \param bzpt The array to store the bezier values in
256 * \return The number of bezier segments output (max 4)
257 */
258 static int
259 circle_arc_to_bezier(double start, double extent, double *bzpt)
260 {
261 int bzsegments;
262 double increment;
263 double controllen;
264 int pos = 0;
265 int segment;
266 double angle;
267 double dx, dy;
268
269 bzsegments = (int) ceil(fabs(extent) / M_PI_2);
270 increment = extent / bzsegments;
271 controllen = 4.0 / 3.0 * sin(increment / 2.0) / (1.0 + cos(increment / 2.0));
272
273 for (segment = 0; segment < bzsegments; segment++) {
274 /* first control point */
275 angle = start + (segment * increment);
276 dx = cos(angle);
277 dy = sin(angle);
278 bzpt[pos++] = dx - controllen * dy;
279 bzpt[pos++] = dy + controllen * dx;
280 /* second control point */
281 angle+=increment;
282 dx = cos(angle);
283 dy = sin(angle);
284 bzpt[pos++] = dx + controllen * dy;
285 bzpt[pos++] = dy - controllen * dx;
286 /* endpoint */
287 bzpt[pos++] = dx;
288 bzpt[pos++] = dy;
289
290 }
291 return bzsegments;
292 }
293
294
295 /**
296 * transform coordinate list
297 *
298 * perform a scale, rotate and translate on list of coordinates
299 *
300 * scale(rx,ry)
301 * rotate(an)
302 * translate (cx, cy)
303 *
304 * homogeneous transforms
305 *
306 * scaling
307 * | rx 0 0 |
308 * S = | 0 ry 0 |
309 * | 0 0 1 |
310 *
311 * rotate
312 * | cos(an) -sin(an) 0 |
313 * R = | sin(an) cos(an) 0 |
314 * | 0 0 1 |
315 *
316 * {{cos(a), -sin(a) 0}, {sin(a), cos(a),0}, {0,0,1}}
317 *
318 * translate
319 * | 1 0 cx |
320 * T = | 0 1 cy |
321 * | 0 0 1 |
322 *
323 * note order is significat here and the combined matrix is
324 * M = T.R.S
325 *
326 * | cos(an) -sin(an) cx |
327 * T.R = | sin(an) cos(an) cy |
328 * | 0 0 1 |
329 *
330 * | rx * cos(an) ry * -sin(an) cx |
331 * T.R.S = | rx * sin(an) ry * cos(an) cy |
332 * | 0 0 1 |
333 *
334 * {{Cos[a], -Sin[a], c}, {Sin[a], Cos[a], d}, {0, 0, 1}} . {{r, 0, 0}, {0, s, 0}, {0, 0, 1}}
335 *
336 * Each point
337 * | x1 |
338 * P = | y1 |
339 * | 1 |
340 *
341 * output
342 * | x2 |
343 * | y2 | = M . P
344 * | 1 |
345 *
346 * x2 = cx + (rx * x1 * cos(a)) + (ry * y1 * -1 * sin(a))
347 * y2 = cy + (ry * y1 * cos(a)) + (rx * x1 * sin(a))
348 *
349 *
350 * \param rx X scaling to apply
351 * \param ry Y scaling to apply
352 * \param radangle rotation to apply (in radians)
353 * \param cx X translation to apply
354 * \param cy Y translation to apply
355 * \param points The size of the bzpoints array
356 * \param bzpoints an array of x,y values to apply the transform to
357 */
358 static void
359 scale_rotate_translate_points(double rx, double ry,
360 double radangle,
361 double cx, double cy,
362 int pntsize,
363 double *points)
364 {
365 int pnt;
366 double cosangle; /* cosine of rotation angle */
367 double sinangle; /* sine of rotation angle */
368 double rxcosangle, rxsinangle, rycosangle, rynsinangle;
369 double x2,y2;
370
371 /* compute the sin and cos of the angle */
372 cosangle = cos(radangle);
373 sinangle = sin(radangle);
374
375 rxcosangle = rx * cosangle;
376 rxsinangle = rx * sinangle;
377 rycosangle = ry * cosangle;
378 rynsinangle = ry * -1 * sinangle;
379
380 for (pnt = 0; pnt < pntsize; pnt+=2) {
381 x2 = cx + (points[pnt] * rxcosangle) + (points[pnt + 1] * rynsinangle);
382 y2 = cy + (points[pnt + 1] * rycosangle) + (points[pnt] * rxsinangle);
383 points[pnt] = x2;
384 points[pnt + 1] = y2;
385 }
386 }
387
388
389 /**
390 * convert an svg path arc to a bezier curve
391 *
392 * This function perfoms a transform on the nine arc parameters
393 * (coordinate pairs for start and end together with the radii of the
394 * elipse, the rotation angle and which of the four arcs to draw)
395 * which generates the parameters (coordinate pairs for start,
396 * end and their control points) for a set of up to four bezier curves.
397 *
398 * Obviously the start and end coordinates are not altered between
399 * representations so the aim is to calculate the coordinate pairs for
400 * the bezier control points.
401 *
402 * \param bzpoints the array to fill with bezier curves
403 * \return the number of bezier segments generated or -1 for a line
404 */
405 static int
406 svgarc_to_bezier(float start_x,
407 float start_y,
408 float end_x,
409 float end_y,
410 float rx,
411 float ry,
412 float angle,
413 bool largearc,
414 bool sweep,
415 double *bzpoints)
416 {
417 double radangle; /* normalised elipsis rotation angle in radians */
418 double rx_sq; /* x radius squared */
419 double ry_sq; /* y radius squared */
420 double x1, y1; /* rotated midpoint vector */
421 double x1_sq, y1_sq; /* x1 vector squared */
422 double cx1,cy1; /* transformed circle center */
423 double cx,cy; /* circle center */
424 double start, extent;
425 int bzsegments;
426
427 if ((start_x == end_x) && (start_y == end_y)) {
428 /*
429 * if the start and end coordinates are the same the
430 * svg spec says this is equivalent to having no segment
431 * at all
432 */
433 return 0;
434 }
435
436 if ((rx == 0) || (ry == 0)) {
437 /*
438 * if either radii is zero the specified behaviour is a line
439 */
440 return -1;
441 }
442
443 /* obtain the absolute values of the radii */
444 rx = fabsf(rx);
445 ry = fabsf(ry);
446
447 /* convert normalised angle to radians */
448 radangle = degToRad(fmod(angle, 360.0));
449
450 /* step 1 */
451 /* x1,x2 is the midpoint vector rotated to remove the arc angle */
452 rotate_midpoint_vector(start_x, start_y, end_x, end_y, radangle, &x1, &y1);
453
454 /* step 2 */
455 /* get squared x1 values */
456 x1_sq = x1 * x1;
457 y1_sq = y1 * y1;
458
459 /* ensure radii are correctly scaled */
460 ensure_radii_scale(x1_sq, y1_sq, &rx, &ry, &rx_sq, &ry_sq);
461
462 /* compute the transformed centre point */
463 compute_transformed_centre_point(largearc == sweep?-1:1,
464 rx, ry,
465 rx_sq, ry_sq,
466 x1, y1,
467 x1_sq, y1_sq,
468 &cx1, &cy1);
469
470 /* step 3 */
471 /* get the untransformed centre point */
472 compute_centre_point(start_x, start_y,
473 end_x, end_y,
474 cx1, cy1,
475 radangle,
476 &cx, &cy);
477
478 /* step 4 */
479 /* compute anglestart and extent */
480 compute_angle_start_extent(rx,ry,
481 x1,y1,
482 cx1, cy1,
483 &start, &extent);
484
485 /* extent of 0 is a straight line */
486 if (extent == 0) {
487 return -1;
488 }
489
490 /* take account of sweep */
491 if (!sweep && extent > 0) {
492 extent -= TAU;
493 } else if (sweep && extent < 0) {
494 extent += TAU;
495 }
496
497 /* normalise start and extent */
498 extent = fmod(extent, TAU);
499 start = fmod(start, TAU);
500
501 /* convert the arc to unit circle bezier curves */
502 bzsegments = circle_arc_to_bezier(start, extent, bzpoints);
503
504 /* transform the bezier curves */
505 scale_rotate_translate_points(rx, ry,
506 radangle,
507 cx, cy,
508 bzsegments * 6,
509 bzpoints);
510
511 return bzsegments;
512 }
513
514
515 /**
516 * Call this to ref the strings in a gradient state.
517 */
518 static void svgtiny_grad_string_ref(struct svgtiny_parse_state_gradient *grad)
519 {
520 if (grad->gradient_x1 != NULL) {
521 dom_string_ref(grad->gradient_x1);
522 }
523 if (grad->gradient_y1 != NULL) {
524 dom_string_ref(grad->gradient_y1);
525 }
526 if (grad->gradient_x2 != NULL) {
527 dom_string_ref(grad->gradient_x2);
528 }
529 if (grad->gradient_y2 != NULL) {
530 dom_string_ref(grad->gradient_y2);
531 }
532 }
533
534 /**
535 * Call this to clean up the strings in a gradient state.
536 */
537 static void svgtiny_grad_string_cleanup(
538 struct svgtiny_parse_state_gradient *grad)
539 {
540 if (grad->gradient_x1 != NULL) {
541 dom_string_unref(grad->gradient_x1);
542 grad->gradient_x1 = NULL;
543 }
544 if (grad->gradient_y1 != NULL) {
545 dom_string_unref(grad->gradient_y1);
546 grad->gradient_y1 = NULL;
547 }
548 if (grad->gradient_x2 != NULL) {
549 dom_string_unref(grad->gradient_x2);
550 grad->gradient_x2 = NULL;
551 }
552 if (grad->gradient_y2 != NULL) {
553 dom_string_unref(grad->gradient_y2);
554 grad->gradient_y2 = NULL;
555 }
556 }
557
558 /**
559 * Set the local externally-stored parts of a parse state.
560 * Call this in functions that made a new state on the stack.
561 * Doesn't make own copy of global state, such as the interned string list.
562 */
563 static void svgtiny_setup_state_local(struct svgtiny_parse_state *state)
564 {
565 svgtiny_grad_string_ref(&(state->fill_grad));
566 svgtiny_grad_string_ref(&(state->stroke_grad));
567 }
568
569 /**
570 * Cleanup the local externally-stored parts of a parse state.
571 * Call this in functions that made a new state on the stack.
572 * Doesn't cleanup global state, such as the interned string list.
573 */
574 static void svgtiny_cleanup_state_local(struct svgtiny_parse_state *state)
575 {
576 svgtiny_grad_string_cleanup(&(state->fill_grad));
577 svgtiny_grad_string_cleanup(&(state->stroke_grad));
578 }
579
580
581 /**
582 * Create a new svgtiny_diagram structure.
583 */
584
585 struct svgtiny_diagram *svgtiny_create(void)
586 {
587 struct svgtiny_diagram *diagram;
588
589 diagram = calloc(sizeof(*diagram), 1);
590 if (!diagram)
591 return 0;
592
593 return diagram;
594 free(diagram);
595 return NULL;
596 }
597
598 static void ignore_msg(uint32_t severity, void *ctx, const char *msg, ...)
599 {
600 UNUSED(severity);
601 UNUSED(ctx);
602 UNUSED(msg);
603 }
604
605 /**
606 * Parse a block of memory into a svgtiny_diagram.
607 */
608
609 svgtiny_code svgtiny_parse(struct svgtiny_diagram *diagram,
610 const char *buffer, size_t size, const char *url,
611 int viewport_width, int viewport_height)
612 {
613 dom_document *document;
614 dom_exception exc;
615 dom_xml_parser *parser;
616 dom_xml_error err;
617 dom_element *svg;
618 dom_string *svg_name;
619 lwc_string *svg_name_lwc;
620 struct svgtiny_parse_state state;
621 float x, y, width, height;
622 svgtiny_code code;
623
624 assert(diagram);
625 assert(buffer);
626 assert(url);
627
628 UNUSED(url);
629
630 parser = dom_xml_parser_create(NULL, NULL,
631 ignore_msg, NULL, &document);
632
633 if (parser == NULL)
634 return svgtiny_LIBDOM_ERROR;
635
636 err = dom_xml_parser_parse_chunk(parser, (uint8_t *)buffer, size);
637 if (err != DOM_XML_OK) {
638 dom_node_unref(document);
639 dom_xml_parser_destroy(parser);
640 return svgtiny_LIBDOM_ERROR;
641 }
642
643 err = dom_xml_parser_completed(parser);
644 if (err != DOM_XML_OK) {
645 dom_node_unref(document);
646 dom_xml_parser_destroy(parser);
647 return svgtiny_LIBDOM_ERROR;
648 }
649
650 /* We're done parsing, drop the parser.
651 * We now own the document entirely.
652 */
653 dom_xml_parser_destroy(parser);
654
655 /* find root <svg> element */
656 exc = dom_document_get_document_element(document, &svg);
657 if (exc != DOM_NO_ERR) {
658 dom_node_unref(document);
659 return svgtiny_LIBDOM_ERROR;
660 }
661 if (svg == NULL) {
662 /* no root svg element */
663 dom_node_unref(document);
664 return svgtiny_SVG_ERROR;
665 }
666
667 exc = dom_node_get_node_name(svg, &svg_name);
668 if (exc != DOM_NO_ERR) {
669 dom_node_unref(svg);
670 dom_node_unref(document);
671 return svgtiny_LIBDOM_ERROR;
672 }
673 if (lwc_intern_string("svg", 3 /* SLEN("svg") */,
674 &svg_name_lwc) != lwc_error_ok) {
675 dom_string_unref(svg_name);
676 dom_node_unref(svg);
677 dom_node_unref(document);
678 return svgtiny_LIBDOM_ERROR;
679 }
680 if (!dom_string_caseless_lwc_isequal(svg_name, svg_name_lwc)) {
681 lwc_string_unref(svg_name_lwc);
682 dom_string_unref(svg_name);
683 dom_node_unref(svg);
684 dom_node_unref(document);
685 return svgtiny_NOT_SVG;
686 }
687
688 lwc_string_unref(svg_name_lwc);
689 dom_string_unref(svg_name);
690
691 /* get graphic dimensions */
692 memset(&state, 0, sizeof(state));
693 state.diagram = diagram;
694 state.document = document;
695 state.viewport_width = viewport_width;
696 state.viewport_height = viewport_height;
697
698 #define SVGTINY_STRING_ACTION2(s,n) \
699 if (dom_string_create_interned((const uint8_t *) #n, \
700 strlen(#n), &state.interned_##s) \
701 != DOM_NO_ERR) { \
702 code = svgtiny_LIBDOM_ERROR; \
703 goto cleanup; \
704 }
705 #include "svgtiny_strings.h"
706 #undef SVGTINY_STRING_ACTION2
707
708 svgtiny_parse_position_attributes(svg, state, &x, &y, &width, &height);
709 diagram->width = width;
710 diagram->height = height;
711
712 /* set up parsing state */
713 state.viewport_width = width;
714 state.viewport_height = height;
715 state.ctm.a = 1; /*(float) viewport_width / (float) width;*/
716 state.ctm.b = 0;
717 state.ctm.c = 0;
718 state.ctm.d = 1; /*(float) viewport_height / (float) height;*/
719 state.ctm.e = 0; /*x;*/
720 state.ctm.f = 0; /*y;*/
721 state.fill = 0x000000;
722 state.stroke = svgtiny_TRANSPARENT;
723 state.stroke_width = 1;
724
725 /* parse tree */
726 code = svgtiny_parse_svg(svg, state);
727
728 dom_node_unref(svg);
729 dom_node_unref(document);
730
731 cleanup:
732 svgtiny_cleanup_state_local(&state);
733 #define SVGTINY_STRING_ACTION2(s,n) \
734 if (state.interned_##s != NULL) \
735 dom_string_unref(state.interned_##s);
736 #include "svgtiny_strings.h"
737 #undef SVGTINY_STRING_ACTION2
738 return code;
739 }
740
741
742 /**
743 * Parse a <svg> or <g> element node.
744 */
745
746 svgtiny_code svgtiny_parse_svg(dom_element *svg,
747 struct svgtiny_parse_state state)
748 {
749 float x, y, width, height;
750 dom_string *view_box;
751 dom_element *child;
752 dom_exception exc;
753
754 svgtiny_setup_state_local(&state);
755
756 svgtiny_parse_position_attributes(svg, state, &x, &y, &width, &height);
757 svgtiny_parse_paint_attributes(svg, &state);
758 svgtiny_parse_font_attributes(svg, &state);
759
760 exc = dom_element_get_attribute(svg, state.interned_viewBox,
761 &view_box);
762 if (exc != DOM_NO_ERR) {
763 svgtiny_cleanup_state_local(&state);
764 return svgtiny_LIBDOM_ERROR;
765 }
766
767 if (view_box) {
768 char *s = strndup(dom_string_data(view_box),
769 dom_string_byte_length(view_box));
770 float min_x, min_y, vwidth, vheight;
771 if (sscanf(s, "%f,%f,%f,%f",
772 &min_x, &min_y, &vwidth, &vheight) == 4 ||
773 sscanf(s, "%f %f %f %f",
774 &min_x, &min_y, &vwidth, &vheight) == 4) {
775 state.ctm.a = (float) state.viewport_width / vwidth;
776 state.ctm.d = (float) state.viewport_height / vheight;
777 state.ctm.e += -min_x * state.ctm.a;
778 state.ctm.f += -min_y * state.ctm.d;
779 }
780 free(s);
781 dom_string_unref(view_box);
782 }
783
784 svgtiny_parse_transform_attributes(svg, &state);
785
786 exc = dom_node_get_first_child(svg, (dom_node **) (void *) &child);
787 if (exc != DOM_NO_ERR) {
788 svgtiny_cleanup_state_local(&state);
789 return svgtiny_LIBDOM_ERROR;
790 }
791 while (child != NULL) {
792 dom_element *next;
793 dom_node_type nodetype;
794 svgtiny_code code = svgtiny_OK;
795
796 exc = dom_node_get_node_type(child, &nodetype);
797 if (exc != DOM_NO_ERR) {
798 dom_node_unref(child);
799 return svgtiny_LIBDOM_ERROR;
800 }
801 if (nodetype == DOM_ELEMENT_NODE) {
802 dom_string *nodename;
803 exc = dom_node_get_node_name(child, &nodename);
804 if (exc != DOM_NO_ERR) {
805 dom_node_unref(child);
806 svgtiny_cleanup_state_local(&state);
807 return svgtiny_LIBDOM_ERROR;
808 }
809 if (dom_string_caseless_isequal(state.interned_svg,
810 nodename))
811 code = svgtiny_parse_svg(child, state);
812 else if (dom_string_caseless_isequal(state.interned_g,
813 nodename))
814 code = svgtiny_parse_svg(child, state);
815 else if (dom_string_caseless_isequal(state.interned_a,
816 nodename))
817 code = svgtiny_parse_svg(child, state);
818 else if (dom_string_caseless_isequal(state.interned_path,
819 nodename))
820 code = svgtiny_parse_path(child, state);
821 else if (dom_string_caseless_isequal(state.interned_rect,
822 nodename))
823 code = svgtiny_parse_rect(child, state);
824 else if (dom_string_caseless_isequal(state.interned_circle,
825 nodename))
826 code = svgtiny_parse_circle(child, state);
827 else if (dom_string_caseless_isequal(state.interned_ellipse,
828 nodename))
829 code = svgtiny_parse_ellipse(child, state);
830 else if (dom_string_caseless_isequal(state.interned_line,
831 nodename))
832 code = svgtiny_parse_line(child, state);
833 else if (dom_string_caseless_isequal(state.interned_polyline,
834 nodename))
835 code = svgtiny_parse_poly(child, state, false);
836 else if (dom_string_caseless_isequal(state.interned_polygon,
837 nodename))
838 code = svgtiny_parse_poly(child, state, true);
839 else if (dom_string_caseless_isequal(state.interned_text,
840 nodename))
841 code = svgtiny_parse_text(child, state);
842 dom_string_unref(nodename);
843 }
844 if (code != svgtiny_OK) {
845 dom_node_unref(child);
846 svgtiny_cleanup_state_local(&state);
847 return code;
848 }
849 exc = dom_node_get_next_sibling(child,
850 (dom_node **) (void *) &next);
851 dom_node_unref(child);
852 if (exc != DOM_NO_ERR) {
853 svgtiny_cleanup_state_local(&state);
854 return svgtiny_LIBDOM_ERROR;
855 }
856 child = next;
857 }
858
859 svgtiny_cleanup_state_local(&state);
860 return svgtiny_OK;
861 }
862
863
864
865 /**
866 * Parse a <path> element node.
867 *
868 * http://www.w3.org/TR/SVG11/paths#PathElement
869 */
870
871 svgtiny_code svgtiny_parse_path(dom_element *path,
872 struct svgtiny_parse_state state)
873 {
874 svgtiny_code err;
875 dom_string *path_d_str;
876 dom_exception exc;
877 char *s, *path_d;
878 float *p; /* path elemets */
879 unsigned int palloc; /* number of path elements allocated */
880 unsigned int i;
881 float last_x = 0, last_y = 0;
882 float last_cubic_x = 0, last_cubic_y = 0;
883 float last_quad_x = 0, last_quad_y = 0;
884 float subpath_first_x = 0, subpath_first_y = 0;
885
886 svgtiny_setup_state_local(&state);
887
888 svgtiny_parse_paint_attributes(path, &state);
889 svgtiny_parse_transform_attributes(path, &state);
890
891 /* read d attribute */
892 exc = dom_element_get_attribute(path, state.interned_d, &path_d_str);
893 if (exc != DOM_NO_ERR) {
894 state.diagram->error_line = -1; /* path->line; */
895 state.diagram->error_message = "path: error retrieving d attribute";
896 svgtiny_cleanup_state_local(&state);
897 return svgtiny_SVG_ERROR;
898 }
899
900 if (path_d_str == NULL) {
901 state.diagram->error_line = -1; /* path->line; */
902 state.diagram->error_message = "path: missing d attribute";
903 svgtiny_cleanup_state_local(&state);
904 return svgtiny_SVG_ERROR;
905 }
906
907 /* empty path is permitted it just disables the path */
908 palloc = dom_string_byte_length(path_d_str);
909 if (palloc == 0) {
910 dom_string_unref(path_d_str);
911 svgtiny_cleanup_state_local(&state);
912 return svgtiny_OK;
913 }
914
915 /* local copy of the path data allowing in-place modification */
916 s = path_d = strndup(dom_string_data(path_d_str), palloc);
917 dom_string_unref(path_d_str);
918 if (s == NULL) {
919 svgtiny_cleanup_state_local(&state);
920 return svgtiny_OUT_OF_MEMORY;
921 }
922
923 /* ensure path element allocation is sensibly bounded */
924 if (palloc < 8) {
925 palloc = 8;
926 } else if (palloc > 64) {
927 palloc = palloc / 2;
928 }
929
930 /* allocate initial space for path elements */
931 p = malloc(sizeof p[0] * palloc);
932 if (p == NULL) {
933 free(path_d);
934 svgtiny_cleanup_state_local(&state);
935 return svgtiny_OUT_OF_MEMORY;
936 }
937
938 /* parse d and build path */
939 for (i = 0; s[i]; i++)
940 if (s[i] == ',')
941 s[i] = ' ';
942 i = 0;
943 while (*s) {
944 char command[2];
945 int plot_command;
946 float x, y, x1, y1, x2, y2, rx, ry, rotation, large_arc, sweep;
947 int n;
948
949 /* Ensure there is sufficient space for path elements */
950 #define ALLOC_PATH_ELEMENTS(NUM_ELEMENTS) \
951 do { \
952 if ((palloc - i) < NUM_ELEMENTS) { \
953 float *tp; \
954 palloc = (palloc * 2) + (palloc / 2); \
955 tp = realloc(p, sizeof p[0] * palloc); \
956 if (tp == NULL) { \
957 free(p); \
958 free(path_d); \
959 svgtiny_cleanup_state_local(&state); \
960 return svgtiny_OUT_OF_MEMORY; \
961 } \
962 p = tp; \
963 } \
964 } while(0)
965
966
967 /* moveto (M, m), lineto (L, l) (2 arguments) */
968 if (sscanf(s, " %1[MmLl] %f %f %n", command, &x, &y, &n) == 3) {
969 /*LOG(("moveto or lineto"));*/
970 if (*command == 'M' || *command == 'm')
971 plot_command = svgtiny_PATH_MOVE;
972 else
973 plot_command = svgtiny_PATH_LINE;
974 do {
975 ALLOC_PATH_ELEMENTS(3);
976 p[i++] = plot_command;
977 if ('a' <= *command) {
978 x += last_x;
979 y += last_y;
980 }
981 if (plot_command == svgtiny_PATH_MOVE) {
982 subpath_first_x = x;
983 subpath_first_y = y;
984 }
985 p[i++] = last_cubic_x = last_quad_x = last_x
986 = x;
987 p[i++] = last_cubic_y = last_quad_y = last_y
988 = y;
989 s += n;
990 plot_command = svgtiny_PATH_LINE;
991 } while (sscanf(s, "%f %f %n", &x, &y, &n) == 2);
992
993 /* closepath (Z, z) (no arguments) */
994 } else if (sscanf(s, " %1[Zz] %n", command, &n) == 1) {
995 /*LOG(("closepath"));*/
996 ALLOC_PATH_ELEMENTS(1);
997
998 p[i++] = svgtiny_PATH_CLOSE;
999 s += n;
1000 last_cubic_x = last_quad_x = last_x = subpath_first_x;
1001 last_cubic_y = last_quad_y = last_y = subpath_first_y;
1002
1003 /* horizontal lineto (H, h) (1 argument) */
1004 } else if (sscanf(s, " %1[Hh] %f %n", command, &x, &n) == 2) {
1005 /*LOG(("horizontal lineto"));*/
1006 do {
1007 ALLOC_PATH_ELEMENTS(3);
1008
1009 p[i++] = svgtiny_PATH_LINE;
1010 if (*command == 'h')
1011 x += last_x;
1012 p[i++] = last_cubic_x = last_quad_x = last_x
1013 = x;
1014 p[i++] = last_cubic_y = last_quad_y = last_y;
1015 s += n;
1016 } while (sscanf(s, "%f %n", &x, &n) == 1);
1017
1018 /* vertical lineto (V, v) (1 argument) */
1019 } else if (sscanf(s, " %1[Vv] %f %n", command, &y, &n) == 2) {
1020 /*LOG(("vertical lineto"));*/
1021 do {
1022 ALLOC_PATH_ELEMENTS(3);
1023
1024 p[i++] = svgtiny_PATH_LINE;
1025 if (*command == 'v')
1026 y += last_y;
1027 p[i++] = last_cubic_x = last_quad_x = last_x;
1028 p[i++] = last_cubic_y = last_quad_y = last_y
1029 = y;
1030 s += n;
1031 } while (sscanf(s, "%f %n", &y, &n) == 1);
1032
1033 /* curveto (C, c) (6 arguments) */
1034 } else if (sscanf(s, " %1[Cc] %f %f %f %f %f %f %n", command,
1035 &x1, &y1, &x2, &y2, &x, &y, &n) == 7) {
1036 /*LOG(("curveto"));*/
1037 do {
1038 ALLOC_PATH_ELEMENTS(7);
1039
1040 p[i++] = svgtiny_PATH_BEZIER;
1041 if (*command == 'c') {
1042 x1 += last_x;
1043 y1 += last_y;
1044 x2 += last_x;
1045 y2 += last_y;
1046 x += last_x;
1047 y += last_y;
1048 }
1049 p[i++] = x1;
1050 p[i++] = y1;
1051 p[i++] = last_cubic_x = x2;
1052 p[i++] = last_cubic_y = y2;
1053 p[i++] = last_quad_x = last_x = x;
1054 p[i++] = last_quad_y = last_y = y;
1055 s += n;
1056 } while (sscanf(s, "%f %f %f %f %f %f %n",
1057 &x1, &y1, &x2, &y2, &x, &y, &n) == 6);
1058
1059 /* shorthand/smooth curveto (S, s) (4 arguments) */
1060 } else if (sscanf(s, " %1[Ss] %f %f %f %f %n", command,
1061 &x2, &y2, &x, &y, &n) == 5) {
1062 /*LOG(("shorthand/smooth curveto"));*/
1063 do {
1064 ALLOC_PATH_ELEMENTS(7);
1065
1066 p[i++] = svgtiny_PATH_BEZIER;
1067 x1 = last_x + (last_x - last_cubic_x);
1068 y1 = last_y + (last_y - last_cubic_y);
1069 if (*command == 's') {
1070 x2 += last_x;
1071 y2 += last_y;
1072 x += last_x;
1073 y += last_y;
1074 }
1075 p[i++] = x1;
1076 p[i++] = y1;
1077 p[i++] = last_cubic_x = x2;
1078 p[i++] = last_cubic_y = y2;
1079 p[i++] = last_quad_x = last_x = x;
1080 p[i++] = last_quad_y = last_y = y;
1081 s += n;
1082 } while (sscanf(s, "%f %f %f %f %n",
1083 &x2, &y2, &x, &y, &n) == 4);
1084
1085 /* quadratic Bezier curveto (Q, q) (4 arguments) */
1086 } else if (sscanf(s, " %1[Qq] %f %f %f %f %n", command,
1087 &x1, &y1, &x, &y, &n) == 5) {
1088 /*LOG(("quadratic Bezier curveto"));*/
1089 do {
1090 ALLOC_PATH_ELEMENTS(7);
1091
1092 p[i++] = svgtiny_PATH_BEZIER;
1093 last_quad_x = x1;
1094 last_quad_y = y1;
1095 if (*command == 'q') {
1096 x1 += last_x;
1097 y1 += last_y;
1098 x += last_x;
1099 y += last_y;
1100 }
1101 p[i++] = 1./3 * last_x + 2./3 * x1;
1102 p[i++] = 1./3 * last_y + 2./3 * y1;
1103 p[i++] = 2./3 * x1 + 1./3 * x;
1104 p[i++] = 2./3 * y1 + 1./3 * y;
1105 p[i++] = last_cubic_x = last_x = x;
1106 p[i++] = last_cubic_y = last_y = y;
1107 s += n;
1108 } while (sscanf(s, "%f %f %f %f %n",
1109 &x1, &y1, &x, &y, &n) == 4);
1110
1111 /* shorthand/smooth quadratic Bezier curveto (T, t)
1112 (2 arguments) */
1113 } else if (sscanf(s, " %1[Tt] %f %f %n", command,
1114 &x, &y, &n) == 3) {
1115 /*LOG(("shorthand/smooth quadratic Bezier curveto"));*/
1116 do {
1117 ALLOC_PATH_ELEMENTS(7);
1118
1119 p[i++] = svgtiny_PATH_BEZIER;
1120 x1 = last_x + (last_x - last_quad_x);
1121 y1 = last_y + (last_y - last_quad_y);
1122 last_quad_x = x1;
1123 last_quad_y = y1;
1124 if (*command == 't') {
1125 x1 += last_x;
1126 y1 += last_y;
1127 x += last_x;
1128 y += last_y;
1129 }
1130 p[i++] = 1./3 * last_x + 2./3 * x1;
1131 p[i++] = 1./3 * last_y + 2./3 * y1;
1132 p[i++] = 2./3 * x1 + 1./3 * x;
1133 p[i++] = 2./3 * y1 + 1./3 * y;
1134 p[i++] = last_cubic_x = last_x = x;
1135 p[i++] = last_cubic_y = last_y = y;
1136 s += n;
1137 } while (sscanf(s, "%f %f %n",
1138 &x, &y, &n) == 2);
1139
1140 /* elliptical arc (A, a) (7 arguments) */
1141 } else if (sscanf(s, " %1[Aa] %f %f %f %f %f %f %f %n", command,
1142 &rx, &ry, &rotation, &large_arc, &sweep,
1143 &x, &y, &n) == 8) {
1144 do {
1145 int bzsegments;
1146 double bzpoints[6*4]; /* allow for up to four bezier segments per arc */
1147
1148 if (*command == 'a') {
1149 x += last_x;
1150 y += last_y;
1151 }
1152
1153 bzsegments = svgarc_to_bezier(last_x, last_y,
1154 x, y,
1155 rx, ry,
1156 rotation,
1157 large_arc,
1158 sweep,
1159 bzpoints);
1160 if (bzsegments == -1) {
1161 /* draw a line */
1162 ALLOC_PATH_ELEMENTS(3);
1163 p[i++] = svgtiny_PATH_LINE;
1164 p[i++] = x;
1165 p[i++] = y;
1166 } else if (bzsegments > 0) {
1167 int bzpnt;
1168 ALLOC_PATH_ELEMENTS((unsigned int)bzsegments * 7);
1169 for (bzpnt = 0;bzpnt < (bzsegments * 6); bzpnt+=6) {
1170 p[i++] = svgtiny_PATH_BEZIER;
1171 p[i++] = bzpoints[bzpnt];
1172 p[i++] = bzpoints[bzpnt+1];
1173 p[i++] = bzpoints[bzpnt+2];
1174 p[i++] = bzpoints[bzpnt+3];
1175 p[i++] = bzpoints[bzpnt+4];
1176 p[i++] = bzpoints[bzpnt+5];
1177 }
1178 }
1179 if (bzsegments != 0) {
1180 last_cubic_x = last_quad_x = last_x = p[i-2];
1181 last_cubic_y = last_quad_y = last_y = p[i-1];
1182 }
1183
1184
1185 s += n;
1186 } while (sscanf(s, "%f %f %f %f %f %f %f %n",
1187 &rx, &ry, &rotation, &large_arc, &sweep,
1188 &x, &y, &n) == 7);
1189
1190 } else {
1191 /* fprintf(stderr, "parse failed at \"%s\"\n", s); */
1192 break;
1193 }
1194 }
1195
1196 free(path_d);
1197
1198 if (i <= 4) {
1199 /* no real segments in path */
1200 free(p);
1201 svgtiny_cleanup_state_local(&state);
1202 return svgtiny_OK;
1203 }
1204
1205 /* resize path element array to not be over allocated */
1206 if (palloc != i) {
1207 float *tp;
1208
1209 /* try the resize, if it fails just continue to use the old
1210 * allocation
1211 */
1212 tp = realloc(p, sizeof p[0] * i);
1213 if (tp != NULL) {
1214 p = tp;
1215 }
1216 }
1217
1218 err = svgtiny_add_path(p, i, &state);
1219
1220 svgtiny_cleanup_state_local(&state);
1221
1222 return err;
1223 }
1224
1225
1226 /**
1227 * Parse a <rect> element node.
1228 *
1229 * http://www.w3.org/TR/SVG11/shapes#RectElement
1230 */
1231
1232 svgtiny_code svgtiny_parse_rect(dom_element *rect,
1233 struct svgtiny_parse_state state)
1234 {
1235 svgtiny_code err;
1236 float x, y, width, height;
1237 float *p;
1238
1239 svgtiny_setup_state_local(&state);
1240
1241 svgtiny_parse_position_attributes(rect, state,
1242 &x, &y, &width, &height);
1243 svgtiny_parse_paint_attributes(rect, &state);
1244 svgtiny_parse_transform_attributes(rect, &state);
1245
1246 p = malloc(13 * sizeof p[0]);
1247 if (!p) {
1248 svgtiny_cleanup_state_local(&state);
1249 return svgtiny_OUT_OF_MEMORY;
1250 }
1251
1252 p[0] = svgtiny_PATH_MOVE;
1253 p[1] = x;
1254 p[2] = y;
1255 p[3] = svgtiny_PATH_LINE;
1256 p[4] = x + width;
1257 p[5] = y;
1258 p[6] = svgtiny_PATH_LINE;
1259 p[7] = x + width;
1260 p[8] = y + height;
1261 p[9] = svgtiny_PATH_LINE;
1262 p[10] = x;
1263 p[11] = y + height;
1264 p[12] = svgtiny_PATH_CLOSE;
1265
1266 err = svgtiny_add_path(p, 13, &state);
1267
1268 svgtiny_cleanup_state_local(&state);
1269
1270 return err;
1271 }
1272
1273
1274 /**
1275 * Parse a <circle> element node.
1276 */
1277
1278 svgtiny_code svgtiny_parse_circle(dom_element *circle,
1279 struct svgtiny_parse_state state)
1280 {
1281 svgtiny_code err;
1282 float x = 0, y = 0, r = -1;
1283 float *p;
1284 dom_string *attr;
1285 dom_exception exc;
1286
1287 svgtiny_setup_state_local(&state);
1288
1289 exc = dom_element_get_attribute(circle, state.interned_cx, &attr);
1290 if (exc != DOM_NO_ERR) {
1291 svgtiny_cleanup_state_local(&state);
1292 return svgtiny_LIBDOM_ERROR;
1293 }
1294 if (attr != NULL) {
1295 x = svgtiny_parse_length(attr, state.viewport_width, state);
1296 }
1297 dom_string_unref(attr);
1298
1299 exc = dom_element_get_attribute(circle, state.interned_cy, &attr);
1300 if (exc != DOM_NO_ERR) {
1301 svgtiny_cleanup_state_local(&state);
1302 return svgtiny_LIBDOM_ERROR;
1303 }
1304 if (attr != NULL) {
1305 y = svgtiny_parse_length(attr, state.viewport_height, state);
1306 }
1307 dom_string_unref(attr);
1308
1309 exc = dom_element_get_attribute(circle, state.interned_r, &attr);
1310 if (exc != DOM_NO_ERR) {
1311 svgtiny_cleanup_state_local(&state);
1312 return svgtiny_LIBDOM_ERROR;
1313 }
1314 if (attr != NULL) {
1315 r = svgtiny_parse_length(attr, state.viewport_width, state);
1316 }
1317 dom_string_unref(attr);
1318
1319 svgtiny_parse_paint_attributes(circle, &state);
1320 svgtiny_parse_transform_attributes(circle, &state);
1321
1322 if (r < 0) {
1323 state.diagram->error_line = -1; /* circle->line; */
1324 state.diagram->error_message = "circle: r missing or negative";
1325 svgtiny_cleanup_state_local(&state);
1326 return svgtiny_SVG_ERROR;
1327 }
1328 if (r == 0) {
1329 svgtiny_cleanup_state_local(&state);
1330 return svgtiny_OK;
1331 }
1332
1333 p = malloc(32 * sizeof p[0]);
1334 if (!p) {
1335 svgtiny_cleanup_state_local(&state);
1336 return svgtiny_OUT_OF_MEMORY;
1337 }
1338
1339 p[0] = svgtiny_PATH_MOVE;
1340 p[1] = x + r;
1341 p[2] = y;
1342 p[3] = svgtiny_PATH_BEZIER;
1343 p[4] = x + r;
1344 p[5] = y + r * KAPPA;
1345 p[6] = x + r * KAPPA;
1346 p[7] = y + r;
1347 p[8] = x;
1348 p[9] = y + r;
1349 p[10] = svgtiny_PATH_BEZIER;
1350 p[11] = x - r * KAPPA;
1351 p[12] = y + r;
1352 p[13] = x - r;
1353 p[14] = y + r * KAPPA;
1354 p[15] = x - r;
1355 p[16] = y;
1356 p[17] = svgtiny_PATH_BEZIER;
1357 p[18] = x - r;
1358 p[19] = y - r * KAPPA;
1359 p[20] = x - r * KAPPA;
1360 p[21] = y - r;
1361 p[22] = x;
1362 p[23] = y - r;
1363 p[24] = svgtiny_PATH_BEZIER;
1364 p[25] = x + r * KAPPA;
1365 p[26] = y - r;
1366 p[27] = x + r;
1367 p[28] = y - r * KAPPA;
1368 p[29] = x + r;
1369 p[30] = y;
1370 p[31] = svgtiny_PATH_CLOSE;
1371
1372 err = svgtiny_add_path(p, 32, &state);
1373
1374 svgtiny_cleanup_state_local(&state);
1375
1376 return err;
1377 }
1378
1379
1380 /**
1381 * Parse an <ellipse> element node.
1382 */
1383
1384 svgtiny_code svgtiny_parse_ellipse(dom_element *ellipse,
1385 struct svgtiny_parse_state state)
1386 {
1387 svgtiny_code err;
1388 float x = 0, y = 0, rx = -1, ry = -1;
1389 float *p;
1390 dom_string *attr;
1391 dom_exception exc;
1392
1393 svgtiny_setup_state_local(&state);
1394
1395 exc = dom_element_get_attribute(ellipse, state.interned_cx, &attr);
1396 if (exc != DOM_NO_ERR) {
1397 svgtiny_cleanup_state_local(&state);
1398 return svgtiny_LIBDOM_ERROR;
1399 }
1400 if (attr != NULL) {
1401 x = svgtiny_parse_length(attr, state.viewport_width, state);
1402 }
1403 dom_string_unref(attr);
1404
1405 exc = dom_element_get_attribute(ellipse, state.interned_cy, &attr);
1406 if (exc != DOM_NO_ERR) {
1407 svgtiny_cleanup_state_local(&state);
1408 return svgtiny_LIBDOM_ERROR;
1409 }
1410 if (attr != NULL) {
1411 y = svgtiny_parse_length(attr, state.viewport_height, state);
1412 }
1413 dom_string_unref(attr);
1414
1415 exc = dom_element_get_attribute(ellipse, state.interned_rx, &attr);
1416 if (exc != DOM_NO_ERR) {
1417 svgtiny_cleanup_state_local(&state);
1418 return svgtiny_LIBDOM_ERROR;
1419 }
1420 if (attr != NULL) {
1421 rx = svgtiny_parse_length(attr, state.viewport_width, state);
1422 }
1423 dom_string_unref(attr);
1424
1425 exc = dom_element_get_attribute(ellipse, state.interned_ry, &attr);
1426 if (exc != DOM_NO_ERR) {
1427 svgtiny_cleanup_state_local(&state);
1428 return svgtiny_LIBDOM_ERROR;
1429 }
1430 if (attr != NULL) {
1431 ry = svgtiny_parse_length(attr, state.viewport_width, state);
1432 }
1433 dom_string_unref(attr);
1434
1435 svgtiny_parse_paint_attributes(ellipse, &state);
1436 svgtiny_parse_transform_attributes(ellipse, &state);
1437
1438 if (rx < 0 || ry < 0) {
1439 state.diagram->error_line = -1; /* ellipse->line; */
1440 state.diagram->error_message = "ellipse: rx or ry missing "
1441 "or negative";
1442 svgtiny_cleanup_state_local(&state);
1443 return svgtiny_SVG_ERROR;
1444 }
1445 if (rx == 0 || ry == 0) {
1446 svgtiny_cleanup_state_local(&state);
1447 return svgtiny_OK;
1448 }
1449
1450 p = malloc(32 * sizeof p[0]);
1451 if (!p) {
1452 svgtiny_cleanup_state_local(&state);
1453 return svgtiny_OUT_OF_MEMORY;
1454 }
1455
1456 p[0] = svgtiny_PATH_MOVE;
1457 p[1] = x + rx;
1458 p[2] = y;
1459 p[3] = svgtiny_PATH_BEZIER;
1460 p[4] = x + rx;
1461 p[5] = y + ry * KAPPA;
1462 p[6] = x + rx * KAPPA;
1463 p[7] = y + ry;
1464 p[8] = x;
1465 p[9] = y + ry;
1466 p[10] = svgtiny_PATH_BEZIER;
1467 p[11] = x - rx * KAPPA;
1468 p[12] = y + ry;
1469 p[13] = x - rx;
1470 p[14] = y + ry * KAPPA;
1471 p[15] = x - rx;
1472 p[16] = y;
1473 p[17] = svgtiny_PATH_BEZIER;
1474 p[18] = x - rx;
1475 p[19] = y - ry * KAPPA;
1476 p[20] = x - rx * KAPPA;
1477 p[21] = y - ry;
1478 p[22] = x;
1479 p[23] = y - ry;
1480 p[24] = svgtiny_PATH_BEZIER;
1481 p[25] = x + rx * KAPPA;
1482 p[26] = y - ry;
1483 p[27] = x + rx;
1484 p[28] = y - ry * KAPPA;
1485 p[29] = x + rx;
1486 p[30] = y;
1487 p[31] = svgtiny_PATH_CLOSE;
1488
1489 err = svgtiny_add_path(p, 32, &state);
1490
1491 svgtiny_cleanup_state_local(&state);
1492
1493 return err;
1494 }
1495
1496
1497 /**
1498 * Parse a <line> element node.
1499 */
1500
1501 svgtiny_code svgtiny_parse_line(dom_element *line,
1502 struct svgtiny_parse_state state)
1503 {
1504 svgtiny_code err;
1505 float x1 = 0, y1 = 0, x2 = 0, y2 = 0;
1506 float *p;
1507 dom_string *attr;
1508 dom_exception exc;
1509
1510 svgtiny_setup_state_local(&state);
1511
1512 exc = dom_element_get_attribute(line, state.interned_x1, &attr);
1513 if (exc != DOM_NO_ERR) {
1514 svgtiny_cleanup_state_local(&state);
1515 return svgtiny_LIBDOM_ERROR;
1516 }
1517 if (attr != NULL) {
1518 x1 = svgtiny_parse_length(attr, state.viewport_width, state);
1519 }
1520 dom_string_unref(attr);
1521
1522 exc = dom_element_get_attribute(line, state.interned_y1, &attr);
1523 if (exc != DOM_NO_ERR) {
1524 svgtiny_cleanup_state_local(&state);
1525 return svgtiny_LIBDOM_ERROR;
1526 }
1527 if (attr != NULL) {
1528 y1 = svgtiny_parse_length(attr, state.viewport_height, state);
1529 }
1530 dom_string_unref(attr);
1531
1532 exc = dom_element_get_attribute(line, state.interned_x2, &attr);
1533 if (exc != DOM_NO_ERR) {
1534 svgtiny_cleanup_state_local(&state);
1535 return svgtiny_LIBDOM_ERROR;
1536 }
1537 if (attr != NULL) {
1538 x2 = svgtiny_parse_length(attr, state.viewport_width, state);
1539 }
1540 dom_string_unref(attr);
1541
1542 exc = dom_element_get_attribute(line, state.interned_y2, &attr);
1543 if (exc != DOM_NO_ERR) {
1544 svgtiny_cleanup_state_local(&state);
1545 return svgtiny_LIBDOM_ERROR;
1546 }
1547 if (attr != NULL) {
1548 y2 = svgtiny_parse_length(attr, state.viewport_height, state);
1549 }
1550 dom_string_unref(attr);
1551
1552 svgtiny_parse_paint_attributes(line, &state);
1553 svgtiny_parse_transform_attributes(line, &state);
1554
1555 p = malloc(7 * sizeof p[0]);
1556 if (!p) {
1557 svgtiny_cleanup_state_local(&state);
1558 return svgtiny_OUT_OF_MEMORY;
1559 }
1560
1561 p[0] = svgtiny_PATH_MOVE;
1562 p[1] = x1;
1563 p[2] = y1;
1564 p[3] = svgtiny_PATH_LINE;
1565 p[4] = x2;
1566 p[5] = y2;
1567 p[6] = svgtiny_PATH_CLOSE;
1568
1569 err = svgtiny_add_path(p, 7, &state);
1570
1571 svgtiny_cleanup_state_local(&state);
1572
1573 return err;
1574 }
1575
1576
1577 /**
1578 * Parse a <polyline> or <polygon> element node.
1579 *
1580 * http://www.w3.org/TR/SVG11/shapes#PolylineElement
1581 * http://www.w3.org/TR/SVG11/shapes#PolygonElement
1582 */
1583
1584 svgtiny_code svgtiny_parse_poly(dom_element *poly,
1585 struct svgtiny_parse_state state, bool polygon)
1586 {
1587 svgtiny_code err;
1588 dom_string *points_str;
1589 dom_exception exc;
1590 char *s, *points;
1591 float *p;
1592 unsigned int i;
1593
1594 svgtiny_setup_state_local(&state);
1595
1596 svgtiny_parse_paint_attributes(poly, &state);
1597 svgtiny_parse_transform_attributes(poly, &state);
1598
1599 exc = dom_element_get_attribute(poly, state.interned_points,
1600 &points_str);
1601 if (exc != DOM_NO_ERR) {
1602 svgtiny_cleanup_state_local(&state);
1603 return svgtiny_LIBDOM_ERROR;
1604 }
1605
1606 if (points_str == NULL) {
1607 state.diagram->error_line = -1; /* poly->line; */
1608 state.diagram->error_message =
1609 "polyline/polygon: missing points attribute";
1610 svgtiny_cleanup_state_local(&state);
1611 return svgtiny_SVG_ERROR;
1612 }
1613
1614 s = points = strndup(dom_string_data(points_str),
1615 dom_string_byte_length(points_str));
1616 dom_string_unref(points_str);
1617 /* read points attribute */
1618 if (s == NULL) {
1619 svgtiny_cleanup_state_local(&state);
1620 return svgtiny_OUT_OF_MEMORY;
1621 }
1622 /* allocate space for path: it will never have more elements than s */
1623 p = malloc(sizeof p[0] * strlen(s));
1624 if (!p) {
1625 free(points);
1626 svgtiny_cleanup_state_local(&state);
1627 return svgtiny_OUT_OF_MEMORY;
1628 }
1629
1630 /* parse s and build path */
1631 for (i = 0; s[i]; i++)
1632 if (s[i] == ',')
1633 s[i] = ' ';
1634 i = 0;
1635 while (*s) {
1636 float x, y;
1637 int n;
1638
1639 if (sscanf(s, "%f %f %n", &x, &y, &n) == 2) {
1640 if (i == 0)
1641 p[i++] = svgtiny_PATH_MOVE;
1642 else
1643 p[i++] = svgtiny_PATH_LINE;
1644 p[i++] = x;
1645 p[i++] = y;
1646 s += n;
1647 } else {
1648 break;
1649 }
1650 }
1651 if (polygon)
1652 p[i++] = svgtiny_PATH_CLOSE;
1653
1654 free(points);
1655
1656 err = svgtiny_add_path(p, i, &state);
1657
1658 svgtiny_cleanup_state_local(&state);
1659
1660 return err;
1661 }
1662
1663
1664 /**
1665 * Parse a <text> or <tspan> element node.
1666 */
1667
1668 svgtiny_code svgtiny_parse_text(dom_element *text,
1669 struct svgtiny_parse_state state)
1670 {
1671 float x, y, width, height;
1672 float px, py;
1673 dom_node *child;
1674 dom_exception exc;
1675
1676 svgtiny_setup_state_local(&state);
1677
1678 svgtiny_parse_position_attributes(text, state,
1679 &x, &y, &width, &height);
1680 svgtiny_parse_font_attributes(text, &state);
1681 svgtiny_parse_transform_attributes(text, &state);
1682
1683 px = state.ctm.a * x + state.ctm.c * y + state.ctm.e;
1684 py = state.ctm.b * x + state.ctm.d * y + state.ctm.f;
1685 /* state.ctm.e = px - state.origin_x; */
1686 /* state.ctm.f = py - state.origin_y; */
1687
1688 exc = dom_node_get_first_child(text, &child);
1689 if (exc != DOM_NO_ERR) {
1690 return svgtiny_LIBDOM_ERROR;
1691 svgtiny_cleanup_state_local(&state);
1692 }
1693 while (child != NULL) {
1694 dom_node *next;
1695 dom_node_type nodetype;
1696 svgtiny_code code = svgtiny_OK;
1697
1698 exc = dom_node_get_node_type(child, &nodetype);
1699 if (exc != DOM_NO_ERR) {
1700 dom_node_unref(child);
1701 svgtiny_cleanup_state_local(&state);
1702 return svgtiny_LIBDOM_ERROR;
1703 }
1704 if (nodetype == DOM_ELEMENT_NODE) {
1705 dom_string *nodename;
1706 exc = dom_node_get_node_name(child, &nodename);
1707 if (exc != DOM_NO_ERR) {
1708 dom_node_unref(child);
1709 svgtiny_cleanup_state_local(&state);
1710 return svgtiny_LIBDOM_ERROR;
1711 }
1712 if (dom_string_caseless_isequal(nodename,
1713 state.interned_tspan))
1714 code = svgtiny_parse_text((dom_element *)child,
1715 state);
1716 dom_string_unref(nodename);
1717 } else if (nodetype == DOM_TEXT_NODE) {
1718 struct svgtiny_shape *shape = svgtiny_add_shape(&state);
1719 dom_string *content;
1720 if (shape == NULL) {
1721 dom_node_unref(child);
1722 svgtiny_cleanup_state_local(&state);
1723 return svgtiny_OUT_OF_MEMORY;
1724 }
1725 exc = dom_text_get_whole_text(child, &content);
1726 if (exc != DOM_NO_ERR) {
1727 dom_node_unref(child);
1728 svgtiny_cleanup_state_local(&state);
1729 return svgtiny_LIBDOM_ERROR;
1730 }
1731 if (content != NULL) {
1732 shape->text = strndup(dom_string_data(content),
1733 dom_string_byte_length(content));
1734 dom_string_unref(content);
1735 } else {
1736 shape->text = strdup("");
1737 }
1738 shape->text_x = px;
1739 shape->text_y = py;
1740 state.diagram->shape_count++;
1741 }
1742
1743 if (code != svgtiny_OK) {
1744 dom_node_unref(child);
1745 svgtiny_cleanup_state_local(&state);
1746 return code;
1747 }
1748 exc = dom_node_get_next_sibling(child, &next);
1749 dom_node_unref(child);
1750 if (exc != DOM_NO_ERR) {
1751 svgtiny_cleanup_state_local(&state);
1752 return svgtiny_LIBDOM_ERROR;
1753 }
1754 child = next;
1755 }
1756
1757 svgtiny_cleanup_state_local(&state);
1758
1759 return svgtiny_OK;
1760 }
1761
1762
1763 /**
1764 * Parse x, y, width, and height attributes, if present.
1765 */
1766
1767 void svgtiny_parse_position_attributes(dom_element *node,
1768 const struct svgtiny_parse_state state,
1769 float *x, float *y, float *width, float *height)
1770 {
1771 dom_string *attr;
1772 dom_exception exc;
1773
1774 *x = 0;
1775 *y = 0;
1776 *width = state.viewport_width;
1777 *height = state.viewport_height;
1778
1779 exc = dom_element_get_attribute(node, state.interned_x, &attr);
1780 if (exc == DOM_NO_ERR && attr != NULL) {
1781 *x = svgtiny_parse_length(attr, state.viewport_width, state);
1782 dom_string_unref(attr);
1783 }
1784
1785 exc = dom_element_get_attribute(node, state.interned_y, &attr);
1786 if (exc == DOM_NO_ERR && attr != NULL) {
1787 *y = svgtiny_parse_length(attr, state.viewport_height, state);
1788 dom_string_unref(attr);
1789 }
1790
1791 exc = dom_element_get_attribute(node, state.interned_width, &attr);
1792 if (exc == DOM_NO_ERR && attr != NULL) {
1793 *width = svgtiny_parse_length(attr, state.viewport_width,
1794 state);
1795 dom_string_unref(attr);
1796 }
1797
1798 exc = dom_element_get_attribute(node, state.interned_height, &attr);
1799 if (exc == DOM_NO_ERR && attr != NULL) {
1800 *height = svgtiny_parse_length(attr, state.viewport_height,
1801 state);
1802 dom_string_unref(attr);
1803 }
1804 }
1805
1806
1807 /**
1808 * Parse a length as a number of pixels.
1809 */
1810
1811 static float _svgtiny_parse_length(const char *s, int viewport_size,
1812 const struct svgtiny_parse_state state)
1813 {
1814 int num_length = strspn(s, "0123456789+-.");
1815 const char *unit = s + num_length;
1816 float n = atof((const char *) s);
1817 float font_size = 20;
1818
1819 UNUSED(state);
1820
1821 if (unit[0] == 0) {
1822 return n;
1823 } else if (unit[0] == '%') {
1824 return n / 100.0 * viewport_size;
1825 } else if (unit[0] == 'e' && unit[1] == 'm') {
1826 return n * font_size;
1827 } else if (unit[0] == 'e' && unit[1] == 'x') {
1828 return n / 2.0 * font_size;
1829 } else if (unit[0] == 'p' && unit[1] == 'x') {
1830 return n;
1831 } else if (unit[0] == 'p' && unit[1] == 't') {
1832 return n * 1.25;
1833 } else if (unit[0] == 'p' && unit[1] == 'c') {
1834 return n * 15.0;
1835 } else if (unit[0] == 'm' && unit[1] == 'm') {
1836 return n * 3.543307;
1837 } else if (unit[0] == 'c' && unit[1] == 'm') {
1838 return n * 35.43307;
1839 } else if (unit[0] == 'i' && unit[1] == 'n') {
1840 return n * 90;
1841 }
1842
1843 return 0;
1844 }
1845
1846 float svgtiny_parse_length(dom_string *s, int viewport_size,
1847 const struct svgtiny_parse_state state)
1848 {
1849 char *ss = strndup(dom_string_data(s), dom_string_byte_length(s));
1850 float ret = _svgtiny_parse_length(ss, viewport_size, state);
1851 free(ss);
1852 return ret;
1853 }
1854
1855 /**
1856 * Parse paint attributes, if present.
1857 */
1858
1859 void svgtiny_parse_paint_attributes(dom_element *node,
1860 struct svgtiny_parse_state *state)
1861 {
1862 dom_string *attr;
1863 dom_exception exc;
1864
1865 exc = dom_element_get_attribute(node, state->interned_fill, &attr);
1866 if (exc == DOM_NO_ERR && attr != NULL) {
1867 svgtiny_parse_color(attr, &state->fill, &state->fill_grad, state);
1868 dom_string_unref(attr);
1869 }
1870
1871 exc = dom_element_get_attribute(node, state->interned_stroke, &attr);
1872 if (exc == DOM_NO_ERR && attr != NULL) {
1873 svgtiny_parse_color(attr, &state->stroke, &state->stroke_grad, state);
1874 dom_string_unref(attr);
1875 }
1876
1877 exc = dom_element_get_attribute(node, state->interned_stroke_width, &attr);
1878 if (exc == DOM_NO_ERR && attr != NULL) {
1879 state->stroke_width = svgtiny_parse_length(attr,
1880 state->viewport_width, *state);
1881 dom_string_unref(attr);
1882 }
1883
1884 exc = dom_element_get_attribute(node, state->interned_style, &attr);
1885 if (exc == DOM_NO_ERR && attr != NULL) {
1886 char *style = strndup(dom_string_data(attr),
1887 dom_string_byte_length(attr));
1888 const char *s;
1889 char *value;
1890 if ((s = strstr(style, "fill:"))) {
1891 s += 5;
1892 while (*s == ' ')
1893 s++;
1894 value = strndup(s, strcspn(s, "; "));
1895 _svgtiny_parse_color(value, &state->fill, &state->fill_grad, state);
1896 free(value);
1897 }
1898 if ((s = strstr(style, "stroke:"))) {
1899 s += 7;
1900 while (*s == ' ')
1901 s++;
1902 value = strndup(s, strcspn(s, "; "));
1903 _svgtiny_parse_color(value, &state->stroke, &state->stroke_grad, state);
1904 free(value);
1905 }
1906 if ((s = strstr(style, "stroke-width:"))) {
1907 s += 13;
1908 while (*s == ' ')
1909 s++;
1910 value = strndup(s, strcspn(s, "; "));
1911 state->stroke_width = _svgtiny_parse_length(value,
1912 state->viewport_width, *state);
1913 free(value);
1914 }
1915 free(style);
1916 dom_string_unref(attr);
1917 }
1918 }
1919
1920
1921 /**
1922 * Parse a colour.
1923 */
1924
1925 static void _svgtiny_parse_color(const char *s, svgtiny_colour *c,
1926 struct svgtiny_parse_state_gradient *grad,
1927 struct svgtiny_parse_state *state)
1928 {
1929 unsigned int r, g, b;
1930 float rf, gf, bf;
1931 size_t len = strlen(s);
1932 char *id = 0, *rparen;
1933
1934 if (len == 4 && s[0] == '#') {
1935 if (sscanf(s + 1, "%1x%1x%1x", &r, &g, &b) == 3)
1936 *c = svgtiny_RGB(r | r << 4, g | g << 4, b | b << 4);
1937
1938 } else if (len == 7 && s[0] == '#') {
1939 if (sscanf(s + 1, "%2x%2x%2x", &r, &g, &b) == 3)
1940 *c = svgtiny_RGB(r, g, b);
1941
1942 } else if (10 <= len && s[0] == 'r' && s[1] == 'g' && s[2] == 'b' &&
1943 s[3] == '(' && s[len - 1] == ')') {
1944 if (sscanf(s + 4, "%u,%u,%u", &r, &g, &b) == 3)
1945 *c = svgtiny_RGB(r, g, b);
1946 else if (sscanf(s + 4, "%f%%,%f%%,%f%%", &rf, &gf, &bf) == 3) {
1947 b = bf * 255 / 100;
1948 g = gf * 255 / 100;
1949 r = rf * 255 / 100;
1950 *c = svgtiny_RGB(r, g, b);
1951 }
1952
1953 } else if (len == 4 && strcmp(s, "none") == 0) {
1954 *c = svgtiny_TRANSPARENT;
1955
1956 } else if (5 < len && s[0] == 'u' && s[1] == 'r' && s[2] == 'l' &&
1957 s[3] == '(') {
1958 if (grad == NULL) {
1959 *c = svgtiny_RGB(0, 0, 0);
1960 } else if (s[4] == '#') {
1961 id = strdup(s + 5);
1962 if (!id)
1963 return;
1964 rparen = strchr(id, ')');
1965 if (rparen)
1966 *rparen = 0;
1967 svgtiny_find_gradient(id, grad, state);
1968 free(id);
1969 if (grad->linear_gradient_stop_count == 0)
1970 *c = svgtiny_TRANSPARENT;
1971 else if (grad->linear_gradient_stop_count == 1)
1972 *c = grad->gradient_stop[0].color;
1973 else
1974 *c = svgtiny_LINEAR_GRADIENT;
1975 }
1976
1977 } else {
1978 const struct svgtiny_named_color *named_color;
1979 named_color = svgtiny_color_lookup(s, strlen(s));
1980 if (named_color)
1981 *c = named_color->color;
1982 }
1983 }
1984
1985 void svgtiny_parse_color(dom_string *s, svgtiny_colour *c,
1986 struct svgtiny_parse_state_gradient *grad,
1987 struct svgtiny_parse_state *state)
1988 {
1989 dom_string_ref(s);
1990 _svgtiny_parse_color(dom_string_data(s), c, grad, state);
1991 dom_string_unref(s);
1992 }
1993
1994 /**
1995 * Parse font attributes, if present.
1996 */
1997
1998 void svgtiny_parse_font_attributes(dom_element *node,
1999 struct svgtiny_parse_state *state)
2000 {
2001 /* TODO: Implement this, it never used to be */
2002 UNUSED(node);
2003 UNUSED(state);
2004 #ifdef WRITTEN_THIS_PROPERLY
2005 const xmlAttr *attr;
2006
2007 UNUSED(state);
2008
2009 for (attr = node->properties; attr; attr = attr->next) {
2010 if (strcmp((const char *) attr->name, "font-size") == 0) {
2011 /* TODO */
2012 }
2013 }
2014 #endif
2015 }
2016
2017
2018 /**
2019 * Parse transform attributes, if present.
2020 *
2021 * http://www.w3.org/TR/SVG11/coords#TransformAttribute
2022 */
2023
2024 void svgtiny_parse_transform_attributes(dom_element *node,
2025 struct svgtiny_parse_state *state)
2026 {
2027 char *transform;
2028 dom_string *attr;
2029 dom_exception exc;
2030
2031 exc = dom_element_get_attribute(node, state->interned_transform,
2032 &attr);
2033 if (exc == DOM_NO_ERR && attr != NULL) {
2034 transform = strndup(dom_string_data(attr),
2035 dom_string_byte_length(attr));
2036 svgtiny_parse_transform(transform, &state->ctm.a, &state->ctm.b,
2037 &state->ctm.c, &state->ctm.d,
2038 &state->ctm.e, &state->ctm.f);
2039 free(transform);
2040 dom_string_unref(attr);
2041 }
2042 }
2043
2044
2045 /**
2046 * Parse a transform string.
2047 */
2048
2049 void svgtiny_parse_transform(char *s, float *ma, float *mb,
2050 float *mc, float *md, float *me, float *mf)
2051 {
2052 float a, b, c, d, e, f;
2053 float za, zb, zc, zd, ze, zf;
2054 float angle, x, y;
2055 int n;
2056 unsigned int i;
2057
2058 for (i = 0; s[i]; i++)
2059 if (s[i] == ',')
2060 s[i] = ' ';
2061
2062 while (*s) {
2063 a = d = 1;
2064 b = c = 0;
2065 e = f = 0;
2066 n = 0;
2067 if ((sscanf(s, " matrix (%f %f %f %f %f %f ) %n",
2068 &a, &b, &c, &d, &e, &f, &n) == 6) && (n > 0))
2069 ;
2070 else if ((sscanf(s, " translate (%f %f ) %n",
2071 &e, &f, &n) == 2) && (n > 0))
2072 ;
2073 else if ((sscanf(s, " translate (%f ) %n",
2074 &e, &n) == 1) && (n > 0))
2075 ;
2076 else if ((sscanf(s, " scale (%f %f ) %n",
2077 &a, &d, &n) == 2) && (n > 0))
2078 ;
2079 else if ((sscanf(s, " scale (%f ) %n",
2080 &a, &n) == 1) && (n > 0))
2081 d = a;
2082 else if ((sscanf(s, " rotate (%f %f %f ) %n",
2083 &angle, &x, &y, &n) == 3) && (n > 0)) {
2084 angle = angle / 180 * M_PI;
2085 a = cos(angle);
2086 b = sin(angle);
2087 c = -sin(angle);
2088 d = cos(angle);
2089 e = -x * cos(angle) + y * sin(angle) + x;
2090 f = -x * sin(angle) - y * cos(angle) + y;
2091 } else if ((sscanf(s, " rotate (%f ) %n",
2092 &angle, &n) == 1) && (n > 0)) {
2093 angle = angle / 180 * M_PI;
2094 a = cos(angle);
2095 b = sin(angle);
2096 c = -sin(angle);
2097 d = cos(angle);
2098 } else if ((sscanf(s, " skewX (%f ) %n",
2099 &angle, &n) == 1) && (n > 0)) {
2100 angle = angle / 180 * M_PI;
2101 c = tan(angle);
2102 } else if ((sscanf(s, " skewY (%f ) %n",
2103 &angle, &n) == 1) && (n > 0)) {
2104 angle = angle / 180 * M_PI;
2105 b = tan(angle);
2106 } else
2107 break;
2108 za = *ma * a + *mc * b;
2109 zb = *mb * a + *md * b;
2110 zc = *ma * c + *mc * d;
2111 zd = *mb * c + *md * d;
2112 ze = *ma * e + *mc * f + *me;
2113 zf = *mb * e + *md * f + *mf;
2114 *ma = za;
2115 *mb = zb;
2116 *mc = zc;
2117 *md = zd;
2118 *me = ze;
2119 *mf = zf;
2120 s += n;
2121 }
2122 }
2123
2124
2125 /**
2126 * Add a path to the svgtiny_diagram.
2127 */
2128
2129 svgtiny_code svgtiny_add_path(float *p, unsigned int n,
2130 struct svgtiny_parse_state *state)
2131 {
2132 struct svgtiny_shape *shape;
2133
2134 if (state->fill == svgtiny_LINEAR_GRADIENT)
2135 return svgtiny_add_path_linear_gradient(p, n, state);
2136
2137 svgtiny_transform_path(p, n, state);
2138
2139 shape = svgtiny_add_shape(state);
2140 if (!shape) {
2141 free(p);
2142 return svgtiny_OUT_OF_MEMORY;
2143 }
2144 shape->path = p;
2145 shape->path_length = n;
2146 state->diagram->shape_count++;
2147
2148 return svgtiny_OK;
2149 }
2150
2151
2152 /**
2153 * Add a svgtiny_shape to the svgtiny_diagram.
2154 */
2155
2156 struct svgtiny_shape *svgtiny_add_shape(struct svgtiny_parse_state *state)
2157 {
2158 struct svgtiny_shape *shape = realloc(state->diagram->shape,
2159 (state->diagram->shape_count + 1) *
2160 sizeof (state->diagram->shape[0]));
2161 if (!shape)
2162 return 0;
2163 state->diagram->shape = shape;
2164
2165 shape += state->diagram->shape_count;
2166 shape->path = 0;
2167 shape->path_length = 0;
2168 shape->text = 0;
2169 shape->fill = state->fill;
2170 shape->stroke = state->stroke;
2171 shape->stroke_width = lroundf((float) state->stroke_width *
2172 (state->ctm.a + state->ctm.d) / 2.0);
2173 if (0 < state->stroke_width && shape->stroke_width == 0)
2174 shape->stroke_width = 1;
2175
2176 return shape;
2177 }
2178
2179
2180 /**
2181 * Apply the current transformation matrix to a path.
2182 */
2183
2184 void svgtiny_transform_path(float *p, unsigned int n,
2185 struct svgtiny_parse_state *state)
2186 {
2187 unsigned int j;
2188
2189 for (j = 0; j != n; ) {
2190 unsigned int points = 0;
2191 unsigned int k;
2192 switch ((int) p[j]) {
2193 case svgtiny_PATH_MOVE:
2194 case svgtiny_PATH_LINE:
2195 points = 1;
2196 break;
2197 case svgtiny_PATH_CLOSE:
2198 points = 0;
2199 break;
2200 case svgtiny_PATH_BEZIER:
2201 points = 3;
2202 break;
2203 default:
2204 assert(0);
2205 }
2206 j++;
2207 for (k = 0; k != points; k++) {
2208 float x0 = p[j], y0 = p[j + 1];
2209 float x = state->ctm.a * x0 + state->ctm.c * y0 +
2210 state->ctm.e;
2211 float y = state->ctm.b * x0 + state->ctm.d * y0 +
2212 state->ctm.f;
2213 p[j] = x;
2214 p[j + 1] = y;
2215 j += 2;
2216 }
2217 }
2218 }
2219
2220
2221 /**
2222 * Free all memory used by a diagram.
2223 */
2224
2225 void svgtiny_free(struct svgtiny_diagram *svg)
2226 {
2227 unsigned int i;
2228 assert(svg);
2229
2230 for (i = 0; i != svg->shape_count; i++) {
2231 free(svg->shape[i].path);
2232 free(svg->shape[i].text);
2233 }
2234
2235 free(svg->shape);
2236
2237 free(svg);
2238 }
2239
2240 #ifndef HAVE_STRNDUP
2241 char *svgtiny_strndup(const char *s, size_t n)
2242 {
2243 size_t len;
2244 char *s2;
2245
2246 for (len = 0; len != n && s[len]; len++)
2247 continue;
2248
2249 s2 = malloc(len + 1);
2250 if (s2 == NULL)
2251 return NULL;
2252
2253 memcpy(s2, s, len);
2254 s2[len] = '\0';
2255
2256 return s2;
2257 }
2258 #endif