Implement keyword sizes on floats (#33666)

Adds support for min-content, max-content, fit-content and stretch,
for floated elements.

Signed-off-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
Oriol Brufau 2024-10-08 15:07:59 +02:00 committed by GitHub
parent 39133a5478
commit c1b744b2b2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 290 additions and 41 deletions

View file

@ -27,10 +27,10 @@ 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, ToLogical};
use crate::geom::{LogicalRect, LogicalVec2, PhysicalPoint, PhysicalRect, Size, ToLogical};
use crate::positioned::{relative_adjustement, PositioningContext};
use crate::style_ext::{Clamp, ComputedValuesExt, DisplayInside, PaddingBorderMargin};
use crate::{ContainingBlock, IndefiniteContainingBlock};
use crate::{AuOrAuto, ContainingBlock, IndefiniteContainingBlock};
/// A floating box.
#[derive(Debug, Serialize)]
@ -914,43 +914,61 @@ impl FloatBox {
// Calculate inline size.
// https://drafts.csswg.org/css2/#float-width
let style = non_replaced.style.clone();
let box_size = style
.content_box_size_deprecated(containing_block, &pbm)
.map(|v| v.map(Au::from));
let max_box_size = style
.content_max_box_size_deprecated(containing_block, &pbm)
.map(|v| v.map(Au::from));
let min_box_size = style
.content_min_box_size_deprecated(containing_block, &pbm)
.map(|v| v.map(Au::from))
.auto_is(Au::zero);
let block_size = box_size.block.map(|size| {
size.clamp_between_extremums(min_box_size.block, max_box_size.block)
});
let tentative_inline_size = box_size.inline.auto_is(|| {
let available_size =
containing_block.inline_size - pbm_sums.inline_sum();
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, block_size,
&style,
tentative_block_size,
);
non_replaced
.inline_content_sizes(
layout_context,
&containing_block_for_children,
)
.shrink_to_fit(available_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_box_size.inline, max_box_size.inline);
.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,
block_size: tentative_block_size,
style: &non_replaced.style,
};
let independent_layout = non_replaced.layout(
@ -964,17 +982,23 @@ impl FloatBox {
Some(inline_size) => {
(independent_layout.content_block_size, inline_size)
},
None => (
block_size.auto_is(|| {
independent_layout
.content_block_size
.clamp_between_extremums(
min_box_size.block,
max_box_size.block,
)
}),
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,

View file

@ -16,6 +16,7 @@ use style::values::generics::length::GenericLengthPercentageOrAuto as AutoOr;
use style::Zero;
use style_traits::CSSPixel;
use crate::sizing::ContentSizes;
use crate::ContainingBlock;
pub type PhysicalPoint<U> = euclid::Point2D<U, CSSPixel>;
@ -656,12 +657,24 @@ impl<T> Default for Size<T> {
}
}
impl<T: Clone> Size<T> {
impl<T> Size<T> {
#[inline]
pub(crate) fn fit_content() -> Self {
Self::Keyword(SizeKeyword::FitContent)
}
#[inline]
pub(crate) fn is_keyword(&self) -> bool {
matches!(self, Self::Keyword(_))
}
#[inline]
pub(crate) fn is_initial(&self) -> bool {
matches!(self, Self::Keyword(SizeKeyword::Initial))
}
}
impl<T: Clone> Size<T> {
#[inline]
pub(crate) fn to_numeric(&self) -> Option<T> {
match self {
@ -761,3 +774,66 @@ impl LogicalVec2<Size<LengthPercentage>> {
}
}
}
impl Size<Au> {
/// Resolves any size into a numerical value.
#[inline]
pub(crate) fn resolve(
&self,
get_initial_behavior: impl Fn() -> Self,
stretch_size: Au,
get_content_size: &mut impl FnMut() -> ContentSizes,
) -> Au {
if self.is_initial() {
let initial_behavior = get_initial_behavior();
assert!(!initial_behavior.is_initial());
initial_behavior.resolve_non_initial(stretch_size, get_content_size)
} else {
self.resolve_non_initial(stretch_size, get_content_size)
}
.unwrap()
}
/// Resolves a non-initial size into a numerical value.
/// Returns `None` if the size is the initial one.
#[inline]
pub(crate) fn resolve_non_initial(
&self,
stretch_size: Au,
get_content_size: &mut impl FnMut() -> ContentSizes,
) -> Option<Au> {
match self {
Self::Keyword(SizeKeyword::Initial) => None,
Self::Keyword(SizeKeyword::MinContent) => Some(get_content_size().min_content),
Self::Keyword(SizeKeyword::MaxContent) => Some(get_content_size().max_content),
Self::Keyword(SizeKeyword::FitContent) => {
Some(get_content_size().shrink_to_fit(stretch_size))
},
Self::Keyword(SizeKeyword::Stretch) => Some(stretch_size),
Self::Numeric(numeric) => Some(*numeric),
}
}
/// Tries to resolve an extrinsic size into a numerical value.
/// Extrinsic sizes are those based on the context of an element, without regard for its contents.
/// <https://drafts.csswg.org/css-sizing-3/#extrinsic>
///
/// Returns `None` if either:
/// - The size is intrinsic.
/// - The size is the initial one.
/// TODO: should we allow it to behave as `stretch` instead of assuming it's intrinsic?
/// - The provided `stretch_size` is `None` but we need its value.
#[inline]
pub(crate) fn maybe_resolve_extrinsic(&self, stretch_size: Option<Au>) -> Option<Au> {
match self {
Self::Keyword(keyword) => match keyword {
SizeKeyword::Initial |
SizeKeyword::MinContent |
SizeKeyword::MaxContent |
SizeKeyword::FitContent => None,
SizeKeyword::Stretch => stretch_size,
},
Self::Numeric(numeric) => Some(*numeric),
}
}
}