diff options
| author | Tobias Schmitz <tobiasschmitz2001@gmail.com> | 2025-07-01 17:47:18 +0200 |
|---|---|---|
| committer | Tobias Schmitz <tobiasschmitz2001@gmail.com> | 2025-07-03 18:43:15 +0200 |
| commit | 746926c7da8187e784120043fe93e96ebd691754 (patch) | |
| tree | 9abe5792caafdf6173958e8ce29680b6cd371f6d | |
| parent | 773efb5572c0eb18163001359c4e4f06d4dba5f2 (diff) | |
fix: ignore repeated table headers/footers in tag tree
| -rw-r--r-- | crates/typst-pdf/src/tags.rs | 26 |
1 files changed, 24 insertions, 2 deletions
diff --git a/crates/typst-pdf/src/tags.rs b/crates/typst-pdf/src/tags.rs index 911278e1..815b752e 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<TableCell>) -> 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<TableCell>, nodes: Vec<TagNode>) { 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::<Vec<_>>(); @@ -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::<TableCell>() { - 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::<LinkMarker>() { let link_id = gc.tags.next_link_id(); |
