Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

XFA - Add support for reftests #13506

Merged
merged 1 commit into from
Jun 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/display/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -2272,6 +2272,7 @@ class WorkerTransport {
docId: loadingTask.docId,
onUnsupportedFeature: this._onUnsupportedFeature.bind(this),
ownerDocument: params.ownerDocument,
styleElement: params.styleElement,
});
this._params = params;
this.CMapReaderFactory = new params.CMapReaderFactory({
Expand Down
21 changes: 18 additions & 3 deletions src/display/font_loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ class BaseFontLoader {
docId,
onUnsupportedFeature,
ownerDocument = globalThis.document,
// For testing only.
styleElement = null,
}) {
if (this.constructor === BaseFontLoader) {
unreachable("Cannot initialize BaseFontLoader.");
Expand All @@ -38,7 +40,10 @@ class BaseFontLoader {
this._document = ownerDocument;

this.nativeFontFaces = [];
this.styleElement = null;
this.styleElement =
typeof PDFJSDev === "undefined" || PDFJSDev.test("!PRODUCTION || TESTING")
? styleElement
: null;
}

addNativeFontFace(nativeFontFace) {
Expand All @@ -55,7 +60,6 @@ class BaseFontLoader {
.getElementsByTagName("head")[0]
.appendChild(styleElement);
}

const styleSheet = styleElement.sheet;
styleSheet.insertRule(rule, styleSheet.cssRules.length);
}
Expand Down Expand Up @@ -121,7 +125,18 @@ class BaseFontLoader {
}

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
Expand Down
229 changes: 155 additions & 74 deletions test/driver.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,27 +25,60 @@ const STANDARD_FONT_DATA_URL = "/build/generic/web/standard_fonts/";
const IMAGE_RESOURCES_PATH = "/web/images/";
const WORKER_SRC = "../build/generic/build/pdf.worker.js";
const RENDER_TASK_ON_CONTINUE_DELAY = 5; // ms
const SVG_NS = "http://www.w3.org/2000/svg";

/**
* @class
*/
var rasterizeTextLayer = (function rasterizeTextLayerClosure() {
var 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));
}

var textLayerStylePromise = null;
function getTextLayerStyle() {
if (textLayerStylePromise) {
return textLayerStylePromise;
}
textLayerStylePromise = new Promise(function (resolve) {
var xhr = new XMLHttpRequest();
xhr.open("GET", "./text_layer_test.css");
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 textLayerStylePromise;
}

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
*/
var rasterizeTextLayer = (function rasterizeTextLayerClosure() {
const styles = {
common: {
file: "./text_layer_test.css",
promise: null,
},
};

function getTextLayerStyle() {
return loadStyles(styles);
}

// eslint-disable-next-line no-shadow
Expand Down Expand Up @@ -92,19 +125,7 @@ var rasterizeTextLayer = (function rasterizeTextLayerClosure() {
task.expandTextDivs(true);
svg.appendChild(foreignObject);

// We need to have UTF-8 encoded XML.
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));
};
writeSVG(svg, ctx, resolve, reject);
})
.catch(reason => {
reject(new Error(`rasterizeTextLayer: "${reason?.message}".`));
Expand All @@ -119,8 +140,6 @@ var rasterizeTextLayer = (function rasterizeTextLayerClosure() {
* @class
*/
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
* achieve this, we load the common styles as used by the viewer and extend
Expand All @@ -142,27 +161,7 @@ var rasterizeAnnotationLayer = (function rasterizeAnnotationLayerClosure() {
};

function getAnnotationLayerStyle() {
// Use the cached promises if they are available.
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]);
return loadStyles(styles);
}

function inlineAnnotationImages(images) {
Expand Down Expand Up @@ -256,19 +255,7 @@ var rasterizeAnnotationLayer = (function rasterizeAnnotationLayerClosure() {
foreignObject.appendChild(div);
svg.appendChild(foreignObject);

// We need to have UTF-8 encoded XML.
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));
};
writeSVG(svg, ctx, resolve, reject);
})
.catch(reason => {
reject(new Error(`rasterizeAnnotationLayer: "${reason?.message}".`));
Expand All @@ -279,6 +266,65 @@ var rasterizeAnnotationLayer = (function rasterizeAnnotationLayerClosure() {
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
* @property {HTMLSpanElement} inflight - Field displaying the number of
Expand Down Expand Up @@ -392,6 +438,7 @@ var Driver = (function DriverClosure() {
task.round = 0;
task.pageNum = task.firstPage || 1;
task.stats = { times: [] };
task.enableXfa = task.enableXfa === true;

// Support *linked* test-cases for the other suites, e.g. unit- and
// integration-tests, without needing to run them as reference-tests.
Expand All @@ -411,6 +458,17 @@ var Driver = (function DriverClosure() {

const absoluteUrl = new URL(task.file, window.location).href;
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({
url: absoluteUrl,
password: task.password,
Expand All @@ -422,9 +480,18 @@ var Driver = (function DriverClosure() {
pdfBug: true,
useSystemFonts: task.useSystemFonts,
useWorkerFetch: task.useWorkerFetch,
enableXfa: task.enableXfa,
styleElement: xfaStyleElement,
});
loadingTask.promise.then(
doc => {
if (task.enableXfa) {
task.fontRules = "";
for (const rule of xfaStyleElement.sheet.cssRules) {
task.fontRules += rule.cssText + "\n";
}
}

task.pdfDoc = doc;
task.optionalContentConfigPromise =
doc.getOptionalContentConfig();
Expand Down Expand Up @@ -552,7 +619,8 @@ var Driver = (function DriverClosure() {
// Initialize various `eq` test subtypes, see comment below.
var renderAnnotations = false,
renderForms = false,
renderPrint = false;
renderPrint = false,
renderXfa = false;

var textLayerCanvas, annotationLayerCanvas;
var initPromise;
Expand Down Expand Up @@ -594,9 +662,10 @@ var Driver = (function DriverClosure() {
renderAnnotations = !!task.annotations;
renderForms = !!task.forms;
renderPrint = !!task.print;
renderXfa = !!task.enableXfa;

// Render the annotation layer if necessary.
if (renderAnnotations || renderForms) {
if (renderAnnotations || renderForms || renderXfa) {
// Create a dummy canvas for the drawing operations.
annotationLayerCanvas = self.annotationLayerCanvas;
if (!annotationLayerCanvas) {
Expand All @@ -614,19 +683,31 @@ var Driver = (function DriverClosure() {
annotationLayerCanvas.height
);

// The annotation builder will draw its content on the canvas.
initPromise = page
.getAnnotations({ intent: "display" })
.then(function (annotations) {
return rasterizeAnnotationLayer(
if (!renderXfa) {
// The annotation builder will draw its content
// on the canvas.
initPromise = page
.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,
viewport,
annotations,
page,
IMAGE_RESOURCES_PATH,
renderForms
xfa,
task.fontRules
);
});
}
} else {
annotationLayerCanvas = null;
initPromise = Promise.resolve();
Expand Down
1 change: 1 addition & 0 deletions test/pdfs/hsbc.pdf.link
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
https://web.archive.org/web/20210607145115/https://www.hsbc.fr/content/dam/hsbc/fr/docs/pib/Contestation-Transaction-Carte-Bancaire.pdf
Loading