Skip to content

Commit c29ffd3

Browse files
committed
[Editor] Add support for printing/saving free highlight annotations
1 parent 5c2f4f5 commit c29ffd3

File tree

6 files changed

+612
-46
lines changed

6 files changed

+612
-46
lines changed

src/core/annotation.js

+110-22
Original file line numberDiff line numberDiff line change
@@ -355,13 +355,19 @@ class AnnotationFactory {
355355
);
356356
break;
357357
case AnnotationEditorType.HIGHLIGHT:
358-
promises.push(
359-
HighlightAnnotation.createNewAnnotation(
360-
xref,
361-
annotation,
362-
dependencies
363-
)
364-
);
358+
if (annotation.quadPoints) {
359+
promises.push(
360+
HighlightAnnotation.createNewAnnotation(
361+
xref,
362+
annotation,
363+
dependencies
364+
)
365+
);
366+
} else {
367+
promises.push(
368+
InkAnnotation.createNewAnnotation(xref, annotation, dependencies)
369+
);
370+
}
365371
break;
366372
case AnnotationEditorType.INK:
367373
promises.push(
@@ -439,16 +445,29 @@ class AnnotationFactory {
439445
);
440446
break;
441447
case AnnotationEditorType.HIGHLIGHT:
442-
promises.push(
443-
HighlightAnnotation.createNewPrintAnnotation(
444-
annotationGlobals,
445-
xref,
446-
annotation,
447-
{
448-
evaluatorOptions: options,
449-
}
450-
)
451-
);
448+
if (annotation.quadPoints) {
449+
promises.push(
450+
HighlightAnnotation.createNewPrintAnnotation(
451+
annotationGlobals,
452+
xref,
453+
annotation,
454+
{
455+
evaluatorOptions: options,
456+
}
457+
)
458+
);
459+
} else {
460+
promises.push(
461+
InkAnnotation.createNewPrintAnnotation(
462+
annotationGlobals,
463+
xref,
464+
annotation,
465+
{
466+
evaluatorOptions: options,
467+
}
468+
)
469+
);
470+
}
452471
break;
453472
case AnnotationEditorType.INK:
454473
promises.push(
@@ -4340,19 +4359,22 @@ class InkAnnotation extends MarkupAnnotation {
43404359
}
43414360

43424361
static createNewDict(annotation, xref, { apRef, ap }) {
4343-
const { color, opacity, paths, rect, rotation, thickness } = annotation;
4362+
const { color, opacity, paths, outlines, rect, rotation, thickness } =
4363+
annotation;
43444364
const ink = new Dict(xref);
43454365
ink.set("Type", Name.get("Annot"));
43464366
ink.set("Subtype", Name.get("Ink"));
43474367
ink.set("CreationDate", `D:${getModificationDate()}`);
43484368
ink.set("Rect", rect);
4349-
ink.set(
4350-
"InkList",
4351-
paths.map(p => p.points)
4352-
);
4369+
ink.set("InkList", outlines?.points || paths.map(p => p.points));
43534370
ink.set("F", 4);
43544371
ink.set("Rotate", rotation);
43554372

4373+
if (outlines) {
4374+
// Free highlight.
4375+
ink.set("IT", Name.get("InkHighlight"));
4376+
}
4377+
43564378
// Line thickness.
43574379
const bs = new Dict(xref);
43584380
ink.set("BS", bs);
@@ -4380,6 +4402,13 @@ class InkAnnotation extends MarkupAnnotation {
43804402
}
43814403

43824404
static async createNewAppearanceStream(annotation, xref, params) {
4405+
if (annotation.outlines) {
4406+
return this.createNewAppearanceStreamForHighlight(
4407+
annotation,
4408+
xref,
4409+
params
4410+
);
4411+
}
43834412
const { color, rect, paths, thickness, opacity } = annotation;
43844413

43854414
const appearanceBuffer = [
@@ -4438,6 +4467,65 @@ class InkAnnotation extends MarkupAnnotation {
44384467

44394468
return ap;
44404469
}
4470+
4471+
static async createNewAppearanceStreamForHighlight(annotation, xref, params) {
4472+
const {
4473+
color,
4474+
rect,
4475+
outlines: { outline },
4476+
opacity,
4477+
} = annotation;
4478+
const appearanceBuffer = [
4479+
`${getPdfColor(color, /* isFill */ true)}`,
4480+
"/R0 gs",
4481+
];
4482+
4483+
appearanceBuffer.push(
4484+
`${numberToString(outline[4])} ${numberToString(outline[5])} m`
4485+
);
4486+
for (let i = 6, ii = outline.length; i < ii; i += 6) {
4487+
if (isNaN(outline[i]) || outline[i] === null) {
4488+
appearanceBuffer.push(
4489+
`${numberToString(outline[i + 4])} ${numberToString(
4490+
outline[i + 5]
4491+
)} l`
4492+
);
4493+
} else {
4494+
const curve = outline
4495+
.slice(i, i + 6)
4496+
.map(numberToString)
4497+
.join(" ");
4498+
appearanceBuffer.push(`${curve} c`);
4499+
}
4500+
}
4501+
appearanceBuffer.push("h f");
4502+
const appearance = appearanceBuffer.join("\n");
4503+
4504+
const appearanceStreamDict = new Dict(xref);
4505+
appearanceStreamDict.set("FormType", 1);
4506+
appearanceStreamDict.set("Subtype", Name.get("Form"));
4507+
appearanceStreamDict.set("Type", Name.get("XObject"));
4508+
appearanceStreamDict.set("BBox", rect);
4509+
appearanceStreamDict.set("Length", appearance.length);
4510+
4511+
const resources = new Dict(xref);
4512+
const extGState = new Dict(xref);
4513+
resources.set("ExtGState", extGState);
4514+
appearanceStreamDict.set("Resources", resources);
4515+
const r0 = new Dict(xref);
4516+
extGState.set("R0", r0);
4517+
r0.set("BM", Name.get("Multiply"));
4518+
4519+
if (opacity !== 1) {
4520+
r0.set("ca", opacity);
4521+
r0.set("Type", Name.get("ExtGState"));
4522+
}
4523+
4524+
const ap = new StringStream(appearance);
4525+
ap.dict = appearanceStreamDict;
4526+
4527+
return ap;
4528+
}
44414529
}
44424530

44434531
class HighlightAnnotation extends MarkupAnnotation {

src/core/writer.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ async function writeObject(ref, obj, buffer, { encrypt = null }) {
3232
await writeDict(obj, buffer, transform);
3333
} else if (obj instanceof BaseStream) {
3434
await writeStream(obj, buffer, transform);
35-
} else if (Array.isArray(obj)) {
35+
} else if (Array.isArray(obj) || ArrayBuffer.isView(obj)) {
3636
await writeArray(obj, buffer, transform);
3737
}
3838
buffer.push("\nendobj\n");
@@ -132,7 +132,7 @@ async function writeValue(value, buffer, transform) {
132132
buffer.push(`/${escapePDFName(value.name)}`);
133133
} else if (value instanceof Ref) {
134134
buffer.push(`${value.num} ${value.gen} R`);
135-
} else if (Array.isArray(value)) {
135+
} else if (Array.isArray(value) || ArrayBuffer.isView(value)) {
136136
await writeArray(value, buffer, transform);
137137
} else if (typeof value === "string") {
138138
if (transform) {

src/display/editor/highlight.js

+1
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,7 @@ class HighlightEditor extends AnnotationEditor {
572572
annotationType: AnnotationEditorType.HIGHLIGHT,
573573
color,
574574
opacity: this.#opacity,
575+
thickness: 2 * HighlightEditor._defaultThickness,
575576
quadPoints: this.#serializeBoxes(rect),
576577
outlines: this.#serializeOutlines(rect),
577578
pageIndex: this.pageIndex,

src/display/editor/outliner.js

+61-22
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,11 @@ class FreeOutliner {
599599
(last[4] - layerX) / layerWidth,
600600
(last[5] - layerY) / layerHeight,
601601
];
602+
const points = new Float64Array(this.#points?.length ?? 0);
603+
for (let i = 0, ii = points.length; i < ii; i += 2) {
604+
points[i] = (this.#points[i] - layerX) / layerWidth;
605+
points[i + 1] = (this.#points[i + 1] - layerY) / layerHeight;
606+
}
602607

603608
if (isNaN(last[6]) && !this.isEmpty()) {
604609
// We've only two points.
@@ -632,7 +637,12 @@ class FreeOutliner {
632637
],
633638
0
634639
);
635-
return new FreeHighlightOutline(outline, this.#innerMargin, lastPoint);
640+
return new FreeHighlightOutline(
641+
outline,
642+
points,
643+
this.#innerMargin,
644+
lastPoint
645+
);
636646
}
637647

638648
const outline = new Float64Array(
@@ -679,7 +689,12 @@ class FreeOutliner {
679689
}
680690
}
681691
outline.set([NaN, NaN, NaN, NaN, bottom[4], bottom[5]], N);
682-
return new FreeHighlightOutline(outline, this.#innerMargin, lastPoint);
692+
return new FreeHighlightOutline(
693+
outline,
694+
points,
695+
this.#innerMargin,
696+
lastPoint
697+
);
683698
}
684699
}
685700

@@ -690,11 +705,14 @@ class FreeHighlightOutline extends Outline {
690705

691706
#lastPoint;
692707

708+
#points;
709+
693710
#outline;
694711

695-
constructor(outline, innerMargin, lastPoint) {
712+
constructor(outline, points, innerMargin, lastPoint) {
696713
super();
697714
this.#outline = outline;
715+
this.#points = points;
698716
this.#innerMargin = innerMargin;
699717
this.#lastPoint = lastPoint;
700718

@@ -703,6 +721,10 @@ class FreeHighlightOutline extends Outline {
703721
outline[i] = (outline[i] - x) / width;
704722
outline[i + 1] = (outline[i + 1] - y) / height;
705723
}
724+
for (let i = 0, ii = points.length; i < ii; i += 2) {
725+
points[i] = (points[i] - x) / width;
726+
points[i + 1] = (points[i + 1] - y) / height;
727+
}
706728
}
707729

708730
toSVGPath() {
@@ -723,36 +745,53 @@ class FreeHighlightOutline extends Outline {
723745
}
724746

725747
serialize([blX, blY, trX, trY], rotation) {
726-
const src = this.#outline;
727-
const outline = new Float64Array(src.length);
728748
const width = trX - blX;
729749
const height = trY - blY;
750+
let outline;
751+
let points;
730752
switch (rotation) {
731753
case 0:
732-
for (let i = 0, ii = src.length; i < ii; i += 2) {
733-
outline[i] = blX + src[i] * width;
734-
outline[i + 1] = trY - src[i + 1] * height;
735-
}
754+
outline = this.#rescale(this.#outline, blX, trY, width, -height);
755+
points = this.#rescale(this.#points, blX, trY, width, -height);
736756
break;
737757
case 90:
738-
for (let i = 0, ii = src.length; i < ii; i += 2) {
739-
outline[i] = blX + src[i + 1] * width;
740-
outline[i + 1] = blY + src[i] * height;
741-
}
758+
outline = this.#rescaleAndSwap(this.#outline, blX, blY, width, height);
759+
points = this.#rescaleAndSwap(this.#points, blX, blY, width, height);
742760
break;
743761
case 180:
744-
for (let i = 0, ii = src.length; i < ii; i += 2) {
745-
outline[i] = trX - src[i] * width;
746-
outline[i + 1] = blY + src[i + 1] * height;
747-
}
762+
outline = this.#rescale(this.#outline, trX, blY, -width, height);
763+
points = this.#rescale(this.#points, trX, blY, -width, height);
748764
break;
749765
case 270:
750-
for (let i = 0, ii = src.length; i < ii; i += 2) {
751-
outline[i] = trX - src[i + 1] * width;
752-
outline[i + 1] = trY - src[i] * height;
753-
}
766+
outline = this.#rescaleAndSwap(
767+
this.#outline,
768+
trX,
769+
trY,
770+
-width,
771+
-height
772+
);
773+
points = this.#rescaleAndSwap(this.#points, trX, trY, -width, -height);
774+
break;
775+
}
776+
return { outline: Array.from(outline), points: [Array.from(points)] };
777+
}
778+
779+
#rescale(src, tx, ty, sx, sy) {
780+
const dest = new Float64Array(src.length);
781+
for (let i = 0, ii = src.length; i < ii; i += 2) {
782+
dest[i] = tx + src[i] * sx;
783+
dest[i + 1] = ty + src[i + 1] * sy;
784+
}
785+
return dest;
786+
}
787+
788+
#rescaleAndSwap(src, tx, ty, sx, sy) {
789+
const dest = new Float64Array(src.length);
790+
for (let i = 0, ii = src.length; i < ii; i += 2) {
791+
dest[i] = tx + src[i + 1] * sx;
792+
dest[i + 1] = ty + src[i] * sy;
754793
}
755-
return outline;
794+
return dest;
756795
}
757796

758797
get box() {

0 commit comments

Comments
 (0)