import { getDocument, GlobalWorkerOptions, InvalidPDFException, MissingPDFException, UnexpectedResponseException, getPdfFilenameFromUrl } from 'pdfjs-dist/legacy/build/pdf'
import CryptoJS from 'crypto-js'
import JSEncrypt from 'jsencrypt'
import Base64 from "base-64"
import printJS from 'print-js'
import { saveAs } from 'file-saver'
import { PDFLinkService } from "./pdf_link_service"
import { PasswordPrompt } from "./password_prompt.js";
import { PDFViewer } from './pdf_viewer'
import { PDFThumbnailViewer } from './pdf_thumbnail_viewer'
import { PDFRenderingQueue } from "./pdf_rendering_queue";
import { PDFSidebar } from "./pdf_sidebar.js";
import { GenericExternalServices } from "./genericcom.js";
import {  PDFCursorTools } from "./pdf_cursor_tools.js";
import { EventBus, isValidRotation, isValidScrollMode, shadow, isValidSpreadMode, toDateObject, findIndex, scrollIntoView, DEFAULT_SCALE_VALUE, SidebarView, parseAnnotationFromXml, PromiseExt, parseAdobePDFTimestamp, round } from './ui_utils'
import { ALIGNMAP, AnnotationTypeString, AnnotationState, AnnotationStateString } from "../constants";
import { PDFPresentationMode } from "./pdf_presentation_mode.js";
import annotationStore from "./annotation_store"
import { Signatures } from "./signatures"
import MessageHandler from "./message_handler"
import JSZip from 'jszip'
import Outline from './Outline'
import { v4 as uuidv4 } from 'uuid';
import { ContentEditManager } from "./editor/content_edit_manager.js";

GlobalWorkerOptions.workerSrc = './lib/pdf.worker.min.js'
const CMAP_URL = './cmaps/'

class ComPDFKitViewer {
  #pwd = ''
  #oldPwd = ''
  #textSearch = null
  #activeSearchIndex = 0
  #fileList = null
  constructor(options) {
    this.config = options
    this.viewerContainer = null
    this.pdfLoadingTask = null
    this.printService = null
    this.pdfSidebar = null
    this.pdfOutlineViewer = null
    this.outlines = []
    this.pdfLayerViewer = null
    this.pdfDocument = null
    this.downloadComplete = false
    this.isInitialViewSet = false
    this.pdfLinkService = null
    this.externalServices = GenericExternalServices
    this.percent = 0
    this.pageChangedCallback = null
    this.scaleChangedCallback = null
    this.annotationsNumChangedCallback = null
    this.distanceChangedCallback = null
    this._license = ''
    this._token = null
    this._pdfId = null
    this.annotations = null
    this.annotationHistory = []
    this._annotationMode = 0
    this._boundEvents = Object.create(null)
    this._title = document.title
    this.activeTool = ''
    this.color = 'color'
    this.pagesPtr = []
    this.saveAction = null
    this.enableReply = true
    this.enableDefaultFont = false
    this.enableCustomFont = false
    this.fontsJson = null
    this.webFontURL = 'https://www.compdf.com/fonts/'
    this.optionUrl = {
      // baseUrl: 'https://test-compdf.kdan.cn',
      baseUrl: 'https://wms.compdf.com',
      verifyUrl: '/api/license/verify/v1',
      // webviewBaseUrl: 'http://101.132.103.13:7000',
      webviewBaseUrl: 'https://web-viewer-backend.compdf.com',
      uploadUrl: '/web-viewer/annotate/openPdfToAnnotate',
      editUrl: '/web-viewer/annotate/edit',
      saveUrl: '/web-viewer/annotate/save',
      flattenUrl: '/web-viewer/annotate/saveAndFlatten',
      uploadAnnotationsUrl: '/web-viewer/annotate/importAnnotateToPdf',
      downloadAnnotation: '/web-viewer/annotate/exportAnnotate',
      compareUrl: '/web-viewer/annotate/pdfCompare'
    }

    this.eventBus = new EventBus();
    this.toolMode = ''
    this.$t = options.$t
    this.fontFileInited = false
    this.contentEditAnnotationsManager = {}

    this.initWorker()
    this.annotator = 'Guest'
    this.isAnnotationModified = false
    this.isContentEditModyfied = false
    this.redactionList = []
  }

  get pagesCount() {
    return this.doc ? this._pagesCount : 0;
  }

  get annotationsCount() {
    return this.pdfViewer.annotationStorage ? this.pdfViewer.annotationStorage.size : 0;
  }

  get page() {
    return this.pdfViewer.currentPageNumber;
  }

  set page(val) {
    this.pdfViewer.currentPageNumber = val;
  }

  get currentPageNumber() {
    return this.pdfViewer.currentPageNumber
  }

  get scale() {
    return this.pdfViewer.currentScale;
  }

  get isDocumentModified() {
    return this.isAnnotationModified || this.isContentEditModyfied;
  }

  getPageWidth(pageNumber) {
    return this.pagesPtr[pageNumber - 1].width
  }

  getPageHeight(pageNumber) {
    return this.pagesPtr[pageNumber - 1].height
  }

  initWorker() {
    const worker = new PDFWorker()

    this.worker = worker
    const messageHandler = worker.messageHandler
    this.messageHandler = messageHandler
  }

  async init() {
    await new Promise((resolve, reject) => {
      const worker = new PDFWorker()

      this.worker = worker
      const messageHandler = worker.messageHandler
      this.messageHandler = messageHandler
    })
  }

  _decrypt(data) {
    const crypt = new JSEncrypt()

    const privateKey = `MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCb9LJZzBAOR3YDT0/rLqY1pCu6
    ge7lb0aU4k/aTnxSz10t/JUUtGMvQ2tBy36hjwulouM6xoFSm8gnjovALuVpTltRPPEfN+uX6AVy
    Wa58TsS2+Q9JRPHJlOb4sDEriUnzLtonahqF/S/EiijMAkNc2b1KNNUb2bDNmLi6YvT7d1sh3iIf
    kTggsyt3UVUjh2exEVpb8Ww4grmLJjFssi5nZksLDhlA0MUr4dPmIcDl0RlIwHTsxrJbewnX7kNd
    MsSvCxYV1EtmBx9KRCu8ON2bRwKaaWd7fAT9yZWYpjQmbZ5A4CAqDcEAlW8RrhMv0FNKVn1/GMVu
    DC/PsWTyI1fJAgMBAAECggEAazkHThmwiEC1cH0dEzLVb1faALbIitHAkygtgognjb+n7rATx1Jm
    Rt5xZaEFBQqWFWEyHPzRePxF1nx4tL8JVydaPTZLCQ2nWp9Y70+dWTXGpY4C5nsSSWeojR6GD8qq
    Hxn9PtACQFDcgKIEfo6nxbSmIdjFFArNNe1kRDE5AZHtZi7Q6JDRewp6mESrSGED1JodQ4xJoEZl
    1HPvMTxcGGQk/8EZOWXaGvGuBEfIMc33g61YxxhWXb6Rf7x8QKpa2cRLbqN+74wdiWyQUAcSjZSi
    LzeY0a4h7DKkhBEHWXT4svrXsF1RjMRpTi8amxNgUP7RSSTiF9iT6fpGpnwWKQKBgQDsP18lwMjl
    blt8mKqrL9SKVMoOLKUaci9WNV14qEUgNp/RIqjflS0wasbRo7suix2Cwd58ZVAmueuH5W9ZM9NP
    A9RP5vnm8fqfqMZIq+Z7ORhry5QpGlOo0tKkik716kTRXl5w3O/YcR8wBZ7Xzwh467OYcRMNnr2i
    8F5eT4Ny5wKBgQCo/sQrc4KqsYwdWWZW+O92jIylxgtCQsZNg1cXql91IuB3lx1HnDaN/R2MeCkp
    ok+hYXWB0848321x+vYdL9ivLUrnHpNoycFEuhqNTYbyZWnq1tKPz9RDhPIwEmlQVW3dU9hWEjF5
    6sa4NXP7/lSiYaQe0FS4BYQ7KCwahxw5zwKBgQCyCYv92oxLguBFRJQR0EjfOsjO58xA4t5TnIHl
    hQIbJa9JH1KetEBK5Z0Rrc9JYyOJP+sYWg2OMbyTPE+urMTJHjypPosEBUX3fmx3pbxbDosd6Vyt
    oR588uw1fdnFr5fPJMwNSCMkPRPgz8uGHYrn/dDpyAUw1XlaSsUDU+7e7wKBgDHCej6VD3d1Od12
    cmTPfar5bXxuWOhUwxEVyDImr17NOyzbaPqp64JZcjbnNq+UpD/Pc4sbs5FG9HHoREmJyhKikniF
    D0B+j5LN/X13w3FiHRLR2EQSHN0YqzsuYKYP0X6m7n9BciIE2ChrTNB5LbB2cZ3FF/zrWukURhA1
    kkEPAoGBAIpoB6ehuqTn7cisbXjaLqwzX35y50+KED2TamB1FkTjfU5YPsSnKBX9CfCeEd+yr08r
    mecHPUYVj5SpLlcpzx+n7taJCzNJ4SuE186qwHR+MGAS3reXsz5l5GVu7WSH2uKZ7OuXnzCJC4/r
    dx/f2C0wOi6fdC46fAZVACROoV9T`
    crypt.setPrivateKey(privateKey)

    const licenseData = Base64.decode(data)
    const length = licenseData.length
    let license = ''
    let i = 0
    while (i < length) {
      const licensePart = Base64.encode(licenseData.substr(i, 256))
      const plaintext = crypt.decrypt(licensePart);
      license += plaintext
      i += 256
    }

    return license ? license : false
  }

  async _verifyLicense(license) {
    const domain = window.location.hostname.split('.').slice(-2).join('.')
    const appId = domain
    const messageHandler = this.messageHandler
    const initStatus = await messageHandler.sendWithPromise('InitConfig', {
      license,
      appId
    })
    return initStatus
  }

