diff options
Diffstat (limited to 'apps/pdfapp.c')
-rw-r--r-- | apps/pdfapp.c | 685 |
1 files changed, 685 insertions, 0 deletions
diff --git a/apps/pdfapp.c b/apps/pdfapp.c new file mode 100644 index 00000000..137825ec --- /dev/null +++ b/apps/pdfapp.c @@ -0,0 +1,685 @@ +#include <fitz.h> +#include <mupdf.h> +#include "pdfapp.h" + +enum panning +{ + DONT_PAN = 0, + PAN_TO_TOP, + PAN_TO_BOTTOM +}; + +static void pdfapp_showpage(pdfapp_t *app, int loadpage, int drawpage); + +static void pdfapp_warn(pdfapp_t *app, const char *fmt, ...) +{ + char buf[1024]; + va_list ap; + va_start(ap, fmt); + vsprintf(buf, fmt, ap); + va_end(ap); + winwarn(app, buf); +} + +static void pdfapp_error(pdfapp_t *app, fz_error error) +{ + winerror(app, error); +} + +char *pdfapp_usage(pdfapp_t *app) +{ + return + " l <\t\t-- rotate left\n" + " r >\t\t-- rotate right\n" + " u up\t\t-- scroll up\n" + " d down\t-- scroll down\n" + " = +\t\t-- zoom in\n" + " -\t\t-- zoom out\n" + " w\t\t-- shrinkwrap\n" + "\n" + " n pgdn space\t-- next page\n" + " b pgup back\t-- previous page\n" + " right\t\t-- next page\n" + " left\t\t-- previous page\n" + " N F\t\t-- next 10\n" + " B\t\t-- back 10\n" + " m\t\t-- mark page for snap back\n" + " t\t\t-- pop back to last mark\n" + " 123g\t\t-- go to page\n" + "\n" + " left drag to pan, right drag to copy text\n"; +} + +void pdfapp_init(pdfapp_t *app) +{ + fz_error error; + + memset(app, 0, sizeof(pdfapp_t)); + + error = fz_newrenderer(&app->rast, pdf_devicergb, 0, 1024 * 512); + if (error) + pdfapp_error(app, error); + + app->scrw = 640; + app->scrh = 480; + app->zoom = 1.0; +} + +void pdfapp_open(pdfapp_t *app, char *filename) +{ + fz_error error; + fz_obj *obj; + char *password = ""; + + /* + * Open PDF and load xref table + */ + + app->filename = filename; + + app->xref = pdf_newxref(); + error = pdf_loadxref(app->xref, filename); + if (error) + { + fz_catch(error, "trying to repair"); + pdfapp_warn(app, "There was a problem with file \"%s\".\nIt may be corrupted or generated by faulty software.\nTrying to repair the file.", filename); + error = pdf_repairxref(app->xref, filename); + if (error) + pdfapp_error(app, error); + } + + error = pdf_decryptxref(app->xref); + if (error) + pdfapp_error(app, error); + + /* + * Handle encrypted PDF files + */ + + if (pdf_needspassword(app->xref)) + { + int okay = pdf_authenticatepassword(app->xref, password); + while (!okay) + { + password = winpassword(app, filename); + if (!password) + exit(1); + okay = pdf_authenticatepassword(app->xref, password); + if (!okay) + pdfapp_warn(app, "Invalid password."); + } + } + + /* + * Load meta information + * TODO: move this into mupdf library + */ + + obj = fz_dictgets(app->xref->trailer, "Root"); + app->xref->root = fz_resolveindirect(obj); + if (!app->xref->root) + pdfapp_error(app, fz_throw("syntaxerror: missing Root object")); + fz_keepobj(app->xref->root); + + obj = fz_dictgets(app->xref->trailer, "Info"); + app->xref->info = fz_resolveindirect(obj); + if (!app->xref->info) + pdfapp_warn(app, "Could not load PDF meta information."); + if (app->xref->info) + fz_keepobj(app->xref->info); + + app->outline = pdf_loadoutline(app->xref); + + app->doctitle = filename; + if (strrchr(app->doctitle, '\\')) + app->doctitle = strrchr(app->doctitle, '\\') + 1; + if (strrchr(app->doctitle, '/')) + app->doctitle = strrchr(app->doctitle, '/') + 1; + if (app->xref->info) + { + obj = fz_dictgets(app->xref->info, "Title"); + if (obj) + { + app->doctitle = pdf_toutf8(obj); + } + } + + /* + * Start at first page + */ + + app->pagecount = pdf_getpagecount(app->xref); + + app->shrinkwrap = 1; + if (app->pageno < 1) + app->pageno = 1; + if (app->zoom < 0.1) + app->zoom = 0.1; + if (app->zoom > 3.0) + app->zoom = 3.0; + app->rotate = 0; + app->panx = 0; + app->pany = 0; + + pdfapp_showpage(app, 1, 1); +} + +void pdfapp_close(pdfapp_t *app) +{ + if (app->page) + pdf_droppage(app->page); + app->page = nil; + + if (app->image) + fz_droppixmap(app->image); + app->image = nil; + + if (app->outline) + pdf_dropoutline(app->outline); + app->outline = nil; + + if (app->xref->store) + pdf_dropstore(app->xref->store); + app->xref->store = nil; + + pdf_closexref(app->xref); + app->xref = nil; +} + +static fz_matrix pdfapp_viewctm(pdfapp_t *app) +{ + fz_matrix ctm; + ctm = fz_identity(); + ctm = fz_concat(ctm, fz_translate(0, -app->page->mediabox.y1)); + ctm = fz_concat(ctm, fz_scale(app->zoom, -app->zoom)); + ctm = fz_concat(ctm, fz_rotate(app->rotate + app->page->rotate)); + return ctm; +} + +static void pdfapp_panview(pdfapp_t *app, int newx, int newy) +{ + if (newx > 0) + newx = 0; + if (newy > 0) + newy = 0; + + if (newx + app->image->w < app->winw) + newx = app->winw - app->image->w; + if (newy + app->image->h < app->winh) + newy = app->winh - app->image->h; + + if (app->winw >= app->image->w) + newx = (app->winw - app->image->w) / 2; + if (app->winh >= app->image->h) + newy = (app->winh - app->image->h) / 2; + + if (newx != app->panx || newy != app->pany) + winrepaint(app); + + app->panx = newx; + app->pany = newy; +} + +static void pdfapp_showpage(pdfapp_t *app, int loadpage, int drawpage) +{ + char buf[256]; + fz_error error; + fz_matrix ctm; + fz_rect bbox; + fz_obj *obj; + + if (loadpage) + { + wincursor(app, WAIT); + + if (app->page) + pdf_droppage(app->page); + app->page = nil; + + pdf_flushxref(app->xref, 0); + + obj = pdf_getpageobject(app->xref, app->pageno); + error = pdf_loadpage(&app->page, app->xref, obj); + if (error) + pdfapp_error(app, error); + + sprintf(buf, "%s - %d/%d", app->doctitle, + app->pageno, app->pagecount); + wintitle(app, buf); + } + + if (drawpage) + { + wincursor(app, WAIT); + + if (app->image) + fz_droppixmap(app->image); + app->image = nil; + + ctm = pdfapp_viewctm(app); + bbox = fz_transformaabb(ctm, app->page->mediabox); + + error = fz_rendertree(&app->image, app->rast, app->page->tree, + ctm, fz_roundrect(bbox), 1); + if (error) + pdfapp_error(app, error); + + winconvert(app, app->image); + } + + pdfapp_panview(app, app->panx, app->pany); + + if (app->shrinkwrap) + { + int w = app->image->w; + int h = app->image->h; + if (app->winw == w) + app->panx = 0; + if (app->winh == h) + app->pany = 0; + if (w > app->scrw * 90 / 100) + w = app->scrw * 90 / 100; + if (h > app->scrh * 90 / 100) + h = app->scrh * 90 / 100; + if (w != app->winw || h != app->winh) + winresize(app, w, h); + } + + winrepaint(app); + + wincursor(app, ARROW); +} + +static void pdfapp_gotouri(pdfapp_t *app, fz_obj *uri) +{ + char *buf; + buf = fz_malloc(fz_tostrlen(uri) + 1); + memcpy(buf, fz_tostrbuf(uri), fz_tostrlen(uri)); + buf[fz_tostrlen(uri)] = 0; + winopenuri(app, buf); + fz_free(buf); +} + +static void pdfapp_gotopage(pdfapp_t *app, fz_obj *obj) +{ + int page; + + page = pdf_findpageobject(app->xref, obj); + + if (app->histlen + 1 == 256) + { + memmove(app->hist, app->hist + 1, sizeof(int) * 255); + app->histlen --; + } + app->hist[app->histlen++] = app->pageno; + app->pageno = page; + pdfapp_showpage(app, 1, 1); +} + +void pdfapp_onresize(pdfapp_t *app, int w, int h) +{ + if (app->winw != w || app->winh != h) + { + app->winw = w; + app->winh = h; + pdfapp_panview(app, app->panx, app->pany); + winrepaint(app); + } +} + +void pdfapp_onkey(pdfapp_t *app, int c) +{ + int oldpage = app->pageno; + enum panning panto = PAN_TO_TOP; + + /* + * Save numbers typed for later + */ + + if (c >= '0' && c <= '9') + { + app->number[app->numberlen++] = c; + app->number[app->numberlen] = '\0'; + } + + switch (c) + { + + /* + * Zoom and rotate + */ + + case '+': + case '=': + app->zoom += 0.1; + if (app->zoom > 3.0) + app->zoom = 3.0; + pdfapp_showpage(app, 0, 1); + break; + case '-': + app->zoom -= 0.1; + if (app->zoom < 0.1) + app->zoom = 0.1; + pdfapp_showpage(app, 0, 1); + break; + case 'l': + case '<': + app->rotate -= 90; + pdfapp_showpage(app, 0, 1); + break; + case 'r': + case '>': + app->rotate += 90; + pdfapp_showpage(app, 0, 1); + break; + + case 'a': + app->rotate -= 15; + pdfapp_showpage(app, 0, 1); + break; + case 's': + app->rotate += 15; + pdfapp_showpage(app, 0, 1); + break; + + /* + * Pan view, but dont need to repaint image + */ + + case 'w': + app->shrinkwrap = 1; + app->panx = app->pany = 0; + pdfapp_showpage(app, 0, 0); + break; + + case 'd': + app->pany -= app->image->h / 10; + pdfapp_showpage(app, 0, 0); + break; + + case 'u': + app->pany += app->image->h / 10; + pdfapp_showpage(app, 0, 0); + break; + + case ',': + app->panx += app->image->w / 10; + pdfapp_showpage(app, 0, 0); + break; + + case '.': + app->panx -= app->image->w / 10; + pdfapp_showpage(app, 0, 0); + break; + + /* + * Page navigation + */ + + case 'g': + case '\n': + case '\r': + if (app->numberlen > 0) + app->pageno = atoi(app->number); + break; + + case 'G': + app->pageno = app->pagecount; + break; + + case 'm': + if (app->histlen + 1 == 256) + { + memmove(app->hist, app->hist + 1, sizeof(int) * 255); + app->histlen --; + } + app->hist[app->histlen++] = app->pageno; + break; + + case 't': + if (app->histlen > 0) + app->pageno = app->hist[--app->histlen]; + break; + + /* + * Back and forth ... + */ + + case 'p': + panto = PAN_TO_BOTTOM; + if (app->numberlen > 0) + app->pageno -= atoi(app->number); + else + app->pageno--; + break; + + case 'n': + panto = PAN_TO_TOP; + if (app->numberlen > 0) + app->pageno += atoi(app->number); + else + app->pageno++; + break; + + case 'b': + case '\b': + panto = DONT_PAN; + if (app->numberlen > 0) + app->pageno -= atoi(app->number); + else + app->pageno--; + break; + + case 'f': + case ' ': + panto = DONT_PAN; + if (app->numberlen > 0) + app->pageno += atoi(app->number); + else + app->pageno++; + break; + + case 'B': + panto = PAN_TO_TOP; app->pageno -= 10; break; + case 'F': + panto = PAN_TO_TOP; app->pageno += 10; break; + } + + if (c < '0' || c > '9') + app->numberlen = 0; + + if (app->pageno < 1) + app->pageno = 1; + if (app->pageno > app->pagecount) + app->pageno = app->pagecount; + + if (app->pageno != oldpage) + { + switch (panto) + { + case PAN_TO_TOP: app->pany = 0; break; + case PAN_TO_BOTTOM: app->pany = -2000; break; + case DONT_PAN: break; + } + pdfapp_showpage(app, 1, 1); + } +} + +void pdfapp_onmouse(pdfapp_t *app, int x, int y, int btn, int modifiers, int state) +{ + pdf_link *link; + fz_matrix ctm; + fz_point p; + + p.x = x - app->panx + app->image->x; + p.y = y - app->pany + app->image->y; + + ctm = pdfapp_viewctm(app); + ctm = fz_invertmatrix(ctm); + + p = fz_transformpoint(ctm, p); + + for (link = app->page->links; link; link = link->next) + { + if (p.x >= link->rect.x0 && p.x <= link->rect.x1) + if (p.y >= link->rect.y0 && p.y <= link->rect.y1) + break; + } + + if (link) + { + wincursor(app, HAND); + if (btn == 1 && state == 1) + { + if (link->kind == PDF_LURI) + pdfapp_gotouri(app, link->dest); + else if (link->kind == PDF_LGOTO) + pdfapp_gotopage(app, link->dest); + return; + } + } + else + { + wincursor(app, ARROW); + } + + if (state == 1) + { + if (btn == 1 && !app->iscopying) + { + app->ispanning = 1; + app->selx = x; + app->sely = y; + } + if (btn == 3 && !app->ispanning) + { + app->iscopying = 1; + app->selx = x; + app->sely = y; + app->selr.x0 = x; + app->selr.x1 = x; + app->selr.y0 = y; + app->selr.y1 = y; + } + if (btn == 4 || btn == 5) /* scroll wheel */ + { + int dir = btn == 4 ? 1 : -1; + app->ispanning = app->iscopying = 0; + if (modifiers & (1<<2)) + { + /* zoom in/out if ctrl is pressed */ + app->zoom += 0.1 * dir; + if (app->zoom > 3.0) + app->zoom = 3.0; + if (app->zoom < 0.1) + app->zoom = 0.1; + pdfapp_showpage(app, 0, 1); + } + else + { + /* scroll up/down, or left/right if + shift is pressed */ + int isx = (modifiers & (1<<0)); + int xstep = isx ? 20 * dir : 0; + int ystep = !isx ? 20 * dir : 0; + pdfapp_panview(app, app->panx + xstep, app->pany + ystep); + } + } + } + + else if (state == -1) + { + if (app->iscopying) + { + app->iscopying = 0; + app->selr.x0 = MIN(app->selx, x); + app->selr.x1 = MAX(app->selx, x); + app->selr.y0 = MIN(app->sely, y); + app->selr.y1 = MAX(app->sely, y); + winrepaint(app); + if (app->selr.x0 < app->selr.x1 && app->selr.y0 < app->selr.y1) + windocopy(app); + } + if (app->ispanning) + app->ispanning = 0; + } + + else if (app->ispanning) + { + int newx = app->panx + x - app->selx; + int newy = app->pany + y - app->sely; + pdfapp_panview(app, newx, newy); + app->selx = x; + app->sely = y; + } + + else if (app->iscopying) + { + app->selr.x0 = MIN(app->selx, x); + app->selr.x1 = MAX(app->selx, x); + app->selr.y0 = MIN(app->sely, y); + app->selr.y1 = MAX(app->sely, y); + winrepaint(app); + } + +} + +void pdfapp_oncopy(pdfapp_t *app, unsigned short *ucsbuf, int ucslen) +{ + ucsbuf[0] = 0; +#if 0 /* pdf_loadtextfromtree needs rewriting, so removing this temporarily */ + fz_error error; + pdf_textline *line, *ln; + int y, c; + int i, p; + + int bx0, bx1, by0, by1; + + int x0 = app->image->x + app->selr.x0 - app->panx; + int x1 = app->image->x + app->selr.x1 - app->panx; + int y0 = app->image->y + app->selr.y0 - app->pany; + int y1 = app->image->y + app->selr.y1 - app->pany; + + error = pdf_loadtextfromtree(&line, app->page->tree, pdfapp_viewctm(app)); + if (error) + pdfapp_error(app, error); + + p = 0; + for (ln = line; ln; ln = ln->next) + { + if (ln->len > 0) + { + y = y0 - 1; + + for (i = 0; i < ln->len; i++) + { + bx0 = ln->text[i].bbox.x0; + bx1 = ln->text[i].bbox.x1; + by0 = ln->text[i].bbox.y0; + by1 = ln->text[i].bbox.y1; + c = ln->text[i].c; + if (c < 32) + c = '?'; + if (bx1 >= x0 && bx0 <= x1 && by1 >= y0 && by0 <= y1) + if (p < ucslen - 1) + ucsbuf[p++] = c; + } + + if (by1 >= y0 && by0 <= y1) + { +#ifdef WIN32 + if (p < ucslen - 1) + ucsbuf[p++] = '\r'; +#endif + if (p < ucslen - 1) + ucsbuf[p++] = '\n'; + } + } + } + + ucsbuf[p] = 0; + + pdf_droptextline(line); +#endif +} + |