2323This module provides the :class:`~wx.lib.pdfviewer.viewer.pdfViewer` to view PDF
2424files.
2525"""
26-
27- import sys
28- import os
29- import time
30- import types
26+ import bisect
27+ import itertools
3128import copy
3229import shutil
3330from six import BytesIO , string_types
@@ -210,8 +207,8 @@ def create_fileobject(filename):
210207 self .pdfdoc = pypdfProcessor (self , pdf_file , self .ShowLoadProgress )
211208
212209 self .numpages = self .pdfdoc .numpages
213- self .pagewidth = self .pdfdoc .pagewidth
214- self . pageheight = self . pdfdoc . pageheight
210+ self .pagesizes = [ self .pdfdoc .GetPageSize ( i ) for i in range ( self . numpages )]
211+
215212 self .page_buffer_valid = False
216213 self .Scroll (0 , 0 ) # in case this is a re-LoadFile
217214 self .CalculateDimensions () # to get initial visible page range
@@ -295,12 +292,19 @@ def GoPage(self, pagenum):
295292 :param integer `pagenum`: go to the provided page number if it is valid
296293
297294 """
295+ # calling Scroll sometimes doesn't raise wx.EVT_SCROLLWIN eg Windows 8 64 bit - so
296+ wx .CallAfter (self .Render )
297+
298+ if not hasattr (self , "cumYpagespixels" ):
299+ return False # This could happen if the file is still loading
300+
298301 if pagenum > 0 and pagenum <= self .numpages :
299- self .Scroll (0 , pagenum * self .Ypagepixels // self .GetScrollPixelsPerUnit ()[1 ] + 1 )
302+ self .Scroll (0 , self .cumYpagespixels [pagenum - 1 ] //
303+ self .GetScrollPixelsPerUnit ()[1 ])
304+ return True
300305 else :
301306 self .Scroll (0 , 0 )
302- # calling Scroll sometimes doesn't raise wx.EVT_SCROLLWIN eg Windows 8 64 bit - so
303- wx .CallAfter (self .Render )
307+ return False
304308
305309 @property
306310 def ShowLoadProgress (self ):
@@ -335,53 +339,73 @@ def CalculateDimensions(self):
335339 if not have_cairo :
336340 self .font_scale_size = 1.0 / device_scale
337341
338- self .winwidth , self .winheight = self .GetClientSize ()
339- self .Ypage = self .pageheight + self .nom_page_gap
342+ clientSize = self .GetClientSize ()
343+ if clientSize .width < 5 or clientSize .height < 5 :
344+ return False # Window is too small to render
345+ self .winwidth , self .winheight = clientSize
346+ self .Ypages = [self .pagesizes [pageno ][1 ] + self .nom_page_gap
347+ for pageno in range (self .numpages )]
340348 if self .zoomscale > 0.0 :
341- self .scale = self .zoomscale * device_scale
349+ self .scales = [ self .zoomscale * device_scale ] * self . numpages
342350 else :
343351 if int (self .zoomscale ) == - 1 : # fit width
344- self .scale = self .winwidth / self .pagewidth
352+ self .scales = [self .winwidth / self .pagesizes [pageno ][0 ]
353+ for pageno in range (self .numpages )]
345354 else : # fit page
346- self .scale = self .winheight / self .pageheight
347- if self .scale == 0.0 : # this could happen if the window was not yet initialized
348- self .scale = 1.0
349- self .Xpagepixels = int (round (self .pagewidth * self .scale ))
350- self .Ypagepixels = int (round (self .Ypage * self .scale ))
351-
352- # adjust inter-page gap so Ypagepixels is a whole number of scroll increments
353- # and page numbers change precisely on a scroll click
354- idiv = self .Ypagepixels // self .scrollrate
355- nlo = idiv * self .scrollrate
356- nhi = (idiv + 1 ) * self .scrollrate
357- if nhi - self .Ypagepixels < self .Ypagepixels - nlo :
358- self .Ypagepixels = nhi
359- else :
360- self .Ypagepixels = nlo
361- self .page_gap = self .Ypagepixels / self .scale - self .pageheight
355+ self .scales = [self .winheight / self .pagesizes [pageno ][1 ]
356+ for pageno in range (self .numpages )]
357+ self .Xpagespixels = [int (round (self .pagesizes [pageno ][0 ] * self .scales [pageno ]))
358+ for pageno in range (self .numpages )]
359+
360+ self .Ypagespixels = [None ]* self .numpages
361+ self .page_gaps = [None ]* self .numpages
362+ for pageno in range (self .numpages ):
363+ Ypagepixels = int (round (self .Ypages [pageno ] * self .scales [pageno ]))
364+ # adjust Ypagespixels (total number of vertical pixels per page including bottom
365+ # inter-page gap so Ypagepixels is a whole number of scroll increments and pages
366+ # change precisely on a scroll click
367+ idiv = Ypagepixels // self .scrollrate
368+ nlo = idiv * self .scrollrate
369+ nhi = (idiv + 1 ) * self .scrollrate
370+ if nhi - Ypagepixels < Ypagepixels - nlo :
371+ self .Ypagespixels [pageno ] = nhi
372+ else :
373+ self .Ypagespixels [pageno ] = nlo
374+ self .page_gaps [pageno ] = (self .Ypagespixels [pageno ]/ self .scales [pageno ] -
375+ self .pagesizes [pageno ][1 ])
362376
363- self .maxwidth = max (self .winwidth , self .Xpagepixels )
364- self .maxheight = max (self .winheight , self .numpages * self .Ypagepixels )
377+ self .cumYpagespixels = list (itertools .accumulate (self .Ypagespixels ))
378+
379+ self .maxwidth = max (self .winwidth ,
380+ max (self .Xpagespixels [pageno ] for pageno in range (self .numpages )))
381+ self .maxheight = max (self .winheight , self .cumYpagespixels [- 1 ])
365382 self .SetVirtualSize ((self .maxwidth , self .maxheight ))
366383 self .SetScrollRate (self .scrollrate , self .scrollrate )
367384
368385 xv , yv = self .GetViewStart ()
369386 dx , dy = self .GetScrollPixelsPerUnit ()
387+
370388 self .x0 , self .y0 = (xv * dx , yv * dy )
371- self .frompage = int (min (self .y0 / self .Ypagepixels , self .numpages - 1 ))
372- self .topage = int (min ((self .y0 + self .winheight - 1 )/ self .Ypagepixels , self .numpages - 1 ))
373- self .pagebufferwidth = max (self .Xpagepixels , self .winwidth )
374- self .pagebufferheight = (self .topage - self .frompage + 1 ) * self .Ypagepixels
389+ self .frompage = max (bisect .bisect_left (self .cumYpagespixels , self .y0 + 1 ), 0 )
390+ self .topage = min (bisect .bisect_right (self .cumYpagespixels , self .y0 + self .winheight - 1 ),
391+ self .numpages - 1 )
392+ if self .frompage > self .topage :
393+ return False # Nothing to render. Can happen during initialization
394+
395+ self .page_x0 = 0
396+ self .pagebufferwidth = max (self .Xpagespixels [pageno ]
397+ for pageno in range (self .frompage , self .topage + 1 ))
398+ self .page_y0 = self .cumYpagespixels [self .frompage - 1 ] if self .frompage else 0
399+ self .pagebufferheight = self .cumYpagespixels [self .topage ] - self .page_y0
375400
376401 # Inform buttonpanel controls of any changes
377402 if self .buttonpanel :
378403 self .buttonpanel .Update (self .frompage , self .numpages ,
379- self .scale / device_scale )
380-
381- self .page_y0 = self .frompage * self .Ypagepixels
382- self .page_x0 = 0
404+ self .scales [self .frompage ]/ device_scale )
405+
383406 self .xshift = self .x0 - self .page_x0
384407 self .yshift = self .y0 - self .page_y0
408+
385409 if not self .page_buffer_valid : # via external setting
386410 self .cur_frompage = self .frompage
387411 self .cur_topage = self .topage
@@ -390,7 +414,8 @@ def CalculateDimensions(self):
390414 self .page_buffer_valid = False # due to page buffer change
391415 self .cur_frompage = self .frompage
392416 self .cur_topage = self .topage
393- return
417+
418+ return True
394419
395420 def Render (self ):
396421 """
@@ -404,7 +429,8 @@ def Render(self):
404429 """
405430 if not self .have_file :
406431 return
407- self .CalculateDimensions ()
432+ if not self .CalculateDimensions ():
433+ return # Invalid dimensions: Nothing to render
408434 if not self .page_buffer_valid :
409435 # Initialize the buffer bitmap.
410436 self .pagebuffer = wx .Bitmap (self .pagebufferwidth , self .pagebufferheight )
@@ -420,31 +446,40 @@ def Render(self):
420446 gc .FillPath (path )
421447
422448 for pageno in range (self .frompage , self .topage + 1 ):
449+ scale = self .scales [pageno ]
450+ pagegap = self .page_gaps [pageno ]
423451 self .xpageoffset = 0 - self .x0
424- self .ypageoffset = pageno * self .Ypagepixels - self .page_y0
452+ self .ypageoffset = (self .cumYpagespixels [pageno ] - self .Ypagespixels [pageno ] -
453+ self .page_y0 )
454+
425455 gc .PushState ()
426456 if mupdf :
427457 gc .Translate (self .xpageoffset , self .ypageoffset )
428458 # scaling is done inside RenderPage
429459 else :
430460
431461 gc .Translate (self .xpageoffset , self .ypageoffset +
432- self .pageheight * self .scale )
433- gc .Scale (self .scale , self .scale )
434- self .pdfdoc .RenderPage (gc , pageno , scale = self .scale )
435- # Show inter-page gap
436- gc .SetBrush (wx .Brush (wx .Colour (180 , 180 , 180 ))) #mid grey
462+ self .pagesizes [pageno ][1 ]* scale )
463+ gc .Scale (scale , scale )
464+ self .pdfdoc .RenderPage (gc , pageno , scale = scale )
465+
466+ # Show non-page areas as gray
467+ gc .PushState ()
468+ gc .SetBrush (wx .Brush (self .GetBackgroundColour ()))
437469 gc .SetPen (wx .TRANSPARENT_PEN )
438- if mupdf :
439- gc .DrawRectangle (0 , self .pageheight * self .scale ,
440- self .pagewidth * self .scale , self .page_gap * self .scale )
441- else :
442- gc .DrawRectangle (0 , 0 , self .pagewidth , self .page_gap )
443- gc .PopState ()
444- gc .PushState ()
445- gc .Translate (0 - self .x0 , 0 - self .page_y0 )
446- self .RenderPageBoundaries (gc )
447- gc .PopState ()
470+ gc .Scale (1.0 , 1.0 )
471+
472+ #inter-page gap
473+ gc .DrawRectangle (0 , self .pagesizes [pageno ][1 ]* scale ,
474+ self .pagesizes [pageno ][0 ]* scale , pagegap * scale )
475+ # gap to the right of the page
476+ extrawidth = self .winwidth - self .Xpagespixels [pageno ]
477+ if extrawidth > 0 :
478+ gc .DrawRectangle (self .pagesizes [pageno ][0 ]* scale , 0 ,
479+ extrawidth , self .Ypagespixels [pageno ])
480+ gc .PopState () # Pop non-page area
481+
482+ gc .PopState () # Pop page area
448483
449484 self .page_buffer_valid = True
450485 self .Refresh (0 ) # Blit appropriate area of new or existing page buffer to screen
@@ -454,20 +489,6 @@ def Render(self):
454489 self .GoPage (self .page_after_zoom_change )
455490 self .page_after_zoom_change = None
456491
457- def RenderPageBoundaries (self , gc ):
458- """
459- Show non-page areas in grey.
460- """
461- gc .SetBrush (wx .Brush (wx .Colour (180 , 180 , 180 ))) #mid grey
462- gc .SetPen (wx .TRANSPARENT_PEN )
463- gc .Scale (1.0 , 1.0 )
464- extrawidth = self .winwidth - self .Xpagepixels
465- if extrawidth > 0 :
466- gc .DrawRectangle (self .winwidth - extrawidth , 0 , extrawidth , self .maxheight )
467- extraheight = self .winheight - (self .numpages * self .Ypagepixels - self .y0 )
468- if extraheight > 0 :
469- gc .DrawRectangle (0 , self .winheight - extraheight , self .maxwidth , extraheight )
470-
471492#============================================================================
472493
473494class mupdfProcessor (object ):
@@ -500,15 +521,18 @@ def __init__(self, parent, pdf_file):
500521 self .numpages = self .pdfdoc .page_count
501522 except AttributeError : # old PyMuPDF version
502523 self .numpages = self .pdfdoc .pageCount
524+
525+ self .zoom_error = False #set if memory errors during render
526+
527+ def GetPageSize (self , pageNum ):
528+ """ Return width, height for the page """
503529 try :
504- page = self .pdfdoc .load_page (0 )
530+ page = self .pdfdoc .load_page (pageNum )
505531 except AttributeError : # old PyMuPDF version
506- page = self .pdfdoc .loadPage (0 )
507- self .pagewidth = page .bound ().width
508- self .pageheight = page .bound ().height
509- self .page_rect = page .bound ()
510- self .zoom_error = False #set if memory errors during render
511-
532+ page = self .pdfdoc .loadPage (pageNum )
533+ bound = page .bound ()
534+ return bound .width , bound .height
535+
512536 def DrawFile (self , frompage , topage ):
513537 """
514538 This is a no-op for mupdf. Each page is scaled and drawn on
@@ -552,9 +576,6 @@ def __init__(self, parent, fileobj, showloadprogress):
552576 self .showloadprogress = showloadprogress
553577 self .pdfdoc = PdfFileReader (fileobj )
554578 self .numpages = self .pdfdoc .getNumPages ()
555- page1 = self .pdfdoc .getPage (0 )
556- self .pagewidth = float (page1 .mediaBox .getUpperRight_x ())
557- self .pageheight = float (page1 .mediaBox .getUpperRight_y ())
558579 self .pagedrawings = {}
559580 self .unimplemented = {}
560581 self .formdrawings = {}
@@ -563,6 +584,10 @@ def __init__(self, parent, fileobj, showloadprogress):
563584 self .saved_state = None
564585 self .knownfont = False
565586 self .progbar = None
587+
588+ def GetPageSize (self , pageNum ):
589+ mediaBox = self .pdfdoc .getPage (pageNum ).mediaBox
590+ return float (mediaBox .getUpperRight_x ()), float (mediaBox .getUpperRight_y ())
566591
567592 # These methods interpret the PDF contents as a set of drawing commands
568593
@@ -1075,8 +1100,8 @@ def OnPrintPage(self, page):
10751100 if mupdf :
10761101 sfac = 4.0
10771102 pageno = page - 1 # zero based
1078- width = self .view .pagewidth
1079- height = self .view .pageheight
1103+ width = self .view .pagesizes [ pageno ][ 0 ]
1104+ height = self .view .pagesizes [ pageno ][ 1 ]
10801105 self .FitThisSizeToPage (wx .Size (int (width * sfac ), int (height * sfac )))
10811106 dc = self .GetDC ()
10821107 gc = wx .GraphicsContext .Create (dc )
0 commit comments