Skip to content

Commit

Permalink
XFA - Add support for reftests
Browse files Browse the repository at this point in the history
  • Loading branch information
calixteman committed Jun 7, 2021
1 parent 1775d5e commit 44016b9
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 76 deletions.
1 change: 1 addition & 0 deletions src/display/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -2237,6 +2237,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
17 changes: 15 additions & 2 deletions src/display/font_loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class BaseFontLoader {
docId,
onUnsupportedFeature,
ownerDocument = globalThis.document,
styleElement = null,
}) {
if (this.constructor === BaseFontLoader) {
unreachable("Cannot initialize BaseFontLoader.");
Expand All @@ -38,7 +39,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 +59,6 @@ class BaseFontLoader {
.getElementsByTagName("head")[0]
.appendChild(styleElement);
}

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

get isFontLoadingAPISupported() {
if (
typeof PDFJSDev === "undefined" ||
PDFJSDev.test("!PRODUCTION || TESTING")
) {
return shadow(
this,
"isFontLoadingAPISupported",
!!this._document?.fonts && !this.styleElement
);
}
return shadow(this, "isFontLoadingAPISupported", !!this._document?.fonts);
}

Expand Down
231 changes: 157 additions & 74 deletions test/driver.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,27 +24,60 @@ const CMAP_PACKED = true;
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 @@ -91,19 +124,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 @@ -118,8 +139,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 @@ -141,27 +160,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 @@ -255,19 +254,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 @@ -278,6 +265,67 @@ 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");

// Adding element to host our HTML (style + text layer div).
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 @@ -391,6 +439,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 @@ -410,6 +459,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 @@ -418,9 +478,18 @@ var Driver = (function DriverClosure() {
disableRange: task.disableRange,
disableAutoFetch: !task.enableAutoFetch,
pdfBug: true,
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 @@ -548,7 +617,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 @@ -590,9 +660,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 @@ -610,19 +681,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/Contestation-Transaction-Carte-Bancaire.pdf.link
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
https://web.archive.org/save/https://www.hsbc.fr/content/dam/hsbc/fr/docs/pib/Contestation-Transaction-Carte-Bancaire.pdf
Loading

0 comments on commit 44016b9

Please sign in to comment.