Coverage Report

Created: 2026-06-30 07:15

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