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