diff --git a/src/core/annotation.js b/src/core/annotation.js index fcf5e420977856..569a7bcab5b7a6 100644 --- a/src/core/annotation.js +++ b/src/core/annotation.js @@ -49,18 +49,21 @@ class AnnotationFactory { * instance. */ static create(xref, ref, pdfManager, idFactory) { - return pdfManager.ensure(this, "_create", [ - xref, - ref, - pdfManager, - idFactory, - ]); + return pdfManager.ensureDoc("acroForm").then(acroForm => { + return pdfManager.ensure(this, "_create", [ + xref, + ref, + pdfManager, + idFactory, + acroForm, + ]); + }); } /** * @private */ - static _create(xref, ref, pdfManager, idFactory) { + static _create(xref, ref, pdfManager, idFactory, acroForm) { const dict = xref.fetchIfRef(ref); if (!isDict(dict)) { return undefined; @@ -78,6 +81,7 @@ class AnnotationFactory { subtype, id, pdfManager, + acroForm: acroForm instanceof Dict ? acroForm : Dict.empty, }; switch (subtype) { @@ -471,6 +475,7 @@ class Annotation { */ setAppearance(dict) { this.appearance = null; + this.checkedAppearance = null; const appearanceStates = dict.get("AP"); if (!isDict(appearanceStates)) { @@ -514,8 +519,9 @@ class Annotation { return Promise.resolve(new OperatorList()); } + const appearance = this.appearance; const data = this.data; - const appearanceDict = this.appearance.dict; + const appearanceDict = appearance.dict; const resourcesPromise = this.loadResources([ "ExtGState", "ColorSpace", @@ -533,14 +539,14 @@ class Annotation { opList.addOp(OPS.beginAnnotation, [data.rect, transform, matrix]); return evaluator .getOperatorList({ - stream: this.appearance, + stream: appearance, task, resources, operatorList: opList, }) .then(() => { opList.addOp(OPS.endAnnotation, []); - this.appearance.reset(); + appearance.reset(); return opList; }); }); @@ -795,11 +801,16 @@ class WidgetAnnotation extends Annotation { getArray: true, }); data.alternativeText = stringToPDFString(dict.get("TU") || ""); - data.defaultAppearance = getInheritableProperty({ dict, key: "DA" }) || ""; + data.defaultAppearance = + getInheritableProperty({ dict, key: "DA" }) || + params.acroForm.get("DA") || + ""; const fieldType = getInheritableProperty({ dict, key: "FT" }); data.fieldType = isName(fieldType) ? fieldType.name : null; this.fieldResources = - getInheritableProperty({ dict, key: "DR" }) || Dict.empty; + getInheritableProperty({ dict, key: "DR" }) || + params.acroForm.get("DR") || + Dict.empty; data.fieldFlags = getInheritableProperty({ dict, key: "Ff" }); if (!Number.isInteger(data.fieldFlags) || data.fieldFlags < 0) { @@ -980,6 +991,31 @@ class ButtonWidgetAnnotation extends WidgetAnnotation { } } + getOperatorList(evaluator, task, renderForms, annotationStorage) { + if (annotationStorage) { + const value = annotationStorage[this.data.id] || false; + if (value && this.checkedAppearance) { + const savedAppearance = this.appearance; + this.appearance = this.checkedAppearance; + const operatorList = super.getOperatorList( + evaluator, + task, + renderForms, + annotationStorage + ); + this.appearance = savedAppearance; + return operatorList; + } + return Promise.resolve(new OperatorList()); + } + return super.getOperatorList( + evaluator, + task, + renderForms, + annotationStorage + ); + } + _processCheckBox(params) { if (isName(this.data.fieldValue)) { this.data.fieldValue = this.data.fieldValue.name; @@ -1003,6 +1039,13 @@ class ButtonWidgetAnnotation extends WidgetAnnotation { this.data.exportValue = exportValues[0] === "Off" ? exportValues[1] : exportValues[0]; + + const normalAppearanceState = customAppearance.get("N"); + if (!isDict(normalAppearanceState)) { + return; + } + + this.checkedAppearance = normalAppearanceState.get(this.data.exportValue); } _processRadioButton(params) { diff --git a/src/display/annotation_layer.js b/src/display/annotation_layer.js index 42b53588095802..d209c595fd9b81 100644 --- a/src/display/annotation_layer.js +++ b/src/display/annotation_layer.js @@ -542,16 +542,28 @@ class CheckboxWidgetAnnotationElement extends WidgetAnnotationElement { * @returns {HTMLSectionElement} */ render() { + const storage = this.annotationStorage; + const data = this.data; + const id = data.id; + const value = storage.getOrCreateValue( + id, + data.fieldValue && data.fieldValue !== "Off" + ); + this.container.className = "buttonWidgetAnnotation checkBox"; const element = document.createElement("input"); - element.disabled = this.data.readOnly; + element.disabled = data.readOnly; element.type = "checkbox"; element.name = this.data.fieldName; - if (this.data.fieldValue && this.data.fieldValue !== "Off") { + if (value) { element.setAttribute("checked", true); } + element.addEventListener("change", function (event) { + storage.setValue(id, event.target.checked); + }); + this.container.appendChild(element); return this.container; } diff --git a/test/unit/annotation_spec.js b/test/unit/annotation_spec.js index 100ca9dbbeca15..1387d7630ae77c 100644 --- a/test/unit/annotation_spec.js +++ b/test/unit/annotation_spec.js @@ -31,12 +31,17 @@ import { import { createIdFactory, XRefMock } from "./test_utils.js"; import { Dict, Name, Ref } from "../../src/core/primitives.js"; import { Lexer, Parser } from "../../src/core/parser.js"; +import { PartialEvaluator } from "../../src/core/evaluator.js"; import { StringStream } from "../../src/core/stream.js"; +import { WorkerTask } from "../../src/core/worker.js"; describe("annotation", function () { class PDFManagerMock { constructor(params) { this.docBaseUrl = params.docBaseUrl || null; + this.pdfDocument = { + acroForm: new Dict(), + }; } ensure(obj, prop, args) { @@ -49,21 +54,41 @@ describe("annotation", function () { } }); } + + ensureDoc(prop, args) { + return this.ensure(this.pdfDocument, prop, args); + } } - let pdfManagerMock, idFactoryMock; + function HandlerMock() { + this.inputs = []; + } + HandlerMock.prototype = { + send(name, data) { + this.inputs.push({ name, data }); + }, + }; + + let pdfManagerMock, idFactoryMock, partialEvaluator; beforeAll(function (done) { pdfManagerMock = new PDFManagerMock({ docBaseUrl: null, }); idFactoryMock = createIdFactory(/* pageIndex = */ 0); + partialEvaluator = new PartialEvaluator({ + xref: new XRefMock(), + handler: new HandlerMock(), + pageIndex: 0, + idFactory: createIdFactory(/* pageIndex = */ 0), + }); done(); }); afterAll(function () { pdfManagerMock = null; idFactoryMock = null; + partialEvaluator = null; }); describe("AnnotationFactory", function () { @@ -1659,6 +1684,60 @@ describe("annotation", function () { done(); }, done.fail); }); + + it("should get rendered annotation for printing", function (done) { + buttonWidgetDict.set("V", Name.get("1")); + + const appearanceStatesDict = new Dict(); + const exportValueOptionsDict = new Dict(); + const normalAppearanceDict = new Dict(); + const checkedAppearanceDict = new Dict(); + + const stream = new StringStream("0.1 0.2 0.3 rg"); + stream.dict = checkedAppearanceDict; + + checkedAppearanceDict.set("BBox", [0, 0, 8, 8]); + checkedAppearanceDict.set("FormType", 1); + checkedAppearanceDict.set("Matrix", [1, 0, 0, 1, 0, 0]); + normalAppearanceDict.set("Checked", stream); + exportValueOptionsDict.set("Off", 0); + exportValueOptionsDict.set("Checked", 1); + appearanceStatesDict.set("D", exportValueOptionsDict); + appearanceStatesDict.set("N", normalAppearanceDict); + + buttonWidgetDict.set("AP", appearanceStatesDict); + + const buttonWidgetRef = Ref.get(124, 0); + const xref = new XRefMock([ + { ref: buttonWidgetRef, data: buttonWidgetDict }, + ]); + const task = new WorkerTask("test print"); + + AnnotationFactory.create( + xref, + buttonWidgetRef, + pdfManagerMock, + idFactoryMock + ) + .then(annotation => { + const id = annotation.data.id; + const annotationStorage = {}; + annotationStorage[id] = true; + return annotation.getOperatorList( + partialEvaluator, + task, + false, + annotationStorage + ); + }, done.fail) + .then(opList => { + expect(opList.argsArray.length).toEqual(3); + expect(opList.argsArray[1]).toEqual( + new Uint8ClampedArray([26, 51, 76]) + ); + done(); + }, done.fail); + }); }); describe("ChoiceWidgetAnnotation", function () {