diff --git a/src/core/annotation.js b/src/core/annotation.js index 508863acdea4f..13aa20477125e 100644 --- a/src/core/annotation.js +++ b/src/core/annotation.js @@ -676,14 +676,13 @@ var WidgetAnnotation = (function WidgetAnnotationClosure() { * * @public * @memberof WidgetAnnotation - * @param {number} flag - Bit position, numbered from one instead of - * zero, to check + * @param {number} flag - Hexadecimal representation for an annotation + * field characteristic * @return {boolean} * @see {@link shared/util.js} */ hasFieldFlag: function WidgetAnnotation_hasFieldFlag(flag) { - var mask = 1 << (flag - 1); - return !!(this.data.fieldFlags & mask); + return !!(this.data.fieldFlags & flag); }, }); @@ -711,6 +710,11 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() { // 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) && + !this.hasFieldFlag(AnnotationFieldFlag.PASSWORD) && + !this.hasFieldFlag(AnnotationFieldFlag.FILESELECT) && + this.data.maxLen !== null; } Util.inherit(TextWidgetAnnotation, WidgetAnnotation, { diff --git a/src/display/annotation_layer.js b/src/display/annotation_layer.js index 3fc536d18d48b..29ed79a702c75 100644 --- a/src/display/annotation_layer.js +++ b/src/display/annotation_layer.js @@ -465,6 +465,14 @@ var TextWidgetAnnotationElement = ( if (this.data.maxLen !== null) { element.maxLength = this.data.maxLen; } + + if (this.data.comb) { + var fieldWidth = this.data.rect[2] - this.data.rect[0]; + var combWidth = fieldWidth / this.data.maxLen; + + element.classList.add('comb'); + element.style.letterSpacing = 'calc(' + combWidth + 'px - 1ch)'; + } } else { element = document.createElement('div'); element.textContent = this.data.fieldValue; diff --git a/src/shared/util.js b/src/shared/util.js index 853cc1a3455a4..48876dad31159 100644 --- a/src/shared/util.js +++ b/src/shared/util.js @@ -94,25 +94,25 @@ var AnnotationFlag = { }; 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, + READONLY: 0x0000001, + REQUIRED: 0x0000002, + NOEXPORT: 0x0000004, + MULTILINE: 0x0001000, + PASSWORD: 0x0002000, + NOTOGGLETOOFF: 0x0004000, + RADIO: 0x0008000, + PUSHBUTTON: 0x0010000, + COMBO: 0x0020000, + EDIT: 0x0040000, + SORT: 0x0080000, + FILESELECT: 0x0100000, + MULTISELECT: 0x0200000, + DONOTSPELLCHECK: 0x0400000, + DONOTSCROLL: 0x0800000, + COMB: 0x1000000, + RICHTEXT: 0x2000000, + RADIOSINUNISON: 0x2000000, + COMMITONSELCHANGE: 0x4000000, }; var AnnotationBorderStyleType = { diff --git a/test/annotation_layer_test.css b/test/annotation_layer_test.css index ae5881c62801f..df5dea06cf89e 100644 --- a/test/annotation_layer_test.css +++ b/test/annotation_layer_test.css @@ -67,6 +67,12 @@ border: 1px solid transparent; } +.annotationLayer .textWidgetAnnotation input.comb { + font-family: monospace; + padding-left: 2px; + padding-right: 0; +} + .annotationLayer .popupAnnotation { display: block !important; } diff --git a/test/pdfs/annotation-text-widget.pdf b/test/pdfs/annotation-text-widget.pdf index d6c959c1a5fae..6cf0b77ccc64b 100644 Binary files a/test/pdfs/annotation-text-widget.pdf and b/test/pdfs/annotation-text-widget.pdf differ diff --git a/test/test_manifest.json b/test/test_manifest.json index f34623bdf41cb..531e054fbdf67 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -3204,14 +3204,14 @@ }, { "id": "annotation-text-widget-annotations", "file": "pdfs/annotation-text-widget.pdf", - "md5": "cc9672539ad5b837152a9c6961e5f106", + "md5": "b7b8923a12998fca8603fae53f73f19b", "rounds": 1, "type": "eq", "annotations": true }, { "id": "annotation-text-widget-forms", "file": "pdfs/annotation-text-widget.pdf", - "md5": "cc9672539ad5b837152a9c6961e5f106", + "md5": "b7b8923a12998fca8603fae53f73f19b", "rounds": 1, "type": "eq", "forms": true diff --git a/test/unit/annotation_layer_spec.js b/test/unit/annotation_layer_spec.js index 267b6e8a980ee..a1e38168ea31b 100644 --- a/test/unit/annotation_layer_spec.js +++ b/test/unit/annotation_layer_spec.js @@ -1,7 +1,7 @@ /* globals expect, it, describe, Dict, Name, Annotation, AnnotationBorderStyle, AnnotationBorderStyleType, AnnotationType, AnnotationFlag, PDFJS, beforeEach, afterEach, stringToBytes, AnnotationFactory, Ref, isRef, - beforeAll, afterAll */ + beforeAll, afterAll, AnnotationFieldFlag */ 'use strict'; @@ -481,6 +481,7 @@ describe('Annotation layer', function() { expect(textWidgetAnnotation.data.maxLen).toEqual(null); expect(textWidgetAnnotation.data.readOnly).toEqual(false); expect(textWidgetAnnotation.data.multiLine).toEqual(false); + expect(textWidgetAnnotation.data.comb).toEqual(false); }); it('should not set invalid text alignment, maximum length and flags', @@ -499,13 +500,15 @@ describe('Annotation layer', function() { expect(textWidgetAnnotation.data.maxLen).toEqual(null); expect(textWidgetAnnotation.data.readOnly).toEqual(false); expect(textWidgetAnnotation.data.multiLine).toEqual(false); + expect(textWidgetAnnotation.data.comb).toEqual(false); }); it('should set valid text alignment, maximum length and flags', function() { textWidgetDict.set('Q', 1); textWidgetDict.set('MaxLen', 20); - textWidgetDict.set('Ff', 4097); + textWidgetDict.set('Ff', AnnotationFieldFlag.READONLY + + AnnotationFieldFlag.MULTILINE); var textWidgetRef = new Ref(84, 0); var xref = new XRefMock([ @@ -518,6 +521,64 @@ describe('Annotation layer', function() { expect(textWidgetAnnotation.data.readOnly).toEqual(true); expect(textWidgetAnnotation.data.multiLine).toEqual(true); }); + + it('should reject comb fields without a maximum length', function() { + textWidgetDict.set('Ff', AnnotationFieldFlag.COMB); + + var textWidgetRef = new Ref(46, 0); + var xref = new XRefMock([ + { ref: textWidgetRef, data: textWidgetDict, } + ]); + + var textWidgetAnnotation = annotationFactory.create(xref, textWidgetRef); + expect(textWidgetAnnotation.data.comb).toEqual(false); + }); + + it('should accept comb fields with a maximum length', function() { + textWidgetDict.set('MaxLen', 20); + textWidgetDict.set('Ff', AnnotationFieldFlag.COMB); + + var textWidgetRef = new Ref(46, 0); + var xref = new XRefMock([ + { ref: textWidgetRef, data: textWidgetDict, } + ]); + + var textWidgetAnnotation = annotationFactory.create(xref, textWidgetRef); + expect(textWidgetAnnotation.data.comb).toEqual(true); + }); + + it('should only accept comb fields when the flags are valid', function() { + var invalidFieldFlags = [ + AnnotationFieldFlag.MULTILINE, AnnotationFieldFlag.PASSWORD, + AnnotationFieldFlag.FILESELECT + ]; + + // Start with all invalid flags set and remove them one by one. + // The field may only use combs when all invalid flags are unset. + var flags = AnnotationFieldFlag.COMB + AnnotationFieldFlag.MULTILINE + + AnnotationFieldFlag.PASSWORD + AnnotationFieldFlag.FILESELECT; + + for (var i = 0, ii = invalidFieldFlags.length; i <= ii; i++) { + textWidgetDict.set('MaxLen', 20); + textWidgetDict.set('Ff', flags); + + var textWidgetRef = new Ref(93, 0); + var xref = new XRefMock([ + { ref: textWidgetRef, data: textWidgetDict, } + ]); + + var textWidgetAnnotation = annotationFactory.create(xref, + textWidgetRef); + + var valid = (invalidFieldFlags.length === 0); + expect(textWidgetAnnotation.data.comb).toEqual(valid); + + // Remove the last invalid flag for the next iteration. + if (!valid) { + flags -= invalidFieldFlags.splice(-1, 1); + } + } + }); }); describe('FileAttachmentAnnotation', function() { diff --git a/web/annotation_layer_builder.css b/web/annotation_layer_builder.css index b7427a159ca3a..0d46e72849474 100644 --- a/web/annotation_layer_builder.css +++ b/web/annotation_layer_builder.css @@ -77,6 +77,22 @@ border: 1px solid transparent; } +.annotationLayer .textWidgetAnnotation input.comb { + font-family: monospace; + padding-left: 2px; + padding-right: 0; +} + +.annotationLayer .textWidgetAnnotation input.comb:focus { + /* + * Letter spacing is placed on the right side of each character. Hence, the + * letter spacing of the last character may be placed outside the visible + * area, causing horizontal scrolling. We avoid this by extending the width + * when the element has focus and revert this when it loses focus. + */ + width: 115%; +} + .annotationLayer .popupWrapper { position: absolute; width: 20em;