comparison mupdf-source/source/tools/muraster.c @ 2:b50eed0cc0ef upstream

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