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