diff --git a/crates/typst-library/src/foundations/content.rs b/crates/typst-library/src/foundations/content.rs index 8cd46f0dd..611942c88 100644 --- a/crates/typst-library/src/foundations/content.rs +++ b/crates/typst-library/src/foundations/content.rs @@ -16,12 +16,12 @@ use crate::diag::{SourceResult, StrResult}; use crate::engine::Engine; use crate::foundations::{ elem, func, scope, ty, Context, Dict, Element, Fields, IntoValue, Label, - NativeElement, Recipe, RecipeIndex, Repr, Selector, Str, Style, StyleChain, Styles, - Value, + NativeElement, Recipe, RecipeIndex, Repr, Selector, Show, Str, Style, StyleChain, + Styles, Value, }; -use crate::introspection::Location; +use crate::introspection::{Locatable, Location}; use crate::layout::{AlignElem, Alignment, Axes, Length, MoveElem, PadElem, Rel, Sides}; -use crate::model::{Destination, EmphElem, LinkElem, StrongElem}; +use crate::model::{Destination, EmphElem, StrongElem}; use crate::pdf::{ArtifactElem, ArtifactKind}; use crate::text::UnderlineElem; @@ -504,9 +504,9 @@ impl Content { } /// Link the content somewhere. - pub fn linked(self, alt: Option, dest: Destination) -> Self { - self.styled(LinkElem::set_alt(alt)) - .styled(LinkElem::set_current(Some(dest))) + pub fn linked(self, dest: Destination, alt: Option) -> Self { + let span = self.span(); + LinkMarker::new(self, dest, alt).pack().spanned(span) } /// Set alignments for this content. @@ -988,6 +988,26 @@ pub trait PlainText { fn plain_text(&self, text: &mut EcoString); } + +// TODO: remove `FrameItem::Link` and `FrameModifiers::dest`, then handle links by looking at tags +/// An element that associates the body of a link with the destination. +#[elem(Show, Locatable)] +pub struct LinkMarker { + /// The content. + #[required] + pub body: Content, + #[required] + pub dest: Destination, + #[required] + pub alt: Option, +} + +impl Show for Packed { + fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult { + Ok(self.body.clone()) + } +} + /// An error arising when trying to access a field of content. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum FieldAccessError { diff --git a/crates/typst-library/src/foundations/styles.rs b/crates/typst-library/src/foundations/styles.rs index d124f2c87..699d0c179 100644 --- a/crates/typst-library/src/foundations/styles.rs +++ b/crates/typst-library/src/foundations/styles.rs @@ -523,6 +523,8 @@ pub struct StyleChain<'a> { } impl<'a> StyleChain<'a> { + pub const EMPTY: Self = Self { head: &[], tail: None }; + /// Start a new style chain with root styles. pub fn new(root: &'a Styles) -> Self { Self { head: &root.0, tail: None } diff --git a/crates/typst-library/src/model/bibliography.rs b/crates/typst-library/src/model/bibliography.rs index b302f8aa2..58158a669 100644 --- a/crates/typst-library/src/model/bibliography.rs +++ b/crates/typst-library/src/model/bibliography.rs @@ -874,7 +874,7 @@ impl<'a> Generator<'a> { if let Some(location) = first_occurrences.get(item.key.as_str()) { let dest = Destination::Location(*location); // TODO: accept user supplied alt text - content = content.linked(None, dest); + content = content.linked(dest, None); } StrResult::Ok(content) }) @@ -1010,7 +1010,7 @@ impl ElemRenderer<'_> { if let Some(location) = (self.link)(i) { let dest = Destination::Location(location); // TODO: accept user supplied alt text - content = content.linked(None, dest); + content = content.linked(dest, None); } } diff --git a/crates/typst-library/src/model/footnote.rs b/crates/typst-library/src/model/footnote.rs index af6664cb9..872827d90 100644 --- a/crates/typst-library/src/model/footnote.rs +++ b/crates/typst-library/src/model/footnote.rs @@ -148,7 +148,7 @@ impl Show for Packed { let loc = loc.variant(1); // Add zero-width weak spacing to make the footnote "sticky". // TODO: accept user supplied alt text - Ok(HElem::hole().pack() + sup.linked(None, Destination::Location(loc))) + Ok(HElem::hole().pack() + sup.linked(Destination::Location(loc), None)) } } @@ -298,7 +298,7 @@ impl Show for Packed { .pack() .spanned(span) // TODO: accept user supplied alt text - .linked(None, Destination::Location(loc)) + .linked(Destination::Location(loc), None) .located(loc.variant(1)); Ok(Content::sequence([ diff --git a/crates/typst-library/src/model/link.rs b/crates/typst-library/src/model/link.rs index d64192f29..53b2c8fd9 100644 --- a/crates/typst-library/src/model/link.rs +++ b/crates/typst-library/src/model/link.rs @@ -91,11 +91,6 @@ pub struct LinkElem { _ => args.expect("body")?, })] pub body: Content, - - /// A destination style that should be applied to elements. - #[internal] - #[ghost] - pub current: Option, } impl LinkElem { @@ -128,11 +123,11 @@ impl Show for Packed { } else { let alt = self.alt(styles); match &self.dest { - LinkTarget::Dest(dest) => body.linked(alt, dest.clone()), + LinkTarget::Dest(dest) => body.linked(dest.clone(), alt), LinkTarget::Label(label) => { let elem = engine.introspector.query_label(*label).at(self.span())?; let dest = Destination::Location(elem.location().unwrap()); - body.clone().linked(alt, dest) + body.clone().linked(dest, alt) } } }) diff --git a/crates/typst-library/src/model/outline.rs b/crates/typst-library/src/model/outline.rs index a13e0c859..b98d983d1 100644 --- a/crates/typst-library/src/model/outline.rs +++ b/crates/typst-library/src/model/outline.rs @@ -446,7 +446,7 @@ impl Show for Packed { }; let loc = self.element_location().at(span)?; - Ok(block.linked(Some(alt), Destination::Location(loc))) + Ok(block.linked(Destination::Location(loc), Some(alt))) } } diff --git a/crates/typst-library/src/model/reference.rs b/crates/typst-library/src/model/reference.rs index 3f3ece05e..9302f9711 100644 --- a/crates/typst-library/src/model/reference.rs +++ b/crates/typst-library/src/model/reference.rs @@ -310,7 +310,7 @@ fn show_reference( } // TODO: accept user supplied alt text - Ok(content.linked(None, Destination::Location(loc))) + Ok(content.linked(Destination::Location(loc), None)) } /// Turn a reference into a citation. diff --git a/crates/typst-pdf/src/image.rs b/crates/typst-pdf/src/image.rs index 93bdb1950..0809ae046 100644 --- a/crates/typst-pdf/src/image.rs +++ b/crates/typst-pdf/src/image.rs @@ -30,10 +30,6 @@ pub(crate) fn handle_image( let interpolate = image.scaling() == Smart::Custom(ImageScaling::Smooth); - if let Some(alt) = image.alt() { - surface.start_alt_text(alt); - } - gc.image_spans.insert(span); match image.kind() { @@ -62,10 +58,6 @@ pub(crate) fn handle_image( } } - if image.alt().is_some() { - surface.end_alt_text(); - } - surface.pop(); surface.reset_location(); diff --git a/crates/typst-pdf/src/tags.rs b/crates/typst-pdf/src/tags.rs index 780a88418..df7b9e967 100644 --- a/crates/typst-pdf/src/tags.rs +++ b/crates/typst-pdf/src/tags.rs @@ -9,10 +9,11 @@ use krilla::tagging::{ use typst_library::foundations::{Content, StyleChain}; use typst_library::introspection::Location; use typst_library::model::{ - CiteElem, FigureCaption, FigureElem, HeadingElem, LinkElem, OutlineElem, + CiteElem, FigureCaption, FigureElem, HeadingElem, LinkElem, Outlinable, OutlineElem, OutlineEntry, RefElem, }; use typst_library::pdf::{ArtifactElem, ArtifactKind, PdfTagElem, PdfTagKind}; +use typst_library::visualize::ImageElem; use crate::convert::GlobalContext; @@ -177,7 +178,7 @@ pub(crate) fn handle_start( _ => todo!(), } } else if let Some(heading) = elem.to_packed::() { - let level = heading.resolve_level(StyleChain::default()); + let level = heading.level(); let name = heading.body.plain_text().to_string(); match level.get() { 1 => Tag::H1(Some(name)), @@ -195,6 +196,25 @@ pub(crate) fn handle_start( } else if let Some(_) = elem.to_packed::() { let alt = None; // TODO Tag::Figure(alt) + } else if let Some(image) = elem.to_packed::() { + let alt = image.alt(StyleChain::default()).map(|s| s.to_string()); + + end_open(gc, surface); + let id = surface.start_tagged(ContentTag::Other); + let mut node = TagNode::Leaf(id); + + if let Some(Tag::Figure(alt_text)) = gc.tags.parent().0 { + // HACK: set alt text of outer figure tag, if the contained image + // has alt text specified + if alt_text.is_none() { + *alt_text = alt; + } + } else { + node = TagNode::Group(Tag::Figure(alt), vec![node]); + } + gc.tags.push(node); + + return; } else if let Some(_) = elem.to_packed::() { Tag::Caption } else if let Some(_) = elem.to_packed::() { @@ -212,9 +232,7 @@ pub(crate) fn handle_start( } // close previous marked-content and open a nested tag. - if !gc.tags.stack.is_empty() { - surface.end_tagged(); - } + end_open(gc, surface); let id = surface.start_tagged(krilla::tagging::ContentTag::Other); gc.tags.stack.push((loc, tag, vec![TagNode::Leaf(id)])); }