From cc1fd2ff2a440a7fdb2a30ef66aba31043c4ba92 Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Wed, 1 Jun 2016 16:06:07 +0200 Subject: Add FZ_ENABLE_JS configuration define. --- source/pdf/js/pdf-js-none.c | 31 -- source/pdf/js/pdf-js.c | 718 ------------------------------------ source/pdf/js/pdf-util.js | 874 -------------------------------------------- source/pdf/pdf-js-util.js | 874 ++++++++++++++++++++++++++++++++++++++++++++ source/pdf/pdf-js.c | 734 +++++++++++++++++++++++++++++++++++++ source/tools/mutool.c | 2 + 6 files changed, 1610 insertions(+), 1623 deletions(-) delete mode 100644 source/pdf/js/pdf-js-none.c delete mode 100644 source/pdf/js/pdf-js.c delete mode 100644 source/pdf/js/pdf-util.js create mode 100644 source/pdf/pdf-js-util.js create mode 100644 source/pdf/pdf-js.c (limited to 'source') diff --git a/source/pdf/js/pdf-js-none.c b/source/pdf/js/pdf-js-none.c deleted file mode 100644 index 7cce12be..00000000 --- a/source/pdf/js/pdf-js-none.c +++ /dev/null @@ -1,31 +0,0 @@ -#include "mupdf/pdf.h" - -void pdf_enable_js(fz_context *ctx, pdf_document *doc) -{ -} - -void pdf_disable_js(fz_context *ctx, pdf_document *doc) -{ -} - -int pdf_js_supported(fz_context *ctx, pdf_document *doc) -{ - return 0; -} - -void pdf_js_setup_event(pdf_js *js, pdf_js_event *e) -{ -} - -pdf_js_event *pdf_js_get_event(pdf_js *js) -{ - return NULL; -} - -void pdf_js_execute(pdf_js *js, char *code) -{ -} - -void pdf_js_execute_count(pdf_js *js, char *code, int count) -{ -} diff --git a/source/pdf/js/pdf-js.c b/source/pdf/js/pdf-js.c deleted file mode 100644 index 57a0191b..00000000 --- a/source/pdf/js/pdf-js.c +++ /dev/null @@ -1,718 +0,0 @@ -#include "mupdf/pdf.h" -#include "mujs.h" - -struct pdf_js_s -{ - fz_context *ctx; - pdf_document *doc; - pdf_obj *form; - pdf_js_event event; - js_State *imp; -}; - -FZ_NORETURN static void rethrow(pdf_js *js) -{ - js_newerror(js->imp, fz_caught_message(js->ctx)); - js_throw(js->imp); -} - -/* Unpack argument object with named arguments into actual parameters. */ -static pdf_js *arguments(js_State *J, ...) -{ - if (js_isobject(J, 1)) - { - int i = 1; - va_list args; - - js_copy(J, 1); - - va_start(args, J); - for (;;) - { - const char *s = va_arg(args, const char *); - if (!s) - break; - js_getproperty(J, -1, s); - js_replace(J, i++); - } - va_end(args); - - js_pop(J, 1); - } - return js_getcontext(J); -} - -static char *pdf_from_utf8(fz_context *ctx, const char *utf8) -{ - char *pdf = fz_malloc(ctx, strlen(utf8)+1); - int i = 0; - unsigned char c; - - while ((c = *utf8) != 0) - { - if ((c & 0x80) == 0 && pdf_doc_encoding[c] == c) - { - pdf[i++] = c; - utf8++ ; - } - else - { - int rune; - int j; - - utf8 += fz_chartorune(&rune, utf8); - - for (j = 0; j < sizeof(pdf_doc_encoding) && pdf_doc_encoding[j] != rune; j++) - ; - - if (j < sizeof(pdf_doc_encoding)) - pdf[i++] = j; - } - } - - pdf[i] = 0; - - return pdf; -} - -static void app_alert(js_State *J) -{ - pdf_js *js = arguments(J, "cMsg", "nIcon", "nType", "cTitle", 0); - pdf_alert_event event; - - event.message = js_tostring(J, 1); - event.icon_type = js_tointeger(J, 2); - event.button_group_type = js_tointeger(J, 3); - event.title = js_tostring(J, 4); - - fz_try(js->ctx) - pdf_event_issue_alert(js->ctx, js->doc, &event); - fz_catch(js->ctx) - rethrow(js); - - js_pushnumber(J, event.button_pressed); -} - -static void app_execDialog(js_State *J) -{ - pdf_js *js = js_getcontext(J); - // monitor - // inheritDialog - // parentDoc - - fz_try(js->ctx) - pdf_event_issue_exec_dialog(js->ctx, js->doc); - fz_catch(js->ctx) - rethrow(js); - - // return "ok" or "cancel" - js_pushstring(J, "cancel"); -} - -static void app_execMenuItem(js_State *J) -{ - pdf_js *js = js_getcontext(J); - const char *cMenuItem = js_tostring(J, 1); - fz_try(js->ctx) - pdf_event_issue_exec_menu_item(js->ctx, js->doc, cMenuItem); - fz_catch(js->ctx) - rethrow(js); -} - -static void app_launchURL(js_State *J) -{ - pdf_js *js = js_getcontext(J); - const char *cUrl = js_tostring(J, 1); - int bNewFrame = js_toboolean(J, 1); - fz_try(js->ctx) - pdf_event_issue_launch_url(js->ctx, js->doc, cUrl, bNewFrame); - fz_catch(js->ctx) - rethrow(js); -} - -static void field_finalize(js_State *J, void *p) -{ - pdf_js *js = js_getcontext(J); - pdf_drop_obj(js->ctx, p); -} - -static void field_buttonSetCaption(js_State *J) -{ - pdf_js *js = js_getcontext(J); - pdf_obj *field = js_touserdata(J, 0, "Field"); - const char *cCaption = js_tostring(J, 1); - char *caption = pdf_from_utf8(js->ctx, cCaption); - fz_try(js->ctx) - pdf_field_set_button_caption(js->ctx, js->doc, field, caption); - fz_always(js->ctx) - fz_free(js->ctx, caption); - fz_catch(js->ctx) - rethrow(js); -} - -static void field_getName(js_State *J) -{ - pdf_js *js = js_getcontext(J); - pdf_obj *field = js_touserdata(J, 0, "Field"); - char *name; - fz_try(js->ctx) - name = pdf_field_name(js->ctx, js->doc, field); - fz_catch(js->ctx) - rethrow(js); - js_pushstring(J, name); /* to utf8? */ -} - -static void field_setName(js_State *J) -{ - pdf_js *js = js_getcontext(J); - fz_warn(js->ctx, "Unexpected call to field_setName"); -} - -static void field_getDisplay(js_State *J) -{ - pdf_js *js = js_getcontext(J); - pdf_obj *field = js_touserdata(J, 0, "Field"); - int display; - fz_try(js->ctx) - display = pdf_field_display(js->ctx, js->doc, field); - fz_catch(js->ctx) - rethrow(js); - js_pushnumber(J, display); -} - -static void field_setDisplay(js_State *J) -{ - pdf_js *js = js_getcontext(J); - pdf_obj *field = js_touserdata(J, 0, "Field"); - int display = js_tonumber(J, 1); - fz_try(js->ctx) - pdf_field_set_display(js->ctx, js->doc, field, display); - fz_catch(js->ctx) - rethrow(js); -} - -static pdf_obj *load_color(pdf_js *js, int idx) -{ - fz_context *ctx = js->ctx; - pdf_document *doc = js->doc; - js_State *J = js->imp; - - pdf_obj *color = NULL; - int i, n; - float c; - - n = js_getlength(J, idx); - - /* The only legitimate color expressed as an array of length 1 - * is [T], meaning transparent. Return a NULL object to represent - * transparent */ - if (n <= 1) - return NULL; - - fz_var(color); - - fz_try(ctx) - { - color = pdf_new_array(ctx, doc, n-1); - for (i = 0; i < n-1; i++) - { - js_getindex(J, idx, i+1); - c = js_tonumber(J, -1); - js_pop(J, 1); - - pdf_array_push_drop(ctx, color, pdf_new_real(ctx, doc, c)); - } - } - fz_catch(ctx) - { - pdf_drop_obj(ctx, color); - rethrow(js); - } - - return color; -} - -static void field_getFillColor(js_State *J) -{ - js_pushundefined(J); -} - -static void field_setFillColor(js_State *J) -{ - pdf_js *js = js_getcontext(J); - pdf_obj *field = js_touserdata(J, 0, "Field"); - pdf_obj *color = load_color(js, 1); - fz_try(js->ctx) - pdf_field_set_fill_color(js->ctx, js->doc, field, color); - fz_always(js->ctx) - pdf_drop_obj(js->ctx, color); - fz_catch(js->ctx) - rethrow(js); -} - -static void field_getTextColor(js_State *J) -{ - js_pushundefined(J); -} - -static void field_setTextColor(js_State *J) -{ - pdf_js *js = js_getcontext(J); - pdf_obj *field = js_touserdata(J, 0, "Field"); - pdf_obj *color = load_color(js, 1); - fz_try(js->ctx) - pdf_field_set_text_color(js->ctx, js->doc, field, color); - fz_always(js->ctx) - pdf_drop_obj(js->ctx, color); - fz_catch(js->ctx) - rethrow(js); -} - -static void field_getBorderStyle(js_State *J) -{ - pdf_js *js = js_getcontext(J); - pdf_obj *field = js_touserdata(J, 0, "Field"); - const char *border_style; - fz_try(js->ctx) - border_style = pdf_field_border_style(js->ctx, js->doc, field); - fz_catch(js->ctx) - rethrow(js); - js_pushstring(J, border_style); -} - -static void field_setBorderStyle(js_State *J) -{ - pdf_js *js = js_getcontext(J); - pdf_obj *field = js_touserdata(J, 0, "Field"); - const char *border_style = js_tostring(J, 1); - fz_try(js->ctx) - pdf_field_set_border_style(js->ctx, js->doc, field, border_style); - fz_catch(js->ctx) - rethrow(js); -} - -static void field_getValue(js_State *J) -{ - pdf_js *js = js_getcontext(J); - pdf_obj *field = js_touserdata(J, 0, "Field"); - char *val; - - fz_try(js->ctx) - val = pdf_field_value(js->ctx, js->doc, field); - fz_catch(js->ctx) - rethrow(js); - - js_pushstring(J, val ? val : ""); - - fz_free(js->ctx, val); -} - -static void field_setValue(js_State *J) -{ - pdf_js *js = js_getcontext(J); - pdf_obj *field = js_touserdata(J, 0, "Field"); - const char *value = js_tostring(J, 1); - - fz_try(js->ctx) - (void)pdf_field_set_value(js->ctx, js->doc, field, value); - fz_catch(js->ctx) - rethrow(js); -} - -static void event_getTarget(js_State *J) -{ - pdf_js *js = js_getcontext(J); - js_getregistry(J, "Field"); - js_newuserdata(J, "Field", pdf_keep_obj(js->ctx, js->event.target), field_finalize); -} - -static void event_setTarget(js_State *J) -{ - pdf_js *js = js_getcontext(J); - fz_warn(js->ctx, "Unexpected call to event_setTarget"); -} - -static void event_getValue(js_State *J) -{ - pdf_js *js = js_getcontext(J); - const char *v = js->event.value; - js_pushstring(J, v ? v : ""); -} - -static void event_setValue(js_State *J) -{ - pdf_js *js = js_getcontext(J); - const char *value = js_tostring(J, 1); - fz_free(js->ctx, js->event.value); - js->event.value = fz_strdup(js->ctx, value); -} - -static void event_getWillCommit(js_State *J) -{ - js_pushnumber(J, 1); -} - -static void event_setWillCommit(js_State *J) -{ - pdf_js *js = js_getcontext(J); - fz_warn(js->ctx, "Unexpected call to event_setWillCommit"); -} - -static void event_getRC(js_State *J) -{ - pdf_js *js = js_getcontext(J); - js_pushnumber(J, js->event.rc); -} - -static void event_setRC(js_State *J) -{ - pdf_js *js = js_getcontext(J); - js->event.rc = js_tointeger(js->imp, 1); -} - -static void doc_getField(js_State *J) -{ - pdf_js *js = js_getcontext(J); - fz_context *ctx = js->ctx; - const char *cName = js_tostring(J, 1); - char *name = pdf_from_utf8(ctx, cName); - pdf_obj *dict; - - fz_try(ctx) - dict = pdf_lookup_field(ctx, js->form, name); - fz_always(ctx) - fz_free(ctx, name); - fz_catch(ctx) - rethrow(js); - - if (dict) - { - js_getregistry(J, "Field"); - js_newuserdata(J, "Field", pdf_keep_obj(js->ctx, dict), field_finalize); - } - else - { - js_pushnull(J); - } -} - -static void reset_field(pdf_js *js, const char *cName) -{ - fz_context *ctx = js->ctx; - if (cName) - { - char *name = pdf_from_utf8(ctx, cName); - fz_try(ctx) - { - pdf_obj *field = js_touserdata(js->imp, 0, "Field"); - if (field) - pdf_field_reset(ctx, js->doc, field); - } - fz_always(ctx) - fz_free(ctx, name); - fz_catch(ctx) - rethrow(js); - } -} - -static void doc_resetForm(js_State *J) -{ - pdf_js *js = js_getcontext(J); - fz_context *ctx = js->ctx; - int i, n; - - /* An array of fields has been passed in. Call pdf_reset_field on each item. */ - if (js_isarray(J, 1)) - { - n = js_getlength(J, 1); - for (i = 0; i < n; ++i) - { - js_getindex(J, 1, i); - reset_field(js, js_tostring(J, -1)); - js_pop(J, 1); - } - } - - /* No argument or null passed in means reset all. */ - else - { - n = pdf_array_len(ctx, js->form); - for (i = 0; i < n; i++) - { - fz_try(ctx) - pdf_field_reset(ctx, js->doc, pdf_array_get(ctx, js->form, i)); - fz_catch(ctx) - rethrow(js); - } - } -} - -static void doc_print(js_State *J) -{ - pdf_js *js = js_getcontext(J); - fz_try(js->ctx) - pdf_event_issue_print(js->ctx, js->doc); - fz_catch(js->ctx) - rethrow(js); -} - -static void doc_mailDoc(js_State *J) -{ - pdf_js *js = arguments(J, "bUI", "cTo", "cCc", "cBcc", "cSubject", "cMessage", 0); - pdf_mail_doc_event event; - - event.ask_user = js_isdefined(J, 1) ? js_toboolean(J, 1) : 1; - event.to = js_tostring(J, 2); - event.cc = js_tostring(J, 3); - event.bcc = js_tostring(J, 4); - event.subject = js_tostring(J, 5); - event.message = js_tostring(J, 6); - - fz_try(js->ctx) - pdf_event_issue_mail_doc(js->ctx, js->doc, &event); - fz_catch(js->ctx) - rethrow(js); -} - -static void addmethod(js_State *J, const char *name, js_CFunction fun, int n) -{ - const char *realname = strchr(name, '.'); - realname = realname ? realname + 1 : name; - js_newcfunction(J, fun, name, n); - js_defproperty(J, -2, realname, JS_READONLY | JS_DONTENUM | JS_DONTCONF); -} - -static void addproperty(js_State *J, const char *name, js_CFunction getfun, js_CFunction setfun) -{ - const char *realname = strchr(name, '.'); - realname = realname ? realname + 1 : name; - js_newcfunction(J, getfun, name, 0); - js_newcfunction(J, setfun, name, 1); - js_defaccessor(J, -3, realname, JS_READONLY | JS_DONTENUM | JS_DONTCONF); -} - -static void declare_dom(pdf_js *js) -{ - js_State *J = js->imp; - - /* Allow access to the global environment via the 'global' name */ - js_pushglobal(J); - js_defglobal(J, "global", JS_READONLY | JS_DONTCONF | JS_DONTENUM); - - /* Create the 'app' object */ - js_newobject(J); - { - addmethod(J, "app.alert", app_alert, 4); - addmethod(J, "app.execDialog", app_execDialog, 0); - addmethod(J, "app.execMenuItem", app_execMenuItem, 1); - addmethod(J, "app.launchURL", app_launchURL, 2); - } - js_defglobal(J, "app", JS_READONLY | JS_DONTCONF | JS_DONTENUM); - - /* Create the 'event' object */ - js_newobject(J); - { - addproperty(J, "event.target", event_getTarget, event_setTarget); - addproperty(J, "event.value", event_getValue, event_setValue); - addproperty(J, "event.willCommit", event_getWillCommit, event_setWillCommit); - addproperty(J, "event.rc", event_getRC, event_setRC); - } - js_defglobal(J, "event", JS_READONLY | JS_DONTCONF | JS_DONTENUM); - - /* Create the Field prototype object */ - js_newobject(J); - { - addproperty(J, "Field.value", field_getValue, field_setValue); - addproperty(J, "Field.borderStyle", field_getBorderStyle, field_setBorderStyle); - addproperty(J, "Field.textColor", field_getTextColor, field_setTextColor); - addproperty(J, "Field.fillColor", field_getFillColor, field_setFillColor); - addproperty(J, "Field.display", field_getDisplay, field_setDisplay); - addproperty(J, "Field.name", field_getName, field_setName); - addmethod(J, "Field.buttonSetCaption", field_buttonSetCaption, 1); - } - js_setregistry(J, "Field"); - - /* Create the Doc prototype object */ - js_newobject(J); - { - addmethod(J, "Doc.getField", doc_getField, 1); - addmethod(J, "Doc.resetForm", doc_resetForm, 0); - addmethod(J, "Doc.print", doc_print, 0); - addmethod(J, "Doc.mailDoc", doc_mailDoc, 6); - } - js_setregistry(J, "Doc"); - - js_getregistry(J, "Doc"); - js_setglobal(J, "MuPDF_Doc"); /* for pdf-util.js use */ -} - -static void preload_helpers(pdf_js *js) -{ - /* When testing on the cluster: - * Use a fixed date for "new Date" and Date.now(). - * Sadly, this breaks uses of the Date function without the new keyword. - * Return a fixed number from Math.random(). - */ -#ifdef CLUSTER - js_dostring(js->imp, -"var MuPDFOldDate = Date\n" -"Date = function() { return new MuPDFOldDate(298252800000); }\n" -"Date.now = function() { return 298252800000; }\n" -"Date.UTC = function() { return 298252800000; }\n" -"Date.parse = MuPDFOldDate.parse;\n" -"Math.random = function() { return 1/4; }\n" - ); -#endif - - js_dostring(js->imp, -#include "gen_js_util.h" - ); -} - -void pdf_drop_js(fz_context *ctx, pdf_js *js) -{ - if (js) - { - js_freestate(js->imp); - fz_free(ctx, js->event.value); - fz_free(ctx, js); - } -} - -static void *pdf_js_alloc(void *actx, void *ptr, int n) -{ - fz_context *ctx = actx; - if (n == 0) { - fz_free(ctx, ptr); - return NULL; - } - if (ptr) - return fz_resize_array(ctx, ptr, n, 1); - return fz_malloc_array(ctx, n, 1); -} - -static pdf_js *pdf_new_js(fz_context *ctx, pdf_document *doc) -{ - pdf_js *js = fz_malloc_struct(ctx, pdf_js); - - js->ctx = ctx; - js->doc = doc; - - fz_try(ctx) - { - pdf_obj *root, *acroform; - - /* Find the form array */ - root = pdf_dict_get(ctx, pdf_trailer(ctx, doc), PDF_NAME_Root); - acroform = pdf_dict_get(ctx, root, PDF_NAME_AcroForm); - js->form = pdf_dict_get(ctx, acroform, PDF_NAME_Fields); - - /* Initialise the javascript engine, passing the fz_context for use in memory allocation. */ - js->imp = js_newstate(pdf_js_alloc, ctx, 0); - if (!js->imp) - fz_throw(ctx, FZ_ERROR_GENERIC, "cannot initialize javascript engine"); - - /* Also set our pdf_js context, so we can retrieve it in callbacks. */ - js_setcontext(js->imp, js); - - declare_dom(js); - preload_helpers(js); - } - fz_catch(ctx) - { - pdf_drop_js(ctx, js); - js = NULL; - } - - return js; -} - -static void pdf_js_load_document_level(pdf_js *js) -{ - fz_context *ctx = js->ctx; - pdf_document *doc = js->doc; - pdf_obj *javascript; - int len, i; - - javascript = pdf_load_name_tree(ctx, doc, PDF_NAME_JavaScript); - len = pdf_dict_len(ctx, javascript); - - fz_try(ctx) - { - for (i = 0; i < len; i++) - { - pdf_obj *fragment = pdf_dict_get_val(ctx, javascript, i); - pdf_obj *code = pdf_dict_get(ctx, fragment, PDF_NAME_JS); - char *codebuf = pdf_to_utf8(ctx, doc, code); - pdf_js_execute(js, codebuf); - fz_free(ctx, codebuf); - } - } - fz_always(ctx) - pdf_drop_obj(ctx, javascript); - fz_catch(ctx) - fz_rethrow(ctx); -} - -void pdf_js_setup_event(pdf_js *js, pdf_js_event *e) -{ - if (js) - { - fz_context *ctx = js->ctx; - char *ev = e->value ? e->value : ""; - char *v = fz_strdup(ctx, ev); - - fz_free(ctx, js->event.value); - js->event.value = v; - - js->event.target = e->target; - js->event.rc = 1; - } -} - -pdf_js_event *pdf_js_get_event(pdf_js *js) -{ - return js ? &js->event : NULL; -} - -void pdf_js_execute(pdf_js *js, char *source) -{ - if (js) - { - if (js_ploadstring(js->imp, "[pdf]", source)) - { - fz_warn(js->ctx, "%s", js_tostring(js->imp, -1)); - js_pop(js->imp, 1); - return; - } - js_getregistry(js->imp, "Doc"); /* set 'this' to the Doc object */ - if (js_pcall(js->imp, 0)) - { - fz_warn(js->ctx, "%s", js_tostring(js->imp, -1)); - js_pop(js->imp, 1); - return; - } - js_pop(js->imp, 1); - } -} - -void pdf_enable_js(fz_context *ctx, pdf_document *doc) -{ - if (!doc->js) - { - doc->js = pdf_new_js(ctx, doc); - pdf_js_load_document_level(doc->js); - } -} - -void pdf_disable_js(fz_context *ctx, pdf_document *doc) -{ - if (doc->js) - pdf_drop_js(ctx, doc->js); - doc->js = NULL; -} - -int pdf_js_supported(fz_context *ctx, pdf_document *doc) -{ - return doc->js != NULL; -} diff --git a/source/pdf/js/pdf-util.js b/source/pdf/js/pdf-util.js deleted file mode 100644 index 299f80ac..00000000 --- a/source/pdf/js/pdf-util.js +++ /dev/null @@ -1,874 +0,0 @@ -var MuPDF = { - monthName: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], - shortMonthName: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], - monthPattern: /Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec/, - dayName: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], - - padZeros: function(num, places) { - var s = String(num) - while (s.length < places) - s = '0' + s - return s; - }, - - convertCase: function(str, cmd) { - switch (cmd) { - case '>': return str.toUpperCase(); - case '<': return str.toLowerCase(); - default: return str; - } - }, -}; - -/* display must be kept in sync with an enum in pdf_form.c */ -var display = { - visible: 0, - hidden: 1, - noPrint: 2, - noView: 3, -}; - -var border = { - s: "Solid", - d: "Dashed", - b: "Beveled", - i: "Inset", - u: "Underline", -}; - -var color = { - transparent: [ "T" ], - black: [ "G", 0 ], - white: [ "G", 1 ], - red: [ "RGB", 1,0,0 ], - green: [ "RGB", 0,1,0 ], - blue: [ "RGB", 0,0,1 ], - cyan: [ "CMYK", 1,0,0,0 ], - magenta: [ "CMYK", 0,1,0,0 ], - yellow: [ "CMYK", 0,0,1,0 ], - dkGray: [ "G", 0.25 ], - gray: [ "G", 0.5 ], - ltGray: [ "G", 0.75 ], -}; - -var util = {}; - -util.printd = function(fmt, d) -{ - var regexp = /(m+|d+|y+|H+|h+|M+|s+|t+|[^mdyHhMst]+)/g; - var res = ''; - var i; - - if (!d) - return null; - - var tokens = fmt.match(regexp); - var length = tokens ? tokens.length : 0; - - for (i = 0; i < length; i++) - { - switch(tokens[i]) - { - case 'mmmm': res += MuPDF.monthName[d.getMonth()]; break; - case 'mmm': res += MuPDF.monthName[d.getMonth()].substring(0,3); break; - case 'mm': res += MuPDF.padZeros(d.getMonth()+1, 2); break; - case 'm': res += d.getMonth()+1; break; - case 'dddd': res += MuPDF.dayName[d.getDay()]; break; - case 'ddd': res += MuPDF.dayName[d.getDay()].substring(0,3); break; - case 'dd': res += MuPDF.padZeros(d.getDate(), 2); break; - case 'd': res += d.getDate(); break; - case 'yyyy': res += d.getFullYear(); break; - case 'yy': res += d.getFullYear()%100; break; - case 'HH': res += MuPDF.padZeros(d.getHours(), 2); break; - case 'H': res += d.getHours(); break; - case 'hh': res += MuPDF.padZeros((d.getHours()+11)%12+1, 2); break; - case 'h': res += (d.getHours()+11)%12+1; break; - case 'MM': res += MuPDF.padZeros(d.getMinutes(), 2); break; - case 'M': res += d.getMinutes(); break; - case 'ss': res += MuPDF.padZeros(d.getSeconds(), 2); break; - case 's': res += d.getSeconds(); break; - case 'tt': res += d.getHours() < 12 ? 'am' : 'pm'; break; - case 't': res += d.getHours() < 12 ? 'a' : 'p'; break; - default: res += tokens[i]; - } - } - - return res; -} - -util.printx = function(fmt, val) -{ - var cs = '='; - var res = ''; - var i = 0; - var m; - var length = fmt ? fmt.length : 0; - - while (i < length) - { - switch (fmt.charAt(i)) - { - case '\\': - i++; - if (i >= length) return res; - res += fmt.charAt(i); - break; - - case 'X': - m = val.match(/\w/); - if (!m) return res; - res += MuPDF.convertCase(m[0],cs); - val = val.replace(/^\W*\w/,''); - break; - - case 'A': - m = val.match(/[A-Za-z]/); - if (!m) return res; - res += MuPDF.convertCase(m[0],cs); - val = val.replace(/^[^A-Za-z]*[A-Za-z]/,''); - break; - - case '9': - m = val.match(/\d/); - if (!m) return res; - res += m[0]; - val = val.replace(/^\D*\d/,''); - break; - - case '*': - res += val; - val = ''; - break; - - case '?': - if (!val) return res; - res += MuPDF.convertCase(val.charAt(0),cs); - val = val.substring(1); - break; - - case '=': - case '>': - case '<': - cs = fmt.charAt(i); - break; - - default: - res += MuPDF.convertCase(fmt.charAt(i),cs); - break; - } - - i++; - } - - return res; -} - -util.printf = function() -{ - var i; - - if (arguments.length < 1) - return ""; - - var res = ""; - var arg_i = 1; - var regexp = /%[^dfsx]*[dfsx]|[^%]*/g; - var tokens = arguments[0].match(regexp); - var length = tokens ? tokens.length : 0; - - for (i = 0; i < length; i++) - { - var tok = tokens[i]; - if (tok.match(/^%/)) - { - if (arg_i < arguments.length) - { - var val = arguments[arg_i++]; - var fval = ''; - var neg = false; - var decsep_re = /^,[0123]/; - var flags_re = /^[+ 0#]+/; - var width_re = /^\d+/; - var prec_re = /^\.\d+/; - var conv_re = /^[dfsx]/; - - tok = tok.replace(/^%/, ""); - var decsep = tok.match(decsep_re); - if (decsep) decsep = decsep[0]; - tok = tok.replace(decsep_re, ""); - var flags = tok.match(flags_re); - if (flags) flags = flags[0]; - tok = tok.replace(flags_re, ""); - var width = tok.match(width_re); - if (width) width = width[0]; - tok = tok.replace(width_re, ""); - var prec = tok.match(prec_re); - if (prec) prec = prec[0]; - tok = tok.replace(prec_re, ""); - var conv = tok.match(conv_re); - if (conv) conv = conv[0]; - - prec = prec ? Number(prec.replace(/^\./, '')) : 0; - var poschar = (flags && flags.match(/[+ ]/)) ? flags.match(/[+ ]/)[0] : ''; - var pad = (flags && flags.match(/0/)) ? '0' : ' '; - - var point = '.'; - var thou = ''; - - if (decsep) - { - switch(decsep) - { - case ',0': thou = ','; break; - case ',1': break; - case ',2': thou = '.'; point = ','; break; - case ',3': point = ','; break; - } - } - - switch(conv) - { - case 'x': - val = Math.floor(val); - neg = (val < 0); - if (neg) - val = -val; - - // Convert to hex - while (val) - { - fval = '0123456789ABCDEF'.charAt(val%16) + fval; - val = Math.floor(val/16); - } - - if (neg) - fval = '-' + fval; - else - fval = poschar + fval; - break; - - case 'd': - fval = String(Math.floor(val)); - break; - - case 's': - // Always pad strings with space - pad = ' '; - fval = String(val); - break; - - case 'f': - fval = String(val); - - if (prec) - { - var frac = fval.match(/\.\d+/); - if (frac) - { - frac = frac[0]; - // Matched string includes the dot, so make it - // prec+1 in length - if (frac.length > prec+1) - frac = frac.substring(0, prec+1); - else if(frac.length < prec+1) - frac += new Array(prec+1-frac.length+1).join('0'); - - fval = fval.replace(/\.\d+/, frac); - } - } - break; - } - - if (conv.match(/[fd]/)) - { - if (fval >= 0) - fval = poschar + fval; - - if (point !== '.') - fval.replace(/\./, point); - - if (thou) - { - var intpart = fval.match(/\d+/)[0]; - intpart = new Array(2-(intpart.length+2)%3+1).join('0') + intpart; - intpart = intpart.match(/.../g).join(thou).replace(/^0*[,.]?/,''); - fval = fval.replace(/\d+/, intpart); - } - } - - if (width && fval.length < width) - fval = new Array(width - fval.length + 1).join(pad) + fval; - - res += fval; - } - } - else - { - res += tok; - } - } - - return res; -} - -function AFMergeChange(event) -{ - return event.value; -} - -function AFMakeNumber(str) -{ - var nums = str.match(/\d+/g); - - if (!nums) - return null; - - var res = nums.join('.'); - - if (str.match(/^[^0-9]*\./)) - res = '0.'+res; - - return res * (str.match(/-/) ? -1.0 : 1.0); -} - -function AFExtractTime(dt) -{ - var ampm = dt.match(/(am|pm)/); - dt = dt.replace(/(am|pm)/, ''); - var t = dt.match(/\d{1,2}:\d{1,2}:\d{1,2}/); - dt = dt.replace(/\d{1,2}:\d{1,2}:\d{1,2}/, ''); - if (!t) - { - t = dt.match(/\d{1,2}:\d{1,2}/); - dt = dt.replace(/\d{1,2}:\d{1,2}/, ''); - } - - return [dt, t?t[0]+(ampm?ampm[0]:''):'']; -} - -function AFParseDateOrder(fmt) -{ - var i; - var order = ''; - - // Ensure all present with those not added in default order - fmt += "mdy"; - - for (i = 0; i < fmt.length; i++) - { - var c = fmt.charAt(i); - - if ('ymd'.indexOf(c) !== -1 && order.indexOf(c) === -1) - order += c; - } - - return order; -} - -function AFMatchMonth(d) -{ - var m = d.match(MuPDF.monthPattern); - - return m ? MuPDF.shortMonthName.indexOf(m[0]) : null; -} - -function AFParseTime(str, d) -{ - if (!str) - return d; - - if (!d) - d = new Date(); - - var ampm = str.match(/(am|pm)/); - var nums = str.match(/\d+/g); - var hour, min, sec; - - if (!nums) - return null; - - sec = 0; - - switch (nums.length) - { - case 3: - sec = parseInt(nums[2]); - case 2: - hour = parseInt(nums[0]); - min = parseInt(nums[1]); - break; - - default: - return null; - } - - ampm = ampm && ampm[0] - - if (ampm === 'am' && hour < 12) - hour = 12 + hour; - if (ampm === 'pm' && hour >= 12) - hour = 0 + hour - 12; - - d.setHours(hour, min, sec); - - if (d.getHours() !== hour || d.getMinutes() !== min || d.getSeconds() !== sec) - return null; - - return d; -} - -function AFParseDateEx(d, fmt) -{ - var i; - var dt = AFExtractTime(d); - var nums = dt[0].match(/\d+/g); - var order = AFParseDateOrder(fmt); - var text_month = AFMatchMonth(dt[0]); - var dout = new Date(); - var year = dout.getFullYear(); - var month = dout.getMonth(); - var date = dout.getDate(); - - dout.setHours(12,0,0); - - if (!nums || nums.length < 1 || nums.length > 3) - return null; - - if (nums.length < 3 && text_month) - { - // Use the text month rather than one of the numbers - month = text_month; - order = order.replace('m',''); - } - - order = order.substring(0, nums.length); - - // If year and month specified but not date then use the 1st - if (order === "ym" || (order === "y" && text_month)) - date = 1; - - for (i = 0; i < nums.length; i++) - { - switch (order.charAt(i)) - { - case 'y': year = parseInt(nums[i]); break; - case 'm': month = parseInt(nums[i]) - 1; break; - case 'd': date = parseInt(nums[i]); break; - } - } - - if (year < 100) - { - if (fmt.search("yyyy") !== -1) - return null; - - if (year >= 50) - year = 1900 + year; - else if (year >= 0) - year = 2000 + year; - } - - dout.setFullYear(year, month, date); - - if (dout.getFullYear() !== year || dout.getMonth() !== month || dout.getDate() !== date) - return null; - - return AFParseTime(dt[1], dout); -} - -function AFDate_KeystrokeEx(fmt) -{ - if (event.willCommit && !AFParseDateEx(event.value, fmt)) - { - app.alert("The date/time entered ("+event.value+") does not match the format ("+fmt+") of the field [ "+event.target.name+" ]"); - event.rc = false; - } -} - -function AFDate_Keystroke(index) -{ - var formats = ['m/d','m/d/yy','mm/dd/yy','mm/yy','d-mmm','d-mmm-yy','dd-mm-yy','yy-mm-dd', - 'mmm-yy','mmmm-yy','mmm d, yyyy','mmmm d, yyyy','m/d/yy h:MM tt','m/d/yy HH:MM']; - AFDate_KeystrokeEx(formats[index]); -} - -function AFDate_FormatEx(fmt) -{ - var d = AFParseDateEx(event.value, fmt); - - event.value = d ? util.printd(fmt, d) : ""; -} - -function AFDate_Format(index) -{ - var formats = ['m/d','m/d/yy','mm/dd/yy','mm/yy','d-mmm','d-mmm-yy','dd-mm-yy','yy-mm-dd', - 'mmm-yy','mmmm-yy','mmm d, yyyy','mmmm d, yyyy','m/d/yy h:MM tt','m/d/yy HH:MM']; - AFDate_FormatEx(formats[index]); -} - -function AFTime_Keystroke(index) -{ - if (event.willCommit && !AFParseTime(event.value, null)) - { - app.alert("The value entered ("+event.value+") does not match the format of the field [ "+event.target.name+" ]"); - event.rc = false; - } -} - -function AFTime_FormatEx(fmt) -{ - var d = AFParseTime(event.value, null); - - event.value = d ? util.printd(fmt, d) : ''; -} - -function AFTime_Format(index) -{ - var formats = ['HH:MM','h:MM tt','HH:MM:ss','h:MM:ss tt']; - - AFTime_FormatEx(formats[index]); -} - -function AFSpecial_KeystrokeEx(fmt) -{ - var cs = '='; - var val = event.value; - var res = ''; - var i = 0; - var m; - var length = fmt ? fmt.length : 0; - - while (i < length) - { - switch (fmt.charAt(i)) - { - case '\\': - i++; - if (i >= length) - break; - res += fmt.charAt(i); - if (val && val.charAt(0) === fmt.charAt(i)) - val = val.substring(1); - break; - - case 'X': - m = val.match(/^\w/); - if (!m) - { - event.rc = false; - break; - } - res += MuPDF.convertCase(m[0],cs); - val = val.substring(1); - break; - - case 'A': - m = val.match(/^[A-Za-z]/); - if (!m) - { - event.rc = false; - break; - } - res += MuPDF.convertCase(m[0],cs); - val = val.substring(1); - break; - - case '9': - m = val.match(/^\d/); - if (!m) - { - event.rc = false; - break; - } - res += m[0]; - val = val.substring(1); - break; - - case '*': - res += val; - val = ''; - break; - - case '?': - if (!val) - { - event.rc = false; - break; - } - res += MuPDF.convertCase(val.charAt(0),cs); - val = val.substring(1); - break; - - case '=': - case '>': - case '<': - cs = fmt.charAt(i); - break; - - default: - res += fmt.charAt(i); - if (val && val.charAt(0) === fmt.charAt(i)) - val = val.substring(1); - break; - } - - i++; - } - - if (event.rc) - event.value = res; - else if (event.willCommit) - app.alert("The value entered ("+event.value+") does not match the format of the field [ "+event.target.name+" ] should be "+fmt); -} - -function AFSpecial_Keystroke(index) -{ - if (event.willCommit) - { - switch (index) - { - case 0: - if (!event.value.match(/^\d{5}$/)) - event.rc = false; - break; - case 1: - if (!event.value.match(/^\d{5}[-. ]?\d{4}$/)) - event.rc = false; - break; - case 2: - if (!event.value.match(/^((\(\d{3}\)|\d{3})[-. ]?)?\d{3}[-. ]?\d{4}$/)) - event.rc = false; - break; - case 3: - if (!event.value.match(/^\d{3}[-. ]?\d{2}[-. ]?\d{4}$/)) - event.rc = false; - break; - } - - if (!event.rc) - app.alert("The value entered ("+event.value+") does not match the format of the field [ "+event.target.name+" ]"); - } -} - -function AFSpecial_Format(index) -{ - var res; - - switch (index) - { - case 0: - res = util.printx('99999', event.value); - break; - case 1: - res = util.printx('99999-9999', event.value); - break; - case 2: - res = util.printx('9999999999', event.value); - res = util.printx(res.length >= 10 ? '(999) 999-9999' : '999-9999', event.value); - break; - case 3: - res = util.printx('999-99-9999', event.value); - break; - } - - event.value = res ? res : ''; -} - -function AFNumber_Keystroke(nDec, sepStyle, negStyle, currStyle, strCurrency, bCurrencyPrepend) -{ - if (sepStyle & 2) - { - if (!event.value.match(/^[+-]?\d*[,.]?\d*$/)) - event.rc = false; - } - else - { - if (!event.value.match(/^[+-]?\d*\.?\d*$/)) - event.rc = false; - } - - if (event.willCommit) - { - if (!event.value.match(/\d/)) - event.rc = false; - - if (!event.rc) - app.alert("The value entered ("+event.value+") does not match the format of the field [ "+event.target.name+" ]"); - } -} - -function AFNumber_Format(nDec,sepStyle,negStyle,currStyle,strCurrency,bCurrencyPrepend) -{ - var val = event.value; - var fracpart; - var intpart; - var point = sepStyle&2 ? ',' : '.'; - var separator = sepStyle&2 ? '.' : ','; - - if (/^\D*\./.test(val)) - val = '0'+val; - - var groups = val.match(/\d+/g); - - if (!groups) - return; - - switch (groups.length) - { - case 0: - return; - case 1: - fracpart = ''; - intpart = groups[0]; - break; - default: - fracpart = groups.pop(); - intpart = groups.join(''); - break; - } - - // Remove leading zeros - intpart = intpart.replace(/^0*/,''); - if (!intpart) - intpart = '0'; - - if ((sepStyle & 1) === 0) - { - // Add the thousands sepearators: pad to length multiple of 3 with zeros, - // split into 3s, join with separator, and remove the leading zeros - intpart = new Array(2-(intpart.length+2)%3+1).join('0') + intpart; - intpart = intpart.match(/.../g).join(separator).replace(/^0*/,''); - } - - if (!intpart) - intpart = '0'; - - // Adjust fractional part to correct number of decimal places - fracpart += new Array(nDec+1).join('0'); - fracpart = fracpart.substring(0,nDec); - - if (fracpart) - intpart += point+fracpart; - - if (bCurrencyPrepend) - intpart = strCurrency+intpart; - else - intpart += strCurrency; - - if (/-/.test(val)) - { - switch (negStyle) - { - case 0: - intpart = '-'+intpart; - break; - case 1: - break; - case 2: - case 3: - intpart = '('+intpart+')'; - break; - } - } - - if (negStyle&1) - event.target.textColor = /-/.test(val) ? color.red : color.black; - - event.value = intpart; -} - -function AFPercent_Keystroke(nDec, sepStyle) -{ - AFNumber_Keystroke(nDec, sepStyle, 0, 0, "", true); -} - -function AFPercent_Format(nDec, sepStyle) -{ - var val = AFMakeNumber(event.value); - - if (!val) - { - event.value = ''; - return; - } - - event.value = (val * 100) + ''; - - AFNumber_Format(nDec, sepStyle, 0, 0, "%", false); -} - -function AFSimple_Calculate(op, list) -{ - var i, res; - - switch (op) - { - case 'SUM': - res = 0; - break; - case 'PRD': - res = 1; - break; - case 'AVG': - res = 0; - break; - } - - if (typeof list === 'string') - list = list.split(/ *, */); - - for (i = 0; i < list.length; i++) - { - var field = MuPDF_Doc.getField(list[i]); - var value = Number(field.value); - - switch (op) - { - case 'SUM': - res += value; - break; - case 'PRD': - res *= value; - break; - case 'AVG': - res += value; - break; - case 'MIN': - if (i === 0 || value < res) - res = value; - break; - case 'MAX': - if (i === 0 || value > res) - res = value; - break; - } - } - - if (op === 'AVG') - res /= list.length; - - event.value = res; -} - -function AFRange_Validate(lowerCheck, lowerLimit, upperCheck, upperLimit) -{ - if (upperCheck && event.value > upperLimit) - { - event.rc = false; - } - - if (lowerCheck && event.value < lowerLimit) - { - event.rc = false; - } - - if (!event.rc) - { - if (lowerCheck && upperCheck) - app.alert(util.printf("The entered value ("+event.value+") must be greater than or equal to %s and less than or equal to %s", lowerLimit, upperLimit)); - else if (lowerCheck) - app.alert(util.printf("The entered value ("+event.value+") must be greater than or equal to %s", lowerLimit)); - else - app.alert(util.printf("The entered value ("+event.value+") must be less than or equal to %s", upperLimit)); - } -} diff --git a/source/pdf/pdf-js-util.js b/source/pdf/pdf-js-util.js new file mode 100644 index 00000000..299f80ac --- /dev/null +++ b/source/pdf/pdf-js-util.js @@ -0,0 +1,874 @@ +var MuPDF = { + monthName: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + shortMonthName: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + monthPattern: /Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec/, + dayName: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], + + padZeros: function(num, places) { + var s = String(num) + while (s.length < places) + s = '0' + s + return s; + }, + + convertCase: function(str, cmd) { + switch (cmd) { + case '>': return str.toUpperCase(); + case '<': return str.toLowerCase(); + default: return str; + } + }, +}; + +/* display must be kept in sync with an enum in pdf_form.c */ +var display = { + visible: 0, + hidden: 1, + noPrint: 2, + noView: 3, +}; + +var border = { + s: "Solid", + d: "Dashed", + b: "Beveled", + i: "Inset", + u: "Underline", +}; + +var color = { + transparent: [ "T" ], + black: [ "G", 0 ], + white: [ "G", 1 ], + red: [ "RGB", 1,0,0 ], + green: [ "RGB", 0,1,0 ], + blue: [ "RGB", 0,0,1 ], + cyan: [ "CMYK", 1,0,0,0 ], + magenta: [ "CMYK", 0,1,0,0 ], + yellow: [ "CMYK", 0,0,1,0 ], + dkGray: [ "G", 0.25 ], + gray: [ "G", 0.5 ], + ltGray: [ "G", 0.75 ], +}; + +var util = {}; + +util.printd = function(fmt, d) +{ + var regexp = /(m+|d+|y+|H+|h+|M+|s+|t+|[^mdyHhMst]+)/g; + var res = ''; + var i; + + if (!d) + return null; + + var tokens = fmt.match(regexp); + var length = tokens ? tokens.length : 0; + + for (i = 0; i < length; i++) + { + switch(tokens[i]) + { + case 'mmmm': res += MuPDF.monthName[d.getMonth()]; break; + case 'mmm': res += MuPDF.monthName[d.getMonth()].substring(0,3); break; + case 'mm': res += MuPDF.padZeros(d.getMonth()+1, 2); break; + case 'm': res += d.getMonth()+1; break; + case 'dddd': res += MuPDF.dayName[d.getDay()]; break; + case 'ddd': res += MuPDF.dayName[d.getDay()].substring(0,3); break; + case 'dd': res += MuPDF.padZeros(d.getDate(), 2); break; + case 'd': res += d.getDate(); break; + case 'yyyy': res += d.getFullYear(); break; + case 'yy': res += d.getFullYear()%100; break; + case 'HH': res += MuPDF.padZeros(d.getHours(), 2); break; + case 'H': res += d.getHours(); break; + case 'hh': res += MuPDF.padZeros((d.getHours()+11)%12+1, 2); break; + case 'h': res += (d.getHours()+11)%12+1; break; + case 'MM': res += MuPDF.padZeros(d.getMinutes(), 2); break; + case 'M': res += d.getMinutes(); break; + case 'ss': res += MuPDF.padZeros(d.getSeconds(), 2); break; + case 's': res += d.getSeconds(); break; + case 'tt': res += d.getHours() < 12 ? 'am' : 'pm'; break; + case 't': res += d.getHours() < 12 ? 'a' : 'p'; break; + default: res += tokens[i]; + } + } + + return res; +} + +util.printx = function(fmt, val) +{ + var cs = '='; + var res = ''; + var i = 0; + var m; + var length = fmt ? fmt.length : 0; + + while (i < length) + { + switch (fmt.charAt(i)) + { + case '\\': + i++; + if (i >= length) return res; + res += fmt.charAt(i); + break; + + case 'X': + m = val.match(/\w/); + if (!m) return res; + res += MuPDF.convertCase(m[0],cs); + val = val.replace(/^\W*\w/,''); + break; + + case 'A': + m = val.match(/[A-Za-z]/); + if (!m) return res; + res += MuPDF.convertCase(m[0],cs); + val = val.replace(/^[^A-Za-z]*[A-Za-z]/,''); + break; + + case '9': + m = val.match(/\d/); + if (!m) return res; + res += m[0]; + val = val.replace(/^\D*\d/,''); + break; + + case '*': + res += val; + val = ''; + break; + + case '?': + if (!val) return res; + res += MuPDF.convertCase(val.charAt(0),cs); + val = val.substring(1); + break; + + case '=': + case '>': + case '<': + cs = fmt.charAt(i); + break; + + default: + res += MuPDF.convertCase(fmt.charAt(i),cs); + break; + } + + i++; + } + + return res; +} + +util.printf = function() +{ + var i; + + if (arguments.length < 1) + return ""; + + var res = ""; + var arg_i = 1; + var regexp = /%[^dfsx]*[dfsx]|[^%]*/g; + var tokens = arguments[0].match(regexp); + var length = tokens ? tokens.length : 0; + + for (i = 0; i < length; i++) + { + var tok = tokens[i]; + if (tok.match(/^%/)) + { + if (arg_i < arguments.length) + { + var val = arguments[arg_i++]; + var fval = ''; + var neg = false; + var decsep_re = /^,[0123]/; + var flags_re = /^[+ 0#]+/; + var width_re = /^\d+/; + var prec_re = /^\.\d+/; + var conv_re = /^[dfsx]/; + + tok = tok.replace(/^%/, ""); + var decsep = tok.match(decsep_re); + if (decsep) decsep = decsep[0]; + tok = tok.replace(decsep_re, ""); + var flags = tok.match(flags_re); + if (flags) flags = flags[0]; + tok = tok.replace(flags_re, ""); + var width = tok.match(width_re); + if (width) width = width[0]; + tok = tok.replace(width_re, ""); + var prec = tok.match(prec_re); + if (prec) prec = prec[0]; + tok = tok.replace(prec_re, ""); + var conv = tok.match(conv_re); + if (conv) conv = conv[0]; + + prec = prec ? Number(prec.replace(/^\./, '')) : 0; + var poschar = (flags && flags.match(/[+ ]/)) ? flags.match(/[+ ]/)[0] : ''; + var pad = (flags && flags.match(/0/)) ? '0' : ' '; + + var point = '.'; + var thou = ''; + + if (decsep) + { + switch(decsep) + { + case ',0': thou = ','; break; + case ',1': break; + case ',2': thou = '.'; point = ','; break; + case ',3': point = ','; break; + } + } + + switch(conv) + { + case 'x': + val = Math.floor(val); + neg = (val < 0); + if (neg) + val = -val; + + // Convert to hex + while (val) + { + fval = '0123456789ABCDEF'.charAt(val%16) + fval; + val = Math.floor(val/16); + } + + if (neg) + fval = '-' + fval; + else + fval = poschar + fval; + break; + + case 'd': + fval = String(Math.floor(val)); + break; + + case 's': + // Always pad strings with space + pad = ' '; + fval = String(val); + break; + + case 'f': + fval = String(val); + + if (prec) + { + var frac = fval.match(/\.\d+/); + if (frac) + { + frac = frac[0]; + // Matched string includes the dot, so make it + // prec+1 in length + if (frac.length > prec+1) + frac = frac.substring(0, prec+1); + else if(frac.length < prec+1) + frac += new Array(prec+1-frac.length+1).join('0'); + + fval = fval.replace(/\.\d+/, frac); + } + } + break; + } + + if (conv.match(/[fd]/)) + { + if (fval >= 0) + fval = poschar + fval; + + if (point !== '.') + fval.replace(/\./, point); + + if (thou) + { + var intpart = fval.match(/\d+/)[0]; + intpart = new Array(2-(intpart.length+2)%3+1).join('0') + intpart; + intpart = intpart.match(/.../g).join(thou).replace(/^0*[,.]?/,''); + fval = fval.replace(/\d+/, intpart); + } + } + + if (width && fval.length < width) + fval = new Array(width - fval.length + 1).join(pad) + fval; + + res += fval; + } + } + else + { + res += tok; + } + } + + return res; +} + +function AFMergeChange(event) +{ + return event.value; +} + +function AFMakeNumber(str) +{ + var nums = str.match(/\d+/g); + + if (!nums) + return null; + + var res = nums.join('.'); + + if (str.match(/^[^0-9]*\./)) + res = '0.'+res; + + return res * (str.match(/-/) ? -1.0 : 1.0); +} + +function AFExtractTime(dt) +{ + var ampm = dt.match(/(am|pm)/); + dt = dt.replace(/(am|pm)/, ''); + var t = dt.match(/\d{1,2}:\d{1,2}:\d{1,2}/); + dt = dt.replace(/\d{1,2}:\d{1,2}:\d{1,2}/, ''); + if (!t) + { + t = dt.match(/\d{1,2}:\d{1,2}/); + dt = dt.replace(/\d{1,2}:\d{1,2}/, ''); + } + + return [dt, t?t[0]+(ampm?ampm[0]:''):'']; +} + +function AFParseDateOrder(fmt) +{ + var i; + var order = ''; + + // Ensure all present with those not added in default order + fmt += "mdy"; + + for (i = 0; i < fmt.length; i++) + { + var c = fmt.charAt(i); + + if ('ymd'.indexOf(c) !== -1 && order.indexOf(c) === -1) + order += c; + } + + return order; +} + +function AFMatchMonth(d) +{ + var m = d.match(MuPDF.monthPattern); + + return m ? MuPDF.shortMonthName.indexOf(m[0]) : null; +} + +function AFParseTime(str, d) +{ + if (!str) + return d; + + if (!d) + d = new Date(); + + var ampm = str.match(/(am|pm)/); + var nums = str.match(/\d+/g); + var hour, min, sec; + + if (!nums) + return null; + + sec = 0; + + switch (nums.length) + { + case 3: + sec = parseInt(nums[2]); + case 2: + hour = parseInt(nums[0]); + min = parseInt(nums[1]); + break; + + default: + return null; + } + + ampm = ampm && ampm[0] + + if (ampm === 'am' && hour < 12) + hour = 12 + hour; + if (ampm === 'pm' && hour >= 12) + hour = 0 + hour - 12; + + d.setHours(hour, min, sec); + + if (d.getHours() !== hour || d.getMinutes() !== min || d.getSeconds() !== sec) + return null; + + return d; +} + +function AFParseDateEx(d, fmt) +{ + var i; + var dt = AFExtractTime(d); + var nums = dt[0].match(/\d+/g); + var order = AFParseDateOrder(fmt); + var text_month = AFMatchMonth(dt[0]); + var dout = new Date(); + var year = dout.getFullYear(); + var month = dout.getMonth(); + var date = dout.getDate(); + + dout.setHours(12,0,0); + + if (!nums || nums.length < 1 || nums.length > 3) + return null; + + if (nums.length < 3 && text_month) + { + // Use the text month rather than one of the numbers + month = text_month; + order = order.replace('m',''); + } + + order = order.substring(0, nums.length); + + // If year and month specified but not date then use the 1st + if (order === "ym" || (order === "y" && text_month)) + date = 1; + + for (i = 0; i < nums.length; i++) + { + switch (order.charAt(i)) + { + case 'y': year = parseInt(nums[i]); break; + case 'm': month = parseInt(nums[i]) - 1; break; + case 'd': date = parseInt(nums[i]); break; + } + } + + if (year < 100) + { + if (fmt.search("yyyy") !== -1) + return null; + + if (year >= 50) + year = 1900 + year; + else if (year >= 0) + year = 2000 + year; + } + + dout.setFullYear(year, month, date); + + if (dout.getFullYear() !== year || dout.getMonth() !== month || dout.getDate() !== date) + return null; + + return AFParseTime(dt[1], dout); +} + +function AFDate_KeystrokeEx(fmt) +{ + if (event.willCommit && !AFParseDateEx(event.value, fmt)) + { + app.alert("The date/time entered ("+event.value+") does not match the format ("+fmt+") of the field [ "+event.target.name+" ]"); + event.rc = false; + } +} + +function AFDate_Keystroke(index) +{ + var formats = ['m/d','m/d/yy','mm/dd/yy','mm/yy','d-mmm','d-mmm-yy','dd-mm-yy','yy-mm-dd', + 'mmm-yy','mmmm-yy','mmm d, yyyy','mmmm d, yyyy','m/d/yy h:MM tt','m/d/yy HH:MM']; + AFDate_KeystrokeEx(formats[index]); +} + +function AFDate_FormatEx(fmt) +{ + var d = AFParseDateEx(event.value, fmt); + + event.value = d ? util.printd(fmt, d) : ""; +} + +function AFDate_Format(index) +{ + var formats = ['m/d','m/d/yy','mm/dd/yy','mm/yy','d-mmm','d-mmm-yy','dd-mm-yy','yy-mm-dd', + 'mmm-yy','mmmm-yy','mmm d, yyyy','mmmm d, yyyy','m/d/yy h:MM tt','m/d/yy HH:MM']; + AFDate_FormatEx(formats[index]); +} + +function AFTime_Keystroke(index) +{ + if (event.willCommit && !AFParseTime(event.value, null)) + { + app.alert("The value entered ("+event.value+") does not match the format of the field [ "+event.target.name+" ]"); + event.rc = false; + } +} + +function AFTime_FormatEx(fmt) +{ + var d = AFParseTime(event.value, null); + + event.value = d ? util.printd(fmt, d) : ''; +} + +function AFTime_Format(index) +{ + var formats = ['HH:MM','h:MM tt','HH:MM:ss','h:MM:ss tt']; + + AFTime_FormatEx(formats[index]); +} + +function AFSpecial_KeystrokeEx(fmt) +{ + var cs = '='; + var val = event.value; + var res = ''; + var i = 0; + var m; + var length = fmt ? fmt.length : 0; + + while (i < length) + { + switch (fmt.charAt(i)) + { + case '\\': + i++; + if (i >= length) + break; + res += fmt.charAt(i); + if (val && val.charAt(0) === fmt.charAt(i)) + val = val.substring(1); + break; + + case 'X': + m = val.match(/^\w/); + if (!m) + { + event.rc = false; + break; + } + res += MuPDF.convertCase(m[0],cs); + val = val.substring(1); + break; + + case 'A': + m = val.match(/^[A-Za-z]/); + if (!m) + { + event.rc = false; + break; + } + res += MuPDF.convertCase(m[0],cs); + val = val.substring(1); + break; + + case '9': + m = val.match(/^\d/); + if (!m) + { + event.rc = false; + break; + } + res += m[0]; + val = val.substring(1); + break; + + case '*': + res += val; + val = ''; + break; + + case '?': + if (!val) + { + event.rc = false; + break; + } + res += MuPDF.convertCase(val.charAt(0),cs); + val = val.substring(1); + break; + + case '=': + case '>': + case '<': + cs = fmt.charAt(i); + break; + + default: + res += fmt.charAt(i); + if (val && val.charAt(0) === fmt.charAt(i)) + val = val.substring(1); + break; + } + + i++; + } + + if (event.rc) + event.value = res; + else if (event.willCommit) + app.alert("The value entered ("+event.value+") does not match the format of the field [ "+event.target.name+" ] should be "+fmt); +} + +function AFSpecial_Keystroke(index) +{ + if (event.willCommit) + { + switch (index) + { + case 0: + if (!event.value.match(/^\d{5}$/)) + event.rc = false; + break; + case 1: + if (!event.value.match(/^\d{5}[-. ]?\d{4}$/)) + event.rc = false; + break; + case 2: + if (!event.value.match(/^((\(\d{3}\)|\d{3})[-. ]?)?\d{3}[-. ]?\d{4}$/)) + event.rc = false; + break; + case 3: + if (!event.value.match(/^\d{3}[-. ]?\d{2}[-. ]?\d{4}$/)) + event.rc = false; + break; + } + + if (!event.rc) + app.alert("The value entered ("+event.value+") does not match the format of the field [ "+event.target.name+" ]"); + } +} + +function AFSpecial_Format(index) +{ + var res; + + switch (index) + { + case 0: + res = util.printx('99999', event.value); + break; + case 1: + res = util.printx('99999-9999', event.value); + break; + case 2: + res = util.printx('9999999999', event.value); + res = util.printx(res.length >= 10 ? '(999) 999-9999' : '999-9999', event.value); + break; + case 3: + res = util.printx('999-99-9999', event.value); + break; + } + + event.value = res ? res : ''; +} + +function AFNumber_Keystroke(nDec, sepStyle, negStyle, currStyle, strCurrency, bCurrencyPrepend) +{ + if (sepStyle & 2) + { + if (!event.value.match(/^[+-]?\d*[,.]?\d*$/)) + event.rc = false; + } + else + { + if (!event.value.match(/^[+-]?\d*\.?\d*$/)) + event.rc = false; + } + + if (event.willCommit) + { + if (!event.value.match(/\d/)) + event.rc = false; + + if (!event.rc) + app.alert("The value entered ("+event.value+") does not match the format of the field [ "+event.target.name+" ]"); + } +} + +function AFNumber_Format(nDec,sepStyle,negStyle,currStyle,strCurrency,bCurrencyPrepend) +{ + var val = event.value; + var fracpart; + var intpart; + var point = sepStyle&2 ? ',' : '.'; + var separator = sepStyle&2 ? '.' : ','; + + if (/^\D*\./.test(val)) + val = '0'+val; + + var groups = val.match(/\d+/g); + + if (!groups) + return; + + switch (groups.length) + { + case 0: + return; + case 1: + fracpart = ''; + intpart = groups[0]; + break; + default: + fracpart = groups.pop(); + intpart = groups.join(''); + break; + } + + // Remove leading zeros + intpart = intpart.replace(/^0*/,''); + if (!intpart) + intpart = '0'; + + if ((sepStyle & 1) === 0) + { + // Add the thousands sepearators: pad to length multiple of 3 with zeros, + // split into 3s, join with separator, and remove the leading zeros + intpart = new Array(2-(intpart.length+2)%3+1).join('0') + intpart; + intpart = intpart.match(/.../g).join(separator).replace(/^0*/,''); + } + + if (!intpart) + intpart = '0'; + + // Adjust fractional part to correct number of decimal places + fracpart += new Array(nDec+1).join('0'); + fracpart = fracpart.substring(0,nDec); + + if (fracpart) + intpart += point+fracpart; + + if (bCurrencyPrepend) + intpart = strCurrency+intpart; + else + intpart += strCurrency; + + if (/-/.test(val)) + { + switch (negStyle) + { + case 0: + intpart = '-'+intpart; + break; + case 1: + break; + case 2: + case 3: + intpart = '('+intpart+')'; + break; + } + } + + if (negStyle&1) + event.target.textColor = /-/.test(val) ? color.red : color.black; + + event.value = intpart; +} + +function AFPercent_Keystroke(nDec, sepStyle) +{ + AFNumber_Keystroke(nDec, sepStyle, 0, 0, "", true); +} + +function AFPercent_Format(nDec, sepStyle) +{ + var val = AFMakeNumber(event.value); + + if (!val) + { + event.value = ''; + return; + } + + event.value = (val * 100) + ''; + + AFNumber_Format(nDec, sepStyle, 0, 0, "%", false); +} + +function AFSimple_Calculate(op, list) +{ + var i, res; + + switch (op) + { + case 'SUM': + res = 0; + break; + case 'PRD': + res = 1; + break; + case 'AVG': + res = 0; + break; + } + + if (typeof list === 'string') + list = list.split(/ *, */); + + for (i = 0; i < list.length; i++) + { + var field = MuPDF_Doc.getField(list[i]); + var value = Number(field.value); + + switch (op) + { + case 'SUM': + res += value; + break; + case 'PRD': + res *= value; + break; + case 'AVG': + res += value; + break; + case 'MIN': + if (i === 0 || value < res) + res = value; + break; + case 'MAX': + if (i === 0 || value > res) + res = value; + break; + } + } + + if (op === 'AVG') + res /= list.length; + + event.value = res; +} + +function AFRange_Validate(lowerCheck, lowerLimit, upperCheck, upperLimit) +{ + if (upperCheck && event.value > upperLimit) + { + event.rc = false; + } + + if (lowerCheck && event.value < lowerLimit) + { + event.rc = false; + } + + if (!event.rc) + { + if (lowerCheck && upperCheck) + app.alert(util.printf("The entered value ("+event.value+") must be greater than or equal to %s and less than or equal to %s", lowerLimit, upperLimit)); + else if (lowerCheck) + app.alert(util.printf("The entered value ("+event.value+") must be greater than or equal to %s", lowerLimit)); + else + app.alert(util.printf("The entered value ("+event.value+") must be less than or equal to %s", upperLimit)); + } +} diff --git a/source/pdf/pdf-js.c b/source/pdf/pdf-js.c new file mode 100644 index 00000000..259a9b7a --- /dev/null +++ b/source/pdf/pdf-js.c @@ -0,0 +1,734 @@ +#include "mupdf/pdf.h" + +#if FZ_ENABLE_JS + +#include "mujs.h" + +struct pdf_js_s +{ + fz_context *ctx; + pdf_document *doc; + pdf_obj *form; + pdf_js_event event; + js_State *imp; +}; + +FZ_NORETURN static void rethrow(pdf_js *js) +{ + js_newerror(js->imp, fz_caught_message(js->ctx)); + js_throw(js->imp); +} + +/* Unpack argument object with named arguments into actual parameters. */ +static pdf_js *arguments(js_State *J, ...) +{ + if (js_isobject(J, 1)) + { + int i = 1; + va_list args; + + js_copy(J, 1); + + va_start(args, J); + for (;;) + { + const char *s = va_arg(args, const char *); + if (!s) + break; + js_getproperty(J, -1, s); + js_replace(J, i++); + } + va_end(args); + + js_pop(J, 1); + } + return js_getcontext(J); +} + +static char *pdf_from_utf8(fz_context *ctx, const char *utf8) +{ + char *pdf = fz_malloc(ctx, strlen(utf8)+1); + int i = 0; + unsigned char c; + + while ((c = *utf8) != 0) + { + if ((c & 0x80) == 0 && pdf_doc_encoding[c] == c) + { + pdf[i++] = c; + utf8++ ; + } + else + { + int rune; + int j; + + utf8 += fz_chartorune(&rune, utf8); + + for (j = 0; j < sizeof(pdf_doc_encoding) && pdf_doc_encoding[j] != rune; j++) + ; + + if (j < sizeof(pdf_doc_encoding)) + pdf[i++] = j; + } + } + + pdf[i] = 0; + + return pdf; +} + +static void app_alert(js_State *J) +{ + pdf_js *js = arguments(J, "cMsg", "nIcon", "nType", "cTitle", 0); + pdf_alert_event event; + + event.message = js_tostring(J, 1); + event.icon_type = js_tointeger(J, 2); + event.button_group_type = js_tointeger(J, 3); + event.title = js_tostring(J, 4); + + fz_try(js->ctx) + pdf_event_issue_alert(js->ctx, js->doc, &event); + fz_catch(js->ctx) + rethrow(js); + + js_pushnumber(J, event.button_pressed); +} + +static void app_execDialog(js_State *J) +{ + pdf_js *js = js_getcontext(J); + // monitor + // inheritDialog + // parentDoc + + fz_try(js->ctx) + pdf_event_issue_exec_dialog(js->ctx, js->doc); + fz_catch(js->ctx) + rethrow(js); + + // return "ok" or "cancel" + js_pushstring(J, "cancel"); +} + +static void app_execMenuItem(js_State *J) +{ + pdf_js *js = js_getcontext(J); + const char *cMenuItem = js_tostring(J, 1); + fz_try(js->ctx) + pdf_event_issue_exec_menu_item(js->ctx, js->doc, cMenuItem); + fz_catch(js->ctx) + rethrow(js); +} + +static void app_launchURL(js_State *J) +{ + pdf_js *js = js_getcontext(J); + const char *cUrl = js_tostring(J, 1); + int bNewFrame = js_toboolean(J, 1); + fz_try(js->ctx) + pdf_event_issue_launch_url(js->ctx, js->doc, cUrl, bNewFrame); + fz_catch(js->ctx) + rethrow(js); +} + +static void field_finalize(js_State *J, void *p) +{ + pdf_js *js = js_getcontext(J); + pdf_drop_obj(js->ctx, p); +} + +static void field_buttonSetCaption(js_State *J) +{ + pdf_js *js = js_getcontext(J); + pdf_obj *field = js_touserdata(J, 0, "Field"); + const char *cCaption = js_tostring(J, 1); + char *caption = pdf_from_utf8(js->ctx, cCaption); + fz_try(js->ctx) + pdf_field_set_button_caption(js->ctx, js->doc, field, caption); + fz_always(js->ctx) + fz_free(js->ctx, caption); + fz_catch(js->ctx) + rethrow(js); +} + +static void field_getName(js_State *J) +{ + pdf_js *js = js_getcontext(J); + pdf_obj *field = js_touserdata(J, 0, "Field"); + char *name; + fz_try(js->ctx) + name = pdf_field_name(js->ctx, js->doc, field); + fz_catch(js->ctx) + rethrow(js); + js_pushstring(J, name); /* to utf8? */ +} + +static void field_setName(js_State *J) +{ + pdf_js *js = js_getcontext(J); + fz_warn(js->ctx, "Unexpected call to field_setName"); +} + +static void field_getDisplay(js_State *J) +{ + pdf_js *js = js_getcontext(J); + pdf_obj *field = js_touserdata(J, 0, "Field"); + int display; + fz_try(js->ctx) + display = pdf_field_display(js->ctx, js->doc, field); + fz_catch(js->ctx) + rethrow(js); + js_pushnumber(J, display); +} + +static void field_setDisplay(js_State *J) +{ + pdf_js *js = js_getcontext(J); + pdf_obj *field = js_touserdata(J, 0, "Field"); + int display = js_tonumber(J, 1); + fz_try(js->ctx) + pdf_field_set_display(js->ctx, js->doc, field, display); + fz_catch(js->ctx) + rethrow(js); +} + +static pdf_obj *load_color(pdf_js *js, int idx) +{ + fz_context *ctx = js->ctx; + pdf_document *doc = js->doc; + js_State *J = js->imp; + + pdf_obj *color = NULL; + int i, n; + float c; + + n = js_getlength(J, idx); + + /* The only legitimate color expressed as an array of length 1 + * is [T], meaning transparent. Return a NULL object to represent + * transparent */ + if (n <= 1) + return NULL; + + fz_var(color); + + fz_try(ctx) + { + color = pdf_new_array(ctx, doc, n-1); + for (i = 0; i < n-1; i++) + { + js_getindex(J, idx, i+1); + c = js_tonumber(J, -1); + js_pop(J, 1); + + pdf_array_push_drop(ctx, color, pdf_new_real(ctx, doc, c)); + } + } + fz_catch(ctx) + { + pdf_drop_obj(ctx, color); + rethrow(js); + } + + return color; +} + +static void field_getFillColor(js_State *J) +{ + js_pushundefined(J); +} + +static void field_setFillColor(js_State *J) +{ + pdf_js *js = js_getcontext(J); + pdf_obj *field = js_touserdata(J, 0, "Field"); + pdf_obj *color = load_color(js, 1); + fz_try(js->ctx) + pdf_field_set_fill_color(js->ctx, js->doc, field, color); + fz_always(js->ctx) + pdf_drop_obj(js->ctx, color); + fz_catch(js->ctx) + rethrow(js); +} + +static void field_getTextColor(js_State *J) +{ + js_pushundefined(J); +} + +static void field_setTextColor(js_State *J) +{ + pdf_js *js = js_getcontext(J); + pdf_obj *field = js_touserdata(J, 0, "Field"); + pdf_obj *color = load_color(js, 1); + fz_try(js->ctx) + pdf_field_set_text_color(js->ctx, js->doc, field, color); + fz_always(js->ctx) + pdf_drop_obj(js->ctx, color); + fz_catch(js->ctx) + rethrow(js); +} + +static void field_getBorderStyle(js_State *J) +{ + pdf_js *js = js_getcontext(J); + pdf_obj *field = js_touserdata(J, 0, "Field"); + const char *border_style; + fz_try(js->ctx) + border_style = pdf_field_border_style(js->ctx, js->doc, field); + fz_catch(js->ctx) + rethrow(js); + js_pushstring(J, border_style); +} + +static void field_setBorderStyle(js_State *J) +{ + pdf_js *js = js_getcontext(J); + pdf_obj *field = js_touserdata(J, 0, "Field"); + const char *border_style = js_tostring(J, 1); + fz_try(js->ctx) + pdf_field_set_border_style(js->ctx, js->doc, field, border_style); + fz_catch(js->ctx) + rethrow(js); +} + +static void field_getValue(js_State *J) +{ + pdf_js *js = js_getcontext(J); + pdf_obj *field = js_touserdata(J, 0, "Field"); + char *val; + + fz_try(js->ctx) + val = pdf_field_value(js->ctx, js->doc, field); + fz_catch(js->ctx) + rethrow(js); + + js_pushstring(J, val ? val : ""); + + fz_free(js->ctx, val); +} + +static void field_setValue(js_State *J) +{ + pdf_js *js = js_getcontext(J); + pdf_obj *field = js_touserdata(J, 0, "Field"); + const char *value = js_tostring(J, 1); + + fz_try(js->ctx) + (void)pdf_field_set_value(js->ctx, js->doc, field, value); + fz_catch(js->ctx) + rethrow(js); +} + +static void event_getTarget(js_State *J) +{ + pdf_js *js = js_getcontext(J); + js_getregistry(J, "Field"); + js_newuserdata(J, "Field", pdf_keep_obj(js->ctx, js->event.target), field_finalize); +} + +static void event_setTarget(js_State *J) +{ + pdf_js *js = js_getcontext(J); + fz_warn(js->ctx, "Unexpected call to event_setTarget"); +} + +static void event_getValue(js_State *J) +{ + pdf_js *js = js_getcontext(J); + const char *v = js->event.value; + js_pushstring(J, v ? v : ""); +} + +static void event_setValue(js_State *J) +{ + pdf_js *js = js_getcontext(J); + const char *value = js_tostring(J, 1); + fz_free(js->ctx, js->event.value); + js->event.value = fz_strdup(js->ctx, value); +} + +static void event_getWillCommit(js_State *J) +{ + js_pushnumber(J, 1); +} + +static void event_setWillCommit(js_State *J) +{ + pdf_js *js = js_getcontext(J); + fz_warn(js->ctx, "Unexpected call to event_setWillCommit"); +} + +static void event_getRC(js_State *J) +{ + pdf_js *js = js_getcontext(J); + js_pushnumber(J, js->event.rc); +} + +static void event_setRC(js_State *J) +{ + pdf_js *js = js_getcontext(J); + js->event.rc = js_tointeger(js->imp, 1); +} + +static void doc_getField(js_State *J) +{ + pdf_js *js = js_getcontext(J); + fz_context *ctx = js->ctx; + const char *cName = js_tostring(J, 1); + char *name = pdf_from_utf8(ctx, cName); + pdf_obj *dict; + + fz_try(ctx) + dict = pdf_lookup_field(ctx, js->form, name); + fz_always(ctx) + fz_free(ctx, name); + fz_catch(ctx) + rethrow(js); + + if (dict) + { + js_getregistry(J, "Field"); + js_newuserdata(J, "Field", pdf_keep_obj(js->ctx, dict), field_finalize); + } + else + { + js_pushnull(J); + } +} + +static void reset_field(pdf_js *js, const char *cName) +{ + fz_context *ctx = js->ctx; + if (cName) + { + char *name = pdf_from_utf8(ctx, cName); + fz_try(ctx) + { + pdf_obj *field = js_touserdata(js->imp, 0, "Field"); + if (field) + pdf_field_reset(ctx, js->doc, field); + } + fz_always(ctx) + fz_free(ctx, name); + fz_catch(ctx) + rethrow(js); + } +} + +static void doc_resetForm(js_State *J) +{ + pdf_js *js = js_getcontext(J); + fz_context *ctx = js->ctx; + int i, n; + + /* An array of fields has been passed in. Call pdf_reset_field on each item. */ + if (js_isarray(J, 1)) + { + n = js_getlength(J, 1); + for (i = 0; i < n; ++i) + { + js_getindex(J, 1, i); + reset_field(js, js_tostring(J, -1)); + js_pop(J, 1); + } + } + + /* No argument or null passed in means reset all. */ + else + { + n = pdf_array_len(ctx, js->form); + for (i = 0; i < n; i++) + { + fz_try(ctx) + pdf_field_reset(ctx, js->doc, pdf_array_get(ctx, js->form, i)); + fz_catch(ctx) + rethrow(js); + } + } +} + +static void doc_print(js_State *J) +{ + pdf_js *js = js_getcontext(J); + fz_try(js->ctx) + pdf_event_issue_print(js->ctx, js->doc); + fz_catch(js->ctx) + rethrow(js); +} + +static void doc_mailDoc(js_State *J) +{ + pdf_js *js = arguments(J, "bUI", "cTo", "cCc", "cBcc", "cSubject", "cMessage", 0); + pdf_mail_doc_event event; + + event.ask_user = js_isdefined(J, 1) ? js_toboolean(J, 1) : 1; + event.to = js_tostring(J, 2); + event.cc = js_tostring(J, 3); + event.bcc = js_tostring(J, 4); + event.subject = js_tostring(J, 5); + event.message = js_tostring(J, 6); + + fz_try(js->ctx) + pdf_event_issue_mail_doc(js->ctx, js->doc, &event); + fz_catch(js->ctx) + rethrow(js); +} + +static void addmethod(js_State *J, const char *name, js_CFunction fun, int n) +{ + const char *realname = strchr(name, '.'); + realname = realname ? realname + 1 : name; + js_newcfunction(J, fun, name, n); + js_defproperty(J, -2, realname, JS_READONLY | JS_DONTENUM | JS_DONTCONF); +} + +static void addproperty(js_State *J, const char *name, js_CFunction getfun, js_CFunction setfun) +{ + const char *realname = strchr(name, '.'); + realname = realname ? realname + 1 : name; + js_newcfunction(J, getfun, name, 0); + js_newcfunction(J, setfun, name, 1); + js_defaccessor(J, -3, realname, JS_READONLY | JS_DONTENUM | JS_DONTCONF); +} + +static void declare_dom(pdf_js *js) +{ + js_State *J = js->imp; + + /* Allow access to the global environment via the 'global' name */ + js_pushglobal(J); + js_defglobal(J, "global", JS_READONLY | JS_DONTCONF | JS_DONTENUM); + + /* Create the 'app' object */ + js_newobject(J); + { + addmethod(J, "app.alert", app_alert, 4); + addmethod(J, "app.execDialog", app_execDialog, 0); + addmethod(J, "app.execMenuItem", app_execMenuItem, 1); + addmethod(J, "app.launchURL", app_launchURL, 2); + } + js_defglobal(J, "app", JS_READONLY | JS_DONTCONF | JS_DONTENUM); + + /* Create the 'event' object */ + js_newobject(J); + { + addproperty(J, "event.target", event_getTarget, event_setTarget); + addproperty(J, "event.value", event_getValue, event_setValue); + addproperty(J, "event.willCommit", event_getWillCommit, event_setWillCommit); + addproperty(J, "event.rc", event_getRC, event_setRC); + } + js_defglobal(J, "event", JS_READONLY | JS_DONTCONF | JS_DONTENUM); + + /* Create the Field prototype object */ + js_newobject(J); + { + addproperty(J, "Field.value", field_getValue, field_setValue); + addproperty(J, "Field.borderStyle", field_getBorderStyle, field_setBorderStyle); + addproperty(J, "Field.textColor", field_getTextColor, field_setTextColor); + addproperty(J, "Field.fillColor", field_getFillColor, field_setFillColor); + addproperty(J, "Field.display", field_getDisplay, field_setDisplay); + addproperty(J, "Field.name", field_getName, field_setName); + addmethod(J, "Field.buttonSetCaption", field_buttonSetCaption, 1); + } + js_setregistry(J, "Field"); + + /* Create the Doc prototype object */ + js_newobject(J); + { + addmethod(J, "Doc.getField", doc_getField, 1); + addmethod(J, "Doc.resetForm", doc_resetForm, 0); + addmethod(J, "Doc.print", doc_print, 0); + addmethod(J, "Doc.mailDoc", doc_mailDoc, 6); + } + js_setregistry(J, "Doc"); + + js_getregistry(J, "Doc"); + js_setglobal(J, "MuPDF_Doc"); /* for pdf-util.js use */ +} + +static void preload_helpers(pdf_js *js) +{ + /* When testing on the cluster: + * Use a fixed date for "new Date" and Date.now(). + * Sadly, this breaks uses of the Date function without the new keyword. + * Return a fixed number from Math.random(). + */ +#ifdef CLUSTER + js_dostring(js->imp, +"var MuPDFOldDate = Date\n" +"Date = function() { return new MuPDFOldDate(298252800000); }\n" +"Date.now = function() { return 298252800000; }\n" +"Date.UTC = function() { return 298252800000; }\n" +"Date.parse = MuPDFOldDate.parse;\n" +"Math.random = function() { return 1/4; }\n" + ); +#endif + + js_dostring(js->imp, +#include "gen_js_util.h" + ); +} + +void pdf_drop_js(fz_context *ctx, pdf_js *js) +{ + if (js) + { + js_freestate(js->imp); + fz_free(ctx, js->event.value); + fz_free(ctx, js); + } +} + +static void *pdf_js_alloc(void *actx, void *ptr, int n) +{ + fz_context *ctx = actx; + if (n == 0) { + fz_free(ctx, ptr); + return NULL; + } + if (ptr) + return fz_resize_array(ctx, ptr, n, 1); + return fz_malloc_array(ctx, n, 1); +} + +static pdf_js *pdf_new_js(fz_context *ctx, pdf_document *doc) +{ + pdf_js *js = fz_malloc_struct(ctx, pdf_js); + + js->ctx = ctx; + js->doc = doc; + + fz_try(ctx) + { + pdf_obj *root, *acroform; + + /* Find the form array */ + root = pdf_dict_get(ctx, pdf_trailer(ctx, doc), PDF_NAME_Root); + acroform = pdf_dict_get(ctx, root, PDF_NAME_AcroForm); + js->form = pdf_dict_get(ctx, acroform, PDF_NAME_Fields); + + /* Initialise the javascript engine, passing the fz_context for use in memory allocation. */ + js->imp = js_newstate(pdf_js_alloc, ctx, 0); + if (!js->imp) + fz_throw(ctx, FZ_ERROR_GENERIC, "cannot initialize javascript engine"); + + /* Also set our pdf_js context, so we can retrieve it in callbacks. */ + js_setcontext(js->imp, js); + + declare_dom(js); + preload_helpers(js); + } + fz_catch(ctx) + { + pdf_drop_js(ctx, js); + js = NULL; + } + + return js; +} + +static void pdf_js_load_document_level(pdf_js *js) +{ + fz_context *ctx = js->ctx; + pdf_document *doc = js->doc; + pdf_obj *javascript; + int len, i; + + javascript = pdf_load_name_tree(ctx, doc, PDF_NAME_JavaScript); + len = pdf_dict_len(ctx, javascript); + + fz_try(ctx) + { + for (i = 0; i < len; i++) + { + pdf_obj *fragment = pdf_dict_get_val(ctx, javascript, i); + pdf_obj *code = pdf_dict_get(ctx, fragment, PDF_NAME_JS); + char *codebuf = pdf_to_utf8(ctx, doc, code); + pdf_js_execute(js, codebuf); + fz_free(ctx, codebuf); + } + } + fz_always(ctx) + pdf_drop_obj(ctx, javascript); + fz_catch(ctx) + fz_rethrow(ctx); +} + +void pdf_js_setup_event(pdf_js *js, pdf_js_event *e) +{ + if (js) + { + fz_context *ctx = js->ctx; + char *ev = e->value ? e->value : ""; + char *v = fz_strdup(ctx, ev); + + fz_free(ctx, js->event.value); + js->event.value = v; + + js->event.target = e->target; + js->event.rc = 1; + } +} + +pdf_js_event *pdf_js_get_event(pdf_js *js) +{ + return js ? &js->event : NULL; +} + +void pdf_js_execute(pdf_js *js, char *source) +{ + if (js) + { + if (js_ploadstring(js->imp, "[pdf]", source)) + { + fz_warn(js->ctx, "%s", js_tostring(js->imp, -1)); + js_pop(js->imp, 1); + return; + } + js_getregistry(js->imp, "Doc"); /* set 'this' to the Doc object */ + if (js_pcall(js->imp, 0)) + { + fz_warn(js->ctx, "%s", js_tostring(js->imp, -1)); + js_pop(js->imp, 1); + return; + } + js_pop(js->imp, 1); + } +} + +void pdf_enable_js(fz_context *ctx, pdf_document *doc) +{ + if (!doc->js) + { + doc->js = pdf_new_js(ctx, doc); + pdf_js_load_document_level(doc->js); + } +} + +void pdf_disable_js(fz_context *ctx, pdf_document *doc) +{ + if (doc->js) + pdf_drop_js(ctx, doc->js); + doc->js = NULL; +} + +int pdf_js_supported(fz_context *ctx, pdf_document *doc) +{ + return doc->js != NULL; +} + +#else /* FZ_ENABLE_JS */ + +void pdf_drop_js(fz_context *ctx, pdf_js *js) { } +void pdf_enable_js(fz_context *ctx, pdf_document *doc) { } +void pdf_disable_js(fz_context *ctx, pdf_document *doc) { } +int pdf_js_supported(fz_context *ctx, pdf_document *doc) { return 0; } +void pdf_js_setup_event(pdf_js *js, pdf_js_event *e) { } +pdf_js_event *pdf_js_get_event(pdf_js *js) { return NULL; } +void pdf_js_execute(pdf_js *js, char *code) { } +void pdf_js_execute_count(pdf_js *js, char *code, int count) { } + +#endif /* FZ_ENABLE_JS */ diff --git a/source/tools/mutool.c b/source/tools/mutool.c index 95c49fa1..20f4f957 100644 --- a/source/tools/mutool.c +++ b/source/tools/mutool.c @@ -28,7 +28,9 @@ static struct { } tools[] = { { muconvert_main, "convert", "convert document" }, { mudraw_main, "draw", "convert document" }, +#if FZ_ENABLE_JS { murun_main, "run", "run javascript" }, +#endif #if FZ_ENABLE_PDF { pdfclean_main, "clean", "rewrite pdf file" }, { pdfextract_main, "extract", "extract font and image resources" }, -- cgit v1.2.3