typst_layout/
pad.rs

1use typst_library::diag::SourceResult;
2use typst_library::engine::Engine;
3use typst_library::foundations::{Packed, Resolve, StyleChain};
4use typst_library::introspection::Locator;
5use typst_library::layout::{
6    Abs, Fragment, Frame, PadElem, Point, Regions, Rel, Sides, Size,
7};
8
9/// Layout the padded content.
10#[typst_macros::time(span = elem.span())]
11pub fn layout_pad(
12    elem: &Packed<PadElem>,
13    engine: &mut Engine,
14    locator: Locator,
15    styles: StyleChain,
16    regions: Regions,
17) -> SourceResult<Fragment> {
18    let padding = Sides::new(
19        elem.left(styles).resolve(styles),
20        elem.top(styles).resolve(styles),
21        elem.right(styles).resolve(styles),
22        elem.bottom(styles).resolve(styles),
23    );
24
25    let mut backlog = vec![];
26    let pod = regions.map(&mut backlog, |size| shrink(size, &padding));
27
28    // Layout child into padded regions.
29    let mut fragment = crate::layout_fragment(engine, &elem.body, locator, styles, pod)?;
30
31    for frame in &mut fragment {
32        grow(frame, &padding);
33    }
34
35    Ok(fragment)
36}
37
38/// Shrink a region size by an inset relative to the size itself.
39pub fn shrink(size: Size, inset: &Sides<Rel<Abs>>) -> Size {
40    size - inset.sum_by_axis().relative_to(size)
41}
42
43/// Shrink the components of possibly multiple `Regions` by an inset relative to
44/// the regions themselves.
45pub fn shrink_multiple(
46    size: &mut Size,
47    full: &mut Abs,
48    backlog: &mut [Abs],
49    last: &mut Option<Abs>,
50    inset: &Sides<Rel<Abs>>,
51) {
52    let summed = inset.sum_by_axis();
53    *size -= summed.relative_to(*size);
54    *full -= summed.y.relative_to(*full);
55    for item in backlog {
56        *item -= summed.y.relative_to(*item);
57    }
58    *last = last.map(|v| v - summed.y.relative_to(v));
59}
60
61/// Grow a frame's size by an inset relative to the grown size.
62/// This is the inverse operation to `shrink()`.
63///
64/// For the horizontal axis the derivation looks as follows.
65/// (Vertical axis is analogous.)
66///
67/// Let w be the grown target width,
68///     s be the given width,
69///     l be the left inset,
70///     r be the right inset,
71///     p = l + r.
72///
73/// We want that: w - l.resolve(w) - r.resolve(w) = s
74///
75/// Thus: w - l.resolve(w) - r.resolve(w) = s
76///   <=> w - p.resolve(w) = s
77///   <=> w - p.rel * w - p.abs = s
78///   <=> (1 - p.rel) * w = s + p.abs
79///   <=> w = (s + p.abs) / (1 - p.rel)
80pub fn grow(frame: &mut Frame, inset: &Sides<Rel<Abs>>) {
81    // Apply the padding inversely such that the grown size padded
82    // yields the frame's size.
83    let padded = frame
84        .size()
85        .zip_map(inset.sum_by_axis(), |s, p| (s + p.abs) / (1.0 - p.rel.get()));
86
87    let inset = inset.relative_to(padded);
88    let offset = Point::new(inset.left, inset.top);
89
90    // Grow the frame and translate everything in the frame inwards.
91    frame.set_size(padded);
92    frame.translate(offset);
93}