mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
140 lines
3.9 KiB
Rust
140 lines
3.9 KiB
Rust
//! Block-style layouting of boxes.
|
|
|
|
use crate::doc::{Document, Page, LayoutAction};
|
|
use crate::size::{Size, Size2D};
|
|
use super::{ActionList, LayoutSpace, LayoutResult, LayoutError};
|
|
|
|
|
|
/// A box layout has a fixed width and height and composes of actions.
|
|
#[derive(Debug, Clone)]
|
|
pub struct BoxLayout {
|
|
/// The size of the box.
|
|
pub dimensions: Size2D,
|
|
/// The actions composing this layout.
|
|
pub actions: Vec<LayoutAction>,
|
|
}
|
|
|
|
impl BoxLayout {
|
|
/// Convert this layout into a document.
|
|
pub fn into_doc(self) -> Document {
|
|
Document {
|
|
pages: vec![Page {
|
|
width: self.dimensions.x,
|
|
height: self.dimensions.y,
|
|
actions: self.actions,
|
|
}],
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The context for layouting boxes.
|
|
#[derive(Debug, Copy, Clone)]
|
|
pub struct BoxContext {
|
|
/// The space to layout the boxes in.
|
|
pub space: LayoutSpace,
|
|
}
|
|
|
|
/// Layouts boxes block-style.
|
|
#[derive(Debug)]
|
|
pub struct BoxLayouter {
|
|
ctx: BoxContext,
|
|
actions: ActionList,
|
|
dimensions: Size2D,
|
|
usable: Size2D,
|
|
cursor: Size2D,
|
|
}
|
|
|
|
impl BoxLayouter {
|
|
/// Create a new box layouter.
|
|
pub fn new(ctx: BoxContext) -> BoxLayouter {
|
|
let space = ctx.space;
|
|
BoxLayouter {
|
|
ctx,
|
|
actions: ActionList::new(),
|
|
dimensions: Size2D::zero(),
|
|
usable: space.usable(),
|
|
cursor: Size2D::new(space.padding.left, space.padding.right),
|
|
}
|
|
}
|
|
|
|
/// Add a sublayout.
|
|
pub fn add_box(&mut self, layout: BoxLayout) -> LayoutResult<()> {
|
|
// In the flow direction (vertical) add the layout and in the second
|
|
// direction just consider the maximal size of any child layout.
|
|
let new = Size2D {
|
|
x: crate::size::max(self.dimensions.x, layout.dimensions.x),
|
|
y: self.dimensions.y + layout.dimensions.y,
|
|
};
|
|
|
|
// Check whether this box fits.
|
|
if self.overflows(new) {
|
|
return Err(LayoutError::NotEnoughSpace);
|
|
}
|
|
|
|
// Apply the dimensions as they fit.
|
|
let height = layout.dimensions.y;
|
|
self.dimensions = new;
|
|
|
|
// Add the box.
|
|
self.add_box_absolute(self.cursor, layout);
|
|
|
|
// Adjust the cursor.
|
|
self.cursor.y += height;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Add a sublayout at an absolute position.
|
|
pub fn add_box_absolute(&mut self, position: Size2D, layout: BoxLayout) {
|
|
// Move all actions into this layout and translate absolute positions.
|
|
self.actions.reset_origin();
|
|
self.actions.add(LayoutAction::MoveAbsolute(position));
|
|
self.actions.set_origin(position);
|
|
self.actions.extend(layout.actions);
|
|
}
|
|
|
|
/// Add some space in between two boxes.
|
|
pub fn add_space(&mut self, space: Size) -> LayoutResult<()> {
|
|
// Check whether this space fits.
|
|
if self.overflows(self.dimensions + Size2D::with_y(space)) {
|
|
return Err(LayoutError::NotEnoughSpace);
|
|
}
|
|
|
|
// Adjust the sizes.
|
|
self.cursor.y += space;
|
|
self.dimensions.y += space;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// The remaining space for new boxes.
|
|
pub fn remaining(&self) -> Size2D {
|
|
Size2D {
|
|
x: self.usable.x,
|
|
y: self.usable.y - self.dimensions.y,
|
|
}
|
|
}
|
|
|
|
/// Whether this layouter contains any items.
|
|
pub fn is_empty(&self) -> bool {
|
|
self.actions.is_empty()
|
|
}
|
|
|
|
/// Finish the layouting and create a box layout from this.
|
|
pub fn finish(self) -> BoxLayout {
|
|
BoxLayout {
|
|
dimensions: if self.ctx.space.shrink_to_fit {
|
|
self.dimensions.padded(self.ctx.space.padding)
|
|
} else {
|
|
self.ctx.space.dimensions
|
|
},
|
|
actions: self.actions.into_vec(),
|
|
}
|
|
}
|
|
|
|
/// Whether the given box is bigger than what we can hold.
|
|
fn overflows(&self, dimensions: Size2D) -> bool {
|
|
dimensions.x > self.usable.x || dimensions.y > self.usable.y
|
|
}
|
|
}
|