Skip to main content

typst_layout/pages/
mod.rs

1//! Layout of content into a [`PagedDocument`].
2
3mod collect;
4mod finalize;
5mod run;
6
7use comemo::{Track, Tracked, TrackedMut};
8use ecow::EcoVec;
9use typst_library::diag::SourceResult;
10use typst_library::engine::{Engine, Route, Sink, Traced};
11use typst_library::foundations::{Content, StyleChain};
12use typst_library::introspection::{
13    Introspector, Locator, LocatorLink, ManualPageCounter, SplitLocator, TagElem,
14};
15use typst_library::layout::{FrameItem, Point};
16use typst_library::model::DocumentInfo;
17use typst_library::routines::{Arenas, Pair, RealizationKind};
18use typst_library::{Library, World};
19use typst_utils::{LazyHash, Protected};
20
21use self::collect::{Item, collect};
22use self::finalize::finalize;
23use self::run::{LayoutedPage, layout_blank_page, layout_page_run};
24use crate::{Page, PagedDocument};
25
26/// Layout content into a document.
27///
28/// This first performs root-level realization and then lays out the resulting
29/// elements. In contrast to [`layout_fragment`](crate::layout_fragment),
30/// this does not take regions since the regions are defined by the page
31/// configuration in the content and style chain.
32#[typst_macros::time(name = "layout document")]
33pub fn layout_document(
34    engine: &mut Engine,
35    content: &Content,
36    styles: StyleChain,
37) -> SourceResult<PagedDocument> {
38    layout_document_impl(
39        engine.world,
40        engine.library,
41        engine.introspector.into_raw(),
42        engine.traced,
43        TrackedMut::reborrow_mut(&mut engine.sink),
44        engine.route.track(),
45        content,
46        styles,
47    )
48}
49
50/// The internal implementation of `layout_document`.
51#[comemo::memoize]
52#[allow(clippy::too_many_arguments)]
53fn layout_document_impl(
54    world: Tracked<dyn World + '_>,
55    library: &LazyHash<Library>,
56    introspector: Tracked<dyn Introspector + '_>,
57    traced: Tracked<Traced>,
58    sink: TrackedMut<Sink>,
59    route: Tracked<Route>,
60    content: &Content,
61    styles: StyleChain,
62) -> SourceResult<PagedDocument> {
63    layout_document_common(
64        library,
65        world,
66        introspector,
67        traced,
68        sink,
69        route,
70        content,
71        Locator::root(),
72        styles,
73    )
74}
75
76/// Layout content into a document, as part of a bundle compilation process.
77#[typst_macros::time(name = "layout document")]
78pub fn layout_document_for_bundle(
79    engine: &mut Engine,
80    content: &Content,
81    locator: Locator,
82    styles: StyleChain,
83) -> SourceResult<PagedDocument> {
84    layout_document_for_bundle_impl(
85        engine.world,
86        engine.library,
87        engine.introspector.into_raw(),
88        engine.traced,
89        TrackedMut::reborrow_mut(&mut engine.sink),
90        engine.route.track(),
91        content,
92        locator.track(),
93        styles,
94    )
95}
96
97/// The internal implementation of `layout_document_for_bundle`.
98#[comemo::memoize]
99#[allow(clippy::too_many_arguments)]
100fn layout_document_for_bundle_impl(
101    world: Tracked<dyn World + '_>,
102    library: &LazyHash<Library>,
103    introspector: Tracked<dyn Introspector + '_>,
104    traced: Tracked<Traced>,
105    sink: TrackedMut<Sink>,
106    route: Tracked<Route>,
107    content: &Content,
108    locator: Tracked<Locator>,
109    styles: StyleChain,
110) -> SourceResult<PagedDocument> {
111    let link = LocatorLink::new(locator);
112    layout_document_common(
113        library,
114        world,
115        introspector,
116        traced,
117        sink,
118        route,
119        content,
120        Locator::link(&link),
121        styles,
122    )
123}
124
125/// The shared, unmemoized implementation of `layout_document` and
126/// `layout_document_for_bundle`.
127#[allow(clippy::too_many_arguments)]
128fn layout_document_common(
129    library: &LazyHash<Library>,
130    world: Tracked<dyn World + '_>,
131    introspector: Tracked<dyn Introspector + '_>,
132    traced: Tracked<Traced>,
133    sink: TrackedMut<Sink>,
134    route: Tracked<Route>,
135    content: &Content,
136    locator: Locator,
137    styles: StyleChain,
138) -> SourceResult<PagedDocument> {
139    let introspector = Protected::from_raw(introspector);
140    let mut locator = locator.split();
141    let mut engine = Engine {
142        library,
143        world,
144        introspector,
145        traced,
146        sink,
147        route: Route::extend(route).unnested(),
148    };
149
150    // Mark the external styles as "outside" so that they are valid at the page
151    // level.
152    let styles = styles.to_map().outside();
153    let styles = StyleChain::new(&styles);
154    let arenas = Arenas::default();
155
156    let mut info = DocumentInfo::default();
157    info.populate(styles);
158    info.populate_locale(styles);
159
160    let mut children = (engine.library.routines.realize)(
161        RealizationKind::Document { info: &mut info },
162        &mut engine,
163        &mut locator,
164        &arenas,
165        content,
166        styles,
167    )?;
168
169    let pages = layout_pages(&mut engine, &mut children, &mut locator, styles)?;
170
171    Ok(PagedDocument::new(pages, info))
172}
173
174/// Layouts the document's pages.
175fn layout_pages<'a>(
176    engine: &mut Engine,
177    children: &'a mut [Pair<'a>],
178    locator: &mut SplitLocator<'a>,
179    styles: StyleChain<'a>,
180) -> SourceResult<EcoVec<Page>> {
181    // Slice up the children into logical parts.
182    let items = collect(children, locator, styles);
183
184    // Layout the page runs in parallel.
185    let mut runs = engine.parallelize(
186        items.iter().filter_map(|item| match item {
187            Item::Run(children, initial, locator) => {
188                Some((children, initial, locator.relayout()))
189            }
190            _ => None,
191        }),
192        |engine, (children, initial, locator)| {
193            layout_page_run(engine, children, locator, *initial)
194        },
195    );
196
197    let mut pages = EcoVec::new();
198    let mut tags = vec![];
199    let mut counter = ManualPageCounter::new();
200
201    // Collect and finalize the runs, handling things like page parity and tags
202    // between pages.
203    for item in &items {
204        match item {
205            Item::Run(..) => {
206                let layouted = runs.next().unwrap()?;
207                for layouted in layouted {
208                    let page = finalize(engine, &mut counter, &mut tags, layouted)?;
209                    pages.push(page);
210                }
211            }
212            Item::Parity(parity, initial, locator) => {
213                if !parity.matches(pages.len()) {
214                    continue;
215                }
216
217                let layouted = layout_blank_page(engine, locator.relayout(), *initial)?;
218                let page = finalize(engine, &mut counter, &mut tags, layouted)?;
219                pages.push(page);
220            }
221            Item::Tags(items) => {
222                tags.extend(
223                    items
224                        .iter()
225                        .filter_map(|(c, _)| c.to_packed::<TagElem>())
226                        .map(|elem| elem.tag.clone()),
227                );
228            }
229        }
230    }
231
232    // Add the remaining tags to the very end of the last page.
233    if !tags.is_empty() {
234        let last = pages.make_mut().last_mut().unwrap();
235        let pos = Point::with_y(last.frame.height());
236        last.frame
237            .push_multiple(tags.into_iter().map(|tag| (pos, FrameItem::Tag(tag))));
238    }
239
240    Ok(pages)
241}