From d1202d961722e3de6b3b2f2bde48d3cadc975ec2 Mon Sep 17 00:00:00 2001 From: Tobias Schmitz Date: Mon, 21 Jul 2025 10:03:03 +0200 Subject: [PATCH] refactor: update krilla --- Cargo.lock | 4 +- crates/typst-pdf/src/tags/list.rs | 14 ++--- crates/typst-pdf/src/tags/mod.rs | 61 ++++++++++------------ crates/typst-pdf/src/tags/outline.rs | 8 +-- crates/typst-pdf/src/tags/table.rs | 77 +++++++++++++++------------- 5 files changed, 81 insertions(+), 83 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7e83abd4d..bb7778ac1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1430,7 +1430,7 @@ dependencies = [ [[package]] name = "krilla" version = "0.4.0" -source = "git+https://github.com/LaurenzV/krilla?branch=main#37b9a00bfac87ed0b347b7cf8e9d37a6f68fcccd" +source = "git+https://github.com/LaurenzV/krilla?branch=main#9e825532895036c7dfb440710d19271c6ad0473a" dependencies = [ "base64", "bumpalo", @@ -1460,7 +1460,7 @@ dependencies = [ [[package]] name = "krilla-svg" version = "0.1.0" -source = "git+https://github.com/LaurenzV/krilla?branch=main#37b9a00bfac87ed0b347b7cf8e9d37a6f68fcccd" +source = "git+https://github.com/LaurenzV/krilla?branch=main#9e825532895036c7dfb440710d19271c6ad0473a" dependencies = [ "flate2", "fontdb", diff --git a/crates/typst-pdf/src/tags/list.rs b/crates/typst-pdf/src/tags/list.rs index ce18fcd2f..13d3971c1 100644 --- a/crates/typst-pdf/src/tags/list.rs +++ b/crates/typst-pdf/src/tags/list.rs @@ -1,4 +1,4 @@ -use krilla::tagging::{ListNumbering, TagKind}; +use krilla::tagging::{ListNumbering, Tag, TagKind}; use crate::tags::TagNode; @@ -61,7 +61,7 @@ impl ListCtx { // // So move the nested list out of the list item. if let [_, TagNode::Group(tag, _)] = nodes.as_slice() { - if matches!(tag.kind, TagKind::L(_)) { + if let TagKind::L(_) = tag { item.sub_list = nodes.pop(); } } @@ -70,7 +70,7 @@ impl ListCtx { } pub(crate) fn push_bib_entry(&mut self, nodes: Vec) { - let nodes = vec![TagNode::Group(TagKind::BibEntry.into(), nodes)]; + let nodes = vec![TagNode::Group(Tag::BibEntry.into(), nodes)]; // Bibliography lists cannot be nested, but may be missing labels. if let Some(item) = self.items.last_mut().filter(|item| item.body.is_none()) { item.body = Some(nodes); @@ -86,16 +86,16 @@ impl ListCtx { pub(crate) fn build_list(self, mut nodes: Vec) -> TagNode { for item in self.items.into_iter() { nodes.push(TagNode::Group( - TagKind::LI.into(), + Tag::LI.into(), vec![ - TagNode::Group(TagKind::Lbl.into(), item.label), - TagNode::Group(TagKind::LBody.into(), item.body.unwrap_or_default()), + TagNode::Group(Tag::Lbl.into(), item.label), + TagNode::Group(Tag::LBody.into(), item.body.unwrap_or_default()), ], )); if let Some(sub_list) = item.sub_list { nodes.push(sub_list); } } - TagNode::Group(TagKind::L(self.numbering).into(), nodes) + TagNode::Group(Tag::L(self.numbering).into(), nodes) } } diff --git a/crates/typst-pdf/src/tags/mod.rs b/crates/typst-pdf/src/tags/mod.rs index 159a2380b..0d8df6fb6 100644 --- a/crates/typst-pdf/src/tags/mod.rs +++ b/crates/typst-pdf/src/tags/mod.rs @@ -8,8 +8,8 @@ use krilla::configure::Validator; use krilla::page::Page; use krilla::surface::Surface; use krilla::tagging::{ - ArtifactType, ContentTag, Identifier, ListNumbering, Node, SpanTag, TableDataCell, - Tag, TagBuilder, TagGroup, TagKind, TagTree, + ArtifactType, ContentTag, Identifier, ListNumbering, Node, SpanTag, Tag, TagGroup, + TagKind, TagTree, }; use typst_library::diag::SourceResult; use typst_library::foundations::{ @@ -58,13 +58,13 @@ pub(crate) fn handle_start( return Ok(()); } - let tag: Tag = if let Some(tag) = elem.to_packed::() { + let mut tag: TagKind = if let Some(tag) = elem.to_packed::() { match tag.kind { PdfMarkerTagKind::OutlineBody => { push_stack(gc, loc, StackEntryKind::Outline(OutlineCtx::new()))?; return Ok(()); } - PdfMarkerTagKind::FigureBody => TagKind::Figure.into(), + PdfMarkerTagKind::FigureBody => Tag::Figure(None).into(), PdfMarkerTagKind::Bibliography(numbered) => { let numbering = if numbered { ListNumbering::Decimal } else { ListNumbering::None }; @@ -83,7 +83,7 @@ pub(crate) fn handle_start( push_stack(gc, loc, StackEntryKind::ListItemBody)?; return Ok(()); } - PdfMarkerTagKind::Label => TagKind::Lbl.into(), + PdfMarkerTagKind::Label => Tag::Lbl.into(), } } else if let Some(entry) = elem.to_packed::() { push_stack(gc, loc, StackEntryKind::OutlineEntry(entry.clone()))?; @@ -105,27 +105,25 @@ pub(crate) fn handle_start( // 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() + Tag::P.into() } else if let Some(_) = elem.to_packed::() { - TagKind::Caption.into() + Tag::Caption.into() } else if let Some(image) = elem.to_packed::() { let alt = image.alt.get_as_ref().map(|s| s.to_string()); - let figure_tag = (gc.tags.stack.parent()) - .and_then(StackEntryKind::as_standard_mut) - .filter(|tag| tag.kind == TagKind::Figure); - if let Some(figure_tag) = figure_tag { + let figure_tag = gc.tags.stack.parent().and_then(StackEntryKind::as_standard_mut); + if let Some(TagKind::Figure(figure_tag)) = figure_tag { // Set alt text of outer figure tag, if not present. - if figure_tag.alt_text.is_none() { - figure_tag.alt_text = alt; + if figure_tag.alt_text().is_none() { + figure_tag.set_alt_text(alt); } return Ok(()); } else { - TagKind::Figure.with_alt_text(alt) + Tag::Figure(alt).into() } } else if let Some(equation) = elem.to_packed::() { let alt = equation.alt.get_as_ref().map(|s| s.to_string()); - TagKind::Formula.with_alt_text(alt) + Tag::Formula(alt).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()); @@ -153,7 +151,7 @@ pub(crate) fn handle_start( } else if let Some(heading) = elem.to_packed::() { let level = heading.level().try_into().unwrap_or(NonZeroU32::MAX); let name = heading.body.plain_text().to_string(); - TagKind::Hn(level, Some(name)).into() + Tag::Hn(level, Some(name)).into() } else if let Some(link) = elem.to_packed::() { let link_id = gc.tags.next_link_id(); push_stack(gc, loc, StackEntryKind::Link(link_id, link.clone()))?; @@ -168,15 +166,15 @@ pub(crate) fn handle_start( } else if let Some(quote) = elem.to_packed::() { // TODO: should the attribution be handled somehow? if quote.block.get(StyleChain::default()) { - TagKind::BlockQuote.into() + Tag::BlockQuote.into() } else { - TagKind::InlineQuote.into() + Tag::InlineQuote.into() } } else { return Ok(()); }; - let tag = tag.with_location(Some(elem.span().into_raw().get())); + tag.set_location(Some(elem.span().into_raw().get())); push_stack(gc, loc, StackEntryKind::Standard(tag))?; Ok(()) @@ -203,9 +201,7 @@ pub(crate) fn handle_end(gc: &mut GlobalContext, surface: &mut Surface, loc: Loc // PDF/UA compliance of the structure hierarchy is checked // elsewhere. While this doesn't make a lot of sense, just // avoid crashing here. - let tag = TagKind::TOCI - .with_location(Some(outline_entry.span().into_raw().get())); - gc.tags.push(TagNode::Group(tag, entry.nodes)); + gc.tags.push(TagNode::Group(Tag::TOCI.into(), entry.nodes)); return; }; @@ -218,9 +214,8 @@ pub(crate) fn handle_end(gc: &mut GlobalContext, surface: &mut Surface, loc: Loc // PDF/UA compliance of the structure hierarchy is checked // elsewhere. While this doesn't make a lot of sense, just // avoid crashing here. - let tag = TagKind::TD(TableDataCell::new()) - .with_location(Some(cell.span().into_raw().get())); - gc.tags.push(TagNode::Group(tag, entry.nodes)); + let tag = Tag::TD.with_location(Some(cell.span().into_raw().get())); + gc.tags.push(TagNode::Group(tag.into(), entry.nodes)); return; }; @@ -245,11 +240,11 @@ pub(crate) fn handle_end(gc: &mut GlobalContext, surface: &mut Surface, loc: Loc } StackEntryKind::Link(_, link) => { let alt = link.alt.as_ref().map(EcoString::to_string); - let tag = TagKind::Link.with_alt_text(alt); - let mut node = TagNode::Group(tag, entry.nodes); + let tag = Tag::Link.with_alt_text(alt); + let mut node = TagNode::Group(tag.into(), entry.nodes); // Wrap link in reference tag, if it's not a url. if let Destination::Position(_) | Destination::Location(_) = link.dest { - node = TagNode::Group(TagKind::Reference.into(), vec![node]); + node = TagNode::Group(Tag::Reference.into(), vec![node]); } node } @@ -262,7 +257,7 @@ pub(crate) fn handle_end(gc: &mut GlobalContext, surface: &mut Surface, loc: Loc StackEntryKind::FootNoteEntry(footnote_loc) => { // Store footnotes separately so they can be inserted directly after // the footnote reference in the reading order. - let tag = TagNode::Group(TagKind::Note.into(), entry.nodes); + let tag = TagNode::Group(Tag::Note.into(), entry.nodes); gc.tags.footnotes.insert(footnote_loc, tag); return; } @@ -518,7 +513,7 @@ pub(crate) struct StackEntry { #[derive(Debug)] pub(crate) enum StackEntryKind { - Standard(Tag), + Standard(TagKind), Outline(OutlineCtx), OutlineEntry(Packed), Table(TableCtx), @@ -536,7 +531,7 @@ pub(crate) enum StackEntryKind { } impl StackEntryKind { - pub(crate) fn as_standard_mut(&mut self) -> Option<&mut Tag> { + pub(crate) fn as_standard_mut(&mut self) -> Option<&mut TagKind> { if let Self::Standard(v) = self { Some(v) } else { None } } @@ -557,9 +552,9 @@ impl StackEntryKind { } } -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub(crate) enum TagNode { - Group(Tag, Vec), + Group(TagKind, Vec), Leaf(Identifier), /// Allows inserting a placeholder into the tag tree. /// Currently used for [`krilla::page::Page::add_tagged_annotation`]. diff --git a/crates/typst-pdf/src/tags/outline.rs b/crates/typst-pdf/src/tags/outline.rs index e809489f3..e1b074055 100644 --- a/crates/typst-pdf/src/tags/outline.rs +++ b/crates/typst-pdf/src/tags/outline.rs @@ -1,4 +1,4 @@ -use krilla::tagging::TagKind; +use krilla::tagging::Tag; use typst_library::foundations::Packed; use typst_library::model::OutlineEntry; @@ -29,7 +29,7 @@ impl OutlineCtx { } } - let section_entry = TagNode::Group(TagKind::TOCI.into(), nodes); + let section_entry = TagNode::Group(Tag::TOCI.into(), nodes); self.push(outline_nodes, section_entry); } @@ -49,7 +49,7 @@ impl OutlineCtx { while !self.stack.is_empty() { self.finish_section(&mut outline_nodes); } - TagNode::Group(TagKind::TOC.into(), outline_nodes) + TagNode::Group(Tag::TOC.into(), outline_nodes) } } @@ -68,6 +68,6 @@ impl OutlineSection { } fn into_tag(self) -> TagNode { - TagNode::Group(TagKind::TOC.into(), self.entries) + TagNode::Group(Tag::TOC.into(), self.entries) } } diff --git a/crates/typst-pdf/src/tags/table.rs b/crates/typst-pdf/src/tags/table.rs index 5cbdaae9f..9bf916809 100644 --- a/crates/typst-pdf/src/tags/table.rs +++ b/crates/typst-pdf/src/tags/table.rs @@ -2,9 +2,7 @@ use std::io::Write as _; use std::num::NonZeroU32; use az::SaturatingAs; -use krilla::tagging::{ - TableCellSpan, TableDataCell, TableHeaderCell, TagBuilder, TagId, TagKind, -}; +use krilla::tagging::{Tag, TagId, TagKind}; use smallvec::SmallVec; use typst_library::foundations::{Packed, Smart, StyleChain}; use typst_library::model::TableCell; @@ -101,7 +99,7 @@ impl TableCtx { // Table layouting ensures that there are no overlapping cells, and that // any gaps left by the user are filled with empty cells. if self.rows.is_empty() { - return TagNode::Group(TagKind::Table(self.summary).into(), nodes); + return TagNode::Group(Tag::Table.with_summary(self.summary).into(), nodes); } let height = self.rows.len(); let width = self.rows[0].len(); @@ -166,31 +164,32 @@ impl TableCtx { .into_iter() .filter_map(|cell| { let cell = cell.into_cell()?; - let span = TableCellSpan { rows: cell.rowspan, cols: cell.colspan }; + let rowspan = (cell.rowspan.get() != 1).then_some(cell.rowspan); + let colspan = (cell.colspan.get() != 1).then_some(cell.colspan); let tag = match cell.unwrap_kind() { TableCellKind::Header(_, scope) => { let id = table_cell_id(self.id, cell.x, cell.y); let scope = table_header_scope(scope); - TagKind::TH( - TableHeaderCell::new(scope) - .with_span(span) - .with_headers(cell.headers), - ) - .with_id(Some(id)) - .with_location(Some(cell.span.into_raw().get())) + Tag::TH(scope) + .with_id(Some(id)) + .with_headers(cell.headers) + .with_row_span(rowspan) + .with_col_span(colspan) + .with_location(Some(cell.span.into_raw().get())) + .into() } - TableCellKind::Footer | TableCellKind::Data => TagKind::TD( - TableDataCell::new() - .with_span(span) - .with_headers(cell.headers), - ) - .with_location(Some(cell.span.into_raw().get())), + TableCellKind::Footer | TableCellKind::Data => Tag::TD + .with_headers(cell.headers) + .with_row_span(rowspan) + .with_col_span(colspan) + .with_location(Some(cell.span.into_raw().get())) + .into(), }; Some(TagNode::Group(tag, cell.nodes)) }) .collect(); - let row = TagNode::Group(TagKind::TR.into(), row_nodes); + let row = TagNode::Group(Tag::TR.into(), row_nodes); // Push the `TR` tags directly. if !gen_row_groups { @@ -200,10 +199,10 @@ impl TableCtx { // Generate row groups. if !should_group_rows(chunk_kind, row_kind) { - let tag = match chunk_kind { - TableCellKind::Header(..) => TagKind::THead, - TableCellKind::Footer => TagKind::TFoot, - TableCellKind::Data => TagKind::TBody, + let tag: TagKind = match chunk_kind { + TableCellKind::Header(..) => Tag::THead.into(), + TableCellKind::Footer => Tag::TFoot.into(), + TableCellKind::Data => Tag::TBody.into(), }; nodes.push(TagNode::Group(tag.into(), std::mem::take(&mut row_chunk))); @@ -213,15 +212,15 @@ impl TableCtx { } if !row_chunk.is_empty() { - let tag = match chunk_kind { - TableCellKind::Header(..) => TagKind::THead, - TableCellKind::Footer => TagKind::TFoot, - TableCellKind::Data => TagKind::TBody, + let tag: TagKind = match chunk_kind { + TableCellKind::Header(..) => Tag::THead.into(), + TableCellKind::Footer => Tag::TFoot.into(), + TableCellKind::Data => Tag::TBody.into(), }; nodes.push(TagNode::Group(tag.into(), row_chunk)); } - TagNode::Group(TagKind::Table(self.summary).into(), nodes) + TagNode::Group(Tag::Table.with_summary(self.summary).into(), nodes) } fn resolve_cell_headers( @@ -379,24 +378,24 @@ mod tests { } fn table_tag(nodes: [TagNode; SIZE]) -> TagNode { - let tag = TagKind::Table(Some("summary".into())); + let tag = Tag::Table.with_summary(Some("summary".into())); TagNode::Group(tag.into(), nodes.into()) } fn thead(nodes: [TagNode; SIZE]) -> TagNode { - TagNode::Group(TagKind::THead.into(), nodes.into()) + TagNode::Group(Tag::THead.into(), nodes.into()) } fn tbody(nodes: [TagNode; SIZE]) -> TagNode { - TagNode::Group(TagKind::TBody.into(), nodes.into()) + TagNode::Group(Tag::TBody.into(), nodes.into()) } fn tfoot(nodes: [TagNode; SIZE]) -> TagNode { - TagNode::Group(TagKind::TFoot.into(), nodes.into()) + TagNode::Group(Tag::TFoot.into(), nodes.into()) } fn trow(nodes: [TagNode; SIZE]) -> TagNode { - TagNode::Group(TagKind::TR.into(), nodes.into()) + TagNode::Group(Tag::TR.into(), nodes.into()) } fn th( @@ -408,9 +407,11 @@ mod tests { let id = table_cell_id(TableId(324), x, y); let ids = headers.map(|(x, y)| table_cell_id(TableId(324), x, y)); TagNode::Group( - TagKind::TH(TableHeaderCell::new(scope).with_headers(ids)) + Tag::TH(scope) .with_id(Some(id)) - .with_location(Some(Span::detached().into_raw().get())), + .with_headers(ids) + .with_location(Some(Span::detached().into_raw().get())) + .into(), Vec::new(), ) } @@ -418,8 +419,10 @@ mod tests { fn td(headers: [(u32, u32); SIZE]) -> TagNode { let ids = headers.map(|(x, y)| table_cell_id(TableId(324), x, y)); TagNode::Group( - TagKind::TD(TableDataCell::new().with_headers(ids)) - .with_location(Some(Span::detached().into_raw().get())), + Tag::TD + .with_headers(ids) + .with_location(Some(Span::detached().into_raw().get())) + .into(), Vec::new(), ) }