Coverage Report

Created: 2026-10-13 07:30

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mupdf/source/fitz/svg-device.c
Line
Count
Source
1
// Copyright (C) 2004-2026 Artifex Software, Inc.
2
//
3
// This file is part of MuPDF.
4
//
5
// MuPDF is free software: you can redistribute it and/or modify it under the
6
// terms of the GNU Affero General Public License as published by the Free
7
// Software Foundation, either version 3 of the License, or (at your option)
8
// any later version.
9
//
10
// MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY
11
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12
// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
13
// details.
14
//
15
// You should have received a copy of the GNU Affero General Public License
16
// along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html>
17
//
18
// Alternative licensing terms are available from the licensor.
19
// For commercial licensing, see <https://www.artifex.com/> or contact
20
// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco,
21
// CA 94129, USA, for further information.
22
23
#include "mupdf/fitz.h"
24
25
#include <string.h>
26
#include <float.h>
27
#include <math.h>
28
29
typedef struct
30
{
31
  int pattern;
32
  fz_matrix ctm;
33
  fz_rect view;
34
  fz_rect area;
35
  fz_point step;
36
} svg_tile;
37
38
typedef struct
39
{
40
  int id;
41
  fz_font *font;
42
  int max_sentlist;
43
  char *sentlist;
44
} svg_font;
45
46
typedef struct
47
{
48
  int id;
49
  fz_image *image;
50
} svg_image;
51
52
typedef struct
53
{
54
  fz_device super;
55
56
  int text_as_text;
57
  int reuse_images;
58
59
  fz_output *real_out;
60
  int in_defs;
61
  fz_buffer *defs;
62
  fz_buffer *main;
63
  fz_buffer *out;
64
65
  int *save_id;
66
  int id;
67
68
  int blend_bitmask;
69
70
  fz_list(svg_tile, tiles);
71
72
  fz_list(svg_font, fonts);
73
74
  fz_list(svg_image, images);
75
76
  int layers;
77
78
  float page_width;
79
  float page_height;
80
81
  float raster_scale; /* resolution of rasterized content (shadings, etc) */
82
} svg_device;
83
84
static fz_buffer *
85
start_def(fz_context *ctx, svg_device *sdev, int need_tag)
86
0
{
87
0
  if (sdev->in_defs > 0)
88
0
  {
89
0
    if (need_tag)
90
0
      fz_append_string(ctx, sdev->defs, "<defs>\n");
91
0
  }
92
0
  else
93
0
  {
94
0
    sdev->out = sdev->defs;
95
0
  }
96
0
  sdev->in_defs++;
97
0
  return sdev->out;
98
0
}
99
100
static fz_buffer *
101
end_def(fz_context *ctx, svg_device *sdev, int need_tag)
102
0
{
103
0
  sdev->in_defs--;
104
0
  if (sdev->in_defs > 0)
105
0
  {
106
0
    if (need_tag)
107
0
      fz_append_string(ctx, sdev->defs, "</defs>\n");
108
0
  }
109
0
  else
110
0
  {
111
0
    sdev->out = sdev->main;
112
0
  }
113
0
  return sdev->out;
114
0
}
115
116
/* Helper functions */
117
118
struct svg_path_walker_state {
119
  fz_buffer *out;
120
  int space; // needs space
121
  float x, y; // last location
122
  int cmd; // last command
123
};
124
125
static void
126
svg_path_emit_number(fz_context *ctx, struct svg_path_walker_state *pws, float a)
127
0
{
128
0
  if (pws->space && a >= 0)
129
0
    fz_append_byte(ctx, pws->out, ' ');
130
0
  fz_append_printf(ctx, pws->out, "%g", a);
131
0
  pws->space = 1;
132
0
}
133
134
static void
135
svg_path_emit_command(fz_context *ctx, struct svg_path_walker_state *pws, char cmd)
136
0
{
137
0
  if (pws->cmd != cmd) {
138
0
    fz_append_byte(ctx, pws->out, cmd);
139
0
    pws->space = 0;
140
0
    pws->cmd = cmd;
141
0
  }
142
0
}
143
144
static void
145
svg_path_moveto(fz_context *ctx, void *arg, float x, float y)
146
0
{
147
0
  struct svg_path_walker_state *pws = arg;
148
0
  svg_path_emit_command(ctx, pws, 'M');
149
0
  svg_path_emit_number(ctx, pws, x);
150
0
  svg_path_emit_number(ctx, pws, y);
151
0
  pws->cmd = 'L';
152
0
  pws->x = x;
153
0
  pws->y = y;
154
0
}
155
156
static void
157
svg_path_lineto(fz_context *ctx, void *arg, float x, float y)
158
0
{
159
0
  struct svg_path_walker_state *pws = arg;
160
0
  if (pws->x == x) {
161
0
    svg_path_emit_command(ctx, pws, 'V');
162
0
    svg_path_emit_number(ctx, pws, y);
163
0
  } else if (pws->y == y) {
164
0
    svg_path_emit_command(ctx, pws, 'H');
165
0
    svg_path_emit_number(ctx, pws, x);
166
0
  } else {
167
0
    svg_path_emit_command(ctx, pws, 'L');
168
0
    svg_path_emit_number(ctx, pws, x);
169
0
    svg_path_emit_number(ctx, pws, y);
170
0
  }
171
0
  pws->x = x;
172
0
  pws->y = y;
173
0
}
174
175
static void
176
svg_path_curveto(fz_context *ctx, void *arg, float x1, float y1, float x2, float y2, float x3, float y3)
177
0
{
178
0
  struct svg_path_walker_state *pws = arg;
179
0
  svg_path_emit_command(ctx, pws, 'C');
180
0
  svg_path_emit_number(ctx, pws, x1);
181
0
  svg_path_emit_number(ctx, pws, y1);
182
0
  svg_path_emit_number(ctx, pws, x2);
183
0
  svg_path_emit_number(ctx, pws, y2);
184
0
  svg_path_emit_number(ctx, pws, x3);
185
0
  svg_path_emit_number(ctx, pws, y3);
186
0
  pws->x = x3;
187
0
  pws->y = y3;
188
0
}
189
190
static void
191
svg_path_close(fz_context *ctx, void *arg)
192
0
{
193
0
  struct svg_path_walker_state *pws = arg;
194
0
  svg_path_emit_command(ctx, arg, 'Z');
195
0
  pws->x = NAN;
196
0
  pws->y = NAN;
197
0
}
198
199
static const fz_path_walker svg_path_walker =
200
{
201
  svg_path_moveto,
202
  svg_path_lineto,
203
  svg_path_curveto,
204
  svg_path_close
205
};
206
207
static void
208
svg_dev_path(fz_context *ctx, svg_device *sdev, const fz_path *path)
209
0
{
210
0
  struct svg_path_walker_state pws = { sdev->out, 0, NAN, NAN, 0 };
211
0
  fz_append_printf(ctx, sdev->out, " d=\"");
212
0
  fz_walk_path(ctx, path, &svg_path_walker, &pws);
213
0
  fz_append_printf(ctx, sdev->out, "\"");
214
0
}
215
216
static void
217
svg_dev_ctm(fz_context *ctx, svg_device *sdev, fz_matrix ctm)
218
0
{
219
0
  fz_buffer *out = sdev->out;
220
221
0
  if (ctm.a != 1.0f || ctm.b != 0 || ctm.c != 0 || ctm.d != 1.0f || ctm.e != 0 || ctm.f != 0)
222
0
  {
223
0
    fz_append_printf(ctx, out, " transform=\"matrix(%g,%g,%g,%g,%g,%g)\"",
224
0
      ctm.a, ctm.b, ctm.c, ctm.d, ctm.e, ctm.f);
225
0
  }
226
0
}
227
228
static void
229
svg_dev_stroke_state(fz_context *ctx, svg_device *sdev, const fz_stroke_state *stroke_state, fz_matrix ctm)
230
0
{
231
0
  fz_buffer *out = sdev->out;
232
0
  float exp;
233
234
0
  exp = fz_matrix_expansion(ctm);
235
0
  if (exp == 0)
236
0
    exp = 1;
237
0
  exp = stroke_state->linewidth/exp;
238
239
  /* Leave 0 width lines as the default "1px". */
240
0
  if (exp != 0)
241
0
    fz_append_printf(ctx, out, " stroke-width=\"%g\"", exp);
242
0
  fz_append_printf(ctx, out, " stroke-linecap=\"%s\"",
243
0
    (stroke_state->start_cap == FZ_LINECAP_SQUARE ? "square" :
244
0
      (stroke_state->start_cap == FZ_LINECAP_ROUND ? "round" : "butt")));
245
0
  if (stroke_state->dash_len != 0)
246
0
  {
247
0
    int i;
248
0
    fz_append_printf(ctx, out, " stroke-dasharray=");
249
0
    for (i = 0; i < stroke_state->dash_len; i++)
250
0
      fz_append_printf(ctx, out, "%c%g", (i == 0 ? '\"' : ','), stroke_state->dash_list[i]);
251
0
    fz_append_printf(ctx, out, "\"");
252
0
    if (stroke_state->dash_phase != 0)
253
0
      fz_append_printf(ctx, out, " stroke-dashoffset=\"%g\"", stroke_state->dash_phase);
254
0
  }
255
0
  if (stroke_state->linejoin == FZ_LINEJOIN_MITER || stroke_state->linejoin == FZ_LINEJOIN_MITER_XPS)
256
0
    fz_append_printf(ctx, out, " stroke-miterlimit=\"%g\"", stroke_state->miterlimit);
257
0
  fz_append_printf(ctx, out, " stroke-linejoin=\"%s\"",
258
0
    (stroke_state->linejoin == FZ_LINEJOIN_BEVEL ? "bevel" :
259
0
      (stroke_state->linejoin == FZ_LINEJOIN_ROUND ? "round" : "miter")));
260
0
}
261
262
static unsigned int
263
svg_hex_color(fz_context *ctx, fz_colorspace *colorspace, const float *color, fz_color_params color_params)
264
0
{
265
0
  float rgb[3];
266
0
  int r, g, b;
267
268
0
  if (colorspace != fz_device_rgb(ctx))
269
0
  {
270
0
    fz_convert_color(ctx, colorspace, color, fz_device_rgb(ctx), rgb, NULL, color_params);
271
0
    color = rgb;
272
0
  }
273
274
0
  r = fz_clampi(255 * color[0] + 0.5f, 0, 255);
275
0
  g = fz_clampi(255 * color[1] + 0.5f, 0, 255);
276
0
  b = fz_clampi(255 * color[2] + 0.5f, 0, 255);
277
278
0
  return (r << 16) | (g << 8) | b;
279
0
}
280
281
static void
282
svg_dev_fill_color(fz_context *ctx, svg_device *sdev, fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params)
283
0
{
284
0
  fz_buffer *out = sdev->out;
285
0
  if (colorspace)
286
0
  {
287
0
    int rgb = svg_hex_color(ctx, colorspace, color, color_params);
288
0
    if (rgb != 0) /* black is the default value */
289
0
      fz_append_printf(ctx, out, " fill=\"#%06x\"", rgb);
290
0
  }
291
0
  else
292
0
    fz_append_printf(ctx, out, " fill=\"none\"");
293
0
  if (alpha != 1)
294
0
    fz_append_printf(ctx, out, " fill-opacity=\"%g\"", alpha);
295
0
}
296
297
static void
298
svg_dev_stroke_color(fz_context *ctx, svg_device *sdev, fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params)
299
0
{
300
0
  fz_buffer *out = sdev->out;
301
0
  if (colorspace)
302
0
    fz_append_printf(ctx, out, " fill=\"none\" stroke=\"#%06x\"", svg_hex_color(ctx, colorspace, color, color_params));
303
0
  else
304
0
    fz_append_printf(ctx, out, " fill=\"none\" stroke=\"none\"");
305
0
  if (alpha != 1)
306
0
    fz_append_printf(ctx, out, " stroke-opacity=\"%g\"", alpha);
307
0
}
308
309
static int
310
find_first_char(fz_context *ctx, const fz_text_span *span, int i)
311
0
{
312
0
  for (; i < span->len; ++i)
313
0
    if (span->items[i].ucs >= 0)
314
0
      return i;
315
0
  return i;
316
0
}
317
318
static int
319
find_next_line_break(fz_context *ctx, const fz_text_span *span, fz_matrix inv_tm, int i)
320
0
{
321
0
  fz_point p, old_p;
322
323
0
  old_p.x = span->items[i].x;
324
0
  old_p.y = span->items[i].y;
325
0
  old_p = fz_transform_point(old_p, inv_tm);
326
327
0
  for (++i; i < span->len; ++i)
328
0
  {
329
0
    if (span->items[i].ucs >= 0)
330
0
    {
331
0
      p.x = span->items[i].x;
332
0
      p.y = span->items[i].y;
333
0
      p = fz_transform_point(p, inv_tm);
334
0
      if (span->wmode == 0)
335
0
      {
336
0
        if (p.y != old_p.y)
337
0
          return i;
338
0
      }
339
0
      else
340
0
      {
341
0
        if (p.x != old_p.x)
342
0
          return i;
343
0
      }
344
0
      old_p = p;
345
0
    }
346
0
  }
347
348
0
  return i;
349
0
}
350
351
static float
352
svg_cluster_advance(fz_context *ctx, const fz_text_span *span, int i, int end)
353
0
{
354
0
  int n = 1;
355
0
  while (i + n < end && span->items[i + n].gid == -1)
356
0
    ++n;
357
0
  if (n > 1)
358
0
    return span->items[i].adv / n;
359
0
  return 0; /* this value is never used (since n==1) */
360
0
}
361
362
static void
363
svg_dev_text_span(fz_context *ctx, svg_device *sdev, fz_matrix ctm, const fz_text_span *span)
364
0
{
365
0
  fz_buffer *out = sdev->out;
366
0
  char *font_family;
367
0
  int is_bold, is_italic;
368
0
  fz_matrix tm, inv_tm, final_tm;
369
0
  fz_point p;
370
0
  float font_size;
371
0
  fz_text_item *it;
372
0
  int start, end, i;
373
0
  float cluster_advance = 0;
374
375
0
  if (span->len == 0)
376
0
  {
377
0
    fz_append_printf(ctx, out, "/>\n");
378
0
    return;
379
0
  }
380
381
0
  tm = span->trm;
382
0
  font_size = fz_matrix_expansion(tm);
383
0
  final_tm.a = tm.a / font_size;
384
0
  final_tm.b = tm.b / font_size;
385
0
  final_tm.c = -tm.c / font_size;
386
0
  final_tm.d = -tm.d / font_size;
387
0
  final_tm.e = 0;
388
0
  final_tm.f = 0;
389
0
  inv_tm = fz_invert_matrix(final_tm);
390
0
  final_tm = fz_concat(final_tm, ctm);
391
392
0
  tm.e = span->items[0].x;
393
0
  tm.f = span->items[0].y;
394
395
0
  font_family = span->font->family;
396
0
  is_bold = fz_font_is_bold(ctx, span->font);
397
0
  is_italic = fz_font_is_italic(ctx, span->font);
398
399
0
  fz_append_printf(ctx, out, " xml:space=\"preserve\"");
400
0
  fz_append_printf(ctx, out, " transform=\"matrix(%M)\"", &final_tm);
401
0
  fz_append_printf(ctx, out, " font-size=\"%g\"", font_size);
402
0
  fz_append_printf(ctx, out, " font-family=\"%s\"", font_family);
403
0
  if (is_bold) fz_append_printf(ctx, out, " font-weight=\"bold\"");
404
0
  if (is_italic) fz_append_printf(ctx, out, " font-style=\"italic\"");
405
0
  if (span->wmode != 0) fz_append_printf(ctx, out, " writing-mode=\"tb\"");
406
407
0
  fz_append_byte(ctx, out, '>');
408
409
0
  start = find_first_char(ctx, span, 0);
410
0
  while (start < span->len)
411
0
  {
412
0
    end = find_next_line_break(ctx, span, inv_tm, start);
413
414
0
    p.x = span->items[start].x;
415
0
    p.y = span->items[start].y;
416
0
    p = fz_transform_point(p, inv_tm);
417
0
    if (span->items[start].gid >= 0)
418
0
      cluster_advance = svg_cluster_advance(ctx, span, start, end);
419
0
    if (span->wmode == 0)
420
0
      fz_append_printf(ctx, out, "<tspan y=\"%g\" x=\"%g", p.y, p.x);
421
0
    else
422
0
      fz_append_printf(ctx, out, "<tspan x=\"%g\" y=\"%g", p.x, p.y);
423
0
    for (i = start + 1; i < end; ++i)
424
0
    {
425
0
      it = &span->items[i];
426
0
      if (it->gid >= 0)
427
0
        cluster_advance = svg_cluster_advance(ctx, span, i, end);
428
0
      if (it->ucs >= 0)
429
0
      {
430
0
        if (it->gid >= 0)
431
0
        {
432
0
          p.x = it->x;
433
0
          p.y = it->y;
434
0
          p = fz_transform_point(p, inv_tm);
435
0
        }
436
0
        else
437
0
        {
438
          /* we have no glyph (such as in a ligature) -- advance a bit */
439
0
          if (span->wmode == 0)
440
0
            p.x += font_size * cluster_advance;
441
0
          else
442
0
            p.y += font_size * cluster_advance;
443
0
        }
444
0
        fz_append_printf(ctx, out, " %g", span->wmode == 0 ? p.x : p.y);
445
0
      }
446
0
    }
447
0
    fz_append_printf(ctx, out, "\">");
448
0
    for (i = start; i < end; ++i)
449
0
    {
450
0
      it = &span->items[i];
451
0
      if (it->ucs >= 0)
452
0
      {
453
0
        int c = it->ucs;
454
0
        if (c >= 32 && c <= 127 && c != '<' && c != '&' && c != '>')
455
0
          fz_append_byte(ctx, out, c);
456
0
        else
457
0
          fz_append_printf(ctx, out, "&#x%04x;", c);
458
0
      }
459
0
    }
460
0
    fz_append_printf(ctx, out, "</tspan>");
461
462
0
    start = find_first_char(ctx, span, end);
463
0
  }
464
465
0
  fz_append_printf(ctx, out, "</text>\n");
466
0
}
467
468
static svg_font *
469
svg_dev_text_span_as_paths_defs(fz_context *ctx, fz_device *dev, fz_text_span *span, fz_matrix ctm)
470
0
{
471
0
  svg_device *sdev = (svg_device*)dev;
472
0
  fz_buffer *out = sdev->out;
473
0
  int i, font_idx;
474
0
  svg_font *fnt;
475
0
  fz_path *path = NULL;
476
477
0
  fz_var(path);
478
479
0
  for (font_idx = 0; font_idx < sdev->fonts_len; font_idx++)
480
0
  {
481
0
    fnt = &sdev->fonts[font_idx];
482
0
    if (fnt->font == span->font)
483
0
      break;
484
0
  }
485
0
  if (font_idx == sdev->fonts_len)
486
0
  {
487
    /* New font */
488
0
    fnt = fz_push_list(ctx, sdev->fonts);
489
0
    fnt->id = sdev->id++;
490
0
    fnt->font = fz_keep_font(ctx, span->font);
491
0
  }
492
493
0
  fz_try(ctx)
494
0
  {
495
0
    for (i=0; i < span->len; i++)
496
0
    {
497
0
      fz_text_item *it = &span->items[i];
498
0
      int gid = it->gid;
499
500
0
      if (gid < 0)
501
0
        continue;
502
0
      if (gid >= fnt->max_sentlist)
503
0
      {
504
0
        int j;
505
0
        fnt->sentlist = fz_realloc_array(ctx, fnt->sentlist, gid+1, char);
506
0
        for (j = fnt->max_sentlist; j <= gid; j++)
507
0
          fnt->sentlist[j] = 0;
508
0
        fnt->max_sentlist = gid+1;
509
0
      }
510
0
      if (!fnt->sentlist[gid])
511
0
      {
512
        /* Need to send this one */
513
0
        out = start_def(ctx, sdev, 1);
514
0
        if (fz_font_ft_face(ctx, span->font))
515
0
        {
516
0
          path = fz_outline_glyph(ctx, span->font, gid, fz_identity);
517
0
          if (path)
518
0
          {
519
0
            fz_append_printf(ctx, out, "<path id=\"font_%d_%d\"", fnt->id, gid);
520
0
            svg_dev_path(ctx, sdev, path);
521
0
            fz_append_printf(ctx, out, "/>\n");
522
0
            fz_drop_path(ctx, path);
523
0
            path = NULL;
524
0
          }
525
0
          else
526
0
          {
527
0
            fz_append_printf(ctx, out, "<g id=\"font_%d_%d\"></g>\n", fnt->id, gid);
528
0
          }
529
0
        }
530
0
        else if (fz_font_t3_procs(ctx, span->font))
531
0
        {
532
0
          fz_append_printf(ctx, out, "<g id=\"font_%d_%d\">\n", fnt->id, gid);
533
0
          fz_run_t3_glyph(ctx, span->font, gid, fz_identity, dev);
534
0
          fnt = &sdev->fonts[font_idx]; /* recursion may realloc the font array! */
535
0
          fz_append_printf(ctx, out, "</g>\n");
536
0
        }
537
0
        out = end_def(ctx, sdev, 1);
538
0
        fnt->sentlist[gid] = 1;
539
0
      }
540
0
    }
541
0
  }
542
0
  fz_catch(ctx)
543
0
  {
544
0
    fz_drop_path(ctx, path);
545
0
    fz_rethrow(ctx);
546
0
  }
547
548
0
  return fnt;
549
0
}
550
551
static int is_valid_xml_char(int c)
552
0
{
553
  /* exclude C0 (except tab and newline) and C1 and surrogates */
554
0
  return (
555
0
    c == 0x9 ||
556
0
    c == 0xA ||
557
0
    c == 0xD ||
558
0
    (c >= 0x20 && c <= 0x7E) ||
559
0
    (c >= 0xA0 && c <= 0xD7FF) ||
560
0
    (c >= 0xE000 && c <= 0xFFFD) ||
561
0
    (c >= 0x10000 && c <= 0x10FFFF)
562
0
  );
563
0
}
564
565
static void
566
svg_dev_data_text(fz_context *ctx, fz_buffer *out, int c)
567
0
{
568
0
  if (c > 0)
569
0
  {
570
0
    fz_append_string(ctx, out, " data-text=\"");
571
0
    if (c == '&')
572
0
      fz_append_string(ctx, out, "&amp;");
573
0
    else if (c == '"')
574
0
      fz_append_string(ctx, out, "&quot;");
575
0
    else if (c >= 32 && c < 127 && c != '<' && c != '>')
576
0
      fz_append_byte(ctx, out, c);
577
0
    else if (!is_valid_xml_char(c))
578
      /* no surrogate or other invalid characters in SVG */
579
0
      fz_append_printf(ctx, out, "&#xFFFD;");
580
0
    else
581
0
      fz_append_printf(ctx, out, "&#x%04x;", c);
582
0
    fz_append_byte(ctx, out, '"');
583
0
  }
584
0
}
585
586
static void
587
svg_dev_text_span_as_paths_fill(fz_context *ctx, fz_device *dev, const fz_text_span *span, fz_matrix ctm,
588
  fz_colorspace *colorspace, const float *color, float alpha, svg_font *fnt, fz_color_params color_params)
