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}