app.js

/* eslint-disable prettier/prettier */
import { BookmarkLayer } from "./bookmarks.js";
import { DownloadManager } from "./download_manager.js";
import { getDocument } from "./pdfjs-lib.js";
import { MessageBox } from "./message_box.js";
import { MultiViewer } from "./multi_viewer.js";
import { OverlayManager } from "./overlay_manager.js";
import { PasswordPrompt } from "./password_prompt.js";
import { PDFDocumentProperties } from "./pdf_document_properties.js";
import { PDFLinkService } from "./pdf_link_service.js";
import { PdfPrintService } from "./pdf_print_service.js";

import { SingleViewer } from "./single_viewer.js";
import { TextSelectionOptions } from "./text_selection_options.js";

import { Toolbar } from "./toolbar.js";
import { TWPdfDocument } from "./pdf_document.js";

import { ViewerType } from "./base_viewer.js";

// eslint-disable-next-line sort-imports
import { tw2018func_getPDFFileNameFromURL, tw_focus } from "./util.js";
import { ZoomService } from "./zoom_service.js";

/**
 * TWViewerPdfApp is the main class for the viewer application.
 */
class TWViewerPdfApp {
  constructor(options, mainDiv) {
    this._setElements(mainDiv);
    this.overlayManager = new OverlayManager();

    this._totalInit = 0;
    this._initialPageNumber = options.pageNumber || 1;

    this._totalRendered = 0;
    this._printService = null;
    this._isMobile = false;
    this.texts = this._setTexts();
    this._msgBox = new MessageBox({
      overlayName: "tw2018elem_messageOverlay",
      container: this.elements.tw2018elem_messageOverlay,
      app: this,
    });

    this._showErrorPopup = options.showErrorPopup || false;

    this._documentProperties = {
      app: this,
      overlayName: "tw2018elem_documentPropertiesOverlay",
      container: this.elements.tw2018elem_documentPropertiesOverlay,
      closeButton: this.elements.tw2018elem_documentPropertiesClose,
      fields: {
        fileName: this.elements.tw2018elem_fileNameField,
        fileSize: this.elements.tw2018elem_fileSizeField,
        title: this.elements.tw2018elem_titleField,
        author: this.elements.tw2018elem_authorField,
        subject: this.elements.tw2018elem_subjectField,
        keywords: this.elements.tw2018elem_keywordsField,
        creationDate: this.elements.tw2018elem_creationDateField,
        modificationDate: this.elements.tw2018elem_modificationDateField,
        creator: this.elements.tw2018elem_creatorField,
        producer: this.elements.tw2018elem_producerField,
        version: this.elements.tw2018elem_versionField,
        pageCount: this.elements.tw2018elem_pageCountField,
        pageSize: this.elements.tw2018elem_pageSizeField,
      },
    };
    this._pdfDocumentProperties = new PDFDocumentProperties(
      this._documentProperties
    );

    this._activePasswordPrompt = null;

    this.downloadManager = new DownloadManager();
    this.linkService = new PDFLinkService();

    /**
     * Bookmark layer manager
     * @member {BookmarkLayer}
     */
    this.bookmarkLayer = new BookmarkLayer(
      this,
      options.bookmark.showBookmarksOnOpen,
      options.bookmark.preserveBookmarksState,
      options.bookmark.bookmarksFullyExpanded
    );

    /**
     * @member {HTMLdivElement} - The main container for the viewer app.
     */
    this.viewerDiv = mainDiv;

    /**
     * @member {number} - Id of the viewer app.
     */
    this.id = mainDiv.id;

    /**
     * @member {object} - Initial viewer app options.
     */
    this.options = options;

    this.viewer =
      options.viewerType === ViewerType.SINGLE_VIEW
        ? new SingleViewer(this)
        : new MultiViewer(this);

    /**
     * @member {PDFDocument} - The loaded PDF document.
     */
    this.pdfDocument = new TWPdfDocument();

    /**
     * @member {TextSelectionOptions} - Text selection options.
     */
    this.textSelectionOptions = new TextSelectionOptions(this);
    this.textSelectionOptions.disabled = options.textSelectionDisabled || false;

    this.visibleAreaHeight =
      this.elements.tw2018elem_viewerContainer.getBoundingClientRect().height;
    this.visibleAreaWidth =
      this.elements.tw2018elem_viewerContainer.getBoundingClientRect().width;

    this._isMobile = this.isMobile;
    if (!this._isMobile) {
      this.elements.tw2018elem_bookmarksDivDesktop.style.height =
        this.elements.tw2018elem_viewerContainer.getBoundingClientRect()
          .height -
        1 +
        "px";
    }

    /**
     * @member {ZoomService} - Zoom service.
     */
    this.zoomService = new ZoomService(
      this,
      options.zoom.zoomValue,
      options.zoom.isZoomFitToPage,
      options.texts.miscTexts.zoomActualSize,
      options.texts.miscTexts.zoomPageLevel
    );

    /**
     * @member {Toolbar} - Toolbar.
     */
    this.toolbar = new Toolbar(this, options, this.zoomService);

    this._bindEvents();
    this.dispatchTWEvent("twWiewerInitialized", { app: this }, document);
  }

