typst_library/layout/
place.rs

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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
use crate::foundations::{elem, scope, Cast, Content, Packed, Smart};
use crate::introspection::{Locatable, Unqueriable};
use crate::layout::{Alignment, Em, Length, Rel};

/// Places content relatively to its parent container.
///
/// Placed content can be either overlaid (the default) or floating. Overlaid
/// content is aligned with the parent container according to the given
/// [`alignment`]($place.alignment), and shown over any other content added so
/// far in the container. Floating content is placed at the top or bottom of
/// the container, displacing other content down or up respectively. In both
/// cases, the content position can be adjusted with [`dx`]($place.dx) and
/// [`dy`]($place.dy) offsets without affecting the layout.
///
/// The parent can be any container such as a [`block`], [`box`],
/// [`rect`], etc. A top level `place` call will place content directly
/// in the text area of the current page. This can be used for absolute
/// positioning on the page: with a `top + left`
/// [`alignment`]($place.alignment), the offsets `dx` and `dy` will set the
/// position of the element's top left corner relatively to the top left corner
/// of the text area. For absolute positioning on the full page including
/// margins, you can use `place` in [`page.foreground`]($page.foreground) or
/// [`page.background`]($page.background).
///
/// # Examples
/// ```example
/// #set page(height: 120pt)
/// Hello, world!
///
/// #rect(
///   width: 100%,
///   height: 2cm,
///   place(horizon + right, square()),
/// )
///
/// #place(
///   top + left,
///   dx: -5pt,
///   square(size: 5pt, fill: red),
/// )
/// ```
///
/// # Effect on the position of other elements { #effect-on-other-elements }
/// Overlaid elements don't take space in the flow of content, but a `place`
/// call inserts an invisible block-level element in the flow. This can
/// affect the layout by breaking the current paragraph. To avoid this,
/// you can wrap the `place` call in a [`box`] when the call is made
/// in the middle of a paragraph. The alignment and offsets will then be
/// relative to this zero-size box. To make sure it doesn't interfere with
/// spacing, the box should be attached to a word using a word joiner.
///
/// For example, the following defines a function for attaching an annotation
/// to the following word:
///
/// ```example
/// >>> #set page(height: 70pt)
/// #let annotate(..args) = {
///   box(place(..args))
///   sym.wj
///   h(0pt, weak: true)
/// }
///
/// A placed #annotate(square(), dy: 2pt)
/// square in my text.
/// ```
///
/// The zero-width weak spacing serves to discard spaces between the function
/// call and the next word.
#[elem(scope, Locatable, Unqueriable)]
pub struct PlaceElem {
    /// Relative to which position in the parent container to place the content.
    ///
    /// - If `float` is `{false}`, then this can be any alignment other than `{auto}`.
    /// - If `float` is `{true}`, then this must be `{auto}`, `{top}`, or `{bottom}`.
    ///
    /// When `float` is `{false}` and no vertical alignment is specified, the
    /// content is placed at the current position on the vertical axis.
    #[positional]
    #[default(Smart::Custom(Alignment::START))]
    pub alignment: Smart<Alignment>,

    /// Relative to which containing scope something is placed.
    ///
    /// The parent scope is primarily used with figures and, for
    /// this reason, the figure function has a mirrored [`scope`
    /// parameter]($figure.scope). Nonetheless, it can also be more generally
    /// useful to break out of the columns. A typical example would be to
    /// [create a single-column title section]($guides/page-setup-guide/#columns)
    /// in a two-column document.
    ///
    /// Note that parent-scoped placement is currently only supported if `float`
    /// is `{true}`. This may change in the future.
    ///
    /// ```example
    /// #set page(height: 150pt, columns: 2)
    /// #place(
    ///   top + center,
    ///   scope: "parent",
    ///   float: true,
    ///   rect(width: 80%, fill: aqua),
    /// )
    ///
    /// #lorem(25)
    /// ```
    pub scope: PlacementScope,

    /// Whether the placed element has floating layout.
    ///
    /// Floating elements are positioned at the top or bottom of the parent
    /// container, displacing in-flow content. They are always placed in the
    /// in-flow order relative to each other, as well as before any content
    /// following a later [`place.flush`] element.
    ///
    /// ```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 spacing between the placed element and other elements in a floating
    /// layout.
    ///
    /// Has no effect if `float` is `{false}`.
    #[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]
    /// }
    /// ```
    ///
    /// This does not affect the layout of in-flow content.
    /// In other words, the placed content is treated as if it
    /// were wrapped in a [`move`] element.
    pub dx: Rel<Length>,

    /// The vertical displacement of the placed content.
    ///
    /// This does not affect the layout of in-flow content.
    /// In other words, the placed content is treated as if it
    /// were wrapped in a [`move`] element.
    pub dy: Rel<Length>,

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

/// `PlaceElem` must be locatable to support logical ordering of floats, but I
/// do not want to expose `query(place)` for now.
impl Unqueriable for Packed<PlaceElem> {}

#[scope]
impl PlaceElem {
    #[elem]
    type FlushElem;
}

/// Relative to which containing scope something shall be placed.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Cast)]
pub enum PlacementScope {
    /// Place into the current column.
    #[default]
    Column,
    /// Place relative to the parent, letting the content span over all columns.
    Parent,
}

/// Asks the layout algorithm to place pending floating elements before
/// continuing with the content.
///
/// This is useful for preventing floating figures from spilling
/// into the next section.
///
/// ```example
/// >>> #set page(height: 160pt, width: 150pt)
/// #lorem(15)
///
/// #figure(
///   rect(width: 100%, height: 50pt),
///   placement: auto,
///   caption: [A rectangle],
/// )
///
/// #place.flush()
///
/// This text appears after the figure.
/// ```
#[elem]
pub struct FlushElem {}