refactor: update krilla

This commit is contained in:
Tobias Schmitz 2025-07-21 10:03:03 +02:00
parent de72040ce6
commit d1202d9617
No known key found for this signature in database
5 changed files with 81 additions and 83 deletions

4
Cargo.lock generated
View File

@ -1430,7 +1430,7 @@ dependencies = [
[[package]] [[package]]
name = "krilla" name = "krilla"
version = "0.4.0" 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 = [ dependencies = [
"base64", "base64",
"bumpalo", "bumpalo",
@ -1460,7 +1460,7 @@ dependencies = [
[[package]] [[package]]
name = "krilla-svg" name = "krilla-svg"
version = "0.1.0" 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 = [ dependencies = [
"flate2", "flate2",
"fontdb", "fontdb",

View File

@ -1,4 +1,4 @@
use krilla::tagging::{ListNumbering, TagKind}; use krilla::tagging::{ListNumbering, Tag, TagKind};
use crate::tags::TagNode; use crate::tags::TagNode;
@ -61,7 +61,7 @@ impl ListCtx {
// //
// So move the nested list out of the list item. // So move the nested list out of the list item.
if let [_, TagNode::Group(tag, _)] = nodes.as_slice() { if let [_, TagNode::Group(tag, _)] = nodes.as_slice() {
if matches!(tag.kind, TagKind::L(_)) { if let TagKind::L(_) = tag {
item.sub_list = nodes.pop(); item.sub_list = nodes.pop();
} }
} }
@ -70,7 +70,7 @@ impl ListCtx {
} }
pub(crate) fn push_bib_entry(&mut self, nodes: Vec<TagNode>) { pub(crate) fn push_bib_entry(&mut self, nodes: Vec<TagNode>) {
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. // Bibliography lists cannot be nested, but may be missing labels.
if let Some(item) = self.items.last_mut().filter(|item| item.body.is_none()) { if let Some(item) = self.items.last_mut().filter(|item| item.body.is_none()) {
item.body = Some(nodes); item.body = Some(nodes);
@ -86,16 +86,16 @@ impl ListCtx {
pub(crate) fn build_list(self, mut nodes: Vec<TagNode>) -> TagNode { pub(crate) fn build_list(self, mut nodes: Vec<TagNode>) -> TagNode {
for item in self.items.into_iter() { for item in self.items.into_iter() {
nodes.push(TagNode::Group( nodes.push(TagNode::Group(
TagKind::LI.into(), Tag::LI.into(),
vec![ vec![
TagNode::Group(TagKind::Lbl.into(), item.label), TagNode::Group(Tag::Lbl.into(), item.label),
TagNode::Group(TagKind::LBody.into(), item.body.unwrap_or_default()), TagNode::Group(Tag::LBody.into(), item.body.unwrap_or_default()),
], ],
)); ));
if let Some(sub_list) = item.sub_list { if let Some(sub_list) = item.sub_list {
nodes.push(sub_list); nodes.push(sub_list);
} }
} }
TagNode::Group(TagKind::L(self.numbering).into(), nodes) TagNode::Group(Tag::L(self.numbering).into(), nodes)
} }
} }

View File

@ -8,8 +8,8 @@ use krilla::configure::Validator;
use krilla::page::Page; use krilla::page::Page;
use krilla::surface::Surface; use krilla::surface::Surface;
use krilla::tagging::{ use krilla::tagging::{
ArtifactType, ContentTag, Identifier, ListNumbering, Node, SpanTag, TableDataCell, ArtifactType, ContentTag, Identifier, ListNumbering, Node, SpanTag, Tag, TagGroup,
Tag, TagBuilder, TagGroup, TagKind, TagTree, TagKind, TagTree,
}; };
use typst_library::diag::SourceResult; use typst_library::diag::SourceResult;
use typst_library::foundations::{ use typst_library::foundations::{
@ -58,13 +58,13 @@ pub(crate) fn handle_start(
return Ok(()); return Ok(());
} }
let tag: Tag = if let Some(tag) = elem.to_packed::<PdfMarkerTag>() { let mut tag: TagKind = if let Some(tag) = elem.to_packed::<PdfMarkerTag>() {
match tag.kind { match tag.kind {
PdfMarkerTagKind::OutlineBody => { PdfMarkerTagKind::OutlineBody => {
push_stack(gc, loc, StackEntryKind::Outline(OutlineCtx::new()))?; push_stack(gc, loc, StackEntryKind::Outline(OutlineCtx::new()))?;
return Ok(()); return Ok(());
} }
PdfMarkerTagKind::FigureBody => TagKind::Figure.into(), PdfMarkerTagKind::FigureBody => Tag::Figure(None).into(),
PdfMarkerTagKind::Bibliography(numbered) => { PdfMarkerTagKind::Bibliography(numbered) => {
let numbering = let numbering =
if numbered { ListNumbering::Decimal } else { ListNumbering::None }; if numbered { ListNumbering::Decimal } else { ListNumbering::None };
@ -83,7 +83,7 @@ pub(crate) fn handle_start(
push_stack(gc, loc, StackEntryKind::ListItemBody)?; push_stack(gc, loc, StackEntryKind::ListItemBody)?;
return Ok(()); return Ok(());
} }
PdfMarkerTagKind::Label => TagKind::Lbl.into(), PdfMarkerTagKind::Label => Tag::Lbl.into(),
} }
} else if let Some(entry) = elem.to_packed::<OutlineEntry>() { } else if let Some(entry) = elem.to_packed::<OutlineEntry>() {
push_stack(gc, loc, StackEntryKind::OutlineEntry(entry.clone()))?; 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 // caption is contained within the figure like recommended for tables
// screen readers might ignore it. // screen readers might ignore it.
// TODO: maybe this could be a `NonStruct` tag? // TODO: maybe this could be a `NonStruct` tag?
TagKind::P.into() Tag::P.into()
} else if let Some(_) = elem.to_packed::<FigureCaption>() { } else if let Some(_) = elem.to_packed::<FigureCaption>() {
TagKind::Caption.into() Tag::Caption.into()
} else if let Some(image) = elem.to_packed::<ImageElem>() { } else if let Some(image) = elem.to_packed::<ImageElem>() {
let alt = image.alt.get_as_ref().map(|s| s.to_string()); let alt = image.alt.get_as_ref().map(|s| s.to_string());
let figure_tag = (gc.tags.stack.parent()) let figure_tag = gc.tags.stack.parent().and_then(StackEntryKind::as_standard_mut);
.and_then(StackEntryKind::as_standard_mut) if let Some(TagKind::Figure(figure_tag)) = figure_tag {
.filter(|tag| tag.kind == TagKind::Figure);
if let Some(figure_tag) = figure_tag {
// Set alt text of outer figure tag, if not present. // Set alt text of outer figure tag, if not present.
if figure_tag.alt_text.is_none() { if figure_tag.alt_text().is_none() {
figure_tag.alt_text = alt; figure_tag.set_alt_text(alt);
} }
return Ok(()); return Ok(());
} else { } else {
TagKind::Figure.with_alt_text(alt) Tag::Figure(alt).into()
} }
} else if let Some(equation) = elem.to_packed::<EquationElem>() { } else if let Some(equation) = elem.to_packed::<EquationElem>() {
let alt = equation.alt.get_as_ref().map(|s| s.to_string()); 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::<TableElem>() { } else if let Some(table) = elem.to_packed::<TableElem>() {
let table_id = gc.tags.next_table_id(); let table_id = gc.tags.next_table_id();
let summary = table.summary.get_as_ref().map(|s| s.to_string()); 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::<HeadingElem>() { } else if let Some(heading) = elem.to_packed::<HeadingElem>() {
let level = heading.level().try_into().unwrap_or(NonZeroU32::MAX); let level = heading.level().try_into().unwrap_or(NonZeroU32::MAX);
let name = heading.body.plain_text().to_string(); 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::<LinkMarker>() { } else if let Some(link) = elem.to_packed::<LinkMarker>() {
let link_id = gc.tags.next_link_id(); let link_id = gc.tags.next_link_id();
push_stack(gc, loc, StackEntryKind::Link(link_id, link.clone()))?; 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::<QuoteElem>() { } else if let Some(quote) = elem.to_packed::<QuoteElem>() {
// TODO: should the attribution be handled somehow? // TODO: should the attribution be handled somehow?
if quote.block.get(StyleChain::default()) { if quote.block.get(StyleChain::default()) {
TagKind::BlockQuote.into() Tag::BlockQuote.into()
} else { } else {
TagKind::InlineQuote.into() Tag::InlineQuote.into()
} }
} else { } else {
return Ok(()); 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))?; push_stack(gc, loc, StackEntryKind::Standard(tag))?;
Ok(()) 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 // PDF/UA compliance of the structure hierarchy is checked
// elsewhere. While this doesn't make a lot of sense, just // elsewhere. While this doesn't make a lot of sense, just
// avoid crashing here. // avoid crashing here.
let tag = TagKind::TOCI gc.tags.push(TagNode::Group(Tag::TOCI.into(), entry.nodes));
.with_location(Some(outline_entry.span().into_raw().get()));
gc.tags.push(TagNode::Group(tag, entry.nodes));
return; 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 // PDF/UA compliance of the structure hierarchy is checked
// elsewhere. While this doesn't make a lot of sense, just // elsewhere. While this doesn't make a lot of sense, just
// avoid crashing here. // avoid crashing here.
let tag = TagKind::TD(TableDataCell::new()) let tag = Tag::TD.with_location(Some(cell.span().into_raw().get()));
.with_location(Some(cell.span().into_raw().get())); gc.tags.push(TagNode::Group(tag.into(), entry.nodes));
gc.tags.push(TagNode::Group(tag, entry.nodes));
return; return;
}; };
@ -245,11 +240,11 @@ pub(crate) fn handle_end(gc: &mut GlobalContext, surface: &mut Surface, loc: Loc
} }
StackEntryKind::Link(_, link) => { StackEntryKind::Link(_, link) => {
let alt = link.alt.as_ref().map(EcoString::to_string); let alt = link.alt.as_ref().map(EcoString::to_string);
let tag = TagKind::Link.with_alt_text(alt); let tag = Tag::Link.with_alt_text(alt);
let mut node = TagNode::Group(tag, entry.nodes); let mut node = TagNode::Group(tag.into(), entry.nodes);
// Wrap link in reference tag, if it's not a url. // Wrap link in reference tag, if it's not a url.
if let Destination::Position(_) | Destination::Location(_) = link.dest { 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 node
} }
@ -262,7 +257,7 @@ pub(crate) fn handle_end(gc: &mut GlobalContext, surface: &mut Surface, loc: Loc
StackEntryKind::FootNoteEntry(footnote_loc) => { StackEntryKind::FootNoteEntry(footnote_loc) => {
// Store footnotes separately so they can be inserted directly after // Store footnotes separately so they can be inserted directly after
// the footnote reference in the reading order. // 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); gc.tags.footnotes.insert(footnote_loc, tag);
return; return;
} }
@ -518,7 +513,7 @@ pub(crate) struct StackEntry {
#[derive(Debug)] #[derive(Debug)]
pub(crate) enum StackEntryKind { pub(crate) enum StackEntryKind {
Standard(Tag), Standard(TagKind),
Outline(OutlineCtx), Outline(OutlineCtx),
OutlineEntry(Packed<OutlineEntry>), OutlineEntry(Packed<OutlineEntry>),
Table(TableCtx), Table(TableCtx),
@ -536,7 +531,7 @@ pub(crate) enum StackEntryKind {
} }
impl 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 } 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 { pub(crate) enum TagNode {
Group(Tag, Vec<TagNode>), Group(TagKind, Vec<TagNode>),
Leaf(Identifier), Leaf(Identifier),
/// Allows inserting a placeholder into the tag tree. /// Allows inserting a placeholder into the tag tree.
/// Currently used for [`krilla::page::Page::add_tagged_annotation`]. /// Currently used for [`krilla::page::Page::add_tagged_annotation`].

View File

@ -1,4 +1,4 @@
use krilla::tagging::TagKind; use krilla::tagging::Tag;
use typst_library::foundations::Packed; use typst_library::foundations::Packed;
use typst_library::model::OutlineEntry; 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); self.push(outline_nodes, section_entry);
} }
@ -49,7 +49,7 @@ impl OutlineCtx {
while !self.stack.is_empty() { while !self.stack.is_empty() {
self.finish_section(&mut outline_nodes); 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 { fn into_tag(self) -> TagNode {
TagNode::Group(TagKind::TOC.into(), self.entries) TagNode::Group(Tag::TOC.into(), self.entries)
} }
} }

View File

@ -2,9 +2,7 @@ use std::io::Write as _;
use std::num::NonZeroU32; use std::num::NonZeroU32;
use az::SaturatingAs; use az::SaturatingAs;
use krilla::tagging::{ use krilla::tagging::{Tag, TagId, TagKind};
TableCellSpan, TableDataCell, TableHeaderCell, TagBuilder, TagId, TagKind,
};
use smallvec::SmallVec; use smallvec::SmallVec;
use typst_library::foundations::{Packed, Smart, StyleChain}; use typst_library::foundations::{Packed, Smart, StyleChain};
use typst_library::model::TableCell; use typst_library::model::TableCell;
@ -101,7 +99,7 @@ impl TableCtx {
// Table layouting ensures that there are no overlapping cells, and that // Table layouting ensures that there are no overlapping cells, and that
// any gaps left by the user are filled with empty cells. // any gaps left by the user are filled with empty cells.
if self.rows.is_empty() { 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 height = self.rows.len();
let width = self.rows[0].len(); let width = self.rows[0].len();
@ -166,31 +164,32 @@ impl TableCtx {
.into_iter() .into_iter()
.filter_map(|cell| { .filter_map(|cell| {
let cell = cell.into_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() { let tag = match cell.unwrap_kind() {
TableCellKind::Header(_, scope) => { TableCellKind::Header(_, scope) => {
let id = table_cell_id(self.id, cell.x, cell.y); let id = table_cell_id(self.id, cell.x, cell.y);
let scope = table_header_scope(scope); let scope = table_header_scope(scope);
TagKind::TH( Tag::TH(scope)
TableHeaderCell::new(scope)
.with_span(span)
.with_headers(cell.headers),
)
.with_id(Some(id)) .with_id(Some(id))
.with_headers(cell.headers)
.with_row_span(rowspan)
.with_col_span(colspan)
.with_location(Some(cell.span.into_raw().get())) .with_location(Some(cell.span.into_raw().get()))
.into()
} }
TableCellKind::Footer | TableCellKind::Data => TagKind::TD( TableCellKind::Footer | TableCellKind::Data => Tag::TD
TableDataCell::new() .with_headers(cell.headers)
.with_span(span) .with_row_span(rowspan)
.with_headers(cell.headers), .with_col_span(colspan)
) .with_location(Some(cell.span.into_raw().get()))
.with_location(Some(cell.span.into_raw().get())), .into(),
}; };
Some(TagNode::Group(tag, cell.nodes)) Some(TagNode::Group(tag, cell.nodes))
}) })
.collect(); .collect();
let row = TagNode::Group(TagKind::TR.into(), row_nodes); let row = TagNode::Group(Tag::TR.into(), row_nodes);
// Push the `TR` tags directly. // Push the `TR` tags directly.
if !gen_row_groups { if !gen_row_groups {
@ -200,10 +199,10 @@ impl TableCtx {
// Generate row groups. // Generate row groups.
if !should_group_rows(chunk_kind, row_kind) { if !should_group_rows(chunk_kind, row_kind) {
let tag = match chunk_kind { let tag: TagKind = match chunk_kind {
TableCellKind::Header(..) => TagKind::THead, TableCellKind::Header(..) => Tag::THead.into(),
TableCellKind::Footer => TagKind::TFoot, TableCellKind::Footer => Tag::TFoot.into(),
TableCellKind::Data => TagKind::TBody, TableCellKind::Data => Tag::TBody.into(),
}; };
nodes.push(TagNode::Group(tag.into(), std::mem::take(&mut row_chunk))); nodes.push(TagNode::Group(tag.into(), std::mem::take(&mut row_chunk)));
@ -213,15 +212,15 @@ impl TableCtx {
} }
if !row_chunk.is_empty() { if !row_chunk.is_empty() {
let tag = match chunk_kind { let tag: TagKind = match chunk_kind {
TableCellKind::Header(..) => TagKind::THead, TableCellKind::Header(..) => Tag::THead.into(),
TableCellKind::Footer => TagKind::TFoot, TableCellKind::Footer => Tag::TFoot.into(),
TableCellKind::Data => TagKind::TBody, TableCellKind::Data => Tag::TBody.into(),
}; };
nodes.push(TagNode::Group(tag.into(), row_chunk)); 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<F>( fn resolve_cell_headers<F>(
@ -379,24 +378,24 @@ mod tests {
} }
fn table_tag<const SIZE: usize>(nodes: [TagNode; SIZE]) -> TagNode { fn table_tag<const SIZE: usize>(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()) TagNode::Group(tag.into(), nodes.into())
} }
fn thead<const SIZE: usize>(nodes: [TagNode; SIZE]) -> TagNode { fn thead<const SIZE: usize>(nodes: [TagNode; SIZE]) -> TagNode {
TagNode::Group(TagKind::THead.into(), nodes.into()) TagNode::Group(Tag::THead.into(), nodes.into())
} }
fn tbody<const SIZE: usize>(nodes: [TagNode; SIZE]) -> TagNode { fn tbody<const SIZE: usize>(nodes: [TagNode; SIZE]) -> TagNode {
TagNode::Group(TagKind::TBody.into(), nodes.into()) TagNode::Group(Tag::TBody.into(), nodes.into())
} }
fn tfoot<const SIZE: usize>(nodes: [TagNode; SIZE]) -> TagNode { fn tfoot<const SIZE: usize>(nodes: [TagNode; SIZE]) -> TagNode {
TagNode::Group(TagKind::TFoot.into(), nodes.into()) TagNode::Group(Tag::TFoot.into(), nodes.into())
} }
fn trow<const SIZE: usize>(nodes: [TagNode; SIZE]) -> TagNode { fn trow<const SIZE: usize>(nodes: [TagNode; SIZE]) -> TagNode {
TagNode::Group(TagKind::TR.into(), nodes.into()) TagNode::Group(Tag::TR.into(), nodes.into())
} }
fn th<const SIZE: usize>( fn th<const SIZE: usize>(
@ -408,9 +407,11 @@ mod tests {
let id = table_cell_id(TableId(324), x, y); let id = table_cell_id(TableId(324), x, y);
let ids = headers.map(|(x, y)| table_cell_id(TableId(324), x, y)); let ids = headers.map(|(x, y)| table_cell_id(TableId(324), x, y));
TagNode::Group( TagNode::Group(
TagKind::TH(TableHeaderCell::new(scope).with_headers(ids)) Tag::TH(scope)
.with_id(Some(id)) .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(), Vec::new(),
) )
} }
@ -418,8 +419,10 @@ mod tests {
fn td<const SIZE: usize>(headers: [(u32, u32); SIZE]) -> TagNode { fn td<const SIZE: usize>(headers: [(u32, u32); SIZE]) -> TagNode {
let ids = headers.map(|(x, y)| table_cell_id(TableId(324), x, y)); let ids = headers.map(|(x, y)| table_cell_id(TableId(324), x, y));
TagNode::Group( TagNode::Group(
TagKind::TD(TableDataCell::new().with_headers(ids)) Tag::TD
.with_location(Some(Span::detached().into_raw().get())), .with_headers(ids)
.with_location(Some(Span::detached().into_raw().get()))
.into(),
Vec::new(), Vec::new(),
) )
} }