Unify logic for laying out floats and atomic inlines (#33802)

Signed-off-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
Oriol Brufau 2024-10-11 20:26:00 +02:00 committed by GitHub
parent 0eb8d22d88
commit a86dcfc6e7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 224 additions and 320 deletions

View file

@ -26,11 +26,11 @@ use crate::context::LayoutContext;
use crate::dom::NodeExt;
use crate::dom_traversal::{Contents, NodeAndStyleInfo};
use crate::formatting_contexts::IndependentFormattingContext;
use crate::fragment_tree::{BoxFragment, CollapsedBlockMargins, CollapsedMargin};
use crate::geom::{LogicalRect, LogicalVec2, PhysicalPoint, PhysicalRect, Size, ToLogical};
use crate::fragment_tree::{BoxFragment, CollapsedMargin};
use crate::geom::{LogicalRect, LogicalVec2, ToLogical};
use crate::positioned::{relative_adjustement, PositioningContext};
use crate::style_ext::{Clamp, ComputedValuesExt, DisplayInside, PaddingBorderMargin};
use crate::{AuOrAuto, ContainingBlock, IndefiniteContainingBlock};
use crate::style_ext::{DisplayInside, PaddingBorderMargin};
use crate::ContainingBlock;
/// A floating box.
#[derive(Debug, Serialize)]
@ -902,145 +902,13 @@ impl FloatBox {
containing_block,
&style,
|positioning_context| {
// Margin is computed this way regardless of whether the element is replaced
// or non-replaced.
let pbm = style.padding_border_margin(containing_block);
let margin = pbm.margin.auto_is(Au::zero);
let pbm_sums = pbm.padding + pbm.border + margin;
let (content_size, children);
match self.contents {
IndependentFormattingContext::NonReplaced(ref mut non_replaced) => {
// Calculate inline size.
// https://drafts.csswg.org/css2/#float-width
let style = non_replaced.style.clone();
let box_size = style.content_box_size(containing_block, &pbm);
let max_box_size = style.content_max_box_size(containing_block, &pbm);
let min_box_size = style.content_min_box_size(containing_block, &pbm);
let available_inline_size =
containing_block.inline_size - pbm_sums.inline_sum();
let available_block_size = containing_block
.block_size
.non_auto()
.map(|block_size| block_size - pbm_sums.block_sum());
let tentative_block_size = box_size
.block
.maybe_resolve_extrinsic(available_block_size)
.map(|size| {
let min_block_size = min_box_size
.block
.maybe_resolve_extrinsic(available_block_size)
.unwrap_or_default();
let max_block_size = max_box_size
.block
.maybe_resolve_extrinsic(available_block_size);
size.clamp_between_extremums(min_block_size, max_block_size)
})
.map_or(AuOrAuto::Auto, AuOrAuto::LengthPercentage);
let mut get_content_size = || {
let containing_block_for_children =
IndefiniteContainingBlock::new_for_style_and_block_size(
&style,
tentative_block_size,
);
non_replaced.inline_content_sizes(
layout_context,
&containing_block_for_children,
)
};
let tentative_inline_size = box_size.inline.resolve(
Size::fit_content,
available_inline_size,
&mut get_content_size,
);
let min_inline_size = min_box_size
.inline
.resolve_non_initial(available_inline_size, &mut get_content_size)
.unwrap_or_default();
let max_inline_size = max_box_size
.inline
.resolve_non_initial(available_inline_size, &mut get_content_size);
let inline_size = tentative_inline_size
.clamp_between_extremums(min_inline_size, max_inline_size);
// Calculate block size.
// https://drafts.csswg.org/css2/#block-root-margin
// FIXME(pcwalton): Is a tree rank of zero correct here?
let containing_block_for_children = ContainingBlock {
inline_size,
block_size: tentative_block_size,
style: &non_replaced.style,
};
let independent_layout = non_replaced.layout(
layout_context,
positioning_context,
&containing_block_for_children,
containing_block,
);
let (block_size, inline_size) =
match independent_layout.content_inline_size_for_table {
Some(inline_size) => {
(independent_layout.content_block_size, inline_size)
},
None => {
let stretch_size = available_block_size
.unwrap_or(independent_layout.content_block_size);
let mut get_content_size =
|| independent_layout.content_block_size.into();
let min_block_size = min_box_size
.block
.resolve_non_initial(stretch_size, &mut get_content_size)
.unwrap_or_default();
let max_block_size = max_box_size
.block
.resolve_non_initial(stretch_size, &mut get_content_size);
let block_size = tentative_block_size
.auto_is(|| independent_layout.content_block_size)
.clamp_between_extremums(min_block_size, max_block_size);
(block_size, inline_size)
},
};
content_size = LogicalVec2 {
inline: inline_size,
block: block_size,
};
children = independent_layout.fragments;
},
IndependentFormattingContext::Replaced(ref replaced) => {
// https://drafts.csswg.org/css2/#float-replaced-width
// https://drafts.csswg.org/css2/#inline-replaced-height
content_size = replaced.contents.used_size_as_if_inline_element(
containing_block,
&replaced.style,
&pbm,
);
children = replaced.contents.make_fragments(
&replaced.style,
containing_block,
content_size.to_physical_size(containing_block.style.writing_mode),
)
},
};
let containing_block_writing_mode = containing_block.style.writing_mode;
let content_rect = PhysicalRect::new(
PhysicalPoint::zero(),
content_size.to_physical_size(containing_block_writing_mode),
);
BoxFragment::new(
self.contents.base_fragment_info(),
style.clone(),
children,
content_rect,
pbm.padding.to_physical(containing_block_writing_mode),
pbm.border.to_physical(containing_block_writing_mode),
margin.to_physical(containing_block_writing_mode),
// Clearance is handled internally by the float placement logic, so there's no need
// to store it explicitly in the fragment.
None, // clearance
CollapsedBlockMargins::zero(),
)
self.contents
.layout_float_or_atomic_inline(
layout_context,
positioning_context,
containing_block,
)
.fragment
},
)
}

