mirror of
https://github.com/mozilla/pdf.js.git
synced 2025-04-20 15:18:08 +02:00
[Editor] Add a new dialog for alt-text settings (bug 1909604)
This patch adds a new entry in the secondary menu in order to open a dialog to let the user: - disables the alt-text generation thanks to a ML model; - deletes the alt-text model downloaded in Firefox; - disabled the new alt-text flow.
This commit is contained in:
parent
d562e0525d
commit
32d09276f0
17 changed files with 627 additions and 54 deletions
|
@ -1349,3 +1349,63 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#altTextSettingsDialog {
|
||||
padding: 16px;
|
||||
|
||||
#altTextSettingsContainer {
|
||||
display: flex;
|
||||
width: 573px;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
|
||||
.mainContainer {
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.description {
|
||||
color: var(--text-secondary-color);
|
||||
}
|
||||
|
||||
#aiModelSettings {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
|
||||
button {
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
&.download {
|
||||
#deleteModelButton {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.download) {
|
||||
#downloadModelButton {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#automaticAltText,
|
||||
#altTextEditor {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
#createModelDescription,
|
||||
#aiModelSettings,
|
||||
#showAltTextDialogDescription {
|
||||
padding-inline-start: 40px;
|
||||
}
|
||||
|
||||
#automaticSettings {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
63
web/app.js
63
web/app.js
|
@ -59,12 +59,15 @@ import {
|
|||
import { AppOptions, OptionKind } from "./app_options.js";
|
||||
import { EventBus, FirefoxEventBus } from "./event_utils.js";
|
||||
import { ExternalServices, initCom, MLManager } from "web-external_services";
|
||||
import {
|
||||
ImageAltTextSettings,
|
||||
NewAltTextManager,
|
||||
} from "web-new_alt_text_manager";
|
||||
import { LinkTarget, PDFLinkService } from "./pdf_link_service.js";
|
||||
import { AltTextManager } from "web-alt_text_manager";
|
||||
import { AnnotationEditorParams } from "web-annotation_editor_params";
|
||||
import { CaretBrowsingMode } from "./caret_browsing.js";
|
||||
import { DownloadManager } from "web-download_manager";
|
||||
import { NewAltTextManager } from "web-new_alt_text_manager";
|
||||
import { OverlayManager } from "./overlay_manager.js";
|
||||
import { PasswordPrompt } from "./password_prompt.js";
|
||||
import { PDFAttachmentViewer } from "web-pdf_attachment_viewer";
|
||||
|
@ -151,6 +154,8 @@ const PDFViewerApplication = {
|
|||
l10n: null,
|
||||
/** @type {AnnotationEditorParams} */
|
||||
annotationEditorParams: null,
|
||||
/** @type {ImageAltTextSettings} */
|
||||
imageAltTextSettings: null,
|
||||
isInitialViewSet: false,
|
||||
isViewerEmbedded: window.parent !== window,
|
||||
url: "",
|
||||
|
@ -211,6 +216,9 @@ const PDFViewerApplication = {
|
|||
this.mlManager =
|
||||
MLManager.getFakeMLManager?.({
|
||||
enableGuessAltText: AppOptions.get("enableGuessAltText"),
|
||||
enableAltTextModelDownload: AppOptions.get(
|
||||
"enableAltTextModelDownload"
|
||||
),
|
||||
}) || null;
|
||||
}
|
||||
}
|
||||
|
@ -218,6 +226,9 @@ const PDFViewerApplication = {
|
|||
// We want to load the image-to-text AI engine as soon as possible.
|
||||
this.mlManager = new MLManager({
|
||||
enableGuessAltText: AppOptions.get("enableGuessAltText"),
|
||||
enableAltTextModelDownload: AppOptions.get(
|
||||
"enableAltTextModelDownload"
|
||||
),
|
||||
altTextLearnMoreUrl: AppOptions.get("altTextLearnMoreUrl"),
|
||||
});
|
||||
}
|
||||
|
@ -390,12 +401,12 @@ const PDFViewerApplication = {
|
|||
externalServices,
|
||||
AppOptions.get("isInAutomation")
|
||||
);
|
||||
if (this.mlManager) {
|
||||
this.mlManager.eventBus = eventBus;
|
||||
}
|
||||
} else {
|
||||
eventBus = new EventBus();
|
||||
}
|
||||
if (this.mlManager) {
|
||||
this.mlManager.eventBus = eventBus;
|
||||
}
|
||||
this.eventBus = eventBus;
|
||||
|
||||
this.overlayManager = new OverlayManager();
|
||||
|
@ -445,7 +456,11 @@ const PDFViewerApplication = {
|
|||
let altTextManager;
|
||||
if (AppOptions.get("enableUpdatedAddImage")) {
|
||||
altTextManager = appConfig.newAltTextDialog
|
||||
? new NewAltTextManager(appConfig.newAltTextDialog, this.overlayManager)
|
||||
? new NewAltTextManager(
|
||||
appConfig.newAltTextDialog,
|
||||
this.overlayManager,
|
||||
eventBus
|
||||
)
|
||||
: null;
|
||||
} else {
|
||||
altTextManager = appConfig.altTextDialog
|
||||
|
@ -479,6 +494,9 @@ const PDFViewerApplication = {
|
|||
"enableHighlightFloatingButton"
|
||||
),
|
||||
enableUpdatedAddImage: AppOptions.get("enableUpdatedAddImage"),
|
||||
enableNewAltTextWhenAddingImage: AppOptions.get(
|
||||
"enableNewAltTextWhenAddingImage"
|
||||
),
|
||||
imageResourcesPath: AppOptions.get("imageResourcesPath"),
|
||||
enablePrintAutoRotate: AppOptions.get("enablePrintAutoRotate"),
|
||||
maxCanvasPixels: AppOptions.get("maxCanvasPixels"),
|
||||
|
@ -539,6 +557,15 @@ const PDFViewerApplication = {
|
|||
}
|
||||
}
|
||||
|
||||
if (appConfig.secondaryToolbar?.imageAltTextSettingsButton) {
|
||||
this.imageAltTextSettings = new ImageAltTextSettings(
|
||||
appConfig.altTextSettingsDialog,
|
||||
this.overlayManager,
|
||||
eventBus,
|
||||
this.mlManager
|
||||
);
|
||||
}
|
||||
|
||||
if (appConfig.documentProperties) {
|
||||
this.pdfDocumentProperties = new PDFDocumentProperties(
|
||||
appConfig.documentProperties,
|
||||
|
@ -579,6 +606,15 @@ const PDFViewerApplication = {
|
|||
}
|
||||
|
||||
if (appConfig.secondaryToolbar) {
|
||||
if (AppOptions.get("enableAltText")) {
|
||||
appConfig.secondaryToolbar.imageAltTextSettingsButton?.classList.remove(
|
||||
"hidden"
|
||||
);
|
||||
appConfig.secondaryToolbar.imageAltTextSettingsSeparator?.classList.remove(
|
||||
"hidden"
|
||||
);
|
||||
}
|
||||
|
||||
this.secondaryToolbar = new SecondaryToolbar(
|
||||
appConfig.secondaryToolbar,
|
||||
eventBus
|
||||
|
@ -1914,6 +1950,9 @@ const PDFViewerApplication = {
|
|||
eventBus._on("scrollmodechanged", webViewerScrollModeChanged, { signal });
|
||||
eventBus._on("switchspreadmode", webViewerSwitchSpreadMode, { signal });
|
||||
eventBus._on("spreadmodechanged", webViewerSpreadModeChanged, { signal });
|
||||
eventBus._on("imagealttextsettings", webViewerImageAltTextSettings, {
|
||||
signal,
|
||||
});
|
||||
eventBus._on("documentproperties", webViewerDocumentProperties, { signal });
|
||||
eventBus._on("findfromurlhash", webViewerFindFromUrlHash, { signal });
|
||||
eventBus._on("updatefindmatchescount", webViewerUpdateFindMatchesCount, {
|
||||
|
@ -1934,6 +1973,11 @@ const PDFViewerApplication = {
|
|||
{ signal }
|
||||
);
|
||||
eventBus._on("reporttelemetry", webViewerReportTelemetry, { signal });
|
||||
}
|
||||
if (
|
||||
typeof PDFJSDev === "undefined" ||
|
||||
PDFJSDev.test("TESTING || MOZCENTRAL")
|
||||
) {
|
||||
eventBus._on("setpreference", webViewerSetPreference, { signal });
|
||||
}
|
||||
},
|
||||
|
@ -2470,6 +2514,15 @@ function webViewerDocumentProperties() {
|
|||
PDFViewerApplication.pdfDocumentProperties?.open();
|
||||
}
|
||||
|
||||
function webViewerImageAltTextSettings() {
|
||||
PDFViewerApplication.imageAltTextSettings?.open({
|
||||
enableGuessAltText: AppOptions.get("enableGuessAltText"),
|
||||
enableNewAltTextWhenAddingImage: AppOptions.get(
|
||||
"enableNewAltTextWhenAddingImage"
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
function webViewerFindFromUrlHash(evt) {
|
||||
PDFViewerApplication.eventBus.dispatch("find", {
|
||||
source: evt.source,
|
||||
|
|
|
@ -190,6 +190,11 @@ const defaultOptions = {
|
|||
value: false,
|
||||
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
|
||||
},
|
||||
enableAltTextModelDownload: {
|
||||
/** @type {boolean} */
|
||||
value: true,
|
||||
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
|
||||
},
|
||||
enableGuessAltText: {
|
||||
/** @type {boolean} */
|
||||
value: true,
|
||||
|
@ -211,6 +216,11 @@ const defaultOptions = {
|
|||
value: typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING"),
|
||||
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
|
||||
},
|
||||
enableNewAltTextWhenAddingImage: {
|
||||
/** @type {boolean} */
|
||||
value: true,
|
||||
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
|
||||
},
|
||||
enablePermissions: {
|
||||
/** @type {boolean} */
|
||||
value: false,
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
--focus-ring-outline: 2px solid var(--focus-ring-color);
|
||||
--link-fg-color: #0060df;
|
||||
--link-hover-fg-color: #0250bb;
|
||||
--separator-color: #f0f0f4;
|
||||
|
||||
--textarea-border-color: #8f8f9d;
|
||||
--textarea-bg-color: white;
|
||||
|
@ -57,6 +58,7 @@
|
|||
--hover-filter: brightness(1.4);
|
||||
--link-fg-color: #0df;
|
||||
--link-hover-fg-color: #80ebff;
|
||||
--separator-color: #52525e;
|
||||
|
||||
--textarea-bg-color: #42414d;
|
||||
|
||||
|
@ -79,6 +81,7 @@
|
|||
--focus-ring-color: ButtonBorder;
|
||||
--link-fg-color: LinkText;
|
||||
--link-hover-fg-color: LinkText;
|
||||
--separator-color: CanvasText;
|
||||
|
||||
--textarea-border-color: ButtonBorder;
|
||||
--textarea-bg-color: Field;
|
||||
|
@ -134,6 +137,13 @@
|
|||
}
|
||||
}
|
||||
|
||||
.dialogSeparator {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
margin-block: 4px;
|
||||
background-color: var(--separator-color);
|
||||
}
|
||||
|
||||
.dialogButtonsGroup {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
|
|
|
@ -314,34 +314,62 @@ class MLManager {
|
|||
|
||||
eventBus = null;
|
||||
|
||||
constructor(options) {
|
||||
this.enable({ ...options, listenToProgress: false });
|
||||
hasProgress = false;
|
||||
|
||||
static #AI_ALT_TEXT_MODEL_NAME = "moz-image-to-text";
|
||||
|
||||
constructor({
|
||||
altTextLearnMoreUrl,
|
||||
enableGuessAltText,
|
||||
enableAltTextModelDownload,
|
||||
}) {
|
||||
// The `altTextLearnMoreUrl` is used to provide a link to the user to learn
|
||||
// more about the "alt text" feature.
|
||||
// The link is used in the Alt Text dialog or in the Image Settings.
|
||||
this.altTextLearnMoreUrl = altTextLearnMoreUrl;
|
||||
this.enableAltTextModelDownload = enableAltTextModelDownload;
|
||||
this.enableGuessAltText = enableGuessAltText;
|
||||
|
||||
if (enableAltTextModelDownload) {
|
||||
this.#loadAltTextEngine(false);
|
||||
}
|
||||
}
|
||||
|
||||
async isEnabledFor(name) {
|
||||
return !!(await this.#enabled?.get(name));
|
||||
return this.enableGuessAltText && !!(await this.#enabled?.get(name));
|
||||
}
|
||||
|
||||
isReady(name) {
|
||||
return this.#ready?.has(name) ?? false;
|
||||
}
|
||||
|
||||
deleteModel(service) {
|
||||
return FirefoxCom.requestAsync("mlDelete", service);
|
||||
}
|
||||
|
||||
guess(data) {
|
||||
return FirefoxCom.requestAsync("mlGuess", data);
|
||||
}
|
||||
|
||||
enable({ altTextLearnMoreUrl, enableGuessAltText, listenToProgress }) {
|
||||
if (enableGuessAltText) {
|
||||
this.#loadAltTextEngine(listenToProgress);
|
||||
async deleteModel(name) {
|
||||
if (name !== "altText") {
|
||||
return;
|
||||
}
|
||||
// The `altTextLearnMoreUrl` is used to provide a link to the user to learn
|
||||
// more about the "alt text" feature.
|
||||
// The link is used in the Alt Text dialog or in the Image Settings.
|
||||
this.altTextLearnMoreUrl = altTextLearnMoreUrl;
|
||||
this.enableAltTextModelDownload = false;
|
||||
this.#ready?.delete(name);
|
||||
this.#enabled?.delete(name);
|
||||
await Promise.all([
|
||||
this.toggleService("altText", false),
|
||||
FirefoxCom.requestAsync("mlDelete", MLManager.#AI_ALT_TEXT_MODEL_NAME),
|
||||
]);
|
||||
}
|
||||
|
||||
async downloadModel(name) {
|
||||
if (name !== "altText") {
|
||||
return null;
|
||||
}
|
||||
this.enableAltTextModelDownload = true;
|
||||
return this.#loadAltTextEngine(true);
|
||||
}
|
||||
|
||||
async guess(data) {
|
||||
if (data?.name !== "altText") {
|
||||
return null;
|
||||
}
|
||||
data.service = MLManager.#AI_ALT_TEXT_MODEL_NAME;
|
||||
return FirefoxCom.requestAsync("mlGuess", data);
|
||||
}
|
||||
|
||||
async toggleService(name, enabled) {
|
||||
|
@ -349,11 +377,9 @@ class MLManager {
|
|||
return;
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
this.enableGuessAltText = enabled;
|
||||
if (enabled && this.enableAltTextModelDownload) {
|
||||
await this.#loadAltTextEngine(false);
|
||||
} else {
|
||||
this.#enabled?.delete(name);
|
||||
this.#ready?.delete(name);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -364,7 +390,7 @@ class MLManager {
|
|||
}
|
||||
this.#ready ||= new Set();
|
||||
const promise = FirefoxCom.requestAsync("loadAIEngine", {
|
||||
service: "moz-image-to-text",
|
||||
service: MLManager.#AI_ALT_TEXT_MODEL_NAME,
|
||||
listenToProgress,
|
||||
}).then(ok => {
|
||||
if (ok) {
|
||||
|
@ -374,22 +400,26 @@ class MLManager {
|
|||
});
|
||||
(this.#enabled ||= new Map()).set("altText", promise);
|
||||
if (listenToProgress) {
|
||||
this.hasProgress = true;
|
||||
const callback = ({ detail }) => {
|
||||
this.eventBus.dispatch("loadaiengineprogress", {
|
||||
source: this,
|
||||
detail,
|
||||
});
|
||||
if (detail.finished) {
|
||||
this.hasProgress = false;
|
||||
window.removeEventListener("loadAIEngineProgress", callback);
|
||||
}
|
||||
};
|
||||
window.addEventListener("loadAIEngineProgress", callback);
|
||||
promise.then(ok => {
|
||||
if (!ok) {
|
||||
this.hasProgress = false;
|
||||
window.removeEventListener("loadAIEngineProgress", callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
await promise;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -70,20 +70,57 @@ class MLManager {
|
|||
}
|
||||
|
||||
class FakeMLManager {
|
||||
constructor({ enableGuessAltText }) {
|
||||
eventBus = null;
|
||||
|
||||
hasProgress = false;
|
||||
|
||||
constructor({ enableGuessAltText, enableAltTextModelDownload }) {
|
||||
this.enableGuessAltText = enableGuessAltText;
|
||||
this.enableAltTextModelDownload = enableAltTextModelDownload;
|
||||
}
|
||||
|
||||
async isEnabledFor(_name) {
|
||||
return this.enableGuessAltText;
|
||||
}
|
||||
|
||||
async deleteModel(_service) {
|
||||
async deleteModel(_name) {
|
||||
this.enableAltTextModelDownload = false;
|
||||
return null;
|
||||
}
|
||||
|
||||
async downloadModel(_name) {
|
||||
// Simulate downloading the model but with progress.
|
||||
// The progress can be seen in the new alt-text dialog.
|
||||
this.hasProgress = true;
|
||||
|
||||
const { promise, resolve } = Promise.withResolvers();
|
||||
const total = 1e8;
|
||||
const end = 1.5 * total;
|
||||
const increment = 5e6;
|
||||
let loaded = 0;
|
||||
const id = setInterval(() => {
|
||||
loaded += increment;
|
||||
if (loaded <= end) {
|
||||
this.eventBus.dispatch("loadaiengineprogress", {
|
||||
source: this,
|
||||
detail: {
|
||||
total,
|
||||
totalLoaded: loaded,
|
||||
finished: loaded + increment >= end,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
clearInterval(id);
|
||||
this.hasProgress = false;
|
||||
this.enableAltTextModelDownload = true;
|
||||
resolve(true);
|
||||
}, 900);
|
||||
return promise;
|
||||
}
|
||||
|
||||
isReady(_name) {
|
||||
return true;
|
||||
return this.enableAltTextModelDownload;
|
||||
}
|
||||
|
||||
guess({ request: { data } }) {
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { noContextMenu } from "pdfjs-lib";
|
||||
|
||||
class NewAltTextManager {
|
||||
#boundCancel = this.#cancel.bind(this);
|
||||
|
||||
|
@ -28,6 +30,12 @@ class NewAltTextManager {
|
|||
|
||||
#disclaimer;
|
||||
|
||||
#downloadModel;
|
||||
|
||||
#downloadModelDescription;
|
||||
|
||||
#eventBus;
|
||||
|
||||
#firstTime = false;
|
||||
|
||||
#guessedAltText;
|
||||
|
@ -71,9 +79,12 @@ class NewAltTextManager {
|
|||
learnMore,
|
||||
errorCloseButton,
|
||||
createAutomaticallyButton,
|
||||
downloadModel,
|
||||
downloadModelDescription,
|
||||
title,
|
||||
},
|
||||
overlayManager
|
||||
overlayManager,
|
||||
eventBus
|
||||
) {
|
||||
this.#cancelButton = cancelButton;
|
||||
this.#createAutomaticallyButton = createAutomaticallyButton;
|
||||
|
@ -85,7 +96,10 @@ class NewAltTextManager {
|
|||
this.#textarea = textarea;
|
||||
this.#learnMore = learnMore;
|
||||
this.#title = title;
|
||||
this.#downloadModel = downloadModel;
|
||||
this.#downloadModelDescription = downloadModelDescription;
|
||||
this.#overlayManager = overlayManager;
|
||||
this.#eventBus = eventBus;
|
||||
|
||||
dialog.addEventListener("close", this.#close.bind(this));
|
||||
dialog.addEventListener("contextmenu", event => {
|
||||
|
@ -236,7 +250,7 @@ class NewAltTextManager {
|
|||
// When calling #mlGuessAltText we don't wait for it, so we must take care
|
||||
// that the alt text dialog can have been closed before the response is.
|
||||
const response = await this.#uiManager.mlGuess({
|
||||
service: "moz-image-to-text",
|
||||
name: "altText",
|
||||
request: {
|
||||
data,
|
||||
width,
|
||||
|
@ -274,6 +288,45 @@ class NewAltTextManager {
|
|||
this.#textarea.value = altText;
|
||||
}
|
||||
|
||||
#setProgress() {
|
||||
// Show the download model progress.
|
||||
this.#downloadModel.classList.toggle("hidden", false);
|
||||
|
||||
const callback = async ({ detail: { finished, total, totalLoaded } }) => {
|
||||
const ONE_MEGA_BYTES = 1e6;
|
||||
// totalLoaded can be greater than total if the download is compressed.
|
||||
// So we cheat to avoid any confusion.
|
||||
totalLoaded = Math.min(0.99 * total, totalLoaded);
|
||||
|
||||
// Update the progress.
|
||||
this.#downloadModelDescription.setAttribute(
|
||||
"data-l10n-args",
|
||||
`{"totalSize": ${Math.round(total / ONE_MEGA_BYTES)}, "downloadedSize": ${Math.round(totalLoaded / ONE_MEGA_BYTES)}}`
|
||||
);
|
||||
if (!finished) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We're done, remove the listener and hide the download model progress.
|
||||
this.#eventBus._off("loadaiengineprogress", callback);
|
||||
this.#downloadModel.classList.toggle("hidden", true);
|
||||
|
||||
this.#toggleAI(true);
|
||||
if (!this.#uiManager) {
|
||||
return;
|
||||
}
|
||||
const { mlManager } = this.#uiManager;
|
||||
|
||||
// The model has been downloaded, we can now enable the AI service.
|
||||
mlManager.toggleService("altText", true);
|
||||
this.#toggleGuessAltText(
|
||||
await mlManager.isEnabledFor("altText"),
|
||||
/* isInitial = */ true
|
||||
);
|
||||
};
|
||||
this.#eventBus._on("loadaiengineprogress", callback);
|
||||
}
|
||||
|
||||
async editAltText(uiManager, editor, firstTime) {
|
||||
if (this.#currentEditor || !editor) {
|
||||
return;
|
||||
|
@ -286,9 +339,19 @@ class NewAltTextManager {
|
|||
|
||||
this.#firstTime = firstTime;
|
||||
let { mlManager } = uiManager;
|
||||
if (!mlManager?.isReady("altText")) {
|
||||
mlManager = null;
|
||||
let hasAI = !!mlManager;
|
||||
|
||||
if (mlManager && !mlManager.isReady("altText")) {
|
||||
hasAI = false;
|
||||
if (mlManager.hasProgress) {
|
||||
this.#setProgress();
|
||||
} else {
|
||||
mlManager = null;
|
||||
}
|
||||
} else {
|
||||
this.#downloadModel.classList.toggle("hidden", true);
|
||||
}
|
||||
|
||||
const isAltTextEnabledPromise = mlManager?.isEnabledFor("altText");
|
||||
|
||||
this.#currentEditor = editor;
|
||||
|
@ -311,10 +374,12 @@ class NewAltTextManager {
|
|||
AI_MAX_IMAGE_DIMENSION,
|
||||
/* createImageData = */ true
|
||||
));
|
||||
this.#toggleGuessAltText(
|
||||
await isAltTextEnabledPromise,
|
||||
/* isInitial = */ true
|
||||
);
|
||||
if (hasAI) {
|
||||
this.#toggleGuessAltText(
|
||||
await isAltTextEnabledPromise,
|
||||
/* isInitial = */ true
|
||||
);
|
||||
}
|
||||
} else {
|
||||
({ canvas } = editor.copyCanvas(
|
||||
AI_MAX_IMAGE_DIMENSION,
|
||||
|
@ -326,7 +391,7 @@ class NewAltTextManager {
|
|||
this.#imagePreview.append(canvas);
|
||||
|
||||
this.#toggleNotNow();
|
||||
this.#toggleAI(!!mlManager);
|
||||
this.#toggleAI(hasAI);
|
||||
this.#toggleError(false);
|
||||
|
||||
try {
|
||||
|
@ -396,4 +461,161 @@ class NewAltTextManager {
|
|||
}
|
||||
}
|
||||
|
||||
export { NewAltTextManager };
|
||||
class ImageAltTextSettings {
|
||||
#aiModelSettings;
|
||||
|
||||
#boundOnClickCreateModel;
|
||||
|
||||
#createModelButton;
|
||||
|
||||
#dialog;
|
||||
|
||||
#eventBus;
|
||||
|
||||
#mlManager;
|
||||
|
||||
#overlayManager;
|
||||
|
||||
#showAltTextDialogButton;
|
||||
|
||||
constructor(
|
||||
{
|
||||
dialog,
|
||||
createModelButton,
|
||||
aiModelSettings,
|
||||
learnMore,
|
||||
closeButton,
|
||||
deleteModelButton,
|
||||
downloadModelButton,
|
||||
showAltTextDialogButton,
|
||||
},
|
||||
overlayManager,
|
||||
eventBus,
|
||||
mlManager
|
||||
) {
|
||||
this.#dialog = dialog;
|
||||
this.#aiModelSettings = aiModelSettings;
|
||||
this.#createModelButton = createModelButton;
|
||||
this.#showAltTextDialogButton = showAltTextDialogButton;
|
||||
this.#overlayManager = overlayManager;
|
||||
this.#eventBus = eventBus;
|
||||
this.#mlManager = mlManager;
|
||||
this.#boundOnClickCreateModel = this.#togglePref.bind(
|
||||
this,
|
||||
"enableGuessAltText"
|
||||
);
|
||||
|
||||
const { altTextLearnMoreUrl } = mlManager;
|
||||
if (altTextLearnMoreUrl) {
|
||||
learnMore.href = altTextLearnMoreUrl;
|
||||
}
|
||||
|
||||
dialog.addEventListener("close", this.#close.bind(this));
|
||||
dialog.addEventListener("contextmenu", noContextMenu);
|
||||
|
||||
createModelButton.addEventListener("click", async e => {
|
||||
const checked = this.#togglePref("enableGuessAltText", e);
|
||||
await mlManager.toggleService("altText", checked);
|
||||
});
|
||||
|
||||
showAltTextDialogButton.addEventListener(
|
||||
"click",
|
||||
this.#togglePref.bind(this, "enableNewAltTextWhenAddingImage")
|
||||
);
|
||||
|
||||
deleteModelButton.addEventListener("click", async () => {
|
||||
await mlManager.deleteModel("altText");
|
||||
|
||||
aiModelSettings.classList.toggle("download", true);
|
||||
createModelButton.removeEventListener(
|
||||
"click",
|
||||
this.#boundOnClickCreateModel
|
||||
);
|
||||
createModelButton.setAttribute("aria-pressed", false);
|
||||
this.#setPref("enableGuessAltText", false);
|
||||
this.#setPref("enableAltTextModelDownload", false);
|
||||
});
|
||||
|
||||
downloadModelButton.addEventListener("click", async () => {
|
||||
downloadModelButton.disabled = true;
|
||||
downloadModelButton.firstChild.setAttribute(
|
||||
"data-l10n-id",
|
||||
"pdfjs-editor-alt-text-settings-downloading-model-button"
|
||||
);
|
||||
|
||||
await mlManager.downloadModel("altText");
|
||||
|
||||
aiModelSettings.classList.toggle("download", false);
|
||||
downloadModelButton.firstChild.setAttribute(
|
||||
"data-l10n-id",
|
||||
"pdfjs-editor-alt-text-settings-download-model-button"
|
||||
);
|
||||
createModelButton.addEventListener(
|
||||
"click",
|
||||
this.#boundOnClickCreateModel
|
||||
);
|
||||
createModelButton.setAttribute("aria-pressed", true);
|
||||
this.#setPref("enableGuessAltText", true);
|
||||
mlManager.toggleService("altText", true);
|
||||
this.#setPref("enableAltTextModelDownload", true);
|
||||
downloadModelButton.disabled = false;
|
||||
});
|
||||
|
||||
closeButton.addEventListener("click", this.#finish.bind(this));
|
||||
this.#overlayManager.register(dialog);
|
||||
}
|
||||
|
||||
async open({ enableGuessAltText, enableNewAltTextWhenAddingImage }) {
|
||||
const { enableAltTextModelDownload } = this.#mlManager;
|
||||
this.#createModelButton.disabled = !enableAltTextModelDownload;
|
||||
this.#createModelButton.setAttribute(
|
||||
"aria-pressed",
|
||||
enableAltTextModelDownload && enableGuessAltText
|
||||
);
|
||||
this.#showAltTextDialogButton.setAttribute(
|
||||
"aria-pressed",
|
||||
enableNewAltTextWhenAddingImage
|
||||
);
|
||||
this.#aiModelSettings.classList.toggle(
|
||||
"download",
|
||||
!enableAltTextModelDownload
|
||||
);
|
||||
|
||||
try {
|
||||
await this.#overlayManager.open(this.#dialog);
|
||||
} catch (ex) {
|
||||
this.#close();
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
#togglePref(name, { target }) {
|
||||
const checked = target.getAttribute("aria-pressed") !== "true";
|
||||
this.#setPref(name, checked);
|
||||
target.setAttribute("aria-pressed", checked);
|
||||
return checked;
|
||||
}
|
||||
|
||||
#setPref(name, value) {
|
||||
this.#eventBus.dispatch("setpreference", {
|
||||
source: this,
|
||||
name,
|
||||
value,
|
||||
});
|
||||
}
|
||||
|
||||
#finish() {
|
||||
if (this.#overlayManager.active === this.#dialog) {
|
||||
this.#overlayManager.close(this.#dialog);
|
||||
}
|
||||
}
|
||||
|
||||
#close() {
|
||||
this.#createModelButton.removeEventListener(
|
||||
"click",
|
||||
this.#boundOnClickCreateModel
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { ImageAltTextSettings, NewAltTextManager };
|
||||
|
|
|
@ -221,6 +221,8 @@ class PDFViewer {
|
|||
|
||||
#enableUpdatedAddImage = false;
|
||||
|
||||
#enableNewAltTextWhenAddingImage = false;
|
||||
|
||||
#eventAbortController = null;
|
||||
|
||||
#mlManager = null;
|
||||
|
@ -294,6 +296,8 @@ class PDFViewer {
|
|||
this.#enableHighlightFloatingButton =
|
||||
options.enableHighlightFloatingButton === true;
|
||||
this.#enableUpdatedAddImage = options.enableUpdatedAddImage === true;
|
||||
this.#enableNewAltTextWhenAddingImage =
|
||||
options.enableNewAltTextWhenAddingImage === true;
|
||||
this.imageResourcesPath = options.imageResourcesPath || "";
|
||||
this.enablePrintAutoRotate = options.enablePrintAutoRotate || false;
|
||||
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
|
||||
|
@ -894,6 +898,7 @@ class PDFViewer {
|
|||
this.#annotationEditorHighlightColors,
|
||||
this.#enableHighlightFloatingButton,
|
||||
this.#enableUpdatedAddImage,
|
||||
this.#enableNewAltTextWhenAddingImage,
|
||||
this.#mlManager
|
||||
);
|
||||
eventBus.dispatch("annotationeditoruimanager", {
|
||||
|
|
|
@ -49,6 +49,8 @@ import { PagesCountLimit } from "./pdf_viewer.js";
|
|||
* select tool.
|
||||
* @property {HTMLButtonElement} cursorHandToolButton - Button to enable the
|
||||
* hand tool.
|
||||
* @property {HTMLButtonElement} imageAltTextSettingsButton - Button for opening
|
||||
* the image alt-text settings dialog.
|
||||
* @property {HTMLButtonElement} documentPropertiesButton - Button for opening
|
||||
* the document properties dialog.
|
||||
*/
|
||||
|
@ -137,6 +139,11 @@ class SecondaryToolbar {
|
|||
eventDetails: { mode: SpreadMode.EVEN },
|
||||
close: true,
|
||||
},
|
||||
{
|
||||
element: options.imageAltTextSettingsButton,
|
||||
eventName: "imagealttextsettings",
|
||||
close: true,
|
||||
},
|
||||
{
|
||||
element: options.documentPropertiesButton,
|
||||
eventName: "documentproperties",
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
const AltTextManager = null;
|
||||
const AnnotationEditorParams = null;
|
||||
const ImageAltTextSettings = null;
|
||||
const NewAltTextManager = null;
|
||||
const PDFAttachmentViewer = null;
|
||||
const PDFCursorTools = null;
|
||||
|
@ -30,6 +31,7 @@ const SecondaryToolbar = null;
|
|||
export {
|
||||
AltTextManager,
|
||||
AnnotationEditorParams,
|
||||
ImageAltTextSettings,
|
||||
NewAltTextManager,
|
||||
PDFAttachmentViewer,
|
||||
PDFCursorTools,
|
||||
|
|
|
@ -120,6 +120,9 @@
|
|||
--secondaryToolbarButton-spreadNone-icon: url(images/secondaryToolbarButton-spreadNone.svg);
|
||||
--secondaryToolbarButton-spreadOdd-icon: url(images/secondaryToolbarButton-spreadOdd.svg);
|
||||
--secondaryToolbarButton-spreadEven-icon: url(images/secondaryToolbarButton-spreadEven.svg);
|
||||
--secondaryToolbarButton-imageAltTextSettings-icon: var(
|
||||
--toolbarButton-editorStamp-icon
|
||||
);
|
||||
--secondaryToolbarButton-documentProperties-icon: url(images/secondaryToolbarButton-documentProperties.svg);
|
||||
--editorParams-stampAddImage-icon: url(images/toolbarButton-zoomIn.svg);
|
||||
}
|
||||
|
@ -1077,6 +1080,10 @@ a:is(.toolbarButton, .secondaryToolbarButton)[href="#"] {
|
|||
mask-image: var(--secondaryToolbarButton-documentProperties-icon);
|
||||
}
|
||||
|
||||
#imageAltTextSettings::before {
|
||||
mask-image: var(--secondaryToolbarButton-imageAltTextSettings-icon);
|
||||
}
|
||||
|
||||
.verticalToolbarSeparator {
|
||||
display: block;
|
||||
margin: 5px 2px;
|
||||
|
|
|
@ -323,9 +323,14 @@ See https://github.com/adobe-type-tools/cmap-resources
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<div id="imageAltTextSettingsSeparator" class="horizontalToolbarSeparator hidden"></div>
|
||||
<button id="imageAltTextSettings" type="button" class="secondaryToolbarButton hidden" title="Image alt text settings" tabindex="69" data-l10n-id="pdfjs-image-alt-text-settings-button" aria-controls="altTextSettingsDialog">
|
||||
<span data-l10n-id="pdfjs-image-alt-text-settings-button-label">Image alt text settings</span>
|
||||
</button>
|
||||
|
||||
<div class="horizontalToolbarSeparator"></div>
|
||||
|
||||
<button id="documentProperties" class="secondaryToolbarButton" type="button" title="Document Properties…" tabindex="69" data-l10n-id="pdfjs-document-properties-button" aria-controls="documentPropertiesDialog">
|
||||
<button id="documentProperties" class="secondaryToolbarButton" type="button" title="Document Properties…" tabindex="70" data-l10n-id="pdfjs-document-properties-button" aria-controls="documentPropertiesDialog">
|
||||
<span data-l10n-id="pdfjs-document-properties-button-label">Document Properties…</span>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -593,6 +598,53 @@ See https://github.com/adobe-type-tools/cmap-resources
|
|||
</div>
|
||||
</dialog>
|
||||
|
||||
<dialog class="dialog" id="altTextSettingsDialog" aria-labelledby="altTextSettingsTitle">
|
||||
<div id="altTextSettingsContainer" class="mainContainer">
|
||||
<div class="title">
|
||||
<span id="altTextSettingsTitle" data-l10n-id="pdfjs-editor-alt-text-settings-dialog-label" role="sectionhead" tabindex="0" class="title">Image alt text settings</span>
|
||||
</div>
|
||||
<div id="automaticAltText">
|
||||
<span data-l10n-id="pdfjs-editor-alt-text-settings-automatic-title">Automatic alt text</span>
|
||||
<div id="automaticSettings">
|
||||
<div id="createModelSetting">
|
||||
<div class="toggler">
|
||||
<button id="createModelButton" type="button" class="toggle-button" aria-pressed="true" tabindex="0"></button>
|
||||
<label for="createModelButton" class="togglerLabel" data-l10n-id="pdfjs-editor-alt-text-settings-create-model-button-label">Create alt text automatically</label>
|
||||
</div>
|
||||
<div id="createModelDescription" class="description">
|
||||
<span data-l10n-id="pdfjs-editor-alt-text-settings-create-model-description">Suggests descriptions to help people who can’t see the image or when the image doesn’t load.</span> <a href="https://support.mozilla.org/en-US/kb/pdf-alt-text" target="_blank" rel="noopener noreferrer" id="altTextSettingsLearnMore" data-l10n-id="pdfjs-editor-new-alt-text-disclaimer-learn-more-url" tabindex="0">Learn more</a>
|
||||
</div>
|
||||
</div>
|
||||
<div id="aiModelSettings">
|
||||
<div>
|
||||
<span data-l10n-id="pdfjs-editor-alt-text-settings-download-model-label" data-l10n-args='{ "totalSize": 180 }'>Alt text AI model (180MB)</span>
|
||||
<div id="aiModelDescription" class="description">
|
||||
<span data-l10n-id="pdfjs-editor-alt-text-settings-ai-model-description">Runs locally on your device so your data stays private. Required for automatic alt text.</span>
|
||||
</div>
|
||||
</div>
|
||||
<button id="deleteModelButton" type="button" class="secondaryButton" tabindex="0"><span data-l10n-id="pdfjs-editor-alt-text-settings-delete-model-button">Delete</span></button>
|
||||
<button id="downloadModelButton"type="button" class="secondaryButton" tabindex="0"><span data-l10n-id="pdfjs-editor-alt-text-settings-download-model-button">Download</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dialogSeparator"></div>
|
||||
<div id="altTextEditor">
|
||||
<span data-l10n-id="pdfjs-editor-alt-text-settings-editor-title">Alt text editor</span>
|
||||
<div id="showAltTextEditor">
|
||||
<div class="toggler">
|
||||
<button id="showAltTextDialogButton" type="button" class="toggle-button" aria-pressed="true" tabindex="0"></button>
|
||||
<label for="showAltTextDialogButton" class="togglerLabel" data-l10n-id="pdfjs-editor-alt-text-settings-show-dialog-button-label">Show alt text editor right away when adding an image</label>
|
||||
</div>
|
||||
<div id="showAltTextDialogDescription" class="description">
|
||||
<span data-l10n-id="pdfjs-editor-alt-text-settings-show-dialog-description">Helps you make sure all your images have alt text.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="buttons" class="dialogButtonsGroup">
|
||||
<button id="altTextSettingsCloseButton" type="button" class="primaryButton" tabindex="0"><span data-l10n-id="pdfjs-editor-alt-text-settings-close-button">Close</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
<!--#if !MOZCENTRAL-->
|
||||
<dialog id="printServiceDialog" style="min-width: 200px;">
|
||||
<div class="row">
|
||||
|
|
|
@ -93,6 +93,12 @@ function getViewerConfiguration() {
|
|||
spreadNoneButton: document.getElementById("spreadNone"),
|
||||
spreadOddButton: document.getElementById("spreadOdd"),
|
||||
spreadEvenButton: document.getElementById("spreadEven"),
|
||||
imageAltTextSettingsButton: document.getElementById(
|
||||
"imageAltTextSettings"
|
||||
),
|
||||
imageAltTextSettingsSeparator: document.getElementById(
|
||||
"imageAltTextSettingsSeparator"
|
||||
),
|
||||
documentPropertiesButton: document.getElementById("documentProperties"),
|
||||
},
|
||||
sidebar: {
|
||||
|
@ -188,6 +194,21 @@ function getViewerConfiguration() {
|
|||
notNowButton: document.getElementById("newAltTextNotNow"),
|
||||
saveButton: document.getElementById("newAltTextSave"),
|
||||
},
|
||||
altTextSettingsDialog: {
|
||||
dialog: document.getElementById("altTextSettingsDialog"),
|
||||
createModelButton: document.getElementById("createModelButton"),
|
||||
aiModelSettings: document.getElementById("aiModelSettings"),
|
||||
learnMore: document.getElementById("altTextSettingsLearnMore"),
|
||||
deleteModelButton: document.getElementById("deleteModelButton"),
|
||||
downloadModelButton: document.getElementById("downloadModelButton"),
|
||||
showAltTextDialogButton: document.getElementById(
|
||||
"showAltTextDialogButton"
|
||||
),
|
||||
altTextSettingsCloseButton: document.getElementById(
|
||||
"altTextSettingsCloseButton"
|
||||
),
|
||||
closeButton: document.getElementById("altTextSettingsCloseButton"),
|
||||
},
|
||||
annotationEditorParams: {
|
||||
editorFreeTextFontSize: document.getElementById("editorFreeTextFontSize"),
|
||||
editorFreeTextColor: document.getElementById("editorFreeTextColor"),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue