Code

gtk-tpdfv: Support relative pathnames as well.
[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         gchar *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         GError *err = NULL;
134         char *scheme;
136         if ((! pdf) || (! filename))
137                 return FALSE;
139         scheme = g_uri_parse_scheme(filename);
140         if (scheme)
141                 filename += strlen(scheme);
143         if (g_path_is_absolute(filename)) {
144                 pdf->filename = g_filename_to_uri(filename,
145                                 /* hostname = */ NULL, &err);
146         }
147         else {
148                 gchar *tmp = g_build_filename(g_get_current_dir(), filename, NULL);
149                 pdf->filename = g_filename_to_uri(tmp, /* hostname = */ NULL, &err);
150                 g_free(tmp);
151         }
153         /* XXX: error reporting mechanism */
155         if (! pdf->filename) {
156                 if (err)
157                         fprintf(stderr, "Failed to allocate string: %s.\n", err->message);
158                 return FALSE;
159         }
161         pdf->doc = NULL;
162         tpdfv_open(pdf);
164         pdf->current_page_no = 0;
165         pdf->current_page = poppler_document_get_page(pdf->doc,
166                         pdf->current_page_no);
167         if (! pdf->current_page) {
168                 fprintf(stderr, "Failed to open first page of the document.\n");
169                 return FALSE;
170         }
172         pdf->zoom_mode   = TPDFV_ZOOM_CUSTOM;
173         pdf->zoom_factor = 1.0;
174         pdf->delta_x = pdf->delta_y = 0.0;
175         return TRUE;
176 } /* tpdfv_init */
178 static void
179 tpdfv_clean(gtk_tpdfv_t *pdf)
181         if (! pdf)
182                 return;
184         tpdfv_close(pdf);
185         pdf->current_page = NULL;
186         g_free(pdf->filename);
187         pdf->filename = NULL;
188         return;
189 } /* tpdfv_clean */
191 static void
192 do_redraw(GtkWidget *widget)
194         GdkRegion *region;
195         gtk_tpdfv_t *pdf;
197         pdf = GTK_TPDFV_GET_PRIVATE(widget);
199         if ((! pdf->current_page) || (poppler_page_get_index(pdf->current_page)
200                         != pdf->current_page_no)) {
201                 pdf->current_page = poppler_document_get_page(pdf->doc,
202                 pdf->current_page_no);
204                 if (! pdf->current_page) {
205                         fprintf(stderr, "Failed to open page %i of the document.\n",
206                                         pdf->current_page_no + 1);
207                         return;
208                 }
209         }
211         region = gdk_drawable_get_clip_region(widget->window);
212         gdk_window_invalidate_region(widget->window, region, TRUE);
213         gdk_window_process_updates(widget->window, TRUE);
215         gdk_region_destroy(region);
216 } /* do_redraw */
218 /*
219  * Gtk+ class definition.
220  */
222 static gboolean
223 gtk_tpdfv_expose(GtkWidget *tpdfv, GdkEventExpose *event)
225         gtk_tpdfv_t *pdf;
226         cairo_t *cr;
228         struct stat statbuf;
230         gdouble width, height;
232         gdouble page_width  = 0.0;
233         gdouble page_height = 0.0;
235         double zoom_factor  = 1.0;
236         double x, y;
238         pdf = GTK_TPDFV_GET_PRIVATE(tpdfv);
240         if (! pdf)
241                 return FALSE;
243         memset(&statbuf, 0, sizeof(statbuf));
244         if (stat(pdf->filename + strlen("file://"), &statbuf)) {
245                 char errbuf[1024];
246                 strerror_r(errno, errbuf, sizeof(errbuf));
247                 fprintf(stderr, "Failed to access PDF: %s.\n", errbuf);
248                 return FALSE;
249         }
251         if (statbuf.st_mtime > pdf->mtime)
252                 gtk_tpdfv_reload(tpdfv);
254         cr = gdk_cairo_create(tpdfv->window);
255         cairo_rectangle(cr, event->area.x, event->area.y,
256                         event->area.width, event->area.height);
257         cairo_clip(cr);
259         poppler_page_get_size(pdf->current_page, &page_width, &page_height);
260         width  = (gdouble)tpdfv->allocation.width;
261         height = (gdouble)tpdfv->allocation.height;
263         /* zoom */
264         if (pdf->zoom_mode == TPDFV_ZOOM_CUSTOM) {
265                 zoom_factor = pdf->zoom_factor;
266         }
267         else {
268                 if (pdf->zoom_mode == TPDFV_ZOOM_WIDTH) {
269                         zoom_factor = (double)(width / page_width);
270                 }
271                 else if (pdf->zoom_mode == TPDFV_ZOOM_HEIGHT) {
272                         zoom_factor = (double)(height / page_height);
273                 }
274                 else if (pdf->zoom_mode == TPDFV_ZOOM_FIT) {
275                         zoom_factor = (double)TPDFV_MIN(width / page_width,
276                                         height / page_height);
277                 }
278                 else {
279                         assert(0);
280                 }
281         }
282         cairo_scale(cr, zoom_factor, zoom_factor);
284         /* centered position / scrolling */
285         x = (double)(width - page_width * zoom_factor) / 2.0 / zoom_factor;
286         y = (double)(height - page_height * zoom_factor) / 2.0 / zoom_factor;
288         cairo_translate(cr, x + pdf->delta_x, y + pdf->delta_y);
290         /* draw empty white page */
291         cairo_save(cr);
293         cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0);
294         cairo_rectangle(cr, 0.0, 0.0, page_width, page_height);
295         cairo_fill(cr);
297         cairo_restore(cr);
299         poppler_page_render(pdf->current_page, cr);
301         cairo_destroy(cr);
302         return FALSE;
303 } /* gtk_tpdfv_expose */
305 static void
306 gtk_tpdfv_destroy(GtkObject *obj)
308         GtkTPDFV    *tpdfv = GTK_TPDFV(obj);
309         gtk_tpdfv_t *pdf;
311         pdf = GTK_TPDFV_GET_PRIVATE(tpdfv);
312         tpdfv_clean(pdf);
314         GTK_OBJECT_CLASS(gtk_tpdfv_parent_class)->destroy(obj);
315 } /* gtk_tpdfv_destroy */
317 static void
318 gtk_tpdfv_class_init(GtkTPDFVClass *class)
320         GObjectClass   *goclass;
321         GtkObjectClass *oclass;
322         GtkWidgetClass *wclass;
324         goclass = G_OBJECT_CLASS(class);
325         g_type_class_add_private(goclass, sizeof(gtk_tpdfv_t));
327         oclass = GTK_OBJECT_CLASS(class);
328         oclass->destroy = gtk_tpdfv_destroy;
330         wclass = GTK_WIDGET_CLASS(class);
331         wclass->expose_event = gtk_tpdfv_expose;
332 } /* gtk_tpdfv_class_init */
334 static void
335 gtk_tpdfv_init(GtkTPDFV __attribute__((unused)) *tpdfv)
337         /* nothing to do for now */
338 } /* gtk_tpdfv_init */
340 /*
341  * Public API.
342  */
344 GtkWidget *
345 gtk_tpdfv_new(const char *filename)
347         GtkTPDFV    *tpdfv;
348         gtk_tpdfv_t *pdf;
350         tpdfv = g_object_new(GTK_TYPE_TPDFV, NULL);
351         pdf   = GTK_TPDFV_GET_PRIVATE(tpdfv);
353         if (TRUE != tpdfv_init(pdf, filename))
354                 return NULL;
355         return GTK_WIDGET(tpdfv);
356 } /* gtk_tpdfv_new */
358 void
359 gtk_tpdfv_reload(GtkWidget *widget)
361         gtk_tpdfv_t *pdf;
363         pdf = GTK_TPDFV_GET_PRIVATE(widget);
364         tpdfv_close(pdf);
365         tpdfv_open(pdf);
366         do_redraw(widget);
367 } /* gtk_tpdfv_reload */
369 int
370 gtk_tpdfv_get_n_pages(GtkWidget *widget)
372         gtk_tpdfv_t *pdf;
374         if (! widget)
375                 return -1;
377         pdf = GTK_TPDFV_GET_PRIVATE(widget);
378         return pdf->total_pages;
379 } /* gtk_tpdfv_get_n_pages */
381 int
382 gtk_tpdfv_get_current_page(GtkWidget *widget)
384         gtk_tpdfv_t *pdf;
386         if (! widget)
387                 return -1;
389         pdf = GTK_TPDFV_GET_PRIVATE(widget);
390         return pdf->current_page_no;
391 } /* gtk_tpdfv_get_current_page */
393 void
394 gtk_tpdfv_page_up(GtkWidget *widget)
396         gtk_tpdfv_t *pdf;
398         pdf = GTK_TPDFV_GET_PRIVATE(widget);
400         if (pdf->current_page_no)
401                 --pdf->current_page_no;
402         do_redraw(widget);
403 } /* gtk_tpdfv_page_up */
405 void
406 gtk_tpdfv_page_down(GtkWidget *widget)
408         gtk_tpdfv_t *pdf;
410         pdf = GTK_TPDFV_GET_PRIVATE(widget);
412         if (pdf->current_page_no < pdf->total_pages - 1)
413                 ++pdf->current_page_no;
414         do_redraw(widget);
415 } /* gtk_tpdfv_page_down */
417 void
418 gtk_tpdfv_first_page(GtkWidget *widget)
420         gtk_tpdfv_t *pdf;
422         pdf = GTK_TPDFV_GET_PRIVATE(widget);
423         pdf->current_page_no = 0;
424         do_redraw(widget);
425 } /* gtk_tpdfv_first_page */
427 void
428 gtk_tpdfv_last_page(GtkWidget *widget)
430         gtk_tpdfv_t *pdf;
432         pdf = GTK_TPDFV_GET_PRIVATE(widget);
433         pdf->current_page_no = pdf->total_pages - 1;
434         do_redraw(widget);
435 } /* gtk_tpdfv_last_page */
437 void
438 gtk_tpdfv_goto_page(GtkWidget *widget, int page)
440         gtk_tpdfv_t *pdf;
442         pdf = GTK_TPDFV_GET_PRIVATE(widget);
444         if ((page < 0) || (page >= pdf->total_pages))
445                 return;
447         pdf->current_page_no = page;
448         do_redraw(widget);
449 } /* gtk_tpdfv_goto_page */
451 void
452 gtk_tpdfv_zoom_in(GtkWidget *widget)
454         gtk_tpdfv_t *pdf;
456         pdf = GTK_TPDFV_GET_PRIVATE(widget);
457         pdf->zoom_factor *= 1.2;
458         pdf->zoom_mode    = TPDFV_ZOOM_CUSTOM;
459         do_redraw(widget);
460 } /* gtk_tpdfv_zoom_in */
462 void
463 gtk_tpdfv_zoom_out(GtkWidget *widget)
465         gtk_tpdfv_t *pdf;
467         pdf = GTK_TPDFV_GET_PRIVATE(widget);
469         if (pdf->zoom_factor > DBL_EPSILON * 2.0) {
470                 pdf->zoom_factor /= 1.2;
471                 pdf->zoom_mode    = TPDFV_ZOOM_CUSTOM;
472                 do_redraw(widget);
473         }
474 } /* gtk_tpdfv_zoom_out */
476 void
477 gtk_tpdfv_zoom_1(GtkWidget *widget)
479         gtk_tpdfv_t *pdf;
481         pdf = GTK_TPDFV_GET_PRIVATE(widget);
482         pdf->zoom_factor = 1.0;
483         pdf->zoom_mode   = TPDFV_ZOOM_CUSTOM;
484         do_redraw(widget);
485 } /* gtk_tpdfv_zoom_1 */
487 void
488 gtk_tpdfv_zoom_width(GtkWidget *widget)
490         gtk_tpdfv_t *pdf;
492         pdf = GTK_TPDFV_GET_PRIVATE(widget);
493         pdf->zoom_mode = TPDFV_ZOOM_WIDTH;
494         do_redraw(widget);
495 } /* gtk_tpdfv_zoom_width */
497 void
498 gtk_tpdfv_zoom_height(GtkWidget *widget)
500         gtk_tpdfv_t *pdf;
502         pdf = GTK_TPDFV_GET_PRIVATE(widget);
503         pdf->zoom_mode = TPDFV_ZOOM_HEIGHT;
504         do_redraw(widget);
505 } /* gtk_tpdfv_zoom_width */
507 void
508 gtk_tpdfv_zoom_fit(GtkWidget *widget)
510         gtk_tpdfv_t *pdf;
512         pdf = GTK_TPDFV_GET_PRIVATE(widget);
513         pdf->zoom_mode = TPDFV_ZOOM_FIT;
514         do_redraw(widget);
515 } /* gtk_tpdfv_zoom_width */
517 void
518 gtk_tpdfv_scroll_up(GtkWidget *widget)
520         gtk_tpdfv_t *pdf;
522         pdf = GTK_TPDFV_GET_PRIVATE(widget);
523         pdf->delta_y += 10.0;
524         do_redraw(widget);
525 } /* gtk_tpdfv_scroll_up */
527 void
528 gtk_tpdfv_scroll_down(GtkWidget *widget)
530         gtk_tpdfv_t *pdf;
532         pdf = GTK_TPDFV_GET_PRIVATE(widget);
533         pdf->delta_y -= 10.0;
534         do_redraw(widget);
535 } /* gtk_tpdfv_scroll_down */
537 void
538 gtk_tpdfv_scroll_left(GtkWidget *widget)
540         gtk_tpdfv_t *pdf;
542         pdf = GTK_TPDFV_GET_PRIVATE(widget);
543         pdf->delta_x += 10.0;
544         do_redraw(widget);
545 } /* gtk_tpdfv_scroll_left */
547 void
548 gtk_tpdfv_scroll_right(GtkWidget *widget)
550         gtk_tpdfv_t *pdf;
552         pdf = GTK_TPDFV_GET_PRIVATE(widget);
553         pdf->delta_x -= 10.0;
554         do_redraw(widget);
555 } /* gtk_tpdfv_scroll_right */
557 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */