diff --git a/src/library/structure/table.rs b/src/library/structure/table.rs index b983a7eab..f39ea9784 100644 --- a/src/library/structure/table.rs +++ b/src/library/structure/table.rs @@ -14,12 +14,9 @@ pub struct TableNode { #[node(showable)] impl TableNode { - /// The primary cell fill color. - #[property(shorthand(fill))] - pub const PRIMARY: Option = None; - /// The secondary cell fill color. - #[property(shorthand(fill))] - pub const SECONDARY: Option = None; + /// How to fill the cells. + #[property(referenced)] + pub const FILL: Celled> = Celled::Value(None); /// How to stroke the cells. #[property(resolve, fold)] pub const STROKE: Option = Some(RawStroke::default()); @@ -71,9 +68,8 @@ impl Show for TableNode { } } - fn realize(&self, _: &mut Context, styles: StyleChain) -> TypResult { - let primary = styles.get(Self::PRIMARY); - let secondary = styles.get(Self::SECONDARY); + fn realize(&self, ctx: &mut Context, styles: StyleChain) -> TypResult { + let fill = styles.get(Self::FILL); let stroke = styles.get(Self::STROKE).map(RawStroke::unwrap_or_default); let padding = styles.get(Self::PADDING); @@ -92,13 +88,13 @@ impl Show for TableNode { let x = i % cols; let y = i / cols; - if let Some(fill) = [primary, secondary][(x + y) % 2] { + if let Some(fill) = fill.resolve(ctx, x, y)? { child = child.filled(fill); } - child + Ok(child) }) - .collect(); + .collect::>()?; Ok(Content::block(GridNode { tracks: self.tracks.clone(), @@ -116,3 +112,43 @@ impl Show for TableNode { Ok(realized.spaced(styles.get(Self::ABOVE), styles.get(Self::BELOW))) } } + +/// A value that can be configured per cell. +#[derive(Debug, Clone, PartialEq, Hash)] +pub enum Celled { + /// A bare value, the same for all cells. + Value(T), + /// A closure mapping from cell coordinates to a value. + Func(Func, Span), +} + +impl Celled { + /// Resolve the value based on the cell position. + pub fn resolve(&self, ctx: &mut Context, x: usize, y: usize) -> TypResult { + Ok(match self { + Self::Value(value) => value.clone(), + Self::Func(func, span) => { + let args = Args::from_values(*span, [ + Value::Int(x as i64), + Value::Int(y as i64), + ]); + func.call(ctx, args)?.cast().at(*span)? + } + }) + } +} + +impl Cast> for Celled { + fn is(value: &Spanned) -> bool { + matches!(&value.v, Value::Func(_)) || T::is(&value.v) + } + + fn cast(value: Spanned) -> StrResult { + match value.v { + Value::Func(v) => Ok(Self::Func(v, value.span)), + v => T::cast(v) + .map(Self::Value) + .map_err(|msg| with_alternative(msg, "function")), + } + } +} diff --git a/tests/typ/structure/table.typ b/tests/typ/structure/table.typ index 57b71ede7..527141c57 100644 --- a/tests/typ/structure/table.typ +++ b/tests/typ/structure/table.typ @@ -2,7 +2,7 @@ --- #set page(height: 70pt) -#set table(primary: rgb("aaa"), secondary: none) +#set table(fill: (x, y) => if even(x + y) { rgb("aaa") }) #table( columns: (1fr,) * 3, @@ -16,3 +16,7 @@ --- // Ref: false #table() + +--- +// Error: 14-19 expected color or none or function, found string +#table(fill: "hey") diff --git a/tests/typ/text/par.typ b/tests/typ/text/par.typ index 64a2dd175..059653c50 100644 --- a/tests/typ/text/par.typ +++ b/tests/typ/text/par.typ @@ -26,7 +26,7 @@ Fourth #set par(spacing: 100pt) #set table(around: 5pt) Hello -#table(columns: 4, secondary: silver)[A][B][C][D] +#table(columns: 4, fill: (x, y) => if odd(x + y) { silver })[A][B][C][D] --- // While we're at it, test the larger block spacing wins.