Skip to content

Commit a26aed7

Browse files
committed
[api-minor] Use the NodeCanvasFactory/NodeCMapReaderFactory classes as defaults in Node.js environments (issue 11900)
This moves, and slightly simplifies, code that's currently residing in the unit-test utils into the actual library, such that it's bundled with `GENERIC`-builds and used in e.g. the API-code. As an added bonus, this also brings out-of-the-box support for CMaps in e.g. the Node.js examples.
1 parent 9993397 commit a26aed7

File tree

10 files changed

+159
-99
lines changed

10 files changed

+159
-99
lines changed

examples/node/pdf2png/pdf2png.js

+11-5
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,20 @@ 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 = process.argv[2] || "../../web/compressed.tracemonkey-pldi-09.pdf";
59+
var data = new Uint8Array(fs.readFileSync(pdfPath));
5860

5961
// Load the PDF file.
60-
var loadingTask = pdfjsLib.getDocument(rawData);
62+
var loadingTask = pdfjsLib.getDocument({
63+
data: data,
64+
cMapUrl: CMAP_URL,
65+
cMapPacked: CMAP_PACKED,
66+
});
6167
loadingTask.promise
6268
.then(function (pdfDocument) {
6369
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

+37-13
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,12 +64,37 @@ 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
}
7591

92+
async fetch({ name }) {
93+
unreachable("Abstract method `fetch` called.");
94+
}
95+
}
96+
97+
class DOMCMapReaderFactory extends BaseCMapReaderFactory {
7698
async fetch({ name }) {
7799
if (!this.baseUrl) {
78100
throw new Error(
@@ -609,7 +631,9 @@ export {
609631
getFilenameFromUrl,
610632
LinkTarget,
611633
DEFAULT_LINK_REL,
634+
BaseCanvasFactory,
612635
DOMCanvasFactory,
636+
BaseCMapReaderFactory,
613637
DOMCMapReaderFactory,
614638
DOMSVGFactory,
615639
StatTimer,

src/display/node_utils.js

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/* Copyright 2015 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 { CMapCompressionType } from "../shared/util.js";
20+
import { isNodeJS } from "../shared/is_node.js";
21+
22+
let NodeCanvasFactory = class {
23+
constructor() {
24+
throw new Error("Not implemented: NodeCanvasFactory");
25+
}
26+
};
27+
28+
let NodeCMapReaderFactory = class {
29+
constructor() {
30+
throw new Error("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+
async fetch({ name }) {
51+
if (!this.baseUrl) {
52+
throw new Error(
53+
'The CMap "baseUrl" parameter must be specified, ensure that ' +
54+
'the "cMapUrl" and "cMapPacked" API parameters are provided.'
55+
);
56+
}
57+
if (!name) {
58+
throw new Error("CMap name must be specified.");
59+
}
60+
const url = this.baseUrl + name + (this.isCompressed ? ".bcmap" : "");
61+
const compressionType = this.isCompressed
62+
? CMapCompressionType.BINARY
63+
: CMapCompressionType.NONE;
64+
65+
return new Promise((resolve, reject) => {
66+
const fs = __non_webpack_require__("fs");
67+
fs.readFile(url, (error, data) => {
68+
if (error || !data) {
69+
reject(new Error(error));
70+
return;
71+
}
72+
resolve({ cMapData: new Uint8Array(data), compressionType });
73+
});
74+
}).catch(reason => {
75+
throw new Error(
76+
`Unable to load ${this.isCompressed ? "binary " : ""}` +
77+
`CMap at: ${url}`
78+
);
79+
});
80+
}
81+
};
82+
}
83+
84+
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)