]> gitweb.michael.orlitzky.com - libsvgtiny.git/blob - src/svgtiny.c
165fc215758f330d32beb0dc8dda704fd9e234c0
[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 css_select_results *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 /* Initialize the unit context here because it has a
633 * const member and doing it any other way subverts
634 * the type system. The magic numbers below were taken
635 * from the libcss example program without much
636 * thought, because at the moment we don't support any
637 * properties with units. */
638 .unit_ctx = {
639 .font_size_default = FLTTOFIX(16.0),
640 .font_size_minimum = FLTTOFIX(6.0),
641 .device_dpi = FLTTOFIX(96.0),
642 .root_style = NULL,
643 .pw = NULL,
644 .measure = NULL,
645 }
646 };
647 float x, y, width, height;
648 svgtiny_code code;
649
650 assert(diagram);
651 assert(buffer);
652 assert(url);
653
654 UNUSED(url);
655
656 parser = dom_xml_parser_create(NULL, NULL,
657 ignore_msg, NULL, &document);
658
659 if (parser == NULL)
660 return svgtiny_LIBDOM_ERROR;
661
662 err = dom_xml_parser_parse_chunk(parser, (uint8_t *)buffer, size);
663 if (err != DOM_XML_OK) {
664 dom_node_unref(document);
665 dom_xml_parser_destroy(parser);
666 return svgtiny_LIBDOM_ERROR;
667 }
668
669 err = dom_xml_parser_completed(parser);
670 if (err != DOM_XML_OK) {
671 dom_node_unref(document);
672 dom_xml_parser_destroy(parser);
673 return svgtiny_LIBDOM_ERROR;
674 }
675
676 /* We're done parsing, drop the parser.
677 * We now own the document entirely.
678 */
679 dom_xml_parser_destroy(parser);
680
681 /* find root <svg> element */
682 exc = dom_document_get_document_element(document, &svg);
683 if (exc != DOM_NO_ERR) {
684 dom_node_unref(document);
685 return svgtiny_LIBDOM_ERROR;
686 }
687 if (svg == NULL) {
688 /* no root svg element */
689 dom_node_unref(document);
690 return svgtiny_SVG_ERROR;
691 }
692
693 exc = dom_node_get_node_name(svg, &svg_name);
694 if (exc != DOM_NO_ERR) {
695 dom_node_unref(svg);
696 dom_node_unref(document);
697 return svgtiny_LIBDOM_ERROR;
698 }
699 if (lwc_intern_string("svg", 3 /* SLEN("svg") */,
700 &svg_name_lwc) != lwc_error_ok) {
701 dom_string_unref(svg_name);
702 dom_node_unref(svg);
703 dom_node_unref(document);
704 return svgtiny_LIBDOM_ERROR;
705 }
706 if (!dom_string_caseless_lwc_isequal(svg_name, svg_name_lwc)) {
707 lwc_string_unref(svg_name_lwc);
708 dom_string_unref(svg_name);
709 dom_node_unref(svg);
710 dom_node_unref(document);
711 return svgtiny_NOT_SVG;
712 }
713
714 lwc_string_unref(svg_name_lwc);
715 dom_string_unref(svg_name);
716
717 /* initialize the state struct with zeros */
718 memset(&state, 0, sizeof(state));
719
720 /* get graphic dimensions */
721 state.diagram = diagram;
722 state.document = document;
723 state.viewport_width = viewport_width;
724 state.viewport_height = viewport_height;
725
726 /* Initialize CSS context */
727 css_code = css_select_ctx_create(&state.select_ctx);
728 if (css_code != CSS_OK) {
729 dom_node_unref(svg);
730 dom_node_unref(document);
731 return svgtiny_LIBCSS_ERROR;
732 }
733
734 /* ...and the unit context, whose other fields were
735 * initialized along with the parser state itself */
736 state.unit_ctx.viewport_width = FLTTOFIX(viewport_width);
737 state.unit_ctx.viewport_height = FLTTOFIX(viewport_height);
738
739 #define SVGTINY_STRING_ACTION2(s,n) \
740 if (dom_string_create_interned((const uint8_t *) #n, \
741 strlen(#n), &state.interned_##s) \
742 != DOM_NO_ERR) { \
743 code = svgtiny_LIBDOM_ERROR; \
744 goto cleanup; \
745 }
746 #include "svgtiny_strings.h"
747 #undef SVGTINY_STRING_ACTION2
748
749 /* Intern SVG's xmlns separately because it's an lwc_string
750 * and not a dom_string. We initialize its pointer to NULL
751 * because the "cleanup:" test to see if it needs to be free'd
752 * looks for NULL. Returning a LIBDOM_ERROR on failure is not
753 * perfect but it's the closest of the available options. */
754 state.interned_svg_xmlns = NULL;
755 if (lwc_intern_string("http://www.w3.org/2000/svg",
756 26,
757 &state.interned_svg_xmlns) != lwc_error_ok) {
758 code = svgtiny_LIBDOM_ERROR;
759 goto cleanup;
760 }
761
762 svgtiny_parse_position_attributes(svg, state, &x, &y, &width, &height);
763 diagram->width = width;
764 diagram->height = height;
765
766 /* set up parsing state */
767 state.viewport_width = width;
768 state.viewport_height = height;
769 state.ctm.a = 1; /*(float) viewport_width / (float) width;*/
770 state.ctm.b = 0;
771 state.ctm.c = 0;
772 state.ctm.d = 1; /*(float) viewport_height / (float) height;*/
773 state.ctm.e = 0; /*x;*/
774 state.ctm.f = 0; /*y;*/
775 state.fill = 0x000000;
776 state.stroke = svgtiny_TRANSPARENT;
777 state.stroke_width = 1;
778
779 /* parse tree */
780 code = svgtiny_preparse_styles(svg, state);
781 if (code == svgtiny_OK) {
782 code = svgtiny_parse_svg(svg, state);
783 }
784
785 dom_node_unref(svg);
786 dom_node_unref(document);
787 css_code = css_select_ctx_destroy(state.select_ctx);
788 if (css_code != CSS_OK) {
789 code = svgtiny_LIBCSS_ERROR;
790 }
791
792 cleanup:
793 svgtiny_cleanup_state_local(&state);
794 #define SVGTINY_STRING_ACTION2(s,n) \
795 if (state.interned_##s != NULL) \
796 dom_string_unref(state.interned_##s);
797 #include "svgtiny_strings.h"
798 #undef SVGTINY_STRING_ACTION2
799
800 if (state.interned_svg_xmlns != NULL) {
801 lwc_string_unref(state.interned_svg_xmlns);
802 }
803
804 return code;
805 }
806
807
808 /**
809 * Parse a single <style> element, appending the result to the CSS
810 * select context within the given parser state.
811 */
812 svgtiny_code svgtiny_parse_style_element(dom_element *style,
813 struct svgtiny_parse_state state)
814 {
815 css_stylesheet *sheet;
816 css_error code;
817 dom_exception exc;
818
819 code = svgtiny_create_stylesheet(&sheet, false);
820 if (code != CSS_OK) {
821 return svgtiny_LIBCSS_ERROR;
822 }
823
824 /* Parse the style element's "media" attribute if it has
825 one. We don't do anything with it right now. */
826 dom_string *media_attr;
827 exc = dom_element_get_attribute(style, state.interned_media,
828 &media_attr);
829 if (exc != DOM_NO_ERR) {
830 css_stylesheet_destroy(sheet);
831 return svgtiny_LIBDOM_ERROR;
832 }
833
834 if (media_attr) {
835 /* Here's where we'd actually change the media type if
836 we were going to use it */
837 dom_string_unref(media_attr);
838 }
839
840 dom_string *data;
841 dom_node_get_text_content(style, &data);
842 if (data == NULL) {
843 /* Empty stylesheet? That's fine. */
844 css_stylesheet_destroy(sheet);
845 return svgtiny_OK;
846 }
847
848 code = css_stylesheet_append_data(sheet,
849 (uint8_t *)dom_string_data(data),
850 dom_string_byte_length(data));
851 if (code != CSS_OK && code != CSS_NEEDDATA) {
852 dom_string_unref(data);
853 css_stylesheet_destroy(sheet);
854 return svgtiny_LIBCSS_ERROR;
855 }
856
857 code = css_stylesheet_data_done(sheet);
858 if (code != CSS_OK) {
859 dom_string_unref(data);
860 css_stylesheet_destroy(sheet);
861 return svgtiny_LIBCSS_ERROR;
862 }
863
864 code = css_select_ctx_append_sheet(state.select_ctx,
865 sheet,
866 CSS_ORIGIN_AUTHOR,
867 NULL);
868 if (code != CSS_OK) {
869 dom_string_unref(data);
870 return svgtiny_LIBCSS_ERROR;
871 }
872
873 dom_string_unref(data);
874 return svgtiny_OK;
875 }
876
877
878 /**
879 * Parse the contents of an inline style and return (a pointer to) the
880 * corresponding stylesheet for use with css_select_style(). Returns
881 * NULL if anything goes wrong.
882 */
883 css_stylesheet *svgtiny_parse_style_inline(const uint8_t *data,
884 size_t len)
885 {
886 css_stylesheet *sheet;
887 css_error code;
888
889 code = svgtiny_create_stylesheet(&sheet, true);
890 if (code != CSS_OK) {
891 return NULL;
892 }
893
894 code = css_stylesheet_append_data(sheet, data, len);
895 if (code != CSS_OK && code != CSS_NEEDDATA) {
896 css_stylesheet_destroy(sheet);
897 return NULL;
898 }
899
900 code = css_stylesheet_data_done(sheet);
901 if (code != CSS_OK) {
902 css_stylesheet_destroy(sheet);
903 return NULL;
904 }
905
906 return sheet;
907 }
908
909 /**
910 * Parse all <style> elements within a root <svg> element. This
911 * should be called before svgtiny_parse_svg() because that function
912 * makes a single pass through the document and we'd like all style
913 * information to be available during that pass. Specifically, we'd
914 * like a <style> sheet at the end of the document to affect the
915 * rendering of elements at its beginning.
916 *
917 * The element-parsing inner loop here is essentially the same as
918 * that within svgtiny_parse_svg().
919 */
920 svgtiny_code svgtiny_preparse_styles(dom_element *svg,
921 struct svgtiny_parse_state state)
922 {
923 dom_element *child;
924 dom_exception exc;
925
926 exc = dom_node_get_first_child(svg, (dom_node **) (void *) &child);
927 if (exc != DOM_NO_ERR) {
928 return svgtiny_LIBDOM_ERROR;
929 }
930 while (child != NULL) {
931 dom_element *next;
932 dom_node_type nodetype;
933 svgtiny_code code = svgtiny_OK;
934
935 exc = dom_node_get_node_type(child, &nodetype);
936 if (exc != DOM_NO_ERR) {
937 dom_node_unref(child);
938 return svgtiny_LIBDOM_ERROR;
939 }
940 if (nodetype == DOM_ELEMENT_NODE) {
941 dom_string *nodename;
942 exc = dom_node_get_node_name(child, &nodename);
943 if (exc != DOM_NO_ERR) {
944 dom_node_unref(child);
945 return svgtiny_LIBDOM_ERROR;
946 }
947
948 if (dom_string_caseless_isequal(state.interned_style,
949 nodename)) {
950 /* We have a <style> element, parse it */
951 code = svgtiny_parse_style_element(child,
952 state);
953 }
954
955
956 dom_string_unref(nodename);
957 }
958 if (code != svgtiny_OK) {
959 dom_node_unref(child);
960 return code;
961 }
962 exc = dom_node_get_next_sibling(child,
963 (dom_node **) (void *) &next);
964 dom_node_unref(child);
965 if (exc != DOM_NO_ERR) {
966 return svgtiny_LIBDOM_ERROR;
967 }
968 child = next;
969 }
970
971 return svgtiny_OK;
972 }
973
974 /**
975 * Parse <svg>, <g>, and <a> element nodes.
976 */
977
978 svgtiny_code svgtiny_parse_svg(dom_element *svg,
979 struct svgtiny_parse_state state)
980 {
981 float x, y, width, height;
982 dom_string *view_box;
983 dom_element *child;
984 dom_exception exc;
985 css_select_results *styles;
986
987 svgtiny_setup_state_local(&state);
988
989 svgtiny_parse_position_attributes(svg, state, &x, &y, &width, &height);
990 svgtiny_parse_paint_attributes(svg, &state);
991 svgtiny_parse_font_attributes(svg, &state);
992 styles = svgtiny_parse_styles(svg, &state);
993
994 exc = dom_element_get_attribute(svg, state.interned_viewBox,
995 &view_box);
996 if (exc != DOM_NO_ERR) {
997 svgtiny_cleanup_state_local(&state);
998 return svgtiny_LIBDOM_ERROR;
999 }
1000
1001 if (view_box) {
1002 char *s = strndup(dom_string_data(view_box),
1003 dom_string_byte_length(view_box));
1004 float min_x, min_y, vwidth, vheight;
1005 if (sscanf(s, "%f,%f,%f,%f",
1006 &min_x, &min_y, &vwidth, &vheight) == 4 ||
1007 sscanf(s, "%f %f %f %f",
1008 &min_x, &min_y, &vwidth, &vheight) == 4) {
1009 state.ctm.a = (float) state.viewport_width / vwidth;
1010 state.ctm.d = (float) state.viewport_height / vheight;
1011 state.ctm.e += -min_x * state.ctm.a;
1012 state.ctm.f += -min_y * state.ctm.d;
1013 }
1014 free(s);
1015 dom_string_unref(view_box);
1016 }
1017
1018 svgtiny_parse_transform_attributes(svg, &state);
1019
1020 exc = dom_node_get_first_child(svg, (dom_node **) (void *) &child);
1021 if (exc != DOM_NO_ERR) {
1022 svgtiny_cleanup_state_local(&state);
1023 return svgtiny_LIBDOM_ERROR;
1024 }
1025 while (child != NULL) {
1026 dom_element *next;
1027 dom_node_type nodetype;
1028 svgtiny_code code = svgtiny_OK;
1029
1030 /* Before we descend to one of my child elements, set
1031 * the "parent style" to my style. */
1032 if (styles) {
1033 /* For now at least, the root element won't
1034 * have any styles; hence the null check. */
1035 state.parent_style = styles->styles[CSS_PSEUDO_ELEMENT_NONE];
1036 }
1037
1038 exc = dom_node_get_node_type(child, &nodetype);
1039 if (exc != DOM_NO_ERR) {
1040 dom_node_unref(child);
1041 return svgtiny_LIBDOM_ERROR;
1042 }
1043 if (nodetype == DOM_ELEMENT_NODE) {
1044 dom_string *nodename;
1045 exc = dom_node_get_node_name(child, &nodename);
1046 if (exc != DOM_NO_ERR) {
1047 dom_node_unref(child);
1048 svgtiny_cleanup_state_local(&state);
1049 return svgtiny_LIBDOM_ERROR;
1050 }
1051 if (dom_string_caseless_isequal(state.interned_svg,
1052 nodename))
1053 code = svgtiny_parse_svg(child, state);
1054 else if (dom_string_caseless_isequal(state.interned_g,
1055 nodename))
1056 code = svgtiny_parse_svg(child, state);
1057 else if (dom_string_caseless_isequal(state.interned_a,
1058 nodename))
1059 code = svgtiny_parse_svg(child, state);
1060 else if (dom_string_caseless_isequal(state.interned_path,
1061 nodename))
1062 code = svgtiny_parse_path(child, state);
1063 else if (dom_string_caseless_isequal(state.interned_rect,
1064 nodename))
1065 code = svgtiny_parse_rect(child, state);
1066 else if (dom_string_caseless_isequal(state.interned_circle,
1067 nodename))
1068 code = svgtiny_parse_circle(child, state);
1069 else if (dom_string_caseless_isequal(state.interned_ellipse,
1070 nodename))
1071 code = svgtiny_parse_ellipse(child, state);
1072 else if (dom_string_caseless_isequal(state.interned_line,
1073 nodename))
1074 code = svgtiny_parse_line(child, state);
1075 else if (dom_string_caseless_isequal(state.interned_polyline,
1076 nodename))
1077 code = svgtiny_parse_poly(child, state, false);
1078 else if (dom_string_caseless_isequal(state.interned_polygon,
1079 nodename))
1080 code = svgtiny_parse_poly(child, state, true);
1081 else if (dom_string_caseless_isequal(state.interned_text,
1082 nodename))
1083 code = svgtiny_parse_text(child, state);
1084 dom_string_unref(nodename);
1085 }
1086 if (code != svgtiny_OK) {
1087 dom_node_unref(child);
1088 svgtiny_cleanup_state_local(&state);
1089 return code;
1090 }
1091 exc = dom_node_get_next_sibling(child,
1092 (dom_node **) (void *) &next);
1093 dom_node_unref(child);
1094 if (exc != DOM_NO_ERR) {
1095 svgtiny_cleanup_state_local(&state);
1096 return svgtiny_LIBDOM_ERROR;
1097 }
1098 child = next;
1099 }
1100
1101 /* Hoping that destroying "styles" destroys state.parent_style
1102 * as well. */
1103 css_select_results_destroy(styles);
1104 svgtiny_cleanup_state_local(&state);
1105 return svgtiny_OK;
1106 }
1107
1108
1109
1110 /**
1111 * Parse a <path> element node.
1112 *
1113 * http://www.w3.org/TR/SVG11/paths#PathElement
1114 */
1115
1116 svgtiny_code svgtiny_parse_path(dom_element *path,
1117 struct svgtiny_parse_state state)
1118 {
1119 svgtiny_code err;
1120 dom_string *path_d_str;
1121 dom_exception exc;
1122 char *s, *path_d;
1123 float *p; /* path elemets */
1124 unsigned int palloc; /* number of path elements allocated */
1125 unsigned int i;
1126 float last_x = 0, last_y = 0;
1127 float last_cubic_x = 0, last_cubic_y = 0;
1128 float last_quad_x = 0, last_quad_y = 0;
1129 float subpath_first_x = 0, subpath_first_y = 0;
1130
1131 svgtiny_setup_state_local(&state);
1132
1133 svgtiny_parse_paint_attributes(path, &state);
1134 svgtiny_parse_transform_attributes(path, &state);
1135 css_select_results_destroy(svgtiny_parse_styles(path, &state));
1136
1137 /* read d attribute */
1138 exc = dom_element_get_attribute(path, state.interned_d, &path_d_str);
1139 if (exc != DOM_NO_ERR) {
1140 state.diagram->error_line = -1; /* path->line; */
1141 state.diagram->error_message = "path: error retrieving d attribute";
1142 svgtiny_cleanup_state_local(&state);
1143 return svgtiny_SVG_ERROR;
1144 }
1145
1146 if (path_d_str == NULL) {
1147 state.diagram->error_line = -1; /* path->line; */
1148 state.diagram->error_message = "path: missing d attribute";
1149 svgtiny_cleanup_state_local(&state);
1150 return svgtiny_SVG_ERROR;
1151 }
1152
1153 /* empty path is permitted it just disables the path */
1154 palloc = dom_string_byte_length(path_d_str);
1155 if (palloc == 0) {
1156 dom_string_unref(path_d_str);
1157 svgtiny_cleanup_state_local(&state);
1158 return svgtiny_OK;
1159 }
1160
1161 /* local copy of the path data allowing in-place modification */
1162 s = path_d = strndup(dom_string_data(path_d_str), palloc);
1163 dom_string_unref(path_d_str);
1164 if (s == NULL) {
1165 svgtiny_cleanup_state_local(&state);
1166 return svgtiny_OUT_OF_MEMORY;
1167 }
1168
1169 /* ensure path element allocation is sensibly bounded */
1170 if (palloc < 8) {
1171 palloc = 8;
1172 } else if (palloc > 64) {
1173 palloc = palloc / 2;
1174 }
1175
1176 /* allocate initial space for path elements */
1177 p = malloc(sizeof p[0] * palloc);
1178 if (p == NULL) {
1179 free(path_d);
1180 svgtiny_cleanup_state_local(&state);
1181 return svgtiny_OUT_OF_MEMORY;
1182 }
1183
1184 /* parse d and build path */
1185 for (i = 0; s[i]; i++)
1186 if (s[i] == ',')
1187 s[i] = ' ';
1188 i = 0;
1189 while (*s) {
1190 char command[2];
1191 int plot_command;
1192 float x, y, x1, y1, x2, y2, rx, ry, rotation, large_arc, sweep;
1193 int n;
1194
1195 /* Ensure there is sufficient space for path elements */
1196 #define ALLOC_PATH_ELEMENTS(NUM_ELEMENTS) \
1197 do { \
1198 if ((palloc - i) < NUM_ELEMENTS) { \
1199 float *tp; \
1200 palloc = (palloc * 2) + (palloc / 2); \
1201 tp = realloc(p, sizeof p[0] * palloc); \
1202 if (tp == NULL) { \
1203 free(p); \
1204 free(path_d); \
1205 svgtiny_cleanup_state_local(&state); \
1206 return svgtiny_OUT_OF_MEMORY; \
1207 } \
1208 p = tp; \
1209 } \
1210 } while(0)
1211
1212
1213 /* moveto (M, m), lineto (L, l) (2 arguments) */
1214 if (sscanf(s, " %1[MmLl] %f %f %n", command, &x, &y, &n) == 3) {
1215 /*LOG(("moveto or lineto"));*/
1216 if (*command == 'M' || *command == 'm')
1217 plot_command = svgtiny_PATH_MOVE;
1218 else
1219 plot_command = svgtiny_PATH_LINE;
1220 do {
1221 ALLOC_PATH_ELEMENTS(3);
1222 p[i++] = plot_command;
1223 if ('a' <= *command) {
1224 x += last_x;
1225 y += last_y;
1226 }
1227 if (plot_command == svgtiny_PATH_MOVE) {
1228 subpath_first_x = x;
1229 subpath_first_y = y;
1230 }
1231 p[i++] = last_cubic_x = last_quad_x = last_x
1232 = x;
1233 p[i++] = last_cubic_y = last_quad_y = last_y
1234 = y;
1235 s += n;
1236 plot_command = svgtiny_PATH_LINE;
1237 } while (sscanf(s, "%f %f %n", &x, &y, &n) == 2);
1238
1239 /* closepath (Z, z) (no arguments) */
1240 } else if (sscanf(s, " %1[Zz] %n", command, &n) == 1) {
1241 /*LOG(("closepath"));*/
1242 ALLOC_PATH_ELEMENTS(1);
1243
1244 p[i++] = svgtiny_PATH_CLOSE;
1245 s += n;
1246 last_cubic_x = last_quad_x = last_x = subpath_first_x;
1247 last_cubic_y = last_quad_y = last_y = subpath_first_y;
1248
1249 /* horizontal lineto (H, h) (1 argument) */
1250 } else if (sscanf(s, " %1[Hh] %f %n", command, &x, &n) == 2) {
1251 /*LOG(("horizontal lineto"));*/
1252 do {
1253 ALLOC_PATH_ELEMENTS(3);
1254
1255 p[i++] = svgtiny_PATH_LINE;
1256 if (*command == 'h')
1257 x += last_x;
1258 p[i++] = last_cubic_x = last_quad_x = last_x
1259 = x;
1260 p[i++] = last_cubic_y = last_quad_y = last_y;
1261 s += n;
1262 } while (sscanf(s, "%f %n", &x, &n) == 1);
1263
1264 /* vertical lineto (V, v) (1 argument) */
1265 } else if (sscanf(s, " %1[Vv] %f %n", command, &y, &n) == 2) {
1266 /*LOG(("vertical lineto"));*/
1267 do {
1268 ALLOC_PATH_ELEMENTS(3);
1269
1270 p[i++] = svgtiny_PATH_LINE;
1271 if (*command == 'v')
1272 y += last_y;
1273 p[i++] = last_cubic_x = last_quad_x = last_x;
1274 p[i++] = last_cubic_y = last_quad_y = last_y
1275 = y;
1276 s += n;
1277 } while (sscanf(s, "%f %n", &y, &n) == 1);
1278
1279 /* curveto (C, c) (6 arguments) */
1280 } else if (sscanf(s, " %1[Cc] %f %f %f %f %f %f %n", command,
1281 &x1, &y1, &x2, &y2, &x, &y, &n) == 7) {
1282 /*LOG(("curveto"));*/
1283 do {
1284 ALLOC_PATH_ELEMENTS(7);
1285
1286 p[i++] = svgtiny_PATH_BEZIER;
1287 if (*command == 'c') {
1288 x1 += last_x;
1289 y1 += last_y;
1290 x2 += last_x;
1291 y2 += last_y;
1292 x += last_x;
1293 y += last_y;
1294 }
1295 p[i++] = x1;
1296 p[i++] = y1;
1297 p[i++] = last_cubic_x = x2;
1298 p[i++] = last_cubic_y = y2;
1299 p[i++] = last_quad_x = last_x = x;
1300 p[i++] = last_quad_y = last_y = y;
1301 s += n;
1302 } while (sscanf(s, "%f %f %f %f %f %f %n",
1303 &x1, &y1, &x2, &y2, &x, &y, &n) == 6);
1304
1305 /* shorthand/smooth curveto (S, s) (4 arguments) */
1306 } else if (sscanf(s, " %1[Ss] %f %f %f %f %n", command,
1307 &x2, &y2, &x, &y, &n) == 5) {
1308 /*LOG(("shorthand/smooth curveto"));*/
1309 do {
1310 ALLOC_PATH_ELEMENTS(7);
1311
1312 p[i++] = svgtiny_PATH_BEZIER;
1313 x1 = last_x + (last_x - last_cubic_x);
1314 y1 = last_y + (last_y - last_cubic_y);
1315 if (*command == 's') {
1316 x2 += last_x;
1317 y2 += last_y;
1318 x += last_x;
1319 y += last_y;
1320 }
1321 p[i++] = x1;
1322 p[i++] = y1;
1323 p[i++] = last_cubic_x = x2;
1324 p[i++] = last_cubic_y = y2;
1325 p[i++] = last_quad_x = last_x = x;
1326 p[i++] = last_quad_y = last_y = y;
1327 s += n;
1328 } while (sscanf(s, "%f %f %f %f %n",
1329 &x2, &y2, &x, &y, &n) == 4);
1330
1331 /* quadratic Bezier curveto (Q, q) (4 arguments) */
1332 } else if (sscanf(s, " %1[Qq] %f %f %f %f %n", command,
1333 &x1, &y1, &x, &y, &n) == 5) {
1334 /*LOG(("quadratic Bezier curveto"));*/
1335 do {
1336 ALLOC_PATH_ELEMENTS(7);
1337
1338 p[i++] = svgtiny_PATH_BEZIER;
1339 last_quad_x = x1;
1340 last_quad_y = y1;
1341 if (*command == 'q') {
1342 x1 += last_x;
1343 y1 += last_y;
1344 x += last_x;
1345 y += last_y;
1346 }
1347 p[i++] = 1./3 * last_x + 2./3 * x1;
1348 p[i++] = 1./3 * last_y + 2./3 * y1;
1349 p[i++] = 2./3 * x1 + 1./3 * x;
1350 p[i++] = 2./3 * y1 + 1./3 * y;
1351 p[i++] = last_cubic_x = last_x = x;
1352 p[i++] = last_cubic_y = last_y = y;
1353 s += n;
1354 } while (sscanf(s, "%f %f %f %f %n",
1355 &x1, &y1, &x, &y, &n) == 4);
1356
1357 /* shorthand/smooth quadratic Bezier curveto (T, t)
1358 (2 arguments) */
1359 } else if (sscanf(s, " %1[Tt] %f %f %n", command,
1360 &x, &y, &n) == 3) {
1361 /*LOG(("shorthand/smooth quadratic Bezier curveto"));*/
1362 do {
1363 ALLOC_PATH_ELEMENTS(7);
1364
1365 p[i++] = svgtiny_PATH_BEZIER;
1366 x1 = last_x + (last_x - last_quad_x);
1367 y1 = last_y + (last_y - last_quad_y);
1368 last_quad_x = x1;
1369 last_quad_y = y1;
1370 if (*command == 't') {
1371 x1 += last_x;
1372 y1 += last_y;
1373 x += last_x;
1374 y += last_y;
1375 }
1376 p[i++] = 1./3 * last_x + 2./3 * x1;
1377 p[i++] = 1./3 * last_y + 2./3 * y1;
1378 p[i++] = 2./3 * x1 + 1./3 * x;
1379 p[i++] = 2./3 * y1 + 1./3 * y;
1380 p[i++] = last_cubic_x = last_x = x;
1381 p[i++] = last_cubic_y = last_y = y;
1382 s += n;
1383 } while (sscanf(s, "%f %f %n",
1384 &x, &y, &n) == 2);
1385
1386 /* elliptical arc (A, a) (7 arguments) */
1387 } else if (sscanf(s, " %1[Aa] %f %f %f %f %f %f %f %n", command,
1388 &rx, &ry, &rotation, &large_arc, &sweep,
1389 &x, &y, &n) == 8) {
1390 do {
1391 int bzsegments;
1392 double bzpoints[6*4]; /* allow for up to four bezier segments per arc */
1393
1394 if (*command == 'a') {
1395 x += last_x;
1396 y += last_y;
1397 }
1398
1399 bzsegments = svgarc_to_bezier(last_x, last_y,
1400 x, y,
1401 rx, ry,
1402 rotation,
1403 large_arc,
1404 sweep,
1405 bzpoints);
1406 if (bzsegments == -1) {
1407 /* draw a line */
1408 ALLOC_PATH_ELEMENTS(3);
1409 p[i++] = svgtiny_PATH_LINE;
1410 p[i++] = x;
1411 p[i++] = y;
1412 } else if (bzsegments > 0) {
1413 int bzpnt;
1414 ALLOC_PATH_ELEMENTS((unsigned int)bzsegments * 7);
1415 for (bzpnt = 0;bzpnt < (bzsegments * 6); bzpnt+=6) {
1416 p[i++] = svgtiny_PATH_BEZIER;
1417 p[i++] = bzpoints[bzpnt];
1418 p[i++] = bzpoints[bzpnt+1];
1419 p[i++] = bzpoints[bzpnt+2];
1420 p[i++] = bzpoints[bzpnt+3];
1421 p[i++] = bzpoints[bzpnt+4];
1422 p[i++] = bzpoints[bzpnt+5];
1423 }
1424 }
1425 if (bzsegments != 0) {
1426 last_cubic_x = last_quad_x = last_x = p[i-2];
1427 last_cubic_y = last_quad_y = last_y = p[i-1];
1428 }
1429
1430
1431 s += n;
1432 } while (sscanf(s, "%f %f %f %f %f %f %f %n",
1433 &rx, &ry, &rotation, &large_arc, &sweep,
1434 &x, &y, &n) == 7);
1435
1436 } else {
1437 /* fprintf(stderr, "parse failed at \"%s\"\n", s); */
1438 break;
1439 }
1440 }
1441
1442 free(path_d);
1443
1444 if (i <= 4) {
1445 /* no real segments in path */
1446 free(p);
1447 svgtiny_cleanup_state_local(&state);
1448 return svgtiny_OK;
1449 }
1450
1451 /* resize path element array to not be over allocated */
1452 if (palloc != i) {
1453 float *tp;
1454
1455 /* try the resize, if it fails just continue to use the old
1456 * allocation
1457 */
1458 tp = realloc(p, sizeof p[0] * i);
1459 if (tp != NULL) {
1460 p = tp;
1461 }
1462 }
1463
1464 err = svgtiny_add_path(p, i, &state);
1465
1466 svgtiny_cleanup_state_local(&state);
1467
1468 return err;
1469 }
1470
1471
1472 /**
1473 * Parse a <rect> element node.
1474 *
1475 * http://www.w3.org/TR/SVG11/shapes#RectElement
1476 */
1477
1478 svgtiny_code svgtiny_parse_rect(dom_element *rect,
1479 struct svgtiny_parse_state state)
1480 {
1481 svgtiny_code err;
1482 float x, y, width, height;
1483 float *p;
1484
1485 svgtiny_setup_state_local(&state);
1486
1487 svgtiny_parse_position_attributes(rect, state,
1488 &x, &y, &width, &height);
1489 svgtiny_parse_paint_attributes(rect, &state);
1490 svgtiny_parse_transform_attributes(rect, &state);
1491 css_select_results_destroy(svgtiny_parse_styles(rect, &state));
1492
1493 p = malloc(13 * sizeof p[0]);
1494 if (!p) {
1495 svgtiny_cleanup_state_local(&state);
1496 return svgtiny_OUT_OF_MEMORY;
1497 }
1498
1499 p[0] = svgtiny_PATH_MOVE;
1500 p[1] = x;
1501 p[2] = y;
1502 p[3] = svgtiny_PATH_LINE;
1503 p[4] = x + width;
1504 p[5] = y;
1505 p[6] = svgtiny_PATH_LINE;
1506 p[7] = x + width;
1507 p[8] = y + height;
1508 p[9] = svgtiny_PATH_LINE;
1509 p[10] = x;
1510 p[11] = y + height;
1511 p[12] = svgtiny_PATH_CLOSE;
1512
1513 err = svgtiny_add_path(p, 13, &state);
1514
1515 svgtiny_cleanup_state_local(&state);
1516
1517 return err;
1518 }
1519
1520
1521 /**
1522 * Parse a <circle> element node.
1523 */
1524
1525 svgtiny_code svgtiny_parse_circle(dom_element *circle,
1526 struct svgtiny_parse_state state)
1527 {
1528 svgtiny_code err;
1529 float x = 0, y = 0, r = -1;
1530 float *p;
1531 dom_string *attr;
1532 dom_exception exc;
1533
1534 svgtiny_setup_state_local(&state);
1535
1536 exc = dom_element_get_attribute(circle, state.interned_cx, &attr);
1537 if (exc != DOM_NO_ERR) {
1538 svgtiny_cleanup_state_local(&state);
1539 return svgtiny_LIBDOM_ERROR;
1540 }
1541 if (attr != NULL) {
1542 x = svgtiny_parse_length(attr, state.viewport_width, state);
1543 }
1544 dom_string_unref(attr);
1545
1546 exc = dom_element_get_attribute(circle, state.interned_cy, &attr);
1547 if (exc != DOM_NO_ERR) {
1548 svgtiny_cleanup_state_local(&state);
1549 return svgtiny_LIBDOM_ERROR;
1550 }
1551 if (attr != NULL) {
1552 y = svgtiny_parse_length(attr, state.viewport_height, state);
1553 }
1554 dom_string_unref(attr);
1555
1556 exc = dom_element_get_attribute(circle, state.interned_r, &attr);
1557 if (exc != DOM_NO_ERR) {
1558 svgtiny_cleanup_state_local(&state);
1559 return svgtiny_LIBDOM_ERROR;
1560 }
1561 if (attr != NULL) {
1562 r = svgtiny_parse_length(attr, state.viewport_width, state);
1563 }
1564 dom_string_unref(attr);
1565
1566 svgtiny_parse_paint_attributes(circle, &state);
1567 svgtiny_parse_transform_attributes(circle, &state);
1568 css_select_results_destroy(svgtiny_parse_styles(circle, &state));
1569
1570 if (r < 0) {
1571 state.diagram->error_line = -1; /* circle->line; */
1572 state.diagram->error_message = "circle: r missing or negative";
1573 svgtiny_cleanup_state_local(&state);
1574 return svgtiny_SVG_ERROR;
1575 }
1576 if (r == 0) {
1577 svgtiny_cleanup_state_local(&state);
1578 return svgtiny_OK;
1579 }
1580
1581 p = malloc(32 * sizeof p[0]);
1582 if (!p) {
1583 svgtiny_cleanup_state_local(&state);
1584 return svgtiny_OUT_OF_MEMORY;
1585 }
1586
1587 p[0] = svgtiny_PATH_MOVE;
1588 p[1] = x + r;
1589 p[2] = y;
1590 p[3] = svgtiny_PATH_BEZIER;
1591 p[4] = x + r;
1592 p[5] = y + r * KAPPA;
1593 p[6] = x + r * KAPPA;
1594 p[7] = y + r;
1595 p[8] = x;
1596 p[9] = y + r;
1597 p[10] = svgtiny_PATH_BEZIER;
1598 p[11] = x - r * KAPPA;
1599 p[12] = y + r;
1600 p[13] = x - r;
1601 p[14] = y + r * KAPPA;
1602 p[15] = x - r;
1603 p[16] = y;
1604 p[17] = svgtiny_PATH_BEZIER;
1605 p[18] = x - r;
1606 p[19] = y - r * KAPPA;
1607 p[20] = x - r * KAPPA;
1608 p[21] = y - r;
1609 p[22] = x;
1610 p[23] = y - r;
1611 p[24] = svgtiny_PATH_BEZIER;
1612 p[25] = x + r * KAPPA;
1613 p[26] = y - r;
1614 p[27] = x + r;
1615 p[28] = y - r * KAPPA;
1616 p[29] = x + r;
1617 p[30] = y;
1618 p[31] = svgtiny_PATH_CLOSE;
1619
1620 err = svgtiny_add_path(p, 32, &state);
1621
1622 svgtiny_cleanup_state_local(&state);
1623
1624 return err;
1625 }
1626
1627
1628 /**
1629 * Parse an <ellipse> element node.
1630 */
1631
1632 svgtiny_code svgtiny_parse_ellipse(dom_element *ellipse,
1633 struct svgtiny_parse_state state)
1634 {
1635 svgtiny_code err;
1636 float x = 0, y = 0, rx = -1, ry = -1;
1637 float *p;
1638 dom_string *attr;
1639 dom_exception exc;
1640
1641 svgtiny_setup_state_local(&state);
1642
1643 exc = dom_element_get_attribute(ellipse, state.interned_cx, &attr);
1644 if (exc != DOM_NO_ERR) {
1645 svgtiny_cleanup_state_local(&state);
1646 return svgtiny_LIBDOM_ERROR;
1647 }
1648 if (attr != NULL) {
1649 x = svgtiny_parse_length(attr, state.viewport_width, state);
1650 }
1651 dom_string_unref(attr);
1652
1653 exc = dom_element_get_attribute(ellipse, state.interned_cy, &attr);
1654 if (exc != DOM_NO_ERR) {
1655 svgtiny_cleanup_state_local(&state);
1656 return svgtiny_LIBDOM_ERROR;
1657 }
1658 if (attr != NULL) {
1659 y = svgtiny_parse_length(attr, state.viewport_height, state);
1660 }
1661 dom_string_unref(attr);
1662
1663 exc = dom_element_get_attribute(ellipse, state.interned_rx, &attr);
1664 if (exc != DOM_NO_ERR) {
1665 svgtiny_cleanup_state_local(&state);
1666 return svgtiny_LIBDOM_ERROR;
1667 }
1668 if (attr != NULL) {
1669 rx = svgtiny_parse_length(attr, state.viewport_width, state);
1670 }
1671 dom_string_unref(attr);
1672
1673 exc = dom_element_get_attribute(ellipse, state.interned_ry, &attr);
1674 if (exc != DOM_NO_ERR) {
1675 svgtiny_cleanup_state_local(&state);
1676 return svgtiny_LIBDOM_ERROR;
1677 }
1678 if (attr != NULL) {
1679 ry = svgtiny_parse_length(attr, state.viewport_width, state);
1680 }
1681 dom_string_unref(attr);
1682
1683 svgtiny_parse_paint_attributes(ellipse, &state);
1684 svgtiny_parse_transform_attributes(ellipse, &state);
1685 css_select_results_destroy(svgtiny_parse_styles(ellipse, &state));
1686
1687 if (rx < 0 || ry < 0) {
1688 state.diagram->error_line = -1; /* ellipse->line; */
1689 state.diagram->error_message = "ellipse: rx or ry missing "
1690 "or negative";
1691 svgtiny_cleanup_state_local(&state);
1692 return svgtiny_SVG_ERROR;
1693 }
1694 if (rx == 0 || ry == 0) {
1695 svgtiny_cleanup_state_local(&state);
1696 return svgtiny_OK;
1697 }
1698
1699 p = malloc(32 * sizeof p[0]);
1700 if (!p) {
1701 svgtiny_cleanup_state_local(&state);
1702 return svgtiny_OUT_OF_MEMORY;
1703 }
1704
1705 p[0] = svgtiny_PATH_MOVE;
1706 p[1] = x + rx;
1707 p[2] = y;
1708 p[3] = svgtiny_PATH_BEZIER;
1709 p[4] = x + rx;
1710 p[5] = y + ry * KAPPA;
1711 p[6] = x + rx * KAPPA;
1712 p[7] = y + ry;
1713 p[8] = x;
1714 p[9] = y + ry;
1715 p[10] = svgtiny_PATH_BEZIER;
1716 p[11] = x - rx * KAPPA;
1717 p[12] = y + ry;
1718 p[13] = x - rx;
1719 p[14] = y + ry * KAPPA;
1720 p[15] = x - rx;
1721 p[16] = y;
1722 p[17] = svgtiny_PATH_BEZIER;
1723 p[18] = x - rx;
1724 p[19] = y - ry * KAPPA;
1725 p[20] = x - rx * KAPPA;
1726 p[21] = y - ry;
1727 p[22] = x;
1728 p[23] = y - ry;
1729 p[24] = svgtiny_PATH_BEZIER;
1730 p[25] = x + rx * KAPPA;
1731 p[26] = y - ry;
1732 p[27] = x + rx;
1733 p[28] = y - ry * KAPPA;
1734 p[29] = x + rx;
1735 p[30] = y;
1736 p[31] = svgtiny_PATH_CLOSE;
1737
1738 err = svgtiny_add_path(p, 32, &state);
1739
1740 svgtiny_cleanup_state_local(&state);
1741
1742 return err;
1743 }
1744
1745
1746 /**
1747 * Parse a <line> element node.
1748 */
1749
1750 svgtiny_code svgtiny_parse_line(dom_element *line,
1751 struct svgtiny_parse_state state)
1752 {
1753 svgtiny_code err;
1754 float x1 = 0, y1 = 0, x2 = 0, y2 = 0;
1755 float *p;
1756 dom_string *attr;
1757 dom_exception exc;
1758
1759 svgtiny_setup_state_local(&state);
1760
1761 exc = dom_element_get_attribute(line, state.interned_x1, &attr);
1762 if (exc != DOM_NO_ERR) {
1763 svgtiny_cleanup_state_local(&state);
1764 return svgtiny_LIBDOM_ERROR;
1765 }
1766 if (attr != NULL) {
1767 x1 = svgtiny_parse_length(attr, state.viewport_width, state);
1768 }
1769 dom_string_unref(attr);
1770
1771 exc = dom_element_get_attribute(line, state.interned_y1, &attr);
1772 if (exc != DOM_NO_ERR) {
1773 svgtiny_cleanup_state_local(&state);
1774 return svgtiny_LIBDOM_ERROR;
1775 }
1776 if (attr != NULL) {
1777 y1 = svgtiny_parse_length(attr, state.viewport_height, state);
1778 }
1779 dom_string_unref(attr);
1780
1781 exc = dom_element_get_attribute(line, state.interned_x2, &attr);
1782 if (exc != DOM_NO_ERR) {
1783 svgtiny_cleanup_state_local(&state);
1784 return svgtiny_LIBDOM_ERROR;
1785 }
1786 if (attr != NULL) {
1787 x2 = svgtiny_parse_length(attr, state.viewport_width, state);
1788 }
1789 dom_string_unref(attr);
1790
1791 exc = dom_element_get_attribute(line, state.interned_y2, &attr);
1792 if (exc != DOM_NO_ERR) {
1793 svgtiny_cleanup_state_local(&state);
1794 return svgtiny_LIBDOM_ERROR;
1795 }
1796 if (attr != NULL) {
1797 y2 = svgtiny_parse_length(attr, state.viewport_height, state);
1798 }
1799 dom_string_unref(attr);
1800
1801 svgtiny_parse_paint_attributes(line, &state);
1802 svgtiny_parse_transform_attributes(line, &state);
1803 css_select_results_destroy(svgtiny_parse_styles(line, &state));
1804
1805 p = malloc(7 * sizeof p[0]);
1806 if (!p) {
1807 svgtiny_cleanup_state_local(&state);
1808 return svgtiny_OUT_OF_MEMORY;
1809 }
1810
1811 p[0] = svgtiny_PATH_MOVE;
1812 p[1] = x1;
1813 p[2] = y1;
1814 p[3] = svgtiny_PATH_LINE;
1815 p[4] = x2;
1816 p[5] = y2;
1817 p[6] = svgtiny_PATH_CLOSE;
1818
1819 err = svgtiny_add_path(p, 7, &state);
1820
1821 svgtiny_cleanup_state_local(&state);
1822
1823 return err;
1824 }
1825
1826
1827 /**
1828 * Parse a <polyline> or <polygon> element node.
1829 *
1830 * http://www.w3.org/TR/SVG11/shapes#PolylineElement
1831 * http://www.w3.org/TR/SVG11/shapes#PolygonElement
1832 */
1833
1834 svgtiny_code svgtiny_parse_poly(dom_element *poly,
1835 struct svgtiny_parse_state state, bool polygon)
1836 {
1837 svgtiny_code err;
1838 dom_string *points_str;
1839 dom_exception exc;
1840 char *s, *points;
1841 float *p;
1842 unsigned int i;
1843
1844 svgtiny_setup_state_local(&state);
1845
1846 svgtiny_parse_paint_attributes(poly, &state);
1847 svgtiny_parse_transform_attributes(poly, &state);
1848 css_select_results_destroy(svgtiny_parse_styles(poly, &state));
1849
1850 exc = dom_element_get_attribute(poly, state.interned_points,
1851 &points_str);
1852 if (exc != DOM_NO_ERR) {
1853 svgtiny_cleanup_state_local(&state);
1854 return svgtiny_LIBDOM_ERROR;
1855 }
1856
1857 if (points_str == NULL) {
1858 state.diagram->error_line = -1; /* poly->line; */
1859 state.diagram->error_message =
1860 "polyline/polygon: missing points attribute";
1861 svgtiny_cleanup_state_local(&state);
1862 return svgtiny_SVG_ERROR;
1863 }
1864
1865 s = points = strndup(dom_string_data(points_str),
1866 dom_string_byte_length(points_str));
1867 dom_string_unref(points_str);
1868 /* read points attribute */
1869 if (s == NULL) {
1870 svgtiny_cleanup_state_local(&state);
1871 return svgtiny_OUT_OF_MEMORY;
1872 }
1873 /* allocate space for path: it will never have more elements than s */
1874 p = malloc(sizeof p[0] * strlen(s));
1875 if (!p) {
1876 free(points);
1877 svgtiny_cleanup_state_local(&state);
1878 return svgtiny_OUT_OF_MEMORY;
1879 }
1880
1881 /* parse s and build path */
1882 for (i = 0; s[i]; i++)
1883 if (s[i] == ',')
1884 s[i] = ' ';
1885 i = 0;
1886 while (*s) {
1887 float x, y;
1888 int n;
1889
1890 if (sscanf(s, "%f %f %n", &x, &y, &n) == 2) {
1891 if (i == 0)
1892 p[i++] = svgtiny_PATH_MOVE;
1893 else
1894 p[i++] = svgtiny_PATH_LINE;
1895 p[i++] = x;
1896 p[i++] = y;
1897 s += n;
1898 } else {
1899 break;
1900 }
1901 }
1902 if (polygon)
1903 p[i++] = svgtiny_PATH_CLOSE;
1904
1905 free(points);
1906
1907 err = svgtiny_add_path(p, i, &state);
1908
1909 svgtiny_cleanup_state_local(&state);
1910
1911 return err;
1912 }
1913
1914
1915 /**
1916 * Parse a <text> or <tspan> element node.
1917 */
1918
1919 svgtiny_code svgtiny_parse_text(dom_element *text,
1920 struct svgtiny_parse_state state)
1921 {
1922 float x, y, width, height;
1923 float px, py;
1924 dom_node *child;
1925 dom_exception exc;
1926
1927 svgtiny_setup_state_local(&state);
1928
1929 svgtiny_parse_position_attributes(text, state,
1930 &x, &y, &width, &height);
1931 svgtiny_parse_font_attributes(text, &state);
1932 svgtiny_parse_transform_attributes(text, &state);
1933
1934 px = state.ctm.a * x + state.ctm.c * y + state.ctm.e;
1935 py = state.ctm.b * x + state.ctm.d * y + state.ctm.f;
1936 /* state.ctm.e = px - state.origin_x; */
1937 /* state.ctm.f = py - state.origin_y; */
1938
1939 exc = dom_node_get_first_child(text, &child);
1940 if (exc != DOM_NO_ERR) {
1941 return svgtiny_LIBDOM_ERROR;
1942 svgtiny_cleanup_state_local(&state);
1943 }
1944 while (child != NULL) {
1945 dom_node *next;
1946 dom_node_type nodetype;
1947 svgtiny_code code = svgtiny_OK;
1948
1949 exc = dom_node_get_node_type(child, &nodetype);
1950 if (exc != DOM_NO_ERR) {
1951 dom_node_unref(child);
1952 svgtiny_cleanup_state_local(&state);
1953 return svgtiny_LIBDOM_ERROR;
1954 }
1955 if (nodetype == DOM_ELEMENT_NODE) {
1956 dom_string *nodename;
1957 exc = dom_node_get_node_name(child, &nodename);
1958 if (exc != DOM_NO_ERR) {
1959 dom_node_unref(child);
1960 svgtiny_cleanup_state_local(&state);
1961 return svgtiny_LIBDOM_ERROR;
1962 }
1963 if (dom_string_caseless_isequal(nodename,
1964 state.interned_tspan))
1965 code = svgtiny_parse_text((dom_element *)child,
1966 state);
1967 dom_string_unref(nodename);
1968 } else if (nodetype == DOM_TEXT_NODE) {
1969 struct svgtiny_shape *shape = svgtiny_add_shape(&state);
1970 dom_string *content;
1971 if (shape == NULL) {
1972 dom_node_unref(child);
1973 svgtiny_cleanup_state_local(&state);
1974 return svgtiny_OUT_OF_MEMORY;
1975 }
1976 exc = dom_text_get_whole_text(child, &content);
1977 if (exc != DOM_NO_ERR) {
1978 dom_node_unref(child);
1979 svgtiny_cleanup_state_local(&state);
1980 return svgtiny_LIBDOM_ERROR;
1981 }
1982 if (content != NULL) {
1983 shape->text = strndup(dom_string_data(content),
1984 dom_string_byte_length(content));
1985 dom_string_unref(content);
1986 } else {
1987 shape->text = strdup("");
1988 }
1989 shape->text_x = px;
1990 shape->text_y = py;
1991 state.diagram->shape_count++;
1992 }
1993
1994 if (code != svgtiny_OK) {
1995 dom_node_unref(child);
1996 svgtiny_cleanup_state_local(&state);
1997 return code;
1998 }
1999 exc = dom_node_get_next_sibling(child, &next);
2000 dom_node_unref(child);
2001 if (exc != DOM_NO_ERR) {
2002 svgtiny_cleanup_state_local(&state);
2003 return svgtiny_LIBDOM_ERROR;
2004 }
2005 child = next;
2006 }
2007
2008 svgtiny_cleanup_state_local(&state);
2009
2010 return svgtiny_OK;
2011 }
2012
2013
2014 /**
2015 * Parse x, y, width, and height attributes, if present.
2016 */
2017
2018 void svgtiny_parse_position_attributes(dom_element *node,
2019 const struct svgtiny_parse_state state,
2020 float *x, float *y, float *width, float *height)
2021 {
2022 dom_string *attr;
2023 dom_exception exc;
2024
2025 *x = 0;
2026 *y = 0;
2027 *width = state.viewport_width;
2028 *height = state.viewport_height;
2029
2030 exc = dom_element_get_attribute(node, state.interned_x, &attr);
2031 if (exc == DOM_NO_ERR && attr != NULL) {
2032 *x = svgtiny_parse_length(attr, state.viewport_width, state);
2033 dom_string_unref(attr);
2034 }
2035
2036 exc = dom_element_get_attribute(node, state.interned_y, &attr);
2037 if (exc == DOM_NO_ERR && attr != NULL) {
2038 *y = svgtiny_parse_length(attr, state.viewport_height, state);
2039 dom_string_unref(attr);
2040 }
2041
2042 exc = dom_element_get_attribute(node, state.interned_width, &attr);
2043 if (exc == DOM_NO_ERR && attr != NULL) {
2044 *width = svgtiny_parse_length(attr, state.viewport_width,
2045 state);
2046 dom_string_unref(attr);
2047 }
2048
2049 exc = dom_element_get_attribute(node, state.interned_height, &attr);
2050 if (exc == DOM_NO_ERR && attr != NULL) {
2051 *height = svgtiny_parse_length(attr, state.viewport_height,
2052 state);
2053 dom_string_unref(attr);
2054 }
2055 }
2056
2057
2058 /**
2059 * Parse a length as a number of pixels.
2060 */
2061
2062 static float _svgtiny_parse_length(const char *s, int viewport_size,
2063 const struct svgtiny_parse_state state)
2064 {
2065 int num_length = strspn(s, "0123456789+-.");
2066 const char *unit = s + num_length;
2067 float n = atof((const char *) s);
2068 float font_size = 20;
2069
2070 UNUSED(state);
2071
2072 if (unit[0] == 0) {
2073 return n;
2074 } else if (unit[0] == '%') {
2075 return n / 100.0 * viewport_size;
2076 } else if (unit[0] == 'e' && unit[1] == 'm') {
2077 return n * font_size;
2078 } else if (unit[0] == 'e' && unit[1] == 'x') {
2079 return n / 2.0 * font_size;
2080 } else if (unit[0] == 'p' && unit[1] == 'x') {
2081 return n;
2082 } else if (unit[0] == 'p' && unit[1] == 't') {
2083 return n * 1.25;
2084 } else if (unit[0] == 'p' && unit[1] == 'c') {
2085 return n * 15.0;
2086 } else if (unit[0] == 'm' && unit[1] == 'm') {
2087 return n * 3.543307;
2088 } else if (unit[0] == 'c' && unit[1] == 'm') {
2089 return n * 35.43307;
2090 } else if (unit[0] == 'i' && unit[1] == 'n') {
2091 return n * 90;
2092 }
2093
2094 return 0;
2095 }
2096
2097 float svgtiny_parse_length(dom_string *s, int viewport_size,
2098 const struct svgtiny_parse_state state)
2099 {
2100 char *ss = strndup(dom_string_data(s), dom_string_byte_length(s));
2101 float ret = _svgtiny_parse_length(ss, viewport_size, state);
2102 free(ss);
2103 return ret;
2104 }
2105
2106 /**
2107 * Parse paint attributes, if present.
2108 */
2109
2110 void svgtiny_parse_paint_attributes(dom_element *node,
2111 struct svgtiny_parse_state *state)
2112 {
2113 dom_string *attr;
2114 dom_exception exc;
2115
2116 exc = dom_element_get_attribute(node, state->interned_fill, &attr);
2117 if (exc == DOM_NO_ERR && attr != NULL) {
2118 svgtiny_parse_color(attr, &state->fill, &state->fill_grad, state);
2119 dom_string_unref(attr);
2120 }
2121
2122 exc = dom_element_get_attribute(node, state->interned_stroke, &attr);
2123 if (exc == DOM_NO_ERR && attr != NULL) {
2124 svgtiny_parse_color(attr, &state->stroke, &state->stroke_grad, state);
2125 dom_string_unref(attr);
2126 }
2127
2128 exc = dom_element_get_attribute(node, state->interned_stroke_width, &attr);
2129 if (exc == DOM_NO_ERR && attr != NULL) {
2130 state->stroke_width = svgtiny_parse_length(attr,
2131 state->viewport_width, *state);
2132 dom_string_unref(attr);
2133 }
2134
2135 exc = dom_element_get_attribute(node, state->interned_style, &attr);
2136 if (exc == DOM_NO_ERR && attr != NULL) {
2137 /* Parse a few properties "by hand" until they can
2138 be supported in libcss. */
2139 char *style = strndup(dom_string_data(attr),
2140 dom_string_byte_length(attr));
2141 const char *s;
2142 char *value;
2143 if ((s = strstr(style, "fill:"))) {
2144 s += 5;
2145 while (*s == ' ')
2146 s++;
2147 value = strndup(s, strcspn(s, "; "));
2148 _svgtiny_parse_color(value, &state->fill, &state->fill_grad, state);
2149 free(value);
2150 }
2151 if ((s = strstr(style, "stroke:"))) {
2152 s += 7;
2153 while (*s == ' ')
2154 s++;
2155 value = strndup(s, strcspn(s, "; "));
2156 _svgtiny_parse_color(value, &state->stroke, &state->stroke_grad, state);
2157 free(value);
2158 }
2159 if ((s = strstr(style, "stroke-width:"))) {
2160 s += 13;
2161 while (*s == ' ')
2162 s++;
2163 value = strndup(s, strcspn(s, "; "));
2164 state->stroke_width = _svgtiny_parse_length(value,
2165 state->viewport_width, *state);
2166 free(value);
2167 }
2168 free(style);
2169 dom_string_unref(attr);
2170 }
2171 }
2172
2173
2174 /**
2175 * Parse a colour.
2176 */
2177
2178 static void _svgtiny_parse_color(const char *s, svgtiny_colour *c,
2179 struct svgtiny_parse_state_gradient *grad,
2180 struct svgtiny_parse_state *state)
2181 {
2182 unsigned int r, g, b;
2183 float rf, gf, bf;
2184 size_t len = strlen(s);
2185 char *id = 0, *rparen;
2186
2187 if (len == 4 && s[0] == '#') {
2188 if (sscanf(s + 1, "%1x%1x%1x", &r, &g, &b) == 3)
2189 *c = svgtiny_RGB(r | r << 4, g | g << 4, b | b << 4);
2190
2191 } else if (len == 7 && s[0] == '#') {
2192 if (sscanf(s + 1, "%2x%2x%2x", &r, &g, &b) == 3)
2193 *c = svgtiny_RGB(r, g, b);
2194
2195 } else if (10 <= len && s[0] == 'r' && s[1] == 'g' && s[2] == 'b' &&
2196 s[3] == '(' && s[len - 1] == ')') {
2197 if (sscanf(s + 4, "%u,%u,%u", &r, &g, &b) == 3)
2198 *c = svgtiny_RGB(r, g, b);
2199 else if (sscanf(s + 4, "%f%%,%f%%,%f%%", &rf, &gf, &bf) == 3) {
2200 b = bf * 255 / 100;
2201 g = gf * 255 / 100;
2202 r = rf * 255 / 100;
2203 *c = svgtiny_RGB(r, g, b);
2204 }
2205
2206 } else if (len == 4 && strcmp(s, "none") == 0) {
2207 *c = svgtiny_TRANSPARENT;
2208
2209 } else if (5 < len && s[0] == 'u' && s[1] == 'r' && s[2] == 'l' &&
2210 s[3] == '(') {
2211 if (grad == NULL) {
2212 *c = svgtiny_RGB(0, 0, 0);
2213 } else if (s[4] == '#') {
2214 id = strdup(s + 5);
2215 if (!id)
2216 return;
2217 rparen = strchr(id, ')');
2218 if (rparen)
2219 *rparen = 0;
2220 svgtiny_find_gradient(id, grad, state);
2221 free(id);
2222 if (grad->linear_gradient_stop_count == 0)
2223 *c = svgtiny_TRANSPARENT;
2224 else if (grad->linear_gradient_stop_count == 1)
2225 *c = grad->gradient_stop[0].color;
2226 else
2227 *c = svgtiny_LINEAR_GRADIENT;
2228 }
2229
2230 } else {
2231 const struct svgtiny_named_color *named_color;
2232 named_color = svgtiny_color_lookup(s, strlen(s));
2233 if (named_color)
2234 *c = named_color->color;
2235 }
2236 }
2237
2238 void svgtiny_parse_color(dom_string *s, svgtiny_colour *c,
2239 struct svgtiny_parse_state_gradient *grad,
2240 struct svgtiny_parse_state *state)
2241 {
2242 dom_string_ref(s);
2243 _svgtiny_parse_color(dom_string_data(s), c, grad, state);
2244 dom_string_unref(s);
2245 }
2246
2247 /**
2248 * Parse font attributes, if present.
2249 */
2250
2251 void svgtiny_parse_font_attributes(dom_element *node,
2252 struct svgtiny_parse_state *state)
2253 {
2254 /* TODO: Implement this, it never used to be */
2255 UNUSED(node);
2256 UNUSED(state);
2257 #ifdef WRITTEN_THIS_PROPERLY
2258 const xmlAttr *attr;
2259
2260 UNUSED(state);
2261
2262 for (attr = node->properties; attr; attr = attr->next) {
2263 if (strcmp((const char *) attr->name, "font-size") == 0) {
2264 /* TODO */
2265 }
2266 }
2267 #endif
2268 }
2269
2270
2271 /**
2272 * Parse transform attributes, if present.
2273 *
2274 * http://www.w3.org/TR/SVG11/coords#TransformAttribute
2275 */
2276
2277 void svgtiny_parse_transform_attributes(dom_element *node,
2278 struct svgtiny_parse_state *state)
2279 {
2280 char *transform;
2281 dom_string *attr;
2282 dom_exception exc;
2283
2284 exc = dom_element_get_attribute(node, state->interned_transform,
2285 &attr);
2286 if (exc == DOM_NO_ERR && attr != NULL) {
2287 transform = strndup(dom_string_data(attr),
2288 dom_string_byte_length(attr));
2289 svgtiny_parse_transform(transform, &state->ctm.a, &state->ctm.b,
2290 &state->ctm.c, &state->ctm.d,
2291 &state->ctm.e, &state->ctm.f);
2292 free(transform);
2293 dom_string_unref(attr);
2294 }
2295 }
2296
2297 /**
2298 * Parse element styles.
2299 *
2300 * First we parse any inline "style" attributes. We then compose the
2301 * element's style with any parent styles. Finally, we compute any
2302 * styles that we support and set the corresponding fields in the
2303 * parser state.
2304 */
2305 css_select_results *svgtiny_parse_styles(dom_element *node,
2306 struct svgtiny_parse_state *state)
2307 {
2308 css_error code;
2309 uint8_t fill_opacity_type;
2310 css_fixed fill_opacity;
2311 uint8_t stroke_opacity_type;
2312 css_fixed stroke_opacity;
2313
2314 /* We store the result of svgtiny_parse_style_inline() in
2315 * inline_sheet, and that function returns NULL on error; in
2316 * particular you do not need to css_stylesheet_destroy() the
2317 * result if it is NULL, and css_stylesheet_destroy() checks
2318 * for that case. */
2319 css_stylesheet *inline_sheet = NULL;
2320
2321 /* Initialize this to NULL for the same reason: so that we can
2322 * safely destroy it later even if we never populated it. */
2323 css_select_results *styles = NULL;
2324
2325 /* The result of composing this node's styles with its
2326 * parent's styles. */
2327 css_computed_style *composed = NULL;
2328
2329 dom_exception exc;
2330 dom_string *attr;
2331
2332 exc = dom_element_get_attribute(node, state->interned_style, &attr);
2333 if (exc != DOM_NO_ERR) {
2334 return NULL;
2335 }
2336 if (attr != NULL) {
2337 inline_sheet = svgtiny_parse_style_inline(
2338 (uint8_t *)dom_string_data(attr),
2339 dom_string_byte_length(attr));
2340 dom_string_unref(attr);
2341 }
2342
2343 code = svgtiny_select_style(state, node, inline_sheet, &styles);
2344 css_stylesheet_destroy(inline_sheet);
2345 if (code != CSS_OK) {
2346 return NULL;
2347 }
2348
2349 if (state->parent_style != NULL) {
2350 code = css_computed_style_compose(
2351 state->parent_style,
2352 styles->styles[CSS_PSEUDO_ELEMENT_NONE],
2353 &state->unit_ctx,
2354 &composed);
2355
2356 if (code != CSS_OK || composed == NULL) {
2357 /* This function promises to return a
2358 * fully-composed set of styles, so if
2359 * we can't do that, we should fail. */
2360 css_select_results_destroy(styles);
2361 return NULL;
2362 }
2363
2364 /* Replace my original computed styles with the
2365 * composed ones */
2366 css_computed_style_destroy(
2367 styles->styles[CSS_PSEUDO_ELEMENT_NONE]);
2368 styles->styles[CSS_PSEUDO_ELEMENT_NONE] = composed;
2369 }
2370
2371 fill_opacity_type = css_computed_fill_opacity(
2372 styles->styles[CSS_PSEUDO_ELEMENT_NONE],
2373 &fill_opacity);
2374 stroke_opacity_type = css_computed_stroke_opacity(
2375 styles->styles[CSS_PSEUDO_ELEMENT_NONE],
2376 &stroke_opacity);
2377
2378 state->fill_opacity = FIXTOFLT(fill_opacity);
2379 state->stroke_opacity = FIXTOFLT(stroke_opacity);
2380
2381 return styles;
2382 }
2383
2384 /**
2385 * Parse a transform string.
2386 */
2387
2388 void svgtiny_parse_transform(char *s, float *ma, float *mb,
2389 float *mc, float *md, float *me, float *mf)
2390 {
2391 float a, b, c, d, e, f;
2392 float za, zb, zc, zd, ze, zf;
2393 float angle, x, y;
2394 int n;
2395 unsigned int i;
2396
2397 for (i = 0; s[i]; i++)
2398 if (s[i] == ',')
2399 s[i] = ' ';
2400
2401 while (*s) {
2402 a = d = 1;
2403 b = c = 0;
2404 e = f = 0;
2405 n = 0;
2406 if ((sscanf(s, " matrix (%f %f %f %f %f %f ) %n",
2407 &a, &b, &c, &d, &e, &f, &n) == 6) && (n > 0))
2408 ;
2409 else if ((sscanf(s, " translate (%f %f ) %n",
2410 &e, &f, &n) == 2) && (n > 0))
2411 ;
2412 else if ((sscanf(s, " translate (%f ) %n",
2413 &e, &n) == 1) && (n > 0))
2414 ;
2415 else if ((sscanf(s, " scale (%f %f ) %n",
2416 &a, &d, &n) == 2) && (n > 0))
2417 ;
2418 else if ((sscanf(s, " scale (%f ) %n",
2419 &a, &n) == 1) && (n > 0))
2420 d = a;
2421 else if ((sscanf(s, " rotate (%f %f %f ) %n",
2422 &angle, &x, &y, &n) == 3) && (n > 0)) {
2423 angle = angle / 180 * M_PI;
2424 a = cos(angle);
2425 b = sin(angle);
2426 c = -sin(angle);
2427 d = cos(angle);
2428 e = -x * cos(angle) + y * sin(angle) + x;
2429 f = -x * sin(angle) - y * cos(angle) + y;
2430 } else if ((sscanf(s, " rotate (%f ) %n",
2431 &angle, &n) == 1) && (n > 0)) {
2432 angle = angle / 180 * M_PI;
2433 a = cos(angle);
2434 b = sin(angle);
2435 c = -sin(angle);
2436 d = cos(angle);
2437 } else if ((sscanf(s, " skewX (%f ) %n",
2438 &angle, &n) == 1) && (n > 0)) {
2439 angle = angle / 180 * M_PI;
2440 c = tan(angle);
2441 } else if ((sscanf(s, " skewY (%f ) %n",
2442 &angle, &n) == 1) && (n > 0)) {
2443 angle = angle / 180 * M_PI;
2444 b = tan(angle);
2445 } else
2446 break;
2447 za = *ma * a + *mc * b;
2448 zb = *mb * a + *md * b;
2449 zc = *ma * c + *mc * d;
2450 zd = *mb * c + *md * d;
2451 ze = *ma * e + *mc * f + *me;
2452 zf = *mb * e + *md * f + *mf;
2453 *ma = za;
2454 *mb = zb;
2455 *mc = zc;
2456 *md = zd;
2457 *me = ze;
2458 *mf = zf;
2459 s += n;
2460 }
2461 }
2462
2463
2464 /**
2465 * Add a path to the svgtiny_diagram.
2466 */
2467
2468 svgtiny_code svgtiny_add_path(float *p, unsigned int n,
2469 struct svgtiny_parse_state *state)
2470 {
2471 struct svgtiny_shape *shape;
2472
2473 if (state->fill == svgtiny_LINEAR_GRADIENT)
2474 return svgtiny_add_path_linear_gradient(p, n, state);
2475
2476 svgtiny_transform_path(p, n, state);
2477
2478 shape = svgtiny_add_shape(state);
2479 if (!shape) {
2480 free(p);
2481 return svgtiny_OUT_OF_MEMORY;
2482 }
2483 shape->path = p;
2484 shape->path_length = n;
2485 state->diagram->shape_count++;
2486
2487 return svgtiny_OK;
2488 }
2489
2490
2491 /**
2492 * Add a svgtiny_shape to the svgtiny_diagram.
2493 */
2494
2495 struct svgtiny_shape *svgtiny_add_shape(struct svgtiny_parse_state *state)
2496 {
2497 struct svgtiny_shape *shape = realloc(state->diagram->shape,
2498 (state->diagram->shape_count + 1) *
2499 sizeof (state->diagram->shape[0]));
2500 if (!shape)
2501 return 0;
2502 state->diagram->shape = shape;
2503
2504 shape += state->diagram->shape_count;
2505 shape->path = 0;
2506 shape->path_length = 0;
2507 shape->text = 0;
2508 shape->fill = state->fill;
2509 shape->stroke = state->stroke;
2510 shape->fill_opacity = state->fill_opacity;
2511 shape->stroke_opacity = state->stroke_opacity;
2512 shape->stroke_width = lroundf((float) state->stroke_width *
2513 (state->ctm.a + state->ctm.d) / 2.0);
2514 if (0 < state->stroke_width && shape->stroke_width == 0)
2515 shape->stroke_width = 1;
2516
2517 return shape;
2518 }
2519
2520
2521 /**
2522 * Apply the current transformation matrix to a path.
2523 */
2524
2525 void svgtiny_transform_path(float *p, unsigned int n,
2526 struct svgtiny_parse_state *state)
2527 {
2528 unsigned int j;
2529
2530 for (j = 0; j != n; ) {
2531 unsigned int points = 0;
2532 unsigned int k;
2533 switch ((int) p[j]) {
2534 case svgtiny_PATH_MOVE:
2535 case svgtiny_PATH_LINE:
2536 points = 1;
2537 break;
2538 case svgtiny_PATH_CLOSE:
2539 points = 0;
2540 break;
2541 case svgtiny_PATH_BEZIER:
2542 points = 3;
2543 break;
2544 default:
2545 assert(0);
2546 }
2547 j++;
2548 for (k = 0; k != points; k++) {
2549 float x0 = p[j], y0 = p[j + 1];
2550 float x = state->ctm.a * x0 + state->ctm.c * y0 +
2551 state->ctm.e;
2552 float y = state->ctm.b * x0 + state->ctm.d * y0 +
2553 state->ctm.f;
2554 p[j] = x;
2555 p[j + 1] = y;
2556 j += 2;
2557 }
2558 }
2559 }
2560
2561
2562 /**
2563 * Free all memory used by a diagram.
2564 */
2565
2566 void svgtiny_free(struct svgtiny_diagram *svg)
2567 {
2568 unsigned int i;
2569 assert(svg);
2570
2571 for (i = 0; i != svg->shape_count; i++) {
2572 free(svg->shape[i].path);
2573 free(svg->shape[i].text);
2574 }
2575
2576 free(svg->shape);
2577
2578 free(svg);
2579 }
2580
2581 #ifndef HAVE_STRNDUP
2582 char *svgtiny_strndup(const char *s, size_t n)
2583 {
2584 size_t len;
2585 char *s2;
2586
2587 for (len = 0; len != n && s[len]; len++)
2588 continue;
2589
2590 s2 = malloc(len + 1);
2591 if (s2 == NULL)
2592 return NULL;
2593
2594 memcpy(s2, s, len);
2595 s2[len] = '\0';
2596
2597 return s2;
2598 }
2599 #endif