comparison mupdf-source/source/tools/mudraw.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-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 /*
24 * mudraw -- command line tool for drawing and converting documents
25 */
26
27 #include "mupdf/fitz.h"
28
29 #if FZ_ENABLE_PDF
30 #include "mupdf/pdf.h" /* for pdf output */
31 #endif
32
33 #ifndef DISABLE_MUTHREADS
34 #include "mupdf/helpers/mu-threads.h"
35 #endif
36
37 #ifdef HAVE_SMARTOFFICE
38 #include "sodochandler.h"
39 #endif
40
41 #include <string.h>
42 #include <limits.h>
43 #include <stdlib.h>
44 #include <stdio.h>
45 #ifdef _WIN32
46 struct timeval;
47 struct timezone;
48 int gettimeofday(struct timeval *tv, struct timezone *tz);
49 #else
50 #include <sys/time.h>
51 #endif
52 #ifdef _WIN32
53 #include <windows.h>
54 #include <direct.h> /* for getcwd */
55 #else
56 #include <sys/stat.h> /* for mkdir */
57 #include <unistd.h> /* for getcwd */
58 #endif
59
60 /* Allow for windows stdout being made binary */
61 #ifdef _WIN32
62 #include <io.h>
63 #include <fcntl.h>
64 #endif
65
66 /* Enable for helpful threading debug */
67 /* #define DEBUG_THREADS(A) do { printf A; fflush(stdout); } while (0) */
68 #define DEBUG_THREADS(A) do { } while (0)
69
70 enum {
71 OUT_BBOX,
72 OUT_HTML,
73 OUT_NONE,
74 OUT_OCR_HTML,
75 OUT_OCR_PDF,
76 OUT_OCR_STEXT_JSON,
77 OUT_OCR_STEXT_XML,
78 OUT_OCR_TEXT,
79 OUT_OCR_TRACE,
80 OUT_OCR_XHTML,
81 OUT_PAM,
82 OUT_PBM,
83 OUT_PCL,
84 OUT_PCLM,
85 OUT_PGM,
86 OUT_PKM,
87 OUT_PNG,
88 OUT_J2K,
89 OUT_PNM,
90 OUT_PPM,
91 OUT_PS,
92 OUT_PSD,
93 OUT_PWG,
94 OUT_STEXT_JSON,
95 OUT_STEXT_XML,
96 OUT_SVG,
97 OUT_TEXT,
98 OUT_TRACE,
99 OUT_XHTML,
100 OUT_XMLTEXT,
101 #if FZ_ENABLE_PDF
102 OUT_PDF,
103 #endif
104 };
105
106 enum { CS_INVALID, CS_UNSET, CS_MONO, CS_GRAY, CS_GRAY_ALPHA, CS_RGB, CS_RGB_ALPHA, CS_CMYK, CS_CMYK_ALPHA, CS_ICC };
107
108 enum { SPOTS_NONE, SPOTS_OVERPRINT_SIM, SPOTS_FULL };
109
110 typedef struct
111 {
112 char *suffix;
113 int format;
114 int spots;
115 } suffix_t;
116
117 static const suffix_t suffix_table[] =
118 {
119 /* All the 'triple extension' ones must go first. */
120 { ".ocr.stext.json", OUT_OCR_STEXT_JSON, 0 },
121
122 /* All the 'double extension' ones must go next. */
123 { ".ocr.txt", OUT_OCR_TEXT, 0 },
124 { ".ocr.text", OUT_OCR_TEXT, 0 },
125 { ".ocr.html", OUT_OCR_HTML, 0 },
126 { ".ocr.xhtml", OUT_OCR_XHTML, 0 },
127 { ".ocr.stext", OUT_OCR_STEXT_XML, 0 },
128 { ".ocr.pdf", OUT_OCR_PDF, 0 },
129 { ".ocr.trace", OUT_OCR_TRACE, 0 },
130 { ".stext.json", OUT_STEXT_JSON, 0 },
131
132 /* And the 'single extension' ones go last. */
133 #if FZ_ENABLE_JPX
134 { ".j2k", OUT_J2K, 0 },
135 #endif
136 { ".png", OUT_PNG, 0 },
137 { ".pgm", OUT_PGM, 0 },
138 { ".ppm", OUT_PPM, 0 },
139 { ".pnm", OUT_PNM, 0 },
140 { ".pam", OUT_PAM, 0 },
141 { ".pbm", OUT_PBM, 0 },
142 { ".pkm", OUT_PKM, 0 },
143 { ".svg", OUT_SVG, 0 },
144 { ".pwg", OUT_PWG, 0 },
145 { ".pclm", OUT_PCLM, 0 },
146 { ".pcl", OUT_PCL, 0 },
147 #if FZ_ENABLE_PDF
148 { ".pdf", OUT_PDF, 0 },
149 #endif
150 { ".psd", OUT_PSD, 1 },
151 { ".ps", OUT_PS, 0 },
152
153 { ".txt", OUT_TEXT, 0 },
154 { ".text", OUT_TEXT, 0 },
155 { ".html", OUT_HTML, 0 },
156 { ".xhtml", OUT_XHTML, 0 },
157 { ".stext", OUT_STEXT_XML, 0 },
158
159 { ".trace", OUT_TRACE, 0 },
160 { ".xmltext", OUT_XMLTEXT, 0 },
161 { ".bbox", OUT_BBOX, 0 },
162 };
163
164 typedef struct
165 {
166 char *name;
167 int colorspace;
168 } cs_name_t;
169
170 static const cs_name_t cs_name_table[] =
171 {
172 { "m", CS_MONO },
173 { "mono", CS_MONO },
174 { "g", CS_GRAY },
175 { "gray", CS_GRAY },
176 { "grey", CS_GRAY },
177 { "ga", CS_GRAY_ALPHA },
178 { "grayalpha", CS_GRAY_ALPHA },
179 { "greyalpha", CS_GRAY_ALPHA },
180 { "rgb", CS_RGB },
181 { "rgba", CS_RGB_ALPHA },
182 { "rgbalpha", CS_RGB_ALPHA },
183 { "cmyk", CS_CMYK },
184 { "cmyka", CS_CMYK_ALPHA },
185 { "cmykalpha", CS_CMYK_ALPHA },
186 };
187
188 typedef struct
189 {
190 int format;
191 int default_cs;
192 int permitted_cs[7];
193 } format_cs_table_t;
194
195 static const format_cs_table_t format_cs_table[] =
196 {
197 { OUT_PNG, CS_RGB, { CS_GRAY, CS_GRAY_ALPHA, CS_RGB, CS_RGB_ALPHA, CS_ICC } },
198 { OUT_J2K, CS_RGB, { CS_GRAY, CS_RGB } },
199 { OUT_PPM, CS_RGB, { CS_GRAY, CS_RGB } },
200 { OUT_PNM, CS_GRAY, { CS_GRAY, CS_RGB } },
201 { OUT_PAM, CS_RGB_ALPHA, { CS_GRAY, CS_GRAY_ALPHA, CS_RGB, CS_RGB_ALPHA, CS_CMYK, CS_CMYK_ALPHA } },
202 { OUT_PGM, CS_GRAY, { CS_GRAY, CS_RGB } },
203 { OUT_PBM, CS_MONO, { CS_MONO } },
204 { OUT_PKM, CS_CMYK, { CS_CMYK } },
205 { OUT_PWG, CS_RGB, { CS_MONO, CS_GRAY, CS_RGB, CS_CMYK } },
206 { OUT_PCL, CS_MONO, { CS_MONO, CS_RGB } },
207 { OUT_PCLM, CS_RGB, { CS_RGB, CS_GRAY } },
208 { OUT_PS, CS_RGB, { CS_GRAY, CS_RGB, CS_CMYK } },
209 { OUT_PSD, CS_CMYK, { CS_GRAY, CS_GRAY_ALPHA, CS_RGB, CS_RGB_ALPHA, CS_CMYK, CS_CMYK_ALPHA, CS_ICC } },
210
211 { OUT_TRACE, CS_RGB, { CS_RGB } },
212 { OUT_XMLTEXT, CS_RGB, { CS_RGB } },
213 { OUT_BBOX, CS_RGB, { CS_RGB } },
214 { OUT_SVG, CS_RGB, { CS_RGB } },
215 { OUT_OCR_PDF, CS_RGB, { CS_RGB, CS_GRAY } },
216 #if FZ_ENABLE_PDF
217 { OUT_PDF, CS_RGB, { CS_RGB } },
218 #endif
219
220 { OUT_TEXT, CS_RGB, { CS_RGB } },
221 { OUT_HTML, CS_RGB, { CS_RGB } },
222 { OUT_XHTML, CS_RGB, { CS_RGB } },
223 { OUT_STEXT_XML, CS_RGB, { CS_RGB } },
224 { OUT_STEXT_JSON, CS_RGB, { CS_RGB } },
225 { OUT_OCR_TEXT, CS_GRAY, { CS_GRAY } },
226 { OUT_OCR_HTML, CS_GRAY, { CS_GRAY } },
227 { OUT_OCR_XHTML, CS_GRAY, { CS_GRAY } },
228 { OUT_OCR_STEXT_XML, CS_GRAY, { CS_GRAY } },
229 { OUT_OCR_STEXT_JSON, CS_GRAY, { CS_GRAY } },
230 { OUT_OCR_TRACE, CS_GRAY, { CS_GRAY } },
231 };
232
233 /*
234 In the presence of pthreads or Windows threads, we can offer
235 a multi-threaded option. In the absence, of such, we degrade
236 nicely.
237 */
238 #ifndef DISABLE_MUTHREADS
239
240 static mu_mutex mutexes[FZ_LOCK_MAX];
241
242 static void mudraw_lock(void *user, int lock)
243 {
244 mu_lock_mutex(&mutexes[lock]);
245 }
246
247 static void mudraw_unlock(void *user, int lock)
248 {
249 mu_unlock_mutex(&mutexes[lock]);
250 }
251
252 static fz_locks_context mudraw_locks =
253 {
254 NULL, mudraw_lock, mudraw_unlock
255 };
256
257 static void fin_mudraw_locks(void)
258 {
259 int i;
260
261 for (i = 0; i < FZ_LOCK_MAX; i++)
262 mu_destroy_mutex(&mutexes[i]);
263 }
264
265 static fz_locks_context *init_mudraw_locks(void)
266 {
267 int i;
268 int failed = 0;
269
270 for (i = 0; i < FZ_LOCK_MAX; i++)
271 failed |= mu_create_mutex(&mutexes[i]);
272
273 if (failed)
274 {
275 fin_mudraw_locks();
276 return NULL;
277 }
278
279 return &mudraw_locks;
280 }
281
282 #endif
283
284 typedef struct worker_t {
285 fz_context *ctx;
286 int num;
287 int band; /* -1 to shutdown, or band to render */
288 int error;
289 int running; /* set to 1 by main thread when it thinks the worker is running, 0 when it thinks it is not running */
290 fz_display_list *list;
291 fz_matrix ctm;
292 fz_rect tbounds;
293 fz_pixmap *pix;
294 fz_bitmap *bit;
295 fz_cookie cookie;
296 #ifndef DISABLE_MUTHREADS
297 mu_semaphore start;
298 mu_semaphore stop;
299 mu_thread thread;
300 #endif
301 } worker_t;
302
303 static char *output = NULL;
304 static fz_output *out = NULL;
305 static int output_pagenum = 0;
306 static int output_file_per_page = 0;
307
308 static char *format = NULL;
309 static int output_format = OUT_NONE;
310
311 static float rotation = 0;
312 static float resolution = 72;
313 static int res_specified = 0;
314 static int width = 0;
315 static int height = 0;
316 static int fit = 0;
317 static int page_box = FZ_CROP_BOX;
318
319 static float layout_w = FZ_DEFAULT_LAYOUT_W;
320 static float layout_h = FZ_DEFAULT_LAYOUT_H;
321 static float layout_em = FZ_DEFAULT_LAYOUT_EM;
322 static char *layout_css = NULL;
323 static int layout_use_doc_css = 1;
324 static float min_line_width = 0.0f;
325
326 static int showfeatures = 0;
327 static int showtime = 0;
328 static int showmemory = 0;
329 static int showmd5 = 0;
330
331 #if FZ_ENABLE_PDF
332 static pdf_document *pdfout = NULL;
333 #endif
334
335 static int no_icc = 0;
336 static int ignore_errors = 0;
337 static int uselist = 1;
338 static int alphabits_text = 8;
339 static int alphabits_graphics = 8;
340
341 static int out_cs = CS_UNSET;
342 static const char *proof_filename = NULL;
343 fz_colorspace *proof_cs = NULL;
344 static const char *icc_filename = NULL;
345 static float gamma_value = 1;
346 static int invert = 0;
347 static int s_kill = 0; /* Using `kill` causes problems on Android. */
348 static int band_height = 0;
349 static int lowmemory = 0;
350
351 static int quiet = 0;
352 static int errored = 0;
353 static fz_colorspace *colorspace = NULL;
354 static fz_colorspace *oi = NULL;
355 #if FZ_ENABLE_SPOT_RENDERING
356 static int spots = SPOTS_OVERPRINT_SIM;
357 #else
358 static int spots = SPOTS_NONE;
359 #endif
360 static int alpha;
361 static int useaccel = 1;
362 static char *filename;
363 static int files = 0;
364 static int num_workers = 0;
365 static worker_t *workers;
366 static fz_band_writer *bander = NULL;
367
368 static const char *layer_config = NULL;
369 static int layer_list = 0;
370 static int layer_on[1000];
371 static int layer_off[1000];
372 static int layer_on_len;
373 static int layer_off_len;
374
375 static int skew_correct;
376 static float skew_angle;
377 static int skew_border;
378
379 static const char ocr_language_default[] = "eng";
380 static const char *ocr_language = ocr_language_default;
381 static const char *ocr_datadir = NULL;
382
383 static struct {
384 int active;
385 int started;
386 fz_context *ctx;
387 #ifndef DISABLE_MUTHREADS
388 mu_thread thread;
389 mu_semaphore start;
390 mu_semaphore stop;
391 #endif
392 int pagenum;
393 int error;
394 char *filename;
395 fz_display_list *list;
396 fz_page *page;
397 int interptime;
398 fz_separations *seps;
399 } bgprint;
400
401 static struct {
402 int count, total;
403 int min, max;
404 int mininterp, maxinterp;
405 int minpage, maxpage;
406 char *minfilename;
407 char *maxfilename;
408 int layout;
409 int minlayout, maxlayout;
410 char *minlayoutfilename;
411 char *maxlayoutfilename;
412 } timing;
413
414 static int usage(void)
415 {
416 fprintf(stderr,
417 "usage: mudraw [options] file [pages]\n"
418 "\t-p -\tpassword\n"
419 "\n"
420 "\t-o -\toutput file name (%%d for page number)\n"
421 "\t-F -\toutput format (default inferred from output file name)\n"
422 "\t\traster: png, pnm, pam, pbm, pkm, pwg, pcl, ps, pdf, j2k\n"
423 "\t\tvector: svg, pdf, trace, ocr.trace\n"
424 "\t\ttext: txt, html, xhtml, stext, stext.json\n"
425 #ifndef OCR_DISABLED
426 "\t\tocr'd text: ocr.txt, ocr.html, ocr.xhtml, ocr.stext, ocr.stext.json\n"
427 #else
428 "\t\tocr'd text: ocr.txt, ocr.html, ocr.xhtml, ocr.stext, ocr.stext.json (disabled)\n"
429 #endif
430 "\t\tbitmap-wrapped-as-pdf: pclm, ocr.pdf\n"
431 "\n"
432 "\t-q\tbe quiet (don't print progress messages)\n"
433 "\t-s -\tshow extra information:\n"
434 "\t\tm - show memory use\n"
435 "\t\tt - show timings\n"
436 "\t\tf - show page features\n"
437 "\t\t5 - show md5 checksum of rendered image\n"
438 "\n"
439 "\t-R -\trotate clockwise (default: 0 degrees)\n"
440 "\t-r -\tresolution in dpi (default: 72)\n"
441 "\t-w -\twidth (in pixels) (maximum width if -r is specified)\n"
442 "\t-h -\theight (in pixels) (maximum height if -r is specified)\n"
443 "\t-f\tfit width and/or height exactly; ignore original aspect ratio\n"
444 "\t-b -\tuse named page box (MediaBox, CropBox, BleedBox, TrimBox, or ArtBox)\n"
445 "\t-B -\tmaximum band_height (pXm, pcl, pclm, ocr.pdf, ps, psd and png output only)\n"
446 #ifndef DISABLE_MUTHREADS
447 "\t-T -\tnumber of threads to use for rendering (banded mode only)\n"
448 #else
449 "\t-T -\tnumber of threads to use for rendering (disabled in this non-threading build)\n"
450 #endif
451 "\n"
452 "\t-W -\tpage width for EPUB layout\n"
453 "\t-H -\tpage height for EPUB layout\n"
454 "\t-S -\tfont size for EPUB layout\n"
455 "\t-U -\tfile name of user stylesheet for EPUB layout\n"
456 "\t-X\tdisable document styles for EPUB layout\n"
457 "\t-a\tdisable usage of accelerator file\n"
458 "\n"
459 "\t-c -\tcolorspace (mono, gray, grayalpha, rgb, rgba, cmyk, cmykalpha, filename of ICC profile)\n"
460 "\t-e -\tproof icc profile (filename of ICC profile)\n"
461 "\t-G -\tapply gamma correction\n"
462 "\t-I\tinvert colors\n"
463 "\n"
464 "\t-A -\tnumber of bits of antialiasing (0 to 8)\n"
465 "\t-A -/-\tnumber of bits of antialiasing (0 to 8) (graphics, text)\n"
466 "\t-l -\tminimum stroked line width (in pixels)\n"
467 "\t-K\tdo not draw text\n"
468 "\t-KK\tonly draw text\n"
469 "\t-D\tdisable use of display list\n"
470 "\t-i\tignore errors\n"
471 "\t-m -\tlimit memory usage in bytes\n"
472 "\t-L\tlow memory mode (avoid caching, clear objects after each page)\n"
473 #ifndef DISABLE_MUTHREADS
474 "\t-P\tparallel interpretation/rendering\n"
475 #else
476 "\t-P\tparallel interpretation/rendering (disabled in this non-threading build)\n"
477 #endif
478 "\t-N\tdisable ICC workflow (\"N\"o color management)\n"
479 "\t-O -\tControl spot/overprint rendering\n"
480 #if FZ_ENABLE_SPOT_RENDERING
481 "\t\t 0 = No spot rendering\n"
482 "\t\t 1 = Overprint simulation (default)\n"
483 "\t\t 2 = Full spot rendering\n"
484 #else
485 "\t\t 0 = No spot rendering (default)\n"
486 "\t\t 1 = Overprint simulation (Disabled in this build)\n"
487 "\t\t 2 = Full spot rendering (Disabled in this build)\n"
488 #endif
489 #ifndef OCR_DISABLED
490 "\t-t -\tSpecify language/script for OCR (default: eng)\n"
491 "\t-d -\tSpecify path for OCR files (default: rely on TESSDATA_PREFIX environment variable)\n"
492 "\t-k -{,-}\tSkew correction options. auto or angle {0=increase size, 1=maintain size, 2=decrease size}\n"
493 #else
494 "\t-t -\tSpecify language/script for OCR (default: eng) (disabled)\n"
495 "\t-d -\tSpecify path for OCR files (default: rely on TESSDATA_PREFIX environment variable) (disabled)\n"
496 "\t-k -{,-}\tSkew correction options. auto or angle {0=increase size, 1=maintain size, 2=decrease size} (disabled)\n"
497 #endif
498 "\n"
499 "\t-y l\tList the layer configs to stderr\n"
500 "\t-y -\tSelect layer config (by number)\n"
501 "\t-y -{,-}*\tSelect layer config (by number), and toggle the listed entries\n"
502 "\n"
503 "\t-Y\tList individual layers to stderr\n"
504 "\t-z -\tHide individual layer\n"
505 "\t-Z -\tShow individual layer\n"
506 "\n"
507 "\tpages\tcomma separated list of page numbers and ranges\n"
508 );
509 return 1;
510 }
511
512 static int gettime(void)
513 {
514 static struct timeval first;
515 static int once = 1;
516 struct timeval now;
517 if (once)
518 {
519 gettimeofday(&first, NULL);
520 once = 0;
521 }
522 gettimeofday(&now, NULL);
523 return (now.tv_sec - first.tv_sec) * 1000 + (now.tv_usec - first.tv_usec) / 1000;
524 }
525
526 static int has_percent_d(char *s)
527 {
528 /* find '%[0-9]*d' */
529 while (*s)
530 {
531 if (*s++ == '%')
532 {
533 while (*s >= '0' && *s <= '9')
534 ++s;
535 if (*s == 'd')
536 return 1;
537 }
538 }
539 return 0;
540 }
541
542 /* Output file level (as opposed to page level) headers */
543 static void
544 file_level_headers(fz_context *ctx)
545 {
546 if (output_format == OUT_STEXT_XML || output_format == OUT_TRACE || output_format == OUT_BBOX || output_format == OUT_OCR_STEXT_XML || output_format == OUT_XMLTEXT)
547 fz_write_printf(ctx, out, "<?xml version=\"1.0\"?>\n");
548
549 if (output_format == OUT_HTML || output_format == OUT_OCR_HTML)
550 fz_print_stext_header_as_html(ctx, out);
551 if (output_format == OUT_XHTML || output_format == OUT_OCR_XHTML)
552 fz_print_stext_header_as_xhtml(ctx, out);
553
554 if (output_format == OUT_STEXT_XML || output_format == OUT_TRACE || output_format == OUT_BBOX || output_format == OUT_OCR_STEXT_XML)
555 fz_write_printf(ctx, out, "<document filename=\"%s\">\n", filename);
556 if (output_format == OUT_STEXT_JSON || output_format == OUT_OCR_STEXT_JSON)
557 fz_write_printf(ctx, out, "{%q:%q,%q:[", "file", filename, "pages");
558
559 if (output_format == OUT_PS)
560 fz_write_ps_file_header(ctx, out);
561
562 if (output_format == OUT_PWG)
563 fz_write_pwg_file_header(ctx, out);
564
565 if (output_format == OUT_PCLM)
566 {
567 fz_pclm_options opts = { 0 };
568 fz_parse_pclm_options(ctx, &opts, "compression=flate");
569 bander = fz_new_pclm_band_writer(ctx, out, &opts);
570 }
571
572 if (output_format == OUT_OCR_PDF)
573 {
574 char options[300];
575 fz_pdfocr_options opts = { 0 };
576 fz_snprintf(options, sizeof(options), "compression=flate,ocr-language=%s", ocr_language);
577 if (ocr_datadir)
578 {
579 fz_strlcat(options, ",ocr-datadir=", sizeof (options));
580 fz_strlcat(options, ocr_datadir, sizeof (options));
581 }
582 fz_parse_pdfocr_options(ctx, &opts, options);
583 opts.skew_correct = skew_correct;
584 opts.skew_border = skew_border;
585 opts.skew_angle = skew_angle;
586 bander = fz_new_pdfocr_band_writer(ctx, out, &opts);
587 }
588 }
589
590 static void
591 file_level_trailers(fz_context *ctx)
592 {
593 if (output_format == OUT_STEXT_XML || output_format == OUT_TRACE || output_format == OUT_OCR_TRACE || output_format == OUT_BBOX || output_format == OUT_OCR_STEXT_XML)
594 fz_write_printf(ctx, out, "</document>\n");
595 if (output_format == OUT_STEXT_JSON || output_format == OUT_OCR_STEXT_JSON)
596 fz_write_printf(ctx, out, "]}");
597
598 if (output_format == OUT_HTML || output_format == OUT_OCR_HTML)
599 fz_print_stext_trailer_as_html(ctx, out);
600 if (output_format == OUT_XHTML || output_format == OUT_OCR_HTML)
601 fz_print_stext_trailer_as_xhtml(ctx, out);
602
603 if (output_format == OUT_PS)
604 fz_write_ps_file_trailer(ctx, out, output_pagenum);
605
606 if (output_format == OUT_PCLM || output_format == OUT_OCR_PDF)
607 {
608 fz_close_band_writer(ctx, bander);
609 fz_drop_band_writer(ctx, bander);
610 bander = NULL;
611 }
612 }
613
614 static void apply_kill_switch(fz_device *dev)
615 {
616 if (s_kill == 1)
617 {
618 /* kill all non-clipping text operators */
619 dev->fill_text = NULL;
620 dev->stroke_text = NULL;
621 dev->ignore_text = NULL;
622 }
623 else if (s_kill == 2)
624 {
625 /* kill all non-clipping path, image, and shading operators */
626 dev->fill_path = NULL;
627 dev->stroke_path = NULL;
628 dev->fill_shade = NULL;
629 dev->fill_image = NULL;
630 dev->fill_image_mask = NULL;
631 }
632 }
633
634 static void drawband(fz_context *ctx, fz_page *page, fz_display_list *list, fz_matrix ctm, fz_rect tbounds, fz_cookie *cookie, int band_start, fz_pixmap *pix, fz_bitmap **bit)
635 {
636 fz_device *dev = NULL;
637
638 fz_var(dev);
639
640 *bit = NULL;
641
642 fz_try(ctx)
643 {
644 if (pix->alpha)
645 fz_clear_pixmap(ctx, pix);
646 else
647 fz_clear_pixmap_with_value(ctx, pix, 255);
648
649 dev = fz_new_draw_device_with_proof(ctx, fz_identity, pix, proof_cs);
650 apply_kill_switch(dev);
651 if (lowmemory)
652 fz_enable_device_hints(ctx, dev, FZ_NO_CACHE);
653 if (alphabits_graphics == 0)
654 fz_enable_device_hints(ctx, dev, FZ_DONT_INTERPOLATE_IMAGES);
655 if (list)
656 fz_run_display_list(ctx, list, dev, ctm, tbounds, cookie);
657 else
658 fz_run_page(ctx, page, dev, ctm, cookie);
659 fz_close_device(ctx, dev);
660 fz_drop_device(ctx, dev);
661 dev = NULL;
662
663 if (invert)
664 fz_invert_pixmap(ctx, pix);
665 if (gamma_value != 1)
666 fz_gamma_pixmap(ctx, pix, gamma_value);
667
668 if (((output_format == OUT_PCL || output_format == OUT_PWG) && out_cs == CS_MONO) || (output_format == OUT_PBM) || (output_format == OUT_PKM))
669 *bit = fz_new_bitmap_from_pixmap_band(ctx, pix, NULL, band_start);
670 }
671 fz_catch(ctx)
672 {
673 fz_drop_device(ctx, dev);
674 fz_rethrow(ctx);
675 }
676 }
677
678 static void dodrawpage(fz_context *ctx, fz_page *page, fz_display_list *list, int pagenum, fz_cookie *cookie, int start, int interptime, char *fname, int bg, fz_separations *seps)
679 {
680 fz_rect mediabox;
681 fz_device *dev = NULL;
682
683 fz_var(dev);
684
685 if (output_file_per_page)
686 file_level_headers(ctx);
687
688 if (list)
689 mediabox = fz_bound_display_list(ctx, list);
690 else
691 mediabox = fz_bound_page_box(ctx, page, page_box);
692
693 if (output_format == OUT_TRACE || output_format == OUT_OCR_TRACE)
694 {
695 float zoom;
696 fz_matrix ctm;
697 fz_device *pre_ocr_dev = NULL;
698 fz_rect tmediabox;
699
700 zoom = resolution / 72;
701 ctm = fz_pre_scale(fz_rotate(rotation), zoom, zoom);
702 tmediabox = fz_transform_rect(mediabox, ctm);
703
704 fz_var(pre_ocr_dev);
705
706 fz_try(ctx)
707 {
708 fz_write_printf(ctx, out, "<page number=\"%d\" mediabox=\"%g %g %g %g\">\n",
709 pagenum, tmediabox.x0, tmediabox.y0, tmediabox.x1, tmediabox.y1);
710 dev = fz_new_trace_device(ctx, out);
711 apply_kill_switch(dev);
712 if (output_format == OUT_OCR_TRACE)
713 {
714 pre_ocr_dev = dev;
715 dev = NULL;
716 dev = fz_new_ocr_device(ctx, pre_ocr_dev, ctm, mediabox, 1, ocr_language, ocr_datadir, NULL, NULL);
717 }
718 if (lowmemory)
719 fz_enable_device_hints(ctx, dev, FZ_NO_CACHE);
720 if (list)
721 fz_run_display_list(ctx, list, dev, ctm, fz_infinite_rect, cookie);
722 else
723 fz_run_page(ctx, page, dev, ctm, cookie);
724 fz_close_device(ctx, dev);
725 fz_drop_device(ctx, dev);
726 dev = NULL;
727 fz_close_device(ctx, pre_ocr_dev);
728 fz_drop_device(ctx, pre_ocr_dev);
729 pre_ocr_dev = NULL;
730 fz_write_printf(ctx, out, "</page>\n");
731 }
732 fz_always(ctx)
733 {
734 fz_drop_device(ctx, pre_ocr_dev);
735 fz_drop_device(ctx, dev);
736 }
737 fz_catch(ctx)
738 {
739 fz_rethrow(ctx);
740 }
741 }
742
743 if (output_format == OUT_XMLTEXT)
744 {
745 fz_try(ctx)
746 {
747 float zoom;
748 fz_matrix ctm;
749 fz_rect tmediabox;
750
751 zoom = resolution / 72;
752 ctm = fz_pre_scale(fz_rotate(rotation), zoom, zoom);
753 tmediabox = fz_transform_rect(mediabox, ctm);
754
755 fz_write_printf(ctx, out, "<page mediabox=\"%g %g %g %g\">\n",
756 tmediabox.x0, tmediabox.y0, tmediabox.x1, tmediabox.y1);
757 dev = fz_new_xmltext_device(ctx, out);
758 apply_kill_switch(dev);
759 if (list)
760 fz_run_display_list(ctx, list, dev, ctm, fz_infinite_rect, cookie);
761 else
762 fz_run_page(ctx, page, dev, ctm, cookie);
763 fz_write_printf(ctx, out, "</page>\n");
764 fz_close_device(ctx, dev);
765 }
766 fz_always(ctx)
767 {
768 fz_drop_device(ctx, dev);
769 }
770 fz_catch(ctx)
771 {
772 fz_rethrow(ctx);
773 }
774 }
775
776 else if (output_format == OUT_BBOX)
777 {
778 fz_try(ctx)
779 {
780 fz_rect bbox = fz_empty_rect;
781 float zoom;
782 fz_matrix ctm;
783 fz_rect tmediabox;
784
785 zoom = resolution / 72;
786 ctm = fz_pre_scale(fz_rotate(rotation), zoom, zoom);
787 tmediabox = fz_transform_rect(mediabox, ctm);
788
789 dev = fz_new_bbox_device(ctx, &bbox);
790 apply_kill_switch(dev);
791 if (lowmemory)
792 fz_enable_device_hints(ctx, dev, FZ_NO_CACHE);
793 if (list)
794 fz_run_display_list(ctx, list, dev, ctm, fz_infinite_rect, cookie);
795 else
796 fz_run_page(ctx, page, dev, ctm, cookie);
797 fz_close_device(ctx, dev);
798 fz_write_printf(ctx, out, "<page bbox=\"%R\" mediabox=\"%R\" />\n", &bbox, &tmediabox);
799 }
800 fz_always(ctx)
801 {
802 fz_drop_device(ctx, dev);
803 }
804 fz_catch(ctx)
805 {
806 fz_rethrow(ctx);
807 }
808 }
809
810 else if (output_format == OUT_TEXT || output_format == OUT_HTML || output_format == OUT_XHTML || output_format == OUT_STEXT_XML || output_format == OUT_STEXT_JSON ||
811 output_format == OUT_OCR_TEXT || output_format == OUT_OCR_HTML || output_format == OUT_OCR_XHTML || output_format == OUT_OCR_STEXT_XML || output_format == OUT_OCR_STEXT_JSON)
812 {
813 fz_stext_page *text = NULL;
814 float zoom;
815 fz_matrix ctm;
816 fz_device *pre_ocr_dev = NULL;
817 fz_rect tmediabox;
818
819 zoom = resolution / 72;
820 ctm = fz_pre_scale(fz_rotate(rotation), zoom, zoom);
821
822 fz_var(text);
823 fz_var(pre_ocr_dev);
824
825 fz_try(ctx)
826 {
827 fz_stext_options stext_options = { 0 };
828
829 stext_options.flags = (output_format == OUT_HTML ||
830 output_format == OUT_XHTML ||
831 output_format == OUT_OCR_HTML ||
832 output_format == OUT_OCR_XHTML
833 ) ? FZ_STEXT_PRESERVE_IMAGES : 0;
834 stext_options.flags |= FZ_STEXT_CLIP;
835 stext_options.flags |= FZ_STEXT_ACCURATE_BBOXES;
836 stext_options.flags |= FZ_STEXT_COLLECT_STYLES;
837 if (output_format == OUT_STEXT_JSON || output_format == OUT_OCR_STEXT_JSON)
838 stext_options.flags |= FZ_STEXT_PRESERVE_SPANS;
839 tmediabox = fz_transform_rect(mediabox, ctm);
840 text = fz_new_stext_page(ctx, tmediabox);
841 dev = fz_new_stext_device(ctx, text, &stext_options);
842 apply_kill_switch(dev);
843 if (lowmemory)
844 fz_enable_device_hints(ctx, dev, FZ_NO_CACHE);
845 if (output_format == OUT_OCR_TEXT ||
846 output_format == OUT_OCR_STEXT_JSON ||
847 output_format == OUT_OCR_STEXT_XML ||
848 output_format == OUT_OCR_HTML ||
849 output_format == OUT_OCR_XHTML)
850 {
851 pre_ocr_dev = dev;
852 dev = NULL;
853 dev = fz_new_ocr_device(ctx, pre_ocr_dev, ctm, mediabox, 1, ocr_language, ocr_datadir, NULL, NULL);
854 }
855 if (list)
856 fz_run_display_list(ctx, list, dev, ctm, fz_infinite_rect, cookie);
857 else
858 fz_run_page(ctx, page, dev, ctm, cookie);
859 fz_close_device(ctx, dev);
860 fz_drop_device(ctx, dev);
861 dev = NULL;
862 fz_close_device(ctx, pre_ocr_dev);
863 fz_drop_device(ctx, pre_ocr_dev);
864 pre_ocr_dev = NULL;
865 if (output_format == OUT_STEXT_XML || output_format == OUT_OCR_STEXT_XML)
866 {
867 fz_print_stext_page_as_xml(ctx, out, text, pagenum);
868 }
869 else if (output_format == OUT_STEXT_JSON || output_format == OUT_OCR_STEXT_JSON)
870 {
871 static int first = 1;
872 if (first || output_file_per_page)
873 first = 0;
874 else
875 fz_write_string(ctx, out, ",");
876 fz_print_stext_page_as_json(ctx, out, text, 1);
877 }
878 else if (output_format == OUT_HTML || output_format == OUT_OCR_HTML)
879 {
880 fz_print_stext_page_as_html(ctx, out, text, pagenum);
881 }
882 else if (output_format == OUT_XHTML || output_format == OUT_OCR_XHTML)
883 {
884 fz_print_stext_page_as_xhtml(ctx, out, text, pagenum);
885 }
886 else if (output_format == OUT_TEXT || output_format == OUT_OCR_TEXT)
887 {
888 fz_print_stext_page_as_text(ctx, out, text);
889 fz_write_printf(ctx, out, "\f\n");
890 }
891 }
892 fz_always(ctx)
893 {
894 fz_drop_device(ctx, pre_ocr_dev);
895 fz_drop_device(ctx, dev);
896 fz_drop_stext_page(ctx, text);
897 }
898 fz_catch(ctx)
899 {
900 fz_rethrow(ctx);
901 }
902 }
903
904 #if FZ_ENABLE_PDF
905 else if (output_format == OUT_PDF)
906 {
907 fz_buffer *contents = NULL;
908 pdf_obj *resources = NULL;
909
910 fz_var(contents);
911 fz_var(resources);
912
913 fz_try(ctx)
914 {
915 /* We are ignoring ctm here. Understandable in a way, as resolution makes no sense
916 * when writing PDFs. Rotation is taken care of by the pdf_add_page call. */
917 pdf_obj *page_obj;
918
919 dev = pdf_page_write(ctx, pdfout, mediabox, &resources, &contents);
920 apply_kill_switch(dev);
921 if (list)
922 fz_run_display_list(ctx, list, dev, fz_identity, fz_infinite_rect, cookie);
923 else
924 fz_run_page(ctx, page, dev, fz_identity, cookie);
925 fz_close_device(ctx, dev);
926 fz_drop_device(ctx, dev);
927 dev = NULL;
928
929 page_obj = pdf_add_page(ctx, pdfout, mediabox, rotation, resources, contents);
930 pdf_insert_page(ctx, pdfout, -1, page_obj);
931 pdf_drop_obj(ctx, page_obj);
932 }
933 fz_always(ctx)
934 {
935 pdf_drop_obj(ctx, resources);
936 fz_drop_buffer(ctx, contents);
937 fz_drop_device(ctx, dev);
938 }
939 fz_catch(ctx)
940 {
941 fz_rethrow(ctx);
942 }
943 }
944 #endif
945
946 else if (output_format == OUT_SVG)
947 {
948 float zoom;
949 fz_matrix ctm;
950 fz_rect tbounds;
951 char buf[512];
952 fz_output *outs = NULL;
953
954 fz_var(outs);
955
956 zoom = resolution / 72;
957 ctm = fz_pre_rotate(fz_scale(zoom, zoom), rotation);
958 tbounds = fz_transform_rect(mediabox, ctm);
959
960 fz_try(ctx)
961 {
962 if (!output)
963 outs = fz_stdout(ctx);
964 else
965 {
966 fz_format_output_path(ctx, buf, sizeof buf, output, pagenum);
967 outs = fz_new_output_with_path(ctx, buf, 0);
968 }
969
970 dev = fz_new_svg_device(ctx, outs, tbounds.x1-tbounds.x0, tbounds.y1-tbounds.y0, FZ_SVG_TEXT_AS_PATH, 1);
971 apply_kill_switch(dev);
972 if (lowmemory)
973 fz_enable_device_hints(ctx, dev, FZ_NO_CACHE);
974 if (list)
975 fz_run_display_list(ctx, list, dev, ctm, tbounds, cookie);
976 else
977 fz_run_page(ctx, page, dev, ctm, cookie);
978 fz_close_device(ctx, dev);
979 fz_close_output(ctx, outs);
980 }
981 fz_always(ctx)
982 {
983 fz_drop_device(ctx, dev);
984 fz_drop_output(ctx, outs);
985 }
986 fz_catch(ctx)
987 {
988 fz_rethrow(ctx);
989 }
990 }
991 else
992 {
993 float zoom;
994 fz_matrix ctm;
995 fz_rect tbounds;
996 fz_irect ibounds;
997 fz_pixmap *pix = NULL;
998 int w, h;
999 fz_bitmap *bit = NULL;
1000
1001 fz_var(pix);
1002 fz_var(bander);
1003 fz_var(bit);
1004
1005 zoom = resolution / 72;
1006 ctm = fz_pre_scale(fz_rotate(rotation), zoom, zoom);
1007
1008 tbounds = fz_transform_rect(mediabox, ctm);
1009 ibounds = fz_round_rect(tbounds);
1010
1011 /* Make local copies of our width/height */
1012 w = width;
1013 h = height;
1014
1015 /* If a resolution is specified, check to see whether w/h are
1016 * exceeded; if not, unset them. */
1017 if (res_specified)
1018 {
1019 int t;
1020 t = ibounds.x1 - ibounds.x0;
1021 if (w && t <= w)
1022 w = 0;
1023 t = ibounds.y1 - ibounds.y0;
1024 if (h && t <= h)
1025 h = 0;
1026 }
1027
1028 /* Now w or h will be 0 unless they need to be enforced. */
1029 if (w || h)
1030 {
1031 float scalex = w / (tbounds.x1 - tbounds.x0);
1032 float scaley = h / (tbounds.y1 - tbounds.y0);
1033 fz_matrix scale_mat;
1034
1035 if (fit)
1036 {
1037 if (w == 0)
1038 scalex = 1.0f;
1039 if (h == 0)
1040 scaley = 1.0f;
1041 }
1042 else
1043 {
1044 if (w == 0)
1045 scalex = scaley;
1046 if (h == 0)
1047 scaley = scalex;
1048 }
1049 if (!fit)
1050 {
1051 if (scalex > scaley)
1052 scalex = scaley;
1053 else
1054 scaley = scalex;
1055 }
1056 scale_mat = fz_scale(scalex, scaley);
1057 ctm = fz_concat(ctm, scale_mat);
1058 tbounds = fz_transform_rect(mediabox, ctm);
1059 }
1060 ibounds = fz_round_rect(tbounds);
1061 tbounds = fz_rect_from_irect(ibounds);
1062
1063 fz_try(ctx)
1064 {
1065 fz_irect band_ibounds = ibounds;
1066 int band, bands = 1;
1067 int totalheight = ibounds.y1 - ibounds.y0;
1068 int drawheight = totalheight;
1069
1070 if (band_height != 0)
1071 {
1072 /* Banded rendering; we'll only render to a
1073 * given height at a time. */
1074 drawheight = band_height;
1075 if (totalheight > band_height)
1076 band_ibounds.y1 = band_ibounds.y0 + band_height;
1077 bands = (totalheight + band_height-1)/band_height;
1078 tbounds.y1 = tbounds.y0 + band_height + 2;
1079 DEBUG_THREADS(("Using %d Bands\n", bands));
1080 }
1081
1082 if (num_workers > 0)
1083 {
1084 for (band = 0; band < fz_mini(num_workers, bands); band++)
1085 {
1086 workers[band].band = band;
1087 workers[band].error = 0;
1088 workers[band].ctm = ctm;
1089 workers[band].tbounds = tbounds;
1090 tbounds.y0 += band_height;
1091 tbounds.y1 += band_height;
1092 memset(&workers[band].cookie, 0, sizeof(fz_cookie));
1093 workers[band].list = list;
1094 workers[band].pix = fz_new_pixmap_with_bbox(ctx, colorspace, band_ibounds, seps, alpha);
1095 workers[band].pix->y += band * band_height;
1096 fz_set_pixmap_resolution(ctx, workers[band].pix, resolution, resolution);
1097 workers[band].running = 1;
1098 #ifndef DISABLE_MUTHREADS
1099 DEBUG_THREADS(("Worker %d, Pre-triggering band %d\n", band, band));
1100 mu_trigger_semaphore(&workers[band].start);
1101 #endif
1102 }
1103 pix = workers[0].pix;
1104 }
1105 else
1106 {
1107 pix = fz_new_pixmap_with_bbox(ctx, colorspace, band_ibounds, seps, alpha);
1108 fz_set_pixmap_resolution(ctx, pix, resolution, resolution);
1109 }
1110
1111 /* Output any page level headers (for banded formats) */
1112 if (output)
1113 {
1114 if (output_format == OUT_PGM || output_format == OUT_PPM || output_format == OUT_PNM)
1115 bander = fz_new_pnm_band_writer(ctx, out);
1116 else if (output_format == OUT_PAM)
1117 bander = fz_new_pam_band_writer(ctx, out);
1118 else if (output_format == OUT_PNG)
1119 bander = fz_new_png_band_writer(ctx, out);
1120 else if (output_format == OUT_PBM)
1121 bander = fz_new_pbm_band_writer(ctx, out);
1122 else if (output_format == OUT_PKM)
1123 bander = fz_new_pkm_band_writer(ctx, out);
1124 else if (output_format == OUT_PS)
1125 bander = fz_new_ps_band_writer(ctx, out);
1126 else if (output_format == OUT_PSD)
1127 bander = fz_new_psd_band_writer(ctx, out);
1128 else if (output_format == OUT_PWG)
1129 {
1130 if (out_cs == CS_MONO)
1131 bander = fz_new_mono_pwg_band_writer(ctx, out, NULL);
1132 else
1133 bander = fz_new_pwg_band_writer(ctx, out, NULL);
1134 }
1135 else if (output_format == OUT_PCL)
1136 {
1137 if (out_cs == CS_MONO)
1138 bander = fz_new_mono_pcl_band_writer(ctx, out, NULL);
1139 else
1140 bander = fz_new_color_pcl_band_writer(ctx, out, NULL);
1141 }
1142 if (bander)
1143 {
1144 fz_write_header(ctx, bander, pix->w, totalheight, pix->n, pix->alpha, pix->xres, pix->yres, output_pagenum++, pix->colorspace, pix->seps);
1145 }
1146 }
1147 if (output_format == OUT_J2K && bands > 1)
1148 {
1149 fz_throw(ctx, FZ_ERROR_ARGUMENT, "Can't band with J2k output!");
1150 }
1151
1152 for (band = 0; band < bands; band++)
1153 {
1154 if (num_workers > 0)
1155 {
1156 worker_t *work = &workers[band % num_workers];
1157 #ifndef DISABLE_MUTHREADS
1158 DEBUG_THREADS(("Waiting for worker %d to complete band %d\n", work->num, band));
1159 mu_wait_semaphore(&work->stop);
1160 #endif
1161 work->running = 0;
1162 cookie->errors += work->cookie.errors;
1163 pix = work->pix;
1164 bit = work->bit;
1165 work->bit = NULL;
1166
1167 if (work->error)
1168 fz_throw(ctx, FZ_ERROR_GENERIC, "worker %d failed to render band %d", work->num, band);
1169 }
1170 else
1171 drawband(ctx, page, list, ctm, tbounds, cookie, band * band_height, pix, &bit);
1172
1173 if (output)
1174 {
1175 if (bander && (pix || bit))
1176 fz_write_band(ctx, bander, bit ? bit->stride : pix->stride, drawheight, bit ? bit->samples : pix->samples);
1177 #if FZ_ENABLE_JPX
1178 if (output_format == OUT_J2K)
1179 {
1180 fz_write_pixmap_as_jpx(ctx, out, pix, 80);
1181 }
1182 #else
1183 fz_throw(ctx, FZ_ERROR_GENERIC, "JPX support disabled");
1184 #endif
1185 fz_drop_bitmap(ctx, bit);
1186 bit = NULL;
1187 }
1188
1189 if (num_workers > 0 && band + num_workers < bands)
1190 {
1191 worker_t *work = &workers[band % num_workers];
1192 work->band = band + num_workers;
1193 work->pix->y = band_ibounds.y0 + work->band * band_height;
1194 work->ctm = ctm;
1195 work->tbounds = tbounds;
1196 memset(&work->cookie, 0, sizeof(fz_cookie));
1197 work->running = 1;
1198 #ifndef DISABLE_MUTHREADS
1199 DEBUG_THREADS(("Triggering worker %d for band %d\n", work->num, work->band));
1200 mu_trigger_semaphore(&work->start);
1201 #endif
1202 }
1203 if (num_workers <= 0)
1204 pix->y += band_height;
1205 tbounds.y0 += band_height;
1206 tbounds.y1 += band_height;
1207 }
1208
1209 if (output_format != OUT_PCLM && output_format != OUT_OCR_PDF)
1210 fz_close_band_writer(ctx, bander);
1211
1212 /* FIXME */
1213 if (showmd5 && pix)
1214 {
1215 unsigned char digest[16];
1216 int i;
1217
1218 fz_md5_pixmap(ctx, pix, digest);
1219 fprintf(stderr, " ");
1220 for (i = 0; i < 16; i++)
1221 fprintf(stderr, "%02x", digest[i]);
1222 }
1223 }
1224 fz_always(ctx)
1225 {
1226 if (output_format != OUT_PCLM && output_format != OUT_OCR_PDF)
1227 {
1228 fz_drop_band_writer(ctx, bander);
1229 /* bander must be set to NULL to avoid use-after-frees. A use-after-free
1230 * would occur when a valid page was followed by a page with invalid
1231 * pixmap dimensions, causing bander -- a static -- to point to previously
1232 * freed memory instead of a new band_writer. */
1233 bander = NULL;
1234 }
1235 fz_drop_bitmap(ctx, bit);
1236 bit = NULL;
1237 if (num_workers > 0)
1238 {
1239 int i;
1240 DEBUG_THREADS(("Stopping workers and removing their pixmaps\n"));
1241 for (i = 0; i < num_workers; i++)
1242 {
1243 if (workers[i].running)
1244 {
1245 #ifndef DISABLE_MUTHREADS
1246 DEBUG_THREADS(("Waiting on worker %d to finish processing\n", i));
1247 mu_wait_semaphore(&workers[i].stop);
1248 #endif
1249 workers[i].running = 0;
1250 }
1251 else
1252 DEBUG_THREADS(("Worker %d not processing anything\n", i));
1253 fz_drop_pixmap(ctx, workers[i].pix);
1254 workers[i].pix = NULL;
1255 }
1256 }
1257 else
1258 fz_drop_pixmap(ctx, pix);
1259 }
1260 fz_catch(ctx)
1261 {
1262 if (output_format == OUT_PCLM || output_format == OUT_OCR_PDF)
1263 {
1264 fz_drop_band_writer(ctx, bander);
1265 bander = NULL;
1266 }
1267 fz_rethrow(ctx);
1268 }
1269 }
1270
1271 if (output_file_per_page)
1272 file_level_trailers(ctx);
1273
1274 if (showtime)
1275 {
1276 int end = gettime();
1277 int diff = end - start;
1278
1279 if (bg)
1280 {
1281 if (diff + interptime < timing.min)
1282 {
1283 timing.min = diff + interptime;
1284 timing.mininterp = interptime;
1285 timing.minpage = pagenum;
1286 timing.minfilename = fname;
1287 }
1288 if (diff + interptime > timing.max)
1289 {
1290 timing.max = diff + interptime;
1291 timing.maxinterp = interptime;
1292 timing.maxpage = pagenum;
1293 timing.maxfilename = fname;
1294 }
1295 timing.count ++;
1296
1297 fprintf(stderr, " %dms (interpretation) %dms (rendering) %dms (total)", interptime, diff, diff + interptime);
1298 }
1299 else
1300 {
1301 if (diff < timing.min)
1302 {
1303 timing.min = diff;
1304 timing.minpage = pagenum;
1305 timing.minfilename = fname;
1306 }
1307 if (diff > timing.max)
1308 {
1309 timing.max = diff;
1310 timing.maxpage = pagenum;
1311 timing.maxfilename = fname;
1312 }
1313 timing.total += diff;
1314 timing.count ++;
1315
1316 fprintf(stderr, " %dms", diff);
1317 }
1318 }
1319
1320 if (!quiet || showfeatures || showtime || showmd5)
1321 fprintf(stderr, "\n");
1322
1323 if (lowmemory)
1324 fz_empty_store(ctx);
1325
1326 if (showmemory)
1327 fz_dump_glyph_cache_stats(ctx, fz_stderr(ctx));
1328
1329 fz_flush_warnings(ctx);
1330
1331 if (cookie->errors)
1332 errored = 1;
1333 }
1334
1335 static void bgprint_flush(void)
1336 {
1337 if (!bgprint.active || !bgprint.started)
1338 return;
1339
1340 #ifndef DISABLE_MUTHREADS
1341 mu_wait_semaphore(&bgprint.stop);
1342 #endif
1343 bgprint.started = 0;
1344 }
1345
1346 static void drawpage(fz_context *ctx, fz_document *doc, int pagenum)
1347 {
1348 fz_page *page;
1349 fz_display_list *list = NULL;
1350 fz_device *dev = NULL;
1351 int start;
1352 fz_cookie cookie = { 0 };
1353 fz_separations *seps = NULL;
1354 const char *features = "";
1355
1356 fz_var(list);
1357 fz_var(dev);
1358 fz_var(seps);
1359
1360 start = (showtime ? gettime() : 0);
1361
1362 if (output_file_per_page)
1363 {
1364 char text_buffer[512];
1365
1366 bgprint_flush();
1367 if (out)
1368 {
1369 fz_close_output(ctx, out);
1370 fz_drop_output(ctx, out);
1371 }
1372 fz_format_output_path(ctx, text_buffer, sizeof text_buffer, output, pagenum);
1373 out = fz_new_output_with_path(ctx, text_buffer, 0);
1374 }
1375
1376 page = fz_load_page(ctx, doc, pagenum - 1);
1377
1378 if (spots != SPOTS_NONE)
1379 {
1380 fz_try(ctx)
1381 {
1382 seps = fz_page_separations(ctx, page);
1383 if (seps)
1384 {
1385 int i, n = fz_count_separations(ctx, seps);
1386 if (spots == SPOTS_FULL)
1387 for (i = 0; i < n; i++)
1388 fz_set_separation_behavior(ctx, seps, i, FZ_SEPARATION_SPOT);
1389 else
1390 for (i = 0; i < n; i++)
1391 fz_set_separation_behavior(ctx, seps, i, FZ_SEPARATION_COMPOSITE);
1392 }
1393 else if (fz_page_uses_overprint(ctx, page))
1394 {
1395 /* This page uses overprint, so we need an empty
1396 * sep object to force the overprint simulation on. */
1397 seps = fz_new_separations(ctx, 0);
1398 }
1399 else if (oi && fz_colorspace_n(ctx, oi) != fz_colorspace_n(ctx, colorspace))
1400 {
1401 /* We have an output intent, and it's incompatible
1402 * with the colorspace our device needs. Force the
1403 * overprint simulation on, because this ensures that
1404 * we 'simulate' the output intent too. */
1405 seps = fz_new_separations(ctx, 0);
1406 }
1407 }
1408 fz_catch(ctx)
1409 {
1410 fz_drop_page(ctx, page);
1411 fz_rethrow(ctx);
1412 }
1413 }
1414
1415 if (uselist)
1416 {
1417 fz_try(ctx)
1418 {
1419 list = fz_new_display_list(ctx, fz_bound_page_box(ctx, page, page_box));
1420 dev = fz_new_list_device(ctx, list);
1421 if (lowmemory)
1422 fz_enable_device_hints(ctx, dev, FZ_NO_CACHE);
1423 fz_run_page(ctx, page, dev, fz_identity, &cookie);
1424 fz_close_device(ctx, dev);
1425 }
1426 fz_always(ctx)
1427 {
1428 fz_drop_device(ctx, dev);
1429 dev = NULL;
1430 }
1431 fz_catch(ctx)
1432 {
1433 fz_drop_display_list(ctx, list);
1434 fz_drop_separations(ctx, seps);
1435 fz_drop_page(ctx, page);
1436 fz_rethrow(ctx);
1437 }
1438
1439 if (bgprint.active && showtime)
1440 {
1441 int end = gettime();
1442 start = end - start;
1443 }
1444 }
1445
1446 if (showfeatures)
1447 {
1448 int iscolor;
1449 dev = fz_new_test_device(ctx, &iscolor, 0.02f, 0, NULL);
1450 apply_kill_switch(dev);
1451 if (lowmemory)
1452 fz_enable_device_hints(ctx, dev, FZ_NO_CACHE);
1453 fz_try(ctx)
1454 {
1455 if (list)
1456 fz_run_display_list(ctx, list, dev, fz_identity, fz_infinite_rect, NULL);
1457 else
1458 fz_run_page(ctx, page, dev, fz_identity, &cookie);
1459 fz_close_device(ctx, dev);
1460 }
1461 fz_always(ctx)
1462 {
1463 fz_drop_device(ctx, dev);
1464 dev = NULL;
1465 }
1466 fz_catch(ctx)
1467 {
1468 fz_drop_display_list(ctx, list);
1469 fz_drop_separations(ctx, seps);
1470 fz_drop_page(ctx, page);
1471 fz_rethrow(ctx);
1472 }
1473 features = iscolor ? " color" : " grayscale";
1474 }
1475
1476 if (bgprint.active)
1477 {
1478 bgprint_flush();
1479 if (bgprint.error)
1480 {
1481 fz_drop_display_list(ctx, list);
1482 fz_drop_separations(ctx, seps);
1483 fz_drop_page(ctx, page);
1484
1485 /* it failed, do not continue trying */
1486 bgprint.active = 0;
1487 }
1488 else if (bgprint.active)
1489 {
1490 if (!quiet || showfeatures || showtime || showmd5)
1491 fprintf(stderr, "page %s %d%s", filename, pagenum, features);
1492
1493 bgprint.started = 1;
1494 bgprint.page = page;
1495 bgprint.list = list;
1496 bgprint.seps = seps;
1497 bgprint.filename = filename;
1498 bgprint.pagenum = pagenum;
1499 bgprint.interptime = start;
1500 bgprint.error = 0;
1501 #ifndef DISABLE_MUTHREADS
1502 mu_trigger_semaphore(&bgprint.start);
1503 #else
1504 fz_drop_display_list(ctx, list);
1505 fz_drop_separations(ctx, seps);
1506 fz_drop_page(ctx, page);
1507 #endif
1508 }
1509 }
1510 else
1511 {
1512 if (!quiet || showfeatures || showtime || showmd5)
1513 fprintf(stderr, "page %s %d%s", filename, pagenum, features);
1514 fz_try(ctx)
1515 dodrawpage(ctx, page, list, pagenum, &cookie, start, 0, filename, 0, seps);
1516 fz_always(ctx)
1517 {
1518 fz_drop_display_list(ctx, list);
1519 fz_drop_separations(ctx, seps);
1520 fz_drop_page(ctx, page);
1521 }
1522 fz_catch(ctx)
1523 {
1524 fz_rethrow(ctx);
1525 }
1526 }
1527 }
1528
1529 static void drawrange(fz_context *ctx, fz_document *doc, const char *range)
1530 {
1531 int page, spage, epage, pagecount;
1532
1533 pagecount = fz_count_pages(ctx, doc);
1534
1535 while ((range = fz_parse_page_range(ctx, range, &spage, &epage, pagecount)))
1536 {
1537 if (spage < epage)
1538 for (page = spage; page <= epage; page++)
1539 {
1540 fz_try(ctx)
1541 drawpage(ctx, doc, page);
1542 fz_catch(ctx)
1543 {
1544 if (ignore_errors)
1545 fz_warn(ctx, "ignoring error on page %d in '%s'", page, filename);
1546 else
1547 fz_rethrow(ctx);
1548 }
1549 }
1550 else
1551 for (page = spage; page >= epage; page--)
1552 {
1553 fz_try(ctx)
1554 drawpage(ctx, doc, page);
1555 fz_catch(ctx)
1556 {
1557 if (ignore_errors)
1558 fz_warn(ctx, "ignoring error on page %d in '%s'", page, filename);
1559 else
1560 fz_rethrow(ctx);
1561 }
1562 }
1563 }
1564 }
1565
1566 static int
1567 parse_colorspace(const char *name)
1568 {
1569 int i;
1570
1571 for (i = 0; i < (int)nelem(cs_name_table); i++)
1572 {
1573 if (!strcmp(name, cs_name_table[i].name))
1574 return cs_name_table[i].colorspace;
1575 }
1576
1577 /* Assume ICC. We will error out later if not the case. */
1578 icc_filename = name;
1579 return CS_ICC;
1580 }
1581
1582 typedef struct
1583 {
1584 size_t size;
1585 #if defined(_M_IA64) || defined(_M_AMD64)
1586 size_t align;
1587 #endif
1588 } trace_header;
1589
1590 typedef struct
1591 {
1592 size_t current;
1593 size_t peak;
1594 size_t total;
1595 size_t allocs;
1596 size_t mem_limit;
1597 size_t alloc_limit;
1598 } trace_info;
1599
1600 static void *hit_limit(void *val)
1601 {
1602 return val;
1603 }
1604
1605 static void *hit_memory_limit(trace_info *info, int is_malloc, size_t oldsize, size_t size)
1606 {
1607 if (is_malloc)
1608 printf("Memory limit (%zu) hit upon malloc(%zu) when %zu already allocated.\n", info->mem_limit, size, info->current);
1609 else
1610 printf("Memory limit (%zu) hit upon realloc(%zu) from %zu bytes when %zu already allocated.\n", info->mem_limit, size, oldsize, info->current);
1611 return hit_limit(NULL);
1612 }
1613
1614
1615 static void *hit_alloc_limit(trace_info *info, int is_malloc, size_t oldsize, size_t size)
1616 {
1617 if (is_malloc)
1618 printf("Allocation limit (%zu) hit upon malloc(%zu) when %zu already allocated.\n", info->alloc_limit, size, info->current);
1619 else
1620 printf("Allocation limit (%zu) hit upon realloc(%zu) from %zu bytes when %zu already allocated.\n", info->alloc_limit, size, oldsize, info->current);
1621 return hit_limit(NULL);
1622 }
1623
1624 static void *
1625 trace_malloc(void *arg, size_t size)
1626 {
1627 trace_info *info = (trace_info *) arg;
1628 trace_header *p;
1629 if (size == 0)
1630 return NULL;
1631 if (size > SIZE_MAX - sizeof(trace_header))
1632 return NULL;
1633 if (info->mem_limit > 0 && size > info->mem_limit - info->current)
1634 return hit_memory_limit(info, 1, 0, size);
1635 if (info->alloc_limit > 0 && info->allocs > info->alloc_limit)
1636 return hit_alloc_limit(info, 1, 0, size);
1637 p = malloc(size + sizeof(trace_header));
1638 if (p == NULL)
1639 return NULL;
1640 p[0].size = size;
1641 info->current += size;
1642 info->total += size;
1643 if (info->current > info->peak)
1644 info->peak = info->current;
1645 info->allocs++;
1646 return (void *)&p[1];
1647 }
1648
1649 static void
1650 trace_free(void *arg, void *p_)
1651 {
1652 trace_info *info = (trace_info *) arg;
1653 trace_header *p = (trace_header *)p_;
1654
1655 if (p == NULL)
1656 return;
1657 info->current -= p[-1].size;
1658 free(&p[-1]);
1659 }
1660
1661 static void *
1662 trace_realloc(void *arg, void *p_, size_t size)
1663 {
1664 trace_info *info = (trace_info *) arg;
1665 trace_header *p = (trace_header *)p_;
1666 size_t oldsize;
1667
1668 if (size == 0)
1669 {
1670 trace_free(arg, p_);
1671 return NULL;
1672 }
1673 if (p == NULL)
1674 return trace_malloc(arg, size);
1675 if (size > SIZE_MAX - sizeof(trace_header))
1676 return NULL;
1677 oldsize = p[-1].size;
1678 if (info->mem_limit > 0 && size > info->mem_limit - info->current + oldsize)
1679 return hit_memory_limit(info, 0, oldsize, size);
1680 if (info->alloc_limit > 0 && info->allocs > info->alloc_limit)
1681 return hit_alloc_limit(info, 0, oldsize, size);
1682 p = realloc(&p[-1], size + sizeof(trace_header));
1683 if (p == NULL)
1684 return NULL;
1685 info->current += size - oldsize;
1686 if (size > oldsize)
1687 info->total += size - oldsize;
1688 if (info->current > info->peak)
1689 info->peak = info->current;
1690 p[0].size = size;
1691 info->allocs++;
1692 return &p[1];
1693 }
1694
1695 #ifndef DISABLE_MUTHREADS
1696 static void worker_thread(void *arg)
1697 {
1698 worker_t *me = (worker_t *)arg;
1699 int band;
1700
1701 do
1702 {
1703 DEBUG_THREADS(("Worker %d waiting\n", me->num));
1704 mu_wait_semaphore(&me->start);
1705 band = me->band;
1706 DEBUG_THREADS(("Worker %d woken for band %d\n", me->num, band));
1707 if (band >= 0)
1708 {
1709 fz_try(me->ctx)
1710 {
1711 drawband(me->ctx, NULL, me->list, me->ctm, me->tbounds, &me->cookie, band * band_height, me->pix, &me->bit);
1712 DEBUG_THREADS(("Worker %d completed band %d\n", me->num, band));
1713 }
1714 fz_catch(me->ctx)
1715 {
1716 DEBUG_THREADS(("Worker %d failed on band %d\n", me->num, band));
1717 me->error = 1;
1718 }
1719 }
1720 mu_trigger_semaphore(&me->stop);
1721 }
1722 while (band >= 0);
1723 DEBUG_THREADS(("Worker %d shutting down\n", me->num));
1724 }
1725
1726 static void bgprint_worker(void *arg)
1727 {
1728 fz_cookie cookie = { 0 };
1729 int pagenum;
1730
1731 (void)arg;
1732
1733 do
1734 {
1735 DEBUG_THREADS(("BGPrint waiting\n"));
1736 mu_wait_semaphore(&bgprint.start);
1737 pagenum = bgprint.pagenum;
1738 DEBUG_THREADS(("BGPrint woken for pagenum %d\n", pagenum));
1739 if (pagenum >= 0)
1740 {
1741 int start = gettime();
1742 memset(&cookie, 0, sizeof(cookie));
1743 fz_try(bgprint.ctx)
1744 {
1745 dodrawpage(bgprint.ctx, bgprint.page, bgprint.list, pagenum, &cookie, start, bgprint.interptime, bgprint.filename, 1, bgprint.seps);
1746 DEBUG_THREADS(("BGPrint completed page %d\n", pagenum));
1747 }
1748 fz_always(bgprint.ctx)
1749 {
1750 fz_drop_display_list(bgprint.ctx, bgprint.list);
1751 fz_drop_separations(bgprint.ctx, bgprint.seps);
1752 fz_drop_page(bgprint.ctx, bgprint.page);
1753 }
1754 fz_catch(bgprint.ctx)
1755 {
1756 DEBUG_THREADS(("BGPrint failed on page %d\n", pagenum));
1757 bgprint.error = 1;
1758 }
1759
1760 }
1761 mu_trigger_semaphore(&bgprint.stop);
1762 }
1763 while (pagenum >= 0);
1764 DEBUG_THREADS(("BGPrint shutting down\n"));
1765 }
1766 #endif
1767
1768 static inline int iswhite(int ch)
1769 {
1770 return
1771 ch == '\011' || ch == '\012' ||
1772 ch == '\014' || ch == '\015' || ch == '\040';
1773 }
1774
1775 static void list_layers(fz_context *ctx, fz_document *doc)
1776 {
1777 #if FZ_ENABLE_PDF
1778 pdf_document *pdoc = pdf_specifics(ctx, doc);
1779 int k, n = pdf_count_layers(ctx, pdoc);
1780 for (k = 0; k < n; ++k) {
1781 const char *name = pdf_layer_name(ctx, pdoc, k);
1782 int state = pdf_layer_is_enabled(ctx, pdoc, k);
1783 fprintf(stderr, "layer %d (%s): %s\n", k+1, state ? "on" : "off", name);
1784 }
1785 #endif
1786 }
1787
1788 static void toggle_layers(fz_context *ctx, fz_document *doc)
1789 {
1790 #if FZ_ENABLE_PDF
1791 pdf_document *pdoc = pdf_specifics(ctx, doc);
1792 int i, k, n;
1793
1794 n = pdf_count_layers(ctx, pdoc);
1795 for (i = 0; i < layer_off_len; ++i)
1796 {
1797 if (layer_off[i] == -1)
1798 for (k = 0; k < n; ++k)
1799 pdf_enable_layer(ctx, pdoc, k, 0);
1800 else if (layer_off[i] >= 1 && layer_off[i] <= n)
1801 pdf_enable_layer(ctx, pdoc, layer_off[i] - 1, 0);
1802 else
1803 fprintf(stderr, "invalid layer: %d\n", layer_off[i]);
1804 }
1805
1806 for (i = 0; i < layer_on_len; ++i)
1807 {
1808 if (layer_on[i] == -1)
1809 for (k = 0; k < n; ++k)
1810 pdf_enable_layer(ctx, pdoc, k, 1);
1811 else if (layer_on[i] >= 1 && layer_on[i] <= n)
1812 pdf_enable_layer(ctx, pdoc, layer_on[i] - 1, 1);
1813 else
1814 fprintf(stderr, "invalid layer: %d\n", layer_on[i]);
1815 }
1816 #endif
1817 }
1818
1819 static void apply_layer_config(fz_context *ctx, fz_document *doc, const char *lc)
1820 {
1821 #if FZ_ENABLE_PDF
1822 pdf_document *pdoc = pdf_specifics(ctx, doc);
1823 int config;
1824 int n, j;
1825 pdf_layer_config info;
1826
1827 if (!pdoc)
1828 {
1829 fz_warn(ctx, "Only PDF files have layers");
1830 return;
1831 }
1832
1833 while (iswhite(*lc))
1834 lc++;
1835
1836 if (*lc == 0 || *lc == 'l')
1837 {
1838 int num_configs = pdf_count_layer_configs(ctx, pdoc);
1839
1840 fprintf(stderr, "Layer configs:\n");
1841 for (config = 0; config < num_configs; config++)
1842 {
1843 fprintf(stderr, " %s%d:", config < 10 ? " " : "", config);
1844 pdf_layer_config_info(ctx, pdoc, config, &info);
1845 if (info.name)
1846 fprintf(stderr, " Name=\"%s\"", info.name);
1847 if (info.creator)
1848 fprintf(stderr, " Creator=\"%s\"", info.creator);
1849 fprintf(stderr, "\n");
1850 }
1851 return;
1852 }
1853
1854 /* Read the config number */
1855 if (*lc < '0' || *lc > '9')
1856 {
1857 fprintf(stderr, "cannot find number expected for -y\n");
1858 return;
1859 }
1860 config = fz_atoi(lc);
1861 pdf_select_layer_config(ctx, pdoc, config);
1862
1863 while (*lc)
1864 {
1865 int item;
1866
1867 /* Skip over the last number we read (in the fz_atoi) */
1868 while (*lc >= '0' && *lc <= '9')
1869 lc++;
1870 while (iswhite(*lc))
1871 lc++;
1872 if (*lc != ',')
1873 break;
1874 lc++;
1875 while (iswhite(*lc))
1876 lc++;
1877 if (*lc < '0' || *lc > '9')
1878 {
1879 fprintf(stderr, "Expected a number for UI item to toggle\n");
1880 return;
1881 }
1882 item = fz_atoi(lc);
1883 pdf_toggle_layer_config_ui(ctx, pdoc, item);
1884 }
1885
1886 /* Now list the final state of the config */
1887 fprintf(stderr, "Layer Config %d:\n", config);
1888 pdf_layer_config_info(ctx, pdoc, config, &info);
1889 if (info.name)
1890 fprintf(stderr, " Name=\"%s\"", info.name);
1891 if (info.creator)
1892 fprintf(stderr, " Creator=\"%s\"", info.creator);
1893 fprintf(stderr, "\n");
1894 n = pdf_count_layer_config_ui(ctx, pdoc);
1895 for (j = 0; j < n; j++)
1896 {
1897 pdf_layer_config_ui ui;
1898
1899 pdf_layer_config_ui_info(ctx, pdoc, j, &ui);
1900 fprintf(stderr, "%s%d: ", j < 10 ? " " : "", j);
1901 while (ui.depth > 0)
1902 {
1903 ui.depth--;
1904 fprintf(stderr, " ");
1905 }
1906 if (ui.type == PDF_LAYER_UI_CHECKBOX)
1907 fprintf(stderr, " [%c] ", ui.selected ? 'x' : ' ');
1908 else if (ui.type == PDF_LAYER_UI_RADIOBOX)
1909 fprintf(stderr, " (%c) ", ui.selected ? 'x' : ' ');
1910 if (ui.text)
1911 fprintf(stderr, "%s", ui.text);
1912 if (ui.type != PDF_LAYER_UI_LABEL && ui.locked)
1913 fprintf(stderr, " <locked>");
1914 fprintf(stderr, "\n");
1915 }
1916 #endif
1917 }
1918
1919 static int create_accel_path(fz_context *ctx, char outname[], size_t len, int create, const char *absname, ...)
1920 {
1921 va_list args;
1922 char *s = outname;
1923 size_t z, remain = len;
1924 char *arg;
1925
1926 va_start(args, absname);
1927
1928 while ((arg = va_arg(args, char *)) != NULL)
1929 {
1930 z = fz_snprintf(s, remain, "%s", arg);
1931 if (z+1 > remain)
1932 goto fail; /* won't fit */
1933
1934 if (create)
1935 (void) fz_mkdir(outname);
1936 if (!fz_is_directory(ctx, outname))
1937 goto fail; /* directory creation failed, or that dir doesn't exist! */
1938 #ifdef _WIN32
1939 s[z] = '\\';
1940 #else
1941 s[z] = '/';
1942 #endif
1943 s[z+1] = 0;
1944 s += z+1;
1945 remain -= z+1;
1946 }
1947
1948 if (fz_snprintf(s, remain, "%s.accel", absname) >= remain)
1949 goto fail; /* won't fit */
1950
1951 va_end(args);
1952
1953 return 1;
1954
1955 fail:
1956 va_end(args);
1957
1958 return 0;
1959 }
1960
1961 static int convert_to_accel_path(fz_context *ctx, char outname[], char *absname, size_t len, int create)
1962 {
1963 char *tmpdir;
1964 char *s;
1965
1966 if (absname[0] == '/' || absname[0] == '\\')
1967 ++absname;
1968
1969 s = absname;
1970 while (*s) {
1971 if (*s == '/' || *s == '\\' || *s == ':')
1972 *s = '%';
1973 ++s;
1974 }
1975
1976 #ifdef _WIN32
1977 tmpdir = getenv("USERPROFILE");
1978 if (tmpdir && create_accel_path(ctx, outname, len, create, absname, tmpdir, ".config", "mupdf", NULL))
1979 return 1; /* OK! */
1980 /* TEMP and TMP are user-specific on modern windows. */
1981 tmpdir = getenv("TEMP");
1982 if (tmpdir && create_accel_path(ctx, outname, len, create, absname, tmpdir, "mupdf", NULL))
1983 return 1; /* OK! */
1984 tmpdir = getenv("TMP");
1985 if (tmpdir && create_accel_path(ctx, outname, len, create, absname, tmpdir, "mupdf", NULL))
1986 return 1; /* OK! */
1987 #else
1988 tmpdir = getenv("XDG_CACHE_HOME");
1989 if (tmpdir && create_accel_path(ctx, outname, len, create, absname, tmpdir, "mupdf", NULL))
1990 return 1; /* OK! */
1991 tmpdir = getenv("HOME");
1992 if (tmpdir && create_accel_path(ctx, outname, len, create, absname, tmpdir, ".cache", "mupdf", NULL))
1993 return 1; /* OK! */
1994 #endif
1995 return 0; /* Fail */
1996 }
1997
1998 static int get_accelerator_filename(fz_context *ctx, char outname[], size_t len, const char *fname, int create)
1999 {
2000 char absname[PATH_MAX];
2001 if (!fz_realpath(fname, absname))
2002 return 0;
2003 if (!convert_to_accel_path(ctx, outname, absname, len, create))
2004 return 0;
2005 return 1;
2006 }
2007
2008 static void save_accelerator(fz_context *ctx, fz_document *doc, const char *fname)
2009 {
2010 char absname[PATH_MAX];
2011
2012 if (!doc)
2013 return;
2014 if (!fz_document_supports_accelerator(ctx, doc))
2015 return;
2016 if (!get_accelerator_filename(ctx, absname, sizeof(absname), fname, 1))
2017 return;
2018
2019 fz_save_accelerator(ctx, doc, absname);
2020 }
2021
2022 #ifdef MUDRAW_STANDALONE
2023 int main(int argc, char **argv)
2024 #else
2025 int mudraw_main(int argc, char **argv)
2026 #endif
2027 {
2028 char *password = "";
2029 fz_document *doc = NULL;
2030 int c;
2031 fz_context *ctx;
2032 trace_info trace_info = { 0, 0, 0, 0, 0, 0 };
2033 fz_alloc_context trace_alloc_ctx = { &trace_info, trace_malloc, trace_realloc, trace_free };
2034 fz_alloc_context *alloc_ctx = NULL;
2035 fz_locks_context *locks = NULL;
2036 size_t max_store = FZ_STORE_DEFAULT;
2037
2038 fz_var(doc);
2039
2040 while ((c = fz_getopt(argc, argv, "qp:o:F:R:r:w:h:fB:c:e:G:Is:A:DiW:H:S:T:t:d:U:XLvPl:y:Yz:Z:NO:am:Kb:k:")) != -1)
2041 {
2042 switch (c)
2043 {
2044 default: return usage();
2045
2046 case 'q': quiet = 1; break;
2047
2048 case 'p': password = fz_optarg; break;
2049
2050 case 'o': output = fz_optpath(fz_optarg); break;
2051 case 'F': format = fz_optarg; break;
2052
2053 case 'R': rotation = fz_atof(fz_optarg); break;
2054 case 'r': resolution = fz_atof(fz_optarg); res_specified = 1; break;
2055 case 'w': width = fz_atof(fz_optarg); break;
2056 case 'h': height = fz_atof(fz_optarg); break;
2057 case 'f': fit = 1; break;
2058 case 'B': band_height = atoi(fz_optarg); break;
2059 case 'b':
2060 page_box = fz_box_type_from_string(fz_optarg);
2061 if (page_box == FZ_UNKNOWN_BOX)
2062 {
2063 fprintf(stderr, "Invalid box type: %s\n", fz_optarg);
2064 return 1;
2065 }
2066 break;
2067
2068 case 'c': out_cs = parse_colorspace(fz_optarg); break;
2069 case 'e': proof_filename = fz_optarg; break;
2070 case 'G': gamma_value = fz_atof(fz_optarg); break;
2071 case 'I': invert++; break;
2072
2073 case 'W': layout_w = fz_atof(fz_optarg); break;
2074 case 'H': layout_h = fz_atof(fz_optarg); break;
2075 case 'S': layout_em = fz_atof(fz_optarg); break;
2076 case 'U': layout_css = fz_optarg; break;
2077 case 'X': layout_use_doc_css = 0; break;
2078
2079 case 'K': ++s_kill; break;
2080
2081 case 'O': spots = fz_atof(fz_optarg);
2082 #ifndef FZ_ENABLE_SPOT_RENDERING
2083 fprintf(stderr, "Spot rendering/Overprint/Overprint simulation not enabled in this build\n");
2084 spots = SPOTS_NONE;
2085 #endif
2086 break;
2087
2088 case 's':
2089 if (strchr(fz_optarg, 't')) ++showtime;
2090 if (strchr(fz_optarg, 'm')) ++showmemory;
2091 if (strchr(fz_optarg, 'f')) ++showfeatures;
2092 if (strchr(fz_optarg, '5')) ++showmd5;
2093 break;
2094
2095 case 'A':
2096 {
2097 char *sep;
2098 alphabits_graphics = atoi(fz_optarg);
2099 sep = strchr(fz_optarg, '/');
2100 if (sep)
2101 alphabits_text = atoi(sep+1);
2102 else
2103 alphabits_text = alphabits_graphics;
2104 break;
2105 }
2106 case 'D': uselist = 0; break;
2107 case 'l': min_line_width = fz_atof(fz_optarg); break;
2108 case 'i': ignore_errors = 1; break;
2109 case 'N': no_icc = 1; break;
2110
2111 case 'T':
2112 #ifndef DISABLE_MUTHREADS
2113 num_workers = atoi(fz_optarg); break;
2114 #else
2115 fprintf(stderr, "Threads not enabled in this build\n");
2116 break;
2117 #endif
2118 case 'd':
2119 #ifndef OCR_DISABLED
2120 ocr_datadir = fz_optarg; break;
2121 #else
2122 fprintf(stderr, "OCR functionality not enabled in this build\n");
2123 break;
2124 #endif
2125 case 't':
2126 #ifndef OCR_DISABLED
2127 ocr_language = fz_optarg; break;
2128 #else
2129 fprintf(stderr, "OCR functionality not enabled in this build\n");
2130 break;
2131 #endif
2132 case 'm':
2133 if (fz_optarg[0] == 's') trace_info.mem_limit = fz_atoi64(&fz_optarg[1]);
2134 else if (fz_optarg[0] == 'a') trace_info.alloc_limit = fz_atoi64(&fz_optarg[1]);
2135 else trace_info.mem_limit = fz_atoi64(fz_optarg);
2136 break;
2137 case 'L': lowmemory = 1; break;
2138 case 'P':
2139 #ifndef DISABLE_MUTHREADS
2140 bgprint.active = 1; break;
2141 #else
2142 fprintf(stderr, "Threads not enabled in this build\n");
2143 break;
2144 #endif
2145 case 'y': layer_config = fz_optarg; break;
2146 case 'Y': layer_list = 1; break;
2147 case 'z': layer_off[layer_off_len++] = !strcmp(fz_optarg, "all") ? -1 : fz_atoi(fz_optarg); break;
2148 case 'Z': layer_on[layer_on_len++] = !strcmp(fz_optarg, "all") ? -1 : fz_atoi(fz_optarg); break;
2149 case 'a': useaccel = 0; break;
2150 case 'k':
2151 {
2152 const char *a;
2153 if (fz_optarg[0] == 'a')
2154 skew_correct = 1;
2155 else
2156 skew_correct = 2, skew_angle = fz_atof(fz_optarg);
2157 a = strchr(fz_optarg, ',');
2158 if (a != NULL)
2159 skew_border = fz_atoi(fz_optarg+1);
2160 break;
2161 }
2162
2163 case 'v': fprintf(stderr, "mudraw version %s\n", FZ_VERSION); return 1;
2164 }
2165 }
2166
2167 if (fz_optind == argc)
2168 return usage();
2169
2170 if (num_workers > 0)
2171 {
2172 if (uselist == 0)
2173 {
2174 fprintf(stderr, "cannot use multiple threads without using display list\n");
2175 exit(1);
2176 }
2177
2178 if (band_height == 0)
2179 {
2180 fprintf(stderr, "Using multiple threads without banding is pointless\n");
2181 }
2182 }
2183
2184 if (bgprint.active)
2185 {
2186 if (uselist == 0)
2187 {
2188 fprintf(stderr, "cannot bgprint without using display list\n");
2189 exit(1);
2190 }
2191 }
2192
2193 #ifndef DISABLE_MUTHREADS
2194 locks = init_mudraw_locks();
2195 if (locks == NULL)
2196 {
2197 fprintf(stderr, "mutex initialisation failed\n");
2198 exit(1);
2199 }
2200 #endif
2201
2202 if (trace_info.mem_limit || trace_info.alloc_limit || showmemory)
2203 alloc_ctx = &trace_alloc_ctx;
2204
2205 if (lowmemory)
2206 max_store = 1;
2207
2208 ctx = fz_new_context(alloc_ctx, locks, max_store);
2209 if (!ctx)
2210 {
2211 fprintf(stderr, "cannot initialise context\n");
2212 exit(1);
2213 }
2214
2215 fz_try(ctx)
2216 {
2217 if (proof_filename)
2218 {
2219 fz_buffer *proof_buffer = fz_read_file(ctx, proof_filename);
2220 proof_cs = fz_new_icc_colorspace(ctx, FZ_COLORSPACE_NONE, 0, NULL, proof_buffer);
2221 fz_drop_buffer(ctx, proof_buffer);
2222 }
2223
2224 fz_set_text_aa_level(ctx, alphabits_text);
2225 fz_set_graphics_aa_level(ctx, alphabits_graphics);
2226 fz_set_graphics_min_line_width(ctx, min_line_width);
2227 if (no_icc)
2228 fz_disable_icc(ctx);
2229 else
2230 fz_enable_icc(ctx);
2231
2232 #ifndef DISABLE_MUTHREADS
2233 if (bgprint.active)
2234 {
2235 int fail = 0;
2236 bgprint.ctx = fz_clone_context(ctx);
2237 fail |= mu_create_semaphore(&bgprint.start);
2238 fail |= mu_create_semaphore(&bgprint.stop);
2239 fail |= mu_create_thread(&bgprint.thread, bgprint_worker, NULL);
2240 if (fail)
2241 {
2242 fprintf(stderr, "bgprint startup failed\n");
2243 exit(1);
2244 }
2245 }
2246
2247 if (num_workers > 0)
2248 {
2249 int i;
2250 int fail = 0;
2251 workers = fz_calloc(ctx, num_workers, sizeof(*workers));
2252 for (i = 0; i < num_workers; i++)
2253 {
2254 workers[i].ctx = fz_clone_context(ctx);
2255 workers[i].num = i;
2256 fail |= mu_create_semaphore(&workers[i].start);
2257 fail |= mu_create_semaphore(&workers[i].stop);
2258 fail |= mu_create_thread(&workers[i].thread, worker_thread, &workers[i]);
2259 }
2260 if (fail)
2261 {
2262 fprintf(stderr, "worker startup failed\n");
2263 exit(1);
2264 }
2265 }
2266 #endif /* DISABLE_MUTHREADS */
2267
2268 if (layout_css)
2269 fz_load_user_css(ctx, layout_css);
2270
2271 fz_set_use_document_css(ctx, layout_use_doc_css);
2272
2273 /* Determine output type */
2274 if (band_height < 0)
2275 {
2276 fprintf(stderr, "Bandheight must be > 0\n");
2277 exit(1);
2278 }
2279
2280 output_format = OUT_PNG;
2281 if (format)
2282 {
2283 int i;
2284
2285 for (i = 0; i < (int)nelem(suffix_table); i++)
2286 {
2287 if (!strcmp(format, suffix_table[i].suffix+1))
2288 {
2289 output_format = suffix_table[i].format;
2290 if (spots == SPOTS_FULL && suffix_table[i].spots == 0)
2291 {
2292 fprintf(stderr, "Output format '%s' does not support spot rendering.\nDoing overprint simulation instead.\n", suffix_table[i].suffix+1);
2293 spots = SPOTS_OVERPRINT_SIM;
2294 }
2295 break;
2296 }
2297 }
2298 if (i == (int)nelem(suffix_table))
2299 {
2300 fprintf(stderr, "Unknown output format '%s'\n", format);
2301 exit(1);
2302 }
2303 }
2304 else if (output)
2305 {
2306 char *suffix = output;
2307 int i;
2308
2309 for (i = 0; i < (int)nelem(suffix_table); i++)
2310 {
2311 char *s = strstr(suffix, suffix_table[i].suffix);
2312
2313 if (s != NULL)
2314 {
2315 suffix = s+strlen(suffix_table[i].suffix);
2316 output_format = suffix_table[i].format;
2317 if (spots == SPOTS_FULL && suffix_table[i].spots == 0)
2318 {
2319 fprintf(stderr, "Output format '%s' does not support spot rendering.\nDoing overprint simulation instead.\n", suffix_table[i].suffix+1);
2320 spots = SPOTS_OVERPRINT_SIM;
2321 }
2322 i = -1;
2323 }
2324 }
2325 }
2326
2327 if (band_height)
2328 {
2329 if (output_format != OUT_PAM &&
2330 output_format != OUT_PGM &&
2331 output_format != OUT_PPM &&
2332 output_format != OUT_PNM &&
2333 output_format != OUT_PNG &&
2334 output_format != OUT_PBM &&
2335 output_format != OUT_PKM &&
2336 output_format != OUT_PCL &&
2337 output_format != OUT_PCLM &&
2338 output_format != OUT_PS &&
2339 output_format != OUT_PSD &&
2340 output_format != OUT_OCR_PDF)
2341 {
2342 fprintf(stderr, "Banded operation only possible with PxM, PCL, PCLM, PDFOCR, PS, PSD, and PNG outputs\n");
2343 exit(1);
2344 }
2345 if (showmd5)
2346 {
2347 fprintf(stderr, "Banded operation not compatible with MD5\n");
2348 exit(1);
2349 }
2350 }
2351
2352 {
2353 int i, j;
2354
2355 for (i = 0; i < (int)nelem(format_cs_table); i++)
2356 {
2357 if (format_cs_table[i].format == output_format)
2358 {
2359 if (out_cs == CS_UNSET)
2360 out_cs = format_cs_table[i].default_cs;
2361 for (j = 0; j < (int)nelem(format_cs_table[i].permitted_cs); j++)
2362 {
2363 if (format_cs_table[i].permitted_cs[j] == out_cs)
2364 break;
2365 }
2366 if (j == (int)nelem(format_cs_table[i].permitted_cs))
2367 {
2368 fprintf(stderr, "Unsupported colorspace for this format\n");
2369 exit(1);
2370 }
2371 }
2372 }
2373 }
2374
2375 alpha = 1;
2376 switch (out_cs)
2377 {
2378 case CS_MONO:
2379 case CS_GRAY:
2380 case CS_GRAY_ALPHA:
2381 colorspace = fz_device_gray(ctx);
2382 alpha = (out_cs == CS_GRAY_ALPHA);
2383 break;
2384 case CS_RGB:
2385 case CS_RGB_ALPHA:
2386 colorspace = fz_device_rgb(ctx);
2387 alpha = (out_cs == CS_RGB_ALPHA);
2388 break;
2389 case CS_CMYK:
2390 case CS_CMYK_ALPHA:
2391 colorspace = fz_device_cmyk(ctx);
2392 alpha = (out_cs == CS_CMYK_ALPHA);
2393 break;
2394 case CS_ICC:
2395 fz_try(ctx)
2396 {
2397 fz_buffer *icc_buffer = fz_read_file(ctx, icc_filename);
2398 colorspace = fz_new_icc_colorspace(ctx, FZ_COLORSPACE_NONE, 0, NULL, icc_buffer);
2399 fz_drop_buffer(ctx, icc_buffer);
2400 }
2401 fz_catch(ctx)
2402 {
2403 fprintf(stderr, "Invalid ICC destination color space\n");
2404 exit(1);
2405 }
2406 if (colorspace == NULL)
2407 {
2408 fprintf(stderr, "Invalid ICC destination color space\n");
2409 exit(1);
2410 }
2411 alpha = 0;
2412 break;
2413 default:
2414 fprintf(stderr, "Unknown colorspace!\n");
2415 exit(1);
2416 break;
2417 }
2418
2419 if (out_cs != CS_ICC)
2420 colorspace = fz_keep_colorspace(ctx, colorspace);
2421 else
2422 {
2423 int i, j, okay;
2424
2425 /* Check to make sure this icc profile is ok with the output format */
2426 okay = 0;
2427 for (i = 0; i < (int)nelem(format_cs_table); i++)
2428 {
2429 if (format_cs_table[i].format == output_format)
2430 {
2431 for (j = 0; j < (int)nelem(format_cs_table[i].permitted_cs); j++)
2432 {
2433 switch (format_cs_table[i].permitted_cs[j])
2434 {
2435 case CS_MONO:
2436 case CS_GRAY:
2437 case CS_GRAY_ALPHA:
2438 if (fz_colorspace_is_gray(ctx, colorspace))
2439 okay = 1;
2440 break;
2441 case CS_RGB:
2442 case CS_RGB_ALPHA:
2443 if (fz_colorspace_is_rgb(ctx, colorspace))
2444 okay = 1;
2445 break;
2446 case CS_CMYK:
2447 case CS_CMYK_ALPHA:
2448 if (fz_colorspace_is_cmyk(ctx, colorspace))
2449 okay = 1;
2450 break;
2451 }
2452 }
2453 }
2454 }
2455
2456 if (!okay)
2457 {
2458 fprintf(stderr, "ICC profile uses a colorspace that cannot be used for this format\n");
2459 exit(1);
2460 }
2461 }
2462
2463 #if FZ_ENABLE_PDF
2464 if (output_format == OUT_PDF)
2465 {
2466 pdfout = pdf_create_document(ctx);
2467 }
2468 else
2469 #endif
2470 if (output_format == OUT_SVG)
2471 {
2472 /* SVG files are always opened for each page. Do not open "output". */
2473 }
2474 else if (output && (output[0] != '-' || output[1] != 0) && *output != 0)
2475 {
2476 if (has_percent_d(output))
2477 output_file_per_page = 1;
2478 else
2479 out = fz_new_output_with_path(ctx, output, 0);
2480 }
2481 else
2482 {
2483 quiet = 1; /* automatically be quiet if printing to stdout */
2484 #ifdef _WIN32
2485 /* Windows specific code to make stdout binary. */
2486 if (output_format != OUT_TEXT &&
2487 output_format != OUT_STEXT_XML &&
2488 output_format != OUT_STEXT_JSON &&
2489 output_format != OUT_HTML &&
2490 output_format != OUT_XHTML &&
2491 output_format != OUT_TRACE &&
2492 output_format != OUT_OCR_TRACE &&
2493 output_format != OUT_BBOX &&
2494 output_format != OUT_OCR_TEXT &&
2495 output_format != OUT_OCR_STEXT_XML &&
2496 output_format != OUT_OCR_STEXT_JSON &&
2497 output_format != OUT_OCR_HTML &&
2498 output_format != OUT_OCR_XHTML &&
2499 output_format != OUT_XMLTEXT)
2500 {
2501 (void)setmode(fileno(stdout), O_BINARY);
2502 }
2503 #endif
2504 out = fz_stdout(ctx);
2505 }
2506
2507 filename = argv[fz_optind];
2508
2509 timing.count = 0;
2510 timing.total = 0;
2511 timing.min = 1 << 30;
2512 timing.max = 0;
2513 timing.mininterp = 1 << 30;
2514 timing.maxinterp = 0;
2515 timing.minpage = 0;
2516 timing.maxpage = 0;
2517 timing.minfilename = "";
2518 timing.maxfilename = "";
2519 timing.layout = 0;
2520 timing.minlayout = 1 << 30;
2521 timing.maxlayout = 0;
2522 timing.minlayoutfilename = "";
2523 timing.maxlayoutfilename = "";
2524 if (showtime && bgprint.active)
2525 timing.total = gettime();
2526
2527 fz_try(ctx)
2528 {
2529 if (!output_file_per_page)
2530 file_level_headers(ctx);
2531 fz_register_document_handlers(ctx);
2532 #ifdef HAVE_SMARTOFFICE
2533 {
2534 void *cfg = so_doc_handler_enable(ctx, "en-gb", NULL, 1);
2535 so_doc_handler_configure(ctx, cfg, SO_DOC_HANDLER_MODE, SO_DOC_HANDLER_MODE_HTML);
2536 }
2537 #endif
2538
2539 while (fz_optind < argc)
2540 {
2541 char accelpath[PATH_MAX];
2542 char *accel = NULL;
2543 time_t atime;
2544 time_t dtime;
2545 int layouttime;
2546
2547 fz_try(ctx)
2548 {
2549 filename = argv[fz_optind++];
2550 files++;
2551
2552 if (!useaccel)
2553 accel = NULL;
2554 /* If there was an accelerator to load, what would it be called? */
2555 else if (get_accelerator_filename(ctx, accelpath, sizeof(accelpath), filename, 0))
2556 {
2557 /* Check whether that file exists, and isn't older than
2558 * the document. */
2559 atime = fz_stat_mtime(accelpath);
2560 dtime = fz_stat_mtime(filename);
2561 if (atime == 0)
2562 {
2563 /* No accelerator */
2564 }
2565 else if (atime > dtime)
2566 accel = accelpath;
2567 else
2568 {
2569 /* Accelerator data is out of date */
2570 #ifdef _WIN32
2571 fz_remove_utf8(accelpath);
2572 #else
2573 remove(accelpath);
2574 #endif
2575 accel = NULL; /* In case we have jumped up from below */
2576 }
2577 }
2578
2579 doc = fz_open_accelerated_document(ctx, filename, accel);
2580
2581 #ifdef CLUSTER
2582 /* Load and then drop the outline if we're running under the cluster.
2583 * This allows our outline handling to be tested automatically. */
2584 fz_try(ctx)
2585 fz_drop_outline(ctx, fz_load_outline(ctx, doc));
2586 fz_catch(ctx)
2587 {
2588 /* Drop any error */
2589 fz_report_error(ctx);
2590 }
2591 #endif
2592
2593 if (fz_needs_password(ctx, doc))
2594 {
2595 if (!fz_authenticate_password(ctx, doc, password))
2596 fz_throw(ctx, FZ_ERROR_ARGUMENT, "cannot authenticate password: %s", filename);
2597 }
2598
2599 #ifdef CLUSTER
2600 /* Load and then drop the outline if we're running under the cluster.
2601 * This allows our outline handling to be tested automatically. */
2602 fz_try(ctx)
2603 fz_drop_outline(ctx, fz_load_outline(ctx, doc));
2604 fz_catch(ctx)
2605 {
2606 /* Drop any error */
2607 fz_report_error(ctx);
2608 }
2609 #endif
2610
2611 /* Once document is open check for output intent colorspace */
2612 oi = fz_document_output_intent(ctx, doc);
2613 if (oi)
2614 {
2615 /* See if we had explicitly set a profile to render */
2616 if (out_cs != CS_ICC)
2617 {
2618 /* In this case, we want to render to the output intent
2619 * color space if the number of channels is the same */
2620 if (fz_colorspace_n(ctx, oi) == fz_colorspace_n(ctx, colorspace))
2621 {
2622 fz_drop_colorspace(ctx, colorspace);
2623 colorspace = fz_keep_colorspace(ctx, oi);
2624 }
2625 }
2626 }
2627
2628 layouttime = gettime();
2629 fz_layout_document(ctx, doc, layout_w, layout_h, layout_em);
2630 (void) fz_count_pages(ctx, doc);
2631 layouttime = gettime() - layouttime;
2632
2633 timing.layout += layouttime;
2634 if (layouttime < timing.minlayout)
2635 {
2636 timing.minlayout = layouttime;
2637 timing.minlayoutfilename = filename;
2638 }
2639 if (layouttime > timing.maxlayout)
2640 {
2641 timing.maxlayout = layouttime;
2642 timing.maxlayoutfilename = filename;
2643 }
2644
2645 if (layer_config)
2646 apply_layer_config(ctx, doc, layer_config);
2647 if (layer_on_len > 0 || layer_off_len > 0)
2648 toggle_layers(ctx, doc);
2649 if (layer_list)
2650 list_layers(ctx, doc);
2651
2652 if (fz_optind == argc || !fz_is_page_range(ctx, argv[fz_optind]))
2653 drawrange(ctx, doc, "1-N");
2654 if (fz_optind < argc && fz_is_page_range(ctx, argv[fz_optind]))
2655 drawrange(ctx, doc, argv[fz_optind++]);
2656
2657 bgprint_flush();
2658 if (bgprint.error)
2659 fz_throw(ctx, FZ_ERROR_GENERIC, "failed to parse page");
2660
2661 if (useaccel)
2662 save_accelerator(ctx, doc, filename);
2663 }
2664 fz_always(ctx)
2665 {
2666 fz_drop_document(ctx, doc);
2667 doc = NULL;
2668 }
2669 fz_catch(ctx)
2670 {
2671 if (!ignore_errors)
2672 fz_rethrow(ctx);
2673
2674 bgprint_flush();
2675 fz_report_error(ctx);
2676 fz_warn(ctx, "ignoring error in '%s'", filename);
2677 }
2678 }
2679 }
2680 fz_catch(ctx)
2681 {
2682 bgprint_flush();
2683 fz_drop_document(ctx, doc);
2684 fz_report_error(ctx);
2685 fz_log_error_printf(ctx, "cannot draw '%s'", filename);
2686 errored = 1;
2687 }
2688
2689 if (!output_file_per_page)
2690 file_level_trailers(ctx);
2691
2692 #if FZ_ENABLE_PDF
2693 if (output_format == OUT_PDF)
2694 {
2695 if (!output)
2696 output = "out.pdf";
2697 pdf_save_document(ctx, pdfout, output, NULL);
2698 pdf_drop_document(ctx, pdfout);
2699 }
2700 else
2701 #endif
2702 {
2703 fz_close_output(ctx, out);
2704 fz_drop_output(ctx, out);
2705 out = NULL;
2706 }
2707
2708 if (showtime && timing.count > 0)
2709 {
2710 if (bgprint.active)
2711 timing.total = gettime() - timing.total;
2712
2713 if (files == 1)
2714 {
2715 fprintf(stderr, "total %dms (%dms layout) / %d pages for an average of %dms\n",
2716 timing.total, timing.layout, timing.count, timing.total / timing.count);
2717 if (bgprint.active)
2718 {
2719 fprintf(stderr, "fastest page %d: %dms (interpretation) %dms (rendering) %dms(total)\n",
2720 timing.minpage, timing.mininterp, timing.min - timing.mininterp, timing.min);
2721 fprintf(stderr, "slowest page %d: %dms (interpretation) %dms (rendering) %dms(total)\n",
2722 timing.maxpage, timing.maxinterp, timing.max - timing.maxinterp, timing.max);
2723 }
2724 else
2725 {
2726 fprintf(stderr, "fastest page %d: %dms\n", timing.minpage, timing.min);
2727 fprintf(stderr, "slowest page %d: %dms\n", timing.maxpage, timing.max);
2728 }
2729 }
2730 else
2731 {
2732 fprintf(stderr, "total %dms (%dms layout) / %d pages for an average of %dms in %d files\n",
2733 timing.total, timing.layout, timing.count, timing.total / timing.count, files);
2734 fprintf(stderr, "fastest layout: %dms (%s)\n", timing.minlayout, timing.minlayoutfilename);
2735 fprintf(stderr, "slowest layout: %dms (%s)\n", timing.maxlayout, timing.maxlayoutfilename);
2736 fprintf(stderr, "fastest page %d: %dms (%s)\n", timing.minpage, timing.min, timing.minfilename);
2737 fprintf(stderr, "slowest page %d: %dms (%s)\n", timing.maxpage, timing.max, timing.maxfilename);
2738 }
2739 }
2740
2741 #ifndef DISABLE_MUTHREADS
2742 if (num_workers > 0)
2743 {
2744 int i;
2745 DEBUG_THREADS(("Asking workers to shutdown, then destroy their resources\n"));
2746 for (i = 0; i < num_workers; i++)
2747 {
2748 workers[i].band = -1;
2749 mu_trigger_semaphore(&workers[i].start);
2750 mu_wait_semaphore(&workers[i].stop);
2751 mu_destroy_semaphore(&workers[i].start);
2752 mu_destroy_semaphore(&workers[i].stop);
2753 mu_destroy_thread(&workers[i].thread);
2754 fz_drop_context(workers[i].ctx);
2755 }
2756 fz_free(ctx, workers);
2757 }
2758
2759 if (bgprint.active)
2760 {
2761 bgprint.pagenum = -1;
2762 mu_trigger_semaphore(&bgprint.start);
2763 mu_wait_semaphore(&bgprint.stop);
2764 mu_destroy_semaphore(&bgprint.start);
2765 mu_destroy_semaphore(&bgprint.stop);
2766 mu_destroy_thread(&bgprint.thread);
2767 fz_drop_context(bgprint.ctx);
2768 }
2769 #endif /* DISABLE_MUTHREADS */
2770 }
2771 fz_always(ctx)
2772 {
2773 fz_drop_colorspace(ctx, colorspace);
2774 fz_drop_colorspace(ctx, proof_cs);
2775 }
2776 fz_catch(ctx)
2777 {
2778 fz_report_error(ctx);
2779 if (!errored) {
2780 fprintf(stderr, "Rendering failed\n");
2781 errored = 1;
2782 }
2783 }
2784
2785 fz_drop_context(ctx);
2786
2787 #ifndef DISABLE_MUTHREADS
2788 fin_mudraw_locks();
2789 #endif /* DISABLE_MUTHREADS */
2790
2791 if (trace_info.mem_limit || trace_info.alloc_limit || showmemory)
2792 {
2793 char buf[200];
2794 fz_snprintf(buf, sizeof buf, "Memory use total=%zu peak=%zu current=%zu\nAllocations total=%zu\n", trace_info.total, trace_info.peak, trace_info.current, trace_info.allocs);
2795 fprintf(stderr, "%s", buf);
2796 }
2797
2798 return (errored != 0);
2799 }