1
0
Fork 0
mirror of https://github.com/mozilla/pdf.js.git synced 2025-04-19 22:58:07 +02:00

Make tagged images visible for screen readers (bug 1708040)

The idea is to insert a span in the text layer with an aria-role set to img
and use the bounding box provided by the attribute field in the tag dict in
order to have non-null dimensions for the image to make it "visible".
This commit is contained in:
Calixte Denizet 2024-09-04 16:45:09 +02:00
parent 4b906ad0a8
commit ddba096191
12 changed files with 240 additions and 10 deletions

View file

@ -71,6 +71,10 @@
&:not(.free) span {
cursor: var(--editorHighlight-editing-cursor);
&[role="img"] {
cursor: var(--editorFreeHighlight-editing-cursor);
}
}
&.free span {

View file

@ -474,10 +474,13 @@ class PDFPageView {
}
const treeDom = await this.structTreeLayer?.render();
if (treeDom && this.canvas && treeDom.parentNode !== this.canvas) {
// Pause translation when inserting the structTree in the DOM.
if (treeDom) {
this.l10n.pause();
this.canvas.append(treeDom);
this.structTreeLayer?.addElementsToTextLayer();
if (this.canvas && treeDom.parentNode !== this.canvas) {
// Pause translation when inserting the structTree in the DOM.
this.canvas.append(treeDom);
}
this.l10n.resume();
}
this.structTreeLayer?.show();
@ -768,7 +771,7 @@ class PDFPageView {
this.annotationLayer = null;
this._annotationCanvasMap = null;
}
if (this.structTreeLayer && !(this.textLayer || this.annotationLayer)) {
if (this.structTreeLayer && !this.textLayer) {
this.structTreeLayer = null;
}
if (
@ -1068,7 +1071,10 @@ class PDFPageView {
await this.#finishRenderTask(renderTask);
if (this.textLayer || this.annotationLayer) {
this.structTreeLayer ||= new StructTreeLayerBuilder(pdfPage);
this.structTreeLayer ||= new StructTreeLayerBuilder(
pdfPage,
viewport.rawDims
);
}
this.#renderTextLayer();

View file

@ -82,8 +82,13 @@ class StructTreeLayerBuilder {
#elementAttributes = new Map();
constructor(pdfPage) {
#rawDims;
#elementsToAddToTextLayer = null;
constructor(pdfPage, rawDims) {
this.#promise = pdfPage.getStructTree();
this.#rawDims = rawDims;
}
async render() {
@ -156,6 +161,50 @@ class StructTreeLayerBuilder {
}
}
#addImageInTextLayer(node, element) {
const { alt, bbox, children } = node;
const child = children?.[0];
if (!this.#rawDims || !alt || !bbox || child?.type !== "content") {
return false;
}
const { id } = child;
if (!id) {
return false;
}
// We cannot add the created element to the text layer immediately, as the
// text layer might not be ready yet. Instead, we store the element and add
// it later in `addElementsToTextLayer`.
element.setAttribute("aria-owns", id);
const img = document.createElement("span");
(this.#elementsToAddToTextLayer ||= new Map()).set(id, img);
img.setAttribute("role", "img");
img.setAttribute("aria-label", removeNullCharacters(alt));
const { pageHeight, pageX, pageY } = this.#rawDims;
const calc = "calc(var(--scale-factor)*";
const { style } = img;
style.width = `${calc}${bbox[2] - bbox[0]}px)`;
style.height = `${calc}${bbox[3] - bbox[1]}px)`;
style.left = `${calc}${bbox[0] - pageX}px)`;
style.top = `${calc}${pageHeight - bbox[3] + pageY}px)`;
return true;
}
addElementsToTextLayer() {
if (!this.#elementsToAddToTextLayer) {
return;
}
for (const [id, img] of this.#elementsToAddToTextLayer) {
document.getElementById(id)?.append(img);
}
this.#elementsToAddToTextLayer.clear();
this.#elementsToAddToTextLayer = null;
}
#walk(node) {
if (!node) {
return null;
@ -171,6 +220,9 @@ class StructTreeLayerBuilder {
} else if (PDF_ROLE_TO_HTML_ROLE[role]) {
element.setAttribute("role", PDF_ROLE_TO_HTML_ROLE[role]);
}
if (role === "Figure" && this.#addImageInTextLayer(node, element)) {
return element;
}
}
this.#setAttributes(node, element);

View file

@ -52,6 +52,11 @@
}
/*#endif*/
span[role="img"] {
user-select: none;
cursor: default;
}
.highlight {
--highlight-bg-color: rgb(180 0 170 / 0.25);
--highlight-selected-bg-color: rgb(0 100 0 / 0.25);