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:
parent
4b906ad0a8
commit
ddba096191
12 changed files with 240 additions and 10 deletions
|
@ -71,6 +71,10 @@
|
|||
|
||||
&:not(.free) span {
|
||||
cursor: var(--editorHighlight-editing-cursor);
|
||||
|
||||
&[role="img"] {
|
||||
cursor: var(--editorFreeHighlight-editing-cursor);
|
||||
}
|
||||
}
|
||||
|
||||
&.free span {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue