Mercurial > hgrepos > Python2 > PyMuPDF
diff mupdf-source/scripts/mutool_draw.py @ 2:b50eed0cc0ef upstream
ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4.
The directory name has changed: no version number in the expanded directory now.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 15 Sep 2025 11:43:07 +0200 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mupdf-source/scripts/mutool_draw.py Mon Sep 15 11:43:07 2025 +0200 @@ -0,0 +1,1260 @@ +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( "<?xml version=\"1.0\"?>\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'<document name="{state.filename}">\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( "</document>\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( "<page mediabox=\"%g %g %g %g\">\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( "</page>\n") + dev.fz_close_device() + dev = None # lgtm [py/unused-local-variable] + + elif state.output_format == OUT_XMLTEXT: + state.out.fz_write_string( "<page mediabox=\"%g %g %g %g\">\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( "</page>\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( "<page bbox=\"%s %s %s %s\" mediabox=\"%s %s %s %s\" />\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