  // #region private functions
  _bindEvents() {
    window.addEventListener("resize", this._viewerContainerResize.bind(this));

    const me = this;
    this.elements.tw2018elem_fileInput.addEventListener(
      "change",
      function (evt) {
        me.loadDocumentFromClick(me.elements.tw2018elem_fileInput.files[0]);
      }
    );

    this.elements.tw2018elem_mainContainer.addEventListener(
      "dragover",
      function (event) {
        event.preventDefault();
      }
    );

    this.elements.tw2018elem_mainContainer.addEventListener(
      "drop",
      function (ev) {
        console.log("ondrop", ev);
        ev.preventDefault();
        if (ev.dataTransfer.items) {
          // Use DataTransferItemList interface to access the file(s)
          for (let i = 0; i < ev.dataTransfer.items.length; i++) {
            // If dropped items aren't files, reject them
            if (ev.dataTransfer.items[i].kind === "file") {
              const file = ev.dataTransfer.items[i].getAsFile();
              console.log("1 ... file[" + i + "].name = " + file.name);
              me.loadDocumentFromFile(file);
            }
          }
        } else {
          // Use DataTransfer interface to access the file(s)
          for (let i = 0; i < ev.dataTransfer.files.length; i++) {
            console.log(
              "2 ... file[" + i + "].name = " + ev.dataTransfer.files[i].name
            );
            me.loadDocumentFromFile(ev.dataTransfer.files[i]);
          }
        }
      }
    );

    this.elements.tw2018elem_viewerContainer.addEventListener(
      "mousewheel",
      this._mouseWheelHandler.bind(this),
      false
    ); // IE9. Chrome, Safari, Opera
    this.elements.tw2018elem_viewerContainer.addEventListener(
      "DOMMouseScroll",
      this._mouseWheelHandler.bind(this),
      false
    ); // FF
    this.elements.tw2018elem_viewerContainer.addEventListener(
      "scroll",
      this._multiViewScroll.bind(this)
    );

    this.elements.tw2018elem_passwordPromptField.addEventListener(
      "keydown",
      function (event) {
        switch (event.keyCode) {
          case 13: // Enter
            me.elements.tw2018elem_passwordPromptOKBtn.click();
            break;
          case 27: // Escape
            me.elements.tw2018elem_activePasswordPrompt.hide();
        }
      }
    );
  }

  _showDocumentLoading(val) {
    this.elements.tw2018elem_viewerContainer.classList.toggle(
      "tw2018css_loading",
      val
    );
  }

  _activateView(viewer) {
    const currentViewerSettings = this.viewer.getCurrentSettings();
    this.viewer = viewer;
    this.viewer.applySettings(currentViewerSettings);
    this.pdfDocument.resetTextLayer();
    this.toolbar.setFindController();
    this.toolbar.findBar.reset();
    this.linkService.setViewer(this.viewer);
    this.zoomService.calculateZoom();
    this.zoomService.executeZoom(false);
    this.viewer.show();
  }

