Spruce up table docs (#3593)

This commit is contained in:
Martin Haug 2024-03-09 17:42:11 +01:00 committed by GitHub
parent 639a8d0dc0
commit 15ac6c3166
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 412 additions and 219 deletions

View File

@ -36,9 +36,19 @@ use crate::visualize::{Paint, Stroke};
/// There are multiple sizing modes for columns and rows that can be used to /// There are multiple sizing modes for columns and rows that can be used to
/// create complex layouts. /// create complex layouts.
/// ///
/// The sizing of the grid is determined by the track sizes specified in the /// While the grid and table elements work very similarly, they are intended for
/// arguments. Because each of the sizing parameters accepts the same values, we /// different use cases and carry different semantics. The grid element is
/// will explain them just once, here. Each sizing argument accepts an array of /// intended for presentational and layout purposes, while the
/// [`{table}`]($table) element is intended for, in broad terms, presenting
/// multiple related data points. In the future, Typst will annotate its output
/// such that screenreaders will annouce content in `table` as tabular while a
/// grid's content will be announced no different than multiple content blocks
/// in the document flow. Set and show rules on one of these elements do not
/// affect the other.
///
/// A grid's sizing 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: /// individual track sizes. A track size is either:
/// ///
/// - `{auto}`: The track will be sized to fit its contents. It will be at most /// - `{auto}`: The track will be sized to fit its contents. It will be at most
@ -60,22 +70,10 @@ use crate::visualize::{Paint, Stroke};
/// instead of an array. For example, `columns:` `{3}` is equivalent to /// instead of an array. For example, `columns:` `{3}` is equivalent to
/// `columns:` `{(auto, auto, auto)}`. /// `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 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. It also
/// shows how you can use [`grid.cell`]($grid.cell) in to make an individual
/// cell span two grid tracks.
/// ///
/// ```example /// ```example
/// // We use `rect` to emphasize the /// // We use `rect` to emphasize the
@ -94,8 +92,10 @@ use crate::visualize::{Paint, Stroke};
/// rect[1/3 of the remains], /// rect[1/3 of the remains],
/// rect[2/3 of the remains], /// rect[2/3 of the remains],
/// rect(height: 100%)[Fixed height], /// rect(height: 100%)[Fixed height],
/// image("tiger.jpg", height: 100%), /// grid.cell(
/// image("tiger.jpg", height: 100%), /// colspan: 2,
/// image("tiger.jpg", width: 100%),
/// ),
/// ) /// )
/// ``` /// ```
/// ///
@ -110,60 +110,42 @@ use crate::visualize::{Paint, Stroke};
/// ) /// )
/// ``` /// ```
/// ///
/// Additionally, you can use [`grid.cell`]($grid.cell) in various ways to /// # Styling the grid
/// not only style each cell based on its position and other fields, but also /// The grid's appearance can be customized through different parameters. These
/// to determine the cell's preferential position in the table. /// are the most important ones:
/// ///
/// ```example /// - [`fill`]($grid.fill) to give all cells a background
/// #set page(width: auto) /// - [`align`]($grid.align) to change how cells are aligned
/// #show grid.cell: it => { /// - [`inset`]($grid.inset) to optionally add internal padding to each cell
/// if it.y == 0 { /// - [`stroke`]($grid.stroke) to optionally enable grid lines with a certain
/// // The first row's text must be white and bold. /// stroke
/// 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. /// If you need to override one of the above options for a single cell, you can
/// // Days start in the second grid row, so we subtract 1 row. /// use the [`grid.cell`]($grid.cell) element. Likewise, you can override
/// // But the first day is day 1, not day 0, so we add 1. /// individual grid lines with the [`grid.hline`]($grid.hline) and
/// let day = it.x + 7 * (it.y - 1) + 1 /// [`grid.vline`]($grid.vline) elements.
/// 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( /// Alternatively, if you need the appearance options to depend on a cell's
/// fill: (x, y) => if y == 0 { gray.darken(50%) }, /// position (column and row), you may specify a function to `fill` or `align`
/// columns: (30pt,) * 7, /// of the form `(column, row) => value`. You may also use a show rule on
/// rows: (auto, 30pt), /// [`grid.cell`]($grid.cell) - see that element's examples or the examples
/// // Events will be written at the bottom of each day square. /// below for more information.
/// align: bottom,
/// inset: 5pt,
/// stroke: (thickness: 0.5pt, dash: "densely-dotted"),
/// ///
/// [Sun], [Mon], [Tue], [Wed], [Thu], [Fri], [Sat], /// Locating most of your styling in set and show rules is recommended, as it
/// keeps the grid's or table's actual usages clean and easy to read. It also
/// allows you to easily change the grid's appearance in one place.
/// ///
/// // This event will occur on the first Friday (sixth column). /// ## Stroke styling precedence
/// grid.cell(x: 5, fill: yellow.darken(10%))[Call], /// There are three ways to set the stroke of a grid cell: through
/// [`{grid.cell}`'s `stroke` field]($grid.cell.stroke), by using
/// [`{grid.hline}`]($grid.hline) and [`{grid.vline}`]($grid.vline), or by
/// setting the [`{grid}`'s `stroke` field]($grid.stroke). When multiple of
/// these settings are present and conflict, the `hline` and `vline` settings
/// take the highest precedence, followed by the `cell` settings, and finally
/// the `grid` settings.
/// ///
/// // This event will occur every Monday (second column). /// Furthermore, strokes of a repeated grid header or footer will take
/// // We have to repeat it 5 times so it occurs every week. /// precedence over regular cell strokes.
/// ..(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, LayoutMultiple)] #[elem(scope, LayoutMultiple)]
pub struct GridElem { pub struct GridElem {
/// The column sizes. /// The column sizes.
@ -209,13 +191,16 @@ pub struct GridElem {
/// ///
/// ```example /// ```example
/// #grid( /// #grid(
/// fill: (col, row) => if calc.even(col + row) { luma(240) } else { white }, /// fill: (col, row) =>
/// if calc.even(col + row) { luma(230) }
/// else { white },
/// align: center + horizon, /// align: center + horizon,
/// columns: 4, /// columns: 4,
/// inset: 2pt,
/// [X], [O], [X], [O], /// [X], [O], [X], [O],
/// [O], [X], [O], [X], /// [O], [X], [O], [X],
/// [X], [O], [X], [O], /// [X], [O], [X], [O],
/// [O], [X], [O], [X] /// [O], [X], [O], [X],
/// ) /// )
/// ``` /// ```
#[borrowed] #[borrowed]
@ -225,17 +210,11 @@ pub struct GridElem {
/// ///
/// This can either be a single alignment, an array of alignments /// This can either be a single alignment, an array of alignments
/// (corresponding to each column) or a function that returns an alignment. /// (corresponding to each column) or a function that returns an alignment.
/// The function is passed the cells' column and row index, starting at zero. /// The function is passed the cells' column and row index, starting at
/// If set to `{auto}`, the outer alignment is used. /// zero. If set to `{auto}`, the outer alignment is used.
/// ///
/// ```example /// You can find an example for this argument at the
/// #grid( /// [`table.align`]($table.align) parameter.
/// columns: 3,
/// align: (x, y) => (left, center, right).at(x),
/// [Hello], [Hello], [Hello],
/// [A], [B], [C],
/// )
/// ```
#[borrowed] #[borrowed]
pub align: Celled<Smart<Alignment>>, pub align: Celled<Smart<Alignment>>,
@ -249,31 +228,81 @@ pub struct GridElem {
/// multiple specific cells, consider specifying one or more of /// multiple specific cells, consider specifying one or more of
/// [`grid.hline`]($grid.hline) and [`grid.vline`]($grid.vline) alongside /// [`grid.hline`]($grid.hline) and [`grid.vline`]($grid.vline) alongside
/// your grid cells. /// your grid cells.
///
/// ```example
/// #set page(height: 13em, width: 26em)
///
/// #let cv(..jobs) = grid(
/// columns: 2,
/// inset: 5pt,
/// stroke: (x, y) => if x == 0 and y > 0 {
/// (right: (
/// paint: luma(180),
/// thickness: 1.5pt,
/// dash: "dotted"
/// ))
/// },
/// grid.header(grid.cell(colspan: 2)[
/// *Professional Experience*
/// #box(width: 1fr, line(length: 100%, stroke: luma(180)))
/// ]),
/// ..{
/// let last = none
/// for job in jobs.pos() {
/// (
/// if job.year != last [*#job.year*],
/// [
/// *#job.company* - #job.role _(#job.timeframe)_ \
/// #job.details
/// ]
/// )
/// last = job.year
/// }
/// }
/// )
///
/// #cv(
/// (
/// year: 2012,
/// company: [Pear Seed & Co.],
/// role: [Lead Engineer],
/// timeframe: [Jul - Dec],
/// details: [
/// - Raised engineers from 3x to 10x
/// - Did a great job
/// ],
/// ),
/// (
/// year: 2012,
/// company: [Mega Corp.],
/// role: [VP of Sales],
/// timeframe: [Mar - Jun],
/// details: [- Closed tons of customers],
/// ),
/// (
/// year: 2013,
/// company: [Tiny Co.],
/// role: [CEO],
/// timeframe: [Jan - Dec],
/// details: [- Delivered 4x more shareholder value],
/// ),
/// (
/// year: 2014,
/// company: [Glorbocorp Ltd],
/// role: [CTO],
/// timeframe: [Jan - Mar],
/// details: [- Drove containerization forward],
/// ),
/// )
/// ```
#[resolve] #[resolve]
#[fold] #[fold]
pub stroke: Celled<Sides<Option<Option<Arc<Stroke>>>>>, pub stroke: Celled<Sides<Option<Option<Arc<Stroke>>>>>,
/// How much to pad the cells' content. /// How much to pad the cells' content.
/// ///
/// ```example /// You can find an example for this argument at the
/// #grid( /// [`table.inset`]($table.inset) parameter.
/// 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] #[fold]
pub inset: Celled<Sides<Option<Rel<Length>>>>, pub inset: Celled<Sides<Option<Rel<Length>>>>,
@ -508,6 +537,10 @@ impl TryFrom<Content> for GridItem {
} }
/// A repeatable grid header. /// A repeatable grid header.
///
/// If `repeat` is set to `true`, the header will be repeated across pages. For
/// an example, refer to the [`table.header`]($table.header) element and the
/// [`grid.stroke`]($grid.stroke) parameter.
#[elem(name = "header", title = "Grid Header")] #[elem(name = "header", title = "Grid Header")]
pub struct GridHeader { pub struct GridHeader {
/// Whether this header should be repeated across pages. /// Whether this header should be repeated across pages.
@ -520,6 +553,11 @@ pub struct GridHeader {
} }
/// A repeatable grid footer. /// A repeatable grid footer.
///
/// Just like the [`grid.header`]($grid.header) element, the footer can repeat
/// itself on every page of the table.
///
/// No other grid cells may be placed after the footer.
#[elem(name = "footer", title = "Grid Footer")] #[elem(name = "footer", title = "Grid Footer")]
pub struct GridFooter { pub struct GridFooter {
/// Whether this footer should be repeated across pages. /// Whether this footer should be repeated across pages.
@ -533,9 +571,12 @@ pub struct GridFooter {
/// A horizontal line in the grid. /// A horizontal line in the grid.
/// ///
/// Overrides any per-cell stroke, including stroke specified through the /// Overrides any per-cell stroke, including stroke specified through the grid's
/// grid's `stroke` field. Can cross spacing between cells created through /// `stroke` field. Can cross spacing between cells created through the grid's
/// the grid's `column-gutter` option. /// `column-gutter` option.
///
/// An example for this function can be found at the
/// [`table.hline`]($table.hline) element.
#[elem(name = "hline", title = "Grid Horizontal Line")] #[elem(name = "hline", title = "Grid Horizontal Line")]
pub struct GridHLine { pub struct GridHLine {
/// The row above which the horizontal line is placed (zero-indexed). /// The row above which the horizontal line is placed (zero-indexed).
@ -644,53 +685,46 @@ pub struct GridVLine {
pub position: OuterHAlignment, pub position: OuterHAlignment,
} }
/// A cell in the grid. Use this to either override grid properties for a /// A cell in the grid. You can use this function in the argument list of a grid
/// particular cell, or in show rules to apply certain styles to multiple cells /// to override grid style properties for an individual cell or manually
/// at once. /// positioning it within the grid. You can also use this function 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 /// For example, you can override the position and stroke for a single cell:
/// cell:
/// ///
/// ```example /// ```example
/// >>> #set page(width: auto)
/// >>> #set text(15pt, font: "Noto Sans Symbols 2", bottom-edge: -.2em)
/// <<< #set text(15pt, font: "Noto Sans Symbols 2")
/// #show regex("[♚-♟︎]"): set text(fill: rgb("21212A"))
/// #show regex("[♔-♙]"): set text(fill: rgb("111015"))
///
/// #grid( /// #grid(
/// columns: 2, /// fill: (x, y) => rgb(
/// fill: red, /// if calc.odd(x + y) { "EFF0F3" }
/// align: left, /// else { "7F8396" }
/// inset: 5pt, /// ),
/// [ABC], [ABC], /// columns: (1em,) * 8,
/// grid.cell(fill: blue)[C], [D], /// rows: 1em,
/// grid.cell(align: center)[E], [F], /// align: center + horizon,
/// [G], grid.cell(inset: 0pt)[H] ///
/// [♜], [♞], [♝], [♛], [♚], [♝], [♞], [♜],
/// [♟], [♟], [♟], [♟], [], [♟], [♟], [♟],
/// grid.cell(
/// x: 4, y: 3,
/// stroke: blue.transparentize(60%)
/// )[♟],
///
/// ..(grid.cell(y: 6)[♙],) * 8,
/// ..([♖], [♘], [♗], [♕], [♔], [♗], [♘], [♖])
/// .map(grid.cell.with(y: 7)),
/// ) /// )
/// ``` /// ```
/// ///
/// You may also apply a show rule on `grid.cell` to style all cells at once, /// 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: /// which allows you, for example, to apply styles based on a cell's position.
/// /// Refer to the examples of the [`table.cell`]($table.cell) element to learn
/// ```example /// more about this.
/// #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.
@ -710,14 +744,21 @@ pub struct GridCell {
/// position before cells with automatic positions). /// position before cells with automatic positions).
/// ///
/// ```example /// ```example
/// #let circ(c) = circle(
/// fill: c, width: 5mm
/// )
///
/// #grid( /// #grid(
/// columns: 4, /// columns: 4,
/// rows: 2.5em, /// rows: 7mm,
/// fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) }, /// stroke: .5pt + blue,
/// align: center + horizon, /// align: center + horizon,
/// inset: 3pt, /// inset: 1mm,
/// grid.cell(x: 2, y: 2)[3], ///
/// [1], grid.cell(x: 3)[4], [2], /// grid.cell(x: 2, y: 2, circ(aqua)),
/// circ(yellow),
/// grid.cell(x: 3, circ(green)),
/// circ(black),
/// ) /// )
/// ``` /// ```
pub x: Smart<usize>, pub x: Smart<usize>,
@ -732,11 +773,21 @@ pub struct GridCell {
/// columns in the chosen row are already occupied, an error is raised. /// columns in the chosen row are already occupied, an error is raised.
/// ///
/// ```example /// ```example
/// #let tri(c) = polygon.regular(
/// fill: c,
/// size: 5mm,
/// vertices: 3,
/// )
///
/// #grid( /// #grid(
/// columns: 2, /// columns: 2,
/// fill: (x, y) => if calc.odd(x + y) { gray.lighten(40%) }, /// stroke: blue,
/// inset: 1pt, /// inset: 1mm,
/// [A], grid.cell(y: 1)[B], grid.cell(y: 1)[C], grid.cell(y: 2)[D] ///
/// tri(black),
/// grid.cell(y: 1, tri(teal)),
/// grid.cell(y: 1, tri(red)),
/// grid.cell(y: 2, tri(orange))
/// ) /// )
/// ``` /// ```
pub y: Smart<usize>, pub y: Smart<usize>,
@ -749,16 +800,16 @@ pub struct GridCell {
#[default(NonZeroUsize::ONE)] #[default(NonZeroUsize::ONE)]
pub rowspan: NonZeroUsize, pub rowspan: NonZeroUsize,
/// The cell's fill override. /// The cell's [fill]($grid.fill) override.
pub fill: Smart<Option<Paint>>, pub fill: Smart<Option<Paint>>,
/// The cell's alignment override. /// The cell's [alignment]($grid.align) override.
pub align: Smart<Alignment>, pub align: Smart<Alignment>,
/// The cell's inset override. /// The cell's [inset]($grid.inset) override.
pub inset: Smart<Sides<Option<Rel<Length>>>>, pub inset: Smart<Sides<Option<Rel<Length>>>>,
/// The cell's stroke override. /// The cell's [stroke]($grid.stroke) override.
#[resolve] #[resolve]
#[fold] #[fold]
pub stroke: Sides<Option<Option<Arc<Stroke>>>>, pub stroke: Sides<Option<Option<Arc<Stroke>>>>,

View File

@ -25,14 +25,26 @@ use crate::visualize::{Paint, Stroke};
/// Tables are used to arrange content in cells. Cells can contain arbitrary /// Tables are used to arrange content in cells. Cells can contain arbitrary
/// content, including multiple paragraphs and are specified in row-major order. /// content, including multiple paragraphs and are specified in row-major order.
/// Because tables are just grids with different defaults for some cell /// Because tables are just grids with different defaults for some cell
/// properties (notably `stroke` and `inset`), refer to the /// properties (notably `stroke` and `inset`), refer to the [grid
/// [grid documentation]($grid) for more information on how to size the table /// documentation]($grid) for more information on how to size the table tracks
/// tracks and specify the cell appearance properties. /// and specify the cell appearance properties.
/// ///
/// Note that, to override a particular cell's properties or apply show rules /// If you are unsure whether you should be using a table or a grid, consider
/// on table cells, you can use the [`table.cell`]($table.cell) element (but /// whether the content you are arranging semantically belongs together as a set
/// not `grid.cell`, which is exclusive to grids). See its documentation for /// of related data points or similar or whether you are just want to enhance
/// more information. /// your presentation by arranging unrelated content in a grid. In the former
/// case, a table is the right choice, while in the latter case, a grid is more
/// appropriate. Furthermore, Typst will annotate its output in the future such
/// that screenreaders will annouce content in `table` as tabular while a grid's
/// content will be announced no different than multiple content blocks in the
/// document flow.
///
/// 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. See its
/// documentation for more information.
///
/// Although the `table` and the `grid` share most properties, set and show
/// rules on one of them do not affect the other.
/// ///
/// To give a table a caption and make it [referenceable]($ref), put it into a /// To give a table a caption and make it [referenceable]($ref), put it into a
/// [figure]. /// [figure].
@ -45,7 +57,9 @@ use crate::visualize::{Paint, Stroke};
/// columns: (1fr, auto, auto), /// columns: (1fr, auto, auto),
/// inset: 10pt, /// inset: 10pt,
/// align: horizon, /// align: horizon,
/// [], [*Area*], [*Parameters*], /// table.header(
/// [], [*Area*], [*Parameters*],
/// ),
/// image("cylinder.svg"), /// image("cylinder.svg"),
/// $ pi h (D^2 - d^2) / 4 $, /// $ pi h (D^2 - d^2) / 4 $,
/// [ /// [
@ -63,33 +77,44 @@ use crate::visualize::{Paint, Stroke};
/// the appearance and the position of each cell. /// the appearance and the position of each cell.
/// ///
/// ```example /// ```example
/// #set page(width: auto) /// >>> #set page(width: auto)
/// >>> #set text(font: "IBM Plex Sans")
/// >>> #let gray = rgb("#565565")
/// >>>
/// #set table(
/// stroke: none,
/// gutter: 0.2em,
/// fill: (x, y) =>
/// if x == 0 or y == 0 { gray },
/// inset: (right: 1.5em),
/// )
///
/// #show table.cell: it => { /// #show table.cell: it => {
/// if it.x == 0 or it.y == 0 { /// if it.x == 0 or it.y == 0 {
/// set text(white) /// set text(white)
/// strong(it) /// strong(it)
/// } else if it.body == [] { /// } else if it.body == [] {
/// // Replace empty cells with 'N/A' /// // Replace empty cells with 'N/A'
/// pad(rest: it.inset)[_N/A_] /// pad(..it.inset)[_N/A_]
/// } else { /// } else {
/// it /// it
/// } /// }
/// } /// }
/// ///
/// #let a = table.cell(
/// fill: green.lighten(60%),
/// )[A]
/// #let b = table.cell(
/// fill: aqua.lighten(60%),
/// )[B]
///
/// #table( /// #table(
/// fill: (x, y) => if x == 0 or y == 0 { gray.darken(50%) },
/// columns: 4, /// columns: 4,
/// [], [Exam 1], [Exam 2], [Exam 3], /// [], [Exam 1], [Exam 2], [Exam 3],
/// ..([John], [Mary], [Jake], [Robert]).map(table.cell.with(x: 0)),
/// ///
/// // Mary got grade A on Exam 3. /// [John], [], a, [],
/// table.cell(x: 3, y: 2, fill: green)[A], /// [Mary], [], a, a,
/// /// [Robert], b, a, b,
/// // 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, LayoutMultiple, LocalName, Figurable)] #[elem(scope, LayoutMultiple, LocalName, Figurable)]
@ -157,7 +182,7 @@ pub struct TableElem {
/// ```example /// ```example
/// #table( /// #table(
/// columns: 3, /// columns: 3,
/// align: (x, y) => (left, center, right).at(x), /// align: (left, center, right),
/// [Hello], [Hello], [Hello], /// [Hello], [Hello], [Hello],
/// [A], [B], [C], /// [A], [B], [C],
/// ) /// )
@ -172,10 +197,11 @@ pub struct TableElem {
/// If it is necessary to place lines which can cross spacing between cells /// If it is necessary to place lines which can cross spacing between cells
/// produced by the `gutter` option, or to override the stroke between /// produced by the `gutter` option, or to override the stroke between
/// multiple specific cells, consider specifying one or more of /// multiple specific cells, consider specifying one or more of
/// [`table.hline`]($table.hline) and [`table.vline`]($table.vline) alongside /// [`table.hline`]($table.hline) and [`table.vline`]($table.vline)
/// your table cells. /// alongside your table cells.
/// ///
/// See the [grid documentation]($grid) for more information on stroke. /// See the [grid documentation]($grid.stroke) for more information on
/// strokes.
#[resolve] #[resolve]
#[fold] #[fold]
#[default(Celled::Value(Sides::splat(Some(Some(Arc::new(Stroke::default()))))))] #[default(Celled::Value(Sides::splat(Some(Some(Arc::new(Stroke::default()))))))]
@ -464,6 +490,49 @@ impl TryFrom<Content> for TableItem {
} }
/// A repeatable table header. /// A repeatable table header.
///
/// You should wrap your tables' heading rows in this function even if you do not
/// plan to wrap your table across pages because Typst will use this function to
/// attach accessibility metadata to tables in the future and ensure universal
/// access to your document.
///
/// You can use the `repeat` parameter to control whether your table's header
/// will be repeated across pages.
///
/// ```example
/// #set page(height: 11.5em)
/// #set table(
/// fill: (x, y) =>
/// if x == 0 or y == 0 {
/// gray.lighten(40%)
/// },
/// align: right,
/// )
///
/// #show table.cell.where(x: 0): strong
/// #show table.cell.where(y: 0): strong
///
/// #table(
/// columns: 4,
/// table.header(
/// [], [Blue chip],
/// [Fresh IPO], [Penny st'k],
/// ),
/// table.cell(
/// rowspan: 6,
/// align: horizon,
/// rotate(-90deg, reflow: true)[
/// *USD / day*
/// ],
/// ),
/// [0.20], [104], [5],
/// [3.17], [108], [4],
/// [1.59], [84], [1],
/// [0.26], [98], [15],
/// [0.01], [195], [4],
/// [7.34], [57], [2],
/// )
/// ```
#[elem(name = "header", title = "Table Header")] #[elem(name = "header", title = "Table Header")]
pub struct TableHeader { pub struct TableHeader {
/// Whether this header should be repeated across pages. /// Whether this header should be repeated across pages.
@ -476,6 +545,13 @@ pub struct TableHeader {
} }
/// A repeatable table footer. /// A repeatable table footer.
///
/// Just like the [`table.header`]($table.header) element, the footer can repeat
/// itself on every page of the table. This is useful for improving legibility
/// by adding the column labels in both the header and footer of a large table,
/// totals, or other information that should be visible on every page.
///
/// No other table cells may be placed after the footer.
#[elem(name = "footer", title = "Table Footer")] #[elem(name = "footer", title = "Table Footer")]
pub struct TableFooter { pub struct TableFooter {
/// Whether this footer should be repeated across pages. /// Whether this footer should be repeated across pages.
@ -487,17 +563,42 @@ pub struct TableFooter {
pub children: Vec<TableItem>, pub children: Vec<TableItem>,
} }
/// A horizontal line in the table. See the docs for /// A horizontal line in the table.
/// [`grid.hline`]($grid.hline) for more information regarding how to use this
/// element's fields.
/// ///
/// Overrides any per-cell stroke, including stroke specified through the /// Overrides any per-cell stroke, including stroke specified through the
/// table's `stroke` field. Can cross spacing between cells created through /// table's `stroke` field. Can cross spacing between cells created through the
/// the table's `column-gutter` option. /// table's [`column-gutter`]($table.column-gutter) option.
///
/// Use this function instead of the table's `stroke` field if you want to
/// manually place a horizontal line at a specific position in a single table.
/// Consider using [table's `stroke`]($table.stroke) field or [`table.cell`'s
/// `stroke`]($table.cell.stroke) field instead if the line you want to place is
/// part of all your tables' designs.
///
/// ```example
/// #set table.hline(stroke: .6pt)
///
/// #table(
/// stroke: none,
/// columns: (auto, 1fr),
/// [09:00], [Badge pick up],
/// [09:45], [Opening Keynote],
/// [10:30], [Talk: Typst's Future],
/// [11:15], [Session: Good PRs],
/// table.hline(start: 1),
/// [Noon], [_Lunch break_],
/// table.hline(start: 1),
/// [14:00], [Talk: Tracked Layout],
/// [15:00], [Talk: Automations],
/// [16:00], [Workshop: Tables],
/// table.hline(),
/// [19:00], [Day 1 Attendee Mixer],
/// )
/// ```
#[elem(name = "hline", title = "Table Horizontal Line")] #[elem(name = "hline", title = "Table Horizontal Line")]
pub struct TableHLine { pub struct TableHLine {
/// The row above which the horizontal line is placed (zero-indexed). /// The row above which the horizontal line is placed (zero-indexed).
/// Functions identically to the `y` field in [`grid.hline`]($grid.hline). /// Functions identically to the `y` field in [`grid.hline`]($grid.hline.y).
pub y: Smart<usize>, pub y: Smart<usize>,
/// The column at which the horizontal line starts (zero-indexed, inclusive). /// The column at which the horizontal line starts (zero-indexed, inclusive).
@ -531,8 +632,14 @@ pub struct TableHLine {
/// for more information regarding how to use this element's fields. /// for more information regarding how to use this element's fields.
/// ///
/// Overrides any per-cell stroke, including stroke specified through the /// Overrides any per-cell stroke, including stroke specified through the
/// table's `stroke` field. Can cross spacing between cells created through /// table's `stroke` field. Can cross spacing between cells created through the
/// the table's `row-gutter` option. /// table's [`row-gutter`]($table.row-gutter) option.
///
/// Similar to [`table.hline`]($table.hline), use this function if you want to
/// manually place a vertical line at a specific position in a single table and
/// use the [table's `stroke`]($table.stroke) field or [`table.cell`'s
/// `stroke`]($table.cell.stroke) field instead if the line you want to place is
/// part of all your tables' designs.
#[elem(name = "vline", title = "Table Vertical Line")] #[elem(name = "vline", title = "Table Vertical Line")]
pub struct TableVLine { pub struct TableVLine {
/// The column before which the horizontal line is placed (zero-indexed). /// The column before which the horizontal line is placed (zero-indexed).
@ -571,50 +678,85 @@ pub struct TableVLine {
pub position: OuterHAlignment, pub position: OuterHAlignment,
} }
/// A cell in the table. Use this to either override table properties for a /// A cell in the table. Use this to position a cell manually or to apply
/// particular cell, or in show rules to apply certain styles to multiple cells /// styling. To do the latter, you can either use the function to override the
/// at once. /// properties for a particular cell, or use it in show rules to apply certain
/// styles to multiple cells at once.
///
/// Perhaps the most important use-case of `{table.cell}` is to make a cell span
/// multiple columns or rows with the `colspan` and `rowspan` fields.
///
/// ```example
/// >>> #set page(width: auto)
/// >>> #show table.cell.where(y: 0): strong
/// >>> #set table(
/// >>> stroke: (x, y) => if y == 0 {
/// >>> (bottom: 0.7pt + black)
/// >>> },
/// >>> align: (x, y) =>
/// >>> if x > 0 { center }
/// >>> else { left }
/// >>> )
/// #table(
/// columns: 3,
/// table.header(
/// [Substance],
/// [Subcritical °C],
/// [Supercritical °C],
/// ),
/// [Hydrochloric Acid],
/// [12.0], [92.1],
/// [Sodium Myreth Sulfate],
/// [16.6], [104],
/// [Potassium Hydroxide],
/// table.cell(colspan: 2)[24.7],
/// )
/// ```
/// ///
/// For example, you can override the fill, alignment or inset for a single /// For example, you can override the fill, alignment or inset for a single
/// cell: /// cell:
/// ///
/// ```example /// ```example
/// >>> #set page(width: auto)
/// // You can also import those.
/// #import table: cell, header
///
/// #table( /// #table(
/// columns: 2, /// columns: 2,
/// fill: green, /// align: center,
/// align: right, /// header(
/// [*Name*], [*Data*], /// [*Trip progress*],
/// table.cell(fill: blue)[J.], [Organizer], /// [*Itinerary*],
/// table.cell(align: center)[K.], [Leader], /// ),
/// [M.], table.cell(inset: 0pt)[Player] /// cell(
/// align: right,
/// fill: fuchsia.lighten(80%),
/// [🚗],
/// ),
/// [Get in, folks!],
/// [🚗], [Eat curbside hotdog],
/// cell(align: left)[🌴🚗],
/// cell(
/// inset: 0.06em,
/// text(1.62em)[🛖🌅🌊],
/// ),
/// ) /// )
/// ``` /// ```
/// ///
/// You may also apply a show rule on `table.cell` to style all cells at once, /// 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: /// Combined with selectors, this allows you to apply styles based on a cell's
/// position:
/// ///
/// ```example /// ```example
/// #show table.cell: it => { /// #show table.cell.where(x: 0): strong
/// 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( /// #table(
/// columns: 3, /// columns: 3,
/// gutter: 3pt, /// gutter: 3pt,
/// [Name], [Age], [Info], /// [Name], [Age], [Strength],
/// [John], [52], [Nice], /// [Hannes], [36], [Grace],
/// [Mary], [50], [Cool], /// [Irma], [50], [Resourcefulness],
/// [Jake], [49], [Epic] /// [Vikram], [49], [Perseverance],
/// ) /// )
/// ``` /// ```
#[elem(name = "cell", title = "Table Cell", Show)] #[elem(name = "cell", title = "Table Cell", Show)]
@ -631,9 +773,6 @@ pub struct TableCell {
/// Functions identically to the `y` field in [`grid.cell`]($grid.cell). /// Functions identically to the `y` field in [`grid.cell`]($grid.cell).
pub y: Smart<usize>, pub y: Smart<usize>,
/// The cell's fill override.
pub fill: Smart<Option<Paint>>,
/// The amount of columns spanned by this cell. /// The amount of columns spanned by this cell.
#[default(NonZeroUsize::ONE)] #[default(NonZeroUsize::ONE)]
pub colspan: NonZeroUsize, pub colspan: NonZeroUsize,
@ -642,13 +781,16 @@ pub struct TableCell {
#[default(NonZeroUsize::ONE)] #[default(NonZeroUsize::ONE)]
rowspan: NonZeroUsize, rowspan: NonZeroUsize,
/// The cell's alignment override. /// The cell's [fill]($table.fill) override.
pub fill: Smart<Option<Paint>>,
/// The cell's [alignment]($table.align) override.
pub align: Smart<Alignment>, pub align: Smart<Alignment>,
/// The cell's inset override. /// The cell's [inset]($table.inset) override.
pub inset: Smart<Sides<Option<Rel<Length>>>>, pub inset: Smart<Sides<Option<Rel<Length>>>>,
/// The cell's stroke override. /// The cell's [stroke]($table.stroke) override.
#[resolve] #[resolve]
#[fold] #[fold]
pub stroke: Sides<Option<Option<Arc<Stroke>>>>, pub stroke: Sides<Option<Option<Arc<Stroke>>>>,