tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 788HTML 740Markdown 12TOML 62536 Definitions

Implement `calc()` in the low-level API (#773)

Signed-off-by: Nico Burns <nico@nicoburns.com>

0386dc966a41b6b10e4089018fcbeada72504df6 · by Nico Burns · (1 year ago) · parent afa6437b0 · browse the tree at this commit

Semantic delta · 98

examples/custom_tree_owned_partial.rs

examples/custom_tree_owned_unsafe.rs

examples/custom_tree_vec.rs

src/compute/block.rs

src/compute/flexbox.rs

src/compute/grid/alignment.rs

src/compute/grid/mod.rs

src/compute/grid/track_sizing.rs

src/compute/grid/types/grid_item.rs

src/compute/leaf.rs

src/compute/mod.rs

src/style/dimension.rs

src/style/grid.rs

src/tree/taffy_tree.rs

src/tree/traits.rs

src/util/resolve.rs

18 files · +603 −275

diff --git a/examples/custom_tree_owned_partial.rs b/examples/custom_tree_owned_partial.rs
index 7cae4b6ae..ac070e257 100644
--- a/examples/custom_tree_owned_partial.rs
+++ b/examples/custom_tree_owned_partial.rs
@@ -146,6 +146,10 @@ impl taffy::LayoutPartialTree for Node {
         self.node_from_id_mut(node_id).layout = *layout
     }
 
+    fn resolve_calc_value(&self, _val: u64, _basis: f32) -> f32 {
+        0.0
+    }
+
     fn compute_child_layout(&mut self, node_id: NodeId, inputs: taffy::tree::LayoutInput) -> taffy::tree::LayoutOutput {
         compute_cached_layout(self, node_id, inputs, |parent, node_id, inputs| {
             let node = parent.node_from_id_mut(node_id);
@@ -154,17 +158,27 @@ impl taffy::LayoutPartialTree for Node {
             match node.kind {
                 NodeKind::Flexbox => compute_flexbox_layout(node, node_id, inputs),
                 NodeKind::Grid => compute_grid_layout(node, node_id, inputs),
-                NodeKind::Text => compute_leaf_layout(inputs, &node.style, |known_dimensions, available_space| {
-                    text_measure_function(
-                        known_dimensions,
-                        available_space,
-                        node.text_data.as_ref().unwrap(),
-                        &font_metrics,
-                    )
-                }),
-                NodeKind::Image => compute_leaf_layout(inputs, &node.style, |known_dimensions, _available_space| {
-                    image_measure_function(known_dimensions, node.image_data.as_ref().unwrap())
-                }),
+                NodeKind::Text => compute_leaf_layout(
+                    inputs,
+                    &node.style,
+                    |_val, _basis| 0.0,
+                    |known_dimensions, available_space| {
+                        text_measure_function(
+                            known_dimensions,
+                            available_space,
+                            node.text_data.as_ref().unwrap(),
+                            &font_metrics,
+                        )
+                    },
+                ),
+                NodeKind::Image => compute_leaf_layout(
+                    inputs,
+                    &node.style,
+                    |_val, _basis| 0.0,
+                    |known_dimensions, _available_space| {
+                        image_measure_function(known_dimensions, node.image_data.as_ref().unwrap())
+                    },
+                ),
             }
         })
     }
diff --git a/examples/custom_tree_owned_unsafe.rs b/examples/custom_tree_owned_unsafe.rs
index 753424b06..ed13d653a 100644
--- a/examples/custom_tree_owned_unsafe.rs
+++ b/examples/custom_tree_owned_unsafe.rs
@@ -143,6 +143,10 @@ impl LayoutPartialTree for StatelessLayoutTree {
         unsafe { node_from_id_mut(node_id).unrounded_layout = *layout };
     }
 
+    fn resolve_calc_value(&self, _val: u64, _basis: f32) -> f32 {
+        0.0
+    }
+
     fn compute_child_layout(&mut self, node_id: NodeId, inputs: taffy::tree::LayoutInput) -> taffy::tree::LayoutOutput {
         compute_cached_layout(self, node_id, inputs, |tree, node_id, inputs| {
             let node = unsafe { node_from_id_mut(node_id) };
@@ -151,17 +155,27 @@ impl LayoutPartialTree for StatelessLayoutTree {
             match node.kind {
                 NodeKind::Flexbox => compute_flexbox_layout(tree, node_id, inputs),
                 NodeKind::Grid => compute_grid_layout(tree, node_id, inputs),
-                NodeKind::Text => compute_leaf_layout(inputs, &node.style, |known_dimensions, available_space| {
-                    text_measure_function(
-                        known_dimensions,
-                        available_space,
-                        node.text_data.as_ref().unwrap(),
-                        &font_metrics,
-                    )
-                }),
-                NodeKind::Image => compute_leaf_layout(inputs, &node.style, |known_dimensions, _available_space| {
-                    image_measure_function(known_dimensions, node.image_data.as_ref().unwrap())
-                }),
+                NodeKind::Text => compute_leaf_layout(
+                    inputs,
+                    &node.style,
+                    |val, basis| tree.resolve_calc_value(val, basis),
+                    |known_dimensions, available_space| {
+                        text_measure_function(
+                            known_dimensions,
+                            available_space,
+                            node.text_data.as_ref().unwrap(),
+                            &font_metrics,
+                        )
+                    },
+                ),
+                NodeKind::Image => compute_leaf_layout(
+                    inputs,
+                    &node.style,
+                    |val, basis| tree.resolve_calc_value(val, basis),
+                    |known_dimensions, _available_space| {
+                        image_measure_function(known_dimensions, node.image_data.as_ref().unwrap())
+                    },
+                ),
             }
         })
     }
diff --git a/examples/custom_tree_vec.rs b/examples/custom_tree_vec.rs
index 89ed08584..a7125cb94 100644
--- a/examples/custom_tree_vec.rs
+++ b/examples/custom_tree_vec.rs
@@ -152,25 +152,39 @@ impl taffy::LayoutPartialTree for Tree {
         self.node_from_id_mut(node_id).unrounded_layout = *layout;
     }
 
+    fn resolve_calc_value(&self, _val: u64, _basis: f32) -> f32 {
+        0.0
+    }
+
     fn compute_child_layout(&mut self, node_id: NodeId, inputs: taffy::tree::LayoutInput) -> taffy::tree::LayoutOutput {
         compute_cached_layout(self, node_id, inputs, |tree, node_id, inputs| {
-            let node = tree.node_from_id_mut(node_id);
+            let node = &mut tree.nodes[usize::from(node_id)];
             let font_metrics = FontMetrics { char_width: 10.0, char_height: 10.0 };
 
             match node.kind {
                 NodeKind::Flexbox => compute_flexbox_layout(tree, node_id, inputs),
                 NodeKind::Grid => compute_grid_layout(tree, node_id, inputs),
-                NodeKind::Text => compute_leaf_layout(inputs, &node.style, |known_dimensions, available_space| {
-                    text_measure_function(
-                        known_dimensions,
-                        available_space,
-                        node.text_data.as_ref().unwrap(),
-                        &font_metrics,
-                    )
-                }),
-                NodeKind::Image => compute_leaf_layout(inputs, &node.style, |known_dimensions, _available_space| {
-                    image_measure_function(known_dimensions, node.image_data.as_ref().unwrap())
-                }),
+                NodeKind::Text => compute_leaf_layout(
+                    inputs,
+                    &node.style,
+                    |_val, _basis| 0.0,
+                    |known_dimensions, available_space| {
+                        text_measure_function(
+                            known_dimensions,
+                            available_space,
+                            node.text_data.as_ref().unwrap(),
+                            &font_metrics,
+                        )
+                    },
+                ),
+                NodeKind::Image => compute_leaf_layout(
+                    inputs,
+                    &node.style,
+                    |_val, _basis| 0.0,
+                    |known_dimensions, _available_space| {
+                        image_measure_function(known_dimensions, node.image_data.as_ref().unwrap())
+                    },
+                ),
             }
         })
     }
diff --git a/src/compute/block.rs b/src/compute/block.rs
index be1c4f16d..35f5be89f 100644
--- a/src/compute/block.rs
+++ b/src/compute/block.rs
@@ -71,26 +71,26 @@ pub fn compute_block_layout(
 
     // Pull these out earlier to avoid borrowing issues
     let aspect_ratio = style.aspect_ratio();
-    let padding = style.padding().resolve_or_zero(parent_size.width);
-    let border = style.border().resolve_or_zero(parent_size.width);
+    let padding = style.padding().resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
+    let border = style.border().resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
     let padding_border_size = (padding + border).sum_axes();
     let box_sizing_adjustment =
         if style.box_sizing() == BoxSizing::ContentBox { padding_border_size } else { Size::ZERO };
 
     let min_size = style
         .min_size()
-        .maybe_resolve(parent_size)
+        .maybe_resolve(parent_size, |val, basis| tree.calc(val, basis))
         .maybe_apply_aspect_ratio(aspect_ratio)
         .maybe_add(box_sizing_adjustment);
     let max_size = style
         .max_size()
-        .maybe_resolve(parent_size)
+        .maybe_resolve(parent_size, |val, basis| tree.calc(val, basis))
         .maybe_apply_aspect_ratio(aspect_ratio)
         .maybe_add(box_sizing_adjustment);
     let clamped_style_size = if inputs.sizing_mode == SizingMode::InherentSize {
         style
             .size()
-            .maybe_resolve(parent_size)
+            .maybe_resolve(parent_size, |val, basis| tree.calc(val, basis))
             .maybe_apply_aspect_ratio(aspect_ratio)
             .maybe_add(box_sizing_adjustment)
             .maybe_clamp(min_size, max_size)
@@ -132,8 +132,8 @@ fn compute_inner(tree: &mut impl LayoutBlockContainer, node_id: NodeId, inputs:
     let raw_border = style.border();
     let raw_margin = style.margin();
     let aspect_ratio = style.aspect_ratio();
-    let padding = raw_padding.resolve_or_zero(parent_size.width);
-    let border = raw_border.resolve_or_zero(parent_size.width);
+    let padding = raw_padding.resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
+    let border = raw_border.resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
 
     // Scrollbar gutters are reserved when the `overflow` property is set to `Overflow::Scroll`.
     // However, the axis are switched (transposed) because a node that scrolls vertically needs
@@ -153,16 +153,19 @@ fn compute_inner(tree: &mut impl LayoutBlockContainer, node_id: NodeId, inputs:
 
     let box_sizing_adjustment =
         if style.box_sizing() == BoxSizing::ContentBox { padding_border_size } else { Size::ZERO };
-    let size =
-        style.size().maybe_resolve(parent_size).maybe_apply_aspect_ratio(aspect_ratio).maybe_add(box_sizing_adjustment);
+    let size = style
+        .size()
+        .maybe_resolve(parent_size, |val, basis| tree.calc(val, basis))
+        .maybe_apply_aspect_ratio(aspect_ratio)
+        .maybe_add(box_sizing_adjustment);
     let min_size = style
         .min_size()
-        .maybe_resolve(parent_size)
+        .maybe_resolve(parent_size, |val, basis| tree.calc(val, basis))
         .maybe_apply_aspect_ratio(aspect_ratio)
         .maybe_add(box_sizing_adjustment);
     let max_size = style
         .max_size()
-        .maybe_resolve(parent_size)
+        .maybe_resolve(parent_size, |val, basis| tree.calc(val, basis))
         .maybe_apply_aspect_ratio(aspect_ratio)
         .maybe_add(box_sizing_adjustment);
 
@@ -214,8 +217,8 @@ fn compute_inner(tree: &mut impl LayoutBlockContainer, node_id: NodeId, inputs:
     }
 
     // 3. Perform final item layout and return content height
-    let resolved_padding = raw_padding.resolve_or_zero(Some(container_outer_width));
-    let resolved_border = raw_border.resolve_or_zero(Some(container_outer_width));
+    let resolved_padding = raw_padding.resolve_or_zero(Some(container_outer_width), |val, basis| tree.calc(val, basis));
+    let resolved_border = raw_border.resolve_or_zero(Some(container_outer_width), |val, basis| tree.calc(val, basis));
     let resolved_content_box_inset = resolved_padding + resolved_border + scrollbar_gutter;
     let (inflow_content_size, intrinsic_outer_height, first_child_top_margin_set, last_child_bottom_margin_set) =
         perform_final_layout_on_in_flow_children(
@@ -279,13 +282,14 @@ fn compute_inner(tree: &mut impl LayoutBlockContainer, node_id: NodeId, inputs:
         top_margin: if own_margins_collapse_with_children.start {
             first_child_top_margin_set
         } else {
-            let margin_top = raw_margin.top.resolve_or_zero(parent_size.width);
+            let margin_top = raw_margin.top.resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
             CollapsibleMarginSet::from_margin(margin_top)
         },
         bottom_margin: if own_margins_collapse_with_children.end {
             last_child_bottom_margin_set
         } else {
-            let margin_bottom = raw_margin.bottom.resolve_or_zero(parent_size.width);
+            let margin_bottom =
+                raw_margin.bottom.resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
             CollapsibleMarginSet::from_margin(margin_bottom)
         },
         margins_can_collapse_through: can_be_collapsed_through,
@@ -305,8 +309,8 @@ fn generate_item_list(
         .enumerate()
         .map(|(order, (child_node_id, child_style))| {
             let aspect_ratio = child_style.aspect_ratio();
-            let padding = child_style.padding().resolve_or_zero(node_inner_size);
-            let border = child_style.border().resolve_or_zero(node_inner_size);
+            let padding = child_style.padding().resolve_or_zero(node_inner_size, |val, basis| tree.calc(val, basis));
+            let border = child_style.border().resolve_or_zero(node_inner_size, |val, basis| tree.calc(val, basis));
             let pb_sum = (padding + border).sum_axes();
             let box_sizing_adjustment =
                 if child_style.box_sizing() == BoxSizing::ContentBox { pb_sum } else { Size::ZERO };
@@ -316,17 +320,17 @@ fn generate_item_list(
                 is_table: child_style.is_table(),
                 size: child_style
                     .size()
-                    .maybe_resolve(node_inner_size)
+                    .maybe_resolve(node_inner_size, |val, basis| tree.calc(val, basis))
                     .maybe_apply_aspect_ratio(aspect_ratio)
                     .maybe_add(box_sizing_adjustment),
                 min_size: child_style
                     .min_size()
-                    .maybe_resolve(node_inner_size)
+                    .maybe_resolve(node_inner_size, |val, basis| tree.calc(val, basis))
                     .maybe_apply_aspect_ratio(aspect_ratio)
                     .maybe_add(box_sizing_adjustment),
                 max_size: child_style
                     .max_size()
-                    .maybe_resolve(node_inner_size)
+                    .maybe_resolve(node_inner_size, |val, basis| tree.calc(val, basis))
                     .maybe_apply_aspect_ratio(aspect_ratio)
                     .maybe_add(box_sizing_adjustment),
                 overflow: child_style.overflow(),
@@ -361,8 +365,10 @@ fn determine_content_based_container_width(
         let known_dimensions = item.size.maybe_clamp(item.min_size, item.max_size);
 
         let width = known_dimensions.width.unwrap_or_else(|| {
-            let item_x_margin_sum =
-                item.margin.resolve_or_zero(available_space.width.into_option()).horizontal_axis_sum();
+            let item_x_margin_sum = item
+                .margin
+                .resolve_or_zero(available_space.width.into_option(), |val, basis| tree.calc(val, basis))
+                .horizontal_axis_sum();
             let size_and_baselines = tree.perform_child_layout(
                 item.node_id,
                 known_dimensions,
@@ -410,7 +416,9 @@ fn perform_final_layout_on_in_flow_children(
         if item.position == Position::Absolute {
             item.static_position = Point { x: resolved_content_box_inset.left, y: y_offset_for_absolute }
         } else {
-            let item_margin = item.margin.map(|margin| margin.resolve_to_option(container_outer_width));
+            let item_margin = item
+                .margin
+                .map(|margin| margin.resolve_to_option(container_outer_width, |val, basis| tree.calc(val, basis)));
             let item_non_auto_margin = item_margin.map(|m| m.unwrap_or(0.0));
             let item_non_auto_x_margin_sum = item_non_auto_margin.horizontal_axis_sum();
             let known_dimensions = if item.is_table {
@@ -462,8 +470,9 @@ fn perform_final_layout_on_in_flow_children(
             };
 
             // Resolve item inset
-            let inset =
-                item.inset.zip_size(Size { width: container_inner_width, height: 0.0 }, |p, s| p.maybe_resolve(s));
+            let inset = item.inset.zip_size(Size { width: container_inner_width, height: 0.0 }, |p, s| {
+                p.maybe_resolve(s, |val, basis| tree.calc(val, basis))
+            });
             let inset_offset = Point {
                 x: inset.left.or(inset.right.map(|x| -x)).unwrap_or(0.0),
                 y: inset.top.or(inset.bottom.map(|x| -x)).unwrap_or(0.0),
@@ -590,35 +599,36 @@ fn perform_absolute_layout_on_absolute_children(
         }
 
         let aspect_ratio = child_style.aspect_ratio();
-        let margin = child_style.margin().map(|margin| margin.resolve_to_option(area_width));
-        let padding = child_style.padding().resolve_or_zero(Some(area_width));
-        let border = child_style.border().resolve_or_zero(Some(area_width));
+        let margin =
+            child_style.margin().map(|margin| margin.resolve_to_option(area_width, |val, basis| tree.calc(val, basis)));
+        let padding = child_style.padding().resolve_or_zero(Some(area_width), |val, basis| tree.calc(val, basis));
+        let border = child_style.border().resolve_or_zero(Some(area_width), |val, basis| tree.calc(val, basis));
         let padding_border_sum = (padding + border).sum_axes();
         let box_sizing_adjustment =
             if child_style.box_sizing() == BoxSizing::ContentBox { padding_border_sum } else { Size::ZERO };
 
         // Resolve inset
-        let left = child_style.inset().left.maybe_resolve(area_width);
-        let right = child_style.inset().right.maybe_resolve(area_width);
-        let top = child_style.inset().top.maybe_resolve(area_height);
-        let bottom = child_style.inset().bottom.maybe_resolve(area_height);
+        let left = child_style.inset().left.maybe_resolve(area_width, |val, basis| tree.calc(val, basis));
+        let right = child_style.inset().right.maybe_resolve(area_width, |val, basis| tree.calc(val, basis));
+        let top = child_style.inset().top.maybe_resolve(area_height, |val, basis| tree.calc(val, basis));
+        let bottom = child_style.inset().bottom.maybe_resolve(area_height, |val, basis| tree.calc(val, basis));
 
         // Compute known dimensions from min/max/inherent size styles
         let style_size = child_style
             .size()
-            .maybe_resolve(area_size)
+            .maybe_resolve(area_size, |val, basis| tree.calc(val, basis))
             .maybe_apply_aspect_ratio(aspect_ratio)
             .maybe_add(box_sizing_adjustment);
         let min_size = child_style
             .min_size()
-            .maybe_resolve(area_size)
+            .maybe_resolve(area_size, |val, basis| tree.calc(val, basis))
             .maybe_apply_aspect_ratio(aspect_ratio)
             .maybe_add(box_sizing_adjustment)
             .or(padding_border_sum.map(Some))
             .maybe_max(padding_border_sum);
         let max_size = child_style
             .max_size()
-            .maybe_resolve(area_size)
+            .maybe_resolve(area_size, |val, basis| tree.calc(val, basis))
             .maybe_apply_aspect_ratio(aspect_ratio)
             .maybe_add(box_sizing_adjustment);
         let mut known_dimensions = style_size.maybe_clamp(min_size, max_size);
diff --git a/src/compute/flexbox.rs b/src/compute/flexbox.rs
index e34d193ba..90d526440 100644
--- a/src/compute/flexbox.rs
+++ b/src/compute/flexbox.rs
@@ -171,26 +171,26 @@ pub fn compute_flexbox_layout(
 
     // Pull these out earlier to avoid borrowing issues
     let aspect_ratio = style.aspect_ratio();
-    let padding = style.padding().resolve_or_zero(parent_size.width);
-    let border = style.border().resolve_or_zero(parent_size.width);
+    let padding = style.padding().resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
+    let border = style.border().resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
     let padding_border_sum = padding.sum_axes() + border.sum_axes();
     let box_sizing_adjustment =
         if style.box_sizing() == BoxSizing::ContentBox { padding_border_sum } else { Size::ZERO };
 
     let min_size = style
         .min_size()
-        .maybe_resolve(parent_size)
+        .maybe_resolve(parent_size, |val, basis| tree.calc(val, basis))
         .maybe_apply_aspect_ratio(aspect_ratio)
         .maybe_add(box_sizing_adjustment);
     let max_size = style
         .max_size()
-        .maybe_resolve(parent_size)
+        .maybe_resolve(parent_size, |val, basis| tree.calc(val, basis))
         .maybe_apply_aspect_ratio(aspect_ratio)
         .maybe_add(box_sizing_adjustment);
     let clamped_style_size = if inputs.sizing_mode == SizingMode::InherentSize {
         style
             .size()
-            .maybe_resolve(parent_size)
+            .maybe_resolve(parent_size, |val, basis| tree.calc(val, basis))
             .maybe_apply_aspect_ratio(aspect_ratio)
             .maybe_add(box_sizing_adjustment)
             .maybe_clamp(min_size, max_size)
@@ -227,7 +227,7 @@ fn compute_preliminary(tree: &mut impl LayoutFlexboxContainer, node: NodeId, inp
     let LayoutInput { known_dimensions, parent_size, available_space, run_mode, .. } = inputs;
 
     // Define some general constants we will need for the remainder of the algorithm.
-    let mut constants = compute_constants(tree.get_flexbox_container_style(node), known_dimensions, parent_size);
+    let mut constants = compute_constants(tree, tree.get_flexbox_container_style(node), known_dimensions, parent_size);
 
     // 9. Flex Layout Algorithm
 
@@ -284,7 +284,11 @@ fn compute_preliminary(tree: &mut impl LayoutFlexboxContainer, node: NodeId, inp
         // Re-resolve percentage gaps
         let style = tree.get_flexbox_container_style(node);
         let inner_container_size = constants.inner_container_size.main(constants.dir);
-        let new_gap = style.gap().main(constants.dir).maybe_resolve(inner_container_size).unwrap_or(0.0);
+        let new_gap = style
+            .gap()
+            .main(constants.dir)
+            .maybe_resolve(inner_container_size, |val, basis| tree.calc(val, basis))
+            .unwrap_or(0.0);
         constants.gap.set_main(constants.dir, new_gap);
     }
 
@@ -411,6 +415,7 @@ fn compute_preliminary(tree: &mut impl LayoutFlexboxContainer, node: NodeId, inp
 /// Compute constants that can be reused during the flexbox algorithm.
 #[inline]
 fn compute_constants(
+    tree: &impl LayoutFlexboxContainer,
     style: impl FlexboxContainerStyle,
     known_dimensions: Size<Option<f32>>,
     parent_size: Size<Option<f32>>,
@@ -422,9 +427,9 @@ fn compute_constants(
     let is_wrap_reverse = style.flex_wrap() == FlexWrap::WrapReverse;
 
     let aspect_ratio = style.aspect_ratio();
-    let margin = style.margin().resolve_or_zero(parent_size.width);
-    let padding = style.padding().resolve_or_zero(parent_size.width);
-    let border = style.border().resolve_or_zero(parent_size.width);
+    let margin = style.margin().resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
+    let padding = style.padding().resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
+    let border = style.border().resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
     let padding_border_sum = padding.sum_axes() + border.sum_axes();
     let box_sizing_adjustment =
         if style.box_sizing() == BoxSizing::ContentBox { padding_border_sum } else { Size::ZERO };
@@ -447,7 +452,7 @@ fn compute_constants(
 
     let node_outer_size = known_dimensions;
     let node_inner_size = node_outer_size.maybe_sub(content_box_inset.sum_axes());
-    let gap = style.gap().resolve_or_zero(node_inner_size.or(Size::zero()));
+    let gap = style.gap().resolve_or_zero(node_inner_size.or(Size::zero()), |val, basis| tree.calc(val, basis));
 
     let container_size = Size::zero();
     let inner_container_size = Size::zero();
@@ -460,12 +465,12 @@ fn compute_constants(
         is_wrap_reverse,
         min_size: style
             .min_size()
-            .maybe_resolve(parent_size)
+            .maybe_resolve(parent_size, |val, basis| tree.calc(val, basis))
             .maybe_apply_aspect_ratio(aspect_ratio)
             .maybe_add(box_sizing_adjustment),
         max_size: style
             .max_size()
-            .maybe_resolve(parent_size)
+            .maybe_resolve(parent_size, |val, basis| tree.calc(val, basis))
             .maybe_apply_aspect_ratio(aspect_ratio)
             .maybe_add(box_sizing_adjustment),
         margin,
@@ -501,8 +506,12 @@ fn generate_anonymous_flex_items(
         .filter(|(_, _, style)| style.box_generation_mode() != BoxGenerationMode::None)
         .map(|(index, child, child_style)| {
             let aspect_ratio = child_style.aspect_ratio();
-            let padding = child_style.padding().resolve_or_zero(constants.node_inner_size.width);
-            let border = child_style.border().resolve_or_zero(constants.node_inner_size.width);
+            let padding = child_style
+                .padding()
+                .resolve_or_zero(constants.node_inner_size.width, |val, basis| tree.calc(val, basis));
+            let border = child_style
+                .border()
+                .resolve_or_zero(constants.node_inner_size.width, |val, basis| tree.calc(val, basis));
             let pb_sum = (padding + border).sum_axes();
             let box_sizing_adjustment =
                 if child_style.box_sizing() == BoxSizing::ContentBox { pb_sum } else { Size::ZERO };
@@ -511,25 +520,33 @@ fn generate_anonymous_flex_items(
                 order: index as u32,
                 size: child_style
                     .size()
-                    .maybe_resolve(constants.node_inner_size)
+                    .maybe_resolve(constants.node_inner_size, |val, basis| tree.calc(val, basis))
                     .maybe_apply_aspect_ratio(aspect_ratio)
                     .maybe_add(box_sizing_adjustment),
                 min_size: child_style
                     .min_size()
-                    .maybe_resolve(constants.node_inner_size)
+                    .maybe_resolve(constants.node_inner_size, |val, basis| tree.calc(val, basis))
                     .maybe_apply_aspect_ratio(aspect_ratio)
                     .maybe_add(box_sizing_adjustment),
                 max_size: child_style
                     .max_size()
-                    .maybe_resolve(constants.node_inner_size)
+                    .maybe_resolve(constants.node_inner_size, |val, basis| tree.calc(val, basis))
                     .maybe_apply_aspect_ratio(aspect_ratio)
                     .maybe_add(box_sizing_adjustment),
 
-                inset: child_style.inset().zip_size(constants.node_inner_size, |p, s| p.maybe_resolve(s)),
-                margin: child_style.margin().resolve_or_zero(constants.node_inner_size.width),
+                inset: child_style
+                    .inset()
+                    .zip_size(constants.node_inner_size, |p, s| p.maybe_resolve(s, |val, basis| tree.calc(val, basis))),
+                margin: child_style
+                    .margin()
+                    .resolve_or_zero(constants.node_inner_size.width, |val, basis| tree.calc(val, basis)),
                 margin_is_auto: child_style.margin().map(LengthPercentageAuto::is_auto),
-                padding: child_style.padding().resolve_or_zero(constants.node_inner_size.width),
-                border: child_style.border().resolve_or_zero(constants.node_inner_size.width),
+                padding: child_style
+                    .padding()
+                    .resolve_or_zero(constants.node_inner_size.width, |val, basis| tree.calc(val, basis)),
+                border: child_style
+                    .border()
+                    .resolve_or_zero(constants.node_inner_size.width, |val, basis| tree.calc(val, basis)),
                 align_self: child_style.align_self().unwrap_or(constants.align_items),
                 overflow: child_style.overflow(),
                 scrollbar_width: child_style.scrollbar_width(),
@@ -670,14 +687,17 @@ fn determine_flex_base_size(
 
         let container_width = constants.node_inner_size.main(dir);
         let box_sizing_adjustment = if child_style.box_sizing() == BoxSizing::ContentBox {
-            let padding = child_style.padding().resolve_or_zero(container_width);
-            let border = child_style.border().resolve_or_zero(container_width);
+            let padding = child_style.padding().resolve_or_zero(container_width, |val, basis| tree.calc(val, basis));
+            let border = child_style.border().resolve_or_zero(container_width, |val, basis| tree.calc(val, basis));
             (padding + border).sum_axes()
         } else {
             Size::ZERO
         }
         .main(dir);
-        let flex_basis = child_style.flex_basis().maybe_resolve(container_width).maybe_add(box_sizing_adjustment);
+        let flex_basis = child_style
+            .flex_basis()
+            .maybe_resolve(container_width, |val, basis| tree.calc(val, basis))
+            .maybe_add(box_sizing_adjustment);
 
         drop(child_style);
 
@@ -1583,15 +1603,19 @@ fn determine_used_cross_size(
                     // For some reason this particular usage of max_width is an exception to the rule that max_width's transfer
                     // using the aspect_ratio (if set). Both Chrome and Firefox agree on this. And reading the spec, it seems like
                     // a reasonable interpretation. Although it seems to me that the spec *should* apply aspect_ratio here.
-                    let padding = child_style.padding().resolve_or_zero(constants.node_inner_size);
-                    let border = child_style.border().resolve_or_zero(constants.node_inner_size);
+                    let padding = child_style
+                        .padding()
+                        .resolve_or_zero(constants.node_inner_size, |val, basis| tree.calc(val, basis));
+                    let border = child_style
+                        .border()
+                        .resolve_or_zero(constants.node_inner_size, |val, basis| tree.calc(val, basis));
                     let pb_sum = (padding + border).sum_axes();
                     let box_sizing_adjustment =
                         if child_style.box_sizing() == BoxSizing::ContentBox { pb_sum } else { Size::ZERO };
 
                     let max_size_ignoring_aspect_ratio = child_style
                         .max_size()
-                        .maybe_resolve(constants.node_inner_size)
+                        .maybe_resolve(constants.node_inner_size, |val, basis| tree.calc(val, basis))
                         .maybe_add(box_sizing_adjustment);
 
                     (line_cross_size - child.margin.cross_axis_sum(constants.dir)).maybe_clamp(
@@ -2056,36 +2080,43 @@ fn perform_absolute_layout_on_absolute_children(
         let scrollbar_width = child_style.scrollbar_width();
         let aspect_ratio = child_style.aspect_ratio();
         let align_self = child_style.align_self().unwrap_or(constants.align_items);
-        let margin = child_style.margin().map(|margin| margin.resolve_to_option(inset_relative_size.width));
-        let padding = child_style.padding().resolve_or_zero(Some(inset_relative_size.width));
-        let border = child_style.border().resolve_or_zero(Some(inset_relative_size.width));
+        let margin = child_style
+            .margin()
+            .map(|margin| margin.resolve_to_option(inset_relative_size.width, |val, basis| tree.calc(val, basis)));
+        let padding =
+            child_style.padding().resolve_or_zero(Some(inset_relative_size.width), |val, basis| tree.calc(val, basis));
+        let border =
+            child_style.border().resolve_or_zero(Some(inset_relative_size.width), |val, basis| tree.calc(val, basis));
         let padding_border_sum = (padding + border).sum_axes();
         let box_sizing_adjustment =
             if child_style.box_sizing() == BoxSizing::ContentBox { padding_border_sum } else { Size::ZERO };
 
         // Resolve inset
         // Insets are resolved against the container size minus border
-        let left = child_style.inset().left.maybe_resolve(inset_relative_size.width);
-        let right = child_style.inset().right.maybe_resolve(inset_relative_size.width);
-        let top = child_style.inset().top.maybe_resolve(inset_relative_size.height);
-        let bottom = child_style.inset().bottom.maybe_resolve(inset_relative_size.height);
+        let left =
+            child_style.inset().left.maybe_resolve(inset_relative_size.width, |val, basis| tree.calc(val, basis));
+        let right =
+            child_style.inset().right.maybe_resolve(inset_relative_size.width, |val, basis| tree.calc(val, basis));
+        let top = child_style.inset().top.maybe_resolve(inset_relative_size.height, |val, basis| tree.calc(val, basis));
+        let bottom =
+            child_style.inset().bottom.maybe_resolve(inset_relative_size.height, |val, basis| tree.calc(val, basis));
 
         // Compute known dimensions from min/max/inherent size styles
         let style_size = child_style
             .size()
-            .maybe_resolve(inset_relative_size)
+            .maybe_resolve(inset_relative_size, |val, basis| tree.calc(val, basis))
             .maybe_apply_aspect_ratio(aspect_ratio)
             .maybe_add(box_sizing_adjustment);
         let min_size = child_style
             .min_size()
-            .maybe_resolve(inset_relative_size)
+            .maybe_resolve(inset_relative_size, |val, basis| tree.calc(val, basis))
             .maybe_apply_aspect_ratio(aspect_ratio)
             .maybe_add(box_sizing_adjustment)
             .or(padding_border_sum.map(Some))
             .maybe_max(padding_border_sum);
         let max_size = child_style
             .max_size()
-            .maybe_resolve(inset_relative_size)
+            .maybe_resolve(inset_relative_size, |val, basis| tree.calc(val, basis))
             .maybe_apply_aspect_ratio(aspect_ratio)
             .maybe_add(box_sizing_adjustment);
         let mut known_dimensions = style_size.maybe_clamp(min_size, max_size);
diff --git a/src/compute/grid/alignment.rs b/src/compute/grid/alignment.rs
index aeeed4424..68303f9bc 100644
--- a/src/compute/grid/alignment.rs
+++ b/src/compute/grid/alignment.rs
@@ -75,11 +75,18 @@ pub(super) fn align_and_position_item(
     let align_self = style.align_self();
 
     let position = style.position();
-    let inset_horizontal =
-        style.inset().horizontal_components().map(|size| size.resolve_to_option(grid_area_size.width));
-    let inset_vertical = style.inset().vertical_components().map(|size| size.resolve_to_option(grid_area_size.height));
-    let padding = style.padding().map(|p| p.resolve_or_zero(Some(grid_area_size.width)));
-    let border = style.border().map(|p| p.resolve_or_zero(Some(grid_area_size.width)));
+    let inset_horizontal = style
+        .inset()
+        .horizontal_components()
+        .map(|size| size.resolve_to_option(grid_area_size.width, |val, basis| tree.calc(val, basis)));
+    let inset_vertical = style
+        .inset()
+        .vertical_components()
+        .map(|size| size.resolve_to_option(grid_area_size.height, |val, basis| tree.calc(val, basis)));
+    let padding =
+        style.padding().map(|p| p.resolve_or_zero(Some(grid_area_size.width), |val, basis| tree.calc(val, basis)));
+    let border =
+        style.border().map(|p| p.resolve_or_zero(Some(grid_area_size.width), |val, basis| tree.calc(val, basis)));
     let padding_border_size = (padding + border).sum_axes();
 
     let box_sizing_adjustment =
@@ -87,19 +94,19 @@ pub(super) fn align_and_position_item(
 
     let inherent_size = style
         .size()
-        .maybe_resolve(grid_area_size)
+        .maybe_resolve(grid_area_size, |val, basis| tree.calc(val, basis))
         .maybe_apply_aspect_ratio(aspect_ratio)
         .maybe_add(box_sizing_adjustment);
     let min_size = style
         .min_size()
-        .maybe_resolve(grid_area_size)
+        .maybe_resolve(grid_area_size, |val, basis| tree.calc(val, basis))
         .maybe_add(box_sizing_adjustment)
         .or(padding_border_size.map(Some))
         .maybe_max(padding_border_size)
         .maybe_apply_aspect_ratio(aspect_ratio);
     let max_size = style
         .max_size()
-        .maybe_resolve(grid_area_size)
+        .maybe_resolve(grid_area_size, |val, basis| tree.calc(val, basis))
         .maybe_apply_aspect_ratio(aspect_ratio)
         .maybe_add(box_sizing_adjustment);
 
@@ -126,7 +133,8 @@ pub(super) fn align_and_position_item(
 
     // Note: This is not a bug. It is part of the CSS spec that both horizontal and vertical margins
     // resolve against the WIDTH of the grid area.
-    let margin = style.margin().map(|margin| margin.resolve_to_option(grid_area_size.width));
+    let margin =
+        style.margin().map(|margin| margin.resolve_to_option(grid_area_size.width, |val, basis| tree.calc(val, basis)));
 
     let grid_area_minus_item_margins_size = Size {
         width: grid_area_size.width.maybe_sub(margin.left).maybe_sub(margin.right),
diff --git a/src/compute/grid/explicit_grid.rs b/src/compute/grid/explicit_grid.rs
index 6faca1af9..fbb5168bd 100644
--- a/src/compute/grid/explicit_grid.rs
+++ b/src/compute/grid/explicit_grid.rs
@@ -14,6 +14,7 @@ pub(crate) fn compute_explicit_grid_size_in_axis(
     style: &impl GridContainerStyle,
     template: &[TrackSizingFunction],
     inner_container_size: Size<Option<f32>>,
+    resolve_calc_value: impl Fn(u64, f32) -> f32,
     axis: AbsoluteAxis,
 ) -> u16 {
     // If template contains no tracks, then there are trivially zero explicit tracks
@@ -88,9 +89,10 @@ pub(crate) fn compute_explicit_grid_size_in_axis(
     // Otherwise, if the grid container has a definite min size in the relevant axis:
     //   - then the number of repetitions is the smallest possible positive integer that fulfills that minimum requirement
     // Otherwise, the specified track list repeats only once.
-    let style_size_is_definite = style.size().get_abs(axis).maybe_resolve(inner_container_size.get_abs(axis)).is_some();
+    let style_size_is_definite =
+        style.size().get_abs(axis).maybe_resolve(inner_container_size.get_abs(axis), &resolve_calc_value).is_some();
     let style_max_size_is_definite =
-        style.max_size().get_abs(axis).maybe_resolve(inner_container_size.get_abs(axis)).is_some();
+        style.max_size().get_abs(axis).maybe_resolve(inner_container_size.get_abs(axis), &resolve_calc_value).is_some();
     let size_is_maximum = style_size_is_definite | style_max_size_is_definite;
 
     // Determine the number of repetitions
@@ -101,9 +103,13 @@ pub(crate) fn compute_explicit_grid_size_in_axis(
 
             /// ...treating each track as its max track sizing function if that is definite or as its minimum track sizing function
             /// otherwise, flooring the max track sizing function by the min track sizing function if both are definite
-            fn track_definite_value(sizing_function: &NonRepeatedTrackSizingFunction, parent_size: Option<f32>) -> f32 {
-                let max_size = sizing_function.max.definite_value(parent_size);
-                let min_size = sizing_function.min.definite_value(parent_size);
+            fn track_definite_value(
+                sizing_function: &NonRepeatedTrackSizingFunction,
+                parent_size: Option<f32>,
+                calc_resolver: impl Fn(u64, f32) -> f32,
+            ) -> f32 {
+                let max_size = sizing_function.max.definite_value(parent_size, &calc_resolver);
+                let min_size = sizing_function.min.definite_value(parent_size, &calc_resolver);
                 max_size.map(|max| max.maybe_min(min_size)).or(min_size).unwrap()
             }
 
@@ -113,12 +119,14 @@ pub(crate) fn compute_explicit_grid_size_in_axis(
                     use GridTrackRepetition::{AutoFill, AutoFit, Count};
                     match track_def {
                         TrackSizingFunction::Single(sizing_function) => {
-                            track_definite_value(sizing_function, parent_size)
+                            track_definite_value(sizing_function, parent_size, &resolve_calc_value)
                         }
                         TrackSizingFunction::Repeat(Count(count), repeated_tracks) => {
                             let sum = repeated_tracks
                                 .iter()
-                                .map(|sizing_function| track_definite_value(sizing_function, parent_size))
+                                .map(|sizing_function| {
+                                    track_definite_value(sizing_function, parent_size, &resolve_calc_value)
+                                })
                                 .sum::<f32>();
                             sum * (*count as f32)
                         }
@@ -126,12 +134,12 @@ pub(crate) fn compute_explicit_grid_size_in_axis(
                     }
                 })
                 .sum();
-            let gap_size = style.gap().get_abs(axis).resolve_or_zero(Some(inner_container_size));
+            let gap_size = style.gap().get_abs(axis).resolve_or_zero(Some(inner_container_size), &resolve_calc_value);
 
             // Compute the amount of space that a single repetition of the repeated track list takes
             let per_repetition_track_used_space: f32 = repetition_definition
                 .iter()
-                .map(|sizing_function| track_definite_value(sizing_function, parent_size))
+                .map(|sizing_function| track_definite_value(sizing_function, parent_size, &resolve_calc_value))
                 .sum::<f32>();
 
             // We special case the first repetition here because the number of gaps in the first repetition
@@ -295,12 +303,14 @@ mod test {
             &grid_style,
             &grid_style.grid_template_columns,
             preferred_size,
+            |_, _| 42.42,
             AbsoluteAxis::Horizontal,
         );
         let height = compute_explicit_grid_size_in_axis(
             &grid_style,
             &grid_style.grid_template_rows,
             preferred_size,
+            |_, _| 42.42,
             AbsoluteAxis::Vertical,
         );
         assert_eq!(width, 2);
@@ -322,12 +332,14 @@ mod test {
             &grid_style,
             &grid_style.grid_template_columns,
             preferred_size,
+            |_, _| 42.42,
             AbsoluteAxis::Horizontal,
         );
         let height = compute_explicit_grid_size_in_axis(
             &grid_style,
             &grid_style.grid_template_rows,
             preferred_size,
+            |_, _| 42.42,
             AbsoluteAxis::Vertical,
         );
         assert_eq!(width, 3);
@@ -349,12 +361,14 @@ mod test {
             &grid_style,
             &grid_style.grid_template_columns,
             preferred_size,
+            |_, _| 42.42,
             AbsoluteAxis::Horizontal,
         );
         let height = compute_explicit_grid_size_in_axis(
             &grid_style,
             &grid_style.grid_template_rows,
             preferred_size,
+            |_, _| 42.42,
             AbsoluteAxis::Vertical,
         );
         assert_eq!(width, 3);
@@ -376,12 +390,14 @@ mod test {
             &grid_style,
             &grid_style.grid_template_columns,
             inner_container_size,
+            |_, _| 42.42,
             AbsoluteAxis::Horizontal,
         );
         let height = compute_explicit_grid_size_in_axis(
             &grid_style,
             &grid_style.grid_template_rows,
             inner_container_size,
+            |_, _| 42.42,
             AbsoluteAxis::Vertical,
         );
         assert_eq!(width, 3);
@@ -403,12 +419,14 @@ mod test {
             &grid_style,
             &grid_style.grid_template_columns,
             inner_container_size,
+            |_, _| 42.42,
             AbsoluteAxis::Horizontal,
         );
         let height = compute_explicit_grid_size_in_axis(
             &grid_style,
             &grid_style.grid_template_rows,
             inner_container_size,
+            |_, _| 42.42,
             AbsoluteAxis::Vertical,
         );
         assert_eq!(width, 4);
@@ -430,12 +448,14 @@ mod test {
             &grid_style,
             &grid_style.grid_template_columns,
             preferred_size,
+            |_, _| 42.42,
             AbsoluteAxis::Horizontal,
         );
         let height = compute_explicit_grid_size_in_axis(
             &grid_style,
             &grid_style.grid_template_rows,
             preferred_size,
+            |_, _| 42.42,
             AbsoluteAxis::Vertical,
         );
         assert_eq!(width, 4); // 2 repetitions * 2 repeated tracks = 4 tracks in total
@@ -458,12 +478,14 @@ mod test {
             &grid_style,
             &grid_style.grid_template_columns,
             preferred_size,
+            |_, _| 42.42,
             AbsoluteAxis::Horizontal,
         );
         let height = compute_explicit_grid_size_in_axis(
             &grid_style,
             &grid_style.grid_template_rows,
             preferred_size,
+            |_, _| 42.42,
             AbsoluteAxis::Vertical,
         );
         assert_eq!(width, 2); // 2 tracks + 1 gap
@@ -485,12 +507,14 @@ mod test {
             &grid_style,
             &grid_style.grid_template_columns,
             preferred_size,
+            |_, _| 42.42,
             AbsoluteAxis::Horizontal,
         );
         let height = compute_explicit_grid_size_in_axis(
             &grid_style,
             &grid_style.grid_template_rows,
             preferred_size,
+            |_, _| 42.42,
             AbsoluteAxis::Vertical,
         );
         assert_eq!(width, 3);
@@ -513,12 +537,14 @@ mod test {
             &grid_style,
             &grid_style.grid_template_columns,
             preferred_size,
+            |_, _| 42.42,
             AbsoluteAxis::Horizontal,
         );
         let height = compute_explicit_grid_size_in_axis(
             &grid_style,
             &grid_style.grid_template_rows,
             preferred_size,
+            |_, _| 42.42,
             AbsoluteAxis::Vertical,
         );
         assert_eq!(width, 3); // 3 tracks + 2 gaps
@@ -541,12 +567,14 @@ mod test {
             &grid_style,
             &grid_style.grid_template_columns,
             inner_container_size,
+            |_, _| 42.42,
             AbsoluteAxis::Horizontal,
         );
         let height = compute_explicit_grid_size_in_axis(
             &grid_style,
             &grid_style.grid_template_rows,
             inner_container_size,
+            |_, _| 42.42,
             AbsoluteAxis::Vertical,
         );
         assert_eq!(width, 5); // 40px horizontal padding
diff --git a/src/compute/grid/mod.rs b/src/compute/grid/mod.rs
index d8949dfe4..32338efed 100644
--- a/src/compute/grid/mod.rs
+++ b/src/compute/grid/mod.rs
@@ -42,7 +42,11 @@ mod util;
 ///   - Placing items (which also resolves the implicit grid)
 ///   - Track (row/column) sizing
 ///   - Alignment & Final item placement
-pub fn compute_grid_layout(tree: &mut impl LayoutGridContainer, node: NodeId, inputs: LayoutInput) -> LayoutOutput {
+pub fn compute_grid_layout<Tree: LayoutGridContainer>(
+    tree: &mut Tree,
+    node: NodeId,
+    inputs: LayoutInput,
+) -> LayoutOutput {
     let LayoutInput { known_dimensions, parent_size, available_space, run_mode, .. } = inputs;
 
     let style = tree.get_grid_container_style(node);
@@ -50,8 +54,8 @@ pub fn compute_grid_layout(tree: &mut impl LayoutGridContainer, node: NodeId, in
     // 1. Compute "available grid space"
     // https://www.w3.org/TR/css-grid-1/#available-grid-space
     let aspect_ratio = style.aspect_ratio();
-    let padding = style.padding().resolve_or_zero(parent_size.width);
-    let border = style.border().resolve_or_zero(parent_size.width);
+    let padding = style.padding().resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
+    let border = style.border().resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
     let padding_border = padding + border;
     let padding_border_size = padding_border.sum_axes();
     let box_sizing_adjustment =
@@ -59,18 +63,18 @@ pub fn compute_grid_layout(tree: &mut impl LayoutGridContainer, node: NodeId, in
 
     let min_size = style
         .min_size()
-        .maybe_resolve(parent_size)
+        .maybe_resolve(parent_size, |val, basis| tree.calc(val, basis))
         .maybe_apply_aspect_ratio(aspect_ratio)
         .maybe_add(box_sizing_adjustment);
     let max_size = style
         .max_size()
-        .maybe_resolve(parent_size)
+        .maybe_resolve(parent_size, |val, basis| tree.calc(val, basis))
         .maybe_apply_aspect_ratio(aspect_ratio)
         .maybe_add(box_sizing_adjustment);
     let preferred_size = if inputs.sizing_mode == SizingMode::InherentSize {
         style
             .size()
-            .maybe_resolve(parent_size)
+            .maybe_resolve(parent_size, |val, basis| tree.calc(val, basis))
             .maybe_apply_aspect_ratio(style.aspect_ratio())
             .maybe_add(box_sizing_adjustment)
     } else {
@@ -153,12 +157,14 @@ pub fn compute_grid_layout(tree: &mut impl LayoutGridContainer, node: NodeId, in
         &style,
         grid_template_columms.borrow(),
         auto_fit_container_size,
+        |val, basis| tree.calc(val, basis),
         AbsoluteAxis::Horizontal,
     );
     let explicit_row_count = compute_explicit_grid_size_in_axis(
         &style,
         grid_template_rows.borrow(),
         auto_fit_container_size,
+        |val, basis| tree.calc(val, basis),
         AbsoluteAxis::Vertical,
     );
 
@@ -247,7 +253,9 @@ pub fn compute_grid_layout(tree: &mut impl LayoutGridContainer, node: NodeId, in
         &mut columns,
         &mut rows,
         &mut items,
-        |track: &GridTrack, parent_size: Option<f32>| track.max_track_sizing_function.definite_value(parent_size),
+        |track: &GridTrack, parent_size: Option<f32>, tree: &Tree| {
+            track.max_track_sizing_function.definite_value(parent_size, |val, basis| tree.calc(val, basis))
+        },
         has_baseline_aligned_item,
     );
     let initial_column_sum = columns.iter().map(|track| track.base_size).sum::<f32>();
@@ -267,7 +275,7 @@ pub fn compute_grid_layout(tree: &mut impl LayoutGridContainer, node: NodeId, in
         &mut rows,
         &mut columns,
         &mut items,
-        |track: &GridTrack, _| Some(track.base_size),
+        |track: &GridTrack, _, _| Some(track.base_size),
         false, // TODO: Support baseline alignment in the vertical axis
     );
     let initial_row_sum = rows.iter().map(|track| track.base_size).sum::<f32>();
@@ -307,17 +315,23 @@ pub fn compute_grid_layout(tree: &mut impl LayoutGridContainer, node: NodeId, in
     // and therefore need to be re-resolved here based on the content-sized content box of the container
     if !available_grid_space.width.is_definite() {
         for column in &mut columns {
-            let min: Option<f32> =
-                column.min_track_sizing_function.resolved_percentage_size(container_content_box.width);
-            let max: Option<f32> =
-                column.max_track_sizing_function.resolved_percentage_size(container_content_box.width);
+            let min: Option<f32> = column
+                .min_track_sizing_function
+                .resolved_percentage_size(container_content_box.width, |val, basis| tree.calc(val, basis));
+            let max: Option<f32> = column
+                .max_track_sizing_function
+                .resolved_percentage_size(container_content_box.width, |val, basis| tree.calc(val, basis));
             column.base_size = column.base_size.maybe_clamp(min, max);
         }
     }
     if !available_grid_space.height.is_definite() {
         for row in &mut rows {
-            let min: Option<f32> = row.min_track_sizing_function.resolved_percentage_size(container_content_box.height);
-            let max: Option<f32> = row.max_track_sizing_function.resolved_percentage_size(container_content_box.height);
+            let min: Option<f32> = row
+                .min_track_sizing_function
+                .resolved_percentage_size(container_content_box.height, |val, basis| tree.calc(val, basis));
+            let max: Option<f32> = row
+                .max_track_sizing_function
+                .resolved_percentage_size(container_content_box.height, |val, basis| tree.calc(val, basis));
             row.base_size = row.base_size.maybe_clamp(min, max);
         }
     }
@@ -377,7 +391,7 @@ pub fn compute_grid_layout(tree: &mut impl LayoutGridContainer, node: NodeId, in
             &mut columns,
             &mut rows,
             &mut items,
-            |track: &GridTrack, _| Some(track.base_size),
+            |track: &GridTrack, _, _| Some(track.base_size),
             has_baseline_aligned_item,
         );
 
@@ -436,7 +450,7 @@ pub fn compute_grid_layout(tree: &mut impl LayoutGridContainer, node: NodeId, in
                 &mut rows,
                 &mut columns,
                 &mut items,
-                |track: &GridTrack, _| Some(track.base_size),
+                |track: &GridTrack, _, _| Some(track.base_size),
                 false, // TODO: Support baseline alignment in the vertical axis
             );
         }
diff --git a/src/compute/grid/track_sizing.rs b/src/compute/grid/track_sizing.rs
index d0483ecec..bf95772b7 100644
--- a/src/compute/grid/track_sizing.rs
+++ b/src/compute/grid/track_sizing.rs
@@ -67,7 +67,7 @@ impl ItemBatcher {
 struct IntrisicSizeMeasurer<'tree, 'oat, Tree, EstimateFunction>
 where
     Tree: LayoutPartialTree,
-    EstimateFunction: Fn(&GridTrack, Option<f32>) -> Option<f32>,
+    EstimateFunction: Fn(&GridTrack, Option<f32>, &Tree) -> Option<f32>,
 {
     /// The layout tree
     tree: &'tree mut Tree,
@@ -85,7 +85,7 @@ where
 impl<Tree, EstimateFunction> IntrisicSizeMeasurer<'_, '_, Tree, EstimateFunction>
 where
     Tree: LayoutPartialTree,
-    EstimateFunction: Fn(&GridTrack, Option<f32>) -> Option<f32>,
+    EstimateFunction: Fn(&GridTrack, Option<f32>, &Tree) -> Option<f32>,
 {
     /// Compute the available_space to be passed to the child sizing functions
     /// These are estimates based on either the max track sizing function or the provisional base size in the opposite
@@ -97,7 +97,7 @@ where
             self.axis,
             self.other_axis_tracks,
             self.inner_node_size.get(self.axis.other()),
-            &self.get_track_size_estimate,
+            |track, basis| (self.get_track_size_estimate)(track, basis, self.tree),
         )
     }
 
@@ -105,7 +105,13 @@ where
     /// to zero if the container size is indefinite as otherwise this would introduce a cyclic dependency.
     #[inline(always)]
     fn margins_axis_sums_with_baseline_shims(&self, item: &GridItem) -> Size<f32> {
-        item.margins_axis_sums_with_baseline_shims(self.inner_node_size.width)
+        item.margins_axis_sums_with_baseline_shims(self.inner_node_size.width, self.tree)
+    }
+
+    /// Simple pass-through function to `LayoutPartialTreeExt::calc`
+    #[inline(always)]
+    fn calc(&self, val: u64, basis: f32) -> f32 {
+        self.tree.calc(val, basis)
     }
 
     /// Retrieve the item's min content contribution from the cache or compute it using the provided parameters
@@ -271,12 +277,12 @@ pub(super) fn track_sizing_algorithm<Tree: LayoutPartialTree>(
     axis_tracks: &mut [GridTrack],
     other_axis_tracks: &mut [GridTrack],
     items: &mut [GridItem],
-    get_track_size_estimate: fn(&GridTrack, Option<f32>) -> Option<f32>,
+    get_track_size_estimate: fn(&GridTrack, Option<f32>, &Tree) -> Option<f32>,
     has_baseline_aligned_item: bool,
 ) {
     // 11.4 Initialise Track sizes
     // Initialize each track’s base size and growth limit.
-    initialize_track_sizes(axis_tracks, inner_node_size.get(axis));
+    initialize_track_sizes(tree, axis_tracks, inner_node_size.get(axis));
 
     // 11.5.1 Shim item baselines
     if has_baseline_aligned_item {
@@ -296,7 +302,7 @@ pub(super) fn track_sizing_algorithm<Tree: LayoutPartialTree>(
     let gutter_alignment_adjustment = compute_alignment_gutter_adjustment(
         other_axis_alignment,
         inner_node_size.get(axis.other()),
-        get_track_size_estimate,
+        |track, basis| get_track_size_estimate(track, basis, tree),
         other_axis_tracks,
     );
     if other_axis_tracks.len() > 3 {
@@ -397,7 +403,11 @@ fn flush_planned_growth_limit_increases(tracks: &mut [GridTrack], set_infinitely
 /// 11.4 Initialise Track sizes
 /// Initialize each track’s base size and growth limit.
 #[inline(always)]
-fn initialize_track_sizes(axis_tracks: &mut [GridTrack], axis_inner_node_size: Option<f32>) {
+fn initialize_track_sizes(
+    tree: &impl LayoutPartialTree,
+    axis_tracks: &mut [GridTrack],
+    axis_inner_node_size: Option<f32>,
+) {
     for track in axis_tracks.iter_mut() {
         // For each track, if the track’s min track sizing function is:
         // - A fixed sizing function
@@ -405,7 +415,10 @@ fn initialize_track_sizes(axis_tracks: &mut [GridTrack], axis_inner_node_size: O
         //     Note: Indefinite lengths cannot occur, as they’re treated as auto.
         // - An intrinsic sizing function
         //     Use an initial base size of zero.
-        track.base_size = track.min_track_sizing_function.definite_value(axis_inner_node_size).unwrap_or(0.0);
+        track.base_size = track
+            .min_track_sizing_function
+            .definite_value(axis_inner_node_size, |val, basis| tree.calc(val, basis))
+            .unwrap_or(0.0);
 
         // For each track, if the track’s max track sizing function is:
         // - A fixed sizing function
@@ -414,8 +427,10 @@ fn initialize_track_sizes(axis_tracks: &mut [GridTrack], axis_inner_node_size: O
         //     Use an initial growth limit of infinity.
         // - A flexible sizing function
         //     Use an initial growth limit of infinity.
-        track.growth_limit =
-            track.max_track_sizing_function.definite_value(axis_inner_node_size).unwrap_or(f32::INFINITY);
+        track.growth_limit = track
+            .max_track_sizing_function
+            .definite_value(axis_inner_node_size, |val, basis| tree.calc(val, basis))
+            .unwrap_or(f32::INFINITY);
 
         // In all cases, if the growth limit is less than the base size, increase the growth limit to match the base size.
         if track.growth_limit < track.base_size {
@@ -482,7 +497,10 @@ fn resolve_item_baselines(
             let baseline = measured_size_and_baselines.first_baselines.y;
             let height = measured_size_and_baselines.size.height;
 
-            item.baseline = Some(baseline.unwrap_or(height) + item.margin.top.resolve_or_zero(inner_node_size.width));
+            item.baseline = Some(
+                baseline.unwrap_or(height)
+                    + item.margin.top.resolve_or_zero(inner_node_size.width, |val, basis| tree.calc(val, basis)),
+            );
         }
 
         // Compute the max baseline of all items in the row
@@ -498,15 +516,15 @@ fn resolve_item_baselines(
 
 /// 11.5 Resolve Intrinsic Track Sizes
 #[allow(clippy::too_many_arguments)]
-fn resolve_intrinsic_track_sizes(
-    tree: &mut impl LayoutPartialTree,
+fn resolve_intrinsic_track_sizes<Tree: LayoutPartialTree>(
+    tree: &mut Tree,
     axis: AbstractAxis,
     axis_tracks: &mut [GridTrack],
     other_axis_tracks: &[GridTrack],
     items: &mut [GridItem],
     axis_available_grid_space: AvailableSpace,
     inner_node_size: Size<Option<f32>>,
-    get_track_size_estimate: fn(&GridTrack, Option<f32>) -> Option<f32>,
+    get_track_size_estimate: impl Fn(&GridTrack, Option<f32>, &Tree) -> Option<f32>,
 ) {
     // Step 1. Shim baseline-aligned items so their intrinsic size contributions reflect their baseline alignment.
 
@@ -577,7 +595,9 @@ fn resolve_intrinsic_track_sizes(
                             {
                                 let axis_minimum_size = item_sizer.minimum_contribution(item, axis_tracks);
                                 let axis_min_content_size = item_sizer.min_content_contribution(item);
-                                let limit = track.max_track_sizing_function.definite_limit(axis_inner_node_size);
+                                let limit = track
+                                    .max_track_sizing_function
+                                    .definite_limit(axis_inner_node_size, |val, basis| item_sizer.calc(val, basis));
                                 axis_min_content_size.maybe_min(limit).max(axis_minimum_size)
                             }
                             _ => item_sizer.minimum_contribution(item, axis_tracks),
@@ -588,6 +608,14 @@ fn resolve_intrinsic_track_sizes(
                         // Do nothing as it's not an intrinsic track sizing function
                         track.base_size
                     }
+                    // Handle calc() like percentage
+                    _ if track.min_track_sizing_function.0.is_calc() => {
+                        if axis_inner_node_size.is_none() {
+                            f32_max(track.base_size, item_sizer.min_content_contribution(item))
+                        } else {
+                            track.base_size
+                        }
+                    }
                     _ => unreachable!(),
                 };
                 let track = &mut axis_tracks[track_index as usize];
@@ -645,8 +673,6 @@ fn resolve_intrinsic_track_sizes(
 
         // 1. For intrinsic minimums:
         // First increase the base size of tracks with an intrinsic min track sizing function
-        let has_intrinsic_min_track_sizing_function =
-            move |track: &GridTrack| track.min_track_sizing_function.definite_value(axis_inner_node_size).is_none();
         for item in batch.iter_mut().filter(|item| item.crosses_intrinsic_track(axis)) {
             // ...by distributing extra space as needed to accommodate these items’ minimum contributions.
             //
@@ -663,13 +689,21 @@ fn resolve_intrinsic_track_sizes(
                 {
                     let axis_minimum_size = item_sizer.minimum_contribution(item, axis_tracks);
                     let axis_min_content_size = item_sizer.min_content_contribution(item);
-                    let limit = item.spanned_track_limit(axis, axis_tracks, axis_inner_node_size);
+                    let limit = item.spanned_track_limit(axis, axis_tracks, axis_inner_node_size, &|val, basis| {
+                        item_sizer.calc(val, basis)
+                    });
                     axis_min_content_size.maybe_min(limit).max(axis_minimum_size)
                 }
                 _ => item_sizer.minimum_contribution(item, axis_tracks),
             };
             let tracks = &mut axis_tracks[item.track_range_excluding_lines(axis)];
             if space > 0.0 {
+                let has_intrinsic_min_track_sizing_function = |track: &GridTrack| {
+                    track
+                        .min_track_sizing_function
+                        .definite_value(axis_inner_node_size, |val, basis| item_sizer.calc(val, basis))
+                        .is_none()
+                };
                 if item.overflow.get(axis).is_scroll_container() {
                     let fit_content_limit =
                         move |track: &GridTrack| track.fit_content_limited_growth_limit(axis_inner_node_size);
@@ -764,7 +798,9 @@ fn resolve_intrinsic_track_sizes(
 
             for item in batch.iter_mut() {
                 let axis_max_content_size = item_sizer.max_content_contribution(item);
-                let limit = item.spanned_track_limit(axis, axis_tracks, axis_inner_node_size);
+                let limit = item.spanned_track_limit(axis, axis_tracks, axis_inner_node_size, &|val, basis| {
+                    item_sizer.calc(val, basis)
+                });
                 let space = axis_max_content_size.maybe_min(limit);
                 let tracks = &mut axis_tracks[item.track_range_excluding_lines(axis)];
                 if space > 0.0 {
@@ -841,7 +877,7 @@ fn resolve_intrinsic_track_sizes(
             // 5. For intrinsic maximums: Next increase the growth limit of tracks with an intrinsic max track sizing function by
             // distributing extra space as needed to account for these items' min-content contributions.
             let has_intrinsic_max_track_sizing_function =
-                move |track: &GridTrack| track.max_track_sizing_function.definite_value(axis_inner_node_size).is_none();
+                move |track: &GridTrack| track.max_track_sizing_function.has_definite_value(axis_inner_node_size);
             for item in batch.iter_mut() {
                 let axis_min_content_size = item_sizer.min_content_contribution(item);
                 let space = axis_min_content_size;
diff --git a/src/compute/grid/types/grid_item.rs b/src/compute/grid/types/grid_item.rs
index ef7722f0d..28f81a125 100644
--- a/src/compute/grid/types/grid_item.rs
+++ b/src/compute/grid/types/grid_item.rs
@@ -189,15 +189,18 @@ impl GridItem {
         axis: AbstractAxis,
         axis_tracks: &[GridTrack],
         axis_parent_size: Option<f32>,
+        resolve_calc_value: &dyn Fn(u64, f32) -> f32,
     ) -> Option<f32> {
         let spanned_tracks = &axis_tracks[self.track_range_excluding_lines(axis)];
-        let tracks_all_fixed = spanned_tracks
-            .iter()
-            .all(|track| track.max_track_sizing_function.definite_limit(axis_parent_size).is_some());
+        let tracks_all_fixed = spanned_tracks.iter().all(|track| {
+            track.max_track_sizing_function.definite_limit(axis_parent_size, resolve_calc_value).is_some()
+        });
         if tracks_all_fixed {
             let limit: f32 = spanned_tracks
                 .iter()
-                .map(|track| track.max_track_sizing_function.definite_limit(axis_parent_size).unwrap())
+                .map(|track| {
+                    track.max_track_sizing_function.definite_limit(axis_parent_size, resolve_calc_value).unwrap()
+                })
                 .sum();
             Some(limit)
         } else {
@@ -212,15 +215,18 @@ impl GridItem {
         axis: AbstractAxis,
         axis_tracks: &[GridTrack],
         axis_parent_size: Option<f32>,
+        resolve_calc_value: &dyn Fn(u64, f32) -> f32,
     ) -> Option<f32> {
         let spanned_tracks = &axis_tracks[self.track_range_excluding_lines(axis)];
-        let tracks_all_fixed = spanned_tracks
-            .iter()
-            .all(|track| track.max_track_sizing_function.definite_value(axis_parent_size).is_some());
+        let tracks_all_fixed = spanned_tracks.iter().all(|track| {
+            track.max_track_sizing_function.definite_value(axis_parent_size, resolve_calc_value).is_some()
+        });
         if tracks_all_fixed {
             let limit: f32 = spanned_tracks
                 .iter()
-                .map(|track| track.max_track_sizing_function.definite_value(axis_parent_size).unwrap())
+                .map(|track| {
+                    track.max_track_sizing_function.definite_value(axis_parent_size, resolve_calc_value).unwrap()
+                })
                 .sum();
             Some(limit)
         } else {
@@ -233,30 +239,31 @@ impl GridItem {
     /// allow percentage sizes further down the tree to resolve properly in some cases
     fn known_dimensions(
         &self,
+        tree: &mut impl LayoutPartialTree,
         inner_node_size: Size<Option<f32>>,
         grid_area_size: Size<Option<f32>>,
     ) -> Size<Option<f32>> {
-        let margins = self.margins_axis_sums_with_baseline_shims(inner_node_size.width);
+        let margins = self.margins_axis_sums_with_baseline_shims(inner_node_size.width, tree);
 
         let aspect_ratio = self.aspect_ratio;
-        let padding = self.padding.resolve_or_zero(grid_area_size);
-        let border = self.border.resolve_or_zero(grid_area_size);
+        let padding = self.padding.resolve_or_zero(grid_area_size, |val, basis| tree.calc(val, basis));
+        let border = self.border.resolve_or_zero(grid_area_size, |val, basis| tree.calc(val, basis));
         let padding_border_size = (padding + border).sum_axes();
         let box_sizing_adjustment =
             if self.box_sizing == BoxSizing::ContentBox { padding_border_size } else { Size::ZERO };
         let inherent_size = self
             .size
-            .maybe_resolve(grid_area_size)
+            .maybe_resolve(grid_area_size, |val, basis| tree.calc(val, basis))
             .maybe_apply_aspect_ratio(aspect_ratio)
             .maybe_add(box_sizing_adjustment);
         let min_size = self
             .min_size
-            .maybe_resolve(grid_area_size)
+            .maybe_resolve(grid_area_size, |val, basis| tree.calc(val, basis))
             .maybe_apply_aspect_ratio(aspect_ratio)
             .maybe_add(box_sizing_adjustment);
         let max_size = self
             .max_size
-            .maybe_resolve(grid_area_size)
+            .maybe_resolve(grid_area_size, |val, basis| tree.calc(val, basis))
             .maybe_apply_aspect_ratio(aspect_ratio)
             .maybe_add(box_sizing_adjustment);
 
@@ -344,12 +351,17 @@ impl GridItem {
     /// Compute the item's resolved margins for size contributions. Horizontal percentage margins always resolve
     /// to zero if the container size is indefinite as otherwise this would introduce a cyclic dependency.
     #[inline(always)]
-    pub fn margins_axis_sums_with_baseline_shims(&self, inner_node_width: Option<f32>) -> Size<f32> {
+    pub fn margins_axis_sums_with_baseline_shims(
+        &self,
+        inner_node_width: Option<f32>,
+        tree: &impl LayoutPartialTree,
+    ) -> Size<f32> {
         Rect {
-            left: self.margin.left.resolve_or_zero(Some(0.0)),
-            right: self.margin.right.resolve_or_zero(Some(0.0)),
-            top: self.margin.top.resolve_or_zero(inner_node_width) + self.baseline_shim,
-            bottom: self.margin.bottom.resolve_or_zero(inner_node_width),
+            left: self.margin.left.resolve_or_zero(Some(0.0), |val, basis| tree.calc(val, basis)),
+            right: self.margin.right.resolve_or_zero(Some(0.0), |val, basis| tree.calc(val, basis)),
+            top: self.margin.top.resolve_or_zero(inner_node_width, |val, basis| tree.calc(val, basis))
+                + self.baseline_shim,
+            bottom: self.margin.bottom.resolve_or_zero(inner_node_width, |val, basis| tree.calc(val, basis)),
         }
         .sum_axes()
     }
@@ -362,7 +374,7 @@ impl GridItem {
         available_space: Size<Option<f32>>,
         inner_node_size: Size<Option<f32>>,
     ) -> f32 {
-        let known_dimensions = self.known_dimensions(inner_node_size, available_space);
+        let known_dimensions = self.known_dimensions(tree, inner_node_size, available_space);
         tree.measure_child_size(
             self.node,
             known_dimensions,
@@ -401,7 +413,7 @@ impl GridItem {
         available_space: Size<Option<f32>>,
         inner_node_size: Size<Option<f32>>,
     ) -> f32 {
-        let known_dimensions = self.known_dimensions(inner_node_size, available_space);
+        let known_dimensions = self.known_dimensions(tree, inner_node_size, available_space);
         tree.measure_child_size(
             self.node,
             known_dimensions,
@@ -448,20 +460,20 @@ impl GridItem {
         known_dimensions: Size<Option<f32>>,
         inner_node_size: Size<Option<f32>>,
     ) -> f32 {
-        let padding = self.padding.resolve_or_zero(inner_node_size);
-        let border = self.border.resolve_or_zero(inner_node_size);
+        let padding = self.padding.resolve_or_zero(inner_node_size, |val, basis| tree.calc(val, basis));
+        let border = self.border.resolve_or_zero(inner_node_size, |val, basis| tree.calc(val, basis));
         let padding_border_size = (padding + border).sum_axes();
         let box_sizing_adjustment =
             if self.box_sizing == BoxSizing::ContentBox { padding_border_size } else { Size::ZERO };
         let size = self
             .size
-            .maybe_resolve(inner_node_size)
+            .maybe_resolve(inner_node_size, |val, basis| tree.calc(val, basis))
             .maybe_apply_aspect_ratio(self.aspect_ratio)
             .maybe_add(box_sizing_adjustment)
             .get(axis)
             .or_else(|| {
                 self.min_size
-                    .maybe_resolve(inner_node_size)
+                    .maybe_resolve(inner_node_size, |val, basis| tree.calc(val, basis))
                     .maybe_apply_aspect_ratio(self.aspect_ratio)
                     .maybe_add(box_sizing_adjustment)
                     .get(axis)
@@ -501,7 +513,9 @@ impl GridItem {
         // In all cases, the size suggestion is additionally clamped by the maximum size in the affected axis, if it’s definite.
         // Note: The argument to fit-content() does not clamp the content-based minimum size in the same way as a fixed max track
         // sizing function.
-        let limit = self.spanned_fixed_track_limit(axis, axis_tracks, inner_node_size.get(axis));
+        let limit = self.spanned_fixed_track_limit(axis, axis_tracks, inner_node_size.get(axis), &|val, basis| {
+            tree.resolve_calc_value(val, basis)
+        });
         size.maybe_min(limit)
     }
 
diff --git a/src/compute/leaf.rs b/src/compute/leaf.rs
index c05d54eb2..9adbcc9cf 100644
--- a/src/compute/leaf.rs
+++ b/src/compute/leaf.rs
@@ -15,6 +15,7 @@ use core::unreachable;
 pub fn compute_leaf_layout<MeasureFunction>(
     inputs: LayoutInput,
     style: &impl CoreStyle,
+    resolve_calc_value: impl Fn(u64, f32) -> f32,
     measure_function: MeasureFunction,
 ) -> LayoutOutput
 where
@@ -24,9 +25,9 @@ where
 
     // Note: both horizontal and vertical percentage padding/borders are resolved against the container's inline size (i.e. width).
     // This is not a bug, but is how CSS is specified (see: https://developer.mozilla.org/en-US/docs/Web/CSS/padding#values)
-    let margin = style.margin().resolve_or_zero(parent_size.width);
-    let padding = style.padding().resolve_or_zero(parent_size.width);
-    let border = style.border().resolve_or_zero(parent_size.width);
+    let margin = style.margin().resolve_or_zero(parent_size.width, &resolve_calc_value);
+    let padding = style.padding().resolve_or_zero(parent_size.width, &resolve_calc_value);
+    let border = style.border().resolve_or_zero(parent_size.width, &resolve_calc_value);
     let padding_border = padding + border;
     let pb_sum = padding_border.sum_axes();
     let box_sizing_adjustment = if style.box_sizing() == BoxSizing::ContentBox { pb_sum } else { Size::ZERO };
@@ -44,15 +45,16 @@ where
             let aspect_ratio = style.aspect_ratio();
             let style_size = style
                 .size()
-                .maybe_resolve(parent_size)
+                .maybe_resolve(parent_size, &resolve_calc_value)
                 .maybe_apply_aspect_ratio(aspect_ratio)
                 .maybe_add(box_sizing_adjustment);
             let style_min_size = style
                 .min_size()
-                .maybe_resolve(parent_size)
+                .maybe_resolve(parent_size, &resolve_calc_value)
                 .maybe_apply_aspect_ratio(aspect_ratio)
                 .maybe_add(box_sizing_adjustment);
-            let style_max_size = style.max_size().maybe_resolve(parent_size).maybe_add(box_sizing_adjustment);
+            let style_max_size =
+                style.max_size().maybe_resolve(parent_size, &resolve_calc_value).maybe_add(box_sizing_adjustment);
 
             let node_size = known_dimensions.or(style_size);
             (node_size, style_min_size, style_max_size, aspect_ratio)
diff --git a/src/compute/mod.rs b/src/compute/mod.rs
index 4228e21a0..cf0b78ca8 100644
--- a/src/compute/mod.rs
+++ b/src/compute/mod.rs
@@ -66,26 +66,26 @@ pub fn compute_root_layout(tree: &mut impl LayoutPartialTree, root: NodeId, avai
         if style.is_block() {
             // Pull these out earlier to avoid borrowing issues
             let aspect_ratio = style.aspect_ratio();
-            let margin = style.margin().resolve_or_zero(parent_size.width);
-            let padding = style.padding().resolve_or_zero(parent_size.width);
-            let border = style.border().resolve_or_zero(parent_size.width);
+            let margin = style.margin().resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
+            let padding = style.padding().resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
+            let border = style.border().resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
             let padding_border_size = (padding + border).sum_axes();
             let box_sizing_adjustment =
                 if style.box_sizing() == BoxSizing::ContentBox { padding_border_size } else { Size::ZERO };
 
             let min_size = style
                 .min_size()
-                .maybe_resolve(parent_size)
+                .maybe_resolve(parent_size, |val, basis| tree.calc(val, basis))
                 .maybe_apply_aspect_ratio(aspect_ratio)
                 .maybe_add(box_sizing_adjustment);
             let max_size = style
                 .max_size()
-                .maybe_resolve(parent_size)
+                .maybe_resolve(parent_size, |val, basis| tree.calc(val, basis))
                 .maybe_apply_aspect_ratio(aspect_ratio)
                 .maybe_add(box_sizing_adjustment);
             let clamped_style_size = style
                 .size()
-                .maybe_resolve(parent_size)
+                .maybe_resolve(parent_size, |val, basis| tree.calc(val, basis))
                 .maybe_apply_aspect_ratio(aspect_ratio)
                 .maybe_add(box_sizing_adjustment)
                 .maybe_clamp(min_size, max_size);
@@ -123,9 +123,12 @@ pub fn compute_root_layout(tree: &mut impl LayoutPartialTree, root: NodeId, avai
     );
 
     let style = tree.get_core_container_style(root);
-    let padding = style.padding().resolve_or_zero(available_space.width.into_option());
-    let border = style.border().resolve_or_zero(available_space.width.into_option());
-    let margin = style.margin().resolve_or_zero(available_space.width.into_option());
+    let padding =
+        style.padding().resolve_or_zero(available_space.width.into_option(), |val, basis| tree.calc(val, basis));
+    let border =
+        style.border().resolve_or_zero(available_space.width.into_option(), |val, basis| tree.calc(val, basis));
+    let margin =
+        style.margin().resolve_or_zero(available_space.width.into_option(), |val, basis| tree.calc(val, basis));
     let scrollbar_size = Size {
         width: if style.overflow().y == Overflow::Scroll { style.scrollbar_width() } else { 0.0 },
         height: if style.overflow().x == Overflow::Scroll { style.scrollbar_width() } else { 0.0 },
diff --git a/src/lib.rs b/src/lib.rs
index 85ab924b9..d3a252d25 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -63,7 +63,6 @@
 #![cfg_attr(docsrs, feature(doc_auto_cfg))]
 #![cfg_attr(not(feature = "std"), no_std)]
 #![deny(unsafe_code)]
-#![forbid(unsafe_code)]
 #![warn(missing_docs)]
 #![warn(clippy::missing_docs_in_private_items)]
 
diff --git a/src/style/dimension.rs b/src/style/dimension.rs
index 59fbd2bf3..70342c335 100644
--- a/src/style/dimension.rs
+++ b/src/style/dimension.rs
@@ -12,8 +12,10 @@ pub struct CompactLength(u64);
 impl CompactLength {
     // Masks
 
-    /// The low bytes (8 bits)
+    /// The low byte (8 bits)
     pub const TAG_MASK: u64 = 0b11111111;
+    /// The low 3 bits
+    pub const CALC_TAG_MASK: u64 = 0b111;
     /// The high 63 bits
     pub const CALC_PTR_MASK: u64 = u64::MAX ^ 0b111;
 
@@ -55,16 +57,16 @@ impl CompactLength {
         Self(((val.to_bits() as u64) << 32) | Self::PERCENT_TAG)
     }
 
-    // /// A `calc()` value. The value passed here is treated as an opaque handle to
-    // /// the actual calc representation and may be a pointer, index, etc.
-    // ///
-    // /// The low 3 bits are used as a tag value and will be returned as 0.
-    // #[inline]
-    // pub fn calc(ptr: *const ()) -> Self {
-    //     assert_ne!(ptr as u64, 0);
-    //     assert_eq!(ptr as u64 & Self::CALC_PTR_MASK, 0);
-    //     Self(ptr as u64 & Self::CALC_TAG)
-    // }
+    /// A `calc()` value. The value passed here is treated as an opaque handle to
+    /// the actual calc representation and may be a pointer, index, etc.
+    ///
+    /// The low 3 bits are used as a tag value and will be returned as 0.
+    #[inline]
+    pub fn calc(ptr: *const ()) -> Self {
+        assert_ne!(ptr as u64, 0);
+        assert_eq!(ptr as u64 & 0b111, 0);
+        Self(ptr as u64 | Self::CALC_TAG)
+    }
 
     /// The dimension should be automatically computed according to algorithm-specific rules
     /// regarding the default size of boxes.
@@ -123,6 +125,12 @@ impl CompactLength {
         Self(((limit.to_bits() as u64) << 32) | Self::FIT_CONTENT_PERCENT_TAG)
     }
 
+    /// Get the primary tag
+    #[inline(always)]
+    pub const fn tag(self) -> u64 {
+        self.0 & Self::TAG_MASK
+    }
+
     /// Get the numeric value associated with the `CompactLength`
     /// (e.g. the pixel value for a LENGTH variant)
     #[inline(always)]
@@ -130,10 +138,17 @@ impl CompactLength {
         f32::from_bits((self.0 >> 32) as u32)
     }
 
-    /// Get the primary tag
+    /// Get the numeric value associated with the `CompactLength`
+    /// (e.g. the pixel value for a LENGTH variant)
     #[inline(always)]
-    pub const fn tag(self) -> u64 {
-        self.0 & Self::TAG_MASK
+    pub const fn calc_value(self) -> u64 {
+        self.0
+    }
+
+    /// Returns true if the value is 0 px
+    #[inline(always)]
+    pub const fn is_calc(self) -> bool {
+        self.0 & Self::CALC_TAG_MASK == 0
     }
 
     /// Returns true if the value is 0 px
@@ -221,15 +236,16 @@ impl CompactLength {
     #[inline(always)]
     pub const fn uses_percentage(self) -> bool {
         // TODO: handle calc() values
-        matches!(self.tag(), CompactLength::PERCENT_TAG | CompactLength::FIT_CONTENT_PERCENT_TAG)
+        matches!(self.tag(), CompactLength::PERCENT_TAG | CompactLength::FIT_CONTENT_PERCENT_TAG) || self.is_calc()
     }
 
     /// Resolve percentage values against the passed parent_size, returning Some(value)
     /// Non-percentage values always return None.
     #[inline(always)]
-    pub fn resolved_percentage_size(self, parent_size: f32) -> Option<f32> {
+    pub fn resolved_percentage_size(self, parent_size: f32, calc_resolver: impl Fn(u64, f32) -> f32) -> Option<f32> {
         match self.tag() {
             CompactLength::PERCENT_TAG => Some(self.value() * parent_size),
+            _ if self.is_calc() => Some(calc_resolver(self.0, parent_size)),
             _ => None,
         }
     }
@@ -293,10 +309,18 @@ impl FromPercent for LengthPercentage {
     }
 }
 impl LengthPercentage {
-    /// Get the raw bit representation of the value
+    /// Get the underlying `CompactLength` representation of the value
     pub fn into_raw(self) -> CompactLength {
         self.0
     }
+
+    /// Create a LengthPercentage from a raw `CompactLength`.
+    /// # Safety
+    /// CompactLength must represent a valid variant for LengthPercentage
+    #[allow(unsafe_code)]
+    pub unsafe fn from_raw(val: CompactLength) -> Self {
+        Self(val)
+    }
 }
 
 /// A unit of linear measurement
@@ -326,12 +350,6 @@ impl From<LengthPercentage> for LengthPercentageAuto {
         Self(input.0)
     }
 }
-impl LengthPercentageAuto {
-    /// Get the raw bit representation of the value
-    pub fn into_raw(self) -> CompactLength {
-        self.0
-    }
-}
 
 impl LengthPercentageAuto {
     /// Returns:
@@ -339,11 +357,12 @@ impl LengthPercentageAuto {
     ///   - Some(resolved) using the provided context for Percent variants
     ///   - None for Auto variants
     #[inline(always)]
-    pub fn resolve_to_option(self, context: f32) -> Option<f32> {
+    pub fn resolve_to_option(self, context: f32, calc_resolver: impl Fn(u64, f32) -> f32) -> Option<f32> {
         match self.0.tag() {
             CompactLength::LENGTH_TAG => Some(self.0.value()),
             CompactLength::PERCENT_TAG => Some(context * self.0.value()),
             CompactLength::AUTO_TAG => None,
+            _ if self.0.is_calc() => Some(calc_resolver(self.0.calc_value(), context)),
             _ => unreachable!("LengthPercentageAuto values cannot be constructed with other tags"),
         }
     }
@@ -353,6 +372,19 @@ impl LengthPercentageAuto {
     pub fn is_auto(self) -> bool {
         self.0.is_auto()
     }
+
+    /// Get the underlying `CompactLength` representation of the value
+    pub fn into_raw(self) -> CompactLength {
+        self.0
+    }
+
+    /// Create a LengthPercentageAuto from a raw `CompactLength`.
+    /// # Safety
+    /// CompactLength must represent a valid variant for LengthPercentageAuto
+    #[allow(unsafe_code)]
+    pub unsafe fn from_raw(val: CompactLength) -> Self {
+        Self(val)
+    }
 }
 
 /// A unit of linear measurement
@@ -403,10 +435,28 @@ impl Dimension {
         self.0.is_auto()
     }
 
-    /// Get the raw bit representation of the value
+    /// Get the raw `CompactLength` tag
+    pub fn tag(self) -> u64 {
+        self.0.tag()
+    }
+
+    /// Get the raw `CompactLength` value for non-calc variants that have a numeric parameter
+    pub fn value(self) -> f32 {
+        self.0.value()
+    }
+
+    /// Get the underlying `CompactLength` representation of the value
     pub fn into_raw(self) -> CompactLength {
         self.0
     }
+
+    /// Create a Dimension from a raw `CompactLength`.
+    /// # Safety
+    ///  CompactLength must represent a valid variant for Dimension
+    #[allow(unsafe_code)]
+    pub unsafe fn from_raw(val: CompactLength) -> Self {
+        Self(val)
+    }
 }
 
 impl Rect<Dimension> {
diff --git a/src/style/grid.rs b/src/style/grid.rs
index 835ed7fe0..23ea1167d 100644
--- a/src/style/grid.rs
+++ b/src/style/grid.rs
@@ -1,5 +1,8 @@
 //! Style types for CSS Grid layout
-use super::{AlignContent, AlignItems, AlignSelf, CompactLength, CoreStyle, JustifyContent, LengthPercentage, Style};
+use super::{
+    AlignContent, AlignItems, AlignSelf, CompactLength, CoreStyle, Dimension, JustifyContent, LengthPercentage,
+    LengthPercentageAuto, Style,
+};
 use crate::compute::grid::{GridCoordinate, GridLine, OriginZeroLine};
 use crate::geometry::{AbsoluteAxis, AbstractAxis, Line, MinMax, Size};
 use crate::style_helpers::*;
@@ -397,13 +400,36 @@ impl FromFr for MaxTrackSizingFunction {
         Self(CompactLength::from_fr(fr))
     }
 }
+impl From<LengthPercentage> for MaxTrackSizingFunction {
+    fn from(input: LengthPercentage) -> Self {
+        Self(input.0)
+    }
+}
+impl From<LengthPercentageAuto> for MaxTrackSizingFunction {
+    fn from(input: LengthPercentageAuto) -> Self {
+        Self(input.0)
+    }
+}
+impl From<Dimension> for MaxTrackSizingFunction {
+    fn from(input: Dimension) -> Self {
+        Self(input.0)
+    }
+}
 
 impl MaxTrackSizingFunction {
-    /// Get the raw bit representation of the value
+    /// Get the underlying `CompactLength` representation of the value
     pub fn into_raw(self) -> CompactLength {
         self.0
     }
 
+    /// Create a MaxTrackSizingFunction from a raw `CompactLength`.
+    /// # Safety
+    /// CompactLength must represent a valid variant for MaxTrackSizingFunction
+    #[allow(unsafe_code)]
+    pub unsafe fn from_raw(val: CompactLength) -> Self {
+        Self(val)
+    }
+
     /// Returns true if the max track sizing function is `MinContent`, `MaxContent`, `FitContent` or `Auto`, else false.
     #[inline(always)]
     pub fn is_intrinsic(&self) -> bool {
@@ -454,14 +480,26 @@ impl MaxTrackSizingFunction {
         self.0.is_max_or_fit_content()
     }
 
+    /// Returns whether the value can be resolved using `Self::definite_value`
+    #[inline(always)]
+    pub fn has_definite_value(self, parent_size: Option<f32>) -> bool {
+        match self.0.tag() {
+            CompactLength::LENGTH_TAG => true,
+            CompactLength::PERCENT_TAG => parent_size.is_some(),
+            _ if self.0.is_calc() => parent_size.is_some(),
+            _ => false,
+        }
+    }
+
     /// Returns fixed point values directly. Attempts to resolve percentage values against
     /// the passed available_space and returns if this results in a concrete value (which it
     /// will if the available_space is `Some`). Otherwise returns None.
     #[inline(always)]
-    pub fn definite_value(self, parent_size: Option<f32>) -> Option<f32> {
+    pub fn definite_value(self, parent_size: Option<f32>, calc_resolver: impl Fn(u64, f32) -> f32) -> Option<f32> {
         match self.0.tag() {
             CompactLength::LENGTH_TAG => Some(self.0.value()),
             CompactLength::PERCENT_TAG => parent_size.map(|size| self.0.value() * size),
+            _ if self.0.is_calc() => parent_size.map(|size| calc_resolver(self.0.calc_value(), size)),
             _ => None,
         }
     }
@@ -473,19 +511,19 @@ impl MaxTrackSizingFunction {
     ///     - A fit-content sizing function with percentage argument (with definite available space)
     /// All other kinds of track sizing function return None.
     #[inline(always)]
-    pub fn definite_limit(self, parent_size: Option<f32>) -> Option<f32> {
+    pub fn definite_limit(self, parent_size: Option<f32>, calc_resolver: impl Fn(u64, f32) -> f32) -> Option<f32> {
         match self.0.tag() {
             CompactLength::FIT_CONTENT_PX_TAG => Some(self.0.value()),
             CompactLength::FIT_CONTENT_PERCENT_TAG => parent_size.map(|size| self.0.value() * size),
-            _ => self.definite_value(parent_size),
+            _ => self.definite_value(parent_size, calc_resolver),
         }
     }
 
     /// Resolve percentage values against the passed parent_size, returning Some(value)
     /// Non-percentage values always return None.
     #[inline(always)]
-    pub fn resolved_percentage_size(self, parent_size: f32) -> Option<f32> {
-        self.0.resolved_percentage_size(parent_size)
+    pub fn resolved_percentage_size(self, parent_size: f32, calc_resolver: impl Fn(u64, f32) -> f32) -> Option<f32> {
+        self.0.resolved_percentage_size(parent_size, calc_resolver)
     }
 
     /// Whether the track sizing functions depends on the size of the parent node
@@ -530,13 +568,31 @@ impl From<LengthPercentage> for MinTrackSizingFunction {
         Self(input.0)
     }
 }
+impl From<LengthPercentageAuto> for MinTrackSizingFunction {
+    fn from(input: LengthPercentageAuto) -> Self {
+        Self(input.0)
+    }
+}
+impl From<Dimension> for MinTrackSizingFunction {
+    fn from(input: Dimension) -> Self {
+        Self(input.0)
+    }
+}
 
 impl MinTrackSizingFunction {
-    /// Get the raw bit representation of the value
+    /// Get the underlying `CompactLength` representation of the value
     pub fn into_raw(self) -> CompactLength {
         self.0
     }
 
+    /// Create a MinTrackSizingFunction from a raw `CompactLength`.
+    /// # Safety
+    /// CompactLength must represent a valid variant for MinTrackSizingFunction
+    #[allow(unsafe_code)]
+    pub unsafe fn from_raw(val: CompactLength) -> Self {
+        Self(val)
+    }
+
     /// Returns true if the min track sizing function is `MinContent`, `MaxContent` or `Auto`, else false.
     #[inline(always)]
     pub fn is_intrinsic(&self) -> bool {
@@ -577,10 +633,11 @@ impl MinTrackSizingFunction {
     /// the passed available_space and returns if this results in a concrete value (which it
     /// will if the available_space is `Some`). Otherwise returns `None`.
     #[inline(always)]
-    pub fn definite_value(self, parent_size: Option<f32>) -> Option<f32> {
+    pub fn definite_value(self, parent_size: Option<f32>, calc_resolver: impl Fn(u64, f32) -> f32) -> Option<f32> {
         match self.0.tag() {
             CompactLength::LENGTH_TAG => Some(self.0.value()),
             CompactLength::PERCENT_TAG => parent_size.map(|size| self.0.value() * size),
+            _ if self.0.is_calc() => parent_size.map(|size| calc_resolver(self.0.calc_value(), size)),
             _ => None,
         }
     }
@@ -588,14 +645,14 @@ impl MinTrackSizingFunction {
     /// Resolve percentage values against the passed parent_size, returning Some(value)
     /// Non-percentage values always return None.
     #[inline(always)]
-    pub fn resolved_percentage_size(self, parent_size: f32) -> Option<f32> {
-        self.0.resolved_percentage_size(parent_size)
+    pub fn resolved_percentage_size(self, parent_size: f32, calc_resolver: impl Fn(u64, f32) -> f32) -> Option<f32> {
+        self.0.resolved_percentage_size(parent_size, calc_resolver)
     }
 
     /// Whether the track sizing functions depends on the size of the parent node
     #[inline(always)]
     pub fn uses_percentage(self) -> bool {
-        matches!(self.0.tag(), CompactLength::PERCENT_TAG)
+        matches!(self.0.tag(), CompactLength::PERCENT_TAG) || self.0.is_calc()
     }
 }
 
@@ -652,9 +709,19 @@ impl FromFr for NonRepeatedTrackSizingFunction {
         Self { min: MinTrackSizingFunction::AUTO, max: MaxTrackSizingFunction::from_fr(flex) }
     }
 }
-impl From<LengthPercentage> for MaxTrackSizingFunction {
+impl From<LengthPercentage> for NonRepeatedTrackSizingFunction {
     fn from(input: LengthPercentage) -> Self {
-        Self(input.0)
+        Self { min: input.into(), max: input.into() }
+    }
+}
+impl From<LengthPercentageAuto> for NonRepeatedTrackSizingFunction {
+    fn from(input: LengthPercentageAuto) -> Self {
+        Self { min: input.into(), max: input.into() }
+    }
+}
+impl From<Dimension> for NonRepeatedTrackSizingFunction {
+    fn from(input: Dimension) -> Self {
+        Self { min: input.into(), max: input.into() }
     }
 }
 
diff --git a/src/tree/taffy_tree.rs b/src/tree/taffy_tree.rs
index 831226067..67666e691 100644
--- a/src/tree/taffy_tree.rs
+++ b/src/tree/taffy_tree.rs
@@ -335,6 +335,11 @@ where
         self.taffy.nodes[node_id.into()].unrounded_layout = *layout;
     }
 
+    #[inline(always)]
+    fn resolve_calc_value(&self, _val: u64, _basis: f32) -> f32 {
+        0.0
+    }
+
     #[inline(always)]
     fn compute_child_layout(&mut self, node: NodeId, inputs: LayoutInput) -> LayoutOutput {
         // If RunMode is PerformHiddenLayout then this indicates that an ancestor node is `Display::None`
@@ -379,7 +384,8 @@ where
                     let measure_function = |known_dimensions, available_space| {
                         (tree.measure_function)(known_dimensions, available_space, node, node_context, style)
                     };
-                    compute_leaf_layout(inputs, style, measure_function)
+                    // TODO: implement calc() in high-level API
+                    compute_leaf_layout(inputs, style, |_, _| 0.0, measure_function)
                 }
             }
         })
diff --git a/src/tree/traits.rs b/src/tree/traits.rs
index c59b28976..8837d3f18 100644
--- a/src/tree/traits.rs
+++ b/src/tree/traits.rs
@@ -180,6 +180,9 @@ pub trait LayoutPartialTree: TraversePartialTree {
     /// Get core style
     fn get_core_container_style(&self, node_id: NodeId) -> Self::CoreContainerStyle<'_>;
 
+    /// Resolve calc value
+    fn resolve_calc_value(&self, val: u64, basis: f32) -> f32;
+
     /// Set the node's unrounded layout
     fn set_unrounded_layout(&mut self, node_id: NodeId, layout: &Layout);
 
@@ -360,6 +363,12 @@ pub(crate) trait LayoutPartialTreeExt: LayoutPartialTree {
             },
         )
     }
+
+    /// Alias to `resolve_calc_value` with a shorter function name
+    #[inline(always)]
+    fn calc(&self, val: u64, basis: f32) -> f32 {
+        self.resolve_calc_value(val, basis)
+    }
 }
 
 impl<T: LayoutPartialTree> LayoutPartialTreeExt for T {}
diff --git a/src/util/resolve.rs b/src/util/resolve.rs
index 3f9dc619c..e52861cec 100644
--- a/src/util/resolve.rs
+++ b/src/util/resolve.rs
@@ -12,7 +12,7 @@ use crate::CompactLength;
 /// Will return a `None` if it unable to resolve.
 pub trait MaybeResolve<In, Out> {
     /// Resolve a dimension that might be dependent on a context, with `None` as fallback value
-    fn maybe_resolve(self, context: In) -> Out;
+    fn maybe_resolve(self, context: In, calc: impl Fn(u64, f32) -> f32) -> Out;
 }
 
 /// Trait to encapsulate behaviour where we need to resolve from a
@@ -22,16 +22,17 @@ pub trait MaybeResolve<In, Out> {
 /// Will return a default value if it unable to resolve.
 pub trait ResolveOrZero<TContext, TOutput: TaffyZero> {
     /// Resolve a dimension that might be dependent on a context, with a default fallback value
-    fn resolve_or_zero(self, context: TContext) -> TOutput;
+    fn resolve_or_zero(self, context: TContext, calc: impl Fn(u64, f32) -> f32) -> TOutput;
 }
 
 impl MaybeResolve<Option<f32>, Option<f32>> for LengthPercentage {
     /// Converts the given [`LengthPercentage`] into an absolute length
     /// Can return `None`
-    fn maybe_resolve(self, context: Option<f32>) -> Option<f32> {
+    fn maybe_resolve(self, context: Option<f32>, calc: impl Fn(u64, f32) -> f32) -> Option<f32> {
         match self.0.tag() {
             CompactLength::LENGTH_TAG => Some(self.0.value()),
             CompactLength::PERCENT_TAG => context.map(|dim| dim * self.0.value()),
+            _ if self.0.is_calc() => context.map(|dim| calc(self.0.calc_value(), dim)),
             _ => unreachable!(),
         }
     }
@@ -40,11 +41,12 @@ impl MaybeResolve<Option<f32>, Option<f32>> for LengthPercentage {
 impl MaybeResolve<Option<f32>, Option<f32>> for LengthPercentageAuto {
     /// Converts the given [`LengthPercentageAuto`] into an absolute length
     /// Can return `None`
-    fn maybe_resolve(self, context: Option<f32>) -> Option<f32> {
+    fn maybe_resolve(self, context: Option<f32>, calc: impl Fn(u64, f32) -> f32) -> Option<f32> {
         match self.0.tag() {
+            CompactLength::AUTO_TAG => None,
             CompactLength::LENGTH_TAG => Some(self.0.value()),
             CompactLength::PERCENT_TAG => context.map(|dim| dim * self.0.value()),
-            CompactLength::AUTO_TAG => None,
+            _ if self.0.is_calc() => context.map(|dim| calc(self.0.calc_value(), dim)),
             _ => unreachable!(),
         }
     }
@@ -54,11 +56,12 @@ impl MaybeResolve<Option<f32>, Option<f32>> for Dimension {
     /// Converts the given [`Dimension`] into an absolute length
     ///
     /// Can return `None`
-    fn maybe_resolve(self, context: Option<f32>) -> Option<f32> {
+    fn maybe_resolve(self, context: Option<f32>, calc: impl Fn(u64, f32) -> f32) -> Option<f32> {
         match self.0.tag() {
+            CompactLength::AUTO_TAG => None,
             CompactLength::LENGTH_TAG => Some(self.0.value()),
             CompactLength::PERCENT_TAG => context.map(|dim| dim * self.0.value()),
-            CompactLength::AUTO_TAG => None,
+            _ if self.0.is_calc() => context.map(|dim| calc(self.0.calc_value(), dim)),
             _ => unreachable!(),
         }
     }
@@ -69,57 +72,63 @@ impl MaybeResolve<Option<f32>, Option<f32>> for Dimension {
 impl<T: MaybeResolve<Option<f32>, Option<f32>>> MaybeResolve<f32, Option<f32>> for T {
     /// Converts the given MaybeResolve value into an absolute length
     /// Can return `None`
-    fn maybe_resolve(self, context: f32) -> Option<f32> {
-        self.maybe_resolve(Some(context))
+    fn maybe_resolve(self, context: f32, calc: impl Fn(u64, f32) -> f32) -> Option<f32> {
+        self.maybe_resolve(Some(context), calc)
     }
 }
 
 // Generic MaybeResolve for Size
 impl<In, Out, T: MaybeResolve<In, Out>> MaybeResolve<Size<In>, Size<Out>> for Size<T> {
     /// Converts any `parent`-relative values for size into an absolute size
-    fn maybe_resolve(self, context: Size<In>) -> Size<Out> {
-        Size { width: self.width.maybe_resolve(context.width), height: self.height.maybe_resolve(context.height) }
+    fn maybe_resolve(self, context: Size<In>, calc: impl Fn(u64, f32) -> f32) -> Size<Out> {
+        Size {
+            width: self.width.maybe_resolve(context.width, &calc),
+            height: self.height.maybe_resolve(context.height, &calc),
+        }
     }
 }
 
 impl ResolveOrZero<Option<f32>, f32> for LengthPercentage {
     /// Will return a default value of result is evaluated to `None`
-    fn resolve_or_zero(self, context: Option<f32>) -> f32 {
-        self.maybe_resolve(context).unwrap_or(0.0)
+    fn resolve_or_zero(self, context: Option<f32>, calc: impl Fn(u64, f32) -> f32) -> f32 {
+        self.maybe_resolve(context, calc).unwrap_or(0.0)
     }
 }
 
 impl ResolveOrZero<Option<f32>, f32> for LengthPercentageAuto {
     /// Will return a default value of result is evaluated to `None`
-    fn resolve_or_zero(self, context: Option<f32>) -> f32 {
-        self.maybe_resolve(context).unwrap_or(0.0)
+    fn resolve_or_zero(self, context: Option<f32>, calc: impl Fn(u64, f32) -> f32) -> f32 {
+        self.maybe_resolve(context, calc).unwrap_or(0.0)
     }
 }
 
 impl ResolveOrZero<Option<f32>, f32> for Dimension {
     /// Will return a default value of result is evaluated to `None`
-    fn resolve_or_zero(self, context: Option<f32>) -> f32 {
-        self.maybe_resolve(context).unwrap_or(0.0)
+    fn resolve_or_zero(self, context: Option<f32>, calc: impl Fn(u64, f32) -> f32) -> f32 {
+        self.maybe_resolve(context, calc).unwrap_or(0.0)
     }
 }
 
 // Generic ResolveOrZero for Size
 impl<In, Out: TaffyZero, T: ResolveOrZero<In, Out>> ResolveOrZero<Size<In>, Size<Out>> for Size<T> {
     /// Converts any `parent`-relative values for size into an absolute size
-    fn resolve_or_zero(self, context: Size<In>) -> Size<Out> {
-        Size { width: self.width.resolve_or_zero(context.width), height: self.height.resolve_or_zero(context.height) }
+    fn resolve_or_zero(self, context: Size<In>, calc: impl Fn(u64, f32) -> f32) -> Size<Out> {
+        Size {
+            width: self.width.resolve_or_zero(context.width, &calc),
+            height: self.height.resolve_or_zero(context.height, &calc),
+        }
     }
 }
 
 // Generic ResolveOrZero for resolving Rect against Size
 impl<In: Copy, Out: TaffyZero, T: ResolveOrZero<In, Out>> ResolveOrZero<Size<In>, Rect<Out>> for Rect<T> {
     /// Converts any `parent`-relative values for Rect into an absolute Rect
-    fn resolve_or_zero(self, context: Size<In>) -> Rect<Out> {
+    fn resolve_or_zero(self, context: Size<In>, calc: impl Fn(u64, f32) -> f32) -> Rect<Out> {
         Rect {
-            left: self.left.resolve_or_zero(context.width),
-            right: self.right.resolve_or_zero(context.width),
-            top: self.top.resolve_or_zero(context.height),
-            bottom: self.bottom.resolve_or_zero(context.height),
+            left: self.left.resolve_or_zero(context.width, &calc),
+            right: self.right.resolve_or_zero(context.width, &calc),
+            top: self.top.resolve_or_zero(context.height, &calc),
+            bottom: self.bottom.resolve_or_zero(context.height, &calc),
         }
     }
 }
@@ -127,12 +136,12 @@ impl<In: Copy, Out: TaffyZero, T: ResolveOrZero<In, Out>> ResolveOrZero<Size<In>
 // Generic ResolveOrZero for resolving Rect against Option
 impl<Out: TaffyZero, T: ResolveOrZero<Option<f32>, Out>> ResolveOrZero<Option<f32>, Rect<Out>> for Rect<T> {
     /// Converts any `parent`-relative values for Rect into an absolute Rect
-    fn resolve_or_zero(self, context: Option<f32>) -> Rect<Out> {
+    fn resolve_or_zero(self, context: Option<f32>, calc: impl Fn(u64, f32) -> f32) -> Rect<Out> {
         Rect {
-            left: self.left.resolve_or_zero(context),
-            right: self.right.resolve_or_zero(context),
-            top: self.top.resolve_or_zero(context),
-            bottom: self.bottom.resolve_or_zero(context),
+            left: self.left.resolve_or_zero(context, &calc),
+            right: self.right.resolve_or_zero(context, &calc),
+            top: self.top.resolve_or_zero(context, &calc),
+            bottom: self.bottom.resolve_or_zero(context, &calc),
         }
     }
 }
@@ -149,7 +158,7 @@ mod tests {
         Lhs: MaybeResolve<Rhs, Out>,
         Out: PartialEq + Debug,
     {
-        assert_eq!(input.maybe_resolve(context), expected);
+        assert_eq!(input.maybe_resolve(context, |_, _| 42.42), expected);
     }
 
     // ResolveOrZero test runner
@@ -158,7 +167,7 @@ mod tests {
         Lhs: ResolveOrZero<Rhs, Out>,
         Out: PartialEq + Debug + TaffyZero,
     {
-        assert_eq!(input.resolve_or_zero(context), expected);
+        assert_eq!(input.resolve_or_zero(context, |_, _| 42.42), expected);
     }
 
     mod maybe_resolve_dimension {