  _setElements(mainDiv) {
    const elementsClass = [
      "tw2018elem_zoominBtn",
      "tw2018elem_zoomoutBtn",
      "tw2018elem_bookmarksDivMobile",
      "tw2018elem_bookmarksDivDesktop",
      "tw2018elem_viewerContainer",
      "tw2018elem_toolbar",
      "tw2018elem_passwordPromptOverlay",
      "tw2018elem_passwordPromptField",
      "tw2018elem_passwordPromptDiv",
      "tw2018elem_passwordPromptOKBtn",
      "tw2018elem_passwordPromptCancelBtn",
      "tw2018elem_activePasswordPrompt",
      "tw2018elem_totalPages",
      "tw2018elem_pageNumber",
      "tw2018elem_findMsgClose",
      "tw2018elem_findOptionsClose",
      "tw2018elem_openBtn",
      "tw2018elem_previousvisitedBtn",
      "tw2018elem_nextvisitedBtn",
      "tw2018elem_searchTerm",
      "tw2018elem_searchBtn",
      "tw2018elem_printBtn",
      "tw2018elem_documentinfoBtn",
      "tw2018elem_optionsBtn",
      "tw2018elem_searchOptions",
      "tw2018elem_downloadBtn",
      "tw2018elem_closeBtn",
      "tw2018elem_zoomDropDown",
      "tw2018elem_zoomDropDownValue",
      "tw2018elem_zoomValues",
      "tw2018elem_zoomValuesDropDown",
      "tw2018elem_zoomValuesDropDownClose",
      "tw2018elem_counterclockwiseBtn",
      "tw2018elem_clockwiseBtn",
      "tw2018elem_bookmarkBtn",
      "tw2018elem_zoomDropDownArrow",
      "tw2018elem_nextpageBtn",
      "tw2018elem_previouspageBtn",
      "tw2018elem_singlepageBtn",
      "tw2018elem_multipageBtn",
      "tw2018elem_sepTotalPages",
      "tw2018elem_searchDiv",
      "tw2018elem_multiViewer",
      "tw2018elem_singleViewer",
      "tw2018elem_messageOverlay",
      "tw2018elem_documentPropertiesOverlay",
      "tw2018elem_documentPropertiesClose",
      "tw2018elem_fileNameField",
      "tw2018elem_fileSizeField",
      "tw2018elem_titleField",
      "tw2018elem_authorField",
      "tw2018elem_subjectField",
      "tw2018elem_keywordsField",
      "tw2018elem_creationDateField",
      "tw2018elem_modificationDateField",
      "tw2018elem_creatorField",
      "tw2018elem_producerField",
      "tw2018elem_versionField",
      "tw2018elem_pageCountField",
      "tw2018elem_pageSizeField",
      "tw2018elem_bookmarksDivFrame",
      "tw2018elem_bookmarksDiv",
      "tw2018elem_msgClose",
      "tw2018elem_msg",
      "tw2018elem_msgBox",
      "tw2018elem_printCanvas",
      "tw2018elem_printLayer",
      "tw2018elem_findHighlightAll",
      "tw2018elem_findMatchCase",
      "tw2018elem_findMsg",
      "tw2018elem_findMsgBox",
      "tw2018elem_findWholeWord",
      "tw2018elem_findResultsCount",
      "tw2018elem_previousfindBtn",
      "tw2018elem_nextfindBtn",
      "tw2018elem_searchBtn",
      "tw2018elem_searchResult",
      "tw2018elem_findResultCurrent",
      "tw2018elem_pageNumberDiv",
      "tw2018elem_fileInput",
      "tw2018elem_bookmarkPopupOverlay",
      "tw2018elem_bookmarkPopup",
      "tw2018elem_bookmarkPopupClose",
      "tw2018elem_texts",
    ];

    this.elements = {};
    for (let i = 0; i < elementsClass.length; i++) {
      this.elements[elementsClass[i]] = mainDiv.getElementsByClassName(
        elementsClass[i]
      )[0];
    }

    this.elements.tw2018elem_mainContainer = mainDiv;
  }

  _setTexts() {
    const texts = {};
    const textItems = this.elements.tw2018elem_texts.getElementsByTagName("li");
    for (let i = 0; i < textItems.length; i++) {
      texts[textItems[i].dataset.key] = textItems[i].innerText;
    }
    return texts;
  }

