diff options
Diffstat (limited to 'components/layout_2020/flexbox')
-rw-r--r-- | components/layout_2020/flexbox/geom.rs | 2 | ||||
-rw-r--r-- | components/layout_2020/flexbox/layout.rs | 515 |
2 files changed, 268 insertions, 249 deletions
diff --git a/components/layout_2020/flexbox/geom.rs b/components/layout_2020/flexbox/geom.rs index 623c031585b..ac80f451f70 100644 --- a/components/layout_2020/flexbox/geom.rs +++ b/components/layout_2020/flexbox/geom.rs @@ -67,7 +67,7 @@ impl<T> FlexRelativeSides<T> { /// One of the two bits set by the `flex-direction` property /// (The other is "forward" v.s. reverse.) -#[derive(Clone, Copy, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq)] pub(super) enum FlexAxis { /// The main axis is the inline axis of the container (not necessarily of flex items!), /// cross is block. diff --git a/components/layout_2020/flexbox/layout.rs b/components/layout_2020/flexbox/layout.rs index 25c2bec24eb..bbe22d9556f 100644 --- a/components/layout_2020/flexbox/layout.rs +++ b/components/layout_2020/flexbox/layout.rs @@ -205,14 +205,61 @@ impl FlexContainer { layout_context: &LayoutContext, writing_mode: WritingMode, ) -> ContentSizes { + let flex_axis = FlexAxis::from(used_flex_direction(&*self.style)); + match flex_axis { + FlexAxis::Row => self.main_content_sizes(layout_context, flex_axis, writing_mode), + FlexAxis::Column => self.cross_content_sizes(layout_context, flex_axis), + } + } + + fn cross_content_sizes( + &mut self, + layout_context: &LayoutContext, + flex_axis: FlexAxis, + ) -> ContentSizes { + // <https://drafts.csswg.org/css-flexbox/#intrinsic-cross-sizes> + assert_eq!( + flex_axis, + FlexAxis::Column, + "The cross axis should be the inline one" + ); + let mut content_sizes = ContentSizes::zero(); + for kid in self.children.iter() { + let kid = &mut *kid.borrow_mut(); + match kid { + FlexLevelBox::FlexItem(item) => { + // TODO: For the max-content size we should distribute items into + // columns, and sum the column sizes and gaps. + content_sizes.max_assign( + item.independent_formatting_context + .inline_content_sizes(layout_context), + ); + }, + FlexLevelBox::OutOfFlowAbsolutelyPositionedBox(_) => {}, + } + } + content_sizes + } + + fn main_content_sizes( + &mut self, + layout_context: &LayoutContext, + flex_axis: FlexAxis, + writing_mode: WritingMode, + ) -> ContentSizes { // - TODO: calculate intrinsic cross sizes when container is a column // (and check for ‘writing-mode’?) // - TODO: Collapsed flex items need to be skipped for intrinsic size calculation. - // <https://drafts.csswg.org/css-flexbox/#intrinsic-cross-sizes> + // <https://drafts.csswg.org/css-flexbox-1/#intrinsic-main-sizes> // > It is calculated, considering only non-collapsed flex items, by: // > 1. For each flex item, subtract its outer flex base size from its max-content // > contribution size. + assert_eq!( + flex_axis, + FlexAxis::Row, + "The main axis should be the inline one" + ); let mut chosen_max_flex_fraction = f32::NEG_INFINITY; let mut chosen_min_flex_fraction = f32::NEG_INFINITY; let mut sum_of_flex_grow_factors = 0.0; @@ -236,7 +283,7 @@ impl FlexContainer { sum_of_flex_grow_factors += item.style().get_position().flex_grow.0; sum_of_flex_shrink_factors += item.style().get_position().flex_shrink.0; - let info = item.inline_content_size_info( + let info = item.main_content_size_info( layout_context, writing_mode, container_is_horizontal, @@ -455,7 +502,7 @@ impl FlexContainer { // https://github.com/w3c/csswg-drafts/issues/4905 // Gecko reportedly uses `block-size: fit-content` in this case // (which requires running another pass of the "full" layout algorithm) - todo!() + containing_block.block_size.auto_is(Au::zero) // Note: this panic shouldn’t happen since the start of `FlexContainer::layout` // forces `FlexAxis::Row`. }, @@ -842,9 +889,11 @@ impl<'a> FlexItem<'a> { box_.automatic_min_size( flex_context.layout_context, cross_axis_is_item_block_axis, - content_box_size, - min_size, - max_size, + flex_context + .flex_axis + .vec2_to_flex_relative(content_box_size), + flex_context.flex_axis.vec2_to_flex_relative(min_size), + flex_context.flex_axis.vec2_to_flex_relative(max_size), ) }), block: min_size.block.auto_is(Au::zero), @@ -969,7 +1018,7 @@ fn flex_base_size( } else { // FIXME: block-axis content sizing requires another pass // of "full" layout - todo!() + Au::zero() // Note: this panic shouldn’t happen since the start of `FlexContainer::layout` // forces `FlexAxis::Row` and the `writing-mode` property is disabled. } @@ -989,13 +1038,7 @@ fn cross_axis_is_item_block_axis( } fn used_flex_direction(container_style: &ComputedValues) -> FlexDirection { - // Column flex containers are not fully implemented yet, - // so give a different layout instead of panicking. - // FIXME: implement `todo!`s for FlexAxis::Column below, and remove this - match container_style.clone_flex_direction() { - FlexDirection::Row | FlexDirection::Column => FlexDirection::Row, - FlexDirection::RowReverse | FlexDirection::ColumnReverse => FlexDirection::RowReverse, - } + container_style.clone_flex_direction() } // “Collect flex items into flex lines” @@ -1596,112 +1639,140 @@ impl FlexItem<'_> { .positioning_context .collects_for_nearest_positioned_ancestor(), ); - match flex_context.flex_axis { - FlexAxis::Row => { - // The main axis is the container’s inline axis - // https://drafts.csswg.org/css-writing-modes/#orthogonal-flows - assert_eq!( - flex_context.containing_block.style.writing_mode, - self.box_.style().writing_mode, - "Mixed writing modes are not supported yet" + // https://drafts.csswg.org/css-writing-modes/#orthogonal-flows + let container_writing_mode = flex_context.containing_block.style.writing_mode; + assert_eq!( + container_writing_mode, + self.box_.style().writing_mode, + "Mixed writing modes are not supported yet" + ); + // … and also the item’s inline axis. + + match self.box_ { + IndependentFormattingContext::Replaced(replaced) => { + let pbm = replaced + .style + .padding_border_margin(flex_context.containing_block); + let box_size = used_cross_size_override.map(|size| LogicalVec2 { + inline: replaced + .style + .content_box_size(flex_context.containing_block, &pbm) + .inline + .map(Au::from), + block: AuOrAuto::LengthPercentage(size), + }); + let size = replaced.contents.used_size_as_if_inline_element( + flex_context.containing_block, + &replaced.style, + box_size, + &pbm, ); - // … and also the item’s inline axis. - - match self.box_ { - IndependentFormattingContext::Replaced(replaced) => { - let pbm = replaced - .style - .padding_border_margin(flex_context.containing_block); - let box_size = used_cross_size_override.map(|size| LogicalVec2 { - inline: replaced - .style - .content_box_size(flex_context.containing_block, &pbm) - .inline - .map(Au::from), - block: AuOrAuto::LengthPercentage(size), - }); - let size = replaced.contents.used_size_as_if_inline_element( - flex_context.containing_block, - &replaced.style, - box_size, - &pbm, - ); - let cross_size = flex_context.vec2_to_flex_relative(size).cross; - let container_writing_mode = - flex_context.containing_block.style.writing_mode; - let fragments = replaced.contents.make_fragments( - &replaced.style, - size.to_physical_size(container_writing_mode), - ); - - FlexItemLayoutResult { - hypothetical_cross_size: cross_size, - fragments, - positioning_context, - - // We will need to synthesize the baseline, but since the used cross - // size can differ from the hypothetical cross size, we should defer - // synthesizing until needed. - baseline_relative_to_margin_box: None, - } - }, - IndependentFormattingContext::NonReplaced(non_replaced) => { - let block_size = match used_cross_size_override { - Some(s) => AuOrAuto::LengthPercentage(s), - None => self.content_box_size.cross.map(|t| t), - }; - - let item_as_containing_block = ContainingBlock { - inline_size: used_main_size, - block_size, - style: &non_replaced.style, - }; - let IndependentLayout { - fragments, - content_block_size, - baselines: content_box_baselines, - .. - } = non_replaced.layout( - flex_context.layout_context, - &mut positioning_context, - &item_as_containing_block, - flex_context.containing_block, - ); - - let baselines_relative_to_margin_box = - self.layout_baselines_relative_to_margin_box(&content_box_baselines); - - let baseline_relative_to_margin_box = match self.align_self.0.value() { - // ‘baseline’ computes to ‘first baseline’. - AlignFlags::BASELINE => baselines_relative_to_margin_box.first, - AlignFlags::LAST_BASELINE => baselines_relative_to_margin_box.last, - _ => None, - }; - - let hypothetical_cross_size = self - .content_box_size - .cross - .auto_is(|| content_block_size) - .clamp_between_extremums( - self.content_min_size.cross, - self.content_max_size.cross, + let cross_size = flex_context.vec2_to_flex_relative(size).cross; + let container_writing_mode = flex_context.containing_block.style.writing_mode; + let fragments = replaced.contents.make_fragments( + &replaced.style, + size.to_physical_size(container_writing_mode), + ); + + FlexItemLayoutResult { + hypothetical_cross_size: cross_size, + fragments, + positioning_context, + + // We will need to synthesize the baseline, but since the used cross + // size can differ from the hypothetical cross size, we should defer + // synthesizing until needed. + baseline_relative_to_margin_box: None, + } + }, + IndependentFormattingContext::NonReplaced(non_replaced) => { + let cross_size = match used_cross_size_override { + Some(s) => AuOrAuto::LengthPercentage(s), + None => self.content_box_size.cross.map(|t| t), + }; + + let item_writing_mode = non_replaced.style.writing_mode; + let item_is_horizontal = item_writing_mode.is_horizontal(); + let cross_axis_is_item_block_axis = cross_axis_is_item_block_axis( + flex_context + .containing_block + .style + .writing_mode + .is_horizontal(), + item_is_horizontal, + flex_context.flex_axis, + ); + let (inline_size, block_size) = if cross_axis_is_item_block_axis { + (used_main_size, cross_size) + } else { + ( + cross_size.auto_is(|| { + let content_contributions = non_replaced.outer_inline_content_sizes( + flex_context.layout_context, + container_writing_mode, + Au::zero, ); + flex_context + .containing_block + .inline_size + .min(content_contributions.max_content) + .max(content_contributions.min_content) - + self.pbm_auto_is_zero.cross + }), + AuOrAuto::LengthPercentage(used_main_size), + ) + }; - FlexItemLayoutResult { - hypothetical_cross_size, - fragments, - positioning_context, - baseline_relative_to_margin_box, + let item_as_containing_block = ContainingBlock { + inline_size, + block_size, + style: &non_replaced.style, + }; + let IndependentLayout { + fragments, + content_block_size, + baselines: content_box_baselines, + .. + } = non_replaced.layout( + flex_context.layout_context, + &mut positioning_context, + &item_as_containing_block, + flex_context.containing_block, + ); + + let baselines_relative_to_margin_box = + self.layout_baselines_relative_to_margin_box(&content_box_baselines); + + let baseline_relative_to_margin_box = match self.align_self.0.value() { + // ‘baseline’ computes to ‘first baseline’. + AlignFlags::BASELINE => baselines_relative_to_margin_box.first, + AlignFlags::LAST_BASELINE => baselines_relative_to_margin_box.last, + _ => None, + }; + + let hypothetical_cross_size = self + .content_box_size + .cross + .auto_is(|| { + if cross_axis_is_item_block_axis { + content_block_size + } else { + inline_size } - }, + }) + .clamp_between_extremums( + self.content_min_size.cross, + self.content_max_size.cross, + ); + + FlexItemLayoutResult { + hypothetical_cross_size, + fragments, + positioning_context, + baseline_relative_to_margin_box, } }, - FlexAxis::Column => { - todo!() - // Note: this panic shouldn’t happen since the start of `FlexContainer::layout` - // forces `FlexAxis::Row`. - }, } } @@ -1841,7 +1912,7 @@ impl FlexItem<'_> { } impl FlexItemBox { - fn inline_content_size_info( + fn main_content_size_info( &mut self, layout_context: &LayoutContext, container_writing_mode: WritingMode, @@ -1877,9 +1948,9 @@ impl FlexItemBox { let automatic_min_size = self.automatic_min_size( layout_context, cross_axis_is_item_block_axis, - content_box_size, - content_min_size, - content_max_size, + flex_axis.vec2_to_flex_relative(content_box_size), + flex_axis.vec2_to_flex_relative(content_min_size), + flex_axis.vec2_to_flex_relative(content_max_size), ); let content_box_size = flex_axis.vec2_to_flex_relative(content_box_size); @@ -1913,14 +1984,19 @@ impl FlexItemBox { padding_border, ); - let content_contribution_sizes = self.inline_content_sizes( - layout_context, - container_writing_mode, - content_box_size, - content_min_size_no_auto, - content_max_size, - pbm_auto_is_zero, + // Compute the min-content and max-content contributions of the item. + // <https://drafts.csswg.org/css-flexbox/#intrinsic-item-contributions> + assert_eq!( + flex_axis, + FlexAxis::Row, + "The main axis should be the inline one" ); + let content_contribution_sizes = self + .independent_formatting_context + .outer_inline_content_sizes(layout_context, container_writing_mode, || { + automatic_min_size + }); + let outer_flex_base_size = flex_base_size + pbm_auto_is_zero.main; let max_flex_factors = self.desired_flex_factors_for_preferred_width( content_contribution_sizes.max_content, @@ -2012,69 +2088,14 @@ impl FlexItemBox { } } - fn inline_content_sizes( - &mut self, - layout_context: &LayoutContext, - writing_mode: WritingMode, - content_box_size: FlexRelativeVec2<GenericLengthPercentageOrAuto<Au>>, - content_min_size: FlexRelativeVec2<Au>, - content_max_size: FlexRelativeVec2<Option<Au>>, - pbm_auto_is_zero: FlexRelativeVec2<Au>, - ) -> ContentSizes { - // TODO: use cross sizes when container is a column - // (and check for ‘writing-mode’?) - - // <https://drafts.csswg.org/css-flexbox/#intrinsic-item-contributions> - let outer_inline_content_sizes = self - .independent_formatting_context - .outer_inline_content_sizes(layout_context, writing_mode); - let outer_preferred_size = content_box_size - .main - .non_auto() - .map(|preferred_size| preferred_size + pbm_auto_is_zero.main); - let outer_min_main_size = content_min_size.main + pbm_auto_is_zero.main; - let outer_max_main_size = content_max_size - .main - .map(|max_main_size| max_main_size + pbm_auto_is_zero.main); - - // > The main-size min-content contribution of a flex item is the larger of its - // > outer min-content size and outer preferred size if that is not auto, clamped by - // > its min/max main size. - let min_content_contribution = outer_preferred_size - .map_or( - outer_inline_content_sizes.min_content, - |outer_preferred_size| { - outer_preferred_size.max(outer_inline_content_sizes.min_content) - }, - ) - .clamp_between_extremums(outer_min_main_size, outer_max_main_size); - - // > The main-size max-content contribution of a flex item is the larger of its - // > outer max-content size and outer preferred size if that is not auto, clamped by - // > its min/max main size. - let max_content_contribution = outer_preferred_size - .map_or( - outer_inline_content_sizes.max_content, - |outer_preferred_size| { - outer_preferred_size.max(outer_inline_content_sizes.max_content) - }, - ) - .clamp_between_extremums(outer_min_main_size, outer_max_main_size); - - ContentSizes { - min_content: min_content_contribution, - max_content: max_content_contribution, - } - } - /// This is an implementation of <https://drafts.csswg.org/css-flexbox/#min-size-auto>. fn automatic_min_size( &mut self, layout_context: &LayoutContext, cross_axis_is_item_block_axis: bool, - content_box_size: LogicalVec2<AuOrAuto>, - min_size: LogicalVec2<GenericLengthPercentageOrAuto<Au>>, - max_size: LogicalVec2<Option<Au>>, + content_box_size: FlexRelativeVec2<AuOrAuto>, + min_size: FlexRelativeVec2<GenericLengthPercentageOrAuto<Au>>, + max_size: FlexRelativeVec2<Option<Au>>, ) -> Au { // FIXME(stshine): Consider more situations when auto min size is not needed. if self @@ -2085,80 +2106,78 @@ impl FlexItemBox { return Au::zero(); } - if cross_axis_is_item_block_axis { - // > **specified size suggestion** - // > If the item’s preferred main size is definite and not automatic, then the specified - // > size suggestion is that size. It is otherwise undefined. - let specified_size_suggestion = content_box_size.inline.non_auto(); - - // > **transferred size suggestion** - // > If the item has a preferred aspect ratio and its preferred cross size is definite, then the - // > transferred size suggestion is that size (clamped by its minimum and maximum cross sizes if they - // > are definite), converted through the aspect ratio. It is otherwise undefined. - let transferred_size_suggestion = match self.independent_formatting_context { - IndependentFormattingContext::NonReplaced(_) => None, - IndependentFormattingContext::Replaced(ref bfc) => { - match ( - bfc.contents.inline_size_over_block_size_intrinsic_ratio( - self.independent_formatting_context.style(), - ), - content_box_size.block, - ) { - (Some(ratio), AuOrAuto::LengthPercentage(block_size)) => { - let block_size = block_size.clamp_between_extremums( - min_size.block.auto_is(Au::zero), - max_size.block, - ); - Some(block_size.scale_by(ratio)) - }, - _ => None, - } - }, - }; + // > **specified size suggestion** + // > If the item’s preferred main size is definite and not automatic, then the specified + // > size suggestion is that size. It is otherwise undefined. + let specified_size_suggestion = content_box_size.main.non_auto(); - // > **content size suggestion** - // > The content size suggestion is the min-content size in the main axis, clamped, if it has a - // > preferred aspect ratio, by any definite minimum and maximum cross sizes converted through the - // > aspect ratio. - let inline_content_size = self - .independent_formatting_context - .inline_content_sizes(layout_context) - .min_content; - let (is_replaced, aspect_ratio) = match self.independent_formatting_context { + let (is_replaced, main_size_over_cross_size_intrinsic_ratio) = + match self.independent_formatting_context { IndependentFormattingContext::NonReplaced(_) => (false, None), - IndependentFormattingContext::Replaced(ref replaced) => ( - true, - replaced + IndependentFormattingContext::Replaced(ref replaced) => { + let ratio = replaced .contents .inline_size_over_block_size_intrinsic_ratio( self.independent_formatting_context.style(), - ), - ), + ) + .map(|ratio| { + if cross_axis_is_item_block_axis { + ratio + } else { + 1.0 / ratio + } + }); + (true, ratio) + }, }; - let content_size_suggestion = aspect_ratio - .map(|aspect_ratio| { - inline_content_size.clamp_between_extremums( - min_size.block.auto_is(Au::zero).scale_by(aspect_ratio), - max_size.block.map(|l| l.scale_by(aspect_ratio)), - ) - }) - .unwrap_or(inline_content_size); - - // > The content-based minimum size of a flex item is the smaller of its specified size - // > suggestion and its content size suggestion if its specified size suggestion exists; - // > otherwise, the smaller of its transferred size suggestion and its content size - // > suggestion if the element is replaced and its transferred size suggestion exists; - // > otherwise its content size suggestion. In all cases, the size is clamped by the maximum - // > main size if it’s definite. - match (specified_size_suggestion, transferred_size_suggestion) { - (Some(specified), _) => specified.min(content_size_suggestion), - (_, Some(transferred)) if is_replaced => transferred.min(content_size_suggestion), - _ => content_size_suggestion, - } - .clamp_below_max(max_size.inline) + + // > **transferred size suggestion** + // > If the item has a preferred aspect ratio and its preferred cross size is definite, then the + // > transferred size suggestion is that size (clamped by its minimum and maximum cross sizes if they + // > are definite), converted through the aspect ratio. It is otherwise undefined. + let transferred_size_suggestion = match ( + main_size_over_cross_size_intrinsic_ratio, + content_box_size.cross, + ) { + (Some(ratio), AuOrAuto::LengthPercentage(cross_size)) => { + let cross_size = cross_size + .clamp_between_extremums(min_size.cross.auto_is(Au::zero), max_size.cross); + Some(cross_size.scale_by(ratio)) + }, + _ => None, + }; + + // > **content size suggestion** + // > The content size suggestion is the min-content size in the main axis, clamped, if it has a + // > preferred aspect ratio, by any definite minimum and maximum cross sizes converted through the + // > aspect ratio. + let main_content_size = if cross_axis_is_item_block_axis { + self.independent_formatting_context + .inline_content_sizes(layout_context) + .min_content } else { - // FIXME(stshine): Implement this when main axis is item's block axis. Au::zero() + }; + let content_size_suggestion = main_size_over_cross_size_intrinsic_ratio + .map(|ratio| { + main_content_size.clamp_between_extremums( + min_size.cross.auto_is(Au::zero).scale_by(ratio), + max_size.cross.map(|l| l.scale_by(ratio)), + ) + }) + .unwrap_or(main_content_size); + + // > The content-based minimum size of a flex item is the smaller of its specified size + // > suggestion and its content size suggestion if its specified size suggestion exists; + // > otherwise, the smaller of its transferred size suggestion and its content size + // > suggestion if the element is replaced and its transferred size suggestion exists; + // > otherwise its content size suggestion. In all cases, the size is clamped by the maximum + // > main size if it’s definite. + match (specified_size_suggestion, transferred_size_suggestion) { + (Some(specified), _) => specified.min(content_size_suggestion), + (_, Some(transferred)) if is_replaced => transferred.min(content_size_suggestion), + _ => content_size_suggestion, } + .clamp_below_max(max_size.main) } } |