Skip to content

Commit 65fa567

Browse files
committed
[Editor] Add support for printing newly added Ink annotations
1 parent 05cab5c commit 65fa567

File tree

7 files changed

+271
-91
lines changed

7 files changed

+271
-91
lines changed

src/core/annotation.js

+61-14
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,27 @@ class AnnotationFactory {
294294
dependencies,
295295
};
296296
}
297+
298+
static async printNewAnnotations(evaluator, task, annotations) {
299+
if (!annotations) {
300+
return null;
301+
}
302+
303+
const xref = evaluator.xref;
304+
const promises = [];
305+
for (const annotation of annotations) {
306+
switch (annotation.annotationType) {
307+
case AnnotationEditorType.FREETEXT:
308+
break;
309+
case AnnotationEditorType.INK:
310+
promises.push(
311+
InkAnnotation.createNewPrintAnnotation(annotation, xref)
312+
);
313+
}
314+
}
315+
316+
return Promise.all(promises);
317+
}
297318
}
298319

299320
function getRgbColor(color, defaultColor = new Uint8ClampedArray(3)) {
@@ -3431,15 +3452,7 @@ class InkAnnotation extends MarkupAnnotation {
34313452
}
34323453
}
34333454

3434-
static async createNewAnnotation(
3435-
xref,
3436-
evaluator,
3437-
task,
3438-
annotation,
3439-
results,
3440-
others
3441-
) {
3442-
const inkRef = xref.getNewRef();
3455+
static createInkDict(annotation, xref, { apRef, ap }) {
34433456
const ink = new Dict(xref);
34443457
ink.set("Type", Name.get("Annot"));
34453458
ink.set("Subtype", Name.get("Ink"));
@@ -3453,6 +3466,19 @@ class InkAnnotation extends MarkupAnnotation {
34533466
ink.set("Border", [0, 0, 0]);
34543467
ink.set("Rotate", 0);
34553468

3469+
const n = new Dict(xref);
3470+
ink.set("AP", n);
3471+
3472+
if (apRef) {
3473+
n.set("N", apRef);
3474+
} else {
3475+
n.set("N", ap);
3476+
}
3477+
3478+
return ink;
3479+
}
3480+
3481+
static createNewAppearanceStream(annotation, xref) {
34563482
const [x1, y1, x2, y2] = annotation.rect;
34573483
const w = x2 - x1;
34583484
const h = y2 - y1;
@@ -3489,18 +3515,29 @@ class InkAnnotation extends MarkupAnnotation {
34893515
const ap = new StringStream(appearance);
34903516
ap.dict = appearanceStreamDict;
34913517

3492-
buffer.length = 0;
3518+
return ap;
3519+
}
3520+
3521+
static async createNewAnnotation(
3522+
xref,
3523+
evaluator,
3524+
task,
3525+
annotation,
3526+
results,
3527+
others
3528+
) {
3529+
const inkRef = xref.getNewRef();
34933530
const apRef = xref.getNewRef();
3531+
const ink = this.createInkDict(annotation, xref, { apRef });
3532+
const ap = this.createNewAppearanceStream(annotation, xref);
3533+
3534+
const buffer = [];
34943535
let transform = xref.encrypt
34953536
? xref.encrypt.createCipherTransform(apRef.num, apRef.gen)
34963537
: null;
34973538
writeObject(apRef, ap, buffer, transform);
34983539
others.push({ ref: apRef, data: buffer.join("") });
34993540

3500-
const n = new Dict(xref);
3501-
n.set("N", apRef);
3502-
ink.set("AP", n);
3503-
35043541
buffer.length = 0;
35053542
transform = xref.encrypt
35063543
? xref.encrypt.createCipherTransform(inkRef.num, inkRef.gen)
@@ -3509,6 +3546,16 @@ class InkAnnotation extends MarkupAnnotation {
35093546

35103547
results.push({ ref: inkRef, data: buffer.join("") });
35113548
}
3549+
3550+
static async createNewPrintAnnotation(annotation, xref) {
3551+
const ap = this.createNewAppearanceStream(annotation, xref);
3552+
const ink = this.createInkDict(annotation, xref, { ap });
3553+
3554+
return new InkAnnotation({
3555+
dict: ink,
3556+
xref,
3557+
});
3558+
}
35123559
}
35133560

35143561
class HighlightAnnotation extends MarkupAnnotation {

src/core/core_utils.js

+23
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
*/
1515

1616
import {
17+
AnnotationEditorPrefix,
1718
assert,
1819
BaseException,
1920
FontType,
@@ -548,6 +549,27 @@ function numberToString(value) {
548549
return value.toFixed(2);
549550
}
550551

552+
function getNewAnnotationsMap(annotationStorage) {
553+
if (!annotationStorage) {
554+
return null;
555+
}
556+
const newAnnotationsByPage = new Map();
557+
// The concept of page in a XFA is very different, so
558+
// editing is just not implemented.
559+
for (const [key, value] of annotationStorage) {
560+
if (!key.startsWith(AnnotationEditorPrefix)) {
561+
continue;
562+
}
563+
let annotations = newAnnotationsByPage.get(value.pageIndex);
564+
if (!annotations) {
565+
annotations = [];
566+
newAnnotationsByPage.set(value.pageIndex, annotations);
567+
}
568+
annotations.push(value);
569+
}
570+
return newAnnotationsByPage.size > 0 ? newAnnotationsByPage : null;
571+
}
572+
551573
export {
552574
collectActions,
553575
DocStats,
@@ -556,6 +578,7 @@ export {
556578
getArrayLookupTableFactory,
557579
getInheritableProperty,
558580
getLookupTableFactory,
581+
getNewAnnotationsMap,
559582
isWhiteSpace,
560583
log2,
561584
MissingDataException,

src/core/document.js

+71-48
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
import {
3434
collectActions,
3535
getInheritableProperty,
36+
getNewAnnotationsMap,
3637
isWhiteSpace,
3738
MissingDataException,
3839
validateCSSFont,
@@ -312,6 +313,8 @@ class Page {
312313
{ ref: this.ref, data: buffer.join("") },
313314
...newData.annotations
314315
);
316+
317+
this.xref.resetNewRef();
315318
return objects;
316319
}
317320

@@ -397,6 +400,21 @@ class Page {
397400
options: this.evaluatorOptions,
398401
});
399402

403+
const newAnnotationsByPage = !this.xfaFactory
404+
? getNewAnnotationsMap(annotationStorage)
405+
: null;
406+
407+
let newAnnotationsPromise = Promise.resolve(null);
408+
if (newAnnotationsByPage) {
409+
const newAnnotations = newAnnotationsByPage.get(this.pageIndex);
410+
if (newAnnotations) {
411+
newAnnotationsPromise = AnnotationFactory.printNewAnnotations(
412+
partialEvaluator,
413+
task,
414+
newAnnotations
415+
);
416+
}
417+
}
400418
const dataPromises = Promise.all([contentStreamPromise, resourcesPromise]);
401419
const pageListPromise = dataPromises.then(([contentStream]) => {
402420
const opList = new OperatorList(intent, sink);
@@ -424,58 +442,63 @@ class Page {
424442

425443
// Fetch the page's annotations and add their operator lists to the
426444
// page's operator list to render them.
427-
return Promise.all([pageListPromise, this._parsedAnnotations]).then(
428-
function ([pageOpList, annotations]) {
445+
return Promise.all([
446+
pageListPromise,
447+
this._parsedAnnotations,
448+
newAnnotationsPromise,
449+
]).then(function ([pageOpList, annotations, newAnnotations]) {
450+
if (newAnnotations) {
451+
annotations = annotations.concat(newAnnotations);
452+
}
453+
if (
454+
annotations.length === 0 ||
455+
intent & RenderingIntentFlag.ANNOTATIONS_DISABLE
456+
) {
457+
pageOpList.flush(true);
458+
return { length: pageOpList.totalLength };
459+
}
460+
const renderForms = !!(intent & RenderingIntentFlag.ANNOTATIONS_FORMS),
461+
intentAny = !!(intent & RenderingIntentFlag.ANY),
462+
intentDisplay = !!(intent & RenderingIntentFlag.DISPLAY),
463+
intentPrint = !!(intent & RenderingIntentFlag.PRINT);
464+
465+
// Collect the operator list promises for the annotations. Each promise
466+
// is resolved with the complete operator list for a single annotation.
467+
const opListPromises = [];
468+
for (const annotation of annotations) {
429469
if (
430-
annotations.length === 0 ||
431-
intent & RenderingIntentFlag.ANNOTATIONS_DISABLE
470+
intentAny ||
471+
(intentDisplay && annotation.mustBeViewed(annotationStorage)) ||
472+
(intentPrint && annotation.mustBePrinted(annotationStorage))
432473
) {
433-
pageOpList.flush(true);
434-
return { length: pageOpList.totalLength };
435-
}
436-
const renderForms = !!(intent & RenderingIntentFlag.ANNOTATIONS_FORMS),
437-
intentAny = !!(intent & RenderingIntentFlag.ANY),
438-
intentDisplay = !!(intent & RenderingIntentFlag.DISPLAY),
439-
intentPrint = !!(intent & RenderingIntentFlag.PRINT);
440-
441-
// Collect the operator list promises for the annotations. Each promise
442-
// is resolved with the complete operator list for a single annotation.
443-
const opListPromises = [];
444-
for (const annotation of annotations) {
445-
if (
446-
intentAny ||
447-
(intentDisplay && annotation.mustBeViewed(annotationStorage)) ||
448-
(intentPrint && annotation.mustBePrinted(annotationStorage))
449-
) {
450-
opListPromises.push(
451-
annotation
452-
.getOperatorList(
453-
partialEvaluator,
454-
task,
455-
intent,
456-
renderForms,
457-
annotationStorage
458-
)
459-
.catch(function (reason) {
460-
warn(
461-
"getOperatorList - ignoring annotation data during " +
462-
`"${task.name}" task: "${reason}".`
463-
);
464-
return null;
465-
})
466-
);
467-
}
474+
opListPromises.push(
475+
annotation
476+
.getOperatorList(
477+
partialEvaluator,
478+
task,
479+
intent,
480+
renderForms,
481+
annotationStorage
482+
)
483+
.catch(function (reason) {
484+
warn(
485+
"getOperatorList - ignoring annotation data during " +
486+
`"${task.name}" task: "${reason}".`
487+
);
488+
return null;
489+
})
490+
);
468491
}
469-
470-
return Promise.all(opListPromises).then(function (opLists) {
471-
for (const opList of opLists) {
472-
pageOpList.addOpList(opList);
473-
}
474-
pageOpList.flush(true);
475-
return { length: pageOpList.totalLength };
476-
});
477492
}
478-
);
493+
494+
return Promise.all(opListPromises).then(function (opLists) {
495+
for (const opList of opLists) {
496+
pageOpList.addOpList(opList);
497+
}
498+
pageOpList.flush(true);
499+
return { length: pageOpList.totalLength };
500+
});
501+
});
479502
}
480503

481504
extractTextContent({

src/core/worker.js

+17-29
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515

1616
import {
1717
AbortException,
18-
AnnotationEditorPrefix,
1918
arrayByteLength,
2019
arraysToBytes,
2120
createPromiseCapability,
@@ -33,13 +32,13 @@ import {
3332
warn,
3433
} from "../shared/util.js";
3534
import { Dict, Ref } from "./primitives.js";
35+
import { getNewAnnotationsMap, XRefParseException } from "./core_utils.js";
3636
import { LocalPdfManager, NetworkPdfManager } from "./pdf_manager.js";
3737
import { clearGlobalCaches } from "./cleanup_helper.js";
3838
import { incrementalUpdate } from "./writer.js";
3939
import { isNodeJS } from "../shared/is_node.js";
4040
import { MessageHandler } from "../shared/message_handler.js";
4141
import { PDFWorkerStream } from "./worker_stream.js";
42-
import { XRefParseException } from "./core_utils.js";
4342

4443
class WorkerTask {
4544
constructor(name) {
@@ -558,22 +557,9 @@ class WorkerMessageHandler {
558557
function ({ isPureXfa, numPages, annotationStorage, filename }) {
559558
pdfManager.requestLoadedStream();
560559

561-
const newAnnotationsByPage = new Map();
562-
if (!isPureXfa) {
563-
// The concept of page in a XFA is very different, so
564-
// editing is just not implemented.
565-
for (const [key, value] of annotationStorage) {
566-
if (!key.startsWith(AnnotationEditorPrefix)) {
567-
continue;
568-
}
569-
let annotations = newAnnotationsByPage.get(value.pageIndex);
570-
if (!annotations) {
571-
annotations = [];
572-
newAnnotationsByPage.set(value.pageIndex, annotations);
573-
}
574-
annotations.push(value);
575-
}
576-
}
560+
const newAnnotationsByPage = !isPureXfa
561+
? getNewAnnotationsMap(annotationStorage)
562+
: null;
577563

578564
const promises = [
579565
pdfManager.onLoadedStream(),
@@ -583,17 +569,19 @@ class WorkerMessageHandler {
583569
pdfManager.ensureDoc("startXRef"),
584570
];
585571

586-
for (const [pageIndex, annotations] of newAnnotationsByPage) {
587-
promises.push(
588-
pdfManager.getPage(pageIndex).then(page => {
589-
const task = new WorkerTask(`Save (editor): page ${pageIndex}`);
590-
return page
591-
.saveNewAnnotations(handler, task, annotations)
592-
.finally(function () {
593-
finishWorkerTask(task);
594-
});
595-
})
596-
);
572+
if (newAnnotationsByPage) {
573+
for (const [pageIndex, annotations] of newAnnotationsByPage) {
574+
promises.push(
575+
pdfManager.getPage(pageIndex).then(page => {
576+
const task = new WorkerTask(`Save (editor): page ${pageIndex}`);
577+
return page
578+
.saveNewAnnotations(handler, task, annotations)
579+
.finally(function () {
580+
finishWorkerTask(task);
581+
});
582+
})
583+
);
584+
}
597585
}
598586

599587
if (isPureXfa) {

0 commit comments

Comments
 (0)