kozan_core/layout/result.rs
1//! Layout result — the output of a layout algorithm.
2//!
3//! Chrome equivalent: `NGLayoutResult`. Wraps the fragment plus
4//! additional information needed by the parent (out-of-flow descendants,
5//! break tokens for fragmentation, intrinsic sizes).
6//!
7//! The parent layout algorithm calls `child.layout(space)` and gets
8//! back a `LayoutResult`. It reads the fragment to know the child's size,
9//! then positions it.
10
11use std::sync::Arc;
12
13use super::fragment::Fragment;
14
15/// The result of running a layout algorithm on a node.
16///
17/// Chrome equivalent: `NGLayoutResult`.
18///
19/// Contains the fragment (immutable output) plus metadata the parent needs.
20/// Caching is handled externally (by `LayoutObject` or Taffy's `Cache`).
21#[derive(Debug, Clone)]
22#[non_exhaustive]
23pub struct LayoutResult {
24 /// The computed fragment for this node.
25 pub fragment: Arc<Fragment>,
26
27 /// Intrinsic inline sizes (min-content and max-content).
28 /// Cached here because flex/grid algorithms query them.
29 /// None = not yet computed.
30 pub intrinsic_sizes: Option<IntrinsicSizes>,
31
32 /// Margins that escaped from first/last child through this block.
33 /// Used by the parent for proper parent-child margin collapsing.
34 /// Zero for blocks that establish a BFC or have border/padding.
35 pub escaped_margins: EscapedMargins,
36}
37
38/// Intrinsic (preferred) sizes for a node.
39///
40/// Chrome equivalent: `MinMaxSizes`.
41/// Used by flex, grid, and shrink-to-fit layout to determine
42/// how wide a node "wants" to be.
43#[derive(Debug, Clone, Copy, PartialEq)]
44pub struct IntrinsicSizes {
45 /// Minimum content size (narrowest the content can be without overflow).
46 /// The width of the longest unbreakable word or widest atomic inline.
47 pub min_content: f32,
48
49 /// Maximum content size (widest the content would be with infinite space).
50 /// All content on one line.
51 pub max_content: f32,
52}
53
54/// Margins that escaped through a block due to CSS parent-child margin collapsing.
55///
56/// CSS rule: if a block has no border-top/padding-top and doesn't establish a BFC,
57/// the first child's top margin "escapes" and becomes part of the parent's top margin.
58/// Same for the bottom edge. The escaped margin is `max(parent_margin, child_margin)`.
59///
60/// Chrome equivalent: part of `NGMarginStrut` propagation in `NGBlockLayoutAlgorithm`.
61#[derive(Debug, Clone, Copy, Default, PartialEq)]
62pub struct EscapedMargins {
63 /// First child's margin that escaped through the top of this block.
64 pub top: f32,
65 /// Last child's margin that escaped through the bottom of this block.
66 pub bottom: f32,
67}
68
69impl IntrinsicSizes {
70 /// Clamp a given available size to the intrinsic range.
71 /// Used in shrink-to-fit calculations.
72 ///
73 /// CSS: `width = min(max_content, max(min_content, available))`.
74 #[inline]
75 #[must_use]
76 pub fn shrink_to_fit(&self, available: f32) -> f32 {
77 self.max_content.min(self.min_content.max(available))
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84 use crate::layout::fragment::{BoxFragmentData, Fragment};
85 use kozan_primitives::geometry::Size;
86
87 #[test]
88 fn layout_result_construction() {
89 let fragment = Fragment::new_box(Size::new(800.0, 200.0), BoxFragmentData::default());
90
91 let result = LayoutResult {
92 fragment,
93 intrinsic_sizes: None,
94 escaped_margins: EscapedMargins::default(),
95 };
96
97 assert!(result.fragment.is_box());
98 }
99
100 #[test]
101 fn intrinsic_sizes_shrink_to_fit() {
102 let sizes = IntrinsicSizes {
103 min_content: 100.0,
104 max_content: 500.0,
105 };
106
107 // Available > max_content → use max_content.
108 assert_eq!(sizes.shrink_to_fit(1000.0), 500.0);
109
110 // Available between min and max → use available.
111 assert_eq!(sizes.shrink_to_fit(300.0), 300.0);
112
113 // Available < min_content → use min_content.
114 assert_eq!(sizes.shrink_to_fit(50.0), 100.0);
115 }
116}