mirror of
https://github.com/mozilla/pdf.js.git
synced 2025-04-19 22:58:07 +02:00
[Editor] Add the possibility to save an updated stamp annotation (bug 1921291)
This commit is contained in:
parent
0308b8075f
commit
c9050be863
9 changed files with 338 additions and 83 deletions
|
@ -381,11 +381,10 @@ class AnnotationFactory {
|
|||
);
|
||||
break;
|
||||
case AnnotationEditorType.STAMP:
|
||||
if (!isOffscreenCanvasSupported) {
|
||||
break;
|
||||
}
|
||||
const image = await imagePromises.get(annotation.bitmapId);
|
||||
if (image.imageStream) {
|
||||
const image = isOffscreenCanvasSupported
|
||||
? await imagePromises?.get(annotation.bitmapId)
|
||||
: null;
|
||||
if (image?.imageStream) {
|
||||
const { imageStream, smaskStream } = image;
|
||||
const buffer = [];
|
||||
if (smaskStream) {
|
||||
|
@ -488,11 +487,10 @@ class AnnotationFactory {
|
|||
);
|
||||
break;
|
||||
case AnnotationEditorType.STAMP:
|
||||
if (!options.isOffscreenCanvasSupported) {
|
||||
break;
|
||||
}
|
||||
const image = await imagePromises.get(annotation.bitmapId);
|
||||
if (image.imageStream) {
|
||||
const image = options.isOffscreenCanvasSupported
|
||||
? await imagePromises?.get(annotation.bitmapId)
|
||||
: null;
|
||||
if (image?.imageStream) {
|
||||
const { imageStream, smaskStream } = image;
|
||||
if (smaskStream) {
|
||||
imageStream.dict.set("SMask", smaskStream);
|
||||
|
@ -653,17 +651,6 @@ class Annotation {
|
|||
const isLocked = !!(this.flags & AnnotationFlag.LOCKED);
|
||||
const isContentLocked = !!(this.flags & AnnotationFlag.LOCKEDCONTENTS);
|
||||
|
||||
if (annotationGlobals.structTreeRoot) {
|
||||
let structParent = dict.get("StructParent");
|
||||
structParent =
|
||||
Number.isInteger(structParent) && structParent >= 0 ? structParent : -1;
|
||||
|
||||
annotationGlobals.structTreeRoot.addAnnotationIdToPage(
|
||||
params.pageRef,
|
||||
structParent
|
||||
);
|
||||
}
|
||||
|
||||
// Expose public properties using a data object.
|
||||
this.data = {
|
||||
annotationFlags: this.flags,
|
||||
|
@ -682,8 +669,20 @@ class Annotation {
|
|||
noRotate: !!(this.flags & AnnotationFlag.NOROTATE),
|
||||
noHTML: isLocked && isContentLocked,
|
||||
isEditable: false,
|
||||
structParent: -1,
|
||||
};
|
||||
|
||||
if (annotationGlobals.structTreeRoot) {
|
||||
let structParent = dict.get("StructParent");
|
||||
this.data.structParent = structParent =
|
||||
Number.isInteger(structParent) && structParent >= 0 ? structParent : -1;
|
||||
|
||||
annotationGlobals.structTreeRoot.addAnnotationIdToPage(
|
||||
params.pageRef,
|
||||
structParent
|
||||
);
|
||||
}
|
||||
|
||||
if (params.collectFields) {
|
||||
// Fields can act as container for other fields and have
|
||||
// some actions even if no Annotation inherit from them.
|
||||
|
@ -1751,10 +1750,7 @@ class MarkupAnnotation extends Annotation {
|
|||
}
|
||||
|
||||
static async createNewAnnotation(xref, annotation, dependencies, params) {
|
||||
let oldAnnotation;
|
||||
if (annotation.ref) {
|
||||
oldAnnotation = (await xref.fetchIfRefAsync(annotation.ref)).clone();
|
||||
} else {
|
||||
if (!annotation.ref) {
|
||||
annotation.ref = xref.getNewTemporaryRef();
|
||||
}
|
||||
|
||||
|
@ -1767,12 +1763,11 @@ class MarkupAnnotation extends Annotation {
|
|||
const apRef = xref.getNewTemporaryRef();
|
||||
annotationDict = this.createNewDict(annotation, xref, {
|
||||
apRef,
|
||||
oldAnnotation,
|
||||
});
|
||||
await writeObject(apRef, ap, buffer, xref);
|
||||
dependencies.push({ ref: apRef, data: buffer.join("") });
|
||||
} else {
|
||||
annotationDict = this.createNewDict(annotation, xref, { oldAnnotation });
|
||||
annotationDict = this.createNewDict(annotation, xref, {});
|
||||
}
|
||||
if (Number.isInteger(annotation.parentTreeId)) {
|
||||
annotationDict.set("StructParent", annotation.parentTreeId);
|
||||
|
@ -1791,7 +1786,11 @@ class MarkupAnnotation extends Annotation {
|
|||
params
|
||||
) {
|
||||
const ap = await this.createNewAppearanceStream(annotation, xref, params);
|
||||
const annotationDict = this.createNewDict(annotation, xref, { ap });
|
||||
const annotationDict = this.createNewDict(
|
||||
annotation,
|
||||
xref,
|
||||
ap ? { ap } : {}
|
||||
);
|
||||
|
||||
const newAnnotation = new this.prototype.constructor({
|
||||
dict: annotationDict,
|
||||
|
@ -3904,8 +3903,9 @@ class FreeTextAnnotation extends MarkupAnnotation {
|
|||
return this._hasAppearance;
|
||||
}
|
||||
|
||||
static createNewDict(annotation, xref, { apRef, ap, oldAnnotation }) {
|
||||
const { color, fontSize, rect, rotation, user, value } = annotation;
|
||||
static createNewDict(annotation, xref, { apRef, ap }) {
|
||||
const { color, fontSize, oldAnnotation, rect, rotation, user, value } =
|
||||
annotation;
|
||||
const freetext = oldAnnotation || new Dict(xref);
|
||||
freetext.set("Type", Name.get("Annot"));
|
||||
freetext.set("Subtype", Name.get("FreeText"));
|
||||
|
@ -4646,8 +4646,9 @@ class HighlightAnnotation extends MarkupAnnotation {
|
|||
}
|
||||
}
|
||||
|
||||
static createNewDict(annotation, xref, { apRef, ap, oldAnnotation }) {
|
||||
const { color, opacity, rect, rotation, user, quadPoints } = annotation;
|
||||
static createNewDict(annotation, xref, { apRef, ap }) {
|
||||
const { color, oldAnnotation, opacity, rect, rotation, user, quadPoints } =
|
||||
annotation;
|
||||
const highlight = oldAnnotation || new Dict(xref);
|
||||
highlight.set("Type", Name.get("Annot"));
|
||||
highlight.set("Subtype", Name.get("Highlight"));
|
||||
|
@ -4943,10 +4944,14 @@ class StampAnnotation extends MarkupAnnotation {
|
|||
}
|
||||
|
||||
static createNewDict(annotation, xref, { apRef, ap }) {
|
||||
const { rect, rotation, user } = annotation;
|
||||
const stamp = new Dict(xref);
|
||||
const { oldAnnotation, rect, rotation, user } = annotation;
|
||||
const stamp = oldAnnotation || new Dict(xref);
|
||||
stamp.set("Type", Name.get("Annot"));
|
||||
stamp.set("Subtype", Name.get("Stamp"));
|
||||
stamp.set(
|
||||
oldAnnotation ? "M" : "CreationDate",
|
||||
`D:${getModificationDate()}`
|
||||
);
|
||||
stamp.set("CreationDate", `D:${getModificationDate()}`);
|
||||
stamp.set("Rect", rect);
|
||||
stamp.set("F", 4);
|
||||
|
@ -4972,6 +4977,11 @@ class StampAnnotation extends MarkupAnnotation {
|
|||
}
|
||||
|
||||
static async createNewAppearanceStream(annotation, xref, params) {
|
||||
if (annotation.oldAnnotation) {
|
||||
// We'll use the AP we already have.
|
||||
return null;
|
||||
}
|
||||
|
||||
const { rotation } = annotation;
|
||||
const { imageRef, width, height } = params.image;
|
||||
const resources = new Dict(xref);
|
||||
|
|
|
@ -274,7 +274,8 @@ class Page {
|
|||
);
|
||||
}
|
||||
|
||||
#replaceIdByRef(annotations, deletedAnnotations, existingAnnotations) {
|
||||
async #replaceIdByRef(annotations, deletedAnnotations, existingAnnotations) {
|
||||
const promises = [];
|
||||
for (const annotation of annotations) {
|
||||
if (annotation.id) {
|
||||
const ref = Ref.fromString(annotation.id);
|
||||
|
@ -294,9 +295,22 @@ class Page {
|
|||
}
|
||||
existingAnnotations?.put(ref);
|
||||
annotation.ref = ref;
|
||||
promises.push(
|
||||
this.xref.fetchAsync(ref).then(
|
||||
obj => {
|
||||
if (obj instanceof Dict) {
|
||||
annotation.oldAnnotation = obj.clone();
|
||||
}
|
||||
},
|
||||
() => {
|
||||
warn(`Cannot fetch \`oldAnnotation\` for: ${ref}.`);
|
||||
}
|
||||
)
|
||||
);
|
||||
delete annotation.id;
|
||||
}
|
||||
}
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
async saveNewAnnotations(handler, task, annotations, imagePromises) {
|
||||
|
@ -319,7 +333,11 @@ class Page {
|
|||
|
||||
const deletedAnnotations = new RefSetCache();
|
||||
const existingAnnotations = new RefSet();
|
||||
this.#replaceIdByRef(annotations, deletedAnnotations, existingAnnotations);
|
||||
await this.#replaceIdByRef(
|
||||
annotations,
|
||||
deletedAnnotations,
|
||||
existingAnnotations
|
||||
);
|
||||
|
||||
const pageDict = this.pageDict;
|
||||
const annotationsArray = this.annotations.filter(
|
||||
|
@ -489,23 +507,23 @@ class Page {
|
|||
}
|
||||
|
||||
deletedAnnotations = new RefSet();
|
||||
this.#replaceIdByRef(newAnnots, deletedAnnotations, null);
|
||||
|
||||
newAnnotationsPromise = annotationGlobalsPromise.then(
|
||||
annotationGlobals => {
|
||||
if (!annotationGlobals) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return AnnotationFactory.printNewAnnotations(
|
||||
annotationGlobals,
|
||||
partialEvaluator,
|
||||
task,
|
||||
newAnnots,
|
||||
imagePromises
|
||||
);
|
||||
newAnnotationsPromise = Promise.all([
|
||||
annotationGlobalsPromise,
|
||||
this.#replaceIdByRef(newAnnots, deletedAnnotations, null),
|
||||
]).then(([annotationGlobals]) => {
|
||||
if (!annotationGlobals) {
|
||||
return null;
|
||||
}
|
||||
);
|
||||
|
||||
return AnnotationFactory.printNewAnnotations(
|
||||
annotationGlobals,
|
||||
partialEvaluator,
|
||||
task,
|
||||
newAnnots,
|
||||
imagePromises
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const pageListPromise = Promise.all([
|
||||
|
|
|
@ -74,7 +74,7 @@ class NameOrNumberTree {
|
|||
return map;
|
||||
}
|
||||
|
||||
get(key) {
|
||||
getRaw(key) {
|
||||
if (!this.root) {
|
||||
return null;
|
||||
}
|
||||
|
@ -135,12 +135,16 @@ class NameOrNumberTree {
|
|||
} else if (key > currentKey) {
|
||||
l = m + 2;
|
||||
} else {
|
||||
return xref.fetchIfRef(entries[m + 1]);
|
||||
return entries[m + 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
get(key) {
|
||||
return this.xref.fetchIfRef(this.getRaw(key));
|
||||
}
|
||||
}
|
||||
|
||||
class NameTree extends NameOrNumberTree {
|
||||
|
|
|
@ -141,10 +141,12 @@ class StructTreeRoot {
|
|||
const nextKey = await this.#writeKids({
|
||||
newAnnotationsByPage,
|
||||
structTreeRootRef,
|
||||
structTreeRoot: null,
|
||||
kids,
|
||||
nums,
|
||||
xref,
|
||||
pdfManager,
|
||||
newRefs,
|
||||
cache,
|
||||
});
|
||||
structTreeRoot.set("ParentTreeNextKey", nextKey);
|
||||
|
@ -209,8 +211,12 @@ class StructTreeRoot {
|
|||
|
||||
for (const element of elements) {
|
||||
if (element.accessibilityData?.type) {
|
||||
// Each tag must have a structure type.
|
||||
element.parentTreeId = nextKey++;
|
||||
// structParent can be undefined and in this case the positivity check
|
||||
// will fail (it's why the expression isn't equivalent to a `.<.`).
|
||||
if (!(element.accessibilityData.structParent >= 0)) {
|
||||
// Each tag must have a structure type.
|
||||
element.parentTreeId = nextKey++;
|
||||
}
|
||||
hasNothingToUpdate = false;
|
||||
}
|
||||
}
|
||||
|
@ -259,16 +265,24 @@ class StructTreeRoot {
|
|||
parentTree.set("Nums", nums);
|
||||
}
|
||||
|
||||
const newNextkey = await StructTreeRoot.#writeKids({
|
||||
const newNextKey = await StructTreeRoot.#writeKids({
|
||||
newAnnotationsByPage,
|
||||
structTreeRootRef,
|
||||
structTreeRoot: this,
|
||||
kids: null,
|
||||
nums,
|
||||
xref,
|
||||
pdfManager,
|
||||
newRefs,
|
||||
cache,
|
||||
});
|
||||
structTreeRoot.set("ParentTreeNextKey", newNextkey);
|
||||
|
||||
if (newNextKey === -1) {
|
||||
// No new tags were added.
|
||||
return;
|
||||
}
|
||||
|
||||
structTreeRoot.set("ParentTreeNextKey", newNextKey);
|
||||
|
||||
if (numsRef) {
|
||||
cache.put(numsRef, nums);
|
||||
|
@ -285,17 +299,22 @@ class StructTreeRoot {
|
|||
static async #writeKids({
|
||||
newAnnotationsByPage,
|
||||
structTreeRootRef,
|
||||
structTreeRoot,
|
||||
kids,
|
||||
nums,
|
||||
xref,
|
||||
pdfManager,
|
||||
newRefs,
|
||||
cache,
|
||||
}) {
|
||||
const objr = Name.get("OBJR");
|
||||
let nextKey = -Infinity;
|
||||
let nextKey = -1;
|
||||
let structTreePageObjs;
|
||||
const buffer = [];
|
||||
|
||||
for (const [pageIndex, elements] of newAnnotationsByPage) {
|
||||
const { ref: pageRef } = await pdfManager.getPage(pageIndex);
|
||||
const page = await pdfManager.getPage(pageIndex);
|
||||
const { ref: pageRef } = page;
|
||||
const isPageRef = pageRef instanceof Ref;
|
||||
for (const {
|
||||
accessibilityData,
|
||||
|
@ -306,31 +325,43 @@ class StructTreeRoot {
|
|||
if (!accessibilityData?.type) {
|
||||
continue;
|
||||
}
|
||||
const { type, title, lang, alt, expanded, actualText } =
|
||||
accessibilityData;
|
||||
|
||||
// We've some accessibility data, so we need to create a new tag or
|
||||
// update an existing one.
|
||||
const { structParent } = accessibilityData;
|
||||
|
||||
if (
|
||||
structTreeRoot &&
|
||||
Number.isInteger(structParent) &&
|
||||
structParent >= 0
|
||||
) {
|
||||
let objs = (structTreePageObjs ||= new Map()).get(pageIndex);
|
||||
if (objs === undefined) {
|
||||
// We need to collect the objects for the page.
|
||||
const structTreePage = new StructTreePage(
|
||||
structTreeRoot,
|
||||
page.pageDict
|
||||
);
|
||||
objs = structTreePage.collectObjects(pageRef);
|
||||
structTreePageObjs.set(pageIndex, objs);
|
||||
}
|
||||
const objRef = objs?.get(structParent);
|
||||
if (objRef) {
|
||||
// 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("") });
|
||||
continue;
|
||||
}
|
||||
}
|
||||
nextKey = Math.max(nextKey, parentTreeId);
|
||||
|
||||
const tagRef = xref.getNewTemporaryRef();
|
||||
const tagDict = new Dict(xref);
|
||||
|
||||
// The structure type is required.
|
||||
tagDict.set("S", Name.get(type));
|
||||
|
||||
if (title) {
|
||||
tagDict.set("T", stringToAsciiOrUTF16BE(title));
|
||||
}
|
||||
if (lang) {
|
||||
tagDict.set("Lang", lang);
|
||||
}
|
||||
if (alt) {
|
||||
tagDict.set("Alt", stringToAsciiOrUTF16BE(alt));
|
||||
}
|
||||
if (expanded) {
|
||||
tagDict.set("E", stringToAsciiOrUTF16BE(expanded));
|
||||
}
|
||||
if (actualText) {
|
||||
tagDict.set("ActualText", stringToAsciiOrUTF16BE(actualText));
|
||||
}
|
||||
StructTreeRoot.#writeProperties(tagDict, accessibilityData);
|
||||
|
||||
await this.#updateParentTag({
|
||||
structTreeParent,
|
||||
|
@ -358,6 +389,30 @@ class StructTreeRoot {
|
|||
return nextKey + 1;
|
||||
}
|
||||
|
||||
static #writeProperties(
|
||||
tagDict,
|
||||
{ type, title, lang, alt, expanded, actualText }
|
||||
) {
|
||||
// The structure type is required.
|
||||
tagDict.set("S", Name.get(type));
|
||||
|
||||
if (title) {
|
||||
tagDict.set("T", stringToAsciiOrUTF16BE(title));
|
||||
}
|
||||
if (lang) {
|
||||
tagDict.set("Lang", stringToAsciiOrUTF16BE(lang));
|
||||
}
|
||||
if (alt) {
|
||||
tagDict.set("Alt", stringToAsciiOrUTF16BE(alt));
|
||||
}
|
||||
if (expanded) {
|
||||
tagDict.set("E", stringToAsciiOrUTF16BE(expanded));
|
||||
}
|
||||
if (actualText) {
|
||||
tagDict.set("ActualText", stringToAsciiOrUTF16BE(actualText));
|
||||
}
|
||||
}
|
||||
|
||||
static #collectParents({ elements, xref, pageDict, numberTree }) {
|
||||
const idToElements = new Map();
|
||||
for (const element of elements) {
|
||||
|
@ -616,8 +671,40 @@ class StructTreePage {
|
|||
this.nodes = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect all the objects (i.e. tag) that are part of the page and return a
|
||||
* map of the structure element id to the object reference.
|
||||
* @param {Ref} pageRef
|
||||
* @returns {Map<number, Ref>}
|
||||
*/
|
||||
collectObjects(pageRef) {
|
||||
if (!this.root || !this.rootDict || !(pageRef instanceof Ref)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const parentTree = this.rootDict.get("ParentTree");
|
||||
if (!parentTree) {
|
||||
return null;
|
||||
}
|
||||
const ids = this.root.structParentIds?.get(pageRef);
|
||||
if (!ids) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const map = new Map();
|
||||
const numberTree = new NumberTree(parentTree, this.rootDict.xref);
|
||||
|
||||
for (const [elemId] of ids) {
|
||||
const obj = numberTree.getRaw(elemId);
|
||||
if (obj instanceof Ref) {
|
||||
map.set(elemId, obj);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
parse(pageRef) {
|
||||
if (!this.root || !this.rootDict) {
|
||||
if (!this.root || !this.rootDict || !(pageRef instanceof Ref)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -626,8 +713,7 @@ class StructTreePage {
|
|||
return;
|
||||
}
|
||||
const id = this.pageDict.get("StructParents");
|
||||
const ids =
|
||||
pageRef instanceof Ref && this.root.structParentIds?.get(pageRef);
|
||||
const ids = this.root.structParentIds?.get(pageRef);
|
||||
if (!Number.isInteger(id) && !ids) {
|
||||
return;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue