diff --git a/src/display/api.js b/src/display/api.js index ee2830699..c319802ad 100644 --- a/src/display/api.js +++ b/src/display/api.js @@ -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({ diff --git a/src/display/font_loader.js b/src/display/font_loader.js index 45e59989a..2536a943b 100644 --- a/src/display/font_loader.js +++ b/src/display/font_loader.js @@ -29,6 +29,8 @@ class BaseFontLoader { docId, onUnsupportedFeature, ownerDocument = globalThis.document, + // For testing only. + styleElement = null, }) { if (this.constructor === BaseFontLoader) { unreachable("Cannot initialize BaseFontLoader."); @@ -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) { @@ -55,7 +60,6 @@ class BaseFontLoader { .getElementsByTagName("head")[0] .appendChild(styleElement); } - const styleSheet = styleElement.sheet; styleSheet.insertRule(rule, styleSheet.cssRules.length); } @@ -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 diff --git a/test/driver.js b/test/driver.js index 80ad61157..70eacaab9 100644 --- a/test/driver.js +++ b/test/driver.js @@ -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"; + +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 */ 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() { - if (textLayerStylePromise) { - 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; + return loadStyles(styles); } // eslint-disable-next-line no-shadow @@ -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}".`)); @@ -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 @@ -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) { @@ -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}".`)); @@ -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 @@ -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. @@ -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, @@ -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(); @@ -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; @@ -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) { @@ -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(); diff --git a/test/pdfs/hsbc.pdf.link b/test/pdfs/hsbc.pdf.link new file mode 100644 index 000000000..24f45d007 --- /dev/null +++ b/test/pdfs/hsbc.pdf.link @@ -0,0 +1 @@ +https://web.archive.org/web/20210607145115/https://www.hsbc.fr/content/dam/hsbc/fr/docs/pib/Contestation-Transaction-Carte-Bancaire.pdf diff --git a/test/test_manifest.json b/test/test_manifest.json index df9a9c57b..b84fbd7ac 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -2725,6 +2725,14 @@ "rounds": 1, "type": "eq" }, + { "id": "hsbc", + "file": "pdfs/hsbc.pdf", + "md5": "34a63a9ed9cdf3790562a3dfd1703e48", + "rounds": 1, + "link": true, + "enableXfa": true, + "type": "eq" + }, { "id": "issue7580-text", "file": "pdfs/issue7580.pdf", "md5": "44dd5a9b4373fcab9890cf567722a766", diff --git a/web/xfa_layer_builder.css b/web/xfa_layer_builder.css index f812aa608..ab18b9342 100644 --- a/web/xfa_layer_builder.css +++ b/web/xfa_layer_builder.css @@ -33,6 +33,8 @@ vertical-align: inherit; box-sizing: border-box; background: transparent; + padding: 0; + margin: 0; } .xfaLayer a {