  _mouseWheelHandler(e) {
    if (!e.ctrlKey || !this._scrollEnabled) {
      return;
    }

    this.zoomService.mouseWheelHandler(e);
    e.preventDefault();
  }

  _pageInitialized() {
    this._totalInit++;
    if (this._totalInit === this.viewer.pdfDoc.numPages) {
      this._allPagesInitialized();
    }
  }

  _pageRendered(pageNum) {
    this.dispatchTWEvent(
      "pageRendered",
      {
        pageNum,
        doc: this.pdfDocument,
      },
      this.viewerDiv
    );

    this._totalRendered++;
    if (this._totalRendered >= this.viewer.visiblePagesCount) {
      this._allPagesRendered();
    }
  }

  _allPagesRendered() {
    this._totalRendered = 0;
    if (
      this.viewer.type === ViewerType.MULTI_VIEW &&
      this.viewer.pdfDoc.numPages > 1
    ) {
      const lastPage = document.getElementById(
        this.id + "page" + this.viewer.pdfDoc.numPages
      );

      if (
        lastPage.getBoundingClientRect().height + lastPage.offsetTop <
        this.visibleAreaHeight
      ) {
        const firstPage = document.getElementById(this.id + "page1");
        let totalHeight = firstPage.getBoundingClientRect().height + 10;

        for (let i = 2; i <= this.viewer.pdfDoc.numPages; i++) {
          totalHeight +=
            10 +
            document
              .getElementById(this.id + "page" + i)
              .getBoundingClientRect().height;
        }

        const topPos = (this.visibleAreaHeight - totalHeight - 10) / 2;
        firstPage.style.marginTop = topPos + "px";
        firstPage.style.top = 10;
      }
    }
    this.dispatchTWEvent(
      "allVisiblePagesRendered",
      { doc: this.pdfDocument },
      this.viewerDiv
    );
  }

  _initializeAllPages() {
    this._totalInit = 0;
    const me = this;
    console.time("initPages");
    console.log(this.viewer.pdfDoc);
    for (let i = 1; i <= this.viewer.pdfDoc.numPages; i++) {
      const tempPageNum = i;
      const pagePromise = me.viewer.pdfDoc.getPage(tempPageNum);
      pagePromise.then(function (page) {
        console.log(page);
        me.pdfDocument.pages[tempPageNum - 1] = page;
        page.status = "";
        const viewport = page.getViewport({
          scale: 1,
          rotation: me.viewer.pagesRotation,
        });
        page.originalHeight = viewport.height;
        page.originalWidth = viewport.width;
        me._pageInitialized();
      });
    }
  }

  _multiViewScroll() {
    if (this.viewer.type === ViewerType.SINGLE_VIEW) {
      return; // no effect for single view
    }

    if (!this._scrollEnabled) {
      return;
    }

    const pageNum = this.viewer.getCurrentVisiblePage();
    this.viewer.currentPageNumber = pageNum;
    this.viewer.renderVisiblePages();
    this.toolbar.updateStatus(pageNum);
    this.elements.tw2018elem_pageNumber.value = pageNum;
  }

  _loadDocument(pdfDoc, url) {
    console.time("_loadDocument");
    this.viewer.pdfDoc = pdfDoc;
    this.viewer.currentPageNumber = 1;
    this.viewer.tempPageNumber = 1;
    this.pdfDocument.pages = new Array(this.viewer.pdfDoc.numPages);
    this._pdfDocumentProperties.setDocument(pdfDoc, url);
    this.toolbar.setFindController();
    this.linkService.setDocument(pdfDoc);
    this.linkService.setViewer(this.viewer);
    console.timeEnd("_loadDocument");
    this._initializeAllPages(); // async
    this._scrollEnabled = true;
  }

