From 85e6c67cf3ec92356beda87daa0b9d9689d6a499 Mon Sep 17 00:00:00 2001
From: Calixte Denizet <calixte.denizet@gmail.com>
Date: Wed, 21 Oct 2020 13:12:43 +0200
Subject: [PATCH] Split highlight annotation div into multiple divs

Fix for issue #12504.
Highlight annotation may have several rectangles so we must have several divs to add mouse events handlers.
---
 src/core/annotation.js          |  7 +++
 src/display/annotation_layer.js | 86 ++++++++++++++++++++++++++-------
 2 files changed, 75 insertions(+), 18 deletions(-)

diff --git a/src/core/annotation.js b/src/core/annotation.js
index ec5d51869..e6aaf8453 100644
--- a/src/core/annotation.js
+++ b/src/core/annotation.js
@@ -2086,6 +2086,13 @@ class PopupAnnotation extends Annotation {
     const rawParent = parameters.dict.getRaw("Parent");
     this.data.parentId = isRef(rawParent) ? rawParent.toString() : null;
 
+    const parentRect = parentItem.getArray("Rect");
+    if (Array.isArray(parentRect) && parentRect.length === 4) {
+      this.data.parentRect = Util.normalizeRect(parentRect);
+    } else {
+      this.data.parentRect = [0, 0, 0, 0];
+    }
+
     const rt = parentItem.get("RT");
     if (isName(rt, AnnotationReplyType.GROUP)) {
       // Subordinate annotations in a group should inherit
diff --git a/src/display/annotation_layer.js b/src/display/annotation_layer.js
index 71d7945c0..c736e05c5 100644
--- a/src/display/annotation_layer.js
+++ b/src/display/annotation_layer.js
@@ -239,6 +239,35 @@ class AnnotationElement {
     return container;
   }
 
+  /**
+   * Create quadrilaterals for the quadPoints.
+   *
+   * @private
+   * @param {boolean} ignoreBorder
+   * @memberof AnnotationElement
+   * @returns {HTMLSectionElement}
+   */
+  _createQuadrilaterals(ignoreBorder = false) {
+    if (!this.data.quadPoints) {
+      return null;
+    }
+
+    const quadrilaterals = [];
+    const savedRect = this.data.rect;
+    for (const quadPoint of this.data.quadPoints) {
+      const rect = [
+        quadPoint[2].x,
+        quadPoint[2].y,
+        quadPoint[1].x,
+        quadPoint[1].y,
+      ];
+      this.data.rect = rect;
+      quadrilaterals.push(this._createContainer(ignoreBorder));
+    }
+    this.data.rect = savedRect;
+    return quadrilaterals;
+  }
+
   /**
    * Create a popup for the annotation's HTML element. This is used for
    * annotations that do not have a Popup entry in the dictionary, but
@@ -789,14 +818,14 @@ class PopupAnnotationElement extends AnnotationElement {
     }
 
     const selector = `[data-annotation-id="${this.data.parentId}"]`;
-    const parentElement = this.layer.querySelector(selector);
-    if (!parentElement) {
+    const parentElements = this.layer.querySelectorAll(selector);
+    if (parentElements.length === 0) {
       return this.container;
     }
 
     const popup = new PopupElement({
       container: this.container,
-      trigger: parentElement,
+      trigger: Array.from(parentElements),
       color: this.data.color,
       title: this.data.title,
       modificationDate: this.data.modificationDate,
@@ -805,12 +834,18 @@ class PopupAnnotationElement extends AnnotationElement {
 
     // Position the popup next to the parent annotation's container.
     // PDF viewers ignore a popup annotation's rectangle.
-    const parentTop = parseFloat(parentElement.style.top),
-      parentLeft = parseFloat(parentElement.style.left),
-      parentWidth = parseFloat(parentElement.style.width);
-    const popupLeft = parentLeft + parentWidth;
+    const page = this.page;
+    const rect = Util.normalizeRect([
+      this.data.parentRect[0],
+      page.view[3] - this.data.parentRect[1] + page.view[1],
+      this.data.parentRect[2],
+      page.view[3] - this.data.parentRect[3] + page.view[1],
+    ]);
+    const popupLeft =
+      rect[0] + this.data.parentRect[2] - this.data.parentRect[0];
+    const popupTop = rect[1];
 
-    this.container.style.transformOrigin = `${-popupLeft}px ${-parentTop}px`;
+    this.container.style.transformOrigin = `${-popupLeft}px ${-popupTop}px`;
     this.container.style.left = `${popupLeft}px`;
 
     this.container.appendChild(popup.render());
@@ -885,10 +920,16 @@ class PopupElement {
     const contents = this._formatContents(this.contents);
     popup.appendChild(contents);
 
+    if (!Array.isArray(this.trigger)) {
+      this.trigger = [this.trigger];
+    }
+
     // Attach the event listeners to the trigger element.
-    this.trigger.addEventListener("click", this._toggle.bind(this));
-    this.trigger.addEventListener("mouseover", this._show.bind(this, false));
-    this.trigger.addEventListener("mouseout", this._hide.bind(this, false));
+    this.trigger.forEach(element => {
+      element.addEventListener("click", this._toggle.bind(this));
+      element.addEventListener("mouseover", this._show.bind(this, false));
+      element.addEventListener("mouseout", this._hide.bind(this, false));
+    });
     popup.addEventListener("click", this._hide.bind(this, true));
 
     wrapper.appendChild(popup);
@@ -1324,6 +1365,7 @@ class HighlightAnnotationElement extends AnnotationElement {
       parameters.data.contents
     );
     super(parameters, isRenderable, /* ignoreBorder = */ true);
+    this.quadrilaterals = this._createQuadrilaterals(/* ignoreBorder = */ true);
   }
 
   /**
@@ -1339,7 +1381,7 @@ class HighlightAnnotationElement extends AnnotationElement {
     if (!this.data.hasPopup) {
       this._createPopup(this.container, null, this.data);
     }
-    return this.container;
+    return this.quadrilaterals || this.container;
   }
 }
 
@@ -1567,7 +1609,14 @@ class AnnotationLayer {
           parameters.annotationStorage || new AnnotationStorage(),
       });
       if (element.isRenderable) {
-        parameters.div.appendChild(element.render());
+        const rendered = element.render();
+        if (Array.isArray(rendered)) {
+          for (const renderedElement of rendered) {
+            parameters.div.appendChild(renderedElement);
+          }
+        } else {
+          parameters.div.appendChild(rendered);
+        }
       }
     }
   }
@@ -1580,14 +1629,15 @@ class AnnotationLayer {
    * @memberof AnnotationLayer
    */
   static update(parameters) {
+    const transform = `matrix(${parameters.viewport.transform.join(",")})`;
     for (const data of parameters.annotations) {
-      const element = parameters.div.querySelector(
+      const elements = parameters.div.querySelectorAll(
         `[data-annotation-id="${data.id}"]`
       );
-      if (element) {
-        element.style.transform = `matrix(${parameters.viewport.transform.join(
-          ","
-        )})`;
+      if (elements) {
+        elements.forEach(element => {
+          element.style.transform = transform;
+        });
       }
     }
     parameters.div.removeAttribute("hidden");