1#include "mupdf/fitz.h"
2#include "svg-imp.h"
3
4#include <string.h>
5#include <math.h>
6
7/* default page size */
8#define DEF_WIDTH 612
9#define DEF_HEIGHT 792
10#define DEF_FONTSIZE 12
11
12#define MAX_USE_DEPTH 100
13
14typedef struct svg_state_s svg_state;
15
16struct svg_state_s
17{
18 fz_matrix transform;
19 fz_stroke_state stroke;
20 int use_depth;
21
22 float viewport_w, viewport_h;
23 float viewbox_w, viewbox_h, viewbox_size;
24 float fontsize;
25
26 float opacity;
27
28 int fill_rule;
29 int fill_is_set;
30 float fill_color[3];
31 float fill_opacity;
32
33 int stroke_is_set;
34 float stroke_color[3];
35 float stroke_opacity;
36};
37
38static void svg_parse_common(fz_context *ctx, svg_document *doc, fz_xml *node, svg_state *state);
39static void svg_run_element(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *root, const svg_state *state);
40
41static void svg_fill(fz_context *ctx, fz_device *dev, svg_document *doc, fz_path *path, svg_state *state)
42{
43 float opacity = state->opacity * state->fill_opacity;
44 if (path)
45 fz_fill_path(ctx, dev, path, state->fill_rule, state->transform, fz_device_rgb(ctx), state->fill_color, opacity, fz_default_color_params);
46}
47
48static void svg_stroke(fz_context *ctx, fz_device *dev, svg_document *doc, fz_path *path, svg_state *state)
49{
50 float opacity = state->opacity * state->stroke_opacity;
51 if (path)
52 fz_stroke_path(ctx, dev, path, &state->stroke, state->transform, fz_device_rgb(ctx), state->stroke_color, opacity, fz_default_color_params);
53}
54
55static void svg_draw_path(fz_context *ctx, fz_device *dev, svg_document *doc, fz_path *path, svg_state *state)
56{
57 if (state->fill_is_set)
58 svg_fill(ctx, dev, doc, path, state);
59 if (state->stroke_is_set)
60 svg_stroke(ctx, dev, doc, path, state);
61}
62
63/*
64 We use the MAGIC number 0.551915 as a bezier subdivision to approximate
65 a quarter circle arc. The reasons for this can be found here:
66 http://mechanicalexpressions.com/explore/geometric-modeling/circle-spline-approximation.pdf
67*/
68static const float MAGIC_CIRCLE = 0.551915f;
69
70static void approx_circle(fz_context *ctx, fz_path *path, float cx, float cy, float rx, float ry)
71{
72 float mx = rx * MAGIC_CIRCLE;
73 float my = ry * MAGIC_CIRCLE;
74 fz_moveto(ctx, path, cx, cy+ry);
75 fz_curveto(ctx, path, cx + mx, cy + ry, cx + rx, cy + my, cx + rx, cy);
76 fz_curveto(ctx, path, cx + rx, cy - my, cx + mx, cy - ry, cx, cy - ry);
77 fz_curveto(ctx, path, cx - mx, cy - ry, cx - rx, cy - my, cx - rx, cy);
78 fz_curveto(ctx, path, cx - rx, cy + my, cx - mx, cy + ry, cx, cy + ry);
79 fz_closepath(ctx, path);
80}
81
82static void
83svg_run_rect(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state)
84{
85 svg_state local_state = *inherit_state;
86
87 char *x_att = fz_xml_att(node, "x");
88 char *y_att = fz_xml_att(node, "y");
89 char *w_att = fz_xml_att(node, "width");
90 char *h_att = fz_xml_att(node, "height");
91 char *rx_att = fz_xml_att(node, "rx");
92 char *ry_att = fz_xml_att(node, "ry");
93
94 float x = 0;
95 float y = 0;
96 float w = 0;
97 float h = 0;
98 float rx = 0;
99 float ry = 0;
100
101 fz_path *path;
102
103 svg_parse_common(ctx, doc, node, &local_state);
104
105 if (x_att) x = svg_parse_length(x_att, local_state.viewbox_w, local_state.fontsize);
106 if (y_att) y = svg_parse_length(y_att, local_state.viewbox_h, local_state.fontsize);
107 if (w_att) w = svg_parse_length(w_att, local_state.viewbox_w, local_state.fontsize);
108 if (h_att) h = svg_parse_length(h_att, local_state.viewbox_h, local_state.fontsize);
109 if (rx_att) rx = svg_parse_length(rx_att, local_state.viewbox_w, local_state.fontsize);
110 if (ry_att) ry = svg_parse_length(ry_att, local_state.viewbox_h, local_state.fontsize);
111
112 if (rx_att && !ry_att)
113 ry = rx;
114 if (ry_att && !rx_att)
115 rx = ry;
116 if (rx > w * 0.5f)
117 rx = w * 0.5f;
118 if (ry > h * 0.5f)
119 ry = h * 0.5f;
120
121 if (w <= 0 || h <= 0)
122 return;
123
124 path = fz_new_path(ctx);
125 fz_try(ctx)
126 {
127 if (rx == 0 || ry == 0)
128 {
129 fz_moveto(ctx, path, x, y);
130 fz_lineto(ctx, path, x + w, y);
131 fz_lineto(ctx, path, x + w, y + h);
132 fz_lineto(ctx, path, x, y + h);
133 }
134 else
135 {
136 float rxs = rx * MAGIC_CIRCLE;
137 float rys = rx * MAGIC_CIRCLE;
138 fz_moveto(ctx, path, x + w - rx, y);
139 fz_curveto(ctx, path, x + w - rxs, y, x + w, y + rys, x + w, y + ry);
140 fz_lineto(ctx, path, x + w, y + h - ry);
141 fz_curveto(ctx, path, x + w, y + h - rys, x + w - rxs, y + h, x + w - rx, y + h);
142 fz_lineto(ctx, path, x + rx, y + h);
143 fz_curveto(ctx, path, x + rxs, y + h, x, y + h - rys, x, y + h - rx);
144 fz_lineto(ctx, path, x, y + rx);
145 fz_curveto(ctx, path, x, y + rxs, x + rxs, y, x + rx, y);
146 }
147 fz_closepath(ctx, path);
148
149 svg_draw_path(ctx, dev, doc, path, &local_state);
150 }
151 fz_always(ctx)
152 fz_drop_path(ctx, path);
153 fz_catch(ctx)
154 fz_rethrow(ctx);
155
156}
157
158static void
159svg_run_circle(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state)
160{
161 svg_state local_state = *inherit_state;
162
163 char *cx_att = fz_xml_att(node, "cx");
164 char *cy_att = fz_xml_att(node, "cy");
165 char *r_att = fz_xml_att(node, "r");
166
167 float cx = 0;
168 float cy = 0;
169 float r = 0;
170 fz_path *path;
171
172 svg_parse_common(ctx, doc, node, &local_state);
173
174 if (cx_att) cx = svg_parse_length(cx_att, local_state.viewbox_w, local_state.fontsize);
175 if (cy_att) cy = svg_parse_length(cy_att, local_state.viewbox_h, local_state.fontsize);
176 if (r_att) r = svg_parse_length(r_att, local_state.viewbox_size, 12);
177
178 if (r <= 0)
179 return;
180
181 path = fz_new_path(ctx);
182 fz_try(ctx)
183 {
184 approx_circle(ctx, path, cx, cy, r, r);
185 svg_draw_path(ctx, dev, doc, path, &local_state);
186 }
187 fz_always(ctx)
188 fz_drop_path(ctx, path);
189 fz_catch(ctx)
190 fz_rethrow(ctx);
191}
192
193static void
194svg_run_ellipse(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state)
195{
196 svg_state local_state = *inherit_state;
197
198 char *cx_att = fz_xml_att(node, "cx");
199 char *cy_att = fz_xml_att(node, "cy");
200 char *rx_att = fz_xml_att(node, "rx");
201 char *ry_att = fz_xml_att(node, "ry");
202
203 float cx = 0;
204 float cy = 0;
205 float rx = 0;
206 float ry = 0;
207
208 fz_path *path;
209
210 svg_parse_common(ctx, doc, node, &local_state);
211
212 if (cx_att) cx = svg_parse_length(cx_att, local_state.viewbox_w, local_state.fontsize);
213 if (cy_att) cy = svg_parse_length(cy_att, local_state.viewbox_h, local_state.fontsize);
214 if (rx_att) rx = svg_parse_length(rx_att, local_state.viewbox_w, local_state.fontsize);
215 if (ry_att) ry = svg_parse_length(ry_att, local_state.viewbox_h, local_state.fontsize);
216
217 if (rx <= 0 || ry <= 0)
218 return;
219
220 path = fz_new_path(ctx);
221 fz_try(ctx)
222 {
223 approx_circle(ctx, path, cx, cy, rx, ry);
224 svg_draw_path(ctx, dev, doc, path, &local_state);
225 }
226 fz_always(ctx)
227 fz_drop_path(ctx, path);
228 fz_catch(ctx)
229 fz_rethrow(ctx);
230}
231
232static void
233svg_run_line(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state)
234{
235 svg_state local_state = *inherit_state;
236
237 char *x1_att = fz_xml_att(node, "x1");
238 char *y1_att = fz_xml_att(node, "y1");
239 char *x2_att = fz_xml_att(node, "x2");
240 char *y2_att = fz_xml_att(node, "y2");
241
242 float x1 = 0;
243 float y1 = 0;
244 float x2 = 0;
245 float y2 = 0;
246
247 svg_parse_common(ctx, doc, node, &local_state);
248
249 if (x1_att) x1 = svg_parse_length(x1_att, local_state.viewbox_w, local_state.fontsize);
250 if (y1_att) y1 = svg_parse_length(y1_att, local_state.viewbox_h, local_state.fontsize);
251 if (x2_att) x2 = svg_parse_length(x2_att, local_state.viewbox_w, local_state.fontsize);
252 if (y2_att) y2 = svg_parse_length(y2_att, local_state.viewbox_h, local_state.fontsize);
253
254 if (local_state.stroke_is_set)
255 {
256 fz_path *path = fz_new_path(ctx);
257 fz_try(ctx)
258 {
259 fz_moveto(ctx, path, x1, y1);
260 fz_lineto(ctx, path, x2, y2);
261 svg_stroke(ctx, dev, doc, path, &local_state);
262 }
263 fz_always(ctx)
264 fz_drop_path(ctx, path);
265 fz_catch(ctx)
266 fz_rethrow(ctx);
267 }
268}
269
270static fz_path *
271svg_parse_polygon_imp(fz_context *ctx, svg_document *doc, fz_xml *node, int doclose)
272{
273 fz_path *path;
274
275 const char *str = fz_xml_att(node, "points");
276 float number;
277 float args[2];
278 int nargs;
279 int isfirst;
280
281 if (!str)
282 return NULL;
283
284 isfirst = 1;
285 nargs = 0;
286
287 path = fz_new_path(ctx);
288 fz_try(ctx)
289 {
290 while (*str)
291 {
292 while (svg_is_whitespace_or_comma(*str))
293 str ++;
294
295 if (svg_is_digit(*str))
296 {
297 str = svg_lex_number(&number, str);
298 args[nargs++] = number;
299 }
300
301 if (nargs == 2)
302 {
303 if (isfirst)
304 {
305 fz_moveto(ctx, path, args[0], args[1]);
306 isfirst = 0;
307 }
308 else
309 {
310 fz_lineto(ctx, path, args[0], args[1]);
311 }
312 nargs = 0;
313 }
314 }
315 }
316 fz_catch(ctx)
317 {
318 fz_drop_path(ctx, path);
319 fz_rethrow(ctx);
320 }
321
322 return path;
323}
324
325static void
326svg_run_polyline(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state)
327{
328 svg_state local_state = *inherit_state;
329
330 svg_parse_common(ctx, doc, node, &local_state);
331
332 if (local_state.stroke_is_set)
333 {
334 fz_path *path = svg_parse_polygon_imp(ctx, doc, node, 0);
335 fz_try(ctx)
336 svg_stroke(ctx, dev, doc, path, &local_state);
337 fz_always(ctx)
338 fz_drop_path(ctx, path);
339 fz_catch(ctx)
340 fz_rethrow(ctx);
341 }
342}
343
344static void
345svg_run_polygon(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state)
346{
347 svg_state local_state = *inherit_state;
348 fz_path *path;
349
350 svg_parse_common(ctx, doc, node, &local_state);
351
352 path = svg_parse_polygon_imp(ctx, doc, node, 1);
353 fz_try(ctx)
354 svg_draw_path(ctx, dev, doc, path, &local_state);
355 fz_always(ctx)
356 fz_drop_path(ctx, path);
357 fz_catch(ctx)
358 fz_rethrow(ctx);
359}
360
361static void
362svg_add_arc_segment(fz_context *ctx, fz_path *path, fz_matrix mtx, float th0, float th1, int iscw)
363{
364 float t, d;
365 fz_point p;
366
367 while (th1 < th0)
368 th1 += FZ_PI * 2;
369
370 d = FZ_PI / 180; /* 1-degree precision */
371
372 if (iscw)
373 {
374 for (t = th0 + d; t < th1 - d/2; t += d)
375 {
376 p = fz_transform_point_xy(cosf(t), sinf(t), mtx);
377 fz_lineto(ctx, path, p.x, p.y);
378 }
379 }
380 else
381 {
382 th0 += FZ_PI * 2;
383 for (t = th0 - d; t > th1 + d/2; t -= d)
384 {
385 p = fz_transform_point_xy(cosf(t), sinf(t), mtx);
386 fz_lineto(ctx, path, p.x, p.y);
387 }
388 }
389}
390
391static float
392angle_between(const fz_point u, const fz_point v)
393{
394 float det = u.x * v.y - u.y * v.x;
395 float sign = (det < 0 ? -1 : 1);
396 float magu = u.x * u.x + u.y * u.y;
397 float magv = v.x * v.x + v.y * v.y;
398 float udotv = u.x * v.x + u.y * v.y;
399 float t = udotv / (magu * magv);
400 /* guard against rounding errors when near |1| (where acos will return NaN) */
401 if (t < -1) t = -1;
402 if (t > 1) t = 1;
403 return sign * acosf(t);
404}
405
406static void
407svg_add_arc(fz_context *ctx, fz_path *path,
408 float size_x, float size_y, float rotation_angle,
409 int is_large_arc, int is_clockwise,
410 float point_x, float point_y)
411{
412 fz_matrix rotmat, revmat;
413 fz_matrix mtx;
414 fz_point pt;
415 float rx, ry;
416 float x1, y1, x2, y2;
417 float x1t, y1t;
418 float cxt, cyt, cx, cy;
419 float t1, t2, t3;
420 float sign;
421 float th1, dth;
422
423 pt = fz_currentpoint(ctx, path);
424 x1 = pt.x;
425 y1 = pt.y;
426 x2 = point_x;
427 y2 = point_y;
428 rx = size_x;
429 ry = size_y;
430
431 if (is_clockwise != is_large_arc)
432 sign = 1;
433 else
434 sign = -1;
435
436 rotmat = fz_rotate(rotation_angle);
437 revmat = fz_rotate(-rotation_angle);
438
439 /* http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes */
440 /* Conversion from endpoint to center parameterization */
441
442 /* F.6.6.1 -- ensure radii are positive and non-zero */
443 rx = fabsf(rx);
444 ry = fabsf(ry);
445 if (rx < 0.001f || ry < 0.001f || (x1 == x2 && y1 == y2))
446 {
447 fz_lineto(ctx, path, x2, y2);
448 return;
449 }
450
451 /* F.6.5.1 */
452 pt.x = (x1 - x2) / 2;
453 pt.y = (y1 - y2) / 2;
454 pt = fz_transform_vector(pt, revmat);
455 x1t = pt.x;
456 y1t = pt.y;
457
458 /* F.6.6.2 -- ensure radii are large enough */
459 t1 = (x1t * x1t) / (rx * rx) + (y1t * y1t) / (ry * ry);
460 if (t1 > 1)
461 {
462 rx = rx * sqrtf(t1);
463 ry = ry * sqrtf(t1);
464 }
465
466 /* F.6.5.2 */
467 t1 = (rx * rx * ry * ry) - (rx * rx * y1t * y1t) - (ry * ry * x1t * x1t);
468 t2 = (rx * rx * y1t * y1t) + (ry * ry * x1t * x1t);
469 t3 = t1 / t2;
470 /* guard against rounding errors; sqrt of negative numbers is bad for your health */
471 if (t3 < 0) t3 = 0;
472 t3 = sqrtf(t3);
473
474 cxt = sign * t3 * (rx * y1t) / ry;
475 cyt = sign * t3 * -(ry * x1t) / rx;
476
477 /* F.6.5.3 */
478 pt.x = cxt;
479 pt.y = cyt;
480 pt = fz_transform_vector(pt, rotmat);
481 cx = pt.x + (x1 + x2) / 2;
482 cy = pt.y + (y1 + y2) / 2;
483
484 /* F.6.5.4 */
485 {
486 fz_point coord1, coord2, coord3, coord4;
487 coord1.x = 1;
488 coord1.y = 0;
489 coord2.x = (x1t - cxt) / rx;
490 coord2.y = (y1t - cyt) / ry;
491 coord3.x = (x1t - cxt) / rx;
492 coord3.y = (y1t - cyt) / ry;
493 coord4.x = (-x1t - cxt) / rx;
494 coord4.y = (-y1t - cyt) / ry;
495 th1 = angle_between(coord1, coord2);
496 dth = angle_between(coord3, coord4);
497 if (dth < 0 && !is_clockwise)
498 dth += ((FZ_PI / 180) * 360);
499 if (dth > 0 && is_clockwise)
500 dth -= ((FZ_PI / 180) * 360);
501 }
502
503 mtx = fz_pre_scale(fz_pre_rotate(fz_translate(cx, cy), rotation_angle), rx, ry);
504 svg_add_arc_segment(ctx, path, mtx, th1, th1 + dth, is_clockwise);
505
506 fz_lineto(ctx, path, point_x, point_y);
507}
508
509static fz_path *
510svg_parse_path_data(fz_context *ctx, svg_document *doc, const char *str)
511{
512 fz_path *path;
513
514 fz_point p;
515 float x1, y1, x2, y2;
516
517 int cmd;
518 float number;
519 float args[7];
520 int nargs;
521
522 /* saved control point for smooth curves */
523 int reset_smooth = 1;
524 float smooth_x = 0.0f;
525 float smooth_y = 0.0f;
526
527 cmd = 0;
528 nargs = 0;
529
530 path = fz_new_path(ctx);
531 fz_try(ctx)
532 {
533 fz_moveto(ctx, path, 0.0f, 0.0f); /* for the case of opening 'm' */
534
535 while (*str)
536 {
537 while (svg_is_whitespace_or_comma(*str))
538 str ++;
539
540 if (svg_is_digit(*str))
541 {
542 str = svg_lex_number(&number, str);
543 if (nargs == nelem(args))
544 fz_throw(ctx, FZ_ERROR_GENERIC, "stack overflow in path data");
545 args[nargs++] = number;
546 }
547 else if (svg_is_alpha(*str))
548 {
549 if (nargs != 0)
550 fz_throw(ctx, FZ_ERROR_GENERIC, "syntax error in path data (wrong number of parameters to '%c')", cmd);
551 cmd = *str++;
552 }
553 else if (*str == 0)
554 {
555 break;
556 }
557 else
558 {
559 fz_throw(ctx, FZ_ERROR_GENERIC, "syntax error in path data: '%c'", *str);
560 }
561
562 if (reset_smooth)
563 {
564 smooth_x = 0.0f;
565 smooth_y = 0.0f;
566 }
567
568 reset_smooth = 1;
569
570 switch (cmd)
571 {
572 case 'M':
573 if (nargs == 2)
574 {
575 fz_moveto(ctx, path, args[0], args[1]);
576 nargs = 0;
577 cmd = 'L'; /* implicit lineto after */
578 }
579 break;
580
581 case 'm':
582 if (nargs == 2)
583 {
584 p = fz_currentpoint(ctx, path);
585 fz_moveto(ctx, path, p.x + args[0], p.y + args[1]);
586 nargs = 0;
587 cmd = 'l'; /* implicit lineto after */
588 }
589 break;
590
591 case 'Z':
592 case 'z':
593 if (nargs == 0)
594 {
595 fz_closepath(ctx, path);
596 }
597 break;
598
599 case 'L':
600 if (nargs == 2)
601 {
602 fz_lineto(ctx, path, args[0], args[1]);
603 nargs = 0;
604 }
605 break;
606
607 case 'l':
608 if (nargs == 2)
609 {
610 p = fz_currentpoint(ctx, path);
611 fz_lineto(ctx, path, p.x + args[0], p.y + args[1]);
612 nargs = 0;
613 }
614 break;
615
616 case 'H':
617 if (nargs == 1)
618 {
619 p = fz_currentpoint(ctx, path);
620 fz_lineto(ctx, path, args[0], p.y);
621 nargs = 0;
622 }
623 break;
624
625 case 'h':
626 if (nargs == 1)
627 {
628 p = fz_currentpoint(ctx, path);
629 fz_lineto(ctx, path, p.x + args[0], p.y);
630 nargs = 0;
631 }
632 break;
633
634 case 'V':
635 if (nargs == 1)
636 {
637 p = fz_currentpoint(ctx, path);
638 fz_lineto(ctx, path, p.x, args[0]);
639 nargs = 0;
640 }
641 break;
642
643 case 'v':
644 if (nargs == 1)
645 {
646 p = fz_currentpoint(ctx, path);
647 fz_lineto(ctx, path, p.x, p.y + args[0]);
648 nargs = 0;
649 }
650 break;
651
652 case 'C':
653 reset_smooth = 0;
654 if (nargs == 6)
655 {
656 fz_curveto(ctx, path, args[0], args[1], args[2], args[3], args[4], args[5]);
657 smooth_x = args[4] - args[2];
658 smooth_y = args[5] - args[3];
659 nargs = 0;
660 }
661 break;
662
663 case 'c':
664 reset_smooth = 0;
665 if (nargs == 6)
666 {
667 p = fz_currentpoint(ctx, path);
668 fz_curveto(ctx, path,
669 p.x + args[0], p.y + args[1],
670 p.x + args[2], p.y + args[3],
671 p.x + args[4], p.y + args[5]);
672 smooth_x = args[4] - args[2];
673 smooth_y = args[5] - args[3];
674 nargs = 0;
675 }
676 break;
677
678 case 'S':
679 reset_smooth = 0;
680 if (nargs == 4)
681 {
682 p = fz_currentpoint(ctx, path);
683 fz_curveto(ctx, path,
684 p.x + smooth_x, p.y + smooth_y,
685 args[0], args[1],
686 args[2], args[3]);
687 smooth_x = args[2] - args[0];
688 smooth_y = args[3] - args[1];
689 nargs = 0;
690 }
691 break;
692
693 case 's':
694 reset_smooth = 0;
695 if (nargs == 4)
696 {
697 p = fz_currentpoint(ctx, path);
698 fz_curveto(ctx, path,
699 p.x + smooth_x, p.y + smooth_y,
700 p.x + args[0], p.y + args[1],
701 p.x + args[2], p.y + args[3]);
702 smooth_x = args[2] - args[0];
703 smooth_y = args[3] - args[1];
704 nargs = 0;
705 }
706 break;
707
708 case 'Q':
709 reset_smooth = 0;
710 if (nargs == 4)
711 {
712 p = fz_currentpoint(ctx, path);
713 x1 = args[0];
714 y1 = args[1];
715 x2 = args[2];
716 y2 = args[3];
717 fz_curveto(ctx, path,
718 (p.x + 2 * x1) / 3, (p.y + 2 * y1) / 3,
719 (x2 + 2 * x1) / 3, (y2 + 2 * y1) / 3,
720 x2, y2);
721 smooth_x = x2 - x1;
722 smooth_y = y2 - y1;
723 nargs = 0;
724 }
725 break;
726
727 case 'q':
728 reset_smooth = 0;
729 if (nargs == 4)
730 {
731 p = fz_currentpoint(ctx, path);
732 x1 = args[0] + p.x;
733 y1 = args[1] + p.y;
734 x2 = args[2] + p.x;
735 y2 = args[3] + p.y;
736 fz_curveto(ctx, path,
737 (p.x + 2 * x1) / 3, (p.y + 2 * y1) / 3,
738 (x2 + 2 * x1) / 3, (y2 + 2 * y1) / 3,
739 x2, y2);
740 smooth_x = x2 - x1;
741 smooth_y = y2 - y1;
742 nargs = 0;
743 }
744 break;
745
746 case 'T':
747 reset_smooth = 0;
748 if (nargs == 4)
749 {
750 p = fz_currentpoint(ctx, path);
751 x1 = p.x + smooth_x;
752 y1 = p.y + smooth_y;
753 x2 = args[0];
754 y2 = args[1];
755 fz_curveto(ctx, path,
756 (p.x + 2 * x1) / 3, (p.y + 2 * y1) / 3,
757 (x2 + 2 * x1) / 3, (y2 + 2 * y1) / 3,
758 x2, y2);
759 smooth_x = x2 - x1;
760 smooth_y = y2 - y1;
761 nargs = 0;
762 }
763 break;
764
765 case 't':
766 reset_smooth = 0;
767 if (nargs == 4)
768 {
769 p = fz_currentpoint(ctx, path);
770 x1 = p.x + smooth_x;
771 y1 = p.y + smooth_y;
772 x2 = args[0] + p.x;
773 y2 = args[1] + p.y;
774 fz_curveto(ctx, path,
775 (p.x + 2 * x1) / 3, (p.y + 2 * y1) / 3,
776 (x2 + 2 * x1) / 3, (y2 + 2 * y1) / 3,
777 x2, y2);
778 smooth_x = x2 - x1;
779 smooth_y = y2 - y1;
780 nargs = 0;
781 }
782 break;
783
784 case 'A':
785 if (nargs == 7)
786 {
787 svg_add_arc(ctx, path, args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
788 nargs = 0;
789 }
790 break;
791 case 'a':
792 if (nargs == 7)
793 {
794 p = fz_currentpoint(ctx, path);
795 svg_add_arc(ctx, path, args[0], args[1], args[2], args[3], args[4], args[5] + p.x, args[6] + p.y);
796 nargs = 0;
797 }
798 break;
799
800 case 0:
801 if (nargs != 0)
802 fz_throw(ctx, FZ_ERROR_GENERIC, "path data must begin with a command");
803 break;
804
805 default:
806 fz_throw(ctx, FZ_ERROR_GENERIC, "unrecognized command in path data: '%c'", cmd);
807 }
808 }
809 }
810 fz_catch(ctx)
811 {
812 fz_drop_path(ctx, path);
813 fz_rethrow(ctx);
814 }
815
816 return path;
817}
818
819static void
820svg_run_path(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state)
821{
822 svg_state local_state = *inherit_state;
823
824 const char *d_att = fz_xml_att(node, "d");
825 /* unused: char *path_length_att = fz_xml_att(node, "pathLength"); */
826
827 svg_parse_common(ctx, doc, node, &local_state);
828
829 if (d_att)
830 {
831 fz_path *path = svg_parse_path_data(ctx, doc, d_att);
832 fz_try(ctx)
833 svg_draw_path(ctx, dev, doc, path, &local_state);
834 fz_always(ctx)
835 fz_drop_path(ctx, path);
836 fz_catch(ctx)
837 fz_rethrow(ctx);
838 }
839}
840
841/* svg, symbol, image, foreignObject establish new viewports */
842void
843svg_parse_viewport(fz_context *ctx, svg_document *doc, fz_xml *node, svg_state *state)
844{
845 char *w_att = fz_xml_att(node, "width");
846 char *h_att = fz_xml_att(node, "height");
847
848 if (w_att)
849 state->viewport_w = svg_parse_length(w_att, state->viewbox_w, state->fontsize);
850 if (h_att)
851 state->viewport_h = svg_parse_length(h_att, state->viewbox_h, state->fontsize);
852
853}
854
855static void
856svg_lex_viewbox(const char *s, float *x, float *y, float *w, float *h)
857{
858 while (svg_is_whitespace_or_comma(*s)) ++s;
859 if (svg_is_digit(*s)) s = svg_lex_number(x, s);
860 while (svg_is_whitespace_or_comma(*s)) ++s;
861 if (svg_is_digit(*s)) s = svg_lex_number(y, s);
862 while (svg_is_whitespace_or_comma(*s)) ++s;
863 if (svg_is_digit(*s)) s = svg_lex_number(w, s);
864 while (svg_is_whitespace_or_comma(*s)) ++s;
865 if (svg_is_digit(*s)) s = svg_lex_number(h, s);
866}
867
868static int
869svg_parse_preserve_aspect_ratio(const char *att, int *x, int *y)
870{
871 *x = *y = 1;
872 if (strstr(att, "none")) return 0;
873 if (strstr(att, "xMin")) *x = 0;
874 if (strstr(att, "xMid")) *x = 1;
875 if (strstr(att, "xMax")) *x = 2;
876 if (strstr(att, "YMin")) *y = 0;
877 if (strstr(att, "YMid")) *y = 1;
878 if (strstr(att, "YMax")) *y = 2;
879 return 1;
880}
881
882/* svg, symbol, image, foreignObject plus marker, pattern, view can use viewBox to set the transform */
883void
884svg_parse_viewbox(fz_context *ctx, svg_document *doc, fz_xml *node, svg_state *state)
885{
886 char *viewbox_att = fz_xml_att(node, "viewBox");
887 char *preserve_att = fz_xml_att(node, "preserveAspectRatio");
888 if (viewbox_att)
889 {
890 /* scale and translate to fit [minx miny minx+w miny+h] to [0 0 viewport.w viewport.h] */
891 float min_x, min_y, box_w, box_h, sx, sy;
892 int align_x=1, align_y=1, preserve=1;
893 float pad_x=0, pad_y=0;
894
895 svg_lex_viewbox(viewbox_att, &min_x, &min_y, &box_w, &box_h);
896 sx = state->viewport_w / box_w;
897 sy = state->viewport_h / box_h;
898
899 if (preserve_att)
900 preserve = svg_parse_preserve_aspect_ratio(preserve_att, &align_x, &align_y);
901 if (preserve)
902 {
903 sx = sy = fz_min(sx, sy);
904 if (align_x == 1) pad_x = (box_w * sx - state->viewport_w) / 2;
905 if (align_x == 2) pad_x = (box_w * sx - state->viewport_w);
906 if (align_y == 1) pad_y = (box_h * sy - state->viewport_h) / 2;
907 if (align_y == 2) pad_y = (box_h * sy - state->viewport_h);
908 state->transform = fz_concat(fz_translate(-pad_x, -pad_y), state->transform);
909 }
910 state->transform = fz_concat(fz_scale(sx, sy), state->transform);
911 state->transform = fz_concat(fz_translate(-min_x, -min_y), state->transform);
912 state->viewbox_w = box_w;
913 state->viewbox_h = box_h;
914 state->viewbox_size = sqrtf(box_w*box_w + box_h*box_h) / sqrtf(2);
915 }
916}
917
918/* parse transform and presentation attributes */
919void
920svg_parse_common(fz_context *ctx, svg_document *doc, fz_xml *node, svg_state *state)
921{
922 fz_stroke_state *stroke = &state->stroke;
923
924 char *transform_att = fz_xml_att(node, "transform");
925
926 char *font_size_att = fz_xml_att(node, "font-size");
927 // TODO: all font stuff
928
929 char *style_att = fz_xml_att(node, "style");
930
931 // TODO: clip, clip-path, clip-rule
932
933 char *opacity_att = fz_xml_att(node, "opacity");
934
935 char *fill_att = fz_xml_att(node, "fill");
936 char *fill_rule_att = fz_xml_att(node, "fill-rule");
937 char *fill_opacity_att = fz_xml_att(node, "fill-opacity");
938
939 char *stroke_att = fz_xml_att(node, "stroke");
940 char *stroke_opacity_att = fz_xml_att(node, "stroke-opacity");
941 char *stroke_width_att = fz_xml_att(node, "stroke-width");
942 char *stroke_linecap_att = fz_xml_att(node, "stroke-linecap");
943 char *stroke_linejoin_att = fz_xml_att(node, "stroke-linejoin");
944 char *stroke_miterlimit_att = fz_xml_att(node, "stroke-miterlimit");
945 // TODO: stroke-dasharray, stroke-dashoffset
946
947 // TODO: marker, marker-start, marker-mid, marker-end
948
949 // TODO: overflow
950 // TODO: mask
951
952 /* Dirty hack scans of CSS style */
953 if (style_att)
954 {
955 svg_parse_color_from_style(ctx, doc, style_att,
956 &state->fill_is_set, state->fill_color,
957 &state->stroke_is_set, state->stroke_color);
958 }
959
960 if (transform_att)
961 {
962 state->transform = svg_parse_transform(ctx, doc, transform_att, state->transform);
963 }
964
965 if (font_size_att)
966 {
967 state->fontsize = svg_parse_length(font_size_att, state->fontsize, state->fontsize);
968 }
969
970 if (opacity_att)
971 {
972 state->opacity = svg_parse_number(opacity_att, 0, 1, state->opacity);
973 }
974
975 if (fill_att)
976 {
977 if (!strcmp(fill_att, "none"))
978 {
979 state->fill_is_set = 0;
980 }
981 else
982 {
983 state->fill_is_set = 1;
984 svg_parse_color(ctx, doc, fill_att, state->fill_color);
985 }
986 }
987
988 if (fill_opacity_att)
989 state->fill_opacity = svg_parse_number(fill_opacity_att, 0, 1, state->fill_opacity);
990
991 if (fill_rule_att)
992 {
993 if (!strcmp(fill_rule_att, "nonzero"))
994 state->fill_rule = 1;
995 if (!strcmp(fill_rule_att, "evenodd"))
996 state->fill_rule = 0;
997 }
998
999 if (stroke_att)
1000 {
1001 if (!strcmp(stroke_att, "none"))
1002 {
1003 state->stroke_is_set = 0;
1004 }
1005 else
1006 {
1007 state->stroke_is_set = 1;
1008 svg_parse_color(ctx, doc, stroke_att, state->stroke_color);
1009 }
1010 }
1011
1012 if (stroke_opacity_att)
1013 state->stroke_opacity = svg_parse_number(stroke_opacity_att, 0, 1, state->stroke_opacity);
1014
1015 if (stroke_width_att)
1016 {
1017 if (!strcmp(stroke_width_att, "inherit"))
1018 ;
1019 else
1020 stroke->linewidth = svg_parse_length(stroke_width_att, state->viewbox_size, state->fontsize);
1021 }
1022 else
1023 {
1024 stroke->linewidth = 1;
1025 }
1026
1027 if (stroke_linecap_att)
1028 {
1029 if (!strcmp(stroke_linecap_att, "butt"))
1030 stroke->start_cap = FZ_LINECAP_BUTT;
1031 if (!strcmp(stroke_linecap_att, "round"))
1032 stroke->start_cap = FZ_LINECAP_ROUND;
1033 if (!strcmp(stroke_linecap_att, "square"))
1034 stroke->start_cap = FZ_LINECAP_SQUARE;
1035 }
1036 else
1037 {
1038 stroke->start_cap = FZ_LINECAP_BUTT;
1039 }
1040
1041 stroke->dash_cap = stroke->start_cap;
1042 stroke->end_cap = stroke->start_cap;
1043
1044 if (stroke_linejoin_att)
1045 {
1046 if (!strcmp(stroke_linejoin_att, "miter"))
1047 stroke->linejoin = FZ_LINEJOIN_MITER;
1048 if (!strcmp(stroke_linejoin_att, "round"))
1049 stroke->linejoin = FZ_LINEJOIN_ROUND;
1050 if (!strcmp(stroke_linejoin_att, "bevel"))
1051 stroke->linejoin = FZ_LINEJOIN_BEVEL;
1052 }
1053 else
1054 {
1055 stroke->linejoin = FZ_LINEJOIN_MITER;
1056 }
1057
1058 if (stroke_miterlimit_att)
1059 {
1060 if (!strcmp(stroke_miterlimit_att, "inherit"))
1061 ;
1062 else
1063 stroke->miterlimit = svg_parse_length(stroke_miterlimit_att, state->viewbox_size, state->fontsize);
1064 }
1065 else
1066 {
1067 stroke->miterlimit = 4.0f;
1068 }
1069}
1070
1071static void
1072svg_run_svg(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *root, const svg_state *inherit_state)
1073{
1074 svg_state local_state = *inherit_state;
1075 fz_xml *node;
1076
1077 char *w_att = fz_xml_att(root, "width");
1078 char *h_att = fz_xml_att(root, "height");
1079 char *viewbox_att = fz_xml_att(root, "viewBox");
1080
1081 /* get default viewport from viewBox if width and/or height is missing */
1082 if (viewbox_att && (!w_att || !h_att))
1083 {
1084 float x, y;
1085 svg_lex_viewbox(viewbox_att, &x, &y, &local_state.viewbox_w, &local_state.viewbox_h);
1086 if (!w_att) local_state.viewport_w = local_state.viewbox_w;
1087 if (!h_att) local_state.viewport_h = local_state.viewbox_h;
1088 }
1089
1090 svg_parse_viewport(ctx, doc, root, &local_state);
1091 svg_parse_viewbox(ctx, doc, root, &local_state);
1092 svg_parse_common(ctx, doc, root, &local_state);
1093
1094 for (node = fz_xml_down(root); node; node = fz_xml_next(node))
1095 svg_run_element(ctx, dev, doc, node, &local_state);
1096}
1097
1098static void
1099svg_run_g(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *root, const svg_state *inherit_state)
1100{
1101 svg_state local_state = *inherit_state;
1102 fz_xml *node;
1103
1104 svg_parse_common(ctx, doc, root, &local_state);
1105
1106 for (node = fz_xml_down(root); node; node = fz_xml_next(node))
1107 svg_run_element(ctx, dev, doc, node, &local_state);
1108}
1109
1110static void
1111svg_run_use_symbol(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *use, fz_xml *symbol, const svg_state *inherit_state)
1112{
1113 svg_state local_state = *inherit_state;
1114 fz_xml *node;
1115
1116 svg_parse_viewport(ctx, doc, use, &local_state);
1117 svg_parse_viewbox(ctx, doc, use, &local_state);
1118 svg_parse_common(ctx, doc, use, &local_state);
1119
1120 for (node = fz_xml_down(symbol); node; node = fz_xml_next(node))
1121 svg_run_element(ctx, dev, doc, node, &local_state);
1122}
1123
1124static void
1125svg_run_use(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *root, const svg_state *inherit_state)
1126{
1127 svg_state local_state = *inherit_state;
1128
1129 char *xlink_href_att = fz_xml_att(root, "xlink:href");
1130 char *x_att = fz_xml_att(root, "x");
1131 char *y_att = fz_xml_att(root, "y");
1132
1133 float x = 0;
1134 float y = 0;
1135
1136 if (++local_state.use_depth > MAX_USE_DEPTH)
1137 {
1138 fz_warn(ctx, "svg: too much recursion");
1139 return;
1140 }
1141
1142 svg_parse_common(ctx, doc, root, &local_state);
1143 if (x_att) x = svg_parse_length(x_att, local_state.viewbox_w, local_state.fontsize);
1144 if (y_att) y = svg_parse_length(y_att, local_state.viewbox_h, local_state.fontsize);
1145
1146 local_state.transform = fz_concat(fz_translate(x, y), local_state.transform);
1147
1148 if (xlink_href_att && xlink_href_att[0] == '#')
1149 {
1150 fz_xml *linked = fz_tree_lookup(ctx, doc->idmap, xlink_href_att + 1);
1151 if (linked)
1152 {
1153 if (fz_xml_is_tag(linked, "symbol"))
1154 svg_run_use_symbol(ctx, dev, doc, root, linked, &local_state);
1155 else
1156 svg_run_element(ctx, dev, doc, linked, &local_state);
1157 return;
1158 }
1159 }
1160
1161 fz_warn(ctx, "svg: cannot find linked symbol");
1162}
1163
1164static void
1165svg_run_image(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *root, const svg_state *inherit_state)
1166{
1167 svg_state local_state = *inherit_state;
1168 float x=0, y=0, w=0, h=0;
1169 const char *data;
1170
1171 static const char *jpeg_uri = "data:image/jpeg;base64,";
1172 static const char *png_uri = "data:image/png;base64,";
1173
1174 char *href_att = fz_xml_att(root, "xlink:href");
1175 char *x_att = fz_xml_att(root, "x");
1176 char *y_att = fz_xml_att(root, "y");
1177 char *w_att = fz_xml_att(root, "width");
1178 char *h_att = fz_xml_att(root, "height");
1179
1180 svg_parse_common(ctx, doc, root, &local_state);
1181 if (x_att) x = svg_parse_length(x_att, local_state.viewbox_w, local_state.fontsize);
1182 if (y_att) y = svg_parse_length(y_att, local_state.viewbox_h, local_state.fontsize);
1183 if (w_att) w = svg_parse_length(w_att, local_state.viewbox_w, local_state.fontsize);
1184 if (h_att) h = svg_parse_length(h_att, local_state.viewbox_h, local_state.fontsize);
1185
1186 if (w <= 0 || h <= 0)
1187 return;
1188
1189 if (!href_att)
1190 return;
1191
1192 local_state.transform = fz_concat(fz_translate(x, y), local_state.transform);
1193 local_state.transform = fz_concat(fz_scale(w, h), local_state.transform);
1194
1195 if (!strncmp(href_att, jpeg_uri, strlen(jpeg_uri)))
1196 data = href_att + strlen(jpeg_uri);
1197 else if (!strncmp(href_att, png_uri, strlen(png_uri)))
1198 data = href_att + strlen(png_uri);
1199 else
1200 data = NULL;
1201 if (data)
1202 {
1203 fz_image *img = NULL;
1204 fz_buffer *buf;
1205
1206 fz_var(img);
1207
1208 buf = fz_new_buffer_from_base64(ctx, data, 0);
1209 fz_try(ctx)
1210 {
1211 img = fz_new_image_from_buffer(ctx, buf);
1212 fz_fill_image(ctx, dev, img, local_state.transform, 1, fz_default_color_params);
1213 }
1214 fz_always(ctx)
1215 {
1216 fz_drop_buffer(ctx, buf);
1217 fz_drop_image(ctx, img);
1218 }
1219 fz_catch(ctx)
1220 fz_warn(ctx, "svg: ignoring embedded image '%s'", href_att);
1221 }
1222 else if (doc->zip)
1223 {
1224 char path[2048];
1225 fz_buffer *buf = NULL;
1226 fz_image *img = NULL;
1227
1228 fz_var(buf);
1229 fz_var(img);
1230
1231 fz_strlcpy(path, doc->base_uri, sizeof path);
1232 fz_strlcat(path, "/", sizeof path);
1233 fz_strlcat(path, href_att, sizeof path);
1234 fz_urldecode(path);
1235 fz_cleanname(path);
1236
1237 fz_try(ctx)
1238 {
1239 buf = fz_read_archive_entry(ctx, doc->zip, path);
1240 img = fz_new_image_from_buffer(ctx, buf);
1241 fz_fill_image(ctx, dev, img, local_state.transform, 1, fz_default_color_params);
1242 }
1243 fz_always(ctx)
1244 {
1245 fz_drop_buffer(ctx, buf);
1246 fz_drop_image(ctx, img);
1247 }
1248 fz_catch(ctx)
1249 fz_warn(ctx, "svg: ignoring external image '%s'", href_att);
1250 }
1251 else
1252 {
1253 fz_warn(ctx, "svg: ignoring external image '%s'", href_att);
1254 }
1255}
1256
1257static void
1258svg_run_element(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *root, const svg_state *state)
1259{
1260 if (fz_xml_is_tag(root, "svg"))
1261 svg_run_svg(ctx, dev, doc, root, state);
1262
1263 else if (fz_xml_is_tag(root, "g"))
1264 svg_run_g(ctx, dev, doc, root, state);
1265
1266 else if (fz_xml_is_tag(root, "title"))
1267 ;
1268 else if (fz_xml_is_tag(root, "desc"))
1269 ;
1270
1271 else if (fz_xml_is_tag(root, "defs"))
1272 ;
1273 else if (fz_xml_is_tag(root, "symbol"))
1274 ;
1275
1276 else if (fz_xml_is_tag(root, "use"))
1277 svg_run_use(ctx, dev, doc, root, state);
1278
1279 else if (fz_xml_is_tag(root, "path"))
1280 svg_run_path(ctx, dev, doc, root, state);
1281 else if (fz_xml_is_tag(root, "rect"))
1282 svg_run_rect(ctx, dev, doc, root, state);
1283 else if (fz_xml_is_tag(root, "circle"))
1284 svg_run_circle(ctx, dev, doc, root, state);
1285 else if (fz_xml_is_tag(root, "ellipse"))
1286 svg_run_ellipse(ctx, dev, doc, root, state);
1287 else if (fz_xml_is_tag(root, "line"))
1288 svg_run_line(ctx, dev, doc, root, state);
1289 else if (fz_xml_is_tag(root, "polyline"))
1290 svg_run_polyline(ctx, dev, doc, root, state);
1291 else if (fz_xml_is_tag(root, "polygon"))
1292 svg_run_polygon(ctx, dev, doc, root, state);
1293
1294 else if (fz_xml_is_tag(root, "image"))
1295 svg_run_image(ctx, dev, doc, root, state);
1296
1297#if 0
1298 else if (fz_xml_is_tag(root, "text"))
1299 svg_run_text(ctx, dev, doc, root);
1300 else if (fz_xml_is_tag(root, "tspan"))
1301 svg_run_text_span(ctx, dev, doc, root);
1302 else if (fz_xml_is_tag(root, "tref"))
1303 svg_run_text_ref(ctx, dev, doc, root);
1304 else if (fz_xml_is_tag(root, "textPath"))
1305 svg_run_text_path(ctx, dev, doc, root);
1306#endif
1307
1308 else
1309 {
1310 /* ignore unrecognized tags */
1311 }
1312}
1313
1314void
1315svg_parse_document_bounds(fz_context *ctx, svg_document *doc, fz_xml *root)
1316{
1317 char *version_att;
1318 char *w_att;
1319 char *h_att;
1320 char *viewbox_att;
1321 int version;
1322
1323 if (!fz_xml_is_tag(root, "svg"))
1324 fz_throw(ctx, FZ_ERROR_GENERIC, "expected svg element (found %s)", fz_xml_tag(root));
1325
1326 version_att = fz_xml_att(root, "version");
1327 w_att = fz_xml_att(root, "width");
1328 h_att = fz_xml_att(root, "height");
1329 viewbox_att = fz_xml_att(root, "viewBox");
1330
1331 version = 10;
1332 if (version_att)
1333 version = fz_atof(version_att) * 10;
1334
1335 if (version > 12)
1336 fz_warn(ctx, "svg document version is newer than we support");
1337
1338 /* If no width or height attributes, then guess from the viewbox */
1339 if (w_att == NULL && h_att == NULL && viewbox_att != NULL)
1340 {
1341 float min_x, min_y, box_w, box_h;
1342 svg_lex_viewbox(viewbox_att, &min_x, &min_y, &box_w, &box_h);
1343 doc->width = box_w;
1344 doc->height = box_h;
1345 }
1346 else
1347 {
1348 doc->width = DEF_WIDTH;
1349 if (w_att)
1350 doc->width = svg_parse_length(w_att, doc->width, DEF_FONTSIZE);
1351
1352 doc->height = DEF_HEIGHT;
1353 if (h_att)
1354 doc->height = svg_parse_length(h_att, doc->height, DEF_FONTSIZE);
1355 }
1356}
1357
1358void
1359svg_run_document(fz_context *ctx, svg_document *doc, fz_xml *root, fz_device *dev, fz_matrix ctm)
1360{
1361 svg_state state;
1362
1363 svg_parse_document_bounds(ctx, doc, root);
1364
1365 /* Initial graphics state */
1366 state.transform = ctm;
1367 state.stroke = fz_default_stroke_state;
1368 state.use_depth = 0;
1369
1370 state.viewport_w = DEF_WIDTH;
1371 state.viewport_h = DEF_HEIGHT;
1372
1373 state.viewbox_w = DEF_WIDTH;
1374 state.viewbox_h = DEF_HEIGHT;
1375 state.viewbox_size = sqrtf(DEF_WIDTH*DEF_WIDTH + DEF_HEIGHT*DEF_HEIGHT) / sqrtf(2);
1376
1377 state.fontsize = 12;
1378
1379 state.opacity = 1;
1380
1381 state.fill_rule = 0;
1382
1383 state.fill_is_set = 1;
1384 state.fill_color[0] = 0;
1385 state.fill_color[1] = 0;
1386 state.fill_color[2] = 0;
1387 state.fill_opacity = 1;
1388
1389 state.stroke_is_set = 0;
1390 state.stroke_color[0] = 0;
1391 state.stroke_color[1] = 0;
1392 state.stroke_color[2] = 0;
1393 state.stroke_opacity = 1;
1394
1395 svg_run_svg(ctx, dev, doc, root, &state);
1396}
1397