pdf_page_view.js 34 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162
  1. /* Copyright 2012 Mozilla Foundation
  2. *
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15. // eslint-disable-next-line max-len
  16. /** @typedef {import("../src/display/display_utils").PageViewport} PageViewport */
  17. // eslint-disable-next-line max-len
  18. /** @typedef {import("../src/display/optional_content_config").OptionalContentConfig} OptionalContentConfig */
  19. /** @typedef {import("./event_utils").EventBus} EventBus */
  20. /** @typedef {import("./interfaces").IL10n} IL10n */
  21. /** @typedef {import("./interfaces").IRenderableView} IRenderableView */
  22. // eslint-disable-next-line max-len
  23. /** @typedef {import("./pdf_rendering_queue").PDFRenderingQueue} PDFRenderingQueue */
  24. import {
  25. AbortException,
  26. AnnotationMode,
  27. createPromiseCapability,
  28. PixelsPerInch,
  29. RenderingCancelledException,
  30. setLayerDimensions,
  31. shadow,
  32. SVGGraphics,
  33. } from "pdfjs-lib";
  34. import {
  35. approximateFraction,
  36. DEFAULT_SCALE,
  37. docStyle,
  38. OutputScale,
  39. RendererType,
  40. RenderingStates,
  41. roundToDivide,
  42. TextLayerMode,
  43. } from "./ui_utils.js";
  44. import { AnnotationEditorLayerBuilder } from "./annotation_editor_layer_builder.js";
  45. import { AnnotationLayerBuilder } from "./annotation_layer_builder.js";
  46. import { compatibilityParams } from "./app_options.js";
  47. import { NullL10n } from "./l10n_utils.js";
  48. import { StructTreeLayerBuilder } from "./struct_tree_layer_builder.js";
  49. import { TextAccessibilityManager } from "./text_accessibility.js";
  50. import { TextHighlighter } from "./text_highlighter.js";
  51. import { TextLayerBuilder } from "./text_layer_builder.js";
  52. import { XfaLayerBuilder } from "./xfa_layer_builder.js";
  53. /**
  54. * @typedef {Object} PDFPageViewOptions
  55. * @property {HTMLDivElement} [container] - The viewer element.
  56. * @property {EventBus} eventBus - The application event bus.
  57. * @property {number} id - The page unique ID (normally its number).
  58. * @property {number} [scale] - The page scale display.
  59. * @property {PageViewport} defaultViewport - The page viewport.
  60. * @property {Promise<OptionalContentConfig>} [optionalContentConfigPromise] -
  61. * A promise that is resolved with an {@link OptionalContentConfig} instance.
  62. * The default value is `null`.
  63. * @property {PDFRenderingQueue} [renderingQueue] - The rendering queue object.
  64. * @property {number} [textLayerMode] - Controls if the text layer used for
  65. * selection and searching is created. The constants from {TextLayerMode}
  66. * should be used. The default value is `TextLayerMode.ENABLE`.
  67. * @property {number} [annotationMode] - Controls if the annotation layer is
  68. * created, and if interactive form elements or `AnnotationStorage`-data are
  69. * being rendered. The constants from {@link AnnotationMode} should be used;
  70. * see also {@link RenderParameters} and {@link GetOperatorListParameters}.
  71. * The default value is `AnnotationMode.ENABLE_FORMS`.
  72. * @property {string} [imageResourcesPath] - Path for image resources, mainly
  73. * for annotation icons. Include trailing slash.
  74. * @property {boolean} [useOnlyCssZoom] - Enables CSS only zooming. The default
  75. * value is `false`.
  76. * @property {boolean} [isOffscreenCanvasSupported] - Allows to use an
  77. * OffscreenCanvas if needed.
  78. * @property {number} [maxCanvasPixels] - The maximum supported canvas size in
  79. * total pixels, i.e. width * height. Use -1 for no limit. The default value
  80. * is 4096 * 4096 (16 mega-pixels).
  81. * @property {Object} [pageColors] - Overwrites background and foreground colors
  82. * with user defined ones in order to improve readability in high contrast
  83. * mode.
  84. * @property {IL10n} [l10n] - Localization service.
  85. * @property {function} [layerProperties] - The function that is used to lookup
  86. * the necessary layer-properties.
  87. */
  88. const MAX_CANVAS_PIXELS = compatibilityParams.maxCanvasPixels || 16777216;
  89. const DEFAULT_LAYER_PROPERTIES = () => {
  90. if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("COMPONENTS")) {
  91. return null;
  92. }
  93. return {
  94. annotationEditorUIManager: null,
  95. annotationStorage: null,
  96. downloadManager: null,
  97. enableScripting: false,
  98. fieldObjectsPromise: null,
  99. findController: null,
  100. hasJSActionsPromise: null,
  101. get linkService() {
  102. const { SimpleLinkService } = require("./pdf_link_service.js");
  103. return new SimpleLinkService();
  104. },
  105. };
  106. };
  107. /**
  108. * @implements {IRenderableView}
  109. */
  110. class PDFPageView {
  111. #annotationMode = AnnotationMode.ENABLE_FORMS;
  112. #layerProperties = null;
  113. #previousRotation = null;
  114. #renderingState = RenderingStates.INITIAL;
  115. #useThumbnailCanvas = {
  116. initialOptionalContent: true,
  117. regularAnnotations: true,
  118. };
  119. /**
  120. * @param {PDFPageViewOptions} options
  121. */
  122. constructor(options) {
  123. const container = options.container;
  124. const defaultViewport = options.defaultViewport;
  125. this.id = options.id;
  126. this.renderingId = "page" + this.id;
  127. this.#layerProperties = options.layerProperties || DEFAULT_LAYER_PROPERTIES;
  128. this.pdfPage = null;
  129. this.pageLabel = null;
  130. this.rotation = 0;
  131. this.scale = options.scale || DEFAULT_SCALE;
  132. this.viewport = defaultViewport;
  133. this.pdfPageRotate = defaultViewport.rotation;
  134. this._optionalContentConfigPromise =
  135. options.optionalContentConfigPromise || null;
  136. this.hasRestrictedScaling = false;
  137. this.textLayerMode = options.textLayerMode ?? TextLayerMode.ENABLE;
  138. this.#annotationMode =
  139. options.annotationMode ?? AnnotationMode.ENABLE_FORMS;
  140. this.imageResourcesPath = options.imageResourcesPath || "";
  141. this.useOnlyCssZoom = options.useOnlyCssZoom || false;
  142. this.isOffscreenCanvasSupported =
  143. options.isOffscreenCanvasSupported ?? true;
  144. this.maxCanvasPixels = options.maxCanvasPixels || MAX_CANVAS_PIXELS;
  145. this.pageColors = options.pageColors || null;
  146. this.eventBus = options.eventBus;
  147. this.renderingQueue = options.renderingQueue;
  148. if (
  149. typeof PDFJSDev === "undefined" ||
  150. PDFJSDev.test("!PRODUCTION || GENERIC")
  151. ) {
  152. this.renderer = options.renderer || RendererType.CANVAS;
  153. }
  154. this.l10n = options.l10n || NullL10n;
  155. this.paintTask = null;
  156. this.paintedViewportMap = new WeakMap();
  157. this.resume = null;
  158. this._renderError = null;
  159. if (
  160. typeof PDFJSDev === "undefined" ||
  161. PDFJSDev.test("!PRODUCTION || GENERIC")
  162. ) {
  163. this._isStandalone = !this.renderingQueue?.hasViewer();
  164. }
  165. this._annotationCanvasMap = null;
  166. this.annotationLayer = null;
  167. this.annotationEditorLayer = null;
  168. this.textLayer = null;
  169. this.zoomLayer = null;
  170. this.xfaLayer = null;
  171. this.structTreeLayer = null;
  172. const div = document.createElement("div");
  173. div.className = "page";
  174. div.setAttribute("data-page-number", this.id);
  175. div.setAttribute("role", "region");
  176. this.l10n.get("page_landmark", { page: this.id }).then(msg => {
  177. div.setAttribute("aria-label", msg);
  178. });
  179. this.div = div;
  180. this.#setDimensions();
  181. container?.append(div);
  182. if (
  183. (typeof PDFJSDev === "undefined" ||
  184. PDFJSDev.test("!PRODUCTION || GENERIC")) &&
  185. this._isStandalone
  186. ) {
  187. // Ensure that the various layers always get the correct initial size,
  188. // see issue 15795.
  189. docStyle.setProperty(
  190. "--scale-factor",
  191. this.scale * PixelsPerInch.PDF_TO_CSS_UNITS
  192. );
  193. const { optionalContentConfigPromise } = options;
  194. if (optionalContentConfigPromise) {
  195. // Ensure that the thumbnails always display the *initial* document
  196. // state, for documents with optional content.
  197. optionalContentConfigPromise.then(optionalContentConfig => {
  198. if (
  199. optionalContentConfigPromise !== this._optionalContentConfigPromise
  200. ) {
  201. return;
  202. }
  203. this.#useThumbnailCanvas.initialOptionalContent =
  204. optionalContentConfig.hasInitialVisibility;
  205. });
  206. }
  207. }
  208. }
  209. get renderingState() {
  210. return this.#renderingState;
  211. }
  212. set renderingState(state) {
  213. this.#renderingState = state;
  214. switch (state) {
  215. case RenderingStates.INITIAL:
  216. case RenderingStates.PAUSED:
  217. this.loadingIconDiv?.classList.add("notVisible");
  218. break;
  219. case RenderingStates.RUNNING:
  220. this.loadingIconDiv?.classList.remove("notVisible");
  221. break;
  222. case RenderingStates.FINISHED:
  223. if (this.loadingIconDiv) {
  224. this.loadingIconDiv.remove();
  225. delete this.loadingIconDiv;
  226. }
  227. break;
  228. }
  229. }
  230. #setDimensions() {
  231. const { viewport } = this;
  232. if (this.pdfPage) {
  233. if (this.#previousRotation === viewport.rotation) {
  234. return;
  235. }
  236. this.#previousRotation = viewport.rotation;
  237. }
  238. setLayerDimensions(
  239. this.div,
  240. viewport,
  241. /* mustFlip = */ true,
  242. /* mustRotate = */ false
  243. );
  244. }
  245. setPdfPage(pdfPage) {
  246. this.pdfPage = pdfPage;
  247. this.pdfPageRotate = pdfPage.rotate;
  248. const totalRotation = (this.rotation + this.pdfPageRotate) % 360;
  249. this.viewport = pdfPage.getViewport({
  250. scale: this.scale * PixelsPerInch.PDF_TO_CSS_UNITS,
  251. rotation: totalRotation,
  252. });
  253. this.#setDimensions();
  254. this.reset();
  255. }
  256. destroy() {
  257. this.reset();
  258. this.pdfPage?.cleanup();
  259. }
  260. get _textHighlighter() {
  261. return shadow(
  262. this,
  263. "_textHighlighter",
  264. new TextHighlighter({
  265. pageIndex: this.id - 1,
  266. eventBus: this.eventBus,
  267. findController: this.#layerProperties().findController,
  268. })
  269. );
  270. }
  271. async #renderAnnotationLayer() {
  272. let error = null;
  273. try {
  274. await this.annotationLayer.render(this.viewport, "display");
  275. } catch (ex) {
  276. console.error(`#renderAnnotationLayer: "${ex}".`);
  277. error = ex;
  278. } finally {
  279. this.eventBus.dispatch("annotationlayerrendered", {
  280. source: this,
  281. pageNumber: this.id,
  282. error,
  283. });
  284. }
  285. }
  286. async #renderAnnotationEditorLayer() {
  287. let error = null;
  288. try {
  289. await this.annotationEditorLayer.render(this.viewport, "display");
  290. } catch (ex) {
  291. console.error(`#renderAnnotationEditorLayer: "${ex}".`);
  292. error = ex;
  293. } finally {
  294. this.eventBus.dispatch("annotationeditorlayerrendered", {
  295. source: this,
  296. pageNumber: this.id,
  297. error,
  298. });
  299. }
  300. }
  301. async #renderXfaLayer() {
  302. let error = null;
  303. try {
  304. const result = await this.xfaLayer.render(this.viewport, "display");
  305. if (result?.textDivs && this._textHighlighter) {
  306. this.#buildXfaTextContentItems(result.textDivs);
  307. }
  308. } catch (ex) {
  309. console.error(`#renderXfaLayer: "${ex}".`);
  310. error = ex;
  311. } finally {
  312. this.eventBus.dispatch("xfalayerrendered", {
  313. source: this,
  314. pageNumber: this.id,
  315. error,
  316. });
  317. }
  318. }
  319. async #renderTextLayer() {
  320. const { pdfPage, textLayer, viewport } = this;
  321. if (!textLayer) {
  322. return;
  323. }
  324. let error = null;
  325. try {
  326. if (!textLayer.renderingDone) {
  327. const readableStream = pdfPage.streamTextContent({
  328. includeMarkedContent: true,
  329. });
  330. textLayer.setTextContentSource(readableStream);
  331. }
  332. await textLayer.render(viewport);
  333. } catch (ex) {
  334. if (ex instanceof AbortException) {
  335. return;
  336. }
  337. console.error(`#renderTextLayer: "${ex}".`);
  338. error = ex;
  339. }
  340. this.eventBus.dispatch("textlayerrendered", {
  341. source: this,
  342. pageNumber: this.id,
  343. numTextDivs: textLayer.numTextDivs,
  344. error,
  345. });
  346. this.#renderStructTreeLayer();
  347. }
  348. /**
  349. * The structure tree is currently only supported when the text layer is
  350. * enabled and a canvas is used for rendering.
  351. *
  352. * The structure tree must be generated after the text layer for the
  353. * aria-owns to work.
  354. */
  355. async #renderStructTreeLayer() {
  356. if (!this.textLayer) {
  357. return;
  358. }
  359. this.structTreeLayer ||= new StructTreeLayerBuilder();
  360. const tree = await (!this.structTreeLayer.renderingDone
  361. ? this.pdfPage.getStructTree()
  362. : null);
  363. const treeDom = this.structTreeLayer?.render(tree);
  364. if (treeDom) {
  365. this.canvas?.append(treeDom);
  366. }
  367. }
  368. async #buildXfaTextContentItems(textDivs) {
  369. const text = await this.pdfPage.getTextContent();
  370. const items = [];
  371. for (const item of text.items) {
  372. items.push(item.str);
  373. }
  374. this._textHighlighter.setTextMapping(textDivs, items);
  375. this._textHighlighter.enable();
  376. }
  377. /**
  378. * @private
  379. */
  380. _resetZoomLayer(removeFromDOM = false) {
  381. if (!this.zoomLayer) {
  382. return;
  383. }
  384. const zoomLayerCanvas = this.zoomLayer.firstChild;
  385. this.paintedViewportMap.delete(zoomLayerCanvas);
  386. // Zeroing the width and height causes Firefox to release graphics
  387. // resources immediately, which can greatly reduce memory consumption.
  388. zoomLayerCanvas.width = 0;
  389. zoomLayerCanvas.height = 0;
  390. if (removeFromDOM) {
  391. // Note: `ChildNode.remove` doesn't throw if the parent node is undefined.
  392. this.zoomLayer.remove();
  393. }
  394. this.zoomLayer = null;
  395. }
  396. reset({
  397. keepZoomLayer = false,
  398. keepAnnotationLayer = false,
  399. keepAnnotationEditorLayer = false,
  400. keepXfaLayer = false,
  401. keepTextLayer = false,
  402. } = {}) {
  403. this.cancelRendering({
  404. keepAnnotationLayer,
  405. keepAnnotationEditorLayer,
  406. keepXfaLayer,
  407. keepTextLayer,
  408. });
  409. this.renderingState = RenderingStates.INITIAL;
  410. const div = this.div;
  411. const childNodes = div.childNodes,
  412. zoomLayerNode = (keepZoomLayer && this.zoomLayer) || null,
  413. annotationLayerNode =
  414. (keepAnnotationLayer && this.annotationLayer?.div) || null,
  415. annotationEditorLayerNode =
  416. (keepAnnotationEditorLayer && this.annotationEditorLayer?.div) || null,
  417. xfaLayerNode = (keepXfaLayer && this.xfaLayer?.div) || null,
  418. textLayerNode = (keepTextLayer && this.textLayer?.div) || null;
  419. for (let i = childNodes.length - 1; i >= 0; i--) {
  420. const node = childNodes[i];
  421. switch (node) {
  422. case zoomLayerNode:
  423. case annotationLayerNode:
  424. case annotationEditorLayerNode:
  425. case xfaLayerNode:
  426. case textLayerNode:
  427. case this.loadingIconDiv:
  428. continue;
  429. }
  430. node.remove();
  431. }
  432. div.removeAttribute("data-loaded");
  433. if (annotationLayerNode) {
  434. // Hide the annotation layer until all elements are resized
  435. // so they are not displayed on the already resized page.
  436. this.annotationLayer.hide();
  437. }
  438. if (annotationEditorLayerNode) {
  439. this.annotationEditorLayer.hide();
  440. }
  441. if (xfaLayerNode) {
  442. // Hide the XFA layer until all elements are resized
  443. // so they are not displayed on the already resized page.
  444. this.xfaLayer.hide();
  445. }
  446. if (textLayerNode) {
  447. this.textLayer.hide();
  448. }
  449. if (!zoomLayerNode) {
  450. if (this.canvas) {
  451. this.paintedViewportMap.delete(this.canvas);
  452. // Zeroing the width and height causes Firefox to release graphics
  453. // resources immediately, which can greatly reduce memory consumption.
  454. this.canvas.width = 0;
  455. this.canvas.height = 0;
  456. delete this.canvas;
  457. }
  458. this._resetZoomLayer();
  459. }
  460. if (
  461. (typeof PDFJSDev === "undefined" ||
  462. PDFJSDev.test("!PRODUCTION || GENERIC")) &&
  463. this.svg
  464. ) {
  465. this.paintedViewportMap.delete(this.svg);
  466. delete this.svg;
  467. }
  468. if (!this.loadingIconDiv) {
  469. this.loadingIconDiv = document.createElement("div");
  470. this.loadingIconDiv.className = "loadingIcon notVisible";
  471. this.loadingIconDiv.setAttribute("role", "img");
  472. this.l10n.get("loading").then(msg => {
  473. this.loadingIconDiv?.setAttribute("aria-label", msg);
  474. });
  475. div.append(this.loadingIconDiv);
  476. }
  477. }
  478. update({
  479. scale = 0,
  480. rotation = null,
  481. optionalContentConfigPromise = null,
  482. drawingDelay = -1,
  483. }) {
  484. this.scale = scale || this.scale;
  485. if (typeof rotation === "number") {
  486. this.rotation = rotation; // The rotation may be zero.
  487. }
  488. if (optionalContentConfigPromise instanceof Promise) {
  489. this._optionalContentConfigPromise = optionalContentConfigPromise;
  490. // Ensure that the thumbnails always display the *initial* document state,
  491. // for documents with optional content.
  492. optionalContentConfigPromise.then(optionalContentConfig => {
  493. if (
  494. optionalContentConfigPromise !== this._optionalContentConfigPromise
  495. ) {
  496. return;
  497. }
  498. this.#useThumbnailCanvas.initialOptionalContent =
  499. optionalContentConfig.hasInitialVisibility;
  500. });
  501. }
  502. const totalRotation = (this.rotation + this.pdfPageRotate) % 360;
  503. this.viewport = this.viewport.clone({
  504. scale: this.scale * PixelsPerInch.PDF_TO_CSS_UNITS,
  505. rotation: totalRotation,
  506. });
  507. this.#setDimensions();
  508. if (
  509. (typeof PDFJSDev === "undefined" ||
  510. PDFJSDev.test("!PRODUCTION || GENERIC")) &&
  511. this._isStandalone
  512. ) {
  513. docStyle.setProperty("--scale-factor", this.viewport.scale);
  514. }
  515. if (
  516. (typeof PDFJSDev === "undefined" ||
  517. PDFJSDev.test("!PRODUCTION || GENERIC")) &&
  518. this.svg
  519. ) {
  520. this.cssTransform({
  521. target: this.svg,
  522. redrawAnnotationLayer: true,
  523. redrawAnnotationEditorLayer: true,
  524. redrawXfaLayer: true,
  525. redrawTextLayer: true,
  526. });
  527. this.eventBus.dispatch("pagerendered", {
  528. source: this,
  529. pageNumber: this.id,
  530. cssTransform: true,
  531. timestamp: performance.now(),
  532. error: this._renderError,
  533. });
  534. return;
  535. }
  536. let isScalingRestricted = false;
  537. if (this.canvas && this.maxCanvasPixels > 0) {
  538. const outputScale = this.outputScale;
  539. if (
  540. ((Math.floor(this.viewport.width) * outputScale.sx) | 0) *
  541. ((Math.floor(this.viewport.height) * outputScale.sy) | 0) >
  542. this.maxCanvasPixels
  543. ) {
  544. isScalingRestricted = true;
  545. }
  546. }
  547. const postponeDrawing = drawingDelay >= 0 && drawingDelay < 1000;
  548. if (this.canvas) {
  549. if (
  550. postponeDrawing ||
  551. this.useOnlyCssZoom ||
  552. (this.hasRestrictedScaling && isScalingRestricted)
  553. ) {
  554. if (
  555. postponeDrawing &&
  556. this.renderingState !== RenderingStates.FINISHED
  557. ) {
  558. this.cancelRendering({
  559. keepZoomLayer: true,
  560. keepAnnotationLayer: true,
  561. keepAnnotationEditorLayer: true,
  562. keepXfaLayer: true,
  563. keepTextLayer: true,
  564. cancelExtraDelay: drawingDelay,
  565. });
  566. // It isn't really finished, but once we have finished
  567. // to postpone, we'll call this.reset(...) which will set
  568. // the rendering state to INITIAL, hence the next call to
  569. // PDFViewer.update() will trigger a redraw (if it's mandatory).
  570. this.renderingState = RenderingStates.FINISHED;
  571. }
  572. this.cssTransform({
  573. target: this.canvas,
  574. redrawAnnotationLayer: true,
  575. redrawAnnotationEditorLayer: true,
  576. redrawXfaLayer: true,
  577. redrawTextLayer: !postponeDrawing,
  578. hideTextLayer: postponeDrawing,
  579. });
  580. this.eventBus.dispatch("pagerendered", {
  581. source: this,
  582. pageNumber: this.id,
  583. cssTransform: true,
  584. timestamp: performance.now(),
  585. error: this._renderError,
  586. });
  587. return;
  588. }
  589. if (!this.zoomLayer && !this.canvas.hidden) {
  590. this.zoomLayer = this.canvas.parentNode;
  591. this.zoomLayer.style.position = "absolute";
  592. }
  593. }
  594. if (this.zoomLayer) {
  595. this.cssTransform({ target: this.zoomLayer.firstChild });
  596. }
  597. this.reset({
  598. keepZoomLayer: true,
  599. keepAnnotationLayer: true,
  600. keepAnnotationEditorLayer: true,
  601. keepXfaLayer: true,
  602. keepTextLayer: true,
  603. });
  604. }
  605. /**
  606. * PLEASE NOTE: Most likely you want to use the `this.reset()` method,
  607. * rather than calling this one directly.
  608. */
  609. cancelRendering({
  610. keepAnnotationLayer = false,
  611. keepAnnotationEditorLayer = false,
  612. keepXfaLayer = false,
  613. keepTextLayer = false,
  614. cancelExtraDelay = 0,
  615. } = {}) {
  616. if (this.paintTask) {
  617. this.paintTask.cancel(cancelExtraDelay);
  618. this.paintTask = null;
  619. }
  620. this.resume = null;
  621. if (this.textLayer && (!keepTextLayer || !this.textLayer.div)) {
  622. this.textLayer.cancel();
  623. this.textLayer = null;
  624. }
  625. if (this.structTreeLayer && !this.textLayer) {
  626. this.structTreeLayer = null;
  627. }
  628. if (
  629. this.annotationLayer &&
  630. (!keepAnnotationLayer || !this.annotationLayer.div)
  631. ) {
  632. this.annotationLayer.cancel();
  633. this.annotationLayer = null;
  634. this._annotationCanvasMap = null;
  635. }
  636. if (
  637. this.annotationEditorLayer &&
  638. (!keepAnnotationEditorLayer || !this.annotationEditorLayer.div)
  639. ) {
  640. this.annotationEditorLayer.cancel();
  641. this.annotationEditorLayer = null;
  642. }
  643. if (this.xfaLayer && (!keepXfaLayer || !this.xfaLayer.div)) {
  644. this.xfaLayer.cancel();
  645. this.xfaLayer = null;
  646. this._textHighlighter?.disable();
  647. }
  648. }
  649. cssTransform({
  650. target,
  651. redrawAnnotationLayer = false,
  652. redrawAnnotationEditorLayer = false,
  653. redrawXfaLayer = false,
  654. redrawTextLayer = false,
  655. hideTextLayer = false,
  656. }) {
  657. // Scale target (canvas or svg), its wrapper and page container.
  658. if (target instanceof HTMLCanvasElement) {
  659. if (!target.hasAttribute("zooming")) {
  660. target.setAttribute("zooming", true);
  661. const { style } = target;
  662. style.width = style.height = "";
  663. }
  664. } else {
  665. const div = this.div;
  666. const { width, height } = this.viewport;
  667. target.style.width =
  668. target.parentNode.style.width =
  669. div.style.width =
  670. Math.floor(width) + "px";
  671. target.style.height =
  672. target.parentNode.style.height =
  673. div.style.height =
  674. Math.floor(height) + "px";
  675. }
  676. const originalViewport = this.paintedViewportMap.get(target);
  677. if (this.viewport !== originalViewport) {
  678. // The canvas may have been originally rotated; rotate relative to that.
  679. const relativeRotation =
  680. this.viewport.rotation - originalViewport.rotation;
  681. const absRotation = Math.abs(relativeRotation);
  682. let scaleX = 1,
  683. scaleY = 1;
  684. if (absRotation === 90 || absRotation === 270) {
  685. const { width, height } = this.viewport;
  686. // Scale x and y because of the rotation.
  687. scaleX = height / width;
  688. scaleY = width / height;
  689. }
  690. if (absRotation !== 0) {
  691. target.style.transform = `rotate(${relativeRotation}deg) scale(${scaleX}, ${scaleY})`;
  692. }
  693. }
  694. if (redrawAnnotationLayer && this.annotationLayer) {
  695. this.#renderAnnotationLayer();
  696. }
  697. if (redrawAnnotationEditorLayer && this.annotationEditorLayer) {
  698. this.#renderAnnotationEditorLayer();
  699. }
  700. if (redrawXfaLayer && this.xfaLayer) {
  701. this.#renderXfaLayer();
  702. }
  703. if (this.textLayer) {
  704. if (hideTextLayer) {
  705. this.textLayer.hide();
  706. } else if (redrawTextLayer) {
  707. this.#renderTextLayer();
  708. }
  709. }
  710. }
  711. get width() {
  712. return this.viewport.width;
  713. }
  714. get height() {
  715. return this.viewport.height;
  716. }
  717. getPagePoint(x, y) {
  718. return this.viewport.convertToPdfPoint(x, y);
  719. }
  720. draw() {
  721. if (this.renderingState !== RenderingStates.INITIAL) {
  722. console.error("Must be in new state before drawing");
  723. this.reset(); // Ensure that we reset all state to prevent issues.
  724. }
  725. const { div, pdfPage } = this;
  726. if (!pdfPage) {
  727. this.renderingState = RenderingStates.FINISHED;
  728. return Promise.reject(new Error("pdfPage is not loaded"));
  729. }
  730. this.renderingState = RenderingStates.RUNNING;
  731. // Wrap the canvas so that if it has a CSS transform for high DPI the
  732. // overflow will be hidden in Firefox.
  733. const canvasWrapper = document.createElement("div");
  734. canvasWrapper.classList.add("canvasWrapper");
  735. div.append(canvasWrapper);
  736. if (
  737. !this.textLayer &&
  738. this.textLayerMode !== TextLayerMode.DISABLE &&
  739. !pdfPage.isPureXfa
  740. ) {
  741. this._accessibilityManager ||= new TextAccessibilityManager();
  742. this.textLayer = new TextLayerBuilder({
  743. highlighter: this._textHighlighter,
  744. accessibilityManager: this._accessibilityManager,
  745. isOffscreenCanvasSupported: this.isOffscreenCanvasSupported,
  746. });
  747. div.append(this.textLayer.div);
  748. }
  749. if (
  750. !this.annotationLayer &&
  751. this.#annotationMode !== AnnotationMode.DISABLE
  752. ) {
  753. const {
  754. annotationStorage,
  755. downloadManager,
  756. enableScripting,
  757. fieldObjectsPromise,
  758. hasJSActionsPromise,
  759. linkService,
  760. } = this.#layerProperties();
  761. this._annotationCanvasMap ||= new Map();
  762. this.annotationLayer = new AnnotationLayerBuilder({
  763. pageDiv: div,
  764. pdfPage,
  765. annotationStorage,
  766. imageResourcesPath: this.imageResourcesPath,
  767. renderForms: this.#annotationMode === AnnotationMode.ENABLE_FORMS,
  768. linkService,
  769. downloadManager,
  770. l10n: this.l10n,
  771. enableScripting,
  772. hasJSActionsPromise,
  773. fieldObjectsPromise,
  774. annotationCanvasMap: this._annotationCanvasMap,
  775. accessibilityManager: this._accessibilityManager,
  776. });
  777. }
  778. if (this.xfaLayer?.div) {
  779. // The xfa layer needs to stay on top.
  780. div.append(this.xfaLayer.div);
  781. }
  782. let renderContinueCallback = null;
  783. if (this.renderingQueue) {
  784. renderContinueCallback = cont => {
  785. if (!this.renderingQueue.isHighestPriority(this)) {
  786. this.renderingState = RenderingStates.PAUSED;
  787. this.resume = () => {
  788. this.renderingState = RenderingStates.RUNNING;
  789. cont();
  790. };
  791. return;
  792. }
  793. cont();
  794. };
  795. }
  796. const finishPaintTask = async (error = null) => {
  797. // The paintTask may have been replaced by a new one, so only remove
  798. // the reference to the paintTask if it matches the one that is
  799. // triggering this callback.
  800. if (paintTask === this.paintTask) {
  801. this.paintTask = null;
  802. }
  803. if (error instanceof RenderingCancelledException) {
  804. this._renderError = null;
  805. return;
  806. }
  807. this._renderError = error;
  808. this.renderingState = RenderingStates.FINISHED;
  809. this._resetZoomLayer(/* removeFromDOM = */ true);
  810. // Ensure that the thumbnails won't become partially (or fully) blank,
  811. // for documents that contain interactive form elements.
  812. this.#useThumbnailCanvas.regularAnnotations = !paintTask.separateAnnots;
  813. this.eventBus.dispatch("pagerendered", {
  814. source: this,
  815. pageNumber: this.id,
  816. cssTransform: false,
  817. timestamp: performance.now(),
  818. error: this._renderError,
  819. });
  820. if (error) {
  821. throw error;
  822. }
  823. };
  824. const paintTask =
  825. (typeof PDFJSDev === "undefined" ||
  826. PDFJSDev.test("!PRODUCTION || GENERIC")) &&
  827. this.renderer === RendererType.SVG
  828. ? this.paintOnSvg(canvasWrapper)
  829. : this.paintOnCanvas(canvasWrapper);
  830. paintTask.onRenderContinue = renderContinueCallback;
  831. this.paintTask = paintTask;
  832. const resultPromise = paintTask.promise.then(
  833. () => {
  834. return finishPaintTask(null).then(async () => {
  835. this.#renderTextLayer();
  836. if (this.annotationLayer) {
  837. await this.#renderAnnotationLayer();
  838. }
  839. if (!this.annotationEditorLayer) {
  840. const { annotationEditorUIManager } = this.#layerProperties();
  841. if (!annotationEditorUIManager) {
  842. return;
  843. }
  844. this.annotationEditorLayer = new AnnotationEditorLayerBuilder({
  845. uiManager: annotationEditorUIManager,
  846. pageDiv: div,
  847. pdfPage,
  848. l10n: this.l10n,
  849. accessibilityManager: this._accessibilityManager,
  850. });
  851. }
  852. this.#renderAnnotationEditorLayer();
  853. });
  854. },
  855. function (reason) {
  856. return finishPaintTask(reason);
  857. }
  858. );
  859. if (pdfPage.isPureXfa) {
  860. if (!this.xfaLayer) {
  861. const { annotationStorage, linkService } = this.#layerProperties();
  862. this.xfaLayer = new XfaLayerBuilder({
  863. pageDiv: div,
  864. pdfPage,
  865. annotationStorage,
  866. linkService,
  867. });
  868. }
  869. this.#renderXfaLayer();
  870. }
  871. div.setAttribute("data-loaded", true);
  872. this.eventBus.dispatch("pagerender", {
  873. source: this,
  874. pageNumber: this.id,
  875. });
  876. return resultPromise;
  877. }
  878. paintOnCanvas(canvasWrapper) {
  879. const renderCapability = createPromiseCapability();
  880. const result = {
  881. promise: renderCapability.promise,
  882. onRenderContinue(cont) {
  883. cont();
  884. },
  885. cancel(extraDelay = 0) {
  886. renderTask.cancel(extraDelay);
  887. },
  888. get separateAnnots() {
  889. return renderTask.separateAnnots;
  890. },
  891. };
  892. const viewport = this.viewport;
  893. const { width, height } = viewport;
  894. const canvas = document.createElement("canvas");
  895. canvas.setAttribute("role", "presentation");
  896. // Keep the canvas hidden until the first draw callback, or until drawing
  897. // is complete when `!this.renderingQueue`, to prevent black flickering.
  898. canvas.hidden = true;
  899. let isCanvasHidden = true;
  900. const showCanvas = function () {
  901. if (isCanvasHidden) {
  902. canvas.hidden = false;
  903. isCanvasHidden = false;
  904. }
  905. };
  906. canvasWrapper.append(canvas);
  907. this.canvas = canvas;
  908. const ctx = canvas.getContext("2d", { alpha: false });
  909. const outputScale = (this.outputScale = new OutputScale());
  910. if (this.useOnlyCssZoom) {
  911. const actualSizeViewport = viewport.clone({
  912. scale: PixelsPerInch.PDF_TO_CSS_UNITS,
  913. });
  914. // Use a scale that makes the canvas have the originally intended size
  915. // of the page.
  916. outputScale.sx *= actualSizeViewport.width / width;
  917. outputScale.sy *= actualSizeViewport.height / height;
  918. }
  919. if (this.maxCanvasPixels > 0) {
  920. const pixelsInViewport = width * height;
  921. const maxScale = Math.sqrt(this.maxCanvasPixels / pixelsInViewport);
  922. if (outputScale.sx > maxScale || outputScale.sy > maxScale) {
  923. outputScale.sx = maxScale;
  924. outputScale.sy = maxScale;
  925. this.hasRestrictedScaling = true;
  926. } else {
  927. this.hasRestrictedScaling = false;
  928. }
  929. }
  930. const sfx = approximateFraction(outputScale.sx);
  931. const sfy = approximateFraction(outputScale.sy);
  932. canvas.width = roundToDivide(viewport.width * outputScale.sx, sfx[0]);
  933. canvas.height = roundToDivide(viewport.height * outputScale.sy, sfy[0]);
  934. const { style } = canvas;
  935. style.width = roundToDivide(viewport.width, sfx[1]) + "px";
  936. style.height = roundToDivide(viewport.height, sfy[1]) + "px";
  937. // Add the viewport so it's known what it was originally drawn with.
  938. this.paintedViewportMap.set(canvas, viewport);
  939. // Rendering area
  940. const transform = outputScale.scaled
  941. ? [outputScale.sx, 0, 0, outputScale.sy, 0, 0]
  942. : null;
  943. const renderContext = {
  944. canvasContext: ctx,
  945. transform,
  946. viewport,
  947. annotationMode: this.#annotationMode,
  948. optionalContentConfigPromise: this._optionalContentConfigPromise,
  949. annotationCanvasMap: this._annotationCanvasMap,
  950. pageColors: this.pageColors,
  951. };
  952. const renderTask = this.pdfPage.render(renderContext);
  953. renderTask.onContinue = function (cont) {
  954. showCanvas();
  955. if (result.onRenderContinue) {
  956. result.onRenderContinue(cont);
  957. } else {
  958. cont();
  959. }
  960. };
  961. renderTask.promise.then(
  962. function () {
  963. showCanvas();
  964. renderCapability.resolve();
  965. },
  966. function (error) {
  967. showCanvas();
  968. renderCapability.reject(error);
  969. }
  970. );
  971. return result;
  972. }
  973. paintOnSvg(wrapper) {
  974. if (
  975. !(
  976. typeof PDFJSDev === "undefined" ||
  977. PDFJSDev.test("!PRODUCTION || GENERIC")
  978. )
  979. ) {
  980. throw new Error("Not implemented: paintOnSvg");
  981. }
  982. let cancelled = false;
  983. const ensureNotCancelled = () => {
  984. if (cancelled) {
  985. throw new RenderingCancelledException(
  986. `Rendering cancelled, page ${this.id}`,
  987. "svg"
  988. );
  989. }
  990. };
  991. const pdfPage = this.pdfPage;
  992. const actualSizeViewport = this.viewport.clone({
  993. scale: PixelsPerInch.PDF_TO_CSS_UNITS,
  994. });
  995. const promise = pdfPage
  996. .getOperatorList({
  997. annotationMode: this.#annotationMode,
  998. })
  999. .then(opList => {
  1000. ensureNotCancelled();
  1001. const svgGfx = new SVGGraphics(pdfPage.commonObjs, pdfPage.objs);
  1002. return svgGfx.getSVG(opList, actualSizeViewport).then(svg => {
  1003. ensureNotCancelled();
  1004. this.svg = svg;
  1005. this.paintedViewportMap.set(svg, actualSizeViewport);
  1006. svg.style.width = wrapper.style.width;
  1007. svg.style.height = wrapper.style.height;
  1008. this.renderingState = RenderingStates.FINISHED;
  1009. wrapper.append(svg);
  1010. });
  1011. });
  1012. return {
  1013. promise,
  1014. onRenderContinue(cont) {
  1015. cont();
  1016. },
  1017. cancel() {
  1018. cancelled = true;
  1019. },
  1020. get separateAnnots() {
  1021. return false;
  1022. },
  1023. };
  1024. }
  1025. /**
  1026. * @param {string|null} label
  1027. */
  1028. setPageLabel(label) {
  1029. this.pageLabel = typeof label === "string" ? label : null;
  1030. if (this.pageLabel !== null) {
  1031. this.div.setAttribute("data-page-label", this.pageLabel);
  1032. } else {
  1033. this.div.removeAttribute("data-page-label");
  1034. }
  1035. }
  1036. /**
  1037. * For use by the `PDFThumbnailView.setImage`-method.
  1038. * @ignore
  1039. */
  1040. get thumbnailCanvas() {
  1041. const { initialOptionalContent, regularAnnotations } =
  1042. this.#useThumbnailCanvas;
  1043. return initialOptionalContent && regularAnnotations ? this.canvas : null;
  1044. }
  1045. }
  1046. export { PDFPageView };