summaryrefslogtreecommitdiff
path: root/library/src/layout/table.rs
diff options
context:
space:
mode:
Diffstat (limited to 'library/src/layout/table.rs')
-rw-r--r--library/src/layout/table.rs334
1 files changed, 0 insertions, 334 deletions
diff --git a/library/src/layout/table.rs b/library/src/layout/table.rs
deleted file mode 100644
index c2faedba..00000000
--- a/library/src/layout/table.rs
+++ /dev/null
@@ -1,334 +0,0 @@
-use typst::eval::{CastInfo, Reflect};
-
-use crate::layout::{AlignElem, GridLayouter, TrackSizings};
-use crate::meta::{Figurable, LocalName};
-use crate::prelude::*;
-
-/// A table of items.
-///
-/// 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]($func/grid) for more information on how to size the
-/// table tracks.
-///
-/// To give a table a caption and make it [referenceable]($func/ref), put it
-/// into a [figure]($func/figure).
-///
-/// ## Example { #example }
-/// ```example
-/// #table(
-/// columns: (1fr, auto, auto),
-/// inset: 10pt,
-/// align: horizon,
-/// [], [*Area*], [*Parameters*],
-/// image("cylinder.svg"),
-/// $ pi h (D^2 - d^2) / 4 $,
-/// [
-/// $h$: height \
-/// $D$: outer radius \
-/// $d$: inner radius
-/// ],
-/// image("tetrahedron.svg"),
-/// $ sqrt(2) / 12 a^3 $,
-/// [$a$: edge length]
-/// )
-/// ```
-///
-/// Display: Table
-/// Category: layout
-#[element(Layout, LocalName, Figurable)]
-pub struct TableElem {
- /// The column sizes. See the [grid documentation]($func/grid) for more
- /// information on track sizing.
- pub columns: TrackSizings,
-
- /// The row sizes. See the [grid documentation]($func/grid) for more
- /// information on track sizing.
- pub rows: TrackSizings,
-
- /// The gaps between rows & columns. See the [grid
- /// documentation]($func/grid) for more information on gutters.
- #[external]
- pub gutter: TrackSizings,
-
- /// The gaps between columns. Takes precedence over `gutter`. See the [grid
- /// documentation]($func/grid) for more information on gutters.
- #[parse(
- let gutter = args.named("gutter")?;
- args.named("column-gutter")?.or_else(|| gutter.clone())
- )]
- pub column_gutter: TrackSizings,
-
- /// The gaps between rows. Takes precedence over `gutter`. See the [grid
- /// documentation]($func/grid) for more information on gutters.
- #[parse(args.named("row-gutter")?.or_else(|| gutter.clone()))]
- 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 tables.
- ///
- /// ```example
- /// #table(
- /// fill: (col, _) => if calc.odd(col) { luma(240) } else { white },
- /// align: (col, row) =>
- /// if row == 0 { center }
- /// else if col == 0 { left }
- /// else { right },
- /// columns: 4,
- /// [], [*Q1*], [*Q2*], [*Q3*],
- /// [Revenue:], [1000 €], [2000 €], [3000 €],
- /// [Expenses:], [500 €], [1000 €], [1500 €],
- /// [Profit:], [500 €], [1000 €], [1500 €],
- /// )
- /// ```
- 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
- /// #table(
- /// columns: 3,
- /// align: (x, y) => (left, center, right).at(x),
- /// [Hello], [Hello], [Hello],
- /// [A], [B], [C],
- /// )
- /// ```
- pub align: Celled<Smart<Axes<Option<GenAlign>>>>,
-
- /// How to stroke the cells.
- ///
- /// See the [line's documentation]($func/line.stroke) for more details.
- /// Strokes can be disabled by setting this to `{none}`.
- ///
- /// _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]
- #[default(Some(PartialStroke::default()))]
- pub stroke: Option<PartialStroke>,
-
- /// How much to pad the cells' content.
- #[default(Abs::pt(5.0).into())]
- pub inset: Rel<Length>,
-
- /// The contents of the table cells.
- #[variadic]
- pub children: Vec<Content>,
-}
-
-impl Layout for TableElem {
- #[tracing::instrument(name = "TableElem::layout", skip_all)]
- fn layout(
- &self,
- vt: &mut Vt,
- styles: StyleChain,
- regions: Regions,
- ) -> SourceResult<Fragment> {
- let inset = self.inset(styles);
- let align = self.align(styles);
-
- let tracks = Axes::new(self.columns(styles).0, self.rows(styles).0);
- let gutter = Axes::new(self.column_gutter(styles).0, self.row_gutter(styles).0);
- let cols = tracks.x.len().max(1);
- let cells: Vec<_> = self
- .children()
- .into_iter()
- .enumerate()
- .map(|(i, child)| {
- let mut child = child.padded(Sides::splat(inset));
-
- let x = i % cols;
- let y = i / cols;
- if let Smart::Custom(alignment) = align.resolve(vt, x, y)? {
- child = child.styled(AlignElem::set_alignment(alignment));
- }
-
- Ok(child)
- })
- .collect::<SourceResult<_>>()?;
-
- let fill = self.fill(styles);
- let stroke = self.stroke(styles).map(PartialStroke::unwrap_or_default);
-
- // Prepare grid layout by unifying content and gutter tracks.
- let layouter = GridLayouter::new(
- tracks.as_deref(),
- gutter.as_deref(),
- &cells,
- regions,
- styles,
- );
-
- // Measure the columns and layout the grid row-by-row.
- let mut layout = layouter.layout(vt)?;
-
- // Add lines and backgrounds.
- for (frame, rows) in layout.fragment.iter_mut().zip(&layout.rows) {
- if layout.cols.is_empty() || rows.is_empty() {
- continue;
- }
-
- // Render table lines.
- if let Some(stroke) = &stroke {
- let thickness = stroke.thickness;
- let half = thickness / 2.0;
-
- // Render horizontal lines.
- for offset in points(rows.iter().map(|piece| piece.height)) {
- let target = Point::with_x(frame.width() + thickness);
- let hline = Geometry::Line(target).stroked(stroke.clone());
- frame.prepend(
- Point::new(-half, offset),
- FrameItem::Shape(hline, self.span()),
- );
- }
-
- // Render vertical lines.
- for offset in points(layout.cols.iter().copied()) {
- let target = Point::with_y(frame.height() + thickness);
- let vline = Geometry::Line(target).stroked(stroke.clone());
- frame.prepend(
- Point::new(offset, -half),
- FrameItem::Shape(vline, self.span()),
- );
- }
- }
-
- // Render cell backgrounds.
- let mut dx = Abs::zero();
- for (x, &col) in layout.cols.iter().enumerate() {
- let mut dy = Abs::zero();
- for row in rows {
- if let Some(fill) = fill.resolve(vt, x, row.y)? {
- let pos = Point::new(dx, dy);
- let size = Size::new(col, row.height);
- let rect = Geometry::Rect(size).filled(fill);
- frame.prepend(pos, FrameItem::Shape(rect, self.span()));
- }
- dy += row.height;
- }
- dx += col;
- }
- }
-
- Ok(layout.fragment)
- }
-}
-
-/// Turn an iterator of extents into an iterator of offsets before, in between,
-/// and after the extents, e.g. [10mm, 5mm] -> [0mm, 10mm, 15mm].
-fn points(extents: impl IntoIterator<Item = Abs>) -> impl Iterator<Item = Abs> {
- let mut offset = Abs::zero();
- std::iter::once(Abs::zero())
- .chain(extents.into_iter())
- .map(move |extent| {
- offset += extent;
- offset
- })
-}
-
-/// A value that can be configured per cell.
-#[derive(Debug, Clone, PartialEq, Hash)]
-pub enum Celled<T> {
- /// A bare value, the same for all cells.
- Value(T),
- /// A closure mapping from cell coordinates to a value.
- Func(Func),
- /// An array of alignment values corresponding to each column.
- Array(Vec<T>),
-}
-
-impl<T: Default + Clone + FromValue> Celled<T> {
- /// Resolve the value based on the cell position.
- pub fn resolve(&self, vt: &mut Vt, x: usize, y: usize) -> SourceResult<T> {
- Ok(match self {
- Self::Value(value) => value.clone(),
- Self::Func(func) => func.call_vt(vt, [x, y])?.cast().at(func.span())?,
- Self::Array(array) => x
- .checked_rem(array.len())
- .and_then(|i| array.get(i))
- .cloned()
- .unwrap_or_default(),
- })
- }
-}
-
-impl<T: Default> Default for Celled<T> {
- fn default() -> Self {
- Self::Value(T::default())
- }
-}
-
-impl<T: Reflect> Reflect for Celled<T> {
- fn describe() -> CastInfo {
- T::describe() + Array::describe() + Func::describe()
- }
-
- fn castable(value: &Value) -> bool {
- Array::castable(value) || Func::castable(value) || T::castable(value)
- }
-}
-
-impl<T: IntoValue> IntoValue for Celled<T> {
- fn into_value(self) -> Value {
- match self {
- Self::Value(value) => value.into_value(),
- Self::Func(func) => func.into_value(),
- Self::Array(arr) => arr.into_value(),
- }
- }
-}
-
-impl<T: FromValue> FromValue for Celled<T> {
- fn from_value(value: Value) -> StrResult<Self> {
- match value {
- Value::Func(v) => Ok(Self::Func(v)),
- Value::Array(array) => Ok(Self::Array(
- array.into_iter().map(T::from_value).collect::<StrResult<_>>()?,
- )),
- v if T::castable(&v) => Ok(Self::Value(T::from_value(v)?)),
- v => Err(Self::error(&v)),
- }
- }
-}
-
-impl LocalName for TableElem {
- fn local_name(&self, lang: Lang, _: Option<Region>) -> &'static str {
- match lang {
- Lang::ALBANIAN => "Tabel",
- Lang::ARABIC => "جدول",
- Lang::BOKMÅL => "Tabell",
- Lang::CHINESE => "表",
- Lang::CZECH => "Tabulka",
- Lang::DANISH => "Tabel",
- Lang::DUTCH => "Tabel",
- Lang::FILIPINO => "Talaan",
- Lang::FRENCH => "Tableau",
- Lang::GERMAN => "Tabelle",
- Lang::ITALIAN => "Tabella",
- Lang::NYNORSK => "Tabell",
- Lang::POLISH => "Tabela",
- Lang::PORTUGUESE => "Tabela",
- Lang::RUSSIAN => "Таблица",
- Lang::SLOVENIAN => "Tabela",
- Lang::SPANISH => "Tabla",
- Lang::SWEDISH => "Tabell",
- Lang::TURKISH => "Tablo",
- Lang::UKRAINIAN => "Таблиця",
- Lang::VIETNAMESE => "Bảng",
- Lang::ENGLISH | _ => "Table",
- }
- }
-}
-
-impl Figurable for TableElem {}