typst_layout/pages/
mod.rs

1//! Layout of content into a [`Document`].
2
3mod collect;
4mod finalize;
5mod run;
6
7use std::num::NonZeroUsize;
8
9use comemo::{Tracked, TrackedMut};
10use typst_library::World;
11use typst_library::diag::SourceResult;
12use typst_library::engine::{Engine, Route, Sink, Traced};
13use typst_library::foundations::{Content, StyleChain};
14use typst_library::introspection::{
15    Introspector, IntrospectorBuilder, Locator, ManualPageCounter, SplitLocator, TagElem,
16};
17use typst_library::layout::{FrameItem, Page, PagedDocument, Point, Transform};
18use typst_library::model::DocumentInfo;
19use typst_library::routines::{Arenas, Pair, RealizationKind, Routines};
20
21use self::collect::{Item, collect};
22use self::finalize::finalize;
23use self::run::{LayoutedPage, layout_blank_page, layout_page_run};
24
25/// Layout content into a document.
26///
27/// This first performs root-level realization and then lays out the resulting
28/// elements. In contrast to [`layout_fragment`](crate::layout_fragment),
29/// this does not take regions since the regions are defined by the page
30/// configuration in the content and style chain.
31#[typst_macros::time(name = "layout document")]
32pub fn layout_document(
33    engine: &mut Engine,
34    content: &Content,
35    styles: StyleChain,
36) -> SourceResult<PagedDocument> {
37    layout_document_impl(
38        engine.routines,
39        engine.world,
40        engine.introspector,
41        engine.traced,
42        TrackedMut::reborrow_mut(&mut engine.sink),
43        engine.route.track(),
44        content,
45        styles,
46    )
47}
48
49/// The internal implementation of `layout_document`.
50#[comemo::memoize]
51#[allow(clippy::too_many_arguments)]
52fn layout_document_impl(
53    routines: &Routines,
54    world: Tracked<dyn World + '_>,
55    introspector: Tracked<Introspector>,
56    traced: Tracked<Traced>,
57    sink: TrackedMut<Sink>,
58    route: Tracked<Route>,
59    content: &Content,
60    styles: StyleChain,
61) -> SourceResult<PagedDocument> {
62    let mut locator = Locator::root().split();
63    let mut engine = Engine {
64        routines,
65        world,
66        introspector,
67        traced,
68        sink,
69        route: Route::extend(route).unnested(),
70    };
71
72    // Mark the external styles as "outside" so that they are valid at the page
73    // level.
74    let styles = styles.to_map().outside();
75    let styles = StyleChain::new(&styles);
76
77    let arenas = Arenas::default();
78    let mut info = DocumentInfo::default();
79    let mut children = (engine.routines.realize)(
80        RealizationKind::LayoutDocument { info: &mut info },
81        &mut engine,
82        &mut locator,
83        &arenas,
84        content,
85        styles,
86    )?;
87
88    let pages = layout_pages(&mut engine, &mut children, &mut locator, styles)?;
89    let introspector = introspect_pages(&pages);
90
91    Ok(PagedDocument { pages, info, introspector })
92}
93
94/// Layouts the document's pages.
95fn layout_pages<'a>(
96    engine: &mut Engine,
97    children: &'a mut [Pair<'a>],
98    locator: &mut SplitLocator<'a>,
99    styles: StyleChain<'a>,
100) -> SourceResult<Vec<Page>> {
101    // Slice up the children into logical parts.
102    let items = collect(children, locator, styles);
103
104    // Layout the page runs in parallel.
105    let mut runs = engine.parallelize(
106        items.iter().filter_map(|item| match item {
107            Item::Run(children, initial, locator) => {
108                Some((children, initial, locator.relayout()))
109            }
110            _ => None,
111        }),
112        |engine, (children, initial, locator)| {
113            layout_page_run(engine, children, locator, *initial)
114        },
115    );
116
117    let mut pages = vec![];
118    let mut tags = vec![];
119    let mut counter = ManualPageCounter::new();
120
121    // Collect and finalize the runs, handling things like page parity and tags
122    // between pages.
123    for item in &items {
124        match item {
125            Item::Run(..) => {
126                let layouted = runs.next().unwrap()?;
127                for layouted in layouted {
128                    let page = finalize(engine, &mut counter, &mut tags, layouted)?;
129                    pages.push(page);
130                }
131            }
132            Item::Parity(parity, initial, locator) => {
133                if !parity.matches(pages.len()) {
134                    continue;
135                }
136
137                let layouted = layout_blank_page(engine, locator.relayout(), *initial)?;
138                let page = finalize(engine, &mut counter, &mut tags, layouted)?;
139                pages.push(page);
140            }
141            Item::Tags(items) => {
142                tags.extend(
143                    items
144                        .iter()
145                        .filter_map(|(c, _)| c.to_packed::<TagElem>())
146                        .map(|elem| elem.tag.clone()),
147                );
148            }
149        }
150    }
151
152    // Add the remaining tags to the very end of the last page.
153    if !tags.is_empty() {
154        let last = pages.last_mut().unwrap();
155        let pos = Point::with_y(last.frame.height());
156        last.frame
157            .push_multiple(tags.into_iter().map(|tag| (pos, FrameItem::Tag(tag))));
158    }
159
160    Ok(pages)
161}
162
163/// Introspects pages.
164#[typst_macros::time(name = "introspect pages")]
165fn introspect_pages(pages: &[Page]) -> Introspector {
166    let mut builder = IntrospectorBuilder::new();
167    builder.pages = pages.len();
168    builder.page_numberings.reserve(pages.len());
169    builder.page_supplements.reserve(pages.len());
170
171    // Discover all elements.
172    let mut elems = Vec::new();
173    for (i, page) in pages.iter().enumerate() {
174        builder.page_numberings.push(page.numbering.clone());
175        builder.page_supplements.push(page.supplement.clone());
176        builder.discover_in_frame(
177            &mut elems,
178            &page.frame,
179            NonZeroUsize::new(1 + i).unwrap(),
180            Transform::identity(),
181        );
182    }
183
184    builder.finalize(elems)
185}