diff --git a/crates/typst-layout/src/rules.rs b/crates/typst-layout/src/rules.rs index 829a402d3..915751d9f 100644 --- a/crates/typst-layout/src/rules.rs +++ b/crates/typst-layout/src/rules.rs @@ -222,9 +222,10 @@ const LINK_MARKER_RULE: ShowFn = |elem, _, _| Ok(elem.body.clone()); const LINK_RULE: ShowFn = |elem, engine, styles| { let body = elem.body.clone(); let dest = elem.dest.resolve(engine.introspector).at(elem.span())?; - let url = || dest.as_url().map(|url| url.clone().into_inner()); - // TODO(accessibility): remove custom alt field and generate alt text - let alt = elem.alt.get_cloned(styles).or_else(url); + let alt = match elem.alt.get_cloned(styles) { + Some(alt) => Some(alt), + None => dest.alt_text(engine, styles)?, + }; Ok(body.linked(dest, alt)) }; diff --git a/crates/typst-library/src/model/bibliography.rs b/crates/typst-library/src/model/bibliography.rs index 4cc252f5b..529f1db40 100644 --- a/crates/typst-library/src/model/bibliography.rs +++ b/crates/typst-library/src/model/bibliography.rs @@ -795,8 +795,8 @@ impl<'a> Generator<'a> { renderer.display_elem_child(elem, &mut None, false)?; if let Some(location) = first_occurrences.get(item.key.as_str()) { let dest = Destination::Location(*location); - // TODO(accessibility): generate alt text - content = content.linked(dest, None); + let alt = content.plain_text(); + content = content.linked(dest, Some(alt)); } StrResult::Ok(content) }) @@ -931,8 +931,8 @@ impl ElemRenderer<'_> { if let Some(hayagriva::ElemMeta::Entry(i)) = elem.meta { if let Some(location) = (self.link)(i) { let dest = Destination::Location(location); - // TODO(accessibility): generate alt text - content = content.linked(dest, None); + let alt = content.plain_text(); + content = content.linked(dest, Some(alt)); } } diff --git a/crates/typst-library/src/model/link.rs b/crates/typst-library/src/model/link.rs index fe92ebd19..5d40a306e 100644 --- a/crates/typst-library/src/model/link.rs +++ b/crates/typst-library/src/model/link.rs @@ -1,15 +1,18 @@ use std::ops::Deref; +use std::str::FromStr; use comemo::Tracked; use ecow::{eco_format, EcoString}; -use crate::diag::{bail, StrResult}; +use crate::diag::{bail, SourceResult, StrResult}; +use crate::engine::Engine; use crate::foundations::{ cast, elem, Content, Label, Packed, Repr, ShowSet, Smart, StyleChain, Styles, }; -use crate::introspection::{Introspector, Locatable, Location}; -use crate::layout::Position; -use crate::text::TextElem; +use crate::introspection::{Counter, CounterKey, Introspector, Locatable, Location}; +use crate::layout::{PageElem, Position}; +use crate::model::NumberingPattern; +use crate::text::{LocalName, TextElem}; /// Links to a URL or a location in the document. /// @@ -216,12 +219,26 @@ pub enum Destination { } impl Destination { - pub fn as_url(&self) -> Option<&Url> { - if let Self::Url(v) = self { - Some(v) - } else { - None - } + pub fn alt_text( + &self, + engine: &mut Engine, + styles: StyleChain, + ) -> SourceResult> { + let alt = match self { + Destination::Url(url) => Some(url.clone().into_inner()), + Destination::Position(_) => None, + &Destination::Location(loc) => { + let numbering = loc + .page_numbering(engine) + .unwrap_or_else(|| NumberingPattern::from_str("1").unwrap().into()); + let content = Counter::new(CounterKey::Page) + .display_at_loc(engine, loc, styles, &numbering)?; + let page_nr = content.plain_text(); + let page_str = PageElem::local_name_in(styles); + Some(eco_format!("{page_str} {page_nr}")) + } + }; + Ok(alt) } }