From a8efc0296beb4c390c55a40daa1aa1c1e8d133a7 Mon Sep 17 00:00:00 2001 From: Tim van der Meij Date: Sat, 22 Aug 2020 15:45:33 +0200 Subject: [PATCH] Obtain the export values for choice widgets from the normal appearance The down appearance (`D`) is optional and not available in the document from #12233, so the checkboxes are never saved/printed as checked because the checked appearance is based on the export value that is missing because the `D` entry is not available. Instead, we should use the normal appearance (`N`) since that one is required and therefore always available. Finally, the /Off appearance is optional according to section 12.7.4.2.3 of the specification, so that needs to be taken into account to match the specification and to fix reference test failures for the `annotation-button-widget-print` test. That is a file that doesn't specify an /Off appearance in the normal appearance dictionary. --- src/core/annotation.js | 18 +++++------ test/unit/annotation_spec.js | 60 +++++++++++++++++++++--------------- 2 files changed, 44 insertions(+), 34 deletions(-) diff --git a/src/core/annotation.js b/src/core/annotation.js index bca494b587f65..5fe3d86547620 100644 --- a/src/core/annotation.js +++ b/src/core/annotation.js @@ -1531,25 +1531,23 @@ class ButtonWidgetAnnotation extends WidgetAnnotation { return; } - const exportValueOptionsDict = customAppearance.get("D"); - if (!isDict(exportValueOptionsDict)) { + const normalAppearance = customAppearance.get("N"); + if (!isDict(normalAppearance)) { return; } - const exportValues = exportValueOptionsDict.getKeys(); - const hasCorrectOptionCount = exportValues.length === 2; - if (!hasCorrectOptionCount) { + const exportValues = normalAppearance.getKeys(); + if (!exportValues.includes("Off")) { + // The /Off appearance is optional. + exportValues.push("Off"); + } + if (exportValues.length !== 2) { return; } this.data.exportValue = exportValues[0] === "Off" ? exportValues[1] : exportValues[0]; - const normalAppearance = customAppearance.get("N"); - if (!isDict(normalAppearance)) { - return; - } - this.checkedAppearance = normalAppearance.get(this.data.exportValue); this.uncheckedAppearance = normalAppearance.get("Off") || null; } diff --git a/test/unit/annotation_spec.js b/test/unit/annotation_spec.js index 46ed399741e75..f948a926ddcf4 100644 --- a/test/unit/annotation_spec.js +++ b/test/unit/annotation_spec.js @@ -1882,11 +1882,11 @@ describe("annotation", function () { buttonWidgetDict.set("V", Name.get("1")); const appearanceStatesDict = new Dict(); - const exportValueOptionsDict = new Dict(); + const normalAppearanceDict = new Dict(); - exportValueOptionsDict.set("Off", 0); - exportValueOptionsDict.set("Checked", 1); - appearanceStatesDict.set("D", exportValueOptionsDict); + normalAppearanceDict.set("Off", 0); + normalAppearanceDict.set("Checked", 1); + appearanceStatesDict.set("N", normalAppearanceDict); buttonWidgetDict.set("AP", appearanceStatesDict); const buttonWidgetRef = Ref.get(124, 0); @@ -1931,9 +1931,38 @@ describe("annotation", function () { }, done.fail); }); + it("should handle checkboxes without /Off appearance", function (done) { + buttonWidgetDict.set("V", Name.get("1")); + + const appearanceStatesDict = new Dict(); + const normalAppearanceDict = new Dict(); + + normalAppearanceDict.set("Checked", 1); + appearanceStatesDict.set("N", normalAppearanceDict); + buttonWidgetDict.set("AP", appearanceStatesDict); + + const buttonWidgetRef = Ref.get(124, 0); + const xref = new XRefMock([ + { ref: buttonWidgetRef, data: buttonWidgetDict }, + ]); + + AnnotationFactory.create( + xref, + buttonWidgetRef, + pdfManagerMock, + idFactoryMock + ).then(({ data }) => { + expect(data.annotationType).toEqual(AnnotationType.WIDGET); + expect(data.checkBox).toEqual(true); + expect(data.fieldValue).toEqual("1"); + expect(data.radioButton).toEqual(false); + expect(data.exportValue).toEqual("Checked"); + done(); + }, done.fail); + }); + it("should render checkboxes for printing", function (done) { const appearanceStatesDict = new Dict(); - const exportValueOptionsDict = new Dict(); const normalAppearanceDict = new Dict(); const checkedAppearanceDict = new Dict(); const uncheckedAppearanceDict = new Dict(); @@ -1949,9 +1978,6 @@ describe("annotation", function () { checkedAppearanceDict.set("Matrix", [1, 0, 0, 1, 0, 0]); normalAppearanceDict.set("Checked", checkedStream); normalAppearanceDict.set("Off", uncheckedStream); - exportValueOptionsDict.set("Off", 0); - exportValueOptionsDict.set("Checked", 1); - appearanceStatesDict.set("D", exportValueOptionsDict); appearanceStatesDict.set("N", normalAppearanceDict); buttonWidgetDict.set("AP", appearanceStatesDict); @@ -2019,14 +2045,10 @@ describe("annotation", function () { it("should save checkboxes", function (done) { const appearanceStatesDict = new Dict(); - const exportValueOptionsDict = new Dict(); const normalAppearanceDict = new Dict(); normalAppearanceDict.set("Checked", Ref.get(314, 0)); normalAppearanceDict.set("Off", Ref.get(271, 0)); - exportValueOptionsDict.set("Off", 0); - exportValueOptionsDict.set("Checked", 1); - appearanceStatesDict.set("D", exportValueOptionsDict); appearanceStatesDict.set("N", normalAppearanceDict); buttonWidgetDict.set("AP", appearanceStatesDict); @@ -2059,8 +2081,7 @@ describe("annotation", function () { expect(oldData.data).toEqual( "123 0 obj\n" + "<< /Type /Annot /Subtype /Widget /FT /Btn " + - "/AP << /D << /Off 0 /Checked 1>> " + - "/N << /Checked 314 0 R /Off 271 0 R>>>> " + + "/AP << /N << /Checked 314 0 R /Off 271 0 R>>>> " + "/V /Checked /AS /Checked /M (date)>>\nendobj\n" ); return annotation; @@ -2142,7 +2163,6 @@ describe("annotation", function () { it("should render radio buttons for printing", function (done) { const appearanceStatesDict = new Dict(); - const exportValueOptionsDict = new Dict(); const normalAppearanceDict = new Dict(); const checkedAppearanceDict = new Dict(); const uncheckedAppearanceDict = new Dict(); @@ -2158,9 +2178,6 @@ describe("annotation", function () { checkedAppearanceDict.set("Matrix", [1, 0, 0, 1, 0, 0]); normalAppearanceDict.set("Checked", checkedStream); normalAppearanceDict.set("Off", uncheckedStream); - exportValueOptionsDict.set("Off", 0); - exportValueOptionsDict.set("Checked", 1); - appearanceStatesDict.set("D", exportValueOptionsDict); appearanceStatesDict.set("N", normalAppearanceDict); buttonWidgetDict.set("Ff", AnnotationFieldFlag.RADIO); @@ -2229,14 +2246,10 @@ describe("annotation", function () { it("should save radio buttons", function (done) { const appearanceStatesDict = new Dict(); - const exportValueOptionsDict = new Dict(); const normalAppearanceDict = new Dict(); normalAppearanceDict.set("Checked", Ref.get(314, 0)); normalAppearanceDict.set("Off", Ref.get(271, 0)); - exportValueOptionsDict.set("Off", 0); - exportValueOptionsDict.set("Checked", 1); - appearanceStatesDict.set("D", exportValueOptionsDict); appearanceStatesDict.set("N", normalAppearanceDict); buttonWidgetDict.set("Ff", AnnotationFieldFlag.RADIO); @@ -2282,8 +2295,7 @@ describe("annotation", function () { expect(radioData.data).toEqual( "123 0 obj\n" + "<< /Type /Annot /Subtype /Widget /FT /Btn /Ff 32768 " + - "/AP << /D << /Off 0 /Checked 1>> " + - "/N << /Checked 314 0 R /Off 271 0 R>>>> " + + "/AP << /N << /Checked 314 0 R /Off 271 0 R>>>> " + "/Parent 456 0 R /AS /Checked /M (date)>>\nendobj\n" ); expect(parentData.ref).toEqual(Ref.get(456, 0));