Coverage Report

Created: 2026-06-08 06:46

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