typst_library/layout/place.rs
1use crate::foundations::{elem, scope, Cast, Content, Packed, Smart};
2use crate::introspection::{Locatable, Unqueriable};
3use crate::layout::{Alignment, Em, Length, Rel};
4
5/// Places content relatively to its parent container.
6///
7/// Placed content can be either overlaid (the default) or floating. Overlaid
8/// content is aligned with the parent container according to the given
9/// [`alignment`]($place.alignment), and shown over any other content added so
10/// far in the container. Floating content is placed at the top or bottom of
11/// the container, displacing other content down or up respectively. In both
12/// cases, the content position can be adjusted with [`dx`]($place.dx) and
13/// [`dy`]($place.dy) offsets without affecting the layout.
14///
15/// The parent can be any container such as a [`block`], [`box`],
16/// [`rect`], etc. A top level `place` call will place content directly
17/// in the text area of the current page. This can be used for absolute
18/// positioning on the page: with a `top + left`
19/// [`alignment`]($place.alignment), the offsets `dx` and `dy` will set the
20/// position of the element's top left corner relatively to the top left corner
21/// of the text area. For absolute positioning on the full page including
22/// margins, you can use `place` in [`page.foreground`]($page.foreground) or
23/// [`page.background`]($page.background).
24///
25/// # Examples
26/// ```example
27/// #set page(height: 120pt)
28/// Hello, world!
29///
30/// #rect(
31/// width: 100%,
32/// height: 2cm,
33/// place(horizon + right, square()),
34/// )
35///
36/// #place(
37/// top + left,
38/// dx: -5pt,
39/// square(size: 5pt, fill: red),
40/// )
41/// ```
42///
43/// # Effect on the position of other elements { #effect-on-other-elements }
44/// Overlaid elements don't take space in the flow of content, but a `place`
45/// call inserts an invisible block-level element in the flow. This can
46/// affect the layout by breaking the current paragraph. To avoid this,
47/// you can wrap the `place` call in a [`box`] when the call is made
48/// in the middle of a paragraph. The alignment and offsets will then be
49/// relative to this zero-size box. To make sure it doesn't interfere with
50/// spacing, the box should be attached to a word using a word joiner.
51///
52/// For example, the following defines a function for attaching an annotation
53/// to the following word:
54///
55/// ```example
56/// >>> #set page(height: 70pt)
57/// #let annotate(..args) = {
58/// box(place(..args))
59/// sym.wj
60/// h(0pt, weak: true)
61/// }
62///
63/// A placed #annotate(square(), dy: 2pt)
64/// square in my text.
65/// ```
66///
67/// The zero-width weak spacing serves to discard spaces between the function
68/// call and the next word.
69#[elem(scope, Locatable, Unqueriable)]
70pub struct PlaceElem {
71 /// Relative to which position in the parent container to place the content.
72 ///
73 /// - If `float` is `{false}`, then this can be any alignment other than `{auto}`.
74 /// - If `float` is `{true}`, then this must be `{auto}`, `{top}`, or `{bottom}`.
75 ///
76 /// When `float` is `{false}` and no vertical alignment is specified, the
77 /// content is placed at the current position on the vertical axis.
78 #[positional]
79 #[default(Smart::Custom(Alignment::START))]
80 pub alignment: Smart<Alignment>,
81
82 /// Relative to which containing scope something is placed.
83 ///
84 /// The parent scope is primarily used with figures and, for
85 /// this reason, the figure function has a mirrored [`scope`
86 /// parameter]($figure.scope). Nonetheless, it can also be more generally
87 /// useful to break out of the columns. A typical example would be to
88 /// [create a single-column title section]($guides/page-setup-guide/#columns)
89 /// in a two-column document.
90 ///
91 /// Note that parent-scoped placement is currently only supported if `float`
92 /// is `{true}`. This may change in the future.
93 ///
94 /// ```example
95 /// #set page(height: 150pt, columns: 2)
96 /// #place(
97 /// top + center,
98 /// scope: "parent",
99 /// float: true,
100 /// rect(width: 80%, fill: aqua),
101 /// )
102 ///
103 /// #lorem(25)
104 /// ```
105 pub scope: PlacementScope,
106
107 /// Whether the placed element has floating layout.
108 ///
109 /// Floating elements are positioned at the top or bottom of the parent
110 /// container, displacing in-flow content. They are always placed in the
111 /// in-flow order relative to each other, as well as before any content
112 /// following a later [`place.flush`] element.
113 ///
114 /// ```example
115 /// #set page(height: 150pt)
116 /// #let note(where, body) = place(
117 /// center + where,
118 /// float: true,
119 /// clearance: 6pt,
120 /// rect(body),
121 /// )
122 ///
123 /// #lorem(10)
124 /// #note(bottom)[Bottom 1]
125 /// #note(bottom)[Bottom 2]
126 /// #lorem(40)
127 /// #note(top)[Top]
128 /// #lorem(10)
129 /// ```
130 pub float: bool,
131
132 /// The spacing between the placed element and other elements in a floating
133 /// layout.
134 ///
135 /// Has no effect if `float` is `{false}`.
136 #[default(Em::new(1.5).into())]
137 #[resolve]
138 pub clearance: Length,
139
140 /// The horizontal displacement of the placed content.
141 ///
142 /// ```example
143 /// #set page(height: 100pt)
144 /// #for i in range(16) {
145 /// let amount = i * 4pt
146 /// place(center, dx: amount - 32pt, dy: amount)[A]
147 /// }
148 /// ```
149 ///
150 /// This does not affect the layout of in-flow content.
151 /// In other words, the placed content is treated as if it
152 /// were wrapped in a [`move`] element.
153 pub dx: Rel<Length>,
154
155 /// The vertical displacement of the placed content.
156 ///
157 /// This does not affect the layout of in-flow content.
158 /// In other words, the placed content is treated as if it
159 /// were wrapped in a [`move`] element.
160 pub dy: Rel<Length>,
161
162 /// The content to place.
163 #[required]
164 pub body: Content,
165}
166
167/// `PlaceElem` must be locatable to support logical ordering of floats, but I
168/// do not want to expose `query(place)` for now.
169impl Unqueriable for Packed<PlaceElem> {}
170
171#[scope]
172impl PlaceElem {
173 #[elem]
174 type FlushElem;
175}
176
177/// Relative to which containing scope something shall be placed.
178#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Cast)]
179pub enum PlacementScope {
180 /// Place into the current column.
181 #[default]
182 Column,
183 /// Place relative to the parent, letting the content span over all columns.
184 Parent,
185}
186
187/// Asks the layout algorithm to place pending floating elements before
188/// continuing with the content.
189///
190/// This is useful for preventing floating figures from spilling
191/// into the next section.
192///
193/// ```example
194/// >>> #set page(height: 160pt, width: 150pt)
195/// #lorem(15)
196///
197/// #figure(
198/// rect(width: 100%, height: 50pt),
199/// placement: auto,
200/// caption: [A rectangle],
201/// )
202///
203/// #place.flush()
204///
205/// This text appears after the figure.
206/// ```
207#[elem]
208pub struct FlushElem {}