summaryrefslogtreecommitdiff
path: root/library/src/layout/pad.rs
blob: e43763e5f078e6a99fbecfdfb782728a422c3d55 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
use crate::prelude::*;

/// # Padding
/// Add spacing around content.
///
/// The `pad` function adds spacing around content. The spacing can be specified
/// for each side individually, or for all sides at once by specifying a
/// positional argument.
///
/// ## Example
/// ```example
/// #set align(center)
///
/// #pad(x: 16pt, image("typing.jpg"))
/// _Typing speeds can be
/// measured in words per minute._
/// ```
///
/// ## Parameters
/// - body: `Content` (positional, required)
///   The content to pad at the sides.
///
/// - left: `Rel<Length>` (named)
///   The padding at the left side.
///
/// - right: `Rel<Length>` (named)
///   The padding at the right side.
///
/// - top: `Rel<Length>` (named)
///   The padding at the top side.
///
/// - bottom: `Rel<Length>` (named)
///   The padding at the bottom side.
///
/// - x: `Rel<Length>` (named)
///   The horizontal padding. Both `left` and `right` take precedence over this.
///
/// - y: `Rel<Length>` (named)
///   The vertical padding. Both `top` and `bottom` take precedence over this.
///
/// - rest: `Rel<Length>` (named)
///   The padding for all sides. All other parameters take precedence over this.
///
/// ## Category
/// layout
#[func]
#[capable(Layout)]
#[derive(Debug, Hash)]
pub struct PadNode {
    /// The amount of padding.
    pub padding: Sides<Rel<Length>>,
    /// The content whose sides to pad.
    pub body: Content,
}

#[node]
impl PadNode {
    fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
        let all = args.named("rest")?.or(args.find()?);
        let x = args.named("x")?;
        let y = args.named("y")?;
        let left = args.named("left")?.or(x).or(all).unwrap_or_default();
        let top = args.named("top")?.or(y).or(all).unwrap_or_default();
        let right = args.named("right")?.or(x).or(all).unwrap_or_default();
        let bottom = args.named("bottom")?.or(y).or(all).unwrap_or_default();
        let body = args.expect::<Content>("body")?;
        let padding = Sides::new(left, top, right, bottom);
        Ok(Self { padding, body }.pack())
    }
}

impl Layout for PadNode {
    fn layout(
        &self,
        vt: &mut Vt,
        styles: StyleChain,
        regions: Regions,
    ) -> SourceResult<Fragment> {
        let mut backlog = vec![];

        // Layout child into padded regions.
        let padding = self.padding.resolve(styles);
        let pod = regions.map(&mut backlog, |size| shrink(size, padding));
        let mut fragment = self.body.layout(vt, styles, pod)?;

        for frame in &mut fragment {
            // Apply the padding inversely such that the grown size padded
            // yields the frame's size.
            let padded = grow(frame.size(), padding);
            let padding = padding.relative_to(padded);
            let offset = Point::new(padding.left, padding.top);

            // Grow the frame and translate everything in the frame inwards.
            frame.set_size(padded);
            frame.translate(offset);
        }

        Ok(fragment)
    }
}

/// Shrink a size by padding relative to the size itself.
fn shrink(size: Size, padding: Sides<Rel<Abs>>) -> Size {
    size - padding.relative_to(size).sum_by_axis()
}

/// Grow a size by padding relative to the grown size.
/// This is the inverse operation to `shrink()`.
///
/// For the horizontal axis the derivation looks as follows.
/// (Vertical axis is analogous.)
///
/// Let w be the grown target width,
///     s be the given width,
///     l be the left padding,
///     r be the right padding,
///     p = l + r.
///
/// We want that: w - l.resolve(w) - r.resolve(w) = s
///
/// Thus: w - l.resolve(w) - r.resolve(w) = s
///   <=> w - p.resolve(w) = s
///   <=> w - p.rel * w - p.abs = s
///   <=> (1 - p.rel) * w = s + p.abs
///   <=> w = (s + p.abs) / (1 - p.rel)
fn grow(size: Size, padding: Sides<Rel<Abs>>) -> Size {
    size.zip(padding.sum_by_axis())
        .map(|(s, p)| (s + p.abs).safe_div(1.0 - p.rel.get()))
}