  _loadDocumentError(reason, url, file, evt, passwordCallback) {
    this._showDocumentLoading(false);
    if (reason.name === "PasswordException") {
      this._activePasswordPrompt = new PasswordPrompt({
        callback: passwordCallback,
        url,
        evt,
        file,
        app: this,
        container: this.elements.tw2018elem_passwordPromptOverlay,
        overlayName: "tw2018elem_passwordPromptOverlay",
        passwordField: this.elements.tw2018elem_passwordPromptField,
        passwordPromptDiv: this.elements.tw2018elem_passwordPromptDiv,
      });
      this._activePasswordPrompt.open();
      return;
    }

    let error = this.texts.load_document_error;
    const reasonTxt = !reason.name ? this.texts.unknown_error : reason.message;
    error += " " + reasonTxt;

    if (this._showErrorPopup) {
      this.showError(error);
    }

    this.dispatchTWEvent(
      "loadDocumentError",
      {
        reason: reasonTxt,
        url,
        file,
        evt,
      },
      this.viewerDiv
    );
  }

  _viewerContainerResize() {
    if (
      this.elements.tw2018elem_optionsBtn.classList.contains(
        "tw2018css_focused"
      )
    ) {
      tw_focus(this.elements.tw2018elem_optionsBtn, false);
      this.elements.tw2018elem_searchOptions.classList.add("tw2018css_hidden");
    }

    this.setElementsSize();
  }

  _allPagesInitialized() {
    this.bookmarkLayer.reset();
    this.bookmarkLayer.loadBookmarks();

    this._totalInit = 0;
    if (this._initialPageNumber) {
      this.viewer.tempPageNumber = this._initialPageNumber;
      this._initialPageNumber = 0;
    }

    if (!this.zoomService.useInitialZoomValueIfSet()) {
      this.renderPdfDocInViewer();
    }

    this.dispatchTWEvent(
      "allPagesInitialized",
      {
        doc: this.pdfDocument,
      },
      this.viewerDiv
    );
    this._showDocumentLoading(false);
    console.timeEnd("initPages");
    this.showMsg("Document is opened.", "info");
  }
  // #endregion

  // #region internal functions
  setElementsSize() {
    if (this.viewer.pdfDoc) {
      if (this.isMobile !== this._isMobile) {
        this._isMobile = this.isMobile;
        this.bookmarkLayer.show(false);
      }

      if (!this._isMobile) {
        this.elements.tw2018elem_bookmarksDivDesktop.style.height =
          this.elements.tw2018elem_viewerContainer.getBoundingClientRect()
            .height -
          1 +
          "px";
      }
    }

    this.visibleAreaHeight =
      this.elements.tw2018elem_viewerContainer.getBoundingClientRect().height;

    let bookmarkDivWidth = 0;
    if (this.viewer.pdfDoc) {
      bookmarkDivWidth = this._isMobile
        ? 0
        : this.elements.tw2018elem_bookmarksDivDesktop.getBoundingClientRect()
            .width;

      if (
        this.elements.tw2018elem_bookmarksDivDesktop.style.display !==
        "inline-block"
      ) {
        bookmarkDivWidth = 0;
      }
    }

    this.visibleAreaWidth =
      this.elements.tw2018elem_toolbar.getBoundingClientRect().width -
      bookmarkDivWidth;
    this.elements.tw2018elem_viewerContainer.style.width =
      this.visibleAreaWidth + "px";

    if (this.viewer.pdfDoc) {
      this.zoomService.fitToPageIfNeeded();
      if (this.zoomService.zoomDropDownOpened) {
        this.zoomService.showZoomDropDown();
      }
    }
  }

  loadDocumentFromClick(file, password) {
    this.loadDocumentFromFile(file, password);
    this.elements.tw2018elem_fileInput.type = "";
    this.elements.tw2018elem_fileInput.value = "";
    this.elements.tw2018elem_fileInput.type = "file";
  }

  dispatchTWEvent(name, details, target) {
    const twEvent = new CustomEvent(name, { detail: details });
    target.dispatchEvent(twEvent);
  }

  get isMobile() {
    const mainWidth =
      this.elements.tw2018elem_mainContainer.getBoundingClientRect().width;
    return mainWidth < 560;
  }