589
0
{
590
0
  svg_device *sdev = (svg_device*)dev;
591
0
  fz_buffer *out = sdev->out;
592
0
  fz_matrix trm, mtx;
593
0
  int i;
594
595
  /* Rely on the fact that trm.{e,f} == 0 */
596
0
  trm.a = span->trm.a;
597
0
  trm.b = span->trm.b;
598
0
  trm.c = span->trm.c;
599
0
  trm.d = span->trm.d;
600
0
  trm.e = 0;
601
0
  trm.f = 0;
602
603
0
  for (i=0; i < span->len; i++)
604
0
  {
605
0
    fz_text_item *it = &span->items[i];
606
0
    int gid = it->gid;
607
0
    if (gid < 0)
608
0
      continue;
609
610
0
    trm.e = it->x;
611
0
    trm.f = it->y;
612
0
    mtx = fz_concat(trm, ctm);
613
614
0
    fz_append_string(ctx, out, "<use");
615
0
    svg_dev_data_text(ctx, out, it->ucs);
616
0
    fz_append_printf(ctx, out, " xlink:href=\"#font_%d_%d\"", fnt->id, gid);
617
0
    svg_dev_ctm(ctx, sdev, mtx);
618
0
    svg_dev_fill_color(ctx, sdev, colorspace, color, alpha, color_params);
619
0
    fz_append_printf(ctx, out, "/>\n");
620
0
  }
621
0
}
622
623
static void
624
svg_dev_text_span_as_paths_stroke(fz_context *ctx, fz_device *dev, const fz_text_span *span,
625
  const fz_stroke_state *stroke, fz_matrix ctm,
626
  fz_colorspace *colorspace, const float *color, float alpha, svg_font *fnt, fz_color_params color_params)
627
0
{
628
0
  svg_device *sdev = (svg_device*)dev;
629
0
  fz_buffer *out = sdev->out;
630
0
  fz_matrix trm, mtx;
631
0
  int i;
632
633
  /* Rely on the fact that trm.{e,f} == 0 */
634
0
  trm.a = span->trm.a;
635
0
  trm.b = span->trm.b;
636
0
  trm.c = span->trm.c;
637
0
  trm.d = span->trm.d;
638
0
  trm.e = 0;
639
0
  trm.f = 0;
640
641
0
  for (i=0; i < span->len; i++)
642
0
  {
643
0
    fz_text_item *it = &span->items[i];
644
0
    int gid = it->gid;
645
0
    if (gid < 0)
646
0
      continue;
647
648
0
    trm.e = it->x;
649
0
    trm.f = it->y;
650
0
    mtx = fz_concat(trm, ctm);
651
652
0
    fz_append_string(ctx, out, "<use");
653
0
    svg_dev_data_text(ctx, out, it->ucs);
654
0
    fz_append_printf(ctx, out, " xlink:href=\"#font_%d_%d\"", fnt->id, gid);
655
0
    svg_dev_stroke_state(ctx, sdev, stroke, mtx);
656
0
    svg_dev_ctm(ctx, sdev, mtx);
657
0
    svg_dev_stroke_color(ctx, sdev, colorspace, color, alpha, color_params);
658
0
    fz_append_printf(ctx, out, "/>\n");
659
0
  }
660
0
}
661
662
/* Entry points */
663
664
static void
665
svg_dev_fill_path(fz_context *ctx, fz_device *dev, const fz_path *path, int even_odd, fz_matrix ctm,
666
  fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params)
667
0
{
668
0
  svg_device *sdev = (svg_device*)dev;
669
0
  fz_buffer *out = sdev->out;
670
671
0
  fz_append_printf(ctx, out, "<path");
672
0
  svg_dev_ctm(ctx, sdev, ctm);
673
0
  svg_dev_path(ctx, sdev, path);
674
0
  svg_dev_fill_color(ctx, sdev, colorspace, color, alpha, color_params);
675
0
  if (even_odd)
676
0
    fz_append_printf(ctx, out, " fill-rule=\"evenodd\"");
677
0
  fz_append_printf(ctx, out, "/>\n");
678
0
}
679
680
static void
681
svg_dev_stroke_path(fz_context *ctx, fz_device *dev, const fz_path *path, const fz_stroke_state *stroke, fz_matrix ctm,
682
  fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params)
683
0
{
684
0
  svg_device *sdev = (svg_device*)dev;
685
0
  fz_buffer *out = sdev->out;
686
687
0
  fz_append_printf(ctx, out, "<path");
688
0
  svg_dev_ctm(ctx, sdev, ctm);
689
0
  svg_dev_stroke_state(ctx, sdev, stroke, fz_identity);
690
0
  svg_dev_stroke_color(ctx, sdev, colorspace, color, alpha, color_params);
691
0
  svg_dev_path(ctx, sdev, path);
692
0
  fz_append_printf(ctx, out, "/>\n");
693
0
}
694
695
static void
696
svg_dev_clip_path(fz_context *ctx, fz_device *dev, const fz_path *path, int even_odd, fz_matrix ctm, fz_rect scissor)
697
0
{
698
0
  svg_device *sdev = (svg_device*)dev;
699
0
  fz_buffer *out;
700
701
0
  int num = sdev->id++;
702
703
0
  out = start_def(ctx, sdev, 0);
704
0
  fz_append_printf(ctx, out, "<clipPath id=\"clip_%d\">\n", num);
705
0
  fz_append_printf(ctx, out, "<path");
706
0
  svg_dev_ctm(ctx, sdev, ctm);
707
0
  svg_dev_path(ctx, sdev, path);
708
0
  if (even_odd)
709
0
    fz_append_printf(ctx, out, " clip-rule=\"evenodd\"");
710
0
  fz_append_printf(ctx, out, "/>\n</clipPath>\n");
711
0
  out = end_def(ctx, sdev, 0);
712
0
  fz_append_printf(ctx, out, "<g clip-path=\"url(#clip_%d)\">\n", num);
713
0
}
714
715
static void
716
svg_dev_clip_stroke_path(fz_context *ctx, fz_device *dev, const fz_path *path, const fz_stroke_state *stroke, fz_matrix ctm, fz_rect scissor)
717
0
{
718
0
  svg_device *sdev = (svg_device*)dev;
719
720
0
  fz_buffer *out;
721
0
  fz_rect bounds;
722
0
  int num = sdev->id++;
723
0
  float white[3] = { 1, 1, 1 };
724
725
0
  bounds = fz_bound_path(ctx, path, stroke, ctm);
726
727
0
  out = start_def(ctx, sdev, 0);
728
0
  fz_append_printf(ctx, out, "<mask id=\"mask_%d\" x=\"%g\" y=\"%g\" width=\"%g\" height=\"%g\" maskUnits=\"userSpaceOnUse\" maskContentUnits=\"userSpaceOnUse\">\n",
729
0
    num, bounds.x0, bounds.y0, bounds.x1 - bounds.x0, bounds.y1 - bounds.y0);
730
0
  fz_append_printf(ctx, out, "<path");
731
0
  svg_dev_ctm(ctx, sdev, ctm);
732
0
  svg_dev_stroke_state(ctx, sdev, stroke, fz_identity);
733
0
  svg_dev_stroke_color(ctx, sdev, fz_device_rgb(ctx), white, 1, fz_default_color_params);
734
0
  svg_dev_path(ctx, sdev, path);
735
0
  fz_append_printf(ctx, out, "/>\n</mask>\n");
736
0
  out = end_def(ctx, sdev, 0);
737
0
  fz_append_printf(ctx, out, "<g mask=\"url(#mask_%d)\">\n", num);
738
0
}
739
740
static void
741
svg_dev_fill_text(fz_context *ctx, fz_device *dev, const fz_text *text, fz_matrix ctm,
742
  fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params)
743
0
{
744
0
  svg_device *sdev = (svg_device*)dev;
745
0
  fz_buffer *out = sdev->out;
746
0
  svg_font *fnt;
747
0
  fz_text_span *span;
748
749
0
  if (sdev->text_as_text)
750
0
  {
751
0
    for (span = text->head; span; span = span->next)
752
0
    {
753
0
      fz_append_printf(ctx, out, "<text");
754
0
      svg_dev_fill_color(ctx, sdev, colorspace, color, alpha, color_params);
755
0
      svg_dev_text_span(ctx, sdev, ctm, span);
756
0
    }
757
0
  }
758
0
  else
759
0
  {
760
0
    for (span = text->head; span; span = span->next)
761
0
    {
762
0
      fnt = svg_dev_text_span_as_paths_defs(ctx, dev, span, ctm);
763
0
      svg_dev_text_span_as_paths_fill(ctx, dev, span, ctm, colorspace, color, alpha, fnt, color_params);
764
0
    }
765
0
  }
766
0
}
767
768
static void
769
svg_dev_stroke_text(fz_context *ctx, fz_device *dev, const fz_text *text, const fz_stroke_state *stroke, fz_matrix ctm,
770
  fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params)
