Browse Source

Merge pull request #7633 from timvandermeij/interactive-forms-tx-flags

Text widget annotations: support read-only/multiline fields and improve testing
Tim van der Meij 9 years ago committed by GitHub
parent
commit
f062695d62
  1. 60
      src/core/annotation.js
  2. 8
      src/core/document.js
  3. 3
      src/core/worker.js
  4. 10
      src/display/annotation_layer.js
  5. 8
      src/display/api.js
  6. 23
      src/shared/util.js
  7. 15
      test/annotation_layer_test.css
  8. 1
      test/pdfs/.gitignore
  9. BIN
      test/pdfs/annotation-text-widget.pdf
  10. 7
      test/test_manifest.json
  11. 17
      test/unit/annotation_layer_spec.js
  12. 22
      web/annotation_layer_builder.css
  13. 1
      web/pdf_page_view.js

60
src/core/annotation.js

@ -33,6 +33,7 @@
coreColorSpace, coreObj, coreEvaluator) { coreColorSpace, coreObj, coreEvaluator) {
var AnnotationBorderStyleType = sharedUtil.AnnotationBorderStyleType; var AnnotationBorderStyleType = sharedUtil.AnnotationBorderStyleType;
var AnnotationFieldFlag = sharedUtil.AnnotationFieldFlag;
var AnnotationFlag = sharedUtil.AnnotationFlag; var AnnotationFlag = sharedUtil.AnnotationFlag;
var AnnotationType = sharedUtil.AnnotationType; var AnnotationType = sharedUtil.AnnotationType;
var OPS = sharedUtil.OPS; var OPS = sharedUtil.OPS;
@ -65,10 +66,14 @@ AnnotationFactory.prototype = /** @lends AnnotationFactory.prototype */ {
/** /**
* @param {XRef} xref * @param {XRef} xref
* @param {Object} ref * @param {Object} ref
* @param {string} uniquePrefix
* @param {Object} idCounters
* @param {boolean} renderInteractiveForms
* @returns {Annotation} * @returns {Annotation}
*/ */
create: function AnnotationFactory_create(xref, ref, create: function AnnotationFactory_create(xref, ref,
uniquePrefix, idCounters) { uniquePrefix, idCounters,
renderInteractiveForms) {
var dict = xref.fetchIfRef(ref); var dict = xref.fetchIfRef(ref);
if (!isDict(dict)) { if (!isDict(dict)) {
return; return;
@ -87,6 +92,7 @@ AnnotationFactory.prototype = /** @lends AnnotationFactory.prototype */ {
ref: isRef(ref) ? ref : null, ref: isRef(ref) ? ref : null,
subtype: subtype, subtype: subtype,
id: id, id: id,
renderInteractiveForms: renderInteractiveForms,
}; };
switch (subtype) { switch (subtype) {
@ -621,9 +627,13 @@ var WidgetAnnotation = (function WidgetAnnotationClosure() {
data.defaultAppearance = Util.getInheritableProperty(dict, 'DA') || ''; data.defaultAppearance = Util.getInheritableProperty(dict, 'DA') || '';
var fieldType = Util.getInheritableProperty(dict, 'FT'); var fieldType = Util.getInheritableProperty(dict, 'FT');
data.fieldType = isName(fieldType) ? fieldType.name : null; data.fieldType = isName(fieldType) ? fieldType.name : null;
data.fieldFlags = Util.getInheritableProperty(dict, 'Ff') || 0;
this.fieldResources = Util.getInheritableProperty(dict, 'DR') || Dict.empty; this.fieldResources = Util.getInheritableProperty(dict, 'DR') || Dict.empty;
data.fieldFlags = Util.getInheritableProperty(dict, 'Ff');
if (!isInt(data.fieldFlags) || data.fieldFlags < 0) {
data.fieldFlags = 0;
}
// Hide signatures because we cannot validate them. // Hide signatures because we cannot validate them.
if (data.fieldType === 'Sig') { if (data.fieldType === 'Sig') {
this.setFlags(AnnotationFlag.HIDDEN); this.setFlags(AnnotationFlag.HIDDEN);
@ -662,7 +672,22 @@ var WidgetAnnotation = (function WidgetAnnotationClosure() {
data.fullName = fieldName.join('.'); data.fullName = fieldName.join('.');
} }
Util.inherit(WidgetAnnotation, Annotation, {}); Util.inherit(WidgetAnnotation, Annotation, {
/**
* Check if a provided field flag is set.
*
* @public
* @memberof WidgetAnnotation
* @param {number} flag - Bit position, numbered from one instead of
* zero, to check
* @return {boolean}
* @see {@link shared/util.js}
*/
hasFieldFlag: function WidgetAnnotation_hasFieldFlag(flag) {
var mask = 1 << (flag - 1);
return !!(this.data.fieldFlags & mask);
},
});
return WidgetAnnotation; return WidgetAnnotation;
})(); })();
@ -671,6 +696,8 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() {
function TextWidgetAnnotation(params) { function TextWidgetAnnotation(params) {
WidgetAnnotation.call(this, params); WidgetAnnotation.call(this, params);
this.renderInteractiveForms = params.renderInteractiveForms;
// Determine the alignment of text in the field. // Determine the alignment of text in the field.
var alignment = Util.getInheritableProperty(params.dict, 'Q'); var alignment = Util.getInheritableProperty(params.dict, 'Q');
if (!isInt(alignment) || alignment < 0 || alignment > 2) { if (!isInt(alignment) || alignment < 0 || alignment > 2) {
@ -684,29 +711,38 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() {
maximumLength = null; maximumLength = null;
} }
this.data.maxLen = maximumLength; 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);
} }
Util.inherit(TextWidgetAnnotation, WidgetAnnotation, { Util.inherit(TextWidgetAnnotation, WidgetAnnotation, {
getOperatorList: function TextWidgetAnnotation_getOperatorList(evaluator, getOperatorList: function TextWidgetAnnotation_getOperatorList(evaluator,
task) { task) {
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 (this.renderInteractiveForms) {
return Promise.resolve(operatorList);
}
if (this.appearance) { if (this.appearance) {
return Annotation.prototype.getOperatorList.call(this, evaluator, task); return Annotation.prototype.getOperatorList.call(this, evaluator, task);
} }
var opList = new OperatorList();
var data = this.data;
// Even if there is an appearance stream, ignore it. This is the // Even if there is an appearance stream, ignore it. This is the
// behaviour used by Adobe Reader. // behaviour used by Adobe Reader.
if (!data.defaultAppearance) { if (!this.data.defaultAppearance) {
return Promise.resolve(opList); return Promise.resolve(operatorList);
} }
var stream = new Stream(stringToBytes(data.defaultAppearance)); var stream = new Stream(stringToBytes(this.data.defaultAppearance));
return evaluator.getOperatorList(stream, task, return evaluator.getOperatorList(stream, task, this.fieldResources,
this.fieldResources, opList). operatorList).
then(function () { then(function () {
return opList; return operatorList;
}); });
} }
}); });

8
src/core/document.js

@ -205,7 +205,8 @@ var Page = (function PageClosure() {
}.bind(this)); }.bind(this));
}, },
getOperatorList: function Page_getOperatorList(handler, task, intent) { getOperatorList: function Page_getOperatorList(handler, task, intent,
renderInteractiveForms) {
var self = this; var self = this;
var pdfManager = this.pdfManager; var pdfManager = this.pdfManager;
@ -245,6 +246,8 @@ var Page = (function PageClosure() {
}); });
}); });
this.renderInteractiveForms = renderInteractiveForms;
var annotationsPromise = pdfManager.ensure(this, 'annotations'); var annotationsPromise = pdfManager.ensure(this, 'annotations');
return Promise.all([pageListPromise, annotationsPromise]).then( return Promise.all([pageListPromise, annotationsPromise]).then(
function(datas) { function(datas) {
@ -328,7 +331,8 @@ var Page = (function PageClosure() {
var annotationRef = annotationRefs[i]; var annotationRef = annotationRefs[i];
var annotation = annotationFactory.create(this.xref, annotationRef, var annotation = annotationFactory.create(this.xref, annotationRef,
this.uniquePrefix, this.uniquePrefix,
this.idCounters); this.idCounters,
this.renderInteractiveForms);
if (annotation) { if (annotation) {
annotations.push(annotation); annotations.push(annotation);
} }

3
src/core/worker.js

@ -839,7 +839,8 @@ var WorkerMessageHandler = {
var pageNum = pageIndex + 1; var pageNum = pageIndex + 1;
var start = Date.now(); var start = Date.now();
// Pre compile the pdf page and fetch the fonts/images. // Pre compile the pdf page and fetch the fonts/images.
page.getOperatorList(handler, task, data.intent).then( page.getOperatorList(handler, task, data.intent,
data.renderInteractiveForms).then(
function(operatorList) { function(operatorList) {
finishWorkerTask(task); finishWorkerTask(task);

10
src/display/annotation_layer.js

@ -446,9 +446,15 @@ var TextWidgetAnnotationElement = (
var element = null; var element = null;
if (this.renderInteractiveForms) { if (this.renderInteractiveForms) {
element = document.createElement('input'); if (this.data.multiLine) {
element.type = 'text'; element = document.createElement('textarea');
} else {
element = document.createElement('input');
element.type = 'text';
}
element.value = this.data.fieldValue; element.value = this.data.fieldValue;
element.disabled = this.data.readOnly;
if (this.data.maxLen !== null) { if (this.data.maxLen !== null) {
element.maxLength = this.data.maxLen; element.maxLength = this.data.maxLen;

8
src/display/api.js

@ -656,6 +656,9 @@ var PDFDocumentProxy = (function PDFDocumentProxyClosure() {
* calling of PDFPage.getViewport method. * calling of PDFPage.getViewport method.
* @property {string} intent - Rendering intent, can be 'display' or 'print' * @property {string} intent - Rendering intent, can be 'display' or 'print'
* (default value is 'display'). * (default value is 'display').
* @property {boolean} renderInteractiveForms - (optional) Whether or not
* interactive form elements are rendered in the display
* layer. If so, we do not render them on canvas as well.
* @property {Array} transform - (optional) Additional transform, applied * @property {Array} transform - (optional) Additional transform, applied
* just before viewport transform. * just before viewport transform.
* @property {Object} imageLayer - (optional) An object that has beginLayout, * @property {Object} imageLayer - (optional) An object that has beginLayout,
@ -764,6 +767,8 @@ var PDFPageProxy = (function PDFPageProxyClosure() {
this.pendingCleanup = false; this.pendingCleanup = false;
var renderingIntent = (params.intent === 'print' ? 'print' : 'display'); var renderingIntent = (params.intent === 'print' ? 'print' : 'display');
var renderInteractiveForms = (params.renderInteractiveForms === true ?
true : /* Default */ false);
if (!this.intentStates[renderingIntent]) { if (!this.intentStates[renderingIntent]) {
this.intentStates[renderingIntent] = Object.create(null); this.intentStates[renderingIntent] = Object.create(null);
@ -784,7 +789,8 @@ var PDFPageProxy = (function PDFPageProxyClosure() {
this.stats.time('Page Request'); this.stats.time('Page Request');
this.transport.messageHandler.send('RenderPageRequest', { this.transport.messageHandler.send('RenderPageRequest', {
pageIndex: this.pageNumber - 1, pageIndex: this.pageNumber - 1,
intent: renderingIntent intent: renderingIntent,
renderInteractiveForms: renderInteractiveForms,
}); });
} }

23
src/shared/util.js

@ -93,6 +93,28 @@ var AnnotationFlag = {
LOCKEDCONTENTS: 0x200 LOCKEDCONTENTS: 0x200
}; };
var AnnotationFieldFlag = {
READONLY: 1,
REQUIRED: 2,
NOEXPORT: 3,
MULTILINE: 13,
PASSWORD: 14,
NOTOGGLETOOFF: 15,
RADIO: 16,
PUSHBUTTON: 17,
COMBO: 18,
EDIT: 19,
SORT: 20,
FILESELECT: 21,
MULTISELECT: 22,
DONOTSPELLCHECK: 23,
DONOTSCROLL: 24,
COMB: 25,
RICHTEXT: 26,
RADIOSINUNISON: 26,
COMMITONSELCHANGE: 27,
};
var AnnotationBorderStyleType = { var AnnotationBorderStyleType = {
SOLID: 1, SOLID: 1,
DASHED: 2, DASHED: 2,
@ -2364,6 +2386,7 @@ exports.OPS = OPS;
exports.VERBOSITY_LEVELS = VERBOSITY_LEVELS; exports.VERBOSITY_LEVELS = VERBOSITY_LEVELS;
exports.UNSUPPORTED_FEATURES = UNSUPPORTED_FEATURES; exports.UNSUPPORTED_FEATURES = UNSUPPORTED_FEATURES;
exports.AnnotationBorderStyleType = AnnotationBorderStyleType; exports.AnnotationBorderStyleType = AnnotationBorderStyleType;
exports.AnnotationFieldFlag = AnnotationFieldFlag;
exports.AnnotationFlag = AnnotationFlag; exports.AnnotationFlag = AnnotationFlag;
exports.AnnotationType = AnnotationType; exports.AnnotationType = AnnotationType;
exports.FontType = FontType; exports.FontType = FontType;

15
test/annotation_layer_test.css

@ -43,7 +43,8 @@
position: absolute; position: absolute;
} }
.annotationLayer .textWidgetAnnotation input { .annotationLayer .textWidgetAnnotation input,
.annotationLayer .textWidgetAnnotation textarea {
background-color: rgba(0, 54, 255, 0.13); background-color: rgba(0, 54, 255, 0.13);
border: 1px solid transparent; border: 1px solid transparent;
box-sizing: border-box; box-sizing: border-box;
@ -54,6 +55,18 @@
width: 100%; width: 100%;
} }
.annotationLayer .textWidgetAnnotation textarea {
font: message-box;
font-size: 9px;
resize: none;
}
.annotationLayer .textWidgetAnnotation input[disabled],
.annotationLayer .textWidgetAnnotation textarea[disabled] {
background: none;
border: 1px solid transparent;
}
.annotationLayer .popupAnnotation { .annotationLayer .popupAnnotation {
display: block !important; display: block !important;
} }

1
test/pdfs/.gitignore vendored

@ -250,4 +250,5 @@
!annotation-squiggly.pdf !annotation-squiggly.pdf
!annotation-highlight.pdf !annotation-highlight.pdf
!annotation-fileattachment.pdf !annotation-fileattachment.pdf
!annotation-text-widget.pdf
!zero_descent.pdf !zero_descent.pdf

BIN
test/pdfs/annotation-text-widget.pdf

Binary file not shown.

7
test/test_manifest.json

@ -3151,6 +3151,13 @@
"type": "eq", "type": "eq",
"annotations": true "annotations": true
}, },
{ "id": "annotation-text-widget-forms",
"file": "pdfs/annotation-text-widget.pdf",
"md5": "cc9672539ad5b837152a9c6961e5f106",
"rounds": 1,
"type": "eq",
"forms": true
},
{ "id": "issue6108", { "id": "issue6108",
"file": "pdfs/issue6108.pdf", "file": "pdfs/issue6108.pdf",
"md5": "8961cb55149495989a80bf0487e0f076", "md5": "8961cb55149495989a80bf0487e0f076",

17
test/unit/annotation_layer_spec.js

@ -469,7 +469,8 @@ describe('Annotation layer', function() {
textWidgetDict = null; textWidgetDict = null;
}); });
it('should handle unknown text alignment and maximum length', function() { it('should handle unknown text alignment, maximum length and flags',
function() {
var textWidgetRef = new Ref(124, 0); var textWidgetRef = new Ref(124, 0);
var xref = new XRefMock([ var xref = new XRefMock([
{ ref: textWidgetRef, data: textWidgetDict, } { ref: textWidgetRef, data: textWidgetDict, }
@ -478,11 +479,15 @@ describe('Annotation layer', function() {
var textWidgetAnnotation = annotationFactory.create(xref, textWidgetRef); var textWidgetAnnotation = annotationFactory.create(xref, textWidgetRef);
expect(textWidgetAnnotation.data.textAlignment).toEqual(null); expect(textWidgetAnnotation.data.textAlignment).toEqual(null);
expect(textWidgetAnnotation.data.maxLen).toEqual(null); expect(textWidgetAnnotation.data.maxLen).toEqual(null);
expect(textWidgetAnnotation.data.readOnly).toEqual(false);
expect(textWidgetAnnotation.data.multiLine).toEqual(false);
}); });
it('should not set invalid text alignment and maximum length', function() { it('should not set invalid text alignment, maximum length and flags',
function() {
textWidgetDict.set('Q', 'center'); textWidgetDict.set('Q', 'center');
textWidgetDict.set('MaxLen', 'five'); textWidgetDict.set('MaxLen', 'five');
textWidgetDict.set('Ff', 'readonly');
var textWidgetRef = new Ref(43, 0); var textWidgetRef = new Ref(43, 0);
var xref = new XRefMock([ var xref = new XRefMock([
@ -492,11 +497,15 @@ describe('Annotation layer', function() {
var textWidgetAnnotation = annotationFactory.create(xref, textWidgetRef); var textWidgetAnnotation = annotationFactory.create(xref, textWidgetRef);
expect(textWidgetAnnotation.data.textAlignment).toEqual(null); expect(textWidgetAnnotation.data.textAlignment).toEqual(null);
expect(textWidgetAnnotation.data.maxLen).toEqual(null); expect(textWidgetAnnotation.data.maxLen).toEqual(null);
expect(textWidgetAnnotation.data.readOnly).toEqual(false);
expect(textWidgetAnnotation.data.multiLine).toEqual(false);
}); });
it('should set valid text alignment and maximum length', function() { it('should set valid text alignment, maximum length and flags',
function() {
textWidgetDict.set('Q', 1); textWidgetDict.set('Q', 1);
textWidgetDict.set('MaxLen', 20); textWidgetDict.set('MaxLen', 20);
textWidgetDict.set('Ff', 4097);
var textWidgetRef = new Ref(84, 0); var textWidgetRef = new Ref(84, 0);
var xref = new XRefMock([ var xref = new XRefMock([
@ -506,6 +515,8 @@ describe('Annotation layer', function() {
var textWidgetAnnotation = annotationFactory.create(xref, textWidgetRef); var textWidgetAnnotation = annotationFactory.create(xref, textWidgetRef);
expect(textWidgetAnnotation.data.textAlignment).toEqual(1); expect(textWidgetAnnotation.data.textAlignment).toEqual(1);
expect(textWidgetAnnotation.data.maxLen).toEqual(20); expect(textWidgetAnnotation.data.maxLen).toEqual(20);
expect(textWidgetAnnotation.data.readOnly).toEqual(true);
expect(textWidgetAnnotation.data.multiLine).toEqual(true);
}); });
}); });

22
web/annotation_layer_builder.css

@ -41,7 +41,8 @@
cursor: pointer; cursor: pointer;
} }
.annotationLayer .textWidgetAnnotation input { .annotationLayer .textWidgetAnnotation input,
.annotationLayer .textWidgetAnnotation textarea {
background-color: rgba(0, 54, 255, 0.13); background-color: rgba(0, 54, 255, 0.13);
border: 1px solid transparent; border: 1px solid transparent;
box-sizing: border-box; box-sizing: border-box;
@ -52,11 +53,26 @@
width: 100%; width: 100%;
} }
.annotationLayer .textWidgetAnnotation input:hover { .annotationLayer .textWidgetAnnotation textarea {
font: message-box;
font-size: 9px;
resize: none;
}
.annotationLayer .textWidgetAnnotation input[disabled],
.annotationLayer .textWidgetAnnotation textarea[disabled] {
background: none;
border: 1px solid transparent;
cursor: not-allowed;
}
.annotationLayer .textWidgetAnnotation input:hover,
.annotationLayer .textWidgetAnnotation textarea:hover {
border: 1px solid #000; border: 1px solid #000;
} }
.annotationLayer .textWidgetAnnotation input:focus { .annotationLayer .textWidgetAnnotation input:focus,
.annotationLayer .textWidgetAnnotation textarea:focus {
background: none; background: none;
border: 1px solid transparent; border: 1px solid transparent;
} }

1
web/pdf_page_view.js

@ -498,6 +498,7 @@ var PDFPageView = (function PDFPageViewClosure() {
canvasContext: ctx, canvasContext: ctx,
transform: transform, transform: transform,
viewport: this.viewport, viewport: this.viewport,
renderInteractiveForms: pdfjsLib.PDFJS.renderInteractiveForms,
// intent: 'default', // === 'display' // intent: 'default', // === 'display'
}; };
var renderTask = this.renderTask = this.pdfPage.render(renderContext); var renderTask = this.renderTask = this.pdfPage.render(renderContext);

Loading…
Cancel
Save