[Pkg-javascript-commits] [pdf.js] 35/414: Implement support for Popup annotations

David Prévot taffit at moszumanska.debian.org
Tue Jun 28 17:12:03 UTC 2016


This is an automated email from the git hooks/post-receive script.

taffit pushed a commit to branch master
in repository pdf.js.

commit 7d43971f54291fc71b96d9e612a277bfeae2fd62
Author: Tim van der Meij <timvandermeij at gmail.com>
Date:   Tue Dec 22 21:31:56 2015 +0100

    Implement support for Popup annotations
    
    Most code for Popup annotations is already present for Text annotations.
    This patch extracts the popup creation logic from the Text annotation
    code so it can be reused for Popup annotations.
    
    Not only does this add support for Popup annotations, the Text
    annotation code is also considerably easier. If a `Popup` entry is
    available for a Text annotation, it will not be more than an image. The
    popup will be handled by the Popup annotation. However, it is also
    possible for Text annotations to not have a separate Popup annotation,
    in which case the Text annotation handles the popup creation itself.
---
 src/core/annotation.js                      |  80 +++++--
 src/display/annotation_layer.js             | 331 ++++++++++++++++++----------
 test/annotation_layer_test.css              |  14 +-
 test/pdfs/.gitignore                        |   2 +
 test/pdfs/annotation-link-text-popup.pdf    | Bin 0 -> 98997 bytes
 test/pdfs/annotation-text-without-popup.pdf | Bin 0 -> 24281 bytes
 test/test_manifest.json                     |  15 ++
 web/annotation_layer_builder.css            |  14 +-
 8 files changed, 305 insertions(+), 151 deletions(-)

diff --git a/src/core/annotation.js b/src/core/annotation.js
index f09affc..7c94100 100644
--- a/src/core/annotation.js
+++ b/src/core/annotation.js
@@ -52,8 +52,6 @@ var ColorSpace = coreColorSpace.ColorSpace;
 var ObjectLoader = coreObj.ObjectLoader;
 var OperatorList = coreEvaluator.OperatorList;
 
-var DEFAULT_ICON_SIZE = 22; // px
-
 /**
  * @class
  * @alias AnnotationFactory
@@ -95,6 +93,9 @@ AnnotationFactory.prototype = /** @lends AnnotationFactory.prototype */ {
         }
         return new WidgetAnnotation(parameters);
 
+      case 'Popup':
+        return new PopupAnnotation(parameters);
+
       default:
         warn('Unimplemented annotation type "' + subtype + '", ' +
              'falling back to base annotation');
@@ -160,7 +161,7 @@ var Annotation = (function AnnotationClosure() {
 
     // Expose public properties using a data object.
     this.data = {};
-    this.data.id = params.ref.num;
+    this.data.id = params.ref.toString();
     this.data.subtype = dict.get('Subtype').name;
     this.data.annotationFlags = this.flags;
     this.data.rect = this.rectangle;
@@ -639,29 +640,35 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() {
 })();
 
 var TextAnnotation = (function TextAnnotationClosure() {
-  function TextAnnotation(params) {
-    Annotation.call(this, params);
+  var DEFAULT_ICON_SIZE = 22; // px
 
-    var dict = params.dict;
-    var data = this.data;
+  function TextAnnotation(parameters) {
+    Annotation.call(this, parameters);
 
-    var content = dict.get('Contents');
-    var title = dict.get('T');
-    data.annotationType = AnnotationType.TEXT;
-    data.content = stringToPDFString(content || '');
-    data.title = stringToPDFString(title || '');
-    data.hasHtml = true;
+    this.data.annotationType = AnnotationType.TEXT;
+    this.data.hasHtml = true;
 
-    if (data.hasAppearance) {
-      data.name = 'NoIcon';
+    var dict = parameters.dict;
+    if (this.data.hasAppearance) {
+      this.data.name = 'NoIcon';
     } else {
-      data.rect[1] = data.rect[3] - DEFAULT_ICON_SIZE;
-      data.rect[2] = data.rect[0] + DEFAULT_ICON_SIZE;
-      data.name = dict.has('Name') ? dict.get('Name').name : 'Note';
+      this.data.rect[1] = this.data.rect[3] - DEFAULT_ICON_SIZE;
+      this.data.rect[2] = this.data.rect[0] + DEFAULT_ICON_SIZE;
+      this.data.name = dict.has('Name') ? dict.get('Name').name : 'Note';
     }
 
-    if (dict.has('C')) {
-      data.hasBgColor = true;
+    if (!dict.has('C')) {
+      // Fall back to the default background color.
+      this.data.color = null;
+    }
+
+    this.data.hasPopup = dict.has('Popup');
+    if (!this.data.hasPopup) {
+      // There is no associated Popup annotation, so the Text annotation
+      // must create its own popup.
+      this.data.title = stringToPDFString(dict.get('T') || '');
+      this.data.contents = stringToPDFString(dict.get('Contents') || '');
+      this.data.hasHtml = (this.data.title || this.data.contents);
     }
   }
 
@@ -746,6 +753,39 @@ var LinkAnnotation = (function LinkAnnotationClosure() {
   return LinkAnnotation;
 })();
 
+var PopupAnnotation = (function PopupAnnotationClosure() {
+  function PopupAnnotation(parameters) {
+    Annotation.call(this, parameters);
+
+    this.data.annotationType = AnnotationType.POPUP;
+
+    var dict = parameters.dict;
+    var parentItem = dict.get('Parent');
+    if (!parentItem) {
+      warn('Popup annotation has a missing or invalid parent annotation.');
+      return;
+    }
+
+    this.data.parentId = dict.getRaw('Parent').toString();
+    this.data.title = stringToPDFString(parentItem.get('T') || '');
+    this.data.contents = stringToPDFString(parentItem.get('Contents') || '');
+
+    if (!parentItem.has('C')) {
+      // Fall back to the default background color.
+      this.data.color = null;
+    } else {
+      this.setColor(parentItem.get('C'));
+      this.data.color = this.color;
+    }
+
+    this.data.hasHtml = (this.data.title || this.data.contents);
+  }
+
+  Util.inherit(PopupAnnotation, Annotation, {});
+
+  return PopupAnnotation;
+})();
+
 exports.Annotation = Annotation;
 exports.AnnotationBorderStyle = AnnotationBorderStyle;
 exports.AnnotationFactory = AnnotationFactory;
diff --git a/src/display/annotation_layer.js b/src/display/annotation_layer.js
index 65d337d..9a1545d 100644
--- a/src/display/annotation_layer.js
+++ b/src/display/annotation_layer.js
@@ -36,11 +36,10 @@ var LinkTargetStringMap = sharedUtil.LinkTargetStringMap;
 var warn = sharedUtil.warn;
 var CustomStyle = displayDOMUtils.CustomStyle;
 
-var ANNOT_MIN_SIZE = 10; // px
-
 /**
  * @typedef {Object} AnnotationElementParameters
  * @property {Object} data
+ * @property {HTMLDivElement} layer
  * @property {PDFPage} page
  * @property {PageViewport} viewport
  * @property {IPDFLinkService} linkService
@@ -70,6 +69,9 @@ AnnotationElementFactory.prototype =
       case AnnotationType.WIDGET:
         return new WidgetAnnotationElement(parameters);
 
+      case AnnotationType.POPUP:
+        return new PopupAnnotationElement(parameters);
+
       default:
         throw new Error('Unimplemented annotation type "' + subtype + '"');
     }
@@ -83,6 +85,7 @@ AnnotationElementFactory.prototype =
 var AnnotationElement = (function AnnotationElementClosure() {
   function AnnotationElement(parameters) {
     this.data = parameters.data;
+    this.layer = parameters.layer;
     this.page = parameters.page;
     this.viewport = parameters.viewport;
     this.linkService = parameters.linkService;
@@ -292,8 +295,6 @@ var LinkAnnotationElement = (function LinkAnnotationElementClosure() {
 var TextAnnotationElement = (function TextAnnotationElementClosure() {
   function TextAnnotationElement(parameters) {
     AnnotationElement.call(this, parameters);
-
-    this.pinned = false;
   }
 
   Util.inherit(TextAnnotationElement, AnnotationElement, {
@@ -305,127 +306,35 @@ var TextAnnotationElement = (function TextAnnotationElementClosure() {
      * @returns {HTMLSectionElement}
      */
     render: function TextAnnotationElement_render() {
-      var rect = this.data.rect, container = this.container;
-
-      // Sanity check because of OOo-generated PDFs.
-      if ((rect[3] - rect[1]) < ANNOT_MIN_SIZE) {
-        rect[3] = rect[1] + ANNOT_MIN_SIZE;
-      }
-      if ((rect[2] - rect[0]) < ANNOT_MIN_SIZE) {
-        rect[2] = rect[0] + (rect[3] - rect[1]); // make it square
-      }
-
-      container.className = 'annotText';
+      this.container.className = 'textAnnotation';
 
-      var image  = document.createElement('img');
-      image.style.height = container.style.height;
-      image.style.width = container.style.width;
-      var iconName = this.data.name;
+      var image = document.createElement('img');
+      image.style.height = this.container.style.height;
+      image.style.width = this.container.style.width;
       image.src = PDFJS.imageResourcesPath + 'annotation-' +
-        iconName.toLowerCase() + '.svg';
+        this.data.name.toLowerCase() + '.svg';
       image.alt = '[{{type}} Annotation]';
       image.dataset.l10nId = 'text_annotation_type';
-      image.dataset.l10nArgs = JSON.stringify({type: iconName});
-
-      var contentWrapper = document.createElement('div');
-      contentWrapper.className = 'annotTextContentWrapper';
-      contentWrapper.style.left = Math.floor(rect[2] - rect[0] + 5) + 'px';
-      contentWrapper.style.top = '-10px';
-
-      var content = this.content = document.createElement('div');
-      content.className = 'annotTextContent';
-      content.setAttribute('hidden', true);
-
-      var i, ii;
-      if (this.data.hasBgColor && this.data.color) {
-        var color = this.data.color;
-
-        // Enlighten the color (70%).
-        var BACKGROUND_ENLIGHT = 0.7;
-        var r = BACKGROUND_ENLIGHT * (255 - color[0]) + color[0];
-        var g = BACKGROUND_ENLIGHT * (255 - color[1]) + color[1];
-        var b = BACKGROUND_ENLIGHT * (255 - color[2]) + color[2];
-        content.style.backgroundColor = Util.makeCssRgb(r | 0, g | 0, b | 0);
-      }
-
-      var title = document.createElement('h1');
-      var text = document.createElement('p');
-      title.textContent = this.data.title;
-
-      if (!this.data.content && !this.data.title) {
-        content.setAttribute('hidden', true);
-      } else {
-        var e = document.createElement('span');
-        var lines = this.data.content.split(/(?:\r\n?|\n)/);
-        for (i = 0, ii = lines.length; i < ii; ++i) {
-          var line = lines[i];
-          e.appendChild(document.createTextNode(line));
-          if (i < (ii - 1)) {
-            e.appendChild(document.createElement('br'));
-          }
-        }
-        text.appendChild(e);
-
-        image.addEventListener('click', this._toggle.bind(this));
-        image.addEventListener('mouseover', this._show.bind(this, false));
-        image.addEventListener('mouseout', this._hide.bind(this, false));
-        content.addEventListener('click', this._hide.bind(this, true));
-      }
-
-      content.appendChild(title);
-      content.appendChild(text);
-      contentWrapper.appendChild(content);
-      container.appendChild(image);
-      container.appendChild(contentWrapper);
-      return container;
-    },
-
-    /**
-     * Toggle the visibility of the content box.
-     *
-     * @private
-     * @memberof TextAnnotationElement
-     */
-    _toggle: function TextAnnotationElement_toggle() {
-      if (this.pinned) {
-        this._hide(true);
-      } else {
-        this._show(true);
-      }
-    },
-
-    /**
-     * Show the content box.
-     *
-     * @private
-     * @param {boolean} pin
-     * @memberof TextAnnotationElement
-     */
-    _show: function TextAnnotationElement_show(pin) {
-      if (pin) {
-        this.pinned = true;
+      image.dataset.l10nArgs = JSON.stringify({type: this.data.name});
+
+      if (!this.data.hasPopup) {
+        var popupElement = new PopupElement({
+          parentContainer: this.container,
+          parentTrigger: image,
+          color: this.data.color,
+          title: this.data.title,
+          contents: this.data.contents
+        });
+        var popup = popupElement.render();
+
+        // Position the popup next to the Text annotation's container.
+        popup.style.left = image.style.width;
+
+        this.container.appendChild(popup);
       }
-      if (this.content.hasAttribute('hidden')) {
-        this.container.style.zIndex += 1;
-        this.content.removeAttribute('hidden');
-      }
-    },
 
-    /**
-     * Hide the content box.
-     *
-     * @private
-     * @param {boolean} unpin
-     * @memberof TextAnnotationElement
-     */
-    _hide: function TextAnnotationElement_hide(unpin) {
-      if (unpin) {
-        this.pinned = false;
-      }
-      if (!this.content.hasAttribute('hidden') && !this.pinned) {
-        this.container.style.zIndex -= 1;
-        this.content.setAttribute('hidden', true);
-      }
+      this.container.appendChild(image);
+      return this.container;
     }
   });
 
@@ -500,6 +409,189 @@ var WidgetAnnotationElement = (function WidgetAnnotationElementClosure() {
 })();
 
 /**
+ * @class
+ * @alias PopupAnnotationElement
+ */
+var PopupAnnotationElement = (function PopupAnnotationElementClosure() {
+  function PopupAnnotationElement(parameters) {
+    AnnotationElement.call(this, parameters);
+  }
+
+  Util.inherit(PopupAnnotationElement, AnnotationElement, {
+    /**
+     * Render the popup annotation's HTML element in the empty container.
+     *
+     * @public
+     * @memberof PopupAnnotationElement
+     * @returns {HTMLSectionElement}
+     */
+    render: function PopupAnnotationElement_render() {
+      this.container.className = 'popupAnnotation';
+
+      var selector = '[data-annotation-id="' + this.data.parentId + '"]';
+      var parentElement = this.layer.querySelector(selector);
+      if (!parentElement) {
+        return this.container;
+      }
+
+      var popup = new PopupElement({
+        parentContainer: parentElement,
+        parentTrigger: parentElement,
+        color: this.data.color,
+        title: this.data.title,
+        contents: this.data.contents
+      });
+
+      // Position the popup next to the parent annotation's container.
+      // PDF viewers ignore a popup annotation's rectangle.
+      var parentLeft = parseFloat(parentElement.style.left);
+      var parentWidth = parseFloat(parentElement.style.width);
+      CustomStyle.setProp('transformOrigin', this.container,
+                          -(parentLeft + parentWidth) + 'px -' +
+                          parentElement.style.top);
+      this.container.style.left = (parentLeft + parentWidth) + 'px';
+
+      this.container.appendChild(popup.render());
+      return this.container;
+    }
+  });
+
+  return PopupAnnotationElement;
+})();
+
+/**
+ * @class
+ * @alias PopupElement 
+ */
+var PopupElement = (function PopupElementClosure() {
+  var BACKGROUND_ENLIGHT = 0.7;
+
+  function PopupElement(parameters) {
+    this.parentContainer = parameters.parentContainer;
+    this.parentTrigger = parameters.parentTrigger;
+    this.color = parameters.color;
+    this.title = parameters.title;
+    this.contents = parameters.contents;
+
+    this.pinned = false;
+  }
+
+  PopupElement.prototype = /** @lends PopupElement.prototype */ {
+    /**
+     * Render the popup's HTML element.
+     *
+     * @public
+     * @memberof PopupElement
+     * @returns {HTMLSectionElement}
+     */
+    render: function PopupElement_render() {
+      var wrapper = document.createElement('div');
+      wrapper.className = 'popupWrapper';
+
+      var popup = this.popup = document.createElement('div');
+      popup.className = 'popup';
+      popup.setAttribute('hidden', true);
+
+      var color = this.color;
+      if (color) {
+        // Enlighten the color.
+        var r = BACKGROUND_ENLIGHT * (255 - color[0]) + color[0];
+        var g = BACKGROUND_ENLIGHT * (255 - color[1]) + color[1];
+        var b = BACKGROUND_ENLIGHT * (255 - color[2]) + color[2];
+        popup.style.backgroundColor = Util.makeCssRgb(r | 0, g | 0, b | 0);
+      }
+
+      var contents = this._formatContents(this.contents);
+      var title = document.createElement('h1');
+      title.textContent = this.title;
+
+      // Attach the event listeners to the trigger element.
+      var trigger = this.parentTrigger;
+      trigger.addEventListener('click', this._toggle.bind(this));
+      trigger.addEventListener('mouseover', this._show.bind(this, false));
+      trigger.addEventListener('mouseout', this._hide.bind(this, false));
+      popup.addEventListener('click', this._hide.bind(this, true));
+
+      popup.appendChild(title);
+      popup.appendChild(contents);
+      wrapper.appendChild(popup);
+      return wrapper;
+    },
+
+    /**
+     * Format the contents of the popup by adding newlines where necessary.
+     *
+     * @private
+     * @param {string} contents
+     * @memberof PopupElement
+     * @returns {HTMLParagraphElement}
+     */
+    _formatContents: function PopupElement_formatContents(contents) {
+      var p = document.createElement('p');
+      var lines = contents.split(/(?:\r\n?|\n)/);
+      for (var i = 0, ii = lines.length; i < ii; ++i) {
+        var line = lines[i];
+        p.appendChild(document.createTextNode(line));
+        if (i < (ii - 1)) {
+          p.appendChild(document.createElement('br'));
+        }
+      }
+      return p;
+    },
+
+    /**
+     * Toggle the visibility of the popup.
+     *
+     * @private
+     * @memberof PopupElement
+     */
+    _toggle: function PopupElement_toggle() {
+      if (this.pinned) {
+        this._hide(true);
+      } else {
+        this._show(true);
+      }
+    },
+
+    /**
+     * Show the popup.
+     *
+     * @private
+     * @param {boolean} pin
+     * @memberof PopupElement
+     */
+    _show: function PopupElement_show(pin) {
+      if (pin) {
+        this.pinned = true;
+      }
+      if (this.popup.hasAttribute('hidden')) {
+        this.popup.removeAttribute('hidden');
+        this.parentContainer.style.zIndex += 1;
+      }
+    },
+
+    /**
+     * Hide the popup.
+     *
+     * @private
+     * @param {boolean} unpin
+     * @memberof PopupElement
+     */
+    _hide: function PopupElement_hide(unpin) {
+      if (unpin) {
+        this.pinned = false;
+      }
+      if (!this.popup.hasAttribute('hidden') && !this.pinned) {
+        this.popup.setAttribute('hidden', true);
+        this.parentContainer.style.zIndex -= 1;
+      }
+    }
+  };
+
+  return PopupElement;
+})();
+
+/**
  * @typedef {Object} AnnotationLayerParameters
  * @property {PageViewport} viewport
  * @property {HTMLDivElement} div
@@ -532,6 +624,7 @@ var AnnotationLayer = (function AnnotationLayerClosure() {
 
         var properties = {
           data: data,
+          layer: parameters.div,
           page: parameters.page,
           viewport: parameters.viewport,
           linkService: parameters.linkService
diff --git a/test/annotation_layer_test.css b/test/annotation_layer_test.css
index 3101be4..9ea5dc1 100644
--- a/test/annotation_layer_test.css
+++ b/test/annotation_layer_test.css
@@ -39,35 +39,37 @@
   box-shadow: 0px 2px 10px #ff0;
 }
 
-.annotationLayer .annotText > img {
+.annotationLayer .textAnnotation img {
   position: absolute;
 }
 
-.annotationLayer .annotTextContentWrapper {
+.annotationLayer .popupWrapper {
   position: absolute;
   width: 20em;
 }
 
-.annotationLayer .annotTextContent {
+.annotationLayer .popup {
+  position: absolute;
   z-index: 200;
-  float: left;
   max-width: 20em;
   background-color: #FFFF99;
   box-shadow: 0px 2px 5px #333;
   border-radius: 2px;
   padding: 0.6em;
+  margin-left: 5px;
   display: block !important;
   font: message-box;
+  word-wrap: break-word;
 }
 
-.annotationLayer .annotTextContent > h1 {
+.annotationLayer .popup h1 {
   font-size: 1em;
   border-bottom: 1px solid #000000;
   margin: 0;
   padding: 0 0 0.2em 0;
 }
 
-.annotationLayer .annotTextContent > p {
+.annotationLayer .popup p {
   margin: 0;
   padding: 0.2em 0 0 0;
 }
diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore
index c2a6733..d3a7680 100644
--- a/test/pdfs/.gitignore
+++ b/test/pdfs/.gitignore
@@ -198,3 +198,5 @@
 !issue6108.pdf
 !issue6113.pdf
 !openoffice.pdf
+!annotation-link-text-popup.pdf
+!annotation-text-without-popup.pdf
diff --git a/test/pdfs/annotation-link-text-popup.pdf b/test/pdfs/annotation-link-text-popup.pdf
new file mode 100644
index 0000000..2a840b1
Binary files /dev/null and b/test/pdfs/annotation-link-text-popup.pdf differ
diff --git a/test/pdfs/annotation-text-without-popup.pdf b/test/pdfs/annotation-text-without-popup.pdf
new file mode 100644
index 0000000..5c02df1
Binary files /dev/null and b/test/pdfs/annotation-text-without-popup.pdf differ
diff --git a/test/test_manifest.json b/test/test_manifest.json
index 0538c17..92d433d 100644
--- a/test/test_manifest.json
+++ b/test/test_manifest.json
@@ -2616,6 +2616,21 @@
        "rounds": 1,
        "type": "load"
     },
+    {  "id": "annotation-link-text-popup",
+       "file": "pdfs/annotation-link-text-popup.pdf",
+       "md5": "4bbf56e81d47232de5f305124ab0ba27",
+       "rounds": 1,
+       "type": "eq",
+       "annotations": true
+    },
+    {  "id": "annotation-text-without-popup",
+       "file": "pdfs/annotation-text-without-popup.pdf",
+       "md5": "7c2d241babe00139e34b9f8369a909eb",
+       "rounds": 1,
+       "type": "eq",
+       "annotations": true,
+       "about": "Text annotation without a separate Popup annotation"
+    },
     {  "id": "issue6108",
        "file": "pdfs/issue6108.pdf",
        "md5": "8961cb55149495989a80bf0487e0f076",
diff --git a/web/annotation_layer_builder.css b/web/annotation_layer_builder.css
index dc5a161..daeb48f 100644
--- a/web/annotation_layer_builder.css
+++ b/web/annotation_layer_builder.css
@@ -23,34 +23,36 @@
   box-shadow: 0px 2px 10px #ff0;
 }
 
-.annotationLayer .annotText > img {
+.annotationLayer .textAnnotation img {
   position: absolute;
   cursor: pointer;
 }
 
-.annotationLayer .annotTextContentWrapper {
+.annotationLayer .popupWrapper {
   position: absolute;
   width: 20em;
 }
 
-.annotationLayer .annotTextContent {
+.annotationLayer .popup {
+  position: absolute;
   z-index: 200;
-  float: left;
   max-width: 20em;
   background-color: #FFFF99;
   box-shadow: 0px 2px 5px #333;
   border-radius: 2px;
   padding: 0.6em;
+  margin-left: 5px;
   cursor: pointer;
+  word-wrap: break-word;
 }
 
-.annotationLayer .annotTextContent > h1 {
+.annotationLayer .popup h1 {
   font-size: 1em;
   border-bottom: 1px solid #000000;
   padding-bottom: 0.2em;
 }
 
-.annotationLayer .annotTextContent > p {
+.annotationLayer .popup p {
   padding-top: 0.2em;
 }
 

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/pdf.js.git



More information about the Pkg-javascript-commits mailing list