mirror of
https://github.com/typst/typst
synced 2025-05-18 19:15:29 +08:00
Table cell x
and y
fields [More Flexible Tables Pt.2b] (#3050)
This commit is contained in:
parent
7cb257a1ac
commit
21585e03cf
@ -1,4 +1,8 @@
|
|||||||
use crate::diag::{bail, At, SourceResult, StrResult};
|
use ecow::eco_format;
|
||||||
|
|
||||||
|
use crate::diag::{
|
||||||
|
bail, At, Hint, HintedStrResult, HintedString, SourceResult, StrResult,
|
||||||
|
};
|
||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
Array, CastInfo, Content, FromValue, Func, IntoValue, Reflect, Resolve, Smart,
|
Array, CastInfo, Content, FromValue, Func, IntoValue, Reflect, Resolve, Smart,
|
||||||
@ -83,6 +87,7 @@ impl<T: FromValue> FromValue for Celled<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a cell in CellGrid, to be laid out by GridLayouter.
|
/// Represents a cell in CellGrid, to be laid out by GridLayouter.
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Cell {
|
pub struct Cell {
|
||||||
/// The cell's body.
|
/// The cell's body.
|
||||||
pub body: Content,
|
pub body: Content,
|
||||||
@ -123,6 +128,15 @@ pub trait ResolvableCell {
|
|||||||
inset: Sides<Rel<Length>>,
|
inset: Sides<Rel<Length>>,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> Cell;
|
) -> Cell;
|
||||||
|
|
||||||
|
/// Returns this cell's column override.
|
||||||
|
fn x(&self, styles: StyleChain) -> Smart<usize>;
|
||||||
|
|
||||||
|
/// Returns this cell's row override.
|
||||||
|
fn y(&self, styles: StyleChain) -> Smart<usize>;
|
||||||
|
|
||||||
|
/// The cell's span, for errors.
|
||||||
|
fn span(&self) -> Span;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A grid of cells, including the columns, rows, and cell data.
|
/// A grid of cells, including the columns, rows, and cell data.
|
||||||
@ -200,12 +214,12 @@ impl CellGrid {
|
|||||||
Self { cols, rows, cells, has_gutter, is_rtl }
|
Self { cols, rows, cells, has_gutter, is_rtl }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolves all cells in the grid before creating it.
|
/// Resolves and positions all cells in the grid before creating it.
|
||||||
/// Allows them to keep track of their final properties and adjust their
|
/// Allows them to keep track of their final properties and positions
|
||||||
/// fields accordingly.
|
/// and adjust their fields accordingly.
|
||||||
/// Cells must implement Clone as they will be owned. Additionally, they
|
/// 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
|
/// must implement Default in order to fill positions in the grid which
|
||||||
/// empty cells, if it is not completely filled.
|
/// weren't explicitly specified by the user with empty cells.
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn resolve<T: ResolvableCell + Clone + Default>(
|
pub fn resolve<T: ResolvableCell + Clone + Default>(
|
||||||
tracks: Axes<&[Sizing]>,
|
tracks: Axes<&[Sizing]>,
|
||||||
@ -216,38 +230,129 @@ impl CellGrid {
|
|||||||
inset: Sides<Rel<Length>>,
|
inset: Sides<Rel<Length>>,
|
||||||
engine: &mut Engine,
|
engine: &mut Engine,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
|
span: Span,
|
||||||
) -> SourceResult<Self> {
|
) -> SourceResult<Self> {
|
||||||
// Number of content columns: Always at least one.
|
// Number of content columns: Always at least one.
|
||||||
let c = tracks.x.len().max(1);
|
let c = tracks.x.len().max(1);
|
||||||
|
|
||||||
// If not all columns in the last row have cells, we will add empty
|
// We can't just use the cell's index in the 'cells' vector to
|
||||||
// cells and complete the row so that those positions are susceptible
|
// determine its automatic position, since cells could have arbitrary
|
||||||
// to show rules and receive grid styling.
|
// positions, so the position of a cell in 'cells' can differ from its
|
||||||
// We apply '% c' twice so that 'cells_remaining' is zero when
|
// final position in 'resolved_cells' (see below).
|
||||||
// the last row is already filled (then 'cell_count % c' would be zero).
|
// Therefore, we use a counter, 'auto_index', to determine the position
|
||||||
let cell_count = cells.len();
|
// of the next cell with (x: auto, y: auto). It is only stepped when
|
||||||
let cells_remaining = (c - cell_count % c) % c;
|
// a cell with (x: auto, y: auto), usually the vast majority, is found.
|
||||||
let cells = cells
|
let mut auto_index = 0;
|
||||||
.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(
|
// We have to rebuild the grid to account for arbitrary positions.
|
||||||
|
// Create at least 'cells.len()' positions, since there will be at
|
||||||
|
// least 'cells.len()' cells, even though some of them might be placed
|
||||||
|
// in arbitrary positions and thus cause the grid to expand.
|
||||||
|
// Additionally, make sure we allocate up to the next multiple of 'c',
|
||||||
|
// since each row will have 'c' cells, even if the last few cells
|
||||||
|
// weren't explicitly specified by the user.
|
||||||
|
// We apply '% c' twice so that the amount of cells potentially missing
|
||||||
|
// is zero when 'cells.len()' is already a multiple of 'c' (thus
|
||||||
|
// 'cells.len() % c' would be zero).
|
||||||
|
let Some(cell_count) = cells.len().checked_add((c - cells.len() % c) % c) else {
|
||||||
|
bail!(span, "too many cells were given")
|
||||||
|
};
|
||||||
|
let mut resolved_cells: Vec<Option<Cell>> = Vec::with_capacity(cell_count);
|
||||||
|
for cell in cells.iter().cloned() {
|
||||||
|
let cell_span = cell.span();
|
||||||
|
// Let's calculate the cell's final position based on its
|
||||||
|
// requested position.
|
||||||
|
let resolved_index = {
|
||||||
|
let cell_x = cell.x(styles);
|
||||||
|
let cell_y = cell.y(styles);
|
||||||
|
resolve_cell_position(cell_x, cell_y, &resolved_cells, &mut auto_index, c)
|
||||||
|
.at(cell_span)?
|
||||||
|
};
|
||||||
|
let x = resolved_index % c;
|
||||||
|
let y = resolved_index / c;
|
||||||
|
|
||||||
|
// Let's resolve the cell so it can determine its own fields
|
||||||
|
// based on its final position.
|
||||||
|
let cell = cell.resolve_cell(
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
&fill.resolve(engine, x, y)?,
|
&fill.resolve(engine, x, y)?,
|
||||||
align.resolve(engine, x, y)?,
|
align.resolve(engine, x, y)?,
|
||||||
inset,
|
inset,
|
||||||
styles,
|
styles,
|
||||||
))
|
);
|
||||||
})
|
|
||||||
.collect::<SourceResult<Vec<_>>>()?;
|
|
||||||
|
|
||||||
Ok(Self::new(tracks, gutter, cells, styles))
|
if resolved_index >= resolved_cells.len() {
|
||||||
|
// Ensure the length of the vector of resolved cells is always
|
||||||
|
// a multiple of 'c' by pushing full rows every time. Here, we
|
||||||
|
// add enough absent positions (later converted to empty cells)
|
||||||
|
// to ensure the last row in the new vector length is
|
||||||
|
// completely filled. This is necessary so that those
|
||||||
|
// positions, even if not explicitly used at the end, are
|
||||||
|
// eventually susceptible to show rules and receive grid
|
||||||
|
// styling, as they will be resolved as empty cells in a second
|
||||||
|
// loop below.
|
||||||
|
let Some(new_len) = resolved_index
|
||||||
|
.checked_add(1)
|
||||||
|
.and_then(|new_len| new_len.checked_add((c - new_len % c) % c))
|
||||||
|
else {
|
||||||
|
bail!(cell_span, "cell position too large")
|
||||||
|
};
|
||||||
|
|
||||||
|
// Here, the cell needs to be placed in a position which
|
||||||
|
// doesn't exist yet in the grid (out of bounds). We will add
|
||||||
|
// enough absent positions for this to be possible. They must
|
||||||
|
// be absent as no cells actually occupy them (they can be
|
||||||
|
// overridden later); however, if no cells occupy them as we
|
||||||
|
// finish building the grid, then such positions will be
|
||||||
|
// replaced by empty cells.
|
||||||
|
resolved_cells.resize(new_len, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The vector is large enough to contain the cell, so we can just
|
||||||
|
// index it directly to access the position it will be placed in.
|
||||||
|
// However, we still need to ensure we won't try to place a cell
|
||||||
|
// where there already is one.
|
||||||
|
let slot = &mut resolved_cells[resolved_index];
|
||||||
|
if slot.is_some() {
|
||||||
|
bail!(
|
||||||
|
cell_span,
|
||||||
|
"attempted to place a second cell at column {x}, row {y}";
|
||||||
|
hint: "try specifying your cells in a different order"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
*slot = Some(cell);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace absent entries by resolved empty cells, and produce a vector
|
||||||
|
// of 'Cell' from 'Option<Cell>' (final step).
|
||||||
|
let resolved_cells = resolved_cells
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, cell)| {
|
||||||
|
if let Some(cell) = cell {
|
||||||
|
Ok(cell)
|
||||||
|
} else {
|
||||||
|
let x = i % c;
|
||||||
|
let y = i / c;
|
||||||
|
|
||||||
|
// Ensure all absent entries are affected by show rules and
|
||||||
|
// grid styling by turning them into resolved empty cells.
|
||||||
|
let new_cell = T::default().resolve_cell(
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
&fill.resolve(engine, x, y)?,
|
||||||
|
align.resolve(engine, x, y)?,
|
||||||
|
inset,
|
||||||
|
styles,
|
||||||
|
);
|
||||||
|
Ok(new_cell)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<SourceResult<Vec<Cell>>>()?;
|
||||||
|
|
||||||
|
Ok(Self::new(tracks, gutter, resolved_cells, styles))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the content of the cell in column `x` and row `y`.
|
/// Get the content of the cell in column `x` and row `y`.
|
||||||
@ -278,6 +383,98 @@ impl CellGrid {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Given a cell's requested x and y, the vector with the resolved cell
|
||||||
|
/// positions, the `auto_index` counter (determines the position of the next
|
||||||
|
/// `(auto, auto)` cell) and the amount of columns in the grid, returns the
|
||||||
|
/// final index of this cell in the vector of resolved cells.
|
||||||
|
fn resolve_cell_position(
|
||||||
|
cell_x: Smart<usize>,
|
||||||
|
cell_y: Smart<usize>,
|
||||||
|
resolved_cells: &[Option<Cell>],
|
||||||
|
auto_index: &mut usize,
|
||||||
|
columns: usize,
|
||||||
|
) -> HintedStrResult<usize> {
|
||||||
|
// Translates a (x, y) position to the equivalent index in the final cell vector.
|
||||||
|
// Errors if the position would be too large.
|
||||||
|
let cell_index = |x, y: usize| {
|
||||||
|
y.checked_mul(columns)
|
||||||
|
.and_then(|row_index| row_index.checked_add(x))
|
||||||
|
.ok_or_else(|| HintedString::from(eco_format!("cell position too large")))
|
||||||
|
};
|
||||||
|
match (cell_x, cell_y) {
|
||||||
|
// Fully automatic cell positioning. The cell did not
|
||||||
|
// request a coordinate.
|
||||||
|
(Smart::Auto, Smart::Auto) => {
|
||||||
|
// Let's find the first available position starting from the
|
||||||
|
// automatic position counter, searching in row-major order.
|
||||||
|
let mut resolved_index = *auto_index;
|
||||||
|
while let Some(Some(_)) = resolved_cells.get(resolved_index) {
|
||||||
|
// Skip any non-absent cell positions (`Some(None)`) to
|
||||||
|
// determine where this cell will be placed. An out of bounds
|
||||||
|
// position (thus `None`) is also a valid new position (only
|
||||||
|
// requires expanding the vector).
|
||||||
|
resolved_index += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the next cell with automatic position will be
|
||||||
|
// placed after this one (maybe not immediately after).
|
||||||
|
*auto_index = resolved_index + 1;
|
||||||
|
|
||||||
|
Ok(resolved_index)
|
||||||
|
}
|
||||||
|
// Cell has chosen at least its column.
|
||||||
|
(Smart::Custom(cell_x), cell_y) => {
|
||||||
|
if cell_x >= columns {
|
||||||
|
return Err(HintedString::from(eco_format!(
|
||||||
|
"cell could not be placed at invalid column {cell_x}"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
if let Smart::Custom(cell_y) = cell_y {
|
||||||
|
// Cell has chosen its exact position.
|
||||||
|
cell_index(cell_x, cell_y)
|
||||||
|
} else {
|
||||||
|
// Cell has only chosen its column.
|
||||||
|
// Let's find the first row which has that column available.
|
||||||
|
let mut resolved_y = 0;
|
||||||
|
while let Some(Some(_)) =
|
||||||
|
resolved_cells.get(cell_index(cell_x, resolved_y)?)
|
||||||
|
{
|
||||||
|
// Try each row until either we reach an absent position
|
||||||
|
// (`Some(None)`) or an out of bounds position (`None`),
|
||||||
|
// in which case we'd create a new row to place this cell in.
|
||||||
|
resolved_y += 1;
|
||||||
|
}
|
||||||
|
cell_index(cell_x, resolved_y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Cell has only chosen its row, not its column.
|
||||||
|
(Smart::Auto, Smart::Custom(cell_y)) => {
|
||||||
|
// Let's find the first column which has that row available.
|
||||||
|
let first_row_pos = cell_index(0, cell_y)?;
|
||||||
|
let last_row_pos = first_row_pos
|
||||||
|
.checked_add(columns)
|
||||||
|
.ok_or_else(|| eco_format!("cell position too large"))?;
|
||||||
|
|
||||||
|
(first_row_pos..last_row_pos)
|
||||||
|
.find(|possible_index| {
|
||||||
|
// Much like in the previous cases, we skip any occupied
|
||||||
|
// positions until we either reach an absent position
|
||||||
|
// (`Some(None)`) or an out of bounds position (`None`),
|
||||||
|
// in which case we can just expand the vector enough to
|
||||||
|
// place this cell. In either case, we found an available
|
||||||
|
// position.
|
||||||
|
!matches!(resolved_cells.get(*possible_index), Some(Some(_)))
|
||||||
|
})
|
||||||
|
.ok_or_else(|| {
|
||||||
|
eco_format!(
|
||||||
|
"cell could not be placed in row {cell_y} because it was full"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.hint("try specifying your cells in a different order")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Performs grid layout.
|
/// Performs grid layout.
|
||||||
pub struct GridLayouter<'a> {
|
pub struct GridLayouter<'a> {
|
||||||
/// The grid of cells.
|
/// The grid of cells.
|
||||||
|
@ -4,18 +4,19 @@ pub use self::layout::{Cell, CellGrid, Celled, GridLayouter, ResolvableCell};
|
|||||||
|
|
||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
|
use ecow::eco_format;
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
|
||||||
use crate::diag::{SourceResult, StrResult};
|
use crate::diag::{SourceResult, StrResult, Trace, Tracepoint};
|
||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
cast, elem, scope, Array, Content, Fold, NativeElement, Packed, Show, Smart,
|
cast, elem, scope, Array, Content, Fold, Packed, Show, Smart, StyleChain, Value,
|
||||||
StyleChain, Value,
|
|
||||||
};
|
};
|
||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
Abs, AlignElem, Alignment, Axes, Fragment, Layout, Length, Regions, Rel, Sides,
|
Abs, AlignElem, Alignment, Axes, Fragment, Layout, Length, Regions, Rel, Sides,
|
||||||
Sizing,
|
Sizing,
|
||||||
};
|
};
|
||||||
|
use crate::syntax::Span;
|
||||||
use crate::visualize::{Paint, Stroke};
|
use crate::visualize::{Paint, Stroke};
|
||||||
|
|
||||||
/// Arranges content in a grid.
|
/// Arranges content in a grid.
|
||||||
@ -60,7 +61,8 @@ use crate::visualize::{Paint, Stroke};
|
|||||||
/// appearance options to depend on a cell's position (column and row), you may
|
/// appearance options to depend on a cell's position (column and row), you may
|
||||||
/// specify a function to `fill` or `align` of the form
|
/// specify a function to `fill` or `align` of the form
|
||||||
/// `(column, row) => value`. You may also use a show rule on
|
/// `(column, row) => value`. You may also use a show rule on
|
||||||
/// [`grid.cell`]($grid.cell) - see that element's examples for more information.
|
/// [`grid.cell`]($grid.cell) - see that element's examples or the examples
|
||||||
|
/// below for more information.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// The example below demonstrates the different track sizing options.
|
/// The example below demonstrates the different track sizing options.
|
||||||
@ -97,6 +99,61 @@ use crate::visualize::{Paint, Stroke};
|
|||||||
/// ..range(25).map(str)
|
/// ..range(25).map(str)
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
|
///
|
||||||
|
/// Additionally, you can use [`grid.cell`]($grid.cell) in various ways to
|
||||||
|
/// not only style each cell based on its position and other fields, but also
|
||||||
|
/// to determine the cell's preferential position in the table.
|
||||||
|
///
|
||||||
|
/// ```example
|
||||||
|
/// #set page(width: auto)
|
||||||
|
/// #show grid.cell: it => {
|
||||||
|
/// if it.y == 0 {
|
||||||
|
/// // The first row's text must be white and bold.
|
||||||
|
/// set text(white)
|
||||||
|
/// strong(it)
|
||||||
|
/// } else {
|
||||||
|
/// // For the second row and beyond, we will show the day number for each
|
||||||
|
/// // cell.
|
||||||
|
///
|
||||||
|
/// // In general, a cell's index is given by cell.x + columns * cell.y.
|
||||||
|
/// // Days start in the second grid row, so we subtract 1 row.
|
||||||
|
/// // But the first day is day 1, not day 0, so we add 1.
|
||||||
|
/// let day = it.x + 7 * (it.y - 1) + 1
|
||||||
|
/// if day <= 31 {
|
||||||
|
/// // Place the day's number at the top left of the cell.
|
||||||
|
/// // Only if the day is valid for this month (not 32 or higher).
|
||||||
|
/// place(top + left, dx: 2pt, dy: 2pt, text(8pt, red.darken(40%))[#day])
|
||||||
|
/// }
|
||||||
|
/// it
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #grid(
|
||||||
|
/// fill: (x, y) => if y == 0 { gray.darken(50%) },
|
||||||
|
/// columns: (30pt,) * 7,
|
||||||
|
/// rows: (auto, 30pt),
|
||||||
|
/// // Events will be written at the bottom of each day square.
|
||||||
|
/// align: bottom,
|
||||||
|
/// inset: 5pt,
|
||||||
|
/// stroke: (thickness: 0.5pt, dash: "densely-dotted"),
|
||||||
|
///
|
||||||
|
/// [Sun], [Mon], [Tue], [Wed], [Thu], [Fri], [Sat],
|
||||||
|
///
|
||||||
|
/// // This event will occur on the first Friday (sixth column).
|
||||||
|
/// grid.cell(x: 5, fill: yellow.darken(10%))[Call],
|
||||||
|
///
|
||||||
|
/// // This event will occur every Monday (second column).
|
||||||
|
/// // We have to repeat it 5 times so it occurs every week.
|
||||||
|
/// ..(grid.cell(x: 1, fill: red.lighten(50%))[Meet],) * 5,
|
||||||
|
///
|
||||||
|
/// // This event will occur at day 19.
|
||||||
|
/// grid.cell(x: 4, y: 3, fill: orange.lighten(25%))[Talk],
|
||||||
|
///
|
||||||
|
/// // These events will occur at the second week, where available.
|
||||||
|
/// grid.cell(y: 2, fill: aqua)[Chat],
|
||||||
|
/// grid.cell(y: 2, fill: aqua)[Walk],
|
||||||
|
/// )
|
||||||
|
/// ```
|
||||||
#[elem(scope, Layout)]
|
#[elem(scope, Layout)]
|
||||||
pub struct GridElem {
|
pub struct GridElem {
|
||||||
/// The column sizes.
|
/// The column sizes.
|
||||||
@ -213,7 +270,7 @@ pub struct GridElem {
|
|||||||
///
|
///
|
||||||
/// The cells are populated in row-major order.
|
/// The cells are populated in row-major order.
|
||||||
#[variadic]
|
#[variadic]
|
||||||
pub children: Vec<GridCell>,
|
pub children: Vec<Packed<GridCell>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[scope]
|
#[scope]
|
||||||
@ -241,6 +298,8 @@ impl Layout for Packed<GridElem> {
|
|||||||
|
|
||||||
let tracks = Axes::new(columns.0.as_slice(), rows.0.as_slice());
|
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 gutter = Axes::new(column_gutter.0.as_slice(), row_gutter.0.as_slice());
|
||||||
|
// Use trace to link back to the grid when a specific cell errors
|
||||||
|
let tracepoint = || Tracepoint::Call(Some(eco_format!("grid")));
|
||||||
let grid = CellGrid::resolve(
|
let grid = CellGrid::resolve(
|
||||||
tracks,
|
tracks,
|
||||||
gutter,
|
gutter,
|
||||||
@ -250,7 +309,9 @@ impl Layout for Packed<GridElem> {
|
|||||||
inset,
|
inset,
|
||||||
engine,
|
engine,
|
||||||
styles,
|
styles,
|
||||||
)?;
|
self.span(),
|
||||||
|
)
|
||||||
|
.trace(engine.world, tracepoint, self.span())?;
|
||||||
|
|
||||||
let layouter = GridLayouter::new(&grid, &stroke, regions, styles, self.span());
|
let layouter = GridLayouter::new(&grid, &stroke, regions, styles, self.span());
|
||||||
|
|
||||||
@ -290,12 +351,84 @@ cast! {
|
|||||||
/// [G], grid.cell(inset: 0pt)[H]
|
/// [G], grid.cell(inset: 0pt)[H]
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
|
///
|
||||||
|
/// You may also apply a show rule on `grid.cell` to style all cells at once,
|
||||||
|
/// which allows you, for example, to apply styles based on a cell's position:
|
||||||
|
///
|
||||||
|
/// ```example
|
||||||
|
/// #show grid.cell: it => {
|
||||||
|
/// if it.y == 0 {
|
||||||
|
/// // First row is bold
|
||||||
|
/// strong(it)
|
||||||
|
/// } else if it.x == 1 {
|
||||||
|
/// // Second column is italicized
|
||||||
|
/// // (except at the first row)
|
||||||
|
/// emph(it)
|
||||||
|
/// } else {
|
||||||
|
/// // Remaining cells aren't changed
|
||||||
|
/// it
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #grid(
|
||||||
|
/// columns: 3,
|
||||||
|
/// gutter: 3pt,
|
||||||
|
/// [Name], [Age], [Info],
|
||||||
|
/// [John], [52], [Nice],
|
||||||
|
/// [Mary], [50], [Cool],
|
||||||
|
/// [Jake], [49], [Epic]
|
||||||
|
/// )
|
||||||
|
/// ```
|
||||||
#[elem(name = "cell", title = "Grid Cell", Show)]
|
#[elem(name = "cell", title = "Grid Cell", Show)]
|
||||||
pub struct GridCell {
|
pub struct GridCell {
|
||||||
/// The cell's body.
|
/// The cell's body.
|
||||||
#[required]
|
#[required]
|
||||||
body: Content,
|
body: Content,
|
||||||
|
|
||||||
|
/// The cell's column (zero-indexed).
|
||||||
|
/// This field may be used in show rules to style a cell depending on its
|
||||||
|
/// column.
|
||||||
|
///
|
||||||
|
/// You may override this field to pick in which column the cell must
|
||||||
|
/// be placed. If no row (`y`) is chosen, the cell will be placed in the
|
||||||
|
/// first row (starting at row 0) with that column available (or a new row
|
||||||
|
/// if none). If both `x` and `y` are chosen, however, the cell will be
|
||||||
|
/// placed in that exact position. An error is raised if that position is
|
||||||
|
/// not available (thus, it is usually wise to specify cells with a custom
|
||||||
|
/// position before cells with automatic positions).
|
||||||
|
///
|
||||||
|
/// ```example
|
||||||
|
/// #grid(
|
||||||
|
/// columns: 4,
|
||||||
|
/// rows: 2.5em,
|
||||||
|
/// fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) },
|
||||||
|
/// align: center + horizon,
|
||||||
|
/// inset: 3pt,
|
||||||
|
/// grid.cell(x: 2, y: 2)[3],
|
||||||
|
/// [1], grid.cell(x: 3)[4], [2],
|
||||||
|
/// )
|
||||||
|
/// ```
|
||||||
|
x: Smart<usize>,
|
||||||
|
|
||||||
|
/// The cell's row (zero-indexed).
|
||||||
|
/// This field may be used in show rules to style a cell depending on its
|
||||||
|
/// row.
|
||||||
|
///
|
||||||
|
/// You may override this field to pick in which row the cell must be
|
||||||
|
/// placed. If no column (`x`) is chosen, the cell will be placed in the
|
||||||
|
/// first column (starting at column 0) available in the chosen row. If all
|
||||||
|
/// columns in the chosen row are already occupied, an error is raised.
|
||||||
|
///
|
||||||
|
/// ```example
|
||||||
|
/// #grid(
|
||||||
|
/// columns: 2,
|
||||||
|
/// fill: (x, y) => if calc.odd(x + y) { gray.lighten(40%) },
|
||||||
|
/// inset: 1pt,
|
||||||
|
/// [A], grid.cell(y: 1)[B], grid.cell(y: 1)[C], grid.cell(y: 2)[D]
|
||||||
|
/// )
|
||||||
|
/// ```
|
||||||
|
y: Smart<usize>,
|
||||||
|
|
||||||
/// The cell's fill override.
|
/// The cell's fill override.
|
||||||
fill: Smart<Option<Paint>>,
|
fill: Smart<Option<Paint>>,
|
||||||
|
|
||||||
@ -311,39 +444,54 @@ cast! {
|
|||||||
v: Content => v.into(),
|
v: Content => v.into(),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for GridCell {
|
impl Default for Packed<GridCell> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new(Content::default())
|
Packed::new(GridCell::new(Content::default()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResolvableCell for GridCell {
|
impl ResolvableCell for Packed<GridCell> {
|
||||||
fn resolve_cell(
|
fn resolve_cell(
|
||||||
mut self,
|
mut self,
|
||||||
_: usize,
|
x: usize,
|
||||||
_: usize,
|
y: usize,
|
||||||
fill: &Option<Paint>,
|
fill: &Option<Paint>,
|
||||||
align: Smart<Alignment>,
|
align: Smart<Alignment>,
|
||||||
inset: Sides<Rel<Length>>,
|
inset: Sides<Rel<Length>>,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> Cell {
|
) -> Cell {
|
||||||
let fill = self.fill(styles).unwrap_or_else(|| fill.clone());
|
let cell = &mut *self;
|
||||||
self.push_fill(Smart::Custom(fill.clone()));
|
let fill = cell.fill(styles).unwrap_or_else(|| fill.clone());
|
||||||
self.push_align(match align {
|
cell.push_x(Smart::Custom(x));
|
||||||
|
cell.push_y(Smart::Custom(y));
|
||||||
|
cell.push_fill(Smart::Custom(fill.clone()));
|
||||||
|
cell.push_align(match align {
|
||||||
Smart::Custom(align) => {
|
Smart::Custom(align) => {
|
||||||
Smart::Custom(self.align(styles).map_or(align, |inner| inner.fold(align)))
|
Smart::Custom(cell.align(styles).map_or(align, |inner| inner.fold(align)))
|
||||||
}
|
}
|
||||||
// Don't fold if the grid is using outer alignment. Use the
|
// Don't fold if the grid is using outer alignment. Use the
|
||||||
// cell's alignment instead (which, in the end, will fold with
|
// cell's alignment instead (which, in the end, will fold with
|
||||||
// the outer alignment when it is effectively displayed).
|
// the outer alignment when it is effectively displayed).
|
||||||
Smart::Auto => self.align(styles),
|
Smart::Auto => cell.align(styles),
|
||||||
});
|
});
|
||||||
self.push_inset(Smart::Custom(
|
cell.push_inset(Smart::Custom(
|
||||||
self.inset(styles).map_or(inset, |inner| inner.fold(inset)).map(Some),
|
cell.inset(styles).map_or(inset, |inner| inner.fold(inset)).map(Some),
|
||||||
));
|
));
|
||||||
|
|
||||||
Cell { body: self.pack(), fill }
|
Cell { body: self.pack(), fill }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn x(&self, styles: StyleChain) -> Smart<usize> {
|
||||||
|
(**self).x(styles)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn y(&self, styles: StyleChain) -> Smart<usize> {
|
||||||
|
(**self).y(styles)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn span(&self) -> Span {
|
||||||
|
Packed::span(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<GridCell> {
|
impl Show for Packed<GridCell> {
|
||||||
|
@ -240,8 +240,13 @@ impl Show for Packed<BibliographyElem> {
|
|||||||
if references.iter().any(|(prefix, _)| prefix.is_some()) {
|
if references.iter().any(|(prefix, _)| prefix.is_some()) {
|
||||||
let mut cells = vec![];
|
let mut cells = vec![];
|
||||||
for (prefix, reference) in references {
|
for (prefix, reference) in references {
|
||||||
cells.push(GridCell::new(prefix.clone().unwrap_or_default()));
|
cells.push(
|
||||||
cells.push(GridCell::new(reference.clone()));
|
Packed::new(GridCell::new(prefix.clone().unwrap_or_default()))
|
||||||
|
.spanned(span),
|
||||||
|
);
|
||||||
|
cells.push(
|
||||||
|
Packed::new(GridCell::new(reference.clone())).spanned(span),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
seq.push(VElem::new(row_gutter).with_weakness(3).pack());
|
seq.push(VElem::new(row_gutter).with_weakness(3).pack());
|
||||||
@ -945,7 +950,10 @@ impl ElemRenderer<'_> {
|
|||||||
|
|
||||||
if let Some(prefix) = suf_prefix {
|
if let Some(prefix) = suf_prefix {
|
||||||
const COLUMN_GUTTER: Em = Em::new(0.65);
|
const COLUMN_GUTTER: Em = Em::new(0.65);
|
||||||
content = GridElem::new(vec![GridCell::new(prefix), GridCell::new(content)])
|
content = GridElem::new(vec![
|
||||||
|
Packed::new(GridCell::new(prefix)).spanned(self.span),
|
||||||
|
Packed::new(GridCell::new(content)).spanned(self.span),
|
||||||
|
])
|
||||||
.with_columns(TrackSizings(smallvec![Sizing::Auto; 2]))
|
.with_columns(TrackSizings(smallvec![Sizing::Auto; 2]))
|
||||||
.with_column_gutter(TrackSizings(smallvec![COLUMN_GUTTER.into()]))
|
.with_column_gutter(TrackSizings(smallvec![COLUMN_GUTTER.into()]))
|
||||||
.pack()
|
.pack()
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
use crate::diag::SourceResult;
|
use ecow::eco_format;
|
||||||
|
|
||||||
|
use crate::diag::{SourceResult, Trace, Tracepoint};
|
||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
cast, elem, scope, Content, Fold, NativeElement, 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, Fragment, GridLayouter,
|
show_grid_cell, Abs, Alignment, Axes, Cell, CellGrid, Celled, Fragment, GridLayouter,
|
||||||
Layout, Length, Regions, Rel, ResolvableCell, Sides, TrackSizings,
|
Layout, Length, Regions, Rel, ResolvableCell, Sides, TrackSizings,
|
||||||
};
|
};
|
||||||
use crate::model::Figurable;
|
use crate::model::Figurable;
|
||||||
|
use crate::syntax::Span;
|
||||||
use crate::text::{Lang, LocalName, Region};
|
use crate::text::{Lang, LocalName, Region};
|
||||||
use crate::visualize::{Paint, Stroke};
|
use crate::visualize::{Paint, Stroke};
|
||||||
|
|
||||||
@ -29,6 +32,8 @@ use crate::visualize::{Paint, Stroke};
|
|||||||
/// [figure]($figure).
|
/// [figure]($figure).
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
|
///
|
||||||
|
/// The example below demonstrates some of the most common table options.
|
||||||
/// ```example
|
/// ```example
|
||||||
/// #table(
|
/// #table(
|
||||||
/// columns: (1fr, auto, auto),
|
/// columns: (1fr, auto, auto),
|
||||||
@ -47,6 +52,40 @@ use crate::visualize::{Paint, Stroke};
|
|||||||
/// [$a$: edge length]
|
/// [$a$: edge length]
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
|
///
|
||||||
|
/// Much like with grids, you can use [`table.cell`]($table.cell) to customize
|
||||||
|
/// the appearance and the position of each cell.
|
||||||
|
///
|
||||||
|
/// ```example
|
||||||
|
/// #set page(width: auto)
|
||||||
|
/// #show table.cell: it => {
|
||||||
|
/// if it.x == 0 or it.y == 0 {
|
||||||
|
/// set text(white)
|
||||||
|
/// strong(it)
|
||||||
|
/// } else if it.body == [] {
|
||||||
|
/// // Replace empty cells with 'N/A'
|
||||||
|
/// pad(rest: it.inset)[_N/A_]
|
||||||
|
/// } else {
|
||||||
|
/// it
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #table(
|
||||||
|
/// fill: (x, y) => if x == 0 or y == 0 { gray.darken(50%) },
|
||||||
|
/// columns: 4,
|
||||||
|
/// [], [Exam 1], [Exam 2], [Exam 3],
|
||||||
|
/// ..([John], [Mary], [Jake], [Robert]).map(table.cell.with(x: 0)),
|
||||||
|
///
|
||||||
|
/// // Mary got grade A on Exam 3.
|
||||||
|
/// table.cell(x: 3, y: 2, fill: green)[A],
|
||||||
|
///
|
||||||
|
/// // Everyone got grade A on Exam 2.
|
||||||
|
/// ..(table.cell(x: 2, fill: green)[A],) * 4,
|
||||||
|
///
|
||||||
|
/// // Robert got grade B on other exams.
|
||||||
|
/// ..(table.cell(y: 4, fill: aqua)[B],) * 2,
|
||||||
|
/// )
|
||||||
|
/// ```
|
||||||
#[elem(scope, Layout, LocalName, Figurable)]
|
#[elem(scope, Layout, LocalName, Figurable)]
|
||||||
pub struct TableElem {
|
pub struct TableElem {
|
||||||
/// The column sizes. See the [grid documentation]($grid) for more
|
/// The column sizes. See the [grid documentation]($grid) for more
|
||||||
@ -157,7 +196,7 @@ pub struct TableElem {
|
|||||||
|
|
||||||
/// The contents of the table cells.
|
/// The contents of the table cells.
|
||||||
#[variadic]
|
#[variadic]
|
||||||
pub children: Vec<TableCell>,
|
pub children: Vec<Packed<TableCell>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[scope]
|
#[scope]
|
||||||
@ -185,6 +224,8 @@ impl Layout for Packed<TableElem> {
|
|||||||
|
|
||||||
let tracks = Axes::new(columns.0.as_slice(), rows.0.as_slice());
|
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 gutter = Axes::new(column_gutter.0.as_slice(), row_gutter.0.as_slice());
|
||||||
|
// Use trace to link back to the table when a specific cell errors
|
||||||
|
let tracepoint = || Tracepoint::Call(Some(eco_format!("table")));
|
||||||
let grid = CellGrid::resolve(
|
let grid = CellGrid::resolve(
|
||||||
tracks,
|
tracks,
|
||||||
gutter,
|
gutter,
|
||||||
@ -194,7 +235,9 @@ impl Layout for Packed<TableElem> {
|
|||||||
inset,
|
inset,
|
||||||
engine,
|
engine,
|
||||||
styles,
|
styles,
|
||||||
)?;
|
self.span(),
|
||||||
|
)
|
||||||
|
.trace(engine.world, tracepoint, self.span())?;
|
||||||
|
|
||||||
let layouter = GridLayouter::new(&grid, &stroke, regions, styles, self.span());
|
let layouter = GridLayouter::new(&grid, &stroke, regions, styles, self.span());
|
||||||
|
|
||||||
@ -259,12 +302,48 @@ impl Figurable for Packed<TableElem> {}
|
|||||||
/// [M.], table.cell(inset: 0pt)[Player]
|
/// [M.], table.cell(inset: 0pt)[Player]
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
|
///
|
||||||
|
/// You may also apply a show rule on `table.cell` to style all cells at once,
|
||||||
|
/// which allows you, for example, to apply styles based on a cell's position:
|
||||||
|
///
|
||||||
|
/// ```example
|
||||||
|
/// #show table.cell: it => {
|
||||||
|
/// if it.y == 0 {
|
||||||
|
/// // First row is bold
|
||||||
|
/// strong(it)
|
||||||
|
/// } else if it.x == 1 {
|
||||||
|
/// // Second column is italicized
|
||||||
|
/// // (except at the first row)
|
||||||
|
/// emph(it)
|
||||||
|
/// } else {
|
||||||
|
/// // Remaining cells aren't changed
|
||||||
|
/// it
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #table(
|
||||||
|
/// columns: 3,
|
||||||
|
/// gutter: 3pt,
|
||||||
|
/// [Name], [Age], [Info],
|
||||||
|
/// [John], [52], [Nice],
|
||||||
|
/// [Mary], [50], [Cool],
|
||||||
|
/// [Jake], [49], [Epic]
|
||||||
|
/// )
|
||||||
|
/// ```
|
||||||
#[elem(name = "cell", title = "Table Cell", Show)]
|
#[elem(name = "cell", title = "Table Cell", Show)]
|
||||||
pub struct TableCell {
|
pub struct TableCell {
|
||||||
/// The cell's body.
|
/// The cell's body.
|
||||||
#[required]
|
#[required]
|
||||||
body: Content,
|
body: Content,
|
||||||
|
|
||||||
|
/// The cell's column (zero-indexed).
|
||||||
|
/// Functions identically to the `x` field in [`grid.cell`]($grid.cell).
|
||||||
|
x: Smart<usize>,
|
||||||
|
|
||||||
|
/// The cell's row (zero-indexed).
|
||||||
|
/// Functions identically to the `y` field in [`grid.cell`]($grid.cell).
|
||||||
|
y: Smart<usize>,
|
||||||
|
|
||||||
/// The cell's fill override.
|
/// The cell's fill override.
|
||||||
fill: Smart<Option<Paint>>,
|
fill: Smart<Option<Paint>>,
|
||||||
|
|
||||||
@ -280,39 +359,54 @@ cast! {
|
|||||||
v: Content => v.into(),
|
v: Content => v.into(),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TableCell {
|
impl Default for Packed<TableCell> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new(Content::default())
|
Packed::new(TableCell::new(Content::default()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResolvableCell for TableCell {
|
impl ResolvableCell for Packed<TableCell> {
|
||||||
fn resolve_cell(
|
fn resolve_cell(
|
||||||
mut self,
|
mut self,
|
||||||
_: usize,
|
x: usize,
|
||||||
_: usize,
|
y: usize,
|
||||||
fill: &Option<Paint>,
|
fill: &Option<Paint>,
|
||||||
align: Smart<Alignment>,
|
align: Smart<Alignment>,
|
||||||
inset: Sides<Rel<Length>>,
|
inset: Sides<Rel<Length>>,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> Cell {
|
) -> Cell {
|
||||||
let fill = self.fill(styles).unwrap_or_else(|| fill.clone());
|
let cell = &mut *self;
|
||||||
self.push_fill(Smart::Custom(fill.clone()));
|
let fill = cell.fill(styles).unwrap_or_else(|| fill.clone());
|
||||||
self.push_align(match align {
|
cell.push_x(Smart::Custom(x));
|
||||||
|
cell.push_y(Smart::Custom(y));
|
||||||
|
cell.push_fill(Smart::Custom(fill.clone()));
|
||||||
|
cell.push_align(match align {
|
||||||
Smart::Custom(align) => {
|
Smart::Custom(align) => {
|
||||||
Smart::Custom(self.align(styles).map_or(align, |inner| inner.fold(align)))
|
Smart::Custom(cell.align(styles).map_or(align, |inner| inner.fold(align)))
|
||||||
}
|
}
|
||||||
// Don't fold if the table is using outer alignment. Use the
|
// Don't fold if the table is using outer alignment. Use the
|
||||||
// cell's alignment instead (which, in the end, will fold with
|
// cell's alignment instead (which, in the end, will fold with
|
||||||
// the outer alignment when it is effectively displayed).
|
// the outer alignment when it is effectively displayed).
|
||||||
Smart::Auto => self.align(styles),
|
Smart::Auto => cell.align(styles),
|
||||||
});
|
});
|
||||||
self.push_inset(Smart::Custom(
|
cell.push_inset(Smart::Custom(
|
||||||
self.inset(styles).map_or(inset, |inner| inner.fold(inset)).map(Some),
|
cell.inset(styles).map_or(inset, |inner| inner.fold(inset)).map(Some),
|
||||||
));
|
));
|
||||||
|
|
||||||
Cell { body: self.pack(), fill }
|
Cell { body: self.pack(), fill }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn x(&self, styles: StyleChain) -> Smart<usize> {
|
||||||
|
(**self).x(styles)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn y(&self, styles: StyleChain) -> Smart<usize> {
|
||||||
|
(**self).y(styles)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn span(&self) -> Span {
|
||||||
|
Packed::span(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<TableCell> {
|
impl Show for Packed<TableCell> {
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 44 KiB |
BIN
tests/ref/layout/grid-positioning.png
Normal file
BIN
tests/ref/layout/grid-positioning.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 54 KiB |
Binary file not shown.
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 48 KiB |
@ -105,3 +105,25 @@
|
|||||||
[Sweet], [Italics]
|
[Sweet], [Italics]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
// Style based on position
|
||||||
|
#{
|
||||||
|
show grid.cell: it => {
|
||||||
|
if it.y == 0 {
|
||||||
|
strong(it)
|
||||||
|
} else if it.x == 1 {
|
||||||
|
emph(it)
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
grid(
|
||||||
|
columns: 3,
|
||||||
|
gutter: 3pt,
|
||||||
|
[Name], [Age], [Info],
|
||||||
|
[John], [52], [Nice],
|
||||||
|
[Mary], [50], [Cool],
|
||||||
|
[Jake], [49], [Epic]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
223
tests/typ/layout/grid-positioning.typ
Normal file
223
tests/typ/layout/grid-positioning.typ
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
// Test cell positioning in grids.
|
||||||
|
|
||||||
|
---
|
||||||
|
#{
|
||||||
|
show grid.cell: it => (it.x, it.y)
|
||||||
|
grid(
|
||||||
|
columns: 2,
|
||||||
|
inset: 5pt,
|
||||||
|
fill: aqua,
|
||||||
|
gutter: 3pt,
|
||||||
|
[Hello], [World],
|
||||||
|
[Sweet], [Home]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
#{
|
||||||
|
show table.cell: it => pad(rest: it.inset)[#(it.x, it.y)]
|
||||||
|
table(
|
||||||
|
columns: 2,
|
||||||
|
gutter: 3pt,
|
||||||
|
[Hello], [World],
|
||||||
|
[Sweet], [Home]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
// Positioning cells in a different order than they appear
|
||||||
|
#grid(
|
||||||
|
columns: 2,
|
||||||
|
[A], [B],
|
||||||
|
grid.cell(x: 1, y: 2)[C], grid.cell(x: 0, y: 2)[D],
|
||||||
|
grid.cell(x: 1, y: 1)[E], grid.cell(x: 0, y: 1)[F],
|
||||||
|
)
|
||||||
|
|
||||||
|
---
|
||||||
|
// Creating more rows by positioning out of bounds
|
||||||
|
#grid(
|
||||||
|
columns: 3,
|
||||||
|
rows: 1.5em,
|
||||||
|
inset: 5pt,
|
||||||
|
fill: (x, y) => if (x, y) == (0, 0) { blue } else if (x, y) == (2, 3) { red } else { green },
|
||||||
|
[A],
|
||||||
|
grid.cell(x: 2, y: 3)[B]
|
||||||
|
)
|
||||||
|
|
||||||
|
#table(
|
||||||
|
columns: (3em, 1em, 3em),
|
||||||
|
rows: 1.5em,
|
||||||
|
inset: (top: 0pt, bottom: 0pt, rest: 5pt),
|
||||||
|
fill: (x, y) => if (x, y) == (0, 0) { blue } else if (x, y) == (2, 3) { red } else { green },
|
||||||
|
align: (x, y) => (left, center, right).at(x),
|
||||||
|
[A],
|
||||||
|
table.cell(x: 2, y: 3)[B]
|
||||||
|
)
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 3:3-3:42 attempted to place a second cell at column 0, row 0
|
||||||
|
// Hint: 3:3-3:42 try specifying your cells in a different order
|
||||||
|
#grid(
|
||||||
|
[A],
|
||||||
|
grid.cell(x: 0, y: 0)[This shall error]
|
||||||
|
)
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 3:3-3:43 attempted to place a second cell at column 0, row 0
|
||||||
|
// Hint: 3:3-3:43 try specifying your cells in a different order
|
||||||
|
#table(
|
||||||
|
[A],
|
||||||
|
table.cell(x: 0, y: 0)[This shall error]
|
||||||
|
)
|
||||||
|
|
||||||
|
---
|
||||||
|
// Automatic position cell skips custom position cell
|
||||||
|
#grid(
|
||||||
|
grid.cell(x: 0, y: 0)[This shall not error],
|
||||||
|
[A]
|
||||||
|
)
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 4:3-4:36 cell could not be placed at invalid column 2
|
||||||
|
#grid(
|
||||||
|
columns: 2,
|
||||||
|
[A],
|
||||||
|
grid.cell(x: 2)[This shall error]
|
||||||
|
)
|
||||||
|
|
||||||
|
---
|
||||||
|
// Partial positioning
|
||||||
|
#grid(
|
||||||
|
columns: 3,
|
||||||
|
rows: 1.5em,
|
||||||
|
inset: 5pt,
|
||||||
|
fill: aqua,
|
||||||
|
[A], grid.cell(y: 1, fill: green)[B], [C], grid.cell(x: auto, y: 1, fill: green)[D], [E],
|
||||||
|
grid.cell(y: 2, fill: green)[F], grid.cell(x: 0, fill: orange)[G], grid.cell(x: 0, y: auto, fill: orange)[H],
|
||||||
|
grid.cell(x: 1, fill: orange)[I]
|
||||||
|
)
|
||||||
|
|
||||||
|
#table(
|
||||||
|
columns: 3,
|
||||||
|
rows: 1.5em,
|
||||||
|
inset: 5pt,
|
||||||
|
fill: aqua,
|
||||||
|
[A], table.cell(y: 1, fill: green)[B], [C], table.cell(x: auto, y: 1, fill: green)[D], [E],
|
||||||
|
table.cell(y: 2, fill: green)[F], table.cell(x: 0, fill: orange)[G], table.cell(x: 0, y: auto, fill: orange)[H],
|
||||||
|
table.cell(x: 1, fill: orange)[I]
|
||||||
|
)
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 4:3-4:21 cell could not be placed in row 0 because it was full
|
||||||
|
// Hint: 4:3-4:21 try specifying your cells in a different order
|
||||||
|
#grid(
|
||||||
|
columns: 2,
|
||||||
|
[A], [B],
|
||||||
|
grid.cell(y: 0)[C]
|
||||||
|
)
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 4:3-4:22 cell could not be placed in row 0 because it was full
|
||||||
|
// Hint: 4:3-4:22 try specifying your cells in a different order
|
||||||
|
#table(
|
||||||
|
columns: 2,
|
||||||
|
[A], [B],
|
||||||
|
table.cell(y: 0)[C]
|
||||||
|
)
|
||||||
|
|
||||||
|
---
|
||||||
|
// Doc example 1
|
||||||
|
#set page(width: auto)
|
||||||
|
#show grid.cell: it => {
|
||||||
|
if it.y == 0 {
|
||||||
|
set text(white)
|
||||||
|
strong(it)
|
||||||
|
} else {
|
||||||
|
// For the second row and beyond, we will write the day number for each
|
||||||
|
// cell.
|
||||||
|
|
||||||
|
// In general, a cell's index is given by cell.x + columns * cell.y.
|
||||||
|
// Days start in the second grid row, so we subtract 1 row.
|
||||||
|
// But the first day is day 1, not day 0, so we add 1.
|
||||||
|
let day = it.x + 7 * (it.y - 1) + 1
|
||||||
|
if day <= 31 {
|
||||||
|
// Place the day's number at the top left of the cell.
|
||||||
|
// Only if the day is valid for this month (not 32 or higher).
|
||||||
|
place(top + left, dx: 2pt, dy: 2pt, text(8pt, red.darken(40%))[#day])
|
||||||
|
}
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#grid(
|
||||||
|
fill: (x, y) => if y == 0 { gray.darken(50%) },
|
||||||
|
columns: (30pt,) * 7,
|
||||||
|
rows: (auto, 30pt),
|
||||||
|
// Events will be written at the bottom of each day square.
|
||||||
|
align: bottom,
|
||||||
|
inset: 5pt,
|
||||||
|
stroke: (thickness: 0.5pt, dash: "densely-dotted"),
|
||||||
|
|
||||||
|
[Sun], [Mon], [Tue], [Wed], [Thu], [Fri], [Sat],
|
||||||
|
|
||||||
|
// This event will occur on the first Friday (sixth column).
|
||||||
|
grid.cell(x: 5, fill: yellow.darken(10%))[Call],
|
||||||
|
|
||||||
|
// This event will occur every Monday (second column).
|
||||||
|
// We have to repeat it 5 times so it occurs every week.
|
||||||
|
..(grid.cell(x: 1, fill: red.lighten(50%))[Meet],) * 5,
|
||||||
|
|
||||||
|
// This event will occur at day 19.
|
||||||
|
grid.cell(x: 4, y: 3, fill: orange.lighten(25%))[Talk],
|
||||||
|
|
||||||
|
// These events will occur at the second week, where available.
|
||||||
|
grid.cell(y: 2, fill: aqua)[Chat],
|
||||||
|
grid.cell(y: 2, fill: aqua)[Walk],
|
||||||
|
)
|
||||||
|
|
||||||
|
---
|
||||||
|
// Doc example 2
|
||||||
|
#set page(width: auto)
|
||||||
|
#show table.cell: it => {
|
||||||
|
if it.x == 0 or it.y == 0 {
|
||||||
|
set text(white)
|
||||||
|
strong(it)
|
||||||
|
} else if it.body == [] {
|
||||||
|
// Replace empty cells with 'N/A'
|
||||||
|
pad(rest: it.inset)[_N/A_]
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#table(
|
||||||
|
fill: (x, y) => if x == 0 or y == 0 { gray.darken(50%) },
|
||||||
|
columns: 4,
|
||||||
|
[], [Exam 1], [Exam 2], [Exam 3],
|
||||||
|
..([John], [Mary], [Jake], [Robert]).map(table.cell.with(x: 0)),
|
||||||
|
|
||||||
|
// Mary got grade A on Exam 3.
|
||||||
|
table.cell(x: 3, y: 2, fill: green)[A],
|
||||||
|
|
||||||
|
// Everyone got grade A on Exam 2.
|
||||||
|
..(table.cell(x: 2, fill: green)[A],) * 4,
|
||||||
|
|
||||||
|
// Robert got grade B on other exams.
|
||||||
|
..(table.cell(y: 4, fill: aqua)[B],) * 2,
|
||||||
|
)
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 5:3-5:39 cell position too large
|
||||||
|
#grid(
|
||||||
|
columns: 3,
|
||||||
|
rows: 2em,
|
||||||
|
fill: (x, y) => if calc.odd(x + y) { red.lighten(50%) } else { green },
|
||||||
|
grid.cell(y: 6148914691236517206)[a],
|
||||||
|
)
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 5:3-5:46 cell position too large
|
||||||
|
#table(
|
||||||
|
columns: 3,
|
||||||
|
rows: 2em,
|
||||||
|
fill: (x, y) => if calc.odd(x + y) { red.lighten(50%) } else { green },
|
||||||
|
table.cell(x: 2, y: 6148914691236517206)[a],
|
||||||
|
)
|
@ -100,3 +100,25 @@
|
|||||||
[John], [Dog]
|
[John], [Dog]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
// Style based on position
|
||||||
|
#{
|
||||||
|
show table.cell: it => {
|
||||||
|
if it.y == 0 {
|
||||||
|
strong(it)
|
||||||
|
} else if it.x == 1 {
|
||||||
|
emph(it)
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
table(
|
||||||
|
columns: 3,
|
||||||
|
gutter: 3pt,
|
||||||
|
[Name], [Age], [Info],
|
||||||
|
[John], [52], [Nice],
|
||||||
|
[Mary], [50], [Cool],
|
||||||
|
[Jake], [49], [Epic]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user