From 090536e6841d41f7a85c960252707b307d360b58 Mon Sep 17 00:00:00 2001 From: Xiliang Chen Date: Mon, 6 Jul 2015 18:05:55 +1200 Subject: [PATCH] implement interactive text forms --- src/core/annotation.js | 5 +- src/display/annotation_helper.js | 89 ++++++++++++++++++++++++++++++- web/annotations_layer_builder.css | 37 +++++++++++++ web/annotations_layer_builder.js | 19 +++++-- web/default_preferences.js | 3 +- web/pdf_viewer.css | 1 + web/viewer.js | 9 ++++ 7 files changed, 155 insertions(+), 8 deletions(-) create mode 100644 web/annotations_layer_builder.css diff --git a/src/core/annotation.js b/src/core/annotation.js index 6a4c6101184dda..dc3524fd284e6d 100644 --- a/src/core/annotation.js +++ b/src/core/annotation.js @@ -478,6 +478,7 @@ var WidgetAnnotation = (function WidgetAnnotationClosure() { var dict = params.dict; var data = this.data; + data.annotationType = AnnotationType.WIDGET; data.fieldValue = stringToPDFString( Util.getInheritableProperty(dict, 'V') || ''); data.alternativeText = stringToPDFString(dict.get('TU') || ''); @@ -540,8 +541,8 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() { WidgetAnnotation.call(this, params); this.data.textAlignment = Util.getInheritableProperty(params.dict, 'Q'); - this.data.annotationType = AnnotationType.WIDGET; this.data.hasHtml = !this.data.hasAppearance && !!this.data.fieldValue; + this.data.maxLen = Util.getInheritableProperty(params.dict, 'MaxLen'); } Util.inherit(TextWidgetAnnotation, WidgetAnnotation, { @@ -637,7 +638,7 @@ var LinkAnnotation = (function LinkAnnotationClosure() { if (!isValidUrl(url, false)) { url = ''; } - // According to ISO 32000-1:2008, section 12.6.4.7, + // According to ISO 32000-1:2008, section 12.6.4.7, // URI should to be encoded in 7-bit ASCII. // Some bad PDFs may have URIs in UTF-8 encoding, see Bugzilla 1122280. try { diff --git a/src/display/annotation_helper.js b/src/display/annotation_helper.js index 215c7c281b8b40..aeead0bda10189 100644 --- a/src/display/annotation_helper.js +++ b/src/display/annotation_helper.js @@ -26,7 +26,11 @@ var AnnotationUtils = (function AnnotationUtilsClosure() { function setTextStyles(element, item, fontObj) { var style = element.style; - style.fontSize = item.fontSize + 'px'; + + if ('fontSize' in item) { + style.fontSize = item.fontSize + 'px'; + } + style.direction = item.fontDirection < 0 ? 'rtl': 'ltr'; if (!fontObj) { @@ -114,6 +118,82 @@ var AnnotationUtils = (function AnnotationUtilsClosure() { return container; } + function isBitSet(value, pos) { + // Note: pos is 1 based index + return !!(value & (1 << (pos - 1))); + } + + function createWidgetAnnotationContainer(item) { + var element = document.createElement('div'); + var width = item.rect[2] - item.rect[0]; + var height = item.rect[3] - item.rect[1]; + element.style.width = width + 'px'; + element.style.height = height + 'px'; + element.className = 'widgetContainer'; + + return element; + } + + function createTextWidgetAnnotation(item, commonObjs) { + var isMultiline = isBitSet(item.fieldFlags, 13); + // var isPassword = isBitSet(item.fieldFlags, 14); + // var isFileSelect = isBitSet(item.fieldFlags, 21); + // var isDoNotSpellCheck = isBitSet(item.fieldFlags, 23); + // var isDoNotScroll = isBitSet(item.fieldFlags, 24); + // var isComb = isBitSet(item.fieldFlags, 25); + // var isRichText = isBitSet(item.fieldFlags, 26); + + var content; + + if (isMultiline) { + content = document.createElement('textarea'); + } else { + content = document.createElement('input'); + content.type = 'text'; + } + + content.value = item.fieldValue; + var textAlignment = item.textAlignment; + content.style.textAlign = ['left', 'center', 'right'][textAlignment]; + content.style.verticalAlign = 'middle'; + content.className = 'widgetControl'; + if (item.maxLen !== null) { + content.maxLength = item.maxLen; + } + + var fontObj = item.fontRefName ? + commonObjs.getData(item.fontRefName) : null; + setTextStyles(content, item, fontObj); + + console.log(item.defaultAppearance); + + return content; + } + + function getHtmlElementForInteractiveWidgetAnnotation(item, commonObjs) { + var element, container; + switch(item.fieldType) { + case 'Tx': + element = createTextWidgetAnnotation(item, commonObjs); + break; + } + + if (element) { + container = createWidgetAnnotationContainer(item); + var isReadonly = isBitSet(item.fieldFlags, 1); + element.disabled = isReadonly; + + element.style.width = container.style.width; + element.style.height = container.style.height; + + container.appendChild(element); + + return container; + } + + return null; + } + function getHtmlElementForTextWidgetAnnotation(item, commonObjs) { var element = document.createElement('div'); var width = item.rect[2] - item.rect[0]; @@ -275,7 +355,12 @@ var AnnotationUtils = (function AnnotationUtilsClosure() { function getHtmlElement(data, objs) { switch (data.annotationType) { case AnnotationType.WIDGET: - return getHtmlElementForTextWidgetAnnotation(data, objs); + if (PDFJS.enableInteractiveForms) { + return getHtmlElementForInteractiveWidgetAnnotation(data, objs); + } else { + return getHtmlElementForTextWidgetAnnotation(data, objs); + } + break; case AnnotationType.TEXT: return getHtmlElementForTextAnnotation(data); case AnnotationType.LINK: diff --git a/web/annotations_layer_builder.css b/web/annotations_layer_builder.css new file mode 100644 index 00000000000000..66b91945b580d8 --- /dev/null +++ b/web/annotations_layer_builder.css @@ -0,0 +1,37 @@ +/* Copyright 2014 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.annotationLayer .widgetContainer { + display: table; + background: rgba(192, 192, 192, 0.2); +} + +.annotationLayer .widgetControl { + display: table-cell; + background: transparent; + border: 0px none; +} + +.annotationLayer textarea.widgetControl { + resize: none; +} + +.annotationLayer .widgetControl[type='checkbox'] { + margin: 0px; +} + +.annotationLayer .widgetControl[disabled] { + cursor: not-allowed; +} diff --git a/web/annotations_layer_builder.js b/web/annotations_layer_builder.js index b5ef814ec79c6c..fdded01abeda9f 100644 --- a/web/annotations_layer_builder.js +++ b/web/annotations_layer_builder.js @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/*globals PDFJS, CustomStyle, mozL10n */ +/*globals PDFJS, CustomStyle, mozL10n, AnnotationType */ 'use strict'; @@ -78,7 +78,7 @@ var AnnotationsLayerBuilder = (function AnnotationsLayerBuilderClosure() { viewport = viewport.clone({ dontFlip: true }); var transform = viewport.transform; var transformStr = 'matrix(' + transform.join(',') + ')'; - var data, element, i, ii; + var data, element, i, ii, hasHtml; if (self.div) { // If an annotationLayer already exists, refresh its children's @@ -96,12 +96,25 @@ var AnnotationsLayerBuilder = (function AnnotationsLayerBuilderClosure() { } else { for (i = 0, ii = annotationsData.length; i < ii; i++) { data = annotationsData[i]; - if (!data || !data.hasHtml) { + if (!data) { + continue; + } + + hasHtml = data.hasHtml || + (PDFJS.enableInteractiveForms && + data.annotationType === AnnotationType.WIDGET); + + if (!hasHtml) { continue; } element = PDFJS.AnnotationUtils.getHtmlElement(data, pdfPage.commonObjs); + + if (!element) { // not supported element + continue; + } + element.setAttribute('data-annotation-id', data.id); if (typeof mozL10n !== 'undefined') { mozL10n.translate(element); diff --git a/web/default_preferences.js b/web/default_preferences.js index 10474144a7dec5..9396e7b28e2758 100644 --- a/web/default_preferences.js +++ b/web/default_preferences.js @@ -32,5 +32,6 @@ var DEFAULT_PREFERENCES = { disableAutoFetch: false, disableFontFace: false, disableTextLayer: false, - useOnlyCssZoom: false + useOnlyCssZoom: false, + enableInteractiveForms: false }; diff --git a/web/pdf_viewer.css b/web/pdf_viewer.css index 47f2450b6a5640..61e0bb4dd08ea3 100644 --- a/web/pdf_viewer.css +++ b/web/pdf_viewer.css @@ -13,6 +13,7 @@ * limitations under the License. */ @import url(text_layer_builder.css); +@import url(annotations_layer_builder.css); .pdfViewer .canvasWrapper { overflow: hidden; diff --git a/web/viewer.js b/web/viewer.js index 1588ad6059871f..77be44808194fd 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -265,6 +265,9 @@ var PDFViewerApplication = { } PDFJS.disableTextLayer = value; }), + Preferences.get('enableInteractiveForms').then(function resolved(value) { + PDFJS.enableInteractiveForms = value; + }), Preferences.get('disableRange').then(function resolved(value) { if (PDFJS.disableRange === true) { return; @@ -1336,6 +1339,12 @@ function webViewerInitialized() { break; } } + + if ('enableinteractiveforms' in hashParams) { + PDFJS.enableInteractiveForms = + hashParams['enableinteractiveforms'] === 'true'; + } + if ('pdfbug' in hashParams) { PDFJS.pdfBug = true; var pdfBug = hashParams['pdfbug'];