WIP [no ci]

This commit is contained in:
Tobias Schmitz 2025-06-18 18:02:49 +02:00
parent 4a64a717e8
commit 616c626acb
No known key found for this signature in database
9 changed files with 60 additions and 33 deletions

View File

@ -16,12 +16,12 @@ use crate::diag::{SourceResult, StrResult};
use crate::engine::Engine; use crate::engine::Engine;
use crate::foundations::{ use crate::foundations::{
elem, func, scope, ty, Context, Dict, Element, Fields, IntoValue, Label, elem, func, scope, ty, Context, Dict, Element, Fields, IntoValue, Label,
NativeElement, Recipe, RecipeIndex, Repr, Selector, Str, Style, StyleChain, Styles, NativeElement, Recipe, RecipeIndex, Repr, Selector, Show, Str, Style, StyleChain,
Value, Styles, Value,
}; };
use crate::introspection::Location; use crate::introspection::{Locatable, Location};
use crate::layout::{AlignElem, Alignment, Axes, Length, MoveElem, PadElem, Rel, Sides}; 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::pdf::{ArtifactElem, ArtifactKind};
use crate::text::UnderlineElem; use crate::text::UnderlineElem;
@ -504,9 +504,9 @@ impl Content {
} }
/// Link the content somewhere. /// Link the content somewhere.
pub fn linked(self, alt: Option<EcoString>, dest: Destination) -> Self { pub fn linked(self, dest: Destination, alt: Option<EcoString>) -> Self {
self.styled(LinkElem::set_alt(alt)) let span = self.span();
.styled(LinkElem::set_current(Some(dest))) LinkMarker::new(self, dest, alt).pack().spanned(span)
} }
/// Set alignments for this content. /// Set alignments for this content.
@ -988,6 +988,26 @@ pub trait PlainText {
fn plain_text(&self, text: &mut EcoString); 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<EcoString>,
}
impl Show for Packed<LinkMarker> {
fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
Ok(self.body.clone())
}
}
/// An error arising when trying to access a field of content. /// An error arising when trying to access a field of content.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum FieldAccessError { pub enum FieldAccessError {

View File

@ -523,6 +523,8 @@ pub struct StyleChain<'a> {
} }
impl<'a> StyleChain<'a> { impl<'a> StyleChain<'a> {
pub const EMPTY: Self = Self { head: &[], tail: None };
/// Start a new style chain with root styles. /// Start a new style chain with root styles.
pub fn new(root: &'a Styles) -> Self { pub fn new(root: &'a Styles) -> Self {
Self { head: &root.0, tail: None } Self { head: &root.0, tail: None }

View File

@ -874,7 +874,7 @@ impl<'a> Generator<'a> {
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: accept user supplied alt text // TODO: accept user supplied alt text
content = content.linked(None, dest); content = content.linked(dest, None);
} }
StrResult::Ok(content) StrResult::Ok(content)
}) })
@ -1010,7 +1010,7 @@ impl ElemRenderer<'_> {
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: accept user supplied alt text // TODO: accept user supplied alt text
content = content.linked(None, dest); content = content.linked(dest, None);
} }
} }

View File

@ -148,7 +148,7 @@ impl Show for Packed<FootnoteElem> {
let loc = loc.variant(1); let loc = loc.variant(1);
// Add zero-width weak spacing to make the footnote "sticky". // Add zero-width weak spacing to make the footnote "sticky".
// TODO: accept user supplied alt text // 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<FootnoteEntry> {
.pack() .pack()
.spanned(span) .spanned(span)
// TODO: accept user supplied alt text // TODO: accept user supplied alt text
.linked(None, Destination::Location(loc)) .linked(Destination::Location(loc), None)
.located(loc.variant(1)); .located(loc.variant(1));
Ok(Content::sequence([ Ok(Content::sequence([

View File

@ -91,11 +91,6 @@ pub struct LinkElem {
_ => args.expect("body")?, _ => args.expect("body")?,
})] })]
pub body: Content, pub body: Content,
/// A destination style that should be applied to elements.
#[internal]
#[ghost]
pub current: Option<Destination>,
} }
impl LinkElem { impl LinkElem {
@ -128,11 +123,11 @@ impl Show for Packed<LinkElem> {
} else { } else {
let alt = self.alt(styles); let alt = self.alt(styles);
match &self.dest { match &self.dest {
LinkTarget::Dest(dest) => body.linked(alt, dest.clone()), LinkTarget::Dest(dest) => body.linked(dest.clone(), alt),
LinkTarget::Label(label) => { LinkTarget::Label(label) => {
let elem = engine.introspector.query_label(*label).at(self.span())?; let elem = engine.introspector.query_label(*label).at(self.span())?;
let dest = Destination::Location(elem.location().unwrap()); let dest = Destination::Location(elem.location().unwrap());
body.clone().linked(alt, dest) body.clone().linked(dest, alt)
} }
} }
}) })

