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}