/* * mudraw -- command line tool for drawing and converting documents */ #include "mupdf/fitz.h" #if FZ_ENABLE_PDF #include "mupdf/pdf.h" /* for pdf output */ #endif #ifndef DISABLE_MUTHREADS #include "mupdf/helpers/mu-threads.h" #endif #include #include #include #ifdef _MSC_VER struct timeval; struct timezone; int gettimeofday(struct timeval *tv, struct timezone *tz); #else #include #endif /* Enable for helpful threading debug */ /* #define DEBUG_THREADS(A) do { printf A; fflush(stdout); } while (0) */ #define DEBUG_THREADS(A) do { } while (0) enum { OUT_NONE, OUT_PNG, OUT_TGA, OUT_PNM, OUT_PGM, OUT_PPM, OUT_PAM, OUT_PBM, OUT_PKM, OUT_PWG, OUT_PCL, OUT_PS, OUT_TEXT, OUT_HTML, OUT_STEXT, OUT_TRACE, OUT_SVG, #if FZ_ENABLE_PDF OUT_PDF, #endif OUT_GPROOF }; enum { CS_INVALID, CS_UNSET, CS_MONO, CS_GRAY, CS_GRAY_ALPHA, CS_RGB, CS_RGB_ALPHA, CS_CMYK, CS_CMYK_ALPHA }; typedef struct { char *suffix; int format; } suffix_t; static const suffix_t suffix_table[] = { { ".png", OUT_PNG }, { ".pgm", OUT_PGM }, { ".ppm", OUT_PPM }, { ".pnm", OUT_PNM }, { ".pam", OUT_PAM }, { ".pbm", OUT_PBM }, { ".pkm", OUT_PKM }, { ".svg", OUT_SVG }, { ".pwg", OUT_PWG }, { ".pcl", OUT_PCL }, { ".ps", OUT_PS }, #if FZ_ENABLE_PDF { ".pdf", OUT_PDF }, #endif { ".tga", OUT_TGA }, { ".txt", OUT_TEXT }, { ".text", OUT_TEXT }, { ".html", OUT_HTML }, { ".stext", OUT_STEXT }, { ".trace", OUT_TRACE }, { ".gproof", OUT_GPROOF }, }; typedef struct { char *name; int colorspace; } cs_name_t; static const cs_name_t cs_name_table[] = { { "m", CS_MONO }, { "mono", CS_MONO }, { "g", CS_GRAY }, { "gray", CS_GRAY }, { "grey", CS_GRAY }, { "ga", CS_GRAY_ALPHA }, { "grayalpha", CS_GRAY_ALPHA }, { "greyalpha", CS_GRAY_ALPHA }, { "rgb", CS_RGB }, { "rgba", CS_RGB_ALPHA }, { "rgbalpha", CS_RGB_ALPHA }, { "cmyk", CS_CMYK }, { "cmyka", CS_CMYK_ALPHA }, { "cmykalpha", CS_CMYK_ALPHA }, }; typedef struct { int format; int default_cs; int permitted_cs[6]; } format_cs_table_t; static const format_cs_table_t format_cs_table[] = { { OUT_PNG, CS_RGB, { CS_GRAY, CS_GRAY_ALPHA, CS_RGB, CS_RGB_ALPHA } }, { OUT_PPM, CS_RGB, { CS_GRAY, CS_RGB } }, { OUT_PNM, CS_GRAY, { CS_GRAY, CS_RGB } }, { OUT_PAM, CS_RGB_ALPHA, { CS_GRAY, CS_GRAY_ALPHA, CS_RGB, CS_RGB_ALPHA, CS_CMYK, CS_CMYK_ALPHA } }, { OUT_PGM, CS_GRAY, { CS_GRAY, CS_RGB } }, { OUT_PBM, CS_MONO, { CS_MONO } }, { OUT_PKM, CS_CMYK, { CS_CMYK } }, { OUT_PWG, CS_RGB, { CS_MONO, CS_GRAY, CS_RGB, CS_CMYK } }, { OUT_PCL, CS_MONO, { CS_MONO, CS_RGB } }, { OUT_PS, CS_RGB, { CS_GRAY, CS_RGB, CS_CMYK } }, { OUT_TGA, CS_RGB, { CS_GRAY, CS_GRAY_ALPHA, CS_RGB, CS_RGB_ALPHA } }, { OUT_TRACE, CS_RGB, { CS_RGB } }, { OUT_SVG, CS_RGB, { CS_RGB } }, #if FZ_ENABLE_PDF { OUT_PDF, CS_RGB, { CS_RGB } }, #endif { OUT_GPROOF, CS_RGB, { CS_RGB } }, { OUT_TEXT, CS_RGB, { CS_RGB } }, { OUT_HTML, CS_RGB, { CS_RGB } }, { OUT_STEXT, CS_RGB, { CS_RGB } }, }; /* In the presence of pthreads or Windows threads, we can offer a multi-threaded option. In the absence, of such, we degrade nicely. */ #ifndef DISABLE_MUTHREADS static mu_mutex mutexes[FZ_LOCK_MAX]; static void mudraw_lock(void *user, int lock) { mu_lock_mutex(&mutexes[lock]); } static void mudraw_unlock(void *user, int lock) { mu_unlock_mutex(&mutexes[lock]); } static fz_locks_context mudraw_locks = { NULL, mudraw_lock, mudraw_unlock }; static void fin_mudraw_locks(void) { int i; for (i = 0; i < FZ_LOCK_MAX; i++) mu_destroy_mutex(&mutexes[i]); } static fz_locks_context *init_mudraw_locks(void) { int i; int failed = 0; for (i = 0; i < FZ_LOCK_MAX; i++) failed |= mu_create_mutex(&mutexes[i]); if (failed) { fin_mudraw_locks(); return NULL; } return &mudraw_locks; } #endif typedef struct worker_t { fz_context *ctx; int num; int band; /* -1 to shutdown, or band to render */ fz_display_list *list; fz_matrix ctm; fz_rect tbounds; fz_pixmap *pix; fz_bitmap *bit; fz_cookie cookie; #ifndef DISABLE_MUTHREADS mu_semaphore start; mu_semaphore stop; mu_thread thread; #endif } worker_t; static char *output = NULL; fz_output *out = NULL; static int output_pagenum = 0; static int output_append = 0; static int output_file_per_page = 0; static char *format = NULL; static int output_format = OUT_NONE; static float rotation = 0; static float resolution = 72; static int res_specified = 0; static int width = 0; static int height = 0; static int fit = 0; static float layout_w = 450; static float layout_h = 600; static float layout_em = 12; static char *layout_css = NULL; static int layout_use_doc_css = 1; static float min_line_width = 0.0f; static int showfeatures = 0; static int showtime = 0; static size_t memtrace_current = 0; static size_t memtrace_peak = 0; static size_t memtrace_total = 0; static int showmemory = 0; static int showmd5 = 0; #if FZ_ENABLE_PDF static pdf_document *pdfout = NULL; #endif static int ignore_errors = 0; static int uselist = 1; static int alphabits_text = 8; static int alphabits_graphics = 8; static int out_cs = CS_UNSET; static float gamma_value = 1; static int invert = 0; static int band_height = 0; static int lowmemory = 0; static int errored = 0; static fz_stext_sheet *sheet = NULL; static fz_colorspace *colorspace; static int alpha; static char *filename; static int files = 0; static int num_workers = 0; static worker_t *workers; static const char *layer_config = NULL; static struct { int active; int started; fz_context *ctx; #ifndef DISABLE_MUTHREADS mu_thread thread; mu_semaphore start; mu_semaphore stop; #endif int pagenum; char *filename; fz_display_list *list; fz_page *page; int interptime; } bgprint; static struct { int count, total; int min, max; int mininterp, maxinterp; int minpage, maxpage; char *minfilename; char *maxfilename; } timing; static void usage(void) { fprintf(stderr, "mudraw version " FZ_VERSION "\n" "Usage: mudraw [options] file [pages]\n" "\t-p -\tpassword\n" "\n" "\t-o -\toutput file name (%%d for page number)\n" "\t-F -\toutput format (default inferred from output file name)\n" "\t\traster: png, tga, pnm, pam, pbm, pkm, pwg, pcl, ps\n" "\t\tvector: svg, pdf, trace\n" "\t\ttext: txt, html, stext\n" "\n" "\t-s -\tshow extra information:\n" "\t\tm - show memory use\n" "\t\tt - show timings\n" "\t\tf - show page features\n" "\t\t5 - show md5 checksum of rendered image\n" "\n" "\t-R -\trotate clockwise (default: 0 degrees)\n" "\t-r -\tresolution in dpi (default: 72)\n" "\t-w -\twidth (in pixels) (maximum width if -r is specified)\n" "\t-h -\theight (in pixels) (maximum height if -r is specified)\n" "\t-f -\tfit width and/or height exactly; ignore original aspect ratio\n" "\t-B -\tmaximum band_height (pgm, ppm, pam, png output only)\n" #ifdef MUDRAW_THREADS "\t-T -\tnumber of threads to use for rendering (banded mode only)\n" #endif "\n" "\t-W -\tpage width for EPUB layout\n" "\t-H -\tpage height for EPUB layout\n" "\t-S -\tfont size for EPUB layout\n" "\t-U -\tfile name of user stylesheet for EPUB layout\n" "\t-X\tdisable document styles for EPUB layout\n" "\n" "\t-c -\tcolorspace (mono, gray, grayalpha, rgb, rgba, cmyk, cmykalpha)\n" "\t-G -\tapply gamma correction\n" "\t-I\tinvert colors\n" "\n" "\t-A -\tnumber of bits of antialiasing (0 to 8)\n" "\t-A -/-\tnumber of bits of antialiasing (0 to 8) (graphics, text)\n" "\t-l -\tminimum stroked line width (in pixels)\n" "\t-D\tdisable use of display list\n" "\t-i\tignore errors\n" "\t-L\tlow memory mode (avoid caching, clear objects after each page)\n" "\t-P\tparallel interpretation/rendering\n" "\n" "\t-y l\tList the layer configs to stderr\n" "\t-y -\tSelect layer config (by number)\n" "\t-y -{,-}*\tSelect layer config (by number), and toggle the listed entries\n" "\n" "\tpages\tcomma separated list of page numbers and ranges\n" ); exit(1); } static int gettime(void) { static struct timeval first; static int once = 1; struct timeval now; if (once) { gettimeofday(&first, NULL); once = 0; } gettimeofday(&now, NULL); return (now.tv_sec - first.tv_sec) * 1000 + (now.tv_usec - first.tv_usec) / 1000; } static int has_percent_d(char *s) { /* find '%[0-9]*d' */ while (*s) { if (*s++ == '%') { while (*s >= '0' && *s <= '9') ++s; if (*s == 'd') return 1; } } return 0; } /* Output file level (as opposed to page level) headers */ static void file_level_headers(fz_context *ctx) { if (output_format == OUT_STEXT || output_format == OUT_TRACE) fz_write_printf(ctx, out, "\n"); if (output_format == OUT_TEXT || output_format == OUT_HTML || output_format == OUT_STEXT) sheet = fz_new_stext_sheet(ctx); if (output_format == OUT_HTML) { fz_write_printf(ctx, out, "\n"); fz_write_printf(ctx, out, "\n"); } if (output_format == OUT_STEXT || output_format == OUT_TRACE) fz_write_printf(ctx, out, "\n", filename); if (output_format == OUT_PS) fz_write_ps_file_header(ctx, out); if (output_format == OUT_PWG) fz_write_pwg_file_header(ctx, out); } static void file_level_trailers(fz_context *ctx) { if (output_format == OUT_STEXT || output_format == OUT_TRACE) fz_write_printf(ctx, out, "\n"); if (output_format == OUT_HTML) { fz_write_printf(ctx, out, "\n"); fz_write_printf(ctx, out, "\n"); } if (output_format == OUT_PS) fz_write_ps_file_trailer(ctx, out, output_pagenum); fz_drop_stext_sheet(ctx, sheet); } static void drawband(fz_context *ctx, fz_page *page, fz_display_list *list, const fz_matrix *ctm, const fz_rect *tbounds, fz_cookie *cookie, int band_start, fz_pixmap *pix, fz_bitmap **bit) { fz_device *dev = NULL; *bit = NULL; fz_try(ctx) { if (pix->alpha) fz_clear_pixmap(ctx, pix); else fz_clear_pixmap_with_value(ctx, pix, 255); dev = fz_new_draw_device(ctx, NULL, pix); if (lowmemory) fz_enable_device_hints(ctx, dev, FZ_NO_CACHE); if (alphabits_graphics == 0) fz_enable_device_hints(ctx, dev, FZ_DONT_INTERPOLATE_IMAGES); if (list) fz_run_display_list(ctx, list, dev, ctm, tbounds, cookie); else fz_run_page(ctx, page, dev, ctm, cookie); fz_close_device(ctx, dev); fz_drop_device(ctx, dev); dev = NULL; if (invert) fz_invert_pixmap(ctx, pix); if (gamma_value != 1) fz_gamma_pixmap(ctx, pix, gamma_value); if (pix->alpha) fz_unmultiply_pixmap(ctx, pix); if (((output_format == OUT_PCL || output_format == OUT_PWG) && out_cs == CS_MONO) || (output_format == OUT_PBM) || (output_format == OUT_PKM)) *bit = fz_new_bitmap_from_pixmap_band(ctx, pix, NULL, band_start); } fz_catch(ctx) { fz_drop_device(ctx, dev); fz_rethrow(ctx); } } static 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_rect mediabox; fz_device *dev = NULL; fz_bound_page(ctx, page, &mediabox); if (output_format == OUT_TRACE) { fz_try(ctx) { fz_write_printf(ctx, out, "\n", mediabox.x0, mediabox.y0, mediabox.x1, mediabox.y1); dev = fz_new_trace_device(ctx, out); if (lowmemory) fz_enable_device_hints(ctx, dev, FZ_NO_CACHE); if (list) fz_run_display_list(ctx, list, dev, &fz_identity, &fz_infinite_rect, cookie); else fz_run_page(ctx, page, dev, &fz_identity, cookie); fz_write_printf(ctx, out, "\n"); fz_close_device(ctx, dev); } fz_always(ctx) { fz_drop_device(ctx, dev); dev = NULL; } fz_catch(ctx) { fz_drop_display_list(ctx, list); fz_drop_page(ctx, page); fz_rethrow(ctx); } } else if (output_format == OUT_TEXT || output_format == OUT_HTML || output_format == OUT_STEXT) { fz_stext_page *text = NULL; fz_var(text); fz_try(ctx) { fz_rect mediabox; fz_stext_options stext_options; if (list) fz_bound_display_list(ctx, list, &mediabox); else fz_bound_page(ctx, page, &mediabox); stext_options.flags = (output_format == OUT_HTML) ? FZ_STEXT_PRESERVE_IMAGES : 0; text = fz_new_stext_page(ctx, &mediabox); dev = fz_new_stext_device(ctx, sheet, text, &stext_options); if (lowmemory) fz_enable_device_hints(ctx, dev, FZ_NO_CACHE); if (list) fz_run_display_list(ctx, list, dev, &fz_identity, &fz_infinite_rect, cookie); else fz_run_page(ctx, page, dev, &fz_identity, cookie); fz_close_device(ctx, dev); fz_drop_device(ctx, dev); dev = NULL; if (output_format == OUT_STEXT) { fz_print_stext_page_xml(ctx, out, text); } else if (output_format == OUT_HTML) { fz_analyze_text(ctx, sheet, text); fz_print_stext_page_html(ctx, out, text); } else if (output_format == OUT_TEXT) { fz_print_stext_page(ctx, out, text); fz_write_printf(ctx, out, "\f\n"); } } fz_always(ctx) { fz_drop_device(ctx, dev); dev = NULL; fz_drop_stext_page(ctx, text); } fz_catch(ctx) { fz_drop_display_list(ctx, list); fz_drop_page(ctx, page); fz_rethrow(ctx); } } #if FZ_ENABLE_PDF else if (output_format == OUT_PDF) { fz_buffer *contents; pdf_obj *resources; dev = pdf_page_write(ctx, pdfout, &mediabox, &resources, &contents); fz_try(ctx) { pdf_obj *page_obj; if (list) fz_run_display_list(ctx, list, dev, &fz_identity, NULL, cookie); else fz_run_page(ctx, page, dev, &fz_identity, cookie); fz_close_device(ctx, dev); page_obj = pdf_add_page(ctx, pdfout, &mediabox, rotation, resources, contents); pdf_insert_page(ctx, pdfout, -1, page_obj); pdf_drop_obj(ctx, page_obj); } fz_always(ctx) { pdf_drop_obj(ctx, resources); fz_drop_buffer(ctx, contents); fz_drop_device(ctx, dev); dev = NULL; } fz_catch(ctx) { fz_drop_display_list(ctx, list); fz_drop_page(ctx, page); fz_rethrow(ctx); } } #endif else if (output_format == OUT_SVG) { float zoom; fz_matrix ctm; fz_rect bounds, tbounds; char buf[512]; fz_output *out; if (!output || !strcmp(output, "-")) out = fz_stdout(ctx); else { sprintf(buf, output, pagenum); out = fz_new_output_with_path(ctx, buf, 0); } fz_bound_page(ctx, page, &bounds); zoom = resolution / 72; fz_pre_rotate(fz_scale(&ctm, zoom, zoom), rotation); tbounds = bounds; fz_transform_rect(&tbounds, &ctm); fz_try(ctx) { dev = fz_new_svg_device(ctx, out, tbounds.x1-tbounds.x0, tbounds.y1-tbounds.y0, FZ_SVG_TEXT_AS_PATH, 1); if (lowmemory) fz_enable_device_hints(ctx, dev, FZ_NO_CACHE); if (list) fz_run_display_list(ctx, list, dev, &ctm, &tbounds, cookie); else fz_run_page(ctx, page, dev, &ctm, cookie); fz_close_device(ctx, dev); fz_drop_device(ctx, dev); dev = NULL; } fz_always(ctx) { fz_drop_device(ctx, dev); dev = NULL; fz_drop_output(ctx, out); } fz_catch(ctx) { fz_drop_display_list(ctx, list); fz_drop_page(ctx, page); fz_rethrow(ctx); } } else { float zoom; fz_matrix ctm; fz_rect bounds, tbounds; fz_irect ibounds; fz_pixmap *pix = NULL; int w, h; fz_band_writer *bander = NULL; fz_bitmap *bit = NULL; fz_var(pix); fz_var(bander); fz_var(bit); fz_bound_page(ctx, page, &bounds); zoom = resolution / 72; fz_pre_scale(fz_rotate(&ctm, rotation), zoom, zoom); if (output_format == OUT_TGA) { fz_pre_scale(fz_pre_translate(&ctm, 0, -height), 1, -1); } tbounds = bounds; fz_round_rect(&ibounds, fz_transform_rect(&tbounds, &ctm)); /* Make local copies of our width/height */ w = width; h = height; /* If a resolution is specified, check to see whether w/h are * exceeded; if not, unset them. */ if (res_specified) { int t; t = ibounds.x1 - ibounds.x0; if (w && t <= w) w = 0; t = ibounds.y1 - ibounds.y0; if (h && t <= h) h = 0; } /* Now w or h will be 0 unless they need to be enforced. */ if (w || h) { float scalex = w / (tbounds.x1 - tbounds.x0); float scaley = h / (tbounds.y1 - tbounds.y0); fz_matrix scale_mat; if (fit) { if (w == 0) scalex = 1.0f; if (h == 0) scaley = 1.0f; } else { if (w == 0) scalex = scaley; if (h == 0) scaley = scalex; } if (!fit) { if (scalex > scaley) scalex = scaley; else scaley = scalex; } fz_scale(&scale_mat, scalex, scaley); fz_concat(&ctm, &ctm, &scale_mat); tbounds = bounds; fz_transform_rect(&tbounds, &ctm); } fz_round_rect(&ibounds, &tbounds); fz_rect_from_irect(&tbounds, &ibounds); fz_try(ctx) { fz_irect band_ibounds = ibounds; int band, bands = 1; int totalheight = ibounds.y1 - ibounds.y0; int drawheight = totalheight; if (band_height != 0) { /* Banded rendering; we'll only render to a * given height at a time. */ drawheight = band_height; if (totalheight > band_height) band_ibounds.y1 = band_ibounds.y0 + band_height; bands = (totalheight + band_height-1)/band_height; tbounds.y1 = tbounds.y0 + band_height + 2; DEBUG_THREADS(("Using %d Bands\n", bands)); } if (num_workers > 0) { for (band = 0; band < fz_mini(num_workers, bands); band++) { workers[band].band = band; workers[band].ctm = ctm; workers[band].tbounds = tbounds; memset(&workers[band].cookie, 0, sizeof(fz_cookie)); workers[band].list = list; workers[band].pix = fz_new_pixmap_with_bbox(ctx, colorspace, &band_ibounds, alpha); fz_set_pixmap_resolution(ctx, workers[band].pix, resolution, resolution); #ifndef DISABLE_MUTHREADS DEBUG_THREADS(("Worker %d, Pre-triggering band %d\n", band, band)); mu_trigger_semaphore(&workers[band].start); #endif ctm.f -= drawheight; } pix = workers[0].pix; } else { pix = fz_new_pixmap_with_bbox(ctx, colorspace, &band_ibounds, alpha); fz_set_pixmap_resolution(ctx, pix, resolution, resolution); } /* Output any page level headers (for banded formats) */ if (output) { if (output_format == OUT_PGM || output_format == OUT_PPM || output_format == OUT_PNM) bander = fz_new_pnm_band_writer(ctx, out); else if (output_format == OUT_PAM) bander = fz_new_pam_band_writer(ctx, out); else if (output_format == OUT_PNG) bander = fz_new_png_band_writer(ctx, out); else if (output_format == OUT_PBM) bander = fz_new_pbm_band_writer(ctx, out); else if (output_format == OUT_PKM) bander = fz_new_pkm_band_writer(ctx, out); else if (output_format == OUT_PS) bander = fz_new_ps_band_writer(ctx, out); else if (output_format == OUT_TGA) bander = fz_new_tga_band_writer(ctx, out, colorspace == fz_device_bgr(ctx)); else if (output_format == OUT_PWG) { if (out_cs == CS_MONO) bander = fz_new_mono_pwg_band_writer(ctx, out, NULL); else bander = fz_new_pwg_band_writer(ctx, out, NULL); } else if (output_format == OUT_PCL) { if (out_cs == CS_MONO) bander = fz_new_mono_pcl_band_writer(ctx, out, NULL); else bander = fz_new_color_pcl_band_writer(ctx, out, NULL); } if (bander) fz_write_header(ctx, bander, pix->w, totalheight, pix->n, pix->alpha, pix->xres, pix->yres, output_pagenum++); } for (band = 0; band < bands; band++) { if (num_workers > 0) { worker_t *w = &workers[band % num_workers]; #ifndef DISABLE_MUTHREADS DEBUG_THREADS(("Waiting for worker %d to complete band %d\n", w->num, band)); mu_wait_semaphore(&w->stop); #endif pix = w->pix; bit = w->bit; w->bit = NULL; cookie->errors += w->cookie.errors; } else drawband(ctx, page, list, &ctm, &tbounds, cookie, band * band_height, pix, &bit); if (output) { if (bander) fz_write_band(ctx, bander, bit ? bit->stride : pix->stride, drawheight, bit ? bit->samples : pix->samples); fz_drop_bitmap(ctx, bit); bit = NULL; } if (num_workers > 0 && band + num_workers < bands) { worker_t *w = &workers[band % num_workers]; w->band = band + num_workers; w->ctm = ctm; w->tbounds = tbounds; memset(&w->cookie, 0, sizeof(fz_cookie)); #ifndef DISABLE_MUTHREADS DEBUG_THREADS(("Triggering worker %d for band %d\n", w->num, w->band)); mu_trigger_semaphore(&w->start); #endif } ctm.f -= drawheight; } /* FIXME */ if (showmd5) { unsigned char digest[16]; int i; fz_md5_pixmap(ctx, pix, digest); fprintf(stderr, " "); for (i = 0; i < 16; i++) fprintf(stderr, "%02x", digest[i]); } } fz_always(ctx) { fz_drop_band_writer(ctx, bander); fz_drop_bitmap(ctx, bit); bit = NULL; if (num_workers > 0) { int band; for (band = 0; band < num_workers; band++) fz_drop_pixmap(ctx, workers[band].pix); } else fz_drop_pixmap(ctx, pix); } fz_catch(ctx) { fz_drop_display_list(ctx, list); fz_drop_page(ctx, page); fz_rethrow(ctx); } } fz_drop_display_list(ctx, list); if (!output_append) file_level_trailers(ctx); fz_drop_page(ctx, page); if (showtime) { int end = gettime(); int diff = end - start; if (bg) { if (diff + interptime < timing.min) { timing.min = diff + interptime; timing.mininterp = interptime; timing.minpage = pagenum; timing.minfilename = filename; } if (diff + interptime > timing.max) { timing.max = diff + interptime; timing.maxinterp = interptime; timing.maxpage = pagenum; timing.maxfilename = filename; } timing.count ++; fprintf(stderr, " %dms (interpretation) %dms (rendering) %dms (total)", interptime, diff, diff + interptime); } else { if (diff < timing.min) { timing.min = diff; timing.minpage = pagenum; timing.minfilename = filename; } if (diff > timing.max) { timing.max = diff; timing.maxpage = pagenum; timing.maxfilename = filename; } timing.total += diff; timing.count ++; fprintf(stderr, " %dms", diff); } } fprintf(stderr, "\n"); if (lowmemory) { fz_empty_store(ctx); } if (showmemory) { fz_dump_glyph_cache_stats(ctx); } fz_flush_warnings(ctx); if (cookie->errors) errored = 1; } static void bgprint_flush(void) { if (!bgprint.active || !bgprint.started) return; #ifndef DISABLE_MUTHREADS mu_wait_semaphore(&bgprint.stop); #endif bgprint.started = 0; } static void drawpage(fz_context *ctx, fz_document *doc, int pagenum) { fz_page *page; fz_display_list *list = NULL; fz_device *dev = NULL; int start; fz_cookie cookie = { 0 }; int first_page = !output_append; fz_rect bounds; fz_var(list); fz_var(dev); start = (showtime ? gettime() : 0); page = fz_load_page(ctx, doc, pagenum - 1); /* Output any file level (as opposed to page level) headers. */ if (first_page) file_level_headers(ctx); if (uselist) { fz_try(ctx) { list = fz_new_display_list(ctx, fz_bound_page(ctx, page, &bounds)); dev = fz_new_list_device(ctx, list); if (lowmemory) fz_enable_device_hints(ctx, dev, FZ_NO_CACHE); fz_run_page(ctx, page, dev, &fz_identity, &cookie); fz_close_device(ctx, dev); } fz_always(ctx) { fz_drop_device(ctx, dev); dev = NULL; } fz_catch(ctx) { fz_drop_display_list(ctx, list); fz_drop_page(ctx, page); fz_rethrow(ctx); } if (bgprint.active && showtime) { int end = gettime(); start = end - start; } } if (showfeatures) { int iscolor; dev = fz_new_test_device(ctx, &iscolor, 0.02f, 0, NULL); if (lowmemory) fz_enable_device_hints(ctx, dev, FZ_NO_CACHE); fz_try(ctx) { if (list) fz_run_display_list(ctx, list, dev, &fz_identity, &fz_infinite_rect, NULL); else fz_run_page(ctx, page, dev, &fz_identity, &cookie); fz_close_device(ctx, dev); } fz_always(ctx) { fz_drop_device(ctx, dev); dev = NULL; } fz_catch(ctx) { fz_rethrow(ctx); } fprintf(stderr, " %s", iscolor ? "color" : "grayscale"); } if (output_file_per_page) { char text_buffer[512]; bgprint_flush(); fz_drop_output(ctx, out); fz_snprintf(text_buffer, sizeof(text_buffer), output, pagenum); out = fz_new_output_with_path(ctx, text_buffer, output_append); output_append = 1; } if (bgprint.active) { bgprint_flush(); if (bgprint.active) { fprintf(stderr, "page %s %d", filename, pagenum); } bgprint.started = 1; bgprint.page = page; bgprint.list = list; bgprint.filename = filename; bgprint.pagenum = pagenum; bgprint.interptime = start; #ifndef DISABLE_MUTHREADS mu_trigger_semaphore(&bgprint.start); #endif } else { fprintf(stderr, "page %s %d", filename, pagenum); dodrawpage(ctx, page, list, pagenum, &cookie, start, 0, filename, 0); } } static void drawrange(fz_context *ctx, fz_document *doc, const char *range) { int page, spage, epage, pagecount; pagecount = fz_count_pages(ctx, doc); while ((range = fz_parse_page_range(ctx, range, &spage, &epage, pagecount))) { if (spage < epage) for (page = spage; page <= epage; page++) { fz_try(ctx) drawpage(ctx, doc, page); fz_catch(ctx) { if (ignore_errors) fz_warn(ctx, "ignoring error on page %d in '%s'", page, filename); else fz_rethrow(ctx); } } else for (page = spage; page >= epage; page--) { fz_try(ctx) drawpage(ctx, doc, page); fz_catch(ctx) { if (ignore_errors) fz_warn(ctx, "ignoring error on page %d in '%s'", page, filename); else fz_rethrow(ctx); } } } } static int parse_colorspace(const char *name) { int i; for (i = 0; i < nelem(cs_name_table); i++) { if (!strcmp(name, cs_name_table[i].name)) return cs_name_table[i].colorspace; } fprintf(stderr, "Unknown colorspace \"%s\"\n", name); exit(1); } typedef struct { size_t size; #if defined(_M_IA64) || defined(_M_AMD64) size_t align; #endif } trace_header; static void * trace_malloc(void *arg, size_t size) { trace_header *p; if (size == 0) return NULL; p = malloc(size + sizeof(trace_header)); if (p == NULL) return NULL; p[0].size = size; memtrace_current += size; memtrace_total += size; if (memtrace_current > memtrace_peak) memtrace_peak = memtrace_current; return (void *)&p[1]; } static void trace_free(void *arg, void *p_) { trace_header *p = (trace_header *)p_; if (p == NULL) return; memtrace_current -= p[-1].size; free(&p[-1]); } static void * trace_realloc(void *arg, void *p_, size_t size) { trace_header *p = (trace_header *)p_; size_t oldsize; if (size == 0) { trace_free(arg, p_); return NULL; } if (p == NULL) return trace_malloc(arg, size); oldsize = p[-1].size; p = realloc(&p[-1], size + sizeof(trace_header)); if (p == NULL) return NULL; memtrace_current += size - oldsize; if (size > oldsize) memtrace_total += size - oldsize; if (memtrace_current > memtrace_peak) memtrace_peak = memtrace_current; p[0].size = size; return &p[1]; } #ifndef DISABLE_MUTHREADS static void worker_thread(void *arg) { worker_t *me = (worker_t *)arg; do { DEBUG_THREADS(("Worker %d waiting\n", me->num)); mu_wait_semaphore(&me->start); DEBUG_THREADS(("Worker %d woken for band %d\n", me->num, me->band)); if (me->band >= 0) drawband(me->ctx, NULL, me->list, &me->ctm, &me->tbounds, &me->cookie, me->band * band_height, me->pix, &me->bit); DEBUG_THREADS(("Worker %d completed band %d\n", me->num, me->band)); mu_trigger_semaphore(&me->stop); } while (me->band >= 0); } static void bgprint_worker(void *arg) { fz_cookie cookie = { 0 }; int pagenum; (void)arg; do { DEBUG_THREADS(("BGPrint waiting\n")); mu_wait_semaphore(&bgprint.start); pagenum = bgprint.pagenum; DEBUG_THREADS(("BGPrint woken for pagenum %d\n", pagenum)); if (pagenum >= 0) { int start = gettime(); memset(&cookie, 0, sizeof(cookie)); dodrawpage(bgprint.ctx, bgprint.page, bgprint.list, pagenum, &cookie, start, bgprint.interptime, bgprint.filename, 1); } DEBUG_THREADS(("BGPrint completed page %d\n", pagenum)); mu_trigger_semaphore(&bgprint.stop); } while (pagenum >= 0); } #endif static inline int iswhite(int ch) { return ch == '\011' || ch == '\012' || ch == '\014' || ch == '\015' || ch == '\040'; } static void apply_layer_config(fz_context *ctx, fz_document *doc, const char *lc) { #if FZ_ENABLE_PDF pdf_document *pdoc = pdf_specifics(ctx, doc); int config = -1; int n, j; pdf_layer_config info; if (!pdoc) { fz_warn(ctx, "Only PDF files have layers"); return; } while (iswhite(*lc)) lc++; if (*lc == 0 || *lc == 'l') { int num_configs = pdf_count_layer_configs(ctx, pdoc); fprintf(stderr, "Layer configs:\n"); for (config = 0; config < num_configs; config++) { fprintf(stderr, " %s%d:", config < 10 ? " " : "", config); pdf_layer_config_info(ctx, pdoc, config, &info); if (info.name) fprintf(stderr, " Name=\"%s\"", info.name); if (info.creator) fprintf(stderr, " Creator=\"%s\"", info.creator); fprintf(stderr, "\n"); } return; } while (*lc) { int i; if (*lc < '0' || *lc > '9') { fprintf(stderr, "cannot find number expected for -y\n"); return; } i = fz_atoi(lc); pdf_select_layer_config(ctx, pdoc, i); if (config < 0) config = i; while (*lc >= '0' && *lc <= '9') lc++; while (iswhite(*lc)) lc++; if (*lc == ',') { lc++; while (iswhite(*lc)) lc++; } else if (*lc) { fprintf(stderr, "cannot find comma expected for -y\n"); return; } } /* Now list the final state of the config */ fprintf(stderr, "Layer Config %d:\n", config); pdf_layer_config_info(ctx, pdoc, config, &info); if (info.name) fprintf(stderr, " Name=\"%s\"", info.name); if (info.creator) fprintf(stderr, " Creator=\"%s\"", info.creator); fprintf(stderr, "\n"); n = pdf_count_layer_config_ui(ctx, pdoc); for (j = 0; j < n; j++) { pdf_layer_config_ui ui; pdf_layer_config_ui_info(ctx, pdoc, j, &ui); fprintf(stderr, "%s%d: ", j < 10 ? " " : "", j); while (ui.depth > 0) { ui.depth--; fprintf(stderr, " "); } if (ui.type == PDF_LAYER_UI_CHECKBOX) fprintf(stderr, " [%c] ", ui.selected ? 'x' : ' '); else if (ui.type == PDF_LAYER_UI_RADIOBOX) fprintf(stderr, " (%c) ", ui.selected ? 'x' : ' '); if (ui.text) fprintf(stderr, "%s", ui.text); if (ui.type != PDF_LAYER_UI_LABEL && ui.locked) fprintf(stderr, " "); fprintf(stderr, "\n"); } #endif } #ifdef MUDRAW_STANDALONE int main(int argc, char **argv) #else int mudraw_main(int argc, char **argv) #endif { char *password = ""; fz_document *doc = NULL; int c; fz_context *ctx; fz_alloc_context alloc_ctx = { NULL, trace_malloc, trace_realloc, trace_free }; fz_locks_context *locks = NULL; fz_var(doc); while ((c = fz_getopt(argc, argv, "p:o:F:R:r:w:h:fB:c:G:Is:A:DiW:H:S:T:U:XLvPl:y:")) != -1) { switch (c) { default: usage(); break; case 'p': password = fz_optarg; break; case 'o': output = fz_optarg; break; case 'F': format = fz_optarg; break; case 'R': rotation = fz_atof(fz_optarg); break; case 'r': resolution = fz_atof(fz_optarg); res_specified = 1; break; case 'w': width = fz_atof(fz_optarg); break; case 'h': height = fz_atof(fz_optarg); break; case 'f': fit = 1; break; case 'B': band_height = atoi(fz_optarg); break; case 'c': out_cs = parse_colorspace(fz_optarg); break; case 'G': gamma_value = fz_atof(fz_optarg); break; case 'I': invert++; break; case 'W': layout_w = fz_atof(fz_optarg); break; case 'H': layout_h = fz_atof(fz_optarg); break; case 'S': layout_em = fz_atof(fz_optarg); break; case 'U': layout_css = fz_optarg; break; case 'X': layout_use_doc_css = 0; break; case 's': if (strchr(fz_optarg, 't')) ++showtime; if (strchr(fz_optarg, 'm')) ++showmemory; if (strchr(fz_optarg, 'f')) ++showfeatures; if (strchr(fz_optarg, '5')) ++showmd5; break; case 'A': { char *sep; alphabits_graphics = atoi(fz_optarg); sep = strchr(fz_optarg, '/'); if (sep) alphabits_text = atoi(sep+1); else alphabits_text = alphabits_graphics; break; } case 'D': uselist = 0; break; case 'l': min_line_width = fz_atof(fz_optarg); break; case 'i': ignore_errors = 1; break; case 'T': #ifndef DISABLE_MUTHREADS num_workers = atoi(fz_optarg); break; #else fprintf(stderr, "Threads not enabled in this build\n"); break; #endif case 'L': lowmemory = 1; break; case 'P': bgprint.active = 1; break; case 'y': layer_config = fz_optarg; break; case 'v': fprintf(stderr, "mudraw version %s\n", FZ_VERSION); return 1; } } if (fz_optind == argc) usage(); if (num_workers > 0) { if (uselist == 0) { fprintf(stderr, "cannot use multiple threads without using display list\n"); exit(1); } if (band_height == 0) { fprintf(stderr, "Using multiple threads without banding is pointless\n"); } } if (bgprint.active) { if (uselist == 0) { fprintf(stderr, "cannot bgprint without using display list\n"); exit(1); } } #ifndef DISABLE_MUTHREADS locks = init_mudraw_locks(); if (locks == NULL) { fprintf(stderr, "mutex initialisation failed\n"); exit(1); } #endif ctx = fz_new_context((showmemory == 0 ? NULL : &alloc_ctx), locks, (lowmemory ? 1 : FZ_STORE_DEFAULT)); if (!ctx) { fprintf(stderr, "cannot initialise context\n"); exit(1); } fz_set_text_aa_level(ctx, alphabits_text); fz_set_graphics_aa_level(ctx, alphabits_graphics); fz_set_graphics_min_line_width(ctx, min_line_width); #ifndef DISABLE_MUTHREADS if (bgprint.active) { int fail = 0; bgprint.ctx = fz_clone_context(ctx); fail |= mu_create_semaphore(&bgprint.start); fail |= mu_create_semaphore(&bgprint.stop); fail |= mu_create_thread(&bgprint.thread, bgprint_worker, NULL); if (fail) { fprintf(stderr, "bgprint startup failed\n"); exit(1); } } if (num_workers > 0) { int i; int fail = 0; workers = fz_calloc(ctx, num_workers, sizeof(*workers)); for (i = 0; i < num_workers; i++) { workers[i].ctx = fz_clone_context(ctx); workers[i].num = i; fail |= mu_create_semaphore(&workers[i].start); fail |= mu_create_semaphore(&workers[i].stop); fail |= mu_create_thread(&workers[i].thread, worker_thread, &workers[i]); } if (fail) { fprintf(stderr, "worker startup failed\n"); exit(1); } } #endif /* DISABLE_MUTHREADS */ if (layout_css) { fz_buffer *buf = fz_read_file(ctx, layout_css); fz_set_user_css(ctx, fz_string_from_buffer(ctx, buf)); fz_drop_buffer(ctx, buf); } fz_set_use_document_css(ctx, layout_use_doc_css); /* Determine output type */ if (band_height < 0) { fprintf(stderr, "Bandheight must be > 0\n"); exit(1); } output_format = OUT_PNG; if (format) { int i; for (i = 0; i < nelem(suffix_table); i++) { if (!strcmp(format, suffix_table[i].suffix+1)) { output_format = suffix_table[i].format; break; } } if (i == nelem(suffix_table)) { fprintf(stderr, "Unknown output format '%s'\n", format); exit(1); } } else if (output) { char *suffix = output; int i; for (i = 0; i < nelem(suffix_table); i++) { char *s = strstr(suffix, suffix_table[i].suffix); if (s != NULL) { suffix = s+1; output_format = suffix_table[i].format; i = 0; } } } if (band_height) { 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_PS) { fprintf(stderr, "Banded operation only possible with PAM, PBM, PGM, PKM, PPM, PNM, PCL, PS and PNG outputs\n"); exit(1); } if (showmd5) { fprintf(stderr, "Banded operation not compatible with MD5\n"); exit(1); } } { int i, j; for (i = 0; i < nelem(format_cs_table); i++) { if (format_cs_table[i].format == output_format) { if (out_cs == CS_UNSET) out_cs = format_cs_table[i].default_cs; for (j = 0; j < nelem(format_cs_table[i].permitted_cs); j++) { if (format_cs_table[i].permitted_cs[j] == out_cs) break; } if (j == nelem(format_cs_table[i].permitted_cs)) { fprintf(stderr, "Unsupported colorspace for this format\n"); exit(1); } } } } alpha = 1; switch (out_cs) { case CS_MONO: case CS_GRAY: case CS_GRAY_ALPHA: colorspace = fz_device_gray(ctx); alpha = (out_cs == CS_GRAY_ALPHA); break; case CS_RGB: case CS_RGB_ALPHA: colorspace = fz_device_rgb(ctx); alpha = (out_cs == CS_RGB_ALPHA); break; case CS_CMYK: case CS_CMYK_ALPHA: colorspace = fz_device_cmyk(ctx); alpha = (out_cs == CS_CMYK_ALPHA); break; default: fprintf(stderr, "Unknown colorspace!\n"); exit(1); break; } #if FZ_ENABLE_PDF if (output_format == OUT_PDF) { pdfout = pdf_create_document(ctx); } else #endif if (output_format == OUT_GPROOF || output_format == OUT_SVG) { /* GPROOF files are saved direct. Do not open "output". */ /* SVG files are always opened for each page. Do not open "output". */ } else if (output && (output[0] != '-' || output[1] != 0) && *output != 0) { if (has_percent_d(output)) output_file_per_page = 1; else out = fz_new_output_with_path(ctx, output, 0); } else out = fz_stdout(ctx); timing.count = 0; timing.total = 0; timing.min = 1 << 30; timing.max = 0; timing.mininterp = 1 << 30; timing.maxinterp = 0; timing.minpage = 0; timing.maxpage = 0; timing.minfilename = ""; timing.maxfilename = ""; if (showtime && bgprint.active) timing.total = gettime(); fz_try(ctx) { fz_register_document_handlers(ctx); while (fz_optind < argc) { fz_try(ctx) { filename = argv[fz_optind++]; files++; doc = fz_open_document(ctx, filename); if (fz_needs_password(ctx, doc)) { if (!fz_authenticate_password(ctx, doc, password)) fz_throw(ctx, FZ_ERROR_GENERIC, "cannot authenticate password: %s", filename); } fz_layout_document(ctx, doc, layout_w, layout_h, layout_em); if (layer_config) apply_layer_config(ctx, doc, layer_config); if (output_format == OUT_GPROOF) { fz_save_gproof(ctx, filename, doc, output, resolution, "", ""); } else { if (fz_optind == argc || !fz_is_page_range(ctx, argv[fz_optind])) drawrange(ctx, doc, "1-N"); if (fz_optind < argc && fz_is_page_range(ctx, argv[fz_optind])) drawrange(ctx, doc, argv[fz_optind++]); } bgprint_flush(); fz_drop_document(ctx, doc); doc = NULL; } fz_catch(ctx) { if (!ignore_errors) fz_rethrow(ctx); bgprint_flush(); fz_drop_document(ctx, doc); doc = NULL; fz_warn(ctx, "ignoring error in '%s'", filename); } } } fz_catch(ctx) { bgprint_flush(); fz_drop_document(ctx, doc); fprintf(stderr, "error: cannot draw '%s'\n", filename); errored = 1; } if (output_append) file_level_trailers(ctx); #if FZ_ENABLE_PDF if (output_format == OUT_PDF) { if (!output) output = "out.pdf"; pdf_save_document(ctx, pdfout, output, NULL); pdf_drop_document(ctx, pdfout); } else #endif if (output_format == OUT_GPROOF) { /* No output file to close */ } else { fz_drop_output(ctx, out); out = NULL; } if (showtime && timing.count > 0) { if (bgprint.active) timing.total = gettime() - timing.total; if (files == 1) { fprintf(stderr, "total %dms / %d pages for an average of %dms\n", timing.total, timing.count, timing.total / timing.count); if (bgprint.active) { fprintf(stderr, "fastest page %d: %dms (interpretation) %dms (rendering) %dms(total)\n", timing.minpage, timing.mininterp, timing.min - timing.mininterp, timing.min); fprintf(stderr, "slowest page %d: %dms (interpretation) %dms (rendering) %dms(total)\n", timing.maxpage, timing.maxinterp, timing.max - timing.maxinterp, timing.max); } else { fprintf(stderr, "fastest page %d: %dms\n", timing.minpage, timing.min); fprintf(stderr, "slowest page %d: %dms\n", timing.maxpage, timing.max); } } else { fprintf(stderr, "total %dms / %d pages for an average of %dms in %d files\n", timing.total, timing.count, timing.total / timing.count, files); fprintf(stderr, "fastest page %d: %dms (%s)\n", timing.minpage, timing.min, timing.minfilename); fprintf(stderr, "slowest page %d: %dms (%s)\n", timing.maxpage, timing.max, timing.maxfilename); } } #ifndef DISABLE_MUTHREADS if (num_workers > 0) { int i; for (i = 0; i < num_workers; i++) { workers[i].band = -1; mu_trigger_semaphore(&workers[i].start); mu_wait_semaphore(&workers[i].stop); mu_destroy_semaphore(&workers[i].start); mu_destroy_semaphore(&workers[i].stop); mu_destroy_thread(&workers[i].thread); fz_drop_context(workers[i].ctx); } fz_free(ctx, workers); } if (bgprint.active) { bgprint.pagenum = -1; mu_trigger_semaphore(&bgprint.start); mu_wait_semaphore(&bgprint.stop); mu_destroy_semaphore(&bgprint.start); mu_destroy_semaphore(&bgprint.stop); mu_destroy_thread(&bgprint.thread); fz_drop_context(bgprint.ctx); } #endif /* DISABLE_MUTHREADS */ fz_drop_context(ctx); #ifndef DISABLE_MUTHREADS fin_mudraw_locks(); #endif /* DISABLE_MUTHREADS */ if (showmemory) { fprintf(stderr, "Total memory use = " FZ_FMT_zu " bytes\n", memtrace_total); fprintf(stderr, "Peak memory use = " FZ_FMT_zu " bytes\n", memtrace_peak); fprintf(stderr, "Current memory use = " FZ_FMT_zu " bytes\n", memtrace_current); } return (errored != 0); }