diff --git a/library/src/layout/grid.rs b/library/src/layout/grid.rs index 92bf19d1a..c5bbe9e5e 100644 --- a/library/src/layout/grid.rs +++ b/library/src/layout/grid.rs @@ -139,7 +139,7 @@ impl Layout for GridNode { ); // Measure the columns and layout the grid row-by-row. - layouter.layout() + Ok(layouter.layout()?.fragment) } } @@ -165,7 +165,7 @@ castable! { } /// Performs grid layout. -struct GridLayouter<'a, 'v> { +pub struct GridLayouter<'a, 'v> { /// The core context. vt: &'a mut Vt<'v>, /// The grid cells. @@ -184,6 +184,8 @@ struct GridLayouter<'a, 'v> { styles: StyleChain<'a>, /// Resolved column sizes. rcols: Vec, + /// Resolve row sizes, by region. + rrows: Vec>, /// Rows in the current region. lrows: Vec, /// The used-up size of the current region. The horizontal size is @@ -195,11 +197,31 @@ struct GridLayouter<'a, 'v> { finished: Vec, } +/// The resulting sizes of columns and rows in a grid. +#[derive(Debug)] +pub struct GridLayout { + /// The fragment. + pub fragment: Fragment, + /// The column widths. + pub cols: Vec, + /// The heights of the resulting rows segments, by region. + pub rows: Vec>, +} + +/// Details about a resulting row piece. +#[derive(Debug)] +pub struct RowPiece { + /// The height of the segment. + pub height: Abs, + /// The index of the row. + pub y: usize, +} + /// Produced by initial row layout, auto and relative rows are already finished, /// fractional rows not yet. enum Row { - /// Finished row frame of auto or relative row. - Frame(Frame), + /// Finished row frame of auto or relative row with y index. + Frame(Frame, usize), /// Fractional row with y index. Fr(Fr, usize), } @@ -208,7 +230,7 @@ impl<'a, 'v> GridLayouter<'a, 'v> { /// Create a new grid layouter. /// /// This prepares grid layout by unifying content and gutter tracks. - fn new( + pub fn new( vt: &'a mut Vt<'v>, tracks: Axes<&[Sizing]>, gutter: Axes<&[Sizing]>, @@ -284,6 +306,7 @@ impl<'a, 'v> GridLayouter<'a, 'v> { regions, styles, rcols, + rrows: vec![], lrows, used: Size::zero(), fr: Fr::zero(), @@ -292,7 +315,7 @@ impl<'a, 'v> GridLayouter<'a, 'v> { } /// Determines the columns sizes and then layouts the grid row-by-row. - fn layout(mut self) -> SourceResult { + pub fn layout(mut self) -> SourceResult { self.measure_columns()?; for y in 0..self.rows.len() { @@ -313,7 +336,12 @@ impl<'a, 'v> GridLayouter<'a, 'v> { } self.finish_region()?; - Ok(Fragment::frames(self.finished)) + + Ok(GridLayout { + fragment: Fragment::frames(self.finished), + cols: self.rcols, + rows: self.rrows, + }) } /// Determine all column sizes. @@ -485,7 +513,7 @@ impl<'a, 'v> GridLayouter<'a, 'v> { // Layout into a single region. if let &[first] = resolved.as_slice() { let frame = self.layout_single_row(first, y)?; - self.push_row(frame); + self.push_row(frame, y); return Ok(()); } @@ -508,7 +536,7 @@ impl<'a, 'v> GridLayouter<'a, 'v> { let fragment = self.layout_multi_row(&resolved, y)?; let len = fragment.len(); for (i, frame) in fragment.into_iter().enumerate() { - self.push_row(frame); + self.push_row(frame, y); if i + 1 < len { self.finish_region()?; } @@ -534,7 +562,7 @@ impl<'a, 'v> GridLayouter<'a, 'v> { } } - self.push_row(frame); + self.push_row(frame, y); Ok(()) } @@ -595,10 +623,10 @@ impl<'a, 'v> GridLayouter<'a, 'v> { } /// Push a row frame into the current region. - fn push_row(&mut self, frame: Frame) { + fn push_row(&mut self, frame: Frame, y: usize) { self.regions.size.y -= frame.height(); self.used.y += frame.height(); - self.lrows.push(Row::Frame(frame)); + self.lrows.push(Row::Frame(frame, y)); } /// Finish rows for one region. @@ -613,25 +641,28 @@ impl<'a, 'v> GridLayouter<'a, 'v> { // The frame for the region. let mut output = Frame::new(size); let mut pos = Point::zero(); + let mut rrows = vec![]; // Place finished rows and layout fractional rows. for row in std::mem::take(&mut self.lrows) { - let frame = match row { - Row::Frame(frame) => frame, + let (frame, y) = match row { + Row::Frame(frame, y) => (frame, y), Row::Fr(v, y) => { let remaining = self.regions.full - self.used.y; let height = v.share(self.fr, remaining); - self.layout_single_row(height, y)? + (self.layout_single_row(height, y)?, y) } }; let height = frame.height(); output.push_frame(pos, frame); + rrows.push(RowPiece { height, y }); pos.y += height; } self.finished.push(output); self.regions.next(); + self.rrows.push(rrows); self.used.y = Abs::zero(); self.fr = Fr::zero(); diff --git a/library/src/layout/table.rs b/library/src/layout/table.rs index 1ceea9b94..3084c3d41 100644 --- a/library/src/layout/table.rs +++ b/library/src/layout/table.rs @@ -1,4 +1,4 @@ -use crate::layout::{AlignNode, GridNode, Sizing, TrackSizings}; +use crate::layout::{AlignNode, GridLayouter, Sizing, TrackSizings}; use crate::prelude::*; /// # Table @@ -153,13 +153,11 @@ impl Layout for TableNode { styles: StyleChain, regions: Regions, ) -> SourceResult { - let fill = styles.get(Self::FILL); - let stroke = styles.get(Self::STROKE).map(PartialStroke::unwrap_or_default); let inset = styles.get(Self::INSET); let align = styles.get(Self::ALIGN); let cols = self.tracks.x.len().max(1); - let cells = self + let cells: Vec<_> = self .cells .iter() .cloned() @@ -173,27 +171,81 @@ impl Layout for TableNode { child = child.styled(AlignNode::ALIGNS, alignment) } - if let Some(stroke) = stroke { - child = child.stroked(stroke); - } - - if let Some(fill) = fill.resolve(vt, x, y)? { - child = child.filled(fill); - } - Ok(child) }) .collect::>()?; - GridNode { - tracks: self.tracks.clone(), - gutter: self.gutter.clone(), - cells, + let fill = styles.get(Self::FILL); + let stroke = styles.get(Self::STROKE).map(PartialStroke::unwrap_or_default); + + // Prepare grid layout by unifying content and gutter tracks. + let layouter = GridLayouter::new( + vt, + self.tracks.as_deref(), + self.gutter.as_deref(), + &cells, + regions, + styles, + ); + + // Measure the columns and layout the grid row-by-row. + let mut layout = layouter.layout()?; + + // Add lines and backgrounds. + for (frame, rows) in layout.fragment.iter_mut().zip(&layout.rows) { + // 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); + frame.prepend(Point::new(-half, offset), Element::Shape(hline)); + } + + // 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); + frame.prepend(Point::new(offset, -half), Element::Shape(vline)); + } + } + + // 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, Element::Shape(rect)); + } + dy += row.height; + } + dx += col; + } } - .layout(vt, styles, regions) + + Ok(layout.fragment) } } +/// Turn an iterator 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) -> impl Iterator { + 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 { diff --git a/tests/ref/bugs/grid-1.png b/tests/ref/bugs/grid-1.png index 6fd950ad3..1ed227e91 100644 Binary files a/tests/ref/bugs/grid-1.png and b/tests/ref/bugs/grid-1.png differ diff --git a/tests/ref/compiler/break-continue.png b/tests/ref/compiler/break-continue.png index 401b39409..e81c29fd8 100644 Binary files a/tests/ref/compiler/break-continue.png and b/tests/ref/compiler/break-continue.png differ diff --git a/tests/ref/compute/data.png b/tests/ref/compute/data.png index 678a1fd10..5b2320839 100644 Binary files a/tests/ref/compute/data.png and b/tests/ref/compute/data.png differ diff --git a/tests/ref/layout/grid-auto-shrink.png b/tests/ref/layout/grid-auto-shrink.png index 851e8b7ee..64b14f63c 100644 Binary files a/tests/ref/layout/grid-auto-shrink.png and b/tests/ref/layout/grid-auto-shrink.png differ diff --git a/tests/ref/layout/grid-rtl.png b/tests/ref/layout/grid-rtl.png index 8b8384801..d0cdb16ed 100644 Binary files a/tests/ref/layout/grid-rtl.png and b/tests/ref/layout/grid-rtl.png differ diff --git a/tests/ref/layout/par.png b/tests/ref/layout/par.png index 199c89197..2c19ffcf8 100644 Binary files a/tests/ref/layout/par.png and b/tests/ref/layout/par.png differ diff --git a/tests/ref/layout/table.png b/tests/ref/layout/table.png index f50f613b8..9c91b8922 100644 Binary files a/tests/ref/layout/table.png and b/tests/ref/layout/table.png differ diff --git a/tests/ref/math/content.png b/tests/ref/math/content.png index 46d5ef9b2..728e1a1ef 100644 Binary files a/tests/ref/math/content.png and b/tests/ref/math/content.png differ diff --git a/tests/ref/text/shift.png b/tests/ref/text/shift.png index 7e8d99a9d..1316fb779 100644 Binary files a/tests/ref/text/shift.png and b/tests/ref/text/shift.png differ diff --git a/tests/typ/layout/grid-rtl.typ b/tests/typ/layout/grid-rtl.typ index 31015097e..9c8714249 100644 --- a/tests/typ/layout/grid-rtl.typ +++ b/tests/typ/layout/grid-rtl.typ @@ -6,4 +6,4 @@ --- #set text(dir: rtl) -#table(columns: 2, gutter: 5pt)[A][B][C][D] +#table(columns: 2)[A][B][C][D]