Skip to content

Commit

Permalink
Obtain the export values for choice widgets from the normal appearance
Browse files Browse the repository at this point in the history
The down appearance (`D`) is optional and not available in the document
from mozilla#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.
  • Loading branch information
timvandermeij committed Aug 22, 2020
1 parent cadebc7 commit a1593d6
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 34 deletions.
18 changes: 8 additions & 10 deletions src/core/annotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -1529,25 +1529,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 appearance for the off state 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;
}
Expand Down
60 changes: 36 additions & 24 deletions test/unit/annotation_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -1931,9 +1931,38 @@ describe("annotation", function () {
}, done.fail);
});

it("should handle checkboxes with missing 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();
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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));
Expand Down

0 comments on commit a1593d6

Please sign in to comment.