XFA - Add support for reftests

This commit is contained in:
Calixte Denizet 2021-06-07 15:20:29 +02:00
parent e7dc822e74
commit 63caa101f8
6 changed files with 185 additions and 77 deletions

View File

@ -2272,6 +2272,7 @@ class WorkerTransport {
docId: loadingTask.docId, docId: loadingTask.docId,
onUnsupportedFeature: this._onUnsupportedFeature.bind(this), onUnsupportedFeature: this._onUnsupportedFeature.bind(this),
ownerDocument: params.ownerDocument, ownerDocument: params.ownerDocument,
styleElement: params.styleElement,
}); });
this._params = params; this._params = params;
this.CMapReaderFactory = new params.CMapReaderFactory({ this.CMapReaderFactory = new params.CMapReaderFactory({

View File

@ -29,6 +29,8 @@ class BaseFontLoader {
docId, docId,
onUnsupportedFeature, onUnsupportedFeature,
ownerDocument = globalThis.document, ownerDocument = globalThis.document,
// For testing only.
styleElement = null,
}) { }) {
if (this.constructor === BaseFontLoader) { if (this.constructor === BaseFontLoader) {
unreachable("Cannot initialize BaseFontLoader."); unreachable("Cannot initialize BaseFontLoader.");
@ -38,7 +40,10 @@ class BaseFontLoader {
this._document = ownerDocument; this._document = ownerDocument;
this.nativeFontFaces = []; this.nativeFontFaces = [];
this.styleElement = null; this.styleElement =
typeof PDFJSDev === "undefined" || PDFJSDev.test("!PRODUCTION || TESTING")
? styleElement
: null;
} }
addNativeFontFace(nativeFontFace) { addNativeFontFace(nativeFontFace) {
@ -55,7 +60,6 @@ class BaseFontLoader {
.getElementsByTagName("head")[0] .getElementsByTagName("head")[0]
.appendChild(styleElement); .appendChild(styleElement);
} }
const styleSheet = styleElement.sheet; const styleSheet = styleElement.sheet;
styleSheet.insertRule(rule, styleSheet.cssRules.length); styleSheet.insertRule(rule, styleSheet.cssRules.length);
} }
@ -121,7 +125,18 @@ class BaseFontLoader {
} }
get isFontLoadingAPISupported() { get isFontLoadingAPISupported() {
return shadow(this, "isFontLoadingAPISupported", !!this._document?.fonts); const hasFonts = !!this._document?.fonts;
if (
typeof PDFJSDev === "undefined" ||
PDFJSDev.test("!PRODUCTION || TESTING")
) {
return shadow(
this,
"isFontLoadingAPISupported",
hasFonts && !this.styleElement
);
}
return shadow(this, "isFontLoadingAPISupported", hasFonts);
} }
// eslint-disable-next-line getter-return // eslint-disable-next-line getter-return

View File

@ -25,27 +25,60 @@ const STANDARD_FONT_DATA_URL = "/build/generic/web/standard_fonts/";
const IMAGE_RESOURCES_PATH = "/web/images/"; const IMAGE_RESOURCES_PATH = "/web/images/";
const WORKER_SRC = "../build/generic/build/pdf.worker.js"; const WORKER_SRC = "../build/generic/build/pdf.worker.js";
const RENDER_TASK_ON_CONTINUE_DELAY = 5; // ms const RENDER_TASK_ON_CONTINUE_DELAY = 5; // ms
const SVG_NS = "http://www.w3.org/2000/svg";
function loadStyles(styles) {
styles = Object.values(styles);
if (styles.every(style => style.promise)) {
return Promise.all(styles.map(style => style.promise));
}
for (const style of styles) {
style.promise = new Promise(function (resolve, reject) {
const xhr = new XMLHttpRequest();
xhr.open("GET", style.file);
xhr.onload = function () {
resolve(xhr.responseText);
};
xhr.onerror = function (e) {
reject(new Error(`Error fetching style (${style.file}): ${e}`));
};
xhr.send(null);
});
}
return Promise.all(styles.map(style => style.promise));
}
function writeSVG(svgElement, ctx, resolve, reject) {
// We need to have UTF-8 encoded XML.
const svg_xml = unescape(
encodeURIComponent(new XMLSerializer().serializeToString(svgElement))
);
const img = new Image();
img.src = "data:image/svg+xml;base64," + btoa(svg_xml);
img.onload = function () {
ctx.drawImage(img, 0, 0);
resolve();
};
img.onerror = function (e) {
reject(new Error("Error rasterizing text layer " + e));
};
}
/** /**
* @class * @class
*/ */
var rasterizeTextLayer = (function rasterizeTextLayerClosure() { var rasterizeTextLayer = (function rasterizeTextLayerClosure() {
var SVG_NS = "http://www.w3.org/2000/svg"; const styles = {
common: {
file: "./text_layer_test.css",
promise: null,
},
};
var textLayerStylePromise = null;
function getTextLayerStyle() { function getTextLayerStyle() {
if (textLayerStylePromise) { return loadStyles(styles);
return textLayerStylePromise;
}
textLayerStylePromise = new Promise(function (resolve) {
var xhr = new XMLHttpRequest();
xhr.open("GET", "./text_layer_test.css");
xhr.onload = function () {
resolve(xhr.responseText);
};
xhr.send(null);
});
return textLayerStylePromise;
} }
// eslint-disable-next-line no-shadow // eslint-disable-next-line no-shadow
@ -92,19 +125,7 @@ var rasterizeTextLayer = (function rasterizeTextLayerClosure() {
task.expandTextDivs(true); task.expandTextDivs(true);
svg.appendChild(foreignObject); svg.appendChild(foreignObject);
// We need to have UTF-8 encoded XML. writeSVG(svg, ctx, resolve, reject);
var svg_xml = unescape(
encodeURIComponent(new XMLSerializer().serializeToString(svg))
);
var img = new Image();
img.src = "data:image/svg+xml;base64," + btoa(svg_xml);
img.onload = function () {
ctx.drawImage(img, 0, 0);
resolve();
};
img.onerror = function (e) {
reject(new Error("Error rasterizing text layer " + e));
};
}) })
.catch(reason => { .catch(reason => {
reject(new Error(`rasterizeTextLayer: "${reason?.message}".`)); reject(new Error(`rasterizeTextLayer: "${reason?.message}".`));
@ -119,8 +140,6 @@ var rasterizeTextLayer = (function rasterizeTextLayerClosure() {
* @class * @class
*/ */
var rasterizeAnnotationLayer = (function rasterizeAnnotationLayerClosure() { var rasterizeAnnotationLayer = (function rasterizeAnnotationLayerClosure() {
const SVG_NS = "http://www.w3.org/2000/svg";
/** /**
* For the reference tests, the entire annotation layer must be visible. To * For the reference tests, the entire annotation layer must be visible. To
* achieve this, we load the common styles as used by the viewer and extend * achieve this, we load the common styles as used by the viewer and extend
@ -142,27 +161,7 @@ var rasterizeAnnotationLayer = (function rasterizeAnnotationLayerClosure() {
}; };
function getAnnotationLayerStyle() { function getAnnotationLayerStyle() {
// Use the cached promises if they are available. return loadStyles(styles);
if (styles.common.promise && styles.overrides.promise) {
return Promise.all([styles.common.promise, styles.overrides.promise]);
}
// Load the style files and cache the results.
for (const key in styles) {
styles[key].promise = new Promise(function (resolve, reject) {
const xhr = new XMLHttpRequest();
xhr.open("GET", styles[key].file);
xhr.onload = function () {
resolve(xhr.responseText);
};
xhr.onerror = function (e) {
reject(new Error("Error fetching annotation style " + e));
};
xhr.send(null);
});
}
return Promise.all([styles.common.promise, styles.overrides.promise]);
} }
function inlineAnnotationImages(images) { function inlineAnnotationImages(images) {
@ -256,19 +255,7 @@ var rasterizeAnnotationLayer = (function rasterizeAnnotationLayerClosure() {
foreignObject.appendChild(div); foreignObject.appendChild(div);
svg.appendChild(foreignObject); svg.appendChild(foreignObject);
// We need to have UTF-8 encoded XML. writeSVG(svg, ctx, resolve, reject);
var svg_xml = unescape(
encodeURIComponent(new XMLSerializer().serializeToString(svg))
);
var img = new Image();
img.src = "data:image/svg+xml;base64," + btoa(svg_xml);
img.onload = function () {
ctx.drawImage(img, 0, 0);
resolve();
};
img.onerror = function (e) {
reject(new Error("Error rasterizing annotation layer " + e));
};
}) })
.catch(reason => { .catch(reason => {
reject(new Error(`rasterizeAnnotationLayer: "${reason?.message}".`)); reject(new Error(`rasterizeAnnotationLayer: "${reason?.message}".`));
@ -279,6 +266,65 @@ var rasterizeAnnotationLayer = (function rasterizeAnnotationLayerClosure() {
return rasterizeAnnotationLayer; return rasterizeAnnotationLayer;
})(); })();
/**
* @class
*/
var rasterizeXfaLayer = (function rasterizeXfaLayerClosure() {
const styles = {
common: {
file: "../web/xfa_layer_builder.css",
promise: null,
},
};
function getXfaLayerStyle() {
return loadStyles(styles);
}
// eslint-disable-next-line no-shadow
function rasterizeXfaLayer(ctx, viewport, xfa, fontRules) {
return new Promise(function (resolve, reject) {
// Building SVG with size of the viewport.
const svg = document.createElementNS(SVG_NS, "svg:svg");
svg.setAttribute("width", viewport.width + "px");
svg.setAttribute("height", viewport.height + "px");
const foreignObject = document.createElementNS(
SVG_NS,
"svg:foreignObject"
);
foreignObject.setAttribute("x", "0");
foreignObject.setAttribute("y", "0");
foreignObject.setAttribute("width", viewport.width + "px");
foreignObject.setAttribute("height", viewport.height + "px");
const style = document.createElement("style");
const stylePromise = getXfaLayerStyle();
foreignObject.appendChild(style);
const div = document.createElement("div");
foreignObject.appendChild(div);
stylePromise
.then(async cssRules => {
style.textContent = fontRules + "\n" + cssRules;
pdfjsLib.XfaLayer.render({
xfa,
div,
viewport: viewport.clone({ dontFlip: true }),
});
svg.appendChild(foreignObject);
writeSVG(svg, ctx, resolve, reject);
})
.catch(reason => {
reject(new Error(`rasterizeXfaLayer: "${reason?.message}".`));
});
});
}
return rasterizeXfaLayer;
})();
/** /**
* @typedef {Object} DriverOptions * @typedef {Object} DriverOptions
* @property {HTMLSpanElement} inflight - Field displaying the number of * @property {HTMLSpanElement} inflight - Field displaying the number of
@ -392,6 +438,7 @@ var Driver = (function DriverClosure() {
task.round = 0; task.round = 0;
task.pageNum = task.firstPage || 1; task.pageNum = task.firstPage || 1;
task.stats = { times: [] }; task.stats = { times: [] };
task.enableXfa = task.enableXfa === true;
// Support *linked* test-cases for the other suites, e.g. unit- and // Support *linked* test-cases for the other suites, e.g. unit- and
// integration-tests, without needing to run them as reference-tests. // integration-tests, without needing to run them as reference-tests.
@ -411,6 +458,17 @@ var Driver = (function DriverClosure() {
const absoluteUrl = new URL(task.file, window.location).href; const absoluteUrl = new URL(task.file, window.location).href;
try { try {
let xfaStyleElement = null;
if (task.enableXfa) {
// Need to get the font definitions to inject them in the SVG.
// So we create this element and those definitions will be
// appended in font_loader.js.
xfaStyleElement = document.createElement("style");
document.documentElement
.getElementsByTagName("head")[0]
.appendChild(xfaStyleElement);
}
const loadingTask = pdfjsLib.getDocument({ const loadingTask = pdfjsLib.getDocument({
url: absoluteUrl, url: absoluteUrl,
password: task.password, password: task.password,
@ -422,9 +480,18 @@ var Driver = (function DriverClosure() {
pdfBug: true, pdfBug: true,
useSystemFonts: task.useSystemFonts, useSystemFonts: task.useSystemFonts,
useWorkerFetch: task.useWorkerFetch, useWorkerFetch: task.useWorkerFetch,
enableXfa: task.enableXfa,
styleElement: xfaStyleElement,
}); });
loadingTask.promise.then( loadingTask.promise.then(
doc => { doc => {
if (task.enableXfa) {
task.fontRules = "";
for (const rule of xfaStyleElement.sheet.cssRules) {
task.fontRules += rule.cssText + "\n";
}
}
task.pdfDoc = doc; task.pdfDoc = doc;
task.optionalContentConfigPromise = task.optionalContentConfigPromise =
doc.getOptionalContentConfig(); doc.getOptionalContentConfig();
@ -552,7 +619,8 @@ var Driver = (function DriverClosure() {
// Initialize various `eq` test subtypes, see comment below. // Initialize various `eq` test subtypes, see comment below.
var renderAnnotations = false, var renderAnnotations = false,
renderForms = false, renderForms = false,
renderPrint = false; renderPrint = false,
renderXfa = false;
var textLayerCanvas, annotationLayerCanvas; var textLayerCanvas, annotationLayerCanvas;
var initPromise; var initPromise;
@ -594,9 +662,10 @@ var Driver = (function DriverClosure() {
renderAnnotations = !!task.annotations; renderAnnotations = !!task.annotations;
renderForms = !!task.forms; renderForms = !!task.forms;
renderPrint = !!task.print; renderPrint = !!task.print;
renderXfa = !!task.enableXfa;
// Render the annotation layer if necessary. // Render the annotation layer if necessary.
if (renderAnnotations || renderForms) { if (renderAnnotations || renderForms || renderXfa) {
// Create a dummy canvas for the drawing operations. // Create a dummy canvas for the drawing operations.
annotationLayerCanvas = self.annotationLayerCanvas; annotationLayerCanvas = self.annotationLayerCanvas;
if (!annotationLayerCanvas) { if (!annotationLayerCanvas) {
@ -614,19 +683,31 @@ var Driver = (function DriverClosure() {
annotationLayerCanvas.height annotationLayerCanvas.height
); );
// The annotation builder will draw its content on the canvas. if (!renderXfa) {
initPromise = page // The annotation builder will draw its content
.getAnnotations({ intent: "display" }) // on the canvas.
.then(function (annotations) { initPromise = page
return rasterizeAnnotationLayer( .getAnnotations({ intent: "display" })
.then(function (annotations) {
return rasterizeAnnotationLayer(
annotationLayerContext,
viewport,
annotations,
page,
IMAGE_RESOURCES_PATH,
renderForms
);
});
} else {
initPromise = page.getXfa().then(function (xfa) {
return rasterizeXfaLayer(
annotationLayerContext, annotationLayerContext,
viewport, viewport,
annotations, xfa,
page, task.fontRules
IMAGE_RESOURCES_PATH,
renderForms
); );
}); });
}
} else { } else {
annotationLayerCanvas = null; annotationLayerCanvas = null;
initPromise = Promise.resolve(); initPromise = Promise.resolve();

1
test/pdfs/hsbc.pdf.link Normal file
View File

@ -0,0 +1 @@
https://web.archive.org/web/20210607145115/https://www.hsbc.fr/content/dam/hsbc/fr/docs/pib/Contestation-Transaction-Carte-Bancaire.pdf

View File

@ -2725,6 +2725,14 @@
"rounds": 1, "rounds": 1,
"type": "eq" "type": "eq"
}, },
{ "id": "hsbc",
"file": "pdfs/hsbc.pdf",
"md5": "34a63a9ed9cdf3790562a3dfd1703e48",
"rounds": 1,
"link": true,
"enableXfa": true,
"type": "eq"
},
{ "id": "issue7580-text", { "id": "issue7580-text",
"file": "pdfs/issue7580.pdf", "file": "pdfs/issue7580.pdf",
"md5": "44dd5a9b4373fcab9890cf567722a766", "md5": "44dd5a9b4373fcab9890cf567722a766",

View File

@ -33,6 +33,8 @@
vertical-align: inherit; vertical-align: inherit;
box-sizing: border-box; box-sizing: border-box;
background: transparent; background: transparent;
padding: 0;
margin: 0;
} }
.xfaLayer a { .xfaLayer a {