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