771
0
{
772
0
  svg_device *sdev = (svg_device*)dev;
773
0
  fz_buffer *out = sdev->out;
774
0
  svg_font *fnt;
775
0
  fz_text_span *span;
776
777
0
  if (sdev->text_as_text)
778
0
  {
779
0
    for (span = text->head; span; span = span->next)
780
0
    {
781
0
      fz_append_printf(ctx, out, "<text");
782
0
      svg_dev_fill_color(ctx, sdev, colorspace, color, alpha, color_params);
783
0
      svg_dev_text_span(ctx, sdev, ctm, span);
784
0
    }
785
0
  }
786
0
  else
787
0
  {
788
0
    for (span = text->head; span; span = span->next)
789
0
    {
790
0
      fnt = svg_dev_text_span_as_paths_defs(ctx, dev, span, ctm);
791
0
      svg_dev_text_span_as_paths_stroke(ctx, dev, span, stroke, ctm, colorspace, color, alpha, fnt, color_params);
792
0
    }
793
0
  }
794
0
}
795
796
static void
797
svg_dev_clip_text(fz_context *ctx, fz_device *dev, const fz_text *text, fz_matrix ctm, fz_rect scissor)
798
0
{
799
0
  svg_device *sdev = (svg_device*)dev;
800
0
  fz_buffer *out = sdev->out;
801
802
0
  fz_rect bounds;
803
0
  int num = sdev->id++;
804
0
  float white[3] = { 1, 1, 1 };
805
0
  svg_font *fnt;
806
0
  fz_text_span *span;
807
808
0
  bounds = fz_bound_text(ctx, text, NULL, ctm);
809
810
0
  out = start_def(ctx, sdev, 0);
811
0
  fz_append_printf(ctx, out, "<mask id=\"mask_%d\" x=\"%g\" y=\"%g\" width=\"%g\" height=\"%g\"",
812
0
      num, bounds.x0, bounds.y0, bounds.x1 - bounds.x0, bounds.y1 - bounds.y0);
813
0
  fz_append_printf(ctx, out, " maskUnits=\"userSpaceOnUse\" maskContentUnits=\"userSpaceOnUse\">\n");
814
0
  if (sdev->text_as_text)
815
0
  {
816
0
    for (span = text->head; span; span = span->next)
817
0
    {
818
0
      fz_append_printf(ctx, out, "<text");
819
0
      svg_dev_fill_color(ctx, sdev, fz_device_rgb(ctx), white, 1, fz_default_color_params);
820
0
      svg_dev_text_span(ctx, sdev, ctm, span);
821
0
    }
822
0
  }
823
0
  else
824
0
  {
825
0
    for (span = text->head; span; span = span->next)
826
0
    {
827
0
      fnt = svg_dev_text_span_as_paths_defs(ctx, dev, span, ctm);
828
0
      svg_dev_text_span_as_paths_fill(ctx, dev, span, ctm, fz_device_rgb(ctx), white, 1.0f, fnt, fz_default_color_params);
829
0
    }
830
0
  }
831
0
  fz_append_printf(ctx, out, "</mask>\n");
832
0
  out = end_def(ctx, sdev, 0);
833
0
  fz_append_printf(ctx, out, "<g mask=\"url(#mask_%d)\">\n", num);
834
0
}
835
836
static void
837
svg_dev_clip_stroke_text(fz_context *ctx, fz_device *dev, const fz_text *text, const fz_stroke_state *stroke, fz_matrix ctm, fz_rect scissor)
838
0
{
839
0
  svg_device *sdev = (svg_device*)dev;
840
841
0
  fz_buffer *out;
842
0
  fz_rect bounds;
843
0
  int num = sdev->id++;
844
0
  float white[3] = { 255, 255, 255 };
845
0
  svg_font *fnt;
846
0
  fz_text_span *span;
847
848
0
  bounds = fz_bound_text(ctx, text, NULL, ctm);
849
850
0
  out = start_def(ctx, sdev, 0);
851
0
  fz_append_printf(ctx, out, "<mask id=\"mask_%d\" x=\"%g\" y=\"%g\" width=\"%g\" height=\"%g\"",
852
0
    num, bounds.x0, bounds.y0, bounds.x1 - bounds.x0, bounds.y1 - bounds.y0);
853
0
  fz_append_printf(ctx, out, " maskUnits=\"userSpaceOnUse\" maskContentUnits=\"userSpaceOnUse\">\n");
854
0
  if (sdev->text_as_text)
855
0
  {
856
0
    for (span = text->head; span; span = span->next)
857
0
    {
858
0
      fz_append_printf(ctx, out, "<text");
859
0
      svg_dev_stroke_state(ctx, sdev, stroke, fz_identity);
860
0
      svg_dev_stroke_color(ctx, sdev, fz_device_rgb(ctx), white, 1, fz_default_color_params);
861
0
      svg_dev_text_span(ctx, sdev, ctm, span);
862
0
    }
863
0
  }
864
0
  else
865
0
  {
866
0
    for (span = text->head; span; span = span->next)
867
0
    {
868
0
      fnt = svg_dev_text_span_as_paths_defs(ctx, dev, span, ctm);
869
0
      svg_dev_text_span_as_paths_stroke(ctx, dev, span, stroke, ctm, fz_device_rgb(ctx), white, 1.0f, fnt, fz_default_color_params);
870
0
    }
871
0
  }
872
0
  fz_append_printf(ctx, out, "</mask>\n");
873
0
  out = end_def(ctx, sdev, 0);
874
0
  fz_append_printf(ctx, out, "<g mask=\"url(#mask_%d)\">\n", num);
875
0
}
876
877
static void
878
svg_dev_ignore_text(fz_context *ctx, fz_device *dev, const fz_text *text, fz_matrix ctm)
879
0
{
880
0
  svg_device *sdev = (svg_device*)dev;
881
0
  fz_buffer *out = sdev->out;
882
0
  fz_text_span *span;
883
884
0
  float black[3] = { 0, 0, 0};
885
886
0
  if (sdev->text_as_text)
887
0
  {
888
0
    for (span = text->head; span; span = span->next)
889
0
    {
890
0
      fz_append_printf(ctx, out, "<text");
891
0
      svg_dev_fill_color(ctx, sdev, fz_device_rgb(ctx), black, 0.0f, fz_default_color_params);
892
0
      svg_dev_text_span(ctx, sdev, ctm, span);
893
0
    }
894
0
  }
895
0
}
896
897
/* We spot repeated images, and send them just once using
898
 * defs. Unfortunately, for pathological files, such
899
 * as the example in Bug695988, this can cause viewers to
900
 * have conniptions. We therefore have an option that is
901
 * made to avoid this (reuse-images=no). */
902
static void
903
svg_send_image(fz_context *ctx, svg_device *sdev, fz_image *img, fz_color_params color_params)
904
0
{
905
0
  fz_buffer *out = sdev->out;
906
0
  int i;
907
0
  int id;
908
909
0
  if (sdev->reuse_images)
910
0
  {
911
0
    svg_image *si;
912
913
0
    for (i = sdev->images_len-1; i >= 0; i--)
914
0
      if (img == sdev->images[i].image)
915
0
        break;
916
0
    if (i >= 0)
917
0
    {
918
0
      fz_append_printf(ctx, out, "<use xlink:href=\"#image_%d\" x=\"0\" y=\"0\" width=\"%d\" height=\"%d\"/>\n",
919
0
          sdev->images[i].id, img->w, img->h);
920
0
      return;
921
0
    }
922
923
0
    id = sdev->id++;
924
925
0
    fz_append_printf(ctx, out, "<image id=\"image_%d\" width=\"%d\" height=\"%d\" xlink:href=\"", id, img->w, img->h);
926
0
    fz_append_image_as_data_uri(ctx, out, img);
927
0
    fz_append_printf(ctx, out, "\"/>\n");
928
929
    /* We need to send this image for the first time */
930
0
    si = fz_push_list(ctx, sdev->images);
931
932
0
    si->id = id;
933
0
    si->image = fz_keep_image(ctx, img);
934
0
  }
935
0
  else
936
0
  {
937
0
    fz_append_printf(ctx, out, "<image width=\"%d\" height=\"%d\" xlink:href=\"", img->w, img->h);
938
0
    fz_append_image_as_data_uri(ctx, out, img);
939
0
    fz_append_printf(ctx, out, "\"/>\n");
940
0
  }
941
0
}
942
943
static void
944
svg_dev_fill_image(fz_context *ctx, fz_device *dev, fz_image *image, fz_matrix ctm, float alpha, fz_color_params color_params)
945
0
{
946
0
  svg_device *sdev = (svg_device*)dev;
947
0
  fz_buffer *out = sdev->out;
948
949
0
  fz_matrix local_ctm = ctm;
950
0
  fz_matrix scale = { 0 };
951
952
0
  if (alpha == 0)
953
0
    return;
954
955
0
  scale.a = 1.0f / image->w;
956
0
  scale.d = 1.0f / image->h;
957
958
0
  local_ctm = fz_concat(scale, ctm);
959
0
  fz_append_printf(ctx, out, "<g");
960
0
  if (alpha != 1.0f)
961
0
    fz_append_printf(ctx, out, " opacity=\"%g\"", alpha);
962
0
  svg_dev_ctm(ctx, sdev, local_ctm);
963
0
  fz_append_printf(ctx, out, ">\n");
964
0
  svg_send_image(ctx, sdev, image, color_params);
965
0
  fz_append_printf(ctx, out, "</g>\n");
966
0
}
967
968
static void
969
svg_dev_fill_shade(fz_context *ctx, fz_device *dev, fz_shade *shade, fz_matrix ctm, float alpha, fz_color_params color_params)
970
0
{
971
0
  svg_device *sdev = (svg_device*)dev;
972
0
  fz_buffer *out = sdev->out;
973
0
  fz_rect rect;
974
0
  fz_irect bbox;
975
0
  fz_pixmap *pix;
976
0
  fz_rect scissor = fz_device_current_scissor(ctx, dev);
977
978
0
  if (alpha == 0)
979
0
    return;
980
981
0
  if (fz_is_infinite_rect(scissor))
982
0
  {
983
0
    scissor.x0 = 0;
984
0
    scissor.x1 = sdev->page_width;
985
0
    scissor.y0 = 0;
986
0
    scissor.y1 = sdev->page_height;
987
0
  }
988
989
0
  rect = fz_intersect_rect(fz_bound_shade(ctx, shade, ctm), scissor);
990
0
  bbox = fz_round_rect(fz_transform_rect(rect, fz_scale(sdev->raster_scale, sdev->raster_scale)));
991
0
  if (fz_is_empty_irect(bbox))
992
0
    return;
993
0
  pix = fz_new_pixmap_with_bbox(ctx, fz_device_rgb(ctx), bbox, NULL, 1);
994
0
  fz_clear_pixmap(ctx, pix);
995
996
0
  fz_try(ctx)
997
0
  {
998
0
    fz_paint_shade(ctx, shade, NULL, fz_post_scale(ctm, sdev->raster_scale, sdev->raster_scale), pix, color_params, bbox, NULL, NULL);
999
0
    if (alpha != 1.0f)
1000
0
      fz_append_printf(ctx, out, "<g opacity=\"%g\">\n", alpha);
1001
0
    fz_append_printf(ctx, out, "<image x=\"%g\" y=\"%g\" width=\"%g\" height=\"%g\" xlink:href=\"", pix->x/sdev->raster_scale, pix->y/sdev->raster_scale, pix->w/sdev->raster_scale, pix->h/sdev->raster_scale);
1002
0
    fz_append_pixmap_as_data_uri(ctx, out, pix);
1003
0
    fz_append_printf(ctx, out, "\"/>\n");
1004
0
    if (alpha != 1.0f)
1005
0
      fz_append_printf(ctx, out, "</g>\n");
1006
0
  }
1007
0
  fz_always(ctx)
1008
0
  {
1009
0
    fz_drop_pixmap(ctx, pix);
1010
0
  }
1011
0
  fz_catch(ctx)
1012
0
  {
1013
0
    fz_rethrow(ctx);
1014
0
  }
1015
0
}
1016
1017
static void
1018
svg_dev_fill_image_mask(fz_context *ctx, fz_device *dev, fz_image *image, fz_matrix ctm,
1019
  fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params)
