Refactor grid row layout

This commit is contained in:
Laurenz 2021-06-15 16:34:41 +02:00
parent a61ee46ed2
commit e2cdda67dc
8 changed files with 496 additions and 555 deletions

View File

@ -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<I: Iterator<Item = &'a Length>>(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 {

View File

@ -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<SpecAxis> for Size {

View File

@ -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);
}
}
}
}

View File

@ -16,9 +16,27 @@ pub struct GridNode {
pub children: Vec<AnyNode>,
}
/// 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<Frame> {
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<GridNode> 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<TrackSizing>,
/// The row tracks including gutter tracks.
rows: Vec<TrackSizing>,
cells: Vec<Cell<'a>>,
/// The children of the grid.
children: &'a [AnyNode],
/// The region to layout into.
regions: Regions,
/// Resolved column sizes.
rcols: Vec<Length>,
rrows: Vec<(usize, Option<Length>, Option<Vec<Option<Frame>>>)>,
/// 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<Length>,
/// The sum of fractional ratios in the current region.
fr: Fractional,
/// Rows in the current region.
lrows: Vec<Row>,
/// Frames for finished regions.
finished: Vec<Frame>,
}
#[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<Frame> {
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<Length> {
// 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, &regions).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, &regions);
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<Vec<(usize, Length)>>,
) -> Option<Vec<Option<Frame>>> {
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, &regions);
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, &regions).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<Frame> {
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<Length> = 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, &regions).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<Frame> {
// 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, &regions);
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<TrackSizing> {
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),
}

View File

@ -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<F>(&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<F>(&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);
}
}

View File

@ -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<Range>,
}
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<Frame> {
let mut stack = LineStack::new(par.line_spacing, regions);
fn layout(self, ctx: &mut LayoutContext, regions: Regions) -> Vec<Frame> {
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<Frame>,
lines: Vec<LineLayout<'a>>,
size: Size,
lines: Vec<LineLayout<'a>>,
finished: Vec<Frame>,
}
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<Frame> {
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 {

View File

@ -39,7 +39,7 @@ impl From<StackNode> 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<bool>,
/// 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<Align>, 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<Length>,
/// 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<Align>, Frame)>,
/// Finished frames for previous regions.
finished: Vec<Frame>,
}
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<Frame> {
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<Align>) {
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;

View File

@ -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);