gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 1061HTML 997Markdown 12TOML 83120 Definitions

implement `direction` property

diff is adopted from #736 originally written by @tjamaan

230ab9359bba75fdda0ff35319d082e79189046f · by Kane Wang · (9 months ago) · parent 25759dad4 · browse the tree at this commit

Semantic delta · 45

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/mod.rs

src/style/alignment.rs

src/style/mod.rs

src/tree/layout.rs

src/tree/traits.rs

11 files · +271 −52

diff --git a/src/compute/block.rs b/src/compute/block.rs
index b18c134b3..9b7680bc8 100644
--- a/src/compute/block.rs
+++ b/src/compute/block.rs
@@ -10,7 +10,8 @@ use crate::util::sys::Vec;
 use crate::util::MaybeMath;
 use crate::util::{MaybeResolve, ResolveOrZero};
 use crate::{
-    BlockContainerStyle, BlockItemStyle, BoxGenerationMode, BoxSizing, LayoutBlockContainer, RequestedAxis, TextAlign,
+    BlockContainerStyle, BlockItemStyle, BoxGenerationMode, BoxSizing, Direction, LayoutBlockContainer, RequestedAxis,
+    TextAlign,
 };
 
 #[cfg(feature = "float_layout")]
@@ -206,6 +207,8 @@ struct BlockItem {
     #[cfg(feature = "float_layout")]
     /// The `clear` style of the node
     clear: Clear,
+    /// Direction (LTR or RTL)
+    direction: Direction,
 
     /// The base size of this item
     size: Size<Option<f32>>,
@@ -476,7 +479,10 @@ fn compute_inner(
     let len = tree.child_count(node_id);
     for order in 0..len {
         let child = tree.get_child_id(node_id, order);
-        if tree.get_block_child_style(child).box_generation_mode() == BoxGenerationMode::None {
+        let child_style = tree.get_block_child_style(child);
+        if child_style.box_generation_mode() == BoxGenerationMode::None {
+            let direction = child_style.direction();
+            drop(child_style);
             tree.set_unrounded_layout(child, &Layout::with_order(order as u32));
             tree.perform_child_layout(
                 child,
@@ -484,6 +490,7 @@ fn compute_inner(
                 Size::NONE,
                 Size::MAX_CONTENT,
                 SizingMode::InherentSize,
+                direction,
                 Line::FALSE,
             );
         }
@@ -566,6 +573,8 @@ fn generate_item_list(
                 float,
                 #[cfg(feature = "float_layout")]
                 clear: child_style.clear(),
+
+                direction: child_style.direction(),
                 size: child_style
                     .size()
                     .maybe_resolve(node_inner_size, |val, basis| tree.calc(val, basis))
@@ -625,6 +634,7 @@ fn determine_content_based_container_width(
                 Size::NONE,
                 available_space.map_width(|w| w.maybe_sub(item_x_margin_sum)),
                 SizingMode::InherentSize,
+                item.direction,
                 Line::TRUE,
             );
 
@@ -721,6 +731,7 @@ fn perform_final_layout_on_in_flow_children(
                     // available_space,
                     Size::MAX_CONTENT,
                     SizingMode::InherentSize,
+                    item.direction,
                     Line::TRUE,
                 );
                 let margin_box = item_layout.size + item_non_auto_margin.sum_axes();
@@ -829,6 +840,7 @@ fn perform_final_layout_on_in_flow_children(
                 axis: RequestedAxis::Both,
                 known_dimensions,
                 parent_size,
+                direction: item.direction,
                 available_space: available_space.map_width(|_| AvailableSpace::Definite(stretch_width)),
                 vertical_margins_are_collapsible: if item.is_in_same_bfc { Line::TRUE } else { Line::FALSE },
             };
@@ -864,6 +876,7 @@ fn perform_final_layout_on_in_flow_children(
             } else {
                 tree.compute_child_layout(item.node_id, inputs)
             };
+
             let final_size = item_layout.size;
 
             let top_margin_set = item_layout.top_margin.collapse_with_margin(item_margin.top.unwrap_or(0.0));
@@ -1104,6 +1117,7 @@ fn perform_absolute_layout_on_absolute_children(
                 height: AvailableSpace::Definite(area_height.maybe_clamp(min_size.height, max_size.height)),
             },
             SizingMode::ContentSize,
+            item.direction,
             Line::FALSE,
         );
 
@@ -1118,6 +1132,7 @@ fn perform_absolute_layout_on_absolute_children(
                 height: AvailableSpace::Definite(area_height.maybe_clamp(min_size.height, max_size.height)),
             },
             SizingMode::ContentSize,
+            item.direction,
             Line::FALSE,
         );
 
diff --git a/src/compute/flexbox.rs b/src/compute/flexbox.rs
index 2cf25ae4b..aa0e044f7 100644
--- a/src/compute/flexbox.rs
+++ b/src/compute/flexbox.rs
@@ -13,7 +13,7 @@ use crate::util::debug::debug_log;
 use crate::util::sys::{f32_max, new_vec_with_capacity, Vec};
 use crate::util::MaybeMath;
 use crate::util::{MaybeResolve, ResolveOrZero};
-use crate::{BoxGenerationMode, BoxSizing};
+use crate::{BoxGenerationMode, BoxSizing, Direction};
 
 use super::common::alignment::apply_alignment_fallback;
 #[cfg(feature = "content_size")]
@@ -27,6 +27,9 @@ struct FlexItem {
     /// The order of the node relative to it's siblings
     order: u32,
 
+    /// Direction (LTR or RTL)
+    direction: Direction,
+
     /// The base size of this item
     size: Size<Option<f32>>,
     /// The minimum allowable size of this item
@@ -118,6 +121,8 @@ struct FlexLine<'a> {
 struct AlgoConstants {
     /// The direction of the current segment being laid out
     dir: FlexDirection,
+    /// The layout direction of the current segment being laid out
+    layout_direction: Direction,
     /// Is this segment a row
     is_row: bool,
     /// Is this segment a column
@@ -376,7 +381,10 @@ fn compute_preliminary(tree: &mut impl LayoutFlexboxContainer, node: NodeId, inp
     let len = tree.child_count(node);
     for order in 0..len {
         let child = tree.get_child_id(node, order);
-        if tree.get_flexbox_child_style(child).box_generation_mode() == BoxGenerationMode::None {
+        let child_style = tree.get_flexbox_child_style(child);
+        if child_style.box_generation_mode() == BoxGenerationMode::None {
+            let direction = child_style.direction();
+            drop(child_style);
             tree.set_unrounded_layout(child, &Layout::with_order(order as u32));
             tree.perform_child_layout(
                 child,
@@ -384,6 +392,7 @@ fn compute_preliminary(tree: &mut impl LayoutFlexboxContainer, node: NodeId, inp
                 Size::NONE,
                 Size::MAX_CONTENT,
                 SizingMode::InherentSize,
+                direction,
                 Line::FALSE,
             );
         }
@@ -421,6 +430,7 @@ fn compute_constants(
     parent_size: Size<Option<f32>>,
 ) -> AlgoConstants {
     let dir = style.flex_direction();
+    let layout_direction = style.direction();
     let is_row = dir.is_row();
     let is_column = dir.is_column();
     let is_wrap = matches!(style.flex_wrap(), FlexWrap::Wrap | FlexWrap::WrapReverse);
@@ -436,7 +446,13 @@ fn compute_constants(
 
     let align_items = style.align_items().unwrap_or(AlignItems::Stretch);
     let align_content = style.align_content().unwrap_or(AlignContent::Stretch);
-    let justify_content = style.justify_content();
+    let mut justify_content = style.justify_content();
+
+    if layout_direction.is_rtl() {
+        if let Some(justify_content) = justify_content.as_mut() {
+            justify_content.flip();
+        }
+    }
 
     // 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
@@ -445,11 +461,15 @@ fn compute_constants(
         Overflow::Scroll => style.scrollbar_width(),
         _ => 0.0,
     });
-    // TODO: make side configurable based on the `direction` property
+
     let mut content_box_inset = padding + border;
-    content_box_inset.right += scrollbar_gutter.x;
     content_box_inset.bottom += scrollbar_gutter.y;
 
+    match layout_direction {
+        Direction::Ltr => content_box_inset.right += scrollbar_gutter.x,
+        Direction::Rtl => content_box_inset.left += scrollbar_gutter.x,
+    };
+
     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()), |val, basis| tree.calc(val, basis));
@@ -459,6 +479,7 @@ fn compute_constants(
 
     AlgoConstants {
         dir,
+        layout_direction,
         is_row,
         is_column,
         is_wrap,
@@ -518,6 +539,7 @@ fn generate_anonymous_flex_items(
             FlexItem {
                 node: child,
                 order: index as u32,
+                direction: child_style.direction(),
                 size: child_style
                     .size()
                     .maybe_resolve(constants.node_inner_size, |val, basis| tree.calc(val, basis))
@@ -764,6 +786,7 @@ fn determine_flex_base_size(
                 child_available_space,
                 SizingMode::ContentSize,
                 dir.main_axis(),
+                child.direction,
                 Line::FALSE,
             );
         };
@@ -807,6 +830,7 @@ fn determine_flex_base_size(
                     child_available_space,
                     SizingMode::ContentSize,
                     dir.main_axis(),
+                    child.direction,
                     Line::FALSE,
                 )
             };
@@ -1062,6 +1086,7 @@ fn determine_container_main_size(
                                     child_available_space,
                                     SizingMode::InherentSize,
                                     dir.main_axis(),
+                                    item.direction,
                                     Line::FALSE,
                                 ) + item.margin.main_axis_sum(constants.dir);
 
@@ -1397,6 +1422,7 @@ fn determine_hypothetical_cross_size(
                 },
                 SizingMode::ContentSize,
                 constants.dir.cross_axis(),
+                child.direction,
                 Line::FALSE,
             )
             .maybe_clamp(child.min_size.cross(constants.dir), child.max_size.cross(constants.dir))
@@ -1467,6 +1493,7 @@ fn calculate_children_base_lines(
                     },
                 },
                 SizingMode::ContentSize,
+                child.direction,
                 Line::FALSE,
             );
 
@@ -1771,10 +1798,12 @@ fn align_flex_items_along_cross_axis(
     max_baseline: f32,
     constants: &AlgoConstants,
 ) -> f32 {
+    let cross_axis_should_reverse = constants.is_column && matches!(constants.layout_direction, Direction::Rtl);
+
     match child.align_self {
         AlignSelf::Start => 0.0,
         AlignSelf::FlexStart => {
-            if constants.is_wrap_reverse {
+            if constants.is_wrap_reverse ^ cross_axis_should_reverse {
                 free_space
             } else {
                 0.0
@@ -1782,7 +1811,7 @@ fn align_flex_items_along_cross_axis(
         }
         AlignSelf::End => free_space,
         AlignSelf::FlexEnd => {
-            if constants.is_wrap_reverse {
+            if constants.is_wrap_reverse ^ cross_axis_should_reverse {
                 0.0
             } else {
                 free_space
@@ -1795,7 +1824,7 @@ fn align_flex_items_along_cross_axis(
             } else {
                 // Until we support vertical writing modes, baseline alignment only makes sense if
                 // the constants.direction is row, so we treat it as flex-start alignment in columns.
-                if constants.is_wrap_reverse {
+                if constants.is_wrap_reverse ^ cross_axis_should_reverse {
                     free_space
                 } else {
                     0.0
@@ -1803,7 +1832,7 @@ fn align_flex_items_along_cross_axis(
             }
         }
         AlignSelf::Stretch => {
-            if constants.is_wrap_reverse {
+            if constants.is_wrap_reverse ^ cross_axis_should_reverse {
                 free_space
             } else {
                 0.0
@@ -1887,6 +1916,7 @@ fn calculate_flex_item(
     container_size: Size<f32>,
     node_inner_size: Size<Option<f32>>,
     direction: FlexDirection,
+    layout_direction: Direction,
 ) {
     let layout_output = tree.perform_child_layout(
         item.node,
@@ -1894,6 +1924,7 @@ fn calculate_flex_item(
         node_inner_size,
         container_size.map(|s| s.into()),
         SizingMode::ContentSize,
+        item.direction,
         Line::FALSE,
     );
     let LayoutOutput {
@@ -1903,16 +1934,31 @@ fn calculate_flex_item(
         ..
     } = layout_output;
 
-    let offset_main = *total_offset_main
-        + item.offset_main
-        + item.margin.main_start(direction)
-        + (item.inset.main_start(direction).or(item.inset.main_end(direction).map(|pos| -pos)).unwrap_or(0.0));
+    let offset_main = if direction.is_row() && layout_direction.is_rtl() {
+        *total_offset_main
+            - item.offset_main
+            - item.margin.main_end(direction)
+            - (item.inset.main_end(direction).or(item.inset.main_start(direction).map(|pos| -pos)).unwrap_or(0.0))
+    } else {
+        *total_offset_main
+            + item.offset_main
+            + item.margin.main_start(direction)
+            + (item.inset.main_start(direction).or(item.inset.main_end(direction).map(|pos| -pos)).unwrap_or(0.0))
+    };
 
-    let offset_cross = total_offset_cross
-        + item.offset_cross
-        + line_offset_cross
-        + item.margin.cross_start(direction)
-        + (item.inset.cross_start(direction).or(item.inset.cross_end(direction).map(|pos| -pos)).unwrap_or(0.0));
+    let offset_cross = if direction.is_column() && layout_direction.is_rtl() {
+        total_offset_cross
+            - item.offset_cross
+            - line_offset_cross
+            - item.margin.cross_end(direction)
+            - (item.inset.cross_end(direction).or(item.inset.cross_start(direction).map(|pos| -pos)).unwrap_or(0.0))
+    } else {
+        total_offset_cross
+            + item.offset_cross
+            + line_offset_cross
+            + item.margin.cross_start(direction)
+            + (item.inset.cross_start(direction).or(item.inset.cross_end(direction).map(|pos| -pos)).unwrap_or(0.0))
+    };
 
     if direction.is_row() {
         let baseline_offset_cross = total_offset_cross + item.offset_cross + item.margin.cross_start(direction);
@@ -1924,14 +1970,22 @@ fn calculate_flex_item(
         item.baseline = baseline_offset_main + inner_baseline;
     }
 
-    let location = match direction.is_row() {
-        true => Point { x: offset_main, y: offset_cross },
-        false => Point { x: offset_cross, y: offset_main },
-    };
     let scrollbar_size = Size {
         width: if item.overflow.y == Overflow::Scroll { item.scrollbar_width } else { 0.0 },
         height: if item.overflow.x == Overflow::Scroll { item.scrollbar_width } else { 0.0 },
     };
+    let location = if direction.is_row() {
+        Point {
+            x: if layout_direction.is_rtl() {
+                container_size.width - (offset_main + size.width) + scrollbar_size.width
+            } else {
+                offset_main
+            },
+            y: offset_cross,
+        }
+    } else {
+        Point { x: offset_cross, y: offset_main }
+    };
 
     tree.set_unrounded_layout(
         item.node,
@@ -1968,10 +2022,20 @@ fn calculate_layout_line(
     node_inner_size: Size<Option<f32>>,
     padding_border: Rect<f32>,
     direction: FlexDirection,
+    layout_direction: Direction,
 ) {
-    let mut total_offset_main = padding_border.main_start(direction);
+    let mut total_offset_main = if layout_direction.is_rtl() && direction.is_row() {
+        padding_border.main_end(direction)
+    } else {
+        padding_border.main_start(direction)
+    };
     let line_offset_cross = line.offset_cross;
 
+    let is_rtl_column = layout_direction.is_rtl() && direction.is_column();
+    if is_rtl_column {
+        *total_offset_cross -= line_offset_cross + line.cross_size;
+    }
+
     if direction.is_reverse() {
         for item in line.items.iter_mut().rev() {
             calculate_flex_item(
@@ -1985,6 +2049,7 @@ fn calculate_layout_line(
                 container_size,
                 node_inner_size,
                 direction,
+                layout_direction,
             );
         }
     } else {
@@ -2000,11 +2065,14 @@ fn calculate_layout_line(
                 container_size,
                 node_inner_size,
                 direction,
+                layout_direction,
             );
         }
     }
 
-    *total_offset_cross += line_offset_cross + line.cross_size;
+    if !is_rtl_column {
+        *total_offset_cross += line_offset_cross + line.cross_size;
+    }
 }
 
 /// Do a final layout pass and collect the resulting layouts.
@@ -2014,7 +2082,11 @@ fn final_layout_pass(
     flex_lines: &mut [FlexLine],
     constants: &AlgoConstants,
 ) -> Size<f32> {
-    let mut total_offset_cross = constants.content_box_inset.cross_start(constants.dir);
+    let mut total_offset_cross = if constants.is_column && constants.layout_direction.is_rtl() {
+        constants.container_size.width - constants.content_box_inset.cross_end(constants.dir)
+    } else {
+        constants.content_box_inset.cross_start(constants.dir)
+    };
 
     #[cfg_attr(not(feature = "content_size"), allow(unused_mut))]
     let mut content_size = Size::ZERO;
@@ -2031,6 +2103,7 @@ fn final_layout_pass(
                 constants.node_inner_size,
                 constants.content_box_inset,
                 constants.dir,
+                constants.layout_direction,
             );
         }
     } else {
@@ -2045,6 +2118,7 @@ fn final_layout_pass(
                 constants.node_inner_size,
                 constants.content_box_inset,
                 constants.dir,
+                constants.layout_direction,
             );
         }
     }
@@ -2124,6 +2198,7 @@ fn perform_absolute_layout_on_absolute_children(
             .maybe_apply_aspect_ratio(aspect_ratio)
             .maybe_add(box_sizing_adjustment);
         let mut known_dimensions = style_size.maybe_clamp(min_size, max_size);
+        let direction = child_style.direction();
 
         drop(child_style);
 
@@ -2154,6 +2229,7 @@ fn perform_absolute_layout_on_absolute_children(
                 height: AvailableSpace::Definite(container_height.maybe_clamp(min_size.height, max_size.height)),
             },
             SizingMode::InherentSize,
+            direction,
             Line::FALSE,
         );
         let final_size = known_dimensions.unwrap_or(measured_size).maybe_clamp(min_size, max_size);
@@ -2167,6 +2243,7 @@ fn perform_absolute_layout_on_absolute_children(
                 height: AvailableSpace::Definite(container_height.maybe_clamp(min_size.height, max_size.height)),
             },
             SizingMode::InherentSize,
+            direction,
             Line::FALSE,
         );
 
diff --git a/src/compute/grid/alignment.rs b/src/compute/grid/alignment.rs
index 9ec256adb..09db9d32d 100644
--- a/src/compute/grid/alignment.rs
+++ b/src/compute/grid/alignment.rs
@@ -198,6 +198,7 @@ pub(super) fn align_and_position_item(
     let Size { width, height } = Size { width, height }.maybe_clamp(min_size, max_size);
 
     // Layout node
+    let direction = style.direction();
     drop(style);
 
     let size = if position == Position::Absolute && (width.is_none() || height.is_none()) {
@@ -207,6 +208,7 @@ pub(super) fn align_and_position_item(
             grid_area_size.map(Option::Some),
             grid_area_minus_item_margins_size.map(AvailableSpace::Definite),
             SizingMode::InherentSize,
+            direction,
             Line::FALSE,
         )
         .map(Some)
@@ -220,6 +222,7 @@ pub(super) fn align_and_position_item(
         grid_area_size.map(Option::Some),
         grid_area_minus_item_margins_size.map(AvailableSpace::Definite),
         SizingMode::InherentSize,
+        direction,
         Line::FALSE,
     );
 
diff --git a/src/compute/grid/mod.rs b/src/compute/grid/mod.rs
index a9a3ab089..47f4bc6c0 100644
--- a/src/compute/grid/mod.rs
+++ b/src/compute/grid/mod.rs
@@ -9,8 +9,8 @@ use crate::util::sys::{f32_max, GridTrackVec, Vec};
 use crate::util::MaybeMath;
 use crate::util::{MaybeResolve, ResolveOrZero};
 use crate::{
-    style_helpers::*, AlignContent, BoxGenerationMode, BoxSizing, CoreStyle, GridContainerStyle, GridItemStyle,
-    JustifyContent, LayoutGridContainer,
+    style_helpers::*, AlignContent, BoxGenerationMode, BoxSizing, CoreStyle, Direction, GridContainerStyle,
+    GridItemStyle, JustifyContent, LayoutGridContainer,
 };
 use alignment::{align_and_position_item, align_tracks};
 use explicit_grid::{compute_explicit_grid_size_in_axis, initialize_grid_tracks, AutoRepeatStrategy};
@@ -45,7 +45,7 @@ pub fn compute_grid_layout<Tree: LayoutGridContainer>(
     node: NodeId,
     inputs: LayoutInput,
 ) -> LayoutOutput {
-    let LayoutInput { known_dimensions, parent_size, available_space, run_mode, .. } = inputs;
+    let LayoutInput { known_dimensions, parent_size, available_space, run_mode, direction, .. } = inputs;
 
     let style = tree.get_grid_container_style(node);
 
@@ -86,15 +86,26 @@ pub fn compute_grid_layout<Tree: LayoutGridContainer>(
         Overflow::Scroll => style.scrollbar_width(),
         _ => 0.0,
     });
-    // TODO: make side configurable based on the `direction` property
     let mut content_box_inset = padding_border;
-    content_box_inset.right += scrollbar_gutter.x;
     content_box_inset.bottom += scrollbar_gutter.y;
 
+    match direction {
+        Direction::Ltr => content_box_inset.right += scrollbar_gutter.x,
+        Direction::Rtl => content_box_inset.left += scrollbar_gutter.x,
+    };
+
     let align_content = style.align_content().unwrap_or(AlignContent::Stretch);
-    let justify_content = style.justify_content().unwrap_or(JustifyContent::Stretch);
+    let mut justify_content = style.justify_content().unwrap_or(JustifyContent::Stretch);
     let align_items = style.align_items();
-    let justify_items = style.justify_items();
+    let mut justify_items = style.justify_items();
+
+    if direction.is_rtl() {
+        justify_content.flip();
+
+        if let Some(justify_items) = justify_items.as_mut() {
+            justify_items.flip();
+        }
+    }
 
     // Note: we avoid accessing the grid rows/columns methods more than once as this can
     // cause an expensive-ish computation
@@ -501,11 +512,22 @@ pub fn compute_grid_layout<Tree: LayoutGridContainer>(
 
     // Position in-flow children (stored in items vector)
     for (index, item) in items.iter_mut().enumerate() {
+        let (left, right) = match item.direction {
+            Direction::Ltr => (
+                columns[item.column_indexes.start as usize + 1].offset,
+                columns[item.column_indexes.end as usize].offset,
+            ),
+            Direction::Rtl => (
+                container_border_box.width - columns[item.column_indexes.end as usize].offset,
+                container_border_box.width - columns[item.column_indexes.start as usize + 1].offset,
+            ),
+        };
+
         let grid_area = Rect {
             top: rows[item.row_indexes.start as usize + 1].offset,
             bottom: rows[item.row_indexes.end as usize].offset,
-            left: columns[item.column_indexes.start as usize + 1].offset,
-            right: columns[item.column_indexes.end as usize].offset,
+            left,
+            right,
         };
         #[cfg_attr(not(feature = "content_size"), allow(unused_variables))]
         let (content_size_contribution, y_position, height) = align_and_position_item(
@@ -533,6 +555,7 @@ pub fn compute_grid_layout<Tree: LayoutGridContainer>(
 
         // Position hidden child
         if child_style.box_generation_mode() == BoxGenerationMode::None {
+            let direction = child_style.direction();
             drop(child_style);
             tree.set_unrounded_layout(child, &Layout::with_order(order));
             tree.perform_child_layout(
@@ -541,6 +564,7 @@ pub fn compute_grid_layout<Tree: LayoutGridContainer>(
                 Size::NONE,
                 Size::MAX_CONTENT,
                 SizingMode::InherentSize,
+                direction,
                 Line::FALSE,
             );
             order += 1;
@@ -568,17 +592,30 @@ pub fn compute_grid_layout<Tree: LayoutGridContainer>(
                     maybe_grid_line.and_then(|line: OriginZeroLine| line.try_into_track_vec_index(final_row_counts))
                 });
 
+            let (left, right) = match direction {
+                Direction::Ltr => (
+                    maybe_col_indexes.start.map(|index| columns[index].offset).unwrap_or(border.left),
+                    maybe_col_indexes
+                        .end
+                        .map(|index| columns[index].offset)
+                        .unwrap_or(container_border_box.width - border.right - scrollbar_gutter.x),
+                ),
+                Direction::Rtl => (
+                    maybe_col_indexes
+                        .start
+                        .map(|index| columns[index].offset + border.left + scrollbar_gutter.x)
+                        .unwrap_or(border.left),
+                    maybe_col_indexes.end.map(|index| columns[index].offset).unwrap_or(container_border_box.width),
+                ),
+            };
             let grid_area = Rect {
                 top: maybe_row_indexes.start.map(|index| rows[index].offset).unwrap_or(border.top),
                 bottom: maybe_row_indexes
                     .end
                     .map(|index| rows[index].offset)
                     .unwrap_or(container_border_box.height - border.bottom - scrollbar_gutter.y),
-                left: maybe_col_indexes.start.map(|index| columns[index].offset).unwrap_or(border.left),
-                right: maybe_col_indexes
-                    .end
-                    .map(|index| columns[index].offset)
-                    .unwrap_or(container_border_box.width - border.right - scrollbar_gutter.x),
+                left,
+                right,
             };
             drop(child_style);
 
diff --git a/src/compute/grid/track_sizing.rs b/src/compute/grid/track_sizing.rs
index 2773cefd2..804390890 100644
--- a/src/compute/grid/track_sizing.rs
+++ b/src/compute/grid/track_sizing.rs
@@ -495,6 +495,7 @@ fn resolve_item_baselines(
                 inner_node_size,
                 Size::MIN_CONTENT,
                 SizingMode::InherentSize,
+                item.direction,
                 Line::FALSE,
             );
 
diff --git a/src/compute/grid/types/grid_item.rs b/src/compute/grid/types/grid_item.rs
index 5746303c4..a94962acc 100644
--- a/src/compute/grid/types/grid_item.rs
+++ b/src/compute/grid/types/grid_item.rs
@@ -6,7 +6,7 @@ use crate::geometry::{Line, Point, Rect, Size};
 use crate::style::{AlignItems, AlignSelf, AvailableSpace, Dimension, LengthPercentageAuto, Overflow};
 use crate::tree::{LayoutPartialTree, LayoutPartialTreeExt, NodeId, SizingMode};
 use crate::util::{MaybeMath, MaybeResolve, ResolveOrZero};
-use crate::{BoxSizing, GridItemStyle, LengthPercentage};
+use crate::{BoxSizing, Direction, GridItemStyle, LengthPercentage};
 use core::ops::Range;
 
 /// Represents a single grid item
@@ -20,6 +20,8 @@ pub(in super::super) struct GridItem {
     /// We sort the list of grid items during track sizing. This field allows us to sort back the original order
     /// for final positioning
     pub source_order: u16,
+    /// The item's direction
+    pub direction: Direction,
 
     /// The item's definite row-start and row-end, as resolved by the placement algorithm
     /// (in origin-zero coordinates)
@@ -105,6 +107,7 @@ impl GridItem {
         GridItem {
             node,
             source_order,
+            direction: style.direction(),
             row: row_span,
             column: col_span,
             is_compressible_replaced: style.is_compressible_replaced(),
@@ -389,6 +392,7 @@ impl GridItem {
             }),
             SizingMode::InherentSize,
             axis.as_abs_naive(),
+            self.direction,
             Line::FALSE,
         )
     }
@@ -428,6 +432,7 @@ impl GridItem {
             }),
             SizingMode::InherentSize,
             axis.as_abs_naive(),
+            self.direction,
             Line::FALSE,
         )
     }
diff --git a/src/compute/mod.rs b/src/compute/mod.rs
index 9a13a2ee5..7a3e630fe 100644
--- a/src/compute/mod.rs
+++ b/src/compute/mod.rs
@@ -120,16 +120,6 @@ pub fn compute_root_layout(tree: &mut impl LayoutPartialTree, root: NodeId, avai
         }
     }
 
-    // Recursively compute node layout
-    let output = tree.perform_child_layout(
-        root,
-        known_dimensions,
-        available_space.into_options(),
-        available_space,
-        SizingMode::InherentSize,
-        Line::FALSE,
-    );
-
     let style = tree.get_core_container_style(root);
     let padding =
         style.padding().resolve_or_zero(available_space.width.into_option(), |val, basis| tree.calc(val, basis));
@@ -141,8 +131,20 @@ pub fn compute_root_layout(tree: &mut impl LayoutPartialTree, root: NodeId, avai
         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 },
     };
+    let direction = style.direction();
     drop(style);
 
+    // Recursively compute node layout
+    let output = tree.perform_child_layout(
+        root,
+        known_dimensions,
+        available_space.into_options(),
+        available_space,
+        SizingMode::InherentSize,
+        direction,
+        Line::FALSE,
+    );
+
     tree.set_unrounded_layout(
         root,
         &Layout {
diff --git a/src/style/alignment.rs b/src/style/alignment.rs
index f15e18cb9..d27440295 100644
--- a/src/style/alignment.rs
+++ b/src/style/alignment.rs
@@ -29,6 +29,21 @@ pub enum AlignItems {
     /// Stretch to fill the container
     Stretch,
 }
+
+impl AlignItems {
+    /// Flips the alignment value for the given direction
+    #[inline]
+    pub fn flip(&mut self) {
+        *self = match self {
+            Self::Start => Self::End,
+            Self::End => Self::Start,
+            Self::FlexStart => Self::FlexEnd,
+            Self::FlexEnd => Self::FlexStart,
+            _ => return,
+        }
+    }
+}
+
 /// Used to control how child nodes are aligned.
 /// Does not apply to Flexbox, and will be ignored if specified on a flex container
 /// For Grid it controls alignment in the inline axis
@@ -95,3 +110,17 @@ pub enum AlignContent {
 ///
 /// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content)
 pub type JustifyContent = AlignContent;
+
+impl AlignContent {
+    /// Flips the alignment value for the given direction
+    #[inline]
+    pub fn flip(&mut self) {
+        *self = match self {
+            Self::Start => Self::End,
+            Self::End => Self::Start,
+            Self::FlexStart => Self::FlexEnd,
+            Self::FlexEnd => Self::FlexStart,
+            _ => return,
+        }
+    }
+}
diff --git a/src/style/mod.rs b/src/style/mod.rs
index fe60e0411..aff44cde5 100644
--- a/src/style/mod.rs
+++ b/src/style/mod.rs
@@ -102,6 +102,12 @@ pub trait CoreStyle {
         BoxSizing::BorderBox
     }
 
+    /// The direction of text, table and grid columns, and horizontal overflow.
+    #[inline(always)]
+    fn direction(&self) -> Direction {
+        Direction::Ltr
+    }
+
     // Overflow properties
     /// How children overflowing their container should affect layout
     #[inline(always)]
@@ -347,6 +353,26 @@ impl Overflow {
     }
 }
 
+/// Sets the direction of text, table and grid columns, and horizontal overflow.
+/// <https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/direction>
+#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+pub enum Direction {
+    #[default]
+    /// Left-to-right
+    Ltr,
+    /// Right-to-left
+    Rtl,
+}
+
+impl Direction {
+    /// Returns true if the direction is right-to-left
+    #[inline]
+    pub(crate) fn is_rtl(&self) -> bool {
+        matches!(self, Direction::Rtl)
+    }
+}
+
 /// A typed representation of the CSS style information for a single node.
 ///
 /// The most important idea in flexbox is the notion of a "main" and "cross" axis, which are always perpendicular to each other.
@@ -378,6 +404,8 @@ pub struct Style<S: CheapCloneStr = DefaultCheapStr> {
     pub item_is_replaced: bool,
     /// Should size styles apply to the content box or the border box of the node
     pub box_sizing: BoxSizing,
+    /// Sets the direction of text, table and grid columns, and horizontal overflow.
+    pub direction: Direction,
 
     // Overflow properties
     /// How children overflowing their container should affect layout
@@ -524,6 +552,7 @@ impl<S: CheapCloneStr> Style<S> {
         item_is_table: false,
         item_is_replaced: false,
         box_sizing: BoxSizing::BorderBox,
+        direction: Direction::Ltr,
         overflow: Point { x: Overflow::Visible, y: Overflow::Visible },
         scrollbar_width: 0.0,
         #[cfg(feature = "float_layout")]
@@ -622,6 +651,10 @@ impl<S: CheapCloneStr> CoreStyle for Style<S> {
         self.box_sizing
     }
     #[inline(always)]
+    fn direction(&self) -> Direction {
+        self.direction
+    }
+    #[inline(always)]
     fn overflow(&self) -> Point<Overflow> {
         self.overflow
     }
@@ -687,6 +720,10 @@ impl<T: CoreStyle> CoreStyle for &'_ T {
         (*self).box_sizing()
     }
     #[inline(always)]
+    fn direction(&self) -> Direction {
+        (*self).direction()
+    }
+    #[inline(always)]
     fn overflow(&self) -> Point<Overflow> {
         (*self).overflow()
     }
@@ -1126,6 +1163,7 @@ mod tests {
             float: Default::default(),
             #[cfg(feature = "float_layout")]
             clear: Default::default(),
+            direction: Default::default(),
             overflow: Default::default(),
             scrollbar_width: 0.0,
             position: Default::default(),
diff --git a/src/tree/layout.rs b/src/tree/layout.rs
index 304953708..2d7d4ca43 100644
--- a/src/tree/layout.rs
+++ b/src/tree/layout.rs
@@ -3,6 +3,7 @@ use crate::geometry::{AbsoluteAxis, Line, Point, Rect, Size};
 use crate::style::AvailableSpace;
 use crate::style_helpers::TaffyMaxContent;
 use crate::util::sys::{f32_max, f32_min};
+use crate::Direction;
 
 /// Whether we are performing a full layout, or we merely need to size the node
 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
@@ -115,6 +116,8 @@ pub struct LayoutInput {
     pub sizing_mode: SizingMode,
     /// Which axis we need the size of
     pub axis: RequestedAxis,
+    /// The direction of layout
+    pub direction: Direction,
 
     /// Known dimensions represent dimensions (width/height) which should be taken as fixed when performing layout.
     /// For example, if known_dimensions.width is set to Some(WIDTH) then this means something like:
@@ -141,6 +144,7 @@ impl LayoutInput {
         // The important property for hidden layout
         run_mode: RunMode::PerformHiddenLayout,
         // The rest will be ignored
+        direction: Direction::Ltr,
         known_dimensions: Size::NONE,
         parent_size: Size::NONE,
         available_space: Size::MAX_CONTENT,
diff --git a/src/tree/traits.rs b/src/tree/traits.rs
index 194110ef5..0b03efda5 100644
--- a/src/tree/traits.rs
+++ b/src/tree/traits.rs
@@ -136,6 +136,7 @@ use crate::style::{FlexboxContainerStyle, FlexboxItemStyle};
 #[cfg(feature = "grid")]
 use crate::style::{GridContainerStyle, GridItemStyle};
 use crate::CheapCloneStr;
+use crate::Direction;
 #[cfg(feature = "block_layout")]
 use crate::{BlockContainerStyle, BlockContext, BlockItemStyle};
 
@@ -343,6 +344,7 @@ pub(crate) trait LayoutPartialTreeExt: LayoutPartialTree {
         available_space: Size<AvailableSpace>,
         sizing_mode: SizingMode,
         axis: AbsoluteAxis,
+        direction: Direction,
         vertical_margins_are_collapsible: Line<bool>,
     ) -> f32 {
         self.compute_child_layout(
@@ -353,6 +355,7 @@ pub(crate) trait LayoutPartialTreeExt: LayoutPartialTree {
                 available_space,
                 sizing_mode,
                 axis: axis.into(),
+                direction,
                 run_mode: RunMode::ComputeSize,
                 vertical_margins_are_collapsible,
             },
@@ -371,6 +374,7 @@ pub(crate) trait LayoutPartialTreeExt: LayoutPartialTree {
         parent_size: Size<Option<f32>>,
         available_space: Size<AvailableSpace>,
         sizing_mode: SizingMode,
+        direction: Direction,
         vertical_margins_are_collapsible: Line<bool>,
     ) -> Size<f32> {
         self.compute_child_layout(
@@ -381,6 +385,7 @@ pub(crate) trait LayoutPartialTreeExt: LayoutPartialTree {
                 available_space,
                 sizing_mode,
                 axis: RequestedAxis::Both,
+                direction,
                 run_mode: RunMode::ComputeSize,
                 vertical_margins_are_collapsible,
             },
@@ -390,6 +395,7 @@ pub(crate) trait LayoutPartialTreeExt: LayoutPartialTree {
 
     /// Perform a full layout on the node given the specified constraints
     #[inline(always)]
+    #[allow(clippy::too_many_arguments)]
     fn perform_child_layout(
         &mut self,
         node_id: NodeId,
@@ -397,6 +403,7 @@ pub(crate) trait LayoutPartialTreeExt: LayoutPartialTree {
         parent_size: Size<Option<f32>>,
         available_space: Size<AvailableSpace>,
         sizing_mode: SizingMode,
+        direction: Direction,
         vertical_margins_are_collapsible: Line<bool>,
     ) -> LayoutOutput {
         self.compute_child_layout(
@@ -407,6 +414,7 @@ pub(crate) trait LayoutPartialTreeExt: LayoutPartialTree {
                 available_space,
                 sizing_mode,
                 axis: RequestedAxis::Both,
+                direction,
                 run_mode: RunMode::PerformLayout,
                 vertical_margins_are_collapsible,
             },