diff --git a/src/display/annotation_layer.js b/src/display/annotation_layer.js index 59cc9dfcb..80af717fd 100644 --- a/src/display/annotation_layer.js +++ b/src/display/annotation_layer.js @@ -14,7 +14,7 @@ */ /** @typedef {import("./api").PDFPageProxy} PDFPageProxy */ -/** @typedef {import("./display_utils").PageViewport} PageViewport */ +/** @typedef {import("./page_viewport").PageViewport} PageViewport */ // eslint-disable-next-line max-len /** @typedef {import("../../web/text_accessibility.js").TextAccessibilityManager} TextAccessibilityManager */ // eslint-disable-next-line max-len diff --git a/src/display/api.js b/src/display/api.js index 005d1ec5a..6a9077694 100644 --- a/src/display/api.js +++ b/src/display/api.js @@ -57,7 +57,6 @@ import { import { isDataScheme, isValidFetchUrl, - PageViewport, RenderingCancelledException, StatTimer, } from "./display_utils.js"; @@ -78,6 +77,7 @@ import { MathClamp } from "../shared/math_clamp.js"; import { Metadata } from "./metadata.js"; import { OptionalContentConfig } from "./optional_content_config.js"; import { PagesMapper } from "./pages_mapper.js"; +import { PageViewport } from "./page_viewport.js"; import { PDFDataTransportStream } from "./transport_stream.js"; import { PDFObjects } from "./pdf_objects.js"; import { TextLayer } from "./text_layer.js"; diff --git a/src/display/display_utils.js b/src/display/display_utils.js index 2d96f1b4d..2c3fd3f2d 100644 --- a/src/display/display_utils.js +++ b/src/display/display_utils.js @@ -22,6 +22,7 @@ import { warn, } from "../shared/util.js"; import { MathClamp } from "../shared/math_clamp.js"; +import { PageViewport } from "./page_viewport.js"; import { XfaLayer } from "./xfa_layer.js"; const SVG_NS = "http://www.w3.org/2000/svg"; @@ -84,220 +85,6 @@ async function fetchData(url, type = "text") { }); } -/** - * @typedef {Object} PageViewportParameters - * @property {Array} viewBox - The xMin, yMin, xMax and - * yMax coordinates. - * @property {number} userUnit - The size of units. - * @property {number} scale - The scale of the viewport. - * @property {number} rotation - The rotation, in degrees, of the viewport. - * @property {number} [offsetX] - The horizontal, i.e. x-axis, offset. The - * default value is `0`. - * @property {number} [offsetY] - The vertical, i.e. y-axis, offset. The - * default value is `0`. - * @property {boolean} [dontFlip] - If true, the y-axis will not be flipped. - * The default value is `false`. - */ - -/** - * @typedef {Object} PageViewportCloneParameters - * @property {number} [scale] - The scale, overriding the one in the cloned - * viewport. The default value is `this.scale`. - * @property {number} [rotation] - The rotation, in degrees, overriding the one - * in the cloned viewport. The default value is `this.rotation`. - * @property {number} [offsetX] - The horizontal, i.e. x-axis, offset. - * The default value is `this.offsetX`. - * @property {number} [offsetY] - The vertical, i.e. y-axis, offset. - * The default value is `this.offsetY`. - * @property {boolean} [dontFlip] - If true, the x-axis will not be flipped. - * The default value is `false`. - */ - -/** - * PDF page viewport created based on scale, rotation and offset. - */ -class PageViewport { - /** - * @param {PageViewportParameters} - */ - constructor({ - viewBox, - userUnit, - scale, - rotation, - offsetX = 0, - offsetY = 0, - dontFlip = false, - }) { - this.viewBox = viewBox; - this.userUnit = userUnit; - this.scale = scale; - this.rotation = rotation; - this.offsetX = offsetX; - this.offsetY = offsetY; - - scale *= userUnit; // Take the userUnit into account. - - // creating transform to convert pdf coordinate system to the normal - // canvas like coordinates taking in account scale and rotation - const centerX = (viewBox[2] + viewBox[0]) / 2; - const centerY = (viewBox[3] + viewBox[1]) / 2; - let rotateA, rotateB, rotateC, rotateD; - // Normalize the rotation, by clamping it to the [0, 360) range. - rotation %= 360; - if (rotation < 0) { - rotation += 360; - } - switch (rotation) { - case 180: - rotateA = -1; - rotateB = 0; - rotateC = 0; - rotateD = 1; - break; - case 90: - rotateA = 0; - rotateB = 1; - rotateC = 1; - rotateD = 0; - break; - case 270: - rotateA = 0; - rotateB = -1; - rotateC = -1; - rotateD = 0; - break; - case 0: - rotateA = 1; - rotateB = 0; - rotateC = 0; - rotateD = -1; - break; - default: - throw new Error( - "PageViewport: Invalid rotation, must be a multiple of 90 degrees." - ); - } - - if (dontFlip) { - rotateC = -rotateC; - rotateD = -rotateD; - } - - let offsetCanvasX, offsetCanvasY; - let width, height; - if (rotateA === 0) { - offsetCanvasX = Math.abs(centerY - viewBox[1]) * scale + offsetX; - offsetCanvasY = Math.abs(centerX - viewBox[0]) * scale + offsetY; - width = (viewBox[3] - viewBox[1]) * scale; - height = (viewBox[2] - viewBox[0]) * scale; - } else { - offsetCanvasX = Math.abs(centerX - viewBox[0]) * scale + offsetX; - offsetCanvasY = Math.abs(centerY - viewBox[1]) * scale + offsetY; - width = (viewBox[2] - viewBox[0]) * scale; - height = (viewBox[3] - viewBox[1]) * scale; - } - // creating transform for the following operations: - // translate(-centerX, -centerY), rotate and flip vertically, - // scale, and translate(offsetCanvasX, offsetCanvasY) - this.transform = [ - rotateA * scale, - rotateB * scale, - rotateC * scale, - rotateD * scale, - offsetCanvasX - rotateA * scale * centerX - rotateC * scale * centerY, - offsetCanvasY - rotateB * scale * centerX - rotateD * scale * centerY, - ]; - - this.width = width; - this.height = height; - } - - /** - * The original, un-scaled, viewport dimensions. - * @type {Object} - */ - get rawDims() { - const dims = this.viewBox; - - return shadow(this, "rawDims", { - pageWidth: dims[2] - dims[0], - pageHeight: dims[3] - dims[1], - pageX: dims[0], - pageY: dims[1], - }); - } - - /** - * Clones viewport, with optional additional properties. - * @param {PageViewportCloneParameters} [params] - * @returns {PageViewport} Cloned viewport. - */ - clone({ - scale = this.scale, - rotation = this.rotation, - offsetX = this.offsetX, - offsetY = this.offsetY, - dontFlip = false, - } = {}) { - return new PageViewport({ - viewBox: this.viewBox.slice(), - userUnit: this.userUnit, - scale, - rotation, - offsetX, - offsetY, - dontFlip, - }); - } - - /** - * Converts PDF point to the viewport coordinates. For examples, useful for - * converting PDF location into canvas pixel coordinates. - * @param {number} x - The x-coordinate. - * @param {number} y - The y-coordinate. - * @returns {Array} Array containing `x`- and `y`-coordinates of the - * point in the viewport coordinate space. - * @see {@link convertToPdfPoint} - * @see {@link convertToViewportRectangle} - */ - convertToViewportPoint(x, y) { - const p = [x, y]; - Util.applyTransform(p, this.transform); - return p; - } - - /** - * Converts PDF rectangle to the viewport coordinates. - * @param {Array} rect - The xMin, yMin, xMax and yMax coordinates. - * @returns {Array} Array containing corresponding coordinates of the - * rectangle in the viewport coordinate space. - * @see {@link convertToViewportPoint} - */ - convertToViewportRectangle(rect) { - const topLeft = [rect[0], rect[1]]; - Util.applyTransform(topLeft, this.transform); - const bottomRight = [rect[2], rect[3]]; - Util.applyTransform(bottomRight, this.transform); - return [topLeft[0], topLeft[1], bottomRight[0], bottomRight[1]]; - } - - /** - * Converts viewport coordinates to the PDF location. For examples, useful - * for converting canvas pixel location into PDF one. - * @param {number} x - The x-coordinate. - * @param {number} y - The y-coordinate. - * @returns {Array} Array containing `x`- and `y`-coordinates of the - * point in the PDF coordinate space. - * @see {@link convertToViewportPoint} - */ - convertToPdfPoint(x, y) { - const p = [x, y]; - Util.applyInverseTransform(p, this.transform); - return p; - } -} - class RenderingCancelledException extends BaseException { constructor(msg, extraDelay = 0) { super(msg, "RenderingCancelledException"); @@ -1065,7 +852,6 @@ export { makePathFromDrawOPS, noContextMenu, OutputScale, - PageViewport, PDFDateString, PixelsPerInch, RenderingCancelledException, diff --git a/src/display/editor/annotation_editor_layer.js b/src/display/editor/annotation_editor_layer.js index 32fd60436..9e5544854 100644 --- a/src/display/editor/annotation_editor_layer.js +++ b/src/display/editor/annotation_editor_layer.js @@ -15,7 +15,7 @@ // eslint-disable-next-line max-len /** @typedef {import("./tools.js").AnnotationEditorUIManager} AnnotationEditorUIManager */ -/** @typedef {import("../display_utils.js").PageViewport} PageViewport */ +/** @typedef {import("../page_viewport.js").PageViewport} PageViewport */ // eslint-disable-next-line max-len /** @typedef {import("../../../web/text_accessibility.js").TextAccessibilityManager} TextAccessibilityManager */ // eslint-disable-next-line max-len diff --git a/src/display/page_viewport.js b/src/display/page_viewport.js new file mode 100644 index 000000000..07d10dc83 --- /dev/null +++ b/src/display/page_viewport.js @@ -0,0 +1,232 @@ +/* Copyright 2015 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. + */ + +import { shadow, Util } from "../shared/util.js"; + +/** + * @typedef {Object} PageViewportParameters + * @property {Array} viewBox - The xMin, yMin, xMax and + * yMax coordinates. + * @property {number} userUnit - The size of units. + * @property {number} scale - The scale of the viewport. + * @property {number} rotation - The rotation, in degrees, of the viewport. + * @property {number} [offsetX] - The horizontal, i.e. x-axis, offset. The + * default value is `0`. + * @property {number} [offsetY] - The vertical, i.e. y-axis, offset. The + * default value is `0`. + * @property {boolean} [dontFlip] - If true, the y-axis will not be flipped. + * The default value is `false`. + */ + +/** + * @typedef {Object} PageViewportCloneParameters + * @property {number} [scale] - The scale, overriding the one in the cloned + * viewport. The default value is `this.scale`. + * @property {number} [rotation] - The rotation, in degrees, overriding the one + * in the cloned viewport. The default value is `this.rotation`. + * @property {number} [offsetX] - The horizontal, i.e. x-axis, offset. + * The default value is `this.offsetX`. + * @property {number} [offsetY] - The vertical, i.e. y-axis, offset. + * The default value is `this.offsetY`. + * @property {boolean} [dontFlip] - If true, the x-axis will not be flipped. + * The default value is `false`. + */ + +/** + * PDF page viewport created based on scale, rotation and offset. + */ +class PageViewport { + /** + * @param {PageViewportParameters} + */ + constructor({ + viewBox, + userUnit, + scale, + rotation, + offsetX = 0, + offsetY = 0, + dontFlip = false, + }) { + this.viewBox = viewBox; + this.userUnit = userUnit; + this.scale = scale; + this.rotation = rotation; + this.offsetX = offsetX; + this.offsetY = offsetY; + + scale *= userUnit; // Take the userUnit into account. + + // creating transform to convert pdf coordinate system to the normal + // canvas like coordinates taking in account scale and rotation + const centerX = (viewBox[2] + viewBox[0]) / 2; + const centerY = (viewBox[3] + viewBox[1]) / 2; + let rotateA, rotateB, rotateC, rotateD; + // Normalize the rotation, by clamping it to the [0, 360) range. + rotation %= 360; + if (rotation < 0) { + rotation += 360; + } + switch (rotation) { + case 180: + rotateA = -1; + rotateB = 0; + rotateC = 0; + rotateD = 1; + break; + case 90: + rotateA = 0; + rotateB = 1; + rotateC = 1; + rotateD = 0; + break; + case 270: + rotateA = 0; + rotateB = -1; + rotateC = -1; + rotateD = 0; + break; + case 0: + rotateA = 1; + rotateB = 0; + rotateC = 0; + rotateD = -1; + break; + default: + throw new Error( + "PageViewport: Invalid rotation, must be a multiple of 90 degrees." + ); + } + + if (dontFlip) { + rotateC = -rotateC; + rotateD = -rotateD; + } + + let offsetCanvasX, offsetCanvasY; + let width, height; + if (rotateA === 0) { + offsetCanvasX = Math.abs(centerY - viewBox[1]) * scale + offsetX; + offsetCanvasY = Math.abs(centerX - viewBox[0]) * scale + offsetY; + width = (viewBox[3] - viewBox[1]) * scale; + height = (viewBox[2] - viewBox[0]) * scale; + } else { + offsetCanvasX = Math.abs(centerX - viewBox[0]) * scale + offsetX; + offsetCanvasY = Math.abs(centerY - viewBox[1]) * scale + offsetY; + width = (viewBox[2] - viewBox[0]) * scale; + height = (viewBox[3] - viewBox[1]) * scale; + } + // creating transform for the following operations: + // translate(-centerX, -centerY), rotate and flip vertically, + // scale, and translate(offsetCanvasX, offsetCanvasY) + this.transform = [ + rotateA * scale, + rotateB * scale, + rotateC * scale, + rotateD * scale, + offsetCanvasX - rotateA * scale * centerX - rotateC * scale * centerY, + offsetCanvasY - rotateB * scale * centerX - rotateD * scale * centerY, + ]; + + this.width = width; + this.height = height; + } + + /** + * The original, un-scaled, viewport dimensions. + * @type {Object} + */ + get rawDims() { + const dims = this.viewBox; + + return shadow(this, "rawDims", { + pageWidth: dims[2] - dims[0], + pageHeight: dims[3] - dims[1], + pageX: dims[0], + pageY: dims[1], + }); + } + + /** + * Clones viewport, with optional additional properties. + * @param {PageViewportCloneParameters} [params] + * @returns {PageViewport} Cloned viewport. + */ + clone({ + scale = this.scale, + rotation = this.rotation, + offsetX = this.offsetX, + offsetY = this.offsetY, + dontFlip = false, + } = {}) { + return new PageViewport({ + viewBox: this.viewBox.slice(), + userUnit: this.userUnit, + scale, + rotation, + offsetX, + offsetY, + dontFlip, + }); + } + + /** + * Converts PDF point to the viewport coordinates. For examples, useful for + * converting PDF location into canvas pixel coordinates. + * @param {number} x - The x-coordinate. + * @param {number} y - The y-coordinate. + * @returns {Array} Array containing `x`- and `y`-coordinates of the + * point in the viewport coordinate space. + * @see {@link convertToPdfPoint} + * @see {@link convertToViewportRectangle} + */ + convertToViewportPoint(x, y) { + const p = [x, y]; + Util.applyTransform(p, this.transform); + return p; + } + + /** + * Converts PDF rectangle to the viewport coordinates. + * @param {Array} rect - The xMin, yMin, xMax and yMax coordinates. + * @returns {Array} Array containing corresponding coordinates of the + * rectangle in the viewport coordinate space. + * @see {@link convertToViewportPoint} + */ + convertToViewportRectangle(rect) { + const topLeft = [rect[0], rect[1]]; + Util.applyTransform(topLeft, this.transform); + const bottomRight = [rect[2], rect[3]]; + Util.applyTransform(bottomRight, this.transform); + return [topLeft[0], topLeft[1], bottomRight[0], bottomRight[1]]; + } + + /** + * Converts viewport coordinates to the PDF location. For examples, useful + * for converting canvas pixel location into PDF one. + * @param {number} x - The x-coordinate. + * @param {number} y - The y-coordinate. + * @returns {Array} Array containing `x`- and `y`-coordinates of the + * point in the PDF coordinate space. + * @see {@link convertToViewportPoint} + */ + convertToPdfPoint(x, y) { + const p = [x, y]; + Util.applyInverseTransform(p, this.transform); + return p; + } +} + +export { PageViewport }; diff --git a/src/display/text_layer.js b/src/display/text_layer.js index bfe0c0cb7..c5487bd07 100644 --- a/src/display/text_layer.js +++ b/src/display/text_layer.js @@ -13,7 +13,7 @@ * limitations under the License. */ -/** @typedef {import("./display_utils").PageViewport} PageViewport */ +/** @typedef {import("./page_viewport").PageViewport} PageViewport */ /** @typedef {import("./api").TextContent} TextContent */ /** @typedef {import("./text_layer_images").TextLayerImages} TextLayerImages */ diff --git a/src/display/xfa_layer.js b/src/display/xfa_layer.js index a23ab1c20..4d055cb42 100644 --- a/src/display/xfa_layer.js +++ b/src/display/xfa_layer.js @@ -15,7 +15,7 @@ // eslint-disable-next-line max-len /** @typedef {import("./annotation_storage").AnnotationStorage} AnnotationStorage */ -/** @typedef {import("./display_utils").PageViewport} PageViewport */ +/** @typedef {import("./page_viewport").PageViewport} PageViewport */ // eslint-disable-next-line max-len /** @typedef {import("../../web/pdf_link_service.js").PDFLinkService} PDFLinkService */ diff --git a/src/pdf.js b/src/pdf.js index 73a844159..8e86e7762 100644 --- a/src/pdf.js +++ b/src/pdf.js @@ -20,7 +20,7 @@ /** @typedef {import("./display/api").PDFDocumentProxy} PDFDocumentProxy */ /** @typedef {import("./display/api").PDFPageProxy} PDFPageProxy */ /** @typedef {import("./display/api").RenderTask} RenderTask */ -/** @typedef {import("./display/display_utils").PageViewport} PageViewport */ +/** @typedef {import("./display/page_viewport").PageViewport} PageViewport */ import { AbortException, diff --git a/test/unit/api_spec.js b/test/unit/api_spec.js index 9ca24d216..60419ed46 100644 --- a/test/unit/api_spec.js +++ b/test/unit/api_spec.js @@ -39,7 +39,6 @@ import { } from "./test_utils.js"; import { fetchData as fetchDataDOM, - PageViewport, RenderingCancelledException, StatTimer, } from "../../src/display/display_utils.js"; @@ -56,6 +55,7 @@ import { AutoPrintRegExp } from "../../web/ui_utils.js"; import { GlobalImageCache } from "../../src/core/image_utils.js"; import { GlobalWorkerOptions } from "../../src/display/worker_options.js"; import { Metadata } from "../../src/display/metadata.js"; +import { PageViewport } from "../../src/display/page_viewport.js"; const WORKER_SRC = "../../build/generic/build/pdf.worker.mjs"; diff --git a/web/annotation_editor_layer_builder.js b/web/annotation_editor_layer_builder.js index 456987a3d..e02a570c3 100644 --- a/web/annotation_editor_layer_builder.js +++ b/web/annotation_editor_layer_builder.js @@ -15,7 +15,7 @@ /** @typedef {import("../src/display/api").PDFPageProxy} PDFPageProxy */ // eslint-disable-next-line max-len -/** @typedef {import("../src/display/display_utils").PageViewport} PageViewport */ +/** @typedef {import("../src/display/page_viewport").PageViewport} PageViewport */ // eslint-disable-next-line max-len /** @typedef {import("../src/display/editor/tools.js").AnnotationEditorUIManager} AnnotationEditorUIManager */ // eslint-disable-next-line max-len diff --git a/web/annotation_layer_builder.js b/web/annotation_layer_builder.js index 5d4a443c4..8c48a2bdb 100644 --- a/web/annotation_layer_builder.js +++ b/web/annotation_layer_builder.js @@ -15,7 +15,7 @@ /** @typedef {import("../src/display/api").PDFPageProxy} PDFPageProxy */ // eslint-disable-next-line max-len -/** @typedef {import("../src/display/display_utils").PageViewport} PageViewport */ +/** @typedef {import("../src/display/page_viewport").PageViewport} PageViewport */ // eslint-disable-next-line max-len /** @typedef {import("../src/display/annotation_storage").AnnotationStorage} AnnotationStorage */ // eslint-disable-next-line max-len diff --git a/web/pdf_page_view.js b/web/pdf_page_view.js index 653c2489e..e04788bac 100644 --- a/web/pdf_page_view.js +++ b/web/pdf_page_view.js @@ -14,7 +14,7 @@ */ // eslint-disable-next-line max-len -/** @typedef {import("../src/display/display_utils").PageViewport} PageViewport */ +/** @typedef {import("../src/display/page_viewport").PageViewport} PageViewport */ // eslint-disable-next-line max-len /** @typedef {import("../src/display/optional_content_config").OptionalContentConfig} OptionalContentConfig */ /** @typedef {import("./event_utils").EventBus} EventBus */ diff --git a/web/pdf_thumbnail_view.js b/web/pdf_thumbnail_view.js index b9503795e..74d622927 100644 --- a/web/pdf_thumbnail_view.js +++ b/web/pdf_thumbnail_view.js @@ -16,7 +16,7 @@ // eslint-disable-next-line max-len /** @typedef {import("../src/display/optional_content_config").OptionalContentConfig} OptionalContentConfig */ // eslint-disable-next-line max-len -/** @typedef {import("../src/display/display_utils").PageViewport} PageViewport */ +/** @typedef {import("../src/display/page_viewport").PageViewport} PageViewport */ /** @typedef {import("./event_utils").EventBus} EventBus */ // eslint-disable-next-line max-len /** @typedef {import("./pdf_rendering_queue").PDFRenderingQueue} PDFRenderingQueue */ diff --git a/web/pdf_viewer.js b/web/pdf_viewer.js index 17295ed8c..1228a097f 100644 --- a/web/pdf_viewer.js +++ b/web/pdf_viewer.js @@ -16,7 +16,7 @@ /** @typedef {import("../src/display/api").PDFDocumentProxy} PDFDocumentProxy */ /** @typedef {import("../src/display/api").PDFPageProxy} PDFPageProxy */ // eslint-disable-next-line max-len -/** @typedef {import("../src/display/display_utils").PageViewport} PageViewport */ +/** @typedef {import("../src/display/page_viewport").PageViewport} PageViewport */ // eslint-disable-next-line max-len /** @typedef {import("../src/display/optional_content_config").OptionalContentConfig} OptionalContentConfig */ /** @typedef {import("./event_utils").EventBus} EventBus */ diff --git a/web/text_layer_builder.js b/web/text_layer_builder.js index a26b42856..53541763a 100644 --- a/web/text_layer_builder.js +++ b/web/text_layer_builder.js @@ -15,7 +15,7 @@ /** @typedef {import("../src/display/api").PDFPageProxy} PDFPageProxy */ // eslint-disable-next-line max-len -/** @typedef {import("../src/display/display_utils").PageViewport} PageViewport */ +/** @typedef {import("../src/display/page_viewport").PageViewport} PageViewport */ // eslint-disable-next-line max-len /** @typedef {import("../src/display/text_layer_images.js").TextLayerImages} TextLayerImages */ /** @typedef {import("./text_highlighter").TextHighlighter} TextHighlighter */ diff --git a/web/xfa_layer_builder.js b/web/xfa_layer_builder.js index 88e74fc20..8cae48ad3 100644 --- a/web/xfa_layer_builder.js +++ b/web/xfa_layer_builder.js @@ -17,7 +17,7 @@ // eslint-disable-next-line max-len /** @typedef {import("../src/display/annotation_storage").AnnotationStorage} AnnotationStorage */ // eslint-disable-next-line max-len -/** @typedef {import("../src/display/display_utils").PageViewport} PageViewport */ +/** @typedef {import("../src/display/page_viewport").PageViewport} PageViewport */ /** @typedef {import("./pdf_link_service.js").PDFLinkService} PDFLinkService */ import { XfaLayer } from "pdfjs-lib";