mirror of
https://github.com/mozilla/pdf.js.git
synced 2025-04-23 08:38:06 +02:00
Simplify saving added/modified annotations.
Having this map to collect the different changes will allow to know if some objects have already been modified.
This commit is contained in:
parent
7a962031e9
commit
4bf7787084
8 changed files with 301 additions and 331 deletions
|
@ -68,7 +68,6 @@ import { FileSpec } from "./file_spec.js";
|
|||
import { JpegStream } from "./jpeg_stream.js";
|
||||
import { ObjectLoader } from "./object_loader.js";
|
||||
import { OperatorList } from "./operator_list.js";
|
||||
import { writeObject } from "./writer.js";
|
||||
import { XFAFactory } from "./xfa/factory.js";
|
||||
|
||||
class AnnotationFactory {
|
||||
|
@ -332,10 +331,15 @@ class AnnotationFactory {
|
|||
return imagePromises;
|
||||
}
|
||||
|
||||
static async saveNewAnnotations(evaluator, task, annotations, imagePromises) {
|
||||
static async saveNewAnnotations(
|
||||
evaluator,
|
||||
task,
|
||||
annotations,
|
||||
imagePromises,
|
||||
changes
|
||||
) {
|
||||
const xref = evaluator.xref;
|
||||
let baseFontRef;
|
||||
const dependencies = [];
|
||||
const promises = [];
|
||||
const { isOffscreenCanvasSupported } = evaluator.options;
|
||||
|
||||
|
@ -351,38 +355,33 @@ class AnnotationFactory {
|
|||
baseFont.set("Type", Name.get("Font"));
|
||||
baseFont.set("Subtype", Name.get("Type1"));
|
||||
baseFont.set("Encoding", Name.get("WinAnsiEncoding"));
|
||||
const buffer = [];
|
||||
baseFontRef = xref.getNewTemporaryRef();
|
||||
await writeObject(baseFontRef, baseFont, buffer, xref);
|
||||
dependencies.push({ ref: baseFontRef, data: buffer.join("") });
|
||||
changes.put(baseFontRef, {
|
||||
data: baseFont,
|
||||
});
|
||||
}
|
||||
promises.push(
|
||||
FreeTextAnnotation.createNewAnnotation(
|
||||
xref,
|
||||
annotation,
|
||||
dependencies,
|
||||
{ evaluator, task, baseFontRef }
|
||||
)
|
||||
FreeTextAnnotation.createNewAnnotation(xref, annotation, changes, {
|
||||
evaluator,
|
||||
task,
|
||||
baseFontRef,
|
||||
})
|
||||
);
|
||||
break;
|
||||
case AnnotationEditorType.HIGHLIGHT:
|
||||
if (annotation.quadPoints) {
|
||||
promises.push(
|
||||
HighlightAnnotation.createNewAnnotation(
|
||||
xref,
|
||||
annotation,
|
||||
dependencies
|
||||
)
|
||||
HighlightAnnotation.createNewAnnotation(xref, annotation, changes)
|
||||
);
|
||||
} else {
|
||||
promises.push(
|
||||
InkAnnotation.createNewAnnotation(xref, annotation, dependencies)
|
||||
InkAnnotation.createNewAnnotation(xref, annotation, changes)
|
||||
);
|
||||
}
|
||||
break;
|
||||
case AnnotationEditorType.INK:
|
||||
promises.push(
|
||||
InkAnnotation.createNewAnnotation(xref, annotation, dependencies)
|
||||
InkAnnotation.createNewAnnotation(xref, annotation, changes)
|
||||
);
|
||||
break;
|
||||
case AnnotationEditorType.STAMP:
|
||||
|
@ -391,26 +390,23 @@ class AnnotationFactory {
|
|||
: null;
|
||||
if (image?.imageStream) {
|
||||
const { imageStream, smaskStream } = image;
|
||||
const buffer = [];
|
||||
if (smaskStream) {
|
||||
const smaskRef = xref.getNewTemporaryRef();
|
||||
await writeObject(smaskRef, smaskStream, buffer, xref);
|
||||
dependencies.push({ ref: smaskRef, data: buffer.join("") });
|
||||
changes.put(smaskRef, {
|
||||
data: smaskStream,
|
||||
});
|
||||
imageStream.dict.set("SMask", smaskRef);
|
||||
buffer.length = 0;
|
||||
}
|
||||
const imageRef = (image.imageRef = xref.getNewTemporaryRef());
|
||||
await writeObject(imageRef, imageStream, buffer, xref);
|
||||
dependencies.push({ ref: imageRef, data: buffer.join("") });
|
||||
changes.put(imageRef, {
|
||||
data: imageStream,
|
||||
});
|
||||
image.imageStream = image.smaskStream = null;
|
||||
}
|
||||
promises.push(
|
||||
StampAnnotation.createNewAnnotation(
|
||||
xref,
|
||||
annotation,
|
||||
dependencies,
|
||||
{ image }
|
||||
)
|
||||
StampAnnotation.createNewAnnotation(xref, annotation, changes, {
|
||||
image,
|
||||
})
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
@ -418,7 +414,6 @@ class AnnotationFactory {
|
|||
|
||||
return {
|
||||
annotations: await Promise.all(promises),
|
||||
dependencies,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1227,7 +1222,7 @@ class Annotation {
|
|||
return { opList, separateForm: false, separateCanvas: isUsingOwnCanvas };
|
||||
}
|
||||
|
||||
async save(evaluator, task, annotationStorage) {
|
||||
async save(evaluator, task, annotationStorage, changes) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -1758,14 +1753,13 @@ class MarkupAnnotation extends Annotation {
|
|||
this._streams.push(this.appearance, appearanceStream);
|
||||
}
|
||||
|
||||
static async createNewAnnotation(xref, annotation, dependencies, params) {
|
||||
static async createNewAnnotation(xref, annotation, changes, params) {
|
||||
if (!annotation.ref) {
|
||||
annotation.ref = xref.getNewTemporaryRef();
|
||||
}
|
||||
|
||||
const annotationRef = annotation.ref;
|
||||
const ap = await this.createNewAppearanceStream(annotation, xref, params);
|
||||
const buffer = [];
|
||||
let annotationDict;
|
||||
|
||||
if (ap) {
|
||||
|
@ -1773,8 +1767,9 @@ class MarkupAnnotation extends Annotation {
|
|||
annotationDict = this.createNewDict(annotation, xref, {
|
||||
apRef,
|
||||
});
|
||||
await writeObject(apRef, ap, buffer, xref);
|
||||
dependencies.push({ ref: apRef, data: buffer.join("") });
|
||||
changes.put(apRef, {
|
||||
data: ap,
|
||||
});
|
||||
} else {
|
||||
annotationDict = this.createNewDict(annotation, xref, {});
|
||||
}
|
||||
|
@ -1782,10 +1777,11 @@ class MarkupAnnotation extends Annotation {
|
|||
annotationDict.set("StructParent", annotation.parentTreeId);
|
||||
}
|
||||
|
||||
buffer.length = 0;
|
||||
await writeObject(annotationRef, annotationDict, buffer, xref);
|
||||
changes.put(annotationRef, {
|
||||
data: annotationDict,
|
||||
});
|
||||
|
||||
return { ref: annotationRef, data: buffer.join("") };
|
||||
return { ref: annotationRef };
|
||||
}
|
||||
|
||||
static async createNewPrintAnnotation(
|
||||
|
@ -2112,7 +2108,7 @@ class WidgetAnnotation extends Annotation {
|
|||
|
||||
amendSavedDict(annotationStorage, dict) {}
|
||||
|
||||
async save(evaluator, task, annotationStorage) {
|
||||
async save(evaluator, task, annotationStorage, changes) {
|
||||
const storageEntry = annotationStorage?.get(this.data.id);
|
||||
const flags = this._buildFlags(storageEntry?.noView, storageEntry?.noPrint);
|
||||
let value = storageEntry?.value,
|
||||
|
@ -2123,7 +2119,7 @@ class WidgetAnnotation extends Annotation {
|
|||
rotation === undefined &&
|
||||
flags === undefined
|
||||
) {
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
value ||= this.data.fieldValue;
|
||||
}
|
||||
|
@ -2137,7 +2133,7 @@ class WidgetAnnotation extends Annotation {
|
|||
isArrayEqual(value, this.data.fieldValue) &&
|
||||
flags === undefined
|
||||
) {
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (rotation === undefined) {
|
||||
|
@ -2154,7 +2150,7 @@ class WidgetAnnotation extends Annotation {
|
|||
);
|
||||
if (appearance === null && flags === undefined) {
|
||||
// Appearance didn't change.
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// No need to create an appearance: the pdf has the flag /NeedAppearances
|
||||
|
@ -2171,7 +2167,7 @@ class WidgetAnnotation extends Annotation {
|
|||
|
||||
const originalDict = xref.fetchIfRef(this.ref);
|
||||
if (!(originalDict instanceof Dict)) {
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
|
||||
const dict = new Dict(xref);
|
||||
|
@ -2208,12 +2204,11 @@ class WidgetAnnotation extends Annotation {
|
|||
dict.set("MK", maybeMK);
|
||||
}
|
||||
|
||||
const buffer = [];
|
||||
const changes = [
|
||||
// data for the original object
|
||||
// V field changed + reference for new AP
|
||||
{ ref: this.ref, data: "", xfa, needAppearances },
|
||||
];
|
||||
changes.put(this.ref, {
|
||||
data: dict,
|
||||
xfa,
|
||||
needAppearances,
|
||||
});
|
||||
if (appearance !== null) {
|
||||
const newRef = xref.getNewTemporaryRef();
|
||||
const AP = new Dict(xref);
|
||||
|
@ -2238,26 +2233,14 @@ class WidgetAnnotation extends Annotation {
|
|||
appearanceDict.set("Matrix", rotationMatrix);
|
||||
}
|
||||
|
||||
await writeObject(newRef, appearanceStream, buffer, xref);
|
||||
|
||||
changes.push(
|
||||
// data for the new AP
|
||||
{
|
||||
ref: newRef,
|
||||
data: buffer.join(""),
|
||||
xfa: null,
|
||||
needAppearances: false,
|
||||
}
|
||||
);
|
||||
buffer.length = 0;
|
||||
changes.put(newRef, {
|
||||
data: appearanceStream,
|
||||
xfa: null,
|
||||
needAppearances: false,
|
||||
});
|
||||
}
|
||||
|
||||
dict.set("M", `D:${getModificationDate()}`);
|
||||
await writeObject(this.ref, dict, buffer, xref);
|
||||
|
||||
changes[0].data = buffer.join("");
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
async _getAppearance(evaluator, task, intent, annotationStorage) {
|
||||
|
@ -3078,22 +3061,20 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
|
|||
};
|
||||
}
|
||||
|
||||
async save(evaluator, task, annotationStorage) {
|
||||
async save(evaluator, task, annotationStorage, changes) {
|
||||
if (this.data.checkBox) {
|
||||
return this._saveCheckbox(evaluator, task, annotationStorage);
|
||||
this._saveCheckbox(evaluator, task, annotationStorage, changes);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.data.radioButton) {
|
||||
return this._saveRadioButton(evaluator, task, annotationStorage);
|
||||
this._saveRadioButton(evaluator, task, annotationStorage, changes);
|
||||
}
|
||||
|
||||
// Nothing to save
|
||||
return null;
|
||||
}
|
||||
|
||||
async _saveCheckbox(evaluator, task, annotationStorage) {
|
||||
async _saveCheckbox(evaluator, task, annotationStorage, changes) {
|
||||
if (!annotationStorage) {
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
const storageEntry = annotationStorage.get(this.data.id);
|
||||
const flags = this._buildFlags(storageEntry?.noView, storageEntry?.noPrint);
|
||||
|
@ -3102,18 +3083,18 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
|
|||
|
||||
if (rotation === undefined && flags === undefined) {
|
||||
if (value === undefined) {
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
|
||||
const defaultValue = this.data.fieldValue === this.data.exportValue;
|
||||
if (defaultValue === value) {
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let dict = evaluator.xref.fetchIfRef(this.ref);
|
||||
if (!(dict instanceof Dict)) {
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
dict = dict.clone();
|
||||
|
||||
|
@ -3142,15 +3123,16 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
|
|||
dict.set("MK", maybeMK);
|
||||
}
|
||||
|
||||
const buffer = [];
|
||||
await writeObject(this.ref, dict, buffer, evaluator.xref);
|
||||
|
||||
return [{ ref: this.ref, data: buffer.join(""), xfa }];
|
||||
changes.put(this.ref, {
|
||||
data: dict,
|
||||
xfa,
|
||||
needAppearances: false,
|
||||
});
|
||||
}
|
||||
|
||||
async _saveRadioButton(evaluator, task, annotationStorage) {
|
||||
async _saveRadioButton(evaluator, task, annotationStorage, changes) {
|
||||
if (!annotationStorage) {
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
const storageEntry = annotationStorage.get(this.data.id);
|
||||
const flags = this._buildFlags(storageEntry?.noView, storageEntry?.noPrint);
|
||||
|
@ -3159,18 +3141,18 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
|
|||
|
||||
if (rotation === undefined && flags === undefined) {
|
||||
if (value === undefined) {
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
|
||||
const defaultValue = this.data.fieldValue === this.data.buttonValue;
|
||||
if (defaultValue === value) {
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let dict = evaluator.xref.fetchIfRef(this.ref);
|
||||
if (!(dict instanceof Dict)) {
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
dict = dict.clone();
|
||||
|
||||
|
@ -3188,16 +3170,16 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
|
|||
};
|
||||
|
||||
const name = Name.get(value ? this.data.buttonValue : "Off");
|
||||
const buffer = [];
|
||||
let parentData = null;
|
||||
|
||||
if (value) {
|
||||
if (this.parent instanceof Ref) {
|
||||
const parent = evaluator.xref.fetch(this.parent);
|
||||
const parent = evaluator.xref.fetch(this.parent).clone();
|
||||
parent.set("V", name);
|
||||
await writeObject(this.parent, parent, buffer, evaluator.xref);
|
||||
parentData = buffer.join("");
|
||||
buffer.length = 0;
|
||||
changes.put(this.parent, {
|
||||
data: parent,
|
||||
xfa: null,
|
||||
needAppearances: false,
|
||||
});
|
||||
} else if (this.parent instanceof Dict) {
|
||||
this.parent.set("V", name);
|
||||
}
|
||||
|
@ -3219,13 +3201,11 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
|
|||
dict.set("MK", maybeMK);
|
||||
}
|
||||
|
||||
await writeObject(this.ref, dict, buffer, evaluator.xref);
|
||||
const newRefs = [{ ref: this.ref, data: buffer.join(""), xfa }];
|
||||
if (parentData) {
|
||||
newRefs.push({ ref: this.parent, data: parentData, xfa: null });
|
||||
}
|
||||
|
||||
return newRefs;
|
||||
changes.put(this.ref, {
|
||||
data: dict,
|
||||
xfa,
|
||||
needAppearances: false,
|
||||
});
|
||||
}
|
||||
|
||||
_getDefaultCheckedAppearance(params, type) {
|
||||
|
|
|
@ -70,7 +70,6 @@ import { OperatorList } from "./operator_list.js";
|
|||
import { PartialEvaluator } from "./evaluator.js";
|
||||
import { StreamsSequenceStream } from "./decode_stream.js";
|
||||
import { StructTreePage } from "./struct_tree.js";
|
||||
import { writeObject } from "./writer.js";
|
||||
import { XFAFactory } from "./xfa/factory.js";
|
||||
import { XRef } from "./xref.js";
|
||||
|
||||
|
@ -314,7 +313,7 @@ class Page {
|
|||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
async saveNewAnnotations(handler, task, annotations, imagePromises) {
|
||||
async saveNewAnnotations(handler, task, annotations, imagePromises, changes) {
|
||||
if (this.xfaFactory) {
|
||||
throw new Error("XFA: Cannot save new annotations.");
|
||||
}
|
||||
|
@ -348,7 +347,8 @@ class Page {
|
|||
partialEvaluator,
|
||||
task,
|
||||
annotations,
|
||||
imagePromises
|
||||
imagePromises,
|
||||
changes
|
||||
);
|
||||
|
||||
for (const { ref } of newData.annotations) {
|
||||
|
@ -358,27 +358,20 @@ class Page {
|
|||
}
|
||||
}
|
||||
|
||||
const savedDict = pageDict.get("Annots");
|
||||
pageDict.set("Annots", annotationsArray);
|
||||
const buffer = [];
|
||||
await writeObject(this.ref, pageDict, buffer, this.xref);
|
||||
if (savedDict) {
|
||||
pageDict.set("Annots", savedDict);
|
||||
}
|
||||
const dict = pageDict.clone();
|
||||
dict.set("Annots", annotationsArray);
|
||||
changes.put(this.ref, {
|
||||
data: dict,
|
||||
});
|
||||
|
||||
const objects = newData.dependencies;
|
||||
objects.push(
|
||||
{ ref: this.ref, data: buffer.join("") },
|
||||
...newData.annotations
|
||||
);
|
||||
for (const deletedRef of deletedAnnotations) {
|
||||
objects.push({ ref: deletedRef, data: null });
|
||||
changes.put(deletedRef, {
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
|
||||
return objects;
|
||||
}
|
||||
|
||||
save(handler, task, annotationStorage) {
|
||||
save(handler, task, annotationStorage, changes) {
|
||||
const partialEvaluator = new PartialEvaluator({
|
||||
xref: this.xref,
|
||||
handler,
|
||||
|
@ -395,11 +388,11 @@ class Page {
|
|||
// Fetch the page's annotations and save the content
|
||||
// in case of interactive form fields.
|
||||
return this._parsedAnnotations.then(function (annotations) {
|
||||
const newRefsPromises = [];
|
||||
const promises = [];
|
||||
for (const annotation of annotations) {
|
||||
newRefsPromises.push(
|
||||
promises.push(
|
||||
annotation
|
||||
.save(partialEvaluator, task, annotationStorage)
|
||||
.save(partialEvaluator, task, annotationStorage, changes)
|
||||
.catch(function (reason) {
|
||||
warn(
|
||||
"save - ignoring annotation data during " +
|
||||
|
@ -410,9 +403,7 @@ class Page {
|
|||
);
|
||||
}
|
||||
|
||||
return Promise.all(newRefsPromises).then(function (newRefs) {
|
||||
return newRefs.filter(newRef => !!newRef);
|
||||
});
|
||||
return Promise.all(promises);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -383,6 +383,10 @@ class RefSetCache {
|
|||
this._map.clear();
|
||||
}
|
||||
|
||||
*values() {
|
||||
yield* this._map.values();
|
||||
}
|
||||
|
||||
*items() {
|
||||
for (const [ref, value] of this._map) {
|
||||
yield [Ref.fromString(ref), value];
|
||||
|
|
|
@ -17,7 +17,6 @@ import { AnnotationPrefix, stringToPDFString, warn } from "../shared/util.js";
|
|||
import { Dict, isName, Name, Ref, RefSetCache } from "./primitives.js";
|
||||
import { lookupNormalRect, stringToAsciiOrUTF16BE } from "./core_utils.js";
|
||||
import { NumberTree } from "./name_number_tree.js";
|
||||
import { writeObject } from "./writer.js";
|
||||
|
||||
const MAX_DEPTH = 40;
|
||||
|
||||
|
@ -117,7 +116,7 @@ class StructTreeRoot {
|
|||
xref,
|
||||
catalogRef,
|
||||
pdfManager,
|
||||
newRefs,
|
||||
changes,
|
||||
}) {
|
||||
const root = pdfManager.catalog.cloneDict();
|
||||
const cache = new RefSetCache();
|
||||
|
@ -146,18 +145,17 @@ class StructTreeRoot {
|
|||
nums,
|
||||
xref,
|
||||
pdfManager,
|
||||
newRefs,
|
||||
changes,
|
||||
cache,
|
||||
});
|
||||
structTreeRoot.set("ParentTreeNextKey", nextKey);
|
||||
|
||||
cache.put(parentTreeRef, parentTree);
|
||||
|
||||
const buffer = [];
|
||||
for (const [ref, obj] of cache.items()) {
|
||||
buffer.length = 0;
|
||||
await writeObject(ref, obj, buffer, xref);
|
||||
newRefs.push({ ref, data: buffer.join("") });
|
||||
changes.put(ref, {
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -235,7 +233,7 @@ class StructTreeRoot {
|
|||
return true;
|
||||
}
|
||||
|
||||
async updateStructureTree({ newAnnotationsByPage, pdfManager, newRefs }) {
|
||||
async updateStructureTree({ newAnnotationsByPage, pdfManager, changes }) {
|
||||
const xref = this.dict.xref;
|
||||
const structTreeRoot = this.dict.clone();
|
||||
const structTreeRootRef = this.ref;
|
||||
|
@ -273,7 +271,7 @@ class StructTreeRoot {
|
|||
nums,
|
||||
xref,
|
||||
pdfManager,
|
||||
newRefs,
|
||||
changes,
|
||||
cache,
|
||||
});
|
||||
|
||||
|
@ -288,11 +286,10 @@ class StructTreeRoot {
|
|||
cache.put(numsRef, nums);
|
||||
}
|
||||
|
||||
const buffer = [];
|
||||
for (const [ref, obj] of cache.items()) {
|
||||
buffer.length = 0;
|
||||
await writeObject(ref, obj, buffer, xref);
|
||||
newRefs.push({ ref, data: buffer.join("") });
|
||||
changes.put(ref, {
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -304,13 +301,12 @@ class StructTreeRoot {
|
|||
nums,
|
||||
xref,
|
||||
pdfManager,
|
||||
newRefs,
|
||||
changes,
|
||||
cache,
|
||||
}) {
|
||||
const objr = Name.get("OBJR");
|
||||
let nextKey = -1;
|
||||
let structTreePageObjs;
|
||||
const buffer = [];
|
||||
|
||||
for (const [pageIndex, elements] of newAnnotationsByPage) {
|
||||
const page = await pdfManager.getPage(pageIndex);
|
||||
|
@ -350,9 +346,9 @@ class StructTreeRoot {
|
|||
// We update the existing tag.
|
||||
const tagDict = xref.fetch(objRef).clone();
|
||||
StructTreeRoot.#writeProperties(tagDict, accessibilityData);
|
||||
buffer.length = 0;
|
||||
await writeObject(objRef, tagDict, buffer, xref);
|
||||
newRefs.push({ ref: objRef, data: buffer.join("") });
|
||||
changes.put(objRef, {
|
||||
data: tagDict,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ import {
|
|||
getNewAnnotationsMap,
|
||||
XRefParseException,
|
||||
} from "./core_utils.js";
|
||||
import { Dict, isDict, Ref } from "./primitives.js";
|
||||
import { Dict, isDict, Ref, RefSetCache } from "./primitives.js";
|
||||
import { LocalPdfManager, NetworkPdfManager } from "./pdf_manager.js";
|
||||
import { AnnotationFactory } from "./annotation.js";
|
||||
import { clearGlobalCaches } from "./cleanup_helper.js";
|
||||
|
@ -540,6 +540,7 @@ class WorkerMessageHandler {
|
|||
pdfManager.ensureDoc("linearization"),
|
||||
pdfManager.ensureCatalog("structTreeRoot"),
|
||||
];
|
||||
const changes = new RefSetCache();
|
||||
const promises = [];
|
||||
|
||||
const newAnnotationsByPage = !isPureXfa
|
||||
|
@ -590,7 +591,13 @@ class WorkerMessageHandler {
|
|||
pdfManager.getPage(pageIndex).then(page => {
|
||||
const task = new WorkerTask(`Save (editor): page ${pageIndex}`);
|
||||
return page
|
||||
.saveNewAnnotations(handler, task, annotations, imagePromises)
|
||||
.saveNewAnnotations(
|
||||
handler,
|
||||
task,
|
||||
annotations,
|
||||
imagePromises,
|
||||
changes
|
||||
)
|
||||
.finally(function () {
|
||||
finishWorkerTask(task);
|
||||
});
|
||||
|
@ -600,26 +607,24 @@ class WorkerMessageHandler {
|
|||
if (structTreeRoot === null) {
|
||||
// No structTreeRoot exists, so we need to create one.
|
||||
promises.push(
|
||||
Promise.all(newAnnotationPromises).then(async newRefs => {
|
||||
Promise.all(newAnnotationPromises).then(async () => {
|
||||
await StructTreeRoot.createStructureTree({
|
||||
newAnnotationsByPage,
|
||||
xref,
|
||||
catalogRef,
|
||||
pdfManager,
|
||||
newRefs,
|
||||
changes,
|
||||
});
|
||||
return newRefs;
|
||||
})
|
||||
);
|
||||
} else if (structTreeRoot) {
|
||||
promises.push(
|
||||
Promise.all(newAnnotationPromises).then(async newRefs => {
|
||||
Promise.all(newAnnotationPromises).then(async () => {
|
||||
await structTreeRoot.updateStructureTree({
|
||||
newAnnotationsByPage,
|
||||
pdfManager,
|
||||
newRefs,
|
||||
changes,
|
||||
});
|
||||
return newRefs;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -633,7 +638,7 @@ class WorkerMessageHandler {
|
|||
pdfManager.getPage(pageIndex).then(function (page) {
|
||||
const task = new WorkerTask(`Save: page ${pageIndex}`);
|
||||
return page
|
||||
.save(handler, task, annotationStorage)
|
||||
.save(handler, task, annotationStorage, changes)
|
||||
.finally(function () {
|
||||
finishWorkerTask(task);
|
||||
});
|
||||
|
@ -643,26 +648,21 @@ class WorkerMessageHandler {
|
|||
}
|
||||
const refs = await Promise.all(promises);
|
||||
|
||||
let newRefs = [];
|
||||
let xfaData = null;
|
||||
if (isPureXfa) {
|
||||
xfaData = refs[0];
|
||||
if (!xfaData) {
|
||||
return stream.bytes;
|
||||
}
|
||||
} else {
|
||||
newRefs = refs.flat(2);
|
||||
|
||||
if (newRefs.length === 0) {
|
||||
// No new refs so just return the initial bytes
|
||||
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 &&
|
||||
newRefs.some(ref => ref.needAppearances);
|
||||
changes.values().some(ref => ref.needAppearances);
|
||||
|
||||
const xfa = (acroForm instanceof Dict && acroForm.get("XFA")) || null;
|
||||
let xfaDatasetsRef = null;
|
||||
|
@ -712,7 +712,7 @@ class WorkerMessageHandler {
|
|||
return incrementalUpdate({
|
||||
originalData: stream.bytes,
|
||||
xrefInfo: newXrefInfo,
|
||||
newRefs,
|
||||
changes,
|
||||
xref,
|
||||
hasXfa: !!xfa,
|
||||
xfaDatasetsRef,
|
||||
|
|
|
@ -23,9 +23,9 @@ import {
|
|||
parseXFAPath,
|
||||
} from "./core_utils.js";
|
||||
import { SimpleDOMNode, SimpleXMLParser } from "./xml_parser.js";
|
||||
import { Stream, StringStream } from "./stream.js";
|
||||
import { BaseStream } from "./base_stream.js";
|
||||
import { calculateMD5 } from "./crypto.js";
|
||||
import { Stream } from "./stream.js";
|
||||
|
||||
async function writeObject(ref, obj, buffer, { encrypt = null }) {
|
||||
const transform = encrypt?.createCipherTransform(ref.num, ref.gen);
|
||||
|
@ -192,10 +192,10 @@ function computeMD5(filesize, xrefInfo) {
|
|||
return bytesToString(calculateMD5(array));
|
||||
}
|
||||
|
||||
function writeXFADataForAcroform(str, newRefs) {
|
||||
function writeXFADataForAcroform(str, changes) {
|
||||
const xml = new SimpleXMLParser({ hasAttributes: true }).parseFromString(str);
|
||||
|
||||
for (const { xfa } of newRefs) {
|
||||
for (const { xfa } of changes) {
|
||||
if (!xfa) {
|
||||
continue;
|
||||
}
|
||||
|
@ -230,7 +230,7 @@ async function updateAcroform({
|
|||
hasXfaDatasetsEntry,
|
||||
xfaDatasetsRef,
|
||||
needAppearances,
|
||||
newRefs,
|
||||
changes,
|
||||
}) {
|
||||
if (hasXfa && !hasXfaDatasetsEntry && !xfaDatasetsRef) {
|
||||
warn("XFA - Cannot save it");
|
||||
|
@ -257,33 +257,23 @@ async function updateAcroform({
|
|||
dict.set("NeedAppearances", true);
|
||||
}
|
||||
|
||||
const buffer = [];
|
||||
await writeObject(acroFormRef, dict, buffer, xref);
|
||||
|
||||
newRefs.push({ ref: acroFormRef, data: buffer.join("") });
|
||||
changes.put(acroFormRef, {
|
||||
data: dict,
|
||||
});
|
||||
}
|
||||
|
||||
function updateXFA({ xfaData, xfaDatasetsRef, newRefs, xref }) {
|
||||
function updateXFA({ xfaData, xfaDatasetsRef, changes, xref }) {
|
||||
if (xfaData === null) {
|
||||
const datasets = xref.fetchIfRef(xfaDatasetsRef);
|
||||
xfaData = writeXFADataForAcroform(datasets.getString(), newRefs);
|
||||
xfaData = writeXFADataForAcroform(datasets.getString(), changes);
|
||||
}
|
||||
const xfaDataStream = new StringStream(xfaData);
|
||||
xfaDataStream.dict = new Dict(xref);
|
||||
xfaDataStream.dict.set("Type", Name.get("EmbeddedFile"));
|
||||
|
||||
const encrypt = xref.encrypt;
|
||||
if (encrypt) {
|
||||
const transform = encrypt.createCipherTransform(
|
||||
xfaDatasetsRef.num,
|
||||
xfaDatasetsRef.gen
|
||||
);
|
||||
xfaData = transform.encryptString(xfaData);
|
||||
}
|
||||
const data =
|
||||
`${xfaDatasetsRef.num} ${xfaDatasetsRef.gen} obj\n` +
|
||||
`<< /Type /EmbeddedFile /Length ${xfaData.length}>>\nstream\n` +
|
||||
xfaData +
|
||||
"\nendstream\nendobj\n";
|
||||
|
||||
newRefs.push({ ref: xfaDatasetsRef, data });
|
||||
changes.put(xfaDatasetsRef, {
|
||||
data: xfaDataStream,
|
||||
});
|
||||
}
|
||||
|
||||
async function getXRefTable(xrefInfo, baseOffset, newRefs, newXref, buffer) {
|
||||
|
@ -383,12 +373,12 @@ function computeIDs(baseOffset, xrefInfo, newXref) {
|
|||
}
|
||||
}
|
||||
|
||||
function getTrailerDict(xrefInfo, newRefs, useXrefStream) {
|
||||
function getTrailerDict(xrefInfo, changes, useXrefStream) {
|
||||
const newXref = new Dict(null);
|
||||
newXref.set("Prev", xrefInfo.startXRef);
|
||||
const refForXrefTable = xrefInfo.newRef;
|
||||
if (useXrefStream) {
|
||||
newRefs.push({ ref: refForXrefTable, data: "" });
|
||||
changes.put(refForXrefTable, { data: "" });
|
||||
newXref.set("Size", refForXrefTable.num + 1);
|
||||
newXref.set("Type", Name.get("XRef"));
|
||||
} else {
|
||||
|
@ -406,10 +396,24 @@ function getTrailerDict(xrefInfo, newRefs, useXrefStream) {
|
|||
return newXref;
|
||||
}
|
||||
|
||||
async function writeChanges(changes, xref, buffer = []) {
|
||||
const newRefs = [];
|
||||
for (const [ref, { data }] of changes.items()) {
|
||||
if (data === null || typeof data === "string") {
|
||||
newRefs.push({ ref, data });
|
||||
continue;
|
||||
}
|
||||
await writeObject(ref, data, buffer, xref);
|
||||
newRefs.push({ ref, data: buffer.join("") });
|
||||
buffer.length = 0;
|
||||
}
|
||||
return newRefs.sort((a, b) => /* compare the refs */ a.ref.num - b.ref.num);
|
||||
}
|
||||
|
||||
async function incrementalUpdate({
|
||||
originalData,
|
||||
xrefInfo,
|
||||
newRefs,
|
||||
changes,
|
||||
xref = null,
|
||||
hasXfa = false,
|
||||
xfaDatasetsRef = null,
|
||||
|
@ -428,19 +432,21 @@ async function incrementalUpdate({
|
|||
hasXfaDatasetsEntry,
|
||||
xfaDatasetsRef,
|
||||
needAppearances,
|
||||
newRefs,
|
||||
changes,
|
||||
});
|
||||
|
||||
if (hasXfa) {
|
||||
updateXFA({
|
||||
xfaData,
|
||||
xfaDatasetsRef,
|
||||
newRefs,
|
||||
changes,
|
||||
xref,
|
||||
});
|
||||
}
|
||||
|
||||
const newXref = getTrailerDict(xrefInfo, changes, useXrefStream);
|
||||
const buffer = [];
|
||||
const newRefs = await writeChanges(changes, xref, buffer);
|
||||
let baseOffset = originalData.length;
|
||||
const lastByte = originalData.at(-1);
|
||||
if (lastByte !== /* \n */ 0x0a && lastByte !== /* \r */ 0x0d) {
|
||||
|
@ -449,10 +455,6 @@ async function incrementalUpdate({
|
|||
baseOffset += 1;
|
||||
}
|
||||
|
||||
const newXref = getTrailerDict(xrefInfo, newRefs, useXrefStream);
|
||||
newRefs = newRefs.sort(
|
||||
(a, b) => /* compare the refs */ a.ref.num - b.ref.num
|
||||
);
|
||||
for (const { data } of newRefs) {
|
||||
if (data !== null) {
|
||||
buffer.push(data);
|
||||
|
@ -482,4 +484,4 @@ async function incrementalUpdate({
|
|||
return array;
|
||||
}
|
||||
|
||||
export { incrementalUpdate, writeDict, writeObject };
|
||||
export { incrementalUpdate, writeChanges, writeDict, writeObject };
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue