diff options
| author | Laurenz <laurmaedje@gmail.com> | 2023-11-23 16:25:49 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2023-11-24 12:30:02 +0100 |
| commit | 7eebafa7837ec173a7b2064ae60fd45b5413d17c (patch) | |
| tree | b63b302b6d7747bcbb28571713745b9ca1aa83a4 /crates/typst-library/src/layout/grid.rs | |
| parent | 76e173b78b511b506b928c27ac360f75fa455747 (diff) | |
Merge `typst` and `typst-library`
Diffstat (limited to 'crates/typst-library/src/layout/grid.rs')
| -rw-r--r-- | crates/typst-library/src/layout/grid.rs | 734 |
1 files changed, 0 insertions, 734 deletions
diff --git a/crates/typst-library/src/layout/grid.rs b/crates/typst-library/src/layout/grid.rs deleted file mode 100644 index 1cde6564..00000000 --- a/crates/typst-library/src/layout/grid.rs +++ /dev/null @@ -1,734 +0,0 @@ -use smallvec::{smallvec, SmallVec}; - -use crate::layout::Sizing; -use crate::prelude::*; -use crate::text::TextElem; - -/// Arranges content in a grid. -/// -/// The grid element allows you to arrange content in a grid. You can define the -/// number of rows and columns, as well as the size of the gutters between them. -/// There are multiple sizing modes for columns and rows that can be used to -/// create complex layouts. -/// -/// The sizing of the grid is determined by the track sizes specified in the -/// arguments. Because each of the sizing parameters accepts the same values, we -/// will explain them just once, here. Each sizing argument accepts an array of -/// individual track sizes. A track size is either: -/// -/// - `{auto}`: The track will be sized to fit its contents. It will be at most -/// as large as the remaining space. If there is more than one `{auto}` track -/// which, and together they claim more than the available space, the `{auto}` -/// tracks will fairly distribute the available space among themselves. -/// -/// - A fixed or relative length (e.g. `{10pt}` or `{20% - 1cm}`): The track -/// will be exactly of this size. -/// -/// - A fractional length (e.g. `{1fr}`): Once all other tracks have been sized, -/// the remaining space will be divided among the fractional tracks according -/// to their fractions. For example, if there are two fractional tracks, each -/// with a fraction of `{1fr}`, they will each take up half of the remaining -/// space. -/// -/// To specify a single track, the array can be omitted in favor of a single -/// value. To specify multiple `{auto}` tracks, enter the number of tracks -/// instead of an array. For example, `columns:` `{3}` is equivalent to -/// `columns:` `{(auto, auto, auto)}`. -/// -/// # Examples -/// The example below demonstrates the different track sizing options. -/// -/// ```example -/// // We use `rect` to emphasize the -/// // area of cells. -/// #set rect( -/// inset: 8pt, -/// fill: rgb("e4e5ea"), -/// width: 100%, -/// ) -/// -/// #grid( -/// columns: (60pt, 1fr, 2fr), -/// rows: (auto, 60pt), -/// gutter: 3pt, -/// rect[Fixed width, auto height], -/// rect[1/3 of the remains], -/// rect[2/3 of the remains], -/// rect(height: 100%)[Fixed height], -/// image("tiger.jpg", height: 100%), -/// image("tiger.jpg", height: 100%), -/// ) -/// ``` -/// -/// You can also [spread]($arguments/#spreading) an array of strings or content -/// into a grid to populate its cells. -/// -/// ```example -/// #grid( -/// columns: 5, -/// gutter: 5pt, -/// ..range(25).map(str) -/// ) -/// ``` -#[elem(Layout)] -pub struct GridElem { - /// The column sizes. - /// - /// Either specify a track size array or provide an integer to create a grid - /// with that many `{auto}`-sized columns. Note that opposed to rows and - /// gutters, providing a single track size will only ever create a single - /// column. - #[borrowed] - pub columns: TrackSizings, - - /// The row sizes. - /// - /// If there are more cells than fit the defined rows, the last row is - /// repeated until there are no more cells. - #[borrowed] - pub rows: TrackSizings, - - /// The gaps between rows & columns. - /// - /// If there are more gutters than defined sizes, the last gutter is repeated. - #[external] - pub gutter: TrackSizings, - - /// The gaps between columns. Takes precedence over `gutter`. - #[parse( - let gutter = args.named("gutter")?; - args.named("column-gutter")?.or_else(|| gutter.clone()) - )] - #[borrowed] - pub column_gutter: TrackSizings, - - /// The gaps between rows. Takes precedence over `gutter`. - #[parse(args.named("row-gutter")?.or_else(|| gutter.clone()))] - #[borrowed] - pub row_gutter: TrackSizings, - - /// The contents of the grid cells. - /// - /// The cells are populated in row-major order. - #[variadic] - pub children: Vec<Content>, -} - -impl Layout for GridElem { - #[tracing::instrument(name = "GridElem::layout", skip_all)] - fn layout( - &self, - vt: &mut Vt, - styles: StyleChain, - regions: Regions, - ) -> SourceResult<Fragment> { - let columns = self.columns(styles); - let rows = self.rows(styles); - let column_gutter = self.column_gutter(styles); - let row_gutter = self.row_gutter(styles); - - // Prepare grid layout by unifying content and gutter tracks. - let layouter = GridLayouter::new( - Axes::new(&columns.0, &rows.0), - Axes::new(&column_gutter.0, &row_gutter.0), - &self.children, - regions, - styles, - self.span(), - ); - - // Measure the columns and layout the grid row-by-row. - Ok(layouter.layout(vt)?.fragment) - } -} - -/// Track sizing definitions. -#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)] -pub struct TrackSizings(pub SmallVec<[Sizing; 4]>); - -cast! { - TrackSizings, - self => self.0.into_value(), - sizing: Sizing => Self(smallvec![sizing]), - count: NonZeroUsize => Self(smallvec![Sizing::Auto; count.get()]), - values: Array => Self(values.into_iter().map(Value::cast).collect::<StrResult<_>>()?), -} - -/// Performs grid layout. -pub struct GridLayouter<'a> { - /// The grid cells. - cells: &'a [Content], - /// Whether this is an RTL grid. - is_rtl: bool, - /// Whether this grid has gutters. - has_gutter: bool, - /// The column tracks including gutter tracks. - cols: Vec<Sizing>, - /// The row tracks including gutter tracks. - rows: Vec<Sizing>, - /// The regions to layout children into. - regions: Regions<'a>, - /// The inherited styles. - styles: StyleChain<'a>, - /// Resolved column sizes. - rcols: Vec<Abs>, - /// The sum of `rcols`. - width: Abs, - /// Resolve row sizes, by region. - rrows: Vec<Vec<RowPiece>>, - /// Rows in the current region. - lrows: Vec<Row>, - /// The initial size of the current region before we started subtracting. - initial: Size, - /// Frames for finished regions. - finished: Vec<Frame>, - /// The span of the grid element. - span: Span, -} - -/// 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<Abs>, - /// The heights of the resulting rows segments, by region. - pub rows: Vec<Vec<RowPiece>>, -} - -/// 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 with y index. - Frame(Frame, usize), - /// Fractional row with y index. - Fr(Fr, usize), -} - -impl<'a> GridLayouter<'a> { - /// Create a new grid layouter. - /// - /// This prepares grid layout by unifying content and gutter tracks. - pub fn new( - tracks: Axes<&[Sizing]>, - gutter: Axes<&[Sizing]>, - cells: &'a [Content], - regions: Regions<'a>, - styles: StyleChain<'a>, - span: Span, - ) -> Self { - let mut cols = vec![]; - let mut rows = vec![]; - - // Number of content columns: Always at least one. - let c = tracks.x.len().max(1); - - // Number of content rows: At least as many as given, but also at least - // as many as needed to place each item. - let r = { - let len = cells.len(); - let given = tracks.y.len(); - let needed = len / c + (len % c).clamp(0, 1); - given.max(needed) - }; - - let has_gutter = gutter.any(|tracks| !tracks.is_empty()); - let auto = Sizing::Auto; - let zero = Sizing::Rel(Rel::zero()); - let get_or = |tracks: &[_], idx, default| { - tracks.get(idx).or(tracks.last()).copied().unwrap_or(default) - }; - - // Collect content and gutter columns. - for x in 0..c { - cols.push(get_or(tracks.x, x, auto)); - if has_gutter { - cols.push(get_or(gutter.x, x, zero)); - } - } - - // Collect content and gutter rows. - for y in 0..r { - rows.push(get_or(tracks.y, y, auto)); - if has_gutter { - rows.push(get_or(gutter.y, y, zero)); - } - } - - // Remove superfluous gutter tracks. - if has_gutter { - cols.pop(); - rows.pop(); - } - - // Reverse for RTL. - let is_rtl = TextElem::dir_in(styles) == Dir::RTL; - if is_rtl { - cols.reverse(); - } - - // We use these regions for auto row measurement. Since at that moment, - // columns are already sized, we can enable horizontal expansion. - let mut regions = regions; - regions.expand = Axes::new(true, false); - - Self { - cells, - is_rtl, - has_gutter, - rows, - regions, - styles, - rcols: vec![Abs::zero(); cols.len()], - cols, - width: Abs::zero(), - rrows: vec![], - lrows: vec![], - initial: regions.size, - finished: vec![], - span, - } - } - - /// Determines the columns sizes and then layouts the grid row-by-row. - pub fn layout(mut self, vt: &mut Vt) -> SourceResult<GridLayout> { - self.measure_columns(vt)?; - - for y in 0..self.rows.len() { - // Skip to next region if current one is full, but only for content - // rows, not for gutter rows. - if self.regions.is_full() && (!self.has_gutter || y % 2 == 0) { - self.finish_region(vt)?; - } - - match self.rows[y] { - Sizing::Auto => self.layout_auto_row(vt, y)?, - Sizing::Rel(v) => self.layout_relative_row(vt, v, y)?, - Sizing::Fr(v) => self.lrows.push(Row::Fr(v, y)), - } - } - - self.finish_region(vt)?; - - Ok(GridLayout { - fragment: Fragment::frames(self.finished), - cols: self.rcols, - rows: self.rrows, - }) - } - - /// Determine all column sizes. - #[tracing::instrument(name = "GridLayouter::measure_columns", skip_all)] - fn measure_columns(&mut self, vt: &mut Vt) -> SourceResult<()> { - // Sum of sizes of resolved relative tracks. - let mut rel = Abs::zero(); - - // Sum of fractions of all fractional tracks. - let mut fr = Fr::zero(); - - // Resolve the size of all relative columns and compute the sum of all - // fractional tracks. - for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) { - match col { - Sizing::Auto => {} - Sizing::Rel(v) => { - let resolved = - v.resolve(self.styles).relative_to(self.regions.base().x); - *rcol = resolved; - rel += resolved; - } - Sizing::Fr(v) => fr += v, - } - } - - // Size that is not used by fixed-size columns. - let available = self.regions.size.x - rel; - if available >= Abs::zero() { - // Determine size of auto columns. - let (auto, count) = self.measure_auto_columns(vt, available)?; - - // If there is remaining space, distribute it to fractional columns, - // otherwise shrink auto columns. - let remaining = available - auto; - if remaining >= Abs::zero() { - self.grow_fractional_columns(remaining, fr); - } else { - self.shrink_auto_columns(available, count); - } - } - - // Sum up the resolved column sizes once here. - self.width = self.rcols.iter().sum(); - - Ok(()) - } - - /// Measure the size that is available to auto columns. - fn measure_auto_columns( - &mut self, - vt: &mut Vt, - available: Abs, - ) -> SourceResult<(Abs, usize)> { - let mut auto = Abs::zero(); - let mut count = 0; - - // Determine size of auto columns by laying out all cells in those - // columns, measuring them and finding the largest one. - for (x, &col) in self.cols.iter().enumerate() { - if col != Sizing::Auto { - continue; - } - - let mut resolved = Abs::zero(); - for y in 0..self.rows.len() { - if let Some(cell) = self.cell(x, y) { - // For relative rows, we can already resolve the correct - // base and for auto and fr we could only guess anyway. - let height = match self.rows[y] { - Sizing::Rel(v) => { - v.resolve(self.styles).relative_to(self.regions.base().y) - } - _ => self.regions.base().y, - }; - - let size = Size::new(available, height); - let pod = Regions::one(size, Axes::splat(false)); - let frame = cell.measure(vt, self.styles, pod)?.into_frame(); - resolved.set_max(frame.width()); - } - } - - self.rcols[x] = resolved; - auto += resolved; - count += 1; - } - - Ok((auto, count)) - } - - /// Distribute remaining space to fractional columns. - fn grow_fractional_columns(&mut self, remaining: Abs, fr: Fr) { - if fr.is_zero() { - return; - } - - for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) { - if let Sizing::Fr(v) = col { - *rcol = v.share(fr, remaining); - } - } - } - - /// Redistribute space to auto columns so that each gets a fair share. - fn shrink_auto_columns(&mut self, available: Abs, count: usize) { - let mut last; - let mut fair = -Abs::inf(); - let mut redistribute = available; - let mut overlarge = count; - let mut changed = true; - - // Iteratively remove columns that don't need to be shrunk. - while changed && overlarge > 0 { - changed = false; - last = fair; - fair = redistribute / (overlarge as f64); - - for (&col, &rcol) in self.cols.iter().zip(&self.rcols) { - // Remove an auto column if it is not overlarge (rcol <= fair), - // but also hasn't already been removed (rcol > last). - if col == Sizing::Auto && rcol <= fair && rcol > last { - redistribute -= rcol; - overlarge -= 1; - changed = true; - } - } - } - - // Redistribute space fairly among overlarge columns. - for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) { - if col == Sizing::Auto && *rcol > fair { - *rcol = fair; - } - } - } - - /// Layout a row with automatic height. Such a row may break across multiple - /// regions. - fn layout_auto_row(&mut self, vt: &mut Vt, y: usize) -> SourceResult<()> { - // Determine the size for each region of the row. If the first region - // ends up empty for some column, skip the region and remeasure. - let mut resolved = match self.measure_auto_row(vt, y, true)? { - Some(resolved) => resolved, - None => { - self.finish_region(vt)?; - self.measure_auto_row(vt, y, false)?.unwrap() - } - }; - - // Nothing to layout. - if resolved.is_empty() { - return Ok(()); - } - - // Layout into a single region. - if let &[first] = resolved.as_slice() { - let frame = self.layout_single_row(vt, first, y)?; - self.push_row(frame, y); - return Ok(()); - } - - // Expand all but the last region. - // Skip the first region if the space is eaten up by an fr row. - let len = resolved.len(); - for (region, target) in self - .regions - .iter() - .zip(&mut resolved[..len - 1]) - .skip(self.lrows.iter().any(|row| matches!(row, Row::Fr(..))) as usize) - { - target.set_max(region.y); - } - - // Layout into multiple regions. - let fragment = self.layout_multi_row(vt, &resolved, y)?; - let len = fragment.len(); - for (i, frame) in fragment.into_iter().enumerate() { - self.push_row(frame, y); - if i + 1 < len { - self.finish_region(vt)?; - } - } - - Ok(()) - } - - /// Measure the regions sizes of an auto row. The option is always `Some(_)` - /// if `can_skip` is false. - fn measure_auto_row( - &mut self, - vt: &mut Vt, - y: usize, - can_skip: bool, - ) -> SourceResult<Option<Vec<Abs>>> { - let mut resolved: Vec<Abs> = vec![]; - - for (x, &rcol) in self.rcols.iter().enumerate() { - if let Some(cell) = self.cell(x, y) { - let mut pod = self.regions; - pod.size.x = rcol; - - let frames = cell.measure(vt, self.styles, pod)?.into_frames(); - - // Skip the first region if one cell in it is empty. Then, - // remeasure. - if let [first, rest @ ..] = frames.as_slice() { - if can_skip - && first.is_empty() - && rest.iter().any(|frame| !frame.is_empty()) - { - return Ok(None); - } - } - - let mut sizes = frames.iter().map(|frame| frame.height()); - for (target, size) in resolved.iter_mut().zip(&mut sizes) { - target.set_max(size); - } - - // New heights are maximal by virtue of being new. Note that - // this extend only uses the rest of the sizes iterator. - resolved.extend(sizes); - } - } - - Ok(Some(resolved)) - } - - /// Layout a row with relative height. Such a row cannot break across - /// multiple regions, but it may force a region break. - fn layout_relative_row( - &mut self, - vt: &mut Vt, - v: Rel<Length>, - y: usize, - ) -> SourceResult<()> { - let resolved = v.resolve(self.styles).relative_to(self.regions.base().y); - let frame = self.layout_single_row(vt, resolved, y)?; - - // Skip to fitting region. - let height = frame.height(); - while !self.regions.size.y.fits(height) && !self.regions.in_last() { - self.finish_region(vt)?; - - // Don't skip multiple regions for gutter and don't push a row. - if self.has_gutter && y % 2 == 1 { - return Ok(()); - } - } - - self.push_row(frame, y); - - Ok(()) - } - - /// Layout a row with fixed height and return its frame. - fn layout_single_row( - &mut self, - vt: &mut Vt, - height: Abs, - y: usize, - ) -> SourceResult<Frame> { - if !height.is_finite() { - bail!(error!(self.span, "cannot create grid with infinite height")); - } - - let mut output = Frame::soft(Size::new(self.width, height)); - let mut pos = Point::zero(); - - for (x, &rcol) in self.rcols.iter().enumerate() { - if let Some(cell) = self.cell(x, y) { - let size = Size::new(rcol, height); - let mut pod = Regions::one(size, Axes::splat(true)); - if self.rows[y] == Sizing::Auto { - pod.full = self.regions.full; - } - let frame = cell.layout(vt, self.styles, pod)?.into_frame(); - output.push_frame(pos, frame); - } - - pos.x += rcol; - } - - Ok(output) - } - - /// Layout a row spanning multiple regions. - fn layout_multi_row( - &mut self, - vt: &mut Vt, - heights: &[Abs], - y: usize, - ) -> SourceResult<Fragment> { - // Prepare frames. - let mut outputs: Vec<_> = heights - .iter() - .map(|&h| Frame::soft(Size::new(self.width, h))) - .collect(); - - // Prepare regions. - let size = Size::new(self.width, heights[0]); - let mut pod = Regions::one(size, Axes::splat(true)); - pod.full = self.regions.full; - pod.backlog = &heights[1..]; - - // Layout the row. - let mut pos = Point::zero(); - for (x, &rcol) in self.rcols.iter().enumerate() { - if let Some(cell) = self.cell(x, y) { - pod.size.x = rcol; - - // Push the layouted frames into the individual output frames. - let fragment = cell.layout(vt, self.styles, pod)?; - for (output, frame) in outputs.iter_mut().zip(fragment) { - output.push_frame(pos, frame); - } - } - - pos.x += rcol; - } - - Ok(Fragment::frames(outputs)) - } - - /// Push a row frame into the current region. - fn push_row(&mut self, frame: Frame, y: usize) { - self.regions.size.y -= frame.height(); - self.lrows.push(Row::Frame(frame, y)); - } - - /// Finish rows for one region. - fn finish_region(&mut self, vt: &mut Vt) -> SourceResult<()> { - // Determine the height of existing rows in the region. - let mut used = Abs::zero(); - let mut fr = Fr::zero(); - for row in &self.lrows { - match row { - Row::Frame(frame, _) => used += frame.height(), - Row::Fr(v, _) => fr += *v, - } - } - - // Determine the size of the grid in this region, expanding fully if - // there are fr rows. - let mut size = Size::new(self.width, used).min(self.initial); - if fr.get() > 0.0 && self.initial.y.is_finite() { - size.y = self.initial.y; - } - - // The frame for the region. - let mut output = Frame::soft(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, y) = match row { - Row::Frame(frame, y) => (frame, y), - Row::Fr(v, y) => { - let remaining = self.regions.full - used; - let height = v.share(fr, remaining); - (self.layout_single_row(vt, 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.rrows.push(rrows); - self.regions.next(); - self.initial = self.regions.size; - - Ok(()) - } - - /// Get the content of the cell in column `x` and row `y`. - /// - /// Returns `None` if it's a gutter cell. - #[track_caller] - fn cell(&self, mut x: usize, y: usize) -> Option<&'a Content> { - assert!(x < self.cols.len()); - assert!(y < self.rows.len()); - - // Columns are reorder, but the cell slice is not. - if self.is_rtl { - x = self.cols.len() - 1 - x; - } - - if self.has_gutter { - // Even columns and rows are children, odd ones are gutter. - if x % 2 == 0 && y % 2 == 0 { - let c = 1 + self.cols.len() / 2; - self.cells.get((y / 2) * c + x / 2) - } else { - None - } - } else { - let c = self.cols.len(); - self.cells.get(y * c + x) - } - } -} |
