gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 497HTML 254C 19JavaScript 81329 Definiciones

initial commit. Something works

bd78e4a192d77d56ec577a1e5a707be4f4cf08f7 · por Emil Sjölander · (hace 7 años) · ver el árbol en este commit

Delta semántico · 64

examples/basic.rs

src/algo.rs

src/layout.rs

  • nueva struct Edges pub
  • nueva struct Node pub

src/lib.rs

src/style.rs

tests/lib.rs

9 archivos · +1190 −0

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..693699042
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+/target
+**/*.rs.bk
+Cargo.lock
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 000000000..7ef034ff2
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "stretch"
+version = "0.1.0"
+authors = ["Emil Sjölander <emil@visly.app>"]
+
+[dependencies]
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 000000000..5c31aa9d1
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,22 @@
+MIT License
+
+Copyright (c) 2018 Visly Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
diff --git a/examples/basic.rs b/examples/basic.rs
new file mode 100644
index 000000000..7289695d7
--- /dev/null
+++ b/examples/basic.rs
@@ -0,0 +1,24 @@
+extern crate stretch;
+
+fn main() {
+    let node = stretch::style::Node {
+        width: stretch::style::Dimension::Points(100.0),
+        height: stretch::style::Dimension::Points(100.0),
+        justify_content: stretch::style::JustifyContent::Center,
+
+        children: vec![
+            stretch::style::Node {
+                width: stretch::style::Dimension::Percent(0.5),
+
+                ..Default::default()
+            },
+        ],
+
+        ..Default::default()
+    };
+
+    let layout = stretch::compute(&node);
+
+    println!("{:#?}", layout);
+}
+
diff --git a/src/algo.rs b/src/algo.rs
new file mode 100644
index 000000000..d5ad87481
--- /dev/null
+++ b/src/algo.rs
@@ -0,0 +1,704 @@
+use std::f32;
+
+use style;
+use layout;
+
+#[derive(Debug, Copy, Clone)]
+struct FlexSize {
+  main: f32,
+  cross: f32,
+}
+
+impl FlexSize {
+  fn max() -> FlexSize {
+    FlexSize {
+      main: f32::MAX,
+      cross: f32::MAX,
+    }
+  }
+
+  fn min() -> FlexSize {
+    FlexSize {
+      main: f32::MIN,
+      cross: f32::MIN,
+    }
+  }
+}
+
+struct FlexItem<'a> {
+  node: &'a style::Node,
+
+  // temporary values for main size determination
+  flex_basis: f32,
+  hypothetical_inner_main_size: f32,
+  hypothetical_outer_main_size: f32, 
+  target_main_size: f32,
+  violation: f32,
+  frozen: bool,
+
+  // temporary values for cross size determination
+  hypothetical_inner_cross_size: f32,
+  hypothetical_outer_cross_size: f32, 
+  target_cross_size: f32,
+
+  main_margin_start: f32,
+  main_margin_end: f32,
+  cross_margin_start: f32,
+  cross_margin_end: f32,
+
+  // temporary values for holding offset in the main / cross direction.
+  // offset is the relative position from the item's natural flow position based on
+  // relative position values, alignment, and justification. Does not unclude margin/padding/border.
+  offset_main: f32,
+  offset_cross: f32,
+}
+
+struct ComputeResult {
+  size: FlexSize,
+  children: Vec<layout::Node>,
+}
+
+struct FlexLine<'a> {
+  items: Vec<FlexItem<'a>>,
+  cross_size: f32,
+  offset_cross: f32,
+}
+
+pub fn compute(root: &style::Node) -> layout::Node {
+  let result = compute_internal(root, FlexSize::min(), FlexSize::max());
+
+  layout::Node {
+    width: if root.flex_direction.is_row() { result.size.main } else { result.size.cross },
+    height: if root.flex_direction.is_column() { result.size.main } else { result.size.cross },
+    x: 0.0,
+    y: 0.0,
+    children: result.children,
+  }
+}
+
+fn compute_internal(node: &style::Node, min: FlexSize, max: FlexSize) -> ComputeResult {
+  // 9.2. Line Length Determination
+
+  // 1. Generate anonymous flex items as described in §4 Flex Items.
+
+  let mut flex_items: Vec<FlexItem> = node.children.iter().map(|child| { 
+    FlexItem { 
+      node: child, 
+
+      flex_basis: 0.0, 
+      hypothetical_inner_main_size: 0.0,
+      hypothetical_outer_main_size: 0.0, 
+      target_main_size: 0.0,
+      violation: 0.0,
+      frozen: false,
+
+      hypothetical_inner_cross_size: 0.0,
+      hypothetical_outer_cross_size: 0.0, 
+      target_cross_size: 0.0,
+
+      main_margin_start: 0.0,
+      main_margin_end: 0.0,
+      cross_margin_start: 0.0,
+      cross_margin_end: 0.0,
+
+      offset_main: 0.0,
+      offset_cross: 0.0,
+    }
+  }).collect();
+
+  // 2. Determine the available main and cross space for the flex items. 
+  //    For each dimension, if that dimension of the flex container’s content box
+  //    is a definite size, use that; if that dimension of the flex container is 
+  //    being sized under a min or max-content constraint, the available space in 
+  //    that dimension is that constraint; otherwise, subtract the flex container’s 
+  //    margin, border, and padding from the space available to the flex container 
+  //    in that dimension and use that value. This might result in an infinite value.
+
+  let avaliable_main = node.main_size().resolve(max.main, {
+    max.main 
+        - node.main_margin_start().resolve(max.main, 0.0) 
+        - node.main_margin_end().resolve(max.main, 0.0) 
+        - node.main_padding_start().resolve(max.main, 0.0) 
+        - node.main_padding_end().resolve(max.main, 0.0) 
+        - node.main_border_start().resolve(max.main, 0.0)
+        - node.main_border_end().resolve(max.main, 0.0)
+  });
+
+  let avaliable_cross = node.cross_size().resolve(max.cross, {
+    max.cross 
+        - node.cross_margin_start().resolve(max.cross, 0.0) 
+        - node.cross_margin_end().resolve(max.cross, 0.0) 
+        - node.cross_padding_start().resolve(max.cross, 0.0) 
+        - node.cross_padding_end().resolve(max.cross, 0.0) 
+        - node.cross_border_start().resolve(max.cross, 0.0)
+        - node.cross_border_end().resolve(max.cross, 0.0)
+  });
+  
+  // 3. Determine the flex base size and hypothetical main size of each item:
+  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(avaliable_main, f32::INFINITY);
+    if flex_basis.is_finite() {
+      child.flex_basis = flex_basis;
+      break;
+    }
+
+    // B. If the flex item has an intrinsic aspect ratio,
+    //    a used flex basis of content, and a definite cross size,
+    //    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 {
+      let cross_size = node.cross_size().resolve(avaliable_cross, f32::INFINITY);
+
+      if cross_size.is_finite() && child.node.flex_basis == style::Dimension::Auto {
+        child.flex_basis = ratio * cross_size;
+        break;
+      }
+    }
+
+    // C. If the used flex basis is content or depends on its available space, 
+    //    and the flex container is being sized under a min-content or max-content 
+    //    constraint (e.g. when performing automatic table layout [CSS21]),
+    //    size the item under that constraint. The flex base size is the item’s 
+    //    resulting main size.
+
+    if min.main.is_finite() || max.main.is_finite() {
+      let size = compute_internal(child.node, min, max).size;
+      child.flex_basis = size.main;
+      break;
+    }
+
+    // D. Otherwise, if the used flex basis is content or depends on its
+    //    available space, the available main size is infinite, and the flex item’s
+    //    inline axis is parallel to the main axis, lay the item out using the rules
+    //    for a box in an orthogonal flow [CSS3-WRITING-MODES]. The flex base size
+    //    is the item’s max-content main size.
+
+    if avaliable_main.is_infinite() {
+      let size = compute_internal(child.node, FlexSize::min(), FlexSize::max()).size;
+      child.flex_basis = size.main;
+      break;
+    }
+
+    // E. Otherwise, size the item into the available space using its used flex basis
+    //    in place of its main size, treating a value of content as max-content. 
+    //    If a cross size is needed to determine the main size (e.g. when the
+    //    flex item’s main size is in its block axis) and the flex item’s cross size 
+    //    is auto and not definite, in this calculation use fit-content as the
+    //    flex item’s cross size. The flex base size is the item’s resulting main size.
+
+    let size = compute_internal(child.node, FlexSize::min(), FlexSize { main: avaliable_main, cross: avaliable_cross }).size;
+    child.flex_basis = size.main;
+  }
+
+  // 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 {
+    child.hypothetical_inner_main_size = child.flex_basis
+        .max(child.node.min_main_size().resolve(avaliable_main, f32::MIN))
+        .min(child.node.max_main_size().resolve(avaliable_main, f32::MAX))
+        .max(0.0);
+
+    child.hypothetical_outer_main_size = child.hypothetical_inner_main_size
+        + node.main_margin_start().resolve(avaliable_main, 0.0) 
+        + node.main_margin_end().resolve(avaliable_main, 0.0);
+  }
+
+  // 9.3. Main Size Determination
+
+  // 5. Collect flex items into flex lines:
+  //    - If the flex container is single-line, collect all the flex items into
+  //      a single flex line.
+  //    - Otherwise, starting from the first uncollected item, collect consecutive 
+  //      items one by one until the first time that the next collected item would 
+  //      not fit into the flex container’s inner main size (or until a forced break 
+  //      is encountered, see §10 Fragmenting Flex Layout). If the very first 
+  //      uncollected item wouldn’t fit, collect just it into the line.
+  //
+  //      For this step, the size of a flex item is its outer hypothetical main size. (Note: This can be negative.)
+  //      Repeat until all flex items have been collected into flex lines
+  //
+  //      Note that the "collect as many" line will collect zero-sized flex items onto
+  //      the end of the previous line even if the last non-zero item exactly "filled up" the line.
+
+  let mut flex_lines = {
+    let mut lines: Vec<FlexLine> = vec![];
+    let mut line_length = 0.0;
+
+    if node.wrap == style::Wrap::NoWrap {
+      lines.push(FlexLine { items: flex_items, cross_size: 0.0, offset_cross: 0.0 });
+    } else {
+      let mut line = FlexLine { items: vec![], cross_size: 0.0, offset_cross: 0.0 };
+
+      for child in flex_items {
+        line_length += child.hypothetical_outer_main_size;
+        line.items.push(child);
+        
+        if line_length > avaliable_main {
+          line_length = 0.0;
+          lines.push(line);
+          line = FlexLine { items: vec![], cross_size: 0.0, offset_cross: 0.0 };
+        }
+      }
+
+      lines.push(line);
+    }
+
+    lines
+  };
+
+  // 6. Resolve the flexible lengths of all the flex items to find their used main size.
+  //    See §9.7 Resolving Flexible Lengths.
+  //
+  // 9.7. Resolving Flexible Lengths
+
+  for line in &mut flex_lines {
+    // 1. Determine the used flex factor. Sum the outer hypothetical main sizes of all 
+    //    items on the line. If the sum is less than the flex container’s inner main size, 
+    //    use the flex grow factor for the rest of this algorithm; otherwise, use the
+    //    flex shrink factor.
+
+    let mut used_space = 0.0;
+    for child in line.items.iter() {
+      used_space += child.hypothetical_outer_main_size;
+    }
+
+    let growing = used_space < avaliable_main;
+    let shrinking = !growing;
+
+    // 2. Size inflexible items. Freeze, setting its target main size to its hypothetical main size
+    //    - Any item that has a flex factor of zero
+    //    - If using the flex grow factor: any item that has a flex base size
+    //      greater than its hypothetical main size
+    //    - If using the flex shrink factor: any item that has a flex base size
+    //      smaller than its hypothetical main size
+
+    for child in line.items.iter_mut() {
+      if (child.node.flex_grow == 0.0 && child.node.flex_shrink == 0.0) ||
+         (growing && child.flex_basis > child.hypothetical_inner_main_size) ||
+         (shrinking && child.flex_basis < child.hypothetical_inner_main_size) {
+        child.target_main_size = child.hypothetical_inner_main_size;
+        child.frozen = true;
+      }
+    }
+
+    // 3. Calculate initial free space. Sum the outer sizes of all items on the line,
+    //    and subtract this from the flex container’s inner main size. For frozen items,
+    //    use their outer target main size; for other items, use their outer flex base size.
+
+    let used_space: f32 = line.items.iter().map(|child| {
+      if child.frozen {
+        child.target_main_size
+      } else {
+        child.flex_basis
+      }
+    }).sum();
+
+    let initial_free_space = avaliable_main - used_space;
+
+    // 4. Loop
+
+    loop {
+      // a. Check for flexible items. If all the flex items on the line are frozen,
+      //    free space has been distributed; exit this loop.
+
+      let mut frozen: Vec<&mut FlexItem> = vec![];
+      let mut unfrozen: Vec<&mut FlexItem> = vec![];
+
+      for child in line.items.iter_mut() {
+        if child.frozen {
+          frozen.push(child);
+        } else {
+          unfrozen.push(child);
+        }
+      }
+
+      if unfrozen.len() == 0 {
+        break;
+      }
+
+      // b. Calculate the remaining free space as for initial free space, above.
+      //    If the sum of the unfrozen flex items’ flex factors is less than one,
+      //    multiply the initial free space by this sum. If the magnitude of this
+      //    value is less than the magnitude of the remaining free space, use this
+      //    as the remaining free space.
+
+      let used_space: f32 = Iterator::chain(frozen.iter(), unfrozen.iter()).map(|child| {
+        if child.frozen {
+          child.target_main_size
+        } else {
+          child.flex_basis
+        }
+      }).sum();
+
+      let sum_flex_grow: f32 = unfrozen.iter().map(|item| { item.node.flex_grow }).sum();
+      let sum_flex_shrink: f32 = unfrozen.iter().map(|item| { item.node.flex_shrink }).sum();
+
+      let free_space = if growing && sum_flex_grow < 1.0 {
+        (initial_free_space * sum_flex_grow).min(avaliable_main - used_space)
+      } else if shrinking && sum_flex_shrink < 1.0 {
+        (initial_free_space * sum_flex_shrink).max(avaliable_main - used_space)
+      } else {
+        avaliable_main - used_space
+      };
+
+      // c. Distribute free space proportional to the flex factors.
+      //    - If the remaining free space is zero
+      //        Do Nothing
+      //    - If using the flex grow factor
+      //        Find the ratio of the item’s flex grow factor to the sum of the
+      //        flex grow factors of all unfrozen items on the line. Set the item’s
+      //        target main size to its flex base size plus a fraction of the remaining
+      //        free space proportional to the ratio.
+      //    - If using the flex shrink factor
+      //        For every unfrozen item on the line, multiply its flex shrink factor by
+      //        its inner flex base size, and note this as its scaled flex shrink factor.
+      //        Find the ratio of the item’s scaled flex shrink factor to the sum of the
+      //        scaled flex shrink factors of all unfrozen items on the line. Set the item’s
+      //        target main size to its flex base size minus a fraction of the absolute value
+      //        of the remaining free space proportional to the ratio. Note this may result
+      //        in a negative inner main size; it will be corrected in the next step.
+      //    - Otherwise
+      //        Do Nothing
+
+      if free_space > 0.0 && growing {
+        for child in &mut unfrozen {
+          child.target_main_size = child.flex_basis + free_space * (child.node.flex_grow / sum_flex_grow);
+        }
+      } else if free_space < 0.0 && shrinking {
+        for child in &mut unfrozen {
+          child.target_main_size = child.flex_basis + free_space * (child.node.flex_shrink / sum_flex_shrink);
+        }
+      }
+
+      // d. Fix min/max violations. Clamp each non-frozen item’s target main size by its
+      //    used min and max main sizes and floor its content-box size at zero. If the
+      //    item’s target main size was made smaller by this, it’s a max violation.
+      //    If the item’s target main size was made larger by this, it’s a min violation.
+
+      let mut total_violation = 0.0;
+      for child in &mut unfrozen {
+        let max = child.node.max_main_size().resolve(avaliable_main, f32::MAX);
+        let min = child.node.min_main_size().resolve(avaliable_main, f32::MIN).max(0.0);
+
+        let clamped = if child.target_main_size > max {
+          max
+        } else if child.target_main_size < min {
+          min
+        } else {
+          child.target_main_size
+        };
+
+        child.violation = clamped - child.target_main_size;
+        total_violation += child.violation;
+        child.target_main_size = clamped;
+      }
+
+      // e. Freeze over-flexed items. The total violation is the sum of the adjustments
+      //    from the previous step ∑(clamped size - unclamped size). If the total violation is:
+      //    - Zero
+      //        Freeze all items.
+      //    - Positive
+      //        Freeze all the items with min violations.
+      //    - Negative
+      //        Freeze all the items with max violations.
+
+      if total_violation > 0.0 {
+        for child in &mut unfrozen {
+          child.frozen = child.violation > 0.0;
+        }
+      } else if total_violation < 0.0 {
+        for child in &mut unfrozen {
+          child.frozen = child.violation > 0.0;
+        }
+      } else {
+        for child in &mut unfrozen {
+          child.frozen = true;
+        }
+      }
+
+      // f. Return to the start of this loop.
+    }
+  }
+
+  // 9.4. Cross Size Determination
+
+  // 7. Determine the hypothetical cross size of each item by performing layout with the
+  //    used main size and the available space, treating auto as fit-content.
+
+  for line in &mut flex_lines {
+    for child in &mut line.items {
+      let size = compute_internal(
+        child.node, 
+        FlexSize { main: child.target_main_size, cross: min.cross }, 
+        FlexSize { main: child.target_main_size, cross: avaliable_cross },
+      ).size;
+
+      child.hypothetical_inner_cross_size = size.cross
+          .max(child.node.min_cross_size().resolve(avaliable_cross, f32::MIN))
+          .min(child.node.max_cross_size().resolve(avaliable_cross, f32::MAX))
+          .max(0.0);
+
+      child.hypothetical_outer_cross_size = child.hypothetical_inner_cross_size
+          + node.cross_margin_start().resolve(avaliable_cross, 0.0) 
+          + node.cross_margin_end().resolve(avaliable_cross, 0.0);
+    }
+  }
+
+  // 8. Calculate the cross size of each flex line.
+  //    If the flex container is single-line and has a definite cross size, the cross size
+  //    of the flex line is the flex container’s inner cross size. Otherwise, for each flex line:
+  //
+  //    1. Collect all the flex items whose inline-axis is parallel to the main-axis, whose
+  //       align-self is baseline, and whose cross-axis margins are both non-auto. Find the 
+  //       largest of the distances between each item’s baseline and its hypothetical outer
+  //       cross-start edge, and the largest of the distances between each item’s baseline
+  //       and its hypothetical outer cross-end edge, and sum these two values.
+  //    2. Among all the items not collected by the previous step, find the largest
+  //       outer hypothetical cross size.
+  //    3. The used cross-size of the flex line is the largest of the numbers found in the
+  //       previous two steps and zero.
+  //
+  //    If the flex container is single-line, then clamp the line’s cross-size to be within
+  //    the container’s computed min and max cross sizes. Note that if CSS 2.1’s definition
+  //    of min/max-width/height applied more generally, this behavior would fall out automatically.
+
+  if flex_lines.len() == 1 && avaliable_cross.is_finite() {
+    flex_lines[0].cross_size = avaliable_cross.min(max.cross).max(min.cross);
+  } else {
+    for line in &mut flex_lines {
+      // TODO handle baseline (1)
+
+      line.cross_size = line.items.iter().map(|child| {
+        child.hypothetical_outer_cross_size
+      }).fold(0.0, |acc, x| acc.max(x));
+    }
+  }
+
+  // 9. Handle 'align-content: stretch'. If the flex container has a definite cross size,
+  //    align-content is stretch, and the sum of the flex lines' cross sizes is less than
+  //    the flex container’s inner cross size, increase the cross size of each flex line
+  //    by equal amounts such that the sum of their cross sizes exactly equals the
+  //    flex container’s inner cross size.
+
+  if node.align_content == style::AlignContent::Stretch && avaliable_cross.is_finite() {
+    let total_cross: f32 = flex_lines.iter().map(|line| line.cross_size).sum();
+
+    if total_cross < avaliable_cross {
+      let remaining = avaliable_cross - total_cross;
+      let addition = remaining / flex_lines.len() as f32;
+
+      for line in &mut flex_lines {
+        line.cross_size += addition;
+      }
+    }
+  }
+
+  // 10. Collapse visibility:collapse items. If any flex items have visibility: collapse,
+  //     note the cross size of the line they’re in as the item’s strut size, and restart
+  //     layout from the beginning.
+  //
+  //     In this second layout round, when collecting items into lines, treat the collapsed
+  //     items as having zero main size. For the rest of the algorithm following that step,
+  //     ignore the collapsed items entirely (as if they were display:none) except that after
+  //     calculating the cross size of the lines, if any line’s cross size is less than the
+  //     largest strut size among all the collapsed items in the line, set its cross size to
+  //     that strut size.
+  //
+  //     Skip this step in the second layout round.
+
+  // TODO implement once (if ever) we support visibility:collapse
+
+  // 11. Determine the used cross size of each flex item. If a flex item has align-self: stretch,
+  //     its computed cross size property is auto, and neither of its cross-axis margins are auto,
+  //     the used outer cross size is the used cross size of its flex line, clamped according to
+  //     the item’s used min and max cross sizes. Otherwise, the used cross size is the item’s
+  //     hypothetical cross size.
+  //
+  //     If the flex item has align-self: stretch, redo layout for its contents, treating this
+  //     used size as its definite cross size so that percentage-sized children can be resolved.
+  //
+  //     Note that this step does not affect the main size of the flex item, even if it has an
+  //     intrinsic aspect ratio.
+
+  for line in &mut flex_lines {
+    for child in &mut line.items {
+      let is_stretch = child.node.align_self(node) == style::AlignSelf::Stretch;
+
+      if is_stretch && 
+         child.node.cross_margin_start() != style::Dimension::Auto && 
+         child.node.cross_margin_end() != style::Dimension::Auto &&
+         child.node.cross_size() == style::Dimension::Auto {
+        child.target_cross_size = line.cross_size
+            .min(child.node.max_cross_size().resolve(avaliable_cross, f32::MAX))
+            .max(child.node.min_cross_size().resolve(avaliable_cross, f32::MIN));
+      } else {
+        child.target_cross_size = child.hypothetical_inner_cross_size;
+      }
+
+      if is_stretch {
+        let size = FlexSize { main: child.target_main_size, cross: child.target_cross_size };
+        compute_internal(child.node, size, size);
+      }
+    }
+  }
+
+  // 9.5. Main-Axis Alignment
+
+  // 12. Distribute any remaining free space. For each flex line:
+  //     1. If the remaining free space is positive and at least one main-axis margin on this
+  //        line is auto, distribute the free space equally among these margins. Otherwise, 
+  //        set all auto margins to zero.
+  //     2. Align the items along the main-axis per justify-content.
+
+  for line in &mut flex_lines {
+    let used_space: f32 = line.items.iter().map(|child| child.target_main_size).sum();
+    let free_space = avaliable_main - used_space;
+
+    if free_space > 0.0 {
+      let mut num_auto_margins = 0;
+
+      for child in &mut line.items {
+        if child.node.main_margin_start() == style::Dimension::Auto {
+          num_auto_margins += 1;
+        }
+        if child.node.main_margin_end() == style::Dimension::Auto {
+          num_auto_margins += 1;
+        }
+      }
+
+      if num_auto_margins > 0 {
+        let margin = free_space / num_auto_margins as f32;
+        
+        for child in &mut line.items {
+          if child.node.main_margin_start() == style::Dimension::Auto {
+            child.main_margin_start = margin;
+          }
+          if child.node.main_margin_end() == style::Dimension::Auto {
+            child.main_margin_end = margin;
+          }
+        }
+      } else {
+        let num_items = line.items.len();
+        let mut is_first = true;
+
+        for child in &mut line.items {
+          child.main_margin_start = 0.0;
+          child.main_margin_end = 0.0;
+
+          child.offset_main = match node.justify_content {
+            style::JustifyContent::FlexStart => 0.0,
+            style::JustifyContent::Center => if is_first { free_space / 2.0 } else { 0.0 },
+            style::JustifyContent::FlexEnd => if is_first { free_space } else { 0.0 },
+            style::JustifyContent::SpaceBetween => if is_first { 0.0 } else { free_space / (num_items - 1) as f32 },
+            style::JustifyContent::SpaceAround => if is_first { (free_space / num_items as f32) / 2.0 } else { free_space / num_items as f32 },
+            style::JustifyContent::SpaceEvenly => free_space / (num_items + 1) as f32,
+          };
+
+          is_first = false;
+        }
+      }
+    }
+  }
+
+  // 9.6. Cross-Axis Alignment
+
+  // 13. Resolve cross-axis auto margins. If a flex item has auto cross-axis margins:
+  //     - If its outer cross size (treating those auto margins as zero) is less than the
+  //       cross size of its flex line, distribute the difference in those sizes equally
+  //       to the auto margins.
+  //     - Otherwise, if the block-start or inline-start margin (whichever is in the cross axis)
+  //       is auto, set it to zero. Set the opposite margin so that the outer cross size of the
+  //       item equals the cross size of its flex line.
+
+  for line in &mut flex_lines {
+    for child in &mut line.items {
+      if child.target_cross_size < line.cross_size {
+        let free_space = line.cross_size - child.target_cross_size;
+
+        if child.node.cross_margin_start() == style::Dimension::Auto && child.node.cross_margin_end() == style::Dimension::Auto {
+          child.cross_margin_start = free_space / 2.0;
+          child.cross_margin_end = free_space / 2.0;
+        } else if child.node.cross_margin_start() == style::Dimension::Auto {
+          child.cross_margin_start = free_space;
+        } else if child.node.cross_margin_end() == style::Dimension::Auto {
+          child.cross_margin_end = free_space;
+        } else {
+          child.cross_margin_start = child.node.cross_margin_start().resolve(line.cross_size, 0.0);
+          child.cross_margin_end = child.node.cross_margin_end().resolve(line.cross_size, 0.0);
+
+          // 14. Align all flex items along the cross-axis per align-self, if neither of the item’s
+          //     cross-axis margins are auto.
+
+          child.offset_cross = match child.node.align_self(node) {
+            style::AlignSelf::Auto => 0.0, // Should never happen
+            style::AlignSelf::FlexStart => 0.0,
+            style::AlignSelf::FlexEnd => free_space, 
+            style::AlignSelf::Center => free_space / 2.0, 
+            style::AlignSelf::Baseline => free_space / 2.0, // Treat as center for now until we have baseline support
+            style::AlignSelf::Stretch => 0.0,
+          };
+        }
+      }
+    }
+  }
+
+  // 15. Determine the flex container’s used cross size:
+  //     - If the cross size property is a definite size, use that, clamped by the used
+  //       min and max cross sizes of the flex container.
+  //     - Otherwise, use the sum of the flex lines' cross sizes, clamped by the used
+  //       min and max cross sizes of the flex container.
+
+  let total_cross_size = flex_lines.iter().map(|line| line.cross_size).sum();
+  let container_cross_size = if avaliable_cross.is_finite() {
+    avaliable_cross
+  } else {
+    total_cross_size
+  }.min(max.cross).max(min.cross);;
+
+  // 16. Align all flex lines per align-content.
+
+  let free_space = container_cross_size - total_cross_size;
+  let num_lines = flex_lines.len();
+  let mut is_first = true;
+
+  for line in &mut flex_lines {
+    line.offset_cross = match node.align_content {
+      style::AlignContent::FlexStart => 0.0,
+      style::AlignContent::FlexEnd => if is_first { free_space } else { 0.0 },
+      style::AlignContent::Center => if is_first { free_space / 2.0 } else { 0.0 },
+      style::AlignContent::Stretch => 0.0,
+      style::AlignContent::SpaceBetween => if is_first { 0.0 } else { free_space / (num_lines - 1) as f32 },
+      style::AlignContent::SpaceAround => if is_first { (free_space / num_lines as f32) / 2.0 } else { free_space / num_lines as f32 },
+    };
+    is_first = false;
+  }
+
+  let mut children: Vec<layout::Node> = vec![];
+  for line in &mut flex_lines {
+    for child in &mut line.items {
+      let size = FlexSize { main: avaliable_main, cross: container_cross_size };
+      let result = compute_internal(child.node, size, size);
+
+      // TODO incorporate margins, paddings, borders, and sublings
+      children.push(layout::Node {
+        width: if child.node.flex_direction.is_row() { result.size.main } else { result.size.cross },
+        height: if child.node.flex_direction.is_column() { result.size.main } else { result.size.cross },
+        x: if child.node.flex_direction.is_row() { child.offset_main } else { child.offset_cross + line.offset_cross },
+        y: if child.node.flex_direction.is_column() { child.offset_main } else { child.offset_cross + line.offset_cross },
+        children: result.children,
+      });
+    }
+  }
+
+  ComputeResult {
+    size: FlexSize {
+      main: avaliable_main,
+      cross: container_cross_size,
+    },
+    children: children,
+  }
+}
diff --git a/src/layout.rs b/src/layout.rs
new file mode 100644
index 000000000..c9906be2c
--- /dev/null
+++ b/src/layout.rs
@@ -0,0 +1,16 @@
+#[derive(Debug)]
+pub struct Edges {
+  pub left: f32,
+  pub right: f32,
+  pub top: f32,
+  pub bottom: f32,
+}
+
+#[derive(Debug)]
+pub struct Node {
+  pub width: f32,
+  pub height: f32,
+  pub x: f32,
+  pub y: f32,
+  pub children: Vec<Node>,
+}
\ No newline at end of file
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 000000000..87fba59fa
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,5 @@
+pub mod style;
+pub mod layout;
+
+mod algo;
+pub use algo::compute;
\ No newline at end of file
diff --git a/src/style.rs b/src/style.rs
new file mode 100644
index 000000000..64a6553b4
--- /dev/null
+++ b/src/style.rs
@@ -0,0 +1,396 @@
+#[derive(Copy, Clone, PartialEq, Debug)]
+pub enum AlignItems {
+  FlexStart,
+  FlexEnd,
+  Center,
+  Baseline,
+  Stretch,
+}
+
+impl Default for AlignItems {
+  fn default() -> AlignItems { AlignItems::Stretch }
+}
+
+#[derive(Copy, Clone, PartialEq, Debug)]
+pub enum AlignSelf {
+  Auto,
+  FlexStart,
+  FlexEnd,
+  Center,
+  Baseline,
+  Stretch,
+}
+
+impl Default for AlignSelf {
+  fn default() -> AlignSelf { AlignSelf::Auto }
+}
+
+#[derive(Copy, Clone, PartialEq, Debug)]
+pub enum AlignContent {
+  FlexStart,
+  FlexEnd,
+  Center,
+  Stretch,
+  SpaceBetween,
+  SpaceAround,
+}
+
+impl Default for AlignContent {
+  fn default() -> AlignContent { AlignContent::Stretch }
+}
+
+#[derive(Copy, Clone, PartialEq, Debug)]
+pub enum Direction {
+  Inherit,
+  LTR,
+  RTL,
+}
+
+impl Default for Direction {
+  fn default() -> Direction { Direction::Inherit }
+}
+
+#[derive(Copy, Clone, PartialEq, Debug)]
+pub enum Display {
+  Flex,
+  None,
+}
+
+impl Default for Display {
+  fn default() -> Display { Display::Flex }
+}
+
+#[derive(Copy, Clone, PartialEq, Debug)]
+pub enum FlexDirection {
+  Row,
+  Column,
+  RowReverse,
+  ColumnReverse,
+}
+
+impl Default for FlexDirection {
+  fn default() -> FlexDirection { FlexDirection::Row }
+}
+
+impl FlexDirection {
+  pub(crate) fn is_row(self) -> bool {
+    self == FlexDirection::Row || self == FlexDirection::RowReverse
+  }
+
+  pub(crate) fn is_column(self) -> bool {
+    self == FlexDirection::Column || self == FlexDirection::ColumnReverse
+  }
+}
+
+#[derive(Copy, Clone, PartialEq, Debug)]
+pub enum JustifyContent {
+  FlexStart,
+  Center,
+  FlexEnd,
+  SpaceBetween,
+  SpaceAround,
+  SpaceEvenly,
+}
+
+impl Default for JustifyContent {
+  fn default() -> JustifyContent { JustifyContent::FlexStart }
+}
+
+#[derive(Copy, Clone, PartialEq, Debug)]
+pub enum Overflow {
+  Visible,
+  Hidden,
+  Scroll,
+}
+
+impl Default for Overflow {
+  fn default() -> Overflow { Overflow::Visible }
+}
+
+#[derive(Copy, Clone, PartialEq, Debug)]
+pub enum Position {
+  Relative,
+  Absolute,
+}
+
+impl Default for Position {
+  fn default() -> Position { Position::Relative }
+}
+
+#[derive(Copy, Clone, PartialEq, Debug)]
+pub enum Wrap {
+  NoWrap,
+  Wrap,
+  WrapReverse,
+}
+
+impl Default for Wrap {
+  fn default() -> Wrap { Wrap::NoWrap }
+}
+
+#[derive(Copy, Clone, PartialEq, Debug)]
+pub enum Dimension {
+  Auto,
+  Points(f32),
+  Percent(f32),
+}
+
+impl Default for Dimension {
+  fn default() -> Dimension { Dimension::Auto }
+}
+
+impl Dimension {
+  pub(crate) fn resolve(&self, parent_size: f32, auto_size: f32) -> f32 {
+    match self {
+      Dimension::Points(points) => *points,
+      Dimension::Percent(percent) => percent * parent_size,
+      Dimension::Auto => auto_size,
+    }
+  }
+}
+
+#[derive(Debug)]
+pub struct Edges {
+  pub start: Dimension,
+  pub end: Dimension,
+  pub top: Dimension,
+  pub bottom: Dimension,
+}
+
+impl Default for Edges {
+  fn default() -> Edges {
+    Edges {
+      start: Dimension::Points(0.0),
+      end: Dimension::Points(0.0),
+      top: Dimension::Points(0.0),
+      bottom: Dimension::Points(0.0),
+    }
+  }
+}
+
+#[derive(Debug)]
+pub struct Node {
+  pub position: Position,
+  pub direction: Direction,
+  pub flex_direction: FlexDirection,
+  
+  pub wrap: Wrap,
+  pub overflow: Overflow,
+
+  pub align_items: AlignItems,
+  pub align_self: AlignSelf,
+  pub align_content: AlignContent,
+  
+  pub justify_content: JustifyContent,
+
+  pub margin: Edges,
+  pub padding: Edges,
+  pub border: Edges,
+
+  pub flex_grow: f32,
+  pub flex_shrink: f32,
+  pub flex_basis: Dimension,
+
+  pub left: Dimension,
+  pub right: Dimension,
+  pub top: Dimension,
+  pub bottom: Dimension,
+
+  pub width: Dimension,
+  pub min_width: Dimension,
+  pub max_width: Dimension,
+
+  pub height: Dimension,
+  pub min_height: Dimension,
+  pub max_height: Dimension,
+
+  pub aspect_ratio: Option<f32>,
+
+  pub children: Vec<Node>,
+}
+
+impl Default for Node {
+  fn default() -> Node {
+    Node {
+      position: Default::default(),
+      direction: Default::default(),
+      flex_direction: Default::default(),
+
+      wrap: Default::default(),
+      overflow: Default::default(),
+
+      align_items: Default::default(),
+      align_self: Default::default(),
+      align_content: Default::default(),
+
+      justify_content: Default::default(),
+
+      margin: Default::default(),
+      padding: Default::default(),
+      border: Default::default(),
+
+      flex_grow: 0.0,
+      flex_shrink: 1.0,
+      flex_basis: Default::default(),
+
+      left: Default::default(),
+      right: Default::default(),
+      top: Default::default(),
+      bottom: Default::default(),
+
+      width: Default::default(),
+      min_width: Default::default(),
+      max_width: Default::default(),
+
+      height: Default::default(),
+      min_height: Default::default(),
+      max_height: Default::default(),
+
+      aspect_ratio: Default::default(),
+
+      children: vec![],
+    }
+  }
+}
+
+impl Node {
+  pub(crate) fn main_size(&self) -> Dimension {
+    match self.flex_direction {
+      FlexDirection::Row | FlexDirection::RowReverse => self.width,
+      FlexDirection::Column | FlexDirection::ColumnReverse => self.height,
+    }
+  }
+
+  pub(crate) fn min_main_size(&self) -> Dimension {
+    match self.flex_direction {
+      FlexDirection::Row | FlexDirection::RowReverse => self.min_width,
+      FlexDirection::Column | FlexDirection::ColumnReverse => self.min_height,
+    }
+  }
+
+  pub(crate) fn max_main_size(&self) -> Dimension {
+    match self.flex_direction {
+      FlexDirection::Row | FlexDirection::RowReverse => self.max_width,
+      FlexDirection::Column | FlexDirection::ColumnReverse => self.max_height,
+    }
+  }
+
+  pub(crate) fn main_margin_start(&self) -> Dimension {
+    match self.flex_direction {
+      FlexDirection::Row | FlexDirection::RowReverse => self.margin.start,
+      FlexDirection::Column | FlexDirection::ColumnReverse => self.margin.top,
+    }
+  }
+
+  pub(crate) fn main_margin_end(&self) -> Dimension {
+    match self.flex_direction {
+      FlexDirection::Row | FlexDirection::RowReverse => self.margin.end,
+      FlexDirection::Column | FlexDirection::ColumnReverse => self.margin.bottom,
+    }
+  }
+
+  pub(crate) fn main_padding_start(&self) -> Dimension {
+    match self.flex_direction {
+      FlexDirection::Row | FlexDirection::RowReverse => self.padding.start,
+      FlexDirection::Column | FlexDirection::ColumnReverse => self.padding.top,
+    }
+  }
+
+  pub(crate) fn main_padding_end(&self) -> Dimension {
+    match self.flex_direction {
+      FlexDirection::Row | FlexDirection::RowReverse => self.padding.end,
+      FlexDirection::Column | FlexDirection::ColumnReverse => self.padding.bottom,
+    }
+  }
+
+  pub(crate) fn main_border_start(&self) -> Dimension {
+    match self.flex_direction {
+      FlexDirection::Row | FlexDirection::RowReverse => self.border.start,
+      FlexDirection::Column | FlexDirection::ColumnReverse => self.border.top,
+    }
+  }
+
+  pub(crate) fn main_border_end(&self) -> Dimension {
+    match self.flex_direction {
+      FlexDirection::Row | FlexDirection::RowReverse => self.border.end,
+      FlexDirection::Column | FlexDirection::ColumnReverse => self.border.bottom,
+    }
+  }
+
+  pub(crate) fn cross_size(&self) -> Dimension {
+    match self.flex_direction {
+      FlexDirection::Row | FlexDirection::RowReverse => self.height,
+      FlexDirection::Column | FlexDirection::ColumnReverse => self.width,
+    }
+  }
+
+  pub(crate) fn min_cross_size(&self) -> Dimension {
+    match self.flex_direction {
+      FlexDirection::Row | FlexDirection::RowReverse => self.min_height,
+      FlexDirection::Column | FlexDirection::ColumnReverse => self.min_width,
+    }
+  }
+
+  pub(crate) fn max_cross_size(&self) -> Dimension {
+    match self.flex_direction {
+      FlexDirection::Row | FlexDirection::RowReverse => self.max_height,
+      FlexDirection::Column | FlexDirection::ColumnReverse => self.max_width,
+    }
+  }
+
+  pub(crate) fn cross_margin_start(&self) -> Dimension {
+    match self.flex_direction {
+      FlexDirection::Row | FlexDirection::RowReverse => self.margin.top,
+      FlexDirection::Column | FlexDirection::ColumnReverse => self.margin.start,
+    }
+  }
+
+  pub(crate) fn cross_margin_end(&self) -> Dimension {
+    match self.flex_direction {
+      FlexDirection::Row | FlexDirection::RowReverse => self.margin.bottom,
+      FlexDirection::Column | FlexDirection::ColumnReverse => self.margin.end,
+    }
+  }
+
+  pub(crate) fn cross_padding_start(&self) -> Dimension {
+    match self.flex_direction {
+      FlexDirection::Row | FlexDirection::RowReverse => self.padding.top,
+      FlexDirection::Column | FlexDirection::ColumnReverse => self.padding.start,
+    }
+  }
+
+  pub(crate) fn cross_padding_end(&self) -> Dimension {
+    match self.flex_direction {
+      FlexDirection::Row | FlexDirection::RowReverse => self.padding.bottom,
+      FlexDirection::Column | FlexDirection::ColumnReverse => self.padding.end,
+    }
+  }
+
+  pub(crate) fn cross_border_start(&self) -> Dimension {
+    match self.flex_direction {
+      FlexDirection::Row | FlexDirection::RowReverse => self.border.top,
+      FlexDirection::Column | FlexDirection::ColumnReverse => self.border.start,
+    }
+  }
+
+  pub(crate) fn cross_border_end(&self) -> Dimension {
+    match self.flex_direction {
+      FlexDirection::Row | FlexDirection::RowReverse => self.border.bottom,
+      FlexDirection::Column | FlexDirection::ColumnReverse => self.border.end,
+    }
+  }
+
+  pub(crate) fn align_self(&self, parent: &Node) -> AlignSelf {
+    if self.align_self == AlignSelf::Auto {
+      match parent.align_items {
+        AlignItems::FlexStart => AlignSelf::FlexStart,
+        AlignItems::FlexEnd => AlignSelf::FlexEnd,
+        AlignItems::Center => AlignSelf::Center,
+        AlignItems::Baseline => AlignSelf::Baseline,
+        AlignItems::Stretch => AlignSelf::Stretch,
+      }
+    } else {
+      self.align_self
+    }
+  }
+}
diff --git a/tests/lib.rs b/tests/lib.rs
new file mode 100644
index 000000000..4f0490ff5
--- /dev/null
+++ b/tests/lib.rs
@@ -0,0 +1,14 @@
+#[cfg(test)]
+mod tests {
+    #[test]
+    fn defined_width_height() {
+        let layout = stretch::compute(&stretch::style::Node {
+            width: stretch::style::Dimension::Points(100.0),
+            height: stretch::style::Dimension::Points(100.0),
+            ..Default::default()
+        });
+
+        assert_eq!(layout.width, 100.0);
+        assert_eq!(layout.height, 100.0);
+    }
+}