Merge pull request #12154 from jsg2021/issue-8271
[api-minor] Allow loading pdf fonts into another document.
This commit is contained in:
commit
1f8b54b1fe
@ -163,6 +163,9 @@ function setPDFNetworkStreamFactory(pdfNetworkStreamFactory) {
|
||||
* parsed font data from the worker-thread. This may be useful for debugging
|
||||
* purposes (and backwards compatibility), but note that it will lead to
|
||||
* increased memory usage. The default value is `false`.
|
||||
* @property {HTMLDocument} [ownerDocument] - Specify an explicit document
|
||||
* context to create elements with and to load resources, such as fonts,
|
||||
* into. Defaults to the current document.
|
||||
* @property {boolean} [disableRange] - Disable range request loading of PDF
|
||||
* files. When enabled, and if the server supports partial content requests,
|
||||
* then the PDF will be fetched in chunks. The default value is `false`.
|
||||
@ -282,6 +285,9 @@ function getDocument(src) {
|
||||
if (typeof params.disableFontFace !== "boolean") {
|
||||
params.disableFontFace = apiCompatibilityParams.disableFontFace || false;
|
||||
}
|
||||
if (typeof params.ownerDocument === "undefined") {
|
||||
params.ownerDocument = globalThis.document;
|
||||
}
|
||||
|
||||
if (typeof params.disableRange !== "boolean") {
|
||||
params.disableRange = false;
|
||||
@ -976,9 +982,10 @@ class PDFDocumentProxy {
|
||||
* Proxy to a `PDFPage` in the worker thread.
|
||||
*/
|
||||
class PDFPageProxy {
|
||||
constructor(pageIndex, pageInfo, transport, pdfBug = false) {
|
||||
constructor(pageIndex, pageInfo, transport, ownerDocument, pdfBug = false) {
|
||||
this._pageIndex = pageIndex;
|
||||
this._pageInfo = pageInfo;
|
||||
this._ownerDocument = ownerDocument;
|
||||
this._transport = transport;
|
||||
this._stats = pdfBug ? new StatTimer() : null;
|
||||
this._pdfBug = pdfBug;
|
||||
@ -1111,7 +1118,9 @@ class PDFPageProxy {
|
||||
intentState.streamReaderCancelTimeout = null;
|
||||
}
|
||||
|
||||
const canvasFactoryInstance = canvasFactory || new DefaultCanvasFactory();
|
||||
const canvasFactoryInstance =
|
||||
canvasFactory ||
|
||||
new DefaultCanvasFactory({ ownerDocument: this._ownerDocument });
|
||||
const webGLContext = new WebGLContext({
|
||||
enable: enableWebGL,
|
||||
});
|
||||
@ -2028,6 +2037,7 @@ class WorkerTransport {
|
||||
this.fontLoader = new FontLoader({
|
||||
docId: loadingTask.docId,
|
||||
onUnsupportedFeature: this._onUnsupportedFeature.bind(this),
|
||||
ownerDocument: params.ownerDocument,
|
||||
});
|
||||
this._params = params;
|
||||
this.CMapReaderFactory = new params.CMapReaderFactory({
|
||||
@ -2484,6 +2494,7 @@ class WorkerTransport {
|
||||
pageIndex,
|
||||
pageInfo,
|
||||
this,
|
||||
this._params.ownerDocument,
|
||||
this._params.pdfBug
|
||||
);
|
||||
this.pageCache[pageIndex] = page;
|
||||
|
@ -65,11 +65,16 @@ class BaseCanvasFactory {
|
||||
}
|
||||
|
||||
class DOMCanvasFactory extends BaseCanvasFactory {
|
||||
constructor({ ownerDocument = globalThis.document } = {}) {
|
||||
super();
|
||||
this._document = ownerDocument;
|
||||
}
|
||||
|
||||
create(width, height) {
|
||||
if (width <= 0 || height <= 0) {
|
||||
throw new Error("Invalid canvas size");
|
||||
}
|
||||
const canvas = document.createElement("canvas");
|
||||
const canvas = this._document.createElement("canvas");
|
||||
const context = canvas.getContext("2d");
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
@ -25,12 +25,17 @@ import {
|
||||
} from "../shared/util.js";
|
||||
|
||||
class BaseFontLoader {
|
||||
constructor({ docId, onUnsupportedFeature }) {
|
||||
constructor({
|
||||
docId,
|
||||
onUnsupportedFeature,
|
||||
ownerDocument = globalThis.document,
|
||||
}) {
|
||||
if (this.constructor === BaseFontLoader) {
|
||||
unreachable("Cannot initialize BaseFontLoader.");
|
||||
}
|
||||
this.docId = docId;
|
||||
this._onUnsupportedFeature = onUnsupportedFeature;
|
||||
this._document = ownerDocument;
|
||||
|
||||
this.nativeFontFaces = [];
|
||||
this.styleElement = null;
|
||||
@ -38,15 +43,15 @@ class BaseFontLoader {
|
||||
|
||||
addNativeFontFace(nativeFontFace) {
|
||||
this.nativeFontFaces.push(nativeFontFace);
|
||||
document.fonts.add(nativeFontFace);
|
||||
this._document.fonts.add(nativeFontFace);
|
||||
}
|
||||
|
||||
insertRule(rule) {
|
||||
let styleElement = this.styleElement;
|
||||
if (!styleElement) {
|
||||
styleElement = this.styleElement = document.createElement("style");
|
||||
styleElement = this.styleElement = this._document.createElement("style");
|
||||
styleElement.id = `PDFJS_FONT_STYLE_TAG_${this.docId}`;
|
||||
document.documentElement
|
||||
this._document.documentElement
|
||||
.getElementsByTagName("head")[0]
|
||||
.appendChild(styleElement);
|
||||
}
|
||||
@ -56,8 +61,8 @@ class BaseFontLoader {
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.nativeFontFaces.forEach(function (nativeFontFace) {
|
||||
document.fonts.delete(nativeFontFace);
|
||||
this.nativeFontFaces.forEach(nativeFontFace => {
|
||||
this._document.fonts.delete(nativeFontFace);
|
||||
});
|
||||
this.nativeFontFaces.length = 0;
|
||||
|
||||
@ -116,7 +121,8 @@ class BaseFontLoader {
|
||||
}
|
||||
|
||||
get isFontLoadingAPISupported() {
|
||||
const supported = typeof document !== "undefined" && !!document.fonts;
|
||||
const supported =
|
||||
typeof this._document !== "undefined" && !!this._document.fonts;
|
||||
return shadow(this, "isFontLoadingAPISupported", supported);
|
||||
}
|
||||
|
||||
@ -146,8 +152,8 @@ if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
|
||||
// PDFJSDev.test('CHROME || GENERIC')
|
||||
|
||||
FontLoader = class GenericFontLoader extends BaseFontLoader {
|
||||
constructor(docId) {
|
||||
super(docId);
|
||||
constructor(params) {
|
||||
super(params);
|
||||
this.loadingContext = {
|
||||
requests: [],
|
||||
nextRequestId: 0,
|
||||
@ -254,7 +260,7 @@ if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
|
||||
let i, ii;
|
||||
|
||||
// The temporary canvas is used to determine if fonts are loaded.
|
||||
const canvas = document.createElement("canvas");
|
||||
const canvas = this._document.createElement("canvas");
|
||||
canvas.width = 1;
|
||||
canvas.height = 1;
|
||||
const ctx = canvas.getContext("2d");
|
||||
@ -316,22 +322,22 @@ if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
|
||||
}
|
||||
names.push(loadTestFontId);
|
||||
|
||||
const div = document.createElement("div");
|
||||
const div = this._document.createElement("div");
|
||||
div.style.visibility = "hidden";
|
||||
div.style.width = div.style.height = "10px";
|
||||
div.style.position = "absolute";
|
||||
div.style.top = div.style.left = "0px";
|
||||
|
||||
for (i = 0, ii = names.length; i < ii; ++i) {
|
||||
const span = document.createElement("span");
|
||||
const span = this._document.createElement("span");
|
||||
span.textContent = "Hi";
|
||||
span.style.fontFamily = names[i];
|
||||
div.appendChild(span);
|
||||
}
|
||||
document.body.appendChild(div);
|
||||
this._document.body.appendChild(div);
|
||||
|
||||
isFontReady(loadTestFontId, function () {
|
||||
document.body.removeChild(div);
|
||||
isFontReady(loadTestFontId, () => {
|
||||
this._document.body.removeChild(div);
|
||||
request.complete();
|
||||
});
|
||||
/** Hack end */
|
||||
|
@ -521,6 +521,7 @@ var renderTextLayer = (function renderTextLayerClosure() {
|
||||
this._textContent = textContent;
|
||||
this._textContentStream = textContentStream;
|
||||
this._container = container;
|
||||
this._document = container.ownerDocument;
|
||||
this._viewport = viewport;
|
||||
this._textDivs = textDivs || [];
|
||||
this._textContentItemsStr = textContentItemsStr || [];
|
||||
@ -625,7 +626,7 @@ var renderTextLayer = (function renderTextLayerClosure() {
|
||||
let styleCache = Object.create(null);
|
||||
|
||||
// The temporary canvas is used to measure text length in the DOM.
|
||||
const canvas = document.createElement("canvas");
|
||||
const canvas = this._document.createElement("canvas");
|
||||
if (
|
||||
typeof PDFJSDev === "undefined" ||
|
||||
PDFJSDev.test("MOZCENTRAL || GENERIC")
|
||||
|
@ -107,3 +107,127 @@ describe("custom canvas rendering", function () {
|
||||
.catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe("custom ownerDocument", function () {
|
||||
const FontFace = globalThis.FontFace;
|
||||
|
||||
const checkFont = font => /g_d\d+_f1/.test(font.family);
|
||||
const checkFontFaceRule = rule =>
|
||||
/^@font-face {font-family:"g_d\d+_f1";src:/.test(rule);
|
||||
|
||||
beforeEach(() => {
|
||||
globalThis.FontFace = function MockFontFace(name) {
|
||||
this.family = name;
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
globalThis.FontFace = FontFace;
|
||||
});
|
||||
|
||||
function getMocks() {
|
||||
const elements = [];
|
||||
const createElement = name => {
|
||||
let element =
|
||||
typeof document !== "undefined" && document.createElement(name);
|
||||
if (name === "style") {
|
||||
element = {
|
||||
tagName: name,
|
||||
sheet: {
|
||||
cssRules: [],
|
||||
insertRule(rule) {
|
||||
this.cssRules.push(rule);
|
||||
},
|
||||
},
|
||||
};
|
||||
Object.assign(element, {
|
||||
remove() {
|
||||
this.remove.called = true;
|
||||
},
|
||||
});
|
||||
}
|
||||
elements.push(element);
|
||||
return element;
|
||||
};
|
||||
const ownerDocument = {
|
||||
fonts: new Set(),
|
||||
createElement,
|
||||
documentElement: {
|
||||
getElementsByTagName: () => [{ appendChild: () => {} }],
|
||||
},
|
||||
};
|
||||
|
||||
const CanvasFactory = isNodeJS
|
||||
? new NodeCanvasFactory()
|
||||
: new DOMCanvasFactory({ ownerDocument });
|
||||
return {
|
||||
elements,
|
||||
ownerDocument,
|
||||
CanvasFactory,
|
||||
};
|
||||
}
|
||||
|
||||
it("should use given document for loading fonts (with Font Loading API)", async function () {
|
||||
const { ownerDocument, elements, CanvasFactory } = getMocks();
|
||||
const getDocumentParams = buildGetDocumentParams(
|
||||
"TrueType_without_cmap.pdf",
|
||||
{
|
||||
disableFontFace: false,
|
||||
ownerDocument,
|
||||
}
|
||||
);
|
||||
|
||||
const loadingTask = getDocument(getDocumentParams);
|
||||
const doc = await loadingTask.promise;
|
||||
const page = await doc.getPage(1);
|
||||
|
||||
const viewport = page.getViewport({ scale: 1 });
|
||||
const canvasAndCtx = CanvasFactory.create(viewport.width, viewport.height);
|
||||
|
||||
await page.render({
|
||||
canvasContext: canvasAndCtx.context,
|
||||
viewport,
|
||||
}).promise;
|
||||
|
||||
const style = elements.find(element => element.tagName === "style");
|
||||
expect(style).toBeFalsy();
|
||||
expect(ownerDocument.fonts.size).toBeGreaterThanOrEqual(1);
|
||||
expect(Array.from(ownerDocument.fonts).find(checkFont)).toBeTruthy();
|
||||
await doc.destroy();
|
||||
await loadingTask.destroy();
|
||||
CanvasFactory.destroy(canvasAndCtx);
|
||||
expect(ownerDocument.fonts.size).toBe(0);
|
||||
});
|
||||
|
||||
it("should use given document for loading fonts (with CSS rules)", async function () {
|
||||
const { ownerDocument, elements, CanvasFactory } = getMocks();
|
||||
ownerDocument.fonts = null;
|
||||
const getDocumentParams = buildGetDocumentParams(
|
||||
"TrueType_without_cmap.pdf",
|
||||
{
|
||||
disableFontFace: false,
|
||||
ownerDocument,
|
||||
}
|
||||
);
|
||||
|
||||
const loadingTask = getDocument(getDocumentParams);
|
||||
const doc = await loadingTask.promise;
|
||||
const page = await doc.getPage(1);
|
||||
|
||||
const viewport = page.getViewport({ scale: 1 });
|
||||
const canvasAndCtx = CanvasFactory.create(viewport.width, viewport.height);
|
||||
|
||||
await page.render({
|
||||
canvasContext: canvasAndCtx.context,
|
||||
viewport,
|
||||
}).promise;
|
||||
|
||||
const style = elements.find(element => element.tagName === "style");
|
||||
expect(style.sheet.cssRules.length).toBeGreaterThanOrEqual(1);
|
||||
expect(style.sheet.cssRules.find(checkFontFaceRule)).toBeTruthy();
|
||||
await doc.destroy();
|
||||
await loadingTask.destroy();
|
||||
CanvasFactory.destroy(canvasAndCtx);
|
||||
expect(style.remove.called).toBe(true);
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user