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

Merge pull request #18492 from calixteman/new_stamp_dialog

[Editor] Implement the new alt text flow (bug 1909604)
This commit is contained in:
calixteman 2024-07-30 10:25:02 +02:00 committed by GitHub
commit 8f45374881
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 1366 additions and 91 deletions

View file

@ -46,6 +46,8 @@
--editorFreeHighlight-editing-cursor: url(images/cursor-editorFreeHighlight.svg)
1 18,
pointer;
--new-alt-text-warning-image: url(images/altText_warning.svg);
}
/* The following class is used to hide an element but keep it available to
@ -76,6 +78,12 @@
}
}
#viewerContainer.pdfPresentationMode:fullscreen {
.noAltTextBadge {
display: none !important;
}
}
@media (min-resolution: 1.1dppx) {
:root {
--editorFreeText-editing-cursor: url(images/cursor-editorFreeText.svg) 0 16,
@ -222,12 +230,18 @@
--editor-toolbar-vert-offset: 6px;
--editor-toolbar-height: 28px;
--editor-toolbar-padding: 2px;
--alt-text-done-color: #2ac3a2;
--alt-text-warning-color: #0090ed;
--alt-text-hover-done-color: var(--alt-text-done-color);
--alt-text-hover-warning-color: var(--alt-text-warning-color);
@media (prefers-color-scheme: dark) {
--editor-toolbar-bg-color: #2b2a33;
--editor-toolbar-fg-color: #fbfbfe;
--editor-toolbar-hover-bg-color: #52525e;
--editor-toolbar-focus-outline-color: #0df;
--alt-text-done-color: #54ffbd;
--alt-text-warning-color: #80ebff;
}
@media screen and (forced-colors: active) {
@ -241,6 +255,10 @@
var(--editor-toolbar-hover-border-color);
--editor-toolbar-focus-outline-color: ButtonBorder;
--editor-toolbar-shadow: none;
--alt-text-done-color: var(--editor-toolbar-fg-color);
--alt-text-warning-color: var(--editor-toolbar-fg-color);
--alt-text-hover-done-color: var(--editor-toolbar-hover-fg-color);
--alt-text-hover-warning-color: var(--editor-toolbar-hover-fg-color);
}
display: flex;
@ -400,6 +418,31 @@
mask-image: var(--alt-text-done-image);
}
&.new {
&::before {
width: 16px;
height: 16px;
mask-image: var(--new-alt-text-warning-image);
background-color: var(--alt-text-warning-color);
mask-size: cover;
}
&:hover::before {
background-color: var(--alt-text-hover-warning-color);
}
&.done {
&::before {
mask-image: var(--alt-text-done-image);
background-color: var(--alt-text-done-color);
}
&:hover::before {
background-color: var(--alt-text-hover-done-color);
}
}
}
.tooltip {
display: none;
@ -519,6 +562,50 @@
top: 0;
left: 0;
}
.noAltTextBadge {
--no-alt-text-badge-border-color: #f0f0f4;
--no-alt-text-badge-bg-color: #cfcfd8;
--no-alt-text-badge-fg-color: #5b5b66;
@media (prefers-color-scheme: dark) {
--no-alt-text-badge-border-color: #52525e;
--no-alt-text-badge-bg-color: #fbfbfe;
--no-alt-text-badge-fg-color: #15141a;
}
@media screen and (forced-colors: active) {
--no-alt-text-badge-border-color: ButtonText;
--no-alt-text-badge-bg-color: ButtonFace;
--no-alt-text-badge-fg-color: ButtonText;
}
position: absolute;
inset-inline-end: 5px;
inset-block-end: 5px;
display: inline-flex;
width: 32px;
height: 32px;
padding: 3px;
justify-content: center;
align-items: center;
pointer-events: none;
z-index: 1;
border-radius: 2px;
border: 1px solid var(--no-alt-text-badge-border-color);
background: var(--no-alt-text-badge-bg-color);
&::before {
content: "";
display: inline-block;
width: 16px;
height: 16px;
mask-image: var(--new-alt-text-warning-image);
mask-size: cover;
background-color: var(--no-alt-text-badge-fg-color);
}
}
}
.annotationEditorLayer {
@ -767,6 +854,177 @@
}
}
.dialog.newAltText {
--new-alt-text-ai-disclaimer-icon: url(images/altText_disclaimer.svg);
--new-alt-text-spinner-icon: url(images/altText_spinner.svg);
width: 80%;
max-width: 570px;
min-width: 300px;
padding: 0;
&.noAi {
#newAltTextDisclaimer,
#newAltTextCreateAutomatically {
display: none !important;
}
}
&.aiInstalling {
#newAltTextCreateAutomatically {
display: none !important;
}
#newAltTextDownloadModel {
display: flex !important;
}
}
&.error {
#newAltTextNotNow {
display: none !important;
}
#newAltTextCancel {
display: inline-block !important;
}
}
&:not(.error) #newAltTextError {
display: none !important;
}
#newAltTextContainer {
display: flex;
width: auto;
padding: 16px;
flex-direction: column;
justify-content: flex-end;
align-items: flex-start;
gap: 12px;
flex: 0 1 auto;
#mainContent {
display: flex;
justify-content: flex-end;
align-items: flex-start;
gap: 12px;
align-self: stretch;
flex: 1 1 auto;
#descriptionAndSettings {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 16px;
flex: 1 0 0;
align-self: stretch;
}
#descriptionInstruction {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 8px;
align-self: stretch;
flex: 1 1 auto;
#newAltTextDescriptionContainer {
width: 100%;
height: 70px;
position: relative;
textarea {
width: 100%;
height: 100%;
padding: 8px;
&::placeholder {
color: var(--text-secondary-color);
}
}
.altTextSpinner {
display: none;
position: absolute;
width: 16px;
height: 16px;
inset-inline-start: 8px;
inset-block-start: 8px;
mask-size: cover;
background-color: var(--text-secondary-color);
pointer-events: none;
}
&.loading {
textarea::placeholder {
color: transparent;
}
.altTextSpinner {
display: inline-block;
mask-image: var(--new-alt-text-spinner-icon);
}
}
}
#newAltTextDescription {
font-size: 11px;
}
#newAltTextDisclaimer {
display: flex;
align-items: center;
gap: 4px;
align-self: stretch;
flex-wrap: wrap;
font-size: 11px;
&::before {
content: "";
display: inline-block;
width: 16px;
height: 16px;
mask-image: var(--new-alt-text-ai-disclaimer-icon);
mask-size: cover;
background-color: var(--text-secondary-color);
}
}
}
#newAltTextDownloadModel {
display: flex;
align-items: center;
gap: 4px;
align-self: stretch;
&::before {
content: "";
display: inline-block;
width: 16px;
height: 16px;
mask-image: var(--new-alt-text-spinner-icon);
mask-size: cover;
background-color: var(--text-secondary-color);
}
}
#newAltTextImagePreview {
width: 180px;
aspect-ratio: 1;
display: flex;
justify-content: center;
align-items: center;
flex: 0 0 auto;
> canvas {
max-width: 100%;
max-height: 100%;
}
}
}
}
}
.colorPicker {
--hover-outline-color: #0250bb;
--selected-outline-color: #0060df;

View file

@ -64,6 +64,7 @@ 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";
@ -205,6 +206,14 @@ const PDFViewerApplication = {
if (mode) {
document.documentElement.classList.add(mode);
}
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
if (AppOptions.get("enableFakeMLManager")) {
this.mlManager =
MLManager.getFakeMLManager?.({
enableGuessAltText: AppOptions.get("enableGuessAltText"),
}) || null;
}
}
} else if (AppOptions.get("enableAltText")) {
// We want to load the image-to-text AI engine as soon as possible.
this.mlManager = new MLManager({
@ -433,14 +442,21 @@ const PDFViewerApplication = {
foreground: AppOptions.get("pageColorsForeground"),
}
: null;
const altTextManager = appConfig.altTextDialog
? new AltTextManager(
appConfig.altTextDialog,
container,
this.overlayManager,
eventBus
)
: null;
let altTextManager;
if (AppOptions.get("enableUpdatedAddImage")) {
altTextManager = appConfig.newAltTextDialog
? new NewAltTextManager(appConfig.newAltTextDialog, this.overlayManager)
: null;
} else {
altTextManager = appConfig.altTextDialog
? new AltTextManager(
appConfig.altTextDialog,
container,
this.overlayManager,
eventBus
)
: null;
}
const enableHWA = AppOptions.get("enableHWA");
const pdfViewer = new PDFViewer({

View file

@ -469,6 +469,11 @@ if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) {
value: typeof PDFJSDev !== "undefined" && PDFJSDev.test("CHROME") ? 2 : 0,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
};
defaultOptions.enableFakeMLManager = {
/** @type {boolean} */
value: true,
kind: OptionKind.VIEWER,
};
}
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
defaultOptions.disablePreferences = {

View file

@ -22,6 +22,8 @@
--hover-filter: brightness(0.9);
--focus-ring-color: #0060df;
--focus-ring-outline: 2px solid var(--focus-ring-color);
--link-fg-color: #0060df;
--link-hover-fg-color: #0250bb;
--textarea-border-color: #8f8f9d;
--textarea-bg-color: white;
@ -53,6 +55,8 @@
--text-secondary-color: #cfcfd8;
--focus-ring-color: #0df;
--hover-filter: brightness(1.4);
--link-fg-color: #0df;
--link-hover-fg-color: #80ebff;
--textarea-bg-color: #42414d;
@ -73,6 +77,8 @@
--text-secondary-color: CanvasText;
--hover-filter: none;
--focus-ring-color: ButtonBorder;
--link-fg-color: LinkText;
--link-hover-fg-color: LinkText;
--textarea-border-color: ButtonBorder;
--textarea-bg-color: Field;
@ -112,6 +118,28 @@
outline-offset: 2px;
}
.title {
display: flex;
width: auto;
flex-direction: column;
justify-content: flex-end;
align-items: flex-start;
gap: 12px;
> span {
font-size: 13px;
font-style: normal;
font-weight: 590;
line-height: 150%; /* 19.5px */
}
}
.dialogButtonsGroup {
display: flex;
gap: 12px;
align-self: flex-end;
}
.radio {
display: flex;
flex-direction: column;
@ -159,7 +187,7 @@
}
}
button {
button:not(:is(.toggle-button, .closeButton)) {
border-radius: 4px;
border: 1px solid;
font: menu;
@ -199,6 +227,14 @@
}
}
a {
color: var(--link-fg-color);
&:hover {
color: var(--link-hover-fg-color);
}
}
textarea {
font: inherit;
padding: 8px;
@ -220,5 +256,148 @@
opacity: 0.4;
}
}
.messageBar {
--message-bar-warning-icon: url(images/messageBar_warning.svg);
--closing-button-icon: url(images/messageBar_closingButton.svg);
--message-bar-bg-color: #ffebcd;
--message-bar-fg-color: #15141a;
--message-bar-border-color: rgb(0 0 0 / 0.08);
--message-bar-icon-color: #cd411e;
--message-bar-close-button-border-radius: 4px;
--message-bar-close-button-border: none;
--message-bar-close-button-color: var(--text-primary-color);
--message-bar-close-button-hover-bg-color: rgb(21 20 26 / 0.14);
--message-bar-close-button-active-bg-color: rgb(21 20 26 / 0.21);
--message-bar-close-button-focus-bg-color: rgb(21 20 26 / 0.07);
--message-bar-close-button-color-hover: var(--text-primary-color);
@media (prefers-color-scheme: dark) {
--message-bar-bg-color: #5a3100;
--message-bar-fg-color: #fbfbfe;
--message-bar-border-color: rgb(255 255 255 / 0.08);
--message-bar-icon-color: #e49c49;
--message-bar-close-button-hover-bg-color: rgb(251 251 254 / 0.14);
--message-bar-close-button-active-bg-color: rgb(251 251 254 / 0.21);
--message-bar-close-button-focus-bg-color: rgb(251 251 254 / 0.07);
}
@media screen and (forced-colors: active) {
--message-bar-bg-color: HighlightText;
--message-bar-fg-color: CanvasText;
--message-bar-border-color: CanvasText;
--message-bar-icon-color: CanvasText;
--message-bar-close-button-color: ButtonText;
--message-bar-close-button-border: 1px solid ButtonText;
--message-bar-close-button-hover-bg-color: ButtonText;
--message-bar-close-button-active-bg-color: ButtonText;
--message-bar-close-button-focus-bg-color: ButtonText;
--message-bar-close-button-color-hover: HighlightText;
}
display: flex;
position: relative;
padding: 12px 8px 12px 0;
flex-direction: column;
justify-content: center;
align-items: flex-start;
gap: 8px;
align-self: stretch;
border-radius: 4px;
border: 1px solid var(--message-bar-border-color);
background: var(--message-bar-bg-color);
> div {
display: flex;
padding-inline-start: 16px;
align-items: flex-start;
gap: 8px;
align-self: stretch;
&::before {
content: "";
display: inline-block;
width: 16px;
height: 16px;
mask-image: var(--message-bar-warning-icon);
mask-size: cover;
background-color: var(--message-bar-icon-color);
}
> div {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 8px;
flex: 1 0 0;
.title {
font-size: 13px;
font-weight: 590;
}
.description {
font-size: 13px;
}
}
}
.closeButton {
position: absolute;
width: 32px;
height: 32px;
inset-inline-end: 8px;
inset-block-start: 8px;
background: none;
border-radius: var(--message-bar-close-button-border-radius);
border: var(--message-bar-close-button-border);
&::before {
content: "";
display: inline-block;
width: 16px;
height: 16px;
mask-image: var(--closing-button-icon);
mask-size: cover;
background-color: var(--message-bar-close-button-color);
}
&:is(:hover, :active, :focus)::before {
background-color: var(--message-bar-close-button-color-hover);
}
&:hover {
background-color: var(--message-bar-close-button-hover-bg-color);
}
&:active {
background-color: var(--message-bar-close-button-active-bg-color);
}
&:focus {
background-color: var(--message-bar-close-button-focus-bg-color);
}
> span {
display: inline-block;
width: 0;
height: 0;
overflow: hidden;
}
}
}
.toggler {
display: flex;
align-items: center;
gap: 8px;
align-self: stretch;
> .togglerLabel {
user-select: none;
}
}
}
}

View file

@ -310,6 +310,8 @@ class FirefoxScripting {
class MLManager {
#enabled = null;
#ready = null;
eventBus = null;
constructor(options) {
@ -320,6 +322,10 @@ class MLManager {
return !!(await this.#enabled?.get(name));
}
isReady(name) {
return this.#ready?.has(name) ?? false;
}
deleteModel(service) {
return FirefoxCom.requestAsync("mlDelete", service);
}
@ -338,14 +344,33 @@ class MLManager {
this.altTextLearnMoreUrl = altTextLearnMoreUrl;
}
async toggleService(name, enabled) {
if (name !== "altText") {
return;
}
if (enabled) {
await this.#loadAltTextEngine(false);
} else {
this.#enabled?.delete(name);
this.#ready?.delete(name);
}
}
async #loadAltTextEngine(listenToProgress) {
if (this.#enabled?.has("altText")) {
// We already have a promise for the "altText" service.
return;
}
this.#ready ||= new Set();
const promise = FirefoxCom.requestAsync("loadAIEngine", {
service: "moz-image-to-text",
listenToProgress,
}).then(ok => {
if (ok) {
this.#ready.add("altText");
}
return ok;
});
(this.#enabled ||= new Map()).set("altText", promise);
if (listenToProgress) {

View file

@ -56,9 +56,47 @@ class MLManager {
return null;
}
async guess() {
isReady(_name) {
return false;
}
guess(_data) {}
toggleService(_name, _enabled) {}
static getFakeMLManager(options) {
return new FakeMLManager(options);
}
}
class FakeMLManager {
constructor({ enableGuessAltText }) {
this.enableGuessAltText = enableGuessAltText;
}
async isEnabledFor(_name) {
return this.enableGuessAltText;
}
async deleteModel(_service) {
return null;
}
isReady(_name) {
return true;
}
guess({ request: { data } }) {
return new Promise(resolve => {
setTimeout(() => {
resolve(data ? { output: "Fake alt text" } : { error: true });
}, 3000);
});
}
toggleService(_name, enabled) {
this.enableGuessAltText = enabled;
}
}
export { ExternalServices, initCom, MLManager, Preferences };

View file

@ -0,0 +1,3 @@
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.49073 1.3015L3.30873 2.1505C3.29349 2.22246 3.25769 2.28844 3.20568 2.34045C3.15368 2.39246 3.08769 2.42826 3.01573 2.4435L2.16673 2.6255C1.76473 2.7125 1.76473 3.2865 2.16673 3.3725L3.01573 3.5555C3.08769 3.57074 3.15368 3.60654 3.20568 3.65855C3.25769 3.71056 3.29349 3.77654 3.30873 3.8485L3.49073 4.6975C3.57773 5.0995 4.15173 5.0995 4.23773 4.6975L4.42073 3.8485C4.43598 3.77654 4.47177 3.71056 4.52378 3.65855C4.57579 3.60654 4.64178 3.57074 4.71373 3.5555L5.56173 3.3725C5.96373 3.2855 5.96373 2.7115 5.56173 2.6255L4.71273 2.4435C4.64083 2.42814 4.57491 2.3923 4.52292 2.34031C4.47093 2.28832 4.43509 2.2224 4.41973 2.1505L4.23773 1.3015C4.15073 0.8995 3.57673 0.8995 3.49073 1.3015ZM10.8647 13.9995C10.4853 14.0056 10.1158 13.8782 9.82067 13.6397C9.52553 13.4013 9.32347 13.0667 9.24973 12.6945L8.89273 11.0275C8.83676 10.7687 8.70738 10.5316 8.52009 10.3445C8.3328 10.1574 8.09554 10.0282 7.83673 9.9725L6.16973 9.6155C5.38873 9.4465 4.86473 8.7975 4.86473 7.9995C4.86473 7.2015 5.38873 6.5525 6.16973 6.3845L7.83673 6.0275C8.09551 5.97135 8.33267 5.84193 8.51992 5.65468C8.70716 5.46744 8.83658 5.23028 8.89273 4.9715L9.25073 3.3045C9.41773 2.5235 10.0667 1.9995 10.8647 1.9995C11.6627 1.9995 12.3117 2.5235 12.4797 3.3045L12.8367 4.9715C12.9507 5.4995 13.3647 5.9135 13.8927 6.0265L15.5597 6.3835C16.3407 6.5525 16.8647 7.2015 16.8647 7.9995C16.8647 8.7975 16.3407 9.4465 15.5597 9.6145L13.8927 9.9715C13.6337 10.0275 13.3963 10.157 13.209 10.3445C13.0217 10.5319 12.8925 10.7694 12.8367 11.0285L12.4787 12.6945C12.4054 13.0667 12.2036 13.4014 11.9086 13.6399C11.6135 13.8784 11.2441 14.0057 10.8647 13.9995ZM10.8647 3.2495C10.7667 3.2495 10.5337 3.2795 10.4727 3.5655L10.1147 5.2335C10.0081 5.72777 9.76116 6.18082 9.40361 6.53837C9.04606 6.89593 8.59301 7.14283 8.09873 7.2495L6.43173 7.6065C6.14573 7.6685 6.11473 7.9015 6.11473 7.9995C6.11473 8.0975 6.14573 8.3305 6.43173 8.3925L8.09873 8.7495C8.59301 8.85617 9.04606 9.10307 9.40361 9.46062C9.76116 9.81817 10.0081 10.2712 10.1147 10.7655L10.4727 12.4335C10.5337 12.7195 10.7667 12.7495 10.8647 12.7495C10.9627 12.7495 11.1957 12.7195 11.2567 12.4335L11.6147 10.7665C11.7212 10.272 11.9681 9.81878 12.3256 9.46103C12.6832 9.10329 13.1363 8.85624 13.6307 8.7495L15.2977 8.3925C15.5837 8.3305 15.6147 8.0975 15.6147 7.9995C15.6147 7.9015 15.5837 7.6685 15.2977 7.6065L13.6307 7.2495C13.1365 7.14283 12.6834 6.89593 12.3259 6.53837C11.9683 6.18082 11.7214 5.72777 11.6147 5.2335L11.2567 3.5655C11.1957 3.2795 10.9627 3.2495 10.8647 3.2495ZM3.30873 12.1505L3.49073 11.3015C3.57673 10.8995 4.15073 10.8995 4.23773 11.3015L4.41973 12.1505C4.43509 12.2224 4.47093 12.2883 4.52292 12.3403C4.57491 12.3923 4.64083 12.4281 4.71273 12.4435L5.56173 12.6255C5.96373 12.7115 5.96373 13.2855 5.56173 13.3725L4.71273 13.5545C4.64083 13.5699 4.57491 13.6057 4.52292 13.6577C4.47093 13.7097 4.43509 13.7756 4.41973 13.8475L4.23773 14.6965C4.15173 15.0985 3.57773 15.0985 3.49073 14.6965L3.30873 13.8475C3.29337 13.7756 3.25754 13.7097 3.20555 13.6577C3.15356 13.6057 3.08764 13.5699 3.01573 13.5545L2.16673 13.3725C1.76473 13.2865 1.76473 12.7125 2.16673 12.6255L3.01573 12.4435C3.08769 12.4283 3.15368 12.3925 3.20568 12.3405C3.25769 12.2884 3.29349 12.2225 3.30873 12.1505Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

View file

@ -0,0 +1,16 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg id="loading-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12" width="12" height="12">
<style>
@keyframes loadingSVGRotate {
from { rotate: 0; } to { rotate: 360deg }
}
#loading-svg {
animation: loadingSVGRotate 1.2s linear infinite;
transform-origin: 50% 50%;
}
</style>
<path d="M8.9 3.8c-.2-.2-.1-.5.1-.7.2-.1.6-.1.7.2.5.7.8 1.6.8 2.5 0 2.5-2 4.5-4.5 4.5l0 1.5c0 .2-.2.3-.3.1l-2-1.9 0-.4 1.9-1.9c.2-.2.4-.1.4.1l0 1.5c1.9 0 3.5-1.6 3.5-3.5 0-.7-.2-1.4-.6-2z"/>
<path d="M3.1 8.2c.2.2.1.5-.1.7-.2.1-.6.1-.7-.2-.5-.7-.8-1.6-.8-2.5 0-2.5 2-4.5 4.5-4.5L6 .2c0-.2.2-.3.3-.1l2 1.9 0 .4-2 2c-.1.1-.3 0-.3-.2l0-1.5c-1.9 0-3.5 1.6-3.5 3.5 0 .7.2 1.4.6 2z"/>
</svg>

After

Width:  |  Height:  |  Size: 926 B

View file

@ -0,0 +1,3 @@
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.78182 2.63903C8.58882 2.28803 8.25782 2.25003 8.12482 2.25003C7.99019 2.24847 7.85771 2.28393 7.74185 2.35253C7.62599 2.42113 7.5312 2.52023 7.46782 2.63903L1.97082 12.639C1.90673 12.7528 1.87406 12.8816 1.87617 13.0122C1.87828 13.1427 1.91509 13.2704 1.98282 13.382C2.04798 13.4951 2.14207 13.5888 2.25543 13.6535C2.36879 13.7182 2.49732 13.7515 2.62782 13.75H13.6218C13.7523 13.7515 13.8809 13.7182 13.9942 13.6535C14.1076 13.5888 14.2017 13.4951 14.2668 13.382C14.3346 13.2704 14.3714 13.1427 14.3735 13.0122C14.3756 12.8816 14.3429 12.7528 14.2788 12.639L8.78182 2.63903ZM6.37282 2.03703C6.75182 1.34603 7.43882 1.00003 8.12482 1.00003C8.48341 0.997985 8.83583 1.09326 9.14454 1.2757C9.45325 1.45814 9.70668 1.72092 9.87782 2.03603L15.3748 12.036C16.1078 13.369 15.1438 15 13.6228 15H2.62782C1.10682 15 0.141823 13.37 0.875823 12.037L6.37282 2.03703ZM8.74982 9.06203C8.74982 9.22779 8.68397 9.38676 8.56676 9.50397C8.44955 9.62118 8.29058 9.68703 8.12482 9.68703C7.95906 9.68703 7.80009 9.62118 7.68288 9.50397C7.56566 9.38676 7.49982 9.22779 7.49982 9.06203V5.62503C7.49982 5.45927 7.56566 5.3003 7.68288 5.18309C7.80009 5.06588 7.95906 5.00003 8.12482 5.00003C8.29058 5.00003 8.44955 5.06588 8.56676 5.18309C8.68397 5.3003 8.74982 5.45927 8.74982 5.62503V9.06203ZM7.74982 12L7.49982 11.75V11L7.74982 10.75H8.49982L8.74982 11V11.75L8.49982 12H7.74982Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.85822 8.84922L4.85322 11.8542C4.75891 11.9453 4.63261 11.9957 4.50151 11.9946C4.37042 11.9934 4.24501 11.9408 4.15231 11.8481C4.0596 11.7554 4.00702 11.63 4.00588 11.4989C4.00474 11.3678 4.05514 11.2415 4.14622 11.1472L7.15122 8.14222V7.85922L4.14622 4.85322C4.05514 4.75891 4.00474 4.63261 4.00588 4.50151C4.00702 4.37042 4.0596 4.24501 4.15231 4.15231C4.24501 4.0596 4.37042 4.00702 4.50151 4.00588C4.63261 4.00474 4.75891 4.05514 4.85322 4.14622L7.85822 7.15122H8.14122L11.1462 4.14622C11.2405 4.05514 11.3668 4.00474 11.4979 4.00588C11.629 4.00702 11.7544 4.0596 11.8471 4.15231C11.9398 4.24501 11.9924 4.37042 11.9936 4.50151C11.9947 4.63261 11.9443 4.75891 11.8532 4.85322L8.84822 7.85922V8.14222L11.8532 11.1472C11.9443 11.2415 11.9947 11.3678 11.9936 11.4989C11.9924 11.63 11.9398 11.7554 11.8471 11.8481C11.7544 11.9408 11.629 11.9934 11.4979 11.9946C11.3668 11.9957 11.2405 11.9453 11.1462 11.8542L8.14122 8.84922L8.14222 8.85022L7.85822 8.84922Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.8748 12.037L9.37782 2.037C8.99682 1.346 8.31082 1 7.62482 1C6.93882 1 6.25282 1.346 5.87282 2.037L0.375823 12.037C-0.358177 13.37 0.606823 15 2.12782 15H13.1228C14.6428 15 15.6078 13.37 14.8748 12.037ZM8.24982 11.75L7.99982 12H7.24982L6.99982 11.75V11L7.24982 10.75H7.99982L8.24982 11V11.75ZM8.24982 9.062C8.24982 9.22776 8.18398 9.38673 8.06677 9.50394C7.94955 9.62115 7.79058 9.687 7.62482 9.687C7.45906 9.687 7.30009 9.62115 7.18288 9.50394C7.06567 9.38673 6.99982 9.22776 6.99982 9.062V5.625C6.99982 5.45924 7.06567 5.30027 7.18288 5.18306C7.30009 5.06585 7.45906 5 7.62482 5C7.79058 5 7.94955 5.06585 8.06677 5.18306C8.18398 5.30027 8.24982 5.45924 8.24982 5.625V9.062Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 811 B

399
web/new_alt_text_manager.js Normal file
View file

@ -0,0 +1,399 @@
/* Copyright 2024 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 NewAltTextManager {
#boundCancel = this.#cancel.bind(this);
#createAutomaticallyButton;
#currentEditor = null;
#cancelButton;
#descriptionContainer;
#dialog;
#disclaimer;
#firstTime = false;
#guessedAltText;
#isEditing = null;
#imagePreview;
#imageData;
#isAILoading = false;
#wasAILoading = false;
#learnMore;
#notNowButton;
#overlayManager;
#textarea;
#title;
#uiManager;
#previousAltText = null;
#telemetryData = null;
constructor(
{
descriptionContainer,
dialog,
imagePreview,
cancelButton,
disclaimer,
notNowButton,
saveButton,
textarea,
learnMore,
errorCloseButton,
createAutomaticallyButton,
title,
},
overlayManager
) {
this.#cancelButton = cancelButton;
this.#createAutomaticallyButton = createAutomaticallyButton;
this.#descriptionContainer = descriptionContainer;
this.#dialog = dialog;
this.#disclaimer = disclaimer;
this.#notNowButton = notNowButton;
this.#imagePreview = imagePreview;
this.#textarea = textarea;
this.#learnMore = learnMore;
this.#title = title;
this.#overlayManager = overlayManager;
dialog.addEventListener("close", this.#close.bind(this));
dialog.addEventListener("contextmenu", event => {
if (event.target !== this.#textarea) {
event.preventDefault();
}
});
cancelButton.addEventListener("click", this.#boundCancel);
notNowButton.addEventListener("click", this.#boundCancel);
saveButton.addEventListener("click", this.#save.bind(this));
errorCloseButton.addEventListener("click", () => {
this.#toggleError(false);
});
createAutomaticallyButton.addEventListener("click", async () => {
const checked =
createAutomaticallyButton.getAttribute("aria-pressed") !== "true";
if (this.#uiManager) {
this.#uiManager.setPreference("enableGuessAltText", checked);
await this.#uiManager.mlManager.toggleService("altText", checked);
}
this.#toggleGuessAltText(checked, /* isInitial = */ false);
});
textarea.addEventListener("focus", () => {
this.#wasAILoading = this.#isAILoading;
this.#toggleLoading(false);
});
textarea.addEventListener("blur", () => {
if (textarea.value) {
return;
}
this.#toggleLoading(this.#wasAILoading);
});
textarea.addEventListener("input", () => {
this.#toggleTitle();
this.#toggleDisclaimer();
});
this.#overlayManager.register(dialog);
}
#toggleLoading(value) {
if (!this.#uiManager || this.#isAILoading === value) {
return;
}
this.#isAILoading = value;
this.#descriptionContainer.classList.toggle("loading", value);
}
#toggleError(value) {
if (!this.#uiManager) {
return;
}
this.#dialog.classList.toggle("error", value);
}
#toggleTitle() {
const isEditing = this.#isAILoading || !!this.#textarea.value;
if (this.#isEditing === isEditing) {
return;
}
this.#isEditing = isEditing;
this.#title.setAttribute(
"data-l10n-id",
`pdfjs-editor-new-alt-text-dialog-${isEditing ? "edit" : "add"}-label`
);
}
async #toggleGuessAltText(value, isInitial = false) {
if (!this.#uiManager) {
return;
}
this.#dialog.classList.toggle("aiDisabled", !value);
this.#createAutomaticallyButton.setAttribute("aria-pressed", value);
if (value) {
const { altTextLearnMoreUrl } = this.#uiManager.mlManager;
if (altTextLearnMoreUrl) {
this.#learnMore.href = altTextLearnMoreUrl;
}
this.#mlGuessAltText(isInitial);
} else {
this.#toggleLoading(false);
this.#isAILoading = false;
this.#toggleTitle();
this.#toggleDisclaimer();
}
}
#toggleNotNow() {
this.#notNowButton.classList.toggle("hidden", !this.#firstTime);
this.#cancelButton.classList.toggle("hidden", this.#firstTime);
}
#toggleAI(value) {
this.#dialog.classList.toggle("noAi", !value);
this.#toggleTitle();
}
#toggleDisclaimer(value = null) {
if (!this.#uiManager) {
return;
}
const hidden =
value === null
? !this.#guessedAltText || this.#guessedAltText !== this.#textarea.value
: !value;
this.#disclaimer.classList.toggle("hidden", hidden);
}
async #mlGuessAltText(isInitial) {
if (this.#isAILoading) {
// We're still loading the previous guess.
return;
}
if (this.#textarea.value) {
// The user has already set an alt text.
return;
}
if (isInitial && this.#previousAltText !== null) {
// The user has already set an alt text (empty or not).
return;
}
this.#guessedAltText = this.#currentEditor.guessedAltText;
if (this.#previousAltText === null && this.#guessedAltText) {
// We have a guessed alt text and the user didn't change it.
this.#addAltText(this.#guessedAltText);
this.#toggleDisclaimer();
this.#toggleTitle();
return;
}
this.#toggleLoading(true);
this.#toggleTitle();
this.#toggleDisclaimer(true);
let hasError = false;
try {
const { width, height, data } = this.#imageData;
// Take a reference on the current editor, as it can be set to null (if
// the dialog is closed before the end of the guess).
// But in case we've an alt-text, we want to set it on the editor.
const editor = this.#currentEditor;
// 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",
request: {
data,
width,
height,
channels: data.length / (width * height),
},
});
if (!response || response.error || !response.output) {
throw new Error("No valid response from the AI service.");
}
const altText = (this.#guessedAltText = response.output);
await editor.setGuessedAltText(altText);
this.#wasAILoading = this.#isAILoading;
if (this.#isAILoading) {
this.#addAltText(altText);
}
} catch (e) {
console.error(e);
hasError = true;
}
this.#toggleLoading(false);
if (hasError && this.#uiManager) {
this.#toggleError(true);
this.#toggleTitle();
this.#toggleDisclaimer();
}
}
#addAltText(altText) {
if (!this.#uiManager || this.#textarea.value) {
return;
}
this.#textarea.value = altText;
}
async editAltText(uiManager, editor, firstTime) {
if (this.#currentEditor || !editor) {
return;
}
if (firstTime && editor.hasAltTextData()) {
editor.altTextFinish();
return;
}
this.#firstTime = firstTime;
let { mlManager } = uiManager;
if (!mlManager?.isReady("altText")) {
mlManager = null;
}
const isAltTextEnabledPromise = mlManager?.isEnabledFor("altText");
this.#currentEditor = editor;
this.#uiManager = uiManager;
this.#uiManager.removeEditListeners();
({ altText: this.#previousAltText } = editor.altTextData);
this.#textarea.value = this.#previousAltText ?? "";
// TODO: get this value from Firefox
// (https://bugzilla.mozilla.org/show_bug.cgi?id=1908184)
const AI_MAX_IMAGE_DIMENSION = 224;
// The max dimension of the preview in the dialog is 180px, so we keep 224px
// and rescale it thanks to css.
let canvas;
if (mlManager) {
({ canvas, imageData: this.#imageData } = editor.copyCanvas(
AI_MAX_IMAGE_DIMENSION,
/* createImageData = */ true
));
this.#toggleGuessAltText(
await isAltTextEnabledPromise,
/* isInitial = */ true
);
} else {
({ canvas } = editor.copyCanvas(
AI_MAX_IMAGE_DIMENSION,
/* createImageData = */ false
));
}
canvas.setAttribute("role", "presentation");
this.#imagePreview.append(canvas);
this.#toggleNotNow();
this.#toggleAI(!!mlManager);
this.#toggleError(false);
try {
await this.#overlayManager.open(this.#dialog);
} catch (ex) {
this.#close();
throw ex;
}
}
#cancel() {
this.#currentEditor.altTextData = {
cancel: true,
};
this.#finish();
}
#finish() {
if (this.#overlayManager.active === this.#dialog) {
this.#overlayManager.close(this.#dialog);
}
}
#close() {
const canvas = this.#imagePreview.firstChild;
canvas.remove();
canvas.width = canvas.height = 0;
this.#imageData = null;
this.#currentEditor._reportTelemetry(
this.#telemetryData || {
action: "alt_text_cancel",
}
);
this.#telemetryData = null;
this.#toggleLoading(false);
this.#uiManager?.addEditListeners();
this.#currentEditor.altTextFinish();
this.#uiManager?.setSelected(this.#currentEditor);
this.#currentEditor = null;
this.#uiManager = null;
}
#save() {
const altText = this.#textarea.value.trim();
this.#currentEditor.altTextData = {
altText,
decorative: false,
};
this.#telemetryData = {
action: "alt_text_save",
alt_text_description: !!altText,
alt_text_edit:
!!this.#previousAltText && this.#previousAltText !== altText,
alt_text_decorative: false,
alt_text_altered:
this.#guessedAltText && this.#guessedAltText !== altText,
};
this.#finish();
}
destroy() {
this.#uiManager = null; // Avoid re-adding the edit listeners.
this.#finish();
}
}
export { NewAltTextManager };

View file

@ -15,6 +15,7 @@
const AltTextManager = null;
const AnnotationEditorParams = null;
const NewAltTextManager = null;
const PDFAttachmentViewer = null;
const PDFCursorTools = null;
const PDFDocumentProperties = null;
@ -29,6 +30,7 @@ const SecondaryToolbar = null;
export {
AltTextManager,
AnnotationEditorParams,
NewAltTextManager,
PDFAttachmentViewer,
PDFCursorTools,
PDFDocumentProperties,

View file

@ -68,6 +68,7 @@ See https://github.com/adobe-type-tools/cmap-resources
"web-annotation_editor_params": "./stubs-geckoview.js",
"web-download_manager": "./download_manager.js",
"web-external_services": "./genericcom.js",
"web-new_alt_text_manager": "./stubs-geckoview.js",
"web-null_l10n": "./genericl10n.js",
"web-pdf_attachment_viewer": "./stubs-geckoview.js",
"web-pdf_cursor_tools": "./stubs-geckoview.js",

View file

@ -71,6 +71,7 @@ See https://github.com/adobe-type-tools/cmap-resources
"web-annotation_editor_params": "./annotation_editor_params.js",
"web-download_manager": "./download_manager.js",
"web-external_services": "./genericcom.js",
"web-new_alt_text_manager": "./new_alt_text_manager.js",
"web-null_l10n": "./genericl10n.js",
"web-pdf_attachment_viewer": "./pdf_attachment_viewer.js",
"web-pdf_cursor_tools": "./pdf_cursor_tools.js",
@ -550,6 +551,48 @@ See https://github.com/adobe-type-tools/cmap-resources
</div>
</div>
</dialog>
<dialog class="dialog newAltText" id="newAltTextDialog" aria-labelledby="newAltTextTitle" aria-describedby="newAltTextDescription" tabindex="0">
<div id="newAltTextContainer" class="mainContainer">
<div class="title">
<span id="newAltTextTitle" data-l10n-id="pdfjs-editor-new-alt-text-dialog-edit-label" role="sectionhead" tabindex="0">Edit alt text (image description)</span>
</div>
<div id="mainContent">
<div id="descriptionAndSettings">
<div id="descriptionInstruction">
<div id="newAltTextDescriptionContainer">
<div class="altTextSpinner" role="status" aria-live="polite"></div>
<textarea id="newAltTextDescriptionTextarea" placeholder="Write your description here…" aria-labelledby="descriptionAreaLabel" data-l10n-id="pdfjs-editor-new-alt-text-textarea" tabindex="0"></textarea>
</div>
<span id="newAltTextDescription" role="note" data-l10n-id="pdfjs-editor-new-alt-text-description">Short description for people who cant see the image or when the image doesnt load.</span>
<div id="newAltTextDisclaimer" role="note"><span data-l10n-id="pdfjs-editor-new-alt-text-disclaimer">This alt text was created automatically.</span> <a href="https://support.mozilla.org/en-US/kb/pdf-alt-text" target="_blank" rel="noopener noreferrer" id="newAltTextLearnMore" data-l10n-id="pdfjs-editor-new-alt-text-disclaimer-learn-more-url" tabindex="0">Learn more</a></div>
</div>
<div id="newAltTextCreateAutomatically" class="toggler">
<button id="newAltTextCreateAutomaticallyButton" class="toggle-button" aria-pressed="true" tabindex="0"></button>
<label for="newAltTextCreateAutomaticallyButton" class="togglerLabel" data-l10n-id="pdfjs-editor-new-alt-text-create-automatically-button-label">Create alt text automatically</label>
</div>
<div id="newAltTextDownloadModel" class="hidden">
<span id="newAltTextDownloadModelDescription" data-l10n-id="pdfjs-editor-new-alt-text-ai-model-downloading-progress" data-l10n-args='{ "totalSize": 0, "downloadedSize": 0 }'>Downloading alt text AI model (0 of 0 MB)</span>
</div>
</div>
<div id="newAltTextImagePreview"></div>
</div>
<div id="newAltTextError" class="messageBar">
<div>
<div>
<span class="title" data-l10n-id="pdfjs-editor-new-alt-text-error-title">Couldnt create alt text automatically</span>
<span class="description" data-l10n-id="pdfjs-editor-new-alt-text-error-description">Please write your own alt text or try again later.</span>
</div>
<button id="newAltTextCloseButton" class="closeButton" tabindex="0" title="Close"><span data-l10n-id="pdfjs-editor-new-alt-text-error-close-button">Close</span></button>
</div>
</div>
<div id="newAltTextButtons" class="dialogButtonsGroup">
<button id="newAltTextCancel" type="button" class="secondaryButton hidden" tabindex="0"><span data-l10n-id="pdfjs-editor-alt-text-cancel-button">Cancel</span></button>
<button id="newAltTextNotNow" type="button" class="secondaryButton" tabindex="0"><span data-l10n-id="pdfjs-editor-new-alt-text-not-now-button">Not now</span></button>
<button id="newAltTextSave" type="button" class="primaryButton" tabindex="0"><span data-l10n-id="pdfjs-editor-alt-text-save-button">Save</span></button>
</div>
</div>
</dialog>
<!--#if !MOZCENTRAL-->
<dialog id="printServiceDialog" style="min-width: 200px;">
<div class="row">

View file

@ -162,6 +162,32 @@ function getViewerConfiguration() {
cancelButton: document.getElementById("altTextCancel"),
saveButton: document.getElementById("altTextSave"),
},
newAltTextDialog: {
dialog: document.getElementById("newAltTextDialog"),
title: document.getElementById("newAltTextTitle"),
descriptionContainer: document.getElementById(
"newAltTextDescriptionContainer"
),
textarea: document.getElementById("newAltTextDescriptionTextarea"),
disclaimer: document.getElementById("newAltTextDisclaimer"),
learnMore: document.getElementById("newAltTextLearnMore"),
imagePreview: document.getElementById("newAltTextImagePreview"),
createAutomatically: document.getElementById(
"newAltTextCreateAutomatically"
),
createAutomaticallyButton: document.getElementById(
"newAltTextCreateAutomaticallyButton"
),
downloadModel: document.getElementById("newAltTextDownloadModel"),
downloadModelDescription: document.getElementById(
"newAltTextDownloadModelDescription"
),
error: document.getElementById("newAltTextError"),
errorCloseButton: document.getElementById("newAltTextCloseButton"),
cancelButton: document.getElementById("newAltTextCancel"),
notNowButton: document.getElementById("newAltTextNotNow"),
saveButton: document.getElementById("newAltTextSave"),
},
annotationEditorParams: {
editorFreeTextFontSize: document.getElementById("editorFreeTextFontSize"),
editorFreeTextColor: document.getElementById("editorFreeTextColor"),