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