Code

771dff99871ebd2f2490207790760175caa684e9
[tpdfview.git] / src / gtk-tpdfv.c
1 /*
2  * tpdfview - src/gtk-tpdfv.c
3  * Copyright (C) 2011 Sebastian 'tokkee' Harl <sh@tokkee.org>
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
17  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
19  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
28 /*
29  * This module provides a Gtk widget to display PDF files.
30  */
32 #include "gtk-tpdfv.h"
34 #include <assert.h>
36 #include <errno.h>
38 #include <cairo.h>
39 #include <gtk/gtk.h>
41 #include <poppler.h>
43 #include <stdlib.h>
44 #include <string.h>
46 #define TPDFV_MIN(a, b) ((a) <= (b) ? (a) : (b))
47 #define TPDFV_MAX(a, b) ((a) >= (b) ? (a) : (b))
49 typedef enum {
50         TPDFV_ZOOM_CUSTOM = 0,
51         TPDFV_ZOOM_WIDTH,
52         TPDFV_ZOOM_HEIGHT,
53         TPDFV_ZOOM_FIT,
54 } tpdfv_zoommode_t;
56 typedef struct {
57         char *filename;
59         PopplerDocument *doc;
60         PopplerPage     *current_page;
62         int current_page_no;
63         int total_pages;
65         tpdfv_zoommode_t zoom_mode;
66         double zoom_factor;
68         double delta_x;
69         double delta_y;
70 } gtk_tpdfv_t;
72 GType gtk_tpdfv_get_type(void);
73 G_DEFINE_TYPE(GtkTPDFV, gtk_tpdfv, GTK_TYPE_DRAWING_AREA);
75 #define GTK_TPDFV_GET_PRIVATE(obj) \
76         (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_TPDFV, gtk_tpdfv_t))
78 /*
79  * Private helper functions.
80  */
82 static gboolean
83 tpdfv_init(gtk_tpdfv_t *pdf, const char *filename)
84 {
85         GError *err = NULL;
87         if (strstr(filename, "://"))
88                 pdf->filename = strdup(filename);
89         else {
90                 size_t len = strlen("file://") + strlen(filename);
91                 pdf->filename = (char *)malloc(len + 1);
92                 if (pdf->filename) {
93                         *pdf->filename = '\0';
94                         strncat(pdf->filename, "file://", len);
95                         strncat(pdf->filename, filename, len - strlen("file://"));
96                 }
97         }
99         /* XXX: error reporting mechanism */
101         if (! pdf->filename) {
102                 char errbuf[1024];
103                 strerror_r(errno, errbuf, sizeof(errbuf));
104                 fprintf(stderr, "Failed to allocate string: %s.\n", errbuf);
105                 return FALSE;
106         }
108         pdf->doc = poppler_document_new_from_file(pdf->filename,
109                         /* password = */ NULL, &err);
110         if (! pdf->doc) {
111                 fprintf(stderr, "Failed to open PDF: %s.\n", err->message);
112                 return FALSE;
113         }
115         pdf->current_page_no = 0;
116         pdf->current_page = poppler_document_get_page(pdf->doc,
117                         pdf->current_page_no);
118         if (! pdf->current_page) {
119                 fprintf(stderr, "Failed to open first page of the document.\n");
120                 return FALSE;
121         }
123         pdf->total_pages = poppler_document_get_n_pages(pdf->doc);
125         pdf->zoom_mode   = TPDFV_ZOOM_CUSTOM;
126         pdf->zoom_factor = 1.0;
127         pdf->delta_x = pdf->delta_y = 0.0;
128         return TRUE;
129 } /* tpdfv_init */
131 static void
132 tpdfv_clean(gtk_tpdfv_t *pdf)
134         if (! pdf)
135                 return;
137         g_object_unref(pdf->doc);
138         pdf->doc          = NULL;
139         pdf->current_page = NULL;
140         free(pdf->filename);
141         pdf->filename = NULL;
142         return;
143 } /* tpdfv_clean */
145 static void
146 do_redraw(GtkWidget *widget)
148         GdkRegion *region;
149         gtk_tpdfv_t *pdf;
151         pdf = GTK_TPDFV_GET_PRIVATE(widget);
153         if (poppler_page_get_index(pdf->current_page)
154                         != pdf->current_page_no) {
155                 pdf->current_page = poppler_document_get_page(pdf->doc,
156                 pdf->current_page_no);
158                 if (! pdf->current_page) {
159                         fprintf(stderr, "Failed to open page %i of the document.\n",
160                                         pdf->current_page_no + 1);
161                         return;
162                 }
163         }
165         region = gdk_drawable_get_clip_region(widget->window);
166         gdk_window_invalidate_region(widget->window, region, TRUE);
167         gdk_window_process_updates(widget->window, TRUE);
169         gdk_region_destroy(region);
170 } /* do_redraw */
172 /*
173  * Gtk+ class definition.
174  */
176 static gboolean
177 gtk_tpdfv_expose(GtkWidget *tpdfv, GdkEventExpose *event)
179         gtk_tpdfv_t *pdf;
180         cairo_t *cr;
182         gdouble width, height;
184         gdouble page_width  = 0.0;
185         gdouble page_height = 0.0;
187         double zoom_factor  = 1.0;
188         double x, y;
190         pdf = GTK_TPDFV_GET_PRIVATE(tpdfv);
192         if (! pdf)
193                 return FALSE;
195         cr = gdk_cairo_create(tpdfv->window);
196         cairo_rectangle(cr, event->area.x, event->area.y,
197                         event->area.width, event->area.height);
198         cairo_clip(cr);
200         poppler_page_get_size(pdf->current_page, &page_width, &page_height);
201         width  = (gdouble)tpdfv->allocation.width;
202         height = (gdouble)tpdfv->allocation.height;
204         /* zoom */
205         if (pdf->zoom_mode == TPDFV_ZOOM_CUSTOM) {
206                 zoom_factor = pdf->zoom_factor;
207         }
208         else {
209                 if (pdf->zoom_mode == TPDFV_ZOOM_WIDTH) {
210                         zoom_factor = (double)(width / page_width);
211                 }
212                 else if (pdf->zoom_mode == TPDFV_ZOOM_HEIGHT) {
213                         zoom_factor = (double)(height / page_height);
214                 }
215                 else if (pdf->zoom_mode == TPDFV_ZOOM_FIT) {
216                         zoom_factor = (double)TPDFV_MIN(width / page_width,
217                                         height / page_height);
218                 }
219                 else {
220                         assert(0);
221                 }
222         }
223         cairo_scale(cr, zoom_factor, zoom_factor);
225         /* centered position / scrolling */
226         x = (double)(width - page_width * zoom_factor) / 2.0 / zoom_factor;
227         y = (double)(height - page_height * zoom_factor) / 2.0 / zoom_factor;
229         cairo_translate(cr, x + pdf->delta_x, y + pdf->delta_y);
231         poppler_page_render(pdf->current_page, cr);
233         cairo_destroy(cr);
234         return FALSE;
235 } /* gtk_tpdfv_expose */
237 static void
238 gtk_tpdfv_destroy(GtkObject *obj)
240         GtkTPDFV    *tpdfv = GTK_TPDFV(obj);
241         gtk_tpdfv_t *pdf;
243         pdf = GTK_TPDFV_GET_PRIVATE(tpdfv);
244         tpdfv_clean(pdf);
246         GTK_OBJECT_CLASS(gtk_tpdfv_parent_class)->destroy(obj);
247 } /* gtk_tpdfv_destroy */
249 static void
250 gtk_tpdfv_class_init(GtkTPDFVClass *class)
252         GObjectClass   *goclass;
253         GtkObjectClass *oclass;
254         GtkWidgetClass *wclass;
256         goclass = G_OBJECT_CLASS(class);
257         g_type_class_add_private(goclass, sizeof(gtk_tpdfv_t));
259         oclass = GTK_OBJECT_CLASS(class);
260         oclass->destroy = gtk_tpdfv_destroy;
262         wclass = GTK_WIDGET_CLASS(class);
263         wclass->expose_event = gtk_tpdfv_expose;
264 } /* gtk_tpdfv_class_init */
266 static void
267 gtk_tpdfv_init(GtkTPDFV __attribute__((unused)) *tpdfv)
269         /* nothing to do for now */
270 } /* gtk_tpdfv_init */
272 /*
273  * Public API.
274  */
276 GtkWidget *
277 gtk_tpdfv_new(const char *filename)
279         GtkTPDFV    *tpdfv;
280         gtk_tpdfv_t *pdf;
282         tpdfv = g_object_new(GTK_TYPE_TPDFV, NULL);
283         pdf   = GTK_TPDFV_GET_PRIVATE(tpdfv);
285         if (TRUE != tpdfv_init(pdf, filename))
286                 return NULL;
287         return GTK_WIDGET(tpdfv);
288 } /* gtk_tpdfv_new */
290 void
291 gtk_tpdfv_page_up(GtkWidget *widget)
293         gtk_tpdfv_t *pdf;
295         pdf = GTK_TPDFV_GET_PRIVATE(widget);
297         if (pdf->current_page_no)
298                 --pdf->current_page_no;
299         do_redraw(widget);
300 } /* gtk_tpdfv_page_up */
302 void
303 gtk_tpdfv_page_down(GtkWidget *widget)
305         gtk_tpdfv_t *pdf;
307         pdf = GTK_TPDFV_GET_PRIVATE(widget);
309         if (pdf->current_page_no < pdf->total_pages - 1)
310                 ++pdf->current_page_no;
311         do_redraw(widget);
312 } /* gtk_tpdfv_page_down */
314 void
315 gtk_tpdfv_first_page(GtkWidget *widget)
317         gtk_tpdfv_t *pdf;
319         pdf = GTK_TPDFV_GET_PRIVATE(widget);
320         pdf->current_page_no = 0;
321         do_redraw(widget);
322 } /* gtk_tpdfv_first_page */
324 void
325 gtk_tpdfv_last_page(GtkWidget *widget)
327         gtk_tpdfv_t *pdf;
329         pdf = GTK_TPDFV_GET_PRIVATE(widget);
330         pdf->current_page_no = pdf->total_pages - 1;
331         do_redraw(widget);
332 } /* gtk_tpdfv_last_page */
334 void
335 gtk_tpdfv_zoom_in(GtkWidget *widget)
337         gtk_tpdfv_t *pdf;
339         pdf = GTK_TPDFV_GET_PRIVATE(widget);
340         pdf->zoom_factor *= 1.2;
341         pdf->zoom_mode    = TPDFV_ZOOM_CUSTOM;
342         do_redraw(widget);
343 } /* gtk_tpdfv_zoom_in */
345 void
346 gtk_tpdfv_zoom_out(GtkWidget *widget)
348         gtk_tpdfv_t *pdf;
350         pdf = GTK_TPDFV_GET_PRIVATE(widget);
352         if (pdf->zoom_factor > DBL_EPSILON * 2.0) {
353                 pdf->zoom_factor /= 1.2;
354                 pdf->zoom_mode    = TPDFV_ZOOM_CUSTOM;
355                 do_redraw(widget);
356         }
357 } /* gtk_tpdfv_zoom_out */
359 void
360 gtk_tpdfv_zoom_1(GtkWidget *widget)
362         gtk_tpdfv_t *pdf;
364         pdf = GTK_TPDFV_GET_PRIVATE(widget);
365         pdf->zoom_factor = 1.0;
366         pdf->zoom_mode   = TPDFV_ZOOM_CUSTOM;
367         do_redraw(widget);
368 } /* gtk_tpdfv_zoom_1 */
370 void
371 gtk_tpdfv_zoom_width(GtkWidget *widget)
373         gtk_tpdfv_t *pdf;
375         pdf = GTK_TPDFV_GET_PRIVATE(widget);
376         pdf->zoom_mode = TPDFV_ZOOM_WIDTH;
377         do_redraw(widget);
378 } /* gtk_tpdfv_zoom_width */
380 void
381 gtk_tpdfv_zoom_height(GtkWidget *widget)
383         gtk_tpdfv_t *pdf;
385         pdf = GTK_TPDFV_GET_PRIVATE(widget);
386         pdf->zoom_mode = TPDFV_ZOOM_HEIGHT;
387         do_redraw(widget);
388 } /* gtk_tpdfv_zoom_width */
390 void
391 gtk_tpdfv_zoom_fit(GtkWidget *widget)
393         gtk_tpdfv_t *pdf;
395         pdf = GTK_TPDFV_GET_PRIVATE(widget);
396         pdf->zoom_mode = TPDFV_ZOOM_FIT;
397         do_redraw(widget);
398 } /* gtk_tpdfv_zoom_width */
400 void
401 gtk_tpdfv_scroll_up(GtkWidget *widget)
403         gtk_tpdfv_t *pdf;
405         pdf = GTK_TPDFV_GET_PRIVATE(widget);
406         pdf->delta_y += 10.0;
407         do_redraw(widget);
408 } /* gtk_tpdfv_scroll_up */
410 void
411 gtk_tpdfv_scroll_down(GtkWidget *widget)
413         gtk_tpdfv_t *pdf;
415         pdf = GTK_TPDFV_GET_PRIVATE(widget);
416         pdf->delta_y -= 10.0;
417         do_redraw(widget);
418 } /* gtk_tpdfv_scroll_down */
420 void
421 gtk_tpdfv_scroll_left(GtkWidget *widget)
423         gtk_tpdfv_t *pdf;
425         pdf = GTK_TPDFV_GET_PRIVATE(widget);
426         pdf->delta_x += 10.0;
427         do_redraw(widget);
428 } /* gtk_tpdfv_scroll_left */
430 void
431 gtk_tpdfv_scroll_right(GtkWidget *widget)
433         gtk_tpdfv_t *pdf;
435         pdf = GTK_TPDFV_GET_PRIVATE(widget);
436         pdf->delta_x -= 10.0;
437         do_redraw(widget);
438 } /* gtk_tpdfv_scroll_right */
440 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */