1
0
Fork 0
mirror of https://github.com/mozilla/pdf.js.git synced 2025-04-20 15:18:08 +02:00

[Editor] Allow the user to add and save an alt-text for images (bug 1844952)

This commit is contained in:
Calixte Denizet 2023-09-19 00:03:49 +02:00
parent daae6589b6
commit c12049db07
8 changed files with 316 additions and 8 deletions

190
web/alt_text_manager.js Normal file
View file

@ -0,0 +1,190 @@
/* Copyright 2023 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.
*/
class AltTextManager {
#boundUpdateUIState = this.#updateUIState.bind(this);
#boundSetPosition = this.#setPosition.bind(this);
#currentEditor = null;
#dialog;
#eventBus = null;
#optionDescription;
#optionDecorative;
#overlayManager;
#saveButton;
#textarea;
#uiManager;
constructor(
{
dialog,
optionDescription,
optionDecorative,
textarea,
cancelButton,
saveButton,
},
overlayManager,
eventBus
) {
this.#dialog = dialog;
this.#optionDescription = optionDescription;
this.#optionDecorative = optionDecorative;
this.#textarea = textarea;
this.#saveButton = saveButton;
this.#overlayManager = overlayManager;
this.#eventBus = eventBus;
dialog.addEventListener("close", this.#close.bind(this));
cancelButton.addEventListener("click", this.#finish.bind(this));
saveButton.addEventListener("click", this.#save.bind(this));
optionDescription.addEventListener("change", this.#boundUpdateUIState);
optionDecorative.addEventListener("change", this.#boundUpdateUIState);
textarea.addEventListener("input", this.#boundUpdateUIState);
this.#overlayManager.register(dialog);
}
async editAltText(uiManager, editor) {
if (this.#currentEditor || !editor) {
return;
}
const { altText, decorative } = editor.altTextData;
if (decorative === true) {
this.#optionDecorative.checked = true;
this.#optionDescription.checked = false;
} else {
this.#optionDecorative.checked = false;
this.#optionDescription.checked = true;
}
this.#textarea.value = altText?.trim() || "";
this.#updateUIState();
this.#currentEditor = editor;
this.#uiManager = uiManager;
this.#uiManager.removeKeyboardManager();
this.#eventBus._on("resize", this.#boundSetPosition);
try {
await this.#overlayManager.open(this.#dialog);
this.#setPosition();
} catch (ex) {
this.#close();
throw ex;
}
}
#setPosition() {
if (!this.#currentEditor) {
return;
}
const dialog = this.#dialog;
const { style } = dialog;
const { innerWidth: windowW, innerHeight: windowH } = window;
const { width: dialogW, height: dialogH } = dialog.getBoundingClientRect();
const { x, y, width, height } = this.#currentEditor.getClientDimensions();
const MARGIN = 10;
const isLTR = this.#uiManager.direction === "ltr";
let left = null;
let top = Math.max(0, y - MARGIN);
top += Math.min(windowH - (top + dialogH), 0);
if (isLTR) {
// Prefer to position the dialog "after" (so on the right) the editor.
if (x + width + MARGIN + dialogW < windowW) {
left = x + width + MARGIN;
} else if (x > dialogW + MARGIN) {
left = x - dialogW - MARGIN;
}
} else if (x > dialogW + MARGIN) {
left = x - dialogW - MARGIN;
} else if (x + width + MARGIN + dialogW < windowW) {
left = x + width + MARGIN;
}
if (left === null) {
top = null;
left = Math.max(0, x - MARGIN);
left += Math.min(windowW - (left + dialogW), 0);
if (y > dialogH + MARGIN) {
top = y - dialogH - MARGIN;
} else if (y + height + MARGIN + dialogH < windowH) {
top = y + height + MARGIN;
}
}
if (top !== null) {
dialog.classList.add("positioned");
if (isLTR) {
style.left = `${left}px`;
} else {
style.right = `${windowW - left - dialogW}px`;
}
style.top = `${top}px`;
} else {
dialog.classList.remove("positioned");
style.left = "";
style.top = "";
}
}
#finish() {
if (this.#dialog) {
this.#overlayManager.close(this.#dialog);
}
}
#close() {
this.#uiManager?.addKeyboardManager();
this.#eventBus._off("resize", this.#boundSetPosition);
this.#currentEditor = null;
this.#uiManager = null;
}
#updateUIState() {
const hasAltText = !!this.#textarea.value.trim();
const decorative = this.#optionDecorative.checked;
this.#textarea.disabled = decorative;
this.#saveButton.disabled = !decorative && !hasAltText;
}
#save() {
this.#currentEditor.altTextData = {
altText: this.#textarea.value.trim(),
decorative: this.#optionDecorative.checked,
};
this.#finish();
}
destroy() {
this.#currentEditor = null;
this.#uiManager = null;
this.#finish();
}
}
export { AltTextManager };

