typst_layout/
repeat.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
use typst_library::diag::{bail, SourceResult};
use typst_library::engine::Engine;
use typst_library::foundations::{Packed, Resolve, StyleChain};
use typst_library::introspection::Locator;
use typst_library::layout::{
    Abs, AlignElem, Axes, Frame, Point, Region, RepeatElem, Size,
};
use typst_utils::Numeric;

/// Layout the repeated content.
#[typst_macros::time(span = elem.span())]
pub fn layout_repeat(
    elem: &Packed<RepeatElem>,
    engine: &mut Engine,
    locator: Locator,
    styles: StyleChain,
    region: Region,
) -> SourceResult<Frame> {
    let pod = Region::new(region.size, Axes::new(false, false));
    let piece = crate::layout_frame(engine, &elem.body, locator, styles, pod)?;
    let size = Size::new(region.size.x, piece.height());

    if !size.is_finite() {
        bail!(elem.span(), "repeat with no size restrictions");
    }

    let mut frame = Frame::soft(size);
    if piece.has_baseline() {
        frame.set_baseline(piece.baseline());
    }

    let mut gap = elem.gap(styles).resolve(styles);
    let fill = region.size.x;
    let width = piece.width();

    // We need to fit the body N times, but the number of gaps is (N - 1):
    // N * w + (N - 1) * g ≤ F
    // where N - body count (count)
    //       w - body width (width)
    //       g - gap width (gap)
    //       F - available space to fill (fill)
    //
    // N * w + N * g - g ≤ F
    // N * (w + g) ≤ F + g
    // N ≤ (F + g) / (w + g)
    // N = ⌊(F + g) / (w + g)⌋
    let count = ((fill + gap) / (width + gap)).floor();
    let remaining = (fill + gap) % (width + gap);

    let justify = elem.justify(styles);
    if justify {
        gap += remaining / (count - 1.0);
    }

    let align = AlignElem::alignment_in(styles).resolve(styles);
    let mut offset = Abs::zero();
    if count == 1.0 || !justify {
        offset += align.x.position(remaining);
    }

    if width > Abs::zero() {
        for _ in 0..(count as usize).min(1000) {
            frame.push_frame(Point::with_x(offset), piece.clone());
            offset += width + gap;
        }
    }

    Ok(frame)
}