From edd213074f476b283374c4e6bcb39ede5cf17e39 Mon Sep 17 00:00:00 2001 From: Tobias Schmitz Date: Tue, 8 Jul 2025 14:14:37 +0200 Subject: [PATCH] refactor: remove general api to set cell kind and add pdf.(header|data)-cell --- .../typst-library/src/layout/grid/resolve.rs | 4 +- crates/typst-library/src/model/table.rs | 71 ++---------------- crates/typst-library/src/pdf/accessibility.rs | 74 ++++++++++++++++++- crates/typst-library/src/pdf/mod.rs | 2 + crates/typst-pdf/src/tags/mod.rs | 2 +- crates/typst-pdf/src/tags/table.rs | 9 ++- 6 files changed, 86 insertions(+), 76 deletions(-) diff --git a/crates/typst-library/src/layout/grid/resolve.rs b/crates/typst-library/src/layout/grid/resolve.rs index 49f9e0edd..0724280b0 100644 --- a/crates/typst-library/src/layout/grid/resolve.rs +++ b/crates/typst-library/src/layout/grid/resolve.rs @@ -22,7 +22,7 @@ use typst_syntax::Span; use typst_utils::NonZeroExt; use crate::introspection::SplitLocator; -use crate::model::{TableCellKind, TableHeaderScope}; +use crate::pdf::{TableCellKind, TableHeaderScope}; /// Convert a grid to a cell grid. #[typst_macros::time(span = elem.span())] @@ -226,7 +226,7 @@ impl ResolvableCell for Packed { let breakable = cell.breakable(styles).unwrap_or(breakable); let fill = cell.fill(styles).unwrap_or_else(|| fill.clone()); - let kind = cell.kind(styles).or(kind); + let kind = cell.kind().copied().unwrap_or_default().or(kind); let cell_stroke = cell.stroke(styles); let stroke_overridden = diff --git a/crates/typst-library/src/model/table.rs b/crates/typst-library/src/model/table.rs index f8fe76918..7aacf07fa 100644 --- a/crates/typst-library/src/model/table.rs +++ b/crates/typst-library/src/model/table.rs @@ -2,14 +2,13 @@ use std::num::{NonZeroU32, NonZeroUsize}; use std::sync::Arc; use ecow::EcoString; -use typst_macros::Cast; use typst_utils::NonZeroExt; use crate::diag::{bail, HintedStrResult, HintedString, SourceResult}; use crate::engine::Engine; use crate::foundations::{ - cast, dict, elem, scope, Content, Dict, NativeElement, Packed, Show, Smart, - StyleChain, TargetElem, + cast, elem, scope, Content, NativeElement, Packed, Show, Smart, StyleChain, + TargetElem, }; use crate::html::{attr, tag, HtmlAttrs, HtmlElem, HtmlTag}; use crate::introspection::{Locatable, Locator}; @@ -20,6 +19,7 @@ use crate::layout::{ TrackSizings, }; use crate::model::Figurable; +use crate::pdf::TableCellKind; use crate::text::LocalName; use crate::visualize::{Paint, Stroke}; @@ -811,7 +811,8 @@ pub struct TableCell { #[fold] pub stroke: Sides>>>, - // TODO: feature gate + #[internal] + #[synthesized] pub kind: Smart, /// Whether rows spanned by this cell can be placed in different pages. @@ -851,65 +852,3 @@ impl From for TableCell { value.unpack::().unwrap_or_else(Self::new) } } - -#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)] -pub enum TableCellKind { - Header(NonZeroU32, TableHeaderScope), - Footer, - #[default] - Data, -} - -cast! { - TableCellKind, - self => match self { - Self::Header(level, scope) => dict! { "level" => level, "scope" => scope }.into_value(), - Self::Footer => "footer".into_value(), - Self::Data => "data".into_value(), - }, - "header" => Self::Header(NonZeroU32::ONE, TableHeaderScope::default()), - "footer" => Self::Footer, - "data" => Self::Data, - mut dict: Dict => { - // TODO: have a `pdf.header` function instead? - #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)] - enum HeaderKind { - Header, - } - dict.take("kind")?.cast::()?; - let level = dict.take("level").ok().map(|v| v.cast()).transpose()?; - let scope = dict.take("scope").ok().map(|v| v.cast()).transpose()?; - dict.finish(&["kind", "level", "scope"])?; - Self::Header(level.unwrap_or(NonZeroU32::ONE), scope.unwrap_or_default()) - }, -} - -/// The scope of a table header cell. -#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Cast)] -pub enum TableHeaderScope { - /// The header cell refers to both the row and the column. - Both, - /// The header cell refers to the column. - #[default] - Column, - /// The header cell refers to the row. - Row, -} - -impl TableHeaderScope { - pub fn refers_to_column(&self) -> bool { - match self { - TableHeaderScope::Both => true, - TableHeaderScope::Column => true, - TableHeaderScope::Row => false, - } - } - - pub fn refers_to_row(&self) -> bool { - match self { - TableHeaderScope::Both => true, - TableHeaderScope::Column => false, - TableHeaderScope::Row => true, - } - } -} diff --git a/crates/typst-library/src/pdf/accessibility.rs b/crates/typst-library/src/pdf/accessibility.rs index 7ec52f8cb..9399c1c60 100644 --- a/crates/typst-library/src/pdf/accessibility.rs +++ b/crates/typst-library/src/pdf/accessibility.rs @@ -1,11 +1,14 @@ +use std::num::NonZeroU32; + use ecow::EcoString; -use typst_macros::{cast, elem, Cast}; +use typst_macros::{cast, elem, func, Cast}; +use typst_utils::NonZeroExt; use crate::diag::SourceResult; use crate::engine::Engine; -use crate::foundations::{Content, Packed, Show, StyleChain}; +use crate::foundations::{Content, NativeElement, Packed, Show, Smart, StyleChain}; use crate::introspection::Locatable; -use crate::model::TableHeaderScope; +use crate::model::TableCell; // TODO: docs #[elem(Locatable, Show)] @@ -210,3 +213,68 @@ impl Show for Packed { Ok(self.body.clone()) } } + +// TODO: feature gate +/// Explicity define this cell as a header cell. +#[func] +pub fn header_cell( + #[named] + #[default(NonZeroU32::ONE)] + level: NonZeroU32, + #[named] + #[default] + scope: TableHeaderScope, + /// The table cell. + cell: TableCell, +) -> Content { + cell.with_kind(Smart::Custom(TableCellKind::Header(level, scope))) + .pack() +} + +// TODO: feature gate +/// Explicity define this cell as a data cell. +#[func] +pub fn data_cell( + /// The table cell. + cell: TableCell, +) -> Content { + cell.with_kind(Smart::Custom(TableCellKind::Data)).pack() +} + +#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)] +pub enum TableCellKind { + Header(NonZeroU32, TableHeaderScope), + Footer, + #[default] + Data, +} + +/// The scope of a table header cell. +#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Cast)] +pub enum TableHeaderScope { + /// The header cell refers to both the row and the column. + Both, + /// The header cell refers to the column. + #[default] + Column, + /// The header cell refers to the row. + Row, +} + +impl TableHeaderScope { + pub fn refers_to_column(&self) -> bool { + match self { + TableHeaderScope::Both => true, + TableHeaderScope::Column => true, + TableHeaderScope::Row => false, + } + } + + pub fn refers_to_row(&self) -> bool { + match self { + TableHeaderScope::Both => true, + TableHeaderScope::Column => false, + TableHeaderScope::Row => true, + } + } +} diff --git a/crates/typst-library/src/pdf/mod.rs b/crates/typst-library/src/pdf/mod.rs index 952e7fe32..8a0d40b9c 100644 --- a/crates/typst-library/src/pdf/mod.rs +++ b/crates/typst-library/src/pdf/mod.rs @@ -15,5 +15,7 @@ pub fn module() -> Module { pdf.define_elem::(); pdf.define_elem::(); pdf.define_elem::(); + pdf.define_func::(); + pdf.define_func::(); Module::new("pdf", pdf) } diff --git a/crates/typst-pdf/src/tags/mod.rs b/crates/typst-pdf/src/tags/mod.rs index 5aa4d3904..fd9e9394a 100644 --- a/crates/typst-pdf/src/tags/mod.rs +++ b/crates/typst-pdf/src/tags/mod.rs @@ -181,7 +181,7 @@ pub(crate) fn handle_end(gc: &mut GlobalContext, loc: Location) { return; }; - table_ctx.insert(cell, entry.nodes); + table_ctx.insert(&cell, entry.nodes); return; } StackEntryKind::Link(_, link) => { diff --git a/crates/typst-pdf/src/tags/table.rs b/crates/typst-pdf/src/tags/table.rs index 177de9624..fc3ab8a5d 100644 --- a/crates/typst-pdf/src/tags/table.rs +++ b/crates/typst-pdf/src/tags/table.rs @@ -7,7 +7,8 @@ use krilla::tagging::{ }; use smallvec::SmallVec; use typst_library::foundations::{Packed, Smart, StyleChain}; -use typst_library::model::{TableCell, TableCellKind, TableHeaderScope}; +use typst_library::model::TableCell; +use typst_library::pdf::{TableCellKind, TableHeaderScope}; use crate::tags::{TableId, TagNode}; @@ -54,12 +55,12 @@ impl TableCtx { } } - pub(crate) fn insert(&mut self, cell: Packed, nodes: Vec) { + pub(crate) fn insert(&mut self, cell: &TableCell, nodes: Vec) { let x = cell.x(StyleChain::default()).unwrap_or_else(|| unreachable!()); let y = cell.y(StyleChain::default()).unwrap_or_else(|| unreachable!()); let rowspan = cell.rowspan(StyleChain::default()); let colspan = cell.colspan(StyleChain::default()); - let kind = cell.kind(StyleChain::default()); + let kind = cell.kind().copied().expect("kind to be set after layouting"); // Extend the table grid to fit this cell. let required_height = y + rowspan.get(); @@ -344,7 +345,7 @@ mod tests { fn table(cells: [TableCell; SIZE]) -> TableCtx { let mut table = TableCtx::new(TableId(324), Some("summary".into())); for cell in cells { - table.insert(Packed::new(cell), Vec::new()); + table.insert(&cell, Vec::new()); } table }