Code

gtk-tpdfv: Create an empty white page to draw the PDF page on.
[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         /* draw empty white page */
283         cairo_save(cr);
285         cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0);
286         cairo_rectangle(cr, 0.0, 0.0, page_width, page_height);
287         cairo_fill(cr);
289         cairo_restore(cr);
291         poppler_page_render(pdf->current_page, cr);
293         cairo_destroy(cr);
294         return FALSE;
295 } /* gtk_tpdfv_expose */
297 static void
298 gtk_tpdfv_destroy(GtkObject *obj)
300         GtkTPDFV    *tpdfv = GTK_TPDFV(obj);
301         gtk_tpdfv_t *pdf;
303         pdf = GTK_TPDFV_GET_PRIVATE(tpdfv);
304         tpdfv_clean(pdf);
306         GTK_OBJECT_CLASS(gtk_tpdfv_parent_class)->destroy(obj);
307 } /* gtk_tpdfv_destroy */
309 static void
310 gtk_tpdfv_class_init(GtkTPDFVClass *class)
312         GObjectClass   *goclass;
313         GtkObjectClass *oclass;
314         GtkWidgetClass *wclass;
316         goclass = G_OBJECT_CLASS(class);
317         g_type_class_add_private(goclass, sizeof(gtk_tpdfv_t));
319         oclass = GTK_OBJECT_CLASS(class);
320         oclass->destroy = gtk_tpdfv_destroy;
322         wclass = GTK_WIDGET_CLASS(class);
323         wclass->expose_event = gtk_tpdfv_expose;
324 } /* gtk_tpdfv_class_init */
326 static void
327 gtk_tpdfv_init(GtkTPDFV __attribute__((unused)) *tpdfv)
329         /* nothing to do for now */
330 } /* gtk_tpdfv_init */
332 /*
333  * Public API.
334  */
336 GtkWidget *
337 gtk_tpdfv_new(const char *filename)
339         GtkTPDFV    *tpdfv;
340         gtk_tpdfv_t *pdf;
342         tpdfv = g_object_new(GTK_TYPE_TPDFV, NULL);
343         pdf   = GTK_TPDFV_GET_PRIVATE(tpdfv);
345         if (TRUE != tpdfv_init(pdf, filename))
346                 return NULL;
347         return GTK_WIDGET(tpdfv);
348 } /* gtk_tpdfv_new */
350 void
351 gtk_tpdfv_reload(GtkWidget *widget)
353         gtk_tpdfv_t *pdf;
355         pdf = GTK_TPDFV_GET_PRIVATE(widget);
356         tpdfv_close(pdf);
357         tpdfv_open(pdf);
358         do_redraw(widget);
359 } /* gtk_tpdfv_reload */
361 void
362 gtk_tpdfv_page_up(GtkWidget *widget)
364         gtk_tpdfv_t *pdf;
366         pdf = GTK_TPDFV_GET_PRIVATE(widget);
368         if (pdf->current_page_no)
369                 --pdf->current_page_no;
370         do_redraw(widget);
371 } /* gtk_tpdfv_page_up */
373 void
374 gtk_tpdfv_page_down(GtkWidget *widget)
376         gtk_tpdfv_t *pdf;
378         pdf = GTK_TPDFV_GET_PRIVATE(widget);
380         if (pdf->current_page_no < pdf->total_pages - 1)
381                 ++pdf->current_page_no;
382         do_redraw(widget);
383 } /* gtk_tpdfv_page_down */
385 void
386 gtk_tpdfv_first_page(GtkWidget *widget)
388         gtk_tpdfv_t *pdf;
390         pdf = GTK_TPDFV_GET_PRIVATE(widget);
391         pdf->current_page_no = 0;
392         do_redraw(widget);
393 } /* gtk_tpdfv_first_page */
395 void
396 gtk_tpdfv_last_page(GtkWidget *widget)
398         gtk_tpdfv_t *pdf;
400         pdf = GTK_TPDFV_GET_PRIVATE(widget);
401         pdf->current_page_no = pdf->total_pages - 1;
402         do_redraw(widget);
403 } /* gtk_tpdfv_last_page */
405 void
406 gtk_tpdfv_zoom_in(GtkWidget *widget)
408         gtk_tpdfv_t *pdf;
410         pdf = GTK_TPDFV_GET_PRIVATE(widget);
411         pdf->zoom_factor *= 1.2;
412         pdf->zoom_mode    = TPDFV_ZOOM_CUSTOM;
413         do_redraw(widget);
414 } /* gtk_tpdfv_zoom_in */
416 void
417 gtk_tpdfv_zoom_out(GtkWidget *widget)
419         gtk_tpdfv_t *pdf;
421         pdf = GTK_TPDFV_GET_PRIVATE(widget);
423         if (pdf->zoom_factor > DBL_EPSILON * 2.0) {
424                 pdf->zoom_factor /= 1.2;
425                 pdf->zoom_mode    = TPDFV_ZOOM_CUSTOM;
426                 do_redraw(widget);
427         }
428 } /* gtk_tpdfv_zoom_out */
430 void
431 gtk_tpdfv_zoom_1(GtkWidget *widget)
433         gtk_tpdfv_t *pdf;
435         pdf = GTK_TPDFV_GET_PRIVATE(widget);
436         pdf->zoom_factor = 1.0;
437         pdf->zoom_mode   = TPDFV_ZOOM_CUSTOM;
438         do_redraw(widget);
439 } /* gtk_tpdfv_zoom_1 */
441 void
442 gtk_tpdfv_zoom_width(GtkWidget *widget)
444         gtk_tpdfv_t *pdf;
446         pdf = GTK_TPDFV_GET_PRIVATE(widget);
447         pdf->zoom_mode = TPDFV_ZOOM_WIDTH;
448         do_redraw(widget);
449 } /* gtk_tpdfv_zoom_width */
451 void
452 gtk_tpdfv_zoom_height(GtkWidget *widget)
454         gtk_tpdfv_t *pdf;
456         pdf = GTK_TPDFV_GET_PRIVATE(widget);
457         pdf->zoom_mode = TPDFV_ZOOM_HEIGHT;
458         do_redraw(widget);
459 } /* gtk_tpdfv_zoom_width */
461 void
462 gtk_tpdfv_zoom_fit(GtkWidget *widget)
464         gtk_tpdfv_t *pdf;
466         pdf = GTK_TPDFV_GET_PRIVATE(widget);
467         pdf->zoom_mode = TPDFV_ZOOM_FIT;
468         do_redraw(widget);
469 } /* gtk_tpdfv_zoom_width */
471 void
472 gtk_tpdfv_scroll_up(GtkWidget *widget)
474         gtk_tpdfv_t *pdf;
476         pdf = GTK_TPDFV_GET_PRIVATE(widget);
477         pdf->delta_y += 10.0;
478         do_redraw(widget);
479 } /* gtk_tpdfv_scroll_up */
481 void
482 gtk_tpdfv_scroll_down(GtkWidget *widget)
484         gtk_tpdfv_t *pdf;
486         pdf = GTK_TPDFV_GET_PRIVATE(widget);
487         pdf->delta_y -= 10.0;
488         do_redraw(widget);
489 } /* gtk_tpdfv_scroll_down */
491 void
492 gtk_tpdfv_scroll_left(GtkWidget *widget)
494         gtk_tpdfv_t *pdf;
496         pdf = GTK_TPDFV_GET_PRIVATE(widget);
497         pdf->delta_x += 10.0;
498         do_redraw(widget);
499 } /* gtk_tpdfv_scroll_left */
501 void
502 gtk_tpdfv_scroll_right(GtkWidget *widget)
504         gtk_tpdfv_t *pdf;
506         pdf = GTK_TPDFV_GET_PRIVATE(widget);
507         pdf->delta_x -= 10.0;
508         do_redraw(widget);
509 } /* gtk_tpdfv_scroll_right */
511 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */