diff --git a/src/lib/constants.js b/src/lib/constants.js index 052f7f72a..6fa574e32 100644 --- a/src/lib/constants.js +++ b/src/lib/constants.js @@ -82,6 +82,11 @@ export const X_REP_HINT_IMAGE = '[jpg?dimensions=2048x2048,png?dimensions=2048x2 export const X_REP_HINT_VIDEO_DASH = '[dash,mp4][filmstrip]'; export const X_REP_HINT_VIDEO_MP4 = '[mp4]'; +export const PDFJS_CSS_UNITS = 96.0 / 72.0; // Should match CSS_UNITS in pdf_viewer.js +export const PDFJS_MAX_AUTO_SCALE = 1.25; // Should match MAX_AUTO_SCALE in pdf_viewer.js +export const PDFJS_WIDTH_PADDING_PX = 40; // Should match SCROLLBAR_PADDING in pdf_viewer.js +export const PDFJS_HEIGHT_PADDING_PX = 5; // Should match VERTICAL_PADDING in pdf_viewer.js + // These should be updated to match the Preview version in package.json whenever a file in that third party directory // is updated. Also, update the matching configuration in karma.conf.js, which is needed for tests export const DOC_STATIC_ASSETS_VERSION = '1.17.0'; diff --git a/src/lib/viewers/doc/DocPreloader.js b/src/lib/viewers/doc/DocPreloader.js index 5b908ed1f..d7a32572e 100644 --- a/src/lib/viewers/doc/DocPreloader.js +++ b/src/lib/viewers/doc/DocPreloader.js @@ -7,18 +7,17 @@ import { CLASS_INVISIBLE, CLASS_IS_TRANSPARENT, CLASS_PREVIEW_LOADED, - CLASS_SPINNER + CLASS_SPINNER, + PDFJS_CSS_UNITS, + PDFJS_MAX_AUTO_SCALE, + PDFJS_WIDTH_PADDING_PX, + PDFJS_HEIGHT_PADDING_PX } from '../../constants'; import { get, setDimensions } from '../../util'; const EXIF_COMMENT_TAG_NAME = 'UserComment'; // Read EXIF data from 'UserComment' tag const EXIF_COMMENT_REGEX = /pdfWidth:([0-9.]+)pts,pdfHeight:([0-9.]+)pts,numPages:([0-9]+)/; -const PDFJS_CSS_UNITS = 96.0 / 72.0; // Should match CSS_UNITS in pdf_viewer.js -const PDFJS_MAX_AUTO_SCALE = 1.25; // Should match MAX_AUTO_SCALE in pdf_viewer.js -const PDFJS_WIDTH_PADDING_PX = 40; // Should match SCROLLBAR_PADDING in pdf_viewer.js -const PDFJS_HEIGHT_PADDING_PX = 5; // Should match VERTICAL_PADDING in pdf_viewer.js - const NUM_PAGES_DEFAULT = 2; // Default to 2 pages for preload if true number of pages cannot be read const NUM_PAGES_MAX = 500; // Don't show more than 500 placeholder pages @@ -33,6 +32,9 @@ class DocPreloader extends EventEmitter { /** @property {HTMLElement} - Preload image element */ imageEl; + /** @property {HTMLElement} - Maximum auto-zoom scale */ + maxZoomScale = PDFJS_MAX_AUTO_SCALE; + /** @property {HTMLElement} - Preload overlay element */ overlayEl; @@ -59,7 +61,6 @@ class DocPreloader extends EventEmitter { */ constructor(previewUI) { super(); - this.previewUI = previewUI; this.wrapperClassName = CLASS_BOX_PREVIEW_PRELOAD_WRAPPER_DOCUMENT; } @@ -242,6 +243,33 @@ class DocPreloader extends EventEmitter { }); }; + /** + * Returns scaled PDF dimensions using same algorithm as pdf.js up to a maximum of 1.25x zoom. + * + * @private + * @param {number} pdfWidth - Width of PDF in pixels + * @param {number} pdfHeight - Height of PDF in pixels + * @return {Object} Scaled width and height in pixels + */ + getScaledDimensions(pdfWidth, pdfHeight) { + const { clientWidth, clientHeight } = this.wrapperEl; + const widthScale = (clientWidth - PDFJS_WIDTH_PADDING_PX) / pdfWidth; + const heightScale = (clientHeight - PDFJS_HEIGHT_PADDING_PX) / pdfHeight; + + const isLandscape = pdfWidth > pdfHeight; + let scale = isLandscape ? Math.min(heightScale, widthScale) : widthScale; + + // Optionally limit to maximum zoom scale if defined + if (this.maxZoomScale) { + scale = Math.min(this.maxZoomScale, scale); + } + + return { + scaledWidth: Math.floor(scale * pdfWidth), + scaledHeight: Math.floor(scale * pdfHeight) + }; + } + /** * Reads EXIF from preload JPG for PDF width, height, and numPages. This is currently encoded * by Box Conversion into the preload JPG itself, but eventually this information will be @@ -309,29 +337,6 @@ class DocPreloader extends EventEmitter { }); } - /** - * Returns scaled PDF dimensions using same algorithm as pdf.js up to a maximum of 1.25x zoom. - * - * @private - * @param {number} pdfWidth - Width of PDF in pixels - * @param {number} pdfHeight - Height of PDF in pixels - * @return {Object} Scaled width and height in pixels - */ - getScaledDimensions(pdfWidth, pdfHeight) { - const { clientWidth, clientHeight } = this.wrapperEl; - const widthScale = (clientWidth - PDFJS_WIDTH_PADDING_PX) / pdfWidth; - const heightScale = (clientHeight - PDFJS_HEIGHT_PADDING_PX) / pdfHeight; - const isLandscape = pdfWidth > pdfHeight; - - let scale = isLandscape ? Math.min(heightScale, widthScale) : widthScale; - scale = Math.min(PDFJS_MAX_AUTO_SCALE, scale); - - return { - scaledWidth: Math.floor(scale * pdfWidth), - scaledHeight: Math.floor(scale * pdfHeight) - }; - } - /** * Check if full document is already loaded - if so, hide the preload. * diff --git a/src/lib/viewers/doc/PresentationPreloader.js b/src/lib/viewers/doc/PresentationPreloader.js index 1569d05d2..7108ab490 100644 --- a/src/lib/viewers/doc/PresentationPreloader.js +++ b/src/lib/viewers/doc/PresentationPreloader.js @@ -3,6 +3,12 @@ import { CLASS_INVISIBLE, CLASS_BOX_PREVIEW_PRELOAD_WRAPPER_PRESENTATION } from import { setDimensions } from '../../util'; class PresentationPreloader extends DocPreloader { + /** + * @property {HTMLELement} - Maximum auto-zoom scale, set to 0 for no limit since presentation viewer doesn't + * have a maximum zoom scale and scales up to available viewport + */ + maxZoomScale = 0; + /** @property {HTMLElement} - Preload container element */ preloadEl; @@ -20,6 +26,7 @@ class PresentationPreloader extends DocPreloader { */ constructor(previewUI) { super(previewUI); + this.wrapperClassName = CLASS_BOX_PREVIEW_PRELOAD_WRAPPER_PRESENTATION; } diff --git a/src/lib/viewers/doc/__tests__/DocPreloader-test.js b/src/lib/viewers/doc/__tests__/DocPreloader-test.js index 46c7b54c2..d1d27225a 100644 --- a/src/lib/viewers/doc/__tests__/DocPreloader-test.js +++ b/src/lib/viewers/doc/__tests__/DocPreloader-test.js @@ -66,6 +66,53 @@ describe('lib/viewers/doc/DocPreloader', () => { }); }); + describe('scaleAndShowPreload()', () => { + beforeEach(() => { + stubs.checkDocumentLoaded = sandbox.stub(docPreloader, 'checkDocumentLoaded'); + stubs.emit = sandbox.stub(docPreloader, 'emit'); + stubs.setDimensions = sandbox.stub(util, 'setDimensions'); + stubs.hideLoadingIndicator = sandbox.stub(docPreloader.previewUI, 'hideLoadingIndicator'); + docPreloader.imageEl = {}; + docPreloader.preloadEl = document.createElement('div'); + }); + + it('should not do anything if document is loaded', () => { + stubs.checkDocumentLoaded.returns(true); + + docPreloader.scaleAndShowPreload(1, 1, 1); + + expect(stubs.setDimensions).to.not.be.called; + expect(stubs.hideLoadingIndicator).to.not.be.called; + }); + + it('should set preload image dimensions, hide loading indicator, show preload element, and emit preload event', () => { + docPreloader.preloadEl.classList.add(CLASS_INVISIBLE); + + const width = 100; + const height = 100; + + docPreloader.scaleAndShowPreload(width, height, 1); + + expect(stubs.setDimensions).to.be.calledWith(docPreloader.imageEl, width, height); + expect(stubs.setDimensions).to.be.calledWith(docPreloader.overlayEl, width, height); + expect(stubs.hideLoadingIndicator).to.be.called; + expect(stubs.emit).to.be.calledWith('preload'); + expect(docPreloader.preloadEl).to.not.have.class(CLASS_INVISIBLE); + }); + + [5, 10, 11, 100].forEach((numPages) => { + it('should create and set dimensions for numPages - 1 placeholders', () => { + docPreloader.scaleAndShowPreload(100, 100, numPages); + + // Should scale 1 preload image, one overlay, and numPages - 1 placeholders + expect(stubs.setDimensions).to.have.callCount(numPages + 1); + + // Should have numPages - 1 placeholder elements + expect(docPreloader.preloadEl).to.have.length(numPages - 1); + }); + }); + }); + describe('hidePreload()', () => { beforeEach(() => { stubs.restoreScrollPosition = sandbox.stub(docPreloader, 'restoreScrollPosition'); @@ -131,53 +178,6 @@ describe('lib/viewers/doc/DocPreloader', () => { }); }); - describe('scaleAndShowPreload()', () => { - beforeEach(() => { - stubs.checkDocumentLoaded = sandbox.stub(docPreloader, 'checkDocumentLoaded'); - stubs.emit = sandbox.stub(docPreloader, 'emit'); - stubs.setDimensions = sandbox.stub(util, 'setDimensions'); - stubs.hideLoadingIndicator = sandbox.stub(docPreloader.previewUI, 'hideLoadingIndicator'); - docPreloader.imageEl = {}; - docPreloader.preloadEl = document.createElement('div'); - }); - - it('should not do anything if document is loaded', () => { - stubs.checkDocumentLoaded.returns(true); - - docPreloader.scaleAndShowPreload(1, 1, 1); - - expect(stubs.setDimensions).to.not.be.called; - expect(stubs.hideLoadingIndicator).to.not.be.called; - }); - - it('should set preload image dimensions, hide loading indicator, show preload element, and emit preload event', () => { - docPreloader.preloadEl.classList.add(CLASS_INVISIBLE); - - const width = 100; - const height = 100; - - docPreloader.scaleAndShowPreload(width, height, 1); - - expect(stubs.setDimensions).to.be.calledWith(docPreloader.imageEl, width, height); - expect(stubs.setDimensions).to.be.calledWith(docPreloader.overlayEl, width, height); - expect(stubs.hideLoadingIndicator).to.be.called; - expect(stubs.emit).to.be.calledWith('preload'); - expect(docPreloader.preloadEl).to.not.have.class(CLASS_INVISIBLE); - }); - - [5, 10, 11, 100].forEach((numPages) => { - it('should create and set dimensions for numPages - 1 placeholders', () => { - docPreloader.scaleAndShowPreload(100, 100, numPages); - - // Should scale 1 preload image, one overlay, and numPages - 1 placeholders - expect(stubs.setDimensions).to.have.callCount(numPages + 1); - - // Should have numPages - 1 placeholder elements - expect(docPreloader.preloadEl).to.have.length(numPages - 1); - }); - }); - }); - describe('bindDOMListeners()', () => { it('should bind load event listener to image element', () => { docPreloader.imageEl = { @@ -328,6 +328,82 @@ describe('lib/viewers/doc/DocPreloader', () => { }); }); + describe('getScaledDimensions()', () => { + beforeEach(() => { + docPreloader.wrapperEl = document.createElement('div'); + containerEl.appendChild(docPreloader.wrapperEl); + }); + + it('should scale up to a max defined by maxZoomScale', () => { + const clientWidth = 500; + const clientHeight = 500; + docPreloader.wrapperEl.style.width = `${clientWidth}px`; + docPreloader.wrapperEl.style.height = `${clientHeight}px`; + const expectedScale = 1.25; + docPreloader.maxZoomScale = expectedScale; + + const scaledDimensions = docPreloader.getScaledDimensions(100, 100); + expect(scaledDimensions).to.deep.equal({ + scaledWidth: Math.floor(expectedScale * 100), + scaledHeight: Math.floor(expectedScale * 100) + }); + }); + + it('should scale with height scale if in landscape and height scale is less than width scale', () => { + const clientWidth = 1000; + const clientHeight = 500; + docPreloader.wrapperEl.style.width = `${clientWidth}px`; + docPreloader.wrapperEl.style.height = `${clientHeight}px`; + + const pdfWidth = 1000; + const pdfHeight = 600; + const scaledDimensions = docPreloader.getScaledDimensions(pdfWidth, pdfHeight); + + // Expect height scale to be used + const expectedScale = (clientHeight - 5) / pdfHeight; + expect(scaledDimensions).to.deep.equal({ + scaledWidth: Math.floor(expectedScale * pdfWidth), + scaledHeight: Math.floor(expectedScale * pdfHeight) + }); + }); + + it('should scale with width scale if in landscape and width scale is less than height scale', () => { + const clientWidth = 1000; + const clientHeight = 500; + docPreloader.wrapperEl.style.width = `${clientWidth}px`; + docPreloader.wrapperEl.style.height = `${clientHeight}px`; + + const pdfWidth = 1000; + const pdfHeight = 500; + const scaledDimensions = docPreloader.getScaledDimensions(pdfWidth, pdfHeight); + + // Expect width scale to be used + const expectedScale = (clientWidth - 40) / pdfWidth; + expect(scaledDimensions).to.deep.equal({ + scaledWidth: Math.floor(expectedScale * pdfWidth), + scaledHeight: Math.floor(expectedScale * pdfHeight) + }); + }); + + it('should scale with width scale if not in landscape', () => { + const clientWidth = 600; + const clientHeight = 1100; + docPreloader.wrapperEl.style.width = `${clientWidth}px`; + docPreloader.wrapperEl.style.height = `${clientHeight}px`; + + const pdfWidth = 500; + const pdfHeight = 1000; + const scaledDimensions = docPreloader.getScaledDimensions(pdfWidth, pdfHeight); + + // Expect width scale to be used + const expectedScale = (clientWidth - 40) / pdfWidth; + expect(scaledDimensions).to.deep.equal({ + scaledWidth: Math.floor(expectedScale * pdfWidth), + scaledHeight: Math.floor(expectedScale * pdfHeight) + }); + }); + }); + describe('readEXIF()', () => { let fakeImageEl; @@ -458,83 +534,6 @@ describe('lib/viewers/doc/DocPreloader', () => { }); }); - describe('getScaledDimensions()', () => { - beforeEach(() => { - docPreloader.wrapperEl = document.createElement('div'); - containerEl.appendChild(docPreloader.wrapperEl); - }); - - it('should scale up to a max of 1.25', () => { - const clientWidth = 500; - const clientHeight = 500; - docPreloader.wrapperEl.style.width = `${clientWidth}px`; - docPreloader.wrapperEl.style.height = `${clientHeight}px`; - - const scaledDimensions = docPreloader.getScaledDimensions(100, 100); - - // Expect max scale of 1.25 - const expectedScale = 1.25; - expect(scaledDimensions).to.deep.equal({ - scaledWidth: Math.floor(expectedScale * 100), - scaledHeight: Math.floor(expectedScale * 100) - }); - }); - - it('should scale with height scale if in landscape and height scale is less than width scale', () => { - const clientWidth = 1000; - const clientHeight = 500; - docPreloader.wrapperEl.style.width = `${clientWidth}px`; - docPreloader.wrapperEl.style.height = `${clientHeight}px`; - - const pdfWidth = 1000; - const pdfHeight = 600; - const scaledDimensions = docPreloader.getScaledDimensions(pdfWidth, pdfHeight); - - // Expect height scale to be used - const expectedScale = (clientHeight - 5) / pdfHeight; - expect(scaledDimensions).to.deep.equal({ - scaledWidth: Math.floor(expectedScale * pdfWidth), - scaledHeight: Math.floor(expectedScale * pdfHeight) - }); - }); - - it('should scale with width scale if in landscape and width scale is less than height scale', () => { - const clientWidth = 1000; - const clientHeight = 500; - docPreloader.wrapperEl.style.width = `${clientWidth}px`; - docPreloader.wrapperEl.style.height = `${clientHeight}px`; - - const pdfWidth = 1000; - const pdfHeight = 500; - const scaledDimensions = docPreloader.getScaledDimensions(pdfWidth, pdfHeight); - - // Expect width scale to be used - const expectedScale = (clientWidth - 40) / pdfWidth; - expect(scaledDimensions).to.deep.equal({ - scaledWidth: Math.floor(expectedScale * pdfWidth), - scaledHeight: Math.floor(expectedScale * pdfHeight) - }); - }); - - it('should scale with width scale if not in landscape', () => { - const clientWidth = 600; - const clientHeight = 1100; - docPreloader.wrapperEl.style.width = `${clientWidth}px`; - docPreloader.wrapperEl.style.height = `${clientHeight}px`; - - const pdfWidth = 500; - const pdfHeight = 1000; - const scaledDimensions = docPreloader.getScaledDimensions(pdfWidth, pdfHeight); - - // Expect width scale to be used - const expectedScale = (clientWidth - 40) / pdfWidth; - expect(scaledDimensions).to.deep.equal({ - scaledWidth: Math.floor(expectedScale * pdfWidth), - scaledHeight: Math.floor(expectedScale * pdfHeight) - }); - }); - }); - describe('checkDocumentLoaded()', () => { beforeEach(() => { docPreloader.containerEl = document.createElement('div');