Merge pull request #7898 from timvandermeij/acroforms-checkbox-radiobutton

Interactive forms: render button widget annotations (checkboxes and radio buttons)
This commit is contained in:
Tim van der Meij 2016-12-17 21:22:15 +01:00 committed by GitHub
commit 017e9b98d1
8 changed files with 270 additions and 5 deletions

View File

@ -106,6 +106,8 @@ AnnotationFactory.prototype = /** @lends AnnotationFactory.prototype */ {
switch (fieldType) {
case 'Tx':
return new TextWidgetAnnotation(parameters);
case 'Btn':
return new ButtonWidgetAnnotation(parameters);
case 'Ch':
return new ChoiceWidgetAnnotation(parameters);
}
@ -767,6 +769,78 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() {
return TextWidgetAnnotation;
})();
var ButtonWidgetAnnotation = (function ButtonWidgetAnnotationClosure() {
function ButtonWidgetAnnotation(params) {
WidgetAnnotation.call(this, params);
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;
// The parent field's `V` entry holds a `Name` object with the appearance
// state of whichever child field is currently in the "on" state.
var fieldParent = params.dict.get('Parent');
if (!isDict(fieldParent) || !fieldParent.has('V')) {
return;
}
var fieldParentValue = fieldParent.get('V');
if (!isName(fieldParentValue)) {
return;
}
this.data.fieldValue = fieldParentValue.name;
// The button's value corresponds to its appearance state.
var appearanceStates = params.dict.get('AP');
if (!isDict(appearanceStates)) {
return;
}
var normalAppearanceState = appearanceStates.get('N');
if (!isDict(normalAppearanceState)) {
return;
}
var keys = normalAppearanceState.getKeys();
for (var i = 0, ii = keys.length; i < ii; i++) {
if (keys[i] !== 'Off') {
this.data.buttonValue = keys[i];
break;
}
}
}
}
Util.inherit(ButtonWidgetAnnotation, WidgetAnnotation, {
getOperatorList:
function ButtonWidgetAnnotation_getOperatorList(evaluator, task,
renderForms) {
var operatorList = new OperatorList();
// Do not render form elements on the canvas when interactive forms are
// enabled. The display layer is responsible for rendering them instead.
if (renderForms) {
return Promise.resolve(operatorList);
}
if (this.appearance) {
return Annotation.prototype.getOperatorList.call(this, evaluator, task,
renderForms);
}
return Promise.resolve(operatorList);
}
});
return ButtonWidgetAnnotation;
})();
var ChoiceWidgetAnnotation = (function ChoiceWidgetAnnotationClosure() {
function ChoiceWidgetAnnotation(params) {
WidgetAnnotation.call(this, params);

View File

@ -76,6 +76,15 @@ AnnotationElementFactory.prototype =
switch (fieldType) {
case 'Tx':
return new TextWidgetAnnotationElement(parameters);
case 'Btn':
if (parameters.data.radioButton) {
return new RadioButtonWidgetAnnotationElement(parameters);
} else if (parameters.data.checkBox) {
return new CheckboxWidgetAnnotationElement(parameters);
} else {
warn('Unimplemented button widget annotation: pushbutton');
}
break;
case 'Ch':
return new ChoiceWidgetAnnotationElement(parameters);
}
@ -531,6 +540,83 @@ var TextWidgetAnnotationElement = (
})();
/**
* @class
* @alias CheckboxWidgetAnnotationElement
*/
var CheckboxWidgetAnnotationElement =
(function CheckboxWidgetAnnotationElementClosure() {
function CheckboxWidgetAnnotationElement(parameters) {
WidgetAnnotationElement.call(this, parameters,
parameters.renderInteractiveForms);
}
Util.inherit(CheckboxWidgetAnnotationElement, WidgetAnnotationElement, {
/**
* Render the checkbox widget annotation's HTML element
* in the empty container.
*
* @public
* @memberof CheckboxWidgetAnnotationElement
* @returns {HTMLSectionElement}
*/
render: function CheckboxWidgetAnnotationElement_render() {
this.container.className = 'buttonWidgetAnnotation checkBox';
var element = document.createElement('input');
element.disabled = this.data.readOnly;
element.type = 'checkbox';
if (this.data.fieldValue && this.data.fieldValue !== 'Off') {
element.setAttribute('checked', true);
}
this.container.appendChild(element);
return this.container;
}
});
return CheckboxWidgetAnnotationElement;
})();
/**
* @class
* @alias RadioButtonWidgetAnnotationElement
*/
var RadioButtonWidgetAnnotationElement =
(function RadioButtonWidgetAnnotationElementClosure() {
function RadioButtonWidgetAnnotationElement(parameters) {
WidgetAnnotationElement.call(this, parameters,
parameters.renderInteractiveForms);
}
Util.inherit(RadioButtonWidgetAnnotationElement, WidgetAnnotationElement, {
/**
* Render the radio button widget annotation's HTML element
* in the empty container.
*
* @public
* @memberof RadioButtonWidgetAnnotationElement
* @returns {HTMLSectionElement}
*/
render: function RadioButtonWidgetAnnotationElement_render() {
this.container.className = 'buttonWidgetAnnotation radioButton';
var element = document.createElement('input');
element.disabled = this.data.readOnly;
element.type = 'radio';
element.name = this.data.fieldName;
if (this.data.fieldValue === this.data.buttonValue) {
element.setAttribute('checked', true);
}
this.container.appendChild(element);
return this.container;
}
});
return RadioButtonWidgetAnnotationElement;
})();
/**
* @class
* @alias ChoiceWidgetAnnotationElement
*/

View File

@ -15,6 +15,11 @@
/* Used for annotation layer tests */
* {
padding: 0;
margin: 0;
}
.annotationLayer {
position: absolute;
left: 0;
@ -45,7 +50,9 @@
.annotationLayer .textWidgetAnnotation input,
.annotationLayer .textWidgetAnnotation textarea,
.annotationLayer .choiceWidgetAnnotation select {
.annotationLayer .choiceWidgetAnnotation select,
.annotationLayer .buttonWidgetAnnotation.checkBox input,
.annotationLayer .buttonWidgetAnnotation.radioButton input {
background-color: rgba(0, 54, 255, 0.13);
border: 1px solid transparent;
box-sizing: border-box;
@ -64,7 +71,9 @@
.annotationLayer .textWidgetAnnotation input[disabled],
.annotationLayer .textWidgetAnnotation textarea[disabled],
.annotationLayer .choiceWidgetAnnotation select[disabled] {
.annotationLayer .choiceWidgetAnnotation select[disabled],
.annotationLayer .buttonWidgetAnnotation.checkBox input[disabled],
.annotationLayer .buttonWidgetAnnotation.radioButton input[disabled] {
background: none;
border: 1px solid transparent;
}
@ -75,6 +84,14 @@
padding-right: 0;
}
.annotationLayer .buttonWidgetAnnotation.checkBox input,
.annotationLayer .buttonWidgetAnnotation.radioButton input {
-webkit-appearance: none;
-moz-appearance: none;
-ms-appearance: none;
appearance: none;
}
.annotationLayer .popupAnnotation {
display: block !important;
}

View File

@ -266,5 +266,6 @@
!annotation-fileattachment.pdf
!annotation-text-widget.pdf
!annotation-choice-widget.pdf
!annotation-button-widget.pdf
!zero_descent.pdf
!operator-in-TJ-array.pdf

Binary file not shown.

View File

@ -3312,6 +3312,20 @@
"type": "eq",
"forms": true
},
{ "id": "annotation-button-widget-annotations",
"file": "pdfs/annotation-button-widget.pdf",
"md5": "5cf23adfff84256d9cfe261bea96dade",
"rounds": 1,
"type": "eq",
"annotations": true
},
{ "id": "annotation-button-widget-forms",
"file": "pdfs/annotation-button-widget.pdf",
"md5": "5cf23adfff84256d9cfe261bea96dade",
"rounds": 1,
"type": "eq",
"forms": true
},
{ "id": "issue6108",
"file": "pdfs/issue6108.pdf",
"md5": "8961cb55149495989a80bf0487e0f076",

View File

@ -869,6 +869,65 @@ describe('Annotation layer', function() {
});
});
describe('ButtonWidgetAnnotation', function() {
var buttonWidgetDict;
beforeEach(function (done) {
buttonWidgetDict = new Dict();
buttonWidgetDict.set('Type', Name.get('Annot'));
buttonWidgetDict.set('Subtype', Name.get('Widget'));
buttonWidgetDict.set('FT', Name.get('Btn'));
done();
});
afterEach(function () {
buttonWidgetDict = null;
});
it('should handle checkboxes', function() {
buttonWidgetDict.set('V', Name.get('1'));
var buttonWidgetRef = new Ref(124, 0);
var xref = new XRefMock([
{ ref: buttonWidgetRef, data: buttonWidgetDict, }
]);
var buttonWidgetAnnotation =
annotationFactory.create(xref, buttonWidgetRef);
expect(buttonWidgetAnnotation.data.checkBox).toEqual(true);
expect(buttonWidgetAnnotation.data.fieldValue).toEqual('1');
expect(buttonWidgetAnnotation.data.radioButton).toEqual(false);
});
it('should handle radio buttons', function() {
var parentDict = new Dict();
parentDict.set('V', Name.get('1'));
var normalAppearanceStateDict = new Dict();
normalAppearanceStateDict.set('2', null);
var appearanceStatesDict = new Dict();
appearanceStatesDict.set('N', normalAppearanceStateDict);
buttonWidgetDict.set('Ff', AnnotationFieldFlag.RADIO);
buttonWidgetDict.set('Parent', parentDict);
buttonWidgetDict.set('AP', appearanceStatesDict);
var buttonWidgetRef = new Ref(124, 0);
var xref = new XRefMock([
{ ref: buttonWidgetRef, data: buttonWidgetDict, }
]);
var buttonWidgetAnnotation =
annotationFactory.create(xref, buttonWidgetRef);
expect(buttonWidgetAnnotation.data.checkBox).toEqual(false);
expect(buttonWidgetAnnotation.data.radioButton).toEqual(true);
expect(buttonWidgetAnnotation.data.fieldValue).toEqual('1');
expect(buttonWidgetAnnotation.data.buttonValue).toEqual('2');
});
});
describe('ChoiceWidgetAnnotation', function() {
var choiceWidgetDict;

View File

@ -43,7 +43,9 @@
.annotationLayer .textWidgetAnnotation input,
.annotationLayer .textWidgetAnnotation textarea,
.annotationLayer .choiceWidgetAnnotation select {
.annotationLayer .choiceWidgetAnnotation select,
.annotationLayer .buttonWidgetAnnotation.checkBox input,
.annotationLayer .buttonWidgetAnnotation.radioButton input {
background-color: rgba(0, 54, 255, 0.13);
border: 1px solid transparent;
box-sizing: border-box;
@ -62,7 +64,9 @@
.annotationLayer .textWidgetAnnotation input[disabled],
.annotationLayer .textWidgetAnnotation textarea[disabled],
.annotationLayer .choiceWidgetAnnotation select[disabled] {
.annotationLayer .choiceWidgetAnnotation select[disabled],
.annotationLayer .buttonWidgetAnnotation.checkBox input[disabled],
.annotationLayer .buttonWidgetAnnotation.radioButton input[disabled] {
background: none;
border: 1px solid transparent;
cursor: not-allowed;
@ -70,7 +74,9 @@
.annotationLayer .textWidgetAnnotation input:hover,
.annotationLayer .textWidgetAnnotation textarea:hover,
.annotationLayer .choiceWidgetAnnotation select:hover {
.annotationLayer .choiceWidgetAnnotation select:hover,
.annotationLayer .buttonWidgetAnnotation.checkBox input:hover,
.annotationLayer .buttonWidgetAnnotation.radioButton input:hover {
border: 1px solid #000;
}
@ -97,6 +103,14 @@
width: 115%;
}
.annotationLayer .buttonWidgetAnnotation.checkBox input,
.annotationLayer .buttonWidgetAnnotation.radioButton input {
-webkit-appearance: none;
-moz-appearance: none;
-ms-appearance: none;
appearance: none;
}
.annotationLayer .popupWrapper {
position: absolute;
width: 20em;