comparison mupdf-source/source/fitz/svg-device.c @ 3:2c135c81b16c

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