2018-08-21 03:45:09 +00:00
"""
@created : 2018 - 08 - 19 18 : 00 : 00
@author : ( c ) 2018 Jorj X . McKie
Display a PyMuPDF Document using Tkinter
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Dependencies :
- - - - - - - - - - - - -
PyMuPDF , PySimpleGUI > v2 .9 .0 , Tkinter with Tk v8 .6 + , Python 3
License :
- - - - - - - -
GNU GPL V3 +
Description
- - - - - - - - - - - -
Read filename from command line and start display with page 1.
Pages can be directly jumped to , or buttons for paging can be used .
For experimental / demonstration purposes , we have included options to zoom
into the four page quadrants ( top - left , bottom - right , etc . ) .
We also interpret keyboard events to support paging by PageDown / PageUp
keys as if the resp . buttons were clicked . Similarly , we do not include
2018-09-24 22:01:00 +00:00
a ' Quit ' button . Instead , the ESCAPE key can be used , or cancelling the window .
2018-08-21 03:45:09 +00:00
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 .
"""
2018-08-20 22:43:33 +00:00
import sys
import fitz
2018-09-28 18:57:37 +00:00
if sys . version_info [ 0 ] > = 3 :
import PySimpleGUI as sg
else :
import PySimpleGUI27 as sg
2018-09-24 22:01:00 +00:00
from sys import exit as exit
2018-08-21 03:45:09 +00:00
from binascii import hexlify
2018-08-20 22:43:33 +00:00
2018-08-21 23:10:26 +00:00
sg . ChangeLookAndFeel ( ' GreenTan ' )
2018-08-21 03:45:09 +00:00
if len ( sys . argv ) == 1 :
2018-09-18 16:05:44 +00:00
rc , fname = sg . PopupGetFile ( ' PDF Browser ' , ' PDF file to open ' , file_types = ( ( " PDF Files " , " *.pdf " ) , ) )
2018-08-21 03:45:09 +00:00
if rc is False :
2018-09-18 16:05:44 +00:00
sg . PopupCancel ( ' Cancelling ' )
2018-08-21 03:45:09 +00:00
exit ( 0 )
else :
2018-08-20 22:43:33 +00:00
fname = sys . argv [ 1 ]
2018-08-21 03:45:09 +00:00
2018-08-20 22:43:33 +00:00
doc = fitz . open ( fname )
2018-08-21 03:45:09 +00:00
page_count = len ( doc )
# 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 = 0 ) :
""" Return a PNG image for a document page number. If zoom is other than 0, one of the 4 page quadrants are zoomed-in instead and the corresponding clip returned.
"""
dlist = dlist_tab [ pno ] # get display list
if not dlist : # create if not yet there
dlist_tab [ pno ] = doc [ pno ] . getDisplayList ( )
dlist = dlist_tab [ pno ]
r = dlist . rect # page rectangle
mp = r . tl + ( r . br - r . tl ) * 0.5 # rect middle point
mt = r . tl + ( r . tr - r . tl ) * 0.5 # middle of top edge
ml = r . tl + ( r . bl - r . tl ) * 0.5 # middle of left edge
mr = r . tr + ( r . br - r . tr ) * 0.5 # middle of right egde
mb = r . bl + ( r . br - r . bl ) * 0.5 # middle of bottom edge
mat = fitz . Matrix ( 2 , 2 ) # zoom matrix
if zoom == 1 : # top-left quadrant
2018-08-20 22:43:33 +00:00
clip = fitz . Rect ( r . tl , mp )
2018-08-21 03:45:09 +00:00
elif zoom == 4 : # bot-right quadrant
2018-08-20 22:43:33 +00:00
clip = fitz . Rect ( mp , r . br )
2018-08-21 03:45:09 +00:00
elif zoom == 2 : # top-right
2018-08-20 22:43:33 +00:00
clip = fitz . Rect ( mt , mr )
2018-08-21 03:45:09 +00:00
elif zoom == 3 : # bot-left
2018-08-20 22:43:33 +00:00
clip = fitz . Rect ( ml , mb )
2018-08-21 03:45:09 +00:00
if zoom == 0 : # total page
pix = dlist . getPixmap ( alpha = False )
2018-08-20 22:43:33 +00:00
else :
2018-08-21 03:45:09 +00:00
pix = dlist . getPixmap ( alpha = False , matrix = mat , clip = clip )
return pix . getPNGData ( ) # return the PNG image
2018-08-20 22:43:33 +00:00
2018-09-24 22:01:00 +00:00
window = sg . Window ( title , return_keyboard_events = True , use_default_focus = False )
2018-08-20 22:43:33 +00:00
2018-08-21 03:45:09 +00:00
cur_page = 0
data = get_page ( cur_page ) # show page 1 for start
2018-08-20 22:43:33 +00:00
image_elem = sg . Image ( data = data )
2018-08-21 03:45:09 +00:00
goto = sg . InputText ( str ( cur_page + 1 ) , size = ( 5 , 1 ) , do_not_clear = True )
layout = [
[
2018-09-24 22:01:00 +00:00
sg . ReadButton ( ' Next ' ) ,
sg . ReadButton ( ' Prev ' ) ,
2018-08-21 03:45:09 +00:00
sg . Text ( ' Page: ' ) ,
goto ,
] ,
[
sg . Text ( " Zoom: " ) ,
2018-09-24 22:01:00 +00:00
sg . ReadButton ( ' Top-L ' ) ,
sg . ReadButton ( ' Top-R ' ) ,
sg . ReadButton ( ' Bot-L ' ) ,
sg . ReadButton ( ' Bot-R ' ) ,
2018-08-21 03:45:09 +00:00
] ,
[ image_elem ] ,
]
2018-08-20 22:43:33 +00:00
2018-09-24 22:01:00 +00:00
window . Layout ( layout )
2018-08-21 03:45:09 +00:00
my_keys = ( " Next " , " Next:34 " , " Prev " , " Prior:33 " , " Top-L " , " Top-R " ,
" Bot-L " , " Bot-R " , " MouseWheel:Down " , " MouseWheel:Up " )
zoom_buttons = ( " Top-L " , " Top-R " , " Bot-L " , " Bot-R " )
old_page = 0
old_zoom = 0 # used for zoom on/off
# the zoom buttons work in on/off mode.
2018-08-20 22:43:33 +00:00
while True :
2018-10-15 20:07:23 +00:00
event , values = window . ReadNonBlocking ( )
2018-08-20 22:43:33 +00:00
zoom = 0
2018-08-21 03:45:09 +00:00
force_page = False
2018-10-15 20:07:23 +00:00
if event is None and values is None :
2018-08-20 22:43:33 +00:00
break
2018-10-15 20:07:23 +00:00
if event is None :
2018-08-21 03:45:09 +00:00
continue
2018-10-15 20:07:23 +00:00
if event in ( " Escape:27 " , ) : # this spares me a 'Quit' button!
2018-08-21 03:45:09 +00:00
break
# print("hex(button)", hexlify(button.encode()))
2018-10-15 20:07:23 +00:00
if event [ 0 ] == chr ( 13 ) : # surprise: this is 'Enter'!
2018-08-21 03:45:09 +00:00
try :
2018-10-15 20:07:23 +00:00
cur_page = int ( values [ 0 ] ) - 1 # check if valid
2018-08-21 03:45:09 +00:00
while cur_page < 0 :
cur_page + = page_count
except :
cur_page = 0 # this guy's trying to fool me
goto . Update ( str ( cur_page + 1 ) )
# goto.TKStringVar.set(str(cur_page + 1))
2018-10-15 20:07:23 +00:00
elif event in ( " Next " , " Next:34 " , " MouseWheel:Down " ) :
2018-08-21 03:45:09 +00:00
cur_page + = 1
2018-10-15 20:07:23 +00:00
elif event in ( " Prev " , " Prior:33 " , " MouseWheel:Up " ) :
2018-08-21 03:45:09 +00:00
cur_page - = 1
2018-10-15 20:07:23 +00:00
elif event == " Top-L " :
2018-08-21 03:45:09 +00:00
zoom = 1
2018-10-15 20:07:23 +00:00
elif event == " Top-R " :
2018-08-21 03:45:09 +00:00
zoom = 2
2018-10-15 20:07:23 +00:00
elif event == " Bot-L " :
2018-08-21 03:45:09 +00:00
zoom = 3
2018-10-15 20:07:23 +00:00
elif event == " Bot-R " :
2018-08-21 03:45:09 +00:00
zoom = 4
# sanitize page number
if cur_page > = page_count : # wrap around
cur_page = 0
while cur_page < 0 : # we show conventional page numbers
cur_page + = page_count
# prevent creating same data again
if cur_page != old_page :
zoom = old_zoom = 0
force_page = True
2018-10-15 20:07:23 +00:00
if event in zoom_buttons :
2018-08-21 03:45:09 +00:00
if 0 < zoom == old_zoom :
zoom = 0
force_page = True
if zoom != old_zoom :
force_page = True
if force_page :
data = get_page ( cur_page , zoom )
image_elem . Update ( data = data )
old_page = cur_page
old_zoom = zoom
# update page number field
2018-10-15 20:07:23 +00:00
if event in my_keys or not values [ 0 ] :
2018-08-21 03:45:09 +00:00
goto . Update ( str ( cur_page + 1 ) )
# goto.TKStringVar.set(str(cur_page + 1))