Skip to content

Commit 1f71755

Browse files
Merge pull request #12039 from Snuffleupagus/Node-utils
[api-minor] Use the `NodeCanvasFactory`/`NodeCMapReaderFactory` classes as defaults in Node.js environments (issue 11900)
2 parents fe3df49 + 4a7e298 commit 1f71755

File tree

10 files changed

+164
-123
lines changed

10 files changed

+164
-123
lines changed

examples/node/pdf2png/pdf2png.js

+12-5
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,21 @@ NodeCanvasFactory.prototype = {
5050

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

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

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

5962
// Load the PDF file.
60-
var loadingTask = pdfjsLib.getDocument(rawData);
63+
var loadingTask = pdfjsLib.getDocument({
64+
data: data,
65+
cMapUrl: CMAP_URL,
66+
cMapPacked: CMAP_PACKED,
67+
});
6168
loadingTask.promise
6269
.then(function (pdfDocument) {
6370
console.log("# PDF document loaded.");

examples/node/pdf2svg.js

+6
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ require("./domstubs.js").setStubs(global);
1616
// Run `gulp dist-install` to generate 'pdfjs-dist' npm package files.
1717
var pdfjsLib = require("pdfjs-dist/es5/build/pdf.js");
1818

19+
// Some PDFs need external cmaps.
20+
var CMAP_URL = "../../node_modules/pdfjs-dist/cmaps/";
21+
var CMAP_PACKED = true;
22+
1923
// Loading file from file system into typed array
2024
var pdfPath = process.argv[2] || "../../web/compressed.tracemonkey-pldi-09.pdf";
2125
var data = new Uint8Array(fs.readFileSync(pdfPath));
@@ -86,6 +90,8 @@ function writeSvgToFile(svgElement, filePath) {
8690
// callback.
8791
var loadingTask = pdfjsLib.getDocument({
8892
data: data,
93+
cMapUrl: CMAP_URL,
94+
cMapPacked: CMAP_PACKED,
8995
fontExtraProperties: true,
9096
});
9197
loadingTask.promise

gulpfile.js

+1
Original file line numberDiff line numberDiff line change
@@ -1617,6 +1617,7 @@ gulp.task(
16171617
bugs: DIST_BUGS_URL,
16181618
license: DIST_LICENSE,
16191619
browser: {
1620+
canvas: false,
16201621
fs: false,
16211622
http: false,
16221623
https: false,

src/display/api.js

+15-4
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import {
4747
StatTimer,
4848
} from "./display_utils.js";
4949
import { FontFaceObject, FontLoader } from "./font_loader.js";
50+
import { NodeCanvasFactory, NodeCMapReaderFactory } from "./node_utils.js";
5051
import { apiCompatibilityParams } from "./api_compatibility.js";
5152
import { CanvasGraphics } from "./canvas.js";
5253
import { GlobalWorkerOptions } from "./worker_options.js";
@@ -59,6 +60,15 @@ import { WebGLContext } from "./webgl.js";
5960
const DEFAULT_RANGE_CHUNK_SIZE = 65536; // 2^16 = 65536
6061
const RENDERING_CANCELLED_TIMEOUT = 100; // ms
6162

63+
const DefaultCanvasFactory =
64+
(typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) && isNodeJS
65+
? NodeCanvasFactory
66+
: DOMCanvasFactory;
67+
const DefaultCMapReaderFactory =
68+
(typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) && isNodeJS
69+
? NodeCMapReaderFactory
70+
: DOMCMapReaderFactory;
71+
6272
/**
6373
* @typedef {function} IPDFStreamFactory
6474
* @param {DocumentInitParameters} params - The document initialization
@@ -242,7 +252,8 @@ function getDocument(src) {
242252
}
243253

244254
params.rangeChunkSize = params.rangeChunkSize || DEFAULT_RANGE_CHUNK_SIZE;
245-
params.CMapReaderFactory = params.CMapReaderFactory || DOMCMapReaderFactory;
255+
params.CMapReaderFactory =
256+
params.CMapReaderFactory || DefaultCMapReaderFactory;
246257
params.ignoreErrors = params.stopAtErrors !== true;
247258
params.fontExtraProperties = params.fontExtraProperties === true;
248259
params.pdfBug = params.pdfBug === true;
@@ -863,9 +874,9 @@ class PDFDocumentProxy {
863874
* just before viewport transform.
864875
* @property {Object} [imageLayer] - An object that has beginLayout,
865876
* endLayout and appendImage functions.
866-
* @property {Object} [canvasFactory] - The factory that will be used
877+
* @property {Object} [canvasFactory] - The factory instance that will be used
867878
* when creating canvases. The default value is
868-
* {DOMCanvasFactory}.
879+
* {new DOMCanvasFactory()}.
869880
* @property {Object} [background] - Background to use for the canvas.
870881
* Can use any valid canvas.fillStyle: A DOMString parsed as
871882
* CSS <color> value, a CanvasGradient object (a linear or
@@ -1015,7 +1026,7 @@ class PDFPageProxy {
10151026
intentState.streamReaderCancelTimeout = null;
10161027
}
10171028

1018-
const canvasFactoryInstance = canvasFactory || new DOMCanvasFactory();
1029+
const canvasFactoryInstance = canvasFactory || new DefaultCanvasFactory();
10191030
const webGLContext = new WebGLContext({
10201031
enable: enableWebGL,
10211032
});

src/display/display_utils.js

+60-37
Original file line numberDiff line numberDiff line change
@@ -21,26 +21,23 @@ import {
2121
isString,
2222
removeNullCharacters,
2323
stringToBytes,
24+
unreachable,
2425
Util,
2526
warn,
2627
} from "../shared/util.js";
2728

2829
const DEFAULT_LINK_REL = "noopener noreferrer nofollow";
2930
const SVG_NS = "http://www.w3.org/2000/svg";
3031

31-
class DOMCanvasFactory {
32-
create(width, height) {
33-
if (width <= 0 || height <= 0) {
34-
throw new Error("Invalid canvas size");
32+
class BaseCanvasFactory {
33+
constructor() {
34+
if (this.constructor === BaseCanvasFactory) {
35+
unreachable("Cannot initialize BaseCanvasFactory.");
3536
}
36-
const canvas = document.createElement("canvas");
37-
const context = canvas.getContext("2d");
38-
canvas.width = width;
39-
canvas.height = height;
40-
return {
41-
canvas,
42-
context,
43-
};
37+
}
38+
39+
create(width, height) {
40+
unreachable("Abstract method `create` called.");
4441
}
4542

4643
reset(canvasAndContext, width, height) {
@@ -67,8 +64,27 @@ class DOMCanvasFactory {
6764
}
6865
}
6966

70-
class DOMCMapReaderFactory {
67+
class DOMCanvasFactory extends BaseCanvasFactory {
68+
create(width, height) {
69+
if (width <= 0 || height <= 0) {
70+
throw new Error("Invalid canvas size");
71+
}
72+
const canvas = document.createElement("canvas");
73+
const context = canvas.getContext("2d");
74+
canvas.width = width;
75+
canvas.height = height;
76+
return {
77+
canvas,
78+
context,
79+
};
80+
}
81+
}
82+
83+
class BaseCMapReaderFactory {
7184
constructor({ baseUrl = null, isCompressed = false }) {
85+
if (this.constructor === BaseCMapReaderFactory) {
86+
unreachable("Cannot initialize BaseCMapReaderFactory.");
87+
}
7288
this.baseUrl = baseUrl;
7389
this.isCompressed = isCompressed;
7490
}
@@ -88,29 +104,39 @@ class DOMCMapReaderFactory {
88104
? CMapCompressionType.BINARY
89105
: CMapCompressionType.NONE;
90106

107+
return this._fetchData(url, compressionType).catch(reason => {
108+
throw new Error(
109+
`Unable to load ${this.isCompressed ? "binary " : ""}CMap at: ${url}`
110+
);
111+
});
112+
}
113+
114+
/**
115+
* @private
116+
*/
117+
_fetchData(url, compressionType) {
118+
unreachable("Abstract method `_fetchData` called.");
119+
}
120+
}
121+
122+
class DOMCMapReaderFactory extends BaseCMapReaderFactory {
123+
_fetchData(url, compressionType) {
91124
if (
92125
(typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) ||
93126
(isFetchSupported() && isValidFetchUrl(url, document.baseURI))
94127
) {
95-
return fetch(url)
96-
.then(async response => {
97-
if (!response.ok) {
98-
throw new Error(response.statusText);
99-
}
100-
let cMapData;
101-
if (this.isCompressed) {
102-
cMapData = new Uint8Array(await response.arrayBuffer());
103-
} else {
104-
cMapData = stringToBytes(await response.text());
105-
}
106-
return { cMapData, compressionType };
107-
})
108-
.catch(reason => {
109-
throw new Error(
110-
`Unable to load ${this.isCompressed ? "binary " : ""}` +
111-
`CMap at: ${url}`
112-
);
113-
});
128+
return fetch(url).then(async response => {
129+
if (!response.ok) {
130+
throw new Error(response.statusText);
131+
}
132+
let cMapData;
133+
if (this.isCompressed) {
134+
cMapData = new Uint8Array(await response.arrayBuffer());
135+
} else {
136+
cMapData = stringToBytes(await response.text());
137+
}
138+
return { cMapData, compressionType };
139+
});
114140
}
115141

116142
// The Fetch API is not supported.
@@ -141,11 +167,6 @@ class DOMCMapReaderFactory {
141167
};
142168

143169
request.send(null);
144-
}).catch(reason => {
145-
throw new Error(
146-
`Unable to load ${this.isCompressed ? "binary " : ""}` +
147-
`CMap at: ${url}`
148-
);
149170
});
150171
}
151172
}
@@ -609,7 +630,9 @@ export {
609630
getFilenameFromUrl,
610631
LinkTarget,
611632
DEFAULT_LINK_REL,
633+
BaseCanvasFactory,
612634
DOMCanvasFactory,
635+
BaseCMapReaderFactory,
613636
DOMCMapReaderFactory,
614637
DOMSVGFactory,
615638
StatTimer,

src/display/node_utils.js

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/* Copyright 2020 Mozilla Foundation
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
/* globals __non_webpack_require__ */
16+
/* eslint no-var: error */
17+
18+
import { BaseCanvasFactory, BaseCMapReaderFactory } from "./display_utils.js";
19+
import { isNodeJS } from "../shared/is_node.js";
20+
import { unreachable } from "../shared/util.js";
21+
22+
let NodeCanvasFactory = class {
23+
constructor() {
24+
unreachable("Not implemented: NodeCanvasFactory");
25+
}
26+
};
27+
28+
let NodeCMapReaderFactory = class {
29+
constructor() {
30+
unreachable("Not implemented: NodeCMapReaderFactory");
31+
}
32+
};
33+
34+
if ((typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) && isNodeJS) {
35+
NodeCanvasFactory = class extends BaseCanvasFactory {
36+
create(width, height) {
37+
if (width <= 0 || height <= 0) {
38+
throw new Error("Invalid canvas size");
39+
}
40+
const Canvas = __non_webpack_require__("canvas");
41+
const canvas = Canvas.createCanvas(width, height);
42+
return {
43+
canvas,
44+
context: canvas.getContext("2d"),
45+
};
46+
}
47+
};
48+
49+
NodeCMapReaderFactory = class extends BaseCMapReaderFactory {
50+
_fetchData(url, compressionType) {
51+
return new Promise((resolve, reject) => {
52+
const fs = __non_webpack_require__("fs");
53+
fs.readFile(url, (error, data) => {
54+
if (error || !data) {
55+
reject(new Error(error));
56+
return;
57+
}
58+
resolve({ cMapData: new Uint8Array(data), compressionType });
59+
});
60+
});
61+
}
62+
};
63+
}
64+
65+
export { NodeCanvasFactory, NodeCMapReaderFactory };

test/unit/api_spec.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
import {
1717
buildGetDocumentParams,
1818
DOMFileReaderFactory,
19-
NodeCanvasFactory,
2019
NodeFileReaderFactory,
2120
TEST_PDFS_PATH,
2221
} from "./test_utils.js";
@@ -49,6 +48,7 @@ import { GlobalImageCache } from "../../src/core/image_utils.js";
4948
import { GlobalWorkerOptions } from "../../src/display/worker_options.js";
5049
import { isNodeJS } from "../../src/shared/is_node.js";
5150
import { Metadata } from "../../src/display/metadata.js";
51+
import { NodeCanvasFactory } from "../../src/display/node_utils.js";
5252

5353
describe("api", function () {
5454
const basicApiFileName = "basicapi.pdf";

test/unit/cmap_spec.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { CMap, CMapFactory, IdentityCMap } from "../../src/core/cmap.js";
1717
import { DOMCMapReaderFactory } from "../../src/display/display_utils.js";
1818
import { isNodeJS } from "../../src/shared/is_node.js";
1919
import { Name } from "../../src/core/primitives.js";
20-
import { NodeCMapReaderFactory } from "./test_utils.js";
20+
import { NodeCMapReaderFactory } from "../../src/display/node_utils.js";
2121
import { StringStream } from "../../src/core/stream.js";
2222

2323
var cMapUrl = {

test/unit/custom_spec.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@
1313
* limitations under the License.
1414
*/
1515

16-
import { buildGetDocumentParams, NodeCanvasFactory } from "./test_utils.js";
16+
import { buildGetDocumentParams } from "./test_utils.js";
1717
import { DOMCanvasFactory } from "../../src/display/display_utils.js";
1818
import { getDocument } from "../../src/display/api.js";
1919
import { isNodeJS } from "../../src/shared/is_node.js";
20+
import { NodeCanvasFactory } from "../../src/display/node_utils.js";
2021

2122
function getTopLeftPixel(canvasContext) {
2223
const imgData = canvasContext.getImageData(0, 0, 1, 1);

0 commit comments

Comments
 (0)