From 9bbfe4c14ac49a265dacb6fda66ae582197fa77a Mon Sep 17 00:00:00 2001 From: Tobias Schmitz Date: Mon, 14 Jul 2025 10:31:17 +0200 Subject: [PATCH] fix: make figure captions sibling elements if the caption is contained within the figure screen readers might ignore it --- crates/typst-layout/src/rules.rs | 2 +- crates/typst-library/src/pdf/accessibility.rs | 2 ++ crates/typst-pdf/src/tags/mod.rs | 12 ++++++++---- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/crates/typst-layout/src/rules.rs b/crates/typst-layout/src/rules.rs index f4392d2b5..32fdbe1f9 100644 --- a/crates/typst-layout/src/rules.rs +++ b/crates/typst-layout/src/rules.rs @@ -294,7 +294,7 @@ const HEADING_RULE: ShowFn = |elem, engine, styles| { const FIGURE_RULE: ShowFn = |elem, _, styles| { let span = elem.span(); - let mut realized = elem.body.clone(); + let mut realized = PdfMarkerTag::FigureBody(elem.body.clone()); // Build the caption, if any. if let Some(caption) = elem.caption.get_cloned(styles) { diff --git a/crates/typst-library/src/pdf/accessibility.rs b/crates/typst-library/src/pdf/accessibility.rs index f987a7645..a2cea8ddc 100644 --- a/crates/typst-library/src/pdf/accessibility.rs +++ b/crates/typst-library/src/pdf/accessibility.rs @@ -132,6 +132,8 @@ macro_rules! pdf_marker_tag { pdf_marker_tag! { /// `TOC` OutlineBody, + /// `Figure` + FigureBody, /// `Lbl` (marker) of the list item ListItemLabel, /// `LBody` of the enum item diff --git a/crates/typst-pdf/src/tags/mod.rs b/crates/typst-pdf/src/tags/mod.rs index e65f73aa0..d83274a08 100644 --- a/crates/typst-pdf/src/tags/mod.rs +++ b/crates/typst-pdf/src/tags/mod.rs @@ -60,6 +60,7 @@ pub(crate) fn handle_start( push_stack(gc, loc, StackEntryKind::Outline(OutlineCtx::new()))?; return Ok(()); } + PdfMarkerTagKind::FigureBody => TagKind::Figure.into(), PdfMarkerTagKind::ListItemLabel => { push_stack(gc, loc, StackEntryKind::ListItemLabel)?; return Ok(()); @@ -81,8 +82,13 @@ pub(crate) fn handle_start( push_stack(gc, loc, StackEntryKind::List(ListCtx::new(numbering)))?; return Ok(()); } else if let Some(_) = elem.to_packed::() { - let alt = None; // TODO - TagKind::Figure.with_alt_text(alt) + // Wrap the figure tag and the sibling caption in a container, if the + // caption is contained within the figure like recommended for tables + // screen readers might ignore it. + // TODO: maybe this could be a `NonStruct` tag? + TagKind::P.into() + } else if let Some(_) = elem.to_packed::() { + TagKind::Caption.into() } else if let Some(image) = elem.to_packed::() { let alt = image.alt.get_as_ref().map(|s| s.to_string()); @@ -98,8 +104,6 @@ pub(crate) fn handle_start( } else { TagKind::Figure.with_alt_text(alt) } - } else if let Some(_) = elem.to_packed::() { - TagKind::Caption.into() } else if let Some(table) = elem.to_packed::() { let table_id = gc.tags.next_table_id(); let summary = table.summary.get_as_ref().map(|s| s.to_string());