comparison 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
comparison
equal deleted inserted replaced
1:1d09e1dec1d9 2:b50eed0cc0ef
1 // Copyright (C) 2004-2022 Artifex Software, Inc.
2 //
3 // This file is part of MuPDF.
4 //
5 // MuPDF is free software: you can redistribute it and/or modify it under the
6 // terms of the GNU Affero General Public License as published by the Free
7 // Software Foundation, either version 3 of the License, or (at your option)
8 // any later version.
9 //
10 // MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY
11 // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
13 // details.
14 //
15 // You should have received a copy of the GNU Affero General Public License
16 // along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html>
17 //
18 // Alternative licensing terms are available from the licensor.
19 // For commercial licensing, see <https://www.artifex.com/> or contact
20 // Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco,
21 // CA 94129, USA, for further information.
22
23 #include "mupdf/fitz.h"
24 #include "mupdf/pdf.h"
25
26 #if FZ_ENABLE_JS
27
28 #include "mujs.h"
29
30 #include <stdarg.h>
31 #include <string.h>
32
33 struct pdf_js
34 {
35 fz_context *ctx;
36 pdf_document *doc;
37 pdf_obj *form;
38 js_State *imp;
39 pdf_js_console *console;
40 void *console_user;
41 };
42
43 FZ_NORETURN static void rethrow(pdf_js *js)
44 {
45 js_newerror(js->imp, fz_convert_error(js->ctx, NULL));
46 js_throw(js->imp);
47 }
48
49 /* Unpack argument object with named arguments into actual parameters. */
50 static pdf_js *unpack_arguments(js_State *J, ...)
51 {
52 if (js_isobject(J, 1))
53 {
54 int i = 1;
55 va_list args;
56
57 js_copy(J, 1);
58
59 va_start(args, J);
60 for (;;)
61 {
62 const char *s = va_arg(args, const char *);
63 if (!s)
64 break;
65 js_getproperty(J, -1, s);
66 js_replace(J, i++);
67 }
68 va_end(args);
69
70 js_pop(J, 1);
71 }
72 return js_getcontext(J);
73 }
74
75 static void app_alert(js_State *J)
76 {
77 pdf_js *js = unpack_arguments(J, "cMsg", "nIcon", "nType", "cTitle", "oDoc", "oCheckbox", NULL);
78 pdf_alert_event evt;
79
80 /* TODO: Currently we do not support app.openDoc() in javascript actions, hence
81 oDoc can only point to the current document (or not be passed). When mupdf
82 supports opening other documents oDoc must be converted to a pdf_document * that
83 can be passed to the callback. In the mean time, we just pas the current document.
84 */
85 evt.doc = js->doc;
86
87 evt.message = js_tostring(J, 1);
88 evt.icon_type = js_tointeger(J, 2);
89 evt.button_group_type = js_tointeger(J, 3);
90 evt.title = js_isdefined(J, 4) ? js_tostring(J, 4) : "PDF alert";
91
92 evt.has_check_box = 0;
93 evt.check_box_message = NULL;
94 evt.initially_checked = 0;
95 evt.finally_checked = 0;
96
97 if (js_isobject(J, 6))
98 {
99 evt.has_check_box = 1;
100 evt.check_box_message = "Do not show this message again";
101 if (js_hasproperty(J, 6, "cMsg"))
102 {
103 if (js_iscoercible(J, -1))
104 evt.check_box_message = js_tostring(J, -1);
105 js_pop(J, 1);
106 }
107 if (js_hasproperty(J, 6, "bInitialValue"))
108 {
109 evt.initially_checked = js_tointeger(J, -1);
110 js_pop(J, 1);
111 }
112 if (js_hasproperty(J, 6, "bAfterValue"))
113 {
114 evt.finally_checked = js_tointeger(J, -1);
115 js_pop(J, 1);
116 }
117 }
118
119 /* These are the default buttons automagically "pressed"
120 when the dialog box window is closed in Acrobat. */
121 switch (evt.button_group_type)
122 {
123 default:
124 case PDF_ALERT_BUTTON_GROUP_OK:
125 evt.button_pressed = PDF_ALERT_BUTTON_OK;
126 break;
127 case PDF_ALERT_BUTTON_GROUP_OK_CANCEL:
128 evt.button_pressed = PDF_ALERT_BUTTON_CANCEL;
129 break;
130 case PDF_ALERT_BUTTON_GROUP_YES_NO:
131 evt.button_pressed = PDF_ALERT_BUTTON_YES;
132 break;
133 case PDF_ALERT_BUTTON_GROUP_YES_NO_CANCEL:
134 evt.button_pressed = PDF_ALERT_BUTTON_CANCEL;
135 break;
136 }
137
138 fz_try(js->ctx)
139 pdf_event_issue_alert(js->ctx, js->doc, &evt);
140 fz_catch(js->ctx)
141 rethrow(js);
142
143 if (js_isobject(J, 6))
144 {
145 js_pushboolean(js->imp, evt.finally_checked);
146 js_setproperty(js->imp, 6, "bAfterValue");
147 }
148
149 js_pushnumber(J, evt.button_pressed);
150 }
151
152 static void app_execMenuItem(js_State *J)
153 {
154 pdf_js *js = js_getcontext(J);
155 const char *cMenuItem = js_tostring(J, 1);
156 fz_try(js->ctx)
157 pdf_event_issue_exec_menu_item(js->ctx, js->doc, cMenuItem);
158 fz_catch(js->ctx)
159 rethrow(js);
160 }
161
162 static void app_launchURL(js_State *J)
163 {
164 pdf_js *js = js_getcontext(J);
165 const char *cUrl = js_tostring(J, 1);
166 int bNewFrame = js_toboolean(J, 1);
167 fz_try(js->ctx)
168 pdf_event_issue_launch_url(js->ctx, js->doc, cUrl, bNewFrame);
169 fz_catch(js->ctx)
170 rethrow(js);
171 }
172
173 static void field_finalize(js_State *J, void *p)
174 {
175 pdf_js *js = js_getcontext(J);
176 pdf_drop_obj(js->ctx, p);
177 }
178
179 static void field_buttonSetCaption(js_State *J)
180 {
181 pdf_js *js = js_getcontext(J);
182 pdf_obj *field = js_touserdata(J, 0, "Field");
183 const char *cCaption = js_tostring(J, 1);
184 fz_try(js->ctx)
185 pdf_field_set_button_caption(js->ctx, field, cCaption);
186 fz_catch(js->ctx)
187 rethrow(js);
188 }
189
190 static void field_getName(js_State *J)
191 {
192 pdf_js *js = js_getcontext(J);
193 pdf_obj *field = js_touserdata(J, 0, "Field");
194 char *name = NULL;
195 fz_try(js->ctx)
196 name = pdf_load_field_name(js->ctx, field);
197 fz_catch(js->ctx)
198 rethrow(js);
199 if (js_try(J)) {
200 fz_free(js->ctx, name);
201 js_throw(J);
202 } else {
203 js_pushstring(J, name);
204 js_endtry(J);
205 fz_free(js->ctx, name);
206 }
207 }
208
209 static void field_setName(js_State *J)
210 {
211 pdf_js *js = js_getcontext(J);
212 fz_warn(js->ctx, "Unexpected call to field_setName");
213 }
214
215 static void field_getDisplay(js_State *J)
216 {
217 pdf_js *js = js_getcontext(J);
218 pdf_obj *field = js_touserdata(J, 0, "Field");
219 int display = 0;
220 fz_try(js->ctx)
221 display = pdf_field_display(js->ctx, field);
222 fz_catch(js->ctx)
223 rethrow(js);
224 js_pushnumber(J, display);
225 }
226
227 static void field_setDisplay(js_State *J)
228 {
229 pdf_js *js = js_getcontext(J);
230 pdf_obj *field = js_touserdata(J, 0, "Field");
231 int display = js_tonumber(J, 1);
232 fz_try(js->ctx)
233 pdf_field_set_display(js->ctx, field, display);
234 fz_catch(js->ctx)
235 rethrow(js);
236 }
237
238 static pdf_obj *load_color(pdf_js *js, int idx)
239 {
240 fz_context *ctx = js->ctx;
241 pdf_document *doc = js->doc;
242 js_State *J = js->imp;
243
244 pdf_obj *color = NULL;
245 int i, n;
246 float c;
247
248 n = js_getlength(J, idx);
249
250 /* The only legitimate color expressed as an array of length 1
251 * is [T], meaning transparent. Return a NULL object to represent
252 * transparent */
253 if (n <= 1)
254 return NULL;
255
256 fz_var(color);
257
258 fz_try(ctx)
259 {
260 color = pdf_new_array(ctx, doc, n-1);
261 for (i = 0; i < n-1; i++)
262 {
263 js_getindex(J, idx, i+1);
264 c = js_tonumber(J, -1);
265 js_pop(J, 1);
266
267 pdf_array_push_real(ctx, color, c);
268 }
269 }
270 fz_catch(ctx)
271 {
272 pdf_drop_obj(ctx, color);
273 rethrow(js);
274 }
275
276 return color;
277 }
278
279 static void field_getFillColor(js_State *J)
280 {
281 js_pushundefined(J);
282 }
283
284 static void field_setFillColor(js_State *J)
285 {
286 pdf_js *js = js_getcontext(J);
287 pdf_obj *field = js_touserdata(J, 0, "Field");
288 pdf_obj *color = load_color(js, 1);
289 fz_try(js->ctx)
290 pdf_field_set_fill_color(js->ctx, field, color);
291 fz_always(js->ctx)
292 pdf_drop_obj(js->ctx, color);
293 fz_catch(js->ctx)
294 rethrow(js);
295 }
296
297 static void field_getTextColor(js_State *J)
298 {
299 js_pushundefined(J);
300 }
301
302 static void field_setTextColor(js_State *J)
303 {
304 pdf_js *js = js_getcontext(J);
305 pdf_obj *field = js_touserdata(J, 0, "Field");
306 pdf_obj *color = load_color(js, 1);
307 fz_try(js->ctx)
308 pdf_field_set_text_color(js->ctx, field, color);
309 fz_always(js->ctx)
310 pdf_drop_obj(js->ctx, color);
311 fz_catch(js->ctx)
312 rethrow(js);
313 }
314
315 static void field_getBorderStyle(js_State *J)
316 {
317 pdf_js *js = js_getcontext(J);
318 pdf_obj *field = js_touserdata(J, 0, "Field");
319 const char *border_style = NULL;
320 fz_try(js->ctx)
321 border_style = pdf_field_border_style(js->ctx, field);
322 fz_catch(js->ctx)
323 rethrow(js);
324 js_pushstring(J, border_style);
325 }
326
327 static void field_setBorderStyle(js_State *J)
328 {
329 pdf_js *js = js_getcontext(J);
330 pdf_obj *field = js_touserdata(J, 0, "Field");
331 const char *border_style = js_tostring(J, 1);
332 fz_try(js->ctx)
333 pdf_field_set_border_style(js->ctx, field, border_style);
334 fz_catch(js->ctx)
335 rethrow(js);
336 }
337
338 static void field_getValue(js_State *J)
339 {
340 pdf_js *js = js_getcontext(J);
341 pdf_obj *field = js_touserdata(J, 0, "Field");
342 const char *str = NULL;
343 char *end;
344 double num;
345
346 fz_try(js->ctx)
347 str = pdf_field_value(js->ctx, field);
348 fz_catch(js->ctx)
349 rethrow(js);
350
351 num = strtod(str, &end);
352 if (*str && *end == 0)
353 js_pushnumber(J, num);
354 else
355 js_pushstring(J, str);
356 }
357
358 static void field_setValue(js_State *J)
359 {
360 pdf_js *js = js_getcontext(J);
361 pdf_obj *field = js_touserdata(J, 0, "Field");
362 const char *value = js_tostring(J, 1);
363
364 fz_try(js->ctx)
365 (void)pdf_set_field_value(js->ctx, js->doc, field, value, 0);
366 fz_catch(js->ctx)
367 rethrow(js);
368 }
369
370 static void field_getType(js_State *J)
371 {
372 pdf_js *js = js_getcontext(J);
373 pdf_obj *field = js_touserdata(J, 0, "Field");
374 const char *type;
375
376 fz_try(js->ctx)
377 type = pdf_field_type_string(js->ctx, field);
378 fz_catch(js->ctx)
379 rethrow(js);
380
381 js_pushstring(J, type);
382 }
383
384 static void field_setType(js_State *J)
385 {
386 pdf_js *js = js_getcontext(J);
387 fz_warn(js->ctx, "Unexpected call to field_setType");
388 }
389
390 static void doc_getField(js_State *J)
391 {
392 pdf_js *js = js_getcontext(J);
393 fz_context *ctx = js->ctx;
394 const char *cName = js_tostring(J, 1);
395 pdf_obj *dict = NULL;
396
397 fz_try(ctx)
398 dict = pdf_lookup_field(ctx, js->form, cName);
399 fz_catch(ctx)
400 rethrow(js);
401
402 if (dict)
403 {
404 js_getregistry(J, "Field");
405 js_newuserdata(J, "Field", pdf_keep_obj(js->ctx, dict), field_finalize);
406 }
407 else
408 {
409 js_pushnull(J);
410 }
411 }
412
413 static void doc_getNumPages(js_State *J)
414 {
415 pdf_js *js = js_getcontext(J);
416 int pages = pdf_count_pages(js->ctx, js->doc);
417 js_pushnumber(J, pages);
418 }
419
420 static void doc_setNumPages(js_State *J)
421 {
422 pdf_js *js = js_getcontext(J);
423 fz_warn(js->ctx, "Unexpected call to doc_setNumPages");
424 }
425
426 static void doc_getMetaString(js_State *J, const char *key)
427 {
428 pdf_js *js = js_getcontext(J);
429 char buf[256];
430 int ret;
431
432 fz_try(js->ctx)
433 ret = fz_lookup_metadata(js->ctx, &js->doc->super, key, buf, nelem(buf)) > 0;
434 fz_catch(js->ctx)
435 rethrow(js);
436
437 if (ret > 0)
438 js_pushstring(J, buf);
439 else
440 js_pushundefined(J);
441 }
442
443 static void doc_setMetaString(js_State *J, const char *key)
444 {
445 pdf_js *js = js_getcontext(J);
446 const char *value = js_tostring(J, 1);
447 fz_set_metadata(js->ctx, &js->doc->super, key, value);
448 }
449
450 static void doc_getMetaDate(js_State *J, const char *key)
451 {
452 pdf_js *js = js_getcontext(J);
453 char buf[256];
454 int ret;
455 double time;
456
457 fz_try(js->ctx)
458 {
459 ret = fz_lookup_metadata(js->ctx, &js->doc->super, key, buf, nelem(buf)) > 0;
460 if (ret > 0)
461 time = pdf_parse_date(js->ctx, buf);
462 }
463 fz_catch(js->ctx)
464 rethrow(js);
465
466 if (ret > 0)
467 {
468 js_getglobal(J, "Date");
469 js_pushnumber(J, time * 1000);
470 js_construct(J, 1);
471 }
472 else
473 js_pushundefined(J);
474 }
475
476 static void doc_setMetaDate(js_State *J, const char *key)
477 {
478 pdf_js *js = js_getcontext(J);
479 int64_t time;
480 char value[40];
481
482 // Coerce the argument into a date object and extract the time value.
483 js_getglobal(J, "Date");
484 js_copy(J, 1);
485 js_construct(J, 1);
486 time = js_tonumber(J, -1) / 1000;
487 js_pop(J, 1);
488
489 fz_try(js->ctx)
490 if (pdf_format_date(js->ctx, time, value, nelem(value)))
491 fz_set_metadata(js->ctx, &js->doc->super, key, value);
492 fz_catch(js->ctx)
493 rethrow(js);
494 }
495
496 static void doc_getAuthor(js_State *J) { doc_getMetaString(J, FZ_META_INFO_AUTHOR); }
497 static void doc_setAuthor(js_State *J) { doc_setMetaString(J, FZ_META_INFO_AUTHOR); }
498 static void doc_getTitle(js_State *J) { doc_getMetaString(J, FZ_META_INFO_TITLE); }
499 static void doc_setTitle(js_State *J) { doc_setMetaString(J, FZ_META_INFO_TITLE); }
500 static void doc_getSubject(js_State *J) { doc_getMetaString(J, FZ_META_INFO_SUBJECT); }
501 static void doc_setSubject(js_State *J) { doc_setMetaString(J, FZ_META_INFO_SUBJECT); }
502 static void doc_getKeywords(js_State *J) { doc_getMetaString(J, FZ_META_INFO_KEYWORDS); }
503 static void doc_setKeywords(js_State *J) { doc_setMetaString(J, FZ_META_INFO_KEYWORDS); }
504 static void doc_getCreator(js_State *J) { doc_getMetaString(J, FZ_META_INFO_CREATOR); }
505 static void doc_setCreator(js_State *J) { doc_setMetaString(J, FZ_META_INFO_CREATOR); }
506 static void doc_getProducer(js_State *J) { doc_getMetaString(J, FZ_META_INFO_PRODUCER); }
507 static void doc_setProducer(js_State *J) { doc_setMetaString(J, FZ_META_INFO_PRODUCER); }
508 static void doc_getCreationDate(js_State *J) { doc_getMetaDate(J, FZ_META_INFO_CREATIONDATE); }
509 static void doc_setCreationDate(js_State *J) { doc_setMetaDate(J, FZ_META_INFO_CREATIONDATE); }
510 static void doc_getModDate(js_State *J) { doc_getMetaDate(J, FZ_META_INFO_MODIFICATIONDATE); }
511 static void doc_setModDate(js_State *J) { doc_setMetaDate(J, FZ_META_INFO_MODIFICATIONDATE); }
512
513 static void doc_resetForm(js_State *J)
514 {
515 pdf_js *js = js_getcontext(J);
516 pdf_obj *field;
517 fz_context *ctx = js->ctx;
518 int i, n;
519
520 /* An array of fields has been passed in. Call pdf_reset_field on each item. */
521 if (js_isarray(J, 1))
522 {
523 n = js_getlength(J, 1);
524 for (i = 0; i < n; ++i)
525 {
526 js_getindex(J, 1, i);
527 field = pdf_lookup_field(ctx, js->form, js_tostring(J, -1));
528 if (field)
529 pdf_field_reset(ctx, js->doc, field);
530 js_pop(J, 1);
531 }
532 }
533
534 /* No argument or null passed in means reset all. */
535 else
536 {
537 n = pdf_array_len(ctx, js->form);
538 for (i = 0; i < n; i++)
539 {
540 fz_try(ctx)
541 pdf_field_reset(ctx, js->doc, pdf_array_get(ctx, js->form, i));
542 fz_catch(ctx)
543 rethrow(js);
544 }
545 }
546 }
547
548 static void doc_print(js_State *J)
549 {
550 pdf_js *js = js_getcontext(J);
551 fz_try(js->ctx)
552 pdf_event_issue_print(js->ctx, js->doc);
553 fz_catch(js->ctx)
554 rethrow(js);
555 }
556
557 static void doc_mailDoc(js_State *J)
558 {
559 pdf_js *js = unpack_arguments(J, "bUI", "cTo", "cCc", "cBcc", "cSubject", "cMessage", NULL);
560 pdf_mail_doc_event evt;
561
562 evt.ask_user = js_isdefined(J, 1) ? js_toboolean(J, 1) : 1;
563 evt.to = js_tostring(J, 2);
564 evt.cc = js_tostring(J, 3);
565 evt.bcc = js_tostring(J, 4);
566 evt.subject = js_tostring(J, 5);
567 evt.message = js_tostring(J, 6);
568
569 fz_try(js->ctx)
570 pdf_event_issue_mail_doc(js->ctx, js->doc, &evt);
571 fz_catch(js->ctx)
572 rethrow(js);
573 }
574
575 static void doc_calculateNow(js_State *J)
576 {
577 pdf_js *js = js_getcontext(J);
578 fz_try(js->ctx)
579 pdf_calculate_form(js->ctx, js->doc);
580 fz_catch(js->ctx)
581 rethrow(js);
582 }
583
584 static void console_println(js_State *J)
585 {
586 pdf_js *js = js_getcontext(J);
587 if (js->console && js->console->write)
588 {
589 int i, top = js_gettop(J);
590 js->console->write(js->console_user, "\n");
591 for (i = 1; i < top; ++i) {
592 const char *s = js_tostring(J, i);
593 if (i > 1)
594 js->console->write(js->console_user, " ");
595 js->console->write(js->console_user, s);
596 }
597 }
598 js_pushboolean(J, 1);
599 }
600
601 static void console_clear(js_State *J)
602 {
603 pdf_js *js = js_getcontext(J);
604 if (js->console && js->console->clear)
605 js->console->clear(js->console_user);
606 js_pushundefined(J);
607 }
608
609 static void console_show(js_State *J)
610 {
611 pdf_js *js = js_getcontext(J);
612 if (js->console && js->console->show)
613 js->console->show(js->console_user);
614 js_pushundefined(J);
615 }
616
617 static void console_hide(js_State *J)
618 {
619 pdf_js *js = js_getcontext(J);
620 if (js->console && js->console->hide)
621 js->console->hide(js->console_user);
622 js_pushundefined(J);
623 }
624
625 static void util_printf_d(fz_context *ctx, fz_buffer *out, int ds, int sign, int pad, unsigned int w, int base, int value)
626 {
627 static const char *digits = "0123456789abcdef";
628 char buf[50];
629 unsigned int a, i;
630 int m = 0;
631
632 if (w > sizeof buf)
633 w = sizeof buf;
634
635 if (value < 0)
636 {
637 sign = '-';
638 a = -value;
639 }
640 else
641 {
642 a = value;
643 }
644
645 i = 0;
646 do
647 {
648 buf[i++] = digits[a % base];
649 a /= base;
650 if (a > 0 && ++m == 3)
651 {
652 if (ds == 0) buf[i++] = ',';
653 if (ds == 2) buf[i++] = '.';
654 m = 0;
655 }
656 } while (a);
657
658 if (sign)
659 {
660 if (pad == '0')
661 while (i < w - 1)
662 buf[i++] = pad;
663 buf[i++] = sign;
664 }
665 while (i < w)
666 buf[i++] = pad;
667
668 while (i > 0)
669 fz_append_byte(ctx, out, buf[--i]);
670 }
671
672 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)
673 {
674 char buf[40], *point, *digits = buf;
675 size_t n = 0;
676 int m = 0;
677
678 fz_snprintf(buf, sizeof buf, "%.*f", p, value);
679
680 if (*digits == '-')
681 {
682 sign = '-';
683 ++digits;
684 }
685
686 if (*digits != '.' && (*digits < '0' || *digits > '9'))
687 {
688 fz_append_string(ctx, out, "nan");
689 return;
690 }
691
692 n = strlen(digits);
693 if (sign)
694 ++n;
695 point = strchr(digits, '.');
696 if (point)
697 m = 3 - (point - digits) % 3;
698 else
699 {
700 m = 3 - n % 3;
701 if (special)
702 ++n;
703 }
704 if (m == 3)
705 m = 0;
706
707 if (pad == '0' && sign)
708 fz_append_byte(ctx, out, sign);
709 for (; n < w; ++n)
710 fz_append_byte(ctx, out, pad);
711 if (pad == ' ' && sign)
712 fz_append_byte(ctx, out, sign);
713
714 while (*digits && *digits != '.')
715 {
716 fz_append_byte(ctx, out, *digits++);
717 if (++m == 3 && *digits && *digits != '.')
718 {
719 if (ds == 0) fz_append_byte(ctx, out, ',');
720 if (ds == 2) fz_append_byte(ctx, out, '.');
721 m = 0;
722 }
723 }
724
725 if (*digits == '.' || special)
726 {
727 if (ds == 0 || ds == 1)
728 fz_append_byte(ctx, out, '.');
729 else
730 fz_append_byte(ctx, out, ',');
731 }
732
733 if (*digits == '.')
734 {
735 ++digits;
736 while (*digits)
737 fz_append_byte(ctx, out, *digits++);
738 }
739 }
740
741 static void util_printf(js_State *J)
742 {
743 pdf_js *js = js_getcontext(J);
744 fz_context *ctx = js->ctx;
745 const char *fmt = js_tostring(J, 1);
746 fz_buffer *out = NULL;
747 int ds, p, sign, pad, special;
748 unsigned int w;
749 int c, i = 1;
750 int failed = 0;
751 const char *str;
752
753 fz_var(out);
754 fz_try(ctx)
755 {
756 out = fz_new_buffer(ctx, 256);
757
758 while ((c = *fmt++) != 0)
759 {
760 if (c == '%')
761 {
762 c = *fmt++;
763
764 ds = 1;
765 if (c == ',')
766 {
767 c = *fmt++;
768 if (!c)
769 break;
770 ds = c - '0';
771 }
772
773 special = 0;
774 sign = 0;
775 pad = ' ';
776 while (c == ' ' || c == '+' || c == '0' || c == '#')
777 {
778 if (c == '+') sign = '+';
779 else if (c == ' ') sign = ' ';
780 else if (c == '0') pad = '0';
781 else if (c == '#') special = 1;
782 c = *fmt++;
783 }
784 if (!pad)
785 pad = ' ';
786 if (!c)
787 break;
788
789 w = 0;
790 while (c >= '0' && c <= '9')
791 {
792 w = w * 10 + (c - '0');
793 c = *fmt++;
794 }
795 if (!c)
796 break;
797
798 p = 0;
799 if (c == '.')
800 {
801 c = *fmt++;
802 while (c >= '0' && c <= '9')
803 {
804 p = p * 10 + (c - '0');
805 c = *fmt++;
806 }
807 }
808 else
809 {
810 special = 1;
811 }
812 if (!c)
813 break;
814
815 switch (c)
816 {
817 case '%':
818 fz_append_byte(ctx, out, '%');
819 break;
820 case 'x':
821 util_printf_d(ctx, out, ds, sign, pad, w, 16, js_tryinteger(J, ++i, 0));
822 break;
823 case 'd':
824 util_printf_d(ctx, out, ds, sign, pad, w, 10, js_tryinteger(J, ++i, 0));
825 break;
826 case 'f':
827 util_printf_f(ctx, out, ds, sign, pad, special, w, p, js_trynumber(J, ++i, 0));
828 break;
829 case 's':
830 default:
831 fz_append_string(ctx, out, js_trystring(J, ++i, ""));
832 }
833 }
834 else
835 {
836 fz_append_byte(ctx, out, c);
837 }
838 }
839
840 str = fz_string_from_buffer(ctx, out);
841 if (js_try(J))
842 {
843 failed = 1;
844 }
845 else
846 {
847 js_pushstring(J, str);
848 js_endtry(J);
849 }
850 }
851 fz_always(ctx)
852 fz_drop_buffer(ctx, out);
853 fz_catch(ctx)
854 rethrow(js);
855
856 if (failed)
857 js_throw(J);
858 }
859
860 static void addmethod(js_State *J, const char *name, js_CFunction fun, int n)
861 {
862 const char *realname = strchr(name, '.');
863 realname = realname ? realname + 1 : name;
864 js_newcfunction(J, fun, name, n);
865 js_defproperty(J, -2, realname, JS_READONLY | JS_DONTENUM | JS_DONTCONF);
866 }
867
868 static void addproperty(js_State *J, const char *name, js_CFunction getfun, js_CFunction setfun)
869 {
870 const char *realname = strchr(name, '.');
871 realname = realname ? realname + 1 : name;
872 js_newcfunction(J, getfun, name, 0);
873 js_newcfunction(J, setfun, name, 1);
874 js_defaccessor(J, -3, realname, JS_READONLY | JS_DONTENUM | JS_DONTCONF);
875 }
876
877 static int declare_dom(pdf_js *js)
878 {
879 js_State *J = js->imp;
880
881 if (js_try(J))
882 {
883 return -1;
884 }
885
886 /* Allow access to the global environment via the 'global' name */
887 js_pushglobal(J);
888 js_defglobal(J, "global", JS_READONLY | JS_DONTCONF | JS_DONTENUM);
889
890 /* Create the 'event' object */
891 js_newobject(J);
892 js_defglobal(J, "event", JS_READONLY | JS_DONTCONF | JS_DONTENUM);
893
894 /* Create the 'util' object */
895 js_newobject(J);
896 {
897 // TODO: util.printd
898 // TODO: util.printx
899 addmethod(J, "util.printf", util_printf, 1);
900 }
901 js_defglobal(J, "util", JS_READONLY | JS_DONTCONF | JS_DONTENUM);
902
903 /* Create the 'app' object */
904 js_newobject(J);
905 {
906 #ifdef _WIN32
907 js_pushstring(J, "WIN");
908 #elif defined(__APPLE__)
909 js_pushstring(J, "MAC");
910 #else
911 js_pushstring(J, "UNIX");
912 #endif
913 js_defproperty(J, -2, "app.platform", JS_READONLY | JS_DONTENUM | JS_DONTCONF);
914
915 addmethod(J, "app.alert", app_alert, 6);
916 addmethod(J, "app.execMenuItem", app_execMenuItem, 1);
917 addmethod(J, "app.launchURL", app_launchURL, 2);
918 }
919 js_defglobal(J, "app", JS_READONLY | JS_DONTCONF | JS_DONTENUM);
920
921 /* Create the Field prototype object */
922 js_newobject(J);
923 {
924 addproperty(J, "Field.value", field_getValue, field_setValue);
925 addproperty(J, "Field.type", field_getType, field_setType);
926 addproperty(J, "Field.borderStyle", field_getBorderStyle, field_setBorderStyle);
927 addproperty(J, "Field.textColor", field_getTextColor, field_setTextColor);
928 addproperty(J, "Field.fillColor", field_getFillColor, field_setFillColor);
929 addproperty(J, "Field.display", field_getDisplay, field_setDisplay);
930 addproperty(J, "Field.name", field_getName, field_setName);
931 addmethod(J, "Field.buttonSetCaption", field_buttonSetCaption, 1);
932 }
933 js_setregistry(J, "Field");
934
935 /* Create the console object */
936 js_newobject(J);
937 {
938 addmethod(J, "console.println", console_println, 1);
939 addmethod(J, "console.clear", console_clear, 0);
940 addmethod(J, "console.show", console_show, 0);
941 addmethod(J, "console.hide", console_hide, 0);
942 }
943 js_defglobal(J, "console", JS_READONLY | JS_DONTCONF | JS_DONTENUM);
944
945 /* Put all of the Doc methods in the global object, which is used as
946 * the 'this' binding for regular non-strict function calls. */
947 js_pushglobal(J);
948 {
949 addproperty(J, "Doc.numPages", doc_getNumPages, doc_setNumPages);
950 addproperty(J, "Doc.author", doc_getAuthor, doc_setAuthor);
951 addproperty(J, "Doc.title", doc_getTitle, doc_setTitle);
952 addproperty(J, "Doc.subject", doc_getSubject, doc_setSubject);
953 addproperty(J, "Doc.keywords", doc_getKeywords, doc_setKeywords);
954 addproperty(J, "Doc.creator", doc_getCreator, doc_setCreator);
955 addproperty(J, "Doc.producer", doc_getProducer, doc_setProducer);
956 addproperty(J, "Doc.creationDate", doc_getCreationDate, doc_setCreationDate);
957 addproperty(J, "Doc.modDate", doc_getModDate, doc_setModDate);
958 addmethod(J, "Doc.getField", doc_getField, 1);
959 addmethod(J, "Doc.resetForm", doc_resetForm, 0);
960 addmethod(J, "Doc.calculateNow", doc_calculateNow, 0);
961 addmethod(J, "Doc.print", doc_print, 0);
962 addmethod(J, "Doc.mailDoc", doc_mailDoc, 6);
963 }
964 js_pop(J, 1);
965
966 js_endtry(J);
967
968 return 0;
969 }
970
971 static int preload_helpers(pdf_js *js)
972 {
973 if (js_try(js->imp))
974 return -1;
975
976 /* When testing on the cluster:
977 * Use a fixed date for "new Date" and Date.now().
978 * Sadly, this breaks uses of the Date function without the new keyword.
979 * Return a fixed random sequence from Math.random().
980 */
981 #ifdef CLUSTER
982 js_dostring(js->imp,
983 "var MuPDFOldDate = Date\n"
984 "Date = function() { return new MuPDFOldDate(298252800000); }\n"
985 "Date.now = function() { return 298252800000; }\n"
986 "Date.UTC = function() { return 298252800000; }\n"
987 "Date.parse = MuPDFOldDate.parse;\n"
988 "Math.random = function() { return (Math.random.seed = Math.random.seed * 48271 % 2147483647) / 2147483647; }\n"
989 "Math.random.seed = 217;\n"
990 );
991 #endif
992
993 js_dostring(js->imp,
994 #include "js/util.js.h"
995 );
996
997 js_endtry(js->imp);
998 return 0;
999 }
1000
1001 void pdf_drop_js(fz_context *ctx, pdf_js *js)
1002 {
1003 if (js)
1004 {
1005 if (js->console && js->console->drop)
1006 js->console->drop(js->console, js->console_user);
1007 js_freestate(js->imp);
1008 fz_free(ctx, js);
1009 }
1010 }
1011
1012 static void *pdf_js_alloc(void *actx, void *ptr, int n)
1013 {
1014 return fz_realloc_no_throw(actx, ptr, n);
1015 }
1016
1017 static void default_js_console_clear(void *user)
1018 {
1019 fz_context *ctx = user;
1020 fz_write_string(ctx, fz_stddbg(ctx), "--- clear console ---\n");
1021 }
1022
1023 static void default_js_console_write(void *user, const char *message)
1024 {
1025 fz_context *ctx = user;
1026 fz_write_string(ctx, fz_stddbg(ctx), message);
1027 }
1028
1029 static pdf_js_console default_js_console = {
1030 NULL,
1031 NULL,
1032 NULL,
1033 default_js_console_clear,
1034 default_js_console_write,
1035 };
1036
1037 static pdf_js *pdf_new_js(fz_context *ctx, pdf_document *doc)
1038 {
1039 pdf_js *js = fz_malloc_struct(ctx, pdf_js);
1040
1041 js->ctx = ctx;
1042 js->doc = doc;
1043
1044 fz_try(ctx)
1045 {
1046 pdf_obj *root, *acroform;
1047
1048 /* Find the form array */
1049 root = pdf_dict_get(ctx, pdf_trailer(ctx, doc), PDF_NAME(Root));
1050 acroform = pdf_dict_get(ctx, root, PDF_NAME(AcroForm));
1051 js->form = pdf_dict_get(ctx, acroform, PDF_NAME(Fields));
1052
1053 /* Initialise the javascript engine, passing the fz_context for use in memory allocation. */
1054 js->imp = js_newstate(pdf_js_alloc, ctx, 0);
1055 if (!js->imp)
1056 fz_throw(ctx, FZ_ERROR_LIBRARY, "cannot initialize javascript engine");
1057
1058 /* Also set our pdf_js context, so we can retrieve it in callbacks. */
1059 js_setcontext(js->imp, js);
1060
1061 js->console = &default_js_console;
1062 js->console_user = js->ctx;
1063
1064 if (declare_dom(js))
1065 fz_throw(ctx, FZ_ERROR_LIBRARY, "cannot initialize dom interface");
1066 if (preload_helpers(js))
1067 fz_throw(ctx, FZ_ERROR_LIBRARY, "cannot initialize helper functions");
1068 }
1069 fz_catch(ctx)
1070 {
1071 pdf_drop_js(ctx, js);
1072 fz_rethrow(ctx);
1073 }
1074
1075 return js;
1076 }
1077
1078 static void pdf_js_load_document_level(pdf_js *js)
1079 {
1080 fz_context *ctx = js->ctx;
1081 pdf_document *doc = js->doc;
1082 pdf_obj *javascript;
1083 int len, i;
1084 int in_op = 0;
1085
1086 javascript = pdf_load_name_tree(ctx, doc, PDF_NAME(JavaScript));
1087 len = pdf_dict_len(ctx, javascript);
1088
1089 fz_var(in_op);
1090
1091 fz_try(ctx)
1092 {
1093 pdf_begin_operation(ctx, doc, "Document level Javascript");
1094 in_op = 1;
1095 for (i = 0; i < len; i++)
1096 {
1097 pdf_obj *fragment = pdf_dict_get_val(ctx, javascript, i);
1098 pdf_obj *code = pdf_dict_get(ctx, fragment, PDF_NAME(JS));
1099 char *codebuf = pdf_load_stream_or_string_as_utf8(ctx, code);
1100 char buf[100];
1101 if (pdf_is_indirect(ctx, code))
1102 fz_snprintf(buf, sizeof buf, "%d", pdf_to_num(ctx, code));
1103 else
1104 fz_snprintf(buf, sizeof buf, "Root/Names/JavaScript/Names/%d/JS", (i+1)*2);
1105 pdf_js_execute(js, buf, codebuf, NULL);
1106 fz_free(ctx, codebuf);
1107 }
1108 pdf_end_operation(ctx, doc);
1109 }
1110 fz_always(ctx)
1111 pdf_drop_obj(ctx, javascript);
1112 fz_catch(ctx)
1113 {
1114 if (in_op)
1115 pdf_abandon_operation(ctx, doc);
1116 fz_rethrow(ctx);
1117 }
1118 }
1119
1120 void pdf_js_event_init(pdf_js *js, pdf_obj *target, const char *value, int willCommit)
1121 {
1122 if (js)
1123 {
1124 js_getglobal(js->imp, "event");
1125 {
1126 js_pushboolean(js->imp, 1);
1127 js_setproperty(js->imp, -2, "rc");
1128
1129 js_pushboolean(js->imp, willCommit);
1130 js_setproperty(js->imp, -2, "willCommit");
1131
1132 js_getregistry(js->imp, "Field");
1133 js_newuserdata(js->imp, "Field", pdf_keep_obj(js->ctx, target), field_finalize);
1134 js_setproperty(js->imp, -2, "target");
1135
1136 js_pushstring(js->imp, value);
1137 js_setproperty(js->imp, -2, "value");
1138 }
1139 js_pop(js->imp, 1);
1140 }
1141 }
1142
1143 int pdf_js_event_result(pdf_js *js)
1144 {
1145 int rc = 1;
1146 if (js)
1147 {
1148 js_getglobal(js->imp, "event");
1149 js_getproperty(js->imp, -1, "rc");
1150 rc = js_tryboolean(js->imp, -1, 1);
1151 js_pop(js->imp, 2);
1152 }
1153 return rc;
1154 }
1155
1156 int pdf_js_event_result_validate(pdf_js *js, char **newtext)
1157 {
1158 int rc = 1;
1159 *newtext = NULL;
1160 if (js)
1161 {
1162 js_getglobal(js->imp, "event");
1163 js_getproperty(js->imp, -1, "rc");
1164 rc = js_tryboolean(js->imp, -1, 1);
1165 js_pop(js->imp, 1);
1166 if (rc)
1167 {
1168 js_getproperty(js->imp, -1, "value");
1169 *newtext = fz_strdup(js->ctx, js_trystring(js->imp, -1, ""));
1170 js_pop(js->imp, 1);
1171 }
1172 js_pop(js->imp, 1);
1173 }
1174 return rc;
1175 }
1176
1177 void pdf_js_event_init_keystroke(pdf_js *js, pdf_obj *target, pdf_keystroke_event *evt)
1178 {
1179 if (js)
1180 {
1181 pdf_js_event_init(js, target, evt->value, evt->willCommit);
1182 js_getglobal(js->imp, "event");
1183 {
1184 js_pushstring(js->imp, evt->change);
1185 js_setproperty(js->imp, -2, "change");
1186 js_pushnumber(js->imp, evt->selStart);
1187 js_setproperty(js->imp, -2, "selStart");
1188 js_pushnumber(js->imp, evt->selEnd);
1189 js_setproperty(js->imp, -2, "selEnd");
1190 }
1191 js_pop(js->imp, 1);
1192 }
1193 }
1194
1195 int pdf_js_event_result_keystroke(pdf_js *js, pdf_keystroke_event *evt)
1196 {
1197 int rc = 1;
1198 if (js)
1199 {
1200 js_getglobal(js->imp, "event");
1201 {
1202 js_getproperty(js->imp, -1, "rc");
1203 rc = js_tryboolean(js->imp, -1, 1);
1204 js_pop(js->imp, 1);
1205 if (rc)
1206 {
1207 js_getproperty(js->imp, -1, "change");
1208 evt->newChange = fz_strdup(js->ctx, js_trystring(js->imp, -1, ""));
1209 js_pop(js->imp, 1);
1210 js_getproperty(js->imp, -1, "value");
1211 evt->newValue = fz_strdup(js->ctx, js_trystring(js->imp, -1, ""));
1212 js_pop(js->imp, 1);
1213 js_getproperty(js->imp, -1, "selStart");
1214 evt->selStart = js_tryinteger(js->imp, -1, 0);
1215 js_pop(js->imp, 1);
1216 js_getproperty(js->imp, -1, "selEnd");
1217 evt->selEnd = js_tryinteger(js->imp, -1, 0);
1218 js_pop(js->imp, 1);
1219 }
1220 }
1221 js_pop(js->imp, 1);
1222 }
1223 return rc;
1224 }
1225
1226 char *pdf_js_event_value(pdf_js *js)
1227 {
1228 char *value = NULL;
1229 if (js)
1230 {
1231 js_getglobal(js->imp, "event");
1232 js_getproperty(js->imp, -1, "value");
1233 value = fz_strdup(js->ctx, js_trystring(js->imp, -1, "undefined"));
1234 js_pop(js->imp, 2);
1235 }
1236 return value;
1237 }
1238
1239 void pdf_js_execute(pdf_js *js, const char *name, const char *source, char **result)
1240 {
1241 fz_context *ctx;
1242 js_State *J;
1243
1244 if (!js)
1245 return;
1246
1247 ctx = js->ctx;
1248 J = js->imp;
1249
1250 pdf_begin_implicit_operation(ctx, js->doc);
1251 fz_try(ctx)
1252 {
1253 if (js_ploadstring(J, name, source)) {
1254 if (result)
1255 *result = fz_strdup(ctx, js_trystring(J, -1, "Error"));
1256 js_pop(J, 1);
1257 } else {
1258 js_pushundefined(J);
1259 if (js_pcall(J, 0)) {
1260 if (result)
1261 *result = fz_strdup(ctx, js_trystring(J, -1, "Error"));
1262 js_pop(J, 1);
1263 } else {
1264 if (result)
1265 *result = fz_strdup(ctx, js_tryrepr(J, -1, "can't convert to string"));
1266 js_pop(J, 1);
1267 }
1268 }
1269 pdf_end_operation(ctx, js->doc);
1270 }
1271 fz_catch(ctx)
1272 {
1273 pdf_abandon_operation(ctx, js->doc);
1274 fz_rethrow(ctx);
1275 }
1276 }
1277
1278 pdf_js_console *pdf_js_get_console(fz_context *ctx, pdf_document *doc)
1279 {
1280 return (doc && doc->js) ? doc->js->console : NULL;
1281 }
1282
1283 void pdf_js_set_console(fz_context *ctx, pdf_document *doc, pdf_js_console *console, void *user)
1284 {
1285 if (doc->js)
1286 {
1287 if (doc->js->console && doc->js->console->drop)
1288 doc->js->console->drop(doc->js->console, doc->js->console_user);
1289
1290 doc->js->console = console;
1291 doc->js->console_user = user;
1292 }
1293 }
1294
1295 void pdf_enable_js(fz_context *ctx, pdf_document *doc)
1296 {
1297 if (!doc->js)
1298 {
1299 doc->js = pdf_new_js(ctx, doc);
1300 pdf_js_load_document_level(doc->js);
1301 }
1302 }
1303
1304 void pdf_disable_js(fz_context *ctx, pdf_document *doc)
1305 {
1306 pdf_drop_js(ctx, doc->js);
1307 doc->js = NULL;
1308 }
1309
1310 int pdf_js_supported(fz_context *ctx, pdf_document *doc)
1311 {
1312 return doc->js != NULL;
1313 }
1314
1315 #else /* FZ_ENABLE_JS */
1316
1317 void pdf_drop_js(fz_context *ctx, pdf_js *js) { }
1318 void pdf_enable_js(fz_context *ctx, pdf_document *doc) { }
1319 void pdf_disable_js(fz_context *ctx, pdf_document *doc) { }
1320 int pdf_js_supported(fz_context *ctx, pdf_document *doc) { return 0; }
1321 void pdf_js_event_init(pdf_js *js, pdf_obj *target, const char *value, int willCommit) { }
1322 void pdf_js_event_init_keystroke(pdf_js *js, pdf_obj *target, pdf_keystroke_event *evt) { }
1323 int pdf_js_event_result_keystroke(pdf_js *js, pdf_keystroke_event *evt) { return 1; }
1324 int pdf_js_event_result(pdf_js *js) { return 1; }
1325 char *pdf_js_event_value(pdf_js *js) { return ""; }
1326 void pdf_js_execute(pdf_js *js, const char *name, const char *source, char **result) { }
1327 int pdf_js_event_result_validate(pdf_js *js, char **newvalue) { *newvalue=NULL; return 1; }
1328 pdf_js_console *pdf_js_get_console(fz_context *ctx, pdf_document *doc) { return NULL; }
1329 void pdf_js_set_console(fz_context *ctx, pdf_document *doc, pdf_js_console *console, void *user) { }
1330
1331 #endif /* FZ_ENABLE_JS */