Mercurial > hgrepos > Python2 > PyMuPDF
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mupdf-source/source/pdf/js/util.js Mon Sep 15 11:43:07 2025 +0200 @@ -0,0 +1,887 @@ +// 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. + +Error.prototype.toString = function() { + if (this.stackTrace) return this.name + ': ' + this.message + this.stackTrace; + return this.name + ': ' + this.message; +}; + +// display must be kept in sync with an enum in pdf_form.c +var display = { + visible: 0, + hidden: 1, + noPrint: 2, + noView: 3, +}; + +var border = { + b: 'beveled', + d: 'dashed', + i: 'inset', + s: 'solid', + u: 'underline', +}; + +var color = { + transparent: [ 'T' ], + black: [ 'G', 0 ], + white: [ 'G', 1 ], + gray: [ 'G', 0.5 ], + ltGray: [ 'G', 0.75 ], + dkGray: [ 'G', 0.25 ], + red: [ 'RGB', 1, 0, 0 ], + green: [ 'RGB', 0, 1, 0 ], + blue: [ 'RGB', 0, 0, 1 ], + cyan: [ 'CMYK', 1, 0, 0, 0 ], + magenta: [ 'CMYK', 0, 1, 0, 0 ], + yellow: [ 'CMYK', 0, 0, 1, 0 ], +}; + +color.convert = function (c, colorspace) { + switch (colorspace) { + case 'G': + if (c[0] === 'RGB') + return [ 'G', c[1] * 0.3 + c[2] * 0.59 + c[3] * 0.11 ]; + if (c[0] === 'CMYK') + return [ 'CMYK', 1 - Math.min(1, c[1] * 0.3 + c[2] * 0.59 + c[3] * 0.11 + c[4])]; + break; + case 'RGB': + if (c[0] === 'G') + return [ 'RGB', c[1], c[1], c[1] ]; + if (c[0] === 'CMYK') + return [ 'RGB', + 1 - Math.min(1, c[1] + c[4]), + 1 - Math.min(1, c[2] + c[4]), + 1 - Math.min(1, c[3] + c[4]) ]; + break; + case 'CMYK': + if (c[0] === 'G') + return [ 'CMYK', 0, 0, 0, 1 - c[1] ]; + if (c[0] === 'RGB') + return [ 'CMYK', 1 - c[1], 1 - c[2], 1 - c[3], 0 ]; + break; + } + return c; +} + +color.equal = function (a, b) { + var i, n; + if (a[0] === 'G') + a = color.convert(a, b[0]); + else + b = color.convert(b, a[0]); + if (a[0] !== b[0]) + return false; + switch (a[0]) { + case 'G': n = 1; break; + case 'RGB': n = 3; break; + case 'CMYK': n = 4; break; + default: n = 0; break; + } + for (i = 1; i <= n; ++i) + if (a[i] !== b[i]) + return false; + return true; +} + +var font = { + Cour: 'Courier', + CourB: 'Courier-Bold', + CourBI: 'Courier-BoldOblique', + CourI: 'Courier-Oblique', + Helv: 'Helvetica', + HelvB: 'Helvetica-Bold', + HelvBI: 'Helvetica-BoldOblique', + HelvI: 'Helvetica-Oblique', + Symbol: 'Symbol', + Times: 'Times-Roman', + TimesB: 'Times-Bold', + TimesBI: 'Times-BoldItalic', + TimesI: 'Times-Italic', + ZapfD: 'ZapfDingbats', +}; + +var highlight = { + i: 'invert', + n: 'none', + o: 'outline', + p: 'push', +}; + +var position = { + textOnly: 0, + iconOnly: 1, + iconTextV: 2, + textIconV: 3, + iconTextH: 4, + textIconH: 5, + overlay: 6, +}; + +var scaleHow = { + proportional: 0, + anamorphic: 1, +}; + +var scaleWhen = { + always: 0, + never: 1, + tooBig: 2, + tooSmall: 3, +}; + +var style = { + ch: 'check', + ci: 'circle', + cr: 'cross', + di: 'diamond', + sq: 'square', + st: 'star', +}; + +var zoomtype = { + fitH: 'FitHeight', + fitP: 'FitPage', + fitV: 'FitVisibleWidth', + fitW: 'FitWidth', + none: 'NoVary', + pref: 'Preferred', + refW: 'ReflowWidth', +}; + +util.scand = function (fmt, input) { + // This seems to match Acrobat's parsing behavior + return AFParseDateEx(input, fmt); +} + +util.printd = function (fmt, date) { + var monthName = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December' + ]; + var dayName = [ + 'Sunday', + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday', + 'Saturday' + ]; + if (fmt === 0) + fmt = 'D:yyyymmddHHMMss'; + else if (fmt === 1) + fmt = 'yyyy.mm.dd HH:MM:ss'; + else if (fmt === 2) + fmt = 'm/d/yy h:MM:ss tt'; + if (!date) + date = new Date(); + else if (!(date instanceof Date)) + date = new Date(date); + var tokens = fmt.match(/(\\.|m+|d+|y+|H+|h+|M+|s+|t+|[^\\mdyHhMst]*)/g); + var out = ''; + for (var i = 0; i < tokens.length; ++i) { + var token = tokens[i]; + switch (token) { + case 'mmmm': out += monthName[date.getMonth()]; break; + case 'mmm': out += monthName[date.getMonth()].substring(0, 3); break; + case 'mm': out += util.printf('%02d', date.getMonth()+1); break; + case 'm': out += date.getMonth()+1; break; + case 'dddd': out += dayName[date.getDay()]; break; + case 'ddd': out += dayName[date.getDay()].substring(0, 3); break; + case 'dd': out += util.printf('%02d', date.getDate()); break; + case 'd': out += date.getDate(); break; + case 'yyyy': out += date.getFullYear(); break; + case 'yy': out += date.getFullYear() % 100; break; + case 'HH': out += util.printf('%02d', date.getHours()); break; + case 'H': out += date.getHours(); break; + case 'hh': out += util.printf('%02d', (date.getHours()+11)%12+1); break; + case 'h': out += (date.getHours() + 11) % 12 + 1; break; + case 'MM': out += util.printf('%02d', date.getMinutes()); break; + case 'M': out += date.getMinutes(); break; + case 'ss': out += util.printf('%02d', date.getSeconds()); break; + case 's': out += date.getSeconds(); break; + case 'tt': out += date.getHours() < 12 ? 'am' : 'pm'; break; + case 't': out += date.getHours() < 12 ? 'a' : 'p'; break; + default: out += (token[0] == '\\') ? token[1] : token; break; + } + } + return out; +} + +util.printx = function (fmt, val) { + function toUpper(str) { return str.toUpperCase(); } + function toLower(str) { return str.toLowerCase(); } + function toSame(str) { return str; } + var convertCase = toSame; + var res = ''; + var i, m; + var n = fmt ? fmt.length : 0; + for (i = 0; i < n; ++i) { + switch (fmt.charAt(i)) { + case '\\': + if (++i < n) + res += fmt.charAt(i); + break; + case 'X': + m = val.match(/\w/); + if (m) { + res += convertCase(m[0]); + val = val.replace(/^\W*\w/, ''); + } + break; + case 'A': + m = val.match(/[A-Za-z]/); + if (m) { + res += convertCase(m[0]); + val = val.replace(/^[^A-Za-z]*[A-Za-z]/, ''); + } + break; + case '9': + m = val.match(/\d/); + if (m) { + res += m[0]; + val = val.replace(/^\D*\d/, ''); + } + break; + case '*': + res += convertCase(val); + val = ''; + break; + case '?': + if (val !== '') { + res += convertCase(val.charAt(0)); + val = val.substring(1); + } + break; + case '=': + convertCase = toSame; + break; + case '>': + convertCase = toUpper; + break; + case '<': + convertCase = toLower; + break; + default: + res += convertCase(fmt.charAt(i)); + break; + } + } + return res; +} + +// To the best of my understanding, events are called with: +// if (willCommit == false) { +// event.value = <current value of field> +// event.change = <text selection to drop into the selected area> +// event.selStart = <index of start of selected area, <= 0 means start of string> +// event.selEnd = <index of end of selected area, <= 0 means end of string> +// If the routine can't rationalise the proposed input to something sane it should +// return false, and the caller won't change anything. Otherwise, the routine +// can update value/change/selStart/selEnd as required, and should return true. +// The routine should accept 'partial' values (i.e. values that do not entirely +// fulfill the requirements as they are being typed). +// } else { +// event.value = <proposed value> +// event.change = '' +// event.selStart = -1 +// event.selEnd = -1 +// The routine can rewrite the proposed value if required (by changing value, not +// change or the selection). It should accept (return 1) or reject (return 0) the +// value it returns. +// } +// +// The following is a helper function to form the proposed 'changed' string that +// various handlers use. +function AFMergeChange(event) { + var prefix, postfix; + var value = event.value; + if (event.willCommit) + return value; + if (event.selStart >= 0) + prefix = value.substring(0, event.selStart); + else + prefix = ''; + if (event.selEnd >= 0 && event.selEnd <= value.length) + postfix = value.substring(event.selEnd, value.length); + else + postfix = ''; + return prefix + event.change + postfix; +} + +function AFExtractNums(string) { + if (string.charAt(0) == '.' || string.charAt(0) == ',') + string = '0' + string; + return string.match(/\d+/g); +} + +function AFMakeNumber(string) { + if (typeof string == 'number') + return string; + if (typeof string != 'string') + return null; + var nums = AFExtractNums(string); + if (!nums) + return null; + var result = nums.join('.'); + if (string.indexOf('-.') >= 0) + result = '0.' + result; + if (string.indexOf('-') >= 0) + return -result; + return +result; +} + +function AFExtractTime(string) { + var pattern = /\d\d?:\d\d?(:\d\d?)?\s*(am|pm)?/i; + var match = pattern.exec(string); + if (match) { + var prefix = string.substring(0, match.index); + var suffix = string.substring(match.index + match[0].length); + return [ prefix + suffix, match[0] ]; + } + return null; +} + +function AFParseDateOrder(fmt) { + var order = ''; + fmt += 'mdy'; // Default order if any parts are missing. + for (var i = 0; i < fmt.length; i++) { + var c = fmt.charAt(i); + if ((c == 'y' || c == 'm' || c == 'd') && order.indexOf(c) < 0) + order += c; + } + return order; +} + +function AFMatchMonth(date) { + var names = ['jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec']; + var month = date.match(/Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec/i); + if (month) + return names.indexOf(month[0].toLowerCase()) + 1; + return null; +} + +function AFParseTime(string, date) { + if (!date) + date = new Date(); + if (!string) + return date; + var nums = AFExtractNums(string); + if (!nums || nums.length < 2 || nums.length > 3) + return null; + var hour = nums[0]; + var min = nums[1]; + var sec = (nums.length == 3) ? nums[2] : 0; + if (hour < 12 && (/pm/i).test(string)) + hour += 12; + if (hour >= 12 && (/am/i).test(string)) + hour -= 12; + date.setHours(hour, min, sec); + if (date.getHours() != hour || date.getMinutes() != min || date.getSeconds() != sec) + return null; + return date; +} + +function AFMakeDate(out, year, month, date, time) +{ + year = year | 0; // force type to integer + if (year < 50) + year += 2000; + if (year < 100) + year += 1900; + out.setFullYear(year, month, date); + if (out.getFullYear() != year || out.getMonth() != month || out.getDate() != date) + return null; + if (time) + out = AFParseTime(time, out); + else + out.setHours(0, 0, 0); + return out; +} + +function AFParseDateEx(string, fmt) { + var out = new Date(); + var year = out.getFullYear(); + var month; + var date; + var i; + + out.setHours(12, 0, 0); + + var order = AFParseDateOrder(fmt); + + var time = AFExtractTime(string); + if (time) { + string = time[0]; + time = time[1]; + } + + var nums = AFExtractNums(string); + if (!nums) + return null; + + if (nums.length == 3) { + year = nums[order.indexOf('y')]; + month = nums[order.indexOf('m')]; + date = nums[order.indexOf('d')]; + return AFMakeDate(out, year, month-1, date, time); + } + + month = AFMatchMonth(string); + + if (nums.length == 2) { + // We have a textual month. + if (month) { + if (order.indexOf('y') < order.indexOf('d')) { + year = nums[0]; + date = nums[1]; + } else { + year = nums[1]; + date = nums[0]; + } + } + + // Year before date: set year and month. + else if (order.indexOf('y') < order.indexOf('d')) { + if (order.indexOf('y') < order.indexOf('m')) { + year = nums[0]; + month = nums[1]; + date = 1; + } else { + year = nums[1]; + month = nums[0]; + date = 1; + } + } + + // Date before year: set date and month. + else { + if (order.indexOf('d') < order.indexOf('m')) { + date = nums[0]; + month = nums[1]; + } else { + date = nums[1]; + month = nums[0]; + } + } + + return AFMakeDate(out, year, month-1, date, time); + } + + if (nums.length == 1) { + if (month) { + if (order.indexOf('y') < order.indexOf('d')) { + year = nums[0]; + date = 1; + } else { + date = nums[0]; + } + return AFMakeDate(out, year, month-1, date, time); + } + + // Only one number: must match format exactly! + if (string.length == fmt.length) { + year = month = date = ''; + for (i = 0; i < fmt.length; ++i) { + switch (fmt.charAt(i)) { + case '\\': ++i; break; + case 'y': year += string.charAt(i); break; + case 'm': month += string.charAt(i); break; + case 'd': date += string.charAt(i); break; + } + } + return AFMakeDate(out, year, month-1, date, time); + } + } + + return null; +} + +var AFDate_oldFormats = [ + 'm/d', + 'm/d/yy', + 'mm/dd/yy', + 'mm/yy', + 'd-mmm', + 'd-mmm-yy', + 'dd-mm-yy', + 'yy-mm-dd', + 'mmm-yy', + 'mmmm-yy', + 'mmm d, yyyy', + 'mmmm d, yyyy', + 'm/d/yy h:MM tt', + 'm/d/yy HH:MM' +]; + +function AFDate_KeystrokeEx(fmt) { + var value = AFMergeChange(event); + if (event.willCommit && !AFParseDateEx(value, fmt)) { + app.alert('The date/time entered ('+value+') does not match the format ('+fmt+') of the field [ '+event.target.name+' ]'); + event.rc = false; + } +} + +function AFDate_Keystroke(index) { + AFDate_KeystrokeEx(AFDate_oldFormats[index]); +} + +function AFDate_FormatEx(fmt) { + var d = AFParseDateEx(event.value, fmt); + event.value = d ? util.printd(fmt, d) : ''; +} + +function AFDate_Format(index) { + AFDate_FormatEx(AFDate_oldFormats[index]); +} + +function AFTime_Keystroke(index) { + if (event.willCommit && !AFParseTime(event.value, null)) { + app.alert('The value entered ('+event.value+') does not match the format of the field [ '+event.target.name+' ]'); + event.rc = false; + } +} + +function AFTime_FormatEx(fmt) { + var d = AFParseTime(event.value, null); + event.value = d ? util.printd(fmt, d) : ''; +} + +function AFTime_Format(index) { + var formats = [ 'HH:MM', 'h:MM tt', 'HH:MM:ss', 'h:MM:ss tt' ]; + AFTime_FormatEx(formats[index]); +} + +function AFSpecial_KeystrokeEx(fmt) { + function toUpper(str) { return str.toUpperCase(); } + function toLower(str) { return str.toLowerCase(); } + function toSame(str) { return str; } + var convertCase = toSame; + var val = AFMergeChange(event); + var res = ''; + var i = 0; + var m; + var length = fmt ? fmt.length : 0; + + // We always accept reverting to an empty string. + if (!val || val == "") { + event.rc = true; + return; + } + + while (i < length) { + // In the !willCommit case, we'll exit nicely if we run out of value. + if (!event.willCommit && (!val || val.length == 0)) + break; + switch (fmt.charAt(i)) { + case '\\': + i++; + if (i >= length) + break; + res += fmt.charAt(i); + if (val && val.charAt(0) === fmt.charAt(i)) + val = val.substring(1); + break; + + case 'X': + m = val.match(/^\w/); + if (!m) { + event.rc = false; + break; + } + res += convertCase(m[0]); + val = val.substring(1); + break; + + case 'A': + m = val.match(/^[A-Za-z]/); + if (!m) { + event.rc = false; + break; + } + res += convertCase(m[0]); + val = val.substring(1); + break; + + case '9': + m = val.match(/^\d/); + if (!m) { + event.rc = false; + break; + } + res += m[0]; + val = val.substring(1); + break; + + case '*': + res += convertCase(val); + val = ''; + break; + + case '?': + res += convertCase(val.charAt(0)); + val = val.substring(1); + break; + + case '=': + convertCase = toSame; + break; + case '>': + convertCase = toUpper; + break; + case '<': + convertCase = toLower; + break; + + default: + res += fmt.charAt(i); + if (val && val.charAt(0) === fmt.charAt(i)) + val = val.substring(1); + break; + } + + i++; + } + + // If we didn't make it through the fmt string then this is a failure + // in the willCommit case. + if (i < length && event.willCommit) + event.rc = false; + + // If there are characters left over in the value, it's not a match. + if (val.length > 0) + event.rc = false; + + if (event.rc) { + if (event.willCommit) + event.value = res; + else { + event.change = res; + event.selStart = 0; + event.selEnd = event.value.length; + } + } else + app.alert('The value entered ('+event.value+') does not match the format of the field [ '+event.target.name+' ] should be '+fmt); +} + +function AFSpecial_Keystroke(index) { + if (event.willCommit) { + switch (index) { + case 0: + if (!event.value.match(/^\d{5}$/)) + event.rc = false; + break; + case 1: + if (!event.value.match(/^\d{5}[-. ]?\d{4}$/)) + event.rc = false; + break; + case 2: + if (!event.value.match(/^((\(\d{3}\)|\d{3})[-. ]?)?\d{3}[-. ]?\d{4}$/)) + event.rc = false; + break; + case 3: + if (!event.value.match(/^\d{3}[-. ]?\d{2}[-. ]?\d{4}$/)) + event.rc = false; + break; + } + if (!event.rc) + app.alert('The value entered ('+event.value+') does not match the format of the field [ '+event.target.name+' ]'); + } +} + +function AFSpecial_Format(index) { + var res; + if (!event.value) + return; + switch (index) { + case 0: + res = util.printx('99999', event.value); + break; + case 1: + res = util.printx('99999-9999', event.value); + break; + case 2: + res = util.printx('9999999999', event.value); + res = util.printx(res.length >= 10 ? '(999) 999-9999' : '999-9999', event.value); + break; + case 3: + res = util.printx('999-99-9999', event.value); + break; + } + event.value = res ? res : ''; +} + +function AFNumber_Keystroke(nDec, sepStyle, negStyle, currStyle, strCurrency, bCurrencyPrepend) { + var value = AFMergeChange(event); + if (sepStyle & 2) { + if (!value.match(/^[+-]?\d*[,.]?\d*$/)) + event.rc = false; + } else { + if (!value.match(/^[+-]?\d*\.?\d*$/)) + event.rc = false; + } + if (event.willCommit) { + if (!value.match(/\d/)) + event.rc = false; + if (!event.rc) + app.alert('The value entered ('+value+') does not match the format of the field [ '+event.target.name+' ]'); + } +} + +function AFNumber_Format(nDec, sepStyle, negStyle, currStyle, strCurrency, bCurrencyPrepend) { + var value = AFMakeNumber(event.value); + var fmt = '%,' + sepStyle + '.' + nDec + 'f'; + if (value == null) { + event.value = ''; + return; + } + if (bCurrencyPrepend) + fmt = strCurrency + fmt; + else + fmt = fmt + strCurrency; + if (value < 0) { + /* negStyle: 0=MinusBlack, 1=Red, 2=ParensBlack, 3=ParensRed */ + value = Math.abs(value); + if (negStyle == 2 || negStyle == 3) + fmt = '(' + fmt + ')'; + else if (negStyle == 0) + fmt = '-' + fmt; + if (negStyle == 1 || negStyle == 3) + event.target.textColor = color.red; + else + event.target.textColor = color.black; + } else { + event.target.textColor = color.black; + } + event.value = util.printf(fmt, value); +} + +function AFPercent_Keystroke(nDec, sepStyle) { + AFNumber_Keystroke(nDec, sepStyle, 0, 0, '', true); +} + +function AFPercent_Format(nDec, sepStyle) { + var val = AFMakeNumber(event.value); + if (val == null) { + event.value = ''; + return; + } + event.value = (val * 100) + ''; + AFNumber_Format(nDec, sepStyle, 0, 0, '%', false); +} + +function AFSimple_Calculate(op, list) { + var i, res; + + switch (op) { + case 'SUM': res = 0; break; + case 'PRD': res = 1; break; + case 'AVG': res = 0; break; + } + + if (typeof list === 'string') + list = list.split(/ *, */); + + for (i = 0; i < list.length; i++) { + var field = this.getField(list[i]); + var value = Number(field.value); + switch (op) { + case 'SUM': res += value; break; + case 'PRD': res *= value; break; + case 'AVG': res += value; break; + case 'MIN': if (i === 0 || value < res) res = value; break; + case 'MAX': if (i === 0 || value > res) res = value; break; + } + } + + if (op === 'AVG') + res /= list.length; + + event.value = res; +} + +function AFRange_Validate(lowerCheck, lowerLimit, upperCheck, upperLimit) { + if (upperCheck && event.value > upperLimit) + event.rc = false; + if (lowerCheck && event.value < lowerLimit) + event.rc = false; + if (!event.rc) { + if (lowerCheck && upperCheck) + 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)); + else if (lowerCheck) + app.alert(util.printf('The entered value ('+event.value+') must be greater than or equal to %s', lowerLimit)); + else + app.alert(util.printf('The entered value ('+event.value+') must be less than or equal to %s', upperLimit)); + } +} + +// Create Doc.info proxy object. +function mupdf_createInfoProxy(doc) { + doc.info = { + get Title() { return doc.title; }, + set Title(value) { doc.title = value; }, + get Author() { return doc.author; }, + set Author(value) { doc.author = value; }, + get Subject() { return doc.subject; }, + set Subject(value) { doc.subject = value; }, + get Keywords() { return doc.keywords; }, + set Keywords(value) { doc.keywords = value; }, + get Creator() { return doc.creator; }, + set Creator(value) { doc.creator = value; }, + get Producer() { return doc.producer; }, + set Producer(value) { doc.producer = value; }, + get CreationDate() { return doc.creationDate; }, + set CreationDate(value) { doc.creationDate = value; }, + get ModDate() { return doc.modDate; }, + set ModDate(value) { doc.modDate = value; }, + }; +} +mupdf_createInfoProxy(global); + +/* Compatibility ECMAScript functions */ +String.prototype.substr = function (start, length) { + if (start < 0) + start = this.length + start; + if (length === undefined) + return this.substring(start, this.length); + return this.substring(start, start + length); +} +Date.prototype.getYear = Date.prototype.getFullYear; +Date.prototype.setYear = Date.prototype.setFullYear; +Date.prototype.toGMTString = Date.prototype.toUTCString; + +app.plugIns = []; +app.viewerType = 'Reader'; +app.language = 'ENU'; +app.viewerVersion = NaN; +app.execDialog = function () { return 'cancel'; }
