#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
@created: 2015-10-23 13:40:00
@author: Jorj X. McKie
Let the user select a Python file and then scroll through it.
Dependencies:
PyMuPDF, wxPython 3.x
License:
GNU GPL 3.x
Changes in PyMuPDF 1.9.2
------------------------
- optionally show links in displayed pages
- when clicking on a link, attempt to follow the link
- remove inline code for dialog icon and import from a library
Changes in PyMuPDF 1.8.0
------------------------
- display a fancy icon on the dialogs, defined as inline code in base64 format
- display pages with a zoom factor of 120%
- dynamically resize dialog to reflect each page's image size / orientation
- minor cosmetic changes
"""
import fitz
import wx
import os
try:
from PageFormat import FindFit # may have paper format finder ...
do_paper = True
except:
do_paper = False
try:
from icons import ico_pdf # PDF icon in upper left screen corner
do_icon = True
except:
do_icon = False
def getint(v):
import types
try:
return int(v)
except:
pass
if not isinstance(v, types.StringTypes):
return 0
a = "0"
for d in v:
if d in "0123456789":
a += d
return int(a)
# abbreviations to get rid of those long pesky names ...
defPos = wx.DefaultPosition
defSiz = wx.DefaultSize
khaki = wx.Colour(240, 230, 140)
zoom = 1.2 # zoom factor of display
#==============================================================================
# Define our dialog as a subclass of wx.Dialog.
# Only special thing is, that we are being invoked with a filename ...
#==============================================================================
class PDFdisplay (wx.Dialog):
def __init__(self, parent, filename):
wx.Dialog.__init__ (self, parent, id = wx.ID_ANY,
title = u"Display with PyMuPDF: ",
pos = defPos, size = defSiz,
style = wx.CAPTION|wx.CLOSE_BOX|wx.DEFAULT_DIALOG_STYLE)
#======================================================================
# display an icon top left of dialog, append filename to title
#======================================================================
if do_icon:
self.SetIcon(ico_pdf.img.GetIcon()) # set a screen icon
self.SetTitle(self.Title + filename)
self.SetBackgroundColour(khaki)
#======================================================================
# open the document with MuPDF when dialog gets created
#======================================================================
self.doc = fitz.open(filename) # create Document object
if self.doc.needsPass: # check password protection
self.decrypt_doc()
if self.doc.isEncrypted: # quit if we cannot decrpt
self.Destroy()
return
self.last_page = -1 # memorize last page displayed
self.link_rects = [] # store link rectangles here
self.link_texts = [] # store link texts here
self.current_idx = -1 # store entry of found rectangle
self.current_lnks = [] # store entry of found rectangle
self.cursor_hand = wx.StockCursor(wx.CURSOR_HAND)
self.cursor_norm = wx.StockCursor(wx.CURSOR_DEFAULT)
#======================================================================
# define zooming matrix for displaying PDF page images
# we increase images by 20%, so take 1.2 as scale factors
#======================================================================
self.matrix = fitz.Matrix(zoom, zoom) # will use a constant zoom
'''
=======================================================================
Overall Dialog Structure:
-------------------------
szr10 (main sizer for the whole dialog - vertical orientation)
+-> szr20 (sizer for buttons etc. - horizontal orientation)
+-> button forward
+-> button backward
+-> field for page number to jump to
+-> field displaying total pages
+-> PDF image area
=======================================================================
'''
#======================================================================
# the main sizer of the dialog
#======================================================================
self.szr10 = wx.BoxSizer(wx.VERTICAL)
#======================================================================
# this sizer will contain scrolling buttons, page numbers etc.
#======================================================================
szr20 = wx.BoxSizer(wx.HORIZONTAL)
#======================================================================
# forward button
#======================================================================
self.ButtonNext = wx.Button(self, wx.ID_ANY, u"forw",
defPos, defSiz, wx.BU_EXACTFIT)
szr20.Add(self.ButtonNext, 0, wx.ALL, 5)
#======================================================================
# backward button
#======================================================================
self.ButtonPrevious = wx.Button(self, wx.ID_ANY, u"back",
defPos, defSiz, wx.BU_EXACTFIT)
szr20.Add(self.ButtonPrevious, 0, wx.ALL, 5)
#======================================================================
# text field for entering a target page. wx.TE_PROCESS_ENTER is
# required to get data entry fired as events.
#======================================================================
self.TextToPage = wx.TextCtrl(self, wx.ID_ANY, u"1", defPos,
wx.Size(40, -1),
wx.TE_RIGHT|wx.TE_PROCESS_ENTER)
szr20.Add(self.TextToPage, 0, wx.ALL, 5)
#======================================================================
# displays total pages and page paper format
#======================================================================
self.statPageMax = wx.StaticText(self, wx.ID_ANY,
"of " + str(len(self.doc)) + " pages.",
defPos, defSiz, 0)
szr20.Add(self.statPageMax, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
self.links = wx.CheckBox( self, wx.ID_ANY,
u"show links",
defPos, defSiz, wx.ALIGN_LEFT)
szr20.Add( self.links, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 )
self.paperform = wx.StaticText(self, wx.ID_ANY,
"", defPos, defSiz, 0)
szr20.Add(self.paperform, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
#======================================================================
# sizer ready, represents top dialog line
#======================================================================
self.szr10.Add(szr20, 0, wx.EXPAND, 5)
#======================================================================
# define the area for page images and load page 1 for primary display
#======================================================================
self.PDFimage = wx.StaticBitmap(self, wx.ID_ANY, self.pdf_show(1),
defPos, defSiz, 0)
self.szr10.Add(self.PDFimage, 0, wx.ALL, 5)
#======================================================================
# main sizer now ready - request final size & layout adjustments
#======================================================================
self.szr10.Fit(self)
self.SetSizer(self.szr10)
self.Layout()
#======================================================================
# center dialog on screen
#======================================================================
self.Centre(wx.BOTH)
#======================================================================
# Bind buttons and fields to event handlers
#======================================================================
self.ButtonNext.Bind(wx.EVT_BUTTON, self.NextPage)
self.ButtonPrevious.Bind(wx.EVT_BUTTON, self.PreviousPage)
self.TextToPage.Bind(wx.EVT_TEXT_ENTER, self.GotoPage)
self.PDFimage.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
self.PDFimage.Bind(wx.EVT_MOTION, self.move_mouse)
self.PDFimage.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
def __del__(self):
pass
#==============================================================================
# Button handlers and other functions
#==============================================================================
def OnLeftDown(self, evt):
if self.current_idx < 0:
evt.Skip()
return
lnk = self.current_lnks[self.current_idx]
if lnk["kind"] == fitz.LINK_GOTO:
self.TextToPage.Value = str(lnk["page"] + 1)
self.GotoPage(evt)
elif lnk["kind"] == fitz.LINK_URI:
import webbrowser
try:
webbrowser.open_new(self.link_texts[self.current_idx])
except:
pass
elif lnk["kind"] == fitz.LINK_GOTOR:
import subprocess
try:
subprocess.Popen(self.link_texts[self.current_idx])
except:
pass
elif lnk["kind"] == fitz.LINK_NAMED:
if lnk["name"] == "FirstPage":
self.TextToPage.Value = "1"
elif lnk["name"] == "LastPage":
self.TextToPage.Value = str(len(self.doc))
elif lnk["name"] == "NextPage":
self.TextToPage.Value = str(int(self.TextToPage.Value) + 1)
elif lnk["name"] == "PrevPage":
self.TextToPage.Value = str(int(self.TextToPage.Value) - 1)
self.GotoPage(evt)
evt.Skip()
return
def cursor_in_rect(self, pos, rect): # is mouse in a hot area?
# check whether cursor is in rectangle
if (rect[0] <= pos.x <= (rect[0] + rect[2])) and \
(rect[1] <= pos.y <= (rect[1] + rect[3])):
return True
return False
def move_mouse(self, evt): # show hand if in a rectangle
if not self.links.Value: # do not process links
evt.Skip()
return
pos = evt.GetPosition()
in_rect = False
for i, rect in enumerate(self.link_rects):
if self.cursor_in_rect(pos, rect):
in_rect = True
self.current_idx = i
break
if in_rect:
self.PDFimage.SetCursor(self.cursor_hand)
self.PDFimage.SetToolTipString(self.link_texts[i])
self.current_idx = i
else:
self.PDFimage.SetCursor(self.cursor_norm)
self.PDFimage.UnsetToolTip()
self.current_idx = -1
evt.Skip()
return
def OnMouseWheel(self, evt):
# process wheel as paging operations
d = evt.GetWheelRotation() # int indicating direction
if d < 0:
self.NextPage(evt)
elif d > 0:
self.PreviousPage(evt)
return
def NextPage(self, event): # means: page forward
page = getint(self.TextToPage.Value) + 1 # current page + 1
page = min(page, self.doc.pageCount) # cannot go beyond last page
self.TextToPage.Value = str(page) # put target page# in screen
self.bitmap = self.pdf_show(page) # get page image
self.NeuesImage(page) # refresh the layout
event.Skip()
def PreviousPage(self, event): # means: page back
page = getint(self.TextToPage.Value) - 1 # current page - 1
page = max(page, 1) # cannot go before page 1
self.TextToPage.Value = str(page) # put target page# in screen
self.NeuesImage(page)
event.Skip()
def GotoPage(self, event): # means: go to page number
page = getint(self.TextToPage.Value) # get page# from screen
page = min(page, len(self.doc)) # cannot go beyond last page
page = max(page, 1) # cannot go before page 1
self.TextToPage.Value = str(page) # make sure it's on the screen
self.NeuesImage(page)
event.Skip()
#==============================================================================
# Read / render a PDF page. Parameters are: pdf = document, page = page#
#==============================================================================
def NeuesImage(self, page):
if page == self.last_page:
return
self.last_page = page
self.link_rects = []
self.link_texts = []
self.current_lnks = []
self.bitmap = self.pdf_show(page) # read page image
if self.links.Value: # show links?
self.draw_links(self.bitmap, page)
self.PDFimage.SetSize(self.bitmap.Size) # adjust screen to image size
self.PDFimage.SetBitmap(self.bitmap) # put it in screen
return
def draw_links(self, bmp, pno):
dc = wx.MemoryDC()
dc.SelectObject(bmp)
dc.SetPen(wx.Pen("BLUE", width=1))
dc.SetBrush(wx.Brush("BLUE", style=wx.BRUSHSTYLE_TRANSPARENT))
pg_w = self.pg_ir.x1 - self.pg_ir.x0
pg_h = self.pg_ir.y1 - self.pg_ir.y0
zoom_w = float(bmp.Size[0]) / float(pg_w)
zoom_h = float(bmp.Size[1]) / float(pg_h)
for lnk in self.current_lnks:
r = lnk["from"].round()
wx_r = wx.Rect(int(r.x0 * zoom_w),
int(r.y0 * zoom_h),
int((r.x1 - r.x0)*zoom_w),
int((r.y1 - r.y0)*zoom_h))
dc.DrawRectangle(wx_r[0], wx_r[1], wx_r[2], wx_r[3])
self.link_rects.append(wx_r)
if lnk["kind"] == fitz.LINK_GOTO:
txt = "page " + str(lnk["page"] + 1)
elif lnk["kind"] == fitz.LINK_GOTOR:
txt = lnk["file"]
elif lnk["kind"] == fitz.LINK_URI:
txt = lnk["uri"]
else:
txt = "unkown target"
self.link_texts.append(txt)
dc.SelectObject(wx.NullBitmap)
dc = None
return
def pdf_show(self, pg_nr):
page = self.doc.loadPage(int(pg_nr) - 1) # load the page & get Pixmap
pix = page.getPixmap(matrix = self.matrix)
bmp = wx.BitmapFromBuffer(pix.w, pix.h, pix.samplesRGB())
if do_paper:
paper = FindFit(page.bound().x1, page.bound().y1)
else:
paper = "not implemented"
self.paperform.Label = "Page format: " + paper
if self.links.Value:
self.current_lnks = page.getLinks()
self.pg_ir = page.bound().round()
page = None
pix = None
return bmp
def decrypt_doc(self):
# let user enter document password
pw = None
dlg = wx.TextEntryDialog(self, 'Please enter password below:',
'Document needs password to open', '',
style = wx.TextEntryDialogStyle|wx.TE_PASSWORD)
while pw is None:
rc = dlg.ShowModal()
if rc == wx.ID_OK:
pw = str(dlg.GetValue().encode("utf-8"))
self.doc.authenticate(pw)
else:
return
if self.doc.isEncrypted:
pw = None
dlg.SetTitle("Wrong password. Enter correct one or cancel.")
return
#==============================================================================
# main program
#==============================================================================
# start the wx application
app = None
app = wx.App()
#==============================================================================
# Show a FileSelect dialog to choose a file for display
#==============================================================================
# Wildcard: offer all supported filetypes for display
wild = "supported files|*.pdf;*.xps;*.oxps;*.epub;*.cbz"
#==============================================================================
# define the file selection dialog
#==============================================================================
dlg = wx.FileDialog(None, message = "Choose a file to display",
defaultDir = os.path.expanduser("~"),
defaultFile = "",
wildcard = wild, style=wx.OPEN|wx.CHANGE_DIR)
#==============================================================================
# now display and ask for return code in one go
#==============================================================================
# We got a file only when one was selected and OK pressed
if dlg.ShowModal() == wx.ID_OK:
# This returns a Python list of selected files (we only have one though)
filename = dlg.GetPaths()[0]
else:
filename = None
# destroy this dialog
dlg.Destroy()
#==============================================================================
# only continue if we have a filename
#==============================================================================
if filename:
# create the dialog
dlg = PDFdisplay(None, filename)
# show it - this will only return for final housekeeping
rc = dlg.ShowModal()
app = None
Diff to Previous Revision
--- revision 1 2016-03-15 18:44:33
+++ revision 2 2016-09-28 12:21:03
@@ -13,62 +13,55 @@
License:
GNU GPL 3.x
+
+Changes in PyMuPDF 1.9.2
+------------------------
+- optionally show links in displayed pages
+- when clicking on a link, attempt to follow the link
+- remove inline code for dialog icon and import from a library
Changes in PyMuPDF 1.8.0
------------------------
- display a fancy icon on the dialogs, defined as inline code in base64 format
-- display PDF pages with a zoom factor of 120%
+- display pages with a zoom factor of 120%
- dynamically resize dialog to reflect each page's image size / orientation
- minor cosmetic changes
-- adjust the scaling matrix in function pdf_show as you like it.
"""
-
-import fitz # bindings for MuPDF
+import fitz
import wx
import os
-from wx.lib.embeddedimage import PyEmbeddedImage
-#==============================================================================
-# The following data has been created by the wxPython tool img2py.py
-# It contains SumatraPDF's icon (only for demonstration purposes) as a base64
-# version (like PyMuPDF, SumatraPDF software is licensed under GNU GPL 3.x).
-#==============================================================================
-img = PyEmbeddedImage(
- "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")
-
-# first abbreviations to get rid of those long pesky names ...
+
+try:
+ from PageFormat import FindFit # may have paper format finder ...
+ do_paper = True
+except:
+ do_paper = False
+
+try:
+ from icons import ico_pdf # PDF icon in upper left screen corner
+ do_icon = True
+except:
+ do_icon = False
+
+def getint(v):
+ import types
+ try:
+ return int(v)
+ except:
+ pass
+ if not isinstance(v, types.StringTypes):
+ return 0
+ a = "0"
+ for d in v:
+ if d in "0123456789":
+ a += d
+ return int(a)
+
+# abbreviations to get rid of those long pesky names ...
defPos = wx.DefaultPosition
defSiz = wx.DefaultSize
-
+khaki = wx.Colour(240, 230, 140)
+zoom = 1.2 # zoom factor of display
#==============================================================================
# Define our dialog as a subclass of wx.Dialog.
# Only special thing is, that we are being invoked with a filename ...
@@ -77,28 +70,40 @@
def __init__(self, parent, filename):
wx.Dialog.__init__ (self, parent, id = wx.ID_ANY,
- title = u"PDF Display with PyMuPDF: ",
+ title = u"Display with PyMuPDF: ",
pos = defPos, size = defSiz,
- style = wx.CAPTION|wx.CLOSE_BOX|wx.DEFAULT_DIALOG_STYLE|
- wx.DIALOG_NO_PARENT|wx.MAXIMIZE_BOX|wx.MINIMIZE_BOX|
- wx.RESIZE_BORDER)
+ style = wx.CAPTION|wx.CLOSE_BOX|wx.DEFAULT_DIALOG_STYLE)
#======================================================================
# display an icon top left of dialog, append filename to title
#======================================================================
- self.SetIcon(img.GetIcon())
+ if do_icon:
+ self.SetIcon(ico_pdf.img.GetIcon()) # set a screen icon
self.SetTitle(self.Title + filename)
+ self.SetBackgroundColour(khaki)
#======================================================================
# open the document with MuPDF when dialog gets created
#======================================================================
- self.doc = fitz.Document(filename)
+ self.doc = fitz.open(filename) # create Document object
+ if self.doc.needsPass: # check password protection
+ self.decrypt_doc()
+ if self.doc.isEncrypted: # quit if we cannot decrpt
+ self.Destroy()
+ return
+ self.last_page = -1 # memorize last page displayed
+ self.link_rects = [] # store link rectangles here
+ self.link_texts = [] # store link texts here
+ self.current_idx = -1 # store entry of found rectangle
+ self.current_lnks = [] # store entry of found rectangle
+ self.cursor_hand = wx.StockCursor(wx.CURSOR_HAND)
+ self.cursor_norm = wx.StockCursor(wx.CURSOR_DEFAULT)
#======================================================================
# define zooming matrix for displaying PDF page images
# we increase images by 20%, so take 1.2 as scale factors
#======================================================================
- self.matrix = fitz.Matrix(1, 1).preScale(1.2, 1.2)
+ self.matrix = fitz.Matrix(zoom, zoom) # will use a constant zoom
'''
=======================================================================
@@ -128,31 +133,42 @@
# forward button
#======================================================================
self.ButtonNext = wx.Button(self, wx.ID_ANY, u"forw",
- defPos, defSiz, 0)
+ defPos, defSiz, wx.BU_EXACTFIT)
szr20.Add(self.ButtonNext, 0, wx.ALL, 5)
#======================================================================
# backward button
#======================================================================
self.ButtonPrevious = wx.Button(self, wx.ID_ANY, u"back",
- defPos, defSiz, 0)
+ defPos, defSiz, wx.BU_EXACTFIT)
szr20.Add(self.ButtonPrevious, 0, wx.ALL, 5)
#======================================================================
# text field for entering a target page. wx.TE_PROCESS_ENTER is
# required to get data entry fired as events.
#======================================================================
- self.TextToPage = wx.TextCtrl(self, wx.ID_ANY, u"1", defPos, defSiz,
- wx.TE_PROCESS_ENTER)
+ self.TextToPage = wx.TextCtrl(self, wx.ID_ANY, u"1", defPos,
+ wx.Size(40, -1),
+ wx.TE_RIGHT|wx.TE_PROCESS_ENTER)
szr20.Add(self.TextToPage, 0, wx.ALL, 5)
#======================================================================
- # displays total pages
+ # displays total pages and page paper format
#======================================================================
self.statPageMax = wx.StaticText(self, wx.ID_ANY,
- str(self.doc.pageCount), defPos, defSiz, 0)
+ "of " + str(len(self.doc)) + " pages.",
+ defPos, defSiz, 0)
szr20.Add(self.statPageMax, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
+ self.links = wx.CheckBox( self, wx.ID_ANY,
+ u"show links",
+ defPos, defSiz, wx.ALIGN_LEFT)
+ szr20.Add( self.links, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 )
+
+ self.paperform = wx.StaticText(self, wx.ID_ANY,
+ "", defPos, defSiz, 0)
+ szr20.Add(self.paperform, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
+
#======================================================================
# sizer ready, represents top dialog line
#======================================================================
@@ -162,8 +178,8 @@
# define the area for page images and load page 1 for primary display
#======================================================================
self.PDFimage = wx.StaticBitmap(self, wx.ID_ANY, self.pdf_show(1),
- defPos, defSiz, wx.NO_BORDER)
- self.szr10.Add(self.PDFimage, 0, wx.ALL, 0)
+ defPos, defSiz, 0)
+ self.szr10.Add(self.PDFimage, 0, wx.ALL, 5)
#======================================================================
# main sizer now ready - request final size & layout adjustments
@@ -183,15 +199,89 @@
self.ButtonNext.Bind(wx.EVT_BUTTON, self.NextPage)
self.ButtonPrevious.Bind(wx.EVT_BUTTON, self.PreviousPage)
self.TextToPage.Bind(wx.EVT_TEXT_ENTER, self.GotoPage)
+ self.PDFimage.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
+ self.PDFimage.Bind(wx.EVT_MOTION, self.move_mouse)
+ self.PDFimage.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
def __del__(self):
pass
#==============================================================================
-# Button handlers
-#==============================================================================
+# Button handlers and other functions
+#==============================================================================
+ def OnLeftDown(self, evt):
+ if self.current_idx < 0:
+ evt.Skip()
+ return
+ lnk = self.current_lnks[self.current_idx]
+ if lnk["kind"] == fitz.LINK_GOTO:
+ self.TextToPage.Value = str(lnk["page"] + 1)
+ self.GotoPage(evt)
+ elif lnk["kind"] == fitz.LINK_URI:
+ import webbrowser
+ try:
+ webbrowser.open_new(self.link_texts[self.current_idx])
+ except:
+ pass
+ elif lnk["kind"] == fitz.LINK_GOTOR:
+ import subprocess
+ try:
+ subprocess.Popen(self.link_texts[self.current_idx])
+ except:
+ pass
+ elif lnk["kind"] == fitz.LINK_NAMED:
+ if lnk["name"] == "FirstPage":
+ self.TextToPage.Value = "1"
+ elif lnk["name"] == "LastPage":
+ self.TextToPage.Value = str(len(self.doc))
+ elif lnk["name"] == "NextPage":
+ self.TextToPage.Value = str(int(self.TextToPage.Value) + 1)
+ elif lnk["name"] == "PrevPage":
+ self.TextToPage.Value = str(int(self.TextToPage.Value) - 1)
+ self.GotoPage(evt)
+ evt.Skip()
+ return
+
+ def cursor_in_rect(self, pos, rect): # is mouse in a hot area?
+ # check whether cursor is in rectangle
+ if (rect[0] <= pos.x <= (rect[0] + rect[2])) and \
+ (rect[1] <= pos.y <= (rect[1] + rect[3])):
+ return True
+ return False
+
+ def move_mouse(self, evt): # show hand if in a rectangle
+ if not self.links.Value: # do not process links
+ evt.Skip()
+ return
+ pos = evt.GetPosition()
+ in_rect = False
+ for i, rect in enumerate(self.link_rects):
+ if self.cursor_in_rect(pos, rect):
+ in_rect = True
+ self.current_idx = i
+ break
+ if in_rect:
+ self.PDFimage.SetCursor(self.cursor_hand)
+ self.PDFimage.SetToolTipString(self.link_texts[i])
+ self.current_idx = i
+ else:
+ self.PDFimage.SetCursor(self.cursor_norm)
+ self.PDFimage.UnsetToolTip()
+ self.current_idx = -1
+ evt.Skip()
+ return
+
+ def OnMouseWheel(self, evt):
+ # process wheel as paging operations
+ d = evt.GetWheelRotation() # int indicating direction
+ if d < 0:
+ self.NextPage(evt)
+ elif d > 0:
+ self.PreviousPage(evt)
+ return
+
def NextPage(self, event): # means: page forward
- page = int(self.TextToPage.Value) + 1 # current page + 1
+ page = getint(self.TextToPage.Value) + 1 # current page + 1
page = min(page, self.doc.pageCount) # cannot go beyond last page
self.TextToPage.Value = str(page) # put target page# in screen
self.bitmap = self.pdf_show(page) # get page image
@@ -199,15 +289,15 @@
event.Skip()
def PreviousPage(self, event): # means: page back
- page = int(self.TextToPage.Value) - 1 # current page - 1
+ page = getint(self.TextToPage.Value) - 1 # current page - 1
page = max(page, 1) # cannot go before page 1
self.TextToPage.Value = str(page) # put target page# in screen
self.NeuesImage(page)
event.Skip()
def GotoPage(self, event): # means: go to page number
- page = int(self.TextToPage.Value) # get page# from screen
- page = min(page, self.doc.pageCount) # cannot go beyond last page
+ page = getint(self.TextToPage.Value) # get page# from screen
+ page = min(page, len(self.doc)) # cannot go beyond last page
page = max(page, 1) # cannot go before page 1
self.TextToPage.Value = str(page) # make sure it's on the screen
self.NeuesImage(page)
@@ -217,36 +307,95 @@
# Read / render a PDF page. Parameters are: pdf = document, page = page#
#==============================================================================
def NeuesImage(self, page):
+ if page == self.last_page:
+ return
+ self.last_page = page
+ self.link_rects = []
+ self.link_texts = []
+ self.current_lnks = []
self.bitmap = self.pdf_show(page) # read page image
+ if self.links.Value: # show links?
+ self.draw_links(self.bitmap, page)
self.PDFimage.SetSize(self.bitmap.Size) # adjust screen to image size
self.PDFimage.SetBitmap(self.bitmap) # put it in screen
- self.PDFimage.Refresh(True) # refresh the image
- self.szr10.Fit(self)
- self.Layout() # and the layout
+ return
+
+ def draw_links(self, bmp, pno):
+ dc = wx.MemoryDC()
+ dc.SelectObject(bmp)
+ dc.SetPen(wx.Pen("BLUE", width=1))
+ dc.SetBrush(wx.Brush("BLUE", style=wx.BRUSHSTYLE_TRANSPARENT))
+ pg_w = self.pg_ir.x1 - self.pg_ir.x0
+ pg_h = self.pg_ir.y1 - self.pg_ir.y0
+ zoom_w = float(bmp.Size[0]) / float(pg_w)
+ zoom_h = float(bmp.Size[1]) / float(pg_h)
+ for lnk in self.current_lnks:
+ r = lnk["from"].round()
+ wx_r = wx.Rect(int(r.x0 * zoom_w),
+ int(r.y0 * zoom_h),
+ int((r.x1 - r.x0)*zoom_w),
+ int((r.y1 - r.y0)*zoom_h))
+ dc.DrawRectangle(wx_r[0], wx_r[1], wx_r[2], wx_r[3])
+ self.link_rects.append(wx_r)
+ if lnk["kind"] == fitz.LINK_GOTO:
+ txt = "page " + str(lnk["page"] + 1)
+ elif lnk["kind"] == fitz.LINK_GOTOR:
+ txt = lnk["file"]
+ elif lnk["kind"] == fitz.LINK_URI:
+ txt = lnk["uri"]
+ else:
+ txt = "unkown target"
+ self.link_texts.append(txt)
+ dc.SelectObject(wx.NullBitmap)
+ dc = None
return
def pdf_show(self, pg_nr):
page = self.doc.loadPage(int(pg_nr) - 1) # load the page & get Pixmap
- pix = page.getPixmap(matrix = self.matrix,
- colorspace = 'RGB')
- data = str(pix.samples) # point to pixel area
-
- data2 = "".join([data[4*i:4*i+3] for i in range(len(data)/4)])
- bitmap = wx.BitmapFromBuffer(pix.width, pix.height, data2)
-
- return bitmap
+ pix = page.getPixmap(matrix = self.matrix)
+ bmp = wx.BitmapFromBuffer(pix.w, pix.h, pix.samplesRGB())
+ if do_paper:
+ paper = FindFit(page.bound().x1, page.bound().y1)
+ else:
+ paper = "not implemented"
+ self.paperform.Label = "Page format: " + paper
+ if self.links.Value:
+ self.current_lnks = page.getLinks()
+ self.pg_ir = page.bound().round()
+ page = None
+ pix = None
+ return bmp
+
+ def decrypt_doc(self):
+ # let user enter document password
+ pw = None
+ dlg = wx.TextEntryDialog(self, 'Please enter password below:',
+ 'Document needs password to open', '',
+ style = wx.TextEntryDialogStyle|wx.TE_PASSWORD)
+ while pw is None:
+ rc = dlg.ShowModal()
+ if rc == wx.ID_OK:
+ pw = str(dlg.GetValue().encode("utf-8"))
+ self.doc.authenticate(pw)
+ else:
+ return
+ if self.doc.isEncrypted:
+ pw = None
+ dlg.SetTitle("Wrong password. Enter correct one or cancel.")
+ return
#==============================================================================
# main program
#==============================================================================
# start the wx application
+app = None
app = wx.App()
#==============================================================================
# Show a FileSelect dialog to choose a file for display
#==============================================================================
# Wildcard: offer all supported filetypes for display
-wild = "supported files|*.pdf;*.xps;*.oxps;*.epub"
+wild = "supported files|*.pdf;*.xps;*.oxps;*.epub;*.cbz"
#==============================================================================
# define the file selection dialog
@@ -255,11 +404,6 @@
defaultDir = os.path.expanduser("~"),
defaultFile = "",
wildcard = wild, style=wx.OPEN|wx.CHANGE_DIR)
-
-#==============================================================================
-# give an icon before we display it
-#==============================================================================
-dlg.SetIcon(img.GetIcon())
#==============================================================================
# now display and ask for return code in one go
@@ -274,9 +418,6 @@
# destroy this dialog
dlg.Destroy()
-if filename:
- if not os.path.exists(filename): # should not happen actually
- filename = None
#==============================================================================
# only continue if we have a filename
#==============================================================================