mirror of https://github.com/stelzo/typst.git
179 lines
4.5 KiB
Rust
179 lines
4.5 KiB
Rust
//! Layouting of documents.
|
|
|
|
mod document;
|
|
mod fixed;
|
|
mod node;
|
|
mod pad;
|
|
mod par;
|
|
mod spacing;
|
|
mod stack;
|
|
mod text;
|
|
|
|
use crate::font::SharedFontLoader;
|
|
use crate::geom::*;
|
|
use crate::shaping::Shaped;
|
|
|
|
pub use document::*;
|
|
pub use fixed::*;
|
|
pub use node::*;
|
|
pub use pad::*;
|
|
pub use par::*;
|
|
pub use spacing::*;
|
|
pub use stack::*;
|
|
pub use text::*;
|
|
|
|
/// Layout a document and return the produced layouts.
|
|
pub fn layout(document: &Document, loader: SharedFontLoader) -> Vec<BoxLayout> {
|
|
let mut ctx = LayoutContext { loader };
|
|
document.layout(&mut ctx)
|
|
}
|
|
|
|
/// The context for layouting.
|
|
#[derive(Debug, Clone)]
|
|
pub struct LayoutContext {
|
|
/// The font loader to query fonts from when typesetting text.
|
|
pub loader: SharedFontLoader,
|
|
}
|
|
|
|
/// Layout a node.
|
|
pub trait Layout {
|
|
/// Layout the node in the given layout context.
|
|
fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Vec<Layouted>;
|
|
}
|
|
|
|
/// A sequence of areas to layout into.
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub struct Areas {
|
|
/// The current area.
|
|
pub current: Area,
|
|
/// The backlog of followup areas.
|
|
///
|
|
/// _Note_: This works stack-like and not queue-like!
|
|
pub backlog: Vec<Size>,
|
|
/// The last area that is repeated when the backlog is empty.
|
|
pub last: Option<Size>,
|
|
}
|
|
|
|
impl Areas {
|
|
/// Create a new length-1 sequence of areas with just one `area`.
|
|
pub fn once(size: Size) -> Self {
|
|
Self {
|
|
current: Area::new(size),
|
|
backlog: vec![],
|
|
last: None,
|
|
}
|
|
}
|
|
|
|
/// Create a new sequence of areas that repeats `area` indefinitely.
|
|
pub fn repeat(size: Size) -> Self {
|
|
Self {
|
|
current: Area::new(size),
|
|
backlog: vec![],
|
|
last: Some(size),
|
|
}
|
|
}
|
|
|
|
/// Advance to the next area if there is any.
|
|
pub fn next(&mut self) {
|
|
if let Some(size) = self.backlog.pop().or(self.last) {
|
|
self.current = Area::new(size);
|
|
}
|
|
}
|
|
|
|
/// Whether `current` is a fully sized (untouched) copy of the last area.
|
|
///
|
|
/// If this is false calling `next()` will have no effect.
|
|
pub fn in_full_last(&self) -> bool {
|
|
self.backlog.is_empty() && self.last.map_or(true, |size| self.current.rem == size)
|
|
}
|
|
}
|
|
|
|
/// The area into which content can be laid out.
|
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
pub struct Area {
|
|
/// The remaining size of this area.
|
|
pub rem: Size,
|
|
/// The full size this area once had (used for relative sizing).
|
|
pub full: Size,
|
|
}
|
|
|
|
impl Area {
|
|
/// Create a new area.
|
|
pub fn new(size: Size) -> Self {
|
|
Self { rem: size, full: size }
|
|
}
|
|
}
|
|
|
|
/// How to determine a container's size along an axis.
|
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
|
pub enum Expansion {
|
|
/// Fit the content.
|
|
Fit,
|
|
/// Fill the available space.
|
|
Fill,
|
|
}
|
|
|
|
impl Expansion {
|
|
/// Returns `Fill` if the condition is true and `Fit` otherwise.
|
|
pub fn fill_if(condition: bool) -> Self {
|
|
if condition { Self::Fill } else { Self::Fit }
|
|
}
|
|
}
|
|
|
|
/// An item that is produced by [layouting] a node.
|
|
///
|
|
/// [layouting]: trait.Layout.html#method.layout
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub enum Layouted {
|
|
/// Spacing that should be added to the parent.
|
|
Spacing(Length),
|
|
/// A box that should be added to and aligned in the parent.
|
|
Boxed(BoxLayout, Gen<Align>),
|
|
}
|
|
|
|
impl Layouted {
|
|
/// Return the box if this if its a box variant.
|
|
pub fn into_boxed(self) -> Option<BoxLayout> {
|
|
match self {
|
|
Self::Spacing(_) => None,
|
|
Self::Boxed(boxed, _) => Some(boxed),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A finished box with content at fixed positions.
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub struct BoxLayout {
|
|
/// The size of the box.
|
|
pub size: Size,
|
|
/// The elements composing this layout.
|
|
pub elements: Vec<(Point, LayoutElement)>,
|
|
}
|
|
|
|
impl BoxLayout {
|
|
/// Create a new empty collection.
|
|
pub fn new(size: Size) -> Self {
|
|
Self { size, elements: vec![] }
|
|
}
|
|
|
|
/// Add an element at a position.
|
|
pub fn push(&mut self, pos: Point, element: LayoutElement) {
|
|
self.elements.push((pos, element));
|
|
}
|
|
|
|
/// Add all elements of another collection, placing them relative to the
|
|
/// given position.
|
|
pub fn push_layout(&mut self, pos: Point, more: Self) {
|
|
for (subpos, element) in more.elements {
|
|
self.push(pos + subpos, element);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A layout element, the basic building block layouts are composed of.
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub enum LayoutElement {
|
|
/// Shaped text.
|
|
Text(Shaped),
|
|
}
|