Mercurial > hgrepos > Python2 > PyMuPDF
comparison mupdf-source/scripts/mutool_draw.py @ 3:2c135c81b16c
MERGE: upstream PyMuPDF 1.26.4 with MuPDF 1.26.7
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 15 Sep 2025 11:44:09 +0200 |
| parents | b50eed0cc0ef |
| children |
comparison
equal
deleted
inserted
replaced
| 0:6015a75abc2d | 3:2c135c81b16c |
|---|---|
| 1 import getopt | |
| 2 import os | |
| 3 import re | |
| 4 import sys | |
| 5 import time | |
| 6 | |
| 7 if os.environ.get('MUPDF_PYTHON') in ('swig', None): | |
| 8 # PYTHONPATH should have been set up to point to a build/shared-*/ | |
| 9 # directory containing mupdf.so generated by scripts/mupdfwrap.py and SWIG. | |
| 10 import mupdf | |
| 11 elif os.environ.get('MUPDF_PYTHON') == 'cppyy': | |
| 12 sys.path.insert(0, os.path.abspath(f'{__file__}/../../platform/python')) | |
| 13 import mupdf_cppyy | |
| 14 del sys.path[0] | |
| 15 mupdf = mupdf_cppyy.cppyy.gbl.mupdf | |
| 16 else: | |
| 17 raise Exception(f'Unrecognised $MUPDF_PYTHON: {os.environ.get("MUPDF_PYTHON")}') | |
| 18 | |
| 19 # Force stderr to be line-buffered - i.e. python will flush to the underlying | |
| 20 # stderr stream every newline. This ensures that our output interleaves with | |
| 21 # the output of mupdf C code, making it easier to compare our output with that | |
| 22 # of mutool. | |
| 23 # | |
| 24 sys.stderr = os.fdopen( os.dup( sys.stderr.fileno()), 'w', 1) | |
| 25 | |
| 26 | |
| 27 OUT_NONE = 0 | |
| 28 OUT_PNG = 1 | |
| 29 OUT_PNM = 2 | |
| 30 OUT_PGM = 3 | |
| 31 OUT_PPM = 4 | |
| 32 OUT_PAM = 5 | |
| 33 OUT_PBM = 6 | |
| 34 OUT_PKM = 7 | |
| 35 OUT_PWG = 8 | |
| 36 OUT_PCL = 9 | |
| 37 OUT_PS = 10 | |
| 38 OUT_PSD = 11 | |
| 39 OUT_TEXT = 12 | |
| 40 OUT_HTML = 13 | |
| 41 OUT_XHTML = 14 | |
| 42 OUT_STEXT = 15 | |
| 43 OUT_PCLM = 16 | |
| 44 OUT_TRACE = 17 | |
| 45 OUT_BBOX = 18 | |
| 46 OUT_SVG = 19 | |
| 47 OUT_XMLTEXT = 20 | |
| 48 | |
| 49 CS_INVALID = 0 | |
| 50 CS_UNSET = 1 | |
| 51 CS_MONO = 2 | |
| 52 CS_GRAY = 3 | |
| 53 CS_GRAY_ALPHA = 4 | |
| 54 CS_RGB = 5 | |
| 55 CS_RGB_ALPHA = 6 | |
| 56 CS_CMYK = 7 | |
| 57 CS_CMYK_ALPHA = 8 | |
| 58 CS_ICC = 9 | |
| 59 | |
| 60 CS_INVALID = 0 | |
| 61 CS_UNSET = 1 | |
| 62 CS_MONO = 2 | |
| 63 CS_GRAY = 3 | |
| 64 CS_GRAY_ALPHA = 4 | |
| 65 CS_RGB = 5 | |
| 66 CS_RGB_ALPHA = 6 | |
| 67 CS_CMYK = 7 | |
| 68 CS_CMYK_ALPHA = 8 | |
| 69 CS_ICC = 9 | |
| 70 | |
| 71 SPOTS_NONE = 0 | |
| 72 SPOTS_OVERPRINT_SIM = 1 | |
| 73 SPOTS_FULL = 2 | |
| 74 | |
| 75 | |
| 76 class suffix_t: | |
| 77 def __init__( self, suffix, format_, spots): | |
| 78 self.suffix = suffix | |
| 79 self.format = format_ | |
| 80 self.spots = spots | |
| 81 | |
| 82 suffix_table = [ | |
| 83 suffix_t( ".png", OUT_PNG, 0 ), | |
| 84 suffix_t( ".pgm", OUT_PGM, 0 ), | |
| 85 suffix_t( ".ppm", OUT_PPM, 0 ), | |
| 86 suffix_t( ".pnm", OUT_PNM, 0 ), | |
| 87 suffix_t( ".pam", OUT_PAM, 0 ), | |
| 88 suffix_t( ".pbm", OUT_PBM, 0 ), | |
| 89 suffix_t( ".pkm", OUT_PKM, 0 ), | |
| 90 suffix_t( ".svg", OUT_SVG, 0 ), | |
| 91 suffix_t( ".pwg", OUT_PWG, 0 ), | |
| 92 suffix_t( ".pclm", OUT_PCLM, 0 ), | |
| 93 suffix_t( ".pcl", OUT_PCL, 0 ), | |
| 94 suffix_t( ".psd", OUT_PSD, 1 ), | |
| 95 suffix_t( ".ps", OUT_PS, 0 ), | |
| 96 | |
| 97 suffix_t( ".txt", OUT_TEXT, 0 ), | |
| 98 suffix_t( ".text", OUT_TEXT, 0 ), | |
| 99 suffix_t( ".html", OUT_HTML, 0 ), | |
| 100 suffix_t( ".xhtml", OUT_XHTML, 0 ), | |
| 101 suffix_t( ".stext", OUT_STEXT, 0 ), | |
| 102 | |
| 103 suffix_t( ".trace", OUT_TRACE, 0 ), | |
| 104 suffix_t( ".raw", OUT_XMLTEXT, 0 ), | |
| 105 suffix_t( ".bbox", OUT_BBOX, 0 ), | |
| 106 ] | |
| 107 | |
| 108 class cs_name_t: | |
| 109 def __init__( self, name, colorspace): | |
| 110 self.name = name | |
| 111 self.colorspace = colorspace | |
| 112 | |
| 113 cs_name_table = dict( | |
| 114 m = CS_MONO, | |
| 115 mono = CS_MONO, | |
| 116 g = CS_GRAY, | |
| 117 gray = CS_GRAY, | |
| 118 grey = CS_GRAY, | |
| 119 ga = CS_GRAY_ALPHA, | |
| 120 grayalpha = CS_GRAY_ALPHA, | |
| 121 greyalpha = CS_GRAY_ALPHA, | |
| 122 rgb = CS_RGB, | |
| 123 rgba = CS_RGB_ALPHA, | |
| 124 rgbalpha = CS_RGB_ALPHA, | |
| 125 cmyk = CS_CMYK, | |
| 126 cmyka = CS_CMYK_ALPHA, | |
| 127 cmykalpha = CS_CMYK_ALPHA, | |
| 128 ) | |
| 129 | |
| 130 | |
| 131 class format_cs_table_t: | |
| 132 def __init__( self, format_, default_cs, permitted_cs): | |
| 133 self.format = format_ | |
| 134 self.default_cs = default_cs | |
| 135 self.permitted_cs = permitted_cs | |
| 136 | |
| 137 format_cs_table = [ | |
| 138 format_cs_table_t( OUT_PNG, CS_RGB, [ CS_GRAY, CS_GRAY_ALPHA, CS_RGB, CS_RGB_ALPHA, CS_ICC ] ), | |
| 139 format_cs_table_t( OUT_PPM, CS_RGB, [ CS_GRAY, CS_RGB ] ), | |
| 140 format_cs_table_t( OUT_PNM, CS_GRAY, [ CS_GRAY, CS_RGB ] ), | |
| 141 format_cs_table_t( OUT_PAM, CS_RGB_ALPHA, [ CS_GRAY, CS_GRAY_ALPHA, CS_RGB, CS_RGB_ALPHA, CS_CMYK, CS_CMYK_ALPHA ] ), | |
| 142 format_cs_table_t( OUT_PGM, CS_GRAY, [ CS_GRAY, CS_RGB ] ), | |
| 143 format_cs_table_t( OUT_PBM, CS_MONO, [ CS_MONO ] ), | |
| 144 format_cs_table_t( OUT_PKM, CS_CMYK, [ CS_CMYK ] ), | |
| 145 format_cs_table_t( OUT_PWG, CS_RGB, [ CS_MONO, CS_GRAY, CS_RGB, CS_CMYK ] ), | |
| 146 format_cs_table_t( OUT_PCL, CS_MONO, [ CS_MONO, CS_RGB ] ), | |
| 147 format_cs_table_t( OUT_PCLM, CS_RGB, [ CS_RGB, CS_GRAY ] ), | |
| 148 format_cs_table_t( OUT_PS, CS_RGB, [ CS_GRAY, CS_RGB, CS_CMYK ] ), | |
| 149 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 ] ), | |
| 150 | |
| 151 format_cs_table_t( OUT_TRACE, CS_RGB, [ CS_RGB ] ), | |
| 152 format_cs_table_t( OUT_XMLTEXT, CS_RGB, [ CS_RGB ] ), | |
| 153 format_cs_table_t( OUT_BBOX, CS_RGB, [ CS_RGB ] ), | |
| 154 format_cs_table_t( OUT_SVG, CS_RGB, [ CS_RGB ] ), | |
| 155 | |
| 156 format_cs_table_t( OUT_TEXT, CS_RGB, [ CS_RGB ] ), | |
| 157 format_cs_table_t( OUT_HTML, CS_RGB, [ CS_RGB ] ), | |
| 158 format_cs_table_t( OUT_XHTML, CS_RGB, [ CS_RGB ] ), | |
| 159 format_cs_table_t( OUT_STEXT, CS_RGB, [ CS_RGB ] ), | |
| 160 ] | |
| 161 | |
| 162 def stat_mtime(path): | |
| 163 try: | |
| 164 return os.path.getmtime(path) | |
| 165 except Exception: | |
| 166 return 0 | |
| 167 | |
| 168 | |
| 169 class worker_t: | |
| 170 def __init__( self): | |
| 171 self.num = 0 | |
| 172 self.band = 0 | |
| 173 self.list = None | |
| 174 self.ctm = None | |
| 175 self.tbounds = None | |
| 176 self.pix = None | |
| 177 self.bit = None | |
| 178 self.cookie = mupdf.FzCookie() | |
| 179 | |
| 180 class state: | |
| 181 | |
| 182 output = None | |
| 183 out = None | |
| 184 output_pagenum = 0 | |
| 185 output_file_per_page = 0 | |
| 186 | |
| 187 format_ = None | |
| 188 output_format = OUT_NONE | |
| 189 | |
| 190 rotation = 0 | |
| 191 resolution = 72 | |
| 192 res_specified = 0 | |
| 193 width = 0 | |
| 194 height = 0 | |
| 195 fit = 0 | |
| 196 | |
| 197 layout_w = mupdf.FZ_DEFAULT_LAYOUT_W | |
| 198 layout_h = mupdf.FZ_DEFAULT_LAYOUT_H | |
| 199 layout_em = mupdf.FZ_DEFAULT_LAYOUT_EM | |
| 200 layout_css = None | |
| 201 layout_use_doc_css = 1 | |
| 202 min_line_width = 0.0 | |
| 203 | |
| 204 showfeatures = 0 | |
| 205 showtime = 0 | |
| 206 showmemory = 0 | |
| 207 showmd5 = 0 | |
| 208 | |
| 209 no_icc = 0 | |
| 210 ignore_errors = 0 | |
| 211 uselist = 1 | |
| 212 alphabits_text = 8 | |
| 213 alphabits_graphics = 8 | |
| 214 | |
| 215 out_cs = CS_UNSET | |
| 216 proof_filename = None | |
| 217 proof_cs = mupdf.FzColorspace() | |
| 218 icc_filename = None | |
| 219 gamma_value = 1 | |
| 220 invert = 0 | |
| 221 band_height = 0 | |
| 222 lowmemory = 0 | |
| 223 | |
| 224 quiet = 0 | |
| 225 errored = 0 | |
| 226 colorspace = mupdf.FzColorspace() | |
| 227 oi = None | |
| 228 spots = SPOTS_OVERPRINT_SIM | |
| 229 alpha = 0 | |
| 230 useaccel = 1 | |
| 231 filename = None | |
| 232 files = 0 | |
| 233 num_workers = 0 | |
| 234 workers = None | |
| 235 bander = None | |
| 236 | |
| 237 layer_config = None | |
| 238 | |
| 239 | |
| 240 class bgprint: | |
| 241 active = 0 | |
| 242 started = 0 | |
| 243 pagenum = 0 | |
| 244 filename = None | |
| 245 list_ = None | |
| 246 page = None | |
| 247 interptime = 0 | |
| 248 seps = None | |
| 249 | |
| 250 | |
| 251 class timing: | |
| 252 count = 0 | |
| 253 total = 0 | |
| 254 min_ = 0 | |
| 255 max_ = 0 | |
| 256 mininterp = 0 | |
| 257 maxinterp = 0 | |
| 258 minpage = 0 | |
| 259 maxpage = 0 | |
| 260 minfilename = None | |
| 261 maxfilename = None | |
| 262 layout = 0 | |
| 263 minlayout = 0 | |
| 264 maxlayout = 0 | |
| 265 minlayoutfilename = None | |
| 266 maxlayoutfilename = None | |
| 267 | |
| 268 | |
| 269 | |
| 270 def usage(): | |
| 271 sys.stderr.write( f''' | |
| 272 mudraw version {mupdf.FZ_VERSION} " | |
| 273 Usage: mudraw [options] file [pages] | |
| 274 \t-p -\tpassword | |
| 275 | |
| 276 \t-o -\toutput file name (%d for page number) | |
| 277 \t-F -\toutput format (default inferred from output file name) | |
| 278 \t\traster: png, pnm, pam, pbm, pkm, pwg, pcl, ps | |
| 279 \t\tvector: svg, pdf, trace | |
| 280 \t\ttext: txt, html, stext | |
| 281 | |
| 282 \t-q\tbe quiet (don't print progress messages) | |
| 283 \t-s -\tshow extra information: | |
| 284 \t\tm - show memory use | |
| 285 \t\tt - show timings | |
| 286 \t\tf - show page features | |
| 287 \t\t5 - show md5 checksum of rendered image | |
| 288 | |
| 289 \t-R -\trotate clockwise (default: 0 degrees) | |
| 290 \t-r -\tresolution in dpi (default: 72) | |
| 291 \t-w -\twidth (in pixels) (maximum width if -r is specified) | |
| 292 \t-h -\theight (in pixels) (maximum height if -r is specified) | |
| 293 \t-f -\tfit width and/or height exactly; ignore original aspect ratio | |
| 294 \t-B -\tmaximum band_height (pXm, pcl, pclm, ps, psd and png output only) | |
| 295 \t-T -\tnumber of threads to use for rendering (banded mode only) | |
| 296 | |
| 297 \t-W -\tpage width for EPUB layout | |
| 298 \t-H -\tpage height for EPUB layout | |
| 299 \t-S -\tfont size for EPUB layout | |
| 300 \t-U -\tfile name of user stylesheet for EPUB layout | |
| 301 \t-X\tdisable document styles for EPUB layout | |
| 302 \t-a\tdisable usage of accelerator file | |
| 303 | |
| 304 \t-c -\tcolorspace (mono, gray, grayalpha, rgb, rgba, cmyk, cmykalpha, filename of ICC profile) | |
| 305 \t-e -\tproof icc profile (filename of ICC profile) | |
| 306 \t-G -\tapply gamma correction | |
| 307 \t-I\tinvert colors | |
| 308 | |
| 309 \t-A -\tnumber of bits of antialiasing (0 to 8) | |
| 310 \t-A -/-\tnumber of bits of antialiasing (0 to 8) (graphics, text) | |
| 311 \t-l -\tminimum stroked line width (in pixels) | |
| 312 \t-D\tdisable use of display list | |
| 313 \t-i\tignore errors | |
| 314 \t-L\tlow memory mode (avoid caching, clear objects after each page) | |
| 315 \t-P\tparallel interpretation/rendering | |
| 316 \t-N\tdisable ICC workflow (\"N\"o color management) | |
| 317 \t-O -\tControl spot/overprint rendering | |
| 318 \t\t 0 = No spot rendering | |
| 319 \t\t 1 = Overprint simulation (default) | |
| 320 \t\t 2 = Full spot rendering | |
| 321 | |
| 322 \t-y l\tList the layer configs to stderr | |
| 323 \t-y -\tSelect layer config (by number) | |
| 324 \t-y -{{,-}}*\tSelect layer config (by number), and toggle the listed entries | |
| 325 | |
| 326 \tpages\tcomma separated list of page numbers and ranges | |
| 327 ''') | |
| 328 sys.exit(1) | |
| 329 | |
| 330 | |
| 331 gettime_first = None | |
| 332 | |
| 333 def gettime(): | |
| 334 global gettime_first | |
| 335 if gettime_first is None: | |
| 336 gettime_first = time.time() | |
| 337 | |
| 338 now = time.time() | |
| 339 return (now - gettime_first) * 1000 | |
| 340 | |
| 341 | |
| 342 def has_percent_d(s): | |
| 343 # find '%[0-9]*d' */ | |
| 344 m = re.search( '%[0-9]*d', s) | |
| 345 if m: | |
| 346 return 1 | |
| 347 return 0 | |
| 348 | |
| 349 | |
| 350 | |
| 351 # Output file level (as opposed to page level) headers | |
| 352 def file_level_headers(): | |
| 353 | |
| 354 if state.output_format in (OUT_STEXT, OUT_TRACE, OUT_XMLTEXT, OUT_BBOX): | |
| 355 state.out.fz_write_string( "<?xml version=\"1.0\"?>\n") | |
| 356 | |
| 357 if state.output_format == OUT_HTML: | |
| 358 state.out.fz_print_stext_header_as_html() | |
| 359 if state.output_format == OUT_XHTML: | |
| 360 state.out.fz_print_stext_header_as_xhtml() | |
| 361 | |
| 362 if state.output_format in (OUT_STEXT, OUT_TRACE, OUT_BBOX): | |
| 363 state.out.fz_write_string( f'<document name="{state.filename}">\n') | |
| 364 | |
| 365 if state.output_format == OUT_PS: | |
| 366 state.out.fz_write_ps_file_header() | |
| 367 | |
| 368 if state.output_format == OUT_PWG: | |
| 369 state.out.fz_write_pwg_file_header() | |
| 370 | |
| 371 if state.output_format == OUT_PCLM: | |
| 372 opts = mupdf.FzPclmOptions( 'compression=flate') | |
| 373 state.bander = mupdf.FzBandWriter(state.out, opts) | |
| 374 | |
| 375 def file_level_trailers(): | |
| 376 if state.output_format in (OUT_STEXT, OUT_TRACE, OUT_BBOX): | |
| 377 state.out.fz_write_string( "</document>\n") | |
| 378 | |
| 379 if state.output_format == OUT_HTML: | |
| 380 state.out.fz_print_stext_trailer_as_html() | |
| 381 if state.output_format == OUT_XHTML: | |
| 382 state.out.fz_print_stext_trailer_as_xhtml() | |
| 383 | |
| 384 if state.output_format == OUT_PS: | |
| 385 state.out.fz_write_ps_file_trailer( state.output_pagenum) | |
| 386 | |
| 387 def drawband( page, list_, ctm, tbounds, cookie, band_start, pix): | |
| 388 | |
| 389 bit = None | |
| 390 | |
| 391 if pix.alpha(): | |
| 392 pix.fz_clear_pixmap() | |
| 393 else: | |
| 394 pix.fz_clear_pixmap_with_value( 255) | |
| 395 | |
| 396 dev = mupdf.FzDevice( mupdf.FzMatrix(), pix, state.proof_cs) | |
| 397 if state.lowmemory: | |
| 398 dev.enable_device_hints( mupdf.FZ_NO_CACHE) | |
| 399 if state.alphabits_graphics == 0: | |
| 400 dev.enable_device_hints( mupdf.FZ_DONT_INTERPOLATE_IMAGES) | |
| 401 if list_: | |
| 402 list_.fz_run_display_list( dev, ctm, tbounds, cookie) | |
| 403 else: | |
| 404 page.fz_run_page( dev, ctm, cookie) | |
| 405 dev.fz_close_device() | |
| 406 dev = None # lgtm [py/unused-local-variable] | |
| 407 | |
| 408 if state.invert: | |
| 409 pix.fz_invert_pixmap() | |
| 410 if state.gamma_value != 1: | |
| 411 pix.fz_gamma_pixmap( state.gamma_value) | |
| 412 | |
| 413 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): | |
| 414 bit = mupdf.FzBitmap( pix, mupdf.FzHalftone(), band_start) | |
| 415 return bit | |
| 416 | |
| 417 | |
| 418 | |
| 419 | |
| 420 | |
| 421 | |
| 422 def dodrawpage( page, list_, pagenum, cookie, start, interptime, filename, bg, seps): | |
| 423 | |
| 424 if state.output_file_per_page: | |
| 425 file_level_headers() | |
| 426 | |
| 427 if list_: | |
| 428 mediabox = mupdf.FzRect( list_) | |
| 429 else: | |
| 430 mediabox = page.fz_bound_page() | |
| 431 | |
| 432 if state.output_format == OUT_TRACE: | |
| 433 state.out.fz_write_string( "<page mediabox=\"%g %g %g %g\">\n" % ( | |
| 434 mediabox.x0, mediabox.y0, mediabox.x1, mediabox.y1)) | |
| 435 dev = mupdf.FzDevice( state.out) | |
| 436 if state.lowmemory: | |
| 437 dev.fz_enable_device_hints( mupdf.FZ_NO_CACHE) | |
| 438 if list_: | |
| 439 list_.fz_run_display_list( dev, mupdf.FzMatrix(), mupdf.FzRect(mupdf.fz_infinite_rect), cookie) | |
| 440 else: | |
| 441 page.fz_run_page( dev, fz_identity, cookie) | |
| 442 state.out.fz_write_string( "</page>\n") | |
| 443 dev.fz_close_device() | |
| 444 dev = None # lgtm [py/unused-local-variable] | |
| 445 | |
| 446 elif state.output_format == OUT_XMLTEXT: | |
| 447 state.out.fz_write_string( "<page mediabox=\"%g %g %g %g\">\n" % ( | |
| 448 mediabox.x0, mediabox.y0, mediabox.x1, mediabox.y1)) | |
| 449 dev = mupdf.FzDevice.fz_new_raw_device( state.out) | |
| 450 if list_: | |
| 451 list_.fz_run_display_list( dev, mupdf.FzMatrix(), mupdf.FzRect(mupdf.fz_infinite_rect), cookie) | |
| 452 else: | |
| 453 page.fz_run_page( dev, fz_identity, cookie) | |
| 454 state.out.fz_write_string( "</page>\n") | |
| 455 dev.fz_close_device() | |
| 456 dev = None # lgtm [py/unused-local-variable] | |
| 457 | |
| 458 elif state.output_format == OUT_BBOX: | |
| 459 bbox = mupdf.FzRect( mupdf.FzRect.Fixed_EMPTY) | |
| 460 dev = mupdf.FzDevice( bbox) | |
| 461 if state.lowmemory: | |
| 462 dev.fz_enable_device_hints( mupdf.FZ_NO_CACHE) | |
| 463 if list_: | |
| 464 list_.fz_run_display_list( dev, fz_identity, mupdf.FzRect(mupdf.fz_infinite_rect), cookie) | |
| 465 else: | |
| 466 page.fz_run_page( dev, fz_identity, cookie) | |
| 467 dev.fz_close_device() | |
| 468 state.out.fz_write_string( "<page bbox=\"%s %s %s %s\" mediabox=\"%s %s %s %s\" />\n", | |
| 469 bbox.x0, | |
| 470 bbox.y0, | |
| 471 bbox.x1, | |
| 472 bbox.y1, | |
| 473 mediabox.x0, | |
| 474 mediabox.y0, | |
| 475 mediabox.x1, | |
| 476 mediabox.y1, | |
| 477 ) | |
| 478 | |
| 479 elif state.output_format in (OUT_TEXT, OUT_HTML, OUT_XHTML, OUT_STEXT): | |
| 480 zoom = state.resolution / 72 | |
| 481 ctm = mupdf.FzMatrix(mupdf.fz_pre_scale(mupdf.fz_rotate(state.rotation), zoom, zoom)) | |
| 482 | |
| 483 stext_options = mupdf.FzStextOptions() | |
| 484 | |
| 485 stext_options.flags = mupdf.FZ_STEXT_PRESERVE_IMAGES if (state.output_format == OUT_HTML or state.output_format == OUT_XHTML) else 0 | |
| 486 text = mupdf.FzStextPage( mediabox) | |
| 487 dev = mupdf.FzDevice( text, stext_options) | |
| 488 if state.lowmemory: | |
| 489 fz_enable_device_hints( dev, FZ_NO_CACHE) | |
| 490 if list_: | |
| 491 list_.fz_run_display_list( dev, ctm, mupdf.FzRect(mupdf.fz_infinite_rect), cookie) | |
| 492 else: | |
| 493 page.fz_run_page( dev, ctm, cookie) | |
| 494 dev.fz_close_device() | |
| 495 dev = None # lgtm [py/unused-local-variable] | |
| 496 if state.output_format == OUT_STEXT: | |
| 497 state.out.fz_print_stext_page_as_xml( text, pagenum) | |
| 498 elif state.output_format == OUT_HTML: | |
| 499 state.out.fz_print_stext_page_as_html( text, pagenum) | |
| 500 elif state.output_format == OUT_XHTML: | |
| 501 state.out.fz_print_stext_page_as_xhtml( text, pagenum) | |
| 502 elif state.output_format == OUT_TEXT: | |
| 503 state.out.fz_print_stext_page_as_text( text) | |
| 504 state.out.fz_write_string( "\f\n") | |
| 505 | |
| 506 elif state.output_format == OUT_SVG: | |
| 507 zoom = state.resolution / 72 | |
| 508 ctm = mupdf.FzMatrix(zoom, zoom) | |
| 509 ctm.fz_pre_rotate( state.rotation) | |
| 510 tbounds = mupdf.FzRect(mediabox, ctm) | |
| 511 | |
| 512 if not state.output or state.output == "-": | |
| 513 state.out = mupdf.FzOutput( mupdf.FzOutput.Fixed_STDOUT) | |
| 514 else: | |
| 515 buf = mupdf.fz_format_output_path( state.output, pagenum) | |
| 516 state.out = mupdf.FzOutput( buf, 0) | |
| 517 | |
| 518 dev = mupdf.FzDevice( state.out, tbounds.x1-tbounds.x0, tbounds.y1-tbounds.y0, mupdf.FZ_SVG_TEXT_AS_PATH, 1) | |
| 519 if state.lowmemory: | |
| 520 dev.fz_enable_device_hints( dev, mupdf.FZ_NO_CACHE) | |
| 521 if list_: | |
| 522 list_.fz_run_display_list( dev, ctm, tbounds, cookie) | |
| 523 else: | |
| 524 page.fz_run_page( dev, ctm, cookie) | |
| 525 dev.fz_close_device() | |
| 526 state.out.fz_close_output() | |
| 527 else: | |
| 528 zoom = state.resolution / 72 | |
| 529 ctm = mupdf.fz_pre_scale( mupdf.fz_rotate(state.rotation), zoom, zoom) | |
| 530 tbounds = mupdf.FzRect(mediabox, ctm) | |
| 531 ibounds = tbounds.fz_round_rect() | |
| 532 | |
| 533 # Make local copies of our width/height | |
| 534 w = state.width | |
| 535 h = state.height | |
| 536 | |
| 537 # If a resolution is specified, check to see whether w/h are | |
| 538 # exceeded; if not, unset them. */ | |
| 539 if state.res_specified: | |
| 540 t = ibounds.x1 - ibounds.x0 | |
| 541 if w and t <= w: | |
| 542 w = 0 | |
| 543 t = ibounds.y1 - ibounds.y0 | |
| 544 if h and t <= h: | |
| 545 h = 0 | |
| 546 | |
| 547 # Now w or h will be 0 unless they need to be enforced. | |
| 548 if w or h: | |
| 549 scalex = w / (tbounds.x1 - tbounds.x0) | |
| 550 scaley = h / (tbounds.y1 - tbounds.y0) | |
| 551 | |
| 552 if state.fit: | |
| 553 if w == 0: | |
| 554 scalex = 1.0 | |
| 555 if h == 0: | |
| 556 scaley = 1.0 | |
| 557 else: | |
| 558 if w == 0: | |
| 559 scalex = scaley | |
| 560 if h == 0: | |
| 561 scaley = scalex | |
| 562 if not state.fit: | |
| 563 if scalex > scaley: | |
| 564 scalex = scaley | |
| 565 else: | |
| 566 scaley = scalex | |
| 567 scale_mat = mupdf.fz_scale(scalex, scaley) | |
| 568 ctm = mupdf.fz_concat(ctm, scale_mat) | |
| 569 tbounds = mupdf.FzRect( mediabox, ctm) | |
| 570 ibounds = tbounds.fz_round_rect() | |
| 571 tbounds = ibounds.fz_rect_from_irect() | |
| 572 | |
| 573 band_ibounds = ibounds | |
| 574 bands = 1 | |
| 575 totalheight = ibounds.y1 - ibounds.y0 | |
| 576 drawheight = totalheight | |
| 577 | |
| 578 if state.band_height != 0: | |
| 579 # Banded rendering; we'll only render to a | |
| 580 # given height at a time. | |
| 581 drawheight = state.band_height | |
| 582 if totalheight > state.band_height: | |
| 583 band_ibounds.y1 = band_ibounds.y0 + state.band_height | |
| 584 bands = (totalheight + state.band_height-1)/state.band_height | |
| 585 tbounds.y1 = tbounds.y0 + state.band_height + 2 | |
| 586 #DEBUG_THREADS(("Using %d Bands\n", bands)); | |
| 587 | |
| 588 if state.num_workers > 0: | |
| 589 for band in range( min(state.num_workers, bands)): | |
| 590 state.workers[band].band = band | |
| 591 state.workers[band].ctm = ctm | |
| 592 state.workers[band].tbounds = tbounds | |
| 593 state.workers[band].cookie = mupdf.FzCookie() | |
| 594 state.workers[band].list = list_ | |
| 595 state.workers[band].pix = mupdf.FzPixmap( state.colorspace, band_ibounds, seps, state.alpha) | |
| 596 state.workers[band].pix.fz_set_pixmap_resolution( state.resolution, state.resolution) | |
| 597 ctm.f -= drawheight | |
| 598 pix = state.workers[0].pix | |
| 599 else: | |
| 600 pix = mupdf.FzPixmap( state.colorspace, band_ibounds, seps, state.alpha) | |
| 601 pix.fz_set_pixmap_resolution( int(state.resolution), int(state.resolution)) | |
| 602 | |
| 603 # Output any page level headers (for banded formats) | |
| 604 if state.output: | |
| 605 state.bander = None | |
| 606 if state.output_format == OUT_PGM or state.output_format == OUT_PPM or state.output_format == OUT_PNM: | |
| 607 state.bander = mupdf.FzBandWriter( state.out, mupdf.FzBandWriter.PNM) | |
| 608 elif state.output_format == OUT_PAM: | |
| 609 state.bander = mupdf.FzBandWriter( state.out, mupdf.FzBandWriter.PAM) | |
| 610 elif state.output_format == OUT_PNG: | |
| 611 state.bander = mupdf.FzBandWriter( state.out, mupdf.FzBandWriter.PNG) | |
| 612 elif state.output_format == OUT_PBM: | |
| 613 state.bander = mupdf.FzBandWriter( state.out, mupdf.FzBandWriter.PBM) | |
| 614 elif state.output_format == OUT_PKM: | |
| 615 state.bander = mupdf.FzBandWriter( state.out, mupdf.FzBandWriter.PKM) | |
| 616 elif state.output_format == OUT_PS: | |
| 617 state.bander = mupdf.FzBandWriter( state.out, mupdf.FzBandWriter.PS) | |
| 618 elif state.output_format == OUT_PSD: | |
| 619 state.bander = mupdf.FzBandWriter( state.out, mupdf.FzBandWriter.PSD) | |
| 620 elif state.output_format == OUT_PWG: | |
| 621 if state.out_cs == CS_MONO: | |
| 622 state.bander = mupdf.FzBandWriter( state.out, mupdf.FzBandWriter.MONO, mupdf.FzPwgOptions()) | |
| 623 else: | |
| 624 state.bander = mupdf.FzBandWriter( state.out, mupdf.FzBandWriter.COLOR, mupdf.FzPwgOptions()) | |
| 625 elif state.output_format == OUT_PCL: | |
| 626 if state.out_cs == CS_MONO: | |
| 627 state.bander = mupdf.FzBandWriter( state.out, mupdf.FzBandWriter.MONO, mupdf.FzPclOptions()) | |
| 628 else: | |
| 629 state.bander = mupdf.FzBandWriter( state.out, mupdf.FzBandWriter.COLOR, mupdf.FzPclOptions()) | |
| 630 if state.bander: | |
| 631 state.bander.fz_write_header( pix.w(), totalheight, pix.n(), pix.alpha(), pix.xres(), pix.yres(), state.output_pagenum, pix.colorspace(), pix.seps()) | |
| 632 state.output_pagenum += 1 | |
| 633 | |
| 634 for band in range( bands): | |
| 635 if state.num_workers > 0: | |
| 636 w = state.workers[band % state.num_workers] | |
| 637 pix = w.pix | |
| 638 bit = w.bit | |
| 639 w.bit = None | |
| 640 cookie.fz_increment_errors(w.cookie.errors()) | |
| 641 | |
| 642 else: | |
| 643 bit = drawband( page, list_, ctm, tbounds, cookie, band * state.band_height, pix) | |
| 644 | |
| 645 if state.output: | |
| 646 if state.bander: | |
| 647 if bit: | |
| 648 state.bander.fz_write_band( bit.stride(), drawheight, bit.samples()) | |
| 649 else: | |
| 650 state.bander.fz_write_band( pix.stride(), drawheight, pix.samples()) | |
| 651 bit = None | |
| 652 | |
| 653 if state.num_workers > 0 and band + state.num_workers < bands: | |
| 654 w = state.workers[band % state.num_workers] | |
| 655 w.band = band + state.num_workers | |
| 656 w.ctm = ctm | |
| 657 w.tbounds = tbounds | |
| 658 w.cookie = mupdf.FzCookie() | |
| 659 ctm.f -= drawheight | |
| 660 | |
| 661 # FIXME | |
| 662 if state.showmd5: | |
| 663 digest = pix.fz_md5_pixmap() | |
| 664 sys.stderr.write( ' ') | |
| 665 for i in range(16): | |
| 666 sys.stderr.write( '%02x', digest[i]) | |
| 667 | |
| 668 if state.output_file_per_page: | |
| 669 file_level_trailers() | |
| 670 | |
| 671 if state.showtime: | |
| 672 end = gettime() | |
| 673 diff = end - start | |
| 674 | |
| 675 if bg: | |
| 676 if diff + interptime < timing.min: | |
| 677 timing.min = diff + interptime | |
| 678 timing.mininterp = interptime | |
| 679 timing.minpage = pagenum | |
| 680 timing.minfilename = filename | |
| 681 if diff + interptime > timing.max: | |
| 682 timing.max = diff + interptime | |
| 683 timing.maxinterp = interptime | |
| 684 timing.maxpage = pagenum | |
| 685 timing.maxfilename = filename | |
| 686 timing.count += 1 | |
| 687 | |
| 688 sys.stderr.write( " %dms (interpretation) %dms (rendering) %dms (total)" % (interptime, diff, diff + interptime)) | |
| 689 else: | |
| 690 if diff < timing.min: | |
| 691 timing.min = diff | |
| 692 timing.minpage = pagenum | |
| 693 timing.minfilename = filename | |
| 694 if diff > timing.max: | |
| 695 timing.max = diff | |
| 696 timing.maxpage = pagenum | |
| 697 timing.maxfilename = filename | |
| 698 | |
| 699 timing.total += diff | |
| 700 timing.count += 1 | |
| 701 | |
| 702 sys.stderr.write( " %dms" % diff) | |
| 703 | |
| 704 if not state.quiet or state.showfeatures or state.showtime or state.showmd5: | |
| 705 sys.stderr.write( "\n") | |
| 706 | |
| 707 if state.lowmemory: | |
| 708 mupdf.fz_empty_store() | |
| 709 | |
| 710 if state.showmemory: | |
| 711 # Use low-level fn because mupdf.fz_stderr() returns fz_output*, not | |
| 712 # FzOutput. | |
| 713 mupdf.ll_fz_dump_glyph_cache_stats(mupdf.ll_fz_stderr()) | |
| 714 | |
| 715 mupdf.fz_flush_warnings() | |
| 716 | |
| 717 if cookie.errors(): | |
| 718 state.errored = 1 | |
| 719 | |
| 720 | |
| 721 | |
| 722 def bgprint_flush(): | |
| 723 if not bgprint.active or not bgprint.started: | |
| 724 return | |
| 725 bgprint.started = 0 | |
| 726 | |
| 727 | |
| 728 | |
| 729 def drawpage( doc, pagenum): | |
| 730 list_ = None | |
| 731 cookie = mupdf.FzCookie() | |
| 732 seps = None | |
| 733 features = "" | |
| 734 | |
| 735 start = gettime() if state.showtime else 0 | |
| 736 | |
| 737 page = mupdf.FzPage( doc, pagenum - 1) | |
| 738 | |
| 739 if state.spots != SPOTS_NONE: | |
| 740 seps = page.fz_page_separations() | |
| 741 if seps.m_internal: | |
| 742 n = seps.fz_count_separations() | |
| 743 if state.spots == SPOTS_FULL: | |
| 744 for i in range(n): | |
| 745 seps.fz_set_separation_behavior( i, mupdf.FZ_SEPARATION_SPOT) | |
| 746 else: | |
| 747 for i in range(n): | |
| 748 seps.fz_set_separation_behavior( i, mupdf.FZ_SEPARATION_COMPOSITE) | |
| 749 elif page.fz_page_uses_overprint(): | |
| 750 # This page uses overprint, so we need an empty | |
| 751 # sep object to force the overprint simulation on. | |
| 752 seps = mupdf.FzSeparations(0) | |
| 753 elif state.oi and state.oi.m_internal and state.oi.fz_colorspace_n() != state.colorspace.fz_colorspace_n(): | |
| 754 # We have an output intent, and it's incompatible | |
| 755 # with the colorspace our device needs. Force the | |
| 756 # overprint simulation on, because this ensures that | |
| 757 # we 'simulate' the output intent too. */ | |
| 758 seps = mupdf.FzSeparations(0) | |
| 759 | |
| 760 if state.uselist: | |
| 761 list_ = mupdf.FzDisplayList( page.fz_bound_page()) | |
| 762 dev = mupdf.FzDevice( list_) | |
| 763 if state.lowmemory: | |
| 764 dev.fz_enable_device_hints( FZ_NO_CACHE) | |
| 765 page.fz_run_page( dev, mupdf.FzMatrix(), cookie) | |
| 766 dev.fz_close_device() | |
| 767 | |
| 768 if bgprint.active and state.showtime: | |
| 769 end = gettime() | |
| 770 start = end - start | |
| 771 | |
| 772 if state.showfeatures: | |
| 773 # SWIG doesn't appear to handle the out-param is_color in | |
| 774 # mupdf.Device() constructor that wraps fz_new_test_device(), so we use | |
| 775 # the underlying mupdf function() instead. | |
| 776 # | |
| 777 dev, iscolor = mupdf.ll_fz_new_test_device( 0.02, 0, None) | |
| 778 dev = mupdf.FzDevice( dev) | |
| 779 if state.lowmemory: | |
| 780 dev.fz_enable_device_hints( mupdf.FZ_NO_CACHE) | |
| 781 if list_: | |
| 782 list_.fz_run_display_list( dev, mupdf.FzMatrix(mupdf.fz_identity), mupdf.FzRect(mupdf.fz_infinite_rect), mupdf.FzCookie()) | |
| 783 else: | |
| 784 page.fz_run_page( dev, fz_identity, cookie) | |
| 785 dev.fz_close_device() | |
| 786 features = " color" if iscolor else " grayscale" | |
| 787 | |
| 788 if state.output_file_per_page: | |
| 789 bgprint_flush() | |
| 790 if state.out: | |
| 791 state.out.fz_close_output() | |
| 792 text_buffer = mupdf.fz_format_output_path( state.output, pagenum) | |
| 793 state.out = mupdf.FzOutput( text_buffer, 0) | |
| 794 | |
| 795 if bgprint.active: | |
| 796 bgprint_flush() | |
| 797 if bgprint.active: | |
| 798 if not state.quiet or state.showfeatures or state.showtime or state.showmd5: | |
| 799 sys.stderr.write( "page %s %d%s" % (state.filename, pagenum, features)) | |
| 800 | |
| 801 bgprint.started = 1 | |
| 802 bgprint.page = page | |
| 803 bgprint.list = list_ | |
| 804 bgprint.seps = seps | |
| 805 bgprint.filename = state.filename | |
| 806 bgprint.pagenum = pagenum | |
| 807 bgprint.interptime = start | |
| 808 else: | |
| 809 if not state.quiet or state.showfeatures or state.showtime or state.showmd5: | |
| 810 sys.stderr.write( "page %s %d%s" % (state.filename, pagenum, features)) | |
| 811 dodrawpage( page, list_, pagenum, cookie, start, 0, state.filename, 0, seps) | |
| 812 | |
| 813 | |
| 814 | |
| 815 | |
| 816 | |
| 817 | |
| 818 | |
| 819 | |
| 820 | |
| 821 | |
| 822 | |
| 823 def drawrange( doc, range_): | |
| 824 pagecount = doc.fz_count_pages() | |
| 825 | |
| 826 while 1: | |
| 827 range_, spage, epage = mupdf.fz_parse_page_range( range_, pagecount) | |
| 828 if range_ is None: | |
| 829 break | |
| 830 if spage < epage: | |
| 831 for page in range(spage, epage+1): | |
| 832 drawpage( doc, page) | |
| 833 else: | |
| 834 for page in range( spage, epage-1, -1): | |
| 835 drawpage( doc, page) | |
| 836 | |
| 837 | |
| 838 | |
| 839 def parse_colorspace( name): | |
| 840 ret = cs_name_table.get( name) | |
| 841 if ret: | |
| 842 return ret | |
| 843 state.icc_filename = name | |
| 844 return CS_ICC | |
| 845 | |
| 846 | |
| 847 class trace_info: | |
| 848 def __init__( self): | |
| 849 self.current = 0 | |
| 850 self.peak = 0 | |
| 851 self.total = 0 | |
| 852 | |
| 853 | |
| 854 def iswhite(ch): | |
| 855 return ( | |
| 856 ch == '\011' or ch == '\012' or | |
| 857 ch == '\014' or ch == '\015' or ch == '\040' | |
| 858 ) | |
| 859 | |
| 860 def apply_layer_config( doc, lc): | |
| 861 pass | |
| 862 | |
| 863 | |
| 864 | |
| 865 | |
| 866 def convert_to_accel_path(absname): | |
| 867 tmpdir = os.getenv('TEMP') | |
| 868 if not tmpdir: | |
| 869 tmpdir = os.getenv('TMP') | |
| 870 if not tmpdir: | |
| 871 tmpdir = '/var/tmp' | |
| 872 if not os.path.isdir(tmpdir): | |
| 873 tmpdir = '/tmp' | |
| 874 | |
| 875 if absname.startswith( '/') or absname.startswith( '\\'): | |
| 876 absname = absname[1:] | |
| 877 | |
| 878 absname = absname.replace( '/', '%') | |
| 879 absname = absname.replace( '\\', '%') | |
| 880 absname = absname.replace( ':', '%') | |
| 881 | |
| 882 return '%s/%s.accel' % (tmpdir, absname) | |
| 883 | |
| 884 def get_accelerator_filename( filename): | |
| 885 absname = os.path.realpath( filename) | |
| 886 return convert_to_accel_path( absname) | |
| 887 | |
| 888 def save_accelerator(doc, filename): | |
| 889 if not doc.fz_document_supports_accelerator(): | |
| 890 return | |
| 891 absname = get_accelerator_filename( filename) | |
| 892 doc.fz_save_accelerator( absname) | |
| 893 | |
| 894 | |
| 895 def draw( argv): | |
| 896 | |
| 897 password = '' | |
| 898 info = trace_info() | |
| 899 | |
| 900 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') | |
| 901 for option, value in items: | |
| 902 if 0: pass | |
| 903 elif option == '-q': state.quiet = 1 | |
| 904 elif option == '-p': password = value | |
| 905 elif option == '-o': state.output = value | |
| 906 elif option == '-F': state.format_ = value | |
| 907 elif option == '-R': state.rotation = float( value) | |
| 908 elif option == '-r': | |
| 909 state.resolution = float( value) | |
| 910 state.res_specified = 1 | |
| 911 elif option == '-w': state.width = float( value) | |
| 912 elif option == '-h': state.height = float( value) | |
| 913 elif option == '-f': state.fit = 1 | |
| 914 elif option == '-B': state.band_height = int( value) | |
| 915 elif option == '-c': state.out_cs = parse_colorspace( value) | |
| 916 elif option == '-e': state.proof_filename = value | |
| 917 elif option == '-G': state.gamma_value = float( value) | |
| 918 elif option == '-I': state.invert += 1 | |
| 919 elif option == '-W': state.layout_w = float( value) | |
| 920 elif option == '-H': state.layout_h = float( value) | |
| 921 elif option == '-S': state.layout_em = float( value) | |
| 922 elif option == '-U': state.layout_css = value | |
| 923 elif option == '-X': state.layout_use_doc_css = 0 | |
| 924 elif option == '-O': | |
| 925 state.spots = float( value) | |
| 926 if not mupdf.FZ_ENABLE_SPOT_RENDERING: | |
| 927 sys.stderr.write( 'Spot rendering/Overprint/Overprint simulation not enabled in this build\n') | |
| 928 state.spots = SPOTS_NONE | |
| 929 elif option == '-s': | |
| 930 if 't' in value: state.showtime += 1 | |
| 931 if 'm' in value: state.showmemory += 1 | |
| 932 if 'f' in value: state.showfeatures += 1 | |
| 933 if '5' in value: state.showmd5 += 1 | |
| 934 | |
| 935 elif option == '-A': | |
| 936 state.alphabits_graphics = int(value) | |
| 937 sep = value.find( '/') | |
| 938 if sep >= 0: | |
| 939 state.alphabits_text = int(value[sep+1:]) | |
| 940 else: | |
| 941 state.alphabits_text = state.alphabits_graphics | |
| 942 elif option == '-D': state.uselist = 0 | |
| 943 elif option == '-l': state.min_line_width = float(value) | |
| 944 elif option == '-i': state.ignore_errors = 1 | |
| 945 elif option == '-N': state.no_icc = 1 | |
| 946 | |
| 947 elif option == '-T': state.num_workers = int(value) | |
| 948 elif option == '-L': state.lowmemory = 1 | |
| 949 elif option == '-P': bgprint.active = 1 | |
| 950 elif option == '-y': state.layer_config = value | |
| 951 elif option == '-a': state.useaccel = 0 | |
| 952 | |
| 953 elif option == '-v': sys.stderr.write( f'mudraw version {mupdf.FZ_VERSION}\n') | |
| 954 | |
| 955 if not argv: | |
| 956 usage() | |
| 957 | |
| 958 if state.num_workers > 0: | |
| 959 if state.uselist == 0: | |
| 960 sys.stderr.write('cannot use multiple threads without using display list\n') | |
| 961 sys.exit(1) | |
| 962 | |
| 963 if state.band_height == 0: | |
| 964 sys.stderr.write('Using multiple threads without banding is pointless\n') | |
| 965 | |
| 966 if bgprint.active: | |
| 967 if state.uselist == 0: | |
| 968 sys.stderr.write('cannot bgprint without using display list\n') | |
| 969 sys.exit(1) | |
| 970 | |
| 971 if state.proof_filename: | |
| 972 proof_buffer = mupdf.FzBuffer( state.proof_filename) | |
| 973 state.proof_cs = mupdf.FzColorspace( FZ_COLORSPACE_NONE, 0, None, proof_buffer) | |
| 974 | |
| 975 mupdf.fz_set_text_aa_level( state.alphabits_text) | |
| 976 mupdf.fz_set_graphics_aa_level( state.alphabits_graphics) | |
| 977 mupdf.fz_set_graphics_min_line_width( state.min_line_width) | |
| 978 if state.no_icc: | |
| 979 mupdf.fz_disable_icc() | |
| 980 else: | |
| 981 mupdf.fz_enable_icc() | |
| 982 | |
| 983 if state.layout_css: | |
| 984 buf = mupdf.FzBuffer( state.layout_css) | |
| 985 mupdf.fz_set_user_css( buf.string_from_buffer()) | |
| 986 | |
| 987 mupdf.fz_set_use_document_css( state.layout_use_doc_css) | |
| 988 | |
| 989 # Determine output type | |
| 990 if state.band_height < 0: | |
| 991 sys.stderr.write( 'Bandheight must be > 0\n') | |
| 992 sys.exit(1) | |
| 993 | |
| 994 state.output_format = OUT_PNG | |
| 995 if state.format_: | |
| 996 for i in range(len(suffix_table)): | |
| 997 if state.format_ == suffix_table[i].suffix[1:]: | |
| 998 state.output_format = suffix_table[i].format | |
| 999 if state.spots == SPOTS_FULL and suffix_table[i].spots == 0: | |
| 1000 sys.stderr.write( f'Output format {suffix_table[i].suffix[1:]} does not support spot rendering.\nDoing overprint simulation instead.\n') | |
| 1001 state.spots = SPOTS_OVERPRINT_SIM | |
| 1002 break | |
| 1003 else: | |
| 1004 sys.stderr.write( f'Unknown output format {format}\n') | |
| 1005 sys.exit(1) | |
| 1006 elif state.output: | |
| 1007 suffix = state.output | |
| 1008 i = 0 | |
| 1009 while 1: | |
| 1010 if i == len(suffix_table): | |
| 1011 break | |
| 1012 s = suffix.find( suffix_table[i].suffix) | |
| 1013 if s != -1: | |
| 1014 suffix = suffix_table[i].suffix[s+1:] | |
| 1015 state.output_format = suffix_table[i].format | |
| 1016 if state.spots == SPOTS_FULL and suffix_table[i].spots == 0: | |
| 1017 sys.stderr.write( 'Output format {suffix_table[i].suffix[1:]} does not support spot rendering\nDoing overprint simulation instead.\n') | |
| 1018 state.spots = SPOTS_OVERPRINT_SIM | |
| 1019 i = 0 | |
| 1020 else: | |
| 1021 i += 1 | |
| 1022 | |
| 1023 if state.band_height: | |
| 1024 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): | |
| 1025 sys.stderr.write( 'Banded operation only possible with PxM, PCL, PCLM, PS, PSD, and PNG outputs\n') | |
| 1026 sys.exit(1) | |
| 1027 if state.showmd5: | |
| 1028 sys.stderr.write( 'Banded operation not compatible with MD5\n') | |
| 1029 sys.exit(1) | |
| 1030 | |
| 1031 for i in range(len(format_cs_table)): | |
| 1032 if format_cs_table[i].format == state.output_format: | |
| 1033 if state.out_cs == CS_UNSET: | |
| 1034 state.out_cs = format_cs_table[i].default_cs | |
| 1035 for j in range( len(format_cs_table[i].permitted_cs)): | |
| 1036 if format_cs_table[i].permitted_cs[j] == state.out_cs: | |
| 1037 break | |
| 1038 else: | |
| 1039 sys.stderr.write( 'Unsupported colorspace for this format\n') | |
| 1040 sys.exit(1) | |
| 1041 | |
| 1042 state.alpha = 1 | |
| 1043 if state.out_cs in ( CS_MONO, CS_GRAY, CS_GRAY_ALPHA): | |
| 1044 state.colorspace = mupdf.FzColorspace( mupdf.FzColorspace.Fixed_GRAY) | |
| 1045 state.alpha = (state.out_cs == CS_GRAY_ALPHA) | |
| 1046 elif state.out_cs in ( CS_RGB, CS_RGB_ALPHA): | |
| 1047 state.colorspace = mupdf.FzColorspace( mupdf.FzColorspace.Fixed_RGB) | |
| 1048 state.alpha = (state.out_cs == CS_RGB_ALPHA) | |
| 1049 elif state.out_cs in ( CS_CMYK, CS_CMYK_ALPHA): | |
| 1050 state.colorspace = mupdf.FzColorspace( mupdf.FzColorspace.Fixed_CMYK) | |
| 1051 state.alpha = (state.out_cs == CS_CMYK_ALPHA) | |
| 1052 elif state.out_cs == CS_ICC: | |
| 1053 try: | |
| 1054 icc_buffer = mupdf.FzBuffer( state.icc_filename) | |
| 1055 state.colorspace = Colorspace( mupdf.FZ_COLORSPACE_NONE, 0, None, icc_buffer) | |
| 1056 except Exception as e: | |
| 1057 sys.stderr.write( 'Invalid ICC destination color space\n') | |
| 1058 sys.exit(1) | |
| 1059 if state.colorspace.m_internal is None: | |
| 1060 sys.stderr.write( 'Invalid ICC destination color space\n') | |
| 1061 sys.exit(1) | |
| 1062 state.alpha = 0 | |
| 1063 else: | |
| 1064 sys.stderr.write( 'Unknown colorspace!\n') | |
| 1065 sys.exit(1) | |
| 1066 | |
| 1067 if state.out_cs != CS_ICC: | |
| 1068 state.colorspace = mupdf.FzColorspace( state.colorspace) | |
| 1069 else: | |
| 1070 # Check to make sure this icc profile is ok with the output format */ | |
| 1071 okay = 0 | |
| 1072 for i in range( len(format_cs_table)): | |
| 1073 if format_cs_table[i].format == state.output_format: | |
| 1074 for j in range( len(format_cs_table[i].permitted_cs)): | |
| 1075 x = format_cs_table[i].permitted_cs[j] | |
| 1076 if x in ( CS_MONO, CS_GRAY, CS_GRAY_ALPHA): | |
| 1077 if state.colorspace.fz_colorspace_is_gray(): | |
| 1078 okay = 1 | |
| 1079 elif x in ( CS_RGB, CS_RGB_ALPHA): | |
| 1080 if state.colorspace.fz_colorspace_is_rgb(): | |
| 1081 okay = 1 | |
| 1082 elif x in ( CS_CMYK, CS_CMYK_ALPHA): | |
| 1083 if state.colorspace.fz_colorspace_is_cmyk(): | |
| 1084 okay = 1 | |
| 1085 | |
| 1086 if not okay: | |
| 1087 sys.stderr.write( 'ICC profile uses a colorspace that cannot be used for this format\n') | |
| 1088 sys.exit(1) | |
| 1089 | |
| 1090 if state.output_format == OUT_SVG: | |
| 1091 # SVG files are always opened for each page. Do not open "output". | |
| 1092 pass | |
| 1093 elif state.output and (not state.output.startswith('-') or len(state.output) >= 2) and len(state.output) >= 1: | |
| 1094 if has_percent_d(state.output): | |
| 1095 state.output_file_per_page = 1 | |
| 1096 else: | |
| 1097 state.out = mupdf.FzOutput(state.output, 0) | |
| 1098 else: | |
| 1099 state.quiet = 1 # automatically be quiet if printing to stdout | |
| 1100 if 0: # lgtm [py/unreachable-statement] | |
| 1101 # Windows specific code to make stdout binary. | |
| 1102 if state.output_format not in( OUT_TEXT, OUT_STEXT, OUT_HTML, OUT_XHTML, OUT_TRACE, OUT_XMLTEXT): | |
| 1103 setmode(fileno(stdout), O_BINARY) | |
| 1104 state.out = mupdf.FzOutput( mupdf.FzOutput.Fixed_STDOUT) | |
| 1105 | |
| 1106 state.filename = argv[0] | |
| 1107 if not state.output_file_per_page: | |
| 1108 file_level_headers() | |
| 1109 | |
| 1110 timing.count = 0 | |
| 1111 timing.total = 0 | |
| 1112 timing.min = 1 << 30 | |
| 1113 timing.max = 0 | |
| 1114 timing.mininterp = 1 << 30 | |
| 1115 timing.maxinterp = 0 | |
| 1116 timing.minpage = 0 | |
| 1117 timing.maxpage = 0 | |
| 1118 timing.minfilename = "" | |
| 1119 timing.maxfilename = "" | |
| 1120 timing.layout = 0 | |
| 1121 timing.minlayout = 1 << 30 | |
| 1122 timing.maxlayout = 0 | |
| 1123 timing.minlayoutfilename = "" | |
| 1124 timing.maxlayoutfilename = "" | |
| 1125 | |
| 1126 if state.showtime and bgprint.active: | |
| 1127 timing.total = gettime() | |
| 1128 | |
| 1129 fz_optind = 0 | |
| 1130 try: | |
| 1131 while fz_optind < len( argv): | |
| 1132 | |
| 1133 try: | |
| 1134 accel = None | |
| 1135 | |
| 1136 state.filename = argv[fz_optind] | |
| 1137 fz_optind += 1 | |
| 1138 | |
| 1139 state.files += 1 | |
| 1140 | |
| 1141 if not state.useaccel: | |
| 1142 accel = None | |
| 1143 # If there was an accelerator to load, what would it be called? | |
| 1144 else: | |
| 1145 accelpath = get_accelerator_filename( state.filename) | |
| 1146 # Check whether that file exists, and isn't older than | |
| 1147 # the document. | |
| 1148 atime = stat_mtime( accelpath) | |
| 1149 dtime = stat_mtime( state.filename) | |
| 1150 if atime == 0: | |
| 1151 # No accelerator | |
| 1152 pass | |
| 1153 elif atime > dtime: | |
| 1154 accel = accelpath | |
| 1155 else: | |
| 1156 # Accelerator data is out of date | |
| 1157 os.unlink( accelpath) | |
| 1158 accel = None # In case we have jumped up from below | |
| 1159 | |
| 1160 # Unfortunately if accel=None, SWIG doesn't seem to think of it | |
| 1161 # as a char*, so we end up in fz_open_document_with_stream(). | |
| 1162 # | |
| 1163 # If we try to avoid this by setting accel='', SWIG correctly | |
| 1164 # calls Document(const char *filename, const char *accel) => | |
| 1165 # fz_open_accelerated_document(), but the latter function tests | |
| 1166 # for NULL not "" so fails. | |
| 1167 # | |
| 1168 # So we choose the constructor explicitly rather than leaving | |
| 1169 # it up to SWIG. | |
| 1170 # | |
| 1171 if accel: | |
| 1172 doc = mupdf.FzDocument(state.filename, accel) | |
| 1173 else: | |
| 1174 doc = mupdf.FzDocument(state.filename) | |
| 1175 | |
| 1176 if doc.fz_needs_password(): | |
| 1177 if not doc.fz_authenticate_password( password): | |
| 1178 raise Exception( f'cannot authenticate password: {state.filename}') | |
| 1179 | |
| 1180 # Once document is open check for output intent colorspace | |
| 1181 state.oi = doc.fz_document_output_intent() | |
| 1182 if state.oi.m_internal: | |
| 1183 # See if we had explicitly set a profile to render | |
| 1184 if state.out_cs != CS_ICC: | |
| 1185 # In this case, we want to render to the output intent | |
| 1186 # color space if the number of channels is the same | |
| 1187 if state.oi.fz_colorspace_n() == state.colorspace.fz_colorspace_n(): | |
| 1188 state.colorspace = state.oi | |
| 1189 | |
| 1190 layouttime = time.time() | |
| 1191 doc.fz_layout_document( state.layout_w, state.layout_h, state.layout_em) | |
| 1192 doc.fz_count_pages() | |
| 1193 layouttime = time.time() - layouttime | |
| 1194 | |
| 1195 timing.layout += layouttime | |
| 1196 if layouttime < timing.minlayout: | |
| 1197 timing.minlayout = layouttime | |
| 1198 timing.minlayoutfilename = state.filename | |
| 1199 if layouttime > timing.maxlayout: | |
| 1200 timing.maxlayout = layouttime | |
| 1201 timing.maxlayoutfilename = state.filename | |
| 1202 | |
| 1203 if state.layer_config: | |
| 1204 apply_layer_config( doc, state.layer_config) | |
| 1205 | |
| 1206 if fz_optind == len(argv) or not mupdf.fz_is_page_range( argv[fz_optind]): | |
| 1207 drawrange( doc, "1-N") | |
| 1208 if fz_optind < len( argv) and mupdf.fz_is_page_range( argv[fz_optind]): | |
| 1209 drawrange( doc, argv[fz_optind]) | |
| 1210 fz_optind += 1 | |
| 1211 | |
| 1212 bgprint_flush() | |
| 1213 | |
| 1214 if state.useaccel: | |
| 1215 save_accelerator( doc, state.filename) | |
| 1216 except Exception as e: | |
| 1217 if not state.ignore_errors: | |
| 1218 raise | |
| 1219 bgprint_flush() | |
| 1220 sys.stderr.write( f'ignoring error in {state.filename}\n') | |
| 1221 | |
| 1222 except Exception as e: | |
| 1223 bgprint_flush() | |
| 1224 sys.stderr.write( f'error: cannot draw \'{state.filename}\' because: {e}\n') | |
| 1225 state.errored = 1 | |
| 1226 if 0: | |
| 1227 # Enable for debugging. | |
| 1228 import traceback | |
| 1229 traceback.print_exc() | |
| 1230 | |
| 1231 if not state.output_file_per_page: | |
| 1232 file_level_trailers() | |
| 1233 | |
| 1234 if state.out: | |
| 1235 state.out.fz_close_output() | |
| 1236 state.out = None | |
| 1237 | |
| 1238 if state.showtime and timing.count > 0: | |
| 1239 if bgprint.active: | |
| 1240 timing.total = gettime() - timing.total | |
| 1241 | |
| 1242 if state.files == 1: | |
| 1243 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') | |
| 1244 if bgprint.active: | |
| 1245 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') | |
| 1246 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') | |
| 1247 else: | |
| 1248 sys.stderr.write( f'fastest page {timing.minpage}: {timing.min:.0f}ms\n') | |
| 1249 sys.stderr.write( f'slowest page {timing.maxpage}: {timing.max:.0f}ms\n') | |
| 1250 else: | |
| 1251 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') | |
| 1252 sys.stderr.write( f'fastest layout: {timing.minlayout:.0f}ms ({timing.minlayoutfilename})\n') | |
| 1253 sys.stderr.write( f'slowest layout: {timing.maxlayout:.0f}ms ({timing.maxlayoutfilename})\n') | |
| 1254 sys.stderr.write( f'fastest page {timing.minpage}: {timing.min:.0f}ms ({timing.minfilename})\n') | |
| 1255 sys.stderr.write( f'slowest page {timing.maxpage}: {timing.max:.0f}ms ({timing.maxfilename})\n') | |
| 1256 | |
| 1257 if state.showmemory: | |
| 1258 sys.stderr.write( f'Memory use total={info.total} peak={info.peak} current={info.current}\n') | |
| 1259 | |
| 1260 return state.errored != 0 |
