typst_library/layout/
container.rs

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