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