[Annotations] Add support for printing/saving choice list with multiple selections
- it aims to fix issue #12189.
This commit is contained in:
parent
0dd6bc9a85
commit
ad3fb71a02
@ -50,6 +50,11 @@ import { StringStream } from "./stream.js";
|
|||||||
import { writeDict } from "./writer.js";
|
import { writeDict } from "./writer.js";
|
||||||
import { XFAFactory } from "./xfa/factory.js";
|
import { XFAFactory } from "./xfa/factory.js";
|
||||||
|
|
||||||
|
// Represent the percentage of the height of a single-line field over
|
||||||
|
// the font size.
|
||||||
|
// Acrobat seems to use this value.
|
||||||
|
const LINE_FACTOR = 1.35;
|
||||||
|
|
||||||
class AnnotationFactory {
|
class AnnotationFactory {
|
||||||
/**
|
/**
|
||||||
* Create an `Annotation` object of the correct type for the given reference
|
* Create an `Annotation` object of the correct type for the given reference
|
||||||
@ -1405,6 +1410,16 @@ class WidgetAnnotation extends Annotation {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Value can be an array (with choice list and multiple selections)
|
||||||
|
if (
|
||||||
|
Array.isArray(value) &&
|
||||||
|
Array.isArray(this.data.fieldValue) &&
|
||||||
|
value.length === this.data.fieldValue.length &&
|
||||||
|
value.every((x, i) => x === this.data.fieldValue[i])
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
let appearance = await this._getAppearance(
|
let appearance = await this._getAppearance(
|
||||||
evaluator,
|
evaluator,
|
||||||
task,
|
task,
|
||||||
@ -1448,7 +1463,8 @@ class WidgetAnnotation extends Annotation {
|
|||||||
appearance = newTransform.encryptString(appearance);
|
appearance = newTransform.encryptString(appearance);
|
||||||
}
|
}
|
||||||
|
|
||||||
dict.set("V", isAscii(value) ? value : stringToUTF16BEString(value));
|
const encoder = val => (isAscii(val) ? val : stringToUTF16BEString(val));
|
||||||
|
dict.set("V", Array.isArray(value) ? value.map(encoder) : encoder(value));
|
||||||
dict.set("AP", AP);
|
dict.set("AP", AP);
|
||||||
dict.set("M", `D:${getModificationDate()}`);
|
dict.set("M", `D:${getModificationDate()}`);
|
||||||
|
|
||||||
@ -1629,11 +1645,6 @@ class WidgetAnnotation extends Annotation {
|
|||||||
|
|
||||||
const roundWithTwoDigits = x => Math.floor(x * 100) / 100;
|
const roundWithTwoDigits = x => Math.floor(x * 100) / 100;
|
||||||
|
|
||||||
// Represent the percentage of the height of a single-line field over
|
|
||||||
// the font size.
|
|
||||||
// Acrobat seems to use this value.
|
|
||||||
const LINE_FACTOR = 1.35;
|
|
||||||
|
|
||||||
if (lineCount === -1) {
|
if (lineCount === -1) {
|
||||||
const textWidth = this._getTextWidth(text, font);
|
const textWidth = this._getTextWidth(text, font);
|
||||||
fontSize = roundWithTwoDigits(
|
fontSize = roundWithTwoDigits(
|
||||||
@ -1703,14 +1714,14 @@ class WidgetAnnotation extends Annotation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_renderText(text, font, fontSize, totalWidth, alignment, hPadding, vPadding) {
|
_renderText(text, font, fontSize, totalWidth, alignment, hPadding, vPadding) {
|
||||||
// We need to get the width of the text in order to align it correctly
|
|
||||||
const width = this._getTextWidth(text, font) * fontSize;
|
|
||||||
let shift;
|
let shift;
|
||||||
if (alignment === 1) {
|
if (alignment === 1) {
|
||||||
// Center
|
// Center
|
||||||
|
const width = this._getTextWidth(text, font) * fontSize;
|
||||||
shift = (totalWidth - width) / 2;
|
shift = (totalWidth - width) / 2;
|
||||||
} else if (alignment === 2) {
|
} else if (alignment === 2) {
|
||||||
// Right
|
// Right
|
||||||
|
const width = this._getTextWidth(text, font) * fontSize;
|
||||||
shift = totalWidth - width - hPadding;
|
shift = totalWidth - width - hPadding;
|
||||||
} else {
|
} else {
|
||||||
shift = hPadding;
|
shift = hPadding;
|
||||||
@ -2483,6 +2494,135 @@ class ChoiceWidgetAnnotation extends WidgetAnnotation {
|
|||||||
type,
|
type,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _getAppearance(evaluator, task, annotationStorage) {
|
||||||
|
if (this.data.combo) {
|
||||||
|
return super._getAppearance(evaluator, task, annotationStorage);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!annotationStorage) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const storageEntry = annotationStorage.get(this.data.id);
|
||||||
|
let exportedValue = storageEntry && storageEntry.value;
|
||||||
|
if (exportedValue === undefined) {
|
||||||
|
// The annotation hasn't been rendered so use the appearance
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(exportedValue)) {
|
||||||
|
exportedValue = [exportedValue];
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultPadding = 2;
|
||||||
|
const hPadding = defaultPadding;
|
||||||
|
const totalHeight = this.data.rect[3] - this.data.rect[1];
|
||||||
|
const totalWidth = this.data.rect[2] - this.data.rect[0];
|
||||||
|
const lineCount = this.data.options.length;
|
||||||
|
const valueIndices = [];
|
||||||
|
for (let i = 0; i < lineCount; i++) {
|
||||||
|
const { exportValue } = this.data.options[i];
|
||||||
|
if (exportedValue.includes(exportValue)) {
|
||||||
|
valueIndices.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this._defaultAppearance) {
|
||||||
|
// The DA is required and must be a string.
|
||||||
|
// If there is no font named Helvetica in the resource dictionary,
|
||||||
|
// the evaluator will fall back to a default font.
|
||||||
|
// Doing so prevents exceptions and allows saving/printing
|
||||||
|
// the file as expected.
|
||||||
|
this.data.defaultAppearanceData = parseDefaultAppearance(
|
||||||
|
(this._defaultAppearance = "/Helvetica 0 Tf 0 g")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const font = await this._getFontData(evaluator, task);
|
||||||
|
|
||||||
|
let defaultAppearance;
|
||||||
|
let { fontSize } = this.data.defaultAppearanceData;
|
||||||
|
if (!fontSize) {
|
||||||
|
const lineHeight = (totalHeight - defaultPadding) / lineCount;
|
||||||
|
let lineWidth = -1;
|
||||||
|
let value;
|
||||||
|
for (const { displayValue } of this.data.options) {
|
||||||
|
const width = this._getTextWidth(displayValue);
|
||||||
|
if (width > lineWidth) {
|
||||||
|
lineWidth = width;
|
||||||
|
value = displayValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[defaultAppearance, fontSize] = this._computeFontSize(
|
||||||
|
lineHeight,
|
||||||
|
totalWidth - 2 * hPadding,
|
||||||
|
value,
|
||||||
|
font,
|
||||||
|
-1
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
defaultAppearance = this._defaultAppearance;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lineHeight = fontSize * LINE_FACTOR;
|
||||||
|
const vPadding = (lineHeight - fontSize) / 2;
|
||||||
|
const numberOfVisibleLines = Math.floor(totalHeight / lineHeight);
|
||||||
|
|
||||||
|
let firstIndex;
|
||||||
|
if (valueIndices.length === 1) {
|
||||||
|
const valuePosition = valueIndices[0];
|
||||||
|
const indexInPage = valuePosition % numberOfVisibleLines;
|
||||||
|
firstIndex = valuePosition - indexInPage;
|
||||||
|
} else {
|
||||||
|
// If nothing is selected (valueIndice.length === 0), we render
|
||||||
|
// from the first element.
|
||||||
|
firstIndex = valueIndices.length ? valueIndices[0] : 0;
|
||||||
|
}
|
||||||
|
const end = Math.min(firstIndex + numberOfVisibleLines + 1, lineCount);
|
||||||
|
|
||||||
|
const buf = ["/Tx BMC q", `1 1 ${totalWidth} ${totalHeight} re W n`];
|
||||||
|
|
||||||
|
if (valueIndices.length) {
|
||||||
|
// This value has been copied/pasted from annotation-choice-widget.pdf.
|
||||||
|
// It corresponds to rgb(153, 193, 218).
|
||||||
|
buf.push("0.600006 0.756866 0.854904 rg");
|
||||||
|
|
||||||
|
// Highlight the lines in filling a blue rectangle at the selected
|
||||||
|
// positions.
|
||||||
|
for (const index of valueIndices) {
|
||||||
|
if (firstIndex <= index && index < end) {
|
||||||
|
buf.push(
|
||||||
|
`1 ${
|
||||||
|
totalHeight - (index - firstIndex + 1) * lineHeight
|
||||||
|
} ${totalWidth} ${lineHeight} re f`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf.push("BT", defaultAppearance, `1 0 0 1 0 ${totalHeight} Tm`);
|
||||||
|
|
||||||
|
for (let i = firstIndex; i < end; i++) {
|
||||||
|
const { displayValue } = this.data.options[i];
|
||||||
|
const hpadding = i === firstIndex ? hPadding : 0;
|
||||||
|
const vpadding = i === firstIndex ? vPadding : 0;
|
||||||
|
buf.push(
|
||||||
|
this._renderText(
|
||||||
|
displayValue,
|
||||||
|
font,
|
||||||
|
fontSize,
|
||||||
|
totalWidth,
|
||||||
|
0,
|
||||||
|
hpadding,
|
||||||
|
-lineHeight + vpadding
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.push("ET Q EMC");
|
||||||
|
|
||||||
|
return buf.join("\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SignatureWidgetAnnotation extends WidgetAnnotation {
|
class SignatureWidgetAnnotation extends WidgetAnnotation {
|
||||||
|
@ -143,7 +143,11 @@ function writeXFADataForAcroform(str, newRefs) {
|
|||||||
}
|
}
|
||||||
const node = xml.documentElement.searchNode(parseXFAPath(path), 0);
|
const node = xml.documentElement.searchNode(parseXFAPath(path), 0);
|
||||||
if (node) {
|
if (node) {
|
||||||
node.childNodes = [new SimpleDOMNode("#text", value)];
|
if (Array.isArray(value)) {
|
||||||
|
node.childNodes = value.map(val => new SimpleDOMNode("value", val));
|
||||||
|
} else {
|
||||||
|
node.childNodes = [new SimpleDOMNode("#text", value)];
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
warn(`Node not found for path: ${path}`);
|
warn(`Node not found for path: ${path}`);
|
||||||
}
|
}
|
||||||
|
@ -1336,16 +1336,8 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
|
|||||||
const storage = this.annotationStorage;
|
const storage = this.annotationStorage;
|
||||||
const id = this.data.id;
|
const id = this.data.id;
|
||||||
|
|
||||||
// For printing/saving we currently only support choice widgets with one
|
const storedData = storage.getValue(id, {
|
||||||
// option selection. Therefore, listboxes (#12189) and comboboxes (#12224)
|
value: this.data.fieldValue,
|
||||||
// are not properly printed/saved yet, so we only store the first item in
|
|
||||||
// the field value array instead of the entire array. Once support for those
|
|
||||||
// two field types is implemented, we should use the same pattern as the
|
|
||||||
// other interactive widgets where the return value of `getValue`
|
|
||||||
// is used and the full array of field values is stored.
|
|
||||||
storage.getValue(id, {
|
|
||||||
value:
|
|
||||||
this.data.fieldValue.length > 0 ? this.data.fieldValue[0] : undefined,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let { fontSize } = this.data.defaultAppearanceData;
|
let { fontSize } = this.data.defaultAppearanceData;
|
||||||
@ -1386,7 +1378,7 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
|
|||||||
if (this.data.combo) {
|
if (this.data.combo) {
|
||||||
optionElement.style.fontSize = fontSizeStyle;
|
optionElement.style.fontSize = fontSizeStyle;
|
||||||
}
|
}
|
||||||
if (this.data.fieldValue.includes(option.exportValue)) {
|
if (storedData.value.includes(option.exportValue)) {
|
||||||
optionElement.setAttribute("selected", true);
|
optionElement.setAttribute("selected", true);
|
||||||
}
|
}
|
||||||
selectElement.appendChild(optionElement);
|
selectElement.appendChild(optionElement);
|
||||||
@ -1537,7 +1529,7 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
selectElement.addEventListener("input", function (event) {
|
selectElement.addEventListener("input", function (event) {
|
||||||
storage.setValue(id, { value: getValue(event) });
|
storage.setValue(id, { value: getValue(event, /* isExport */ true) });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,6 +49,9 @@ class EventDispatcher {
|
|||||||
|
|
||||||
mergeChange(event) {
|
mergeChange(event) {
|
||||||
let value = event.value;
|
let value = event.value;
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
if (typeof value !== "string") {
|
if (typeof value !== "string") {
|
||||||
value = value.toString();
|
value = value.toString();
|
||||||
}
|
}
|
||||||
|
@ -233,7 +233,11 @@ class Field extends PDFObject {
|
|||||||
if (this._isChoice) {
|
if (this._isChoice) {
|
||||||
if (this.multipleSelection) {
|
if (this.multipleSelection) {
|
||||||
const values = new Set(value);
|
const values = new Set(value);
|
||||||
this._currentValueIndices.length = 0;
|
if (Array.isArray(this._currentValueIndices)) {
|
||||||
|
this._currentValueIndices.length = 0;
|
||||||
|
} else {
|
||||||
|
this._currentValueIndices = [];
|
||||||
|
}
|
||||||
this._items.forEach(({ displayValue }, i) => {
|
this._items.forEach(({ displayValue }, i) => {
|
||||||
if (values.has(displayValue)) {
|
if (values.has(displayValue)) {
|
||||||
this._currentValueIndices.push(i);
|
this._currentValueIndices.push(i);
|
||||||
|
@ -5897,10 +5897,7 @@
|
|||||||
"value": "Dolor"
|
"value": "Dolor"
|
||||||
},
|
},
|
||||||
"62R": {
|
"62R": {
|
||||||
"value": "Sit"
|
"value": ["Sit", "Adipiscing"]
|
||||||
},
|
|
||||||
"63R": {
|
|
||||||
"value": ""
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -3376,8 +3376,111 @@ describe("annotation", function () {
|
|||||||
annotationStorage
|
annotationStorage
|
||||||
);
|
);
|
||||||
expect(appearance).toEqual(
|
expect(appearance).toEqual(
|
||||||
"/Tx BMC q BT /Helv 5 Tf 1 0 0 1 0 0 Tm" +
|
[
|
||||||
" 2.00 3.04 Td (a value) Tj ET Q EMC"
|
"/Tx BMC q",
|
||||||
|
"1 1 32 10 re W n",
|
||||||
|
"BT",
|
||||||
|
"/Helv 5 Tf",
|
||||||
|
"1 0 0 1 0 10 Tm",
|
||||||
|
"ET Q EMC",
|
||||||
|
].join("\n")
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should render choice with multiple selections but one is visible for printing", async function () {
|
||||||
|
choiceWidgetDict.set("Ff", AnnotationFieldFlag.MULTISELECT);
|
||||||
|
choiceWidgetDict.set("Opt", [
|
||||||
|
["A", "a"],
|
||||||
|
["B", "b"],
|
||||||
|
["C", "c"],
|
||||||
|
["D", "d"],
|
||||||
|
]);
|
||||||
|
choiceWidgetDict.set("V", ["A"]);
|
||||||
|
|
||||||
|
const choiceWidgetRef = Ref.get(271, 0);
|
||||||
|
const xref = new XRefMock([
|
||||||
|
{ ref: choiceWidgetRef, data: choiceWidgetDict },
|
||||||
|
fontRefObj,
|
||||||
|
]);
|
||||||
|
const task = new WorkerTask("test print");
|
||||||
|
partialEvaluator.xref = xref;
|
||||||
|
|
||||||
|
const annotation = await AnnotationFactory.create(
|
||||||
|
xref,
|
||||||
|
choiceWidgetRef,
|
||||||
|
pdfManagerMock,
|
||||||
|
idFactoryMock
|
||||||
|
);
|
||||||
|
const annotationStorage = new Map();
|
||||||
|
annotationStorage.set(annotation.data.id, { value: ["A", "C"] });
|
||||||
|
|
||||||
|
const appearance = await annotation._getAppearance(
|
||||||
|
partialEvaluator,
|
||||||
|
task,
|
||||||
|
annotationStorage
|
||||||
|
);
|
||||||
|
expect(appearance).toEqual(
|
||||||
|
[
|
||||||
|
"/Tx BMC q",
|
||||||
|
"1 1 32 10 re W n",
|
||||||
|
"0.600006 0.756866 0.854904 rg",
|
||||||
|
"1 3.25 32 6.75 re f",
|
||||||
|
"BT",
|
||||||
|
"/Helv 5 Tf",
|
||||||
|
"1 0 0 1 0 10 Tm",
|
||||||
|
"2.00 -5.88 Td (a) Tj",
|
||||||
|
"0.00 -6.75 Td (b) Tj",
|
||||||
|
"ET Q EMC",
|
||||||
|
].join("\n")
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should render choice with multiple selections for printing", async function () {
|
||||||
|
choiceWidgetDict.set("Ff", AnnotationFieldFlag.MULTISELECT);
|
||||||
|
choiceWidgetDict.set("Opt", [
|
||||||
|
["A", "a"],
|
||||||
|
["B", "b"],
|
||||||
|
["C", "c"],
|
||||||
|
["D", "d"],
|
||||||
|
]);
|
||||||
|
choiceWidgetDict.set("V", ["A"]);
|
||||||
|
|
||||||
|
const choiceWidgetRef = Ref.get(271, 0);
|
||||||
|
const xref = new XRefMock([
|
||||||
|
{ ref: choiceWidgetRef, data: choiceWidgetDict },
|
||||||
|
fontRefObj,
|
||||||
|
]);
|
||||||
|
const task = new WorkerTask("test print");
|
||||||
|
partialEvaluator.xref = xref;
|
||||||
|
|
||||||
|
const annotation = await AnnotationFactory.create(
|
||||||
|
xref,
|
||||||
|
choiceWidgetRef,
|
||||||
|
pdfManagerMock,
|
||||||
|
idFactoryMock
|
||||||
|
);
|
||||||
|
const annotationStorage = new Map();
|
||||||
|
annotationStorage.set(annotation.data.id, { value: ["B", "C"] });
|
||||||
|
|
||||||
|
const appearance = await annotation._getAppearance(
|
||||||
|
partialEvaluator,
|
||||||
|
task,
|
||||||
|
annotationStorage
|
||||||
|
);
|
||||||
|
expect(appearance).toEqual(
|
||||||
|
[
|
||||||
|
"/Tx BMC q",
|
||||||
|
"1 1 32 10 re W n",
|
||||||
|
"0.600006 0.756866 0.854904 rg",
|
||||||
|
"1 3.25 32 6.75 re f",
|
||||||
|
"1 -3.5 32 6.75 re f",
|
||||||
|
"BT",
|
||||||
|
"/Helv 5 Tf",
|
||||||
|
"1 0 0 1 0 10 Tm",
|
||||||
|
"2.00 -5.88 Td (b) Tj",
|
||||||
|
"0.00 -6.75 Td (c) Tj",
|
||||||
|
"ET Q EMC",
|
||||||
|
].join("\n")
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -3421,11 +3524,90 @@ describe("annotation", function () {
|
|||||||
"/AP << /N 2 0 R>> /M (date)>>\nendobj\n"
|
"/AP << /N 2 0 R>> /M (date)>>\nendobj\n"
|
||||||
);
|
);
|
||||||
expect(newData.data).toEqual(
|
expect(newData.data).toEqual(
|
||||||
"2 0 obj\n" +
|
[
|
||||||
"<< /Length 67 /Subtype /Form /Resources << /Font << /Helv 314 0 R>>>> " +
|
"2 0 obj",
|
||||||
"/BBox [0 0 32 10]>> stream\n" +
|
"<< /Length 136 /Subtype /Form /Resources << /Font << /Helv 314 0 R>>>> " +
|
||||||
"/Tx BMC q BT /Helv 5 Tf 1 0 0 1 0 0 Tm 2.00 3.04 Td (C) Tj ET Q EMC\n" +
|
"/BBox [0 0 32 10]>> stream",
|
||||||
"endstream\nendobj\n"
|
"/Tx BMC q",
|
||||||
|
"1 1 32 10 re W n",
|
||||||
|
"0.600006 0.756866 0.854904 rg",
|
||||||
|
"1 3.25 32 6.75 re f",
|
||||||
|
"BT",
|
||||||
|
"/Helv 5 Tf",
|
||||||
|
"1 0 0 1 0 10 Tm",
|
||||||
|
"2.00 -5.88 Td (C) Tj",
|
||||||
|
"ET Q EMC",
|
||||||
|
"endstream",
|
||||||
|
"endobj\n",
|
||||||
|
].join("\n")
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should save choice with multiple selections", async function () {
|
||||||
|
choiceWidgetDict.set("Ff", AnnotationFieldFlag.MULTISELECT);
|
||||||
|
choiceWidgetDict.set("Opt", [
|
||||||
|
["A", "a"],
|
||||||
|
["B", "b"],
|
||||||
|
["C", "c"],
|
||||||
|
["D", "d"],
|
||||||
|
]);
|
||||||
|
choiceWidgetDict.set("V", ["A"]);
|
||||||
|
|
||||||
|
const choiceWidgetRef = Ref.get(123, 0);
|
||||||
|
const xref = new XRefMock([
|
||||||
|
{ ref: choiceWidgetRef, data: choiceWidgetDict },
|
||||||
|
fontRefObj,
|
||||||
|
]);
|
||||||
|
const task = new WorkerTask("test save");
|
||||||
|
partialEvaluator.xref = xref;
|
||||||
|
|
||||||
|
const annotation = await AnnotationFactory.create(
|
||||||
|
xref,
|
||||||
|
choiceWidgetRef,
|
||||||
|
pdfManagerMock,
|
||||||
|
idFactoryMock
|
||||||
|
);
|
||||||
|
const annotationStorage = new Map();
|
||||||
|
annotationStorage.set(annotation.data.id, { value: ["B", "C"] });
|
||||||
|
|
||||||
|
const data = await annotation.save(
|
||||||
|
partialEvaluator,
|
||||||
|
task,
|
||||||
|
annotationStorage
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(data.length).toEqual(2);
|
||||||
|
const [oldData, newData] = data;
|
||||||
|
expect(oldData.ref).toEqual(Ref.get(123, 0));
|
||||||
|
expect(newData.ref).toEqual(Ref.get(2, 0));
|
||||||
|
|
||||||
|
oldData.data = oldData.data.replace(/\(D:\d+\)/, "(date)");
|
||||||
|
expect(oldData.data).toEqual(
|
||||||
|
"123 0 obj\n" +
|
||||||
|
"<< /Type /Annot /Subtype /Widget /FT /Ch /DA (/Helv 5 Tf) /DR " +
|
||||||
|
"<< /Font << /Helv 314 0 R>>>> /Rect [0 0 32 10] /Ff 2097152 /Opt " +
|
||||||
|
"[[(A) (a)] [(B) (b)] [(C) (c)] [(D) (d)]] /V [(B) (C)] /AP " +
|
||||||
|
"<< /N 2 0 R>> /M (date)>>\nendobj\n"
|
||||||
|
);
|
||||||
|
expect(newData.data).toEqual(
|
||||||
|
[
|
||||||
|
"2 0 obj",
|
||||||
|
"<< /Length 177 /Subtype /Form /Resources << /Font << /Helv 314 0 R>>>> " +
|
||||||
|
"/BBox [0 0 32 10]>> stream",
|
||||||
|
"/Tx BMC q",
|
||||||
|
"1 1 32 10 re W n",
|
||||||
|
"0.600006 0.756866 0.854904 rg",
|
||||||
|
"1 3.25 32 6.75 re f",
|
||||||
|
"1 -3.5 32 6.75 re f",
|
||||||
|
"BT",
|
||||||
|
"/Helv 5 Tf",
|
||||||
|
"1 0 0 1 0 10 Tm",
|
||||||
|
"2.00 -5.88 Td (b) Tj",
|
||||||
|
"0.00 -6.75 Td (c) Tj",
|
||||||
|
"ET Q EMC",
|
||||||
|
"endstream",
|
||||||
|
"endobj\n",
|
||||||
|
].join("\n")
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user