comparison mupdf-source/source/svg/svg-run.c @ 2:b50eed0cc0ef upstream

ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4. The directory name has changed: no version number in the expanded directory now.
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 15 Sep 2025 11:43:07 +0200
parents
children
comparison
equal deleted inserted replaced
1:1d09e1dec1d9 2:b50eed0cc0ef
1 // Copyright (C) 2004-2025 Artifex Software, Inc.
2 //
3 // This file is part of MuPDF.
4 //
5 // MuPDF is free software: you can redistribute it and/or modify it under the
6 // terms of the GNU Affero General Public License as published by the Free
7 // Software Foundation, either version 3 of the License, or (at your option)
8 // any later version.
9 //
10 // MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY
11 // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
13 // details.
14 //
15 // You should have received a copy of the GNU Affero General Public License
16 // along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html>
17 //
18 // Alternative licensing terms are available from the licensor.
19 // For commercial licensing, see <https://www.artifex.com/> or contact
20 // Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco,
21 // CA 94129, USA, for further information.
22
23 #include "mupdf/fitz.h"
24 #include "svg-imp.h"
25
26 #include <string.h>
27 #include <math.h>
28
29 /* default page size */
30 #define DEF_WIDTH 612
31 #define DEF_HEIGHT 792
32 #define DEF_FONTSIZE 12
33
34 #define MAX_USE_DEPTH 100
35
36 typedef struct svg_state
37 {
38 fz_matrix transform;
39 fz_stroke_state *stroke;
40 int use_depth;
41
42 float viewport_w, viewport_h;
43 float viewbox_w, viewbox_h, viewbox_size;
44 float fontsize;
45
46 float opacity;
47
48 int fill_rule;
49 int fill_is_set;
50 float fill_color[3];
51 float fill_opacity;
52
53 int stroke_is_set;
54 float stroke_color[3];
55 float stroke_opacity;
56
57 const char *font_family;
58 int is_bold;
59 int is_italic;
60 int text_anchor;
61 } svg_state;
62
63 static void svg_parse_common(fz_context *ctx, svg_document *doc, fz_xml *node, svg_state *state);
64 static void svg_run_element(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *root, const svg_state *state);
65
66 void svg_begin_state(fz_context *ctx, svg_state *child, const svg_state *parent)
67 {
68 memcpy(child, parent, sizeof(svg_state));
69 child->stroke = fz_clone_stroke_state(ctx, parent->stroke);
70 }
71
72 void svg_end_state(fz_context *ctx, svg_state *child)
73 {
74 fz_drop_stroke_state(ctx, child->stroke);
75 }
76
77 static void svg_fill(fz_context *ctx, fz_device *dev, svg_document *doc, fz_path *path, svg_state *state)
78 {
79 float opacity = state->opacity * state->fill_opacity;
80 if (path)
81 fz_fill_path(ctx, dev, path, state->fill_rule, state->transform, fz_device_rgb(ctx), state->fill_color, opacity, fz_default_color_params);
82 }
83
84 static void svg_stroke(fz_context *ctx, fz_device *dev, svg_document *doc, fz_path *path, svg_state *state)
85 {
86 float opacity = state->opacity * state->stroke_opacity;
87 if (path)
88 fz_stroke_path(ctx, dev, path, state->stroke, state->transform, fz_device_rgb(ctx), state->stroke_color, opacity, fz_default_color_params);
89 }
90
91 static void svg_draw_path(fz_context *ctx, fz_device *dev, svg_document *doc, fz_path *path, svg_state *state)
92 {
93 if (state->fill_is_set)
94 svg_fill(ctx, dev, doc, path, state);
95 if (state->stroke_is_set)
96 svg_stroke(ctx, dev, doc, path, state);
97 }
98
99 /*
100 We use the MAGIC number 0.551915 as a bezier subdivision to approximate
101 a quarter circle arc. The reasons for this can be found here:
102 http://mechanicalexpressions.com/explore/geometric-modeling/circle-spline-approximation.pdf
103 */
104 static const float MAGIC_CIRCLE = 0.551915f;
105
106 static void approx_circle(fz_context *ctx, fz_path *path, float cx, float cy, float rx, float ry)
107 {
108 float mx = rx * MAGIC_CIRCLE;
109 float my = ry * MAGIC_CIRCLE;
110 fz_moveto(ctx, path, cx, cy+ry);
111 fz_curveto(ctx, path, cx + mx, cy + ry, cx + rx, cy + my, cx + rx, cy);
112 fz_curveto(ctx, path, cx + rx, cy - my, cx + mx, cy - ry, cx, cy - ry);
113 fz_curveto(ctx, path, cx - mx, cy - ry, cx - rx, cy - my, cx - rx, cy);
114 fz_curveto(ctx, path, cx - rx, cy + my, cx - mx, cy + ry, cx, cy + ry);
115 fz_closepath(ctx, path);
116 }
117
118 static void
119 svg_run_rect(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state)
120 {
121 svg_state local_state;
122
123 char *x_att = fz_xml_att(node, "x");
124 char *y_att = fz_xml_att(node, "y");
125 char *w_att = fz_xml_att(node, "width");
126 char *h_att = fz_xml_att(node, "height");
127 char *rx_att = fz_xml_att(node, "rx");
128 char *ry_att = fz_xml_att(node, "ry");
129
130 float x = 0;
131 float y = 0;
132 float w = 0;
133 float h = 0;
134 float rx = 0;
135 float ry = 0;
136
137 fz_path *path = NULL;
138
139 fz_var(path);
140
141 fz_try(ctx)
142 {
143 svg_begin_state(ctx, &local_state, inherit_state);
144 svg_parse_common(ctx, doc, node, &local_state);
145
146 if (x_att) x = svg_parse_length(x_att, local_state.viewbox_w, local_state.fontsize);
147 if (y_att) y = svg_parse_length(y_att, local_state.viewbox_h, local_state.fontsize);
148 if (w_att) w = svg_parse_length(w_att, local_state.viewbox_w, local_state.fontsize);
149 if (h_att) h = svg_parse_length(h_att, local_state.viewbox_h, local_state.fontsize);
150 if (rx_att) rx = svg_parse_length(rx_att, local_state.viewbox_w, local_state.fontsize);
151 if (ry_att) ry = svg_parse_length(ry_att, local_state.viewbox_h, local_state.fontsize);
152
153 if (rx_att && !ry_att)
154 ry = rx;
155 if (ry_att && !rx_att)
156 rx = ry;
157 if (rx > w * 0.5f)
158 rx = w * 0.5f;
159 if (ry > h * 0.5f)
160 ry = h * 0.5f;
161
162 if (w <= 0 || h <= 0)
163 return;
164
165 path = fz_new_path(ctx);
166 if (rx == 0 || ry == 0)
167 {
168 fz_moveto(ctx, path, x, y);
169 fz_lineto(ctx, path, x + w, y);
170 fz_lineto(ctx, path, x + w, y + h);
171 fz_lineto(ctx, path, x, y + h);
172 }
173 else
174 {
175 float rxs = rx * MAGIC_CIRCLE;
176 float rys = rx * MAGIC_CIRCLE;
177 fz_moveto(ctx, path, x + w - rx, y);
178 fz_curveto(ctx, path, x + w - rxs, y, x + w, y + rys, x + w, y + ry);
179 fz_lineto(ctx, path, x + w, y + h - ry);
180 fz_curveto(ctx, path, x + w, y + h - rys, x + w - rxs, y + h, x + w - rx, y + h);
181 fz_lineto(ctx, path, x + rx, y + h);
182 fz_curveto(ctx, path, x + rxs, y + h, x, y + h - rys, x, y + h - rx);
183 fz_lineto(ctx, path, x, y + rx);
184 fz_curveto(ctx, path, x, y + rxs, x + rxs, y, x + rx, y);
185 }
186 fz_closepath(ctx, path);
187
188 svg_draw_path(ctx, dev, doc, path, &local_state);
189 }
190 fz_always(ctx)
191 {
192 fz_drop_path(ctx, path);
193 svg_end_state(ctx, &local_state);
194 }
195 fz_catch(ctx)
196 fz_rethrow(ctx);
197
198 }
199
200 static void
201 svg_run_circle(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state)
202 {
203 svg_state local_state;
204
205 char *cx_att = fz_xml_att(node, "cx");
206 char *cy_att = fz_xml_att(node, "cy");
207 char *r_att = fz_xml_att(node, "r");
208
209 float cx = 0;
210 float cy = 0;
211 float r = 0;
212 fz_path *path = NULL;
213
214 fz_var(path);
215
216 fz_try(ctx)
217 {
218 svg_begin_state(ctx, &local_state, inherit_state);
219 svg_parse_common(ctx, doc, node, &local_state);
220
221 if (cx_att) cx = svg_parse_length(cx_att, local_state.viewbox_w, local_state.fontsize);
222 if (cy_att) cy = svg_parse_length(cy_att, local_state.viewbox_h, local_state.fontsize);
223 if (r_att) r = svg_parse_length(r_att, local_state.viewbox_size, 12);
224
225 if (r > 0)
226 {
227 path = fz_new_path(ctx);
228 approx_circle(ctx, path, cx, cy, r, r);
229 svg_draw_path(ctx, dev, doc, path, &local_state);
230 }
231 }
232 fz_always(ctx)
233 {
234 fz_drop_path(ctx, path);
235 svg_end_state(ctx, &local_state);
236 }
237 fz_catch(ctx)
238 fz_rethrow(ctx);
239
240 }
241
242 static void
243 svg_run_ellipse(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state)
244 {
245 svg_state local_state;
246
247 char *cx_att = fz_xml_att(node, "cx");
248 char *cy_att = fz_xml_att(node, "cy");
249 char *rx_att = fz_xml_att(node, "rx");
250 char *ry_att = fz_xml_att(node, "ry");
251
252 float cx = 0;
253 float cy = 0;
254 float rx = 0;
255 float ry = 0;
256
257 fz_path *path = NULL;
258
259 fz_var(path);
260
261 fz_try(ctx)
262 {
263 svg_begin_state(ctx, &local_state, inherit_state);
264 svg_parse_common(ctx, doc, node, &local_state);
265
266 if (cx_att) cx = svg_parse_length(cx_att, local_state.viewbox_w, local_state.fontsize);
267 if (cy_att) cy = svg_parse_length(cy_att, local_state.viewbox_h, local_state.fontsize);
268 if (rx_att) rx = svg_parse_length(rx_att, local_state.viewbox_w, local_state.fontsize);
269 if (ry_att) ry = svg_parse_length(ry_att, local_state.viewbox_h, local_state.fontsize);
270
271 if (rx > 0 && ry > 0)
272 {
273 path = fz_new_path(ctx);
274 approx_circle(ctx, path, cx, cy, rx, ry);
275 svg_draw_path(ctx, dev, doc, path, &local_state);
276 }
277 }
278 fz_always(ctx)
279 {
280 fz_drop_path(ctx, path);
281 svg_end_state(ctx, &local_state);
282 }
283 fz_catch(ctx)
284 fz_rethrow(ctx);
285 }
286
287 static void
288 svg_run_line(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state)
289 {
290 svg_state local_state;
291 fz_path *path = NULL;
292
293 char *x1_att = fz_xml_att(node, "x1");
294 char *y1_att = fz_xml_att(node, "y1");
295 char *x2_att = fz_xml_att(node, "x2");
296 char *y2_att = fz_xml_att(node, "y2");
297
298 float x1 = 0;
299 float y1 = 0;
300 float x2 = 0;
301 float y2 = 0;
302
303 fz_var(path);
304
305 fz_try(ctx)
306 {
307 svg_begin_state(ctx, &local_state, inherit_state);
308 svg_parse_common(ctx, doc, node, &local_state);
309
310 if (x1_att) x1 = svg_parse_length(x1_att, local_state.viewbox_w, local_state.fontsize);
311 if (y1_att) y1 = svg_parse_length(y1_att, local_state.viewbox_h, local_state.fontsize);
312 if (x2_att) x2 = svg_parse_length(x2_att, local_state.viewbox_w, local_state.fontsize);
313 if (y2_att) y2 = svg_parse_length(y2_att, local_state.viewbox_h, local_state.fontsize);
314
315 if (local_state.stroke_is_set)
316 {
317 path = fz_new_path(ctx);
318 fz_moveto(ctx, path, x1, y1);
319 fz_lineto(ctx, path, x2, y2);
320 svg_stroke(ctx, dev, doc, path, &local_state);
321 }
322 }
323 fz_always(ctx)
324 {
325 fz_drop_path(ctx, path);
326 svg_end_state(ctx, &local_state);
327 }
328 fz_catch(ctx)
329 fz_rethrow(ctx);
330 }
331
332 static fz_path *
333 svg_parse_polygon_imp(fz_context *ctx, svg_document *doc, fz_xml *node, int doclose)
334 {
335 fz_path *path;
336
337 const char *str = fz_xml_att(node, "points");
338 float number;
339 float args[2];
340 int nargs;
341 int isfirst;
342
343 if (!str)
344 return NULL;
345
346 isfirst = 1;
347 nargs = 0;
348
349 path = fz_new_path(ctx);
350 fz_try(ctx)
351 {
352 while (*str)
353 {
354 while (svg_is_whitespace_or_comma(*str))
355 str ++;
356
357 if (svg_is_digit(*str))
358 {
359 str = svg_lex_number(&number, str);
360 args[nargs++] = number;
361 }
362
363 if (nargs == 2)
364 {
365 if (isfirst)
366 {
367 fz_moveto(ctx, path, args[0], args[1]);
368 isfirst = 0;
369 }
370 else
371 {
372 fz_lineto(ctx, path, args[0], args[1]);
373 }
374 nargs = 0;
375 }
376 }
377 }
378 fz_catch(ctx)
379 {
380 fz_drop_path(ctx, path);
381 fz_rethrow(ctx);
382 }
383
384 return path;
385 }
386
387 static void
388 svg_run_polyline(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state)
389 {
390 svg_state local_state;
391 fz_path *path = NULL;
392
393 fz_var(path);
394
395 fz_try(ctx)
396 {
397 svg_begin_state(ctx, &local_state, inherit_state);
398 svg_parse_common(ctx, doc, node, &local_state);
399
400 if (local_state.stroke_is_set)
401 {
402 path = svg_parse_polygon_imp(ctx, doc, node, 0);
403 svg_stroke(ctx, dev, doc, path, &local_state);
404 }
405 }
406 fz_always(ctx)
407 {
408 fz_drop_path(ctx, path);
409 svg_end_state(ctx, &local_state);
410 }
411 fz_catch(ctx)
412 fz_rethrow(ctx);
413 }
414
415 static void
416 svg_run_polygon(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state)
417 {
418 svg_state local_state;
419 fz_path *path = NULL;
420
421 fz_var(path);
422
423 fz_try(ctx)
424 {
425 svg_begin_state(ctx, &local_state, inherit_state);
426 svg_parse_common(ctx, doc, node, &local_state);
427
428 path = svg_parse_polygon_imp(ctx, doc, node, 1);
429 svg_draw_path(ctx, dev, doc, path, &local_state);
430 }
431 fz_always(ctx)
432 {
433 fz_drop_path(ctx, path);
434 svg_end_state(ctx, &local_state);
435 }
436 fz_catch(ctx)
437 fz_rethrow(ctx);
438 }
439
440 static void
441 svg_add_arc_segment(fz_context *ctx, fz_path *path, fz_matrix mtx, float th0, float th1, int iscw)
442 {
443 float t, d;
444 fz_point p;
445
446 while (th1 < th0)
447 th1 += FZ_PI * 2;
448
449 d = FZ_PI / 180; /* 1-degree precision */
450
451 if (iscw)
452 {
453 for (t = th0 + d; t < th1 - d/2; t += d)
454 {
455 p = fz_transform_point_xy(cosf(t), sinf(t), mtx);
456 fz_lineto(ctx, path, p.x, p.y);
457 }
458 }
459 else
460 {
461 th0 += FZ_PI * 2;
462 for (t = th0 - d; t > th1 + d/2; t -= d)
463 {
464 p = fz_transform_point_xy(cosf(t), sinf(t), mtx);
465 fz_lineto(ctx, path, p.x, p.y);
466 }
467 }
468 }
469
470 static float
471 angle_between(const fz_point u, const fz_point v)
472 {
473 float det = u.x * v.y - u.y * v.x;
474 float sign = (det < 0 ? -1 : 1);
475 float magu = u.x * u.x + u.y * u.y;
476 float magv = v.x * v.x + v.y * v.y;
477 float udotv = u.x * v.x + u.y * v.y;
478 float t = udotv / (magu * magv);
479 /* guard against rounding errors when near |1| (where acos will return NaN) */
480 if (t < -1) t = -1;
481 if (t > 1) t = 1;
482 return sign * acosf(t);
483 }
484
485 static void
486 svg_add_arc(fz_context *ctx, fz_path *path,
487 float size_x, float size_y, float rotation_angle,
488 int is_large_arc, int is_clockwise,
489 float point_x, float point_y)
490 {
491 fz_matrix rotmat, revmat;
492 fz_matrix mtx;
493 fz_point pt;
494 float rx, ry;
495 float x1, y1, x2, y2;
496 float x1t, y1t;
497 float cxt, cyt, cx, cy;
498 float t1, t2, t3;
499 float sign;
500 float th1, dth;
501
502 pt = fz_currentpoint(ctx, path);
503 x1 = pt.x;
504 y1 = pt.y;
505 x2 = point_x;
506 y2 = point_y;
507 rx = size_x;
508 ry = size_y;
509
510 if (is_clockwise != is_large_arc)
511 sign = 1;
512 else
513 sign = -1;
514
515 rotmat = fz_rotate(rotation_angle);
516 revmat = fz_rotate(-rotation_angle);
517
518 /* http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes */
519 /* Conversion from endpoint to center parameterization */
520
521 /* F.6.6.1 -- ensure radii are positive and non-zero */
522 rx = fabsf(rx);
523 ry = fabsf(ry);
524 if (rx < 0.001f || ry < 0.001f || (x1 == x2 && y1 == y2))
525 {
526 fz_lineto(ctx, path, x2, y2);
527 return;
528 }
529
530 /* F.6.5.1 */
531 pt.x = (x1 - x2) / 2;
532 pt.y = (y1 - y2) / 2;
533 pt = fz_transform_vector(pt, revmat);
534 x1t = pt.x;
535 y1t = pt.y;
536
537 /* F.6.6.2 -- ensure radii are large enough */
538 t1 = (x1t * x1t) / (rx * rx) + (y1t * y1t) / (ry * ry);
539 if (t1 > 1)
540 {
541 rx = rx * sqrtf(t1);
542 ry = ry * sqrtf(t1);
543 }
544
545 /* F.6.5.2 */
546 t1 = (rx * rx * ry * ry) - (rx * rx * y1t * y1t) - (ry * ry * x1t * x1t);
547 t2 = (rx * rx * y1t * y1t) + (ry * ry * x1t * x1t);
548 t3 = t1 / t2;
549 /* guard against rounding errors; sqrt of negative numbers is bad for your health */
550 if (t3 < 0) t3 = 0;
551 t3 = sqrtf(t3);
552
553 cxt = sign * t3 * (rx * y1t) / ry;
554 cyt = sign * t3 * -(ry * x1t) / rx;
555
556 /* F.6.5.3 */
557 pt.x = cxt;
558 pt.y = cyt;
559 pt = fz_transform_vector(pt, rotmat);
560 cx = pt.x + (x1 + x2) / 2;
561 cy = pt.y + (y1 + y2) / 2;
562
563 /* F.6.5.4 */
564 {
565 fz_point coord1, coord2, coord3, coord4;
566 coord1.x = 1;
567 coord1.y = 0;
568 coord2.x = (x1t - cxt) / rx;
569 coord2.y = (y1t - cyt) / ry;
570 coord3.x = (x1t - cxt) / rx;
571 coord3.y = (y1t - cyt) / ry;
572 coord4.x = (-x1t - cxt) / rx;
573 coord4.y = (-y1t - cyt) / ry;
574 th1 = angle_between(coord1, coord2);
575 dth = angle_between(coord3, coord4);
576 if (dth < 0 && !is_clockwise)
577 dth += ((FZ_PI / 180) * 360);
578 if (dth > 0 && is_clockwise)
579 dth -= ((FZ_PI / 180) * 360);
580 }
581
582 mtx = fz_pre_scale(fz_pre_rotate(fz_translate(cx, cy), rotation_angle), rx, ry);
583 svg_add_arc_segment(ctx, path, mtx, th1, th1 + dth, is_clockwise);
584
585 fz_lineto(ctx, path, point_x, point_y);
586 }
587
588 static void
589 svg_parse_path_data(fz_context *ctx, fz_path *path, const char *str)
590 {
591 fz_point p;
592 float x1, y1, x2, y2;
593
594 int cmd;
595 float number;
596 float args[7];
597 int nargs;
598
599 /* saved control point for smooth curves */
600 int reset_smooth = 1;
601 float smooth_x = 0.0f;
602 float smooth_y = 0.0f;
603
604 cmd = 0;
605 nargs = 0;
606
607 fz_moveto(ctx, path, 0.0f, 0.0f); /* for the case of opening 'm' */
608
609 while (*str)
610 {
611 while (svg_is_whitespace_or_comma(*str))
612 str ++;
613
614 /* arcto flag arguments are 1-character 0 or 1 */
615 if ((cmd == 'a' || cmd == 'A') && (nargs == 3 || nargs == 4) && (*str == '0' || *str == '1'))
616 {
617 args[nargs++] = *str++ - '0';
618 }
619 else if (svg_is_digit(*str))
620 {
621 str = svg_lex_number(&number, str);
622 if (nargs == nelem(args))
623 {
624 fz_warn(ctx, "stack overflow in path data");
625 return;
626 }
627 args[nargs++] = number;
628 }
629 else if (svg_is_alpha(*str))
630 {
631 if (nargs != 0)
632 {
633 fz_warn(ctx, "syntax error in path data (wrong number of parameters to '%c')", cmd);
634 return;
635 }
636 cmd = *str++;
637 }
638 else if (*str == 0)
639 {
640 return;
641 }
642 else
643 {
644 fz_warn(ctx, "syntax error in path data: '%c'", *str);
645 return;
646 }
647
648 if (reset_smooth)
649 {
650 smooth_x = 0.0f;
651 smooth_y = 0.0f;
652 }
653
654 reset_smooth = 1;
655
656 switch (cmd)
657 {
658 case 'M':
659 if (nargs == 2)
660 {
661 fz_moveto(ctx, path, args[0], args[1]);
662 nargs = 0;
663 cmd = 'L'; /* implicit lineto after */
664 }
665 break;
666
667 case 'm':
668 if (nargs == 2)
669 {
670 p = fz_currentpoint(ctx, path);
671 fz_moveto(ctx, path, p.x + args[0], p.y + args[1]);
672 nargs = 0;
673 cmd = 'l'; /* implicit lineto after */
674 }
675 break;
676
677 case 'Z':
678 case 'z':
679 if (nargs == 0)
680 {
681 fz_closepath(ctx, path);
682 }
683 break;
684
685 case 'L':
686 if (nargs == 2)
687 {
688 fz_lineto(ctx, path, args[0], args[1]);
689 nargs = 0;
690 }
691 break;
692
693 case 'l':
694 if (nargs == 2)
695 {
696 p = fz_currentpoint(ctx, path);
697 fz_lineto(ctx, path, p.x + args[0], p.y + args[1]);
698 nargs = 0;
699 }
700 break;
701
702 case 'H':
703 if (nargs == 1)
704 {
705 p = fz_currentpoint(ctx, path);
706 fz_lineto(ctx, path, args[0], p.y);
707 nargs = 0;
708 }
709 break;
710
711 case 'h':
712 if (nargs == 1)
713 {
714 p = fz_currentpoint(ctx, path);
715 fz_lineto(ctx, path, p.x + args[0], p.y);
716 nargs = 0;
717 }
718 break;
719
720 case 'V':
721 if (nargs == 1)
722 {
723 p = fz_currentpoint(ctx, path);
724 fz_lineto(ctx, path, p.x, args[0]);
725 nargs = 0;
726 }
727 break;
728
729 case 'v':
730 if (nargs == 1)
731 {
732 p = fz_currentpoint(ctx, path);
733 fz_lineto(ctx, path, p.x, p.y + args[0]);
734 nargs = 0;
735 }
736 break;
737
738 case 'C':
739 reset_smooth = 0;
740 if (nargs == 6)
741 {
742 fz_curveto(ctx, path, args[0], args[1], args[2], args[3], args[4], args[5]);
743 smooth_x = args[4] - args[2];
744 smooth_y = args[5] - args[3];
745 nargs = 0;
746 }
747 break;
748
749 case 'c':
750 reset_smooth = 0;
751 if (nargs == 6)
752 {
753 p = fz_currentpoint(ctx, path);
754 fz_curveto(ctx, path,
755 p.x + args[0], p.y + args[1],
756 p.x + args[2], p.y + args[3],
757 p.x + args[4], p.y + args[5]);
758 smooth_x = args[4] - args[2];
759 smooth_y = args[5] - args[3];
760 nargs = 0;
761 }
762 break;
763
764 case 'S':
765 reset_smooth = 0;
766 if (nargs == 4)
767 {
768 p = fz_currentpoint(ctx, path);
769 fz_curveto(ctx, path,
770 p.x + smooth_x, p.y + smooth_y,
771 args[0], args[1],
772 args[2], args[3]);
773 smooth_x = args[2] - args[0];
774 smooth_y = args[3] - args[1];
775 nargs = 0;
776 }
777 break;
778
779 case 's':
780 reset_smooth = 0;
781 if (nargs == 4)
782 {
783 p = fz_currentpoint(ctx, path);
784 fz_curveto(ctx, path,
785 p.x + smooth_x, p.y + smooth_y,
786 p.x + args[0], p.y + args[1],
787 p.x + args[2], p.y + args[3]);
788 smooth_x = args[2] - args[0];
789 smooth_y = args[3] - args[1];
790 nargs = 0;
791 }
792 break;
793
794 case 'Q':
795 reset_smooth = 0;
796 if (nargs == 4)
797 {
798 p = fz_currentpoint(ctx, path);
799 x1 = args[0];
800 y1 = args[1];
801 x2 = args[2];
802 y2 = args[3];
803 fz_curveto(ctx, path,
804 (p.x + 2 * x1) / 3, (p.y + 2 * y1) / 3,
805 (x2 + 2 * x1) / 3, (y2 + 2 * y1) / 3,
806 x2, y2);
807 smooth_x = x2 - x1;
808 smooth_y = y2 - y1;
809 nargs = 0;
810 }
811 break;
812
813 case 'q':
814 reset_smooth = 0;
815 if (nargs == 4)
816 {
817 p = fz_currentpoint(ctx, path);
818 x1 = args[0] + p.x;
819 y1 = args[1] + p.y;
820 x2 = args[2] + p.x;
821 y2 = args[3] + p.y;
822 fz_curveto(ctx, path,
823 (p.x + 2 * x1) / 3, (p.y + 2 * y1) / 3,
824 (x2 + 2 * x1) / 3, (y2 + 2 * y1) / 3,
825 x2, y2);
826 smooth_x = x2 - x1;
827 smooth_y = y2 - y1;
828 nargs = 0;
829 }
830 break;
831
832 case 'T':
833 reset_smooth = 0;
834 if (nargs == 2)
835 {
836 p = fz_currentpoint(ctx, path);
837 x1 = p.x + smooth_x;
838 y1 = p.y + smooth_y;
839 x2 = args[0];
840 y2 = args[1];
841 fz_curveto(ctx, path,
842 (p.x + 2 * x1) / 3, (p.y + 2 * y1) / 3,
843 (x2 + 2 * x1) / 3, (y2 + 2 * y1) / 3,
844 x2, y2);
845 smooth_x = x2 - x1;
846 smooth_y = y2 - y1;
847 nargs = 0;
848 }
849 break;
850
851 case 't':
852 reset_smooth = 0;
853 if (nargs == 2)
854 {
855 p = fz_currentpoint(ctx, path);
856 x1 = p.x + smooth_x;
857 y1 = p.y + smooth_y;
858 x2 = args[0] + p.x;
859 y2 = args[1] + p.y;
860 fz_curveto(ctx, path,
861 (p.x + 2 * x1) / 3, (p.y + 2 * y1) / 3,
862 (x2 + 2 * x1) / 3, (y2 + 2 * y1) / 3,
863 x2, y2);
864 smooth_x = x2 - x1;
865 smooth_y = y2 - y1;
866 nargs = 0;
867 }
868 break;
869
870 case 'A':
871 if (nargs == 7)
872 {
873 svg_add_arc(ctx, path, args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
874 nargs = 0;
875 }
876 break;
877 case 'a':
878 if (nargs == 7)
879 {
880 p = fz_currentpoint(ctx, path);
881 svg_add_arc(ctx, path, args[0], args[1], args[2], args[3], args[4], args[5] + p.x, args[6] + p.y);
882 nargs = 0;
883 }
884 break;
885
886 case 0:
887 if (nargs != 0)
888 {
889 fz_warn(ctx, "path data must begin with a command");
890 return;
891 }
892 break;
893
894 default:
895 fz_warn(ctx, "unrecognized command in path data: '%c'", cmd);
896 return;
897 }
898 }
899 }
900
901 static void
902 svg_run_path(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state)
903 {
904 svg_state local_state;
905 fz_path *path = NULL;
906
907 const char *d_att = fz_xml_att(node, "d");
908 /* unused: char *path_length_att = fz_xml_att(node, "pathLength"); */
909
910 fz_var(path);
911
912 fz_try(ctx)
913 {
914 svg_begin_state(ctx, &local_state, inherit_state);
915 svg_parse_common(ctx, doc, node, &local_state);
916
917 if (d_att)
918 {
919 path = fz_new_path(ctx);
920 svg_parse_path_data(ctx, path, d_att);
921 svg_draw_path(ctx, dev, doc, path, &local_state);
922 }
923 }
924 fz_always(ctx)
925 {
926 fz_drop_path(ctx, path);
927 svg_end_state(ctx, &local_state);
928 }
929 fz_catch(ctx)
930 fz_rethrow(ctx);
931 }
932
933 /* svg, symbol, image, foreignObject establish new viewports */
934 static void
935 svg_parse_viewport(fz_context *ctx, svg_document *doc, fz_xml *node, svg_state *state)
936 {
937 char *w_att = fz_xml_att(node, "width");
938 char *h_att = fz_xml_att(node, "height");
939
940 if (w_att)
941 state->viewport_w = svg_parse_length(w_att, state->viewbox_w, state->fontsize);
942 if (h_att)
943 state->viewport_h = svg_parse_length(h_att, state->viewbox_h, state->fontsize);
944
945 }
946
947 static void
948 svg_lex_viewbox(const char *s, float *x, float *y, float *w, float *h)
949 {
950 *x = *y = *w = *h = 0;
951 while (svg_is_whitespace_or_comma(*s)) ++s;
952 if (svg_is_digit(*s)) s = svg_lex_number(x, s);
953 while (svg_is_whitespace_or_comma(*s)) ++s;
954 if (svg_is_digit(*s)) s = svg_lex_number(y, s);
955 while (svg_is_whitespace_or_comma(*s)) ++s;
956 if (svg_is_digit(*s)) s = svg_lex_number(w, s);
957 while (svg_is_whitespace_or_comma(*s)) ++s;
958 if (svg_is_digit(*s)) s = svg_lex_number(h, s);
959 }
960
961 static int
962 svg_parse_preserve_aspect_ratio(const char *att, int *x, int *y)
963 {
964 *x = *y = 1;
965 if (strstr(att, "none")) return 0;
966 if (strstr(att, "xMin")) *x = 0;
967 if (strstr(att, "xMid")) *x = 1;
968 if (strstr(att, "xMax")) *x = 2;
969 if (strstr(att, "YMin")) *y = 0;
970 if (strstr(att, "YMid")) *y = 1;
971 if (strstr(att, "YMax")) *y = 2;
972 return 1;
973 }
974
975 /* svg, symbol, image, foreignObject plus marker, pattern, view can use viewBox to set the transform */
976 static void
977 svg_parse_viewbox(fz_context *ctx, svg_document *doc, fz_xml *node, svg_state *state)
978 {
979 char *viewbox_att = fz_xml_att(node, "viewBox");
980 char *preserve_att = fz_xml_att(node, "preserveAspectRatio");
981 if (viewbox_att)
982 {
983 /* scale and translate to fit [minx miny minx+w miny+h] to [0 0 viewport.w viewport.h] */
984 float min_x, min_y, box_w, box_h, sx, sy;
985 int align_x=1, align_y=1, preserve=1;
986 float pad_x=0, pad_y=0;
987
988 svg_lex_viewbox(viewbox_att, &min_x, &min_y, &box_w, &box_h);
989 sx = state->viewport_w / box_w;
990 sy = state->viewport_h / box_h;
991
992 if (preserve_att)
993 preserve = svg_parse_preserve_aspect_ratio(preserve_att, &align_x, &align_y);
994 if (preserve)
995 {
996 sx = sy = fz_min(sx, sy);
997 if (align_x == 1) pad_x = (box_w * sx - state->viewport_w) / 2;
998 if (align_x == 2) pad_x = (box_w * sx - state->viewport_w);
999 if (align_y == 1) pad_y = (box_h * sy - state->viewport_h) / 2;
1000 if (align_y == 2) pad_y = (box_h * sy - state->viewport_h);
1001 state->transform = fz_concat(fz_translate(-pad_x, -pad_y), state->transform);
1002 }
1003 state->transform = fz_concat(fz_scale(sx, sy), state->transform);
1004 state->transform = fz_concat(fz_translate(-min_x, -min_y), state->transform);
1005 state->viewbox_w = box_w;
1006 state->viewbox_h = box_h;
1007 state->viewbox_size = sqrtf(box_w*box_w + box_h*box_h) / sqrtf(2);
1008 }
1009 }
1010
1011 static const char *linecap_table[] = { "butt", "round", "square" };
1012 static const char *linejoin_table[] = { "miter", "round", "bevel" };
1013
1014 /* parse transform and presentation attributes */
1015 static void
1016 svg_parse_common(fz_context *ctx, svg_document *doc, fz_xml *node, svg_state *state)
1017 {
1018 fz_stroke_state *stroke = state->stroke;
1019
1020 char *transform_att = fz_xml_att(node, "transform");
1021
1022 char *font_size_att = fz_xml_att(node, "font-size");
1023
1024 char *style_att = fz_xml_att(node, "style");
1025
1026 // TODO: clip, clip-path, clip-rule
1027
1028 char *opacity_att = fz_xml_att(node, "opacity");
1029
1030 char *fill_att = fz_xml_att(node, "fill");
1031 char *fill_rule_att = fz_xml_att(node, "fill-rule");
1032 char *fill_opacity_att = fz_xml_att(node, "fill-opacity");
1033
1034 char *stroke_att = fz_xml_att(node, "stroke");
1035 char *stroke_opacity_att = fz_xml_att(node, "stroke-opacity");
1036 char *stroke_width_att = fz_xml_att(node, "stroke-width");
1037 char *stroke_linecap_att = fz_xml_att(node, "stroke-linecap");
1038 char *stroke_linejoin_att = fz_xml_att(node, "stroke-linejoin");
1039 char *stroke_miterlimit_att = fz_xml_att(node, "stroke-miterlimit");
1040 // TODO: stroke-dasharray, stroke-dashoffset
1041
1042 // TODO: marker, marker-start, marker-mid, marker-end
1043
1044 // TODO: overflow
1045 // TODO: mask
1046
1047 /* Dirty hack scans of CSS style */
1048 if (style_att)
1049 {
1050 svg_parse_color_from_style(ctx, doc, style_att,
1051 &state->fill_is_set, state->fill_color,
1052 &state->stroke_is_set, state->stroke_color);
1053 }
1054
1055 if (transform_att)
1056 {
1057 state->transform = svg_parse_transform(ctx, doc, transform_att, state->transform);
1058 }
1059
1060 if (font_size_att)
1061 {
1062 state->fontsize = svg_parse_length(font_size_att, state->fontsize, state->fontsize);
1063 }
1064 else
1065 {
1066 state->fontsize = svg_parse_number_from_style(ctx, doc, style_att, "font-size", state->fontsize);
1067 }
1068
1069 if (opacity_att)
1070 {
1071 state->opacity = svg_parse_number(opacity_att, 0, 1, state->opacity);
1072 }
1073
1074 if (fill_att)
1075 {
1076 if (!strcmp(fill_att, "none"))
1077 {
1078 state->fill_is_set = 0;
1079 }
1080 else
1081 {
1082 state->fill_is_set = 1;
1083 svg_parse_color(ctx, doc, fill_att, state->fill_color);
1084 }
1085 }
1086
1087 if (fill_opacity_att)
1088 state->fill_opacity = svg_parse_number(fill_opacity_att, 0, 1, state->fill_opacity);
1089
1090 if (fill_rule_att)
1091 {
1092 if (!strcmp(fill_rule_att, "nonzero"))
1093 state->fill_rule = 0;
1094 if (!strcmp(fill_rule_att, "evenodd"))
1095 state->fill_rule = 1;
1096 }
1097
1098 if (stroke_att)
1099 {
1100 if (!strcmp(stroke_att, "none"))
1101 {
1102 state->stroke_is_set = 0;
1103 }
1104 else
1105 {
1106 state->stroke_is_set = 1;
1107 svg_parse_color(ctx, doc, stroke_att, state->stroke_color);
1108 }
1109 }
1110
1111 if (stroke_opacity_att)
1112 state->stroke_opacity = svg_parse_number(stroke_opacity_att, 0, 1, state->stroke_opacity);
1113
1114 if (stroke_width_att)
1115 {
1116 if (!strcmp(stroke_width_att, "inherit"))
1117 ;
1118 else
1119 stroke->linewidth = svg_parse_length(stroke_width_att, state->viewbox_size, state->fontsize);
1120 }
1121 else
1122 {
1123 stroke->linewidth = svg_parse_number_from_style(ctx, doc, style_att, "stroke-width", state->stroke->linewidth);
1124 }
1125
1126 if (stroke_linecap_att)
1127 {
1128 if (!strcmp(stroke_linecap_att, "butt"))
1129 stroke->start_cap = FZ_LINECAP_BUTT;
1130 if (!strcmp(stroke_linecap_att, "round"))
1131 stroke->start_cap = FZ_LINECAP_ROUND;
1132 if (!strcmp(stroke_linecap_att, "square"))
1133 stroke->start_cap = FZ_LINECAP_SQUARE;
1134 }
1135 else
1136 {
1137 stroke->start_cap = svg_parse_enum_from_style(ctx, doc, style_att, "stroke-linecap",
1138 nelem(linecap_table), linecap_table, FZ_LINECAP_BUTT);
1139 }
1140
1141 stroke->dash_cap = stroke->start_cap;
1142 stroke->end_cap = stroke->start_cap;
1143
1144 if (stroke_linejoin_att)
1145 {
1146 if (!strcmp(stroke_linejoin_att, "miter"))
1147 stroke->linejoin = FZ_LINEJOIN_MITER;
1148 if (!strcmp(stroke_linejoin_att, "round"))
1149 stroke->linejoin = FZ_LINEJOIN_ROUND;
1150 if (!strcmp(stroke_linejoin_att, "bevel"))
1151 stroke->linejoin = FZ_LINEJOIN_BEVEL;
1152 }
1153 else
1154 {
1155 stroke->linejoin = svg_parse_enum_from_style(ctx, doc, style_att, "stroke-linejoin",
1156 nelem(linejoin_table), linejoin_table, FZ_LINEJOIN_MITER);
1157 }
1158
1159 if (stroke_miterlimit_att)
1160 {
1161 if (!strcmp(stroke_miterlimit_att, "inherit"))
1162 ;
1163 else
1164 stroke->miterlimit = svg_parse_length(stroke_miterlimit_att, state->viewbox_size, state->fontsize);
1165 }
1166 else
1167 {
1168 stroke->miterlimit = svg_parse_number_from_style(ctx, doc, style_att, "stroke-miterlimit", state->stroke->miterlimit);
1169 }
1170 }
1171
1172 static void
1173 svg_parse_font_attributes(fz_context *ctx, svg_document *doc, fz_xml *node, svg_state *state, char *buf, int buf_size)
1174 {
1175 char *style_att = fz_xml_att(node, "style");
1176 char *font_family_att = fz_xml_att(node, "font-family");
1177 char *font_weight_att = fz_xml_att(node, "font-weight");
1178 char *font_style_att = fz_xml_att(node, "font-style");
1179 char *text_anchor_att = fz_xml_att(node, "text-anchor");
1180
1181 if (font_family_att)
1182 fz_strlcpy(buf, font_family_att, buf_size);
1183 else
1184 svg_parse_string_from_style(ctx, doc, style_att, "font-family", buf, buf_size, state->font_family);
1185 state->font_family = buf;
1186
1187 if (font_weight_att)
1188 {
1189 state->is_bold = atoi(font_weight_att) > 400;
1190 if (!strcmp(font_weight_att, "bold")) state->is_bold = 1;
1191 if (!strcmp(font_weight_att, "bolder")) state->is_bold = 1;
1192 }
1193 else
1194 {
1195 static const char *is_bold_table[] = {
1196 "normal", "100", "200", "300", "400", "bold", "bolder", "500", "600", "700", "800", "900"
1197 };
1198 state->is_bold = svg_parse_enum_from_style(ctx, doc, style_att, "font-weight",
1199 nelem(is_bold_table), is_bold_table, state->is_bold ? 5 : 0) >= 5;
1200 }
1201
1202 if (font_style_att)
1203 {
1204 state->is_italic = 0;
1205 if (!strcmp(font_style_att, "italic")) state->is_italic = 1;
1206 if (!strcmp(font_style_att, "oblique")) state->is_italic = 1;
1207 }
1208 else
1209 {
1210 static const char *is_italic_table[] = {
1211 "normal", "italic", "oblique"
1212 };
1213 state->is_italic = svg_parse_enum_from_style(ctx, doc, style_att, "font-style",
1214 nelem(is_italic_table), is_italic_table, state->is_italic) >= 1;
1215 }
1216
1217 if (text_anchor_att)
1218 {
1219 state->text_anchor = 0;
1220 if (!strcmp(text_anchor_att, "middle")) state->text_anchor = 1;
1221 if (!strcmp(text_anchor_att, "end")) state->text_anchor = 2;
1222 }
1223 else
1224 {
1225 static const char *text_anchor_table[] = {
1226 "start", "middle", "end"
1227 };
1228 state->text_anchor = svg_parse_enum_from_style(ctx, doc, style_att, "text-anchor",
1229 nelem(text_anchor_table), text_anchor_table, state->text_anchor);
1230 }
1231 }
1232
1233 static void
1234 svg_run_svg(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *root, const svg_state *inherit_state)
1235 {
1236 svg_state local_state;
1237 fz_xml *node;
1238
1239 char *w_att = fz_xml_att(root, "width");
1240 char *h_att = fz_xml_att(root, "height");
1241 char *viewbox_att = fz_xml_att(root, "viewBox");
1242
1243 fz_try(ctx)
1244 {
1245 svg_begin_state(ctx, &local_state, inherit_state);
1246
1247 /* get default viewport from viewBox if width and/or height is missing */
1248 if (viewbox_att && (!w_att || !h_att))
1249 {
1250 float x, y;
1251 svg_lex_viewbox(viewbox_att, &x, &y, &local_state.viewbox_w, &local_state.viewbox_h);
1252 if (!w_att) local_state.viewport_w = local_state.viewbox_w;
1253 if (!h_att) local_state.viewport_h = local_state.viewbox_h;
1254 }
1255
1256 svg_parse_viewport(ctx, doc, root, &local_state);
1257 svg_parse_viewbox(ctx, doc, root, &local_state);
1258 svg_parse_common(ctx, doc, root, &local_state);
1259
1260 for (node = fz_xml_down(root); node; node = fz_xml_next(node))
1261 svg_run_element(ctx, dev, doc, node, &local_state);
1262 }
1263 fz_always(ctx)
1264 svg_end_state(ctx, &local_state);
1265 fz_catch(ctx)
1266 fz_rethrow(ctx);
1267 }
1268
1269 static void
1270 svg_run_g(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *root, const svg_state *inherit_state)
1271 {
1272 svg_state local_state;
1273 fz_xml *node;
1274
1275 fz_try(ctx)
1276 {
1277 svg_begin_state(ctx, &local_state, inherit_state);
1278 svg_parse_common(ctx, doc, root, &local_state);
1279
1280 for (node = fz_xml_down(root); node; node = fz_xml_next(node))
1281 svg_run_element(ctx, dev, doc, node, &local_state);
1282 }
1283 fz_always(ctx)
1284 svg_end_state(ctx, &local_state);
1285 fz_catch(ctx)
1286 fz_rethrow(ctx);
1287 }
1288
1289 static void
1290 svg_run_use_symbol(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *use, fz_xml *symbol, const svg_state *inherit_state)
1291 {
1292 svg_state local_state;
1293 fz_xml *node;
1294
1295 fz_try(ctx)
1296 {
1297 svg_begin_state(ctx, &local_state, inherit_state);
1298
1299 svg_parse_viewport(ctx, doc, use, &local_state);
1300 svg_parse_viewbox(ctx, doc, use, &local_state);
1301
1302 for (node = fz_xml_down(symbol); node; node = fz_xml_next(node))
1303 svg_run_element(ctx, dev, doc, node, &local_state);
1304 }
1305 fz_always(ctx)
1306 svg_end_state(ctx, &local_state);
1307 fz_catch(ctx)
1308 fz_rethrow(ctx);
1309 }
1310
1311 static int
1312 is_use_cycle(fz_xml *use, fz_xml *symbol)
1313 {
1314 /* If "use" is a direct child of "symbol", we have a recursive symbol/use definition! */
1315 while (use)
1316 {
1317 if (use == symbol)
1318 return 1;
1319 use = fz_xml_up(use);
1320 }
1321 return 0;
1322 }
1323
1324 static void
1325 svg_run_use(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *root, const svg_state *inherit_state)
1326 {
1327 svg_state local_state;
1328
1329 char *href_att = fz_xml_att_alt(root, "xlink:href", "href");
1330 char *x_att = fz_xml_att(root, "x");
1331 char *y_att = fz_xml_att(root, "y");
1332 fz_xml *linked = NULL;
1333
1334 float x = 0;
1335 float y = 0;
1336
1337 fz_try(ctx)
1338 {
1339 svg_begin_state(ctx, &local_state, inherit_state);
1340
1341 if (++local_state.use_depth > MAX_USE_DEPTH)
1342 {
1343 fz_warn(ctx, "svg: too much recursion");
1344 break;
1345 }
1346
1347 svg_parse_common(ctx, doc, root, &local_state);
1348 if (x_att) x = svg_parse_length(x_att, local_state.viewbox_w, local_state.fontsize);
1349 if (y_att) y = svg_parse_length(y_att, local_state.viewbox_h, local_state.fontsize);
1350
1351 local_state.transform = fz_concat(fz_translate(x, y), local_state.transform);
1352
1353 if (href_att && href_att[0] == '#')
1354 {
1355 linked = fz_tree_lookup(ctx, doc->idmap, href_att + 1);
1356 if (linked)
1357 {
1358 if (is_use_cycle(root, linked))
1359 fz_warn(ctx, "svg: cyclic <use> reference");
1360
1361 if (fz_xml_is_tag(linked, "symbol"))
1362 svg_run_use_symbol(ctx, dev, doc, root, linked, &local_state);
1363 else
1364 svg_run_element(ctx, dev, doc, linked, &local_state);
1365 }
1366 else
1367 {
1368 fz_warn(ctx, "svg: cannot find linked symbol");
1369 }
1370 }
1371 else
1372 {
1373 fz_warn(ctx, "svg: cannot find linked symbol");
1374 }
1375
1376 }
1377 fz_always(ctx)
1378 svg_end_state(ctx, &local_state);
1379 fz_catch(ctx)
1380 fz_rethrow(ctx);
1381 }
1382
1383 static void
1384 svg_run_image(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *root, const svg_state *inherit_state)
1385 {
1386 svg_state local_state;
1387 float x=0, y=0, w=0, h=0;
1388 const char *data;
1389
1390 static const char *jpeg_uri = "data:image/jpeg;base64,";
1391 static const char *png_uri = "data:image/png;base64,";
1392
1393 char *href_att = fz_xml_att_alt(root, "xlink:href", "href");
1394 char *x_att = fz_xml_att(root, "x");
1395 char *y_att = fz_xml_att(root, "y");
1396 char *w_att = fz_xml_att(root, "width");
1397 char *h_att = fz_xml_att(root, "height");
1398
1399 fz_try(ctx)
1400 {
1401 svg_begin_state(ctx, &local_state, inherit_state);
1402
1403 svg_parse_common(ctx, doc, root, &local_state);
1404 if (x_att) x = svg_parse_length(x_att, local_state.viewbox_w, local_state.fontsize);
1405 if (y_att) y = svg_parse_length(y_att, local_state.viewbox_h, local_state.fontsize);
1406 if (w_att) w = svg_parse_length(w_att, local_state.viewbox_w, local_state.fontsize);
1407 if (h_att) h = svg_parse_length(h_att, local_state.viewbox_h, local_state.fontsize);
1408
1409 if (w <= 0 || h <= 0)
1410 break; // out of try-catch
1411
1412 if (!href_att)
1413 break; // out of try-catch
1414
1415 local_state.transform = fz_concat(fz_translate(x, y), local_state.transform);
1416 local_state.transform = fz_concat(fz_scale(w, h), local_state.transform);
1417
1418 if (!strncmp(href_att, jpeg_uri, strlen(jpeg_uri)))
1419 data = href_att + strlen(jpeg_uri);
1420 else if (!strncmp(href_att, png_uri, strlen(png_uri)))
1421 data = href_att + strlen(png_uri);
1422 else
1423 data = NULL;
1424 if (data)
1425 {
1426 fz_image *img = NULL;
1427 fz_buffer *buf;
1428
1429 fz_var(img);
1430
1431 buf = fz_new_buffer_from_base64(ctx, data, 0);
1432 fz_try(ctx)
1433 {
1434 fz_matrix orient;
1435 img = fz_new_image_from_buffer(ctx, buf);
1436 orient = fz_image_orientation_matrix(ctx, img);
1437 local_state.transform = fz_concat(orient, local_state.transform);
1438 fz_fill_image(ctx, dev, img, local_state.transform, 1, fz_default_color_params);
1439 }
1440 fz_always(ctx)
1441 {
1442 fz_drop_buffer(ctx, buf);
1443 fz_drop_image(ctx, img);
1444 }
1445 fz_catch(ctx)
1446 {
1447 fz_rethrow_if(ctx, FZ_ERROR_SYSTEM);
1448 fz_report_error(ctx);
1449 fz_warn(ctx, "svg: ignoring embedded image '%s'", href_att);
1450 }
1451 }
1452 else if (doc->zip)
1453 {
1454 char path[2048];
1455 fz_buffer *buf = NULL;
1456 fz_image *img = NULL;
1457
1458 fz_var(buf);
1459 fz_var(img);
1460
1461 fz_strlcpy(path, doc->base_uri, sizeof path);
1462 fz_strlcat(path, "/", sizeof path);
1463 fz_strlcat(path, href_att, sizeof path);
1464 fz_urldecode(path);
1465
1466 fz_try(ctx)
1467 {
1468 fz_matrix orient;
1469 buf = fz_read_archive_entry(ctx, doc->zip, path);
1470 img = fz_new_image_from_buffer(ctx, buf);
1471 orient = fz_image_orientation_matrix(ctx, img);
1472 local_state.transform = fz_concat(orient, local_state.transform);
1473 fz_fill_image(ctx, dev, img, local_state.transform, 1, fz_default_color_params);
1474 }
1475 fz_always(ctx)
1476 {
1477 fz_drop_buffer(ctx, buf);
1478 fz_drop_image(ctx, img);
1479 }
1480 fz_catch(ctx)
1481 {
1482 fz_rethrow_if(ctx, FZ_ERROR_SYSTEM);
1483 fz_report_error(ctx);
1484 fz_warn(ctx, "svg: ignoring external image '%s'", href_att);
1485 }
1486 }
1487 else
1488 {
1489 fz_warn(ctx, "svg: ignoring external image '%s'", href_att);
1490 }
1491
1492 }
1493 fz_always(ctx)
1494 svg_end_state(ctx, &local_state);
1495 fz_catch(ctx)
1496 fz_rethrow(ctx);
1497 }
1498
1499 static fz_font *
1500 svg_load_font(fz_context *ctx, const svg_state *state)
1501 {
1502 int bold = state->is_bold;
1503 int italic = state->is_italic;
1504 int mono = 0;
1505 int serif = 1;
1506
1507 /* scan font-family property for common fallback names */
1508
1509 if (!mono && strstr(state->font_family, "monospace")) mono = 1;
1510 if (!mono && strstr(state->font_family, "Courier")) mono = 1;
1511
1512 if (serif && strstr(state->font_family, "sans-serif")) serif = 0;
1513 if (serif && strstr(state->font_family, "Arial")) serif = 0;
1514 if (serif && strstr(state->font_family, "Helvetica")) serif = 0;
1515
1516 if (mono) {
1517 if (bold) {
1518 if (italic) return fz_new_base14_font(ctx, "Courier-BoldOblique");
1519 else return fz_new_base14_font(ctx, "Courier-Bold");
1520 } else {
1521 if (italic) return fz_new_base14_font(ctx, "Courier-Oblique");
1522 else return fz_new_base14_font(ctx, "Courier");
1523 }
1524 } else if (serif) {
1525 if (bold) {
1526 if (italic) return fz_new_base14_font(ctx, "Times-BoldItalic");
1527 else return fz_new_base14_font(ctx, "Times-Bold");
1528 } else {
1529 if (italic) return fz_new_base14_font(ctx, "Times-Italic");
1530 else return fz_new_base14_font(ctx, "Times-Roman");
1531 }
1532 } else {
1533 if (bold) {
1534 if (italic) return fz_new_base14_font(ctx, "Helvetica-BoldOblique");
1535 else return fz_new_base14_font(ctx, "Helvetica-Bold");
1536 } else {
1537 if (italic) return fz_new_base14_font(ctx, "Helvetica-Oblique");
1538 else return fz_new_base14_font(ctx, "Helvetica");
1539 }
1540 }
1541 }
1542
1543 static fz_matrix
1544 svg_run_text_string(fz_context *ctx, fz_device *dev, fz_matrix trm, const char *s, const svg_state *state)
1545 {
1546 fz_font *font = NULL;
1547 fz_text *text = NULL;
1548
1549 fz_var(font);
1550 fz_var(text);
1551
1552 fz_try(ctx)
1553 {
1554 font = svg_load_font(ctx, state);
1555 text = fz_new_text(ctx);
1556
1557 if (state->text_anchor > 0)
1558 {
1559 fz_matrix adv = fz_measure_string(ctx, font, trm, s, 0, 0, FZ_BIDI_LTR, FZ_LANG_UNSET);
1560 if (state->text_anchor == 1)
1561 trm.e -= (adv.e - trm.e) / 2;
1562 else if (state->text_anchor == 2)
1563 trm.e -= (adv.e - trm.e);
1564 }
1565
1566 trm = fz_show_string(ctx, text, font, trm, s, 0, 0, FZ_BIDI_LTR, FZ_LANG_UNSET);
1567
1568 if (state->fill_is_set)
1569 fz_fill_text(ctx, dev, text,
1570 state->transform,
1571 fz_device_rgb(ctx), state->fill_color,
1572 state->opacity,
1573 fz_default_color_params);
1574 if (state->stroke_is_set)
1575 fz_stroke_text(ctx, dev, text,
1576 state->stroke,
1577 state->transform,
1578 fz_device_rgb(ctx), state->stroke_color,
1579 state->opacity,
1580 fz_default_color_params);
1581 if (!state->fill_is_set && !state->stroke_is_set)
1582 fz_ignore_text(ctx, dev, text, state->transform);
1583 }
1584 fz_always(ctx)
1585 {
1586 fz_drop_text(ctx, text);
1587 fz_drop_font(ctx, font);
1588 }
1589 fz_catch(ctx)
1590 {
1591 fz_rethrow(ctx);
1592 }
1593
1594 return trm;
1595 }
1596
1597 static void
1598 svg_collapse_whitespace(char *start, int is_first, int is_last)
1599 {
1600 int c, last_c = (is_first ? ' ' : 0);
1601 char *s, *p;
1602 s = p = start;
1603 while ((c = *s++) != 0)
1604 {
1605 if (c == '\n' || c == '\r')
1606 continue;
1607 if (c == '\t')
1608 c = ' ';
1609 if (c == ' ' && last_c == ' ')
1610 continue;
1611 *p++ = last_c = c;
1612 }
1613 if (is_last && p > start && p[-1] == ' ')
1614 --p;
1615 *p = 0;
1616 }
1617
1618 static fz_matrix
1619 svg_run_text(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *root, const svg_state *inherit_state,
1620 float x, float y, int is_first, int is_last)
1621 {
1622 svg_state local_state;
1623 char font_family[100];
1624 fz_xml *node;
1625 fz_matrix trm;
1626 int cif, cil;
1627 char *text;
1628
1629 char *x_att = fz_xml_att(root, "x");
1630 char *y_att = fz_xml_att(root, "y");
1631 char *dx_att = fz_xml_att(root, "dx");
1632 char *dy_att = fz_xml_att(root, "dy");
1633
1634 fz_try(ctx)
1635 {
1636 svg_begin_state(ctx, &local_state, inherit_state);
1637
1638 svg_parse_common(ctx, doc, root, &local_state);
1639 svg_parse_font_attributes(ctx, doc, root, &local_state, font_family, sizeof font_family);
1640
1641 trm = fz_scale(local_state.fontsize, -local_state.fontsize);
1642 trm.e = x;
1643 trm.f = y;
1644
1645 if (x_att) trm.e = svg_parse_length(x_att, local_state.viewbox_w, local_state.fontsize);
1646 if (y_att) trm.f = svg_parse_length(y_att, local_state.viewbox_h, local_state.fontsize);
1647
1648 if (dx_att) trm.e += svg_parse_length(dx_att, local_state.viewbox_w, local_state.fontsize);
1649 if (dy_att) trm.f += svg_parse_length(dy_att, local_state.viewbox_h, local_state.fontsize);
1650
1651 cif = is_first;
1652 for (node = fz_xml_down(root); node; node = fz_xml_next(node))
1653 {
1654 cil = is_last && !fz_xml_next(node);
1655 text = fz_xml_text(node);
1656 if (text)
1657 {
1658 svg_collapse_whitespace(text, cif, cil);
1659 trm = svg_run_text_string(ctx, dev, trm, text, &local_state);
1660 }
1661 else if (fz_xml_is_tag(node, "tspan"))
1662 trm = svg_run_text(ctx, dev, doc, node, &local_state, trm.e, trm.f, cif, cil);
1663 else if (fz_xml_is_tag(node, "textPath"))
1664 trm = svg_run_text(ctx, dev, doc, node, &local_state, trm.e, trm.f, cif, cil);
1665 cif = 0;
1666 }
1667 }
1668 fz_always(ctx)
1669 svg_end_state(ctx, &local_state);
1670 fz_catch(ctx)
1671 fz_rethrow(ctx);
1672
1673 return trm;
1674 }
1675
1676 static void
1677 svg_run_element(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *root, const svg_state *state)
1678 {
1679 if (fz_xml_is_tag(root, "svg"))
1680 svg_run_svg(ctx, dev, doc, root, state);
1681
1682 else if (fz_xml_is_tag(root, "g"))
1683 svg_run_g(ctx, dev, doc, root, state);
1684
1685 else if (fz_xml_is_tag(root, "title"))
1686 ;
1687 else if (fz_xml_is_tag(root, "desc"))
1688 ;
1689
1690 else if (fz_xml_is_tag(root, "defs"))
1691 ;
1692 else if (fz_xml_is_tag(root, "symbol"))
1693 ;
1694
1695 else if (fz_xml_is_tag(root, "use"))
1696 svg_run_use(ctx, dev, doc, root, state);
1697
1698 else if (fz_xml_is_tag(root, "path"))
1699 svg_run_path(ctx, dev, doc, root, state);
1700 else if (fz_xml_is_tag(root, "rect"))
1701 svg_run_rect(ctx, dev, doc, root, state);
1702 else if (fz_xml_is_tag(root, "circle"))
1703 svg_run_circle(ctx, dev, doc, root, state);
1704 else if (fz_xml_is_tag(root, "ellipse"))
1705 svg_run_ellipse(ctx, dev, doc, root, state);
1706 else if (fz_xml_is_tag(root, "line"))
1707 svg_run_line(ctx, dev, doc, root, state);
1708 else if (fz_xml_is_tag(root, "polyline"))
1709 svg_run_polyline(ctx, dev, doc, root, state);
1710 else if (fz_xml_is_tag(root, "polygon"))
1711 svg_run_polygon(ctx, dev, doc, root, state);
1712
1713 else if (fz_xml_is_tag(root, "image"))
1714 svg_run_image(ctx, dev, doc, root, state);
1715
1716 else if (fz_xml_is_tag(root, "text"))
1717 svg_run_text(ctx, dev, doc, root, state, 0, 0, 1, 1);
1718
1719 else
1720 {
1721 /* ignore unrecognized tags */
1722 }
1723 }
1724
1725 void
1726 svg_parse_document_bounds(fz_context *ctx, svg_document *doc, fz_xml *root)
1727 {
1728 char *version_att;
1729 char *w_att;
1730 char *h_att;
1731 char *viewbox_att;
1732 int version;
1733
1734 if (!fz_xml_is_tag(root, "svg"))
1735 fz_throw(ctx, FZ_ERROR_SYNTAX, "expected svg element (found %s)", fz_xml_tag(root));
1736
1737 version_att = fz_xml_att(root, "version");
1738 w_att = fz_xml_att(root, "width");
1739 h_att = fz_xml_att(root, "height");
1740 viewbox_att = fz_xml_att(root, "viewBox");
1741
1742 version = 10;
1743 if (version_att)
1744 version = fz_atof(version_att) * 10;
1745
1746 if (version > 12)
1747 fz_warn(ctx, "svg document version is newer than we support");
1748
1749 /* If no width or height attributes, then guess from the viewbox */
1750 if (w_att == NULL && h_att == NULL && viewbox_att != NULL)
1751 {
1752 float min_x, min_y, box_w, box_h;
1753 svg_lex_viewbox(viewbox_att, &min_x, &min_y, &box_w, &box_h);
1754 doc->width = box_w;
1755 doc->height = box_h;
1756 }
1757 else
1758 {
1759 doc->width = DEF_WIDTH;
1760 if (w_att)
1761 doc->width = svg_parse_length(w_att, doc->width, DEF_FONTSIZE);
1762
1763 doc->height = DEF_HEIGHT;
1764 if (h_att)
1765 doc->height = svg_parse_length(h_att, doc->height, DEF_FONTSIZE);
1766 }
1767 }
1768
1769 void
1770 svg_run_document(fz_context *ctx, svg_document *doc, fz_xml *root, fz_device *dev, fz_matrix ctm)
1771 {
1772 svg_state state;
1773
1774 svg_parse_document_bounds(ctx, doc, root);
1775
1776 /* Initial graphics state */
1777 state.transform = ctm;
1778 state.stroke = fz_new_stroke_state(ctx);
1779 state.use_depth = 0;
1780
1781 state.viewport_w = DEF_WIDTH;
1782 state.viewport_h = DEF_HEIGHT;
1783
1784 state.viewbox_w = DEF_WIDTH;
1785 state.viewbox_h = DEF_HEIGHT;
1786 state.viewbox_size = sqrtf(DEF_WIDTH*DEF_WIDTH + DEF_HEIGHT*DEF_HEIGHT) / sqrtf(2);
1787
1788 state.fontsize = 12;
1789
1790 state.opacity = 1;
1791
1792 state.fill_rule = 0;
1793
1794 state.fill_is_set = 1;
1795 state.fill_color[0] = 0;
1796 state.fill_color[1] = 0;
1797 state.fill_color[2] = 0;
1798 state.fill_opacity = 1;
1799
1800 state.stroke_is_set = 0;
1801 state.stroke_color[0] = 0;
1802 state.stroke_color[1] = 0;
1803 state.stroke_color[2] = 0;
1804 state.stroke_opacity = 1;
1805
1806 state.font_family = "serif";
1807 state.is_bold = 0;
1808 state.is_italic = 0;
1809 state.text_anchor = 0;
1810
1811 fz_try(ctx)
1812 {
1813 svg_run_svg(ctx, dev, doc, root, &state);
1814 }
1815 fz_always(ctx)
1816 {
1817 fz_drop_stroke_state(ctx, state.stroke);
1818 }
1819 fz_catch(ctx)
1820 {
1821 fz_rethrow(ctx);
1822 }
1823 }