typst_layout/pages/
mod.rs

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