1mod block;
4mod collect;
5mod compose;
6mod distribute;
7
8pub(crate) use self::block::unbreakable_pod;
9
10use std::collections::HashSet;
11use std::num::NonZeroUsize;
12use std::rc::Rc;
13
14use bumpalo::Bump;
15use comemo::{Track, Tracked, TrackedMut};
16use ecow::EcoVec;
17use typst_library::diag::{bail, At, SourceDiagnostic, SourceResult};
18use typst_library::engine::{Engine, Route, Sink, Traced};
19use typst_library::foundations::{Content, Packed, Resolve, StyleChain};
20use typst_library::introspection::{
21 Introspector, Location, Locator, LocatorLink, SplitLocator, Tag,
22};
23use typst_library::layout::{
24 Abs, ColumnsElem, Dir, Em, Fragment, Frame, PageElem, PlacementScope, Region,
25 Regions, Rel, Size,
26};
27use typst_library::model::{FootnoteElem, FootnoteEntry, LineNumberingScope, ParLine};
28use typst_library::routines::{Arenas, FragmentKind, Pair, RealizationKind, Routines};
29use typst_library::text::TextElem;
30use typst_library::World;
31use typst_utils::{NonZeroExt, Numeric};
32
33use self::block::{layout_multi_block, layout_single_block};
34use self::collect::{
35 collect, Child, LineChild, MultiChild, MultiSpill, PlacedChild, SingleChild,
36};
37use self::compose::{compose, Composer};
38use self::distribute::distribute;
39
40pub fn layout_frame(
42 engine: &mut Engine,
43 content: &Content,
44 locator: Locator,
45 styles: StyleChain,
46 region: Region,
47) -> SourceResult<Frame> {
48 layout_fragment(engine, content, locator, styles, region.into())
49 .map(Fragment::into_frame)
50}
51
52pub fn layout_fragment(
56 engine: &mut Engine,
57 content: &Content,
58 locator: Locator,
59 styles: StyleChain,
60 regions: Regions,
61) -> SourceResult<Fragment> {
62 layout_fragment_impl(
63 engine.routines,
64 engine.world,
65 engine.introspector,
66 engine.traced,
67 TrackedMut::reborrow_mut(&mut engine.sink),
68 engine.route.track(),
69 content,
70 locator.track(),
71 styles,
72 regions,
73 NonZeroUsize::ONE,
74 Rel::zero(),
75 )
76}
77
78#[typst_macros::time(span = elem.span())]
83pub fn layout_columns(
84 elem: &Packed<ColumnsElem>,
85 engine: &mut Engine,
86 locator: Locator,
87 styles: StyleChain,
88 regions: Regions,
89) -> SourceResult<Fragment> {
90 layout_fragment_impl(
91 engine.routines,
92 engine.world,
93 engine.introspector,
94 engine.traced,
95 TrackedMut::reborrow_mut(&mut engine.sink),
96 engine.route.track(),
97 &elem.body,
98 locator.track(),
99 styles,
100 regions,
101 elem.count(styles),
102 elem.gutter(styles),
103 )
104}
105
106#[comemo::memoize]
108#[allow(clippy::too_many_arguments)]
109fn layout_fragment_impl(
110 routines: &Routines,
111 world: Tracked<dyn World + '_>,
112 introspector: Tracked<Introspector>,
113 traced: Tracked<Traced>,
114 sink: TrackedMut<Sink>,
115 route: Tracked<Route>,
116 content: &Content,
117 locator: Tracked<Locator>,
118 styles: StyleChain,
119 regions: Regions,
120 columns: NonZeroUsize,
121 column_gutter: Rel<Abs>,
122) -> SourceResult<Fragment> {
123 if !regions.size.x.is_finite() && regions.expand.x {
124 bail!(content.span(), "cannot expand into infinite width");
125 }
126 if !regions.size.y.is_finite() && regions.expand.y {
127 bail!(content.span(), "cannot expand into infinite height");
128 }
129
130 let link = LocatorLink::new(locator);
131 let mut locator = Locator::link(&link).split();
132 let mut engine = Engine {
133 routines,
134 world,
135 introspector,
136 traced,
137 sink,
138 route: Route::extend(route),
139 };
140
141 engine.route.check_layout_depth().at(content.span())?;
142
143 let mut kind = FragmentKind::Block;
144 let arenas = Arenas::default();
145 let children = (engine.routines.realize)(
146 RealizationKind::LayoutFragment(&mut kind),
147 &mut engine,
148 &mut locator,
149 &arenas,
150 content,
151 styles,
152 )?;
153
154 layout_flow(
155 &mut engine,
156 &children,
157 &mut locator,
158 styles,
159 regions,
160 columns,
161 column_gutter,
162 kind.into(),
163 )
164}
165
166#[derive(Debug, Copy, Clone, Eq, PartialEq)]
168pub enum FlowMode {
169 Root,
172 Block,
174 Inline,
176}
177
178impl From<FragmentKind> for FlowMode {
179 fn from(value: FragmentKind) -> Self {
180 match value {
181 FragmentKind::Inline => Self::Inline,
182 FragmentKind::Block => Self::Block,
183 }
184 }
185}
186
187#[allow(clippy::too_many_arguments)]
189pub fn layout_flow<'a>(
190 engine: &mut Engine,
191 children: &[Pair<'a>],
192 locator: &mut SplitLocator<'a>,
193 shared: StyleChain<'a>,
194 mut regions: Regions,
195 columns: NonZeroUsize,
196 column_gutter: Rel<Abs>,
197 mode: FlowMode,
198) -> SourceResult<Fragment> {
199 let config = configuration(shared, regions, columns, column_gutter, mode);
201
202 let bump = Bump::new();
205 let children = collect(
206 engine,
207 &bump,
208 children,
209 locator.next(&()),
210 Size::new(config.columns.width, regions.full),
211 regions.expand.x,
212 mode,
213 )?;
214
215 let mut work = Work::new(&children);
216 let mut finished = vec![];
217
218 loop {
220 let frame = compose(engine, &mut work, &config, locator.next(&()), regions)?;
221 finished.push(frame);
222
223 if work.done() && (!regions.expand.y || regions.backlog.is_empty()) {
226 break;
227 }
228
229 regions.next();
230 }
231
232 Ok(Fragment::frames(finished))
233}
234
235fn configuration<'x>(
237 shared: StyleChain<'x>,
238 regions: Regions,
239 columns: NonZeroUsize,
240 column_gutter: Rel<Abs>,
241 mode: FlowMode,
242) -> Config<'x> {
243 Config {
244 mode,
245 shared,
246 columns: {
247 let mut count = columns.get();
248 if !regions.size.x.is_finite() {
249 count = 1;
250 }
251
252 let gutter = column_gutter.relative_to(regions.base().x);
253 let width = (regions.size.x - gutter * (count - 1) as f64) / count as f64;
254 let dir = TextElem::dir_in(shared);
255 ColumnConfig { count, width, gutter, dir }
256 },
257 footnote: FootnoteConfig {
258 separator: FootnoteEntry::separator_in(shared),
259 clearance: FootnoteEntry::clearance_in(shared),
260 gap: FootnoteEntry::gap_in(shared),
261 expand: regions.expand.x,
262 },
263 line_numbers: (mode == FlowMode::Root).then(|| LineNumberConfig {
264 scope: ParLine::numbering_scope_in(shared),
265 default_clearance: {
266 let width = if PageElem::flipped_in(shared) {
267 PageElem::height_in(shared)
268 } else {
269 PageElem::width_in(shared)
270 };
271
272 (0.026 * width.unwrap_or_default()).clamp(
276 Em::new(0.75).resolve(shared).max(Abs::zero()),
277 Em::new(2.5).resolve(shared).max(Abs::zero()),
278 )
279 },
280 }),
281 }
282}
283
284#[derive(Clone)]
290struct Work<'a, 'b> {
291 children: &'b [Child<'a>],
293 spill: Option<MultiSpill<'a, 'b>>,
295 floats: EcoVec<&'b PlacedChild<'a>>,
297 footnotes: EcoVec<Packed<FootnoteElem>>,
299 footnote_spill: Option<std::vec::IntoIter<Frame>>,
301 tags: EcoVec<&'a Tag>,
303 skips: Rc<HashSet<Location>>,
307}
308
309impl<'a, 'b> Work<'a, 'b> {
310 fn new(children: &'b [Child<'a>]) -> Self {
312 Self {
313 children,
314 spill: None,
315 floats: EcoVec::new(),
316 footnotes: EcoVec::new(),
317 footnote_spill: None,
318 tags: EcoVec::new(),
319 skips: Rc::new(HashSet::new()),
320 }
321 }
322
323 fn head(&self) -> Option<&'b Child<'a>> {
325 self.children.first()
326 }
327
328 fn advance(&mut self) {
330 self.children = &self.children[1..];
331 }
332
333 fn done(&self) -> bool {
335 self.children.is_empty()
336 && self.spill.is_none()
337 && self.floats.is_empty()
338 && self.footnote_spill.is_none()
339 && self.footnotes.is_empty()
340 }
341
342 fn extend_skips(&mut self, skips: &[Location]) {
345 if !skips.is_empty() {
346 Rc::make_mut(&mut self.skips).extend(skips.iter().copied());
347 }
348 }
349}
350
351struct Config<'x> {
353 mode: FlowMode,
356 shared: StyleChain<'x>,
359 columns: ColumnConfig,
361 footnote: FootnoteConfig,
363 line_numbers: Option<LineNumberConfig>,
365}
366
367struct FootnoteConfig {
369 separator: Content,
371 clearance: Abs,
373 gap: Abs,
375 expand: bool,
377}
378
379struct ColumnConfig {
381 count: usize,
383 width: Abs,
385 gutter: Abs,
387 dir: Dir,
390}
391
392struct LineNumberConfig {
394 scope: LineNumberingScope,
396 default_clearance: Abs,
408}
409
410type FlowResult<T> = Result<T, Stop>;
415
416enum Stop {
418 Finish(bool),
421 Relayout(PlacementScope),
423 Error(EcoVec<SourceDiagnostic>),
425}
426
427impl From<EcoVec<SourceDiagnostic>> for Stop {
428 fn from(error: EcoVec<SourceDiagnostic>) -> Self {
429 Stop::Error(error)
430 }
431}