Skip to content

Commit 8fc51dc

Browse files
committed
[Editor] Add the possibility to add a popup to an annotation when saving
When saving/printing, only update the properties which are provided and set a default value only when there is no pre-existing one.
1 parent e853a8f commit 8fc51dc

File tree

6 files changed

+181
-15
lines changed

6 files changed

+181
-15
lines changed

src/core/annotation.js

Lines changed: 78 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -428,7 +428,7 @@ class AnnotationFactory {
428428
}
429429

430430
return {
431-
annotations: await Promise.all(promises),
431+
annotations: (await Promise.all(promises)).flat(),
432432
};
433433
}
434434

@@ -1798,7 +1798,29 @@ class MarkupAnnotation extends Annotation {
17981798
data: annotationDict,
17991799
});
18001800

1801-
return { ref: annotationRef };
1801+
const retRef = { ref: annotationRef };
1802+
if (annotation.popup) {
1803+
const popup = annotation.popup;
1804+
if (popup.deleted) {
1805+
annotationDict.delete("Popup");
1806+
annotationDict.delete("Contents");
1807+
annotationDict.delete("RC");
1808+
return retRef;
1809+
}
1810+
const popupRef = (popup.ref ||= xref.getNewTemporaryRef());
1811+
popup.parent = annotationRef;
1812+
const popupDict = PopupAnnotation.createNewDict(popup, xref);
1813+
changes.put(popupRef, { data: popupDict });
1814+
annotationDict.setIfDefined(
1815+
"Contents",
1816+
stringToAsciiOrUTF16BE(popup.contents)
1817+
);
1818+
annotationDict.set("Popup", popupRef);
1819+
1820+
return [retRef, { ref: popupRef }];
1821+
}
1822+
1823+
return retRef;
18021824
}
18031825

18041826
static async createNewPrintAnnotation(
@@ -3880,6 +3902,22 @@ class PopupAnnotation extends Annotation {
38803902

38813903
this.data.open = !!dict.get("Open");
38823904
}
3905+
3906+
static createNewDict(annotation, xref, _params) {
3907+
const { oldAnnotation, rect, parent } = annotation;
3908+
const popup = oldAnnotation || new Dict(xref);
3909+
popup.setIfNotExists("Type", Name.get("Annot"));
3910+
popup.setIfNotExists("Subtype", Name.get("Popup"));
3911+
popup.setIfNotExists("Open", false);
3912+
popup.setIfArray("Rect", rect);
3913+
popup.set("Parent", parent);
3914+
3915+
return popup;
3916+
}
3917+
3918+
static async createNewAppearanceStream(annotation, xref, params) {
3919+
return null;
3920+
}
38833921
}
38843922

