text: /* 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.
   */
   /*globals watchScroll, PDFPageView, UNKNOWN_SCALE,
             SCROLLBAR_PADDING, VERTICAL_PADDING, MAX_AUTO_SCALE, CSS_UNITS,
             DEFAULT_SCALE, scrollIntoView, getVisibleElements, RenderingStates,
             PDFJS, Promise, TextLayerBuilder, PDFRenderingQueue,
             AnnotationsLayerBuilder, DEFAULT_SCALE_VALUE */
  
  'use strict';
  
  var PresentationModeState = {
    UNKNOWN: 0,
    NORMAL: 1,
    CHANGING: 2,
    FULLSCREEN: 3,
  };
  
  var IGNORE_CURRENT_POSITION_ON_ZOOM = false;
  var DEFAULT_CACHE_SIZE = 10;
  
  //#include pdf_rendering_queue.js
  //#include pdf_page_view.js
  //#include text_layer_builder.js
  //#include annotations_layer_builder.js
  
  /**
   * @typedef {Object} PDFViewerOptions
   * @property {HTMLDivElement} container - The container for the viewer element.
   * @property {HTMLDivElement} viewer - (optional) The viewer element.
   * @property {IPDFLinkService} linkService - The navigation/linking service.
   * @property {PDFRenderingQueue} renderingQueue - (optional) The rendering
   *   queue object.
   * @property {boolean} removePageBorders - (optional) Removes the border shadow
   *   around the pages. The default is false.
   */
  
  /**
   * Simple viewer control to display PDF content/pages.
   * @class
   * @implements {IRenderableView}
   */
  var PDFViewer = (function pdfViewer() {
    function PDFPageViewBuffer(size) {
      var data = [];
      this.push = function cachePush(view) {
        var i = data.indexOf(view);
        if (i >= 0) {
          data.splice(i, 1);
        }
        data.push(view);
        if (data.length > size) {
          data.shift().destroy();
        }
      };
      this.resize = function (newSize) {
        size = newSize;
        while (data.length > size) {
          data.shift().destroy();
        }
      };
    }
  
    function isSameScale(oldScale, newScale) {
      if (newScale === oldScale) {
        return true;
      }
      if (Math.abs(newScale - oldScale) < 1e-15) {
        // Prevent unnecessary re-rendering of all pages when the scale
        // changes only because of limited numerical precision.
        return true;
      }
      return false;
    }
  
    /**
     * @constructs PDFViewer
     * @param {PDFViewerOptions} options
     */
    function PDFViewer(options) {
      this.container = options.container;
      this.viewer = options.viewer || options.container.firstElementChild;
      this.linkService = options.linkService || new SimpleLinkService();
      this.removePageBorders = options.removePageBorders || false;
  
      this.defaultRenderingQueue = !options.renderingQueue;
      if (this.defaultRenderingQueue) {
        // Custom rendering queue is not specified, using default one
        this.renderingQueue = new PDFRenderingQueue();
        this.renderingQueue.setViewer(this);
      } else {
        this.renderingQueue = options.renderingQueue;
      }
  
      this.scroll = watchScroll(this.container, this._scrollUpdate.bind(this));
      this.updateInProgress = false;
      this.presentationModeState = PresentationModeState.UNKNOWN;
      this._resetView();
  
      if (this.removePageBorders) {
        this.viewer.classList.add('removePageBorders');
      }
    }
  
    PDFViewer.prototype = /** @lends PDFViewer.prototype */{
      get pagesCount() {
        return this._pages.length;
      },
  
      getPageView: function (index) {
        return this._pages[index];
      },
  
      get currentPageNumber() {
        return this._currentPageNumber;
      },
  
      set currentPageNumber(val) {
        if (!this.pdfDocument) {
          this._currentPageNumber = val;
          return;
        }
  
        var event = document.createEvent('UIEvents');
        event.initUIEvent('pagechange', true, true, window, 0);
        event.updateInProgress = this.updateInProgress;
  
        if (!(0 < val && val <= this.pagesCount)) {
          event.pageNumber = this._currentPageNumber;
          event.previousPageNumber = val;
          this.container.dispatchEvent(event);
          return;
        }
  
        event.previousPageNumber = this._currentPageNumber;
        this._currentPageNumber = val;
        event.pageNumber = val;
        this.container.dispatchEvent(event);
  
        // Check if the caller is `PDFViewer_update`, to avoid breaking scrolling.
        if (this.updateInProgress) {
          return;
        }
        this.scrollPageIntoView(val);
      },
  
      /**
       * @returns {number}
       */
      get currentScale() {
        return this._currentScale !== UNKNOWN_SCALE ? this._currentScale :
                                                      DEFAULT_SCALE;
      },
  
      /**
       * @param {number} val - Scale of the pages in percents.
       */
      set currentScale(val) {
        if (isNaN(val))  {
          throw new Error('Invalid numeric scale');
        }
        if (!this.pdfDocument) {
          this._currentScale = val;
          this._currentScaleValue = val !== UNKNOWN_SCALE ? val.toString() : null;
          return;
        }
        this._setScale(val, false);
      },
  
      /**
       * @returns {string}
       */
      get currentScaleValue() {
        return this._currentScaleValue;
      },
  
      /**
       * @param val - The scale of the pages (in percent or predefined value).
       */
      set currentScaleValue(val) {
        if (!this.pdfDocument) {
          this._currentScale = isNaN(val) ? UNKNOWN_SCALE : val;
          this._currentScaleValue = val;
          return;
        }
        this._setScale(val, false);
      },
  
      /**
       * @returns {number}
       */
      get pagesRotation() {
        return this._pagesRotation;
      },
  
      /**
       * @param {number} rotation - The rotation of the pages (0, 90, 180, 270).
       */
      set pagesRotation(rotation) {
        this._pagesRotation = rotation;
  
        for (var i = 0, l = this._pages.length; i < l; i++) {
          var pageView = this._pages[i];
          pageView.update(pageView.scale, rotation);
        }
  
        this._setScale(this._currentScaleValue, true);
  
        if (this.defaultRenderingQueue) {
          this.update();
        }
      },
  
      /**
       * @param pdfDocument {PDFDocument}
       */
      setDocument: function (pdfDocument) {
        if (this.pdfDocument) {
          this._resetView();
        }
  
        this.pdfDocument = pdfDocument;
        if (!pdfDocument) {
          return;
        }
  
        var pagesCount = pdfDocument.numPages;
        var self = this;
  
        var resolvePagesPromise;
        var pagesPromise = new Promise(function (resolve) {
          resolvePagesPromise = resolve;
        });
        this.pagesPromise = pagesPromise;
        pagesPromise.then(function () {
          var event = document.createEvent('CustomEvent');
          event.initCustomEvent('pagesloaded', true, true, {
            pagesCount: pagesCount
          });
          self.container.dispatchEvent(event);
        });
  
        var isOnePageRenderedResolved = false;
        var resolveOnePageRendered = null;
        var onePageRendered = new Promise(function (resolve) {
          resolveOnePageRendered = resolve;
        });
        this.onePageRendered = onePageRendered;
  
        var bindOnAfterAndBeforeDraw = function (pageView) {
          pageView.onBeforeDraw = function pdfViewLoadOnBeforeDraw() {
            // Add the page to the buffer at the start of drawing. That way it can
            // be evicted from the buffer and destroyed even if we pause its
            // rendering.
            self._buffer.push(this);
          };
          // when page is painted, using the image as thumbnail base
          pageView.onAfterDraw = function pdfViewLoadOnAfterDraw() {
            if (!isOnePageRenderedResolved) {
              isOnePageRenderedResolved = true;
              resolveOnePageRendered();
            }
          };
        };
  
        var firstPagePromise = pdfDocument.getPage(1);
        this.firstPagePromise = firstPagePromise;
  
        // Fetch a single page so we can get a viewport that will be the default
        // viewport for all pages
        return firstPagePromise.then(function(pdfPage) {
          var scale = this.currentScale;
          var viewport = pdfPage.getViewport(scale * CSS_UNITS);
          for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
            var textLayerFactory = null;
            if (!PDFJS.disableTextLayer) {
              textLayerFactory = this;
            }
            var pageView = new PDFPageView({
              container: this.viewer,
              id: pageNum,
              scale: scale,
              defaultViewport: viewport.clone(),
              renderingQueue: this.renderingQueue,
              textLayerFactory: textLayerFactory,
              annotationsLayerFactory: this
            });
            bindOnAfterAndBeforeDraw(pageView);
            this._pages.push(pageView);
          }
  
          var linkService = this.linkService;
  
          // Fetch all the pages since the viewport is needed before printing
          // starts to create the correct size canvas. Wait until one page is
          // rendered so we don't tie up too many resources early on.
          onePageRendered.then(function () {
            if (!PDFJS.disableAutoFetch) {
              var getPagesLeft = pagesCount;
              for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
                pdfDocument.getPage(pageNum).then(function (pageNum, pdfPage) {
                  var pageView = self._pages[pageNum - 1];
                  if (!pageView.pdfPage) {
                    pageView.setPdfPage(pdfPage);
                  }
                  linkService.cachePageRef(pageNum, pdfPage.ref);
                  getPagesLeft--;
                  if (!getPagesLeft) {
                    resolvePagesPromise();
                  }
                }.bind(null, pageNum));
              }
            } else {
              // XXX: Printing is semi-broken with auto fetch disabled.
              resolvePagesPromise();
            }
          });
  
          var event = document.createEvent('CustomEvent');
          event.initCustomEvent('pagesinit', true, true, null);
          self.container.dispatchEvent(event);
  
          if (this.defaultRenderingQueue) {
            this.update();
          }
  
          if (this.findController) {
            this.findController.resolveFirstPage();
          }
        }.bind(this));
      },
  
      _resetView: function () {
        this._pages = [];
        this._currentPageNumber = 1;
        this._currentScale = UNKNOWN_SCALE;
        this._currentScaleValue = null;
        this._buffer = new PDFPageViewBuffer(DEFAULT_CACHE_SIZE);
        this._location = null;
        this._pagesRotation = 0;
        this._pagesRequests = [];
  
        var container = this.viewer;
        while (container.hasChildNodes()) {
          container.removeChild(container.lastChild);
        }
      },
  
      _scrollUpdate: function PDFViewer_scrollUpdate() {
        if (this.pagesCount === 0) {
          return;
        }
        this.update();
        for (var i = 0, ii = this._pages.length; i < ii; i++) {
          this._pages[i].updatePosition();
        }
      },
  
      _setScaleDispatchEvent: function pdfViewer_setScaleDispatchEvent(
          newScale, newValue, preset) {
        var event = document.createEvent('UIEvents');
        event.initUIEvent('scalechange', true, true, window, 0);
        event.scale = newScale;
        if (preset) {
          event.presetValue = newValue;
        }
        this.container.dispatchEvent(event);
      },
  
      _setScaleUpdatePages: function pdfViewer_setScaleUpdatePages(
          newScale, newValue, noScroll, preset) {
        this._currentScaleValue = newValue;
  
        if (isSameScale(this._currentScale, newScale)) {
          if (preset) {
            this._setScaleDispatchEvent(newScale, newValue, true);
          }
          return;
        }
  
        for (var i = 0, ii = this._pages.length; i < ii; i++) {
          this._pages[i].update(newScale);
        }
        this._currentScale = newScale;
  
        if (!noScroll) {
          var page = this._currentPageNumber, dest;
          if (this._location && !IGNORE_CURRENT_POSITION_ON_ZOOM &&
              !(this.isInPresentationMode || this.isChangingPresentationMode)) {
            page = this._location.pageNumber;
            dest = [null, { name: 'XYZ' }, this._location.left,
                    this._location.top, null];
          }
          this.scrollPageIntoView(page, dest);
        }
  
        this._setScaleDispatchEvent(newScale, newValue, preset);
  
        if (this.defaultRenderingQueue) {
          this.update();
        }
      },
  
      _setScale: function pdfViewer_setScale(value, noScroll) {
        var scale = parseFloat(value);
  
        if (scale > 0) {
          this._setScaleUpdatePages(scale, value, noScroll, false);
        } else {
          var currentPage = this._pages[this._currentPageNumber - 1];
          if (!currentPage) {
            return;
          }
          var hPadding = (this.isInPresentationMode || this.removePageBorders) ?
            0 : SCROLLBAR_PADDING;
          var vPadding = (this.isInPresentationMode || this.removePageBorders) ?
            0 : VERTICAL_PADDING;
          var pageWidthScale = (this.container.clientWidth - hPadding) /
                               currentPage.width * currentPage.scale;
          var pageHeightScale = (this.container.clientHeight - vPadding) /
                                currentPage.height * currentPage.scale;
          switch (value) {
            case 'page-actual':
              scale = 1;
              break;
            case 'page-width':
              scale = pageWidthScale;
              break;
            case 'page-height':
              scale = pageHeightScale;
              break;
            case 'page-fit':
              scale = Math.min(pageWidthScale, pageHeightScale);
              break;
            case 'auto':
              var isLandscape = (currentPage.width > currentPage.height);
              // For pages in landscape mode, fit the page height to the viewer
              // *unless* the page would thus become too wide to fit horizontally.
              var horizontalScale = isLandscape ?
                Math.min(pageHeightScale, pageWidthScale) : pageWidthScale;
              scale = Math.min(MAX_AUTO_SCALE, horizontalScale);
              break;
            default:
              console.error('pdfViewSetScale: \'' + value +
                '\' is an unknown zoom value.');
              return;
          }
          this._setScaleUpdatePages(scale, value, noScroll, true);
        }
      },
  
      /**
       * Scrolls page into view.
       * @param {number} pageNumber
       * @param {Array} dest - (optional) original PDF destination array:
       *   <page-ref> </XYZ|FitXXX> <args..>
       */
      scrollPageIntoView: function PDFViewer_scrollPageIntoView(pageNumber,
                                                                dest) {
        if (!this.pdfDocument) {
          return;
        }
        
        var pageView = this._pages[pageNumber - 1];
  
        if (this.isInPresentationMode) {
          if (this._currentPageNumber !== pageView.id) {
            // Avoid breaking getVisiblePages in presentation mode.
            this.currentPageNumber = pageView.id;
            return;
          }
          dest = null;
          // Fixes the case when PDF has different page sizes.
          this._setScale(this._currentScaleValue, true);
        }
        if (!dest) {
          scrollIntoView(pageView.div);
          return;
        }
  
        var x = 0, y = 0;
        var width = 0, height = 0, widthScale, heightScale;
        var changeOrientation = (pageView.rotation % 180 === 0 ? false : true);
        var pageWidth = (changeOrientation ? pageView.height : pageView.width) /
          pageView.scale / CSS_UNITS;
        var pageHeight = (changeOrientation ? pageView.width : pageView.height) /
          pageView.scale / CSS_UNITS;
        var scale = 0;
        switch (dest[1].name) {
          case 'XYZ':
            x = dest[2];
            y = dest[3];
            scale = dest[4];
            // If x and/or y coordinates are not supplied, default to
            // _top_ left of the page (not the obvious bottom left,
            // since aligning the bottom of the intended page with the
            // top of the window is rarely helpful).
            x = x !== null ? x : 0;
            y = y !== null ? y : pageHeight;
            break;
          case 'Fit':
          case 'FitB':
            scale = 'page-fit';
            break;
          case 'FitH':
          case 'FitBH':
            y = dest[2];
            scale = 'page-width';
            // According to the PDF spec, section 12.3.2.2, a `null` value in the
            // parameter should maintain the position relative to the new page.
            if (y === null && this._location) {
              x = this._location.left;
              y = this._location.top;
            }
            break;
          case 'FitV':
          case 'FitBV':
            x = dest[2];
            width = pageWidth;
            height = pageHeight;
            scale = 'page-height';
            break;
          case 'FitR':
            x = dest[2];
            y = dest[3];
            width = dest[4] - x;
            height = dest[5] - y;
            var hPadding = this.removePageBorders ? 0 : SCROLLBAR_PADDING;
            var vPadding = this.removePageBorders ? 0 : VERTICAL_PADDING;
  
            widthScale = (this.container.clientWidth - hPadding) /
              width / CSS_UNITS;
            heightScale = (this.container.clientHeight - vPadding) /
              height / CSS_UNITS;
            scale = Math.min(Math.abs(widthScale), Math.abs(heightScale));
            break;
          default:
            return;
        }
  
        if (scale && scale !== this._currentScale) {
          this.currentScaleValue = scale;
        } else if (this._currentScale === UNKNOWN_SCALE) {
          this.currentScaleValue = DEFAULT_SCALE_VALUE;
        }
  
        if (scale === 'page-fit' && !dest[4]) {
          scrollIntoView(pageView.div);
          return;
        }
  
        var boundingRect = [
          pageView.viewport.convertToViewportPoint(x, y),
          pageView.viewport.convertToViewportPoint(x + width, y + height)
        ];
        var left = Math.min(boundingRect[0][0], boundingRect[1][0]);
        var top = Math.min(boundingRect[0][1], boundingRect[1][1]);
  
        scrollIntoView(pageView.div, { left: left, top: top });
      },
  
      _updateLocation: function (firstPage) {
        var currentScale = this._currentScale;
        var currentScaleValue = this._currentScaleValue;
        var normalizedScaleValue =
          parseFloat(currentScaleValue) === currentScale ?
          Math.round(currentScale * 10000) / 100 : currentScaleValue;
  
        var pageNumber = firstPage.id;
        var pdfOpenParams = '#page=' + pageNumber;
        pdfOpenParams += '&zoom=' + normalizedScaleValue;
        var currentPageView = this._pages[pageNumber - 1];
        var container = this.container;
        var topLeft = currentPageView.getPagePoint(
          (container.scrollLeft - firstPage.x),
          (container.scrollTop - firstPage.y));
        var intLeft = Math.round(topLeft[0]);
        var intTop = Math.round(topLeft[1]);
        pdfOpenParams += ',' + intLeft + ',' + intTop;
  
        this._location = {
          pageNumber: pageNumber,
          scale: normalizedScaleValue,
          top: intTop,
          left: intLeft,
          pdfOpenParams: pdfOpenParams
        };
      },
  
      update: function PDFViewer_update() {
        var visible = this._getVisiblePages();
        var visiblePages = visible.views;
        if (visiblePages.length === 0) {
          return;
        }
  
        this.updateInProgress = true;
  
        var suggestedCacheSize = Math.max(DEFAULT_CACHE_SIZE,
            2 * visiblePages.length + 1);
        this._buffer.resize(suggestedCacheSize);
  
        this.renderingQueue.renderHighestPriority(visible);
  
        var currentId = this._currentPageNumber;
        var firstPage = visible.first;
  
        for (var i = 0, ii = visiblePages.length, stillFullyVisible = false;
             i < ii; ++i) {
          var page = visiblePages[i];
  
          if (page.percent < 100) {
            break;
          }
          if (page.id === currentId) {
            stillFullyVisible = true;
            break;
          }
        }
  
        if (!stillFullyVisible) {
          currentId = visiblePages[0].id;
        }
  
        if (!this.isInPresentationMode) {
          this.currentPageNumber = currentId;
        }
  
        this._updateLocation(firstPage);
  
        this.updateInProgress = false;
  
        var event = document.createEvent('UIEvents');
        event.initUIEvent('updateviewarea', true, true, window, 0);
        event.location = this._location;
        this.container.dispatchEvent(event);
      },
  
      containsElement: function (element) {
        return this.container.contains(element);
      },
  
      focus: function () {
        this.container.focus();
      },
  
      get isInPresentationMode() {
        return this.presentationModeState === PresentationModeState.FULLSCREEN;
      },
  
      get isChangingPresentationMode() {
        return this.presentationModeState === PresentationModeState.CHANGING;
      },
  
      get isHorizontalScrollbarEnabled() {
        return (this.isInPresentationMode ?
          false : (this.container.scrollWidth > this.container.clientWidth));
      },
  
      _getVisiblePages: function () {
        if (!this.isInPresentationMode) {
          return getVisibleElements(this.container, this._pages, true);
        } else {
          // The algorithm in getVisibleElements doesn't work in all browsers and
          // configurations when presentation mode is active.
          var visible = [];
          var currentPage = this._pages[this._currentPageNumber - 1];
          visible.push({ id: currentPage.id, view: currentPage });
          return { first: currentPage, last: currentPage, views: visible };
        }
      },
  
      cleanup: function () {
        for (var i = 0, ii = this._pages.length; i < ii; i++) {
          if (this._pages[i] &&
              this._pages[i].renderingState !== RenderingStates.FINISHED) {
            this._pages[i].reset();
          }
        }
      },
  
      /**
       * @param {PDFPageView} pageView
       * @returns {PDFPage}
       * @private
       */
      _ensurePdfPageLoaded: function (pageView) {
        if (pageView.pdfPage) {
          return Promise.resolve(pageView.pdfPage);
        }
        var pageNumber = pageView.id;
        if (this._pagesRequests[pageNumber]) {
          return this._pagesRequests[pageNumber];
        }
        var promise = this.pdfDocument.getPage(pageNumber).then(
            function (pdfPage) {
          pageView.setPdfPage(pdfPage);
          this._pagesRequests[pageNumber] = null;
          return pdfPage;
        }.bind(this));
        this._pagesRequests[pageNumber] = promise;
        return promise;
      },
  
      forceRendering: function (currentlyVisiblePages) {
        var visiblePages = currentlyVisiblePages || this._getVisiblePages();
        var pageView = this.renderingQueue.getHighestPriority(visiblePages,
                                                              this._pages,
                                                              this.scroll.down);
        if (pageView) {
          this._ensurePdfPageLoaded(pageView).then(function () {
            this.renderingQueue.renderView(pageView);
          }.bind(this));
          return true;
        }
        return false;
      },
  
      getPageTextContent: function (pageIndex) {
        return this.pdfDocument.getPage(pageIndex + 1).then(function (page) {
          return page.getTextContent();
        });
      },
  
      /**
       * @param {HTMLDivElement} textLayerDiv
       * @param {number} pageIndex
       * @param {PageViewport} viewport
       * @returns {TextLayerBuilder}
       */
      createTextLayerBuilder: function (textLayerDiv, pageIndex, viewport) {
        return new TextLayerBuilder({
          textLayerDiv: textLayerDiv,
          pageIndex: pageIndex,
          viewport: viewport,
          findController: this.isInPresentationMode ? null : this.findController
        });
      },
  
      /**
       * @param {HTMLDivElement} pageDiv
       * @param {PDFPage} pdfPage
       * @returns {AnnotationsLayerBuilder}
       */
      createAnnotationsLayerBuilder: function (pageDiv, pdfPage) {
        return new AnnotationsLayerBuilder({
          pageDiv: pageDiv,
          pdfPage: pdfPage,
          linkService: this.linkService
        });
      },
  
      setFindController: function (findController) {
        this.findController = findController;
      },
    };
  
    return PDFViewer;
  })();
  
  var SimpleLinkService = (function SimpleLinkServiceClosure() {
    function SimpleLinkService() {}
  
    SimpleLinkService.prototype = {
      /**
       * @returns {number}
       */
      get page() {
        return 0;
      },
      /**
       * @param {number} value
       */
      set page(value) {},
      /**
       * @param dest - The PDF destination object.
       */
      navigateTo: function (dest) {},
      /**
       * @param dest - The PDF destination object.
       * @returns {string} The hyperlink to the PDF object.
       */
      getDestinationHash: function (dest) {
        return '#';
      },
      /**
       * @param hash - The PDF parameters/hash.
       * @returns {string} The hyperlink to the PDF object.
       */
      getAnchorUrl: function (hash) {
        return '#';
      },
      /**
       * @param {string} hash
       */
      setHash: function (hash) {},
      /**
       * @param {string} action
       */
      executeNamedAction: function (action) {},
      /**
       * @param {number} pageNum - page number.
       * @param {Object} pageRef - reference to the page.
       */
      cachePageRef: function (pageNum, pageRef) {}
    };
    return SimpleLinkService;
  })();
  