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