  async _twoStepVerification(rawLicense) {
    let initStatus = await this._verifyLicense(rawLicense)
    let license = rawLicense
    if (initStatus.code) {
      const domain = window.location.hostname.split('.').slice(-2).join('.')
      const signature = CryptoJS.MD5(`${rawLicense}7${domain}compdfkitonlinelicense`).toString()
      const data = {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          license,
          platform: 7,
          app_id: domain,
          signature
        })
      }
      license = await fetch(this.optionUrl.baseUrl + this.optionUrl.verifyUrl, data)
        .then((res) => {
          return res.json()
        })
        .then((resp) => {
          if (resp.code === 200) {
            const decryptedLicense = this._decrypt(resp.license)
            if (!decryptedLicense) {
              alert('Invalid License')
              return false
            }
            return decryptedLicense
          } else {
            alert(resp.message)
            return false
          }
        })
        .catch((error) => {
          alert('Network Error')
          console.log({ error: true, message: error.message })
          return false
        });
      if (!license) return false
      initStatus = await this._verifyLicense(license)
    }
    if (!initStatus.code) {
      this._license = license
      return true
    } else {
      alert(initStatus.message)
      return false
    }
  }

  async initConfig(options) {
    if (!options.license) {
      console.log('License code not exists')
      alert('License code not exists')
      return false
    }

    try {
      await this.worker.promise
    } catch (error) {
      console.log(error)
      return
    }

    const verified = await this._twoStepVerification(options.license)

    if (options.webviewerServer === 'Server') {
      this.webviewerServer = true
    }

    // this.webviewerServer = true

    if (verified) {
      if ('enableDefaultFont' in options) {
        this.enableDefaultFont = options.enableDefaultFont
      }
      if ('enableReply' in options) {
        this.enableReply = options.enableReply
      }
      if (options.enableCustomFont) {
        this.enableCustomFont = true
      }
      if (options.webFontURL) {
        this.enableCustomFont = true
        this.webFontURL = options.webFontURL
      }
      if (!this.webviewerServer && this.enableCustomFont) {
        const webFontURL = this.webFontURL

        const fileData = await fetch(webFontURL + 'fonts.json')
        if (fileData.ok) {
          this.fontsJson = await fileData.json()
        } else {
          console.error('Invalid webFontURL')
        }

        if (this.fontsJson && this.fontsJson[0].filePath && this.fontsJson[0].filePath.endsWith('brotli')) {
          const response = await fetch(webFontURL + 'font.ff')
          if (response.ok) {
            const resData = await response.arrayBuffer()
            this.#fileList = new Uint8Array(resData)
          } else {
            console.error('Invalid webFontURL')
          }
        }

        this.messageHandler.on('LoadFont', data => {
          const fontFamily = data.fontFamily
          this.eventBus.dispatch("LoadFont", { fontFamily })
        })
      }
      if (options.autoJumpNextSign) {
        this.autoJumpNextSign = options.autoJumpNextSign
      }
    }
    return verified
  }

  initializeViewer({
    container,
    viewer,
    thumbnailView,
    annotationView,
    findbarView,
    toggleButton,
  }) {
    this._initializeViewerComponents({
      container,
      viewer,
      thumbnailView,
      annotationView,
      findbarView,
      toggleButton,
    })
    this.initBindEvents()
    if (thumbnailView && annotationView) {
      this.bindEvents();
      this.bindWindowEvents();
    }
  }

  setTool({
    tool,
    color
  }) {
    this.activeTool = tool
    this.color = color
    this.eventBus.dispatch("toolChanged", { tool, color });
  }

  initAnnotations(annotations, render = false) {
    for (let index = 0; index < annotations.length; index++) {
      const result = this.handleAnnotations(annotations[index], true)
      if (result && result.code) return false
      render && this.pdfViewer.renderAnnotation(annotations[index])
    }
    this.eventBus.dispatch('annotationChanged', { annotations: this.annotations })
  }

  async handleAnnotations(annotation, init = false) {
    if (!this.annotations) {
      this.annotations = {}
    }
    if (init) {
      this.initAddAnnotations(annotation)
    } else {
      if (Number(annotation.pageIndex) + 1 > this.pagesCount) return
      this.pushAnnotations(annotation)
      if (!this.webviewerServer || annotation.type === 'stamp') {
        if ('destPage' in annotation) {
          annotation.doc = this.doc
          annotation.pageHeight = this.pagesPtr[annotation.pageIndex].height
        }
        const pagePtr = this.pagesPtr[annotation.pageIndex].pagePtr
        const result = await this.messageHandler.sendWithPromise('CreateAnnotation', {
          doc: this.doc,
          pagePtr,
          annotation
        })

        if (result && result.code) {
          console.log(result.message)
          alert(result.message)
          return result
        }

        const { annotPtr } = result

        annotation.pagePtr = pagePtr
        annotation.annotPtr = annotPtr

        if (!annotation.rect && this.webviewerServer) {
          const getRect = await this.messageHandler.sendWithPromise('GetAnnotRect', {
            pagePtr: pagePtr,
            annotPtr: annotPtr
          })

          let pageView = this.pdfViewer.getPageView(annotation.pageIndex)

          const pageWidth = pageView.viewport.viewBox[2]
          const pageHeight = pageView.viewport.viewBox[3]
          const imgWidth = getRect.right - getRect.left
          const imgHeight = getRect.bottom - getRect.top
    
          const left = (pageWidth - imgWidth) / 2
          const top = (pageHeight - imgHeight) / 2
          const right = (pageWidth + imgWidth) / 2
          const bottom = (pageHeight + imgHeight) / 2
    
          const rect = {
            left,
            top,
            right,
            bottom,
          }

          annotation.rect = rect
        }
      }
    }
  }

  initAddAnnotations(annotation) {
    const pageIndex = annotation.pageIndex
    const annotations = this.annotations
    if (!annotations[pageIndex]) {
      annotations[pageIndex] = []
    }
    if (typeof annotation.index !== 'undefined') {
      annotations[pageIndex][annotation.index] = annotation
    } else {
      annotations[pageIndex].push(annotation)
    }
    if (!annotationStore.annotationsAll) {
      annotationStore.annotationsAll = annotations
    }
  }

  pushAnnotations(annotation) {
    const pageIndex = annotation.pageIndex
    const annotations = this.annotations
    if (!annotations[pageIndex]) {
      annotations[pageIndex] = []
      this.pdfViewer._pages[pageIndex].annotations = annotations[pageIndex]
    }
    if (!annotationStore.annotationsAll) {
      annotationStore.annotationsAll = annotations
    }
    annotations[pageIndex].push(annotation)
  }

  addAnnotations(annotation) {
    const annotations = Array.isArray(annotation) ? annotation : [annotation]
    annotations.forEach(item => {
      this.handleAnnotationChange({ type: 'add', annotation: item })
    })
  }

  async delAnnotations(annotation) {
    const annotations = Array.isArray(annotation) ? annotation : [annotation]
    for (const item of annotations) {
      await this.handleAnnotationChange({ type: 'delete', annotation: item })
    }
  }

  deleteAnnotations(annotation) {
    const annotations = Array.isArray(annotation) ? annotation : [annotation]
    this.pdfViewer.deleteAnnotations(annotations)
  }

  addEvent(eventName, fn) {
    this.eventBus._on(eventName, fn);
  }

  removeEvent(eventName, fn) {
    this.eventBus._off(eventName, fn);
  }

  async loadDocument(file, options = {}) {
    if (this.pdfLoadingTask) {
      await this.close()
    }
    if (options.annotations) {
      parseAnnotationFromXml(options.annotations)
    }
    this.info = options.info || null
    this.pageChangedCallback = options.pageChangedCallback || this.pageChangedCallback
    this.scaleChangedCallback = options.scaleChangedCallback || this.scaleChangedCallback
    this.annotationsNumChangedCallback = options.annotationsNumChangedCallback || this.annotationsNumChangedCallback
    this.distanceChangedCallback = options.distanceChangedCallback || this.distanceChangedCallback
    this.annotations = null
    annotationStore.annotationsAll = null
    annotationStore.signatures = null
    annotationStore.selectedName = null
    this.fontFileInited = false
    this.saveAction = options.saveAction || null
    this.isAnnotationModified = false
    this.isContentEditModyfied = false
    const parameters = {
      cMapUrl: CMAP_URL,
      cMapPacked: true,
      enableXfa: true
    };
    if (typeof file === "string") {
      // URL
      parameters.url = file
    } else if (file && "byteLength" in file) {
      // ArrayBuffer
      parameters.data = file;
    } else if (file.url && file.originalUrl) {
      parameters.url = file.url;
    }
    parameters.password = options.password || ''
    this.#pwd = parameters.password
    // 获取pdf信息，返回promise
    const loadingTask = getDocument(parameters)
    this.pdfLoadingTask = loadingTask
    this._docName = options.filename || getPdfFilenameFromUrl(parameters.url)
    const getPwd = (pwd) => this.#pwd = pwd
    const handlePassword = (updateCallback, reason) => {
      if (this.#oldPwd && options.notUpdatePwd) {
        getPwd(this.#oldPwd);
        updateCallback(this.#oldPwd);
        loadingTask.onPassword = null;
      } else if (options.pwd) {
        getPwd(options.pwd);
        updateCallback(options.pwd);
        loadingTask.onPassword = null;
      } else {
        this.passwordPrompt.setUpdateCallback(updateCallback, reason, getPwd);
        this.passwordPrompt.open();
      }
    };

    loadingTask.onPassword = handlePassword;

    loadingTask.onProgress = ({ loaded, total }) => {
      const progress = options.progress || this.progress
      progress.call(this, loaded / total)
    }

    // notUploadPdf -> 仅预览 不上传到服务器
    // if (options.notUploadPdf) {
    //   const pdfDocument = await loadingTask.promise;
    //   await this.load(pdfDocument);
    //   return this.#pwd
    // }

    // 处理传入的PDF
    return loadingTask.promise.then(
      async pdfDocument => {
        // 上传pdf
        await pdfDocument.getData().then(async (binaryData) => {
          this.pdfDocument = pdfDocument
          if (!options.notUpdatePwd) {
            this.#oldPwd = this.#pwd
          }

          let buffer = new Uint8Array(binaryData)
          const doc = await this.messageHandler.sendWithPromise('LoadFile', {
            doc: this.doc,
            pagesPtr: this.pagesPtr,
            buffer,
            webFontURL: this.webFontURL,
            fileList: this.#fileList,
            fontsJson: this.fontsJson,
            enableDefaultFont: this.enableDefaultFont
          })

          this.doc = doc

          const loadRes = await this.messageHandler.sendWithPromise('LoadDocumentByStream', {
            doc: this.doc,
            fileId: 0,
            length: buffer.length,
            password: this.#pwd
          })
          this.contentEditAnnotationsManager.addTextEditor = (data) => {
            const pageNumber = data.pageNumber
            if (pageNumber > this.pagesCount || pageNumber < 1) {
              throw new Error({
                message: 'Invalid pageNumber'
              })
            }
            if (this.toolMode !== 'editor') {
              this.setToolMode('editor')
            }
            if (this.currentPageNumber !== pageNumber) {
              this.pdfViewer.scrollPageIntoView({ pageNumber: pageNumber })
            }
            const pageIndex = pageNumber - 1
            const pageView = this.pdfViewer._pages[pageIndex]
            if (pageView) {
              pageView.addTextEditor(data)
            }
          }
          this.contentEditAnnotationsManager.getEditAnnotation = async (pageNumber) => {
            if (pageNumber > this.pagesCount || pageNumber < 1) {
              throw new Error({
                message: 'Invalid pageNumber'
              })
            }
            if (this.toolMode !== 'editor') {
              this.setToolMode('editor')
            }
            if (this.currentPageNumber !== pageNumber) {
              this.pdfViewer.scrollPageIntoView({ pageNumber: pageNumber })
            }
            const pageIndex = pageNumber - 1
            const pageView = this.pdfViewer._pages[pageIndex]
            if (pageView) {
              const editAnnotation = await pageView.getEditAnnotation()
              return editAnnotation
            }
          }

          let annotations = null

          await this.initPage()

          // const response = await fetch('./lib/ComPDFKit.pfx')
          // const blob = await response.blob()
          // const certFile = new File([blob], 'ComPDFKit.pfx', { type: blob.type })
          // this.certFile = certFile
          // const a = await this.messageHandler.sendWithPromise('AddSignature', {
          //   doc: this.doc,
          //   pagePtr: this.pagesPtr[0],
          //   certFile,
          // })

          // console.log(a)
          // saveAs(a, 'test.pdf')

          if (this.webviewerServer) {
            if (this.controller) {
              this.controller.abort()
              this.controller = null
            }
            const formData = new FormData();
            formData.append('file', new Blob([binaryData], { type: 'application/pdf' }), this._docName);
            formData.append('password', this.#pwd);
            this.controller = new AbortController()
            const signal = this.controller.signal

            const data = {
              method: 'POST',
              headers: {
                'Authorization': this._token
              },
              signal,
              body: formData
            }
            await fetch(this.optionUrl.webviewBaseUrl + this.optionUrl.uploadUrl, data)
              .then((res) => {
                return res.json()
              })
              .then((resp) => {
                if (resp.code === "200") {
                  const data = resp.data
                  this._pdfId = data.pdfId
                  const rawAnnotations = data.annotateJson && data.annotateJson
                  annotations = this.convertAnnotation(rawAnnotations)
                } else if (resp.code === "319") {
  
                }
              })
              .catch((error) => {
                if (error.name !== 'AbortError') {
                  console.log(error)
                  return { error: true, message: error.message }
                }
              });
          } else {
            annotations = await this.getAnnotations()
          }
          annotations && this.initAnnotations(annotations)
        })
        await this.load(pdfDocument);
        return { pwd: this.#pwd };
      },
      reason => {
        if (loadingTask !== this.pdfLoadingTask) {
          return undefined; // Ignore errors for previously opened PDF files.
        }

        let key = "loading_error";
        if (reason instanceof InvalidPDFException) {
          key = "invalid_file_error";
          console.warn('Invalid or corrupted PDF file.')
          return new Promise(function (resolve, reject) {
            reject('invalid_file_error')
          });
        } else if (reason instanceof MissingPDFException) {
          key = "missing_file_error";
          console.warn('Missing PDF file.')
        } else if (reason instanceof UnexpectedResponseException) {
          key = "unexpected_response_error";
          console.warn('Unexpected server response.')
        } else if (reason) {
          if (reason.message === 'No password given') {
            key = "no_password_given";
            console.warn('No password given.')
            return new Promise(function (resolve, reject) {
              reject('no_password_given')
            });
          } else if (reason.message === 'Incorrect Password') {
            return new Promise(function (resolve, reject) {
              reject('incorrect_password')
            });
          }
        }
        throw new Error(key)
      }
    )
  }

  getSelectedText(pageNumber) {
    if (pageNumber) {
      const page = this.pdfViewer._pages[pageNumber - 1]
      if (page) {
        return page.selectedText
      }
    }
    return this.pdfViewer._pages.map(function(page) {
      return page.selectedText
    }).filter(function(text) {
      return text !== null
    }).join('\n')
  }

  // 获取选中的文字的坐标数据
  getSelectedTextQuads(pageNumber) {
    if (pageNumber) {
      const page = this.pdfViewer._pages[pageNumber - 1]
      if (page) {
        return page.selectedTextQuads
      }
    }

    const textQuads = this.pdfViewer._pages.map(function(page) {
      return page.selectedTextQuads
    }).filter((selectedTextQuad) => selectedTextQuad !== null)

    return textQuads.length ? textQuads : null
  }

  convertAnnotation(rawAnnotations) {
    const annotations = []

    for (const type in rawAnnotations) {
      let annotationData = rawAnnotations[type]
      if (!Array.isArray(annotationData)) {
        annotationData = [annotationData]
      }
      for (let i = 0; i < annotationData.length; i++) {
        !annotationData[i].type && (annotationData[i].type = type)
        const annotation = this.#formatAnnotation(annotationData[i])
        annotations.push(annotation)
      }
    }

    return annotations
  }

  #formatRect(annotation) {
    const pageIndex = annotation.page
    if (!this.pagesPtr[pageIndex]?.height) return
    const { height } = this.pagesPtr[pageIndex]

    const rect = annotation.rect.split(',')
    const left = round(rect[0], 2)
    const bottom = round((height - Number(rect[1])), 2)
    const right = round(rect[2], 2)
    const top = round((height - Number(rect[3])), 2)

    return {
      left,
      bottom,
      right,
      top
    }
  }

  #formatCoords(annotation) {
    const formattedCoords = []
    const pageIndex = annotation.page
    if (!this.pagesPtr[pageIndex]?.height) return
    const { height } = this.pagesPtr[pageIndex]

    const coords = annotation.coords.split(',')
    for (let i = 0; (i + 7) < coords.length; i += 8) {
      const x1 = round(coords[i], 2),
        y1 = round((height - Number(coords[i + 1])), 2),
        x2 = round(coords[i + 2], 2),
        y2 = round((height - Number(coords[i + 3])), 2),
        x3 = round(coords[i + 4], 2),
        y3 = round((height - Number(coords[i + 5])), 2),
        x4 = round(coords[i + 6], 2),
        y4 = round((height - Number(coords[i + 7])), 2)
      const left = Math.min(x1, x2)
      const right = Math.max(x1, x2)
      const top = Math.min(y1, y3)
      const bottom = Math.max(y1, y3)
      formattedCoords.push({
        PointX: left,
        PointY: top
      })
      formattedCoords.push({
        PointX: right,
        PointY: top
      })
      formattedCoords.push({
        PointX: left,
        PointY: bottom
      })
      formattedCoords.push({
        PointX: right,
        PointY: bottom
      })
    }
    return formattedCoords
  }

  #formatLinePoints(annotation) {
    const pageIndex = annotation.page
    if (!this.pagesPtr[pageIndex]?.height) return
    const { height } = this.pagesPtr[pageIndex]
    const linePoints = []

    const start = annotation.start.split(',')
    const end = annotation.end.split(',')
    linePoints.push(round(start[0], 2))
    linePoints.push(round((height - Number(start[1])), 2))
    linePoints.push(round(end[0], 2))
    linePoints.push(round((height - Number(end[1])), 2))

    return linePoints
  }

  #formatInkPointes(annotation) {
    const pageIndex = annotation.page
    if (!this.pagesPtr[pageIndex]?.height) return
    const { height } = this.pagesPtr[pageIndex]
    const inklist = []

    let inklistData = annotation.inklist.gesture

    if (!Array.isArray(inklistData)) {
      inklistData = [inklistData]
    }

    for (let i = 0; i < inklistData.length; i++) {
      if (inklistData[i][inklistData[i].length - 1] === ';') {
        inklistData[i] = inklistData[i].slice(0, inklistData[i].length - 1)
      }
      const points = inklistData[i].split(';').map((rawPoint) => {
        const point = rawPoint.split(',')
        return {
          PointX: round(point[0], 2),
          PointY: round((height - Number(point[1])), 2)
        }
      })

      inklist.push(points)
    }

    return inklist
  }

  #formatAnnotation(rawAnnotation) {
    const annotation = {}
    annotation.type = rawAnnotation.type
    rawAnnotation.rect && (annotation.rect = this.#formatRect(rawAnnotation))
    annotation.date = parseAdobePDFTimestamp(rawAnnotation.creationdate)
    annotation.pageIndex = Number(rawAnnotation.page)
    annotation.index = Number(rawAnnotation.index)
    annotation.contents = rawAnnotation.content || ''
    annotation.opacity = round(rawAnnotation.opacity, 2) || 1
    rawAnnotation.mainName && (annotation.mainName = rawAnnotation.mainName)
    rawAnnotation.replies && (annotation.replies = rawAnnotation.replies);
    (rawAnnotation.markedAnnotState || rawAnnotation.markedAnnotState === 0) && (annotation.markedAnnotState = AnnotationStateString[rawAnnotation.markedAnnotState]);
    (rawAnnotation.reviewAnnotState || rawAnnotation.reviewAnnotState === 0) && (annotation.reviewAnnotState = AnnotationStateString[rawAnnotation.reviewAnnotState])
    annotation.name = rawAnnotation.name || uuidv4()

    switch (rawAnnotation.type) {
      case 'freetext':
        let fontName = rawAnnotation.textStyle.fontName
        fontName = fontName[0].toUpperCase() + fontName.slice(1)
        annotation.fontName = fontName
        annotation.fontSize = round(rawAnnotation.textStyle.fontSize, 0)
        annotation.color = rawAnnotation.textStyle.fontColor
        annotation.textAlignment = rawAnnotation.justification
        annotation.contents = rawAnnotation['contents-richtext'] || rawAnnotation.content
        if (rawAnnotation['contents-richtext']) {
          const parser = new DOMParser()
          const xmlDoc = parser.parseFromString(rawAnnotation['contents-richtext'], 'text/xml');
          const error = xmlDoc.getElementsByTagName("parsererror")
          if (error.length > 0) {
            annotation.contents = rawAnnotation['contents-richtext']
          } else {
            annotation.contents = xmlDoc.firstElementChild.innerText
          }
        }
        break
      case 'text':
        annotation.color = rawAnnotation.color
        annotation.isHidden = rawAnnotation.isHidden || rawAnnotation.flags && rawAnnotation.flags === 'invisible' ? 1 : 0
        break
      case 'highlight':
      case 'underline':
      case 'strikeout':
      case 'squiggly':
        annotation.color = rawAnnotation.color
        annotation.quadPoints = this.#formatCoords(rawAnnotation)
        break
      case 'line':
        annotation.linePoints = this.#formatLinePoints(rawAnnotation)
        annotation.tail = rawAnnotation.tail
        annotation.head = rawAnnotation.head
        annotation.borderColor = rawAnnotation.color
        annotation.borderWidth = round(rawAnnotation.width, 2)
        break
      case 'square':
      case 'circle':
        annotation.borderColor = rawAnnotation.color
        annotation.borderWidth = round(rawAnnotation.width, 2)
        rawAnnotation['interior-opacity'] && (annotation.fillTransparency = round(rawAnnotation['interior-opacity'], 2))
        rawAnnotation['interior-color'] && (annotation.fillColor = rawAnnotation['interior-color'])
        break
      case 'ink':
        annotation.borderColor = rawAnnotation.color
        annotation.borderWidth = round(rawAnnotation.width, 2)
        annotation.inkPointes = this.#formatInkPointes(rawAnnotation)
        break
      case 'link':
        if (rawAnnotation.Dest && rawAnnotation.Dest.XYZ) {
          annotation.destPage = Number(rawAnnotation.Dest.XYZ.Page) + 1
          annotation.url = ''
        } else if (rawAnnotation.OnActivation && rawAnnotation.OnActivation.Action.URI) {
          annotation.url = rawAnnotation.OnActivation.Action.URI.URI
          annotation.destPage = ''
        }
        break
      case 'stamp':
        annotation.contents = rawAnnotation.icon
        rawAnnotation.image && (annotation.image = rawAnnotation.image)
        annotation.stampType = rawAnnotation['stamp-type']
        break
      case 'textfield': {
        rawAnnotation['background-color'] && (annotation.backgroundColor = rawAnnotation['background-color'])
        annotation.borderStyle = rawAnnotation.borderStyle || 'solid'
        annotation.borderWidth = (rawAnnotation.width && round(rawAnnotation.width, 2)) || 1
        rawAnnotation['border-color'] && (annotation.borderColor = rawAnnotation['border-color'])
        const fontName = rawAnnotation.textStyle.fontName
        annotation.isBold = /bold/i.test(fontName)
        annotation.isItalic = /(oblique|italic)/i.test(fontName)
        annotation.fontName = fontName.split('-')[0]
        annotation.fontSize = round(rawAnnotation.textStyle.fontSize, 0)
        annotation.color = rawAnnotation.textStyle.fontColor
        annotation.textAlignment = rawAnnotation.textStyle.alignment
        annotation.contents = rawAnnotation.value
        annotation.fieldName = rawAnnotation.fieldname
        annotation.isMultiLine = rawAnnotation.textStyle.multiLine === 'true' ? 1 : 0
        annotation.isHidden = (!rawAnnotation.flags || rawAnnotation.flags === 'invisible') ? 1 : 0
        break
      }
      case 'checkbox':
      case 'radiobutton':
        rawAnnotation['background-color'] && (annotation.backgroundColor = rawAnnotation['background-color'])
        annotation.borderStyle = rawAnnotation.borderStyle || 'solid'
        annotation.borderWidth = (rawAnnotation.width && round(rawAnnotation.width, 2)) || 1
        rawAnnotation['border-color'] && (annotation.borderColor = rawAnnotation['border-color'])
        annotation.fieldName = rawAnnotation.fieldname
        annotation.isHidden = (!rawAnnotation.flags || rawAnnotation.flags === 'invisible') ? 1 : 0
        annotation.isChecked = rawAnnotation.value === 'Yes' ? 1 : 0
        annotation.checkStyle = Number(rawAnnotation.styleCA)
        break
      case 'combobox':
      case 'listbox':
        rawAnnotation['background-color'] && (annotation.backgroundColor = rawAnnotation['background-color'])
        annotation.borderStyle = rawAnnotation.borderStyle || 'solid'
        annotation.borderWidth = (rawAnnotation.width && round(rawAnnotation.width, 2)) || 1
        rawAnnotation['border-color'] && (annotation.borderColor = rawAnnotation['border-color'])
        annotation.fieldName = rawAnnotation.fieldname
        if (rawAnnotation.textStyle && rawAnnotation.textStyle.fontName) {
          let comboboxFontName = rawAnnotation.textStyle.fontName
          comboboxFontName = comboboxFontName[0].toUpperCase() + comboboxFontName.slice(1)
          annotation.fontName = comboboxFontName
          annotation.fontSize = round(rawAnnotation.textStyle.fontSize, 0)
        }
        annotation.color = rawAnnotation.textStyle.fontColor
        annotation.isBold = rawAnnotation.textStyle.blod === 'true'
        annotation.isItalic = rawAnnotation.textStyle.italic === 'true'
        annotation.isMultiLine = rawAnnotation.textStyle.multiLine === 'true' ? 1 : 0
        annotation.isHidden = !rawAnnotation.flags && rawAnnotation.flags === 'invisible' ? 1 : 0
        annotation.items = this.#getItems(rawAnnotation)
        annotation.selected = round(rawAnnotation.select, 0) - 1
        break
      case 'pushbutton': {
        const pushbuttonAction = rawAnnotation.OnActivation && rawAnnotation.OnActivation.Action
        if (pushbuttonAction && pushbuttonAction.URI) {
          annotation.url = pushbuttonAction.URI.URI
          annotation.destPage = ''
          annotation.actionType = 6
        } else if (pushbuttonAction && pushbuttonAction.Dest && pushbuttonAction.Dest.XYZ) {
          annotation.destPage = Number(pushbuttonAction.Dest.XYZ.Page) + 1
          annotation.url = ''
          annotation.actionType = 1
        }
        rawAnnotation['background-color'] && (annotation.backgroundColor = rawAnnotation['background-color'])
        annotation.borderStyle = rawAnnotation.borderStyle || 'solid'
        annotation.borderWidth = (rawAnnotation.width && round(rawAnnotation.width, 2)) || 1
        rawAnnotation['border-color'] && (annotation.borderColor = rawAnnotation['border-color'])
        annotation.fieldName = rawAnnotation.fieldname
        if (rawAnnotation.textStyle.fontName) {
          let pushbuttonFontName = rawAnnotation.textStyle.fontName
          pushbuttonFontName = pushbuttonFontName[0].toUpperCase() + pushbuttonFontName.slice(1)
          annotation.fontName = pushbuttonFontName
        } else {
          annotation.fontName = 'Helvetica'
        }
        annotation.fontSize = round(rawAnnotation.textStyle.fontSize, 0)
        annotation.color = rawAnnotation.textStyle.fontColor
        annotation.isBold = rawAnnotation.textStyle.blod === 'true'
        annotation.isItalic = rawAnnotation.textStyle.italic === 'true'
        annotation.title = rawAnnotation.styleCA
        annotation.isHidden = !rawAnnotation.flags && rawAnnotation.flags === 'invisible' ? 1 : 0
        break
      }
    }

    if (annotation.replies?.length) {
      for (const reply of annotation.replies) {
        reply.name = reply.name || uuidv4()
        reply.mainName = reply.mainName || annotation.name
        reply.contents = reply.content
        delete reply.content
        reply.date = parseAdobePDFTimestamp(rawAnnotation.recentlyModifyDate || rawAnnotation.date || rawAnnotation.creationdate)
        reply.pageIndex = Number(rawAnnotation.page)
        delete reply.page
      }
    }

    return annotation
  }

  #getItems(rawAnnotation) {
    const items = []
    const opt = rawAnnotation.opt
    for (let i = 0; i < opt.length; i++) {
      items.push({
        Value: opt[i].value,
        String: opt[i].key
      })
    }
    return items
  }

  getOutlines() {
    if (this.outlines) {
      return this.outlines
    }
  }

  scrollPageIntoView({
    pageNumber,
  }) {
    // if (this.currentPage)
  }

  async search(value) {
    const result = await this.messageHandler.sendWithPromise('Search', {
      pagesPtr: this.pagesPtr,
      value
    })

    if (result && result.code) {
      console.log(result.message)
      alert(result.message)
      return
    }

    const { searchResults } = result

    this.searchResults = searchResults
    this.eventBus.dispatch('search', searchResults)
    if (!searchResults.length) {
      this.clearSearchResults()
      this.#activeSearchIndex = 0
    } else {
      this.setActiveSearchResult()
    }
    return searchResults
  }

  setActiveSearchResult(result = null, activeSearchIndex = 0) {
    if (!this.searchResults) return
    const activeResult = result ? result : this.searchResults[0]
    const quad = activeResult.quads[0]
    const left = quad.left * this.scale
    const top = quad.top * this.scale

    const pageView = this.pdfViewer._pages[activeResult.pageNum - 1]
    this.pdfViewer.currentPageNumber = activeResult.pageNum

    scrollIntoView(pageView.div, {
      left,
      top
    })

    if (result) {
      const preActiveSearchResult = this.searchResults[this.#activeSearchIndex]
      this.pdfViewer._pages[preActiveSearchResult.pageNum - 1].clearActiveSearchResult(preActiveSearchResult)

      pageView.setActiveSearchResult(result)
      this.#activeSearchIndex = activeSearchIndex
    } else {
      pageView.setActiveSearchResult(activeResult)
      this.#activeSearchIndex = 0
    }

  }

  clearSearchResults() {
    if (!this.searchResults || !this.pdfViewer || !this.pdfViewer._pages || !this.pdfViewer._pages.length) return
    for (let i = 0; i < this.pdfViewer._pages.length; i++) {
      const _page = this.pdfViewer._pages[i]
      _page.clearSearchResults()
    }
    this.searchResults = null
  }

  async initPage() {
    const pagesCount = await this.messageHandler.sendWithPromise("GetPageCount", {
      doc: this.doc,
    })

    this._pagesCount = pagesCount

    this.pagesPtr = []
    for (let pageIndex = 0; pageIndex < pagesCount; pageIndex++) {
      const page = await this.messageHandler.sendWithPromise("GetPageSize", {
        doc: this.doc,
        pageIndex
      })

      const textPtr = await this.messageHandler.sendWithPromise('InitText', {
        pagePtr: page.pagePtr
      })

      this.pagesPtr.push({
        doc: this.doc,
        pagePtr: page.pagePtr,
        textPtr,
        width: page.width,
        height: page.height
      })
    }
  }

  async getAnnotations() {
    const signaturePtrList = await this.getSignatures()

    const annotations = []
    const pagesPtr = this.pagesPtr
    for (let pageIndex = 0; pageIndex < this.pagesCount; pageIndex++) {
      const page = pagesPtr[pageIndex]

      const data = await this.messageHandler.sendWithPromise('InitAnnot', {
        pageIndex,
        pagePtr: page.pagePtr
      })
      for (let annotCountNum = 0; annotCountNum < data.annotCount; annotCountNum++) {
        const annotation = data.annotations[annotCountNum]
        const typeInt = annotation.type
        const type = AnnotationTypeString[typeInt]
        annotation.pageIndex = pageIndex
        annotation.type = type
        let attr = null
        if (type === 'widget') {
          attr = await this.messageHandler.sendWithPromise('GetWidgetAnnotation', {
            doc: this.doc,
            annotPtr: annotation.annotPtr,
            pagePtr: annotation.pagePtr,
            typeInt,
            signaturePtrList
          })
        } else {
          attr = await this.messageHandler.sendWithPromise('GetAnnotation', {
            doc: this.doc,
            annotPtr: annotation.annotPtr,
            pagePtr: annotation.pagePtr,
            typeInt,
            scale: this.scale
          })
          if (attr && attr.replies && attr.replies.length) attr.replies.forEach(reply => reply.name = uuidv4())
        }
        Object.assign(annotation, attr)
        annotations.push(annotation)
      }
    }

    this.pdfViewer.pagesPtr = this.pagesPtr

    return annotations
  }

  async reRenderAnnotations() {
    const signaturePtrList = await this.getSignatures()

    this.annotations = null
    const annotations = []
    const pagesPtr = this.pagesPtr

    for (let pageIndex = 0; pageIndex < pagesPtr.length; pageIndex++) {
      const pagePtr = pagesPtr[pageIndex]
      const data = await this.messageHandler.sendWithPromise('InitAnnot', {
        pageIndex,
        pagePtr: pagePtr.pagePtr
      })
      for (let annotCountNum = 0; annotCountNum < data.annotCount; annotCountNum++) {
        const annotation = data.annotations[annotCountNum]
        const typeInt = annotation.type
        const type = AnnotationTypeString[typeInt]
        annotation.pageIndex = pageIndex
        annotation.type = type
        let attr = null
        if (type === 'widget') {
          attr = await this.messageHandler.sendWithPromise('GetWidgetAnnotation', {
            doc: this.doc,
            annotPtr: annotation.annotPtr,
            pagePtr: annotation.pagePtr,
            typeInt,
            signaturePtrList
          })
        } else {
          attr = await this.messageHandler.sendWithPromise('GetAnnotation', {
            doc: this.doc,
            annotPtr: annotation.annotPtr,
            pagePtr: annotation.pagePtr,
            typeInt,
            scale: this.scale
          })
          if (attr && attr.replies && attr.replies.length) attr.replies.forEach(reply => reply.name = uuidv4())
        }
        Object.assign(annotation, attr)
        annotations.push(annotation)
      }
    }

    for (let index = 0; index < annotations.length; index++) {
      if (!this.annotations) {
        this.annotations = {}
      }
      this.pushAnnotations(annotations[index])

      this.pdfViewer.renderAnnotation(annotations[index])
    }
    this.eventBus.dispatch('annotationChanged', { annotations: this.annotations })
  }

  get supportsFullscreen() {
    const fullscreenEnabled = document.webkitFullscreenEnabled || document.mozFullscreenEnabled || document.fullscreenEnabled
    return shadow(this, "supportsFullscreen", fullscreenEnabled);
  }

  removeAllAnnotations() {
    if (this.pdfViewer.annotationStorage) {
      this.pdfViewer.annotationStorage.cleanup()
    }
  }

  async importAnnotations(data) {
    const source = await new Promise((resolve) => {
      const reader = new FileReader();
      reader.onload = (event) => {
        const contents = event.target.result
        resolve(contents)
      };

      if (this.webviewerServer) {
        reader.readAsText(data);
      } else {
        reader.readAsArrayBuffer(data);
      }
    });
    if (this.webviewerServer) {
      const formData = new FormData();
      formData.append('xfdfFile', data);
      formData.append('pdfId', this._pdfId);

      await fetch(this.optionUrl.webviewBaseUrl + this.optionUrl.uploadAnnotationsUrl, {
        method: 'POST',
        headers: {
          'Authorization': this._token
        },
        body: formData
      })
        .then((response) => response.json())
        .then((res) => {
          if (res.code === "200") {
            const data = res.data
            this._pdfId = data.pdfId
            const rawAnnotations = data.annotateJson
            const annotations = this.convertAnnotation(rawAnnotations)
            if (annotations) {
              this.annotations = null
              // this.emptyAnnotations()
              this.initAnnotations(annotations, true)
            }
          }
        })
        .catch((error) => {
          if (error.name !== 'AbortError') {
            console.log(error)
            return { error: true, message: error.message }
          }
        });
    } else {
      const xfdfBuffer = new Uint8Array(source)
      const result = await this.messageHandler.sendWithPromise('XFDFImportAnnotations', {
        doc: this.doc,
        xfdfBuffer
      })

      if (result && result.code) {
        console.log(result.message)
        alert(result.message)
        return false
      }

      this.emptyAnnotations()
      this.reRenderAnnotations()
    }
    // for (let i = 0; i < len; i++) {
    //   const item = annotationsXml[i]
    //   const data = {
    //     annotationType: 3,
    //     fontSize: 10,
    //     value: item.obj_attr.content,
    //     rotation: 0,
    //     position: item.obj_attr.position,
    //     type: 'custom'
    //   }
    //   this.pdfViewer._pages[annotationsXml[i].obj_attr.page].pdfAnnotationLayer.deserialize(data)
    // }
  }

  saveAnnotations() {
    if (!this.pdfDocument || !this.info) return
    let append_objects = []
    for (let [key, value] of this.pdfViewer.annotationStorage._storage) {
      append_objects.push(value.annotationObj);
    }
    this.uploadAnnotations({
      append_objects,
      tid: this.info.tid
    })
  }

  initApiUrl(urls) {
    this.optionUrl = urls
  }

  setPassword(value) {
    if (this.webviewerServer) {
      const options = {
        method: 'POST',
        headers: {
          "Content-Type": "application/json",
          'Authorization': this._token
        },
        body: JSON.stringify({
          pdfId: this._pdfId,
          annotateHandles: [{
            operate: 'set-password',
            userPassword: value
          }]
        }),
      }

      return fetch(this.optionUrl.webviewBaseUrl + this.optionUrl.editUrl, options)
        .then((res) => {
          return res.json()
        })
        .then((data) => {
          if (data.code === "200") {
            return true
          } else {
            return false
          }
        })
        .catch((error) => {
          console.log(error)
          return false
        });
    } else {
      // 设置密码标志
      this.saveAction = 'set'
      this.#pwd = value
      return true
    }

  }

  removePassword() {
    if (this.webviewerServer) {
      const options = {
        method: 'POST',
        headers: {
          "Content-Type": "application/json",
          'Authorization': this._token
        },
        body: JSON.stringify({
          pdfId: this._pdfId,
          annotateHandles: [{
            operate: 'remove-password'
          }]
        }),
      }

      return fetch(this.optionUrl.webviewBaseUrl + this.optionUrl.editUrl, options)
        .then((res) => {
          return res.json()
        })
        .then((data) => {
          if (data.code === "200") {
            return true
          } else {
            return false
          }
        })
        .catch((error) => {
          console.log(error)
          return false
        });
    } else {
      if (this.#pwd) {
        // 移除密码标志
        this.saveAction = 'remove'
        this.#pwd = ''
      }
      return true
    }
  }

  getOptionUrl() {
    return this.optionUrl
  }

  uploadAnnotations(data) {
    const options = {
      method: 'POST',
      headers: {
        Accept: 'application/json',
        'Content-type': 'application/json',
      },
    }
    if (!data.append_objects.length) return
    options.body = JSON.stringify(data)
    fetch(this.optionUrl.baseUrl + this.optionUrl.saveAnnotations + '?f=' + this.info.token, options)
      .then((res) => {
        alert('Save Success')
        return res.json()
      })
      .then((resp) => resp)
      .catch((error) => ({ error: true, message: error.message }));
  }

  toggleSidebar() {
    this.pdfSidebar.toggle()
  }

  // 渲染PDF
  async load(pdfDocument) {
    this.pdfLinkService.setDocument(pdfDocument);

    const pdfViewer = this.pdfViewer;
    pdfViewer.setDocument(pdfDocument, this.annotations, this.pagesPtr, this.enableReply);
    const { firstPagePromise, onePageRendered, pagesPromise } = pdfViewer;

    
    pdfDocument.getDownloadInfo().then(({ length }) => {
      this._contentLength = length; // Ensure that the correct length is used.
      this.downloadComplete = true;

      firstPagePromise.then(() => {
        this.eventBus.dispatch("documentloaded", { source: this });
      });
    })

    if (this.pdfThumbnailViewer) {
      const pdfThumbnailViewer = this.pdfThumbnailViewer;
      pdfThumbnailViewer.setDocument(pdfDocument);
    }

    const doc = this.doc
    const messageHandler = this.messageHandler
    const outlines = await messageHandler.sendWithPromise('GetOutlines', {
      doc
    })
    const generateOutline = (outline, index, parent) => {
      const outlineItem = new Outline({
        outline,
        index,
        doc,
        messageHandler,
        parent
      })
      outline.children.forEach(function (outline, index) {
        outlineItem.children.push(generateOutline(outline, index, outlineItem))
      })

      return outlineItem
    }

    outlines.forEach((outline, index) => {
      const outlineItem = generateOutline(outline, index, null)
      this.outlines.push(outlineItem)
    })

    firstPagePromise.then(pdfPage => {
      let hash = null;

      let rotation = null;
      let sidebarView = 0
      let scrollMode = -1
      let spreadMode = -1

      this.setInitialView(hash, {
        rotation,
        sidebarView,
        scrollMode,
        spreadMode,
      });
    })

    this.eventBus.dispatch("documentinit", { source: this });
  }

  initBindEvents() {
    const { eventBus, _boundEvents } = this;

    eventBus._on("scalechanging", this.webViewerScaleChanging.bind(this));
  }

  // 绑定自定义事件
  bindEvents() {
    const { eventBus, _boundEvents } = this;

    eventBus._on("resize", this.webViewerResize.bind(this));
    eventBus._on("pagerendered", this.webViewerPageRendered.bind(this));
    eventBus._on("updateviewarea", this.webViewerUpdateViewarea.bind(this));
    eventBus._on("onPageNumberUpdated", this.webViewerPageChanging.bind(this));
    eventBus._on("rotationchanging", this.webViewerRotationChanging.bind(this));
    eventBus._on("sidebarviewchanged", this.webViewerSidebarViewChanged.bind(this));
    eventBus._on("presentationmodechanged", this.webViewerPresentationModeChanged.bind(this));
    eventBus._on("presentationmode", this.webViewerPresentationMode.bind(this));
    eventBus._on("nextpage", this.webViewerNextPage.bind(this));
    eventBus._on("previouspage", this.webViewerPreviousPage.bind(this));
    eventBus._on("zoomin", this.webViewerZoomIn.bind(this));
    eventBus._on("zoomout", this.webViewerZoomOut.bind(this));
    eventBus._on("pagenumberchanged", this.webViewerPageNumberChanged.bind(this));
    eventBus._on("scalechanged", this.webViewerScaleChanged.bind(this));
    eventBus._on("rotatecw", this.webViewerRotateCw.bind(this));
    eventBus._on("rotateccw", this.webViewerRotateCcw.bind(this));
    eventBus._on("switchscrollmode", this.webViewerSwitchScrollMode.bind(this));
    eventBus._on("scrollmodechanged", this.webViewerScrollModeChanged.bind(this));
    eventBus._on("switchspreadmode", this.webViewerSwitchSpreadMode.bind(this));
    eventBus._on("spreadmodechanged", this.webViewerSpreadModeChanged.bind(this));
    eventBus._on("findfromurlhash", this.webViewerFindFromUrlHash.bind(this));
    eventBus._on("updatefindmatchescount", this.webViewerUpdateFindMatchesCount.bind(this));
    eventBus._on("updatefindcontrolstate", this.webViewerUpdateFindControlState.bind(this));

    eventBus._on("fileinputchange", this.webViewerFileInputChange.bind(this));
    eventBus._on("openfile", this.webViewerOpenFile.bind(this));
    eventBus._on("switchannotationeditormode", this.webViewerSwitchAnnotationEditorMode.bind(this));
    eventBus._on("annotationsCountChanged", this.webViewerAnnotationsCountChanged.bind(this))
    eventBus._on("distanceChanged", this.webViewerDistanceChanged.bind(this));

    eventBus._on("annotationChange", this.handleAnnotationChange.bind(this));
    eventBus._on("createSignature", this.handleCreateSignature.bind(this));
    eventBus._on("handleField", this.handleField.bind(this));
    eventBus._on("pageNumberChanged", this.pageNumberChanged.bind(this));
    eventBus._on("updateSignatureAp", this.updateSignatureAp.bind(this));
    eventBus._on("isContentEditModyfied", this.setContentEditModyfied.bind(this));
  }

  bindWindowEvents() {
    const { eventBus, _boundEvents } = this;

    _boundEvents.windowResize = () => {
      eventBus.dispatch("resize", { source: window });
    };
    _boundEvents.windowHashChange = () => {
      eventBus.dispatch("hashchange", {
        source: window,
        hash: document.location.hash.substring(1),
      });
    };
    _boundEvents.windowUpdateFromSandbox = event => {
      eventBus.dispatch("updatefromsandbox", {
        source: window,
        detail: event.detail,
      });
    };

    window.addEventListener("resize", _boundEvents.windowResize);
    window.addEventListener("hashchange", _boundEvents.windowHashChange);
    window.addEventListener(
      "updatefromsandbox",
      _boundEvents.windowUpdateFromSandbox
    );
  }

  async close() {
    if (!this.pdfLoadingTask) {
      return
    }
    const promises = [];

    promises.push(this.pdfLoadingTask.destroy());
    this.pdfLoadingTask = null
    if (this.pdfDocument) {
      this.pdfDocument = null;

      this.pdfThumbnailViewer?.setDocument(null);
      this.pdfViewer.setDocument(null);
      this.pdfLinkService.setDocument(null);
    }
    this.isInitialViewSet = false;
    this.downloadComplete = false;
    this.url = "";
    this.baseUrl = "";
    this._downloadUrl = "";
    this.documentInfo = null;
    this.saveAction = null
    this.metadata = null;
    this._contentDispositionFilename = null;
    this._contentLength = null;
    this._saveInProgress = false;
    this._hasAnnotationEditors = false;

    this.pdfSidebar?.reset();
    this.pdfOutlineViewer?.reset();
    this.pdfAttachmentViewer?.reset();

    this.outlines = []
    this.#pwd = ''
    this._pdfId = null
    this.annotations = null

    await Promise.all(promises);
  }

  handleInk(annotation) {
    let rawAnnotation = JSON.parse(JSON.stringify(annotation))
    let inklist = ''
    let inkArray = []
    if (Array.isArray(rawAnnotation.inklist)) {
      inkArray = rawAnnotation.inklist
    }
    else {
      inkArray.push(rawAnnotation.inklist)
    }
    for (let i = 0; i < inkArray.length; i++) {
      inklist = inklist + inkArray[i].replaceAll(';', ',') + '//'
    }
    rawAnnotation.inklist = inklist// rawAnnotation.inklist.replaceAll(';', ',') + '//'
    return rawAnnotation
  }

  async handleAnnotationChange(data) {
    let annotation = data.annotation
    this.annotationHistory.push(annotation)
    let annotateHandles = []
    const formTypes = ['textfield', 'checkbox', 'radiobutton', 'listbox', 'combobox', 'pushbutton', 'signatureFields']
    const markupTypes = ['highlight', 'underline', 'strikeout', 'squiggly']
    if (data.type === 'add') {
      if (!Array.isArray(annotation)) {
        annotation = [annotation]
      }
      const length = annotation.length
      for (let i = 0; i < length; i++) {
        if (Number(annotation[i].pageIndex) + 1 > this.pagesCount) continue
        if (formTypes.includes(annotation[i].type)) {
          annotation[i].operate = "add-form"
        } else {
          annotation[i].operate = "add-annot"
          annotation[i].author = this.annotator
        }

        if (markupTypes.includes(annotation[i].type)) {
          const quadPoints = []
          if (!annotation[i].quadPoints[0].PointX && !annotation[i].quadPoints[0].PointY) {
            for (let j = 0; j < annotation[i].quadPoints.length; j++) {
              const quadPoint = annotation[i].quadPoints[j]
              const left = quadPoint.left
              const top = quadPoint.top
              const right = quadPoint.right
              const bottom = quadPoint.bottom

              quadPoints.push({
                PointX: left,
                PointY: top,
              })
              quadPoints.push({
                PointX: right,
                PointY: top,
              })
              quadPoints.push({
                PointX: left,
                PointY: bottom,
              })
              quadPoints.push({
                PointX: right,
                PointY: bottom,
              })
            }
            annotation[i].quadPoints = quadPoints
          }
        }

        const result = await this.handleAnnotations(annotation[i])
        if (result && result.code) return false
        if (this.pdfViewer) {
          this.pdfViewer.renderAnnotation(annotation[i], !!data.show)
        }
        annotation[i].targetPage = annotation[i].pageIndex * 1 + 1
        let currentAnnotation = JSON.parse(JSON.stringify(annotation[i]))
        if (currentAnnotation.inklist) {
          currentAnnotation = this.handleInk(currentAnnotation)
        }
        annotateHandles.push(currentAnnotation)
        if (annotation[i].type === 'redact') this.redactionList.push(annotation[i])
      }
    } else {
      let annotations = this.annotations[annotation.pageIndex]
      if (!annotations) return
      const index = findIndex(annotation.name, annotations) 
      if (this.webviewerServer) {
        annotation.index = index
      }
      if (data.type === 'delete') {
        if (!this.webviewerServer) {
          const annot = annotations[index]
          let result = null
          if (annot.replies?.length) {
            result = await this.messageHandler.sendWithPromise('RemoveFromPageIncludeReplyAnnot', {
              pagePtr: annot.pagePtr,
              annotPtr: annotation.annotPtr
            })
          } else {
            result = await this.messageHandler.sendWithPromise('RemoveAnnot', {
              annotPtr: annotation.annotPtr
            })
          }

          if (result && result.code) {
            console.log(result.message)
            alert(result.message)
            return
          }
        }
        annotations.splice(index, 1)
        if (!annotations.length) {
          delete this.annotations[annotation.pageIndex]
        }
        this.redactionList = this.redactionList.filter((item) => item.name !== annotation.name)
      } else {
        if (!this.webviewerServer) {
          annotation.doc = this.doc
          const result = await this.messageHandler.sendWithPromise('EditAnnotation', {
            annotation
          })

          if (result && result.code) {
            console.log(result.message)
            alert(result.message)
            return false
          }
        }
        const rawAnnotation = annotations[index]
        annotations[index] = {
          ...rawAnnotation,
          ...annotation
        }
      }
      annotation.targetPage = annotation.pageIndex + 1

      let currentAnnotation = annotation
      if (currentAnnotation.inklist) {
        currentAnnotation = this.handleInk(currentAnnotation)
      }
      annotateHandles.push(currentAnnotation)
    }

    this.eventBus.dispatch('annotationChanged', { annotations: this.annotations })

    if (this.webviewerServer) {
      this.#convertAnnotationsForBackend(annotateHandles)
      await this.#postAnnotations(annotateHandles)
    }

    this.eventBus.dispatch('redactAnnotationChanged', this.redactionList.length)

    this.isAnnotationModified = true
    return true
  }

  #convertAnnotationsForBackend(annotations) {
    for (let i = 0; i < annotations.length; i++) {
      const annotation = annotations[i]
      const { height } = this.pagesPtr[annotation.pageIndex]
      switch (annotation.type) {
        case 'text':
          annotation.borderColor = annotation.color
          annotation.fillTransparency = 1
          delete annotation.color
          break
        case 'freetext':
          annotation.fillColor = annotation.bgColor
          annotation.textColor = annotation.color
          delete annotation.color
          delete annotation.bgColor
          break
        case 'highlight':
        case 'underline':
        case 'strikeout':
        case 'squiggly':
          annotation.borderColor = annotation.color
          delete annotation.color
          break
        case 'square':
        case 'circle':
          annotation.arrow = false
          if (!annotation.linePoints) {
          const { left, top, right, bottom } = annotation.rect
            annotation.linePoints = [left, bottom, right, top]
          }
          if (!annotation.opacity && annotation.opacity !== 0) {
            annotation.transparency = 1
          }
          break
        case 'line':
          if (annotation.tail === 'OpenArrow' || annotation.arrow) {
            annotation.arrow = true
          } else {
            annotation.arrow = false
          }
          break
        case 'stamp':
          if (annotation.stampType === 'standard') {
            annotation.standardStampType = annotation.contents
          } else if (annotation.stampType === 'text') {
            annotation.textStampFirststring = annotation.contents
            annotation.textStampSecondstring = annotation.time
            annotation.textStampColor = annotation.stampColor
            annotation.textStampShape = annotation.stampShape

            delete annotation.time
            delete annotation.stampColor
            delete annotation.stampShape
          } else if (annotation.stampType === 'image') {
            annotation.type = 'image'
            delete annotation.annotPtr
            delete annotation.imageData
            delete annotation.pagePtr
          }
          delete annotation.contents
          break
        case 'textfield':
          annotation.type = 'text-field'
          annotation.color && (annotation.textColor = annotation.color)
          annotation.textAlignment && (annotation.alignment = ALIGNMAP[annotation.textAlignment] || 0)
          annotation.format = '0'
          delete annotation.color
          delete annotation.textAlignment
          break
        case 'checkbox':
          annotation.type = 'check-box'
          break
        case 'radiobutton':
          annotation.type = 'radio-button'
          break
        case 'listbox':
          annotation.type = 'list-box'
          annotation.color && (annotation.textColor = annotation.color)
          'selected' in annotation && (annotation.selecte = annotation.selected)
          delete annotation.selected
          delete annotation.color
          break
        case 'combobox':
          annotation.type = 'combo-box'
          annotation.color && (annotation.textColor = annotation.color)
          delete annotation.color
          break
        case 'pushbutton':
          annotation.type = 'push-button'
          annotation.color && (annotation.textColor = annotation.color)
          'destPage' in annotation && (annotation.destPage = annotation.destPage - 1)
          delete annotation.color
          break
        case 'reply':
          annotation.page = annotation.pageIndex
          annotation.targetPage = annotation.pageIndex + 1
          delete annotation.pageIndex
          break
      }

      if (annotation.operate === 'mod-form') {
        annotation.color && (annotation.textColor = annotation.color)
        'destPage' in annotation && (annotation.destPage = annotation.destPage - 1)
        delete annotation.color
      }

      for (let key in annotation) {
        switch (key) {
          case 'index':
            annotation.index += 1
            break
          case 'rect':
            annotation.rect = this.#formatRectForBackend(annotation.rect, annotation.pageIndex)
            break
          case 'linePoints':
            annotation.linePoints = this.#formatLinePointsForBackend(annotation.linePoints, annotation.pageIndex)
            break
          case 'borderWidth':
            annotation.lineWidth = annotation.borderWidth
            delete annotation.borderWidth
            break
          case 'isChecked':
            annotation.select = annotation.isChecked
            delete annotation.isChecked
            break
          case 'checkStyle':
            annotation.style = annotation.checkStyle
            delete annotation.checkStyle
            break
          case 'selected':
            annotation.selecte = annotation.selected
            delete annotation.selected
            break
          case 'items':
            const items = annotation.items
            let options = ''
            for (let i = 0; i < items.length; i++) {
              if (i === 0) {
                options = items[i].Value
              } else {
                options+=`,${items[i].Value}`
              }
            }
            annotation.options = options
            delete annotation.items
            break
          case 'textAlignment':
          annotation.textAlignment && (annotation.alignment = ALIGNMAP[annotation.textAlignment] || 0)
            delete annotation.textAlignment
            break
          case 'contents':
            annotation.content = annotation.contents
            delete annotation.contents
            break
          case 'opacity':
            annotation.transparency = annotation.opacity
            delete annotation.opacity
            break
          case 'quadPoints': {
            const quadPoints = annotation.quadPoints
            let points = ''
            for (let i = 0; i < quadPoints.length; i++) {
              if (i === 0) {
                points+=quadPoints[i].PointX
              } else {
                points+=`,${quadPoints[i].PointX}`
              }
              points+=`,${height - quadPoints[i].PointY}`
            }
            annotation.quadPoints = points
            break
          }
          case 'inkPointes':
            const inkPointes = annotation.inkPointes
            let points = ''
            for (let i = 0; i < inkPointes.length; i++) {
              const inkPoint = inkPointes[i]
              for (let j = 0; j + 1 < inkPoint.length; j+=2) {
                if (j === 0) {
                  points+=inkPoint[j].PointX
                } else {
                  points+=`,${inkPoint[j].PointX}`
                }

                points+=`,${height - inkPoint[j + 1].PointY}`
              }

              points+='//'
            }
            delete annotation.inkPointes
            annotation.inklist = points
            break
          case 'title':
            if (annotation.operate.indexOf('form') > -1) {
              annotation.title && (annotation.value = annotation.title)
              delete annotation.title
            }
            break
          case 'replies':
            for (const reply of annotation.replies) {
              reply.page = reply.pageIndex
              reply.targetPage = reply.pageIndex + 1
              delete reply.pageIndex
            }
            break
          case 'markedAnnotState':
            annotation.markedAnnotState = AnnotationState[annotation.markedAnnotState]
            break
          case 'reviewAnnotState':
            annotation.reviewAnnotState = AnnotationState[annotation.reviewAnnotState]
            break
          case 'repliesIndex':
            annotation.repliesIndex += 1
            break
        }
      }
    }
  }

  #formatRectForBackend(rect, pageIndex) {
    if (!this.pagesPtr[pageIndex]?.height) return
    const { height } = this.pagesPtr[pageIndex]
    const { left, top: rawTop, right, bottom: rawBottom } = rect
    const top = height - rawTop
    const bottom = height - rawBottom
    return `${left},${bottom},${right},${top}`
  }

  #formatLinePointsForBackend(linePoints, pageIndex) {
    if (!this.pagesPtr[pageIndex]?.height) return
    const { height } = this.pagesPtr[pageIndex]
    const top = height - linePoints[1]
    const bottom = height - linePoints[3]
    return `${linePoints[0]},${top},${linePoints[2]},${bottom}`
  }

  emptyAnnotations() {
    const annotations = this.annotations
    if (this.annotations) {
      for (let pageIndex in annotations) {
        this.pdfViewer._pages[pageIndex].emptyAnnotations()
      }
    }
  }

  handleCreateSignature(data) {
    this.eventBus.dispatch('annotationChange', {
      type: 'add',
      annotation: data
    })
  }

  webViewerDistanceChanged(evt) {
    this.distanceChangedCallback(evt.distance)
  }

  progress(level) {
    if (this.downloadComplete) {
      // Don't accidentally show the loading bar again when the entire file has
      // already been fetched (only an issue when disableAutoFetch is enabled).
      return
    }
    const percent = Math.round(level * 100);
    // When we transition from full request to range requests, it's possible
    // that we discard some of the loaded data. This can cause the loading
    // bar to move backwards. So prevent this by only updating the bar if it
    // increases.
    if (percent <= this.percent) {
      return
    }
    this.percent = percent;
  }

  async flattenPdf() {
    if (this._token && this._pdfId) {
      const data = {
        method: 'POST',
        headers: {
          "Content-Type": "application/json",
          'Authorization': this._token
        },
        body: JSON.stringify({
          pdfId: this._pdfId
        })
      }
      return fetch(this.optionUrl.webviewBaseUrl + this.optionUrl.flattenUrl, data)
        .then(async (res) => {
          const type = res.headers.get("content-type")
          if (type.startsWith("application/json")) {
            const errorObj = await res.json();
            return Promise.reject(errorObj);
          } else {
            return res.blob()
          }
        })
        .then((data) => {
          return data
        })
        .catch((error) => {
          console.log(error)
          return false
        });
    } else {
      const result = await this.messageHandler.sendWithPromise('FlattenPage', {
        doc: this.doc,
        pagesPtr: this.pagesPtr,
        saveType: 2,
        password: this.#pwd,
        oldPassword: this.#oldPwd
      })

      if (result && result.code) {
        console.log(result.message)
        alert(result.message)
        return false
      }

      const { blobData } = result
      return blobData
    }
  }

  async flattenPdfDownload() {
    const blobData = await this.flattenPdf()
    if (blobData) {
      saveAs(blobData, this._docName)
      return true
    } else {
      return false
    }
  }

  async loadCertificates(data) {
    const result = await this.messageHandler.sendWithPromise('LoadCertificates', data)
    return result
  }

  async download(download = false, data, type) {
    if (data) {
      data.forEach(file => {
        saveAs(file.url, file.fileName)
      })
    } else if (this._token && this._pdfId) {
      const data = {
        method: 'POST',
        headers: {
          "Content-Type": "application/json",
          'Authorization': this._token
        },
        body: JSON.stringify({
          pdfId: this._pdfId
        })
      }
      return fetch(this.optionUrl.webviewBaseUrl + this.optionUrl.saveUrl, data)
        .then(async (res) => {
          const type = res.headers.get("content-type")
          if (type.startsWith("application/json")) {
            const errorObj = await res.json();
            return Promise.reject(errorObj);
          } else {
            return res.blob()
          }
        })
        .then((data) => {
          if (download) {
            saveAs(data, this._docName)
          }
          return data
        })
        .catch((error) => {
          console.log(error)
          return false
        });
    } else {
      const saveTypeMap = {
        'set': 2,
        'remove': 3
      }
      const saveType = saveTypeMap[this.saveAction] || 1

      const blobData = await this.messageHandler.sendWithPromise('SaveDocumentByStream', {
        doc: this.doc,
        saveType: type || saveType,
        password: this.#pwd,
        oldPassword: this.#oldPwd
      })
      if (download) {
        saveAs(blobData, this._docName)
      }
      return blobData
    }
  }

  async exportXfdf(download = false) {
    let xfdfString = ''
    if (this.webviewerServer) {
      xfdfString = await fetch(this.optionUrl.webviewBaseUrl + this.optionUrl.downloadAnnotation + "?pdfId=" + this._pdfId, {
        method: 'GET',
        headers: {
          "Content-Type": "application/json",
          'Authorization': this._token
        }
      })
        .then(async (res) => {
          const type = res.headers.get("content-type")
          if (type.startsWith("application/json")) {
            const errorObj = await res.json();
            return Promise.reject(errorObj);
          } else {
            return res.text()
          }
        })
        .then((xfdfString) => {
          return xfdfString
        })
        .catch((error) => {
          console.log(error)
          return false
        });
    } else {
      const result = await this.messageHandler.sendWithPromise('XFDFExportAnnotations', {
        doc: this.doc
      })

      if (result && result.code) {
        console.log(result.message)
        alert(result.message)
        return false
      }

      xfdfString = result.Content
    }

    if (download) {
      const name = this._docName.replace(/\.pdf$/, '.xfdf')
      const blob = new Blob([xfdfString], { type: 'application/xfdf' })
      saveAs(blob, name)
      return true
    } else {
      return xfdfString
    }
  }

  getScrollViewElement() {
    return this.scrollContainer
  }

  getViewerElement() {
    return this.viewerContainer
  }

  getSelectedPage(firstCoordinates, lastCoordinates) {
    let firstPageIndex = null, lastPageIndex = null
    for (let i = 0; i < this.pagesCount; i++) {
      const el = this.pdfViewer._pages[i].div
      const left = el.offsetLeft
      const right = left + el.clientWidth
      const top = el.offsetTop + el.offsetParent?.offsetTop
      const bottom = top + el.clientHeight

      null === firstPageIndex && firstCoordinates.x <= right && firstCoordinates.x >= left && firstCoordinates.y <= bottom && firstCoordinates.y >= top && (firstPageIndex = i + 1)
      null === lastPageIndex && lastCoordinates.x <= right && lastCoordinates.x >= left && lastCoordinates.y <= bottom && lastCoordinates.y >= top && (lastPageIndex = i + 1)
      if (null !== firstPageIndex && null !== lastPageIndex) {
        break
      }
    }

    if (firstPageIndex > lastPageIndex) {
      let pages = [lastPageIndex, firstPageIndex]
      firstPageIndex = pages[0]
      lastPageIndex = pages[1]
    }

    return {
      first: firstPageIndex,
      last: lastPageIndex
    }
  }

  pageToWindow(coordinates, pageNumber, initial = true) {
    if (pageNumber > this.page && pageNumber < 1) return null
    const el = this.pdfViewer._pages[pageNumber - 1].div
    const left = el.offsetLeft
    const top = el.offsetTop + el.offsetParent?.offsetTop

    const scale = this.scale
    return {
      x: (initial ? coordinates.x * scale : coordinates.x) + left,
      y: (initial ? coordinates.y * scale : coordinates.y) + top
    }
  }

  windowToPage(coordinates, pageNumber, initial = true) {
    if (pageNumber > this.page && pageNumber < 1) return null
    const el = this.pdfViewer._pages[pageNumber - 1].div
    const left = el.offsetLeft
    const top = el.offsetTop + el.offsetParent?.offsetTop

    const scale = this.scale
    const x = initial ? (coordinates.x - left) / scale : coordinates.x - left
    const y = initial ? (coordinates.y - top) / scale : coordinates.y - top
    return {
      pageNumber,
      x,
      y
    }
  }

  _initializeViewerComponents({
    container,
    viewer,
    thumbnailView,
    annotationView,
    toggleButton,
  }) {
    this.scrollContainer = container
    const self = this
    this.scrollContainer.addEventListener('click', function(event) {
      self.eventBus.dispatch('click', event)
    })
    this.viewerContainer = viewer
    const pdfRenderingQueue = new PDFRenderingQueue();
    pdfRenderingQueue.onIdle = this._cleanup.bind(this);
    this.pdfRenderingQueue = pdfRenderingQueue;

    const pdfLinkService = new PDFLinkService();
    this.pdfLinkService = pdfLinkService;

    this.pdfViewer = new PDFViewer({
      container,
      viewer,
      documentViewer: this,
      messageHandler: this.messageHandler,
      annotationStore,
      tool: {
        tool: this.activeTool,
        color: this.color
      },
      annotationView,
      renderingQueue: pdfRenderingQueue,
      linkService: pdfLinkService,
      renderer: 'canvas',
      textLayerMode: 1,
      enablePrintAutoRotate: true,
      eventBus: this.eventBus,
      $t: this.$t,
      doc: this.doc
    });

    pdfRenderingQueue.setViewer(this.pdfViewer);
    pdfLinkService.setViewer(this.pdfViewer);

    if (thumbnailView) {
      this.pdfThumbnailViewer = new PDFThumbnailViewer({
        container: thumbnailView,
        renderingQueue: pdfRenderingQueue,
        linkService: pdfLinkService
      });
      pdfRenderingQueue.setThumbnailViewer(this.pdfThumbnailViewer);
    }

    this.pdfCursorTools = new PDFCursorTools({
      container,
      eventBus: this.eventBus,
      cursorToolOnLoad: 0,
    });

    if (this.supportsFullscreen) {
      this.pdfPresentationMode = new PDFPresentationMode({
        container,
        pdfViewer: this.pdfViewer,
        eventBus: this.eventBus,
      });
    }

    this.passwordPrompt = new PasswordPrompt(
      {
        dialog: document.getElementById("passwordDialog"),
        label: document.getElementById("passwordText"),
        input: document.getElementById("password"),
        submitButton: document.getElementById("passwordSubmit"),
        cancelButton: document.getElementById("passwordCancel"),
        $t: this.$t
      }
    );

    this.pdfSidebar = new PDFSidebar({
      elements: {
        toggleButton
      },
      pdfViewer: this.pdfViewer,
      pdfThumbnailViewer: this.pdfThumbnailViewer,
      eventBus: this.eventBus
    });
    this.pdfSidebar.onToggled = this.forceRendering.bind(this);

    this.contentEditManager = new ContentEditManager(this);
  }

  setInitialView(
    storedHash,
    { rotation, sidebarView, scrollMode, spreadMode } = {}
  ) {
    const setRotation = angle => {
      if (isValidRotation(angle)) {
        this.pdfViewer.pagesRotation = angle;
      }
    };
    const setViewerModes = (scroll, spread) => {
      if (isValidScrollMode(scroll)) {
        this.pdfViewer.scrollMode = scroll;
      }
      if (isValidSpreadMode(spread)) {
        this.pdfViewer.spreadMode = spread;
      }
    };
    this.isInitialViewSet = true;
    // this.pdfSidebar.setInitialView(sidebarView);

    setViewerModes(scrollMode, spreadMode);

    if (!this.pdfViewer.currentScaleValue) {
      // Scale was not initialized: invalid bookmark or scale was not specified.
      // Setting the default one.
      this.pdfViewer.currentScaleValue = DEFAULT_SCALE_VALUE;
    }

    this.pdfViewer.forceRendering()
    this.pdfSidebar?.isOpen && this.pdfThumbnailViewer.forceRendering();
  }

  requestFullScreenMode() {
    this.pdfPresentationMode?.request();
  }

  /**
   * @private
   */
  _cleanup() {
    if (!this.pdfDocument) {
      return; // run cleanup when document is loaded
    }
    this.pdfViewer.cleanup();
    this.pdfThumbnailViewer?.cleanup();
    // We don't want to remove fonts used by active page SVGs.
    this.pdfDocument.cleanup(
      /* keepLoadedFonts = */ this.pdfViewer.renderer === 'svg'
    );
  }

  cleanup() {
    this._cleanup()
  }

  zoomIn(steps) {
    if (this.pdfViewer.isInPresentationMode) {
      return;
    }
    const oldPage = this.page;
    this.pdfViewer.increaseScale({
      drawingDelay: 400
    });
    this.page = oldPage;
  }

  zoomOut(steps) {
    if (this.pdfViewer.isInPresentationMode) {
      return;
    }
    const oldPage = this.page;
    this.pdfViewer.decreaseScale({
      drawingDelay: 400
    });
    this.page = oldPage;
  }

  nextPage() {
    return this.pdfViewer.nextPage()
  }

  previousPage() {
    return this.pdfViewer.previousPage()
  }

  pageNumberChanged(value) {
    // Note that for `<input type="number">` HTML elements, an empty string will
    // be returned for non-number inputs; hence we simply do nothing in that case.
    if (value !== "") {
      this.pdfLinkService.goToPage(value);
    }

    // Ensure that the page number input displays the correct value, even if the
    // value entered by the user was invalid (e.g. a floating point number).
    if (
      value !== this.pdfViewer.currentPageNumber.toString() &&
      value !== this.pdfViewer.currentPageLabel
    ) {
      this.toolbar && this.toolbar.setPageNumber(
        pdfViewer.currentPageNumber,
        pdfViewer.currentPageLabel
      );
    }
  }

  scrollTo({
    left,
    top
  }) {
    scrollIntoView(this.viewerContainer, {
      left,
      top
    })
  }
  
  scrollToPercent({
    left,
    top
  }) {
    const scrollTotalHeight = this.scrollContainer.scrollHeight - this.scrollContainer.clientHeight;
    const scrollTotalWidth = this.scrollContainer.scrollWidth - this.scrollContainer.clientWidth;

    const topDistance = scrollTotalHeight * top / 100;
    const leftDistance = scrollTotalWidth * left / 100;

    this.scrollTo({
      top: topDistance,
      left: leftDistance
    });
  }

  webViewerAnnotationsCountChanged(evt) {
    this.annotationsNumChangedCallback(evt.annotationsCount);
  }

  forceRendering() {
    this.pdfRenderingQueue.printing = !!this.printService;
    this.pdfRenderingQueue.isThumbnailViewEnabled =
      this.pdfSidebar.visibleView === SidebarView.THUMBS;
    this.pdfRenderingQueue.renderHighestPriority();
  }

  webViewerResize() {
    const { pdfDocument, pdfViewer, pdfRenderingQueue } = this;

    if (pdfRenderingQueue.printing && window.matchMedia("print").matches) {
      // Work-around issue 15324 by ignoring "resize" events during printing.
      return;
    }

    if (!pdfDocument) {
      return;
    }
    const currentScaleValue = pdfViewer.currentScaleValue;
    if (
      currentScaleValue === "auto" ||
      currentScaleValue === "page-fit" ||
      currentScaleValue === "page-width"
    ) {
      // Note: the scale is constant for 'page-actual'.
      pdfViewer.resize(currentScaleValue)
    }
    // pdfViewer.update();
  }

  webViewerPageRendered({ pageNumber, error }) {
    // If the page is still visible when it has finished rendering,
    // ensure that the page number input loading indicator is hidden.
    /** if (pageNumber === this.page) {
      this.toolbar.updateLoadingIndicatorState(false);
    } */

    // Use the rendered page to set the corresponding thumbnail image.
    if (this.pdfSidebar?.visibleView === SidebarView.THUMBS) {
      const pageView = this.pdfViewer.getPageView(
        /* index = */ pageNumber - 1
      );
      const thumbnailView = this.pdfThumbnailViewer.getThumbnail(
        /* index = */ pageNumber - 1
      );
      if (pageView && thumbnailView) {
        thumbnailView.setImage(pageView);
      }
    }
  }

  get annotationMode() {
    return this._annotationMode
  }

  set annotationMode(mode) {
    this._annotationMode = mode;
  }

  webViewerSwitchAnnotationEditorMode(evt) {
    this.pdfViewer.annotationEditorMode = evt.mode;
  }

  switchTool(mode) {
    this.pdfCursorTools.switchTool(mode);
  }

  webViewerUpdateViewarea({ location }) {
    if (this.isInitialViewSet) {
      // Only update the storage when the document has been loaded *and* rendered.
      this.store
        ?.setMultiple({
          page: location.pageNumber,
          zoom: location.scale,
          scrollLeft: location.left,
          scrollTop: location.top,
          rotation: location.rotation,
        })
        .catch(() => {
          // Unable to write to storage.
        });
    }

    if (this.#textSearch) {
      this.#textSearch.updateMatch()
    }
  }

  webViewerScrollModeChanged(evt) {
    if (
      this.isInitialViewSet &&
      !this.pdfViewer.isInPresentationMode
    ) {
      // Only update the storage when the document has been loaded *and* rendered.
      this.store?.set("scrollMode", evt.mode).catch(() => {
        // Unable to write to storage.
      });
    }
  }

  webViewerScaleChanging(evt) {
    // this.toolbar.setPageScale(evt.presetValue, evt.scale);
    // this.pdfViewer.update();
    this.scaleChangedCallback && this.scaleChangedCallback(evt.scale)
  }

  webViewerRotationChanging(evt) {
    this.pdfThumbnailViewer.pagesRotation = evt.pagesRotation;

    this.forceRendering();
    // Ensure that the active page doesn't change during rotation.
    this.pdfViewer.currentPageNumber = evt.pageNumber;
  }

  webViewerPageChanging({ pageNumber, pageLabel }) {
    // this.toolbar.setPageNumber(pageNumber, pageLabel);
    // this.secondaryToolbar.setPageNumber(pageNumber);
    if (this.pdfSidebar?.visibleView === SidebarView.THUMBS) {
      this.pdfThumbnailViewer.scrollThumbnailIntoView(pageNumber);
    }
    this.pageChangedCallback(this.page)
    return pageNumber
  }

  webViewerSidebarViewChanged({ view }) {
    this.pdfRenderingQueue.isThumbnailViewEnabled =
      view === SidebarView.THUMBS;

    if (this.isInitialViewSet) {
      // Only update the storage when the document has been loaded *and* rendered.
      this.store?.set("sidebarView", view).catch(() => {
        // Unable to write to storage.
      });
    }
  }

  selectAnnotation(annotation) {
    const pageIndex = annotation.pageIndex
    const pageView = this.pdfViewer._pages[pageIndex]
    if (!pageView) return
    pageView.selectAnnotation(annotation)
  }

  jumpToAnnotation(annotation) {
    const { rect, pageIndex } = annotation
    const left = rect.left * this.scale
    const top = rect.top * this.scale - (annotation.type === 'text' ? 34 : 10)
    const pageView = this.pdfViewer._pages[pageIndex]
    scrollIntoView(pageView.div, {
      left,
      top
    })
  }

  webViewerPageMode({ mode }) {
    // Handle the 'pagemode' hash parameter, see also `PDFLinkService_setHash`.
    let view;
    switch (mode) {
      case "thumbs":
        view = SidebarView.THUMBS;
        break;
      case "bookmarks":
      case "outline": // non-standard
        view = SidebarView.OUTLINE;
        break;
      case "attachments": // non-standard
        view = SidebarView.ATTACHMENTS;
        break;
      case "layers": // non-standard
        view = SidebarView.LAYERS;
        break;
      case "none":
        view = SidebarView.NONE;
        break;
      default:
        console.error('Invalid "pagemode" hash parameter: ' + mode);
        return;
    }
    this.pdfSidebar.switchView(view, /* forceOpen = */ true);
  }

  async webViewerNamedAction(evt) {
    // Processing a couple of named actions that might be useful, see also
    // `PDFLinkService.executeNamedAction`.
    switch (evt.action) {
      case "GoToPage":
        this.appConfig.toolbar.pageNumber.select();
        break;

      case "Print":
        const url = await this.download(false, null, 3)
        this.triggerPrinting(url)
        return url

      case "SaveAs":
        this.downloadOrSave();
        break;
    }
  }

  triggerPrinting(data) {
    let url = data instanceof Blob ? URL.createObjectURL(data) : data
    url = decodeURIComponent(url)
    printJS({
      url,
      printable: url,
      type: 'pdf',
      showModal: false
    });
  }

  webViewerPresentationModeChanged(evt) {
    this.pdfViewer.presentationModeState = evt.state;
  }

  webViewerPresentationMode() {
    this.requestPresentationMode();
  }

  webViewerNextPage() {
    this.pdfViewer.nextPage();
  }

  webViewerPreviousPage() {
    this.pdfViewer.previousPage();
  }
  webViewerZoomIn() {
    this.zoomIn();
  }
  webViewerZoomOut() {
    this.zoomOut();
  }

  webViewerPageNumberChanged(evt) {
    const pdfViewer = this.pdfViewer;
    // Note that for `<input type="number">` HTML elements, an empty string will
    // be returned for non-number inputs; hence we simply do nothing in that case.
    if (evt.value !== "") {
      this.pdfLinkService.goToPage(evt.value);
    }

    // Ensure that the page number input displays the correct value, even if the
    // value entered by the user was invalid (e.g. a floating point number).
    if (
      evt.value !== pdfViewer.currentPageNumber.toString() &&
      evt.value !== pdfViewer.currentPageLabel
    ) {
      this.toolbar.setPageNumber(
        pdfViewer.currentPageNumber,
        pdfViewer.currentPageLabel
      );
    }
  }
  webViewerScaleChanged(value) {
    const oldPage = this.page;
    this.pdfViewer.currentScaleValue = value;
    this.page = oldPage;
  }
  webViewerRotateCw() {
    this.rotatePages(90);
  }
  webViewerRotateCcw() {
    this.rotatePages(-90);
  }
  webViewerSwitchScrollMode(mode) {
    this.pdfViewer.scrollMode = mode;
  }
  webViewerSwitchSpreadMode(mode) {
    this.pdfViewer.spreadMode = mode;
  }

  rotatePages(delta) {
    this.pdfViewer.pagesRotation += delta;
    // Note that the thumbnail viewer is updated, and rendering is triggered,
    // in the 'rotationchanging' event handler.
  }

  webViewerSpreadModeChanged(evt) {
    if (
      this.isInitialViewSet &&
      !this.pdfViewer.isInPresentationMode
    ) {
      // Only update the storage when the document has been loaded *and* rendered.
      this.store?.set("spreadMode", evt.mode).catch(() => {
        // Unable to write to storage.
      });
    }
  }

  webViewerFindFromUrlHash(evt) {
    this.eventBus.dispatch("find", {
      source: evt.source,
      type: "",
      query: evt.query,
      phraseSearch: evt.phraseSearch,
      caseSensitive: false,
      entireWord: false,
      highlightAll: true,
      findPrevious: false,
      matchDiacritics: true,
    });
  }

  webViewerUpdateFindMatchesCount({ matchesCount }) {
    if (this.supportsIntegratedFind) {
      this.externalServices.updateFindMatchesCount(matchesCount);
    } else {
      this.findBar.updateResultsCount(matchesCount);
    }
  }

  webViewerUpdateFindControlState({
    state,
    previous,
    matchesCount,
    rawQuery,
  }) {
    if (this.supportsIntegratedFind) {
      this.externalServices.updateFindControlState({
        result: state,
        findPrevious: previous,
        matchesCount,
        rawQuery,
      });
    } else {
      this.findBar.updateUIState(state, previous, matchesCount);
    }
  }

  webViewerFileInputChange(evt) {
    if (this.pdfViewer?.isInPresentationMode) {
      return; // Opening a new PDF file isn't supported in Presentation Mode.
    }
    const file = evt.fileInput.files[0];

    let url = URL.createObjectURL(file);
    if (file.name) {
      url = { url, originalUrl: file.name };
    }
    this.open(url);
  };

  // eslint-disable-next-line no-var
  webViewerOpenFile(evt) {
    const fileInput = this.appConfig.openFileInput;
    fileInput.click();
  }

  async setToolMode(mode) {
    const oldMode = this.toolMode;
    this.toolMode = mode;
    if (mode === 'editor' && !this.fontFileInited) {
      // await this.initFontFile()
    }
    if (['redaction', 'remove'].includes(this.activeTool) && this.toolMode !== 'security') {
      this.redactionList.length && this.applyRedactions()
      this.setTool({ color: '', tool: '' })
    }
    this.eventBus.dispatch("toolModeChanged", mode);

    if (oldMode === 'editor' && this.toolMode !== 'editor') {
      await this.updateTextPtr();
    }

    for (let page in this.annotations) {
      for (let i = 0; i < this.annotations[page].length; i++) {
        const annot = this.annotations[page][i]
        if (!annot) continue
        const element = document.getElementById(annot['name'])

        if (element) {
          if (this.toolMode !== 'form') {
            if (annot.isHidden == 1) {
              element.style.display = 'none';
              element.style.pointerEvents = 'none';
            } else if (annot.isHidden == 0) {
              element.style.display = '';
              element.style.pointerEvents = '';
            }
          } else {
            element.style.display = '';
            element.style.pointerEvents = '';
          }
        }
      }
    }
  }

  async updateTextPtr() {
    await this.messageHandler.sendWithPromise('ClearPage', {
      doc: this.doc
    })

    for (let pageIndex = 0; pageIndex < this.pagesCount; pageIndex++) {
      await this.messageHandler.sendWithPromise('ClearText', {
        textPtr: this.pagesPtr[pageIndex].textPtr
      })

      const pageSize = await this.messageHandler.sendWithPromise("GetPageSize", {
        doc: this.doc,
        pageIndex
      })

      const textPtr = await this.messageHandler.sendWithPromise('InitText', {
        pagePtr: pageSize.pagePtr
      })

      this.pagesPtr[pageIndex].textPtr = textPtr
      this.pagesPtr[pageIndex].pagePtr = pageSize.pagePtr
    }

    this.pdfViewer.pagesPtr = this.pagesPtr
    this.pdfViewer._pages.forEach(page => {
      page.pagePtr = this.pagesPtr[page.pageIndex].pagePtr
    });

    this.pdfViewer.refresh()

    return new Promise((resolve) => {
      const eventHandler = () => {
        this.eventBus._off('pagerendered', eventHandler)
        resolve()
      }
      this.eventBus._on('pagerendered', eventHandler)
    })
  }

  setPropertyPanel(props) {
    this.eventBus.dispatch("propertyPanelChanged", props);
  }

  setContentEditorProperty(type, props) {
    if (type === 'text') {
      this.eventBus.dispatch("textPropertyChanged", props);
    } else if (type === 'image') {
      this.eventBus.dispatch("imagePropertyChanged", props);
    }
  }

  setInitProperty() {
    if (this.activeTool === 'textfield') {
      this.eventBus.dispatch("initTextfieldName");
    } else if (this.activeTool === 'checkbox') {
      this.eventBus.dispatch("initCheckboxName");
    } else if (this.activeTool === 'radiobutton') {
      this.eventBus.dispatch("initRadioButtonName");
    } else if (this.activeTool === 'pushbutton') {
      this.eventBus.dispatch("initPushButtonName");
    }
  }

  handleField(prop) {
    const id = prop.id
    const key = prop.key
    const value = prop.value

    if (!id) return

    let annotationsAll = Object.values(annotationStore.annotationsAll).reduce(function (acc, curr) {
      return acc.concat(curr);
    }, []);
    let targetObj = annotationsAll.find(obj => obj.name === id)
    if (key && (value || value === 0)) {
      let props = {
        name: id
      }
      props[key] = value
      this.eventBus.dispatch("setProperty", props)
    }
    return targetObj
  }

  async handleSign(data) {
    const { type = 0, flag, param } = data

    if (flag === 'create' && !this.Signatures) {
      this.Signatures = new Signatures({
        pdfViewer: this.pdfViewer,
        eventBus: this.eventBus,
        type
      })
    }
    if (flag === 'save' && type) {
      const { pkcs12Buffer, password } = data
      await this.updateSignatureAp({
        type,
        annotPtr: param.annotation.annotPtr,
        pkcs12Buffer,
        password,
        ...param
      })
    }
    if (!this.Signatures) return

    if (flag === 'clear' || flag === 'create') {
      this.Signatures.clear()
    }
    if (flag === 'save') {
      this.Signatures.toImage(param)
    }
    if (flag === 'reset') {
      this.Signatures.reset()
      this.Signatures = null
    }
    if (flag === 'update') {
      this.Signatures.update(param)
    }
    if (flag === 'image') {
      return this.Signatures.getImageBase64(param)
    }
  }

  async handleStamp(data) {
    if (!data) return
    let imgUi = data.stampContainer
    let pageNum = data.page
    let pageView = this.pdfViewer.getPageView(pageNum - 1)
    let rect = null
    let annotData = null

    if (pageView && imgUi) {
      let pageWidth = pageView.viewport.viewBox[2]
      let pageHeight = pageView.viewport.viewBox[3]
      let imgWidth = imgUi.width
      let imgHeight = imgUi.height

      let left = (pageWidth - imgWidth) / 2
      let top = (pageHeight - imgHeight) / 2
      let right = (pageWidth + imgWidth) / 2
      let bottom = (pageHeight + imgHeight) / 2

      rect = {
        left,
        top,
        right,
        bottom,
      }
    }
    if (data.stampType === 'standard') {
      annotData = {
        operate: 'add-annot',
        type: 'stamp',
        pageIndex: pageNum - 1,
        rect,
        stampColor: 'white',
        date: new Date(),
        stampType: 'standard',
        contents: imgUi.alt
      }
    }
    if (data.stampType === 'dynamic') {
      annotData = {
        operate: 'add-annot',
        type: 'stamp',
        date: new Date(),
        pageIndex: pageNum - 1,
        contents: data.text,
        time: data.time,
        stampColor: data.color,
        stampType: 'text'
      }
    }
    annotData.stampShape = 0

    if (!annotData.rect) {
      const rect = await this.messageHandler.sendWithPromise('GetStampRect', {
        doc: this.doc,
        pagePtr: this.pagesPtr[pageNum - 1].pagePtr,
        annotation: annotData
      })
      if (rect && rect.code) {
        console.log(result.message)
        alert(result.message)
        return false
      }
      const pageWidth = pageView.viewport.viewBox[2]
      const pageHeight = pageView.viewport.viewBox[3]
      const imgWidth = rect.right - rect.left
      const imgHeight = rect.bottom - rect.top

      const left = (pageWidth - imgWidth) / 2
      const top = (pageHeight - imgHeight) / 2
      const right = (pageWidth + imgWidth) / 2
      const bottom = (pageHeight + imgHeight) / 2
      annotData.rect = {
        left,
        right,
        top,
        bottom
      }
    }
    this.eventBus.dispatch('annotationChange', {
      type: 'add',
      annotation: annotData
    })
  }

  async compare(data) {
    // if (this.webviewerServer) {
    //   const formData = new FormData()
    //   for (let key in data) {
    //     formData.append(key, data[key])
    //   }
    //   const options = {
    //     method: 'POST',
    //     headers: {
    //       'Authorization': this._token
    //     },
    //     body: formData
    //   }
    //   return fetch(this.optionUrl.webviewBaseUrl + this.optionUrl.compareUrl, options)
    //     .then(async (res) => {
    //       const type = res.headers.get("content-type")
    //       if (type.startsWith("application/json")) {
    //         const errorObj = await res.json();
    //         return Promise.reject(errorObj);
    //       } else {
    //         const filename = res.headers.get("Content-Disposition").split("filename=")[1];
    //         return { filename, blobData: res.blob(), type };
    //       }
    //     })
    //     .then(async ({ filename, blobData, type }) => {
    //       if (type === 'application/zip') {
    //         return await unzipAndReadFiles(blobData)
    //       } else {
    //         // return blobData.then(blob => {
    //         //   const reader = new FileReader();
    //         //   reader.onload = function(event) {
    //         //     const content = event.target.result;
    //         //     console.log(content); // 打印blobData的内容
    //         //   };
    //         //   reader.readAsDataURL(blob);
    //         //   return { filename, url: URL.createObjectURL(blob) };
    //         // })
    //         return blobData.then(blob => ({
    //           filename,
    //           url: URL.createObjectURL(blob)
    //         }));
    //       }
    //     })
    //     .then((resp) => resp)
    //     .catch((error) => ({ error: true, message: error.message }));
    // } else {
      
    //   // const { oldData, newData } = res
    //   // let oldBlobData = new Blob(oldData, { type: "application/pdf" })
    //   // saveAs(oldBlobData, 'old' + this._docName)


    //   // let newBlobData = new Blob(newData, { type: "application/pdf" })
    //   // saveAs(newBlobData, 'new' + this._docName)

    // }
    const result = await this.messageHandler.sendWithPromise('CompareDocument', {
      data
    })

    if (result && result.code) {
      console.log(result.message)
      alert(result.message)
      return false
    }
    
    if (data.type === 1) {
      return [{
        filename: data.leftFile.name.replace(/(\.pdf)/, `_${this.$t('compare.byComPDFKit')}.pdf`),
        url: URL.createObjectURL(result.oldDocument)
      }, {
        filename: data.rightFile.name.replace(/(\.pdf)/, `_${this.$t('compare.byComPDFKit')}.pdf`),
        url: URL.createObjectURL(result.newDocument)
      }, {
        filename: this.$t('compare.byComPDFKit') + '.pdf',
        url: URL.createObjectURL(result.blobData)
      }
      ]
    } else if (data.type === 2) {
      return {
        filename: this._docName,
        url: URL.createObjectURL(result.blobData)
      }
    }
  }

  async checkPassword(file, notUpdatePwd = false) {
    const parameters = {
      cMapUrl: CMAP_URL,
      cMapPacked: true,
      enableXfa: true
    };
    if (typeof file === "string") {
      // URL
      parameters.url = file
    } else if (file && "byteLength" in file) {
      // ArrayBuffer
      parameters.data = file;
    } else if (file.url && file.originalUrl) {
      parameters.url = file.url;
    }
    const loadingTask = getDocument(parameters)
    this.pdfLoadingTask = loadingTask
    let password = ''
    const getPwd = (pwd) => password = pwd
    loadingTask.onPassword = (updateCallback, reason) => {
      this.passwordPrompt.setUpdateCallback(updateCallback, reason, getPwd);
      this.passwordPrompt.open();
    };

    return await loadingTask.promise.then(() => {
      if (password && !notUpdatePwd) this.#pwd = password
      return password
    }).catch((error) => {
      return false
    })
  }

  handleCreateStatus({
    bool,
    fileData
  }) {
    annotationStore.creating = bool
    annotationStore.fileData = fileData
  }

  async initFontFile() {
    try {
      this.pdfViewer.setFontFile(this.#fileList)
      this.fontFileInited = true
    } catch (error) {
      console.log(error)
    }
  }

  async getPages(doc) {
    const pagesCount = await this.messageHandler.sendWithPromise("GetPageCount", { doc })

    const pages = []
    for (let pageIndex = 0; pageIndex < pagesCount; pageIndex++) {
      const page = await this.messageHandler.sendWithPromise("GetPageSize", {
        doc,
        pageIndex
      })

      pages.push({
        pagePtr: page.pagePtr,
        width: page.width,
        height: page.height
      })
    }
    return pages
  }

  getMinRadioSize(width, height, pageSize) {
    const radio = Math.min(
      pageSize.width / width,
      pageSize.height / height
    );
    return {
      width: width * radio,
      height: height * radio
    }
  }

  // 页面编辑 - 获取每个页面图片
  // 获取范围内的页面图片 index起始索引 num页面数量
  async getDocEditorPages(pageSize, index = 0, num = null) {
    if (!this.docEditorCopy) {
      const doc = await this.messageHandler.sendWithPromise('createEditorDoc', {
        doc: this.doc,
        password: this.#oldPwd || this.#pwd
      });
      const pages = await this.getPages(doc);

      this.docEditorCopy = { doc, pages };
    }

    const pages = this.docEditorCopy.pages;
    const radio = window.devicePixelRatio || 1;
  
    const canvasPool = [];
    const contextPool = [];

    for (let i = 0; i < (num ?? pages.length); i++) {
      if (this.toolMode !== 'document') {
        break;
      }

      const page = pages[i + index];
      const canvas = canvasPool.pop() || document.createElement('canvas');
      const context = contextPool.pop() || canvas.getContext('2d', { alpha: false });

      const { width, height } = this.getMinRadioSize(page.width, page.height, pageSize)

      const canvasWidth = Math.round(width * radio);
      const canvasHeight = Math.round(height * radio);

      const result = await this.messageHandler.sendWithPromise('RenderPageBitmap', {
        pagePtr: page.pagePtr,
        x: 0,
        y: 0,
        width: canvasWidth,
        height: canvasHeight
      });

      if (result && result.code) {
        console.log(result.message)
        alert(result.message)
        return
      }

      const { imageArray } = result

      canvas.width = canvasWidth;
      canvas.height = canvasHeight;

      const imageData = context.createImageData(canvas.width, canvas.height);
      const imageDataArray = new Uint8ClampedArray(imageArray.buffer);

      imageData.data.set(imageDataArray);
      context.putImageData(imageData, 0, 0);
      const imageURL = canvas.toDataURL('image/png', 1);

      const pageData = {
        type: 'page',
        img: imageURL,
        size: {
          width: page.width,
          height: page.height
        },
        rotation: 0
      };

      this.eventBus.dispatch('getDocEditorPages', {
        index: i + index,
        pageData
      });

      canvas.width = 0;
      canvas.height = 0;
      context.clearRect(0, 0, canvasWidth, canvasHeight);
      canvasPool.push(canvas);
      contextPool.push(context);
    }

    canvasPool.forEach(canvas => URL.revokeObjectURL(canvas.toDataURL()));
    canvasPool.length = 0;
    contextPool.length = 0;
  }

  async updatePages() {
    const blobData = await this.messageHandler.sendWithPromise('SaveDocumentByStream', {
      doc: this.doc,
      saveType: 2,
      password: this.#oldPwd,
      oldPassword: this.#oldPwd
    })

    const saveAction = this.saveAction
    await this.loadDocument(URL.createObjectURL(blobData), {
      filename: this._docName,
      notUpdatePwd: true,
      saveAction
    })
    this.eventBus.dispatch('onPagesUpdated')
  }

  /**
   * 
   * @param {number} pageIndex
   * @param {number} pageWidth
   * @param {number} pageHeight
   */
  async insertBlankPage(pageIndex = 0, pageWidth = 595, pageHeight = 842) {
    if (pageIndex > this.pagesCount || pageIndex < 0) return

    const result = await this.messageHandler.sendWithPromise('InsertPage', {
      doc: this.doc,
      index: pageIndex,
      width: pageWidth,
      height: pageHeight
    })

    if (result && result.code) {
      console.log(result.message)
      alert(result.message)
      return false
    }

    const { status } = result

    if(!status) {
      console.warn('insert page', status)
      return false
    }
    await this.updatePages()
  }

  /**
   *
   * @param {File} file
   * @param {number} pageIndex
   * @param {Array} range
   */
  async insertPages(file, pageIndex = 0, pagesIndex = [0], password = '') {
    if (!file || pageIndex >= this.pagesCount || pageIndex < 0) return

    let range = null
    if (Array.isArray(pagesIndex)) {
      range = pagesIndex.join(',')
    } else if (pagesIndex === 'all') {
      range = 'all'
    } else {
      return
    }

    const result = await this.messageHandler.sendWithPromise('ImportPagesAtIndex', {
      doc: this.doc,
      file,
      range,
      index: pageIndex,
      password,
    })

    if (result && result.code) {
      console.log(result.message)
      alert(result.message)
      return false
    }

    const { status } = result

    if(!status) {
      console.warn('insert pdf', status)
      return false
    }

    await this.updatePages()
  }

  /**
   * 
   * @param {Array} pagesIndex
   * @returns 
   */
  async removePages(pagesIndex) {
    pagesIndex.sort(function(prev, next) {
      return next - prev
    })
    let result = null
    pagesIndex.forEach(async index => {
      if (result && result.code) return

      if (index >= this.pagesCount || index < 0) return

      result = await this.messageHandler.sendWithPromise('RemovePage', {
        doc: this.doc,
        index
      })

      const { status } = result

      if(!status) console.warn('delete', status)
    })

    if (result && result.code) {
      console.log(result.message)
      alert(result.message)
      return false
    }

    await this.updatePages()
  }

  /**
   * 
   * @param {Array} pagesIndex
   * @param {number} angle
   * @returns 
   */
  async rotatePages(pagesIndex, angle) {
    let result = null
    pagesIndex.forEach(async index => {
      if (result && result.code) return

      if (index >= this.pagesCount || index < 0) return

      result = await this.messageHandler.sendWithPromise('RotatePage', {
        pagePtr: this.pagesPtr[index].pagePtr,
        rotation: angle
      })
    })

    if (result && result.code) {
      console.log(result.message)
      alert(result.message)
      return false
    }

    await this.updatePages()
  }

  /**
   * 
   * @param {number} pageIndex
   * @returns 
   */
  async copyPages(pagesIndex) {
    pagesIndex.sort(function(prev, next) {
      return next - prev
    })
    let result = null
    pagesIndex.forEach(async index => {
      if (result && result.code) return

      if (index >= this.pagesCount || index < 0) return

      result = await this.messageHandler.sendWithPromise('CopyPage', {
        doc: this.doc,
        index
      })

      const { status } = result

      if(!status) console.warn('copy', status)
    })

    if (result && result.code) {
      console.log(result.message)
      alert(result.message)
      return false
    }

    await this.updatePages()
  }

  /**
   * 
   * @param {Array} pagesIndex
   * @returns 
   */
  async extractPages(pagesIndex) {
    const range = pagesIndex.join(',')
    const result = await this.messageHandler.sendWithPromise('ExtractPage', {
      doc: this.doc,
      range
    })

    if (result && result.code) {
      console.log(result.message)
      alert(result.message)
      return false
    }

    const { blobDataList } = result

    if(!blobDataList) {
      console.warn('extract', 0)
      return false
    }

    return blobDataList[0]
  }

  /**
   * 
   * @param {Array} pagesIndex
   * @param {number} targetPageIndex
   * @returns 
   */
  async movePages(pagesIndex, targetPageIndex) {
    pagesIndex.sort((a, b) => a - b)
    targetPageIndex = Math.min(this.pagesCount, targetPageIndex)
    targetPageIndex = Math.max(0, targetPageIndex)

    for (let i = 0; i < pagesIndex.length; i++) {
      let index, tIndex

      if (pagesIndex[i] >= this.pagesCount || pagesIndex[i] < 0) continue

      if (pagesIndex[i] === targetPageIndex) {
        index = pagesIndex[i]
        tIndex = targetPageIndex
      } else if (pagesIndex[i] > targetPageIndex) {
        index = pagesIndex[i]
        if (pagesIndex.includes(targetPageIndex)) {
          tIndex = targetPageIndex
          for (let j = 0; j < pagesIndex.length; j++) {
            if (pagesIndex[j] === tIndex && index !== tIndex) tIndex++
          }
        } else {
          tIndex = targetPageIndex
        }
      } else {
        index = pagesIndex[i] - i
        tIndex = targetPageIndex - 1
      }

      if (pagesIndex.includes(index) && pagesIndex.includes(tIndex) && index === tIndex) continue

      const result = await this.messageHandler.sendWithPromise('MovePage', {
        doc: this.doc,
        index,
        targetIndex: tIndex
      })

      if (result && result.code) {
        console.log(result.message)
        alert(result.message)
        return false
      }

      const { status } = result

      if(!status) console.warn('move', status)
    }

    await this.updatePages()
  }

  // 页面编辑 - 保存
  async saveDocumentEdit(op) {
    const doc = this.docEditorCopy.doc
    const clearDocument = async () => {
      await this.messageHandler.sendWithPromise('ClearDocument', doc)
      this.docEditorCopy = null
    }
    let result = null

    if (typeof op !== 'object') {
      if (op === 'cancel') {
        await clearDocument()
        return
      }

      if (this.saveAction === 'remove') this.#pwd = ''
      const blobData = await this.messageHandler.sendWithPromise('SaveDocumentByStream', {
        doc,
        saveType: this.saveAction === 'remove' ? 3 : 2,
        password: this.#pwd,
        oldPassword: this.#oldPwd,

      })
      const filename = this._docName

      if (op === 'saveAs') return { blobData, filename }

      const newUrl = URL.createObjectURL(blobData)
      if (this.saveAction === 'set' && this.#oldPwd !== this.#pwd) this.#oldPwd = this.#pwd
      await clearDocument()
      return newUrl
    }

    if (op.type === 'insert') {
      if (op.file) {
        const result = await this.messageHandler.sendWithPromise('ImportPagesAtIndex', {
          doc,
          file: op.file,
          range: op.range,
          index: op.pageIndex,
          password: op.password
        })

        if (result && result.code) {
          console.log(result.message)
          alert(result.message)
          return false
        }

        const { status } = result
    
        if(!status) {
          console.warn('insert pdf', status)
          return false
        }
        
        this.docEditorCopy.pages = await this.getPages(doc)
        return this.docEditorCopy.pages.length
      } else {
        const result = await this.messageHandler.sendWithPromise('InsertPage', {
          doc,
          index: op.pageIndex,
          width: op.size.width,
          height: op.size.height
        })

        if (result && result.code) {
          console.log(result.message)
          alert(result.message)
          return false
        }

        const { status } = result

        if(!status) console.warn('insert page', status)
      }
    }
    if (op.type === 'delete') {
      op.selectedPageIndexArray.forEach(async index => {
        if (result && result.code) return

        result = await this.messageHandler.sendWithPromise('RemovePage', {
          doc,
          index
        })

        if (result && result.code) {
          console.log(result.message)
          alert(result.message)
          return
        }

        const { status } = result
  
        if(!status) console.warn('delete', status)
      })
    }
    if (op.type === 'rotate') {
      op.selectedPageIndexArray.forEach(async index => {
        if (result && result.code) return

        result = await this.messageHandler.sendWithPromise('RotatePage', {
          pagePtr: this.docEditorCopy.pages[index].pagePtr,
          rotation: op.rotation
        })

        if (result && result.code) {
          console.log(result.message)
          alert(result.message)
          return
        }
      })
    }
    if (op.type === 'copy') {
      op.selectedPageIndexArray.forEach(async index => {
        if (result && result.code) return

        result = await this.messageHandler.sendWithPromise('CopyPage', {
          doc,
          index
        })

        if (result && result.code) {
          console.log(result.message)
          alert(result.message)
          return false
        }

        const { status } = result
        if(!status) console.warn('copy', status)
      })
    }
    if (op.type === 'extract') {
      const result = await this.messageHandler.sendWithPromise('ExtractPage', {
        doc,
        range: op.range,
        separateFile: op.separateFile
      })

      if (result && result.code) {
        console.log(result.message)
        alert(result.message)
        return false
      }

      const { blobDataList } = result
      if(!blobDataList) console.warn('extract', false)

      if (op.separateFile) {
        const zip = new JSZip()

        let files = []
        for (let i = 0; i < blobDataList.length; i++) {
          const file = blobDataList[i]
          files.push({ blobData: file.blobData, filename: file.name + '.pdf' })
        }
        
        files.forEach((file) => {
          zip.file(file.filename, file.blobData, { binary: true })
        })
        
        zip.generateAsync({ type: 'blob' }).then((content) => {
          saveAs(content, 'extracted documents.zip')
        })
      } else {
        const filename = this._docName
        saveAs(blobDataList[0], filename)
      }
      return
    }
    if (op.type === 'move') {
      const result = await this.messageHandler.sendWithPromise('MovePage', {
        doc,
        index: op.pageIndex,
        targetIndex: op.targetPageIndex
      })

      if (result && result.code) {
        console.log(result.message)
        alert(result.message)
        return false
      }

      const { status } = result

      if(!status) console.warn('move', status)
    }

    if (result && result.code) {
      console.log(result.message)
      alert(result.message)
      return false
    }

    this.docEditorCopy.pages = await this.getPages(doc)
  }

  // iOS添加Editor图片
  addEditorImage(data) {
    if (!data) return
    const { imageBase64, width, height, pageNum } = data

    const pageView = this.pdfViewer.getPageView(pageNum - 1)
    if (pageView) {
      const rect = this.getAreaToPageRect(width, height, pageNum)

      pageView.contentContainer.addImageEditor({
        rect,
        imageBase64
      })
    }
  }

  addAnnotationImage(data) {
    if (!data) return
    const { imageBase64, width, height, pageNum } = data

    const rect = this.getAreaToPageRect(width, height, pageNum)

    const annotation = {
      operate: "add-annot",
      type: "stamp",
      stampType: 'image',
      pageIndex: pageNum - 1,
      imageBase64,
      rect,
      targetPage: pageNum,
      date: new Date(),
      name: uuidv4()
    }
    this.addAnnotations(annotation)
  }

  getAreaToPageRect(width, height, pageNum) {
    let pageView = this.pdfViewer.getPageView(pageNum - 1)

    if (pageView) {
      let pageWidth = pageView.viewport.viewBox[2] * 0.9
      let pageHeight = pageView.viewport.viewBox[3] * 0.9

      const imageRatio = width / height
      const rectRatio = pageWidth / pageHeight
      let scaledWidth, scaledHeight

      if (imageRatio > rectRatio) {
        scaledWidth = pageWidth
        scaledHeight = pageWidth / imageRatio
      } else {
        scaledWidth = pageHeight * imageRatio
        scaledHeight = pageHeight
      }

      const imgWidth = Math.min(pageWidth, scaledWidth)
      const imgHeight = Math.min(pageHeight, scaledHeight)

      const offsetX = pageView.viewport.viewBox[2] * 0.1
      const offsetY = pageView.viewport.viewBox[3] * 0.1

      const rect = {
        left: (pageWidth - imgWidth) / 2 + offsetX,
        top: (pageHeight - imgHeight) / 2 + offsetY,
        right: (pageWidth + imgWidth) / 2,
        bottom: (pageHeight + imgHeight) / 2,
      }
      return rect
    }
  }

  async getSignature(data) {
    const ratio = window.devicePixelRatio || 1

    const rect = data.rect
    const width = parseInt(rect.width * ratio)
    const height = parseInt(rect.height * ratio)
    rect.width = width
    rect.height = height

    const imageArray = await this.messageHandler.sendWithPromise('CreateSignatureAP', {
      rect,
      content: data.content,
      text: data.text ? data.text : '',
      isContentAlginLeft: data.isContentAlginLeft ? data.isContentAlginLeft : false,
      isDrawOnlyContent: data.isDrawOnlyContent ? data.isDrawOnlyContent : false,
      isDrowLogo: data.isDrowLogo ? data.isDrowLogo : false,
      logoBase64: data.logoBase64 ? data.logoBase64 : '',
      imageBase64: data.imageBase64 ? data.imageBase64 : ''
    })

    const canvas = document.createElement('canvas')
    canvas.style.width = width + 'px'
    canvas.style.height = height + 'px'
    canvas.width = width
    canvas.height = height
    let drawContext = canvas.getContext("2d")
    drawContext.clearRect(0, 0, canvas.width, canvas.height)
    let imageData = drawContext.createImageData(width, height)
    imageData.data.set(imageArray)
    drawContext.putImageData(imageData, 0, 0)
    const imgSrc = canvas.toDataURL("image/png", 1)
    return imgSrc
  }

  async deleteSignature(signature) {
    this.messageHandler.sendWithPromise('RemoveSignature', {
      doc: this.doc,
      signaturePtr: signature.signaturePtr
    })
    this.eventBus.dispatch('updateSignatureFieldAp', signature.signaturePtr)

    annotationStore.signatures.splice(signature.index, 1)
    await this.getSignatures()
  }

  async getSignatures() {
    const { signatures, signaturePtrList } = await this.messageHandler.sendWithPromise('GetSignatures', {
      doc: this.doc
    })
    annotationStore.signatures = signatures
    this.eventBus.dispatch('getSignatures', annotationStore.signatures)
    return signaturePtrList
  }

  async addToTrustedCertificateLists(index) {
    const signerPtr = annotationStore.signatures[index].signerList[0].signerPtr
    const res = await this.messageHandler.sendWithPromise('AddToTrustedCertificates', {
      signerPtr
    })
    annotationStore.signatures[index].signerList[0].isCertTrusted = !!res
    this.eventBus.dispatch('getSignatures', annotationStore.signatures)
    return res
  }

  async updateSignatureAp(data) {
    const { type, pagePtr, rect, annotPtr, imageArray, imageBase64, annotation } = data

    const oldIndex = this.autoJumpNextSign ? Object.values(this.annotations).findIndex(item => item.name === annotation.name) : 0
    
    if (type) {
      const { content, isDrawOnlyContent = false, isDrowLogo = false, isContentAlginLeft, text, logoBase64, pkcs12Buffer, password, } = data
      const blobData = await this.messageHandler.sendWithPromise('UpdateDigitalSignAp', {
        doc: this.doc,
        annotPtr,
        content,
        isDrawOnlyContent,
        isDrowLogo,
        isContentAlginLeft,
        text,
        logoBase64,
        imageBase64,
        pkcs12Buffer,
        password,
      })
      const newUrl = URL.createObjectURL(blobData)
      const filename = this._docName
      const pageNumber = this.page
      await this.loadDocument(newUrl, { filename, notUpdatePwd: true })
      this.page = pageNumber
      this.eventBus.dispatch('onPagesUpdated', { pageNumber })
      URL.revokeObjectURL(blobData)
    } else {
      await this.messageHandler.sendWithPromise('UpdateSignatureAP', {
        annotPtr,
        pagePtr,
        rect,
        imageArray,
        imageBase64
      })
      this.eventBus.dispatch('updateSignatureFieldAp', annotPtr)
    }

    if (this.autoJumpNextSign) {
      const annotations = Object.values(this.annotations)
      const signatureFieldsNotSign = annotations
      .slice(oldIndex)
      .concat(annotations.slice(0, oldIndex))
      .flat()
      .filter(item => item.type === 'signatureFields' && !item.isSigned)

      const index = signatureFieldsNotSign.findIndex(item => item.name === annotation.name)
      const nextSign = signatureFieldsNotSign[index + 1]
      nextSign && this.jumpToAnnotation(nextSign)
    }
  }

  async verifySignature() {
    await this.getSignatures()
  }

  // 处理回复注释
  async handleReplyAnnotation(data) {
    const { type, annotation, reply, reviewAnnotState, markedAnnotState } = data
    const { pagePtr, annotPtr } = annotation

    const annot = this.annotations[annotation.pageIndex].find(item => item?.name === annotation.name)
    if (!annot) return
    let annotateHandles = []
    
    if (type === 'add') {
      if (reply) { // 添加回复
        reply.name = uuidv4()
        if (!this.webviewerServer) {
          await this.messageHandler.sendWithPromise('CreateReplyAnnotation', {
            pagePtr,
            annotPtr,
            reply
          })
          const { replies, markedAnnotState, reviewAnnotState } = await this.messageHandler.sendWithPromise('GetReplyAnnotation', {
            pagePtr,
            annotPtr
          })
          if (replies.length) {
            replies.forEach(reply => reply.name = uuidv4())
            annot.replies = replies
          }
          if (reviewAnnotState) annot.reviewAnnotState = reviewAnnotState
          if (markedAnnotState) annot.markedAnnotState = markedAnnotState
        } else {
          const annotations = this.annotations[annotation.pageIndex]
          const index = findIndex(annot.name, annotations)
          annot.index = index

          annotateHandles.push({
            operate: 'add-annot',
            type: 'reply',
            pageIndex: annot.pageIndex,
            title: reply.author,
            content: reply.contents,
            date: reply.date,
            index: annot.index
          })
          if (!annot.replies) annot.replies = []
          annot.replies.push(reply)

          annotations.push(reply)
        }
      }
      if (reviewAnnotState || markedAnnotState) { // 添加有状态回复（首次设置注释状态）
        if (!this.webviewerServer) {
          const newState = await this.messageHandler.sendWithPromise('CreateReplyStateAnnotation', {
            pagePtr,
            annotPtr,
            state: reviewAnnotState || markedAnnotState
          })
          if (markedAnnotState) annot.markedAnnotState = newState
          else annot.reviewAnnotState = newState
        } else {
          const annotations = this.annotations[annotation.pageIndex]
          const index = findIndex(annot.name, annotations)
          const data = {
            operate: 'mod-annot',
            index,
            pageIndex: annot.pageIndex,
            targetPage: annot.pageIndex + 1
          }
          if (markedAnnotState) {
            annot.markedAnnotState = markedAnnotState
            data.markedAnnotState = markedAnnotState
          }
          if (reviewAnnotState) {
            annot.reviewAnnotState = reviewAnnotState
            data.reviewAnnotState = reviewAnnotState
          }
          annotateHandles.push(data)
        }
      }
    } else if (type === 'delete') {
      if (!this.webviewerServer) {
        await this.messageHandler.sendWithPromise('RemoveAnnot', {
          annotPtr: reply.annotPtr
        })
        annot.replies = annot.replies.filter(item => item.name !== reply.name)
        annot.replies.forEach(item => {
          if (item.index > reply.index) {
            item.index--
          }
        })
  
      } else {
        annotateHandles.push({
          operate: 'del-annot',
          index: annot.index,
          repliesIndex: reply.index,
          pageIndex: reply.pageIndex,
          targetPage: reply.pageIndex + 1
        })
        const replyIndex = findIndex(reply.name, annot.replies)
        annot.replies.splice(replyIndex, 1)
        annot.replies.map((item, index) => item.index = index)
  
        const annotations = this.annotations[annotation.pageIndex]
        const index = findIndex(reply.name, annotations)
        if (index > -1) annotations.splice(index, 1)
      }
    } else if (type === 'edit') {
      if (markedAnnotState || reviewAnnotState) { // 修改注释状态
        if (!this.webviewerServer) {
          const newState = await this.messageHandler.sendWithPromise('SetState', {
            reviewAnnotState,
            markedAnnotState
          })
          if (markedAnnotState) {
            annot.markedAnnotState = newState
          }
          if (reviewAnnotState) {
            annot.reviewAnnotState = newState
          }
        } else {
          const annotations = this.annotations[annotation.pageIndex]
          const index = findIndex(annot.name, annotations)
          const data = {
            operate: 'mod-annot',
            index,
            pageIndex: annot.pageIndex,
            targetPage: annot.pageIndex + 1
          }
          if (markedAnnotState) {
            annot.markedAnnotState = markedAnnotState
            data.markedAnnotState = markedAnnotState
          }
          if (reviewAnnotState) {
            annot.reviewAnnotState = reviewAnnotState
            data.reviewAnnotState = reviewAnnotState
          }
          annotateHandles.push(data)
        }
      } else { // 修改回复内容
        if (!this.webviewerServer) {
          this.messageHandler.sendWithPromise('EditReplyAnnotation', {
            reply
          })
        }
        const index = annot.replies.findIndex(obj => obj.name === reply.name)
        const replyItem = annot.replies[index]
        annot.replies[index] = {
          ...replyItem,
          ...reply
        }
        if (this.webviewerServer) {
          annotateHandles.push({
            operate: 'mod-annot',
            index: annot.index,
            repliesIndex: reply.index,
            pageIndex: reply.pageIndex,
            targetPage: reply.pageIndex + 1,
            content: reply.contents,
            date: new Date(),
            author: this.annotator
          })
  
          const replayAnnotation = this.annotations[annotation.pageIndex].find(item => item?.name === replyItem.name)
          replayAnnotation.contents = reply.contents
        }
      }
    }

    this.eventBus.dispatch('annotationChanged', { annotations: this.annotations })

    if (this.webviewerServer) {
      this.#convertAnnotationsForBackend(annotateHandles)
      this.#postAnnotations(annotateHandles)
    }

    this.isAnnotationModified = true
  }

  async #postAnnotations(annotateHandles) {
    const options = {
      method: 'POST',
      headers: {
        "Content-Type": "application/json",
        'Authorization': this._token
      },
      body: JSON.stringify({
        pdfId: this._pdfId,
        annotateHandles
      }),
    }
    return await fetch(this.optionUrl.webviewBaseUrl + this.optionUrl.editUrl, options)
      .then((res) => {
        return res.json()
      })
      .then((data) => {
        if (data.code === "200") {
          return true
        } else {
          return false
        }
      })
      .catch((error) => (console.log(error)));
  }

  addReplyAnnotation(annotation, reply) {
    this.handleReplyAnnotation({
      type: 'add',
      annotation: [annotation],
      reply: [reply]
    })
  }

  delReplyAnnotation(reply) {
    this.handleReplyAnnotation({
      type: 'delete',
      reply: [reply]
    })
  }

  editReplyAnnotation(annotation, reply) {
    this.handleReplyAnnotation({
      type: 'edit',
      annotation: [annotation],
      reply: [reply]
    })
  }

  setAnnotationState(data) {
    const { annotation, reviewAnnotState, markedAnnotState } = data

    if (markedAnnotState) {
      this.handleReplyAnnotation({
        type: 'edit',
        annotation,
        markedAnnotState
      })
    } else if (reviewAnnotState) {
      this.handleReplyAnnotation({
        type: 'edit',
        annotation,
        reviewAnnotState
      })
    }
  }

  setHighlightLink(bool) {
    annotationStore.highlightLink = bool
    this.eventBus.dispatch('setHighlightLink', bool)
  }

  setHighlightForm(bool) {
    annotationStore.highlightForm = bool
    this.eventBus.dispatch('setHighlightForm', bool)
  }

  setAnnotator(name) {
    this.annotator = name
  }

  handleTextPopup(tool) {
    this.eventBus.dispatch('textPopupClicked', tool)
  }

  handleCropPagePopup(data) {
    this.eventBus.dispatch('cropPagePopupClicked', data)
  }

  handleContentEditorPopup(tool) {
    this.eventBus.dispatch('contentEditorPopupClicked', tool)
  }

  async getPageAreaImage (data) {
    const { pageNumber, left, right, bottom, top } = data
    const ratio = window.devicePixelRatio || 1

    const scale = this.scale * ratio
    const leftInt = parseInt(left * scale)
    const rightInt = parseInt(right * scale)
    const bottomInt = parseInt(bottom * scale)
    const topInt = parseInt(top * scale)

    const { imageArray } = await this.messageHandler.sendWithPromise('PushRenderTask', {
      pagePtr: this.pagesPtr[pageNumber - 1].pagePtr,
      scale,
      left: leftInt,
      right: rightInt,
      bottom: bottomInt,
      top: topInt,
      flag: 1,
      form: 1
    })
    const imageUrl = this.imageArrayToUrl(imageArray, rightInt - leftInt, bottomInt - topInt)
    return imageUrl
  }

  // 图片Unit8Array数据转为图片链接
  imageArrayToUrl (imageArray, width, height) {
    let canvas = document.createElement('canvas')
    const context = canvas.getContext('2d')

    canvas.width = width
    canvas.height = height
    
    const imageData = context.createImageData(width, height)
    const imageDataArray = new Uint8ClampedArray(imageArray.buffer)

    imageData.data.set(imageDataArray)
    context.putImageData(imageData, 0, 0)
    const url = canvas.toDataURL('image/png', 1)

    canvas.remove()
    canvas = null

    return url
  }
  
  // 内容编辑 - undo/redo
  async resetOperate(data) {
    const { operation, pageNumber } = data
    this.pdfViewer._pages[pageNumber - 1].contentContainer.handleResetOperate(operation)
  }

  getContentEditManager() {
    return this.contentEditManager
  }

  getContentEditHistoryManager() {
    return this.pdfViewer.contentEditHistoryManager
  }

  setContentEditModyfied(value) {
    this.isContentEditModyfied = value
  }

  async applyRedactions(redaction) {
    if (!redaction) {
      await this.delAnnotations(this.redactionList)
      this.redactionList.length = 0
      this.pdfViewer.refresh()
      return
    }

    let redactionList
    if (redaction === 'all') {
      redactionList = [...this.redactionList]
      await this.delAnnotations(this.redactionList)
      this.redactionList.length = 0
    } else {
      const redactions = Array.isArray(redaction) ? redaction : [redaction]
      redactionList = redactions
    }

    for (const item of redactionList.filter(item => !item.erasure)) {
      await this.messageHandler.sendWithPromise('ApplyRedaction', { annotPtr: item.annotPtr })
    }
    for (const item of redactionList.filter(item => item.erasure)) {
      if (item.rects && item.rects.length) {
        for (const rect of item.rects) {
          await this.messageHandler.sendWithPromise('ErasureRedactionFromRect', { pagePtr: item.pagePtr, rect })
        }
      } else {
        await this.messageHandler.sendWithPromise('ErasureRedactionFromRect', { pagePtr: item.pagePtr, rect: item.rect })
      }
    }
    
    await this.updateTextPtr()
    this.emptyAnnotations()
    this.pdfViewer.refresh()
    this.reRenderAnnotations()

    return new Promise((resolve) => {
      const eventHandler = () => {
        this.eventBus._off('annotationChanged', eventHandler)
        this.pdfViewer.refresh()
        resolve()
      }
      this.eventBus._on('annotationChanged', eventHandler)
    })
  }

  setAutoJumpNextSign(value) {
    this.autoJumpNextSign = value
  }
}

