From ecadaba03e64d6076d2a9b5f19406e2131a8ce47 Mon Sep 17 00:00:00 2001 From: ldm0 Date: Tue, 8 Sep 2026 04:55:02 +0800 Subject: [PATCH] fix(layout): model flex static-position edges --- moli-layout/src/positioned.rs | 366 +++++++++++++++++- moli-layout/src/taffy_tree.rs | 278 +++++++++---- moli-renderer-v8/src/runtime/phase_one/mod.rs | 106 +++++ 3 files changed, 676 insertions(+), 74 deletions(-) diff --git a/moli-layout/src/positioned.rs b/moli-layout/src/positioned.rs index 8ebeabe17..ee94f7ed1 100644 --- a/moli-layout/src/positioned.rs +++ b/moli-layout/src/positioned.rs @@ -1,4 +1,275 @@ -use taffy::Line; +use taffy::{ + AbsoluteAxis, AlignContent, AlignContentKeyword, AlignItems, AlignItemsKeyword, AlignSelf, + AlignmentSafety, Direction, FlexWrap, Line, Point, Rect, Size, WritingMode, +}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum LogicalStaticEdge { + Start, + Center, + End, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum HorizontalStaticEdge { + Left, + Center, + Right, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum VerticalStaticEdge { + Top, + Center, + Bottom, +} + +/// Physical static-position contract consumed by absolute positioning. +/// +/// A point alone is insufficient: a centered point denotes the center of the +/// margin box, while an end point denotes its far edge. This is the same +/// distinction represented by Blink's `PhysicalStaticPosition`. +#[derive(Clone, Copy, Debug, PartialEq)] +pub(crate) struct PhysicalStaticPosition { + point: Point, + horizontal_edge: HorizontalStaticEdge, + vertical_edge: VerticalStaticEdge, +} + +impl PhysicalStaticPosition { + pub(crate) const fn new( + point: Point, + horizontal_edge: HorizontalStaticEdge, + vertical_edge: VerticalStaticEdge, + ) -> Self { + Self { + point, + horizontal_edge, + vertical_edge, + } + } + + pub(crate) fn relative_to(self, origin: Point) -> Self { + Self { + point: Point { + x: self.point.x - origin.x, + y: self.point.y - origin.y, + }, + ..self + } + } + + pub(crate) fn margin_box_origin(self, box_size: Size, margin: Rect) -> Point { + let x = match self.horizontal_edge { + HorizontalStaticEdge::Left => self.point.x + margin.left, + HorizontalStaticEdge::Center => { + self.point.x - box_size.width / 2.0 + (margin.left - margin.right) / 2.0 + } + HorizontalStaticEdge::Right => self.point.x - box_size.width - margin.right, + }; + let y = match self.vertical_edge { + VerticalStaticEdge::Top => self.point.y + margin.top, + VerticalStaticEdge::Center => { + self.point.y - box_size.height / 2.0 + (margin.top - margin.bottom) / 2.0 + } + VerticalStaticEdge::Bottom => self.point.y - box_size.height - margin.bottom, + }; + Point { x, y } + } +} + +pub(crate) fn flex_main_axis_static_edge( + justify_content: Option, + is_reverse: bool, +) -> LogicalStaticEdge { + match justify_content + .unwrap_or(AlignContent::FLEX_START) + .keyword() + { + AlignContentKeyword::FlexEnd => { + if is_reverse { + LogicalStaticEdge::Start + } else { + LogicalStaticEdge::End + } + } + AlignContentKeyword::Center + | AlignContentKeyword::SpaceAround + | AlignContentKeyword::SpaceEvenly => LogicalStaticEdge::Center, + AlignContentKeyword::Start => LogicalStaticEdge::Start, + AlignContentKeyword::End => LogicalStaticEdge::End, + AlignContentKeyword::FlexStart + | AlignContentKeyword::Stretch + | AlignContentKeyword::SpaceBetween => { + if is_reverse { + LogicalStaticEdge::End + } else { + LogicalStaticEdge::Start + } + } + } +} + +pub(crate) struct FlexCrossAxisStaticContext { + pub(crate) align_self: Option, + pub(crate) align_items: Option, + pub(crate) flex_wrap: FlexWrap, + pub(crate) child_writing_mode: WritingMode, + pub(crate) child_direction: Direction, + pub(crate) container_writing_mode: WritingMode, + pub(crate) container_direction: Direction, + pub(crate) physical_axis: AbsoluteAxis, + pub(crate) overflows: bool, +} + +impl FlexCrossAxisStaticContext { + pub(crate) fn resolve(self) -> LogicalStaticEdge { + let alignment = self + .align_self + .or(self.align_items) + .unwrap_or(AlignItems::STRETCH); + let mut keyword = if alignment.safety == AlignmentSafety::Safe && self.overflows { + AlignItemsKeyword::Start + } else { + alignment.keyword() + }; + + keyword = + match keyword { + AlignItemsKeyword::Start => AlignItemsKeyword::FlexStart, + AlignItemsKeyword::End => AlignItemsKeyword::FlexEnd, + AlignItemsKeyword::SelfStart | AlignItemsKeyword::SelfEnd => { + let child_start_reversed = self + .child_writing_mode + .is_axis_flow_reversed(self.physical_axis, self.child_direction); + let container_start_reversed = self + .container_writing_mode + .is_axis_flow_reversed(self.physical_axis, self.container_direction); + let starts_match = child_start_reversed == container_start_reversed; + match (keyword, starts_match) { + (AlignItemsKeyword::SelfStart, true) + | (AlignItemsKeyword::SelfEnd, false) => AlignItemsKeyword::FlexStart, + (AlignItemsKeyword::SelfStart, false) + | (AlignItemsKeyword::SelfEnd, true) => AlignItemsKeyword::FlexEnd, + _ => unreachable!("self-relative alignment was matched above"), + } + } + keyword => keyword, + }; + + if self.flex_wrap == FlexWrap::WrapReverse { + keyword = match keyword { + AlignItemsKeyword::FlexStart => AlignItemsKeyword::FlexEnd, + AlignItemsKeyword::FlexEnd => AlignItemsKeyword::FlexStart, + keyword => keyword, + }; + } + + match keyword { + AlignItemsKeyword::Center => LogicalStaticEdge::Center, + AlignItemsKeyword::FlexEnd => LogicalStaticEdge::End, + AlignItemsKeyword::Stretch if self.flex_wrap == FlexWrap::WrapReverse => { + LogicalStaticEdge::End + } + AlignItemsKeyword::Start + | AlignItemsKeyword::End + | AlignItemsKeyword::FlexStart + | AlignItemsKeyword::SelfStart + | AlignItemsKeyword::SelfEnd + | AlignItemsKeyword::Baseline + | AlignItemsKeyword::Stretch => LogicalStaticEdge::Start, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum PhysicalAxisStaticEdge { + Min, + Center, + Max, +} + +fn physical_axis_static_position( + min: f32, + size: f32, + edge: LogicalStaticEdge, + start_is_reversed: bool, +) -> (f32, PhysicalAxisStaticEdge) { + match (edge, start_is_reversed) { + (LogicalStaticEdge::Start, false) | (LogicalStaticEdge::End, true) => { + (min, PhysicalAxisStaticEdge::Min) + } + (LogicalStaticEdge::Center, _) => (min + size / 2.0, PhysicalAxisStaticEdge::Center), + (LogicalStaticEdge::End, false) | (LogicalStaticEdge::Start, true) => { + (min + size, PhysicalAxisStaticEdge::Max) + } + } +} + +pub(crate) fn physical_static_position_from_logical( + content_origin: Point, + content_size: Size, + writing_mode: WritingMode, + direction: Direction, + inline_edge: LogicalStaticEdge, + block_edge: LogicalStaticEdge, +) -> PhysicalStaticPosition { + let inline_axis = writing_mode.inline_axis(); + let (inline_offset, inline_physical_edge) = physical_axis_static_position( + match inline_axis { + AbsoluteAxis::Horizontal => content_origin.x, + AbsoluteAxis::Vertical => content_origin.y, + }, + content_size.get_abs(inline_axis), + inline_edge, + writing_mode.is_inline_flow_reversed(direction), + ); + let block_axis = writing_mode.block_axis(); + let (block_offset, block_physical_edge) = physical_axis_static_position( + match block_axis { + AbsoluteAxis::Horizontal => content_origin.x, + AbsoluteAxis::Vertical => content_origin.y, + }, + content_size.get_abs(block_axis), + block_edge, + writing_mode.is_block_flow_reversed(), + ); + + match inline_axis { + AbsoluteAxis::Horizontal => PhysicalStaticPosition::new( + Point { + x: inline_offset, + y: block_offset, + }, + match inline_physical_edge { + PhysicalAxisStaticEdge::Min => HorizontalStaticEdge::Left, + PhysicalAxisStaticEdge::Center => HorizontalStaticEdge::Center, + PhysicalAxisStaticEdge::Max => HorizontalStaticEdge::Right, + }, + match block_physical_edge { + PhysicalAxisStaticEdge::Min => VerticalStaticEdge::Top, + PhysicalAxisStaticEdge::Center => VerticalStaticEdge::Center, + PhysicalAxisStaticEdge::Max => VerticalStaticEdge::Bottom, + }, + ), + AbsoluteAxis::Vertical => PhysicalStaticPosition::new( + Point { + x: block_offset, + y: inline_offset, + }, + match block_physical_edge { + PhysicalAxisStaticEdge::Min => HorizontalStaticEdge::Left, + PhysicalAxisStaticEdge::Center => HorizontalStaticEdge::Center, + PhysicalAxisStaticEdge::Max => HorizontalStaticEdge::Right, + }, + match inline_physical_edge { + PhysicalAxisStaticEdge::Min => VerticalStaticEdge::Top, + PhysicalAxisStaticEdge::Center => VerticalStaticEdge::Center, + PhysicalAxisStaticEdge::Max => VerticalStaticEdge::Bottom, + }, + ), + } +} /// Resolve auto margins in one physical axis of an absolutely positioned box. /// @@ -67,6 +338,99 @@ mod tests { end: Some(0.0), }; + #[test] + fn centered_static_position_centers_the_margin_box() { + let position = PhysicalStaticPosition::new( + Point { x: 100.0, y: 50.0 }, + HorizontalStaticEdge::Center, + VerticalStaticEdge::Center, + ); + assert_eq!( + position.margin_box_origin( + Size { + width: 20.0, + height: 10.0, + }, + Rect { + left: 4.0, + right: 8.0, + top: 2.0, + bottom: 6.0, + }, + ), + Point { x: 88.0, y: 43.0 } + ); + } + + #[test] + fn logical_static_position_respects_vertical_flow_and_rtl() { + let position = physical_static_position_from_logical( + Point { x: 20.0, y: 10.0 }, + Size { + width: 160.0, + height: 80.0, + }, + WritingMode::VerticalRl, + Direction::Rtl, + LogicalStaticEdge::Start, + LogicalStaticEdge::Start, + ); + assert_eq!( + position, + PhysicalStaticPosition::new( + Point { x: 180.0, y: 90.0 }, + HorizontalStaticEdge::Right, + VerticalStaticEdge::Bottom, + ) + ); + } + + #[test] + fn flex_static_edges_distinguish_flow_and_flex_relative_values() { + assert_eq!( + flex_main_axis_static_edge(Some(AlignContent::START), true), + LogicalStaticEdge::Start + ); + assert_eq!( + flex_main_axis_static_edge(Some(AlignContent::FLEX_START), true), + LogicalStaticEdge::End + ); + assert_eq!( + flex_main_axis_static_edge(Some(AlignContent::FLEX_END), true), + LogicalStaticEdge::Start + ); + assert_eq!( + FlexCrossAxisStaticContext { + align_self: None, + align_items: None, + flex_wrap: FlexWrap::WrapReverse, + child_writing_mode: WritingMode::HorizontalTb, + child_direction: Direction::Ltr, + container_writing_mode: WritingMode::HorizontalTb, + container_direction: Direction::Ltr, + physical_axis: AbsoluteAxis::Vertical, + overflows: false, + } + .resolve(), + LogicalStaticEdge::End + ); + assert_eq!( + FlexCrossAxisStaticContext { + align_self: Some(AlignItems::SAFE_CENTER), + align_items: None, + flex_wrap: FlexWrap::NoWrap, + child_writing_mode: WritingMode::HorizontalTb, + child_direction: Direction::Ltr, + container_writing_mode: WritingMode::HorizontalTb, + container_direction: Direction::Ltr, + physical_axis: AbsoluteAxis::Vertical, + overflows: true, + } + .resolve(), + LogicalStaticEdge::Start + ); + } + #[test] fn positive_space_is_shared_even_when_the_box_is_wider_than_that_space() { assert_eq!( diff --git a/moli-layout/src/taffy_tree.rs b/moli-layout/src/taffy_tree.rs index 559196524..971a32a02 100644 --- a/moli-layout/src/taffy_tree.rs +++ b/moli-layout/src/taffy_tree.rs @@ -5,12 +5,13 @@ use style::Atom; use taffy::{ AbsoluteAxis, AlignContent, AlignContentKeyword, AlignmentSafety, AutoSizeBehavior, AvailableSpace, BlockContext, BlockFormattingContext, BoxSizing, CacheTree, Clear, - DetailedGridInfo, Dimension, Display, FloatDirection, Layout, LayoutBlockContainer, - LayoutFlexboxContainer, LayoutGridContainer, LayoutInput, LayoutOutput, LayoutPartialTree, - LeafLayoutContext, Line, MaybeMath, MaybeResolve, NodeId, Point, ResolveOrZero, RoundTree, - RunMode, Size, SizingMode, SizingPurpose, Style, TraversePartialTree, TraverseTree, - compute_block_layout, compute_cached_layout, compute_flexbox_layout, compute_grid_layout, - compute_hidden_layout, compute_leaf_layout_with_context, compute_root_layout, round_layout, + DetailedGridInfo, Dimension, Display, FlexDirection, FloatDirection, Layout, + LayoutBlockContainer, LayoutFlexboxContainer, LayoutGridContainer, LayoutInput, LayoutOutput, + LayoutPartialTree, LeafLayoutContext, Line, MaybeMath, MaybeResolve, NodeId, Point, + ResolveOrZero, RoundTree, RunMode, Size, SizingMode, SizingPurpose, Style, TraversePartialTree, + TraverseTree, compute_block_layout, compute_cached_layout, compute_flexbox_layout, + compute_grid_layout, compute_hidden_layout, compute_leaf_layout_with_context, + compute_root_layout, round_layout, }; use crate::{ @@ -21,7 +22,11 @@ use crate::{ InlineObjectRole, break_inline_lines, build_inline_fragments, build_inline_line_placements, measure_inline_lines, relative_atomic_inset_offset, reset_inline_layout_for_probe, }, - positioned::resolve_absolute_axis_margins, + positioned::{ + FlexCrossAxisStaticContext, HorizontalStaticEdge, PhysicalStaticPosition, + VerticalStaticEdge, flex_main_axis_static_edge, physical_static_position_from_logical, + resolve_absolute_axis_margins, + }, replaced::measure_replaced, style::{InlineDirection, resolve_stylo_calc_value}, table::{compute_table_layout, prepare_table_layout_trees}, @@ -29,7 +34,7 @@ use crate::{ }; pub(crate) struct PreparedWorldLayout { - positioned_static_placeholders: Vec, + positioned_static_sources: Vec, numeric_unrounded_layouts: Vec, numeric_viewport_layout: Layout, feedback_invalidation_marks: Vec, @@ -145,10 +150,10 @@ where world.viewport_layout.unrounded_layout = Layout::with_order(0); world.viewport_layout.final_layout = Layout::with_order(0); update_viewport_layout_style(world, viewport); - let positioned_static_placeholders = prepare_layout_tree(world); + let positioned_static_sources = prepare_layout_tree(world); prepare_table_layout_trees(world); let mut prepared = PreparedWorldLayout { - positioned_static_placeholders, + positioned_static_sources, numeric_unrounded_layouts: Vec::with_capacity(world.boxes.len()), numeric_viewport_layout: Layout::with_order(0), feedback_invalidation_marks: vec![false; world.boxes.len()], @@ -223,7 +228,7 @@ where ); prepared.capture_numeric_geometry(world); physicalize_vertical_block_flow(world); - finish_block_positioned_layout(world, viewport, &prepared.positioned_static_placeholders); + finish_positioned_static_layout(world, viewport, &prepared.positioned_static_sources); finish_inline_positioned_layout(world, viewport); finish_form_control_contents(world); finish_outside_list_markers(world); @@ -385,11 +390,11 @@ fn scale_layout(layout: Layout, factor: f32) -> Layout { } } -fn prepare_layout_tree(world: &mut LayoutWorld) -> Vec +fn prepare_layout_tree(world: &mut LayoutWorld) -> Vec where N: Copy + Debug + Eq + Hash, { - let mut positioned_static_placeholders = Vec::new(); + let mut positioned_static_sources = Vec::new(); let root = world.root; world.viewport_layout.children.push(root); @@ -445,7 +450,16 @@ where && world.boxes[id.index()].style.has_auto_inset_axis() && inline_owner.is_none(); if needs_static_position { - if original_parent_uses_block_layout(world, original_parent) { + if world.boxes[original_parent.index()] + .style + .display() + .is_flex_container() + { + positioned_static_sources.push(PositionedStaticSource::FlexContainer { + child: id, + container: original_parent, + }); + } else if original_parent_uses_block_layout(world, original_parent) { let placeholder_style = world.boxes[id.index()] .style .positioned_static_placeholder(); @@ -470,10 +484,10 @@ where world.boxes[original_parent.index()] .layout_children .push(placeholder); - positioned_static_placeholders.push(PositionedStaticPlaceholder { + positioned_static_sources.push(PositionedStaticSource::BlockPlaceholder { child: id, placeholder, - original_parent, + container: original_parent, }); } else { push_layout_diagnostic( @@ -514,7 +528,7 @@ where children.sort_by_key(|child| world.boxes[child.index()].style.order()); world.boxes[parent_index].layout_children = children; } - positioned_static_placeholders + positioned_static_sources } fn original_parent_uses_block_layout(world: &LayoutWorld, parent: LayoutBoxId) -> bool @@ -861,62 +875,179 @@ struct PositionedContainingArea { } #[derive(Clone, Copy, Debug)] -struct PositionedStaticPlaceholder { - child: LayoutBoxId, - placeholder: LayoutBoxId, - original_parent: LayoutBoxId, +enum PositionedStaticSource { + /// A block formatting context computes the hypothetical position through + /// a zero-sized out-of-flow probe in the original formatting parent. + BlockPlaceholder { + child: LayoutBoxId, + placeholder: LayoutBoxId, + container: LayoutBoxId, + }, + /// Flex alignment contributes a static-position point and edge pair even + /// when the flex container is not the child's absolute containing block. + FlexContainer { + child: LayoutBoxId, + container: LayoutBoxId, + }, } -/// Applies block-container static positions gathered by zero-sized absolute -/// placeholders in the original numeric parent. This is the block analogue -/// of Parley's out-of-flow inline placeholder and keeps the real box attached -/// to its actual absolute/fixed containing block. -fn finish_block_positioned_layout( +/// Resolves static-position contributions after the normal-flow formatting +/// parents have their final numeric geometry. The real positioned box remains +/// attached to its CSS containing block throughout numeric layout. +fn finish_positioned_static_layout( world: &mut LayoutWorld, viewport: PaintViewport, - placeholders: &[PositionedStaticPlaceholder], + sources: &[PositionedStaticSource], ) where N: Copy + Debug + Eq + Hash, { - for placeholder in placeholders { - let placeholder_layout = world.boxes[placeholder.placeholder.index()].unrounded_layout; - let parent_origin = unrounded_global_origin(world, placeholder.original_parent); - let parent_direction = world.boxes[placeholder.original_parent.index()] - .style - .taffy - .direction; - let parent_is_rtl = parent_direction == taffy::Direction::Rtl; - let static_local_x = if parent_is_rtl { - placeholder_layout.location.x - + placeholder_layout.size.width - + placeholder_layout.margin.right - } else { - placeholder_layout.location.x - placeholder_layout.margin.left - }; - let static_global = Point { - x: parent_origin.x + static_local_x, - y: parent_origin.y + placeholder_layout.location.y - placeholder_layout.margin.top, - }; - let area = positioned_containing_area(world, placeholder.child, viewport); - let static_in_area = Point { - x: static_global.x - area.origin.x, - y: static_global.y - area.origin.y, + for source in sources { + let (child, static_global) = match *source { + PositionedStaticSource::BlockPlaceholder { + child, + placeholder, + container, + } => (child, block_static_position(world, placeholder, container)), + PositionedStaticSource::FlexContainer { child, container } => { + (child, flex_static_position(world, child, container)) + } }; - let numeric_parent_origin = world.boxes[placeholder.child.index()] + let area = positioned_containing_area(world, child, viewport); + let static_in_area = static_global.relative_to(area.origin); + let numeric_parent_origin = world.boxes[child.index()] .layout_parent .map(|parent| unrounded_global_origin(world, parent)) .unwrap_or(Point::ZERO); - apply_inline_static_position( - world, - placeholder.child, - area, - static_in_area, - parent_is_rtl, - numeric_parent_origin, - ); + apply_static_position(world, child, area, static_in_area, numeric_parent_origin); } } +fn block_static_position( + world: &LayoutWorld, + placeholder: LayoutBoxId, + container: LayoutBoxId, +) -> PhysicalStaticPosition +where + N: Copy + Debug + Eq + Hash, +{ + let placeholder_layout = world.boxes[placeholder.index()].unrounded_layout; + let container_origin = unrounded_global_origin(world, container); + let is_rtl = world.boxes[container.index()].style.taffy.direction == taffy::Direction::Rtl; + let (x, horizontal_edge) = if is_rtl { + ( + placeholder_layout.location.x + + placeholder_layout.size.width + + placeholder_layout.margin.right, + HorizontalStaticEdge::Right, + ) + } else { + ( + placeholder_layout.location.x - placeholder_layout.margin.left, + HorizontalStaticEdge::Left, + ) + }; + PhysicalStaticPosition::new( + Point { + x: container_origin.x + x, + y: container_origin.y + placeholder_layout.location.y - placeholder_layout.margin.top, + }, + horizontal_edge, + VerticalStaticEdge::Top, + ) +} + +fn flex_static_position( + world: &LayoutWorld, + child: LayoutBoxId, + container: LayoutBoxId, +) -> PhysicalStaticPosition +where + N: Copy + Debug + Eq + Hash, +{ + let container_box = &world.boxes[container.index()]; + let child_box = &world.boxes[child.index()]; + let container_layout = container_box.unrounded_layout; + let scrollbar = world.get_scrollbar_insets(container.to_taffy()); + let container_origin = unrounded_global_origin(world, container); + let content_origin = Point { + x: container_origin.x + + container_layout.border.left + + scrollbar.left + + container_layout.padding.left, + y: container_origin.y + + container_layout.border.top + + scrollbar.top + + container_layout.padding.top, + }; + let content_size = Size { + width: (container_layout.size.width + - container_layout.border.left + - container_layout.border.right + - scrollbar.left + - scrollbar.right + - container_layout.padding.left + - container_layout.padding.right) + .max(0.0), + height: (container_layout.size.height + - container_layout.border.top + - container_layout.border.bottom + - scrollbar.top + - scrollbar.bottom + - container_layout.padding.top + - container_layout.padding.bottom) + .max(0.0), + }; + let flex_direction = container_box.style.taffy.flex_direction; + let is_column = matches!( + flex_direction, + FlexDirection::Column | FlexDirection::ColumnReverse + ); + let is_reverse = matches!( + flex_direction, + FlexDirection::RowReverse | FlexDirection::ColumnReverse + ); + let container_writing_mode = container_box.style.writing_mode(); + let physical_cross_axis = if is_column { + container_writing_mode.inline_axis() + } else { + container_writing_mode.block_axis() + }; + let child_layout = child_box.unrounded_layout; + let child_cross_margin_size = match physical_cross_axis { + AbsoluteAxis::Horizontal => child_layout.margin.left + child_layout.margin.right, + AbsoluteAxis::Vertical => child_layout.margin.top + child_layout.margin.bottom, + }; + let cross_overflows = child_layout.size.get_abs(physical_cross_axis) + child_cross_margin_size + > content_size.get_abs(physical_cross_axis); + let main_edge = + flex_main_axis_static_edge(container_box.style.taffy.justify_content, is_reverse); + let cross_edge = FlexCrossAxisStaticContext { + align_self: child_box.style.taffy.align_self, + align_items: container_box.style.taffy.align_items, + flex_wrap: container_box.style.taffy.flex_wrap, + child_writing_mode: child_box.style.writing_mode(), + child_direction: child_box.style.taffy.direction, + container_writing_mode, + container_direction: container_box.style.taffy.direction, + physical_axis: physical_cross_axis, + overflows: cross_overflows, + } + .resolve(); + let (inline_edge, block_edge) = if is_column { + (cross_edge, main_edge) + } else { + (main_edge, cross_edge) + }; + physical_static_position_from_logical( + content_origin, + content_size, + container_writing_mode, + container_box.style.taffy.direction, + inline_edge, + block_edge, + ) +} + /// Completes positioned descendants whose hypothetical position came from an /// IFC. Taffy can size ordinary absolute children itself, but an IFC is a leaf /// in the numeric tree and a flattened positioned inline is not a numeric node @@ -965,12 +1096,19 @@ where numeric_parent_origin, ); } else { - apply_inline_static_position( + apply_static_position( world, child, area, - static_in_area, - area.direction == taffy::Direction::Rtl && static_position.inline_level, + PhysicalStaticPosition::new( + static_in_area, + if area.direction == taffy::Direction::Rtl && static_position.inline_level { + HorizontalStaticEdge::Right + } else { + HorizontalStaticEdge::Left + }, + VerticalStaticEdge::Top, + ), numeric_parent_origin, ); } @@ -1084,12 +1222,11 @@ where origin } -fn apply_inline_static_position( +fn apply_static_position( world: &mut LayoutWorld, child: LayoutBoxId, area: PositionedContainingArea, - static_position: Point, - static_position_at_inline_end: bool, + static_position: PhysicalStaticPosition, numeric_parent_origin: Point, ) where N: Copy + Debug + Eq + Hash, @@ -1101,17 +1238,12 @@ fn apply_inline_static_position( return; } let layout = &mut world.boxes[child.index()].unrounded_layout; + let origin = static_position.margin_box_origin(layout.size, layout.margin); if both_horizontal_insets_auto { - let x = if static_position_at_inline_end { - static_position.x - layout.size.width - layout.margin.right - } else { - static_position.x + layout.margin.left - }; - layout.location.x = area.origin.x + x - numeric_parent_origin.x; + layout.location.x = area.origin.x + origin.x - numeric_parent_origin.x; } if both_vertical_insets_auto { - layout.location.y = - area.origin.y + static_position.y + layout.margin.top - numeric_parent_origin.y; + layout.location.y = area.origin.y + origin.y - numeric_parent_origin.y; } } diff --git a/moli-renderer-v8/src/runtime/phase_one/mod.rs b/moli-renderer-v8/src/runtime/phase_one/mod.rs index 4ad3a1908..d9f7cc68a 100644 --- a/moli-renderer-v8/src/runtime/phase_one/mod.rs +++ b/moli-renderer-v8/src/runtime/phase_one/mod.rs @@ -832,6 +832,112 @@ html, body { display: block; margin: 0; padding: 0 } })); } + #[test] + fn layout_renderer_preserves_flex_static_position_edges_across_containing_blocks() { + let runtime = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .expect("current-thread runtime should build"); + + runtime.block_on(tokio::task::LocalSet::new().run_until(async move { + let mut page_vm = parse_phase_one_html_into_page_vm_for_test( + r#" +
+
+
+
+
+
+
+
+
+
+
+"#, + ) + .await; + page_vm.vm_mut().sync_live_document_style_sources(); + + let snapshot = page_vm + .vm_mut() + .screenshot_layout_snapshot(moli_layout::PaintViewport::new(800, 1200, 1.0)) + .expect("native layout should succeed") + .expect("fixture should have a document element"); + + let cases = [ + ( + moli_layout::PaintColor::new(1.0, 0.0, 0.0, 1.0), + moli_layout::PaintRect::new(20.0, 10.0, 20.0, 10.0), + ), + ( + moli_layout::PaintColor::new(0.0, 128.0 / 255.0, 0.0, 1.0), + moli_layout::PaintRect::new(90.0, 145.0, 20.0, 10.0), + ), + ( + moli_layout::PaintColor::new(0.0, 0.0, 1.0, 1.0), + moli_layout::PaintRect::new(160.0, 280.0, 20.0, 10.0), + ), + ( + moli_layout::PaintColor::new(1.0, 1.0, 0.0, 1.0), + moli_layout::PaintRect::new(160.0, 310.0, 20.0, 10.0), + ), + ( + moli_layout::PaintColor::new(1.0, 0.0, 1.0, 1.0), + moli_layout::PaintRect::new(20.0, 480.0, 20.0, 10.0), + ), + ( + moli_layout::PaintColor::new(0.0, 1.0, 1.0, 1.0), + moli_layout::PaintRect::new(160.0, 510.0, 20.0, 10.0), + ), + ( + moli_layout::PaintColor::new(128.0 / 255.0, 0.0, 0.0, 1.0), + moli_layout::PaintRect::new(20.0, 610.0, 20.0, 10.0), + ), + ( + moli_layout::PaintColor::new(0.0, 100.0 / 255.0, 0.0, 1.0), + moli_layout::PaintRect::new(90.0, 745.0, 20.0, 10.0), + ), + ( + moli_layout::PaintColor::new(0.0, 0.0, 128.0 / 255.0, 1.0), + moli_layout::PaintRect::new(160.0, 810.0, 20.0, 10.0), + ), + ( + moli_layout::PaintColor::new(128.0 / 255.0, 128.0 / 255.0, 0.0, 1.0), + moli_layout::PaintRect::new(160.0, 980.0, 20.0, 10.0), + ), + ( + moli_layout::PaintColor::new(123.0 / 255.0, 45.0 / 255.0, 67.0 / 255.0, 1.0), + moli_layout::PaintRect::new(20.0, 1010.0, 20.0, 100.0), + ), + ]; + for (color, expected) in cases { + assert_paint_rect(solid_paint_rect(&snapshot, color), expected); + } + assert!( + snapshot + .diagnostics + .iter() + .all(|diagnostic| { diagnostic.code != "positioned-static-position-deferred" }) + ); + })); + } + #[test] fn layout_renderer_preserves_calc_min_width_in_float_intrinsic_contribution() { let runtime = tokio::runtime::Builder::new_current_thread()