diff --git a/src/lib/viewers/doc/DocBaseViewer.js b/src/lib/viewers/doc/DocBaseViewer.js index d2bb33b1f..3d20e9ce5 100644 --- a/src/lib/viewers/doc/DocBaseViewer.js +++ b/src/lib/viewers/doc/DocBaseViewer.js @@ -177,6 +177,7 @@ class DocBaseViewer extends BaseViewer { this.viewerEl = this.docEl.appendChild(document.createElement('div')); this.viewerEl.classList.add('pdfViewer'); + this.loadTimeout = LOAD_TIMEOUT_MS; this.startPageNum = this.getStartPage(this.startAt); @@ -795,7 +796,15 @@ class DocBaseViewer extends BaseViewer { const { AnnotationMode: PDFAnnotationMode = {} } = this.pdfjsLib; const assetUrlCreator = createAssetUrlCreator(location); const hasDownload = checkPermission(file, PERMISSION_DOWNLOAD); - const hasTextLayer = hasDownload && !this.getViewerOption('disableTextLayer'); + const hasTextLayer = !this.getViewerOption('disableTextLayer'); + const enabledTextLayerMode = hasDownload + ? PDFJS_TEXT_LAYER_MODE.ENABLE + : PDFJS_TEXT_LAYER_MODE.ENABLE_PERMISSIONS; // This mode will prevent default behavior for copy events in the TextLayerBuilder + + // Text layer should be rendered for a11y reasons thats why we will block user from selecting content when no download permissions was granted + if (!hasDownload) { + this.viewerEl.classList.add('pdfViewer--viewOnly'); + } return new PdfViewerClass({ annotationMode: PDFAnnotationMode.ENABLE, // Show annotations, but not forms @@ -806,7 +815,7 @@ class DocBaseViewer extends BaseViewer { linkService: this.pdfLinkService, maxCanvasPixels: this.isMobile ? MOBILE_MAX_CANVAS_SIZE : -1, renderInteractiveForms: false, // Enabling prevents unverified signatures from being displayed - textLayerMode: hasTextLayer ? PDFJS_TEXT_LAYER_MODE.ENABLE : PDFJS_TEXT_LAYER_MODE.DISABLE, + textLayerMode: hasTextLayer ? enabledTextLayerMode : PDFJS_TEXT_LAYER_MODE.DISABLE, }); } diff --git a/src/lib/viewers/doc/__tests__/DocBaseViewer-test.js b/src/lib/viewers/doc/__tests__/DocBaseViewer-test.js index 08b629028..815f18ac2 100644 --- a/src/lib/viewers/doc/__tests__/DocBaseViewer-test.js +++ b/src/lib/viewers/doc/__tests__/DocBaseViewer-test.js @@ -1140,43 +1140,65 @@ describe('src/lib/viewers/doc/DocBaseViewer', () => { }); }); - test('should enable the text layer based on download permissions', () => { + test('should enable the text layer if the user is on mobile', () => { + docBase.isMobile = true; stubs.checkPermission.mockReturnValueOnce(true); return docBase.initViewer('').then(() => { - expect(stubs.checkPermission).toBeCalledWith(docBase.options.file, PERMISSION_DOWNLOAD); + expect(stubs.getViewerOption).toBeCalledWith('disableTextLayer'); // Text Layer mode 1 = enabled expect(stubs.pdfViewerClass).toBeCalledWith(expect.objectContaining({ textLayerMode: 1 })); }); }); - test('should enable the text layer if the user is on mobile', () => { - docBase.isMobile = true; + test('should add view only class if user does not have download permissions', () => { + docBase.containerEl = containerEl; + stubs.checkPermission.mockReturnValueOnce(false); + + return docBase.initViewer('').then(() => { + expect(stubs.checkPermission).toBeCalledWith(docBase.options.file, PERMISSION_DOWNLOAD); + expect(docBase.viewerEl).toHaveClass('pdfViewer--viewOnly'); + }); + }); + + test('should not add view only class if user has download permissions', () => { + docBase.containerEl = containerEl; + stubs.checkPermission.mockReturnValueOnce(true); + + return docBase.initViewer('').then(() => { + expect(stubs.checkPermission).toBeCalledWith(docBase.options.file, PERMISSION_DOWNLOAD); + expect(docBase.viewerEl).not.toHaveClass('pdfViewer--viewOnly'); + }); + }); + + test('should use proper text layer mode if user has download permissions and disableTextLayer viewer option is not set', () => { + stubs.getViewerOption.mockReturnValue(false); stubs.checkPermission.mockReturnValueOnce(true); return docBase.initViewer('').then(() => { expect(stubs.checkPermission).toBeCalledWith(docBase.options.file, PERMISSION_DOWNLOAD); + expect(stubs.getViewerOption).toBeCalledWith('disableTextLayer'); // Text Layer mode 1 = enabled expect(stubs.pdfViewerClass).toBeCalledWith(expect.objectContaining({ textLayerMode: 1 })); }); }); - test('should disable the text layer based on download permissions', () => { + test('should use proper text layer mode if user does not have download permissions and disableTextLayer viewer option is not set', () => { + stubs.getViewerOption.mockReturnValue(false); stubs.checkPermission.mockReturnValueOnce(false); return docBase.initViewer('').then(() => { expect(stubs.checkPermission).toBeCalledWith(docBase.options.file, PERMISSION_DOWNLOAD); - // Text Layer mode 0 = disabled - expect(stubs.pdfViewerClass).toBeCalledWith(expect.objectContaining({ textLayerMode: 0 })); + expect(stubs.getViewerOption).toBeCalledWith('disableTextLayer'); + // Text Layer mode 2 = enabled permissions + expect(stubs.pdfViewerClass).toBeCalledWith(expect.objectContaining({ textLayerMode: 2 })); }); }); test('should disable the text layer if disableTextLayer viewer option is set', () => { - stubs.checkPermission.mockReturnValueOnce(true); stubs.getViewerOption.mockReturnValue(true); return docBase.initViewer('').then(() => { - expect(stubs.checkPermission).toBeCalledWith(docBase.options.file, PERMISSION_DOWNLOAD); expect(stubs.getViewerOption).toBeCalledWith('disableTextLayer'); // Text Layer mode 0 = disabled expect(stubs.pdfViewerClass).toBeCalledWith(expect.objectContaining({ textLayerMode: 0 })); diff --git a/src/lib/viewers/doc/_docBase.scss b/src/lib/viewers/doc/_docBase.scss index bb1d28c20..ddbc5d80d 100644 --- a/src/lib/viewers/doc/_docBase.scss +++ b/src/lib/viewers/doc/_docBase.scss @@ -304,6 +304,17 @@ $thumbnail-sidebar-width: 191px; // Extra pixel to account for sidebar border } } + .pdfViewer { + &.pdfViewer--viewOnly { + .textLayer { + span { + cursor: unset; + user-select: none; + } + } + } + } + .textLayer { caret-color: black; // Required for caret navigation on transparent text top: $pdfjs-page-padding;