Coverage Report

Created: 2025-09-04 06:50

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