Button widget annotations: implement checkboxes and radio buttons

This commit is contained in:
benweet 2016-11-04 12:01:42 +00:00 committed by Tim van der Meij
parent d0893b0c48
commit ba012c7a68
4 changed files with 305 additions and 0 deletions

View File

@ -106,6 +106,8 @@ AnnotationFactory.prototype = /** @lends AnnotationFactory.prototype */ {
switch (fieldType) { switch (fieldType) {
case 'Tx': case 'Tx':
return new TextWidgetAnnotation(parameters); return new TextWidgetAnnotation(parameters);
case 'Btn':
return new ButtonWidgetAnnotation(parameters);
case 'Ch': case 'Ch':
return new ChoiceWidgetAnnotation(parameters); return new ChoiceWidgetAnnotation(parameters);
} }
@ -767,6 +769,59 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() {
return TextWidgetAnnotation; return TextWidgetAnnotation;
})(); })();
var ButtonWidgetAnnotation = (function ButtonWidgetAnnotationClosure() {
function ButtonWidgetAnnotation(params) {
WidgetAnnotation.call(this, params);
this.data.pushbutton = this.hasFieldFlag(AnnotationFieldFlag.PUSHBUTTON);
this.data.radio = !this.data.pushbutton &&
this.hasFieldFlag(AnnotationFieldFlag.RADIO);
if (isName(this.data.fieldValue)) {
this.data.fieldValue = this.data.fieldValue.name;
}
// Get the value of the radio button
if (this.data.radio) {
// Generate a unique ID in case no value is found
this.data.buttonValue = Math.random().toString(16).slice(2);
var appearanceState = params.dict.get('AP');
if (isDict(appearanceState)) {
var appearances = appearanceState.get('N');
if (isDict(appearances) && appearances.has('Off')) {
var keys = appearances.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() { var ChoiceWidgetAnnotation = (function ChoiceWidgetAnnotationClosure() {
function ChoiceWidgetAnnotation(params) { function ChoiceWidgetAnnotation(params) {
WidgetAnnotation.call(this, params); WidgetAnnotation.call(this, params);

View File

@ -76,6 +76,17 @@ AnnotationElementFactory.prototype =
switch (fieldType) { switch (fieldType) {
case 'Tx': case 'Tx':
return new TextWidgetAnnotationElement(parameters); return new TextWidgetAnnotationElement(parameters);
case 'Btn':
if (!parameters.data.pushbutton) {
if (parameters.data.radio) {
return new RadioButtonWidgetAnnotationElement(parameters);
} else {
return new CheckboxWidgetAnnotationElement(parameters);
}
} else {
warn('Unimplemented push button');
}
break;
case 'Ch': case 'Ch':
return new ChoiceWidgetAnnotationElement(parameters); return new ChoiceWidgetAnnotationElement(parameters);
} }
@ -141,6 +152,7 @@ var AnnotationElement = (function AnnotationElementClosure() {
var height = data.rect[3] - data.rect[1]; var height = data.rect[3] - data.rect[1];
container.setAttribute('data-annotation-id', data.id); container.setAttribute('data-annotation-id', data.id);
container.setAttribute('data-annotation-name', data.fieldName);
// Do *not* modify `data.rect`, since that will corrupt the annotation // Do *not* modify `data.rect`, since that will corrupt the annotation
// position on subsequent calls to `_createContainer` (see issue 6804). // position on subsequent calls to `_createContainer` (see issue 6804).
@ -531,6 +543,91 @@ 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 = 'checkboxWidgetAnnotation';
var element = document.createElement('input');
element.type = 'checkbox';
element.id = this.data.fieldName;
if (this.data.fieldValue && this.data.fieldValue !== 'Off') {
element.checked = true;
}
this.container.appendChild(element);
element = document.createElement('label');
element.htmlFor = this.data.fieldName;
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 = 'radioButtonWidgetAnnotation';
var element = document.createElement('input');
var id = this.data.fieldName + '.' + this.data.buttonValue;
element.type = 'radio';
element.id = id;
element.name = this.data.fieldName;
element.value = this.data.buttonValue;
if (this.data.fieldValue === this.data.buttonValue) {
element.checked = true;
}
this.container.appendChild(element);
element = document.createElement('label');
element.htmlFor = id;
this.container.appendChild(element);
return this.container;
},
});
return RadioButtonWidgetAnnotationElement;
})();
/**
* @class * @class
* @alias ChoiceWidgetAnnotationElement * @alias ChoiceWidgetAnnotationElement
*/ */

View File

@ -869,6 +869,97 @@ describe('Annotation layer', function() {
}); });
}); });
describe('CheckboxWidgetAnnotation', function() {
var checkboxWidgetDict;
beforeEach(function (done) {
checkboxWidgetDict = new Dict();
checkboxWidgetDict.set('Type', Name.get('Annot'));
checkboxWidgetDict.set('Subtype', Name.get('Widget'));
checkboxWidgetDict.set('FT', Name.get('Btn'));
done();
});
afterEach(function () {
checkboxWidgetDict = null;
});
it('should have proper flags',
function() {
var checkboxWidgetRef = new Ref(124, 0);
var xref = new XRefMock([
{ ref: checkboxWidgetRef, data: checkboxWidgetDict, }
]);
var checkboxWidgetAnnotation =
annotationFactory.create(xref, checkboxWidgetRef);
expect(checkboxWidgetAnnotation.data.radio).toEqual(false);
expect(checkboxWidgetAnnotation.data.pushbutton).toEqual(false);
expect(checkboxWidgetAnnotation.data.fieldValue).toEqual(null);
});
it('should have a proper value',
function() {
checkboxWidgetDict.set('V', Name.get('1'));
var checkboxWidgetRef = new Ref(124, 0);
var xref = new XRefMock([
{ ref: checkboxWidgetRef, data: checkboxWidgetDict, }
]);
var checkboxWidgetAnnotation =
annotationFactory.create(xref, checkboxWidgetRef);
expect(checkboxWidgetAnnotation.data.fieldValue).toEqual('1');
});
});
describe('RadioButtonWidgetAnnotation', function() {
var radioButtonWidgetDict;
beforeEach(function (done) {
radioButtonWidgetDict = new Dict();
radioButtonWidgetDict.set('Type', Name.get('Annot'));
radioButtonWidgetDict.set('Subtype', Name.get('Widget'));
radioButtonWidgetDict.set('FT', Name.get('Btn'));
radioButtonWidgetDict.set('Ff', AnnotationFieldFlag.RADIO);
done();
});
afterEach(function () {
radioButtonWidgetDict = null;
});
it('should have proper flags',
function() {
var radioButtonWidgetRef = new Ref(124, 0);
var xref = new XRefMock([
{ ref: radioButtonWidgetRef, data: radioButtonWidgetDict, }
]);
var radioButtonWidgetAnnotation =
annotationFactory.create(xref, radioButtonWidgetRef);
expect(radioButtonWidgetAnnotation.data.radio).toEqual(true);
expect(radioButtonWidgetAnnotation.data.pushbutton).toEqual(false);
expect(radioButtonWidgetAnnotation.data.fieldValue).toEqual(null);
});
it('should have a proper value',
function() {
radioButtonWidgetDict.set('V', Name.get('1'));
var radioButtonWidgetRef = new Ref(124, 0);
var xref = new XRefMock([
{ ref: radioButtonWidgetRef, data: radioButtonWidgetDict, }
]);
var radioButtonWidgetAnnotation =
annotationFactory.create(xref, radioButtonWidgetRef);
expect(radioButtonWidgetAnnotation.data.fieldValue).toEqual('1');
});
});
describe('ChoiceWidgetAnnotation', function() { describe('ChoiceWidgetAnnotation', function() {
var choiceWidgetDict; var choiceWidgetDict;

View File

@ -97,6 +97,68 @@
width: 115%; width: 115%;
} }
.annotationLayer .checkboxWidgetAnnotation label,
.annotationLayer .radioButtonWidgetAnnotation label {
background-color: rgba(0, 54, 255, 0.13);
border: 1px solid transparent;
box-sizing: border-box;
cursor: pointer;
height: 100%;
position: absolute;
width: 100%;
}
.annotationLayer .checkboxWidgetAnnotation input,
.annotationLayer .radioButtonWidgetAnnotation input {
position: absolute;
left: -9999px;
}
.annotationLayer .radioButtonWidgetAnnotation label {
border-radius: 50%;
}
.annotationLayer .checkboxWidgetAnnotation label:hover,
.annotationLayer .radioButtonWidgetAnnotation label:hover,
.annotationLayer .checkboxWidgetAnnotation label:focus,
.annotationLayer .radioButtonWidgetAnnotation label:focus,
.annotationLayer .checkboxWidgetAnnotation input:focus + label,
.annotationLayer .radioButtonWidgetAnnotation input:focus + label {
border: 1px solid #000;
}
.annotationLayer .checkboxWidgetAnnotation input:checked + label:before,
.annotationLayer .radioButtonWidgetAnnotation input:checked + label:before {
content: '';
left: 50%;
top: 50%;
position: absolute;
}
.annotationLayer .checkboxWidgetAnnotation input:checked + label:before {
border-bottom: 1px solid #000;
border-left: 1px solid #000;
height: 25%;
-webkit-transform: translate(-50%, -65%) rotateZ(-45deg);
-moz-transform: translate(-50%, -65%) rotateZ(-45deg);
-o-transform: translate(-50%, -65%) rotateZ(-45deg);
-ms-transform: translate(-50%, -65%) rotateZ(-45deg);
transform: translate(-50%, -65%) rotateZ(-45deg);
width: 60%;
}
.annotationLayer .radioButtonWidgetAnnotation input:checked + label:before {
background-color: #000;
border-radius: 50%;
height: 50%;
-webkit-transform: translate(-50%, -50%);
-moz-transform: translate(-50%, -50%);
-o-transform: translate(-50%, -50%);
-ms-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
width: 50%;
}
.annotationLayer .popupWrapper { .annotationLayer .popupWrapper {
position: absolute; position: absolute;
width: 20em; width: 20em;