~phoenix1987/gtumbler/trunk

« back to all changes in this revision

Viewing changes to gtumbler_lib/DocumentView.py

  • Committer: Gabriele N. Tornetta
  • Date: 2015-04-13 22:11:01 UTC
  • Revision ID: phoenix1987@gmail.com-20150413221101-on5p7eo205wsncwe
Resuming work on new version

Show diffs side-by-side

added added

removed removed

Lines of Context:
18
18
from gi.repository import Gtk
19
19
from gi.repository import Poppler
20
20
 
21
 
from gtumbler_lib import pdf
 
21
#from gtumbler_lib import pdf
 
22
import PyPDF2 as pdf
22
23
from gtumbler_lib.helpers import enum
23
24
 
24
 
import rsvg
 
25
from gi.repository import Rsvg as rsvg
25
26
 
26
27
BOX_NAMES = ['artBox', 'bleedBox', 'cropBox', 'mediaBox', 'trimBox']
27
28
 
28
29
EditType = enum('NO_EDIT', 'BOUNDING_BOXES')
29
30
 
30
31
class Document(object):
31
 
    """
32
 
    This class represents a single document
33
 
    """
34
 
 
35
 
    def __init__(self, filename):
36
 
        self._filename = filename
37
 
        self._document = Poppler.Document.new_from_file("file://%s" % self._filename, None)
38
 
        self._fin = file(filename, 'rb')
39
 
        self._pdf_rd = pdf.PdfFileReader(self._fin)
40
 
        self._page = None
41
 
        self._bboxes = [[] for i in range(len(BOX_NAMES))]
42
 
        self.set_page(0)
43
 
 
44
 
    def close(self):
45
 
        """
46
 
        Closes the current document nicely
47
 
        """
48
 
        
49
 
        try:
50
 
            self._fin.close()
51
 
        except:
52
 
            pass
53
 
 
54
 
    def get_origin(self):
55
 
        """
56
 
        Get the origin the page should be drawn at on the Cairo context
57
 
        """
58
 
        
59
 
        return self._origin
60
 
 
61
 
    def set_page(self, page = None):
62
 
        """
63
 
        Set the current page.
64
 
        
65
 
        @param page: The zero-based page number to set.
66
 
        """
67
 
        
68
 
        if page != None and page != self._page:
69
 
            self._page_obj = self._document.get_page(page)
70
 
            paper = self._pdf_rd.getPage(page)
71
 
            left, bottom = paper.mediaBox.lowerLeft
72
 
            right, top = paper.mediaBox.upperRight
73
 
 
74
 
            self._width, self._height = float(right - left), float(top - bottom)
75
 
            for i, box_name in enumerate(BOX_NAMES):
76
 
                box = getattr(paper, box_name, None)
77
 
                if box:
78
 
                    self._bboxes[i] = [self._height - float(box.upperRight[1]),
79
 
                                        float(box.lowerLeft[1]),
80
 
                                        float(box.lowerLeft[0]),
81
 
                                        self._width - float(box.upperRight[0])]
82
 
 
83
 
            self._origin = (float(paper.cropBox.lowerLeft[0]),
84
 
                            self._height - float(paper.cropBox.upperRight[1]))
85
 
            self._page = page
86
 
        
87
 
    def render(self, cairo_context):
88
 
        """
89
 
        Renders the current page on the passed cairo_context
90
 
        
91
 
        @param cairo_context: A cairo context to draw on
92
 
        """
93
 
        
94
 
        self._page_obj.render(cairo_context)
95
 
 
96
 
    def get_page(self):
97
 
        """
98
 
        Returns the current page number
99
 
        
100
 
        @return: The current zero-based page number
101
 
        """
102
 
        
103
 
        return self._page
104
 
 
105
 
    def get_n_pages(self):
106
 
        """
107
 
        Returns the document page count
108
 
        
109
 
        @return: The document page count
110
 
        """
111
 
        
112
 
        if getattr(self, '_n_pages', None):
