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
147struct timeval;
148struct timezone;
149int 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
243enum {
244 OUT_PGM,
245 OUT_PPM,
246 OUT_PAM,
247 OUT_PBM,
248 OUT_PKM
249};
250
251enum {
252 CS_GRAY,
253 CS_RGB,
254 CS_CMYK
255};
256
257typedef struct
258{
259 char *suffix;
260 int format;
261 int cs;
262} suffix_t;
263
264static 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
285static mu_mutex mutexes[FZ_LOCK_MAX];
286
287static void muraster_lock(void *user, int lock)
288{
289 mu_lock_mutex(&mutexes[lock]);
290}
291
292static void muraster_unlock(void *user, int lock)
293{
294 mu_unlock_mutex(&mutexes[lock]);
295}
296
297static fz_locks_context muraster_locks =
298{
299 NULL, muraster_lock, muraster_unlock
300};
301
302static 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
310static 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
353typedef 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
370static char *output = NULL;
371static fz_output *out = NULL;
372
373static char *format;
374static int output_format;
375static int output_cs;
376
377static int rotation = -1;
378static float x_resolution;
379static float y_resolution;
380static int width = 0;
381static int height = 0;
382static int fit = 0;
383
384static float layout_w = FZ_DEFAULT_LAYOUT_W;
385static float layout_h = FZ_DEFAULT_LAYOUT_H;
386static float layout_em = FZ_DEFAULT_LAYOUT_EM;
387static char *layout_css = NULL;
388static int layout_use_doc_css = 1;
389
390static int showtime = 0;
391static int showmemory = 0;
392
393static int ignore_errors = 0;
394static int alphabits_text = 8;
395static int alphabits_graphics = 8;
396
397static int min_band_height;
398static size_t max_band_memory;
399
400static int errored = 0;
401static fz_colorspace *colorspace;
402static char *filename;
403static int num_workers = 0;
404static worker_t *workers;
405
406typedef 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
451enum
452{
453 RENDER_OK = 0,
454 RENDER_RETRY = 1,
455 RENDER_FATAL = 2
456};
457
458static 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
473static 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
484static 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
526static 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
540static 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
572static 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. */
719static int try_render_page(fz_context *ctx, int pagenum, fz_cookie *cookie, int start, int interptime, char *filename, 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 = filename;
800 }
801 if (diff + interptime > timing.max)
802 {
803 timing.max = diff + interptime;
804 timing.maxinterp = interptime;
805 timing.maxpage = pagenum;
806 timing.maxfilename = filename;
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 = filename;
820 }
821 if (diff > timing.max)
822 {
823 timing.max = diff;
824 timing.maxpage = pagenum;
825 timing.maxfilename = filename;
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);
837 }
838
839 fz_flush_warnings(ctx);
840
841 return status;
842}
843
844static 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
854static void
855get_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
937static void
938initialise_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 = 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
1024static 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. */
1211static void
1212finish_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
1243static 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
1260typedef struct
1261{
1262 size_t size;
1263#if defined(_M_IA64) || defined(_M_AMD64)
1264 size_t align;
1265#endif
1266} trace_header;
1267
1268typedef struct
1269{
1270 size_t current;
1271 size_t peak;
1272 size_t total;
1273} trace_info;
1274
1275static void *
1276trace_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
1293static void
1294trace_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
1305static void *
1306trace_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
1333static void worker_thread(void *arg)
1334{
1335 worker_t *me = (worker_t *)arg;
1336
1337 do
1338 {
1339 DEBUG_THREADS(("Worker %d waiting\n", me->num));
1340 mu_wait_semaphore(&me->start);
1341 DEBUG_THREADS(("Worker %d woken for band_start %d\n", me->num, me->band_start));
1342 me->status = RENDER_OK;
1343 if (me->band_start >= 0)
1344 me->status = drawband(me->ctx, NULL, me->list, me->ctm, me->tbounds, &me->cookie, me->band_start, me->pix, &me->bit);
1345 DEBUG_THREADS(("Worker %d completed band_start %d (status=%d)\n", me->num, me->band_start, me->status));
1346 mu_trigger_semaphore(&me->stop);
1347 }
1348 while (me->band_start >= 0);
1349}
1350
1351static void bgprint_worker(void *arg)
1352{
1353 fz_cookie cookie = { 0 };
1354 int pagenum;
1355
1356 (void)arg;
1357
1358 do
1359 {
1360 DEBUG_THREADS(("BGPrint waiting\n"));
1361 mu_wait_semaphore(&bgprint.start);
1362 pagenum = bgprint.pagenum;
1363 DEBUG_THREADS(("BGPrint woken for pagenum %d\n", pagenum));
1364 if (pagenum >= 0)
1365 {
1366 int start = gettime();
1367 memset(&cookie, 0, sizeof(cookie));
1368 bgprint.status = try_render_page(bgprint.ctx, pagenum, &cookie, start, bgprint.interptime, bgprint.filename, 1, bgprint.solo, &bgprint.render);
1369 }
1370 DEBUG_THREADS(("BGPrint completed page %d\n", pagenum));
1371 mu_trigger_semaphore(&bgprint.stop);
1372 }
1373 while (pagenum >= 0);
1374}
1375#endif
1376
1377static void
1378read_resolution(const char *arg)
1379{
1380 char *sep = strchr(arg, ',');
1381
1382 if (sep == NULL)
1383 sep = strchr(arg, 'x');
1384 if (sep == NULL)
1385 sep = strchr(arg, ':');
1386 if (sep == NULL)
1387 sep = strchr(arg, ';');
1388
1389 x_resolution = fz_atoi(arg);
1390 if (sep && sep[1])
1391 y_resolution = fz_atoi(arg);
1392 else
1393 y_resolution = x_resolution;
1394}
1395
1396static int
1397read_rotation(const char *arg)
1398{
1399 int i;
1400
1401 if (strcmp(arg, "auto"))
1402 {
1403 return -1;
1404 }
1405
1406 i = fz_atoi(arg);
1407
1408 i = i % 360;
1409 if (i % 90 != 0)
1410 {
1411 fprintf(stderr, "Ignoring invalid rotation\n");
1412 i = 0;
1413 }
1414
1415 return i;
1416}
1417
1418int main(int argc, char **argv)
1419{
1420 char *password = "";
1421 fz_document *doc = NULL;
1422 int c;
1423 fz_context *ctx;
1424 trace_info info = { 0, 0, 0 };
1425 fz_alloc_context alloc_ctx = { &info, trace_malloc, trace_realloc, trace_free };
1426 fz_locks_context *locks = NULL;
1427
1428 fz_var(doc);
1429
1430 bgprint.active = 0; /* set by -P */
1431 min_band_height = MIN_BAND_HEIGHT;
1432 max_band_memory = BAND_MEMORY;
1433 width = 0;
1434 height = 0;
1435 num_workers = NUM_RENDER_THREADS;
1436 x_resolution = X_RESOLUTION;
1437 y_resolution = Y_RESOLUTION;
1438
1439 while ((c = fz_getopt(argc, argv, "p:o:F:R:r:w:h:fB:M:s:A:iW:H:S:T:U:XvP")) != -1)
1440 {
1441 switch (c)
1442 {
1443 default: usage(); break;
1444
1445 case 'p': password = fz_optarg; break;
1446
1447 case 'o': output = fz_optarg; break;
1448 case 'F': format = fz_optarg; break;
1449
1450 case 'R': rotation = read_rotation(fz_optarg); break;
1451 case 'r': read_resolution(fz_optarg); break;
1452 case 'w': width = fz_atof(fz_optarg); break;
1453 case 'h': height = fz_atof(fz_optarg); break;
1454 case 'f': fit = 1; break;
1455 case 'B': min_band_height = atoi(fz_optarg); break;
1456 case 'M': max_band_memory = atoi(fz_optarg); break;
1457
1458 case 'W': layout_w = fz_atof(fz_optarg); break;
1459 case 'H': layout_h = fz_atof(fz_optarg); break;
1460 case 'S': layout_em = fz_atof(fz_optarg); break;
1461 case 'U': layout_css = fz_optarg; break;
1462 case 'X': layout_use_doc_css = 0; break;
1463
1464 case 's':
1465 if (strchr(fz_optarg, 't')) ++showtime;
1466 if (strchr(fz_optarg, 'm')) ++showmemory;
1467 break;
1468
1469 case 'A':
1470 {
1471 char *sep;
1472 alphabits_graphics = atoi(fz_optarg);
1473 sep = strchr(fz_optarg, '/');
1474 if (sep)
1475 alphabits_text = atoi(sep+1);
1476 else
1477 alphabits_text = alphabits_graphics;
1478 break;
1479 }
1480 case 'i': ignore_errors = 1; break;
1481
1482 case 'T':
1483#if MURASTER_THREADS != 0
1484 num_workers = atoi(fz_optarg); break;
1485#else
1486 fprintf(stderr, "Threads not enabled in this build\n");
1487 break;
1488#endif
1489 case 'P':
1490#if MURASTER_THREADS != 0
1491 bgprint.active = 1; break;
1492#else
1493 fprintf(stderr, "Threads not enabled in this build\n");
1494 break;
1495#endif
1496 case 'v': fprintf(stderr, "muraster version %s\n", FZ_VERSION); return 1;
1497 }
1498 }
1499
1500 if (width == 0)
1501 width = x_resolution * PAPER_WIDTH;
1502
1503 if (height == 0)
1504 height = y_resolution * PAPER_HEIGHT;
1505
1506 if (fz_optind == argc)
1507 usage();
1508
1509 if (min_band_height <= 0)
1510 {
1511 fprintf(stderr, "Require a positive minimum band height\n");
1512 exit(1);
1513 }
1514
1515#ifndef DISABLE_MUTHREADS
1516 locks = init_muraster_locks();
1517 if (locks == NULL)
1518 {
1519 fprintf(stderr, "cannot initialise mutexes\n");
1520 exit(1);
1521 }
1522#endif
1523
1524 ctx = fz_new_context((showmemory == 0 ? NULL : &alloc_ctx), locks, FZ_STORE_DEFAULT);
1525 if (!ctx)
1526 {
1527 fprintf(stderr, "cannot initialise context\n");
1528 exit(1);
1529 }
1530
1531 fz_set_text_aa_level(ctx, alphabits_text);
1532 fz_set_graphics_aa_level(ctx, alphabits_graphics);
1533
1534#ifndef DISABLE_MUTHREADS
1535 if (bgprint.active)
1536 {
1537 int fail = 0;
1538 bgprint.ctx = fz_clone_context(ctx);
1539 fail |= mu_create_semaphore(&bgprint.start);
1540 fail |= mu_create_semaphore(&bgprint.stop);
1541 fail |= mu_create_thread(&bgprint.thread, bgprint_worker, NULL);
1542 if (fail)
1543 {
1544 fprintf(stderr, "bgprint startup failed\n");
1545 exit(1);
1546 }
1547 }
1548
1549 if (num_workers > 0)
1550 {
1551 int i;
1552 int fail = 0;
1553 workers = fz_calloc(ctx, num_workers, sizeof(*workers));
1554 for (i = 0; i < num_workers; i++)
1555 {
1556 workers[i].ctx = fz_clone_context(ctx);
1557 workers[i].num = i;
1558 fail |= mu_create_semaphore(&workers[i].start);
1559 fail |= mu_create_semaphore(&workers[i].stop);
1560 fail |= mu_create_thread(&workers[i].thread, worker_thread, &workers[i]);
1561 }
1562 if (fail)
1563 {
1564 fprintf(stderr, "worker startup failed\n");
1565 exit(1);
1566 }
1567 }
1568#endif /* DISABLE_MUTHREADS */
1569
1570 if (layout_css)
1571 {
1572 fz_buffer *buf = fz_read_file(ctx, layout_css);
1573 fz_set_user_css(ctx, fz_string_from_buffer(ctx, buf));
1574 fz_drop_buffer(ctx, buf);
1575 }
1576
1577 fz_set_use_document_css(ctx, layout_use_doc_css);
1578
1579 output_format = suffix_table[0].format;
1580 output_cs = suffix_table[0].cs;
1581 if (format)
1582 {
1583 int i;
1584
1585 for (i = 0; i < nelem(suffix_table); i++)
1586 {
1587 if (!strcmp(format, suffix_table[i].suffix+1))
1588 {
1589 output_format = suffix_table[i].format;
1590 output_cs = suffix_table[i].cs;
1591 break;
1592 }
1593 }
1594 if (i == nelem(suffix_table))
1595 {
1596 fprintf(stderr, "Unknown output format '%s'\n", format);
1597 exit(1);
1598 }
1599 }
1600 else if (output)
1601 {
1602 char *suffix = output;
1603 int i;
1604
1605 for (i = 0; i < nelem(suffix_table); i++)
1606 {
1607 char *s = strstr(suffix, suffix_table[i].suffix);
1608
1609 if (s != NULL)
1610 {
1611 suffix = s+1;
1612 output_format = suffix_table[i].format;
1613 output_cs = suffix_table[i].cs;
1614 i = 0;
1615 }
1616 }
1617 }
1618
1619 switch (output_cs)
1620 {
1621 case CS_GRAY:
1622 colorspace = fz_device_gray(ctx);
1623 break;
1624 case CS_RGB:
1625 colorspace = fz_device_rgb(ctx);
1626 break;
1627 case CS_CMYK:
1628 colorspace = fz_device_cmyk(ctx);
1629 break;
1630 }
1631
1632 if (output && (output[0] != '-' || output[1] != 0) && *output != 0)
1633 {
1634 out = fz_new_output_with_path(ctx, output, 0);
1635 }
1636 else
1637 out = fz_stdout(ctx);
1638
1639 timing.count = 0;
1640 timing.total = 0;
1641 timing.min = 1 << 30;
1642 timing.max = 0;
1643 timing.mininterp = 1 << 30;
1644 timing.maxinterp = 0;
1645 timing.minpage = 0;
1646 timing.maxpage = 0;
1647 timing.minfilename = "";
1648 timing.maxfilename = "";
1649
1650 fz_try(ctx)
1651 {
1652 fz_register_document_handlers(ctx);
1653
1654 while (fz_optind < argc)
1655 {
1656 fz_try(ctx)
1657 {
1658 filename = argv[fz_optind++];
1659
1660 doc = fz_open_document(ctx, filename);
1661
1662 if (fz_needs_password(ctx, doc))
1663 {
1664 if (!fz_authenticate_password(ctx, doc, password))
1665 fz_throw(ctx, FZ_ERROR_GENERIC, "cannot authenticate password: %s", filename);
1666 }
1667
1668 fz_layout_document(ctx, doc, layout_w, layout_h, layout_em);
1669
1670 if (fz_optind == argc || !fz_is_page_range(ctx, argv[fz_optind]))
1671 drawrange(ctx, doc, "1-N");
1672 if (fz_optind < argc && fz_is_page_range(ctx, argv[fz_optind]))
1673 drawrange(ctx, doc, argv[fz_optind++]);
1674
1675 fz_drop_document(ctx, doc);
1676 doc = NULL;
1677 }
1678 fz_catch(ctx)
1679 {
1680 if (!ignore_errors)
1681 fz_rethrow(ctx);
1682
1683 fz_drop_document(ctx, doc);
1684 doc = NULL;
1685 fz_warn(ctx, "ignoring error in '%s'", filename);
1686 }
1687 }
1688 finish_bgprint(ctx);
1689 }
1690 fz_catch(ctx)
1691 {
1692 fz_drop_document(ctx, doc);
1693 fprintf(stderr, "error: cannot draw '%s'\n", filename);
1694 errored = 1;
1695 }
1696
1697 if (showtime && timing.count > 0)
1698 {
1699 fprintf(stderr, "total %dms / %d pages for an average of %dms\n",
1700 timing.total, timing.count, timing.total / timing.count);
1701 fprintf(stderr, "fastest page %d: %dms\n", timing.minpage, timing.min);
1702 fprintf(stderr, "slowest page %d: %dms\n", timing.maxpage, timing.max);
1703 }
1704
1705#ifndef DISABLE_MUTHREADS
1706 if (num_workers > 0)
1707 {
1708 int i;
1709 for (i = 0; i < num_workers; i++)
1710 {
1711 workers[i].band_start = -1;
1712 mu_trigger_semaphore(&workers[i].start);
1713 mu_wait_semaphore(&workers[i].stop);
1714 mu_destroy_semaphore(&workers[i].start);
1715 mu_destroy_semaphore(&workers[i].stop);
1716 mu_destroy_thread(&workers[i].thread);
1717 fz_drop_context(workers[i].ctx);
1718 }
1719 fz_free(ctx, workers);
1720 }
1721
1722 if (bgprint.active)
1723 {
1724 bgprint.pagenum = -1;
1725 mu_trigger_semaphore(&bgprint.start);
1726 mu_wait_semaphore(&bgprint.stop);
1727 mu_destroy_semaphore(&bgprint.start);
1728 mu_destroy_semaphore(&bgprint.stop);
1729 mu_destroy_thread(&bgprint.thread);
1730 fz_drop_context(bgprint.ctx);
1731 }
1732#endif /* DISABLE_MUTHREADS */
1733
1734 fz_close_output(ctx, out);
1735 fz_drop_output(ctx, out);
1736 out = NULL;
1737
1738 fz_drop_context(ctx);
1739#ifndef DISABLE_MUTHREADS
1740 fin_muraster_locks();
1741#endif /* DISABLE_MUTHREADS */
1742
1743 if (showmemory)
1744 {
1745 char buf[100];
1746 fz_snprintf(buf, sizeof buf, "Memory use total=%zu peak=%zu current=%zu", info.total, info.peak, info.current);
1747 fprintf(stderr, "%s\n", buf);
1748 }
1749
1750 return (errored != 0);
1751}
1752