diff --git a/web/app.js b/web/app.js index 704ac1b95..451151b86 100644 --- a/web/app.js +++ b/web/app.js @@ -51,7 +51,6 @@ import { MissingPDFException, OPS, PDFWorker, - PermissionFlag, shadow, UnexpectedResponseException, UNSUPPORTED_FEATURES, @@ -82,7 +81,6 @@ import { ViewHistory } from "./view_history.js"; const DISABLE_AUTO_FETCH_LOADING_BAR_TIMEOUT = 5000; // ms const FORCE_PAGES_LOADED_TIMEOUT = 10000; // ms const WHEEL_ZOOM_DISABLED_TIMEOUT = 1000; // ms -const ENABLE_PERMISSIONS_CLASS = "enablePermissions"; const ViewOnLoad = { UNKNOWN: -1, @@ -530,6 +528,7 @@ const PDFViewerApplication = { enablePrintAutoRotate: AppOptions.get("enablePrintAutoRotate"), useOnlyCssZoom: AppOptions.get("useOnlyCssZoom"), maxCanvasPixels: AppOptions.get("maxCanvasPixels"), + enablePermissions: AppOptions.get("enablePermissions"), }); pdfRenderingQueue.setViewer(this.pdfViewer); pdfLinkService.setViewer(this.pdfViewer); @@ -841,7 +840,6 @@ const PDFViewerApplication = { this.pdfLinkService.setDocument(null); this.pdfDocumentProperties.setDocument(null); } - webViewerResetPermissions(); this.pdfLinkService.externalLinkEnabled = true; this._fellback = false; this.store = null; @@ -1326,10 +1324,6 @@ const PDFViewerApplication = { pdfViewer.focus(); } - // Currently only the "copy"-permission is supported, hence we delay - // the `getPermissions` API call until *after* rendering has started. - this._initializePermissions(pdfDocument); - // For documents with different page sizes, once all pages are // resolved, ensure that the correct location becomes visible on load. // (To reduce the risk, in very large and/or slow loading documents, @@ -1709,24 +1703,6 @@ const PDFViewerApplication = { } }, - /** - * @private - */ - async _initializePermissions(pdfDocument) { - const permissions = await pdfDocument.getPermissions(); - - if (pdfDocument !== this.pdfDocument) { - return; // The document was closed while the permissions resolved. - } - if (!permissions || !AppOptions.get("enablePermissions")) { - return; - } - // Currently only the "copy"-permission is supported. - if (!permissions.includes(PermissionFlag.COPY)) { - this.appConfig.viewerContainer.classList.add(ENABLE_PERMISSIONS_CLASS); - } - }, - /** * @private */ @@ -2335,15 +2311,6 @@ function webViewerOpenFileViaURL(file) { } } -function webViewerResetPermissions() { - const { appConfig } = PDFViewerApplication; - if (!appConfig) { - return; - } - // Currently only the "copy"-permission is supported. - appConfig.viewerContainer.classList.remove(ENABLE_PERMISSIONS_CLASS); -} - function webViewerPageRendered({ pageNumber, error }) { // If the page is still visible when it has finished rendering, // ensure that the page number input loading indicator is hidden. diff --git a/web/base_viewer.js b/web/base_viewer.js index 8fac5d0ce..6c6e381e3 100644 --- a/web/base_viewer.js +++ b/web/base_viewer.js @@ -16,6 +16,7 @@ import { AnnotationMode, createPromiseCapability, + PermissionFlag, PixelsPerInch, version, } from "pdfjs-lib"; @@ -53,6 +54,7 @@ import { TextLayerBuilder } from "./text_layer_builder.js"; import { XfaLayerBuilder } from "./xfa_layer_builder.js"; const DEFAULT_CACHE_SIZE = 10; +const ENABLE_PERMISSIONS_CLASS = "enablePermissions"; const PagesCountLimit = { FORCE_SCROLL_MODE_PAGE: 15000, @@ -95,6 +97,8 @@ const PagesCountLimit = { * total pixels, i.e. width * height. Use -1 for no limit. The default value * is 4096 * 4096 (16 mega-pixels). * @property {IL10n} l10n - Localization service. + * @property {boolean} [enablePermissions] - Enables PDF document permissions, + * when they exist. The default value is `false`. */ class PDFPageViewBuffer { @@ -171,6 +175,12 @@ class PDFPageViewBuffer { class BaseViewer { #buffer = null; + #annotationMode = AnnotationMode.ENABLE_FORMS; + + #previousAnnotationMode = null; + + #enablePermissions = false; + #previousContainerHeight = 0; #scrollModePageState = null; @@ -219,7 +229,7 @@ class BaseViewer { this._scriptingManager = options.scriptingManager || null; this.removePageBorders = options.removePageBorders || false; this.textLayerMode = options.textLayerMode ?? TextLayerMode.ENABLE; - this._annotationMode = + this.#annotationMode = options.annotationMode ?? AnnotationMode.ENABLE_FORMS; this.imageResourcesPath = options.imageResourcesPath || ""; this.enablePrintAutoRotate = options.enablePrintAutoRotate || false; @@ -227,6 +237,7 @@ class BaseViewer { this.useOnlyCssZoom = options.useOnlyCssZoom || false; this.maxCanvasPixels = options.maxCanvasPixels; this.l10n = options.l10n || NullL10n; + this.#enablePermissions = options.enablePermissions || false; this.defaultRenderingQueue = !options.renderingQueue; if (this.defaultRenderingQueue) { @@ -279,7 +290,7 @@ class BaseViewer { * @type {boolean} */ get renderForms() { - return this._annotationMode === AnnotationMode.ENABLE_FORMS; + return this.#annotationMode === AnnotationMode.ENABLE_FORMS; } /** @@ -473,9 +484,32 @@ class BaseViewer { } /** - * @private + * Currently only *some* permissions are supported. */ - _onePageRenderedOrForceFetch() { + #initializePermissions(permissions, pdfDocument) { + if (pdfDocument !== this.pdfDocument) { + return; // The document was closed while the permissions resolved. + } + if (!permissions || !this.#enablePermissions) { + return; + } + + if (!permissions.includes(PermissionFlag.COPY)) { + this.viewer.classList.add(ENABLE_PERMISSIONS_CLASS); + } + + if ( + !permissions.includes(PermissionFlag.MODIFY_ANNOTATIONS) && + !permissions.includes(PermissionFlag.FILL_INTERACTIVE_FORMS) + ) { + if (this.#annotationMode === AnnotationMode.ENABLE_FORMS) { + this.#previousAnnotationMode = this.#annotationMode; // Allow resetting. + this.#annotationMode = AnnotationMode.ENABLE; + } + } + } + + #onePageRenderedOrForceFetch() { // Unless the viewer *and* its pages are visible, rendering won't start and // `this._onePageRenderedCapability` thus won't be resolved. // To ensure that automatic printing, on document load, still works even in @@ -520,6 +554,7 @@ class BaseViewer { const firstPagePromise = pdfDocument.getPage(1); // Rendering (potentially) depends on this, hence fetching it immediately. const optionalContentConfigPromise = pdfDocument.getOptionalContentConfig(); + const permissionsPromise = pdfDocument.getPermissions(); // Given that browsers don't handle huge amounts of DOM-elements very well, // enforce usage of PAGE-scrolling when loading *very* long/large documents. @@ -564,10 +599,11 @@ class BaseViewer { // Fetch a single page so we can get a viewport that will be the default // viewport for all pages - firstPagePromise - .then(firstPdfPage => { + Promise.all([firstPagePromise, permissionsPromise]) + .then(([firstPdfPage, permissions]) => { this._firstPageCapability.resolve(firstPdfPage); this._optionalContentConfigPromise = optionalContentConfigPromise; + this.#initializePermissions(permissions, pdfDocument); const viewerElement = this._scrollMode === ScrollMode.PAGE ? null : this.viewer; @@ -580,7 +616,7 @@ class BaseViewer { ? this : null; const annotationLayerFactory = - this._annotationMode !== AnnotationMode.DISABLE ? this : null; + this.#annotationMode !== AnnotationMode.DISABLE ? this : null; const xfaLayerFactory = isPureXfa ? this : null; for (let pageNum = 1; pageNum <= pagesCount; ++pageNum) { @@ -595,7 +631,7 @@ class BaseViewer { textLayerFactory, textLayerMode: this.textLayerMode, annotationLayerFactory, - annotationMode: this._annotationMode, + annotationMode: this.#annotationMode, xfaLayerFactory, textHighlighterFactory: this, structTreeLayerFactory: this, @@ -626,7 +662,7 @@ class BaseViewer { // Fetch all the pages since the viewport is needed before printing // starts to create the correct size canvas. Wait until one page is // rendered so we don't tie up too many resources early on. - this._onePageRenderedOrForceFetch().then(async () => { + this.#onePageRenderedOrForceFetch().then(async () => { if (this.findController) { this.findController.setDocument(pdfDocument); // Enable searching. } @@ -750,6 +786,14 @@ class BaseViewer { this.viewer.textContent = ""; // ... and reset the Scroll mode CSS class(es) afterwards. this._updateScrollMode(); + + // Reset all PDF document permissions. + this.viewer.classList.remove(ENABLE_PERMISSIONS_CLASS); + + if (this.#previousAnnotationMode !== null) { + this.#annotationMode = this.#previousAnnotationMode; + this.#previousAnnotationMode = null; + } } #ensurePageViewVisible() { diff --git a/web/pdf_page_view.js b/web/pdf_page_view.js index ac669892b..c103451c8 100644 --- a/web/pdf_page_view.js +++ b/web/pdf_page_view.js @@ -76,6 +76,8 @@ const MAX_CANVAS_PIXELS = compatibilityParams.maxCanvasPixels || 16777216; * @implements {IRenderableView} */ class PDFPageView { + #annotationMode = AnnotationMode.ENABLE_FORMS; + /** * @param {PDFPageViewOptions} options */ @@ -96,7 +98,7 @@ class PDFPageView { options.optionalContentConfigPromise || null; this.hasRestrictedScaling = false; this.textLayerMode = options.textLayerMode ?? TextLayerMode.ENABLE; - this._annotationMode = + this.#annotationMode = options.annotationMode ?? AnnotationMode.ENABLE_FORMS; this.imageResourcesPath = options.imageResourcesPath || ""; this.useOnlyCssZoom = options.useOnlyCssZoom || false; @@ -597,7 +599,7 @@ class PDFPageView { this.textLayer = textLayer; if ( - this._annotationMode !== AnnotationMode.DISABLE && + this.#annotationMode !== AnnotationMode.DISABLE && this.annotationLayerFactory ) { this._annotationCanvasMap ||= new Map(); @@ -607,7 +609,7 @@ class PDFPageView { pdfPage, /* annotationStorage = */ null, this.imageResourcesPath, - this._annotationMode === AnnotationMode.ENABLE_FORMS, + this.#annotationMode === AnnotationMode.ENABLE_FORMS, this.l10n, /* enableScripting = */ null, /* hasJSActionsPromise = */ null, @@ -835,7 +837,7 @@ class PDFPageView { canvasContext: ctx, transform, viewport: this.viewport, - annotationMode: this._annotationMode, + annotationMode: this.#annotationMode, optionalContentConfigPromise: this._optionalContentConfigPromise, annotationCanvasMap: this._annotationCanvasMap, }; @@ -892,7 +894,7 @@ class PDFPageView { }); const promise = pdfPage .getOperatorList({ - annotationMode: this._annotationMode, + annotationMode: this.#annotationMode, }) .then(opList => { ensureNotCancelled(); diff --git a/web/pdf_viewer.css b/web/pdf_viewer.css index d98a17969..01c9cfa2d 100644 --- a/web/pdf_viewer.css +++ b/web/pdf_viewer.css @@ -141,6 +141,11 @@ background: none; } +.pdfViewer.enablePermissions .textLayer span { + user-select: none !important; + cursor: not-allowed; +} + .pdfPresentationMode .pdfViewer { padding-bottom: 0; } diff --git a/web/viewer.css b/web/viewer.css index a8810c528..0541efd3b 100644 --- a/web/viewer.css +++ b/web/viewer.css @@ -204,11 +204,6 @@ select { display: none !important; } -.pdfViewer.enablePermissions .textLayer span { - user-select: none !important; - cursor: not-allowed; -} - #viewerContainer.pdfPresentationMode:fullscreen { top: 0; background-color: rgba(0, 0, 0, 1);