Complete implementation of keyword sizes for block layout (#34641)

Adds support for min-content, max-content, fit-content and stretch,
for the case that was missing from #34568: block-level elements that
establish an independent formatting context, when there are floats.

Signed-off-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
Oriol Brufau 2024-12-16 19:17:46 +01:00 committed by GitHub
parent 53740fdd16
commit 3d816d6d24
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 165 additions and 78 deletions

View file

@ -41,10 +41,7 @@ use crate::layout_box_base::LayoutBoxBase;
use crate::positioned::{AbsolutelyPositionedBox, PositioningContext, PositioningContextLength};
use crate::replaced::ReplacedContents;
use crate::sizing::{self, ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult};
use crate::style_ext::{
Clamp, ComputedValuesExt, ContentBoxSizesAndPBM, ContentBoxSizesAndPBMDeprecated,
PaddingBorderMargin,
};
use crate::style_ext::{Clamp, ComputedValuesExt, ContentBoxSizesAndPBM, PaddingBorderMargin};
use crate::{
ConstraintSpace, ContainingBlock, ContainingBlockSize, IndefiniteContainingBlock,
SizeConstraint,
@ -1143,23 +1140,15 @@ impl IndependentNonReplacedContents {
containing_block: &ContainingBlock<'_>,
sequential_layout_state: &mut SequentialLayoutState,
) -> BoxFragment {
let style = &base.style;
let containing_block_writing_mode = containing_block.style.writing_mode;
let ContentBoxSizesAndPBMDeprecated {
let ContentBoxSizesAndPBM {
content_box_size,
content_min_box_size,
content_max_box_size,
pbm,
depends_on_block_constraints,
} = base
.style
.content_box_sizes_and_padding_border_margin(&containing_block.into())
.into();
let content_min_box_size = content_min_box_size.auto_is(Au::zero);
let block_size = content_box_size.block.map(|block_size| {
block_size
.clamp_between_extremums(content_min_box_size.block, content_max_box_size.block)
});
} = style.content_box_sizes_and_padding_border_margin(&containing_block.into());
let (margin_block_start, margin_block_end) =
solve_block_margins_for_in_flow_block_level(&pbm);
@ -1178,7 +1167,6 @@ impl IndependentNonReplacedContents {
let mut content_size;
let mut layout;
let mut placement_rect;
let style = &base.style;
// First compute the clear position required by the 'clear' property.
// The code below may then add extra clearance when the element can't fit
@ -1191,16 +1179,96 @@ impl IndependentNonReplacedContents {
sequential_layout_state.position_without_clearance(&collapsed_margin_block_start)
});
if let AuOrAuto::LengthPercentage(ref inline_size) = content_box_size.inline {
let inline_size = inline_size
.clamp_between_extremums(content_min_box_size.inline, content_max_box_size.inline);
// Then compute a tentative block size, only taking extrinsic values into account.
let margin = pbm.margin.auto_is(Au::zero);
let pbm_sums = pbm.padding + pbm.border + margin;
let available_block_size = containing_block
.size
.block
.non_auto()
.map(|block_size| Au::zero().max(block_size - pbm_sums.block_sum()));
let preferred_block_size = content_box_size
.block
.maybe_resolve_extrinsic(available_block_size);
let min_block_size = content_min_box_size
.block
.maybe_resolve_extrinsic(available_block_size)
.unwrap_or_default();
let max_block_size = content_max_box_size
.block
.maybe_resolve_extrinsic(available_block_size);
let tentative_block_size =
SizeConstraint::new(preferred_block_size, min_block_size, max_block_size);
// With the tentative block size we can compute the inline min/max-content sizes.
let inline_content_sizes = LazyCell::new(|| {
let constraint_space = ConstraintSpace::new(
tentative_block_size,
style.writing_mode,
self.preferred_aspect_ratio(),
);
base.inline_content_sizes(layout_context, &constraint_space, self)
.sizes
});
// The final inline size can depend on the available space, which depends on where
// we are placing the box, since floats reduce the available space.
// TODO: this logic could be refined further, since even if some of the 3 sizes
// depends on the available space, the resulting size might not. For example,
// `min-width: 200px; width: 100px; max-width: stretch`.
let inline_size_depends_on_available_space = matches!(
content_box_size.inline,
Size::Initial | Size::Stretch | Size::FitContent
) || matches!(
content_min_box_size.inline,
Size::Stretch | Size::FitContent
) || matches!(
content_max_box_size.inline,
Size::Stretch | Size::FitContent
);
let compute_inline_size = |stretch_size| {
let preferred_inline_size =
content_box_size
.inline
.resolve(Size::Stretch, stretch_size, &inline_content_sizes);
let min_inline_size = content_min_box_size
.inline
.resolve_non_initial(stretch_size, &inline_content_sizes)
.unwrap_or_default();
let max_inline_size = content_max_box_size
.inline
.resolve_non_initial(stretch_size, &inline_content_sizes);
preferred_inline_size.clamp_between_extremums(min_inline_size, max_inline_size)
};
let compute_block_size = |layout: &IndependentLayout| {
let stretch_size = available_block_size.unwrap_or(layout.content_block_size);
let block_contents_size = LazyCell::new(|| layout.content_block_size.into());
let min_block_size = content_min_box_size
.block
.resolve_non_initial(stretch_size, &block_contents_size)
.unwrap_or_default();
let max_block_size = content_max_box_size
.block
.resolve_non_initial(stretch_size, &block_contents_size);
tentative_block_size
.to_definite()
.unwrap_or(layout.content_block_size)
.clamp_between_extremums(min_block_size, max_block_size)
};
if !inline_size_depends_on_available_space {
// If the inline size doesn't depend on the available inline space, we can just
// compute it with an available inline space of zero. Then, after layout we can
// compute the block size, and finally place among floats.
let inline_size = compute_inline_size(Au::zero());
layout = self.layout(
layout_context,
positioning_context,
&ContainingBlock {
size: ContainingBlockSize {
inline: inline_size,
block: block_size,
block: tentative_block_size.to_auto_or(),
},
style,
},
@ -1214,12 +1282,7 @@ impl IndependentNonReplacedContents {
};
} else {
content_size = LogicalVec2 {
block: block_size.auto_is(|| {
layout.content_block_size.clamp_between_extremums(
content_min_box_size.block,
content_max_box_size.block,
)
}),
block: compute_block_size(&layout),
inline: inline_size,
};
}
@ -1232,10 +1295,24 @@ impl IndependentNonReplacedContents {
);
placement_rect = placement.place();
} else {
// Create a PlacementAmongFloats using the minimum size in all dimensions as the object size.
// If the inline size depends on the available space, then we need to iterate
// the various placement candidates, resolve both the inline and block sizes
// on each one placement area, and then check if the box actually fits it.
// As an optimization, we first compute a lower bound of the final box size,
// and skip placement candidates where not even the lower bound would fit.
let minimum_size_of_block = LogicalVec2 {
inline: content_min_box_size.inline,
block: block_size.auto_is(|| content_min_box_size.block),
// For the lower bound of the inline size, simply assume no available space.
// TODO: this won't work for things like `calc-size(stretch, 100px - size)`,
// which should result in a bigger size when the available space gets smaller.
inline: compute_inline_size(Au::zero()),
block: match tentative_block_size {
// If we were able to resolve the preferred and maximum block sizes,
// use the tentative block size (it takes the 3 sizes into account).
SizeConstraint::Definite(size) if max_block_size.is_some() => size,
// Oherwise the preferred or maximum block size might end up being zero,
// so can only rely on the minimum block size.
_ => min_block_size,
},
} + pbm.padding_border_sums;
let mut placement = PlacementAmongFloats::new(
&sequential_layout_state.floats,
@ -1247,12 +1324,9 @@ impl IndependentNonReplacedContents {
loop {
// First try to place the block using the minimum size as the object size.
placement_rect = placement.place();
let proposed_inline_size = (placement_rect.size.inline -
pbm.padding_border_sums.inline)
.clamp_between_extremums(
content_min_box_size.inline,
content_max_box_size.inline,
);
let available_inline_size =
placement_rect.size.inline - pbm.padding_border_sums.inline;
let proposed_inline_size = compute_inline_size(available_inline_size);
// Now lay out the block using the inline size we calculated from the placement.
// Later we'll check to see if the resulting block size is compatible with the
@ -1264,7 +1338,7 @@ impl IndependentNonReplacedContents {
&ContainingBlock {
size: ContainingBlockSize {
inline: proposed_inline_size,
block: block_size,
block: tentative_block_size.to_auto_or(),
},
style,
},
@ -1291,12 +1365,7 @@ impl IndependentNonReplacedContents {
};
} else {
content_size = LogicalVec2 {
block: block_size.auto_is(|| {
layout.content_block_size.clamp_between_extremums(
content_min_box_size.block,
content_max_box_size.block,
)
}),
block: compute_block_size(&layout),
inline: proposed_inline_size,
};
}

View file

@ -243225,6 +243225,19 @@
{}
]
],
"bfc-next-to-float-2.html": [
"9683f92d49c11c2a436a023a0f758ae30c76da54",
[
null,
[
[
"/css/reference/ref-filled-green-100px-square.xht",
"=="
]
],
{}
]
],
"block-height-003.tentative.html": [
"a469f8d5df2968f8e8ebfb68593563447bf11a10",
[

View file

@ -1,36 +0,0 @@
[grid-container-percentage-001.html]
[.grid 3]
expected: FAIL
[.grid 4]
expected: FAIL
[.grid 5]
expected: FAIL
[.grid 8]
expected: FAIL
[.grid 9]
expected: FAIL
[.grid 10]
expected: FAIL
[.grid 13]
expected: FAIL
[.grid 14]
expected: FAIL
[.grid 15]
expected: FAIL
[.grid 18]
expected: FAIL
[.grid 19]
expected: FAIL
[.grid 20]
expected: FAIL

View file

@ -0,0 +1,41 @@
<!DOCTYPE html>
<link rel="author" title="Oriol Brufau" href="obrufau@igalia.com">
<link rel="help" href="https://drafts.csswg.org/css-sizing-4/#stretch-fit-sizing">
<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/4028">
<link rel="help" href="https://drafts.csswg.org/css2/#floats">
<meta name="assert" content="The border box of a block-level element that
establishes an independent formatting context can't overlap the margin box
of any float in the same block formatting context.
The stretch size needs to respect that.
">
<link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
<style>
#reference-overlapped-red {
position: absolute;
background-color: red;
width: 100px;
height: 100px;
z-index: -1;
}
.stretch {
display: flow-root;
height: 25px;
background: green;
}
</style>
<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
<div id="reference-overlapped-red"></div>
<div style="width:200px; margin-left: -100px;">
<div style="float: left; width: 100px; height: 75px;"></div>
<div class="stretch" style="width: 0; min-width: stretch"></div>
<div class="stretch" style="width: 1000px; max-width: stretch"></div>
<div class="stretch" style="width: stretch; padding: 0 10px; border: solid green; border-width: 0 10px; margin-left: 10px"></div>
</div>
<div style="width:250px; margin-left: -150px;">
<div style="float: left; clear: left; width: 100px; height: 1px;"></div>
<div style="float: left; clear: left; width: 150px; height: 1px;"></div>
<div style="float: left; clear: left; width: 125px; height: 1px;"></div>
<div class="stretch" style="width: stretch"></div>
</div>