Skip to main content

fleuron/
pages.rs

1//! Page output: the display structure.
2//!
3//! The engine's only product. Painters (SVG preview, PDF export) consume
4//! this and never re-derive layout. Coordinates are page units (points),
5//! origin top-left.
6
7use std::ops::Range;
8
9use serde::{Deserialize, Serialize};
10
11use crate::content::{NodeId, SourceRange};
12use crate::fonts::Features;
13use crate::style::{Color, Edges};
14
15/// Which side of the spread a page falls on.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
17#[serde(rename_all = "snake_case")]
18pub enum Side {
19    /// A right-hand page.
20    Recto,
21    /// A left-hand page.
22    Verso,
23}
24
25impl Side {
26    /// Books open on a right-hand page: odd numbers are recto.
27    pub fn of_number(number: u32) -> Side {
28        if number % 2 == 1 {
29            Side::Recto
30        } else {
31            Side::Verso
32        }
33    }
34}
35
36/// One typeset page: a number, a side, a trim size, and what to
37/// paint on it.
38#[derive(Debug, PartialEq, Serialize, Deserialize)]
39pub struct Page {
40    /// Folio, counting from 1.
41    pub number: u32,
42    /// Which side of the spread this page falls on.
43    pub side: Side,
44    /// Trimmed page width in points.
45    pub width: f32,
46    /// Trimmed page height in points.
47    pub height: f32,
48    /// The sections whose content appears on this page, in the order their
49    /// content appears on it. A chapter that ends mid-page is followed
50    /// there by the next one opening, so the page names both. A blank
51    /// leaf names none.
52    pub sections: Vec<NodeId>,
53    /// What to paint, in paint order: by layer, and inside one layer
54    /// in the order the blocks are written.
55    pub items: Vec<DrawItem>,
56    /// The links set on this page, in the order their text is painted.
57    /// Empty on a page with no link.
58    pub links: Vec<Link>,
59}
60
61impl Page {
62    /// Puts the page's items in paint order. The sort is stable, so
63    /// one layer keeps the order the flow produced it in.
64    pub(crate) fn sort_by_layer(&mut self) {
65        self.items.sort_by_key(DrawItem::layer);
66    }
67}
68
69/// Where one node's content is set: the folios it runs between, and
70/// the pages of the book those folios are.
71///
72/// `first` and `last` are what a page has printed on it, which is
73/// what a host puts on screen. They are read in reading order rather
74/// than compared, so a book whose page counter restarts still opens
75/// at `first` and a node that fits on one page answers with the same
76/// folio twice.
77///
78/// `at` and `count` are where those pages fall in the book, counting
79/// from 0. A page counter that restarts makes them differ from the
80/// folios, so they are answered rather than left to arithmetic, and
81/// they are the numbers a host fetches the pages by.
82#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
83pub struct Folios {
84    /// The folio the node's content begins on.
85    pub first: u32,
86    /// The folio it ends on.
87    pub last: u32,
88    /// Which page of the book that first folio is, counting from 0.
89    pub at: u32,
90    /// How many pages the node's content runs across, so `at` and
91    /// `count` are every page it is on.
92    pub count: u32,
93}
94
95/// One box on one page, in points, origin top-left.
96#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
97pub struct PageBox {
98    /// Which page of the book the box is on, counting from 0.
99    pub page: u32,
100    /// Left edge.
101    pub x: f32,
102    /// Top edge.
103    pub y: f32,
104    /// Width in points.
105    pub width: f32,
106    /// Height in points.
107    pub height: f32,
108}
109
110impl PageBox {
111    /// Whether a point on the box's page falls inside it.
112    pub fn contains(&self, x: f32, y: f32) -> bool {
113        (self.x..=self.x + self.width).contains(&x) && (self.y..=self.y + self.height).contains(&y)
114    }
115}
116
117/// A single paint operation. Deliberately tiny: text, rules, images,
118/// and the rounded boxes a border radius draws.
119#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
120pub enum DrawItem {
121    /// A run of shaped glyphs sharing a font, size, and baseline.
122    Text {
123        /// Left edge of the run.
124        x: f32,
125        /// The run's baseline.
126        y: f32,
127        /// Index into `LayoutOutput::fonts`.
128        font_id: u16,
129        /// Em size in points.
130        size: f32,
131        /// How far the run's glyphs advance, in points: the run ends
132        /// at `x + width`.
133        width: f32,
134        /// The text the glyphs were shaped from, which the glyphs'
135        /// ranges index. A painter that draws characters rather than
136        /// glyphs draws these.
137        text: String,
138        /// What the author wrote, where `text-transform` or small
139        /// capitals made that differ from what was shaped, and empty
140        /// where the two are the same. Text extraction and copy and
141        /// paste return this rather than `text`: a title set in
142        /// capitals is read back in the case it was written in.
143        source: String,
144        /// The offset in `source` of every byte boundary of `text`,
145        /// so `source_map[range.start]..source_map[range.end]` is the
146        /// source a glyph stands for. Empty alongside `source`.
147        source_map: Vec<u32>,
148        /// Where the run was written: the content node it was shaped
149        /// from and the bytes of that node's own text it stands for.
150        /// The runs that name one node tile it, so a cursor in the
151        /// manuscript lands on a run and a run lands back on the
152        /// manuscript. The text of `::before` and `::after` names the
153        /// id of that pseudo-element. Absent on text the engine adds
154        /// to the book: a folio, a running head, a scene break's
155        /// ornament, and a table's header row set again on a later
156        /// page.
157        origin: Option<SourceRange>,
158        /// The pseudo-element the run was cut from: a drop cap, the
159        /// line a paragraph opens on, or the text of `::before` or
160        /// `::after`. `origin` still names the text it stands for.
161        pseudo_element: Option<NodeId>,
162        /// The features the run was shaped with. A painter that draws
163        /// characters asks the face for these; one that draws glyphs
164        /// has the answer already.
165        features: Features,
166        /// What the glyphs are painted in.
167        color: Color,
168        /// The glyphs, in visual order.
169        glyphs: Vec<Glyph>,
170        /// Which layer the run paints in.
171        layer: i32,
172    },
173    /// Filled rectangle: rules, borders, backgrounds.
174    Rect {
175        /// Left edge.
176        x: f32,
177        /// Top edge.
178        y: f32,
179        /// Width in points.
180        w: f32,
181        /// Height in points.
182        h: f32,
183        /// What the rectangle is filled with.
184        color: Color,
185        /// Which layer the rectangle paints in.
186        layer: i32,
187    },
188    /// Placed image; `asset` indexes the asset table.
189    Image {
190        /// Left edge.
191        x: f32,
192        /// Top edge.
193        y: f32,
194        /// Width in points.
195        w: f32,
196        /// Height in points.
197        h: f32,
198        /// Index into the asset table.
199        asset: u32,
200        /// How much of the image shows, from 0 to 255, where 255 is
201        /// all of it: `opacity` on the image and the blocks around it.
202        alpha: u8,
203        /// Which layer the image paints in.
204        layer: i32,
205    },
206    /// An image painted behind a box: the page's own, or a block's
207    /// border box.
208    ///
209    /// The box is what the image is clipped to, and the tile is
210    /// where one copy of it is drawn, which may reach outside the
211    /// box. A painter clips to the box, draws the tile, and repeats
212    /// the tile across and down the box where `repeat` asks for it.
213    Background {
214        /// Left edge of the box the image is painted behind.
215        x: f32,
216        /// Its top edge.
217        y: f32,
218        /// Its width in points.
219        w: f32,
220        /// Its height in points.
221        h: f32,
222        /// How far each corner of the box is rounded. The image is
223        /// clipped to the rounded box.
224        radii: Corners,
225        /// Left edge of the first tile.
226        tile_x: f32,
227        /// Its top edge.
228        tile_y: f32,
229        /// The width one copy of the image is drawn at.
230        tile_w: f32,
231        /// The height one copy is drawn at.
232        tile_h: f32,
233        /// Whether the tile repeats to cover the box.
234        repeat: bool,
235        /// Index into the asset table.
236        asset: u32,
237        /// How much of the image shows, from 0 to 255, where 255 is
238        /// all of it: `opacity` on the blocks the box belongs to.
239        alpha: u8,
240        /// Which layer the image paints in.
241        layer: i32,
242    },
243    /// A filled box with rounded corners: a background, or a border
244    /// drawn as a ring.
245    ///
246    /// The outer shape is the box with its corners rounded by `radii`.
247    /// Where `ring` is zero on all four edges, the whole shape is
248    /// filled. Otherwise the fill is the band between the outer shape
249    /// and an inner one: the box `ring` in from each edge, with the
250    /// corners [`Corners::inside`] gives.
251    Rounded {
252        /// Left edge.
253        x: f32,
254        /// Top edge.
255        y: f32,
256        /// Width in points.
257        w: f32,
258        /// Height in points.
259        h: f32,
260        /// How far each corner is rounded.
261        radii: Corners,
262        /// How far in from each edge the fill reaches, in points. Zero
263        /// on all four fills the whole shape.
264        ring: Edges,
265        /// What the shape is filled with.
266        color: Color,
267        /// Which layer the shape paints in.
268        layer: i32,
269    },
270}
271
272/// How far one corner of a box is rounded: the two radii of the
273/// quarter ellipse the corner follows, in points.
274#[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
275pub struct Radius {
276    /// Along the top or bottom edge.
277    pub x: f32,
278    /// Along the left or right edge.
279    pub y: f32,
280}
281
282impl Radius {
283    /// A corner that is not rounded.
284    pub const SQUARE: Radius = Radius { x: 0.0, y: 0.0 };
285
286    /// Whether the corner is rounded at all. A radius of zero on
287    /// either axis leaves it square.
288    pub fn is_square(self) -> bool {
289        self.x <= 0.0 || self.y <= 0.0
290    }
291}
292
293/// The four corners of a box, each rounded by its own radius.
294///
295/// The radii of two corners on one edge never add up to more than the
296/// edge is long: layout scales all four down together until they fit.
297#[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
298pub struct Corners {
299    /// The top left corner.
300    pub top_left: Radius,
301    /// The top right corner.
302    pub top_right: Radius,
303    /// The bottom right corner.
304    pub bottom_right: Radius,
305    /// The bottom left corner.
306    pub bottom_left: Radius,
307}
308
309impl Corners {
310    /// Four square corners.
311    pub const SQUARE: Corners = Corners {
312        top_left: Radius::SQUARE,
313        top_right: Radius::SQUARE,
314        bottom_right: Radius::SQUARE,
315        bottom_left: Radius::SQUARE,
316    };
317
318    /// Whether no corner is rounded.
319    pub fn is_square(&self) -> bool {
320        [
321            self.top_left,
322            self.top_right,
323            self.bottom_right,
324            self.bottom_left,
325        ]
326        .iter()
327        .all(|radius| radius.is_square())
328    }
329
330    /// The corners of the box `ring` in from this one. Each radius
331    /// loses the width of the edge it runs along, and none goes below
332    /// zero, so a ring wider than a radius leaves that inner corner
333    /// square.
334    pub fn inside(&self, ring: Edges) -> Corners {
335        let less = |radius: Radius, across: f32, down: f32| Radius {
336            x: (radius.x - across).max(0.0),
337            y: (radius.y - down).max(0.0),
338        };
339        Corners {
340            top_left: less(self.top_left, ring.left, ring.top),
341            top_right: less(self.top_right, ring.right, ring.top),
342            bottom_right: less(self.bottom_right, ring.right, ring.bottom),
343            bottom_left: less(self.bottom_left, ring.left, ring.bottom),
344        }
345    }
346}
347
348impl DrawItem {
349    /// The layer a page's own background paints in: under every layer
350    /// a stylesheet can name. Where a stylesheet names no `z-index`,
351    /// the text of a page still covers the background. A stylesheet
352    /// that names this number paints over the background as well. The
353    /// page puts its own background in first, and one layer keeps the
354    /// order it arrived in.
355    pub const PAGE_BACKGROUND: i32 = i32::MIN;
356
357    /// The layer a page's margin boxes paint in: over every layer a
358    /// stylesheet can name. A page number and a running head stay
359    /// visible whatever layer the blocks of the book are raised to.
360    pub const PAGE_FURNITURE: i32 = i32::MAX;
361
362    /// Which layer this item paints in. Higher paints later, over
363    /// what a lower layer put down.
364    pub fn layer(&self) -> i32 {
365        match self {
366            DrawItem::Text { layer, .. }
367            | DrawItem::Rect { layer, .. }
368            | DrawItem::Image { layer, .. }
369            | DrawItem::Background { layer, .. }
370            | DrawItem::Rounded { layer, .. } => *layer,
371        }
372    }
373}
374
375/// An eight-bit alpha scaled by `opacity`, from 0 to 1.
376pub(crate) fn fade(alpha: u8, opacity: f32) -> u8 {
377    (alpha as f32 * opacity.clamp(0.0, 1.0)).round() as u8
378}
379
380/// One glyph: an id in its font and an absolute x. Kerning and
381/// justification mean no two glyphs are uniformly spaced — the glyph is
382/// the atom of layout, so positions are per-glyph.
383#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
384pub struct Glyph {
385    /// Glyph id in the run's font.
386    pub id: u32,
387    /// Absolute x of the glyph's origin.
388    pub x: f32,
389    /// Byte range in the run's `text` this glyph stands for. A
390    /// ligature spans several characters, a decomposed cluster puts
391    /// several glyphs on one range.
392    pub range: Range<u32>,
393}
394
395/// What a reader of the book on a screen follows beyond the links on
396/// its pages: the outline of its headings.
397///
398/// A painter that can express an outline reads this. One that cannot
399/// paints the pages alone, and loses nothing a printed page shows.
400#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
401pub struct Navigation {
402    /// The book's headings, nested by level. Empty for a book with no
403    /// heading.
404    pub outline: Vec<OutlineEntry>,
405}
406
407impl Navigation {
408    /// Whether the book has no heading.
409    pub fn is_empty(&self) -> bool {
410        self.outline.is_empty()
411    }
412}
413
414/// One link on one page: the area its text covers on each line, and
415/// where it goes.
416#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
417pub struct Link {
418    /// One area for each line the link is set on in this page, in the
419    /// order the lines are painted. Each runs across the link's glyphs
420    /// on that line, the text of its `::before` and `::after` included,
421    /// and down from the ascent to the descent of the face.
422    pub areas: Vec<PageBox>,
423    /// Where the link goes.
424    pub to: LinkTo,
425}
426
427impl Link {
428    /// Whether a point on the link's page falls inside one of its areas.
429    pub fn contains(&self, x: f32, y: f32) -> bool {
430        self.areas.iter().any(|area| area.contains(x, y))
431    }
432}
433
434/// Where a link goes.
435#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
436pub enum LinkTo {
437    /// A place in the book.
438    Place {
439        /// The element the link names.
440        node: NodeId,
441        /// The box of that element on the page it opens on. `page` is
442        /// that page's place in the book, counting from 0, which is
443        /// the number a host fetches the page by.
444        place: PageBox,
445    },
446    /// Something outside the book, by its url.
447    Uri(String),
448}
449
450/// One heading in the outline.
451#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
452pub struct OutlineEntry {
453    /// The heading's words, markup discarded and a line break as a
454    /// space.
455    pub title: String,
456    /// The heading's level, from 1 to 6.
457    pub level: u8,
458    /// The box of the heading, on the page it is set on.
459    pub place: PageBox,
460    /// The headings after this one and deeper than it, up to the next
461    /// heading at its level or above.
462    pub children: Vec<OutlineEntry>,
463}