1020
0
{
1021
0
  svg_device *sdev = (svg_device*)dev;
1022
0
  fz_buffer *out;
1023
0
  fz_matrix local_ctm = ctm;
1024
0
  fz_matrix scale = { 0 };
1025
0
  int mask = sdev->id++;
1026
1027
0
  scale.a = 1.0f / image->w;
1028
0
  scale.d = 1.0f / image->h;
1029
1030
0
  local_ctm = fz_concat(scale, ctm);
1031
0
  out = start_def(ctx, sdev, 0);
1032
0
  fz_append_printf(ctx, out, "<mask id=\"mask_%d\">\n", mask);
1033
0
  svg_send_image(ctx, sdev, image, color_params);
1034
0
  fz_append_printf(ctx, out, "</mask>\n");
1035
0
  out = end_def(ctx, sdev, 0);
1036
0
  fz_append_printf(ctx, out, "<rect x=\"0\" y=\"0\" width=\"%d\" height=\"%d\"", image->w, image->h);
1037
0
  svg_dev_fill_color(ctx, sdev, colorspace, color, alpha, color_params);
1038
0
  svg_dev_ctm(ctx, sdev, local_ctm);
1039
0
  fz_append_printf(ctx, out, " mask=\"url(#mask_%d)\"/>\n", mask);
1040
0
}
1041
1042
static void
1043
svg_dev_clip_image_mask(fz_context *ctx, fz_device *dev, fz_image *image, fz_matrix ctm, fz_rect scissor)
1044
0
{
1045
0
  svg_device *sdev = (svg_device*)dev;
1046
0
  fz_buffer *out;
1047
0
  fz_matrix local_ctm = ctm;
1048
0
  fz_matrix scale = { 0 };
1049
0
  int mask = sdev->id++;
1050
1051
0
  scale.a = 1.0f / image->w;
1052
0
  scale.d = 1.0f / image->h;
1053
1054
0
  local_ctm = fz_concat(scale, ctm);
1055
0
  out = start_def(ctx, sdev, 0);
1056
0
  fz_append_printf(ctx, out, "<mask id=\"mask_%d\">\n<g", mask);
1057
0
  svg_dev_ctm(ctx, sdev, local_ctm);
1058
0
  fz_append_printf(ctx, out, ">\n");
1059
0
  svg_send_image(ctx, sdev, image, fz_default_color_params/* FIXME */);
1060
0
  fz_append_printf(ctx, out, "</g>\n</mask>\n");
1061
0
  out = end_def(ctx, sdev, 0);
1062
0
  fz_append_printf(ctx, out, "<g mask=\"url(#mask_%d)\">\n", mask);
1063
0
}
1064
1065
static void
1066
svg_dev_pop_clip(fz_context *ctx, fz_device *dev)
1067
0
{
1068
0
  svg_device *sdev = (svg_device*)dev;
1069
0
  fz_buffer *out = sdev->out;
1070
1071
  /* FIXME */
1072
0
  fz_append_printf(ctx, out, "</g>\n");
1073
0
}
1074
1075
static void
1076
svg_dev_begin_mask(fz_context *ctx, fz_device *dev, fz_rect bbox, int luminosity, fz_colorspace *colorspace, const float *color, fz_color_params color_params)
1077
0
{
1078
0
  svg_device *sdev = (svg_device*)dev;
1079
0
  fz_buffer *out;
1080
0
  int mask = sdev->id++;
1081
1082
0
  out = start_def(ctx, sdev, 0);
1083
0
  fz_append_printf(ctx, out, "<g id=\"mask_%d_contents\">\n", mask);
1084
1085
  /* For luminosity masks, we need to ensure the entire mask is "white", as otherwise it's initialised to
1086
   * transparent. For alpha masks, we need to ensure that the mask is actually large enough, otherwise
1087
   * SVG will minimise it. */
1088
0
  if (luminosity)
1089
0
    fz_append_printf(ctx, out, "<rect x=\"0\" y=\"0\" width=\"100%%\" height=\"100%%\" fill=\"white\"/>\n");
1090
0
  else
1091
0
    fz_append_printf(ctx, out, "<rect x=\"0\" y=\"0\" width=\"100%%\" height=\"100%%\" fill-opacity=\"0\"/>\n");
1092
1093
0
  if (dev->container_len > 0)
1094
0
    dev->container[dev->container_len-1].user = mask ^ (luminosity ? 0x80000000 : 0);
1095
0
}
1096
1097
static void
1098
send_tr(fz_context *ctx, fz_buffer *out, const char *s, fz_function *fn)
1099
0
{
1100
0
  int i;
1101
1102
0
  fz_append_printf(ctx, out, "<%s type=\"table\" tableValues=\"", s);
1103
0
  for (i = 0; i < 256; i++)
1104
0
  {
1105
0
    float f = i/255.0f;
1106
0
    float g;
1107
0
    fz_eval_function(ctx, fn, &f, 1, &g, 1);
1108
0
    fz_append_printf(ctx, out, "%g ", g);
1109
0
  }
1110
0
  fz_append_printf(ctx, out, "\"></%s>\n", s);
1111
0
}
1112
1113
static void
1114
svg_dev_end_mask(fz_context *ctx, fz_device *dev, fz_function *tr)
1115
0
{
1116
0
  svg_device *sdev = (svg_device*)dev;
1117
0
  fz_buffer *out = sdev->out;
1118
0
  int mask = 0;
1119
0
  int luminosity = 0;
1120
1121
0
  if (dev->container_len > 0)
1122
0
    mask = dev->container[dev->container_len-1].user;
1123
0
  if (mask & 0x80000000)
1124
0
    luminosity = 1, mask &= ~0x80000000;
1125
1126
0
  fz_append_printf(ctx, out, "</g>\n");
1127
1128
0
  if (tr)
1129
0
  {
1130
0
    fz_append_printf(ctx, out, "<filter id=\"tr_%d\">\n", mask);
1131
0
    fz_append_printf(ctx, out, "<feComponentTransfer>\n");
1132
0
    if (luminosity)
1133
0
    {
1134
0
      send_tr(ctx, out, "feFuncR", tr);
1135
0
      send_tr(ctx, out, "feFuncG", tr);
1136
0
      send_tr(ctx, out, "feFuncB", tr);
1137
0
    }
1138
0
    else
1139
0
      send_tr(ctx, out, "feFuncA", tr);
1140
0
    fz_append_printf(ctx, out, "</feComponentTransfer>\n");
1141
0
    fz_append_printf(ctx, out, "</filter>\n");
1142
0
  }
1143
1144
0
  fz_append_printf(ctx, out, "<mask id=\"mask_%d\"", mask);
1145
0
  if (luminosity)
1146
0
    fz_append_printf(ctx, out, " mask-type=\"luminance\"");
1147
0
  else
1148
0
    fz_append_printf(ctx, out, " mask-type=\"alpha\"");
1149
0
  fz_append_printf(ctx, out, ">\n");
1150
0
  fz_append_printf(ctx, out, "<use xlink:href=\"#mask_%d_contents\"", mask);
1151
0
  if (tr)
1152
0
  {
1153
0
    fz_append_printf(ctx, out, " filter=\"url(#tr_%d)\"", mask);
1154
0
  }
1155
0
  fz_append_printf(ctx, out, "/>\n</mask>\n");
1156
1157
0
  out = end_def(ctx, sdev, 0);
1158
0
  fz_append_printf(ctx, out, "<g mask=\"url(#mask_%d)\">\n", mask);
1159
0
}
1160
1161
static void
1162
svg_dev_begin_group(fz_context *ctx, fz_device *dev, fz_rect bbox, fz_colorspace *cs, int isolated, int knockout, int blendmode, float alpha)
1163
0
{
1164
0
  svg_device *sdev = (svg_device*)dev;
1165
0
  fz_buffer *out = sdev->out;
1166
1167
  /* SVG only supports normal/multiply/screen/darken/lighten,
1168
   * but we'll send them all, as the spec says that unrecognised
1169
   * ones are treated as normal. */
1170
0
  static char *blend_names[] = {
1171
0
    "normal", /* FZ_BLEND_NORMAL */
1172
0
    "multiply", /* FZ_BLEND_MULTIPLY */
1173
0
    "screen", /* FZ_BLEND_SCREEN */
1174
0
    "overlay",  /* FZ_BLEND_OVERLAY */
1175
0
    "darken", /* FZ_BLEND_DARKEN */
1176
0
    "lighten",  /* FZ_BLEND_LIGHTEN */
1177
0
    "color-dodge",  /* FZ_BLEND_COLOR_DODGE */
1178
0
    "color-burn", /* FZ_BLEND_COLOR_BURN */
1179
0
    "hard-light", /* FZ_BLEND_HARD_LIGHT */
1180
0
    "soft-light", /* FZ_BLEND_SOFT_LIGHT */
1181
0
    "difference", /* FZ_BLEND_DIFFERENCE */
1182
0
    "exclusion",  /* FZ_BLEND_EXCLUSION */
1183
0
    "hue",    /* FZ_BLEND_HUE */
1184
0
    "saturation", /* FZ_BLEND_SATURATION */
1185
0
    "color",  /* FZ_BLEND_COLOR */
1186
0
    "luminosity", /* FZ_BLEND_LUMINOSITY */
1187
0
  };
1188
1189
0
  if (blendmode < FZ_BLEND_NORMAL || blendmode > FZ_BLEND_LUMINOSITY)
1190
0
    blendmode = FZ_BLEND_NORMAL;
1191
0
  if (blendmode != FZ_BLEND_NORMAL && (sdev->blend_bitmask & (1<<blendmode)) == 0)
1192
0
    sdev->blend_bitmask |= (1<<blendmode);
1193
1194
  /* FIXME: Handle alpha == 0 somehow? */
1195
  /* SVG 1.1 doesn't support adequate blendmodes/knockout etc, so just ignore it for now */
1196
0
  if (alpha == 1)
1197
0
    fz_append_printf(ctx, out, "<g");
1198
0
  else
1199
0
    fz_append_printf(ctx, out, "<g opacity=\"%g\"", alpha);
1200
0
  if (blendmode != FZ_BLEND_NORMAL)
1201
0
    fz_append_printf(ctx, out, " style=\"mix-blend-mode:%s\"", blend_names[blendmode]);
1202
0
  fz_append_printf(ctx, out, ">\n");
1203
0
}
1204
1205
static void
1206
svg_dev_end_group(fz_context *ctx, fz_device *dev)
1207
0
{
1208
0
  svg_device *sdev = (svg_device*)dev;
1209
0
  fz_buffer *out = sdev->out;
1210
1211
0
  fz_append_printf(ctx, out, "</g>\n");
1212
0
}
1213
1214
static int
1215
svg_dev_begin_tile(fz_context *ctx, fz_device *dev, fz_rect area, fz_rect view, float xstep, float ystep, fz_matrix ctm, int id, int doc_id)
1216
0
{
1217
0
  svg_device *sdev = (svg_device*)dev;
1218
0
  fz_buffer *out;
1219
0
  svg_tile *t;
1220
1221
0
  t = fz_push_list(ctx, sdev->tiles);
1222
0
  t->area = area;
1223
0
  t->view = view;
1224
0
  t->ctm = ctm;
1225
0
  t->pattern = sdev->id++;
1226
1227
0
  xstep = fabsf(xstep);
1228
0
  ystep = fabsf(ystep);
1229
0
  if (xstep == 0 || ystep == 0) {
1230
0
    fz_warn(ctx, "Pattern cannot have x or ystep == 0.");
1231
0
    if (xstep == 0)
1232
0
      xstep = 1;
1233
0
    if (ystep == 0)
1234
0
      ystep = 1;
1235
0
  }
1236
1237
0
  t->step.x = xstep;
1238
0
  t->step.y = ystep;
1239
1240
  /* view = area of our reference tile in pattern space.
1241
   * area = area to tile into in pattern space.
1242
   * xstep/ystep = pattern repeat step in pattern space.
1243
   * All of these need to be transformed by ctm to get to device space.
1244
   * SVG only allows us to specify pattern tiles as axis aligned
1245
   * rectangles, so we send these through as is, and ensure that the
1246
   * correct matrix is used on the fill.
1247
   */
1248
1249
  /* The first thing we do is to capture the contents of the pattern
1250
   * as a def we can reuse. */
1251
0
  out = start_def(ctx, sdev, 1);
1252
0
  fz_append_printf(ctx, out, "<g id=\"pattern_tile_%d\">\n", t->pattern);
1253
1254
0
  return 0;
1255
0
}
1256
1257
static void
1258
svg_dev_end_tile(fz_context *ctx, fz_device *dev)
1259
0
{
1260
0
  svg_device *sdev = (svg_device*)dev;
1261
0
  fz_buffer *out = sdev->out;
1262
0
  int num, cp = -1;
1263
0
  svg_tile *t;
1264
0
  fz_matrix inverse;
1265
0
  float x, y, w, h;
1266
1267
0
  if (sdev->tiles_len == 0)
1268
0
    return;
1269
0
  num = --sdev->tiles_len;
1270
0
  t = &sdev->tiles[num];
1271
1272
0
  fz_append_printf(ctx, out, "</g>\n");
1273
1274
  /* In svg, the reference tile is taken from (x,y) to (x+width,y+height)
1275
   * and is repeated at (x+n*width,y+m*height) for all integer n and m.
1276
   * This means that width and height generally correspond to xstep and
1277
   * ystep. There are exceptional cases where we have to break this
1278
   * though; when xstep/ystep are smaller than the width/height of the
1279
   * pattern tile, we need to render the pattern contents several times
1280
   * to ensure that the pattern tile contains everything. */
1281
1282
0
  fz_append_printf(ctx, out, "<pattern id=\"pattern_%d\" patternUnits=\"userSpaceOnUse\" patternContentUnits=\"userSpaceOnUse\"",
1283
0
    t->pattern);
1284
0
  fz_append_printf(ctx, out, " x=\"0\" y=\"0\" width=\"%g\" height=\"%g\">\n",
1285
0
    t->step.x, t->step.y);
1286
1287
0
  if (t->view.x0 > 0 || t->step.x < t->view.x1 || t->view.y0 > 0 || t->step.y < t->view.y1)
1288
0
  {
1289
0
    cp = sdev->id++;
1290
0
    fz_append_printf(ctx, out, "<clipPath id=\"clip_%d\">\n", cp);
1291
0
    fz_append_printf(ctx, out, "<path d=\"M %g %g L %g %g L %g %g L %g %g Z\"/>\n",
1292
0
      t->view.x0, t->view.y0,
1293
0
      t->view.x1, t->view.y0,
1294
0
      t->view.x1, t->view.y1,
1295
0
      t->view.x0, t->view.y1);
1296
0
    fz_append_printf(ctx, out, "</clipPath>\n");
1297
0
    fz_append_printf(ctx, out, "<g clip-path=\"url(#clip_%d)\">\n", cp);
1298
0
  }
1299
1300
  /* All the pattern contents will have their own ctm applied. Let's
1301
   * undo the current one to allow for this */
1302
0
  inverse = fz_invert_matrix(t->ctm);
1303
0
  fz_append_printf(ctx, out, "<g");
1304
0
  svg_dev_ctm(ctx, sdev, inverse);
1305
0
  fz_append_printf(ctx, out, ">\n");
1306
1307
0
  w = t->view.x1 - t->view.x0;
1308
0
  h = t->view.y1 - t->view.y0;
1309
1310
0
  for (x = 0; x > -w; x -= t->step.x)
1311
0
    for (y = 0; y > -h; y -= t->step.y)
1312
0
      fz_append_printf(ctx, out, "<use x=\"%g\" y=\"%g\" xlink:href=\"#pattern_tile_%d\"/>\n", x, y, t->pattern);
1313
1314
0
  fz_append_printf(ctx, out, "</g>\n");
1315
0
  if (cp != -1)
1316
0
    fz_append_printf(ctx, out, "</g>\n");
1317
0
  fz_append_printf(ctx, out, "</pattern>\n");
1318
0
  out = end_def(ctx, sdev, 1);
1319
1320
  /* Finally, fill a rectangle with the pattern. */
1321
0
  fz_append_printf(ctx, out, "<rect");
1322
0
  svg_dev_ctm(ctx, sdev, t->ctm);
1323
0
  fz_append_printf(ctx, out, " fill=\"url(#pattern_%d)\" x=\"%g\" y=\"%g\" width=\"%g\" height=\"%g\"/>\n",
1324
0
    t->pattern, t->area.x0, t->area.y0, t->area.x1 - t->area.x0, t->area.y1 - t->area.y0);
1325
0
}
1326
1327
static void
1328
svg_dev_begin_layer(fz_context *ctx, fz_device *dev, const char *name)
1329
0
{
1330
0
  svg_device *sdev = (svg_device*)dev;
1331
0
  fz_buffer *out = sdev->out;
1332
1333
0
  sdev->layers++;
1334
0
  fz_append_printf(ctx, out, "<g inkscape:groupmode=\"layer\" inkscape:label=%<>\n", name ? name : "");
1335
0
}
1336
1337
static void
1338
svg_dev_end_layer(fz_context *ctx, fz_device *dev)
1339
0
{
1340
0
  svg_device *sdev = (svg_device*)dev;
1341
0
  fz_buffer *out = sdev->out;
1342
1343
0
  if (sdev->layers == 0)
1344
0
    return;
1345
1346
0
  sdev->layers--;
1347
0
  fz_append_printf(ctx, out, "</g>\n");
1348
0
}
1349
1350
static void
1351
svg_dev_close_device(fz_context *ctx, fz_device *dev)
1352
0
{
1353
0
  svg_device *sdev = (svg_device*)dev;
1354
0
  fz_output *out = sdev->real_out;
1355
1356
0
  while (sdev->layers > 0)
1357
0
  {
1358
0
    fz_append_string(ctx, sdev->main, "</g>\n");
1359
0
    sdev->layers--;
1360
0
  }
1361
1362
0
  if (sdev->save_id)
1363
0
    *sdev->save_id = sdev->id;
1364
1365
0
  fz_write_string(ctx, out, "<svg");
1366
0
  fz_write_string(ctx, out, " xmlns=\"http://www.w3.org/2000/svg\"");
1367
0
  fz_write_string(ctx, out, " xmlns:xlink=\"http://www.w3.org/1999/xlink\"");
1368
0
  fz_write_string(ctx, out, " xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\"");
1369
0
  fz_write_string(ctx, out, " version=\"1.1\"");
1370
0
  fz_write_printf(ctx, out, " width=\"%g\" height=\"%g\" viewBox=\"0 0 %g %g\">\n",
1371
0
    sdev->page_width, sdev->page_height, sdev->page_width, sdev->page_height);
1372
1373
0
  if (sdev->defs->len > 0)
1374
0
  {
1375
0
    fz_write_printf(ctx, out, "<defs>\n");
1376
0
    fz_write_buffer(ctx, out, sdev->defs);
1377
0
    fz_write_printf(ctx, out, "</defs>\n");
1378
0
  }
1379
1380
0
  fz_write_buffer(ctx, out, sdev->main);
1381
1382
0
  fz_write_printf(ctx, out, "</svg>\n");
1383
0
}
1384
1385
static void
1386
svg_dev_drop_device(fz_context *ctx, fz_device *dev)
1387
0
{
1388
0
  svg_device *sdev = (svg_device*)dev;
1389
0
  int i;
1390
1391
0
  fz_free(ctx, sdev->tiles);
1392
0
  fz_drop_buffer(ctx, sdev->defs);
1393
0
  fz_drop_buffer(ctx, sdev->main);
1394
0
  for (i = 0; i < sdev->fonts_len; i++)
1395
0
  {
1396
0
    fz_drop_font(ctx, sdev->fonts[i].font);
1397
0
    fz_free(ctx, sdev->fonts[i].sentlist);
1398
0
  }
1399
0
  fz_free(ctx, sdev->fonts);
1400
0
  for (i = 0; i < sdev->images_len; i++)
1401
0
  {
1402
0
    fz_drop_image(ctx, sdev->images[i].image);
1403
0
  }
1404
0
  fz_free(ctx, sdev->images);
1405
0
}
1406
1407
void
1408
fz_init_svg_device_options(fz_context *ctx, fz_svg_device_options *opts)
1409
0
{
1410
0
  memset(opts, 0, sizeof *opts);
1411
1412
0
  opts->text_format = FZ_SVG_TEXT_AS_PATH;
1413
0
  opts->reuse_images = 1;
1414
0
  opts->resolution = 72;
1415
0
  opts->id = NULL;
1416
0
}
1417
1418
void
1419
fz_parse_svg_device_options(fz_context *ctx, fz_svg_device_options *opts, const char *args)
1420
0
{
1421
0
  fz_options *options = fz_new_options(ctx, args);
1422
0
  fz_try(ctx)
1423
0
  {
1424
0
    fz_init_svg_device_options(ctx, opts);
1425
0
    fz_apply_svg_device_options(ctx, opts, options);
1426
0
    fz_throw_on_unused_options(ctx, options, "svg");
1427
0
  }
1428
0
  fz_always(ctx)
1429
0
    fz_drop_options(ctx, options);
1430
0
  fz_catch(ctx)
1431
0
    fz_rethrow(ctx);
1432
0
}
1433
1434
static const fz_option_enums svg_text_opt_enum[] =
1435
{
1436
  { "text", FZ_SVG_TEXT_AS_TEXT },
1437
  { "path", FZ_SVG_TEXT_AS_PATH },
1438
  { NULL, -1 }
1439
};
1440
1441
void fz_apply_svg_device_options(fz_context *ctx, fz_svg_device_options *opts, fz_options *args)
1442
0
{
1443
0
  if (fz_lookup_option_enum(ctx, args, "text", &opts->text_format, svg_text_opt_enum) < 0)
1444
0
    fz_throw(ctx, FZ_ERROR_ARGUMENT, "Unknown text option");
1445
0
  fz_lookup_option_boolean(ctx, args, "no-reuse-images", &opts->reuse_images);
1446
0
  fz_lookup_option_integer(ctx, args, "resolution", &opts->resolution);
1447
1448
0
  fz_validate_options(ctx, args, "svg-device");
1449
0
}
1450
1451
fz_device *fz_new_svg_device_with_options(fz_context *ctx, fz_output *out, float page_width, float page_height, fz_svg_device_options *opts)
1452
0
{
1453
0
  svg_device *dev = fz_new_derived_device(ctx, svg_device);
1454
1455
0
  fz_try(ctx)
1456
0
  {
1457
0
    dev->super.close_device = svg_dev_close_device;
1458
0
    dev->super.drop_device = svg_dev_drop_device;
1459
1460
0
    dev->super.fill_path = svg_dev_fill_path;
1461
0
    dev->super.stroke_path = svg_dev_stroke_path;
1462
0
    dev->super.clip_path = svg_dev_clip_path;
1463
0
    dev->super.clip_stroke_path = svg_dev_clip_stroke_path;
1464
1465
0
    dev->super.fill_text = svg_dev_fill_text;
1466
0
    dev->super.stroke_text = svg_dev_stroke_text;
1467
0
    dev->super.clip_text = svg_dev_clip_text;
1468
0
    dev->super.clip_stroke_text = svg_dev_clip_stroke_text;
1469
0
    dev->super.ignore_text = svg_dev_ignore_text;
1470
1471
0
    dev->super.fill_shade = svg_dev_fill_shade;
1472
0
    dev->super.fill_image = svg_dev_fill_image;
1473
0
    dev->super.fill_image_mask = svg_dev_fill_image_mask;
1474
0
    dev->super.clip_image_mask = svg_dev_clip_image_mask;
1475
1476
0
    dev->super.pop_clip = svg_dev_pop_clip;
1477
1478
0
    dev->super.begin_mask = svg_dev_begin_mask;
1479
0
    dev->super.end_mask = svg_dev_end_mask;
1480
0
    dev->super.begin_group = svg_dev_begin_group;
1481
0
    dev->super.end_group = svg_dev_end_group;
1482
1483
0
    dev->super.begin_tile = svg_dev_begin_tile;
1484
0
    dev->super.end_tile = svg_dev_end_tile;
1485
1486
0
    dev->super.begin_layer = svg_dev_begin_layer;
1487
0
    dev->super.end_layer = svg_dev_end_layer;
1488
1489
0
    dev->real_out = out;
1490
0
    dev->in_defs = 0;
1491
0
    dev->defs = fz_new_buffer(ctx, 4096);
1492
0
    dev->main = fz_new_buffer(ctx, 4096);
1493
0
    dev->out = dev->main;
1494
1495
0
    dev->save_id = opts->id;
1496
0
    dev->id = opts->id ? *opts->id : 1;
1497
0
    dev->layers = 0;
1498
0
    dev->text_as_text = (opts->text_format == FZ_SVG_TEXT_AS_TEXT);
1499
0
    dev->reuse_images = opts->reuse_images;
1500
0
    dev->page_width = page_width;
1501
0
    dev->page_height = page_height;
1502
1503
0
    dev->raster_scale = opts->resolution / 72.0f;
1504
0
  }
1505
0
  fz_catch(ctx)
1506
0
  {
1507
0
    fz_drop_device(ctx, &dev->super);
1508
0
    fz_rethrow(ctx);
1509
0
  }
1510
1511
0
  return (fz_device*)dev;
1512
0
}
1513
1514
fz_device *fz_new_svg_device_with_id(fz_context *ctx, fz_output *out, float page_width, float page_height, int text_format, int reuse_images, int *id)
1515
0
{
1516
0
  fz_svg_device_options opts;
1517
0
  opts.text_format = text_format;
1518
0
  opts.reuse_images = reuse_images;
1519
0
  opts.resolution = 72;
1520
0
  opts.id = id;
1521
0
  return fz_new_svg_device_with_options(ctx, out, page_width, page_height, &opts);
1522
0
}
1523
1524
fz_device *fz_new_svg_device(fz_context *ctx, fz_output *out, float page_width, float page_height, int text_format, int reuse_images)
1525
0
{
1526
0
  fz_svg_device_options opts;
1527
0
  opts.text_format = text_format;
1528
0
  opts.reuse_images = reuse_images;
1529
0
  opts.resolution = 72;
1530
  opts.id = NULL;
1531
0
  return fz_new_svg_device_with_options(ctx, out, page_width, page_height, &opts);
1532
0
}