Check into Dev Branch from Master
This commit is contained in:
parent
0c37488dd3
commit
ab92627916
|
@ -0,0 +1,238 @@
|
|||
"""
|
||||
@created: 2018-08-19 18:00:00
|
||||
@author: (c) 2018 Jorj X. McKie
|
||||
Display a PyMuPDF Document using Tkinter
|
||||
-------------------------------------------------------------------------------
|
||||
Dependencies:
|
||||
-------------
|
||||
PyMuPDF, PySimpleGUI (requires Python 3), Tkinter, PIL
|
||||
License:
|
||||
--------
|
||||
GNU GPL V3+
|
||||
Description
|
||||
------------
|
||||
Get filename and start displaying page 1. Please note that all file types
|
||||
of MuPDF are supported (including EPUB e-books and HTML files for example).
|
||||
Pages can be directly jumped to, or buttons can be used for paging.
|
||||
|
||||
This version contains enhancements:
|
||||
* Use of PIL improves response times by a factor 3 or more.
|
||||
* Zooming is now flexible: only one button serves as a toggle. Arrow keys can
|
||||
be used for moving the window when zooming.
|
||||
|
||||
We also interpret keyboard events (PageDown / PageUp) and mouse wheel actions
|
||||
to support paging as if a button was clicked. Similarly, we do not include
|
||||
a 'Quit' button. Instead, the ESCAPE key can be used, or cancelling the form.
|
||||
To improve paging performance, we are not directly creating pixmaps from
|
||||
pages, but instead from the fitz.DisplayList of the page. A display list
|
||||
will be stored in a list and looked up by page number. This way, zooming
|
||||
pixmaps and page re-visits will re-use a once-created display list.
|
||||
|
||||
"""
|
||||
import sys
|
||||
import fitz
|
||||
import PySimpleGUI as sg
|
||||
import tkinter as tk
|
||||
from PIL import Image, ImageTk
|
||||
import time
|
||||
|
||||
if len(sys.argv) == 1:
|
||||
rc, fname = sg.GetFileBox('Document Browser', 'Document file to open',
|
||||
file_types = (
|
||||
("PDF Files", "*.pdf"),
|
||||
("XPS Files", "*.*xps"),
|
||||
("Epub Files", "*.epub"),
|
||||
("Fiction Books", "*.fb2"),
|
||||
("Comic Books", "*.cbz"),
|
||||
("HTML", "*.htm*"),
|
||||
# add more document types here
|
||||
)
|
||||
)
|
||||
else:
|
||||
fname = sys.argv[1]
|
||||
|
||||
if not fname:
|
||||
sg.MsgBox("Cancelling:", "No filename supplied")
|
||||
raise SystemExit("Cancelled: no filename supplied")
|
||||
|
||||
doc = fitz.open(fname)
|
||||
page_count = len(doc)
|
||||
|
||||
# used for response time statistics only
|
||||
fitz_img_time = 0.0
|
||||
tk_img_time = 0.0
|
||||
img_count = 1
|
||||
|
||||
# allocate storage for page display lists
|
||||
dlist_tab = [None] * page_count
|
||||
|
||||
title = "PyMuPDF display of '%s', pages: %i" % (fname, page_count)
|
||||
|
||||
def get_page(pno, zoom = False, max_size = None, first = False):
|
||||
"""Return a PNG image for a document page number.
|
||||
"""
|
||||
dlist = dlist_tab[pno] # get display list of page number
|
||||
if not dlist: # create if not yet there
|
||||
dlist_tab[pno] = doc[pno].getDisplayList()
|
||||
dlist = dlist_tab[pno]
|
||||
r = dlist.rect # the page rectangle
|
||||
clip = r
|
||||
# ensure image fits screen:
|
||||
# exploit, but do not exceed width or height
|
||||
zoom_0 = 1
|
||||
if max_size:
|
||||
zoom_0 = min(1, max_size[0] / r.width, max_size[1] / r.height)
|
||||
if zoom_0 == 1:
|
||||
zoom_0 = min(max_size[0] / r.width, max_size[1] / r.height)
|
||||
mat_0 = fitz.Matrix(zoom_0, zoom_0)
|
||||
|
||||
if not zoom: # show total page
|
||||
pix = dlist.getPixmap(matrix = mat_0, alpha=False)
|
||||
else:
|
||||
mp = r.tl + (r.br - r.tl) * 0.5 # page rect center
|
||||
w2 = r.width / 2
|
||||
h2 = r.height / 2
|
||||
clip = r * 0.5
|
||||
tl = zoom[0] # old top-left
|
||||
tl.x += zoom[1] * (w2 / 2)
|
||||
tl.x = max(0, tl.x)
|
||||
tl.x = min(w2, tl.x)
|
||||
tl.y += zoom[2] * (h2 / 2)
|
||||
tl.y = max(0, tl.y)
|
||||
tl.y = min(h2, tl.y)
|
||||
clip = fitz.Rect(tl, tl.x + w2, tl.y + h2)
|
||||
|
||||
mat = mat_0 * fitz.Matrix(2, 2) # zoom matrix
|
||||
pix = dlist.getPixmap(alpha=False, matrix=mat, clip=clip)
|
||||
|
||||
if first: # first call: tkinter still inactive
|
||||
img = pix.getPNGData() # so use fitz png output
|
||||
else: # else take tk photo image
|
||||
pilimg = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
|
||||
img = ImageTk.PhotoImage(pilimg)
|
||||
|
||||
return img, clip.tl # return image, clip position
|
||||
|
||||
|
||||
root = tk.Tk()
|
||||
max_width = root.winfo_screenwidth() - 20
|
||||
max_height = root.winfo_screenheight() - 135
|
||||
max_size = (max_width, max_height)
|
||||
root.destroy()
|
||||
del root
|
||||
|
||||
form = sg.FlexForm(title, return_keyboard_events = True,
|
||||
location = (0,0), use_default_focus = False)
|
||||
|
||||
cur_page = 0
|
||||
data, clip_pos = get_page(cur_page,
|
||||
zoom = False,
|
||||
max_size = max_size,
|
||||
first = True)
|
||||
|
||||
image_elem = sg.Image(data = data)
|
||||
|
||||
goto = sg.InputText(str(cur_page + 1), size=(5, 1), do_not_clear=True,
|
||||
key = "PageNumber")
|
||||
|
||||
layout = [
|
||||
[
|
||||
sg.ReadFormButton('Next'),
|
||||
sg.ReadFormButton('Prev'),
|
||||
sg.Text('Page:'),
|
||||
goto,
|
||||
sg.Text('(%i)' % page_count),
|
||||
sg.ReadFormButton('Zoom'),
|
||||
sg.Text('(toggle on/off, use arrows to navigate while zooming)'),
|
||||
],
|
||||
[image_elem],
|
||||
]
|
||||
|
||||
form.Layout(layout)
|
||||
|
||||
# now define the buttons / events we want to handle
|
||||
enter_buttons = [chr(13), "Return:13"]
|
||||
quit_buttons = ["Escape:27", chr(27)]
|
||||
next_buttons = ["Next", "Next:34", "MouseWheel:Down"]
|
||||
prev_buttons = ["Prev", "Prior:33", "MouseWheel:Up"]
|
||||
Up = "Up:38"
|
||||
Left = "Left:37"
|
||||
Right = "Right:39"
|
||||
Down = "Down:40"
|
||||
zoom_buttons = ["Zoom", Up, Down, Left, Right]
|
||||
|
||||
# all the buttons we will handle
|
||||
my_keys = enter_buttons + next_buttons + prev_buttons + zoom_buttons
|
||||
|
||||
# old page store and zoom toggle
|
||||
old_page = 0
|
||||
old_zoom = False
|
||||
|
||||
while True:
|
||||
button, value = form.Read()
|
||||
if button is None and (value is None or value['PageNumber'] is None):
|
||||
break
|
||||
if button in quit_buttons:
|
||||
break
|
||||
|
||||
zoom_pressed = False
|
||||
zoom = False
|
||||
|
||||
if button in enter_buttons:
|
||||
try:
|
||||
cur_page = int(value['PageNumber']) - 1 # check if valid
|
||||
while cur_page < 0:
|
||||
cur_page += page_count
|
||||
except:
|
||||
cur_page = 0 # this guy's trying to fool me
|
||||
|
||||
elif button in next_buttons:
|
||||
cur_page += 1
|
||||
elif button in prev_buttons:
|
||||
cur_page -= 1
|
||||
elif button == Up:
|
||||
zoom = (clip_pos, 0, -1)
|
||||
elif button == Down:
|
||||
zoom = (clip_pos, 0, 1)
|
||||
elif button == Left:
|
||||
zoom = (clip_pos, -1, 0)
|
||||
elif button == Right:
|
||||
zoom = (clip_pos, 1, 0)
|
||||
elif button == "Zoom":
|
||||
zoom_pressed = True
|
||||
zoom = (clip_pos, 0, 0)
|
||||
|
||||
# sanitize page number
|
||||
if cur_page >= page_count: # wrap around
|
||||
cur_page = 0
|
||||
while cur_page < 0: # pages > 0 look nicer
|
||||
cur_page += page_count
|
||||
|
||||
if zoom_pressed and old_zoom:
|
||||
zoom = zoom_pressed = old_zoom = False
|
||||
|
||||
t0 = time.perf_counter()
|
||||
data, clip_pos = get_page(cur_page, zoom = zoom, max_size = max_size,
|
||||
first = False)
|
||||
t1 = time.perf_counter()
|
||||
image_elem.Update(data = data)
|
||||
t2 = time.perf_counter()
|
||||
fitz_img_time += t1 - t0
|
||||
tk_img_time += t2 - t1
|
||||
img_count += 1
|
||||
old_page = cur_page
|
||||
old_zoom = zoom_pressed or zoom
|
||||
|
||||
# update page number field
|
||||
if button in my_keys:
|
||||
goto.Update(str(cur_page + 1))
|
||||
|
||||
|
||||
# print some response time statistics
|
||||
if img_count > 0:
|
||||
print("response times for '%s'" % doc.name)
|
||||
print("%.4f" % (fitz_img_time/img_count), "sec fitz avg. image time")
|
||||
print("%.4f" % (tk_img_time/img_count), "sec tk avg. image time")
|
||||
print("%.4f" % ((fitz_img_time + tk_img_time)/img_count), "sec avg. total time")
|
||||
print(img_count, "images read")
|
||||
print(page_count, "pages")
|
Loading…
Reference in New Issue