/* * mudraw -- command line tool for drawing and converting documents */ #include "mupdf/fitz.h" #include "mupdf/pdf.h" /* for pdf output */ #ifdef _MSC_VER #include #include #define MUDRAW_THREADS 1 #else #include #ifdef HAVE_PTHREADS #define MUDRAW_THREADS 2 #include #include #endif #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, OUT_PDF, 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 }, { ".pdf", OUT_PDF }, { ".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 } }, { OUT_PDF, CS_RGB, { CS_RGB } }, { 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. */ #ifdef MUDRAW_THREADS #if MUDRAW_THREADS == 1 /* Windows threads */ #define SEMAPHORE HANDLE #define SEMAPHORE_INIT(A) do { A = CreateSemaphore(NULL, 0, 1, NULL); } while (0) #define SEMAPHORE_FIN(A) do { CloseHandle(A); } while (0) #define SEMAPHORE_TRIGGER(A) do { (void)ReleaseSemaphore(A, 1, NULL); } while (0) #define SEMAPHORE_WAIT(A) do { (void)WaitForSingleObject(A, INFINITE); } while (0) #define THREAD HANDLE #define THREAD_INIT(A,B,C) do { A = CreateThread(NULL, 0, B, C, 0, NULL); } while (0) #define THREAD_FIN(A) do { CloseHandle(A); } while (0) #define THREAD_RETURN_TYPE DWORD WINAPI #define THREAD_RETURN() return 0 #define MUTEX CRITICAL_SECTION #define MUTEX_INIT(A) do { InitializeCriticalSection(&A); } while (0) #define MUTEX_FIN(A) do { DeleteCriticalSection(&A); } while (0) #define MUTEX_LOCK(A) do { EnterCriticalSection(&A); } while (0) #define MUTEX_UNLOCK(A) do { LeaveCriticalSection(&A); } while (0) #elif MUDRAW_THREADS == 2 /* PThreads - without working unnamed semaphores. Neither ios nor OSX supports unnamed semaphores. Named semaphores are a pain to use, so we implement our own sempahores using condition variables and mutexes. */ typedef struct { int count; pthread_mutex_t mutex; pthread_cond_t cond; } my_semaphore_t; int my_semaphore_open(my_semaphore_t *sem) { int scode; sem->count = 0; scode = pthread_mutex_init(&sem->mutex, NULL); if (scode == 0) { scode = pthread_cond_init(&sem->cond, NULL); if (scode) pthread_mutex_destroy(&sem->mutex); } if (scode) memset(sem, 0, sizeof(*sem)); return scode; } int my_semaphore_close(my_semaphore_t *sem) { int scode, scode2; scode = pthread_cond_destroy(&sem->cond); scode2 = pthread_mutex_destroy(&sem->mutex); if (scode == 0) scode = scode2; return scode; } int my_semaphore_wait(my_semaphore_t *sem) { int scode, scode2; scode = pthread_mutex_lock(&sem->mutex); if (scode) return scode; while (sem->count == 0) { scode = pthread_cond_wait(&sem->cond, &sem->mutex); if (scode) break; } if (scode == 0) --sem->count; scode2 = pthread_mutex_unlock(&sem->mutex); if (scode == 0) scode = scode2; return scode; } int my_semaphore_signal(my_semaphore_t * sem) { int scode, scode2; scode = pthread_mutex_lock(&sem->mutex); if (scode) return scode; if (sem->count++ == 0) scode = pthread_cond_signal(&sem->cond); scode2 = pthread_mutex_unlock(&sem->mutex); if (scode == 0) scode = scode2; return scode; } #define SEMAPHORE my_semaphore_t #define SEMAPHORE_INIT(A) do { (void)my_semaphore_open(&A); } while (0) #define SEMAPHORE_FIN(A) do { (void)my_semaphore_close(&A); } while (0) #define SEMAPHORE_TRIGGER(A) do { (void)my_semaphore_signal(&A); } while (0) #define SEMAPHORE_WAIT(A) do { (void)my_semaphore_wait(&A); } while (0) #define THREAD pthread_t #define THREAD_INIT(A,B,C) do { (void)pthread_create(&A, NULL, B, C); } while (0) #define THREAD_FIN(A) do { void *res; (void)pthread_join(A, &res); } while (0) #define THREAD_RETURN_TYPE void * #define THREAD_RETURN() return NULL #define MUTEX pthread_mutex_t #define MUTEX_INIT(A) do { (void)pthread_mutex_init(&A, NULL); } while (0) #define MUTEX_FIN(A) do { (void)pthread_mutex_destroy(&A); } while (0) #define MUTEX_LOCK(A) do { (void)pthread_mutex_lock(&A); } while (0) #define MUTEX_UNLOCK(A) do { (void)pthread_mutex_unlock(&A); } while (0) #else #error Unknown MUDRAW_THREADS setting #endif #define LOCKS_INIT() init_mudraw_locks() #define LOCKS_FIN() fin_mudraw_locks() static MUTEX mutexes[FZ_LOCK_MAX]; static void mudraw_lock(void *user, int lock) { MUTEX_LOCK(mutexes[lock]); } static void mudraw_unlock(void *user, int lock) { MUTEX_UNLOCK(mutexes[lock]); } static fz_locks_context mudraw_locks = { NULL, mudraw_lock, mudraw_unlock }; static fz_locks_context *init_mudraw_locks(void) { int i; for (i = 0; i < FZ_LOCK_MAX; i++) MUTEX_INIT(mutexes[i]); return &mudraw_locks; } static void fin_mudraw_locks(void) { int i; for (i = 0; i < FZ_LOCK_MAX; i++) MUTEX_FIN(mutexes[i]); } #else /* Null Threads implementation */ #define SEMAPHORE int #define THREAD int #define SEMAPHORE_INIT(A) do { A = 0; } while (0) #define SEMAPHORE_FIN(A) do { A = 0; } while (0) #define SEMAPHORE_TRIGGER(A) do { A = 0; } while (0) #define SEMAPHORE_WAIT(A) do { A = 0; } while (0) #define THREAD_INIT(A,B,C) do { A = 0; (void)C; } while (0) #define THREAD_FIN(A) do { A = 0; } while (0) #define LOCKS_INIT() NULL #define LOCKS_FIN() do { } while (0) #endif typedef struct worker_t { fz_context *ctx; int num; int band; /* -1 to shutdown, or band to render */ int savealpha; fz_display_list *list; fz_matrix ctm; fz_rect tbounds; fz_pixmap *pix; fz_cookie cookie; SEMAPHORE start; SEMAPHORE stop; THREAD thread; } 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 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; static pdf_document *pdfout = NULL; 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 bandheight = 0; static int lowmemory = 0; static int errored = 0; static fz_stext_sheet *sheet = NULL; static fz_colorspace *colorspace; static char *filename; static int files = 0; static int num_workers = 0; static worker_t *workers; static struct { int active; int started; fz_context *ctx; THREAD thread; SEMAPHORE start; SEMAPHORE stop; int pagenum; char *filename; fz_display_list *list; fz_page *page; } bgprint; static struct { int count, total; int min, max; int minpage, maxpage; char *minfilename; char *maxfilename; int render_count, render_total; int render_min, render_max; int render_minpage, render_maxpage; char *render_minfilename; char *render_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 bandheight (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" "\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-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" "\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 isrange(char *s) { while (*s) { if ((*s < '0' || *s > '9') && *s != '-' && *s != ',') return 0; s++; } return 1; } 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_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_printf(ctx, out, "\n"); fz_printf(ctx, out, "\n"); } if (output_format == OUT_STEXT || output_format == OUT_TRACE) fz_printf(ctx, out, "\n", filename); if (output_format == OUT_PS) fz_write_ps_file_header(ctx, out); } static void file_level_trailers(fz_context *ctx) { if (output_format == OUT_STEXT || output_format == OUT_TRACE) fz_printf(ctx, out, "\n"); if (output_format == OUT_HTML) { fz_printf(ctx, out, "\n"); fz_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, int savealpha, fz_page *page, fz_display_list *list, const fz_matrix *ctm, const fz_rect *tbounds, fz_cookie *cookie, int band, fz_pixmap *pix) { fz_device *dev = NULL; fz_try(ctx) { if (savealpha) fz_clear_pixmap(ctx, pix); else fz_clear_pixmap_with_value(ctx, pix, 255); dev = fz_new_draw_device(ctx, 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_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 (savealpha) fz_unmultiply_pixmap(ctx, pix); } 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, 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_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_printf(ctx, out, "\n"); } 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) { text = fz_new_stext_page(ctx); dev = fz_new_stext_device(ctx, sheet, text); if (lowmemory) fz_enable_device_hints(ctx, dev, FZ_NO_CACHE); if (output_format == OUT_HTML) fz_disable_device_hints(ctx, dev, FZ_IGNORE_IMAGE); 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_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_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); } } 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); 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); } } else if (output_format == OUT_SVG) { float zoom; fz_matrix ctm; fz_rect bounds, tbounds; char buf[512]; fz_output *out; if (!strcmp(output, "-")) out = fz_new_output_with_file_ptr(ctx, stdout, 0); 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); 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_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_png_output_context *poc = NULL; fz_ps_output_context *psoc = NULL; fz_mono_pcl_output_context *pmcoc = NULL; fz_color_pcl_output_context *pccoc = NULL; fz_device *dev = NULL; fz_var(dev); fz_var(pix); fz_var(poc); fz_var(psoc); fz_var(pmcoc); fz_var(pccoc); fz_bound_page(ctx, page, &bounds); zoom = resolution / 72; fz_pre_scale(fz_rotate(&ctm, rotation), zoom, zoom); 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) { int savealpha = (out_cs == CS_GRAY_ALPHA || out_cs == CS_RGB_ALPHA || out_cs == CS_CMYK_ALPHA); fz_irect band_ibounds = ibounds; int band, bands = 1; int totalheight = ibounds.y1 - ibounds.y0; int drawheight = totalheight; if (bandheight != 0) { /* Banded rendering; we'll only render to a * given height at a time. */ drawheight = bandheight; if (totalheight > bandheight) band_ibounds.y1 = band_ibounds.y0 + bandheight; bands = (totalheight + bandheight-1)/bandheight; tbounds.y1 = tbounds.y0 + bandheight + 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].savealpha = savealpha; /* Constant on a page */ 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); fz_pixmap_set_resolution(workers[band].pix, resolution); DEBUG_THREADS(("Worker %d, Pre-triggering band %d\n", band, band)); SEMAPHORE_TRIGGER(workers[band].start); ctm.f -= drawheight; } pix = workers[0].pix; } else { pix = fz_new_pixmap_with_bbox(ctx, colorspace, &band_ibounds); fz_pixmap_set_resolution(pix, resolution); } /* Output any page level headers (for banded formats) */ if (output) { if (output_format == OUT_PGM || output_format == OUT_PPM || output_format == OUT_PNM) fz_write_pnm_header(ctx, out, pix->w, totalheight, pix->n); else if (output_format == OUT_PAM) fz_write_pam_header(ctx, out, pix->w, totalheight, pix->n, savealpha); else if (output_format == OUT_PNG) poc = fz_write_png_header(ctx, out, pix->w, totalheight, pix->n, savealpha); else if (output_format == OUT_PBM) fz_write_pbm_header(ctx, out, pix->w, totalheight); else if (output_format == OUT_PKM) fz_write_pkm_header(ctx, out, pix->w, totalheight); else if (output_format == OUT_PS) psoc = fz_write_ps_header(ctx, out, pix->w, totalheight, pix->n, pix->xres, pix->yres, ++output_pagenum); else if (output_format == OUT_PCL) { if (out_cs == CS_MONO) pmcoc = fz_write_mono_pcl_header(ctx, out, pix->w, totalheight, pix->xres, pix->yres, ++output_pagenum, NULL); else pccoc = fz_write_color_pcl_header(ctx, out, pix->w, totalheight, pix->n, pix->xres, pix->yres, ++output_pagenum, NULL); } } for (band = 0; band < bands; band++) { if (num_workers > 0) { worker_t *w = &workers[band % num_workers]; DEBUG_THREADS(("Waiting for worker %d to complete band %d\n", w->num, band)); SEMAPHORE_WAIT(w->stop); pix = w->pix; cookie->errors += w->cookie.errors; } else drawband(ctx, savealpha, page, list, &ctm, &tbounds, cookie, band, pix); if (output) { if (output_format == OUT_PGM || output_format == OUT_PPM || output_format == OUT_PNM) fz_write_pnm_band(ctx, out, pix->w, totalheight, pix->n, band, drawheight, pix->samples); else if (output_format == OUT_PAM) fz_write_pam_band(ctx, out, pix->w, totalheight, pix->n, band, drawheight, pix->samples, savealpha); else if (output_format == OUT_PNG) fz_write_png_band(ctx, out, poc, pix->w, totalheight, pix->n, band, drawheight, pix->samples, savealpha); else if (output_format == OUT_PWG) fz_write_pixmap_as_pwg(ctx, out, pix, NULL); else if (output_format == OUT_PCL) { if (out_cs == CS_MONO) { fz_bitmap *bit = fz_new_bitmap_from_pixmap_band(ctx, pix, NULL, band, bandheight); fz_write_mono_pcl_band(ctx, out, pmcoc, bit); fz_drop_bitmap(ctx, bit); } else fz_write_color_pcl_band(ctx, out, pccoc, pix->w, totalheight, pix->n, band, drawheight, pix->samples); } else if (output_format == OUT_PS) fz_write_ps_band(ctx, out, psoc, pix->w, totalheight, pix->n, band, drawheight, pix->samples); else if (output_format == OUT_PBM) { fz_bitmap *bit = fz_new_bitmap_from_pixmap_band(ctx, pix, NULL, band, bandheight); fz_write_pbm_band(ctx, out, bit); fz_drop_bitmap(ctx, bit); } else if (output_format == OUT_PKM) { fz_bitmap *bit = fz_new_bitmap_from_pixmap_band(ctx, pix, NULL, band, bandheight); fz_write_pkm_band(ctx, out, bit); fz_drop_bitmap(ctx, bit); } else if (output_format == OUT_TGA) { fz_write_pixmap_as_tga(ctx, out, pix, savealpha); } } 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)); DEBUG_THREADS(("Triggering worker %d for band %d\n", w->num, w->band)); SEMAPHORE_TRIGGER(w->start); } 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]); } /* Any page level trailers go here */ if (output) { if (output_format == OUT_PNG) fz_write_png_trailer(ctx, out, poc); if (output_format == OUT_PS) fz_write_ps_trailer(ctx, out, psoc); if (output_format == OUT_PCL) { if (out_cs == CS_MONO) fz_write_mono_pcl_trailer(ctx, out, pmcoc); else fz_write_color_pcl_trailer(ctx, out, pccoc); } } } fz_always(ctx) { fz_drop_device(ctx, dev); dev = 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); } } if (list) 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 < timing.render_min) { timing.render_min = diff; timing.render_minpage = pagenum; timing.render_minfilename = filename; } if (diff > timing.render_max) { timing.render_max = diff; timing.render_maxpage = pagenum; timing.render_maxfilename = filename; } timing.render_total += diff; timing.render_count ++; fprintf(stderr, " %dms (rendering)", diff); } 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); } } if (showmd5 || showtime || showfeatures) 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; SEMAPHORE_WAIT(bgprint.stop); 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; int diff; 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); 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_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(); diff = end - start; 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 ++; } } if (showfeatures) { int iscolor; dev = fz_new_test_device(ctx, &iscolor, 0.02f); 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_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 && (showmd5 || showtime || showfeatures)) { fprintf(stderr, "page %s %d", filename, pagenum); if (showtime) fprintf(stderr, " %dms (interpretation)", diff); } bgprint.started = 1; bgprint.page = page; bgprint.list = list; bgprint.filename = filename; bgprint.pagenum = pagenum; SEMAPHORE_TRIGGER(bgprint.start); } else { dodrawpage(ctx, page, list, pagenum, &cookie, start, filename, 0); } } static void drawrange(fz_context *ctx, fz_document *doc, char *range) { int page, spage, epage, pagecount; char *spec, *dash; pagecount = fz_count_pages(ctx, doc); spec = fz_strsep(&range, ","); while (spec) { dash = strchr(spec, '-'); if (dash == spec) spage = epage = pagecount; else spage = epage = atoi(spec); if (dash) { if (strlen(dash) > 1) epage = atoi(dash + 1); else epage = pagecount; } spage = fz_clampi(spage, 1, pagecount); epage = fz_clampi(epage, 1, pagecount); if (spage < epage) for (page = spage; page <= epage; page++) drawpage(ctx, doc, page); else for (page = spage; page >= epage; page--) drawpage(ctx, doc, page); spec = fz_strsep(&range, ","); } } 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, unsigned int 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_, unsigned int 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]; } #ifdef MUDRAW_THREADS static THREAD_RETURN_TYPE worker_thread(void *arg) { worker_t *me = (worker_t *)arg; do { DEBUG_THREADS(("Worker %d waiting\n", me->num)); SEMAPHORE_WAIT(me->start); DEBUG_THREADS(("Worker %d woken for band %d\n", me->num, me->band)); if (me->band >= 0) drawband(me->ctx, me->savealpha, NULL, me->list, &me->ctm, &me->tbounds, &me->cookie, me->band, me->pix); DEBUG_THREADS(("Worker %d completed band %d\n", me->num, me->band)); SEMAPHORE_TRIGGER(me->stop); } while (me->band >= 0); THREAD_RETURN(); } static THREAD_RETURN_TYPE bgprint_worker(void *arg) { fz_cookie cookie = { 0 }; (void)arg; do { DEBUG_THREADS(("BGPrint waiting\n")); SEMAPHORE_WAIT(bgprint.start); DEBUG_THREADS(("BGPrint woken for pagenum %d\n", bgprint.pagenum)); if (bgprint.pagenum >= 0) { int start = gettime(); memset(&cookie, 0, sizeof(cookie)); dodrawpage(bgprint.ctx, bgprint.page, bgprint.list, bgprint.pagenum, &cookie, start, bgprint.filename, 1); } DEBUG_THREADS(("BGPrint completed band %d\n", bgprint.pagenum)); SEMAPHORE_TRIGGER(bgprint.stop); } while (bgprint.pagenum >= 0); THREAD_RETURN(); } #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, i; fz_context *ctx; fz_alloc_context alloc_ctx = { NULL, trace_malloc, trace_realloc, trace_free }; fz_var(doc); while ((c = fz_getopt(argc, argv, "p:o:F:R:r:w:h:fB:c:G:I:s:A:DiW:H:S:T:U:LvP")) != -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 = atof(fz_optarg); break; case 'r': resolution = atof(fz_optarg); res_specified = 1; break; case 'w': width = atof(fz_optarg); break; case 'h': height = atof(fz_optarg); break; case 'f': fit = 1; break; case 'B': bandheight = atoi(fz_optarg); break; case 'c': out_cs = parse_colorspace(fz_optarg); break; case 'G': gamma_value = atof(fz_optarg); break; case 'I': invert++; break; case 'W': layout_w = atof(fz_optarg); break; case 'H': layout_h = atof(fz_optarg); break; case 'S': layout_em = atof(fz_optarg); break; case 'U': layout_css = fz_optarg; 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 'i': ignore_errors = 1; break; case 'T': #ifdef MUDRAW_THREADS 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 '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 (bandheight == 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); } } ctx = fz_new_context((showmemory == 0 ? NULL : &alloc_ctx), LOCKS_INIT(), (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); if (bgprint.active) { bgprint.ctx = fz_clone_context(ctx); SEMAPHORE_INIT(bgprint.start); SEMAPHORE_INIT(bgprint.stop); THREAD_INIT(bgprint.thread, bgprint_worker, NULL); } if (num_workers > 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; SEMAPHORE_INIT(workers[i].start); SEMAPHORE_INIT(workers[i].stop); THREAD_INIT(workers[i].thread, worker_thread, &workers[i]); } } if (layout_css) { fz_buffer *buf = fz_read_file(ctx, layout_css); fz_write_buffer_byte(ctx, buf, 0); fz_set_user_css(ctx, (char*)buf->data); fz_drop_buffer(ctx, buf); } /* Determine output type */ if (bandheight < 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 (bandheight) { 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); } } } } switch (out_cs) { case CS_MONO: case CS_GRAY: case CS_GRAY_ALPHA: colorspace = fz_device_gray(ctx); break; case CS_RGB: case CS_RGB_ALPHA: colorspace = fz_device_rgb(ctx); break; case CS_CMYK: case CS_CMYK_ALPHA: colorspace = fz_device_cmyk(ctx); break; default: fprintf(stderr, "Unknown colorspace!\n"); exit(1); break; } if (output_format == OUT_PDF) { pdfout = pdf_create_document(ctx); } else if (output_format == OUT_GPROOF) { /* GPROOF files are saved direct. 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_new_output_with_file_ptr(ctx, stdout, 0); timing.count = 0; timing.total = 0; timing.min = 1 << 30; timing.max = 0; timing.minpage = 0; timing.maxpage = 0; timing.minfilename = ""; timing.maxfilename = ""; timing.render_count = 0; timing.render_total = 0; timing.render_min = 1 << 30; timing.render_max = 0; timing.render_minpage = 0; timing.render_maxpage = 0; timing.render_minfilename = ""; timing.render_maxfilename = ""; 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 (output_format == OUT_GPROOF) { fz_save_gproof(ctx, filename, doc, output, resolution, "", ""); } else { if (fz_optind == argc || !isrange(argv[fz_optind])) drawrange(ctx, doc, "1-"); if (fz_optind < argc && isrange(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 (output_format == OUT_PDF) { if (!output) output = "out.pdf"; pdf_save_document(ctx, pdfout, output, NULL); pdf_drop_document(ctx, pdfout); } else if (output_format == OUT_GPROOF) { /* No output file to close */ } else { fz_drop_output(ctx, out); out = NULL; } if (showtime && timing.count > 0) { if (files == 1) { fprintf(stderr, "total %dms / %d pages for an average of %dms\n", timing.total, timing.count, timing.total / timing.count); 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); } } if (num_workers > 0) { for (i = 0; i < num_workers; i++) { workers[i].band = -1; SEMAPHORE_TRIGGER(workers[i].start); SEMAPHORE_WAIT(workers[i].stop); SEMAPHORE_FIN(workers[i].start); SEMAPHORE_FIN(workers[i].stop); THREAD_FIN(workers[i].thread); fz_drop_context(workers[i].ctx); } fz_free(ctx, workers); } if (bgprint.active) { bgprint.pagenum = -1; SEMAPHORE_TRIGGER(bgprint.start); SEMAPHORE_WAIT(bgprint.stop); SEMAPHORE_FIN(bgprint.start); SEMAPHORE_FIN(bgprint.stop); THREAD_FIN(bgprint.thread); fz_drop_context(bgprint.ctx); } fz_drop_context(ctx); LOCKS_FIN(); if (showmemory) { #if defined(_WIN64) #define FMT "%Iu" #elif defined(_WIN32) #define FMT "%u" #else #define FMT "%zu" #endif fprintf(stderr, "Total memory use = " FMT " bytes\n", memtrace_total); fprintf(stderr, "Peak memory use = " FMT " bytes\n", memtrace_peak); fprintf(stderr, "Current memory use = " FMT " bytes\n", memtrace_current); } return (errored != 0); }