tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 492HTML 238TOML 3CSS 11112 Sutichaykuna

Implement minimal usage of Number enum

080855712339c97b4f84b648e3c9abc3de7ccb0f · ruwaq Emil Sjölander · (7 wata ñawpaq) · tayta 97ae47bc9 · kay commitpi sach'ata qhaway

Semántico sayk'uy · 36

src/algo.rs

src/lib.rs

src/number.rs

src/style.rs

  • silq'uyninpi t'ikrakurqan struct Node pub
  • silq'uyninpi t'ikrakurqan def resolve

4 qillqakuna · +371 −143

diff --git a/src/algo.rs b/src/algo.rs
index f466c90d8..7e3209b5d 100644
--- a/src/algo.rs
+++ b/src/algo.rs
@@ -3,6 +3,10 @@ use std::f32;
 use crate::layout;
 use crate::style;
 
+use crate::number::MinMax;
+use crate::number::Number;
+use crate::number::Number::*;
+
 #[derive(Debug, Copy, Clone)]
 struct FlexSize {
     width: f32,
@@ -25,31 +29,6 @@ impl FlexSize {
     }
 }
 
-// TODO - Temporary soltion. Will refactor to implement math function on Option<f32> instead
-// of relying on nan.
-trait SafeMath {
-    fn safe_min(&self, rhs: f32) -> f32;
-    fn safe_max(&self, rhs: f32) -> f32;
-}
-
-impl SafeMath for f32 {
-    fn safe_min(&self, rhs: f32) -> f32 {
-        if self.is_nan() {
-            f32::NAN
-        } else {
-            self.min(rhs)
-        }
-    }
-
-    fn safe_max(&self, rhs: f32) -> f32 {
-        if self.is_nan() {
-            f32::NAN
-        } else {
-            self.max(rhs)
-        }
-    }
-}
-
 struct FlexItem<'a> {
     node: &'a style::Node,
 
