Skip to main content

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}