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