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 #define TPDFV_MIN(a, b) ((a) <= (b) ? (a) : (b))
47 #define TPDFV_MAX(a, b) ((a) >= (b) ? (a) : (b))
49 typedef enum {
50 TPDFV_ZOOM_CUSTOM = 0,
51 TPDFV_ZOOM_WIDTH,
52 TPDFV_ZOOM_HEIGHT,
53 TPDFV_ZOOM_FIT,
54 } tpdfv_zoommode_t;
56 typedef struct {
57 char *filename;
59 PopplerDocument *doc;
60 PopplerPage *current_page;
62 int current_page_no;
63 int total_pages;
65 tpdfv_zoommode_t zoom_mode;
66 double zoom_factor;
68 double delta_x;
69 double delta_y;
70 } gtk_tpdfv_t;
72 GType gtk_tpdfv_get_type(void);
73 G_DEFINE_TYPE(GtkTPDFV, gtk_tpdfv, GTK_TYPE_DRAWING_AREA);
75 #define GTK_TPDFV_GET_PRIVATE(obj) \
76 (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_TPDFV, gtk_tpdfv_t))
78 /*
79 * Private helper functions.
80 */
82 static void
83 tpdfv_open(gtk_tpdfv_t *pdf)
84 {
85 GError *err = NULL;
87 if ((! pdf) || (! pdf->filename))
88 return;
90 pdf->doc = poppler_document_new_from_file(pdf->filename,
91 /* password = */ NULL, &err);
92 if (! pdf->doc) {
93 fprintf(stderr, "Failed to open PDF: %s.\n", err->message);
94 return;
95 }
97 pdf->total_pages = poppler_document_get_n_pages(pdf->doc);
98 if (pdf->current_page_no >= pdf->total_pages)
99 pdf->current_page_no = pdf->total_pages - 1;
100 } /* tpdfv_open */
102 static void
103 tpdfv_close(gtk_tpdfv_t *pdf)
104 {
105 if (! pdf)
106 return;
108 g_object_unref(pdf->doc);
109 pdf->doc = NULL;
110 pdf->current_page = NULL;
111 } /* tpdfv_close */
113 static gboolean
114 tpdfv_init(gtk_tpdfv_t *pdf, const char *filename)
115 {
116 if (strstr(filename, "://"))
117 pdf->filename = strdup(filename);
118 else {
119 size_t len = strlen("file://") + strlen(filename);
120 pdf->filename = (char *)malloc(len + 1);
121 if (pdf->filename) {
122 *pdf->filename = '\0';
123 strncat(pdf->filename, "file://", len);
124 strncat(pdf->filename, filename, len - strlen("file://"));
125 }
126 }
128 /* XXX: error reporting mechanism */
130 if (! pdf->filename) {
131 char errbuf[1024];
132 strerror_r(errno, errbuf, sizeof(errbuf));
133 fprintf(stderr, "Failed to allocate string: %s.\n", errbuf);
134 return FALSE;
135 }
137 pdf->doc = NULL;
138 tpdfv_open(pdf);
140 pdf->current_page_no = 0;
141 pdf->current_page = poppler_document_get_page(pdf->doc,
142 pdf->current_page_no);
143 if (! pdf->current_page) {
144 fprintf(stderr, "Failed to open first page of the document.\n");
145 return FALSE;
146 }
148 pdf->zoom_mode = TPDFV_ZOOM_CUSTOM;
149 pdf->zoom_factor = 1.0;
150 pdf->delta_x = pdf->delta_y = 0.0;
151 return TRUE;
152 } /* tpdfv_init */
154 static void
155 tpdfv_clean(gtk_tpdfv_t *pdf)
156 {
157 if (! pdf)
158 return;
160 tpdfv_close(pdf);
161 pdf->current_page = NULL;
162 free(pdf->filename);
163 pdf->filename = NULL;
164 return;
165 } /* tpdfv_clean */
167 static void
168 do_redraw(GtkWidget *widget)
169 {
170 GdkRegion *region;
171 gtk_tpdfv_t *pdf;
173 pdf = GTK_TPDFV_GET_PRIVATE(widget);
175 if ((! pdf->current_page) || (poppler_page_get_index(pdf->current_page)
176 != pdf->current_page_no)) {
177 pdf->current_page = poppler_document_get_page(pdf->doc,
178 pdf->current_page_no);
180 if (! pdf->current_page) {
181 fprintf(stderr, "Failed to open page %i of the document.\n",
182 pdf->current_page_no + 1);
183 return;
184 }
185 }
187 region = gdk_drawable_get_clip_region(widget->window);
188 gdk_window_invalidate_region(widget->window, region, TRUE);
189 gdk_window_process_updates(widget->window, TRUE);
191 gdk_region_destroy(region);
192 } /* do_redraw */
194 /*
195 * Gtk+ class definition.
196 */
198 static gboolean
199 gtk_tpdfv_expose(GtkWidget *tpdfv, GdkEventExpose *event)
200 {
201 gtk_tpdfv_t *pdf;
202 cairo_t *cr;
204 gdouble width, height;
206 gdouble page_width = 0.0;
207 gdouble page_height = 0.0;
209 double zoom_factor = 1.0;
210 double x, y;
212 pdf = GTK_TPDFV_GET_PRIVATE(tpdfv);
214 if (! pdf)
215 return FALSE;
217 cr = gdk_cairo_create(tpdfv->window);
218 cairo_rectangle(cr, event->area.x, event->area.y,
219 event->area.width, event->area.height);
220 cairo_clip(cr);
222 poppler_page_get_size(pdf->current_page, &page_width, &page_height);
223 width = (gdouble)tpdfv->allocation.width;
224 height = (gdouble)tpdfv->allocation.height;
226 /* zoom */
227 if (pdf->zoom_mode == TPDFV_ZOOM_CUSTOM) {
228 zoom_factor = pdf->zoom_factor;
229 }
230 else {
231 if (pdf->zoom_mode == TPDFV_ZOOM_WIDTH) {
232 zoom_factor = (double)(width / page_width);
233 }
234 else if (pdf->zoom_mode == TPDFV_ZOOM_HEIGHT) {
235 zoom_factor = (double)(height / page_height);
236 }
237 else if (pdf->zoom_mode == TPDFV_ZOOM_FIT) {
238 zoom_factor = (double)TPDFV_MIN(width / page_width,
239 height / page_height);
240 }
241 else {
242 assert(0);
243 }
244 }
245 cairo_scale(cr, zoom_factor, zoom_factor);
247 /* centered position / scrolling */
248 x = (double)(width - page_width * zoom_factor) / 2.0 / zoom_factor;
249 y = (double)(height - page_height * zoom_factor) / 2.0 / zoom_factor;
251 cairo_translate(cr, x + pdf->delta_x, y + pdf->delta_y);
253 poppler_page_render(pdf->current_page, cr);
255 cairo_destroy(cr);
256 return FALSE;
257 } /* gtk_tpdfv_expose */
259 static void
260 gtk_tpdfv_destroy(GtkObject *obj)
261 {
262 GtkTPDFV *tpdfv = GTK_TPDFV(obj);
263 gtk_tpdfv_t *pdf;
265 pdf = GTK_TPDFV_GET_PRIVATE(tpdfv);
266 tpdfv_clean(pdf);
268 GTK_OBJECT_CLASS(gtk_tpdfv_parent_class)->destroy(obj);
269 } /* gtk_tpdfv_destroy */
271 static void
272 gtk_tpdfv_class_init(GtkTPDFVClass *class)
273 {
274 GObjectClass *goclass;
275 GtkObjectClass *oclass;
276 GtkWidgetClass *wclass;
278 goclass = G_OBJECT_CLASS(class);
279 g_type_class_add_private(goclass, sizeof(gtk_tpdfv_t));
281 oclass = GTK_OBJECT_CLASS(class);
282 oclass->destroy = gtk_tpdfv_destroy;
284 wclass = GTK_WIDGET_CLASS(class);
285 wclass->expose_event = gtk_tpdfv_expose;
286 } /* gtk_tpdfv_class_init */
288 static void
289 gtk_tpdfv_init(GtkTPDFV __attribute__((unused)) *tpdfv)
290 {
291 /* nothing to do for now */
292 } /* gtk_tpdfv_init */
294 /*
295 * Public API.
296 */
298 GtkWidget *
299 gtk_tpdfv_new(const char *filename)
300 {
301 GtkTPDFV *tpdfv;
302 gtk_tpdfv_t *pdf;
304 tpdfv = g_object_new(GTK_TYPE_TPDFV, NULL);
305 pdf = GTK_TPDFV_GET_PRIVATE(tpdfv);
307 if (TRUE != tpdfv_init(pdf, filename))
308 return NULL;
309 return GTK_WIDGET(tpdfv);
310 } /* gtk_tpdfv_new */
312 void
313 gtk_tpdfv_reload(GtkWidget *widget)
314 {
315 gtk_tpdfv_t *pdf;
317 pdf = GTK_TPDFV_GET_PRIVATE(widget);
318 tpdfv_close(pdf);
319 tpdfv_open(pdf);
320 do_redraw(widget);
321 } /* gtk_tpdfv_reload */
323 void
324 gtk_tpdfv_page_up(GtkWidget *widget)
325 {
326 gtk_tpdfv_t *pdf;
328 pdf = GTK_TPDFV_GET_PRIVATE(widget);
330 if (pdf->current_page_no)
331 --pdf->current_page_no;
332 do_redraw(widget);
333 } /* gtk_tpdfv_page_up */
335 void
336 gtk_tpdfv_page_down(GtkWidget *widget)
337 {
338 gtk_tpdfv_t *pdf;
340 pdf = GTK_TPDFV_GET_PRIVATE(widget);
342 if (pdf->current_page_no < pdf->total_pages - 1)
343 ++pdf->current_page_no;
344 do_redraw(widget);
345 } /* gtk_tpdfv_page_down */
347 void
348 gtk_tpdfv_first_page(GtkWidget *widget)
349 {
350 gtk_tpdfv_t *pdf;
352 pdf = GTK_TPDFV_GET_PRIVATE(widget);
353 pdf->current_page_no = 0;
354 do_redraw(widget);
355 } /* gtk_tpdfv_first_page */
357 void
358 gtk_tpdfv_last_page(GtkWidget *widget)
359 {
360 gtk_tpdfv_t *pdf;
362 pdf = GTK_TPDFV_GET_PRIVATE(widget);
363 pdf->current_page_no = pdf->total_pages - 1;
364 do_redraw(widget);
365 } /* gtk_tpdfv_last_page */
367 void
368 gtk_tpdfv_zoom_in(GtkWidget *widget)
369 {
370 gtk_tpdfv_t *pdf;
372 pdf = GTK_TPDFV_GET_PRIVATE(widget);
373 pdf->zoom_factor *= 1.2;
374 pdf->zoom_mode = TPDFV_ZOOM_CUSTOM;
375 do_redraw(widget);
376 } /* gtk_tpdfv_zoom_in */
378 void
379 gtk_tpdfv_zoom_out(GtkWidget *widget)
380 {
381 gtk_tpdfv_t *pdf;
383 pdf = GTK_TPDFV_GET_PRIVATE(widget);
385 if (pdf->zoom_factor > DBL_EPSILON * 2.0) {
386 pdf->zoom_factor /= 1.2;
387 pdf->zoom_mode = TPDFV_ZOOM_CUSTOM;
388 do_redraw(widget);
389 }
390 } /* gtk_tpdfv_zoom_out */
392 void
393 gtk_tpdfv_zoom_1(GtkWidget *widget)
394 {
395 gtk_tpdfv_t *pdf;
397 pdf = GTK_TPDFV_GET_PRIVATE(widget);
398 pdf->zoom_factor = 1.0;
399 pdf->zoom_mode = TPDFV_ZOOM_CUSTOM;
400 do_redraw(widget);
401 } /* gtk_tpdfv_zoom_1 */
403 void
404 gtk_tpdfv_zoom_width(GtkWidget *widget)
405 {
406 gtk_tpdfv_t *pdf;
408 pdf = GTK_TPDFV_GET_PRIVATE(widget);
409 pdf->zoom_mode = TPDFV_ZOOM_WIDTH;
410 do_redraw(widget);
411 } /* gtk_tpdfv_zoom_width */
413 void
414 gtk_tpdfv_zoom_height(GtkWidget *widget)
415 {
416 gtk_tpdfv_t *pdf;
418 pdf = GTK_TPDFV_GET_PRIVATE(widget);
419 pdf->zoom_mode = TPDFV_ZOOM_HEIGHT;
420 do_redraw(widget);
421 } /* gtk_tpdfv_zoom_width */
423 void
424 gtk_tpdfv_zoom_fit(GtkWidget *widget)
425 {
426 gtk_tpdfv_t *pdf;
428 pdf = GTK_TPDFV_GET_PRIVATE(widget);
429 pdf->zoom_mode = TPDFV_ZOOM_FIT;
430 do_redraw(widget);
431 } /* gtk_tpdfv_zoom_width */
433 void
434 gtk_tpdfv_scroll_up(GtkWidget *widget)
435 {
436 gtk_tpdfv_t *pdf;
438 pdf = GTK_TPDFV_GET_PRIVATE(widget);
439 pdf->delta_y += 10.0;
440 do_redraw(widget);
441 } /* gtk_tpdfv_scroll_up */
443 void
444 gtk_tpdfv_scroll_down(GtkWidget *widget)
445 {
446 gtk_tpdfv_t *pdf;
448 pdf = GTK_TPDFV_GET_PRIVATE(widget);
449 pdf->delta_y -= 10.0;
450 do_redraw(widget);
451 } /* gtk_tpdfv_scroll_down */
453 void
454 gtk_tpdfv_scroll_left(GtkWidget *widget)
455 {
456 gtk_tpdfv_t *pdf;
458 pdf = GTK_TPDFV_GET_PRIVATE(widget);
459 pdf->delta_x += 10.0;
460 do_redraw(widget);
461 } /* gtk_tpdfv_scroll_left */
463 void
464 gtk_tpdfv_scroll_right(GtkWidget *widget)
465 {
466 gtk_tpdfv_t *pdf;
468 pdf = GTK_TPDFV_GET_PRIVATE(widget);
469 pdf->delta_x -= 10.0;
470 do_redraw(widget);
471 } /* gtk_tpdfv_scroll_right */
473 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */