diff --git a/src/core/annotation.js b/src/core/annotation.js index 08d1d670f..01f308853 100644 --- a/src/core/annotation.js +++ b/src/core/annotation.js @@ -1137,7 +1137,8 @@ class WidgetAnnotation extends Annotation { } async save(evaluator, task, annotationStorage) { - if (this.data.fieldValue === annotationStorage[this.data.id]) { + const value = annotationStorage[this.data.id]; + if (value === this.data.fieldValue || value === undefined) { return null; } @@ -1156,7 +1157,6 @@ class WidgetAnnotation extends Annotation { return null; } - const value = annotationStorage[this.data.id]; const bbox = [ 0, 0, @@ -1222,7 +1222,13 @@ class WidgetAnnotation extends Annotation { return null; } const value = annotationStorage[this.data.id]; + if (value === undefined) { + // The annotation hasn't been rendered so use the appearance + return null; + } + if (value === "") { + // the field is empty: nothing to render return ""; } @@ -1695,7 +1701,16 @@ class ButtonWidgetAnnotation extends WidgetAnnotation { } if (annotationStorage) { - const value = annotationStorage[this.data.id] || false; + const value = annotationStorage[this.data.id]; + if (value === undefined) { + return super.getOperatorList( + evaluator, + task, + renderForms, + annotationStorage + ); + } + let appearance; if (value) { appearance = this.checkedAppearance; @@ -1741,9 +1756,12 @@ class ButtonWidgetAnnotation extends WidgetAnnotation { } async _saveCheckbox(evaluator, task, annotationStorage) { - const defaultValue = this.data.fieldValue && this.data.fieldValue !== "Off"; const value = annotationStorage[this.data.id]; + if (value === undefined) { + return null; + } + const defaultValue = this.data.fieldValue && this.data.fieldValue !== "Off"; if (defaultValue === value) { return null; } @@ -1780,9 +1798,12 @@ class ButtonWidgetAnnotation extends WidgetAnnotation { } async _saveRadioButton(evaluator, task, annotationStorage) { - const defaultValue = this.data.fieldValue === this.data.buttonValue; const value = annotationStorage[this.data.id]; + if (value === undefined) { + return null; + } + const defaultValue = this.data.fieldValue === this.data.buttonValue; if (defaultValue === value) { return null; } diff --git a/src/display/annotation_layer.js b/src/display/annotation_layer.js index 643e74c8b..effa122b5 100644 --- a/src/display/annotation_layer.js +++ b/src/display/annotation_layer.js @@ -720,7 +720,7 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement { // used and the full array of field values is stored. storage.getOrCreateValue( id, - this.data.fieldValue.length > 0 ? this.data.fieldValue[0] : null + this.data.fieldValue.length > 0 ? this.data.fieldValue[0] : undefined ); const selectElement = document.createElement("select"); diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 82ebee21d..3bd33c839 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -107,6 +107,7 @@ !bug766086.pdf !bug793632.pdf !bug1020858.pdf +!prefilled_f1040.pdf !bug1050040.pdf !bug1200096.pdf !bug1068432.pdf diff --git a/test/pdfs/prefilled_f1040.pdf b/test/pdfs/prefilled_f1040.pdf new file mode 100644 index 000000000..5529a31f8 Binary files /dev/null and b/test/pdfs/prefilled_f1040.pdf differ diff --git a/test/test_manifest.json b/test/test_manifest.json index 549b8cd1e..0608b1c78 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -3659,6 +3659,16 @@ "type": "eq", "about": "CFF font that is drawn with clipping." }, + { "id": "prefilled_f1040", + "file": "pdfs/prefilled_f1040.pdf", + "md5": "2335da66fb7c2c3b84971597f27785e2", + "rounds": 1, + "type": "eq", + "print": true, + "annotationStorage": { + "1605R": true + } + }, { "id": "clippath", "file": "pdfs/clippath.pdf", "md5": "7ab95c0f106dccd90d6569f241fe8771", diff --git a/test/unit/annotation_spec.js b/test/unit/annotation_spec.js index 26659ffe5..d212c467e 100644 --- a/test/unit/annotation_spec.js +++ b/test/unit/annotation_spec.js @@ -1611,6 +1611,55 @@ describe("annotation", function () { }, done.fail); }); + it("should render regular text for printing using normal appearance", function (done) { + const textWidgetRef = Ref.get(271, 0); + + const appearanceStatesDict = new Dict(); + const normalAppearanceDict = new Dict(); + + const normalAppearanceStream = new StringStream("0.1 0.2 0.3 rg"); + normalAppearanceStream.dict = normalAppearanceDict; + + appearanceStatesDict.set("N", normalAppearanceStream); + textWidgetDict.set("AP", appearanceStatesDict); + + const xref = new XRefMock([ + { ref: textWidgetRef, data: textWidgetDict }, + fontRefObj, + ]); + const task = new WorkerTask("test print"); + partialEvaluator.xref = xref; + + AnnotationFactory.create( + xref, + textWidgetRef, + pdfManagerMock, + idFactoryMock + ) + .then(annotation => { + const annotationStorage = {}; + return annotation.getOperatorList( + partialEvaluator, + task, + false, + annotationStorage + ); + }) + .then(opList => { + expect(opList.argsArray.length).toEqual(3); + expect(opList.fnArray).toEqual([ + OPS.beginAnnotation, + OPS.setFillRGBColor, + OPS.endAnnotation, + ]); + expect(opList.argsArray[1]).toEqual( + new Uint8ClampedArray([26, 51, 76]) + ); + done(); + }) + .catch(done.fail); + }); + it("should render auto-sized text for printing", function (done) { textWidgetDict.set("DA", "/Helv 0 Tf"); @@ -2278,6 +2327,64 @@ describe("annotation", function () { .catch(done.fail); }); + it("should render checkboxes for printing using normal appearance", function (done) { + const appearanceStatesDict = new Dict(); + const normalAppearanceDict = new Dict(); + const checkedAppearanceDict = new Dict(); + const uncheckedAppearanceDict = new Dict(); + + const checkedStream = new StringStream("0.1 0.2 0.3 rg"); + checkedStream.dict = checkedAppearanceDict; + + const uncheckedStream = new StringStream("0.3 0.2 0.1 rg"); + uncheckedStream.dict = uncheckedAppearanceDict; + + checkedAppearanceDict.set("BBox", [0, 0, 8, 8]); + checkedAppearanceDict.set("FormType", 1); + checkedAppearanceDict.set("Matrix", [1, 0, 0, 1, 0, 0]); + normalAppearanceDict.set("Checked", checkedStream); + normalAppearanceDict.set("Off", uncheckedStream); + appearanceStatesDict.set("N", normalAppearanceDict); + + buttonWidgetDict.set("AP", appearanceStatesDict); + buttonWidgetDict.set("AS", Name.get("Checked")); + + 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 annotationStorage = {}; + return annotation.getOperatorList( + partialEvaluator, + task, + false, + annotationStorage + ); + }) + .then(opList => { + expect(opList.argsArray.length).toEqual(3); + expect(opList.fnArray).toEqual([ + OPS.beginAnnotation, + OPS.setFillRGBColor, + OPS.endAnnotation, + ]); + expect(opList.argsArray[1]).toEqual( + new Uint8ClampedArray([26, 51, 76]) + ); + done(); + }) + .catch(done.fail); + }); + it("should save checkboxes", function (done) { const appearanceStatesDict = new Dict(); const normalAppearanceDict = new Dict(); @@ -2513,6 +2620,65 @@ describe("annotation", function () { }, done.fail); }); + it("should render radio buttons for printing using normal appearance", function (done) { + const appearanceStatesDict = new Dict(); + const normalAppearanceDict = new Dict(); + const checkedAppearanceDict = new Dict(); + const uncheckedAppearanceDict = new Dict(); + + const checkedStream = new StringStream("0.1 0.2 0.3 rg"); + checkedStream.dict = checkedAppearanceDict; + + const uncheckedStream = new StringStream("0.3 0.2 0.1 rg"); + uncheckedStream.dict = uncheckedAppearanceDict; + + checkedAppearanceDict.set("BBox", [0, 0, 8, 8]); + checkedAppearanceDict.set("FormType", 1); + checkedAppearanceDict.set("Matrix", [1, 0, 0, 1, 0, 0]); + normalAppearanceDict.set("Checked", checkedStream); + normalAppearanceDict.set("Off", uncheckedStream); + appearanceStatesDict.set("N", normalAppearanceDict); + + buttonWidgetDict.set("Ff", AnnotationFieldFlag.RADIO); + buttonWidgetDict.set("AP", appearanceStatesDict); + buttonWidgetDict.set("AS", Name.get("Off")); + + 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 annotationStorage = {}; + return annotation.getOperatorList( + partialEvaluator, + task, + false, + annotationStorage + ); + }) + .then(opList => { + expect(opList.argsArray.length).toEqual(3); + expect(opList.fnArray).toEqual([ + OPS.beginAnnotation, + OPS.setFillRGBColor, + OPS.endAnnotation, + ]); + expect(opList.argsArray[1]).toEqual( + new Uint8ClampedArray([76, 51, 26]) + ); + done(); + }) + .catch(done.fail); + }); + it("should save radio buttons", function (done) { const appearanceStatesDict = new Dict(); const normalAppearanceDict = new Dict();