in src/core/worker.js [511:712]
async function ({ isPureXfa, numPages, annotationStorage, filename }) {
const globalPromises = [
pdfManager.requestLoadedStream(),
pdfManager.ensureCatalog("acroForm"),
pdfManager.ensureCatalog("acroFormRef"),
pdfManager.ensureDoc("startXRef"),
pdfManager.ensureDoc("xref"),
pdfManager.ensureDoc("linearization"),
pdfManager.ensureCatalog("structTreeRoot"),
];
const changes = new RefSetCache();
const promises = [];
const newAnnotationsByPage = !isPureXfa
? getNewAnnotationsMap(annotationStorage)
: null;
const [
stream,
acroForm,
acroFormRef,
startXRef,
xref,
linearization,
_structTreeRoot,
] = await Promise.all(globalPromises);
const catalogRef = xref.trailer.getRaw("Root") || null;
let structTreeRoot;
if (newAnnotationsByPage) {
if (!_structTreeRoot) {
if (
await StructTreeRoot.canCreateStructureTree({
catalogRef,
pdfManager,
newAnnotationsByPage,
})
) {
structTreeRoot = null;
}
} else if (
await _structTreeRoot.canUpdateStructTree({
pdfManager,
newAnnotationsByPage,
})
) {
structTreeRoot = _structTreeRoot;
}
const imagePromises = AnnotationFactory.generateImages(
annotationStorage.values(),
xref,
pdfManager.evaluatorOptions.isOffscreenCanvasSupported
);
const newAnnotationPromises =
structTreeRoot === undefined ? promises : [];
for (const [pageIndex, annotations] of newAnnotationsByPage) {
newAnnotationPromises.push(
pdfManager.getPage(pageIndex).then(page => {
const task = new WorkerTask(`Save (editor): page ${pageIndex}`);
startWorkerTask(task);
return page
.saveNewAnnotations(
handler,
task,
annotations,
imagePromises,
changes
)
.finally(function () {
finishWorkerTask(task);
});
})
);
}
if (structTreeRoot === null) {
// No structTreeRoot exists, so we need to create one.
promises.push(
Promise.all(newAnnotationPromises).then(async () => {
await StructTreeRoot.createStructureTree({
newAnnotationsByPage,
xref,
catalogRef,
pdfManager,
changes,
});
})
);
} else if (structTreeRoot) {
promises.push(
Promise.all(newAnnotationPromises).then(async () => {
await structTreeRoot.updateStructureTree({
newAnnotationsByPage,
pdfManager,
changes,
});
})
);
}
}
if (isPureXfa) {
promises.push(
pdfManager.ensureDoc("serializeXfaData", [annotationStorage])
);
} else {
for (let pageIndex = 0; pageIndex < numPages; pageIndex++) {
promises.push(
pdfManager.getPage(pageIndex).then(function (page) {
const task = new WorkerTask(`Save: page ${pageIndex}`);
startWorkerTask(task);
return page
.save(handler, task, annotationStorage, changes)
.finally(function () {
finishWorkerTask(task);
});
})
);
}
}
const refs = await Promise.all(promises);
let xfaData = null;
if (isPureXfa) {
xfaData = refs[0];
if (!xfaData) {
return stream.bytes;
}
} else if (changes.size === 0) {
// No new refs so just return the initial bytes
return stream.bytes;
}
const needAppearances =
acroFormRef &&
acroForm instanceof Dict &&
changes.values().some(ref => ref.needAppearances);
const xfa = (acroForm instanceof Dict && acroForm.get("XFA")) || null;
let xfaDatasetsRef = null;
let hasXfaDatasetsEntry = false;
if (Array.isArray(xfa)) {
for (let i = 0, ii = xfa.length; i < ii; i += 2) {
if (xfa[i] === "datasets") {
xfaDatasetsRef = xfa[i + 1];
hasXfaDatasetsEntry = true;
}
}
if (xfaDatasetsRef === null) {
xfaDatasetsRef = xref.getNewTemporaryRef();
}
} else if (xfa) {
// TODO: Support XFA streams.
warn("Unsupported XFA type.");
}
let newXrefInfo = Object.create(null);
if (xref.trailer) {
// Get string info from Info in order to compute fileId.
const infoMap = new Map();
const xrefInfo = xref.trailer.get("Info") || null;
if (xrefInfo instanceof Dict) {
for (const [key, value] of xrefInfo) {
if (typeof value === "string") {
infoMap.set(key, stringToPDFString(value));
}
}
}
newXrefInfo = {
rootRef: catalogRef,
encryptRef: xref.trailer.getRaw("Encrypt") || null,
newRef: xref.getNewTemporaryRef(),
infoRef: xref.trailer.getRaw("Info") || null,
infoMap,
fileIds: xref.trailer.get("ID") || null,
startXRef: linearization
? startXRef
: (xref.lastXRefStreamPos ?? startXRef),
filename,
};
}
return incrementalUpdate({
originalData: stream.bytes,
xrefInfo: newXrefInfo,
changes,
xref,
hasXfa: !!xfa,
xfaDatasetsRef,
hasXfaDatasetsEntry,
needAppearances,
acroFormRef,
acroForm,
xfaData,
// Use the same kind of XRef as the previous one.
useXrefStream: isDict(xref.topDict, "XRef"),
}).finally(() => {
xref.resetNewTemporaryRef();
});
}