View File

@ -446,7 +446,7 @@ impl Show for Packed<OutlineEntry> {
}; };
let loc = self.element_location().at(span)?; let loc = self.element_location().at(span)?;
Ok(block.linked(Some(alt), Destination::Location(loc))) Ok(block.linked(Destination::Location(loc), Some(alt)))
} }
} }

View File

@ -310,7 +310,7 @@ fn show_reference(
} }
// TODO: accept user supplied alt text // 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. /// Turn a reference into a citation.

View File

@ -30,10 +30,6 @@ pub(crate) fn handle_image(
let interpolate = image.scaling() == Smart::Custom(ImageScaling::Smooth); let interpolate = image.scaling() == Smart::Custom(ImageScaling::Smooth);
if let Some(alt) = image.alt() {
surface.start_alt_text(alt);
}
gc.image_spans.insert(span); gc.image_spans.insert(span);
match image.kind() { match image.kind() {
@ -62,10 +58,6 @@ pub(crate) fn handle_image(
} }
} }
if image.alt().is_some() {
surface.end_alt_text();
}
surface.pop(); surface.pop();
surface.reset_location(); surface.reset_location();

View File

@ -9,10 +9,11 @@ use krilla::tagging::{
use typst_library::foundations::{Content, StyleChain}; use typst_library::foundations::{Content, StyleChain};
use typst_library::introspection::Location; use typst_library::introspection::Location;
use typst_library::model::{ use typst_library::model::{
CiteElem, FigureCaption, FigureElem, HeadingElem, LinkElem, OutlineElem, CiteElem, FigureCaption, FigureElem, HeadingElem, LinkElem, Outlinable, OutlineElem,
OutlineEntry, RefElem, OutlineEntry, RefElem,
}; };
use typst_library::pdf::{ArtifactElem, ArtifactKind, PdfTagElem, PdfTagKind}; use typst_library::pdf::{ArtifactElem, ArtifactKind, PdfTagElem, PdfTagKind};
use typst_library::visualize::ImageElem;
use crate::convert::GlobalContext; use crate::convert::GlobalContext;
@ -177,7 +178,7 @@ pub(crate) fn handle_start(
_ => todo!(), _ => todo!(),
} }
} else if let Some(heading) = elem.to_packed::<HeadingElem>() { } else if let Some(heading) = elem.to_packed::<HeadingElem>() {
let level = heading.resolve_level(StyleChain::default()); let level = heading.level();
let name = heading.body.plain_text().to_string(); let name = heading.body.plain_text().to_string();
match level.get() { match level.get() {
1 => Tag::H1(Some(name)), 1 => Tag::H1(Some(name)),
@ -195,6 +196,25 @@ pub(crate) fn handle_start(
} else if let Some(_) = elem.to_packed::<FigureElem>() { } else if let Some(_) = elem.to_packed::<FigureElem>() {
let alt = None; // TODO let alt = None; // TODO
Tag::Figure(alt) Tag::Figure(alt)
} else if let Some(image) = elem.to_packed::<ImageElem>() {
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::<FigureCaption>() { } else if let Some(_) = elem.to_packed::<FigureCaption>() {
Tag::Caption Tag::Caption
} else if let Some(_) = elem.to_packed::<LinkElem>() { } else if let Some(_) = elem.to_packed::<LinkElem>() {
@ -212,9 +232,7 @@ pub(crate) fn handle_start(
} }
// close previous marked-content and open a nested tag. // close previous marked-content and open a nested tag.
if !gc.tags.stack.is_empty() { end_open(gc, surface);
surface.end_tagged();
}
let id = surface.start_tagged(krilla::tagging::ContentTag::Other); let id = surface.start_tagged(krilla::tagging::ContentTag::Other);
gc.tags.stack.push((loc, tag, vec![TagNode::Leaf(id)])); gc.tags.stack.push((loc, tag, vec![TagNode::Leaf(id)]));
} }