typst_library/layout/
layout.rs

1use comemo::Track;
2use typst_syntax::Span;
3
4use crate::diag::SourceResult;
5use crate::engine::Engine;
6use crate::foundations::{
7    dict, elem, func, Content, Context, Func, NativeElement, Packed, Show, StyleChain,
8};
9use crate::introspection::Locatable;
10use crate::layout::{BlockElem, Size};
11
12/// Provides access to the current outer container's (or page's, if none)
13/// dimensions (width and height).
14///
15/// Accepts a function that receives a single parameter, which is a dictionary
16/// with keys `width` and `height`, both of type [`length`]. The function is
17/// provided [context], meaning you don't need to use it in combination with the
18/// `context` keyword. This is why [`measure`] can be called in the example
19/// below.
20///
21/// ```example
22/// #let text = lorem(30)
23/// #layout(size => [
24///   #let (height,) = measure(
25///     block(width: size.width, text),
26///   )
27///   This text is #height high with
28///   the current page width: \
29///   #text
30/// ])
31/// ```
32///
33/// Note that the `layout` function forces its contents into a [block]-level
34/// container, so placement relative to the page or pagebreaks are not possible
35/// within it.
36///
37/// If the `layout` call is placed inside a box with a width of `{800pt}` and a
38/// height of `{400pt}`, then the specified function will be given the argument
39/// `{(width: 800pt, height: 400pt)}`. If it is placed directly into the page, it
40/// receives the page's dimensions minus its margins. This is mostly useful in
41/// combination with [measurement]($measure).
42///
43/// You can also use this function to resolve [`ratio`] to fixed lengths. This
44/// might come in handy if you're building your own layout abstractions.
45///
46/// ```example
47/// #layout(size => {
48///   let half = 50% * size.width
49///   [Half a page is #half wide.]
50/// })
51/// ```
52///
53/// Note that the width or height provided by `layout` will be infinite if the
54/// corresponding page dimension is set to `{auto}`.
55#[func]
56pub fn layout(
57    span: Span,
58    /// A function to call with the outer container's size. Its return value is
59    /// displayed in the document.
60    ///
61    /// The container's size is given as a [dictionary] with the keys `width`
62    /// and `height`.
63    ///
64    /// This function is called once for each time the content returned by
65    /// `layout` appears in the document. This makes it possible to generate
66    /// content that depends on the dimensions of its container.
67    func: Func,
68) -> Content {
69    LayoutElem::new(func).pack().spanned(span)
70}
71
72/// Executes a `layout` call.
73#[elem(Locatable, Show)]
74struct LayoutElem {
75    /// The function to call with the outer container's (or page's) size.
76    #[required]
77    func: Func,
78}
79
80impl Show for Packed<LayoutElem> {
81    fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
82        Ok(BlockElem::multi_layouter(
83            self.clone(),
84            |elem, engine, locator, styles, regions| {
85                // Gets the current region's base size, which will be the size of the
86                // outer container, or of the page if there is no such container.
87                let Size { x, y } = regions.base();
88                let loc = elem.location().unwrap();
89                let context = Context::new(Some(loc), Some(styles));
90                let result = elem
91                    .func
92                    .call(
93                        engine,
94                        context.track(),
95                        [dict! { "width" => x, "height" => y }],
96                    )?
97                    .display();
98                (engine.routines.layout_fragment)(
99                    engine, &result, locator, styles, regions,
100                )
101            },
102        )
103        .pack()
104        .spanned(self.span()))
105    }
106}