| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162 | /* Copyright 2012 Mozilla Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *     http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */// eslint-disable-next-line max-len/** @typedef {import("../src/display/display_utils").PageViewport} PageViewport */// eslint-disable-next-line max-len/** @typedef {import("../src/display/optional_content_config").OptionalContentConfig} OptionalContentConfig *//** @typedef {import("./event_utils").EventBus} EventBus *//** @typedef {import("./interfaces").IL10n} IL10n *//** @typedef {import("./interfaces").IRenderableView} IRenderableView */// eslint-disable-next-line max-len/** @typedef {import("./pdf_rendering_queue").PDFRenderingQueue} PDFRenderingQueue */import {  AbortException,  AnnotationMode,  createPromiseCapability,  PixelsPerInch,  RenderingCancelledException,  setLayerDimensions,  shadow,  SVGGraphics,} from "pdfjs-lib";import {  approximateFraction,  DEFAULT_SCALE,  docStyle,  OutputScale,  RendererType,  RenderingStates,  roundToDivide,  TextLayerMode,} from "./ui_utils.js";import { AnnotationEditorLayerBuilder } from "./annotation_editor_layer_builder.js";import { AnnotationLayerBuilder } from "./annotation_layer_builder.js";import { compatibilityParams } from "./app_options.js";import { NullL10n } from "./l10n_utils.js";import { StructTreeLayerBuilder } from "./struct_tree_layer_builder.js";import { TextAccessibilityManager } from "./text_accessibility.js";import { TextHighlighter } from "./text_highlighter.js";import { TextLayerBuilder } from "./text_layer_builder.js";import { XfaLayerBuilder } from "./xfa_layer_builder.js";/** * @typedef {Object} PDFPageViewOptions * @property {HTMLDivElement} [container] - The viewer element. * @property {EventBus} eventBus - The application event bus. * @property {number} id - The page unique ID (normally its number). * @property {number} [scale] - The page scale display. * @property {PageViewport} defaultViewport - The page viewport. * @property {Promise<OptionalContentConfig>} [optionalContentConfigPromise] - *   A promise that is resolved with an {@link OptionalContentConfig} instance. *   The default value is `null`. * @property {PDFRenderingQueue} [renderingQueue] - The rendering queue object. * @property {number} [textLayerMode] - Controls if the text layer used for *   selection and searching is created. The constants from {TextLayerMode} *   should be used. The default value is `TextLayerMode.ENABLE`. * @property {number} [annotationMode] - Controls if the annotation layer is *   created, and if interactive form elements or `AnnotationStorage`-data are *   being rendered. The constants from {@link AnnotationMode} should be used; *   see also {@link RenderParameters} and {@link GetOperatorListParameters}. *   The default value is `AnnotationMode.ENABLE_FORMS`. * @property {string} [imageResourcesPath] - Path for image resources, mainly *   for annotation icons. Include trailing slash. * @property {boolean} [useOnlyCssZoom] - Enables CSS only zooming. The default *   value is `false`. * @property {boolean} [isOffscreenCanvasSupported] - Allows to use an *   OffscreenCanvas if needed. * @property {number} [maxCanvasPixels] - The maximum supported canvas size in *   total pixels, i.e. width * height. Use -1 for no limit. The default value *   is 4096 * 4096 (16 mega-pixels). * @property {Object} [pageColors] - Overwrites background and foreground colors *   with user defined ones in order to improve readability in high contrast *   mode. * @property {IL10n} [l10n] - Localization service. * @property {function} [layerProperties] - The function that is used to lookup *   the necessary layer-properties. */const MAX_CANVAS_PIXELS = compatibilityParams.maxCanvasPixels || 16777216;const DEFAULT_LAYER_PROPERTIES = () => {  if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("COMPONENTS")) {    return null;  }  return {    annotationEditorUIManager: null,    annotationStorage: null,    downloadManager: null,    enableScripting: false,    fieldObjectsPromise: null,    findController: null,    hasJSActionsPromise: null,    get linkService() {      const { SimpleLinkService } = require("./pdf_link_service.js");      return new SimpleLinkService();    },  };};/** * @implements {IRenderableView} */class PDFPageView {  #annotationMode = AnnotationMode.ENABLE_FORMS;  #layerProperties = null;  #previousRotation = null;  #renderingState = RenderingStates.INITIAL;  #useThumbnailCanvas = {    initialOptionalContent: true,    regularAnnotations: true,  };  /**   * @param {PDFPageViewOptions} options   */  constructor(options) {    const container = options.container;    const defaultViewport = options.defaultViewport;    this.id = options.id;    this.renderingId = "page" + this.id;    this.#layerProperties = options.layerProperties || DEFAULT_LAYER_PROPERTIES;    this.pdfPage = null;    this.pageLabel = null;    this.rotation = 0;    this.scale = options.scale || DEFAULT_SCALE;    this.viewport = defaultViewport;    this.pdfPageRotate = defaultViewport.rotation;    this._optionalContentConfigPromise =      options.optionalContentConfigPromise || null;    this.hasRestrictedScaling = false;    this.textLayerMode = options.textLayerMode ?? TextLayerMode.ENABLE;    this.#annotationMode =      options.annotationMode ?? AnnotationMode.ENABLE_FORMS;    this.imageResourcesPath = options.imageResourcesPath || "";    this.useOnlyCssZoom = options.useOnlyCssZoom || false;    this.isOffscreenCanvasSupported =      options.isOffscreenCanvasSupported ?? true;    this.maxCanvasPixels = options.maxCanvasPixels || MAX_CANVAS_PIXELS;    this.pageColors = options.pageColors || null;    this.eventBus = options.eventBus;    this.renderingQueue = options.renderingQueue;    if (      typeof PDFJSDev === "undefined" ||      PDFJSDev.test("!PRODUCTION || GENERIC")    ) {      this.renderer = options.renderer || RendererType.CANVAS;    }    this.l10n = options.l10n || NullL10n;    this.paintTask = null;    this.paintedViewportMap = new WeakMap();    this.resume = null;    this._renderError = null;    if (      typeof PDFJSDev === "undefined" ||      PDFJSDev.test("!PRODUCTION || GENERIC")    ) {      this._isStandalone = !this.renderingQueue?.hasViewer();    }    this._annotationCanvasMap = null;    this.annotationLayer = null;    this.annotationEditorLayer = null;    this.textLayer = null;    this.zoomLayer = null;    this.xfaLayer = null;    this.structTreeLayer = null;    const div = document.createElement("div");    div.className = "page";    div.setAttribute("data-page-number", this.id);    div.setAttribute("role", "region");    this.l10n.get("page_landmark", { page: this.id }).then(msg => {      div.setAttribute("aria-label", msg);    });    this.div = div;    this.#setDimensions();    container?.append(div);    if (      (typeof PDFJSDev === "undefined" ||        PDFJSDev.test("!PRODUCTION || GENERIC")) &&      this._isStandalone    ) {      // Ensure that the various layers always get the correct initial size,      // see issue 15795.      docStyle.setProperty(        "--scale-factor",        this.scale * PixelsPerInch.PDF_TO_CSS_UNITS      );      const { optionalContentConfigPromise } = options;      if (optionalContentConfigPromise) {        // Ensure that the thumbnails always display the *initial* document        // state, for documents with optional content.        optionalContentConfigPromise.then(optionalContentConfig => {          if (            optionalContentConfigPromise !== this._optionalContentConfigPromise          ) {            return;          }          this.#useThumbnailCanvas.initialOptionalContent =            optionalContentConfig.hasInitialVisibility;        });      }    }  }  get renderingState() {    return this.#renderingState;  }  set renderingState(state) {    this.#renderingState = state;    switch (state) {      case RenderingStates.INITIAL:      case RenderingStates.PAUSED:        this.loadingIconDiv?.classList.add("notVisible");        break;      case RenderingStates.RUNNING:        this.loadingIconDiv?.classList.remove("notVisible");        break;      case RenderingStates.FINISHED:        if (this.loadingIconDiv) {          this.loadingIconDiv.remove();          delete this.loadingIconDiv;        }        break;    }  }  #setDimensions() {    const { viewport } = this;    if (this.pdfPage) {      if (this.#previousRotation === viewport.rotation) {        return;      }      this.#previousRotation = viewport.rotation;    }    setLayerDimensions(      this.div,      viewport,      /* mustFlip = */ true,      /* mustRotate = */ false    );  }  setPdfPage(pdfPage) {    this.pdfPage = pdfPage;    this.pdfPageRotate = pdfPage.rotate;    const totalRotation = (this.rotation + this.pdfPageRotate) % 360;    this.viewport = pdfPage.getViewport({      scale: this.scale * PixelsPerInch.PDF_TO_CSS_UNITS,      rotation: totalRotation,    });    this.#setDimensions();    this.reset();  }  destroy() {    this.reset();    this.pdfPage?.cleanup();  }  get _textHighlighter() {    return shadow(      this,      "_textHighlighter",      new TextHighlighter({        pageIndex: this.id - 1,        eventBus: this.eventBus,        findController: this.#layerProperties().findController,      })    );  }  async #renderAnnotationLayer() {    let error = null;    try {      await this.annotationLayer.render(this.viewport, "display");    } catch (ex) {      console.error(`#renderAnnotationLayer: "${ex}".`);      error = ex;    } finally {      this.eventBus.dispatch("annotationlayerrendered", {        source: this,        pageNumber: this.id,        error,      });    }  }  async #renderAnnotationEditorLayer() {    let error = null;    try {      await this.annotationEditorLayer.render(this.viewport, "display");    } catch (ex) {      console.error(`#renderAnnotationEditorLayer: "${ex}".`);      error = ex;    } finally {      this.eventBus.dispatch("annotationeditorlayerrendered", {        source: this,        pageNumber: this.id,        error,      });    }  }  async #renderXfaLayer() {    let error = null;    try {      const result = await this.xfaLayer.render(this.viewport, "display");      if (result?.textDivs && this._textHighlighter) {        this.#buildXfaTextContentItems(result.textDivs);      }    } catch (ex) {      console.error(`#renderXfaLayer: "${ex}".`);      error = ex;    } finally {      this.eventBus.dispatch("xfalayerrendered", {        source: this,        pageNumber: this.id,        error,      });    }  }  async #renderTextLayer() {    const { pdfPage, textLayer, viewport } = this;    if (!textLayer) {      return;    }    let error = null;    try {      if (!textLayer.renderingDone) {        const readableStream = pdfPage.streamTextContent({          includeMarkedContent: true,        });        textLayer.setTextContentSource(readableStream);      }      await textLayer.render(viewport);    } catch (ex) {      if (ex instanceof AbortException) {        return;      }      console.error(`#renderTextLayer: "${ex}".`);      error = ex;    }    this.eventBus.dispatch("textlayerrendered", {      source: this,      pageNumber: this.id,      numTextDivs: textLayer.numTextDivs,      error,    });    this.#renderStructTreeLayer();  }  /**   * The structure tree is currently only supported when the text layer is   * enabled and a canvas is used for rendering.   *   * The structure tree must be generated after the text layer for the   * aria-owns to work.   */  async #renderStructTreeLayer() {    if (!this.textLayer) {      return;    }    this.structTreeLayer ||= new StructTreeLayerBuilder();    const tree = await (!this.structTreeLayer.renderingDone      ? this.pdfPage.getStructTree()      : null);    const treeDom = this.structTreeLayer?.render(tree);    if (treeDom) {      this.canvas?.append(treeDom);    }  }  async #buildXfaTextContentItems(textDivs) {    const text = await this.pdfPage.getTextContent();    const items = [];    for (const item of text.items) {      items.push(item.str);    }    this._textHighlighter.setTextMapping(textDivs, items);    this._textHighlighter.enable();  }  /**   * @private   */  _resetZoomLayer(removeFromDOM = false) {    if (!this.zoomLayer) {      return;    }    const zoomLayerCanvas = this.zoomLayer.firstChild;    this.paintedViewportMap.delete(zoomLayerCanvas);    // Zeroing the width and height causes Firefox to release graphics    // resources immediately, which can greatly reduce memory consumption.    zoomLayerCanvas.width = 0;    zoomLayerCanvas.height = 0;    if (removeFromDOM) {      // Note: `ChildNode.remove` doesn't throw if the parent node is undefined.      this.zoomLayer.remove();    }    this.zoomLayer = null;  }  reset({    keepZoomLayer = false,    keepAnnotationLayer = false,    keepAnnotationEditorLayer = false,    keepXfaLayer = false,    keepTextLayer = false,  } = {}) {    this.cancelRendering({      keepAnnotationLayer,      keepAnnotationEditorLayer,      keepXfaLayer,      keepTextLayer,    });    this.renderingState = RenderingStates.INITIAL;    const div = this.div;    const childNodes = div.childNodes,      zoomLayerNode = (keepZoomLayer && this.zoomLayer) || null,      annotationLayerNode =        (keepAnnotationLayer && this.annotationLayer?.div) || null,      annotationEditorLayerNode =        (keepAnnotationEditorLayer && this.annotationEditorLayer?.div) || null,      xfaLayerNode = (keepXfaLayer && this.xfaLayer?.div) || null,      textLayerNode = (keepTextLayer && this.textLayer?.div) || null;    for (let i = childNodes.length - 1; i >= 0; i--) {      const node = childNodes[i];      switch (node) {        case zoomLayerNode:        case annotationLayerNode:        case annotationEditorLayerNode:        case xfaLayerNode:        case textLayerNode:        case this.loadingIconDiv:          continue;      }      node.remove();    }    div.removeAttribute("data-loaded");    if (annotationLayerNode) {      // Hide the annotation layer until all elements are resized      // so they are not displayed on the already resized page.      this.annotationLayer.hide();    }    if (annotationEditorLayerNode) {      this.annotationEditorLayer.hide();    }    if (xfaLayerNode) {      // Hide the XFA layer until all elements are resized      // so they are not displayed on the already resized page.      this.xfaLayer.hide();    }    if (textLayerNode) {      this.textLayer.hide();    }    if (!zoomLayerNode) {      if (this.canvas) {        this.paintedViewportMap.delete(this.canvas);        // Zeroing the width and height causes Firefox to release graphics        // resources immediately, which can greatly reduce memory consumption.        this.canvas.width = 0;        this.canvas.height = 0;        delete this.canvas;      }      this._resetZoomLayer();    }    if (      (typeof PDFJSDev === "undefined" ||        PDFJSDev.test("!PRODUCTION || GENERIC")) &&      this.svg    ) {      this.paintedViewportMap.delete(this.svg);      delete this.svg;    }    if (!this.loadingIconDiv) {      this.loadingIconDiv = document.createElement("div");      this.loadingIconDiv.className = "loadingIcon notVisible";      this.loadingIconDiv.setAttribute("role", "img");      this.l10n.get("loading").then(msg => {        this.loadingIconDiv?.setAttribute("aria-label", msg);      });      div.append(this.loadingIconDiv);    }  }  update({    scale = 0,    rotation = null,    optionalContentConfigPromise = null,    drawingDelay = -1,  }) {    this.scale = scale || this.scale;    if (typeof rotation === "number") {      this.rotation = rotation; // The rotation may be zero.    }    if (optionalContentConfigPromise instanceof Promise) {      this._optionalContentConfigPromise = optionalContentConfigPromise;      // Ensure that the thumbnails always display the *initial* document state,      // for documents with optional content.      optionalContentConfigPromise.then(optionalContentConfig => {        if (          optionalContentConfigPromise !== this._optionalContentConfigPromise        ) {          return;        }        this.#useThumbnailCanvas.initialOptionalContent =          optionalContentConfig.hasInitialVisibility;      });    }    const totalRotation = (this.rotation + this.pdfPageRotate) % 360;    this.viewport = this.viewport.clone({      scale: this.scale * PixelsPerInch.PDF_TO_CSS_UNITS,      rotation: totalRotation,    });    this.#setDimensions();    if (      (typeof PDFJSDev === "undefined" ||        PDFJSDev.test("!PRODUCTION || GENERIC")) &&      this._isStandalone    ) {      docStyle.setProperty("--scale-factor", this.viewport.scale);    }    if (      (typeof PDFJSDev === "undefined" ||        PDFJSDev.test("!PRODUCTION || GENERIC")) &&      this.svg    ) {      this.cssTransform({        target: this.svg,        redrawAnnotationLayer: true,        redrawAnnotationEditorLayer: true,        redrawXfaLayer: true,        redrawTextLayer: true,      });      this.eventBus.dispatch("pagerendered", {        source: this,        pageNumber: this.id,        cssTransform: true,        timestamp: performance.now(),        error: this._renderError,      });      return;    }    let isScalingRestricted = false;    if (this.canvas && this.maxCanvasPixels > 0) {      const outputScale = this.outputScale;      if (        ((Math.floor(this.viewport.width) * outputScale.sx) | 0) *          ((Math.floor(this.viewport.height) * outputScale.sy) | 0) >        this.maxCanvasPixels      ) {        isScalingRestricted = true;      }    }    const postponeDrawing = drawingDelay >= 0 && drawingDelay < 1000;    if (this.canvas) {      if (        postponeDrawing ||        this.useOnlyCssZoom ||        (this.hasRestrictedScaling && isScalingRestricted)      ) {        if (          postponeDrawing &&          this.renderingState !== RenderingStates.FINISHED        ) {          this.cancelRendering({            keepZoomLayer: true,            keepAnnotationLayer: true,            keepAnnotationEditorLayer: true,            keepXfaLayer: true,            keepTextLayer: true,            cancelExtraDelay: drawingDelay,          });          // It isn't really finished, but once we have finished          // to postpone, we'll call this.reset(...) which will set          // the rendering state to INITIAL, hence the next call to          // PDFViewer.update() will trigger a redraw (if it's mandatory).          this.renderingState = RenderingStates.FINISHED;        }        this.cssTransform({          target: this.canvas,          redrawAnnotationLayer: true,          redrawAnnotationEditorLayer: true,          redrawXfaLayer: true,          redrawTextLayer: !postponeDrawing,          hideTextLayer: postponeDrawing,        });        this.eventBus.dispatch("pagerendered", {          source: this,          pageNumber: this.id,          cssTransform: true,          timestamp: performance.now(),          error: this._renderError,        });        return;      }      if (!this.zoomLayer && !this.canvas.hidden) {        this.zoomLayer = this.canvas.parentNode;        this.zoomLayer.style.position = "absolute";      }    }    if (this.zoomLayer) {      this.cssTransform({ target: this.zoomLayer.firstChild });    }    this.reset({      keepZoomLayer: true,      keepAnnotationLayer: true,      keepAnnotationEditorLayer: true,      keepXfaLayer: true,      keepTextLayer: true,    });  }  /**   * PLEASE NOTE: Most likely you want to use the `this.reset()` method,   *              rather than calling this one directly.   */  cancelRendering({    keepAnnotationLayer = false,    keepAnnotationEditorLayer = false,    keepXfaLayer = false,    keepTextLayer = false,    cancelExtraDelay = 0,  } = {}) {    if (this.paintTask) {      this.paintTask.cancel(cancelExtraDelay);      this.paintTask = null;    }    this.resume = null;    if (this.textLayer && (!keepTextLayer || !this.textLayer.div)) {      this.textLayer.cancel();      this.textLayer = null;    }    if (this.structTreeLayer && !this.textLayer) {      this.structTreeLayer = null;    }    if (      this.annotationLayer &&      (!keepAnnotationLayer || !this.annotationLayer.div)    ) {      this.annotationLayer.cancel();      this.annotationLayer = null;      this._annotationCanvasMap = null;    }    if (      this.annotationEditorLayer &&      (!keepAnnotationEditorLayer || !this.annotationEditorLayer.div)    ) {      this.annotationEditorLayer.cancel();      this.annotationEditorLayer = null;    }    if (this.xfaLayer && (!keepXfaLayer || !this.xfaLayer.div)) {      this.xfaLayer.cancel();      this.xfaLayer = null;      this._textHighlighter?.disable();    }  }  cssTransform({    target,    redrawAnnotationLayer = false,    redrawAnnotationEditorLayer = false,    redrawXfaLayer = false,    redrawTextLayer = false,    hideTextLayer = false,  }) {    // Scale target (canvas or svg), its wrapper and page container.    if (target instanceof HTMLCanvasElement) {      if (!target.hasAttribute("zooming")) {        target.setAttribute("zooming", true);        const { style } = target;        style.width = style.height = "";      }    } else {      const div = this.div;      const { width, height } = this.viewport;      target.style.width =        target.parentNode.style.width =        div.style.width =          Math.floor(width) + "px";      target.style.height =        target.parentNode.style.height =        div.style.height =          Math.floor(height) + "px";    }    const originalViewport = this.paintedViewportMap.get(target);    if (this.viewport !== originalViewport) {      // The canvas may have been originally rotated; rotate relative to that.      const relativeRotation =        this.viewport.rotation - originalViewport.rotation;      const absRotation = Math.abs(relativeRotation);      let scaleX = 1,        scaleY = 1;      if (absRotation === 90 || absRotation === 270) {        const { width, height } = this.viewport;        // Scale x and y because of the rotation.        scaleX = height / width;        scaleY = width / height;      }      if (absRotation !== 0) {        target.style.transform = `rotate(${relativeRotation}deg) scale(${scaleX}, ${scaleY})`;      }    }    if (redrawAnnotationLayer && this.annotationLayer) {      this.#renderAnnotationLayer();    }    if (redrawAnnotationEditorLayer && this.annotationEditorLayer) {      this.#renderAnnotationEditorLayer();    }    if (redrawXfaLayer && this.xfaLayer) {      this.#renderXfaLayer();    }    if (this.textLayer) {      if (hideTextLayer) {        this.textLayer.hide();      } else if (redrawTextLayer) {        this.#renderTextLayer();      }    }  }  get width() {    return this.viewport.width;  }  get height() {    return this.viewport.height;  }  getPagePoint(x, y) {    return this.viewport.convertToPdfPoint(x, y);  }  draw() {    if (this.renderingState !== RenderingStates.INITIAL) {      console.error("Must be in new state before drawing");      this.reset(); // Ensure that we reset all state to prevent issues.    }    const { div, pdfPage } = this;    if (!pdfPage) {      this.renderingState = RenderingStates.FINISHED;      return Promise.reject(new Error("pdfPage is not loaded"));    }    this.renderingState = RenderingStates.RUNNING;    // Wrap the canvas so that if it has a CSS transform for high DPI the    // overflow will be hidden in Firefox.    const canvasWrapper = document.createElement("div");    canvasWrapper.classList.add("canvasWrapper");    div.append(canvasWrapper);    if (      !this.textLayer &&      this.textLayerMode !== TextLayerMode.DISABLE &&      !pdfPage.isPureXfa    ) {      this._accessibilityManager ||= new TextAccessibilityManager();      this.textLayer = new TextLayerBuilder({        highlighter: this._textHighlighter,        accessibilityManager: this._accessibilityManager,        isOffscreenCanvasSupported: this.isOffscreenCanvasSupported,      });      div.append(this.textLayer.div);    }    if (      !this.annotationLayer &&      this.#annotationMode !== AnnotationMode.DISABLE    ) {      const {        annotationStorage,        downloadManager,        enableScripting,        fieldObjectsPromise,        hasJSActionsPromise,        linkService,      } = this.#layerProperties();      this._annotationCanvasMap ||= new Map();      this.annotationLayer = new AnnotationLayerBuilder({        pageDiv: div,        pdfPage,        annotationStorage,        imageResourcesPath: this.imageResourcesPath,        renderForms: this.#annotationMode === AnnotationMode.ENABLE_FORMS,        linkService,        downloadManager,        l10n: this.l10n,        enableScripting,        hasJSActionsPromise,        fieldObjectsPromise,        annotationCanvasMap: this._annotationCanvasMap,        accessibilityManager: this._accessibilityManager,      });    }    if (this.xfaLayer?.div) {      // The xfa layer needs to stay on top.      div.append(this.xfaLayer.div);    }    let renderContinueCallback = null;    if (this.renderingQueue) {      renderContinueCallback = cont => {        if (!this.renderingQueue.isHighestPriority(this)) {          this.renderingState = RenderingStates.PAUSED;          this.resume = () => {            this.renderingState = RenderingStates.RUNNING;            cont();          };          return;        }        cont();      };    }    const finishPaintTask = async (error = null) => {      // The paintTask may have been replaced by a new one, so only remove      // the reference to the paintTask if it matches the one that is      // triggering this callback.      if (paintTask === this.paintTask) {        this.paintTask = null;      }      if (error instanceof RenderingCancelledException) {        this._renderError = null;        return;      }      this._renderError = error;      this.renderingState = RenderingStates.FINISHED;      this._resetZoomLayer(/* removeFromDOM = */ true);      // Ensure that the thumbnails won't become partially (or fully) blank,      // for documents that contain interactive form elements.      this.#useThumbnailCanvas.regularAnnotations = !paintTask.separateAnnots;      this.eventBus.dispatch("pagerendered", {        source: this,        pageNumber: this.id,        cssTransform: false,        timestamp: performance.now(),        error: this._renderError,      });      if (error) {        throw error;      }    };    const paintTask =      (typeof PDFJSDev === "undefined" ||        PDFJSDev.test("!PRODUCTION || GENERIC")) &&      this.renderer === RendererType.SVG        ? this.paintOnSvg(canvasWrapper)        : this.paintOnCanvas(canvasWrapper);    paintTask.onRenderContinue = renderContinueCallback;    this.paintTask = paintTask;    const resultPromise = paintTask.promise.then(      () => {        return finishPaintTask(null).then(async () => {          this.#renderTextLayer();          if (this.annotationLayer) {            await this.#renderAnnotationLayer();          }          if (!this.annotationEditorLayer) {            const { annotationEditorUIManager } = this.#layerProperties();            if (!annotationEditorUIManager) {              return;            }            this.annotationEditorLayer = new AnnotationEditorLayerBuilder({              uiManager: annotationEditorUIManager,              pageDiv: div,              pdfPage,              l10n: this.l10n,              accessibilityManager: this._accessibilityManager,            });          }          this.#renderAnnotationEditorLayer();        });      },      function (reason) {        return finishPaintTask(reason);      }    );    if (pdfPage.isPureXfa) {      if (!this.xfaLayer) {        const { annotationStorage, linkService } = this.#layerProperties();        this.xfaLayer = new XfaLayerBuilder({          pageDiv: div,          pdfPage,          annotationStorage,          linkService,        });      }      this.#renderXfaLayer();    }    div.setAttribute("data-loaded", true);    this.eventBus.dispatch("pagerender", {      source: this,      pageNumber: this.id,    });    return resultPromise;  }  paintOnCanvas(canvasWrapper) {    const renderCapability = createPromiseCapability();    const result = {      promise: renderCapability.promise,      onRenderContinue(cont) {        cont();      },      cancel(extraDelay = 0) {        renderTask.cancel(extraDelay);      },      get separateAnnots() {        return renderTask.separateAnnots;      },    };    const viewport = this.viewport;    const { width, height } = viewport;    const canvas = document.createElement("canvas");    canvas.setAttribute("role", "presentation");    // Keep the canvas hidden until the first draw callback, or until drawing    // is complete when `!this.renderingQueue`, to prevent black flickering.    canvas.hidden = true;    let isCanvasHidden = true;    const showCanvas = function () {      if (isCanvasHidden) {        canvas.hidden = false;        isCanvasHidden = false;      }    };    canvasWrapper.append(canvas);    this.canvas = canvas;    const ctx = canvas.getContext("2d", { alpha: false });    const outputScale = (this.outputScale = new OutputScale());    if (this.useOnlyCssZoom) {      const actualSizeViewport = viewport.clone({        scale: PixelsPerInch.PDF_TO_CSS_UNITS,      });      // Use a scale that makes the canvas have the originally intended size      // of the page.      outputScale.sx *= actualSizeViewport.width / width;      outputScale.sy *= actualSizeViewport.height / height;    }    if (this.maxCanvasPixels > 0) {      const pixelsInViewport = width * height;      const maxScale = Math.sqrt(this.maxCanvasPixels / pixelsInViewport);      if (outputScale.sx > maxScale || outputScale.sy > maxScale) {        outputScale.sx = maxScale;        outputScale.sy = maxScale;        this.hasRestrictedScaling = true;      } else {        this.hasRestrictedScaling = false;      }    }    const sfx = approximateFraction(outputScale.sx);    const sfy = approximateFraction(outputScale.sy);    canvas.width = roundToDivide(viewport.width * outputScale.sx, sfx[0]);    canvas.height = roundToDivide(viewport.height * outputScale.sy, sfy[0]);    const { style } = canvas;    style.width = roundToDivide(viewport.width, sfx[1]) + "px";    style.height = roundToDivide(viewport.height, sfy[1]) + "px";    // Add the viewport so it's known what it was originally drawn with.    this.paintedViewportMap.set(canvas, viewport);    // Rendering area    const transform = outputScale.scaled      ? [outputScale.sx, 0, 0, outputScale.sy, 0, 0]      : null;    const renderContext = {      canvasContext: ctx,      transform,      viewport,      annotationMode: this.#annotationMode,      optionalContentConfigPromise: this._optionalContentConfigPromise,      annotationCanvasMap: this._annotationCanvasMap,      pageColors: this.pageColors,    };    const renderTask = this.pdfPage.render(renderContext);    renderTask.onContinue = function (cont) {      showCanvas();      if (result.onRenderContinue) {        result.onRenderContinue(cont);      } else {        cont();      }    };    renderTask.promise.then(      function () {        showCanvas();        renderCapability.resolve();      },      function (error) {        showCanvas();        renderCapability.reject(error);      }    );    return result;  }  paintOnSvg(wrapper) {    if (      !(        typeof PDFJSDev === "undefined" ||        PDFJSDev.test("!PRODUCTION || GENERIC")      )    ) {      throw new Error("Not implemented: paintOnSvg");    }    let cancelled = false;    const ensureNotCancelled = () => {      if (cancelled) {        throw new RenderingCancelledException(          `Rendering cancelled, page ${this.id}`,          "svg"        );      }    };    const pdfPage = this.pdfPage;    const actualSizeViewport = this.viewport.clone({      scale: PixelsPerInch.PDF_TO_CSS_UNITS,    });    const promise = pdfPage      .getOperatorList({        annotationMode: this.#annotationMode,      })      .then(opList => {        ensureNotCancelled();        const svgGfx = new SVGGraphics(pdfPage.commonObjs, pdfPage.objs);        return svgGfx.getSVG(opList, actualSizeViewport).then(svg => {          ensureNotCancelled();          this.svg = svg;          this.paintedViewportMap.set(svg, actualSizeViewport);          svg.style.width = wrapper.style.width;          svg.style.height = wrapper.style.height;          this.renderingState = RenderingStates.FINISHED;          wrapper.append(svg);        });      });    return {      promise,      onRenderContinue(cont) {        cont();      },      cancel() {        cancelled = true;      },      get separateAnnots() {        return false;      },    };  }  /**   * @param {string|null} label   */  setPageLabel(label) {    this.pageLabel = typeof label === "string" ? label : null;    if (this.pageLabel !== null) {      this.div.setAttribute("data-page-label", this.pageLabel);    } else {      this.div.removeAttribute("data-page-label");    }  }  /**   * For use by the `PDFThumbnailView.setImage`-method.   * @ignore   */  get thumbnailCanvas() {    const { initialOptionalContent, regularAnnotations } =      this.#useThumbnailCanvas;    return initialOptionalContent && regularAnnotations ? this.canvas : null;  }}export { PDFPageView };
 |