  renderPdfDocInViewer() {
    this.elements.tw2018elem_totalPages.textContent =
      this.viewer.pdfDoc.numPages;
    if (this.viewer.tempPageNumber !== 0) {
      // we want to return to the same page it was before zoom, rotate operation
      this.viewer.currentPageNumber = this.viewer.tempPageNumber;
      this.viewer.tempPageNumber = 0;
    }

    const currentViewerSettings = this.viewer.getCurrentSettings();
    this.viewer.dispose();
    this.viewer =
      this.viewer.type === ViewerType.MULTI_VIEW
        ? new MultiViewer(this)
        : new SingleViewer(this);
    this.viewer.applySettings(currentViewerSettings);
    this.linkService.setViewer(this.viewer);
    this.elements.tw2018elem_pageNumber.value = this.viewer.currentPageNumber;
    this.toolbar.findBar.reset();
    this.zoomService.calculateZoom();
    this.toolbar.enableToolbar(true);
    this.viewer.show();
  }
  // #endregion

  // #region public functions
  /**
   * Opens a file dialog and loads the selected PDF file.
   */
  openThroughDialog() {
    this.elements.tw2018elem_fileInput.click();
  }

  /**
   * Shows the error message.
   * @param {string} error - The error message.
   */
  showError(error) {
    console.log(error);
    if (this._showErrorPopup) {
      this.showMsg(error, "error");
    }
  }

  /**
   * Shows the message.
   * @param {string} msg - The message to show.
   * @param {string} type - Available values: "info", "error".
   */
  showMsg(msg, type) {
    this._msgBox.open(msg, type);
  }

  /**
   * Prints the document by opening a new window.
   */
  print() {
    const printWin = window.open(
      "",
      "",
      "width=" + screen.availWidth + ",height=" + screen.availHeight
    );
    if (!printWin) {
      const error = this.texts.direct_printing_popup;
      this.showError(error);
      console.error(error);
      return;
    }

    this._printService = new PdfPrintService(this);
    this._printService.printWin = printWin;
    const me = this;
    this._printService.renderPrintPages().then(function () {
      if (me._printService.printingCanceled) {
        me.dispatchTWEvent(
          "printingCanceled",
          { doc: me.pdfDoc },
          me.viewerDiv
        );
        printWin.close();
        return;
      }

      me.dispatchTWEvent(
        "allPagesRenderedForPrint",
        { doc: me.pdfDoc },
        me.viewerDiv
      );

      printWin.focus();
      printWin.print();
      printWin.close();
    });
  }

  /**
   * Shows previously visited page from the history of the visited pages.
   */
  prevVisited() {
    this.viewer.prevVisited();
  }

  /**
   * Shows next visited page from the history of the visited pages.
   */
  nextVisited() {
    this.viewer.nextVisited();
  }

  /**
   * Shows the previous page.
   */
  previousPage() {
    if (this.viewer.currentPageNumber <= 1) {
      return;
    }

    this.viewer.currentPageNumber--;
    this.viewer.showPage(this.viewer.currentPageNumber);
  }

  /**
   * Shows the next page.
   */
  nextPage() {
    if (this.viewer.currentPageNumber >= this.viewer.pdfDoc.numPages) {
      return;
    }

    this.viewer.currentPageNumber++;
    this.viewer.showPage(this.viewer.currentPageNumber);
  }

  /**
   * Downloads the loaded document.
   */
  downloadDocument() {
    if (!this.pdfDocument) {
      return;
    }

    if (this.pdfDocument.documentFile) {
      this.downloadManager.downloadData(
        this.pdfDocument.documentFile,
        this.pdfDocument.documentName,
        "application/pdf"
      );
    } else {
      this.downloadManager.downloadUrl(
        this.pdfDocument.documentUrl,
        this.pdfDocument.documentName
      );
    }
  }

  /**
   * Shows the document properties.
   */
  showDocumentProperties() {
    this._pdfDocumentProperties.open();
  }

  /**
   * Activates the single-page view mode.
   */
  activateSinglePageView() {
    this._activateView(new SingleViewer(this));
  }

  /**
   * Activates the multi-page view mode.
   */
  activateMultiPageView() {
    this._activateView(new MultiViewer(this));
  }

  /**
   * Rotates the document by 90 degrees counter-clockwise.
   */
  rotateCounterClockwise() {
    this.viewer.pagesRotation -= 90;
    if (this.viewer.pagesRotation === -360) {
      this.viewer.pagesRotation = 0;
    }
    this.zoomService.executeZoom();
  }

  /**
   * Rotates the document by 90 degrees clockwise.
   */
  rotateClockwise() {
    this.viewer.pagesRotation += 90;
    if (this.viewer.pagesRotation === 360) {
      this.viewer.pagesRotation = 0;
    }
    this.zoomService.executeZoom();
  }

  /**
   * Opens the document's properties.
   */
  openDocumentProperties() {
    this._pdfDocumentProperties.open();
  }

  /**
   * Disables the text selection.
   * @param {boolean} val
   */
  disableSelection(val) {
    const textDivs =
      this.elements.tw2018elem_mainContainer.getElementsByClassName(
        "tw2018css_textLayer"
      );

    for (let i = 0; i < textDivs.length; i++) {
      textDivs[i].classList.toggle("tw2018css_disabled", val);
    }
  }

  /**
   * Goes to the specified page.
   * @param {number} num - The page number.
   */
  gotoPage(num) {
    if (this.viewer.pdfDoc.numPages < num || num < 1) {
      return;
    }
    this.viewer.currentPageNumber = num;
    this.viewer.showPage(num);
    this.toolbar.updateStatus(num);
  }

  /**
   * Loads the document from the specified URL.
   * @param {string} url - The URL of the document.
   * @param {string} password - The password of the document.
   * @param {TWViewerPdfApp} app - The application.
   */
  loadDocumentFromUrl(url, password, app = this) {
    this._showDocumentLoading(true);
    app.releasePdfDoc();
    app.pdfDocument.documentUrl = url;
    app.pdfDocument.documentName = tw2018func_getPDFFileNameFromURL(url);
    const loadingTask = getDocument({ url, password });
    loadingTask.promise
      .then(pdfDoc => app._loadDocument(pdfDoc, url))
      .catch(reason =>
        app._loadDocumentError(
          reason,
          url,
          null,
          null,
          this.loadDocumentFromUrl
        )
      );
  }

  /**
   * Loads the document from the specified file.
   * @param {file} file - The file of the document.
   * @param {string} password - The password of the document.
   * @param {TWViewerPdfApp} app - The application.
   */
  loadDocumentFromFile(file, password, app = this) {
    this._showDocumentLoading(true);
    app.toolbar.enableToolbar(false);
    app.viewer.dispose();
    app.bookmarkLayer.reset();
    const fileReader = new FileReader();
    fileReader.onload = evt1 => {
      const buffer = evt1.target.result;
      app.releasePdfDoc();
      app.pdfDocument.documentFile = file;
      app.pdfDocument.documentName = file.name;
      const loadingTask = getDocument({
        data: new Uint8Array(buffer),
        password,
      });
      console.time("loadingTask");
      loadingTask.promise
        .then(pdfDoc => {
          console.timeEnd("loadingTask");
          app._loadDocument(pdfDoc, "");
        })
        .catch(reason =>
          app._loadDocumentError(
            reason,
            "",
            file,
            evt1,
            app.loadDocumentFromFile
          )
        );
    };

    fileReader.readAsArrayBuffer(file);
  }

  /**
   * Returns the text content of the page.
   * @param {number} pageIndex Page index of the page. Numbering starts from 0.
   * @returns {string} The text content of the page.
   */
  getPageTextContent(pageIndex) {
    return this.viewer.getPageTextContent(pageIndex);
  }

  /**
   * Get currently loaded document name.
   * @returns {string}
   */
  getDocumentName() {
    return this.pdfDocument.name;
  }

  /**
   * Releases the loaded document.
   */
  releasePdfDoc() {
    if (this.viewer.pdfDoc) {
      this.toolbar.findBar.reset();
      this.bookmarkLayer.reset();
      this.bookmarkLayer.show(false);
      this.viewer.pdfDoc.cleanup();
      this.viewer.pdfDoc.destroy();
      this.viewer.pdfDoc = null;
      this.viewer.dispose();
      this._scrollEnabled = false;
      this.dispatchTWEvent("documentReleased", {}, this.viewerDiv);
    }
  }
  // #endregion
}

export { TWViewerPdfApp };