typst_library/layout/
container.rs

1use crate::diag::{bail, SourceResult};
2use crate::engine::Engine;
3use crate::foundations::{
4    cast, elem, Args, AutoValue, Construct, Content, NativeElement, Packed, Smart,
5    StyleChain, Value,
6};
7use crate::introspection::Locator;
8use crate::layout::{
9    Abs, Corners, Em, Fr, Fragment, Frame, Length, Region, Regions, Rel, Sides, Size,
10    Spacing,
11};
12use crate::visualize::{Paint, Stroke};
13
14/// An inline-level container that sizes content.
15///
16/// All elements except inline math, text, and boxes are block-level and cannot
17/// occur inside of a [paragraph]($par). The box function can be used to
18/// integrate such elements into a paragraph. Boxes take the size of their
19/// contents by default but can also be sized explicitly.
20///
21/// # Example
22/// ```example
23/// Refer to the docs
24/// #box(
25///   height: 9pt,
26///   image("docs.svg")
27/// )
28/// for more information.
29/// ```
30#[elem]
31pub struct BoxElem {
32    /// The width of the box.
33    ///
34    /// Boxes can have [fractional]($fraction) widths, as the example below
35    /// demonstrates.
36    ///
37    /// _Note:_ Currently, only boxes and only their widths might be fractionally
38    /// sized within paragraphs. Support for fractionally sized images, shapes,
39    /// and more might be added in the future.
40    ///
41    /// ```example
42    /// Line in #box(width: 1fr, line(length: 100%)) between.
43    /// ```
44    pub width: Sizing,
45
46    /// The height of the box.
47    pub height: Smart<Rel<Length>>,
48
49    /// An amount to shift the box's baseline by.
50    ///
51    /// ```example
52    /// Image: #box(baseline: 40%, image("tiger.jpg", width: 2cm)).
53    /// ```
54    #[resolve]
55    pub baseline: Rel<Length>,
56
57    /// The box's background color. See the
58    /// [rectangle's documentation]($rect.fill) for more details.
59    pub fill: Option<Paint>,
60
61    /// The box's border color. See the
62    /// [rectangle's documentation]($rect.stroke) for more details.
63    #[resolve]
64    #[fold]
65    pub stroke: Sides<Option<Option<Stroke>>>,
66
67    /// How much to round the box's corners. See the
68    /// [rectangle's documentation]($rect.radius) for more details.
69    #[resolve]
70    #[fold]
71    pub radius: Corners<Option<Rel<Length>>>,
72
73    /// How much to pad the box's content.
74    ///
75    /// _Note:_ When the box contains text, its exact size depends on the
76    /// current [text edges]($text.top-edge).
77    ///
78    /// ```example
79    /// #rect(inset: 0pt)[Tight]
80    /// ```
81    #[resolve]
82    #[fold]
83    pub inset: Sides<Option<Rel<Length>>>,
84
85    /// How much to expand the box's size without affecting the layout.
86    ///
87    /// This is useful to prevent padding from affecting line layout. For a
88    /// generalized version of the example below, see the documentation for the
89    /// [raw text's block parameter]($raw.block).
90    ///
91    /// ```example
92    /// An inline
93    /// #box(
94    ///   fill: luma(235),
95    ///   inset: (x: 3pt, y: 0pt),
96    ///   outset: (y: 3pt),
97    ///   radius: 2pt,
98    /// )[rectangle].
99    /// ```
100    #[resolve]
101    #[fold]
102    pub outset: Sides<Option<Rel<Length>>>,
103
104    /// Whether to clip the content inside the box.
105    ///
106    /// Clipping is useful when the box's content is larger than the box itself,
107    /// as any content that exceeds the box's bounds will be hidden.
108    ///
109    /// ```example
110    /// #box(
111    ///   width: 50pt,
112    ///   height: 50pt,
113    ///   clip: true,
114    ///   image("tiger.jpg", width: 100pt, height: 100pt)
115    /// )
116    /// ```
117    #[default(false)]
118    pub clip: bool,
119
120    /// The contents of the box.
121    #[positional]
122    #[borrowed]
123    pub body: Option<Content>,
124}
125
126/// An inline-level container that can produce arbitrary items that can break
127/// across lines.
128#[elem(Construct)]
129pub struct InlineElem {
130    /// A callback that is invoked with the regions to produce arbitrary
131    /// inline items.
132    #[required]
133    #[internal]
134    body: callbacks::InlineCallback,
135}
136
137impl Construct for InlineElem {
138    fn construct(_: &mut Engine, args: &mut Args) -> SourceResult<Content> {
139        bail!(args.span, "cannot be constructed manually");
140    }
141}
142
143impl InlineElem {
144    /// Create an inline-level item with a custom layouter.
145    #[allow(clippy::type_complexity)]
146    pub fn layouter<T: NativeElement>(
147        captured: Packed<T>,
148        callback: fn(
149            content: &Packed<T>,
150            engine: &mut Engine,
151            locator: Locator,
152            styles: StyleChain,
153            region: Size,
154        ) -> SourceResult<Vec<InlineItem>>,
155    ) -> Self {
156        Self::new(callbacks::InlineCallback::new(captured, callback))
157    }
158}
159
160impl Packed<InlineElem> {
161    /// Layout the element.
162    pub fn layout(
163        &self,
164        engine: &mut Engine,
165        locator: Locator,
166        styles: StyleChain,
167        region: Size,
168    ) -> SourceResult<Vec<InlineItem>> {
169        self.body.call(engine, locator, styles, region)
170    }
171}
172
173/// Layouted items suitable for placing in a paragraph.
174#[derive(Debug, Clone)]
175pub enum InlineItem {
176    /// Absolute spacing between other items, and whether it is weak.
177    Space(Abs, bool),
178    /// Layouted inline-level content.
179    Frame(Frame),
180}
181
182/// A block-level container.
183///
184/// Such a container can be used to separate content, size it, and give it a
185/// background or border.
186///
187/// Blocks are also the primary way to control whether text becomes part of a
188/// paragraph or not. See [the paragraph documentation]($par/#what-becomes-a-paragraph)
189/// for more details.
190///
191/// # Examples
192/// With a block, you can give a background to content while still allowing it
193/// to break across multiple pages.
194/// ```example
195/// #set page(height: 100pt)
196/// #block(
197///   fill: luma(230),
198///   inset: 8pt,
199///   radius: 4pt,
200///   lorem(30),
201/// )
202/// ```
203///
204/// Blocks are also useful to force elements that would otherwise be inline to
205/// become block-level, especially when writing show rules.
206/// ```example
207/// #show heading: it => it.body
208/// = Blockless
209/// More text.
210///
211/// #show heading: it => block(it.body)
212/// = Blocky
213/// More text.
214/// ```
215#[elem]
216pub struct BlockElem {
217    /// The block's width.
218    ///
219    /// ```example
220    /// #set align(center)
221    /// #block(
222    ///   width: 60%,
223    ///   inset: 8pt,
224    ///   fill: silver,
225    ///   lorem(10),
226    /// )
227    /// ```
228    pub width: Smart<Rel<Length>>,
229
230    /// The block's height. When the height is larger than the remaining space
231    /// on a page and [`breakable`]($block.breakable) is `{true}`, the
232    /// block will continue on the next page with the remaining height.
233    ///
234    /// ```example
235    /// #set page(height: 80pt)
236    /// #set align(center)
237    /// #block(
238    ///   width: 80%,
239    ///   height: 150%,
240    ///   fill: aqua,
241    /// )
242    /// ```
243    pub height: Sizing,
244
245    /// Whether the block can be broken and continue on the next page.
246    ///
247    /// ```example
248    /// #set page(height: 80pt)
249    /// The following block will
250    /// jump to its own page.
251    /// #block(
252    ///   breakable: false,
253    ///   lorem(15),
254    /// )
255    /// ```
256    #[default(true)]
257    pub breakable: bool,
258
259    /// The block's background color. See the
260    /// [rectangle's documentation]($rect.fill) for more details.
261    pub fill: Option<Paint>,
262
263    /// The block's border color. See the
264    /// [rectangle's documentation]($rect.stroke) for more details.
265    #[resolve]
266    #[fold]
267    pub stroke: Sides<Option<Option<Stroke>>>,
268
269    /// How much to round the block's corners. See the
270    /// [rectangle's documentation]($rect.radius) for more details.
271    #[resolve]
272    #[fold]
273    pub radius: Corners<Option<Rel<Length>>>,
274
275    /// How much to pad the block's content. See the
276    /// [box's documentation]($box.inset) for more details.
277    #[resolve]
278    #[fold]
279    pub inset: Sides<Option<Rel<Length>>>,
280
281    /// How much to expand the block's size without affecting the layout. See
282    /// the [box's documentation]($box.outset) for more details.
283    #[resolve]
284    #[fold]
285    pub outset: Sides<Option<Rel<Length>>>,
286
287    /// The spacing around the block. When `{auto}`, inherits the paragraph
288    /// [`spacing`]($par.spacing).
289    ///
290    /// For two adjacent blocks, the larger of the first block's `above` and the
291    /// second block's `below` spacing wins. Moreover, block spacing takes
292    /// precedence over paragraph [`spacing`]($par.spacing).
293    ///
294    /// Note that this is only a shorthand to set `above` and `below` to the
295    /// same value. Since the values for `above` and `below` might differ, a
296    /// [context] block only provides access to `{block.above}` and
297    /// `{block.below}`, not to `{block.spacing}` directly.
298    ///
299    /// This property can be used in combination with a show rule to adjust the
300    /// spacing around arbitrary block-level elements.
301    ///
302    /// ```example
303    /// #set align(center)
304    /// #show math.equation: set block(above: 8pt, below: 16pt)
305    ///
306    /// This sum of $x$ and $y$:
307    /// $ x + y = z $
308    /// A second paragraph.
309    /// ```
310    #[external]
311    #[default(Em::new(1.2).into())]
312    pub spacing: Spacing,
313
314    /// The spacing between this block and its predecessor.
315    #[parse(
316        let spacing = args.named("spacing")?;
317        args.named("above")?.or(spacing)
318    )]
319    pub above: Smart<Spacing>,
320
321    /// The spacing between this block and its successor.
322    #[parse(args.named("below")?.or(spacing))]
323    pub below: Smart<Spacing>,
324
325    /// Whether to clip the content inside the block.
326    ///
327    /// Clipping is useful when the block's content is larger than the block itself,
328    /// as any content that exceeds the block's bounds will be hidden.
329    ///
330    /// ```example
331    /// #block(
332    ///   width: 50pt,
333    ///   height: 50pt,
334    ///   clip: true,
335    ///   image("tiger.jpg", width: 100pt, height: 100pt)
336    /// )
337    /// ```
338    #[default(false)]
339    pub clip: bool,
340
341    /// Whether this block must stick to the following one, with no break in
342    /// between.
343    ///
344    /// This is, by default, set on heading blocks to prevent orphaned headings
345    /// at the bottom of the page.
346    ///
347    /// ```example
348    /// >>> #set page(height: 140pt)
349    /// // Disable stickiness of headings.
350    /// #show heading: set block(sticky: false)
351    /// #lorem(20)
352    ///
353    /// = Chapter
354    /// #lorem(10)
355    /// ```
356    #[default(false)]
357    pub sticky: bool,
358
359    /// The contents of the block.
360    #[positional]
361    #[borrowed]
362    pub body: Option<BlockBody>,
363}
364
365impl BlockElem {
366    /// Create a block with a custom single-region layouter.
367    ///
368    /// Such a block must have `breakable: false` (which is set by this
369    /// constructor).
370    pub fn single_layouter<T: NativeElement>(
371        captured: Packed<T>,
372        f: fn(
373            content: &Packed<T>,
374            engine: &mut Engine,
375            locator: Locator,
376            styles: StyleChain,
377            region: Region,
378        ) -> SourceResult<Frame>,
379    ) -> Self {
380        Self::new()
381            .with_breakable(false)
382            .with_body(Some(BlockBody::SingleLayouter(
383                callbacks::BlockSingleCallback::new(captured, f),
384            )))
385    }
386
387    /// Create a block with a custom multi-region layouter.
388    pub fn multi_layouter<T: NativeElement>(
389        captured: Packed<T>,
390        f: fn(
391            content: &Packed<T>,
392            engine: &mut Engine,
393            locator: Locator,
394            styles: StyleChain,
395            regions: Regions,
396        ) -> SourceResult<Fragment>,
397    ) -> Self {
398        Self::new().with_body(Some(BlockBody::MultiLayouter(
399            callbacks::BlockMultiCallback::new(captured, f),
400        )))
401    }
402}
403
404/// The contents of a block.
405#[derive(Debug, Clone, PartialEq, Hash)]
406pub enum BlockBody {
407    /// The block contains normal content.
408    Content(Content),
409    /// The block contains a layout callback that needs access to just one
410    /// base region.
411    SingleLayouter(callbacks::BlockSingleCallback),
412    /// The block contains a layout callback that needs access to the exact
413    /// regions.
414    MultiLayouter(callbacks::BlockMultiCallback),
415}
416
417impl Default for BlockBody {
418    fn default() -> Self {
419        Self::Content(Content::default())
420    }
421}
422
423cast! {
424    BlockBody,
425    self => match self {
426        Self::Content(content) => content.into_value(),
427        _ => Value::Auto,
428    },
429    v: Content => Self::Content(v),
430}
431
432/// Defines how to size something along an axis.
433#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
434pub enum Sizing {
435    /// A track that fits its item's contents.
436    Auto,
437    /// A size specified in absolute terms and relative to the parent's size.
438    Rel(Rel),
439    /// A size specified as a fraction of the remaining free space in the
440    /// parent.
441    Fr(Fr),
442}
443
444impl Sizing {
445    /// Whether this is an automatic sizing.
446    pub fn is_auto(self) -> bool {
447        matches!(self, Self::Auto)
448    }
449
450    /// Whether this is fractional sizing.
451    pub fn is_fractional(self) -> bool {
452        matches!(self, Self::Fr(_))
453    }
454}
455
456impl Default for Sizing {
457    fn default() -> Self {
458        Self::Auto
459    }
460}
461
462impl From<Smart<Rel>> for Sizing {
463    fn from(smart: Smart<Rel>) -> Self {
464        match smart {
465            Smart::Auto => Self::Auto,
466            Smart::Custom(rel) => Self::Rel(rel),
467        }
468    }
469}
470
471impl<T: Into<Spacing>> From<T> for Sizing {
472    fn from(spacing: T) -> Self {
473        match spacing.into() {
474            Spacing::Rel(rel) => Self::Rel(rel),
475            Spacing::Fr(fr) => Self::Fr(fr),
476        }
477    }
478}
479
480cast! {
481    Sizing,
482    self => match self {
483        Self::Auto => Value::Auto,
484        Self::Rel(rel) => rel.into_value(),
485        Self::Fr(fr) => fr.into_value(),
486    },
487    _: AutoValue => Self::Auto,
488    v: Rel<Length> => Self::Rel(v),
489    v: Fr => Self::Fr(v),
490}
491
492/// Manual closure implementations for layout callbacks.
493///
494/// Normal closures are not `Hash`, so we can't use them.
495mod callbacks {
496    use super::*;
497
498    macro_rules! callback {
499        ($name:ident = ($($param:ident: $param_ty:ty),* $(,)?) -> $ret:ty) => {
500            #[derive(Debug, Clone, PartialEq, Hash)]
501            pub struct $name {
502                captured: Content,
503                f: fn(&Content, $($param_ty),*) -> $ret,
504            }
505
506            impl $name {
507                pub fn new<T: NativeElement>(
508                    captured: Packed<T>,
509                    f: fn(&Packed<T>, $($param_ty),*) -> $ret,
510                ) -> Self {
511                    Self {
512                        // Type-erased the content.
513                        captured: captured.pack(),
514                        // Safety: The only difference between the two function
515                        // pointer types is the type of the first parameter,
516                        // which changes from `&Packed<T>` to `&Content`. This
517                        // is safe because:
518                        // - `Packed<T>` is a transparent wrapper around
519                        //   `Content`, so for any `T` it has the same memory
520                        //   representation as `Content`.
521                        // - While `Packed<T>` imposes the additional constraint
522                        //   that the content is of type `T`, this constraint is
523                        //   upheld: It is initially the case because we store a
524                        //   `Packed<T>` above. It keeps being the case over the
525                        //   lifetime of the closure because `capture` is a
526                        //   private field and `Content`'s `Clone` impl is
527                        //   guaranteed to retain the type (if it didn't,
528                        //   literally everything would break).
529                        #[allow(clippy::missing_transmute_annotations)]
530                        f: unsafe { std::mem::transmute(f) },
531                    }
532                }
533
534                pub fn call(&self, $($param: $param_ty),*) -> $ret {
535                    (self.f)(&self.captured, $($param),*)
536                }
537            }
538        };
539    }
540
541    callback! {
542        InlineCallback = (
543            engine: &mut Engine,
544            locator: Locator,
545            styles: StyleChain,
546            region: Size,
547        ) -> SourceResult<Vec<InlineItem>>
548    }
549
550    callback! {
551        BlockSingleCallback = (
552            engine: &mut Engine,
553            locator: Locator,
554            styles: StyleChain,
555            region: Region,
556        ) -> SourceResult<Frame>
557    }
558
559    callback! {
560        BlockMultiCallback = (
561            engine: &mut Engine,
562            locator: Locator,
563            styles: StyleChain,
564            regions: Regions,
565        ) -> SourceResult<Fragment>
566    }
567}