kozan_core/layout/context.rs
1//! Layout context — shared state for a layout pass.
2//!
3//! Chrome equivalent: the combination of `LayoutView` (document-level state)
4//! and `NGLayoutAlgorithmParams` (per-algorithm context).
5//!
6//! `LayoutContext` is created once per layout pass and threaded through
7//! every algorithm call. It carries dependencies that algorithms need
8//! but should NOT hardcode (text measurement, font system, etc.).
9//!
10//! # Why a struct, not individual parameters?
11//!
12//! 1. Adding a new dependency (e.g., image loader) doesn't change every
13//! function signature in the call chain.
14//! 2. Algorithms can't accidentally create their own measurer —
15//! they MUST use the one provided.
16//! 3. Mirrors Chrome: algorithms receive context, not individual services.
17
18use super::inline::measurer::TextMeasurer;
19
20/// Shared context for a layout pass.
21///
22/// Created once by the caller (platform/view layer) and passed through
23/// the entire layout tree. Algorithms read from it, never construct
24/// their own dependencies.
25///
26/// Chrome equivalent: combination of `Document::GetLayoutView()` context
27/// and the font/shaping system accessible through `ComputedStyle::GetFont()`.
28pub struct LayoutContext<'a> {
29 /// The text measurer for this layout pass.
30 ///
31 /// Provides text width measurement and font metrics.
32 /// Chrome: accessed via `Font` → `CachingWordShaper` → `HarfBuzzShaper`.
33 ///
34 /// The platform layer sets this to the appropriate implementation:
35 /// - `DefaultTextMeasurer` for estimation (no font system yet)
36 /// - Parley-based measurer when integrated
37 /// - Custom measurer for testing
38 pub text_measurer: &'a dyn TextMeasurer,
39}
40
41#[cfg(test)]
42mod tests {
43 use super::*;
44 use crate::layout::inline::measurer::FontMetrics;
45 use crate::layout::inline::{FontSystem, TextMetrics};
46
47 #[test]
48 fn context_carries_measurer() {
49 let measurer = FontSystem::new();
50 let ctx = LayoutContext {
51 text_measurer: &measurer,
52 };
53
54 // Algorithms use ctx.text_measurer, not create their own.
55 let metrics = ctx.text_measurer.measure("Hello", 16.0);
56 assert!(metrics.width > 0.0);
57 }
58
59 #[test]
60 fn custom_measurer_via_context() {
61 struct TestMeasurer;
62 impl TextMeasurer for TestMeasurer {
63 fn measure(&self, _text: &str, _font_size: f32) -> TextMetrics {
64 TextMetrics { width: 42.0 }
65 }
66 fn font_metrics(&self, _font_size: f32) -> FontMetrics {
67 FontMetrics {
68 ascent: 12.0,
69 descent: 4.0,
70 line_gap: 0.0,
71 }
72 }
73 }
74
75 let measurer = TestMeasurer;
76 let ctx = LayoutContext {
77 text_measurer: &measurer,
78 };
79 assert_eq!(ctx.text_measurer.measure("x", 16.0).width, 42.0);
80 }
81}