diff --git a/src/core/annotation.js b/src/core/annotation.js index 993303302..e5b60bd56 100644 --- a/src/core/annotation.js +++ b/src/core/annotation.js @@ -732,45 +732,70 @@ class ButtonWidgetAnnotation extends WidgetAnnotation { this.data.checkBox = !this.hasFieldFlag(AnnotationFieldFlag.RADIO) && !this.hasFieldFlag(AnnotationFieldFlag.PUSHBUTTON); - if (this.data.checkBox) { - if (!isName(this.data.fieldValue)) { - return; - } - this.data.fieldValue = this.data.fieldValue.name; - } - this.data.radioButton = this.hasFieldFlag(AnnotationFieldFlag.RADIO) && !this.hasFieldFlag(AnnotationFieldFlag.PUSHBUTTON); - if (this.data.radioButton) { - this.data.fieldValue = this.data.buttonValue = null; + this.data.pushButton = this.hasFieldFlag(AnnotationFieldFlag.PUSHBUTTON); - // The parent field's `V` entry holds a `Name` object with the appearance - // state of whichever child field is currently in the "on" state. - let fieldParent = params.dict.get('Parent'); - if (isDict(fieldParent) && fieldParent.has('V')) { - let fieldParentValue = fieldParent.get('V'); - if (isName(fieldParentValue)) { - this.data.fieldValue = fieldParentValue.name; - } - } + if (this.data.checkBox) { + this._processCheckBox(); + } else if (this.data.radioButton) { + this._processRadioButton(params); + } else if (this.data.pushButton) { + this._processPushButton(params); + } else { + warn('Invalid field flags for button widget annotation'); + } + } - // The button's value corresponds to its appearance state. - let appearanceStates = params.dict.get('AP'); - if (!isDict(appearanceStates)) { - return; - } - let normalAppearanceState = appearanceStates.get('N'); - if (!isDict(normalAppearanceState)) { - return; - } - let keys = normalAppearanceState.getKeys(); - for (let i = 0, ii = keys.length; i < ii; i++) { - if (keys[i] !== 'Off') { - this.data.buttonValue = keys[i]; - break; - } + _processCheckBox() { + if (!isName(this.data.fieldValue)) { + return; + } + this.data.fieldValue = this.data.fieldValue.name; + } + + _processRadioButton(params) { + this.data.fieldValue = this.data.buttonValue = null; + + // The parent field's `V` entry holds a `Name` object with the appearance + // state of whichever child field is currently in the "on" state. + let fieldParent = params.dict.get('Parent'); + if (isDict(fieldParent) && fieldParent.has('V')) { + let fieldParentValue = fieldParent.get('V'); + if (isName(fieldParentValue)) { + this.data.fieldValue = fieldParentValue.name; } } + + // The button's value corresponds to its appearance state. + let appearanceStates = params.dict.get('AP'); + if (!isDict(appearanceStates)) { + return; + } + let normalAppearanceState = appearanceStates.get('N'); + if (!isDict(normalAppearanceState)) { + return; + } + let keys = normalAppearanceState.getKeys(); + for (let i = 0, ii = keys.length; i < ii; i++) { + if (keys[i] !== 'Off') { + this.data.buttonValue = keys[i]; + break; + } + } + } + + _processPushButton(params) { + if (!params.dict.has('A')) { + warn('Push buttons without action dictionaries are not supported'); + return; + } + + Catalog.parseDestDictionary({ + destDict: params.dict, + resultObj: this.data, + docBaseUrl: params.pdfManager.docBaseUrl, + }); } } diff --git a/src/display/annotation_layer.js b/src/display/annotation_layer.js index 41272e93c..073c9cfa5 100644 --- a/src/display/annotation_layer.js +++ b/src/display/annotation_layer.js @@ -61,8 +61,7 @@ class AnnotationElementFactory { } else if (parameters.data.checkBox) { return new CheckboxWidgetAnnotationElement(parameters); } - warn('Unimplemented button widget annotation: pushbutton'); - break; + return new PushButtonWidgetAnnotationElement(parameters); case 'Ch': return new ChoiceWidgetAnnotationElement(parameters); } @@ -543,6 +542,25 @@ class RadioButtonWidgetAnnotationElement extends WidgetAnnotationElement { } } +class PushButtonWidgetAnnotationElement extends LinkAnnotationElement { + /** + * Render the push button widget annotation's HTML element + * in the empty container. + * + * @public + * @memberof PushButtonWidgetAnnotationElement + * @returns {HTMLSectionElement} + */ + render() { + // The rendering and functionality of a push button widget annotation is + // equal to that of a link annotation, but may have more functionality, such + // as performing actions on form fields (resetting, submitting, et cetera). + let container = super.render(); + container.className = 'buttonWidgetAnnotation pushButton'; + return container; + } +} + class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement { constructor(parameters) { super(parameters, parameters.renderInteractiveForms); diff --git a/test/annotation_layer_builder_overrides.css b/test/annotation_layer_builder_overrides.css index 404d39f46..97432939d 100644 --- a/test/annotation_layer_builder_overrides.css +++ b/test/annotation_layer_builder_overrides.css @@ -19,7 +19,8 @@ position: absolute; } -.annotationLayer .linkAnnotation > a { +.annotationLayer .linkAnnotation > a, +.annotationLayer .buttonWidgetAnnotation.pushButton > a { opacity: 0.2; background: #ff0; box-shadow: 0px 2px 10px #ff0; diff --git a/test/pdfs/issue4872.pdf.link b/test/pdfs/issue4872.pdf.link new file mode 100644 index 000000000..55f869dc6 --- /dev/null +++ b/test/pdfs/issue4872.pdf.link @@ -0,0 +1 @@ +https://web.archive.org/web/20171003035412/https://www.cs.utexas.edu/users/EWD/ewd02xx/EWD288.PDF diff --git a/test/test_manifest.json b/test/test_manifest.json index 63abe91e8..17b2d882e 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -3717,6 +3717,16 @@ "type": "eq", "annotations": true }, + { "id": "issue4872", + "file": "pdfs/issue4872.pdf", + "md5": "21c6cbc682140d6f6017bbeb45892053", + "rounds": 1, + "link": true, + "firstPage": 1, + "lastPage": 1, + "type": "eq", + "annotations": true + }, { "id": "issue6108", "file": "pdfs/issue6108.pdf", "md5": "8961cb55149495989a80bf0487e0f076", diff --git a/web/annotation_layer_builder.css b/web/annotation_layer_builder.css index 6154d748b..483941955 100644 --- a/web/annotation_layer_builder.css +++ b/web/annotation_layer_builder.css @@ -17,7 +17,8 @@ position: absolute; } -.annotationLayer .linkAnnotation > a { +.annotationLayer .linkAnnotation > a, +.annotationLayer .buttonWidgetAnnotation.pushButton > a { position: absolute; font-size: 1em; top: 0; @@ -26,11 +27,16 @@ height: 100%; } -.annotationLayer .linkAnnotation > a /* -ms-a */ { +.annotationLayer .linkAnnotation > a /* -ms-a */ { background: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7") 0 0 repeat; } -.annotationLayer .linkAnnotation > a:hover { +.annotationLayer .buttonWidgetAnnotation.pushButton > a /* -ms-a */ { + background: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7") 0 0 repeat; +} + +.annotationLayer .linkAnnotation > a:hover, +.annotationLayer .buttonWidgetAnnotation.pushButton > a:hover { opacity: 0.2; background: #ff0; box-shadow: 0px 2px 10px #ff0;