pdf.js/src/scripting_api/util.js
Jonas Jenwald 1fc09f0235 Enable the unicorn/prefer-string-replace-all ESLint plugin rule
Note that the `replaceAll` method still requires that a *global* regular expression is used, however by using this method it's immediately obvious when looking at the code that all occurrences will be replaced; please see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replaceAll#parameters

Please find additional details at https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-string-replace-all.md
2023-03-23 12:57:10 +01:00

588 lines
14 KiB
JavaScript

/* 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 { PDFObject } from "./pdf_object.js";
class Util extends PDFObject {
constructor(data) {
super(data);
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;
// used with crackURL
this._externalCall = data.externalCall;
}
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#]+)?(\d+)?(\.\d+)?(.)/g;
const PLUS = 1;
const SPACE = 2;
const ZERO = 4;
const HASH = 8;
let i = 0;
return args[0].replaceAll(
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 = Math.abs(arg - intPart).toFixed(nPrecision);
} else {
decPart = Math.abs(arg - intPart).toString();
}
if (decPart.length > 2) {
decPart = `${decimalSep}${decPart.substring(2)}`;
} else {
if (decPart === "1") {
intPart += Math.sign(arg);
}
decPart = cFlags & HASH ? "." : "";
}
}
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.replaceAll(patterns, function (match, pattern) {
if (pattern in handlers) {
return handlers[pattern](data);
}
return pattern.charCodeAt(1);
});
}
printx(cFormat, cSource) {
// case
cSource = (cSource ?? "").toString();
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) {
if (typeof cDate !== "string") {
return new Date(cDate);
}
if (cDate === "") {
return new Date();
}
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: `(\\d{2})`,
action: (value, data) => {
data.month = parseInt(value) - 1;
},
},
m: {
pattern: `(\\d{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: "(\\d{2})",
action: (value, data) => {
data.day = parseInt(value);
},
},
d: {
pattern: "(\\d{1,2})",
action: (value, data) => {
data.day = parseInt(value);
},
},
yyyy: {
pattern: "(\\d{4})",
action: (value, data) => {
data.year = parseInt(value);
},
},
yy: {
pattern: "(\\d{2})",
action: (value, data) => {
data.year = 2000 + parseInt(value);
},
},
HH: {
pattern: "(\\d{2})",
action: (value, data) => {
data.hours = parseInt(value);
},
},
H: {
pattern: "(\\d{1,2})",
action: (value, data) => {
data.hours = parseInt(value);
},
},
hh: {
pattern: "(\\d{2})",
action: (value, data) => {
data.hours = parseInt(value);
},
},
h: {
pattern: "(\\d{1,2})",
action: (value, data) => {
data.hours = parseInt(value);
},
},
MM: {
pattern: "(\\d{2})",
action: (value, data) => {
data.minutes = parseInt(value);
},
},
M: {
pattern: "(\\d{1,2})",
action: (value, data) => {
data.minutes = parseInt(value);
},
},
ss: {
pattern: "(\\d{2})",
action: (value, data) => {
data.seconds = parseInt(value);
},
},
s: {
pattern: "(\\d{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.replaceAll(/[.*+\-?^${}()|[\]\\]/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.replaceAll(
patterns,
function (match, patternElement) {
const { pattern, action } = handlers[patternElement];
actions.push(action);
return pattern;
}
);
this._scandCache.set(cFormat, [re, actions]);
}
const [re, actions] = this._scandCache.get(cFormat);
const matches = new RegExp(`^${re}$`, "g").exec(cDate);
if (!matches || matches.length !== actions.length + 1) {
return null;
}
const data = {
year: 2000,
month: 0,
day: 1,
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 };