tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 499HTML 245JavaScript 8CSS 61218 Definiciones

Fix sizing of flexbox nodes under a min-content constraint (#291)

* Fix flexbox nodes sized under a min_content constraint

* Improve caching in light on min_content changes (bump cache size to 5)

* Update measure.rs tests: these were plain wrong (don't match chrome)

* Fix clippy lints

* Add min-content fix to release notes

* Document caching strategy

* Remove debug logs from only_measure_once test

* Remove commented out code

* Cargo fmt

163284a65cd48cdf6c6b96b9216284cb1fefacd3 · por Nico Burns · (hace 3 años) · padre 2b7d63a03 · ver el árbol en este commit

Delta semántico · 39

benches/generated/grid_min_content_flex_column.rs

benches/generated/grid_min_content_flex_row.rs

benches/generated/mod.rs

src/compute/flexbox.rs

src/compute/mod.rs

src/data.rs

tests/generated/grid_min_content_flex_column.rs

tests/generated/grid_min_content_flex_row.rs

tests/generated/mod.rs

tests/measure.rs

13 archivos · +457 −82

diff --git a/RELEASES.md b/RELEASES.md
index 1559ce214..203e632fe 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -1,5 +1,11 @@
 # Release Notes
 
+## 0.3.0-alpha2 (unreleased)
+
+### Fixes
+
+- Flexbox nodes sized under a min-content constraint now size correctly (#291)
+
 ## 0.3.0-alpha1
 
 This is the first in a series of planned alpha releases to allow users of Taffy to try out the new CSS Grid layout mode in advance of a stable release. We hope that by marking this is alpha release we are clearly communicating that this a pre-release and that the implementation is not yet of production quality. But we never-the-less encourage you to try it out. Feedback is welcome, and bug reports for the Grid implementation are being accepted as of this release.
diff --git a/benches/generated/grid_min_content_flex_column.rs b/benches/generated/grid_min_content_flex_column.rs
new file mode 100644
index 000000000..1714e6234
--- /dev/null
+++ b/benches/generated/grid_min_content_flex_column.rs
@@ -0,0 +1,54 @@
+pub fn compute() {
+    #[allow(unused_imports)]
+    use taffy::prelude::*;
+    let mut taffy = taffy::Taffy::new();
+    let node00 = taffy
+        .new_leaf_with_measure(
+            taffy::style::Style { ..Default::default() },
+            taffy::node::MeasureFunc::Raw(|known_dimensions, available_space| {
+                const TEXT: &str = "HH\u{200b}HH";
+                super::measure_standard_text(known_dimensions, available_space, TEXT)
+            }),
+        )
+        .unwrap();
+    let node01 = taffy
+        .new_leaf_with_measure(
+            taffy::style::Style { ..Default::default() },
+            taffy::node::MeasureFunc::Raw(|known_dimensions, available_space| {
+                const TEXT: &str = "HH\u{200b}HH";
+                super::measure_standard_text(known_dimensions, available_space, TEXT)
+            }),
+        )
+        .unwrap();
+    let node02 = taffy
+        .new_leaf_with_measure(
+            taffy::style::Style { ..Default::default() },
+            taffy::node::MeasureFunc::Raw(|known_dimensions, available_space| {
+                const TEXT: &str = "HH\u{200b}HH";
+                super::measure_standard_text(known_dimensions, available_space, TEXT)
+            }),
+        )
+        .unwrap();
+    let node0 = taffy
+        .new_with_children(
+            taffy::style::Style {
+                display: taffy::style::Display::Flex,
+                flex_direction: taffy::style::FlexDirection::Column,
+                ..Default::default()
+            },
+            &[node00, node01, node02],
+        )
+        .unwrap();
+    let node = taffy
+        .new_with_children(
+            taffy::style::Style {
+                display: taffy::style::Display::Grid,
+                grid_template_rows: vec![points(40f32)],
+                grid_template_columns: vec![min_content()],
+                ..Default::default()
+            },
+            &[node0],
+        )
+        .unwrap();
+    taffy.compute_layout(node, taffy::geometry::Size::MAX_CONTENT).unwrap();
+}
diff --git a/benches/generated/grid_min_content_flex_row.rs b/benches/generated/grid_min_content_flex_row.rs
new file mode 100644
index 000000000..0d715bda0
--- /dev/null
+++ b/benches/generated/grid_min_content_flex_row.rs
@@ -0,0 +1,50 @@
+pub fn compute() {
+    #[allow(unused_imports)]
+    use taffy::prelude::*;
+    let mut taffy = taffy::Taffy::new();
+    let node00 = taffy
+        .new_leaf_with_measure(
+            taffy::style::Style { ..Default::default() },
+            taffy::node::MeasureFunc::Raw(|known_dimensions, available_space| {
+                const TEXT: &str = "HH\u{200b}HH";
+                super::measure_standard_text(known_dimensions, available_space, TEXT)
+            }),
+        )
+        .unwrap();
+    let node01 = taffy
+        .new_leaf_with_measure(
+            taffy::style::Style { ..Default::default() },
+            taffy::node::MeasureFunc::Raw(|known_dimensions, available_space| {
+                const TEXT: &str = "HH\u{200b}HH";
+                super::measure_standard_text(known_dimensions, available_space, TEXT)
+            }),
+        )
+        .unwrap();
+    let node02 = taffy
+        .new_leaf_with_measure(
+            taffy::style::Style { ..Default::default() },
+            taffy::node::MeasureFunc::Raw(|known_dimensions, available_space| {
+                const TEXT: &str = "HH\u{200b}HH";
+                super::measure_standard_text(known_dimensions, available_space, TEXT)
+            }),
+        )
+        .unwrap();
+    let node0 = taffy
+        .new_with_children(
+            taffy::style::Style { display: taffy::style::Display::Flex, ..Default::default() },
+            &[node00, node01, node02],
+        )
+        .unwrap();
+    let node = taffy
+        .new_with_children(
+            taffy::style::Style {
+                display: taffy::style::Display::Grid,
+                grid_template_rows: vec![points(40f32)],
+                grid_template_columns: vec![min_content()],
+                ..Default::default()
+            },
+            &[node0],
+        )
+        .unwrap();
+    taffy.compute_layout(node, taffy::geometry::Size::MAX_CONTENT).unwrap();
+}
diff --git a/benches/generated/mod.rs b/benches/generated/mod.rs
index f887159a4..b5e58dc88 100644
--- a/benches/generated/mod.rs
+++ b/benches/generated/mod.rs
@@ -359,6 +359,10 @@ mod grid_max_content_single_item_margin_fixed;
 #[cfg(feature = "experimental_grid")]
 mod grid_max_content_single_item_margin_percent;
 #[cfg(feature = "experimental_grid")]
+mod grid_min_content_flex_column;
+#[cfg(feature = "experimental_grid")]
+mod grid_min_content_flex_row;
+#[cfg(feature = "experimental_grid")]
 mod grid_min_content_flex_single_item;
 #[cfg(feature = "experimental_grid")]
 mod grid_min_content_flex_single_item_margin_auto;
@@ -839,6 +843,10 @@ fn benchmark(c: &mut Criterion) {
             #[cfg(feature = "experimental_grid")]
             grid_max_content_single_item_margin_percent::compute();
             #[cfg(feature = "experimental_grid")]
+            grid_min_content_flex_column::compute();
+            #[cfg(feature = "experimental_grid")]
+            grid_min_content_flex_row::compute();
+            #[cfg(feature = "experimental_grid")]
             grid_min_content_flex_single_item::compute();
             #[cfg(feature = "experimental_grid")]
             grid_min_content_flex_single_item_margin_auto::compute();
diff --git a/src/compute/flexbox.rs b/src/compute/flexbox.rs
index 6eb546859..52b04ee3c 100644
--- a/src/compute/flexbox.rs
+++ b/src/compute/flexbox.rs
@@ -9,7 +9,7 @@ use crate::geometry::{Point, Rect, Size};
 use crate::layout::{Layout, RunMode, SizingMode};
 use crate::math::MaybeMath;
 use crate::node::Node;
-use crate::prelude::TaffyMaxContent;
+use crate::prelude::{TaffyMaxContent, TaffyMinContent};
 use crate::resolve::{MaybeResolve, ResolveOrZero};
 use crate::style::{
     AlignContent, AlignItems, AlignSelf, AvailableSpace, Dimension, Display, FlexWrap, JustifyContent,
@@ -36,6 +36,10 @@ struct FlexItem {
     /// The cross-alignment of this item
     align_self: AlignSelf,
 
+    /// The minimum size of the item. This differs from min_size above because it also
+    /// takes into account content based automatic minimum sizes
+    resolved_minimum_size: Size<f32>,
+
     /// The final offset of this item
     position: Rect<Option<f32>>,
     /// The margin of this item
@@ -202,6 +206,15 @@ fn compute_preliminary(
     NODE_LOGGER.log("determine_flex_base_size");
     determine_flex_base_size(tree, known_dimensions, &constants, available_space, &mut flex_items);
 
+    #[cfg(feature = "debug")]
+    for item in flex_items.iter() {
+        NODE_LOGGER.labelled_log("item.flex_basis", item.flex_basis);
+        NODE_LOGGER.labelled_log("item.inner_flex_basis", item.inner_flex_basis);
+        NODE_LOGGER.labelled_debug_log("item.hypothetical_outer_size", item.hypothetical_outer_size);
+        NODE_LOGGER.labelled_debug_log("item.hypothetical_inner_size", item.hypothetical_inner_size);
+        NODE_LOGGER.labelled_debug_log("item.resolved_minimum_size", item.resolved_minimum_size);
+    }
+
     // TODO: Add step 4 according to spec: https://www.w3.org/TR/css-flexbox-1/#algo-main-container
     // 9.3. Main Size Determination
 
@@ -431,6 +444,7 @@ fn generate_anonymous_flex_items(tree: &impl LayoutTree, node: Node, constants:
             violation: 0.0,
             frozen: false,
 
+            resolved_minimum_size: Size::zero(),
             hypothetical_inner_size: Size::zero(),
             hypothetical_outer_size: Size::zero(),
             target_size: Size::zero(),
@@ -565,13 +579,8 @@ fn determine_flex_base_size(
 
         let child_known_dimensions = {
             let mut ckd = child.size;
-            if child.align_self == AlignSelf::Stretch {
-                if constants.is_column && ckd.width.is_none() {
-                    ckd.width = available_space.width.into_option();
-                }
-                if constants.is_row && ckd.height.is_none() {
-                    ckd.height = available_space.height.into_option();
-                }
+            if child.align_self == AlignSelf::Stretch && ckd.cross(constants.dir).is_none() {
+                ckd.set_cross(constants.dir, available_space.cross(constants.dir).into_option());
             }
             ckd
         };
@@ -579,13 +588,12 @@ fn determine_flex_base_size(
         child.flex_basis = compute_node_layout(
             tree,
             child.node,
-            child_known_dimensions.maybe_min(child.max_size),
+            child_known_dimensions,
             available_space,
             RunMode::ComputeSize,
             SizingMode::ContentSize,
         )
-        .main(constants.dir)
-        .maybe_min(child.max_size.main(constants.dir));
+        .main(constants.dir);
     }
 
     // The hypothetical main size is the item’s flex base size clamped according to its
@@ -595,26 +603,36 @@ fn determine_flex_base_size(
         child.inner_flex_basis =
             child.flex_basis - child.padding.main_axis_sum(constants.dir) - child.border.main_axis_sum(constants.dir);
 
-        // TODO - not really spec abiding but needs to be done somewhere. probably somewhere else though.
-        // The following logic was developed not from the spec but by trail and error looking into how
-        // webkit handled various scenarios. Can probably be solved better by passing in
-        // min-content max-content constraints from the top
-        let min_main = compute_node_layout(
+        let child_known_dimensions = {
+            let mut ckd = Size::NONE;
+            if child.align_self == AlignSelf::Stretch && ckd.cross(constants.dir).is_none() {
+                ckd.set_cross(constants.dir, available_space.cross(constants.dir).into_option());
+            }
+            ckd
+        };
+
+        let min_content_size = compute_node_layout(
             tree,
             child.node,
-            Size::NONE,
-            available_space,
+            child_known_dimensions, // Should possibly also be Size::NONE
+            Size::MIN_CONTENT,
             RunMode::ComputeSize,
             SizingMode::ContentSize,
-        )
-        .main(constants.dir)
-        .maybe_clamp(child.min_size.main(constants.dir), child.size.main(constants.dir))
-        .into();
+        );
 
-        child
-            .hypothetical_inner_size
-            .set_main(constants.dir, child.flex_basis.maybe_clamp(min_main, child.max_size.main(constants.dir)));
+        // 4.5. Automatic Minimum Size of Flex Items
+        // https://www.w3.org/TR/css-flexbox-1/#min-size-auto
+        let specified = child.size.maybe_min(child.max_size);
+        child.resolved_minimum_size = child.min_size.unwrap_or(min_content_size.maybe_min(specified));
 
+        let hypothetical_inner_min_main = min_content_size
+            .main(constants.dir)
+            .maybe_clamp(child.resolved_minimum_size.main(constants.dir).into(), child.size.main(constants.dir))
+            .into();
+        child.hypothetical_inner_size.set_main(
+            constants.dir,
+            child.flex_basis.maybe_clamp(hypothetical_inner_min_main, child.max_size.main(constants.dir)),
+        );
         child.hypothetical_outer_size.set_main(
             constants.dir,
             child.hypothetical_inner_size.main(constants.dir) + child.margin.main_axis_sum(constants.dir),
@@ -770,7 +788,7 @@ fn resolve_flexible_lengths(
             .iter()
             .map(|child| {
                 child.margin.main_axis_sum(constants.dir)
-                    + if child.frozen { child.target_size.main(constants.dir) } else { child.flex_basis }
+                    + if child.frozen { child.outer_target_size.main(constants.dir) } else { child.flex_basis }
             })
             .sum::<f32>();
 
@@ -798,7 +816,7 @@ fn resolve_flexible_lengths(
                 .iter()
                 .map(|child| {
                     child.margin.main_axis_sum(constants.dir)
-                        + if child.frozen { child.target_size.main(constants.dir) } else { child.flex_basis }
+                        + if child.frozen { child.outer_target_size.main(constants.dir) } else { child.flex_basis }
                 })
                 .sum::<f32>();
 
@@ -870,29 +888,9 @@ fn resolve_flexible_lengths(
         //    If the item’s target main size was made larger by this, it’s a min violation.
 
         let total_violation = unfrozen.iter_mut().fold(0.0, |acc, child| -> f32 {
-            // TODO - not really spec abiding but needs to be done somewhere. probably somewhere else though.
-            // The following logic was developed not from the spec but by trial and error looking into how
-            // webkit handled various scenarios. Can probably be solved better by passing in
-            // min-content max-content constraints from the top. Need to figure out correct thing to do here as
-            // just piling on more conditionals.
-            let min_main = if constants.is_row && !tree.needs_measure(child.node) {
-                compute_node_layout(
-                    tree,
-                    child.node,
-                    Size::NONE,
-                    available_space,
-                    RunMode::ComputeSize,
-                    SizingMode::ContentSize,
-                )
-                .width
-                .maybe_clamp(child.min_size.width, child.size.width)
-                .into()
-            } else {
-                child.min_size.main(constants.dir)
-            };
-
+            let resolved_min_main: Option<f32> = child.resolved_minimum_size.main(constants.dir).into();
             let max_main = child.max_size.main(constants.dir);
-            let clamped = child.target_size.main(constants.dir).maybe_clamp(min_main, max_main).max(0.0);
+            let clamped = child.target_size.main(constants.dir).maybe_clamp(resolved_min_main, max_main).max(0.0);
             child.violation = clamped - child.target_size.main(constants.dir);
             child.target_size.set_main(constants.dir, clamped);
             child.outer_target_size.set_main(
diff --git a/src/compute/mod.rs b/src/compute/mod.rs
index 8758f4927..157615ea4 100644
--- a/src/compute/mod.rs
+++ b/src/compute/mod.rs
@@ -7,6 +7,7 @@ pub(crate) mod leaf;
 #[cfg(feature = "experimental_grid")]
 pub(crate) mod grid;
 
+use crate::data::CACHE_SIZE;
 use crate::error::TaffyError;
 use crate::geometry::{Point, Size};
 use crate::layout::{Cache, Layout, RunMode, SizingMode};
@@ -130,7 +131,7 @@ fn compute_node_layout(
     };
 
     // Cache result
-    let cache_slot = (known_dimensions.width.is_some() as usize) + (known_dimensions.height.is_some() as usize * 2);
+    let cache_slot = compute_cache_slot(known_dimensions, available_space);
     *tree.cache_mut(node, cache_slot) =
         Some(Cache { known_dimensions, available_space, run_mode: cache_run_mode, cached_size: computed_size });
 
@@ -142,6 +143,50 @@ fn compute_node_layout(
     computed_size
 }
 
+/// Return the cache slot to cache the current computed result in
+///
+/// ## Caching Strategy
+///
+/// We need multiple cache slots, because a node's size is often queried by it's parent multiple times in the course of the layout
+/// process, and we don't want later results to clobber earlier ones.
+///
+/// The two variables that we care about when determining cache slot are:
+///
+///   - How many "known_dimensions" are set. In the worst case, a node may be called first with neither dimensions known known_dimensions,
+///     then with one dimension known (either width of height - which doesn't matter for our purposes here), and then with both dimensions
+///     known.
+///   - Whether unknown dimensions are being sized under a min-content or a max-content available space constraint (definite available space
+///     shares a cache slot with max-content because a node will generally be sized under one or the other but not both).
+///
+/// ## Cache slots:
+///
+/// - Slot 0: Both known_dimensions were set
+/// - Slot 1: 1 of 2 known_dimensions were set and the other dimension was either a MaxContent or Definite available space constraint
+/// - Slot 2: 1 of 2 known_dimensions were set and the other dimension was a MinContent constraint
+/// - Slot 3: Neither known_dimensions were set and we are sizing under a MaxContent or Definite available space constraint
+/// - Slot 4: Neither known_dimensions were set and we are sizing under a MinContent constraint
+#[inline]
+fn compute_cache_slot(known_dimensions: Size<Option<f32>>, available_space: Size<AvailableSpace>) -> usize {
+    let has_known_width = known_dimensions.width.is_some();
+    let has_known_height = known_dimensions.height.is_some();
+
+    // Slot 0: Both known_dimensions were set
+    if has_known_width && has_known_height {
+        return 0;
+    }
+
+    // Slot 1: 1 of 2 known_dimensions were set and the other dimension was either a MaxContent or Definite available space constraint
+    // Slot 2: 1 of 2 known_dimensions were set and the other dimension was a MinContent constraint
+    if has_known_width || has_known_height {
+        let other_dim_available_space = if has_known_width { available_space.height } else { available_space.width };
+        return 1 + (other_dim_available_space == AvailableSpace::MinContent) as usize;
+    }
+
+    // Slot 3: Neither known_dimensions were set and we are sizing under a MaxContent or Definite available space constraint
+    // Slot 4: Neither known_dimensions were set and we are sizing under a MinContent constraint
+    3 + (available_space.width == AvailableSpace::MinContent) as usize
+}
+
 /// Try to get the computation result from the cache.
 #[inline]
 fn compute_from_cache(
@@ -152,7 +197,7 @@ fn compute_from_cache(
     run_mode: RunMode,
     sizing_mode: SizingMode,
 ) -> Option<Size<f32>> {
-    for idx in 0..4 {
+    for idx in 0..CACHE_SIZE {
         let entry = tree.cache_mut(node, idx);
         #[cfg(feature = "debug")]
         NODE_LOGGER.labelled_debug_log("cache_entry", &entry);
@@ -162,24 +207,20 @@ fn compute_from_cache(
                 return None;
             }
 
-            // if known_dimensions.width == entry.known_dimensions.width
-            // && known_dimensions.height == entry.known_dimensions.height
-            if (known_dimensions.width == entry.known_dimensions.width || known_dimensions.width == Some(entry.cached_size.width))
-                && (known_dimensions.height == entry.known_dimensions.height || known_dimensions.height == Some(entry.cached_size.height))
-                // && entry.available_space.width.is_roughly_equal(available_space.width)
-                // && entry.available_space.height.is_roughly_equal(available_space.height)
-                && (
-                  known_dimensions.width.is_some()
-                  || entry.available_space.width.is_roughly_equal(available_space.width)
-                  || (sizing_mode == SizingMode::ContentSize && available_space.width.is_definite() && available_space.width.unwrap() >= entry.cached_size.width)
-                )
-                && (
-                  known_dimensions.height.is_some()
-                  || entry.available_space.height.is_roughly_equal(available_space.height)
-                  || (sizing_mode == SizingMode::ContentSize && available_space.height.is_definite() && available_space.height.unwrap() >= entry.cached_size.height)
-                )
-            // && (entry.available_space.width.is_roughly_equal(available_space.width) || (available_space.width.is_definite() && available_space.width.unwrap() >= entry.cached_size.width))
-            // && (entry.available_space.height.is_roughly_equal(available_space.height) || (available_space.height.is_definite() && available_space.height.unwrap() >= entry.cached_size.height))
+            if (known_dimensions.width == entry.known_dimensions.width
+                || known_dimensions.width == Some(entry.cached_size.width))
+                && (known_dimensions.height == entry.known_dimensions.height
+                    || known_dimensions.height == Some(entry.cached_size.height))
+                && (known_dimensions.width.is_some()
+                    || entry.available_space.width.is_roughly_equal(available_space.width)
+                    || (sizing_mode == SizingMode::ContentSize
+                        && available_space.width.is_definite()
+                        && available_space.width.unwrap() >= entry.cached_size.width))
+                && (known_dimensions.height.is_some()
+                    || entry.available_space.height.is_roughly_equal(available_space.height)
+                    || (sizing_mode == SizingMode::ContentSize
+                        && available_space.height.is_definite()
+                        && available_space.height.unwrap() >= entry.cached_size.height))
             {
                 return Some(entry.cached_size);
             }
diff --git a/src/data.rs b/src/data.rs
index 871bb5e73..f09d3cb1b 100644
--- a/src/data.rs
+++ b/src/data.rs
@@ -5,6 +5,9 @@
 use crate::layout::{Cache, Layout};
 use crate::style::Style;
 
+/// The number of cache entries for each node in the tree
+pub(crate) const CACHE_SIZE: usize = 5;
+
 /// Layout information for a given [`Node`](crate::node::Node)
 ///
 /// Stored in a [`Taffy`].
@@ -18,14 +21,14 @@ pub(crate) struct NodeData {
     pub(crate) needs_measure: bool,
 
     /// The primary cached results of the layout computation
-    pub(crate) size_cache: [Option<Cache>; 4],
+    pub(crate) size_cache: [Option<Cache>; CACHE_SIZE],
 }
 
 impl NodeData {
     /// Create the data for a new node
     #[must_use]
     pub const fn new(style: Style) -> Self {
-        Self { style, size_cache: [None; 4], layout: Layout::new(), needs_measure: false }
+        Self { style, size_cache: [None; CACHE_SIZE], layout: Layout::new(), needs_measure: false }
     }
 
     /// Marks a node and all of its parents (recursively) as dirty
@@ -33,6 +36,6 @@ impl NodeData {
     /// This clears any cached data and signals that the data must be recomputed.
     #[inline]
     pub fn mark_dirty(&mut self) {
-        self.size_cache = [None; 4];
+        self.size_cache = [None; CACHE_SIZE];
     }
 }
diff --git a/test_fixtures/grid_min_content_flex_column.html b/test_fixtures/grid_min_content_flex_column.html
new file mode 100644
index 000000000..975977e19
--- /dev/null
+++ b/test_fixtures/grid_min_content_flex_column.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <script src="../scripts/gentest/test_helper.js"></script>
+  <link rel="stylesheet" type="text/css" href="../scripts/gentest/test_base_style.css">
+  <title>
+    Test description
+  </title>
+<head/>
+<body>
+
+<div id="test-root" style="display: grid; grid-template-columns: min-content;grid-template-rows: 40px">
+  <div style="display: flex;flex-direction: column">
+    <div>HH&ZeroWidthSpace;HH</div>
+    <div>HH&ZeroWidthSpace;HH</div>
+    <div>HH&ZeroWidthSpace;HH</div>
+  </div>
+</div>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/test_fixtures/grid_min_content_flex_row.html b/test_fixtures/grid_min_content_flex_row.html
new file mode 100644
index 000000000..c4b8249f1
--- /dev/null
+++ b/test_fixtures/grid_min_content_flex_row.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <script src="../scripts/gentest/test_helper.js"></script>
+  <link rel="stylesheet" type="text/css" href="../scripts/gentest/test_base_style.css">
+  <title>
+    Test description
+  </title>
+<head/>
+<body>
+
+<div id="test-root" style="display: grid; grid-template-columns: min-content;grid-template-rows: 40px">
+  <div style="display: flex;flex-direction: row">
+    <div>HH&ZeroWidthSpace;HH</div>
+    <div>HH&ZeroWidthSpace;HH</div>
+    <div>HH&ZeroWidthSpace;HH</div>
+  </div>
+</div>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/tests/generated/grid_min_content_flex_column.rs b/tests/generated/grid_min_content_flex_column.rs
new file mode 100644
index 000000000..412744369
--- /dev/null
+++ b/tests/generated/grid_min_content_flex_column.rs
@@ -0,0 +1,84 @@
+#[test]
+fn grid_min_content_flex_column() {
+    use slotmap::Key;
+    #[allow(unused_imports)]
+    use taffy::{layout::Layout, prelude::*};
+    let mut taffy = taffy::Taffy::new();
+    let node00 = taffy
+        .new_leaf_with_measure(
+            taffy::style::Style { ..Default::default() },
+            taffy::node::MeasureFunc::Raw(|known_dimensions, available_space| {
+                const TEXT: &str = "HH\u{200b}HH";
+                super::measure_standard_text(known_dimensions, available_space, TEXT)
+            }),
+        )
+        .unwrap();
+    let node01 = taffy
+        .new_leaf_with_measure(
+            taffy::style::Style { ..Default::default() },
+            taffy::node::MeasureFunc::Raw(|known_dimensions, available_space| {
+                const TEXT: &str = "HH\u{200b}HH";
+                super::measure_standard_text(known_dimensions, available_space, TEXT)
+            }),
+        )
+        .unwrap();
+    let node02 = taffy
+        .new_leaf_with_measure(
+            taffy::style::Style { ..Default::default() },
+            taffy::node::MeasureFunc::Raw(|known_dimensions, available_space| {
+                const TEXT: &str = "HH\u{200b}HH";
+                super::measure_standard_text(known_dimensions, available_space, TEXT)
+            }),
+        )
+        .unwrap();
+    let node0 = taffy
+        .new_with_children(
+            taffy::style::Style {
+                display: taffy::style::Display::Flex,
+                flex_direction: taffy::style::FlexDirection::Column,
+                ..Default::default()
+            },
+            &[node00, node01, node02],
+        )
+        .unwrap();
+    let node = taffy
+        .new_with_children(
+            taffy::style::Style {
+                display: taffy::style::Display::Grid,
+                grid_template_rows: vec![points(40f32)],
+                grid_template_columns: vec![min_content()],
+                ..Default::default()
+            },
+            &[node0],
+        )
+        .unwrap();
+    taffy.compute_layout(node, taffy::geometry::Size::MAX_CONTENT).unwrap();
+    println!("\nComputed tree:");
+    taffy::debug::print_tree(&taffy, node);
+    println!();
+    let Layout { size, location, .. } = taffy.layout(node).unwrap();
+    assert_eq!(size.width, 20f32, "width of node {:?}. Expected {}. Actual {}", node.data(), 20f32, size.width);
+    assert_eq!(size.height, 40f32, "height of node {:?}. Expected {}. Actual {}", node.data(), 40f32, size.height);
+    assert_eq!(location.x, 0f32, "x of node {:?}. Expected {}. Actual {}", node.data(), 0f32, location.x);
+    assert_eq!(location.y, 0f32, "y of node {:?}. Expected {}. Actual {}", node.data(), 0f32, location.y);
+    let Layout { size, location, .. } = taffy.layout(node0).unwrap();
+    assert_eq!(size.width, 20f32, "width of node {:?}. Expected {}. Actual {}", node0.data(), 20f32, size.width);
+    assert_eq!(size.height, 40f32, "height of node {:?}. Expected {}. Actual {}", node0.data(), 40f32, size.height);
+    assert_eq!(location.x, 0f32, "x of node {:?}. Expected {}. Actual {}", node0.data(), 0f32, location.x);
+    assert_eq!(location.y, 0f32, "y of node {:?}. Expected {}. Actual {}", node0.data(), 0f32, location.y);
+    let Layout { size, location, .. } = taffy.layout(node00).unwrap();
+    assert_eq!(size.width, 20f32, "width of node {:?}. Expected {}. Actual {}", node00.data(), 20f32, size.width);
+    assert_eq!(size.height, 20f32, "height of node {:?}. Expected {}. Actual {}", node00.data(), 20f32, size.height);
+    assert_eq!(location.x, 0f32, "x of node {:?}. Expected {}. Actual {}", node00.data(), 0f32, location.x);
+    assert_eq!(location.y, 0f32, "y of node {:?}. Expected {}. Actual {}", node00.data(), 0f32, location.y);
+    let Layout { size, location, .. } = taffy.layout(node01).unwrap();
+    assert_eq!(size.width, 20f32, "width of node {:?}. Expected {}. Actual {}", node01.data(), 20f32, size.width);
+    assert_eq!(size.height, 20f32, "height of node {:?}. Expected {}. Actual {}", node01.data(), 20f32, size.height);
+    assert_eq!(location.x, 0f32, "x of node {:?}. Expected {}. Actual {}", node01.data(), 0f32, location.x);
+    assert_eq!(location.y, 20f32, "y of node {:?}. Expected {}. Actual {}", node01.data(), 20f32, location.y);
+    let Layout { size, location, .. } = taffy.layout(node02).unwrap();
+    assert_eq!(size.width, 20f32, "width of node {:?}. Expected {}. Actual {}", node02.data(), 20f32, size.width);
+    assert_eq!(size.height, 20f32, "height of node {:?}. Expected {}. Actual {}", node02.data(), 20f32, size.height);
+    assert_eq!(location.x, 0f32, "x of node {:?}. Expected {}. Actual {}", node02.data(), 0f32, location.x);
+    assert_eq!(location.y, 40f32, "y of node {:?}. Expected {}. Actual {}", node02.data(), 40f32, location.y);
+}
diff --git a/tests/generated/grid_min_content_flex_row.rs b/tests/generated/grid_min_content_flex_row.rs
new file mode 100644
index 000000000..084d485a7
--- /dev/null
+++ b/tests/generated/grid_min_content_flex_row.rs
@@ -0,0 +1,80 @@
+#[test]
+fn grid_min_content_flex_row() {
+    use slotmap::Key;
+    #[allow(unused_imports)]
+    use taffy::{layout::Layout, prelude::*};
+    let mut taffy = taffy::Taffy::new();
+    let node00 = taffy
+        .new_leaf_with_measure(
+            taffy::style::Style { ..Default::default() },
+            taffy::node::MeasureFunc::Raw(|known_dimensions, available_space| {
+                const TEXT: &str = "HH\u{200b}HH";
+                super::measure_standard_text(known_dimensions, available_space, TEXT)
+            }),
+        )
+        .unwrap();
+    let node01 = taffy
+        .new_leaf_with_measure(
+            taffy::style::Style { ..Default::default() },
+            taffy::node::MeasureFunc::Raw(|known_dimensions, available_space| {
+                const TEXT: &str = "HH\u{200b}HH";
+                super::measure_standard_text(known_dimensions, available_space, TEXT)
+            }),
+        )
+        .unwrap();
+    let node02 = taffy
+        .new_leaf_with_measure(
+            taffy::style::Style { ..Default::default() },
+            taffy::node::MeasureFunc::Raw(|known_dimensions, available_space| {
+                const TEXT: &str = "HH\u{200b}HH";
+                super::measure_standard_text(known_dimensions, available_space, TEXT)
+            }),
+        )
+        .unwrap();
+    let node0 = taffy
+        .new_with_children(
+            taffy::style::Style { display: taffy::style::Display::Flex, ..Default::default() },
+            &[node00, node01, node02],
+        )
+        .unwrap();
+    let node = taffy
+        .new_with_children(
+            taffy::style::Style {
+                display: taffy::style::Display::Grid,
+                grid_template_rows: vec![points(40f32)],
+                grid_template_columns: vec![min_content()],
+                ..Default::default()
+            },
+            &[node0],
+        )
+        .unwrap();
+    taffy.compute_layout(node, taffy::geometry::Size::MAX_CONTENT).unwrap();
+    println!("\nComputed tree:");
+    taffy::debug::print_tree(&taffy, node);
+    println!();
+    let Layout { size, location, .. } = taffy.layout(node).unwrap();
+    assert_eq!(size.width, 60f32, "width of node {:?}. Expected {}. Actual {}", node.data(), 60f32, size.width);
+    assert_eq!(size.height, 40f32, "height of node {:?}. Expected {}. Actual {}", node.data(), 40f32, size.height);
+    assert_eq!(location.x, 0f32, "x of node {:?}. Expected {}. Actual {}", node.data(), 0f32, location.x);
+    assert_eq!(location.y, 0f32, "y of node {:?}. Expected {}. Actual {}", node.data(), 0f32, location.y);
+    let Layout { size, location, .. } = taffy.layout(node0).unwrap();
+    assert_eq!(size.width, 60f32, "width of node {:?}. Expected {}. Actual {}", node0.data(), 60f32, size.width);
+    assert_eq!(size.height, 40f32, "height of node {:?}. Expected {}. Actual {}", node0.data(), 40f32, size.height);
+    assert_eq!(location.x, 0f32, "x of node {:?}. Expected {}. Actual {}", node0.data(), 0f32, location.x);
+    assert_eq!(location.y, 0f32, "y of node {:?}. Expected {}. Actual {}", node0.data(), 0f32, location.y);
+    let Layout { size, location, .. } = taffy.layout(node00).unwrap();
+    assert_eq!(size.width, 20f32, "width of node {:?}. Expected {}. Actual {}", node00.data(), 20f32, size.width);
+    assert_eq!(size.height, 40f32, "height of node {:?}. Expected {}. Actual {}", node00.data(), 40f32, size.height);
+    assert_eq!(location.x, 0f32, "x of node {:?}. Expected {}. Actual {}", node00.data(), 0f32, location.x);
+    assert_eq!(location.y, 0f32, "y of node {:?}. Expected {}. Actual {}", node00.data(), 0f32, location.y);
+    let Layout { size, location, .. } = taffy.layout(node01).unwrap();
+    assert_eq!(size.width, 20f32, "width of node {:?}. Expected {}. Actual {}", node01.data(), 20f32, size.width);
+    assert_eq!(size.height, 40f32, "height of node {:?}. Expected {}. Actual {}", node01.data(), 40f32, size.height);
+    assert_eq!(location.x, 20f32, "x of node {:?}. Expected {}. Actual {}", node01.data(), 20f32, location.x);
+    assert_eq!(location.y, 0f32, "y of node {:?}. Expected {}. Actual {}", node01.data(), 0f32, location.y);
+    let Layout { size, location, .. } = taffy.layout(node02).unwrap();
+    assert_eq!(size.width, 20f32, "width of node {:?}. Expected {}. Actual {}", node02.data(), 20f32, size.width);
+    assert_eq!(size.height, 40f32, "height of node {:?}. Expected {}. Actual {}", node02.data(), 40f32, size.height);
+    assert_eq!(location.x, 40f32, "x of node {:?}. Expected {}. Actual {}", node02.data(), 40f32, location.x);
+    assert_eq!(location.y, 0f32, "y of node {:?}. Expected {}. Actual {}", node02.data(), 0f32, location.y);
+}
diff --git a/tests/generated/mod.rs b/tests/generated/mod.rs
index 1e21b06bb..93f2a398d 100644
--- a/tests/generated/mod.rs
+++ b/tests/generated/mod.rs
@@ -358,6 +358,10 @@ mod grid_max_content_single_item_margin_fixed;
 #[cfg(feature = "experimental_grid")]
 mod grid_max_content_single_item_margin_percent;
 #[cfg(feature = "experimental_grid")]
+mod grid_min_content_flex_column;
+#[cfg(feature = "experimental_grid")]
+mod grid_min_content_flex_row;
+#[cfg(feature = "experimental_grid")]
 mod grid_min_content_flex_single_item;
 #[cfg(feature = "experimental_grid")]
 mod grid_min_content_flex_single_item_margin_auto;
diff --git a/tests/measure.rs b/tests/measure.rs
index 99fc7b6cb..9b65ae7a3 100644
--- a/tests/measure.rs
+++ b/tests/measure.rs
@@ -68,10 +68,11 @@ mod measure {
 
         taffy.compute_layout(node, Size::MAX_CONTENT).unwrap();
 
+        // Parent
         assert_eq!(taffy.layout(node).unwrap().size.width, 50.0);
         assert_eq!(taffy.layout(node).unwrap().size.height, 100.0);
-
-        assert_eq!(taffy.layout(child).unwrap().size.width, 50.0);
+        // Child
+        assert_eq!(taffy.layout(child).unwrap().size.width, 100.0);
         assert_eq!(taffy.layout(child).unwrap().size.height, 100.0);
     }
 
@@ -105,10 +106,14 @@ mod measure {
             .unwrap();
         taffy.compute_layout(node, Size::MAX_CONTENT).unwrap();
 
+        assert_eq!(taffy.layout(node).unwrap().location.x, 0.0);
+        assert_eq!(taffy.layout(node).unwrap().location.y, 0.0);
         assert_eq!(taffy.layout(node).unwrap().size.width, 50.0);
         assert_eq!(taffy.layout(node).unwrap().size.height, 120.0);
 
-        assert_eq!(taffy.layout(child).unwrap().size.width, 30.0);
+        assert_eq!(taffy.layout(child).unwrap().location.x, 10.0);
+        assert_eq!(taffy.layout(child).unwrap().location.y, 10.0);
+        assert_eq!(taffy.layout(child).unwrap().size.width, 100.0);
         assert_eq!(taffy.layout(child).unwrap().size.height, 100.0);
     }
 
@@ -175,7 +180,7 @@ mod measure {
 
         taffy.compute_layout(node, Size::MAX_CONTENT).unwrap();
 
-        assert_eq!(taffy.layout(child1).unwrap().size.width, 50.0);
+        assert_eq!(taffy.layout(child1).unwrap().size.width, 100.0);
         assert_eq!(taffy.layout(child1).unwrap().size.height, 50.0);
     }
 
@@ -253,8 +258,8 @@ mod measure {
 
         taffy.compute_layout(node, Size::MAX_CONTENT).unwrap();
 
-        assert_eq!(taffy.layout(child1).unwrap().size.width, 50.0);
-        assert_eq!(taffy.layout(child1).unwrap().size.height, 100.0);
+        assert_eq!(taffy.layout(child1).unwrap().size.width, 100.0);
+        assert_eq!(taffy.layout(child1).unwrap().size.height, 200.0);
     }
 
     #[test]
@@ -444,16 +449,16 @@ mod measure {
 
     #[test]
     fn only_measure_once() {
-        use std::sync::atomic;
+        use std::sync::atomic::{AtomicU32, Ordering};
 
         let mut taffy = Taffy::new();
-        static NUM_MEASURES: atomic::AtomicU32 = atomic::AtomicU32::new(0);
+        static NUM_MEASURES: AtomicU32 = AtomicU32::new(0);
 
         let grandchild = taffy
             .new_leaf_with_measure(
                 Style { ..Default::default() },
                 MeasureFunc::Raw(|known_dimensions, _available_space| {
-                    NUM_MEASURES.fetch_add(1, atomic::Ordering::SeqCst);
+                    NUM_MEASURES.fetch_add(1, Ordering::SeqCst);
                     Size {
                         width: known_dimensions.width.unwrap_or(50.0),
                         height: known_dimensions.height.unwrap_or(50.0),
@@ -467,6 +472,6 @@ mod measure {
         let node = taffy.new_with_children(Style { ..Default::default() }, &[child]).unwrap();
         taffy.compute_layout(node, Size::MAX_CONTENT).unwrap();
 
-        assert_eq!(NUM_MEASURES.load(atomic::Ordering::SeqCst), 1);
+        assert_eq!(NUM_MEASURES.load(Ordering::SeqCst), 2);
     }
 }