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