From 19f56ec5c18aa8c161df9b718faf62d831f67657 Mon Sep 17 00:00:00 2001
From: Tim van der Meij <timvandermeij@gmail.com>
Date: Wed, 28 Jun 2017 23:15:16 +0200
Subject: [PATCH] Convert the text layer builder to ES6 syntax

---
 web/text_layer_builder.js | 626 +++++++++++++++++++-------------------
 1 file changed, 312 insertions(+), 314 deletions(-)

diff --git a/web/text_layer_builder.js b/web/text_layer_builder.js
index fa5c752d1..5b4bf6ff8 100644
--- a/web/text_layer_builder.js
+++ b/web/text_layer_builder.js
@@ -16,7 +16,7 @@
 import { getGlobalEventBus } from './dom_events';
 import { renderTextLayer } from 'pdfjs-lib';
 
-var EXPAND_DIVS_TIMEOUT = 300; // ms
+const EXPAND_DIVS_TIMEOUT = 300; // ms
 
 /**
  * @typedef {Object} TextLayerBuilderOptions
@@ -30,378 +30,376 @@ var EXPAND_DIVS_TIMEOUT = 300; // ms
  */
 
 /**
- * TextLayerBuilder provides text-selection functionality for the PDF.
- * It does this by creating overlay divs over the PDF text. These divs
+ * The text layer builder provides text selection functionality for the PDF.
+ * It does this by creating overlay divs over the PDF's text. These divs
  * contain text that matches the PDF text they are overlaying. This object
  * also provides a way to highlight text that is being searched for.
- * @class
  */
-var TextLayerBuilder = (function TextLayerBuilderClosure() {
-  function TextLayerBuilder(options) {
-    this.textLayerDiv = options.textLayerDiv;
-    this.eventBus = options.eventBus || getGlobalEventBus();
+class TextLayerBuilder {
+  constructor({ textLayerDiv, eventBus, pageIndex, viewport,
+                findController = null, enhanceTextSelection = false, }) {
+    this.textLayerDiv = textLayerDiv;
+    this.eventBus = eventBus || getGlobalEventBus();
     this.textContent = null;
     this.textContentItemsStr = [];
     this.textContentStream = null;
     this.renderingDone = false;
-    this.pageIdx = options.pageIndex;
+    this.pageIdx = pageIndex;
     this.pageNumber = this.pageIdx + 1;
     this.matches = [];
-    this.viewport = options.viewport;
+    this.viewport = viewport;
     this.textDivs = [];
-    this.findController = options.findController || null;
+    this.findController = findController;
     this.textLayerRenderTask = null;
-    this.enhanceTextSelection = options.enhanceTextSelection;
+    this.enhanceTextSelection = enhanceTextSelection;
+
     this._bindMouse();
   }
 
-  TextLayerBuilder.prototype = {
-    /**
-     * @private
-     */
-    _finishRendering: function TextLayerBuilder_finishRendering() {
-      this.renderingDone = true;
-
-      if (!this.enhanceTextSelection) {
-        var endOfContent = document.createElement('div');
-        endOfContent.className = 'endOfContent';
-        this.textLayerDiv.appendChild(endOfContent);
-      }
+  /**
+   * @private
+   */
+  _finishRendering() {
+    this.renderingDone = true;
+
+    if (!this.enhanceTextSelection) {
+      let endOfContent = document.createElement('div');
+      endOfContent.className = 'endOfContent';
+      this.textLayerDiv.appendChild(endOfContent);
+    }
+
+    this.eventBus.dispatch('textlayerrendered', {
+      source: this,
+      pageNumber: this.pageNumber,
+      numTextDivs: this.textDivs.length,
+    });
+  }
 
-      this.eventBus.dispatch('textlayerrendered', {
-        source: this,
-        pageNumber: this.pageNumber,
-        numTextDivs: this.textDivs.length,
-      });
-    },
-
-    /**
-     * Renders the text layer.
-     * @param {number} timeout (optional) if specified, the rendering waits
-     *   for specified amount of ms.
-     */
-    render: function TextLayerBuilder_render(timeout) {
-      if (!(this.textContent || this.textContentStream) || this.renderingDone) {
-        return;
-      }
-      this.cancel();
-
-      this.textDivs = [];
-      var textLayerFrag = document.createDocumentFragment();
-      this.textLayerRenderTask = renderTextLayer({
-        textContent: this.textContent,
-        textContentStream: this.textContentStream,
-        container: textLayerFrag,
-        viewport: this.viewport,
-        textDivs: this.textDivs,
-        textContentItemsStr: this.textContentItemsStr,
-        timeout,
-        enhanceTextSelection: this.enhanceTextSelection,
-      });
-      this.textLayerRenderTask.promise.then(() => {
-        this.textLayerDiv.appendChild(textLayerFrag);
-        this._finishRendering();
-        this.updateMatches();
-      }, function (reason) {
-        // cancelled or failed to render text layer -- skipping errors
-      });
-    },
-
-    /**
-     * Cancels rendering of the text layer.
-     */
-    cancel: function TextLayerBuilder_cancel() {
-      if (this.textLayerRenderTask) {
-        this.textLayerRenderTask.cancel();
-        this.textLayerRenderTask = null;
-      }
-    },
-
-    setTextContentStream(readableStream) {
-      this.cancel();
-      this.textContentStream = readableStream;
-    },
-
-    setTextContent: function TextLayerBuilder_setTextContent(textContent) {
-      this.cancel();
-      this.textContent = textContent;
-    },
-
-    convertMatches: function TextLayerBuilder_convertMatches(matches,
-                                                             matchesLength) {
-      var i = 0;
-      var iIndex = 0;
-      let textContentItemsStr = this.textContentItemsStr;
-      var end = textContentItemsStr.length - 1;
-      var queryLen = (this.findController === null ?
-                      0 : this.findController.state.query.length);
-      var ret = [];
-      if (!matches) {
-        return ret;
-      }
-      for (var m = 0, len = matches.length; m < len; m++) {
-        // Calculate the start position.
-        var matchIdx = matches[m];
-
-        // Loop over the divIdxs.
-        while (i !== end && matchIdx >=
-               (iIndex + textContentItemsStr[i].length)) {
-          iIndex += textContentItemsStr[i].length;
-          i++;
-        }
+  /**
+   * Renders the text layer.
+   *
+   * @param {number} timeout - (optional) wait for a specified amount of
+   *                           milliseconds before rendering
+   */
+  render(timeout = 0) {
+    if (!(this.textContent || this.textContentStream) || this.renderingDone) {
+      return;
+    }
+    this.cancel();
 
-        if (i === textContentItemsStr.length) {
-          console.error('Could not find a matching mapping');
-        }
+    this.textDivs = [];
+    let textLayerFrag = document.createDocumentFragment();
+    this.textLayerRenderTask = renderTextLayer({
+      textContent: this.textContent,
+      textContentStream: this.textContentStream,
+      container: textLayerFrag,
+      viewport: this.viewport,
+      textDivs: this.textDivs,
+      textContentItemsStr: this.textContentItemsStr,
+      timeout,
+      enhanceTextSelection: this.enhanceTextSelection,
+    });
+    this.textLayerRenderTask.promise.then(() => {
+      this.textLayerDiv.appendChild(textLayerFrag);
+      this._finishRendering();
+      this.updateMatches();
+    }, function (reason) {
+      // Cancelled or failed to render text layer; skipping errors.
+    });
+  }
 
-        var match = {
-          begin: {
-            divIdx: i,
-            offset: matchIdx - iIndex,
-          },
-        };
-
-        // Calculate the end position.
-        if (matchesLength) { // multiterm search
-          matchIdx += matchesLength[m];
-        } else { // phrase search
-          matchIdx += queryLen;
-        }
+  /**
+   * Cancel rendering of the text layer.
+   */
+  cancel() {
+    if (this.textLayerRenderTask) {
+      this.textLayerRenderTask.cancel();
+      this.textLayerRenderTask = null;
+    }
+  }
 
-        // Somewhat the same array as above, but use > instead of >= to get
-        // the end position right.
-        while (i !== end && matchIdx >
-               (iIndex + textContentItemsStr[i].length)) {
-          iIndex += textContentItemsStr[i].length;
-          i++;
-        }
+  setTextContentStream(readableStream) {
+    this.cancel();
+    this.textContentStream = readableStream;
+  }
 
-        match.end = {
-          divIdx: i,
-          offset: matchIdx - iIndex,
-        };
-        ret.push(match);
-      }
+  setTextContent(textContent) {
+    this.cancel();
+    this.textContent = textContent;
+  }
 
+  convertMatches(matches, matchesLength) {
+    let i = 0;
+    let iIndex = 0;
+    let textContentItemsStr = this.textContentItemsStr;
+    let end = textContentItemsStr.length - 1;
+    let queryLen = (this.findController === null ?
+                    0 : this.findController.state.query.length);
+    let ret = [];
+    if (!matches) {
       return ret;
-    },
+    }
+    for (let m = 0, len = matches.length; m < len; m++) {
+      // Calculate the start position.
+      let matchIdx = matches[m];
+
+      // Loop over the divIdxs.
+      while (i !== end && matchIdx >=
+             (iIndex + textContentItemsStr[i].length)) {
+        iIndex += textContentItemsStr[i].length;
+        i++;
+      }
 
-    renderMatches: function TextLayerBuilder_renderMatches(matches) {
-      // Early exit if there is nothing to render.
-      if (matches.length === 0) {
-        return;
+      if (i === textContentItemsStr.length) {
+        console.error('Could not find a matching mapping');
       }
 
-      let textContentItemsStr = this.textContentItemsStr;
-      var textDivs = this.textDivs;
-      var prevEnd = null;
-      var pageIdx = this.pageIdx;
-      var isSelectedPage = (this.findController === null ?
-        false : (pageIdx === this.findController.selected.pageIdx));
-      var selectedMatchIdx = (this.findController === null ?
-                              -1 : this.findController.selected.matchIdx);
-      var highlightAll = (this.findController === null ?
-                          false : this.findController.state.highlightAll);
-      var infinity = {
-        divIdx: -1,
-        offset: undefined,
+      let match = {
+        begin: {
+          divIdx: i,
+          offset: matchIdx - iIndex,
+        },
       };
 
-      function beginText(begin, className) {
-        var divIdx = begin.divIdx;
-        textDivs[divIdx].textContent = '';
-        appendTextToDiv(divIdx, 0, begin.offset, className);
+      // Calculate the end position.
+      if (matchesLength) { // Multiterm search.
+        matchIdx += matchesLength[m];
+      } else { // Phrase search.
+        matchIdx += queryLen;
       }
 
-      function appendTextToDiv(divIdx, fromOffset, toOffset, className) {
-        var div = textDivs[divIdx];
-        var content =
-          textContentItemsStr[divIdx].substring(fromOffset, toOffset);
-        var node = document.createTextNode(content);
-        if (className) {
-          var span = document.createElement('span');
-          span.className = className;
-          span.appendChild(node);
-          div.appendChild(span);
-          return;
-        }
-        div.appendChild(node);
+      // Somewhat the same array as above, but use > instead of >= to get
+      // the end position right.
+      while (i !== end && matchIdx >
+             (iIndex + textContentItemsStr[i].length)) {
+        iIndex += textContentItemsStr[i].length;
+        i++;
       }
 
-      var i0 = selectedMatchIdx, i1 = i0 + 1;
-      if (highlightAll) {
-        i0 = 0;
-        i1 = matches.length;
-      } else if (!isSelectedPage) {
-        // Not highlighting all and this isn't the selected page, so do nothing.
-        return;
-      }
+      match.end = {
+        divIdx: i,
+        offset: matchIdx - iIndex,
+      };
+      ret.push(match);
+    }
 
-      for (var i = i0; i < i1; i++) {
-        var match = matches[i];
-        var begin = match.begin;
-        var end = match.end;
-        var isSelected = (isSelectedPage && i === selectedMatchIdx);
-        var highlightSuffix = (isSelected ? ' selected' : '');
+    return ret;
+  }
 
-        if (this.findController) {
-          this.findController.updateMatchPosition(pageIdx, i, textDivs,
-                                                  begin.divIdx);
-        }
+  renderMatches(matches) {
+    // Early exit if there is nothing to render.
+    if (matches.length === 0) {
+      return;
+    }
+
+    let textContentItemsStr = this.textContentItemsStr;
+    let textDivs = this.textDivs;
+    let prevEnd = null;
+    let pageIdx = this.pageIdx;
+    let isSelectedPage = (this.findController === null ?
+      false : (pageIdx === this.findController.selected.pageIdx));
+    let selectedMatchIdx = (this.findController === null ?
+                            -1 : this.findController.selected.matchIdx);
+    let highlightAll = (this.findController === null ?
+                        false : this.findController.state.highlightAll);
+    let infinity = {
+      divIdx: -1,
+      offset: undefined,
+    };
+
+    function beginText(begin, className) {
+      let divIdx = begin.divIdx;
+      textDivs[divIdx].textContent = '';
+      appendTextToDiv(divIdx, 0, begin.offset, className);
+    }
+
+    function appendTextToDiv(divIdx, fromOffset, toOffset, className) {
+      let div = textDivs[divIdx];
+      let content = textContentItemsStr[divIdx].substring(fromOffset, toOffset);
+      let node = document.createTextNode(content);
+      if (className) {
+        let span = document.createElement('span');
+        span.className = className;
+        span.appendChild(node);
+        div.appendChild(span);
+        return;
+      }
+      div.appendChild(node);
+    }
+
+    let i0 = selectedMatchIdx, i1 = i0 + 1;
+    if (highlightAll) {
+      i0 = 0;
+      i1 = matches.length;
+    } else if (!isSelectedPage) {
+      // Not highlighting all and this isn't the selected page, so do nothing.
+      return;
+    }
+
+    for (let i = i0; i < i1; i++) {
+      let match = matches[i];
+      let begin = match.begin;
+      let end = match.end;
+      let isSelected = (isSelectedPage && i === selectedMatchIdx);
+      let highlightSuffix = (isSelected ? ' selected' : '');
+
+      if (this.findController) {
+        this.findController.updateMatchPosition(pageIdx, i, textDivs,
+                                                begin.divIdx);
+      }
 
-        // Match inside new div.
-        if (!prevEnd || begin.divIdx !== prevEnd.divIdx) {
-          // If there was a previous div, then add the text at the end.
-          if (prevEnd !== null) {
-            appendTextToDiv(prevEnd.divIdx, prevEnd.offset, infinity.offset);
-          }
-          // Clear the divs and set the content until the starting point.
-          beginText(begin);
-        } else {
-          appendTextToDiv(prevEnd.divIdx, prevEnd.offset, begin.offset);
+      // Match inside new div.
+      if (!prevEnd || begin.divIdx !== prevEnd.divIdx) {
+        // If there was a previous div, then add the text at the end.
+        if (prevEnd !== null) {
+          appendTextToDiv(prevEnd.divIdx, prevEnd.offset, infinity.offset);
         }
+        // Clear the divs and set the content until the starting point.
+        beginText(begin);
+      } else {
+        appendTextToDiv(prevEnd.divIdx, prevEnd.offset, begin.offset);
+      }
 
-        if (begin.divIdx === end.divIdx) {
-          appendTextToDiv(begin.divIdx, begin.offset, end.offset,
-                          'highlight' + highlightSuffix);
-        } else {
-          appendTextToDiv(begin.divIdx, begin.offset, infinity.offset,
-                          'highlight begin' + highlightSuffix);
-          for (var n0 = begin.divIdx + 1, n1 = end.divIdx; n0 < n1; n0++) {
-            textDivs[n0].className = 'highlight middle' + highlightSuffix;
-          }
-          beginText(end, 'highlight end' + highlightSuffix);
+      if (begin.divIdx === end.divIdx) {
+        appendTextToDiv(begin.divIdx, begin.offset, end.offset,
+                        'highlight' + highlightSuffix);
+      } else {
+        appendTextToDiv(begin.divIdx, begin.offset, infinity.offset,
+                        'highlight begin' + highlightSuffix);
+        for (let n0 = begin.divIdx + 1, n1 = end.divIdx; n0 < n1; n0++) {
+          textDivs[n0].className = 'highlight middle' + highlightSuffix;
         }
-        prevEnd = end;
+        beginText(end, 'highlight end' + highlightSuffix);
       }
+      prevEnd = end;
+    }
 
-      if (prevEnd) {
-        appendTextToDiv(prevEnd.divIdx, prevEnd.offset, infinity.offset);
-      }
-    },
+    if (prevEnd) {
+      appendTextToDiv(prevEnd.divIdx, prevEnd.offset, infinity.offset);
+    }
+  }
 
-    updateMatches: function TextLayerBuilder_updateMatches() {
-      // Only show matches when all rendering is done.
-      if (!this.renderingDone) {
-        return;
+  updateMatches() {
+    // Only show matches when all rendering is done.
+    if (!this.renderingDone) {
+      return;
+    }
+
+    // Clear all matches.
+    let matches = this.matches;
+    let textDivs = this.textDivs;
+    let textContentItemsStr = this.textContentItemsStr;
+    let clearedUntilDivIdx = -1;
+
+    // Clear all current matches.
+    for (let i = 0, len = matches.length; i < len; i++) {
+      let match = matches[i];
+      let begin = Math.max(clearedUntilDivIdx, match.begin.divIdx);
+      for (let n = begin, end = match.end.divIdx; n <= end; n++) {
+        let div = textDivs[n];
+        div.textContent = textContentItemsStr[n];
+        div.className = '';
       }
+      clearedUntilDivIdx = match.end.divIdx + 1;
+    }
+
+    if (this.findController === null || !this.findController.active) {
+      return;
+    }
+
+    // Convert the matches on the page controller into the match format
+    // used for the textLayer.
+    let pageMatches, pageMatchesLength;
+    if (this.findController !== null) {
+      pageMatches = this.findController.pageMatches[this.pageIdx] || null;
+      pageMatchesLength = (this.findController.pageMatchesLength) ?
+        this.findController.pageMatchesLength[this.pageIdx] || null : null;
+    }
+
+    this.matches = this.convertMatches(pageMatches, pageMatchesLength);
+    this.renderMatches(this.matches);
+  }
 
-      // Clear all matches.
-      var matches = this.matches;
-      var textDivs = this.textDivs;
-      let textContentItemsStr = this.textContentItemsStr;
-      var clearedUntilDivIdx = -1;
-
-      // Clear all current matches.
-      for (var i = 0, len = matches.length; i < len; i++) {
-        var match = matches[i];
-        var begin = Math.max(clearedUntilDivIdx, match.begin.divIdx);
-        for (var n = begin, end = match.end.divIdx; n <= end; n++) {
-          var div = textDivs[n];
-          div.textContent = textContentItemsStr[n];
-          div.className = '';
+  /**
+   * Improves text selection by adding an additional div where the mouse was
+   * clicked. This reduces flickering of the content if the mouse is slowly
+   * dragged up or down.
+   *
+   * @private
+   */
+  _bindMouse() {
+    let div = this.textLayerDiv;
+    let expandDivsTimer = null;
+
+    div.addEventListener('mousedown', (evt) => {
+      if (this.enhanceTextSelection && this.textLayerRenderTask) {
+        this.textLayerRenderTask.expandTextDivs(true);
+        if ((typeof PDFJSDev === 'undefined' ||
+             !PDFJSDev.test('FIREFOX || MOZCENTRAL')) &&
+            expandDivsTimer) {
+          clearTimeout(expandDivsTimer);
+          expandDivsTimer = null;
         }
-        clearedUntilDivIdx = match.end.divIdx + 1;
-      }
-
-      if (this.findController === null || !this.findController.active) {
         return;
       }
 
-      // Convert the matches on the page controller into the match format
-      // used for the textLayer.
-      var pageMatches, pageMatchesLength;
-      if (this.findController !== null) {
-        pageMatches = this.findController.pageMatches[this.pageIdx] || null;
-        pageMatchesLength = (this.findController.pageMatchesLength) ?
-          this.findController.pageMatchesLength[this.pageIdx] || null : null;
+      let end = div.querySelector('.endOfContent');
+      if (!end) {
+        return;
       }
-
-      this.matches = this.convertMatches(pageMatches, pageMatchesLength);
-      this.renderMatches(this.matches);
-    },
-
-    /**
-     * Fixes text selection: adds additional div where mouse was clicked.
-     * This reduces flickering of the content if mouse slowly dragged down/up.
-     * @private
-     */
-    _bindMouse: function TextLayerBuilder_bindMouse() {
-      var div = this.textLayerDiv;
-      var self = this;
-      var expandDivsTimer = null;
-
-      div.addEventListener('mousedown', function (e) {
-        if (self.enhanceTextSelection && self.textLayerRenderTask) {
-          self.textLayerRenderTask.expandTextDivs(true);
-          if ((typeof PDFJSDev === 'undefined' ||
-               !PDFJSDev.test('FIREFOX || MOZCENTRAL')) &&
-              expandDivsTimer) {
-            clearTimeout(expandDivsTimer);
-            expandDivsTimer = null;
-          }
-          return;
-        }
-        var end = div.querySelector('.endOfContent');
-        if (!end) {
-          return;
-        }
-        if (typeof PDFJSDev === 'undefined' ||
-            !PDFJSDev.test('FIREFOX || MOZCENTRAL')) {
+      if (typeof PDFJSDev === 'undefined' ||
+          !PDFJSDev.test('FIREFOX || MOZCENTRAL')) {
         // On non-Firefox browsers, the selection will feel better if the height
-        // of the endOfContent div will be adjusted to start at mouse click
-        // location -- this will avoid flickering when selections moves up.
-        // However it does not work when selection started on empty space.
-        var adjustTop = e.target !== div;
+        // of the `endOfContent` div is adjusted to start at mouse click
+        // location. This avoids flickering when the selection moves up.
+        // However it does not work when selection is started on empty space.
+        let adjustTop = evt.target !== div;
         if (typeof PDFJSDev === 'undefined' || PDFJSDev.test('GENERIC')) {
           adjustTop = adjustTop && window.getComputedStyle(end).
             getPropertyValue('-moz-user-select') !== 'none';
         }
         if (adjustTop) {
-          var divBounds = div.getBoundingClientRect();
-          var r = Math.max(0, (e.pageY - divBounds.top) / divBounds.height);
+          let divBounds = div.getBoundingClientRect();
+          let r = Math.max(0, (evt.pageY - divBounds.top) / divBounds.height);
           end.style.top = (r * 100).toFixed(2) + '%';
         }
-        }
-        end.classList.add('active');
-      });
-
-      div.addEventListener('mouseup', function (e) {
-        if (self.enhanceTextSelection && self.textLayerRenderTask) {
-          if (typeof PDFJSDev === 'undefined' ||
-              !PDFJSDev.test('FIREFOX || MOZCENTRAL')) {
-            expandDivsTimer = setTimeout(function() {
-              if (self.textLayerRenderTask) {
-                self.textLayerRenderTask.expandTextDivs(false);
-              }
-              expandDivsTimer = null;
-            }, EXPAND_DIVS_TIMEOUT);
-          } else {
-            self.textLayerRenderTask.expandTextDivs(false);
-          }
-          return;
-        }
-        var end = div.querySelector('.endOfContent');
-        if (!end) {
-          return;
-        }
+      }
+      end.classList.add('active');
+    });
+
+    div.addEventListener('mouseup', () => {
+      if (this.enhanceTextSelection && this.textLayerRenderTask) {
         if (typeof PDFJSDev === 'undefined' ||
             !PDFJSDev.test('FIREFOX || MOZCENTRAL')) {
-          end.style.top = '';
+          expandDivsTimer = setTimeout(() => {
+            if (this.textLayerRenderTask) {
+              this.textLayerRenderTask.expandTextDivs(false);
+            }
+            expandDivsTimer = null;
+          }, EXPAND_DIVS_TIMEOUT);
+        } else {
+          this.textLayerRenderTask.expandTextDivs(false);
         }
-        end.classList.remove('active');
-      });
-    },
-  };
-  return TextLayerBuilder;
-})();
+        return;
+      }
+
+      let end = div.querySelector('.endOfContent');
+      if (!end) {
+        return;
+      }
+      if (typeof PDFJSDev === 'undefined' ||
+          !PDFJSDev.test('FIREFOX || MOZCENTRAL')) {
+        end.style.top = '';
+      }
+      end.classList.remove('active');
+    });
+  }
+}
 
 /**
- * @constructor
  * @implements IPDFTextLayerFactory
  */
-function DefaultTextLayerFactory() {}
-DefaultTextLayerFactory.prototype = {
+class DefaultTextLayerFactory {
   /**
    * @param {HTMLDivElement} textLayerDiv
    * @param {number} pageIndex
@@ -417,8 +415,8 @@ DefaultTextLayerFactory.prototype = {
       viewport,
       enhanceTextSelection,
     });
-  },
-};
+  }
+}
 
 export {
   TextLayerBuilder,