Coverage Report

Created: 2025-12-31 07:06

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