typst_library/layout/
measure.rs

1use comemo::Tracked;
2use typst_syntax::Span;
3
4use crate::diag::{At, SourceResult};
5use crate::engine::Engine;
6use crate::foundations::{
7    Content, Context, Dict, Resolve, Smart, Target, TargetElem, dict, func,
8};
9use crate::introspection::{Locator, LocatorLink};
10use crate::layout::{Abs, Axes, Length, Region, Size};
11
12/// Measures the layouted size of content.
13///
14/// The `measure` function lets you determine the layouted size of content.
15/// By default an infinite space is assumed, so the measured dimensions may
16/// not necessarily match the final dimensions of the content.
17/// If you want to measure in the current layout dimensions, you can combine
18/// `measure` and [`layout`].
19///
20/// # Example
21/// The same content can have a different size depending on the [context] that
22/// it is placed into. In the example below, the `[#content]` is of course
23/// bigger when we increase the font size.
24///
25/// ```example
26/// #let content = [Hello!]
27/// #content
28/// #set text(14pt)
29/// #content
30/// ```
31///
32/// For this reason, you can only measure when context is available.
33///
34/// ```example
35/// #let thing(body) = context {
36///   let size = measure(body)
37///   [Width of "#body" is #size.width]
38/// }
39///
40/// #thing[Hey] \
41/// #thing[Welcome]
42/// ```
43///
44/// The measure function returns a dictionary with the entries `width` and
45/// `height`, both of type [`length`].
46#[func(contextual)]
47pub fn measure(
48    engine: &mut Engine,
49    context: Tracked<Context>,
50    span: Span,
51    /// The width available to layout the content.
52    ///
53    /// Setting this to `{auto}` indicates infinite available width.
54    ///
55    /// Note that using the `width` and `height` parameters of this function is
56    /// different from measuring a sized [`block`] containing the content. In
57    /// the following example, the former will get the dimensions of the inner
58    /// content instead of the dimensions of the block.
59    ///
60    /// ```example
61    /// #context measure(lorem(100), width: 400pt)
62    ///
63    /// #context measure(block(lorem(100), width: 400pt))
64    /// ```
65    #[named]
66    #[default(Smart::Auto)]
67    width: Smart<Length>,
68    /// The height available to layout the content.
69    ///
70    /// Setting this to `{auto}` indicates infinite available height.
71    #[named]
72    #[default(Smart::Auto)]
73    height: Smart<Length>,
74    /// The content whose size to measure.
75    content: Content,
76) -> SourceResult<Dict> {
77    // Create a pod region with the available space.
78    let styles = context.styles().at(span)?;
79    let pod = Region::new(
80        Axes::new(
81            width.resolve(styles).unwrap_or(Abs::inf()),
82            height.resolve(styles).unwrap_or(Abs::inf()),
83        ),
84        Axes::splat(false),
85    );
86
87    // We put the locator into a special "measurement mode" to ensure that
88    // introspection-driven features within the content continue to work. Read
89    // the "Dealing with measurement" section of the [`Locator`] docs for more
90    // details.
91    let here = context.location().at(span)?;
92    let link = LocatorLink::measure(here);
93    let locator = Locator::link(&link);
94    let style = TargetElem::target.set(Target::Paged).wrap();
95
96    let frame = (engine.routines.layout_frame)(
97        engine,
98        &content,
99        locator,
100        styles.chain(&style),
101        pod,
102    )?;
103    let Size { x, y } = frame.size();
104    Ok(dict! { "width" => x, "height" => y })
105}