diff --git a/src/geom/length.rs b/src/geom/length.rs index 21843d3e8..ecfe56163 100644 --- a/src/geom/length.rs +++ b/src/geom/length.rs @@ -18,6 +18,11 @@ impl Length { Self { raw: N64::from(0.0) } } + /// The inifinite length. + pub fn inf() -> Self { + Self { raw: N64::from(f64::INFINITY) } + } + /// Create a length from a number of points. pub fn pt(pt: f64) -> Self { Self::with_unit(pt, LengthUnit::Pt) @@ -203,6 +208,12 @@ impl Sum for Length { } } +impl<'a> Sum<&'a Length> for Length { + fn sum>(iter: I) -> Self { + iter.copied().fold(Length::zero(), Add::add) + } +} + /// Different units of length measurement. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] pub enum LengthUnit { diff --git a/src/geom/size.rs b/src/geom/size.rs index 1c411fb30..4b94d0aec 100644 --- a/src/geom/size.rs +++ b/src/geom/size.rs @@ -57,6 +57,13 @@ impl Size { SpecAxis::Vertical => Gen::new(self.width, self.height), } } + + /// Find the largest contained size that satisfies the given `aspect` ratio. + pub fn with_aspect(self, aspect: f64) -> Self { + let width = self.width.min(aspect * self.height); + let height = width / aspect; + Size::new(width, height) + } } impl Get for Size { diff --git a/src/layout/frame.rs b/src/layout/frame.rs index 119aeea65..55f7f99a7 100644 --- a/src/layout/frame.rs +++ b/src/layout/frame.rs @@ -6,7 +6,7 @@ use crate::image::ImageId; use serde::{Deserialize, Serialize}; /// A finished layout with elements at fixed positions. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Frame { /// The size of the frame. pub size: Size, @@ -31,8 +31,12 @@ impl Frame { /// Add all elements of another frame, placing them relative to the given /// position. pub fn push_frame(&mut self, pos: Point, subframe: Self) { - for (subpos, element) in subframe.elements { - self.push(pos + subpos, element); + if pos == Point::zero() && self.elements.is_empty() { + self.elements = subframe.elements; + } else { + for (subpos, element) in subframe.elements { + self.push(pos + subpos, element); + } } } } diff --git a/src/layout/grid.rs b/src/layout/grid.rs index 6ea99476c..52e07d0df 100644 --- a/src/layout/grid.rs +++ b/src/layout/grid.rs @@ -16,9 +16,27 @@ pub struct GridNode { pub children: Vec, } +/// Defines how to size a grid cell along an axis. +#[derive(Debug, Copy, Clone, PartialEq, Hash)] +pub enum TrackSizing { + /// Fit the cell to its contents. + Auto, + /// A length stated in absolute values and fractions of the parent's size. + Linear(Linear), + /// A length that is the fraction of the remaining free space in the parent. + Fractional(Fractional), +} + impl Layout for GridNode { fn layout(&self, ctx: &mut LayoutContext, regions: &Regions) -> Vec { - GridLayouter::new(self, regions.clone()).layout(ctx) + // Prepare grid layout by unifying content and gutter tracks. + let mut layouter = GridLayouter::new(self, regions.clone()); + + // Determine all column sizes. + layouter.measure_columns(ctx); + + // Layout the grid row-by-row. + layouter.layout(ctx) } } @@ -28,125 +46,123 @@ impl From for AnyNode { } } -#[derive(Debug)] +/// Performs grid layout. struct GridLayouter<'a> { + /// The axis of the cross direction. cross: SpecAxis, + /// The axis of the main direction. main: SpecAxis, + /// The column tracks including gutter tracks. cols: Vec, + /// The row tracks including gutter tracks. rows: Vec, - cells: Vec>, + /// The children of the grid. + children: &'a [AnyNode], + /// The region to layout into. regions: Regions, + /// Resolved column sizes. rcols: Vec, - rrows: Vec<(usize, Option, Option>>)>, + /// The full main size of the current region. + full: Length, + /// The used-up size of the current region. The cross size is determined + /// once after columns are resolved and not touched again. + used: Gen, + /// The sum of fractional ratios in the current region. + fr: Fractional, + /// Rows in the current region. + lrows: Vec, + /// Frames for finished regions. finished: Vec, } -#[derive(Debug)] -enum Cell<'a> { - Node(&'a AnyNode), - Gutter, +/// Produced by initial row layout, auto and linear rows are already finished, +/// fractional rows not yet. +enum Row { + /// Finished row frame of auto or linear row. + Frame(Frame), + /// Ratio of a fractional row and y index of the track. + Fr(Fractional, usize), } impl<'a> GridLayouter<'a> { - fn new(grid: &'a GridNode, regions: Regions) -> Self { - let cross = grid.dirs.cross.axis(); - let main = grid.dirs.main.axis(); - + /// Prepare grid layout by unifying content and gutter tracks. + fn new(grid: &'a GridNode, mut regions: Regions) -> Self { let mut cols = vec![]; let mut rows = vec![]; - let mut cells = vec![]; - // A grid always needs to have at least one column. - let content_cols = grid.tracks.cross.len().max(1); + // Number of content columns: Always at least one. + let c = grid.tracks.cross.len().max(1); - // Create at least as many rows as specified and also at least as many - // as necessary to place each item. - let content_rows = { + // 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 = grid.children.len(); - let specified = grid.tracks.main.len(); - let necessary = len / content_cols + (len % content_cols).clamp(0, 1); - specified.max(necessary) + let given = grid.tracks.main.len(); + let needed = len / c + (len % c).clamp(0, 1); + given.max(needed) }; - // Collect the track sizing for all columns, including gutter columns. - for i in 0 .. content_cols { - cols.push(grid.tracks.cross.get_or_last(i)); - if i < content_cols - 1 { - cols.push(grid.gutter.cross.get_or_last(i)); - } + let auto = TrackSizing::Auto; + let zero = TrackSizing::Linear(Linear::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(&grid.tracks.cross, x, auto)); + cols.push(get_or(&grid.gutter.cross, x, zero)); } - // Collect the track sizing for all rows, including gutter rows. - for i in 0 .. content_rows { - rows.push(grid.tracks.main.get_or_last(i)); - if i < content_rows - 1 { - rows.push(grid.gutter.main.get_or_last(i)); - } + // Collect content and gutter rows. + for y in 0 .. r { + rows.push(get_or(&grid.tracks.main, y, auto)); + rows.push(get_or(&grid.gutter.main, y, zero)); } - // Build up the matrix of cells, including gutter cells. - for (i, item) in grid.children.iter().enumerate() { - cells.push(Cell::Node(item)); + // Remove superfluous gutter tracks. + cols.pop(); + rows.pop(); - let row = i / content_cols; - let col = i % content_cols; + let cross = grid.dirs.cross.axis(); + let main = grid.dirs.main.axis(); + let full = regions.current.get(main); + let rcols = vec![Length::zero(); cols.len()]; - if col < content_cols - 1 { - // Push gutter after each child. - cells.push(Cell::Gutter); - } else if row < content_rows - 1 { - // Except for the last child of each row. - // There we push a gutter row. - for _ in 0 .. cols.len() { - cells.push(Cell::Gutter); - } - } - } - - // Fill the thing up. - while cells.len() < cols.len() * rows.len() { - cells.push(Cell::Gutter); - } + // We use the regions only for auto row measurement. + regions.expand = Gen::new(true, false).to_spec(main); Self { cross, main, cols, rows, - cells, + children: &grid.children, regions, - rcols: vec![], - rrows: vec![], + rcols, + lrows: vec![], + full, + used: Gen::zero(), + fr: Fractional::zero(), finished: vec![], } } - fn layout(mut self, ctx: &mut LayoutContext) -> Vec { - self.rcols = self.resolve_columns(ctx); - self.layout_rows(ctx); - self.finished - } + /// Determine all column sizes. + fn measure_columns(&mut self, ctx: &mut LayoutContext) { + // Sum of sizes of resolved linear tracks. + let mut linear = Length::zero(); - /// Determine the size of all columns. - fn resolve_columns(&self, ctx: &mut LayoutContext) -> Vec { + // Sum of fractions of all fractional tracks. + let mut fr = Fractional::zero(); + + // Generic version of current and base size. let current = self.regions.current.to_gen(self.main); let base = self.regions.base.to_gen(self.main); - // Prepare vector for resolved column lengths. - let mut rcols = vec![Length::zero(); self.cols.len()]; - - // - Sum of sizes of resolved linear tracks, - // - Sum of fractions of all fractional tracks, - // - Sum of sizes of resolved (through layouting) auto tracks, - // - Number of auto tracks. - let mut linear = Length::zero(); - let mut fr = Fractional::zero(); - let mut auto = Length::zero(); - let mut auto_count = 0; - - // Resolve size of linear columns and compute the sum of all fractional - // tracks. - for (&col, rcol) in self.cols.iter().zip(&mut rcols) { + // Resolve the size of all linear columns and compute the sum of all + // fractional tracks. + for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) { match col { TrackSizing::Auto => {} TrackSizing::Linear(v) => { @@ -158,379 +174,303 @@ impl<'a> GridLayouter<'a> { } } - // Size available to auto columns (not used by fixed-size columns). + // Size that is not used by fixed-size columns. let available = current.cross - linear; - if available <= Length::zero() { - return rcols; - } + if available >= Length::zero() { + // Determine size of auto columns. + let (auto, count) = self.measure_auto_columns(ctx, available); - // Resolve size of auto columns by laying out all cells in those - // columns, measuring them and finding the largest one. - for (x, (&col, rcol)) in self.cols.iter().zip(&mut rcols).enumerate() { - if col == TrackSizing::Auto { - let mut resolved = Length::zero(); - - for (y, &row) in self.rows.iter().enumerate() { - if let Cell::Node(node) = self.get(x, y) { - // Set the correct main size if the row is fixed-size. - let main = match row { - TrackSizing::Linear(v) => v.resolve(base.main), - _ => current.main, - }; - - let size = Gen::new(available, main).to_size(self.main); - let regions = Regions::one(size, Spec::splat(false)); - let frame = node.layout(ctx, ®ions).remove(0); - resolved = resolved.max(frame.size.get(self.cross)) - } - } - - *rcol = resolved; - auto += resolved; - auto_count += 1; + // If there is remaining space, distribute it to fractional columns, + // otherwise shrink auto columns. + let remaining = available - auto; + if remaining >= Length::zero() { + self.grow_fractional_columns(remaining, fr); + } else { + self.shrink_auto_columns(available, count); } } - // If there is remaining space, distribute it to fractional columns, - // otherwise shrink auto columns. - let remaining = available - auto; - if remaining >= Length::zero() { - for (&col, rcol) in self.cols.iter().zip(&mut rcols) { - if let TrackSizing::Fractional(v) = col { - let ratio = v / fr; - if ratio.is_finite() { - *rcol = ratio * remaining; - } - } - } - } else { - // The fair share each auto column may have. - let fair = available / auto_count as f64; - - // The number of overlarge auto columns and the space that will be - // equally redistributed to them. - let mut overlarge: usize = 0; - let mut redistribute = available; - - for (&col, rcol) in self.cols.iter().zip(&mut rcols) { - if col == TrackSizing::Auto { - if *rcol > fair { - overlarge += 1; - } else { - redistribute -= *rcol; - } - } - } - - // Redistribute the space equally. - let share = redistribute / overlarge as f64; - if overlarge > 0 { - for (&col, rcol) in self.cols.iter().zip(&mut rcols) { - if col == TrackSizing::Auto && *rcol > fair { - *rcol = share; - } - } - } - } - - rcols + self.used.cross = self.rcols.iter().sum(); } - fn layout_rows(&mut self, ctx: &mut LayoutContext) { - // Determine non-`fr` row heights - let mut total_frs = 0.0; - let mut current = self.regions.current.get(self.main); - - for y in 0 .. self.rows.len() { - let resolved = match self.rows[y] { - TrackSizing::Linear(l) => { - (Some(l.resolve(self.regions.base.get(self.main))), None) - } - TrackSizing::Auto => { - let mut max = Length::zero(); - let mut local_max = max; - let mut multi_region = false; - let mut last_size = vec![]; - for (x, &col_size) in self.rcols.iter().enumerate() { - if let Cell::Node(node) = self.get(x, y) { - let colsize = Gen::new(col_size, current).to_size(self.main); - - let mut regions = self.regions.map(|mut s| { - *s.get_mut(self.cross) = col_size; - s - }); - - regions.base = colsize; - regions.current = colsize; - regions.expand = Spec::splat(false); - - let mut frames = node.layout(ctx, ®ions); - multi_region |= frames.len() > 1; - last_size.push(( - frames.len() - 1, - frames.last().unwrap().size.get(self.main), - )); - let frame = frames.remove(0); - local_max = local_max.max(frame.size.get(self.main)); - - if !multi_region { - max = local_max; - } - } else { - last_size.push((0, Length::zero())) - } - } - - let overshoot = if multi_region { - self.rrows.push((y, Some(local_max), None)); - let res = self.finish_region(ctx, total_frs, Some(last_size)); - max = if let Some(overflow) = res.as_ref() { - overflow - .iter() - .filter_map(|x| x.as_ref()) - .map(|x| x.size.get(self.main)) - .max() - .unwrap_or(Length::zero()) - } else { - local_max - }; - - current = self.regions.current.get(self.main); - total_frs = 0.0; - if res.is_none() { - continue; - } - - res - } else { - None - }; - - // If multi-region results: finish_regions, returning - // the last non-set frames. - (Some(max), overshoot) - } - TrackSizing::Fractional(f) => { - total_frs += f.get(); - (None, None) - } - }; - - if let (Some(resolved), _) = resolved { - while !current.fits(resolved) && !self.regions.in_full_last() { - self.finish_region(ctx, total_frs, None); - current = self.regions.current.get(self.main); - total_frs = 0.0; - } - current -= resolved; - } - - self.rrows.push((y, resolved.0, resolved.1)); - } - - self.finish_region(ctx, total_frs, None); - } - - fn finish_region( + /// Measure the size that is available to auto columns. + fn measure_auto_columns( &mut self, ctx: &mut LayoutContext, - total_frs: f64, - multiregion_sizing: Option>, - ) -> Option>> { - if self.rrows.is_empty() { - return None; - } + available: Length, + ) -> (Length, usize) { + let mut auto = Length::zero(); + let mut count = 0; - let mut pos = Gen::splat(Length::zero()); - let frame = Frame::new(Size::zero(), Length::zero()); - let mut total_cross = Length::zero(); - let mut total_main = Length::zero(); - let mut max_regions = 0; - let mut collected_frames = if multiregion_sizing.is_some() { - Some(vec![None; self.rcols.len()]) - } else { - None - }; - - self.finished.push(frame); - - let frame_len = self.finished.len(); - - let total_row_height: Length = self.rrows.iter().filter_map(|(_, x, _)| *x).sum(); - - for &(y, h, ref layouted) in self.rrows.iter().as_ref() { - let last = self.rrows.last().map_or(false, |(o, _, _)| &y == o); - let available = self.regions.current.get(self.main) - total_row_height; - let h = if let Some(len) = h { - len - } else if let TrackSizing::Fractional(f) = self.rows[y] { - if total_frs > 0.0 { - let res = available * (f.get() / total_frs); - if res.is_finite() { res } else { Length::zero() } - } else { - Length::zero() - } - } else { - unreachable!("non-fractional tracks are already resolved"); - }; - total_main += h; - - if let Some(layouted) = layouted { - for (col_index, frame) in layouted.into_iter().enumerate() { - if let Some(frame) = frame { - self.finished - .get_mut(frame_len - 1) - .unwrap() - .push_frame(pos.to_point(self.main), frame.clone()); - } - pos.cross += self.rcols[col_index]; - } - } else { - let mut overshoot_columns = vec![]; - for (x, &w) in self.rcols.iter().enumerate() { - let element = self.get(x, y); - - if y == 0 { - total_cross += w; - } - - if let Cell::Node(n) = element { - let region_size = Gen::new(w, h).to_size(self.main); - let regions = if last { - if let Some(last_sizes) = multiregion_sizing.as_ref() { - let mut regions = self.regions.map(|mut s| { - *s.get_mut(self.cross) = w; - s - }); - - regions.base = region_size; - regions.current = region_size; - regions.expand = Spec::splat(true); - - let (last_region, last_size) = last_sizes[x]; - regions.unique_regions(last_region + 1); - *regions - .nth_mut(last_region) - .unwrap() - .get_mut(self.main) = last_size; - regions - } else { - Regions::one(region_size, Spec::splat(true)) - } - } else { - Regions::one(region_size, Spec::splat(true)) - }; - let mut items = n.layout(ctx, ®ions); - let item = items.remove(0); - - if last && multiregion_sizing.is_some() { - max_regions = max_regions.max(items.len()); - overshoot_columns.push((x, items)); - } else { - assert_eq!(items.len(), 0); - } - - self.finished - .get_mut(frame_len - 1) - .unwrap() - .push_frame(pos.to_point(self.main), item); - } - - pos.cross += w; - } - - if overshoot_columns.iter().any(|(_, items)| !items.is_empty()) { - for (x, col) in overshoot_columns { - let mut cross_offset = Length::zero(); - for col in 0 .. x { - cross_offset += self.rcols[col]; - } - - - let collected_frames = collected_frames.as_mut().unwrap(); - *collected_frames.get_mut(x).unwrap() = - col.get(max_regions - 1).cloned(); - - for (cell_index, subcell) in col.into_iter().enumerate() { - if cell_index >= max_regions - 1 { - continue; - } - let frame = if let Some(frame) = - self.finished.get_mut(frame_len + cell_index) - { - frame - } else { - let frame = Frame::new(Size::zero(), Length::zero()); - // The previous frame always exists: either the - // last iteration created it or it is the normal - // frame. - self.finished.push(frame); - self.finished.last_mut().unwrap() - }; - let pos = Gen::new(cross_offset, Length::zero()); - frame - .size - .get_mut(self.cross) - .set_max(pos.cross + subcell.size.get(self.cross)); - frame - .size - .get_mut(self.main) - .set_max(subcell.size.get(self.main)); - frame.baseline = frame.size.height; - frame.push_frame(pos.to_point(self.main), subcell); - } - } - } + // 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 != TrackSizing::Auto { + continue; } - pos.cross = Length::zero(); - pos.main += h; - } - - let frame = self.finished.get_mut(frame_len - 1).unwrap(); - frame.size = Gen::new(total_cross, total_main).to_size(self.main); - frame.baseline = frame.size.height; - - self.rrows.clear(); - for _ in 0 .. (max_regions.max(1)) { - self.regions.next(); - } - - if let Some(frames) = collected_frames.as_ref() { - if frames.iter().all(|i| i.is_none()) { - collected_frames = None; + let mut resolved = Length::zero(); + for node in (0 .. self.rows.len()).filter_map(|y| self.cell(x, y)) { + let size = Gen::new(available, Length::inf()).to_size(self.main); + let regions = Regions::one(size, Spec::splat(false)); + let frame = node.layout(ctx, ®ions).remove(0); + resolved.set_max(frame.size.get(self.cross)); } + + self.rcols[x] = resolved; + auto += resolved; + count += 1; } - collected_frames + (auto, count) } - fn get(&self, x: usize, y: usize) -> &Cell<'a> { + /// Distribute remaining space to fractional columns. + fn grow_fractional_columns(&mut self, remaining: Length, fr: Fractional) { + for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) { + if let TrackSizing::Fractional(v) = col { + let ratio = v / fr; + if ratio.is_finite() { + *rcol = ratio * remaining; + } + } + } + } + + /// Redistribute space to auto columns so that each gets a fair share. + fn shrink_auto_columns(&mut self, available: Length, count: usize) { + // The fair share each auto column may have. + let fair = available / count as f64; + + // The number of overlarge auto columns and the space that will be + // equally redistributed to them. + let mut overlarge: usize = 0; + let mut redistribute = available; + + // Find out the number of and space used by overlarge auto columns. + for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) { + if col == TrackSizing::Auto { + if *rcol > fair { + overlarge += 1; + } else { + redistribute -= *rcol; + } + } + } + + // Redistribute the space equally. + let share = redistribute / overlarge as f64; + for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) { + if col == TrackSizing::Auto && *rcol > fair { + *rcol = share; + } + } + } + + /// Layout the grid row-by-row. + fn layout(mut self, ctx: &mut LayoutContext) -> Vec { + for y in 0 .. self.rows.len() { + match self.rows[y] { + TrackSizing::Auto => { + self.layout_auto_row(ctx, y); + } + TrackSizing::Linear(v) => { + let base = self.regions.base.get(self.main); + let resolved = v.resolve(base); + let frame = self.layout_single_row(ctx, resolved, y); + self.push_row(ctx, frame); + } + TrackSizing::Fractional(v) => { + self.fr += v; + self.lrows.push(Row::Fr(v, y)); + } + } + } + + self.finish_region(ctx); + self.finished + } + + /// Layout a row with automatic size along the main axis. Such a row may + /// break across multiple regions. + fn layout_auto_row(&mut self, ctx: &mut LayoutContext, y: usize) { + let mut first = Length::zero(); + let mut rest: Vec = vec![]; + + // Determine the size for each region of the row. + for (x, &rcol) in self.rcols.iter().enumerate() { + if let Some(node) = self.cell(x, y) { + let cross = self.cross; + self.regions.mutate(|size| *size.get_mut(cross) = rcol); + + let mut sizes = node + .layout(ctx, &self.regions) + .into_iter() + .map(|frame| frame.size.get(self.main)); + + if let Some(size) = sizes.next() { + first.set_max(size); + } + + for (resolved, size) in rest.iter_mut().zip(&mut sizes) { + resolved.set_max(size); + } + + rest.extend(sizes); + } + } + + // Layout the row. + if rest.is_empty() { + let frame = self.layout_single_row(ctx, first, y); + self.push_row(ctx, frame); + } else { + let frames = self.layout_multi_row(ctx, first, &rest, y); + for frame in frames { + self.push_row(ctx, frame); + } + } + } + + /// Layout a row with a fixed size along the main axis. + fn layout_single_row( + &self, + ctx: &mut LayoutContext, + length: Length, + y: usize, + ) -> Frame { + let size = self.to_size(length); + let mut output = Frame::new(size, size.height); + let mut pos = Gen::zero(); + + for (x, &rcol) in self.rcols.iter().enumerate() { + if let Some(node) = self.cell(x, y) { + let size = Gen::new(rcol, length).to_size(self.main); + let regions = Regions::one(size, Spec::splat(true)); + let frame = node.layout(ctx, ®ions).remove(0); + output.push_frame(pos.to_point(self.main), frame); + } + + pos.cross += rcol; + } + + output + } + + /// Layout a row spanning multiple regions. + fn layout_multi_row( + &self, + ctx: &mut LayoutContext, + first: Length, + rest: &[Length], + y: usize, + ) -> Vec { + // Prepare frames. + let mut outputs: Vec<_> = std::iter::once(first) + .chain(rest.iter().copied()) + .map(|v| self.to_size(v)) + .map(|size| Frame::new(size, size.height)) + .collect(); + + // Prepare regions. + let mut regions = Regions::one(self.to_size(first), Spec::splat(true)); + regions.backlog = rest.iter().rev().map(|&v| self.to_size(v)).collect(); + + // Layout the row. + let mut pos = Gen::zero(); + for (x, &rcol) in self.rcols.iter().enumerate() { + if let Some(node) = self.cell(x, y) { + regions.mutate(|size| *size.get_mut(self.cross) = rcol); + + // Push the layouted frames into the individual output frames. + let frames = node.layout(ctx, ®ions); + for (output, frame) in outputs.iter_mut().zip(frames) { + output.push_frame(pos.to_point(self.main), frame); + } + } + + pos.cross += rcol; + } + + outputs + } + + /// Push a row frame into the current or next fitting region, finishing + /// regions (including layouting fractional rows) if necessary. + fn push_row(&mut self, ctx: &mut LayoutContext, frame: Frame) { + let length = frame.size.get(self.main); + + // Skip to fitting region. + while !self.regions.current.get(self.main).fits(length) + && !self.regions.in_full_last() + { + self.finish_region(ctx); + } + + *self.regions.current.get_mut(self.main) -= length; + self.used.main += length; + self.lrows.push(Row::Frame(frame)); + } + + /// Finish rows for one region. + fn finish_region(&mut self, ctx: &mut LayoutContext) { + // Determine the size of the region. + let length = if self.fr.is_zero() { self.used.main } else { self.full }; + let size = self.to_size(length); + + // The frame for the region. + let mut output = Frame::new(size, size.height); + let mut pos = Gen::zero(); + + // Determine the remaining size for fractional rows. + let remaining = self.full - self.used.main; + + // Place finished rows and layout fractional rows. + for row in std::mem::take(&mut self.lrows) { + let frame = match row { + Row::Frame(frame) => frame, + Row::Fr(v, y) => { + let ratio = v / self.fr; + if remaining > Length::zero() && ratio.is_finite() { + let resolved = ratio * remaining; + self.layout_single_row(ctx, resolved, y) + } else { + continue; + } + } + }; + + let main = frame.size.get(self.main); + output.push_frame(pos.to_point(self.main), frame); + pos.main += main; + } + + self.regions.next(); + self.full = self.regions.current.get(self.main); + self.used.main = Length::zero(); + self.fr = Fractional::zero(); + self.finished.push(output); + } + + /// Get the node in the cell in column `x` and row `y`. + /// + /// Returns `None` if it's a gutter cell. + fn cell(&self, x: usize, y: usize) -> Option<&'a AnyNode> { assert!(x < self.cols.len()); assert!(y < self.rows.len()); - self.cells.get(y * self.cols.len() + x).unwrap() + + // 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.children.get((y / 2) * c + x / 2) + } else { + None + } + } + + /// Return a size where the cross axis spans the whole grid and the main + /// axis the given length. + fn to_size(&self, main_size: Length) -> Size { + Gen::new(self.used.cross, main_size).to_size(self.main) } } - -trait TracksExt { - /// Get the sizing for the track at the given `idx` or fallback to the - /// last defined track or `auto`. - fn get_or_last(&self, idx: usize) -> TrackSizing; -} - -impl TracksExt for Vec { - fn get_or_last(&self, idx: usize) -> TrackSizing { - self.get(idx).or(self.last()).copied().unwrap_or(TrackSizing::Auto) - } -} - -/// Defines how to size a grid cell along an axis. -#[derive(Debug, Copy, Clone, PartialEq, Hash)] -pub enum TrackSizing { - /// Fit the cell to its contents. - Auto, - /// A length stated in absolute values and fractions of the parent's size. - Linear(Linear), - /// A length that is the fraction of the remaining free space in the parent. - Fractional(Fractional), -} diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 6f536e20d..9c4222c83 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -23,8 +23,6 @@ use std::collections::HashMap; use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; -use decorum::N64; - use crate::cache::Cache; use crate::geom::*; use crate::loading::Loader; @@ -245,18 +243,14 @@ impl Regions { } } - /// Map the size of all regions. + /// Create new regions where all sizes are mapped with `f`. pub fn map(&self, mut f: F) -> Self where F: FnMut(Size) -> Size, { - Self { - current: f(self.current), - base: f(self.base), - backlog: self.backlog.iter().copied().map(|s| f(s)).collect(), - last: self.last.map(f), - expand: self.expand, - } + let mut regions = self.clone(); + regions.mutate(|s| *s = f(*s)); + regions } /// Whether `current` is a fully sized (untouched) copy of the last region. @@ -274,38 +268,14 @@ impl Regions { } } - /// Shrink `current` to ensure that the aspect ratio can be satisfied. - pub fn apply_aspect_ratio(&mut self, aspect: N64) { - let width = self.current.width.min(aspect.into_inner() * self.current.height); - let height = width / aspect.into_inner(); - self.current = Size::new(width, height); - } - - /// Appends new elements to the backlog such that the behavior of `next` - /// does not change. Panics when used with finite regions. - pub fn backlogify(&mut self, count: usize) { - for _ in 0 .. count { - self.backlog.push(self.last.unwrap()) - } - } - - /// Ensures that enough unique regions are present, including the current - /// and last ones. Panics when used with finite regions. - pub fn unique_regions(&mut self, count: usize) { - if self.backlog.len() < count.max(2) - 2 { - self.backlogify((count - 2) - self.backlog.len()); - } - } - - /// Returns a mutable reference to the size of the `n`th region. - pub fn nth_mut(&mut self, n: usize) -> Option<&mut Size> { - let backlog_len = self.backlog.len(); - if n == 0 { - Some(&mut self.current) - } else if n <= backlog_len { - self.backlog.get_mut(backlog_len - n) - } else { - self.last.as_mut() - } + /// Mutate all contained sizes in place. + pub fn mutate(&mut self, mut f: F) + where + F: FnMut(&mut Size), + { + f(&mut self.current); + f(&mut self.base); + self.last.as_mut().map(|x| f(x)); + self.backlog.iter_mut().for_each(f); } } diff --git a/src/layout/par.rs b/src/layout/par.rs index d29920400..40c364219 100644 --- a/src/layout/par.rs +++ b/src/layout/par.rs @@ -40,12 +40,12 @@ impl Layout for ParNode { // Find out the BiDi embedding levels. let bidi = BidiInfo::new(&text, Level::from_dir(self.dir)); - // Build a representation of the paragraph on which we can do - // linebreaking without layouting each and every line from scratch. - let layout = ParLayout::new(ctx, regions, self, bidi); + // Prepare paragraph layout by bulding a representation on which we can + // do line breaking without layouting each and every line from scratch. + let layouter = ParLayouter::new(self, ctx, regions, bidi); // Find suitable linebreaks. - layout.build(ctx, regions.clone(), self) + layouter.layout(ctx, regions.clone()) } } @@ -102,9 +102,11 @@ impl Debug for ParChild { /// A paragraph representation in which children are already layouted and text /// is separated into shapable runs. -struct ParLayout<'a> { +struct ParLayouter<'a> { /// The top-level direction. dir: Dir, + /// The line spacing. + line_spacing: Length, /// Bidirectional text embedding levels for the paragraph. bidi: BidiInfo<'a>, /// Layouted children and separated text runs. @@ -113,12 +115,12 @@ struct ParLayout<'a> { ranges: Vec, } -impl<'a> ParLayout<'a> { - /// Build a paragraph layout for the given node. +impl<'a> ParLayouter<'a> { + /// Prepare initial shaped text and layouted children. fn new( + par: &'a ParNode, ctx: &mut LayoutContext, regions: &Regions, - par: &'a ParNode, bidi: BidiInfo<'a>, ) -> Self { // Prepare an iterator over each child an the range it spans. @@ -142,26 +144,25 @@ impl<'a> ParLayout<'a> { } } ParChild::Any(ref node, align) => { - let mut frames = node.layout(ctx, regions).into_iter(); - let frame = frames.next().unwrap(); - assert!(frames.next().is_none()); + let frame = node.layout(ctx, regions).remove(0); items.push(ParItem::Frame(frame, align)); ranges.push(range); } } } - Self { dir: par.dir, bidi, items, ranges } + Self { + dir: par.dir, + line_spacing: par.line_spacing, + bidi, + items, + ranges, + } } /// Find first-fit line breaks and build the paragraph. - fn build( - self, - ctx: &mut LayoutContext, - regions: Regions, - par: &ParNode, - ) -> Vec { - let mut stack = LineStack::new(par.line_spacing, regions); + fn layout(self, ctx: &mut LayoutContext, regions: Regions) -> Vec { + let mut stack = LineStack::new(self.line_spacing, regions); // The current line attempt. // Invariant: Always fits into `stack.regions.current`. @@ -272,37 +273,41 @@ impl ParItem<'_> { } } -/// A simple layouter that stacks lines into regions. +/// Stacks lines on top of each other. struct LineStack<'a> { line_spacing: Length, regions: Regions, - finished: Vec, - lines: Vec>, size: Size, + lines: Vec>, + finished: Vec, } impl<'a> LineStack<'a> { + /// Create an empty line stack. fn new(line_spacing: Length, regions: Regions) -> Self { Self { line_spacing, regions, - finished: vec![], - lines: vec![], size: Size::zero(), + lines: vec![], + finished: vec![], } } + /// Push a new line into the stack. fn push(&mut self, line: LineLayout<'a>) { - self.size.height += line.size.height; + self.regions.current.height -= line.size.height + self.line_spacing; + self.size.width.set_max(line.size.width); + self.size.height += line.size.height; if !self.lines.is_empty() { self.size.height += self.line_spacing; } - self.regions.current.height -= line.size.height + self.line_spacing; self.lines.push(line); } + /// Finish the frame for one region. fn finish_region(&mut self, ctx: &LayoutContext) { if self.regions.expand.horizontal { self.size.width = self.regions.current.width; @@ -312,7 +317,7 @@ impl<'a> LineStack<'a> { let mut offset = Length::zero(); let mut first = true; - for line in std::mem::take(&mut self.lines) { + for line in self.lines.drain(..) { let frame = line.build(ctx, self.size.width); let pos = Point::new(Length::zero(), offset); @@ -325,11 +330,12 @@ impl<'a> LineStack<'a> { output.push_frame(pos, frame); } - self.finished.push(output); self.regions.next(); self.size = Size::zero(); + self.finished.push(output); } + /// Finish the last region and return the built frames. fn finish(mut self, ctx: &LayoutContext) -> Vec { self.finish_region(ctx); self.finished @@ -340,8 +346,10 @@ impl<'a> LineStack<'a> { /// paragraph's text. This type enables you to cheaply measure the size of a /// line in a range before comitting to building the line's frame. struct LineLayout<'a> { - /// The paragraph the line was created in. - par: &'a ParLayout<'a>, + /// The direction of the line. + dir: Dir, + /// Bidi information about the paragraph. + bidi: &'a BidiInfo<'a>, /// The range the line spans in the paragraph. line: Range, /// A reshaped text item if the line sliced up a text item at the start. @@ -363,7 +371,7 @@ struct LineLayout<'a> { impl<'a> LineLayout<'a> { /// Create a line which spans the given range. - fn new(ctx: &mut LayoutContext, par: &'a ParLayout<'a>, mut line: Range) -> Self { + fn new(ctx: &mut LayoutContext, par: &'a ParLayouter<'a>, mut line: Range) -> Self { // Find the items which bound the text range. let last_idx = par.find(line.end.saturating_sub(1)).unwrap(); let first_idx = if line.is_empty() { @@ -436,7 +444,8 @@ impl<'a> LineLayout<'a> { } Self { - par, + dir: par.dir, + bidi: &par.bidi, line, first, items, @@ -474,7 +483,7 @@ impl<'a> LineLayout<'a> { // FIXME: Ruler alignment for RTL. let pos = Point::new( - ruler.resolve(self.par.dir, offset .. free + offset), + ruler.resolve(self.dir, offset .. free + offset), self.baseline - frame.baseline, ); @@ -494,7 +503,6 @@ impl<'a> LineLayout<'a> { // Find the paragraph that contains the line. let para = self - .par .bidi .paragraphs .iter() @@ -502,7 +510,7 @@ impl<'a> LineLayout<'a> { .unwrap(); // Compute the reordered ranges in visual order (left to right). - let (levels, runs) = self.par.bidi.visual_runs(para, self.line.clone()); + let (levels, runs) = self.bidi.visual_runs(para, self.line.clone()); // Find the items for each run. for run in runs { diff --git a/src/layout/stack.rs b/src/layout/stack.rs index a6d836952..8c47597b1 100644 --- a/src/layout/stack.rs +++ b/src/layout/stack.rs @@ -39,7 +39,7 @@ impl From for AnyNode { } } -#[derive(Debug)] +/// Performs stack layout. struct StackLayouter<'a> { /// The stack node to layout. stack: &'a StackNode, @@ -49,9 +49,6 @@ struct StackLayouter<'a> { expand: Spec, /// The region to layout into. regions: Regions, - /// Offset, alignment and frame for all children that fit into the current - /// region. The exact positions are not known yet. - frames: Vec<(Length, Gen, Frame)>, /// The full size of `regions.current` that was available before we started /// subtracting. full: Size, @@ -59,11 +56,15 @@ struct StackLayouter<'a> { used: Gen, /// The alignment ruler for the current region. ruler: Align, + /// Offset, alignment and frame for all children that fit into the current + /// region. The exact positions are not known yet. + frames: Vec<(Length, Gen, Frame)>, /// Finished frames for previous regions. finished: Vec, } impl<'a> StackLayouter<'a> { + /// Create a new stack layouter. fn new(stack: &'a StackNode, mut regions: Regions) -> Self { let main = stack.dirs.main.axis(); let full = regions.current; @@ -73,7 +74,7 @@ impl<'a> StackLayouter<'a> { *regions.expand.get_mut(main) = false; if let Some(aspect) = stack.aspect { - regions.apply_aspect_ratio(aspect); + regions.current = regions.current.with_aspect(aspect.into_inner()); } Self { @@ -81,26 +82,21 @@ impl<'a> StackLayouter<'a> { main, expand, regions, - finished: vec![], - frames: vec![], full, used: Gen::zero(), ruler: Align::Start, + frames: vec![], + finished: vec![], } } + /// Layout all children. fn layout(mut self, ctx: &mut LayoutContext) -> Vec { for child in &self.stack.children { match *child { - StackChild::Spacing(amount) => self.push_spacing(amount), + StackChild::Spacing(amount) => self.space(amount), StackChild::Any(ref node, aligns) => { - let mut frames = node.layout(ctx, &self.regions).into_iter(); - if let Some(frame) = frames.next() { - self.push_frame(frame, aligns); - } - - for frame in frames { - self.finish_region(); + for frame in node.layout(ctx, &self.regions) { self.push_frame(frame, aligns); } } @@ -111,7 +107,8 @@ impl<'a> StackLayouter<'a> { self.finished } - fn push_spacing(&mut self, amount: Length) { + /// Add main-axis spacing into the current region. + fn space(&mut self, amount: Length) { // Cap the spacing to the remaining available space. let remaining = self.regions.current.get_mut(self.main); let capped = amount.min(*remaining); @@ -121,60 +118,65 @@ impl<'a> StackLayouter<'a> { *remaining -= capped; } + /// Push a frame into the current or next fitting region, finishing regions + /// if necessary. fn push_frame(&mut self, frame: Frame, aligns: Gen) { - let size = frame.size; + let size = frame.size.to_gen(self.main); // Don't allow `Start` after `End` in the same region. - if self.ruler > aligns.main { + if aligns.main < self.ruler { self.finish_region(); } - // Adjust the ruler. - self.ruler = aligns.main; - // Find a fitting region. - while !self.regions.current.fits(size) && !self.regions.in_full_last() { + while !self.regions.current.get(self.main).fits(size.main) + && !self.regions.in_full_last() + { self.finish_region(); } - // Remember the frame with offset and alignment. - self.frames.push((self.used.main, aligns, frame)); + // Shrink available space in the region. + *self.regions.current.get_mut(self.main) -= size.main; - // Grow our size and shrink available space in the region. - let gen = size.to_gen(self.main); - self.used.main += gen.main; - self.used.cross.set_max(gen.cross); - *self.regions.current.get_mut(self.main) -= gen.main; + // Grow our size. + let offset = self.used.main; + self.used.main += size.main; + self.used.cross.set_max(size.cross); + self.ruler = aligns.main; + + // Remember the frame with offset and alignment. + self.frames.push((offset, aligns, frame)); } + /// Finish the frame for one region. fn finish_region(&mut self) { - let used = self.used.to_size(self.main); let expand = self.expand; + let used = self.used.to_size(self.main); // Determine the stack's size dependening on whether the region is // fixed. - let mut stack_size = Size::new( + let mut size = Size::new( if expand.horizontal { self.full.width } else { used.width }, if expand.vertical { self.full.height } else { used.height }, ); // Make sure the stack's size satisfies the aspect ratio. if let Some(aspect) = self.stack.aspect { - let width = stack_size + let width = size .width - .max(aspect.into_inner() * stack_size.height) + .max(aspect.into_inner() * size.height) .min(self.full.width) .min(aspect.into_inner() * self.full.height); - stack_size = Size::new(width, width / aspect.into_inner()); + size = Size::new(width, width / aspect.into_inner()); } - let mut output = Frame::new(stack_size, stack_size.height); + let mut output = Frame::new(size, size.height); let mut first = true; // Place all frames. - for (offset, aligns, frame) in std::mem::take(&mut self.frames) { - let stack_size = stack_size.to_gen(self.main); + for (offset, aligns, frame) in self.frames.drain(..) { + let stack_size = size.to_gen(self.main); let child_size = frame.size.to_gen(self.main); // Align along the cross axis. @@ -206,10 +208,9 @@ impl<'a> StackLayouter<'a> { output.push_frame(pos, frame); } - // Move on to the next region. self.regions.next(); if let Some(aspect) = self.stack.aspect { - self.regions.apply_aspect_ratio(aspect); + self.regions.current = self.regions.current.with_aspect(aspect.into_inner()); } self.full = self.regions.current; diff --git a/tests/typeset.rs b/tests/typeset.rs index 420df09c7..ed393aee5 100644 --- a/tests/typeset.rs +++ b/tests/typeset.rs @@ -221,7 +221,7 @@ fn test_part( // We want to have "unbounded" pages, so we allow them to be infinitely // large and fit them to match their content. let mut state = State::default(); - state.page.size = Size::new(Length::pt(120.0), Length::raw(f64::INFINITY)); + state.page.size = Size::new(Length::pt(120.0), Length::inf()); state.page.margins = Sides::splat(Some(Length::pt(10.0).into())); let mut pass = typst::typeset(loader, cache, Some(src_path), &src, &scope, state);