typst_layout/
repeat.rs

1use typst_library::diag::{bail, SourceResult};
2use typst_library::engine::Engine;
3use typst_library::foundations::{Packed, Resolve, StyleChain};
4use typst_library::introspection::Locator;
5use typst_library::layout::{
6    Abs, AlignElem, Axes, Frame, Point, Region, RepeatElem, Size,
7};
8use typst_utils::Numeric;
9
10/// Layout the repeated content.
11#[typst_macros::time(span = elem.span())]
12pub fn layout_repeat(
13    elem: &Packed<RepeatElem>,
14    engine: &mut Engine,
15    locator: Locator,
16    styles: StyleChain,
17    region: Region,
18) -> SourceResult<Frame> {
19    let pod = Region::new(region.size, Axes::new(false, false));
20    let piece = crate::layout_frame(engine, &elem.body, locator, styles, pod)?;
21    let size = Size::new(region.size.x, piece.height());
22
23    if !size.is_finite() {
24        bail!(elem.span(), "repeat with no size restrictions");
25    }
26
27    let mut frame = Frame::soft(size);
28    if piece.has_baseline() {
29        frame.set_baseline(piece.baseline());
30    }
31
32    let mut gap = elem.gap(styles).resolve(styles);
33    let fill = region.size.x;
34    let width = piece.width();
35
36    // We need to fit the body N times, but the number of gaps is (N - 1):
37    // N * w + (N - 1) * g ≤ F
38    // where N - body count (count)
39    //       w - body width (width)
40    //       g - gap width (gap)
41    //       F - available space to fill (fill)
42    //
43    // N * w + N * g - g ≤ F
44    // N * (w + g) ≤ F + g
45    // N ≤ (F + g) / (w + g)
46    // N = ⌊(F + g) / (w + g)⌋
47    let count = ((fill + gap) / (width + gap)).floor();
48    let remaining = (fill + gap) % (width + gap);
49
50    let justify = elem.justify(styles);
51    if justify {
52        gap += remaining / (count - 1.0);
53    }
54
55    let align = AlignElem::alignment_in(styles).resolve(styles);
56    let mut offset = Abs::zero();
57    if count == 1.0 || !justify {
58        offset += align.x.position(remaining);
59    }
60
61    if width > Abs::zero() {
62        for _ in 0..(count as usize).min(1000) {
63            frame.push_frame(Point::with_x(offset), piece.clone());
64            offset += width + gap;
65        }
66    }
67
68    Ok(frame)
69}