| /** |
| * @licstart The following is the entire license notice for the |
| * Javascript code in this page |
| * |
| * Copyright 2020 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. |
| * |
| * @licend The above is the entire license notice for the |
| * Javascript code in this page |
| */ |
| "use strict"; |
| |
| Object.defineProperty(exports, "__esModule", { |
| value: true |
| }); |
| exports.PDFPageView = void 0; |
| |
| var _ui_utils = require("./ui_utils.js"); |
| |
| var _pdf = require("../pdf"); |
| |
| var _pdf_rendering_queue = require("./pdf_rendering_queue.js"); |
| |
| var _viewer_compatibility = require("./viewer_compatibility.js"); |
| |
| const MAX_CANVAS_PIXELS = _viewer_compatibility.viewerCompatibilityParams.maxCanvasPixels || 16777216; |
| |
| class PDFPageView { |
| constructor(options) { |
| const container = options.container; |
| const defaultViewport = options.defaultViewport; |
| this.id = options.id; |
| this.renderingId = "page" + this.id; |
| this.pdfPage = null; |
| this.pageLabel = null; |
| this.rotation = 0; |
| this.scale = options.scale || _ui_utils.DEFAULT_SCALE; |
| this.viewport = defaultViewport; |
| this.pdfPageRotate = defaultViewport.rotation; |
| this._annotationStorage = options.annotationStorage || null; |
| this._optionalContentConfigPromise = options.optionalContentConfigPromise || null; |
| this.hasRestrictedScaling = false; |
| this.textLayerMode = Number.isInteger(options.textLayerMode) ? options.textLayerMode : _ui_utils.TextLayerMode.ENABLE; |
| this.imageResourcesPath = options.imageResourcesPath || ""; |
| this.renderInteractiveForms = typeof options.renderInteractiveForms === "boolean" ? options.renderInteractiveForms : true; |
| this.useOnlyCssZoom = options.useOnlyCssZoom || false; |
| this.maxCanvasPixels = options.maxCanvasPixels || MAX_CANVAS_PIXELS; |
| this.eventBus = options.eventBus; |
| this.renderingQueue = options.renderingQueue; |
| this.textLayerFactory = options.textLayerFactory; |
| this.annotationLayerFactory = options.annotationLayerFactory; |
| this.renderer = options.renderer || _ui_utils.RendererType.CANVAS; |
| this.enableWebGL = options.enableWebGL || false; |
| this.l10n = options.l10n || _ui_utils.NullL10n; |
| this.paintTask = null; |
| this.paintedViewportMap = new WeakMap(); |
| this.renderingState = _pdf_rendering_queue.RenderingStates.INITIAL; |
| this.resume = null; |
| this.error = null; |
| this.annotationLayer = null; |
| this.textLayer = null; |
| this.zoomLayer = null; |
| const div = document.createElement("div"); |
| div.className = "page"; |
| div.style.width = Math.floor(this.viewport.width) + "px"; |
| div.style.height = Math.floor(this.viewport.height) + "px"; |
| div.setAttribute("data-page-number", this.id); |
| this.div = div; |
| container.appendChild(div); |
| } |
| |
| setPdfPage(pdfPage) { |
| this.pdfPage = pdfPage; |
| this.pdfPageRotate = pdfPage.rotate; |
| const totalRotation = (this.rotation + this.pdfPageRotate) % 360; |
| this.viewport = pdfPage.getViewport({ |
| scale: this.scale * _ui_utils.CSS_UNITS, |
| rotation: totalRotation |
| }); |
| this.stats = pdfPage.stats; |
| this.reset(); |
| } |
| |
| destroy() { |
| this.reset(); |
| |
| if (this.pdfPage) { |
| this.pdfPage.cleanup(); |
| } |
| } |
| |
| async _renderAnnotationLayer() { |
| let error = null; |
| |
| try { |
| await this.annotationLayer.render(this.viewport, "display"); |
| } catch (ex) { |
| error = ex; |
| } finally { |
| this.eventBus.dispatch("annotationlayerrendered", { |
| source: this, |
| pageNumber: this.id, |
| error |
| }); |
| } |
| } |
| |
| _resetZoomLayer(removeFromDOM = false) { |
| if (!this.zoomLayer) { |
| return; |
| } |
| |
| const zoomLayerCanvas = this.zoomLayer.firstChild; |
| this.paintedViewportMap.delete(zoomLayerCanvas); |
| zoomLayerCanvas.width = 0; |
| zoomLayerCanvas.height = 0; |
| |
| if (removeFromDOM) { |
| this.zoomLayer.remove(); |
| } |
| |
| this.zoomLayer = null; |
| } |
| |
| reset(keepZoomLayer = false, keepAnnotations = false) { |
| this.cancelRendering(keepAnnotations); |
| this.renderingState = _pdf_rendering_queue.RenderingStates.INITIAL; |
| const div = this.div; |
| div.style.width = Math.floor(this.viewport.width) + "px"; |
| div.style.height = Math.floor(this.viewport.height) + "px"; |
| const childNodes = div.childNodes; |
| const currentZoomLayerNode = keepZoomLayer && this.zoomLayer || null; |
| const currentAnnotationNode = keepAnnotations && this.annotationLayer && this.annotationLayer.div || null; |
| |
| for (let i = childNodes.length - 1; i >= 0; i--) { |
| const node = childNodes[i]; |
| |
| if (currentZoomLayerNode === node || currentAnnotationNode === node) { |
| continue; |
| } |
| |
| div.removeChild(node); |
| } |
| |
| div.removeAttribute("data-loaded"); |
| |
| if (currentAnnotationNode) { |
| this.annotationLayer.hide(); |
| } else if (this.annotationLayer) { |
| this.annotationLayer.cancel(); |
| this.annotationLayer = null; |
| } |
| |
| if (!currentZoomLayerNode) { |
| if (this.canvas) { |
| this.paintedViewportMap.delete(this.canvas); |
| this.canvas.width = 0; |
| this.canvas.height = 0; |
| delete this.canvas; |
| } |
| |
| this._resetZoomLayer(); |
| } |
| |
| if (this.svg) { |
| this.paintedViewportMap.delete(this.svg); |
| delete this.svg; |
| } |
| |
| this.loadingIconDiv = document.createElement("div"); |
| this.loadingIconDiv.className = "loadingIcon"; |
| div.appendChild(this.loadingIconDiv); |
| } |
| |
| update(scale, rotation, optionalContentConfigPromise = null) { |
| this.scale = scale || this.scale; |
| |
| if (typeof rotation !== "undefined") { |
| this.rotation = rotation; |
| } |
| |
| if (optionalContentConfigPromise instanceof Promise) { |
| this._optionalContentConfigPromise = optionalContentConfigPromise; |
| } |
| |
| const totalRotation = (this.rotation + this.pdfPageRotate) % 360; |
| this.viewport = this.viewport.clone({ |
| scale: this.scale * _ui_utils.CSS_UNITS, |
| rotation: totalRotation |
| }); |
| |
| if (this.svg) { |
| this.cssTransform(this.svg, true); |
| this.eventBus.dispatch("pagerendered", { |
| source: this, |
| pageNumber: this.id, |
| cssTransform: true, |
| timestamp: performance.now() |
| }); |
| 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; |
| } |
| } |
| |
| if (this.canvas) { |
| if (this.useOnlyCssZoom || this.hasRestrictedScaling && isScalingRestricted) { |
| this.cssTransform(this.canvas, true); |
| this.eventBus.dispatch("pagerendered", { |
| source: this, |
| pageNumber: this.id, |
| cssTransform: true, |
| timestamp: performance.now() |
| }); |
| return; |
| } |
| |
| if (!this.zoomLayer && !this.canvas.hasAttribute("hidden")) { |
| this.zoomLayer = this.canvas.parentNode; |
| this.zoomLayer.style.position = "absolute"; |
| } |
| } |
| |
| if (this.zoomLayer) { |
| this.cssTransform(this.zoomLayer.firstChild); |
| } |
| |
| this.reset(true, true); |
| } |
| |
| cancelRendering(keepAnnotations = false) { |
| if (this.paintTask) { |
| this.paintTask.cancel(); |
| this.paintTask = null; |
| } |
| |
| this.resume = null; |
| |
| if (this.textLayer) { |
| this.textLayer.cancel(); |
| this.textLayer = null; |
| } |
| |
| if (!keepAnnotations && this.annotationLayer) { |
| this.annotationLayer.cancel(); |
| this.annotationLayer = null; |
| } |
| } |
| |
| cssTransform(target, redrawAnnotations = false) { |
| const width = this.viewport.width; |
| const height = this.viewport.height; |
| const div = this.div; |
| 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 relativeRotation = this.viewport.rotation - this.paintedViewportMap.get(target).rotation; |
| const absRotation = Math.abs(relativeRotation); |
| let scaleX = 1, |
| scaleY = 1; |
| |
| if (absRotation === 90 || absRotation === 270) { |
| scaleX = height / width; |
| scaleY = width / height; |
| } |
| |
| const cssTransform = "rotate(" + relativeRotation + "deg) " + "scale(" + scaleX + "," + scaleY + ")"; |
| target.style.transform = cssTransform; |
| |
| if (this.textLayer) { |
| const textLayerViewport = this.textLayer.viewport; |
| const textRelativeRotation = this.viewport.rotation - textLayerViewport.rotation; |
| const textAbsRotation = Math.abs(textRelativeRotation); |
| let scale = width / textLayerViewport.width; |
| |
| if (textAbsRotation === 90 || textAbsRotation === 270) { |
| scale = width / textLayerViewport.height; |
| } |
| |
| const textLayerDiv = this.textLayer.textLayerDiv; |
| let transX, transY; |
| |
| switch (textAbsRotation) { |
| case 0: |
| transX = transY = 0; |
| break; |
| |
| case 90: |
| transX = 0; |
| transY = "-" + textLayerDiv.style.height; |
| break; |
| |
| case 180: |
| transX = "-" + textLayerDiv.style.width; |
| transY = "-" + textLayerDiv.style.height; |
| break; |
| |
| case 270: |
| transX = "-" + textLayerDiv.style.width; |
| transY = 0; |
| break; |
| |
| default: |
| console.error("Bad rotation value."); |
| break; |
| } |
| |
| textLayerDiv.style.transform = "rotate(" + textAbsRotation + "deg) " + "scale(" + scale + ", " + scale + ") " + "translate(" + transX + ", " + transY + ")"; |
| textLayerDiv.style.transformOrigin = "0% 0%"; |
| } |
| |
| if (redrawAnnotations && this.annotationLayer) { |
| this._renderAnnotationLayer(); |
| } |
| } |
| |
| 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 !== _pdf_rendering_queue.RenderingStates.INITIAL) { |
| console.error("Must be in new state before drawing"); |
| this.reset(); |
| } |
| |
| const { |
| div, |
| pdfPage |
| } = this; |
| |
| if (!pdfPage) { |
| this.renderingState = _pdf_rendering_queue.RenderingStates.FINISHED; |
| |
| if (this.loadingIconDiv) { |
| div.removeChild(this.loadingIconDiv); |
| delete this.loadingIconDiv; |
| } |
| |
| return Promise.reject(new Error("pdfPage is not loaded")); |
| } |
| |
| this.renderingState = _pdf_rendering_queue.RenderingStates.RUNNING; |
| const canvasWrapper = document.createElement("div"); |
| canvasWrapper.style.width = div.style.width; |
| canvasWrapper.style.height = div.style.height; |
| canvasWrapper.classList.add("canvasWrapper"); |
| |
| if (this.annotationLayer && this.annotationLayer.div) { |
| div.insertBefore(canvasWrapper, this.annotationLayer.div); |
| } else { |
| div.appendChild(canvasWrapper); |
| } |
| |
| let textLayer = null; |
| |
| if (this.textLayerMode !== _ui_utils.TextLayerMode.DISABLE && this.textLayerFactory) { |
| const textLayerDiv = document.createElement("div"); |
| textLayerDiv.className = "textLayer"; |
| textLayerDiv.style.width = canvasWrapper.style.width; |
| textLayerDiv.style.height = canvasWrapper.style.height; |
| |
| if (this.annotationLayer && this.annotationLayer.div) { |
| div.insertBefore(textLayerDiv, this.annotationLayer.div); |
| } else { |
| div.appendChild(textLayerDiv); |
| } |
| |
| textLayer = this.textLayerFactory.createTextLayerBuilder(textLayerDiv, this.id - 1, this.viewport, this.textLayerMode === _ui_utils.TextLayerMode.ENABLE_ENHANCE, this.eventBus); |
| } |
| |
| this.textLayer = textLayer; |
| let renderContinueCallback = null; |
| |
| if (this.renderingQueue) { |
| renderContinueCallback = cont => { |
| if (!this.renderingQueue.isHighestPriority(this)) { |
| this.renderingState = _pdf_rendering_queue.RenderingStates.PAUSED; |
| |
| this.resume = () => { |
| this.renderingState = _pdf_rendering_queue.RenderingStates.RUNNING; |
| cont(); |
| }; |
| |
| return; |
| } |
| |
| cont(); |
| }; |
| } |
| |
| const finishPaintTask = async error => { |
| if (paintTask === this.paintTask) { |
| this.paintTask = null; |
| } |
| |
| if (error instanceof _pdf.RenderingCancelledException) { |
| this.error = null; |
| return; |
| } |
| |
| this.renderingState = _pdf_rendering_queue.RenderingStates.FINISHED; |
| |
| if (this.loadingIconDiv) { |
| div.removeChild(this.loadingIconDiv); |
| delete this.loadingIconDiv; |
| } |
| |
| this._resetZoomLayer(true); |
| |
| this.error = error; |
| this.stats = pdfPage.stats; |
| this.eventBus.dispatch("pagerendered", { |
| source: this, |
| pageNumber: this.id, |
| cssTransform: false, |
| timestamp: performance.now() |
| }); |
| |
| if (error) { |
| throw error; |
| } |
| }; |
| |
| const paintTask = this.renderer === _ui_utils.RendererType.SVG ? this.paintOnSvg(canvasWrapper) : this.paintOnCanvas(canvasWrapper); |
| paintTask.onRenderContinue = renderContinueCallback; |
| this.paintTask = paintTask; |
| const resultPromise = paintTask.promise.then(function () { |
| return finishPaintTask(null).then(function () { |
| if (textLayer) { |
| const readableStream = pdfPage.streamTextContent({ |
| normalizeWhitespace: true |
| }); |
| textLayer.setTextContentStream(readableStream); |
| textLayer.render(); |
| } |
| }); |
| }, function (reason) { |
| return finishPaintTask(reason); |
| }); |
| |
| if (this.annotationLayerFactory) { |
| if (!this.annotationLayer) { |
| this.annotationLayer = this.annotationLayerFactory.createAnnotationLayerBuilder(div, pdfPage, this._annotationStorage, this.imageResourcesPath, this.renderInteractiveForms, this.l10n); |
| } |
| |
| this._renderAnnotationLayer(); |
| } |
| |
| div.setAttribute("data-loaded", true); |
| this.eventBus.dispatch("pagerender", { |
| source: this, |
| pageNumber: this.id |
| }); |
| return resultPromise; |
| } |
| |
| paintOnCanvas(canvasWrapper) { |
| const renderCapability = (0, _pdf.createPromiseCapability)(); |
| const result = { |
| promise: renderCapability.promise, |
| |
| onRenderContinue(cont) { |
| cont(); |
| }, |
| |
| cancel() { |
| renderTask.cancel(); |
| } |
| |
| }; |
| const viewport = this.viewport; |
| const canvas = document.createElement("canvas"); |
| this.l10n.get("page_canvas", { |
| page: this.id |
| }, "Page {{page}}").then(msg => { |
| canvas.setAttribute("aria-label", msg); |
| }); |
| canvas.setAttribute("hidden", "hidden"); |
| let isCanvasHidden = true; |
| |
| const showCanvas = function () { |
| if (isCanvasHidden) { |
| canvas.removeAttribute("hidden"); |
| isCanvasHidden = false; |
| } |
| }; |
| |
| canvasWrapper.appendChild(canvas); |
| this.canvas = canvas; |
| canvas.mozOpaque = true; |
| const ctx = canvas.getContext("2d", { |
| alpha: false |
| }); |
| const outputScale = (0, _ui_utils.getOutputScale)(ctx); |
| this.outputScale = outputScale; |
| |
| if (this.useOnlyCssZoom) { |
| const actualSizeViewport = viewport.clone({ |
| scale: _ui_utils.CSS_UNITS |
| }); |
| outputScale.sx *= actualSizeViewport.width / viewport.width; |
| outputScale.sy *= actualSizeViewport.height / viewport.height; |
| outputScale.scaled = true; |
| } |
| |
| if (this.maxCanvasPixels > 0) { |
| const pixelsInViewport = viewport.width * viewport.height; |
| const maxScale = Math.sqrt(this.maxCanvasPixels / pixelsInViewport); |
| |
| if (outputScale.sx > maxScale || outputScale.sy > maxScale) { |
| outputScale.sx = maxScale; |
| outputScale.sy = maxScale; |
| outputScale.scaled = true; |
| this.hasRestrictedScaling = true; |
| } else { |
| this.hasRestrictedScaling = false; |
| } |
| } |
| |
| const sfx = (0, _ui_utils.approximateFraction)(outputScale.sx); |
| const sfy = (0, _ui_utils.approximateFraction)(outputScale.sy); |
| canvas.width = (0, _ui_utils.roundToDivide)(viewport.width * outputScale.sx, sfx[0]); |
| canvas.height = (0, _ui_utils.roundToDivide)(viewport.height * outputScale.sy, sfy[0]); |
| canvas.style.width = (0, _ui_utils.roundToDivide)(viewport.width, sfx[1]) + "px"; |
| canvas.style.height = (0, _ui_utils.roundToDivide)(viewport.height, sfy[1]) + "px"; |
| this.paintedViewportMap.set(canvas, viewport); |
| const transform = !outputScale.scaled ? null : [outputScale.sx, 0, 0, outputScale.sy, 0, 0]; |
| const renderContext = { |
| canvasContext: ctx, |
| transform, |
| viewport: this.viewport, |
| enableWebGL: this.enableWebGL, |
| renderInteractiveForms: this.renderInteractiveForms, |
| optionalContentConfigPromise: this._optionalContentConfigPromise |
| }; |
| 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(undefined); |
| }, function (error) { |
| showCanvas(); |
| renderCapability.reject(error); |
| }); |
| return result; |
| } |
| |
| paintOnSvg(wrapper) { |
| let cancelled = false; |
| |
| const ensureNotCancelled = () => { |
| if (cancelled) { |
| throw new _pdf.RenderingCancelledException(`Rendering cancelled, page ${this.id}`, "svg"); |
| } |
| }; |
| |
| const pdfPage = this.pdfPage; |
| const actualSizeViewport = this.viewport.clone({ |
| scale: _ui_utils.CSS_UNITS |
| }); |
| const promise = pdfPage.getOperatorList().then(opList => { |
| ensureNotCancelled(); |
| const svgGfx = new _pdf.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 = _pdf_rendering_queue.RenderingStates.FINISHED; |
| wrapper.appendChild(svg); |
| }); |
| }); |
| return { |
| promise, |
| |
| onRenderContinue(cont) { |
| cont(); |
| }, |
| |
| cancel() { |
| cancelled = true; |
| } |
| |
| }; |
| } |
| |
| 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"); |
| } |
| } |
| |
| } |
| |
| exports.PDFPageView = PDFPageView; |