diff options
Diffstat (limited to 'library/src/layout/table.rs')
| -rw-r--r-- | library/src/layout/table.rs | 334 |
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 {} |
