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