mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
264 lines
7.3 KiB
Rust
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)
|
|
}
|
|
}
|