diff options
Diffstat (limited to 'platform/x11')
-rw-r--r-- | platform/x11/jstest_main.c | 424 | ||||
-rw-r--r-- | platform/x11/mupdf.ico | bin | 0 -> 15086 bytes | |||
-rw-r--r-- | platform/x11/pdfapp.c | 1545 | ||||
-rw-r--r-- | platform/x11/pdfapp.h | 149 | ||||
-rw-r--r-- | platform/x11/win_main.c | 1163 | ||||
-rw-r--r-- | platform/x11/win_res.rc | 82 | ||||
-rw-r--r-- | platform/x11/x11_image.c | 703 | ||||
-rw-r--r-- | platform/x11/x11_main.c | 993 |
8 files changed, 5059 insertions, 0 deletions
diff --git a/platform/x11/jstest_main.c b/platform/x11/jstest_main.c new file mode 100644 index 00000000..2003ad55 --- /dev/null +++ b/platform/x11/jstest_main.c @@ -0,0 +1,424 @@ +#include "pdfapp.h" + +#include <ctype.h> + +/* + A useful bit of bash script to call this is: + for f in ../ghostpcl/tests_private/pdf/forms/v1.3/ *.pdf ; do g=${f%.*} ; echo $g ; win32/debug/mujstest-v8.exe -o $g-%d.png -p ../ghostpcl/ $g.mjs > $g.log 2>&1 ; done + + Remove the space from "/ *.pdf" before running - can't leave that + in here, as it causes a warning about a possibly malformed comment. +*/ + +static pdfapp_t gapp; +static int file_open = 0; +static char filename[1024] = ""; +static char *scriptname; +static char *output = NULL; +static char *prefix = NULL; +static int shotcount = 0; +static int verbosity = 0; + +#define LONGLINE 4096 + +static char getline_buffer[LONGLINE]; + +void winwarn(pdfapp_t *app, char *msg) +{ + fprintf(stderr, "warning: %s\n", msg); +} + +void winerror(pdfapp_t *app, char *msg) +{ + fprintf(stderr, "%s\n", msg); + exit(1); +} + +void winalert(pdfapp_t *app, pdf_alert_event *alert) +{ + fprintf(stderr, "Alert %s: %s", alert->title, alert->message); + switch (alert->button_group_type) + { + case PDF_ALERT_BUTTON_GROUP_OK: + case PDF_ALERT_BUTTON_GROUP_OK_CANCEL: + alert->button_pressed = PDF_ALERT_BUTTON_OK; + break; + case PDF_ALERT_BUTTON_GROUP_YES_NO: + case PDF_ALERT_BUTTON_GROUP_YES_NO_CANCEL: + alert->button_pressed = PDF_ALERT_BUTTON_YES; + break; + } +} + +void winadvancetimer(pdfapp_t *app, float duration) +{ +} + +void winprint(pdfapp_t *app) +{ + fprintf(stderr, "The MuPDF library supports printing, but this application currently does not"); +} + +static char pd_password[256] = ""; +static char td_textinput[LONGLINE] = ""; + +char *winpassword(pdfapp_t *app, char *filename) +{ + if (pd_password[0] == 0) + return NULL; + return pd_password; +} + +char *wintextinput(pdfapp_t *app, char *inittext, int retry) +{ + if (retry) + return NULL; + + if (td_textinput[0] != 0) + return td_textinput; + return inittext; +} + +int winchoiceinput(pdfapp_t *app, int nopts, char *opts[], int *nvals, char *vals[]) +{ + return 0; +} + +void winhelp(pdfapp_t*app) +{ +} + +void winclose(pdfapp_t *app) +{ + pdfapp_close(app); + exit(0); +} + +int winsavequery(pdfapp_t *app) +{ + return DISCARD; +} + +int wingetsavepath(pdfapp_t *app, char *buf, int len) +{ + return 0; +} + +void winreplacefile(char *source, char *target) +{ +} + +void wincursor(pdfapp_t *app, int curs) +{ +} + +void wintitle(pdfapp_t *app, char *title) +{ +} + +void windrawrect(pdfapp_t *app, int x0, int y0, int x1, int y1) +{ +} + +void windrawstring(pdfapp_t *app, int x, int y, char *s) +{ +} + +void winresize(pdfapp_t *app, int w, int h) +{ +} + +void winrepaint(pdfapp_t *app) +{ +} + +void winrepaintsearch(pdfapp_t *app) +{ +} + +void winfullscreen(pdfapp_t *app, int state) +{ +} + +/* + * Event handling + */ + +void windocopy(pdfapp_t *app) +{ +} + +void winreloadfile(pdfapp_t *app) +{ + pdfapp_close(app); + pdfapp_open(app, filename, 1); +} + +void winopenuri(pdfapp_t *app, char *buf) +{ +} + +static void +usage(void) +{ + fprintf(stderr, "mujstest: Scriptable tester for mupdf + js\n"); + fprintf(stderr, "\nSyntax: mujstest -o <filename> [ -p <prefix> ] [-v] <scriptfile>\n"); + fprintf(stderr, "\n<filename> should sensibly be of the form file-%%d.png\n"); + fprintf(stderr, "\n<prefix> is a path prefix to apply to filenames within the script\n"); + fprintf(stderr, "\n-v\tverbose\n"); + fprintf(stderr, "\nscriptfile contains a list of commands:\n"); + fprintf(stderr, "\tPASSWORD <password>\tSet the password\n"); + fprintf(stderr, "\tOPEN <filename>\tOpen a file\n"); + fprintf(stderr, "\tGOTO <page>\tJump to a particular page\n"); + fprintf(stderr, "\tSCREENSHOT\tSave a screenshot\n"); + fprintf(stderr, "\tRESIZE <w> <h>\tResize the screen to a given size\n"); + fprintf(stderr, "\tCLICK <x> <y> <btn>\tClick at a given position\n"); + fprintf(stderr, "\tTEXT <string>\tSet a value to be entered\n"); + exit(1); +} + +static char * +my_getline(FILE *file) +{ + int c; + char *d = getline_buffer; + int space = sizeof(getline_buffer)-1; + + /* Skip over any prefix of whitespace */ + do + { + c = fgetc(file); + } + while (isspace(c)); + + if (c < 0) + return NULL; + + /* Read the line in */ + do + { + *d++ = (char)c; + c = fgetc(file); + } + while (c >= 32 && space--); + + /* If we ran out of space, skip the rest of the line */ + if (space == 0) + { + while (c >= 32) + c = fgetc(file); + } + + *d = 0; + + return getline_buffer; +} + +static int +match(char **line, const char *match) +{ + char *s = *line; + + if (s == NULL) + return 0; + + while (isspace(*(unsigned char *)s)) + s++; + + while (*s == *match) + { + if (*s == 0) + { + *line = s; + return 1; + } + s++; + match++; + } + + if (*match != 0) + return 0; + + /* We matched! Skip over any whitespace */ + while (isspace(*(unsigned char *)s)) + s++; + + *line = s; + + /* Trim whitespace off the end of the line */ + /* Run to the end of the line */ + while (*s) + s++; + + /* Run back until we find where we started, or non whitespace */ + while (s != *line && isspace((unsigned char)s[-1])) + s--; + + /* Remove the suffix of whitespace */ + *s = 0; + + return 1; +} + +static void unescape_string(char *d, const char *s) +{ + char c; + + while ((c = *s++) != 0) + { + if (c == '\\') + { + c = *s++; + switch(c) + { + case 'n': + c = '\n'; + break; + case 'r': + c = '\r'; + break; + case 't': + c = '\t'; + break; + } + } + *d++ = c; + } + *d = 0; +} + +int +main(int argc, char *argv[]) +{ + fz_context *ctx; + FILE *script = NULL; + int c; + + while ((c = fz_getopt(argc, argv, "o:p:v")) != -1) + { + switch(c) + { + case 'o': output = fz_optarg; break; + case 'p': prefix = fz_optarg; break; + case 'v': verbosity ^= 1; break; + default: usage(); break; + } + } + + if (fz_optind == argc) + usage(); + + ctx = fz_new_context(NULL, NULL, FZ_STORE_DEFAULT); + if (!ctx) + { + fprintf(stderr, "cannot initialise context\n"); + exit(1); + } + pdfapp_init(ctx, &gapp); + gapp.scrw = 640; + gapp.scrh = 480; + gapp.colorspace = fz_device_rgb(ctx); + + fz_try(ctx) + { + while (fz_optind < argc) + { + scriptname = argv[fz_optind++]; + script = fopen(scriptname, "rb"); + if (script == NULL) + fz_throw(ctx, FZ_ERROR_GENERIC, "cannot open script: %s", scriptname); + + do + { + char *line = my_getline(script); + if (line == NULL) + continue; + if (verbosity) + fprintf(stderr, "'%s'\n", line); + if (match(&line, "%")) + { + /* Comment */ + } + else if (match(&line, "PASSWORD")) + { + strcpy(pd_password, line); + } + else if (match(&line, "OPEN")) + { + char path[1024]; + if (file_open) + pdfapp_close(&gapp); + strcpy(filename, line); + if (prefix) + { + sprintf(path, "%s%s", prefix, line); + } + else + { + strcpy(path, line); + } + pdfapp_open(&gapp, path, 0); + file_open = 1; + } + else if (match(&line, "GOTO")) + { + pdfapp_gotopage(&gapp, atoi(line)-1); + } + else if (match(&line, "SCREENSHOT")) + { + char text[1024]; + + sprintf(text, output, ++shotcount); + if (strstr(text, ".pgm") || strstr(text, ".ppm") || strstr(text, ".pnm")) + fz_write_pnm(ctx, gapp.image, text); + else + fz_write_png(ctx, gapp.image, text, 0); + } + else if (match(&line, "RESIZE")) + { + int w, h; + sscanf(line, "%d %d", &w, &h); + pdfapp_onresize(&gapp, w, h); + } + else if (match(&line, "CLICK")) + { + float x, y, b; + int n; + n = sscanf(line, "%f %f %f", &x, &y, &b); + if (n < 1) + x = 0.0f; + if (n < 2) + y = 0.0f; + if (n < 3) + b = 1; + /* state = 1 = transition down */ + pdfapp_onmouse(&gapp, (int)x, (int)y, b, 0, 1); + /* state = -1 = transition up */ + pdfapp_onmouse(&gapp, (int)x, (int)y, b, 0, -1); + } + else if (match(&line, "TEXT")) + { + unescape_string(td_textinput, line); + } + else + { + fprintf(stderr, "Unmatched: %s\n", line); + } + } + while (!feof(script)); + + fclose(script); + } + } + fz_catch(ctx) + { + fprintf(stderr, "error: cannot execute '%s'\n", scriptname); + } + + if (file_open) + pdfapp_close(&gapp); + + fz_free_context(ctx); + + return 0; +} diff --git a/platform/x11/mupdf.ico b/platform/x11/mupdf.ico Binary files differnew file mode 100644 index 00000000..80deb8ea --- /dev/null +++ b/platform/x11/mupdf.ico diff --git a/platform/x11/pdfapp.c b/platform/x11/pdfapp.c new file mode 100644 index 00000000..e76c6c7c --- /dev/null +++ b/platform/x11/pdfapp.c @@ -0,0 +1,1545 @@ +#include "pdfapp.h" + +#include <ctype.h> /* for tolower() */ + +#define ZOOMSTEP 1.142857 +#define BEYOND_THRESHHOLD 40 +#ifndef PATH_MAX +#define PATH_MAX (1024) +#endif + +#ifndef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#endif + +enum panning +{ + DONT_PAN = 0, + PAN_TO_TOP, + PAN_TO_BOTTOM +}; + +static void pdfapp_showpage(pdfapp_t *app, int loadpage, int drawpage, int repaint, int transition); +static void pdfapp_updatepage(pdfapp_t *app); + +static void pdfapp_warn(pdfapp_t *app, const char *fmt, ...) +{ + char buf[1024]; + va_list ap; + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + buf[sizeof(buf)-1] = 0; + winwarn(app, buf); +} + +static void pdfapp_error(pdfapp_t *app, char *msg) +{ + winerror(app, msg); +} + +char *pdfapp_version(pdfapp_t *app) +{ + return + "MuPDF 1.2\n" + "Copyright 2006-2013 Artifex Software, Inc.\n"; +} + +char *pdfapp_usage(pdfapp_t *app) +{ + return + "L\t\t-- rotate left\n" + "R\t\t-- rotate right\n" + "h\t\t-- scroll left\n" + "j down\t\t-- scroll down\n" + "k up\t\t-- scroll up\n" + "l\t\t-- scroll right\n" + "+\t\t-- zoom in\n" + "-\t\t-- zoom out\n" + "W\t\t-- zoom to fit window width\n" + "H\t\t-- zoom to fit window height\n" + "w\t\t-- shrinkwrap\n" + "f\t\t-- fullscreen\n" + "r\t\t-- reload file\n" + ". pgdn right spc\t-- next page\n" + ", pgup left b bkspc\t-- previous page\n" + ">\t\t-- next 10 pages\n" + "<\t\t-- back 10 pages\n" + "m\t\t-- mark page for snap back\n" + "t\t\t-- pop back to latest mark\n" + "1m\t\t-- mark page in register 1\n" + "1t\t\t-- go to page in register 1\n" + "G\t\t-- go to last page\n" + "123g\t\t-- go to page 123\n" + "/\t\t-- search forwards for text\n" + "?\t\t-- search backwards for text\n" + "n\t\t-- find next search result\n" + "N\t\t-- find previous search result\n" + "c\t\t-- toggle between color and grayscale\n" + "i\t\t-- toggle inverted color mode\n" + "q\t\t-- quit\n" + ; +} + +void pdfapp_init(fz_context *ctx, pdfapp_t *app) +{ + memset(app, 0, sizeof(pdfapp_t)); + app->scrw = 640; + app->scrh = 480; + app->resolution = 72; + app->ctx = ctx; +#ifdef _WIN32 + app->colorspace = fz_device_bgr(ctx); +#else + app->colorspace = fz_device_rgb(ctx); +#endif +} + +void pdfapp_invert(pdfapp_t *app, const fz_rect *rect) +{ + fz_irect b; + fz_invert_pixmap_rect(app->image, fz_round_rect(&b, rect)); +} + +static void event_cb(pdf_doc_event *event, void *data) +{ + pdfapp_t *app = (pdfapp_t *)data; + + switch (event->type) + { + case PDF_DOCUMENT_EVENT_ALERT: + { + pdf_alert_event *alert = pdf_access_alert_event(event); + winalert(app, alert); + } + break; + + case PDF_DOCUMENT_EVENT_PRINT: + winprint(app); + break; + + case PDF_DOCUMENT_EVENT_EXEC_MENU_ITEM: + { + char *item = pdf_access_exec_menu_item_event(event); + + if (!strcmp(item, "Print")) + winprint(app); + else + pdfapp_warn(app, "The document attempted to execute menu item: %s. (Not supported)", item); + } + break; + + case PDF_DOCUMENT_EVENT_EXEC_DIALOG: + pdfapp_warn(app, "The document attempted to open a dialog box. (Not supported)"); + break; + + case PDF_DOCUMENT_EVENT_LAUNCH_URL: + { + pdf_launch_url_event *launch_url = pdf_access_launch_url_event(event); + + pdfapp_warn(app, "The document attempted to open url: %s. (Not supported by app)", launch_url->url); + } + break; + + case PDF_DOCUMENT_EVENT_MAIL_DOC: + { + pdf_mail_doc_event *mail_doc = pdf_access_mail_doc_event(event); + + pdfapp_warn(app, "The document attmepted to mail the document%s%s%s%s%s%s%s%s (Not supported)", + mail_doc->to[0]?", To: ":"", mail_doc->to, + mail_doc->cc[0]?", Cc: ":"", mail_doc->cc, + mail_doc->bcc[0]?", Bcc: ":"", mail_doc->bcc, + mail_doc->subject[0]?", Subject: ":"", mail_doc->subject); + } + break; + } +} + +void pdfapp_open(pdfapp_t *app, char *filename, int reload) +{ + fz_context *ctx = app->ctx; + char *password = ""; + + fz_try(ctx) + { + pdf_document *idoc; + + app->doc = fz_open_document(ctx, filename); + + idoc = pdf_specifics(app->doc); + + if (idoc) + pdf_set_doc_event_callback(idoc, event_cb, app); + + if (fz_needs_password(app->doc)) + { + int okay = fz_authenticate_password(app->doc, password); + while (!okay) + { + password = winpassword(app, filename); + if (!password) + fz_throw(ctx, FZ_ERROR_GENERIC, "Needs a password"); + okay = fz_authenticate_password(app->doc, password); + if (!okay) + pdfapp_warn(app, "Invalid password."); + } + } + + app->docpath = fz_strdup(ctx, filename); + app->doctitle = filename; + if (strrchr(app->doctitle, '\\')) + app->doctitle = strrchr(app->doctitle, '\\') + 1; + if (strrchr(app->doctitle, '/')) + app->doctitle = strrchr(app->doctitle, '/') + 1; + app->doctitle = fz_strdup(ctx, app->doctitle); + + app->pagecount = fz_count_pages(app->doc); + app->outline = fz_load_outline(app->doc); + } + fz_catch(ctx) + { + pdfapp_error(app, "cannot open document"); + } + + if (app->pageno < 1) + app->pageno = 1; + if (app->pageno > app->pagecount) + app->pageno = app->pagecount; + if (app->resolution < MINRES) + app->resolution = MINRES; + if (app->resolution > MAXRES) + app->resolution = MAXRES; + + if (!reload) + { + app->shrinkwrap = 1; + app->rotate = 0; + app->panx = 0; + app->pany = 0; + } + + pdfapp_showpage(app, 1, 1, 1, 0); +} + +void pdfapp_close(pdfapp_t *app) +{ + fz_drop_display_list(app->ctx, app->page_list); + app->page_list = NULL; + + fz_drop_display_list(app->ctx, app->annotations_list); + app->annotations_list = NULL; + + fz_free_text_page(app->ctx, app->page_text); + app->page_text = NULL; + + fz_free_text_sheet(app->ctx, app->page_sheet); + app->page_sheet = NULL; + + fz_drop_link(app->ctx, app->page_links); + app->page_links = NULL; + + fz_free(app->ctx, app->doctitle); + app->doctitle = NULL; + + fz_free(app->ctx, app->docpath); + app->docpath = NULL; + + fz_drop_pixmap(app->ctx, app->image); + app->image = NULL; + + fz_drop_pixmap(app->ctx, app->new_image); + app->new_image = NULL; + + fz_drop_pixmap(app->ctx, app->old_image); + app->old_image = NULL; + + fz_free_outline(app->ctx, app->outline); + app->outline = NULL; + + fz_free_page(app->doc, app->page); + app->page = NULL; + + fz_close_document(app->doc); + app->doc = NULL; + + fz_flush_warnings(app->ctx); +} + +static int gen_tmp_file(char *buf, int len) +{ + int i; + char *name = strrchr(buf, '/'); + + if (name == NULL) + name = strrchr(buf, '\\'); + + if (name != NULL) + name++; + else + name = buf; + + for (i = 0; i < 10000; i++) + { + FILE *f; + snprintf(name, buf+len-name, "tmp%04d", i); + f = fopen(buf, "r"); + if (f == NULL) + return 1; + fclose(f); + } + + return 0; +} + +static int pdfapp_save(pdfapp_t *app) +{ + char buf[PATH_MAX]; + + if (wingetsavepath(app, buf, PATH_MAX)) + { + fz_write_options opts; + + opts.do_ascii = 1; + opts.do_expand = 0; + opts.do_garbage = 1; + opts.do_linear = 0; + + if (strcmp(buf, app->docpath) != 0) + { + fz_write_document(app->doc, buf, &opts); + return 1; + } + + if (gen_tmp_file(buf, PATH_MAX)) + { + int written; + + fz_try(app->ctx) + { + fz_write_document(app->doc, buf, &opts); + written = 1; + } + fz_catch(app->ctx) + { + written = 0; + } + + if (written) + { + char buf2[PATH_MAX]; + fz_strlcpy(buf2, app->docpath, PATH_MAX); + pdfapp_close(app); + winreplacefile(buf, buf2); + pdfapp_open(app, buf2, 1); + + return written; + } + } + } + + return 0; +} + +int pdfapp_preclose(pdfapp_t *app) +{ + pdf_document *idoc = pdf_specifics(app->doc); + + if (idoc && pdf_has_unsaved_changes(idoc)) + { + switch (winsavequery(app)) + { + case DISCARD: + return 1; + + case CANCEL: + return 0; + + case SAVE: + return pdfapp_save(app); + } + } + + return 1; +} + +static void pdfapp_viewctm(fz_matrix *mat, pdfapp_t *app) +{ + fz_pre_rotate(fz_scale(mat, app->resolution/72.0f, app->resolution/72.0f), app->rotate); +} + +static void pdfapp_panview(pdfapp_t *app, int newx, int newy) +{ + int image_w = fz_pixmap_width(app->ctx, app->image); + int image_h = fz_pixmap_height(app->ctx, app->image); + + if (newx > 0) + newx = 0; + if (newy > 0) + newy = 0; + + if (newx + image_w < app->winw) + newx = app->winw - image_w; + if (newy + image_h < app->winh) + newy = app->winh - image_h; + + if (app->winw >= image_w) + newx = (app->winw - image_w) / 2; + if (app->winh >= image_h) + newy = (app->winh - image_h) / 2; + + if (newx != app->panx || newy != app->pany) + winrepaint(app); + + app->panx = newx; + app->pany = newy; +} + +static void pdfapp_loadpage(pdfapp_t *app) +{ + fz_device *mdev = NULL; + int errored = 0; + fz_cookie cookie = { 0 }; + + fz_var(mdev); + + fz_drop_display_list(app->ctx, app->page_list); + fz_drop_display_list(app->ctx, app->annotations_list); + fz_free_text_page(app->ctx, app->page_text); + fz_free_text_sheet(app->ctx, app->page_sheet); + fz_drop_link(app->ctx, app->page_links); + fz_free_page(app->doc, app->page); + + app->page_list = NULL; + app->annotations_list = NULL; + app->page_text = NULL; + app->page_sheet = NULL; + app->page_links = NULL; + app->page = NULL; + app->page_bbox.x0 = 0; + app->page_bbox.y0 = 0; + app->page_bbox.x1 = 100; + app->page_bbox.y1 = 100; + + fz_try(app->ctx) + { + app->page = fz_load_page(app->doc, app->pageno - 1); + + fz_bound_page(app->doc, app->page, &app->page_bbox); + } + fz_catch(app->ctx) + { + pdfapp_warn(app, "Cannot load page"); + return; + } + + fz_try(app->ctx) + { + fz_annot *annot; + /* Create display lists */ + app->page_list = fz_new_display_list(app->ctx); + mdev = fz_new_list_device(app->ctx, app->page_list); + fz_run_page_contents(app->doc, app->page, mdev, &fz_identity, &cookie); + fz_free_device(mdev); + mdev = NULL; + app->annotations_list = fz_new_display_list(app->ctx); + mdev = fz_new_list_device(app->ctx, app->annotations_list); + for (annot = fz_first_annot(app->doc, app->page); annot; annot = fz_next_annot(app->doc, annot)) + fz_run_annot(app->doc, app->page, annot, mdev, &fz_identity, &cookie); + if (cookie.errors) + { + pdfapp_warn(app, "Errors found on page"); + errored = 1; + } + } + fz_always(app->ctx) + { + fz_free_device(mdev); + } + fz_catch(app->ctx) + { + pdfapp_warn(app, "Cannot load page"); + errored = 1; + } + + fz_try(app->ctx) + { + app->page_links = fz_load_links(app->doc, app->page); + } + fz_catch(app->ctx) + { + if (!errored) + pdfapp_warn(app, "Cannot load page"); + } + + app->errored = errored; +} + +static void pdfapp_recreate_annotationslist(pdfapp_t *app) +{ + fz_device *mdev = NULL; + int errored = 0; + fz_cookie cookie = { 0 }; + + fz_var(mdev); + + fz_drop_display_list(app->ctx, app->annotations_list); + app->annotations_list = NULL; + + fz_try(app->ctx) + { + fz_annot *annot; + /* Create display list */ + app->annotations_list = fz_new_display_list(app->ctx); + mdev = fz_new_list_device(app->ctx, app->annotations_list); + for (annot = fz_first_annot(app->doc, app->page); annot; annot = fz_next_annot(app->doc, annot)) + fz_run_annot(app->doc, app->page, annot, mdev, &fz_identity, &cookie); + if (cookie.errors) + { + pdfapp_warn(app, "Errors found on page"); + errored = 1; + } + } + fz_always(app->ctx) + { + fz_free_device(mdev); + } + fz_catch(app->ctx) + { + pdfapp_warn(app, "Cannot load page"); + errored = 1; + } + + app->errored = errored; +} + +static void pdfapp_runpage(pdfapp_t *app, fz_device *dev, const fz_matrix *ctm, const fz_rect *rect, fz_cookie *cookie) +{ + fz_begin_page(dev, rect, ctm); + if (app->page_list) + fz_run_display_list(app->page_list, dev, ctm, rect, cookie); + if (app->annotations_list) + fz_run_display_list(app->annotations_list, dev, ctm, rect, cookie); + fz_end_page(dev); +} + +#define MAX_TITLE 256 + +static void pdfapp_updatepage(pdfapp_t *app) +{ + pdf_document *idoc = pdf_specifics(app->doc); + fz_device *idev; + fz_matrix ctm; + fz_annot *annot; + + pdfapp_viewctm(&ctm, app); + pdf_update_page(idoc, (pdf_page *)app->page); + pdfapp_recreate_annotationslist(app); + + while ((annot = (fz_annot *)pdf_poll_changed_annot(idoc, (pdf_page *)app->page)) != NULL) + { + fz_rect bounds; + fz_irect ibounds; + fz_transform_rect(fz_bound_annot(app->doc, annot, &bounds), &ctm); + fz_rect_from_irect(&bounds, fz_round_rect(&ibounds, &bounds)); + fz_clear_pixmap_rect_with_value(app->ctx, app->image, 255, &ibounds); + idev = fz_new_draw_device_with_bbox(app->ctx, app->image, &ibounds); + pdfapp_runpage(app, idev, &ctm, &bounds, NULL); + fz_free_device(idev); + } + + pdfapp_showpage(app, 0, 0, 1, 0); +} + +static void pdfapp_showpage(pdfapp_t *app, int loadpage, int drawpage, int repaint, int transition) +{ + char buf[MAX_TITLE]; + fz_device *idev; + fz_device *tdev; + fz_colorspace *colorspace; + fz_matrix ctm; + fz_rect bounds; + fz_irect ibounds; + fz_cookie cookie = { 0 }; + + if (!app->nowaitcursor) + wincursor(app, WAIT); + + if (!app->transitions_enabled || !app->presentation_mode) + transition = 0; + + if (transition) + { + app->old_image = app->image; + app->image = NULL; + } + + if (loadpage) + { + pdfapp_loadpage(app); + + /* Zero search hit position */ + app->hit_count = 0; + + /* Extract text */ + app->page_sheet = fz_new_text_sheet(app->ctx); + app->page_text = fz_new_text_page(app->ctx); + + if (app->page_list || app->annotations_list) + { + tdev = fz_new_text_device(app->ctx, app->page_sheet, app->page_text); + pdfapp_runpage(app, tdev, &fz_identity, &fz_infinite_rect, &cookie); + fz_free_device(tdev); + } + } + + if (drawpage) + { + char buf2[64]; + int len; + + sprintf(buf2, " - %d/%d (%d dpi)", + app->pageno, app->pagecount, app->resolution); + len = MAX_TITLE-strlen(buf2); + if ((int)strlen(app->doctitle) > len) + { + snprintf(buf, len-3, "%s", app->doctitle); + strcat(buf, "..."); + strcat(buf, buf2); + } + else + sprintf(buf, "%s%s", app->doctitle, buf2); + wintitle(app, buf); + + pdfapp_viewctm(&ctm, app); + bounds = app->page_bbox; + fz_round_rect(&ibounds, fz_transform_rect(&bounds, &ctm)); + fz_rect_from_irect(&bounds, &ibounds); + + /* Draw */ + if (app->image) + fz_drop_pixmap(app->ctx, app->image); + if (app->grayscale) + colorspace = fz_device_gray(app->ctx); + else + colorspace = app->colorspace; + app->image = NULL; + app->image = fz_new_pixmap_with_bbox(app->ctx, colorspace, &ibounds); + fz_clear_pixmap_with_value(app->ctx, app->image, 255); + if (app->page_list || app->annotations_list) + { + idev = fz_new_draw_device(app->ctx, app->image); + pdfapp_runpage(app, idev, &ctm, &bounds, &cookie); + fz_free_device(idev); + } + if (app->invert) + fz_invert_pixmap(app->ctx, app->image); + } + + if (transition) + { + fz_transition *new_trans; + app->new_image = app->image; + app->image = NULL; + app->image = fz_new_pixmap_with_bbox(app->ctx, colorspace, &ibounds); + app->duration = 0; + new_trans = fz_page_presentation(app->doc, app->page, &app->duration); + if (new_trans) + app->transition = *new_trans; + else + { + /* If no transition specified, use a default one */ + memset(&app->transition, 0, sizeof(*new_trans)); + app->transition.duration = 1.0; + app->transition.type = FZ_TRANSITION_WIPE; + app->transition.vertical = 0; + app->transition.direction = 0; + } + if (app->duration == 0) + app->duration = 5; + app->in_transit = fz_generate_transition(app->image, app->old_image, app->new_image, 0, &app->transition); + if (!app->in_transit) + { + if (app->duration != 0) + winadvancetimer(app, app->duration); + } + app->start_time = clock(); + } + + if (repaint) + { + pdfapp_panview(app, app->panx, app->pany); + + if (app->shrinkwrap) + { + int w = fz_pixmap_width(app->ctx, app->image); + int h = fz_pixmap_height(app->ctx, app->image); + 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); + } + + if (cookie.errors && app->errored == 0) + { + app->errored = 1; + pdfapp_warn(app, "Errors found on page. Page rendering may be incomplete."); + } + + fz_flush_warnings(app->ctx); +} + +static void pdfapp_gotouri(pdfapp_t *app, char *uri) +{ + winopenuri(app, uri); +} + +void pdfapp_gotopage(pdfapp_t *app, int number) +{ + app->isediting = 0; + winrepaint(app); + + if (app->histlen + 1 == 256) + { + memmove(app->hist, app->hist + 1, sizeof(int) * 255); + app->histlen --; + } + app->hist[app->histlen++] = app->pageno; + app->pageno = number + 1; + pdfapp_showpage(app, 1, 1, 1, 0); +} + +void pdfapp_inverthit(pdfapp_t *app) +{ + fz_rect bbox; + fz_matrix ctm; + int i; + + pdfapp_viewctm(&ctm, app); + + for (i = 0; i < app->hit_count; i++) + { + bbox = app->hit_bbox[i]; + pdfapp_invert(app, fz_transform_rect(&bbox, &ctm)); + } +} + +static void pdfapp_search_in_direction(pdfapp_t *app, enum panning *panto, int dir) +{ + int firstpage, page; + + wincursor(app, WAIT); + + firstpage = app->pageno; + if (app->searchpage == app->pageno) + page = app->pageno + dir; + else + page = app->pageno; + + if (page < 1) page = app->pagecount; + if (page > app->pagecount) page = 1; + + do + { + if (page != app->pageno) + { + app->pageno = page; + pdfapp_showpage(app, 1, 0, 0, 0); + } + + app->hit_count = fz_search_text_page(app->ctx, app->page_text, app->search, app->hit_bbox, nelem(app->hit_bbox)); + if (app->hit_count > 0) + { + *panto = dir == 1 ? PAN_TO_TOP : PAN_TO_BOTTOM; + app->searchpage = app->pageno; + wincursor(app, HAND); + winrepaint(app); + return; + } + + page += dir; + if (page < 1) page = app->pagecount; + if (page > app->pagecount) page = 1; + } while (page != firstpage); + + pdfapp_warn(app, "String '%s' not found.", app->search); + + app->pageno = firstpage; + pdfapp_showpage(app, 1, 0, 0, 0); + wincursor(app, HAND); + winrepaint(app); +} + +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; + int loadpage = 1; + + if (app->isediting) + { + int n = strlen(app->search); + if (c < ' ') + { + if (c == '\b' && n > 0) + { + app->search[n - 1] = 0; + winrepaintsearch(app); + } + if (c == '\n' || c == '\r') + { + app->isediting = 0; + if (n > 0) + { + winrepaintsearch(app); + + if (app->searchdir < 0) + { + if (app->pageno == 1) + app->pageno = app->pagecount; + else + app->pageno--; + pdfapp_showpage(app, 1, 1, 0, 0); + } + + pdfapp_onkey(app, 'n'); + } + else + winrepaint(app); + } + if (c == '\033') + { + app->isediting = 0; + winrepaint(app); + } + } + else + { + if (n + 2 < sizeof app->search) + { + app->search[n] = c; + app->search[n + 1] = 0; + winrepaintsearch(app); + } + } + return; + } + + /* + * Save numbers typed for later + */ + + if (c >= '0' && c <= '9') + { + app->number[app->numberlen++] = c; + app->number[app->numberlen] = '\0'; + } + + switch (c) + { + + case 'q': + winclose(app); + break; + + /* + * Zoom and rotate + */ + + case '+': + case '=': + app->resolution *= ZOOMSTEP; + if (app->resolution > MAXRES) + app->resolution = MAXRES; + pdfapp_showpage(app, 0, 1, 1, 0); + break; + case '-': + app->resolution /= ZOOMSTEP; + if (app->resolution < MINRES) + app->resolution = MINRES; + pdfapp_showpage(app, 0, 1, 1, 0); + break; + + case 'W': + app->resolution *= (double) app->winw / (double) fz_pixmap_width(app->ctx, app->image); + if (app->resolution > MAXRES) + app->resolution = MAXRES; + else if (app->resolution < MINRES) + app->resolution = MINRES; + pdfapp_showpage(app, 0, 1, 1, 0); + break; + case 'H': + app->resolution *= (double) app->winh / (double) fz_pixmap_height(app->ctx, app->image); + if (app->resolution > MAXRES) + app->resolution = MAXRES; + else if (app->resolution < MINRES) + app->resolution = MINRES; + pdfapp_showpage(app, 0, 1, 1, 0); + break; + + case 'L': + app->rotate -= 90; + pdfapp_showpage(app, 0, 1, 1, 0); + break; + case 'R': + app->rotate += 90; + pdfapp_showpage(app, 0, 1, 1, 0); + break; + + case 'c': + app->grayscale ^= 1; + pdfapp_showpage(app, 0, 1, 1, 0); + break; + + case 'i': + app->invert ^= 1; + pdfapp_showpage(app, 0, 1, 1, 0); + break; + +#ifndef NDEBUG + case 'a': + app->rotate -= 15; + pdfapp_showpage(app, 0, 1, 1, 0); + break; + case 's': + app->rotate += 15; + pdfapp_showpage(app, 0, 1, 1, 0); + break; +#endif + + /* + * Pan view, but don't need to repaint image + */ + + case 'f': + app->shrinkwrap = 0; + winfullscreen(app, !app->fullscreen); + app->fullscreen = !app->fullscreen; + break; + + case 'w': + if (app->fullscreen) + { + winfullscreen(app, 0); + app->fullscreen = 0; + } + app->shrinkwrap = 1; + app->panx = app->pany = 0; + pdfapp_showpage(app, 0, 0, 1, 0); + break; + + case 'h': + app->panx += fz_pixmap_width(app->ctx, app->image) / 10; + pdfapp_showpage(app, 0, 0, 1, 0); + break; + + case 'j': + app->pany -= fz_pixmap_height(app->ctx, app->image) / 10; + pdfapp_showpage(app, 0, 0, 1, 0); + break; + + case 'k': + app->pany += fz_pixmap_height(app->ctx, app->image) / 10; + pdfapp_showpage(app, 0, 0, 1, 0); + break; + + case 'l': + app->panx -= fz_pixmap_width(app->ctx, app->image) / 10; + pdfapp_showpage(app, 0, 0, 1, 0); + break; + + /* + * Page navigation + */ + + case 'g': + case '\n': + case '\r': + if (app->numberlen > 0) + app->pageno = atoi(app->number); + else + app->pageno = 1; + break; + + case 'G': + app->pageno = app->pagecount; + break; + + case 'm': + if (app->numberlen > 0) + { + int idx = atoi(app->number); + + if (idx >= 0 && idx < nelem(app->marks)) + app->marks[idx] = app->pageno; + } + else + { + 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->numberlen > 0) + { + int idx = atoi(app->number); + + if (idx >= 0 && idx < nelem(app->marks)) + if (app->marks[idx] > 0) + app->pageno = app->marks[idx]; + } + else if (app->histlen > 0) + app->pageno = app->hist[--app->histlen]; + break; + + case 'p': + app->presentation_mode = !app->presentation_mode; + break; + + /* + * Back and forth ... + */ + + case ',': + panto = PAN_TO_BOTTOM; + if (app->numberlen > 0) + app->pageno -= atoi(app->number); + else + app->pageno--; + break; + + case '.': + 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 ' ': + panto = DONT_PAN; + if (app->numberlen > 0) + app->pageno += atoi(app->number); + else + app->pageno++; + break; + + case '<': + panto = PAN_TO_TOP; + app->pageno -= 10; + break; + case '>': + panto = PAN_TO_TOP; + app->pageno += 10; + break; + + /* + * Saving the file + */ + case 'S': + pdfapp_save(app); + break; + + /* + * Reloading the file... + */ + + case 'r': + panto = DONT_PAN; + oldpage = -1; + winreloadfile(app); + break; + + /* + * Searching + */ + + case '?': + app->isediting = 1; + app->searchdir = -1; + app->search[0] = 0; + app->hit_count = 0; + app->searchpage = -1; + winrepaintsearch(app); + break; + + case '/': + app->isediting = 1; + app->searchdir = 1; + app->search[0] = 0; + app->hit_count = 0; + app->searchpage = -1; + winrepaintsearch(app); + break; + + case 'n': + if (app->searchdir > 0) + pdfapp_search_in_direction(app, &panto, 1); + else + pdfapp_search_in_direction(app, &panto, -1); + loadpage = 0; + break; + + case 'N': + if (app->searchdir > 0) + pdfapp_search_in_direction(app, &panto, -1); + else + pdfapp_search_in_direction(app, &panto, 1); + loadpage = 0; + 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, loadpage, 1, 1, 1); + } +} + +void pdfapp_onmouse(pdfapp_t *app, int x, int y, int btn, int modifiers, int state) +{ + fz_context *ctx = app->ctx; + fz_irect irect; + fz_link *link; + fz_matrix ctm; + fz_point p; + int processed = 0; + + fz_pixmap_bbox(app->ctx, app->image, &irect); + p.x = x - app->panx + irect.x0; + p.y = y - app->pany + irect.y0; + + pdfapp_viewctm(&ctm, app); + fz_invert_matrix(&ctm, &ctm); + + fz_transform_point(&p, &ctm); + + if (btn == 1 && (state == 1 || state == -1)) + { + pdf_ui_event event; + pdf_document *idoc = pdf_specifics(app->doc); + + event.etype = PDF_EVENT_TYPE_POINTER; + event.event.pointer.pt = p; + if (state == 1) + event.event.pointer.ptype = PDF_POINTER_DOWN; + else /* state == -1 */ + event.event.pointer.ptype = PDF_POINTER_UP; + + if (idoc && pdf_pass_event(idoc, (pdf_page *)app->page, &event)) + { + pdf_widget *widget; + + widget = pdf_focused_widget(idoc); + + app->nowaitcursor = 1; + pdfapp_updatepage(app); + + if (widget) + { + switch (pdf_widget_get_type(widget)) + { + case PDF_WIDGET_TYPE_TEXT: + { + char *text = pdf_text_widget_text(idoc, widget); + char *current_text = text; + int retry = 0; + + do + { + current_text = wintextinput(app, current_text, retry); + retry = 1; + } + while (current_text && !pdf_text_widget_set_text(idoc, widget, current_text)); + + fz_free(app->ctx, text); + pdfapp_updatepage(app); + } + break; + + case PDF_WIDGET_TYPE_LISTBOX: + case PDF_WIDGET_TYPE_COMBOBOX: + { + int nopts; + int nvals; + char **opts = NULL; + char **vals = NULL; + + fz_var(opts); + fz_var(vals); + + fz_try(ctx) + { + nopts = pdf_choice_widget_options(idoc, widget, NULL); + opts = fz_malloc(ctx, nopts * sizeof(*opts)); + (void)pdf_choice_widget_options(idoc, widget, opts); + + nvals = pdf_choice_widget_value(idoc, widget, NULL); + vals = fz_malloc(ctx, MAX(nvals,nopts) * sizeof(*vals)); + (void)pdf_choice_widget_value(idoc, widget, vals); + + if (winchoiceinput(app, nopts, opts, &nvals, vals)) + { + pdf_choice_widget_set_value(idoc, widget, nvals, vals); + pdfapp_updatepage(app); + } + } + fz_always(ctx) + { + fz_free(ctx, opts); + fz_free(ctx, vals); + } + fz_catch(ctx) + { + pdfapp_warn(app, "setting of choice failed"); + } + } + break; + + case PDF_WIDGET_TYPE_SIGNATURE: + { + char ebuf[256]; + + ebuf[0] = 0; + if (pdf_check_signature(ctx, idoc, widget, app->docpath, ebuf, sizeof(ebuf))) + { + winwarn(app, "Signature is valid"); + } + else + { + if (ebuf[0] == 0) + winwarn(app, "Signature check failed for unknown reason"); + else + winwarn(app, ebuf); + } + } + break; + } + } + + app->nowaitcursor = 0; + processed = 1; + } + } + + 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 && !processed) + { + if (link->dest.kind == FZ_LINK_URI) + pdfapp_gotouri(app, link->dest.ld.uri.uri); + else if (link->dest.kind == FZ_LINK_GOTO) + pdfapp_gotopage(app, link->dest.ld.gotor.page); + return; + } + } + else + { + fz_annot *annot; + for (annot = fz_first_annot(app->doc, app->page); annot; annot = fz_next_annot(app->doc, annot)) + { + fz_rect rect; + fz_bound_annot(app->doc, annot, &rect); + if (x >= rect.x0 && x < rect.x1) + if (y >= rect.y0 && y < rect.y1) + break; + } + if (annot) + wincursor(app, CARET); + else + wincursor(app, ARROW); + } + + if (state == 1 && !processed) + { + if (btn == 1 && !app->iscopying) + { + app->ispanning = 1; + app->selx = x; + app->sely = y; + app->beyondy = 0; + } + 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 */ + if (dir > 0) + app->resolution *= ZOOMSTEP; + else + app->resolution /= ZOOMSTEP; + if (app->resolution > MAXRES) + app->resolution = MAXRES; + if (app->resolution < MINRES) + app->resolution = MINRES; + pdfapp_showpage(app, 0, 1, 1, 0); + } + 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 = fz_mini(app->selx, x) - app->panx + irect.x0; + app->selr.x1 = fz_maxi(app->selx, x) - app->panx + irect.x0; + app->selr.y0 = fz_mini(app->sely, y) - app->pany + irect.y0; + app->selr.y1 = fz_maxi(app->sely, y) - app->pany + irect.y0; + winrepaint(app); + if (app->selr.x0 < app->selr.x1 && app->selr.y0 < app->selr.y1) + windocopy(app); + } + app->ispanning = 0; + } + + else if (app->ispanning) + { + int newx = app->panx + x - app->selx; + int newy = app->pany + y - app->sely; + /* Scrolling beyond limits implies flipping pages */ + /* Are we requested to scroll beyond limits? */ + if (newy + fz_pixmap_height(app->ctx, app->image) < app->winh || newy > 0) + { + /* Yes. We can assume that deltay != 0 */ + int deltay = y - app->sely; + /* Check whether the panning has occurred in the + * direction that we are already crossing the + * limit it. If not, we can conclude that we + * have switched ends of the page and will thus + * start over counting. + */ + if( app->beyondy == 0 || (app->beyondy ^ deltay) >= 0 ) + { + /* Updating how far we are beyond and + * flipping pages if beyond threshold + */ + app->beyondy += deltay; + if (app->beyondy > BEYOND_THRESHHOLD) + { + if( app->pageno > 1 ) + { + app->pageno--; + pdfapp_showpage(app, 1, 1, 1, 0); + newy = -fz_pixmap_height(app->ctx, app->image); + } + app->beyondy = 0; + } + else if (app->beyondy < -BEYOND_THRESHHOLD) + { + if( app->pageno < app->pagecount ) + { + app->pageno++; + pdfapp_showpage(app, 1, 1, 1, 0); + newy = 0; + } + app->beyondy = 0; + } + } + else + app->beyondy = 0; + } + /* Although at this point we've already determined that + * or that no scrolling will be performed in + * y-direction, the x-direction has not yet been taken + * care off. Therefore + */ + pdfapp_panview(app, newx, newy); + + app->selx = x; + app->sely = y; + } + + else if (app->iscopying) + { + app->selr.x0 = fz_mini(app->selx, x) - app->panx + irect.x0; + app->selr.x1 = fz_maxi(app->selx, x) - app->panx + irect.x0; + app->selr.y0 = fz_mini(app->sely, y) - app->pany + irect.y0; + app->selr.y1 = fz_maxi(app->sely, y) - app->pany + irect.y0; + winrepaint(app); + } + +} + +void pdfapp_oncopy(pdfapp_t *app, unsigned short *ucsbuf, int ucslen) +{ + fz_rect hitbox; + fz_matrix ctm; + fz_text_page *page = app->page_text; + int c, i, p; + int seen = 0; + int block_num; + + int x0 = app->selr.x0; + int x1 = app->selr.x1; + int y0 = app->selr.y0; + int y1 = app->selr.y1; + + pdfapp_viewctm(&ctm, app); + + p = 0; + + for (block_num = 0; block_num < page->len; block_num++) + { + fz_text_line *line; + fz_text_block *block; + fz_text_span *span; + + if (page->blocks[block_num].type != FZ_PAGE_BLOCK_TEXT) + continue; + block = page->blocks[block_num].u.text; + + for (line = block->lines; line < block->lines + block->len; line++) + { + for (span = line->first_span; span; span = span->next) + { + if (seen) + { +#ifdef _WIN32 + if (p < ucslen - 1) + ucsbuf[p++] = '\r'; +#endif + if (p < ucslen - 1) + ucsbuf[p++] = '\n'; + } + + seen = 0; + + for (i = 0; i < span->len; i++) + { + fz_text_char_bbox(&hitbox, span, i); + fz_transform_rect(&hitbox, &ctm); + c = span->text[i].c; + if (c < 32) + c = '?'; + if (hitbox.x1 >= x0 && hitbox.x0 <= x1 && hitbox.y1 >= y0 && hitbox.y0 <= y1) + { + if (p < ucslen - 1) + ucsbuf[p++] = c; + seen = 1; + } + } + + seen = (seen && span == line->last_span); + } + } + } + + ucsbuf[p] = 0; +} + +void pdfapp_postblit(pdfapp_t *app) +{ + clock_t time; + float seconds; + int llama; + + app->transitions_enabled = 1; + if (!app->in_transit) + return; + time = clock(); + seconds = (float)(time - app->start_time) / CLOCKS_PER_SEC; + llama = seconds * 256 / app->transition.duration; + if (llama >= 256) + { + /* Completed. */ + fz_drop_pixmap(app->ctx, app->image); + app->image = app->new_image; + app->new_image = NULL; + fz_drop_pixmap(app->ctx, app->old_image); + app->old_image = NULL; + if (app->duration != 0) + winadvancetimer(app, app->duration); + } + else + fz_generate_transition(app->image, app->old_image, app->new_image, llama, &app->transition); + winrepaint(app); + if (llama >= 256) + { + /* Completed. */ + app->in_transit = 0; + } +} diff --git a/platform/x11/pdfapp.h b/platform/x11/pdfapp.h new file mode 100644 index 00000000..3e40e0c4 --- /dev/null +++ b/platform/x11/pdfapp.h @@ -0,0 +1,149 @@ +#ifndef PDFAPP_H +#define PDFAPP_H + +#include "mupdf/fitz.h" +#include "mupdf/pdf.h" + +/* + * Utility object for handling a pdf application / view + * Takes care of PDF loading and displaying and navigation, + * uses a number of callbacks to the GUI app. + */ + +#define MINRES 54 +#define MAXRES 300 + +typedef struct pdfapp_s pdfapp_t; + +enum { ARROW, HAND, WAIT, CARET }; + +enum { DISCARD, SAVE, CANCEL }; + +extern void winwarn(pdfapp_t*, char *s); +extern void winerror(pdfapp_t*, char *s); +extern void wintitle(pdfapp_t*, char *title); +extern void winresize(pdfapp_t*, int w, int h); +extern void winrepaint(pdfapp_t*); +extern void winrepaintsearch(pdfapp_t*); +extern char *winpassword(pdfapp_t*, char *filename); +extern char *wintextinput(pdfapp_t*, char *inittext, int retry); +extern int winchoiceinput(pdfapp_t*, int nopts, char *opts[], int *nvals, char *vals[]); +extern void winopenuri(pdfapp_t*, char *s); +extern void wincursor(pdfapp_t*, int curs); +extern void windocopy(pdfapp_t*); +extern void winreloadfile(pdfapp_t*); +extern void windrawstring(pdfapp_t*, int x, int y, char *s); +extern void winclose(pdfapp_t*); +extern void winhelp(pdfapp_t*); +extern void winfullscreen(pdfapp_t*, int state); +extern int winsavequery(pdfapp_t*); +extern int wingetsavepath(pdfapp_t*, char *buf, int len); +extern void winalert(pdfapp_t *, pdf_alert_event *alert); +extern void winprint(pdfapp_t *); +extern void winadvancetimer(pdfapp_t *, float duration); +extern void winreplacefile(char *source, char *target); + +struct pdfapp_s +{ + /* current document params */ + fz_document *doc; + char *docpath; + char *doctitle; + fz_outline *outline; + + int pagecount; + + /* current view params */ + int resolution; + int rotate; + fz_pixmap *image; + int grayscale; + fz_colorspace *colorspace; + int invert; + + /* presentation mode */ + int presentation_mode; + int transitions_enabled; + fz_pixmap *old_image; + fz_pixmap *new_image; + clock_t start_time; + int in_transit; + float duration; + fz_transition transition; + + /* current page params */ + int pageno; + fz_page *page; + fz_rect page_bbox; + fz_display_list *page_list; + fz_display_list *annotations_list; + fz_text_page *page_text; + fz_text_sheet *page_sheet; + fz_link *page_links; + int errored; + + /* snapback history */ + int hist[256]; + int histlen; + int marks[10]; + + /* window system sizes */ + int winw, winh; + int scrw, scrh; + int shrinkwrap; + int fullscreen; + + /* event handling state */ + char number[256]; + int numberlen; + + int ispanning; + int panx, pany; + + int iscopying; + int selx, sely; + /* TODO - While sely keeps track of the relative change in + * cursor position between two ticks/events, beyondy shall keep + * track of the relative change in cursor position from the + * point where the user hits a scrolling limit. This is ugly. + * Used in pdfapp.c:pdfapp_onmouse. + */ + int beyondy; + fz_rect selr; + + int nowaitcursor; + + /* search state */ + int isediting; + int searchdir; + char search[512]; + int searchpage; + fz_rect hit_bbox[512]; + int hit_count; + + /* client context storage */ + void *userdata; + + fz_context *ctx; +}; + +void pdfapp_init(fz_context *ctx, pdfapp_t *app); +void pdfapp_open(pdfapp_t *app, char *filename, int reload); +void pdfapp_close(pdfapp_t *app); +int pdfapp_preclose(pdfapp_t *app); + +char *pdfapp_version(pdfapp_t *app); +char *pdfapp_usage(pdfapp_t *app); + +void pdfapp_onkey(pdfapp_t *app, int c); +void pdfapp_onmouse(pdfapp_t *app, int x, int y, int btn, int modifiers, int state); +void pdfapp_oncopy(pdfapp_t *app, unsigned short *ucsbuf, int ucslen); +void pdfapp_onresize(pdfapp_t *app, int w, int h); +void pdfapp_gotopage(pdfapp_t *app, int number); + +void pdfapp_invert(pdfapp_t *app, const fz_rect *rect); +void pdfapp_inverthit(pdfapp_t *app); + +void pdfapp_postblit(pdfapp_t *app); + +#endif diff --git a/platform/x11/win_main.c b/platform/x11/win_main.c new file mode 100644 index 00000000..bf765d6b --- /dev/null +++ b/platform/x11/win_main.c @@ -0,0 +1,1163 @@ +#include "pdfapp.h" + +#ifndef UNICODE +#define UNICODE +#endif +#ifndef _UNICODE +#define _UNICODE +#endif +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#include <commdlg.h> +#include <shellapi.h> + +#ifndef WM_MOUSEWHEEL +#define WM_MOUSEWHEEL 0x020A +#endif + +#define MIN(x,y) ((x) < (y) ? (x) : (y)) + +#define ID_ABOUT 0x1000 +#define ID_DOCINFO 0x1001 + +static HWND hwndframe = NULL; +static HWND hwndview = NULL; +static HDC hdc; +static HBRUSH bgbrush; +static HBRUSH shbrush; +static BITMAPINFO *dibinf; +static HCURSOR arrowcurs, handcurs, waitcurs, caretcurs; +static LRESULT CALLBACK frameproc(HWND, UINT, WPARAM, LPARAM); +static LRESULT CALLBACK viewproc(HWND, UINT, WPARAM, LPARAM); +static int timer_pending = 0; + +static int justcopied = 0; + +static pdfapp_t gapp; + +#define PATH_MAX (1024) + +static wchar_t wbuf[PATH_MAX]; +static char filename[PATH_MAX]; + +/* + * Create registry keys to associate MuPDF with PDF and XPS files. + */ + +#define OPEN_KEY(parent, name, ptr) \ + RegCreateKeyExA(parent, name, 0, 0, 0, KEY_WRITE, 0, &ptr, 0) + +#define SET_KEY(parent, name, value) \ + RegSetValueExA(parent, name, 0, REG_SZ, (const BYTE *)(value), strlen(value) + 1) + +void install_app(char *argv0) +{ + char buf[512]; + HKEY software, classes, mupdf, dotpdf, dotxps; + HKEY shell, open, command, supported_types; + HKEY pdf_progids, xps_progids; + + OPEN_KEY(HKEY_CURRENT_USER, "Software", software); + OPEN_KEY(software, "Classes", classes); + OPEN_KEY(classes, ".pdf", dotpdf); + OPEN_KEY(dotpdf, "OpenWithProgids", pdf_progids); + OPEN_KEY(classes, ".xps", dotxps); + OPEN_KEY(dotxps, "OpenWithProgids", xps_progids); + OPEN_KEY(classes, "MuPDF", mupdf); + OPEN_KEY(mupdf, "SupportedTypes", supported_types); + OPEN_KEY(mupdf, "shell", shell); + OPEN_KEY(shell, "open", open); + OPEN_KEY(open, "command", command); + + sprintf(buf, "\"%s\" \"%%1\"", argv0); + + SET_KEY(open, "FriendlyAppName", "MuPDF"); + SET_KEY(command, "", buf); + SET_KEY(supported_types, ".pdf", ""); + SET_KEY(supported_types, ".xps", ""); + SET_KEY(pdf_progids, "MuPDF", ""); + SET_KEY(xps_progids, "MuPDF", ""); + + RegCloseKey(dotxps); + RegCloseKey(dotpdf); + RegCloseKey(mupdf); + RegCloseKey(classes); + RegCloseKey(software); +} + +/* + * Dialog boxes + */ + +void winwarn(pdfapp_t *app, char *msg) +{ + MessageBoxA(hwndframe, msg, "MuPDF: Warning", MB_ICONWARNING); +} + +void winerror(pdfapp_t *app, char *msg) +{ + MessageBoxA(hwndframe, msg, "MuPDF: Error", MB_ICONERROR); + exit(1); +} + +void winalert(pdfapp_t *app, pdf_alert_event *alert) +{ + int buttons = MB_OK; + int icon = MB_ICONWARNING; + int pressed = PDF_ALERT_BUTTON_NONE; + + switch (alert->icon_type) + { + case PDF_ALERT_ICON_ERROR: + icon = MB_ICONERROR; + break; + case PDF_ALERT_ICON_WARNING: + icon = MB_ICONWARNING; + break; + case PDF_ALERT_ICON_QUESTION: + icon = MB_ICONQUESTION; + break; + case PDF_ALERT_ICON_STATUS: + icon = MB_ICONINFORMATION; + break; + } + + switch (alert->button_group_type) + { + case PDF_ALERT_BUTTON_GROUP_OK: + buttons = MB_OK; + break; + case PDF_ALERT_BUTTON_GROUP_OK_CANCEL: + buttons = MB_OKCANCEL; + break; + case PDF_ALERT_BUTTON_GROUP_YES_NO: + buttons = MB_YESNO; + break; + case PDF_ALERT_BUTTON_GROUP_YES_NO_CANCEL: + buttons = MB_YESNOCANCEL; + break; + } + + pressed = MessageBoxA(hwndframe, alert->message, alert->title, icon|buttons); + + switch (pressed) + { + case IDOK: + alert->button_pressed = PDF_ALERT_BUTTON_OK; + break; + case IDCANCEL: + alert->button_pressed = PDF_ALERT_BUTTON_CANCEL; + break; + case IDNO: + alert->button_pressed = PDF_ALERT_BUTTON_NO; + break; + case IDYES: + alert->button_pressed = PDF_ALERT_BUTTON_YES; + } +} + +void winprint(pdfapp_t *app) +{ + MessageBoxA(hwndframe, "The MuPDF library supports printing, but this application currently does not", "Print document", MB_ICONWARNING); +} + +int winsavequery(pdfapp_t *app) +{ + switch(MessageBoxA(hwndframe, "File has unsaved changes. Do you want to save", "MuPDF", MB_YESNOCANCEL)) + { + case IDYES: return SAVE; + case IDNO: return DISCARD; + default: return CANCEL; + } +} + +int winfilename(wchar_t *buf, int len) +{ + OPENFILENAME ofn; + buf[0] = 0; + memset(&ofn, 0, sizeof(OPENFILENAME)); + ofn.lStructSize = sizeof(OPENFILENAME); + ofn.hwndOwner = hwndframe; + ofn.lpstrFile = buf; + ofn.nMaxFile = len; + ofn.lpstrInitialDir = NULL; + ofn.lpstrTitle = L"MuPDF: Open PDF file"; + ofn.lpstrFilter = L"Documents (*.pdf;*.xps;*.cbz;*.zip;*.png;*.jpg;*.tif)\0*.zip;*.cbz;*.xps;*.pdf;*.jpe;*.jpg;*.jpeg;*.jfif;*.tif;*.tiff\0PDF Files (*.pdf)\0*.pdf\0XPS Files (*.xps)\0*.xps\0CBZ Files (*.cbz;*.zip)\0*.zip;*.cbz\0Image Files (*.png;*.jpe;*.tif)\0*.png;*.jpg;*.jpe;*.jpeg;*.jfif;*.tif;*.tiff\0All Files\0*\0\0"; + ofn.Flags = OFN_FILEMUSTEXIST|OFN_HIDEREADONLY; + return GetOpenFileNameW(&ofn); +} + +int wingetsavepath(pdfapp_t *app, char *buf, int len) +{ + wchar_t twbuf[PATH_MAX]; + OPENFILENAME ofn; + + wcscpy(twbuf, wbuf); + memset(&ofn, 0, sizeof(OPENFILENAME)); + ofn.lStructSize = sizeof(OPENFILENAME); + ofn.hwndOwner = hwndframe; + ofn.lpstrFile = twbuf; + ofn.nMaxFile = PATH_MAX; + ofn.lpstrInitialDir = NULL; + ofn.lpstrTitle = L"MuPDF: Save PDF file"; + ofn.lpstrFilter = L"Documents (*.pdf;*.xps;*.cbz;*.zip;*.png;*.jpg;*.tif)\0*.zip;*.cbz;*.xps;*.pdf;*.jpe;*.jpg;*.jpeg;*.jfif;*.tif;*.tiff\0PDF Files (*.pdf)\0*.pdf\0XPS Files (*.xps)\0*.xps\0CBZ Files (*.cbz;*.zip)\0*.zip;*.cbz\0Image Files (*.png;*.jpe;*.tif)\0*.png;*.jpg;*.jpe;*.jpeg;*.jfif;*.tif;*.tiff\0All Files\0*\0\0"; + ofn.Flags = OFN_HIDEREADONLY; + if (GetSaveFileName(&ofn)) + { + int code = WideCharToMultiByte(CP_UTF8, 0, twbuf, -1, buf, MIN(PATH_MAX, len), NULL, NULL); + if (code == 0) + { + winerror(&gapp, "cannot convert filename to utf-8"); + return 0; + } + + wcscpy(wbuf, twbuf); + strcpy(filename, buf); + return 1; + } + else + { + return 0; + } +} + +void winreplacefile(char *source, char *target) +{ + wchar_t wsource[PATH_MAX]; + wchar_t wtarget[PATH_MAX]; + + int sz = MultiByteToWideChar(CP_UTF8, 0, source, -1, wsource, PATH_MAX); + if (sz == 0) + { + winerror(&gapp, "cannot convert filename to Unicode"); + return; + } + + sz = MultiByteToWideChar(CP_UTF8, 0, target, -1, wtarget, PATH_MAX); + if (sz == 0) + { + winerror(&gapp, "cannot convert filename to Unicode"); + return; + } + +#if (_WIN32_WINNT >= 0x0500) + ReplaceFile(wtarget, wsource, NULL, REPLACEFILE_IGNORE_MERGE_ERRORS, NULL, NULL); +#else + DeleteFile(wtarget); + MoveFile(wsource, wtarget); +#endif +} + +static char pd_filename[256] = "The file is encrypted."; +static char pd_password[256] = ""; +static wchar_t pd_passwordw[256] = {0}; +static char td_textinput[1024] = ""; +static int td_retry = 0; +static int cd_nopts; +static int *cd_nvals; +static char **cd_opts; +static char **cd_vals; +static int pd_okay = 0; + +INT CALLBACK +dlogpassproc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch(message) + { + case WM_INITDIALOG: + SetDlgItemTextA(hwnd, 4, pd_filename); + return TRUE; + case WM_COMMAND: + switch(wParam) + { + case 1: + pd_okay = 1; + GetDlgItemTextW(hwnd, 3, pd_passwordw, nelem(pd_passwordw)); + EndDialog(hwnd, 1); + WideCharToMultiByte(CP_UTF8, 0, pd_passwordw, -1, pd_password, sizeof pd_password, NULL, NULL); + return TRUE; + case 2: + pd_okay = 0; + EndDialog(hwnd, 1); + return TRUE; + } + break; + } + return FALSE; +} + +INT CALLBACK +dlogtextproc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch(message) + { + case WM_INITDIALOG: + SetDlgItemTextA(hwnd, 3, td_textinput); + if (!td_retry) + ShowWindow(GetDlgItem(hwnd, 4), SW_HIDE); + return TRUE; + case WM_COMMAND: + switch(wParam) + { + case 1: + pd_okay = 1; + GetDlgItemTextA(hwnd, 3, td_textinput, sizeof td_textinput); + EndDialog(hwnd, 1); + return TRUE; + case 2: + pd_okay = 0; + EndDialog(hwnd, 1); + return TRUE; + } + break; + case WM_CTLCOLORSTATIC: + if ((HWND)lParam == GetDlgItem(hwnd, 4)) + { + SetTextColor((HDC)wParam, RGB(255,0,0)); + SetBkMode((HDC)wParam, TRANSPARENT); + + return (INT)GetStockObject(NULL_BRUSH); + } + break; + } + return FALSE; +} + +INT CALLBACK +dlogchoiceproc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + HWND listbox; + int i; + int item; + int sel; + switch(message) + { + case WM_INITDIALOG: + listbox = GetDlgItem(hwnd, 3); + for (i = 0; i < cd_nopts; i++) + SendMessageA(listbox, LB_ADDSTRING, 0, (LPARAM)cd_opts[i]); + + /* FIXME: handle multiple select */ + if (*cd_nvals > 0) + { + item = SendMessageA(listbox, LB_FINDSTRINGEXACT, (WPARAM)-1, (LPARAM)cd_vals[0]); + if (item != LB_ERR) + SendMessageA(listbox, LB_SETCURSEL, item, 0); + } + return TRUE; + case WM_COMMAND: + switch(wParam) + { + case 1: + listbox = GetDlgItem(hwnd, 3); + *cd_nvals = 0; + for (i = 0; i < cd_nopts; i++) + { + item = SendMessageA(listbox, LB_FINDSTRINGEXACT, (WPARAM)-1, (LPARAM)cd_opts[i]); + sel = SendMessageA(listbox, LB_GETSEL, item, 0); + if (sel && sel != LB_ERR) + cd_vals[(*cd_nvals)++] = cd_opts[i]; + } + pd_okay = 1; + EndDialog(hwnd, 1); + return TRUE; + case 2: + pd_okay = 0; + EndDialog(hwnd, 1); + return TRUE; + } + break; + } + return FALSE; +} + +char *winpassword(pdfapp_t *app, char *filename) +{ + char buf[1024], *s; + int code; + strcpy(buf, filename); + s = buf; + if (strrchr(s, '\\')) s = strrchr(s, '\\') + 1; + if (strrchr(s, '/')) s = strrchr(s, '/') + 1; + if (strlen(s) > 32) + strcpy(s + 30, "..."); + sprintf(pd_filename, "The file \"%s\" is encrypted.", s); + code = DialogBoxW(NULL, L"IDD_DLOGPASS", hwndframe, dlogpassproc); + if (code <= 0) + winerror(app, "cannot create password dialog"); + if (pd_okay) + return pd_password; + return NULL; +} + +char *wintextinput(pdfapp_t *app, char *inittext, int retry) +{ + int code; + td_retry = retry; + fz_strlcpy(td_textinput, inittext ? inittext : "", sizeof td_textinput); + code = DialogBoxW(NULL, L"IDD_DLOGTEXT", hwndframe, dlogtextproc); + if (code <= 0) + winerror(app, "cannot create text input dialog"); + if (pd_okay) + return td_textinput; + return NULL; +} + +int winchoiceinput(pdfapp_t *app, int nopts, char *opts[], int *nvals, char *vals[]) +{ + int code; + cd_nopts = nopts; + cd_nvals = nvals; + cd_opts = opts; + cd_vals = vals; + code = DialogBoxW(NULL, L"IDD_DLOGLIST", hwndframe, dlogchoiceproc); + if (code <= 0) + winerror(app, "cannot create text input dialog"); + return pd_okay; +} + +INT CALLBACK +dloginfoproc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + char buf[256]; + fz_document *doc = gapp.doc; + + switch(message) + { + case WM_INITDIALOG: + + SetDlgItemTextW(hwnd, 0x10, wbuf); + + if (fz_meta(doc, FZ_META_FORMAT_INFO, buf, 256) < 0) + { + SetDlgItemTextA(hwnd, 0x11, "Unknown"); + SetDlgItemTextA(hwnd, 0x12, "None"); + SetDlgItemTextA(hwnd, 0x13, "n/a"); + return TRUE; + } + + SetDlgItemTextA(hwnd, 0x11, buf); + + if (fz_meta(doc, FZ_META_CRYPT_INFO, buf, 256) == 0) + { + SetDlgItemTextA(hwnd, 0x12, buf); + } + else + { + SetDlgItemTextA(hwnd, 0x12, "None"); + } + buf[0] = 0; + if (fz_meta(doc, FZ_META_HAS_PERMISSION, NULL, FZ_PERMISSION_PRINT) == 0) + strcat(buf, "print, "); + if (fz_meta(doc, FZ_META_HAS_PERMISSION, NULL, FZ_PERMISSION_CHANGE) == 0) + strcat(buf, "modify, "); + if (fz_meta(doc, FZ_META_HAS_PERMISSION, NULL, FZ_PERMISSION_COPY) == 0) + strcat(buf, "copy, "); + if (fz_meta(doc, FZ_META_HAS_PERMISSION, NULL, FZ_PERMISSION_NOTES) == 0) + strcat(buf, "annotate, "); + if (strlen(buf) > 2) + buf[strlen(buf)-2] = 0; + else + strcpy(buf, "None"); + SetDlgItemTextA(hwnd, 0x13, buf); + +#define SETUTF8(ID, STRING) \ + { \ + *(char **)buf = STRING; \ + if (fz_meta(doc, FZ_META_INFO, buf, 256) <= 0) \ + buf[0] = 0; \ + SetDlgItemTextA(hwnd, ID, buf); \ + } + + SETUTF8(0x20, "Title"); + SETUTF8(0x21, "Author"); + SETUTF8(0x22, "Subject"); + SETUTF8(0x23, "Keywords"); + SETUTF8(0x24, "Creator"); + SETUTF8(0x25, "Producer"); + SETUTF8(0x26, "CreationDate"); + SETUTF8(0x27, "ModDate"); + return TRUE; + + case WM_COMMAND: + EndDialog(hwnd, 1); + return TRUE; + } + return FALSE; +} + +void info() +{ + int code = DialogBoxW(NULL, L"IDD_DLOGINFO", hwndframe, dloginfoproc); + if (code <= 0) + winerror(&gapp, "cannot create info dialog"); +} + +INT CALLBACK +dlogaboutproc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch(message) + { + case WM_INITDIALOG: + SetDlgItemTextA(hwnd, 2, pdfapp_version(&gapp)); + SetDlgItemTextA(hwnd, 3, pdfapp_usage(&gapp)); + return TRUE; + case WM_COMMAND: + EndDialog(hwnd, 1); + return TRUE; + } + return FALSE; +} + +void winhelp(pdfapp_t*app) +{ + int code = DialogBoxW(NULL, L"IDD_DLOGABOUT", hwndframe, dlogaboutproc); + if (code <= 0) + winerror(&gapp, "cannot create help dialog"); +} + +/* + * Main window + */ + +void winopen() +{ + WNDCLASS wc; + HMENU menu; + RECT r; + ATOM a; + + /* Create and register window frame class */ + memset(&wc, 0, sizeof(wc)); + wc.style = 0; + wc.lpfnWndProc = frameproc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = GetModuleHandle(NULL); + wc.hIcon = LoadIconA(wc.hInstance, "IDI_ICONAPP"); + wc.hCursor = NULL; //LoadCursor(NULL, IDC_ARROW); + wc.hbrBackground = NULL; + wc.lpszMenuName = NULL; + wc.lpszClassName = L"FrameWindow"; + a = RegisterClassW(&wc); + if (!a) + winerror(&gapp, "cannot register frame window class"); + + /* Create and register window view class */ + memset(&wc, 0, sizeof(wc)); + wc.style = CS_HREDRAW | CS_VREDRAW; + wc.lpfnWndProc = viewproc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = GetModuleHandle(NULL); + wc.hIcon = NULL; + wc.hCursor = NULL; + wc.hbrBackground = NULL; + wc.lpszMenuName = NULL; + wc.lpszClassName = L"ViewWindow"; + a = RegisterClassW(&wc); + if (!a) + winerror(&gapp, "cannot register view window class"); + + /* Get screen size */ + SystemParametersInfo(SPI_GETWORKAREA, 0, &r, 0); + gapp.scrw = r.right - r.left; + gapp.scrh = r.bottom - r.top; + + /* Create cursors */ + arrowcurs = LoadCursor(NULL, IDC_ARROW); + handcurs = LoadCursor(NULL, IDC_HAND); + waitcurs = LoadCursor(NULL, IDC_WAIT); + caretcurs = LoadCursor(NULL, IDC_IBEAM); + + /* And a background color */ + bgbrush = CreateSolidBrush(RGB(0x70,0x70,0x70)); + shbrush = CreateSolidBrush(RGB(0x40,0x40,0x40)); + + /* Init DIB info for buffer */ + dibinf = malloc(sizeof(BITMAPINFO) + 12); + assert(dibinf); + dibinf->bmiHeader.biSize = sizeof(dibinf->bmiHeader); + dibinf->bmiHeader.biPlanes = 1; + dibinf->bmiHeader.biBitCount = 32; + dibinf->bmiHeader.biCompression = BI_RGB; + dibinf->bmiHeader.biXPelsPerMeter = 2834; + dibinf->bmiHeader.biYPelsPerMeter = 2834; + dibinf->bmiHeader.biClrUsed = 0; + dibinf->bmiHeader.biClrImportant = 0; + dibinf->bmiHeader.biClrUsed = 0; + + /* Create window */ + hwndframe = CreateWindowW(L"FrameWindow", // window class name + NULL, // window caption + WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, + CW_USEDEFAULT, CW_USEDEFAULT, // initial position + 300, // initial x size + 300, // initial y size + 0, // parent window handle + 0, // window menu handle + 0, // program instance handle + 0); // creation parameters + if (!hwndframe) + winerror(&gapp, "cannot create frame"); + + hwndview = CreateWindowW(L"ViewWindow", // window class name + NULL, + WS_VISIBLE | WS_CHILD, + CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, + hwndframe, 0, 0, 0); + if (!hwndview) + winerror(&gapp, "cannot create view"); + + hdc = NULL; + + SetWindowTextW(hwndframe, L"MuPDF"); + + menu = GetSystemMenu(hwndframe, 0); + AppendMenuW(menu, MF_SEPARATOR, 0, NULL); + AppendMenuW(menu, MF_STRING, ID_ABOUT, L"About MuPDF..."); + AppendMenuW(menu, MF_STRING, ID_DOCINFO, L"Document Properties..."); + + SetCursor(arrowcurs); +} + +void winclose(pdfapp_t *app) +{ + if (pdfapp_preclose(app)) + { + pdfapp_close(app); + exit(0); + } +} + +void wincursor(pdfapp_t *app, int curs) +{ + if (curs == ARROW) + SetCursor(arrowcurs); + if (curs == HAND) + SetCursor(handcurs); + if (curs == WAIT) + SetCursor(waitcurs); + if (curs == CARET) + SetCursor(caretcurs); +} + +void wintitle(pdfapp_t *app, char *title) +{ + wchar_t wide[256], *dp; + char *sp; + int rune; + + dp = wide; + sp = title; + while (*sp && dp < wide + 255) + { + sp += fz_chartorune(&rune, sp); + *dp++ = rune; + } + *dp = 0; + + SetWindowTextW(hwndframe, wide); +} + +void windrawrect(pdfapp_t *app, int x0, int y0, int x1, int y1) +{ + RECT r; + r.left = x0; + r.top = y0; + r.right = x1; + r.bottom = y1; + FillRect(hdc, &r, (HBRUSH)GetStockObject(WHITE_BRUSH)); +} + +void windrawstring(pdfapp_t *app, int x, int y, char *s) +{ + HFONT font = (HFONT)GetStockObject(ANSI_FIXED_FONT); + SelectObject(hdc, font); + TextOutA(hdc, x, y - 12, s, strlen(s)); +} + +void winblitsearch() +{ + if (gapp.isediting) + { + char buf[sizeof(gapp.search) + 50]; + sprintf(buf, "Search: %s", gapp.search); + windrawrect(&gapp, 0, 0, gapp.winw, 30); + windrawstring(&gapp, 10, 20, buf); + } +} + +void winblit() +{ + int image_w = fz_pixmap_width(gapp.ctx, gapp.image); + int image_h = fz_pixmap_height(gapp.ctx, gapp.image); + int image_n = fz_pixmap_components(gapp.ctx, gapp.image); + unsigned char *samples = fz_pixmap_samples(gapp.ctx, gapp.image); + int x0 = gapp.panx; + int y0 = gapp.pany; + int x1 = gapp.panx + image_w; + int y1 = gapp.pany + image_h; + RECT r; + + if (gapp.image) + { + if (gapp.iscopying || justcopied) + { + pdfapp_invert(&gapp, &gapp.selr); + justcopied = 1; + } + + pdfapp_inverthit(&gapp); + + dibinf->bmiHeader.biWidth = image_w; + dibinf->bmiHeader.biHeight = -image_h; + dibinf->bmiHeader.biSizeImage = image_h * 4; + + if (image_n == 2) + { + int i = image_w * image_h; + unsigned char *color = malloc(i*4); + unsigned char *s = samples; + unsigned char *d = color; + for (; i > 0 ; i--) + { + d[2] = d[1] = d[0] = *s++; + d[3] = *s++; + d += 4; + } + SetDIBitsToDevice(hdc, + gapp.panx, gapp.pany, image_w, image_h, + 0, 0, 0, image_h, color, + dibinf, DIB_RGB_COLORS); + free(color); + } + if (image_n == 4) + { + SetDIBitsToDevice(hdc, + gapp.panx, gapp.pany, image_w, image_h, + 0, 0, 0, image_h, samples, + dibinf, DIB_RGB_COLORS); + } + + pdfapp_inverthit(&gapp); + + if (gapp.iscopying || justcopied) + { + pdfapp_invert(&gapp, &gapp.selr); + justcopied = 1; + } + } + + /* Grey background */ + r.top = 0; r.bottom = gapp.winh; + r.left = 0; r.right = x0; + FillRect(hdc, &r, bgbrush); + r.left = x1; r.right = gapp.winw; + FillRect(hdc, &r, bgbrush); + r.left = 0; r.right = gapp.winw; + r.top = 0; r.bottom = y0; + FillRect(hdc, &r, bgbrush); + r.top = y1; r.bottom = gapp.winh; + FillRect(hdc, &r, bgbrush); + + /* Drop shadow */ + r.left = x0 + 2; + r.right = x1 + 2; + r.top = y1; + r.bottom = y1 + 2; + FillRect(hdc, &r, shbrush); + r.left = x1; + r.right = x1 + 2; + r.top = y0 + 2; + r.bottom = y1; + FillRect(hdc, &r, shbrush); + + winblitsearch(); +} + +void winresize(pdfapp_t *app, int w, int h) +{ + ShowWindow(hwndframe, SW_SHOWDEFAULT); + w += GetSystemMetrics(SM_CXFRAME) * 2; + h += GetSystemMetrics(SM_CYFRAME) * 2; + h += GetSystemMetrics(SM_CYCAPTION); + SetWindowPos(hwndframe, 0, 0, 0, w, h, SWP_NOZORDER | SWP_NOMOVE); +} + +void winrepaint(pdfapp_t *app) +{ + InvalidateRect(hwndview, NULL, 0); +} + +void winrepaintsearch(pdfapp_t *app) +{ + // TODO: invalidate only search area and + // call only search redraw routine. + InvalidateRect(hwndview, NULL, 0); +} + +void winfullscreen(pdfapp_t *app, int state) +{ + static WINDOWPLACEMENT savedplace; + static int isfullscreen = 0; + if (state && !isfullscreen) + { + GetWindowPlacement(hwndframe, &savedplace); + SetWindowLong(hwndframe, GWL_STYLE, WS_POPUP | WS_VISIBLE); + SetWindowPos(hwndframe, NULL, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_FRAMECHANGED); + ShowWindow(hwndframe, SW_SHOWMAXIMIZED); + isfullscreen = 1; + } + if (!state && isfullscreen) + { + SetWindowLong(hwndframe, GWL_STYLE, WS_OVERLAPPEDWINDOW); + SetWindowPos(hwndframe, NULL, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_FRAMECHANGED); + SetWindowPlacement(hwndframe, &savedplace); + isfullscreen = 0; + } +} + +/* + * Event handling + */ + +void windocopy(pdfapp_t *app) +{ + HGLOBAL handle; + unsigned short *ucsbuf; + + if (!OpenClipboard(hwndframe)) + return; + EmptyClipboard(); + + handle = GlobalAlloc(GMEM_MOVEABLE, 4096 * sizeof(unsigned short)); + if (!handle) + { + CloseClipboard(); + return; + } + + ucsbuf = GlobalLock(handle); + pdfapp_oncopy(&gapp, ucsbuf, 4096); + GlobalUnlock(handle); + + SetClipboardData(CF_UNICODETEXT, handle); + CloseClipboard(); + + justcopied = 1; /* keep inversion around for a while... */ +} + +void winreloadfile(pdfapp_t *app) +{ + pdfapp_close(app); + pdfapp_open(app, filename, 1); +} + +void winopenuri(pdfapp_t *app, char *buf) +{ + ShellExecuteA(hwndframe, "open", buf, 0, 0, SW_SHOWNORMAL); +} + +#define OUR_TIMER_ID 1 + +void winadvancetimer(pdfapp_t *app, float delay) +{ + timer_pending = 1; + SetTimer(hwndview, OUR_TIMER_ID, (unsigned int)(1000*delay), NULL); +} + +static void killtimer(pdfapp_t *app) +{ + timer_pending = 0; +} + +void handlekey(int c) +{ + if (timer_pending) + killtimer(&gapp); + + if (GetCapture() == hwndview) + return; + + if (justcopied) + { + justcopied = 0; + winrepaint(&gapp); + } + + /* translate VK into ASCII equivalents */ + if (c > 256) + { + switch (c - 256) + { + case VK_F1: c = '?'; break; + case VK_ESCAPE: c = '\033'; break; + case VK_DOWN: c = 'j'; break; + case VK_UP: c = 'k'; break; + case VK_LEFT: c = 'b'; break; + case VK_RIGHT: c = ' '; break; + case VK_PRIOR: c = ','; break; + case VK_NEXT: c = '.'; break; + } + } + + pdfapp_onkey(&gapp, c); + winrepaint(&gapp); +} + +void handlemouse(int x, int y, int btn, int state) +{ + if (state != 0 && timer_pending) + killtimer(&gapp); + + if (state != 0 && justcopied) + { + justcopied = 0; + winrepaint(&gapp); + } + + if (state == 1) + SetCapture(hwndview); + if (state == -1) + ReleaseCapture(); + + pdfapp_onmouse(&gapp, x, y, btn, 0, state); +} + +LRESULT CALLBACK +frameproc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch(message) + { + case WM_SETFOCUS: + PostMessage(hwnd, WM_APP+5, 0, 0); + return 0; + case WM_APP+5: + SetFocus(hwndview); + return 0; + + case WM_DESTROY: + PostQuitMessage(0); + return 0; + + case WM_SYSCOMMAND: + if (wParam == ID_ABOUT) + { + winhelp(&gapp); + return 0; + } + if (wParam == ID_DOCINFO) + { + info(); + return 0; + } + if (wParam == SC_MAXIMIZE) + gapp.shrinkwrap = 0; + break; + + case WM_SIZE: + { + // More generally, you should use GetEffectiveClientRect + // if you have a toolbar etc. + RECT rect; + GetClientRect(hwnd, &rect); + MoveWindow(hwndview, rect.left, rect.top, + rect.right-rect.left, rect.bottom-rect.top, TRUE); + return 0; + } + + case WM_SIZING: + gapp.shrinkwrap = 0; + break; + + case WM_NOTIFY: + case WM_COMMAND: + return SendMessage(hwndview, message, wParam, lParam); + + case WM_CLOSE: + if (!pdfapp_preclose(&gapp)) + return 0; + } + + return DefWindowProc(hwnd, message, wParam, lParam); +} + +LRESULT CALLBACK +viewproc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + static int oldx = 0; + static int oldy = 0; + int x = (signed short) LOWORD(lParam); + int y = (signed short) HIWORD(lParam); + + switch (message) + { + case WM_SIZE: + if (wParam == SIZE_MINIMIZED) + return 0; + if (wParam == SIZE_MAXIMIZED) + gapp.shrinkwrap = 0; + pdfapp_onresize(&gapp, LOWORD(lParam), HIWORD(lParam)); + break; + + /* Paint events are low priority and automagically catenated + * so we don't need to do any fancy waiting to defer repainting. + */ + case WM_PAINT: + { + //puts("WM_PAINT"); + PAINTSTRUCT ps; + hdc = BeginPaint(hwnd, &ps); + winblit(); + hdc = NULL; + EndPaint(hwnd, &ps); + pdfapp_postblit(&gapp); + return 0; + } + + case WM_ERASEBKGND: + return 1; // well, we don't need to erase to redraw cleanly + + /* Mouse events */ + + case WM_LBUTTONDOWN: + SetFocus(hwndview); + oldx = x; oldy = y; + handlemouse(x, y, 1, 1); + return 0; + case WM_MBUTTONDOWN: + SetFocus(hwndview); + oldx = x; oldy = y; + handlemouse(x, y, 2, 1); + return 0; + case WM_RBUTTONDOWN: + SetFocus(hwndview); + oldx = x; oldy = y; + handlemouse(x, y, 3, 1); + return 0; + + case WM_LBUTTONUP: + oldx = x; oldy = y; + handlemouse(x, y, 1, -1); + return 0; + case WM_MBUTTONUP: + oldx = x; oldy = y; + handlemouse(x, y, 2, -1); + return 0; + case WM_RBUTTONUP: + oldx = x; oldy = y; + handlemouse(x, y, 3, -1); + return 0; + + case WM_MOUSEMOVE: + oldx = x; oldy = y; + handlemouse(x, y, 0, 0); + return 0; + + /* Mouse wheel */ + + case WM_MOUSEWHEEL: + if ((signed short)HIWORD(wParam) > 0) + handlekey(LOWORD(wParam) & MK_SHIFT ? '+' : 'k'); + else + handlekey(LOWORD(wParam) & MK_SHIFT ? '-' : 'j'); + return 0; + + /* Timer */ + case WM_TIMER: + if (wParam == OUR_TIMER_ID && timer_pending && gapp.presentation_mode) + { + timer_pending = 0; + handlekey(VK_RIGHT + 256); + handlemouse(oldx, oldy, 0, 0); /* update cursor */ + return 0; + } + break; + + /* Keyboard events */ + + case WM_KEYDOWN: + /* only handle special keys */ + switch (wParam) + { + case VK_F1: + case VK_LEFT: + case VK_UP: + case VK_PRIOR: + case VK_RIGHT: + case VK_DOWN: + case VK_NEXT: + case VK_ESCAPE: + handlekey(wParam + 256); + handlemouse(oldx, oldy, 0, 0); /* update cursor */ + return 0; + } + return 1; + + /* unicode encoded chars, including escape, backspace etc... */ + case WM_CHAR: + if (wParam < 256) + { + handlekey(wParam); + handlemouse(oldx, oldy, 0, 0); /* update cursor */ + } + return 0; + } + + fflush(stdout); + + /* Pass on unhandled events to Windows */ + return DefWindowProc(hwnd, message, wParam, lParam); +} + +int WINAPI +WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) +{ + int argc; + LPWSTR *argv = CommandLineToArgvW(GetCommandLineW(), &argc); + char argv0[256]; + MSG msg; + int code; + fz_context *ctx; + + ctx = fz_new_context(NULL, NULL, FZ_STORE_DEFAULT); + if (!ctx) + { + fprintf(stderr, "cannot initialise context\n"); + exit(1); + } + pdfapp_init(ctx, &gapp); + + GetModuleFileNameA(NULL, argv0, sizeof argv0); + install_app(argv0); + + winopen(); + + if (argc == 2) + { + wcscpy(wbuf, argv[1]); + } + else + { + if (!winfilename(wbuf, nelem(wbuf))) + exit(0); + } + + code = WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, filename, sizeof filename, NULL, NULL); + if (code == 0) + winerror(&gapp, "cannot convert filename to utf-8"); + + pdfapp_open(&gapp, filename, 0); + + while (GetMessage(&msg, NULL, 0, 0)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + pdfapp_close(&gapp); + + return 0; +} diff --git a/platform/x11/win_res.rc b/platform/x11/win_res.rc new file mode 100644 index 00000000..fbf6915c --- /dev/null +++ b/platform/x11/win_res.rc @@ -0,0 +1,82 @@ +IDI_ICONAPP ICON "mupdf.ico" + +IDD_DLOGPASS DIALOG 50, 50, 204, 60 +//STYLE DS_MODALFRAME | WS_POPUP +STYLE 128 | 0x80000000 +CAPTION " MuPDF: Password " +FONT 8, "MS Shell Dlg" +BEGIN + EDITTEXT 3, 57, 20, 140, 12, 32 + DEFPUSHBUTTON "Okay", 1, 90, 40, 50, 14, 0x50010001 + PUSHBUTTON "Cancel", 2, 147, 40, 50, 14, 0x50010000 + LTEXT "The file is encrypted.", 4, 10, 7, 180, 10, 0x00000 + LTEXT "Password:", 5, 17, 22, 40, 10, 0x00000 +END + +IDD_DLOGINFO DIALOG 50, 50, 300, 145 +STYLE 128 | 0x80000000 +CAPTION " Document Properties " +FONT 8, "MS Shell Dlg" +BEGIN + DEFPUSHBUTTON "Okay", 1, 300-10-50, 145-7-14, 50, 14, 0x50010001 + + LTEXT "File:", -1, 10, 10, 50, 10, 0 + LTEXT "Format:", -1, 10, 20, 50, 10, 0 + LTEXT "Encryption:", -1, 10, 30, 50, 10, 0 + LTEXT "Permissions:", -1, 10, 40, 50, 10, 0 + + LTEXT "<file", 0x10, 60, 10, 230, 10, 0 + LTEXT "<version", 0x11, 60, 20, 230, 10, 0 + LTEXT "<encryption", 0x12, 60, 30, 230, 10, 0 + LTEXT "<permissions", 0x13, 60, 40, 230, 10, 0 + + LTEXT "Title:", -1, 10, 55, 50, 10, 0 + LTEXT "Author:", -1, 10, 65, 50, 10, 0 + LTEXT "Subject:", -1, 10, 75, 50, 10, 0 + LTEXT "Keywords:", -1, 10, 85, 50, 10, 0 + LTEXT "Creator:", -1, 10, 95, 50, 10, 0 + LTEXT "Producer:", -1, 10, 105, 50, 10, 0 + LTEXT "Created:", -1, 10, 115, 50, 10, 0 + LTEXT "Modified:", -1, 10, 125, 50, 10, 0 + + LTEXT "", 0x20, 60, 55, 230, 10, 0 + LTEXT "", 0x21, 60, 65, 230, 10, 0 + LTEXT "", 0x22, 60, 75, 230, 10, 0 + LTEXT "", 0x23, 60, 85, 230, 10, 0 + LTEXT "", 0x24, 60, 95, 230, 10, 0 + LTEXT "", 0x25, 60, 105, 230, 10, 0 + LTEXT "", 0x26, 60, 115, 100, 10, 0 + LTEXT "", 0x27, 60, 125, 100, 10, 0 +END + +IDD_DLOGTEXT DIALOG 50, 50, 204, 85 +STYLE 128 | 0x80000000 +CAPTION " MuPDF: fill out form" +FONT 8, "MS Shell Dlg" +BEGIN + EDITTEXT 3,8,7,183,50,0x1004 + DEFPUSHBUTTON "Okay",1,89,64,50,14 + PUSHBUTTON "Cancel",2,147,64,50,14 + LTEXT "** Invalid value **",4,11,65,62,8 +END + +IDD_DLOGLIST DIALOG 50, 50, 204, 85 +STYLE 128 | 0x80000000 +CAPTION " MuPDF: select an item" +FONT 8, "MS Shell Dlg" +BEGIN + LISTBOX 3,8,7,183,50,0x210102 + DEFPUSHBUTTON "Okay",1,89,64,50,14 + PUSHBUTTON "Cancel",2,147,64,50,14 +END + +IDD_DLOGABOUT DIALOG 50, 50, 200, 300 +STYLE 128 | 0x80000000 +CAPTION " About MuPDF " +FONT 8, "MS Shell Dlg" +BEGIN + DEFPUSHBUTTON "Okay", 1, 200-10-50, 300-7-14, 50, 14, 0x50010001 + LTEXT "<copyright>", 2, 10, 10, 180, 20, 0 + LTEXT "<usage>", 3, 10, 35, 180, 240, 0 +END + diff --git a/platform/x11/x11_image.c b/platform/x11/x11_image.c new file mode 100644 index 00000000..4f2db2e8 --- /dev/null +++ b/platform/x11/x11_image.c @@ -0,0 +1,703 @@ +/* + * Blit RGBA images to X with X(Shm)Images + */ + +#ifndef _XOPEN_SOURCE +# define _XOPEN_SOURCE 1 +#endif + +#ifndef _XOPEN_SOURCE +# define _XOPEN_SOURCE 1 +#endif + +#define noSHOWINFO + +#include "mupdf/fitz.h" + +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <sys/ipc.h> +#include <sys/shm.h> +#include <X11/extensions/XShm.h> + +extern int ffs(int); + +static int is_big_endian(void) +{ + static const int one = 1; + return *(char*)&one == 0; +} + +typedef void (*ximage_convert_func_t) +( + const unsigned char *src, + int srcstride, + unsigned char *dst, + int dststride, + int w, + int h + ); + +#define POOLSIZE 4 +#define WIDTH 256 +#define HEIGHT 256 + +enum { + ARGB8888, + BGRA8888, + RGBA8888, + ABGR8888, + RGB888, + BGR888, + RGB565, + RGB565_BR, + RGB555, + RGB555_BR, + BGR233, + UNKNOWN +}; + +#ifdef SHOWINFO +static char *modename[] = { + "ARGB8888", + "BGRA8888", + "RGBA8888", + "ABGR8888", + "RGB888", + "BGR888", + "RGB565", + "RGB565_BR", + "RGB555", + "RGB555_BR", + "BGR233", + "UNKNOWN" +}; +#endif + +extern ximage_convert_func_t ximage_convert_funcs[]; + +static struct +{ + Display *display; + int screen; + XVisualInfo visual; + Colormap colormap; + + int bitsperpixel; + int mode; + + XColor rgbcube[256]; + + ximage_convert_func_t convert_func; + + int useshm; + int shmcode; + XImage *pool[POOLSIZE]; + /* MUST exist during the lifetime of the shared ximage according to the + xc/doc/hardcopy/Xext/mit-shm.PS.gz */ + XShmSegmentInfo shminfo[POOLSIZE]; + int lastused; +} info; + +static XImage * +createximage(Display *dpy, Visual *vis, XShmSegmentInfo *xsi, int depth, int w, int h) +{ + XImage *img; + Status status; + + if (!XShmQueryExtension(dpy)) + goto fallback; + if (!info.useshm) + goto fallback; + + img = XShmCreateImage(dpy, vis, depth, ZPixmap, NULL, xsi, w, h); + if (!img) + { + fprintf(stderr, "warn: could not XShmCreateImage\n"); + goto fallback; + } + + xsi->shmid = shmget(IPC_PRIVATE, + img->bytes_per_line * img->height, + IPC_CREAT | 0777); + if (xsi->shmid < 0) + { + XDestroyImage(img); + fprintf(stderr, "warn: could not shmget\n"); + goto fallback; + } + + img->data = xsi->shmaddr = shmat(xsi->shmid, NULL, 0); + if (img->data == (char*)-1) + { + XDestroyImage(img); + fprintf(stderr, "warn: could not shmat\n"); + goto fallback; + } + + xsi->readOnly = False; + status = XShmAttach(dpy, xsi); + if (!status) + { + shmdt(xsi->shmaddr); + XDestroyImage(img); + fprintf(stderr, "warn: could not XShmAttach\n"); + goto fallback; + } + + XSync(dpy, False); + + shmctl(xsi->shmid, IPC_RMID, NULL); + + return img; + +fallback: + info.useshm = 0; + + img = XCreateImage(dpy, vis, depth, ZPixmap, 0, NULL, w, h, 32, 0); + if (!img) + { + fprintf(stderr, "fail: could not XCreateImage"); + abort(); + } + + img->data = malloc(h * img->bytes_per_line); + if (!img->data) + { + fprintf(stderr, "fail: could not malloc"); + abort(); + } + + return img; +} + +static void +make_colormap(void) +{ + if (info.visual.class == PseudoColor && info.visual.depth == 8) + { + int i, r, g, b; + i = 0; + for (b = 0; b < 4; b++) { + for (g = 0; g < 8; g++) { + for (r = 0; r < 8; r++) { + info.rgbcube[i].pixel = i; + info.rgbcube[i].red = (r * 36) << 8; + info.rgbcube[i].green = (g * 36) << 8; + info.rgbcube[i].blue = (b * 85) << 8; + info.rgbcube[i].flags = + DoRed | DoGreen | DoBlue; + i++; + } + } + } + info.colormap = XCreateColormap(info.display, + RootWindow(info.display, info.screen), + info.visual.visual, + AllocAll); + XStoreColors(info.display, info.colormap, info.rgbcube, 256); + return; + } + else if (info.visual.class == TrueColor) + { + info.colormap = 0; + return; + } + fprintf(stderr, "Cannot handle visual class %d with depth: %d\n", + info.visual.class, info.visual.depth); + return; +} + +static void +select_mode(void) +{ + + int byteorder; + int byterev; + unsigned long rm, gm, bm; + unsigned long rs, gs, bs; + + byteorder = ImageByteOrder(info.display); + if (is_big_endian()) + byterev = byteorder != MSBFirst; + else + byterev = byteorder != LSBFirst; + + rm = info.visual.red_mask; + gm = info.visual.green_mask; + bm = info.visual.blue_mask; + + rs = ffs(rm) - 1; + gs = ffs(gm) - 1; + bs = ffs(bm) - 1; + +#ifdef SHOWINFO + printf("ximage: mode %d/%d %08lx %08lx %08lx (%ld,%ld,%ld) %s%s\n", + info.visual.depth, + info.bitsperpixel, + rm, gm, bm, rs, gs, bs, + byteorder == MSBFirst ? "msb" : "lsb", + byterev ? " <swap>":""); +#endif + + info.mode = UNKNOWN; + if (info.bitsperpixel == 8) { + /* Either PseudoColor with BGR233 colormap, or TrueColor */ + info.mode = BGR233; + } + else if (info.bitsperpixel == 16) { + if (rm == 0xF800 && gm == 0x07E0 && bm == 0x001F) + info.mode = !byterev ? RGB565 : RGB565_BR; + if (rm == 0x7C00 && gm == 0x03E0 && bm == 0x001F) + info.mode = !byterev ? RGB555 : RGB555_BR; + } + else if (info.bitsperpixel == 24) { + if (rs == 0 && gs == 8 && bs == 16) + info.mode = byteorder == MSBFirst ? RGB888 : BGR888; + if (rs == 16 && gs == 8 && bs == 0) + info.mode = byteorder == MSBFirst ? BGR888 : RGB888; + } + else if (info.bitsperpixel == 32) { + if (rs == 0 && gs == 8 && bs == 16) + info.mode = byteorder == MSBFirst ? ABGR8888 : RGBA8888; + if (rs == 8 && gs == 16 && bs == 24) + info.mode = byteorder == MSBFirst ? BGRA8888 : ARGB8888; + if (rs == 16 && gs == 8 && bs == 0) + info.mode = byteorder == MSBFirst ? ARGB8888 : BGRA8888; + if (rs == 24 && gs == 16 && bs == 8) + info.mode = byteorder == MSBFirst ? RGBA8888 : ABGR8888; + } + +#ifdef SHOWINFO + printf("ximage: RGBA8888 to %s\n", modename[info.mode]); +#endif + + /* select conversion function */ + info.convert_func = ximage_convert_funcs[info.mode]; +} + +static int +create_pool(void) +{ + int i; + + info.lastused = 0; + + for (i = 0; i < POOLSIZE; i++) { + info.pool[i] = NULL; + } + + for (i = 0; i < POOLSIZE; i++) { + info.pool[i] = createximage(info.display, + info.visual.visual, &info.shminfo[i], info.visual.depth, + WIDTH, HEIGHT); + if (!info.pool[i]) { + return 0; + } + } + + return 1; +} + +static XImage * +next_pool_image(void) +{ + if (info.lastused + 1 >= POOLSIZE) { + if (info.useshm) + XSync(info.display, False); + else + XFlush(info.display); + info.lastused = 0; + } + return info.pool[info.lastused ++]; +} + +static int +ximage_error_handler(Display *display, XErrorEvent *event) +{ + /* Turn off shared memory images if we get an error from the MIT-SHM extension */ + if (event->request_code == info.shmcode) + { + char buf[80]; + XGetErrorText(display, event->error_code, buf, sizeof buf); + fprintf(stderr, "ximage: disabling shared memory extension: %s\n", buf); + info.useshm = 0; + return 0; + } + + XSetErrorHandler(NULL); + return (XSetErrorHandler(ximage_error_handler))(display, event); +} + +int +ximage_init(Display *display, int screen, Visual *visual) +{ + XVisualInfo template; + XVisualInfo *visuals; + int nvisuals; + XPixmapFormatValues *formats; + int nformats; + int ok; + int i; + int major; + int event; + int error; + + info.display = display; + info.screen = screen; + info.colormap = 0; + + /* Get XVisualInfo for this visual */ + template.visualid = XVisualIDFromVisual(visual); + visuals = XGetVisualInfo(display, VisualIDMask, &template, &nvisuals); + if (nvisuals != 1) { + fprintf(stderr, "Visual not found!\n"); + XFree(visuals); + return 0; + } + memcpy(&info.visual, visuals, sizeof (XVisualInfo)); + XFree(visuals); + + /* Get appropriate PixmapFormat for this visual */ + formats = XListPixmapFormats(info.display, &nformats); + for (i = 0; i < nformats; i++) { + if (formats[i].depth == info.visual.depth) { + info.bitsperpixel = formats[i].bits_per_pixel; + break; + } + } + XFree(formats); + if (i == nformats) { + fprintf(stderr, "PixmapFormat not found!\n"); + return 0; + } + + /* extract mode */ + select_mode(); + + /* prepare colormap */ + make_colormap(); + + /* identify code for MIT-SHM extension */ + if (XQueryExtension(display, "MIT-SHM", &major, &event, &error) && + XShmQueryExtension(display)) + info.shmcode = major; + + /* intercept errors looking for SHM code */ + XSetErrorHandler(ximage_error_handler); + + /* prepare pool of XImages */ + info.useshm = 1; + ok = create_pool(); + if (!ok) + return 0; + +#ifdef SHOWINFO + printf("ximage: %sPutImage\n", info.useshm ? "XShm" : "X"); +#endif + + return 1; +} + +int +ximage_get_depth(void) +{ + return info.visual.depth; +} + +Visual * +ximage_get_visual(void) +{ + return info.visual.visual; +} + +Colormap +ximage_get_colormap(void) +{ + return info.colormap; +} + +void +ximage_blit(Drawable d, GC gc, + int dstx, int dsty, + unsigned char *srcdata, + int srcx, int srcy, + int srcw, int srch, + int srcstride) +{ + XImage *image; + int ax, ay; + int w, h; + unsigned char *srcptr; + + for (ay = 0; ay < srch; ay += HEIGHT) + { + h = fz_mini(srch - ay, HEIGHT); + for (ax = 0; ax < srcw; ax += WIDTH) + { + w = fz_mini(srcw - ax, WIDTH); + + image = next_pool_image(); + + srcptr = srcdata + + (ay + srcy) * srcstride + + (ax + srcx) * 4; + + info.convert_func(srcptr, srcstride, + (unsigned char *) image->data, + image->bytes_per_line, w, h); + + if (info.useshm) + { + XShmPutImage(info.display, d, gc, image, + 0, 0, dstx + ax, dsty + ay, + w, h, False); + } + else + { + XPutImage(info.display, d, gc, image, + 0, 0, + dstx + ax, + dsty + ay, + w, h); + } + } + } +} + +/* + * Primitive conversion functions + */ + +#ifndef restrict +#ifndef _C99 +#ifdef __GNUC__ +#define restrict __restrict__ +#else +#define restrict +#endif +#endif +#endif + +#define PARAMS \ + const unsigned char * restrict src, \ + int srcstride, \ + unsigned char * restrict dst, \ + int dststride, \ + int w, \ + int h + +/* + * Convert byte:RGBA8888 to various formats + */ + +static void +ximage_convert_argb8888(PARAMS) +{ + int x, y; + for (y = 0; y < h; y++) { + for (x = 0; x < w; x ++) { + dst[x * 4 + 0] = src[x * 4 + 3]; /* a */ + dst[x * 4 + 1] = src[x * 4 + 0]; /* r */ + dst[x * 4 + 2] = src[x * 4 + 1]; /* g */ + dst[x * 4 + 3] = src[x * 4 + 2]; /* b */ + } + dst += dststride; + src += srcstride; + } +} + +static void +ximage_convert_bgra8888(PARAMS) +{ + int x, y; + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + dst[x * 4 + 0] = src[x * 4 + 2]; + dst[x * 4 + 1] = src[x * 4 + 1]; + dst[x * 4 + 2] = src[x * 4 + 0]; + dst[x * 4 + 3] = src[x * 4 + 3]; + } + dst += dststride; + src += srcstride; + } +} + +static void +ximage_convert_abgr8888(PARAMS) +{ + int x, y; + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + dst[x * 4 + 0] = src[x * 4 + 3]; + dst[x * 4 + 1] = src[x * 4 + 2]; + dst[x * 4 + 2] = src[x * 4 + 1]; + dst[x * 4 + 3] = src[x * 4 + 0]; + } + dst += dststride; + src += srcstride; + } +} + +static void +ximage_convert_rgba8888(PARAMS) +{ + int x, y; + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + ((unsigned *)dst)[x] = ((unsigned *)src)[x]; + } + dst += dststride; + src += srcstride; + } +} + +static void +ximage_convert_bgr888(PARAMS) +{ + int x, y; + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + dst[3*x + 0] = src[4*x + 2]; + dst[3*x + 1] = src[4*x + 1]; + dst[3*x + 2] = src[4*x + 0]; + } + src += srcstride; + dst += dststride; + } +} + +static void +ximage_convert_rgb888(PARAMS) +{ + int x, y; + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + dst[3*x + 0] = src[4*x + 0]; + dst[3*x + 1] = src[4*x + 1]; + dst[3*x + 2] = src[4*x + 2]; + } + src += srcstride; + dst += dststride; + } +} + +static void +ximage_convert_rgb565(PARAMS) +{ + unsigned char r, g, b; + int x, y; + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + r = src[4*x + 0]; + g = src[4*x + 1]; + b = src[4*x + 2]; + ((unsigned short *)dst)[x] = + ((r & 0xF8) << 8) | + ((g & 0xFC) << 3) | + (b >> 3); + } + src += srcstride; + dst += dststride; + } +} + +static void +ximage_convert_rgb565_br(PARAMS) +{ + unsigned char r, g, b; + int x, y; + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + r = src[4*x + 0]; + g = src[4*x + 1]; + b = src[4*x + 2]; + /* final word is: + g4 g3 g2 b7 b6 b5 b4 b3 : r7 r6 r5 r4 r3 g7 g6 g5 + */ + ((unsigned short *)dst)[x] = + (r & 0xF8) | + ((g & 0xE0) >> 5) | + ((g & 0x1C) << 11) | + ((b & 0xF8) << 5); + } + src += srcstride; + dst += dststride; + } +} + +static void +ximage_convert_rgb555(PARAMS) +{ + unsigned char r, g, b; + int x, y; + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + r = src[4*x + 0]; + g = src[4*x + 1]; + b = src[4*x + 2]; + ((unsigned short *)dst)[x] = + ((r & 0xF8) << 7) | + ((g & 0xF8) << 2) | + (b >> 3); + } + src += srcstride; + dst += dststride; + } +} + +static void +ximage_convert_rgb555_br(PARAMS) +{ + unsigned char r, g, b; + int x, y; + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + r = src[4*x + 0]; + g = src[4*x + 1]; + b = src[4*x + 2]; + /* final word is: + g5 g4 g3 b7 b6 b5 b4 b3 : 0 r7 r6 r5 r4 r3 g7 g6 + */ + ((unsigned short *)dst)[x] = + ((r & 0xF8) >> 1) | + ((g & 0xC0) >> 6) | + ((g & 0x38) << 10) | + ((b & 0xF8) << 5); + } + src += srcstride; + dst += dststride; + } +} + +static void +ximage_convert_bgr233(PARAMS) +{ + unsigned char r, g, b; + int x,y; + for(y = 0; y < h; y++) { + for(x = 0; x < w; x++) { + r = src[4*x + 0]; + g = src[4*x + 1]; + b = src[4*x + 2]; + /* format: b7 b6 g7 g6 g5 r7 r6 r5 */ + dst[x] = (b&0xC0) | ((g>>2)&0x38) | ((r>>5)&0x7); + } + src += srcstride; + dst += dststride; + } +} + +ximage_convert_func_t ximage_convert_funcs[] = { + ximage_convert_argb8888, + ximage_convert_bgra8888, + ximage_convert_rgba8888, + ximage_convert_abgr8888, + ximage_convert_rgb888, + ximage_convert_bgr888, + ximage_convert_rgb565, + ximage_convert_rgb565_br, + ximage_convert_rgb555, + ximage_convert_rgb555_br, + ximage_convert_bgr233, +}; diff --git a/platform/x11/x11_main.c b/platform/x11/x11_main.c new file mode 100644 index 00000000..481adce1 --- /dev/null +++ b/platform/x11/x11_main.c @@ -0,0 +1,993 @@ +#include "pdfapp.h" + +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/Xatom.h> +#include <X11/cursorfont.h> +#include <X11/keysym.h> + +#include <sys/select.h> +#include <sys/time.h> +#include <sys/types.h> +#include <unistd.h> +#include <signal.h> + +#define mupdf_icon_bitmap_16_width 16 +#define mupdf_icon_bitmap_16_height 16 +static unsigned char mupdf_icon_bitmap_16_bits[] = { + 0x00, 0x00, 0x00, 0x1e, 0x00, 0x2b, 0x80, 0x55, 0x8c, 0x62, 0x8c, 0x51, + 0x9c, 0x61, 0x1c, 0x35, 0x3c, 0x1f, 0x3c, 0x0f, 0xfc, 0x0f, 0xec, 0x0d, + 0xec, 0x0d, 0xcc, 0x0c, 0xcc, 0x0c, 0x00, 0x00 }; + +#define mupdf_icon_bitmap_16_mask_width 16 +#define mupdf_icon_bitmap_16_mask_height 16 +static unsigned char mupdf_icon_bitmap_16_mask_bits[] = { + 0x00, 0x1e, 0x00, 0x3f, 0x80, 0x7f, 0xce, 0xff, 0xde, 0xff, 0xde, 0xff, + 0xfe, 0xff, 0xfe, 0x7f, 0xfe, 0x3f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, + 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xce, 0x1c }; + +#ifndef timeradd +#define timeradd(a, b, result) \ + do { \ + (result)->tv_sec = (a)->tv_sec + (b)->tv_sec; \ + (result)->tv_usec = (a)->tv_usec + (b)->tv_usec; \ + if ((result)->tv_usec >= 1000000) \ + { \ + ++(result)->tv_sec; \ + (result)->tv_usec -= 1000000; \ + } \ + } while (0) +#endif + +#ifndef timersub +#define timersub(a, b, result) \ + do { \ + (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \ + (result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \ + if ((result)->tv_usec < 0) { \ + --(result)->tv_sec; \ + (result)->tv_usec += 1000000; \ + } \ + } while (0) +#endif + +extern int ximage_init(Display *display, int screen, Visual *visual); +extern int ximage_get_depth(void); +extern Visual *ximage_get_visual(void); +extern Colormap ximage_get_colormap(void); +extern void ximage_blit(Drawable d, GC gc, int dstx, int dsty, + unsigned char *srcdata, + int srcx, int srcy, int srcw, int srch, int srcstride); + +void windrawstringxor(pdfapp_t *app, int x, int y, char *s); +void cleanup(pdfapp_t *app); + +static Display *xdpy; +static Atom XA_CLIPBOARD; +static Atom XA_TARGETS; +static Atom XA_TIMESTAMP; +static Atom XA_UTF8_STRING; +static Atom WM_DELETE_WINDOW; +static Atom NET_WM_STATE; +static Atom NET_WM_STATE_FULLSCREEN; +static int x11fd; +static int xscr; +static Window xwin; +static Pixmap xicon, xmask; +static GC xgc; +static XEvent xevt; +static int mapped = 0; +static Cursor xcarrow, xchand, xcwait, xccaret; +static int justcopied = 0; +static int dirty = 0; +static int transition_dirty = 0; +static int dirtysearch = 0; +static char *password = ""; +static XColor xbgcolor; +static XColor xshcolor; +static int reqw = 0; +static int reqh = 0; +static char copylatin1[1024 * 16] = ""; +static char copyutf8[1024 * 48] = ""; +static Time copytime; +static char *filename; + +static pdfapp_t gapp; +static int closing = 0; +static int reloading = 0; +static int showingpage = 0; + +static int advance_scheduled = 0; +static struct timeval tmo_advance; + +/* + * Dialog boxes + */ + +void winerror(pdfapp_t *app, char *msg) +{ + fprintf(stderr, "mupdf: error: %s\n", msg); + cleanup(app); + exit(1); +} + +void winwarn(pdfapp_t *app, char *msg) +{ + fprintf(stderr, "mupdf: warning: %s\n", msg); +} + +void winalert(pdfapp_t *app, pdf_alert_event *alert) +{ + fprintf(stderr, "Alert %s: %s", alert->title, alert->message); + switch (alert->button_group_type) + { + case PDF_ALERT_BUTTON_GROUP_OK: + case PDF_ALERT_BUTTON_GROUP_OK_CANCEL: + alert->button_pressed = PDF_ALERT_BUTTON_OK; + break; + case PDF_ALERT_BUTTON_GROUP_YES_NO: + case PDF_ALERT_BUTTON_GROUP_YES_NO_CANCEL: + alert->button_pressed = PDF_ALERT_BUTTON_YES; + break; + } +} + +void winprint(pdfapp_t *app) +{ + fprintf(stderr, "The MuPDF library supports printing, but this application currently does not"); +} + +char *winpassword(pdfapp_t *app, char *filename) +{ + char *r = password; + password = NULL; + return r; +} + +char *wintextinput(pdfapp_t *app, char *inittext, int retry) +{ + static char buf[256]; + + if (retry) + return NULL; + + printf("> [%s] ", inittext); + fgets(buf, sizeof buf, stdin); + return buf; +} + +int winchoiceinput(pdfapp_t *app, int nopts, char *opts[], int *nvals, char *vals[]) +{ + /* FIXME: temporary dummy implementation */ + return 0; +} + +/* + * X11 magic + */ + +static void winopen(void) +{ + XWMHints *wmhints; + XClassHint *classhint; + + xdpy = XOpenDisplay(NULL); + if (!xdpy) + fz_throw(gapp.ctx, FZ_ERROR_GENERIC, "cannot open display"); + + XA_CLIPBOARD = XInternAtom(xdpy, "CLIPBOARD", False); + XA_TARGETS = XInternAtom(xdpy, "TARGETS", False); + XA_TIMESTAMP = XInternAtom(xdpy, "TIMESTAMP", False); + XA_UTF8_STRING = XInternAtom(xdpy, "UTF8_STRING", False); + WM_DELETE_WINDOW = XInternAtom(xdpy, "WM_DELETE_WINDOW", False); + NET_WM_STATE = XInternAtom(xdpy, "_NET_WM_STATE", False); + NET_WM_STATE_FULLSCREEN = XInternAtom(xdpy, "_NET_WM_STATE_FULLSCREEN", False); + + xscr = DefaultScreen(xdpy); + + ximage_init(xdpy, xscr, DefaultVisual(xdpy, xscr)); + + xcarrow = XCreateFontCursor(xdpy, XC_left_ptr); + xchand = XCreateFontCursor(xdpy, XC_hand2); + xcwait = XCreateFontCursor(xdpy, XC_watch); + xccaret = XCreateFontCursor(xdpy, XC_xterm); + + xbgcolor.red = 0x7000; + xbgcolor.green = 0x7000; + xbgcolor.blue = 0x7000; + + xshcolor.red = 0x4000; + xshcolor.green = 0x4000; + xshcolor.blue = 0x4000; + + XAllocColor(xdpy, DefaultColormap(xdpy, xscr), &xbgcolor); + XAllocColor(xdpy, DefaultColormap(xdpy, xscr), &xshcolor); + + xwin = XCreateWindow(xdpy, DefaultRootWindow(xdpy), + 10, 10, 200, 100, 0, + ximage_get_depth(), + InputOutput, + ximage_get_visual(), + 0, + NULL); + if (xwin == None) + fz_throw(gapp.ctx, FZ_ERROR_GENERIC, "cannot create window"); + + XSetWindowColormap(xdpy, xwin, ximage_get_colormap()); + XSelectInput(xdpy, xwin, + StructureNotifyMask | ExposureMask | KeyPressMask | + PointerMotionMask | ButtonPressMask | ButtonReleaseMask); + + mapped = 0; + + xgc = XCreateGC(xdpy, xwin, 0, NULL); + + XDefineCursor(xdpy, xwin, xcarrow); + + wmhints = XAllocWMHints(); + if (wmhints) + { + wmhints->flags = IconPixmapHint | IconMaskHint; + xicon = XCreateBitmapFromData(xdpy, xwin, + (char*)mupdf_icon_bitmap_16_bits, + mupdf_icon_bitmap_16_width, + mupdf_icon_bitmap_16_height); + xmask = XCreateBitmapFromData(xdpy, xwin, + (char*)mupdf_icon_bitmap_16_mask_bits, + mupdf_icon_bitmap_16_mask_width, + mupdf_icon_bitmap_16_mask_height); + if (xicon && xmask) + { + wmhints->icon_pixmap = xicon; + wmhints->icon_mask = xmask; + XSetWMHints(xdpy, xwin, wmhints); + } + XFree(wmhints); + } + + classhint = XAllocClassHint(); + if (classhint) + { + classhint->res_name = "mupdf"; + classhint->res_class = "MuPDF"; + XSetClassHint(xdpy, xwin, classhint); + XFree(classhint); + } + + XSetWMProtocols(xdpy, xwin, &WM_DELETE_WINDOW, 1); + + x11fd = ConnectionNumber(xdpy); +} + +void winclose(pdfapp_t *app) +{ + closing = 1; +} + +int winsavequery(pdfapp_t *app) +{ + /* FIXME: temporary dummy implementation */ + return DISCARD; +} + +int wingetsavepath(pdfapp_t *app, char *buf, int len) +{ + /* FIXME: temporary dummy implementation */ + return 0; +} + +void winreplacefile(char *source, char *target) +{ + rename(source, target); +} + +void cleanup(pdfapp_t *app) +{ + fz_context *ctx = app->ctx; + + pdfapp_close(app); + + XDestroyWindow(xdpy, xwin); + + XFreePixmap(xdpy, xicon); + + XFreeCursor(xdpy, xccaret); + XFreeCursor(xdpy, xcwait); + XFreeCursor(xdpy, xchand); + XFreeCursor(xdpy, xcarrow); + + XFreeGC(xdpy, xgc); + + XCloseDisplay(xdpy); + + fz_free_context(ctx); +} + +static int winresolution() +{ + return DisplayWidth(xdpy, xscr) * 25.4 / + DisplayWidthMM(xdpy, xscr) + 0.5; +} + +void wincursor(pdfapp_t *app, int curs) +{ + if (curs == ARROW) + XDefineCursor(xdpy, xwin, xcarrow); + if (curs == HAND) + XDefineCursor(xdpy, xwin, xchand); + if (curs == WAIT) + XDefineCursor(xdpy, xwin, xcwait); + if (curs == CARET) + XDefineCursor(xdpy, xwin, xccaret); + XFlush(xdpy); +} + +void wintitle(pdfapp_t *app, char *s) +{ + XStoreName(xdpy, xwin, s); +#ifdef X_HAVE_UTF8_STRING + Xutf8SetWMProperties(xdpy, xwin, s, s, NULL, 0, NULL, NULL, NULL); +#else + XmbSetWMProperties(xdpy, xwin, s, s, NULL, 0, NULL, NULL, NULL); +#endif +} + +void winhelp(pdfapp_t *app) +{ + fprintf(stderr, "%s\n%s", pdfapp_version(app), pdfapp_usage(app)); +} + +void winresize(pdfapp_t *app, int w, int h) +{ + int image_w = fz_pixmap_width(gapp.ctx, gapp.image); + int image_h = fz_pixmap_height(gapp.ctx, gapp.image); + XWindowChanges values; + int mask, width, height; + + mask = CWWidth | CWHeight; + values.width = w; + values.height = h; + XConfigureWindow(xdpy, xwin, mask, &values); + + reqw = w; + reqh = h; + + if (!mapped) + { + gapp.winw = w; + gapp.winh = h; + width = -1; + height = -1; + + XMapWindow(xdpy, xwin); + XFlush(xdpy); + + while (1) + { + XNextEvent(xdpy, &xevt); + if (xevt.type == ConfigureNotify) + { + width = xevt.xconfigure.width; + height = xevt.xconfigure.height; + } + if (xevt.type == MapNotify) + break; + } + + XSetForeground(xdpy, xgc, WhitePixel(xdpy, xscr)); + XFillRectangle(xdpy, xwin, xgc, 0, 0, image_w, image_h); + XFlush(xdpy); + + if (width != reqw || height != reqh) + { + gapp.shrinkwrap = 0; + dirty = 1; + pdfapp_onresize(&gapp, width, height); + } + + mapped = 1; + } +} + +void winfullscreen(pdfapp_t *app, int state) +{ + XEvent xev; + xev.xclient.type = ClientMessage; + xev.xclient.serial = 0; + xev.xclient.send_event = True; + xev.xclient.window = xwin; + xev.xclient.message_type = NET_WM_STATE; + xev.xclient.format = 32; + xev.xclient.data.l[0] = state; + xev.xclient.data.l[1] = NET_WM_STATE_FULLSCREEN; + xev.xclient.data.l[2] = 0; + XSendEvent(xdpy, DefaultRootWindow(xdpy), False, + SubstructureRedirectMask | SubstructureNotifyMask, + &xev); +} + +static void fillrect(int x, int y, int w, int h) +{ + if (w > 0 && h > 0) + XFillRectangle(xdpy, xwin, xgc, x, y, w, h); +} + +static void winblitsearch(pdfapp_t *app) +{ + if (gapp.isediting) + { + char buf[sizeof(gapp.search) + 50]; + sprintf(buf, "Search: %s", gapp.search); + XSetForeground(xdpy, xgc, WhitePixel(xdpy, xscr)); + fillrect(0, 0, gapp.winw, 30); + windrawstring(&gapp, 10, 20, buf); + } +} + +static void winblit(pdfapp_t *app) +{ + int image_w = fz_pixmap_width(gapp.ctx, gapp.image); + int image_h = fz_pixmap_height(gapp.ctx, gapp.image); + int image_n = fz_pixmap_components(gapp.ctx, gapp.image); + unsigned char *image_samples = fz_pixmap_samples(gapp.ctx, gapp.image); + int x0 = gapp.panx; + int y0 = gapp.pany; + int x1 = gapp.panx + image_w; + int y1 = gapp.pany + image_h; + + XSetForeground(xdpy, xgc, xbgcolor.pixel); + fillrect(0, 0, x0, gapp.winh); + fillrect(x1, 0, gapp.winw - x1, gapp.winh); + fillrect(0, 0, gapp.winw, y0); + fillrect(0, y1, gapp.winw, gapp.winh - y1); + + XSetForeground(xdpy, xgc, xshcolor.pixel); + fillrect(x0+2, y1, image_w, 2); + fillrect(x1, y0+2, 2, image_h); + + if (gapp.iscopying || justcopied) + { + pdfapp_invert(&gapp, &gapp.selr); + justcopied = 1; + } + + pdfapp_inverthit(&gapp); + + if (image_n == 4) + ximage_blit(xwin, xgc, + x0, y0, + image_samples, + 0, 0, + image_w, + image_h, + image_w * image_n); + else if (image_n == 2) + { + int i = image_w*image_h; + unsigned char *color = malloc(i*4); + if (color) + { + unsigned char *s = image_samples; + unsigned char *d = color; + for (; i > 0 ; i--) + { + d[2] = d[1] = d[0] = *s++; + d[3] = *s++; + d += 4; + } + ximage_blit(xwin, xgc, + x0, y0, + color, + 0, 0, + image_w, + image_h, + image_w * 4); + free(color); + } + } + + pdfapp_inverthit(&gapp); + + if (gapp.iscopying || justcopied) + { + pdfapp_invert(&gapp, &gapp.selr); + justcopied = 1; + } + + winblitsearch(app); + + if (showingpage) + { + char buf[42]; + snprintf(buf, sizeof buf, "Page %d/%d", gapp.pageno, gapp.pagecount); + windrawstringxor(&gapp, 10, 20, buf); + } +} + +void winrepaint(pdfapp_t *app) +{ + dirty = 1; + if (app->in_transit) + transition_dirty = 1; +} + +void winrepaintsearch(pdfapp_t *app) +{ + dirtysearch = 1; +} + +void winadvancetimer(pdfapp_t *app, float duration) +{ + struct timeval now; + + gettimeofday(&now, NULL); + memset(&tmo_advance, 0, sizeof(tmo_advance)); + tmo_advance.tv_sec = (int)duration; + tmo_advance.tv_usec = 1000000 * (duration - tmo_advance.tv_sec); + timeradd(&tmo_advance, &now, &tmo_advance); + advance_scheduled = 1; +} + +void windrawstringxor(pdfapp_t *app, int x, int y, char *s) +{ + int prevfunction; + XGCValues xgcv; + + XGetGCValues(xdpy, xgc, GCFunction, &xgcv); + prevfunction = xgcv.function; + xgcv.function = GXxor; + XChangeGC(xdpy, xgc, GCFunction, &xgcv); + + XSetForeground(xdpy, xgc, WhitePixel(xdpy, DefaultScreen(xdpy))); + + XDrawString(xdpy, xwin, xgc, x, y, s, strlen(s)); + XFlush(xdpy); + + XGetGCValues(xdpy, xgc, GCFunction, &xgcv); + xgcv.function = prevfunction; + XChangeGC(xdpy, xgc, GCFunction, &xgcv); +} + +void windrawstring(pdfapp_t *app, int x, int y, char *s) +{ + XSetForeground(xdpy, xgc, BlackPixel(xdpy, DefaultScreen(xdpy))); + XDrawString(xdpy, xwin, xgc, x, y, s, strlen(s)); +} + +void docopy(pdfapp_t *app, Atom copy_target) +{ + unsigned short copyucs2[16 * 1024]; + char *latin1 = copylatin1; + char *utf8 = copyutf8; + unsigned short *ucs2; + int ucs; + + pdfapp_oncopy(&gapp, copyucs2, 16 * 1024); + + for (ucs2 = copyucs2; ucs2[0] != 0; ucs2++) + { + ucs = ucs2[0]; + + utf8 += fz_runetochar(utf8, ucs); + + if (ucs < 256) + *latin1++ = ucs; + else + *latin1++ = '?'; + } + + *utf8 = 0; + *latin1 = 0; + + XSetSelectionOwner(xdpy, copy_target, xwin, copytime); + + justcopied = 1; +} + +void windocopy(pdfapp_t *app) +{ + docopy(app, XA_PRIMARY); +} + +void onselreq(Window requestor, Atom selection, Atom target, Atom property, Time time) +{ + XEvent nevt; + + advance_scheduled = 0; + + if (property == None) + property = target; + + nevt.xselection.type = SelectionNotify; + nevt.xselection.send_event = True; + nevt.xselection.display = xdpy; + nevt.xselection.requestor = requestor; + nevt.xselection.selection = selection; + nevt.xselection.target = target; + nevt.xselection.property = property; + nevt.xselection.time = time; + + if (target == XA_TARGETS) + { + Atom atomlist[4]; + atomlist[0] = XA_TARGETS; + atomlist[1] = XA_TIMESTAMP; + atomlist[2] = XA_STRING; + atomlist[3] = XA_UTF8_STRING; + XChangeProperty(xdpy, requestor, property, target, + 32, PropModeReplace, + (unsigned char *)atomlist, sizeof(atomlist)/sizeof(Atom)); + } + + else if (target == XA_STRING) + { + XChangeProperty(xdpy, requestor, property, target, + 8, PropModeReplace, + (unsigned char *)copylatin1, strlen(copylatin1)); + } + + else if (target == XA_UTF8_STRING) + { + XChangeProperty(xdpy, requestor, property, target, + 8, PropModeReplace, + (unsigned char *)copyutf8, strlen(copyutf8)); + } + + else + { + nevt.xselection.property = None; + } + + XSendEvent(xdpy, requestor, False, 0, &nevt); +} + +void winreloadfile(pdfapp_t *app) +{ + pdfapp_close(app); + pdfapp_open(app, filename, 1); +} + +void winopenuri(pdfapp_t *app, char *buf) +{ + char *browser = getenv("BROWSER"); + if (!browser) + { +#ifdef __APPLE__ + browser = "open"; +#else + browser = "xdg-open"; +#endif + } + if (fork() == 0) + { + execlp(browser, browser, buf, (char*)0); + fprintf(stderr, "cannot exec '%s'\n", browser); + exit(0); + } +} + +static void onkey(int c) +{ + advance_scheduled = 0; + + if (justcopied) + { + justcopied = 0; + winrepaint(&gapp); + } + + if (!gapp.isediting && c == 'P') + { + showingpage = 1; + winrepaint(&gapp); + return; + } + + pdfapp_onkey(&gapp, c); +} + +static void onmouse(int x, int y, int btn, int modifiers, int state) +{ + if (state != 0) + advance_scheduled = 0; + + if (state != 0 && justcopied) + { + justcopied = 0; + winrepaint(&gapp); + } + + pdfapp_onmouse(&gapp, x, y, btn, modifiers, state); +} + +static void signal_handler(int signal) +{ + if (signal == SIGHUP) + reloading = 1; +} + +static void usage(void) +{ + fprintf(stderr, "usage: mupdf [options] file.pdf [page]\n"); + fprintf(stderr, "\t-b -\tset anti-aliasing quality in bits (0=off, 8=best)\n"); + fprintf(stderr, "\t-p -\tpassword\n"); + fprintf(stderr, "\t-r -\tresolution\n"); + exit(1); +} + +int main(int argc, char **argv) +{ + int c; + int len; + char buf[128]; + KeySym keysym; + int oldx = 0; + int oldy = 0; + int resolution = -1; + int pageno = 1; + fd_set fds; + int width = -1; + int height = -1; + fz_context *ctx; + struct timeval tmo_at; + struct timeval now; + struct timeval tmo; + struct timeval *timeout; + struct timeval tmo_advance_delay; + + ctx = fz_new_context(NULL, NULL, FZ_STORE_DEFAULT); + if (!ctx) + { + fprintf(stderr, "cannot initialise context\n"); + exit(1); + } + + while ((c = fz_getopt(argc, argv, "p:r:b:")) != -1) + { + switch (c) + { + case 'p': password = fz_optarg; break; + case 'r': resolution = atoi(fz_optarg); break; + case 'b': fz_set_aa_level(ctx, atoi(fz_optarg)); break; + default: usage(); + } + } + + if (argc - fz_optind == 0) + usage(); + + filename = argv[fz_optind++]; + + if (argc - fz_optind == 1) + pageno = atoi(argv[fz_optind++]); + + pdfapp_init(ctx, &gapp); + + winopen(); + + if (resolution == -1) + resolution = winresolution(); + if (resolution < MINRES) + resolution = MINRES; + if (resolution > MAXRES) + resolution = MAXRES; + + gapp.transitions_enabled = 1; + gapp.scrw = DisplayWidth(xdpy, xscr); + gapp.scrh = DisplayHeight(xdpy, xscr); + gapp.resolution = resolution; + gapp.pageno = pageno; + + pdfapp_open(&gapp, filename, 0); + + FD_ZERO(&fds); + + signal(SIGHUP, signal_handler); + + tmo_at.tv_sec = 0; + tmo_at.tv_usec = 0; + + while (!closing) + { + while (!closing && XPending(xdpy) && !transition_dirty) + { + XNextEvent(xdpy, &xevt); + + switch (xevt.type) + { + case Expose: + dirty = 1; + break; + + case ConfigureNotify: + if (gapp.image) + { + if (xevt.xconfigure.width != reqw || + xevt.xconfigure.height != reqh) + gapp.shrinkwrap = 0; + } + width = xevt.xconfigure.width; + height = xevt.xconfigure.height; + + break; + + case KeyPress: + len = XLookupString(&xevt.xkey, buf, sizeof buf, &keysym, NULL); + + if (!gapp.isediting) + switch (keysym) + { + case XK_Escape: + len = 1; buf[0] = '\033'; + break; + + case XK_Up: + len = 1; buf[0] = 'k'; + break; + case XK_Down: + len = 1; buf[0] = 'j'; + break; + + case XK_Left: + len = 1; buf[0] = 'b'; + break; + case XK_Right: + len = 1; buf[0] = ' '; + break; + + case XK_Page_Up: + len = 1; buf[0] = ','; + break; + case XK_Page_Down: + len = 1; buf[0] = '.'; + break; + } + if (xevt.xkey.state & ControlMask && keysym == XK_c) + docopy(&gapp, XA_CLIPBOARD); + else if (len) + onkey(buf[0]); + + onmouse(oldx, oldy, 0, 0, 0); + + break; + + case MotionNotify: + oldx = xevt.xmotion.x; + oldy = xevt.xmotion.y; + onmouse(xevt.xmotion.x, xevt.xmotion.y, 0, xevt.xmotion.state, 0); + break; + + case ButtonPress: + onmouse(xevt.xbutton.x, xevt.xbutton.y, xevt.xbutton.button, xevt.xbutton.state, 1); + break; + + case ButtonRelease: + copytime = xevt.xbutton.time; + onmouse(xevt.xbutton.x, xevt.xbutton.y, xevt.xbutton.button, xevt.xbutton.state, -1); + break; + + case SelectionRequest: + onselreq(xevt.xselectionrequest.requestor, + xevt.xselectionrequest.selection, + xevt.xselectionrequest.target, + xevt.xselectionrequest.property, + xevt.xselectionrequest.time); + break; + + case ClientMessage: + if (xevt.xclient.format == 32 && xevt.xclient.data.l[0] == WM_DELETE_WINDOW) + closing = 1; + break; + } + } + + if (closing) + continue; + + if (width != -1 || height != -1) + { + pdfapp_onresize(&gapp, width, height); + width = -1; + height = -1; + } + + if (dirty || dirtysearch) + { + if (dirty) + winblit(&gapp); + else if (dirtysearch) + winblitsearch(&gapp); + dirty = 0; + transition_dirty = 0; + dirtysearch = 0; + pdfapp_postblit(&gapp); + } + + if (showingpage && !tmo_at.tv_sec && !tmo_at.tv_usec) + { + tmo.tv_sec = 2; + tmo.tv_usec = 0; + + gettimeofday(&now, NULL); + timeradd(&now, &tmo, &tmo_at); + } + + if (XPending(xdpy) || transition_dirty) + continue; + + timeout = NULL; + + if (tmo_at.tv_sec || tmo_at.tv_usec) + { + gettimeofday(&now, NULL); + timersub(&tmo_at, &now, &tmo); + if (tmo.tv_sec <= 0) + { + tmo_at.tv_sec = 0; + tmo_at.tv_usec = 0; + timeout = NULL; + showingpage = 0; + winrepaint(&gapp); + } + else + timeout = &tmo; + } + + if (advance_scheduled) + { + gettimeofday(&now, NULL); + timersub(&tmo_advance, &now, &tmo_advance_delay); + if (tmo_advance_delay.tv_sec <= 0) + { + /* Too late already */ + onkey(' '); + onmouse(oldx, oldy, 0, 0, 0); + advance_scheduled = 0; + } + else if (timeout == NULL) + { + timeout = &tmo_advance_delay; + } + else + { + struct timeval tmp; + timersub(&tmo_advance_delay, timeout, &tmp); + if (tmp.tv_sec < 0) + { + timeout = &tmo_advance_delay; + } + } + } + + FD_SET(x11fd, &fds); + if (select(x11fd + 1, &fds, NULL, NULL, timeout) < 0) + { + if (reloading) + { + winreloadfile(&gapp); + reloading = 0; + } + } + if (!FD_ISSET(x11fd, &fds)) + { + if (timeout == &tmo_advance_delay) + { + onkey(' '); + onmouse(oldx, oldy, 0, 0, 0); + advance_scheduled = 0; + } + else + { + tmo_at.tv_sec = 0; + tmo_at.tv_usec = 0; + timeout = NULL; + showingpage = 0; + winrepaint(&gapp); + } + } + } + + cleanup(&gapp); + + return 0; +} |