Merge pull request #7671 from timvandermeij/interactive-forms-choice-fields
Interactive forms: render choice widget annotations
This commit is contained in:
commit
9b3a91f365
@ -106,6 +106,8 @@ AnnotationFactory.prototype = /** @lends AnnotationFactory.prototype */ {
|
||||
switch (fieldType) {
|
||||
case 'Tx':
|
||||
return new TextWidgetAnnotation(parameters);
|
||||
case 'Ch':
|
||||
return new ChoiceWidgetAnnotation(parameters);
|
||||
}
|
||||
warn('Unimplemented widget field type "' + fieldType + '", ' +
|
||||
'falling back to base field type.');
|
||||
@ -619,8 +621,8 @@ var WidgetAnnotation = (function WidgetAnnotationClosure() {
|
||||
var data = this.data;
|
||||
|
||||
data.annotationType = AnnotationType.WIDGET;
|
||||
data.fieldValue = stringToPDFString(
|
||||
Util.getInheritableProperty(dict, 'V') || '');
|
||||
data.fieldValue = Util.getInheritableProperty(dict, 'V',
|
||||
/* getArray = */ true);
|
||||
data.alternativeText = stringToPDFString(dict.get('TU') || '');
|
||||
data.defaultAppearance = Util.getInheritableProperty(dict, 'DA') || '';
|
||||
var fieldType = Util.getInheritableProperty(dict, 'FT');
|
||||
@ -632,6 +634,8 @@ var WidgetAnnotation = (function WidgetAnnotationClosure() {
|
||||
data.fieldFlags = 0;
|
||||
}
|
||||
|
||||
data.readOnly = this.hasFieldFlag(AnnotationFieldFlag.READONLY);
|
||||
|
||||
// Hide signatures because we cannot validate them.
|
||||
if (data.fieldType === 'Sig') {
|
||||
this.setFlags(AnnotationFlag.HIDDEN);
|
||||
@ -693,6 +697,9 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() {
|
||||
function TextWidgetAnnotation(params) {
|
||||
WidgetAnnotation.call(this, params);
|
||||
|
||||
// The field value is always a string.
|
||||
this.data.fieldValue = stringToPDFString(this.data.fieldValue || '');
|
||||
|
||||
// Determine the alignment of text in the field.
|
||||
var alignment = Util.getInheritableProperty(params.dict, 'Q');
|
||||
if (!isInt(alignment) || alignment < 0 || alignment > 2) {
|
||||
@ -708,7 +715,6 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() {
|
||||
this.data.maxLen = maximumLength;
|
||||
|
||||
// Process field flags for the display layer.
|
||||
this.data.readOnly = this.hasFieldFlag(AnnotationFieldFlag.READONLY);
|
||||
this.data.multiLine = this.hasFieldFlag(AnnotationFieldFlag.MULTILINE);
|
||||
this.data.comb = this.hasFieldFlag(AnnotationFieldFlag.COMB) &&
|
||||
!this.hasFieldFlag(AnnotationFieldFlag.MULTILINE) &&
|
||||
@ -752,6 +758,62 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() {
|
||||
return TextWidgetAnnotation;
|
||||
})();
|
||||
|
||||
var ChoiceWidgetAnnotation = (function ChoiceWidgetAnnotationClosure() {
|
||||
function ChoiceWidgetAnnotation(params) {
|
||||
WidgetAnnotation.call(this, params);
|
||||
|
||||
// Determine the options. The options array may consist of strings or
|
||||
// arrays. If the array consists of arrays, then the first element of
|
||||
// each array is the export value and the second element of each array is
|
||||
// the display value. If the array consists of strings, then these
|
||||
// represent both the export and display value. In this case, we convert
|
||||
// it to an array of arrays as well for convenience in the display layer.
|
||||
this.data.options = [];
|
||||
|
||||
var options = params.dict.getArray('Opt');
|
||||
if (isArray(options)) {
|
||||
for (var i = 0, ii = options.length; i < ii; i++) {
|
||||
var option = options[i];
|
||||
|
||||
this.data.options[i] = {
|
||||
exportValue: isArray(option) ? option[0] : option,
|
||||
displayValue: isArray(option) ? option[1] : option,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the field value. In this case, it may be a string or an
|
||||
// array of strings. For convenience in the display layer, convert the
|
||||
// string to an array of one string as well.
|
||||
if (!isArray(this.data.fieldValue)) {
|
||||
this.data.fieldValue = [this.data.fieldValue];
|
||||
}
|
||||
|
||||
// Process field flags for the display layer.
|
||||
this.data.combo = this.hasFieldFlag(AnnotationFieldFlag.COMBO);
|
||||
this.data.multiSelect = this.hasFieldFlag(AnnotationFieldFlag.MULTISELECT);
|
||||
}
|
||||
|
||||
Util.inherit(ChoiceWidgetAnnotation, WidgetAnnotation, {
|
||||
getOperatorList:
|
||||
function ChoiceWidgetAnnotation_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);
|
||||
}
|
||||
|
||||
return Annotation.prototype.getOperatorList.call(this, evaluator, task,
|
||||
renderForms);
|
||||
}
|
||||
});
|
||||
|
||||
return ChoiceWidgetAnnotation;
|
||||
})();
|
||||
|
||||
var TextAnnotation = (function TextAnnotationClosure() {
|
||||
var DEFAULT_ICON_SIZE = 22; // px
|
||||
|
||||
|
@ -76,6 +76,8 @@ AnnotationElementFactory.prototype =
|
||||
switch (fieldType) {
|
||||
case 'Tx':
|
||||
return new TextWidgetAnnotationElement(parameters);
|
||||
case 'Ch':
|
||||
return new ChoiceWidgetAnnotationElement(parameters);
|
||||
}
|
||||
return new WidgetAnnotationElement(parameters);
|
||||
|
||||
@ -400,9 +402,7 @@ var TextAnnotationElement = (function TextAnnotationElementClosure() {
|
||||
* @alias WidgetAnnotationElement
|
||||
*/
|
||||
var WidgetAnnotationElement = (function WidgetAnnotationElementClosure() {
|
||||
function WidgetAnnotationElement(parameters) {
|
||||
var isRenderable = parameters.renderInteractiveForms ||
|
||||
(!parameters.data.hasAppearance && !!parameters.data.fieldValue);
|
||||
function WidgetAnnotationElement(parameters, isRenderable) {
|
||||
AnnotationElement.call(this, parameters, isRenderable);
|
||||
}
|
||||
|
||||
@ -432,7 +432,9 @@ var TextWidgetAnnotationElement = (
|
||||
var TEXT_ALIGNMENT = ['left', 'center', 'right'];
|
||||
|
||||
function TextWidgetAnnotationElement(parameters) {
|
||||
WidgetAnnotationElement.call(this, parameters);
|
||||
var isRenderable = parameters.renderInteractiveForms ||
|
||||
(!parameters.data.hasAppearance && !!parameters.data.fieldValue);
|
||||
WidgetAnnotationElement.call(this, parameters, isRenderable);
|
||||
}
|
||||
|
||||
Util.inherit(TextWidgetAnnotationElement, WidgetAnnotationElement, {
|
||||
@ -528,6 +530,64 @@ var TextWidgetAnnotationElement = (
|
||||
return TextWidgetAnnotationElement;
|
||||
})();
|
||||
|
||||
/**
|
||||
* @class
|
||||
* @alias ChoiceWidgetAnnotationElement
|
||||
*/
|
||||
var ChoiceWidgetAnnotationElement = (
|
||||
function ChoiceWidgetAnnotationElementClosure() {
|
||||
function ChoiceWidgetAnnotationElement(parameters) {
|
||||
WidgetAnnotationElement.call(this, parameters,
|
||||
parameters.renderInteractiveForms);
|
||||
}
|
||||
|
||||
Util.inherit(ChoiceWidgetAnnotationElement, WidgetAnnotationElement, {
|
||||
/**
|
||||
* Render the choice widget annotation's HTML element in the empty
|
||||
* container.
|
||||
*
|
||||
* @public
|
||||
* @memberof ChoiceWidgetAnnotationElement
|
||||
* @returns {HTMLSectionElement}
|
||||
*/
|
||||
render: function ChoiceWidgetAnnotationElement_render() {
|
||||
this.container.className = 'choiceWidgetAnnotation';
|
||||
|
||||
var selectElement = document.createElement('select');
|
||||
selectElement.disabled = this.data.readOnly;
|
||||
|
||||
if (!this.data.combo) {
|
||||
// List boxes have a size and (optionally) multiple selection.
|
||||
selectElement.size = this.data.options.length;
|
||||
|
||||
if (this.data.multiSelect) {
|
||||
selectElement.multiple = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Insert the options into the choice field.
|
||||
for (var i = 0, ii = this.data.options.length; i < ii; i++) {
|
||||
var option = this.data.options[i];
|
||||
|
||||
var optionElement = document.createElement('option');
|
||||
optionElement.textContent = option.displayValue;
|
||||
optionElement.value = option.exportValue;
|
||||
|
||||
if (this.data.fieldValue.indexOf(option.displayValue) >= 0) {
|
||||
optionElement.setAttribute('selected', true);
|
||||
}
|
||||
|
||||
selectElement.appendChild(optionElement);
|
||||
}
|
||||
|
||||
this.container.appendChild(selectElement);
|
||||
return this.container;
|
||||
}
|
||||
});
|
||||
|
||||
return ChoiceWidgetAnnotationElement;
|
||||
})();
|
||||
|
||||
/**
|
||||
* @class
|
||||
* @alias PopupAnnotationElement
|
||||
|
@ -873,15 +873,15 @@ var Util = (function UtilClosure() {
|
||||
}
|
||||
};
|
||||
|
||||
Util.getInheritableProperty = function Util_getInheritableProperty(dict,
|
||||
name) {
|
||||
Util.getInheritableProperty =
|
||||
function Util_getInheritableProperty(dict, name, getArray) {
|
||||
while (dict && !dict.has(name)) {
|
||||
dict = dict.get('Parent');
|
||||
}
|
||||
if (!dict) {
|
||||
return null;
|
||||
}
|
||||
return dict.get(name);
|
||||
return getArray ? dict.getArray(name) : dict.get(name);
|
||||
};
|
||||
|
||||
Util.inherit = function Util_inherit(sub, base, prototype) {
|
||||
|
@ -44,7 +44,8 @@
|
||||
}
|
||||
|
||||
.annotationLayer .textWidgetAnnotation input,
|
||||
.annotationLayer .textWidgetAnnotation textarea {
|
||||
.annotationLayer .textWidgetAnnotation textarea,
|
||||
.annotationLayer .choiceWidgetAnnotation select {
|
||||
background-color: rgba(0, 54, 255, 0.13);
|
||||
border: 1px solid transparent;
|
||||
box-sizing: border-box;
|
||||
@ -62,7 +63,8 @@
|
||||
}
|
||||
|
||||
.annotationLayer .textWidgetAnnotation input[disabled],
|
||||
.annotationLayer .textWidgetAnnotation textarea[disabled] {
|
||||
.annotationLayer .textWidgetAnnotation textarea[disabled],
|
||||
.annotationLayer .choiceWidgetAnnotation select[disabled] {
|
||||
background: none;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
1
test/pdfs/.gitignore
vendored
1
test/pdfs/.gitignore
vendored
@ -256,4 +256,5 @@
|
||||
!annotation-highlight.pdf
|
||||
!annotation-fileattachment.pdf
|
||||
!annotation-text-widget.pdf
|
||||
!annotation-choice-widget.pdf
|
||||
!zero_descent.pdf
|
||||
|
BIN
test/pdfs/annotation-choice-widget.pdf
Normal file
BIN
test/pdfs/annotation-choice-widget.pdf
Normal file
Binary file not shown.
@ -3240,6 +3240,20 @@
|
||||
"type": "eq",
|
||||
"forms": true
|
||||
},
|
||||
{ "id": "annotation-choice-widget-annotations",
|
||||
"file": "pdfs/annotation-choice-widget.pdf",
|
||||
"md5": "7dfb0d743a0da0f4a71b209ab43b0be5",
|
||||
"rounds": 1,
|
||||
"type": "eq",
|
||||
"annotations": true
|
||||
},
|
||||
{ "id": "annotation-choice-widget-forms",
|
||||
"file": "pdfs/annotation-choice-widget.pdf",
|
||||
"md5": "7dfb0d743a0da0f4a71b209ab43b0be5",
|
||||
"rounds": 1,
|
||||
"type": "eq",
|
||||
"forms": true
|
||||
},
|
||||
{ "id": "issue6108",
|
||||
"file": "pdfs/issue6108.pdf",
|
||||
"md5": "8961cb55149495989a80bf0487e0f076",
|
||||
|
@ -614,6 +614,175 @@ describe('Annotation layer', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('ChoiceWidgetAnnotation', function() {
|
||||
var choiceWidgetDict;
|
||||
|
||||
beforeEach(function (done) {
|
||||
choiceWidgetDict = new Dict();
|
||||
choiceWidgetDict.set('Type', Name.get('Annot'));
|
||||
choiceWidgetDict.set('Subtype', Name.get('Widget'));
|
||||
choiceWidgetDict.set('FT', Name.get('Ch'));
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
choiceWidgetDict = null;
|
||||
});
|
||||
|
||||
it('should handle missing option arrays', function() {
|
||||
var choiceWidgetRef = new Ref(122, 0);
|
||||
var xref = new XRefMock([
|
||||
{ ref: choiceWidgetRef, data: choiceWidgetDict, }
|
||||
]);
|
||||
|
||||
var choiceWidgetAnnotation = annotationFactory.create(xref,
|
||||
choiceWidgetRef);
|
||||
var data = choiceWidgetAnnotation.data;
|
||||
expect(data.annotationType).toEqual(AnnotationType.WIDGET);
|
||||
expect(data.options).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle option arrays with array elements', function() {
|
||||
var options = [['foo_export', 'Foo'], ['bar_export', 'Bar']];
|
||||
var expected = [
|
||||
{
|
||||
exportValue: 'foo_export',
|
||||
displayValue: 'Foo'
|
||||
},
|
||||
{
|
||||
exportValue: 'bar_export',
|
||||
displayValue: 'Bar'
|
||||
}
|
||||
];
|
||||
|
||||
choiceWidgetDict.set('Opt', options);
|
||||
|
||||
var choiceWidgetRef = new Ref(123, 0);
|
||||
var xref = new XRefMock([
|
||||
{ ref: choiceWidgetRef, data: choiceWidgetDict, }
|
||||
]);
|
||||
|
||||
var choiceWidgetAnnotation = annotationFactory.create(xref,
|
||||
choiceWidgetRef);
|
||||
var data = choiceWidgetAnnotation.data;
|
||||
expect(data.annotationType).toEqual(AnnotationType.WIDGET);
|
||||
expect(data.options).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should handle option arrays with string elements', function() {
|
||||
var options = ['Foo', 'Bar'];
|
||||
var expected = [
|
||||
{
|
||||
exportValue: 'Foo',
|
||||
displayValue: 'Foo'
|
||||
},
|
||||
{
|
||||
exportValue: 'Bar',
|
||||
displayValue: 'Bar'
|
||||
}
|
||||
];
|
||||
|
||||
choiceWidgetDict.set('Opt', options);
|
||||
|
||||
var choiceWidgetRef = new Ref(981, 0);
|
||||
var xref = new XRefMock([
|
||||
{ ref: choiceWidgetRef, data: choiceWidgetDict, }
|
||||
]);
|
||||
|
||||
var choiceWidgetAnnotation = annotationFactory.create(xref,
|
||||
choiceWidgetRef);
|
||||
var data = choiceWidgetAnnotation.data;
|
||||
expect(data.annotationType).toEqual(AnnotationType.WIDGET);
|
||||
expect(data.options).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should handle array field values', function() {
|
||||
var fieldValue = ['Foo', 'Bar'];
|
||||
|
||||
choiceWidgetDict.set('V', fieldValue);
|
||||
|
||||
var choiceWidgetRef = new Ref(968, 0);
|
||||
var xref = new XRefMock([
|
||||
{ ref: choiceWidgetRef, data: choiceWidgetDict, }
|
||||
]);
|
||||
|
||||
var choiceWidgetAnnotation = annotationFactory.create(xref,
|
||||
choiceWidgetRef);
|
||||
var data = choiceWidgetAnnotation.data;
|
||||
expect(data.annotationType).toEqual(AnnotationType.WIDGET);
|
||||
expect(data.fieldValue).toEqual(fieldValue);
|
||||
});
|
||||
|
||||
it('should handle string field values', function() {
|
||||
var fieldValue = 'Foo';
|
||||
|
||||
choiceWidgetDict.set('V', fieldValue);
|
||||
|
||||
var choiceWidgetRef = new Ref(978, 0);
|
||||
var xref = new XRefMock([
|
||||
{ ref: choiceWidgetRef, data: choiceWidgetDict, }
|
||||
]);
|
||||
|
||||
var choiceWidgetAnnotation = annotationFactory.create(xref,
|
||||
choiceWidgetRef);
|
||||
var data = choiceWidgetAnnotation.data;
|
||||
expect(data.annotationType).toEqual(AnnotationType.WIDGET);
|
||||
expect(data.fieldValue).toEqual([fieldValue]);
|
||||
});
|
||||
|
||||
it('should handle unknown flags', function() {
|
||||
var choiceWidgetRef = new Ref(166, 0);
|
||||
var xref = new XRefMock([
|
||||
{ ref: choiceWidgetRef, data: choiceWidgetDict, }
|
||||
]);
|
||||
|
||||
var choiceWidgetAnnotation = annotationFactory.create(xref,
|
||||
choiceWidgetRef);
|
||||
var data = choiceWidgetAnnotation.data;
|
||||
expect(data.annotationType).toEqual(AnnotationType.WIDGET);
|
||||
expect(data.readOnly).toEqual(false);
|
||||
expect(data.combo).toEqual(false);
|
||||
expect(data.multiSelect).toEqual(false);
|
||||
});
|
||||
|
||||
it('should not set invalid flags', function() {
|
||||
choiceWidgetDict.set('Ff', 'readonly');
|
||||
|
||||
var choiceWidgetRef = new Ref(165, 0);
|
||||
var xref = new XRefMock([
|
||||
{ ref: choiceWidgetRef, data: choiceWidgetDict, }
|
||||
]);
|
||||
|
||||
var choiceWidgetAnnotation = annotationFactory.create(xref,
|
||||
choiceWidgetRef);
|
||||
var data = choiceWidgetAnnotation.data;
|
||||
expect(data.annotationType).toEqual(AnnotationType.WIDGET);
|
||||
expect(data.readOnly).toEqual(false);
|
||||
expect(data.combo).toEqual(false);
|
||||
expect(data.multiSelect).toEqual(false);
|
||||
});
|
||||
|
||||
it('should set valid flags', function() {
|
||||
choiceWidgetDict.set('Ff', AnnotationFieldFlag.READONLY +
|
||||
AnnotationFieldFlag.COMBO +
|
||||
AnnotationFieldFlag.MULTISELECT);
|
||||
|
||||
var choiceWidgetRef = new Ref(512, 0);
|
||||
var xref = new XRefMock([
|
||||
{ ref: choiceWidgetRef, data: choiceWidgetDict, }
|
||||
]);
|
||||
|
||||
var choiceWidgetAnnotation = annotationFactory.create(xref,
|
||||
choiceWidgetRef);
|
||||
var data = choiceWidgetAnnotation.data;
|
||||
expect(data.annotationType).toEqual(AnnotationType.WIDGET);
|
||||
expect(data.readOnly).toEqual(true);
|
||||
expect(data.combo).toEqual(true);
|
||||
expect(data.multiSelect).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('FileAttachmentAnnotation', function() {
|
||||
var loadingTask;
|
||||
var annotations;
|
||||
|
@ -42,7 +42,8 @@
|
||||
}
|
||||
|
||||
.annotationLayer .textWidgetAnnotation input,
|
||||
.annotationLayer .textWidgetAnnotation textarea {
|
||||
.annotationLayer .textWidgetAnnotation textarea,
|
||||
.annotationLayer .choiceWidgetAnnotation select {
|
||||
background-color: rgba(0, 54, 255, 0.13);
|
||||
border: 1px solid transparent;
|
||||
box-sizing: border-box;
|
||||
@ -60,19 +61,22 @@
|
||||
}
|
||||
|
||||
.annotationLayer .textWidgetAnnotation input[disabled],
|
||||
.annotationLayer .textWidgetAnnotation textarea[disabled] {
|
||||
.annotationLayer .textWidgetAnnotation textarea[disabled],
|
||||
.annotationLayer .choiceWidgetAnnotation select[disabled] {
|
||||
background: none;
|
||||
border: 1px solid transparent;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.annotationLayer .textWidgetAnnotation input:hover,
|
||||
.annotationLayer .textWidgetAnnotation textarea:hover {
|
||||
.annotationLayer .textWidgetAnnotation textarea:hover,
|
||||
.annotationLayer .choiceWidgetAnnotation select:hover {
|
||||
border: 1px solid #000;
|
||||
}
|
||||
|
||||
.annotationLayer .textWidgetAnnotation input:focus,
|
||||
.annotationLayer .textWidgetAnnotation textarea:focus {
|
||||
.annotationLayer .textWidgetAnnotation textarea:focus,
|
||||
.annotationLayer .choiceWidgetAnnotation select:focus {
|
||||
background: none;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user