mirror of
https://github.com/mozilla/pdf.js.git
synced 2025-04-19 06:38:07 +02:00
Currently we re-implement the same helper function twice, which in hindsight seems like the wrong decision since that way it's quite easy for the implementations to accidentally diverge. The reason for doing it this way was because the code in the worker-thread is able to check for `Ref`- and `Name`-instances directly, which obviously isn't possible in the viewer but can be solved by passing validation-functions to the helper.
1230 lines
30 KiB
JavaScript
1230 lines
30 KiB
JavaScript
/* Copyright 2012 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.
|
||
*/
|
||
/* globals process */
|
||
|
||
// NW.js / Electron is a browser context, but copies some Node.js objects; see
|
||
// http://docs.nwjs.io/en/latest/For%20Users/Advanced/JavaScript%20Contexts%20in%20NW.js/#access-nodejs-and-nwjs-api-in-browser-context
|
||
// https://www.electronjs.org/docs/api/process#processversionselectron-readonly
|
||
// https://www.electronjs.org/docs/api/process#processtype-readonly
|
||
const isNodeJS =
|
||
(typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) &&
|
||
typeof process === "object" &&
|
||
process + "" === "[object process]" &&
|
||
!process.versions.nw &&
|
||
!(process.versions.electron && process.type && process.type !== "browser");
|
||
|
||
const IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0];
|
||
const FONT_IDENTITY_MATRIX = [0.001, 0, 0, 0.001, 0, 0];
|
||
|
||
// Represent the percentage of the height of a single-line field over
|
||
// the font size. Acrobat seems to use this value.
|
||
const LINE_FACTOR = 1.35;
|
||
const LINE_DESCENT_FACTOR = 0.35;
|
||
const BASELINE_FACTOR = LINE_DESCENT_FACTOR / LINE_FACTOR;
|
||
|
||
/**
|
||
* Refer to the `WorkerTransport.getRenderingIntent`-method in the API, to see
|
||
* how these flags are being used:
|
||
* - ANY, DISPLAY, and PRINT are the normal rendering intents, note the
|
||
* `PDFPageProxy.{render, getOperatorList, getAnnotations}`-methods.
|
||
* - SAVE is used, on the worker-thread, when saving modified annotations.
|
||
* - ANNOTATIONS_FORMS, ANNOTATIONS_STORAGE, ANNOTATIONS_DISABLE control which
|
||
* annotations are rendered onto the canvas (i.e. by being included in the
|
||
* operatorList), note the `PDFPageProxy.{render, getOperatorList}`-methods
|
||
* and their `annotationMode`-option.
|
||
* - IS_EDITING is used when editing is active in the viewer.
|
||
* - OPLIST is used with the `PDFPageProxy.getOperatorList`-method, note the
|
||
* `OperatorList`-constructor (on the worker-thread).
|
||
*/
|
||
const RenderingIntentFlag = {
|
||
ANY: 0x01,
|
||
DISPLAY: 0x02,
|
||
PRINT: 0x04,
|
||
SAVE: 0x08,
|
||
ANNOTATIONS_FORMS: 0x10,
|
||
ANNOTATIONS_STORAGE: 0x20,
|
||
ANNOTATIONS_DISABLE: 0x40,
|
||
IS_EDITING: 0x80,
|
||
OPLIST: 0x100,
|
||
};
|
||
|
||
const AnnotationMode = {
|
||
DISABLE: 0,
|
||
ENABLE: 1,
|
||
ENABLE_FORMS: 2,
|
||
ENABLE_STORAGE: 3,
|
||
};
|
||
|
||
const AnnotationEditorPrefix = "pdfjs_internal_editor_";
|
||
|
||
const AnnotationEditorType = {
|
||
DISABLE: -1,
|
||
NONE: 0,
|
||
FREETEXT: 3,
|
||
HIGHLIGHT: 9,
|
||
STAMP: 13,
|
||
INK: 15,
|
||
SIGNATURE: 101,
|
||
};
|
||
|
||
const AnnotationEditorParamsType = {
|
||
RESIZE: 1,
|
||
CREATE: 2,
|
||
FREETEXT_SIZE: 11,
|
||
FREETEXT_COLOR: 12,
|
||
FREETEXT_OPACITY: 13,
|
||
INK_COLOR: 21,
|
||
INK_THICKNESS: 22,
|
||
INK_OPACITY: 23,
|
||
HIGHLIGHT_COLOR: 31,
|
||
HIGHLIGHT_DEFAULT_COLOR: 32,
|
||
HIGHLIGHT_THICKNESS: 33,
|
||
HIGHLIGHT_FREE: 34,
|
||
HIGHLIGHT_SHOW_ALL: 35,
|
||
DRAW_STEP: 41,
|
||
};
|
||
|
||
// Permission flags from Table 22, Section 7.6.3.2 of the PDF specification.
|
||
const PermissionFlag = {
|
||
PRINT: 0x04,
|
||
MODIFY_CONTENTS: 0x08,
|
||
COPY: 0x10,
|
||
MODIFY_ANNOTATIONS: 0x20,
|
||
FILL_INTERACTIVE_FORMS: 0x100,
|
||
COPY_FOR_ACCESSIBILITY: 0x200,
|
||
ASSEMBLE: 0x400,
|
||
PRINT_HIGH_QUALITY: 0x800,
|
||
};
|
||
|
||
const TextRenderingMode = {
|
||
FILL: 0,
|
||
STROKE: 1,
|
||
FILL_STROKE: 2,
|
||
INVISIBLE: 3,
|
||
FILL_ADD_TO_PATH: 4,
|
||
STROKE_ADD_TO_PATH: 5,
|
||
FILL_STROKE_ADD_TO_PATH: 6,
|
||
ADD_TO_PATH: 7,
|
||
FILL_STROKE_MASK: 3,
|
||
ADD_TO_PATH_FLAG: 4,
|
||
};
|
||
|
||
const ImageKind = {
|
||
GRAYSCALE_1BPP: 1,
|
||
RGB_24BPP: 2,
|
||
RGBA_32BPP: 3,
|
||
};
|
||
|
||
const AnnotationType = {
|
||
TEXT: 1,
|
||
LINK: 2,
|
||
FREETEXT: 3,
|
||
LINE: 4,
|
||
SQUARE: 5,
|
||
CIRCLE: 6,
|
||
POLYGON: 7,
|
||
POLYLINE: 8,
|
||
HIGHLIGHT: 9,
|
||
UNDERLINE: 10,
|
||
SQUIGGLY: 11,
|
||
STRIKEOUT: 12,
|
||
STAMP: 13,
|
||
CARET: 14,
|
||
INK: 15,
|
||
POPUP: 16,
|
||
FILEATTACHMENT: 17,
|
||
SOUND: 18,
|
||
MOVIE: 19,
|
||
WIDGET: 20,
|
||
SCREEN: 21,
|
||
PRINTERMARK: 22,
|
||
TRAPNET: 23,
|
||
WATERMARK: 24,
|
||
THREED: 25,
|
||
REDACT: 26,
|
||
};
|
||
|
||
const AnnotationReplyType = {
|
||
GROUP: "Group",
|
||
REPLY: "R",
|
||
};
|
||
|
||
const AnnotationFlag = {
|
||
INVISIBLE: 0x01,
|
||
HIDDEN: 0x02,
|
||
PRINT: 0x04,
|
||
NOZOOM: 0x08,
|
||
NOROTATE: 0x10,
|
||
NOVIEW: 0x20,
|
||
READONLY: 0x40,
|
||
LOCKED: 0x80,
|
||
TOGGLENOVIEW: 0x100,
|
||
LOCKEDCONTENTS: 0x200,
|
||
};
|
||
|
||
const AnnotationFieldFlag = {
|
||
READONLY: 0x0000001,
|
||
REQUIRED: 0x0000002,
|
||
NOEXPORT: 0x0000004,
|
||
MULTILINE: 0x0001000,
|
||
PASSWORD: 0x0002000,
|
||
NOTOGGLETOOFF: 0x0004000,
|
||
RADIO: 0x0008000,
|
||
PUSHBUTTON: 0x0010000,
|
||
COMBO: 0x0020000,
|
||
EDIT: 0x0040000,
|
||
SORT: 0x0080000,
|
||
FILESELECT: 0x0100000,
|
||
MULTISELECT: 0x0200000,
|
||
DONOTSPELLCHECK: 0x0400000,
|
||
DONOTSCROLL: 0x0800000,
|
||
COMB: 0x1000000,
|
||
RICHTEXT: 0x2000000,
|
||
RADIOSINUNISON: 0x2000000,
|
||
COMMITONSELCHANGE: 0x4000000,
|
||
};
|
||
|
||
const AnnotationBorderStyleType = {
|
||
SOLID: 1,
|
||
DASHED: 2,
|
||
BEVELED: 3,
|
||
INSET: 4,
|
||
UNDERLINE: 5,
|
||
};
|
||
|
||
const AnnotationActionEventType = {
|
||
E: "Mouse Enter",
|
||
X: "Mouse Exit",
|
||
D: "Mouse Down",
|
||
U: "Mouse Up",
|
||
Fo: "Focus",
|
||
Bl: "Blur",
|
||
PO: "PageOpen",
|
||
PC: "PageClose",
|
||
PV: "PageVisible",
|
||
PI: "PageInvisible",
|
||
K: "Keystroke",
|
||
F: "Format",
|
||
V: "Validate",
|
||
C: "Calculate",
|
||
};
|
||
|
||
const DocumentActionEventType = {
|
||
WC: "WillClose",
|
||
WS: "WillSave",
|
||
DS: "DidSave",
|
||
WP: "WillPrint",
|
||
DP: "DidPrint",
|
||
};
|
||
|
||
const PageActionEventType = {
|
||
O: "PageOpen",
|
||
C: "PageClose",
|
||
};
|
||
|
||
const VerbosityLevel = {
|
||
ERRORS: 0,
|
||
WARNINGS: 1,
|
||
INFOS: 5,
|
||
};
|
||
|
||
// All the possible operations for an operator list.
|
||
const OPS = {
|
||
// Intentionally start from 1 so it is easy to spot bad operators that will be
|
||
// 0's.
|
||
// PLEASE NOTE: We purposely keep any removed operators commented out, since
|
||
// re-numbering the list would risk breaking third-party users.
|
||
dependency: 1,
|
||
setLineWidth: 2,
|
||
setLineCap: 3,
|
||
setLineJoin: 4,
|
||
setMiterLimit: 5,
|
||
setDash: 6,
|
||
setRenderingIntent: 7,
|
||
setFlatness: 8,
|
||
setGState: 9,
|
||
save: 10,
|
||
restore: 11,
|
||
transform: 12,
|
||
moveTo: 13,
|
||
lineTo: 14,
|
||
curveTo: 15,
|
||
curveTo2: 16,
|
||
curveTo3: 17,
|
||
closePath: 18,
|
||
rectangle: 19,
|
||
stroke: 20,
|
||
closeStroke: 21,
|
||
fill: 22,
|
||
eoFill: 23,
|
||
fillStroke: 24,
|
||
eoFillStroke: 25,
|
||
closeFillStroke: 26,
|
||
closeEOFillStroke: 27,
|
||
endPath: 28,
|
||
clip: 29,
|
||
eoClip: 30,
|
||
beginText: 31,
|
||
endText: 32,
|
||
setCharSpacing: 33,
|
||
setWordSpacing: 34,
|
||
setHScale: 35,
|
||
setLeading: 36,
|
||
setFont: 37,
|
||
setTextRenderingMode: 38,
|
||
setTextRise: 39,
|
||
moveText: 40,
|
||
setLeadingMoveText: 41,
|
||
setTextMatrix: 42,
|
||
nextLine: 43,
|
||
showText: 44,
|
||
showSpacedText: 45,
|
||
nextLineShowText: 46,
|
||
nextLineSetSpacingShowText: 47,
|
||
setCharWidth: 48,
|
||
setCharWidthAndBounds: 49,
|
||
setStrokeColorSpace: 50,
|
||
setFillColorSpace: 51,
|
||
setStrokeColor: 52,
|
||
setStrokeColorN: 53,
|
||
setFillColor: 54,
|
||
setFillColorN: 55,
|
||
setStrokeGray: 56,
|
||
setFillGray: 57,
|
||
setStrokeRGBColor: 58,
|
||
setFillRGBColor: 59,
|
||
setStrokeCMYKColor: 60,
|
||
setFillCMYKColor: 61,
|
||
shadingFill: 62,
|
||
beginInlineImage: 63,
|
||
beginImageData: 64,
|
||
endInlineImage: 65,
|
||
paintXObject: 66,
|
||
markPoint: 67,
|
||
markPointProps: 68,
|
||
beginMarkedContent: 69,
|
||
beginMarkedContentProps: 70,
|
||
endMarkedContent: 71,
|
||
beginCompat: 72,
|
||
endCompat: 73,
|
||
paintFormXObjectBegin: 74,
|
||
paintFormXObjectEnd: 75,
|
||
beginGroup: 76,
|
||
endGroup: 77,
|
||
// beginAnnotations: 78,
|
||
// endAnnotations: 79,
|
||
beginAnnotation: 80,
|
||
endAnnotation: 81,
|
||
// paintJpegXObject: 82,
|
||
paintImageMaskXObject: 83,
|
||
paintImageMaskXObjectGroup: 84,
|
||
paintImageXObject: 85,
|
||
paintInlineImageXObject: 86,
|
||
paintInlineImageXObjectGroup: 87,
|
||
paintImageXObjectRepeat: 88,
|
||
paintImageMaskXObjectRepeat: 89,
|
||
paintSolidColorImageMask: 90,
|
||
constructPath: 91,
|
||
setStrokeTransparent: 92,
|
||
setFillTransparent: 93,
|
||
};
|
||
|
||
const PasswordResponses = {
|
||
NEED_PASSWORD: 1,
|
||
INCORRECT_PASSWORD: 2,
|
||
};
|
||
|
||
let verbosity = VerbosityLevel.WARNINGS;
|
||
|
||
function setVerbosityLevel(level) {
|
||
if (Number.isInteger(level)) {
|
||
verbosity = level;
|
||
}
|
||
}
|
||
|
||
function getVerbosityLevel() {
|
||
return verbosity;
|
||
}
|
||
|
||
// A notice for devs. These are good for things that are helpful to devs, such
|
||
// as warning that Workers were disabled, which is important to devs but not
|
||
// end users.
|
||
function info(msg) {
|
||
if (verbosity >= VerbosityLevel.INFOS) {
|
||
// eslint-disable-next-line no-console
|
||
console.log(`Info: ${msg}`);
|
||
}
|
||
}
|
||
|
||
// Non-fatal warnings.
|
||
function warn(msg) {
|
||
if (verbosity >= VerbosityLevel.WARNINGS) {
|
||
// eslint-disable-next-line no-console
|
||
console.log(`Warning: ${msg}`);
|
||
}
|
||
}
|
||
|
||
function unreachable(msg) {
|
||
throw new Error(msg);
|
||
}
|
||
|
||
function assert(cond, msg) {
|
||
if (!cond) {
|
||
unreachable(msg);
|
||
}
|
||
}
|
||
|
||
// Checks if URLs use one of the allowed protocols, e.g. to avoid XSS.
|
||
function _isValidProtocol(url) {
|
||
switch (url?.protocol) {
|
||
case "http:":
|
||
case "https:":
|
||
case "ftp:":
|
||
case "mailto:":
|
||
case "tel:":
|
||
return true;
|
||
default:
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Attempts to create a valid absolute URL.
|
||
*
|
||
* @param {URL|string} url - An absolute, or relative, URL.
|
||
* @param {URL|string} [baseUrl] - An absolute URL.
|
||
* @param {Object} [options]
|
||
* @returns Either a valid {URL}, or `null` otherwise.
|
||
*/
|
||
function createValidAbsoluteUrl(url, baseUrl = null, options = null) {
|
||
if (!url) {
|
||
return null;
|
||
}
|
||
if (options && typeof url === "string") {
|
||
// Let URLs beginning with "www." default to using the "http://" protocol.
|
||
if (options.addDefaultProtocol && url.startsWith("www.")) {
|
||
const dots = url.match(/\./g);
|
||
// Avoid accidentally matching a *relative* URL pointing to a file named
|
||
// e.g. "www.pdf" or similar.
|
||
if (dots?.length >= 2) {
|
||
url = `http://${url}`;
|
||
}
|
||
}
|
||
|
||
// According to ISO 32000-1:2008, section 12.6.4.7, URIs should be encoded
|
||
// in 7-bit ASCII. Some bad PDFs use UTF-8 encoding; see bug 1122280.
|
||
if (options.tryConvertEncoding) {
|
||
try {
|
||
url = stringToUTF8String(url);
|
||
} catch {}
|
||
}
|
||
}
|
||
|
||
const absoluteUrl = baseUrl ? URL.parse(url, baseUrl) : URL.parse(url);
|
||
return _isValidProtocol(absoluteUrl) ? absoluteUrl : null;
|
||
}
|
||
|
||
function shadow(obj, prop, value, nonSerializable = false) {
|
||
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
|
||
assert(
|
||
prop in obj,
|
||
`shadow: Property "${prop && prop.toString()}" not found in object.`
|
||
);
|
||
}
|
||
Object.defineProperty(obj, prop, {
|
||
value,
|
||
enumerable: !nonSerializable,
|
||
configurable: true,
|
||
writable: false,
|
||
});
|
||
return value;
|
||
}
|
||
|
||
/**
|
||
* @type {any}
|
||
*/
|
||
const BaseException = (function BaseExceptionClosure() {
|
||
// eslint-disable-next-line no-shadow
|
||
function BaseException(message, name) {
|
||
if (
|
||
(typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) &&
|
||
this.constructor === BaseException
|
||
) {
|
||
unreachable("Cannot initialize BaseException.");
|
||
}
|
||
this.message = message;
|
||
this.name = name;
|
||
}
|
||
BaseException.prototype = new Error();
|
||
BaseException.constructor = BaseException;
|
||
|
||
return BaseException;
|
||
})();
|
||
|
||
class PasswordException extends BaseException {
|
||
constructor(msg, code) {
|
||
super(msg, "PasswordException");
|
||
this.code = code;
|
||
}
|
||
}
|
||
|
||
class UnknownErrorException extends BaseException {
|
||
constructor(msg, details) {
|
||
super(msg, "UnknownErrorException");
|
||
this.details = details;
|
||
}
|
||
}
|
||
|
||
class InvalidPDFException extends BaseException {
|
||
constructor(msg) {
|
||
super(msg, "InvalidPDFException");
|
||
}
|
||
}
|
||
|
||
class ResponseException extends BaseException {
|
||
constructor(msg, status, missing) {
|
||
super(msg, "ResponseException");
|
||
this.status = status;
|
||
this.missing = missing;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Error caused during parsing PDF data.
|
||
*/
|
||
class FormatError extends BaseException {
|
||
constructor(msg) {
|
||
super(msg, "FormatError");
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Error used to indicate task cancellation.
|
||
*/
|
||
class AbortException extends BaseException {
|
||
constructor(msg) {
|
||
super(msg, "AbortException");
|
||
}
|
||
}
|
||
|
||
function bytesToString(bytes) {
|
||
if (typeof bytes !== "object" || bytes?.length === undefined) {
|
||
unreachable("Invalid argument for bytesToString");
|
||
}
|
||
const length = bytes.length;
|
||
const MAX_ARGUMENT_COUNT = 8192;
|
||
if (length < MAX_ARGUMENT_COUNT) {
|
||
return String.fromCharCode.apply(null, bytes);
|
||
}
|
||
const strBuf = [];
|
||
for (let i = 0; i < length; i += MAX_ARGUMENT_COUNT) {
|
||
const chunkEnd = Math.min(i + MAX_ARGUMENT_COUNT, length);
|
||
const chunk = bytes.subarray(i, chunkEnd);
|
||
strBuf.push(String.fromCharCode.apply(null, chunk));
|
||
}
|
||
return strBuf.join("");
|
||
}
|
||
|
||
function stringToBytes(str) {
|
||
if (typeof str !== "string") {
|
||
unreachable("Invalid argument for stringToBytes");
|
||
}
|
||
const length = str.length;
|
||
const bytes = new Uint8Array(length);
|
||
for (let i = 0; i < length; ++i) {
|
||
bytes[i] = str.charCodeAt(i) & 0xff;
|
||
}
|
||
return bytes;
|
||
}
|
||
|
||
function string32(value) {
|
||
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
|
||
assert(
|
||
typeof value === "number" && Math.abs(value) < 2 ** 32,
|
||
`string32: Unexpected input "${value}".`
|
||
);
|
||
}
|
||
return String.fromCharCode(
|
||
(value >> 24) & 0xff,
|
||
(value >> 16) & 0xff,
|
||
(value >> 8) & 0xff,
|
||
value & 0xff
|
||
);
|
||
}
|
||
|
||
function objectSize(obj) {
|
||
return Object.keys(obj).length;
|
||
}
|
||
|
||
// Ensure that the returned Object has a `null` prototype; hence why
|
||
// `Object.fromEntries(...)` is not used.
|
||
function objectFromMap(map) {
|
||
const obj = Object.create(null);
|
||
for (const [key, value] of map) {
|
||
obj[key] = value;
|
||
}
|
||
return obj;
|
||
}
|
||
|
||
// Checks the endianness of the platform.
|
||
function isLittleEndian() {
|
||
const buffer8 = new Uint8Array(4);
|
||
buffer8[0] = 1;
|
||
const view32 = new Uint32Array(buffer8.buffer, 0, 1);
|
||
return view32[0] === 1;
|
||
}
|
||
|
||
// Checks if it's possible to eval JS expressions.
|
||
function isEvalSupported() {
|
||
try {
|
||
new Function(""); // eslint-disable-line no-new, no-new-func
|
||
return true;
|
||
} catch {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
class FeatureTest {
|
||
static get isLittleEndian() {
|
||
return shadow(this, "isLittleEndian", isLittleEndian());
|
||
}
|
||
|
||
static get isEvalSupported() {
|
||
return shadow(this, "isEvalSupported", isEvalSupported());
|
||
}
|
||
|
||
static get isOffscreenCanvasSupported() {
|
||
return shadow(
|
||
this,
|
||
"isOffscreenCanvasSupported",
|
||
typeof OffscreenCanvas !== "undefined"
|
||
);
|
||
}
|
||
|
||
static get isImageDecoderSupported() {
|
||
return shadow(
|
||
this,
|
||
"isImageDecoderSupported",
|
||
typeof ImageDecoder !== "undefined"
|
||
);
|
||
}
|
||
|
||
static get platform() {
|
||
if (
|
||
(typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) ||
|
||
(typeof navigator !== "undefined" &&
|
||
typeof navigator?.platform === "string" &&
|
||
typeof navigator?.userAgent === "string")
|
||
) {
|
||
const { platform, userAgent } = navigator;
|
||
|
||
return shadow(this, "platform", {
|
||
isAndroid: userAgent.includes("Android"),
|
||
isLinux: platform.includes("Linux"),
|
||
isMac: platform.includes("Mac"),
|
||
isWindows: platform.includes("Win"),
|
||
isFirefox:
|
||
(typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) ||
|
||
userAgent.includes("Firefox"),
|
||
});
|
||
}
|
||
return shadow(this, "platform", {
|
||
isAndroid: false,
|
||
isLinux: false,
|
||
isMac: false,
|
||
isWindows: false,
|
||
isFirefox: false,
|
||
});
|
||
}
|
||
|
||
static get isCSSRoundSupported() {
|
||
return shadow(
|
||
this,
|
||
"isCSSRoundSupported",
|
||
globalThis.CSS?.supports?.("width: round(1.5px, 1px)")
|
||
);
|
||
}
|
||
}
|
||
|
||
const hexNumbers = Array.from(Array(256).keys(), n =>
|
||
n.toString(16).padStart(2, "0")
|
||
);
|
||
|
||
class Util {
|
||
static makeHexColor(r, g, b) {
|
||
return `#${hexNumbers[r]}${hexNumbers[g]}${hexNumbers[b]}`;
|
||
}
|
||
|
||
// Apply a scaling matrix to some min/max values.
|
||
// If a scaling factor is negative then min and max must be
|
||
// swapped.
|
||
static scaleMinMax(transform, minMax) {
|
||
let temp;
|
||
if (transform[0]) {
|
||
if (transform[0] < 0) {
|
||
temp = minMax[0];
|
||
minMax[0] = minMax[2];
|
||
minMax[2] = temp;
|
||
}
|
||
minMax[0] *= transform[0];
|
||
minMax[2] *= transform[0];
|
||
|
||
if (transform[3] < 0) {
|
||
temp = minMax[1];
|
||
minMax[1] = minMax[3];
|
||
minMax[3] = temp;
|
||
}
|
||
minMax[1] *= transform[3];
|
||
minMax[3] *= transform[3];
|
||
} else {
|
||
temp = minMax[0];
|
||
minMax[0] = minMax[1];
|
||
minMax[1] = temp;
|
||
temp = minMax[2];
|
||
minMax[2] = minMax[3];
|
||
minMax[3] = temp;
|
||
|
||
if (transform[1] < 0) {
|
||
temp = minMax[1];
|
||
minMax[1] = minMax[3];
|
||
minMax[3] = temp;
|
||
}
|
||
minMax[1] *= transform[1];
|
||
minMax[3] *= transform[1];
|
||
|
||
if (transform[2] < 0) {
|
||
temp = minMax[0];
|
||
minMax[0] = minMax[2];
|
||
minMax[2] = temp;
|
||
}
|
||
minMax[0] *= transform[2];
|
||
minMax[2] *= transform[2];
|
||
}
|
||
minMax[0] += transform[4];
|
||
minMax[1] += transform[5];
|
||
minMax[2] += transform[4];
|
||
minMax[3] += transform[5];
|
||
}
|
||
|
||
// Concatenates two transformation matrices together and returns the result.
|
||
static transform(m1, m2) {
|
||
return [
|
||
m1[0] * m2[0] + m1[2] * m2[1],
|
||
m1[1] * m2[0] + m1[3] * m2[1],
|
||
m1[0] * m2[2] + m1[2] * m2[3],
|
||
m1[1] * m2[2] + m1[3] * m2[3],
|
||
m1[0] * m2[4] + m1[2] * m2[5] + m1[4],
|
||
m1[1] * m2[4] + m1[3] * m2[5] + m1[5],
|
||
];
|
||
}
|
||
|
||
// For 2d affine transforms
|
||
static applyTransform(p, m) {
|
||
const xt = p[0] * m[0] + p[1] * m[2] + m[4];
|
||
const yt = p[0] * m[1] + p[1] * m[3] + m[5];
|
||
return [xt, yt];
|
||
}
|
||
|
||
static applyInverseTransform(p, m) {
|
||
const d = m[0] * m[3] - m[1] * m[2];
|
||
const xt = (p[0] * m[3] - p[1] * m[2] + m[2] * m[5] - m[4] * m[3]) / d;
|
||
const yt = (-p[0] * m[1] + p[1] * m[0] + m[4] * m[1] - m[5] * m[0]) / d;
|
||
return [xt, yt];
|
||
}
|
||
|
||
// Applies the transform to the rectangle and finds the minimum axially
|
||
// aligned bounding box.
|
||
static getAxialAlignedBoundingBox(r, m) {
|
||
const p1 = this.applyTransform(r, m);
|
||
const p2 = this.applyTransform(r.slice(2, 4), m);
|
||
const p3 = this.applyTransform([r[0], r[3]], m);
|
||
const p4 = this.applyTransform([r[2], r[1]], m);
|
||
return [
|
||
Math.min(p1[0], p2[0], p3[0], p4[0]),
|
||
Math.min(p1[1], p2[1], p3[1], p4[1]),
|
||
Math.max(p1[0], p2[0], p3[0], p4[0]),
|
||
Math.max(p1[1], p2[1], p3[1], p4[1]),
|
||
];
|
||
}
|
||
|
||
static inverseTransform(m) {
|
||
const d = m[0] * m[3] - m[1] * m[2];
|
||
return [
|
||
m[3] / d,
|
||
-m[1] / d,
|
||
-m[2] / d,
|
||
m[0] / d,
|
||
(m[2] * m[5] - m[4] * m[3]) / d,
|
||
(m[4] * m[1] - m[5] * m[0]) / d,
|
||
];
|
||
}
|
||
|
||
// This calculation uses Singular Value Decomposition.
|
||
// The SVD can be represented with formula A = USV. We are interested in the
|
||
// matrix S here because it represents the scale values.
|
||
static singularValueDecompose2dScale(m) {
|
||
const transpose = [m[0], m[2], m[1], m[3]];
|
||
|
||
// Multiply matrix m with its transpose.
|
||
const a = m[0] * transpose[0] + m[1] * transpose[2];
|
||
const b = m[0] * transpose[1] + m[1] * transpose[3];
|
||
const c = m[2] * transpose[0] + m[3] * transpose[2];
|
||
const d = m[2] * transpose[1] + m[3] * transpose[3];
|
||
|
||
// Solve the second degree polynomial to get roots.
|
||
const first = (a + d) / 2;
|
||
const second = Math.sqrt((a + d) ** 2 - 4 * (a * d - c * b)) / 2;
|
||
const sx = first + second || 1;
|
||
const sy = first - second || 1;
|
||
|
||
// Scale values are the square roots of the eigenvalues.
|
||
return [Math.sqrt(sx), Math.sqrt(sy)];
|
||
}
|
||
|
||
// Normalize rectangle rect=[x1, y1, x2, y2] so that (x1,y1) < (x2,y2)
|
||
// For coordinate systems whose origin lies in the bottom-left, this
|
||
// means normalization to (BL,TR) ordering. For systems with origin in the
|
||
// top-left, this means (TL,BR) ordering.
|
||
static normalizeRect(rect) {
|
||
const r = rect.slice(0); // clone rect
|
||
if (rect[0] > rect[2]) {
|
||
r[0] = rect[2];
|
||
r[2] = rect[0];
|
||
}
|
||
if (rect[1] > rect[3]) {
|
||
r[1] = rect[3];
|
||
r[3] = rect[1];
|
||
}
|
||
return r;
|
||
}
|
||
|
||
// Returns a rectangle [x1, y1, x2, y2] corresponding to the
|
||
// intersection of rect1 and rect2. If no intersection, returns 'null'
|
||
// The rectangle coordinates of rect1, rect2 should be [x1, y1, x2, y2]
|
||
static intersect(rect1, rect2) {
|
||
const xLow = Math.max(
|
||
Math.min(rect1[0], rect1[2]),
|
||
Math.min(rect2[0], rect2[2])
|
||
);
|
||
const xHigh = Math.min(
|
||
Math.max(rect1[0], rect1[2]),
|
||
Math.max(rect2[0], rect2[2])
|
||
);
|
||
if (xLow > xHigh) {
|
||
return null;
|
||
}
|
||
const yLow = Math.max(
|
||
Math.min(rect1[1], rect1[3]),
|
||
Math.min(rect2[1], rect2[3])
|
||
);
|
||
const yHigh = Math.min(
|
||
Math.max(rect1[1], rect1[3]),
|
||
Math.max(rect2[1], rect2[3])
|
||
);
|
||
if (yLow > yHigh) {
|
||
return null;
|
||
}
|
||
|
||
return [xLow, yLow, xHigh, yHigh];
|
||
}
|
||
|
||
static #getExtremumOnCurve(x0, x1, x2, x3, y0, y1, y2, y3, t, minMax) {
|
||
if (t <= 0 || t >= 1) {
|
||
return;
|
||
}
|
||
const mt = 1 - t;
|
||
const tt = t * t;
|
||
const ttt = tt * t;
|
||
const x = mt * (mt * (mt * x0 + 3 * t * x1) + 3 * tt * x2) + ttt * x3;
|
||
const y = mt * (mt * (mt * y0 + 3 * t * y1) + 3 * tt * y2) + ttt * y3;
|
||
minMax[0] = Math.min(minMax[0], x);
|
||
minMax[1] = Math.min(minMax[1], y);
|
||
minMax[2] = Math.max(minMax[2], x);
|
||
minMax[3] = Math.max(minMax[3], y);
|
||
}
|
||
|
||
static #getExtremum(x0, x1, x2, x3, y0, y1, y2, y3, a, b, c, minMax) {
|
||
if (Math.abs(a) < 1e-12) {
|
||
if (Math.abs(b) >= 1e-12) {
|
||
this.#getExtremumOnCurve(
|
||
x0,
|
||
x1,
|
||
x2,
|
||
x3,
|
||
y0,
|
||
y1,
|
||
y2,
|
||
y3,
|
||
-c / b,
|
||
minMax
|
||
);
|
||
}
|
||
return;
|
||
}
|
||
|
||
const delta = b ** 2 - 4 * c * a;
|
||
if (delta < 0) {
|
||
return;
|
||
}
|
||
const sqrtDelta = Math.sqrt(delta);
|
||
const a2 = 2 * a;
|
||
this.#getExtremumOnCurve(
|
||
x0,
|
||
x1,
|
||
x2,
|
||
x3,
|
||
y0,
|
||
y1,
|
||
y2,
|
||
y3,
|
||
(-b + sqrtDelta) / a2,
|
||
minMax
|
||
);
|
||
this.#getExtremumOnCurve(
|
||
x0,
|
||
x1,
|
||
x2,
|
||
x3,
|
||
y0,
|
||
y1,
|
||
y2,
|
||
y3,
|
||
(-b - sqrtDelta) / a2,
|
||
minMax
|
||
);
|
||
}
|
||
|
||
// From https://github.com/adobe-webplatform/Snap.svg/blob/b365287722a72526000ac4bfcf0ce4cac2faa015/src/path.js#L852
|
||
static bezierBoundingBox(x0, y0, x1, y1, x2, y2, x3, y3, minMax) {
|
||
if (minMax) {
|
||
minMax[0] = Math.min(minMax[0], x0, x3);
|
||
minMax[1] = Math.min(minMax[1], y0, y3);
|
||
minMax[2] = Math.max(minMax[2], x0, x3);
|
||
minMax[3] = Math.max(minMax[3], y0, y3);
|
||
} else {
|
||
minMax = [
|
||
Math.min(x0, x3),
|
||
Math.min(y0, y3),
|
||
Math.max(x0, x3),
|
||
Math.max(y0, y3),
|
||
];
|
||
}
|
||
this.#getExtremum(
|
||
x0,
|
||
x1,
|
||
x2,
|
||
x3,
|
||
y0,
|
||
y1,
|
||
y2,
|
||
y3,
|
||
3 * (-x0 + 3 * (x1 - x2) + x3),
|
||
6 * (x0 - 2 * x1 + x2),
|
||
3 * (x1 - x0),
|
||
minMax
|
||
);
|
||
this.#getExtremum(
|
||
x0,
|
||
x1,
|
||
x2,
|
||
x3,
|
||
y0,
|
||
y1,
|
||
y2,
|
||
y3,
|
||
3 * (-y0 + 3 * (y1 - y2) + y3),
|
||
6 * (y0 - 2 * y1 + y2),
|
||
3 * (y1 - y0),
|
||
minMax
|
||
);
|
||
return minMax;
|
||
}
|
||
}
|
||
|
||
const PDFStringTranslateTable = [
|
||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2d8,
|
||
0x2c7, 0x2c6, 0x2d9, 0x2dd, 0x2db, 0x2da, 0x2dc, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||
0, 0, 0, 0, 0, 0, 0, 0, 0x2022, 0x2020, 0x2021, 0x2026, 0x2014, 0x2013, 0x192,
|
||
0x2044, 0x2039, 0x203a, 0x2212, 0x2030, 0x201e, 0x201c, 0x201d, 0x2018,
|
||
0x2019, 0x201a, 0x2122, 0xfb01, 0xfb02, 0x141, 0x152, 0x160, 0x178, 0x17d,
|
||
0x131, 0x142, 0x153, 0x161, 0x17e, 0, 0x20ac,
|
||
];
|
||
|
||
function stringToPDFString(str) {
|
||
// See section 7.9.2.2 Text String Type.
|
||
// The string can contain some language codes bracketed with 0x0b,
|
||
// so we must remove them.
|
||
if (str[0] >= "\xEF") {
|
||
let encoding;
|
||
if (str[0] === "\xFE" && str[1] === "\xFF") {
|
||
encoding = "utf-16be";
|
||
if (str.length % 2 === 1) {
|
||
str = str.slice(0, -1);
|
||
}
|
||
} else if (str[0] === "\xFF" && str[1] === "\xFE") {
|
||
encoding = "utf-16le";
|
||
if (str.length % 2 === 1) {
|
||
str = str.slice(0, -1);
|
||
}
|
||
} else if (str[0] === "\xEF" && str[1] === "\xBB" && str[2] === "\xBF") {
|
||
encoding = "utf-8";
|
||
}
|
||
|
||
if (encoding) {
|
||
try {
|
||
const decoder = new TextDecoder(encoding, { fatal: true });
|
||
const buffer = stringToBytes(str);
|
||
const decoded = decoder.decode(buffer);
|
||
if (!decoded.includes("\x1b")) {
|
||
return decoded;
|
||
}
|
||
return decoded.replaceAll(/\x1b[^\x1b]*(?:\x1b|$)/g, "");
|
||
} catch (ex) {
|
||
warn(`stringToPDFString: "${ex}".`);
|
||
}
|
||
}
|
||
}
|
||
// ISO Latin 1
|
||
const strBuf = [];
|
||
for (let i = 0, ii = str.length; i < ii; i++) {
|
||
const charCode = str.charCodeAt(i);
|
||
if (charCode === 0x1b) {
|
||
// eslint-disable-next-line no-empty
|
||
while (++i < ii && str.charCodeAt(i) !== 0x1b) {}
|
||
continue;
|
||
}
|
||
const code = PDFStringTranslateTable[charCode];
|
||
strBuf.push(code ? String.fromCharCode(code) : str.charAt(i));
|
||
}
|
||
return strBuf.join("");
|
||
}
|
||
|
||
function stringToUTF8String(str) {
|
||
return decodeURIComponent(escape(str));
|
||
}
|
||
|
||
function utf8StringToString(str) {
|
||
return unescape(encodeURIComponent(str));
|
||
}
|
||
|
||
function isArrayEqual(arr1, arr2) {
|
||
if (arr1.length !== arr2.length) {
|
||
return false;
|
||
}
|
||
for (let i = 0, ii = arr1.length; i < ii; i++) {
|
||
if (arr1[i] !== arr2[i]) {
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
function getModificationDate(date = new Date()) {
|
||
const buffer = [
|
||
date.getUTCFullYear().toString(),
|
||
(date.getUTCMonth() + 1).toString().padStart(2, "0"),
|
||
date.getUTCDate().toString().padStart(2, "0"),
|
||
date.getUTCHours().toString().padStart(2, "0"),
|
||
date.getUTCMinutes().toString().padStart(2, "0"),
|
||
date.getUTCSeconds().toString().padStart(2, "0"),
|
||
];
|
||
|
||
return buffer.join("");
|
||
}
|
||
|
||
let NormalizeRegex = null;
|
||
let NormalizationMap = null;
|
||
function normalizeUnicode(str) {
|
||
if (!NormalizeRegex) {
|
||
// In order to generate the following regex:
|
||
// - create a PDF containing all the chars in the range 0000-FFFF with
|
||
// a NFKC which is different of the char.
|
||
// - copy and paste all those chars and get the ones where NFKC is
|
||
// required.
|
||
// It appears that most the chars here contain some ligatures.
|
||
NormalizeRegex =
|
||
/([\u00a0\u00b5\u037e\u0eb3\u2000-\u200a\u202f\u2126\ufb00-\ufb04\ufb06\ufb20-\ufb36\ufb38-\ufb3c\ufb3e\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufba1\ufba4-\ufba9\ufbae-\ufbb1\ufbd3-\ufbdc\ufbde-\ufbe7\ufbea-\ufbf8\ufbfc-\ufbfd\ufc00-\ufc5d\ufc64-\ufcf1\ufcf5-\ufd3d\ufd88\ufdf4\ufdfa-\ufdfb\ufe71\ufe77\ufe79\ufe7b\ufe7d]+)|(\ufb05+)/gu;
|
||
NormalizationMap = new Map([["ſt", "ſt"]]);
|
||
}
|
||
return str.replaceAll(NormalizeRegex, (_, p1, p2) =>
|
||
p1 ? p1.normalize("NFKC") : NormalizationMap.get(p2)
|
||
);
|
||
}
|
||
|
||
function getUuid() {
|
||
if (
|
||
(typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) ||
|
||
typeof crypto.randomUUID === "function"
|
||
) {
|
||
return crypto.randomUUID();
|
||
}
|
||
const buf = new Uint8Array(32);
|
||
crypto.getRandomValues(buf);
|
||
return bytesToString(buf);
|
||
}
|
||
|
||
const AnnotationPrefix = "pdfjs_internal_id_";
|
||
|
||
function _isValidExplicitDest(validRef, validName, dest) {
|
||
if (!Array.isArray(dest) || dest.length < 2) {
|
||
return false;
|
||
}
|
||
const [page, zoom, ...args] = dest;
|
||
if (!validRef(page) && !Number.isInteger(page)) {
|
||
return false;
|
||
}
|
||
if (!validName(zoom)) {
|
||
return false;
|
||
}
|
||
const argsLen = args.length;
|
||
let allowNull = true;
|
||
switch (zoom.name) {
|
||
case "XYZ":
|
||
if (argsLen < 2 || argsLen > 3) {
|
||
return false;
|
||
}
|
||
break;
|
||
case "Fit":
|
||
case "FitB":
|
||
return argsLen === 0;
|
||
case "FitH":
|
||
case "FitBH":
|
||
case "FitV":
|
||
case "FitBV":
|
||
if (argsLen > 1) {
|
||
return false;
|
||
}
|
||
break;
|
||
case "FitR":
|
||
if (argsLen !== 4) {
|
||
return false;
|
||
}
|
||
allowNull = false;
|
||
break;
|
||
default:
|
||
return false;
|
||
}
|
||
for (const arg of args) {
|
||
if (typeof arg === "number" || (allowNull && arg === null)) {
|
||
continue;
|
||
}
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
// TODO: Remove this once `Uint8Array.prototype.toHex` is generally available.
|
||
function toHexUtil(arr) {
|
||
if (Uint8Array.prototype.toHex) {
|
||
return arr.toHex();
|
||
}
|
||
return Array.from(arr, num => hexNumbers[num]).join("");
|
||
}
|
||
|
||
// TODO: Remove this once `Uint8Array.prototype.toBase64` is generally
|
||
// available.
|
||
function toBase64Util(arr) {
|
||
if (Uint8Array.prototype.toBase64) {
|
||
return arr.toBase64();
|
||
}
|
||
return btoa(bytesToString(arr));
|
||
}
|
||
|
||
// TODO: Remove this once `Uint8Array.fromBase64` is generally available.
|
||
function fromBase64Util(str) {
|
||
if (Uint8Array.fromBase64) {
|
||
return Uint8Array.fromBase64(str);
|
||
}
|
||
return stringToBytes(atob(str));
|
||
}
|
||
|
||
// TODO: Remove this once https://bugzilla.mozilla.org/show_bug.cgi?id=1928493
|
||
// is fixed.
|
||
if (
|
||
(typeof PDFJSDev === "undefined" || PDFJSDev.test("SKIP_BABEL")) &&
|
||
typeof Promise.try !== "function"
|
||
) {
|
||
Promise.try = function (fn, ...args) {
|
||
return new Promise(resolve => {
|
||
resolve(fn(...args));
|
||
});
|
||
};
|
||
}
|
||
|
||
export {
|
||
_isValidExplicitDest,
|
||
AbortException,
|
||
AnnotationActionEventType,
|
||
AnnotationBorderStyleType,
|
||
AnnotationEditorParamsType,
|
||
AnnotationEditorPrefix,
|
||
AnnotationEditorType,
|
||
AnnotationFieldFlag,
|
||
AnnotationFlag,
|
||
AnnotationMode,
|
||
AnnotationPrefix,
|
||
AnnotationReplyType,
|
||
AnnotationType,
|
||
assert,
|
||
BaseException,
|
||
BASELINE_FACTOR,
|
||
bytesToString,
|
||
createValidAbsoluteUrl,
|
||
DocumentActionEventType,
|
||
FeatureTest,
|
||
FONT_IDENTITY_MATRIX,
|
||
FormatError,
|
||
fromBase64Util,
|
||
getModificationDate,
|
||
getUuid,
|
||
getVerbosityLevel,
|
||
hexNumbers,
|
||
IDENTITY_MATRIX,
|
||
ImageKind,
|
||
info,
|
||
InvalidPDFException,
|
||
isArrayEqual,
|
||
isNodeJS,
|
||
LINE_DESCENT_FACTOR,
|
||
LINE_FACTOR,
|
||
normalizeUnicode,
|
||
objectFromMap,
|
||
objectSize,
|
||
OPS,
|
||
PageActionEventType,
|
||
PasswordException,
|
||
PasswordResponses,
|
||
PermissionFlag,
|
||
RenderingIntentFlag,
|
||
ResponseException,
|
||
setVerbosityLevel,
|
||
shadow,
|
||
string32,
|
||
stringToBytes,
|
||
stringToPDFString,
|
||
stringToUTF8String,
|
||
TextRenderingMode,
|
||
toBase64Util,
|
||
toHexUtil,
|
||
UnknownErrorException,
|
||
unreachable,
|
||
utf8StringToString,
|
||
Util,
|
||
VerbosityLevel,
|
||
warn,
|
||
};
|