Code

4398719385557f7144ca42c674bdf66acfd47630
[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         pdf = GTK_TPDFV_GET_PRIVATE(tpdfv);
184         if (! pdf)
185                 return FALSE;
187         cr = gdk_cairo_create(tpdfv->window);
188         cairo_rectangle(cr, event->area.x, event->area.y,
189                         event->area.width, event->area.height);
190         cairo_clip(cr);
192         /* zoom, scrolling */
193         if (pdf->zoom_mode == TPDFV_ZOOM_CUSTOM) {
194                 cairo_scale(cr, pdf->zoom_factor, pdf->zoom_factor);
195         }
196         else {
197                 gdouble width;
198                 gdouble height;
200                 gdouble page_width  = 0.0;
201                 gdouble page_height = 0.0;
203                 double zoom_factor  = 1.0;
205                 poppler_page_get_size(pdf->current_page, &page_width, &page_height);
206                 width  = (gdouble)tpdfv->allocation.width;
207                 height = (gdouble)tpdfv->allocation.height;
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                 }
223                 cairo_scale(cr, zoom_factor, zoom_factor);
224         }
226         cairo_translate(cr, pdf->delta_x, pdf->delta_y);
228         poppler_page_render(pdf->current_page, cr);
230         cairo_destroy(cr);
231         return FALSE;
232 } /* gtk_tpdfv_expose */
234 static void
235 gtk_tpdfv_destroy(GtkObject *obj)
237         GtkTPDFV    *tpdfv = GTK_TPDFV(obj);
238         gtk_tpdfv_t *pdf;
240         pdf = GTK_TPDFV_GET_PRIVATE(tpdfv);
241         tpdfv_clean(pdf);
243         GTK_OBJECT_CLASS(gtk_tpdfv_parent_class)->destroy(obj);
244 } /* gtk_tpdfv_destroy */
246 static void
247 gtk_tpdfv_class_init(GtkTPDFVClass *class)
249         GObjectClass   *goclass;
250         GtkObjectClass *oclass;
251         GtkWidgetClass *wclass;
253         goclass = G_OBJECT_CLASS(class);
254         g_type_class_add_private(goclass, sizeof(gtk_tpdfv_t));
256         oclass = GTK_OBJECT_CLASS(class);
257         oclass->destroy = gtk_tpdfv_destroy;
259         wclass = GTK_WIDGET_CLASS(class);
260         wclass->expose_event = gtk_tpdfv_expose;
261 } /* gtk_tpdfv_class_init */
263 static void
264 gtk_tpdfv_init(GtkTPDFV __attribute__((unused)) *tpdfv)
266         /* nothing to do for now */
267 } /* gtk_tpdfv_init */
269 /*
270  * Public API.
271  */
273 GtkWidget *
274 gtk_tpdfv_new(const char *filename)
276         GtkTPDFV    *tpdfv;
277         gtk_tpdfv_t *pdf;
279         tpdfv = g_object_new(GTK_TYPE_TPDFV, NULL);
280         pdf   = GTK_TPDFV_GET_PRIVATE(tpdfv);
282         if (TRUE != tpdfv_init(pdf, filename))
283                 return NULL;
284         return GTK_WIDGET(tpdfv);
285 } /* gtk_tpdfv_new */
287 void
288 gtk_tpdfv_page_up(GtkWidget *widget)
290         gtk_tpdfv_t *pdf;
292         pdf = GTK_TPDFV_GET_PRIVATE(widget);
294         if (pdf->current_page_no)
295                 --pdf->current_page_no;
296         do_redraw(widget);
297 } /* gtk_tpdfv_page_up */
299 void
300 gtk_tpdfv_page_down(GtkWidget *widget)
302         gtk_tpdfv_t *pdf;
304         pdf = GTK_TPDFV_GET_PRIVATE(widget);
306         if (pdf->current_page_no < pdf->total_pages - 1)
307                 ++pdf->current_page_no;
308         do_redraw(widget);
309 } /* gtk_tpdfv_page_down */
311 void
312 gtk_tpdfv_first_page(GtkWidget *widget)
314         gtk_tpdfv_t *pdf;
316         pdf = GTK_TPDFV_GET_PRIVATE(widget);
317         pdf->current_page_no = 0;
318         do_redraw(widget);
319 } /* gtk_tpdfv_first_page */
321 void
322 gtk_tpdfv_last_page(GtkWidget *widget)
324         gtk_tpdfv_t *pdf;
326         pdf = GTK_TPDFV_GET_PRIVATE(widget);
327         pdf->current_page_no = pdf->total_pages - 1;
328         do_redraw(widget);
329 } /* gtk_tpdfv_last_page */
331 void
332 gtk_tpdfv_zoom_in(GtkWidget *widget)
334         gtk_tpdfv_t *pdf;
336         pdf = GTK_TPDFV_GET_PRIVATE(widget);
337         pdf->zoom_factor *= 1.2;
338         pdf->zoom_mode    = TPDFV_ZOOM_CUSTOM;
339         do_redraw(widget);
340 } /* gtk_tpdfv_zoom_in */
342 void
343 gtk_tpdfv_zoom_out(GtkWidget *widget)
345         gtk_tpdfv_t *pdf;
347         pdf = GTK_TPDFV_GET_PRIVATE(widget);
349         if (pdf->zoom_factor > DBL_EPSILON * 2.0) {
350                 pdf->zoom_factor /= 1.2;
351                 pdf->zoom_mode    = TPDFV_ZOOM_CUSTOM;
352                 do_redraw(widget);
353         }
354 } /* gtk_tpdfv_zoom_out */
356 void
357 gtk_tpdfv_zoom_1(GtkWidget *widget)
359         gtk_tpdfv_t *pdf;
361         pdf = GTK_TPDFV_GET_PRIVATE(widget);
362         pdf->zoom_factor = 1.0;
363         pdf->zoom_mode   = TPDFV_ZOOM_CUSTOM;
364         do_redraw(widget);
365 } /* gtk_tpdfv_zoom_1 */
367 void
368 gtk_tpdfv_zoom_width(GtkWidget *widget)
370         gtk_tpdfv_t *pdf;
372         pdf = GTK_TPDFV_GET_PRIVATE(widget);
373         pdf->zoom_mode = TPDFV_ZOOM_WIDTH;
374         do_redraw(widget);
375 } /* gtk_tpdfv_zoom_width */
377 void
378 gtk_tpdfv_zoom_height(GtkWidget *widget)
380         gtk_tpdfv_t *pdf;
382         pdf = GTK_TPDFV_GET_PRIVATE(widget);
383         pdf->zoom_mode = TPDFV_ZOOM_HEIGHT;
384         do_redraw(widget);
385 } /* gtk_tpdfv_zoom_width */
387 void
388 gtk_tpdfv_zoom_fit(GtkWidget *widget)
390         gtk_tpdfv_t *pdf;
392         pdf = GTK_TPDFV_GET_PRIVATE(widget);
393         pdf->zoom_mode = TPDFV_ZOOM_FIT;
394         do_redraw(widget);
395 } /* gtk_tpdfv_zoom_width */
397 void
398 gtk_tpdfv_scroll_up(GtkWidget *widget)
400         gtk_tpdfv_t *pdf;
402         pdf = GTK_TPDFV_GET_PRIVATE(widget);
403         pdf->delta_y += 10.0;
404         do_redraw(widget);
405 } /* gtk_tpdfv_scroll_up */
407 void
408 gtk_tpdfv_scroll_down(GtkWidget *widget)
410         gtk_tpdfv_t *pdf;
412         pdf = GTK_TPDFV_GET_PRIVATE(widget);
413         pdf->delta_y -= 10.0;
414         do_redraw(widget);
415 } /* gtk_tpdfv_scroll_down */
417 void
418 gtk_tpdfv_scroll_left(GtkWidget *widget)
420         gtk_tpdfv_t *pdf;
422         pdf = GTK_TPDFV_GET_PRIVATE(widget);
423         pdf->delta_x += 10.0;
424         do_redraw(widget);
425 } /* gtk_tpdfv_scroll_left */
427 void
428 gtk_tpdfv_scroll_right(GtkWidget *widget)
430         gtk_tpdfv_t *pdf;
432         pdf = GTK_TPDFV_GET_PRIVATE(widget);
433         pdf->delta_x -= 10.0;
434         do_redraw(widget);
435 } /* gtk_tpdfv_scroll_right */
437 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */