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 gboolean
83 tpdfv_init(gtk_tpdfv_t *pdf, const char *filename)
84 {
85 GError *err = NULL;
87 if (strstr(filename, "://"))
88 pdf->filename = strdup(filename);
89 else {
90 size_t len = strlen("file://") + strlen(filename);
91 pdf->filename = (char *)malloc(len + 1);
92 if (pdf->filename) {
93 *pdf->filename = '\0';
94 strncat(pdf->filename, "file://", len);
95 strncat(pdf->filename, filename, len - strlen("file://"));
96 }
97 }
99 /* XXX: error reporting mechanism */
101 if (! pdf->filename) {
102 char errbuf[1024];
103 strerror_r(errno, errbuf, sizeof(errbuf));
104 fprintf(stderr, "Failed to allocate string: %s.\n", errbuf);
105 return FALSE;
106 }
108 pdf->doc = poppler_document_new_from_file(pdf->filename,
109 /* password = */ NULL, &err);
110 if (! pdf->doc) {
111 fprintf(stderr, "Failed to open PDF: %s.\n", err->message);
112 return FALSE;
113 }
115 pdf->current_page_no = 0;
116 pdf->current_page = poppler_document_get_page(pdf->doc,
117 pdf->current_page_no);
118 if (! pdf->current_page) {
119 fprintf(stderr, "Failed to open first page of the document.\n");
120 return FALSE;
121 }
123 pdf->total_pages = poppler_document_get_n_pages(pdf->doc);
125 pdf->zoom_mode = TPDFV_ZOOM_CUSTOM;
126 pdf->zoom_factor = 1.0;
127 pdf->delta_x = pdf->delta_y = 0.0;
128 return TRUE;
129 } /* tpdfv_init */
131 static void
132 tpdfv_clean(gtk_tpdfv_t *pdf)
133 {
134 if (! pdf)
135 return;
137 g_object_unref(pdf->doc);
138 pdf->doc = NULL;
139 pdf->current_page = NULL;
140 free(pdf->filename);
141 pdf->filename = NULL;
142 return;
143 } /* tpdfv_clean */
145 static void
146 do_redraw(GtkWidget *widget)
147 {
148 GdkRegion *region;
149 gtk_tpdfv_t *pdf;
151 pdf = GTK_TPDFV_GET_PRIVATE(widget);
153 if (poppler_page_get_index(pdf->current_page)
154 != pdf->current_page_no) {
155 pdf->current_page = poppler_document_get_page(pdf->doc,
156 pdf->current_page_no);
158 if (! pdf->current_page) {
159 fprintf(stderr, "Failed to open page %i of the document.\n",
160 pdf->current_page_no + 1);
161 return;
162 }
163 }
165 region = gdk_drawable_get_clip_region(widget->window);
166 gdk_window_invalidate_region(widget->window, region, TRUE);
167 gdk_window_process_updates(widget->window, TRUE);
169 gdk_region_destroy(region);
170 } /* do_redraw */
172 /*
173 * Gtk+ class definition.
174 */
176 static gboolean
177 gtk_tpdfv_expose(GtkWidget *tpdfv, GdkEventExpose *event)
178 {
179 gtk_tpdfv_t *pdf;
180 cairo_t *cr;
182 pdf = GTK_TPDFV_GET_PRIVATE(tpdfv);
184 if (! pdf)
185 return FALSE;
187 cr = gdk_cairo_create(tpdfv->window);
188 cairo_rectangle(cr, event->area.x, event->area.y,
189 event->area.width, event->area.height);
190 cairo_clip(cr);
192 /* zoom, scrolling */
193 if (pdf->zoom_mode == TPDFV_ZOOM_CUSTOM) {
194 cairo_scale(cr, pdf->zoom_factor, pdf->zoom_factor);
195 }
196 else {
197 gdouble width;
198 gdouble height;
200 gdouble page_width = 0.0;
201 gdouble page_height = 0.0;
203 double zoom_factor = 1.0;
205 poppler_page_get_size(pdf->current_page, &page_width, &page_height);
206 width = (gdouble)tpdfv->allocation.width;
207 height = (gdouble)tpdfv->allocation.height;
209 if (pdf->zoom_mode == TPDFV_ZOOM_WIDTH) {
210 zoom_factor = (double)(width / page_width);
211 }
212 else if (pdf->zoom_mode == TPDFV_ZOOM_HEIGHT) {
213 zoom_factor = (double)(height / page_height);
214 }
215 else if (pdf->zoom_mode == TPDFV_ZOOM_FIT) {
216 zoom_factor = (double)TPDFV_MIN(width / page_width,
217 height / page_height);
218 }
219 else {
220 assert(0);
221 }
223 cairo_scale(cr, zoom_factor, zoom_factor);
224 }
226 cairo_translate(cr, pdf->delta_x, pdf->delta_y);
228 poppler_page_render(pdf->current_page, cr);
230 cairo_destroy(cr);
231 return FALSE;
232 } /* gtk_tpdfv_expose */
234 static void
235 gtk_tpdfv_destroy(GtkObject *obj)
236 {
237 GtkTPDFV *tpdfv = GTK_TPDFV(obj);
238 gtk_tpdfv_t *pdf;
240 pdf = GTK_TPDFV_GET_PRIVATE(tpdfv);
241 tpdfv_clean(pdf);
243 GTK_OBJECT_CLASS(gtk_tpdfv_parent_class)->destroy(obj);
244 } /* gtk_tpdfv_destroy */
246 static void
247 gtk_tpdfv_class_init(GtkTPDFVClass *class)
248 {
249 GObjectClass *goclass;
250 GtkObjectClass *oclass;
251 GtkWidgetClass *wclass;
253 goclass = G_OBJECT_CLASS(class);
254 g_type_class_add_private(goclass, sizeof(gtk_tpdfv_t));
256 oclass = GTK_OBJECT_CLASS(class);
257 oclass->destroy = gtk_tpdfv_destroy;
259 wclass = GTK_WIDGET_CLASS(class);
260 wclass->expose_event = gtk_tpdfv_expose;
261 } /* gtk_tpdfv_class_init */
263 static void
264 gtk_tpdfv_init(GtkTPDFV __attribute__((unused)) *tpdfv)
265 {
266 /* nothing to do for now */
267 } /* gtk_tpdfv_init */
269 /*
270 * Public API.
271 */
273 GtkWidget *
274 gtk_tpdfv_new(const char *filename)
275 {
276 GtkTPDFV *tpdfv;
277 gtk_tpdfv_t *pdf;
279 tpdfv = g_object_new(GTK_TYPE_TPDFV, NULL);
280 pdf = GTK_TPDFV_GET_PRIVATE(tpdfv);
282 if (TRUE != tpdfv_init(pdf, filename))
283 return NULL;
284 return GTK_WIDGET(tpdfv);
285 } /* gtk_tpdfv_new */
287 void
288 gtk_tpdfv_page_up(GtkWidget *widget)
289 {
290 gtk_tpdfv_t *pdf;
292 pdf = GTK_TPDFV_GET_PRIVATE(widget);
294 if (pdf->current_page_no)
295 --pdf->current_page_no;
296 do_redraw(widget);
297 } /* gtk_tpdfv_page_up */
299 void
300 gtk_tpdfv_page_down(GtkWidget *widget)
301 {
302 gtk_tpdfv_t *pdf;
304 pdf = GTK_TPDFV_GET_PRIVATE(widget);
306 if (pdf->current_page_no < pdf->total_pages - 1)
307 ++pdf->current_page_no;
308 do_redraw(widget);
309 } /* gtk_tpdfv_page_down */
311 void
312 gtk_tpdfv_first_page(GtkWidget *widget)
313 {
314 gtk_tpdfv_t *pdf;
316 pdf = GTK_TPDFV_GET_PRIVATE(widget);
317 pdf->current_page_no = 0;
318 do_redraw(widget);
319 } /* gtk_tpdfv_first_page */
321 void
322 gtk_tpdfv_last_page(GtkWidget *widget)
323 {
324 gtk_tpdfv_t *pdf;
326 pdf = GTK_TPDFV_GET_PRIVATE(widget);
327 pdf->current_page_no = pdf->total_pages - 1;
328 do_redraw(widget);
329 } /* gtk_tpdfv_last_page */
331 void
332 gtk_tpdfv_zoom_in(GtkWidget *widget)
333 {
334 gtk_tpdfv_t *pdf;
336 pdf = GTK_TPDFV_GET_PRIVATE(widget);
337 pdf->zoom_factor *= 1.2;
338 pdf->zoom_mode = TPDFV_ZOOM_CUSTOM;
339 do_redraw(widget);
340 } /* gtk_tpdfv_zoom_in */
342 void
343 gtk_tpdfv_zoom_out(GtkWidget *widget)
344 {
345 gtk_tpdfv_t *pdf;
347 pdf = GTK_TPDFV_GET_PRIVATE(widget);
349 if (pdf->zoom_factor > DBL_EPSILON * 2.0) {
350 pdf->zoom_factor /= 1.2;
351 pdf->zoom_mode = TPDFV_ZOOM_CUSTOM;
352 do_redraw(widget);
353 }
354 } /* gtk_tpdfv_zoom_out */
356 void
357 gtk_tpdfv_zoom_1(GtkWidget *widget)
358 {
359 gtk_tpdfv_t *pdf;
361 pdf = GTK_TPDFV_GET_PRIVATE(widget);
362 pdf->zoom_factor = 1.0;
363 pdf->zoom_mode = TPDFV_ZOOM_CUSTOM;
364 do_redraw(widget);
365 } /* gtk_tpdfv_zoom_1 */
367 void
368 gtk_tpdfv_zoom_width(GtkWidget *widget)
369 {
370 gtk_tpdfv_t *pdf;
372 pdf = GTK_TPDFV_GET_PRIVATE(widget);
373 pdf->zoom_mode = TPDFV_ZOOM_WIDTH;
374 do_redraw(widget);
375 } /* gtk_tpdfv_zoom_width */
377 void
378 gtk_tpdfv_zoom_height(GtkWidget *widget)
379 {
380 gtk_tpdfv_t *pdf;
382 pdf = GTK_TPDFV_GET_PRIVATE(widget);
383 pdf->zoom_mode = TPDFV_ZOOM_HEIGHT;
384 do_redraw(widget);
385 } /* gtk_tpdfv_zoom_width */
387 void
388 gtk_tpdfv_zoom_fit(GtkWidget *widget)
389 {
390 gtk_tpdfv_t *pdf;
392 pdf = GTK_TPDFV_GET_PRIVATE(widget);
393 pdf->zoom_mode = TPDFV_ZOOM_FIT;
394 do_redraw(widget);
395 } /* gtk_tpdfv_zoom_width */
397 void
398 gtk_tpdfv_scroll_up(GtkWidget *widget)
399 {
400 gtk_tpdfv_t *pdf;
402 pdf = GTK_TPDFV_GET_PRIVATE(widget);
403 pdf->delta_y += 10.0;
404 do_redraw(widget);
405 } /* gtk_tpdfv_scroll_up */
407 void
408 gtk_tpdfv_scroll_down(GtkWidget *widget)
409 {
410 gtk_tpdfv_t *pdf;
412 pdf = GTK_TPDFV_GET_PRIVATE(widget);
413 pdf->delta_y -= 10.0;
414 do_redraw(widget);
415 } /* gtk_tpdfv_scroll_down */
417 void
418 gtk_tpdfv_scroll_left(GtkWidget *widget)
419 {
420 gtk_tpdfv_t *pdf;
422 pdf = GTK_TPDFV_GET_PRIVATE(widget);
423 pdf->delta_x += 10.0;
424 do_redraw(widget);
425 } /* gtk_tpdfv_scroll_left */
427 void
428 gtk_tpdfv_scroll_right(GtkWidget *widget)
429 {
430 gtk_tpdfv_t *pdf;
432 pdf = GTK_TPDFV_GET_PRIVATE(widget);
433 pdf->delta_x -= 10.0;
434 do_redraw(widget);
435 } /* gtk_tpdfv_scroll_right */
437 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */