Code

Initial import of the tiny PDF viewer.
[tpdfview.git] / src / tpdfview.c
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)
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)
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)
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)
180         gtk_main_quit();
181 } /* on_destroy */
183 static gboolean
184 on_expose(GtkWidget *widget, GdkEventExpose __attribute__((unused)) *event,
185                 gpointer data)
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)
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)
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 : */