771dff99871ebd2f2490207790760175caa684e9
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 gdouble width, height;
184 gdouble page_width = 0.0;
185 gdouble page_height = 0.0;
187 double zoom_factor = 1.0;
188 double x, y;
190 pdf = GTK_TPDFV_GET_PRIVATE(tpdfv);
192 if (! pdf)
193 return FALSE;
195 cr = gdk_cairo_create(tpdfv->window);
196 cairo_rectangle(cr, event->area.x, event->area.y,
197 event->area.width, event->area.height);
198 cairo_clip(cr);
200 poppler_page_get_size(pdf->current_page, &page_width, &page_height);
201 width = (gdouble)tpdfv->allocation.width;
202 height = (gdouble)tpdfv->allocation.height;
204 /* zoom */
205 if (pdf->zoom_mode == TPDFV_ZOOM_CUSTOM) {
206 zoom_factor = pdf->zoom_factor;
207 }
208 else {
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 }
222 }
223 cairo_scale(cr, zoom_factor, zoom_factor);
225 /* centered position / scrolling */
226 x = (double)(width - page_width * zoom_factor) / 2.0 / zoom_factor;
227 y = (double)(height - page_height * zoom_factor) / 2.0 / zoom_factor;
229 cairo_translate(cr, x + pdf->delta_x, y + pdf->delta_y);
231 poppler_page_render(pdf->current_page, cr);
233 cairo_destroy(cr);
234 return FALSE;
235 } /* gtk_tpdfv_expose */
237 static void
238 gtk_tpdfv_destroy(GtkObject *obj)
239 {
240 GtkTPDFV *tpdfv = GTK_TPDFV(obj);
241 gtk_tpdfv_t *pdf;
243 pdf = GTK_TPDFV_GET_PRIVATE(tpdfv);
244 tpdfv_clean(pdf);
246 GTK_OBJECT_CLASS(gtk_tpdfv_parent_class)->destroy(obj);
247 } /* gtk_tpdfv_destroy */
249 static void
250 gtk_tpdfv_class_init(GtkTPDFVClass *class)
251 {
252 GObjectClass *goclass;
253 GtkObjectClass *oclass;
254 GtkWidgetClass *wclass;
256 goclass = G_OBJECT_CLASS(class);
257 g_type_class_add_private(goclass, sizeof(gtk_tpdfv_t));
259 oclass = GTK_OBJECT_CLASS(class);
260 oclass->destroy = gtk_tpdfv_destroy;
262 wclass = GTK_WIDGET_CLASS(class);
263 wclass->expose_event = gtk_tpdfv_expose;
264 } /* gtk_tpdfv_class_init */
266 static void
267 gtk_tpdfv_init(GtkTPDFV __attribute__((unused)) *tpdfv)
268 {
269 /* nothing to do for now */
270 } /* gtk_tpdfv_init */
272 /*
273 * Public API.
274 */
276 GtkWidget *
277 gtk_tpdfv_new(const char *filename)
278 {
279 GtkTPDFV *tpdfv;
280 gtk_tpdfv_t *pdf;
282 tpdfv = g_object_new(GTK_TYPE_TPDFV, NULL);
283 pdf = GTK_TPDFV_GET_PRIVATE(tpdfv);
285 if (TRUE != tpdfv_init(pdf, filename))
286 return NULL;
287 return GTK_WIDGET(tpdfv);
288 } /* gtk_tpdfv_new */
290 void
291 gtk_tpdfv_page_up(GtkWidget *widget)
292 {
293 gtk_tpdfv_t *pdf;
295 pdf = GTK_TPDFV_GET_PRIVATE(widget);
297 if (pdf->current_page_no)
298 --pdf->current_page_no;
299 do_redraw(widget);
300 } /* gtk_tpdfv_page_up */
302 void
303 gtk_tpdfv_page_down(GtkWidget *widget)
304 {
305 gtk_tpdfv_t *pdf;
307 pdf = GTK_TPDFV_GET_PRIVATE(widget);
309 if (pdf->current_page_no < pdf->total_pages - 1)
310 ++pdf->current_page_no;
311 do_redraw(widget);
312 } /* gtk_tpdfv_page_down */
314 void
315 gtk_tpdfv_first_page(GtkWidget *widget)
316 {
317 gtk_tpdfv_t *pdf;
319 pdf = GTK_TPDFV_GET_PRIVATE(widget);
320 pdf->current_page_no = 0;
321 do_redraw(widget);
322 } /* gtk_tpdfv_first_page */
324 void
325 gtk_tpdfv_last_page(GtkWidget *widget)
326 {
327 gtk_tpdfv_t *pdf;
329 pdf = GTK_TPDFV_GET_PRIVATE(widget);
330 pdf->current_page_no = pdf->total_pages - 1;
331 do_redraw(widget);
332 } /* gtk_tpdfv_last_page */
334 void
335 gtk_tpdfv_zoom_in(GtkWidget *widget)
336 {
337 gtk_tpdfv_t *pdf;
339 pdf = GTK_TPDFV_GET_PRIVATE(widget);
340 pdf->zoom_factor *= 1.2;
341 pdf->zoom_mode = TPDFV_ZOOM_CUSTOM;
342 do_redraw(widget);
343 } /* gtk_tpdfv_zoom_in */
345 void
346 gtk_tpdfv_zoom_out(GtkWidget *widget)
347 {
348 gtk_tpdfv_t *pdf;
350 pdf = GTK_TPDFV_GET_PRIVATE(widget);
352 if (pdf->zoom_factor > DBL_EPSILON * 2.0) {
353 pdf->zoom_factor /= 1.2;
354 pdf->zoom_mode = TPDFV_ZOOM_CUSTOM;
355 do_redraw(widget);
356 }
357 } /* gtk_tpdfv_zoom_out */
359 void
360 gtk_tpdfv_zoom_1(GtkWidget *widget)
361 {
362 gtk_tpdfv_t *pdf;
364 pdf = GTK_TPDFV_GET_PRIVATE(widget);
365 pdf->zoom_factor = 1.0;
366 pdf->zoom_mode = TPDFV_ZOOM_CUSTOM;
367 do_redraw(widget);
368 } /* gtk_tpdfv_zoom_1 */
370 void
371 gtk_tpdfv_zoom_width(GtkWidget *widget)
372 {
373 gtk_tpdfv_t *pdf;
375 pdf = GTK_TPDFV_GET_PRIVATE(widget);
376 pdf->zoom_mode = TPDFV_ZOOM_WIDTH;
377 do_redraw(widget);
378 } /* gtk_tpdfv_zoom_width */
380 void
381 gtk_tpdfv_zoom_height(GtkWidget *widget)
382 {
383 gtk_tpdfv_t *pdf;
385 pdf = GTK_TPDFV_GET_PRIVATE(widget);
386 pdf->zoom_mode = TPDFV_ZOOM_HEIGHT;
387 do_redraw(widget);
388 } /* gtk_tpdfv_zoom_width */
390 void
391 gtk_tpdfv_zoom_fit(GtkWidget *widget)
392 {
393 gtk_tpdfv_t *pdf;
395 pdf = GTK_TPDFV_GET_PRIVATE(widget);
396 pdf->zoom_mode = TPDFV_ZOOM_FIT;
397 do_redraw(widget);
398 } /* gtk_tpdfv_zoom_width */
400 void
401 gtk_tpdfv_scroll_up(GtkWidget *widget)
402 {
403 gtk_tpdfv_t *pdf;
405 pdf = GTK_TPDFV_GET_PRIVATE(widget);
406 pdf->delta_y += 10.0;
407 do_redraw(widget);
408 } /* gtk_tpdfv_scroll_up */
410 void
411 gtk_tpdfv_scroll_down(GtkWidget *widget)
412 {
413 gtk_tpdfv_t *pdf;
415 pdf = GTK_TPDFV_GET_PRIVATE(widget);
416 pdf->delta_y -= 10.0;
417 do_redraw(widget);
418 } /* gtk_tpdfv_scroll_down */
420 void
421 gtk_tpdfv_scroll_left(GtkWidget *widget)
422 {
423 gtk_tpdfv_t *pdf;
425 pdf = GTK_TPDFV_GET_PRIVATE(widget);
426 pdf->delta_x += 10.0;
427 do_redraw(widget);
428 } /* gtk_tpdfv_scroll_left */
430 void
431 gtk_tpdfv_scroll_right(GtkWidget *widget)
432 {
433 gtk_tpdfv_t *pdf;
435 pdf = GTK_TPDFV_GET_PRIVATE(widget);
436 pdf->delta_x -= 10.0;
437 do_redraw(widget);
438 } /* gtk_tpdfv_scroll_right */
440 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */