Skip to main content

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}