comparison mupdf-source/source/pdf/js/util.js @ 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 Error.prototype.toString = function() {
24 if (this.stackTrace) return this.name + ': ' + this.message + this.stackTrace;
25 return this.name + ': ' + this.message;
26 };
27
28 // display must be kept in sync with an enum in pdf_form.c
29 var display = {
30 visible: 0,
31 hidden: 1,
32 noPrint: 2,
33 noView: 3,
34 };
35
36 var border = {
37 b: 'beveled',
38 d: 'dashed',
39 i: 'inset',
40 s: 'solid',
41 u: 'underline',
42 };
43
44 var color = {
45 transparent: [ 'T' ],
46 black: [ 'G', 0 ],
47 white: [ 'G', 1 ],
48 gray: [ 'G', 0.5 ],
49 ltGray: [ 'G', 0.75 ],
50 dkGray: [ 'G', 0.25 ],
51 red: [ 'RGB', 1, 0, 0 ],
52 green: [ 'RGB', 0, 1, 0 ],
53 blue: [ 'RGB', 0, 0, 1 ],
54 cyan: [ 'CMYK', 1, 0, 0, 0 ],
55 magenta: [ 'CMYK', 0, 1, 0, 0 ],
56 yellow: [ 'CMYK', 0, 0, 1, 0 ],
57 };
58
59 color.convert = function (c, colorspace) {
60 switch (colorspace) {
61 case 'G':
62 if (c[0] === 'RGB')
63 return [ 'G', c[1] * 0.3 + c[2] * 0.59 + c[3] * 0.11 ];
64 if (c[0] === 'CMYK')
65 return [ 'CMYK', 1 - Math.min(1, c[1] * 0.3 + c[2] * 0.59 + c[3] * 0.11 + c[4])];
66 break;
67 case 'RGB':
68 if (c[0] === 'G')
69 return [ 'RGB', c[1], c[1], c[1] ];
70 if (c[0] === 'CMYK')
71 return [ 'RGB',
72 1 - Math.min(1, c[1] + c[4]),
73 1 - Math.min(1, c[2] + c[4]),
74 1 - Math.min(1, c[3] + c[4]) ];
75 break;
76 case 'CMYK':
77 if (c[0] === 'G')
78 return [ 'CMYK', 0, 0, 0, 1 - c[1] ];
79 if (c[0] === 'RGB')
80 return [ 'CMYK', 1 - c[1], 1 - c[2], 1 - c[3], 0 ];
81 break;
82 }
83 return c;
84 }
85
86 color.equal = function (a, b) {
87 var i, n;
88 if (a[0] === 'G')
89 a = color.convert(a, b[0]);
90 else
91 b = color.convert(b, a[0]);
92 if (a[0] !== b[0])
93 return false;
94 switch (a[0]) {
95 case 'G': n = 1; break;
96 case 'RGB': n = 3; break;
97 case 'CMYK': n = 4; break;
98 default: n = 0; break;
99 }
100 for (i = 1; i <= n; ++i)
101 if (a[i] !== b[i])
102 return false;
103 return true;
104 }
105
106 var font = {
107 Cour: 'Courier',
108 CourB: 'Courier-Bold',
109 CourBI: 'Courier-BoldOblique',
110 CourI: 'Courier-Oblique',
111 Helv: 'Helvetica',
112 HelvB: 'Helvetica-Bold',
113 HelvBI: 'Helvetica-BoldOblique',
114 HelvI: 'Helvetica-Oblique',
115 Symbol: 'Symbol',
116 Times: 'Times-Roman',
117 TimesB: 'Times-Bold',
118 TimesBI: 'Times-BoldItalic',
119 TimesI: 'Times-Italic',
120 ZapfD: 'ZapfDingbats',
121 };
122
123 var highlight = {
124 i: 'invert',
125 n: 'none',
126 o: 'outline',
127 p: 'push',
128 };
129
130 var position = {
131 textOnly: 0,
132 iconOnly: 1,
133 iconTextV: 2,
134 textIconV: 3,
135 iconTextH: 4,
136 textIconH: 5,
137 overlay: 6,
138 };
139
140 var scaleHow = {
141 proportional: 0,
142 anamorphic: 1,
143 };
144
145 var scaleWhen = {
146 always: 0,
147 never: 1,
148 tooBig: 2,
149 tooSmall: 3,
150 };
151
152 var style = {
153 ch: 'check',
154 ci: 'circle',
155 cr: 'cross',
156 di: 'diamond',
157 sq: 'square',
158 st: 'star',
159 };
160
161 var zoomtype = {
162 fitH: 'FitHeight',
163 fitP: 'FitPage',
164 fitV: 'FitVisibleWidth',
165 fitW: 'FitWidth',
166 none: 'NoVary',
167 pref: 'Preferred',
168 refW: 'ReflowWidth',
169 };
170
171 util.scand = function (fmt, input) {
172 // This seems to match Acrobat's parsing behavior
173 return AFParseDateEx(input, fmt);
174 }
175
176 util.printd = function (fmt, date) {
177 var monthName = [
178 'January',
179 'February',
180 'March',
181 'April',
182 'May',
183 'June',
184 'July',
185 'August',
186 'September',
187 'October',
188 'November',
189 'December'
190 ];
191 var dayName = [
192 'Sunday',
193 'Monday',
194 'Tuesday',
195 'Wednesday',
196 'Thursday',
197 'Friday',
198 'Saturday'
199 ];
200 if (fmt === 0)
201 fmt = 'D:yyyymmddHHMMss';
202 else if (fmt === 1)
203 fmt = 'yyyy.mm.dd HH:MM:ss';
204 else if (fmt === 2)
205 fmt = 'm/d/yy h:MM:ss tt';
206 if (!date)
207 date = new Date();
208 else if (!(date instanceof Date))
209 date = new Date(date);
210 var tokens = fmt.match(/(\\.|m+|d+|y+|H+|h+|M+|s+|t+|[^\\mdyHhMst]*)/g);
211 var out = '';
212 for (var i = 0; i < tokens.length; ++i) {
213 var token = tokens[i];
214 switch (token) {
215 case 'mmmm': out += monthName[date.getMonth()]; break;
216 case 'mmm': out += monthName[date.getMonth()].substring(0, 3); break;
217 case 'mm': out += util.printf('%02d', date.getMonth()+1); break;
218 case 'm': out += date.getMonth()+1; break;
219 case 'dddd': out += dayName[date.getDay()]; break;
220 case 'ddd': out += dayName[date.getDay()].substring(0, 3); break;
221 case 'dd': out += util.printf('%02d', date.getDate()); break;
222 case 'd': out += date.getDate(); break;
223 case 'yyyy': out += date.getFullYear(); break;
224 case 'yy': out += date.getFullYear() % 100; break;
225 case 'HH': out += util.printf('%02d', date.getHours()); break;
226 case 'H': out += date.getHours(); break;
227 case 'hh': out += util.printf('%02d', (date.getHours()+11)%12+1); break;
228 case 'h': out += (date.getHours() + 11) % 12 + 1; break;
229 case 'MM': out += util.printf('%02d', date.getMinutes()); break;
230 case 'M': out += date.getMinutes(); break;
231 case 'ss': out += util.printf('%02d', date.getSeconds()); break;
232 case 's': out += date.getSeconds(); break;
233 case 'tt': out += date.getHours() < 12 ? 'am' : 'pm'; break;
234 case 't': out += date.getHours() < 12 ? 'a' : 'p'; break;
235 default: out += (token[0] == '\\') ? token[1] : token; break;
236 }
237 }
238 return out;
239 }
240
241 util.printx = function (fmt, val) {
242 function toUpper(str) { return str.toUpperCase(); }
243 function toLower(str) { return str.toLowerCase(); }
244 function toSame(str) { return str; }
245 var convertCase = toSame;
246 var res = '';
247 var i, m;
248 var n = fmt ? fmt.length : 0;
249 for (i = 0; i < n; ++i) {
250 switch (fmt.charAt(i)) {
251 case '\\':
252 if (++i < n)
253 res += fmt.charAt(i);
254 break;
255 case 'X':
256 m = val.match(/\w/);
257 if (m) {
258 res += convertCase(m[0]);
259 val = val.replace(/^\W*\w/, '');
260 }
261 break;
262 case 'A':
263 m = val.match(/[A-Za-z]/);
264 if (m) {
265 res += convertCase(m[0]);
266 val = val.replace(/^[^A-Za-z]*[A-Za-z]/, '');
267 }
268 break;
269 case '9':
270 m = val.match(/\d/);
271 if (m) {
272 res += m[0];
273 val = val.replace(/^\D*\d/, '');
274 }
275 break;
276 case '*':
277 res += convertCase(val);
278 val = '';
279 break;
280 case '?':
281 if (val !== '') {
282 res += convertCase(val.charAt(0));
283 val = val.substring(1);
284 }
285 break;
286 case '=':
287 convertCase = toSame;
288 break;
289 case '>':
290 convertCase = toUpper;
291 break;
292 case '<':
293 convertCase = toLower;
294 break;
295 default:
296 res += convertCase(fmt.charAt(i));
297 break;
298 }
299 }
300 return res;
301 }
302
303 // To the best of my understanding, events are called with:
304 // if (willCommit == false) {
305 // event.value = <current value of field>
306 // event.change = <text selection to drop into the selected area>
307 // event.selStart = <index of start of selected area, <= 0 means start of string>
308 // event.selEnd = <index of end of selected area, <= 0 means end of string>
309 // If the routine can't rationalise the proposed input to something sane it should
310 // return false, and the caller won't change anything. Otherwise, the routine
311 // can update value/change/selStart/selEnd as required, and should return true.
312 // The routine should accept 'partial' values (i.e. values that do not entirely
313 // fulfill the requirements as they are being typed).
314 // } else {
315 // event.value = <proposed value>
316 // event.change = ''
317 // event.selStart = -1
318 // event.selEnd = -1
319 // The routine can rewrite the proposed value if required (by changing value, not
320 // change or the selection). It should accept (return 1) or reject (return 0) the
321 // value it returns.
322 // }
323 //
324 // The following is a helper function to form the proposed 'changed' string that
325 // various handlers use.
326 function AFMergeChange(event) {
327 var prefix, postfix;
328 var value = event.value;
329 if (event.willCommit)
330 return value;
331 if (event.selStart >= 0)
332 prefix = value.substring(0, event.selStart);
333 else
334 prefix = '';
335 if (event.selEnd >= 0 && event.selEnd <= value.length)
336 postfix = value.substring(event.selEnd, value.length);
337 else
338 postfix = '';
339 return prefix + event.change + postfix;
340 }
341
342 function AFExtractNums(string) {
343 if (string.charAt(0) == '.' || string.charAt(0) == ',')
344 string = '0' + string;
345 return string.match(/\d+/g);
346 }
347
348 function AFMakeNumber(string) {
349 if (typeof string == 'number')
350 return string;
351 if (typeof string != 'string')
352 return null;
353 var nums = AFExtractNums(string);
354 if (!nums)
355 return null;
356 var result = nums.join('.');
357 if (string.indexOf('-.') >= 0)
358 result = '0.' + result;
359 if (string.indexOf('-') >= 0)
360 return -result;
361 return +result;
362 }
363
364 function AFExtractTime(string) {
365 var pattern = /\d\d?:\d\d?(:\d\d?)?\s*(am|pm)?/i;
366 var match = pattern.exec(string);
367 if (match) {
368 var prefix = string.substring(0, match.index);
369 var suffix = string.substring(match.index + match[0].length);
370 return [ prefix + suffix, match[0] ];
371 }
372 return null;
373 }
374
375 function AFParseDateOrder(fmt) {
376 var order = '';
377 fmt += 'mdy'; // Default order if any parts are missing.
378 for (var i = 0; i < fmt.length; i++) {
379 var c = fmt.charAt(i);
380 if ((c == 'y' || c == 'm' || c == 'd') && order.indexOf(c) < 0)
381 order += c;
382 }
383 return order;
384 }
385
386 function AFMatchMonth(date) {
387 var names = ['jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec'];
388 var month = date.match(/Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec/i);
389 if (month)
390 return names.indexOf(month[0].toLowerCase()) + 1;
391 return null;
392 }
393
394 function AFParseTime(string, date) {
395 if (!date)
396 date = new Date();
397 if (!string)
398 return date;
399 var nums = AFExtractNums(string);
400 if (!nums || nums.length < 2 || nums.length > 3)
401 return null;
402 var hour = nums[0];
403 var min = nums[1];
404 var sec = (nums.length == 3) ? nums[2] : 0;
405 if (hour < 12 && (/pm/i).test(string))
406 hour += 12;
407 if (hour >= 12 && (/am/i).test(string))
408 hour -= 12;
409 date.setHours(hour, min, sec);
410 if (date.getHours() != hour || date.getMinutes() != min || date.getSeconds() != sec)
411 return null;
412 return date;
413 }
414
415 function AFMakeDate(out, year, month, date, time)
416 {
417 year = year | 0; // force type to integer
418 if (year < 50)
419 year += 2000;
420 if (year < 100)
421 year += 1900;
422 out.setFullYear(year, month, date);
423 if (out.getFullYear() != year || out.getMonth() != month || out.getDate() != date)
424 return null;
425 if (time)
426 out = AFParseTime(time, out);
427 else
428 out.setHours(0, 0, 0);
429 return out;
430 }
431
432 function AFParseDateEx(string, fmt) {
433 var out = new Date();
434 var year = out.getFullYear();
435 var month;
436 var date;
437 var i;
438
439 out.setHours(12, 0, 0);
440
441 var order = AFParseDateOrder(fmt);
442
443 var time = AFExtractTime(string);
444 if (time) {
445 string = time[0];
446 time = time[1];
447 }
448
449 var nums = AFExtractNums(string);
450 if (!nums)
451 return null;
452
453 if (nums.length == 3) {
454 year = nums[order.indexOf('y')];
455 month = nums[order.indexOf('m')];
456 date = nums[order.indexOf('d')];
457 return AFMakeDate(out, year, month-1, date, time);
458 }
459
460 month = AFMatchMonth(string);
461
462 if (nums.length == 2) {
463 // We have a textual month.
464 if (month) {
465 if (order.indexOf('y') < order.indexOf('d')) {
466 year = nums[0];
467 date = nums[1];
468 } else {
469 year = nums[1];
470 date = nums[0];
471 }
472 }
473
474 // Year before date: set year and month.
475 else if (order.indexOf('y') < order.indexOf('d')) {
476 if (order.indexOf('y') < order.indexOf('m')) {
477 year = nums[0];
478 month = nums[1];
479 date = 1;
480 } else {
481 year = nums[1];
482 month = nums[0];
483 date = 1;
484 }
485 }
486
487 // Date before year: set date and month.
488 else {
489 if (order.indexOf('d') < order.indexOf('m')) {
490 date = nums[0];
491 month = nums[1];
492 } else {
493 date = nums[1];
494 month = nums[0];
495 }
496 }
497
498 return AFMakeDate(out, year, month-1, date, time);
499 }
500
501 if (nums.length == 1) {
502 if (month) {
503 if (order.indexOf('y') < order.indexOf('d')) {
504 year = nums[0];
505 date = 1;
506 } else {
507 date = nums[0];
508 }
509 return AFMakeDate(out, year, month-1, date, time);
510 }
511
512 // Only one number: must match format exactly!
513 if (string.length == fmt.length) {
514 year = month = date = '';
515 for (i = 0; i < fmt.length; ++i) {
516 switch (fmt.charAt(i)) {
517 case '\\': ++i; break;
518 case 'y': year += string.charAt(i); break;
519 case 'm': month += string.charAt(i); break;
520 case 'd': date += string.charAt(i); break;
521 }
522 }
523 return AFMakeDate(out, year, month-1, date, time);
524 }
525 }
526
527 return null;
528 }
529
530 var AFDate_oldFormats = [
531 'm/d',
532 'm/d/yy',
533 'mm/dd/yy',
534 'mm/yy',
535 'd-mmm',
536 'd-mmm-yy',
537 'dd-mm-yy',
538 'yy-mm-dd',
539 'mmm-yy',
540 'mmmm-yy',
541 'mmm d, yyyy',
542 'mmmm d, yyyy',
543 'm/d/yy h:MM tt',
544 'm/d/yy HH:MM'
545 ];
546
547 function AFDate_KeystrokeEx(fmt) {
548 var value = AFMergeChange(event);
549 if (event.willCommit && !AFParseDateEx(value, fmt)) {
550 app.alert('The date/time entered ('+value+') does not match the format ('+fmt+') of the field [ '+event.target.name+' ]');
551 event.rc = false;
552 }
553 }
554
555 function AFDate_Keystroke(index) {
556 AFDate_KeystrokeEx(AFDate_oldFormats[index]);
557 }
558
559 function AFDate_FormatEx(fmt) {
560 var d = AFParseDateEx(event.value, fmt);
561 event.value = d ? util.printd(fmt, d) : '';
562 }
563
564 function AFDate_Format(index) {
565 AFDate_FormatEx(AFDate_oldFormats[index]);
566 }
567
568 function AFTime_Keystroke(index) {
569 if (event.willCommit && !AFParseTime(event.value, null)) {
570 app.alert('The value entered ('+event.value+') does not match the format of the field [ '+event.target.name+' ]');
571 event.rc = false;
572 }
573 }
574
575 function AFTime_FormatEx(fmt) {
576 var d = AFParseTime(event.value, null);
577 event.value = d ? util.printd(fmt, d) : '';
578 }
579
580 function AFTime_Format(index) {
581 var formats = [ 'HH:MM', 'h:MM tt', 'HH:MM:ss', 'h:MM:ss tt' ];
582 AFTime_FormatEx(formats[index]);
583 }
584
585 function AFSpecial_KeystrokeEx(fmt) {
586 function toUpper(str) { return str.toUpperCase(); }
587 function toLower(str) { return str.toLowerCase(); }
588 function toSame(str) { return str; }
589 var convertCase = toSame;
590 var val = AFMergeChange(event);
591 var res = '';
592 var i = 0;
593 var m;
594 var length = fmt ? fmt.length : 0;
595
596 // We always accept reverting to an empty string.
597 if (!val || val == "") {
598 event.rc = true;
599 return;
600 }
601
602 while (i < length) {
603 // In the !willCommit case, we'll exit nicely if we run out of value.
604 if (!event.willCommit && (!val || val.length == 0))
605 break;
606 switch (fmt.charAt(i)) {
607 case '\\':
608 i++;
609 if (i >= length)
610 break;
611 res += fmt.charAt(i);
612 if (val && val.charAt(0) === fmt.charAt(i))
613 val = val.substring(1);
614 break;
615
616 case 'X':
617 m = val.match(/^\w/);
618 if (!m) {
619 event.rc = false;
620 break;
621 }
622 res += convertCase(m[0]);
623 val = val.substring(1);
624 break;
625
626 case 'A':
627 m = val.match(/^[A-Za-z]/);
628 if (!m) {
629 event.rc = false;
630 break;
631 }
632 res += convertCase(m[0]);
633 val = val.substring(1);
634 break;
635
636 case '9':
637 m = val.match(/^\d/);
638 if (!m) {
639 event.rc = false;
640 break;
641 }
642 res += m[0];
643 val = val.substring(1);
644 break;
645
646 case '*':
647 res += convertCase(val);
648 val = '';
649 break;
650
651 case '?':
652 res += convertCase(val.charAt(0));
653 val = val.substring(1);
654 break;
655
656 case '=':
657 convertCase = toSame;
658 break;
659 case '>':
660 convertCase = toUpper;
661 break;
662 case '<':
663 convertCase = toLower;
664 break;
665
666 default:
667 res += fmt.charAt(i);
668 if (val && val.charAt(0) === fmt.charAt(i))
669 val = val.substring(1);
670 break;
671 }
672
673 i++;
674 }
675
676 // If we didn't make it through the fmt string then this is a failure
677 // in the willCommit case.
678 if (i < length && event.willCommit)
679 event.rc = false;
680
681 // If there are characters left over in the value, it's not a match.
682 if (val.length > 0)
683 event.rc = false;
684
685 if (event.rc) {
686 if (event.willCommit)
687 event.value = res;
688 else {
689 event.change = res;
690 event.selStart = 0;
691 event.selEnd = event.value.length;
692 }
693 } else
694 app.alert('The value entered ('+event.value+') does not match the format of the field [ '+event.target.name+' ] should be '+fmt);
695 }
696
697 function AFSpecial_Keystroke(index) {
698 if (event.willCommit) {
699 switch (index) {
700 case 0:
701 if (!event.value.match(/^\d{5}$/))
702 event.rc = false;
703 break;
704 case 1:
705 if (!event.value.match(/^\d{5}[-. ]?\d{4}$/))
706 event.rc = false;
707 break;
708 case 2:
709 if (!event.value.match(/^((\(\d{3}\)|\d{3})[-. ]?)?\d{3}[-. ]?\d{4}$/))
710 event.rc = false;
711 break;
712 case 3:
713 if (!event.value.match(/^\d{3}[-. ]?\d{2}[-. ]?\d{4}$/))
714 event.rc = false;
715 break;
716 }
717 if (!event.rc)
718 app.alert('The value entered ('+event.value+') does not match the format of the field [ '+event.target.name+' ]');
719 }
720 }
721
722 function AFSpecial_Format(index) {
723 var res;
724 if (!event.value)
725 return;
726 switch (index) {
727 case 0:
728 res = util.printx('99999', event.value);
729 break;
730 case 1:
731 res = util.printx('99999-9999', event.value);
732 break;
733 case 2:
734 res = util.printx('9999999999', event.value);
735 res = util.printx(res.length >= 10 ? '(999) 999-9999' : '999-9999', event.value);
736 break;
737 case 3:
738 res = util.printx('999-99-9999', event.value);
739 break;
740 }
741 event.value = res ? res : '';
742 }
743
744 function AFNumber_Keystroke(nDec, sepStyle, negStyle, currStyle, strCurrency, bCurrencyPrepend) {
745 var value = AFMergeChange(event);
746 if (sepStyle & 2) {
747 if (!value.match(/^[+-]?\d*[,.]?\d*$/))
748 event.rc = false;
749 } else {
750 if (!value.match(/^[+-]?\d*\.?\d*$/))
751 event.rc = false;
752 }
753 if (event.willCommit) {
754 if (!value.match(/\d/))
755 event.rc = false;
756 if (!event.rc)
757 app.alert('The value entered ('+value+') does not match the format of the field [ '+event.target.name+' ]');
758 }
759 }
760
761 function AFNumber_Format(nDec, sepStyle, negStyle, currStyle, strCurrency, bCurrencyPrepend) {
762 var value = AFMakeNumber(event.value);
763 var fmt = '%,' + sepStyle + '.' + nDec + 'f';
764 if (value == null) {
765 event.value = '';
766 return;
767 }
768 if (bCurrencyPrepend)
769 fmt = strCurrency + fmt;
770 else
771 fmt = fmt + strCurrency;
772 if (value < 0) {
773 /* negStyle: 0=MinusBlack, 1=Red, 2=ParensBlack, 3=ParensRed */
774 value = Math.abs(value);
775 if (negStyle == 2 || negStyle == 3)
776 fmt = '(' + fmt + ')';
777 else if (negStyle == 0)
778 fmt = '-' + fmt;
779 if (negStyle == 1 || negStyle == 3)
780 event.target.textColor = color.red;
781 else
782 event.target.textColor = color.black;
783 } else {
784 event.target.textColor = color.black;
785 }
786 event.value = util.printf(fmt, value);
787 }
788
789 function AFPercent_Keystroke(nDec, sepStyle) {
790 AFNumber_Keystroke(nDec, sepStyle, 0, 0, '', true);
791 }
792
793 function AFPercent_Format(nDec, sepStyle) {
794 var val = AFMakeNumber(event.value);
795 if (val == null) {
796 event.value = '';
797 return;
798 }
799 event.value = (val * 100) + '';
800 AFNumber_Format(nDec, sepStyle, 0, 0, '%', false);
801 }
802
803 function AFSimple_Calculate(op, list) {
804 var i, res;
805
806 switch (op) {
807 case 'SUM': res = 0; break;
808 case 'PRD': res = 1; break;
809 case 'AVG': res = 0; break;
810 }
811
812 if (typeof list === 'string')
813 list = list.split(/ *, */);
814
815 for (i = 0; i < list.length; i++) {
816 var field = this.getField(list[i]);
817 var value = Number(field.value);
818 switch (op) {
819 case 'SUM': res += value; break;
820 case 'PRD': res *= value; break;
821 case 'AVG': res += value; break;
822 case 'MIN': if (i === 0 || value < res) res = value; break;
823 case 'MAX': if (i === 0 || value > res) res = value; break;
824 }
825 }
826
827 if (op === 'AVG')
828 res /= list.length;
829
830 event.value = res;
831 }
832
833 function AFRange_Validate(lowerCheck, lowerLimit, upperCheck, upperLimit) {
834 if (upperCheck && event.value > upperLimit)
835 event.rc = false;
836 if (lowerCheck && event.value < lowerLimit)
837 event.rc = false;
838 if (!event.rc) {
839 if (lowerCheck && upperCheck)
840 app.alert(util.printf('The entered value ('+event.value+') must be greater than or equal to %s and less than or equal to %s', lowerLimit, upperLimit));
841 else if (lowerCheck)
842 app.alert(util.printf('The entered value ('+event.value+') must be greater than or equal to %s', lowerLimit));
843 else
844 app.alert(util.printf('The entered value ('+event.value+') must be less than or equal to %s', upperLimit));
845 }
846 }
847
848 // Create Doc.info proxy object.
849 function mupdf_createInfoProxy(doc) {
850 doc.info = {
851 get Title() { return doc.title; },
852 set Title(value) { doc.title = value; },
853 get Author() { return doc.author; },
854 set Author(value) { doc.author = value; },
855 get Subject() { return doc.subject; },
856 set Subject(value) { doc.subject = value; },
857 get Keywords() { return doc.keywords; },
858 set Keywords(value) { doc.keywords = value; },
859 get Creator() { return doc.creator; },
860 set Creator(value) { doc.creator = value; },
861 get Producer() { return doc.producer; },
862 set Producer(value) { doc.producer = value; },
863 get CreationDate() { return doc.creationDate; },
864 set CreationDate(value) { doc.creationDate = value; },
865 get ModDate() { return doc.modDate; },
866 set ModDate(value) { doc.modDate = value; },
867 };
868 }
869 mupdf_createInfoProxy(global);
870
871 /* Compatibility ECMAScript functions */
872 String.prototype.substr = function (start, length) {
873 if (start < 0)
874 start = this.length + start;
875 if (length === undefined)
876 return this.substring(start, this.length);
877 return this.substring(start, start + length);
878 }
879 Date.prototype.getYear = Date.prototype.getFullYear;
880 Date.prototype.setYear = Date.prototype.setFullYear;
881 Date.prototype.toGMTString = Date.prototype.toUTCString;
882
883 app.plugIns = [];
884 app.viewerType = 'Reader';
885 app.language = 'ENU';
886 app.viewerVersion = NaN;
887 app.execDialog = function () { return 'cancel'; }