View file

@ -528,6 +528,15 @@
&.done::before {
mask-image: var(--alt-text-done-image);
}
& .description {
position: absolute;
top: 0;
left: 0;
display: none;
width: 0;
height: 0;
}
}
#altTextDialog {
@ -622,6 +631,10 @@
color: var(--text-primary-color);
box-shadow: var(--dialog-shadow);
&.positioned {
margin: 0;
}
& #altTextContainer {
width: 300px;
height: fit-content;
@ -728,6 +741,7 @@
}
&:disabled {
pointer-events: none;
opacity: 0.4;
}
}
}

View file

@ -55,6 +55,7 @@ import {
import { AppOptions, OptionKind } from "./app_options.js";
import { AutomationEventBus, EventBus } from "./event_utils.js";
import { LinkTarget, PDFLinkService } from "./pdf_link_service.js";
import { AltTextManager } from "./alt_text_manager.js";
import { AnnotationEditorParams } from "web-annotation_editor_params";
import { OverlayManager } from "./overlay_manager.js";
import { PasswordPrompt } from "./password_prompt.js";
@ -505,6 +506,13 @@ const PDFViewerApplication = {
foreground: AppOptions.get("pageColorsForeground"),
}
: null;
const altTextManager = appConfig.altTextDialog
? new AltTextManager(
appConfig.altTextDialog,
this.overlayManager,
eventBus
)
: null;
const pdfViewer = new PDFViewer({
container,
@ -513,6 +521,7 @@ const PDFViewerApplication = {
renderingQueue: pdfRenderingQueue,
linkService: pdfLinkService,
downloadManager,
altTextManager,
findController,
scriptingManager:
AppOptions.get("enableScripting") && pdfScriptingManager,

View file

@ -199,6 +199,8 @@ class PDFPageViewBuffer {
class PDFViewer {
#buffer = null;
#altTextManager = null;
#annotationEditorMode = AnnotationEditorType.NONE;
#annotationEditorUIManager = null;
@ -261,6 +263,7 @@ class PDFViewer {
this.linkService = options.linkService || new SimpleLinkService();
this.downloadManager = options.downloadManager || null;
this.findController = options.findController || null;
this.#altTextManager = options.altTextManager || null;
if (this.findController) {
this.findController.onIsPageVisible = pageNumber =>
@ -854,6 +857,7 @@ class PDFViewer {
this.#annotationEditorUIManager = new AnnotationEditorUIManager(
this.container,
this.viewer,
this.#altTextManager,
this.eventBus,
pdfDocument,
this.pageColors

View file

@ -157,6 +157,14 @@ function getViewerConfiguration() {
linearized: document.getElementById("linearizedField"),
},
},
altTextDialog: {
dialog: document.getElementById("altTextDialog"),
optionDescription: document.getElementById("descriptionButton"),
optionDecorative: document.getElementById("decorativeButton"),
textarea: document.getElementById("descriptionTextarea"),
cancelButton: document.getElementById("altTextCancel"),
saveButton: document.getElementById("altTextSave"),
},
annotationEditorParams: {
editorFreeTextFontSize: document.getElementById("editorFreeTextFontSize"),
editorFreeTextColor: document.getElementById("editorFreeTextColor"),