1mod block;
4mod collect;
5mod compose;
6mod distribute;
7
8pub(crate) use self::block::unbreakable_pod;
9
10use std::num::NonZeroUsize;
11use std::rc::Rc;
12
13use bumpalo::Bump;
14use comemo::{Track, Tracked, TrackedMut};
15use ecow::EcoVec;
16use rustc_hash::FxHashSet;
17use typst_library::diag::{At, SourceDiagnostic, SourceResult, bail};
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::pdf::ArtifactKind;
29use typst_library::routines::{Arenas, FragmentKind, Pair, RealizationKind};
30use typst_library::text::TextElem;
31use typst_library::{Library, World};
32use typst_utils::{LazyHash, NonZeroExt, Numeric, Protected};
33
34use self::block::{layout_multi_block, layout_single_block};
35use self::collect::{
36 Child, LineChild, MultiChild, MultiSpill, PlacedChild, SingleChild, collect,
37};
38use self::compose::{Composer, compose};
39use self::distribute::distribute;
40
41pub fn layout_frame(
43 engine: &mut Engine,
44 content: &Content,
45 locator: Locator,
46 styles: StyleChain,
47 region: Region,
48) -> SourceResult<Frame> {
49 layout_fragment(engine, content, locator, styles, region.into())
50 .map(Fragment::into_frame)
51}
52
53pub fn layout_fragment(
57 engine: &mut Engine,
58 content: &Content,
59 locator: Locator,
60 styles: StyleChain,
61 regions: Regions,
62) -> SourceResult<Fragment> {
63 layout_fragment_impl(
64 engine.world,
65 engine.library,
66 engine.introspector.into_raw(),
67 engine.traced,
68 TrackedMut::reborrow_mut(&mut engine.sink),
69 engine.route.track(),
70 content,
71 locator.track(),
72 styles,
73 regions,
74 NonZeroUsize::ONE,
75 Rel::zero(),
76 )
77}
78
79#[typst_macros::time(span = elem.span())]
84pub fn layout_columns(
85 elem: &Packed<ColumnsElem>,
86 engine: &mut Engine,
87 locator: Locator,
88 styles: StyleChain,
89 regions: Regions,
90) -> SourceResult<Fragment> {
91 layout_fragment_impl(
92 engine.world,
93 engine.library,
94 engine.introspector.into_raw(),
95 engine.traced,
96 TrackedMut::reborrow_mut(&mut engine.sink),
97 engine.route.track(),
98 &elem.body,
99 locator.track(),
100 styles,
101 regions,
102 elem.count.get(styles),
103 elem.gutter.resolve(styles),
104 )
105}
106
107#[comemo::memoize]
109#[allow(clippy::too_many_arguments)]
110fn layout_fragment_impl(
111 world: Tracked<dyn World + '_>,
112 library: &LazyHash<Library>,
113 introspector: Tracked<dyn Introspector + '_>,
114 traced: Tracked<Traced>,
115 sink: TrackedMut<Sink>,
116 route: Tracked<Route>,
117 content: &Content,
118 locator: Tracked<Locator>,
119 styles: StyleChain,
120 regions: Regions,
121 columns: NonZeroUsize,
122 column_gutter: Rel<Abs>,
123) -> SourceResult<Fragment> {
124 if !regions.size.x.is_finite() && regions.expand.x {
125 bail!(content.span(), "cannot expand into infinite width");
126 }
127 if !regions.size.y.is_finite() && regions.expand.y {
128 bail!(content.span(), "cannot expand into infinite height");
129 }
130
131 let introspector = Protected::from_raw(introspector);
132 let link = LocatorLink::new(locator);
133 let mut locator = Locator::link(&link).split();
134 let mut engine = Engine {
135 library,
136 world,
137 introspector,
138 traced,
139 sink,
140 route: Route::extend(route),
141 };
142
143 engine.route.check_layout_depth().at(content.span())?;
144
145 let mut kind = FragmentKind::Block;
146 let arenas = Arenas::default();
147 let children = (engine.library.routines.realize)(
148 RealizationKind::Fragment { kind: &mut kind },
149 &mut engine,
150 &mut locator,
151 &arenas,
152 content,
153 styles,
154 )?;
155
156 layout_flow(
157 &mut engine,
158 &children,
159 &mut locator,
160 styles,
161 regions,
162 columns,
163 column_gutter,
164 kind.into(),
165 )
166}
167
168#[derive(Debug, Copy, Clone, Eq, PartialEq)]
170pub enum FlowMode {
171 Root,
174 Block,
176 Inline,
178}
179
180impl From<FragmentKind> for FlowMode {
181 fn from(value: FragmentKind) -> Self {
182 match value {
183 FragmentKind::Inline => Self::Inline,
184 FragmentKind::Block => Self::Block,
185 }
186 }
187}
188
189#[allow(clippy::too_many_arguments)]
191pub fn layout_flow<'a>(
192 engine: &mut Engine,
193 children: &[Pair<'a>],
194 locator: &mut SplitLocator<'a>,
195 shared: StyleChain<'a>,
196 mut regions: Regions,
197 columns: NonZeroUsize,
198 column_gutter: Rel<Abs>,
199 mode: FlowMode,
200) -> SourceResult<Fragment> {
201 let config = configuration(shared, regions, columns, column_gutter, mode);
203
204 let bump = Bump::new();
207 let children = collect(
208 engine,
209 &bump,
210 children,
211 locator.next(&()),
212 Size::new(config.columns.width, regions.full),
213 regions.expand.x,
214 mode,
215 )?;
216
217 let mut work = Work::new(&children);
218 let mut finished = vec![];
219
220 loop {
222 let frame = compose(engine, &mut work, &config, locator.next(&()), regions)?;
223 finished.push(frame);
224
225 if work.done() && (!regions.expand.y || regions.backlog.is_empty()) {
228 break;
229 }
230
231 regions.next();
232 }
233
234 Ok(Fragment::frames(finished))
235}
236
237fn configuration<'x>(
239 shared: StyleChain<'x>,
240 regions: Regions,
241 columns: NonZeroUsize,
242 column_gutter: Rel<Abs>,
243 mode: FlowMode,
244) -> Config<'x> {
245 Config {
246 mode,
247 shared,
248 columns: {
249 let mut count = columns.get();
250 if !regions.size.x.is_finite() {
251 count = 1;
252 }
253
254 let gutter = column_gutter.relative_to(regions.base().x);
255 let width = (regions.size.x - gutter * (count - 1) as f64) / count as f64;
256 let dir = shared.resolve(TextElem::dir);
257 ColumnConfig { count, width, gutter, dir }
258 },
259 footnote: FootnoteConfig {
260 separator: shared
261 .get_cloned(FootnoteEntry::separator)
262 .artifact(ArtifactKind::Other),
263 clearance: shared.resolve(FootnoteEntry::clearance),
264 gap: shared.resolve(FootnoteEntry::gap),
265 expand: regions.expand.x,
266 },
267 line_numbers: (mode == FlowMode::Root).then(|| LineNumberConfig {
268 scope: shared.get(ParLine::numbering_scope),
269 default_clearance: {
270 let width = if shared.get(PageElem::flipped) {
271 shared.resolve(PageElem::height)
272 } else {
273 shared.resolve(PageElem::width)
274 };
275
276 (0.026 * width.unwrap_or_default()).clamp(
280 Em::new(0.75).resolve(shared).max(Abs::zero()),
281 Em::new(2.5).resolve(shared).max(Abs::zero()),
282 )
283 },
284 }),
285 }
286}
287
288#[derive(Clone)]
294struct Work<'a, 'b> {
295 children: &'b [Child<'a>],
297 spill: Option<MultiSpill<'a, 'b>>,
299 floats: EcoVec<&'b PlacedChild<'a>>,
301 footnotes: EcoVec<Packed<FootnoteElem>>,
303 footnote_spill: Option<std::vec::IntoIter<Frame>>,
305 tags: EcoVec<&'a Tag>,
307 skips: Rc<FxHashSet<Location>>,
311}
312
313impl<'a, 'b> Work<'a, 'b> {
314 fn new(children: &'b [Child<'a>]) -> Self {
316 Self {
317 children,
318 spill: None,
319 floats: EcoVec::new(),
320 footnotes: EcoVec::new(),
321 footnote_spill: None,
322 tags: EcoVec::new(),
323 skips: Rc::new(FxHashSet::default()),
324 }
325 }
326
327 fn head(&self) -> Option<&'b Child<'a>> {
329 self.children.first()
330 }
331
332 fn advance(&mut self) {
334 self.children = &self.children[1..];
335 }
336
337 fn done(&self) -> bool {
339 self.children.is_empty()
340 && self.spill.is_none()
341 && self.floats.is_empty()
342 && self.footnote_spill.is_none()
343 && self.footnotes.is_empty()
344 }
345
346 fn extend_skips(&mut self, skips: &[Location]) {
349 if !skips.is_empty() {
350 Rc::make_mut(&mut self.skips).extend(skips.iter().copied());
351 }
352 }
353}
354
355struct Config<'x> {
357 mode: FlowMode,
360 shared: StyleChain<'x>,
363 columns: ColumnConfig,
365 footnote: FootnoteConfig,
367 line_numbers: Option<LineNumberConfig>,
369}
370
371struct FootnoteConfig {
373 separator: Content,
375 clearance: Abs,
377 gap: Abs,
379 expand: bool,
381}
382
383struct ColumnConfig {
385 count: usize,
387 width: Abs,
389 gutter: Abs,
391 dir: Dir,
394}
395
396struct LineNumberConfig {
398 scope: LineNumberingScope,
400 default_clearance: Abs,
412}
413
414type FlowResult<T> = Result<T, Stop>;
419
420enum Stop {
422 Finish(bool),
425 Relayout(PlacementScope),
427 Error(EcoVec<SourceDiagnostic>),
429}
430
431impl From<EcoVec<SourceDiagnostic>> for Stop {
432 fn from(error: EcoVec<SourceDiagnostic>) -> Self {
433 Stop::Error(error)
434 }
435}