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'; }