/src/mupdf/source/fitz/writer.c
Line | Count | Source |
1 | | // Copyright (C) 2004-2025 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 | | |
27 | | /* Return non-null terminated pointers to key/value entries in comma separated |
28 | | * option string. A plain key has the default value 'yes'. Use strncmp to compare |
29 | | * key/value strings. */ |
30 | | static const char * |
31 | | fz_get_option(fz_context *ctx, const char **key, const char **val, const char *opts) |
32 | 0 | { |
33 | 0 | if (!opts || *opts == 0) |
34 | 0 | return NULL; |
35 | | |
36 | 0 | if (*opts == ',') |
37 | 0 | ++opts; |
38 | |
|
39 | 0 | *key = opts; |
40 | 0 | while (*opts != 0 && *opts != ',' && *opts != '=') |
41 | 0 | ++opts; |
42 | |
|
43 | 0 | if (*opts == '=') |
44 | 0 | { |
45 | 0 | *val = ++opts; |
46 | 0 | while (*opts != 0 && *opts != ',') |
47 | 0 | ++opts; |
48 | 0 | } |
49 | 0 | else |
50 | 0 | { |
51 | 0 | *val = "yes"; |
52 | 0 | } |
53 | |
|
54 | 0 | return opts; |
55 | 0 | } |
56 | | |
57 | | int |
58 | | fz_has_option(fz_context *ctx, const char *opts, const char *key, const char **val) |
59 | 0 | { |
60 | 0 | const char *straw; |
61 | 0 | size_t n = strlen(key); |
62 | 0 | while ((opts = fz_get_option(ctx, &straw, val, opts))) |
63 | 0 | if (!strncmp(straw, key, n) && (straw[n] == '=' || straw[n] == ',' || straw[n] == 0)) |
64 | 0 | return 1; |
65 | 0 | return 0; |
66 | 0 | } |
67 | | |
68 | | int |
69 | | fz_option_eq(const char *a, const char *b) |
70 | 0 | { |
71 | 0 | size_t n = strlen(b); |
72 | 0 | return !strncmp(a, b, n) && (a[n] == ',' || a[n] == 0); |
73 | 0 | } |
74 | | |
75 | | size_t |
76 | | fz_copy_option(fz_context *ctx, const char *val, char *dest, size_t maxlen) |
77 | 0 | { |
78 | 0 | const char *e = val; |
79 | 0 | size_t len, len2; |
80 | |
|
81 | 0 | if (val == NULL) { |
82 | 0 | if (maxlen) |
83 | 0 | *dest = 0; |
84 | 0 | return 0; |
85 | 0 | } |
86 | | |
87 | 0 | while (*e != ',' && *e != 0) |
88 | 0 | e++; |
89 | |
|
90 | 0 | len = e-val; |
91 | 0 | len2 = len+1; /* Allow for terminator */ |
92 | 0 | if (len > maxlen) |
93 | 0 | len = maxlen; |
94 | 0 | memcpy(dest, val, len); |
95 | 0 | if (len < maxlen) |
96 | 0 | memset(dest+len, 0, maxlen-len); |
97 | |
|
98 | 0 | return len2 >= maxlen ? len2 - maxlen : 0; |
99 | 0 | } |
100 | | |
101 | | fz_document_writer *fz_new_document_writer_of_size(fz_context *ctx, size_t size, fz_document_writer_begin_page_fn *begin_page, |
102 | | fz_document_writer_end_page_fn *end_page, fz_document_writer_close_writer_fn *close, fz_document_writer_drop_writer_fn *drop) |
103 | 0 | { |
104 | 0 | fz_document_writer *wri = Memento_label(fz_calloc(ctx, 1, size), "fz_document_writer"); |
105 | |
|
106 | 0 | wri->begin_page = begin_page; |
107 | 0 | wri->end_page = end_page; |
108 | 0 | wri->close_writer = close; |
109 | 0 | wri->drop_writer = drop; |
110 | |
|
111 | 0 | return wri; |
112 | 0 | } |
113 | | |
114 | | static void fz_save_pixmap_as_jpeg_default(fz_context *ctx, fz_pixmap *pixmap, const char *filename) |
115 | 0 | { |
116 | 0 | fz_save_pixmap_as_jpeg(ctx, pixmap, filename, 90); |
117 | 0 | } |
118 | | |
119 | | fz_document_writer *fz_new_jpeg_pixmap_writer(fz_context *ctx, const char *path, const char *options) |
120 | 0 | { |
121 | 0 | return fz_new_pixmap_writer(ctx, path, options, "out-%04d.jpeg", 0, fz_save_pixmap_as_jpeg_default); |
122 | 0 | } |
123 | | |
124 | | fz_document_writer *fz_new_png_pixmap_writer(fz_context *ctx, const char *path, const char *options) |
125 | 0 | { |
126 | 0 | return fz_new_pixmap_writer(ctx, path, options, "out-%04d.png", 0, fz_save_pixmap_as_png); |
127 | 0 | } |
128 | | |
129 | | fz_document_writer *fz_new_pam_pixmap_writer(fz_context *ctx, const char *path, const char *options) |
130 | 0 | { |
131 | 0 | return fz_new_pixmap_writer(ctx, path, options, "out-%04d.pam", 0, fz_save_pixmap_as_pam); |
132 | 0 | } |
133 | | |
134 | | fz_document_writer *fz_new_pnm_pixmap_writer(fz_context *ctx, const char *path, const char *options) |
135 | 0 | { |
136 | 0 | return fz_new_pixmap_writer(ctx, path, options, "out-%04d.pnm", 0, fz_save_pixmap_as_pnm); |
137 | 0 | } |
138 | | |
139 | | fz_document_writer *fz_new_pgm_pixmap_writer(fz_context *ctx, const char *path, const char *options) |
140 | 0 | { |
141 | 0 | return fz_new_pixmap_writer(ctx, path, options, "out-%04d.pgm", 1, fz_save_pixmap_as_pnm); |
142 | 0 | } |
143 | | |
144 | | fz_document_writer *fz_new_ppm_pixmap_writer(fz_context *ctx, const char *path, const char *options) |
145 | 0 | { |
146 | 0 | return fz_new_pixmap_writer(ctx, path, options, "out-%04d.ppm", 3, fz_save_pixmap_as_pnm); |
147 | 0 | } |
148 | | |
149 | | fz_document_writer *fz_new_pbm_pixmap_writer(fz_context *ctx, const char *path, const char *options) |
150 | 0 | { |
151 | 0 | return fz_new_pixmap_writer(ctx, path, options, "out-%04d.pbm", 1, fz_save_pixmap_as_pbm); |
152 | 0 | } |
153 | | |
154 | | fz_document_writer *fz_new_pkm_pixmap_writer(fz_context *ctx, const char *path, const char *options) |
155 | 0 | { |
156 | 0 | return fz_new_pixmap_writer(ctx, path, options, "out-%04d.pkm", 4, fz_save_pixmap_as_pkm); |
157 | 0 | } |
158 | | |
159 | | static void fz_write_pixmap_as_jpeg_default(fz_context *ctx, fz_output *out, fz_pixmap *pixmap) |
160 | 0 | { |
161 | 0 | fz_write_pixmap_as_jpeg(ctx, out, pixmap, 90, 1); |
162 | 0 | } |
163 | | |
164 | | fz_document_writer *fz_new_jpeg_pixmap_writer_with_output(fz_context *ctx, fz_output *out, const char *options) |
165 | 0 | { |
166 | 0 | return fz_new_pixmap_writer_with_output(ctx, out, options, 0, fz_write_pixmap_as_jpeg_default); |
167 | 0 | } |
168 | | |
169 | | fz_document_writer *fz_new_png_pixmap_writer_with_output(fz_context *ctx, fz_output *out, const char *options) |
170 | 0 | { |
171 | 0 | return fz_new_pixmap_writer_with_output(ctx, out, options, 0, fz_write_pixmap_as_png); |
172 | 0 | } |
173 | | |
174 | | fz_document_writer *fz_new_pam_pixmap_writer_with_output(fz_context *ctx, fz_output *out, const char *options) |
175 | 0 | { |
176 | 0 | return fz_new_pixmap_writer_with_output(ctx, out, options, 0, fz_write_pixmap_as_pam); |
177 | 0 | } |
178 | | |
179 | | fz_document_writer *fz_new_pnm_pixmap_writer_with_output(fz_context *ctx, fz_output *out, const char *options) |
180 | 0 | { |
181 | 0 | return fz_new_pixmap_writer_with_output(ctx, out, options, 0, fz_write_pixmap_as_pnm); |
182 | 0 | } |
183 | | |
184 | | fz_document_writer *fz_new_pgm_pixmap_writer_with_output(fz_context *ctx, fz_output *out, const char *options) |
185 | 0 | { |
186 | 0 | return fz_new_pixmap_writer_with_output(ctx, out, options, 1, fz_write_pixmap_as_pnm); |
187 | 0 | } |
188 | | |
189 | | fz_document_writer *fz_new_ppm_pixmap_writer_with_output(fz_context *ctx, fz_output *out, const char *options) |
190 | 0 | { |
191 | 0 | return fz_new_pixmap_writer_with_output(ctx, out, options, 3, fz_write_pixmap_as_pnm); |
192 | 0 | } |
193 | | |
194 | | fz_document_writer *fz_new_pbm_pixmap_writer_with_output(fz_context *ctx, fz_output *out, const char *options) |
195 | 0 | { |
196 | 0 | return fz_new_pixmap_writer_with_output(ctx, out, options, 1, fz_write_pixmap_as_pbm); |
197 | 0 | } |
198 | | |
199 | | fz_document_writer *fz_new_pkm_pixmap_writer_with_output(fz_context *ctx, fz_output *out, const char *options) |
200 | 0 | { |
201 | 0 | return fz_new_pixmap_writer_with_output(ctx, out, options, 4, fz_write_pixmap_as_pkm); |
202 | 0 | } |
203 | | |
204 | | static int is_extension(const char *a, const char *ext) |
205 | 0 | { |
206 | 0 | if (!a) |
207 | 0 | return 0; |
208 | 0 | if (a[0] == '.') |
209 | 0 | ++a; |
210 | 0 | return !fz_strcasecmp(a, ext); |
211 | 0 | } |
212 | | |
213 | | static const char *prev_period(const char *start, const char *p) |
214 | 0 | { |
215 | 0 | while (--p > start) |
216 | 0 | if (*p == '.') |
217 | 0 | return p; |
218 | 0 | return NULL; |
219 | 0 | } |
220 | | |
221 | | fz_document_writer * |
222 | | fz_new_document_writer(fz_context *ctx, const char *path, const char *explicit_format, const char *options) |
223 | 0 | { |
224 | 0 | const char *format = explicit_format; |
225 | 0 | if (!format) |
226 | 0 | format = strrchr(path, '.'); |
227 | 0 | while (format) |
228 | 0 | { |
229 | 0 | #if FZ_ENABLE_OCR_OUTPUT |
230 | 0 | if (is_extension(format, "ocr")) |
231 | 0 | return fz_new_pdfocr_writer(ctx, path, options); |
232 | 0 | #endif |
233 | 0 | #if FZ_ENABLE_PDF |
234 | 0 | if (is_extension(format, "pdf")) |
235 | 0 | return fz_new_pdf_writer(ctx, path, options); |
236 | 0 | #endif |
237 | | |
238 | 0 | if (is_extension(format, "cbz")) |
239 | 0 | return fz_new_cbz_writer(ctx, path, options); |
240 | 0 | if (is_extension(format, "csv")) |
241 | 0 | return fz_new_csv_writer(ctx, path, options); |
242 | | |
243 | 0 | if (is_extension(format, "svg")) |
244 | 0 | return fz_new_svg_writer(ctx, path, options); |
245 | | |
246 | 0 | if (is_extension(format, "png")) |
247 | 0 | return fz_new_png_pixmap_writer(ctx, path, options); |
248 | 0 | if (is_extension(format, "pam")) |
249 | 0 | return fz_new_pam_pixmap_writer(ctx, path, options); |
250 | 0 | if (is_extension(format, "pnm")) |
251 | 0 | return fz_new_pnm_pixmap_writer(ctx, path, options); |
252 | 0 | if (is_extension(format, "pgm")) |
253 | 0 | return fz_new_pgm_pixmap_writer(ctx, path, options); |
254 | 0 | if (is_extension(format, "ppm")) |
255 | 0 | return fz_new_ppm_pixmap_writer(ctx, path, options); |
256 | 0 | if (is_extension(format, "pbm")) |
257 | 0 | return fz_new_pbm_pixmap_writer(ctx, path, options); |
258 | 0 | if (is_extension(format, "pkm")) |
259 | 0 | return fz_new_pkm_pixmap_writer(ctx, path, options); |
260 | 0 | if (is_extension(format, "jpeg") || is_extension(format, "jpg")) |
261 | 0 | return fz_new_jpeg_pixmap_writer(ctx, path, options); |
262 | | |
263 | 0 | if (is_extension(format, "pcl")) |
264 | 0 | return fz_new_pcl_writer(ctx, path, options); |
265 | 0 | if (is_extension(format, "pclm")) |
266 | 0 | return fz_new_pclm_writer(ctx, path, options); |
267 | 0 | if (is_extension(format, "ps")) |
268 | 0 | return fz_new_ps_writer(ctx, path, options); |
269 | 0 | if (is_extension(format, "pwg")) |
270 | 0 | return fz_new_pwg_writer(ctx, path, options); |
271 | | |
272 | 0 | if (is_extension(format, "txt") || is_extension(format, "text")) |
273 | 0 | return fz_new_text_writer(ctx, "text", path, options); |
274 | 0 | if (is_extension(format, "html")) |
275 | 0 | return fz_new_text_writer(ctx, "html", path, options); |
276 | 0 | if (is_extension(format, "xhtml")) |
277 | 0 | return fz_new_text_writer(ctx, "xhtml", path, options); |
278 | 0 | if (is_extension(format, "stext") || is_extension(format, "stext.xml")) |
279 | 0 | return fz_new_text_writer(ctx, "stext.xml", path, options); |
280 | 0 | if (is_extension(format, "stext.json")) |
281 | 0 | return fz_new_text_writer(ctx, "stext.json", path, options); |
282 | | |
283 | 0 | #if FZ_ENABLE_ODT_OUTPUT |
284 | 0 | if (is_extension(format, "odt")) |
285 | 0 | return fz_new_odt_writer(ctx, path, options); |
286 | 0 | #endif |
287 | 0 | #if FZ_ENABLE_DOCX_OUTPUT |
288 | 0 | if (is_extension(format, "docx")) |
289 | 0 | return fz_new_docx_writer(ctx, path, options); |
290 | 0 | #endif |
291 | 0 | if (format != explicit_format) |
292 | 0 | format = prev_period(path, format); |
293 | 0 | else |
294 | 0 | format = NULL; |
295 | 0 | } |
296 | 0 | fz_throw(ctx, FZ_ERROR_ARGUMENT, "cannot detect document format"); |
297 | 0 | } |
298 | | |
299 | | fz_document_writer * |
300 | | fz_new_document_writer_with_output(fz_context *ctx, fz_output *out, const char *format, const char *options) |
301 | 0 | { |
302 | 0 | #if FZ_ENABLE_OCR_OUTPUT |
303 | 0 | if (is_extension(format, "ocr")) |
304 | 0 | return fz_new_pdfocr_writer_with_output(ctx, out, options); |
305 | 0 | #endif |
306 | 0 | #if FZ_ENABLE_PDF |
307 | 0 | if (is_extension(format, "pdf")) |
308 | 0 | return fz_new_pdf_writer_with_output(ctx, out, options); |
309 | 0 | #endif |
310 | | |
311 | 0 | if (is_extension(format, "cbz")) |
312 | 0 | return fz_new_cbz_writer_with_output(ctx, out, options); |
313 | 0 | if (is_extension(format, "csv")) |
314 | 0 | return fz_new_csv_writer_with_output(ctx, out, options); |
315 | | |
316 | 0 | if (is_extension(format, "svg")) |
317 | 0 | return fz_new_svg_writer_with_output(ctx, out, options); |
318 | | |
319 | 0 | if (is_extension(format, "png")) |
320 | 0 | return fz_new_png_pixmap_writer_with_output(ctx, out, options); |
321 | 0 | if (is_extension(format, "pam")) |
322 | 0 | return fz_new_pam_pixmap_writer_with_output(ctx, out, options); |
323 | 0 | if (is_extension(format, "pnm")) |
324 | 0 | return fz_new_pnm_pixmap_writer_with_output(ctx, out, options); |
325 | 0 | if (is_extension(format, "pgm")) |
326 | 0 | return fz_new_pgm_pixmap_writer_with_output(ctx, out, options); |
327 | 0 | if (is_extension(format, "ppm")) |
328 | 0 | return fz_new_ppm_pixmap_writer_with_output(ctx, out, options); |
329 | 0 | if (is_extension(format, "pbm")) |
330 | 0 | return fz_new_pbm_pixmap_writer_with_output(ctx, out, options); |
331 | 0 | if (is_extension(format, "pkm")) |
332 | 0 | return fz_new_pkm_pixmap_writer_with_output(ctx, out, options); |
333 | 0 | if (is_extension(format, "jpeg") || is_extension(format, "jpg")) |
334 | 0 | return fz_new_jpeg_pixmap_writer_with_output(ctx, out, options); |
335 | | |
336 | 0 | if (is_extension(format, "pcl")) |
337 | 0 | return fz_new_pcl_writer_with_output(ctx, out, options); |
338 | 0 | if (is_extension(format, "pclm")) |
339 | 0 | return fz_new_pclm_writer_with_output(ctx, out, options); |
340 | 0 | if (is_extension(format, "ps")) |
341 | 0 | return fz_new_ps_writer_with_output(ctx, out, options); |
342 | 0 | if (is_extension(format, "pwg")) |
343 | 0 | return fz_new_pwg_writer_with_output(ctx, out, options); |
344 | | |
345 | 0 | if (is_extension(format, "txt") || is_extension(format, "text")) |
346 | 0 | return fz_new_text_writer_with_output(ctx, "text", out, options); |
347 | 0 | if (is_extension(format, "html")) |
348 | 0 | return fz_new_text_writer_with_output(ctx, "html", out, options); |
349 | 0 | if (is_extension(format, "xhtml")) |
350 | 0 | return fz_new_text_writer_with_output(ctx, "xhtml", out, options); |
351 | 0 | if (is_extension(format, "stext") || is_extension(format, "stext.xml")) |
352 | 0 | return fz_new_text_writer_with_output(ctx, "stext.xml", out, options); |
353 | 0 | if (is_extension(format, "stext.json")) |
354 | 0 | return fz_new_text_writer_with_output(ctx, "stext.json", out, options); |
355 | | |
356 | 0 | #if FZ_ENABLE_ODT_OUTPUT |
357 | 0 | if (is_extension(format, "odt")) |
358 | 0 | return fz_new_odt_writer_with_output(ctx, out, options); |
359 | 0 | #endif |
360 | 0 | #if FZ_ENABLE_DOCX_OUTPUT |
361 | 0 | if (is_extension(format, "docx")) |
362 | 0 | return fz_new_docx_writer_with_output(ctx, out, options); |
363 | 0 | #endif |
364 | | |
365 | 0 | fz_throw(ctx, FZ_ERROR_ARGUMENT, "unknown output document format: %s", format); |
366 | 0 | } |
367 | | |
368 | | fz_document_writer * |
369 | | fz_new_document_writer_with_buffer(fz_context *ctx, fz_buffer *buffer, const char *format, const char *options) |
370 | 0 | { |
371 | 0 | fz_document_writer *wri; |
372 | 0 | fz_output *out = fz_new_output_with_buffer(ctx, buffer); |
373 | 0 | fz_try(ctx) { |
374 | 0 | wri = fz_new_document_writer_with_output(ctx, out, format, options); |
375 | 0 | } |
376 | 0 | fz_catch(ctx) { |
377 | 0 | fz_drop_output(ctx, out); |
378 | 0 | fz_rethrow(ctx); |
379 | 0 | } |
380 | 0 | return wri; |
381 | 0 | } |
382 | | |
383 | | void |
384 | | fz_close_document_writer(fz_context *ctx, fz_document_writer *wri) |
385 | 0 | { |
386 | 0 | if (wri->close_writer) |
387 | 0 | wri->close_writer(ctx, wri); |
388 | 0 | wri->close_writer = NULL; |
389 | 0 | } |
390 | | |
391 | | void |
392 | | fz_drop_document_writer(fz_context *ctx, fz_document_writer *wri) |
393 | 0 | { |
394 | 0 | if (!wri) |
395 | 0 | return; |
396 | | |
397 | 0 | if (wri->close_writer) |
398 | 0 | fz_warn(ctx, "dropping unclosed document writer"); |
399 | 0 | if (wri->dev) |
400 | 0 | fz_drop_device(ctx, wri->dev); |
401 | 0 | if (wri->drop_writer) |
402 | 0 | wri->drop_writer(ctx, wri); |
403 | 0 | fz_free(ctx, wri); |
404 | 0 | } |
405 | | |
406 | | fz_device * |
407 | | fz_begin_page(fz_context *ctx, fz_document_writer *wri, fz_rect mediabox) |
408 | 0 | { |
409 | 0 | if (!wri) |
410 | 0 | return NULL; |
411 | 0 | if (wri->dev) |
412 | 0 | fz_throw(ctx, FZ_ERROR_ARGUMENT, "called begin page without ending the previous page"); |
413 | 0 | wri->dev = wri->begin_page(ctx, wri, mediabox); |
414 | 0 | return wri->dev; |
415 | 0 | } |
416 | | |
417 | | void |
418 | | fz_end_page(fz_context *ctx, fz_document_writer *wri) |
419 | 0 | { |
420 | 0 | fz_device *dev; |
421 | |
|
422 | 0 | if (!wri) |
423 | 0 | return; |
424 | 0 | dev = wri->dev; |
425 | 0 | wri->dev = NULL; |
426 | 0 | wri->end_page(ctx, wri, dev); |
427 | 0 | } |
428 | | |
429 | | void |
430 | | fz_write_document(fz_context *ctx, fz_document_writer *wri, fz_document *doc) |
431 | 0 | { |
432 | 0 | int i, n; |
433 | 0 | fz_page *page = NULL; |
434 | 0 | fz_device *dev; |
435 | |
|
436 | 0 | fz_var(page); |
437 | |
|
438 | 0 | n = fz_count_pages(ctx, doc); |
439 | 0 | fz_try(ctx) |
440 | 0 | { |
441 | 0 | for (i = 0; i < n; i++) |
442 | 0 | { |
443 | 0 | page = fz_load_page(ctx, doc, i); |
444 | 0 | dev = fz_begin_page(ctx, wri, fz_bound_page(ctx, page)); |
445 | 0 | fz_run_page(ctx, page, dev, fz_identity, NULL); |
446 | 0 | fz_drop_page(ctx, page); |
447 | 0 | page = NULL; |
448 | 0 | fz_end_page(ctx, wri); |
449 | 0 | } |
450 | 0 | } |
451 | 0 | fz_catch(ctx) |
452 | 0 | { |
453 | 0 | fz_drop_page(ctx, page); |
454 | 0 | fz_rethrow(ctx); |
455 | 0 | } |
456 | 0 | } |