summaryrefslogtreecommitdiff
path: root/crates/typst-library/src/layout/place.rs
blob: 95c042ff4d6558dde4bc27d02f39fe5ccbe80cd6 (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
130
131
132
use crate::prelude::*;

/// Places content at an absolute position.
///
/// Placed content will not affect the position of other content. Place is
/// always relative to its parent container and will be in the foreground of all
/// other content in the container. Page margins will be respected.
///
///
/// ## Example { #example }
/// ```example
/// #set page(height: 60pt)
/// Hello, world!
///
/// #place(
///   top + right,
///   square(
///     width: 20pt,
///     stroke: 2pt + blue
///   ),
/// )
/// ```
///
/// Display: Place
/// Category: layout
#[element(Layout, Behave)]
pub struct PlaceElem {
    /// Relative to which position in the parent container to place the content.
    ///
    /// Cannot be `{auto}` if `float` is `{false}` and must be either
    /// `{auto}`, `{top}`, or `{bottom}` if `float` is `{true}`.
    ///
    /// When an axis of the page is `{auto}` sized, all alignments relative to
    /// that axis will be ignored, instead, the item will be placed in the
    /// origin of the axis.
    #[positional]
    #[default(Smart::Custom(Axes::with_x(Some(GenAlign::Start))))]
    pub alignment: Smart<Axes<Option<GenAlign>>>,

    /// Whether the placed element has floating layout.
    ///
    /// Floating elements are positioned at the top or bottom of the page,
    /// displacing in-flow content.
    ///
    /// ```example
    /// #set page(height: 150pt)
    /// #let note(where, body) = place(
    ///   center + where,
    ///   float: true,
    ///   clearance: 6pt,
    ///   rect(body),
    /// )
    ///
    /// #lorem(10)
    /// #note(bottom)[Bottom 1]
    /// #note(bottom)[Bottom 2]
    /// #lorem(40)
    /// #note(top)[Top]
    /// #lorem(10)
    /// ```
    pub float: bool,

    /// The amount of clearance the placed element has in a floating layout.
    #[default(Em::new(1.5).into())]
    #[resolve]
    pub clearance: Length,

    /// The horizontal displacement of the placed content.
    ///
    /// ```example
    /// #set page(height: 100pt)
    /// #for i in range(16) {
    ///   let amount = i * 4pt
    ///   place(center, dx: amount - 32pt, dy: amount)[A]
    /// }
    /// ```
    pub dx: Rel<Length>,

    /// The vertical displacement of the placed content.
    pub dy: Rel<Length>,

    /// The content to place.
    #[required]
    pub body: Content,
}

impl Layout for PlaceElem {
    #[tracing::instrument(name = "PlaceElem::layout", skip_all)]
    fn layout(
        &self,
        vt: &mut Vt,
        styles: StyleChain,
        regions: Regions,
    ) -> SourceResult<Fragment> {
        // The pod is the base area of the region because for absolute
        // placement we don't really care about the already used area.
        let base = regions.base();
        let float = self.float(styles);
        let alignment = self.alignment(styles);

        if float
            && !matches!(
                alignment,
                Smart::Auto
                    | Smart::Custom(Axes {
                        y: Some(GenAlign::Specific(Align::Top | Align::Bottom)),
                        ..
                    })
            )
        {
            bail!(self.span(), "floating placement must be `auto`, `top`, or `bottom`");
        } else if !float && alignment.is_auto() {
            return Err("automatic positioning is only available for floating placement")
                .hint("you can enable floating placement with `place(float: true, ..)`")
                .at(self.span());
        }

        let child = self.body().aligned(
            alignment.unwrap_or_else(|| Axes::with_x(Some(Align::Center.into()))),
        );

        let pod = Regions::one(base, Axes::splat(false));
        let frame = child.layout(vt, styles, pod)?.into_frame();
        Ok(Fragment::frame(frame))
    }
}

impl Behave for PlaceElem {
    fn behaviour(&self) -> Behaviour {
        Behaviour::Ignorant
    }
}