summaryrefslogtreecommitdiff
path: root/apps/gnome/gtkpdf.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/gnome/gtkpdf.c')
-rw-r--r--apps/gnome/gtkpdf.c670
1 files changed, 670 insertions, 0 deletions
diff --git a/apps/gnome/gtkpdf.c b/apps/gnome/gtkpdf.c
new file mode 100644
index 00000000..07730b38
--- /dev/null
+++ b/apps/gnome/gtkpdf.c
@@ -0,0 +1,670 @@
+/*
+
+TODO:
+ - threaded pdf/page loading
+ - info dialog
+ - resource dialog
+ - password dialog
+ - outline tree
+ - magnifying glass
+ - text selection
+ - text search
+
+*/
+
+#include <fitz.h>
+#include <mupdf.h>
+
+#include <gtk/gtk.h>
+#include <glib.h>
+#include <pthread.h>
+
+typedef struct PDFApp PDFApp;
+
+enum { ZOOM, FITWIDTH, FITPAGE };
+
+struct PDFApp
+{
+ GtkWidget *canvas;
+ GtkWidget *status;
+ GtkWidget *scroll;
+ int statusid;
+ int viewmode;
+
+ char *filename;
+ int pageno;
+ int rotate;
+ float zoom;
+
+ fz_renderer *gc;
+ pdf_xref *xref;
+ pdf_pagetree *pagetree;
+ fz_obj *pageobj;
+ pdf_page *page;
+ fz_pixmap *image;
+};
+
+static volatile int busy = 0;
+
+PDFApp *gapp;
+
+static void showstatus(char *fmt, ...)
+{
+ char msg[256];
+ va_list ap;
+
+ va_start(ap, fmt);
+ vsnprintf(msg, 256, fmt, ap);
+ va_end(ap);
+
+ gtk_statusbar_pop(GTK_STATUSBAR(gapp->status), gapp->statusid);
+ gtk_statusbar_push(GTK_STATUSBAR(gapp->status), gapp->statusid, msg);
+}
+
+static void panic(fz_error *error)
+{
+ gapp->filename = "";
+ gapp->pageno = 1;
+ gapp->rotate = 0;
+ gapp->zoom = 1.0;
+
+ gapp->gc = nil;
+ gapp->xref = nil;
+ gapp->pagetree = nil;
+ gapp->page = nil;
+ gapp->image = nil;
+
+ fz_abort(error);
+}
+
+static void forkwork(void*(*func)(void*))
+{
+ pthread_t tid;
+ if (busy) return;
+ pthread_create(&tid, NULL, func, nil);
+}
+
+static void* drawpage(void*args)
+{
+ char msg[256];
+ fz_error *error;
+ float scalex, scaley, scale;
+ fz_matrix ctm;
+ fz_irect bbox;
+ fz_obj *obj;
+
+ if (!gapp->xref)
+ return nil;
+
+ busy = 1;
+
+ while (gapp->rotate < 0)
+ gapp->rotate += 360;
+ gapp->rotate = gapp->rotate % 360;
+
+ obj = pdf_getpageobject(gapp->pagetree, gapp->pageno - 1);
+ if (obj == gapp->pageobj)
+ goto Lskipload;
+ gapp->pageobj = obj;
+
+ if (gapp->page)
+ pdf_droppage(gapp->page);
+
+ gdk_threads_enter();
+ sprintf(msg, " loading page %d ... ", gapp->pageno);
+ showstatus(msg);
+ gdk_threads_leave();
+
+ error = pdf_loadpage(&gapp->page, gapp->xref, gapp->pageobj);
+ if (error)
+ panic(error);
+
+Lskipload:
+
+ gdk_threads_enter();
+ showstatus(" drawing ... ");
+
+ ctm = fz_identity();
+ ctm = fz_concat(ctm, fz_translate(0, -gapp->page->mediabox.max.y));
+ ctm = fz_concat(ctm, fz_rotate(gapp->rotate + gapp->page->rotate));
+ bbox = fz_roundrect(fz_transformaabb(ctm, gapp->page->mediabox));
+
+ scale = gapp->zoom;
+ scalex = scaley = 1.0;
+
+ if (gapp->viewmode == FITWIDTH || gapp->viewmode == FITPAGE)
+ {
+ GtkAdjustment *hadj;
+ int w;
+ hadj = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(gapp->scroll));
+ w = bbox.max.x - bbox.min.x;
+ if (w != 0)
+ scalex = hadj->page_size / (float)w;
+ scale = scalex;
+ }
+
+ if (gapp->viewmode == FITPAGE)
+ {
+ GtkAdjustment *vadj;
+ int h;
+ vadj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(gapp->scroll));
+ h = bbox.max.y - bbox.min.y;
+ if (h != 0)
+ scaley = vadj->page_size / (float)h;
+ scale = MIN(scale, scaley);
+ }
+
+ gapp->zoom = scale;
+
+ gdk_threads_leave();
+
+ if (gapp->image)
+ fz_droppixmap(gapp->image);
+ gapp->image = nil;
+
+ ctm = fz_identity();
+ ctm = fz_concat(ctm, fz_translate(0, -gapp->page->mediabox.max.y));
+ ctm = fz_concat(ctm, fz_scale(gapp->zoom, -gapp->zoom));
+ ctm = fz_concat(ctm, fz_rotate(gapp->rotate + gapp->page->rotate));
+ bbox = fz_roundrect(fz_transformaabb(ctm, gapp->page->mediabox));
+
+ error = fz_rendertree(&gapp->image, gapp->gc, gapp->page->tree, ctm, bbox, 1);
+ if (error)
+ panic(error);
+
+ gdk_threads_enter();
+ sprintf(msg, " page %d of %d zoom %g rotate %d ",
+ gapp->pageno, pdf_getpagecount(gapp->pagetree),
+ gapp->zoom, gapp->rotate);
+ showstatus(msg);
+ gtk_widget_set_usize(gapp->canvas, gapp->image->w, gapp->image->h);
+ gtk_widget_queue_draw(gapp->canvas);
+ gdk_threads_leave();
+
+ busy = 0;
+
+ return nil;
+}
+
+static void* openpdf(void*args)
+{
+ fz_error *error;
+ char msg[256];
+
+ busy = 1;
+
+ gdk_threads_enter();
+ sprintf(msg, " Loading %s...", gapp->filename);
+ showstatus(msg);
+ gdk_threads_leave();
+
+ if (gapp->xref)
+ {
+ pdf_droppage(gapp->page);
+ pdf_droppagetree(gapp->pagetree);
+ pdf_closepdf(gapp->xref);
+ gapp->page = nil;
+ gapp->pagetree = nil;
+ gapp->xref = nil;
+ }
+
+ error = pdf_openpdf(&gapp->xref, gapp->filename);
+ if (error) panic(error);
+
+ error = pdf_decryptpdf(gapp->xref);
+ if (error) panic(error);
+
+ /* TODO: ask for password */
+ if (gapp->xref->crypt)
+ {
+ error = pdf_setpassword(gapp->xref->crypt, "");
+ if (error) panic(error);
+ }
+
+ error = pdf_loadpagetree(&gapp->pagetree, gapp->xref);
+ if (error) panic(error);
+
+ gdk_threads_enter();
+ showstatus("");
+ gdk_threads_leave();
+
+ gapp->pageno = 1;
+ drawpage(nil);
+
+ busy = 0;
+
+ return nil;
+}
+
+/*
+ * Handle event callbacks
+ */
+
+static void onquit(GtkWidget *widget, void *data)
+{
+ exit(0);
+}
+
+static void onopenokay(GtkWidget *w, GtkFileSelection *fs)
+{
+ gapp->filename = strdup(gtk_file_selection_get_filename(GTK_FILE_SELECTION(fs)));
+ gtk_widget_destroy(GTK_WIDGET(fs));
+ forkwork(openpdf);
+}
+
+static void onopen(GtkWidget *widget, void *data)
+{
+ GtkWidget *filew;
+ if (busy) return;
+ filew = gtk_file_selection_new ("Open PDF file");
+ gtk_signal_connect(GTK_OBJECT(GTK_FILE_SELECTION(filew)->ok_button),
+ "clicked", (GtkSignalFunc)onopenokay, filew);
+ gtk_signal_connect_object(GTK_OBJECT(GTK_FILE_SELECTION(filew)->cancel_button),
+ "clicked", (GtkSignalFunc)gtk_widget_destroy, GTK_OBJECT(filew));
+ gtk_widget_show(filew);
+}
+
+static void oninfo(GtkWidget *widget, void *data)
+{
+}
+
+static void onback10(GtkWidget *widget, void *data)
+{
+ if (busy) return;
+ if (!gapp->xref) return;
+ gapp->pageno -= 10;
+ if (gapp->pageno < 1)
+ gapp->pageno = 1;
+ forkwork(drawpage);
+}
+
+static void onnext10(GtkWidget *widget, void *data)
+{
+ if (busy) return;
+ if (!gapp->xref) return;
+ gapp->pageno += 10;
+ if (gapp->pageno > pdf_getpagecount(gapp->pagetree))
+ gapp->pageno = pdf_getpagecount(gapp->pagetree);
+ forkwork(drawpage);
+}
+
+static void onback(GtkWidget *widget, void *data)
+{
+ if (busy) return;
+ if (!gapp->xref) return;
+ if (gapp->pageno > 1)
+ {
+ gapp->pageno --;
+ forkwork(drawpage);
+ }
+}
+
+static void onnext(GtkWidget *widget, void *data)
+{
+ if (busy) return;
+ if (!gapp->xref) return;
+ if (gapp->pageno < pdf_getpagecount(gapp->pagetree))
+ {
+ gapp->pageno ++;
+ forkwork(drawpage);
+ }
+}
+
+static void onfirst(GtkWidget *widget, void *data)
+{
+ if (busy) return;
+ if (!gapp->xref) return;
+ gapp->pageno = 1;
+ forkwork(drawpage);
+}
+
+static void onlast(GtkWidget *widget, void *data)
+{
+ if (busy) return;
+ if (!gapp->xref) return;
+ gapp->pageno = pdf_getpagecount(gapp->pagetree);
+ forkwork(drawpage);
+}
+
+static void onzoomin(GtkWidget * widget, void *data)
+{
+ gapp->viewmode = ZOOM;
+ gapp->zoom *= 1.25;
+ forkwork(drawpage);
+}
+
+static void onzoomout(GtkWidget * widget, void *data)
+{
+ gapp->viewmode = ZOOM;
+ gapp->zoom *= 0.8;
+ forkwork(drawpage);
+}
+
+static void onzoom100(GtkWidget * widget, void *data)
+{
+ gapp->viewmode = ZOOM;
+ gapp->zoom = 1.0;
+ forkwork(drawpage);
+}
+
+static void onrotl(GtkWidget *widget, void *data) { gapp->rotate -= 90; forkwork(drawpage); }
+static void onrotr(GtkWidget *widget, void *data) { gapp->rotate += 90; forkwork(drawpage); }
+
+static void onfitwidth(GtkWidget *widget, void *data)
+{
+ gapp->viewmode = FITWIDTH;
+ forkwork(drawpage);
+}
+
+static void onfitpage(GtkWidget *widget, void *data)
+{
+ gapp->viewmode = FITPAGE;
+ forkwork(drawpage);
+}
+
+static int startxpos;
+static int startypos;
+static int dopan = 0;
+guint32 pangrabtime = 0;
+
+static void mousedown(GtkWidget *widget, GdkEventMotion *event, void *data)
+{
+ GdkModifierType mods;
+ gtk_widget_grab_focus(gapp->scroll);
+
+ gdk_window_get_pointer(gapp->scroll->window, &startxpos, &startypos, &mods);
+ if (mods & GDK_BUTTON2_MASK) {
+ GtkAdjustment *adj;
+ adj = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(gapp->scroll));
+ startxpos += adj->value;
+
+ adj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(gapp->scroll));
+ startypos += adj->value;
+
+ gdk_pointer_grab(gapp->scroll->window, TRUE, GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, NULL, NULL /* TODO: pan cursor */, event->time);
+
+ dopan = 1;
+ }
+ else {
+ dopan = 0;
+ }
+}
+
+static void mouseup(GtkWidget *widget, GdkEventMotion *event, void *data)
+{
+ dopan = 0;
+ gdk_pointer_ungrab(event->time);
+}
+
+static void mousemove(GtkWidget *widget, GdkEventMotion *event, void *data)
+{
+ int xpos, ypos;
+ GdkModifierType mods;
+ GtkAdjustment *adj;
+
+ if (!dopan) return;
+
+ gdk_window_get_pointer(gapp->scroll->window, &xpos, &ypos, &mods);
+
+ if (mods & GDK_BUTTON2_MASK) {
+ adj = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(gapp->scroll));
+ adj->value = startxpos - xpos;
+ adj->value = CLAMP(adj->value, adj->lower, adj->upper - adj->page_size);
+
+ gtk_adjustment_value_changed(adj);
+
+ adj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(gapp->scroll));
+ adj->value = startypos - ypos;
+ adj->value = CLAMP(adj->value, adj->lower, adj->upper - adj->page_size);
+
+ /* clamp to viewport... */
+
+ gtk_adjustment_value_changed(adj);
+ }
+ else
+ mouseup(widget, event, data);
+}
+
+static void keypress(GtkWidget *widget, GdkEventKey *event, void *data)
+{
+ if (busy) return;
+ switch (event->string[0])
+ {
+ case 'q': onquit(widget, data); break;
+ case 'g': onlast(widget, data); break;
+ case '0': onfirst(widget, data); break;
+ case 'b': onback(widget, data); break;
+ case 'B': onback10(widget, data); break;
+ case ' ': onnext(widget, data); break;
+ case 'f': onnext(widget, data); break;
+ case 'F': onnext10(widget, data); break;
+ case '+': onzoomin(widget, data); break;
+ case '-': onzoomout(widget, data); break;
+ case '1': onzoom100(widget, data); break;
+ case 'l': onrotl(widget, data); break;
+ case 'r': onrotr(widget, data); break;
+ case 'L': gapp->rotate -= 15; forkwork(drawpage); break;
+ case 'R': gapp->rotate += 15; forkwork(drawpage); break;
+ }
+}
+
+static void onexpose(GtkWidget *widget, GdkEventExpose *event, void *data)
+{
+ PDFApp *app = data;
+ if (app->image)
+ {
+ int x0 = event->area.x;
+ int y0 = event->area.y;
+ int w = event->area.width;
+ int h = event->area.height;
+ int x1, y1;
+ int x, y;
+
+ unsigned char *rgb = fz_malloc(w * h * 3);
+ unsigned char *s, *d;
+
+ x1 = MIN(x0 + w, app->image->w);
+ y1 = MIN(y0 + h, app->image->h);
+
+ if (x0 + w > x1 || y0 + h > y1)
+ memset(rgb, 200, w * h * 3);
+
+ for (y = y0; y < y1; y++)
+ {
+ s = app->image->samples + (y * app->image->w + x0) * 4;
+ d = rgb + (y - y0) * w * 3;
+ for (x = x0; x < x1; x++)
+ {
+ d[0] = s[1];
+ d[1] = s[2];
+ d[2] = s[3];
+ s += 4;
+ d += 3;
+ }
+ }
+
+ gdk_draw_rgb_image(widget->window,
+ widget->style->black_gc,
+ x0, y0, w, h,
+ GDK_RGB_DITHER_NONE, rgb,
+ w * 3);
+
+ fz_free(rgb);
+ }
+}
+
+/*
+ * Construct widgets
+ */
+
+static GtkWidget *
+addmenuitem(GtkWidget *menu, const char *name, GtkSignalFunc callback,
+ void *callback_data, GtkAccelGroup *ag, const char *accel)
+{
+ GtkWidget *menuitem;
+ menuitem = gtk_menu_item_new_with_label(name);
+ gtk_menu_append(GTK_MENU(menu), menuitem);
+ gtk_widget_show(menuitem);
+ if (accel != NULL)
+ {
+ guint accel_key, accel_mods;
+ gtk_accelerator_parse(accel, &accel_key, &accel_mods);
+ gtk_widget_add_accelerator(menuitem, "activate", ag,
+ accel_key, accel_mods, GTK_ACCEL_VISIBLE);
+ }
+ gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
+ (GtkSignalFunc)callback, callback_data);
+ return menuitem;
+}
+
+static void
+addmenuseparator(GtkWidget *menu)
+{
+ GtkWidget *menuitem;
+ menuitem = gtk_menu_item_new();
+ gtk_menu_append(GTK_MENU(menu), menuitem);
+ gtk_widget_show(menuitem);
+}
+
+void makeapp(PDFApp *app)
+{
+ GtkWidget *frame;
+ GtkWidget *menubar;
+ GtkWidget *menu;
+ GtkWidget *menuitem;
+ GtkWidget *da;
+ GtkWidget *sb;
+ GtkWidget *sv;
+ GtkWidget *vbox;
+ GtkAccelGroup *ag;
+ void *data = app;
+
+ frame = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ gtk_signal_connect(GTK_OBJECT(frame), "destroy", (GtkSignalFunc)onquit, "WM destroy");
+ gtk_window_set_title(GTK_WINDOW(frame), "MuPDF");
+ gtk_widget_set_usize(GTK_WIDGET(frame), 300, 200);
+
+ vbox = gtk_vbox_new(FALSE, 1);
+ gtk_container_add(GTK_CONTAINER(frame), vbox);
+ gtk_widget_show(vbox);
+
+ menubar = gtk_menu_bar_new();
+ ag = gtk_accel_group_new();
+ gtk_window_add_accel_group(GTK_WINDOW(frame), ag);
+ gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
+ gtk_widget_show(menubar);
+
+ menu = gtk_menu_new();
+ menuitem = gtk_menu_item_new_with_label("File");
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu);
+ gtk_menu_bar_append(GTK_MENU_BAR(menubar), menuitem);
+ gtk_widget_show(menuitem);
+ gtk_menu_set_accel_group(GTK_MENU(menu), ag);
+ addmenuitem(menu, "Info...", (GtkSignalFunc)oninfo, data, ag, "<ctrl>I");
+ addmenuitem(menu, "Open...", (GtkSignalFunc)onopen, data, ag, "<ctrl>O");
+ addmenuseparator(menu);
+ addmenuitem(menu, "Quit", (GtkSignalFunc)onquit, data, ag, "<ctrl>Q");
+
+ menu = gtk_menu_new();
+ menuitem = gtk_menu_item_new_with_label("Go");
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu);
+ gtk_menu_bar_append(GTK_MENU_BAR(menubar), menuitem);
+ gtk_widget_show(menuitem);
+ gtk_menu_set_accel_group(GTK_MENU(menu), ag);
+ addmenuitem(menu, "Back", (GtkSignalFunc)onback10, data, ag, "<ctrl>B");
+ addmenuitem(menu, "Next", (GtkSignalFunc)onnext10, data, ag, "<ctrl>F");
+ addmenuitem(menu, "Back 10", (GtkSignalFunc)onback10, data, ag, nil);
+ addmenuitem(menu, "Next 10", (GtkSignalFunc)onnext10, data, ag, nil);
+ addmenuseparator(menu);
+ addmenuitem(menu, "First", (GtkSignalFunc)onfirst, data, ag, nil);
+ addmenuitem(menu, "Last", (GtkSignalFunc)onlast, data, ag, "<ctrl>G");
+
+ menu = gtk_menu_new();
+ menuitem = gtk_menu_item_new_with_label("View");
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu);
+ gtk_menu_bar_append(GTK_MENU_BAR(menubar), menuitem);
+ gtk_widget_show(menuitem);
+ gtk_menu_set_accel_group(GTK_MENU(menu), ag);
+ addmenuitem(menu, "Zoom in", (GtkSignalFunc)onzoomin, data, ag, "<ctrl>'+'");
+ addmenuitem(menu, "Zoom out", (GtkSignalFunc)onzoomout, data, ag, "<ctrl>'-'");
+ addmenuitem(menu, "Zoom 100%", (GtkSignalFunc)onzoom100, data, ag, "<ctrl>1");
+ addmenuitem(menu, "Fit width", (GtkSignalFunc)onfitwidth, data, ag, nil);
+ addmenuitem(menu, "Fit page", (GtkSignalFunc)onfitpage, data, ag, nil);
+ addmenuseparator(menu);
+ addmenuitem(menu, "Rotate left", (GtkSignalFunc)onrotl, data, ag, "<ctrl>L");
+ addmenuitem(menu, "Rotate right", (GtkSignalFunc)onrotr, data, ag, "<ctrl>R");
+
+ sv = gtk_scrolled_window_new(NULL, NULL);
+ app->scroll = sv;
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sv), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+ GTK_WIDGET_SET_FLAGS(sv, GTK_CAN_FOCUS);
+ gtk_widget_set_extension_events(sv, GDK_EXTENSION_EVENTS_ALL);
+ gtk_signal_connect(GTK_OBJECT(sv), "button_press_event", (GtkSignalFunc)mousedown, data);
+ gtk_signal_connect(GTK_OBJECT(sv), "motion_notify_event", (GtkSignalFunc)mousemove, data);
+ gtk_signal_connect(GTK_OBJECT(sv), "button_release_event", (GtkSignalFunc)mouseup, data);
+ gtk_signal_connect(GTK_OBJECT(sv), "key_press_event", (GtkSignalFunc)keypress, data);
+ gtk_box_pack_start(GTK_BOX(vbox), sv, TRUE, TRUE, 0);
+ gtk_widget_show(sv);
+
+ da = gtk_drawing_area_new();
+ app->canvas = da;
+ gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(sv), da);
+ gtk_signal_connect(GTK_OBJECT(da), "expose_event", (GtkSignalFunc)onexpose, data);
+ gtk_widget_set_events (da,
+ GDK_BUTTON_PRESS_MASK
+ | GDK_BUTTON_RELEASE_MASK
+ | GDK_POINTER_MOTION_MASK);
+ gtk_widget_show(da);
+
+ sb = gtk_statusbar_new();
+ app->status = sb;
+ app->statusid = gtk_statusbar_get_context_id(GTK_STATUSBAR(sb), "mupdf");
+ gtk_box_pack_start(GTK_BOX(vbox), sb, FALSE, FALSE, 0);
+ gtk_widget_show(sb);
+
+ gtk_window_set_default_size(GTK_WINDOW(frame), 512, 512);
+ gtk_widget_grab_focus(sv);
+ gtk_widget_show(frame);
+}
+
+int main(int argc, char **argv)
+{
+ fz_error *error;
+ PDFApp theapp;
+
+ g_thread_init(NULL);
+ gtk_init(&argc, &argv);
+ gtk_widget_set_default_colormap(gdk_rgb_get_cmap());
+ gtk_widget_set_default_visual(gdk_rgb_get_visual());
+
+ makeapp(&theapp);
+ gapp = &theapp;
+
+ gapp->pageno = 1;
+ gapp->rotate = 0;
+ gapp->zoom = 1.0;
+
+ gapp->image = nil;
+ gapp->xref = nil;
+ gapp->pagetree = nil;
+ gapp->page = nil;
+
+ fz_cpudetect();
+ fz_accelerate();
+
+ error = fz_newrenderer(&gapp->gc, pdf_devicergb, 0, 1024 * 512);
+ if (error)
+ fz_abort(error);
+
+ if (argc > 1)
+ {
+ gapp->filename = argv[1];
+ forkwork(openpdf);
+ }
+
+ gdk_threads_enter();
+ gtk_main();
+ gdk_threads_leave();
+ return 0;
+}
+