mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Move CellGrid
from typst-layout
to typst-library
(#5585)
This commit is contained in:
parent
ce7f680fd5
commit
5c876535cc
@ -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 {
|
||||
|
@ -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> {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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())]
|
||||
|
@ -1,3 +1,5 @@
|
||||
pub mod resolve;
|
||||
|
||||
use std::num::NonZeroUsize;
|
||||
use std::sync::Arc;
|
||||
|
@ -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
|
@ -12,7 +12,7 @@ mod em;
|
||||
mod fr;
|
||||
mod fragment;
|
||||
mod frame;
|
||||
mod grid;
|
||||
pub mod grid;
|
||||
mod hide;
|
||||
#[path = "layout.rs"]
|
||||
mod layout_;
|
||||
|
Loading…
x
Reference in New Issue
Block a user