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
Notes: sideshowbarker 2024-07-17 06:26:47 +09:00
19 changed files with 651 additions and 652 deletions

View file

@ -804,16 +804,15 @@ void Document::update_layout()
auto& icb = static_cast<Layout::InitialContainingBlock&>(*m_layout_root);
auto& icb_state = layout_state.get_mutable(icb);
icb_state.set_has_definite_width(true);
icb_state.set_has_definite_height(true);
icb_state.set_content_width(viewport_rect.width());
icb_state.set_content_height(viewport_rect.height());
root_formatting_context.run(
*m_layout_root,
Layout::LayoutMode::Normal,
Layout::AvailableSpace::make_definite(viewport_rect.width()),
Layout::AvailableSpace::make_definite(viewport_rect.height()));
Layout::AvailableSpace(
Layout::AvailableSize::make_definite(viewport_rect.width()),
Layout::AvailableSize::make_definite(viewport_rect.height())));
}
layout_state.commit();

View file

@ -9,27 +9,27 @@
namespace Web::Layout {
AvailableSpace AvailableSpace::make_definite(float value)
AvailableSize AvailableSize::make_definite(float value)
{
return AvailableSpace { Type::Definite, value };
return AvailableSize { Type::Definite, value };
}
AvailableSpace AvailableSpace::make_indefinite()
AvailableSize AvailableSize::make_indefinite()
{
return AvailableSpace { Type::Indefinite, INFINITY };
return AvailableSize { Type::Indefinite, INFINITY };
}
AvailableSpace AvailableSpace::make_min_content()
AvailableSize AvailableSize::make_min_content()
{
return AvailableSpace { Type::MinContent, 0 };
return AvailableSize { Type::MinContent, 0 };
}
AvailableSpace AvailableSpace::make_max_content()
AvailableSize AvailableSize::make_max_content()
{
return AvailableSpace { Type::MaxContent, INFINITY };
return AvailableSize { Type::MaxContent, INFINITY };
}
String AvailableSpace::to_string() const
String AvailableSize::to_string() const
{
switch (m_type) {
case Type::Definite:
@ -44,7 +44,12 @@ String AvailableSpace::to_string() const
VERIFY_NOT_REACHED();
}
AvailableSpace::AvailableSpace(Type type, float value)
String AvailableSpace::to_string() const
{
return String::formatted("{} x {}", width, height);
}
AvailableSize::AvailableSize(Type type, float value)
: m_type(type)
, m_value(value)
{

View file

@ -12,7 +12,7 @@
namespace Web::Layout {
class AvailableSpace {
class AvailableSize {
public:
enum class Type {
Definite,
@ -21,10 +21,10 @@ public:
MaxContent,
};
static AvailableSpace make_definite(float);
static AvailableSpace make_indefinite();
static AvailableSpace make_min_content();
static AvailableSpace make_max_content();
static AvailableSize make_definite(float);
static AvailableSize make_indefinite();
static AvailableSize make_min_content();
static AvailableSize make_max_content();
bool is_definite() const { return m_type == Type::Definite; }
bool is_indefinite() const { return m_type == Type::Indefinite; }
@ -32,28 +32,51 @@ public:
bool is_max_content() const { return m_type == Type::MaxContent; }
bool is_intrinsic_sizing_constraint() const { return is_min_content() || is_max_content(); }
float definite_value() const
float to_px() const
{
VERIFY(is_definite());
return m_value;
}
float to_px() const
float to_px_or_zero() const
{
if (!is_definite())
return 0.0f;
return m_value;
}
String to_string() const;
private:
AvailableSpace(Type type, float);
AvailableSize(Type type, float);
Type m_type {};
float m_value {};
};
class AvailableSpace {
public:
AvailableSpace(AvailableSize w, AvailableSize h)
: width(move(w))
, height(move(h))
{
}
AvailableSize width;
AvailableSize height;
String to_string() const;
};
}
template<>
struct AK::Formatter<Web::Layout::AvailableSize> : Formatter<StringView> {
ErrorOr<void> format(FormatBuilder& builder, Web::Layout::AvailableSize const& available_size)
{
return Formatter<StringView>::format(builder, available_size.to_string());
}
};
template<>
struct AK::Formatter<Web::Layout::AvailableSpace> : Formatter<StringView> {
ErrorOr<void> format(FormatBuilder& builder, Web::Layout::AvailableSpace const& available_space)

View file

@ -6,6 +6,7 @@
#include <LibWeb/CSS/Length.h>
#include <LibWeb/DOM/Node.h>
#include <LibWeb/Dump.h>
#include <LibWeb/HTML/BrowsingContext.h>
#include <LibWeb/Layout/BlockContainer.h>
#include <LibWeb/Layout/BlockFormattingContext.h>
@ -41,20 +42,20 @@ bool BlockFormattingContext::is_initial() const
float BlockFormattingContext::automatic_content_height() const
{
return compute_auto_height_for_block_formatting_context_root(m_state, root());
return compute_auto_height_for_block_formatting_context_root(root());
}
void BlockFormattingContext::run(Box const&, LayoutMode layout_mode, [[maybe_unused]] AvailableSpace const& available_width, [[maybe_unused]] AvailableSpace const& available_height)
void BlockFormattingContext::run(Box const&, LayoutMode layout_mode, AvailableSpace const& available_space)
{
if (is_initial()) {
layout_initial_containing_block(layout_mode);
layout_initial_containing_block(layout_mode, available_space);
return;
}
if (root().children_are_inline())
layout_inline_children(root(), layout_mode);
layout_inline_children(root(), layout_mode, available_space);
else
layout_block_level_children(root(), layout_mode);
layout_block_level_children(root(), layout_mode, available_space);
}
void BlockFormattingContext::parent_context_did_dimension_child_root_box()
@ -64,25 +65,29 @@ void BlockFormattingContext::parent_context_did_dimension_child_root_box()
// Left-side floats: offset_from_edge is from left edge (0) to left content edge of floating_box.
for (auto& floating_box : m_left_floats.all_boxes) {
auto& box_state = m_state.get_mutable(floating_box->box);
box_state.offset.set_x(floating_box->offset_from_edge);
box_state.set_content_x(floating_box->offset_from_edge);
}
// Right-side floats: offset_from_edge is from right edge (float_containing_block_width) to the left content edge of floating_box.
for (auto& floating_box : m_right_floats.all_boxes) {
auto float_containing_block_width = containing_block_width_for(floating_box->box);
auto& box_state = m_state.get_mutable(floating_box->box);
box_state.offset.set_x(float_containing_block_width - floating_box->offset_from_edge);
box_state.set_content_x(float_containing_block_width - floating_box->offset_from_edge);
}
// We can also layout absolutely positioned boxes within this BFC.
for (auto& box : m_absolutely_positioned_boxes)
layout_absolutely_positioned_element(box);
for (auto& box : m_absolutely_positioned_boxes) {
auto& cb_state = m_state.get(*box.containing_block());
auto available_width = AvailableSize::make_definite(cb_state.content_width() + cb_state.padding_left + cb_state.padding_right);
auto available_height = AvailableSize::make_definite(cb_state.content_height() + cb_state.padding_top + cb_state.padding_bottom);
layout_absolutely_positioned_element(box, AvailableSpace(available_width, available_height));
}
}
void BlockFormattingContext::compute_width(Box const& box, LayoutMode layout_mode)
void BlockFormattingContext::compute_width(Box const& box, AvailableSpace const& available_space, LayoutMode)
{
if (box.is_absolutely_positioned()) {
compute_width_for_absolutely_positioned_element(box);
compute_width_for_absolutely_positioned_element(box, available_space);
return;
}
@ -91,30 +96,34 @@ void BlockFormattingContext::compute_width(Box const& box, LayoutMode layout_mod
auto& replaced = verify_cast<ReplacedBox>(box);
// FIXME: This const_cast is gross.
const_cast<ReplacedBox&>(replaced).prepare_for_replaced_layout();
compute_width_for_block_level_replaced_element_in_normal_flow(replaced);
compute_width_for_block_level_replaced_element_in_normal_flow(replaced, available_space);
// NOTE: We don't return here.
}
if (box.is_floating()) {
compute_width_for_floating_box(box, layout_mode);
compute_width_for_floating_box(box, available_space);
return;
}
auto const& computed_values = box.computed_values();
float width_of_containing_block = containing_block_width_for(box);
auto width_of_containing_block_as_length = CSS::Length::make_px(width_of_containing_block);
if (should_treat_width_as_auto(box, available_space) && available_space.width.is_intrinsic_sizing_constraint())
return;
float width_of_containing_block = available_space.width.to_px();
auto width_of_containing_block_as_length_for_resolve = available_space.width.is_definite() ? CSS::Length::make_px(width_of_containing_block) : CSS::Length::make_px(0);
auto zero_value = CSS::Length::make_px(0);
auto margin_left = CSS::Length::make_auto();
auto margin_right = CSS::Length::make_auto();
auto padding_left = computed_values.padding().left().resolved(box, width_of_containing_block_as_length).resolved(box);
auto padding_right = computed_values.padding().right().resolved(box, width_of_containing_block_as_length).resolved(box);
auto padding_left = computed_values.padding().left().resolved(box, width_of_containing_block_as_length_for_resolve).resolved(box);
auto padding_right = computed_values.padding().right().resolved(box, width_of_containing_block_as_length_for_resolve).resolved(box);
auto try_compute_width = [&](auto const& a_width) {
CSS::Length width = a_width;
margin_left = computed_values.margin().left().resolved(box, width_of_containing_block_as_length).resolved(box);
margin_right = computed_values.margin().right().resolved(box, width_of_containing_block_as_length).resolved(box);
margin_left = computed_values.margin().left().resolved(box, width_of_containing_block_as_length_for_resolve).resolved(box);
margin_right = computed_values.margin().right().resolved(box, width_of_containing_block_as_length_for_resolve).resolved(box);
float total_px = computed_values.border_left().width + computed_values.border_right().width;
for (auto& value : { margin_left, padding_left, width, padding_right, margin_right }) {
total_px += value.to_px(box);
@ -132,6 +141,8 @@ void BlockFormattingContext::compute_width(Box const& box, LayoutMode layout_mod
// 10.3.3 cont'd.
auto underflow_px = width_of_containing_block - total_px;
if (!isfinite(underflow_px))
underflow_px = 0;
if (width.is_auto()) {
if (margin_left.is_auto())
@ -139,19 +150,7 @@ void BlockFormattingContext::compute_width(Box const& box, LayoutMode layout_mod
if (margin_right.is_auto())
margin_right = zero_value;
if (width_of_containing_block == INFINITY) {
// If width of containing block is infinity
// then we might as well behave like we don't have a containing block
// and remove it from the calculation. In that case, our width
// will end up being the sum of margin_*, padding_*, border_*
float sum_of_all = computed_values.border_left().width + computed_values.border_right().width;
for (const auto& value : { margin_left, padding_left, width, padding_right, margin_right }) {
sum_of_all += value.to_px(box);
}
width = CSS::Length(sum_of_all, CSS::Length::Type::Px);
} else {
if (available_space.width.is_definite()) {
if (underflow_px >= 0) {
width = CSS::Length(underflow_px, CSS::Length::Type::Px);
} else {
@ -178,9 +177,9 @@ void BlockFormattingContext::compute_width(Box const& box, LayoutMode layout_mod
};
auto input_width = [&] {
if (should_treat_width_as_auto(box))
if (should_treat_width_as_auto(box, available_space))
return CSS::Length::make_auto();
return computed_values.width().resolved(box, width_of_containing_block_as_length).resolved(box);
return computed_values.width().resolved(box, width_of_containing_block_as_length_for_resolve).resolved(box);
}();
// 1. The tentative used width is calculated (without 'min-width' and 'max-width')
@ -189,7 +188,7 @@ void BlockFormattingContext::compute_width(Box const& box, LayoutMode layout_mod
// 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'.
if (!computed_values.max_width().is_none()) {
auto max_width = computed_values.max_width().resolved(box, width_of_containing_block_as_length).resolved(box);
auto max_width = computed_values.max_width().resolved(box, width_of_containing_block_as_length_for_resolve).resolved(box);
if (used_width.to_px(box) > max_width.to_px(box)) {
used_width = try_compute_width(max_width);
}
@ -198,7 +197,7 @@ void BlockFormattingContext::compute_width(Box const& box, LayoutMode layout_mod
// 3. If the resulting width is smaller than 'min-width', the rules above are applied again,
// but this time using the value of 'min-width' as the computed value for 'width'.
if (!computed_values.min_width().is_auto()) {
auto min_width = computed_values.min_width().resolved(box, width_of_containing_block_as_length).resolved(box);
auto min_width = computed_values.min_width().resolved(box, width_of_containing_block_as_length_for_resolve).resolved(box);
if (used_width.to_px(box) < min_width.to_px(box)) {
used_width = try_compute_width(min_width);
}
@ -206,7 +205,7 @@ void BlockFormattingContext::compute_width(Box const& box, LayoutMode layout_mod
auto& box_state = m_state.get_mutable(box);
if (!is<ReplacedBox>(box))
if (!is<ReplacedBox>(box) && !used_width.is_auto())
box_state.set_content_width(used_width.to_px(box));
box_state.margin_left = margin_left.to_px(box);
@ -217,19 +216,21 @@ void BlockFormattingContext::compute_width(Box const& box, LayoutMode layout_mod
box_state.padding_right = padding_right.to_px(box);
}
void BlockFormattingContext::compute_width_for_floating_box(Box const& box, LayoutMode)
void BlockFormattingContext::compute_width_for_floating_box(Box const& box, AvailableSpace const& available_space)
{
// 10.3.5 Floating, non-replaced elements
auto& computed_values = box.computed_values();
float width_of_containing_block = containing_block_width_for(box);
auto width_of_containing_block_as_length = CSS::Length::make_px(width_of_containing_block);
auto zero_value = CSS::Length::make_px(0);
float width_of_containing_block = available_space.width.to_px();
auto width_of_containing_block_as_length_for_resolve = CSS::Length::make_px(width_of_containing_block);
if (!available_space.width.is_definite())
width_of_containing_block_as_length_for_resolve = CSS::Length::make_px(0);
auto margin_left = computed_values.margin().left().resolved(box, width_of_containing_block_as_length).resolved(box);
auto margin_right = computed_values.margin().right().resolved(box, width_of_containing_block_as_length).resolved(box);
auto const padding_left = computed_values.padding().left().resolved(box, width_of_containing_block_as_length).resolved(box);
auto const padding_right = computed_values.padding().right().resolved(box, width_of_containing_block_as_length).resolved(box);
auto margin_left = computed_values.margin().left().resolved(box, width_of_containing_block_as_length_for_resolve).resolved(box);
auto margin_right = computed_values.margin().right().resolved(box, width_of_containing_block_as_length_for_resolve).resolved(box);
auto const padding_left = computed_values.padding().left().resolved(box, width_of_containing_block_as_length_for_resolve).resolved(box);
auto const padding_right = computed_values.padding().right().resolved(box, width_of_containing_block_as_length_for_resolve).resolved(box);
// If 'margin-left', or 'margin-right' are computed as 'auto', their used value is '0'.
if (margin_left.is_auto())
@ -258,9 +259,9 @@ void BlockFormattingContext::compute_width_for_floating_box(Box const& box, Layo
};
auto input_width = [&] {
if (should_treat_width_as_auto(box))
if (should_treat_width_as_auto(box, available_space))
return CSS::Length::make_auto();
return computed_values.width().resolved(box, width_of_containing_block_as_length).resolved(box);
return computed_values.width().resolved(box, width_of_containing_block_as_length_for_resolve).resolved(box);
}();
// 1. The tentative used width is calculated (without 'min-width' and 'max-width')
@ -269,7 +270,7 @@ void BlockFormattingContext::compute_width_for_floating_box(Box const& box, Layo
// 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'.
if (!computed_values.max_width().is_none()) {
auto max_width = computed_values.max_width().resolved(box, width_of_containing_block_as_length).resolved(box);
auto max_width = computed_values.max_width().resolved(box, width_of_containing_block_as_length_for_resolve).resolved(box);
if (width.to_px(box) > max_width.to_px(box))
width = compute_width(max_width);
}
@ -277,7 +278,7 @@ void BlockFormattingContext::compute_width_for_floating_box(Box const& box, Layo
// 3. If the resulting width is smaller than 'min-width', the rules above are applied again,
// but this time using the value of 'min-width' as the computed value for 'width'.
if (!computed_values.min_width().is_auto()) {
auto min_width = computed_values.min_width().resolved(box, width_of_containing_block_as_length).resolved(box);
auto min_width = computed_values.min_width().resolved(box, width_of_containing_block_as_length_for_resolve).resolved(box);
if (width.to_px(box) < min_width.to_px(box))
width = compute_width(min_width);
}
@ -292,25 +293,25 @@ void BlockFormattingContext::compute_width_for_floating_box(Box const& box, Layo
box_state.padding_right = padding_right.to_px(box);
}
void BlockFormattingContext::compute_width_for_block_level_replaced_element_in_normal_flow(ReplacedBox const& box)
void BlockFormattingContext::compute_width_for_block_level_replaced_element_in_normal_flow(ReplacedBox const& box, AvailableSpace const& available_space)
{
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));
}
void BlockFormattingContext::compute_height(Box const& box, LayoutState& state)
void BlockFormattingContext::compute_height(Box const& box, AvailableSpace const& available_space)
{
resolve_vertical_box_model_metrics(box, *box.containing_block(), state);
resolve_vertical_box_model_metrics(box, available_space, m_state);
auto const& computed_values = box.computed_values();
auto containing_block_height = CSS::Length::make_px(containing_block_height_for(box, state));
auto containing_block_height = CSS::Length::make_px(available_space.height.to_px());
// Then work out what the height is, based on box type and CSS properties.
float height = 0;
if (is<ReplacedBox>(box)) {
height = compute_height_for_replaced_element(state, verify_cast<ReplacedBox>(box));
height = compute_height_for_replaced_element(m_state, verify_cast<ReplacedBox>(box), available_space);
} else {
if (should_treat_height_as_auto(box, state)) {
height = compute_auto_height_for_block_level_element(state, box);
if (should_treat_height_as_auto(box, available_space)) {
height = compute_auto_height_for_block_level_element(box);
} else {
height = computed_values.height().resolved(box, containing_block_height).to_px(box);
}
@ -321,51 +322,33 @@ void BlockFormattingContext::compute_height(Box const& box, LayoutState& state)
if (!max_height.is_auto())
height = min(height, max_height.to_px(box));
}
auto specified_min_height = computed_values.min_height().resolved(box, containing_block_height).resolved(box);
if (!specified_min_height.is_auto())
height = max(height, specified_min_height.to_px(box));
if (!computed_values.min_height().is_auto()) {
auto min_height = computed_values.min_height().resolved(box, containing_block_height).resolved(box);
height = max(height, min_height.to_px(box));
}
state.get_mutable(box).set_content_height(height);
m_state.get_mutable(box).set_content_height(height);
}
void BlockFormattingContext::layout_inline_children(BlockContainer const& block_container, LayoutMode layout_mode)
void BlockFormattingContext::layout_inline_children(BlockContainer const& block_container, LayoutMode layout_mode, AvailableSpace const& available_space)
{
VERIFY(block_container.children_are_inline());
auto& block_container_state = m_state.get_mutable(block_container);
if (layout_mode == LayoutMode::IntrinsicSizing) {
if (should_treat_width_as_auto(block_container) || block_container_state.width_constraint != SizeConstraint::None)
block_container_state.set_content_width(containing_block_width_for(block_container));
if (should_treat_height_as_auto(block_container) || block_container_state.height_constraint != SizeConstraint::None)
block_container_state.set_content_height(containing_block_height_for(block_container));
}
InlineFormattingContext context(m_state, block_container, *this);
context.run(
block_container,
layout_mode,
AvailableSpace::make_definite(containing_block_width_for(block_container)),
AvailableSpace::make_definite(containing_block_height_for(block_container)));
available_space);
float max_line_width = 0;
float content_height = 0;
for (auto& line_box : block_container_state.line_boxes) {
max_line_width = max(max_line_width, line_box.width());
content_height += line_box.height();
}
if (layout_mode == LayoutMode::IntrinsicSizing) {
if (should_treat_width_as_auto(block_container) || block_container_state.width_constraint != SizeConstraint::None)
block_container_state.set_content_width(max_line_width);
}
// FIXME: This is weird. Figure out a way to make callers responsible for setting the content height.
block_container_state.set_content_height(content_height);
if (!block_container_state.has_definite_width())
block_container_state.set_content_width(context.automatic_content_width());
if (!block_container_state.has_definite_height())
block_container_state.set_content_height(context.automatic_content_height());
}
void BlockFormattingContext::layout_block_level_box(Box const& box, BlockContainer const& block_container, LayoutMode layout_mode, float& bottom_of_lowest_margin_box)
void BlockFormattingContext::layout_block_level_box(Box const& box, BlockContainer const& block_container, LayoutMode layout_mode, float& bottom_of_lowest_margin_box, AvailableSpace const& available_space)
{
auto& box_state = m_state.get_mutable(box);
@ -379,38 +362,39 @@ void BlockFormattingContext::layout_block_level_box(Box const& box, BlockContain
return;
if (box.is_floating()) {
layout_floating_box(box, block_container, layout_mode);
layout_floating_box(box, block_container, layout_mode, available_space);
bottom_of_lowest_margin_box = max(bottom_of_lowest_margin_box, box_state.offset.y() + box_state.content_height() + box_state.margin_box_bottom());
return;
}
compute_width(box, layout_mode);
compute_width(box, available_space, layout_mode);
if (is<ReplacedBox>(box) || is<BlockContainer>(box))
place_block_level_element_in_normal_flow_vertically(box, block_container);
place_block_level_element_in_normal_flow_vertically(box, available_space);
if (box_state.has_definite_height()) {
compute_height(box, m_state);
compute_height(box, available_space);
}
OwnPtr<FormattingContext> independent_formatting_context;
if (box.can_have_children()) {
if (box.children_are_inline()) {
layout_inline_children(verify_cast<BlockContainer>(box), layout_mode);
layout_inline_children(verify_cast<BlockContainer>(box), layout_mode, box_state.available_inner_space_or_constraints_from(available_space));
} else {
independent_formatting_context = create_independent_formatting_context_if_needed(m_state, box);
if (independent_formatting_context)
independent_formatting_context->run(box, layout_mode, AvailableSpace::make_indefinite(), AvailableSpace::make_indefinite());
independent_formatting_context->run(box, layout_mode, box_state.available_inner_space_or_constraints_from(available_space));
else
layout_block_level_children(verify_cast<BlockContainer>(box), layout_mode);
layout_block_level_children(verify_cast<BlockContainer>(box), layout_mode, box_state.available_inner_space_or_constraints_from(available_space));
}
}
compute_height(box, m_state);
compute_height(box, available_space);
compute_inset(box);
if (is<ReplacedBox>(box) || is<BlockContainer>(box))
place_block_level_element_in_normal_flow_horizontally(box, block_container);
place_block_level_element_in_normal_flow_horizontally(box, available_space);
if (is<ListItemBox>(box)) {
layout_list_item_marker(static_cast<ListItemBox const&>(box));
@ -422,54 +406,46 @@ void BlockFormattingContext::layout_block_level_box(Box const& box, BlockContain
independent_formatting_context->parent_context_did_dimension_child_root_box();
}
void BlockFormattingContext::layout_block_level_children(BlockContainer const& block_container, LayoutMode layout_mode)
void BlockFormattingContext::layout_block_level_children(BlockContainer const& block_container, LayoutMode layout_mode, AvailableSpace const& available_space)
{
VERIFY(!block_container.children_are_inline());
float bottom_of_lowest_margin_box = 0;
if (layout_mode == LayoutMode::IntrinsicSizing) {
auto& block_container_state = m_state.get_mutable(block_container);
if (should_treat_width_as_auto(block_container) || block_container_state.width_constraint != SizeConstraint::None)
block_container_state.set_content_width(containing_block_width_for(block_container));
if (should_treat_height_as_auto(block_container) || block_container_state.height_constraint != SizeConstraint::None)
block_container_state.set_content_height(containing_block_height_for(block_container));
}
block_container.for_each_child_of_type<Box>([&](Box& box) {
layout_block_level_box(box, block_container, layout_mode, bottom_of_lowest_margin_box);
layout_block_level_box(box, block_container, layout_mode, bottom_of_lowest_margin_box, available_space);
return IterationDecision::Continue;
});
if (layout_mode == LayoutMode::IntrinsicSizing) {
auto& block_container_state = m_state.get_mutable(block_container);
if (should_treat_width_as_auto(block_container) || block_container_state.width_constraint != SizeConstraint::None)
if (!block_container_state.has_definite_width())
block_container_state.set_content_width(greatest_child_width(block_container));
if (should_treat_height_as_auto(block_container) || block_container_state.height_constraint != SizeConstraint::None)
if (!block_container_state.has_definite_height())
block_container_state.set_content_height(bottom_of_lowest_margin_box);
}
}
void BlockFormattingContext::resolve_vertical_box_model_metrics(Box const& box, BlockContainer const& containing_block, LayoutState& state)
void BlockFormattingContext::resolve_vertical_box_model_metrics(Box const& box, AvailableSpace const& available_space, LayoutState& state)
{
auto& box_state = state.get_mutable(box);
auto const& computed_values = box.computed_values();
auto width_of_containing_block = CSS::Length::make_px(containing_block_width_for(box, state));
auto width_of_containing_block = CSS::Length::make_px(available_space.width.to_px_or_zero());
box_state.margin_top = computed_values.margin().top().resolved(box, width_of_containing_block).resolved(containing_block).to_px(box);
box_state.margin_bottom = computed_values.margin().bottom().resolved(box, width_of_containing_block).resolved(containing_block).to_px(box);
box_state.margin_top = computed_values.margin().top().resolved(box, width_of_containing_block).to_px(box);
box_state.margin_bottom = computed_values.margin().bottom().resolved(box, width_of_containing_block).to_px(box);
box_state.border_top = computed_values.border_top().width;
box_state.border_bottom = computed_values.border_bottom().width;
box_state.padding_top = computed_values.padding().top().resolved(box, width_of_containing_block).resolved(containing_block).to_px(box);
box_state.padding_bottom = computed_values.padding().bottom().resolved(box, width_of_containing_block).resolved(containing_block).to_px(box);
box_state.padding_top = computed_values.padding().top().resolved(box, width_of_containing_block).to_px(box);
box_state.padding_bottom = computed_values.padding().bottom().resolved(box, width_of_containing_block).to_px(box);
}
void BlockFormattingContext::place_block_level_element_in_normal_flow_vertically(Box const& child_box, BlockContainer const& containing_block)
void BlockFormattingContext::place_block_level_element_in_normal_flow_vertically(Box const& child_box, AvailableSpace const& available_space)
{
auto& box_state = m_state.get_mutable(child_box);
auto const& computed_values = child_box.computed_values();
resolve_vertical_box_model_metrics(child_box, containing_block, m_state);
resolve_vertical_box_model_metrics(child_box, available_space, m_state);
auto y = FormattingContext::compute_box_y_position_with_respect_to_siblings(child_box, box_state);
@ -504,15 +480,15 @@ void BlockFormattingContext::place_block_level_element_in_normal_flow_vertically
if ((computed_values.clear() == CSS::Clear::Right || computed_values.clear() == CSS::Clear::Both) && !child_box.is_flex_item())
clear_floating_boxes(m_right_floats);
box_state.offset = Gfx::FloatPoint { box_state.offset.x(), y };
box_state.set_content_offset(Gfx::FloatPoint { box_state.offset.x(), y });
}
void BlockFormattingContext::place_block_level_element_in_normal_flow_horizontally(Box const& child_box, BlockContainer const& containing_block)
void BlockFormattingContext::place_block_level_element_in_normal_flow_horizontally(Box const& child_box, AvailableSpace const& available_space)
{
auto& box_state = m_state.get_mutable(child_box);
float x = 0;
float available_width_within_containing_block = containing_block_width_for(child_box);
float available_width_within_containing_block = available_space.width.to_px();
if ((!m_left_floats.current_boxes.is_empty() || !m_right_floats.current_boxes.is_empty())
&& creates_block_formatting_context(child_box)) {
@ -520,13 +496,13 @@ void BlockFormattingContext::place_block_level_element_in_normal_flow_horizontal
x += m_left_floats.current_width;
}
if (containing_block.computed_values().text_align() == CSS::TextAlign::LibwebCenter) {
if (child_box.containing_block()->computed_values().text_align() == CSS::TextAlign::LibwebCenter) {
x += (available_width_within_containing_block / 2) - box_state.content_width() / 2;
} else {
x += box_state.margin_box_left();
}
box_state.offset = Gfx::FloatPoint { x, box_state.offset.y() };
box_state.set_content_offset(Gfx::FloatPoint { x, box_state.offset.y() });
}
static void measure_scrollable_overflow(LayoutState const& state, Box const& box, float& bottom_edge, float& right_edge)
@ -547,7 +523,7 @@ static void measure_scrollable_overflow(LayoutState const& state, Box const& box
});
}
void BlockFormattingContext::layout_initial_containing_block(LayoutMode layout_mode)
void BlockFormattingContext::layout_initial_containing_block(LayoutMode layout_mode, AvailableSpace const& available_space)
{
auto viewport_rect = root().browsing_context().viewport_rect();
@ -555,9 +531,9 @@ void BlockFormattingContext::layout_initial_containing_block(LayoutMode layout_m
auto& icb_state = m_state.get_mutable(icb);
if (root().children_are_inline())
layout_inline_children(root(), layout_mode);
layout_inline_children(root(), layout_mode, available_space);
else
layout_block_level_children(root(), layout_mode);
layout_block_level_children(root(), layout_mode, available_space);
float bottom_edge = 0;
float right_edge = 0;
@ -572,25 +548,26 @@ void BlockFormattingContext::layout_initial_containing_block(LayoutMode layout_m
}
}
void BlockFormattingContext::layout_floating_box(Box const& box, BlockContainer const& containing_block, LayoutMode layout_mode, LineBuilder* line_builder)
void BlockFormattingContext::layout_floating_box(Box const& box, BlockContainer const&, LayoutMode layout_mode, AvailableSpace const& available_space, LineBuilder* line_builder)
{
VERIFY(box.is_floating());
auto& box_state = m_state.get_mutable(box);
float width_of_containing_block = containing_block_width_for(box);
float width_of_containing_block = available_space.width.to_px();
compute_width(box, layout_mode);
(void)layout_inside(box, layout_mode);
compute_height(box, m_state);
compute_width(box, available_space, layout_mode);
if (auto independent_formatting_context = layout_inside(box, layout_mode, box_state.available_inner_space_or_constraints_from(available_space)))
independent_formatting_context->parent_context_did_dimension_child_root_box();
compute_height(box, available_space);
// First we place the box normally (to get the right y coordinate.)
// If we have a LineBuilder, we're in the middle of inline layout, otherwise this is block layout.
if (line_builder) {
auto y = line_builder->y_for_float_to_be_inserted_here(box);
box_state.offset.set_y(y + box_state.margin_box_top());
box_state.set_content_y(y + box_state.margin_box_top());
} else {
place_block_level_element_in_normal_flow_vertically(box, containing_block);
place_block_level_element_in_normal_flow_horizontally(box, containing_block);
place_block_level_element_in_normal_flow_vertically(box, available_space);
place_block_level_element_in_normal_flow_horizontally(box, available_space);
}
// Then we float it to the left or right.
@ -678,7 +655,7 @@ void BlockFormattingContext::layout_floating_box(Box const& box, BlockContainer
// NOTE: We don't set the X position here, that happens later, once we know the root block width.
// See parent_context_did_dimension_child_root_box() for that logic.
box_state.offset.set_y(y);
box_state.set_content_y(y);
// If the new box was inserted below the bottom of the opposite side,
// we reset the other side back to its edge.
@ -726,10 +703,8 @@ void BlockFormattingContext::layout_list_item_marker(ListItemBox const& list_ite
marker_state.set_content_height(max(image_height, marker.font().glyph_height() + 1));
marker_state.offset = {
-(marker_state.content_width() + default_marker_width),
max(0.f, (marker.line_height() - marker_state.content_height()) / 2.f)
};
marker_state.set_content_offset({ -(marker_state.content_width() + default_marker_width),
max(0.f, (marker.line_height() - marker_state.content_height()) / 2.f) });
if (marker_state.content_height() > list_item_state.content_height())
list_item_state.set_content_height(marker_state.content_height());
@ -801,16 +776,26 @@ float BlockFormattingContext::greatest_child_width(Box const& box)
return max_width;
}
bool BlockFormattingContext::should_treat_width_as_auto(Box const& box, LayoutState const& state)
bool BlockFormattingContext::should_treat_width_as_auto(Box const& box, AvailableSpace const& available_space)
{
return box.computed_values().width().is_auto()
|| (box.computed_values().width().contains_percentage() && !state.get(*box.containing_block()).has_definite_width());
|| (box.computed_values().width().contains_percentage() && !available_space.width.is_definite());
}
bool BlockFormattingContext::should_treat_height_as_auto(Box const& box, LayoutState const& state)
bool BlockFormattingContext::should_treat_height_as_auto(Box const& box, AvailableSpace const& available_space)
{
return box.computed_values().height().is_auto()
|| (box.computed_values().height().contains_percentage() && !state.get(*box.containing_block()).has_definite_height());
|| (box.computed_values().height().contains_percentage() && !available_space.height.is_definite());
}
void BlockFormattingContext::determine_width_of_child(Box const& box, AvailableSpace const& available_space)
{
compute_width(box, available_space);
}
void BlockFormattingContext::determine_height_of_child(Box const& box, AvailableSpace const& available_space)
{
compute_height(box, available_space);
}
}

View file

@ -21,7 +21,7 @@ public:
explicit BlockFormattingContext(LayoutState&, BlockContainer const&, FormattingContext* parent);
~BlockFormattingContext();
virtual void run(Box const&, LayoutMode, AvailableSpace const& available_width, AvailableSpace const& available_height) override;
virtual void run(Box const&, LayoutMode, AvailableSpace const&) override;
virtual float automatic_content_height() const override;
bool is_initial() const;
@ -29,14 +29,14 @@ public:
auto const& left_side_floats() const { return m_left_floats; }
auto const& right_side_floats() const { return m_right_floats; }
void compute_width(Box const&, LayoutMode = LayoutMode::Normal);
void compute_width(Box const&, AvailableSpace const&, LayoutMode = LayoutMode::Normal);
// https://www.w3.org/TR/css-display/#block-formatting-context-root
BlockContainer const& root() const { return static_cast<BlockContainer const&>(context_box()); }
virtual void parent_context_did_dimension_child_root_box() override;
static void compute_height(Box const&, LayoutState&);
void compute_height(Box const&, AvailableSpace const&);
void add_absolutely_positioned_box(Box const& box) { m_absolutely_positioned_boxes.append(box); }
@ -44,38 +44,32 @@ public:
virtual float greatest_child_width(Box const&) override;
void layout_floating_box(Box const& child, BlockContainer const& containing_block, LayoutMode, LineBuilder* = nullptr);
void layout_floating_box(Box const& child, BlockContainer const& containing_block, LayoutMode, AvailableSpace const&, LineBuilder* = nullptr);
void layout_block_level_box(Box const&, BlockContainer const&, LayoutMode, float& bottom_of_lowest_margin_box);
void layout_block_level_box(Box const&, BlockContainer const&, LayoutMode, float& bottom_of_lowest_margin_box, AvailableSpace const&);
static bool should_treat_width_as_auto(Box const&, LayoutState const&);
static bool should_treat_height_as_auto(Box const&, LayoutState const&);
static bool should_treat_width_as_auto(Box const&, AvailableSpace const&);
static bool should_treat_height_as_auto(Box const&, AvailableSpace const&);
bool should_treat_width_as_auto(Box const& box) const
{
return should_treat_width_as_auto(box, m_state);
}
bool should_treat_height_as_auto(Box const& box) const
{
return should_treat_height_as_auto(box, m_state);
}
virtual bool can_determine_size_of_child() const override { return true; }
virtual void determine_width_of_child(Box const&, AvailableSpace const&) override;
virtual void determine_height_of_child(Box const&, AvailableSpace const&) override;
private:
virtual bool is_block_formatting_context() const final { return true; }
void compute_width_for_floating_box(Box const&, LayoutMode);
void compute_width_for_floating_box(Box const&, AvailableSpace const&);
void compute_width_for_block_level_replaced_element_in_normal_flow(ReplacedBox const&);
void compute_width_for_block_level_replaced_element_in_normal_flow(ReplacedBox const&, AvailableSpace const&);
void layout_initial_containing_block(LayoutMode);
void layout_initial_containing_block(LayoutMode, AvailableSpace const&);
void layout_block_level_children(BlockContainer const&, LayoutMode);
void layout_inline_children(BlockContainer const&, LayoutMode);
void layout_block_level_children(BlockContainer const&, LayoutMode, AvailableSpace const&);
void layout_inline_children(BlockContainer const&, LayoutMode, AvailableSpace const&);
static void resolve_vertical_box_model_metrics(Box const& box, BlockContainer const& containing_block, LayoutState&);
void place_block_level_element_in_normal_flow_horizontally(Box const& child_box, BlockContainer const&);
void place_block_level_element_in_normal_flow_vertically(Box const& child_box, BlockContainer const&);
static void resolve_vertical_box_model_metrics(Box const& box, AvailableSpace const&, LayoutState&);
void place_block_level_element_in_normal_flow_horizontally(Box const& child_box, AvailableSpace const&);
void place_block_level_element_in_normal_flow_vertically(Box const& child_box, AvailableSpace const&);
void layout_list_item_marker(ListItemBox const&);

View file

@ -67,10 +67,29 @@ float FlexFormattingContext::automatic_content_height() const
return m_state.get(flex_container()).content_height();
}
void FlexFormattingContext::run(Box const& run_box, LayoutMode layout_mode, [[maybe_unused]] AvailableSpace const& available_width, [[maybe_unused]] AvailableSpace const& available_height)
void FlexFormattingContext::run(Box const& run_box, LayoutMode, AvailableSpace const& available_content_space)
{
VERIFY(&run_box == &flex_container());
// NOTE: The available space provided by the parent context is basically our *content box*.
// FFC is currently written in a way that expects that to include padding and border as well,
// so we pad out the available space here to accommodate that.
// FIXME: Refactor the necessary parts of FFC so we don't need this hack!
auto available_width = available_content_space.width;
if (available_width.is_definite())
available_width = AvailableSize::make_definite(available_width.to_px() + m_flex_container_state.border_box_left() + m_flex_container_state.border_box_right());
auto available_height = available_content_space.height;
if (available_height.is_definite())
available_height = AvailableSize::make_definite(available_height.to_px() + m_flex_container_state.border_box_top() + m_flex_container_state.border_box_bottom());
m_available_space_for_flex_container = AxisAgnosticAvailableSpace {
.main = is_row_layout() ? available_width : available_height,
.cross = !is_row_layout() ? available_width : available_height,
.width = available_width,
.height = available_height,
};
// This implements https://www.w3.org/TR/css-flexbox-1/#layout-algorithm
// 1. Generate anonymous flex items
@ -91,34 +110,14 @@ void FlexFormattingContext::run(Box const& run_box, LayoutMode layout_mode, [[ma
auto item_preferred_outer_cross_size = css_clamp(flex_container_inner_cross_size, item_min_cross_size, item_max_cross_size);
auto item_inner_cross_size = item_preferred_outer_cross_size - item.margins.cross_before - item.margins.cross_after - item.padding.cross_before - item.padding.cross_after - item.borders.cross_before - item.borders.cross_after;
set_cross_size(item.box, item_inner_cross_size);
set_has_definite_cross_size(item.box, true);
item.has_assigned_definite_cross_size = true;
}
}
}
// 2. Determine the available main and cross space for the flex items
float main_max_size = NumericLimits<float>::max();
float main_min_size = 0;
float cross_max_size = NumericLimits<float>::max();
float cross_min_size = 0;
bool main_is_constrained = false;
bool cross_is_constrained = false;
determine_available_main_and_cross_space(main_is_constrained, cross_is_constrained, main_min_size, main_max_size, cross_min_size, cross_max_size);
if (m_flex_container_state.width_constraint == SizeConstraint::MaxContent || m_flex_container_state.height_constraint == SizeConstraint::MaxContent) {
if (is_row_layout())
m_available_space->main = INFINITY;
else
m_available_space->cross = INFINITY;
}
if (m_flex_container_state.width_constraint == SizeConstraint::MinContent || m_flex_container_state.height_constraint == SizeConstraint::MinContent) {
if (is_row_layout())
m_available_space->main = 0;
else
m_available_space->cross = 0;
}
float cross_min_size = has_cross_min_size(flex_container()) ? specified_cross_min_size(flex_container()) : 0;
float cross_max_size = has_cross_max_size(flex_container()) ? specified_cross_max_size(flex_container()) : INFINITY;
determine_available_space_for_items(AvailableSpace(available_width, available_height));
// 3. Determine the flex base size and hypothetical main size of each item
for (auto& flex_item : m_flex_items) {
@ -129,9 +128,9 @@ void FlexFormattingContext::run(Box const& run_box, LayoutMode layout_mode, [[ma
determine_flex_base_size_and_hypothetical_main_size(flex_item);
}
if (m_flex_container_state.width_constraint != SizeConstraint::None || m_flex_container_state.height_constraint != SizeConstraint::None) {
if (available_width.is_intrinsic_sizing_constraint() || available_height.is_intrinsic_sizing_constraint()) {
// We're computing intrinsic size for the flex container.
determine_intrinsic_size_of_flex_container(layout_mode);
determine_intrinsic_size_of_flex_container();
// Our caller is only interested in the content-width and content-height results,
// which have now been set on m_flex_container_state, so there's no need to continue
@ -140,7 +139,7 @@ void FlexFormattingContext::run(Box const& run_box, LayoutMode layout_mode, [[ma
}
// 4. Determine the main size of the flex container
determine_main_size_of_flex_container(main_is_constrained, main_min_size, main_max_size);
determine_main_size_of_flex_container();
// 5. Collect flex items into flex lines:
// After this step no additional items are to be added to flex_lines or any of its items!
@ -187,8 +186,6 @@ void FlexFormattingContext::run(Box const& run_box, LayoutMode layout_mode, [[ma
if (flex_container_computed_cross_size.is_auto()) {
for (auto& item : m_flex_items) {
set_cross_size(item.box, item.cross_size);
set_has_definite_cross_size(item.box, true);
item.has_assigned_definite_cross_size = true;
}
}
}
@ -211,7 +208,8 @@ void FlexFormattingContext::run(Box const& run_box, LayoutMode layout_mode, [[ma
// AD-HOC: Layout the inside of all flex items.
copy_dimensions_from_flex_items_to_boxes();
for (auto& flex_item : m_flex_items) {
if (auto independent_formatting_context = layout_inside(flex_item.box, LayoutMode::Normal))
auto& box_state = m_state.get(flex_item.box);
if (auto independent_formatting_context = layout_inside(flex_item.box, LayoutMode::Normal, box_state.available_inner_space_or_constraints_from(AvailableSpace(m_available_space_for_flex_container->width, m_available_space_for_flex_container->height))))
independent_formatting_context->parent_context_did_dimension_child_root_box();
}
@ -225,8 +223,12 @@ void FlexFormattingContext::run(Box const& run_box, LayoutMode layout_mode, [[ma
void FlexFormattingContext::parent_context_did_dimension_child_root_box()
{
flex_container().for_each_child_of_type<Box>([&](Layout::Box& box) {
if (box.is_absolutely_positioned())
layout_absolutely_positioned_element(box);
if (box.is_absolutely_positioned()) {
auto& cb_state = m_state.get(*box.containing_block());
auto available_width = AvailableSize::make_definite(cb_state.content_width() + cb_state.padding_left + cb_state.padding_right);
auto available_height = AvailableSize::make_definite(cb_state.content_height() + cb_state.padding_top + cb_state.padding_bottom);
layout_absolutely_positioned_element(box, AvailableSpace(available_width, available_height));
}
});
}
@ -363,15 +365,11 @@ float FlexFormattingContext::specified_cross_size(Box const& box) const
float FlexFormattingContext::resolved_definite_cross_size(FlexItem const& item) const
{
if (item.has_assigned_definite_cross_size)
return specified_cross_size(item.box);
return !is_row_layout() ? m_state.resolved_definite_width(item.box) : m_state.resolved_definite_height(item.box);
}
float FlexFormattingContext::resolved_definite_main_size(FlexItem const& item) const
{
if (item.has_assigned_definite_main_size)
return specified_main_size(item.box);
return is_row_layout() ? m_state.resolved_definite_width(item.box) : m_state.resolved_definite_height(item.box);
}
@ -393,13 +391,6 @@ bool FlexFormattingContext::has_definite_cross_size(Box const& box) const
return is_row_layout() ? used_values.has_definite_height() : used_values.has_definite_width();
}
float FlexFormattingContext::specified_main_size_of_child_box(Box const& child_box) const
{
auto main_size_of_parent = specified_main_size(flex_container());
auto& value = is_row_layout() ? child_box.computed_values().width() : child_box.computed_values().height();
return value.resolved(child_box, CSS::Length::make_px(main_size_of_parent)).to_px(child_box);
}
float FlexFormattingContext::specified_main_min_size(Box const& box) const
{
return is_row_layout()
@ -440,12 +431,6 @@ float FlexFormattingContext::specified_cross_max_size(Box const& box) const
: get_pixel_width(box, box.computed_values().max_width());
}
float FlexFormattingContext::calculated_main_size(Box const& box) const
{
auto const& box_state = m_state.get(box);
return is_row_layout() ? box_state.content_width() : box_state.content_height();
}
bool FlexFormattingContext::is_cross_auto(Box const& box) const
{
auto& cross_length = is_row_layout() ? box.computed_values().height() : box.computed_values().width();
@ -468,24 +453,6 @@ void FlexFormattingContext::set_cross_size(Box const& box, float size)
m_state.get_mutable(box).set_content_width(size);
}
void FlexFormattingContext::set_has_definite_main_size(Box const& box, bool definite)
{
auto& used_values = m_state.get_mutable(box);
if (is_row_layout())
used_values.set_has_definite_width(definite);
else
used_values.set_has_definite_height(definite);
}
void FlexFormattingContext::set_has_definite_cross_size(Box const& box, bool definite)
{
auto& used_values = m_state.get_mutable(box);
if (!is_row_layout())
used_values.set_has_definite_width(definite);
else
used_values.set_has_definite_height(definite);
}
void FlexFormattingContext::set_offset(Box const& box, float main_offset, float cross_offset)
{
if (is_row_layout())
@ -512,95 +479,75 @@ void FlexFormattingContext::set_main_axis_second_margin(FlexItem& item, float ma
m_state.get_mutable(item.box).margin_bottom = margin;
}
float FlexFormattingContext::sum_of_margin_padding_border_in_main_axis(Box const& box) const
// https://drafts.csswg.org/css-flexbox-1/#algo-available
void FlexFormattingContext::determine_available_space_for_items(AvailableSpace const& available_space)
{
auto const& box_state = m_state.get(box);
// For each dimension, if that dimension of the flex containers content box is a definite size, use that;
// if that dimension of the flex container is being sized under a min or max-content constraint, the available space in that dimension is that constraint;
// otherwise, subtract the flex containers margin, border, and padding from the space available to the flex container in that dimension and use that value.
// This might result in an infinite value.
Optional<AvailableSize> available_width_for_items;
if (m_flex_container_state.has_definite_width()) {
available_width_for_items = AvailableSize::make_definite(m_state.resolved_definite_width(flex_container()));
} else {
if (available_space.width.is_intrinsic_sizing_constraint()) {
available_width_for_items = available_space.width;
} else {
if (available_space.width.is_definite()) {
auto remaining = available_space.width.to_px()
- m_flex_container_state.margin_left
- m_flex_container_state.margin_right
- m_flex_container_state.border_left
- m_flex_container_state.padding_right
- m_flex_container_state.padding_left
- m_flex_container_state.padding_right;
available_width_for_items = AvailableSize::make_definite(remaining);
} else {
available_width_for_items = AvailableSize::make_indefinite();
}
}
}
Optional<AvailableSize> available_height_for_items;
if (m_flex_container_state.has_definite_height()) {
available_height_for_items = AvailableSize::make_definite(m_state.resolved_definite_height(flex_container()));
} else {
if (available_space.height.is_intrinsic_sizing_constraint()) {
available_height_for_items = available_space.height;
} else {
if (available_space.height.is_definite()) {
auto remaining = available_space.height.to_px()
- m_flex_container_state.margin_top
- m_flex_container_state.margin_bottom
- m_flex_container_state.border_top
- m_flex_container_state.padding_bottom
- m_flex_container_state.padding_top
- m_flex_container_state.padding_bottom;
available_height_for_items = AvailableSize::make_definite(remaining);
} else {
available_height_for_items = AvailableSize::make_indefinite();
}
}
}
if (is_row_layout()) {
return box_state.margin_left + box_state.margin_right
+ box_state.padding_left + box_state.padding_right
+ box_state.border_left + box_state.border_right;
m_available_space_for_items = AxisAgnosticAvailableSpace {
.main = *available_width_for_items,
.cross = *available_height_for_items,
.width = *available_width_for_items,
.height = *available_height_for_items,
};
} else {
return box_state.margin_top + box_state.margin_bottom
+ box_state.padding_top + box_state.padding_bottom
+ box_state.border_top + box_state.border_bottom;
m_available_space_for_items = AxisAgnosticAvailableSpace {
.main = *available_height_for_items,
.cross = *available_width_for_items,
.width = *available_width_for_items,
.height = *available_height_for_items,
};
}
}
// https://www.w3.org/TR/css-flexbox-1/#algo-available
void FlexFormattingContext::determine_available_main_and_cross_space(bool& main_is_constrained, bool& cross_is_constrained, float& main_min_size, float& main_max_size, float& cross_min_size, float& cross_max_size)
{
auto containing_block_effective_main_size = [&](Box const& box) -> Optional<float> {
auto& containing_block = *box.containing_block();
if (has_definite_main_size(containing_block))
return is_row_layout() ? m_state.resolved_definite_width(box) : m_state.resolved_definite_height(box);
return {};
};
Optional<float> main_available_space;
main_is_constrained = false;
// For each dimension,
// if that dimension of the flex containers content box is a definite size, use that;
// if that dimension of the flex container is being sized under a min or max-content constraint, the available space in that dimension is that constraint;
// otherwise, subtract the flex containers margin, border, and padding from the space available to the flex container in that dimension and use that value. (This might result in an infinite value.)
if (has_definite_main_size(flex_container())) {
main_is_constrained = true;
main_available_space = specified_main_size(flex_container());
} else {
if (has_main_max_size(flex_container())) {
bool main_max_size_behaves_like_auto = false;
if (computed_main_max_size(flex_container()).contains_percentage())
main_max_size_behaves_like_auto = !has_definite_main_size(*flex_container().containing_block());
if (!main_max_size_behaves_like_auto) {
main_max_size = specified_main_max_size(flex_container());
main_available_space = main_max_size;
main_is_constrained = true;
}
}
if (has_main_min_size(flex_container())) {
main_min_size = specified_main_min_size(flex_container());
main_is_constrained = true;
}
if (!main_is_constrained) {
auto available_main_size = containing_block_effective_main_size(flex_container());
main_available_space = available_main_size.value_or(NumericLimits<float>::max()) - sum_of_margin_padding_border_in_main_axis(flex_container());
}
}
Optional<float> cross_available_space;
cross_is_constrained = false;
if (has_definite_cross_size(flex_container())) {
cross_available_space = specified_cross_size(flex_container());
} else {
if (has_cross_max_size(flex_container())) {
bool cross_max_size_behaves_like_auto = false;
if (computed_cross_max_size(flex_container()).contains_percentage())
cross_max_size_behaves_like_auto = !has_definite_cross_size(*flex_container().containing_block());
if (!cross_max_size_behaves_like_auto) {
cross_max_size = specified_cross_max_size(flex_container());
cross_is_constrained = true;
}
}
if (has_cross_min_size(flex_container())) {
cross_min_size = specified_cross_min_size(flex_container());
cross_is_constrained = true;
}
// FIXME: Is this right? Probably not.
if (!cross_is_constrained)
cross_available_space = cross_max_size;
}
m_available_space = AvailableSpaceForItems { .main = main_available_space, .cross = cross_available_space };
}
float FlexFormattingContext::calculate_indefinite_main_size(FlexItem const& item)
{
VERIFY(!has_definite_main_size(item.box));
@ -635,7 +582,7 @@ float FlexFormattingContext::calculate_indefinite_main_size(FlexItem const& item
VERIFY(independent_formatting_context);
box_state.set_content_width(fit_content_cross_size);
independent_formatting_context->run(item.box, LayoutMode::Normal, AvailableSpace::make_indefinite(), AvailableSpace::make_indefinite());
independent_formatting_context->run(item.box, LayoutMode::Normal, AvailableSpace(m_available_space_for_items->width, m_available_space_for_items->height));
return independent_formatting_context->automatic_content_height();
}
@ -719,9 +666,8 @@ void FlexFormattingContext::determine_flex_base_size_and_hypothetical_main_size(
// and the flex container is being sized under a min-content or max-content constraint
// (e.g. when performing automatic table layout [CSS21]), size the item under that constraint.
// The flex base size is the items resulting main size.
auto flex_container_main_size_constraint = is_row_layout() ? m_flex_container_state.width_constraint : m_flex_container_state.height_constraint;
if (flex_item.used_flex_basis.type == CSS::FlexBasis::Content && flex_container_main_size_constraint != SizeConstraint::None) {
if (flex_container_main_size_constraint == SizeConstraint::MinContent)
if (flex_item.used_flex_basis.type == CSS::FlexBasis::Content && m_available_space_for_items->main.is_intrinsic_sizing_constraint()) {
if (m_available_space_for_items->main.is_min_content())
return calculate_min_content_main_size(flex_item);
return calculate_max_content_main_size(flex_item);
}
@ -825,15 +771,39 @@ float FlexFormattingContext::content_based_minimum_size(FlexItem const& item) co
return unclamped_size;
}
// https://www.w3.org/TR/css-flexbox-1/#algo-main-container
void FlexFormattingContext::determine_main_size_of_flex_container(bool const main_is_constrained, float const main_min_size, float const main_max_size)
// https://drafts.csswg.org/css-flexbox-1/#algo-main-container
void FlexFormattingContext::determine_main_size_of_flex_container()
{
// FIXME: This needs to be reworked.
if (!main_is_constrained || !m_available_space->main.has_value()) {
auto result = is_row_layout() ? calculate_max_content_width(flex_container()) : calculate_max_content_height(flex_container());
m_available_space->main = css_clamp(result, main_min_size, main_max_size);
// Determine the main size of the flex container using the rules of the formatting context in which it participates.
// NOTE: The automatic block size of a block-level flex container is its max-content size.
// FIXME: Once all parent contexts now how to size a given child, we can remove
// `can_determine_size_of_child()`.
if (parent()->can_determine_size_of_child()) {
AvailableSpace available_space(m_available_space_for_flex_container->width, m_available_space_for_flex_container->height);
if (is_row_layout()) {
parent()->determine_width_of_child(flex_container(), available_space);
} else {
parent()->determine_height_of_child(flex_container(), available_space);
}
return;
}
// HACK: The hack below doesn't know how to size absolutely positioned flex containers at all.
// We just leave it alone for now and let the parent context deal with it.
if (flex_container().is_absolutely_positioned())
return;
if (is_row_layout()) {
if (!flex_container().is_out_of_flow(*parent()) && m_state.get(*flex_container().containing_block()).has_definite_width()) {
set_main_size(flex_container(), calculate_stretch_fit_width(flex_container(), m_available_space_for_flex_container->main));
} else {
set_main_size(flex_container(), calculate_max_content_width(flex_container()));
}
} else {
if (!has_definite_main_size(flex_container()))
set_main_size(flex_container(), calculate_max_content_height(flex_container()));
}
set_main_size(flex_container(), m_available_space->main.value_or(NumericLimits<float>::max()));
}
// https://www.w3.org/TR/css-flexbox-1/#algo-line-break
@ -864,7 +834,7 @@ void FlexFormattingContext::collect_flex_items_into_flex_lines()
float line_main_size = 0;
for (auto& flex_item : m_flex_items) {
auto outer_hypothetical_main_size = flex_item.hypothetical_main_size + flex_item.margins.main_before + flex_item.margins.main_after + flex_item.borders.main_before + flex_item.borders.main_after + flex_item.padding.main_before + flex_item.padding.main_after;
if ((line_main_size + outer_hypothetical_main_size) > m_available_space->main.value_or(NumericLimits<float>::max())) {
if (!line.items.is_empty() && (line_main_size + outer_hypothetical_main_size) > specified_main_size(flex_container())) {
m_flex_lines.append(move(line));
line = {};
line_main_size = 0;
@ -892,7 +862,7 @@ void FlexFormattingContext::resolve_flexible_lengths()
for (auto& flex_item : flex_line.items) {
sum_of_hypothetical_main_sizes += (flex_item->hypothetical_main_size + flex_item->margins.main_before + flex_item->margins.main_after + flex_item->borders.main_before + flex_item->borders.main_after + flex_item->padding.main_before + flex_item->padding.main_after);
}
if (sum_of_hypothetical_main_sizes < m_available_space->main.value_or(NumericLimits<float>::max()))
if (sum_of_hypothetical_main_sizes < specified_main_size(flex_container()))
used_flex_factor = FlexFactor::FlexGrowFactor;
else
used_flex_factor = FlexFactor::FlexShrinkFactor;
@ -1042,14 +1012,6 @@ void FlexFormattingContext::resolve_flexible_lengths()
for (auto& flex_item : flex_line.items) {
flex_item->main_size = flex_item->target_main_size;
set_main_size(flex_item->box, flex_item->main_size);
// https://drafts.csswg.org/css-flexbox-1/#definite-sizes
// 1. If the flex container has a definite main size, then the post-flexing main sizes of its flex items are treated as definite.
// 2. If a flex-items flex basis is definite, then its post-flexing main size is also definite.
if (has_definite_main_size(flex_container()) || flex_item->used_flex_basis_is_definite) {
set_has_definite_main_size(flex_item->box, true);
flex_item->has_assigned_definite_main_size = true;
}
}
flex_line.remaining_free_space = calculate_free_space();
@ -1087,10 +1049,8 @@ void FlexFormattingContext::determine_hypothetical_cross_size_of_item(FlexItem&
auto& containing_block_state = throwaway_state.get_mutable(flex_container());
if (is_row_layout()) {
containing_block_state.set_content_width(item.main_size);
containing_block_state.set_has_definite_width(true);
} else {
containing_block_state.set_content_height(item.main_size);
containing_block_state.set_has_definite_height(true);
}
auto& box_state = throwaway_state.get_mutable(item.box);
@ -1100,7 +1060,7 @@ void FlexFormattingContext::determine_hypothetical_cross_size_of_item(FlexItem&
// NOTE: Flex items should always create an independent formatting context!
VERIFY(independent_formatting_context);
independent_formatting_context->run(item.box, LayoutMode::Normal, AvailableSpace::make_indefinite(), AvailableSpace::make_indefinite());
independent_formatting_context->run(item.box, LayoutMode::Normal, AvailableSpace(m_available_space_for_items->width, m_available_space_for_items->height));
auto automatic_cross_size = is_row_layout() ? independent_formatting_context->automatic_content_height()
: box_state.content_width();
@ -1124,12 +1084,6 @@ void FlexFormattingContext::calculate_cross_size_of_each_flex_line(float const c
// and its hypothetical outer cross-start edge, and the largest of the distances between each items baseline
// and its hypothetical outer cross-end edge, and sum these two values.
// FIXME: This isn't spec but makes sense here
if (has_definite_cross_size(flex_container()) && flex_container().computed_values().align_items() == CSS::AlignItems::Stretch) {
flex_line.cross_size = specified_cross_size(flex_container()) / m_flex_lines.size();
continue;
}
// 2. Among all the items not collected by the previous step, find the largest outer hypothetical cross size.
float largest_hypothetical_cross_size = 0;
for (auto& flex_item : flex_line.items) {
@ -1227,10 +1181,10 @@ void FlexFormattingContext::distribute_any_remaining_free_space()
break;
case CSS::JustifyContent::FlexEnd:
flex_region_render_cursor = FlexRegionRenderCursor::Right;
initial_offset = m_available_space->main.value_or(NumericLimits<float>::max());
initial_offset = specified_main_size(flex_container());
break;
case CSS::JustifyContent::Center:
initial_offset = (m_available_space->main.value_or(NumericLimits<float>::max()) - used_main_space) / 2.0f;
initial_offset = (specified_main_size(flex_container()) - used_main_space) / 2.0f;
break;
case CSS::JustifyContent::SpaceBetween:
space_between_items = flex_line.remaining_free_space / (number_of_items - 1);
@ -1435,12 +1389,11 @@ void FlexFormattingContext::copy_dimensions_from_flex_items_to_boxes()
}
// https://drafts.csswg.org/css-flexbox-1/#intrinsic-sizes
void FlexFormattingContext::determine_intrinsic_size_of_flex_container(LayoutMode layout_mode)
void FlexFormattingContext::determine_intrinsic_size_of_flex_container()
{
VERIFY(layout_mode != LayoutMode::Normal);
float main_size = calculate_intrinsic_main_size_of_flex_container();
float cross_size = calculate_intrinsic_cross_size_of_flex_container();
float main_size = calculate_intrinsic_main_size_of_flex_container(layout_mode);
float cross_size = calculate_intrinsic_cross_size_of_flex_container(layout_mode);
if (is_row_layout()) {
m_flex_container_state.set_content_width(main_size);
m_flex_container_state.set_content_height(cross_size);
@ -1451,14 +1404,12 @@ void FlexFormattingContext::determine_intrinsic_size_of_flex_container(LayoutMod
}
// https://drafts.csswg.org/css-flexbox-1/#intrinsic-main-sizes
float FlexFormattingContext::calculate_intrinsic_main_size_of_flex_container(LayoutMode layout_mode)
float FlexFormattingContext::calculate_intrinsic_main_size_of_flex_container()
{
VERIFY(layout_mode != LayoutMode::Normal);
// The min-content main size of a single-line flex container is calculated identically to the max-content main size,
// except that the flex items min-content contributions are used instead of their max-content contributions.
// However, for a multi-line container, it is simply the largest min-content contribution of all the non-collapsed flex items in the flex container.
if (!is_single_line() && flex_container_main_constraint() == SizeConstraint::MinContent) {
if (!is_single_line() && m_available_space_for_items->main.is_min_content()) {
float largest_contribution = 0;
for (auto const& flex_item : m_flex_items) {
// FIXME: Skip collapsed flex items.
@ -1479,10 +1430,10 @@ float FlexFormattingContext::calculate_intrinsic_main_size_of_flex_container(Lay
// This is the items desired flex fraction.
for (auto& flex_item : m_flex_items) {
float contribution;
if (m_flex_container_state.width_constraint == SizeConstraint::MinContent || m_flex_container_state.height_constraint == SizeConstraint::MinContent)
float contribution = 0;
if (m_available_space_for_items->main.is_min_content())
contribution = calculate_main_min_content_contribution(flex_item);
else
else if (m_available_space_for_items->main.is_max_content())
contribution = calculate_main_max_content_contribution(flex_item);
float outer_flex_base_size = flex_item.flex_base_size + flex_item.margins.main_before + flex_item.margins.main_after + flex_item.borders.main_before + flex_item.borders.main_after + flex_item.padding.main_before + flex_item.padding.main_after;
@ -1539,7 +1490,7 @@ float FlexFormattingContext::calculate_intrinsic_main_size_of_flex_container(Lay
flex_line.chosen_flex_fraction = chosen_flex_fraction;
}
auto determine_main_size = [&](bool resolve_percentage_min_max_sizes) -> float {
auto determine_main_size = [&]() -> float {
float largest_sum = 0;
for (auto& flex_line : m_flex_lines) {
// 4. Add each items flex base size to the product of its flex grow factor (scaled flex shrink factor, if shrinking)
@ -1556,8 +1507,8 @@ float FlexFormattingContext::calculate_intrinsic_main_size_of_flex_container(Lay
auto const& computed_min_size = this->computed_main_min_size(flex_item->box);
auto const& computed_max_size = this->computed_main_max_size(flex_item->box);
auto clamp_min = (!computed_min_size.is_auto() && (resolve_percentage_min_max_sizes || !computed_min_size.contains_percentage())) ? specified_main_min_size(flex_item->box) : automatic_minimum_size(*flex_item);
auto clamp_max = (!computed_max_size.is_none() && (resolve_percentage_min_max_sizes || !computed_max_size.contains_percentage())) ? specified_main_max_size(flex_item->box) : NumericLimits<float>::max();
auto clamp_min = (!computed_min_size.is_auto() && !computed_min_size.contains_percentage()) ? specified_main_min_size(flex_item->box) : automatic_minimum_size(*flex_item);
auto clamp_max = (!computed_max_size.is_none() && !computed_max_size.contains_percentage()) ? specified_main_max_size(flex_item->box) : NumericLimits<float>::max();
result = css_clamp(result, clamp_min, clamp_max);
@ -1573,27 +1524,24 @@ float FlexFormattingContext::calculate_intrinsic_main_size_of_flex_container(Lay
return largest_sum;
};
auto first_pass_main_size = determine_main_size(false);
set_main_size(flex_container(), first_pass_main_size);
auto second_pass_main_size = determine_main_size(true);
return second_pass_main_size;
auto main_size = determine_main_size();
set_main_size(flex_container(), main_size);
return main_size;
}
// https://drafts.csswg.org/css-flexbox-1/#intrinsic-cross-sizes
float FlexFormattingContext::calculate_intrinsic_cross_size_of_flex_container(LayoutMode layout_mode)
float FlexFormattingContext::calculate_intrinsic_cross_size_of_flex_container()
{
VERIFY(layout_mode != LayoutMode::Normal);
// The min-content/max-content cross size of a single-line flex container
// is the largest min-content contribution/max-content contribution (respectively) of its flex items.
if (is_single_line()) {
auto calculate_largest_contribution = [&](bool resolve_percentage_min_max_sizes) {
float largest_contribution = 0;
for (auto& flex_item : m_flex_items) {
float contribution;
if (m_flex_container_state.width_constraint == SizeConstraint::MinContent || m_flex_container_state.height_constraint == SizeConstraint::MinContent)
float contribution = 0;
if (m_available_space_for_items->cross.is_min_content())
contribution = calculate_cross_min_content_contribution(flex_item, resolve_percentage_min_max_sizes);
else if (m_flex_container_state.width_constraint == SizeConstraint::MaxContent || m_flex_container_state.height_constraint == SizeConstraint::MaxContent)
else if (m_available_space_for_items->cross.is_max_content())
contribution = calculate_cross_max_content_contribution(flex_item, resolve_percentage_min_max_sizes);
largest_contribution = max(largest_contribution, contribution);
}
@ -1606,11 +1554,19 @@ float FlexFormattingContext::calculate_intrinsic_cross_size_of_flex_container(La
return second_pass_largest_contribution;
}
// For a multi-line flex container, the min-content/max-content cross size is the sum of the flex line cross sizes
// resulting from sizing the flex container under a cross-axis min-content constraint/max-content constraint (respectively).
// FIXME: For a multi-line flex container, the min-content/max-content cross size is the sum of the flex line cross sizes
// resulting from sizing the flex container under a cross-axis min-content constraint/max-content constraint (respectively).
// FIXME: However, if the flex container is flex-flow: column wrap;, then its sized by first finding the largest
// min-content/max-content cross-size contribution among the flex items (respectively), then using that size
// as the available space in the cross axis for each of the flex items during layout.
// HACK: We run steps 7, 9 and 11 from the main algorithm. This gives us *some* cross size information to work with.
for (auto& flex_item : m_flex_items) {
determine_hypothetical_cross_size_of_item(flex_item, false);
}
calculate_cross_size_of_each_flex_line(0, INFINITY);
determine_used_cross_size_of_each_flex_item();
float sum_of_flex_line_cross_sizes = 0;
for (auto& flex_line : m_flex_lines) {
sum_of_flex_line_cross_sizes += flex_line.cross_size;
@ -1709,14 +1665,14 @@ float FlexFormattingContext::calculate_min_content_main_size(FlexItem const& ite
float FlexFormattingContext::calculate_fit_content_main_size(FlexItem const& item) const
{
return is_row_layout() ? calculate_fit_content_width(item.box, m_state.get(item.box).width_constraint, m_available_space->main)
: calculate_fit_content_height(item.box, m_state.get(item.box).height_constraint, m_available_space->main);
return is_row_layout() ? calculate_fit_content_width(item.box, m_available_space_for_items->main)
: calculate_fit_content_height(item.box, m_available_space_for_items->main);
}
float FlexFormattingContext::calculate_fit_content_cross_size(FlexItem const& item) const
{
return !is_row_layout() ? calculate_fit_content_width(item.box, m_state.get(item.box).width_constraint, m_available_space->cross)
: calculate_fit_content_height(item.box, m_state.get(item.box).height_constraint, m_available_space->cross);
return !is_row_layout() ? calculate_fit_content_width(item.box, m_available_space_for_items->cross)
: calculate_fit_content_height(item.box, m_available_space_for_items->cross);
}
float FlexFormattingContext::calculate_max_content_main_size(FlexItem const& item) const
@ -1734,16 +1690,6 @@ float FlexFormattingContext::calculate_max_content_cross_size(FlexItem const& it
return is_row_layout() ? calculate_max_content_height(item.box) : calculate_max_content_width(item.box);
}
SizeConstraint FlexFormattingContext::flex_container_main_constraint() const
{
return is_row_layout() ? m_flex_container_state.width_constraint : m_flex_container_state.height_constraint;
}
SizeConstraint FlexFormattingContext::flex_container_cross_constraint() const
{
return is_row_layout() ? m_flex_container_state.height_constraint : m_flex_container_state.width_constraint;
}
// https://drafts.csswg.org/css-flexbox-1/#stretched
bool FlexFormattingContext::flex_item_is_stretched(FlexItem const& item) const
{

View file

@ -18,15 +18,12 @@ public:
virtual bool inhibits_floating() const override { return true; }
virtual void run(Box const&, LayoutMode, AvailableSpace const& available_width, AvailableSpace const& available_height) override;
virtual void run(Box const&, LayoutMode, AvailableSpace const&) override;
virtual float automatic_content_height() const override;
Box const& flex_container() const { return context_box(); }
private:
SizeConstraint flex_container_main_constraint() const;
SizeConstraint flex_container_cross_constraint() const;
void dump_items() const;
struct DirectionAgnosticMargins {
@ -63,8 +60,6 @@ private:
DirectionAgnosticMargins padding {};
bool is_min_violation { false };
bool is_max_violation { false };
bool has_assigned_definite_main_size { false };
bool has_assigned_definite_cross_size { false };
float add_main_margin_box_sizes(float content_size) const
{
@ -94,14 +89,11 @@ private:
bool has_cross_min_size(Box const&) const;
float specified_main_max_size(Box const&) const;
float specified_cross_max_size(Box const&) const;
float calculated_main_size(Box const&) const;
bool is_cross_auto(Box const&) const;
float specified_main_size_of_child_box(Box const& child_box) const;
float specified_main_min_size(Box const&) const;
float specified_cross_min_size(Box const&) const;
bool has_main_max_size(Box const&) const;
bool has_cross_max_size(Box const&) const;
float sum_of_margin_padding_border_in_main_axis(Box const&) const;
float automatic_minimum_size(FlexItem const&) const;
float content_based_minimum_size(FlexItem const&) const;
Optional<float> specified_size_suggestion(FlexItem const&) const;
@ -121,8 +113,6 @@ private:
void set_main_size(Box const&, float size);
void set_cross_size(Box const&, float size);
void set_has_definite_main_size(Box const&, bool);
void set_has_definite_cross_size(Box const&, bool);
void set_offset(Box const&, float main_offset, float cross_offset);
void set_main_axis_first_margin(FlexItem&, float margin);
void set_main_axis_second_margin(FlexItem&, float margin);
@ -131,12 +121,12 @@ private:
void generate_anonymous_flex_items();
void determine_available_main_and_cross_space(bool& main_is_constrained, bool& cross_is_constrained, float& main_min_size, float& main_max_size, float& cross_min_size, float& cross_max_size);
void determine_available_space_for_items(AvailableSpace const&);
float calculate_indefinite_main_size(FlexItem const&);
void determine_flex_base_size_and_hypothetical_main_size(FlexItem&);
void determine_main_size_of_flex_container(bool main_is_constrained, float main_min_size, float main_max_size);
void determine_main_size_of_flex_container();
void collect_flex_items_into_flex_lines();
@ -165,9 +155,9 @@ private:
bool is_direction_reverse() const { return m_flex_direction == CSS::FlexDirection::ColumnReverse || m_flex_direction == CSS::FlexDirection::RowReverse; }
void populate_specified_margins(FlexItem&, CSS::FlexDirection) const;
void determine_intrinsic_size_of_flex_container(LayoutMode);
[[nodiscard]] float calculate_intrinsic_main_size_of_flex_container(LayoutMode);
[[nodiscard]] float calculate_intrinsic_cross_size_of_flex_container(LayoutMode);
void determine_intrinsic_size_of_flex_container();
[[nodiscard]] float calculate_intrinsic_main_size_of_flex_container();
[[nodiscard]] float calculate_intrinsic_cross_size_of_flex_container();
[[nodiscard]] float calculate_cross_min_content_contribution(FlexItem const&, bool resolve_percentage_min_max_sizes) const;
[[nodiscard]] float calculate_cross_max_content_contribution(FlexItem const&, bool resolve_percentage_min_max_sizes) const;
@ -192,11 +182,14 @@ private:
Vector<FlexItem> m_flex_items;
CSS::FlexDirection m_flex_direction {};
struct AvailableSpaceForItems {
Optional<float> main;
Optional<float> cross;
struct AxisAgnosticAvailableSpace {
AvailableSize main;
AvailableSize cross;
AvailableSize width;
AvailableSize height;
};
Optional<AvailableSpaceForItems> m_available_space;
Optional<AxisAgnosticAvailableSpace> m_available_space_for_items;
Optional<AxisAgnosticAvailableSpace> m_available_space_for_flex_container;
};
}

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.

View file

@ -25,7 +25,10 @@ public:
SVG,
};
virtual void run(Box const&, LayoutMode, AvailableSpace const& available_width, AvailableSpace const& available_height) = 0;
virtual void run(Box const&, LayoutMode, AvailableSpace const&) = 0;
// This function returns the automatic content height of the context's root box.
virtual float automatic_content_width() const { return 0; }
// This function returns the automatic content height of the context's root box.
virtual float automatic_content_height() const = 0;
@ -42,8 +45,8 @@ public:
static bool creates_block_formatting_context(Box const&);
static float compute_width_for_replaced_element(LayoutState const&, ReplacedBox const&);
static float compute_height_for_replaced_element(LayoutState const&, ReplacedBox const&);
static float compute_width_for_replaced_element(LayoutState const&, ReplacedBox const&, AvailableSpace const&);
static float compute_height_for_replaced_element(LayoutState const&, ReplacedBox const&, AvailableSpace const&);
OwnPtr<FormattingContext> create_independent_formatting_context_if_needed(LayoutState&, Box const& child_box);
@ -54,8 +57,8 @@ public:
float calculate_min_content_height(Layout::Box const&) const;
float calculate_max_content_height(Layout::Box const&) const;
float calculate_fit_content_height(Layout::Box const&, SizeConstraint, Optional<float> available_height) const;
float calculate_fit_content_width(Layout::Box const&, SizeConstraint, Optional<float> available_width) const;
float calculate_fit_content_height(Layout::Box const&, AvailableSize const&) const;
float calculate_fit_content_width(Layout::Box const&, AvailableSize const&) const;
virtual float greatest_child_width(Box const&);
@ -69,14 +72,18 @@ public:
float compute_box_y_position_with_respect_to_siblings(Box const&, LayoutState::UsedValues const&);
float calculate_stretch_fit_width(Box const&, AvailableSpace const& available_width) const;
float calculate_stretch_fit_width(Box const&, AvailableSize const&) const;
virtual bool can_determine_size_of_child() const { return false; }
virtual void determine_width_of_child(Box const&, AvailableSpace const&) { }
virtual void determine_height_of_child(Box const&, AvailableSpace const&) { }
protected:
FormattingContext(Type, LayoutState&, Box const&, FormattingContext* parent = nullptr);
float calculate_fit_content_size(float min_content_size, float max_content_size, SizeConstraint, Optional<float> available_space) const;
float calculate_fit_content_size(float min_content_size, float max_content_size, AvailableSize const&) const;
OwnPtr<FormattingContext> layout_inside(Box const&, LayoutMode);
OwnPtr<FormattingContext> layout_inside(Box const&, LayoutMode, AvailableSpace const&);
void compute_inset(Box const& box);
struct SpaceUsedByFloats {
@ -89,20 +96,20 @@ protected:
float preferred_minimum_width { 0 };
};
static float tentative_width_for_replaced_element(LayoutState const&, ReplacedBox const&, CSS::Size const& computed_width);
static float tentative_height_for_replaced_element(LayoutState const&, ReplacedBox const&, CSS::Size const& computed_height);
static float compute_auto_height_for_block_formatting_context_root(LayoutState const&, BlockContainer const&);
static float compute_auto_height_for_block_level_element(LayoutState const&, Box const&);
static float tentative_width_for_replaced_element(LayoutState const&, ReplacedBox const&, CSS::Size const& computed_width, AvailableSpace const&);
static float tentative_height_for_replaced_element(LayoutState const&, ReplacedBox const&, CSS::Size const& computed_height, AvailableSpace const&);
float compute_auto_height_for_block_formatting_context_root(BlockContainer const&) const;
float compute_auto_height_for_block_level_element(Box const&) const;
ShrinkToFitResult calculate_shrink_to_fit_widths(Box const&);
void layout_absolutely_positioned_element(Box const&);
void compute_width_for_absolutely_positioned_element(Box const&);
void compute_width_for_absolutely_positioned_non_replaced_element(Box const&);
void compute_width_for_absolutely_positioned_replaced_element(ReplacedBox const&);
void compute_height_for_absolutely_positioned_element(Box const&);
void compute_height_for_absolutely_positioned_non_replaced_element(Box const&);
void compute_height_for_absolutely_positioned_replaced_element(ReplacedBox const&);
void layout_absolutely_positioned_element(Box const&, AvailableSpace const&);
void compute_width_for_absolutely_positioned_element(Box const&, AvailableSpace const&);
void compute_width_for_absolutely_positioned_non_replaced_element(Box const&, AvailableSpace const&);
void compute_width_for_absolutely_positioned_replaced_element(ReplacedBox const&, AvailableSpace const&);
void compute_height_for_absolutely_positioned_element(Box const&, AvailableSpace const&);
void compute_height_for_absolutely_positioned_non_replaced_element(Box const&, AvailableSpace const&);
void compute_height_for_absolutely_positioned_replaced_element(ReplacedBox const&, AvailableSpace const&);
Type m_type {};

View file

@ -17,7 +17,7 @@ GridFormattingContext::GridFormattingContext(LayoutState& state, BlockContainer
GridFormattingContext::~GridFormattingContext() = default;
void GridFormattingContext::run(Box const& box, LayoutMode, [[maybe_unused]] AvailableSpace const& available_width, [[maybe_unused]] AvailableSpace const& available_height)
void GridFormattingContext::run(Box const& box, LayoutMode, AvailableSpace const& available_space)
{
auto should_skip_is_anonymous_text_run = [&](Box& child_box) -> bool {
if (child_box.is_anonymous() && !child_box.first_child_of_type<BlockContainer>()) {
@ -374,7 +374,8 @@ void GridFormattingContext::run(Box const& box, LayoutMode, [[maybe_unused]] Ava
auto& child_box_state = m_state.get_mutable(positioned_box.box);
if (child_box_state.content_height() > positioned_box.computed_height)
positioned_box.computed_height = child_box_state.content_height();
(void)layout_inside(positioned_box.box, LayoutMode::Normal);
if (auto independent_formatting_context = layout_inside(positioned_box.box, LayoutMode::Normal, available_space))
independent_formatting_context->parent_context_did_dimension_child_root_box();
if (child_box_state.content_height() > positioned_box.computed_height)
positioned_box.computed_height = child_box_state.content_height();
}

View file

@ -17,7 +17,7 @@ public:
explicit GridFormattingContext(LayoutState&, BlockContainer const&, FormattingContext* parent);
~GridFormattingContext();
virtual void run(Box const&, LayoutMode, AvailableSpace const& available_width, AvailableSpace const& available_height) override;
virtual void run(Box const&, LayoutMode, AvailableSpace const& available_space) override;
virtual float automatic_content_height() const override;
private:

View file

@ -24,17 +24,6 @@ InlineFormattingContext::InlineFormattingContext(LayoutState& state, BlockContai
: FormattingContext(Type::Inline, state, containing_block, &parent)
, m_containing_block_state(state.get(containing_block))
{
switch (m_containing_block_state.width_constraint) {
case SizeConstraint::MinContent:
m_effective_containing_block_width = 0;
break;
case SizeConstraint::MaxContent:
m_effective_containing_block_width = INFINITY;
break;
default:
m_effective_containing_block_width = m_containing_block_state.content_width();
break;
}
}
InlineFormattingContext::~InlineFormattingContext() = default;
@ -55,43 +44,54 @@ float InlineFormattingContext::leftmost_x_offset_at(float y) const
auto box_in_root_rect = content_box_rect_in_ancestor_coordinate_space(containing_block(), parent().root(), m_state);
float y_in_root = box_in_root_rect.y() + y;
auto space = parent().space_used_by_floats(y_in_root);
float containing_block_x = m_containing_block_state.offset.x();
return max(space.left, containing_block_x) - containing_block_x;
return space.left;
}
float InlineFormattingContext::available_space_for_line(float y) const
{
if (m_effective_containing_block_width == 0)
return 0;
if (!isfinite(m_effective_containing_block_width))
return INFINITY;
// NOTE: Floats are relative to the BFC root box, not necessarily the containing block of this IFC.
auto& root_block = parent().root();
auto box_in_root_rect = content_box_rect_in_ancestor_coordinate_space(containing_block(), root_block, m_state);
float y_in_root = box_in_root_rect.y() + y;
auto space = parent().space_used_by_floats(y_in_root);
space.left = max(space.left, m_containing_block_state.offset.x()) - m_containing_block_state.offset.x();
space.right = min(m_containing_block_state.content_width() - space.right, m_containing_block_state.offset.x() + m_effective_containing_block_width);
space.left = space.left;
space.right = min(m_available_space->width.to_px() - space.right, m_available_space->width.to_px());
return space.right - space.left;
}
float InlineFormattingContext::automatic_content_height() const
float InlineFormattingContext::automatic_content_width() const
{
return compute_auto_height_for_block_formatting_context_root(m_state, containing_block());
return m_automatic_content_width;
}
void InlineFormattingContext::run(Box const&, LayoutMode layout_mode, [[maybe_unused]] AvailableSpace const& available_width, [[maybe_unused]] AvailableSpace const& available_height)
float InlineFormattingContext::automatic_content_height() const
{
return m_automatic_content_height;
}
void InlineFormattingContext::run(Box const&, LayoutMode layout_mode, AvailableSpace const& available_space)
{
VERIFY(containing_block().children_are_inline());
m_available_space = available_space;
generate_line_boxes(layout_mode);
float max_line_width = 0;
float content_height = 0;
for (auto& line_box : m_containing_block_state.line_boxes) {
max_line_width = max(max_line_width, line_box.width());
content_height += line_box.height();
}
m_automatic_content_width = max_line_width;
m_automatic_content_height = content_height;
}
void InlineFormattingContext::dimension_box_on_line(Box const& box, LayoutMode layout_mode)
{
auto width_of_containing_block = CSS::Length::make_px(m_effective_containing_block_width);
auto width_of_containing_block = CSS::Length::make_px(m_available_space->width.to_px());
auto& box_state = m_state.get_mutable(box);
auto const& computed_values = box.computed_values();
@ -115,10 +115,10 @@ void InlineFormattingContext::dimension_box_on_line(Box const& box, LayoutMode l
auto& replaced = verify_cast<ReplacedBox>(box);
if (is<SVGSVGBox>(box))
(void)layout_inside(replaced, layout_mode);
(void)layout_inside(replaced, layout_mode, *m_available_space);
box_state.set_content_width(compute_width_for_replaced_element(m_state, replaced));
box_state.set_content_height(compute_height_for_replaced_element(m_state, replaced));
box_state.set_content_width(compute_width_for_replaced_element(m_state, replaced, *m_available_space));
box_state.set_content_height(compute_height_for_replaced_element(m_state, replaced, *m_available_space));
return;
}
@ -129,7 +129,7 @@ void InlineFormattingContext::dimension_box_on_line(Box const& box, LayoutMode l
if (width_value.is_auto()) {
auto result = calculate_shrink_to_fit_widths(inline_block);
auto available_width = m_containing_block_state.content_width()
auto available_width = m_available_space->width.to_px()
- box_state.margin_left
- box_state.border_left
- box_state.padding_left
@ -140,15 +140,19 @@ void InlineFormattingContext::dimension_box_on_line(Box const& box, LayoutMode l
auto width = min(max(result.preferred_minimum_width, available_width), result.preferred_width);
box_state.set_content_width(width);
} else {
auto container_width = CSS::Length::make_px(m_effective_containing_block_width);
box_state.set_content_width(width_value.resolved(box, container_width).to_px(inline_block));
if (width_value.contains_percentage() && !m_available_space->width.is_definite()) {
// NOTE: We can't resolve percentages yet. We'll have to wait until after inner layout.
} else {
auto container_width = CSS::Length::make_px(m_available_space->width.to_px());
box_state.set_content_width(width_value.resolved(box, container_width).to_px(inline_block));
}
}
auto independent_formatting_context = layout_inside(inline_block, layout_mode);
auto independent_formatting_context = layout_inside(inline_block, layout_mode, box_state.available_inner_space_or_constraints_from(*m_available_space));
auto& height_value = inline_block.computed_values().height();
if (height_value.is_auto()) {
// FIXME: (10.6.6) If 'height' is 'auto', the height depends on the element's descendants per 10.6.7.
BlockFormattingContext::compute_height(inline_block, m_state);
parent().compute_height(inline_block, AvailableSpace(AvailableSize::make_indefinite(), AvailableSize::make_indefinite()));
} else {
auto container_height = CSS::Length::make_px(m_containing_block_state.content_height());
box_state.set_content_height(height_value.resolved(box, container_height).to_px(inline_block));
@ -177,11 +181,11 @@ void InlineFormattingContext::apply_justification_to_fragments(CSS::TextJustify
break;
}
float excess_horizontal_space = m_effective_containing_block_width - line_box.width();
float excess_horizontal_space = m_available_space->width.to_px() - line_box.width();
// Only justify the text if the excess horizontal space is less than or
// equal to 10%, or if we are not looking at the last line box.
if (is_last_line && excess_horizontal_space / m_effective_containing_block_width > text_justification_threshold)
if (is_last_line && excess_horizontal_space / m_available_space->width.to_px() > text_justification_threshold)
return;
float excess_horizontal_space_including_whitespace = excess_horizontal_space;
@ -250,7 +254,7 @@ void InlineFormattingContext::generate_line_boxes(LayoutMode layout_mode)
case InlineLevelIterator::Item::Type::FloatingElement:
if (is<Box>(*item.node))
parent().layout_floating_box(static_cast<Layout::Box const&>(*item.node), containing_block(), layout_mode, &line_builder);
parent().layout_floating_box(static_cast<Layout::Box const&>(*item.node), containing_block(), layout_mode, *m_available_space, &line_builder);
break;
case InlineLevelIterator::Item::Type::Text: {
@ -318,9 +322,9 @@ bool InlineFormattingContext::can_fit_new_line_at_y(float y) const
auto space_bottom = parent().space_used_by_floats(y_in_root + containing_block().line_height() - 1);
[[maybe_unused]] auto top_left_edge = space_top.left;
[[maybe_unused]] auto top_right_edge = m_effective_containing_block_width - space_top.right;
[[maybe_unused]] auto top_right_edge = m_available_space->width.to_px() - space_top.right;
[[maybe_unused]] auto bottom_left_edge = space_bottom.left;
[[maybe_unused]] auto bottom_right_edge = m_effective_containing_block_width - space_bottom.right;
[[maybe_unused]] auto bottom_right_edge = m_available_space->width.to_px() - space_bottom.right;
if (top_left_edge > bottom_right_edge)
return false;

View file

@ -23,8 +23,9 @@ public:
BlockContainer const& containing_block() const { return static_cast<BlockContainer const&>(context_box()); }
virtual void run(Box const&, LayoutMode, AvailableSpace const& available_width, AvailableSpace const& available_height) override;
virtual void run(Box const&, LayoutMode, AvailableSpace const&) override;
virtual float automatic_content_height() const override;
virtual float automatic_content_width() const override;
void dimension_box_on_line(Box const&, LayoutMode);
@ -33,14 +34,16 @@ public:
bool any_floats_intrude_at_y(float y) const;
bool can_fit_new_line_at_y(float y) const;
float effective_containing_block_width() const { return m_effective_containing_block_width; }
private:
void generate_line_boxes(LayoutMode);
void apply_justification_to_fragments(CSS::TextJustify, LineBox&, bool is_last_line);
LayoutState::UsedValues const& m_containing_block_state;
float m_effective_containing_block_width { 0 };
Optional<AvailableSpace> m_available_space;
float m_automatic_content_width { 0 };
float m_automatic_content_height { 0 };
};
}

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/Layout/AvailableSpace.h>
#include <LibWeb/Layout/BlockContainer.h>
#include <LibWeb/Layout/LayoutState.h>
#include <LibWeb/Layout/TextNode.h>
@ -195,9 +196,21 @@ void LayoutState::UsedValues::set_node(NodeWithStyleAndBoxModelMetrics& node, Us
if (size.is_auto()) {
// NOTE: The width of a non-flex-item block is considered definite if it's auto and the containing block has definite width.
if (width && node.parent() && !node.parent()->computed_values().display().is_flex_inside()) {
if (width
&& node.computed_values().display().is_block_outside()
&& node.parent()
&& !node.parent()->is_floating()
&& (node.parent()->computed_values().display().is_flow_root_inside()
|| node.parent()->computed_values().display().is_flow_inside())) {
if (containing_block_has_definite_size) {
resolved_definite_size = width ? containing_block_used_values->content_width() : containing_block_used_values->content_height();
float available_width = containing_block_used_values->content_width();
resolved_definite_size = available_width
- margin_left
- margin_right
- padding_left
- padding_right
- border_left
- border_right;
return true;
}
return false;
@ -205,9 +218,22 @@ void LayoutState::UsedValues::set_node(NodeWithStyleAndBoxModelMetrics& node, Us
return false;
}
if (size.is_length()) {
if (size.length().is_calculated())
if (size.is_length() && size.length().is_calculated()) {
if (width && size.length().calculated_style_value()->contains_percentage() && containing_block_has_definite_size) {
auto& calc_value = *size.length().calculated_style_value();
auto containing_block_width_as_length = CSS::Length::make_px(containing_block_used_values->content_width());
resolved_definite_size = calc_value.resolve_length_percentage(node, containing_block_width_as_length).value_or(CSS::Length::make_auto()).to_px(node);
return false;
}
if (size.length().calculated_style_value()->contains_percentage())
return false;
resolved_definite_size = size.length().to_px(node);
return true;
}
if (size.is_length()) {
VERIFY(!size.is_auto()); // This should have been covered by the Size::is_auto() branch above.
VERIFY(!size.length().is_calculated()); // Covered above.
resolved_definite_size = size.length().to_px(node);
return true;
}
@ -230,33 +256,73 @@ void LayoutState::UsedValues::set_node(NodeWithStyleAndBoxModelMetrics& node, Us
void LayoutState::UsedValues::set_content_width(float width)
{
m_content_width = width;
m_has_definite_width = true;
}
void LayoutState::UsedValues::set_content_height(float height)
{
m_content_height = height;
m_has_definite_height = true;
}
float LayoutState::resolved_definite_width(Box const& box) const
{
auto const& computed_value = box.computed_values().width();
if (computed_value.is_auto())
return get(*box.containing_block()).content_width();
if (computed_value.is_length())
return get(box).content_width();
auto containing_block_size = get(*box.containing_block()).content_width();
return computed_value.resolved(box, CSS::Length::make_px(containing_block_size)).to_px(box);
return get(box).content_width();
}
float LayoutState::resolved_definite_height(Box const& box) const
{
auto const& computed_value = box.computed_values().height();
if (computed_value.is_auto())
return get(*box.containing_block()).content_height();
if (computed_value.is_length())
return get(box).content_height();
auto containing_block_size = get(*box.containing_block()).content_height();
return computed_value.resolved(box, CSS::Length::make_px(containing_block_size)).to_px(box);
return get(box).content_height();
}
AvailableSize LayoutState::UsedValues::available_width_inside() const
{
if (width_constraint == SizeConstraint::MinContent)
return AvailableSize::make_min_content();
if (width_constraint == SizeConstraint::MaxContent)
return AvailableSize::make_max_content();
if (has_definite_width())
return AvailableSize::make_definite(m_content_width);
return AvailableSize::make_indefinite();
}
AvailableSize LayoutState::UsedValues::available_height_inside() const
{
if (height_constraint == SizeConstraint::MinContent)
return AvailableSize::make_min_content();
if (height_constraint == SizeConstraint::MaxContent)
return AvailableSize::make_max_content();
if (has_definite_height())
return AvailableSize::make_definite(m_content_height);
return AvailableSize::make_indefinite();
}
AvailableSpace LayoutState::UsedValues::available_inner_space_or_constraints_from(AvailableSpace const& outer_space) const
{
auto inner_width = available_width_inside();
auto inner_height = available_height_inside();
if (inner_width.is_indefinite() && outer_space.width.is_intrinsic_sizing_constraint())
inner_width = outer_space.width;
if (inner_height.is_indefinite() && outer_space.height.is_intrinsic_sizing_constraint())
inner_height = outer_space.height;
return AvailableSpace(inner_width, inner_height);
}
void LayoutState::UsedValues::set_content_offset(Gfx::FloatPoint offset)
{
set_content_x(offset.x());
set_content_y(offset.y());
}
void LayoutState::UsedValues::set_content_x(float x)
{
offset.set_x(x);
}
void LayoutState::UsedValues::set_content_y(float y)
{
offset.set_y(y);
}
}

View file

@ -20,6 +20,9 @@ enum class SizeConstraint {
MaxContent,
};
class AvailableSize;
class AvailableSpace;
struct LayoutState {
LayoutState()
: m_root(*this)
@ -52,8 +55,15 @@ struct LayoutState {
bool has_definite_width() const { return m_has_definite_width && width_constraint == SizeConstraint::None; }
bool has_definite_height() const { return m_has_definite_height && height_constraint == SizeConstraint::None; }
void set_has_definite_width(bool value) { m_has_definite_width = value; }
void set_has_definite_height(bool value) { m_has_definite_height = value; }
// Returns the available space for content inside this layout box.
// If the space in an axis is indefinite, and the outer space is an intrinsic sizing constraint,
// the constraint is used in that axis instead.
AvailableSpace available_inner_space_or_constraints_from(AvailableSpace const& outer_space) const;
void set_content_offset(Gfx::FloatPoint);
void set_content_x(float);
void set_content_y(float);
Gfx::FloatPoint offset;
@ -113,6 +123,9 @@ struct LayoutState {
auto const& floating_descendants() const { return m_floating_descendants; }
private:
AvailableSize available_width_inside() const;
AvailableSize available_height_inside() const;
Layout::NodeWithStyleAndBoxModelMetrics* m_node { nullptr };
float m_content_width { 0 };

View file

@ -25,7 +25,7 @@ float SVGFormattingContext::automatic_content_height() const
return 0;
}
void SVGFormattingContext::run(Box const& box, LayoutMode, [[maybe_unused]] AvailableSpace const& available_width, [[maybe_unused]] AvailableSpace const& available_height)
void SVGFormattingContext::run(Box const& box, LayoutMode, [[maybe_unused]] AvailableSpace const& available_space)
{
auto& svg_svg_element = verify_cast<SVG::SVGSVGElement>(*box.dom_node());
@ -38,7 +38,7 @@ void SVGFormattingContext::run(Box const& box, LayoutMode, [[maybe_unused]] Avai
auto& dom_node = const_cast<SVGGeometryBox&>(geometry_box).dom_node();
if (svg_svg_element.has_attribute(HTML::AttributeNames::width) && svg_svg_element.has_attribute(HTML::AttributeNames::height)) {
geometry_box_state.offset = { 0, 0 };
geometry_box_state.set_content_offset({ 0, 0 });
auto& layout_node = *svg_svg_element.layout_node();
// FIXME: Allow for relative lengths here
@ -67,7 +67,7 @@ void SVGFormattingContext::run(Box const& box, LayoutMode, [[maybe_unused]] Avai
if (maybe_view_box.has_value()) {
auto view_box = maybe_view_box.value();
Gfx::FloatPoint viewbox_offset = { view_box.min_x, view_box.min_y };
geometry_box_state.offset = path_bounding_box.top_left() + viewbox_offset;
geometry_box_state.set_content_offset(path_bounding_box.top_left() + viewbox_offset);
geometry_box_state.set_content_width(view_box.width);
geometry_box_state.set_content_height(view_box.height);
@ -75,7 +75,7 @@ void SVGFormattingContext::run(Box const& box, LayoutMode, [[maybe_unused]] Avai
return IterationDecision::Continue;
}
geometry_box_state.offset = path_bounding_box.top_left();
geometry_box_state.set_content_offset(path_bounding_box.top_left());
geometry_box_state.set_content_width(path_bounding_box.width());
geometry_box_state.set_content_height(path_bounding_box.height());
}

View file

@ -16,7 +16,7 @@ public:
explicit SVGFormattingContext(LayoutState&, Box const&, FormattingContext* parent);
~SVGFormattingContext();
virtual void run(Box const&, LayoutMode, AvailableSpace const& available_width, AvailableSpace const& available_height) override;
virtual void run(Box const&, LayoutMode, AvailableSpace const&) override;
virtual float automatic_content_height() const override;
};

View file

@ -23,11 +23,11 @@ TableFormattingContext::TableFormattingContext(LayoutState& state, BlockContaine
TableFormattingContext::~TableFormattingContext() = default;
void TableFormattingContext::run(Box const& box, LayoutMode, [[maybe_unused]] AvailableSpace const& available_width, [[maybe_unused]] AvailableSpace const& available_height)
void TableFormattingContext::run(Box const& box, LayoutMode, AvailableSpace const& available_space)
{
auto& box_state = m_state.get_mutable(box);
compute_width(box);
compute_width(box, available_space);
auto table_width = CSS::Length::make_px(box_state.content_width());
auto table_width_is_auto = box.computed_values().width().is_auto();
@ -36,12 +36,12 @@ void TableFormattingContext::run(Box const& box, LayoutMode, [[maybe_unused]] Av
Vector<ColumnWidth> column_widths;
box.for_each_child_of_type<TableRowGroupBox>([&](auto& row_group_box) {
compute_width(row_group_box);
compute_width(row_group_box, available_space);
auto column_count = row_group_box.column_count();
column_widths.resize(max(column_count, column_widths.size()));
row_group_box.template for_each_child_of_type<TableRowBox>([&](auto& row) {
calculate_column_widths(row, table_width, column_widths);
calculate_column_widths(row, table_width, column_widths, available_space);
});
});
box.for_each_child_of_type<TableRowGroupBox>([&](auto& row_group_box) {
@ -89,7 +89,7 @@ void TableFormattingContext::run(Box const& box, LayoutMode, [[maybe_unused]] Av
row_group_box.template for_each_child_of_type<TableRowBox>([&](auto& row) {
auto& row_state = m_state.get_mutable(row);
row_state.offset = { 0, content_height };
layout_row(row, column_widths);
layout_row(row, column_widths, available_space);
content_width = max(content_width, row_state.content_width());
content_height += row_state.content_height();
});
@ -110,7 +110,7 @@ void TableFormattingContext::run(Box const& box, LayoutMode, [[maybe_unused]] Av
m_automatic_content_height = total_content_height;
}
void TableFormattingContext::calculate_column_widths(Box const& row, CSS::Length const& table_width, Vector<ColumnWidth>& column_widths)
void TableFormattingContext::calculate_column_widths(Box const& row, CSS::Length const& table_width, Vector<ColumnWidth>& column_widths, AvailableSpace const& available_space)
{
m_state.get_mutable(row);
size_t column_index = 0;
@ -123,10 +123,11 @@ void TableFormattingContext::calculate_column_widths(Box const& row, CSS::Length
auto width = calculate_max_content_width(cell);
cell_state.set_content_width(width);
} else {
compute_width(cell, LayoutMode::Normal);
compute_width(cell, AvailableSpace(AvailableSize::make_indefinite(), AvailableSize::make_indefinite()), LayoutMode::Normal);
}
(void)layout_inside(cell, LayoutMode::Normal);
if (auto independent_formatting_context = layout_inside(cell, LayoutMode::Normal, cell_state.available_inner_space_or_constraints_from(available_space)))
independent_formatting_context->parent_context_did_dimension_child_root_box();
if (cell.colspan() == 1) {
auto min_width = calculate_min_content_width(cell);
@ -167,7 +168,7 @@ void TableFormattingContext::calculate_column_widths(Box const& row, CSS::Length
});
}
void TableFormattingContext::layout_row(Box const& row, Vector<ColumnWidth>& column_widths)
void TableFormattingContext::layout_row(Box const& row, Vector<ColumnWidth>& column_widths, AvailableSpace const& available_space)
{
auto& row_state = m_state.get_mutable(row);
size_t column_index = 0;
@ -184,11 +185,12 @@ void TableFormattingContext::layout_row(Box const& row, Vector<ColumnWidth>& col
span_width += column_widths[column_index++].used;
cell_state.set_content_width(span_width - cell_state.border_box_left() - cell_state.border_box_right());
BlockFormattingContext::compute_height(cell, m_state);
BlockFormattingContext::compute_height(cell, AvailableSpace(AvailableSize::make_indefinite(), AvailableSize::make_indefinite()));
cell_state.offset = row_state.offset.translated(cell_state.border_box_left() + content_width, cell_state.border_box_top());
// Layout the cell contents a second time, now that we know its final width.
(void)layout_inside(cell, LayoutMode::Normal);
if (auto independent_formatting_context = layout_inside(cell, LayoutMode::Normal, cell_state.available_inner_space_or_constraints_from(available_space)))
independent_formatting_context->parent_context_did_dimension_child_root_box();
content_width += span_width;
tallest_cell_height = max(tallest_cell_height, cell_state.border_box_height());

View file

@ -23,12 +23,12 @@ public:
explicit TableFormattingContext(LayoutState&, BlockContainer const&, FormattingContext* parent);
~TableFormattingContext();
virtual void run(Box const&, LayoutMode, AvailableSpace const& available_width, AvailableSpace const& available_height) override;
virtual void run(Box const&, LayoutMode, AvailableSpace const&) override;
virtual float automatic_content_height() const override;
private:
void calculate_column_widths(Box const& row, CSS::Length const& table_width, Vector<ColumnWidth>& column_widths);
void layout_row(Box const& row, Vector<ColumnWidth>& column_widths);
void calculate_column_widths(Box const& row, CSS::Length const& table_width, Vector<ColumnWidth>& column_widths, AvailableSpace const&);
void layout_row(Box const& row, Vector<ColumnWidth>& column_widths, AvailableSpace const&);
float m_automatic_content_height { 0 };
};