1#include "fitz-imp.h"
2
3#include <string.h>
4#include <float.h>
5#include <math.h>
6
7typedef struct svg_device_s svg_device;
8
9typedef struct tile_s tile;
10typedef struct font_s font;
11typedef struct glyph_s glyph;
12typedef struct image_s image;
13
14struct tile_s
15{
16 int pattern;
17 fz_matrix ctm;
18 fz_rect view;
19 fz_rect area;
20 fz_point step;
21};
22
23struct glyph_s
24{
25 float x_off;
26 float y_off;
27};
28
29struct font_s
30{
31 int id;
32 fz_font *font;
33 int max_sentlist;
34 glyph *sentlist;
35};
36
37struct image_s
38{
39 int id;
40 fz_image *image;
41};
42
43struct svg_device_s
44{
45 fz_device super;
46
47 int text_as_text;
48 int reuse_images;
49
50 fz_output *out;
51 fz_output *out_store;
52 fz_output *defs;
53 fz_buffer *defs_buffer;
54 int def_count;
55
56 int *save_id;
57 int id;
58
59 int blend_bitmask;
60
61 int num_tiles;
62 int max_tiles;
63 tile *tiles;
64
65 int num_fonts;
66 int max_fonts;
67 font *fonts;
68
69 int num_images;
70 int max_images;
71 image *images;
72
73 int layers;
74};
75
76/* SVG is awkward about letting us define things within symbol definitions
77 * so we have to delay definitions until after the symbol definition ends. */
78
79static fz_output *
80start_def(fz_context *ctx, svg_device *sdev)
81{
82 sdev->def_count++;
83 if (sdev->def_count == 2)
84 {
85 if (sdev->defs == NULL)
86 {
87 if (sdev->defs_buffer == NULL)
88 sdev->defs_buffer = fz_new_buffer(ctx, 1024);
89 sdev->defs = fz_new_output_with_buffer(ctx, sdev->defs_buffer);
90 }
91 sdev->out = sdev->defs;
92 }
93 return sdev->out;
94}
95
96static fz_output *
97end_def(fz_context *ctx, svg_device *sdev)
98{
99 if (sdev->def_count > 0)
100 sdev->def_count--;
101 if (sdev->def_count == 1)
102 sdev->out = sdev->out_store;
103 if (sdev->def_count == 0 && sdev->defs_buffer != NULL)
104 {
105 fz_write_data(ctx, sdev->out, sdev->defs_buffer->data, sdev->defs_buffer->len);
106 sdev->defs_buffer->len = 0;
107 }
108 return sdev->out;
109}
110
111/* Helper functions */
112
113static void
114svg_path_moveto(fz_context *ctx, void *arg, float x, float y)
115{
116 fz_output *out = (fz_output *)arg;
117
118 fz_write_printf(ctx, out, "M %g %g ", x, y);
119}
120
121static void
122svg_path_lineto(fz_context *ctx, void *arg, float x, float y)
123{
124 fz_output *out = (fz_output *)arg;
125
126 fz_write_printf(ctx, out, "L %g %g ", x, y);
127}
128
129static void
130svg_path_curveto(fz_context *ctx, void *arg, float x1, float y1, float x2, float y2, float x3, float y3)
131{
132 fz_output *out = (fz_output *)arg;
133
134 fz_write_printf(ctx, out, "C %g %g %g %g %g %g ", x1, y1, x2, y2, x3, y3);
135}
136
137static void
138svg_path_close(fz_context *ctx, void *arg)
139{
140 fz_output *out = (fz_output *)arg;
141
142 fz_write_printf(ctx, out, "Z ");
143}
144
145static const fz_path_walker svg_path_walker =
146{
147 svg_path_moveto,
148 svg_path_lineto,
149 svg_path_curveto,
150 svg_path_close
151};
152
153static void
154svg_dev_path(fz_context *ctx, svg_device *sdev, const fz_path *path)
155{
156 fz_write_printf(ctx, sdev->out, " d=\"");
157 fz_walk_path(ctx, path, &svg_path_walker, sdev->out);
158 fz_write_printf(ctx, sdev->out, "\"");
159}
160
161static void
162svg_dev_ctm(fz_context *ctx, svg_device *sdev, fz_matrix ctm)
163{
164 fz_output *out = sdev->out;
165
166 if (ctm.a != 1.0f || ctm.b != 0 || ctm.c != 0 || ctm.d != 1.0f || ctm.e != 0 || ctm.f != 0)
167 {
168 fz_write_printf(ctx, out, " transform=\"matrix(%g,%g,%g,%g,%g,%g)\"",
169 ctm.a, ctm.b, ctm.c, ctm.d, ctm.e, ctm.f);
170 }
171}
172
173static void
174svg_dev_stroke_state(fz_context *ctx, svg_device *sdev, const fz_stroke_state *stroke_state, fz_matrix ctm)
175{
176 fz_output *out = sdev->out;
177 float exp;
178
179 exp = fz_matrix_expansion(ctm);
180 if (exp == 0)
181 exp = 1;
182 exp = stroke_state->linewidth/exp;
183
184 fz_write_printf(ctx, out, " stroke-width=\"%g\"", exp);
185 fz_write_printf(ctx, out, " stroke-linecap=\"%s\"",
186 (stroke_state->start_cap == FZ_LINECAP_SQUARE ? "square" :
187 (stroke_state->start_cap == FZ_LINECAP_ROUND ? "round" : "butt")));
188 if (stroke_state->dash_len != 0)
189 {
190 int i;
191 fz_write_printf(ctx, out, " stroke-dasharray=");
192 for (i = 0; i < stroke_state->dash_len; i++)
193 fz_write_printf(ctx, out, "%c%g", (i == 0 ? '\"' : ','), stroke_state->dash_list[i]);
194 fz_write_printf(ctx, out, "\"");
195 if (stroke_state->dash_phase != 0)
196 fz_write_printf(ctx, out, " stroke-dashoffset=\"%g\"", stroke_state->dash_phase);
197 }
198 if (stroke_state->linejoin == FZ_LINEJOIN_MITER || stroke_state->linejoin == FZ_LINEJOIN_MITER_XPS)
199 fz_write_printf(ctx, out, " stroke-miterlimit=\"%g\"", stroke_state->miterlimit);
200 fz_write_printf(ctx, out, " stroke-linejoin=\"%s\"",
201 (stroke_state->linejoin == FZ_LINEJOIN_BEVEL ? "bevel" :
202 (stroke_state->linejoin == FZ_LINEJOIN_ROUND ? "round" : "miter")));
203}
204
205static unsigned int
206svg_hex_color(fz_context *ctx, fz_colorspace *colorspace, const float *color, fz_color_params color_params)
207{
208 float rgb[3];
209 int r, g, b;
210
211 if (colorspace != fz_device_rgb(ctx))
212 {
213 fz_convert_color(ctx, colorspace, color, fz_device_rgb(ctx), rgb, NULL, color_params);
214 color = rgb;
215 }
216
217 r = fz_clampi(255 * color[0] + 0.5f, 0, 255);
218 g = fz_clampi(255 * color[1] + 0.5f, 0, 255);
219 b = fz_clampi(255 * color[2] + 0.5f, 0, 255);
220
221 return (r << 16) | (g << 8) | b;
222}
223
224static void
225svg_dev_fill_color(fz_context *ctx, svg_device *sdev, fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params)
226{
227 fz_output *out = sdev->out;
228 if (colorspace)
229 {
230 int rgb = svg_hex_color(ctx, colorspace, color, color_params);
231 if (rgb != 0) /* black is the default value */
232 fz_write_printf(ctx, out, " fill=\"#%06x\"", rgb);
233 }
234 else
235 fz_write_printf(ctx, out, " fill=\"none\"");
236 if (alpha != 1)
237 fz_write_printf(ctx, out, " fill-opacity=\"%g\"", alpha);
238}
239
240static void
241svg_dev_stroke_color(fz_context *ctx, svg_device *sdev, fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params)
242{
243 fz_output *out = sdev->out;
244 if (colorspace)
245 fz_write_printf(ctx, out, " fill=\"none\" stroke=\"#%06x\"", svg_hex_color(ctx, colorspace, color, color_params));
246 else
247 fz_write_printf(ctx, out, " fill=\"none\" stroke=\"none\"");
248 if (alpha != 1)
249 fz_write_printf(ctx, out, " stroke-opacity=\"%g\"", alpha);
250}
251
252static void
253svg_font_family(fz_context *ctx, char buf[], int size, const char *name)
254{
255 /* Remove "ABCDEF+" prefix and "-Bold" suffix. */
256 char *p = strchr(name, '+');
257 if (p) fz_strlcpy(buf, p+1, size);
258 else fz_strlcpy(buf, name, size);
259 p = strrchr(buf, '-');
260 if (p) *p = 0;
261}
262
263static int
264find_first_char(fz_context *ctx, const fz_text_span *span, int i)
265{
266 for (; i < span->len; ++i)
267 if (span->items[i].ucs >= 0)
268 return i;
269 return i;
270}
271
272static int
273find_next_line_break(fz_context *ctx, const fz_text_span *span, fz_matrix inv_tm, int i)
274{
275 fz_point p, old_p;
276
277 old_p.x = span->items[i].x;
278 old_p.y = span->items[i].y;
279 old_p = fz_transform_point(old_p, inv_tm);
280
281 for (++i; i < span->len; ++i)
282 {
283 if (span->items[i].ucs >= 0)
284 {
285 p.x = span->items[i].x;
286 p.y = span->items[i].y;
287 p = fz_transform_point(p, inv_tm);
288 if (span->wmode == 0)
289 {
290 if (p.y != old_p.y)
291 return i;
292 }
293 else
294 {
295 if (p.x != old_p.x)
296 return i;
297 }
298 old_p = p;
299 }
300 }
301
302 return i;
303}
304
305static float
306svg_cluster_advance(fz_context *ctx, const fz_text_span *span, int i, int end)
307{
308 int n = 1;
309 while (i + n < end && span->items[i + n].gid == -1)
310 ++n;
311 if (n > 1)
312 return fz_advance_glyph(ctx, span->font, span->items[i].gid, span->wmode) / n;
313 return 0; /* this value is never used (since n==1) */
314}
315
316static void
317svg_dev_text_span(fz_context *ctx, svg_device *sdev, fz_matrix ctm, const fz_text_span *span)
318{
319 fz_output *out = sdev->out;
320 char font_family[100];
321 int is_bold, is_italic;
322 fz_matrix tm, inv_tm, final_tm;
323 fz_point p;
324 float font_size;
325 fz_text_item *it;
326 int start, end, i;
327 float cluster_advance = 0;
328
329 if (span->len == 0)
330 {
331 fz_write_printf(ctx, out, "/>\n");
332 return;
333 }
334
335 tm = span->trm;
336 font_size = fz_matrix_expansion(tm);
337 final_tm.a = tm.a / font_size;
338 final_tm.b = tm.b / font_size;
339 final_tm.c = -tm.c / font_size;
340 final_tm.d = -tm.d / font_size;
341 final_tm.e = 0;
342 final_tm.f = 0;
343 inv_tm = fz_invert_matrix(final_tm);
344 final_tm = fz_concat(final_tm, ctm);
345
346 tm.e = span->items[0].x;
347 tm.f = span->items[0].y;
348
349 svg_font_family(ctx, font_family, sizeof font_family, fz_font_name(ctx, span->font));
350 is_bold = fz_font_is_bold(ctx, span->font);
351 is_italic = fz_font_is_italic(ctx, span->font);
352
353 fz_write_printf(ctx, out, " xml:space=\"preserve\"");
354 fz_write_printf(ctx, out, " transform=\"matrix(%M)\"", &final_tm);
355 fz_write_printf(ctx, out, " font-size=\"%g\"", font_size);
356 fz_write_printf(ctx, out, " font-family=\"%s\"", font_family);
357 if (is_bold) fz_write_printf(ctx, out, " font-weight=\"bold\"");
358 if (is_italic) fz_write_printf(ctx, out, " font-style=\"italic\"");
359 if (span->wmode != 0) fz_write_printf(ctx, out, " writing-mode=\"tb\"");
360
361 fz_write_byte(ctx, out, '>');
362
363 start = find_first_char(ctx, span, 0);
364 while (start < span->len)
365 {
366 end = find_next_line_break(ctx, span, inv_tm, start);
367
368 p.x = span->items[start].x;
369 p.y = span->items[start].y;
370 p = fz_transform_point(p, inv_tm);
371 if (span->items[start].gid >= 0)
372 cluster_advance = svg_cluster_advance(ctx, span, start, end);
373 if (span->wmode == 0)
374 fz_write_printf(ctx, out, "<tspan y=\"%g\" x=\"%g", p.y, p.x);
375 else
376 fz_write_printf(ctx, out, "<tspan x=\"%g\" y=\"%g", p.x, p.y);
377 for (i = start + 1; i < end; ++i)
378 {
379 it = &span->items[i];
380 if (it->gid >= 0)
381 cluster_advance = svg_cluster_advance(ctx, span, i, end);
382 if (it->ucs >= 0)
383 {
384 if (it->gid >= 0)
385 {
386 p.x = it->x;
387 p.y = it->y;
388 p = fz_transform_point(p, inv_tm);
389 }
390 else
391 {
392 /* we have no glyph (such as in a ligature) -- advance a bit */
393 if (span->wmode == 0)
394 p.x += font_size * cluster_advance;
395 else
396 p.y += font_size * cluster_advance;
397 }
398 fz_write_printf(ctx, out, " %g", span->wmode == 0 ? p.x : p.y);
399 }
400 }
401 fz_write_printf(ctx, out, "\">");
402 for (i = start; i < end; ++i)
403 {
404 it = &span->items[i];
405 if (it->ucs >= 0)
406 {
407 int c = it->ucs;
408 if (c >= 32 && c <= 127 && c != '<' && c != '&' && c != '>')
409 fz_write_byte(ctx, out, c);
410 else
411 fz_write_printf(ctx, out, "&#x%04x;", c);
412 }
413 }
414 fz_write_printf(ctx, out, "</tspan>");
415
416 start = find_first_char(ctx, span, end);
417 }
418
419 fz_write_printf(ctx, out, "</text>\n");
420}
421
422static font *
423svg_dev_text_span_as_paths_defs(fz_context *ctx, fz_device *dev, fz_text_span *span, fz_matrix ctm)
424{
425 svg_device *sdev = (svg_device*)dev;
426 fz_output *out = sdev->out;
427 int i, font_idx;
428 font *fnt;
429 fz_matrix shift = fz_identity;
430
431 for (font_idx = 0; font_idx < sdev->num_fonts; font_idx++)
432 {
433 if (sdev->fonts[font_idx].font == span->font)
434 break;
435 }
436 if (font_idx == sdev->num_fonts)
437 {
438 /* New font */
439 if (font_idx == sdev->max_fonts)
440 {
441 int newmax = sdev->max_fonts * 2;
442 if (newmax == 0)
443 newmax = 4;
444 sdev->fonts = fz_realloc_array(ctx, sdev->fonts, newmax, font);
445 memset(&sdev->fonts[font_idx], 0, (newmax - font_idx) * sizeof(font));
446 sdev->max_fonts = newmax;
447 }
448 sdev->fonts[font_idx].id = sdev->id++;
449 sdev->fonts[font_idx].font = fz_keep_font(ctx, span->font);
450 sdev->num_fonts++;
451 }
452 fnt = &sdev->fonts[font_idx];
453
454 for (i=0; i < span->len; i++)
455 {
456 fz_text_item *it = &span->items[i];
457 int gid = it->gid;
458
459 if (gid < 0)
460 continue;
461 if (gid >= fnt->max_sentlist)
462 {
463 int j;
464 fnt->sentlist = fz_realloc_array(ctx, fnt->sentlist, gid+1, glyph);
465 for (j = fnt->max_sentlist; j <= gid; j++)
466 {
467 fnt->sentlist[j].x_off = FLT_MIN;
468 fnt->sentlist[j].y_off = FLT_MIN;
469 }
470 fnt->max_sentlist = gid+1;
471 }
472 if (fnt->sentlist[gid].x_off == FLT_MIN)
473 {
474 /* Need to send this one */
475 fz_rect rect;
476 fz_path *path;
477 out = start_def(ctx, sdev);
478 fz_write_printf(ctx, out, "<symbol id=\"font_%x_%x\">\n", fnt->id, gid);
479 if (fz_font_ft_face(ctx, span->font))
480 {
481 path = fz_outline_glyph(ctx, span->font, gid, fz_identity);
482 if (path)
483 {
484 rect = fz_bound_path(ctx, path, NULL, fz_identity);
485 shift.e = -rect.x0;
486 shift.f = -rect.y0;
487 fz_transform_path(ctx, path, shift);
488 fz_write_printf(ctx, out, "<path");
489 svg_dev_path(ctx, sdev, path);
490 fz_write_printf(ctx, out, "/>\n");
491 fz_drop_path(ctx, path);
492 }
493 else
494 {
495 rect = fz_empty_rect;
496 }
497 }
498 else if (fz_font_t3_procs(ctx, span->font))
499 {
500 rect = fz_bound_glyph(ctx, span->font, gid, fz_identity);
501 shift.e = -rect.x0;
502 shift.f = -rect.y0;
503 fz_run_t3_glyph(ctx, span->font, gid, shift, dev);
504 }
505 fz_write_printf(ctx, out, "</symbol>\n");
506 out = end_def(ctx, sdev);
507 fnt->sentlist[gid].x_off = rect.x0;
508 fnt->sentlist[gid].y_off = rect.y0;
509 }
510 }
511 return fnt;
512}
513
514static void
515svg_dev_text_span_as_paths_fill(fz_context *ctx, fz_device *dev, const fz_text_span *span, fz_matrix ctm,
516 fz_colorspace *colorspace, const float *color, float alpha, font *fnt, fz_color_params color_params)
517{
518 svg_device *sdev = (svg_device*)dev;
519 fz_output *out = sdev->out;
520 fz_matrix shift = { 1, 0, 0, 1, 0, 0};
521 fz_matrix trm, mtx;
522 int i;
523
524 /* Rely on the fact that trm.{e,f} == 0 */
525 trm.a = span->trm.a;
526 trm.b = span->trm.b;
527 trm.c = span->trm.c;
528 trm.d = span->trm.d;
529 trm.e = 0;
530 trm.f = 0;
531
532 for (i=0; i < span->len; i++)
533 {
534 fz_text_item *it = &span->items[i];
535 int gid = it->gid;
536 if (gid < 0)
537 continue;
538
539 shift.e = fnt->sentlist[gid].x_off;
540 shift.f = fnt->sentlist[gid].y_off;
541 trm.e = it->x;
542 trm.f = it->y;
543 mtx = fz_concat(shift, fz_concat(trm, ctm));
544
545 fz_write_printf(ctx, out, "<use xlink:href=\"#font_%x_%x\"", fnt->id, gid);
546 svg_dev_ctm(ctx, sdev, mtx);
547 svg_dev_fill_color(ctx, sdev, colorspace, color, alpha, color_params);
548 fz_write_printf(ctx, out, "/>\n");
549 }
550}
551
552static void
553svg_dev_text_span_as_paths_stroke(fz_context *ctx, fz_device *dev, const fz_text_span *span,
554 const fz_stroke_state *stroke, fz_matrix ctm,
555 fz_colorspace *colorspace, const float *color, float alpha, font *fnt, fz_color_params color_params)
556{
557 svg_device *sdev = (svg_device*)dev;
558 fz_output *out = sdev->out;
559 fz_matrix shift = { 1, 0, 0, 1, 0, 0};
560 fz_matrix trm, mtx;
561 int i;
562
563 /* Rely on the fact that trm.{e,f} == 0 */
564 trm.a = span->trm.a;
565 trm.b = span->trm.b;
566 trm.c = span->trm.c;
567 trm.d = span->trm.d;
568 trm.e = 0;
569 trm.f = 0;
570
571 for (i=0; i < span->len; i++)
572 {
573 fz_text_item *it = &span->items[i];
574 int gid = it->gid;
575 if (gid < 0)
576 continue;
577
578 shift.e = fnt->sentlist[gid].x_off;
579 shift.f = fnt->sentlist[gid].y_off;
580 trm.e = it->x;
581 trm.f = it->y;
582 mtx = fz_concat(shift, fz_concat(trm, ctm));
583
584 fz_write_printf(ctx, out, "<use xlink:href=\"#font_%x_%x\"", fnt->id, gid);
585 svg_dev_stroke_state(ctx, sdev, stroke, mtx);
586 svg_dev_ctm(ctx, sdev, mtx);
587 svg_dev_stroke_color(ctx, sdev, colorspace, color, alpha, color_params);
588 fz_write_printf(ctx, out, "/>\n");
589 }
590}
591
592/* Entry points */
593
594static void
595svg_dev_fill_path(fz_context *ctx, fz_device *dev, const fz_path *path, int even_odd, fz_matrix ctm,
596 fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params)
597{
598 svg_device *sdev = (svg_device*)dev;
599 fz_output *out = sdev->out;
600
601 fz_write_printf(ctx, out, "<path");
602 svg_dev_ctm(ctx, sdev, ctm);
603 svg_dev_path(ctx, sdev, path);
604 svg_dev_fill_color(ctx, sdev, colorspace, color, alpha, color_params);
605 if (even_odd)
606 fz_write_printf(ctx, out, " fill-rule=\"evenodd\"");
607 fz_write_printf(ctx, out, "/>\n");
608}
609
610static void
611svg_dev_stroke_path(fz_context *ctx, fz_device *dev, const fz_path *path, const fz_stroke_state *stroke, fz_matrix ctm,
612 fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params)
613{
614 svg_device *sdev = (svg_device*)dev;
615 fz_output *out = sdev->out;
616
617 fz_write_printf(ctx, out, "<path");
618 svg_dev_ctm(ctx, sdev, ctm);
619 svg_dev_stroke_state(ctx, sdev, stroke, fz_identity);
620 svg_dev_stroke_color(ctx, sdev, colorspace, color, alpha, color_params);
621 svg_dev_path(ctx, sdev, path);
622 fz_write_printf(ctx, out, "/>\n");
623}
624
625static void
626svg_dev_clip_path(fz_context *ctx, fz_device *dev, const fz_path *path, int even_odd, fz_matrix ctm, fz_rect scissor)
627{
628 svg_device *sdev = (svg_device*)dev;
629 fz_output *out;
630
631 int num = sdev->id++;
632
633 out = start_def(ctx, sdev);
634 fz_write_printf(ctx, out, "<clipPath id=\"cp%d\">\n", num);
635 fz_write_printf(ctx, out, "<path");
636 svg_dev_ctm(ctx, sdev, ctm);
637 svg_dev_path(ctx, sdev, path);
638 if (even_odd)
639 fz_write_printf(ctx, out, " fill-rule=\"evenodd\"");
640 fz_write_printf(ctx, out, "/>\n</clipPath>\n");
641 out = end_def(ctx, sdev);
642 fz_write_printf(ctx, out, "<g clip-path=\"url(#cp%d)\">\n", num);
643}
644
645static void
646svg_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)
647{
648 svg_device *sdev = (svg_device*)dev;
649
650 fz_output *out;
651 fz_rect bounds;
652 int num = sdev->id++;
653 float white[3] = { 1, 1, 1 };
654
655 bounds = fz_bound_path(ctx, path, stroke, ctm);
656
657 out = start_def(ctx, sdev);
658 fz_write_printf(ctx, out, "<mask id=\"ma%d\" x=\"%g\" y=\"%g\" width=\"%g\" height=\"%g\" maskUnits=\"userSpaceOnUse\" maskContentUnits=\"userSpaceOnUse\">\n",
659 num, bounds.x0, bounds.y0, bounds.x1 - bounds.x0, bounds.y1 - bounds.y0);
660 fz_write_printf(ctx, out, "<path");
661 svg_dev_ctm(ctx, sdev, ctm);
662 svg_dev_stroke_state(ctx, sdev, stroke, fz_identity);
663 svg_dev_stroke_color(ctx, sdev, fz_device_rgb(ctx), white, 1, fz_default_color_params);
664 svg_dev_path(ctx, sdev, path);
665 fz_write_printf(ctx, out, "/>\n</mask>\n");
666 out = end_def(ctx, sdev);
667 fz_write_printf(ctx, out, "<g mask=\"url(#ma%d)\">\n", num);
668}
669
670static void
671svg_dev_fill_text(fz_context *ctx, fz_device *dev, const fz_text *text, fz_matrix ctm,
672 fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params)
673{
674 svg_device *sdev = (svg_device*)dev;
675 fz_output *out = sdev->out;
676 font *fnt;
677 fz_text_span *span;
678
679 if (sdev->text_as_text)
680 {
681 for (span = text->head; span; span = span->next)
682 {
683 fz_write_printf(ctx, out, "<text");
684 svg_dev_fill_color(ctx, sdev, colorspace, color, alpha, color_params);
685 svg_dev_text_span(ctx, sdev, ctm, span);
686 }
687 }
688 else
689 {
690 for (span = text->head; span; span = span->next)
691 {
692 fnt = svg_dev_text_span_as_paths_defs(ctx, dev, span, ctm);
693 svg_dev_text_span_as_paths_fill(ctx, dev, span, ctm, colorspace, color, alpha, fnt, color_params);
694 }
695 }
696}
697
698static void
699svg_dev_stroke_text(fz_context *ctx, fz_device *dev, const fz_text *text, const fz_stroke_state *stroke, fz_matrix ctm,
700 fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params)
701{
702 svg_device *sdev = (svg_device*)dev;
703 fz_output *out = sdev->out;
704 font *fnt;
705 fz_text_span *span;
706
707 if (sdev->text_as_text)
708 {
709 for (span = text->head; span; span = span->next)
710 {
711 fz_write_printf(ctx, out, "<text");
712 svg_dev_fill_color(ctx, sdev, colorspace, color, alpha, color_params);
713 svg_dev_text_span(ctx, sdev, ctm, span);
714 }
715 }
716 else
717 {
718 for (span = text->head; span; span = span->next)
719 {
720 fnt = svg_dev_text_span_as_paths_defs(ctx, dev, span, ctm);
721 svg_dev_text_span_as_paths_stroke(ctx, dev, span, stroke, ctm, colorspace, color, alpha, fnt, color_params);
722 }
723 }
724}
725
726static void
727svg_dev_clip_text(fz_context *ctx, fz_device *dev, const fz_text *text, fz_matrix ctm, fz_rect scissor)
728{
729 svg_device *sdev = (svg_device*)dev;
730 fz_output *out = sdev->out;
731
732 fz_rect bounds;
733 int num = sdev->id++;
734 float white[3] = { 1, 1, 1 };
735 font *fnt;
736 fz_text_span *span;
737
738 bounds = fz_bound_text(ctx, text, NULL, ctm);
739
740 out = start_def(ctx, sdev);
741 fz_write_printf(ctx, out, "<mask id=\"ma%d\" x=\"%g\" y=\"%g\" width=\"%g\" height=\"%g\"",
742 num, bounds.x0, bounds.y0, bounds.x1 - bounds.x0, bounds.y1 - bounds.y0);
743 fz_write_printf(ctx, out, " maskUnits=\"userSpaceOnUse\" maskContentUnits=\"userSpaceOnUse\">\n");
744 if (sdev->text_as_text)
745 {
746 for (span = text->head; span; span = span->next)
747 {
748 fz_write_printf(ctx, out, "<text");
749 svg_dev_fill_color(ctx, sdev, fz_device_rgb(ctx), white, 1, fz_default_color_params);
750 svg_dev_text_span(ctx, sdev, ctm, span);
751 }
752 }
753 else
754 {
755 for (span = text->head; span; span = span->next)
756 {
757 fnt = svg_dev_text_span_as_paths_defs(ctx, dev, span, ctm);
758 svg_dev_text_span_as_paths_fill(ctx, dev, span, ctm, fz_device_rgb(ctx), white, 1.0f, fnt, fz_default_color_params);
759 }
760 }
761 fz_write_printf(ctx, out, "</mask>\n");
762 out = end_def(ctx, sdev);
763 fz_write_printf(ctx, out, "<g mask=\"url(#ma%d)\">\n", num);
764}
765
766static void
767svg_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)
768{
769 svg_device *sdev = (svg_device*)dev;
770
771 fz_output *out;
772 fz_rect bounds;
773 int num = sdev->id++;
774 float white[3] = { 255, 255, 255 };
775 font *fnt;
776 fz_text_span *span;
777
778 bounds = fz_bound_text(ctx, text, NULL, ctm);
779
780 out = start_def(ctx, sdev);
781 fz_write_printf(ctx, out, "<mask id=\"ma%d\" x=\"%g\" y=\"%g\" width=\"%g\" height=\"%g\"",
782 num, bounds.x0, bounds.y0, bounds.x1 - bounds.x0, bounds.y1 - bounds.y0);
783 fz_write_printf(ctx, out, " maskUnits=\"userSpaceOnUse\" maskContentUnits=\"userSpaceOnUse\">\n");
784 if (sdev->text_as_text)
785 {
786 for (span = text->head; span; span = span->next)
787 {
788 fz_write_printf(ctx, out, "<text");
789 svg_dev_stroke_state(ctx, sdev, stroke, fz_identity);
790 svg_dev_stroke_color(ctx, sdev, fz_device_rgb(ctx), white, 1, fz_default_color_params);
791 svg_dev_text_span(ctx, sdev, ctm, span);
792 }
793 }
794 else
795 {
796 for (span = text->head; span; span = span->next)
797 {
798 fnt = svg_dev_text_span_as_paths_defs(ctx, dev, span, ctm);
799 svg_dev_text_span_as_paths_stroke(ctx, dev, span, stroke, ctm, fz_device_rgb(ctx), white, 1.0f, fnt, fz_default_color_params);
800 }
801 }
802 fz_write_printf(ctx, out, "</mask>\n");
803 out = end_def(ctx, sdev);
804 fz_write_printf(ctx, out, "<g mask=\"url(#ma%d)\">\n", num);
805}
806
807static void
808svg_dev_ignore_text(fz_context *ctx, fz_device *dev, const fz_text *text, fz_matrix ctm)
809{
810 svg_device *sdev = (svg_device*)dev;
811 fz_output *out = sdev->out;
812 fz_text_span *span;
813
814 float black[3] = { 0, 0, 0};
815
816 if (sdev->text_as_text)
817 {
818 for (span = text->head; span; span = span->next)
819 {
820 fz_write_printf(ctx, out, "<text");
821 svg_dev_fill_color(ctx, sdev, fz_device_rgb(ctx), black, 0.0f, fz_default_color_params);
822 svg_dev_text_span(ctx, sdev, ctm, span);
823 }
824 }
825}
826
827/* We spot repeated images, and send them just once using
828 * symbols. Unfortunately, for pathological files, such
829 * as the example in Bug695988, this can cause viewers to
830 * have conniptions. We therefore have an option that is
831 * made to avoid this (reuse-images=no). */
832static void
833svg_send_image(fz_context *ctx, svg_device *sdev, fz_image *img, fz_color_params color_params)
834{
835 fz_output *out = sdev->out;
836 int i;
837 int id;
838
839 if (sdev->reuse_images)
840 {
841 for (i = sdev->num_images-1; i >= 0; i--)
842 if (img == sdev->images[i].image)
843 break;
844 if (i >= 0)
845 {
846 fz_write_printf(ctx, out, "<use xlink:href=\"#im%d\" x=\"0\" y=\"0\" width=\"%d\" height=\"%d\"/>\n",
847 sdev->images[i].id, img->w, img->h);
848 return;
849 }
850
851 /* We need to send this image for the first time */
852 if (sdev->num_images == sdev->max_images)
853 {
854 int new_max = sdev->max_images * 2;
855 if (new_max == 0)
856 new_max = 32;
857 sdev->images = fz_realloc_array(ctx, sdev->images, new_max, image);
858 sdev->max_images = new_max;
859 }
860
861 id = sdev->id++;
862 out = start_def(ctx, sdev);
863 fz_write_printf(ctx, out, "<symbol id=\"im%d\" viewBox=\"0 0 %d %d\">\n", id, img->w, img->h);
864
865 fz_write_printf(ctx, out, "<image width=\"%d\" height=\"%d\" xlink:href=\"", img->w, img->h);
866 fz_write_image_as_data_uri(ctx, out, img);
867 fz_write_printf(ctx, out, "\"/>\n");
868
869 fz_write_printf(ctx, out, "</symbol>\n");
870 out = end_def(ctx, sdev);
871
872 sdev->images[sdev->num_images].id = id;
873 sdev->images[sdev->num_images].image = fz_keep_image(ctx, img);
874 sdev->num_images++;
875
876 fz_write_printf(ctx, out, "<use xlink:href=\"#im%d\" x=\"0\" y=\"0\" width=\"%d\" height=\"%d\"/>\n",
877 id, img->w, img->h);
878 }
879 else
880 {
881 fz_write_printf(ctx, out, "<image width=\"%d\" height=\"%d\" xlink:href=\"", img->w, img->h);
882 fz_write_image_as_data_uri(ctx, out, img);
883 fz_write_printf(ctx, out, "\"/>\n");
884 }
885}
886
887static void
888svg_dev_fill_image(fz_context *ctx, fz_device *dev, fz_image *image, fz_matrix ctm, float alpha, fz_color_params color_params)
889{
890 svg_device *sdev = (svg_device*)dev;
891 fz_output *out = sdev->out;
892
893 fz_matrix local_ctm = ctm;
894 fz_matrix scale = { 0 };
895
896 scale.a = 1.0f / image->w;
897 scale.d = 1.0f / image->h;
898
899 local_ctm = fz_concat(scale, ctm);
900 fz_write_printf(ctx, out, "<g");
901 if (alpha != 1.0f)
902 fz_write_printf(ctx, out, " opacity=\"%g\"", alpha);
903 svg_dev_ctm(ctx, sdev, local_ctm);
904 fz_write_printf(ctx, out, ">\n");
905 svg_send_image(ctx, sdev, image, color_params);
906 fz_write_printf(ctx, out, "</g>\n");
907}
908
909static void
910svg_dev_fill_shade(fz_context *ctx, fz_device *dev, fz_shade *shade, fz_matrix ctm, float alpha, fz_color_params color_params)
911{
912 svg_device *sdev = (svg_device*)dev;
913 fz_output *out = sdev->out;
914 fz_irect bbox;
915 fz_pixmap *pix;
916
917 bbox = fz_round_rect(fz_intersect_rect(fz_bound_shade(ctx, shade, ctm), fz_device_current_scissor(ctx, dev)));
918 if (fz_is_empty_irect(bbox))
919 return;
920 pix = fz_new_pixmap_with_bbox(ctx, fz_device_rgb(ctx), bbox, NULL, 1);
921 fz_clear_pixmap(ctx, pix);
922
923 fz_try(ctx)
924 {
925 fz_paint_shade(ctx, shade, NULL, ctm, pix, color_params, bbox, NULL);
926 if (alpha != 1.0f)
927 fz_write_printf(ctx, out, "<g opacity=\"%g\">\n", alpha);
928 fz_write_printf(ctx, out, "<image x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\" xlink:href=\"", pix->x, pix->y, pix->w, pix->h);
929 fz_write_pixmap_as_data_uri(ctx, out, pix);
930 fz_write_printf(ctx, out, "\"/>\n");
931 if (alpha != 1.0f)
932 fz_write_printf(ctx, out, "</g>\n");
933 }
934 fz_always(ctx)
935 {
936 fz_drop_pixmap(ctx, pix);
937 }
938 fz_catch(ctx)
939 {
940 fz_rethrow(ctx);
941 }
942}
943
944static void
945svg_dev_fill_image_mask(fz_context *ctx, fz_device *dev, fz_image *image, fz_matrix ctm,
946 fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params)
947{
948 svg_device *sdev = (svg_device*)dev;
949 fz_output *out;
950 fz_matrix local_ctm = ctm;
951 fz_matrix scale = { 0 };
952 int mask = sdev->id++;
953
954 scale.a = 1.0f / image->w;
955 scale.d = 1.0f / image->h;
956
957 local_ctm = fz_concat(scale, ctm);
958 out = start_def(ctx, sdev);
959 fz_write_printf(ctx, out, "<mask id=\"ma%d\">\n", mask);
960 svg_send_image(ctx, sdev, image, color_params);
961 fz_write_printf(ctx, out, "</mask>\n");
962 out = end_def(ctx, sdev);
963 fz_write_printf(ctx, out, "<rect x=\"0\" y=\"0\" width=\"%d\" height=\"%d\"", image->w, image->h);
964 svg_dev_fill_color(ctx, sdev, colorspace, color, alpha, color_params);
965 svg_dev_ctm(ctx, sdev, local_ctm);
966 fz_write_printf(ctx, out, " mask=\"url(#ma%d)\"/>\n", mask);
967}
968
969static void
970svg_dev_clip_image_mask(fz_context *ctx, fz_device *dev, fz_image *image, fz_matrix ctm, fz_rect scissor)
971{
972 svg_device *sdev = (svg_device*)dev;
973 fz_output *out;
974 fz_matrix local_ctm = ctm;
975 fz_matrix scale = { 0 };
976 int mask = sdev->id++;
977
978 scale.a = 1.0f / image->w;
979 scale.d = 1.0f / image->h;
980
981 local_ctm = fz_concat(scale, ctm);
982 out = start_def(ctx, sdev);
983 fz_write_printf(ctx, out, "<mask id=\"ma%d\">\n<g", mask);
984 svg_dev_ctm(ctx, sdev, local_ctm);
985 fz_write_printf(ctx, out, ">\n");
986 svg_send_image(ctx, sdev, image, fz_default_color_params/* FIXME */);
987 fz_write_printf(ctx, out, "</g>\n</mask>\n");
988 out = end_def(ctx, sdev);
989 fz_write_printf(ctx, out, "<g mask=\"url(#ma%d)\">\n", mask);
990}
991
992static void
993svg_dev_pop_clip(fz_context *ctx, fz_device *dev)
994{
995 svg_device *sdev = (svg_device*)dev;
996 fz_output *out = sdev->out;
997
998 /* FIXME */
999 fz_write_printf(ctx, out, "</g>\n");
1000}
1001
1002static void
1003svg_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)
1004{
1005 svg_device *sdev = (svg_device*)dev;
1006 fz_output *out;
1007 int mask = sdev->id++;
1008
1009 out = start_def(ctx, sdev);
1010 fz_write_printf(ctx, out, "<mask id=\"ma%d\">\n", mask);
1011
1012 if (dev->container_len > 0)
1013 dev->container[dev->container_len-1].user = mask;
1014}
1015
1016static void
1017svg_dev_end_mask(fz_context *ctx, fz_device *dev)
1018{
1019 svg_device *sdev = (svg_device*)dev;
1020 fz_output *out = sdev->out;
1021 int mask = 0;
1022
1023 if (dev->container_len > 0)
1024 mask = dev->container[dev->container_len-1].user;
1025
1026 fz_write_printf(ctx, out, "\"/>\n</mask>\n");
1027 out = end_def(ctx, sdev);
1028 fz_write_printf(ctx, out, "<g mask=\"url(#ma%d)\">\n", mask);
1029}
1030
1031static void
1032svg_dev_begin_group(fz_context *ctx, fz_device *dev, fz_rect bbox, fz_colorspace *cs, int isolated, int knockout, int blendmode, float alpha)
1033{
1034 svg_device *sdev = (svg_device*)dev;
1035 fz_output *out = sdev->out;
1036
1037 /* SVG only supports normal/multiply/screen/darken/lighten,
1038 * but we'll send them all, as the spec says that unrecognised
1039 * ones are treated as normal. */
1040 static char *blend_names[] = {
1041 "normal", /* FZ_BLEND_NORMAL */
1042 "multiply", /* FZ_BLEND_MULTIPLY */
1043 "screen", /* FZ_BLEND_SCREEN */
1044 "overlay", /* FZ_BLEND_OVERLAY */
1045 "darken", /* FZ_BLEND_DARKEN */
1046 "lighten", /* FZ_BLEND_LIGHTEN */
1047 "color_dodge", /* FZ_BLEND_COLOR_DODGE */
1048 "color_burn", /* FZ_BLEND_COLOR_BURN */
1049 "hard_light", /* FZ_BLEND_HARD_LIGHT */
1050 "soft_light", /* FZ_BLEND_SOFT_LIGHT */
1051 "difference", /* FZ_BLEND_DIFFERENCE */
1052 "exclusion", /* FZ_BLEND_EXCLUSION */
1053 "hue", /* FZ_BLEND_HUE */
1054 "saturation", /* FZ_BLEND_SATURATION */
1055 "color", /* FZ_BLEND_COLOR */
1056 "luminosity", /* FZ_BLEND_LUMINOSITY */
1057 };
1058
1059 if (blendmode < FZ_BLEND_NORMAL || blendmode > FZ_BLEND_LUMINOSITY)
1060 blendmode = FZ_BLEND_NORMAL;
1061 if (blendmode != FZ_BLEND_NORMAL && (sdev->blend_bitmask & (1<<blendmode)) == 0)
1062 {
1063 sdev->blend_bitmask |= (1<<blendmode);
1064 out = start_def(ctx, sdev);
1065 fz_write_printf(ctx, out,
1066 "<filter id=\"blend_%d\"><feBlend mode=\"%s\" in2=\"BackgroundImage\" in=\"SourceGraphic\"/></filter>\n",
1067 blendmode, blend_names[blendmode]);
1068 out = end_def(ctx, sdev);
1069 }
1070
1071 /* SVG 1.1 doesn't support adequate blendmodes/knockout etc, so just ignore it for now */
1072 if (alpha == 1)
1073 fz_write_printf(ctx, out, "<g");
1074 else
1075 fz_write_printf(ctx, out, "<g opacity=\"%g\"", alpha);
1076 if (blendmode != FZ_BLEND_NORMAL)
1077 fz_write_printf(ctx, out, " filter=\"url(#blend_%d)\"", blendmode);
1078 fz_write_printf(ctx, out, ">\n");
1079}
1080
1081static void
1082svg_dev_end_group(fz_context *ctx, fz_device *dev)
1083{
1084 svg_device *sdev = (svg_device*)dev;
1085 fz_output *out = sdev->out;
1086
1087 fz_write_printf(ctx, out, "</g>\n");
1088}
1089
1090static int
1091svg_dev_begin_tile(fz_context *ctx, fz_device *dev, fz_rect area, fz_rect view, float xstep, float ystep, fz_matrix ctm, int id)
1092{
1093 svg_device *sdev = (svg_device*)dev;
1094 fz_output *out;
1095 int num;
1096 tile *t;
1097
1098 if (sdev->num_tiles == sdev->max_tiles)
1099 {
1100 int n = (sdev->num_tiles == 0 ? 4 : sdev->num_tiles * 2);
1101
1102 sdev->tiles = fz_realloc_array(ctx, sdev->tiles, n, tile);
1103 sdev->max_tiles = n;
1104 }
1105 num = sdev->num_tiles++;
1106 t = &sdev->tiles[num];
1107 t->area = area;
1108 t->view = view;
1109 t->ctm = ctm;
1110 t->pattern = sdev->id++;
1111
1112 xstep = fabsf(xstep);
1113 ystep = fabsf(ystep);
1114 if (xstep == 0 || ystep == 0) {
1115 fz_warn(ctx, "Pattern cannot have x or ystep == 0.");
1116 if (xstep == 0)
1117 xstep = 1;
1118 if (ystep == 0)
1119 ystep = 1;
1120 }
1121
1122 t->step.x = xstep;
1123 t->step.y = ystep;
1124
1125 /* view = area of our reference tile in pattern space.
1126 * area = area to tile into in pattern space.
1127 * xstep/ystep = pattern repeat step in pattern space.
1128 * All of these need to be transformed by ctm to get to device space.
1129 * SVG only allows us to specify pattern tiles as axis aligned
1130 * rectangles, so we send these through as is, and ensure that the
1131 * correct matrix is used on the fill.
1132 */
1133
1134 /* The first thing we do is to capture the contents of the pattern
1135 * as a symbol we can reuse. */
1136 out = start_def(ctx, sdev);
1137 fz_write_printf(ctx, out, "<symbol id=\"pac%d\">\n", t->pattern);
1138
1139 return 0;
1140}
1141
1142static void
1143svg_dev_end_tile(fz_context *ctx, fz_device *dev)
1144{
1145 svg_device *sdev = (svg_device*)dev;
1146 fz_output *out = sdev->out;
1147 int num, cp = -1;
1148 tile *t;
1149 fz_matrix inverse;
1150 float x, y, w, h;
1151
1152 if (sdev->num_tiles == 0)
1153 return;
1154 num = --sdev->num_tiles;
1155 t = &sdev->tiles[num];
1156
1157 fz_write_printf(ctx, out, "</symbol>\n");
1158
1159 /* In svg, the reference tile is taken from (x,y) to (x+width,y+height)
1160 * and is repeated at (x+n*width,y+m*height) for all integer n and m.
1161 * This means that width and height generally correspond to xstep and
1162 * ystep. There are exceptional cases where we have to break this
1163 * though; when xstep/ystep are smaller than the width/height of the
1164 * pattern tile, we need to render the pattern contents several times
1165 * to ensure that the pattern tile contains everything. */
1166
1167 fz_write_printf(ctx, out, "<pattern id=\"pa%d\" patternUnits=\"userSpaceOnUse\" patternContentUnits=\"userSpaceOnUse\"",
1168 t->pattern);
1169 fz_write_printf(ctx, out, " x=\"0\" y=\"0\" width=\"%g\" height=\"%g\">\n",
1170 t->step.x, t->step.y);
1171
1172 if (t->view.x0 > 0 || t->step.x < t->view.x1 || t->view.y0 > 0 || t->step.y < t->view.y1)
1173 {
1174 cp = sdev->id++;
1175 fz_write_printf(ctx, out, "<clipPath id=\"cp%d\">\n", cp);
1176 fz_write_printf(ctx, out, "<path d=\"M %g %g L %g %g L %g %g L %g %g Z\"/>\n",
1177 t->view.x0, t->view.y0,
1178 t->view.x1, t->view.y0,
1179 t->view.x1, t->view.y1,
1180 t->view.x0, t->view.y1);
1181 fz_write_printf(ctx, out, "</clipPath>\n");
1182 fz_write_printf(ctx, out, "<g clip-path=\"url(#cp%d)\">\n", cp);
1183 }
1184
1185 /* All the pattern contents will have their own ctm applied. Let's
1186 * undo the current one to allow for this */
1187 inverse = fz_invert_matrix(t->ctm);
1188 fz_write_printf(ctx, out, "<g");
1189 svg_dev_ctm(ctx, sdev, inverse);
1190 fz_write_printf(ctx, out, ">\n");
1191
1192 w = t->view.x1 - t->view.x0;
1193 h = t->view.y1 - t->view.y0;
1194
1195 for (x = 0; x > -w; x -= t->step.x)
1196 for (y = 0; y > -h; y -= t->step.y)
1197 fz_write_printf(ctx, out, "<use x=\"%g\" y=\"%g\" xlink:href=\"#pac%d\"/>\n", x, y, t->pattern);
1198
1199 fz_write_printf(ctx, out, "</g>\n");
1200 if (cp != -1)
1201 fz_write_printf(ctx, out, "</g>\n");
1202 fz_write_printf(ctx, out, "</pattern>\n");
1203 out = end_def(ctx, sdev);
1204
1205 /* Finally, fill a rectangle with the pattern. */
1206 fz_write_printf(ctx, out, "<rect");
1207 svg_dev_ctm(ctx, sdev, t->ctm);
1208 fz_write_printf(ctx, out, " fill=\"url(#pa%d)\" x=\"%g\" y=\"%g\" width=\"%g\" height=\"%g\"/>\n",
1209 t->pattern, t->area.x0, t->area.y0, t->area.x1 - t->area.x0, t->area.y1 - t->area.y0);
1210}
1211
1212static void
1213svg_dev_begin_layer(fz_context *ctx, fz_device *dev, const char *name)
1214{
1215 svg_device *sdev = (svg_device*)dev;
1216 fz_output *out = sdev->out;
1217
1218 sdev->layers++;
1219 fz_write_printf(ctx, out, "<g id=\"Layer-%d\" data-name=\"%s\">\n", sdev->layers, name);
1220}
1221
1222static void
1223svg_dev_end_layer(fz_context *ctx, fz_device *dev)
1224{
1225 svg_device *sdev = (svg_device*)dev;
1226 fz_output *out = sdev->out;
1227
1228 if (sdev->layers == 0)
1229 return;
1230
1231 sdev->layers--;
1232 fz_write_printf(ctx, out, "</g>\n");
1233}
1234
1235static void
1236svg_dev_close_device(fz_context *ctx, fz_device *dev)
1237{
1238 svg_device *sdev = (svg_device*)dev;
1239 fz_output *out = sdev->out;
1240
1241 while (sdev->layers > 0)
1242 {
1243 fz_write_printf(ctx, out, "</g>\n");
1244 sdev->layers--;
1245 }
1246
1247 if (sdev->save_id)
1248 *sdev->save_id = sdev->id;
1249
1250 fz_write_printf(ctx, out, "</g>\n");
1251 fz_write_printf(ctx, out, "</svg>\n");
1252}
1253
1254static void
1255svg_dev_drop_device(fz_context *ctx, fz_device *dev)
1256{
1257 svg_device *sdev = (svg_device*)dev;
1258 int i;
1259
1260 fz_free(ctx, sdev->tiles);
1261 fz_drop_buffer(ctx, sdev->defs_buffer);
1262 fz_drop_output(ctx, sdev->defs);
1263 for (i = 0; i < sdev->num_fonts; i++)
1264 {
1265 fz_drop_font(ctx, sdev->fonts[i].font);
1266 fz_free(ctx, sdev->fonts[i].sentlist);
1267 }
1268 fz_free(ctx, sdev->fonts);
1269 for (i = 0; i < sdev->num_images; i++)
1270 {
1271 fz_drop_image(ctx, sdev->images[i].image);
1272 }
1273 fz_free(ctx, sdev->images);
1274}
1275
1276/*
1277 Create a device that outputs (single page)
1278 SVG files to the given output stream.
1279
1280 output: The output stream to send the constructed SVG page to.
1281
1282 page_width, page_height: The page dimensions to use (in points).
1283
1284 text_format: How to emit text. One of the following values:
1285 FZ_SVG_TEXT_AS_TEXT: As <text> elements with possible layout errors and mismatching fonts.
1286 FZ_SVG_TEXT_AS_PATH: As <path> elements with exact visual appearance.
1287
1288 reuse_images: Share image resources using <symbol> definitions.
1289
1290 id: ID parameter to keep generated IDs unique across SVG files.
1291*/
1292fz_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)
1293{
1294 svg_device *dev = fz_new_derived_device(ctx, svg_device);
1295
1296 dev->super.close_device = svg_dev_close_device;
1297 dev->super.drop_device = svg_dev_drop_device;
1298
1299 dev->super.fill_path = svg_dev_fill_path;
1300 dev->super.stroke_path = svg_dev_stroke_path;
1301 dev->super.clip_path = svg_dev_clip_path;
1302 dev->super.clip_stroke_path = svg_dev_clip_stroke_path;
1303
1304 dev->super.fill_text = svg_dev_fill_text;
1305 dev->super.stroke_text = svg_dev_stroke_text;
1306 dev->super.clip_text = svg_dev_clip_text;
1307 dev->super.clip_stroke_text = svg_dev_clip_stroke_text;
1308 dev->super.ignore_text = svg_dev_ignore_text;
1309
1310 dev->super.fill_shade = svg_dev_fill_shade;
1311 dev->super.fill_image = svg_dev_fill_image;
1312 dev->super.fill_image_mask = svg_dev_fill_image_mask;
1313 dev->super.clip_image_mask = svg_dev_clip_image_mask;
1314
1315 dev->super.pop_clip = svg_dev_pop_clip;
1316
1317 dev->super.begin_mask = svg_dev_begin_mask;
1318 dev->super.end_mask = svg_dev_end_mask;
1319 dev->super.begin_group = svg_dev_begin_group;
1320 dev->super.end_group = svg_dev_end_group;
1321
1322 dev->super.begin_tile = svg_dev_begin_tile;
1323 dev->super.end_tile = svg_dev_end_tile;
1324
1325 dev->super.begin_layer = svg_dev_begin_layer;
1326 dev->super.end_layer = svg_dev_end_layer;
1327
1328 dev->out = out;
1329 dev->out_store = out;
1330 dev->save_id = id;
1331 dev->id = id ? *id : 0;
1332 dev->layers = 0;
1333 dev->text_as_text = (text_format == FZ_SVG_TEXT_AS_TEXT);
1334 dev->reuse_images = reuse_images;
1335
1336 fz_write_printf(ctx, out, "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n");
1337 fz_write_printf(ctx, out, "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
1338 fz_write_printf(ctx, out, "<svg xmlns=\"http://www.w3.org/2000/svg\" "
1339 "xmlns:xlink=\"http://www.w3.org/1999/xlink\" version=\"1.1\" "
1340 "width=\"%gpt\" height=\"%gpt\" viewBox=\"0 0 %g %g\">\n",
1341 page_width, page_height, page_width, page_height);
1342 fz_write_printf(ctx, out, "<g enable-background=\"new\">\n");
1343
1344 return (fz_device*)dev;
1345}
1346
1347fz_device *fz_new_svg_device(fz_context *ctx, fz_output *out, float page_width, float page_height, int text_format, int reuse_images)
1348{
1349 return fz_new_svg_device_with_id(ctx, out, page_width, page_height, text_format, reuse_images, NULL);
1350}
1351