summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Färber <01mf02@gmail.com>2025-01-06 16:20:28 +0100
committerGitHub <noreply@github.com>2025-01-06 15:20:28 +0000
commit5c876535cc89912b32bc29a17c753ae9b1f03938 (patch)
tree987401ef7feb00ae8cf0197cc4c902c2ba3b488e
parentce7f680fd5f21f79548280c844e1abbabc0d4e46 (diff)
Move `CellGrid` from `typst-layout` to `typst-library` (#5585)
-rw-r--r--crates/typst-layout/src/grid/layouter.rs18
-rw-r--r--crates/typst-layout/src/grid/lines.rs38
-rw-r--r--crates/typst-layout/src/grid/mod.rs410
-rw-r--r--crates/typst-layout/src/grid/repeated.rs41
-rw-r--r--crates/typst-layout/src/grid/rowspans.rs6
-rw-r--r--crates/typst-layout/src/lists.rs3
-rw-r--r--crates/typst-library/src/layout/grid/mod.rs (renamed from crates/typst-library/src/layout/grid.rs)2
-rw-r--r--crates/typst-library/src/layout/grid/resolve.rs (renamed from crates/typst-layout/src/grid/cells.rs)474
-rw-r--r--crates/typst-library/src/layout/mod.rs2
9 files changed, 502 insertions, 492 deletions
diff --git a/crates/typst-layout/src/grid/layouter.rs b/crates/typst-layout/src/grid/layouter.rs
index 7c94617d..1f9cf679 100644
--- a/crates/typst-layout/src/grid/layouter.rs
+++ b/crates/typst-layout/src/grid/layouter.rs
@@ -3,6 +3,7 @@ use std::fmt::Debug;
use typst_library::diag::{bail, SourceResult};
use typst_library::engine::Engine;
use typst_library::foundations::{Resolve, StyleChain};
+use typst_library::layout::grid::resolve::{Cell, CellGrid, LinePosition, Repeatable};
use typst_library::layout::{
Abs, Axes, Dir, Fr, Fragment, Frame, FrameItem, Length, Point, Region, Regions, Rel,
Size, Sizing,
@@ -13,8 +14,8 @@ use typst_syntax::Span;
use typst_utils::{MaybeReverseIter, Numeric};
use super::{
- generate_line_segments, hline_stroke_at_column, vline_stroke_at_row, Cell, CellGrid,
- LinePosition, LineSegment, Repeatable, Rowspan, UnbreakableRowGroup,
+ generate_line_segments, hline_stroke_at_column, layout_cell, vline_stroke_at_row,
+ LineSegment, Rowspan, UnbreakableRowGroup,
};
/// Performs grid layout.
@@ -843,7 +844,8 @@ impl<'a> GridLayouter<'a> {
let size = Size::new(available, height);
let pod = Region::new(size, Axes::splat(false));
- let frame = cell.layout(engine, 0, self.styles, pod.into())?.into_frame();
+ let frame =
+ layout_cell(cell, engine, 0, self.styles, pod.into())?.into_frame();
resolved.set_max(frame.width() - already_covered_width);
}
@@ -1086,7 +1088,7 @@ impl<'a> GridLayouter<'a> {
};
let frames =
- cell.layout(engine, disambiguator, self.styles, pod)?.into_frames();
+ layout_cell(cell, engine, disambiguator, self.styles, pod)?.into_frames();
// Skip the first region if one cell in it is empty. Then,
// remeasure.
@@ -1252,9 +1254,9 @@ impl<'a> GridLayouter<'a> {
// rows.
pod.full = self.regions.full;
}
- let frame = cell
- .layout(engine, disambiguator, self.styles, pod)?
- .into_frame();
+ let frame =
+ layout_cell(cell, engine, disambiguator, self.styles, pod)?
+ .into_frame();
let mut pos = pos;
if self.is_rtl {
// In the grid, cell colspans expand to the right,
@@ -1310,7 +1312,7 @@ impl<'a> GridLayouter<'a> {
// Push the layouted frames into the individual output frames.
let fragment =
- cell.layout(engine, disambiguator, self.styles, pod)?;
+ layout_cell(cell, engine, disambiguator, self.styles, pod)?;
for (output, frame) in outputs.iter_mut().zip(fragment) {
let mut pos = pos;
if self.is_rtl {
diff --git a/crates/typst-layout/src/grid/lines.rs b/crates/typst-layout/src/grid/lines.rs
index 3e89612a..1227953d 100644
--- a/crates/typst-layout/src/grid/lines.rs
+++ b/crates/typst-layout/src/grid/lines.rs
@@ -1,41 +1,11 @@
-use std::num::NonZeroUsize;
use std::sync::Arc;
use typst_library::foundations::{AlternativeFold, Fold};
+use typst_library::layout::grid::resolve::{CellGrid, Line, Repeatable};
use typst_library::layout::Abs;
use typst_library::visualize::Stroke;
-use super::{CellGrid, LinePosition, Repeatable, RowPiece};
-
-/// Represents an explicit grid line (horizontal or vertical) specified by the
-/// user.
-pub struct Line {
- /// The index of the track after this line. This will be the index of the
- /// row a horizontal line is above of, or of the column right after a
- /// vertical line.
- ///
- /// Must be within `0..=tracks.len()` (where `tracks` is either `grid.cols`
- /// or `grid.rows`, ignoring gutter tracks, as appropriate).
- pub index: usize,
- /// The index of the track at which this line starts being drawn.
- /// This is the first column a horizontal line appears in, or the first row
- /// a vertical line appears in.
- ///
- /// Must be within `0..tracks.len()` minus gutter tracks.
- pub start: usize,
- /// The index after the last track through which the line is drawn.
- /// Thus, the line is drawn through tracks `start..end` (note that `end` is
- /// exclusive).
- ///
- /// Must be within `1..=tracks.len()` minus gutter tracks.
- /// `None` indicates the line should go all the way to the end.
- pub end: Option<NonZeroUsize>,
- /// The line's stroke. This is `None` when the line is explicitly used to
- /// override a previously specified line.
- pub stroke: Option<Arc<Stroke<Abs>>>,
- /// The line's position in relation to the track with its index.
- pub position: LinePosition,
-}
+use super::RowPiece;
/// Indicates which priority a particular grid line segment should have, based
/// on the highest priority configuration that defined the segment's stroke.
@@ -588,13 +558,13 @@ pub fn hline_stroke_at_column(
#[cfg(test)]
mod test {
+ use std::num::NonZeroUsize;
use typst_library::foundations::Content;
use typst_library::introspection::Locator;
+ use typst_library::layout::grid::resolve::{Cell, Entry, LinePosition};
use typst_library::layout::{Axes, Sides, Sizing};
use typst_utils::NonZeroExt;
- use super::super::cells::Entry;
- use super::super::Cell;
use super::*;
fn sample_cell() -> Cell<'static> {
diff --git a/crates/typst-layout/src/grid/mod.rs b/crates/typst-layout/src/grid/mod.rs
index 769bef8c..1b4380f0 100644
--- a/crates/typst-layout/src/grid/mod.rs
+++ b/crates/typst-layout/src/grid/mod.rs
@@ -1,40 +1,44 @@
-mod cells;
mod layouter;
mod lines;
mod repeated;
mod rowspans;
-pub use self::cells::{Cell, CellGrid};
pub use self::layouter::GridLayouter;
-use std::num::NonZeroUsize;
-use std::sync::Arc;
-
-use ecow::eco_format;
-use typst_library::diag::{SourceResult, Trace, Tracepoint};
+use typst_library::diag::SourceResult;
use typst_library::engine::Engine;
-use typst_library::foundations::{Fold, Packed, Smart, StyleChain};
+use typst_library::foundations::{Packed, StyleChain};
use typst_library::introspection::Locator;
-use typst_library::layout::{
- Abs, Alignment, Axes, Dir, Fragment, GridCell, GridChild, GridElem, GridItem, Length,
- OuterHAlignment, OuterVAlignment, Regions, Rel, Sides,
-};
-use typst_library::model::{TableCell, TableChild, TableElem, TableItem};
-use typst_library::text::TextElem;
-use typst_library::visualize::{Paint, Stroke};
-use typst_syntax::Span;
+use typst_library::layout::grid::resolve::{grid_to_cellgrid, table_to_cellgrid, Cell};
+use typst_library::layout::{Fragment, GridElem, Regions};
+use typst_library::model::TableElem;
-use self::cells::{
- LinePosition, ResolvableCell, ResolvableGridChild, ResolvableGridItem,
-};
use self::layouter::RowPiece;
use self::lines::{
- generate_line_segments, hline_stroke_at_column, vline_stroke_at_row, Line,
- LineSegment,
+ generate_line_segments, hline_stroke_at_column, vline_stroke_at_row, LineSegment,
};
-use self::repeated::{Footer, Header, Repeatable};
use self::rowspans::{Rowspan, UnbreakableRowGroup};
+/// Layout the cell into the given regions.
+///
+/// The `disambiguator` indicates which instance of this cell this should be
+/// layouted as. For normal cells, it is always `0`, but for headers and
+/// footers, it indicates the index of the header/footer among all. See the
+/// [`Locator`] docs for more details on the concepts behind this.
+pub fn layout_cell(
+ cell: &Cell,
+ engine: &mut Engine,
+ disambiguator: usize,
+ styles: StyleChain,
+ regions: Regions,
+) -> SourceResult<Fragment> {
+ let mut locator = cell.locator.relayout();
+ if disambiguator > 0 {
+ locator = locator.split().next_inner(disambiguator as u128);
+ }
+ crate::layout_fragment(engine, &cell.body, locator, styles, regions)
+}
+
/// Layout the grid.
#[typst_macros::time(span = elem.span())]
pub fn layout_grid(
@@ -44,54 +48,8 @@ pub fn layout_grid(
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment> {
- let inset = elem.inset(styles);
- let align = elem.align(styles);
- let columns = elem.columns(styles);
- let rows = elem.rows(styles);
- let column_gutter = elem.column_gutter(styles);
- let row_gutter = elem.row_gutter(styles);
- let fill = elem.fill(styles);
- let stroke = elem.stroke(styles);
-
- 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());
- // Use trace to link back to the grid when a specific cell errors
- let tracepoint = || Tracepoint::Call(Some(eco_format!("grid")));
- let resolve_item = |item: &GridItem| grid_item_to_resolvable(item, styles);
- let children = elem.children().iter().map(|child| match child {
- GridChild::Header(header) => ResolvableGridChild::Header {
- repeat: header.repeat(styles),
- span: header.span(),
- items: header.children().iter().map(resolve_item),
- },
- GridChild::Footer(footer) => ResolvableGridChild::Footer {
- repeat: footer.repeat(styles),
- span: footer.span(),
- items: footer.children().iter().map(resolve_item),
- },
- GridChild::Item(item) => {
- ResolvableGridChild::Item(grid_item_to_resolvable(item, styles))
- }
- });
- let grid = CellGrid::resolve(
- tracks,
- gutter,
- locator,
- children,
- fill,
- align,
- &inset,
- &stroke,
- engine,
- styles,
- elem.span(),
- )
- .trace(engine.world, tracepoint, elem.span())?;
-
- let layouter = GridLayouter::new(&grid, regions, styles, elem.span());
-
- // Measure the columns and layout the grid row-by-row.
- layouter.layout(engine)
+ let grid = grid_to_cellgrid(elem, engine, locator, styles)?;
+ GridLayouter::new(&grid, regions, styles, elem.span()).layout(engine)
}
/// Layout the table.
@@ -103,314 +61,6 @@ pub fn layout_table(
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment> {
- let inset = elem.inset(styles);
- let align = elem.align(styles);
- let columns = elem.columns(styles);
- let rows = elem.rows(styles);
- let column_gutter = elem.column_gutter(styles);
- let row_gutter = elem.row_gutter(styles);
- let fill = elem.fill(styles);
- let stroke = elem.stroke(styles);
-
- 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());
- // Use trace to link back to the table when a specific cell errors
- let tracepoint = || Tracepoint::Call(Some(eco_format!("table")));
- let resolve_item = |item: &TableItem| table_item_to_resolvable(item, styles);
- let children = elem.children().iter().map(|child| match child {
- TableChild::Header(header) => ResolvableGridChild::Header {
- repeat: header.repeat(styles),
- span: header.span(),
- items: header.children().iter().map(resolve_item),
- },
- TableChild::Footer(footer) => ResolvableGridChild::Footer {
- repeat: footer.repeat(styles),
- span: footer.span(),
- items: footer.children().iter().map(resolve_item),
- },
- TableChild::Item(item) => {
- ResolvableGridChild::Item(table_item_to_resolvable(item, styles))
- }
- });
- let grid = CellGrid::resolve(
- tracks,
- gutter,
- locator,
- children,
- fill,
- align,
- &inset,
- &stroke,
- engine,
- styles,
- elem.span(),
- )
- .trace(engine.world, tracepoint, elem.span())?;
-
- let layouter = GridLayouter::new(&grid, regions, styles, elem.span());
- layouter.layout(engine)
-}
-
-fn grid_item_to_resolvable(
- item: &GridItem,
- styles: StyleChain,
-) -> ResolvableGridItem<Packed<GridCell>> {
- match item {
- GridItem::HLine(hline) => ResolvableGridItem::HLine {
- y: hline.y(styles),
- start: hline.start(styles),
- end: hline.end(styles),
- stroke: hline.stroke(styles),
- span: hline.span(),
- position: match hline.position(styles) {
- OuterVAlignment::Top => LinePosition::Before,
- OuterVAlignment::Bottom => LinePosition::After,
- },
- },
- GridItem::VLine(vline) => ResolvableGridItem::VLine {
- x: vline.x(styles),
- start: vline.start(styles),
- end: vline.end(styles),
- stroke: vline.stroke(styles),
- span: vline.span(),
- position: match vline.position(styles) {
- OuterHAlignment::Left if TextElem::dir_in(styles) == Dir::RTL => {
- LinePosition::After
- }
- OuterHAlignment::Right if TextElem::dir_in(styles) == Dir::RTL => {
- LinePosition::Before
- }
- OuterHAlignment::Start | OuterHAlignment::Left => LinePosition::Before,
- OuterHAlignment::End | OuterHAlignment::Right => LinePosition::After,
- },
- },
- GridItem::Cell(cell) => ResolvableGridItem::Cell(cell.clone()),
- }
-}
-
-fn table_item_to_resolvable(
- item: &TableItem,
- styles: StyleChain,
-) -> ResolvableGridItem<Packed<TableCell>> {
- match item {
- TableItem::HLine(hline) => ResolvableGridItem::HLine {
- y: hline.y(styles),
- start: hline.start(styles),
- end: hline.end(styles),
- stroke: hline.stroke(styles),
- span: hline.span(),
- position: match hline.position(styles) {
- OuterVAlignment::Top => LinePosition::Before,
- OuterVAlignment::Bottom => LinePosition::After,
- },
- },
- TableItem::VLine(vline) => ResolvableGridItem::VLine {
- x: vline.x(styles),
- start: vline.start(styles),
- end: vline.end(styles),
- stroke: vline.stroke(styles),
- span: vline.span(),
- position: match vline.position(styles) {
- OuterHAlignment::Left if TextElem::dir_in(styles) == Dir::RTL => {
- LinePosition::After
- }
- OuterHAlignment::Right if TextElem::dir_in(styles) == Dir::RTL => {
- LinePosition::Before
- }
- OuterHAlignment::Start | OuterHAlignment::Left => LinePosition::Before,
- OuterHAlignment::End | OuterHAlignment::Right => LinePosition::After,
- },
- },
- TableItem::Cell(cell) => ResolvableGridItem::Cell(cell.clone()),
- }
-}
-
-impl ResolvableCell for Packed<TableCell> {
- fn resolve_cell<'a>(
- mut self,
- x: usize,
- y: usize,
- fill: &Option<Paint>,
- align: Smart<Alignment>,
- inset: Sides<Option<Rel<Length>>>,
- stroke: Sides<Option<Option<Arc<Stroke<Abs>>>>>,
- breakable: bool,
- locator: Locator<'a>,
- styles: StyleChain,
- ) -> Cell<'a> {
- let cell = &mut *self;
- let colspan = cell.colspan(styles);
- let rowspan = cell.rowspan(styles);
- let breakable = cell.breakable(styles).unwrap_or(breakable);
- let fill = cell.fill(styles).unwrap_or_else(|| fill.clone());
-
- let cell_stroke = cell.stroke(styles);
- let stroke_overridden =
- cell_stroke.as_ref().map(|side| matches!(side, Some(Some(_))));
-
- // Using a typical 'Sides' fold, an unspecified side loses to a
- // specified side. Additionally, when both are specified, an inner
- // None wins over the outer Some, and vice-versa. When both are
- // specified and Some, fold occurs, which, remarkably, leads to an Arc
- // clone.
- //
- // In the end, we flatten because, for layout purposes, an unspecified
- // cell stroke is the same as specifying 'none', so we equate the two
- // concepts.
- let stroke = cell_stroke.fold(stroke).map(Option::flatten);
- 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(cell.align(styles).map_or(align, |inner| inner.fold(align)))
- }
- // Don't fold if the table is using outer alignment. Use the
- // cell's alignment instead (which, in the end, will fold with
- // the outer alignment when it is effectively displayed).
- Smart::Auto => cell.align(styles),
- });
- cell.push_inset(Smart::Custom(
- cell.inset(styles).map_or(inset, |inner| inner.fold(inset)),
- ));
- cell.push_stroke(
- // Here we convert the resolved stroke to a regular stroke, however
- // with resolved units (that is, 'em' converted to absolute units).
- // We also convert any stroke unspecified by both the cell and the
- // outer stroke ('None' in the folded stroke) to 'none', that is,
- // all sides are present in the resulting Sides object accessible
- // by show rules on table cells.
- stroke.as_ref().map(|side| {
- Some(side.as_ref().map(|cell_stroke| {
- Arc::new((**cell_stroke).clone().map(Length::from))
- }))
- }),
- );
- cell.push_breakable(Smart::Custom(breakable));
- Cell {
- body: self.pack(),
- locator,
- fill,
- colspan,
- rowspan,
- stroke,
- stroke_overridden,
- breakable,
- }
- }
-
- fn x(&self, styles: StyleChain) -> Smart<usize> {
- (**self).x(styles)
- }
-
- fn y(&self, styles: StyleChain) -> Smart<usize> {
- (**self).y(styles)
- }
-
- fn colspan(&self, styles: StyleChain) -> NonZeroUsize {
- (**self).colspan(styles)
- }
-
- fn rowspan(&self, styles: StyleChain) -> NonZeroUsize {
- (**self).rowspan(styles)
- }
-
- fn span(&self) -> Span {
- Packed::span(self)
- }
-}
-
-impl ResolvableCell for Packed<GridCell> {
- fn resolve_cell<'a>(
- mut self,
- x: usize,
- y: usize,
- fill: &Option<Paint>,
- align: Smart<Alignment>,
- inset: Sides<Option<Rel<Length>>>,
- stroke: Sides<Option<Option<Arc<Stroke<Abs>>>>>,
- breakable: bool,
- locator: Locator<'a>,
- styles: StyleChain,
- ) -> Cell<'a> {
- let cell = &mut *self;
- let colspan = cell.colspan(styles);
- let rowspan = cell.rowspan(styles);
- let breakable = cell.breakable(styles).unwrap_or(breakable);
- let fill = cell.fill(styles).unwrap_or_else(|| fill.clone());
-
- let cell_stroke = cell.stroke(styles);
- let stroke_overridden =
- cell_stroke.as_ref().map(|side| matches!(side, Some(Some(_))));
-
- // Using a typical 'Sides' fold, an unspecified side loses to a
- // specified side. Additionally, when both are specified, an inner
- // None wins over the outer Some, and vice-versa. When both are
- // specified and Some, fold occurs, which, remarkably, leads to an Arc
- // clone.
- //
- // In the end, we flatten because, for layout purposes, an unspecified
- // cell stroke is the same as specifying 'none', so we equate the two
- // concepts.
- let stroke = cell_stroke.fold(stroke).map(Option::flatten);
- 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(cell.align(styles).map_or(align, |inner| inner.fold(align)))
- }
- // Don't fold if the grid is using outer alignment. Use the
- // cell's alignment instead (which, in the end, will fold with
- // the outer alignment when it is effectively displayed).
- Smart::Auto => cell.align(styles),
- });
- cell.push_inset(Smart::Custom(
- cell.inset(styles).map_or(inset, |inner| inner.fold(inset)),
- ));
- cell.push_stroke(
- // Here we convert the resolved stroke to a regular stroke, however
- // with resolved units (that is, 'em' converted to absolute units).
- // We also convert any stroke unspecified by both the cell and the
- // outer stroke ('None' in the folded stroke) to 'none', that is,
- // all sides are present in the resulting Sides object accessible
- // by show rules on grid cells.
- stroke.as_ref().map(|side| {
- Some(side.as_ref().map(|cell_stroke| {
- Arc::new((**cell_stroke).clone().map(Length::from))
- }))
- }),
- );
- cell.push_breakable(Smart::Custom(breakable));
- Cell {
- body: self.pack(),
- locator,
- fill,
- colspan,
- rowspan,
- stroke,
- stroke_overridden,
- breakable,
- }
- }
-
- fn x(&self, styles: StyleChain) -> Smart<usize> {
- (**self).x(styles)
- }
-
- fn y(&self, styles: StyleChain) -> Smart<usize> {
- (**self).y(styles)
- }
-
- fn colspan(&self, styles: StyleChain) -> NonZeroUsize {
- (**self).colspan(styles)
- }
-
- fn rowspan(&self, styles: StyleChain) -> NonZeroUsize {
- (**self).rowspan(styles)
- }
-
- fn span(&self) -> Span {
- Packed::span(self)
- }
+ let grid = table_to_cellgrid(elem, engine, locator, styles)?;
+ GridLayouter::new(&grid, regions, styles, elem.span()).layout(engine)
}
diff --git a/crates/typst-layout/src/grid/repeated.rs b/crates/typst-layout/src/grid/repeated.rs
index 8d08d56d..22d2a09e 100644
--- a/crates/typst-layout/src/grid/repeated.rs
+++ b/crates/typst-layout/src/grid/repeated.rs
@@ -1,50 +1,11 @@
use typst_library::diag::SourceResult;
use typst_library::engine::Engine;
+use typst_library::layout::grid::resolve::{Footer, Header, Repeatable};
use typst_library::layout::{Abs, Axes, Frame, Regions};
use super::layouter::GridLayouter;
use super::rowspans::UnbreakableRowGroup;
-/// A repeatable grid header. Starts at the first row.
-pub struct Header {
- /// The index after the last row included in this header.
- pub end: usize,
-}
-
-/// A repeatable grid footer. Stops at the last row.
-pub struct Footer {
- /// The first row included in this footer.
- pub start: usize,
-}
-
-/// A possibly repeatable grid object.
-/// It still exists even when not repeatable, but must not have additional
-/// considerations by grid layout, other than for consistency (such as making
-/// a certain group of rows unbreakable).
-pub enum Repeatable<T> {
- Repeated(T),
- NotRepeated(T),
-}
-
-impl<T> Repeatable<T> {
- /// Gets the value inside this repeatable, regardless of whether
- /// it repeats.
- pub fn unwrap(&self) -> &T {
- match self {
- Self::Repeated(repeated) => repeated,
- Self::NotRepeated(not_repeated) => not_repeated,
- }
- }
-
- /// Returns `Some` if the value is repeated, `None` otherwise.
- pub fn as_repeated(&self) -> Option<&T> {
- match self {
- Self::Repeated(repeated) => Some(repeated),
- Self::NotRepeated(_) => None,
- }
- }
-}
-
impl GridLayouter<'_> {
/// Layouts the header's rows.
/// Skips regions as necessary.
diff --git a/crates/typst-layout/src/grid/rowspans.rs b/crates/typst-layout/src/grid/rowspans.rs
index 93d4c960..5039695d 100644
--- a/crates/typst-layout/src/grid/rowspans.rs
+++ b/crates/typst-layout/src/grid/rowspans.rs
@@ -1,12 +1,12 @@
use typst_library::diag::SourceResult;
use typst_library::engine::Engine;
use typst_library::foundations::Resolve;
+use typst_library::layout::grid::resolve::Repeatable;
use typst_library::layout::{Abs, Axes, Frame, Point, Region, Regions, Size, Sizing};
use typst_utils::MaybeReverseIter;
use super::layouter::{in_last_with_offset, points, Row, RowPiece};
-use super::repeated::Repeatable;
-use super::{Cell, GridLayouter};
+use super::{layout_cell, Cell, GridLayouter};
/// All information needed to layout a single rowspan.
pub struct Rowspan {
@@ -141,7 +141,7 @@ impl GridLayouter<'_> {
}
// Push the layouted frames directly into the finished frames.
- let fragment = cell.layout(engine, disambiguator, self.styles, pod)?;
+ let fragment = layout_cell(cell, engine, disambiguator, self.styles, pod)?;
let (current_region, current_rrows) = current_region_data.unzip();
for ((i, finished), frame) in self
.finished
diff --git a/crates/typst-layout/src/lists.rs b/crates/typst-layout/src/lists.rs
index 0d51a1e4..9479959b 100644
--- a/crates/typst-layout/src/lists.rs
+++ b/crates/typst-layout/src/lists.rs
@@ -4,11 +4,12 @@ use typst_library::diag::SourceResult;
use typst_library::engine::Engine;
use typst_library::foundations::{Content, Context, Depth, Packed, StyleChain};
use typst_library::introspection::Locator;
+use typst_library::layout::grid::resolve::{Cell, CellGrid};
use typst_library::layout::{Axes, Fragment, HAlignment, Regions, Sizing, VAlignment};
use typst_library::model::{EnumElem, ListElem, Numbering, ParElem};
use typst_library::text::TextElem;
-use crate::grid::{Cell, CellGrid, GridLayouter};
+use crate::grid::GridLayouter;
/// Layout the list.
#[typst_macros::time(span = elem.span())]
diff --git a/crates/typst-library/src/layout/grid.rs b/crates/typst-library/src/layout/grid/mod.rs
index 2e1e9abc..e46440fb 100644
--- a/crates/typst-library/src/layout/grid.rs
+++ b/crates/typst-library/src/layout/grid/mod.rs
@@ -1,3 +1,5 @@
+pub mod resolve;
+
use std::num::NonZeroUsize;
use std::sync::Arc;
diff --git a/crates/typst-layout/src/grid/cells.rs b/crates/typst-library/src/layout/grid/resolve.rs
index 175e2183..adaff1c1 100644
--- a/crates/typst-layout/src/grid/cells.rs
+++ b/crates/typst-library/src/layout/grid/resolve.rs
@@ -2,19 +2,463 @@ use std::num::NonZeroUsize;
use std::sync::Arc;
use ecow::eco_format;
-use typst_library::diag::{bail, At, Hint, HintedStrResult, HintedString, SourceResult};
+use typst_library::diag::{
+ bail, At, Hint, HintedStrResult, HintedString, SourceResult, Trace, Tracepoint,
+};
use typst_library::engine::Engine;
-use typst_library::foundations::{Content, Smart, StyleChain};
+use typst_library::foundations::{Content, Fold, Packed, Smart, StyleChain};
use typst_library::introspection::Locator;
use typst_library::layout::{
- Abs, Alignment, Axes, Celled, Fragment, Length, Regions, Rel, ResolvedCelled, Sides,
- Sizing,
+ Abs, Alignment, Axes, Celled, GridCell, GridChild, GridElem, GridItem, Length,
+ OuterHAlignment, OuterVAlignment, Rel, ResolvedCelled, Sides, Sizing,
};
+use typst_library::model::{TableCell, TableChild, TableElem, TableItem};
+use typst_library::text::TextElem;
use typst_library::visualize::{Paint, Stroke};
+use typst_library::Dir;
+
use typst_syntax::Span;
use typst_utils::NonZeroExt;
-use super::{Footer, Header, Line, Repeatable};
+/// Convert a grid to a cell grid.
+#[typst_macros::time(span = elem.span())]
+pub fn grid_to_cellgrid<'a>(
+ elem: &Packed<GridElem>,
+ engine: &mut Engine,
+ locator: Locator<'a>,
+ styles: StyleChain,
+) -> SourceResult<CellGrid<'a>> {
+ let inset = elem.inset(styles);
+ let align = elem.align(styles);
+ let columns = elem.columns(styles);
+ let rows = elem.rows(styles);
+ let column_gutter = elem.column_gutter(styles);
+ let row_gutter = elem.row_gutter(styles);
+ let fill = elem.fill(styles);
+ let stroke = elem.stroke(styles);
+
+ 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());
+ // Use trace to link back to the grid when a specific cell errors
+ let tracepoint = || Tracepoint::Call(Some(eco_format!("grid")));
+ let resolve_item = |item: &GridItem| grid_item_to_resolvable(item, styles);
+ let children = elem.children().iter().map(|child| match child {
+ GridChild::Header(header) => ResolvableGridChild::Header {
+ repeat: header.repeat(styles),
+ span: header.span(),
+ items: header.children().iter().map(resolve_item),
+ },
+ GridChild::Footer(footer) => ResolvableGridChild::Footer {
+ repeat: footer.repeat(styles),
+ span: footer.span(),
+ items: footer.children().iter().map(resolve_item),
+ },
+ GridChild::Item(item) => {
+ ResolvableGridChild::Item(grid_item_to_resolvable(item, styles))
+ }
+ });
+ CellGrid::resolve(
+ tracks,
+ gutter,
+ locator,
+ children,
+ fill,
+ align,
+ &inset,
+ &stroke,
+ engine,
+ styles,
+ elem.span(),
+ )
+ .trace(engine.world, tracepoint, elem.span())
+}
+
+/// Convert a table to a cell grid.
+#[typst_macros::time(span = elem.span())]
+pub fn table_to_cellgrid<'a>(
+ elem: &Packed<TableElem>,
+ engine: &mut Engine,
+ locator: Locator<'a>,
+ styles: StyleChain,
+) -> SourceResult<CellGrid<'a>> {
+ let inset = elem.inset(styles);
+ let align = elem.align(styles);
+ let columns = elem.columns(styles);
+ let rows = elem.rows(styles);
+ let column_gutter = elem.column_gutter(styles);
+ let row_gutter = elem.row_gutter(styles);
+ let fill = elem.fill(styles);
+ let stroke = elem.stroke(styles);
+
+ 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());
+ // Use trace to link back to the table when a specific cell errors
+ let tracepoint = || Tracepoint::Call(Some(eco_format!("table")));
+ let resolve_item = |item: &TableItem| table_item_to_resolvable(item, styles);
+ let children = elem.children().iter().map(|child| match child {
+ TableChild::Header(header) => ResolvableGridChild::Header {
+ repeat: header.repeat(styles),
+ span: header.span(),
+ items: header.children().iter().map(resolve_item),
+ },
+ TableChild::Footer(footer) => ResolvableGridChild::Footer {
+ repeat: footer.repeat(styles),
+ span: footer.span(),
+ items: footer.children().iter().map(resolve_item),
+ },
+ TableChild::Item(item) => {
+ ResolvableGridChild::Item(table_item_to_resolvable(item, styles))
+ }
+ });
+ CellGrid::resolve(
+ tracks,
+ gutter,
+ locator,
+ children,
+ fill,
+ align,
+ &inset,
+ &stroke,
+ engine,
+ styles,
+ elem.span(),
+ )
+ .trace(engine.world, tracepoint, elem.span())
+}
+
+fn grid_item_to_resolvable(
+ item: &GridItem,
+ styles: StyleChain,
+) -> ResolvableGridItem<Packed<GridCell>> {
+ match item {
+ GridItem::HLine(hline) => ResolvableGridItem::HLine {
+ y: hline.y(styles),
+ start: hline.start(styles),
+ end: hline.end(styles),
+ stroke: hline.stroke(styles),
+ span: hline.span(),
+ position: match hline.position(styles) {
+ OuterVAlignment::Top => LinePosition::Before,
+ OuterVAlignment::Bottom => LinePosition::After,
+ },
+ },
+ GridItem::VLine(vline) => ResolvableGridItem::VLine {
+ x: vline.x(styles),
+ start: vline.start(styles),
+ end: vline.end(styles),
+ stroke: vline.stroke(styles),
+ span: vline.span(),
+ position: match vline.position(styles) {
+ OuterHAlignment::Left if TextElem::dir_in(styles) == Dir::RTL => {
+ LinePosition::After
+ }
+ OuterHAlignment::Right if TextElem::dir_in(styles) == Dir::RTL => {
+ LinePosition::Before
+ }
+ OuterHAlignment::Start | OuterHAlignment::Left => LinePosition::Before,
+ OuterHAlignment::End | OuterHAlignment::Right => LinePosition::After,
+ },
+ },
+ GridItem::Cell(cell) => ResolvableGridItem::Cell(cell.clone()),
+ }
+}
+
+fn table_item_to_resolvable(
+ item: &TableItem,
+ styles: StyleChain,
+) -> ResolvableGridItem<Packed<TableCell>> {
+ match item {
+ TableItem::HLine(hline) => ResolvableGridItem::HLine {
+ y: hline.y(styles),
+ start: hline.start(styles),
+ end: hline.end(styles),
+ stroke: hline.stroke(styles),
+ span: hline.span(),
+ position: match hline.position(styles) {
+ OuterVAlignment::Top => LinePosition::Before,
+ OuterVAlignment::Bottom => LinePosition::After,
+ },
+ },
+ TableItem::VLine(vline) => ResolvableGridItem::VLine {
+ x: vline.x(styles),
+ start: vline.start(styles),
+ end: vline.end(styles),
+ stroke: vline.stroke(styles),
+ span: vline.span(),
+ position: match vline.position(styles) {
+ OuterHAlignment::Left if TextElem::dir_in(styles) == Dir::RTL => {
+ LinePosition::After
+ }
+ OuterHAlignment::Right if TextElem::dir_in(styles) == Dir::RTL => {
+ LinePosition::Before
+ }
+ OuterHAlignment::Start | OuterHAlignment::Left => LinePosition::Before,
+ OuterHAlignment::End | OuterHAlignment::Right => LinePosition::After,
+ },
+ },
+ TableItem::Cell(cell) => ResolvableGridItem::Cell(cell.clone()),
+ }
+}
+
+impl ResolvableCell for Packed<TableCell> {
+ fn resolve_cell<'a>(
+ mut self,
+ x: usize,
+ y: usize,
+ fill: &Option<Paint>,
+ align: Smart<Alignment>,
+ inset: Sides<Option<Rel<Length>>>,
+ stroke: Sides<Option<Option<Arc<Stroke<Abs>>>>>,
+ breakable: bool,
+ locator: Locator<'a>,
+ styles: StyleChain,
+ ) -> Cell<'a> {
+ let cell = &mut *self;
+ let colspan = cell.colspan(styles);
+ let rowspan = cell.rowspan(styles);
+ let breakable = cell.breakable(styles).unwrap_or(breakable);
+ let fill = cell.fill(styles).unwrap_or_else(|| fill.clone());
+
+ let cell_stroke = cell.stroke(styles);
+ let stroke_overridden =
+ cell_stroke.as_ref().map(|side| matches!(side, Some(Some(_))));
+
+ // Using a typical 'Sides' fold, an unspecified side loses to a
+ // specified side. Additionally, when both are specified, an inner
+ // None wins over the outer Some, and vice-versa. When both are
+ // specified and Some, fold occurs, which, remarkably, leads to an Arc
+ // clone.
+ //
+ // In the end, we flatten because, for layout purposes, an unspecified
+ // cell stroke is the same as specifying 'none', so we equate the two
+ // concepts.
+ let stroke = cell_stroke.fold(stroke).map(Option::flatten);
+ 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(cell.align(styles).map_or(align, |inner| inner.fold(align)))
+ }
+ // Don't fold if the table is using outer alignment. Use the
+ // cell's alignment instead (which, in the end, will fold with
+ // the outer alignment when it is effectively displayed).
+ Smart::Auto => cell.align(styles),
+ });
+ cell.push_inset(Smart::Custom(
+ cell.inset(styles).map_or(inset, |inner| inner.fold(inset)),
+ ));
+ cell.push_stroke(
+ // Here we convert the resolved stroke to a regular stroke, however
+ // with resolved units (that is, 'em' converted to absolute units).
+ // We also convert any stroke unspecified by both the cell and the
+ // outer stroke ('None' in the folded stroke) to 'none', that is,
+ // all sides are present in the resulting Sides object accessible
+ // by show rules on table cells.
+ stroke.as_ref().map(|side| {
+ Some(side.as_ref().map(|cell_stroke| {
+ Arc::new((**cell_stroke).clone().map(Length::from))
+ }))
+ }),
+ );
+ cell.push_breakable(Smart::Custom(breakable));
+ Cell {
+ body: self.pack(),
+ locator,
+ fill,
+ colspan,
+ rowspan,
+ stroke,
+ stroke_overridden,
+ breakable,
+ }
+ }
+
+ fn x(&self, styles: StyleChain) -> Smart<usize> {
+ (**self).x(styles)
+ }
+
+ fn y(&self, styles: StyleChain) -> Smart<usize> {
+ (**self).y(styles)
+ }
+
+ fn colspan(&self, styles: StyleChain) -> NonZeroUsize {
+ (**self).colspan(styles)
+ }
+
+ fn rowspan(&self, styles: StyleChain) -> NonZeroUsize {
+ (**self).rowspan(styles)
+ }
+
+ fn span(&self) -> Span {
+ Packed::span(self)
+ }
+}
+
+impl ResolvableCell for Packed<GridCell> {
+ fn resolve_cell<'a>(
+ mut self,
+ x: usize,
+ y: usize,
+ fill: &Option<Paint>,
+ align: Smart<Alignment>,
+ inset: Sides<Option<Rel<Length>>>,
+ stroke: Sides<Option<Option<Arc<Stroke<Abs>>>>>,
+ breakable: bool,
+ locator: Locator<'a>,
+ styles: StyleChain,
+ ) -> Cell<'a> {
+ let cell = &mut *self;
+ let colspan = cell.colspan(styles);
+ let rowspan = cell.rowspan(styles);
+ let breakable = cell.breakable(styles).unwrap_or(breakable);
+ let fill = cell.fill(styles).unwrap_or_else(|| fill.clone());
+
+ let cell_stroke = cell.stroke(styles);
+ let stroke_overridden =
+ cell_stroke.as_ref().map(|side| matches!(side, Some(Some(_))));
+
+ // Using a typical 'Sides' fold, an unspecified side loses to a
+ // specified side. Additionally, when both are specified, an inner
+ // None wins over the outer Some, and vice-versa. When both are
+ // specified and Some, fold occurs, which, remarkably, leads to an Arc
+ // clone.
+ //
+ // In the end, we flatten because, for layout purposes, an unspecified
+ // cell stroke is the same as specifying 'none', so we equate the two
+ // concepts.
+ let stroke = cell_stroke.fold(stroke).map(Option::flatten);
+ 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(cell.align(styles).map_or(align, |inner| inner.fold(align)))
+ }
+ // Don't fold if the grid is using outer alignment. Use the
+ // cell's alignment instead (which, in the end, will fold with
+ // the outer alignment when it is effectively displayed).
+ Smart::Auto => cell.align(styles),
+ });
+ cell.push_inset(Smart::Custom(
+ cell.inset(styles).map_or(inset, |inner| inner.fold(inset)),
+ ));
+ cell.push_stroke(
+ // Here we convert the resolved stroke to a regular stroke, however
+ // with resolved units (that is, 'em' converted to absolute units).
+ // We also convert any stroke unspecified by both the cell and the
+ // outer stroke ('None' in the folded stroke) to 'none', that is,
+ // all sides are present in the resulting Sides object accessible
+ // by show rules on grid cells.
+ stroke.as_ref().map(|side| {
+ Some(side.as_ref().map(|cell_stroke| {
+ Arc::new((**cell_stroke).clone().map(Length::from))
+ }))
+ }),
+ );
+ cell.push_breakable(Smart::Custom(breakable));
+ Cell {
+ body: self.pack(),
+ locator,
+ fill,
+ colspan,
+ rowspan,
+ stroke,
+ stroke_overridden,
+ breakable,
+ }
+ }
+
+ fn x(&self, styles: StyleChain) -> Smart<usize> {
+ (**self).x(styles)
+ }
+
+ fn y(&self, styles: StyleChain) -> Smart<usize> {
+ (**self).y(styles)
+ }
+
+ fn colspan(&self, styles: StyleChain) -> NonZeroUsize {
+ (**self).colspan(styles)
+ }
+
+ fn rowspan(&self, styles: StyleChain) -> NonZeroUsize {
+ (**self).rowspan(styles)
+ }
+
+ fn span(&self) -> Span {
+ Packed::span(self)
+ }
+}
+
+/// Represents an explicit grid line (horizontal or vertical) specified by the
+/// user.
+pub struct Line {
+ /// The index of the track after this line. This will be the index of the
+ /// row a horizontal line is above of, or of the column right after a
+ /// vertical line.
+ ///
+ /// Must be within `0..=tracks.len()` (where `tracks` is either `grid.cols`
+ /// or `grid.rows`, ignoring gutter tracks, as appropriate).
+ pub index: usize,
+ /// The index of the track at which this line starts being drawn.
+ /// This is the first column a horizontal line appears in, or the first row
+ /// a vertical line appears in.
+ ///
+ /// Must be within `0..tracks.len()` minus gutter tracks.
+ pub start: usize,
+ /// The index after the last track through which the line is drawn.
+ /// Thus, the line is drawn through tracks `start..end` (note that `end` is
+ /// exclusive).
+ ///
+ /// Must be within `1..=tracks.len()` minus gutter tracks.
+ /// `None` indicates the line should go all the way to the end.
+ pub end: Option<NonZeroUsize>,
+ /// The line's stroke. This is `None` when the line is explicitly used to
+ /// override a previously specified line.
+ pub stroke: Option<Arc<Stroke<Abs>>>,
+ /// The line's position in relation to the track with its index.
+ pub position: LinePosition,
+}
+
+/// A repeatable grid header. Starts at the first row.
+pub struct Header {
+ /// The index after the last row included in this header.
+ pub end: usize,
+}
+
+/// A repeatable grid footer. Stops at the last row.
+pub struct Footer {
+ /// The first row included in this footer.
+ pub start: usize,
+}
+
+/// A possibly repeatable grid object.
+/// It still exists even when not repeatable, but must not have additional
+/// considerations by grid layout, other than for consistency (such as making
+/// a certain group of rows unbreakable).
+pub enum Repeatable<T> {
+ Repeated(T),
+ NotRepeated(T),
+}
+
+impl<T> Repeatable<T> {
+ /// Gets the value inside this repeatable, regardless of whether
+ /// it repeats.
+ pub fn unwrap(&self) -> &T {
+ match self {
+ Self::Repeated(repeated) => repeated,
+ Self::NotRepeated(not_repeated) => not_repeated,
+ }
+ }
+
+ /// Returns `Some` if the value is repeated, `None` otherwise.
+ pub fn as_repeated(&self) -> Option<&T> {
+ match self {
+ Self::Repeated(repeated) => Some(repeated),
+ Self::NotRepeated(_) => None,
+ }
+ }
+}
/// Used for cell-like elements which are aware of their final properties in
/// the table, and may have property overrides.
@@ -131,26 +575,6 @@ impl<'a> Cell<'a> {
breakable: true,
}
}
-
- /// Layout the cell into the given regions.
- ///
- /// The `disambiguator` indicates which instance of this cell this should be
- /// layouted as. For normal cells, it is always `0`, but for headers and
- /// footers, it indicates the index of the header/footer among all. See the
- /// [`Locator`] docs for more details on the concepts behind this.
- pub fn layout(
- &self,
- engine: &mut Engine,
- disambiguator: usize,
- styles: StyleChain,
- regions: Regions,
- ) -> SourceResult<Fragment> {
- let mut locator = self.locator.relayout();
- if disambiguator > 0 {
- locator = locator.split().next_inner(disambiguator as u128);
- }
- crate::layout_fragment(engine, &self.body, locator, styles, regions)
- }
}
/// Indicates whether the line should be drawn before or after the track with
diff --git a/crates/typst-library/src/layout/mod.rs b/crates/typst-library/src/layout/mod.rs
index b54d6906..574a2830 100644
--- a/crates/typst-library/src/layout/mod.rs
+++ b/crates/typst-library/src/layout/mod.rs
@@ -12,7 +12,7 @@ mod em;
mod fr;
mod fragment;
mod frame;
-mod grid;
+pub mod grid;
mod hide;
#[path = "layout.rs"]
mod layout_;