Printing framework - Part 8 (Phoenix)

Keywords : PyMuPDF, Viewer, Reading, PDF, XPS, OpenXPS, Epub, Comic and fiction book formats.


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

img_sample_one.png

   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.pageCount)     # 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].getDisplayList()
 377         dl = self.dl_array[pno]
 378         pix = dl.getPixmap(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].getLinks()
 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

source.zip


Additional Information

Link :

https://wiki.wxpython.org/MoreCommentsOnPrinting

https://wiki.wxpython.org/PrintingWithReportGenerators

http://www.blog.pythonlibrary.org/2012/06/17/an-intro-to-rst2pdf-changing-restructured-text-into-pdfs-with-python/

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

https://docs.wxpython.org/


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).


Comments

- blah, blah, blah...

Printing framework - Part 8 (Phoenix) (last edited 2020-10-07 10:42:07 by Ecco)

NOTE: To edit pages in this wiki you must be a member of the TrustedEditorsGroup.