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

Save form data in XFA datasets when pdf is a mix of acroforms and xfa (#12344)

* Move display/xml_parser.js in shared to use it in worker

* Save form data in XFA datasets when pdf is a mix of acroforms and xfa

Co-authored-by: Brendan Dahl <brendan.dahl@gmail.com>
This commit is contained in:
calixteman 2020-09-09 00:13:52 +02:00 committed by GitHub
parent 622e2fbd3a
commit 68b99c59ee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 416 additions and 19 deletions

View file

@ -1073,6 +1073,7 @@ class WidgetAnnotation extends Annotation {
return null;
}
const value = annotationStorage[this.data.id];
const bbox = [
0,
0,
@ -1080,11 +1081,15 @@ class WidgetAnnotation extends Annotation {
this.data.rect[3] - this.data.rect[1],
];
const xfa = {
path: stringToPDFString(dict.get("T") || ""),
value,
};
const newRef = evaluator.xref.getNewRef();
const AP = new Dict(evaluator.xref);
AP.set("N", newRef);
const value = annotationStorage[this.data.id];
const encrypt = evaluator.xref.encrypt;
let originalTransform = null;
let newTransform = null;
@ -1120,9 +1125,9 @@ class WidgetAnnotation extends Annotation {
return [
// data for the original object
// V field changed + reference for new AP
{ ref: this.ref, data: bufferOriginal.join("") },
{ ref: this.ref, data: bufferOriginal.join(""), xfa },
// data for the new AP
{ ref: newRef, data: bufferNew.join("") },
{ ref: newRef, data: bufferNew.join(""), xfa: null },
];
}
@ -1521,6 +1526,11 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
return null;
}
const xfa = {
path: stringToPDFString(dict.get("T") || ""),
value: value ? this.data.exportValue : "",
};
const name = Name.get(value ? this.data.exportValue : "Off");
dict.set("V", name);
dict.set("AS", name);
@ -1539,7 +1549,7 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
writeDict(dict, buffer, originalTransform);
buffer.push("\nendobj\n");
return [{ ref: this.ref, data: buffer.join("") }];
return [{ ref: this.ref, data: buffer.join(""), xfa }];
}
async _saveRadioButton(evaluator, task, annotationStorage) {
@ -1555,6 +1565,11 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
return null;
}
const xfa = {
path: stringToPDFString(dict.get("T") || ""),
value: value ? this.data.buttonValue : "",
};
const name = Name.get(value ? this.data.buttonValue : "Off");
let parentBuffer = null;
const encrypt = evaluator.xref.encrypt;
@ -1593,9 +1608,13 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
writeDict(dict, buffer, originalTransform);
buffer.push("\nendobj\n");
const newRefs = [{ ref: this.ref, data: buffer.join("") }];
const newRefs = [{ ref: this.ref, data: buffer.join(""), xfa }];
if (parentBuffer !== null) {
newRefs.push({ ref: this.parent, data: parentBuffer.join("") });
newRefs.push({
ref: this.parent,
data: parentBuffer.join(""),
xfa: null,
});
}
return newRefs;

View file

@ -32,7 +32,7 @@ import {
VerbosityLevel,
warn,
} from "../shared/util.js";
import { clearPrimitiveCaches, Ref } from "./primitives.js";
import { clearPrimitiveCaches, Dict, isDict, Ref } from "./primitives.js";
import { LocalPdfManager, NetworkPdfManager } from "./pdf_manager.js";
import { incrementalUpdate } from "./writer.js";
import { isNodeJS } from "../shared/is_node.js";
@ -521,7 +521,10 @@ class WorkerMessageHandler {
filename,
}) {
pdfManager.requestLoadedStream();
const promises = [pdfManager.onLoadedStream()];
const promises = [
pdfManager.onLoadedStream(),
pdfManager.ensureCatalog("acroForm"),
];
const document = pdfManager.pdfDocument;
for (let pageIndex = 0; pageIndex < numPages; pageIndex++) {
promises.push(
@ -532,7 +535,7 @@ class WorkerMessageHandler {
);
}
return Promise.all(promises).then(([stream, ...refs]) => {
return Promise.all(promises).then(([stream, acroForm, ...refs]) => {
let newRefs = [];
for (const ref of refs) {
newRefs = ref
@ -545,6 +548,20 @@ class WorkerMessageHandler {
return stream.bytes;
}
acroForm = isDict(acroForm) ? acroForm : Dict.empty;
const xfa = acroForm.get("XFA") || [];
let xfaDatasets = null;
if (Array.isArray(xfa)) {
for (let i = 0, ii = xfa.length; i < ii; i += 2) {
if (xfa[i] === "datasets") {
xfaDatasets = xfa[i + 1];
}
}
} else {
// TODO: Support XFA streams.
warn("Unsupported XFA type.");
}
const xref = document.xref;
let newXrefInfo = Object.create(null);
if (xref.trailer) {
@ -572,7 +589,13 @@ class WorkerMessageHandler {
}
xref.resetNewRef();
return incrementalUpdate(stream.bytes, newXrefInfo, newRefs);
return incrementalUpdate(
stream.bytes,
newXrefInfo,
newRefs,
xref,
xfaDatasets
);
});
});

View file

@ -14,8 +14,14 @@
*/
/* eslint no-var: error */
import { bytesToString, escapeString } from "../shared/util.js";
import {
bytesToString,
escapeString,
parseXFAPath,
warn,
} from "../shared/util.js";
import { Dict, isDict, isName, isRef, isStream, Name } from "./primitives.js";
import { SimpleDOMNode, SimpleXMLParser } from "../shared/xml_parser.js";
import { calculateMD5 } from "./crypto.js";
function writeDict(dict, buffer, transform) {
@ -123,7 +129,55 @@ function computeMD5(filesize, xrefInfo) {
return bytesToString(calculateMD5(array));
}
function incrementalUpdate(originalData, xrefInfo, newRefs) {
function updateXFA(datasetsRef, newRefs, xref) {
if (datasetsRef === null || xref === null) {
return;
}
const datasets = xref.fetchIfRef(datasetsRef);
const str = bytesToString(datasets.getBytes());
const xml = new SimpleXMLParser(/* hasAttributes */ true).parseFromString(
str
);
for (const { xfa } of newRefs) {
if (!xfa) {
continue;
}
const { path, value } = xfa;
if (!path) {
continue;
}
const node = xml.documentElement.searchNode(parseXFAPath(path), 0);
if (node) {
node.childNodes = [new SimpleDOMNode("#text", value)];
} else {
warn(`Node not found for path: ${path}`);
}
}
const buffer = [];
xml.documentElement.dump(buffer);
let updatedXml = buffer.join("");
const encrypt = xref.encrypt;
if (encrypt) {
const transform = encrypt.createCipherTransform(
datasetsRef.num,
datasetsRef.gen
);
updatedXml = transform.encryptString(updatedXml);
}
const data =
`${datasetsRef.num} ${datasetsRef.gen} obj\n` +
`<< /Type /EmbeddedFile /Length ${updatedXml.length}>>\nstream\n` +
updatedXml +
"\nendstream\nendobj\n";
newRefs.push({ ref: datasetsRef, data });
}
function incrementalUpdate(originalData, xrefInfo, newRefs, xref, datasetsRef) {
updateXFA(datasetsRef, newRefs, xref);
const newXref = new Dict(null);
const refForXrefTable = xrefInfo.newRef;