diff options
Diffstat (limited to 'source/pdf/pdf-js.c')
-rw-r--r-- | source/pdf/pdf-js.c | 734 |
1 files changed, 734 insertions, 0 deletions
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 */ |