class PDFWorker {
  destroyed = false
  constructor() {
    this._promiseCapability = new PromiseExt()
    this._messageHandler = null
    this._webWorker = null
    this._initialize()
  }

  _initialize() {
    try {
      const workerSrc = "./lib/PDFWorker.js"
      const worker = new Worker(workerSrc)
      const messageHandler = new MessageHandler('main', 'worker', worker)
      this._messageHandler = messageHandler

      const terminateEarly = () => {
        worker.removeEventListener("error", onWorkerError)
        messageHandler.destroy()
        worker.terminate()
        if (this.destroyed) {
          this._promiseCapability.reject(new Error("Worker was destroyed"))
        } else {
          console.error("The worker has been disabled.")
        }
      }

      const onWorkerError = () => {
        if (!this._webWorker) {
          // Worker failed to initialize due to an error. Clean up and fall
          // back to the fake worker.
          terminateEarly();
        }
      }

      worker.addEventListener("error", onWorkerError)

      messageHandler.on("test", (data) => {
        worker.removeEventListener("error", onWorkerError)
        if (this.destroyed) {
          terminateEarly();
          return; // worker was destroyed
        }
        if (data) {
          this._messageHandler = messageHandler
          this._webWorker = worker

          this._promiseCapability.resolve(true);
        }
      })

      messageHandler.on("ready", data => {
        worker.removeEventListener("error", onWorkerError);
        if (this.destroyed) {
          terminateEarly();
          return; // worker was destroyed
        }
        try {
          sendTest();

          messageHandler.send("Ready", true)
        } catch {
          console.error("The worker has been disabled.")
        }
      })

      const sendTest = () => {
        const testObj = new Uint8Array();
        // Ensure that we can use `postMessage` transfers.
        messageHandler.send("test", testObj);
      }

      sendTest()
    } catch (error) {
      console.log(error)
      console.log("The worker has been disabled.")
    }
  }

  get promise() {
    return this._promiseCapability.promise;
  }

  get messageHandler() {
    return this._messageHandler;
  }

  destroy() {
    this.destroyed = true
    if (this._webWorker) {
      // We need to terminate only web worker created resource.
      this._webWorker.terminate()
      this._webWorker = null
    }
    if (this._messageHandler) {
      this._messageHandler.destroy()
      this._messageHandler = null
    }
  }
}

// 解压缩zip文件
function unzipAndReadFiles(zipBlob) {
  return new Promise((resolve, reject) => {
    const jzip = new JSZip();

    jzip.loadAsync(zipBlob)
      .then((zipFiles) => {
        const filePromises = Object.keys(zipFiles.files).map(fileName => {
          return zipFiles.file(fileName).async("uint8array").then(fileData => {
            const blob = new Blob([fileData], { type: 'application/octet-stream' });
            const url = URL.createObjectURL(blob);
            return { fileName, url };
          });
        });

        Promise.all(filePromises)
          .then(resolve)
          .catch(reject);
      })
      .catch((error) => {
        reject(error);
      });
  });
}

export default ComPDFKitViewer
