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