/src/mupdf/source/fitz/writer.c
Line | Count | Source (jump to first uncovered line) |
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 | | |
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 int is_extension(const char *a, const char *ext) |
160 | 0 | { |
161 | 0 | if (!a) |
162 | 0 | return 0; |
163 | 0 | if (a[0] == '.') |
164 | 0 | ++a; |
165 | 0 | return !fz_strcasecmp(a, ext); |
166 | 0 | } |
167 | | |
168 | | static const char *prev_period(const char *start, const char *p) |
169 | 0 | { |
170 | 0 | while (--p > start) |
171 | 0 | if (*p == '.') |
172 | 0 | return p; |
173 | 0 | return NULL; |
174 | 0 | } |
175 | | |
176 | | fz_document_writer * |
177 | | fz_new_document_writer(fz_context *ctx, const char *path, const char *explicit_format, const char *options) |
178 | 0 | { |
179 | 0 | const char *format = explicit_format; |
180 | 0 | if (!format) |
181 | 0 | format = strrchr(path, '.'); |
182 | 0 | while (format) |
183 | 0 | { |
184 | 0 | #if FZ_ENABLE_OCR_OUTPUT |
185 | 0 | if (is_extension(format, "ocr")) |
186 | 0 | return fz_new_pdfocr_writer(ctx, path, options); |
187 | 0 | #endif |
188 | 0 | #if FZ_ENABLE_PDF |
189 | 0 | if (is_extension(format, "pdf")) |
190 | 0 | return fz_new_pdf_writer(ctx, path, options); |
191 | 0 | #endif |
192 | | |
193 | 0 | if (is_extension(format, "cbz")) |
194 | 0 | return fz_new_cbz_writer(ctx, path, options); |
195 | | |
196 | 0 | if (is_extension(format, "svg")) |
197 | 0 | return fz_new_svg_writer(ctx, path, options); |
198 | | |
199 | 0 | if (is_extension(format, "png")) |
200 | 0 | return fz_new_png_pixmap_writer(ctx, path, options); |
201 | 0 | if (is_extension(format, "pam")) |
202 | 0 | return fz_new_pam_pixmap_writer(ctx, path, options); |
203 | 0 | if (is_extension(format, "pnm")) |
204 | 0 | return fz_new_pnm_pixmap_writer(ctx, path, options); |
205 | 0 | if (is_extension(format, "pgm")) |
206 | 0 | return fz_new_pgm_pixmap_writer(ctx, path, options); |
207 | 0 | if (is_extension(format, "ppm")) |
208 | 0 | return fz_new_ppm_pixmap_writer(ctx, path, options); |
209 | 0 | if (is_extension(format, "pbm")) |
210 | 0 | return fz_new_pbm_pixmap_writer(ctx, path, options); |
211 | 0 | if (is_extension(format, "pkm")) |
212 | 0 | return fz_new_pkm_pixmap_writer(ctx, path, options); |
213 | 0 | if (is_extension(format, "jpeg") || is_extension(format, "jpg")) |
214 | 0 | return fz_new_jpeg_pixmap_writer(ctx, path, options); |
215 | | |
216 | 0 | if (is_extension(format, "pcl")) |
217 | 0 | return fz_new_pcl_writer(ctx, path, options); |
218 | 0 | if (is_extension(format, "pclm")) |
219 | 0 | return fz_new_pclm_writer(ctx, path, options); |
220 | 0 | if (is_extension(format, "ps")) |
221 | 0 | return fz_new_ps_writer(ctx, path, options); |
222 | 0 | if (is_extension(format, "pwg")) |
223 | 0 | return fz_new_pwg_writer(ctx, path, options); |
224 | | |
225 | 0 | if (is_extension(format, "txt") || is_extension(format, "text")) |
226 | 0 | return fz_new_text_writer(ctx, "text", path, options); |
227 | 0 | if (is_extension(format, "html")) |
228 | 0 | return fz_new_text_writer(ctx, "html", path, options); |
229 | 0 | if (is_extension(format, "xhtml")) |
230 | 0 | return fz_new_text_writer(ctx, "xhtml", path, options); |
231 | 0 | if (is_extension(format, "stext") || is_extension(format, "stext.xml")) |
232 | 0 | return fz_new_text_writer(ctx, "stext.xml", path, options); |
233 | 0 | if (is_extension(format, "stext.json")) |
234 | 0 | return fz_new_text_writer(ctx, "stext.json", path, options); |
235 | | |
236 | 0 | #if FZ_ENABLE_ODT_OUTPUT |
237 | 0 | if (is_extension(format, "odt")) |
238 | 0 | return fz_new_odt_writer(ctx, path, options); |
239 | 0 | #endif |
240 | 0 | #if FZ_ENABLE_DOCX_OUTPUT |
241 | 0 | if (is_extension(format, "docx")) |
242 | 0 | return fz_new_docx_writer(ctx, path, options); |
243 | 0 | #endif |
244 | 0 | if (format != explicit_format) |
245 | 0 | format = prev_period(path, format); |
246 | 0 | else |
247 | 0 | format = NULL; |
248 | 0 | } |
249 | 0 | fz_throw(ctx, FZ_ERROR_ARGUMENT, "cannot detect document format"); |
250 | 0 | } |
251 | | |
252 | | fz_document_writer * |
253 | | fz_new_document_writer_with_output(fz_context *ctx, fz_output *out, const char *format, const char *options) |
254 | 0 | { |
255 | 0 | #if FZ_ENABLE_OCR_OUTPUT |
256 | 0 | if (is_extension(format, "ocr")) |
257 | 0 | return fz_new_pdfocr_writer_with_output(ctx, out, options); |
258 | 0 | #endif |
259 | 0 | #if FZ_ENABLE_PDF |
260 | 0 | if (is_extension(format, "pdf")) |
261 | 0 | return fz_new_pdf_writer_with_output(ctx, out, options); |
262 | 0 | #endif |
263 | | |
264 | 0 | if (is_extension(format, "cbz")) |
265 | 0 | return fz_new_cbz_writer_with_output(ctx, out, options); |
266 | | |
267 | 0 | if (is_extension(format, "svg")) |
268 | 0 | return fz_new_svg_writer_with_output(ctx, out, options); |
269 | | |
270 | 0 | if (is_extension(format, "pcl")) |
271 | 0 | return fz_new_pcl_writer_with_output(ctx, out, options); |
272 | 0 | if (is_extension(format, "pclm")) |
273 | 0 | return fz_new_pclm_writer_with_output(ctx, out, options); |
274 | 0 | if (is_extension(format, "ps")) |
275 | 0 | return fz_new_ps_writer_with_output(ctx, out, options); |
276 | 0 | if (is_extension(format, "pwg")) |
277 | 0 | return fz_new_pwg_writer_with_output(ctx, out, options); |
278 | | |
279 | 0 | if (is_extension(format, "txt") || is_extension(format, "text")) |
280 | 0 | return fz_new_text_writer_with_output(ctx, "text", out, options); |
281 | 0 | if (is_extension(format, "html")) |
282 | 0 | return fz_new_text_writer_with_output(ctx, "html", out, options); |
283 | 0 | if (is_extension(format, "xhtml")) |
284 | 0 | return fz_new_text_writer_with_output(ctx, "xhtml", out, options); |
285 | 0 | if (is_extension(format, "stext") || is_extension(format, "stext.xml")) |
286 | 0 | return fz_new_text_writer_with_output(ctx, "stext.xml", out, options); |
287 | 0 | if (is_extension(format, "stext.json")) |
288 | 0 | return fz_new_text_writer_with_output(ctx, "stext.json", out, options); |
289 | | |
290 | 0 | #if FZ_ENABLE_ODT_OUTPUT |
291 | 0 | if (is_extension(format, "odt")) |
292 | 0 | return fz_new_odt_writer_with_output(ctx, out, options); |
293 | 0 | #endif |
294 | 0 | #if FZ_ENABLE_DOCX_OUTPUT |
295 | 0 | if (is_extension(format, "docx")) |
296 | 0 | return fz_new_docx_writer_with_output(ctx, out, options); |
297 | 0 | #endif |
298 | | |
299 | 0 | fz_throw(ctx, FZ_ERROR_ARGUMENT, "unknown output document format: %s", format); |
300 | 0 | } |
301 | | |
302 | | fz_document_writer * |
303 | | fz_new_document_writer_with_buffer(fz_context *ctx, fz_buffer *buffer, const char *format, const char *options) |
304 | 0 | { |
305 | 0 | fz_document_writer *wri; |
306 | 0 | fz_output *out = fz_new_output_with_buffer(ctx, buffer); |
307 | 0 | fz_try(ctx) { |
308 | 0 | wri = fz_new_document_writer_with_output(ctx, out, format, options); |
309 | 0 | } |
310 | 0 | fz_catch(ctx) { |
311 | 0 | fz_drop_output(ctx, out); |
312 | 0 | fz_rethrow(ctx); |
313 | 0 | } |
314 | 0 | return wri; |
315 | 0 | } |
316 | | |
317 | | void |
318 | | fz_close_document_writer(fz_context *ctx, fz_document_writer *wri) |
319 | 0 | { |
320 | 0 | if (wri->close_writer) |
321 | 0 | wri->close_writer(ctx, wri); |
322 | 0 | wri->close_writer = NULL; |
323 | 0 | } |
324 | | |
325 | | void |
326 | | fz_drop_document_writer(fz_context *ctx, fz_document_writer *wri) |
327 | 0 | { |
328 | 0 | if (!wri) |
329 | 0 | return; |
330 | | |
331 | 0 | if (wri->close_writer) |
332 | 0 | fz_warn(ctx, "dropping unclosed document writer"); |
333 | 0 | if (wri->dev) |
334 | 0 | fz_drop_device(ctx, wri->dev); |
335 | 0 | if (wri->drop_writer) |
336 | 0 | wri->drop_writer(ctx, wri); |
337 | 0 | fz_free(ctx, wri); |
338 | 0 | } |
339 | | |
340 | | fz_device * |
341 | | fz_begin_page(fz_context *ctx, fz_document_writer *wri, fz_rect mediabox) |
342 | 0 | { |
343 | 0 | if (!wri) |
344 | 0 | return NULL; |
345 | 0 | if (wri->dev) |
346 | 0 | fz_throw(ctx, FZ_ERROR_ARGUMENT, "called begin page without ending the previous page"); |
347 | 0 | wri->dev = wri->begin_page(ctx, wri, mediabox); |
348 | 0 | return wri->dev; |
349 | 0 | } |
350 | | |
351 | | void |
352 | | fz_end_page(fz_context *ctx, fz_document_writer *wri) |
353 | 0 | { |
354 | 0 | fz_device *dev; |
355 | |
|
356 | 0 | if (!wri) |
357 | 0 | return; |
358 | 0 | dev = wri->dev; |
359 | 0 | wri->dev = NULL; |
360 | 0 | wri->end_page(ctx, wri, dev); |
361 | 0 | } |
362 | | |
363 | | void |
364 | | fz_write_document(fz_context *ctx, fz_document_writer *wri, fz_document *doc) |
365 | 0 | { |
366 | 0 | int i, n; |
367 | 0 | fz_page *page = NULL; |
368 | 0 | fz_device *dev; |
369 | |
|
370 | 0 | fz_var(page); |
371 | |
|
372 | 0 | n = fz_count_pages(ctx, doc); |
373 | 0 | fz_try(ctx) |
374 | 0 | { |
375 | 0 | for (i = 0; i < n; i++) |
376 | 0 | { |
377 | 0 | page = fz_load_page(ctx, doc, i); |
378 | 0 | dev = fz_begin_page(ctx, wri, fz_bound_page(ctx, page)); |
379 | 0 | fz_run_page(ctx, page, dev, fz_identity, NULL); |
380 | 0 | fz_drop_page(ctx, page); |
381 | 0 | page = NULL; |
382 | 0 | fz_end_page(ctx, wri); |
383 | 0 | } |
384 | 0 | } |
385 | 0 | fz_catch(ctx) |
386 | 0 | { |
387 | 0 | fz_drop_page(ctx, page); |
388 | 0 | fz_rethrow(ctx); |
389 | 0 | } |
390 | 0 | } |