typst/src/layout/stack.rs
2019-11-30 14:10:35 +01:00

264 lines
7.3 KiB
Rust

use smallvec::smallvec;
use super::*;
#[derive(Debug, Clone)]
pub struct StackLayouter {
ctx: StackContext,
layouts: MultiLayout,
space: Space,
sub: Subspace,
}
#[derive(Debug, Clone)]
struct Space {
index: usize,
hard: bool,
actions: LayoutActionList,
combined_dimensions: Size2D,
}
impl Space {
fn new(index: usize, hard: bool) -> Space {
Space {
index,
hard,
actions: LayoutActionList::new(),
combined_dimensions: Size2D::zero(),
}
}
}
#[derive(Debug, Clone)]
struct Subspace {
origin: Size2D,
anchor: Size2D,
factor: i32,
boxes: Vec<(Size, Size, Layout)>,
usable: Size2D,
dimensions: Size2D,
space: SpaceState,
}
impl Subspace {
fn new(origin: Size2D, usable: Size2D, axes: LayoutAxes) -> Subspace {
Subspace {
origin,
anchor: axes.anchor(usable),
factor: axes.secondary.axis.factor(),
boxes: vec![],
usable: axes.generalize(usable),
dimensions: Size2D::zero(),
space: SpaceState::Forbidden,
}
}
}
/// The context for stack layouting.
///
/// See [`LayoutContext`] for details about the fields.
#[derive(Debug, Clone)]
pub struct StackContext {
pub spaces: LayoutSpaces,
pub axes: LayoutAxes,
pub expand: bool,
}
impl StackLayouter {
/// Create a new stack layouter.
pub fn new(ctx: StackContext) -> StackLayouter {
let axes = ctx.axes;
let space = ctx.spaces[0];
StackLayouter {
ctx,
layouts: MultiLayout::new(),
space: Space::new(0, true),
sub: Subspace::new(space.start(), space.usable(), axes),
}
}
pub fn add(&mut self, layout: Layout) -> LayoutResult<()> {
if let SpaceState::Soft(space) = self.sub.space {
self.add_space(space, SpaceKind::Hard);
}
let size = self.ctx.axes.generalize(layout.dimensions);
let mut new_dimensions = Size2D {
x: crate::size::max(self.sub.dimensions.x, size.x),
y: self.sub.dimensions.y + size.y
};
while !self.sub.usable.fits(new_dimensions) {
if self.space_is_last() && self.space_is_empty() {
lerr!("box does not fit into stack");
}
self.finish_space(true);
new_dimensions = size;
}
let offset = self.sub.dimensions.y;
let anchor = self.ctx.axes.primary.anchor(size.x);
self.sub.boxes.push((offset, anchor, layout));
self.sub.dimensions = new_dimensions;
self.sub.space = SpaceState::Allowed;
Ok(())
}
pub fn add_multiple(&mut self, layouts: MultiLayout) -> LayoutResult<()> {
for layout in layouts {
self.add(layout)?;
}
Ok(())
}
pub fn add_space(&mut self, space: Size, kind: SpaceKind) {
if kind == SpaceKind::Soft {
if self.sub.space != SpaceState::Forbidden {
self.sub.space = SpaceState::Soft(space);
}
} else {
if self.sub.dimensions.y + space > self.sub.usable.y {
self.sub.dimensions.y = self.sub.usable.y;
} else {
self.sub.dimensions.y += space;
}
if kind == SpaceKind::Hard {
self.sub.space = SpaceState::Forbidden;
}
}
}
pub fn set_axes(&mut self, axes: LayoutAxes) {
if axes != self.ctx.axes {
self.finish_subspace();
let (origin, usable) = self.remaining_subspace();
self.ctx.axes = axes;
self.sub = Subspace::new(origin, usable, axes);
}
}
pub fn set_spaces(&mut self, spaces: LayoutSpaces, replace_empty: bool) {
if replace_empty && self.space_is_empty() {
self.ctx.spaces = spaces;
self.start_space(0, self.space.hard);
} else {
self.ctx.spaces.truncate(self.space.index + 1);
self.ctx.spaces.extend(spaces);
}
}
pub fn remaining(&self) -> LayoutSpaces {
let mut spaces = smallvec![LayoutSpace {
dimensions: self.remaining_subspace().1,
padding: SizeBox::zero(),
}];
for space in &self.ctx.spaces[self.next_space()..] {
spaces.push(space.usable_space());
}
spaces
}
pub fn primary_usable(&self) -> Size {
self.sub.usable.x
}
pub fn space_is_empty(&self) -> bool {
self.space.combined_dimensions == Size2D::zero()
&& self.space.actions.is_empty()
&& self.sub.dimensions == Size2D::zero()
}
pub fn space_is_last(&self) -> bool {
self.space.index == self.ctx.spaces.len() - 1
}
pub fn finish(mut self) -> MultiLayout {
if self.space.hard || !self.space_is_empty() {
self.finish_space(false);
}
self.layouts
}
pub fn finish_space(&mut self, hard: bool) {
self.finish_subspace();
let space = self.ctx.spaces[self.space.index];
self.layouts.add(Layout {
dimensions: match self.ctx.expand {
true => space.dimensions,
false => self.space.combined_dimensions.padded(space.padding),
},
actions: self.space.actions.to_vec(),
debug_render: true,
});
self.start_space(self.next_space(), hard);
}
fn start_space(&mut self, space: usize, hard: bool) {
self.space = Space::new(space, hard);
let space = self.ctx.spaces[space];
self.sub = Subspace::new(space.start(), space.usable(), self.ctx.axes);
}
fn next_space(&self) -> usize {
(self.space.index + 1).min(self.ctx.spaces.len() - 1)
}
fn finish_subspace(&mut self) {
let factor = self.ctx.axes.secondary.axis.factor();
let anchor =
self.ctx.axes.anchor(self.sub.usable)
- self.ctx.axes.anchor(Size2D::with_y(self.sub.dimensions.y));
for (offset, layout_anchor, layout) in self.sub.boxes.drain(..) {
let pos = self.sub.origin
+ self.ctx.axes.specialize(
anchor + Size2D::new(-layout_anchor, factor * offset)
);
self.space.actions.add_layout(pos, layout);
}
if self.ctx.axes.primary.needs_expansion() {
self.sub.dimensions.x = self.sub.usable.x;
}
if self.ctx.axes.secondary.needs_expansion() {
self.sub.dimensions.y = self.sub.usable.y;
}
let space = self.ctx.spaces[self.space.index];
let origin = self.sub.origin;
let dimensions = self.ctx.axes.specialize(self.sub.dimensions);
self.space.combined_dimensions.max_eq(origin - space.start() + dimensions);
}
fn remaining_subspace(&self) -> (Size2D, Size2D) {
let new_origin = self.sub.origin + match self.ctx.axes.secondary.axis.is_positive() {
true => self.ctx.axes.specialize(Size2D::with_y(self.sub.dimensions.y)),
false => Size2D::zero(),
};
let new_usable = self.ctx.axes.specialize(Size2D {
x: self.sub.usable.x,
y: self.sub.usable.y - self.sub.dimensions.y - self.sub.space.soft_or_zero(),
});
(new_origin, new_usable)
}
}