Skip to main content

fleuron/layout/
mod.rs

1//! Layout: box construction, inline layout, fragmentation.
2//!
3//! ```text
4//! content + style ─► box tree ─► line layout ─► fragmentation ─► pages
5//! ```
6//!
7//! v0.2 folds the middle of the pipeline into one pass: each section
8//! becomes fragments (each block with the style the tree computed for
9//! it, via `lines::LineLayout`), and the paginator flows those
10//! fragments into page content boxes. Nothing here decides what
11//! anything looks like — the style tree was told, and this asks it.
12//!
13//! A fragment is what the flow can move: one line, one image, one
14//! ornament. Where a page may end is decided when the fragments are
15//! built — orphans, widows, `break-inside`, an ornament that must
16//! keep the prose around it — and the flow only stacks and, when
17//! something does not fit, walks back to the last place a break was
18//! allowed.
19//!
20//! The stages have a file each: `fragment` is what the flow moves,
21//! `build` turns blocks into fragments, `cap` sets the initial letter
22//! beside them, `list` sets a list an item at a time with the marker
23//! of each, `table` sets a table a row at a time, `flow` stacks
24//! fragments into pages, `exclusion` places the images and blocks the
25//! sheet anchored and wraps prose around them, `background` puts art
26//! behind a box, `inline` paints the box an inline element takes on a
27//! line, `furniture` paints the margin boxes, and `text` turns a
28//! shaped line into paint ops.
29
30mod background;
31mod build;
32mod cap;
33mod exclusion;
34mod flow;
35mod fragment;
36mod furniture;
37mod image;
38mod inline;
39mod list;
40mod navigation;
41mod note;
42mod reference;
43mod table;
44mod text;
45
46#[cfg(test)]
47mod testing;
48
49pub use build::Reflow;
50pub use fragment::{
51    BreakPoint, Decoration, Decorations, DropCap, Fragment, Marker, Marks, Piece, TableRow,
52};
53pub use furniture::margin_band;
54pub use note::Note;
55
56use background::Backdrop;
57
58pub(crate) use exclusion::AnchoredBoxes;
59pub(crate) use flow::{PageInfo, Paged};
60pub(crate) use navigation::{navigation, run_area};
61pub(crate) use note::{Numbering, UNSETTLED};
62pub(crate) use reference::{Named, References, landed, moved};
63
64use std::borrow::Cow;
65use std::cell::{Cell, OnceCell, RefCell};
66use std::collections::BTreeSet;
67
68use crate::content::{Book, Metadata};
69use crate::fonts::{FeatureSetting, FontRegistry};
70use crate::images::{Assets, Contours};
71use crate::lines::{LineLayout, ParagraphStyle, Patterns};
72use crate::pages::{Page, Side};
73use crate::session::Session;
74use crate::style::{Background, PageStyle, Position, StyleTree};
75use crate::{LayoutOutput, Warning};
76
77use flow::{Flow, PageSlot};
78
79/// How many times a book whose notes are numbered by page is laid
80/// out again for them. Each pass numbers the notes of the pass
81/// before, and a book that has not settled by the last of them keeps
82/// the numbers it has.
83pub(crate) const NOTE_PASSES: u32 = 4;
84
85/// One book through the whole pipeline: lines laid out, flowed into
86/// pages, everything the output needs assembled.
87///
88/// A single run over a session that retains nothing. It keeps one
89/// section's lines at a time, which is what a process that renders a
90/// book once and exits wants. A live preview uses `Session` instead.
91///
92/// `assets` is the images the host probed. A book with none of them
93/// passes [`Assets::none`].
94pub fn layout_book(
95    book: &Book,
96    styles: &StyleTree,
97    registry: &FontRegistry,
98    assets: &Assets,
99) -> LayoutOutput {
100    Session::once(book, styles, registry, assets).into_output()
101}
102
103/// The asset table of a host that supplied none.
104pub(crate) fn no_assets() -> &'static Assets {
105    static EMPTY: std::sync::OnceLock<Assets> = std::sync::OnceLock::new();
106    EMPTY.get_or_init(Assets::none)
107}
108
109/// The contours of a book whose sheet asked for none.
110pub(crate) fn no_contours() -> &'static Contours {
111    static EMPTY: std::sync::OnceLock<Contours> = std::sync::OnceLock::new();
112    EMPTY.get_or_init(Contours::none)
113}
114
115/// The fonts a run used, in the order the output indexes them.
116pub(crate) fn font_table(registry: &FontRegistry) -> Vec<crate::fonts::FontRefEntry> {
117    (0..registry.len() as u16)
118        .filter_map(|id| registry.font_ref(id).cloned())
119        .collect()
120}
121
122/// The pagination pass: content in, `Page`s of `DrawItem`s out.
123///
124/// Fragments stack from the top of the content box. One that does not
125/// fit ends the page — at the last point a break was allowed, which
126/// may be several fragments back — and a fragment taller than a whole
127/// page overflows it.
128pub struct Paginator<'a> {
129    registry: &'a FontRegistry,
130    styles: &'a StyleTree,
131    assets: &'a Assets,
132    /// What the trace stage made of the assets the sheet asked to
133    /// wrap around.
134    contours: &'a Contours,
135    lines: LineLayout<'a>,
136    /// The syllable patterns `hyphens: auto` breaks by.
137    patterns: Cell<Patterns>,
138    /// The declared language there are no patterns for, which the
139    /// first paragraph that asks to be hyphenated complains about.
140    unknown: RefCell<Option<String>>,
141    /// What fragmentation had to complain about. Recorded once per
142    /// message: a book that scales the same image twice has one
143    /// problem, not two.
144    warnings: RefCell<Vec<Warning>>,
145    /// Whether the sheet anchors anything to the page, answered once.
146    wraps: OnceCell<bool>,
147    /// How many times the flow set a paragraph again beside an image.
148    rebreaks: Cell<u32>,
149    /// What the references in the book resolve against.
150    references: RefCell<References>,
151    /// What the notes of the book are numbered.
152    notes: RefCell<Numbering>,
153    /// How many times the book was laid out again to print the pages
154    /// its references name.
155    settles: Cell<u32>,
156}
157
158impl<'a> Paginator<'a> {
159    /// A paginator over one book's styling and the faces it shapes
160    /// with, for a book with no images in it.
161    pub fn new(registry: &'a FontRegistry, styles: &'a StyleTree) -> Self {
162        Paginator::with_assets(registry, styles, no_assets())
163    }
164
165    /// The same, over images the host has already probed. A book
166    /// whose sheet names a traced contour also passes what the trace
167    /// stage made of it, through
168    /// [`with_contours`](Paginator::with_contours).
169    pub fn with_assets(
170        registry: &'a FontRegistry,
171        styles: &'a StyleTree,
172        assets: &'a Assets,
173    ) -> Self {
174        Paginator::with_contours(registry, styles, assets, no_contours())
175    }
176
177    /// The same, over the contours the trace stage left.
178    pub fn with_contours(
179        registry: &'a FontRegistry,
180        styles: &'a StyleTree,
181        assets: &'a Assets,
182        contours: &'a Contours,
183    ) -> Self {
184        Paginator {
185            registry,
186            styles,
187            assets,
188            contours,
189            lines: LineLayout::new(registry),
190            patterns: Cell::new(Patterns::default()),
191            unknown: RefCell::new(None),
192            warnings: RefCell::new(Vec::new()),
193            wraps: OnceCell::new(),
194            rebreaks: Cell::new(0),
195            references: RefCell::new(References::default()),
196            notes: RefCell::new(Numbering::default()),
197            settles: Cell::new(0),
198        }
199    }
200}
201
202impl Paginator<'_> {
203    /// Takes the hyphenation patterns from the language the book
204    /// declares. `paginate` reads them from the book it is handed. A
205    /// caller that builds one section's fragments on its own sets
206    /// them here.
207    ///
208    /// A language with no patterns leaves every word whole, rather
209    /// than breaking one language's words at another's syllables,
210    /// and the first paragraph that asks for hyphenation says so.
211    pub fn language(&self, metadata: &Metadata) {
212        let patterns = Patterns::of(metadata);
213        self.patterns.set(patterns);
214        *self.unknown.borrow_mut() = metadata
215            .language()
216            .filter(|_| patterns == Patterns::NONE)
217            .map(str::to_string);
218    }
219
220    /// What `hyphens: auto` has to break with, complaining the first
221    /// time it is asked for and there is nothing behind the language
222    /// the book declares.
223    fn patterns(&self) -> Patterns {
224        if let Some(tag) = self.unknown.borrow().as_deref() {
225            self.warn(
226                format!(
227                    "No hyphenation patterns for `{tag}`. Hyphenation is skipped for that \
228                     language."
229                ),
230                None,
231            );
232        }
233        self.patterns.get()
234    }
235
236    /// What fragmentation had to complain about.
237    pub fn warnings(&self) -> Vec<Warning> {
238        self.warnings.borrow().clone()
239    }
240
241    /// How many times the flow that paints set a paragraph again
242    /// beside an image. A book that anchors nothing never does.
243    pub fn rebreaks(&self) -> u32 {
244        self.rebreaks.get()
245    }
246
247    /// How many times the book was laid out a second time, to print
248    /// the pages its references name. A book whose sheet prints no
249    /// page number is laid out once.
250    pub fn settles(&self) -> u32 {
251        self.settles.get()
252    }
253
254    /// Takes what the book's references resolve against. `paginate`
255    /// reads it from the book it is handed. A caller that builds one
256    /// section's fragments on its own sets it here.
257    pub(crate) fn refer(&self, references: References) {
258        *self.references.borrow_mut() = references;
259    }
260
261    /// Whether the sheet takes anything out of the flow and against
262    /// the page.
263    ///
264    /// A book that anchors nothing never sets a paragraph twice, so
265    /// its fragments keep nothing to set one from.
266    fn wraps(&self) -> bool {
267        *self.wraps.get_or_init(|| {
268            self.styles
269                .styles()
270                .iter()
271                .any(|style| style.position == Position::Absolute)
272        })
273    }
274
275    /// Records one diagnostic, once. A book that hits the same
276    /// problem on every page has one problem.
277    fn warn(&self, message: String, origin: Option<String>) {
278        let mut warnings = self.warnings.borrow_mut();
279        if !warnings.iter().any(|seen| seen.message == message) {
280            warnings.push(Warning { message, origin });
281        }
282    }
283
284    /// The style a note's reference is set in: the style of the
285    /// note, and the face's superior figures over it. The engine
286    /// asks for them, the way it asks for small capitals, because
287    /// `font-feature-settings` inherits and a rule on the note would
288    /// set the prose of the note in superiors as well.
289    ///
290    /// A face with no superior figures sets the reference on the
291    /// baseline at the size the note gives it, and says so once.
292    fn superior(&self, mut style: ParagraphStyle) -> ParagraphStyle {
293        const SUPS: [u8; 4] = *b"sups";
294        if self.registry.has_feature(style.font_id, SUPS) {
295            style.features.push(FeatureSetting::new(SUPS, 1));
296            return style;
297        }
298        let family = self
299            .registry
300            .font_ref(style.font_id)
301            .map(|entry| entry.family.clone())
302            .unwrap_or_default();
303        self.warn(
304            format!(
305                "{family} has no superior figures. The reference of a note stands on the \
306                 baseline."
307            ),
308            None,
309        );
310        style
311    }
312
313    /// Says so where the host supplied no image for a url. The table
314    /// complains about a url it probed and refused. A url the table
315    /// was never offered means the host supplied nothing at all.
316    /// What one box paints behind its content, complaining where the
317    /// sheet named an image nothing answers for.
318    fn backdrop(&self, background: &Background) -> Backdrop {
319        let found = background.image.as_ref().and_then(|url| {
320            let found = self.assets.lookup(&url.value);
321            if found.is_none() {
322                self.missing(&url.value, url.origin.clone().unwrap_or_default());
323            }
324            found
325        });
326        Backdrop::of(background, found)
327    }
328
329    fn missing(&self, url: &str, origin: String) {
330        if !self.assets.probed(url) {
331            self.warn(
332                format!("No image was supplied for {url}. The image is skipped."),
333                (!origin.is_empty()).then_some(origin),
334            );
335        }
336    }
337
338    /// Flows one book into numbered, side-tagged pages.
339    ///
340    /// A section's fragments are built, flowed, and released before
341    /// the next one is measured: what exists at once is the book's
342    /// pages, not every line it was ever broken into.
343    ///
344    /// A book whose references print pages is laid out twice: once to
345    /// find the page each element lands on, and once to print it.
346    pub fn paginate(&self, book: &Book) -> Vec<Page> {
347        self.paginated(book).pages
348    }
349
350    /// The same, with where each id landed and the box of each block.
351    pub(crate) fn paginated(&self, book: &Book) -> Paged {
352        self.language(&book.metadata);
353        if self.styles.refers() {
354            self.refer(References::of(book));
355        }
356        self.number(Numbering::of(book, self.styles));
357        let mut paged = self.pass(book);
358        if self.styles.numbers_notes_per_page() {
359            paged = self.settle_notes(book, paged);
360        }
361        if self.styles.counts_pages() {
362            let found = landed(&paged);
363            let resolved = self.references.borrow().landed(found.clone());
364            self.refer(resolved);
365            self.settles.set(self.settles.get() + 1);
366            paged = self.pass(book);
367            let references = self.references.borrow();
368            let printed: BTreeSet<_> = book
369                .sections
370                .iter()
371                .flat_map(|section| Named::in_section(section, self.styles, &references).pages)
372                .collect();
373            for warning in moved(&found, &landed(&paged), &printed, &references) {
374                self.warn(warning.message, warning.origin);
375            }
376        }
377        self.paint(&mut paged.pages, &paged.infos);
378        paged
379    }
380
381    /// Numbers the notes by the page their references were set on,
382    /// and lays the book out again to print those numbers.
383    ///
384    /// A number of another width moves the line its reference is on,
385    /// which can move a note onto another page and number it again.
386    /// So the book is laid out until the numbering stops changing,
387    /// and the numbering of the last pass stands where it does not.
388    fn settle_notes(&self, book: &Book, mut paged: Paged) -> Paged {
389        let start = self.styles.first_note_number();
390        for _ in 0..NOTE_PASSES {
391            let numbered = self.notes.borrow().on_pages(&paged.notes, start);
392            if numbered == *self.notes.borrow() {
393                return paged;
394            }
395            self.number(numbered);
396            self.settles.set(self.settles.get() + 1);
397            paged = self.pass(book);
398        }
399        self.warn(note::UNSETTLED.to_string(), None);
400        paged
401    }
402
403    /// One pass over the whole book, stopping short of the furniture.
404    fn pass(&self, book: &Book) -> Paged {
405        let anchored = self.anchored(book, |index| {
406            Cow::Owned(self.section_fragments(&book.sections[index]))
407        });
408        let mut flow = Flow::new(self, anchored);
409        for section in &book.sections {
410            let fragments = self.section_fragments(section);
411            flow.section(section, &fragments);
412        }
413        flow.finish()
414    }
415
416    /// The boxes the sheet anchors, each on the page its anchor
417    /// landed on and against the block its insets measure from.
418    ///
419    /// A box inside a positioned block is laid out a second time: the
420    /// first layout breaks its lines to what the page area leaves it,
421    /// and the settling pass answers which block they measure from and
422    /// how wide that block is.
423    fn anchored<'f>(
424        &self,
425        book: &Book,
426        mut fragments: impl FnMut(usize) -> Cow<'f, [Fragment]>,
427    ) -> AnchoredBoxes {
428        let boxes = self.anchored_boxes(book, &[]);
429        if boxes.is_empty() {
430            return AnchoredBoxes::default();
431        }
432        let mut settled = self.settle(book, boxes, &mut fragments);
433        if let Some(within) = settled.narrowed() {
434            let boxes = self.anchored_boxes(book, &within);
435            settled.relaid(boxes, within);
436        }
437        settled
438    }
439
440    /// Fragments in, numbered pages out: fragmentation and page
441    /// assembly, one `Vec<Fragment>` per section of `book`. Nothing
442    /// here measures — every fragment arrives with its box decided.
443    pub fn flow(&self, book: &Book, sections: &[Vec<Fragment>]) -> Vec<Page> {
444        let mut paged = self.fragment(book, sections.iter().map(Vec::as_slice));
445        self.paint(&mut paged.pages, &paged.infos);
446        paged.pages
447    }
448
449    /// The same, stopping short of the furniture: pages as the flow
450    /// settled them, and what each one needs to paint its own.
451    pub(crate) fn fragment<'f>(
452        &self,
453        book: &Book,
454        sections: impl IntoIterator<Item = &'f [Fragment]>,
455    ) -> Paged {
456        let sections: Vec<&[Fragment]> = sections.into_iter().collect();
457        let anchored = self.anchored(book, |index| Cow::Borrowed(sections[index]));
458        let mut flow = Flow::new(self, anchored);
459        for (section, fragments) in book.sections.iter().zip(&sections) {
460            flow.section(section, fragments);
461        }
462        flow.finish()
463    }
464
465    /// The master of the page that will sit at `index`.
466    fn master(&self, index: usize, slot: &PageSlot) -> &PageStyle {
467        self.styles
468            .page(slot.query(Side::of_number(index as u32 + 1)))
469    }
470
471    /// A page of the master's trim size with nothing on it. Numbering
472    /// and side are settled once the whole flow is assembled.
473    fn blank_page(&self, slot: &PageSlot) -> Page {
474        let geometry = self.styles.page(slot.query(Side::Verso)).geometry;
475        Page {
476            number: 0,
477            side: Side::Verso,
478            width: geometry.width,
479            height: geometry.height,
480            sections: Vec::new(),
481            items: Vec::new(),
482            links: Vec::new(),
483        }
484    }
485}
486
487#[cfg(test)]
488mod tests {
489    use super::*;
490    use crate::layout::testing::{book_of, heading, long_prose, registry, section};
491
492    /// Pagination is line layout then flow, and splitting it that way
493    /// changes nothing: the harness times the two halves separately,
494    /// which is only worth doing while their composition is the whole.
495    #[test]
496    fn the_stages_compose_into_what_paginate_does() {
497        let book = book_of(vec![
498            section(long_prose(30)),
499            section([vec![heading("Two")], long_prose(24)].concat()),
500        ]);
501        let styles = crate::style::defaults(&book, registry());
502        let paginator = Paginator::new(registry(), &styles);
503
504        let staged: Vec<Vec<Fragment>> = book
505            .sections
506            .iter()
507            .map(|section| paginator.section_fragments(section))
508            .collect();
509        let by_stage = paginator.flow(&book, &staged);
510        let in_one = paginator.paginate(&book);
511
512        assert!(in_one.len() > 2, "a book worth splitting");
513        assert_eq!(by_stage.len(), in_one.len());
514        for (staged, whole) in by_stage.iter().zip(&in_one) {
515            assert_eq!(staged.number, whole.number);
516            assert_eq!(staged.side, whole.side);
517            assert_eq!(format!("{:?}", staged.items), format!("{:?}", whole.items));
518        }
519    }
520}