mirror of
https://github.com/mozilla/pdf.js.git
synced 2025-04-19 22:58:07 +02:00
[Editor] Add support for printing/saving free highlight annotations
This commit is contained in:
parent
1cdbcfef82
commit
d64f334f98
6 changed files with 611 additions and 46 deletions
|
@ -355,13 +355,19 @@ class AnnotationFactory {
|
|||
);
|
||||
break;
|
||||
case AnnotationEditorType.HIGHLIGHT:
|
||||
promises.push(
|
||||
HighlightAnnotation.createNewAnnotation(
|
||||
xref,
|
||||
annotation,
|
||||
dependencies
|
||||
)
|
||||
);
|
||||
if (annotation.quadPoints) {
|
||||
promises.push(
|
||||
HighlightAnnotation.createNewAnnotation(
|
||||
xref,
|
||||
annotation,
|
||||
dependencies
|
||||
)
|
||||
);
|
||||
} else {
|
||||
promises.push(
|
||||
InkAnnotation.createNewAnnotation(xref, annotation, dependencies)
|
||||
);
|
||||
}
|
||||
break;
|
||||
case AnnotationEditorType.INK:
|
||||
promises.push(
|
||||
|
@ -439,16 +445,29 @@ class AnnotationFactory {
|
|||
);
|
||||
break;
|
||||
case AnnotationEditorType.HIGHLIGHT:
|
||||
promises.push(
|
||||
HighlightAnnotation.createNewPrintAnnotation(
|
||||
annotationGlobals,
|
||||
xref,
|
||||
annotation,
|
||||
{
|
||||
evaluatorOptions: options,
|
||||
}
|
||||
)
|
||||
);
|
||||
if (annotation.quadPoints) {
|
||||
promises.push(
|
||||
HighlightAnnotation.createNewPrintAnnotation(
|
||||
annotationGlobals,
|
||||
xref,
|
||||
annotation,
|
||||
{
|
||||
evaluatorOptions: options,
|
||||
}
|
||||
)
|
||||
);
|
||||
} else {
|
||||
promises.push(
|
||||
InkAnnotation.createNewPrintAnnotation(
|
||||
annotationGlobals,
|
||||
xref,
|
||||
annotation,
|
||||
{
|
||||
evaluatorOptions: options,
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
break;
|
||||
case AnnotationEditorType.INK:
|
||||
promises.push(
|
||||
|
@ -4340,19 +4359,25 @@ class InkAnnotation extends MarkupAnnotation {
|
|||
}
|
||||
|
||||
static createNewDict(annotation, xref, { apRef, ap }) {
|
||||
const { color, opacity, paths, rect, rotation, thickness } = annotation;
|
||||
const { color, opacity, paths, outlines, rect, rotation, thickness } =
|
||||
annotation;
|
||||
const ink = new Dict(xref);
|
||||
ink.set("Type", Name.get("Annot"));
|
||||
ink.set("Subtype", Name.get("Ink"));
|
||||
ink.set("CreationDate", `D:${getModificationDate()}`);
|
||||
ink.set("Rect", rect);
|
||||
ink.set(
|
||||
"InkList",
|
||||
paths.map(p => p.points)
|
||||
);
|
||||
ink.set("InkList", outlines?.points || paths.map(p => p.points));
|
||||
ink.set("F", 4);
|
||||
ink.set("Rotate", rotation);
|
||||
|
||||
if (outlines) {
|
||||
// Free highlight.
|
||||
// There's nothing about this in the spec, but it's used when highlighting
|
||||
// in Edge's viewer. Acrobat takes into account this parameter to indicate
|
||||
// that the Ink is used for highlighting.
|
||||
ink.set("IT", Name.get("InkHighlight"));
|
||||
}
|
||||
|
||||
// Line thickness.
|
||||
const bs = new Dict(xref);
|
||||
ink.set("BS", bs);
|
||||
|
@ -4380,6 +4405,13 @@ class InkAnnotation extends MarkupAnnotation {
|
|||
}
|
||||
|
||||
static async createNewAppearanceStream(annotation, xref, params) {
|
||||
if (annotation.outlines) {
|
||||
return this.createNewAppearanceStreamForHighlight(
|
||||
annotation,
|
||||
xref,
|
||||
params
|
||||
);
|
||||
}
|
||||
const { color, rect, paths, thickness, opacity } = annotation;
|
||||
|
||||
const appearanceBuffer = [
|
||||
|
@ -4438,6 +4470,65 @@ class InkAnnotation extends MarkupAnnotation {
|
|||
|
||||
return ap;
|
||||
}
|
||||
|
||||
static async createNewAppearanceStreamForHighlight(annotation, xref, params) {
|
||||
const {
|
||||
color,
|
||||
rect,
|
||||
outlines: { outline },
|
||||
opacity,
|
||||
} = annotation;
|
||||
const appearanceBuffer = [
|
||||
`${getPdfColor(color, /* isFill */ true)}`,
|
||||
"/R0 gs",
|
||||
];
|
||||
|
||||
appearanceBuffer.push(
|
||||
`${numberToString(outline[4])} ${numberToString(outline[5])} m`
|
||||
);
|
||||
for (let i = 6, ii = outline.length; i < ii; i += 6) {
|
||||
if (isNaN(outline[i]) || outline[i] === null) {
|
||||
appearanceBuffer.push(
|
||||
`${numberToString(outline[i + 4])} ${numberToString(
|
||||
outline[i + 5]
|
||||
)} l`
|
||||
);
|
||||
} else {
|
||||
const curve = outline
|
||||
.slice(i, i + 6)
|
||||
.map(numberToString)
|
||||
.join(" ");
|
||||
appearanceBuffer.push(`${curve} c`);
|
||||
}
|
||||
}
|
||||
appearanceBuffer.push("h f");
|
||||
const appearance = appearanceBuffer.join("\n");
|
||||
|
||||
const appearanceStreamDict = new Dict(xref);
|
||||
appearanceStreamDict.set("FormType", 1);
|
||||
appearanceStreamDict.set("Subtype", Name.get("Form"));
|
||||
appearanceStreamDict.set("Type", Name.get("XObject"));
|
||||
appearanceStreamDict.set("BBox", rect);
|
||||
appearanceStreamDict.set("Length", appearance.length);
|
||||
|
||||
const resources = new Dict(xref);
|
||||
const extGState = new Dict(xref);
|
||||
resources.set("ExtGState", extGState);
|
||||
appearanceStreamDict.set("Resources", resources);
|
||||
const r0 = new Dict(xref);
|
||||
extGState.set("R0", r0);
|
||||
r0.set("BM", Name.get("Multiply"));
|
||||
|
||||
if (opacity !== 1) {
|
||||
r0.set("ca", opacity);
|
||||
r0.set("Type", Name.get("ExtGState"));
|
||||
}
|
||||
|
||||
const ap = new StringStream(appearance);
|
||||
ap.dict = appearanceStreamDict;
|
||||
|
||||
return ap;
|
||||
}
|
||||
}
|
||||
|
||||
class HighlightAnnotation extends MarkupAnnotation {
|
||||
|
|
|
@ -32,7 +32,7 @@ async function writeObject(ref, obj, buffer, { encrypt = null }) {
|
|||
await writeDict(obj, buffer, transform);
|
||||
} else if (obj instanceof BaseStream) {
|
||||
await writeStream(obj, buffer, transform);
|
||||
} else if (Array.isArray(obj)) {
|
||||
} else if (Array.isArray(obj) || ArrayBuffer.isView(obj)) {
|
||||
await writeArray(obj, buffer, transform);
|
||||
}
|
||||
buffer.push("\nendobj\n");
|
||||
|
@ -132,7 +132,7 @@ async function writeValue(value, buffer, transform) {
|
|||
buffer.push(`/${escapePDFName(value.name)}`);
|
||||
} else if (value instanceof Ref) {
|
||||
buffer.push(`${value.num} ${value.gen} R`);
|
||||
} else if (Array.isArray(value)) {
|
||||
} else if (Array.isArray(value) || ArrayBuffer.isView(value)) {
|
||||
await writeArray(value, buffer, transform);
|
||||
} else if (typeof value === "string") {
|
||||
if (transform) {
|
||||
|
|
|
@ -597,6 +597,7 @@ class HighlightEditor extends AnnotationEditor {
|
|||
annotationType: AnnotationEditorType.HIGHLIGHT,
|
||||
color,
|
||||
opacity: this.#opacity,
|
||||
thickness: 2 * HighlightEditor._defaultThickness,
|
||||
quadPoints: this.#serializeBoxes(rect),
|
||||
outlines: this.#serializeOutlines(rect),
|
||||
pageIndex: this.pageIndex,
|
||||
|
|
|
@ -596,6 +596,12 @@ class FreeOutliner {
|
|||
const lastBottom = last.subarray(16, 18);
|
||||
const [layerX, layerY, layerWidth, layerHeight] = this.#box;
|
||||
|
||||
const points = new Float64Array(this.#points?.length ?? 0);
|
||||
for (let i = 0, ii = points.length; i < ii; i += 2) {
|
||||
points[i] = (this.#points[i] - layerX) / layerWidth;
|
||||
points[i + 1] = (this.#points[i + 1] - layerY) / layerHeight;
|
||||
}
|
||||
|
||||
if (isNaN(last[6]) && !this.isEmpty()) {
|
||||
// We've only two points.
|
||||
const outline = new Float64Array(24);
|
||||
|
@ -628,7 +634,12 @@ class FreeOutliner {
|
|||
],
|
||||
0
|
||||
);
|
||||
return new FreeHighlightOutline(outline, this.#innerMargin, isLTR);
|
||||
return new FreeHighlightOutline(
|
||||
outline,
|
||||
points,
|
||||
this.#innerMargin,
|
||||
isLTR
|
||||
);
|
||||
}
|
||||
|
||||
const outline = new Float64Array(
|
||||
|
@ -675,7 +686,7 @@ class FreeOutliner {
|
|||
}
|
||||
}
|
||||
outline.set([NaN, NaN, NaN, NaN, bottom[4], bottom[5]], N);
|
||||
return new FreeHighlightOutline(outline, this.#innerMargin, isLTR);
|
||||
return new FreeHighlightOutline(outline, points, this.#innerMargin, isLTR);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -684,11 +695,14 @@ class FreeHighlightOutline extends Outline {
|
|||
|
||||
#innerMargin;
|
||||
|
||||
#points;
|
||||
|
||||
#outline;
|
||||
|
||||
constructor(outline, innerMargin, isLTR) {
|
||||
constructor(outline, points, innerMargin, isLTR) {
|
||||
super();
|
||||
this.#outline = outline;
|
||||
this.#points = points;
|
||||
this.#innerMargin = innerMargin;
|
||||
this.#computeMinMax(isLTR);
|
||||
|
||||
|
@ -697,6 +711,10 @@ class FreeHighlightOutline extends Outline {
|
|||
outline[i] = (outline[i] - x) / width;
|
||||
outline[i + 1] = (outline[i + 1] - y) / height;
|
||||
}
|
||||
for (let i = 0, ii = points.length; i < ii; i += 2) {
|
||||
points[i] = (points[i] - x) / width;
|
||||
points[i + 1] = (points[i + 1] - y) / height;
|
||||
}
|
||||
}
|
||||
|
||||
toSVGPath() {
|
||||
|
@ -717,36 +735,53 @@ class FreeHighlightOutline extends Outline {
|
|||
}
|
||||
|
||||
serialize([blX, blY, trX, trY], rotation) {
|
||||
const src = this.#outline;
|
||||
const outline = new Float64Array(src.length);
|
||||
const width = trX - blX;
|
||||
const height = trY - blY;
|
||||
let outline;
|
||||
let points;
|
||||
switch (rotation) {
|
||||
case 0:
|
||||
for (let i = 0, ii = src.length; i < ii; i += 2) {
|
||||
outline[i] = blX + src[i] * width;
|
||||
outline[i + 1] = trY - src[i + 1] * height;
|
||||
}
|
||||
outline = this.#rescale(this.#outline, blX, trY, width, -height);
|
||||
points = this.#rescale(this.#points, blX, trY, width, -height);
|
||||
break;
|
||||
case 90:
|
||||
for (let i = 0, ii = src.length; i < ii; i += 2) {
|
||||
outline[i] = blX + src[i + 1] * width;
|
||||
outline[i + 1] = blY + src[i] * height;
|
||||
}
|
||||
outline = this.#rescaleAndSwap(this.#outline, blX, blY, width, height);
|
||||
points = this.#rescaleAndSwap(this.#points, blX, blY, width, height);
|
||||
break;
|
||||
case 180:
|
||||
for (let i = 0, ii = src.length; i < ii; i += 2) {
|
||||
outline[i] = trX - src[i] * width;
|
||||
outline[i + 1] = blY + src[i + 1] * height;
|
||||
}
|
||||
outline = this.#rescale(this.#outline, trX, blY, -width, height);
|
||||
points = this.#rescale(this.#points, trX, blY, -width, height);
|
||||
break;
|
||||
case 270:
|
||||
for (let i = 0, ii = src.length; i < ii; i += 2) {
|
||||
outline[i] = trX - src[i + 1] * width;
|
||||
outline[i + 1] = trY - src[i] * height;
|
||||
}
|
||||
outline = this.#rescaleAndSwap(
|
||||
this.#outline,
|
||||
trX,
|
||||
trY,
|
||||
-width,
|
||||
-height
|
||||
);
|
||||
points = this.#rescaleAndSwap(this.#points, trX, trY, -width, -height);
|
||||
break;
|
||||
}
|
||||
return outline;
|
||||
return { outline: Array.from(outline), points: [Array.from(points)] };
|
||||
}
|
||||
|
||||
#rescale(src, tx, ty, sx, sy) {
|
||||
const dest = new Float64Array(src.length);
|
||||
for (let i = 0, ii = src.length; i < ii; i += 2) {
|
||||
dest[i] = tx + src[i] * sx;
|
||||
dest[i + 1] = ty + src[i + 1] * sy;
|
||||
}
|
||||
return dest;
|
||||
}
|
||||
|
||||
#rescaleAndSwap(src, tx, ty, sx, sy) {
|
||||
const dest = new Float64Array(src.length);
|
||||
for (let i = 0, ii = src.length; i < ii; i += 2) {
|
||||
dest[i] = tx + src[i + 1] * sx;
|
||||
dest[i + 1] = ty + src[i] * sy;
|
||||
}
|
||||
return dest;
|
||||
}
|
||||
|
||||
#computeMinMax(isLTR) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue