Mercurial > hgrepos > Python2 > PyMuPDF
diff mupdf-source/source/pdf/pdf-js.c @ 2:b50eed0cc0ef upstream
ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4.
The directory name has changed: no version number in the expanded directory now.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 15 Sep 2025 11:43:07 +0200 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mupdf-source/source/pdf/pdf-js.c Mon Sep 15 11:43:07 2025 +0200 @@ -0,0 +1,1331 @@ +// Copyright (C) 2004-2022 Artifex Software, Inc. +// +// This file is part of MuPDF. +// +// MuPDF is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html> +// +// Alternative licensing terms are available from the licensor. +// For commercial licensing, see <https://www.artifex.com/> or contact +// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, +// CA 94129, USA, for further information. + +#include "mupdf/fitz.h" +#include "mupdf/pdf.h" + +#if FZ_ENABLE_JS + +#include "mujs.h" + +#include <stdarg.h> +#include <string.h> + +struct pdf_js +{ + fz_context *ctx; + pdf_document *doc; + pdf_obj *form; + js_State *imp; + pdf_js_console *console; + void *console_user; +}; + +FZ_NORETURN static void rethrow(pdf_js *js) +{ + js_newerror(js->imp, fz_convert_error(js->ctx, NULL)); + js_throw(js->imp); +} + +/* Unpack argument object with named arguments into actual parameters. */ +static pdf_js *unpack_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 void app_alert(js_State *J) +{ + pdf_js *js = unpack_arguments(J, "cMsg", "nIcon", "nType", "cTitle", "oDoc", "oCheckbox", NULL); + pdf_alert_event evt; + + /* TODO: Currently we do not support app.openDoc() in javascript actions, hence + oDoc can only point to the current document (or not be passed). When mupdf + supports opening other documents oDoc must be converted to a pdf_document * that + can be passed to the callback. In the mean time, we just pas the current document. + */ + evt.doc = js->doc; + + evt.message = js_tostring(J, 1); + evt.icon_type = js_tointeger(J, 2); + evt.button_group_type = js_tointeger(J, 3); + evt.title = js_isdefined(J, 4) ? js_tostring(J, 4) : "PDF alert"; + + evt.has_check_box = 0; + evt.check_box_message = NULL; + evt.initially_checked = 0; + evt.finally_checked = 0; + + if (js_isobject(J, 6)) + { + evt.has_check_box = 1; + evt.check_box_message = "Do not show this message again"; + if (js_hasproperty(J, 6, "cMsg")) + { + if (js_iscoercible(J, -1)) + evt.check_box_message = js_tostring(J, -1); + js_pop(J, 1); + } + if (js_hasproperty(J, 6, "bInitialValue")) + { + evt.initially_checked = js_tointeger(J, -1); + js_pop(J, 1); + } + if (js_hasproperty(J, 6, "bAfterValue")) + { + evt.finally_checked = js_tointeger(J, -1); + js_pop(J, 1); + } + } + + /* These are the default buttons automagically "pressed" + when the dialog box window is closed in Acrobat. */ + switch (evt.button_group_type) + { + default: + case PDF_ALERT_BUTTON_GROUP_OK: + evt.button_pressed = PDF_ALERT_BUTTON_OK; + break; + case PDF_ALERT_BUTTON_GROUP_OK_CANCEL: + evt.button_pressed = PDF_ALERT_BUTTON_CANCEL; + break; + case PDF_ALERT_BUTTON_GROUP_YES_NO: + evt.button_pressed = PDF_ALERT_BUTTON_YES; + break; + case PDF_ALERT_BUTTON_GROUP_YES_NO_CANCEL: + evt.button_pressed = PDF_ALERT_BUTTON_CANCEL; + break; + } + + fz_try(js->ctx) + pdf_event_issue_alert(js->ctx, js->doc, &evt); + fz_catch(js->ctx) + rethrow(js); + + if (js_isobject(J, 6)) + { + js_pushboolean(js->imp, evt.finally_checked); + js_setproperty(js->imp, 6, "bAfterValue"); + } + + js_pushnumber(J, evt.button_pressed); +} + +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); + fz_try(js->ctx) + pdf_field_set_button_caption(js->ctx, field, cCaption); + 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 = NULL; + fz_try(js->ctx) + name = pdf_load_field_name(js->ctx, field); + fz_catch(js->ctx) + rethrow(js); + if (js_try(J)) { + fz_free(js->ctx, name); + js_throw(J); + } else { + js_pushstring(J, name); + js_endtry(J); + fz_free(js->ctx, name); + } +} + +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 = 0; + fz_try(js->ctx) + display = pdf_field_display(js->ctx, 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, 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_real(ctx, color, 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, 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, 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 = NULL; + fz_try(js->ctx) + border_style = pdf_field_border_style(js->ctx, 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, 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"); + const char *str = NULL; + char *end; + double num; + + fz_try(js->ctx) + str = pdf_field_value(js->ctx, field); + fz_catch(js->ctx) + rethrow(js); + + num = strtod(str, &end); + if (*str && *end == 0) + js_pushnumber(J, num); + else + js_pushstring(J, str); +} + +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_set_field_value(js->ctx, js->doc, field, value, 0); + fz_catch(js->ctx) + rethrow(js); +} + +static void field_getType(js_State *J) +{ + pdf_js *js = js_getcontext(J); + pdf_obj *field = js_touserdata(J, 0, "Field"); + const char *type; + + fz_try(js->ctx) + type = pdf_field_type_string(js->ctx, field); + fz_catch(js->ctx) + rethrow(js); + + js_pushstring(J, type); +} + +static void field_setType(js_State *J) +{ + pdf_js *js = js_getcontext(J); + fz_warn(js->ctx, "Unexpected call to field_setType"); +} + +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); + pdf_obj *dict = NULL; + + fz_try(ctx) + dict = pdf_lookup_field(ctx, js->form, cName); + 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 doc_getNumPages(js_State *J) +{ + pdf_js *js = js_getcontext(J); + int pages = pdf_count_pages(js->ctx, js->doc); + js_pushnumber(J, pages); +} + +static void doc_setNumPages(js_State *J) +{ + pdf_js *js = js_getcontext(J); + fz_warn(js->ctx, "Unexpected call to doc_setNumPages"); +} + +static void doc_getMetaString(js_State *J, const char *key) +{ + pdf_js *js = js_getcontext(J); + char buf[256]; + int ret; + + fz_try(js->ctx) + ret = fz_lookup_metadata(js->ctx, &js->doc->super, key, buf, nelem(buf)) > 0; + fz_catch(js->ctx) + rethrow(js); + + if (ret > 0) + js_pushstring(J, buf); + else + js_pushundefined(J); +} + +static void doc_setMetaString(js_State *J, const char *key) +{ + pdf_js *js = js_getcontext(J); + const char *value = js_tostring(J, 1); + fz_set_metadata(js->ctx, &js->doc->super, key, value); +} + +static void doc_getMetaDate(js_State *J, const char *key) +{ + pdf_js *js = js_getcontext(J); + char buf[256]; + int ret; + double time; + + fz_try(js->ctx) + { + ret = fz_lookup_metadata(js->ctx, &js->doc->super, key, buf, nelem(buf)) > 0; + if (ret > 0) + time = pdf_parse_date(js->ctx, buf); + } + fz_catch(js->ctx) + rethrow(js); + + if (ret > 0) + { + js_getglobal(J, "Date"); + js_pushnumber(J, time * 1000); + js_construct(J, 1); + } + else + js_pushundefined(J); +} + +static void doc_setMetaDate(js_State *J, const char *key) +{ + pdf_js *js = js_getcontext(J); + int64_t time; + char value[40]; + + // Coerce the argument into a date object and extract the time value. + js_getglobal(J, "Date"); + js_copy(J, 1); + js_construct(J, 1); + time = js_tonumber(J, -1) / 1000; + js_pop(J, 1); + + fz_try(js->ctx) + if (pdf_format_date(js->ctx, time, value, nelem(value))) + fz_set_metadata(js->ctx, &js->doc->super, key, value); + fz_catch(js->ctx) + rethrow(js); +} + +static void doc_getAuthor(js_State *J) { doc_getMetaString(J, FZ_META_INFO_AUTHOR); } +static void doc_setAuthor(js_State *J) { doc_setMetaString(J, FZ_META_INFO_AUTHOR); } +static void doc_getTitle(js_State *J) { doc_getMetaString(J, FZ_META_INFO_TITLE); } +static void doc_setTitle(js_State *J) { doc_setMetaString(J, FZ_META_INFO_TITLE); } +static void doc_getSubject(js_State *J) { doc_getMetaString(J, FZ_META_INFO_SUBJECT); } +static void doc_setSubject(js_State *J) { doc_setMetaString(J, FZ_META_INFO_SUBJECT); } +static void doc_getKeywords(js_State *J) { doc_getMetaString(J, FZ_META_INFO_KEYWORDS); } +static void doc_setKeywords(js_State *J) { doc_setMetaString(J, FZ_META_INFO_KEYWORDS); } +static void doc_getCreator(js_State *J) { doc_getMetaString(J, FZ_META_INFO_CREATOR); } +static void doc_setCreator(js_State *J) { doc_setMetaString(J, FZ_META_INFO_CREATOR); } +static void doc_getProducer(js_State *J) { doc_getMetaString(J, FZ_META_INFO_PRODUCER); } +static void doc_setProducer(js_State *J) { doc_setMetaString(J, FZ_META_INFO_PRODUCER); } +static void doc_getCreationDate(js_State *J) { doc_getMetaDate(J, FZ_META_INFO_CREATIONDATE); } +static void doc_setCreationDate(js_State *J) { doc_setMetaDate(J, FZ_META_INFO_CREATIONDATE); } +static void doc_getModDate(js_State *J) { doc_getMetaDate(J, FZ_META_INFO_MODIFICATIONDATE); } +static void doc_setModDate(js_State *J) { doc_setMetaDate(J, FZ_META_INFO_MODIFICATIONDATE); } + +static void doc_resetForm(js_State *J) +{ + pdf_js *js = js_getcontext(J); + pdf_obj *field; + 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); + field = pdf_lookup_field(ctx, js->form, js_tostring(J, -1)); + if (field) + pdf_field_reset(ctx, js->doc, field); + 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 = unpack_arguments(J, "bUI", "cTo", "cCc", "cBcc", "cSubject", "cMessage", NULL); + pdf_mail_doc_event evt; + + evt.ask_user = js_isdefined(J, 1) ? js_toboolean(J, 1) : 1; + evt.to = js_tostring(J, 2); + evt.cc = js_tostring(J, 3); + evt.bcc = js_tostring(J, 4); + evt.subject = js_tostring(J, 5); + evt.message = js_tostring(J, 6); + + fz_try(js->ctx) + pdf_event_issue_mail_doc(js->ctx, js->doc, &evt); + fz_catch(js->ctx) + rethrow(js); +} + +static void doc_calculateNow(js_State *J) +{ + pdf_js *js = js_getcontext(J); + fz_try(js->ctx) + pdf_calculate_form(js->ctx, js->doc); + fz_catch(js->ctx) + rethrow(js); +} + +static void console_println(js_State *J) +{ + pdf_js *js = js_getcontext(J); + if (js->console && js->console->write) + { + int i, top = js_gettop(J); + js->console->write(js->console_user, "\n"); + for (i = 1; i < top; ++i) { + const char *s = js_tostring(J, i); + if (i > 1) + js->console->write(js->console_user, " "); + js->console->write(js->console_user, s); + } + } + js_pushboolean(J, 1); +} + +static void console_clear(js_State *J) +{ + pdf_js *js = js_getcontext(J); + if (js->console && js->console->clear) + js->console->clear(js->console_user); + js_pushundefined(J); +} + +static void console_show(js_State *J) +{ + pdf_js *js = js_getcontext(J); + if (js->console && js->console->show) + js->console->show(js->console_user); + js_pushundefined(J); +} + +static void console_hide(js_State *J) +{ + pdf_js *js = js_getcontext(J); + if (js->console && js->console->hide) + js->console->hide(js->console_user); + js_pushundefined(J); +} + +static void util_printf_d(fz_context *ctx, fz_buffer *out, int ds, int sign, int pad, unsigned int w, int base, int value) +{ + static const char *digits = "0123456789abcdef"; + char buf[50]; + unsigned int a, i; + int m = 0; + + if (w > sizeof buf) + w = sizeof buf; + + if (value < 0) + { + sign = '-'; + a = -value; + } + else + { + a = value; + } + + i = 0; + do + { + buf[i++] = digits[a % base]; + a /= base; + if (a > 0 && ++m == 3) + { + if (ds == 0) buf[i++] = ','; + if (ds == 2) buf[i++] = '.'; + m = 0; + } + } while (a); + + if (sign) + { + if (pad == '0') + while (i < w - 1) + buf[i++] = pad; + buf[i++] = sign; + } + while (i < w) + buf[i++] = pad; + + while (i > 0) + fz_append_byte(ctx, out, buf[--i]); +} + +static void util_printf_f(fz_context *ctx, fz_buffer *out, int ds, int sign, int pad, int special, unsigned int w, int p, double value) +{ + char buf[40], *point, *digits = buf; + size_t n = 0; + int m = 0; + + fz_snprintf(buf, sizeof buf, "%.*f", p, value); + + if (*digits == '-') + { + sign = '-'; + ++digits; + } + + if (*digits != '.' && (*digits < '0' || *digits > '9')) + { + fz_append_string(ctx, out, "nan"); + return; + } + + n = strlen(digits); + if (sign) + ++n; + point = strchr(digits, '.'); + if (point) + m = 3 - (point - digits) % 3; + else + { + m = 3 - n % 3; + if (special) + ++n; + } + if (m == 3) + m = 0; + + if (pad == '0' && sign) + fz_append_byte(ctx, out, sign); + for (; n < w; ++n) + fz_append_byte(ctx, out, pad); + if (pad == ' ' && sign) + fz_append_byte(ctx, out, sign); + + while (*digits && *digits != '.') + { + fz_append_byte(ctx, out, *digits++); + if (++m == 3 && *digits && *digits != '.') + { + if (ds == 0) fz_append_byte(ctx, out, ','); + if (ds == 2) fz_append_byte(ctx, out, '.'); + m = 0; + } + } + + if (*digits == '.' || special) + { + if (ds == 0 || ds == 1) + fz_append_byte(ctx, out, '.'); + else + fz_append_byte(ctx, out, ','); + } + + if (*digits == '.') + { + ++digits; + while (*digits) + fz_append_byte(ctx, out, *digits++); + } +} + +static void util_printf(js_State *J) +{ + pdf_js *js = js_getcontext(J); + fz_context *ctx = js->ctx; + const char *fmt = js_tostring(J, 1); + fz_buffer *out = NULL; + int ds, p, sign, pad, special; + unsigned int w; + int c, i = 1; + int failed = 0; + const char *str; + + fz_var(out); + fz_try(ctx) + { + out = fz_new_buffer(ctx, 256); + + while ((c = *fmt++) != 0) + { + if (c == '%') + { + c = *fmt++; + + ds = 1; + if (c == ',') + { + c = *fmt++; + if (!c) + break; + ds = c - '0'; + } + + special = 0; + sign = 0; + pad = ' '; + while (c == ' ' || c == '+' || c == '0' || c == '#') + { + if (c == '+') sign = '+'; + else if (c == ' ') sign = ' '; + else if (c == '0') pad = '0'; + else if (c == '#') special = 1; + c = *fmt++; + } + if (!pad) + pad = ' '; + if (!c) + break; + + w = 0; + while (c >= '0' && c <= '9') + { + w = w * 10 + (c - '0'); + c = *fmt++; + } + if (!c) + break; + + p = 0; + if (c == '.') + { + c = *fmt++; + while (c >= '0' && c <= '9') + { + p = p * 10 + (c - '0'); + c = *fmt++; + } + } + else + { + special = 1; + } + if (!c) + break; + + switch (c) + { + case '%': + fz_append_byte(ctx, out, '%'); + break; + case 'x': + util_printf_d(ctx, out, ds, sign, pad, w, 16, js_tryinteger(J, ++i, 0)); + break; + case 'd': + util_printf_d(ctx, out, ds, sign, pad, w, 10, js_tryinteger(J, ++i, 0)); + break; + case 'f': + util_printf_f(ctx, out, ds, sign, pad, special, w, p, js_trynumber(J, ++i, 0)); + break; + case 's': + default: + fz_append_string(ctx, out, js_trystring(J, ++i, "")); + } + } + else + { + fz_append_byte(ctx, out, c); + } + } + + str = fz_string_from_buffer(ctx, out); + if (js_try(J)) + { + failed = 1; + } + else + { + js_pushstring(J, str); + js_endtry(J); + } + } + fz_always(ctx) + fz_drop_buffer(ctx, out); + fz_catch(ctx) + rethrow(js); + + if (failed) + js_throw(J); +} + +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 int declare_dom(pdf_js *js) +{ + js_State *J = js->imp; + + if (js_try(J)) + { + return -1; + } + + /* 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 'event' object */ + js_newobject(J); + js_defglobal(J, "event", JS_READONLY | JS_DONTCONF | JS_DONTENUM); + + /* Create the 'util' object */ + js_newobject(J); + { + // TODO: util.printd + // TODO: util.printx + addmethod(J, "util.printf", util_printf, 1); + } + js_defglobal(J, "util", JS_READONLY | JS_DONTCONF | JS_DONTENUM); + + /* Create the 'app' object */ + js_newobject(J); + { +#ifdef _WIN32 + js_pushstring(J, "WIN"); +#elif defined(__APPLE__) + js_pushstring(J, "MAC"); +#else + js_pushstring(J, "UNIX"); +#endif + js_defproperty(J, -2, "app.platform", JS_READONLY | JS_DONTENUM | JS_DONTCONF); + + addmethod(J, "app.alert", app_alert, 6); + 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 Field prototype object */ + js_newobject(J); + { + addproperty(J, "Field.value", field_getValue, field_setValue); + addproperty(J, "Field.type", field_getType, field_setType); + 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 console object */ + js_newobject(J); + { + addmethod(J, "console.println", console_println, 1); + addmethod(J, "console.clear", console_clear, 0); + addmethod(J, "console.show", console_show, 0); + addmethod(J, "console.hide", console_hide, 0); + } + js_defglobal(J, "console", JS_READONLY | JS_DONTCONF | JS_DONTENUM); + + /* Put all of the Doc methods in the global object, which is used as + * the 'this' binding for regular non-strict function calls. */ + js_pushglobal(J); + { + addproperty(J, "Doc.numPages", doc_getNumPages, doc_setNumPages); + addproperty(J, "Doc.author", doc_getAuthor, doc_setAuthor); + addproperty(J, "Doc.title", doc_getTitle, doc_setTitle); + addproperty(J, "Doc.subject", doc_getSubject, doc_setSubject); + addproperty(J, "Doc.keywords", doc_getKeywords, doc_setKeywords); + addproperty(J, "Doc.creator", doc_getCreator, doc_setCreator); + addproperty(J, "Doc.producer", doc_getProducer, doc_setProducer); + addproperty(J, "Doc.creationDate", doc_getCreationDate, doc_setCreationDate); + addproperty(J, "Doc.modDate", doc_getModDate, doc_setModDate); + addmethod(J, "Doc.getField", doc_getField, 1); + addmethod(J, "Doc.resetForm", doc_resetForm, 0); + addmethod(J, "Doc.calculateNow", doc_calculateNow, 0); + addmethod(J, "Doc.print", doc_print, 0); + addmethod(J, "Doc.mailDoc", doc_mailDoc, 6); + } + js_pop(J, 1); + + js_endtry(J); + + return 0; +} + +static int preload_helpers(pdf_js *js) +{ + if (js_try(js->imp)) + return -1; + + /* 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 random sequence 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 (Math.random.seed = Math.random.seed * 48271 % 2147483647) / 2147483647; }\n" +"Math.random.seed = 217;\n" + ); +#endif + + js_dostring(js->imp, +#include "js/util.js.h" + ); + + js_endtry(js->imp); + return 0; +} + +void pdf_drop_js(fz_context *ctx, pdf_js *js) +{ + if (js) + { + if (js->console && js->console->drop) + js->console->drop(js->console, js->console_user); + js_freestate(js->imp); + fz_free(ctx, js); + } +} + +static void *pdf_js_alloc(void *actx, void *ptr, int n) +{ + return fz_realloc_no_throw(actx, ptr, n); +} + +static void default_js_console_clear(void *user) +{ + fz_context *ctx = user; + fz_write_string(ctx, fz_stddbg(ctx), "--- clear console ---\n"); +} + +static void default_js_console_write(void *user, const char *message) +{ + fz_context *ctx = user; + fz_write_string(ctx, fz_stddbg(ctx), message); +} + +static pdf_js_console default_js_console = { + NULL, + NULL, + NULL, + default_js_console_clear, + default_js_console_write, +}; + +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_LIBRARY, "cannot initialize javascript engine"); + + /* Also set our pdf_js context, so we can retrieve it in callbacks. */ + js_setcontext(js->imp, js); + + js->console = &default_js_console; + js->console_user = js->ctx; + + if (declare_dom(js)) + fz_throw(ctx, FZ_ERROR_LIBRARY, "cannot initialize dom interface"); + if (preload_helpers(js)) + fz_throw(ctx, FZ_ERROR_LIBRARY, "cannot initialize helper functions"); + } + fz_catch(ctx) + { + pdf_drop_js(ctx, js); + fz_rethrow(ctx); + } + + 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; + int in_op = 0; + + javascript = pdf_load_name_tree(ctx, doc, PDF_NAME(JavaScript)); + len = pdf_dict_len(ctx, javascript); + + fz_var(in_op); + + fz_try(ctx) + { + pdf_begin_operation(ctx, doc, "Document level Javascript"); + in_op = 1; + 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_load_stream_or_string_as_utf8(ctx, code); + char buf[100]; + if (pdf_is_indirect(ctx, code)) + fz_snprintf(buf, sizeof buf, "%d", pdf_to_num(ctx, code)); + else + fz_snprintf(buf, sizeof buf, "Root/Names/JavaScript/Names/%d/JS", (i+1)*2); + pdf_js_execute(js, buf, codebuf, NULL); + fz_free(ctx, codebuf); + } + pdf_end_operation(ctx, doc); + } + fz_always(ctx) + pdf_drop_obj(ctx, javascript); + fz_catch(ctx) + { + if (in_op) + pdf_abandon_operation(ctx, doc); + fz_rethrow(ctx); + } +} + +void pdf_js_event_init(pdf_js *js, pdf_obj *target, const char *value, int willCommit) +{ + if (js) + { + js_getglobal(js->imp, "event"); + { + js_pushboolean(js->imp, 1); + js_setproperty(js->imp, -2, "rc"); + + js_pushboolean(js->imp, willCommit); + js_setproperty(js->imp, -2, "willCommit"); + + js_getregistry(js->imp, "Field"); + js_newuserdata(js->imp, "Field", pdf_keep_obj(js->ctx, target), field_finalize); + js_setproperty(js->imp, -2, "target"); + + js_pushstring(js->imp, value); + js_setproperty(js->imp, -2, "value"); + } + js_pop(js->imp, 1); + } +} + +int pdf_js_event_result(pdf_js *js) +{ + int rc = 1; + if (js) + { + js_getglobal(js->imp, "event"); + js_getproperty(js->imp, -1, "rc"); + rc = js_tryboolean(js->imp, -1, 1); + js_pop(js->imp, 2); + } + return rc; +} + +int pdf_js_event_result_validate(pdf_js *js, char **newtext) +{ + int rc = 1; + *newtext = NULL; + if (js) + { + js_getglobal(js->imp, "event"); + js_getproperty(js->imp, -1, "rc"); + rc = js_tryboolean(js->imp, -1, 1); + js_pop(js->imp, 1); + if (rc) + { + js_getproperty(js->imp, -1, "value"); + *newtext = fz_strdup(js->ctx, js_trystring(js->imp, -1, "")); + js_pop(js->imp, 1); + } + js_pop(js->imp, 1); + } + return rc; +} + +void pdf_js_event_init_keystroke(pdf_js *js, pdf_obj *target, pdf_keystroke_event *evt) +{ + if (js) + { + pdf_js_event_init(js, target, evt->value, evt->willCommit); + js_getglobal(js->imp, "event"); + { + js_pushstring(js->imp, evt->change); + js_setproperty(js->imp, -2, "change"); + js_pushnumber(js->imp, evt->selStart); + js_setproperty(js->imp, -2, "selStart"); + js_pushnumber(js->imp, evt->selEnd); + js_setproperty(js->imp, -2, "selEnd"); + } + js_pop(js->imp, 1); + } +} + +int pdf_js_event_result_keystroke(pdf_js *js, pdf_keystroke_event *evt) +{ + int rc = 1; + if (js) + { + js_getglobal(js->imp, "event"); + { + js_getproperty(js->imp, -1, "rc"); + rc = js_tryboolean(js->imp, -1, 1); + js_pop(js->imp, 1); + if (rc) + { + js_getproperty(js->imp, -1, "change"); + evt->newChange = fz_strdup(js->ctx, js_trystring(js->imp, -1, "")); + js_pop(js->imp, 1); + js_getproperty(js->imp, -1, "value"); + evt->newValue = fz_strdup(js->ctx, js_trystring(js->imp, -1, "")); + js_pop(js->imp, 1); + js_getproperty(js->imp, -1, "selStart"); + evt->selStart = js_tryinteger(js->imp, -1, 0); + js_pop(js->imp, 1); + js_getproperty(js->imp, -1, "selEnd"); + evt->selEnd = js_tryinteger(js->imp, -1, 0); + js_pop(js->imp, 1); + } + } + js_pop(js->imp, 1); + } + return rc; +} + +char *pdf_js_event_value(pdf_js *js) +{ + char *value = NULL; + if (js) + { + js_getglobal(js->imp, "event"); + js_getproperty(js->imp, -1, "value"); + value = fz_strdup(js->ctx, js_trystring(js->imp, -1, "undefined")); + js_pop(js->imp, 2); + } + return value; +} + +void pdf_js_execute(pdf_js *js, const char *name, const char *source, char **result) +{ + fz_context *ctx; + js_State *J; + + if (!js) + return; + + ctx = js->ctx; + J = js->imp; + + pdf_begin_implicit_operation(ctx, js->doc); + fz_try(ctx) + { + if (js_ploadstring(J, name, source)) { + if (result) + *result = fz_strdup(ctx, js_trystring(J, -1, "Error")); + js_pop(J, 1); + } else { + js_pushundefined(J); + if (js_pcall(J, 0)) { + if (result) + *result = fz_strdup(ctx, js_trystring(J, -1, "Error")); + js_pop(J, 1); + } else { + if (result) + *result = fz_strdup(ctx, js_tryrepr(J, -1, "can't convert to string")); + js_pop(J, 1); + } + } + pdf_end_operation(ctx, js->doc); + } + fz_catch(ctx) + { + pdf_abandon_operation(ctx, js->doc); + fz_rethrow(ctx); + } +} + +pdf_js_console *pdf_js_get_console(fz_context *ctx, pdf_document *doc) +{ + return (doc && doc->js) ? doc->js->console : NULL; +} + +void pdf_js_set_console(fz_context *ctx, pdf_document *doc, pdf_js_console *console, void *user) +{ + if (doc->js) + { + if (doc->js->console && doc->js->console->drop) + doc->js->console->drop(doc->js->console, doc->js->console_user); + + doc->js->console = console; + doc->js->console_user = user; + } +} + +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) +{ + 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_event_init(pdf_js *js, pdf_obj *target, const char *value, int willCommit) { } +void pdf_js_event_init_keystroke(pdf_js *js, pdf_obj *target, pdf_keystroke_event *evt) { } +int pdf_js_event_result_keystroke(pdf_js *js, pdf_keystroke_event *evt) { return 1; } +int pdf_js_event_result(pdf_js *js) { return 1; } +char *pdf_js_event_value(pdf_js *js) { return ""; } +void pdf_js_execute(pdf_js *js, const char *name, const char *source, char **result) { } +int pdf_js_event_result_validate(pdf_js *js, char **newvalue) { *newvalue=NULL; return 1; } +pdf_js_console *pdf_js_get_console(fz_context *ctx, pdf_document *doc) { return NULL; } +void pdf_js_set_console(fz_context *ctx, pdf_document *doc, pdf_js_console *console, void *user) { } + +#endif /* FZ_ENABLE_JS */