38853923
class FreeTextAnnotation extends MarkupAnnotation {
@@ -3947,18 +3985,27 @@ class FreeTextAnnotation extends MarkupAnnotation {
39473985
}
39483986

39493987
static createNewDict(annotation, xref, { apRef, ap }) {
3950-
const { color, fontSize, oldAnnotation, rect, rotation, user, value } =
3951-
annotation;
3988+
const {
3989+
color,
3990+
date,
3991+
fontSize,
3992+
oldAnnotation,
3993+
rect,
3994+
rotation,
3995+
user,
3996+
value,
3997+
} = annotation;
39523998
const freetext = oldAnnotation || new Dict(xref);
39533999
freetext.setIfNotExists("Type", Name.get("Annot"));
39544000
freetext.setIfNotExists("Subtype", Name.get("FreeText"));
4001+
freetext.set(
4002+
oldAnnotation ? "M" : "CreationDate",
4003+
`D:${getModificationDate(date)}`
4004+
);
39554005
if (oldAnnotation) {
3956-
freetext.set("M", `D:${getModificationDate()}`);
39574006
// TODO: We should try to generate a new RC from the content we've.
39584007
// For now we can just remove it to avoid any issues.
39594008
freetext.delete("RC");
3960-
} else {
3961-
freetext.set("CreationDate", `D:${getModificationDate()}`);
39624009
}
39634010
freetext.setIfArray("Rect", rect);
39644011
const da = `/Helv ${fontSize} Tf ${getPdfColor(color, /* isFill */ true)}`;
@@ -4492,6 +4539,7 @@ class InkAnnotation extends MarkupAnnotation {
44924539
const {
44934540
oldAnnotation,
44944541
color,
4542+
date,
44954543
opacity,
44964544
paths,
44974545
outlines,
@@ -4503,7 +4551,10 @@ class InkAnnotation extends MarkupAnnotation {
45034551
const ink = oldAnnotation || new Dict(xref);
45044552
ink.setIfNotExists("Type", Name.get("Annot"));
45054553
ink.setIfNotExists("Subtype", Name.get("Ink"));
4506-
ink.set(oldAnnotation ? "M" : "CreationDate", `D:${getModificationDate()}`);
4554+
ink.set(
4555+
oldAnnotation ? "M" : "CreationDate",
4556+
`D:${getModificationDate(date)}`
4557+
);
45074558
ink.setIfArray("Rect", rect);
45084559
ink.setIfArray("InkList", outlines?.points || paths?.points);
45094560
ink.setIfNotExists("F", 4);
@@ -4730,13 +4781,23 @@ class HighlightAnnotation extends MarkupAnnotation {
47304781
}
47314782

47324783
static createNewDict(annotation, xref, { apRef, ap }) {
4733-
const { color, oldAnnotation, opacity, rect, rotation, user, quadPoints } =
4734-
annotation;
4735-
const date = `D:${getModificationDate()}`;
4784+
const {
4785+
color,
4786+
date,
4787+
oldAnnotation,
4788+
opacity,
4789+
rect,
4790+
rotation,
4791+
user,
4792+
quadPoints,
4793+
} = annotation;
47364794
const highlight = oldAnnotation || new Dict(xref);
47374795
highlight.setIfNotExists("Type", Name.get("Annot"));
47384796
highlight.setIfNotExists("Subtype", Name.get("Highlight"));
4739-
highlight.set(oldAnnotation ? "M" : "CreationDate", date);
4797+
highlight.set(
4798+
oldAnnotation ? "M" : "CreationDate",
4799+
`D:${getModificationDate(date)}`
4800+
);
47404801
highlight.setIfArray("Rect", rect);
47414802
highlight.setIfNotExists("F", 4);
47424803
highlight.setIfNotExists("Border", [0, 0, 0]);
@@ -5046,12 +5107,14 @@ class StampAnnotation extends MarkupAnnotation {
50465107
}
50475108

50485109
static createNewDict(annotation, xref, { apRef, ap }) {
5049-
const { oldAnnotation, rect, rotation, user } = annotation;
5050-
const date = `D:${getModificationDate(annotation.date)}`;
5110+
const { date, oldAnnotation, rect, rotation, user } = annotation;
50515111
const stamp = oldAnnotation || new Dict(xref);
50525112
stamp.setIfNotExists("Type", Name.get("Annot"));
50535113
stamp.setIfNotExists("Subtype", Name.get("Stamp"));
5054-
stamp.set(oldAnnotation ? "M" : "CreationDate", date);
5114+
stamp.set(
5115+
oldAnnotation ? "M" : "CreationDate",
5116+
`D:${getModificationDate(date)}`
5117+
);
50555118
stamp.setIfArray("Rect", rect);
50565119
stamp.setIfNotExists("F", 4);
50575120
stamp.setIfNotExists("Border", [0, 0, 0]);

src/core/document.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,12 @@ class Page {
310310
}
311311
continue;
312312
}
313+
if (annotation.popup?.deleted) {
314+
const popupRef = Ref.fromString(annotation.popupRef);
315+
if (popupRef) {
316+
deletedAnnotations.put(popupRef, popupRef);
317+
}
318+
}
313319
existingAnnotations?.put(ref);
314320
annotation.ref = ref;
315321
promises.push(

src/shared/util.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1092,6 +1092,9 @@ function isArrayEqual(arr1, arr2) {
10921092
}
10931093

10941094
function getModificationDate(date = new Date()) {
1095+
if (!(date instanceof Date)) {
1096+
date = new Date(date);
1097+
}
10951098
const buffer = [
10961099
date.getUTCFullYear().toString(),
10971100
(date.getUTCMonth() + 1).toString().padStart(2, "0"),

test/test_manifest.json

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12171,5 +12171,47 @@
1217112171
"md5": "9fa985242476c642464d94893528e40f",
1217212172
"rounds": 1,
1217312173
"type": "eq"
12174+
},
12175+
{
12176+
"id": "highlights-popup",
12177+
"file": "pdfs/highlights.pdf",
12178+
"md5": "55c12c918f3e2253b39b42075cb38205",
12179+
"rounds": 1,
12180+
"type": "eq",
12181+
"lastPage": 1,
12182+
"save": true,
12183+
"annotations": true,
12184+
"annotationStorage": {
12185+
"pdfjs_internal_editor_0": {
12186+
"annotationType": 9,
12187+
"popup": {
12188+
"contents": "Hello PDF.js World"
12189+
},
12190+
"pageIndex": 0,
12191+
"date": "2013-11-12T14:15:16Z",
12192+
"id": "612R"
12193+
}
12194+
}
12195+
},
12196+
{
12197+
"id": "annotation-caret-ink-popup-deleted",
12198+
"file": "pdfs/annotation-caret-ink.pdf",
12199+
"md5": "6218ca235580d1975474c979e0128c2d",
12200+
"rounds": 1,
12201+
"type": "eq",
12202+
"lastPage": 1,
12203+
"save": true,
12204+
"annotations": true,
12205+
"annotationStorage": {
12206+
"pdfjs_internal_editor_0": {
12207+
"annotationType": 15,
12208+
"popup": {
12209+
"deleted": true
12210+
},
12211+
"pageIndex": 0,
12212+
"id": "25R",
12213+
"popupRef": "27R"
12214+
}
12215+
}
1217412216
}
1217512217
]

test/unit/annotation_spec.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4971,6 +4971,57 @@ describe("annotation", function () {
49714971
OPS.endAnnotation,
49724972
]);
49734973
});
4974+
4975+
it("should update an existing Highlight annotation", async function () {
4976+
const highlightDict = new Dict();
4977+
highlightDict.set("Type", Name.get("Annot"));
4978+
highlightDict.set("Subtype", Name.get("Highlight"));
4979+
highlightDict.set("Rotate", 0);
4980+
highlightDict.set("CreationDate", "D:20190423");
4981+
4982+
const highlightRef = Ref.get(143, 0);
4983+
const xref = (partialEvaluator.xref = new XRefMock([
4984+
{ ref: highlightRef, data: highlightDict },
4985+
]));
4986+
const changes = new RefSetCache();
4987+
4988+
const task = new WorkerTask("test Highlight update");
4989+
await AnnotationFactory.saveNewAnnotations(
4990+
partialEvaluator,
4991+
task,
4992+
[
4993+
{
4994+
annotationType: AnnotationEditorType.HIGHLIGHT,
4995+
rotation: 90,
4996+
popup: {
4997+
contents: "Hello PDF.js World !",
4998+
},
4999+
id: "143R",
5000+
ref: highlightRef,
5001+
oldAnnotation: highlightDict,
5002+
},
5003+
],
5004+
null,
5005+
changes
5006+
);
5007+
5008+
const data = await writeChanges(changes, xref);
5009+
5010+
const popup = data[0];
5011+
expect(popup.data).toEqual(
5012+
"1 0 obj\n" +
5013+
"<< /Type /Annot /Subtype /Popup /Open false /Parent 143 0 R>>\n" +
5014+
"endobj\n"
5015+
);
5016+
5017+
const base = data[1].data.replaceAll(/\(D:\d+\)/g, "(date)");
5018+
expect(base).toEqual(
5019+
"143 0 obj\n" +
5020+
"<< /Type /Annot /Subtype /Highlight /Rotate 90 /CreationDate (date) /M (date) " +
5021+
"/F 4 /Contents (Hello PDF.js World !) /Popup 1 0 R>>\n" +
5022+
"endobj\n"
5023+
);
5024+
});
49745025
});
49755026

49765027
describe("UnderlineAnnotation", function () {

test/unit/util_spec.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ describe("util", function () {
247247
it("should get a correctly formatted date", function () {
248248
const date = new Date(Date.UTC(3141, 5, 9, 2, 6, 53));
249249
expect(getModificationDate(date)).toEqual("31410609020653");
250+
expect(getModificationDate(date.toString())).toEqual("31410609020653");
250251
});
251252
});
252253

0 commit comments

Comments
 (0)