Skip to content

Commit 63caa10

Browse files
committed
XFA - Add support for reftests
1 parent e7dc822 commit 63caa10

File tree

6 files changed

+185
-77
lines changed

6 files changed

+185
-77
lines changed

src/display/api.js

+1
Original file line numberDiff line numberDiff line change
@@ -2272,6 +2272,7 @@ class WorkerTransport {
22722272
docId: loadingTask.docId,
22732273
onUnsupportedFeature: this._onUnsupportedFeature.bind(this),
22742274
ownerDocument: params.ownerDocument,
2275+
styleElement: params.styleElement,
22752276
});
22762277
this._params = params;
22772278
this.CMapReaderFactory = new params.CMapReaderFactory({

src/display/font_loader.js

+18-3
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ class BaseFontLoader {
2929
docId,
3030
onUnsupportedFeature,
3131
ownerDocument = globalThis.document,
32+
// For testing only.
33+
styleElement = null,
3234
}) {
3335
if (this.constructor === BaseFontLoader) {
3436
unreachable("Cannot initialize BaseFontLoader.");
@@ -38,7 +40,10 @@ class BaseFontLoader {
3840
this._document = ownerDocument;
3941

4042
this.nativeFontFaces = [];
41-
this.styleElement = null;
43+
this.styleElement =
44+
typeof PDFJSDev === "undefined" || PDFJSDev.test("!PRODUCTION || TESTING")
45+
? styleElement
46+
: null;
4247
}
4348

4449
addNativeFontFace(nativeFontFace) {
@@ -55,7 +60,6 @@ class BaseFontLoader {
5560
.getElementsByTagName("head")[0]
5661
.appendChild(styleElement);
5762
}
58-
5963
const styleSheet = styleElement.sheet;
6064
styleSheet.insertRule(rule, styleSheet.cssRules.length);
6165
}
@@ -121,7 +125,18 @@ class BaseFontLoader {
121125
}
122126

123127
get isFontLoadingAPISupported() {
124-
return shadow(this, "isFontLoadingAPISupported", !!this._document?.fonts);
128+
const hasFonts = !!this._document?.fonts;
129+
if (
130+
typeof PDFJSDev === "undefined" ||
131+
PDFJSDev.test("!PRODUCTION || TESTING")
132+
) {
133+
return shadow(
134+
this,
135+
"isFontLoadingAPISupported",
136+
hasFonts && !this.styleElement
137+
);
138+
}
139+
return shadow(this, "isFontLoadingAPISupported", hasFonts);
125140
}
126141

127142
// eslint-disable-next-line getter-return

test/driver.js

+155-74
Original file line numberDiff line numberDiff line change
@@ -25,27 +25,60 @@ const STANDARD_FONT_DATA_URL = "/build/generic/web/standard_fonts/";
2525
const IMAGE_RESOURCES_PATH = "/web/images/";
2626
const WORKER_SRC = "../build/generic/build/pdf.worker.js";
2727
const RENDER_TASK_ON_CONTINUE_DELAY = 5; // ms
28+
const SVG_NS = "http://www.w3.org/2000/svg";
2829

29-
/**
30-
* @class
31-
*/
32-
var rasterizeTextLayer = (function rasterizeTextLayerClosure() {
33-
var SVG_NS = "http://www.w3.org/2000/svg";
30+
function loadStyles(styles) {
31+
styles = Object.values(styles);
32+
if (styles.every(style => style.promise)) {
33+
return Promise.all(styles.map(style => style.promise));
34+
}
3435

35-
var textLayerStylePromise = null;
36-
function getTextLayerStyle() {
37-
if (textLayerStylePromise) {
38-
return textLayerStylePromise;
39-
}
40-
textLayerStylePromise = new Promise(function (resolve) {
41-
var xhr = new XMLHttpRequest();
42-
xhr.open("GET", "./text_layer_test.css");
36+
for (const style of styles) {
37+
style.promise = new Promise(function (resolve, reject) {
38+
const xhr = new XMLHttpRequest();
39+
xhr.open("GET", style.file);
4340
xhr.onload = function () {
4441
resolve(xhr.responseText);
4542
};
43+
xhr.onerror = function (e) {
44+
reject(new Error(`Error fetching style (${style.file}): ${e}`));
45+
};
4646
xhr.send(null);
4747
});
48-
return textLayerStylePromise;
48+
}
49+
50+
return Promise.all(styles.map(style => style.promise));
51+
}
52+
53+
function writeSVG(svgElement, ctx, resolve, reject) {
54+
// We need to have UTF-8 encoded XML.
55+
const svg_xml = unescape(
56+
encodeURIComponent(new XMLSerializer().serializeToString(svgElement))
57+
);
58+
const img = new Image();
59+
img.src = "data:image/svg+xml;base64," + btoa(svg_xml);
60+
img.onload = function () {
61+
ctx.drawImage(img, 0, 0);
62+
resolve();
63+
};
64+
img.onerror = function (e) {
65+
reject(new Error("Error rasterizing text layer " + e));
66+
};
67+
}
68+
69+
/**
70+
* @class
71+
*/
72+
var rasterizeTextLayer = (function rasterizeTextLayerClosure() {
73+
const styles = {
74+
common: {
75+
file: "./text_layer_test.css",
76+
promise: null,
77+
},
78+
};
79+
80+
function getTextLayerStyle() {
81+
return loadStyles(styles);
4982
}
5083

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

95-
// We need to have UTF-8 encoded XML.
96-
var svg_xml = unescape(
97-
encodeURIComponent(new XMLSerializer().serializeToString(svg))
98-
);
99-
var img = new Image();
100-
img.src = "data:image/svg+xml;base64," + btoa(svg_xml);
101-
img.onload = function () {
102-
ctx.drawImage(img, 0, 0);
103-
resolve();
104-
};
105-
img.onerror = function (e) {
106-
reject(new Error("Error rasterizing text layer " + e));
107-
};
128+
writeSVG(svg, ctx, resolve, reject);
108129
})
109130
.catch(reason => {
110131
reject(new Error(`rasterizeTextLayer: "${reason?.message}".`));
@@ -119,8 +140,6 @@ var rasterizeTextLayer = (function rasterizeTextLayerClosure() {
119140
* @class
120141
*/
121142
var rasterizeAnnotationLayer = (function rasterizeAnnotationLayerClosure() {
122-
const SVG_NS = "http://www.w3.org/2000/svg";
123-
124143
/**
125144
* For the reference tests, the entire annotation layer must be visible. To
126145
* achieve this, we load the common styles as used by the viewer and extend
@@ -142,27 +161,7 @@ var rasterizeAnnotationLayer = (function rasterizeAnnotationLayerClosure() {
142161
};
143162

144163
function getAnnotationLayerStyle() {
145-
// Use the cached promises if they are available.
146-
if (styles.common.promise && styles.overrides.promise) {
147-
return Promise.all([styles.common.promise, styles.overrides.promise]);
148-
}
149-
150-
// Load the style files and cache the results.
151-
for (const key in styles) {
152-
styles[key].promise = new Promise(function (resolve, reject) {
153-
const xhr = new XMLHttpRequest();
154-
xhr.open("GET", styles[key].file);
155-
xhr.onload = function () {
156-
resolve(xhr.responseText);
157-
};
158-
xhr.onerror = function (e) {
159-
reject(new Error("Error fetching annotation style " + e));
160-
};
161-
xhr.send(null);
162-
});
163-
}
164-
165-
return Promise.all([styles.common.promise, styles.overrides.promise]);
164+
return loadStyles(styles);
166165
}
167166

168167
function inlineAnnotationImages(images) {
@@ -256,19 +255,7 @@ var rasterizeAnnotationLayer = (function rasterizeAnnotationLayerClosure() {
256255
foreignObject.appendChild(div);
257256
svg.appendChild(foreignObject);
258257

259-
// We need to have UTF-8 encoded XML.
260-
var svg_xml = unescape(
261-
encodeURIComponent(new XMLSerializer().serializeToString(svg))
262-
);
263-
var img = new Image();
264-
img.src = "data:image/svg+xml;base64," + btoa(svg_xml);
265-
img.onload = function () {
266-
ctx.drawImage(img, 0, 0);
267-
resolve();
268-
};
269-
img.onerror = function (e) {
270-
reject(new Error("Error rasterizing annotation layer " + e));
271-
};
258+
writeSVG(svg, ctx, resolve, reject);
272259
})
273260
.catch(reason => {
274261
reject(new Error(`rasterizeAnnotationLayer: "${reason?.message}".`));
@@ -279,6 +266,65 @@ var rasterizeAnnotationLayer = (function rasterizeAnnotationLayerClosure() {
279266
return rasterizeAnnotationLayer;
280267
})();
281268

269+
/**
270+
* @class
271+
*/
272+
var rasterizeXfaLayer = (function rasterizeXfaLayerClosure() {
273+
const styles = {
274+
common: {
275+
file: "../web/xfa_layer_builder.css",
276+
promise: null,
277+
},
278+
};
279+
280+
function getXfaLayerStyle() {
281+
return loadStyles(styles);
282+
}
283+
284+
// eslint-disable-next-line no-shadow
285+
function rasterizeXfaLayer(ctx, viewport, xfa, fontRules) {
286+
return new Promise(function (resolve, reject) {
287+
// Building SVG with size of the viewport.
288+
const svg = document.createElementNS(SVG_NS, "svg:svg");
289+
svg.setAttribute("width", viewport.width + "px");
290+
svg.setAttribute("height", viewport.height + "px");
291+
const foreignObject = document.createElementNS(
292+
SVG_NS,
293+
"svg:foreignObject"
294+
);
295+
foreignObject.setAttribute("x", "0");
296+
foreignObject.setAttribute("y", "0");
297+
foreignObject.setAttribute("width", viewport.width + "px");
298+
foreignObject.setAttribute("height", viewport.height + "px");
299+
const style = document.createElement("style");
300+
const stylePromise = getXfaLayerStyle();
301+
foreignObject.appendChild(style);
302+
const div = document.createElement("div");
303+
foreignObject.appendChild(div);
304+
305+
stylePromise
306+
.then(async cssRules => {
307+
style.textContent = fontRules + "\n" + cssRules;
308+
309+
pdfjsLib.XfaLayer.render({
310+
xfa,
311+
div,
312+
viewport: viewport.clone({ dontFlip: true }),
313+
});
314+
315+
svg.appendChild(foreignObject);
316+
317+
writeSVG(svg, ctx, resolve, reject);
318+
})
319+
.catch(reason => {
320+
reject(new Error(`rasterizeXfaLayer: "${reason?.message}".`));
321+
});
322+
});
323+
}
324+
325+
return rasterizeXfaLayer;
326+
})();
327+
282328
/**
283329
* @typedef {Object} DriverOptions
284330
* @property {HTMLSpanElement} inflight - Field displaying the number of
@@ -392,6 +438,7 @@ var Driver = (function DriverClosure() {
392438
task.round = 0;
393439
task.pageNum = task.firstPage || 1;
394440
task.stats = { times: [] };
441+
task.enableXfa = task.enableXfa === true;
395442

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

412459
const absoluteUrl = new URL(task.file, window.location).href;
413460
try {
461+
let xfaStyleElement = null;
462+
if (task.enableXfa) {
463+
// Need to get the font definitions to inject them in the SVG.
464+
// So we create this element and those definitions will be
465+
// appended in font_loader.js.
466+
xfaStyleElement = document.createElement("style");
467+
document.documentElement
468+
.getElementsByTagName("head")[0]
469+
.appendChild(xfaStyleElement);
470+
}
471+
414472
const loadingTask = pdfjsLib.getDocument({
415473
url: absoluteUrl,
416474
password: task.password,
@@ -422,9 +480,18 @@ var Driver = (function DriverClosure() {
422480
pdfBug: true,
423481
useSystemFonts: task.useSystemFonts,
424482
useWorkerFetch: task.useWorkerFetch,
483+
enableXfa: task.enableXfa,
484+
styleElement: xfaStyleElement,
425485
});
426486
loadingTask.promise.then(
427487
doc => {
488+
if (task.enableXfa) {
489+
task.fontRules = "";
490+
for (const rule of xfaStyleElement.sheet.cssRules) {
491+
task.fontRules += rule.cssText + "\n";
492+
}
493+
}
494+
428495
task.pdfDoc = doc;
429496
task.optionalContentConfigPromise =
430497
doc.getOptionalContentConfig();
@@ -552,7 +619,8 @@ var Driver = (function DriverClosure() {
552619
// Initialize various `eq` test subtypes, see comment below.
553620
var renderAnnotations = false,
554621
renderForms = false,
555-
renderPrint = false;
622+
renderPrint = false,
623+
renderXfa = false;
556624

557625
var textLayerCanvas, annotationLayerCanvas;
558626
var initPromise;
@@ -594,9 +662,10 @@ var Driver = (function DriverClosure() {
594662
renderAnnotations = !!task.annotations;
595663
renderForms = !!task.forms;
596664
renderPrint = !!task.print;
665+
renderXfa = !!task.enableXfa;
597666

598667
// Render the annotation layer if necessary.
599-
if (renderAnnotations || renderForms) {
668+
if (renderAnnotations || renderForms || renderXfa) {
600669
// Create a dummy canvas for the drawing operations.
601670
annotationLayerCanvas = self.annotationLayerCanvas;
602671
if (!annotationLayerCanvas) {
@@ -614,19 +683,31 @@ var Driver = (function DriverClosure() {
614683
annotationLayerCanvas.height
615684
);
616685

617-
// The annotation builder will draw its content on the canvas.
618-
initPromise = page
619-
.getAnnotations({ intent: "display" })
620-
.then(function (annotations) {
621-
return rasterizeAnnotationLayer(
686+
if (!renderXfa) {
687+
// The annotation builder will draw its content
688+
// on the canvas.
689+
initPromise = page
690+
.getAnnotations({ intent: "display" })
691+
.then(function (annotations) {
692+
return rasterizeAnnotationLayer(
693+
annotationLayerContext,
694+
viewport,
695+
annotations,
696+
page,
697+
IMAGE_RESOURCES_PATH,
698+
renderForms
699+
);
700+
});
701+
} else {
702+
initPromise = page.getXfa().then(function (xfa) {
703+
return rasterizeXfaLayer(
622704
annotationLayerContext,
623705
viewport,
624-
annotations,
625-
page,
626-
IMAGE_RESOURCES_PATH,
627-
renderForms
706+
xfa,
707+
task.fontRules
628708
);
629709
});
710+
}
630711
} else {
631712
annotationLayerCanvas = null;
632713
initPromise = Promise.resolve();

test/pdfs/hsbc.pdf.link

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

0 commit comments

Comments
 (0)