Code

cba2e1f2a1c13697e384cb8d1eeab7e62c0547e8
[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 #include <sys/stat.h>
48 #define TPDFV_MIN(a, b) ((a) <= (b) ? (a) : (b))
49 #define TPDFV_MAX(a, b) ((a) >= (b) ? (a) : (b))
51 typedef enum {
52         TPDFV_ZOOM_CUSTOM = 0,
53         TPDFV_ZOOM_WIDTH,
54         TPDFV_ZOOM_HEIGHT,
55         TPDFV_ZOOM_FIT,
56 } tpdfv_zoommode_t;
58 typedef struct {
59         char *filename;
61         time_t mtime;
63         PopplerDocument *doc;
64         PopplerPage     *current_page;
66         int current_page_no;
67         int total_pages;
69         tpdfv_zoommode_t zoom_mode;
70         double zoom_factor;
72         double delta_x;
73         double delta_y;
74 } gtk_tpdfv_t;
76 GType gtk_tpdfv_get_type(void);
77 G_DEFINE_TYPE(GtkTPDFV, gtk_tpdfv, GTK_TYPE_DRAWING_AREA);
79 #define GTK_TPDFV_GET_PRIVATE(obj) \
80         (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_TPDFV, gtk_tpdfv_t))
82 /*
83  * Private helper functions.
84  */
86 static void
87 tpdfv_open(gtk_tpdfv_t *pdf)
88 {
89         GError *err = NULL;
91         struct stat statbuf;
93         if ((! pdf) || (! pdf->filename))
94                 return;
96         memset(&statbuf, 0, sizeof(statbuf));
97         if (stat(pdf->filename + strlen("file://"), &statbuf) != 0) {
98                 char errbuf[1024];
99                 strerror_r(errno, errbuf, sizeof(errbuf));
100                 fprintf(stderr, "Failed to access PDF: %s.\n", errbuf);
101                 return;
102         }
104         pdf->mtime = statbuf.st_mtime;
106         pdf->doc = poppler_document_new_from_file(pdf->filename,
107                         /* password = */ NULL, &err);
108         if (! pdf->doc) {
109                 fprintf(stderr, "Failed to open PDF: %s.\n", err->message);
110                 return;
111         }
113         pdf->total_pages = poppler_document_get_n_pages(pdf->doc);
114         if (pdf->current_page_no >= pdf->total_pages)
115                 pdf->current_page_no = pdf->total_pages - 1;
116 } /* tpdfv_open */
118 static void
119 tpdfv_close(gtk_tpdfv_t *pdf)
121         if (! pdf)
122                 return;
124         g_object_unref(pdf->doc);
125         pdf->doc = NULL;
126         pdf->current_page = NULL;
127 } /* tpdfv_close */
129 static gboolean
130 tpdfv_init(gtk_tpdfv_t *pdf, const char *filename)
132         if (strstr(filename, "://"))
133                 pdf->filename = strdup(filename);
134         else {
135                 size_t len = strlen("file://") + strlen(filename);
136                 pdf->filename = (char *)malloc(len + 1);
137                 if (pdf->filename) {
138                         *pdf->filename = '\0';
139                         strncat(pdf->filename, "file://", len);
140                         strncat(pdf->filename, filename, len - strlen("file://"));
141                 }
142         }
144         /* XXX: error reporting mechanism */
146         if (! pdf->filename) {
147                 char errbuf[1024];
148                 strerror_r(errno, errbuf, sizeof(errbuf));
149                 fprintf(stderr, "Failed to allocate string: %s.\n", errbuf);
150                 return FALSE;
151         }
153         pdf->doc = NULL;
154         tpdfv_open(pdf);
156         pdf->current_page_no = 0;
157         pdf->current_page = poppler_document_get_page(pdf->doc,
158                         pdf->current_page_no);
159         if (! pdf->current_page) {
160                 fprintf(stderr, "Failed to open first page of the document.\n");
161                 return FALSE;
162         }
164         pdf->zoom_mode   = TPDFV_ZOOM_CUSTOM;
165         pdf->zoom_factor = 1.0;
166         pdf->delta_x = pdf->delta_y = 0.0;
167         return TRUE;
168 } /* tpdfv_init */
170 static void
171 tpdfv_clean(gtk_tpdfv_t *pdf)
173         if (! pdf)
174                 return;
176         tpdfv_close(pdf);
177         pdf->current_page = NULL;
178         free(pdf->filename);
179         pdf->filename = NULL;
180         return;
181 } /* tpdfv_clean */
183 static void
184 do_redraw(GtkWidget *widget)
186         GdkRegion *region;
187         gtk_tpdfv_t *pdf;
189         pdf = GTK_TPDFV_GET_PRIVATE(widget);
191         if ((! pdf->current_page) || (poppler_page_get_index(pdf->current_page)
192                         != pdf->current_page_no)) {
193                 pdf->current_page = poppler_document_get_page(pdf->doc,
194                 pdf->current_page_no);
196                 if (! pdf->current_page) {
197                         fprintf(stderr, "Failed to open page %i of the document.\n",
198                                         pdf->current_page_no + 1);
199                         return;
200                 }
201         }
203         region = gdk_drawable_get_clip_region(widget->window);
204         gdk_window_invalidate_region(widget->window, region, TRUE);
205         gdk_window_process_updates(widget->window, TRUE);
207         gdk_region_destroy(region);
208 } /* do_redraw */
210 /*
211  * Gtk+ class definition.
212  */
214 static gboolean
215 gtk_tpdfv_expose(GtkWidget *tpdfv, GdkEventExpose *event)
217         gtk_tpdfv_t *pdf;
218         cairo_t *cr;
220         struct stat statbuf;
222         gdouble width, height;
224         gdouble page_width  = 0.0;
225         gdouble page_height = 0.0;
227         double zoom_factor  = 1.0;
228         double x, y;
230         pdf = GTK_TPDFV_GET_PRIVATE(tpdfv);
232         if (! pdf)
233                 return FALSE;
235         memset(&statbuf, 0, sizeof(statbuf));
236         if (stat(pdf->filename + strlen("file://"), &statbuf)) {
237                 char errbuf[1024];
238                 strerror_r(errno, errbuf, sizeof(errbuf));
239                 fprintf(stderr, "Failed to access PDF: %s.\n", errbuf);
240                 return FALSE;
241         }
243         if (statbuf.st_mtime > pdf->mtime)
244                 gtk_tpdfv_reload(tpdfv);
246         cr = gdk_cairo_create(tpdfv->window);
247         cairo_rectangle(cr, event->area.x, event->area.y,
248                         event->area.width, event->area.height);
249         cairo_clip(cr);
251         poppler_page_get_size(pdf->current_page, &page_width, &page_height);
252         width  = (gdouble)tpdfv->allocation.width;
253         height = (gdouble)tpdfv->allocation.height;
255         /* zoom */
256         if (pdf->zoom_mode == TPDFV_ZOOM_CUSTOM) {
257                 zoom_factor = pdf->zoom_factor;
258         }
259         else {
260                 if (pdf->zoom_mode == TPDFV_ZOOM_WIDTH) {
261                         zoom_factor = (double)(width / page_width);
262                 }
263                 else if (pdf->zoom_mode == TPDFV_ZOOM_HEIGHT) {
264                         zoom_factor = (double)(height / page_height);
265                 }
266                 else if (pdf->zoom_mode == TPDFV_ZOOM_FIT) {
267                         zoom_factor = (double)TPDFV_MIN(width / page_width,
268                                         height / page_height);
269                 }
270                 else {
271                         assert(0);
272                 }
273         }
274         cairo_scale(cr, zoom_factor, zoom_factor);
276         /* centered position / scrolling */
277         x = (double)(width - page_width * zoom_factor) / 2.0 / zoom_factor;
278         y = (double)(height - page_height * zoom_factor) / 2.0 / zoom_factor;
280         cairo_translate(cr, x + pdf->delta_x, y + pdf->delta_y);
282         poppler_page_render(pdf->current_page, cr);
284         cairo_destroy(cr);
285         return FALSE;
286 } /* gtk_tpdfv_expose */
288 static void
289 gtk_tpdfv_destroy(GtkObject *obj)
291         GtkTPDFV    *tpdfv = GTK_TPDFV(obj);
292         gtk_tpdfv_t *pdf;
294         pdf = GTK_TPDFV_GET_PRIVATE(tpdfv);
295         tpdfv_clean(pdf);
297         GTK_OBJECT_CLASS(gtk_tpdfv_parent_class)->destroy(obj);
298 } /* gtk_tpdfv_destroy */
300 static void
301 gtk_tpdfv_class_init(GtkTPDFVClass *class)
303         GObjectClass   *goclass;
304         GtkObjectClass *oclass;
305         GtkWidgetClass *wclass;
307         goclass = G_OBJECT_CLASS(class);
308         g_type_class_add_private(goclass, sizeof(gtk_tpdfv_t));
310         oclass = GTK_OBJECT_CLASS(class);
311         oclass->destroy = gtk_tpdfv_destroy;
313         wclass = GTK_WIDGET_CLASS(class);
314         wclass->expose_event = gtk_tpdfv_expose;
315 } /* gtk_tpdfv_class_init */
317 static void
318 gtk_tpdfv_init(GtkTPDFV __attribute__((unused)) *tpdfv)
320         /* nothing to do for now */
321 } /* gtk_tpdfv_init */
323 /*
324  * Public API.
325  */
327 GtkWidget *
328 gtk_tpdfv_new(const char *filename)
330         GtkTPDFV    *tpdfv;
331         gtk_tpdfv_t *pdf;
333         tpdfv = g_object_new(GTK_TYPE_TPDFV, NULL);
334         pdf   = GTK_TPDFV_GET_PRIVATE(tpdfv);
336         if (TRUE != tpdfv_init(pdf, filename))
337                 return NULL;
338         return GTK_WIDGET(tpdfv);
339 } /* gtk_tpdfv_new */
341 void
342 gtk_tpdfv_reload(GtkWidget *widget)
344         gtk_tpdfv_t *pdf;
346         pdf = GTK_TPDFV_GET_PRIVATE(widget);
347         tpdfv_close(pdf);
348         tpdfv_open(pdf);
349         do_redraw(widget);
350 } /* gtk_tpdfv_reload */
352 void
353 gtk_tpdfv_page_up(GtkWidget *widget)
355         gtk_tpdfv_t *pdf;
357         pdf = GTK_TPDFV_GET_PRIVATE(widget);
359         if (pdf->current_page_no)
360                 --pdf->current_page_no;
361         do_redraw(widget);
362 } /* gtk_tpdfv_page_up */
364 void
365 gtk_tpdfv_page_down(GtkWidget *widget)
367         gtk_tpdfv_t *pdf;
369         pdf = GTK_TPDFV_GET_PRIVATE(widget);
371         if (pdf->current_page_no < pdf->total_pages - 1)
372                 ++pdf->current_page_no;
373         do_redraw(widget);
374 } /* gtk_tpdfv_page_down */
376 void
377 gtk_tpdfv_first_page(GtkWidget *widget)
379         gtk_tpdfv_t *pdf;
381         pdf = GTK_TPDFV_GET_PRIVATE(widget);
382         pdf->current_page_no = 0;
383         do_redraw(widget);
384 } /* gtk_tpdfv_first_page */
386 void
387 gtk_tpdfv_last_page(GtkWidget *widget)
389         gtk_tpdfv_t *pdf;
391         pdf = GTK_TPDFV_GET_PRIVATE(widget);
392         pdf->current_page_no = pdf->total_pages - 1;
393         do_redraw(widget);
394 } /* gtk_tpdfv_last_page */
396 void
397 gtk_tpdfv_zoom_in(GtkWidget *widget)
399         gtk_tpdfv_t *pdf;
401         pdf = GTK_TPDFV_GET_PRIVATE(widget);
402         pdf->zoom_factor *= 1.2;
403         pdf->zoom_mode    = TPDFV_ZOOM_CUSTOM;
404         do_redraw(widget);
405 } /* gtk_tpdfv_zoom_in */
407 void
408 gtk_tpdfv_zoom_out(GtkWidget *widget)
410         gtk_tpdfv_t *pdf;
412         pdf = GTK_TPDFV_GET_PRIVATE(widget);
414         if (pdf->zoom_factor > DBL_EPSILON * 2.0) {
415                 pdf->zoom_factor /= 1.2;
416                 pdf->zoom_mode    = TPDFV_ZOOM_CUSTOM;
417                 do_redraw(widget);
418         }
419 } /* gtk_tpdfv_zoom_out */
421 void
422 gtk_tpdfv_zoom_1(GtkWidget *widget)
424         gtk_tpdfv_t *pdf;
426         pdf = GTK_TPDFV_GET_PRIVATE(widget);
427         pdf->zoom_factor = 1.0;
428         pdf->zoom_mode   = TPDFV_ZOOM_CUSTOM;
429         do_redraw(widget);
430 } /* gtk_tpdfv_zoom_1 */
432 void
433 gtk_tpdfv_zoom_width(GtkWidget *widget)
435         gtk_tpdfv_t *pdf;
437         pdf = GTK_TPDFV_GET_PRIVATE(widget);
438         pdf->zoom_mode = TPDFV_ZOOM_WIDTH;
439         do_redraw(widget);
440 } /* gtk_tpdfv_zoom_width */
442 void
443 gtk_tpdfv_zoom_height(GtkWidget *widget)
445         gtk_tpdfv_t *pdf;
447         pdf = GTK_TPDFV_GET_PRIVATE(widget);
448         pdf->zoom_mode = TPDFV_ZOOM_HEIGHT;
449         do_redraw(widget);
450 } /* gtk_tpdfv_zoom_width */
452 void
453 gtk_tpdfv_zoom_fit(GtkWidget *widget)
455         gtk_tpdfv_t *pdf;
457         pdf = GTK_TPDFV_GET_PRIVATE(widget);
458         pdf->zoom_mode = TPDFV_ZOOM_FIT;
459         do_redraw(widget);
460 } /* gtk_tpdfv_zoom_width */
462 void
463 gtk_tpdfv_scroll_up(GtkWidget *widget)
465         gtk_tpdfv_t *pdf;
467         pdf = GTK_TPDFV_GET_PRIVATE(widget);
468         pdf->delta_y += 10.0;
469         do_redraw(widget);
470 } /* gtk_tpdfv_scroll_up */
472 void
473 gtk_tpdfv_scroll_down(GtkWidget *widget)
475         gtk_tpdfv_t *pdf;
477         pdf = GTK_TPDFV_GET_PRIVATE(widget);
478         pdf->delta_y -= 10.0;
479         do_redraw(widget);
480 } /* gtk_tpdfv_scroll_down */
482 void
483 gtk_tpdfv_scroll_left(GtkWidget *widget)
485         gtk_tpdfv_t *pdf;
487         pdf = GTK_TPDFV_GET_PRIVATE(widget);
488         pdf->delta_x += 10.0;
489         do_redraw(widget);
490 } /* gtk_tpdfv_scroll_left */
492 void
493 gtk_tpdfv_scroll_right(GtkWidget *widget)
495         gtk_tpdfv_t *pdf;
497         pdf = GTK_TPDFV_GET_PRIVATE(widget);
498         pdf->delta_x -= 10.0;
499         do_redraw(widget);
500 } /* gtk_tpdfv_scroll_right */
502 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */