[Editor] Add the possibility to save an updated stamp annotation (bug 1921291)
This commit is contained in:
parent
0308b8075f
commit
c9050be863
@ -381,11 +381,10 @@ class AnnotationFactory {
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case AnnotationEditorType.STAMP:
|
case AnnotationEditorType.STAMP:
|
||||||
if (!isOffscreenCanvasSupported) {
|
const image = isOffscreenCanvasSupported
|
||||||
break;
|
? await imagePromises?.get(annotation.bitmapId)
|
||||||
}
|
: null;
|
||||||
const image = await imagePromises.get(annotation.bitmapId);
|
if (image?.imageStream) {
|
||||||
if (image.imageStream) {
|
|
||||||
const { imageStream, smaskStream } = image;
|
const { imageStream, smaskStream } = image;
|
||||||
const buffer = [];
|
const buffer = [];
|
||||||
if (smaskStream) {
|
if (smaskStream) {
|
||||||
@ -488,11 +487,10 @@ class AnnotationFactory {
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case AnnotationEditorType.STAMP:
|
case AnnotationEditorType.STAMP:
|
||||||
if (!options.isOffscreenCanvasSupported) {
|
const image = options.isOffscreenCanvasSupported
|
||||||
break;
|
? await imagePromises?.get(annotation.bitmapId)
|
||||||
}
|
: null;
|
||||||
const image = await imagePromises.get(annotation.bitmapId);
|
if (image?.imageStream) {
|
||||||
if (image.imageStream) {
|
|
||||||
const { imageStream, smaskStream } = image;
|
const { imageStream, smaskStream } = image;
|
||||||
if (smaskStream) {
|
if (smaskStream) {
|
||||||
imageStream.dict.set("SMask", smaskStream);
|
imageStream.dict.set("SMask", smaskStream);
|
||||||
@ -653,17 +651,6 @@ class Annotation {
|
|||||||
const isLocked = !!(this.flags & AnnotationFlag.LOCKED);
|
const isLocked = !!(this.flags & AnnotationFlag.LOCKED);
|
||||||
const isContentLocked = !!(this.flags & AnnotationFlag.LOCKEDCONTENTS);
|
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.
|
// Expose public properties using a data object.
|
||||||
this.data = {
|
this.data = {
|
||||||
annotationFlags: this.flags,
|
annotationFlags: this.flags,
|
||||||
@ -682,8 +669,20 @@ class Annotation {
|
|||||||
noRotate: !!(this.flags & AnnotationFlag.NOROTATE),
|
noRotate: !!(this.flags & AnnotationFlag.NOROTATE),
|
||||||
noHTML: isLocked && isContentLocked,
|
noHTML: isLocked && isContentLocked,
|
||||||
isEditable: false,
|
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) {
|
if (params.collectFields) {
|
||||||
// Fields can act as container for other fields and have
|
// Fields can act as container for other fields and have
|
||||||
// some actions even if no Annotation inherit from them.
|
// some actions even if no Annotation inherit from them.
|
||||||
@ -1751,10 +1750,7 @@ class MarkupAnnotation extends Annotation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async createNewAnnotation(xref, annotation, dependencies, params) {
|
static async createNewAnnotation(xref, annotation, dependencies, params) {
|
||||||
let oldAnnotation;
|
if (!annotation.ref) {
|
||||||
if (annotation.ref) {
|
|
||||||
oldAnnotation = (await xref.fetchIfRefAsync(annotation.ref)).clone();
|
|
||||||
} else {
|
|
||||||
annotation.ref = xref.getNewTemporaryRef();
|
annotation.ref = xref.getNewTemporaryRef();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1767,12 +1763,11 @@ class MarkupAnnotation extends Annotation {
|
|||||||
const apRef = xref.getNewTemporaryRef();
|
const apRef = xref.getNewTemporaryRef();
|
||||||
annotationDict = this.createNewDict(annotation, xref, {
|
annotationDict = this.createNewDict(annotation, xref, {
|
||||||
apRef,
|
apRef,
|
||||||
oldAnnotation,
|
|
||||||
});
|
});
|
||||||
await writeObject(apRef, ap, buffer, xref);
|
await writeObject(apRef, ap, buffer, xref);
|
||||||
dependencies.push({ ref: apRef, data: buffer.join("") });
|
dependencies.push({ ref: apRef, data: buffer.join("") });
|
||||||
} else {
|
} else {
|
||||||
annotationDict = this.createNewDict(annotation, xref, { oldAnnotation });
|
annotationDict = this.createNewDict(annotation, xref, {});
|
||||||
}
|
}
|
||||||
if (Number.isInteger(annotation.parentTreeId)) {
|
if (Number.isInteger(annotation.parentTreeId)) {
|
||||||
annotationDict.set("StructParent", annotation.parentTreeId);
|
annotationDict.set("StructParent", annotation.parentTreeId);
|
||||||
@ -1791,7 +1786,11 @@ class MarkupAnnotation extends Annotation {
|
|||||||
params
|
params
|
||||||
) {
|
) {
|
||||||
const ap = await this.createNewAppearanceStream(annotation, xref, 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({
|
const newAnnotation = new this.prototype.constructor({
|
||||||
dict: annotationDict,
|
dict: annotationDict,
|
||||||
@ -3904,8 +3903,9 @@ class FreeTextAnnotation extends MarkupAnnotation {
|
|||||||
return this._hasAppearance;
|
return this._hasAppearance;
|
||||||
}
|
}
|
||||||
|
|
||||||
static createNewDict(annotation, xref, { apRef, ap, oldAnnotation }) {
|
static createNewDict(annotation, xref, { apRef, ap }) {
|
||||||
const { color, fontSize, rect, rotation, user, value } = annotation;
|
const { color, fontSize, oldAnnotation, rect, rotation, user, value } =
|
||||||
|
annotation;
|
||||||
const freetext = oldAnnotation || new Dict(xref);
|
const freetext = oldAnnotation || new Dict(xref);
|
||||||
freetext.set("Type", Name.get("Annot"));
|
freetext.set("Type", Name.get("Annot"));
|
||||||
freetext.set("Subtype", Name.get("FreeText"));
|
freetext.set("Subtype", Name.get("FreeText"));
|
||||||
@ -4646,8 +4646,9 @@ class HighlightAnnotation extends MarkupAnnotation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static createNewDict(annotation, xref, { apRef, ap, oldAnnotation }) {
|
static createNewDict(annotation, xref, { apRef, ap }) {
|
||||||
const { color, opacity, rect, rotation, user, quadPoints } = annotation;
|
const { color, oldAnnotation, opacity, rect, rotation, user, quadPoints } =
|
||||||
|
annotation;
|
||||||
const highlight = oldAnnotation || new Dict(xref);
|
const highlight = oldAnnotation || new Dict(xref);
|
||||||
highlight.set("Type", Name.get("Annot"));
|
highlight.set("Type", Name.get("Annot"));
|
||||||
highlight.set("Subtype", Name.get("Highlight"));
|
highlight.set("Subtype", Name.get("Highlight"));
|
||||||
@ -4943,10 +4944,14 @@ class StampAnnotation extends MarkupAnnotation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static createNewDict(annotation, xref, { apRef, ap }) {
|
static createNewDict(annotation, xref, { apRef, ap }) {
|
||||||
const { rect, rotation, user } = annotation;
|
const { oldAnnotation, rect, rotation, user } = annotation;
|
||||||
const stamp = new Dict(xref);
|
const stamp = oldAnnotation || new Dict(xref);
|
||||||
stamp.set("Type", Name.get("Annot"));
|
stamp.set("Type", Name.get("Annot"));
|
||||||
stamp.set("Subtype", Name.get("Stamp"));
|
stamp.set("Subtype", Name.get("Stamp"));
|
||||||
|
stamp.set(
|
||||||
|
oldAnnotation ? "M" : "CreationDate",
|
||||||
|
`D:${getModificationDate()}`
|
||||||
|
);
|
||||||
stamp.set("CreationDate", `D:${getModificationDate()}`);
|
stamp.set("CreationDate", `D:${getModificationDate()}`);
|
||||||
stamp.set("Rect", rect);
|
stamp.set("Rect", rect);
|
||||||
stamp.set("F", 4);
|
stamp.set("F", 4);
|
||||||
@ -4972,6 +4977,11 @@ class StampAnnotation extends MarkupAnnotation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async createNewAppearanceStream(annotation, xref, params) {
|
static async createNewAppearanceStream(annotation, xref, params) {
|
||||||
|
if (annotation.oldAnnotation) {
|
||||||
|
// We'll use the AP we already have.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const { rotation } = annotation;
|
const { rotation } = annotation;
|
||||||
const { imageRef, width, height } = params.image;
|
const { imageRef, width, height } = params.image;
|
||||||
const resources = new Dict(xref);
|
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) {
|
for (const annotation of annotations) {
|
||||||
if (annotation.id) {
|
if (annotation.id) {
|
||||||
const ref = Ref.fromString(annotation.id);
|
const ref = Ref.fromString(annotation.id);
|
||||||
@ -294,9 +295,22 @@ class Page {
|
|||||||
}
|
}
|
||||||
existingAnnotations?.put(ref);
|
existingAnnotations?.put(ref);
|
||||||
annotation.ref = 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;
|
delete annotation.id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
await Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveNewAnnotations(handler, task, annotations, imagePromises) {
|
async saveNewAnnotations(handler, task, annotations, imagePromises) {
|
||||||
@ -319,7 +333,11 @@ class Page {
|
|||||||
|
|
||||||
const deletedAnnotations = new RefSetCache();
|
const deletedAnnotations = new RefSetCache();
|
||||||
const existingAnnotations = new RefSet();
|
const existingAnnotations = new RefSet();
|
||||||
this.#replaceIdByRef(annotations, deletedAnnotations, existingAnnotations);
|
await this.#replaceIdByRef(
|
||||||
|
annotations,
|
||||||
|
deletedAnnotations,
|
||||||
|
existingAnnotations
|
||||||
|
);
|
||||||
|
|
||||||
const pageDict = this.pageDict;
|
const pageDict = this.pageDict;
|
||||||
const annotationsArray = this.annotations.filter(
|
const annotationsArray = this.annotations.filter(
|
||||||
@ -489,23 +507,23 @@ class Page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
deletedAnnotations = new RefSet();
|
deletedAnnotations = new RefSet();
|
||||||
this.#replaceIdByRef(newAnnots, deletedAnnotations, null);
|
|
||||||
|
|
||||||
newAnnotationsPromise = annotationGlobalsPromise.then(
|
newAnnotationsPromise = Promise.all([
|
||||||
annotationGlobals => {
|
annotationGlobalsPromise,
|
||||||
if (!annotationGlobals) {
|
this.#replaceIdByRef(newAnnots, deletedAnnotations, null),
|
||||||
return null;
|
]).then(([annotationGlobals]) => {
|
||||||
}
|
if (!annotationGlobals) {
|
||||||
|
return null;
|
||||||
return AnnotationFactory.printNewAnnotations(
|
|
||||||
annotationGlobals,
|
|
||||||
partialEvaluator,
|
|
||||||
task,
|
|
||||||
newAnnots,
|
|
||||||
imagePromises
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
return AnnotationFactory.printNewAnnotations(
|
||||||
|
annotationGlobals,
|
||||||
|
partialEvaluator,
|
||||||
|
task,
|
||||||
|
newAnnots,
|
||||||
|
imagePromises
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const pageListPromise = Promise.all([
|
const pageListPromise = Promise.all([
|
||||||
|
|||||||
@ -74,7 +74,7 @@ class NameOrNumberTree {
|
|||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
get(key) {
|
getRaw(key) {
|
||||||
if (!this.root) {
|
if (!this.root) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -135,12 +135,16 @@ class NameOrNumberTree {
|
|||||||
} else if (key > currentKey) {
|
} else if (key > currentKey) {
|
||||||
l = m + 2;
|
l = m + 2;
|
||||||
} else {
|
} else {
|
||||||
return xref.fetchIfRef(entries[m + 1]);
|
return entries[m + 1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get(key) {
|
||||||
|
return this.xref.fetchIfRef(this.getRaw(key));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class NameTree extends NameOrNumberTree {
|
class NameTree extends NameOrNumberTree {
|
||||||
|
|||||||
@ -141,10 +141,12 @@ class StructTreeRoot {
|
|||||||
const nextKey = await this.#writeKids({
|
const nextKey = await this.#writeKids({
|
||||||
newAnnotationsByPage,
|
newAnnotationsByPage,
|
||||||
structTreeRootRef,
|
structTreeRootRef,
|
||||||
|
structTreeRoot: null,
|
||||||
kids,
|
kids,
|
||||||
nums,
|
nums,
|
||||||
xref,
|
xref,
|
||||||
pdfManager,
|
pdfManager,
|
||||||
|
newRefs,
|
||||||
cache,
|
cache,
|
||||||
});
|
});
|
||||||
structTreeRoot.set("ParentTreeNextKey", nextKey);
|
structTreeRoot.set("ParentTreeNextKey", nextKey);
|
||||||
@ -209,8 +211,12 @@ class StructTreeRoot {
|
|||||||
|
|
||||||
for (const element of elements) {
|
for (const element of elements) {
|
||||||
if (element.accessibilityData?.type) {
|
if (element.accessibilityData?.type) {
|
||||||
// Each tag must have a structure type.
|
// structParent can be undefined and in this case the positivity check
|
||||||
element.parentTreeId = nextKey++;
|
// 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;
|
hasNothingToUpdate = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -259,16 +265,24 @@ class StructTreeRoot {
|
|||||||
parentTree.set("Nums", nums);
|
parentTree.set("Nums", nums);
|
||||||
}
|
}
|
||||||
|
|
||||||
const newNextkey = await StructTreeRoot.#writeKids({
|
const newNextKey = await StructTreeRoot.#writeKids({
|
||||||
newAnnotationsByPage,
|
newAnnotationsByPage,
|
||||||
structTreeRootRef,
|
structTreeRootRef,
|
||||||
|
structTreeRoot: this,
|
||||||
kids: null,
|
kids: null,
|
||||||
nums,
|
nums,
|
||||||
xref,
|
xref,
|
||||||
pdfManager,
|
pdfManager,
|
||||||
|
newRefs,
|
||||||
cache,
|
cache,
|
||||||
});
|
});
|
||||||
structTreeRoot.set("ParentTreeNextKey", newNextkey);
|
|
||||||
|
if (newNextKey === -1) {
|
||||||
|
// No new tags were added.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
structTreeRoot.set("ParentTreeNextKey", newNextKey);
|
||||||
|
|
||||||
if (numsRef) {
|
if (numsRef) {
|
||||||
cache.put(numsRef, nums);
|
cache.put(numsRef, nums);
|
||||||
@ -285,17 +299,22 @@ class StructTreeRoot {
|
|||||||
static async #writeKids({
|
static async #writeKids({
|
||||||
newAnnotationsByPage,
|
newAnnotationsByPage,
|
||||||
structTreeRootRef,
|
structTreeRootRef,
|
||||||
|
structTreeRoot,
|
||||||
kids,
|
kids,
|
||||||
nums,
|
nums,
|
||||||
xref,
|
xref,
|
||||||
pdfManager,
|
pdfManager,
|
||||||
|
newRefs,
|
||||||
cache,
|
cache,
|
||||||
}) {
|
}) {
|
||||||
const objr = Name.get("OBJR");
|
const objr = Name.get("OBJR");
|
||||||
let nextKey = -Infinity;
|
let nextKey = -1;
|
||||||
|
let structTreePageObjs;
|
||||||
|
const buffer = [];
|
||||||
|
|
||||||
for (const [pageIndex, elements] of newAnnotationsByPage) {
|
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;
|
const isPageRef = pageRef instanceof Ref;
|
||||||
for (const {
|
for (const {
|
||||||
accessibilityData,
|
accessibilityData,
|
||||||
@ -306,31 +325,43 @@ class StructTreeRoot {
|
|||||||
if (!accessibilityData?.type) {
|
if (!accessibilityData?.type) {
|
||||||
continue;
|
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);
|
nextKey = Math.max(nextKey, parentTreeId);
|
||||||
|
|
||||||
const tagRef = xref.getNewTemporaryRef();
|
const tagRef = xref.getNewTemporaryRef();
|
||||||
const tagDict = new Dict(xref);
|
const tagDict = new Dict(xref);
|
||||||
|
|
||||||
// The structure type is required.
|
StructTreeRoot.#writeProperties(tagDict, accessibilityData);
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.#updateParentTag({
|
await this.#updateParentTag({
|
||||||
structTreeParent,
|
structTreeParent,
|
||||||
@ -358,6 +389,30 @@ class StructTreeRoot {
|
|||||||
return nextKey + 1;
|
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 }) {
|
static #collectParents({ elements, xref, pageDict, numberTree }) {
|
||||||
const idToElements = new Map();
|
const idToElements = new Map();
|
||||||
for (const element of elements) {
|
for (const element of elements) {
|
||||||
@ -616,8 +671,40 @@ class StructTreePage {
|
|||||||
this.nodes = [];
|
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) {
|
parse(pageRef) {
|
||||||
if (!this.root || !this.rootDict) {
|
if (!this.root || !this.rootDict || !(pageRef instanceof Ref)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -626,8 +713,7 @@ class StructTreePage {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const id = this.pageDict.get("StructParents");
|
const id = this.pageDict.get("StructParents");
|
||||||
const ids =
|
const ids = this.root.structParentIds?.get(pageRef);
|
||||||
pageRef instanceof Ref && this.root.structParentIds?.get(pageRef);
|
|
||||||
if (!Number.isInteger(id) && !ids) {
|
if (!Number.isInteger(id) && !ids) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
1
test/pdfs/.gitignore
vendored
1
test/pdfs/.gitignore
vendored
@ -672,3 +672,4 @@
|
|||||||
!issue16038.pdf
|
!issue16038.pdf
|
||||||
!highlight_popup.pdf
|
!highlight_popup.pdf
|
||||||
!issue18072.pdf
|
!issue18072.pdf
|
||||||
|
!stamps.pdf
|
||||||
|
|||||||
BIN
test/pdfs/stamps.pdf
Executable file
BIN
test/pdfs/stamps.pdf
Executable file
Binary file not shown.
@ -10578,5 +10578,94 @@
|
|||||||
"noPrint": false
|
"noPrint": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "stamps-editor-save-print",
|
||||||
|
"file": "pdfs/stamps.pdf",
|
||||||
|
"md5": "0f8e16d204d4863be159f77aa8045938",
|
||||||
|
"rounds": 1,
|
||||||
|
"lastPage": 1,
|
||||||
|
"type": "eq",
|
||||||
|
"save": true,
|
||||||
|
"print": true,
|
||||||
|
"annotationStorage": {
|
||||||
|
"pdfjs_internal_editor_0": {
|
||||||
|
"annotationType": 13,
|
||||||
|
"pageIndex": 0,
|
||||||
|
"rect": [87, 72, 251, 382],
|
||||||
|
"rotation": 0,
|
||||||
|
"isSvg": false,
|
||||||
|
"structTreeParentId": null,
|
||||||
|
"id": "25R"
|
||||||
|
},
|
||||||
|
"pdfjs_internal_editor_1": {
|
||||||
|
"annotationType": 13,
|
||||||
|
"pageIndex": 0,
|
||||||
|
"rect": [350, 337, 524, 522],
|
||||||
|
"rotation": 0,
|
||||||
|
"isSvg": false,
|
||||||
|
"structTreeParentId": null,
|
||||||
|
"id": "34R"
|
||||||
|
},
|
||||||
|
"pdfjs_internal_editor_2": {
|
||||||
|
"annotationType": 13,
|
||||||
|
"pageIndex": 0,
|
||||||
|
"rect": [82, 608, 234, 735],
|
||||||
|
"rotation": 0,
|
||||||
|
"isSvg": false,
|
||||||
|
"structTreeParentId": null,
|
||||||
|
"id": "58R"
|
||||||
|
},
|
||||||
|
"pdfjs_internal_editor_3": {
|
||||||
|
"pageIndex": 0,
|
||||||
|
"deleted": true,
|
||||||
|
"id": "37R",
|
||||||
|
"popupRef": "44R"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "stamps-editor-print",
|
||||||
|
"file": "pdfs/stamps.pdf",
|
||||||
|
"md5": "0f8e16d204d4863be159f77aa8045938",
|
||||||
|
"rounds": 1,
|
||||||
|
"lastPage": 1,
|
||||||
|
"type": "eq",
|
||||||
|
"print": true,
|
||||||
|
"annotationStorage": {
|
||||||
|
"pdfjs_internal_editor_0": {
|
||||||
|
"annotationType": 13,
|
||||||
|
"pageIndex": 0,
|
||||||
|
"rect": [87, 72, 251, 382],
|
||||||
|
"rotation": 0,
|
||||||
|
"isSvg": false,
|
||||||
|
"structTreeParentId": null,
|
||||||
|
"id": "25R"
|
||||||
|
},
|
||||||
|
"pdfjs_internal_editor_1": {
|
||||||
|
"annotationType": 13,
|
||||||
|
"pageIndex": 0,
|
||||||
|
"rect": [350, 337, 524, 522],
|
||||||
|
"rotation": 0,
|
||||||
|
"isSvg": false,
|
||||||
|
"structTreeParentId": null,
|
||||||
|
"id": "34R"
|
||||||
|
},
|
||||||
|
"pdfjs_internal_editor_2": {
|
||||||
|
"annotationType": 13,
|
||||||
|
"pageIndex": 0,
|
||||||
|
"rect": [82, 608, 234, 735],
|
||||||
|
"rotation": 0,
|
||||||
|
"isSvg": false,
|
||||||
|
"structTreeParentId": null,
|
||||||
|
"id": "58R"
|
||||||
|
},
|
||||||
|
"pdfjs_internal_editor_3": {
|
||||||
|
"pageIndex": 0,
|
||||||
|
"deleted": true,
|
||||||
|
"id": "37R",
|
||||||
|
"popupRef": "44R"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@ -4283,6 +4283,7 @@ describe("annotation", function () {
|
|||||||
value: "Hello PDF.js World !",
|
value: "Hello PDF.js World !",
|
||||||
id: "143R",
|
id: "143R",
|
||||||
ref: freeTextRef,
|
ref: freeTextRef,
|
||||||
|
oldAnnotation: freeTextDict,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|||||||
@ -2800,7 +2800,7 @@ describe("api", function () {
|
|||||||
await loadingTask.destroy();
|
await loadingTask.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("write a new stamp annotation in a non-tagged pdf, save and check that the structure tree", async function () {
|
it("write a new stamp annotation in a non-tagged pdf, save and check the structure tree", async function () {
|
||||||
if (isNodeJS) {
|
if (isNodeJS) {
|
||||||
pending("Cannot create a bitmap from Node.js.");
|
pending("Cannot create a bitmap from Node.js.");
|
||||||
}
|
}
|
||||||
@ -2949,6 +2949,52 @@ describe("api", function () {
|
|||||||
await loadingTask.destroy();
|
await loadingTask.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("write an updated stamp annotation in a tagged pdf, save and check the structure tree", async function () {
|
||||||
|
let loadingTask = getDocument(buildGetDocumentParams("stamps.pdf"));
|
||||||
|
let pdfDoc = await loadingTask.promise;
|
||||||
|
pdfDoc.annotationStorage.setValue("pdfjs_internal_editor_1", {
|
||||||
|
annotationType: AnnotationEditorType.STAMP,
|
||||||
|
pageIndex: 0,
|
||||||
|
rect: [72.5, 134.17, 246.49, 318.7],
|
||||||
|
rotation: 0,
|
||||||
|
isSvg: false,
|
||||||
|
structTreeParentId: null,
|
||||||
|
accessibilityData: {
|
||||||
|
type: "Figure",
|
||||||
|
alt: "The Firefox logo",
|
||||||
|
structParent: -1,
|
||||||
|
},
|
||||||
|
id: "34R",
|
||||||
|
});
|
||||||
|
pdfDoc.annotationStorage.setValue("pdfjs_internal_editor_4", {
|
||||||
|
annotationType: AnnotationEditorType.STAMP,
|
||||||
|
pageIndex: 0,
|
||||||
|
rect: [335.1, 394.83, 487.17, 521.47],
|
||||||
|
rotation: 0,
|
||||||
|
isSvg: false,
|
||||||
|
structTreeParentId: null,
|
||||||
|
accessibilityData: {
|
||||||
|
type: "Figure",
|
||||||
|
alt: "An elephant with a red hat",
|
||||||
|
structParent: 0,
|
||||||
|
},
|
||||||
|
id: "58R",
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await pdfDoc.saveDocument();
|
||||||
|
await loadingTask.destroy();
|
||||||
|
|
||||||
|
loadingTask = getDocument(data);
|
||||||
|
pdfDoc = await loadingTask.promise;
|
||||||
|
const page = await pdfDoc.getPage(1);
|
||||||
|
const tree = await page.getStructTree();
|
||||||
|
|
||||||
|
expect(tree.children[0].alt).toEqual("An elephant with a red hat");
|
||||||
|
expect(tree.children[1].alt).toEqual("The Firefox logo");
|
||||||
|
|
||||||
|
await loadingTask.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
it("read content from multiline textfield containing an empty line", async function () {
|
it("read content from multiline textfield containing an empty line", async function () {
|
||||||
const loadingTask = getDocument(buildGetDocumentParams("issue17492.pdf"));
|
const loadingTask = getDocument(buildGetDocumentParams("issue17492.pdf"));
|
||||||
const pdfDoc = await loadingTask.promise;
|
const pdfDoc = await loadingTask.promise;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user