mos_layout/types.rs
1use std::collections::BTreeMap;
2use std::sync::Arc;
3
4use mos_core::Diagnostic;
5use mos_fonts::{Font, FontFamily, ShapedGlyph};
6
7/// A4 page width in PDF points (1pt = 1/72 inch). Kept as a public
8/// constant so external callers can still read the default; the layout
9/// engine now consults `PageStyle` instead of these directly.
10pub const A4_WIDTH_PT: f32 = 595.276;
11/// A4 page height in PDF points.
12pub const A4_HEIGHT_PT: f32 = 841.890;
13/// Default page margin in points (24mm × 72/25.4).
14pub const MARGIN_PT: f32 = 68.031;
15
16/// Default body font size (manifest §22.1).
17pub(crate) const BODY_SIZE_PT: f32 = 11.0;
18/// Default body leading multiplier (line height = size × leading).
19pub(crate) const BODY_LEADING: f32 = 1.35;
20
21/// Page geometry resolved from `#set page(...)`. `width_pt`/`height_pt`
22/// describe the full media box; `margin_pt` is symmetric on all four
23/// sides for MVP 1.5 (per-side margins are deferred).
24#[derive(Clone, Copy, Debug, PartialEq)]
25pub struct PageStyle {
26 pub width_pt: f32,
27 pub height_pt: f32,
28 pub margin_pt: f32,
29}
30
31impl Default for PageStyle {
32 fn default() -> Self {
33 Self {
34 width_pt: A4_WIDTH_PT,
35 height_pt: A4_HEIGHT_PT,
36 margin_pt: MARGIN_PT,
37 }
38 }
39}
40
41/// Body text style resolved from `#set text(...)`. `leading` applies
42/// to body paragraphs only; headings keep their own multiplier so a
43/// `#set text(leading: 2.0)` doesn't balloon section titles.
44///
45/// `family` is the resolved [`FontFamily`] from `#set text(font: ...)`.
46/// Headings use the family's bold cut; `*emphasis*` uses italic;
47/// `` `raw` `` uses monospace; everything else is `family.regular`.
48#[derive(Clone, Copy, Debug, PartialEq)]
49pub struct TextStyle {
50 pub size_pt: f32,
51 pub leading: f32,
52 pub family: FontFamily,
53}
54
55impl Default for TextStyle {
56 fn default() -> Self {
57 Self {
58 size_pt: BODY_SIZE_PT,
59 leading: BODY_LEADING,
60 family: FontFamily::noto_sans(),
61 }
62 }
63}
64
65/// Decoded raster image data shared between every page that places
66/// the same source image. Held by [`Arc`] so a single PNG referenced
67/// from multiple `#image(...)` directives shares one buffer end-to-end.
68#[derive(Clone, Debug)]
69pub struct ImageHandle {
70 /// Stable index assigned by the layout engine. The PDF backend
71 /// uses it as the suffix on the `/Im<n>` resource-dict key and the
72 /// `XObject`'s indirect ref allocation order, so callers don't have
73 /// to hash the path themselves.
74 pub id: u32,
75 /// Resolved absolute path from the lowerer. Used as the dedup key
76 /// across multiple `#image(...)` calls with the same source.
77 pub resolved_path: String,
78 /// Decoded pixel width.
79 pub pixel_width: u32,
80 /// Decoded pixel height.
81 pub pixel_height: u32,
82 /// Flat RGB8 pixel buffer (`3 * pixel_width * pixel_height` bytes).
83 /// Shared via `Arc<[u8]>` so cloning a handle (e.g. when the same
84 /// image is referenced from multiple `#image`/`#figure` directives,
85 /// or when a future caching layer hands the document back) is cheap.
86 /// The eval crate hands ownership over as an `Arc<[u8]>` already
87 /// (see `AttrValue::Bytes`), so the slice never gets copied here.
88 pub rgb8: Arc<[u8]>,
89}
90
91/// One image placement on a page. The PDF backend emits this as a
92/// `q ... cm /Im<id> Do Q` block in the content stream.
93#[derive(Clone, Debug)]
94pub struct ImagePlacement {
95 pub handle: ImageHandle,
96 /// X coordinate of the image's left edge, measured from the page's
97 /// left edge in points.
98 pub x_pt: f32,
99 /// Y coordinate of the image's **top** edge, measured from the page's
100 /// **top** edge in points. The PDF backend flips to bottom-origin
101 /// once when emitting (same convention as [`TextRun`]).
102 pub top_from_top_pt: f32,
103 /// Rendered width in points.
104 pub width_pt: f32,
105 /// Rendered height in points.
106 pub height_pt: f32,
107}
108
109/// A single horizontal run of text on a page. The MVP 0 emitter
110/// produces one run per word; coalescing same-font neighbours is an
111/// MVP 2 optimisation.
112#[derive(Clone, Debug)]
113pub struct TextRun {
114 /// X coordinate of the run's left edge, measured from the page's
115 /// left edge in points.
116 pub x_pt: f32,
117 /// Y coordinate of the run's baseline, measured from the page's
118 /// **top** edge in points. The PDF backend flips to bottom-origin
119 /// once when emitting.
120 pub baseline_from_top_pt: f32,
121 /// Font size in points.
122 pub size_pt: f32,
123 /// Font face for this run.
124 pub font: Font,
125 /// Original UTF-8 text. Used by the PDF backend's `/ToUnicode`
126 /// `CMap` (so copy-paste from the rendered PDF round-trips back to
127 /// the source) and by the Base14 emit path (which encodes through
128 /// `WinAnsiEncoding` + per-document `/Differences`).
129 pub text: String,
130 /// Optional semantic replacement text for PDF `/ActualText`.
131 /// Raw blocks use this when the painted text must differ from
132 /// source text, e.g. tabs painted as spaces but copied as tabs.
133 pub actual_text: Option<String>,
134 /// Shaped glyph stream for embedded-font runs. Empty for Base14
135 /// runs, which emit through the byte-encoded `WinAnsi` path
136 /// instead.
137 pub glyphs: Vec<ShapedGlyph>,
138}
139
140/// One laid-out page.
141#[derive(Clone, Debug)]
142pub struct Page {
143 pub number: u32,
144 pub width_pt: f32,
145 pub height_pt: f32,
146 pub runs: Vec<TextRun>,
147 /// Raster image placements on this page. Stored as a separate
148 /// vector so PDF emit can walk every placement without filtering
149 /// the text-run stream; the two streams are independent.
150 pub images: Vec<ImagePlacement>,
151}
152
153/// The paginated output graph (manifest §6 stage 7).
154#[derive(Clone, Debug, Default)]
155pub struct PageGraph {
156 pub pages: Vec<Page>,
157 /// Master list of every unique image referenced anywhere in
158 /// `pages`, ordered by [`ImageHandle::id`]. The PDF backend walks
159 /// this once to emit `XObject`s; per-page [`ImagePlacement::handle`]
160 /// references are just thin pointers into the same table.
161 pub images: Vec<ImageHandle>,
162}
163
164/// Result of laying out a [`mos_core::Document`]: a [`PageGraph`] plus
165/// any warnings the engine emitted. Mirrors `mos_eval::LowerResult` so
166/// the CLI can render diagnostics uniformly.
167#[derive(Debug)]
168pub struct LayoutResult {
169 pub graph: PageGraph,
170 pub diagnostics: Vec<Diagnostic>,
171 /// Map from a declared label to the 1-based number of the page its
172 /// target first lands on (issue #72). Built during layout as each
173 /// labelled block commits its first content; first placement wins, so a
174 /// label on a block that spans pages maps to its *start* page, and a
175 /// labelled block that produced no content is absent.
176 ///
177 /// This is the layout-side half of page-reference resolution: the
178 /// resolve↔layout fixpoint feeds this map back into the resolver so
179 /// `@page(label)` can render the target's printed page number. It lives on
180 /// the result rather than the [`PageGraph`] because it feeds the resolver,
181 /// not the PDF backend, which consumes only `graph`.
182 pub label_pages: BTreeMap<String, u32>,
183}