Some table improvements [More flexible tables] (#3473)

This commit is contained in:
PgBiel 2024-02-22 05:42:10 -03:00 committed by GitHub
parent 92a2f01b74
commit a8671962d6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 154 additions and 33 deletions

View File

@ -332,7 +332,7 @@ impl CellGrid {
items: I, items: I,
fill: &Celled<Option<Paint>>, fill: &Celled<Option<Paint>>,
align: &Celled<Smart<Alignment>>, align: &Celled<Smart<Alignment>>,
inset: Sides<Option<Rel<Length>>>, inset: &Celled<Sides<Option<Rel<Length>>>>,
stroke: &ResolvedCelled<Sides<Option<Option<Arc<Stroke>>>>>, stroke: &ResolvedCelled<Sides<Option<Option<Arc<Stroke>>>>>,
engine: &mut Engine, engine: &mut Engine,
styles: StyleChain, styles: StyleChain,
@ -486,7 +486,7 @@ impl CellGrid {
y, y,
&fill.resolve(engine, x, y)?, &fill.resolve(engine, x, y)?,
align.resolve(engine, x, y)?, align.resolve(engine, x, y)?,
inset, inset.resolve(engine, x, y)?,
stroke.resolve(engine, styles, x, y)?, stroke.resolve(engine, styles, x, y)?,
styles, styles,
); );
@ -572,7 +572,7 @@ impl CellGrid {
y, y,
&fill.resolve(engine, x, y)?, &fill.resolve(engine, x, y)?,
align.resolve(engine, x, y)?, align.resolve(engine, x, y)?,
inset, inset.resolve(engine, x, y)?,
stroke.resolve(engine, styles, x, y)?, stroke.resolve(engine, styles, x, y)?,
styles, styles,
); );

View File

@ -130,7 +130,7 @@ where
&CellGrid, &CellGrid,
usize, usize,
usize, usize,
Option<Arc<Stroke<Abs>>>, Option<Option<Arc<Stroke<Abs>>>>,
) -> Option<(Arc<Stroke<Abs>>, StrokePriority)> ) -> Option<(Arc<Stroke<Abs>>, StrokePriority)>
+ 'grid, + 'grid,
I: IntoIterator<Item = (usize, Abs)>, I: IntoIterator<Item = (usize, Abs)>,
@ -204,7 +204,7 @@ where
// Get the expected line stroke at this track by folding the // Get the expected line stroke at this track by folding the
// strokes of each user-specified line (with priority to the // strokes of each user-specified line (with priority to the
// user-specified line specified last). // user-specified line specified last).
let stroke = lines let mut line_strokes = lines
.iter() .iter()
.filter(|line| { .filter(|line| {
line.position == expected_line_position line.position == expected_line_position
@ -222,8 +222,15 @@ where
}) })
.unwrap_or_else(|| track >= gutter_factor * line.start) .unwrap_or_else(|| track >= gutter_factor * line.start)
}) })
.map(|line| line.stroke.clone()) .map(|line| line.stroke.clone());
.fold(None, |acc, line_stroke| line_stroke.fold(acc));
// 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 function shall determine if it is appropriate to draw
// the line at this position or not (i.e. whether or not it // 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 // (which indicates, in the context of 'std::iter::from_fn', that
// our iterator isn't over yet, and this should be its next value). // our iterator isn't over yet, and this should be its next value).
if let Some((stroke, priority)) = 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 // We should draw at this position. Let's check if we were
// already drawing in the previous position. // already drawing in the previous position.
@ -301,9 +308,10 @@ where
} }
/// Returns the correct stroke with which to draw a vline right before column /// 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 /// `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 /// at this position, if any (note that a stroke of `None` is unspecified,
/// depends on its source. /// 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 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 /// 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. /// stroke, as defined by user-specified lines (if any), is returned.
/// ///
/// The priority associated with the returned stroke follows the rules /// 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( pub(super) fn vline_stroke_at_row(
grid: &CellGrid, grid: &CellGrid,
x: usize, x: usize,
y: usize, y: usize,
stroke: Option<Arc<Stroke<Abs>>>, stroke: Option<Option<Arc<Stroke<Abs>>>>,
) -> Option<(Arc<Stroke<Abs>>, StrokePriority)> { ) -> Option<(Arc<Stroke<Abs>>, StrokePriority)> {
if x != 0 && x != grid.cols.len() { if x != 0 && x != grid.cols.len() {
// When the vline isn't at the border, we need to check if a colspan would // 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. // Fold the line stroke and folded cell strokes, if possible.
// Give priority to the explicit line stroke. // Give priority to the explicit line stroke.
// Otherwise, use whichever of the two isn't 'none' or unspecified. // 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)) final_stroke.zip(Some(priority))
} }
/// Returns the correct stroke with which to draw a hline on top of row 'y' /// 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 /// 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 /// at this position, if any (note that a stroke of `None` is unspecified,
/// depends on its source. /// 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 /// 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 /// 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. /// defined by user-specified lines (if any), is directly returned.
/// ///
/// The priority associated with the returned stroke follows the rules /// 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( pub(super) fn hline_stroke_at_column(
grid: &CellGrid, grid: &CellGrid,
y: usize, y: usize,
x: usize, x: usize,
stroke: Option<Arc<Stroke<Abs>>>, stroke: Option<Option<Arc<Stroke<Abs>>>>,
) -> Option<(Arc<Stroke<Abs>>, StrokePriority)> { ) -> Option<(Arc<Stroke<Abs>>, StrokePriority)> {
// There are no rowspans yet, so no need to add a check here. The line will // There are no rowspans yet, so no need to add a check here. The line will
// always be drawn, if it has a stroke. // 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. // Fold the line stroke and folded cell strokes, if possible.
// Give priority to the explicit line stroke. // Give priority to the explicit line stroke.
// Otherwise, use whichever of the two isn't 'none' or unspecified. // 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)) final_stroke.zip(Some(priority))
} }

