Skip to main content

typst_library/layout/
container.rs

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