From 5cdee80c8e548d1caeb5bcf0ff319fc8c8c7bf5f Mon Sep 17 00:00:00 2001 From: Calixte Denizet Date: Fri, 2 Jul 2021 20:05:23 +0200 Subject: [PATCH] XFA - An image can be a stream in the pdf (bug 1718521) - hrefs can be found in catalog > Names > XFAImages --- src/core/catalog.js | 16 ++++++ src/core/document.js | 22 +++++++ src/core/pdf_manager.js | 4 ++ src/core/worker.js | 15 +++-- src/core/xfa/factory.js | 4 ++ src/core/xfa/template.js | 89 +++++++++++++++-------------- test/pdfs/xfa_bug1718521_1.pdf.link | 1 + test/pdfs/xfa_bug1718521_2.pdf.link | 1 + test/pdfs/xfa_bug1718521_3.pdf.link | 1 + test/test_manifest.json | 24 ++++++++ 10 files changed, 128 insertions(+), 49 deletions(-) create mode 100644 test/pdfs/xfa_bug1718521_1.pdf.link create mode 100644 test/pdfs/xfa_bug1718521_2.pdf.link create mode 100644 test/pdfs/xfa_bug1718521_3.pdf.link diff --git a/src/core/catalog.js b/src/core/catalog.js index 6c768e856..f5e75a5ce 100644 --- a/src/core/catalog.js +++ b/src/core/catalog.js @@ -915,6 +915,22 @@ class Catalog { return shadow(this, "attachments", attachments); } + get xfaImages() { + const obj = this._catDict.get("Names"); + let xfaImages = null; + + if (obj instanceof Dict && obj.has("XFAImages")) { + const nameTree = new NameTree(obj.getRaw("XFAImages"), this.xref); + for (const [key, value] of nameTree.getAll()) { + if (!xfaImages) { + xfaImages = new Dict(this.xref); + } + xfaImages.set(key, value); + } + } + return shadow(this, "xfaImages", xfaImages); + } + _collectJavaScript() { const obj = this._catDict.get("Names"); let javaScript = null; diff --git a/src/core/document.js b/src/core/document.js index c9ced6ca2..1e5e18069 100644 --- a/src/core/document.js +++ b/src/core/document.js @@ -869,6 +869,28 @@ class PDFDocument { return null; } + async loadXfaImages() { + const xfaImagesDict = await this.pdfManager.ensureCatalog("xfaImages"); + if (!xfaImagesDict) { + return; + } + + const keys = xfaImagesDict.getKeys(); + const objectLoader = new ObjectLoader(xfaImagesDict, keys, this.xref); + await objectLoader.load(); + + const xfaImages = new Map(); + for (const key of keys) { + const stream = xfaImagesDict.get(key); + if (!isStream(stream)) { + continue; + } + xfaImages.set(key, stream.getBytes()); + } + + this.xfaFactory.setImages(xfaImages); + } + async loadXfaFonts(handler, task) { const acroForm = await this.pdfManager.ensureCatalog("acroForm"); if (!acroForm) { diff --git a/src/core/pdf_manager.js b/src/core/pdf_manager.js index c0f194599..4b5df1a8f 100644 --- a/src/core/pdf_manager.js +++ b/src/core/pdf_manager.js @@ -77,6 +77,10 @@ class BasePdfManager { return this.pdfDocument.loadXfaFonts(handler, task); } + loadXfaImages() { + return this.pdfDocument.loadXfaImages(); + } + serializeXfaData(annotationStorage) { return this.pdfDocument.serializeXfaData(annotationStorage); } diff --git a/src/core/worker.js b/src/core/worker.js index fd5740317..5b9164301 100644 --- a/src/core/worker.js +++ b/src/core/worker.js @@ -191,12 +191,15 @@ class WorkerMessageHandler { if (isPureXfa) { const task = new WorkerTask("loadXfaFonts"); startWorkerTask(task); - await pdfManager - .loadXfaFonts(handler, task) - .catch(reason => { - // Ignore errors, to allow the document to load. - }) - .then(() => finishWorkerTask(task)); + await Promise.all([ + pdfManager + .loadXfaFonts(handler, task) + .catch(reason => { + // Ignore errors, to allow the document to load. + }) + .then(() => finishWorkerTask(task)), + pdfManager.loadXfaImages(), + ]); } const [numPages, fingerprints] = await Promise.all([ diff --git a/src/core/xfa/factory.js b/src/core/xfa/factory.js index d1eb6652f..cc1d9143e 100644 --- a/src/core/xfa/factory.js +++ b/src/core/xfa/factory.js @@ -61,6 +61,10 @@ class XFAFactory { return this.dims.length; } + setImages(images) { + this.form[$globalData].images = images; + } + setFonts(fonts) { this.form[$globalData].fontFinder = new FontFinder(fonts); const missingFonts = []; diff --git a/src/core/xfa/template.js b/src/core/xfa/template.js index 1e0c6c60b..cea5ff157 100644 --- a/src/core/xfa/template.js +++ b/src/core/xfa/template.js @@ -2932,56 +2932,59 @@ class Image extends StringObject { } [$toHTML]() { - if (this.href || !this[$content]) { - // TODO: href can be a Name referring to an internal stream - // containing a picture. + let buffer = + this[$globalData].images && this[$globalData].images.get(this.href); + if (!buffer && (this.href || !this[$content])) { // In general, we don't get remote data and use what we have // in the pdf itself, so no picture for non null href. return HTMLResult.EMPTY; } - // TODO: Firefox doesn't support natively tiff (and tif) format. - if (this.transferEncoding === "base64") { - const buffer = stringToBytes(atob(this[$content])); - const blob = new Blob([buffer], { type: this.contentType }); - let style; - switch (this.aspect) { - case "fit": - case "actual": - // TODO: check what to do with actual. - // Normally we should return {auto, auto} for it but - // it implies some wrong rendering (see xfa_bug1716816.pdf). - break; - case "height": - style = { - width: "auto", - height: "100%", - }; - break; - case "none": - style = { - width: "100%", - height: "100%", - }; - break; - case "width": - style = { - width: "100%", - height: "auto", - }; - break; - } - return HTMLResult.success({ - name: "img", - attributes: { - class: ["xfaImage"], - style, - src: URL.createObjectURL(blob), - }, - }); + if (!buffer && this.transferEncoding === "base64") { + buffer = stringToBytes(atob(this[$content])); } - return HTMLResult.EMPTY; + if (!buffer) { + return HTMLResult.EMPTY; + } + + // TODO: Firefox doesn't support natively tiff (and tif) format. + const blob = new Blob([buffer], { type: this.contentType }); + let style; + switch (this.aspect) { + case "fit": + case "actual": + // TODO: check what to do with actual. + // Normally we should return {auto, auto} for it but + // it implies some wrong rendering (see xfa_bug1716816.pdf). + break; + case "height": + style = { + width: "auto", + height: "100%", + }; + break; + case "none": + style = { + width: "100%", + height: "100%", + }; + break; + case "width": + style = { + width: "100%", + height: "auto", + }; + break; + } + return HTMLResult.success({ + name: "img", + attributes: { + class: ["xfaImage"], + style, + src: URL.createObjectURL(blob), + }, + }); } } diff --git a/test/pdfs/xfa_bug1718521_1.pdf.link b/test/pdfs/xfa_bug1718521_1.pdf.link new file mode 100644 index 000000000..d2e7153f9 --- /dev/null +++ b/test/pdfs/xfa_bug1718521_1.pdf.link @@ -0,0 +1 @@ +https://bugzilla.mozilla.org/attachment.cgi?id=9229396 diff --git a/test/pdfs/xfa_bug1718521_2.pdf.link b/test/pdfs/xfa_bug1718521_2.pdf.link new file mode 100644 index 000000000..1bd5f5c9d --- /dev/null +++ b/test/pdfs/xfa_bug1718521_2.pdf.link @@ -0,0 +1 @@ +https://bugzilla.mozilla.org/attachment.cgi?id=9229409 diff --git a/test/pdfs/xfa_bug1718521_3.pdf.link b/test/pdfs/xfa_bug1718521_3.pdf.link new file mode 100644 index 000000000..ee5150146 --- /dev/null +++ b/test/pdfs/xfa_bug1718521_3.pdf.link @@ -0,0 +1 @@ +https://bugzilla.mozilla.org/attachment.cgi?id=9229430 diff --git a/test/test_manifest.json b/test/test_manifest.json index eaa1956e2..c644f4745 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -938,6 +938,30 @@ "lastPage": 2, "type": "eq" }, + { "id": "xfa_bug1718521_1", + "file": "pdfs/xfa_bug1718521_1.pdf", + "md5": "9b89dd9e6a4c6c3258ca24debd806863", + "link": true, + "rounds": 1, + "enableXfa": true, + "type": "eq" + }, + { "id": "xfa_bug1718521_2", + "file": "pdfs/xfa_bug1718521_2.pdf", + "md5": "c23beb39e1c91a780da9d4b60cbd157f", + "link": true, + "rounds": 1, + "enableXfa": true, + "type": "eq" + }, + { "id": "xfa_bug1718521_3", + "file": "pdfs/xfa_bug1718521_3.pdf", + "md5": "5cc6fb1a65515518e3e64baa81e7a21e", + "link": true, + "rounds": 1, + "enableXfa": true, + "type": "eq" + }, { "id": "xfa_bug1718735", "file": "pdfs/xfa_bug1718735.pdf", "md5": "1001f5c02c026943cbd37f646725d82f",