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)
120 {
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)
131 {
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)
180 {
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)
193 {
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)
224 {
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)
307 {
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)
319 {
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)
336 {
337 /* nothing to do for now */
338 } /* gtk_tpdfv_init */
340 /*
341 * Public API.
342 */
344 GtkWidget *
345 gtk_tpdfv_new(const char *filename)
346 {
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)
360 {
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)
371 {
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)
383 {
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)
395 {
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)
407 {
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)
419 {
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)
429 {
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)
439 {
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)
453 {
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)
464 {
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)
478 {
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)
489 {
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)
499 {
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)
509 {
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)
519 {
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)
529 {
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)
539 {
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)
549 {
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 : */