113
 
            return self._n_pages
114
 
        self._n_pages = self._document.get_n_pages()
115
 
        return self._n_pages
116
 
 
117
 
    def get_page_size(self):
118
 
        return self._width, self._height
119
 
 
120
 
    def get_title(self):
121
 
        return self._document.get_property("title")
 
32
        """
 
33
        This class represents a single document
 
34
        """
 
35
 
 
36
        def __init__(self, filename):
 
37
                self._filename = filename
 
38
                self._document = Poppler.Document.new_from_file("file://%s" % self._filename, None)
 
39
                self._fin = open(filename, 'rb')
 
40
                self._pdf_rd = pdf.PdfFileReader(self._fin)
 
41
                self._page = None
 
42
                self._bboxes = [[] for i in range(len(BOX_NAMES))]
 
43
                self.set_page(0)
 
44
 
 
45
        def close(self):
 
46
                """
 
47
                Closes the current document nicely
 
48
                """
 
49
                
 
50
                try:
 
51
                        self._fin.close()
 
52
                except:
 
53
                        pass
 
54
 
 
55
        def get_origin(self):
 
56
                """
 
57
                Get the origin the page should be drawn at on the Cairo context
 
58
                """
 
59
                
 
60
                return self._origin
 
61
 
 
62
        def set_page(self, page = None):
 
63
                """
 
64
                Set the current page.
 
65
                
 
66
                @param page: The zero-based page number to set.
 
67
                """
 
68
                
 
69
                if page != None and page != self._page:
 
70
                        self._page_obj = self._document.get_page(page)
 
71
                        paper = self._pdf_rd.getPage(page)
 
72
                        left, bottom = paper.mediaBox.lowerLeft
 
73
                        right, top = paper.mediaBox.upperRight
 
74
 
 
75
                        self._width, self._height = float(right - left), float(top - bottom)
 
76
                        for i, box_name in enumerate(BOX_NAMES):
 
77
                                box = getattr(paper, box_name, None)
 
78
                                if box:
 
79
                                        self._bboxes[i] = [self._height - float(box.upperRight[1]),
 
80
                                                                                float(box.lowerLeft[1]),
 
81
                                                                                float(box.lowerLeft[0]),
 
82
                                                                                self._width - float(box.upperRight[0])]
 
83
 
 
84
                        self._origin = (float(paper.cropBox.lowerLeft[0]),
 
85
                                                        self._height - float(paper.cropBox.upperRight[1]))
 
86
                        self._page = page
 
87
                
 
88
        def render(self, cairo_context):
 
89
                """
 
90
                Renders the current page on the passed cairo_context
 
91
                
 
92
                @param cairo_context: A cairo context to draw on
 
93
                """
 
94
                
 
95
                self._page_obj.render(cairo_context)
 
96
 
 
97
        def get_page(self):
 
98
                """
 
99
                Returns the current page number
 
100
                
 
101
                @return: The current zero-based page number
 
102
                """
 
103
                
 
104
                return self._page
 
105
 
 
106
        def get_n_pages(self):
 
107
                """
 
108
                Returns the document page count
 
109
                
 
110
                @return: The document page count
 
111
                """
 
112
                
 
113
                if getattr(self, '_n_pages', None):
 
114
                        return self._n_pages
 
115
                self._n_pages = self._document.get_n_pages()
 
116
                return self._n_pages
 
117
 
 
118
        def get_page_size(self):
 
119
                return self._width, self._height
 
120
 
 
121
        def get_title(self):
 
122
                return self._document.get_property("title")
122
123
 
123
124
 
124
125
class DocumentView(Gtk.DrawingArea):
125
 
    """
126
 
    A Gtk.DrawingArea designed to handle document rendering inside a Gtk.Window
127
 
    """
128
 
    
129
 
    __gtype_name__ = "DocumentView"
130
 
    
131
 
    __gsignals__ = {
132
 
        'document-changed' : (GObject.SIGNAL_RUN_FIRST,
133
 
                              None,
134
 
                              ()
135
 
                             ),
136
 
        
137
 
        'page-changed' : (GObject.SIGNAL_RUN_FIRST,
138
 
                          None, 
139
 
                          (int,) # @param: the new page number
140
 
                         ),
141
 
 
142
 
        'zoom' : (GObject.SIGNAL_RUN_FIRST,
143
 
                          None, 
144
 
                          (float,) # @param: the new zoom level (scale factor, 1 == 100%)
145
 
                 ),
146
 
    }
147
 
    
148
 
    def __init__(self, *args):
149
 
        super(DocumentView, self).__init__()
150
 
        self.document = None
151
 
        self._page_number = None     
152
 
        self.set_zoom(1)
153
 
        self._pad = 3
154
 
        self._edit_mode = EditType.NO_EDIT
155
 
        self._edit_mode_data = None
156
 
        
157
 
        self.connect('draw', self._on_expose_event)
158
 
 
159
 
        #self._draw_logo()
160
 
 
161
 
    def _draw_logo(self):
162
 
        """Draws the application logo on the Document View"""
163
 
        
164
 
        cr = self.get_window().cairo_create()
165
 
        svg = rsvg.Handle(file='data/media/gtumbler.svg')
166
 
        svg.render_cairo(cr)
167
 
 
168
 
    def _on_expose_event(self, widget, event = None, *args):
169
 
        """
170
 
        This is the central event. It is responsible for coordinating the drawing
171
 
        processes on the Document View
172
 
        """
173
 
        
174
 
        ### PREAMBLE ###
175
 
        
176
 
        # Initial logo, displayed when no documents are loaded
177
 
        if not self.document:
178
 
            self._draw_logo()
179
 
            return
180
 
 
181
 
        ### DOCUMENT RENDERING ###
182
 
 
183
 
        #TODO: To probably make this faster use event to create a clipping area
184
 
        #      (http://www.pygtk.org/articles/cairo-pygtk-widgets/cairo-pygtk-widgets.htm)
185
 
        
186
 
        w, h = self.document.get_page_size()
187
 
        
188
 
        # switch Zoom Mode
189
 
        if getattr(self, '_best_fit', False):
190
 
            # case Best Fit
191
 
            rect_sw = self.get_parent().get_parent().get_parent().get_allocation()
192
 
            s = self._zoom = (rect_sw.width - 2 * 6. - float(self._pad) - 2) / w
193
 
            
194
 
        elif getattr(self, '_fit_page', False):
195
 
            # case Fit Page
196
 
            rect_sw = self.get_parent().get_parent().get_parent().get_allocation()
197
 
            sx = (rect_sw.width - 2 * 6. - float(self._pad) - 2) / w
198
 
            sy = (rect_sw.height - 2 * 6. - float(self._pad) - 2) / h
199
 
            s = self._zoom = min([sx, sy])
200
 
            
201
 
        else:
202
 
            # default
203
 
            s = self._zoom
204
 
        
205
 
        cr = widget.get_window().cairo_create()        
206
 
 
207
 
        ## Grey page shadow
208
 
        cr.set_source_rgb(.3, .3, .3)
209
 
        cr.rectangle(self._pad, self._pad, s * w, s * h)
210
 
        cr.fill()
211
 
        
212
 
        ## White default page background
213
 
        cr.set_source_rgb(1, 1, 1)
214
 
        cr.rectangle(0, 0, s * w, s * h)
215
 
        cr.fill()
216
 
        
217
 
        ## Black page contour
218
 
        cr.set_source_rgb(0, 0, 0)
219
 
        cr.set_line_width(.8)
220
 
        cr.rectangle(0, 0, s * w, s * h)
221
 
        cr.stroke()
222
 
        
223
 
        ## Rendering of the current page
224
 
        cr = widget.get_window().cairo_create()
225
 
        if s != 1:
226
 
            cr.scale(s, s)
227
 
 
228
 
        cr.translate(*self.document.get_origin())
229
 
        self.document.render(cr)
230
 
 
231
 
        widget.set_size_request(self._pad + int(w * s), self._pad + int(h * s))
232
 
        
233
 
        
234
 
        ### EDIT MODE HANDLING ###
235
 
        
236
 
        if self._edit_mode == EditType.BOUNDING_BOXES:
237
 
            ## Bounding Boxes
238
 
            self.draw_rectangle()
239
 
 
240
 
    def close(self):
241
 
        """Closes the current document, if any."""
242
 
        if self.document:
243
 
            self.document.close()
244
 
            self.document = None
245
 
 
246
 
    def draw_rectangle(self, rect, color):
247
 
        pass
248
 
 
249
 
    def get_best_fit(self):
250
 
        return getattr(self, '_best_fit', False)
251
 
 
252
 
    def get_state(self):
253
 
        return bool(self.document)
254
 
 
255
 
    def get_zoom(self):
256
 
        return self._zoom
257
 
 
258
 
    def open(self, filename):
259
 
        if self.document: self.close()
260
 
        self.document = Document(filename)
261
 
        self.emit('zoom', self.get_zoom())
262
 
        self.emit('document-changed')
263
 
        self.emit('page-changed', self.document.get_page())
264
 
        self.refresh()
265
 
 
266
 
    def page_first(self):
267
 
        if not self.document: return
268
 
        page = self.document.get_page()
269
 
        if page <= 0: return
270
 
        self.set_page(0)
271
 
        self.refresh()
272
 
 
273
 
    def page_last(self):
274
 
        if not self.document: return
275
 
        page = self.document.get_page()
276
 
        if page >= self.document.get_n_pages() - 1: return
277
 
        self.set_page(self.document.get_n_pages() - 1)
278
 
        self.refresh()
279
 
 
280
 
    def page_next(self):
281
 
        if not self.document: return
282
 
        page = self.document.get_page()
283
 
        if page >= self.document.get_n_pages() - 1: return
284
 
        self.set_page(page + 1)
285
 
        self.refresh()
286
 
 
287
 
    def page_previous(self):
288
 
        if not self.document: return
289
 
        page = self.document.get_page()
290
 
        if page <= 0: return
291
 
        self.set_page(page - 1)
292
 
        self.refresh()
293
 
 
294
 
    def refresh(self):
295
 
        self.queue_draw()
296
 
 
297
 
    def set_best_fit(self, best_fit):
298
 
        self._best_fit = best_fit
299
 
        if best_fit: self._fit_page = False
300
 
        self.refresh()
301
 
 
302
 
    def set_edit_mode(self, mode, data = None):
303
 
        self._edit_mode = mode
304
 
        self._edit_mode_data = data
305
 
 
306
 
    def set_fit_page(self, fit_page):
307
 
        self._fit_page = fit_page
308
 
        if fit_page: self._best_fit = False
309
 
        self.refresh()
310
 
 
311
 
    def set_page(self, page):
312
 
        if page < 0 or page > self.document.get_n_pages() - 1: return
313
 
        self.document.set_page(page)
314
 
        self.emit('page-changed', page)
315
 
 
316
 
    def set_zoom(self, zoom):
317
 
        if zoom > 16 or zoom < .25: return
318
 
        self._zoom = zoom
319
 
        self.refresh()
320
 
        self.emit('zoom', self._zoom)
321
 
 
322
 
    def zoom_in(self, delta):
323
 
        self.set_zoom(self._zoom + delta)
324
 
        
325
 
    def zoom_out(self, delta):
326
 
        if self._zoom - delta > 0:
327
 
            self.set_zoom(self._zoom - delta)
 
126
        """
 
127
        A Gtk.DrawingArea designed to handle document rendering inside a Gtk.Window
 
128
        """
 
129
        
 
130
        __gtype_name__ = "DocumentView"
 
131
        
 
132
        __gsignals__ = {
 
133
                'document-changed' : (GObject.SIGNAL_RUN_FIRST,
 
134
                                                          None,
 
135
                                                          ()
 
136
                                                         ),
 
137
                
 
138
                'page-changed' : (GObject.SIGNAL_RUN_FIRST,
 
139
                                                  None, 
 
140
                                                  (int,) # @param: the new page number
 
141
                                                 ),
 
142
 
 
143
                'zoom' : (GObject.SIGNAL_RUN_FIRST,
 
144
                                                  None, 
 
145
                                                  (float,) # @param: the new zoom level (scale factor, 1 == 100%)
 
146
                                 ),
 
147
        }
 
148
        
 
149
        def __init__(self, *args):
 
150
                super(DocumentView, self).__init__()
 
151
                self.document = None
 
152
                self._page_number = None     
 
153
                self.set_zoom(1)
 
154
                self._pad = 3
 
155
                self._edit_mode = EditType.NO_EDIT
 
156
                self._edit_mode_data = None
 
157
                
 
158
                self.connect('draw', self._on_expose_event)
 
159
 
 
160
                #self._draw_logo()
 
161
 
 
162
        def _draw_logo(self):
 
163
                """Draws the application logo on the Document View"""
 
164
                
 
165
                cr = self.get_window().cairo_create()
 
166
                svg = rsvg.Handle.new_from_file('data/media/gtumbler.svg')
 
167
                svg.render_cairo(cr)
 
168
 
 
169
        def _on_expose_event(self, widget, cr):
 
170
                """
 
171
                This is the central event. It is responsible for coordinating the drawing
 
172
                processes on the Document View
 
173
                """
 
174
                
 
175
                ### PREAMBLE ###
 
176
                
 
177
                # Initial logo, displayed when no documents are loaded
 
178
                if not self.document:
 
179
                        self._draw_logo()
 
180
                        return
 
181
 
 
182
                ### DOCUMENT RENDERING ###
 
183
 
 
184
                #TODO: To probably make this faster use event to create a clipping area
 
185
                #      (http://www.pygtk.org/articles/cairo-pygtk-widgets/cairo-pygtk-widgets.htm)
 
186
                
 
187
                w, h = self.document.get_page_size()
 
188
                
 
189
                s = self.get_zoom()
 
190
                
 
191
                #cr = widget.get_window().cairo_create()        
 
192
 
 
193
                ## Grey page shadow
 
194
                cr.set_source_rgb(.3, .3, .3)
 
195
                cr.rectangle(self._pad, self._pad, s * w, s * h)
 
196
                cr.fill()
 
197
                
 
198
                ## White default page background
 
199
                cr.set_source_rgb(1, 1, 1)
 
200
                cr.rectangle(0, 0, s * w, s * h)
 
201
                cr.fill()
 
202
                
 
203
                ## Black page contour
 
204
                cr.set_source_rgb(0, 0, 0)
 
205
                cr.set_line_width(.8)
 
206
                cr.rectangle(0, 0, s * w, s * h)
 
207
                cr.stroke()
 
208
                
 
209
                ## Rendering of the current page
 
210
                #cr = widget.get_window().cairo_create()
 
211
                if s != 1:
 
212
                        cr.scale(s, s)
 
213
 
 
214
                cr.translate(*self.document.get_origin())
 
215
                self.document.render(cr)
 
216
                
 
217
                ### EDIT MODE HANDLING ###
 
218
                
 
219
                if self._edit_mode == EditType.BOUNDING_BOXES:
 
220
                        ## Bounding Boxes
 
221
                        self.draw_rectangle()
 
222
                
 
223
                return True
 
224
 
 
225
        def close(self):
 
226
                """Closes the current document, if any."""
 
227
                if self.document:
 
228
                        self.document.close()
 
229
                        self.document = None
 
230
 
 
231
        def draw_rectangle(self, rect, color):
 
232
                pass
 
233
 
 
234
        def get_best_fit(self):
 
235
                return getattr(self, '_best_fit', False)
 
236
 
 
237
        def get_state(self):
 
238
                return bool(self.document)
 
239
 
 
240
        def get_zoom(self):
 
241
                return self._zoom
 
242
 
 
243
        def open(self, filename):
 
244
                # Check if a document is already opened and if so close it
 
245
                self.close()
 
246
                
 
247
                # Open the new document
 
248
                self.document = Document(filename)
 
249
                self.emit('zoom', self.get_zoom())
 
250
                self.emit('document-changed')
 
251
                self.emit('page-changed', self.document.get_page())
 
252
                self.refresh()
 
253
 
 
254
        def page_first(self):
 
255
                if not self.document: return
 
256
                page = self.document.get_page()
 
257
                if page <= 0: return
 
258
                self.set_page(0)
 
259
                self.refresh()
 
260
 
 
261
        def page_last(self):
 
262
                if not self.document: return
 
263
                page = self.document.get_page()
 
264
                if page >= self.document.get_n_pages() - 1: return
 
265
                self.set_page(self.document.get_n_pages() - 1)
 
266
                self.refresh()
 
267
 
 
268
        def page_next(self):
 
269
                if not self.document: return
 
270
                page = self.document.get_page()
 
271
                if page >= self.document.get_n_pages() - 1: return
 
272
                self.set_page(page + 1)
 
273
                self.refresh()
 
274
 
 
275
        def page_previous(self):
 
276
                if not self.document: return
 
277
                page = self.document.get_page()
 
278
                if page <= 0: return
 
279
                self.set_page(page - 1)
 
280
                self.refresh()
 
281
 
 
282
        def refresh(self):
 
283
                if self.document:
 
284
                        w, h = self.document.get_page_size()
 
285
                        s = self.get_zoom()
 
286
                        self.set_size_request(self._pad + int(w * s), self._pad + int(h * s))
 
287
                self.queue_draw()
 
288
 
 
289
        def set_best_fit(self, best_fit):
 
290
                self._best_fit = best_fit
 
291
                if best_fit:
 
292
                        self._fit_page = False
 
293
                        rect_sw = self.get_parent().get_parent().get_parent().get_allocation()
 
294
                        w, h = self.document.get_page_size()
 
295
                        self._zoom = (rect_sw.width - 2 * 6. - float(self._pad) - 2) / w
 
296
                self.refresh()
 
297
 
 
298
        def set_edit_mode(self, mode, data = None):
 
299
                self._edit_mode = mode
 
300
                self._edit_mode_data = data
 
301
 
 
302
        def set_fit_page(self, fit_page):
 
303
                self._fit_page = fit_page
 
304
                if fit_page:
 
305
                        self._best_fit = False
 
306
                        rect_sw = self.get_parent().get_parent().get_parent().get_allocation()
 
307
                        w, h = self.document.get_page_size()
 
308
                        sx = (rect_sw.width - 2 * 6. - float(self._pad) - 2) / w
 
309
                        sy = (rect_sw.height - 2 * 6. - float(self._pad) - 2) / h
 
310
                        self._zoom = min([sx, sy])
 
311
                self.refresh()
 
312
 
 
313
        def set_page(self, page):
 
314
                if page < 0 or page > self.document.get_n_pages() - 1: return
 
315
                self.document.set_page(page)
 
316
                self.emit('page-changed', page)
 
317
 
 
318
        def set_zoom(self, zoom):
 
319
                if zoom > 16 or zoom < .25: return
 
320
                self._zoom = zoom
 
321
                self.refresh()
 
322
                self.emit('zoom', self._zoom)
 
323
 
 
324
        def zoom_in(self, delta):
 
325
                self.set_zoom(self._zoom + delta)
 
326
                
 
327
        def zoom_out(self, delta):
 
328
                if self._zoom - delta > 0:
 
329
                        self.set_zoom(self._zoom - delta)
328
330