1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-26 05:27:46 +00:00

LibWeb: Reorganize layout algorithms around available space

This is a big and messy change, and here's the gist:

- AvaliableSpace is now 2x AvailableSize (width and height)

- Layout algorithms are redesigned around the idea of available space

- When doing layout across nested formatting contexts, the parent
  context tells the child context how much space is available for the
  child's root box in both axes.

- "Available space" replaces "containing block width" in most places.

- The width and height in a box's UsedValues are considered to be
  definite after they're assigned to. Marking something as having
  definite size is no longer a separate step,

This probably introduces various regressions, but the big win here is
that our layout system now works with available space, just like the
specs are written. Fixing issues will be much easier going forward,
since you don't need to do nearly as much conversion from "spec logic"
to "LibWeb logic" as you previously did.
This commit is contained in:
Andreas Kling 2022-09-27 15:29:17 +02:00
parent b55c4ccdf7
commit 9c44634ca5
19 changed files with 651 additions and 652 deletions

View file

@ -33,24 +33,18 @@ void FormattingContext::run_intrinsic_sizing(Box const& box)
{
auto& box_state = m_state.get_mutable(box);
if (box_state.has_definite_width())
box_state.set_content_width(box.computed_values().width().resolved(box, CSS::Length::make_px(containing_block_width_for(box))).to_px(box));
if (box_state.has_definite_height())
box_state.set_content_height(box.computed_values().height().resolved(box, CSS::Length::make_px(containing_block_height_for(box))).to_px(box));
auto to_available_space = [&](SizeConstraint constraint) {
auto to_available_size = [&](SizeConstraint constraint) {
if (constraint == SizeConstraint::MinContent)
return AvailableSpace::make_min_content();
return AvailableSize::make_min_content();
if (constraint == SizeConstraint::MaxContent)
return AvailableSpace::make_max_content();
return AvailableSpace::make_indefinite();
return AvailableSize::make_max_content();
return AvailableSize::make_indefinite();
};
auto available_width = to_available_space(box_state.width_constraint);
auto available_height = to_available_space(box_state.height_constraint);
auto available_width = to_available_size(box_state.width_constraint);
auto available_height = to_available_size(box_state.height_constraint);
run(box, LayoutMode::IntrinsicSizing, available_width, available_height);
run(box, LayoutMode::IntrinsicSizing, AvailableSpace(available_width, available_height));
}
bool FormattingContext::creates_block_formatting_context(Box const& box)
@ -117,7 +111,7 @@ OwnPtr<FormattingContext> FormattingContext::create_independent_formatting_conte
{
}
virtual float automatic_content_height() const override { return 0; };
virtual void run(Box const&, LayoutMode, AvailableSpace const&, AvailableSpace const&) override { }
virtual void run(Box const&, LayoutMode, AvailableSpace const&) override { }
};
return make<ReplacedFormattingContext>(state, child_box);
}
@ -159,7 +153,7 @@ OwnPtr<FormattingContext> FormattingContext::create_independent_formatting_conte
{
}
virtual float automatic_content_height() const override { return 0; };
virtual void run(Box const&, LayoutMode, AvailableSpace const&, AvailableSpace const&) override { }
virtual void run(Box const&, LayoutMode, AvailableSpace const&) override { }
};
return make<DummyFormattingContext>(state, child_box);
}
@ -168,7 +162,7 @@ OwnPtr<FormattingContext> FormattingContext::create_independent_formatting_conte
return {};
}
OwnPtr<FormattingContext> FormattingContext::layout_inside(Box const& child_box, LayoutMode layout_mode)
OwnPtr<FormattingContext> FormattingContext::layout_inside(Box const& child_box, LayoutMode layout_mode, AvailableSpace const& available_space)
{
{
// OPTIMIZATION: If we're doing intrinsic sizing and `child_box` has definite size in both axes,
@ -189,9 +183,9 @@ OwnPtr<FormattingContext> FormattingContext::layout_inside(Box const& child_box,
auto independent_formatting_context = create_independent_formatting_context_if_needed(m_state, child_box);
if (independent_formatting_context)
independent_formatting_context->run(child_box, layout_mode, AvailableSpace::make_indefinite(), AvailableSpace::make_indefinite());
independent_formatting_context->run(child_box, layout_mode, available_space);
else
run(child_box, layout_mode, AvailableSpace::make_indefinite(), AvailableSpace::make_indefinite());
run(child_box, layout_mode, available_space);
return independent_formatting_context;
}
@ -262,16 +256,19 @@ static Gfx::FloatSize solve_replaced_size_constraint(LayoutState const& state, f
return { w, h };
}
float FormattingContext::compute_auto_height_for_block_level_element(LayoutState const& state, Box const& box)
float FormattingContext::compute_auto_height_for_block_level_element(Box const& box) const
{
if (creates_block_formatting_context(box))
return compute_auto_height_for_block_formatting_context_root(state, verify_cast<BlockContainer>(box));
return compute_auto_height_for_block_formatting_context_root(verify_cast<BlockContainer>(box));
auto const& box_state = state.get(box);
auto const& box_state = m_state.get(box);
auto display = box.computed_values().display();
if (display.is_flex_inside())
return box_state.content_height();
if (display.is_flex_inside()) {
// https://drafts.csswg.org/css-flexbox-1/#algo-main-container
// NOTE: The automatic block size of a block-level flex container is its max-content size.
return calculate_max_content_height(box);
}
// https://www.w3.org/TR/CSS22/visudet.html#normal-block
// 10.6.3 Block-level non-replaced elements in normal flow when 'overflow' computes to 'visible'
@ -295,7 +292,7 @@ float FormattingContext::compute_auto_height_for_block_level_element(LayoutState
if (child_box->is_list_item_marker_box())
continue;
auto const& child_box_state = state.get(*child_box);
auto const& child_box_state = m_state.get(*child_box);
// Ignore anonymous block containers with no lines. These don't count as in-flow block boxes.
if (child_box->is_anonymous() && child_box->is_block_container() && child_box_state.line_boxes.is_empty())
@ -311,7 +308,7 @@ float FormattingContext::compute_auto_height_for_block_level_element(LayoutState
}
// https://www.w3.org/TR/CSS22/visudet.html#root-height
float FormattingContext::compute_auto_height_for_block_formatting_context_root(LayoutState const& state, BlockContainer const& root)
float FormattingContext::compute_auto_height_for_block_formatting_context_root(BlockContainer const& root) const
{
// 10.6.7 'Auto' heights for block formatting context roots
Optional<float> top;
@ -320,7 +317,7 @@ float FormattingContext::compute_auto_height_for_block_formatting_context_root(L
if (root.children_are_inline()) {
// If it only has inline-level children, the height is the distance between
// the top content edge and the bottom of the bottommost line box.
auto const& line_boxes = state.get(root).line_boxes;
auto const& line_boxes = m_state.get(root).line_boxes;
top = 0;
if (!line_boxes.is_empty())
bottom = line_boxes.last().bottom();
@ -339,7 +336,7 @@ float FormattingContext::compute_auto_height_for_block_formatting_context_root(L
if ((root.computed_values().overflow_y() == CSS::Overflow::Visible) && child_box.is_floating())
return IterationDecision::Continue;
auto const& child_box_state = state.get(child_box);
auto const& child_box_state = m_state.get(child_box);
float child_box_top = child_box_state.offset.y() - child_box_state.margin_box_top();
float child_box_bottom = child_box_state.offset.y() + child_box_state.content_height() + child_box_state.margin_box_bottom();
@ -357,10 +354,10 @@ float FormattingContext::compute_auto_height_for_block_formatting_context_root(L
// In addition, if the element has any floating descendants
// whose bottom margin edge is below the element's bottom content edge,
// then the height is increased to include those edges.
for (auto* floating_box : state.get(root).floating_descendants()) {
for (auto* floating_box : m_state.get(root).floating_descendants()) {
// NOTE: Floating box coordinates are relative to their own containing block,
// which may or may not be the BFC root.
auto margin_box = margin_box_rect_in_ancestor_coordinate_space(*floating_box, root, state);
auto margin_box = margin_box_rect_in_ancestor_coordinate_space(*floating_box, root, m_state);
float floating_box_bottom_margin_edge = margin_box.bottom() + 1;
if (!bottom.has_value() || floating_box_bottom_margin_edge > bottom.value())
bottom = floating_box_bottom_margin_edge;
@ -370,7 +367,7 @@ float FormattingContext::compute_auto_height_for_block_formatting_context_root(L
}
// 10.3.2 Inline, replaced elements, https://www.w3.org/TR/CSS22/visudet.html#inline-replaced-width
float FormattingContext::tentative_width_for_replaced_element(LayoutState const& state, ReplacedBox const& box, CSS::Size const& computed_width)
float FormattingContext::tentative_width_for_replaced_element(LayoutState const& state, ReplacedBox const& box, CSS::Size const& computed_width, AvailableSpace const& available_space)
{
// Treat percentages of indefinite containing block widths as 0 (the initial width).
if (computed_width.is_percentage() && !state.get(*box.containing_block()).has_definite_width())
@ -379,7 +376,7 @@ float FormattingContext::tentative_width_for_replaced_element(LayoutState const&
auto height_of_containing_block = CSS::Length::make_px(containing_block_height_for(box, state));
auto const& computed_height = box.computed_values().height();
float used_width = computed_width.resolved(box, CSS::Length::make_px(containing_block_width_for(box, state))).to_px(box);
float used_width = computed_width.resolved(box, CSS::Length::make_px(available_space.width.to_px())).to_px(box);
// If 'height' and 'width' both have computed values of 'auto' and the element also has an intrinsic width,
// then that intrinsic width is the used value of 'width'.
@ -394,7 +391,7 @@ float FormattingContext::tentative_width_for_replaced_element(LayoutState const&
// (used height) * (intrinsic ratio)
if ((computed_height.is_auto() && computed_width.is_auto() && !box.has_intrinsic_width() && box.has_intrinsic_height() && box.has_intrinsic_aspect_ratio())
|| (computed_width.is_auto() && !computed_height.is_auto() && box.has_intrinsic_aspect_ratio())) {
return compute_height_for_replaced_element(state, box) * box.intrinsic_aspect_ratio().value();
return compute_height_for_replaced_element(state, box, available_space) * box.intrinsic_aspect_ratio().value();
}
// If 'height' and 'width' both have computed values of 'auto' and the element has an intrinsic ratio but no intrinsic height or width,
@ -414,29 +411,29 @@ float FormattingContext::tentative_width_for_replaced_element(LayoutState const&
return used_width;
}
void FormattingContext::compute_width_for_absolutely_positioned_element(Box const& box)
void FormattingContext::compute_width_for_absolutely_positioned_element(Box const& box, AvailableSpace const& available_space)
{
if (is<ReplacedBox>(box))
compute_width_for_absolutely_positioned_replaced_element(verify_cast<ReplacedBox>(box));
compute_width_for_absolutely_positioned_replaced_element(verify_cast<ReplacedBox>(box), available_space);
else
compute_width_for_absolutely_positioned_non_replaced_element(box);
compute_width_for_absolutely_positioned_non_replaced_element(box, available_space);
}
void FormattingContext::compute_height_for_absolutely_positioned_element(Box const& box)
void FormattingContext::compute_height_for_absolutely_positioned_element(Box const& box, AvailableSpace const& available_space)
{
if (is<ReplacedBox>(box))
compute_height_for_absolutely_positioned_replaced_element(verify_cast<ReplacedBox>(box));
compute_height_for_absolutely_positioned_replaced_element(static_cast<ReplacedBox const&>(box), available_space);
else
compute_height_for_absolutely_positioned_non_replaced_element(box);
compute_height_for_absolutely_positioned_non_replaced_element(box, available_space);
}
float FormattingContext::compute_width_for_replaced_element(LayoutState const& state, ReplacedBox const& box)
float FormattingContext::compute_width_for_replaced_element(LayoutState const& state, ReplacedBox const& box, AvailableSpace const& available_space)
{
// 10.3.4 Block-level, replaced elements in normal flow...
// 10.3.2 Inline, replaced elements
auto zero_value = CSS::Length::make_px(0);
auto width_of_containing_block_as_length = CSS::Length::make_px(containing_block_width_for(box, state));
auto width_of_containing_block_as_length = CSS::Length::make_px(available_space.width.to_px());
auto margin_left = box.computed_values().margin().left().resolved(box, width_of_containing_block_as_length).resolved(box);
auto margin_right = box.computed_values().margin().right().resolved(box, width_of_containing_block_as_length).resolved(box);
@ -450,14 +447,14 @@ float FormattingContext::compute_width_for_replaced_element(LayoutState const& s
auto computed_width = box.computed_values().width();
// 1. The tentative used width is calculated (without 'min-width' and 'max-width')
auto used_width = tentative_width_for_replaced_element(state, box, computed_width);
auto used_width = tentative_width_for_replaced_element(state, box, computed_width, available_space);
// 2. The tentative used width is greater than 'max-width', the rules above are applied again,
// but this time using the computed value of 'max-width' as the computed value for 'width'.
auto computed_max_width = box.computed_values().max_width();
if (!computed_max_width.is_none()) {
if (used_width > computed_max_width.resolved(box, width_of_containing_block_as_length).to_px(box)) {
used_width = tentative_width_for_replaced_element(state, box, computed_max_width);
used_width = tentative_width_for_replaced_element(state, box, computed_max_width, available_space);
}
}
@ -466,7 +463,7 @@ float FormattingContext::compute_width_for_replaced_element(LayoutState const& s
auto computed_min_width = box.computed_values().min_width();
if (!computed_min_width.is_auto()) {
if (used_width < computed_min_width.resolved(box, width_of_containing_block_as_length).to_px(box)) {
used_width = tentative_width_for_replaced_element(state, box, computed_min_width);
used_width = tentative_width_for_replaced_element(state, box, computed_min_width, available_space);
}
}
@ -475,7 +472,7 @@ float FormattingContext::compute_width_for_replaced_element(LayoutState const& s
// 10.6.2 Inline replaced elements, block-level replaced elements in normal flow, 'inline-block' replaced elements in normal flow and floating replaced elements
// https://www.w3.org/TR/CSS22/visudet.html#inline-replaced-height
float FormattingContext::tentative_height_for_replaced_element(LayoutState const& state, ReplacedBox const& box, CSS::Size const& computed_height)
float FormattingContext::tentative_height_for_replaced_element(LayoutState const& state, ReplacedBox const& box, CSS::Size const& computed_height, AvailableSpace const& available_space)
{
// Treat percentages of indefinite containing block heights as 0 (the initial height).
if (computed_height.is_percentage() && !state.get(*box.containing_block()).has_definite_height())
@ -492,7 +489,7 @@ float FormattingContext::tentative_height_for_replaced_element(LayoutState const
//
// (used width) / (intrinsic ratio)
if (computed_height.is_auto() && box.has_intrinsic_aspect_ratio())
return compute_width_for_replaced_element(state, box) / box.intrinsic_aspect_ratio().value();
return compute_width_for_replaced_element(state, box, available_space) / box.intrinsic_aspect_ratio().value();
// Otherwise, if 'height' has a computed value of 'auto', and the element has an intrinsic height, then that intrinsic height is the used value of 'height'.
if (computed_height.is_auto() && box.has_intrinsic_height())
@ -504,23 +501,23 @@ float FormattingContext::tentative_height_for_replaced_element(LayoutState const
if (computed_height.is_auto())
return 150;
return computed_height.resolved(box, CSS::Length::make_px(containing_block_height_for(box, state))).to_px(box);
return computed_height.resolved(box, CSS::Length::make_px(available_space.height.to_px())).to_px(box);
}
float FormattingContext::compute_height_for_replaced_element(LayoutState const& state, ReplacedBox const& box)
float FormattingContext::compute_height_for_replaced_element(LayoutState const& state, ReplacedBox const& box, AvailableSpace const& available_space)
{
// 10.6.2 Inline replaced elements, block-level replaced elements in normal flow,
// 'inline-block' replaced elements in normal flow and floating replaced elements
auto width_of_containing_block_as_length = CSS::Length::make_px(containing_block_width_for(box, state));
auto height_of_containing_block_as_length = CSS::Length::make_px(containing_block_height_for(box, state));
auto width_of_containing_block_as_length = CSS::Length::make_px(available_space.width.to_px());
auto height_of_containing_block_as_length = CSS::Length::make_px(available_space.height.to_px());
auto computed_width = box.computed_values().width();
auto computed_height = box.computed_values().height();
float used_height = tentative_height_for_replaced_element(state, box, computed_height);
float used_height = tentative_height_for_replaced_element(state, box, computed_height, available_space);
if (computed_width.is_auto() && computed_height.is_auto() && box.has_intrinsic_aspect_ratio()) {
float w = tentative_width_for_replaced_element(state, box, computed_width);
float w = tentative_width_for_replaced_element(state, box, computed_width, available_space);
float h = used_height;
used_height = solve_replaced_size_constraint(state, w, h, box).height();
}
@ -528,9 +525,9 @@ float FormattingContext::compute_height_for_replaced_element(LayoutState const&
return used_height;
}
void FormattingContext::compute_width_for_absolutely_positioned_non_replaced_element(Box const& box)
void FormattingContext::compute_width_for_absolutely_positioned_non_replaced_element(Box const& box, AvailableSpace const& available_space)
{
auto width_of_containing_block = containing_block_width_for(box);
auto width_of_containing_block = available_space.width.to_px();
auto width_of_containing_block_as_length = CSS::Length::make_px(width_of_containing_block);
auto& computed_values = box.computed_values();
auto zero_value = CSS::Length::make_px(0);
@ -670,17 +667,17 @@ void FormattingContext::compute_width_for_absolutely_positioned_non_replaced_ele
box_state.padding_right = padding_right;
}
void FormattingContext::compute_width_for_absolutely_positioned_replaced_element(ReplacedBox const& box)
void FormattingContext::compute_width_for_absolutely_positioned_replaced_element(ReplacedBox const& box, AvailableSpace const& available_space)
{
// 10.3.8 Absolutely positioned, replaced elements
// The used value of 'width' is determined as for inline replaced elements.
// FIXME: This const_cast is gross.
const_cast<ReplacedBox&>(box).prepare_for_replaced_layout();
m_state.get_mutable(box).set_content_width(compute_width_for_replaced_element(m_state, box));
m_state.get_mutable(box).set_content_width(compute_width_for_replaced_element(m_state, box, available_space));
}
// https://www.w3.org/TR/CSS22/visudet.html#abs-non-replaced-height
void FormattingContext::compute_height_for_absolutely_positioned_non_replaced_element(Box const& box)
void FormattingContext::compute_height_for_absolutely_positioned_non_replaced_element(Box const& box, AvailableSpace const& available_space)
{
// 10.6.4 Absolutely positioned, non-replaced elements
@ -688,7 +685,7 @@ void FormattingContext::compute_height_for_absolutely_positioned_non_replaced_el
auto& computed_values = box.computed_values();
auto width_of_containing_block = containing_block_width_for(box);
auto height_of_containing_block = containing_block_height_for(box);
auto height_of_containing_block = available_space.height.to_px();
auto width_of_containing_block_as_length = CSS::Length::make_px(width_of_containing_block);
auto height_of_containing_block_as_length = CSS::Length::make_px(height_of_containing_block);
@ -714,11 +711,11 @@ void FormattingContext::compute_height_for_absolutely_positioned_non_replaced_el
box_state.padding_bottom = computed_values.padding().bottom().resolved(box, width_of_containing_block_as_length).to_px(box);
if (computed_height.is_auto() && computed_top.is_auto() && computed_bottom.is_auto()) {
tentative_height = CSS::Length(compute_auto_height_for_block_level_element(m_state, box), CSS::Length::Type::Px);
tentative_height = CSS::Length(compute_auto_height_for_block_level_element(box), CSS::Length::Type::Px);
}
else if (computed_height.is_auto() && !computed_top.is_auto() && computed_bottom.is_auto()) {
tentative_height = CSS::Length(compute_auto_height_for_block_level_element(m_state, box), CSS::Length::Type::Px);
tentative_height = CSS::Length(compute_auto_height_for_block_level_element(box), CSS::Length::Type::Px);
box_state.inset_bottom = height_of_containing_block - tentative_height.to_px(box) - used_top - box_state.margin_top - box_state.padding_top - box_state.border_top - box_state.margin_bottom - box_state.padding_bottom - box_state.border_bottom;
}
@ -735,29 +732,22 @@ void FormattingContext::compute_height_for_absolutely_positioned_non_replaced_el
box_state.set_content_height(used_height);
}
void FormattingContext::layout_absolutely_positioned_element(Box const& box)
void FormattingContext::layout_absolutely_positioned_element(Box const& box, AvailableSpace const& available_space)
{
// https://drafts.csswg.org/css-sizing-3/#definite
// Additionally, the size of the containing block of an absolutely positioned element is always definite with respect to that element.
auto& containing_block_state = m_state.get_mutable(*box.containing_block());
auto containing_block_had_definite_width = containing_block_state.has_definite_width();
containing_block_state.set_has_definite_width(true);
auto containing_block_definite_width_guard = ScopeGuard([&] {
containing_block_state.set_has_definite_width(containing_block_had_definite_width);
});
auto& box_state = m_state.get_mutable(box);
auto width_of_containing_block = containing_block_width_for(box);
auto height_of_containing_block = containing_block_height_for(box);
auto width_of_containing_block = available_space.width.to_px();
auto height_of_containing_block = available_space.height.to_px();
auto width_of_containing_block_as_length = CSS::Length::make_px(width_of_containing_block);
auto height_of_containing_block_as_length = CSS::Length::make_px(height_of_containing_block);
auto specified_width = box.computed_values().width().resolved(box, width_of_containing_block_as_length).resolved(box);
compute_width_for_absolutely_positioned_element(box);
auto independent_formatting_context = layout_inside(box, LayoutMode::Normal);
compute_height_for_absolutely_positioned_element(box);
compute_width_for_absolutely_positioned_element(box, available_space);
auto independent_formatting_context = layout_inside(box, LayoutMode::Normal, box_state.available_inner_space_or_constraints_from(available_space));
compute_height_for_absolutely_positioned_element(box, available_space);
auto& box_state = m_state.get_mutable(box);
box_state.margin_left = box.computed_values().margin().left().resolved(box, width_of_containing_block_as_length).to_px(box);
box_state.margin_top = box.computed_values().margin().top().resolved(box, height_of_containing_block_as_length).to_px(box);
box_state.margin_right = box.computed_values().margin().right().resolved(box, width_of_containing_block_as_length).to_px(box);
@ -773,7 +763,7 @@ void FormattingContext::layout_absolutely_positioned_element(Box const& box)
box_state.inset_right = box.computed_values().inset().right().resolved(box, width_of_containing_block_as_length).to_px(box);
box_state.inset_bottom = box.computed_values().inset().bottom().resolved(box, height_of_containing_block_as_length).to_px(box);
if (box.computed_values().inset().left().is_auto() && specified_width.is_auto() && box.computed_values().inset().right().is_auto()) {
if (box.computed_values().inset().left().is_auto() && box.computed_values().width().is_auto() && box.computed_values().inset().right().is_auto()) {
if (box.computed_values().margin().left().is_auto())
box_state.margin_left = 0;
if (box.computed_values().margin().right().is_auto())
@ -826,17 +816,17 @@ void FormattingContext::layout_absolutely_positioned_element(Box const& box)
// NOTE: Absolutely positioned boxes are relative to the *padding edge* of the containing block.
used_offset.translate_by(-containing_block_state.padding_left, -containing_block_state.padding_top);
box_state.offset = used_offset;
box_state.set_content_offset(used_offset);
if (independent_formatting_context)
independent_formatting_context->parent_context_did_dimension_child_root_box();
}
void FormattingContext::compute_height_for_absolutely_positioned_replaced_element(ReplacedBox const& box)
void FormattingContext::compute_height_for_absolutely_positioned_replaced_element(ReplacedBox const& box, AvailableSpace const& available_space)
{
// 10.6.5 Absolutely positioned, replaced elements
// The used value of 'height' is determined as for inline replaced elements.
m_state.get_mutable(box).set_content_height(compute_height_for_replaced_element(m_state, box));
m_state.get_mutable(box).set_content_height(compute_height_for_replaced_element(m_state, box, available_space));
}
// https://www.w3.org/TR/css-position-3/#relpos-insets
@ -879,49 +869,49 @@ void FormattingContext::compute_inset(Box const& box)
resolve_two_opposing_insets(computed_values.inset().top(), computed_values.inset().bottom(), box_state.inset_top, box_state.inset_bottom, containing_block_height_for(box));
}
float FormattingContext::calculate_fit_content_size(float min_content_size, float max_content_size, SizeConstraint constraint, Optional<float> available_space) const
float FormattingContext::calculate_fit_content_size(float min_content_size, float max_content_size, AvailableSize const& available_size) const
{
// If the available space in a given axis is definite, equal to clamp(min-content size, stretch-fit size, max-content size)
// (i.e. max(min-content size, min(max-content size, stretch-fit size))).
if (available_space.has_value()) {
if (available_size.is_definite()) {
// FIXME: Compute the real stretch-fit size.
auto stretch_fit_size = *available_space;
auto stretch_fit_size = available_size.to_px();
auto s = max(min_content_size, min(max_content_size, stretch_fit_size));
return s;
}
// When sizing under a min-content constraint, equal to the min-content size.
if (constraint == SizeConstraint::MinContent)
if (available_size.is_min_content())
return min_content_size;
// Otherwise, equal to the max-content size in that axis.
return max_content_size;
}
float FormattingContext::calculate_fit_content_width(Layout::Box const& box, SizeConstraint constraint, Optional<float> available_space) const
float FormattingContext::calculate_fit_content_width(Layout::Box const& box, AvailableSize const& available_size) const
{
// When sizing under a min-content constraint, equal to the min-content size.
// NOTE: We check this first, to avoid needlessly calculating the max-content size.
if (constraint == SizeConstraint::MinContent)
if (available_size.is_min_content())
return calculate_min_content_width(box);
if (constraint == SizeConstraint::MaxContent)
if (available_size.is_max_content())
return calculate_max_content_width(box);
return calculate_fit_content_size(calculate_min_content_width(box), calculate_max_content_width(box), constraint, available_space);
return calculate_fit_content_size(calculate_min_content_width(box), calculate_max_content_width(box), available_size);
}
float FormattingContext::calculate_fit_content_height(Layout::Box const& box, SizeConstraint constraint, Optional<float> available_space) const
float FormattingContext::calculate_fit_content_height(Layout::Box const& box, AvailableSize const& available_size) const
{
// When sizing under a min-content constraint, equal to the min-content size.
// NOTE: We check this first, to avoid needlessly calculating the max-content size.
if (constraint == SizeConstraint::MinContent)
if (available_size.is_min_content())
return calculate_min_content_height(box);
if (constraint == SizeConstraint::MaxContent)
if (available_size.is_max_content())
return calculate_max_content_height(box);
return calculate_fit_content_size(calculate_min_content_height(box), calculate_max_content_height(box), constraint, available_space);
return calculate_fit_content_size(calculate_min_content_height(box), calculate_max_content_height(box), available_size);
}
float FormattingContext::calculate_min_content_width(Layout::Box const& box) const
@ -936,14 +926,6 @@ float FormattingContext::calculate_min_content_width(Layout::Box const& box) con
return *cache.min_content_width;
LayoutState throwaway_state(&m_state);
auto const& containing_block = *box.containing_block();
auto& containing_block_state = throwaway_state.get_mutable(containing_block);
containing_block_state.set_content_width(0);
if (!containing_block_state.has_definite_height())
containing_block_state.set_content_height(INFINITY);
else if (containing_block.computed_values().height().is_auto())
containing_block_state.set_content_height(containing_block_height_for(containing_block));
auto& box_state = throwaway_state.get_mutable(box);
box_state.width_constraint = SizeConstraint::MinContent;
@ -978,14 +960,6 @@ float FormattingContext::calculate_max_content_width(Layout::Box const& box) con
return *cache.max_content_width;
LayoutState throwaway_state(&m_state);
auto const& containing_block = *box.containing_block();
auto& containing_block_state = throwaway_state.get_mutable(containing_block);
containing_block_state.set_content_width(INFINITY);
if (!containing_block_state.has_definite_height())
containing_block_state.set_content_height(INFINITY);
else if (containing_block.computed_values().height().is_auto())
containing_block_state.set_content_height(containing_block_height_for(containing_block));
auto& box_state = throwaway_state.get_mutable(box);
box_state.width_constraint = SizeConstraint::MaxContent;
@ -1020,14 +994,6 @@ float FormattingContext::calculate_min_content_height(Layout::Box const& box) co
return *cache.min_content_height;
LayoutState throwaway_state(&m_state);
auto const& containing_block = *box.containing_block();
auto& containing_block_state = throwaway_state.get_mutable(containing_block);
containing_block_state.set_content_height(0);
if (!containing_block_state.has_definite_width())
containing_block_state.set_content_width(INFINITY);
else if (containing_block.computed_values().width().is_auto())
containing_block_state.set_content_width(containing_block_width_for(containing_block));
auto& box_state = throwaway_state.get_mutable(box);
box_state.height_constraint = SizeConstraint::MinContent;
@ -1058,14 +1024,6 @@ float FormattingContext::calculate_max_content_height(Layout::Box const& box) co
return *cache.max_content_height;
LayoutState throwaway_state(&m_state);
auto const& containing_block = *box.containing_block();
auto& containing_block_state = throwaway_state.get_mutable(containing_block);
containing_block_state.set_content_height(INFINITY);
if (!containing_block_state.has_definite_width())
containing_block_state.set_content_width(INFINITY);
else if (containing_block.computed_values().width().is_auto())
containing_block_state.set_content_width(containing_block_width_for(containing_block));
auto& box_state = throwaway_state.get_mutable(box);
box_state.height_constraint = SizeConstraint::MaxContent;
@ -1181,7 +1139,7 @@ float FormattingContext::compute_box_y_position_with_respect_to_siblings(Box con
}
// https://drafts.csswg.org/css-sizing-3/#stretch-fit-size
float FormattingContext::calculate_stretch_fit_width(Box const& box, AvailableSpace const& available_width) const
float FormattingContext::calculate_stretch_fit_width(Box const& box, AvailableSize const& available_width) const
{
// The size a box would take if its outer size filled the available space in the given axis;
// in other words, the stretch fit into the available space, if that is definite.