Skip to content

Commit

Permalink
[Editor] Add the possibility to save an updated stamp annotation (bug…
Browse files Browse the repository at this point in the history
… 1921291)
  • Loading branch information
calixteman committed Sep 26, 2024
1 parent c46ac3f commit 37ee41b
Show file tree
Hide file tree
Showing 7 changed files with 300 additions and 56 deletions.
70 changes: 45 additions & 25 deletions src/core/annotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -380,11 +380,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) {
Expand Down Expand Up @@ -487,11 +486,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);
Expand Down Expand Up @@ -652,17 +650,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,
Expand All @@ -681,8 +668,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.
Expand Down Expand Up @@ -1720,7 +1719,9 @@ class MarkupAnnotation extends Annotation {
static async createNewAnnotation(xref, annotation, dependencies, params) {
let oldAnnotation;
if (annotation.ref) {
oldAnnotation = (await xref.fetchIfRefAsync(annotation.ref)).clone();
annotation.oldAnnotation = oldAnnotation = (
await xref.fetchIfRefAsync(annotation.ref)
).clone();
} else {
annotation.ref = xref.getNewTemporaryRef();
}
Expand Down Expand Up @@ -1757,8 +1758,18 @@ class MarkupAnnotation extends Annotation {
annotation,
params
) {
let oldAnnotation;
if (annotation.ref) {
annotation.oldAnnotation = oldAnnotation = (
await xref.fetchIfRefAsync(annotation.ref)
).clone();
}
const ap = await this.createNewAppearanceStream(annotation, xref, params);
const annotationDict = this.createNewDict(annotation, xref, { ap });
const annotationDict = this.createNewDict(
annotation,
xref,
ap ? { ap } : { oldAnnotation }
);

const newAnnotation = new this.prototype.constructor({
dict: annotationDict,
Expand Down Expand Up @@ -4885,11 +4896,15 @@ class StampAnnotation extends MarkupAnnotation {
};
}

static createNewDict(annotation, xref, { apRef, ap }) {
static createNewDict(annotation, xref, { apRef, ap, oldAnnotation }) {
const { rect, rotation, user } = annotation;
const stamp = new Dict(xref);
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);
Expand All @@ -4915,6 +4930,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);
Expand Down
8 changes: 6 additions & 2 deletions src/core/name_number_tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ class NameOrNumberTree {
return map;
}

get(key) {
getRaw(key) {
if (!this.root) {
return null;
}
Expand Down Expand Up @@ -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 {
Expand Down
140 changes: 112 additions & 28 deletions src/core/struct_tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -209,8 +211,10 @@ class StructTreeRoot {

for (const element of elements) {
if (element.accessibilityData?.type) {
// Each tag must have a structure type.
element.parentTreeId = nextKey++;
if (!(element.accessibilityData.structParent >= 0)) {
// Each tag must have a structure type.
element.parentTreeId = nextKey++;
}
hasNothingToUpdate = false;
}
}
Expand Down Expand Up @@ -259,16 +263,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 === -Infinity) {
// No new tags were added.
return;
}

structTreeRoot.set("ParentTreeNextKey", newNextKey);

if (numsRef) {
cache.put(numsRef, nums);
Expand All @@ -285,17 +297,22 @@ class StructTreeRoot {
static async #writeKids({
newAnnotationsByPage,
structTreeRootRef,
structTreeRoot,
kids,
nums,
xref,
pdfManager,
newRefs,
cache,
}) {
const objr = Name.get("OBJR");
let nextKey = -Infinity;
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,
Expand All @@ -306,31 +323,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,
Expand Down Expand Up @@ -358,6 +387,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) {
Expand Down Expand Up @@ -616,8 +669,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;
}

Expand All @@ -626,8 +711,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;
}
Expand Down
1 change: 1 addition & 0 deletions test/pdfs/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -671,3 +671,4 @@
!bug1919513.pdf
!issue16038.pdf
!highlight_popup.pdf
!stamps.pdf
Binary file added test/pdfs/stamps.pdf
Binary file not shown.
Loading

0 comments on commit 37ee41b

Please sign in to comment.