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