comparison 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
comparison
equal deleted inserted replaced
1:1d09e1dec1d9 2:b50eed0cc0ef
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