Skip to content

Commit

Permalink
Merge pull request #12039 from Snuffleupagus/Node-utils
Browse files Browse the repository at this point in the history
[api-minor] Use the `NodeCanvasFactory`/`NodeCMapReaderFactory` classes as defaults in Node.js environments (issue 11900)
  • Loading branch information
timvandermeij authored Jul 3, 2020
2 parents fe3df49 + 4a7e298 commit 1f71755
Show file tree
Hide file tree
Showing 10 changed files with 164 additions and 123 deletions.
17 changes: 12 additions & 5 deletions examples/node/pdf2png/pdf2png.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,21 @@ NodeCanvasFactory.prototype = {

var pdfjsLib = require("pdfjs-dist/es5/build/pdf.js");

// Relative path of the PDF file.
var pdfURL = "../../../web/compressed.tracemonkey-pldi-09.pdf";
// Some PDFs need external cmaps.
var CMAP_URL = "../../../node_modules/pdfjs-dist/cmaps/";
var CMAP_PACKED = true;

// Read the PDF file into a typed array so PDF.js can load it.
var rawData = new Uint8Array(fs.readFileSync(pdfURL));
// Loading file from file system into typed array.
var pdfPath =
process.argv[2] || "../../../web/compressed.tracemonkey-pldi-09.pdf";
var data = new Uint8Array(fs.readFileSync(pdfPath));

// Load the PDF file.
var loadingTask = pdfjsLib.getDocument(rawData);
var loadingTask = pdfjsLib.getDocument({
data: data,
cMapUrl: CMAP_URL,
cMapPacked: CMAP_PACKED,
});
loadingTask.promise
.then(function (pdfDocument) {
console.log("# PDF document loaded.");
Expand Down
6 changes: 6 additions & 0 deletions examples/node/pdf2svg.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ require("./domstubs.js").setStubs(global);
// Run `gulp dist-install` to generate 'pdfjs-dist' npm package files.
var pdfjsLib = require("pdfjs-dist/es5/build/pdf.js");

// Some PDFs need external cmaps.
var CMAP_URL = "../../node_modules/pdfjs-dist/cmaps/";
var CMAP_PACKED = true;

// Loading file from file system into typed array
var pdfPath = process.argv[2] || "../../web/compressed.tracemonkey-pldi-09.pdf";
var data = new Uint8Array(fs.readFileSync(pdfPath));
Expand Down Expand Up @@ -86,6 +90,8 @@ function writeSvgToFile(svgElement, filePath) {
// callback.
var loadingTask = pdfjsLib.getDocument({
data: data,
cMapUrl: CMAP_URL,
cMapPacked: CMAP_PACKED,
fontExtraProperties: true,
});
loadingTask.promise
Expand Down
1 change: 1 addition & 0 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -1617,6 +1617,7 @@ gulp.task(
bugs: DIST_BUGS_URL,
license: DIST_LICENSE,
browser: {
canvas: false,
fs: false,
http: false,
https: false,
Expand Down
19 changes: 15 additions & 4 deletions src/display/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import {
StatTimer,
} from "./display_utils.js";
import { FontFaceObject, FontLoader } from "./font_loader.js";
import { NodeCanvasFactory, NodeCMapReaderFactory } from "./node_utils.js";
import { apiCompatibilityParams } from "./api_compatibility.js";
import { CanvasGraphics } from "./canvas.js";
import { GlobalWorkerOptions } from "./worker_options.js";
Expand All @@ -59,6 +60,15 @@ import { WebGLContext } from "./webgl.js";
const DEFAULT_RANGE_CHUNK_SIZE = 65536; // 2^16 = 65536
const RENDERING_CANCELLED_TIMEOUT = 100; // ms

const DefaultCanvasFactory =
(typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) && isNodeJS
? NodeCanvasFactory
: DOMCanvasFactory;
const DefaultCMapReaderFactory =
(typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) && isNodeJS
? NodeCMapReaderFactory
: DOMCMapReaderFactory;

/**
* @typedef {function} IPDFStreamFactory
* @param {DocumentInitParameters} params - The document initialization
Expand Down Expand Up @@ -242,7 +252,8 @@ function getDocument(src) {
}

params.rangeChunkSize = params.rangeChunkSize || DEFAULT_RANGE_CHUNK_SIZE;
params.CMapReaderFactory = params.CMapReaderFactory || DOMCMapReaderFactory;
params.CMapReaderFactory =
params.CMapReaderFactory || DefaultCMapReaderFactory;
params.ignoreErrors = params.stopAtErrors !== true;
params.fontExtraProperties = params.fontExtraProperties === true;
params.pdfBug = params.pdfBug === true;
Expand Down Expand Up @@ -863,9 +874,9 @@ class PDFDocumentProxy {
* just before viewport transform.
* @property {Object} [imageLayer] - An object that has beginLayout,
* endLayout and appendImage functions.
* @property {Object} [canvasFactory] - The factory that will be used
* @property {Object} [canvasFactory] - The factory instance that will be used
* when creating canvases. The default value is
* {DOMCanvasFactory}.
* {new DOMCanvasFactory()}.
* @property {Object} [background] - Background to use for the canvas.
* Can use any valid canvas.fillStyle: A DOMString parsed as
* CSS <color> value, a CanvasGradient object (a linear or
Expand Down Expand Up @@ -1015,7 +1026,7 @@ class PDFPageProxy {
intentState.streamReaderCancelTimeout = null;
}

const canvasFactoryInstance = canvasFactory || new DOMCanvasFactory();
const canvasFactoryInstance = canvasFactory || new DefaultCanvasFactory();
const webGLContext = new WebGLContext({
enable: enableWebGL,
});
Expand Down
97 changes: 60 additions & 37 deletions src/display/display_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,26 +21,23 @@ import {
isString,
removeNullCharacters,
stringToBytes,
unreachable,
Util,
warn,
} from "../shared/util.js";

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");
class BaseCanvasFactory {
constructor() {
if (this.constructor === BaseCanvasFactory) {
unreachable("Cannot initialize BaseCanvasFactory.");
}
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
canvas.width = width;
canvas.height = height;
return {
canvas,
context,
};
}

create(width, height) {
unreachable("Abstract method `create` called.");
}

reset(canvasAndContext, width, height) {
Expand All @@ -67,8 +64,27 @@ class DOMCanvasFactory {
}
}

class DOMCMapReaderFactory {
class DOMCanvasFactory extends BaseCanvasFactory {
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,
};
}
}

class BaseCMapReaderFactory {
constructor({ baseUrl = null, isCompressed = false }) {
if (this.constructor === BaseCMapReaderFactory) {
unreachable("Cannot initialize BaseCMapReaderFactory.");
}
this.baseUrl = baseUrl;
this.isCompressed = isCompressed;
}
Expand All @@ -88,29 +104,39 @@ class DOMCMapReaderFactory {
? CMapCompressionType.BINARY
: CMapCompressionType.NONE;

return this._fetchData(url, compressionType).catch(reason => {
throw new Error(
`Unable to load ${this.isCompressed ? "binary " : ""}CMap at: ${url}`
);
});
}

/**
* @private
*/
_fetchData(url, compressionType) {
unreachable("Abstract method `_fetchData` called.");
}
}

class DOMCMapReaderFactory extends BaseCMapReaderFactory {
_fetchData(url, compressionType) {
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}`
);
});
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 };
});
}

// The Fetch API is not supported.
Expand Down Expand Up @@ -141,11 +167,6 @@ class DOMCMapReaderFactory {
};

request.send(null);
}).catch(reason => {
throw new Error(
`Unable to load ${this.isCompressed ? "binary " : ""}` +
`CMap at: ${url}`
);
});
}
}
Expand Down Expand Up @@ -609,7 +630,9 @@ export {
getFilenameFromUrl,
LinkTarget,
DEFAULT_LINK_REL,
BaseCanvasFactory,
DOMCanvasFactory,
BaseCMapReaderFactory,
DOMCMapReaderFactory,
DOMSVGFactory,
StatTimer,
Expand Down
65 changes: 65 additions & 0 deletions src/display/node_utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/* Copyright 2020 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* globals __non_webpack_require__ */
/* eslint no-var: error */

import { BaseCanvasFactory, BaseCMapReaderFactory } from "./display_utils.js";
import { isNodeJS } from "../shared/is_node.js";
import { unreachable } from "../shared/util.js";

let NodeCanvasFactory = class {
constructor() {
unreachable("Not implemented: NodeCanvasFactory");
}
};

let NodeCMapReaderFactory = class {
constructor() {
unreachable("Not implemented: NodeCMapReaderFactory");
}
};

if ((typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) && isNodeJS) {
NodeCanvasFactory = class extends BaseCanvasFactory {
create(width, height) {
if (width <= 0 || height <= 0) {
throw new Error("Invalid canvas size");
}
const Canvas = __non_webpack_require__("canvas");
const canvas = Canvas.createCanvas(width, height);
return {
canvas,
context: canvas.getContext("2d"),
};
}
};

NodeCMapReaderFactory = class extends BaseCMapReaderFactory {
_fetchData(url, compressionType) {
return new Promise((resolve, reject) => {
const fs = __non_webpack_require__("fs");
fs.readFile(url, (error, data) => {
if (error || !data) {
reject(new Error(error));
return;
}
resolve({ cMapData: new Uint8Array(data), compressionType });
});
});
}
};
}

export { NodeCanvasFactory, NodeCMapReaderFactory };
2 changes: 1 addition & 1 deletion test/unit/api_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import {
buildGetDocumentParams,
DOMFileReaderFactory,
NodeCanvasFactory,
NodeFileReaderFactory,
TEST_PDFS_PATH,
} from "./test_utils.js";
Expand Down Expand Up @@ -49,6 +48,7 @@ import { GlobalImageCache } from "../../src/core/image_utils.js";
import { GlobalWorkerOptions } from "../../src/display/worker_options.js";
import { isNodeJS } from "../../src/shared/is_node.js";
import { Metadata } from "../../src/display/metadata.js";
import { NodeCanvasFactory } from "../../src/display/node_utils.js";

describe("api", function () {
const basicApiFileName = "basicapi.pdf";
Expand Down
2 changes: 1 addition & 1 deletion test/unit/cmap_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { CMap, CMapFactory, IdentityCMap } from "../../src/core/cmap.js";
import { DOMCMapReaderFactory } from "../../src/display/display_utils.js";
import { isNodeJS } from "../../src/shared/is_node.js";
import { Name } from "../../src/core/primitives.js";
import { NodeCMapReaderFactory } from "./test_utils.js";
import { NodeCMapReaderFactory } from "../../src/display/node_utils.js";
import { StringStream } from "../../src/core/stream.js";

var cMapUrl = {
Expand Down
3 changes: 2 additions & 1 deletion test/unit/custom_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@
* limitations under the License.
*/

import { buildGetDocumentParams, NodeCanvasFactory } from "./test_utils.js";
import { buildGetDocumentParams } from "./test_utils.js";
import { DOMCanvasFactory } from "../../src/display/display_utils.js";
import { getDocument } from "../../src/display/api.js";
import { isNodeJS } from "../../src/shared/is_node.js";
import { NodeCanvasFactory } from "../../src/display/node_utils.js";

function getTopLeftPixel(canvasContext) {
const imgData = canvasContext.getImageData(0, 0, 1, 1);
Expand Down
Loading

0 comments on commit 1f71755

Please sign in to comment.