summaryrefslogtreecommitdiff
path: root/source/pdf/pdf-js.c
diff options
context:
space:
mode:
Diffstat (limited to 'source/pdf/pdf-js.c')
-rw-r--r--source/pdf/pdf-js.c734
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 */