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