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)
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 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)
172 {
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)
185 {
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)
216 {
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)
299 {
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)
311 {
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)
328 {
329 /* nothing to do for now */
330 } /* gtk_tpdfv_init */
332 /*
333 * Public API.
334 */
336 GtkWidget *
337 gtk_tpdfv_new(const char *filename)
338 {
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)
352 {
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 int
362 gtk_tpdfv_get_n_pages(GtkWidget *widget)
363 {
364 gtk_tpdfv_t *pdf;
366 if (! widget)
367 return -1;
369 pdf = GTK_TPDFV_GET_PRIVATE(widget);
370 return pdf->total_pages;
371 } /* gtk_tpdfv_get_n_pages */
373 int
374 gtk_tpdfv_get_current_page(GtkWidget *widget)
375 {
376 gtk_tpdfv_t *pdf;
378 if (! widget)
379 return -1;
381 pdf = GTK_TPDFV_GET_PRIVATE(widget);
382 return pdf->current_page_no;
383 } /* gtk_tpdfv_get_current_page */
385 void
386 gtk_tpdfv_page_up(GtkWidget *widget)
387 {
388 gtk_tpdfv_t *pdf;
390 pdf = GTK_TPDFV_GET_PRIVATE(widget);
392 if (pdf->current_page_no)
393 --pdf->current_page_no;
394 do_redraw(widget);
395 } /* gtk_tpdfv_page_up */
397 void
398 gtk_tpdfv_page_down(GtkWidget *widget)
399 {
400 gtk_tpdfv_t *pdf;
402 pdf = GTK_TPDFV_GET_PRIVATE(widget);
404 if (pdf->current_page_no < pdf->total_pages - 1)
405 ++pdf->current_page_no;
406 do_redraw(widget);
407 } /* gtk_tpdfv_page_down */
409 void
410 gtk_tpdfv_first_page(GtkWidget *widget)
411 {
412 gtk_tpdfv_t *pdf;
414 pdf = GTK_TPDFV_GET_PRIVATE(widget);
415 pdf->current_page_no = 0;
416 do_redraw(widget);
417 } /* gtk_tpdfv_first_page */
419 void
420 gtk_tpdfv_last_page(GtkWidget *widget)
421 {
422 gtk_tpdfv_t *pdf;
424 pdf = GTK_TPDFV_GET_PRIVATE(widget);
425 pdf->current_page_no = pdf->total_pages - 1;
426 do_redraw(widget);
427 } /* gtk_tpdfv_last_page */
429 void
430 gtk_tpdfv_zoom_in(GtkWidget *widget)
431 {
432 gtk_tpdfv_t *pdf;
434 pdf = GTK_TPDFV_GET_PRIVATE(widget);
435 pdf->zoom_factor *= 1.2;
436 pdf->zoom_mode = TPDFV_ZOOM_CUSTOM;
437 do_redraw(widget);
438 } /* gtk_tpdfv_zoom_in */
440 void
441 gtk_tpdfv_zoom_out(GtkWidget *widget)
442 {
443 gtk_tpdfv_t *pdf;
445 pdf = GTK_TPDFV_GET_PRIVATE(widget);
447 if (pdf->zoom_factor > DBL_EPSILON * 2.0) {
448 pdf->zoom_factor /= 1.2;
449 pdf->zoom_mode = TPDFV_ZOOM_CUSTOM;
450 do_redraw(widget);
451 }
452 } /* gtk_tpdfv_zoom_out */
454 void
455 gtk_tpdfv_zoom_1(GtkWidget *widget)
456 {
457 gtk_tpdfv_t *pdf;
459 pdf = GTK_TPDFV_GET_PRIVATE(widget);
460 pdf->zoom_factor = 1.0;
461 pdf->zoom_mode = TPDFV_ZOOM_CUSTOM;
462 do_redraw(widget);
463 } /* gtk_tpdfv_zoom_1 */
465 void
466 gtk_tpdfv_zoom_width(GtkWidget *widget)
467 {
468 gtk_tpdfv_t *pdf;
470 pdf = GTK_TPDFV_GET_PRIVATE(widget);
471 pdf->zoom_mode = TPDFV_ZOOM_WIDTH;
472 do_redraw(widget);
473 } /* gtk_tpdfv_zoom_width */
475 void
476 gtk_tpdfv_zoom_height(GtkWidget *widget)
477 {
478 gtk_tpdfv_t *pdf;
480 pdf = GTK_TPDFV_GET_PRIVATE(widget);
481 pdf->zoom_mode = TPDFV_ZOOM_HEIGHT;
482 do_redraw(widget);
483 } /* gtk_tpdfv_zoom_width */
485 void
486 gtk_tpdfv_zoom_fit(GtkWidget *widget)
487 {
488 gtk_tpdfv_t *pdf;
490 pdf = GTK_TPDFV_GET_PRIVATE(widget);
491 pdf->zoom_mode = TPDFV_ZOOM_FIT;
492 do_redraw(widget);
493 } /* gtk_tpdfv_zoom_width */
495 void
496 gtk_tpdfv_scroll_up(GtkWidget *widget)
497 {
498 gtk_tpdfv_t *pdf;
500 pdf = GTK_TPDFV_GET_PRIVATE(widget);
501 pdf->delta_y += 10.0;
502 do_redraw(widget);
503 } /* gtk_tpdfv_scroll_up */
505 void
506 gtk_tpdfv_scroll_down(GtkWidget *widget)
507 {
508 gtk_tpdfv_t *pdf;
510 pdf = GTK_TPDFV_GET_PRIVATE(widget);
511 pdf->delta_y -= 10.0;
512 do_redraw(widget);
513 } /* gtk_tpdfv_scroll_down */
515 void
516 gtk_tpdfv_scroll_left(GtkWidget *widget)
517 {
518 gtk_tpdfv_t *pdf;
520 pdf = GTK_TPDFV_GET_PRIVATE(widget);
521 pdf->delta_x += 10.0;
522 do_redraw(widget);
523 } /* gtk_tpdfv_scroll_left */
525 void
526 gtk_tpdfv_scroll_right(GtkWidget *widget)
527 {
528 gtk_tpdfv_t *pdf;
530 pdf = GTK_TPDFV_GET_PRIVATE(widget);
531 pdf->delta_x -= 10.0;
532 do_redraw(widget);
533 } /* gtk_tpdfv_scroll_right */
535 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */