Merge pull request #16955 from calixteman/alt_text_button

[Editor] Add a button to trigger a dialog for adding an alt text (bug 1844952)
This commit is contained in:
calixteman 2023-09-18 15:50:10 +02:00 committed by GitHub
commit b85281914f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 304 additions and 26 deletions

View File

@ -263,3 +263,8 @@ editor_stamp_add_image.title=Add image
editor_free_text2_aria_label=Text Editor
editor_ink2_aria_label=Draw Editor
editor_ink_canvas_aria_label=User-created image
# Alt-text dialog
# LOCALIZATION NOTE (alt_text_button_label): Alternative text (alt text) helps
# when people can't see the image.
alt_text_button_label=Alt text

View File

@ -34,6 +34,8 @@ import { FeatureTest, shadow, unreachable } from "../../shared/util.js";
* Base class for editors.
*/
class AnnotationEditor {
#altTextButton = null;
#keepAspectRatio = false;
#resizersDiv = null;
@ -54,6 +56,8 @@ class AnnotationEditor {
_focusEventsAllowed = true;
_l10nPromise = null;
#isDraggable = false;
#zIndex = AnnotationEditor._zIndex++;
@ -64,6 +68,10 @@ class AnnotationEditor {
static _zIndex = 1;
// When one of the dimensions of an editor is smaller than this value, the
// button to edit the alt text is visually moved outside of the editor.
static SMALL_EDITOR_SIZE = 0;
/**
* @param {AnnotationEditorParameters} parameters
*/
@ -124,9 +132,17 @@ class AnnotationEditor {
/**
* Initialize the l10n stuff for this type of editor.
* @param {Object} _l10n
* @param {Object} l10n
*/
static initialize(_l10n) {
static initialize(l10n, options = null) {
AnnotationEditor._l10nPromise ||= new Map(
["alt_text_button_label"].map(str => [str, l10n.get(str)])
);
if (options?.strings) {
for (const str of options.strings) {
AnnotationEditor._l10nPromise.set(str, l10n.get(str));
}
}
if (AnnotationEditor._borderLineWidth !== -1) {
return;
}
@ -522,6 +538,11 @@ class AnnotationEditor {
if (!this.#keepAspectRatio) {
this.div.style.height = `${((100 * height) / parentHeight).toFixed(2)}%`;
}
this.#altTextButton?.classList.toggle(
"small",
width < AnnotationEditor.SMALL_EDITOR_SIZE ||
height < AnnotationEditor.SMALL_EDITOR_SIZE
);
}
fixDims() {
@ -785,6 +806,40 @@ class AnnotationEditor {
this.fixAndSetPosition();
}
addAltTextButton() {
if (this.#altTextButton) {
return;
}
const altText = (this.#altTextButton = document.createElement("span"));
altText.className = "altText";
AnnotationEditor._l10nPromise.get("alt_text_button_label").then(msg => {
altText.textContent = msg;
});
altText.tabIndex = "0";
altText.addEventListener(
"click",
event => {
event.preventDefault();
},
{ capture: true }
);
altText.addEventListener("keydown", event => {
if (event.target === altText && event.key === "Enter") {
event.preventDefault();
}
});
this.div.append(altText);
if (!AnnotationEditor.SMALL_EDITOR_SIZE) {
// We take the width of the alt text button and we add 40% to it to be
// sure to have enough space for it.
const PERCENT = 40;
AnnotationEditor.SMALL_EDITOR_SIZE = Math.min(
128,
Math.round(altText.getBoundingClientRect().width * (1 + PERCENT / 100))
);
}
}
/**
* Render this editor in a div.
* @returns {HTMLDivElement}
@ -1144,13 +1199,21 @@ class AnnotationEditor {
* When the user disables the editing mode some editors can change some of
* their properties.
*/
disableEditing() {}
disableEditing() {
if (this.#altTextButton) {
this.#altTextButton.hidden = true;
}
}
/**
* When the user enables the editing mode some editors can change some of
* their properties.
*/
enableEditing() {}
enableEditing() {
if (this.#altTextButton) {
this.#altTextButton.hidden = false;
}
}
/**
* The editor is about to be edited.

View File

@ -56,8 +56,6 @@ class FreeTextEditor extends AnnotationEditor {
static _freeTextDefaultContent = "";
static _l10nPromise;
static _internalPadding = 0;
static _defaultColor = null;
@ -145,13 +143,9 @@ class FreeTextEditor extends AnnotationEditor {
/** @inheritdoc */
static initialize(l10n) {
super.initialize(l10n);
this._l10nPromise = new Map(
["free_text2_default_content", "editor_free_text2_aria_label"].map(
str => [str, l10n.get(str)]
)
);
AnnotationEditor.initialize(l10n, {
strings: ["free_text2_default_content", "editor_free_text2_aria_label"],
});
const style = getComputedStyle(document.documentElement);
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
@ -548,11 +542,11 @@ class FreeTextEditor extends AnnotationEditor {
this.editorDiv.setAttribute("id", this.#editorDivId);
this.enableEditing();
FreeTextEditor._l10nPromise
AnnotationEditor._l10nPromise
.get("editor_free_text2_aria_label")
.then(msg => this.editorDiv?.setAttribute("aria-label", msg));
FreeTextEditor._l10nPromise
AnnotationEditor._l10nPromise
.get("free_text2_default_content")
.then(msg => this.editorDiv?.setAttribute("default-content", msg));
this.editorDiv.contentEditable = true;

View File

@ -62,8 +62,6 @@ class InkEditor extends AnnotationEditor {
static _defaultThickness = 1;
static _l10nPromise;
static _type = "ink";
constructor(params) {
@ -84,13 +82,9 @@ class InkEditor extends AnnotationEditor {
/** @inheritdoc */
static initialize(l10n) {
super.initialize(l10n);
this._l10nPromise = new Map(
["editor_ink_canvas_aria_label", "editor_ink2_aria_label"].map(str => [
str,
l10n.get(str),
])
);
AnnotationEditor.initialize(l10n, {
strings: ["editor_ink_canvas_aria_label", "editor_ink2_aria_label"],
});
}
/** @inheritdoc */
@ -743,7 +737,7 @@ class InkEditor extends AnnotationEditor {
this.canvas.width = this.canvas.height = 0;
this.canvas.className = "inkEditorCanvas";
InkEditor._l10nPromise
AnnotationEditor._l10nPromise
.get("editor_ink_canvas_aria_label")
.then(msg => this.canvas?.setAttribute("aria-label", msg));
this.div.append(this.canvas);
@ -782,7 +776,7 @@ class InkEditor extends AnnotationEditor {
super.render();
InkEditor._l10nPromise
AnnotationEditor._l10nPromise
.get("editor_ink2_aria_label")
.then(msg => this.div?.setAttribute("aria-label", msg));

View File

@ -50,6 +50,11 @@ class StampEditor extends AnnotationEditor {
this.#bitmapFile = params.bitmapFile;
}
/** @inheritdoc */
static initialize(l10n) {
AnnotationEditor.initialize(l10n);
}
static get supportedTypes() {
// See https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types
// to know which types are supported by the browser.
@ -306,6 +311,7 @@ class StampEditor extends AnnotationEditor {
this.parent.addUndoableEditor(this);
this.#hasBeenAddedInUndoStack = true;
}
this.addAltTextButton();
}
/**

View File

@ -741,6 +741,14 @@ class AnnotationEditorUIManager {
);
}
get direction() {
return shadow(
this,
"direction",
getComputedStyle(this.#container).direction
);
}
onPageChanging({ pageNumber }) {
this.#currentPageIndex = pageNumber - 1;
}

View File

@ -37,6 +37,22 @@
/*#else*/
--editorInk-editing-cursor: url(images/cursor-editorInk.svg) 0 16, pointer;
/*#endif*/
--alt-text-add-image: url(images/altText_add.svg);
--alt-text-done-image: url(images/altText_done.svg);
--alt-text-bg-color: #2b2a33;
--alt-text-fg-color: #fbfbfe;
--alt-text-border-color: var(--alt-text-bg-color);
--alt-text-hover-bg-color: #52525e;
--alt-text-hover-fg-color: var(--alt-text-fg-color);
--alt-text-hover-border-color: var(--alt-text-hover-bg-color);
--alt-text-active-bg-color: #5b5b66;
--alt-text-active-fg-color: var(--alt-text-fg-color);
--alt-text-active-border-color: var(--alt-text-hover-bg-color);
--alt-text-focus-outline-color: #0060df;
--alt-text-focus-border-color: #f0f0f4;
--alt-text-shadow: 0 2px 6px 0 rgba(28, 27, 34, 0.5);
--alt-text-opacity: 0.8;
}
@media (min-resolution: 1.1dppx) {
@ -53,6 +69,20 @@
--outline-color: CanvasText;
--outline-around-color: ButtonFace;
--resizer-bg-color: ButtonText;
--alt-text-bg-color: Canvas;
--alt-text-fg-color: ButtonText;
--alt-text-border-color: ButtonText;
--alt-text-hover-bg-color: Canvas;
--alt-text-hover-fg-color: SelectedItem;
--alt-text-hover-border-color: SelectedItem;
--alt-text-active-bg-color: ButtonFace;
--alt-text-active-fg-color: SelectedItem;
--alt-text-active-border-color: ButtonText;
--alt-text-focus-outline-color: CanvasText;
--alt-text-focus-border-color: ButtonText;
--alt-text-shadow: none;
--alt-text-opacity: 1;
}
}
@ -331,4 +361,174 @@
}
}
}
&
:is(
[data-main-rotation="0"] [data-editor-rotation="90"],
[data-main-rotation="90"] [data-editor-rotation="0"],
[data-main-rotation="180"] [data-editor-rotation="270"],
[data-main-rotation="270"] [data-editor-rotation="180"],
) {
& .altText {
rotate: 270deg;
&:dir(ltr) {
inset-inline-start: calc(100% - 8px);
&.small {
inset-inline-start: calc(100% + 8px);
inset-block-start: 100%;
}
}
&:dir(rtl) {
inset-block-end: calc(100% - 8px);
&.small {
inset-inline-start: -8px;
inset-block-start: 0;
}
}
}
}
&
:is(
[data-main-rotation="0"] [data-editor-rotation="180"],
[data-main-rotation="90"] [data-editor-rotation="90"],
[data-main-rotation="180"] [data-editor-rotation="0"],
[data-main-rotation="270"] [data-editor-rotation="270"],
) {
& .altText {
rotate: 180deg;
inset-block-end: calc(100% - 8px);
inset-inline-start: calc(100% - 8px);
&.small {
inset-inline-start: 100%;
inset-block-start: -8px;
}
}
}
&
:is(
[data-main-rotation="0"] [data-editor-rotation="270"],
[data-main-rotation="90"] [data-editor-rotation="180"],
[data-main-rotation="180"] [data-editor-rotation="90"],
[data-main-rotation="270"] [data-editor-rotation="0"],
) {
& .altText {
rotate: 90deg;
&:dir(ltr) {
inset-block-end: calc(100% - 8px);
&.small {
inset-inline-start: -8px;
inset-block-start: 0;
}
}
&:dir(rtl) {
inset-inline-start: calc(100% - 8px);
&.small {
inset-inline-start: calc(100% + 8px);
inset-block-start: 100%;
}
}
}
}
}
.altText {
display: flex;
align-items: center;
justify-content: center;
width: auto;
height: 24px;
min-width: 88px;
z-index: 1;
pointer-events: all;
color: var(--alt-text-fg-color);
font: menu;
font-size: 12px;
border-radius: 4px;
border: 1px solid var(--alt-text-border-color);
opacity: var(--alt-text-opacity);
background-color: var(--alt-text-bg-color);
box-shadow: var(--alt-text-shadow);
position: absolute;
inset-block-end: 8px;
inset-inline-start: 8px;
&:dir(ltr) {
transform-origin: 0 100%;
}
&:dir(rtl) {
transform-origin: 100% 100%;
}
&.small {
&:dir(ltr) {
transform-origin: 0 0;
}
&:dir(rtl) {
transform-origin: 100% 0;
}
inset-block-end: unset;
inset-inline-start: 0;
inset-block-start: calc(100% + 8px);
}
&:hover {
background-color: var(--alt-text-hover-bg-color);
border-color: var(--alt-text-hover-border-color);
color: var(--alt-text-hover-fg-color);
cursor: pointer;
&::before {
background-color: var(--alt-text-hover-fg-color);
}
}
&:active {
background-color: var(--alt-text-active-bg-color);
border-color: var(--alt-text-active-border-color);
color: var(--alt-text-active-fg-color);
&::before {
background-color: var(--alt-text-active-fg-color);
}
}
&:focus-visible {
outline: 2px solid var(--alt-text-focus-outline-color);
border-color: var(--alt-text-focus-border-color);
}
&::before {
content: "";
mask-image: var(--alt-text-add-image);
mask-size: cover;
mask-repeat: no-repeat;
mask-position: center;
display: inline-block;
width: 12px;
height: 12px;
background-color: var(--alt-text-fg-color);
margin-inline-end: 4px;
}
&.done::before {
mask-image: var(--alt-text-done-image);
}
}

View File

@ -82,6 +82,7 @@ class AnnotationEditorLayerBuilder {
div.className = "annotationEditorLayer";
div.tabIndex = 0;
div.hidden = true;
div.dir = this.#uiManager.direction;
this.pageDiv.append(div);
this.annotationEditorLayer = new AnnotationEditorLayer({

View File

@ -0,0 +1,3 @@
<svg width="12" height="13" viewBox="0 0 12 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.375 7.625V11.875C5.375 12.0408 5.44085 12.1997 5.55806 12.3169C5.67527 12.4342 5.83424 12.5 6 12.5C6.16576 12.5 6.32473 12.4342 6.44194 12.3169C6.55915 12.1997 6.625 12.0408 6.625 11.875V7.625L7.125 7.125H11.375C11.5408 7.125 11.6997 7.05915 11.8169 6.94194C11.9342 6.82473 12 6.66576 12 6.5C12 6.33424 11.9342 6.17527 11.8169 6.05806C11.6997 5.94085 11.5408 5.875 11.375 5.875H7.125L6.625 5.375V1.125C6.625 0.95924 6.55915 0.800269 6.44194 0.683058C6.32473 0.565848 6.16576 0.5 6 0.5C5.83424 0.5 5.67527 0.565848 5.55806 0.683058C5.44085 0.800269 5.375 0.95924 5.375 1.125V5.375L4.875 5.875H0.625C0.45924 5.875 0.300269 5.94085 0.183058 6.05806C0.065848 6.17527 0 6.33424 0 6.5C0 6.66576 0.065848 6.82473 0.183058 6.94194C0.300269 7.05915 0.45924 7.125 0.625 7.125H4.762L5.375 7.625Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 920 B

View File

@ -0,0 +1,3 @@
<svg width="12" height="13" viewBox="0 0 12 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 0.5C5.21207 0.5 4.43185 0.655195 3.7039 0.956723C2.97595 1.25825 2.31451 1.70021 1.75736 2.25736C1.20021 2.81451 0.758251 3.47595 0.456723 4.2039C0.155195 4.93185 0 5.71207 0 6.5C0 7.28793 0.155195 8.06815 0.456723 8.7961C0.758251 9.52405 1.20021 10.1855 1.75736 10.7426C2.31451 11.2998 2.97595 11.7417 3.7039 12.0433C4.43185 12.3448 5.21207 12.5 6 12.5C7.5913 12.5 9.11742 11.8679 10.2426 10.7426C11.3679 9.61742 12 8.0913 12 6.5C12 4.9087 11.3679 3.38258 10.2426 2.25736C9.11742 1.13214 7.5913 0.5 6 0.5ZM5.06 8.9L2.9464 6.7856C2.85273 6.69171 2.80018 6.56446 2.80033 6.43183C2.80048 6.29921 2.85331 6.17207 2.9472 6.0784C3.04109 5.98473 3.16834 5.93218 3.30097 5.93233C3.43359 5.93248 3.56073 5.98531 3.6544 6.0792L5.3112 7.7368L8.3464 4.7008C8.44109 4.6109 8.56715 4.56153 8.69771 4.56322C8.82827 4.56492 8.95301 4.61754 9.04534 4.70986C9.13766 4.80219 9.19028 4.92693 9.19198 5.05749C9.19367 5.18805 9.1443 5.31411 9.0544 5.4088L5.5624 8.9H5.06Z" fill="#FBFBFE"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -82,6 +82,7 @@ const DEFAULT_L10N_STRINGS = {
editor_free_text2_aria_label: "Text Editor",
editor_ink2_aria_label: "Draw Editor",
editor_ink_canvas_aria_label: "User-created image",
alt_text_button_label: "Alt text",
};
if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) {
DEFAULT_L10N_STRINGS.print_progress_percent = "{{progress}}%";