diff --git a/src/core/annotation.js b/src/core/annotation.js index 42f49c2ed..bf4a5a716 100644 --- a/src/core/annotation.js +++ b/src/core/annotation.js @@ -715,6 +715,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 5324476a8..d8d7ac551 100644 --- a/src/display/annotation_layer.js +++ b/src/display/annotation_layer.js @@ -461,6 +461,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/test/annotation_layer_test.css b/test/annotation_layer_test.css index ae5881c62..df5dea06c 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 d6c959c1a..6cf0b77cc 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 785daeefb..3d60a82f4 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -3197,14 +3197,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 267b6e8a9..fb34b2423 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,18 @@ 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() { + var flags = 0; + flags |= 1 << (AnnotationFieldFlag.READONLY - 1); + flags |= 1 << (AnnotationFieldFlag.MULTILINE - 1); + textWidgetDict.set('Q', 1); textWidgetDict.set('MaxLen', 20); - textWidgetDict.set('Ff', 4097); + textWidgetDict.set('Ff', flags); var textWidgetRef = new Ref(84, 0); var xref = new XRefMock([ @@ -518,6 +524,74 @@ 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() { + var flags = 0; + flags |= 1 << (AnnotationFieldFlag.COMB - 1); + + textWidgetDict.set('Ff', flags); + + 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() { + var flags = 0; + flags |= 1 << (AnnotationFieldFlag.COMB - 1); + + textWidgetDict.set('MaxLen', 20); + textWidgetDict.set('Ff', flags); + + 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 + ]; + + // The field may not use combs until all invalid flags are unset. + for (var i = 0, ii = invalidFieldFlags.length; i <= ii; i++) { + var flags = 0; + flags |= 1 << (AnnotationFieldFlag.COMB - 1); + + for (var j = 0, jj = invalidFieldFlags.length; j < jj; j++) { + flags |= 1 << (invalidFieldFlags[j] - 1); + } + + 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) { + invalidFieldFlags.splice(-1, 1); + } + } + }); }); describe('FileAttachmentAnnotation', function() { diff --git a/web/annotation_layer_builder.css b/web/annotation_layer_builder.css index b7427a159..0d46e7284 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;