Allow text-selection, but not copying, when enablePermissions is set (PR 16320 follow-up)

This commit is contained in:
Jonas Jenwald 2023-04-22 13:07:07 +02:00
parent 1b79b0cd21
commit 8a9d7a18cc
5 changed files with 46 additions and 33 deletions

View File

@ -122,6 +122,8 @@ class PDFPageView {
#renderingState = RenderingStates.INITIAL; #renderingState = RenderingStates.INITIAL;
#textLayerMode = TextLayerMode.ENABLE;
#useThumbnailCanvas = { #useThumbnailCanvas = {
initialOptionalContent: true, initialOptionalContent: true,
regularAnnotations: true, regularAnnotations: true,
@ -149,7 +151,7 @@ class PDFPageView {
this._optionalContentConfigPromise = this._optionalContentConfigPromise =
options.optionalContentConfigPromise || null; options.optionalContentConfigPromise || null;
this.hasRestrictedScaling = false; this.hasRestrictedScaling = false;
this.textLayerMode = options.textLayerMode ?? TextLayerMode.ENABLE; this.#textLayerMode = options.textLayerMode ?? TextLayerMode.ENABLE;
this.#annotationMode = this.#annotationMode =
options.annotationMode ?? AnnotationMode.ENABLE_FORMS; options.annotationMode ?? AnnotationMode.ENABLE_FORMS;
this.imageResourcesPath = options.imageResourcesPath || ""; this.imageResourcesPath = options.imageResourcesPath || "";
@ -798,7 +800,7 @@ class PDFPageView {
if ( if (
!this.textLayer && !this.textLayer &&
this.textLayerMode !== TextLayerMode.DISABLE && this.#textLayerMode !== TextLayerMode.DISABLE &&
!pdfPage.isPureXfa !pdfPage.isPureXfa
) { ) {
this._accessibilityManager ||= new TextAccessibilityManager(); this._accessibilityManager ||= new TextAccessibilityManager();
@ -807,6 +809,8 @@ class PDFPageView {
highlighter: this._textHighlighter, highlighter: this._textHighlighter,
accessibilityManager: this._accessibilityManager, accessibilityManager: this._accessibilityManager,
isOffscreenCanvasSupported: this.isOffscreenCanvasSupported, isOffscreenCanvasSupported: this.isOffscreenCanvasSupported,
enablePermissions:
this.#textLayerMode === TextLayerMode.ENABLE_PERMISSIONS,
}); });
div.append(this.textLayer.div); div.append(this.textLayer.div);
} }

View File

@ -185,11 +185,6 @@
display: none; display: none;
} }
.pdfViewer.enablePermissions .textLayer span {
user-select: none !important;
cursor: not-allowed;
}
.pdfPresentationMode .pdfViewer { .pdfPresentationMode .pdfViewer {
padding-bottom: 0; padding-bottom: 0;
} }

View File

@ -64,7 +64,6 @@ import { PDFRenderingQueue } from "./pdf_rendering_queue.js";
import { SimpleLinkService } from "./pdf_link_service.js"; import { SimpleLinkService } from "./pdf_link_service.js";
const DEFAULT_CACHE_SIZE = 10; const DEFAULT_CACHE_SIZE = 10;
const ENABLE_PERMISSIONS_CLASS = "enablePermissions";
const PagesCountLimit = { const PagesCountLimit = {
FORCE_SCROLL_MODE_PAGE: 15000, FORCE_SCROLL_MODE_PAGE: 15000,
@ -206,7 +205,7 @@ class PDFViewer {
#containerTopLeft = null; #containerTopLeft = null;
#copyCallbackBound = this.#copyCallback.bind(this); #copyCallbackBound = null;
#enablePermissions = false; #enablePermissions = false;
@ -226,6 +225,8 @@ class PDFViewer {
#scaleTimeoutId = null; #scaleTimeoutId = null;
#textLayerMode = TextLayerMode.ENABLE;
/** /**
* @param {PDFViewerOptions} options * @param {PDFViewerOptions} options
*/ */
@ -259,7 +260,7 @@ class PDFViewer {
this.downloadManager = options.downloadManager || null; this.downloadManager = options.downloadManager || null;
this.findController = options.findController || null; this.findController = options.findController || null;
this._scriptingManager = options.scriptingManager || null; this._scriptingManager = options.scriptingManager || null;
this.textLayerMode = options.textLayerMode ?? TextLayerMode.ENABLE; this.#textLayerMode = options.textLayerMode ?? TextLayerMode.ENABLE;
this.#annotationMode = this.#annotationMode =
options.annotationMode ?? AnnotationMode.ENABLE_FORMS; options.annotationMode ?? AnnotationMode.ENABLE_FORMS;
this.#annotationEditorMode = this.#annotationEditorMode =
@ -565,15 +566,6 @@ class PDFViewer {
}; };
} }
#createHiddenCopyElement() {
if (this.#hiddenCopyElement) {
return;
}
const element = (this.#hiddenCopyElement = document.createElement("div"));
element.id = "hiddenCopyElement";
this.viewer.before(element);
}
/** /**
* Currently only *some* permissions are supported. * Currently only *some* permissions are supported.
* @returns {Object} * @returns {Object}
@ -582,17 +574,17 @@ class PDFViewer {
const params = { const params = {
annotationEditorMode: this.#annotationEditorMode, annotationEditorMode: this.#annotationEditorMode,
annotationMode: this.#annotationMode, annotationMode: this.#annotationMode,
textLayerMode: this.textLayerMode, textLayerMode: this.#textLayerMode,
}; };
if (!permissions) { if (!permissions) {
this.#createHiddenCopyElement();
return params; return params;
} }
if (!permissions.includes(PermissionFlag.COPY)) { if (
this.viewer.classList.add(ENABLE_PERMISSIONS_CLASS); !permissions.includes(PermissionFlag.COPY) &&
} else { this.#textLayerMode === TextLayerMode.ENABLE
this.#createHiddenCopyElement(); ) {
params.textLayerMode = TextLayerMode.ENABLE_PERMISSIONS;
} }
if (!permissions.includes(PermissionFlag.MODIFY_CONTENTS)) { if (!permissions.includes(PermissionFlag.MODIFY_CONTENTS)) {
@ -683,7 +675,7 @@ class PDFViewer {
return texts.join("\n"); return texts.join("\n");
} }
#copyCallback(event) { #copyCallback(textLayerMode, event) {
const selection = document.getSelection(); const selection = document.getSelection();
const { focusNode, anchorNode } = selection; const { focusNode, anchorNode } = selection;
if ( if (
@ -699,6 +691,11 @@ class PDFViewer {
// including this element so having it in the selection means that all // including this element so having it in the selection means that all
// has been selected. // has been selected.
if (textLayerMode === TextLayerMode.ENABLE_PERMISSIONS) {
event.preventDefault();
event.stopPropagation();
return;
}
// TODO: if all the pages are rendered we don't need to wait for // TODO: if all the pages are rendered we don't need to wait for
// getAllText and we could just get text from the Selection object. // getAllText and we could just get text from the Selection object.
@ -831,6 +828,13 @@ class PDFViewer {
const { annotationEditorMode, annotationMode, textLayerMode } = const { annotationEditorMode, annotationMode, textLayerMode } =
this.#initializePermissions(permissions); this.#initializePermissions(permissions);
if (textLayerMode !== TextLayerMode.DISABLE) {
const element = (this.#hiddenCopyElement =
document.createElement("div"));
element.id = "hiddenCopyElement";
this.viewer.before(element);
}
if (annotationEditorMode !== AnnotationEditorType.DISABLE) { if (annotationEditorMode !== AnnotationEditorType.DISABLE) {
const mode = annotationEditorMode; const mode = annotationEditorMode;
@ -906,6 +910,10 @@ class PDFViewer {
this._scriptingManager?.setDocument(pdfDocument); // Enable scripting. this._scriptingManager?.setDocument(pdfDocument); // Enable scripting.
if (this.#hiddenCopyElement) { if (this.#hiddenCopyElement) {
this.#copyCallbackBound = this.#copyCallback.bind(
this,
textLayerMode
);
document.addEventListener("copy", this.#copyCallbackBound); document.addEventListener("copy", this.#copyCallbackBound);
} }
@ -1051,11 +1059,10 @@ class PDFViewer {
this._updateScrollMode(); this._updateScrollMode();
this.viewer.removeAttribute("lang"); this.viewer.removeAttribute("lang");
// Reset all PDF document permissions.
this.viewer.classList.remove(ENABLE_PERMISSIONS_CLASS);
if (this.#hiddenCopyElement) { if (this.#hiddenCopyElement) {
document.removeEventListener("copy", this.#copyCallbackBound); document.removeEventListener("copy", this.#copyCallbackBound);
this.#copyCallbackBound = null;
this.#hiddenCopyElement.remove(); this.#hiddenCopyElement.remove();
this.#hiddenCopyElement = null; this.#hiddenCopyElement = null;

View File

@ -38,6 +38,8 @@ import { removeNullCharacters } from "./ui_utils.js";
* contain text that matches the PDF text they are overlaying. * contain text that matches the PDF text they are overlaying.
*/ */
class TextLayerBuilder { class TextLayerBuilder {
#enablePermissions = false;
#rotation = 0; #rotation = 0;
#scale = 0; #scale = 0;
@ -48,6 +50,7 @@ class TextLayerBuilder {
highlighter = null, highlighter = null,
accessibilityManager = null, accessibilityManager = null,
isOffscreenCanvasSupported = true, isOffscreenCanvasSupported = true,
enablePermissions = false,
}) { }) {
this.textContentItemsStr = []; this.textContentItemsStr = [];
this.renderingDone = false; this.renderingDone = false;
@ -57,6 +60,7 @@ class TextLayerBuilder {
this.highlighter = highlighter; this.highlighter = highlighter;
this.accessibilityManager = accessibilityManager; this.accessibilityManager = accessibilityManager;
this.isOffscreenCanvasSupported = isOffscreenCanvasSupported; this.isOffscreenCanvasSupported = isOffscreenCanvasSupported;
this.#enablePermissions = enablePermissions === true;
this.div = document.createElement("div"); this.div = document.createElement("div");
this.div.className = "textLayer"; this.div.className = "textLayer";
@ -215,11 +219,13 @@ class TextLayerBuilder {
}); });
div.addEventListener("copy", event => { div.addEventListener("copy", event => {
const selection = document.getSelection(); if (!this.#enablePermissions) {
event.clipboardData.setData( const selection = document.getSelection();
"text/plain", event.clipboardData.setData(
removeNullCharacters(normalizeUnicode(selection.toString())) "text/plain",
); removeNullCharacters(normalizeUnicode(selection.toString()))
);
}
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
}); });

View File

@ -49,6 +49,7 @@ const SidebarView = {
const TextLayerMode = { const TextLayerMode = {
DISABLE: 0, DISABLE: 0,
ENABLE: 1, ENABLE: 1,
ENABLE_PERMISSIONS: 2,
}; };
const ScrollMode = { const ScrollMode = {