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