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