Skip to content

Commit 7848269

Browse files
committed
Refactor python only pdfviewer to support displaying pdf files where not all pages have the same size
1 parent 11c4a77 commit 7848269

File tree

1 file changed

+109
-84
lines changed

1 file changed

+109
-84
lines changed

wx/lib/pdfviewer/viewer.py

Lines changed: 109 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,8 @@
2323
This module provides the :class:`~wx.lib.pdfviewer.viewer.pdfViewer` to view PDF
2424
files.
2525
"""
26-
27-
import sys
28-
import os
29-
import time
30-
import types
26+
import bisect
27+
import itertools
3128
import copy
3229
import shutil
3330
from 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

473494
class 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

Comments
 (0)