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}