Initial table per-cell customization [More Flexible Tables Pt.2a] (#3037)

Co-authored-by: Laurenz <laurmaedje@gmail.com>
This commit is contained in:
PgBiel 2024-01-05 16:30:07 -03:00 committed by GitHub
parent a124694f08
commit 8fa573a9a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 1063 additions and 455 deletions

View File

@ -128,8 +128,20 @@ impl<T> Smart<T> {
}
}
/// Retusn `Auto` if `self` is `Auto`, otherwise calls the provided function onthe contained
/// value and returns the result.
/// Keeps `self` if it contains a custom value, otherwise returns the
/// output of the given function.
pub fn or_else<F>(self, f: F) -> Self
where
F: FnOnce() -> Self,
{
match self {
Self::Custom(x) => Self::Custom(x),
Self::Auto => f(),
}
}
/// Returns `Auto` if `self` is `Auto`, otherwise calls the provided
/// function on the contained value and returns the result.
pub fn and_then<F, U>(self, f: F) -> Smart<U>
where
F: FnOnce(T) -> Smart<U>,

View File

@ -1,282 +1,17 @@
use std::num::NonZeroUsize;
use smallvec::{smallvec, SmallVec};
use crate::diag::{bail, At, SourceResult, StrResult};
use crate::engine::Engine;
use crate::foundations::{
cast, elem, Array, CastInfo, Content, FromValue, Func, IntoValue, NativeElement,
Reflect, Resolve, Smart, StyleChain, Value,
Array, CastInfo, Content, FromValue, Func, IntoValue, Reflect, Resolve, Smart,
StyleChain, Value,
};
use crate::layout::{
Abs, Align, AlignElem, Axes, Dir, Fr, Fragment, Frame, FrameItem, Layout, Length,
Point, Regions, Rel, Sides, Size, Sizing,
Abs, Align, Axes, Dir, Fr, Fragment, Frame, FrameItem, Layout, Length, Point,
Regions, Rel, Sides, Size, Sizing,
};
use crate::syntax::Span;
use crate::text::TextElem;
use crate::util::Numeric;
use crate::visualize::{FixedStroke, Geometry, Paint, Stroke};
/// Arranges content in a grid.
///
/// The grid element allows you to arrange content in a grid. You can define the
/// number of rows and columns, as well as the size of the gutters between them.
/// There are multiple sizing modes for columns and rows that can be used to
/// create complex layouts.
///
/// The sizing of the grid is determined by the track sizes specified in the
/// arguments. Because each of the sizing parameters accepts the same values, we
/// will explain them just once, here. Each sizing argument accepts an array of
/// individual track sizes. A track size is either:
///
/// - `{auto}`: The track will be sized to fit its contents. It will be at most
/// as large as the remaining space. If there is more than one `{auto}` track
/// which, and together they claim more than the available space, the `{auto}`
/// tracks will fairly distribute the available space among themselves.
///
/// - A fixed or relative length (e.g. `{10pt}` or `{20% - 1cm}`): The track
/// will be exactly of this size.
///
/// - A fractional length (e.g. `{1fr}`): Once all other tracks have been sized,
/// the remaining space will be divided among the fractional tracks according
/// to their fractions. For example, if there are two fractional tracks, each
/// with a fraction of `{1fr}`, they will each take up half of the remaining
/// space.
///
/// To specify a single track, the array can be omitted in favor of a single
/// value. To specify multiple `{auto}` tracks, enter the number of tracks
/// instead of an array. For example, `columns:` `{3}` is equivalent to
/// `columns:` `{(auto, auto, auto)}`.
///
/// # Examples
/// The example below demonstrates the different track sizing options.
///
/// ```example
/// // We use `rect` to emphasize the
/// // area of cells.
/// #set rect(
/// inset: 8pt,
/// fill: rgb("e4e5ea"),
/// width: 100%,
/// )
///
/// #grid(
/// columns: (60pt, 1fr, 2fr),
/// rows: (auto, 60pt),
/// gutter: 3pt,
/// rect[Fixed width, auto height],
/// rect[1/3 of the remains],
/// rect[2/3 of the remains],
/// rect(height: 100%)[Fixed height],
/// image("tiger.jpg", height: 100%),
/// image("tiger.jpg", height: 100%),
/// )
/// ```
///
/// You can also [spread]($arguments/#spreading) an array of strings or content
/// into a grid to populate its cells.
///
/// ```example
/// #grid(
/// columns: 5,
/// gutter: 5pt,
/// ..range(25).map(str)
/// )
/// ```
#[elem(Layout)]
pub struct GridElem {
/// The column sizes.
///
/// Either specify a track size array or provide an integer to create a grid
/// with that many `{auto}`-sized columns. Note that opposed to rows and
/// gutters, providing a single track size will only ever create a single
/// column.
#[borrowed]
pub columns: TrackSizings,
/// The row sizes.
///
/// If there are more cells than fit the defined rows, the last row is
/// repeated until there are no more cells.
#[borrowed]
pub rows: TrackSizings,
/// The gaps between rows & columns.
///
/// If there are more gutters than defined sizes, the last gutter is repeated.
#[external]
pub gutter: TrackSizings,
/// The gaps between columns. Takes precedence over `gutter`.
#[parse(
let gutter = args.named("gutter")?;
args.named("column-gutter")?.or_else(|| gutter.clone())
)]
#[borrowed]
pub column_gutter: TrackSizings,
/// The gaps between rows. Takes precedence over `gutter`.
#[parse(args.named("row-gutter")?.or_else(|| gutter.clone()))]
#[borrowed]
pub row_gutter: TrackSizings,
/// How to fill the cells.
///
/// This can be a color or a function that returns a color. The function is
/// passed the cells' column and row index, starting at zero. This can be
/// used to implement striped grids.
///
/// ```example
/// #grid(
/// fill: (col, row) => if calc.even(col + row) { luma(240) } else { white },
/// align: center + horizon,
/// columns: 4,
/// [X], [O], [X], [O],
/// [O], [X], [O], [X],
/// [X], [O], [X], [O],
/// [O], [X], [O], [X]
/// )
/// ```
#[borrowed]
pub fill: Celled<Option<Paint>>,
/// How to align the cells' content.
///
/// This can either be a single alignment, an array of alignments
/// (corresponding to each column) or a function that returns an alignment.
/// The function is passed the cells' column and row index, starting at zero.
/// If set to `{auto}`, the outer alignment is used.
///
/// ```example
/// #grid(
/// columns: 3,
/// align: (x, y) => (left, center, right).at(x),
/// [Hello], [Hello], [Hello],
/// [A], [B], [C],
/// )
/// ```
#[borrowed]
pub align: Celled<Smart<Align>>,
/// How to [stroke]($stroke) the cells.
///
/// Grids have no strokes by default, which can be changed by setting this
/// option to the desired stroke.
///
/// _Note:_ Richer stroke customization for individual cells is not yet
/// implemented, but will be in the future. In the meantime, you can use the
/// third-party [tablex library](https://github.com/PgBiel/typst-tablex/).
#[resolve]
#[fold]
pub stroke: Option<Stroke>,
/// How much to pad the cells' content.
///
/// ```example
/// #grid(
/// inset: 10pt,
/// fill: (_, row) => (red, blue).at(row),
/// [Hello],
/// [World],
/// )
///
/// #grid(
/// columns: 2,
/// inset: (
/// x: 20pt,
/// y: 10pt,
/// ),
/// fill: (col, _) => (red, blue).at(col),
/// [Hello],
/// [World],
/// )
/// ```
#[fold]
#[default(Sides::splat(Abs::pt(0.0).into()))]
pub inset: Sides<Option<Rel<Length>>>,
/// The contents of the grid cells.
///
/// The cells are populated in row-major order.
#[variadic]
pub children: Vec<Content>,
}
impl Layout for GridElem {
#[typst_macros::time(name = "grid", span = self.span())]
fn layout(
&self,
engine: &mut Engine,
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment> {
let inset = self.inset(styles);
let align = self.align(styles);
let columns = self.columns(styles);
let rows = self.rows(styles);
let column_gutter = self.column_gutter(styles);
let row_gutter = self.row_gutter(styles);
let fill = self.fill(styles);
let stroke = self.stroke(styles).map(Stroke::unwrap_or_default);
let tracks = Axes::new(columns.0.as_slice(), rows.0.as_slice());
let gutter = Axes::new(column_gutter.0.as_slice(), row_gutter.0.as_slice());
let cells =
apply_align_inset_to_cells(engine, &tracks, &self.children, align, inset)?;
// Prepare grid layout by unifying content and gutter tracks.
let layouter = GridLayouter::new(
tracks,
gutter,
&cells,
fill,
&stroke,
regions,
styles,
self.span(),
);
// Measure the columns and layout the grid row-by-row.
Ok(layouter.layout(engine)?.fragment)
}
}
pub fn apply_align_inset_to_cells(
engine: &mut Engine,
tracks: &Axes<&[Sizing]>,
cells: &[Content],
align: &Celled<Smart<Align>>,
inset: Sides<Rel<Length>>,
) -> SourceResult<Vec<Content>> {
let cols = tracks.x.len().max(1);
cells
.iter()
.enumerate()
.map(|(i, child)| {
let mut child = child.clone().padded(inset);
let x = i % cols;
let y = i / cols;
if let Smart::Custom(alignment) = align.resolve(engine, x, y)? {
child = child.styled(AlignElem::set_alignment(alignment));
}
Ok(child)
})
.collect()
}
/// Track sizing definitions.
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
pub struct TrackSizings(pub SmallVec<[Sizing; 4]>);
cast! {
TrackSizings,
self => self.0.into_value(),
sizing: Sizing => Self(smallvec![sizing]),
count: NonZeroUsize => Self(smallvec![Sizing::Auto; count.get()]),
values: Array => Self(values.into_iter().map(Value::cast).collect::<StrResult<_>>()?),
}
use crate::visualize::{FixedStroke, Geometry, Paint};
/// A value that can be configured per cell.
#[derive(Debug, Clone, PartialEq, Hash)]
@ -347,87 +82,70 @@ impl<T: FromValue> FromValue for Celled<T> {
}
}
/// Performs grid layout.
pub struct GridLayouter<'a> {
/// Represents a cell in CellGrid, to be laid out by GridLayouter.
pub struct Cell {
/// The cell's body.
pub body: Content,
/// The cell's fill.
pub fill: Option<Paint>,
}
impl From<Content> for Cell {
/// Create a simple cell given its body.
fn from(body: Content) -> Self {
Self { body, fill: None }
}
}
impl Layout for Cell {
fn layout(
&self,
engine: &mut Engine,
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment> {
self.body.layout(engine, styles, regions)
}
}
/// Used for cell-like elements which are aware of their final properties in
/// the table, and may have property overrides.
pub trait ResolvableCell {
/// Resolves the cell's fields, given its coordinates and default grid-wide
/// fill, align and inset properties.
/// Returns a final Cell.
fn resolve_cell(
self,
x: usize,
y: usize,
fill: &Option<Paint>,
align: Smart<Align>,
inset: Sides<Rel<Length>>,
styles: StyleChain,
) -> Cell;
}
/// A grid of cells, including the columns, rows, and cell data.
pub struct CellGrid {
/// The grid cells.
cells: &'a [Content],
/// Whether this is an RTL grid.
is_rtl: bool,
/// Whether this grid has gutters.
has_gutter: bool,
cells: Vec<Cell>,
/// The column tracks including gutter tracks.
cols: Vec<Sizing>,
/// The row tracks including gutter tracks.
rows: Vec<Sizing>,
// How to fill the cells.
#[allow(dead_code)]
fill: &'a Celled<Option<Paint>>,
// How to stroke the cells.
#[allow(dead_code)]
stroke: &'a Option<FixedStroke>,
/// The regions to layout children into.
regions: Regions<'a>,
/// The inherited styles.
styles: StyleChain<'a>,
/// Resolved column sizes.
rcols: Vec<Abs>,
/// The sum of `rcols`.
width: Abs,
/// Resolve row sizes, by region.
rrows: Vec<Vec<RowPiece>>,
/// Rows in the current region.
lrows: Vec<Row>,
/// The initial size of the current region before we started subtracting.
initial: Size,
/// Frames for finished regions.
finished: Vec<Frame>,
/// The span of the grid element.
span: Span,
/// Whether this grid has gutters.
has_gutter: bool,
/// Whether this is an RTL grid.
is_rtl: bool,
}
/// The resulting sizes of columns and rows in a grid.
#[derive(Debug)]
pub struct GridLayout {
/// The fragment.
pub fragment: Fragment,
/// The column widths.
pub cols: Vec<Abs>,
/// The heights of the resulting rows segments, by region.
pub rows: Vec<Vec<RowPiece>>,
}
/// Details about a resulting row piece.
#[derive(Debug)]
pub struct RowPiece {
/// The height of the segment.
pub height: Abs,
/// The index of the row.
pub y: usize,
}
/// Produced by initial row layout, auto and relative rows are already finished,
/// fractional rows not yet.
enum Row {
/// Finished row frame of auto or relative row with y index.
Frame(Frame, usize),
/// Fractional row with y index.
Fr(Fr, usize),
}
impl<'a> GridLayouter<'a> {
/// Create a new grid layouter.
///
/// This prepares grid layout by unifying content and gutter tracks.
#[allow(clippy::too_many_arguments)]
impl CellGrid {
/// Generates the cell grid, given the tracks and resolved cells.
pub fn new(
tracks: Axes<&[Sizing]>,
gutter: Axes<&[Sizing]>,
cells: &'a [Content],
fill: &'a Celled<Option<Paint>>,
stroke: &'a Option<FixedStroke>,
regions: Regions<'a>,
styles: StyleChain<'a>,
span: Span,
cells: Vec<Cell>,
styles: StyleChain,
) -> Self {
let mut cols = vec![];
let mut rows = vec![];
@ -479,22 +197,154 @@ impl<'a> GridLayouter<'a> {
cols.reverse();
}
Self { cols, rows, cells, has_gutter, is_rtl }
}
/// Resolves all cells in the grid before creating it.
/// Allows them to keep track of their final properties and adjust their
/// fields accordingly.
/// Cells must implement Clone as they will be owned. Additionally, they
/// must implement Default in order to fill the last row of the grid with
/// empty cells, if it is not completely filled.
#[allow(clippy::too_many_arguments)]
pub fn resolve<T: ResolvableCell + Clone + Default>(
tracks: Axes<&[Sizing]>,
gutter: Axes<&[Sizing]>,
cells: &[T],
fill: &Celled<Option<Paint>>,
align: &Celled<Smart<Align>>,
inset: Sides<Rel<Length>>,
engine: &mut Engine,
styles: StyleChain,
) -> SourceResult<Self> {
// Number of content columns: Always at least one.
let c = tracks.x.len().max(1);
// If not all columns in the last row have cells, we will add empty
// cells and complete the row so that those positions are susceptible
// to show rules and receive grid styling.
// We apply '% c' twice so that 'cells_remaining' is zero when
// the last row is already filled (then 'cell_count % c' would be zero).
let cell_count = cells.len();
let cells_remaining = (c - cell_count % c) % c;
let cells = cells
.iter()
.cloned()
.chain(std::iter::repeat_with(T::default).take(cells_remaining))
.enumerate()
.map(|(i, cell)| {
let x = i % c;
let y = i / c;
Ok(cell.resolve_cell(
x,
y,
&fill.resolve(engine, x, y)?,
align.resolve(engine, x, y)?,
inset,
styles,
))
})
.collect::<SourceResult<Vec<_>>>()?;
Ok(Self::new(tracks, gutter, cells, styles))
}
/// Get the content of the cell in column `x` and row `y`.
///
/// Returns `None` if it's a gutter cell.
#[track_caller]
fn cell(&self, mut x: usize, y: usize) -> Option<&Cell> {
assert!(x < self.cols.len());
assert!(y < self.rows.len());
// Columns are reorder, but the cell slice is not.
if self.is_rtl {
x = self.cols.len() - 1 - x;
}
if self.has_gutter {
// Even columns and rows are children, odd ones are gutter.
if x % 2 == 0 && y % 2 == 0 {
let c = 1 + self.cols.len() / 2;
self.cells.get((y / 2) * c + x / 2)
} else {
None
}
} else {
let c = self.cols.len();
self.cells.get(y * c + x)
}
}
}
/// Performs grid layout.
pub struct GridLayouter<'a> {
/// The grid of cells.
grid: &'a CellGrid,
// How to stroke the cells.
stroke: &'a Option<FixedStroke>,
/// The regions to layout children into.
regions: Regions<'a>,
/// The inherited styles.
styles: StyleChain<'a>,
/// Resolved column sizes.
rcols: Vec<Abs>,
/// The sum of `rcols`.
width: Abs,
/// Resolve row sizes, by region.
rrows: Vec<Vec<RowPiece>>,
/// Rows in the current region.
lrows: Vec<Row>,
/// The initial size of the current region before we started subtracting.
initial: Size,
/// Frames for finished regions.
finished: Vec<Frame>,
/// The span of the grid element.
span: Span,
}
/// Details about a resulting row piece.
#[derive(Debug)]
pub struct RowPiece {
/// The height of the segment.
pub height: Abs,
/// The index of the row.
pub y: usize,
}
/// Produced by initial row layout, auto and relative rows are already finished,
/// fractional rows not yet.
enum Row {
/// Finished row frame of auto or relative row with y index.
Frame(Frame, usize),
/// Fractional row with y index.
Fr(Fr, usize),
}
impl<'a> GridLayouter<'a> {
/// Create a new grid layouter.
///
/// This prepares grid layout by unifying content and gutter tracks.
#[allow(clippy::too_many_arguments)]
pub fn new(
grid: &'a CellGrid,
stroke: &'a Option<FixedStroke>,
regions: Regions<'a>,
styles: StyleChain<'a>,
span: Span,
) -> Self {
// We use these regions for auto row measurement. Since at that moment,
// columns are already sized, we can enable horizontal expansion.
let mut regions = regions;
regions.expand = Axes::new(true, false);
Self {
cells,
is_rtl,
has_gutter,
rows,
fill,
grid,
stroke,
regions,
styles,
rcols: vec![Abs::zero(); cols.len()],
cols,
rcols: vec![Abs::zero(); grid.cols.len()],
width: Abs::zero(),
rrows: vec![],
lrows: vec![],
@ -505,17 +355,17 @@ impl<'a> GridLayouter<'a> {
}
/// Determines the columns sizes and then layouts the grid row-by-row.
pub fn layout(mut self, engine: &mut Engine) -> SourceResult<GridLayout> {
pub fn layout(mut self, engine: &mut Engine) -> SourceResult<Fragment> {
self.measure_columns(engine)?;
for y in 0..self.rows.len() {
for y in 0..self.grid.rows.len() {
// Skip to next region if current one is full, but only for content
// rows, not for gutter rows.
if self.regions.is_full() && (!self.has_gutter || y % 2 == 0) {
if self.regions.is_full() && (!self.grid.has_gutter || y % 2 == 0) {
self.finish_region(engine)?;
}
match self.rows[y] {
match self.grid.rows[y] {
Sizing::Auto => self.layout_auto_row(engine, y)?,
Sizing::Rel(v) => self.layout_relative_row(engine, v, y)?,
Sizing::Fr(v) => self.lrows.push(Row::Fr(v, y)),
@ -524,19 +374,13 @@ impl<'a> GridLayouter<'a> {
self.finish_region(engine)?;
if self.stroke.is_some() || !matches!(self.fill, Celled::Value(None)) {
self.render_fills_strokes(engine)?;
}
self.render_fills_strokes()?;
Ok(GridLayout {
fragment: Fragment::frames(self.finished),
cols: self.rcols,
rows: self.rrows,
})
Ok(Fragment::frames(self.finished))
}
/// Add lines and backgrounds.
fn render_fills_strokes(&mut self, engine: &mut Engine) -> SourceResult<()> {
fn render_fills_strokes(&mut self) -> SourceResult<()> {
for (frame, rows) in self.finished.iter_mut().zip(&self.rrows) {
if self.rcols.is_empty() || rows.is_empty() {
continue;
@ -573,7 +417,9 @@ impl<'a> GridLayouter<'a> {
for (x, &col) in self.rcols.iter().enumerate() {
let mut dy = Abs::zero();
for row in rows {
if let Some(fill) = self.fill.resolve(engine, x, row.y)? {
let fill =
self.grid.cell(x, row.y).and_then(|cell| cell.fill.clone());
if let Some(fill) = fill {
let pos = Point::new(dx, dy);
let size = Size::new(col, row.height);
let rect = Geometry::Rect(size).filled(fill);
@ -598,7 +444,7 @@ impl<'a> GridLayouter<'a> {
// Resolve the size of all relative columns and compute the sum of all
// fractional tracks.
for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) {
for (&col, rcol) in self.grid.cols.iter().zip(&mut self.rcols) {
match col {
Sizing::Auto => {}
Sizing::Rel(v) => {
@ -644,17 +490,17 @@ impl<'a> GridLayouter<'a> {
// Determine size of auto columns by laying out all cells in those
// columns, measuring them and finding the largest one.
for (x, &col) in self.cols.iter().enumerate() {
for (x, &col) in self.grid.cols.iter().enumerate() {
if col != Sizing::Auto {
continue;
}
let mut resolved = Abs::zero();
for y in 0..self.rows.len() {
if let Some(cell) = self.cell(x, y) {
for y in 0..self.grid.rows.len() {
if let Some(cell) = self.grid.cell(x, y) {
// For relative rows, we can already resolve the correct
// base and for auto and fr we could only guess anyway.
let height = match self.rows[y] {
let height = match self.grid.rows[y] {
Sizing::Rel(v) => {
v.resolve(self.styles).relative_to(self.regions.base().y)
}
@ -682,7 +528,7 @@ impl<'a> GridLayouter<'a> {
return;
}
for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) {
for (&col, rcol) in self.grid.cols.iter().zip(&mut self.rcols) {
if let Sizing::Fr(v) = col {
*rcol = v.share(fr, remaining);
}
@ -703,7 +549,7 @@ impl<'a> GridLayouter<'a> {
last = fair;
fair = redistribute / (overlarge as f64);
for (&col, &rcol) in self.cols.iter().zip(&self.rcols) {
for (&col, &rcol) in self.grid.cols.iter().zip(&self.rcols) {
// Remove an auto column if it is not overlarge (rcol <= fair),
// but also hasn't already been removed (rcol > last).
if col == Sizing::Auto && rcol <= fair && rcol > last {
@ -715,7 +561,7 @@ impl<'a> GridLayouter<'a> {
}
// Redistribute space fairly among overlarge columns.
for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) {
for (&col, rcol) in self.grid.cols.iter().zip(&mut self.rcols) {
if col == Sizing::Auto && *rcol > fair {
*rcol = fair;
}
@ -783,7 +629,7 @@ impl<'a> GridLayouter<'a> {
let mut resolved: Vec<Abs> = vec![];
for (x, &rcol) in self.rcols.iter().enumerate() {
if let Some(cell) = self.cell(x, y) {
if let Some(cell) = self.grid.cell(x, y) {
let mut pod = self.regions;
pod.size.x = rcol;
@ -831,7 +677,7 @@ impl<'a> GridLayouter<'a> {
self.finish_region(engine)?;
// Don't skip multiple regions for gutter and don't push a row.
if self.has_gutter && y % 2 == 1 {
if self.grid.has_gutter && y % 2 == 1 {
return Ok(());
}
}
@ -856,10 +702,10 @@ impl<'a> GridLayouter<'a> {
let mut pos = Point::zero();
for (x, &rcol) in self.rcols.iter().enumerate() {
if let Some(cell) = self.cell(x, y) {
if let Some(cell) = self.grid.cell(x, y) {
let size = Size::new(rcol, height);
let mut pod = Regions::one(size, Axes::splat(true));
if self.rows[y] == Sizing::Auto {
if self.grid.rows[y] == Sizing::Auto {
pod.full = self.regions.full;
}
let frame = cell.layout(engine, self.styles, pod)?.into_frame();
@ -894,7 +740,7 @@ impl<'a> GridLayouter<'a> {
// Layout the row.
let mut pos = Point::zero();
for (x, &rcol) in self.rcols.iter().enumerate() {
if let Some(cell) = self.cell(x, y) {
if let Some(cell) = self.grid.cell(x, y) {
pod.size.x = rcol;
// Push the layouted frames into the individual output frames.
@ -964,33 +810,6 @@ impl<'a> GridLayouter<'a> {
Ok(())
}
/// Get the content of the cell in column `x` and row `y`.
///
/// Returns `None` if it's a gutter cell.
#[track_caller]
fn cell(&self, mut x: usize, y: usize) -> Option<&'a Content> {
assert!(x < self.cols.len());
assert!(y < self.rows.len());
// Columns are reorder, but the cell slice is not.
if self.is_rtl {
x = self.cols.len() - 1 - x;
}
if self.has_gutter {
// Even columns and rows are children, odd ones are gutter.
if x % 2 == 0 && y % 2 == 0 {
let c = 1 + self.cols.len() / 2;
self.cells.get((y / 2) * c + x / 2)
} else {
None
}
} else {
let c = self.cols.len();
self.cells.get(y * c + x)
}
}
}
/// Turn an iterator of extents into an iterator of offsets before, in between,

View File

@ -0,0 +1,383 @@
mod layout;
pub use self::layout::{Cell, CellGrid, Celled, GridLayouter, ResolvableCell};
use std::num::NonZeroUsize;
use smallvec::{smallvec, SmallVec};
use crate::diag::{SourceResult, StrResult};
use crate::engine::Engine;
use crate::foundations::{
cast, elem, scope, Array, Content, Fold, NativeElement, Show, Smart, StyleChain,
Value,
};
use crate::layout::{
Abs, Align, AlignElem, Axes, Fragment, Layout, Length, Regions, Rel, Sides, Sizing,
};
use crate::visualize::{Paint, Stroke};
/// Arranges content in a grid.
///
/// The grid element allows you to arrange content in a grid. You can define the
/// number of rows and columns, as well as the size of the gutters between them.
/// There are multiple sizing modes for columns and rows that can be used to
/// create complex layouts.
///
/// The sizing of the grid is determined by the track sizes specified in the
/// arguments. Because each of the sizing parameters accepts the same values, we
/// will explain them just once, here. Each sizing argument accepts an array of
/// individual track sizes. A track size is either:
///
/// - `{auto}`: The track will be sized to fit its contents. It will be at most
/// as large as the remaining space. If there is more than one `{auto}` track
/// which, and together they claim more than the available space, the `{auto}`
/// tracks will fairly distribute the available space among themselves.
///
/// - A fixed or relative length (e.g. `{10pt}` or `{20% - 1cm}`): The track
/// will be exactly of this size.
///
/// - A fractional length (e.g. `{1fr}`): Once all other tracks have been sized,
/// the remaining space will be divided among the fractional tracks according
/// to their fractions. For example, if there are two fractional tracks, each
/// with a fraction of `{1fr}`, they will each take up half of the remaining
/// space.
///
/// To specify a single track, the array can be omitted in favor of a single
/// value. To specify multiple `{auto}` tracks, enter the number of tracks
/// instead of an array. For example, `columns:` `{3}` is equivalent to
/// `columns:` `{(auto, auto, auto)}`.
///
/// # Styling the grid
/// The grid's appearance can be customized through different parameters, such
/// as `fill` to give all cells a background; `align` to change how cells are
/// aligned; `inset` to optionally add internal padding to each cell; and
/// `stroke` to optionally enable grid lines with a certain stroke.
///
/// If you need to override one of the above options for a single cell, you can
/// use the [`grid.cell`]($grid.cell) element. Alternatively, if you need the
/// appearance options to depend on a cell's position (column and row), you may
/// specify a function to `fill` or `align` of the form
/// `(column, row) => value`. You may also use a show rule on
/// [`grid.cell`]($grid.cell) - see that element's examples for more information.
///
/// # Examples
/// The example below demonstrates the different track sizing options.
///
/// ```example
/// // We use `rect` to emphasize the
/// // area of cells.
/// #set rect(
/// inset: 8pt,
/// fill: rgb("e4e5ea"),
/// width: 100%,
/// )
///
/// #grid(
/// columns: (60pt, 1fr, 2fr),
/// rows: (auto, 60pt),
/// gutter: 3pt,
/// rect[Fixed width, auto height],
/// rect[1/3 of the remains],
/// rect[2/3 of the remains],
/// rect(height: 100%)[Fixed height],
/// image("tiger.jpg", height: 100%),
/// image("tiger.jpg", height: 100%),
/// )
/// ```
///
/// You can also [spread]($arguments/#spreading) an array of strings or content
/// into a grid to populate its cells.
///
/// ```example
/// #grid(
/// columns: 5,
/// gutter: 5pt,
/// ..range(25).map(str)
/// )
/// ```
#[elem(scope, Layout)]
pub struct GridElem {
/// The column sizes.
///
/// Either specify a track size array or provide an integer to create a grid
/// with that many `{auto}`-sized columns. Note that opposed to rows and
/// gutters, providing a single track size will only ever create a single
/// column.
#[borrowed]
pub columns: TrackSizings,
/// The row sizes.
///
/// If there are more cells than fit the defined rows, the last row is
/// repeated until there are no more cells.
#[borrowed]
pub rows: TrackSizings,
/// The gaps between rows & columns.
///
/// If there are more gutters than defined sizes, the last gutter is repeated.
#[external]
pub gutter: TrackSizings,
/// The gaps between columns. Takes precedence over `gutter`.
#[parse(
let gutter = args.named("gutter")?;
args.named("column-gutter")?.or_else(|| gutter.clone())
)]
#[borrowed]
pub column_gutter: TrackSizings,
/// The gaps between rows. Takes precedence over `gutter`.
#[parse(args.named("row-gutter")?.or_else(|| gutter.clone()))]
#[borrowed]
pub row_gutter: TrackSizings,
/// How to fill the cells.
///
/// This can be a color or a function that returns a color. The function is
/// passed the cells' column and row index, starting at zero. This can be
/// used to implement striped grids.
///
/// ```example
/// #grid(
/// fill: (col, row) => if calc.even(col + row) { luma(240) } else { white },
/// align: center + horizon,
/// columns: 4,
/// [X], [O], [X], [O],
/// [O], [X], [O], [X],
/// [X], [O], [X], [O],
/// [O], [X], [O], [X]
/// )
/// ```
#[borrowed]
pub fill: Celled<Option<Paint>>,
/// How to align the cells' content.
///
/// This can either be a single alignment, an array of alignments
/// (corresponding to each column) or a function that returns an alignment.
/// The function is passed the cells' column and row index, starting at zero.
/// If set to `{auto}`, the outer alignment is used.
///
/// ```example
/// #grid(
/// columns: 3,
/// align: (x, y) => (left, center, right).at(x),
/// [Hello], [Hello], [Hello],
/// [A], [B], [C],
/// )
/// ```
#[borrowed]
pub align: Celled<Smart<Align>>,
/// How to [stroke]($stroke) the cells.
///
/// Grids have no strokes by default, which can be changed by setting this
/// option to the desired stroke.
///
/// _Note:_ Richer stroke customization for individual cells is not yet
/// implemented, but will be in the future. In the meantime, you can use the
/// third-party [tablex library](https://github.com/PgBiel/typst-tablex/).
#[resolve]
#[fold]
pub stroke: Option<Stroke>,
/// How much to pad the cells' content.
///
/// ```example
/// #grid(
/// inset: 10pt,
/// fill: (_, row) => (red, blue).at(row),
/// [Hello],
/// [World],
/// )
///
/// #grid(
/// columns: 2,
/// inset: (
/// x: 20pt,
/// y: 10pt,
/// ),
/// fill: (col, _) => (red, blue).at(col),
/// [Hello],
/// [World],
/// )
/// ```
#[fold]
#[default(Sides::splat(Abs::pt(0.0).into()))]
pub inset: Sides<Option<Rel<Length>>>,
/// The contents of the grid cells.
///
/// The cells are populated in row-major order.
#[variadic]
pub children: Vec<GridCell>,
}
#[scope]
impl GridElem {
#[elem]
type GridCell;
}
impl Layout for GridElem {
#[typst_macros::time(name = "grid", span = self.span())]
fn layout(
&self,
engine: &mut Engine,
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment> {
let inset = self.inset(styles);
let align = self.align(styles);
let columns = self.columns(styles);
let rows = self.rows(styles);
let column_gutter = self.column_gutter(styles);
let row_gutter = self.row_gutter(styles);
let fill = self.fill(styles);
let stroke = self.stroke(styles).map(Stroke::unwrap_or_default);
let tracks = Axes::new(columns.0.as_slice(), rows.0.as_slice());
let gutter = Axes::new(column_gutter.0.as_slice(), row_gutter.0.as_slice());
let grid = CellGrid::resolve(
tracks,
gutter,
self.children(),
fill,
align,
inset,
engine,
styles,
)?;
let layouter = GridLayouter::new(&grid, &stroke, regions, styles, self.span());
// Measure the columns and layout the grid row-by-row.
layouter.layout(engine)
}
}
/// Track sizing definitions.
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
pub struct TrackSizings(pub SmallVec<[Sizing; 4]>);
cast! {
TrackSizings,
self => self.0.into_value(),
sizing: Sizing => Self(smallvec![sizing]),
count: NonZeroUsize => Self(smallvec![Sizing::Auto; count.get()]),
values: Array => Self(values.into_iter().map(Value::cast).collect::<StrResult<_>>()?),
}
/// A cell in the grid. Use this to either override grid properties for a
/// particular cell, or in show rules to apply certain styles to multiple cells
/// at once.
///
/// For example, you can override the fill, alignment or inset for a single
/// cell:
///
/// ```example
/// #grid(
/// columns: 2,
/// fill: red,
/// align: left,
/// inset: 5pt,
/// [ABC], [ABC],
/// grid.cell(fill: blue)[C], [D],
/// grid.cell(align: center)[E], [F],
/// [G], grid.cell(inset: 0pt)[H]
/// )
/// ```
#[elem(name = "cell", title = "Grid Cell", Show)]
pub struct GridCell {
/// The cell's body.
#[required]
body: Content,
/// The cell's fill override.
fill: Smart<Option<Paint>>,
/// The cell's alignment override.
align: Smart<Align>,
/// The cell's inset override.
inset: Smart<Sides<Option<Rel<Length>>>>,
}
cast! {
GridCell,
v: Content => v.into(),
}
impl Default for GridCell {
fn default() -> Self {
Self::new(Content::default())
}
}
impl ResolvableCell for GridCell {
fn resolve_cell(
mut self,
_: usize,
_: usize,
fill: &Option<Paint>,
align: Smart<Align>,
inset: Sides<Rel<Length>>,
styles: StyleChain,
) -> Cell {
let fill = self.fill(styles).unwrap_or_else(|| fill.clone());
self.push_fill(Smart::Custom(fill.clone()));
self.push_align(match align {
Smart::Custom(align) => {
Smart::Custom(self.align(styles).map_or(align, |inner| inner.fold(align)))
}
// Don't fold if the grid is using outer alignment. Use the
// cell's alignment instead (which, in the end, will fold with
// the outer alignment when it is effectively displayed).
Smart::Auto => self.align(styles),
});
self.push_inset(Smart::Custom(
self.inset(styles).map_or(inset, |inner| inner.fold(inset)).map(Some),
));
Cell { body: self.pack(), fill }
}
}
impl Show for GridCell {
fn show(&self, _engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
show_grid_cell(self.body().clone(), self.inset(styles), self.align(styles))
}
}
impl From<Content> for GridCell {
fn from(value: Content) -> Self {
value
.to::<Self>()
.cloned()
.unwrap_or_else(|| Self::new(value.clone()))
}
}
/// Function with common code to display a grid cell or table cell.
pub fn show_grid_cell(
mut body: Content,
inset: Smart<Sides<Option<Rel<Length>>>>,
align: Smart<Align>,
) -> SourceResult<Content> {
let inset = inset.unwrap_or_default().map(Option::unwrap_or_default);
if inset != Sides::default() {
// Only pad if some inset is not 0pt.
// Avoids a bug where using .padded() in any way inside Show causes
// alignment in align(...) to break.
body = body.padded(inset);
}
if let Smart::Custom(alignment) = align {
body = body.styled(AlignElem::set_alignment(alignment));
}
Ok(body)
}

View File

@ -29,7 +29,7 @@ use crate::foundations::{
};
use crate::introspection::{Introspector, Locatable, Location};
use crate::layout::{
BlockElem, Em, GridElem, HElem, PadElem, Sizing, TrackSizings, VElem,
BlockElem, Em, GridCell, GridElem, HElem, PadElem, Sizing, TrackSizings, VElem,
};
use crate::model::{
CitationForm, CiteGroup, Destination, FootnoteElem, HeadingElem, LinkElem, ParElem,
@ -239,8 +239,8 @@ impl Show for BibliographyElem {
if references.iter().any(|(prefix, _)| prefix.is_some()) {
let mut cells = vec![];
for (prefix, reference) in references {
cells.push(prefix.clone().unwrap_or_default());
cells.push(reference.clone());
cells.push(GridCell::new(prefix.clone().unwrap_or_default()));
cells.push(GridCell::new(reference.clone()));
}
seq.push(VElem::new(row_gutter).with_weakness(3).pack());
@ -947,7 +947,7 @@ impl ElemRenderer<'_> {
if let Some(prefix) = suf_prefix {
const COLUMN_GUTTER: Em = Em::new(0.65);
content = GridElem::new(vec![prefix, content])
content = GridElem::new(vec![GridCell::new(prefix), GridCell::new(content)])
.spanned(self.span)
.with_columns(TrackSizings(smallvec![Sizing::Auto; 2]))
.with_column_gutter(TrackSizings(smallvec![COLUMN_GUTTER.into()]))

View File

@ -6,8 +6,8 @@ use crate::foundations::{
cast, elem, scope, Array, Content, Fold, NativeElement, Smart, StyleChain,
};
use crate::layout::{
Align, Axes, BlockElem, Celled, Em, Fragment, GridLayouter, HAlign, Layout, Length,
Regions, Sizing, Spacing, VAlign,
Align, Axes, BlockElem, Cell, CellGrid, Em, Fragment, GridLayouter, HAlign, Layout,
Length, Regions, Sizing, Spacing, VAlign,
};
use crate::model::{Numbering, NumberingPattern, ParElem};
use crate::text::TextElem;
@ -259,16 +259,17 @@ impl Layout for EnumElem {
let resolved =
resolved.aligned(number_align).styled(TextElem::set_overhang(false));
cells.push(Content::empty());
cells.push(resolved);
cells.push(Content::empty());
cells.push(item.body().clone().styled(Self::set_parents(Parent(number))));
cells.push(Cell::from(Content::empty()));
cells.push(Cell::from(resolved));
cells.push(Cell::from(Content::empty()));
cells.push(Cell::from(
item.body().clone().styled(Self::set_parents(Parent(number))),
));
number = number.saturating_add(1);
}
let fill = Celled::Value(None);
let stroke = None;
let layouter = GridLayouter::new(
let grid = CellGrid::new(
Axes::with_x(&[
Sizing::Rel(indent.into()),
Sizing::Auto,
@ -276,15 +277,12 @@ impl Layout for EnumElem {
Sizing::Auto,
]),
Axes::with_y(&[gutter.into()]),
&cells,
&fill,
&stroke,
regions,
cells,
styles,
self.span(),
);
let layouter = GridLayouter::new(&grid, &stroke, regions, styles, self.span());
Ok(layouter.layout(engine)?.fragment)
layouter.layout(engine)
}
}

View File

@ -5,8 +5,8 @@ use crate::foundations::{
Value,
};
use crate::layout::{
Axes, BlockElem, Celled, Em, Fragment, GridLayouter, HAlign, Layout, Length, Regions,
Sizing, Spacing, VAlign,
Axes, BlockElem, Cell, CellGrid, Em, Fragment, GridLayouter, HAlign, Layout, Length,
Regions, Sizing, Spacing, VAlign,
};
use crate::model::ParElem;
use crate::text::TextElem;
@ -160,15 +160,14 @@ impl Layout for ListElem {
let mut cells = vec![];
for item in self.children() {
cells.push(Content::empty());
cells.push(marker.clone());
cells.push(Content::empty());
cells.push(item.body().clone().styled(Self::set_depth(Depth)));
cells.push(Cell::from(Content::empty()));
cells.push(Cell::from(marker.clone()));
cells.push(Cell::from(Content::empty()));
cells.push(Cell::from(item.body().clone().styled(Self::set_depth(Depth))));
}
let fill = Celled::Value(None);
let stroke = None;
let layouter = GridLayouter::new(
let grid = CellGrid::new(
Axes::with_x(&[
Sizing::Rel(indent.into()),
Sizing::Auto,
@ -176,15 +175,12 @@ impl Layout for ListElem {
Sizing::Auto,
]),
Axes::with_y(&[gutter.into()]),
&cells,
&fill,
&stroke,
regions,
cells,
styles,
self.span(),
);
let layouter = GridLayouter::new(&grid, &stroke, regions, styles, self.span());
Ok(layouter.layout(engine)?.fragment)
layouter.layout(engine)
}
}

View File

@ -1,9 +1,11 @@
use crate::diag::SourceResult;
use crate::engine::Engine;
use crate::foundations::{elem, Content, NativeElement, Smart, StyleChain};
use crate::foundations::{
cast, elem, scope, Content, Fold, NativeElement, Show, Smart, StyleChain,
};
use crate::layout::{
apply_align_inset_to_cells, Abs, Align, Axes, Celled, Fragment, GridLayouter, Layout,
Length, Regions, Rel, Sides, TrackSizings,
show_grid_cell, Abs, Align, Axes, Cell, CellGrid, Celled, Fragment, GridLayouter,
Layout, Length, Regions, Rel, ResolvableCell, Sides, TrackSizings,
};
use crate::model::Figurable;
use crate::text::{Lang, LocalName, Region};
@ -13,9 +15,15 @@ use crate::visualize::{Paint, Stroke};
///
/// Tables are used to arrange content in cells. Cells can contain arbitrary
/// content, including multiple paragraphs and are specified in row-major order.
/// Because tables are just grids with configurable cell properties, refer to
/// the [grid documentation]($grid) for more information on how to size the
/// table tracks.
/// Because tables are just grids with different defaults for some cell
/// properties (notably `stroke` and `inset`), refer to the
/// [grid documentation]($grid) for more information on how to size the table
/// tracks and specify the cell appearance properties.
///
/// Note that, to override a particular cell's properties or apply show rules
/// on table cells, you can use the [`table.cell`]($table.cell) element (but
/// not `grid.cell`, which is exclusive to grids). See its documentation for
/// more information.
///
/// To give a table a caption and make it [referenceable]($ref), put it into a
/// [figure]($figure).
@ -39,7 +47,7 @@ use crate::visualize::{Paint, Stroke};
/// [$a$: edge length]
/// )
/// ```
#[elem(Layout, LocalName, Figurable)]
#[elem(scope, Layout, LocalName, Figurable)]
pub struct TableElem {
/// The column sizes. See the [grid documentation]($grid) for more
/// information on track sizing.
@ -149,7 +157,13 @@ pub struct TableElem {
/// The contents of the table cells.
#[variadic]
pub children: Vec<Content>,
pub children: Vec<TableCell>,
}
#[scope]
impl TableElem {
#[elem]
type TableCell;
}
impl Layout for TableElem {
@ -171,22 +185,20 @@ impl Layout for TableElem {
let tracks = Axes::new(columns.0.as_slice(), rows.0.as_slice());
let gutter = Axes::new(column_gutter.0.as_slice(), row_gutter.0.as_slice());
let cells =
apply_align_inset_to_cells(engine, &tracks, self.children(), align, inset)?;
// Prepare grid layout by unifying content and gutter tracks.
let layouter = GridLayouter::new(
let grid = CellGrid::resolve(
tracks,
gutter,
&cells,
self.children(),
fill,
&stroke,
regions,
align,
inset,
engine,
styles,
self.span(),
);
)?;
Ok(layouter.layout(engine)?.fragment)
let layouter = GridLayouter::new(&grid, &stroke, regions, styles, self.span());
layouter.layout(engine)
}
}
@ -227,3 +239,92 @@ impl LocalName for TableElem {
}
impl Figurable for TableElem {}
/// A cell in the table. Use this to either override table properties for a
/// particular cell, or in show rules to apply certain styles to multiple cells
/// at once.
///
/// For example, you can override the fill, alignment or inset for a single
/// cell:
///
/// ```example
/// #table(
/// columns: 2,
/// fill: green,
/// align: right,
/// [*Name*], [*Data*],
/// table.cell(fill: blue)[J.], [Organizer],
/// table.cell(align: center)[K.], [Leader],
/// [M.], table.cell(inset: 0pt)[Player]
/// )
/// ```
#[elem(name = "cell", title = "Table Cell", Show)]
pub struct TableCell {
/// The cell's body.
#[required]
body: Content,
/// The cell's fill override.
fill: Smart<Option<Paint>>,
/// The cell's alignment override.
align: Smart<Align>,
/// The cell's inset override.
inset: Smart<Sides<Option<Rel<Length>>>>,
}
cast! {
TableCell,
v: Content => v.into(),
}
impl Default for TableCell {
fn default() -> Self {
Self::new(Content::default())
}
}
impl ResolvableCell for TableCell {
fn resolve_cell(
mut self,
_: usize,
_: usize,
fill: &Option<Paint>,
align: Smart<Align>,
inset: Sides<Rel<Length>>,
styles: StyleChain,
) -> Cell {
let fill = self.fill(styles).unwrap_or_else(|| fill.clone());
self.push_fill(Smart::Custom(fill.clone()));
self.push_align(match align {
Smart::Custom(align) => {
Smart::Custom(self.align(styles).map_or(align, |inner| inner.fold(align)))
}
// Don't fold if the table is using outer alignment. Use the
// cell's alignment instead (which, in the end, will fold with
// the outer alignment when it is effectively displayed).
Smart::Auto => self.align(styles),
});
self.push_inset(Smart::Custom(
self.inset(styles).map_or(inset, |inner| inner.fold(inset)).map(Some),
));
Cell { body: self.pack(), fill }
}
}
impl Show for TableCell {
fn show(&self, _engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
show_grid_cell(self.body().clone(), self.inset(styles), self.align(styles))
}
}
impl From<Content> for TableCell {
fn from(value: Content) -> Self {
value
.to::<Self>()
.cloned()
.unwrap_or_else(|| Self::new(value.clone()))
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -0,0 +1,107 @@
// Test basic styling using the grid.cell element.
---
// Cell override
#grid(
align: left,
fill: red,
stroke: blue,
inset: 5pt,
columns: 2,
[AAAAA], [BBBBB],
[A], [B],
grid.cell(align: right)[C], [D],
align(right)[E], [F],
align(horizon)[G], [A\ A\ A],
grid.cell(align: horizon)[G2], [A\ A\ A],
grid.cell(inset: 0pt)[I], [F],
[H], grid.cell(fill: blue)[J]
)
---
// Cell show rule
#show grid.cell: it => [Zz]
#grid(
align: left,
fill: red,
stroke: blue,
inset: 5pt,
columns: 2,
[AAAAA], [BBBBB],
[A], [B],
grid.cell(align: right)[C], [D],
align(right)[E], [F],
align(horizon)[G], [A\ A\ A]
)
---
#show grid.cell: it => (it.align, it.fill)
#grid(
align: left,
row-gutter: 5pt,
[A],
grid.cell(align: right)[B],
grid.cell(fill: aqua)[B],
)
---
// Cell set rules
#set grid.cell(align: center)
#show grid.cell: it => (it.align, it.fill, it.inset)
#set grid.cell(inset: 20pt)
#grid(
align: left,
row-gutter: 5pt,
[A],
grid.cell(align: right)[B],
grid.cell(fill: aqua)[B],
)
---
// Test folding per-cell properties (align and inset)
#grid(
columns: (1fr, 1fr),
rows: (2.5em, auto),
align: right,
inset: 5pt,
fill: (x, y) => (green, aqua).at(calc.rem(x + y, 2)),
[Top], grid.cell(align: bottom)[Bot],
grid.cell(inset: (bottom: 0pt))[Bot], grid.cell(inset: (bottom: 0pt))[Bot]
)
---
// Test overriding outside alignment
#set align(bottom + right)
#grid(
columns: (1fr, 1fr),
rows: 2em,
align: auto,
fill: green,
[BR], [BR],
grid.cell(align: left, fill: aqua)[BL], grid.cell(align: top, fill: red.lighten(50%))[TR]
)
---
// First doc example
#grid(
columns: 2,
fill: red,
align: left,
inset: 5pt,
[ABC], [ABC],
grid.cell(fill: blue)[C], [D],
grid.cell(align: center)[E], [F],
[G], grid.cell(inset: 0pt)[H]
)
---
#{
show grid.cell: emph
grid(
columns: 2,
gutter: 3pt,
[Hello], [World],
[Sweet], [Italics]
)
}

View File

@ -87,3 +87,48 @@ a
[A],
[B],
)
---
// Test interaction with gutters.
#grid(
columns: (3em, 3em),
fill: (x, y) => (red, blue).at(calc.rem(x, 2)),
align: (x, y) => (left, right).at(calc.rem(y, 2)),
[A], [B],
[C], [D],
[E], [F],
[G], [H]
)
#grid(
columns: (3em, 3em),
fill: (x, y) => (red, blue).at(calc.rem(x, 2)),
align: (x, y) => (left, right).at(calc.rem(y, 2)),
row-gutter: 5pt,
[A], [B],
[C], [D],
[E], [F],
[G], [H]
)
#grid(
columns: (3em, 3em),
fill: (x, y) => (red, blue).at(calc.rem(x, 2)),
align: (x, y) => (left, right).at(calc.rem(y, 2)),
column-gutter: 5pt,
[A], [B],
[C], [D],
[E], [F],
[G], [H]
)
#grid(
columns: (3em, 3em),
fill: (x, y) => (red, blue).at(calc.rem(x, 2)),
align: (x, y) => (left, right).at(calc.rem(y, 2)),
gutter: 5pt,
[A], [B],
[C], [D],
[E], [F],
[G], [H]
)

View File

@ -0,0 +1,102 @@
// Test basic styling using the table.cell element.
---
// Cell override
#table(
align: left,
fill: red,
stroke: blue,
columns: 2,
[AAAAA], [BBBBB],
[A], [B],
table.cell(align: right)[C], [D],
align(right)[E], [F],
align(horizon)[G], [A\ A\ A],
table.cell(align: horizon)[G2], [A\ A\ A],
table.cell(inset: 0pt)[I], [F],
[H], table.cell(fill: blue)[J]
)
---
// Cell show rule
#show table.cell: it => [Zz]
#table(
align: left,
fill: red,
stroke: blue,
columns: 2,
[AAAAA], [BBBBB],
[A], [B],
table.cell(align: right)[C], [D],
align(right)[E], [F],
align(horizon)[G], [A\ A\ A]
)
---
#show table.cell: it => (it.align, it.fill)
#table(
align: left,
row-gutter: 5pt,
[A],
table.cell(align: right)[B],
table.cell(fill: aqua)[B],
)
---
// Cell set rules
#set table.cell(align: center)
#show table.cell: it => (it.align, it.fill, it.inset)
#set table.cell(inset: 20pt)
#table(
align: left,
row-gutter: 5pt,
[A],
table.cell(align: right)[B],
table.cell(fill: aqua)[B],
)
---
// Test folding per-cell properties (align and inset)
#table(
columns: (1fr, 1fr),
rows: (2.5em, auto),
align: right,
fill: (x, y) => (green, aqua).at(calc.rem(x + y, 2)),
[Top], table.cell(align: bottom)[Bot],
table.cell(inset: (bottom: 0pt))[Bot], table.cell(inset: (bottom: 0pt))[Bot]
)
---
// Test overriding outside alignment
#set align(bottom + right)
#table(
columns: (1fr, 1fr),
rows: 2em,
align: auto,
fill: green,
[BR], [BR],
table.cell(align: left, fill: aqua)[BL], table.cell(align: top, fill: red.lighten(50%))[TR]
)
---
// First doc example
#table(
columns: 2,
fill: green,
align: right,
[*Name*], [*Data*],
table.cell(fill: blue)[J.], [Organizer],
table.cell(align: center)[K.], [Leader],
[M.], table.cell(inset: 0pt)[Player]
)
---
#{
show table.cell: emph
table(
columns: 2,
[Person], [Animal],
[John], [Dog]
)
}

View File

@ -61,6 +61,51 @@
[B],
)
---
// Test interaction with gutters.
#table(
columns: (3em, 3em),
fill: (x, y) => (red, blue).at(calc.rem(x, 2)),
align: (x, y) => (left, right).at(calc.rem(y, 2)),
[A], [B],
[C], [D],
[E], [F],
[G], [H]
)
#table(
columns: (3em, 3em),
fill: (x, y) => (red, blue).at(calc.rem(x, 2)),
align: (x, y) => (left, right).at(calc.rem(y, 2)),
row-gutter: 5pt,
[A], [B],
[C], [D],
[E], [F],
[G], [H]
)
#table(
columns: (3em, 3em),
fill: (x, y) => (red, blue).at(calc.rem(x, 2)),
align: (x, y) => (left, right).at(calc.rem(y, 2)),
column-gutter: 5pt,
[A], [B],
[C], [D],
[E], [F],
[G], [H]
)
#table(
columns: (3em, 3em),
fill: (x, y) => (red, blue).at(calc.rem(x, 2)),
align: (x, y) => (left, right).at(calc.rem(y, 2)),
gutter: 5pt,
[A], [B],
[C], [D],
[E], [F],
[G], [H]
)
---
// Ref: false
#table()