How to use printing framework - Part 8 (Phoenix)
Keywords : PyMuPDF, Viewer, Reading, PDF, XPS, OpenXPS, Epub, Comic and fiction book formats.
Contents
Demonstrating :
Tested py3.x, wx4.x and Win10.
Printing is an essential element for your programs, here we show you how to print.
Are you ready to use some samples ?
Test, modify, correct, complete, improve and share your discoveries !
PyMuPDF(Ruikai Liu / Jorj X. McKie)
MuPDF can access files in PDF, XPS, OpenXPS, epub, comic and fiction book formats, and it is known for both, its top performance and high rendering quality.
With PyMuPDF you therefore can access files with extensions *.pdf, *.xps, *.oxps, *.epub, *.cbz or *.fb2 from your Python scripts. A number of popular image formats is supported as well, including multi-page TIFF images.
PyMuPDF should run on all platforms that are supported by both, MuPDF and Python. These include, but are not limited to, Windows (XP/SP2 and up), Mac OSX and Linux, 32-bit or 64-bit. If you can generate MuPDF on a Python supported platform, then also PyMuPDF can be used there.
You must install Python bindings for the PDF rendering library MuPDF before use it :
pip install PyMuPDF
1 # sample_one.py
2
3 """
4
5 @created: 2015-10-23 13:40:00
6
7 @author: Jorj X. McKie
8
9 Let the user select a PyMuPDF-supported file and then scroll through it.
10
11 Dependencies:
12 PyMuPDF, wxPython 3.x
13
14 License:
15 GNU GPL 3.x, GNU AFFERO GPL 3
16
17 Changes in PyMuPDF 1.9.2
18 ------------------------
19 - supporting Python 3 as wxPython now supports it
20 - optionally show links in displayed pages
21 - when clicking on a link, attempt to follow it
22 - remove inline code for dialog icon and import from a library
23
24 Changes in PyMuPDF 1.8.0
25 ------------------------
26 - display a fancy icon on the dialogs, defined as inline code in base64 format
27 - display pages with a zoom factor of 120%
28 - dynamically resize dialog to reflect each page's image size / orientation
29 - minor cosmetic changes
30
31 """
32
33 from __future__ import print_function
34 import sys
35 print("Python:", sys.version)
36
37 try:
38 import wx
39 print("wxPython:", wx.version())
40 except:
41 raise SystemExit(__file__ + " needs wxPython.")
42
43 try:
44 import fitz
45 print(fitz.__doc__)
46 except:
47 raise SystemExit(__file__ + " needs PyMuPDF(fitz).")
48
49 try:
50 from PageFormat import FindFit
51 except ImportError:
52 def FindFit(*args):
53 return "not implemented"
54
55 try:
56 from icons import ico_pdf # PDF icon in upper left screen corner
57 do_icon = True
58 except ImportError:
59 do_icon = False
60
61 app = None
62 app = wx.App()
63 assert wx.VERSION[0:3] >= (3,0,2), "need wxPython 3.0.2 or later"
64 assert tuple(map(int, fitz.VersionBind.split("."))) >= (1,9,2), "need PyMuPDF 1.9.2 or later"
65
66 # make some adjustments for differences between wxPython versions 3.0.2 / 3.0.3
67 if wx.VERSION[0:3] >= (3,0,3):
68 cursor_hand = wx.Cursor(wx.CURSOR_HAND)
69 cursor_norm = wx.Cursor(wx.CURSOR_DEFAULT)
70 bmp_buffer = wx.Bitmap.FromBuffer
71 phoenix = True
72 else:
73 cursor_hand = wx.StockCursor(wx.CURSOR_HAND)
74 cursor_norm = wx.StockCursor(wx.CURSOR_DEFAULT)
75 bmp_buffer = wx.BitmapFromBuffer
76 phoenix = False
77
78 if str != bytes:
79 stringtypes = (str, bytes)
80 else:
81 stringtypes = (str, unicode)
82
83 def getint(v):
84 try:
85 return int(v)
86 except:
87 pass
88 if type(v) not in stringtypes:
89 return 0
90 a = "0"
91 for d in v:
92 if d in "0123456789":
93 a += d
94 return int(a)
95
96 # abbreviations to get rid of those long pesky names ...
97 #==============================================================================
98 # Define our dialog as a subclass of wx.Dialog.
99 # Only special thing is, that we are being invoked with a filename ...
100 #==============================================================================
101 class PDFdisplay(wx.Dialog):
102 def __init__(self, parent, filename):
103 defPos = wx.DefaultPosition
104 defSiz = wx.DefaultSize
105 zoom = 1.2 # zoom factor of display
106 wx.Dialog.__init__ (self, parent, id = wx.ID_ANY,
107 title = u"Display with PyMuPDF: ",
108 pos = defPos, size = defSiz,
109 style = wx.CAPTION|wx.CLOSE_BOX|
110 wx.DEFAULT_DIALOG_STYLE)
111
112 #======================================================================
113 # display an icon top left of dialog, append filename to title
114 #======================================================================
115 frameicon = wx.Icon("Icons/wxWidgets.ico")
116 self.SetIcon(frameicon)
117
118 #------------
119
120 if do_icon:
121 self.SetIcon(ico_pdf.img.GetIcon()) # set a screen icon
122 self.SetTitle(self.Title + filename)
123 self.SetBackgroundColour(wx.Colour(240, 230, 140))
124
125 #======================================================================
126 # open the document with MuPDF when dialog gets created
127 #======================================================================
128 self.doc = fitz.open(filename) # create Document object
129 # if self.doc.needsPass: # check password protection
130 # self.decrypt_doc()
131 # if self.doc.isEncrypted: # quit if we cannot decrpt
132 # self.Destroy()
133 # return
134 self.dl_array = [0] * len(self.doc)
135 self.last_page = -1 # memorize last page displayed
136 self.link_rects = [] # store link rectangles here
137 self.link_texts = [] # store link texts here
138 self.current_idx = -1 # store entry of found rectangle
139 self.current_lnks = [] # store entry of found rectangle
140
141 #======================================================================
142 # define zooming matrix for displaying PDF page images
143 # we increase images by 20%, so take 1.2 as scale factors
144 #======================================================================
145 self.matrix = fitz.Matrix(zoom, zoom) # will use a constant zoom
146
147 '''
148 =======================================================================
149 Overall Dialog Structure:
150 -------------------------
151 szr10 (main sizer for the whole dialog - vertical orientation)
152 +-> szr20 (sizer for buttons etc. - horizontal orientation)
153 +-> button forward
154 +-> button backward
155 +-> field for page number to jump to
156 +-> field displaying total pages
157 +-> PDF image area
158 =======================================================================
159 '''
160
161 # forward button
162 self.ButtonNext = wx.Button(self, wx.ID_ANY, u"forw",
163 defPos, defSiz, wx.BU_EXACTFIT)
164 # backward button
165 self.ButtonPrevious = wx.Button(self, wx.ID_ANY, u"back",
166 defPos, defSiz, wx.BU_EXACTFIT)
167 #======================================================================
168 # text field for entering a target page. wx.TE_PROCESS_ENTER is
169 # required to get data entry fired as events.
170 #======================================================================
171 self.TextToPage = wx.TextCtrl(self, wx.ID_ANY, u"1", defPos, wx.Size(40, -1),
172 wx.TE_RIGHT|wx.TE_PROCESS_ENTER)
173 # displays total pages and page paper format
174 self.statPageMax = wx.StaticText(self, wx.ID_ANY,
175 "of " + str(len(self.doc)) + " pages.",
176 defPos, defSiz, 0)
177 self.links = wx.CheckBox( self, wx.ID_ANY, u"show links",
178 defPos, defSiz, wx.ALIGN_LEFT)
179 self.links.Value = True
180 self.paperform = wx.StaticText(self, wx.ID_ANY, "", defPos, defSiz, 0)
181 # define the area for page images and load page 1 for primary display
182 self.PDFimage = wx.StaticBitmap(self, wx.ID_ANY, self.pdf_show(1),
183 defPos, defSiz, style = 0)
184 #======================================================================
185 # the main sizer of the dialog
186 #======================================================================
187 self.szr10 = wx.BoxSizer(wx.VERTICAL)
188 szr20 = wx.BoxSizer(wx.HORIZONTAL)
189 szr20.Add(self.ButtonNext, 0, wx.ALL, 5)
190 szr20.Add(self.ButtonPrevious, 0, wx.ALL, 5)
191 szr20.Add(self.TextToPage, 0, wx.ALL, 5)
192 szr20.Add(self.statPageMax, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
193 szr20.Add( self.links, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 )
194 szr20.Add(self.paperform, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
195 # sizer ready, represents top dialog line
196 self.szr10.Add(szr20, 0, wx.EXPAND, 5)
197 self.szr10.Add(self.PDFimage, 0, wx.ALL, 5)
198 # main sizer now ready - request final size & layout adjustments
199 self.szr10.Fit(self)
200 self.SetSizer(self.szr10)
201 self.Layout()
202 # center dialog on screen
203 self.Centre(wx.BOTH)
204
205 # Bind buttons and fields to event handlers
206 self.ButtonNext.Bind(wx.EVT_BUTTON, self.NextPage)
207 self.ButtonPrevious.Bind(wx.EVT_BUTTON, self.PreviousPage)
208 self.TextToPage.Bind(wx.EVT_TEXT_ENTER, self.GotoPage)
209 self.PDFimage.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
210 self.PDFimage.Bind(wx.EVT_MOTION, self.move_mouse)
211 self.PDFimage.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
212
213 #==============================================================================
214 # Button handlers and other functions
215 #==============================================================================
216 def OnLeftDown(self, evt):
217 if self.current_idx < 0 or not self.links.Value:
218 evt.Skip()
219 return
220 lnk = self.current_lnks[self.current_idx]
221 if lnk["kind"] == fitz.LINK_GOTO:
222 self.TextToPage.Value = str(lnk["page"] + 1)
223 self.GotoPage(evt)
224 elif lnk["kind"] == fitz.LINK_URI:
225 import webbrowser
226 try:
227 webbrowser.open_new(self.link_texts[self.current_idx])
228 except:
229 pass
230 elif lnk["kind"] == fitz.LINK_GOTOR:
231 import subprocess
232 try:
233 subprocess.Popen(self.link_texts[self.current_idx])
234 except:
235 pass
236 elif lnk["kind"] == fitz.LINK_NAMED:
237 if lnk["name"] == "FirstPage":
238 self.TextToPage.Value = "1"
239 elif lnk["name"] == "LastPage":
240 self.TextToPage.Value = str(len(self.doc))
241 elif lnk["name"] == "NextPage":
242 self.TextToPage.Value = str(int(self.TextToPage.Value) + 1)
243 elif lnk["name"] == "PrevPage":
244 self.TextToPage.Value = str(int(self.TextToPage.Value) - 1)
245 self.GotoPage(evt)
246 evt.Skip()
247 return
248
249 def move_mouse(self, evt): # show hand if in a rectangle
250 if not self.links.Value: # do not process links
251 evt.Skip()
252 return
253 if len(self.link_rects) == 0:
254 evt.Skip()
255 return
256 pos = evt.GetPosition()
257 self.current_idx = self.cursor_in_link(pos) # get cursor link rect
258
259 if self.current_idx >= 0: # if in a hot area
260 self.PDFimage.SetCursor(cursor_hand)
261 if phoenix:
262 self.PDFimage.SetToolTip(self.link_texts[self.current_idx])
263 else:
264 self.PDFimage.SetToolTipString(self.link_texts[self.current_idx])
265 else:
266 self.PDFimage.SetCursor(cursor_norm)
267 self.PDFimage.UnsetToolTip()
268
269 evt.Skip()
270 return
271
272 def OnMouseWheel(self, evt):
273 # process wheel as paging operations
274 d = evt.GetWheelRotation() # int indicating direction
275 if d < 0:
276 self.NextPage(evt)
277 elif d > 0:
278 self.PreviousPage(evt)
279 return
280
281 def NextPage(self, event): # means: page forward
282 page = getint(self.TextToPage.Value) + 1 # current page + 1
283 page = min(page, self.doc.page_count) # cannot go beyond last page
284 self.TextToPage.Value = str(page) # put target page# in screen
285 self.NeuesImage(page) # refresh the layout
286 event.Skip()
287
288 def PreviousPage(self, event): # means: page back
289 page = getint(self.TextToPage.Value) - 1 # current page - 1
290 page = max(page, 1) # cannot go before page 1
291 self.TextToPage.Value = str(page) # put target page# in screen
292 self.NeuesImage(page)
293 event.Skip()
294
295 def GotoPage(self, event): # means: go to page number
296 page = getint(self.TextToPage.Value) # get page# from screen
297 page = min(page, len(self.doc)) # cannot go beyond last page
298 page = max(page, 1) # cannot go before page 1
299 self.TextToPage.Value = str(page) # make sure it's on the screen
300 self.NeuesImage(page)
301 event.Skip()
302
303 #==============================================================================
304 # Read / render a PDF page. Parameters are: pdf = document, page = page#
305 #==============================================================================
306 def NeuesImage(self, page):
307 if page == self.last_page:
308 return
309 self.PDFimage.SetCursor(cursor_norm)
310 self.PDFimage.UnsetToolTip()
311 self.last_page = page
312 self.link_rects = []
313 self.link_texts = []
314 bitmap = self.pdf_show(page) # read page image
315 if self.links.Value and len(self.current_lnks) > 0: # show links?
316 self.draw_links(bitmap, page) # modify the bitmap
317 self.PDFimage.SetBitmap(bitmap) # put it in screen
318 self.szr10.Fit(self)
319 self.Layout()
320 # image may be truncated, so we need to recalculate hot areas
321 if len(self.current_lnks) > 0:
322 isize = self.PDFimage.Size
323 bsize = self.PDFimage.Bitmap.Size
324 dis_x = (bsize[0] - isize[0]) / 2.
325 dis_y = (bsize[1] - isize[1]) / 2.
326 zoom_w = float(bsize[0]) / float(self.pg_ir.width)
327 zoom_h = float(bsize[1]) / float(self.pg_ir.height)
328 for l in self.current_lnks:
329 r = l["from"]
330 wx_r = wx.Rect(int(r.x0 * zoom_w - dis_x),
331 int(r.y0 * zoom_h) - dis_y,
332 int(r.width * zoom_w),
333 int(r.height * zoom_h))
334 self.link_rects.append(wx_r)
335
336 return
337
338 def cursor_in_link(self, pos):
339 for i, r in enumerate(self.link_rects):
340 if r.Contains(pos):
341 return i
342 return -1
343
344 def draw_links(self, bmp, pno):
345 dc = wx.MemoryDC()
346 dc.SelectObject(bmp)
347 dc.SetPen(wx.Pen("BLUE", width=1))
348 dc.SetBrush(wx.Brush("BLUE", style=wx.BRUSHSTYLE_TRANSPARENT))
349 pg_w = self.pg_ir.x1 - self.pg_ir.x0
350 pg_h = self.pg_ir.y1 - self.pg_ir.y0
351 zoom_w = float(bmp.Size[0]) / float(pg_w)
352 zoom_h = float(bmp.Size[1]) / float(pg_h)
353 for lnk in self.current_lnks:
354 r = lnk["from"].irect
355 wx_r = wx.Rect(int(r.x0 * zoom_w),
356 int(r.y0 * zoom_h),
357 int(r.width * zoom_w),
358 int(r.height * zoom_h))
359 dc.DrawRectangle(wx_r[0], wx_r[1], wx_r[2]+1, wx_r[3]+1)
360 if lnk["kind"] == fitz.LINK_GOTO:
361 txt = "page " + str(lnk["page"] + 1)
362 elif lnk["kind"] == fitz.LINK_GOTOR:
363 txt = lnk["file"]
364 elif lnk["kind"] == fitz.LINK_URI:
365 txt = lnk["uri"]
366 else:
367 txt = "unkown destination"
368 self.link_texts.append(txt)
369 dc.SelectObject(wx.NullBitmap)
370 dc = None
371 return
372
373 def pdf_show(self, pg_nr):
374 pno = int(pg_nr) - 1
375 if self.dl_array[pno] == 0:
376 self.dl_array[pno] = self.doc[pno].get_displaylist()
377 dl = self.dl_array[pno]
378 pix = dl.get_pixmap(matrix = self.matrix, alpha = False)
379 bmp = bmp_buffer(pix.w, pix.h, pix.samples)
380 r = dl.rect
381 paper = FindFit(r.x1, r.y1)
382 self.paperform.Label = "Page format: " + paper
383 if self.links.Value:
384 self.current_lnks = self.doc[pno].get_links()
385 self.pg_ir = dl.rect.irect
386 pix = None
387 return bmp
388
389 def decrypt_doc(self):
390 # let user enter document password
391 pw = None
392 dlg = wx.TextEntryDialog(self, 'Please enter password below:',
393 'Document needs password to open', '',
394 style = wx.TextEntryDialogStyle|wx.TE_PASSWORD)
395 while pw is None:
396 rc = dlg.ShowModal()
397 if rc == wx.ID_OK:
398 pw = str(dlg.GetValue().encode("utf-8"))
399 self.doc.authenticate(pw)
400 else:
401 return
402 if self.doc.isEncrypted:
403 pw = None
404 dlg.SetTitle("Wrong password. Enter correct one or cancel.")
405 return
406
407 #==============================================================================
408 # main program
409 #------------------------------------------------------------------------------
410 # Show a standard FileSelect dialog to choose a file for display
411 #==============================================================================
412 # Wildcard: offer all supported filetypes for display
413 wild = "*.pdf;*.xps;*.oxps;*.epub;*.cbz;*.fb2"
414
415 #==============================================================================
416 # define the file selection dialog
417 #==============================================================================
418 dlg = wx.FileDialog(None, message = "Choose a file to display",
419 wildcard = wild, style=wx.FD_OPEN|wx.FD_CHANGE_DIR)
420
421 # We got a file only when one was selected and OK pressed
422 if dlg.ShowModal() == wx.ID_OK:
423 # This returns a Python list of selected files (we only have one though)
424 filename = dlg.GetPath()
425 else:
426 filename = None
427
428 # destroy this dialog
429 dlg.Destroy()
430
431 # only continue if we have a filename
432 if filename:
433 # create the dialog
434 dlg = PDFdisplay(None, filename)
435 # show it - this will only return for final housekeeping
436 rc = dlg.ShowModal()
437
438 app = None
Download source
Additional Information
Link :
https://wiki.wxpython.org/MoreCommentsOnPrinting
https://wiki.wxpython.org/PrintingWithReportGenerators
http://www.blog.pythonlibrary.org/2010/05/15/manipulating-pdfs-with-python-and-pypdf/
https://www.blog.pythonlibrary.org/2010/02/14/python-windows-and-printers/
- - - - -
https://wiki.wxpython.org/TitleIndex
Thanks to
Ruikai Liu / Jorj X. McKie (PyMuPDF).
About this page
Date (d/m/y) Person (bot) Comments :
10/10/18 - Ecco (Created page for wxPython Phoenix).
14/01/23 - Ecco (Updated page and "source.zip" for Python 3.10.5).
Comments
- blah, blah, blah...