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