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::World;
18use typst_library::diag::{At, SourceDiagnostic, SourceResult, bail};
19use typst_library::engine::{Engine, Route, Sink, Traced};
20use typst_library::foundations::{Content, Packed, Resolve, StyleChain};
21use typst_library::introspection::{
22 Introspector, Location, Locator, LocatorLink, SplitLocator, Tag,
23};
24use typst_library::layout::{
25 Abs, ColumnsElem, Dir, Em, Fragment, Frame, PageElem, PlacementScope, Region,
26 Regions, Rel, Size,
27};
28use typst_library::model::{FootnoteElem, FootnoteEntry, LineNumberingScope, ParLine};
29use typst_library::pdf::ArtifactKind;
30use typst_library::routines::{Arenas, FragmentKind, Pair, RealizationKind, Routines};
31use typst_library::text::TextElem;
32use typst_utils::{NonZeroExt, Numeric};
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.routines,
65 engine.world,
66 engine.introspector,
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.routines,
93 engine.world,
94 engine.introspector,
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 routines: &Routines,
112 world: Tracked<dyn World + '_>,
113 introspector: Tracked<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 link = LocatorLink::new(locator);
132 let mut locator = Locator::link(&link).split();
133 let mut engine = Engine {
134 routines,
135 world,
136 introspector,
137 traced,
138 sink,
139 route: Route::extend(route),
140 };
141
142 engine.route.check_layout_depth().at(content.span())?;
143
144 let mut kind = FragmentKind::Block;
145 let arenas = Arenas::default();
146 let children = (engine.routines.realize)(
147 RealizationKind::LayoutFragment { kind: &mut kind },
148 &mut engine,
149 &mut locator,
150 &arenas,
151 content,
152 styles,
153 )?;
154
155 layout_flow(
156 &mut engine,
157 &children,
158 &mut locator,
159 styles,
160 regions,
161 columns,
162 column_gutter,
163 kind.into(),
164 )
165}
166
167#[derive(Debug, Copy, Clone, Eq, PartialEq)]
169pub enum FlowMode {
170 Root,
173 Block,
175 Inline,
177}
178
179impl From<FragmentKind> for FlowMode {
180 fn from(value: FragmentKind) -> Self {
181 match value {
182 FragmentKind::Inline => Self::Inline,
183 FragmentKind::Block => Self::Block,
184 }
185 }
186}
187
188#[allow(clippy::too_many_arguments)]
190pub fn layout_flow<'a>(
191 engine: &mut Engine,
192 children: &[Pair<'a>],
193 locator: &mut SplitLocator<'a>,
194 shared: StyleChain<'a>,
195 mut regions: Regions,
196 columns: NonZeroUsize,
197 column_gutter: Rel<Abs>,
198 mode: FlowMode,
199) -> SourceResult<Fragment> {
200 let config = configuration(shared, regions, columns, column_gutter, mode);
202
203 let bump = Bump::new();
206 let children = collect(
207 engine,
208 &bump,
209 children,
210 locator.next(&()),
211 Size::new(config.columns.width, regions.full),
212 regions.expand.x,
213 mode,
214 )?;
215
216 let mut work = Work::new(&children);
217 let mut finished = vec![];
218
219 loop {
221 let frame = compose(engine, &mut work, &config, locator.next(&()), regions)?;
222 finished.push(frame);
223
224 if work.done() && (!regions.expand.y || regions.backlog.is_empty()) {
227 break;
228 }
229
230 regions.next();
231 }
232
233 Ok(Fragment::frames(finished))
234}
235
236fn configuration<'x>(
238 shared: StyleChain<'x>,
239 regions: Regions,
240 columns: NonZeroUsize,
241 column_gutter: Rel<Abs>,
242 mode: FlowMode,
243) -> Config<'x> {
244 Config {
245 mode,
246 shared,
247 columns: {
248 let mut count = columns.get();
249 if !regions.size.x.is_finite() {
250 count = 1;
251 }
252
253 let gutter = column_gutter.relative_to(regions.base().x);
254 let width = (regions.size.x - gutter * (count - 1) as f64) / count as f64;
255 let dir = shared.resolve(TextElem::dir);
256 ColumnConfig { count, width, gutter, dir }
257 },
258 footnote: FootnoteConfig {
259 separator: shared
260 .get_cloned(FootnoteEntry::separator)
261 .artifact(ArtifactKind::Other),
262 clearance: shared.resolve(FootnoteEntry::clearance),
263 gap: shared.resolve(FootnoteEntry::gap),
264 expand: regions.expand.x,
265 },
266 line_numbers: (mode == FlowMode::Root).then(|| LineNumberConfig {
267 scope: shared.get(ParLine::numbering_scope),
268 default_clearance: {
269 let width = if shared.get(PageElem::flipped) {
270 shared.resolve(PageElem::height)
271 } else {
272 shared.resolve(PageElem::width)
273 };
274
275 (0.026 * width.unwrap_or_default()).clamp(
279 Em::new(0.75).resolve(shared).max(Abs::zero()),
280 Em::new(2.5).resolve(shared).max(Abs::zero()),
281 )
282 },
283 }),
284 }
285}
286
287#[derive(Clone)]
293struct Work<'a, 'b> {
294 children: &'b [Child<'a>],
296 spill: Option<MultiSpill<'a, 'b>>,
298 floats: EcoVec<&'b PlacedChild<'a>>,
300 footnotes: EcoVec<Packed<FootnoteElem>>,
302 footnote_spill: Option<std::vec::IntoIter<Frame>>,
304 tags: EcoVec<&'a Tag>,
306 skips: Rc<FxHashSet<Location>>,
310}
311
312impl<'a, 'b> Work<'a, 'b> {
313 fn new(children: &'b [Child<'a>]) -> Self {
315 Self {
316 children,
317 spill: None,
318 floats: EcoVec::new(),
319 footnotes: EcoVec::new(),
320 footnote_spill: None,
321 tags: EcoVec::new(),
322 skips: Rc::new(FxHashSet::default()),
323 }
324 }
325
326 fn head(&self) -> Option<&'b Child<'a>> {
328 self.children.first()
329 }
330
331 fn advance(&mut self) {
333 self.children = &self.children[1..];
334 }
335
336 fn done(&self) -> bool {
338 self.children.is_empty()
339 && self.spill.is_none()
340 && self.floats.is_empty()
341 && self.footnote_spill.is_none()
342 && self.footnotes.is_empty()
343 }
344
345 fn extend_skips(&mut self, skips: &[Location]) {
348 if !skips.is_empty() {
349 Rc::make_mut(&mut self.skips).extend(skips.iter().copied());
350 }
351 }
352}
353
354struct Config<'x> {
356 mode: FlowMode,
359 shared: StyleChain<'x>,
362 columns: ColumnConfig,
364 footnote: FootnoteConfig,
366 line_numbers: Option<LineNumberConfig>,
368}
369
370struct FootnoteConfig {
372 separator: Content,
374 clearance: Abs,
376 gap: Abs,
378 expand: bool,
380}
381
382struct ColumnConfig {
384 count: usize,
386 width: Abs,
388 gutter: Abs,
390 dir: Dir,
393}
394
395struct LineNumberConfig {
397 scope: LineNumberingScope,
399 default_clearance: Abs,
411}
412
413type FlowResult<T> = Result<T, Stop>;
418
419enum Stop {
421 Finish(bool),
424 Relayout(PlacementScope),
426 Error(EcoVec<SourceDiagnostic>),
428}
429
430impl From<EcoVec<SourceDiagnostic>> for Stop {
431 fn from(error: EcoVec<SourceDiagnostic>) -> Self {
432 Stop::Error(error)
433 }
434}