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:
parent
622e2fbd3a
commit
68b99c59ee
11 changed files with 416 additions and 19 deletions
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue