1
0
Fork 0
mirror of https://github.com/mozilla/pdf.js.git synced 2025-04-26 01:58:06 +02:00

XFA - Add a layer to display XFA forms (#13069)

- add an option to enable XFA rendering if any;
  - for now, let the canvas layer: it could be useful to implement XFAF forms (embedded pdf in xml stream for the background and xfa form for the foreground);
  - ui elements in template DOM are pretty close to their html counterpart so we generate a fake html DOM from template one:
    - it makes easier to translate template properties to html ones;
    - it makes faster the creation of the html element in the main thread.
This commit is contained in:
calixteman 2021-03-19 10:11:40 +01:00 committed by GitHub
parent a164941351
commit 24e598a895
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 760 additions and 27 deletions

View file

@ -15,6 +15,7 @@
import {
assert,
bytesToString,
FormatError,
info,
InvalidPDFException,
@ -28,6 +29,7 @@ import {
shadow,
stringToBytes,
stringToPDFString,
stringToUTF8String,
unreachable,
Util,
warn,
@ -56,6 +58,7 @@ import { calculateMD5 } from "./crypto.js";
import { Linearization } from "./parser.js";
import { OperatorList } from "./operator_list.js";
import { PartialEvaluator } from "./evaluator.js";
import { XFAFactory } from "./xfa/factory.js";
const DEFAULT_USER_UNIT = 1.0;
const LETTER_SIZE_MEDIABOX = [0, 0, 612, 792];
@ -79,6 +82,7 @@ class Page {
builtInCMapCache,
globalImageCache,
nonBlendModesSet,
xfaFactory,
}) {
this.pdfManager = pdfManager;
this.pageIndex = pageIndex;
@ -91,6 +95,7 @@ class Page {
this.nonBlendModesSet = nonBlendModesSet;
this.evaluatorOptions = pdfManager.evaluatorOptions;
this.resourcesPromise = null;
this.xfaFactory = xfaFactory;
const idCounters = {
obj: 0,
@ -137,6 +142,11 @@ class Page {
}
_getBoundingBox(name) {
if (this.xfaData) {
const { width, height } = this.xfaData.attributes.style;
return [0, 0, parseInt(width), parseInt(height)];
}
const box = this._getInheritableProperty(name, /* getArray = */ true);
if (Array.isArray(box) && box.length === 4) {
@ -231,6 +241,13 @@ class Page {
return stream;
}
get xfaData() {
if (this.xfaFactory) {
return shadow(this, "xfaData", this.xfaFactory.getPage(this.pageIndex));
}
return shadow(this, "xfaData", null);
}
save(handler, task, annotationStorage) {
const partialEvaluator = new PartialEvaluator({
xref: this.xref,
@ -695,6 +712,9 @@ class PDFDocument {
}
get numPages() {
if (this.xfaFactory) {
return shadow(this, "numPages", this.xfaFactory.numberPages);
}
const linearization = this.linearization;
const num = linearization ? linearization.numPages : this.catalog.numPages;
return shadow(this, "numPages", num);
@ -732,6 +752,80 @@ class PDFDocument {
});
}
get xfaData() {
const acroForm = this.catalog.acroForm;
if (!acroForm) {
return null;
}
const xfa = acroForm.get("XFA");
const entries = {
"xdp:xdp": "",
template: "",
datasets: "",
config: "",
connectionSet: "",
localeSet: "",
stylesheet: "",
"/xdp:xdp": "",
};
if (isStream(xfa) && !xfa.isEmpty) {
try {
entries["xdp:xdp"] = stringToUTF8String(bytesToString(xfa.getBytes()));
return entries;
} catch (_) {
warn("XFA - Invalid utf-8 string.");
return null;
}
}
if (!Array.isArray(xfa) || xfa.length === 0) {
return null;
}
for (let i = 0, ii = xfa.length; i < ii; i += 2) {
let name;
if (i === 0) {
name = "xdp:xdp";
} else if (i === ii - 2) {
name = "/xdp:xdp";
} else {
name = xfa[i];
}
if (!entries.hasOwnProperty(name)) {
continue;
}
const data = this.xref.fetchIfRef(xfa[i + 1]);
if (!isStream(data) || data.isEmpty) {
continue;
}
try {
entries[name] = stringToUTF8String(bytesToString(data.getBytes()));
} catch (_) {
warn("XFA - Invalid utf-8 string.");
return null;
}
}
return entries;
}
get xfaFactory() {
if (
this.pdfManager.enableXfa &&
this.formInfo.hasXfa &&
!this.formInfo.hasAcroForm
) {
const data = this.xfaData;
return shadow(this, "xfaFactory", data ? new XFAFactory(data) : null);
}
return shadow(this, "xfaFaxtory", null);
}
get isPureXfa() {
return this.xfaFactory !== null;
}
get formInfo() {
const formInfo = { hasFields: false, hasAcroForm: false, hasXfa: false };
const acroForm = this.catalog.acroForm;
@ -918,6 +1012,24 @@ class PDFDocument {
}
const { catalog, linearization } = this;
if (this.xfaFactory) {
return Promise.resolve(
new Page({
pdfManager: this.pdfManager,
xref: this.xref,
pageIndex,
pageDict: Dict.empty,
ref: null,
globalIdFactory: this._globalIdFactory,
fontCache: catalog.fontCache,
builtInCMapCache: catalog.builtInCMapCache,
globalImageCache: catalog.globalImageCache,
nonBlendModesSet: catalog.nonBlendModesSet,
xfaFactory: this.xfaFactory,
})
);
}
const promise =
linearization && linearization.pageFirst === pageIndex
? this._getLinearizationPage(pageIndex)
@ -935,6 +1047,7 @@ class PDFDocument {
builtInCMapCache: catalog.builtInCMapCache,
globalImageCache: catalog.globalImageCache,
nonBlendModesSet: catalog.nonBlendModesSet,
xfaFactory: null,
});
}));
}