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
@@ -213,8 +210,8 @@ def create_fileobject(filename):
213210 self .pdfdoc = pypdfProcessor (self , pdf_file , self .ShowLoadProgress )
214211
215212 self .numpages = self .pdfdoc .numpages
216- self .pagewidth = self .pdfdoc .pagewidth
217- self . pageheight = self . pdfdoc . pageheight
213+ self .pagesizes = [ self .pdfdoc .GetPageSize ( i ) for i in range ( self . numpages )]
214+
218215 self .page_buffer_valid = False
219216 self .Scroll (0 , 0 ) # in case this is a re-LoadFile
220217 self .CalculateDimensions () # to get initial visible page range
@@ -298,12 +295,19 @@ def GoPage(self, pagenum):
298295 :param integer `pagenum`: go to the provided page number if it is valid
299296
300297 """
298+ # calling Scroll sometimes doesn't raise wx.EVT_SCROLLWIN eg Windows 8 64 bit - so
299+ wx .CallAfter (self .Render )
300+
301+ if not hasattr (self , "cumYpagespixels" ):
302+ return False # This could happen if the file is still loading
303+
301304 if pagenum > 0 and pagenum <= self .numpages :
302- self .Scroll (0 , pagenum * self .Ypagepixels // self .GetScrollPixelsPerUnit ()[1 ] + 1 )
305+ self .Scroll (0 , self .cumYpagespixels [pagenum - 1 ] //
306+ self .GetScrollPixelsPerUnit ()[1 ])
307+ return True
303308 else :
304309 self .Scroll (0 , 0 )
305- # calling Scroll sometimes doesn't raise wx.EVT_SCROLLWIN eg Windows 8 64 bit - so
306- wx .CallAfter (self .Render )
310+ return False
307311
308312 @property
309313 def ShowLoadProgress (self ):
@@ -338,53 +342,73 @@ def CalculateDimensions(self):
338342 if not have_cairo :
339343 self .font_scale_size = 1.0 / device_scale
340344
341- self .winwidth , self .winheight = self .GetClientSize ()
342- self .Ypage = self .pageheight + self .nom_page_gap
345+ clientSize = self .GetClientSize ()
346+ if clientSize .width < 5 or clientSize .height < 5 :
347+ return False # Window is too small to render
348+ self .winwidth , self .winheight = clientSize
349+ self .Ypages = [self .pagesizes [pageno ][1 ] + self .nom_page_gap
350+ for pageno in range (self .numpages )]
343351 if self .zoomscale > 0.0 :
344- self .scale = self .zoomscale * device_scale
352+ self .scales = [ self .zoomscale * device_scale ] * self . numpages
345353 else :
346354 if int (self .zoomscale ) == - 1 : # fit width
347- self .scale = self .winwidth / self .pagewidth
355+ self .scales = [self .winwidth / self .pagesizes [pageno ][0 ]
356+ for pageno in range (self .numpages )]
348357 else : # fit page
349- self .scale = self .winheight / self .pageheight
350- if self .scale == 0.0 : # this could happen if the window was not yet initialized
351- self .scale = 1.0
352- self .Xpagepixels = int (round (self .pagewidth * self .scale ))
353- self .Ypagepixels = int (round (self .Ypage * self .scale ))
354-
355- # adjust inter-page gap so Ypagepixels is a whole number of scroll increments
356- # and page numbers change precisely on a scroll click
357- idiv = self .Ypagepixels // self .scrollrate
358- nlo = idiv * self .scrollrate
359- nhi = (idiv + 1 ) * self .scrollrate
360- if nhi - self .Ypagepixels < self .Ypagepixels - nlo :
361- self .Ypagepixels = nhi
362- else :
363- self .Ypagepixels = nlo
364- self .page_gap = self .Ypagepixels / self .scale - self .pageheight
358+ self .scales = [self .winheight / self .pagesizes [pageno ][1 ]
359+ for pageno in range (self .numpages )]
360+ self .Xpagespixels = [int (round (self .pagesizes [pageno ][0 ] * self .scales [pageno ]))
361+ for pageno in range (self .numpages )]
362+
363+ self .Ypagespixels = [None ]* self .numpages
364+ self .page_gaps = [None ]* self .numpages
365+ for pageno in range (self .numpages ):
366+ Ypagepixels = int (round (self .Ypages [pageno ] * self .scales [pageno ]))
367+ # adjust Ypagespixels (total number of vertical pixels per page including bottom
368+ # inter-page gap so Ypagepixels is a whole number of scroll increments and pages
369+ # change precisely on a scroll click
370+ idiv = Ypagepixels // self .scrollrate
371+ nlo = idiv * self .scrollrate
372+ nhi = (idiv + 1 ) * self .scrollrate
373+ if nhi - Ypagepixels < Ypagepixels - nlo :
374+ self .Ypagespixels [pageno ] = nhi
375+ else :
376+ self .Ypagespixels [pageno ] = nlo
377+ self .page_gaps [pageno ] = (self .Ypagespixels [pageno ]/ self .scales [pageno ] -
378+ self .pagesizes [pageno ][1 ])
365379
366- self .maxwidth = max (self .winwidth , self .Xpagepixels )
367- self .maxheight = max (self .winheight , self .numpages * self .Ypagepixels )
380+ self .cumYpagespixels = list (itertools .accumulate (self .Ypagespixels ))
381+
382+ self .maxwidth = max (self .winwidth ,
383+ max (self .Xpagespixels [pageno ] for pageno in range (self .numpages )))
384+ self .maxheight = max (self .winheight , self .cumYpagespixels [- 1 ])
368385 self .SetVirtualSize ((self .maxwidth , self .maxheight ))
369386 self .SetScrollRate (self .scrollrate , self .scrollrate )
370387
371388 xv , yv = self .GetViewStart ()
372389 dx , dy = self .GetScrollPixelsPerUnit ()
390+
373391 self .x0 , self .y0 = (xv * dx , yv * dy )
374- self .frompage = int (min (self .y0 / self .Ypagepixels , self .numpages - 1 ))
375- self .topage = int (min ((self .y0 + self .winheight - 1 )/ self .Ypagepixels , self .numpages - 1 ))
376- self .pagebufferwidth = max (self .Xpagepixels , self .winwidth )
377- self .pagebufferheight = (self .topage - self .frompage + 1 ) * self .Ypagepixels
392+ self .frompage = max (bisect .bisect_left (self .cumYpagespixels , self .y0 + 1 ), 0 )
393+ self .topage = min (bisect .bisect_right (self .cumYpagespixels , self .y0 + self .winheight - 1 ),
394+ self .numpages - 1 )
395+ if self .frompage > self .topage :
396+ return False # Nothing to render. Can happen during initialization
397+
398+ self .page_x0 = 0
399+ self .pagebufferwidth = max (self .Xpagespixels [pageno ]
400+ for pageno in range (self .frompage , self .topage + 1 ))
401+ self .page_y0 = self .cumYpagespixels [self .frompage - 1 ] if self .frompage else 0
402+ self .pagebufferheight = self .cumYpagespixels [self .topage ] - self .page_y0
378403
379404 # Inform buttonpanel controls of any changes
380405 if self .buttonpanel :
381406 self .buttonpanel .Update (self .frompage , self .numpages ,
382- self .scale / device_scale )
383-
384- self .page_y0 = self .frompage * self .Ypagepixels
385- self .page_x0 = 0
407+ self .scales [self .frompage ]/ device_scale )
408+
386409 self .xshift = self .x0 - self .page_x0
387410 self .yshift = self .y0 - self .page_y0
411+
388412 if not self .page_buffer_valid : # via external setting
389413 self .cur_frompage = self .frompage
390414 self .cur_topage = self .topage
@@ -393,7 +417,8 @@ def CalculateDimensions(self):
393417 self .page_buffer_valid = False # due to page buffer change
394418 self .cur_frompage = self .frompage
395419 self .cur_topage = self .topage
396- return
420+
421+ return True
397422
398423 def Render (self ):
399424 """
@@ -407,7 +432,8 @@ def Render(self):
407432 """
408433 if not self .have_file :
409434 return
410- self .CalculateDimensions ()
435+ if not self .CalculateDimensions ():
436+ return # Invalid dimensions: Nothing to render
411437 if not self .page_buffer_valid :
412438 # Initialize the buffer bitmap.
413439 self .pagebuffer = wx .Bitmap (self .pagebufferwidth , self .pagebufferheight )
@@ -423,31 +449,40 @@ def Render(self):
423449 gc .FillPath (path )
424450
425451 for pageno in range (self .frompage , self .topage + 1 ):
452+ scale = self .scales [pageno ]
453+ pagegap = self .page_gaps [pageno ]
426454 self .xpageoffset = 0 - self .x0
427- self .ypageoffset = pageno * self .Ypagepixels - self .page_y0
455+ self .ypageoffset = (self .cumYpagespixels [pageno ] - self .Ypagespixels [pageno ] -
456+ self .page_y0 )
457+
428458 gc .PushState ()
429459 if mupdf :
430460 gc .Translate (self .xpageoffset , self .ypageoffset )
431461 # scaling is done inside RenderPage
432462 else :
433463
434464 gc .Translate (self .xpageoffset , self .ypageoffset +
435- self .pageheight * self .scale )
436- gc .Scale (self .scale , self .scale )
437- self .pdfdoc .RenderPage (gc , pageno , scale = self .scale )
438- # Show inter-page gap
439- gc .SetBrush (wx .Brush (wx .Colour (180 , 180 , 180 ))) #mid grey
465+ self .pagesizes [pageno ][1 ]* scale )
466+ gc .Scale (scale , scale )
467+ self .pdfdoc .RenderPage (gc , pageno , scale = scale )
468+
469+ # Show non-page areas as gray
470+ gc .PushState ()
471+ gc .SetBrush (wx .Brush (self .GetBackgroundColour ()))
440472 gc .SetPen (wx .TRANSPARENT_PEN )
441- if mupdf :
442- gc .DrawRectangle (0 , self .pageheight * self .scale ,
443- self .pagewidth * self .scale , self .page_gap * self .scale )
444- else :
445- gc .DrawRectangle (0 , 0 , self .pagewidth , self .page_gap )
446- gc .PopState ()
447- gc .PushState ()
448- gc .Translate (0 - self .x0 , 0 - self .page_y0 )
449- self .RenderPageBoundaries (gc )
450- gc .PopState ()
473+ gc .Scale (1.0 , 1.0 )
474+
475+ #inter-page gap
476+ gc .DrawRectangle (0 , self .pagesizes [pageno ][1 ]* scale ,
477+ self .pagesizes [pageno ][0 ]* scale , pagegap * scale )
478+ # gap to the right of the page
479+ extrawidth = self .winwidth - self .Xpagespixels [pageno ]
480+ if extrawidth > 0 :
481+ gc .DrawRectangle (self .pagesizes [pageno ][0 ]* scale , 0 ,
482+ extrawidth , self .Ypagespixels [pageno ])
483+ gc .PopState () # Pop non-page area
484+
485+ gc .PopState () # Pop page area
451486
452487 self .page_buffer_valid = True
453488 self .Refresh (0 ) # Blit appropriate area of new or existing page buffer to screen
@@ -457,20 +492,6 @@ def Render(self):
457492 self .GoPage (self .page_after_zoom_change )
458493 self .page_after_zoom_change = None
459494
460- def RenderPageBoundaries (self , gc ):
461- """
462- Show non-page areas in grey.
463- """
464- gc .SetBrush (wx .Brush (wx .Colour (180 , 180 , 180 ))) #mid grey
465- gc .SetPen (wx .TRANSPARENT_PEN )
466- gc .Scale (1.0 , 1.0 )
467- extrawidth = self .winwidth - self .Xpagepixels
468- if extrawidth > 0 :
469- gc .DrawRectangle (self .winwidth - extrawidth , 0 , extrawidth , self .maxheight )
470- extraheight = self .winheight - (self .numpages * self .Ypagepixels - self .y0 )
471- if extraheight > 0 :
472- gc .DrawRectangle (0 , self .winheight - extraheight , self .maxwidth , extraheight )
473-
474495#============================================================================
475496
476497class mupdfProcessor (object ):
@@ -503,15 +524,18 @@ def __init__(self, parent, pdf_file):
503524 self .numpages = self .pdfdoc .page_count
504525 except AttributeError : # old PyMuPDF version
505526 self .numpages = self .pdfdoc .pageCount
527+
528+ self .zoom_error = False #set if memory errors during render
529+
530+ def GetPageSize (self , pageNum ):
531+ """ Return width, height for the page """
506532 try :
507- page = self .pdfdoc .load_page (0 )
533+ page = self .pdfdoc .load_page (pageNum )
508534 except AttributeError : # old PyMuPDF version
509- page = self .pdfdoc .loadPage (0 )
510- self .pagewidth = page .bound ().width
511- self .pageheight = page .bound ().height
512- self .page_rect = page .bound ()
513- self .zoom_error = False #set if memory errors during render
514-
535+ page = self .pdfdoc .loadPage (pageNum )
536+ bound = page .bound ()
537+ return bound .width , bound .height
538+
515539 def DrawFile (self , frompage , topage ):
516540 """
517541 This is a no-op for mupdf. Each page is scaled and drawn on
@@ -555,9 +579,6 @@ def __init__(self, parent, fileobj, showloadprogress):
555579 self .showloadprogress = showloadprogress
556580 self .pdfdoc = PdfFileReader (fileobj )
557581 self .numpages = self .pdfdoc .getNumPages ()
558- page1 = self .pdfdoc .getPage (0 )
559- self .pagewidth = float (page1 .mediaBox .getUpperRight_x ())
560- self .pageheight = float (page1 .mediaBox .getUpperRight_y ())
561582 self .pagedrawings = {}
562583 self .unimplemented = {}
563584 self .formdrawings = {}
@@ -566,6 +587,10 @@ def __init__(self, parent, fileobj, showloadprogress):
566587 self .saved_state = None
567588 self .knownfont = False
568589 self .progbar = None
590+
591+ def GetPageSize (self , pageNum ):
592+ mediaBox = self .pdfdoc .getPage (pageNum ).mediaBox
593+ return float (mediaBox .getUpperRight_x ()), float (mediaBox .getUpperRight_y ())
569594
570595 # These methods interpret the PDF contents as a set of drawing commands
571596
@@ -1078,8 +1103,8 @@ def OnPrintPage(self, page):
10781103 if mupdf :
10791104 sfac = 4.0
10801105 pageno = page - 1 # zero based
1081- width = self .view .pagewidth
1082- height = self .view .pageheight
1106+ width = self .view .pagesizes [ pageno ][ 0 ]
1107+ height = self .view .pagesizes [ pageno ][ 1 ]
10831108 self .FitThisSizeToPage (wx .Size (int (width * sfac ), int (height * sfac )))
10841109 dc = self .GetDC ()
10851110 gc = wx .GraphicsContext .Create (dc )
0 commit comments