Welcome, guest | Sign In | My Account | Store | Cart

This example shows a PDF Viewer class, which handles things like Zoom and Scrolling. It requires python-poppler and wxPython >= 2.8.9.

Python, 112 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
#!/usr/bin/env python
# coding: utf-8

""" 
    wxPDFViewer - Simple PDF Viewer using Python-Poppler and wxPython 
    Marcelo Fidel Fernandez - BSD License
    http://www.marcelofernandez.info - marcelo.fidel.fernandez@gmail.com
"""

import wx
import wx.lib.wxcairo as wxcairo
import sys
import poppler

 
class PDFWindow(wx.ScrolledWindow):
    """ This example class implements a PDF Viewer Window, handling Zoom and Scrolling """

    MAX_SCALE = 2
    MIN_SCALE = 1
    SCROLLBAR_UNITS = 20  # pixels per scrollbar unit

    def __init__(self, parent):
        wx.ScrolledWindow.__init__(self, parent, wx.ID_ANY)
        # Wrap a panel inside
        self.panel = wx.Panel(self)
        # Initialize variables
        self.n_page = 0
        self.scale = 1
        self.document = None
        self.n_pages = None
        self.current_page = None
        self.width = None
        self.height = None
        # Connect panel events
        self.panel.Bind(wx.EVT_PAINT, self.OnPaint)
        self.panel.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
        self.panel.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
        self.panel.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown)

    def LoadDocument(self, file):
        self.document = poppler.document_new_from_file("file://" + file, None)
        self.n_pages = self.document.get_n_pages()
        self.current_page = self.document.get_page(self.n_page)
        self.width, self.height = self.current_page.get_size() 
        self._UpdateSize()

    def OnPaint(self, event):
        dc = wx.PaintDC(self.panel)
        cr = wxcairo.ContextFromDC(dc)
        cr.set_source_rgb(1, 1, 1)  # White background
        if self.scale != 1:
            cr.scale(self.scale, self.scale)
        cr.rectangle(0, 0, self.width, self.height)
        cr.fill()
        self.current_page.render(cr)

    def OnLeftDown(self, event):
        self._UpdateScale(self.scale + 0.2)

    def OnRightDown(self, event):
        self._UpdateScale(self.scale - 0.2)

    def _UpdateScale(self, new_scale):
        if new_scale >= PDFWindow.MIN_SCALE and new_scale <= PDFWindow.MAX_SCALE:
            self.scale = new_scale
            # Obtain the current scroll position
            prev_position = self.GetViewStart() 
            # Scroll to the beginning because I'm going to redraw all the panel
            self.Scroll(0, 0) 
            # Redraw (calls OnPaint and such)
            self.Refresh() 
            # Update panel Size and scrollbar config
            self._UpdateSize()
            # Get to the previous scroll position
            self.Scroll(prev_position[0], prev_position[1]) 

    def _UpdateSize(self):
        u = PDFWindow.SCROLLBAR_UNITS
        self.panel.SetSize((self.width*self.scale, self.height*self.scale))
        self.SetScrollbars(u, u, (self.width*self.scale)/u, (self.height*self.scale)/u)

    def OnKeyDown(self, event):
        update = True
        # More keycodes in http://docs.wxwidgets.org/stable/wx_keycodes.html#keycodes
        keycode = event.GetKeyCode() 
        if keycode in (wx.WXK_PAGEDOWN, wx.WXK_SPACE):
            next_page = self.n_page + 1
        elif keycode == wx.WXK_PAGEUP:
            next_page = self.n_page - 1
        else:
            update = False
        if update and (next_page >= 0) and (next_page < self.n_pages):
                self.n_page = next_page
                self.current_page = self.document.get_page(next_page)
                self.Refresh()


class MyFrame(wx.Frame):
 
    def __init__(self):
        wx.Frame.__init__(self, None, -1, "wxPdf Viewer", size=(800,600))
        self.pdfwindow = PDFWindow(self)
        self.pdfwindow.LoadDocument(sys.argv[1])
        self.pdfwindow.SetFocus() # To capture keyboard events

 
if __name__=="__main__":
    app = wx.App()
    f = MyFrame()
    f.Show()
    app.MainLoop()

8 comments

Vinicius Massuchetto 12 years, 9 months ago  # | flag

This gives me segmentation fault on line 50: cr = wxcairo.ContextFromDC(dc)

Perhaps it works nice under valgrind. What can it be?

Marcelo Fernández (author) 12 years, 8 months ago  # | flag

Hi Vinicius,

It works fine here, in my Ubuntu 11.04 installation. Could you describe what's you system environment, python and library versions?

Regards

Luca Saiani 12 years, 6 months ago  # | flag

Hi Marcelo,

I have programmed with the code you have done a full pdf reader with Python! But I need your help: I have many pdf with images inside, and the Cairo back-ends for Poppler aren't good to work with images: the scrolling is very slow. I know that there are other back-ends for drawing pdf documents: Splash. But I cannot find anything: no documentation or tutorials about it. Can you help me?

Thank you so much.

Luca

Marcelo Fernández (author) 12 years, 6 months ago  # | flag

Hi Luca,

Try Evince, which uses poppler-cairo (but from C) to render.

If it is faster, I guess the problem with the slow scrolling for complex pdfs isn't in the backend but in the OnPaint() method. It redraws all the GC context instead of moving the old part and after that, just render the new one (on the top or bottom, depending of the scroll direction).

I'm accepting patches to improve this behavior. :-)

Regards, Marcelo PD: You can see the Evince source code to see how it handles this situation.

Luca Saiani 12 years, 6 months ago  # | flag

Hi Marcelo,

I know Evince, and I use it for read my pdfs. But I need to underline pdfs I have scanned, and with Evince it is impossible. Sometimes I use Xournal for my tasks, but i want to underline with straight lines, not paint on with standard Xournals tool. So I'm doing the software that I need by myself, but I don't know C, only a bit of Python. I have read that Cairo works well with vector graphic and not with raster graphic. Maybe Splash works better with raster graphic. Or maybe I have to learn C !

Luca

Marcelo Fernández (author) 12 years, 6 months ago  # | flag

Luca,

I said that if Evince is faster to render that kind of pdfs when scrolling, I guess the issue is in the OnPaint() method because it uses the same library and backend than this example.

Regards

Luca Saiani 12 years, 6 months ago  # | flag

Marcelo,

I understand now! I'll see if I can find a good workaround for OnPaint() method.

I'll write again if I'll find something better.

Regards :-)

Benjamin 11 years, 10 months ago  # | flag

@Luca,

please share, I am also looking at making Python pdf viewer with annotations and highlighting.