Merge pull request #18988 from Snuffleupagus/split-dom-factory
Move the various DOM-factories into their own files
This commit is contained in:
commit
3634dab10c
12
gulpfile.mjs
12
gulpfile.mjs
@ -191,6 +191,8 @@ function createWebpackAlias(defines) {
|
|||||||
"fluent-dom": "node_modules/@fluent/dom/esm/index.js",
|
"fluent-dom": "node_modules/@fluent/dom/esm/index.js",
|
||||||
};
|
};
|
||||||
const libraryAlias = {
|
const libraryAlias = {
|
||||||
|
"display-cmap_reader_factory": "src/display/stubs.js",
|
||||||
|
"display-standard_fontdata_factory": "src/display/stubs.js",
|
||||||
"display-fetch_stream": "src/display/stubs.js",
|
"display-fetch_stream": "src/display/stubs.js",
|
||||||
"display-network": "src/display/stubs.js",
|
"display-network": "src/display/stubs.js",
|
||||||
"display-node_stream": "src/display/stubs.js",
|
"display-node_stream": "src/display/stubs.js",
|
||||||
@ -219,6 +221,10 @@ function createWebpackAlias(defines) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (defines.CHROME) {
|
if (defines.CHROME) {
|
||||||
|
libraryAlias["display-cmap_reader_factory"] =
|
||||||
|
"src/display/cmap_reader_factory.js";
|
||||||
|
libraryAlias["display-standard_fontdata_factory"] =
|
||||||
|
"src/display/standard_fontdata_factory.js";
|
||||||
libraryAlias["display-fetch_stream"] = "src/display/fetch_stream.js";
|
libraryAlias["display-fetch_stream"] = "src/display/fetch_stream.js";
|
||||||
libraryAlias["display-network"] = "src/display/network.js";
|
libraryAlias["display-network"] = "src/display/network.js";
|
||||||
|
|
||||||
@ -231,6 +237,10 @@ function createWebpackAlias(defines) {
|
|||||||
// Aliases defined here must also be replicated in the paths section of
|
// Aliases defined here must also be replicated in the paths section of
|
||||||
// the tsconfig.json file for the type generation to work.
|
// the tsconfig.json file for the type generation to work.
|
||||||
// In the tsconfig.json files, the .js extension must be omitted.
|
// In the tsconfig.json files, the .js extension must be omitted.
|
||||||
|
libraryAlias["display-cmap_reader_factory"] =
|
||||||
|
"src/display/cmap_reader_factory.js";
|
||||||
|
libraryAlias["display-standard_fontdata_factory"] =
|
||||||
|
"src/display/standard_fontdata_factory.js";
|
||||||
libraryAlias["display-fetch_stream"] = "src/display/fetch_stream.js";
|
libraryAlias["display-fetch_stream"] = "src/display/fetch_stream.js";
|
||||||
libraryAlias["display-network"] = "src/display/network.js";
|
libraryAlias["display-network"] = "src/display/network.js";
|
||||||
libraryAlias["display-node_stream"] = "src/display/node_stream.js";
|
libraryAlias["display-node_stream"] = "src/display/node_stream.js";
|
||||||
@ -1573,6 +1583,8 @@ function buildLibHelper(bundleDefines, inputStream, outputDir) {
|
|||||||
defines: bundleDefines,
|
defines: bundleDefines,
|
||||||
map: {
|
map: {
|
||||||
"pdfjs-lib": "../pdf.js",
|
"pdfjs-lib": "../pdf.js",
|
||||||
|
"display-cmap_reader_factory": "./cmap_reader_factory.js",
|
||||||
|
"display-standard_fontdata_factory": "./standard_fontdata_factory.js",
|
||||||
"display-fetch_stream": "./fetch_stream.js",
|
"display-fetch_stream": "./fetch_stream.js",
|
||||||
"display-network": "./network.js",
|
"display-network": "./network.js",
|
||||||
"display-node_stream": "./node_stream.js",
|
"display-node_stream": "./node_stream.js",
|
||||||
|
|||||||
@ -37,13 +37,10 @@ import {
|
|||||||
Util,
|
Util,
|
||||||
warn,
|
warn,
|
||||||
} from "../shared/util.js";
|
} from "../shared/util.js";
|
||||||
import {
|
import { PDFDateString, setLayerDimensions } from "./display_utils.js";
|
||||||
DOMSVGFactory,
|
|
||||||
PDFDateString,
|
|
||||||
setLayerDimensions,
|
|
||||||
} from "./display_utils.js";
|
|
||||||
import { AnnotationStorage } from "./annotation_storage.js";
|
import { AnnotationStorage } from "./annotation_storage.js";
|
||||||
import { ColorConverters } from "../shared/scripting_utils.js";
|
import { ColorConverters } from "../shared/scripting_utils.js";
|
||||||
|
import { DOMSVGFactory } from "./svg_factory.js";
|
||||||
import { XfaLayer } from "./xfa_layer.js";
|
import { XfaLayer } from "./xfa_layer.js";
|
||||||
|
|
||||||
const DEFAULT_TAB_INDEX = 1000;
|
const DEFAULT_TAB_INDEX = 1000;
|
||||||
|
|||||||
@ -45,10 +45,6 @@ import {
|
|||||||
} from "./annotation_storage.js";
|
} from "./annotation_storage.js";
|
||||||
import {
|
import {
|
||||||
deprecated,
|
deprecated,
|
||||||
DOMCanvasFactory,
|
|
||||||
DOMCMapReaderFactory,
|
|
||||||
DOMFilterFactory,
|
|
||||||
DOMStandardFontDataFactory,
|
|
||||||
isDataScheme,
|
isDataScheme,
|
||||||
isValidFetchUrl,
|
isValidFetchUrl,
|
||||||
PageViewport,
|
PageViewport,
|
||||||
@ -64,6 +60,10 @@ import {
|
|||||||
NodeStandardFontDataFactory,
|
NodeStandardFontDataFactory,
|
||||||
} from "display-node_utils";
|
} from "display-node_utils";
|
||||||
import { CanvasGraphics } from "./canvas.js";
|
import { CanvasGraphics } from "./canvas.js";
|
||||||
|
import { DOMCanvasFactory } from "./canvas_factory.js";
|
||||||
|
import { DOMCMapReaderFactory } from "display-cmap_reader_factory";
|
||||||
|
import { DOMFilterFactory } from "./filter_factory.js";
|
||||||
|
import { DOMStandardFontDataFactory } from "display-standard_fontdata_factory";
|
||||||
import { GlobalWorkerOptions } from "./worker_options.js";
|
import { GlobalWorkerOptions } from "./worker_options.js";
|
||||||
import { MessageHandler } from "../shared/message_handler.js";
|
import { MessageHandler } from "../shared/message_handler.js";
|
||||||
import { Metadata } from "./metadata.js";
|
import { Metadata } from "./metadata.js";
|
||||||
|
|||||||
@ -1,234 +0,0 @@
|
|||||||
/* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { unreachable } from "../shared/util.js";
|
|
||||||
|
|
||||||
class BaseFilterFactory {
|
|
||||||
constructor() {
|
|
||||||
if (
|
|
||||||
(typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) &&
|
|
||||||
this.constructor === BaseFilterFactory
|
|
||||||
) {
|
|
||||||
unreachable("Cannot initialize BaseFilterFactory.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addFilter(maps) {
|
|
||||||
return "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
addHCMFilter(fgColor, bgColor) {
|
|
||||||
return "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
addAlphaFilter(map) {
|
|
||||||
return "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
addLuminosityFilter(map) {
|
|
||||||
return "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
addHighlightHCMFilter(filterName, fgColor, bgColor, newFgColor, newBgColor) {
|
|
||||||
return "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy(keepHCM = false) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
class BaseCanvasFactory {
|
|
||||||
#enableHWA = false;
|
|
||||||
|
|
||||||
constructor({ enableHWA = false }) {
|
|
||||||
if (
|
|
||||||
(typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) &&
|
|
||||||
this.constructor === BaseCanvasFactory
|
|
||||||
) {
|
|
||||||
unreachable("Cannot initialize BaseCanvasFactory.");
|
|
||||||
}
|
|
||||||
this.#enableHWA = enableHWA;
|
|
||||||
}
|
|
||||||
|
|
||||||
create(width, height) {
|
|
||||||
if (width <= 0 || height <= 0) {
|
|
||||||
throw new Error("Invalid canvas size");
|
|
||||||
}
|
|
||||||
const canvas = this._createCanvas(width, height);
|
|
||||||
return {
|
|
||||||
canvas,
|
|
||||||
context: canvas.getContext("2d", {
|
|
||||||
willReadFrequently: !this.#enableHWA,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ignore
|
|
||||||
*/
|
|
||||||
_createCanvas(width, height) {
|
|
||||||
unreachable("Abstract method `_createCanvas` called.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class BaseCMapReaderFactory {
|
|
||||||
constructor({ baseUrl = null, isCompressed = true }) {
|
|
||||||
if (
|
|
||||||
(typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) &&
|
|
||||||
this.constructor === BaseCMapReaderFactory
|
|
||||||
) {
|
|
||||||
unreachable("Cannot initialize BaseCMapReaderFactory.");
|
|
||||||
}
|
|
||||||
this.baseUrl = baseUrl;
|
|
||||||
this.isCompressed = isCompressed;
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetch({ name }) {
|
|
||||||
if (!this.baseUrl) {
|
|
||||||
throw new Error(
|
|
||||||
"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" : "");
|
|
||||||
|
|
||||||
return this._fetch(url)
|
|
||||||
.then(cMapData => ({ cMapData, isCompressed: this.isCompressed }))
|
|
||||||
.catch(reason => {
|
|
||||||
throw new Error(
|
|
||||||
`Unable to load ${this.isCompressed ? "binary " : ""}CMap at: ${url}`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ignore
|
|
||||||
* @returns {Promise<Uint8Array>}
|
|
||||||
*/
|
|
||||||
async _fetch(url) {
|
|
||||||
unreachable("Abstract method `_fetch` called.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class BaseStandardFontDataFactory {
|
|
||||||
constructor({ baseUrl = null }) {
|
|
||||||
if (
|
|
||||||
(typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) &&
|
|
||||||
this.constructor === BaseStandardFontDataFactory
|
|
||||||
) {
|
|
||||||
unreachable("Cannot initialize BaseStandardFontDataFactory.");
|
|
||||||
}
|
|
||||||
this.baseUrl = baseUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetch({ filename }) {
|
|
||||||
if (!this.baseUrl) {
|
|
||||||
throw new Error(
|
|
||||||
"Ensure that the `standardFontDataUrl` API parameter is provided."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (!filename) {
|
|
||||||
throw new Error("Font filename must be specified.");
|
|
||||||
}
|
|
||||||
const url = `${this.baseUrl}${filename}`;
|
|
||||||
|
|
||||||
return this._fetch(url).catch(reason => {
|
|
||||||
throw new Error(`Unable to load font data at: ${url}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ignore
|
|
||||||
* @returns {Promise<Uint8Array>}
|
|
||||||
*/
|
|
||||||
async _fetch(url) {
|
|
||||||
unreachable("Abstract method `_fetch` called.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class BaseSVGFactory {
|
|
||||||
constructor() {
|
|
||||||
if (
|
|
||||||
(typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) &&
|
|
||||||
this.constructor === BaseSVGFactory
|
|
||||||
) {
|
|
||||||
unreachable("Cannot initialize BaseSVGFactory.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
create(width, height, skipDimensions = false) {
|
|
||||||
if (width <= 0 || height <= 0) {
|
|
||||||
throw new Error("Invalid SVG dimensions");
|
|
||||||
}
|
|
||||||
const svg = this._createSVG("svg:svg");
|
|
||||||
svg.setAttribute("version", "1.1");
|
|
||||||
|
|
||||||
if (!skipDimensions) {
|
|
||||||
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) {
|
|
||||||
if (typeof type !== "string") {
|
|
||||||
throw new Error("Invalid SVG element type");
|
|
||||||
}
|
|
||||||
return this._createSVG(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ignore
|
|
||||||
*/
|
|
||||||
_createSVG(type) {
|
|
||||||
unreachable("Abstract method `_createSVG` called.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
|
||||||
BaseCanvasFactory,
|
|
||||||
BaseCMapReaderFactory,
|
|
||||||
BaseFilterFactory,
|
|
||||||
BaseStandardFontDataFactory,
|
|
||||||
BaseSVGFactory,
|
|
||||||
};
|
|
||||||
92
src/display/canvas_factory.js
Normal file
92
src/display/canvas_factory.js
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
/* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { unreachable } from "../shared/util.js";
|
||||||
|
|
||||||
|
class BaseCanvasFactory {
|
||||||
|
#enableHWA = false;
|
||||||
|
|
||||||
|
constructor({ enableHWA = false }) {
|
||||||
|
if (
|
||||||
|
(typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) &&
|
||||||
|
this.constructor === BaseCanvasFactory
|
||||||
|
) {
|
||||||
|
unreachable("Cannot initialize BaseCanvasFactory.");
|
||||||
|
}
|
||||||
|
this.#enableHWA = enableHWA;
|
||||||
|
}
|
||||||
|
|
||||||
|
create(width, height) {
|
||||||
|
if (width <= 0 || height <= 0) {
|
||||||
|
throw new Error("Invalid canvas size");
|
||||||
|
}
|
||||||
|
const canvas = this._createCanvas(width, height);
|
||||||
|
return {
|
||||||
|
canvas,
|
||||||
|
context: canvas.getContext("2d", {
|
||||||
|
willReadFrequently: !this.#enableHWA,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
_createCanvas(width, height) {
|
||||||
|
unreachable("Abstract method `_createCanvas` called.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DOMCanvasFactory extends BaseCanvasFactory {
|
||||||
|
constructor({ ownerDocument = globalThis.document, enableHWA = false }) {
|
||||||
|
super({ enableHWA });
|
||||||
|
this._document = ownerDocument;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
_createCanvas(width, height) {
|
||||||
|
const canvas = this._document.createElement("canvas");
|
||||||
|
canvas.width = width;
|
||||||
|
canvas.height = height;
|
||||||
|
return canvas;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { BaseCanvasFactory, DOMCanvasFactory };
|
||||||
75
src/display/cmap_reader_factory.js
Normal file
75
src/display/cmap_reader_factory.js
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
/* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { stringToBytes, unreachable } from "../shared/util.js";
|
||||||
|
import { fetchData } from "./display_utils.js";
|
||||||
|
|
||||||
|
class BaseCMapReaderFactory {
|
||||||
|
constructor({ baseUrl = null, isCompressed = true }) {
|
||||||
|
if (
|
||||||
|
(typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) &&
|
||||||
|
this.constructor === BaseCMapReaderFactory
|
||||||
|
) {
|
||||||
|
unreachable("Cannot initialize BaseCMapReaderFactory.");
|
||||||
|
}
|
||||||
|
this.baseUrl = baseUrl;
|
||||||
|
this.isCompressed = isCompressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetch({ name }) {
|
||||||
|
if (!this.baseUrl) {
|
||||||
|
throw new Error(
|
||||||
|
"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" : "");
|
||||||
|
|
||||||
|
return this._fetch(url)
|
||||||
|
.then(cMapData => ({ cMapData, isCompressed: this.isCompressed }))
|
||||||
|
.catch(reason => {
|
||||||
|
throw new Error(
|
||||||
|
`Unable to load ${this.isCompressed ? "binary " : ""}CMap at: ${url}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
* @returns {Promise<Uint8Array>}
|
||||||
|
*/
|
||||||
|
async _fetch(url) {
|
||||||
|
unreachable("Abstract method `_fetch` called.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DOMCMapReaderFactory extends BaseCMapReaderFactory {
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
async _fetch(url) {
|
||||||
|
const data = await fetchData(
|
||||||
|
url,
|
||||||
|
/* type = */ this.isCompressed ? "arraybuffer" : "text"
|
||||||
|
);
|
||||||
|
return data instanceof ArrayBuffer
|
||||||
|
? new Uint8Array(data)
|
||||||
|
: stringToBytes(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { BaseCMapReaderFactory, DOMCMapReaderFactory };
|
||||||
@ -13,18 +13,10 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
|
||||||
BaseCanvasFactory,
|
|
||||||
BaseCMapReaderFactory,
|
|
||||||
BaseFilterFactory,
|
|
||||||
BaseStandardFontDataFactory,
|
|
||||||
BaseSVGFactory,
|
|
||||||
} from "./base_factory.js";
|
|
||||||
import {
|
import {
|
||||||
BaseException,
|
BaseException,
|
||||||
FeatureTest,
|
FeatureTest,
|
||||||
shadow,
|
shadow,
|
||||||
stringToBytes,
|
|
||||||
Util,
|
Util,
|
||||||
warn,
|
warn,
|
||||||
} from "../shared/util.js";
|
} from "../shared/util.js";
|
||||||
@ -39,479 +31,6 @@ class PixelsPerInch {
|
|||||||
static PDF_TO_CSS_UNITS = this.CSS / this.PDF;
|
static PDF_TO_CSS_UNITS = this.CSS / this.PDF;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* FilterFactory aims to create some SVG filters we can use when drawing an
|
|
||||||
* image (or whatever) on a canvas.
|
|
||||||
* Filters aren't applied with ctx.putImageData because it just overwrites the
|
|
||||||
* underlying pixels.
|
|
||||||
* With these filters, it's possible for example to apply some transfer maps on
|
|
||||||
* an image without the need to apply them on the pixel arrays: the renderer
|
|
||||||
* does the magic for us.
|
|
||||||
*/
|
|
||||||
class DOMFilterFactory extends BaseFilterFactory {
|
|
||||||
#baseUrl;
|
|
||||||
|
|
||||||
#_cache;
|
|
||||||
|
|
||||||
#_defs;
|
|
||||||
|
|
||||||
#docId;
|
|
||||||
|
|
||||||
#document;
|
|
||||||
|
|
||||||
#_hcmCache;
|
|
||||||
|
|
||||||
#id = 0;
|
|
||||||
|
|
||||||
constructor({ docId, ownerDocument = globalThis.document }) {
|
|
||||||
super();
|
|
||||||
this.#docId = docId;
|
|
||||||
this.#document = ownerDocument;
|
|
||||||
}
|
|
||||||
|
|
||||||
get #cache() {
|
|
||||||
return (this.#_cache ||= new Map());
|
|
||||||
}
|
|
||||||
|
|
||||||
get #hcmCache() {
|
|
||||||
return (this.#_hcmCache ||= new Map());
|
|
||||||
}
|
|
||||||
|
|
||||||
get #defs() {
|
|
||||||
if (!this.#_defs) {
|
|
||||||
const div = this.#document.createElement("div");
|
|
||||||
const { style } = div;
|
|
||||||
style.visibility = "hidden";
|
|
||||||
style.contain = "strict";
|
|
||||||
style.width = style.height = 0;
|
|
||||||
style.position = "absolute";
|
|
||||||
style.top = style.left = 0;
|
|
||||||
style.zIndex = -1;
|
|
||||||
|
|
||||||
const svg = this.#document.createElementNS(SVG_NS, "svg");
|
|
||||||
svg.setAttribute("width", 0);
|
|
||||||
svg.setAttribute("height", 0);
|
|
||||||
this.#_defs = this.#document.createElementNS(SVG_NS, "defs");
|
|
||||||
div.append(svg);
|
|
||||||
svg.append(this.#_defs);
|
|
||||||
this.#document.body.append(div);
|
|
||||||
}
|
|
||||||
return this.#_defs;
|
|
||||||
}
|
|
||||||
|
|
||||||
#createTables(maps) {
|
|
||||||
if (maps.length === 1) {
|
|
||||||
const mapR = maps[0];
|
|
||||||
const buffer = new Array(256);
|
|
||||||
for (let i = 0; i < 256; i++) {
|
|
||||||
buffer[i] = mapR[i] / 255;
|
|
||||||
}
|
|
||||||
|
|
||||||
const table = buffer.join(",");
|
|
||||||
return [table, table, table];
|
|
||||||
}
|
|
||||||
|
|
||||||
const [mapR, mapG, mapB] = maps;
|
|
||||||
const bufferR = new Array(256);
|
|
||||||
const bufferG = new Array(256);
|
|
||||||
const bufferB = new Array(256);
|
|
||||||
for (let i = 0; i < 256; i++) {
|
|
||||||
bufferR[i] = mapR[i] / 255;
|
|
||||||
bufferG[i] = mapG[i] / 255;
|
|
||||||
bufferB[i] = mapB[i] / 255;
|
|
||||||
}
|
|
||||||
return [bufferR.join(","), bufferG.join(","), bufferB.join(",")];
|
|
||||||
}
|
|
||||||
|
|
||||||
#createUrl(id) {
|
|
||||||
if (this.#baseUrl === undefined) {
|
|
||||||
// Unless a `<base>`-element is present a relative URL should work.
|
|
||||||
this.#baseUrl = "";
|
|
||||||
|
|
||||||
const url = this.#document.URL;
|
|
||||||
if (url !== this.#document.baseURI) {
|
|
||||||
if (isDataScheme(url)) {
|
|
||||||
warn('#createUrl: ignore "data:"-URL for performance reasons.');
|
|
||||||
} else {
|
|
||||||
this.#baseUrl = url.split("#", 1)[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return `url(${this.#baseUrl}#${id})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
addFilter(maps) {
|
|
||||||
if (!maps) {
|
|
||||||
return "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
// When a page is zoomed the page is re-drawn but the maps are likely
|
|
||||||
// the same.
|
|
||||||
let value = this.#cache.get(maps);
|
|
||||||
if (value) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [tableR, tableG, tableB] = this.#createTables(maps);
|
|
||||||
const key = maps.length === 1 ? tableR : `${tableR}${tableG}${tableB}`;
|
|
||||||
|
|
||||||
value = this.#cache.get(key);
|
|
||||||
if (value) {
|
|
||||||
this.#cache.set(maps, value);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We create a SVG filter: feComponentTransferElement
|
|
||||||
// https://www.w3.org/TR/SVG11/filters.html#feComponentTransferElement
|
|
||||||
|
|
||||||
const id = `g_${this.#docId}_transfer_map_${this.#id++}`;
|
|
||||||
const url = this.#createUrl(id);
|
|
||||||
this.#cache.set(maps, url);
|
|
||||||
this.#cache.set(key, url);
|
|
||||||
|
|
||||||
const filter = this.#createFilter(id);
|
|
||||||
this.#addTransferMapConversion(tableR, tableG, tableB, filter);
|
|
||||||
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
addHCMFilter(fgColor, bgColor) {
|
|
||||||
const key = `${fgColor}-${bgColor}`;
|
|
||||||
const filterName = "base";
|
|
||||||
let info = this.#hcmCache.get(filterName);
|
|
||||||
if (info?.key === key) {
|
|
||||||
return info.url;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (info) {
|
|
||||||
info.filter?.remove();
|
|
||||||
info.key = key;
|
|
||||||
info.url = "none";
|
|
||||||
info.filter = null;
|
|
||||||
} else {
|
|
||||||
info = {
|
|
||||||
key,
|
|
||||||
url: "none",
|
|
||||||
filter: null,
|
|
||||||
};
|
|
||||||
this.#hcmCache.set(filterName, info);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fgColor || !bgColor) {
|
|
||||||
return info.url;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fgRGB = this.#getRGB(fgColor);
|
|
||||||
fgColor = Util.makeHexColor(...fgRGB);
|
|
||||||
const bgRGB = this.#getRGB(bgColor);
|
|
||||||
bgColor = Util.makeHexColor(...bgRGB);
|
|
||||||
this.#defs.style.color = "";
|
|
||||||
|
|
||||||
if (
|
|
||||||
(fgColor === "#000000" && bgColor === "#ffffff") ||
|
|
||||||
fgColor === bgColor
|
|
||||||
) {
|
|
||||||
return info.url;
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/Accessibility/Understanding_Colors_and_Luminance
|
|
||||||
//
|
|
||||||
// Relative luminance:
|
|
||||||
// https://www.w3.org/TR/WCAG20/#relativeluminancedef
|
|
||||||
//
|
|
||||||
// We compute the rounded luminance of the default background color.
|
|
||||||
// Then for every color in the pdf, if its rounded luminance is the
|
|
||||||
// same as the background one then it's replaced by the new
|
|
||||||
// background color else by the foreground one.
|
|
||||||
const map = new Array(256);
|
|
||||||
for (let i = 0; i <= 255; i++) {
|
|
||||||
const x = i / 255;
|
|
||||||
map[i] = x <= 0.03928 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4;
|
|
||||||
}
|
|
||||||
const table = map.join(",");
|
|
||||||
|
|
||||||
const id = `g_${this.#docId}_hcm_filter`;
|
|
||||||
const filter = (info.filter = this.#createFilter(id));
|
|
||||||
this.#addTransferMapConversion(table, table, table, filter);
|
|
||||||
this.#addGrayConversion(filter);
|
|
||||||
|
|
||||||
const getSteps = (c, n) => {
|
|
||||||
const start = fgRGB[c] / 255;
|
|
||||||
const end = bgRGB[c] / 255;
|
|
||||||
const arr = new Array(n + 1);
|
|
||||||
for (let i = 0; i <= n; i++) {
|
|
||||||
arr[i] = start + (i / n) * (end - start);
|
|
||||||
}
|
|
||||||
return arr.join(",");
|
|
||||||
};
|
|
||||||
this.#addTransferMapConversion(
|
|
||||||
getSteps(0, 5),
|
|
||||||
getSteps(1, 5),
|
|
||||||
getSteps(2, 5),
|
|
||||||
filter
|
|
||||||
);
|
|
||||||
|
|
||||||
info.url = this.#createUrl(id);
|
|
||||||
return info.url;
|
|
||||||
}
|
|
||||||
|
|
||||||
addAlphaFilter(map) {
|
|
||||||
// When a page is zoomed the page is re-drawn but the maps are likely
|
|
||||||
// the same.
|
|
||||||
let value = this.#cache.get(map);
|
|
||||||
if (value) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [tableA] = this.#createTables([map]);
|
|
||||||
const key = `alpha_${tableA}`;
|
|
||||||
|
|
||||||
value = this.#cache.get(key);
|
|
||||||
if (value) {
|
|
||||||
this.#cache.set(map, value);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = `g_${this.#docId}_alpha_map_${this.#id++}`;
|
|
||||||
const url = this.#createUrl(id);
|
|
||||||
this.#cache.set(map, url);
|
|
||||||
this.#cache.set(key, url);
|
|
||||||
|
|
||||||
const filter = this.#createFilter(id);
|
|
||||||
this.#addTransferMapAlphaConversion(tableA, filter);
|
|
||||||
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
addLuminosityFilter(map) {
|
|
||||||
// When a page is zoomed the page is re-drawn but the maps are likely
|
|
||||||
// the same.
|
|
||||||
let value = this.#cache.get(map || "luminosity");
|
|
||||||
if (value) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
let tableA, key;
|
|
||||||
if (map) {
|
|
||||||
[tableA] = this.#createTables([map]);
|
|
||||||
key = `luminosity_${tableA}`;
|
|
||||||
} else {
|
|
||||||
key = "luminosity";
|
|
||||||
}
|
|
||||||
|
|
||||||
value = this.#cache.get(key);
|
|
||||||
if (value) {
|
|
||||||
this.#cache.set(map, value);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = `g_${this.#docId}_luminosity_map_${this.#id++}`;
|
|
||||||
const url = this.#createUrl(id);
|
|
||||||
this.#cache.set(map, url);
|
|
||||||
this.#cache.set(key, url);
|
|
||||||
|
|
||||||
const filter = this.#createFilter(id);
|
|
||||||
this.#addLuminosityConversion(filter);
|
|
||||||
if (map) {
|
|
||||||
this.#addTransferMapAlphaConversion(tableA, filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
addHighlightHCMFilter(filterName, fgColor, bgColor, newFgColor, newBgColor) {
|
|
||||||
const key = `${fgColor}-${bgColor}-${newFgColor}-${newBgColor}`;
|
|
||||||
let info = this.#hcmCache.get(filterName);
|
|
||||||
if (info?.key === key) {
|
|
||||||
return info.url;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (info) {
|
|
||||||
info.filter?.remove();
|
|
||||||
info.key = key;
|
|
||||||
info.url = "none";
|
|
||||||
info.filter = null;
|
|
||||||
} else {
|
|
||||||
info = {
|
|
||||||
key,
|
|
||||||
url: "none",
|
|
||||||
filter: null,
|
|
||||||
};
|
|
||||||
this.#hcmCache.set(filterName, info);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fgColor || !bgColor) {
|
|
||||||
return info.url;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [fgRGB, bgRGB] = [fgColor, bgColor].map(this.#getRGB.bind(this));
|
|
||||||
let fgGray = Math.round(
|
|
||||||
0.2126 * fgRGB[0] + 0.7152 * fgRGB[1] + 0.0722 * fgRGB[2]
|
|
||||||
);
|
|
||||||
let bgGray = Math.round(
|
|
||||||
0.2126 * bgRGB[0] + 0.7152 * bgRGB[1] + 0.0722 * bgRGB[2]
|
|
||||||
);
|
|
||||||
let [newFgRGB, newBgRGB] = [newFgColor, newBgColor].map(
|
|
||||||
this.#getRGB.bind(this)
|
|
||||||
);
|
|
||||||
if (bgGray < fgGray) {
|
|
||||||
[fgGray, bgGray, newFgRGB, newBgRGB] = [
|
|
||||||
bgGray,
|
|
||||||
fgGray,
|
|
||||||
newBgRGB,
|
|
||||||
newFgRGB,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
this.#defs.style.color = "";
|
|
||||||
|
|
||||||
// Now we can create the filters to highlight some canvas parts.
|
|
||||||
// The colors in the pdf will almost be Canvas and CanvasText, hence we
|
|
||||||
// want to filter them to finally get Highlight and HighlightText.
|
|
||||||
// Since we're in HCM the background color and the foreground color should
|
|
||||||
// be really different when converted to grayscale (if they're not then it
|
|
||||||
// means that we've a poor contrast). Once the canvas colors are converted
|
|
||||||
// to grayscale we can easily map them on their new colors.
|
|
||||||
// The grayscale step is important because if we've something like:
|
|
||||||
// fgColor = #FF....
|
|
||||||
// bgColor = #FF....
|
|
||||||
// then we are enable to map the red component on the new red components
|
|
||||||
// which can be different.
|
|
||||||
|
|
||||||
const getSteps = (fg, bg, n) => {
|
|
||||||
const arr = new Array(256);
|
|
||||||
const step = (bgGray - fgGray) / n;
|
|
||||||
const newStart = fg / 255;
|
|
||||||
const newStep = (bg - fg) / (255 * n);
|
|
||||||
let prev = 0;
|
|
||||||
for (let i = 0; i <= n; i++) {
|
|
||||||
const k = Math.round(fgGray + i * step);
|
|
||||||
const value = newStart + i * newStep;
|
|
||||||
for (let j = prev; j <= k; j++) {
|
|
||||||
arr[j] = value;
|
|
||||||
}
|
|
||||||
prev = k + 1;
|
|
||||||
}
|
|
||||||
for (let i = prev; i < 256; i++) {
|
|
||||||
arr[i] = arr[prev - 1];
|
|
||||||
}
|
|
||||||
return arr.join(",");
|
|
||||||
};
|
|
||||||
|
|
||||||
const id = `g_${this.#docId}_hcm_${filterName}_filter`;
|
|
||||||
const filter = (info.filter = this.#createFilter(id));
|
|
||||||
|
|
||||||
this.#addGrayConversion(filter);
|
|
||||||
this.#addTransferMapConversion(
|
|
||||||
getSteps(newFgRGB[0], newBgRGB[0], 5),
|
|
||||||
getSteps(newFgRGB[1], newBgRGB[1], 5),
|
|
||||||
getSteps(newFgRGB[2], newBgRGB[2], 5),
|
|
||||||
filter
|
|
||||||
);
|
|
||||||
|
|
||||||
info.url = this.#createUrl(id);
|
|
||||||
return info.url;
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy(keepHCM = false) {
|
|
||||||
if (keepHCM && this.#hcmCache.size !== 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.#_defs) {
|
|
||||||
this.#_defs.parentNode.parentNode.remove();
|
|
||||||
this.#_defs = null;
|
|
||||||
}
|
|
||||||
if (this.#_cache) {
|
|
||||||
this.#_cache.clear();
|
|
||||||
this.#_cache = null;
|
|
||||||
}
|
|
||||||
this.#id = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#addLuminosityConversion(filter) {
|
|
||||||
const feColorMatrix = this.#document.createElementNS(
|
|
||||||
SVG_NS,
|
|
||||||
"feColorMatrix"
|
|
||||||
);
|
|
||||||
feColorMatrix.setAttribute("type", "matrix");
|
|
||||||
feColorMatrix.setAttribute(
|
|
||||||
"values",
|
|
||||||
"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0.59 0.11 0 0"
|
|
||||||
);
|
|
||||||
filter.append(feColorMatrix);
|
|
||||||
}
|
|
||||||
|
|
||||||
#addGrayConversion(filter) {
|
|
||||||
const feColorMatrix = this.#document.createElementNS(
|
|
||||||
SVG_NS,
|
|
||||||
"feColorMatrix"
|
|
||||||
);
|
|
||||||
feColorMatrix.setAttribute("type", "matrix");
|
|
||||||
feColorMatrix.setAttribute(
|
|
||||||
"values",
|
|
||||||
"0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0 0 0 1 0"
|
|
||||||
);
|
|
||||||
filter.append(feColorMatrix);
|
|
||||||
}
|
|
||||||
|
|
||||||
#createFilter(id) {
|
|
||||||
const filter = this.#document.createElementNS(SVG_NS, "filter");
|
|
||||||
filter.setAttribute("color-interpolation-filters", "sRGB");
|
|
||||||
filter.setAttribute("id", id);
|
|
||||||
this.#defs.append(filter);
|
|
||||||
|
|
||||||
return filter;
|
|
||||||
}
|
|
||||||
|
|
||||||
#appendFeFunc(feComponentTransfer, func, table) {
|
|
||||||
const feFunc = this.#document.createElementNS(SVG_NS, func);
|
|
||||||
feFunc.setAttribute("type", "discrete");
|
|
||||||
feFunc.setAttribute("tableValues", table);
|
|
||||||
feComponentTransfer.append(feFunc);
|
|
||||||
}
|
|
||||||
|
|
||||||
#addTransferMapConversion(rTable, gTable, bTable, filter) {
|
|
||||||
const feComponentTransfer = this.#document.createElementNS(
|
|
||||||
SVG_NS,
|
|
||||||
"feComponentTransfer"
|
|
||||||
);
|
|
||||||
filter.append(feComponentTransfer);
|
|
||||||
this.#appendFeFunc(feComponentTransfer, "feFuncR", rTable);
|
|
||||||
this.#appendFeFunc(feComponentTransfer, "feFuncG", gTable);
|
|
||||||
this.#appendFeFunc(feComponentTransfer, "feFuncB", bTable);
|
|
||||||
}
|
|
||||||
|
|
||||||
#addTransferMapAlphaConversion(aTable, filter) {
|
|
||||||
const feComponentTransfer = this.#document.createElementNS(
|
|
||||||
SVG_NS,
|
|
||||||
"feComponentTransfer"
|
|
||||||
);
|
|
||||||
filter.append(feComponentTransfer);
|
|
||||||
this.#appendFeFunc(feComponentTransfer, "feFuncA", aTable);
|
|
||||||
}
|
|
||||||
|
|
||||||
#getRGB(color) {
|
|
||||||
this.#defs.style.color = color;
|
|
||||||
return getRGB(getComputedStyle(this.#defs).getPropertyValue("color"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class DOMCanvasFactory extends BaseCanvasFactory {
|
|
||||||
constructor({ ownerDocument = globalThis.document, enableHWA = false }) {
|
|
||||||
super({ enableHWA });
|
|
||||||
this._document = ownerDocument;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ignore
|
|
||||||
*/
|
|
||||||
_createCanvas(width, height) {
|
|
||||||
const canvas = this._document.createElement("canvas");
|
|
||||||
canvas.width = width;
|
|
||||||
canvas.height = height;
|
|
||||||
return canvas;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchData(url, type = "text") {
|
async function fetchData(url, type = "text") {
|
||||||
if (
|
if (
|
||||||
(typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) ||
|
(typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) ||
|
||||||
@ -560,40 +79,6 @@ async function fetchData(url, type = "text") {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class DOMCMapReaderFactory extends BaseCMapReaderFactory {
|
|
||||||
/**
|
|
||||||
* @ignore
|
|
||||||
*/
|
|
||||||
async _fetch(url) {
|
|
||||||
const data = await fetchData(
|
|
||||||
url,
|
|
||||||
/* type = */ this.isCompressed ? "arraybuffer" : "text"
|
|
||||||
);
|
|
||||||
return data instanceof ArrayBuffer
|
|
||||||
? new Uint8Array(data)
|
|
||||||
: stringToBytes(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class DOMStandardFontDataFactory extends BaseStandardFontDataFactory {
|
|
||||||
/**
|
|
||||||
* @ignore
|
|
||||||
*/
|
|
||||||
async _fetch(url) {
|
|
||||||
const data = await fetchData(url, /* type = */ "arraybuffer");
|
|
||||||
return new Uint8Array(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class DOMSVGFactory extends BaseSVGFactory {
|
|
||||||
/**
|
|
||||||
* @ignore
|
|
||||||
*/
|
|
||||||
_createSVG(type) {
|
|
||||||
return document.createElementNS(SVG_NS, type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} PageViewportParameters
|
* @typedef {Object} PageViewportParameters
|
||||||
* @property {Array<number>} viewBox - The xMin, yMin, xMax and
|
* @property {Array<number>} viewBox - The xMin, yMin, xMax and
|
||||||
@ -1152,11 +637,6 @@ class OutputScale {
|
|||||||
|
|
||||||
export {
|
export {
|
||||||
deprecated,
|
deprecated,
|
||||||
DOMCanvasFactory,
|
|
||||||
DOMCMapReaderFactory,
|
|
||||||
DOMFilterFactory,
|
|
||||||
DOMStandardFontDataFactory,
|
|
||||||
DOMSVGFactory,
|
|
||||||
fetchData,
|
fetchData,
|
||||||
getColorValues,
|
getColorValues,
|
||||||
getCurrentTransform,
|
getCurrentTransform,
|
||||||
@ -1176,4 +656,5 @@ export {
|
|||||||
RenderingCancelledException,
|
RenderingCancelledException,
|
||||||
setLayerDimensions,
|
setLayerDimensions,
|
||||||
StatTimer,
|
StatTimer,
|
||||||
|
SVG_NS,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -13,7 +13,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DOMSVGFactory } from "./display_utils.js";
|
import { DOMSVGFactory } from "./svg_factory.js";
|
||||||
import { shadow } from "../shared/util.js";
|
import { shadow } from "../shared/util.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
508
src/display/filter_factory.js
Normal file
508
src/display/filter_factory.js
Normal file
@ -0,0 +1,508 @@
|
|||||||
|
/* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { getRGB, isDataScheme, SVG_NS } from "./display_utils.js";
|
||||||
|
import { unreachable, Util, warn } from "../shared/util.js";
|
||||||
|
|
||||||
|
class BaseFilterFactory {
|
||||||
|
constructor() {
|
||||||
|
if (
|
||||||
|
(typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) &&
|
||||||
|
this.constructor === BaseFilterFactory
|
||||||
|
) {
|
||||||
|
unreachable("Cannot initialize BaseFilterFactory.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addFilter(maps) {
|
||||||
|
return "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
addHCMFilter(fgColor, bgColor) {
|
||||||
|
return "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
addAlphaFilter(map) {
|
||||||
|
return "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
addLuminosityFilter(map) {
|
||||||
|
return "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
addHighlightHCMFilter(filterName, fgColor, bgColor, newFgColor, newBgColor) {
|
||||||
|
return "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy(keepHCM = false) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FilterFactory aims to create some SVG filters we can use when drawing an
|
||||||
|
* image (or whatever) on a canvas.
|
||||||
|
* Filters aren't applied with ctx.putImageData because it just overwrites the
|
||||||
|
* underlying pixels.
|
||||||
|
* With these filters, it's possible for example to apply some transfer maps on
|
||||||
|
* an image without the need to apply them on the pixel arrays: the renderer
|
||||||
|
* does the magic for us.
|
||||||
|
*/
|
||||||
|
class DOMFilterFactory extends BaseFilterFactory {
|
||||||
|
#baseUrl;
|
||||||
|
|
||||||
|
#_cache;
|
||||||
|
|
||||||
|
#_defs;
|
||||||
|
|
||||||
|
#docId;
|
||||||
|
|
||||||
|
#document;
|
||||||
|
|
||||||
|
#_hcmCache;
|
||||||
|
|
||||||
|
#id = 0;
|
||||||
|
|
||||||
|
constructor({ docId, ownerDocument = globalThis.document }) {
|
||||||
|
super();
|
||||||
|
this.#docId = docId;
|
||||||
|
this.#document = ownerDocument;
|
||||||
|
}
|
||||||
|
|
||||||
|
get #cache() {
|
||||||
|
return (this.#_cache ||= new Map());
|
||||||
|
}
|
||||||
|
|
||||||
|
get #hcmCache() {
|
||||||
|
return (this.#_hcmCache ||= new Map());
|
||||||
|
}
|
||||||
|
|
||||||
|
get #defs() {
|
||||||
|
if (!this.#_defs) {
|
||||||
|
const div = this.#document.createElement("div");
|
||||||
|
const { style } = div;
|
||||||
|
style.visibility = "hidden";
|
||||||
|
style.contain = "strict";
|
||||||
|
style.width = style.height = 0;
|
||||||
|
style.position = "absolute";
|
||||||
|
style.top = style.left = 0;
|
||||||
|
style.zIndex = -1;
|
||||||
|
|
||||||
|
const svg = this.#document.createElementNS(SVG_NS, "svg");
|
||||||
|
svg.setAttribute("width", 0);
|
||||||
|
svg.setAttribute("height", 0);
|
||||||
|
this.#_defs = this.#document.createElementNS(SVG_NS, "defs");
|
||||||
|
div.append(svg);
|
||||||
|
svg.append(this.#_defs);
|
||||||
|
this.#document.body.append(div);
|
||||||
|
}
|
||||||
|
return this.#_defs;
|
||||||
|
}
|
||||||
|
|
||||||
|
#createTables(maps) {
|
||||||
|
if (maps.length === 1) {
|
||||||
|
const mapR = maps[0];
|
||||||
|
const buffer = new Array(256);
|
||||||
|
for (let i = 0; i < 256; i++) {
|
||||||
|
buffer[i] = mapR[i] / 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
const table = buffer.join(",");
|
||||||
|
return [table, table, table];
|
||||||
|
}
|
||||||
|
|
||||||
|
const [mapR, mapG, mapB] = maps;
|
||||||
|
const bufferR = new Array(256);
|
||||||
|
const bufferG = new Array(256);
|
||||||
|
const bufferB = new Array(256);
|
||||||
|
for (let i = 0; i < 256; i++) {
|
||||||
|
bufferR[i] = mapR[i] / 255;
|
||||||
|
bufferG[i] = mapG[i] / 255;
|
||||||
|
bufferB[i] = mapB[i] / 255;
|
||||||
|
}
|
||||||
|
return [bufferR.join(","), bufferG.join(","), bufferB.join(",")];
|
||||||
|
}
|
||||||
|
|
||||||
|
#createUrl(id) {
|
||||||
|
if (this.#baseUrl === undefined) {
|
||||||
|
// Unless a `<base>`-element is present a relative URL should work.
|
||||||
|
this.#baseUrl = "";
|
||||||
|
|
||||||
|
const url = this.#document.URL;
|
||||||
|
if (url !== this.#document.baseURI) {
|
||||||
|
if (isDataScheme(url)) {
|
||||||
|
warn('#createUrl: ignore "data:"-URL for performance reasons.');
|
||||||
|
} else {
|
||||||
|
this.#baseUrl = url.split("#", 1)[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return `url(${this.#baseUrl}#${id})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
addFilter(maps) {
|
||||||
|
if (!maps) {
|
||||||
|
return "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
// When a page is zoomed the page is re-drawn but the maps are likely
|
||||||
|
// the same.
|
||||||
|
let value = this.#cache.get(maps);
|
||||||
|
if (value) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [tableR, tableG, tableB] = this.#createTables(maps);
|
||||||
|
const key = maps.length === 1 ? tableR : `${tableR}${tableG}${tableB}`;
|
||||||
|
|
||||||
|
value = this.#cache.get(key);
|
||||||
|
if (value) {
|
||||||
|
this.#cache.set(maps, value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We create a SVG filter: feComponentTransferElement
|
||||||
|
// https://www.w3.org/TR/SVG11/filters.html#feComponentTransferElement
|
||||||
|
|
||||||
|
const id = `g_${this.#docId}_transfer_map_${this.#id++}`;
|
||||||
|
const url = this.#createUrl(id);
|
||||||
|
this.#cache.set(maps, url);
|
||||||
|
this.#cache.set(key, url);
|
||||||
|
|
||||||
|
const filter = this.#createFilter(id);
|
||||||
|
this.#addTransferMapConversion(tableR, tableG, tableB, filter);
|
||||||
|
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
addHCMFilter(fgColor, bgColor) {
|
||||||
|
const key = `${fgColor}-${bgColor}`;
|
||||||
|
const filterName = "base";
|
||||||
|
let info = this.#hcmCache.get(filterName);
|
||||||
|
if (info?.key === key) {
|
||||||
|
return info.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info) {
|
||||||
|
info.filter?.remove();
|
||||||
|
info.key = key;
|
||||||
|
info.url = "none";
|
||||||
|
info.filter = null;
|
||||||
|
} else {
|
||||||
|
info = {
|
||||||
|
key,
|
||||||
|
url: "none",
|
||||||
|
filter: null,
|
||||||
|
};
|
||||||
|
this.#hcmCache.set(filterName, info);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fgColor || !bgColor) {
|
||||||
|
return info.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fgRGB = this.#getRGB(fgColor);
|
||||||
|
fgColor = Util.makeHexColor(...fgRGB);
|
||||||
|
const bgRGB = this.#getRGB(bgColor);
|
||||||
|
bgColor = Util.makeHexColor(...bgRGB);
|
||||||
|
this.#defs.style.color = "";
|
||||||
|
|
||||||
|
if (
|
||||||
|
(fgColor === "#000000" && bgColor === "#ffffff") ||
|
||||||
|
fgColor === bgColor
|
||||||
|
) {
|
||||||
|
return info.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/Accessibility/Understanding_Colors_and_Luminance
|
||||||
|
//
|
||||||
|
// Relative luminance:
|
||||||
|
// https://www.w3.org/TR/WCAG20/#relativeluminancedef
|
||||||
|
//
|
||||||
|
// We compute the rounded luminance of the default background color.
|
||||||
|
// Then for every color in the pdf, if its rounded luminance is the
|
||||||
|
// same as the background one then it's replaced by the new
|
||||||
|
// background color else by the foreground one.
|
||||||
|
const map = new Array(256);
|
||||||
|
for (let i = 0; i <= 255; i++) {
|
||||||
|
const x = i / 255;
|
||||||
|
map[i] = x <= 0.03928 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4;
|
||||||
|
}
|
||||||
|
const table = map.join(",");
|
||||||
|
|
||||||
|
const id = `g_${this.#docId}_hcm_filter`;
|
||||||
|
const filter = (info.filter = this.#createFilter(id));
|
||||||
|
this.#addTransferMapConversion(table, table, table, filter);
|
||||||
|
this.#addGrayConversion(filter);
|
||||||
|
|
||||||
|
const getSteps = (c, n) => {
|
||||||
|
const start = fgRGB[c] / 255;
|
||||||
|
const end = bgRGB[c] / 255;
|
||||||
|
const arr = new Array(n + 1);
|
||||||
|
for (let i = 0; i <= n; i++) {
|
||||||
|
arr[i] = start + (i / n) * (end - start);
|
||||||
|
}
|
||||||
|
return arr.join(",");
|
||||||
|
};
|
||||||
|
this.#addTransferMapConversion(
|
||||||
|
getSteps(0, 5),
|
||||||
|
getSteps(1, 5),
|
||||||
|
getSteps(2, 5),
|
||||||
|
filter
|
||||||
|
);
|
||||||
|
|
||||||
|
info.url = this.#createUrl(id);
|
||||||
|
return info.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
addAlphaFilter(map) {
|
||||||
|
// When a page is zoomed the page is re-drawn but the maps are likely
|
||||||
|
// the same.
|
||||||
|
let value = this.#cache.get(map);
|
||||||
|
if (value) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [tableA] = this.#createTables([map]);
|
||||||
|
const key = `alpha_${tableA}`;
|
||||||
|
|
||||||
|
value = this.#cache.get(key);
|
||||||
|
if (value) {
|
||||||
|
this.#cache.set(map, value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = `g_${this.#docId}_alpha_map_${this.#id++}`;
|
||||||
|
const url = this.#createUrl(id);
|
||||||
|
this.#cache.set(map, url);
|
||||||
|
this.#cache.set(key, url);
|
||||||
|
|
||||||
|
const filter = this.#createFilter(id);
|
||||||
|
this.#addTransferMapAlphaConversion(tableA, filter);
|
||||||
|
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
addLuminosityFilter(map) {
|
||||||
|
// When a page is zoomed the page is re-drawn but the maps are likely
|
||||||
|
// the same.
|
||||||
|
let value = this.#cache.get(map || "luminosity");
|
||||||
|
if (value) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
let tableA, key;
|
||||||
|
if (map) {
|
||||||
|
[tableA] = this.#createTables([map]);
|
||||||
|
key = `luminosity_${tableA}`;
|
||||||
|
} else {
|
||||||
|
key = "luminosity";
|
||||||
|
}
|
||||||
|
|
||||||
|
value = this.#cache.get(key);
|
||||||
|
if (value) {
|
||||||
|
this.#cache.set(map, value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = `g_${this.#docId}_luminosity_map_${this.#id++}`;
|
||||||
|
const url = this.#createUrl(id);
|
||||||
|
this.#cache.set(map, url);
|
||||||
|
this.#cache.set(key, url);
|
||||||
|
|
||||||
|
const filter = this.#createFilter(id);
|
||||||
|
this.#addLuminosityConversion(filter);
|
||||||
|
if (map) {
|
||||||
|
this.#addTransferMapAlphaConversion(tableA, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
addHighlightHCMFilter(filterName, fgColor, bgColor, newFgColor, newBgColor) {
|
||||||
|
const key = `${fgColor}-${bgColor}-${newFgColor}-${newBgColor}`;
|
||||||
|
let info = this.#hcmCache.get(filterName);
|
||||||
|
if (info?.key === key) {
|
||||||
|
return info.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info) {
|
||||||
|
info.filter?.remove();
|
||||||
|
info.key = key;
|
||||||
|
info.url = "none";
|
||||||
|
info.filter = null;
|
||||||
|
} else {
|
||||||
|
info = {
|
||||||
|
key,
|
||||||
|
url: "none",
|
||||||
|
filter: null,
|
||||||
|
};
|
||||||
|
this.#hcmCache.set(filterName, info);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fgColor || !bgColor) {
|
||||||
|
return info.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [fgRGB, bgRGB] = [fgColor, bgColor].map(this.#getRGB.bind(this));
|
||||||
|
let fgGray = Math.round(
|
||||||
|
0.2126 * fgRGB[0] + 0.7152 * fgRGB[1] + 0.0722 * fgRGB[2]
|
||||||
|
);
|
||||||
|
let bgGray = Math.round(
|
||||||
|
0.2126 * bgRGB[0] + 0.7152 * bgRGB[1] + 0.0722 * bgRGB[2]
|
||||||
|
);
|
||||||
|
let [newFgRGB, newBgRGB] = [newFgColor, newBgColor].map(
|
||||||
|
this.#getRGB.bind(this)
|
||||||
|
);
|
||||||
|
if (bgGray < fgGray) {
|
||||||
|
[fgGray, bgGray, newFgRGB, newBgRGB] = [
|
||||||
|
bgGray,
|
||||||
|
fgGray,
|
||||||
|
newBgRGB,
|
||||||
|
newFgRGB,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
this.#defs.style.color = "";
|
||||||
|
|
||||||
|
// Now we can create the filters to highlight some canvas parts.
|
||||||
|
// The colors in the pdf will almost be Canvas and CanvasText, hence we
|
||||||
|
// want to filter them to finally get Highlight and HighlightText.
|
||||||
|
// Since we're in HCM the background color and the foreground color should
|
||||||
|
// be really different when converted to grayscale (if they're not then it
|
||||||
|
// means that we've a poor contrast). Once the canvas colors are converted
|
||||||
|
// to grayscale we can easily map them on their new colors.
|
||||||
|
// The grayscale step is important because if we've something like:
|
||||||
|
// fgColor = #FF....
|
||||||
|
// bgColor = #FF....
|
||||||
|
// then we are enable to map the red component on the new red components
|
||||||
|
// which can be different.
|
||||||
|
|
||||||
|
const getSteps = (fg, bg, n) => {
|
||||||
|
const arr = new Array(256);
|
||||||
|
const step = (bgGray - fgGray) / n;
|
||||||
|
const newStart = fg / 255;
|
||||||
|
const newStep = (bg - fg) / (255 * n);
|
||||||
|
let prev = 0;
|
||||||
|
for (let i = 0; i <= n; i++) {
|
||||||
|
const k = Math.round(fgGray + i * step);
|
||||||
|
const value = newStart + i * newStep;
|
||||||
|
for (let j = prev; j <= k; j++) {
|
||||||
|
arr[j] = value;
|
||||||
|
}
|
||||||
|
prev = k + 1;
|
||||||
|
}
|
||||||
|
for (let i = prev; i < 256; i++) {
|
||||||
|
arr[i] = arr[prev - 1];
|
||||||
|
}
|
||||||
|
return arr.join(",");
|
||||||
|
};
|
||||||
|
|
||||||
|
const id = `g_${this.#docId}_hcm_${filterName}_filter`;
|
||||||
|
const filter = (info.filter = this.#createFilter(id));
|
||||||
|
|
||||||
|
this.#addGrayConversion(filter);
|
||||||
|
this.#addTransferMapConversion(
|
||||||
|
getSteps(newFgRGB[0], newBgRGB[0], 5),
|
||||||
|
getSteps(newFgRGB[1], newBgRGB[1], 5),
|
||||||
|
getSteps(newFgRGB[2], newBgRGB[2], 5),
|
||||||
|
filter
|
||||||
|
);
|
||||||
|
|
||||||
|
info.url = this.#createUrl(id);
|
||||||
|
return info.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy(keepHCM = false) {
|
||||||
|
if (keepHCM && this.#hcmCache.size !== 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.#_defs) {
|
||||||
|
this.#_defs.parentNode.parentNode.remove();
|
||||||
|
this.#_defs = null;
|
||||||
|
}
|
||||||
|
if (this.#_cache) {
|
||||||
|
this.#_cache.clear();
|
||||||
|
this.#_cache = null;
|
||||||
|
}
|
||||||
|
this.#id = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#addLuminosityConversion(filter) {
|
||||||
|
const feColorMatrix = this.#document.createElementNS(
|
||||||
|
SVG_NS,
|
||||||
|
"feColorMatrix"
|
||||||
|
);
|
||||||
|
feColorMatrix.setAttribute("type", "matrix");
|
||||||
|
feColorMatrix.setAttribute(
|
||||||
|
"values",
|
||||||
|
"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0.59 0.11 0 0"
|
||||||
|
);
|
||||||
|
filter.append(feColorMatrix);
|
||||||
|
}
|
||||||
|
|
||||||
|
#addGrayConversion(filter) {
|
||||||
|
const feColorMatrix = this.#document.createElementNS(
|
||||||
|
SVG_NS,
|
||||||
|
"feColorMatrix"
|
||||||
|
);
|
||||||
|
feColorMatrix.setAttribute("type", "matrix");
|
||||||
|
feColorMatrix.setAttribute(
|
||||||
|
"values",
|
||||||
|
"0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0 0 0 1 0"
|
||||||
|
);
|
||||||
|
filter.append(feColorMatrix);
|
||||||
|
}
|
||||||
|
|
||||||
|
#createFilter(id) {
|
||||||
|
const filter = this.#document.createElementNS(SVG_NS, "filter");
|
||||||
|
filter.setAttribute("color-interpolation-filters", "sRGB");
|
||||||
|
filter.setAttribute("id", id);
|
||||||
|
this.#defs.append(filter);
|
||||||
|
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
#appendFeFunc(feComponentTransfer, func, table) {
|
||||||
|
const feFunc = this.#document.createElementNS(SVG_NS, func);
|
||||||
|
feFunc.setAttribute("type", "discrete");
|
||||||
|
feFunc.setAttribute("tableValues", table);
|
||||||
|
feComponentTransfer.append(feFunc);
|
||||||
|
}
|
||||||
|
|
||||||
|
#addTransferMapConversion(rTable, gTable, bTable, filter) {
|
||||||
|
const feComponentTransfer = this.#document.createElementNS(
|
||||||
|
SVG_NS,
|
||||||
|
"feComponentTransfer"
|
||||||
|
);
|
||||||
|
filter.append(feComponentTransfer);
|
||||||
|
this.#appendFeFunc(feComponentTransfer, "feFuncR", rTable);
|
||||||
|
this.#appendFeFunc(feComponentTransfer, "feFuncG", gTable);
|
||||||
|
this.#appendFeFunc(feComponentTransfer, "feFuncB", bTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
#addTransferMapAlphaConversion(aTable, filter) {
|
||||||
|
const feComponentTransfer = this.#document.createElementNS(
|
||||||
|
SVG_NS,
|
||||||
|
"feComponentTransfer"
|
||||||
|
);
|
||||||
|
filter.append(feComponentTransfer);
|
||||||
|
this.#appendFeFunc(feComponentTransfer, "feFuncA", aTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
#getRGB(color) {
|
||||||
|
this.#defs.style.color = color;
|
||||||
|
return getRGB(getComputedStyle(this.#defs).getPropertyValue("color"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { BaseFilterFactory, DOMFilterFactory };
|
||||||
@ -13,13 +13,11 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
|
||||||
BaseCanvasFactory,
|
|
||||||
BaseCMapReaderFactory,
|
|
||||||
BaseFilterFactory,
|
|
||||||
BaseStandardFontDataFactory,
|
|
||||||
} from "./base_factory.js";
|
|
||||||
import { isNodeJS, warn } from "../shared/util.js";
|
import { isNodeJS, warn } from "../shared/util.js";
|
||||||
|
import { BaseCanvasFactory } from "./canvas_factory.js";
|
||||||
|
import { BaseCMapReaderFactory } from "./cmap_reader_factory.js";
|
||||||
|
import { BaseFilterFactory } from "./filter_factory.js";
|
||||||
|
import { BaseStandardFontDataFactory } from "./standard_fontdata_factory.js";
|
||||||
|
|
||||||
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
|
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|||||||
65
src/display/standard_fontdata_factory.js
Normal file
65
src/display/standard_fontdata_factory.js
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
/* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { fetchData } from "./display_utils.js";
|
||||||
|
import { unreachable } from "../shared/util.js";
|
||||||
|
|
||||||
|
class BaseStandardFontDataFactory {
|
||||||
|
constructor({ baseUrl = null }) {
|
||||||
|
if (
|
||||||
|
(typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) &&
|
||||||
|
this.constructor === BaseStandardFontDataFactory
|
||||||
|
) {
|
||||||
|
unreachable("Cannot initialize BaseStandardFontDataFactory.");
|
||||||
|
}
|
||||||
|
this.baseUrl = baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetch({ filename }) {
|
||||||
|
if (!this.baseUrl) {
|
||||||
|
throw new Error(
|
||||||
|
"Ensure that the `standardFontDataUrl` API parameter is provided."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!filename) {
|
||||||
|
throw new Error("Font filename must be specified.");
|
||||||
|
}
|
||||||
|
const url = `${this.baseUrl}${filename}`;
|
||||||
|
|
||||||
|
return this._fetch(url).catch(reason => {
|
||||||
|
throw new Error(`Unable to load font data at: ${url}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
* @returns {Promise<Uint8Array>}
|
||||||
|
*/
|
||||||
|
async _fetch(url) {
|
||||||
|
unreachable("Abstract method `_fetch` called.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DOMStandardFontDataFactory extends BaseStandardFontDataFactory {
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
async _fetch(url) {
|
||||||
|
const data = await fetchData(url, /* type = */ "arraybuffer");
|
||||||
|
return new Uint8Array(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { BaseStandardFontDataFactory, DOMStandardFontDataFactory };
|
||||||
@ -13,6 +13,8 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
const DOMCMapReaderFactory = null;
|
||||||
|
const DOMStandardFontDataFactory = null;
|
||||||
const NodeCanvasFactory = null;
|
const NodeCanvasFactory = null;
|
||||||
const NodeCMapReaderFactory = null;
|
const NodeCMapReaderFactory = null;
|
||||||
const NodeFilterFactory = null;
|
const NodeFilterFactory = null;
|
||||||
@ -23,6 +25,8 @@ const PDFNetworkStream = null;
|
|||||||
const PDFNodeStream = null;
|
const PDFNodeStream = null;
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
DOMCMapReaderFactory,
|
||||||
|
DOMStandardFontDataFactory,
|
||||||
NodeCanvasFactory,
|
NodeCanvasFactory,
|
||||||
NodeCMapReaderFactory,
|
NodeCMapReaderFactory,
|
||||||
NodeFilterFactory,
|
NodeFilterFactory,
|
||||||
|
|||||||
71
src/display/svg_factory.js
Normal file
71
src/display/svg_factory.js
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
/* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { SVG_NS } from "./display_utils.js";
|
||||||
|
import { unreachable } from "../shared/util.js";
|
||||||
|
|
||||||
|
class BaseSVGFactory {
|
||||||
|
constructor() {
|
||||||
|
if (
|
||||||
|
(typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) &&
|
||||||
|
this.constructor === BaseSVGFactory
|
||||||
|
) {
|
||||||
|
unreachable("Cannot initialize BaseSVGFactory.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
create(width, height, skipDimensions = false) {
|
||||||
|
if (width <= 0 || height <= 0) {
|
||||||
|
throw new Error("Invalid SVG dimensions");
|
||||||
|
}
|
||||||
|
const svg = this._createSVG("svg:svg");
|
||||||
|
svg.setAttribute("version", "1.1");
|
||||||
|
|
||||||
|
if (!skipDimensions) {
|
||||||
|
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) {
|
||||||
|
if (typeof type !== "string") {
|
||||||
|
throw new Error("Invalid SVG element type");
|
||||||
|
}
|
||||||
|
return this._createSVG(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
_createSVG(type) {
|
||||||
|
unreachable("Abstract method `_createSVG` called.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DOMSVGFactory extends BaseSVGFactory {
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
_createSVG(type) {
|
||||||
|
return document.createElementNS(SVG_NS, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { BaseSVGFactory, DOMSVGFactory };
|
||||||
@ -49,7 +49,6 @@ import {
|
|||||||
version,
|
version,
|
||||||
} from "./display/api.js";
|
} from "./display/api.js";
|
||||||
import {
|
import {
|
||||||
DOMSVGFactory,
|
|
||||||
fetchData,
|
fetchData,
|
||||||
getFilenameFromUrl,
|
getFilenameFromUrl,
|
||||||
getPdfFilenameFromUrl,
|
getPdfFilenameFromUrl,
|
||||||
@ -67,6 +66,7 @@ import { AnnotationEditorLayer } from "./display/editor/annotation_editor_layer.
|
|||||||
import { AnnotationEditorUIManager } from "./display/editor/tools.js";
|
import { AnnotationEditorUIManager } from "./display/editor/tools.js";
|
||||||
import { AnnotationLayer } from "./display/annotation_layer.js";
|
import { AnnotationLayer } from "./display/annotation_layer.js";
|
||||||
import { ColorPicker } from "./display/editor/color_picker.js";
|
import { ColorPicker } from "./display/editor/color_picker.js";
|
||||||
|
import { DOMSVGFactory } from "./display/svg_factory.js";
|
||||||
import { DrawLayer } from "./display/draw_layer.js";
|
import { DrawLayer } from "./display/draw_layer.js";
|
||||||
import { GlobalWorkerOptions } from "./display/worker_options.js";
|
import { GlobalWorkerOptions } from "./display/worker_options.js";
|
||||||
import { HighlightOutliner } from "./display/editor/drawers/highlight.js";
|
import { HighlightOutliner } from "./display/editor/drawers/highlight.js";
|
||||||
|
|||||||
111
test/unit/canvas_factory_spec.js
Normal file
111
test/unit/canvas_factory_spec.js
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
/* Copyright 2017 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 { DOMCanvasFactory } from "../../src/display/canvas_factory.js";
|
||||||
|
import { isNodeJS } from "../../src/shared/util.js";
|
||||||
|
|
||||||
|
describe("canvas_factory", function () {
|
||||||
|
describe("DOMCanvasFactory", function () {
|
||||||
|
let canvasFactory;
|
||||||
|
|
||||||
|
beforeAll(function () {
|
||||||
|
canvasFactory = new DOMCanvasFactory({});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(function () {
|
||||||
|
canvasFactory = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("`create` should throw an error if the dimensions are invalid", function () {
|
||||||
|
// Invalid width.
|
||||||
|
expect(function () {
|
||||||
|
return canvasFactory.create(-1, 1);
|
||||||
|
}).toThrow(new Error("Invalid canvas size"));
|
||||||
|
|
||||||
|
// Invalid height.
|
||||||
|
expect(function () {
|
||||||
|
return canvasFactory.create(1, -1);
|
||||||
|
}).toThrow(new Error("Invalid canvas size"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("`create` should return a canvas if the dimensions are valid", function () {
|
||||||
|
if (isNodeJS) {
|
||||||
|
pending("Document is not supported in Node.js.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const { canvas, context } = canvasFactory.create(20, 40);
|
||||||
|
expect(canvas instanceof HTMLCanvasElement).toBe(true);
|
||||||
|
expect(context instanceof CanvasRenderingContext2D).toBe(true);
|
||||||
|
expect(canvas.width).toBe(20);
|
||||||
|
expect(canvas.height).toBe(40);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("`reset` should throw an error if no canvas is provided", function () {
|
||||||
|
const canvasAndContext = { canvas: null, context: null };
|
||||||
|
|
||||||
|
expect(function () {
|
||||||
|
return canvasFactory.reset(canvasAndContext, 20, 40);
|
||||||
|
}).toThrow(new Error("Canvas is not specified"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("`reset` should throw an error if the dimensions are invalid", function () {
|
||||||
|
const canvasAndContext = { canvas: "foo", context: "bar" };
|
||||||
|
|
||||||
|
// Invalid width.
|
||||||
|
expect(function () {
|
||||||
|
return canvasFactory.reset(canvasAndContext, -1, 1);
|
||||||
|
}).toThrow(new Error("Invalid canvas size"));
|
||||||
|
|
||||||
|
// Invalid height.
|
||||||
|
expect(function () {
|
||||||
|
return canvasFactory.reset(canvasAndContext, 1, -1);
|
||||||
|
}).toThrow(new Error("Invalid canvas size"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("`reset` should alter the canvas/context if the dimensions are valid", function () {
|
||||||
|
if (isNodeJS) {
|
||||||
|
pending("Document is not supported in Node.js.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const canvasAndContext = canvasFactory.create(20, 40);
|
||||||
|
canvasFactory.reset(canvasAndContext, 60, 80);
|
||||||
|
|
||||||
|
const { canvas, context } = canvasAndContext;
|
||||||
|
expect(canvas instanceof HTMLCanvasElement).toBe(true);
|
||||||
|
expect(context instanceof CanvasRenderingContext2D).toBe(true);
|
||||||
|
expect(canvas.width).toBe(60);
|
||||||
|
expect(canvas.height).toBe(80);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("`destroy` should throw an error if no canvas is provided", function () {
|
||||||
|
expect(function () {
|
||||||
|
return canvasFactory.destroy({});
|
||||||
|
}).toThrow(new Error("Canvas is not specified"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("`destroy` should clear the canvas/context", function () {
|
||||||
|
if (isNodeJS) {
|
||||||
|
pending("Document is not supported in Node.js.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const canvasAndContext = canvasFactory.create(20, 40);
|
||||||
|
canvasFactory.destroy(canvasAndContext);
|
||||||
|
|
||||||
|
const { canvas, context } = canvasAndContext;
|
||||||
|
expect(canvas).toBe(null);
|
||||||
|
expect(context).toBe(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -9,6 +9,7 @@
|
|||||||
"api_spec.js",
|
"api_spec.js",
|
||||||
"app_options_spec.js",
|
"app_options_spec.js",
|
||||||
"bidi_spec.js",
|
"bidi_spec.js",
|
||||||
|
"canvas_factory_spec.js",
|
||||||
"cff_parser_spec.js",
|
"cff_parser_spec.js",
|
||||||
"cmap_spec.js",
|
"cmap_spec.js",
|
||||||
"colorspace_spec.js",
|
"colorspace_spec.js",
|
||||||
@ -42,6 +43,7 @@
|
|||||||
"primitives_spec.js",
|
"primitives_spec.js",
|
||||||
"stream_spec.js",
|
"stream_spec.js",
|
||||||
"struct_tree_spec.js",
|
"struct_tree_spec.js",
|
||||||
|
"svg_factory_spec.js",
|
||||||
"text_layer_spec.js",
|
"text_layer_spec.js",
|
||||||
"type1_parser_spec.js",
|
"type1_parser_spec.js",
|
||||||
"ui_utils_spec.js",
|
"ui_utils_spec.js",
|
||||||
|
|||||||
@ -15,8 +15,6 @@
|
|||||||
|
|
||||||
import { bytesToString, isNodeJS } from "../../src/shared/util.js";
|
import { bytesToString, isNodeJS } from "../../src/shared/util.js";
|
||||||
import {
|
import {
|
||||||
DOMCanvasFactory,
|
|
||||||
DOMSVGFactory,
|
|
||||||
getFilenameFromUrl,
|
getFilenameFromUrl,
|
||||||
getPdfFilenameFromUrl,
|
getPdfFilenameFromUrl,
|
||||||
isValidFetchUrl,
|
isValidFetchUrl,
|
||||||
@ -24,151 +22,6 @@ import {
|
|||||||
} from "../../src/display/display_utils.js";
|
} from "../../src/display/display_utils.js";
|
||||||
|
|
||||||
describe("display_utils", function () {
|
describe("display_utils", function () {
|
||||||
describe("DOMCanvasFactory", function () {
|
|
||||||
let canvasFactory;
|
|
||||||
|
|
||||||
beforeAll(function () {
|
|
||||||
canvasFactory = new DOMCanvasFactory({});
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(function () {
|
|
||||||
canvasFactory = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("`create` should throw an error if the dimensions are invalid", function () {
|
|
||||||
// Invalid width.
|
|
||||||
expect(function () {
|
|
||||||
return canvasFactory.create(-1, 1);
|
|
||||||
}).toThrow(new Error("Invalid canvas size"));
|
|
||||||
|
|
||||||
// Invalid height.
|
|
||||||
expect(function () {
|
|
||||||
return canvasFactory.create(1, -1);
|
|
||||||
}).toThrow(new Error("Invalid canvas size"));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("`create` should return a canvas if the dimensions are valid", function () {
|
|
||||||
if (isNodeJS) {
|
|
||||||
pending("Document is not supported in Node.js.");
|
|
||||||
}
|
|
||||||
|
|
||||||
const { canvas, context } = canvasFactory.create(20, 40);
|
|
||||||
expect(canvas instanceof HTMLCanvasElement).toBe(true);
|
|
||||||
expect(context instanceof CanvasRenderingContext2D).toBe(true);
|
|
||||||
expect(canvas.width).toBe(20);
|
|
||||||
expect(canvas.height).toBe(40);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("`reset` should throw an error if no canvas is provided", function () {
|
|
||||||
const canvasAndContext = { canvas: null, context: null };
|
|
||||||
|
|
||||||
expect(function () {
|
|
||||||
return canvasFactory.reset(canvasAndContext, 20, 40);
|
|
||||||
}).toThrow(new Error("Canvas is not specified"));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("`reset` should throw an error if the dimensions are invalid", function () {
|
|
||||||
const canvasAndContext = { canvas: "foo", context: "bar" };
|
|
||||||
|
|
||||||
// Invalid width.
|
|
||||||
expect(function () {
|
|
||||||
return canvasFactory.reset(canvasAndContext, -1, 1);
|
|
||||||
}).toThrow(new Error("Invalid canvas size"));
|
|
||||||
|
|
||||||
// Invalid height.
|
|
||||||
expect(function () {
|
|
||||||
return canvasFactory.reset(canvasAndContext, 1, -1);
|
|
||||||
}).toThrow(new Error("Invalid canvas size"));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("`reset` should alter the canvas/context if the dimensions are valid", function () {
|
|
||||||
if (isNodeJS) {
|
|
||||||
pending("Document is not supported in Node.js.");
|
|
||||||
}
|
|
||||||
|
|
||||||
const canvasAndContext = canvasFactory.create(20, 40);
|
|
||||||
canvasFactory.reset(canvasAndContext, 60, 80);
|
|
||||||
|
|
||||||
const { canvas, context } = canvasAndContext;
|
|
||||||
expect(canvas instanceof HTMLCanvasElement).toBe(true);
|
|
||||||
expect(context instanceof CanvasRenderingContext2D).toBe(true);
|
|
||||||
expect(canvas.width).toBe(60);
|
|
||||||
expect(canvas.height).toBe(80);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("`destroy` should throw an error if no canvas is provided", function () {
|
|
||||||
expect(function () {
|
|
||||||
return canvasFactory.destroy({});
|
|
||||||
}).toThrow(new Error("Canvas is not specified"));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("`destroy` should clear the canvas/context", function () {
|
|
||||||
if (isNodeJS) {
|
|
||||||
pending("Document is not supported in Node.js.");
|
|
||||||
}
|
|
||||||
|
|
||||||
const canvasAndContext = canvasFactory.create(20, 40);
|
|
||||||
canvasFactory.destroy(canvasAndContext);
|
|
||||||
|
|
||||||
const { canvas, context } = canvasAndContext;
|
|
||||||
expect(canvas).toBe(null);
|
|
||||||
expect(context).toBe(null);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("DOMSVGFactory", function () {
|
|
||||||
let svgFactory;
|
|
||||||
|
|
||||||
beforeAll(function () {
|
|
||||||
svgFactory = new DOMSVGFactory();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(function () {
|
|
||||||
svgFactory = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("`create` should throw an error if the dimensions are invalid", function () {
|
|
||||||
// Invalid width.
|
|
||||||
expect(function () {
|
|
||||||
return svgFactory.create(-1, 0);
|
|
||||||
}).toThrow(new Error("Invalid SVG dimensions"));
|
|
||||||
|
|
||||||
// Invalid height.
|
|
||||||
expect(function () {
|
|
||||||
return svgFactory.create(0, -1);
|
|
||||||
}).toThrow(new Error("Invalid SVG dimensions"));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("`create` should return an SVG element if the dimensions are valid", function () {
|
|
||||||
if (isNodeJS) {
|
|
||||||
pending("Document is not supported in Node.js.");
|
|
||||||
}
|
|
||||||
|
|
||||||
const svg = svgFactory.create(20, 40);
|
|
||||||
expect(svg instanceof SVGSVGElement).toBe(true);
|
|
||||||
expect(svg.getAttribute("version")).toBe("1.1");
|
|
||||||
expect(svg.getAttribute("width")).toBe("20px");
|
|
||||||
expect(svg.getAttribute("height")).toBe("40px");
|
|
||||||
expect(svg.getAttribute("preserveAspectRatio")).toBe("none");
|
|
||||||
expect(svg.getAttribute("viewBox")).toBe("0 0 20 40");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("`createElement` should throw an error if the type is not a string", function () {
|
|
||||||
expect(function () {
|
|
||||||
return svgFactory.createElement(true);
|
|
||||||
}).toThrow(new Error("Invalid SVG element type"));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("`createElement` should return an SVG element if the type is valid", function () {
|
|
||||||
if (isNodeJS) {
|
|
||||||
pending("Document is not supported in Node.js.");
|
|
||||||
}
|
|
||||||
|
|
||||||
const svg = svgFactory.createElement("svg:rect");
|
|
||||||
expect(svg instanceof SVGRectElement).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("getFilenameFromUrl", function () {
|
describe("getFilenameFromUrl", function () {
|
||||||
it("should get the filename from an absolute URL", function () {
|
it("should get the filename from an absolute URL", function () {
|
||||||
const url = "https://server.org/filename.pdf";
|
const url = "https://server.org/filename.pdf";
|
||||||
|
|||||||
@ -52,6 +52,7 @@ async function initializePDFJS(callback) {
|
|||||||
"pdfjs-test/unit/api_spec.js",
|
"pdfjs-test/unit/api_spec.js",
|
||||||
"pdfjs-test/unit/app_options_spec.js",
|
"pdfjs-test/unit/app_options_spec.js",
|
||||||
"pdfjs-test/unit/bidi_spec.js",
|
"pdfjs-test/unit/bidi_spec.js",
|
||||||
|
"pdfjs-test/unit/canvas_factory_spec.js",
|
||||||
"pdfjs-test/unit/cff_parser_spec.js",
|
"pdfjs-test/unit/cff_parser_spec.js",
|
||||||
"pdfjs-test/unit/cmap_spec.js",
|
"pdfjs-test/unit/cmap_spec.js",
|
||||||
"pdfjs-test/unit/colorspace_spec.js",
|
"pdfjs-test/unit/colorspace_spec.js",
|
||||||
@ -86,6 +87,7 @@ async function initializePDFJS(callback) {
|
|||||||
"pdfjs-test/unit/scripting_spec.js",
|
"pdfjs-test/unit/scripting_spec.js",
|
||||||
"pdfjs-test/unit/stream_spec.js",
|
"pdfjs-test/unit/stream_spec.js",
|
||||||
"pdfjs-test/unit/struct_tree_spec.js",
|
"pdfjs-test/unit/struct_tree_spec.js",
|
||||||
|
"pdfjs-test/unit/svg_factory_spec.js",
|
||||||
"pdfjs-test/unit/text_layer_spec.js",
|
"pdfjs-test/unit/text_layer_spec.js",
|
||||||
"pdfjs-test/unit/type1_parser_spec.js",
|
"pdfjs-test/unit/type1_parser_spec.js",
|
||||||
"pdfjs-test/unit/ui_utils_spec.js",
|
"pdfjs-test/unit/ui_utils_spec.js",
|
||||||
|
|||||||
@ -41,7 +41,6 @@ import {
|
|||||||
version,
|
version,
|
||||||
} from "../../src/display/api.js";
|
} from "../../src/display/api.js";
|
||||||
import {
|
import {
|
||||||
DOMSVGFactory,
|
|
||||||
fetchData,
|
fetchData,
|
||||||
getFilenameFromUrl,
|
getFilenameFromUrl,
|
||||||
getPdfFilenameFromUrl,
|
getPdfFilenameFromUrl,
|
||||||
@ -59,6 +58,7 @@ import { AnnotationEditorLayer } from "../../src/display/editor/annotation_edito
|
|||||||
import { AnnotationEditorUIManager } from "../../src/display/editor/tools.js";
|
import { AnnotationEditorUIManager } from "../../src/display/editor/tools.js";
|
||||||
import { AnnotationLayer } from "../../src/display/annotation_layer.js";
|
import { AnnotationLayer } from "../../src/display/annotation_layer.js";
|
||||||
import { ColorPicker } from "../../src/display/editor/color_picker.js";
|
import { ColorPicker } from "../../src/display/editor/color_picker.js";
|
||||||
|
import { DOMSVGFactory } from "../../src/display/svg_factory.js";
|
||||||
import { DrawLayer } from "../../src/display/draw_layer.js";
|
import { DrawLayer } from "../../src/display/draw_layer.js";
|
||||||
import { GlobalWorkerOptions } from "../../src/display/worker_options.js";
|
import { GlobalWorkerOptions } from "../../src/display/worker_options.js";
|
||||||
import { TextLayer } from "../../src/display/text_layer.js";
|
import { TextLayer } from "../../src/display/text_layer.js";
|
||||||
|
|||||||
72
test/unit/svg_factory_spec.js
Normal file
72
test/unit/svg_factory_spec.js
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
/* Copyright 2017 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 { DOMSVGFactory } from "../../src/display/svg_factory.js";
|
||||||
|
import { isNodeJS } from "../../src/shared/util.js";
|
||||||
|
|
||||||
|
describe("svg_factory", function () {
|
||||||
|
describe("DOMSVGFactory", function () {
|
||||||
|
let svgFactory;
|
||||||
|
|
||||||
|
beforeAll(function () {
|
||||||
|
svgFactory = new DOMSVGFactory();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(function () {
|
||||||
|
svgFactory = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("`create` should throw an error if the dimensions are invalid", function () {
|
||||||
|
// Invalid width.
|
||||||
|
expect(function () {
|
||||||
|
return svgFactory.create(-1, 0);
|
||||||
|
}).toThrow(new Error("Invalid SVG dimensions"));
|
||||||
|
|
||||||
|
// Invalid height.
|
||||||
|
expect(function () {
|
||||||
|
return svgFactory.create(0, -1);
|
||||||
|
}).toThrow(new Error("Invalid SVG dimensions"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("`create` should return an SVG element if the dimensions are valid", function () {
|
||||||
|
if (isNodeJS) {
|
||||||
|
pending("Document is not supported in Node.js.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const svg = svgFactory.create(20, 40);
|
||||||
|
expect(svg instanceof SVGSVGElement).toBe(true);
|
||||||
|
expect(svg.getAttribute("version")).toBe("1.1");
|
||||||
|
expect(svg.getAttribute("width")).toBe("20px");
|
||||||
|
expect(svg.getAttribute("height")).toBe("40px");
|
||||||
|
expect(svg.getAttribute("preserveAspectRatio")).toBe("none");
|
||||||
|
expect(svg.getAttribute("viewBox")).toBe("0 0 20 40");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("`createElement` should throw an error if the type is not a string", function () {
|
||||||
|
expect(function () {
|
||||||
|
return svgFactory.createElement(true);
|
||||||
|
}).toThrow(new Error("Invalid SVG element type"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("`createElement` should return an SVG element if the type is valid", function () {
|
||||||
|
if (isNodeJS) {
|
||||||
|
pending("Document is not supported in Node.js.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const svg = svgFactory.createElement("svg:rect");
|
||||||
|
expect(svg instanceof SVGRectElement).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -20,6 +20,8 @@
|
|||||||
"fluent-dom": "../../node_modules/@fluent/dom/esm/index.js",
|
"fluent-dom": "../../node_modules/@fluent/dom/esm/index.js",
|
||||||
"cached-iterable": "../../node_modules/cached-iterable/src/index.mjs",
|
"cached-iterable": "../../node_modules/cached-iterable/src/index.mjs",
|
||||||
|
|
||||||
|
"display-cmap_reader_factory": "../../src/display/cmap_reader_factory.js",
|
||||||
|
"display-standard_fontdata_factory": "../../src/display/standard_fontdata_factory.js",
|
||||||
"display-fetch_stream": "../../src/display/fetch_stream.js",
|
"display-fetch_stream": "../../src/display/fetch_stream.js",
|
||||||
"display-network": "../../src/display/network.js",
|
"display-network": "../../src/display/network.js",
|
||||||
"display-node_stream": "../../src/display/stubs.js",
|
"display-node_stream": "../../src/display/stubs.js",
|
||||||
|
|||||||
@ -10,6 +10,10 @@
|
|||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"paths": {
|
"paths": {
|
||||||
"pdfjs-lib": ["./src/pdf"],
|
"pdfjs-lib": ["./src/pdf"],
|
||||||
|
"display-cmap_reader_factory": ["./src/display/cmap_reader_factory"],
|
||||||
|
"display-standard_fontdata_factory": [
|
||||||
|
"./src/display/standard_fontdata_factory"
|
||||||
|
],
|
||||||
"display-fetch_stream": ["./src/display/fetch_stream"],
|
"display-fetch_stream": ["./src/display/fetch_stream"],
|
||||||
"display-network": ["./src/display/network"],
|
"display-network": ["./src/display/network"],
|
||||||
"display-node_stream": ["./src/display/node_stream"],
|
"display-node_stream": ["./src/display/node_stream"],
|
||||||
|
|||||||
@ -59,6 +59,8 @@ See https://github.com/adobe-type-tools/cmap-resources
|
|||||||
"fluent-dom": "../node_modules/@fluent/dom/esm/index.js",
|
"fluent-dom": "../node_modules/@fluent/dom/esm/index.js",
|
||||||
"cached-iterable": "../node_modules/cached-iterable/src/index.mjs",
|
"cached-iterable": "../node_modules/cached-iterable/src/index.mjs",
|
||||||
|
|
||||||
|
"display-cmap_reader_factory": "../src/display/cmap_reader_factory.js",
|
||||||
|
"display-standard_fontdata_factory": "../src/display/standard_fontdata_factory.js",
|
||||||
"display-fetch_stream": "../src/display/fetch_stream.js",
|
"display-fetch_stream": "../src/display/fetch_stream.js",
|
||||||
"display-network": "../src/display/network.js",
|
"display-network": "../src/display/network.js",
|
||||||
"display-node_stream": "../src/display/stubs.js",
|
"display-node_stream": "../src/display/stubs.js",
|
||||||
|
|||||||
@ -62,6 +62,8 @@ See https://github.com/adobe-type-tools/cmap-resources
|
|||||||
"fluent-dom": "../node_modules/@fluent/dom/esm/index.js",
|
"fluent-dom": "../node_modules/@fluent/dom/esm/index.js",
|
||||||
"cached-iterable": "../node_modules/cached-iterable/src/index.mjs",
|
"cached-iterable": "../node_modules/cached-iterable/src/index.mjs",
|
||||||
|
|
||||||
|
"display-cmap_reader_factory": "../src/display/cmap_reader_factory.js",
|
||||||
|
"display-standard_fontdata_factory": "../src/display/standard_fontdata_factory.js",
|
||||||
"display-fetch_stream": "../src/display/fetch_stream.js",
|
"display-fetch_stream": "../src/display/fetch_stream.js",
|
||||||
"display-network": "../src/display/network.js",
|
"display-network": "../src/display/network.js",
|
||||||
"display-node_stream": "../src/display/stubs.js",
|
"display-node_stream": "../src/display/stubs.js",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user