Note that Prettier, purposely, has only limited [configuration options](https://prettier.io/docs/en/options.html). The configuration file is based on [the one in `mozilla central`](https://searchfox.org/mozilla-central/source/.prettierrc) with just a few additions (to avoid future breakage if the defaults ever changes). Prettier is being used for a couple of reasons: - To be consistent with `mozilla-central`, where Prettier is already in use across the tree. - To ensure a *consistent* coding style everywhere, which is automatically enforced during linting (since Prettier is used as an ESLint plugin). This thus ends "all" formatting disussions once and for all, removing the need for review comments on most stylistic matters. Many ESLint options are now redundant, and I've tried my best to remove all the now unnecessary options (but I may have missed some). Note also that since Prettier considers the `printWidth` option as a guide, rather than a hard rule, this patch resorts to a small hack in the ESLint config to ensure that *comments* won't become too long. *Please note:* This patch is generated automatically, by appending the `--fix` argument to the ESLint call used in the `gulp lint` task. It will thus require some additional clean-up, which will be done in a *separate* commit. (On a more personal note, I'll readily admit that some of the changes Prettier makes are *extremely* ugly. However, in the name of consistency we'll probably have to live with that.)
634 lines
19 KiB
JavaScript
634 lines
19 KiB
JavaScript
/* Copyright 2015 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.
|
|
*/
|
|
/* eslint no-var: error */
|
|
|
|
import {
|
|
assert,
|
|
BaseException,
|
|
CMapCompressionType,
|
|
isString,
|
|
removeNullCharacters,
|
|
stringToBytes,
|
|
Util,
|
|
warn,
|
|
} from "../shared/util";
|
|
|
|
const DEFAULT_LINK_REL = "noopener noreferrer nofollow";
|
|
const SVG_NS = "http://www.w3.org/2000/svg";
|
|
|
|
class DOMCanvasFactory {
|
|
create(width, height) {
|
|
if (width <= 0 || height <= 0) {
|
|
throw new Error("Invalid canvas size");
|
|
}
|
|
const canvas = document.createElement("canvas");
|
|
const context = canvas.getContext("2d");
|
|
canvas.width = width;
|
|
canvas.height = height;
|
|
return {
|
|
canvas,
|
|
context,
|
|
};
|
|
}
|
|
|
|
reset(canvasAndContext, width, height) {
|
|
if (!canvasAndContext.canvas) {
|
|
throw new Error("Canvas is not specified");
|
|
}
|
|
if (width <= 0 || height <= 0) {
|
|
throw new Error("Invalid canvas size");
|
|
}
|
|
canvasAndContext.canvas.width = width;
|
|
canvasAndContext.canvas.height = height;
|
|
}
|
|
|
|
destroy(canvasAndContext) {
|
|
if (!canvasAndContext.canvas) {
|
|
throw new Error("Canvas is not specified");
|
|
}
|
|
// Zeroing the width and height cause Firefox to release graphics
|
|
// resources immediately, which can greatly reduce memory consumption.
|
|
canvasAndContext.canvas.width = 0;
|
|
canvasAndContext.canvas.height = 0;
|
|
canvasAndContext.canvas = null;
|
|
canvasAndContext.context = null;
|
|
}
|
|
}
|
|
|
|
class DOMCMapReaderFactory {
|
|
constructor({ baseUrl = null, isCompressed = false }) {
|
|
this.baseUrl = baseUrl;
|
|
this.isCompressed = isCompressed;
|
|
}
|
|
|
|
async fetch({ name }) {
|
|
if (!this.baseUrl) {
|
|
throw new Error(
|
|
'The CMap "baseUrl" parameter must be specified, ensure that ' +
|
|
'the "cMapUrl" and "cMapPacked" API parameters are provided.'
|
|
);
|
|
}
|
|
if (!name) {
|
|
throw new Error("CMap name must be specified.");
|
|
}
|
|
const url = this.baseUrl + name + (this.isCompressed ? ".bcmap" : "");
|
|
const compressionType = this.isCompressed
|
|
? CMapCompressionType.BINARY
|
|
: CMapCompressionType.NONE;
|
|
|
|
if (
|
|
(typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) ||
|
|
(isFetchSupported() && isValidFetchUrl(url, document.baseURI))
|
|
) {
|
|
return fetch(url)
|
|
.then(async response => {
|
|
if (!response.ok) {
|
|
throw new Error(response.statusText);
|
|
}
|
|
let cMapData;
|
|
if (this.isCompressed) {
|
|
cMapData = new Uint8Array(await response.arrayBuffer());
|
|
} else {
|
|
cMapData = stringToBytes(await response.text());
|
|
}
|
|
return { cMapData, compressionType };
|
|
})
|
|
.catch(reason => {
|
|
throw new Error(
|
|
`Unable to load ${this.isCompressed ? "binary " : ""}` +
|
|
`CMap at: ${url}`
|
|
);
|
|
});
|
|
}
|
|
|
|
// The Fetch API is not supported.
|
|
return new Promise((resolve, reject) => {
|
|
const request = new XMLHttpRequest();
|
|
request.open("GET", url, true);
|
|
|
|
if (this.isCompressed) {
|
|
request.responseType = "arraybuffer";
|
|
}
|
|
request.onreadystatechange = () => {
|
|
if (request.readyState !== XMLHttpRequest.DONE) {
|
|
return;
|
|
}
|
|
if (request.status === 200 || request.status === 0) {
|
|
let cMapData;
|
|
if (this.isCompressed && request.response) {
|
|
cMapData = new Uint8Array(request.response);
|
|
} else if (!this.isCompressed && request.responseText) {
|
|
cMapData = stringToBytes(request.responseText);
|
|
}
|
|
if (cMapData) {
|
|
resolve({ cMapData, compressionType });
|
|
return;
|
|
}
|
|
}
|
|
reject(new Error(request.statusText));
|
|
};
|
|
|
|
request.send(null);
|
|
}).catch(reason => {
|
|
throw new Error(
|
|
`Unable to load ${this.isCompressed ? "binary " : ""}` +
|
|
`CMap at: ${url}`
|
|
);
|
|
});
|
|
}
|
|
}
|
|
|
|
class DOMSVGFactory {
|
|
create(width, height) {
|
|
assert(width > 0 && height > 0, "Invalid SVG dimensions");
|
|
|
|
const svg = document.createElementNS(SVG_NS, "svg:svg");
|
|
svg.setAttribute("version", "1.1");
|
|
svg.setAttribute("width", width + "px");
|
|
svg.setAttribute("height", height + "px");
|
|
svg.setAttribute("preserveAspectRatio", "none");
|
|
svg.setAttribute("viewBox", "0 0 " + width + " " + height);
|
|
|
|
return svg;
|
|
}
|
|
|
|
createElement(type) {
|
|
assert(typeof type === "string", "Invalid SVG element type");
|
|
|
|
return document.createElementNS(SVG_NS, type);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @typedef {Object} PageViewportParameters
|
|
* @property {Array} viewBox - The xMin, yMin, xMax and yMax coordinates.
|
|
* @property {number} scale - The scale of the viewport.
|
|
* @property {number} rotation - The rotation, in degrees, of the viewport.
|
|
* @property {number} [offsetX] - The horizontal, i.e. x-axis, offset. The
|
|
* default value is `0`.
|
|
* @property {number} [offsetY] - The vertical, i.e. y-axis, offset. The
|
|
* default value is `0`.
|
|
* @property {boolean} [dontFlip] - If true, the y-axis will not be flipped.
|
|
* The default value is `false`.
|
|
*/
|
|
|
|
/**
|
|
* @typedef {Object} PageViewportCloneParameters
|
|
* @property {number} [scale] - The scale, overriding the one in the cloned
|
|
* viewport. The default value is `this.scale`.
|
|
* @property {number} [rotation] - The rotation, in degrees, overriding the one
|
|
* in the cloned viewport. The default value is `this.rotation`.
|
|
* @property {number} [offsetX] - The horizontal, i.e. x-axis, offset.
|
|
* The default value is `this.offsetX`.
|
|
* @property {number} [offsetY] - The vertical, i.e. y-axis, offset.
|
|
* The default value is `this.offsetY`.
|
|
* @property {boolean} [dontFlip] - If true, the x-axis will not be flipped.
|
|
* The default value is `false`.
|
|
*/
|
|
|
|
/**
|
|
* PDF page viewport created based on scale, rotation and offset.
|
|
*/
|
|
class PageViewport {
|
|
/**
|
|
* @param {PageViewportParameters}
|
|
*/
|
|
constructor({
|
|
viewBox,
|
|
scale,
|
|
rotation,
|
|
offsetX = 0,
|
|
offsetY = 0,
|
|
dontFlip = false,
|
|
}) {
|
|
this.viewBox = viewBox;
|
|
this.scale = scale;
|
|
this.rotation = rotation;
|
|
this.offsetX = offsetX;
|
|
this.offsetY = offsetY;
|
|
|
|
// creating transform to convert pdf coordinate system to the normal
|
|
// canvas like coordinates taking in account scale and rotation
|
|
const centerX = (viewBox[2] + viewBox[0]) / 2;
|
|
const centerY = (viewBox[3] + viewBox[1]) / 2;
|
|
let rotateA, rotateB, rotateC, rotateD;
|
|
rotation = rotation % 360;
|
|
rotation = rotation < 0 ? rotation + 360 : rotation;
|
|
switch (rotation) {
|
|
case 180:
|
|
rotateA = -1;
|
|
rotateB = 0;
|
|
rotateC = 0;
|
|
rotateD = 1;
|
|
break;
|
|
case 90:
|
|
rotateA = 0;
|
|
rotateB = 1;
|
|
rotateC = 1;
|
|
rotateD = 0;
|
|
break;
|
|
case 270:
|
|
rotateA = 0;
|
|
rotateB = -1;
|
|
rotateC = -1;
|
|
rotateD = 0;
|
|
break;
|
|
// case 0:
|
|
default:
|
|
rotateA = 1;
|
|
rotateB = 0;
|
|
rotateC = 0;
|
|
rotateD = -1;
|
|
break;
|
|
}
|
|
|
|
if (dontFlip) {
|
|
rotateC = -rotateC;
|
|
rotateD = -rotateD;
|
|
}
|
|
|
|
let offsetCanvasX, offsetCanvasY;
|
|
let width, height;
|
|
if (rotateA === 0) {
|
|
offsetCanvasX = Math.abs(centerY - viewBox[1]) * scale + offsetX;
|
|
offsetCanvasY = Math.abs(centerX - viewBox[0]) * scale + offsetY;
|
|
width = Math.abs(viewBox[3] - viewBox[1]) * scale;
|
|
height = Math.abs(viewBox[2] - viewBox[0]) * scale;
|
|
} else {
|
|
offsetCanvasX = Math.abs(centerX - viewBox[0]) * scale + offsetX;
|
|
offsetCanvasY = Math.abs(centerY - viewBox[1]) * scale + offsetY;
|
|
width = Math.abs(viewBox[2] - viewBox[0]) * scale;
|
|
height = Math.abs(viewBox[3] - viewBox[1]) * scale;
|
|
}
|
|
// creating transform for the following operations:
|
|
// translate(-centerX, -centerY), rotate and flip vertically,
|
|
// scale, and translate(offsetCanvasX, offsetCanvasY)
|
|
this.transform = [
|
|
rotateA * scale,
|
|
rotateB * scale,
|
|
rotateC * scale,
|
|
rotateD * scale,
|
|
offsetCanvasX - rotateA * scale * centerX - rotateC * scale * centerY,
|
|
offsetCanvasY - rotateB * scale * centerX - rotateD * scale * centerY,
|
|
];
|
|
|
|
this.width = width;
|
|
this.height = height;
|
|
}
|
|
|
|
/**
|
|
* Clones viewport, with optional additional properties.
|
|
* @param {PageViewportCloneParameters} [params]
|
|
* @returns {PageViewport} Cloned viewport.
|
|
*/
|
|
clone({
|
|
scale = this.scale,
|
|
rotation = this.rotation,
|
|
offsetX = this.offsetX,
|
|
offsetY = this.offsetY,
|
|
dontFlip = false,
|
|
} = {}) {
|
|
return new PageViewport({
|
|
viewBox: this.viewBox.slice(),
|
|
scale,
|
|
rotation,
|
|
offsetX,
|
|
offsetY,
|
|
dontFlip,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Converts PDF point to the viewport coordinates. For examples, useful for
|
|
* converting PDF location into canvas pixel coordinates.
|
|
* @param {number} x - The x-coordinate.
|
|
* @param {number} y - The y-coordinate.
|
|
* @returns {Object} Object containing `x` and `y` properties of the
|
|
* point in the viewport coordinate space.
|
|
* @see {@link convertToPdfPoint}
|
|
* @see {@link convertToViewportRectangle}
|
|
*/
|
|
convertToViewportPoint(x, y) {
|
|
return Util.applyTransform([x, y], this.transform);
|
|
}
|
|
|
|
/**
|
|
* Converts PDF rectangle to the viewport coordinates.
|
|
* @param {Array} rect - The xMin, yMin, xMax and yMax coordinates.
|
|
* @returns {Array} Array containing corresponding coordinates of the
|
|
* rectangle in the viewport coordinate space.
|
|
* @see {@link convertToViewportPoint}
|
|
*/
|
|
convertToViewportRectangle(rect) {
|
|
const topLeft = Util.applyTransform([rect[0], rect[1]], this.transform);
|
|
const bottomRight = Util.applyTransform([rect[2], rect[3]], this.transform);
|
|
return [topLeft[0], topLeft[1], bottomRight[0], bottomRight[1]];
|
|
}
|
|
|
|
/**
|
|
* Converts viewport coordinates to the PDF location. For examples, useful
|
|
* for converting canvas pixel location into PDF one.
|
|
* @param {number} x - The x-coordinate.
|
|
* @param {number} y - The y-coordinate.
|
|
* @returns {Object} Object containing `x` and `y` properties of the
|
|
* point in the PDF coordinate space.
|
|
* @see {@link convertToViewportPoint}
|
|
*/
|
|
convertToPdfPoint(x, y) {
|
|
return Util.applyInverseTransform([x, y], this.transform);
|
|
}
|
|
}
|
|
|
|
class RenderingCancelledException extends BaseException {
|
|
constructor(msg, type) {
|
|
super(msg);
|
|
this.type = type;
|
|
}
|
|
}
|
|
|
|
const LinkTarget = {
|
|
NONE: 0, // Default value.
|
|
SELF: 1,
|
|
BLANK: 2,
|
|
PARENT: 3,
|
|
TOP: 4,
|
|
};
|
|
|
|
/**
|
|
* @typedef ExternalLinkParameters
|
|
* @typedef {Object} ExternalLinkParameters
|
|
* @property {string} url - An absolute URL.
|
|
* @property {LinkTarget} [target] - The link target. The default value is
|
|
* `LinkTarget.NONE`.
|
|
* @property {string} [rel] - The link relationship. The default value is
|
|
* `DEFAULT_LINK_REL`.
|
|
* @property {boolean} [enabled] - Whether the link should be enabled. The
|
|
* default value is true.
|
|
*/
|
|
|
|
/**
|
|
* Adds various attributes (href, title, target, rel) to hyperlinks.
|
|
* @param {HTMLLinkElement} link - The link element.
|
|
* @param {ExternalLinkParameters} params
|
|
*/
|
|
function addLinkAttributes(link, { url, target, rel, enabled = true } = {}) {
|
|
assert(
|
|
url && typeof url === "string",
|
|
'addLinkAttributes: A valid "url" parameter must provided.'
|
|
);
|
|
|
|
const urlNullRemoved = removeNullCharacters(url);
|
|
if (enabled) {
|
|
link.href = link.title = urlNullRemoved;
|
|
} else {
|
|
link.href = "";
|
|
link.title = `Disabled: ${urlNullRemoved}`;
|
|
link.onclick = () => {
|
|
return false;
|
|
};
|
|
}
|
|
|
|
let targetStr = ""; // LinkTarget.NONE
|
|
switch (target) {
|
|
case LinkTarget.NONE:
|
|
break;
|
|
case LinkTarget.SELF:
|
|
targetStr = "_self";
|
|
break;
|
|
case LinkTarget.BLANK:
|
|
targetStr = "_blank";
|
|
break;
|
|
case LinkTarget.PARENT:
|
|
targetStr = "_parent";
|
|
break;
|
|
case LinkTarget.TOP:
|
|
targetStr = "_top";
|
|
break;
|
|
}
|
|
link.target = targetStr;
|
|
|
|
link.rel = typeof rel === "string" ? rel : DEFAULT_LINK_REL;
|
|
}
|
|
|
|
// Gets the file name from a given URL.
|
|
function getFilenameFromUrl(url) {
|
|
const anchor = url.indexOf("#");
|
|
const query = url.indexOf("?");
|
|
const end = Math.min(
|
|
anchor > 0 ? anchor : url.length,
|
|
query > 0 ? query : url.length
|
|
);
|
|
return url.substring(url.lastIndexOf("/", end) + 1, end);
|
|
}
|
|
|
|
class StatTimer {
|
|
constructor() {
|
|
this.started = Object.create(null);
|
|
this.times = [];
|
|
}
|
|
|
|
time(name) {
|
|
if (name in this.started) {
|
|
warn(`Timer is already running for ${name}`);
|
|
}
|
|
this.started[name] = Date.now();
|
|
}
|
|
|
|
timeEnd(name) {
|
|
if (!(name in this.started)) {
|
|
warn(`Timer has not been started for ${name}`);
|
|
}
|
|
this.times.push({
|
|
name,
|
|
start: this.started[name],
|
|
end: Date.now(),
|
|
});
|
|
// Remove timer from started so it can be called again.
|
|
delete this.started[name];
|
|
}
|
|
|
|
toString() {
|
|
// Find the longest name for padding purposes.
|
|
let outBuf = [],
|
|
longest = 0;
|
|
for (const time of this.times) {
|
|
const name = time.name;
|
|
if (name.length > longest) {
|
|
longest = name.length;
|
|
}
|
|
}
|
|
for (const time of this.times) {
|
|
const duration = time.end - time.start;
|
|
outBuf.push(`${time.name.padEnd(longest)} ${duration}ms\n`);
|
|
}
|
|
return outBuf.join("");
|
|
}
|
|
}
|
|
|
|
function isFetchSupported() {
|
|
return (
|
|
typeof fetch !== "undefined" &&
|
|
typeof Response !== "undefined" &&
|
|
"body" in Response.prototype &&
|
|
typeof ReadableStream !== "undefined"
|
|
);
|
|
}
|
|
|
|
function isValidFetchUrl(url, baseUrl) {
|
|
try {
|
|
const { protocol } = baseUrl ? new URL(url, baseUrl) : new URL(url);
|
|
// The Fetch API only supports the http/https protocols, and not file/ftp.
|
|
return protocol === "http:" || protocol === "https:";
|
|
} catch (ex) {
|
|
return false; // `new URL()` will throw on incorrect data.
|
|
}
|
|
}
|
|
|
|
function loadScript(src) {
|
|
return new Promise((resolve, reject) => {
|
|
const script = document.createElement("script");
|
|
script.src = src;
|
|
|
|
script.onload = resolve;
|
|
script.onerror = function() {
|
|
reject(new Error(`Cannot load script at: ${script.src}`));
|
|
};
|
|
(document.head || document.documentElement).appendChild(script);
|
|
});
|
|
}
|
|
|
|
// Deprecated API function -- display regardless of the `verbosity` setting.
|
|
function deprecated(details) {
|
|
console.log("Deprecated API usage: " + details);
|
|
}
|
|
|
|
function releaseImageResources(img) {
|
|
assert(img instanceof Image, "Invalid `img` parameter.");
|
|
|
|
const url = img.src;
|
|
if (
|
|
typeof url === "string" &&
|
|
url.startsWith("blob:") &&
|
|
URL.revokeObjectURL
|
|
) {
|
|
URL.revokeObjectURL(url);
|
|
}
|
|
img.removeAttribute("src");
|
|
}
|
|
|
|
let pdfDateStringRegex;
|
|
|
|
class PDFDateString {
|
|
/**
|
|
* Convert a PDF date string to a JavaScript `Date` object.
|
|
*
|
|
* The PDF date string format is described in section 7.9.4 of the official
|
|
* PDF 32000-1:2008 specification. However, in the PDF 1.7 reference (sixth
|
|
* edition) Adobe describes the same format including a trailing apostrophe.
|
|
* This syntax in incorrect, but Adobe Acrobat creates PDF files that contain
|
|
* them. We ignore all apostrophes as they are not necessary for date parsing.
|
|
*
|
|
* Moreover, Adobe Acrobat doesn't handle changing the date to universal time
|
|
* and doesn't use the user's time zone (effectively ignoring the HH' and mm'
|
|
* parts of the date string).
|
|
*
|
|
* @param {string} input
|
|
* @returns {Date|null}
|
|
*/
|
|
static toDateObject(input) {
|
|
if (!input || !isString(input)) {
|
|
return null;
|
|
}
|
|
|
|
// Lazily initialize the regular expression.
|
|
if (!pdfDateStringRegex) {
|
|
pdfDateStringRegex = new RegExp(
|
|
"^D:" + // Prefix (required)
|
|
"(\\d{4})" + // Year (required)
|
|
"(\\d{2})?" + // Month (optional)
|
|
"(\\d{2})?" + // Day (optional)
|
|
"(\\d{2})?" + // Hour (optional)
|
|
"(\\d{2})?" + // Minute (optional)
|
|
"(\\d{2})?" + // Second (optional)
|
|
"([Z|+|-])?" + // Universal time relation (optional)
|
|
"(\\d{2})?" + // Offset hour (optional)
|
|
"'?" + // Splitting apostrophe (optional)
|
|
"(\\d{2})?" + // Offset minute (optional)
|
|
"'?" // Trailing apostrophe (optional)
|
|
);
|
|
}
|
|
|
|
// Optional fields that don't satisfy the requirements from the regular
|
|
// expression (such as incorrect digit counts or numbers that are out of
|
|
// range) will fall back the defaults from the specification.
|
|
const matches = pdfDateStringRegex.exec(input);
|
|
if (!matches) {
|
|
return null;
|
|
}
|
|
|
|
// JavaScript's `Date` object expects the month to be between 0 and 11
|
|
// instead of 1 and 12, so we have to correct for that.
|
|
const year = parseInt(matches[1], 10);
|
|
let month = parseInt(matches[2], 10);
|
|
month = month >= 1 && month <= 12 ? month - 1 : 0;
|
|
let day = parseInt(matches[3], 10);
|
|
day = day >= 1 && day <= 31 ? day : 1;
|
|
let hour = parseInt(matches[4], 10);
|
|
hour = hour >= 0 && hour <= 23 ? hour : 0;
|
|
let minute = parseInt(matches[5], 10);
|
|
minute = minute >= 0 && minute <= 59 ? minute : 0;
|
|
let second = parseInt(matches[6], 10);
|
|
second = second >= 0 && second <= 59 ? second : 0;
|
|
const universalTimeRelation = matches[7] || "Z";
|
|
let offsetHour = parseInt(matches[8], 10);
|
|
offsetHour = offsetHour >= 0 && offsetHour <= 23 ? offsetHour : 0;
|
|
let offsetMinute = parseInt(matches[9], 10) || 0;
|
|
offsetMinute = offsetMinute >= 0 && offsetMinute <= 59 ? offsetMinute : 0;
|
|
|
|
// Universal time relation 'Z' means that the local time is equal to the
|
|
// universal time, whereas the relations '+'/'-' indicate that the local
|
|
// time is later respectively earlier than the universal time. Every date
|
|
// is normalized to universal time.
|
|
if (universalTimeRelation === "-") {
|
|
hour += offsetHour;
|
|
minute += offsetMinute;
|
|
} else if (universalTimeRelation === "+") {
|
|
hour -= offsetHour;
|
|
minute -= offsetMinute;
|
|
}
|
|
|
|
return new Date(Date.UTC(year, month, day, hour, minute, second));
|
|
}
|
|
}
|
|
|
|
export {
|
|
PageViewport,
|
|
RenderingCancelledException,
|
|
addLinkAttributes,
|
|
getFilenameFromUrl,
|
|
LinkTarget,
|
|
DEFAULT_LINK_REL,
|
|
DOMCanvasFactory,
|
|
DOMCMapReaderFactory,
|
|
DOMSVGFactory,
|
|
StatTimer,
|
|
isFetchSupported,
|
|
isValidFetchUrl,
|
|
loadScript,
|
|
deprecated,
|
|
releaseImageResources,
|
|
PDFDateString,
|
|
};
|