View file

@ -117,17 +117,18 @@ use crate::context::LayoutContext;
use crate::flow::float::{FloatBox, SequentialLayoutState};
use crate::flow::{CollapsibleWithParentStartMargin, FlowLayout};
use crate::formatting_contexts::{
Baselines, IndependentFormattingContext, NonReplacedFormattingContextContents,
Baselines, IndependentFormattingContext, IndependentLayoutResult,
NonReplacedFormattingContextContents,
};
use crate::fragment_tree::{
BoxFragment, CollapsedBlockMargins, CollapsedMargin, Fragment, FragmentFlags,
PositioningFragment,
};
use crate::geom::{LogicalRect, LogicalVec2, PhysicalPoint, PhysicalRect, Size, ToLogical};
use crate::geom::{LogicalRect, LogicalVec2, ToLogical};
use crate::positioned::{AbsolutelyPositionedBox, PositioningContext};
use crate::sizing::ContentSizes;
use crate::style_ext::{Clamp, ComputedValuesExt, PaddingBorderMargin};
use crate::{AuOrAuto, ContainingBlock, IndefiniteContainingBlock};
use crate::style_ext::{ComputedValuesExt, PaddingBorderMargin};
use crate::{ContainingBlock, IndefiniteContainingBlock};
// From gfxFontConstants.h in Firefox.
static FONT_SUBSCRIPT_OFFSET_RATIO: f32 = 0.20;
@ -1907,173 +1908,27 @@ impl IndependentFormattingContext {
offset_in_text: usize,
bidi_level: Level,
) {
let style = self.style();
let container_writing_mode = layout.containing_block.style.writing_mode;
let pbm = style.padding_border_margin(layout.containing_block);
let margin = pbm.margin.auto_is(Au::zero);
let pbm_sums = pbm.padding + pbm.border + margin;
// We need to know the inline size of the atomic before deciding whether to do the line break.
let (fragments, content_rect, baselines, mut child_positioning_context) = match self {
IndependentFormattingContext::Replaced(replaced) => {
let size = replaced
.contents
.used_size_as_if_inline_element(layout.containing_block, &replaced.style, &pbm)
.to_physical_size(container_writing_mode);
let fragments = replaced.contents.make_fragments(
&replaced.style,
layout.containing_block,
size,
);
let content_rect = PhysicalRect::new(PhysicalPoint::zero(), size);
(fragments, content_rect, None, None)
},
IndependentFormattingContext::NonReplaced(non_replaced) => {
let box_size = non_replaced
.style
.content_box_size(layout.containing_block, &pbm);
let max_box_size = non_replaced
.style
.content_max_box_size(layout.containing_block, &pbm);
let min_box_size = non_replaced
.style
.content_min_box_size(layout.containing_block, &pbm);
let available_inline_size =
layout.containing_block.inline_size - pbm_sums.inline_sum();
let available_block_size = layout
.containing_block
.block_size
.non_auto()
.map(|block_size| block_size - pbm_sums.block_sum());
let tentative_block_size = box_size
.block
.maybe_resolve_extrinsic(available_block_size)
.map(|v| {
let min_block_size = min_box_size
.block
.maybe_resolve_extrinsic(available_block_size)
.unwrap_or_default();
let max_block_size = max_box_size
.block
.maybe_resolve_extrinsic(available_block_size);
v.clamp_between_extremums(min_block_size, max_block_size)
})
.map_or(AuOrAuto::Auto, AuOrAuto::LengthPercentage);
let style = non_replaced.style.clone();
let mut get_content_size = || {
let containing_block_for_children =
IndefiniteContainingBlock::new_for_style_and_block_size(
&style,
tentative_block_size,
);
non_replaced
.inline_content_sizes(layout.layout_context, &containing_block_for_children)
};
// https://drafts.csswg.org/css2/visudet.html#inlineblock-width
let tentative_inline_size = box_size.inline.resolve(
Size::fit_content,
available_inline_size,
&mut get_content_size,
);
// https://drafts.csswg.org/css2/visudet.html#min-max-widths
// In this case “applying the rules above again” with a non-auto inline-size
// always results in that size.
let min_inline_size = min_box_size
.inline
.resolve_non_initial(available_inline_size, &mut get_content_size)
.unwrap_or_default();
let max_inline_size = max_box_size
.inline
.resolve_non_initial(available_inline_size, &mut get_content_size);
let inline_size =
tentative_inline_size.clamp_between_extremums(min_inline_size, max_inline_size);
let containing_block_for_children = ContainingBlock {
inline_size,
block_size: tentative_block_size,
style: &non_replaced.style,
};
assert_eq!(
layout.containing_block.style.writing_mode.is_horizontal(),
containing_block_for_children
.style
.writing_mode
.is_horizontal(),
"Mixed horizontal and vertical writing modes are not supported yet"
);
let mut positioning_context =
PositioningContext::new_for_style(&non_replaced.style)
.unwrap_or_else(|| PositioningContext::new_for_subtree(true));
let independent_layout = non_replaced.layout(
layout.layout_context,
&mut positioning_context,
&containing_block_for_children,
layout.containing_block,
);
let (inline_size, block_size) = match independent_layout
.content_inline_size_for_table
{
Some(inline) => (inline, independent_layout.content_block_size),
None => {
// https://drafts.csswg.org/css2/visudet.html#block-root-margin
let stretch_size =
available_block_size.unwrap_or(independent_layout.content_block_size);
let mut get_content_size = || independent_layout.content_block_size.into();
let min_block_size = min_box_size
.block
.resolve_non_initial(stretch_size, &mut get_content_size)
.unwrap_or_default();
let max_block_size = max_box_size
.block
.resolve_non_initial(stretch_size, &mut get_content_size);
let block_size = tentative_block_size
.auto_is(|| independent_layout.content_block_size)
.clamp_between_extremums(min_block_size, max_block_size);
(inline_size, block_size)
},
};
let content_rect = PhysicalRect::new(
PhysicalPoint::zero(),
LogicalVec2 {
block: block_size,
inline: inline_size,
}
.to_physical_size(container_writing_mode),
);
(
independent_layout.fragments,
content_rect,
Some(independent_layout.baselines),
Some(positioning_context),
)
},
};
let mut child_positioning_context = PositioningContext::new_for_style(self.style())
.unwrap_or_else(|| PositioningContext::new_for_subtree(true));
let IndependentLayoutResult {
mut fragment,
baselines,
pbm_sums,
} = self.layout_float_or_atomic_inline(
layout.layout_context,
&mut child_positioning_context,
layout.containing_block,
);
// Offset the content rectangle by the physical offset of the padding, border, and margin.
let container_writing_mode = layout.containing_block.style.writing_mode;
let pbm_physical_offset = pbm_sums
.start_offset()
.to_physical_size(container_writing_mode);
let content_rect = content_rect.translate(pbm_physical_offset.to_vector());
let fragment = BoxFragment::new(
self.base_fragment_info(),
self.style().clone(),
fragments,
content_rect,
pbm.padding.to_physical(container_writing_mode),
pbm.border.to_physical(container_writing_mode),
margin.to_physical(container_writing_mode),
None, /* clearance */
CollapsedBlockMargins::zero(),
);
fragment.content_rect = fragment
.content_rect
.translate(pbm_physical_offset.to_vector());
// Apply baselines if necessary.
let mut fragment = match baselines {
@ -2083,14 +1938,18 @@ impl IndependentFormattingContext {
// Lay out absolutely positioned children if this new atomic establishes a containing block
// for absolutes.
if let Some(positioning_context) = child_positioning_context.as_mut() {
let positioning_context = if matches!(self, IndependentFormattingContext::Replaced(_)) {
None
} else {
if fragment
.style
.establishes_containing_block_for_absolute_descendants(fragment.base.flags)
{
positioning_context.layout_collected_children(layout.layout_context, &mut fragment);
child_positioning_context
.layout_collected_children(layout.layout_context, &mut fragment);
}
}
Some(child_positioning_context)
};
if layout.text_wrap_mode == TextWrapMode::Wrap &&
!layout
@ -2122,7 +1981,7 @@ impl IndependentFormattingContext {
AtomicLineItem {
fragment,
size,
positioning_context: child_positioning_context,
positioning_context,
baseline_offset_in_parent,
baseline_offset_in_item: baseline_offset,
bidi_level,

View file

@ -14,7 +14,7 @@ use style::computed_values::clear::T as Clear;
use style::computed_values::float::T as Float;
use style::properties::ComputedValues;
use style::servo::selector_parser::PseudoElement;
use style::values::computed::Size;
use style::values::computed::Size as StyleSize;
use style::values::specified::align::AlignFlags;
use style::values::specified::{Display, TextAlignKeyword};
use style::Zero;
@ -25,14 +25,15 @@ use crate::flow::float::{
ContainingBlockPositionInfo, FloatBox, PlacementAmongFloats, SequentialLayoutState,
};
use crate::formatting_contexts::{
Baselines, IndependentFormattingContext, IndependentLayout, NonReplacedFormattingContext,
Baselines, IndependentFormattingContext, IndependentLayout, IndependentLayoutResult,
NonReplacedFormattingContext,
};
use crate::fragment_tree::{
BaseFragmentInfo, BoxFragment, CollapsedBlockMargins, CollapsedMargin, Fragment, FragmentFlags,
};
use crate::geom::{
AuOrAuto, LogicalRect, LogicalSides, LogicalVec2, PhysicalRect, PhysicalSides, ToLogical,
ToLogicalWithContainingBlock,
AuOrAuto, LogicalRect, LogicalSides, LogicalVec2, PhysicalPoint, PhysicalRect, PhysicalSides,
Size, ToLogical, ToLogicalWithContainingBlock,
};
use crate::positioned::{AbsolutelyPositionedBox, PositioningContext, PositioningContextLength};
use crate::replaced::ReplacedContent;
@ -1904,13 +1905,182 @@ impl<'container> PlacementState<'container> {
}
}
fn block_size_is_zero_or_intrinsic(size: &Size, containing_block: &ContainingBlock) -> bool {
fn block_size_is_zero_or_intrinsic(size: &StyleSize, containing_block: &ContainingBlock) -> bool {
match size {
Size::Auto | Size::MinContent | Size::MaxContent | Size::FitContent | Size::Stretch => true,
Size::LengthPercentage(ref lp) => {
StyleSize::Auto |
StyleSize::MinContent |
StyleSize::MaxContent |
StyleSize::FitContent |
StyleSize::Stretch => true,
StyleSize::LengthPercentage(ref lp) => {
// TODO: Should this resolve definite percentages? Blink does it, Gecko and WebKit don't.
lp.is_definitely_zero() ||
(lp.0.has_percentage() && containing_block.block_size.is_auto())
},
}
}
impl IndependentFormattingContext {
pub(crate) fn layout_float_or_atomic_inline(
&mut self,
layout_context: &LayoutContext,
child_positioning_context: &mut PositioningContext,
containing_block: &ContainingBlock,
) -> IndependentLayoutResult {
let style = self.style();
let container_writing_mode = containing_block.style.writing_mode;
let pbm = style.padding_border_margin(containing_block);
let margin = pbm.margin.auto_is(Au::zero);
let pbm_sums = pbm.padding + pbm.border + margin;
let (fragments, content_rect, baselines) = match self {
IndependentFormattingContext::Replaced(replaced) => {
// https://drafts.csswg.org/css2/visudet.html#float-replaced-width
// https://drafts.csswg.org/css2/visudet.html#inline-replaced-height
let content_size = replaced
.contents
.used_size_as_if_inline_element(containing_block, &replaced.style, &pbm)
.to_physical_size(container_writing_mode);
let fragments = replaced.contents.make_fragments(
&replaced.style,
containing_block,
content_size,
);
let content_rect = PhysicalRect::new(PhysicalPoint::zero(), content_size);
(fragments, content_rect, None)
},
IndependentFormattingContext::NonReplaced(non_replaced) => {
let style = non_replaced.style.clone();
let box_size = style.content_box_size(containing_block, &pbm);
let max_box_size = style.content_max_box_size(containing_block, &pbm);
let min_box_size = style.content_min_box_size(containing_block, &pbm);
let available_inline_size = containing_block.inline_size - pbm_sums.inline_sum();
let available_block_size = containing_block
.block_size
.non_auto()
.map(|block_size| block_size - pbm_sums.block_sum());
let tentative_block_size = box_size
.block
.maybe_resolve_extrinsic(available_block_size)
.map(|size| {
let min_block_size = min_box_size
.block
.maybe_resolve_extrinsic(available_block_size)
.unwrap_or_default();
let max_block_size = max_box_size
.block
.maybe_resolve_extrinsic(available_block_size);
size.clamp_between_extremums(min_block_size, max_block_size)
})
.map_or(AuOrAuto::Auto, AuOrAuto::LengthPercentage);
let mut get_content_size = || {
let containing_block_for_children =
IndefiniteContainingBlock::new_for_style_and_block_size(
&style,
tentative_block_size,
);
non_replaced
.inline_content_sizes(layout_context, &containing_block_for_children)
};
// https://drafts.csswg.org/css2/visudet.html#float-width
// https://drafts.csswg.org/css2/visudet.html#inlineblock-width
let tentative_inline_size = box_size.inline.resolve(
Size::fit_content,
available_inline_size,
&mut get_content_size,
);
// https://drafts.csswg.org/css2/visudet.html#min-max-widths
// In this case “applying the rules above again” with a non-auto inline-size
// always results in that size.
let min_inline_size = min_box_size
.inline
.resolve_non_initial(available_inline_size, &mut get_content_size)
.unwrap_or_default();
let max_inline_size = max_box_size
.inline
.resolve_non_initial(available_inline_size, &mut get_content_size);
let inline_size =
tentative_inline_size.clamp_between_extremums(min_inline_size, max_inline_size);
let containing_block_for_children = ContainingBlock {
inline_size,
block_size: tentative_block_size,
style: &style,
};
assert_eq!(
container_writing_mode.is_horizontal(),
style.writing_mode.is_horizontal(),
"Mixed horizontal and vertical writing modes are not supported yet"
);
let independent_layout = non_replaced.layout(
layout_context,
child_positioning_context,
&containing_block_for_children,
containing_block,
);
let (inline_size, block_size) = match independent_layout
.content_inline_size_for_table
{
Some(inline) => (inline, independent_layout.content_block_size),
None => {
// https://drafts.csswg.org/css2/visudet.html#block-root-margin
let stretch_size =
available_block_size.unwrap_or(independent_layout.content_block_size);
let mut get_content_size = || independent_layout.content_block_size.into();
let min_block_size = min_box_size
.block
.resolve_non_initial(stretch_size, &mut get_content_size)
.unwrap_or_default();
let max_block_size = max_box_size
.block
.resolve_non_initial(stretch_size, &mut get_content_size);
let block_size = tentative_block_size
.auto_is(|| independent_layout.content_block_size)
.clamp_between_extremums(min_block_size, max_block_size);
(inline_size, block_size)
},
};
let content_size = LogicalVec2 {
block: block_size,
inline: inline_size,
}
.to_physical_size(container_writing_mode);
let content_rect = PhysicalRect::new(PhysicalPoint::zero(), content_size);
(
independent_layout.fragments,
content_rect,
Some(independent_layout.baselines),
)
},
};
let fragment = BoxFragment::new(
self.base_fragment_info(),
self.style().clone(),
fragments,
content_rect,
pbm.padding.to_physical(container_writing_mode),
pbm.border.to_physical(container_writing_mode),
margin.to_physical(container_writing_mode),
// Floats can have clearance, but it's handled internally by the float placement logic,
// so there's no need to store it explicitly in the fragment.
// And atomic inlines don't have clearance.
None, /* clearance */
CollapsedBlockMargins::zero(),
);
IndependentLayoutResult {
fragment,
baselines,
pbm_sums,
}
}
}

View file

@ -14,7 +14,8 @@ use crate::dom::NodeExt;
use crate::dom_traversal::{Contents, NodeAndStyleInfo};
use crate::flexbox::FlexContainer;
use crate::flow::BlockFormattingContext;
use crate::fragment_tree::{BaseFragmentInfo, Fragment, FragmentFlags};
use crate::fragment_tree::{BaseFragmentInfo, BoxFragment, Fragment, FragmentFlags};
use crate::geom::LogicalSides;
use crate::positioned::PositioningContext;
use crate::replaced::ReplacedContent;
use crate::sizing::{self, ContentSizes};
@ -91,6 +92,12 @@ pub(crate) struct IndependentLayout {
pub baselines: Baselines,
}
pub(crate) struct IndependentLayoutResult {
pub fragment: BoxFragment,
pub baselines: Option<Baselines>,
pub pbm_sums: LogicalSides<Au>,
}
impl IndependentFormattingContext {
pub fn construct<'dom, Node: NodeExt<'dom>>(
context: &LayoutContext,