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(§ions) {
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}