1 /*
2 * tpdfview - src/tpdfview.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 * A tiny PDF viewer.
30 */
32 #if HAVE_CONFIG_H
33 # include "config.h"
34 #endif /* HAVE_CONFIG_H */
36 #include "tpdfv.h"
37 #include "tpdfv_features.h"
39 #if HAVE_LIBGEN_H
40 # include <libgen.h>
41 #else /* HAVE_LIBGEN_H */
42 # define basename(path) (path)
43 #endif /* ! HAVE_LIBGEN_H */
45 #include <assert.h>
47 #include <errno.h>
49 #include <stdio.h>
50 #include <stdlib.h>
52 #include <string.h>
54 #include <unistd.h>
56 #include <cairo.h>
58 #include <gtk/gtk.h>
59 #include <gdk/gdkkeysyms.h>
60 #include <poppler.h>
62 typedef struct {
63 char *filename;
65 PopplerDocument *doc;
66 PopplerPage *current_page;
68 int current_page_no;
69 int total_pages;
71 double zoom_factor;
73 double delta_x;
74 double delta_y;
75 } tpdfv_t;
77 static tpdfv_t *
78 tpdfv_new(const char *filename)
79 {
80 GError *err = NULL;
81 tpdfv_t *pdf;
83 /* XXX: error reporting mechanism */
85 pdf = (tpdfv_t *)malloc(sizeof(*pdf));
86 if (! pdf) {
87 fprintf(stderr, "Failed to allocate PDF object: %s.\n",
88 strerror(errno));
89 return NULL;
90 }
92 if (strstr(filename, "://"))
93 pdf->filename = strdup(filename);
94 else {
95 size_t len = strlen("file://") + strlen(filename);
96 pdf->filename = (char *)malloc(len + 1);
97 if (pdf->filename) {
98 *pdf->filename = '\0';
99 strncat(pdf->filename, "file://", len);
100 strncat(pdf->filename, filename, len - strlen("file://"));
101 }
102 }
104 if (! pdf->filename) {
105 fprintf(stderr, "Failed to allocate string: %s.\n",
106 strerror(errno));
107 return NULL;
108 }
110 pdf->doc = poppler_document_new_from_file(pdf->filename,
111 /* password = */ NULL, &err);
112 if (! pdf->doc) {
113 fprintf(stderr, "Failed to open PDF: %s.\n", err->message);
114 return NULL;
115 }
117 pdf->current_page_no = 0;
118 pdf->current_page = poppler_document_get_page(pdf->doc,
119 pdf->current_page_no);
120 if (! pdf->current_page) {
121 fprintf(stderr, "Failed to open first page of the document.\n");
122 return NULL;
123 }
125 pdf->total_pages = poppler_document_get_n_pages(pdf->doc);
127 pdf->zoom_factor = 1.0;
128 pdf->delta_x = pdf->delta_y = 0.0;
129 return pdf;
130 } /* tpdfv_new */
132 static void
133 exit_usage(char *name, int status)
134 {
135 printf(
136 "Usage: %s [<options>] <filename>\n"
138 "\nA tiny PDF viewer.\n"
140 "\nOptions:\n"
141 " -h display this help and exit\n"
142 " -V display the version number and copyright\n"
144 "\ntpdfview "TPDFV_VERSION_STRING TPDFV_VERSION_EXTRA", "PACKAGE_URL"\n"
145 "Copyright (C) 2011 "PACKAGE_MAINTAINER"\n",
146 basename(name));
147 exit(status);
148 } /* exit_usage */
150 static void
151 exit_version(void)
152 {
153 printf("tpdfview version "TPDFV_VERSION_STRING TPDFV_VERSION_EXTRA", "
154 "built "BUILD_DATE"\n"
155 "Copyright (C) 2011 "PACKAGE_MAINTAINER"\n"
157 "\nThis is free software under the terms of the BSD license, see "
158 "the source for\ncopying conditions. There is NO WARRANTY; not "
159 "even for MERCHANTABILITY or\nFITNESS FOR A PARTICULAR "
160 "PURPOSE.\n");
161 exit(0);
162 } /* exit_version */
164 static void
165 win_redraw(GtkWidget *widget)
166 {
167 GdkRegion *region;
169 region = gdk_drawable_get_clip_region(widget->window);
170 gdk_window_invalidate_region(widget->window, region, TRUE);
171 gdk_window_process_updates(widget->window, TRUE);
173 gdk_region_destroy(region);
174 } /* win_redraw */
176 static void
177 on_destroy(GtkWidget __attribute__((unused)) *widget,
178 gpointer __attribute__((unused)) data)
179 {
180 gtk_main_quit();
181 } /* on_destroy */
183 static gboolean
184 on_expose(GtkWidget *widget, GdkEventExpose __attribute__((unused)) *event,
185 gpointer data)
186 {
187 cairo_t *cr;
189 tpdfv_t *pdf = (tpdfv_t *)data;
190 assert(data);
192 cr = gdk_cairo_create(widget->window);
194 /* zoom */
195 cairo_scale(cr, pdf->zoom_factor, pdf->zoom_factor);
196 cairo_translate(cr, pdf->delta_x, pdf->delta_y);
198 poppler_page_render(pdf->current_page, cr);
199 cairo_destroy(cr);
200 return FALSE;
201 } /* on_expose */
203 static gboolean
204 key_press(GtkWidget __attribute__((unused)) *widget,
205 GdkEventKey *event, gpointer data)
206 {
207 tpdfv_t *pdf = (tpdfv_t *)data;
209 int old_page_no;
210 _Bool do_redraw = 0;
212 assert(data);
214 old_page_no = pdf->current_page_no;
216 switch (event->keyval) {
217 case GDK_q:
218 gtk_main_quit();
219 break;
221 /* navigation */
222 case GDK_Page_Up:
223 if (pdf->current_page_no) {
224 --pdf->current_page_no;
225 }
226 break;
227 case GDK_Page_Down:
228 /* fall through */
229 case GDK_space:
230 if (pdf->current_page_no < pdf->total_pages - 1) {
231 ++pdf->current_page_no;
232 }
233 break;
234 case GDK_Home:
235 pdf->current_page_no = 0;
236 break;
237 case GDK_End:
238 pdf->current_page_no = pdf->total_pages - 1;
239 break;
241 /* zoom */
242 case GDK_plus:
243 pdf->zoom_factor *= 1.2;
244 do_redraw = 1;
245 break;
246 case GDK_minus:
247 if (pdf->zoom_factor > DBL_EPSILON * 2.0) {
248 pdf->zoom_factor *= 1.0 / 1.2;
249 do_redraw = 1;
250 }
251 break;
252 case GDK_1:
253 pdf->zoom_factor = 1.0;
254 do_redraw = 1;
255 break;
257 /* scrolling */
258 case GDK_Up:
259 pdf->delta_y += 10.0;
260 do_redraw = 1;
261 break;
262 case GDK_Down:
263 pdf->delta_y -= 10.0;
264 do_redraw = 1;
265 break;
266 case GDK_Left:
267 pdf->delta_x += 10.0;
268 do_redraw = 1;
269 break;
270 case GDK_Right:
271 pdf->delta_x -= 10.0;
272 do_redraw = 1;
273 break;
274 }
276 if (old_page_no != pdf->current_page_no) {
277 pdf->current_page = poppler_document_get_page(pdf->doc,
278 pdf->current_page_no);
279 if (! pdf->current_page)
280 fprintf(stderr, "Failed to open page %i of the document.\n",
281 pdf->current_page_no + 1);
282 else
283 do_redraw = 1;
284 }
286 if (do_redraw)
287 win_redraw(widget);
288 return FALSE;
289 } /* key_press */
291 int
292 main(int argc, char **argv)
293 {
294 GtkWidget *win = NULL;
295 tpdfv_t *pdf = NULL;
297 gtk_init(&argc, &argv);
299 while (42) {
300 int opt = getopt(argc, argv, "hV");
302 if (-1 == opt)
303 break;
305 switch (opt) {
306 case 'h':
307 exit_usage(argv[0], 0);
308 break;
309 case 'V':
310 exit_version();
311 break;
312 default:
313 exit_usage(argv[0], 1);
314 }
315 }
317 if (argc - optind != 1) {
318 fprintf(stderr, "%s: missing filename\n", argv[0]);
319 exit_usage(argv[0], 1);
320 }
322 pdf = tpdfv_new(/* filename = */ argv[optind]);
323 if (! pdf)
324 return 1;
326 win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
327 g_signal_connect(G_OBJECT(win), "destroy",
328 G_CALLBACK(on_destroy), NULL);
329 g_signal_connect(G_OBJECT(win), "expose-event",
330 G_CALLBACK(on_expose), pdf);
331 g_signal_connect(G_OBJECT(win), "key-press-event",
332 G_CALLBACK(key_press), pdf);
333 gtk_widget_set_app_paintable(win, TRUE);
334 gtk_widget_show_all(win);
336 gtk_main();
337 return 0;
338 } /* main */
340 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */