1 /*
2  * muraster -- Convert a document to a raster file.
3  *
4  * Deliberately simple. Designed to be a basis for what
5  * printer customers would need.
6  *
7  * Therefore; only supports pgm, ppm, pam, pbm, pkm,
8  * and then only dependent on the FZ_PLOTTERS_{G,RGB,CMYK}
9  * flags.
10  * Only supports banding.
11  * Supports auto fallback to grey if possible.
12  * Supports threading.
13  * Supports fallback in low memory cases.
14  */
15 
16 /*
17 	CONFIGURATION SECTION
18 
19 	The first bit of configuration for this is actually in
20 	how the muthreads helper library is built. If muthreads
21 	does not know how to support threading on your system
22 	then it will ensure that DISABLE_MUTHREADS is set. All
23 	the muthreads entrypoints/types will still be defined
24 	(as dummy types/functions), but attempting to use them
25 	will return errors.
26 
27 	Configuration options affecting threading should be
28 	turned off if DISABLE_MUTHREADS is set.
29 
30 	Integrators can/should define the following
31 	MURASTER_CONFIG_ values. If not set, we'll
32 	attempt to set sensible defaults.
33 */
34 
35 /*
36 	MURASTER_CONFIG_RENDER_THREADS: The number of render
37 	threads to use. Typically you would set this to the
38 	number of CPU cores - 1 (or -2 if background printing
39 	is used).
40 
41 	If no threading library exists for your OS set this
42 	to 0.
43 
44 	If undefined, we will use a default of
45 	3 - MURASTER_CONFIG_BGPRINT.
46 */
47 /* #define MURASTER_CONFIG_RENDER_THREADS 3 */
48 
49 /*
50 	MURASTER_CONFIG_BGPRINT: 0 or 1. Set to 1 to
51 	enable background printing. This relies on
52 	a threading library existing for the OS.
53 
54 	If undefined, we will use a default of 1.
55 */
56 /* #define MURASTER_CONFIG_BGPRINT 1 */
57 
58 /*
59 	MURASTER_CONFIG_X_RESOLUTION: The default X resolution
60 	in dots per inch. If undefined, taken to be 300dpi.
61 */
62 /* #define MURASTER_CONFIG_X_RESOLUTION 300 */
63 
64 /*
65 	MURASTER_CONFIG_Y_RESOLUTION: The default Y resolution
66 	in dots per inch. If undefined, taken to be 300dpi.
67 */
68 /* #define MURASTER_CONFIG_Y_RESOLUTION 300 */
69 
70 /*
71 	MURASTER_CONFIG_WIDTH: The printable page width
72 	(in inches)
73 */
74 /* #define MURASTER_CONFIG_WIDTH 8.27f */
75 
76 /*
77 	MURASTER_CONFIG_HEIGHT: The printable page height
78 	(in inches)
79 */
80 /* #define MURASTER_CONFIG_HEIGHT 11.69f */
81 
82 /*
83 	MURASTER_CONFIG_STORE_SIZE: The maximum size to use
84 	for the fz_store.
85 
86 	If undefined, then on Linux we will attempt to guess
87 	the memory size, and we'll use that for the store
88 	size. This will be too large, but it should work OK.
89 
90 	If undefined and NOT linux, then we'll use the default
91 	store size.
92 */
93 /* #define MURASTER_CONFIG_STORE_SIZE FZ_STORE_DEFAULT */
94 
95 /*
96 	MURASTER_CONFIG_MIN_BAND_HEIGHT: The minimum band
97 	height we will ever use. This might correspond to the
98 	number of nozzles on an inkjet head.
99 
100 	By default, we'll use 32.
101 */
102 /* #define MURASTER_CONFIG_MIN_BAND_HEIGHT 32 */
103 
104 /*
105 	MURASTER_CONFIG_BAND_MEMORY: The maximum amount of
106 	memory (in bytes) to use for any given band.
107 
108 	We will need MURASTER_CONFIG_RENDER_THREADS of these,
109 	one for each render thread.
110 
111 	Having this be a multiple of
112 	MURASTER_CONFIG_MIN_BAND_HEIGHT * MURASTER_CONFIG_MAX_WIDTH * MURASTER_CONFIG_X_RESOLUTION * N
113 	would be sensible.
114 
115 	(Where N = 1 for greyscale, 3 for RGB, 4 for CMYK)
116 */
117 /* #define MURASTER_CONFIG_BAND_MEMORY (32*10*300*4*16) */
118 
119 /*
120 	MURASTER_CONFIG_GREY_FALLBACK: 0, 1 or 2.
121 
122 	Set to 1 to fallback to grey rendering if the page
123 	is definitely grey. Any images in colored color
124 	spaces will be assumed to be color. This may refuse
125 	to fallback in some cases when it could have done.
126 
127 	Set to 2 to fallback to grey rendering if the page
128 	is definitely grey. Any images in colored color
129 	spaces will be exhaustively checked. This will
130 	fallback whenever possible, at the expense of some
131 	runtime as more processing is required to check.
132 */
133 /* #define MURASTER_CONFIG_GREY_FALLBACK 1 */
134 
135 /*
136 	END OF CONFIGURATION SECTION
137 */
138 
139 #include "mupdf/fitz.h"
140 #include "mupdf/helpers/mu-threads.h"
141 
142 #include <string.h>
143 #include <stdlib.h>
144 #include <stdio.h>
145 
146 #ifdef _MSC_VER
147 struct timeval;
148 struct timezone;
149 int gettimeofday(struct timeval *tv, struct timezone *tz);
150 #else
151 #include <sys/time.h>
152 #endif
153 
154 /*
155 	After this point, we convert the #defines set (or not set)
156 	above into sensible values we can work with. Don't edit
157 	these for configuration.
158 */
159 
160 /* Unless we have specifically disabled threading, enable it. */
161 #ifndef DISABLE_MUTHREADS
162 #ifndef MURASTER_THREADS
163 #define MURASTER_THREADS 1
164 #endif
165 #endif
166 
167 /* If we have threading, and we haven't already configured BGPRINT,
168  * enable it. */
169 #if MURASTER_THREADS != 0
170 #ifndef MURASTER_CONFIG_BGPRINT
171 #define MURASTER_CONFIG_BGPRINT 1
172 #endif
173 #endif
174 
175 #ifdef MURASTER_CONFIG_X_RESOLUTION
176 #define X_RESOLUTION MURASTER_CONFIG_X_RESOLUTION
177 #else
178 #define X_RESOLUTION 300
179 #endif
180 
181 #ifdef MURASTER_CONFIG_Y_RESOLUTION
182 #define Y_RESOLUTION MURASTER_CONFIG_Y_RESOLUTION
183 #else
184 #define Y_RESOLUTION 300
185 #endif
186 
187 #ifdef MURASTER_CONFIG_WIDTH
188 #define PAPER_WIDTH MURASTER_CONFIG_WIDTH
189 #else
190 #define PAPER_WIDTH 8.27f
191 #endif
192 
193 #ifdef MURASTER_CONFIG_HEIGHT
194 #define PAPER_HEIGHT MURASTER_CONFIG_HEIGHT
195 #else
196 #define PAPER_HEIGHT 11.69f
197 #endif
198 
199 #ifdef MURASTER_CONFIG_STORE_SIZE
200 #define STORE_SIZE MURASTER_CONFIG_STORE_SIZE
201 #else
202 #define STORE_SIZE FZ_STORE_SIZE
203 #endif
204 
205 #ifdef MURASTER_CONFIG_MIN_BAND_HEIGHT
206 #define MIN_BAND_HEIGHT MURASTER_CONFIG_MIN_BAND_HEIGHT
207 #else
208 #define MIN_BAND_HEIGHT 32
209 #endif
210 
211 #ifdef MURASTER_CONFIG_BAND_MEMORY
212 #define BAND_MEMORY MURASTER_CONFIG_BAND_MEMORY
213 #else
214 #if defined(FZ_PLOTTERS_CMYK) || defined(FZ_PLOTTERS_N)
215 #define BAND_MEMORY (MIN_BAND_HEIGHT * PAPER_WIDTH * X_RESOLUTION * 4 * 16)
216 #elif defined(FZ_PLOTTERS_RGB)
217 #define BAND_MEMORY (MIN_BAND_HEIGHT * PAPER_WIDTH * X_RESOLUTION * 3 * 16)
218 #else
219 #define BAND_MEMORY (MIN_BAND_HEIGHT * PAPER_WIDTH * X_RESOLUTION * 1 * 16)
220 #endif
221 #endif
222 
223 #ifdef MURASTER_CONFIG_GREY_FALLBACK
224 #define GREY_FALLBACK MURASTER_CONFIG_GREY_FALLBACK
225 #else
226 #ifdef FZ_PLOTTERS_N
227 #define GREY_FALLBACK 1
228 #elif defined(FZ_PLOTTERS_G) && (defined(FZ_PLOTTERS_RGB) || defined(FZ_PLOTTERS_CMYK))
229 #define GREY_FALLBACK 1
230 #else
231 #define GREY_FALLBACK 0
232 #endif
233 #endif
234 
235 #if GREY_FALLBACK != 0 && !defined(FZ_PLOTTERS_N) && !defined(FZ_PLOTTERS_G)
236 #error MURASTER_CONFIG_GREY_FALLBACK requires either FZ_PLOTTERS_N or FZ_PLOTTERS_G
237 #endif
238 
239 /* Enable for helpful threading debug */
240 /* #define DEBUG_THREADS(A) do { printf A; fflush(stdout); } while (0) */
241 #define DEBUG_THREADS(A) do { } while (0)
242 
243 enum {
244 	OUT_PGM,
245 	OUT_PPM,
246 	OUT_PAM,
247 	OUT_PBM,
248 	OUT_PKM
249 };
250 
251 enum {
252 	CS_GRAY,
253 	CS_RGB,
254 	CS_CMYK
255 };
256 
257 typedef struct
258 {
259 	char *suffix;
260 	int format;
261 	int cs;
262 } suffix_t;
263 
264 static const suffix_t suffix_table[] =
265 {
266 #if FZ_PLOTTERS_G || FZ_PLOTTERS_N
267 	{ ".pgm", OUT_PGM, CS_GRAY },
268 #endif
269 #if FZ_PLOTTERS_RGB || FZ_PLOTTERS_N
270 	{ ".ppm", OUT_PPM, CS_RGB },
271 #endif
272 #if FZ_PLOTTERS_CMYK || FZ_PLOTTERS_N
273 	{ ".pam", OUT_PAM, CS_CMYK },
274 #endif
275 #if FZ_PLOTTERS_G || FZ_PLOTTERS_N
276 	{ ".pbm", OUT_PBM, CS_GRAY },
277 #endif
278 #if FZ_PLOTTERS_CMYK || FZ_PLOTTERS_N
279 	{ ".pkm", OUT_PKM, CS_CMYK }
280 #endif
281 };
282 
283 #ifndef DISABLE_MUTHREADS
284 
285 static mu_mutex mutexes[FZ_LOCK_MAX];
286 
muraster_lock(void * user,int lock)287 static void muraster_lock(void *user, int lock)
288 {
289 	mu_lock_mutex(&mutexes[lock]);
290 }
291 
muraster_unlock(void * user,int lock)292 static void muraster_unlock(void *user, int lock)
293 {
294 	mu_unlock_mutex(&mutexes[lock]);
295 }
296 
297 static fz_locks_context muraster_locks =
298 {
299 	NULL, muraster_lock, muraster_unlock
300 };
301 
fin_muraster_locks(void)302 static void fin_muraster_locks(void)
303 {
304 	int i;
305 
306 	for (i = 0; i < FZ_LOCK_MAX; i++)
307 		mu_destroy_mutex(&mutexes[i]);
308 }
309 
init_muraster_locks(void)310 static fz_locks_context *init_muraster_locks(void)
311 {
312 	int i;
313 	int failed = 0;
314 
315 	for (i = 0; i < FZ_LOCK_MAX; i++)
316 		failed |= mu_create_mutex(&mutexes[i]);
317 
318 	if (failed)
319 	{
320 		fin_muraster_locks();
321 		return NULL;
322 	}
323 
324 	return &muraster_locks;
325 }
326 
327 #endif
328 
329 #ifdef MURASTER_CONFIG_RENDER_THREADS
330 #define NUM_RENDER_THREADS MURASTER_CONFIG_RENDER_THREADS
331 #elif defined(DISABLE_MUTHREADS)
332 #define NUM_RENDER_THREADS 0
333 #else
334 #define NUM_RENDER_THREADS 3
335 #endif
336 
337 #if defined(DISABLE_MUTHREADS) && NUM_RENDER_THREADS != 0
338 #error "Can't have MURASTER_CONFIG_RENDER_THREADS > 0 without having a threading library!"
339 #endif
340 
341 #ifdef MURASTER_CONFIG_BGPRINT
342 #define BGPRINT MURASTER_CONFIG_BGPRINT
343 #elif MURASTER_THREADS == 0
344 #define BGPRINT 0
345 #else
346 #define BGPRINT 1
347 #endif
348 
349 #if defined(DISABLE_MUTHREADS) && BGPRINT != 0
350 #error "Can't have MURASTER_CONFIG_BGPRINT > 0 without having a threading library!"
351 #endif
352 
353 typedef struct worker_t {
354 	fz_context *ctx;
355 	int started;
356 	int status;
357 	int num;
358 	int band_start; /* -1 to shutdown, or offset of band to render */
359 	fz_display_list *list;
360 	fz_matrix ctm;
361 	fz_rect tbounds;
362 	fz_pixmap *pix;
363 	fz_bitmap *bit;
364 	fz_cookie cookie;
365 	mu_semaphore start;
366 	mu_semaphore stop;
367 	mu_thread thread;
368 } worker_t;
369 
370 static char *output = NULL;
371 static fz_output *out = NULL;
372 
373 static char *format;
374 static int output_format;
375 static int output_cs;
376 
377 static int rotation = -1;
378 static float x_resolution;
379 static float y_resolution;
380 static int width = 0;
381 static int height = 0;
382 static int fit = 0;
383 
384 static float layout_w = FZ_DEFAULT_LAYOUT_W;
385 static float layout_h = FZ_DEFAULT_LAYOUT_H;
386 static float layout_em = FZ_DEFAULT_LAYOUT_EM;
387 static char *layout_css = NULL;
388 static int layout_use_doc_css = 1;
389 
390 static int showtime = 0;
391 static int showmemory = 0;
392 
393 static int ignore_errors = 0;
394 static int alphabits_text = 8;
395 static int alphabits_graphics = 8;
396 
397 static int min_band_height;
398 static size_t max_band_memory;
399 
400 static int errored = 0;
401 static fz_colorspace *colorspace;
402 static char *filename;
403 static int num_workers = 0;
404 static worker_t *workers;
405 
406 typedef struct render_details
407 {
408 	/* Page */
409 	fz_page *page;
410 
411 	/* Display list */
412 	fz_display_list *list;
413 
414 	/* Raw bounds */
415 	fz_rect bounds;
416 
417 	/* Transformed bounds */
418 	fz_rect tbounds;
419 
420 	/* Rounded transformed bounds */
421 	fz_irect ibounds;
422 
423 	/* Transform matrix */
424 	fz_matrix ctm;
425 
426 	/* How many min band heights are we working in? */
427 	int band_height_multiple;
428 
429 	/* What colorspace are we working in? (Adjusted for fallback) */
430 	int colorspace;
431 
432 	/* What output format? (Adjusted for fallback) */
433 	int format;
434 
435 	/* During the course of the rendering, this keeps track of
436 	 * how many 'min_band_heights' have been safely rendered. */
437 	int bands_rendered;
438 
439 	/* The maximum number of workers we'll try to use. This
440 	 * will start at the maximum value, and may drop to 0
441 	 * if we have problems with memory. */
442 	int num_workers;
443 
444 	/* The band writer to output the page */
445 	fz_band_writer *bander;
446 
447 	/* Number of components in image */
448 	int n;
449 } render_details;
450 
451 enum
452 {
453 	RENDER_OK = 0,
454 	RENDER_RETRY = 1,
455 	RENDER_FATAL = 2
456 };
457 
458 static struct {
459 	int active;
460 	int started;
461 	int solo;
462 	int status;
463 	fz_context *ctx;
464 	mu_thread thread;
465 	mu_semaphore start;
466 	mu_semaphore stop;
467 	int pagenum;
468 	char *filename;
469 	render_details render;
470 	int interptime;
471 } bgprint;
472 
473 static struct {
474 	int count, total;
475 	int min, max;
476 	int mininterp, maxinterp;
477 	int minpage, maxpage;
478 	char *minfilename;
479 	char *maxfilename;
480 } timing;
481 
482 #define stringify(A) #A
483 
usage(void)484 static void usage(void)
485 {
486 	fprintf(stderr,
487 		"muraster version " FZ_VERSION "\n"
488 		"Usage: muraster [options] file [pages]\n"
489 		"\t-p -\tpassword\n"
490 		"\n"
491 		"\t-o -\toutput file name\n"
492 		"\t-F -\toutput format (default inferred from output file name)\n"
493 		"\t\tpam, pbm, pgm, pkm, ppm\n"
494 		"\n"
495 		"\t-s -\tshow extra information:\n"
496 		"\t\tm - show memory use\n"
497 		"\t\tt - show timings\n"
498 		"\n"
499 		"\t-R {auto,0,90,180,270}\n"
500 		"\t\trotate clockwise (default: auto)\n"
501 		"\t-r -{,_}\tx and y resolution in dpi (default: " stringify(X_RESOLUTION) "x" stringify(Y_RESOLUTION) ")\n"
502 		"\t-w -\tprintable width (in inches) (default: " stringify(PAPER_WIDTH) ")\n"
503 		"\t-h -\tprintable height (in inches) (default: " stringify(PAPER_HEIGHT) "\n"
504 		"\t-f\tfit file to page if too large\n"
505 		"\t-B -\tminimum band height (e.g. 32)\n"
506 		"\t-M -\tmax bandmemory (e.g. 655360)\n"
507 #ifndef DISABLE_MUTHREADS
508 		"\t-T -\tnumber of threads to use for rendering\n"
509 		"\t-P\tparallel interpretation/rendering\n"
510 #endif
511 		"\n"
512 		"\t-W -\tpage width for EPUB layout\n"
513 		"\t-H -\tpage height for EPUB layout\n"
514 		"\t-S -\tfont size for EPUB layout\n"
515 		"\t-U -\tfile name of user stylesheet for EPUB layout\n"
516 		"\t-X\tdisable document styles for EPUB layout\n"
517 		"\n"
518 		"\t-A -\tnumber of bits of antialiasing (0 to 8)\n"
519 		"\t-A -/-\tnumber of bits of antialiasing (0 to 8) (graphics, text)\n"
520 		"\n"
521 		"\tpages\tcomma separated list of page numbers and ranges\n"
522 		);
523 	exit(1);
524 }
525 
gettime(void)526 static int gettime(void)
527 {
528 	static struct timeval first;
529 	static int once = 1;
530 	struct timeval now;
531 	if (once)
532 	{
533 		gettimeofday(&first, NULL);
534 		once = 0;
535 	}
536 	gettimeofday(&now, NULL);
537 	return (now.tv_sec - first.tv_sec) * 1000 + (now.tv_usec - first.tv_usec) / 1000;
538 }
539 
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)540 static int 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)
541 {
542 	fz_device *dev = NULL;
543 
544 	*bit = NULL;
545 
546 	fz_try(ctx)
547 	{
548 		fz_clear_pixmap_with_value(ctx, pix, 255);
549 
550 		dev = fz_new_draw_device(ctx, fz_identity, pix);
551 		if (alphabits_graphics == 0)
552 			fz_enable_device_hints(ctx, dev, FZ_DONT_INTERPOLATE_IMAGES);
553 		if (list)
554 			fz_run_display_list(ctx, list, dev, ctm, tbounds, cookie);
555 		else
556 			fz_run_page(ctx, page, dev, ctm, cookie);
557 		fz_close_device(ctx, dev);
558 		fz_drop_device(ctx, dev);
559 		dev = NULL;
560 
561 		if ((output_format == OUT_PBM) || (output_format == OUT_PKM))
562 			*bit = fz_new_bitmap_from_pixmap_band(ctx, pix, NULL, band_start);
563 	}
564 	fz_catch(ctx)
565 	{
566 		fz_drop_device(ctx, dev);
567 		return RENDER_RETRY;
568 	}
569 	return RENDER_OK;
570 }
571 
dodrawpage(fz_context * ctx,int pagenum,fz_cookie * cookie,render_details * render)572 static int dodrawpage(fz_context *ctx, int pagenum, fz_cookie *cookie, render_details *render)
573 {
574 	fz_pixmap *pix = NULL;
575 	fz_bitmap *bit = NULL;
576 	int errors_are_fatal = 0;
577 	fz_irect ibounds = render->ibounds;
578 	fz_rect tbounds = render->tbounds;
579 	int total_height = ibounds.y1 - ibounds.y0;
580 	int start_offset = min_band_height * render->bands_rendered;
581 	int remaining_start = ibounds.y0 + start_offset;
582 	int remaining_height = ibounds.y1 - remaining_start;
583 	int band_height = min_band_height * render->band_height_multiple;
584 	int bands = (remaining_height + band_height-1) / band_height;
585 	fz_matrix ctm = render->ctm;
586 	int band;
587 
588 	fz_var(pix);
589 	fz_var(bit);
590 	fz_var(errors_are_fatal);
591 
592 	fz_try(ctx)
593 	{
594 		/* Set up ibounds and tbounds for a single band_height band.
595 		 * We will adjust ctm as we go. */
596 		ibounds.y1 = ibounds.y0 + band_height;
597 		tbounds.y1 = tbounds.y0 + band_height + 2;
598 		DEBUG_THREADS(("Using %d Bands\n", bands));
599 		ctm.f += start_offset;
600 
601 		if (render->num_workers > 0)
602 		{
603 			for (band = 0; band < fz_mini(render->num_workers, bands); band++)
604 			{
605 				int band_start = start_offset + band * band_height;
606 				worker_t *w = &workers[band];
607 				w->band_start = band_start;
608 				w->ctm = ctm;
609 				w->tbounds = tbounds;
610 				memset(&w->cookie, 0, sizeof(fz_cookie));
611 				w->list = render->list;
612 				if (remaining_height < band_height)
613 					ibounds.y1 = ibounds.y0 + remaining_height;
614 				remaining_height -= band_height;
615 				w->pix = fz_new_pixmap_with_bbox(ctx, colorspace, ibounds, NULL, 0);
616 				fz_set_pixmap_resolution(ctx, w->pix, x_resolution, y_resolution);
617 				DEBUG_THREADS(("Worker %d, Pre-triggering band %d\n", band, band));
618 				w->started = 1;
619 				mu_trigger_semaphore(&w->start);
620 				ctm.f -= band_height;
621 			}
622 			pix = workers[0].pix;
623 		}
624 		else
625 		{
626 			pix = fz_new_pixmap_with_bbox(ctx, colorspace, ibounds, NULL, 0);
627 			fz_set_pixmap_resolution(ctx, pix, x_resolution, y_resolution);
628 		}
629 
630 		for (band = 0; band < bands; band++)
631 		{
632 			int status;
633 			int band_start = start_offset + band * band_height;
634 			int draw_height = total_height - band_start;
635 
636 			if (draw_height > band_height)
637 				draw_height = band_height;
638 
639 			if (render->num_workers > 0)
640 			{
641 				worker_t *w = &workers[band % render->num_workers];
642 				DEBUG_THREADS(("Waiting for worker %d to complete band %d\n", w->num, band));
643 				mu_wait_semaphore(&w->stop);
644 				w->started = 0;
645 				status = w->status;
646 				pix = w->pix;
647 				bit = w->bit;
648 				w->bit = NULL;
649 				cookie->errors += w->cookie.errors;
650 			}
651 			else
652 				status = drawband(ctx, render->page, render->list, ctm, tbounds, cookie, band_start, pix, &bit);
653 
654 			if (status != RENDER_OK)
655 				fz_throw(ctx, FZ_ERROR_GENERIC, "Render failed");
656 
657 			render->bands_rendered += render->band_height_multiple;
658 
659 			if (out)
660 			{
661 				/* If we get any errors while outputting the bands, retrying won't help. */
662 				errors_are_fatal = 1;
663 				fz_write_band(ctx, render->bander, bit ? bit->stride : pix->stride, draw_height, bit ? bit->samples : pix->samples);
664 				errors_are_fatal = 0;
665 			}
666 			fz_drop_bitmap(ctx, bit);
667 			bit = NULL;
668 
669 			if (render->num_workers > 0 && band + render->num_workers < bands)
670 			{
671 				worker_t *w = &workers[band % render->num_workers];
672 				w->band_start = band_start;
673 				w->ctm = ctm;
674 				w->tbounds = tbounds;
675 				memset(&w->cookie, 0, sizeof(fz_cookie));
676 				DEBUG_THREADS(("Triggering worker %d for band_start= %d\n", w->num, w->band_start));
677 				w->started = 1;
678 				mu_trigger_semaphore(&w->start);
679 			}
680 			ctm.f -= draw_height;
681 		}
682 	}
683 	fz_always(ctx)
684 	{
685 		fz_drop_bitmap(ctx, bit);
686 		bit = NULL;
687 		if (render->num_workers > 0)
688 		{
689 			int band;
690 			for (band = 0; band < fz_mini(render->num_workers, bands); band++)
691 			{
692 				worker_t *w = &workers[band];
693 				w->cookie.abort = 1;
694 				if (w->started)
695 				{
696 					mu_wait_semaphore(&w->stop);
697 					w->started = 0;
698 				}
699 				fz_drop_pixmap(ctx, w->pix);
700 			}
701 		}
702 		else
703 			fz_drop_pixmap(ctx, pix);
704 	}
705 	fz_catch(ctx)
706 	{
707 		/* Swallow error */
708 		if (errors_are_fatal)
709 			return RENDER_FATAL;
710 		return RENDER_RETRY;
711 	}
712 	if (cookie->errors)
713 		errored = 1;
714 
715 	return RENDER_OK;
716 }
717 
718 /* This functions tries to render a page, falling back repeatedly to try and make it work. */
try_render_page(fz_context * ctx,int pagenum,fz_cookie * cookie,int start,int interptime,char * fname,int bg,int solo,render_details * render)719 static int try_render_page(fz_context *ctx, int pagenum, fz_cookie *cookie, int start, int interptime, char *fname, int bg, int solo, render_details *render)
720 {
721 	int status;
722 
723 	if (out && !(bg && solo))
724 	{
725 		/* Output any page level headers (for banded formats). Don't do this if
726 		 * we're running in solo bgprint mode, cos we've already done it once! */
727 		fz_try(ctx)
728 		{
729 			int w = render->ibounds.x1 - render->ibounds.x0;
730 			int h = render->ibounds.y1 - render->ibounds.y0;
731 			fz_write_header(ctx, render->bander, w, h, render->n, 0, 0, 0, 0, 0, NULL);
732 		}
733 		fz_catch(ctx)
734 		{
735 			/* Failure! */
736 			return RENDER_FATAL;
737 		}
738 	}
739 
740 	while (1)
741 	{
742 		status = dodrawpage(ctx, pagenum, cookie, render);
743 		if (status == RENDER_OK || status == RENDER_FATAL)
744 			break;
745 
746 		/* If we are bgprinting, then ask the caller to try us again in solo mode. */
747 		if (bg && !solo)
748 		{
749 			DEBUG_THREADS(("Render failure; trying again in solo mode\n"));
750 			return RENDER_RETRY; /* Avoids all the cleanup below! */
751 		}
752 
753 		/* Try again with fewer threads */
754 		if (render->num_workers > 1)
755 		{
756 			render->num_workers >>= 1;
757 			DEBUG_THREADS(("Render failure; trying again with %d render threads\n", render->num_workers));
758 			continue;
759 		}
760 
761 		/* Halve the band height, if we still can. */
762 		if (render->band_height_multiple > 2)
763 		{
764 			render->band_height_multiple >>= 1;
765 			DEBUG_THREADS(("Render failure; trying again with %d band height multiple\n", render->band_height_multiple));
766 			continue;
767 		}
768 
769 		/* If all else fails, ditch the list and try again. */
770 		if (render->list)
771 		{
772 			fz_drop_display_list(ctx, render->list);
773 			render->list = NULL;
774 			DEBUG_THREADS(("Render failure; trying again with no list\n"));
775 			continue;
776 		}
777 
778 		/* Give up. */
779 		DEBUG_THREADS(("Render failure; nothing else to try\n"));
780 		break;
781 	}
782 
783 	fz_drop_page(ctx, render->page);
784 	fz_drop_display_list(ctx, render->list);
785 	fz_drop_band_writer(ctx, render->bander);
786 
787 	if (showtime)
788 	{
789 		int end = gettime();
790 		int diff = end - start;
791 
792 		if (bg)
793 		{
794 			if (diff + interptime < timing.min)
795 			{
796 				timing.min = diff + interptime;
797 				timing.mininterp = interptime;
798 				timing.minpage = pagenum;
799 				timing.minfilename = fname;
800 			}
801 			if (diff + interptime > timing.max)
802 			{
803 				timing.max = diff + interptime;
804 				timing.maxinterp = interptime;
805 				timing.maxpage = pagenum;
806 				timing.maxfilename = fname;
807 			}
808 			timing.total += diff + interptime;
809 			timing.count ++;
810 
811 			fprintf(stderr, " %dms (interpretation) %dms (rendering) %dms (total)\n", interptime, diff, diff + interptime);
812 		}
813 		else
814 		{
815 			if (diff < timing.min)
816 			{
817 				timing.min = diff;
818 				timing.minpage = pagenum;
819 				timing.minfilename = fname;
820 			}
821 			if (diff > timing.max)
822 			{
823 				timing.max = diff;
824 				timing.maxpage = pagenum;
825 				timing.maxfilename = fname;
826 			}
827 			timing.total += diff;
828 			timing.count ++;
829 
830 			fprintf(stderr, " %dms\n", diff);
831 		}
832 	}
833 
834 	if (showmemory)
835 	{
836 		fz_dump_glyph_cache_stats(ctx, fz_stderr(ctx));
837 	}
838 
839 	fz_flush_warnings(ctx);
840 
841 	return status;
842 }
843 
wait_for_bgprint_to_finish(void)844 static int wait_for_bgprint_to_finish(void)
845 {
846 	if (!bgprint.active || !bgprint.started)
847 		return 0;
848 
849 	mu_wait_semaphore(&bgprint.stop);
850 	bgprint.started = 0;
851 	return bgprint.status;
852 }
853 
854 static void
get_page_render_details(fz_context * ctx,fz_page * page,render_details * render)855 get_page_render_details(fz_context *ctx, fz_page *page, render_details *render)
856 {
857 	float page_width, page_height;
858 	int rot;
859 	float s_x, s_y;
860 
861 	render->page = page;
862 	render->list = NULL;
863 	render->num_workers = num_workers;
864 
865 	render->bounds = fz_bound_page(ctx, page);
866 	page_width = (render->bounds.x1 - render->bounds.x0)/72;
867 	page_height = (render->bounds.y1 - render->bounds.y0)/72;
868 
869 	s_x = x_resolution / 72;
870 	s_y = y_resolution / 72;
871 	if (rotation == -1)
872 	{
873 		/* Automatic rotation. If we fit, use 0. If we don't, and 90 would be 'better' use that. */
874 		if (page_width <= width && page_height <= height)
875 		{
876 			/* Page fits, so use no rotation. */
877 			rot = 0;
878 		}
879 		else if (fit)
880 		{
881 			/* Use whichever gives the biggest scale */
882 			float sx_0 = width / page_width;
883 			float sy_0 = height / page_height;
884 			float sx_90 = height / page_width;
885 			float sy_90 = width / page_height;
886 			float s_0, s_90;
887 			s_0 = fz_min(sx_0, sy_0);
888 			s_90 = fz_min(sx_90, sy_90);
889 			if (s_0 >= s_90)
890 			{
891 				rot = 0;
892 				if (s_0 < 1)
893 				{
894 					s_x *= s_0;
895 					s_y *= s_0;
896 				}
897 			}
898 			else
899 			{
900 				rot = 90;
901 				if (s_90 < 1)
902 				{
903 					s_x *= s_90;
904 					s_y *= s_90;
905 				}
906 			}
907 		}
908 		else
909 		{
910 			/* Use whichever crops the least area */
911 			float lost0 = 0;
912 			float lost90 = 0;
913 
914 			if (page_width > width)
915 				lost0 += (page_width - width) * (page_height > height ? height : page_height);
916 			if (page_height > height)
917 				lost0 += (page_height - height) * page_width;
918 
919 			if (page_width > height)
920 				lost90 += (page_width - height) * (page_height > width ? width : page_height);
921 			if (page_height > width)
922 				lost90 += (page_height - width) * page_width;
923 
924 			rot = (lost0 <= lost90 ? 0 : 90);
925 		}
926 	}
927 	else
928 	{
929 		rot = rotation;
930 	}
931 
932 	render->ctm = fz_pre_scale(fz_rotate(rot), s_x, s_y);
933 	render->tbounds = fz_transform_rect(render->bounds, render->ctm);;
934 	render->ibounds = fz_round_rect(render->tbounds);
935 }
936 
937 static void
initialise_banding(fz_context * ctx,render_details * render,int color)938 initialise_banding(fz_context *ctx, render_details *render, int color)
939 {
940 	size_t min_band_mem;
941 	int bpp, h, w, reps;
942 
943 	render->colorspace = output_cs;
944 	render->format = output_format;
945 #if GREY_FALLBACK != 0
946 	if (color == 0)
947 	{
948 		if (render->colorspace == CS_RGB)
949 		{
950 			/* Fallback from PPM to PGM */
951 			render->colorspace = CS_GRAY;
952 			render->format = OUT_PGM;
953 		}
954 		else if (render->colorspace == CS_CMYK)
955 		{
956 			render->colorspace = CS_GRAY;
957 			if (render->format == OUT_PKM)
958 				render->format = OUT_PBM;
959 			else
960 				render->format = OUT_PGM;
961 		}
962 	}
963 #endif
964 
965 	switch (render->colorspace)
966 	{
967 	case CS_GRAY:
968 		bpp = 1;
969 		break;
970 	case CS_RGB:
971 		bpp = 2;
972 		break;
973 	default:
974 	case CS_CMYK:
975 		bpp = 3;
976 		break;
977 	}
978 
979 	w = render->ibounds.x1 - render->ibounds.x0;
980 	min_band_mem = (size_t)bpp * w * min_band_height;
981 	reps = (int)(max_band_memory / min_band_mem);
982 	if (reps < 1)
983 		reps = 1;
984 
985 	/* Adjust reps to even out the work between threads */
986 	if (render->num_workers > 0)
987 	{
988 		int runs, num_bands;
989 		h = render->ibounds.y1 - render->ibounds.y0;
990 		num_bands = (h + min_band_height - 1) / min_band_height;
991 		/* num_bands = number of min_band_height bands */
992 		runs = (num_bands + reps-1) / reps;
993 		/* runs = number of worker runs of reps min_band_height bands */
994 		runs = ((runs + render->num_workers - 1) / render->num_workers) * render->num_workers;
995 		/* runs = number of worker runs rounded up to make use of all our threads */
996 		reps = (num_bands + runs - 1) / runs;
997 	}
998 
999 	render->band_height_multiple = reps;
1000 	render->bands_rendered = 0;
1001 
1002 	if (output_format == OUT_PGM || output_format == OUT_PPM)
1003 	{
1004 		render->bander = fz_new_pnm_band_writer(ctx, out);
1005 		render->n = output_format == OUT_PGM ? 1 : 3;
1006 	}
1007 	else if (output_format == OUT_PAM)
1008 	{
1009 		render->bander = fz_new_pam_band_writer(ctx, out);
1010 		render->n = 4;
1011 	}
1012 	else if (output_format == OUT_PBM)
1013 	{
1014 		render->bander = fz_new_pbm_band_writer(ctx, out);
1015 		render->n = 1;
1016 	}
1017 	else if (output_format == OUT_PKM)
1018 	{
1019 		render->bander = fz_new_pkm_band_writer(ctx, out);
1020 		render->n = 4;
1021 	}
1022 }
1023 
drawpage(fz_context * ctx,fz_document * doc,int pagenum)1024 static void drawpage(fz_context *ctx, fz_document *doc, int pagenum)
1025 {
1026 	fz_page *page;
1027 	fz_display_list *list = NULL;
1028 	fz_device *list_dev = NULL;
1029 	int start;
1030 	fz_cookie cookie = { 0 };
1031 #if GREY_FALLBACK != 0
1032 	fz_device *test_dev = NULL;
1033 	int is_color = 0;
1034 #else
1035 	int is_color = 2;
1036 #endif
1037 	render_details render;
1038 	int status;
1039 
1040 	fz_var(list);
1041 	fz_var(list_dev);
1042 	fz_var(test_dev);
1043 
1044 	do
1045 	{
1046 		start = (showtime ? gettime() : 0);
1047 
1048 		page = fz_load_page(ctx, doc, pagenum - 1);
1049 
1050 		/* Calculate Page bounds, transform etc */
1051 		get_page_render_details(ctx, page, &render);
1052 
1053 		/* Make the display list, and see if we need color */
1054 		fz_try(ctx)
1055 		{
1056 			list = fz_new_display_list(ctx, render.bounds);
1057 			list_dev = fz_new_list_device(ctx, list);
1058 #if GREY_FALLBACK != 0
1059 			test_dev = fz_new_test_device(ctx, &is_color, 0.01f, 0, list_dev);
1060 			fz_run_page(ctx, page, test_dev, fz_identity, &cookie);
1061 			fz_close_device(ctx, test_dev);
1062 #else
1063 			fz_run_page(ctx, page, list_dev, fz_identity, &cookie);
1064 #endif
1065 			fz_close_device(ctx, list_dev);
1066 		}
1067 		fz_always(ctx)
1068 		{
1069 #if GREY_FALLBACK != 0
1070 			fz_drop_device(ctx, test_dev);
1071 #endif
1072 			fz_drop_device(ctx, list_dev);
1073 		}
1074 		fz_catch(ctx)
1075 		{
1076 			fz_drop_display_list(ctx, list);
1077 			list = NULL;
1078 			/* Just continue with no list. Also, we can't do multiple
1079 			 * threads if we have no list. */
1080 			render.num_workers = 1;
1081 		}
1082 		render.list = list;
1083 
1084 #if GREY_FALLBACK != 0
1085 		if (list == NULL)
1086 		{
1087 			/* We need to know about color, but the previous test failed
1088 			 * (presumably) due to the size of the list. Rerun direct
1089 			 * from file. */
1090 			fz_try(ctx)
1091 			{
1092 				test_dev = fz_new_test_device(ctx, &is_color, 0.01f, 0, NULL);
1093 				fz_run_page(ctx, page, test_dev, fz_identity, &cookie);
1094 				fz_close_device(ctx, test_dev);
1095 			}
1096 			fz_always(ctx)
1097 			{
1098 				fz_drop_device(ctx, test_dev);
1099 			}
1100 			fz_catch(ctx)
1101 			{
1102 				/* We failed. Just give up. */
1103 				fz_drop_page(ctx, page);
1104 				fz_rethrow(ctx);
1105 			}
1106 		}
1107 #endif
1108 
1109 #if GREY_FALLBACK == 2
1110 		/* If we 'possibly' need color, find out if we 'really' need color. */
1111 		if (is_color == 1)
1112 		{
1113 			/* We know that the device has images or shadings in
1114 			 * colored spaces. We have been told to test exhaustively
1115 			 * so we know whether to use color or grey rendering. */
1116 			is_color = 0;
1117 			fz_try(ctx)
1118 			{
1119 				test_dev = fz_new_test_device(ctx, &is_color, 0.01f, FZ_TEST_OPT_IMAGES | FZ_TEST_OPT_SHADINGS, NULL);
1120 				if (list)
1121 					fz_run_display_list(ctx, list, test_dev, &fz_identity, &fz_infinite_rect, &cookie);
1122 				else
1123 					fz_run_page(ctx, page, test_dev, &fz_identity, &cookie);
1124 				fz_close_device(ctx, test_dev);
1125 			}
1126 			fz_always(ctx)
1127 			{
1128 				fz_drop_device(ctx, test_dev);
1129 			}
1130 			fz_catch(ctx)
1131 			{
1132 				fz_drop_display_list(ctx, list);
1133 				fz_drop_page(ctx, page);
1134 				fz_rethrow(ctx);
1135 			}
1136 		}
1137 #endif
1138 
1139 		/* Figure out banding */
1140 		initialise_banding(ctx, &render, is_color);
1141 
1142 		if (bgprint.active && showtime)
1143 		{
1144 			int end = gettime();
1145 			start = end - start;
1146 		}
1147 
1148 		/* If we're not using bgprint, then no need to wait */
1149 		if (!bgprint.active)
1150 			break;
1151 
1152 		/* If we are using it, then wait for it to finish. */
1153 		status = wait_for_bgprint_to_finish();
1154 		if (status == RENDER_OK)
1155 		{
1156 			/* The background bgprint completed successfully. Drop out of the loop,
1157 			 * and carry on with our next page. */
1158 			break;
1159 		}
1160 
1161 		/* The bgprint in the background failed! This might have been because
1162 		 * we were using memory etc in the foreground. We'd better ditch
1163 		 * everything we can and try again. */
1164 		fz_drop_display_list(ctx, list);
1165 		fz_drop_page(ctx, page);
1166 
1167 		if (status == RENDER_FATAL)
1168 		{
1169 			/* We failed because of not being able to output. No point in retrying. */
1170 			fz_throw(ctx, FZ_ERROR_GENERIC, "Failed to render page");
1171 		}
1172 		bgprint.started = 1;
1173 		bgprint.solo = 1;
1174 		mu_trigger_semaphore(&bgprint.start);
1175 		status = wait_for_bgprint_to_finish();
1176 		if (status != 0)
1177 		{
1178 			/* Hard failure */
1179 			fz_throw(ctx, FZ_ERROR_GENERIC, "Failed to render page");
1180 		}
1181 		/* Loop back to reload this page */
1182 	}
1183 	while (1);
1184 
1185 	if (showtime)
1186 	{
1187 		fprintf(stderr, "page %s %d", filename, pagenum);
1188 	}
1189 	if (bgprint.active)
1190 	{
1191 		bgprint.started = 1;
1192 		bgprint.solo = 0;
1193 		bgprint.render = render;
1194 		bgprint.filename = filename;
1195 		bgprint.pagenum = pagenum;
1196 		bgprint.interptime = start;
1197 		mu_trigger_semaphore(&bgprint.start);
1198 	}
1199 	else
1200 	{
1201 		if (try_render_page(ctx, pagenum, &cookie, start, 0, filename, 0, 0, &render))
1202 		{
1203 			/* Hard failure */
1204 			fz_throw(ctx, FZ_ERROR_GENERIC, "Failed to render page");
1205 		}
1206 	}
1207 }
1208 
1209 /* Wait for the final page being printed by bgprint to complete,
1210  * retrying if necessary. */
1211 static void
finish_bgprint(fz_context * ctx)1212 finish_bgprint(fz_context *ctx)
1213 {
1214 	int status;
1215 
1216 	if (!bgprint.active)
1217 		return;
1218 
1219 	/* If we are using it, then wait for it to finish. */
1220 	status = wait_for_bgprint_to_finish();
1221 	if (status == RENDER_OK)
1222 	{
1223 		/* The background bgprint completed successfully. */
1224 		return;
1225 	}
1226 
1227 	if (status == RENDER_FATAL)
1228 	{
1229 		/* We failed because of not being able to output. No point in retrying. */
1230 		fz_throw(ctx, FZ_ERROR_GENERIC, "Failed to render page");
1231 	}
1232 	bgprint.started = 1;
1233 	bgprint.solo = 1;
1234 	mu_trigger_semaphore(&bgprint.start);
1235 	status = wait_for_bgprint_to_finish();
1236 	if (status != 0)
1237 	{
1238 		/* Hard failure */
1239 		fz_throw(ctx, FZ_ERROR_GENERIC, "Failed to render page");
1240 	}
1241 }
1242 
drawrange(fz_context * ctx,fz_document * doc,const char * range)1243 static void drawrange(fz_context *ctx, fz_document *doc, const char *range)
1244 {
1245 	int page, spage, epage, pagecount;
1246 
1247 	pagecount = fz_count_pages(ctx, doc);
1248 
1249 	while ((range = fz_parse_page_range(ctx, range, &spage, &epage, pagecount)))
1250 	{
1251 		if (spage < epage)
1252 			for (page = spage; page <= epage; page++)
1253 				drawpage(ctx, doc, page);
1254 		else
1255 			for (page = spage; page >= epage; page--)
1256 				drawpage(ctx, doc, page);
1257 	}
1258 }
1259 
1260 typedef struct
1261 {
1262 	size_t size;
1263 #if defined(_M_IA64) || defined(_M_AMD64)
1264 	size_t align;
1265 #endif
1266 } trace_header;
1267 
1268 typedef struct
1269 {
1270 	size_t current;
1271 	size_t peak;
1272 	size_t total;
1273 } trace_info;
1274 
1275 static void *
trace_malloc(void * arg,size_t size)1276 trace_malloc(void *arg, size_t size)
1277 {
1278 	trace_info *info = (trace_info *) arg;
1279 	trace_header *p;
1280 	if (size == 0)
1281 		return NULL;
1282 	p = malloc(size + sizeof(trace_header));
1283 	if (p == NULL)
1284 		return NULL;
1285 	p[0].size = size;
1286 	info->current += size;
1287 	info->total += size;
1288 	if (info->current > info->peak)
1289 		info->peak = info->current;
1290 	return (void *)&p[1];
1291 }
1292 
1293 static void
trace_free(void * arg,void * p_)1294 trace_free(void *arg, void *p_)
1295 {
1296 	trace_info *info = (trace_info *) arg;
1297 	trace_header *p = (trace_header *)p_;
1298 
1299 	if (p == NULL)
1300 		return;
1301 	info->current -= p[-1].size;
1302 	free(&p[-1]);
1303 }
1304 
1305 static void *
trace_realloc(void * arg,void * p_,size_t size)1306 trace_realloc(void *arg, void *p_, size_t size)
1307 {
1308 	trace_info *info = (trace_info *) arg;
1309 	trace_header *p = (trace_header *)p_;
1310 	size_t oldsize;
1311 
1312 	if (size == 0)
1313 	{
1314 		trace_free(arg, p_);
1315 		return NULL;
1316 	}
1317 	if (p == NULL)
1318 		return trace_malloc(arg, size);
1319 	oldsize = p[-1].size;
1320 	p = realloc(&p[-1], size + sizeof(trace_header));
1321 	if (p == NULL)
1322 		return NULL;
1323 	info->current += size - oldsize;
1324 	if (size > oldsize)
1325 		info->total += size - oldsize;
1326 	if (info->current > info->peak)
1327 		info->peak = info->current;
1328 	p[0].size = size;
1329 	return &p[1];
1330 }
1331 
1332 #ifndef DISABLE_MUTHREADS
worker_thread(void * arg)1333 static void worker_thread(void *arg)
1334 {
1335 	worker_t *me = (worker_t *)arg;
1336 	int band_start;
1337 
1338 	do
1339 	{
1340 		DEBUG_THREADS(("Worker %d waiting\n", me->num));
1341 		mu_wait_semaphore(&me->start);
1342 		band_start = me->band_start;
1343 		DEBUG_THREADS(("Worker %d woken for band_start %d\n", me->num, me->band_start));
1344 		me->status = RENDER_OK;
1345 		if (band_start >= 0)
1346 			me->status = drawband(me->ctx, NULL, me->list, me->ctm, me->tbounds, &me->cookie, band_start, me->pix, &me->bit);
1347 		DEBUG_THREADS(("Worker %d completed band_start %d (status=%d)\n", me->num, band_start, me->status));
1348 		mu_trigger_semaphore(&me->stop);
1349 	}
1350 	while (band_start >= 0);
1351 }
1352 
bgprint_worker(void * arg)1353 static void bgprint_worker(void *arg)
1354 {
1355 	fz_cookie cookie = { 0 };
1356 	int pagenum;
1357 
1358 	(void)arg;
1359 
1360 	do
1361 	{
1362 		DEBUG_THREADS(("BGPrint waiting\n"));
1363 		mu_wait_semaphore(&bgprint.start);
1364 		pagenum = bgprint.pagenum;
1365 		DEBUG_THREADS(("BGPrint woken for pagenum %d\n", pagenum));
1366 		if (pagenum >= 0)
1367 		{
1368 			int start = gettime();
1369 			memset(&cookie, 0, sizeof(cookie));
1370 			bgprint.status = try_render_page(bgprint.ctx, pagenum, &cookie, start, bgprint.interptime, bgprint.filename, 1, bgprint.solo, &bgprint.render);
1371 		}
1372 		DEBUG_THREADS(("BGPrint completed page %d\n", pagenum));
1373 		mu_trigger_semaphore(&bgprint.stop);
1374 	}
1375 	while (pagenum >= 0);
1376 }
1377 #endif
1378 
1379 static void
read_resolution(const char * arg)1380 read_resolution(const char *arg)
1381 {
1382 	char *sep = strchr(arg, ',');
1383 
1384 	if (sep == NULL)
1385 		sep = strchr(arg, 'x');
1386 	if (sep == NULL)
1387 		sep = strchr(arg, ':');
1388 	if (sep == NULL)
1389 		sep = strchr(arg, ';');
1390 
1391 	x_resolution = fz_atoi(arg);
1392 	if (sep && sep[1])
1393 		y_resolution = fz_atoi(arg);
1394 	else
1395 		y_resolution = x_resolution;
1396 }
1397 
1398 static int
read_rotation(const char * arg)1399 read_rotation(const char *arg)
1400 {
1401 	int i;
1402 
1403 	if (strcmp(arg, "auto"))
1404 	{
1405 		return -1;
1406 	}
1407 
1408 	i = fz_atoi(arg);
1409 
1410 	i = i % 360;
1411 	if (i % 90 != 0)
1412 	{
1413 		fprintf(stderr, "Ignoring invalid rotation\n");
1414 		i = 0;
1415 	}
1416 
1417 	return i;
1418 }
1419 
main(int argc,char ** argv)1420 int main(int argc, char **argv)
1421 {
1422 	char *password = "";
1423 	fz_document *doc = NULL;
1424 	int c;
1425 	fz_context *ctx;
1426 	trace_info info = { 0, 0, 0 };
1427 	fz_alloc_context alloc_ctx = { &info, trace_malloc, trace_realloc, trace_free };
1428 	fz_locks_context *locks = NULL;
1429 
1430 	fz_var(doc);
1431 
1432 	bgprint.active = 0;			/* set by -P */
1433 	min_band_height = MIN_BAND_HEIGHT;
1434 	max_band_memory = BAND_MEMORY;
1435 	width = 0;
1436 	height = 0;
1437 	num_workers = NUM_RENDER_THREADS;
1438 	x_resolution = X_RESOLUTION;
1439 	y_resolution = Y_RESOLUTION;
1440 
1441 	while ((c = fz_getopt(argc, argv, "p:o:F:R:r:w:h:fB:M:s:A:iW:H:S:T:U:XvP")) != -1)
1442 	{
1443 		switch (c)
1444 		{
1445 		default: usage(); break;
1446 
1447 		case 'p': password = fz_optarg; break;
1448 
1449 		case 'o': output = fz_optarg; break;
1450 		case 'F': format = fz_optarg; break;
1451 
1452 		case 'R': rotation = read_rotation(fz_optarg); break;
1453 		case 'r': read_resolution(fz_optarg); break;
1454 		case 'w': width = fz_atof(fz_optarg); break;
1455 		case 'h': height = fz_atof(fz_optarg); break;
1456 		case 'f': fit = 1; break;
1457 		case 'B': min_band_height = atoi(fz_optarg); break;
1458 		case 'M': max_band_memory = atoi(fz_optarg); break;
1459 
1460 		case 'W': layout_w = fz_atof(fz_optarg); break;
1461 		case 'H': layout_h = fz_atof(fz_optarg); break;
1462 		case 'S': layout_em = fz_atof(fz_optarg); break;
1463 		case 'U': layout_css = fz_optarg; break;
1464 		case 'X': layout_use_doc_css = 0; break;
1465 
1466 		case 's':
1467 			if (strchr(fz_optarg, 't')) ++showtime;
1468 			if (strchr(fz_optarg, 'm')) ++showmemory;
1469 			break;
1470 
1471 		case 'A':
1472 		{
1473 			char *sep;
1474 			alphabits_graphics = atoi(fz_optarg);
1475 			sep = strchr(fz_optarg, '/');
1476 			if (sep)
1477 				alphabits_text = atoi(sep+1);
1478 			else
1479 				alphabits_text = alphabits_graphics;
1480 			break;
1481 		}
1482 		case 'i': ignore_errors = 1; break;
1483 
1484 		case 'T':
1485 #if MURASTER_THREADS != 0
1486 			num_workers = atoi(fz_optarg); break;
1487 #else
1488 			fprintf(stderr, "Threads not enabled in this build\n");
1489 			break;
1490 #endif
1491 		case 'P':
1492 #if MURASTER_THREADS != 0
1493 			bgprint.active = 1; break;
1494 #else
1495 			fprintf(stderr, "Threads not enabled in this build\n");
1496 			break;
1497 #endif
1498 		case 'v': fprintf(stderr, "muraster version %s\n", FZ_VERSION); return 1;
1499 		}
1500 	}
1501 
1502 	if (width == 0)
1503 		width = x_resolution * PAPER_WIDTH;
1504 
1505 	if (height == 0)
1506 		height = y_resolution * PAPER_HEIGHT;
1507 
1508 	if (fz_optind == argc)
1509 		usage();
1510 
1511 	if (min_band_height <= 0)
1512 	{
1513 		fprintf(stderr, "Require a positive minimum band height\n");
1514 		exit(1);
1515 	}
1516 
1517 #ifndef DISABLE_MUTHREADS
1518 	locks = init_muraster_locks();
1519 	if (locks == NULL)
1520 	{
1521 		fprintf(stderr, "cannot initialise mutexes\n");
1522 		exit(1);
1523 	}
1524 #endif
1525 
1526 	ctx = fz_new_context((showmemory == 0 ? NULL : &alloc_ctx), locks, FZ_STORE_DEFAULT);
1527 	if (!ctx)
1528 	{
1529 		fprintf(stderr, "cannot initialise context\n");
1530 		exit(1);
1531 	}
1532 
1533 	fz_set_text_aa_level(ctx, alphabits_text);
1534 	fz_set_graphics_aa_level(ctx, alphabits_graphics);
1535 
1536 #ifndef DISABLE_MUTHREADS
1537 	if (bgprint.active)
1538 	{
1539 		int fail = 0;
1540 		bgprint.ctx = fz_clone_context(ctx);
1541 		fail |= mu_create_semaphore(&bgprint.start);
1542 		fail |= mu_create_semaphore(&bgprint.stop);
1543 		fail |= mu_create_thread(&bgprint.thread, bgprint_worker, NULL);
1544 		if (fail)
1545 		{
1546 			fprintf(stderr, "bgprint startup failed\n");
1547 			exit(1);
1548 		}
1549 	}
1550 
1551 	if (num_workers > 0)
1552 	{
1553 		int i;
1554 		int fail = 0;
1555 		workers = fz_calloc(ctx, num_workers, sizeof(*workers));
1556 		for (i = 0; i < num_workers; i++)
1557 		{
1558 			workers[i].ctx = fz_clone_context(ctx);
1559 			workers[i].num = i;
1560 			fail |= mu_create_semaphore(&workers[i].start);
1561 			fail |= mu_create_semaphore(&workers[i].stop);
1562 			fail |= mu_create_thread(&workers[i].thread, worker_thread, &workers[i]);
1563 		}
1564 		if (fail)
1565 		{
1566 			fprintf(stderr, "worker startup failed\n");
1567 			exit(1);
1568 		}
1569 	}
1570 #endif /* DISABLE_MUTHREADS */
1571 
1572 	if (layout_css)
1573 	{
1574 		fz_buffer *buf = fz_read_file(ctx, layout_css);
1575 		fz_set_user_css(ctx, fz_string_from_buffer(ctx, buf));
1576 		fz_drop_buffer(ctx, buf);
1577 	}
1578 
1579 	fz_set_use_document_css(ctx, layout_use_doc_css);
1580 
1581 	output_format = suffix_table[0].format;
1582 	output_cs = suffix_table[0].cs;
1583 	if (format)
1584 	{
1585 		int i;
1586 
1587 		for (i = 0; i < (int)nelem(suffix_table); i++)
1588 		{
1589 			if (!strcmp(format, suffix_table[i].suffix+1))
1590 			{
1591 				output_format = suffix_table[i].format;
1592 				output_cs = suffix_table[i].cs;
1593 				break;
1594 			}
1595 		}
1596 		if (i == (int)nelem(suffix_table))
1597 		{
1598 			fprintf(stderr, "Unknown output format '%s'\n", format);
1599 			exit(1);
1600 		}
1601 	}
1602 	else if (output)
1603 	{
1604 		char *suffix = output;
1605 		int i;
1606 
1607 		for (i = 0; i < (int)nelem(suffix_table); i++)
1608 		{
1609 			char *s = strstr(suffix, suffix_table[i].suffix);
1610 
1611 			if (s != NULL)
1612 			{
1613 				suffix = s+1;
1614 				output_format = suffix_table[i].format;
1615 				output_cs = suffix_table[i].cs;
1616 				i = 0;
1617 			}
1618 		}
1619 	}
1620 
1621 	switch (output_cs)
1622 	{
1623 	case CS_GRAY:
1624 		colorspace = fz_device_gray(ctx);
1625 		break;
1626 	case CS_RGB:
1627 		colorspace = fz_device_rgb(ctx);
1628 		break;
1629 	case CS_CMYK:
1630 		colorspace = fz_device_cmyk(ctx);
1631 		break;
1632 	}
1633 
1634 	if (output && (output[0] != '-' || output[1] != 0) && *output != 0)
1635 	{
1636 		out = fz_new_output_with_path(ctx, output, 0);
1637 	}
1638 	else
1639 		out = fz_stdout(ctx);
1640 
1641 	timing.count = 0;
1642 	timing.total = 0;
1643 	timing.min = 1 << 30;
1644 	timing.max = 0;
1645 	timing.mininterp = 1 << 30;
1646 	timing.maxinterp = 0;
1647 	timing.minpage = 0;
1648 	timing.maxpage = 0;
1649 	timing.minfilename = "";
1650 	timing.maxfilename = "";
1651 
1652 	fz_try(ctx)
1653 	{
1654 		fz_register_document_handlers(ctx);
1655 
1656 		while (fz_optind < argc)
1657 		{
1658 			fz_try(ctx)
1659 			{
1660 				filename = argv[fz_optind++];
1661 
1662 				doc = fz_open_document(ctx, filename);
1663 
1664 				if (fz_needs_password(ctx, doc))
1665 				{
1666 					if (!fz_authenticate_password(ctx, doc, password))
1667 						fz_throw(ctx, FZ_ERROR_GENERIC, "cannot authenticate password: %s", filename);
1668 				}
1669 
1670 				fz_layout_document(ctx, doc, layout_w, layout_h, layout_em);
1671 
1672 				if (fz_optind == argc || !fz_is_page_range(ctx, argv[fz_optind]))
1673 					drawrange(ctx, doc, "1-N");
1674 				if (fz_optind < argc && fz_is_page_range(ctx, argv[fz_optind]))
1675 					drawrange(ctx, doc, argv[fz_optind++]);
1676 
1677 				fz_drop_document(ctx, doc);
1678 				doc = NULL;
1679 			}
1680 			fz_catch(ctx)
1681 			{
1682 				if (!ignore_errors)
1683 					fz_rethrow(ctx);
1684 
1685 				fz_drop_document(ctx, doc);
1686 				doc = NULL;
1687 				fz_warn(ctx, "ignoring error in '%s'", filename);
1688 			}
1689 		}
1690 		finish_bgprint(ctx);
1691 	}
1692 	fz_catch(ctx)
1693 	{
1694 		fz_drop_document(ctx, doc);
1695 		fprintf(stderr, "error: cannot draw '%s'\n", filename);
1696 		errored = 1;
1697 	}
1698 
1699 	if (showtime && timing.count > 0)
1700 	{
1701 		fprintf(stderr, "total %dms / %d pages for an average of %dms\n",
1702 			timing.total, timing.count, timing.total / timing.count);
1703 		fprintf(stderr, "fastest page %d: %dms\n", timing.minpage, timing.min);
1704 		fprintf(stderr, "slowest page %d: %dms\n", timing.maxpage, timing.max);
1705 	}
1706 
1707 #ifndef DISABLE_MUTHREADS
1708 	if (num_workers > 0)
1709 	{
1710 		int i;
1711 		for (i = 0; i < num_workers; i++)
1712 		{
1713 			workers[i].band_start = -1;
1714 			mu_trigger_semaphore(&workers[i].start);
1715 			mu_wait_semaphore(&workers[i].stop);
1716 			mu_destroy_semaphore(&workers[i].start);
1717 			mu_destroy_semaphore(&workers[i].stop);
1718 			mu_destroy_thread(&workers[i].thread);
1719 			fz_drop_context(workers[i].ctx);
1720 		}
1721 		fz_free(ctx, workers);
1722 	}
1723 
1724 	if (bgprint.active)
1725 	{
1726 		bgprint.pagenum = -1;
1727 		mu_trigger_semaphore(&bgprint.start);
1728 		mu_wait_semaphore(&bgprint.stop);
1729 		mu_destroy_semaphore(&bgprint.start);
1730 		mu_destroy_semaphore(&bgprint.stop);
1731 		mu_destroy_thread(&bgprint.thread);
1732 		fz_drop_context(bgprint.ctx);
1733 	}
1734 #endif /* DISABLE_MUTHREADS */
1735 
1736 	fz_close_output(ctx, out);
1737 	fz_drop_output(ctx, out);
1738 	out = NULL;
1739 
1740 	fz_drop_context(ctx);
1741 #ifndef DISABLE_MUTHREADS
1742 	fin_muraster_locks();
1743 #endif /* DISABLE_MUTHREADS */
1744 
1745 	if (showmemory)
1746 	{
1747 		char buf[100];
1748 		fz_snprintf(buf, sizeof buf, "Memory use total=%zu peak=%zu current=%zu", info.total, info.peak, info.current);
1749 		fprintf(stderr, "%s\n", buf);
1750 	}
1751 
1752 	return (errored != 0);
1753 }
1754