@@ -100,11 +79,11 @@ pub fn compute(root: &style::Node) -> layout::Node {
     // Probably want to pass min/max down as top level paramerer instead.
     let first_pass = compute_internal(
         root,
-        root.width.resolve(f32::NAN, f32::NAN),
-        root.height.resolve(f32::NAN, f32::NAN),
-        f32::NAN,
+        root.width.resolve(Undefined).unwrap_or(f32::NAN),
+        root.height.resolve(Undefined).unwrap_or(f32::NAN),
         f32::NAN,
         f32::NAN,
+        Undefined,
     );
 
     let result = compute_internal(
@@ -112,16 +91,18 @@ pub fn compute(root: &style::Node) -> layout::Node {
         first_pass
             .size
             .width
-            .max(root.min_width.resolve(f32::NAN, f32::NAN))
-            .min(root.max_width.resolve(f32::NAN, f32::NAN)),
+            .maybe_max(root.min_width.resolve(Undefined))
+            .maybe_min(root.max_width.resolve(Undefined))
+            .unwrap_or(f32::NAN),
         first_pass
             .size
             .height
-            .max(root.min_height.resolve(f32::NAN, f32::NAN))
-            .min(root.max_height.resolve(f32::NAN, f32::NAN)),
-        f32::NAN,
+            .maybe_max(root.min_height.resolve(Undefined))
+            .maybe_min(root.max_height.resolve(Undefined))
+            .unwrap_or(f32::NAN),
         f32::NAN,
         f32::NAN,
+        Undefined,
     );
 
     round_layout(
@@ -178,7 +159,7 @@ fn compute_internal(
     // This will always be the parent width except for the case when
     // the child is in an absolute context. This value should only
     // be used for calculated percentage values.
-    percent_calc_base: f32,
+    percent_calc_base: Number,
 ) -> ComputeResult {
     // Define some general constants we will need for the remainder
     // of the algorithm.
@@ -193,38 +174,38 @@ fn compute_internal(
     };
 
     let (margin_main_start, margin_cross_start) = (
-        node.main_margin_start(node.flex_direction).resolve(percent_calc_base, 0.0),
-        node.cross_margin_start(node.flex_direction).resolve(percent_calc_base, 0.0),
+        node.main_margin_start(node.flex_direction).resolve(percent_calc_base).unwrap_or(0.0),
+        node.cross_margin_start(node.flex_direction).resolve(percent_calc_base).unwrap_or(0.0),
     );
 
     let (margin_main_end, margin_cross_end) = (
-        node.main_margin_end(node.flex_direction).resolve(percent_calc_base, 0.0),
-        node.cross_margin_end(node.flex_direction).resolve(percent_calc_base, 0.0),
+        node.main_margin_end(node.flex_direction).resolve(percent_calc_base).unwrap_or(0.0),
+        node.cross_margin_end(node.flex_direction).resolve(percent_calc_base).unwrap_or(0.0),
     );
 
     let (margin_main, margin_cross) = (margin_main_start + margin_main_end, margin_cross_start + margin_cross_end);
 
     let (padding_main_start, padding_cross_start) = (
-        node.main_padding_start(node.flex_direction).resolve(percent_calc_base, 0.0),
-        node.cross_padding_start(node.flex_direction).resolve(percent_calc_base, 0.0),
+        node.main_padding_start(node.flex_direction).resolve(percent_calc_base).unwrap_or(0.0),
+        node.cross_padding_start(node.flex_direction).resolve(percent_calc_base).unwrap_or(0.0),
     );
 
     let (padding_main_end, padding_cross_end) = (
-        node.main_padding_end(node.flex_direction).resolve(percent_calc_base, 0.0),
-        node.cross_padding_end(node.flex_direction).resolve(percent_calc_base, 0.0),
+        node.main_padding_end(node.flex_direction).resolve(percent_calc_base).unwrap_or(0.0),
+        node.cross_padding_end(node.flex_direction).resolve(percent_calc_base).unwrap_or(0.0),
     );
 
     let (padding_main, padding_cross) =
         (padding_main_start + padding_main_end, padding_cross_start + padding_cross_end);
 
     let (border_main_start, border_cross_start) = (
-        node.main_border_start(node.flex_direction).resolve(percent_calc_base, 0.0),
-        node.cross_border_start(node.flex_direction).resolve(percent_calc_base, 0.0),
+        node.main_border_start(node.flex_direction).resolve(percent_calc_base).unwrap_or(0.0),
+        node.cross_border_start(node.flex_direction).resolve(percent_calc_base).unwrap_or(0.0),
     );
 
     let (border_main_end, border_cross_end) = (
-        node.main_border_end(node.flex_direction).resolve(percent_calc_base, 0.0),
-        node.cross_border_end(node.flex_direction).resolve(percent_calc_base, 0.0),
+        node.main_border_end(node.flex_direction).resolve(percent_calc_base).unwrap_or(0.0),
+        node.cross_border_end(node.flex_direction).resolve(percent_calc_base).unwrap_or(0.0),
     );
 
     let (border_main, border_cross) = (border_main_start + border_main_end, border_cross_start + border_cross_end);
@@ -234,7 +215,8 @@ fn compute_internal(
 
     let wrap_reverse = node.flex_wrap == style::FlexWrap::WrapReverse;
 
-    let percent_calc_base_child = if node.flex_direction.is_row() { node_inner_main } else { node_inner_cross };
+    let percent_calc_base_child =
+        Number::from_f32(if node.flex_direction.is_row() { node_inner_main } else { node_inner_cross });
 
     // 9.2. Line Length Determination
 
@@ -296,7 +278,7 @@ fn compute_internal(
     for child in &mut flex_items {
         // A. If the item has a definite used flex basis, that’s the flex base size.
 
-        let flex_basis = child.node.flex_basis.resolve(percent_calc_base_child, f32::NAN);
+        let flex_basis = child.node.flex_basis.resolve(percent_calc_base_child).unwrap_or(f32::NAN);
         if flex_basis.is_finite() {
             child.flex_basis = flex_basis;
             continue;
@@ -307,7 +289,7 @@ fn compute_internal(
         //    then the flex base size is calculated from its inner
         //    cross size and the flex item’s intrinsic aspect ratio.
 
-        if let Some(ratio) = child.node.aspect_ratio {
+        if let Defined(ratio) = child.node.aspect_ratio {
             if node_cross.is_finite() && child.node.flex_basis == style::Dimension::Auto {
                 child.flex_basis = ratio * node_cross;
                 continue;
@@ -342,23 +324,23 @@ fn compute_internal(
         //         child
         //             .node
         //             .width
-        //             .resolve(percent_calc_base_child, f32::NAN)
-        //             .safe_max(child.node.min_width.resolve(percent_calc_base_child, f32::NAN))
-        //             .safe_min(child.node.max_width.resolve(percent_calc_base_child, f32::NAN)),
+        //             .resolve(percent_calc_base_child).unwrap_or(f32::NAN)
+        //             .safe_max(child.node.min_width.resolve(percent_calc_base_child).unwrap_or(f32::NAN))
+        //             .safe_min(child.node.max_width.resolve(percent_calc_base_child).unwrap_or(f32::NAN)),
         //         child
         //             .node
         //             .height
-        //             .resolve(percent_calc_base_child, f32::NAN)
-        //             .safe_max(child.node.min_height.resolve(percent_calc_base_child, f32::NAN))
-        //             .safe_min(child.node.max_height.resolve(percent_calc_base_child, f32::NAN)),
+        //             .resolve(percent_calc_base_child).unwrap_or(f32::NAN)
+        //             .safe_max(child.node.min_height.resolve(percent_calc_base_child).unwrap_or(f32::NAN))
+        //             .safe_min(child.node.max_height.resolve(percent_calc_base_child).unwrap_or(f32::NAN)),
         //         if node.flex_direction.is_row() { available_main } else { available_cross },
         //         if node.flex_direction.is_row() { available_cross } else { available_main },
         //         percent_calc_base_child,
         //     ).size;
 
         //     child.flex_basis = size.main(node.flex_direction)
-        //             .max(child.node.min_main_size(node.flex_direction).resolve(percent_calc_base_child, f32::NAN))
-        //             .min(child.node.max_main_size(node.flex_direction).resolve(percent_calc_base_child, f32::NAN));
+        //             .max(child.node.min_main_size(node.flex_direction).resolve(percent_calc_base_child).unwrap_or(f32::NAN))
+        //             .min(child.node.max_main_size(node.flex_direction).resolve(percent_calc_base_child).unwrap_or(f32::NAN));
         //     continue;
         // }
 
@@ -374,15 +356,17 @@ fn compute_internal(
             child
                 .node
                 .width
-                .resolve(percent_calc_base_child, f32::NAN)
-                .safe_max(child.node.min_width.resolve(percent_calc_base_child, f32::NAN))
-                .safe_min(child.node.max_width.resolve(percent_calc_base_child, f32::NAN)),
+                .resolve(percent_calc_base_child)
+                .maybe_max(child.node.min_width.resolve(percent_calc_base_child))
+                .maybe_min(child.node.max_width.resolve(percent_calc_base_child))
+                .unwrap_or(f32::NAN),
             child
                 .node
                 .height
-                .resolve(percent_calc_base_child, f32::NAN)
-                .safe_max(child.node.min_height.resolve(percent_calc_base_child, f32::NAN))
-                .safe_min(child.node.max_height.resolve(percent_calc_base_child, f32::NAN)),
+                .resolve(percent_calc_base_child)
+                .maybe_max(child.node.min_height.resolve(percent_calc_base_child))
+                .maybe_min(child.node.max_height.resolve(percent_calc_base_child))
+                .unwrap_or(f32::NAN),
             if node.flex_direction.is_row() { available_main } else { available_cross },
             if node.flex_direction.is_row() { available_cross } else { available_main },
             percent_calc_base_child,
@@ -391,18 +375,23 @@ fn compute_internal(
 
         child.flex_basis = size
             .main(node.flex_direction)
-            .max(child.node.min_main_size(node.flex_direction).resolve(percent_calc_base_child, f32::NAN))
-            .min(child.node.max_main_size(node.flex_direction).resolve(percent_calc_base_child, f32::NAN));
+            .maybe_max(child.node.min_main_size(node.flex_direction).resolve(percent_calc_base_child))
+            .maybe_min(child.node.max_main_size(node.flex_direction).resolve(percent_calc_base_child))
+            .unwrap_or(f32::NAN);
     }
 
     // The hypothetical main size is the item’s flex base size clamped according to its
     // used min and max main sizes (and flooring the content box size at zero).
 
     for child in &mut flex_items {
-        let padding_start = child.node.main_padding_start(node.flex_direction).resolve(percent_calc_base_child, 0.0);
-        let padding_end = child.node.main_padding_end(node.flex_direction).resolve(percent_calc_base_child, 0.0);
-        let border_start = child.node.main_border_start(node.flex_direction).resolve(percent_calc_base_child, 0.0);
-        let border_end = child.node.main_border_end(node.flex_direction).resolve(percent_calc_base_child, 0.0);
+        let padding_start =
+            child.node.main_padding_start(node.flex_direction).resolve(percent_calc_base_child).unwrap_or(0.0);
+        let padding_end =
+            child.node.main_padding_end(node.flex_direction).resolve(percent_calc_base_child).unwrap_or(0.0);
+        let border_start =
+            child.node.main_border_start(node.flex_direction).resolve(percent_calc_base_child).unwrap_or(0.0);
+        let border_end =
+            child.node.main_border_end(node.flex_direction).resolve(percent_calc_base_child).unwrap_or(0.0);
         child.inner_flex_basis = child.flex_basis - (padding_start + padding_end + border_start + border_end);
 
         // TODO - not really spec abiding but needs to be done somewhere. probably somewhere else though.
@@ -420,21 +409,21 @@ fn compute_internal(
             )
             .size
             .width
-            .min(child.node.width.resolve(percent_calc_base_child, f32::NAN))
-            .max(child.node.min_width.resolve(percent_calc_base_child, f32::NAN))
+            .maybe_min(child.node.width.resolve(percent_calc_base_child))
+            .maybe_max(child.node.min_width.resolve(percent_calc_base_child))
         } else {
-            child.node.min_main_size(node.flex_direction).resolve(percent_calc_base_child, f32::NAN)
+            child.node.min_main_size(node.flex_direction).resolve(percent_calc_base_child)
         };
 
         child.hypothetical_inner_main_size = child
             .flex_basis
-            .safe_max(min_main)
-            .safe_min(child.node.max_main_size(node.flex_direction).resolve(percent_calc_base_child, f32::NAN))
-            .max(0.0);
+            .maybe_max(min_main)
+            .maybe_min(child.node.max_main_size(node.flex_direction).resolve(percent_calc_base_child))
+            .unwrap_or(0.0);
 
         child.hypothetical_outer_main_size = child.hypothetical_inner_main_size
-            + child.node.main_margin_start(node.flex_direction).resolve(percent_calc_base_child, 0.0)
-            + child.node.main_margin_end(node.flex_direction).resolve(percent_calc_base_child, 0.0);
+            + child.node.main_margin_start(node.flex_direction).resolve(percent_calc_base_child).unwrap_or(0.0)
+            + child.node.main_margin_end(node.flex_direction).resolve(percent_calc_base_child).unwrap_or(0.0);
     }
 
     // 9.3. Main Size Determination
@@ -512,23 +501,26 @@ fn compute_internal(
                     child
                         .node
                         .width
-                        .resolve(percent_calc_base_child, f32::NAN)
-                        .safe_max(child.node.min_width.resolve(percent_calc_base_child, f32::NAN))
-                        .safe_min(child.node.max_width.resolve(percent_calc_base_child, f32::NAN)),
+                        .resolve(percent_calc_base_child)
+                        .maybe_max(child.node.min_width.resolve(percent_calc_base_child))
+                        .maybe_min(child.node.max_width.resolve(percent_calc_base_child))
+                        .unwrap_or(f32::NAN),
                     child
                         .node
                         .height
-                        .resolve(percent_calc_base_child, f32::NAN)
-                        .safe_max(child.node.min_height.resolve(percent_calc_base_child, f32::NAN))
-                        .safe_min(child.node.max_height.resolve(percent_calc_base_child, f32::NAN)),
+                        .resolve(percent_calc_base_child)
+                        .maybe_max(child.node.min_height.resolve(percent_calc_base_child))
+                        .maybe_min(child.node.max_height.resolve(percent_calc_base_child))
+                        .unwrap_or(f32::NAN),
                     if node.flex_direction.is_row() { available_main } else { available_cross },
                     if node.flex_direction.is_row() { available_cross } else { available_main },
                     percent_calc_base_child,
                 )
                 .size
                 .main(node.flex_direction)
-                .max(child.node.min_main_size(node.flex_direction).resolve(percent_calc_base_child, f32::NAN))
-                .min(child.node.max_main_size(node.flex_direction).resolve(percent_calc_base_child, f32::NAN));
+                .maybe_max(child.node.min_main_size(node.flex_direction).resolve(percent_calc_base_child))
+                .maybe_min(child.node.max_main_size(node.flex_direction).resolve(percent_calc_base_child))
+                .unwrap_or(f32::NAN);
             } else {
                 child.target_main_size = child.hypothetical_inner_main_size;
             }
@@ -537,8 +529,8 @@ fn compute_internal(
             // that casues the target_main_size to never be set for some items
 
             child.outer_target_main_size = child.target_main_size
-                + child.node.main_margin_start(node.flex_direction).resolve(percent_calc_base_child, 0.0)
-                + child.node.main_margin_end(node.flex_direction).resolve(percent_calc_base_child, 0.0);
+                + child.node.main_margin_start(node.flex_direction).resolve(percent_calc_base_child).unwrap_or(0.0)
+                + child.node.main_margin_end(node.flex_direction).resolve(percent_calc_base_child).unwrap_or(0.0);
 
             if (child.node.flex_grow == 0.0 && child.node.flex_shrink == 0.0)
                 || (growing && child.flex_basis > child.hypothetical_inner_main_size)
@@ -556,8 +548,8 @@ fn compute_internal(
             .items
             .iter()
             .map(|child| {
-                child.node.main_margin_start(node.flex_direction).resolve(percent_calc_base_child, 0.0)
-                    + child.node.main_margin_end(node.flex_direction).resolve(percent_calc_base_child, 0.0)
+                child.node.main_margin_start(node.flex_direction).resolve(percent_calc_base_child).unwrap_or(0.0)
+                    + child.node.main_margin_end(node.flex_direction).resolve(percent_calc_base_child).unwrap_or(0.0)
                     + if child.frozen { child.target_main_size } else { child.flex_basis }
             })
             .sum();
@@ -593,8 +585,12 @@ fn compute_internal(
 
             let used_space: f32 = Iterator::chain(frozen.iter(), unfrozen.iter())
                 .map(|child| {
-                    child.node.main_margin_start(node.flex_direction).resolve(percent_calc_base_child, 0.0)
-                        + child.node.main_margin_end(node.flex_direction).resolve(percent_calc_base_child, 0.0)
+                    child.node.main_margin_start(node.flex_direction).resolve(percent_calc_base_child).unwrap_or(0.0)
+                        + child
+                            .node
+                            .main_margin_end(node.flex_direction)
+                            .resolve(percent_calc_base_child)
+                            .unwrap_or(0.0)
                         + if child.frozen { child.target_main_size } else { child.flex_basis }
                 })
                 .sum();
@@ -670,21 +666,22 @@ fn compute_internal(
                     )
                     .size
                     .width
-                    .min(child.node.width.resolve(percent_calc_base_child, f32::NAN))
-                    .max(child.node.min_width.resolve(percent_calc_base_child, f32::NAN))
+                    .maybe_min(child.node.width.resolve(percent_calc_base_child))
+                    .maybe_max(child.node.min_width.resolve(percent_calc_base_child))
                 } else {
-                    child.node.min_main_size(node.flex_direction).resolve(percent_calc_base_child, f32::NAN)
+                    child.node.min_main_size(node.flex_direction).resolve(percent_calc_base_child)
                 };
 
-                let max_main = child.node.max_main_size(node.flex_direction).resolve(percent_calc_base_child, f32::NAN);
+                let max_main = child.node.max_main_size(node.flex_direction).resolve(percent_calc_base_child);
 
-                let clamped = child.target_main_size.safe_min(max_main).safe_max(min_main).max(0.0);
+                let clamped =
+                    child.target_main_size.maybe_min(max_main).maybe_max(min_main).maybe_max(0.0).unwrap_or(0.0);
                 child.violation = clamped - child.target_main_size;
                 total_violation += child.violation;
                 child.target_main_size = clamped;
                 child.outer_target_main_size = child.target_main_size
-                    + child.node.main_margin_start(node.flex_direction).resolve(percent_calc_base_child, 0.0)
-                    + child.node.main_margin_end(node.flex_direction).resolve(percent_calc_base_child, 0.0);
+                    + child.node.main_margin_start(node.flex_direction).resolve(percent_calc_base_child).unwrap_or(0.0)
+                    + child.node.main_margin_end(node.flex_direction).resolve(percent_calc_base_child).unwrap_or(0.0);
             }
 
             // e. Freeze over-flexed items. The total violation is the sum of the adjustments
@@ -746,9 +743,10 @@ fn compute_internal(
             let child_cross = child
                 .node
                 .cross_size(node.flex_direction)
-                .resolve(percent_calc_base_child, f32::NAN)
-                .safe_max(child.node.min_cross_size(node.flex_direction).resolve(percent_calc_base_child, f32::NAN))
-                .safe_min(child.node.max_cross_size(node.flex_direction).resolve(percent_calc_base_child, f32::NAN));
+                .resolve(percent_calc_base_child)
+                .maybe_max(child.node.min_cross_size(node.flex_direction).resolve(percent_calc_base_child))
+                .maybe_min(child.node.max_cross_size(node.flex_direction).resolve(percent_calc_base_child))
+                .unwrap_or(f32::NAN);
 
             let size = compute_internal(
                 child.node,
@@ -762,13 +760,13 @@ fn compute_internal(
 
             child.hypothetical_inner_cross_size = size
                 .cross(node.flex_direction)
-                .safe_max(child.node.min_cross_size(node.flex_direction).resolve(percent_calc_base_child, f32::NAN))
-                .safe_min(child.node.max_cross_size(node.flex_direction).resolve(percent_calc_base_child, f32::NAN))
-                .max(0.0);
+                .maybe_max(child.node.min_cross_size(node.flex_direction).resolve(percent_calc_base_child))
+                .maybe_min(child.node.max_cross_size(node.flex_direction).resolve(percent_calc_base_child))
+                .unwrap_or(0.0);
 
             child.hypothetical_outer_cross_size = child.hypothetical_inner_cross_size
-                + child.node.cross_margin_start(node.flex_direction).resolve(percent_calc_base_child, 0.0)
-                + child.node.cross_margin_end(node.flex_direction).resolve(percent_calc_base_child, 0.0);
+                + child.node.cross_margin_start(node.flex_direction).resolve(percent_calc_base_child).unwrap_or(0.0)
+                + child.node.cross_margin_end(node.flex_direction).resolve(percent_calc_base_child).unwrap_or(0.0);
         }
     }
 
@@ -903,17 +901,22 @@ fn compute_internal(
                 && child.node.cross_size(node.flex_direction) == style::Dimension::Auto
             {
                 (line.cross_size
-                    - child.node.cross_margin_start(node.flex_direction).resolve(percent_calc_base_child, 0.0)
-                    - child.node.cross_margin_end(node.flex_direction).resolve(percent_calc_base_child, 0.0))
-                .safe_max(child.node.min_cross_size(node.flex_direction).resolve(percent_calc_base_child, f32::NAN))
-                .safe_min(child.node.max_cross_size(node.flex_direction).resolve(percent_calc_base_child, f32::NAN))
+                    - child
+                        .node
+                        .cross_margin_start(node.flex_direction)
+                        .resolve(percent_calc_base_child)
+                        .unwrap_or(0.0)
+                    - child.node.cross_margin_end(node.flex_direction).resolve(percent_calc_base_child).unwrap_or(0.0))
+                .maybe_max(child.node.min_cross_size(node.flex_direction).resolve(percent_calc_base_child))
+                .maybe_min(child.node.max_cross_size(node.flex_direction).resolve(percent_calc_base_child))
+                .unwrap_or(f32::NAN)
             } else {
                 child.hypothetical_inner_cross_size
             };
 
             child.outer_target_cross_size = child.target_cross_size
-                + child.node.cross_margin_start(node.flex_direction).resolve(percent_calc_base_child, 0.0)
-                + child.node.cross_margin_end(node.flex_direction).resolve(percent_calc_base_child, 0.0);
+                + child.node.cross_margin_start(node.flex_direction).resolve(percent_calc_base_child).unwrap_or(0.0)
+                + child.node.cross_margin_end(node.flex_direction).resolve(percent_calc_base_child).unwrap_or(0.0);
         }
     }
 
@@ -932,9 +935,9 @@ fn compute_internal(
         // TODO does not make a ton of sense to have this here but we want to have this somewhere.
         for child in &mut line.items {
             child.main_margin_start =
-                child.node.main_margin_start(node.flex_direction).resolve(percent_calc_base_child, 0.0);
+                child.node.main_margin_start(node.flex_direction).resolve(percent_calc_base_child).unwrap_or(0.0);
             child.main_margin_end =
-                child.node.main_margin_end(node.flex_direction).resolve(percent_calc_base_child, 0.0);
+                child.node.main_margin_end(node.flex_direction).resolve(percent_calc_base_child).unwrap_or(0.0);
         }
 
         let mut num_auto_margins = 0;
@@ -1031,9 +1034,9 @@ fn compute_internal(
         for child in &mut line.items {
             // TODO probably move this somewhere else, as with main margin resolution. Only auto margins should be resolved here.
             child.cross_margin_start =
-                child.node.cross_margin_start(node.flex_direction).resolve(percent_calc_base_child, 0.0);
+                child.node.cross_margin_start(node.flex_direction).resolve(percent_calc_base_child).unwrap_or(0.0);
             child.cross_margin_end =
-                child.node.cross_margin_end(node.flex_direction).resolve(percent_calc_base_child, 0.0);
+                child.node.cross_margin_end(node.flex_direction).resolve(percent_calc_base_child).unwrap_or(0.0);
 
             let free_space = line.cross_size - child.outer_target_cross_size;
 
@@ -1158,7 +1161,8 @@ fn compute_internal(
     }
 
     // Do a final layout pass and gather the resulting layouts
-    let percent_calc_base_child = if node.flex_direction.is_row() { container_main_size } else { container_cross_size };
+    let percent_calc_base_child =
+        Number::from_f32(if node.flex_direction.is_row() { container_main_size } else { container_cross_size });
 
     let mut children: Vec<layout::Node> = {
         let mut lines: Vec<Vec<layout::Node>> = vec![];
@@ -1185,8 +1189,16 @@ fn compute_internal(
                             total_offset_main
                                 + child.offset_main
                                 + child.main_margin_start
-                                + (child.node.main_start(node.flex_direction).resolve(percent_calc_base_child, 0.0)
-                                    - child.node.main_end(node.flex_direction).resolve(percent_calc_base_child, 0.0))
+                                + (child
+                                    .node
+                                    .main_start(node.flex_direction)
+                                    .resolve(percent_calc_base_child)
+                                    .unwrap_or(0.0)
+                                    - child
+                                        .node
+                                        .main_end(node.flex_direction)
+                                        .resolve(percent_calc_base_child)
+                                        .unwrap_or(0.0))
                         };
 
                         let offset_cross = {
@@ -1194,8 +1206,16 @@ fn compute_internal(
                                 + child.offset_cross
                                 + line_offset_cross
                                 + child.cross_margin_start
-                                + (child.node.cross_start(node.flex_direction).resolve(percent_calc_base_child, 0.0)
-                                    - child.node.cross_end(node.flex_direction).resolve(percent_calc_base_child, 0.0))
+                                + (child
+                                    .node
+                                    .cross_start(node.flex_direction)
+                                    .resolve(percent_calc_base_child)
+                                    .unwrap_or(0.0)
+                                    - child
+                                        .node
+                                        .cross_end(node.flex_direction)
+                                        .resolve(percent_calc_base_child)
+                                        .unwrap_or(0.0))
                         };
 
                         children.push(layout::Node {
@@ -1251,27 +1271,34 @@ fn compute_internal(
         node.children.iter().filter(|child| child.position == style::Position::Absolute).collect();
 
     for child in absolute_children {
-        let start =
-            child.start.resolve(container_width, f32::NAN) + child.margin.start.resolve(container_width, f32::NAN);
-        let end = child.end.resolve(container_width, f32::NAN) + child.margin.end.resolve(container_width, f32::NAN);
-        let top = child.top.resolve(container_height, f32::NAN) + child.margin.top.resolve(container_height, f32::NAN);
-        let bottom =
-            child.bottom.resolve(container_height, f32::NAN) + child.margin.bottom.resolve(container_height, f32::NAN);
+        let width_percent_calc = Number::from_f32(container_width);
+        let height_percent_calc = Number::from_f32(container_height);
+
+        let start = child.start.resolve(width_percent_calc).unwrap_or(f32::NAN)
+            + child.margin.start.resolve(width_percent_calc).unwrap_or(f32::NAN);
+        let end = child.end.resolve(width_percent_calc).unwrap_or(f32::NAN)
+            + child.margin.end.resolve(width_percent_calc).unwrap_or(f32::NAN);
+        let top = child.top.resolve(height_percent_calc).unwrap_or(f32::NAN)
+            + child.margin.top.resolve(height_percent_calc).unwrap_or(f32::NAN);
+        let bottom = child.bottom.resolve(height_percent_calc).unwrap_or(f32::NAN)
+            + child.margin.bottom.resolve(height_percent_calc).unwrap_or(f32::NAN);
 
         let (start_main, end_main) = if node.flex_direction.is_row() { (start, end) } else { (top, bottom) };
         let (start_cross, end_cross) = if node.flex_direction.is_row() { (top, bottom) } else { (start, end) };
 
         let child_width = child
             .width
-            .resolve(container_width, f32::NAN)
-            .safe_max(child.min_width.resolve(container_width, f32::NAN))
-            .safe_min(child.max_width.resolve(container_width, f32::NAN));
+            .resolve(width_percent_calc)
+            .maybe_max(child.min_width.resolve(width_percent_calc))
+            .maybe_min(child.max_width.resolve(width_percent_calc))
+            .unwrap_or(f32::NAN);
 
         let child_height = child
             .height
-            .resolve(container_height, f32::NAN)
-            .safe_max(child.min_height.resolve(container_height, f32::NAN))
-            .safe_min(child.max_height.resolve(container_height, f32::NAN));
+            .resolve(height_percent_calc)
+            .maybe_max(child.min_height.resolve(height_percent_calc))
+            .maybe_min(child.max_height.resolve(height_percent_calc))
+            .unwrap_or(f32::NAN);
 
         let width = if child_width.is_finite() { child_width } else { container_width - start - end };
 
@@ -1283,21 +1310,23 @@ fn compute_internal(
             height,
             if node.flex_direction.is_row() { container_main_size } else { container_cross_size },
             if node.flex_direction.is_row() { container_cross_size } else { container_main_size },
-            container_main_size,
+            width_percent_calc,
         );
 
         let free_main_space = container_main_size
             - result
                 .size
                 .main(node.flex_direction)
-                .max(child.min_main_size(node.flex_direction).resolve(percent_calc_base_child, f32::NAN))
-                .min(child.max_main_size(node.flex_direction).resolve(percent_calc_base_child, f32::NAN));
+                .maybe_max(child.min_main_size(node.flex_direction).resolve(percent_calc_base_child))
+                .maybe_min(child.max_main_size(node.flex_direction).resolve(percent_calc_base_child))
+                .unwrap_or(f32::NAN);
         let free_cross_space = container_cross_size
             - result
                 .size
                 .cross(node.flex_direction)
-                .max(child.min_cross_size(node.flex_direction).resolve(percent_calc_base_child, f32::NAN))
-                .min(child.max_cross_size(node.flex_direction).resolve(percent_calc_base_child, f32::NAN));
+                .maybe_max(child.min_cross_size(node.flex_direction).resolve(percent_calc_base_child))
+                .maybe_min(child.max_cross_size(node.flex_direction).resolve(percent_calc_base_child))
+                .unwrap_or(f32::NAN);
 
         let offset_main = if start_main.is_finite() {
             start_main + border_main_start
diff --git a/src/lib.rs b/src/lib.rs
index 8580b02d7..c14d15171 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,4 +1,5 @@
 pub mod layout;
+pub mod number;
 pub mod style;
 
 mod algo;
diff --git a/src/number.rs b/src/number.rs
new file mode 100644
index 000000000..df99196cc
--- /dev/null
+++ b/src/number.rs
@@ -0,0 +1,196 @@
+use std::ops;
+
+#[derive(Copy, Clone, PartialEq, Debug)]
+pub enum Number {
+    Defined(f32),
+    Undefined,
+}
+
+impl Default for Number {
+    fn default() -> Number {
+        Number::Undefined
+    }
+}
+
+impl Number {
+    pub fn unwrap_or(self, default: f32) -> f32 {
+        match self {
+            Number::Defined(val) => val,
+            Number::Undefined => default,
+        }
+    }
+
+    pub fn is_defined(self) -> bool {
+        match self {
+            Number::Defined(_) => true,
+            Number::Undefined => false,
+        }
+    }
+
+    pub fn from_f32(from: f32) -> Number {
+        if from.is_nan() {
+            Number::Undefined
+        } else {
+            Number::Defined(from)
+        }
+    }
+}
+
+pub trait MinMax<T> {
+    fn maybe_min(self, rhs: T) -> Number;
+    fn maybe_max(self, rhs: T) -> Number;
+}
+
+impl MinMax<Number> for Number {
+    fn maybe_min(self, rhs: Number) -> Number {
+        match self {
+            Number::Defined(val) => match rhs {
+                Number::Defined(other) => Number::Defined(val.min(other)),
+                Number::Undefined => self,
+            },
+            Number::Undefined => Number::Undefined,
+        }
+    }
+
+    fn maybe_max(self, rhs: Number) -> Number {
+        match self {
+            Number::Defined(val) => match rhs {
+                Number::Defined(other) => Number::Defined(val.max(other)),
+                Number::Undefined => self,
+            },
+            Number::Undefined => Number::Undefined,
+        }
+    }
+}
+
+impl MinMax<f32> for Number {
+    fn maybe_min(self, rhs: f32) -> Number {
+        match self {
+            Number::Defined(val) => Number::Defined(val.min(rhs)),
+            Number::Undefined => Number::Undefined,
+        }
+    }
+
+    fn maybe_max(self, rhs: f32) -> Number {
+        match self {
+            Number::Defined(val) => Number::Defined(val.max(rhs)),
+            Number::Undefined => Number::Undefined,
+        }
+    }
+}
+
+impl MinMax<Number> for f32 {
+    fn maybe_min(self, rhs: Number) -> Number {
+        match rhs {
+            Number::Defined(val) => Number::Defined(self.min(val)),
+            Number::Undefined => Number::Defined(self),
+        }
+    }
+
+    fn maybe_max(self, rhs: Number) -> Number {
+        match rhs {
+            Number::Defined(val) => Number::Defined(self.max(val)),
+            Number::Undefined => Number::Defined(self),
+        }
+    }
+}
+
+impl ops::Add<f32> for Number {
+    type Output = Number;
+
+    fn add(self, rhs: f32) -> Number {
+        match self {
+            Number::Defined(val) => Number::Defined(val + rhs),
+            Number::Undefined => Number::Undefined,
+        }
+    }
+}
+
+impl ops::Add<Number> for Number {
+    type Output = Number;
+
+    fn add(self, rhs: Number) -> Number {
+        match self {
+            Number::Defined(val) => match rhs {
+                Number::Defined(other) => Number::Defined(val + other),
+                Number::Undefined => self,
+            },
+            Number::Undefined => Number::Undefined,
+        }
+    }
+}
+
+impl ops::Sub<f32> for Number {
+    type Output = Number;
+
+    fn sub(self, rhs: f32) -> Number {
+        match self {
+            Number::Defined(val) => Number::Defined(val - rhs),
+            Number::Undefined => Number::Undefined,
+        }
+    }
+}
+
+impl ops::Sub<Number> for Number {
+    type Output = Number;
+
+    fn sub(self, rhs: Number) -> Number {
+        match self {
+            Number::Defined(val) => match rhs {
+                Number::Defined(other) => Number::Defined(val - other),
+                Number::Undefined => self,
+            },
+            Number::Undefined => Number::Undefined,
+        }
+    }
+}
+
+impl ops::Mul<f32> for Number {
+    type Output = Number;
+
+    fn mul(self, rhs: f32) -> Number {
+        match self {
+            Number::Defined(val) => Number::Defined(val * rhs),
+            Number::Undefined => Number::Undefined,
+        }
+    }
+}
+
+impl ops::Mul<Number> for Number {
+    type Output = Number;
+
+    fn mul(self, rhs: Number) -> Number {
+        match self {
+            Number::Defined(val) => match rhs {
+                Number::Defined(other) => Number::Defined(val * other),
+                Number::Undefined => self,
+            },
+            Number::Undefined => Number::Undefined,
+        }
+    }
+}
+
+impl ops::Div<f32> for Number {
+    type Output = Number;
+
+    fn div(self, rhs: f32) -> Number {
+        match self {
+            Number::Defined(val) => Number::Defined(val / rhs),
+            Number::Undefined => Number::Undefined,
+        }
+    }
+}
+
+impl ops::Div<Number> for Number {
+    type Output = Number;
+
+    fn div(self, rhs: Number) -> Number {
+        match self {
+            Number::Defined(val) => match rhs {
+                Number::Defined(other) => Number::Defined(val / other),
+                Number::Undefined => self,
+            },
+            Number::Undefined => Number::Undefined,
+        }
+    }
+}
diff --git a/src/style.rs b/src/style.rs
index 93d5b4ba8..de06eb6d0 100644
--- a/src/style.rs
+++ b/src/style.rs
@@ -1,3 +1,5 @@
+use crate::number::Number;
+
 #[derive(Copy, Clone, PartialEq, Debug)]
 pub enum AlignItems {
     FlexStart,
@@ -166,11 +168,11 @@ impl Default for Dimension {
 }
 
 impl Dimension {
-    pub(crate) fn resolve(&self, parent_width: f32, auto_size: f32) -> f32 {
+    pub(crate) fn resolve(&self, parent_width: Number) -> Number {
         match self {
-            Dimension::Points(points) => *points,
-            Dimension::Percent(percent) if parent_width.is_finite() => percent * parent_width,
-            _ => auto_size,
+            Dimension::Points(points) => Number::Defined(*points),
+            Dimension::Percent(percent) => parent_width * *percent,
+            _ => Number::Undefined,
         }
     }
 }
@@ -230,7 +232,7 @@ pub struct Node {
     pub min_height: Dimension,
     pub max_height: Dimension,
 
-    pub aspect_ratio: Option<f32>,
+    pub aspect_ratio: Number,
 
     pub children: Vec<Node>,
 }