From a40d59dcee48ef626416834607c83aadc8b238f3 Mon Sep 17 00:00:00 2001 From: Xiliang Chen Date: Tue, 5 Jan 2016 15:21:10 +1300 Subject: [PATCH 1/3] implement interactive text forms --- src/core/annotation.js | 1 + src/display/annotation_layer.js | 105 ++++++++++++++++++++++++++----- web/annotation_layer_builder.css | 23 +++++++ 3 files changed, 115 insertions(+), 14 deletions(-) diff --git a/src/core/annotation.js b/src/core/annotation.js index b5bc36a2327e1..a461858fb3b18 100644 --- a/src/core/annotation.js +++ b/src/core/annotation.js @@ -643,6 +643,7 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() { WidgetAnnotation.call(this, params); this.data.textAlignment = Util.getInheritableProperty(params.dict, 'Q'); + this.data.maxLen = Util.getInheritableProperty(params.dict, 'MaxLen'); } Util.inherit(TextWidgetAnnotation, WidgetAnnotation, { diff --git a/src/display/annotation_layer.js b/src/display/annotation_layer.js index ea038f2bb92d5..a75a0f6458af3 100644 --- a/src/display/annotation_layer.js +++ b/src/display/annotation_layer.js @@ -68,6 +68,13 @@ AnnotationElementFactory.prototype = return new TextAnnotationElement(parameters); case AnnotationType.WIDGET: + switch (parameters.data.fieldType) { + case 'Tx': + return new TextWidgetAnnotationElement(parameters); + } + warn('Unimplemented Widget annotation type: ' + + parameters.data.fieldType); + // Fallback to default one, which does not render anything. return new WidgetAnnotationElement(parameters); case AnnotationType.POPUP: @@ -386,9 +393,7 @@ var TextAnnotationElement = (function TextAnnotationElementClosure() { * @alias WidgetAnnotationElement */ var WidgetAnnotationElement = (function WidgetAnnotationElementClosure() { - function WidgetAnnotationElement(parameters) { - var isRenderable = !parameters.data.hasAppearance && - !!parameters.data.fieldValue; + function WidgetAnnotationElement(parameters, isRenderable) { AnnotationElement.call(this, parameters, isRenderable); } @@ -401,19 +406,87 @@ var WidgetAnnotationElement = (function WidgetAnnotationElementClosure() { * @returns {HTMLSectionElement} */ render: function WidgetAnnotationElement_render() { - var content = document.createElement('div'); - content.textContent = this.data.fieldValue; + return this.container; + }, + + /** + * Check bit value for given position in fieldFlags. + * Note: position is 1 based index. + * + * @private + * @memberof WidgetAnnotationElement + * @returns {boolean} + */ + _hasFlag: function WidgetAnnotationElement_hasFlag(position) { + return !!(this.data.fieldFlags & (1 << (position - 1))); + } + }); + + return WidgetAnnotationElement; +})(); + +/** + * @class + * @alias TextWidgetAnnotationElement + */ +var TextWidgetAnnotationElement = + (function TextWidgetAnnotationElementClosure() { + + var READONLY_BIT = 1; + var MULTILINE_BIT = 13; + + function TextWidgetAnnotationElement(parameters) { + WidgetAnnotationElement.call(this, parameters, true); + } + + Util.inherit(TextWidgetAnnotationElement, WidgetAnnotationElement, { + /** + * Render the text widget annotation's HTML element. + * + * @public + * @memberof TextWidgetAnnotationElement + * @returns {HTMLSectionElement} + */ + render: function TextWidgetAnnotationElement_render() { + var container = WidgetAnnotationElement.prototype.render.call(this); + + var isReadonly = this._hasFlag(READONLY_BIT); + var isMultiline = this._hasFlag(MULTILINE_BIT); + + var content; + if (isMultiline) { + content = document.createElement('textarea'); + } else { + content = document.createElement('input'); + content.type = 'text'; + } + + content.disabled = isReadonly; + content.value = this.data.fieldValue; var textAlignment = this.data.textAlignment; content.style.textAlign = ['left', 'center', 'right'][textAlignment]; - content.style.verticalAlign = 'middle'; - content.style.display = 'table-cell'; + if (this.data.maxLen !== null) { + content.maxLength = this.data.maxLen; + } var font = (this.data.fontRefName ? this.page.commonObjs.getData(this.data.fontRefName) : null); this._setTextStyle(content, font); - this.container.appendChild(content); - return this.container; + if (!isMultiline && !('fontSize' in this.data)) { + // Hack to guess font size based on content height + // so small text fields are rendered correctly. + // TODO: remove this when we can apply the default appearance. + var height = this.data.rect[3] - this.data.rect[1]; + if (height < 15) { + content.style.fontSize = (height - 1) + 'px'; + } + } + + container.appendChild(content); + + container.className = 'widgetAnnotation'; + return container; }, /** @@ -422,14 +495,18 @@ var WidgetAnnotationElement = (function WidgetAnnotationElementClosure() { * @private * @param {HTMLDivElement} element * @param {Object} font - * @memberof WidgetAnnotationElement + * @memberof TextWidgetAnnotationElement */ _setTextStyle: - function WidgetAnnotationElement_setTextStyle(element, font) { + function TextWidgetAnnotationElement_setTextStyle(element, font) { // TODO: This duplicates some of the logic in CanvasGraphics.setFont(). var style = element.style; - style.fontSize = this.data.fontSize + 'px'; - style.direction = (this.data.fontDirection < 0 ? 'rtl': 'ltr'); + if ('fontSize' in this.data) { + style.fontSize = this.data.fontSize + 'px'; + } + if ('fontDirection' in this.data) { + style.direction = (this.data.fontDirection < 0 ? 'rtl': 'ltr'); + } if (!font) { return; @@ -447,7 +524,7 @@ var WidgetAnnotationElement = (function WidgetAnnotationElementClosure() { } }); - return WidgetAnnotationElement; + return TextWidgetAnnotationElement; })(); /** diff --git a/web/annotation_layer_builder.css b/web/annotation_layer_builder.css index 03494430a7cb4..cae36923dc97c 100644 --- a/web/annotation_layer_builder.css +++ b/web/annotation_layer_builder.css @@ -76,3 +76,26 @@ .annotationLayer .fileAttachmentAnnotation { cursor: pointer; } + +.annotationLayer input[disabled], textarea[disabled] { + cursor: not-allowed; +} + +.annotationLayer .widgetAnnotation > input[type='text'], textarea { + vertical-align: top; +} + +.annotationLayer .widgetAnnotation textarea { + resize: none; + width: 100%; + height: 100%; + background: rgba(192, 192, 192, 0.2); + border: 0px none; +} + +.annotationLayer .widgetAnnotation input[type='text'] { + width: 100%; + height: 100%; + background: rgba(192, 192, 192, 0.2); + border: 0px none; +} From 4b2eeb8b479d22dfd63bfb7c1b482921de83ce1f Mon Sep 17 00:00:00 2001 From: Xiliang Chen Date: Wed, 17 Feb 2016 11:54:19 +1300 Subject: [PATCH 2/3] avoid render form field twice --- src/core/annotation.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/core/annotation.js b/src/core/annotation.js index a461858fb3b18..5b2a4501cd517 100644 --- a/src/core/annotation.js +++ b/src/core/annotation.js @@ -421,8 +421,17 @@ var Annotation = (function AnnotationClosure() { annotations, opList, partialEvaluator, task, intent) { var annotationPromises = []; for (var i = 0, n = annotations.length; i < n; ++i) { - if ((intent === 'display' && annotations[i].viewable) || - (intent === 'print' && annotations[i].printable)) { + // Always include for printing + var include = intent === 'print' && annotations[i].printable; + if (!include && intent === 'display' && annotations[i].viewable) { + var data = annotations[i].data; + // Don't render text widget annotation + // becuase they will be rendered by annotation layer. + if (annotations[i] instanceof TextWidgetAnnotation) { + include = false; + } + } + if (include) { annotationPromises.push( annotations[i].getOperatorList(partialEvaluator, task)); } From e67e316b19031235dee04ee7cedfd5baea36c585 Mon Sep 17 00:00:00 2001 From: Xiliang Chen Date: Wed, 17 Feb 2016 11:54:57 +1300 Subject: [PATCH 3/3] add storage service and use it to store form field value --- src/display/annotation_layer.js | 14 ++++++++++++-- web/annotation_layer_builder.js | 8 ++++++-- web/interfaces.js | 11 +++++++++++ web/pdf_viewer.js | 28 +++++++++++++++++++++++++++- 4 files changed, 56 insertions(+), 5 deletions(-) diff --git a/src/display/annotation_layer.js b/src/display/annotation_layer.js index a75a0f6458af3..36b0d0e07d1e6 100644 --- a/src/display/annotation_layer.js +++ b/src/display/annotation_layer.js @@ -44,6 +44,7 @@ var CustomStyle = displayDOMUtils.CustomStyle; * @property {PageViewport} viewport * @property {IPDFLinkService} linkService * @property {DownloadManager} downloadManager + * @property {IPDFStorageService} storageService */ /** @@ -114,6 +115,7 @@ var AnnotationElement = (function AnnotationElementClosure() { this.viewport = parameters.viewport; this.linkService = parameters.linkService; this.downloadManager = parameters.downloadManager; + this.storageService = parameters.storageService; if (isRenderable) { this.container = this._createContainer(); @@ -462,7 +464,8 @@ var TextWidgetAnnotationElement = } content.disabled = isReadonly; - content.value = this.data.fieldValue; + content.value = this.storageService.get(this.data.fullName) || + this.data.fieldValue; var textAlignment = this.data.textAlignment; content.style.textAlign = ['left', 'center', 'right'][textAlignment]; if (this.data.maxLen !== null) { @@ -483,12 +486,18 @@ var TextWidgetAnnotationElement = } } + content.onchange = this._onchange.bind(this); + container.appendChild(content); container.className = 'widgetAnnotation'; return container; }, + _onchange: function(evt) { + this.storageService.set(this.data.fullName, evt.target.value); + }, + /** * Apply text styles to the text in the element. * @@ -945,7 +954,8 @@ var AnnotationLayer = (function AnnotationLayerClosure() { page: parameters.page, viewport: parameters.viewport, linkService: parameters.linkService, - downloadManager: parameters.downloadManager + downloadManager: parameters.downloadManager, + storageService: parameters.storageService }; var element = annotationElementFactory.create(properties); if (element.isRenderable) { diff --git a/web/annotation_layer_builder.js b/web/annotation_layer_builder.js index 6d985b8cc3b63..0d7ec3fe8977a 100644 --- a/web/annotation_layer_builder.js +++ b/web/annotation_layer_builder.js @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/*globals PDFJS, mozL10n, SimpleLinkService */ +/*globals PDFJS, mozL10n, SimpleLinkService, SimpleStorageService */ 'use strict'; @@ -22,6 +22,7 @@ * @property {PDFPage} pdfPage * @property {IPDFLinkService} linkService * @property {DownloadManager} downloadManager + * @property {IPDFStorageService} storageService */ /** @@ -37,6 +38,7 @@ var AnnotationLayerBuilder = (function AnnotationLayerBuilderClosure() { this.pdfPage = options.pdfPage; this.linkService = options.linkService; this.downloadManager = options.downloadManager; + this.storageService = options.storageService; this.div = null; } @@ -62,7 +64,8 @@ var AnnotationLayerBuilder = (function AnnotationLayerBuilderClosure() { annotations: annotations, page: self.pdfPage, linkService: self.linkService, - downloadManager: self.downloadManager + downloadManager: self.downloadManager, + storageService: self.storageService }; if (self.div) { @@ -116,6 +119,7 @@ DefaultAnnotationLayerFactory.prototype = { pageDiv: pageDiv, pdfPage: pdfPage, linkService: new SimpleLinkService(), + storageService: new SimpleStorageService(), }); } }; diff --git a/web/interfaces.js b/web/interfaces.js index 94d958cde644e..11801b34f40c5 100644 --- a/web/interfaces.js +++ b/web/interfaces.js @@ -115,3 +115,14 @@ IPDFAnnotationLayerFactory.prototype = { */ createAnnotationLayerBuilder: function (pageDiv, pdfPage) {} }; + +/** + * @interface + */ +function IPDFStorageService() {} +IPDFStorageService.prototype = { + get: function (key) {}, + set: function (key, value) {}, + remove: function (key) {}, + removeAll: function () {}, +}; diff --git a/web/pdf_viewer.js b/web/pdf_viewer.js index baf28551d1829..0fec66249c768 100644 --- a/web/pdf_viewer.js +++ b/web/pdf_viewer.js @@ -42,6 +42,7 @@ var DEFAULT_CACHE_SIZE = 10; * @property {IPDFLinkService} linkService - The navigation/linking service. * @property {DownloadManager} downloadManager - (optional) The download * manager component. + * @property {IPDFStorageService} storageService - The storage service. * @property {PDFRenderingQueue} renderingQueue - (optional) The rendering * queue object. * @property {boolean} removePageBorders - (optional) Removes the border shadow @@ -95,6 +96,7 @@ var PDFViewer = (function pdfViewer() { this.viewer = options.viewer || options.container.firstElementChild; this.linkService = options.linkService || new SimpleLinkService(); this.downloadManager = options.downloadManager || null; + this.storageService = options.storageService || new SimpleStorageService(); this.removePageBorders = options.removePageBorders || false; this.defaultRenderingQueue = !options.renderingQueue; @@ -691,6 +693,7 @@ var PDFViewer = (function pdfViewer() { this._pages[i].reset(); } } + this.storageService.removeAll(); }, /** @@ -761,7 +764,8 @@ var PDFViewer = (function pdfViewer() { pageDiv: pageDiv, pdfPage: pdfPage, linkService: this.linkService, - downloadManager: this.downloadManager + downloadManager: this.downloadManager, + storageService: this.storageService }); }, @@ -821,3 +825,25 @@ var SimpleLinkService = (function SimpleLinkServiceClosure() { }; return SimpleLinkService; })(); + +var SimpleStorageService = (function SimpleStorageServiceClosure() { + function SimpleStorageService() { + this._storage = Object.create(null); + } + + SimpleStorageService.prototype = { + set: function(key, value) { + this._storage[key] = value; + }, + get: function(key) { + return this._storage[name]; + }, + remove: function(key) { + delete this._storage[name]; + }, + removeAll: function(key) { + this._storage = Object.create(null); + } + }; + return SimpleStorageService; +})(); \ No newline at end of file