diff --git a/crates/typst/src/layout/grid/layout.rs b/crates/typst/src/layout/grid/layout.rs index 790aedac7..543d736d1 100644 --- a/crates/typst/src/layout/grid/layout.rs +++ b/crates/typst/src/layout/grid/layout.rs @@ -332,7 +332,7 @@ impl CellGrid { items: I, fill: &Celled>, align: &Celled>, - inset: Sides>>, + inset: &Celled>>>, stroke: &ResolvedCelled>>>>, engine: &mut Engine, styles: StyleChain, @@ -486,7 +486,7 @@ impl CellGrid { y, &fill.resolve(engine, x, y)?, align.resolve(engine, x, y)?, - inset, + inset.resolve(engine, x, y)?, stroke.resolve(engine, styles, x, y)?, styles, ); @@ -572,7 +572,7 @@ impl CellGrid { y, &fill.resolve(engine, x, y)?, align.resolve(engine, x, y)?, - inset, + inset.resolve(engine, x, y)?, stroke.resolve(engine, styles, x, y)?, styles, ); diff --git a/crates/typst/src/layout/grid/lines.rs b/crates/typst/src/layout/grid/lines.rs index 9a4da26d3..6eb43c9a4 100644 --- a/crates/typst/src/layout/grid/lines.rs +++ b/crates/typst/src/layout/grid/lines.rs @@ -130,7 +130,7 @@ where &CellGrid, usize, usize, - Option>>, + Option>>>, ) -> Option<(Arc>, StrokePriority)> + 'grid, I: IntoIterator, @@ -204,7 +204,7 @@ where // Get the expected line stroke at this track by folding the // strokes of each user-specified line (with priority to the // user-specified line specified last). - let stroke = lines + let mut line_strokes = lines .iter() .filter(|line| { line.position == expected_line_position @@ -222,8 +222,15 @@ where }) .unwrap_or_else(|| track >= gutter_factor * line.start) }) - .map(|line| line.stroke.clone()) - .fold(None, |acc, line_stroke| line_stroke.fold(acc)); + .map(|line| line.stroke.clone()); + + // Distinguish between unspecified stroke (None, if no lines + // were matched above) and specified stroke of None (Some(None), + // if some lines were matched and the one specified last had a + // stroke of None) by conditionally folding after 'next()'. + let line_stroke = line_strokes.next().map(|first_stroke| { + line_strokes.fold(first_stroke, |acc, line_stroke| line_stroke.fold(acc)) + }); // The function shall determine if it is appropriate to draw // the line at this position or not (i.e. whether or not it @@ -240,7 +247,7 @@ where // (which indicates, in the context of 'std::iter::from_fn', that // our iterator isn't over yet, and this should be its next value). if let Some((stroke, priority)) = - line_stroke_at_track(grid, index, track, stroke) + line_stroke_at_track(grid, index, track, line_stroke) { // We should draw at this position. Let's check if we were // already drawing in the previous position. @@ -301,9 +308,10 @@ where } /// Returns the correct stroke with which to draw a vline right before column -/// 'x' when going through row 'y', given the stroke of the user-specified line -/// at this position, if any. Also returns the stroke's drawing priority, which -/// depends on its source. +/// `x` when going through row `y`, given the stroke of the user-specified line +/// at this position, if any (note that a stroke of `None` is unspecified, +/// while `Some(None)` means specified to remove any stroke at this position). +/// Also returns the stroke's drawing priority, which depends on its source. /// /// If the vline would go through a colspan, returns None (shouldn't be drawn). /// If the one (when at the border) or two (otherwise) cells to the left and @@ -317,12 +325,12 @@ where /// stroke, as defined by user-specified lines (if any), is returned. /// /// The priority associated with the returned stroke follows the rules -/// described in the docs for 'generate_line_segment'. +/// described in the docs for `generate_line_segment`. pub(super) fn vline_stroke_at_row( grid: &CellGrid, x: usize, y: usize, - stroke: Option>>, + stroke: Option>>>, ) -> Option<(Arc>, StrokePriority)> { if x != 0 && x != grid.cols.len() { // When the vline isn't at the border, we need to check if a colspan would @@ -397,15 +405,16 @@ pub(super) fn vline_stroke_at_row( // Fold the line stroke and folded cell strokes, if possible. // Give priority to the explicit line stroke. // Otherwise, use whichever of the two isn't 'none' or unspecified. - let final_stroke = stroke.fold_or(cell_stroke); + let final_stroke = stroke.fold_or(Some(cell_stroke)).flatten(); final_stroke.zip(Some(priority)) } -/// Returns the correct stroke with which to draw a hline on top of row 'y' -/// when going through column 'x', given the stroke of the user-specified line -/// at this position, if any. Also returns the stroke's drawing priority, which -/// depends on its source. +/// Returns the correct stroke with which to draw a hline on top of row `y` +/// when going through column `x`, given the stroke of the user-specified line +/// at this position, if any (note that a stroke of `None` is unspecified, +/// while `Some(None)` means specified to remove any stroke at this position). +/// Also returns the stroke's drawing priority, which depends on its source. /// /// If the one (when at the border) or two (otherwise) cells above and below /// the hline have bottom and top stroke overrides, respectively, then the @@ -418,12 +427,12 @@ pub(super) fn vline_stroke_at_row( /// defined by user-specified lines (if any), is directly returned. /// /// The priority associated with the returned stroke follows the rules -/// described in the docs for 'generate_line_segment'. +/// described in the docs for `generate_line_segment`. pub(super) fn hline_stroke_at_column( grid: &CellGrid, y: usize, x: usize, - stroke: Option>>, + stroke: Option>>>, ) -> Option<(Arc>, StrokePriority)> { // There are no rowspans yet, so no need to add a check here. The line will // always be drawn, if it has a stroke. @@ -505,7 +514,7 @@ pub(super) fn hline_stroke_at_column( // Fold the line stroke and folded cell strokes, if possible. // Give priority to the explicit line stroke. // Otherwise, use whichever of the two isn't 'none' or unspecified. - let final_stroke = stroke.fold_or(cell_stroke); + let final_stroke = stroke.fold_or(Some(cell_stroke)).flatten(); final_stroke.zip(Some(priority)) } diff --git a/crates/typst/src/layout/grid/mod.rs b/crates/typst/src/layout/grid/mod.rs index 0ddecd841..fc884e806 100644 --- a/crates/typst/src/layout/grid/mod.rs +++ b/crates/typst/src/layout/grid/mod.rs @@ -10,7 +10,7 @@ use std::sync::Arc; use ecow::eco_format; use smallvec::{smallvec, SmallVec}; -use crate::diag::{SourceResult, StrResult, Trace, Tracepoint}; +use crate::diag::{bail, SourceResult, StrResult, Trace, Tracepoint}; use crate::engine::Engine; use crate::foundations::{ cast, elem, scope, Array, Content, Fold, Packed, Show, Smart, StyleChain, Value, @@ -19,6 +19,7 @@ use crate::layout::{ Abs, AlignElem, Alignment, Axes, Dir, Fragment, LayoutMultiple, Length, OuterHAlignment, OuterVAlignment, Regions, Rel, Sides, Sizing, }; +use crate::model::{TableCell, TableHLine, TableVLine}; use crate::syntax::Span; use crate::text::TextElem; use crate::util::NonZeroExt; @@ -270,7 +271,7 @@ pub struct GridElem { /// ) /// ``` #[fold] - pub inset: Sides>>, + pub inset: Celled>>>, /// The contents of the grid cells, plus any extra grid lines specified /// with the [`grid.hline`]($grid.hline) and [`grid.vline`]($grid.vline) @@ -353,7 +354,7 @@ impl LayoutMultiple for Packed { items, fill, align, - inset, + &inset, &stroke, engine, styles, @@ -395,7 +396,24 @@ cast! { Self::VLine(vline) => vline.into_value(), Self::Cell(cell) => cell.into_value(), }, - v: Content => v.into(), + v: Content => { + if v.is::() { + bail!( + "cannot use `table.cell` as a grid cell; use `grid.cell` instead" + ); + } + if v.is::() { + bail!( + "cannot use `table.hline` as a grid line; use `grid.hline` instead" + ); + } + if v.is::() { + bail!( + "cannot use `table.vline` as a grid line; use `grid.vline` instead" + ); + } + v.into() + } } impl From for GridChild { diff --git a/crates/typst/src/model/table.rs b/crates/typst/src/model/table.rs index ae4dbce76..a51fd1e7f 100644 --- a/crates/typst/src/model/table.rs +++ b/crates/typst/src/model/table.rs @@ -3,15 +3,16 @@ use std::sync::Arc; use ecow::eco_format; -use crate::diag::{SourceResult, Trace, Tracepoint}; +use crate::diag::{bail, SourceResult, Trace, Tracepoint}; use crate::engine::Engine; use crate::foundations::{ cast, elem, scope, Content, Fold, Packed, Show, Smart, StyleChain, }; use crate::layout::{ show_grid_cell, Abs, Alignment, Axes, Cell, CellGrid, Celled, Dir, Fragment, - GridItem, GridLayouter, LayoutMultiple, Length, LinePosition, OuterHAlignment, - OuterVAlignment, Regions, Rel, ResolvableCell, Sides, TrackSizings, + GridCell, GridHLine, GridItem, GridLayouter, GridVLine, LayoutMultiple, Length, + LinePosition, OuterHAlignment, OuterVAlignment, Regions, Rel, ResolvableCell, Sides, + TrackSizings, }; use crate::model::Figurable; use crate::syntax::Span; @@ -200,8 +201,8 @@ pub struct TableElem { /// ) /// ``` #[fold] - #[default(Sides::splat(Some(Abs::pt(5.0).into())))] - pub inset: Sides>>, + #[default(Celled::Value(Sides::splat(Some(Abs::pt(5.0).into()))))] + pub inset: Celled>>>, /// The contents of the table cells, plus any extra table lines specified /// with the [`table.hline`]($table.hline) and @@ -282,7 +283,7 @@ impl LayoutMultiple for Packed { items, fill, align, - inset, + &inset, &stroke, engine, styles, @@ -349,7 +350,24 @@ cast! { Self::VLine(vline) => vline.into_value(), Self::Cell(cell) => cell.into_value(), }, - v: Content => v.into(), + v: Content => { + if v.is::() { + bail!( + "cannot use `grid.cell` as a table cell; use `table.cell` instead" + ); + } + if v.is::() { + bail!( + "cannot use `grid.hline` as a table line; use `table.hline` instead" + ); + } + if v.is::() { + bail!( + "cannot use `grid.vline` as a table line; use `table.vline` instead" + ); + } + v.into() + } } impl From for TableChild { diff --git a/tests/ref/layout/grid-stroke.png b/tests/ref/layout/grid-stroke.png index c31519e62..0f0b562a9 100644 Binary files a/tests/ref/layout/grid-stroke.png and b/tests/ref/layout/grid-stroke.png differ diff --git a/tests/ref/layout/grid-styling.png b/tests/ref/layout/grid-styling.png index c626d2e7a..dc50dd901 100644 Binary files a/tests/ref/layout/grid-styling.png and b/tests/ref/layout/grid-styling.png differ diff --git a/tests/ref/layout/table.png b/tests/ref/layout/table.png index f2a9d1049..ddd0e0434 100644 Binary files a/tests/ref/layout/table.png and b/tests/ref/layout/table.png differ diff --git a/tests/typ/layout/grid-cell.typ b/tests/typ/layout/grid-cell.typ index 425d036c7..a812fefc5 100644 --- a/tests/typ/layout/grid-cell.typ +++ b/tests/typ/layout/grid-cell.typ @@ -127,3 +127,7 @@ [Jake], [49], [Epic] ) } + +--- +// Error: 7-19 cannot use `table.cell` as a grid cell; use `grid.cell` instead +#grid(table.cell[]) diff --git a/tests/typ/layout/grid-stroke.typ b/tests/typ/layout/grid-stroke.typ index 87389ad56..51b810d9e 100644 --- a/tests/typ/layout/grid-stroke.typ +++ b/tests/typ/layout/grid-stroke.typ @@ -139,7 +139,7 @@ --- // Line specification order priority // The last line should be blue, not red. -// The middle line should have disappeared. +// The middle aqua line should be gone due to the 'none' override. #grid( columns: 2, inset: 2pt, @@ -344,6 +344,22 @@ table.vline(x: 3) ) +--- +// Error: 7-20 cannot use `table.hline` as a grid line; use `grid.hline` instead +#grid(table.hline()) + +--- +// Error: 7-20 cannot use `table.vline` as a grid line; use `grid.vline` instead +#grid(table.vline()) + +--- +// Error: 8-20 cannot use `grid.hline` as a table line; use `table.hline` instead +#table(grid.hline()) + +--- +// Error: 8-20 cannot use `grid.vline` as a table line; use `table.vline` instead +#table(grid.vline()) + --- // Error: 3:3-3:31 line cannot end before it starts #grid( diff --git a/tests/typ/layout/grid-styling.typ b/tests/typ/layout/grid-styling.typ index e076d0c4c..f83c3cc4a 100644 --- a/tests/typ/layout/grid-styling.typ +++ b/tests/typ/layout/grid-styling.typ @@ -88,6 +88,32 @@ a [B], ) +#grid( + columns: 3, + fill: (x, y) => (if y == 0 { aqua } else { orange }).darken(x * 15%), + inset: (x, y) => (left: if x == 0 { 0pt } else { 5pt }, right: if x == 0 { 5pt } else { 0pt }, y: if y == 0 { 0pt } else { 5pt }), + [A], [B], [C], + [A], [B], [C], +) + +#grid( + columns: 3, + inset: (0pt, 5pt, 10pt), + fill: (x, _) => aqua.darken(x * 15%), + [A], [B], [C], +) + +--- +// Test inset folding +#set grid(inset: 10pt) +#set grid(inset: (left: 0pt)) + +#grid( + fill: red, + inset: (right: 0pt), + grid.cell(inset: (top: 0pt))[a] +) + --- // Test interaction with gutters. #grid( diff --git a/tests/typ/layout/table-cell.typ b/tests/typ/layout/table-cell.typ index d79298aea..cbe0b9f0a 100644 --- a/tests/typ/layout/table-cell.typ +++ b/tests/typ/layout/table-cell.typ @@ -122,3 +122,7 @@ [Jake], [49], [Epic] ) } + +--- +// Error: 8-19 cannot use `grid.cell` as a table cell; use `table.cell` instead +#table(grid.cell[]) diff --git a/tests/typ/layout/table.typ b/tests/typ/layout/table.typ index 1b250aa4c..89a7dab53 100644 --- a/tests/typ/layout/table.typ +++ b/tests/typ/layout/table.typ @@ -61,6 +61,32 @@ [B], ) +#table( + columns: 3, + fill: (x, y) => (if y == 0 { aqua } else { orange }).darken(x * 15%), + inset: (x, y) => (left: if x == 0 { 0pt } else { 5pt }, right: if x == 0 { 5pt } else { 0pt }, y: if y == 0 { 0pt } else { 5pt }), + [A], [B], [C], + [A], [B], [C], +) + +#table( + columns: 3, + inset: (0pt, 5pt, 10pt), + fill: (x, _) => aqua.darken(x * 15%), + [A], [B], [C], +) + +--- +// Test inset folding +#set table(inset: 10pt) +#set table(inset: (left: 0pt)) + +#table( + fill: red, + inset: (right: 0pt), + table.cell(inset: (top: 0pt))[a] +) + --- // Test interaction with gutters. #table(