From 746926c7da8187e784120043fe93e96ebd691754 Mon Sep 17 00:00:00 2001 From: Tobias Schmitz Date: Tue, 1 Jul 2025 17:47:18 +0200 Subject: [PATCH] fix: ignore repeated table headers/footers in tag tree --- crates/typst-pdf/src/tags.rs | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/crates/typst-pdf/src/tags.rs b/crates/typst-pdf/src/tags.rs index 911278e15..815b752e6 100644 --- a/crates/typst-pdf/src/tags.rs +++ b/crates/typst-pdf/src/tags.rs @@ -169,6 +169,15 @@ impl TableCtx { Self { table: table.clone(), rows: Vec::new() } } + fn contains(&self, cell: &Packed) -> bool { + let x = cell.x(StyleChain::default()).unwrap_or_else(|| unreachable!()); + let y = cell.y(StyleChain::default()).unwrap_or_else(|| unreachable!()); + + let Some(row) = self.rows.get(y) else { return false }; + let Some(cell) = row.get(x) else { return false }; + !matches!(cell, GridCell::Missing) + } + fn insert(&mut self, cell: Packed, nodes: Vec) { let x = cell.x(StyleChain::default()).unwrap_or_else(|| unreachable!()); let y = cell.y(StyleChain::default()).unwrap_or_else(|| unreachable!()); @@ -230,7 +239,7 @@ impl TableCtx { } a }) - .expect("tables must have at least one column") + .unwrap_or(TableCellKind::Data) }) .collect::>(); @@ -524,7 +533,20 @@ pub(crate) fn handle_start(gc: &mut GlobalContext, elem: &Content) { push_stack(gc, loc, StackEntryKind::Table(TableCtx::new(table.clone()))); return; } else if let Some(cell) = elem.to_packed::() { - push_stack(gc, loc, StackEntryKind::TableCell(cell.clone())); + let parent = gc.tags.stack.last_mut().expect("table"); + let StackEntryKind::Table(table_ctx) = &mut parent.kind else { + unreachable!("expected table") + }; + + // Only repeated table headers and footer cells are layed out multiple + // times. Mark duplicate headers as artifacts, since they have no + // semantic meaning in the tag tree, which doesn't use page breaks for + // it's semantic structure. + if table_ctx.contains(cell) { + start_artifact(gc, loc, ArtifactKind::Other); + } else { + push_stack(gc, loc, StackEntryKind::TableCell(cell.clone())); + } return; } else if let Some(link) = elem.to_packed::() { let link_id = gc.tags.next_link_id();