1
0
Fork 0
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:
Calixte Denizet 2024-08-01 15:29:01 +02:00
parent d562e0525d
commit 32d09276f0
17 changed files with 627 additions and 54 deletions

View file

@ -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;
}
}
}

View file

@ -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,

View file

@ -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,

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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 } }) {

View file

@ -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 };

View file

@ -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", {

View file

@ -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",

View file

@ -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,

View file

@ -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;

View file

@ -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 cant see the image or when the image doesnt 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">

View file

@ -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"),