Merge pull request #12530 from calixteman/js_utils

JS -- Add 'util' object
This commit is contained in:
Brendan Dahl 2020-11-06 09:59:50 -08:00 committed by GitHub
commit 018fd43096
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 652 additions and 1 deletions

View File

@ -1273,7 +1273,7 @@ function buildLib(defines, dir) {
return merge([
gulp.src(
[
"src/{core,display,shared}/*.js",
"src/{core,display,scripting_api,shared}/*.js",
"!src/shared/{cffStandardStrings,fonts_utils}.js",
"src/{pdf,pdf.worker}.js",
],

View File

@ -20,11 +20,562 @@ class Util extends PDFObject {
super(data);
this._crackURL = data.crackURL;
this._scandCache = new Map();
this._months = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
];
this._days = [
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
];
this.MILLISECONDS_IN_DAY = 86400000;
this.MILLISECONDS_IN_WEEK = 604800000;
}
crackURL(cURL) {
return this._crackURL(cURL);
}
printf(...args) {
if (args.length === 0) {
throw new Error("Invalid number of params in printf");
}
if (typeof args[0] !== "string") {
throw new TypeError("First argument of printf must be a string");
}
const pattern = /%(,[0-4])?([\+ 0#]+)?([0-9]+)?(\.[0-9]+)?(.)/g;
const PLUS = 1;
const SPACE = 2;
const ZERO = 4;
const HASH = 8;
let i = 0;
return args[0].replace(pattern, function (
match,
nDecSep,
cFlags,
nWidth,
nPrecision,
cConvChar
) {
// cConvChar must be one of d, f, s, x
if (
cConvChar !== "d" &&
cConvChar !== "f" &&
cConvChar !== "s" &&
cConvChar !== "x"
) {
const buf = ["%"];
for (const str of [nDecSep, cFlags, nWidth, nPrecision, cConvChar]) {
if (str) {
buf.push(str);
}
}
return buf.join("");
}
i++;
if (i === args.length) {
throw new Error("Not enough arguments in printf");
}
const arg = args[i];
if (cConvChar === "s") {
return arg.toString();
}
let flags = 0;
if (cFlags) {
for (const flag of cFlags) {
switch (flag) {
case "+":
flags |= PLUS;
break;
case " ":
flags |= SPACE;
break;
case "0":
flags |= ZERO;
break;
case "#":
flags |= HASH;
break;
}
}
}
cFlags = flags;
if (nWidth) {
nWidth = parseInt(nWidth);
}
let intPart = Math.trunc(arg);
if (cConvChar === "x") {
let hex = Math.abs(intPart).toString(16).toUpperCase();
if (nWidth !== undefined) {
hex = hex.padStart(nWidth, cFlags & ZERO ? "0" : " ");
}
if (cFlags & HASH) {
hex = `0x${hex}`;
}
return hex;
}
if (nPrecision) {
nPrecision = parseInt(nPrecision.substring(1));
}
nDecSep = nDecSep ? nDecSep.substring(1) : "0";
const separators = {
0: [",", "."],
1: ["", "."],
2: [".", ","],
3: ["", ","],
4: ["'", "."],
};
const [thousandSep, decimalSep] = separators[nDecSep];
let decPart = "";
if (cConvChar === "f") {
if (nPrecision !== undefined) {
decPart = (arg - intPart).toFixed(nPrecision);
} else {
decPart = (arg - intPart).toString();
}
if (decPart.length > 2) {
decPart = `${decimalSep}${decPart.substring(2)}`;
} else if (cFlags & HASH) {
decPart = ".";
} else {
decPart = "";
}
}
let sign = "";
if (intPart < 0) {
sign = "-";
intPart = -intPart;
} else if (cFlags & PLUS) {
sign = "+";
} else if (cFlags & SPACE) {
sign = " ";
}
if (thousandSep && intPart >= 1000) {
const buf = [];
while (true) {
buf.push((intPart % 1000).toString().padStart(3, "0"));
intPart = Math.trunc(intPart / 1000);
if (intPart < 1000) {
buf.push(intPart.toString());
break;
}
}
intPart = buf.reverse().join(thousandSep);
} else {
intPart = intPart.toString();
}
let n = `${intPart}${decPart}`;
if (nWidth !== undefined) {
n = n.padStart(nWidth - sign.length, cFlags & ZERO ? "0" : " ");
}
return `${sign}${n}`;
});
}
iconStreamFromIcon() {
/* Not implemented */
}
printd(cFormat, oDate) {
switch (cFormat) {
case 0:
return this.printd("D:yyyymmddHHMMss", oDate);
case 1:
return this.printd("yyyy.mm.dd HH:MM:ss", oDate);
case 2:
return this.printd("m/d/yy h:MM:ss tt", oDate);
}
const handlers = {
mmmm: data => {
return this._months[data.month];
},
mmm: data => {
return this._months[data.month].substring(0, 3);
},
mm: data => {
return (data.month + 1).toString().padStart(2, "0");
},
m: data => {
return (data.month + 1).toString();
},
dddd: data => {
return this._days[data.dayOfWeek];
},
ddd: data => {
return this._days[data.dayOfWeek].substring(0, 3);
},
dd: data => {
return data.day.toString().padStart(2, "0");
},
d: data => {
return data.day.toString();
},
yyyy: data => {
return data.year.toString();
},
yy: data => {
return (data.year % 100).toString().padStart(2, "0");
},
HH: data => {
return data.hours.toString().padStart(2, "0");
},
H: data => {
return data.hours.toString();
},
hh: data => {
return (1 + ((data.hours + 11) % 12)).toString().padStart(2, "0");
},
h: data => {
return (1 + ((data.hours + 11) % 12)).toString();
},
MM: data => {
return data.minutes.toString().padStart(2, "0");
},
M: data => {
return data.minutes.toString();
},
ss: data => {
return data.seconds.toString().padStart(2, "0");
},
s: data => {
return data.seconds.toString();
},
tt: data => {
return data.hours < 12 ? "am" : "pm";
},
t: data => {
return data.hours < 12 ? "a" : "p";
},
};
const data = {
year: oDate.getFullYear(),
month: oDate.getMonth(),
day: oDate.getDate(),
dayOfWeek: oDate.getDay(),
hours: oDate.getHours(),
minutes: oDate.getMinutes(),
seconds: oDate.getSeconds(),
};
const patterns = /(mmmm|mmm|mm|m|dddd|ddd|dd|d|yyyy|yy|HH|H|hh|h|MM|M|ss|s|tt|t|\\.)/g;
return cFormat.replace(patterns, function (match, pattern) {
if (pattern in handlers) {
return handlers[pattern](data);
}
return pattern.charCodeAt(1);
});
}
printx(cFormat, cSource) {
// case
const handlers = [x => x, x => x.toUpperCase(), x => x.toLowerCase()];
const buf = [];
let i = 0;
const ii = cSource.length;
let currCase = handlers[0];
let escaped = false;
for (const command of cFormat) {
if (escaped) {
buf.push(command);
escaped = false;
continue;
}
if (i >= ii) {
break;
}
switch (command) {
case "?":
buf.push(currCase(cSource.charAt(i++)));
break;
case "X":
while (i < ii) {
const char = cSource.charAt(i++);
if (
("a" <= char && char <= "z") ||
("A" <= char && char <= "Z") ||
("0" <= char && char <= "9")
) {
buf.push(currCase(char));
break;
}
}
break;
case "A":
while (i < ii) {
const char = cSource.charAt(i++);
if (("a" <= char && char <= "z") || ("A" <= char && char <= "Z")) {
buf.push(currCase(char));
break;
}
}
break;
case "9":
while (i < ii) {
const char = cSource.charAt(i++);
if ("0" <= char && char <= "9") {
buf.push(char);
break;
}
}
break;
case "*":
while (i < ii) {
buf.push(currCase(cSource.charAt(i++)));
}
break;
case "\\":
escaped = true;
break;
case ">":
currCase = handlers[1];
break;
case "<":
currCase = handlers[2];
break;
case "=":
currCase = handlers[0];
break;
default:
buf.push(command);
}
}
return buf.join("");
}
scand(cFormat, cDate) {
switch (cFormat) {
case 0:
return this.scand("D:yyyymmddHHMMss", cDate);
case 1:
return this.scand("yyyy.mm.dd HH:MM:ss", cDate);
case 2:
return this.scand("m/d/yy h:MM:ss tt", cDate);
}
if (!this._scandCache.has(cFormat)) {
const months = this._months;
const days = this._days;
const handlers = {
mmmm: {
pattern: `(${months.join("|")})`,
action: (value, data) => {
data.month = months.indexOf(value);
},
},
mmm: {
pattern: `(${months.map(month => month.substring(0, 3)).join("|")})`,
action: (value, data) => {
data.month = months.findIndex(
month => month.substring(0, 3) === value
);
},
},
mm: {
pattern: `([0-9]{2})`,
action: (value, data) => {
data.month = parseInt(value) - 1;
},
},
m: {
pattern: `([0-9]{1,2})`,
action: (value, data) => {
data.month = parseInt(value) - 1;
},
},
dddd: {
pattern: `(${days.join("|")})`,
action: (value, data) => {
data.day = days.indexOf(value);
},
},
ddd: {
pattern: `(${days.map(day => day.substring(0, 3)).join("|")})`,
action: (value, data) => {
data.day = days.findIndex(day => day.substring(0, 3) === value);
},
},
dd: {
pattern: "([0-9]{2})",
action: (value, data) => {
data.day = parseInt(value);
},
},
d: {
pattern: "([0-9]{1,2})",
action: (value, data) => {
data.day = parseInt(value);
},
},
yyyy: {
pattern: "([0-9]{4})",
action: (value, data) => {
data.year = parseInt(value);
},
},
yy: {
pattern: "([0-9]{2})",
action: (value, data) => {
data.year = 2000 + parseInt(value);
},
},
HH: {
pattern: "([0-9]{2})",
action: (value, data) => {
data.hours = parseInt(value);
},
},
H: {
pattern: "([0-9]{1,2})",
action: (value, data) => {
data.hours = parseInt(value);
},
},
hh: {
pattern: "([0-9]{2})",
action: (value, data) => {
data.hours = parseInt(value);
},
},
h: {
pattern: "([0-9]{1,2})",
action: (value, data) => {
data.hours = parseInt(value);
},
},
MM: {
pattern: "([0-9]{2})",
action: (value, data) => {
data.minutes = parseInt(value);
},
},
M: {
pattern: "([0-9]{1,2})",
action: (value, data) => {
data.minutes = parseInt(value);
},
},
ss: {
pattern: "([0-9]{2})",
action: (value, data) => {
data.seconds = parseInt(value);
},
},
s: {
pattern: "([0-9]{1,2})",
action: (value, data) => {
data.seconds = parseInt(value);
},
},
tt: {
pattern: "([aApP][mM])",
action: (value, data) => {
const char = value.charAt(0);
data.am = char === "a" || char === "A";
},
},
t: {
pattern: "([aApP])",
action: (value, data) => {
data.am = value === "a" || value === "A";
},
},
};
// escape the string
const escapedFormat = cFormat.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&");
const patterns = /(mmmm|mmm|mm|m|dddd|ddd|dd|d|yyyy|yy|HH|H|hh|h|MM|M|ss|s|tt|t)/g;
const actions = [];
const re = escapedFormat.replace(patterns, function (
match,
patternElement
) {
const { pattern, action } = handlers[patternElement];
actions.push(action);
return pattern;
});
this._scandCache.set(cFormat, [new RegExp(re, "g"), actions]);
}
const [regexForFormat, actions] = this._scandCache.get(cFormat);
const matches = regexForFormat.exec(cDate);
if (matches.length !== actions.length + 1) {
throw new Error("Invalid date in util.scand");
}
const data = {
year: 0,
month: 0,
day: 0,
hours: 0,
minutes: 0,
seconds: 0,
am: null,
};
actions.forEach((action, i) => action(matches[i + 1], data));
if (data.am !== null) {
data.hours = (data.hours % 12) + (data.am ? 0 : 12);
}
return new Date(
data.year,
data.month,
data.day,
data.hours,
data.minutes,
data.seconds
);
}
spansToXML() {
/* Not implemented */
}
stringFromStream() {
/* Not implemented */
}
xmlToSpans() {
/* Not implemented */
}
}
export { Util };

View File

@ -32,6 +32,7 @@
"pdf_find_utils_spec.js",
"pdf_history_spec.js",
"primitives_spec.js",
"scripting_spec.js",
"stream_spec.js",
"type1_parser_spec.js",
"ui_utils_spec.js",

View File

@ -77,6 +77,7 @@ async function initializePDFJS(callback) {
"pdfjs-test/unit/pdf_find_utils_spec.js",
"pdfjs-test/unit/pdf_history_spec.js",
"pdfjs-test/unit/primitives_spec.js",
"pdfjs-test/unit/scripting_spec.js",
"pdfjs-test/unit/stream_spec.js",
"pdfjs-test/unit/type1_parser_spec.js",
"pdfjs-test/unit/ui_utils_spec.js",

View File

@ -0,0 +1,98 @@
/* Copyright 2020 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { initSandbox } from "../../src/scripting_api/initialization.js";
describe("Util", function () {
let sandbox, util;
beforeAll(function (done) {
sandbox = Object.create(null);
const extra = { send: null, crackURL: null };
const data = { objects: {}, calculationOrder: [] };
initSandbox(data, extra, sandbox);
util = sandbox.util;
done();
});
afterAll(function () {
sandbox = util = null;
});
describe("printd", function () {
it("should print a date according to a format", function (done) {
const date = new Date("April 15, 1707 3:14:15");
expect(util.printd(0, date)).toEqual("D:17070415031415");
expect(util.printd(1, date)).toEqual("1707.04.15 03:14:15");
expect(util.printd(2, date)).toEqual("4/15/07 3:14:15 am");
expect(util.printd("mmmm mmm mm m", date)).toEqual("April Apr 04 4");
expect(util.printd("dddd ddd dd d", date)).toEqual("Friday Fri 15 15");
done();
});
});
describe("scand", function () {
it("should parse a date according to a format", function (done) {
const date = new Date("April 15, 1707 3:14:15");
expect(util.scand(0, "D:17070415031415")).toEqual(date);
expect(util.scand(1, "1707.04.15 03:14:15")).toEqual(date);
expect(util.scand(2, "4/15/07 3:14:15 am")).toEqual(
new Date("April 15, 2007 3:14:15")
);
done();
});
});
describe("printf", function () {
it("should print some data according to a format", function (done) {
expect(util.printf("Integer numbers: %d, %d,...", 1.234, 56.789)).toEqual(
"Integer numbers: 1, 56,..."
);
expect(util.printf("Hex numbers: %x, %x,...", 1234, 56789)).toEqual(
"Hex numbers: 4D2, DDD5,..."
);
expect(
util.printf("Hex numbers with 0x: %#x, %#x,...", 1234, 56789)
).toEqual("Hex numbers with 0x: 0x4D2, 0xDDD5,...");
expect(util.printf("Decimal number: %,0+.3f", 1234567.89123)).toEqual(
"Decimal number: +1,234,567.891"
);
expect(util.printf("Decimal number: %,0+8.3f", 1.234567)).toEqual(
"Decimal number: + 1.235"
);
done();
});
it("should print a string with no argument", function (done) {
expect(util.printf("hello world")).toEqual("hello world");
done();
});
it("should print a string with a percent", function (done) {
expect(util.printf("%%s")).toEqual("%%s");
expect(util.printf("%%s", "hello")).toEqual("%%s");
done();
});
});
describe("printx", function () {
it("should print some data according to a format", function (done) {
expect(util.printx("9 (999) 999-9999", "aaa14159697489zzz")).toEqual(
"1 (415) 969-7489"
);
done();
});
});
});