feat: best effort link alt text generation

This commit is contained in:
Tobias Schmitz 2025-07-18 12:01:54 +02:00
parent 79423f3033
commit 99815f449c
No known key found for this signature in database
3 changed files with 35 additions and 17 deletions

View File

@ -222,9 +222,10 @@ const LINK_MARKER_RULE: ShowFn<LinkMarker> = |elem, _, _| Ok(elem.body.clone());
const LINK_RULE: ShowFn<LinkElem> = |elem, engine, styles| { const LINK_RULE: ShowFn<LinkElem> = |elem, engine, styles| {
let body = elem.body.clone(); let body = elem.body.clone();
let dest = elem.dest.resolve(engine.introspector).at(elem.span())?; let dest = elem.dest.resolve(engine.introspector).at(elem.span())?;
let url = || dest.as_url().map(|url| url.clone().into_inner()); let alt = match elem.alt.get_cloned(styles) {
// TODO(accessibility): remove custom alt field and generate alt text Some(alt) => Some(alt),
let alt = elem.alt.get_cloned(styles).or_else(url); None => dest.alt_text(engine, styles)?,
};
Ok(body.linked(dest, alt)) Ok(body.linked(dest, alt))
}; };

View File

@ -795,8 +795,8 @@ impl<'a> Generator<'a> {
renderer.display_elem_child(elem, &mut None, false)?; renderer.display_elem_child(elem, &mut None, false)?;
if let Some(location) = first_occurrences.get(item.key.as_str()) { if let Some(location) = first_occurrences.get(item.key.as_str()) {
let dest = Destination::Location(*location); let dest = Destination::Location(*location);
// TODO(accessibility): generate alt text let alt = content.plain_text();
content = content.linked(dest, None); content = content.linked(dest, Some(alt));
} }
StrResult::Ok(content) StrResult::Ok(content)
}) })
@ -931,8 +931,8 @@ impl ElemRenderer<'_> {
if let Some(hayagriva::ElemMeta::Entry(i)) = elem.meta { if let Some(hayagriva::ElemMeta::Entry(i)) = elem.meta {
if let Some(location) = (self.link)(i) { if let Some(location) = (self.link)(i) {
let dest = Destination::Location(location); let dest = Destination::Location(location);
// TODO(accessibility): generate alt text let alt = content.plain_text();
content = content.linked(dest, None); content = content.linked(dest, Some(alt));
} }
} }

View File

@ -1,15 +1,18 @@
use std::ops::Deref; use std::ops::Deref;
use std::str::FromStr;
use comemo::Tracked; use comemo::Tracked;
use ecow::{eco_format, EcoString}; use ecow::{eco_format, EcoString};
use crate::diag::{bail, StrResult}; use crate::diag::{bail, SourceResult, StrResult};
use crate::engine::Engine;
use crate::foundations::{ use crate::foundations::{
cast, elem, Content, Label, Packed, Repr, ShowSet, Smart, StyleChain, Styles, cast, elem, Content, Label, Packed, Repr, ShowSet, Smart, StyleChain, Styles,
}; };
use crate::introspection::{Introspector, Locatable, Location}; use crate::introspection::{Counter, CounterKey, Introspector, Locatable, Location};
use crate::layout::Position; use crate::layout::{PageElem, Position};
use crate::text::TextElem; use crate::model::NumberingPattern;
use crate::text::{LocalName, TextElem};
/// Links to a URL or a location in the document. /// Links to a URL or a location in the document.
/// ///
@ -216,12 +219,26 @@ pub enum Destination {
} }
impl Destination { impl Destination {
pub fn as_url(&self) -> Option<&Url> { pub fn alt_text(
if let Self::Url(v) = self { &self,
Some(v) engine: &mut Engine,
} else { styles: StyleChain,
None ) -> SourceResult<Option<EcoString>> {
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)
} }
} }