瀏覽代碼

PDF.js version 1.3.124 - See mozilla/pdf.js@cba8a87f8431f736477b52c0e6f1c4da499d8bbf

Pdf Bot 9 年之前
父節點
當前提交
76017d55d9
共有 6 個文件被更改,包括 556 次插入288 次删除
  1. 1 1
      bower.json
  2. 272 139
      build/pdf.combined.js
  3. 212 119
      build/pdf.js
  4. 62 22
      build/pdf.worker.js
  5. 1 1
      package.json
  6. 8 6
      web/pdf_viewer.css

+ 1 - 1
bower.json

@@ -1,6 +1,6 @@
 {
   "name": "pdfjs-dist",
-  "version": "1.3.122",
+  "version": "1.3.124",
   "main": [
     "build/pdf.js",
     "build/pdf.worker.js"

+ 272 - 139
build/pdf.combined.js

@@ -21,8 +21,8 @@ if (typeof PDFJS === 'undefined') {
    typeof global !== 'undefined' ? global : this).PDFJS = {};
 }
 
-PDFJS.version = '1.3.122';
-PDFJS.build = '05b9d37';
+PDFJS.version = '1.3.124';
+PDFJS.build = 'cba8a87';
 
 (function pdfjsWrapper() {
   // Use strict in our context only - users might not want it
@@ -1810,11 +1810,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
@@ -1844,6 +1843,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 + '"');
     }
@@ -1857,6 +1859,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;
@@ -2066,8 +2069,6 @@ var LinkAnnotationElement = (function LinkAnnotationElementClosure() {
 var TextAnnotationElement = (function TextAnnotationElementClosure() {
   function TextAnnotationElement(parameters) {
     AnnotationElement.call(this, parameters);
-
-    this.pinned = false;
   }
 
   Util.inherit(TextAnnotationElement, AnnotationElement, {
@@ -2079,127 +2080,35 @@ var TextAnnotationElement = (function TextAnnotationElementClosure() {
      * @returns {HTMLSectionElement}
      */
     render: function TextAnnotationElement_render() {
-      var rect = this.data.rect, container = this.container;
+      this.container.className = 'textAnnotation';
 
-      // 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';
-
-      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;
-    },
+      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();
 
-    /**
-     * Toggle the visibility of the content box.
-     *
-     * @private
-     * @memberof TextAnnotationElement
-     */
-    _toggle: function TextAnnotationElement_toggle() {
-      if (this.pinned) {
-        this._hide(true);
-      } else {
-        this._show(true);
-      }
-    },
+        // Position the popup next to the Text annotation's container.
+        popup.style.left = image.style.width;
 
-    /**
-     * Show the content box.
-     *
-     * @private
-     * @param {boolean} pin
-     * @memberof TextAnnotationElement
-     */
-    _show: function TextAnnotationElement_show(pin) {
-      if (pin) {
-        this.pinned = true;
-      }
-      if (this.content.hasAttribute('hidden')) {
-        this.container.style.zIndex += 1;
-        this.content.removeAttribute('hidden');
+        this.container.appendChild(popup);
       }
-    },
 
-    /**
-     * 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;
     }
   });
 
@@ -2273,6 +2182,189 @@ var WidgetAnnotationElement = (function WidgetAnnotationElementClosure() {
   return WidgetAnnotationElement;
 })();
 
+/**
+ * @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
@@ -2306,6 +2398,7 @@ var AnnotationLayer = (function AnnotationLayerClosure() {
 
         var properties = {
           data: data,
+          layer: parameters.div,
           page: parameters.page,
           viewport: parameters.viewport,
           linkService: parameters.linkService
@@ -46392,8 +46485,6 @@ var ColorSpace = coreColorSpace.ColorSpace;
 var ObjectLoader = coreObj.ObjectLoader;
 var OperatorList = coreEvaluator.OperatorList;
 
-var DEFAULT_ICON_SIZE = 22; // px
-
 /**
  * @class
  * @alias AnnotationFactory
@@ -46435,6 +46526,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');
@@ -46500,7 +46594,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;
@@ -46979,29 +47073,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);
     }
   }
 
@@ -47086,6 +47186,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;

+ 212 - 119
build/pdf.js

@@ -21,8 +21,8 @@ if (typeof PDFJS === 'undefined') {
    typeof global !== 'undefined' ? global : this).PDFJS = {};
 }
 
-PDFJS.version = '1.3.122';
-PDFJS.build = '05b9d37';
+PDFJS.version = '1.3.124';
+PDFJS.build = 'cba8a87';
 
 (function pdfjsWrapper() {
   // Use strict in our context only - users might not want it
@@ -1810,11 +1810,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
@@ -1844,6 +1843,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 + '"');
     }
@@ -1857,6 +1859,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;
@@ -2066,8 +2069,6 @@ var LinkAnnotationElement = (function LinkAnnotationElementClosure() {
 var TextAnnotationElement = (function TextAnnotationElementClosure() {
   function TextAnnotationElement(parameters) {
     AnnotationElement.call(this, parameters);
-
-    this.pinned = false;
   }
 
   Util.inherit(TextAnnotationElement, AnnotationElement, {
@@ -2079,127 +2080,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));
-      }
+      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();
 
-      content.appendChild(title);
-      content.appendChild(text);
-      contentWrapper.appendChild(content);
-      container.appendChild(image);
-      container.appendChild(contentWrapper);
-      return container;
-    },
+        // Position the popup next to the Text annotation's container.
+        popup.style.left = image.style.width;
 
-    /**
-     * Toggle the visibility of the content box.
-     *
-     * @private
-     * @memberof TextAnnotationElement
-     */
-    _toggle: function TextAnnotationElement_toggle() {
-      if (this.pinned) {
-        this._hide(true);
-      } else {
-        this._show(true);
+        this.container.appendChild(popup);
       }
-    },
 
-    /**
-     * Show the content box.
-     *
-     * @private
-     * @param {boolean} pin
-     * @memberof TextAnnotationElement
-     */
-    _show: function TextAnnotationElement_show(pin) {
-      if (pin) {
-        this.pinned = true;
-      }
-      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;
     }
   });
 
@@ -2273,6 +2182,189 @@ var WidgetAnnotationElement = (function WidgetAnnotationElementClosure() {
   return WidgetAnnotationElement;
 })();
 
+/**
+ * @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
@@ -2306,6 +2398,7 @@ var AnnotationLayer = (function AnnotationLayerClosure() {
 
         var properties = {
           data: data,
+          layer: parameters.div,
           page: parameters.page,
           viewport: parameters.viewport,
           linkService: parameters.linkService

+ 62 - 22
build/pdf.worker.js

@@ -21,8 +21,8 @@ if (typeof PDFJS === 'undefined') {
    typeof global !== 'undefined' ? global : this).PDFJS = {};
 }
 
-PDFJS.version = '1.3.122';
-PDFJS.build = '05b9d37';
+PDFJS.version = '1.3.124';
+PDFJS.build = 'cba8a87';
 
 (function pdfjsWrapper() {
   // Use strict in our context only - users might not want it
@@ -38809,8 +38809,6 @@ var ColorSpace = coreColorSpace.ColorSpace;
 var ObjectLoader = coreObj.ObjectLoader;
 var OperatorList = coreEvaluator.OperatorList;
 
-var DEFAULT_ICON_SIZE = 22; // px
-
 /**
  * @class
  * @alias AnnotationFactory
@@ -38852,6 +38850,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');
@@ -38917,7 +38918,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;
@@ -39396,29 +39397,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);
     }
   }
 
@@ -39503,6 +39510,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;

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "pdfjs-dist",
-  "version": "1.3.122",
+  "version": "1.3.124",
   "main": "build/pdf.js",
   "description": "Generic build of Mozilla's PDF.js library.",
   "keywords": [

+ 8 - 6
web/pdf_viewer.css

@@ -92,34 +92,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;
 }