diff --git a/l10n/en-US/viewer.ftl b/l10n/en-US/viewer.ftl index 07efee7e9..fa1ea61dc 100644 --- a/l10n/en-US/viewer.ftl +++ b/l10n/en-US/viewer.ftl @@ -533,6 +533,14 @@ pdfjs-editor-alt-text-settings-show-dialog-button-label = Show alt text editor r pdfjs-editor-alt-text-settings-show-dialog-description = Helps you make sure all your images have alt text. pdfjs-editor-alt-text-settings-close-button = Close +## Accessibility labels (announced by screen readers) for objects added to the editor. + +pdfjs-editor-highlight-added-alert = Highlight added +pdfjs-editor-freetext-added-alert = Text added +pdfjs-editor-ink-added-alert = Drawing added +pdfjs-editor-stamp-added-alert = Image added +pdfjs-editor-signature-added-alert = Signature added + ## "Annotations removed" bar pdfjs-editor-undo-bar-message-highlight = Highlight removed diff --git a/src/display/editor/draw.js b/src/display/editor/draw.js index e8c2e8ba4..25d05508a 100644 --- a/src/display/editor/draw.js +++ b/src/display/editor/draw.js @@ -106,6 +106,9 @@ class DrawingEditor extends AnnotationEditor { #createDrawOutlines({ drawOutlines, drawId, drawingOptions }) { this.#drawOutlines = drawOutlines; this._drawingOptions ||= drawingOptions; + if (!this.annotationElementId) { + this._uiManager.a11yAlert(`pdfjs-editor-${this.editorType}-added-alert`); + } if (drawId >= 0) { this._drawId = drawId; diff --git a/src/display/editor/editor.js b/src/display/editor/editor.js index a0060d8cb..4714c9a78 100644 --- a/src/display/editor/editor.js +++ b/src/display/editor/editor.js @@ -180,6 +180,7 @@ class AnnotationEditor { this._willKeepAspectRatio = false; this._initialOptions.isCentered = parameters.isCentered; this._structTreeParentId = null; + this.annotationElementId = parameters.annotationElementId || null; const { rotation, @@ -427,6 +428,9 @@ class AnnotationEditor { * Commit the data contained in this editor. */ commit() { + if (!this.isInEditMode()) { + return; + } this.addToAnnotationStorage(); } @@ -1641,6 +1645,7 @@ class AnnotationEditor { parent, id: parent.getNextId(), uiManager, + annotationElementId: data.annotationElementId, }); editor.rotation = data.rotation; editor.#accessibilityData = data.accessibilityData; diff --git a/src/display/editor/freetext.js b/src/display/editor/freetext.js index 15969a2f8..4daccee89 100644 --- a/src/display/editor/freetext.js +++ b/src/display/editor/freetext.js @@ -131,6 +131,9 @@ class FreeTextEditor extends AnnotationEditor { FreeTextEditor._defaultColor || AnnotationEditor._defaultLineColor; this.#fontSize = params.fontSize || FreeTextEditor._defaultFontSize; + if (!this.annotationElementId) { + this._uiManager.a11yAlert("pdfjs-editor-freetext-added-alert"); + } } /** @inheritdoc */ @@ -782,6 +785,7 @@ class FreeTextEditor extends AnnotationEditor { pageIndex: pageNumber - 1, rect: rect.slice(0), rotation, + annotationElementId: id, id, deleted: false, popupRef, @@ -791,7 +795,6 @@ class FreeTextEditor extends AnnotationEditor { editor.#fontSize = data.fontSize; editor.#color = Util.makeHexColor(...data.color); editor.#content = FreeTextEditor.#deserializeContent(data.value); - editor.annotationElementId = data.id || null; editor._initialData = initialData; return editor; diff --git a/src/display/editor/highlight.js b/src/display/editor/highlight.js index 3a0305b7d..e909d91ce 100644 --- a/src/display/editor/highlight.js +++ b/src/display/editor/highlight.js @@ -126,6 +126,10 @@ class HighlightEditor extends AnnotationEditor { this.#addToDrawLayer(); this.rotate(this.rotation); } + + if (!this.annotationElementId) { + this._uiManager.a11yAlert("pdfjs-editor-highlight-added-alert"); + } } /** @inheritdoc */ @@ -876,6 +880,7 @@ class HighlightEditor extends AnnotationEditor { pageIndex: pageNumber - 1, rect: rect.slice(0), rotation, + annotationElementId: id, id, deleted: false, popupRef, @@ -904,6 +909,7 @@ class HighlightEditor extends AnnotationEditor { pageIndex: pageNumber - 1, rect: rect.slice(0), rotation, + annotationElementId: id, id, deleted: false, popupRef, @@ -918,7 +924,6 @@ class HighlightEditor extends AnnotationEditor { if (inkLists) { editor.#thickness = data.thickness; } - editor.annotationElementId = data.id || null; editor._initialData = initialData; const [pageWidth, pageHeight] = editor.pageDimensions; diff --git a/src/display/editor/ink.js b/src/display/editor/ink.js index b15ac6253..56f6c560a 100644 --- a/src/display/editor/ink.js +++ b/src/display/editor/ink.js @@ -164,6 +164,7 @@ class InkEditor extends DrawingEditor { pageIndex: pageNumber - 1, rect: rect.slice(0), rotation, + annotationElementId: id, id, deleted: false, popupRef, @@ -171,7 +172,6 @@ class InkEditor extends DrawingEditor { } const editor = await super.deserialize(data, parent, uiManager); - editor.annotationElementId = data.id || null; editor._initialData = initialData; return editor; diff --git a/src/display/editor/stamp.js b/src/display/editor/stamp.js index 169653622..cd46889d4 100644 --- a/src/display/editor/stamp.js +++ b/src/display/editor/stamp.js @@ -484,6 +484,9 @@ class StampEditor extends AnnotationEditor { if (this.#bitmapFileName) { this.div.setAttribute("aria-description", this.#bitmapFileName); } + if (!this.annotationElementId) { + this._uiManager.a11yAlert("pdfjs-editor-stamp-added-alert"); + } } copyCanvas(maxDataDimension, maxPreviewDimension, createImageData = false) { @@ -781,6 +784,7 @@ class StampEditor extends AnnotationEditor { pageIndex: pageNumber - 1, rect: rect.slice(0), rotation, + annotationElementId: id, id, deleted: false, accessibilityData: { @@ -812,7 +816,6 @@ class StampEditor extends AnnotationEditor { editor.width = (rect[2] - rect[0]) / parentWidth; editor.height = (rect[3] - rect[1]) / parentHeight; - editor.annotationElementId = data.id || null; if (accessibilityData) { editor.altTextData = accessibilityData; } diff --git a/src/display/editor/tools.js b/src/display/editor/tools.js index eba2415a0..772588586 100644 --- a/src/display/editor/tools.js +++ b/src/display/editor/tools.js @@ -676,6 +676,8 @@ class AnnotationEditorUIManager { #viewer = null; + #viewerAlert = null; + #updateModeCapability = null; static TRANSLATE_SMALL = 1; // page units. @@ -818,6 +820,7 @@ class AnnotationEditorUIManager { constructor( container, viewer, + viewerAlert, altTextManager, signatureManager, eventBus, @@ -834,6 +837,7 @@ class AnnotationEditorUIManager { const signal = (this._signal = this.#abortController.signal); this.#container = container; this.#viewer = viewer; + this.#viewerAlert = viewerAlert; this.#altTextManager = altTextManager; this.#signatureManager = signatureManager; this._eventBus = eventBus; @@ -1175,6 +1179,19 @@ class AnnotationEditorUIManager { } } + a11yAlert(messageId, args = null) { + const viewerAlert = this.#viewerAlert; + if (!viewerAlert) { + return; + } + viewerAlert.setAttribute("data-l10n-id", messageId); + if (args) { + viewerAlert.setAttribute("data-l10n-args", JSON.stringify(args)); + } else { + viewerAlert.removeAttribute("data-l10n-args"); + } + } + #selectionChange() { const selection = document.getSelection(); if (!selection || selection.isCollapsed) { diff --git a/test/integration/freetext_editor_spec.mjs b/test/integration/freetext_editor_spec.mjs index 03661f334..288a77d8d 100644 --- a/test/integration/freetext_editor_spec.mjs +++ b/test/integration/freetext_editor_spec.mjs @@ -110,6 +110,9 @@ describe("FreeText Editor", () => { await waitForSelectedEditor(page, editorSelector); await waitForStorageEntries(page, 1); + const alert = await page.$eval("#viewer-alert", el => el.textContent); + expect(alert).toEqual("Text added"); + let content = await page.$eval(editorSelector, el => el.innerText.trimEnd() ); diff --git a/test/integration/highlight_editor_spec.mjs b/test/integration/highlight_editor_spec.mjs index 505e671f6..13f184472 100644 --- a/test/integration/highlight_editor_spec.mjs +++ b/test/integration/highlight_editor_spec.mjs @@ -78,6 +78,9 @@ describe("Highlight Editor", () => { await page.waitForSelector(`${getEditorSelector(0)}`); + const alert = await page.$eval("#viewer-alert", el => el.textContent); + expect(alert).toEqual("Highlight added"); + const oneToOne = Array.from(new Array(13).keys(), n => n + 2).concat( Array.from(new Array(13).keys(), n => 13 - n) ); diff --git a/test/integration/ink_editor_spec.mjs b/test/integration/ink_editor_spec.mjs index aafee5de8..755b02e19 100644 --- a/test/integration/ink_editor_spec.mjs +++ b/test/integration/ink_editor_spec.mjs @@ -84,6 +84,9 @@ describe("Ink Editor", () => { await commit(page); } + const alert = await page.$eval("#viewer-alert", el => el.textContent); + expect(alert).toEqual("Drawing added"); + await clearAll(page); await kbUndo(page); diff --git a/test/integration/signature_editor_spec.mjs b/test/integration/signature_editor_spec.mjs index 6dff1ddfb..1a1200eae 100644 --- a/test/integration/signature_editor_spec.mjs +++ b/test/integration/signature_editor_spec.mjs @@ -181,6 +181,9 @@ describe("Signature Editor", () => { { visible: true } ); + const alert = await page.$eval("#viewer-alert", el => el.textContent); + expect(alert).toEqual("Signature added"); + // Check the tooltip. await page.waitForSelector( `.altText.editDescription[title="Hello World"]` diff --git a/test/integration/stamp_editor_spec.mjs b/test/integration/stamp_editor_spec.mjs index 4fe68ad5d..a7eac0fb4 100644 --- a/test/integration/stamp_editor_spec.mjs +++ b/test/integration/stamp_editor_spec.mjs @@ -125,6 +125,9 @@ describe("Stamp Editor", () => { const editorSelector = getEditorSelector(0); await waitForImage(page, editorSelector); + const alert = await page.$eval("#viewer-alert", el => el.textContent); + expect(alert).toEqual("Image added"); + const { width } = await getEditorDimensions(page, editorSelector); // The image is bigger than the page, so it has been scaled down to diff --git a/web/annotation_editor_layer_builder.css b/web/annotation_editor_layer_builder.css index b29873edd..49e235b81 100644 --- a/web/annotation_editor_layer_builder.css +++ b/web/annotation_editor_layer_builder.css @@ -49,22 +49,6 @@ --new-alt-text-warning-image: url(images/altText_warning.svg); } -/* The following class is used to hide an element but keep it available to - * for screen readers. */ -.visuallyHidden { - position: absolute; - top: 0; - left: 0; - border: 0; - margin: 0; - padding: 0; - width: 0; - height: 0; - overflow: hidden; - white-space: nowrap; - font-size: 0; -} - .textLayer { &.highlighting { cursor: var(--editorFreeHighlight-editing-cursor); diff --git a/web/app.js b/web/app.js index 084f981d0..327f3d6d2 100644 --- a/web/app.js +++ b/web/app.js @@ -492,6 +492,7 @@ const PDFViewerApplication = { const pdfViewer = (this.pdfViewer = new PDFViewer({ container, viewer, + viewerAlert: appConfig.viewerAlert, eventBus, renderingQueue, linkService, diff --git a/web/pdf_viewer.js b/web/pdf_viewer.js index 3d1ed09a1..92667c0b0 100644 --- a/web/pdf_viewer.js +++ b/web/pdf_viewer.js @@ -278,6 +278,8 @@ class PDFViewer { #textLayerMode = TextLayerMode.ENABLE; + #viewerAlert = null; + /** * @param {PDFViewerOptions} options */ @@ -291,6 +293,7 @@ class PDFViewer { } this.container = options.container; this.viewer = options.viewer || options.container.firstElementChild; + this.#viewerAlert = options.viewerAlert || null; if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) { if (this.container?.tagName !== "DIV" || this.viewer?.tagName !== "DIV") { @@ -927,6 +930,7 @@ class PDFViewer { this.#annotationEditorUIManager = new AnnotationEditorUIManager( this.container, viewer, + this.#viewerAlert, this.#altTextManager, this.#signatureManager, eventBus, diff --git a/web/viewer.css b/web/viewer.css index b1d9712ff..c957f10e2 100644 --- a/web/viewer.css +++ b/web/viewer.css @@ -229,6 +229,22 @@ body { } } +/* The following class is used to hide an element but keep it available to + * for screen readers. */ +.visuallyHidden { + position: absolute; + top: 0; + left: 0; + border: 0; + margin: 0; + padding: 0; + width: 0; + height: 0; + overflow: hidden; + white-space: nowrap; + font-size: 0; +} + .hidden, [hidden] { display: none !important; diff --git a/web/viewer.html b/web/viewer.html index 7805b0f1d..ac24c9746 100644 --- a/web/viewer.html +++ b/web/viewer.html @@ -98,6 +98,7 @@ See https://github.com/adobe-type-tools/cmap-resources +
diff --git a/web/viewer.js b/web/viewer.js index 7d654b85d..45bfd0ccd 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -33,6 +33,7 @@ function getViewerConfiguration() { principalContainer: document.getElementById("mainContainer"), mainContainer: document.getElementById("viewerContainer"), viewerContainer: document.getElementById("viewer"), + viewerAlert: document.getElementById("viewer-alert"), toolbar: { container: document.getElementById("toolbarContainer"), numPages: document.getElementById("numPages"),