View File

@ -10,7 +10,7 @@ use std::sync::Arc;
use ecow::eco_format; use ecow::eco_format;
use smallvec::{smallvec, SmallVec}; 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::engine::Engine;
use crate::foundations::{ use crate::foundations::{
cast, elem, scope, Array, Content, Fold, Packed, Show, Smart, StyleChain, Value, 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, Abs, AlignElem, Alignment, Axes, Dir, Fragment, LayoutMultiple, Length,
OuterHAlignment, OuterVAlignment, Regions, Rel, Sides, Sizing, OuterHAlignment, OuterVAlignment, Regions, Rel, Sides, Sizing,
}; };
use crate::model::{TableCell, TableHLine, TableVLine};
use crate::syntax::Span; use crate::syntax::Span;
use crate::text::TextElem; use crate::text::TextElem;
use crate::util::NonZeroExt; use crate::util::NonZeroExt;
@ -270,7 +271,7 @@ pub struct GridElem {
/// ) /// )
/// ``` /// ```
#[fold] #[fold]
pub inset: Sides<Option<Rel<Length>>>, pub inset: Celled<Sides<Option<Rel<Length>>>>,
/// The contents of the grid cells, plus any extra grid lines specified /// The contents of the grid cells, plus any extra grid lines specified
/// with the [`grid.hline`]($grid.hline) and [`grid.vline`]($grid.vline) /// with the [`grid.hline`]($grid.hline) and [`grid.vline`]($grid.vline)
@ -353,7 +354,7 @@ impl LayoutMultiple for Packed<GridElem> {
items, items,
fill, fill,
align, align,
inset, &inset,
&stroke, &stroke,
engine, engine,
styles, styles,
@ -395,7 +396,24 @@ cast! {
Self::VLine(vline) => vline.into_value(), Self::VLine(vline) => vline.into_value(),
Self::Cell(cell) => cell.into_value(), Self::Cell(cell) => cell.into_value(),
}, },
v: Content => v.into(), v: Content => {
if v.is::<TableCell>() {
bail!(
"cannot use `table.cell` as a grid cell; use `grid.cell` instead"
);
}
if v.is::<TableHLine>() {
bail!(
"cannot use `table.hline` as a grid line; use `grid.hline` instead"
);
}
if v.is::<TableVLine>() {
bail!(
"cannot use `table.vline` as a grid line; use `grid.vline` instead"
);
}
v.into()
}
} }
impl From<Content> for GridChild { impl From<Content> for GridChild {

View File

@ -3,15 +3,16 @@ use std::sync::Arc;
use ecow::eco_format; use ecow::eco_format;
use crate::diag::{SourceResult, Trace, Tracepoint}; use crate::diag::{bail, SourceResult, Trace, Tracepoint};
use crate::engine::Engine; use crate::engine::Engine;
use crate::foundations::{ use crate::foundations::{
cast, elem, scope, Content, Fold, Packed, Show, Smart, StyleChain, cast, elem, scope, Content, Fold, Packed, Show, Smart, StyleChain,
}; };
use crate::layout::{ use crate::layout::{
show_grid_cell, Abs, Alignment, Axes, Cell, CellGrid, Celled, Dir, Fragment, show_grid_cell, Abs, Alignment, Axes, Cell, CellGrid, Celled, Dir, Fragment,
GridItem, GridLayouter, LayoutMultiple, Length, LinePosition, OuterHAlignment, GridCell, GridHLine, GridItem, GridLayouter, GridVLine, LayoutMultiple, Length,
OuterVAlignment, Regions, Rel, ResolvableCell, Sides, TrackSizings, LinePosition, OuterHAlignment, OuterVAlignment, Regions, Rel, ResolvableCell, Sides,
TrackSizings,
}; };
use crate::model::Figurable; use crate::model::Figurable;
use crate::syntax::Span; use crate::syntax::Span;
@ -200,8 +201,8 @@ pub struct TableElem {
/// ) /// )
/// ``` /// ```
#[fold] #[fold]
#[default(Sides::splat(Some(Abs::pt(5.0).into())))] #[default(Celled::Value(Sides::splat(Some(Abs::pt(5.0).into()))))]
pub inset: Sides<Option<Rel<Length>>>, pub inset: Celled<Sides<Option<Rel<Length>>>>,
/// The contents of the table cells, plus any extra table lines specified /// The contents of the table cells, plus any extra table lines specified
/// with the [`table.hline`]($table.hline) and /// with the [`table.hline`]($table.hline) and
@ -282,7 +283,7 @@ impl LayoutMultiple for Packed<TableElem> {
items, items,
fill, fill,
align, align,
inset, &inset,
&stroke, &stroke,
engine, engine,
styles, styles,
@ -349,7 +350,24 @@ cast! {
Self::VLine(vline) => vline.into_value(), Self::VLine(vline) => vline.into_value(),
Self::Cell(cell) => cell.into_value(), Self::Cell(cell) => cell.into_value(),
}, },
v: Content => v.into(), v: Content => {
if v.is::<GridCell>() {
bail!(
"cannot use `grid.cell` as a table cell; use `table.cell` instead"
);
}
if v.is::<GridHLine>() {
bail!(
"cannot use `grid.hline` as a table line; use `table.hline` instead"
);
}
if v.is::<GridVLine>() {
bail!(
"cannot use `grid.vline` as a table line; use `table.vline` instead"
);
}
v.into()
}
} }
impl From<Content> for TableChild { impl From<Content> for TableChild {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -127,3 +127,7 @@
[Jake], [49], [Epic] [Jake], [49], [Epic]
) )
} }
---
// Error: 7-19 cannot use `table.cell` as a grid cell; use `grid.cell` instead
#grid(table.cell[])

View File

@ -139,7 +139,7 @@
--- ---
// Line specification order priority // Line specification order priority
// The last line should be blue, not red. // 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( #grid(
columns: 2, columns: 2,
inset: 2pt, inset: 2pt,
@ -344,6 +344,22 @@
table.vline(x: 3) 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 // Error: 3:3-3:31 line cannot end before it starts
#grid( #grid(

View File

@ -88,6 +88,32 @@ a
[B], [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. // Test interaction with gutters.
#grid( #grid(

View File

@ -122,3 +122,7 @@
[Jake], [49], [Epic] [Jake], [49], [Epic]
) )
} }
---
// Error: 8-19 cannot use `grid.cell` as a table cell; use `table.cell` instead
#table(grid.cell[])

View File

@ -61,6 +61,32 @@
[B], [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. // Test interaction with gutters.
#table( #table(