/src/mupdf/source/pdf/pdf-device.c
Line | Count | Source |
1 | | // Copyright (C) 2004-2026 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 | | #include "mupdf/pdf.h" |
25 | | |
26 | | #include <ft2build.h> |
27 | | #include FT_FREETYPE_H |
28 | | #include FT_ADVANCES_H |
29 | | |
30 | | #define ALLOWED_TEXT_POS_ERROR (0.001f) |
31 | | |
32 | 0 | #define ENC_IDENTITY 0 |
33 | 0 | #define ENC_UNICODE 1 |
34 | | |
35 | | typedef struct pdf_device pdf_device; |
36 | | |
37 | | typedef struct |
38 | | { |
39 | | /* The first few entries aren't really graphics state things, but |
40 | | * they are recorded here as they are fundamentally intertwined with |
41 | | * the push/pulling of the gstates. */ |
42 | | fz_buffer *buf; |
43 | | void (*on_pop)(fz_context*,pdf_device*,void *); |
44 | | void *on_pop_arg; |
45 | | |
46 | | /* The graphics state proper */ |
47 | | fz_matrix ctm; |
48 | | fz_colorspace *colorspace[2]; |
49 | | float color[2][4]; |
50 | | float alpha[2]; |
51 | | fz_stroke_state *stroke_state; |
52 | | int font; |
53 | | float font_size; |
54 | | int text_rendering_mode; |
55 | | int knockout; |
56 | | } gstate; |
57 | | |
58 | | /* The image digest information, object reference, as well as indirect reference |
59 | | * ID are all stored in doc->resources->image, and so they are maintained |
60 | | * through the life of the document not just this page level device. As we |
61 | | * encounter images on a page, we will add to the hash table if they are not |
62 | | * already present. When we have an image on a particular page, the resource |
63 | | * dict will be updated with the proper indirect reference across the document. |
64 | | * We do need to maintain some information as to what image resources we have |
65 | | * already specified for this page which is the purpose of image_indices |
66 | | */ |
67 | | |
68 | | typedef struct |
69 | | { |
70 | | float alpha; |
71 | | int stroke; |
72 | | } alpha_entry; |
73 | | |
74 | | typedef struct |
75 | | { |
76 | | int alpha; |
77 | | int isolated; |
78 | | int knockout; |
79 | | fz_colorspace *colorspace; |
80 | | pdf_obj *ref; |
81 | | } group_entry; |
82 | | |
83 | | struct pdf_device |
84 | | { |
85 | | fz_device super; |
86 | | |
87 | | pdf_document *doc; |
88 | | pdf_obj *resources; |
89 | | |
90 | | int in_text; |
91 | | |
92 | | int num_forms; |
93 | | int num_smasks; |
94 | | |
95 | | int num_gstates; |
96 | | int max_gstates; |
97 | | gstate *gstates; |
98 | | |
99 | | int num_imgs; |
100 | | int max_imgs; |
101 | | int *image_indices; |
102 | | |
103 | | int num_cid_fonts; |
104 | | int max_cid_fonts; |
105 | | fz_font **cid_fonts; |
106 | | int *cid_fonts_enc; |
107 | | |
108 | | int num_alphas; |
109 | | int max_alphas; |
110 | | alpha_entry *alphas; |
111 | | |
112 | | int num_groups; |
113 | | int max_groups; |
114 | | group_entry *groups; |
115 | | |
116 | | int num_shadings; |
117 | | }; |
118 | | |
119 | 0 | #define CURRENT_GSTATE(pdev) (&(pdev)->gstates[(pdev)->num_gstates-1]) |
120 | | |
121 | | /* Helper functions */ |
122 | | static void |
123 | | pdf_dev_stroke_state(fz_context *ctx, pdf_device *pdev, const fz_stroke_state *stroke_state) |
124 | 0 | { |
125 | 0 | gstate *gs = CURRENT_GSTATE(pdev); |
126 | |
|
127 | 0 | if (stroke_state == gs->stroke_state) |
128 | 0 | return; |
129 | 0 | if (gs->stroke_state && |
130 | 0 | !memcmp(stroke_state, gs->stroke_state, sizeof(*stroke_state)) |
131 | 0 | && gs->stroke_state->dash_len == stroke_state->dash_len && |
132 | 0 | (stroke_state->dash_len == 0 || |
133 | 0 | !memcmp(&gs->stroke_state->dash_list[0], &stroke_state->dash_list[0], sizeof(stroke_state->dash_list[0]) * stroke_state->dash_len))) |
134 | 0 | return; |
135 | 0 | if (!gs->stroke_state || gs->stroke_state->linewidth != stroke_state->linewidth) |
136 | 0 | { |
137 | 0 | fz_append_printf(ctx, gs->buf, "%g w\n", stroke_state->linewidth); |
138 | 0 | } |
139 | 0 | if (!gs->stroke_state || gs->stroke_state->start_cap != stroke_state->start_cap) |
140 | 0 | { |
141 | 0 | int cap = stroke_state->start_cap; |
142 | | /* FIXME: Triangle caps aren't supported in pdf */ |
143 | 0 | if (cap == FZ_LINECAP_TRIANGLE) |
144 | 0 | cap = FZ_LINECAP_BUTT; |
145 | 0 | fz_append_printf(ctx, gs->buf, "%d J\n", cap); |
146 | 0 | } |
147 | 0 | if (!gs->stroke_state || gs->stroke_state->linejoin != stroke_state->linejoin) |
148 | 0 | { |
149 | 0 | int join = stroke_state->linejoin; |
150 | 0 | if (join == FZ_LINEJOIN_MITER_XPS) |
151 | 0 | join = FZ_LINEJOIN_MITER; |
152 | 0 | fz_append_printf(ctx, gs->buf, "%d j\n", join); |
153 | 0 | } |
154 | 0 | if (!gs->stroke_state || fz_max(1.0f, gs->stroke_state->miterlimit) != fz_max(1.0f, stroke_state->miterlimit)) |
155 | 0 | { |
156 | 0 | fz_append_printf(ctx, gs->buf, "%g M\n", fz_max(1.0f, stroke_state->miterlimit)); |
157 | 0 | } |
158 | 0 | if (gs->stroke_state == NULL && stroke_state->dash_len == 0) |
159 | 0 | { |
160 | | /* No stroke details. */ |
161 | 0 | } |
162 | 0 | else if (!gs->stroke_state || gs->stroke_state->dash_phase != stroke_state->dash_phase || gs->stroke_state->dash_len != stroke_state->dash_len || |
163 | 0 | memcmp(gs->stroke_state->dash_list, stroke_state->dash_list, sizeof(float)*stroke_state->dash_len)) |
164 | 0 | { |
165 | 0 | int i; |
166 | 0 | fz_append_byte(ctx, gs->buf, '['); |
167 | 0 | for (i = 0; i < stroke_state->dash_len; i++) |
168 | 0 | { |
169 | 0 | if (i > 0) |
170 | 0 | fz_append_byte(ctx, gs->buf, ' '); |
171 | 0 | fz_append_printf(ctx, gs->buf, "%g", stroke_state->dash_list[i]); |
172 | 0 | } |
173 | 0 | fz_append_printf(ctx, gs->buf, "]%g d\n", stroke_state->dash_phase); |
174 | 0 | } |
175 | 0 | fz_drop_stroke_state(ctx, gs->stroke_state); |
176 | 0 | gs->stroke_state = fz_keep_stroke_state(ctx, stroke_state); |
177 | 0 | } |
178 | | |
179 | | typedef struct |
180 | | { |
181 | | fz_context *ctx; |
182 | | fz_buffer *buf; |
183 | | } pdf_dev_path_arg; |
184 | | |
185 | | static void |
186 | | pdf_dev_path_moveto(fz_context *ctx, void *arg, float x, float y) |
187 | 0 | { |
188 | 0 | fz_buffer *buf = (fz_buffer *)arg; |
189 | 0 | fz_append_printf(ctx, buf, "%g %g m\n", x, y); |
190 | 0 | } |
191 | | |
192 | | static void |
193 | | pdf_dev_path_lineto(fz_context *ctx, void *arg, float x, float y) |
194 | 0 | { |
195 | 0 | fz_buffer *buf = (fz_buffer *)arg; |
196 | 0 | fz_append_printf(ctx, buf, "%g %g l\n", x, y); |
197 | 0 | } |
198 | | |
199 | | static void |
200 | | pdf_dev_path_curveto(fz_context *ctx, void *arg, float x1, float y1, float x2, float y2, float x3, float y3) |
201 | 0 | { |
202 | 0 | fz_buffer *buf = (fz_buffer *)arg; |
203 | 0 | fz_append_printf(ctx, buf, "%g %g %g %g %g %g c\n", x1, y1, x2, y2, x3, y3); |
204 | 0 | } |
205 | | |
206 | | static void |
207 | | pdf_dev_path_close(fz_context *ctx, void *arg) |
208 | 0 | { |
209 | 0 | fz_buffer *buf = (fz_buffer *)arg; |
210 | 0 | fz_append_string(ctx, buf, "h\n"); |
211 | 0 | } |
212 | | |
213 | | static const fz_path_walker pdf_dev_path_proc = |
214 | | { |
215 | | pdf_dev_path_moveto, |
216 | | pdf_dev_path_lineto, |
217 | | pdf_dev_path_curveto, |
218 | | pdf_dev_path_close |
219 | | }; |
220 | | |
221 | | static void |
222 | | pdf_dev_path(fz_context *ctx, pdf_device *pdev, const fz_path *path) |
223 | 0 | { |
224 | 0 | gstate *gs = CURRENT_GSTATE(pdev); |
225 | 0 | fz_rect bounds; |
226 | |
|
227 | 0 | if (fz_path_is_rect_with_bounds(ctx, path, fz_identity, &bounds)) |
228 | 0 | { |
229 | 0 | fz_append_printf(ctx, gs->buf, "%g %g %g %g re\n", bounds.x0, bounds.y0, bounds.x1-bounds.x0, bounds.y1-bounds.y0); |
230 | 0 | return; |
231 | 0 | } |
232 | | |
233 | 0 | fz_walk_path(ctx, path, &pdf_dev_path_proc, (void *)gs->buf); |
234 | 0 | } |
235 | | |
236 | | static void |
237 | | pdf_dev_stroked_path(fz_context *ctx, pdf_device *pdev, const fz_path *path) |
238 | 0 | { |
239 | 0 | gstate *gs = CURRENT_GSTATE(pdev); |
240 | 0 | fz_rect bounds; |
241 | |
|
242 | 0 | if (fz_path_is_closed(ctx, path) && fz_path_is_rect_with_bounds(ctx, path, fz_identity, &bounds)) |
243 | 0 | { |
244 | 0 | fz_append_printf(ctx, gs->buf, "%g %g %g %g re\n", bounds.x0, bounds.y0, bounds.x1-bounds.x0, bounds.y1-bounds.y0); |
245 | 0 | return; |
246 | 0 | } |
247 | | |
248 | 0 | fz_walk_path(ctx, path, &pdf_dev_path_proc, (void *)gs->buf); |
249 | 0 | } |
250 | | |
251 | | static void |
252 | | pdf_dev_end_text(fz_context *ctx, pdf_device *pdev) |
253 | 0 | { |
254 | 0 | gstate *gs = CURRENT_GSTATE(pdev); |
255 | |
|
256 | 0 | if (!pdev->in_text) |
257 | 0 | return; |
258 | 0 | pdev->in_text = 0; |
259 | 0 | fz_append_string(ctx, gs->buf, "ET\n"); |
260 | 0 | } |
261 | | |
262 | | static void |
263 | | pdf_dev_ctm(fz_context *ctx, pdf_device *pdev, fz_matrix ctm) |
264 | 0 | { |
265 | 0 | fz_matrix inverse; |
266 | 0 | gstate *gs = CURRENT_GSTATE(pdev); |
267 | |
|
268 | 0 | if (memcmp(&gs->ctm, &ctm, sizeof(ctm)) == 0) |
269 | 0 | return; |
270 | 0 | pdf_dev_end_text(ctx, pdev); |
271 | 0 | inverse = fz_invert_matrix(gs->ctm); |
272 | 0 | inverse = fz_concat(ctm, inverse); |
273 | 0 | gs->ctm = ctm; |
274 | 0 | fz_append_printf(ctx, gs->buf, "%M cm\n", &inverse); |
275 | 0 | } |
276 | | |
277 | | static void |
278 | | pdf_dev_color(fz_context *ctx, pdf_device *pdev, fz_colorspace *colorspace, const float *color, int stroke, fz_color_params color_params) |
279 | 0 | { |
280 | 0 | int diff = 0; |
281 | 0 | int i; |
282 | 0 | int cspace = 0; |
283 | 0 | float rgb[FZ_MAX_COLORS]; |
284 | 0 | gstate *gs = CURRENT_GSTATE(pdev); |
285 | |
|
286 | 0 | if (colorspace == fz_device_gray(ctx)) |
287 | 0 | cspace = 1; |
288 | 0 | else if (colorspace == fz_device_rgb(ctx)) |
289 | 0 | cspace = 3; |
290 | 0 | else if (colorspace == fz_device_cmyk(ctx)) |
291 | 0 | cspace = 4; |
292 | |
|
293 | 0 | if (cspace == 0) |
294 | 0 | { |
295 | | /* If it's an unknown colorspace, fallback to rgb */ |
296 | 0 | fz_convert_color(ctx, colorspace, color, fz_device_rgb(ctx), rgb, NULL, color_params); |
297 | 0 | color = rgb; |
298 | 0 | colorspace = fz_device_rgb(ctx); |
299 | 0 | cspace = 3; |
300 | 0 | } |
301 | |
|
302 | 0 | if (gs->colorspace[stroke] != colorspace) |
303 | 0 | { |
304 | 0 | gs->colorspace[stroke] = colorspace; |
305 | 0 | diff = 1; |
306 | 0 | } |
307 | |
|
308 | 0 | for (i=0; i < cspace; i++) |
309 | 0 | if (gs->color[stroke][i] != color[i]) |
310 | 0 | { |
311 | 0 | gs->color[stroke][i] = color[i]; |
312 | 0 | diff = 1; |
313 | 0 | } |
314 | |
|
315 | 0 | if (diff == 0) |
316 | 0 | return; |
317 | | |
318 | 0 | switch (cspace + stroke*8) |
319 | 0 | { |
320 | 0 | case 1: |
321 | 0 | fz_append_printf(ctx, gs->buf, "%g g\n", color[0]); |
322 | 0 | break; |
323 | 0 | case 3: |
324 | 0 | fz_append_printf(ctx, gs->buf, "%g %g %g rg\n", color[0], color[1], color[2]); |
325 | 0 | break; |
326 | 0 | case 4: |
327 | 0 | fz_append_printf(ctx, gs->buf, "%g %g %g %g k\n", color[0], color[1], color[2], color[3]); |
328 | 0 | break; |
329 | 0 | case 1+8: |
330 | 0 | fz_append_printf(ctx, gs->buf, "%g G\n", color[0]); |
331 | 0 | break; |
332 | 0 | case 3+8: |
333 | 0 | fz_append_printf(ctx, gs->buf, "%g %g %g RG\n", color[0], color[1], color[2]); |
334 | 0 | break; |
335 | 0 | case 4+8: |
336 | 0 | fz_append_printf(ctx, gs->buf, "%g %g %g %g K\n", color[0], color[1], color[2], color[3]); |
337 | 0 | break; |
338 | 0 | } |
339 | 0 | } |
340 | | |
341 | | static void |
342 | | pdf_dev_alpha(fz_context *ctx, pdf_device *pdev, float alpha, int stroke) |
343 | 0 | { |
344 | 0 | int i; |
345 | 0 | pdf_document *doc = pdev->doc; |
346 | 0 | gstate *gs = CURRENT_GSTATE(pdev); |
347 | | |
348 | | /* If the alpha is unchanged, nothing to do */ |
349 | 0 | if (gs->alpha[stroke] == alpha) |
350 | 0 | return; |
351 | | |
352 | 0 | gs->alpha[stroke] = alpha; |
353 | | |
354 | | /* Have we sent such an alpha before? */ |
355 | 0 | for (i = 0; i < pdev->num_alphas; i++) |
356 | 0 | if (pdev->alphas[i].alpha == alpha && pdev->alphas[i].stroke == stroke) |
357 | 0 | break; |
358 | |
|
359 | 0 | if (i == pdev->num_alphas) |
360 | 0 | { |
361 | 0 | pdf_obj *o, *ref; |
362 | | |
363 | | /* No. Need to make a new one */ |
364 | 0 | if (pdev->num_alphas == pdev->max_alphas) |
365 | 0 | { |
366 | 0 | int newmax = pdev->max_alphas * 2; |
367 | 0 | if (newmax == 0) |
368 | 0 | newmax = 4; |
369 | 0 | pdev->alphas = fz_realloc_array(ctx, pdev->alphas, newmax, alpha_entry); |
370 | 0 | pdev->max_alphas = newmax; |
371 | 0 | } |
372 | 0 | pdev->alphas[i].alpha = alpha; |
373 | 0 | pdev->alphas[i].stroke = stroke; |
374 | |
|
375 | 0 | o = pdf_new_dict(ctx, doc, 1); |
376 | 0 | fz_try(ctx) |
377 | 0 | { |
378 | 0 | char text[32]; |
379 | 0 | pdf_dict_put_real(ctx, o, (stroke ? PDF_NAME(CA) : PDF_NAME(ca)), alpha); |
380 | 0 | fz_snprintf(text, sizeof(text), "ExtGState/Alp%d", i); |
381 | 0 | ref = pdf_add_object(ctx, doc, o); |
382 | 0 | pdf_dict_putp_drop(ctx, pdev->resources, text, ref); |
383 | 0 | } |
384 | 0 | fz_always(ctx) |
385 | 0 | { |
386 | 0 | pdf_drop_obj(ctx, o); |
387 | 0 | } |
388 | 0 | fz_catch(ctx) |
389 | 0 | { |
390 | 0 | fz_rethrow(ctx); |
391 | 0 | } |
392 | 0 | pdev->num_alphas++; |
393 | 0 | } |
394 | 0 | fz_append_printf(ctx, gs->buf, "/Alp%d gs\n", i); |
395 | 0 | } |
396 | | |
397 | | static int |
398 | | pdf_dev_find_font_res(fz_context *ctx, pdf_device *pdev, fz_font *font) |
399 | 0 | { |
400 | 0 | int k; |
401 | | |
402 | | /* Check if we already had this one */ |
403 | 0 | for (k = 0; k < pdev->num_cid_fonts; k++) |
404 | 0 | if (pdev->cid_fonts[k] == font) |
405 | 0 | return k; |
406 | | |
407 | 0 | return -1; |
408 | 0 | } |
409 | | |
410 | | static int |
411 | | pdf_dev_add_font_res_imp(fz_context *ctx, pdf_device *pdev, fz_font *font, pdf_obj *fres, int enc) |
412 | 0 | { |
413 | 0 | char text[32]; |
414 | 0 | int num; |
415 | | |
416 | | /* Not there so add to resources */ |
417 | 0 | fz_snprintf(text, sizeof(text), "Font/F%d", pdev->num_cid_fonts); |
418 | 0 | pdf_dict_putp_drop(ctx, pdev->resources, text, fres); |
419 | | |
420 | | /* And add index to our list for this page */ |
421 | 0 | if (pdev->num_cid_fonts == pdev->max_cid_fonts) |
422 | 0 | { |
423 | 0 | int newmax = pdev->max_cid_fonts * 2; |
424 | 0 | if (newmax == 0) |
425 | 0 | newmax = 4; |
426 | 0 | pdev->cid_fonts = fz_realloc_array(ctx, pdev->cid_fonts, newmax, fz_font*); |
427 | 0 | pdev->cid_fonts_enc = fz_realloc_array(ctx, pdev->cid_fonts_enc, newmax, int); |
428 | 0 | pdev->max_cid_fonts = newmax; |
429 | 0 | } |
430 | 0 | num = pdev->num_cid_fonts++; |
431 | 0 | pdev->cid_fonts[num] = fz_keep_font(ctx, font); |
432 | 0 | pdev->cid_fonts_enc[num] = enc; |
433 | 0 | return num; |
434 | 0 | } |
435 | | |
436 | | static int |
437 | | pdf_dev_add_substitute_font_res(fz_context *ctx, pdf_device *pdev, fz_font *font) |
438 | 0 | { |
439 | 0 | pdf_obj *fres; |
440 | 0 | int k; |
441 | | |
442 | | /* Check if we already had this one */ |
443 | 0 | k = pdf_dev_find_font_res(ctx, pdev, font); |
444 | 0 | if (k >= 0) |
445 | 0 | return k; |
446 | | |
447 | | /* This will add it to the xref if needed */ |
448 | 0 | if (font->flags.cjk) |
449 | 0 | fres = pdf_add_cjk_font(ctx, pdev->doc, font, font->flags.cjk_lang, 0, font->flags.is_serif); |
450 | 0 | else |
451 | 0 | fres = pdf_add_substitute_font(ctx, pdev->doc, font); |
452 | | |
453 | | /* And add to the resource dictionary. */ |
454 | 0 | return pdf_dev_add_font_res_imp(ctx, pdev, font, fres, ENC_UNICODE); |
455 | 0 | } |
456 | | |
457 | | static int |
458 | | pdf_dev_add_embedded_font_res(fz_context *ctx, pdf_device *pdev, fz_font *font) |
459 | 0 | { |
460 | 0 | pdf_obj *fres; |
461 | 0 | int k; |
462 | | |
463 | | /* Check if we already had this one */ |
464 | 0 | k = pdf_dev_find_font_res(ctx, pdev, font); |
465 | 0 | if (k >= 0) |
466 | 0 | return k; |
467 | | |
468 | | /* This will add it to the xref if needed */ |
469 | 0 | fres = pdf_add_cid_font(ctx, pdev->doc, font); |
470 | | |
471 | | /* And add to the resource dictionary. */ |
472 | 0 | return pdf_dev_add_font_res_imp(ctx, pdev, font, fres, ENC_IDENTITY); |
473 | 0 | } |
474 | | |
475 | | static void |
476 | | pdf_dev_font(fz_context *ctx, pdf_device *pdev, fz_font *font, fz_matrix trm) |
477 | 0 | { |
478 | 0 | gstate *gs = CURRENT_GSTATE(pdev); |
479 | 0 | float font_size = fz_matrix_expansion(trm); |
480 | | |
481 | | /* If the font is unchanged, nothing to do */ |
482 | 0 | if (gs->font >= 0 && pdev->cid_fonts[gs->font] == font && gs->font_size == font_size) |
483 | 0 | return; |
484 | | |
485 | | // TODO: vertical wmode |
486 | | |
487 | 0 | if (fz_font_t3_procs(ctx, font)) |
488 | 0 | fz_throw(ctx, FZ_ERROR_UNSUPPORTED, "pdf device does not support type 3 fonts"); |
489 | | |
490 | 0 | if (fz_font_flags(font)->ft_substitute || !pdf_font_writing_supported(ctx, font)) |
491 | 0 | gs->font = pdf_dev_add_substitute_font_res(ctx, pdev, font); |
492 | 0 | else |
493 | 0 | gs->font = pdf_dev_add_embedded_font_res(ctx, pdev, font); |
494 | |
|
495 | 0 | gs->font_size = font_size; |
496 | |
|
497 | 0 | fz_append_printf(ctx, gs->buf, "/F%d %g Tf\n", gs->font, gs->font_size); |
498 | 0 | } |
499 | | |
500 | | static void |
501 | | pdf_dev_push_new_buf(fz_context *ctx, pdf_device *pdev, fz_buffer *buf, void (*on_pop)(fz_context*,pdf_device*,void*), void *on_pop_arg) |
502 | 0 | { |
503 | 0 | if (pdev->num_gstates == pdev->max_gstates) |
504 | 0 | { |
505 | 0 | int newmax = pdev->max_gstates*2; |
506 | 0 | pdev->gstates = fz_realloc_array(ctx, pdev->gstates, newmax, gstate); |
507 | 0 | pdev->max_gstates = newmax; |
508 | 0 | } |
509 | 0 | memcpy(&pdev->gstates[pdev->num_gstates], &pdev->gstates[pdev->num_gstates-1], sizeof(*pdev->gstates)); |
510 | 0 | fz_keep_stroke_state(ctx, pdev->gstates[pdev->num_gstates].stroke_state); |
511 | 0 | if (buf) |
512 | 0 | pdev->gstates[pdev->num_gstates].buf = buf; |
513 | 0 | else |
514 | 0 | fz_keep_buffer(ctx, pdev->gstates[pdev->num_gstates].buf); |
515 | 0 | pdev->gstates[pdev->num_gstates].on_pop = on_pop; |
516 | 0 | pdev->gstates[pdev->num_gstates].on_pop_arg = on_pop_arg; |
517 | 0 | fz_append_string(ctx, pdev->gstates[pdev->num_gstates].buf, "q\n"); |
518 | 0 | pdev->num_gstates++; |
519 | 0 | } |
520 | | |
521 | | static void |
522 | | pdf_dev_push(fz_context *ctx, pdf_device *pdev) |
523 | 0 | { |
524 | 0 | pdf_dev_push_new_buf(ctx, pdev, NULL, NULL, NULL); |
525 | 0 | } |
526 | | |
527 | | static void * |
528 | | pdf_dev_pop(fz_context *ctx, pdf_device *pdev) |
529 | 0 | { |
530 | 0 | gstate *gs = CURRENT_GSTATE(pdev); |
531 | 0 | void *arg = gs->on_pop_arg; |
532 | |
|
533 | 0 | fz_append_string(ctx, gs->buf, "Q\n"); |
534 | 0 | if (gs->on_pop) |
535 | 0 | gs->on_pop(ctx, pdev, arg); |
536 | 0 | pdev->num_gstates--; |
537 | 0 | fz_drop_stroke_state(ctx, pdev->gstates[pdev->num_gstates].stroke_state); |
538 | 0 | fz_drop_buffer(ctx, pdev->gstates[pdev->num_gstates].buf); |
539 | 0 | return arg; |
540 | 0 | } |
541 | | |
542 | | static void |
543 | | pdf_dev_text_span(fz_context *ctx, pdf_device *pdev, fz_text_span *span) |
544 | 0 | { |
545 | 0 | gstate *gs = CURRENT_GSTATE(pdev); |
546 | 0 | fz_matrix trm, tm, tlm, inv_trm, inv_tm; |
547 | 0 | fz_matrix inv_tfs; |
548 | 0 | fz_point d; |
549 | 0 | float adv; |
550 | 0 | int enc; |
551 | 0 | int dx, dy; |
552 | 0 | int i; |
553 | |
|
554 | 0 | if (span->len == 0) |
555 | 0 | return; |
556 | | |
557 | 0 | inv_tfs = fz_scale(1 / gs->font_size, 1 / gs->font_size); |
558 | |
|
559 | 0 | trm = span->trm; |
560 | 0 | trm.e = span->items[0].x; |
561 | 0 | trm.f = span->items[0].y; |
562 | |
|
563 | 0 | tm = fz_concat(inv_tfs, trm); |
564 | 0 | tlm = tm; |
565 | |
|
566 | 0 | inv_tm = fz_invert_matrix(tm); |
567 | 0 | inv_trm = fz_invert_matrix(trm); |
568 | |
|
569 | 0 | enc = pdev->cid_fonts_enc[gs->font]; |
570 | |
|
571 | 0 | fz_append_printf(ctx, gs->buf, "%M Tm\n[<", &tm); |
572 | |
|
573 | 0 | for (i = 0; i < span->len; ++i) |
574 | 0 | { |
575 | 0 | fz_text_item *it = &span->items[i]; |
576 | 0 | if (enc == ENC_IDENTITY && it->gid < 0) |
577 | 0 | continue; |
578 | 0 | if (enc == ENC_UNICODE && it->ucs < 0) |
579 | 0 | continue; |
580 | | |
581 | | /* transform difference from expected pen position into font units. */ |
582 | 0 | d.x = it->x - trm.e; |
583 | 0 | d.y = it->y - trm.f; |
584 | 0 | d = fz_transform_vector(d, inv_trm); |
585 | 0 | dx = (int)(d.x * 1000 + (d.x < 0 ? -0.5f : 0.5f)); |
586 | 0 | dy = (int)(d.y * 1000 + (d.y < 0 ? -0.5f : 0.5f)); |
587 | |
|
588 | 0 | trm.e = it->x; |
589 | 0 | trm.f = it->y; |
590 | |
|
591 | 0 | if (dx != 0 || dy != 0) |
592 | 0 | { |
593 | 0 | if (span->wmode == 0 && dy == 0) |
594 | 0 | fz_append_printf(ctx, gs->buf, ">%d<", -dx); |
595 | 0 | else if (span->wmode == 1 && dx == 0) |
596 | 0 | fz_append_printf(ctx, gs->buf, ">%d<", -dy); |
597 | 0 | else |
598 | 0 | { |
599 | | /* Calculate offset from start of the previous line */ |
600 | 0 | tm = fz_concat(inv_tfs, trm); |
601 | 0 | d.x = tm.e - tlm.e; |
602 | 0 | d.y = tm.f - tlm.f; |
603 | 0 | d = fz_transform_vector(d, inv_tm); |
604 | 0 | fz_append_printf(ctx, gs->buf, ">]TJ\n%g %g Td\n[<", d.x, d.y); |
605 | 0 | tlm = tm; |
606 | 0 | } |
607 | 0 | } |
608 | |
|
609 | 0 | if (fz_font_t3_procs(ctx, span->font)) |
610 | 0 | fz_append_printf(ctx, gs->buf, "%02x", it->gid); |
611 | 0 | else if (enc == ENC_IDENTITY) |
612 | 0 | fz_append_printf(ctx, gs->buf, "%04x", it->gid); |
613 | 0 | else if (enc == ENC_UNICODE) |
614 | 0 | fz_append_printf(ctx, gs->buf, "%04x", it->ucs); |
615 | |
|
616 | 0 | if (it->gid != -1) |
617 | 0 | { |
618 | 0 | adv = fz_advance_glyph(ctx, span->font, it->gid, span->wmode); |
619 | 0 | if (span->wmode == 0) |
620 | 0 | trm = fz_pre_translate(trm, adv, 0); |
621 | 0 | else |
622 | 0 | trm = fz_pre_translate(trm, 0, adv); |
623 | 0 | } |
624 | 0 | } |
625 | |
|
626 | 0 | fz_append_string(ctx, gs->buf, ">]TJ\n"); |
627 | 0 | } |
628 | | |
629 | | static void |
630 | | pdf_dev_trm(fz_context *ctx, pdf_device *pdev, int trm) |
631 | 0 | { |
632 | 0 | gstate *gs = CURRENT_GSTATE(pdev); |
633 | |
|
634 | 0 | if (gs->text_rendering_mode == trm) |
635 | 0 | return; |
636 | 0 | gs->text_rendering_mode = trm; |
637 | 0 | fz_append_printf(ctx, gs->buf, "%d Tr\n", trm); |
638 | 0 | } |
639 | | |
640 | | static void |
641 | | pdf_dev_begin_text(fz_context *ctx, pdf_device *pdev, int trm) |
642 | 0 | { |
643 | 0 | pdf_dev_trm(ctx, pdev, trm); |
644 | 0 | if (!pdev->in_text) |
645 | 0 | { |
646 | 0 | gstate *gs = CURRENT_GSTATE(pdev); |
647 | 0 | fz_append_string(ctx, gs->buf, "BT\n"); |
648 | 0 | pdev->in_text = 1; |
649 | 0 | } |
650 | 0 | } |
651 | | |
652 | | static int |
653 | | pdf_dev_new_form(fz_context *ctx, pdf_obj **form_ref, pdf_device *pdev, fz_rect bbox, int isolated, int knockout, float alpha, fz_colorspace *colorspace) |
654 | 0 | { |
655 | 0 | pdf_document *doc = pdev->doc; |
656 | 0 | int num; |
657 | 0 | pdf_obj *group_ref = NULL; |
658 | 0 | pdf_obj *group; |
659 | 0 | pdf_obj *form; |
660 | |
|
661 | 0 | *form_ref = NULL; |
662 | | |
663 | | /* Find (or make) a new group with the required options. */ |
664 | 0 | for(num = 0; num < pdev->num_groups; num++) |
665 | 0 | { |
666 | 0 | group_entry *g = &pdev->groups[num]; |
667 | 0 | if (g->isolated == isolated && g->knockout == knockout && g->alpha == alpha && g->colorspace == colorspace) |
668 | 0 | { |
669 | 0 | group_ref = pdev->groups[num].ref; |
670 | 0 | break; |
671 | 0 | } |
672 | 0 | } |
673 | | |
674 | | /* If we didn't find one, make one */ |
675 | 0 | if (num == pdev->num_groups) |
676 | 0 | { |
677 | 0 | if (pdev->num_groups == pdev->max_groups) |
678 | 0 | { |
679 | 0 | int newmax = pdev->max_groups * 2; |
680 | 0 | if (newmax == 0) |
681 | 0 | newmax = 4; |
682 | 0 | pdev->groups = fz_realloc_array(ctx, pdev->groups, newmax, group_entry); |
683 | 0 | pdev->max_groups = newmax; |
684 | 0 | } |
685 | 0 | pdev->num_groups++; |
686 | 0 | pdev->groups[num].isolated = isolated; |
687 | 0 | pdev->groups[num].knockout = knockout; |
688 | 0 | pdev->groups[num].alpha = alpha; |
689 | 0 | pdev->groups[num].colorspace = fz_keep_colorspace(ctx, colorspace); |
690 | 0 | pdev->groups[num].ref = NULL; |
691 | 0 | group = pdf_new_dict(ctx, doc, 5); |
692 | 0 | fz_try(ctx) |
693 | 0 | { |
694 | 0 | pdf_dict_put(ctx, group, PDF_NAME(Type), PDF_NAME(Group)); |
695 | 0 | pdf_dict_put(ctx, group, PDF_NAME(S), PDF_NAME(Transparency)); |
696 | 0 | pdf_dict_put_bool(ctx, group, PDF_NAME(K), knockout); |
697 | 0 | pdf_dict_put_bool(ctx, group, PDF_NAME(I), isolated); |
698 | 0 | switch (fz_colorspace_type(ctx, colorspace)) |
699 | 0 | { |
700 | 0 | case FZ_COLORSPACE_GRAY: |
701 | 0 | pdf_dict_put(ctx, group, PDF_NAME(CS), PDF_NAME(DeviceGray)); |
702 | 0 | break; |
703 | 0 | case FZ_COLORSPACE_RGB: |
704 | 0 | pdf_dict_put(ctx, group, PDF_NAME(CS), PDF_NAME(DeviceRGB)); |
705 | 0 | break; |
706 | 0 | case FZ_COLORSPACE_CMYK: |
707 | 0 | pdf_dict_put(ctx, group, PDF_NAME(CS), PDF_NAME(DeviceCMYK)); |
708 | 0 | break; |
709 | 0 | default: |
710 | 0 | break; |
711 | 0 | } |
712 | 0 | group_ref = pdev->groups[num].ref = pdf_add_object(ctx, doc, group); |
713 | 0 | } |
714 | 0 | fz_always(ctx) |
715 | 0 | { |
716 | 0 | pdf_drop_obj(ctx, group); |
717 | 0 | } |
718 | 0 | fz_catch(ctx) |
719 | 0 | { |
720 | 0 | fz_rethrow(ctx); |
721 | 0 | } |
722 | 0 | } |
723 | | |
724 | | /* Make us a new Forms object that points to that group, and change |
725 | | * to writing into the buffer for that Forms object. */ |
726 | 0 | form = pdf_new_dict(ctx, doc, 4); |
727 | 0 | fz_try(ctx) |
728 | 0 | { |
729 | 0 | pdf_dict_put(ctx, form, PDF_NAME(Subtype), PDF_NAME(Form)); |
730 | 0 | pdf_dict_put(ctx, form, PDF_NAME(Group), group_ref); |
731 | 0 | pdf_dict_put_int(ctx, form, PDF_NAME(FormType), 1); |
732 | 0 | pdf_dict_put_rect(ctx, form, PDF_NAME(BBox), bbox); |
733 | 0 | *form_ref = pdf_add_object(ctx, doc, form); |
734 | 0 | } |
735 | 0 | fz_always(ctx) |
736 | 0 | { |
737 | 0 | pdf_drop_obj(ctx, form); |
738 | 0 | } |
739 | 0 | fz_catch(ctx) |
740 | 0 | { |
741 | 0 | fz_rethrow(ctx); |
742 | 0 | } |
743 | | |
744 | | /* Insert the new form object into the resources */ |
745 | 0 | { |
746 | 0 | char text[32]; |
747 | 0 | num = pdev->num_forms++; |
748 | 0 | fz_snprintf(text, sizeof(text), "XObject/Fm%d", num); |
749 | 0 | pdf_dict_putp(ctx, pdev->resources, text, *form_ref); |
750 | 0 | } |
751 | |
|
752 | 0 | return num; |
753 | 0 | } |
754 | | |
755 | | /* Entry points */ |
756 | | |
757 | | static void |
758 | | pdf_dev_fill_path(fz_context *ctx, fz_device *dev, const fz_path *path, int even_odd, fz_matrix ctm, |
759 | | fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params) |
760 | 0 | { |
761 | 0 | pdf_device *pdev = (pdf_device*)dev; |
762 | 0 | gstate *gs = CURRENT_GSTATE(pdev); |
763 | |
|
764 | 0 | pdf_dev_end_text(ctx, pdev); |
765 | 0 | pdf_dev_alpha(ctx, pdev, alpha, 0); |
766 | 0 | pdf_dev_color(ctx, pdev, colorspace, color, 0, color_params); |
767 | 0 | pdf_dev_ctm(ctx, pdev, ctm); |
768 | 0 | pdf_dev_path(ctx, pdev, path); |
769 | 0 | fz_append_string(ctx, gs->buf, (even_odd ? "f*\n" : "f\n")); |
770 | 0 | } |
771 | | |
772 | | static void |
773 | | pdf_dev_stroke_path(fz_context *ctx, fz_device *dev, const fz_path *path, const fz_stroke_state *stroke, fz_matrix ctm, |
774 | | fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params) |
775 | 0 | { |
776 | 0 | pdf_device *pdev = (pdf_device*)dev; |
777 | 0 | gstate *gs = CURRENT_GSTATE(pdev); |
778 | |
|
779 | 0 | pdf_dev_end_text(ctx, pdev); |
780 | 0 | pdf_dev_alpha(ctx, pdev, alpha, 1); |
781 | 0 | pdf_dev_color(ctx, pdev, colorspace, color, 1, color_params); |
782 | 0 | pdf_dev_ctm(ctx, pdev, ctm); |
783 | 0 | pdf_dev_stroke_state(ctx, pdev, stroke); |
784 | 0 | pdf_dev_stroked_path(ctx, pdev, path); |
785 | 0 | fz_append_string(ctx, gs->buf, "S\n"); |
786 | 0 | } |
787 | | |
788 | | static void |
789 | | pdf_dev_clip_path(fz_context *ctx, fz_device *dev, const fz_path *path, int even_odd, fz_matrix ctm, fz_rect scissor) |
790 | 0 | { |
791 | 0 | pdf_device *pdev = (pdf_device*)dev; |
792 | 0 | gstate *gs; |
793 | |
|
794 | 0 | pdf_dev_end_text(ctx, pdev); |
795 | 0 | pdf_dev_push(ctx, pdev); |
796 | 0 | pdf_dev_ctm(ctx, pdev, ctm); |
797 | 0 | pdf_dev_path(ctx, pdev, path); |
798 | 0 | gs = CURRENT_GSTATE(pdev); |
799 | 0 | fz_append_string(ctx, gs->buf, (even_odd ? "W* n\n" : "W n\n")); |
800 | 0 | } |
801 | | |
802 | | static void |
803 | | pdf_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) |
804 | 0 | { |
805 | 0 | pdf_device *pdev = (pdf_device*)dev; |
806 | 0 | gstate *gs; |
807 | |
|
808 | 0 | pdf_dev_end_text(ctx, pdev); |
809 | 0 | pdf_dev_push(ctx, pdev); |
810 | | /* FIXME: Need to push a group, select a pattern (or shading) here, |
811 | | * stroke with the pattern/shading. Then move to defining that pattern |
812 | | * with the next calls to the device interface until the next pop |
813 | | * when we pop the group. */ |
814 | 0 | pdf_dev_ctm(ctx, pdev, ctm); |
815 | 0 | pdf_dev_stroked_path(ctx, pdev, path); |
816 | 0 | gs = CURRENT_GSTATE(pdev); |
817 | 0 | fz_append_string(ctx, gs->buf, "W n\n"); |
818 | 0 | } |
819 | | |
820 | | static void |
821 | | pdf_dev_fill_text(fz_context *ctx, fz_device *dev, const fz_text *text, fz_matrix ctm, |
822 | | fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params) |
823 | 0 | { |
824 | 0 | pdf_device *pdev = (pdf_device*)dev; |
825 | 0 | fz_text_span *span; |
826 | |
|
827 | 0 | pdf_dev_ctm(ctx, pdev, ctm); |
828 | 0 | pdf_dev_alpha(ctx, pdev, alpha, 0); |
829 | 0 | pdf_dev_color(ctx, pdev, colorspace, color, 0, color_params); |
830 | |
|
831 | 0 | for (span = text->head; span; span = span->next) |
832 | 0 | { |
833 | 0 | pdf_dev_begin_text(ctx, pdev, 0); |
834 | 0 | pdf_dev_font(ctx, pdev, span->font, span->trm); |
835 | 0 | pdf_dev_text_span(ctx, pdev, span); |
836 | 0 | } |
837 | 0 | } |
838 | | |
839 | | static void |
840 | | pdf_dev_stroke_text(fz_context *ctx, fz_device *dev, const fz_text *text, const fz_stroke_state *stroke, fz_matrix ctm, |
841 | | fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params) |
842 | 0 | { |
843 | 0 | pdf_device *pdev = (pdf_device*)dev; |
844 | 0 | fz_text_span *span; |
845 | |
|
846 | 0 | pdf_dev_ctm(ctx, pdev, ctm); |
847 | 0 | pdf_dev_alpha(ctx, pdev, alpha, 1); |
848 | 0 | pdf_dev_color(ctx, pdev, colorspace, color, 1, color_params); |
849 | 0 | pdf_dev_stroke_state(ctx, pdev, stroke); |
850 | |
|
851 | 0 | for (span = text->head; span; span = span->next) |
852 | 0 | { |
853 | 0 | pdf_dev_begin_text(ctx, pdev, 1); |
854 | 0 | pdf_dev_font(ctx, pdev, span->font, span->trm); |
855 | 0 | pdf_dev_text_span(ctx, pdev, span); |
856 | 0 | } |
857 | 0 | } |
858 | | |
859 | | static void |
860 | | pdf_dev_clip_text(fz_context *ctx, fz_device *dev, const fz_text *text, fz_matrix ctm, fz_rect scissor) |
861 | 0 | { |
862 | 0 | pdf_device *pdev = (pdf_device*)dev; |
863 | 0 | fz_text_span *span; |
864 | |
|
865 | 0 | pdf_dev_end_text(ctx, pdev); |
866 | 0 | pdf_dev_push(ctx, pdev); |
867 | |
|
868 | 0 | pdf_dev_ctm(ctx, pdev, ctm); |
869 | |
|
870 | 0 | for (span = text->head; span; span = span->next) |
871 | 0 | { |
872 | 0 | pdf_dev_begin_text(ctx, pdev, 7); |
873 | 0 | pdf_dev_font(ctx, pdev, span->font, span->trm); |
874 | 0 | pdf_dev_text_span(ctx, pdev, span); |
875 | 0 | } |
876 | 0 | } |
877 | | |
878 | | static void |
879 | | pdf_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) |
880 | 0 | { |
881 | 0 | pdf_device *pdev = (pdf_device*)dev; |
882 | 0 | fz_text_span *span; |
883 | |
|
884 | 0 | pdf_dev_end_text(ctx, pdev); |
885 | 0 | pdf_dev_push(ctx, pdev); |
886 | |
|
887 | 0 | pdf_dev_ctm(ctx, pdev, ctm); |
888 | |
|
889 | 0 | for (span = text->head; span; span = span->next) |
890 | 0 | { |
891 | 0 | pdf_dev_begin_text(ctx, pdev, 7); |
892 | 0 | pdf_dev_font(ctx, pdev, span->font, span->trm); |
893 | 0 | pdf_dev_text_span(ctx, pdev, span); |
894 | 0 | } |
895 | 0 | } |
896 | | |
897 | | static void |
898 | | pdf_dev_ignore_text(fz_context *ctx, fz_device *dev, const fz_text *text, fz_matrix ctm) |
899 | 0 | { |
900 | 0 | pdf_device *pdev = (pdf_device*)dev; |
901 | 0 | fz_text_span *span; |
902 | |
|
903 | 0 | pdf_dev_ctm(ctx, pdev, ctm); |
904 | |
|
905 | 0 | for (span = text->head; span; span = span->next) |
906 | 0 | { |
907 | 0 | pdf_dev_begin_text(ctx, pdev, 0); |
908 | 0 | pdf_dev_font(ctx, pdev, span->font, span->trm); |
909 | 0 | pdf_dev_text_span(ctx, pdev, span); |
910 | 0 | } |
911 | 0 | } |
912 | | |
913 | | static void |
914 | | pdf_dev_add_image_res(fz_context *ctx, fz_device *dev, pdf_obj *im_res) |
915 | 0 | { |
916 | 0 | char text[32]; |
917 | 0 | pdf_device *pdev = (pdf_device*)dev; |
918 | 0 | int k; |
919 | 0 | int num; |
920 | | |
921 | | /* Check if we already had this one */ |
922 | 0 | for (k = 0; k < pdev->num_imgs; k++) |
923 | 0 | { |
924 | 0 | if (pdev->image_indices[k] == pdf_to_num(ctx, im_res)) |
925 | 0 | return; |
926 | 0 | } |
927 | | |
928 | | /* Not there so add to resources */ |
929 | 0 | fz_snprintf(text, sizeof(text), "XObject/Img%d", pdf_to_num(ctx, im_res)); |
930 | 0 | pdf_dict_putp(ctx, pdev->resources, text, im_res); |
931 | | |
932 | | /* And add index to our list for this page */ |
933 | 0 | if (pdev->num_imgs == pdev->max_imgs) |
934 | 0 | { |
935 | 0 | int newmax = pdev->max_imgs * 2; |
936 | 0 | if (newmax == 0) |
937 | 0 | newmax = 4; |
938 | 0 | pdev->image_indices = fz_realloc_array(ctx, pdev->image_indices, newmax, int); |
939 | 0 | pdev->max_imgs = newmax; |
940 | 0 | } |
941 | 0 | num = pdev->num_imgs++; |
942 | 0 | pdev->image_indices[num] = pdf_to_num(ctx, im_res); |
943 | 0 | } |
944 | | |
945 | | static void |
946 | | pdf_dev_fill_image(fz_context *ctx, fz_device *dev, fz_image *image, fz_matrix ctm, float alpha, fz_color_params color_params) |
947 | 0 | { |
948 | 0 | pdf_device *pdev = (pdf_device*)dev; |
949 | 0 | pdf_obj *im_res; |
950 | 0 | gstate *gs = CURRENT_GSTATE(pdev); |
951 | |
|
952 | 0 | pdf_dev_end_text(ctx, pdev); |
953 | 0 | im_res = pdf_add_image(ctx, pdev->doc, image); |
954 | 0 | if (im_res == NULL) |
955 | 0 | { |
956 | 0 | fz_warn(ctx, "pdf_add_image: problem adding image resource"); |
957 | 0 | return; |
958 | 0 | } |
959 | | |
960 | 0 | fz_try(ctx) |
961 | 0 | { |
962 | 0 | pdf_dev_alpha(ctx, pdev, alpha, 0); |
963 | | |
964 | | /* PDF images are upside down, so fiddle the ctm */ |
965 | 0 | ctm = fz_pre_scale(ctm, 1, -1); |
966 | 0 | ctm = fz_pre_translate(ctm, 0, -1); |
967 | 0 | pdf_dev_ctm(ctx, pdev, ctm); |
968 | 0 | fz_append_printf(ctx, gs->buf, "/Img%d Do\n", pdf_to_num(ctx, im_res)); |
969 | | |
970 | | /* Possibly add to page resources */ |
971 | 0 | pdf_dev_add_image_res(ctx, dev, im_res); |
972 | 0 | } |
973 | 0 | fz_always(ctx) |
974 | 0 | pdf_drop_obj(ctx, im_res); |
975 | 0 | fz_catch(ctx) |
976 | 0 | fz_rethrow(ctx); |
977 | 0 | } |
978 | | |
979 | | static void |
980 | | pdf_dev_fill_shade(fz_context *ctx, fz_device *dev, fz_shade *shade, fz_matrix ctm, float alpha, fz_color_params color_params) |
981 | 0 | { |
982 | 0 | pdf_device *pdev = (pdf_device*)dev; |
983 | 0 | pdf_document *doc = pdev->doc; |
984 | 0 | gstate *gs = CURRENT_GSTATE(pdev); |
985 | 0 | pdf_obj *func_ref = NULL; |
986 | 0 | pdf_obj *shade_ref = NULL; |
987 | 0 | pdf_obj *func_dict = NULL; |
988 | 0 | pdf_obj *shade_dict = NULL; |
989 | 0 | fz_buffer *sample_buf = NULL; |
990 | 0 | fz_colorspace *src_cs; |
991 | 0 | fz_colorspace *dst_cs; |
992 | 0 | pdf_obj *shadings = NULL; |
993 | 0 | int src_n, dst_n; |
994 | 0 | int i, k; |
995 | 0 | int shading_type; |
996 | 0 | char name_buf[32]; |
997 | 0 | float converted[FZ_MAX_COLORS]; |
998 | |
|
999 | 0 | fz_var(func_ref); |
1000 | 0 | fz_var(shade_ref); |
1001 | 0 | fz_var(func_dict); |
1002 | 0 | fz_var(shade_dict); |
1003 | 0 | fz_var(sample_buf); |
1004 | |
|
1005 | 0 | if (shade == NULL || shade->function_stride <= 0 || shade->function == NULL) |
1006 | 0 | { |
1007 | 0 | fz_warn(ctx, "invalid shading passed to pdf device"); |
1008 | 0 | return; |
1009 | 0 | } |
1010 | | |
1011 | 0 | if (shade->type == FZ_LINEAR) |
1012 | 0 | shading_type = 2; |
1013 | 0 | else if (shade->type == FZ_RADIAL) |
1014 | 0 | shading_type = 3; |
1015 | 0 | else |
1016 | 0 | { |
1017 | 0 | fz_warn(ctx, "pdf device only supports linear/radial shadings for fill_shade"); |
1018 | 0 | return; |
1019 | 0 | } |
1020 | | |
1021 | 0 | src_cs = shade->colorspace ? shade->colorspace : fz_device_gray(ctx); |
1022 | 0 | src_n = fz_colorspace_n(ctx, src_cs); |
1023 | 0 | if (shade->function_stride < src_n) |
1024 | 0 | { |
1025 | 0 | fz_warn(ctx, "pdf device shading function stride too small"); |
1026 | 0 | return; |
1027 | 0 | } |
1028 | | |
1029 | | /* Restrict emitted PDF shading colorspace to device spaces, |
1030 | | * and convert all other colorspaces to DeviceRGB. */ |
1031 | 0 | if (src_cs == fz_device_gray(ctx)) |
1032 | 0 | { |
1033 | 0 | dst_cs = fz_device_gray(ctx); |
1034 | 0 | dst_n = 1; |
1035 | 0 | } |
1036 | 0 | else if (src_cs == fz_device_cmyk(ctx)) |
1037 | 0 | { |
1038 | 0 | dst_cs = fz_device_cmyk(ctx); |
1039 | 0 | dst_n = 4; |
1040 | 0 | } |
1041 | 0 | else |
1042 | 0 | { |
1043 | 0 | dst_cs = fz_device_rgb(ctx); |
1044 | 0 | dst_n = 3; |
1045 | 0 | } |
1046 | |
|
1047 | 0 | pdf_dev_end_text(ctx, pdev); |
1048 | 0 | pdf_dev_alpha(ctx, pdev, alpha, 0); |
1049 | 0 | pdf_dev_ctm(ctx, pdev, fz_concat(shade->matrix, ctm)); |
1050 | |
|
1051 | 0 | fz_try(ctx) |
1052 | 0 | { |
1053 | | /* Build sampled function from MuPDF's canonical 256-entry function table. */ |
1054 | 0 | sample_buf = fz_new_buffer(ctx, 256 * dst_n); |
1055 | 0 | for (i = 0; i < 256; ++i) |
1056 | 0 | { |
1057 | 0 | const float *src = &shade->function[i * shade->function_stride]; |
1058 | 0 | const float *out = src; |
1059 | |
|
1060 | 0 | if (src_n != dst_n || src_cs != dst_cs) |
1061 | 0 | { |
1062 | 0 | fz_convert_color(ctx, src_cs, src, dst_cs, converted, NULL, fz_default_color_params); |
1063 | 0 | out = converted; |
1064 | 0 | } |
1065 | |
|
1066 | 0 | for (k = 0; k < dst_n; ++k) |
1067 | 0 | { |
1068 | 0 | float v = out[k]; |
1069 | 0 | int b = fz_clampi((int)(v * 255.0f + 0.5f), 0, 255); |
1070 | 0 | fz_append_byte(ctx, sample_buf, (unsigned char)b); |
1071 | 0 | } |
1072 | 0 | } |
1073 | |
|
1074 | 0 | func_dict = pdf_new_dict(ctx, doc, 6); |
1075 | 0 | pdf_dict_put_int(ctx, func_dict, PDF_NAME(FunctionType), 0); |
1076 | 0 | pdf_dict_put_int(ctx, func_dict, PDF_NAME(BitsPerSample), 8); |
1077 | 0 | { |
1078 | 0 | pdf_obj *domain = pdf_dict_put_array(ctx, func_dict, PDF_NAME(Domain), 2); |
1079 | 0 | pdf_array_push_int(ctx, domain, 0); |
1080 | 0 | pdf_array_push_int(ctx, domain, 1); |
1081 | 0 | } |
1082 | 0 | { |
1083 | 0 | pdf_obj *range = pdf_dict_put_array(ctx, func_dict, PDF_NAME(Range), dst_n * 2); |
1084 | 0 | for (k = 0; k < dst_n; ++k) |
1085 | 0 | { |
1086 | 0 | pdf_array_push_int(ctx, range, 0); |
1087 | 0 | pdf_array_push_int(ctx, range, 1); |
1088 | 0 | } |
1089 | 0 | } |
1090 | 0 | { |
1091 | 0 | pdf_obj *size = pdf_dict_put_array(ctx, func_dict, PDF_NAME(Size), 1); |
1092 | 0 | pdf_array_push_int(ctx, size, 256); |
1093 | 0 | } |
1094 | |
|
1095 | 0 | func_ref = pdf_add_stream(ctx, doc, sample_buf, func_dict, 0); |
1096 | |
|
1097 | 0 | shade_dict = pdf_new_dict(ctx, doc, 6); |
1098 | 0 | pdf_dict_put_int(ctx, shade_dict, PDF_NAME(ShadingType), shading_type); |
1099 | 0 | if (dst_cs == fz_device_gray(ctx)) |
1100 | 0 | pdf_dict_put(ctx, shade_dict, PDF_NAME(ColorSpace), PDF_NAME(DeviceGray)); |
1101 | 0 | else if (dst_cs == fz_device_cmyk(ctx)) |
1102 | 0 | pdf_dict_put(ctx, shade_dict, PDF_NAME(ColorSpace), PDF_NAME(DeviceCMYK)); |
1103 | 0 | else |
1104 | 0 | pdf_dict_put(ctx, shade_dict, PDF_NAME(ColorSpace), PDF_NAME(DeviceRGB)); |
1105 | 0 | pdf_dict_put(ctx, shade_dict, PDF_NAME(Function), func_ref); |
1106 | 0 | { |
1107 | 0 | pdf_obj *coords = pdf_dict_put_array(ctx, shade_dict, PDF_NAME(Coords), (shading_type == 2) ? 4 : 6); |
1108 | 0 | pdf_array_push_real(ctx, coords, shade->u.l_or_r.coords[0][0]); |
1109 | 0 | pdf_array_push_real(ctx, coords, shade->u.l_or_r.coords[0][1]); |
1110 | 0 | if (shading_type == 3) |
1111 | 0 | pdf_array_push_real(ctx, coords, shade->u.l_or_r.coords[0][2]); |
1112 | 0 | pdf_array_push_real(ctx, coords, shade->u.l_or_r.coords[1][0]); |
1113 | 0 | pdf_array_push_real(ctx, coords, shade->u.l_or_r.coords[1][1]); |
1114 | 0 | if (shading_type == 3) |
1115 | 0 | pdf_array_push_real(ctx, coords, shade->u.l_or_r.coords[1][2]); |
1116 | 0 | } |
1117 | 0 | { |
1118 | 0 | pdf_obj *extend = pdf_dict_put_array(ctx, shade_dict, PDF_NAME(Extend), 2); |
1119 | 0 | pdf_array_push_bool(ctx, extend, shade->u.l_or_r.extend[0]); |
1120 | 0 | pdf_array_push_bool(ctx, extend, shade->u.l_or_r.extend[1]); |
1121 | 0 | } |
1122 | |
|
1123 | 0 | shade_ref = pdf_add_object(ctx, doc, shade_dict); |
1124 | |
|
1125 | 0 | fz_snprintf(name_buf, sizeof(name_buf), "Sh%d", pdev->num_shadings++); |
1126 | 0 | shadings = pdf_dict_get(ctx, pdev->resources, PDF_NAME(Shading)); |
1127 | 0 | if (!pdf_is_dict(ctx, shadings)) |
1128 | 0 | { |
1129 | 0 | shadings = pdf_new_dict(ctx, doc, 4); |
1130 | 0 | pdf_dict_put_drop(ctx, pdev->resources, PDF_NAME(Shading), shadings); |
1131 | 0 | shadings = pdf_dict_get(ctx, pdev->resources, PDF_NAME(Shading)); |
1132 | 0 | } |
1133 | 0 | pdf_dict_puts(ctx, shadings, name_buf, shade_ref); |
1134 | |
|
1135 | 0 | fz_append_printf(ctx, gs->buf, "/%s sh\n", name_buf); |
1136 | 0 | } |
1137 | 0 | fz_always(ctx) |
1138 | 0 | { |
1139 | 0 | fz_drop_buffer(ctx, sample_buf); |
1140 | 0 | pdf_drop_obj(ctx, func_dict); |
1141 | 0 | pdf_drop_obj(ctx, shade_dict); |
1142 | 0 | pdf_drop_obj(ctx, func_ref); |
1143 | 0 | pdf_drop_obj(ctx, shade_ref); |
1144 | 0 | } |
1145 | 0 | fz_catch(ctx) |
1146 | 0 | { |
1147 | 0 | fz_rethrow(ctx); |
1148 | 0 | } |
1149 | 0 | } |
1150 | | |
1151 | | static void |
1152 | | pdf_dev_fill_image_mask(fz_context *ctx, fz_device *dev, fz_image *image, fz_matrix ctm, |
1153 | | fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params) |
1154 | 0 | { |
1155 | 0 | pdf_device *pdev = (pdf_device*)dev; |
1156 | 0 | pdf_obj *im_res = NULL; |
1157 | 0 | gstate *gs = CURRENT_GSTATE(pdev); |
1158 | |
|
1159 | 0 | pdf_dev_end_text(ctx, pdev); |
1160 | 0 | im_res = pdf_add_image(ctx, pdev->doc, image); |
1161 | 0 | if (im_res == NULL) |
1162 | 0 | { |
1163 | 0 | fz_warn(ctx, "pdf_add_image: problem adding image resource"); |
1164 | 0 | return; |
1165 | 0 | } |
1166 | | |
1167 | 0 | fz_try(ctx) |
1168 | 0 | { |
1169 | 0 | fz_append_string(ctx, gs->buf, "q\n"); |
1170 | 0 | pdf_dev_alpha(ctx, pdev, alpha, 0); |
1171 | 0 | pdf_dev_color(ctx, pdev, colorspace, color, 0, color_params); |
1172 | | |
1173 | | /* PDF images are upside down, so fiddle the ctm */ |
1174 | 0 | ctm = fz_pre_scale(ctm, 1, -1); |
1175 | 0 | ctm = fz_pre_translate(ctm, 0, -1); |
1176 | 0 | pdf_dev_ctm(ctx, pdev, ctm); |
1177 | 0 | fz_append_printf(ctx, gs->buf, "/Img%d Do Q\n", pdf_to_num(ctx, im_res)); |
1178 | | |
1179 | | /* Possibly add to page resources */ |
1180 | 0 | pdf_dev_add_image_res(ctx, dev, im_res); |
1181 | 0 | } |
1182 | 0 | fz_always(ctx) |
1183 | 0 | pdf_drop_obj(ctx, im_res); |
1184 | 0 | fz_catch(ctx) |
1185 | 0 | fz_rethrow(ctx); |
1186 | 0 | } |
1187 | | |
1188 | | static void |
1189 | | pdf_dev_clip_image_mask(fz_context *ctx, fz_device *dev, fz_image *image, fz_matrix ctm, fz_rect scissor) |
1190 | 0 | { |
1191 | 0 | pdf_device *pdev = (pdf_device*)dev; |
1192 | |
|
1193 | 0 | fz_warn(ctx, "the pdf device does not support image masks; output may be incomplete"); |
1194 | | |
1195 | | /* FIXME */ |
1196 | 0 | pdf_dev_end_text(ctx, pdev); |
1197 | 0 | pdf_dev_push(ctx, pdev); |
1198 | 0 | } |
1199 | | |
1200 | | static void |
1201 | | pdf_dev_pop_clip(fz_context *ctx, fz_device *dev) |
1202 | 0 | { |
1203 | 0 | pdf_device *pdev = (pdf_device*)dev; |
1204 | | |
1205 | | /* FIXME */ |
1206 | 0 | pdf_dev_end_text(ctx, pdev); |
1207 | 0 | pdf_dev_pop(ctx, pdev); |
1208 | 0 | } |
1209 | | |
1210 | | static void |
1211 | | pdf_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) |
1212 | 0 | { |
1213 | 0 | pdf_device *pdev = (pdf_device*)dev; |
1214 | 0 | gstate *gs; |
1215 | 0 | pdf_obj *smask = NULL; |
1216 | 0 | char egsname[32]; |
1217 | 0 | pdf_obj *egs = NULL; |
1218 | 0 | pdf_obj *egss; |
1219 | 0 | pdf_obj *form_ref; |
1220 | 0 | pdf_obj *color_obj = NULL; |
1221 | 0 | int i, n; |
1222 | |
|
1223 | 0 | fz_var(smask); |
1224 | 0 | fz_var(egs); |
1225 | 0 | fz_var(color_obj); |
1226 | |
|
1227 | 0 | pdf_dev_end_text(ctx, pdev); |
1228 | |
|
1229 | 0 | pdf_dev_ctm(ctx, pdev, fz_identity); |
1230 | | |
1231 | | /* Make a new form to contain the contents of the softmask */ |
1232 | 0 | pdf_dev_new_form(ctx, &form_ref, pdev, bbox, 0, 0, 1, colorspace); |
1233 | |
|
1234 | 0 | fz_try(ctx) |
1235 | 0 | { |
1236 | 0 | fz_snprintf(egsname, sizeof(egsname), "SM%d", pdev->num_smasks++); |
1237 | 0 | egss = pdf_dict_get(ctx, pdev->resources, PDF_NAME(ExtGState)); |
1238 | 0 | if (!egss) |
1239 | 0 | egss = pdf_dict_put_dict(ctx, pdev->resources, PDF_NAME(ExtGState), 10); |
1240 | 0 | egs = pdf_dict_puts_dict(ctx, egss, egsname, 1); |
1241 | |
|
1242 | 0 | pdf_dict_put(ctx, egs, PDF_NAME(Type), PDF_NAME(ExtGState)); |
1243 | 0 | smask = pdf_dict_put_dict(ctx, egs, PDF_NAME(SMask), 4); |
1244 | |
|
1245 | 0 | pdf_dict_put(ctx, smask, PDF_NAME(Type), PDF_NAME(Mask)); |
1246 | 0 | pdf_dict_put(ctx, smask, PDF_NAME(S), (luminosity ? PDF_NAME(Luminosity) : PDF_NAME(Alpha))); |
1247 | 0 | pdf_dict_put(ctx, smask, PDF_NAME(G), form_ref); |
1248 | |
|
1249 | 0 | n = fz_colorspace_n(ctx, colorspace); |
1250 | 0 | color_obj = pdf_dict_put_array(ctx, smask, PDF_NAME(BC), n); |
1251 | 0 | for (i = 0; i < n; i++) |
1252 | 0 | pdf_array_push_real(ctx, color_obj, color[i]); |
1253 | |
|
1254 | 0 | gs = CURRENT_GSTATE(pdev); |
1255 | 0 | fz_append_printf(ctx, gs->buf, "/SM%d gs\n", pdev->num_smasks-1); |
1256 | 0 | } |
1257 | 0 | fz_catch(ctx) |
1258 | 0 | { |
1259 | 0 | pdf_drop_obj(ctx, form_ref); |
1260 | 0 | fz_rethrow(ctx); |
1261 | 0 | } |
1262 | | |
1263 | | /* Now, everything we get until the end_mask needs to go into a |
1264 | | * new buffer, which will be the stream contents for the form. */ |
1265 | 0 | pdf_dev_push_new_buf(ctx, pdev, fz_new_buffer(ctx, 1024), NULL, form_ref); |
1266 | 0 | } |
1267 | | |
1268 | | static void |
1269 | | pdf_dev_end_mask(fz_context *ctx, fz_device *dev, fz_function *tr) |
1270 | 0 | { |
1271 | 0 | pdf_device *pdev = (pdf_device*)dev; |
1272 | 0 | pdf_document *doc = pdev->doc; |
1273 | 0 | gstate *gs = CURRENT_GSTATE(pdev); |
1274 | 0 | pdf_obj *form_ref = (pdf_obj *)gs->on_pop_arg; |
1275 | |
|
1276 | 0 | if (tr) |
1277 | 0 | fz_warn(ctx, "Ignoring Transfer function"); |
1278 | | |
1279 | | /* Here we do part of the pop, but not all of it. */ |
1280 | 0 | pdf_dev_end_text(ctx, pdev); |
1281 | 0 | fz_append_string(ctx, gs->buf, "Q\n"); |
1282 | 0 | pdf_update_stream(ctx, doc, form_ref, gs->buf, 0); |
1283 | 0 | fz_drop_buffer(ctx, gs->buf); |
1284 | 0 | gs->buf = fz_keep_buffer(ctx, gs[-1].buf); |
1285 | 0 | gs->on_pop_arg = NULL; |
1286 | 0 | pdf_drop_obj(ctx, form_ref); |
1287 | 0 | fz_append_string(ctx, gs->buf, "q\n"); |
1288 | 0 | } |
1289 | | |
1290 | | static void |
1291 | | pdf_dev_begin_group(fz_context *ctx, fz_device *dev, fz_rect bbox, fz_colorspace *cs, int isolated, int knockout, int blendmode, float alpha) |
1292 | 0 | { |
1293 | 0 | pdf_device *pdev = (pdf_device*)dev; |
1294 | 0 | int num; |
1295 | 0 | pdf_obj *form_ref; |
1296 | 0 | gstate *gs; |
1297 | |
|
1298 | 0 | pdf_dev_end_text(ctx, pdev); |
1299 | |
|
1300 | 0 | pdf_dev_ctm(ctx, pdev, fz_identity); |
1301 | |
|
1302 | 0 | num = pdf_dev_new_form(ctx, &form_ref, pdev, bbox, isolated, knockout, alpha, cs); |
1303 | | |
1304 | | /* Do we have an appropriate blending extgstate already? */ |
1305 | 0 | { |
1306 | 0 | char text[32]; |
1307 | 0 | pdf_obj *obj; |
1308 | 0 | pdf_obj *egs = pdf_dict_get(ctx, pdev->resources, PDF_NAME(ExtGState)); |
1309 | 0 | if (egs == NULL) |
1310 | 0 | egs = pdf_dict_put_dict(ctx, pdev->resources, PDF_NAME(ExtGState), 4); |
1311 | 0 | fz_snprintf(text, sizeof(text), "BlendMode%d", blendmode); |
1312 | 0 | obj = pdf_dict_gets(ctx, egs, text); |
1313 | 0 | if (obj == NULL) |
1314 | 0 | { |
1315 | | /* No, better make one */ |
1316 | 0 | obj = pdf_dict_puts_dict(ctx, egs, text, 2); |
1317 | 0 | pdf_dict_put(ctx, obj, PDF_NAME(Type), PDF_NAME(ExtGState)); |
1318 | 0 | pdf_dict_put_name(ctx, obj, PDF_NAME(BM), fz_blendmode_name(blendmode)); |
1319 | 0 | } |
1320 | 0 | } |
1321 | | |
1322 | | /* Add the call to this group */ |
1323 | 0 | gs = CURRENT_GSTATE(pdev); |
1324 | 0 | fz_append_printf(ctx, gs->buf, "/BlendMode%d gs /Fm%d Do\n", blendmode, num); |
1325 | | |
1326 | | /* Now, everything we get until the end of group needs to go into a |
1327 | | * new buffer, which will be the stream contents for the form. */ |
1328 | 0 | pdf_dev_push_new_buf(ctx, pdev, fz_new_buffer(ctx, 1024), NULL, form_ref); |
1329 | 0 | } |
1330 | | |
1331 | | static void |
1332 | | pdf_dev_end_group(fz_context *ctx, fz_device *dev) |
1333 | 0 | { |
1334 | 0 | pdf_device *pdev = (pdf_device*)dev; |
1335 | 0 | pdf_document *doc = pdev->doc; |
1336 | 0 | gstate *gs = CURRENT_GSTATE(pdev); |
1337 | 0 | fz_buffer *buf = fz_keep_buffer(ctx, gs->buf); |
1338 | 0 | pdf_obj *form_ref; |
1339 | |
|
1340 | 0 | pdf_dev_end_text(ctx, pdev); |
1341 | 0 | form_ref = (pdf_obj *)pdf_dev_pop(ctx, pdev); |
1342 | 0 | pdf_update_stream(ctx, doc, form_ref, buf, 0); |
1343 | 0 | fz_drop_buffer(ctx, buf); |
1344 | 0 | pdf_drop_obj(ctx, form_ref); |
1345 | 0 | } |
1346 | | |
1347 | | static int |
1348 | | pdf_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) |
1349 | 0 | { |
1350 | 0 | pdf_device *pdev = (pdf_device*)dev; |
1351 | |
|
1352 | 0 | fz_warn(ctx, "tiled patterns are not supported in the pdf-write device"); |
1353 | | |
1354 | | /* FIXME */ |
1355 | 0 | pdf_dev_end_text(ctx, pdev); |
1356 | 0 | return 0; |
1357 | 0 | } |
1358 | | |
1359 | | static void |
1360 | | pdf_dev_end_tile(fz_context *ctx, fz_device *dev) |
1361 | 0 | { |
1362 | 0 | pdf_device *pdev = (pdf_device*)dev; |
1363 | | |
1364 | | /* FIXME */ |
1365 | 0 | pdf_dev_end_text(ctx, pdev); |
1366 | 0 | } |
1367 | | |
1368 | | static void |
1369 | | pdf_dev_close_device(fz_context *ctx, fz_device *dev) |
1370 | 0 | { |
1371 | 0 | pdf_device *pdev = (pdf_device*)dev; |
1372 | 0 | pdf_dev_end_text(ctx, pdev); |
1373 | 0 | } |
1374 | | |
1375 | | static void |
1376 | | pdf_dev_drop_device(fz_context *ctx, fz_device *dev) |
1377 | 0 | { |
1378 | 0 | pdf_device *pdev = (pdf_device*)dev; |
1379 | 0 | int i; |
1380 | |
|
1381 | 0 | for (i = pdev->num_gstates-1; i >= 0; i--) |
1382 | 0 | { |
1383 | 0 | fz_drop_buffer(ctx, pdev->gstates[i].buf); |
1384 | 0 | fz_drop_stroke_state(ctx, pdev->gstates[i].stroke_state); |
1385 | 0 | } |
1386 | |
|
1387 | 0 | for (i = pdev->num_cid_fonts-1; i >= 0; i--) |
1388 | 0 | fz_drop_font(ctx, pdev->cid_fonts[i]); |
1389 | |
|
1390 | 0 | for (i = pdev->num_groups - 1; i >= 0; i--) |
1391 | 0 | { |
1392 | 0 | pdf_drop_obj(ctx, pdev->groups[i].ref); |
1393 | 0 | fz_drop_colorspace(ctx, pdev->groups[i].colorspace); |
1394 | 0 | } |
1395 | |
|
1396 | 0 | pdf_drop_obj(ctx, pdev->resources); |
1397 | 0 | fz_free(ctx, pdev->cid_fonts); |
1398 | 0 | fz_free(ctx, pdev->cid_fonts_enc); |
1399 | 0 | fz_free(ctx, pdev->image_indices); |
1400 | 0 | fz_free(ctx, pdev->groups); |
1401 | 0 | fz_free(ctx, pdev->alphas); |
1402 | 0 | fz_free(ctx, pdev->gstates); |
1403 | 0 | pdf_drop_document(ctx, pdev->doc); |
1404 | 0 | } |
1405 | | |
1406 | | fz_device *pdf_new_pdf_device(fz_context *ctx, pdf_document *doc, fz_matrix topctm, pdf_obj *resources, fz_buffer *buf) |
1407 | 0 | { |
1408 | 0 | pdf_device *dev = fz_new_derived_device(ctx, pdf_device); |
1409 | |
|
1410 | 0 | dev->super.hints = FZ_NO_TILING; |
1411 | |
|
1412 | 0 | dev->super.close_device = pdf_dev_close_device; |
1413 | 0 | dev->super.drop_device = pdf_dev_drop_device; |
1414 | |
|
1415 | 0 | dev->super.fill_path = pdf_dev_fill_path; |
1416 | 0 | dev->super.stroke_path = pdf_dev_stroke_path; |
1417 | 0 | dev->super.clip_path = pdf_dev_clip_path; |
1418 | 0 | dev->super.clip_stroke_path = pdf_dev_clip_stroke_path; |
1419 | |
|
1420 | 0 | dev->super.fill_text = pdf_dev_fill_text; |
1421 | 0 | dev->super.stroke_text = pdf_dev_stroke_text; |
1422 | 0 | dev->super.clip_text = pdf_dev_clip_text; |
1423 | 0 | dev->super.clip_stroke_text = pdf_dev_clip_stroke_text; |
1424 | 0 | dev->super.ignore_text = pdf_dev_ignore_text; |
1425 | |
|
1426 | 0 | dev->super.fill_shade = pdf_dev_fill_shade; |
1427 | 0 | dev->super.fill_image = pdf_dev_fill_image; |
1428 | 0 | dev->super.fill_image_mask = pdf_dev_fill_image_mask; |
1429 | 0 | dev->super.clip_image_mask = pdf_dev_clip_image_mask; |
1430 | |
|
1431 | 0 | dev->super.pop_clip = pdf_dev_pop_clip; |
1432 | |
|
1433 | 0 | dev->super.begin_mask = pdf_dev_begin_mask; |
1434 | 0 | dev->super.end_mask = pdf_dev_end_mask; |
1435 | 0 | dev->super.begin_group = pdf_dev_begin_group; |
1436 | 0 | dev->super.end_group = pdf_dev_end_group; |
1437 | |
|
1438 | 0 | dev->super.begin_tile = pdf_dev_begin_tile; |
1439 | 0 | dev->super.end_tile = pdf_dev_end_tile; |
1440 | |
|
1441 | 0 | fz_var(buf); |
1442 | |
|
1443 | 0 | fz_try(ctx) |
1444 | 0 | { |
1445 | 0 | dev->doc = pdf_keep_document(ctx, doc); |
1446 | 0 | dev->resources = pdf_keep_obj(ctx, resources); |
1447 | 0 | dev->gstates = fz_malloc_struct(ctx, gstate); |
1448 | 0 | if (buf) |
1449 | 0 | dev->gstates[0].buf = fz_keep_buffer(ctx, buf); |
1450 | 0 | else |
1451 | 0 | dev->gstates[0].buf = fz_new_buffer(ctx, 256); |
1452 | 0 | dev->gstates[0].ctm = fz_identity; // XXX |
1453 | 0 | dev->gstates[0].colorspace[0] = fz_device_gray(ctx); |
1454 | 0 | dev->gstates[0].colorspace[1] = fz_device_gray(ctx); |
1455 | 0 | dev->gstates[0].color[0][0] = 0; |
1456 | 0 | dev->gstates[0].color[1][0] = 0; |
1457 | 0 | dev->gstates[0].alpha[0] = 1.0f; |
1458 | 0 | dev->gstates[0].alpha[1] = 1.0f; |
1459 | 0 | dev->gstates[0].font = -1; |
1460 | 0 | dev->num_gstates = 1; |
1461 | 0 | dev->max_gstates = 1; |
1462 | 0 | dev->num_shadings = 0; |
1463 | |
|
1464 | 0 | if (!fz_is_identity(topctm)) |
1465 | 0 | fz_append_printf(ctx, dev->gstates[0].buf, "%M cm\n", &topctm); |
1466 | 0 | } |
1467 | 0 | fz_catch(ctx) |
1468 | 0 | { |
1469 | 0 | fz_drop_device(ctx, &dev->super); |
1470 | 0 | fz_rethrow(ctx); |
1471 | 0 | } |
1472 | | |
1473 | 0 | return (fz_device*)dev; |
1474 | 0 | } |
1475 | | |
1476 | | fz_device *pdf_page_write(fz_context *ctx, pdf_document *doc, fz_rect mediabox, pdf_obj **presources, fz_buffer **pcontents) |
1477 | 0 | { |
1478 | 0 | fz_matrix pagectm = { 1, 0, 0, -1, -mediabox.x0, mediabox.y1 }; |
1479 | 0 | if (!*presources) |
1480 | 0 | *presources = pdf_new_dict(ctx, doc, 0); |
1481 | 0 | if (!*pcontents) |
1482 | 0 | *pcontents = fz_new_buffer(ctx, 0); |
1483 | 0 | return pdf_new_pdf_device(ctx, doc, pagectm, *presources, *pcontents); |
1484 | 0 | } |