import getopt import os import re import sys import time if os.environ.get('MUPDF_PYTHON') in ('swig', None): # PYTHONPATH should have been set up to point to a build/shared-*/ # directory containing mupdf.so generated by scripts/mupdfwrap.py and SWIG. import mupdf elif os.environ.get('MUPDF_PYTHON') == 'cppyy': sys.path.insert(0, os.path.abspath(f'{__file__}/../../platform/python')) import mupdf_cppyy del sys.path[0] mupdf = mupdf_cppyy.cppyy.gbl.mupdf else: raise Exception(f'Unrecognised $MUPDF_PYTHON: {os.environ.get("MUPDF_PYTHON")}') # Force stderr to be line-buffered - i.e. python will flush to the underlying # stderr stream every newline. This ensures that our output interleaves with # the output of mupdf C code, making it easier to compare our output with that # of mutool. # sys.stderr = os.fdopen( os.dup( sys.stderr.fileno()), 'w', 1) OUT_NONE = 0 OUT_PNG = 1 OUT_PNM = 2 OUT_PGM = 3 OUT_PPM = 4 OUT_PAM = 5 OUT_PBM = 6 OUT_PKM = 7 OUT_PWG = 8 OUT_PCL = 9 OUT_PS = 10 OUT_PSD = 11 OUT_TEXT = 12 OUT_HTML = 13 OUT_XHTML = 14 OUT_STEXT = 15 OUT_PCLM = 16 OUT_TRACE = 17 OUT_BBOX = 18 OUT_SVG = 19 OUT_XMLTEXT = 20 CS_INVALID = 0 CS_UNSET = 1 CS_MONO = 2 CS_GRAY = 3 CS_GRAY_ALPHA = 4 CS_RGB = 5 CS_RGB_ALPHA = 6 CS_CMYK = 7 CS_CMYK_ALPHA = 8 CS_ICC = 9 CS_INVALID = 0 CS_UNSET = 1 CS_MONO = 2 CS_GRAY = 3 CS_GRAY_ALPHA = 4 CS_RGB = 5 CS_RGB_ALPHA = 6 CS_CMYK = 7 CS_CMYK_ALPHA = 8 CS_ICC = 9 SPOTS_NONE = 0 SPOTS_OVERPRINT_SIM = 1 SPOTS_FULL = 2 class suffix_t: def __init__( self, suffix, format_, spots): self.suffix = suffix self.format = format_ self.spots = spots suffix_table = [ suffix_t( ".png", OUT_PNG, 0 ), suffix_t( ".pgm", OUT_PGM, 0 ), suffix_t( ".ppm", OUT_PPM, 0 ), suffix_t( ".pnm", OUT_PNM, 0 ), suffix_t( ".pam", OUT_PAM, 0 ), suffix_t( ".pbm", OUT_PBM, 0 ), suffix_t( ".pkm", OUT_PKM, 0 ), suffix_t( ".svg", OUT_SVG, 0 ), suffix_t( ".pwg", OUT_PWG, 0 ), suffix_t( ".pclm", OUT_PCLM, 0 ), suffix_t( ".pcl", OUT_PCL, 0 ), suffix_t( ".psd", OUT_PSD, 1 ), suffix_t( ".ps", OUT_PS, 0 ), suffix_t( ".txt", OUT_TEXT, 0 ), suffix_t( ".text", OUT_TEXT, 0 ), suffix_t( ".html", OUT_HTML, 0 ), suffix_t( ".xhtml", OUT_XHTML, 0 ), suffix_t( ".stext", OUT_STEXT, 0 ), suffix_t( ".trace", OUT_TRACE, 0 ), suffix_t( ".raw", OUT_XMLTEXT, 0 ), suffix_t( ".bbox", OUT_BBOX, 0 ), ] class cs_name_t: def __init__( self, name, colorspace): self.name = name self.colorspace = colorspace cs_name_table = dict( 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, ) class format_cs_table_t: def __init__( self, format_, default_cs, permitted_cs): self.format = format_ self.default_cs = default_cs self.permitted_cs = permitted_cs format_cs_table = [ format_cs_table_t( OUT_PNG, CS_RGB, [ CS_GRAY, CS_GRAY_ALPHA, CS_RGB, CS_RGB_ALPHA, CS_ICC ] ), format_cs_table_t( OUT_PPM, CS_RGB, [ CS_GRAY, CS_RGB ] ), format_cs_table_t( OUT_PNM, CS_GRAY, [ CS_GRAY, CS_RGB ] ), format_cs_table_t( OUT_PAM, CS_RGB_ALPHA, [ CS_GRAY, CS_GRAY_ALPHA, CS_RGB, CS_RGB_ALPHA, CS_CMYK, CS_CMYK_ALPHA ] ), format_cs_table_t( OUT_PGM, CS_GRAY, [ CS_GRAY, CS_RGB ] ), format_cs_table_t( OUT_PBM, CS_MONO, [ CS_MONO ] ), format_cs_table_t( OUT_PKM, CS_CMYK, [ CS_CMYK ] ), format_cs_table_t( OUT_PWG, CS_RGB, [ CS_MONO, CS_GRAY, CS_RGB, CS_CMYK ] ), format_cs_table_t( OUT_PCL, CS_MONO, [ CS_MONO, CS_RGB ] ), format_cs_table_t( OUT_PCLM, CS_RGB, [ CS_RGB, CS_GRAY ] ), format_cs_table_t( OUT_PS, CS_RGB, [ CS_GRAY, CS_RGB, CS_CMYK ] ), format_cs_table_t( OUT_PSD, CS_CMYK, [ CS_GRAY, CS_GRAY_ALPHA, CS_RGB, CS_RGB_ALPHA, CS_CMYK, CS_CMYK_ALPHA, CS_ICC ] ), format_cs_table_t( OUT_TRACE, CS_RGB, [ CS_RGB ] ), format_cs_table_t( OUT_XMLTEXT, CS_RGB, [ CS_RGB ] ), format_cs_table_t( OUT_BBOX, CS_RGB, [ CS_RGB ] ), format_cs_table_t( OUT_SVG, CS_RGB, [ CS_RGB ] ), format_cs_table_t( OUT_TEXT, CS_RGB, [ CS_RGB ] ), format_cs_table_t( OUT_HTML, CS_RGB, [ CS_RGB ] ), format_cs_table_t( OUT_XHTML, CS_RGB, [ CS_RGB ] ), format_cs_table_t( OUT_STEXT, CS_RGB, [ CS_RGB ] ), ] def stat_mtime(path): try: return os.path.getmtime(path) except Exception: return 0 class worker_t: def __init__( self): self.num = 0 self.band = 0 self.list = None self.ctm = None self.tbounds = None self.pix = None self.bit = None self.cookie = mupdf.FzCookie() class state: output = None out = None output_pagenum = 0 output_file_per_page = 0 format_ = None output_format = OUT_NONE rotation = 0 resolution = 72 res_specified = 0 width = 0 height = 0 fit = 0 layout_w = mupdf.FZ_DEFAULT_LAYOUT_W layout_h = mupdf.FZ_DEFAULT_LAYOUT_H layout_em = mupdf.FZ_DEFAULT_LAYOUT_EM layout_css = None layout_use_doc_css = 1 min_line_width = 0.0 showfeatures = 0 showtime = 0 showmemory = 0 showmd5 = 0 no_icc = 0 ignore_errors = 0 uselist = 1 alphabits_text = 8 alphabits_graphics = 8 out_cs = CS_UNSET proof_filename = None proof_cs = mupdf.FzColorspace() icc_filename = None gamma_value = 1 invert = 0 band_height = 0 lowmemory = 0 quiet = 0 errored = 0 colorspace = mupdf.FzColorspace() oi = None spots = SPOTS_OVERPRINT_SIM alpha = 0 useaccel = 1 filename = None files = 0 num_workers = 0 workers = None bander = None layer_config = None class bgprint: active = 0 started = 0 pagenum = 0 filename = None list_ = None page = None interptime = 0 seps = None class timing: count = 0 total = 0 min_ = 0 max_ = 0 mininterp = 0 maxinterp = 0 minpage = 0 maxpage = 0 minfilename = None maxfilename = None layout = 0 minlayout = 0 maxlayout = 0 minlayoutfilename = None maxlayoutfilename = None def usage(): sys.stderr.write( f''' mudraw version {mupdf.FZ_VERSION} " Usage: mudraw [options] file [pages] \t-p -\tpassword \t-o -\toutput file name (%d for page number) \t-F -\toutput format (default inferred from output file name) \t\traster: png, pnm, pam, pbm, pkm, pwg, pcl, ps \t\tvector: svg, pdf, trace \t\ttext: txt, html, stext \t-q\tbe quiet (don't print progress messages) \t-s -\tshow extra information: \t\tm - show memory use \t\tt - show timings \t\tf - show page features \t\t5 - show md5 checksum of rendered image \t-R -\trotate clockwise (default: 0 degrees) \t-r -\tresolution in dpi (default: 72) \t-w -\twidth (in pixels) (maximum width if -r is specified) \t-h -\theight (in pixels) (maximum height if -r is specified) \t-f -\tfit width and/or height exactly; ignore original aspect ratio \t-B -\tmaximum band_height (pXm, pcl, pclm, ps, psd and png output only) \t-T -\tnumber of threads to use for rendering (banded mode only) \t-W -\tpage width for EPUB layout \t-H -\tpage height for EPUB layout \t-S -\tfont size for EPUB layout \t-U -\tfile name of user stylesheet for EPUB layout \t-X\tdisable document styles for EPUB layout \t-a\tdisable usage of accelerator file \t-c -\tcolorspace (mono, gray, grayalpha, rgb, rgba, cmyk, cmykalpha, filename of ICC profile) \t-e -\tproof icc profile (filename of ICC profile) \t-G -\tapply gamma correction \t-I\tinvert colors \t-A -\tnumber of bits of antialiasing (0 to 8) \t-A -/-\tnumber of bits of antialiasing (0 to 8) (graphics, text) \t-l -\tminimum stroked line width (in pixels) \t-D\tdisable use of display list \t-i\tignore errors \t-L\tlow memory mode (avoid caching, clear objects after each page) \t-P\tparallel interpretation/rendering \t-N\tdisable ICC workflow (\"N\"o color management) \t-O -\tControl spot/overprint rendering \t\t 0 = No spot rendering \t\t 1 = Overprint simulation (default) \t\t 2 = Full spot rendering \t-y l\tList the layer configs to stderr \t-y -\tSelect layer config (by number) \t-y -{{,-}}*\tSelect layer config (by number), and toggle the listed entries \tpages\tcomma separated list of page numbers and ranges ''') sys.exit(1) gettime_first = None def gettime(): global gettime_first if gettime_first is None: gettime_first = time.time() now = time.time() return (now - gettime_first) * 1000 def has_percent_d(s): # find '%[0-9]*d' */ m = re.search( '%[0-9]*d', s) if m: return 1 return 0 # Output file level (as opposed to page level) headers def file_level_headers(): if state.output_format in (OUT_STEXT, OUT_TRACE, OUT_XMLTEXT, OUT_BBOX): state.out.fz_write_string( "\n") if state.output_format == OUT_HTML: state.out.fz_print_stext_header_as_html() if state.output_format == OUT_XHTML: state.out.fz_print_stext_header_as_xhtml() if state.output_format in (OUT_STEXT, OUT_TRACE, OUT_BBOX): state.out.fz_write_string( f'\n') if state.output_format == OUT_PS: state.out.fz_write_ps_file_header() if state.output_format == OUT_PWG: state.out.fz_write_pwg_file_header() if state.output_format == OUT_PCLM: opts = mupdf.FzPclmOptions( 'compression=flate') state.bander = mupdf.FzBandWriter(state.out, opts) def file_level_trailers(): if state.output_format in (OUT_STEXT, OUT_TRACE, OUT_BBOX): state.out.fz_write_string( "\n") if state.output_format == OUT_HTML: state.out.fz_print_stext_trailer_as_html() if state.output_format == OUT_XHTML: state.out.fz_print_stext_trailer_as_xhtml() if state.output_format == OUT_PS: state.out.fz_write_ps_file_trailer( state.output_pagenum) def drawband( page, list_, ctm, tbounds, cookie, band_start, pix): bit = None if pix.alpha(): pix.fz_clear_pixmap() else: pix.fz_clear_pixmap_with_value( 255) dev = mupdf.FzDevice( mupdf.FzMatrix(), pix, state.proof_cs) if state.lowmemory: dev.enable_device_hints( mupdf.FZ_NO_CACHE) if state.alphabits_graphics == 0: dev.enable_device_hints( mupdf.FZ_DONT_INTERPOLATE_IMAGES) if list_: list_.fz_run_display_list( dev, ctm, tbounds, cookie) else: page.fz_run_page( dev, ctm, cookie) dev.fz_close_device() dev = None # lgtm [py/unused-local-variable] if state.invert: pix.fz_invert_pixmap() if state.gamma_value != 1: pix.fz_gamma_pixmap( state.gamma_value) if ((state.output_format == OUT_PCL or state.output_format == OUT_PWG) and state.out_cs == CS_MONO) or (state.output_format == OUT_PBM) or (state.output_format == OUT_PKM): bit = mupdf.FzBitmap( pix, mupdf.FzHalftone(), band_start) return bit def dodrawpage( page, list_, pagenum, cookie, start, interptime, filename, bg, seps): if state.output_file_per_page: file_level_headers() if list_: mediabox = mupdf.FzRect( list_) else: mediabox = page.fz_bound_page() if state.output_format == OUT_TRACE: state.out.fz_write_string( "\n" % ( mediabox.x0, mediabox.y0, mediabox.x1, mediabox.y1)) dev = mupdf.FzDevice( state.out) if state.lowmemory: dev.fz_enable_device_hints( mupdf.FZ_NO_CACHE) if list_: list_.fz_run_display_list( dev, mupdf.FzMatrix(), mupdf.FzRect(mupdf.fz_infinite_rect), cookie) else: page.fz_run_page( dev, fz_identity, cookie) state.out.fz_write_string( "\n") dev.fz_close_device() dev = None # lgtm [py/unused-local-variable] elif state.output_format == OUT_XMLTEXT: state.out.fz_write_string( "\n" % ( mediabox.x0, mediabox.y0, mediabox.x1, mediabox.y1)) dev = mupdf.FzDevice.fz_new_raw_device( state.out) if list_: list_.fz_run_display_list( dev, mupdf.FzMatrix(), mupdf.FzRect(mupdf.fz_infinite_rect), cookie) else: page.fz_run_page( dev, fz_identity, cookie) state.out.fz_write_string( "\n") dev.fz_close_device() dev = None # lgtm [py/unused-local-variable] elif state.output_format == OUT_BBOX: bbox = mupdf.FzRect( mupdf.FzRect.Fixed_EMPTY) dev = mupdf.FzDevice( bbox) if state.lowmemory: dev.fz_enable_device_hints( mupdf.FZ_NO_CACHE) if list_: list_.fz_run_display_list( dev, fz_identity, mupdf.FzRect(mupdf.fz_infinite_rect), cookie) else: page.fz_run_page( dev, fz_identity, cookie) dev.fz_close_device() state.out.fz_write_string( "\n", bbox.x0, bbox.y0, bbox.x1, bbox.y1, mediabox.x0, mediabox.y0, mediabox.x1, mediabox.y1, ) elif state.output_format in (OUT_TEXT, OUT_HTML, OUT_XHTML, OUT_STEXT): zoom = state.resolution / 72 ctm = mupdf.FzMatrix(mupdf.fz_pre_scale(mupdf.fz_rotate(state.rotation), zoom, zoom)) stext_options = mupdf.FzStextOptions() stext_options.flags = mupdf.FZ_STEXT_PRESERVE_IMAGES if (state.output_format == OUT_HTML or state.output_format == OUT_XHTML) else 0 text = mupdf.FzStextPage( mediabox) dev = mupdf.FzDevice( text, stext_options) if state.lowmemory: fz_enable_device_hints( dev, FZ_NO_CACHE) if list_: list_.fz_run_display_list( dev, ctm, mupdf.FzRect(mupdf.fz_infinite_rect), cookie) else: page.fz_run_page( dev, ctm, cookie) dev.fz_close_device() dev = None # lgtm [py/unused-local-variable] if state.output_format == OUT_STEXT: state.out.fz_print_stext_page_as_xml( text, pagenum) elif state.output_format == OUT_HTML: state.out.fz_print_stext_page_as_html( text, pagenum) elif state.output_format == OUT_XHTML: state.out.fz_print_stext_page_as_xhtml( text, pagenum) elif state.output_format == OUT_TEXT: state.out.fz_print_stext_page_as_text( text) state.out.fz_write_string( "\f\n") elif state.output_format == OUT_SVG: zoom = state.resolution / 72 ctm = mupdf.FzMatrix(zoom, zoom) ctm.fz_pre_rotate( state.rotation) tbounds = mupdf.FzRect(mediabox, ctm) if not state.output or state.output == "-": state.out = mupdf.FzOutput( mupdf.FzOutput.Fixed_STDOUT) else: buf = mupdf.fz_format_output_path( state.output, pagenum) state.out = mupdf.FzOutput( buf, 0) dev = mupdf.FzDevice( state.out, tbounds.x1-tbounds.x0, tbounds.y1-tbounds.y0, mupdf.FZ_SVG_TEXT_AS_PATH, 1) if state.lowmemory: dev.fz_enable_device_hints( dev, mupdf.FZ_NO_CACHE) if list_: list_.fz_run_display_list( dev, ctm, tbounds, cookie) else: page.fz_run_page( dev, ctm, cookie) dev.fz_close_device() state.out.fz_close_output() else: zoom = state.resolution / 72 ctm = mupdf.fz_pre_scale( mupdf.fz_rotate(state.rotation), zoom, zoom) tbounds = mupdf.FzRect(mediabox, ctm) ibounds = tbounds.fz_round_rect() # Make local copies of our width/height w = state.width h = state.height # If a resolution is specified, check to see whether w/h are # exceeded; if not, unset them. */ if state.res_specified: t = ibounds.x1 - ibounds.x0 if w and t <= w: w = 0 t = ibounds.y1 - ibounds.y0 if h and t <= h: h = 0 # Now w or h will be 0 unless they need to be enforced. if w or h: scalex = w / (tbounds.x1 - tbounds.x0) scaley = h / (tbounds.y1 - tbounds.y0) if state.fit: if w == 0: scalex = 1.0 if h == 0: scaley = 1.0 else: if w == 0: scalex = scaley if h == 0: scaley = scalex if not state.fit: if scalex > scaley: scalex = scaley else: scaley = scalex scale_mat = mupdf.fz_scale(scalex, scaley) ctm = mupdf.fz_concat(ctm, scale_mat) tbounds = mupdf.FzRect( mediabox, ctm) ibounds = tbounds.fz_round_rect() tbounds = ibounds.fz_rect_from_irect() band_ibounds = ibounds bands = 1 totalheight = ibounds.y1 - ibounds.y0 drawheight = totalheight if state.band_height != 0: # Banded rendering; we'll only render to a # given height at a time. drawheight = state.band_height if totalheight > state.band_height: band_ibounds.y1 = band_ibounds.y0 + state.band_height bands = (totalheight + state.band_height-1)/state.band_height tbounds.y1 = tbounds.y0 + state.band_height + 2 #DEBUG_THREADS(("Using %d Bands\n", bands)); if state.num_workers > 0: for band in range( min(state.num_workers, bands)): state.workers[band].band = band state.workers[band].ctm = ctm state.workers[band].tbounds = tbounds state.workers[band].cookie = mupdf.FzCookie() state.workers[band].list = list_ state.workers[band].pix = mupdf.FzPixmap( state.colorspace, band_ibounds, seps, state.alpha) state.workers[band].pix.fz_set_pixmap_resolution( state.resolution, state.resolution) ctm.f -= drawheight pix = state.workers[0].pix else: pix = mupdf.FzPixmap( state.colorspace, band_ibounds, seps, state.alpha) pix.fz_set_pixmap_resolution( int(state.resolution), int(state.resolution)) # Output any page level headers (for banded formats) if state.output: state.bander = None if state.output_format == OUT_PGM or state.output_format == OUT_PPM or state.output_format == OUT_PNM: state.bander = mupdf.FzBandWriter( state.out, mupdf.FzBandWriter.PNM) elif state.output_format == OUT_PAM: state.bander = mupdf.FzBandWriter( state.out, mupdf.FzBandWriter.PAM) elif state.output_format == OUT_PNG: state.bander = mupdf.FzBandWriter( state.out, mupdf.FzBandWriter.PNG) elif state.output_format == OUT_PBM: state.bander = mupdf.FzBandWriter( state.out, mupdf.FzBandWriter.PBM) elif state.output_format == OUT_PKM: state.bander = mupdf.FzBandWriter( state.out, mupdf.FzBandWriter.PKM) elif state.output_format == OUT_PS: state.bander = mupdf.FzBandWriter( state.out, mupdf.FzBandWriter.PS) elif state.output_format == OUT_PSD: state.bander = mupdf.FzBandWriter( state.out, mupdf.FzBandWriter.PSD) elif state.output_format == OUT_PWG: if state.out_cs == CS_MONO: state.bander = mupdf.FzBandWriter( state.out, mupdf.FzBandWriter.MONO, mupdf.FzPwgOptions()) else: state.bander = mupdf.FzBandWriter( state.out, mupdf.FzBandWriter.COLOR, mupdf.FzPwgOptions()) elif state.output_format == OUT_PCL: if state.out_cs == CS_MONO: state.bander = mupdf.FzBandWriter( state.out, mupdf.FzBandWriter.MONO, mupdf.FzPclOptions()) else: state.bander = mupdf.FzBandWriter( state.out, mupdf.FzBandWriter.COLOR, mupdf.FzPclOptions()) if state.bander: state.bander.fz_write_header( pix.w(), totalheight, pix.n(), pix.alpha(), pix.xres(), pix.yres(), state.output_pagenum, pix.colorspace(), pix.seps()) state.output_pagenum += 1 for band in range( bands): if state.num_workers > 0: w = state.workers[band % state.num_workers] pix = w.pix bit = w.bit w.bit = None cookie.fz_increment_errors(w.cookie.errors()) else: bit = drawband( page, list_, ctm, tbounds, cookie, band * state.band_height, pix) if state.output: if state.bander: if bit: state.bander.fz_write_band( bit.stride(), drawheight, bit.samples()) else: state.bander.fz_write_band( pix.stride(), drawheight, pix.samples()) bit = None if state.num_workers > 0 and band + state.num_workers < bands: w = state.workers[band % state.num_workers] w.band = band + state.num_workers w.ctm = ctm w.tbounds = tbounds w.cookie = mupdf.FzCookie() ctm.f -= drawheight # FIXME if state.showmd5: digest = pix.fz_md5_pixmap() sys.stderr.write( ' ') for i in range(16): sys.stderr.write( '%02x', digest[i]) if state.output_file_per_page: file_level_trailers() if state.showtime: end = gettime() 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 += 1 sys.stderr.write( " %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 += 1 sys.stderr.write( " %dms" % diff) if not state.quiet or state.showfeatures or state.showtime or state.showmd5: sys.stderr.write( "\n") if state.lowmemory: mupdf.fz_empty_store() if state.showmemory: # Use low-level fn because mupdf.fz_stderr() returns fz_output*, not # FzOutput. mupdf.ll_fz_dump_glyph_cache_stats(mupdf.ll_fz_stderr()) mupdf.fz_flush_warnings() if cookie.errors(): state.errored = 1 def bgprint_flush(): if not bgprint.active or not bgprint.started: return bgprint.started = 0 def drawpage( doc, pagenum): list_ = None cookie = mupdf.FzCookie() seps = None features = "" start = gettime() if state.showtime else 0 page = mupdf.FzPage( doc, pagenum - 1) if state.spots != SPOTS_NONE: seps = page.fz_page_separations() if seps.m_internal: n = seps.fz_count_separations() if state.spots == SPOTS_FULL: for i in range(n): seps.fz_set_separation_behavior( i, mupdf.FZ_SEPARATION_SPOT) else: for i in range(n): seps.fz_set_separation_behavior( i, mupdf.FZ_SEPARATION_COMPOSITE) elif page.fz_page_uses_overprint(): # This page uses overprint, so we need an empty # sep object to force the overprint simulation on. seps = mupdf.FzSeparations(0) elif state.oi and state.oi.m_internal and state.oi.fz_colorspace_n() != state.colorspace.fz_colorspace_n(): # We have an output intent, and it's incompatible # with the colorspace our device needs. Force the # overprint simulation on, because this ensures that # we 'simulate' the output intent too. */ seps = mupdf.FzSeparations(0) if state.uselist: list_ = mupdf.FzDisplayList( page.fz_bound_page()) dev = mupdf.FzDevice( list_) if state.lowmemory: dev.fz_enable_device_hints( FZ_NO_CACHE) page.fz_run_page( dev, mupdf.FzMatrix(), cookie) dev.fz_close_device() if bgprint.active and state.showtime: end = gettime() start = end - start if state.showfeatures: # SWIG doesn't appear to handle the out-param is_color in # mupdf.Device() constructor that wraps fz_new_test_device(), so we use # the underlying mupdf function() instead. # dev, iscolor = mupdf.ll_fz_new_test_device( 0.02, 0, None) dev = mupdf.FzDevice( dev) if state.lowmemory: dev.fz_enable_device_hints( mupdf.FZ_NO_CACHE) if list_: list_.fz_run_display_list( dev, mupdf.FzMatrix(mupdf.fz_identity), mupdf.FzRect(mupdf.fz_infinite_rect), mupdf.FzCookie()) else: page.fz_run_page( dev, fz_identity, cookie) dev.fz_close_device() features = " color" if iscolor else " grayscale" if state.output_file_per_page: bgprint_flush() if state.out: state.out.fz_close_output() text_buffer = mupdf.fz_format_output_path( state.output, pagenum) state.out = mupdf.FzOutput( text_buffer, 0) if bgprint.active: bgprint_flush() if bgprint.active: if not state.quiet or state.showfeatures or state.showtime or state.showmd5: sys.stderr.write( "page %s %d%s" % (state.filename, pagenum, features)) bgprint.started = 1 bgprint.page = page bgprint.list = list_ bgprint.seps = seps bgprint.filename = state.filename bgprint.pagenum = pagenum bgprint.interptime = start else: if not state.quiet or state.showfeatures or state.showtime or state.showmd5: sys.stderr.write( "page %s %d%s" % (state.filename, pagenum, features)) dodrawpage( page, list_, pagenum, cookie, start, 0, state.filename, 0, seps) def drawrange( doc, range_): pagecount = doc.fz_count_pages() while 1: range_, spage, epage = mupdf.fz_parse_page_range( range_, pagecount) if range_ is None: break if spage < epage: for page in range(spage, epage+1): drawpage( doc, page) else: for page in range( spage, epage-1, -1): drawpage( doc, page) def parse_colorspace( name): ret = cs_name_table.get( name) if ret: return ret state.icc_filename = name return CS_ICC class trace_info: def __init__( self): self.current = 0 self.peak = 0 self.total = 0 def iswhite(ch): return ( ch == '\011' or ch == '\012' or ch == '\014' or ch == '\015' or ch == '\040' ) def apply_layer_config( doc, lc): pass def convert_to_accel_path(absname): tmpdir = os.getenv('TEMP') if not tmpdir: tmpdir = os.getenv('TMP') if not tmpdir: tmpdir = '/var/tmp' if not os.path.isdir(tmpdir): tmpdir = '/tmp' if absname.startswith( '/') or absname.startswith( '\\'): absname = absname[1:] absname = absname.replace( '/', '%') absname = absname.replace( '\\', '%') absname = absname.replace( ':', '%') return '%s/%s.accel' % (tmpdir, absname) def get_accelerator_filename( filename): absname = os.path.realpath( filename) return convert_to_accel_path( absname) def save_accelerator(doc, filename): if not doc.fz_document_supports_accelerator(): return absname = get_accelerator_filename( filename) doc.fz_save_accelerator( absname) def draw( argv): password = '' info = trace_info() items, argv = getopt.getopt( argv, 'qp:o:F:R:r:w:h:fB:c:e:G:Is:A:DiW:H:S:T:U:XLvPl:y:NO:a') for option, value in items: if 0: pass elif option == '-q': state.quiet = 1 elif option == '-p': password = value elif option == '-o': state.output = value elif option == '-F': state.format_ = value elif option == '-R': state.rotation = float( value) elif option == '-r': state.resolution = float( value) state.res_specified = 1 elif option == '-w': state.width = float( value) elif option == '-h': state.height = float( value) elif option == '-f': state.fit = 1 elif option == '-B': state.band_height = int( value) elif option == '-c': state.out_cs = parse_colorspace( value) elif option == '-e': state.proof_filename = value elif option == '-G': state.gamma_value = float( value) elif option == '-I': state.invert += 1 elif option == '-W': state.layout_w = float( value) elif option == '-H': state.layout_h = float( value) elif option == '-S': state.layout_em = float( value) elif option == '-U': state.layout_css = value elif option == '-X': state.layout_use_doc_css = 0 elif option == '-O': state.spots = float( value) if not mupdf.FZ_ENABLE_SPOT_RENDERING: sys.stderr.write( 'Spot rendering/Overprint/Overprint simulation not enabled in this build\n') state.spots = SPOTS_NONE elif option == '-s': if 't' in value: state.showtime += 1 if 'm' in value: state.showmemory += 1 if 'f' in value: state.showfeatures += 1 if '5' in value: state.showmd5 += 1 elif option == '-A': state.alphabits_graphics = int(value) sep = value.find( '/') if sep >= 0: state.alphabits_text = int(value[sep+1:]) else: state.alphabits_text = state.alphabits_graphics elif option == '-D': state.uselist = 0 elif option == '-l': state.min_line_width = float(value) elif option == '-i': state.ignore_errors = 1 elif option == '-N': state.no_icc = 1 elif option == '-T': state.num_workers = int(value) elif option == '-L': state.lowmemory = 1 elif option == '-P': bgprint.active = 1 elif option == '-y': state.layer_config = value elif option == '-a': state.useaccel = 0 elif option == '-v': sys.stderr.write( f'mudraw version {mupdf.FZ_VERSION}\n') if not argv: usage() if state.num_workers > 0: if state.uselist == 0: sys.stderr.write('cannot use multiple threads without using display list\n') sys.exit(1) if state.band_height == 0: sys.stderr.write('Using multiple threads without banding is pointless\n') if bgprint.active: if state.uselist == 0: sys.stderr.write('cannot bgprint without using display list\n') sys.exit(1) if state.proof_filename: proof_buffer = mupdf.FzBuffer( state.proof_filename) state.proof_cs = mupdf.FzColorspace( FZ_COLORSPACE_NONE, 0, None, proof_buffer) mupdf.fz_set_text_aa_level( state.alphabits_text) mupdf.fz_set_graphics_aa_level( state.alphabits_graphics) mupdf.fz_set_graphics_min_line_width( state.min_line_width) if state.no_icc: mupdf.fz_disable_icc() else: mupdf.fz_enable_icc() if state.layout_css: buf = mupdf.FzBuffer( state.layout_css) mupdf.fz_set_user_css( buf.string_from_buffer()) mupdf.fz_set_use_document_css( state.layout_use_doc_css) # Determine output type if state.band_height < 0: sys.stderr.write( 'Bandheight must be > 0\n') sys.exit(1) state.output_format = OUT_PNG if state.format_: for i in range(len(suffix_table)): if state.format_ == suffix_table[i].suffix[1:]: state.output_format = suffix_table[i].format if state.spots == SPOTS_FULL and suffix_table[i].spots == 0: sys.stderr.write( f'Output format {suffix_table[i].suffix[1:]} does not support spot rendering.\nDoing overprint simulation instead.\n') state.spots = SPOTS_OVERPRINT_SIM break else: sys.stderr.write( f'Unknown output format {format}\n') sys.exit(1) elif state.output: suffix = state.output i = 0 while 1: if i == len(suffix_table): break s = suffix.find( suffix_table[i].suffix) if s != -1: suffix = suffix_table[i].suffix[s+1:] state.output_format = suffix_table[i].format if state.spots == SPOTS_FULL and suffix_table[i].spots == 0: sys.stderr.write( 'Output format {suffix_table[i].suffix[1:]} does not support spot rendering\nDoing overprint simulation instead.\n') state.spots = SPOTS_OVERPRINT_SIM i = 0 else: i += 1 if state.band_height: if state.output_format not in ( OUT_PAM, OUT_PGM, OUT_PPM, OUT_PNM, OUT_PNG, OUT_PBM, OUT_PKM, OUT_PCL, OUT_PCLM, OUT_PS, OUT_PSD): sys.stderr.write( 'Banded operation only possible with PxM, PCL, PCLM, PS, PSD, and PNG outputs\n') sys.exit(1) if state.showmd5: sys.stderr.write( 'Banded operation not compatible with MD5\n') sys.exit(1) for i in range(len(format_cs_table)): if format_cs_table[i].format == state.output_format: if state.out_cs == CS_UNSET: state.out_cs = format_cs_table[i].default_cs for j in range( len(format_cs_table[i].permitted_cs)): if format_cs_table[i].permitted_cs[j] == state.out_cs: break else: sys.stderr.write( 'Unsupported colorspace for this format\n') sys.exit(1) state.alpha = 1 if state.out_cs in ( CS_MONO, CS_GRAY, CS_GRAY_ALPHA): state.colorspace = mupdf.FzColorspace( mupdf.FzColorspace.Fixed_GRAY) state.alpha = (state.out_cs == CS_GRAY_ALPHA) elif state.out_cs in ( CS_RGB, CS_RGB_ALPHA): state.colorspace = mupdf.FzColorspace( mupdf.FzColorspace.Fixed_RGB) state.alpha = (state.out_cs == CS_RGB_ALPHA) elif state.out_cs in ( CS_CMYK, CS_CMYK_ALPHA): state.colorspace = mupdf.FzColorspace( mupdf.FzColorspace.Fixed_CMYK) state.alpha = (state.out_cs == CS_CMYK_ALPHA) elif state.out_cs == CS_ICC: try: icc_buffer = mupdf.FzBuffer( state.icc_filename) state.colorspace = Colorspace( mupdf.FZ_COLORSPACE_NONE, 0, None, icc_buffer) except Exception as e: sys.stderr.write( 'Invalid ICC destination color space\n') sys.exit(1) if state.colorspace.m_internal is None: sys.stderr.write( 'Invalid ICC destination color space\n') sys.exit(1) state.alpha = 0 else: sys.stderr.write( 'Unknown colorspace!\n') sys.exit(1) if state.out_cs != CS_ICC: state.colorspace = mupdf.FzColorspace( state.colorspace) else: # Check to make sure this icc profile is ok with the output format */ okay = 0 for i in range( len(format_cs_table)): if format_cs_table[i].format == state.output_format: for j in range( len(format_cs_table[i].permitted_cs)): x = format_cs_table[i].permitted_cs[j] if x in ( CS_MONO, CS_GRAY, CS_GRAY_ALPHA): if state.colorspace.fz_colorspace_is_gray(): okay = 1 elif x in ( CS_RGB, CS_RGB_ALPHA): if state.colorspace.fz_colorspace_is_rgb(): okay = 1 elif x in ( CS_CMYK, CS_CMYK_ALPHA): if state.colorspace.fz_colorspace_is_cmyk(): okay = 1 if not okay: sys.stderr.write( 'ICC profile uses a colorspace that cannot be used for this format\n') sys.exit(1) if state.output_format == OUT_SVG: # SVG files are always opened for each page. Do not open "output". pass elif state.output and (not state.output.startswith('-') or len(state.output) >= 2) and len(state.output) >= 1: if has_percent_d(state.output): state.output_file_per_page = 1 else: state.out = mupdf.FzOutput(state.output, 0) else: state.quiet = 1 # automatically be quiet if printing to stdout if 0: # lgtm [py/unreachable-statement] # Windows specific code to make stdout binary. if state.output_format not in( OUT_TEXT, OUT_STEXT, OUT_HTML, OUT_XHTML, OUT_TRACE, OUT_XMLTEXT): setmode(fileno(stdout), O_BINARY) state.out = mupdf.FzOutput( mupdf.FzOutput.Fixed_STDOUT) state.filename = argv[0] if not state.output_file_per_page: file_level_headers() 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 = "" timing.layout = 0 timing.minlayout = 1 << 30 timing.maxlayout = 0 timing.minlayoutfilename = "" timing.maxlayoutfilename = "" if state.showtime and bgprint.active: timing.total = gettime() fz_optind = 0 try: while fz_optind < len( argv): try: accel = None state.filename = argv[fz_optind] fz_optind += 1 state.files += 1 if not state.useaccel: accel = None # If there was an accelerator to load, what would it be called? else: accelpath = get_accelerator_filename( state.filename) # Check whether that file exists, and isn't older than # the document. atime = stat_mtime( accelpath) dtime = stat_mtime( state.filename) if atime == 0: # No accelerator pass elif atime > dtime: accel = accelpath else: # Accelerator data is out of date os.unlink( accelpath) accel = None # In case we have jumped up from below # Unfortunately if accel=None, SWIG doesn't seem to think of it # as a char*, so we end up in fz_open_document_with_stream(). # # If we try to avoid this by setting accel='', SWIG correctly # calls Document(const char *filename, const char *accel) => # fz_open_accelerated_document(), but the latter function tests # for NULL not "" so fails. # # So we choose the constructor explicitly rather than leaving # it up to SWIG. # if accel: doc = mupdf.FzDocument(state.filename, accel) else: doc = mupdf.FzDocument(state.filename) if doc.fz_needs_password(): if not doc.fz_authenticate_password( password): raise Exception( f'cannot authenticate password: {state.filename}') # Once document is open check for output intent colorspace state.oi = doc.fz_document_output_intent() if state.oi.m_internal: # See if we had explicitly set a profile to render if state.out_cs != CS_ICC: # In this case, we want to render to the output intent # color space if the number of channels is the same if state.oi.fz_colorspace_n() == state.colorspace.fz_colorspace_n(): state.colorspace = state.oi layouttime = time.time() doc.fz_layout_document( state.layout_w, state.layout_h, state.layout_em) doc.fz_count_pages() layouttime = time.time() - layouttime timing.layout += layouttime if layouttime < timing.minlayout: timing.minlayout = layouttime timing.minlayoutfilename = state.filename if layouttime > timing.maxlayout: timing.maxlayout = layouttime timing.maxlayoutfilename = state.filename if state.layer_config: apply_layer_config( doc, state.layer_config) if fz_optind == len(argv) or not mupdf.fz_is_page_range( argv[fz_optind]): drawrange( doc, "1-N") if fz_optind < len( argv) and mupdf.fz_is_page_range( argv[fz_optind]): drawrange( doc, argv[fz_optind]) fz_optind += 1 bgprint_flush() if state.useaccel: save_accelerator( doc, state.filename) except Exception as e: if not state.ignore_errors: raise bgprint_flush() sys.stderr.write( f'ignoring error in {state.filename}\n') except Exception as e: bgprint_flush() sys.stderr.write( f'error: cannot draw \'{state.filename}\' because: {e}\n') state.errored = 1 if 0: # Enable for debugging. import traceback traceback.print_exc() if not state.output_file_per_page: file_level_trailers() if state.out: state.out.fz_close_output() state.out = None if state.showtime and timing.count > 0: if bgprint.active: timing.total = gettime() - timing.total if state.files == 1: sys.stderr.write( f'total {timing.total:.0f}ms ({timing.layout:.0f}ms layout) / {timing.count} pages for an average of {timing.total / timing.count:.0f}ms\n') if bgprint.active: sys.stderr.write( f'fastest page {timing.minpage}: {timing.mininterp:.0f}ms (interpretation) {timing.min - timing.mininterp:.0f}ms (rendering) {timing.min:.0f}ms(total)\n') sys.stderr.write( f'slowest page {timing.maxpage}: {timing.maxinterp:.0f}ms (interpretation) {timing.max - timing.maxinterp:.0f}ms (rendering) {timing.max:.0f}ms(total)\n') else: sys.stderr.write( f'fastest page {timing.minpage}: {timing.min:.0f}ms\n') sys.stderr.write( f'slowest page {timing.maxpage}: {timing.max:.0f}ms\n') else: sys.stderr.write( f'total {timing.total:.0f}ms ({timing.layout:.0f}ms layout) / {timing.count} pages for an average of {timing.total / timing.count:.0f}ms in {state.files} files\n') sys.stderr.write( f'fastest layout: {timing.minlayout:.0f}ms ({timing.minlayoutfilename})\n') sys.stderr.write( f'slowest layout: {timing.maxlayout:.0f}ms ({timing.maxlayoutfilename})\n') sys.stderr.write( f'fastest page {timing.minpage}: {timing.min:.0f}ms ({timing.minfilename})\n') sys.stderr.write( f'slowest page {timing.maxpage}: {timing.max:.0f}ms ({timing.maxfilename})\n') if state.showmemory: sys.stderr.write( f'Memory use total={info.total} peak={info.peak} current={info.current}\n') return state.errored != 0