diff options
author | Andreas Kling <kling@serenityos.org> | 2022-02-20 15:51:24 +0100 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2022-02-21 18:35:12 +0100 |
commit | c9700e100e1cfe463b837b7a1337a5a7b7df63d9 (patch) | |
tree | be46a34022d858f05089a68ed01057737db6d10b | |
parent | 561612f2192adf4c0cecfe7a19df5804fc1960ff (diff) | |
download | serenity-c9700e100e1cfe463b837b7a1337a5a7b7df63d9.zip |
LibWeb: Start making our layout system "transactional"
This patch adds a map of Layout::Node to FormattingState::NodeState.
Instead of updating layout nodes incrementally as layout progresses
through the formatting contexts, all updates are now written to the
corresponding NodeState instead.
At the end of layout, FormattingState::commit() is called, which
transfers all the values from the NodeState objects to the Node.
This will soon allow us to perform completely non-destructive layouts
which don't affect the tree.
Note that there are many imperfections here, and still many places
where we assign to the NodeState, but later read directly from the Node
instead. I'm just committing at this stage to make subsequent diffs
easier to understand.
27 files changed, 755 insertions, 572 deletions
diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index d16ce775e2..946413fdad 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -224,6 +224,7 @@ set(SOURCES Layout/CheckBox.cpp Layout/FlexFormattingContext.cpp Layout/FormattingContext.cpp + Layout/FormattingState.cpp Layout/FrameBox.cpp Layout/ImageBox.cpp Layout/InitialContainingBlock.cpp diff --git a/Userland/Libraries/LibWeb/DOM/Document.cpp b/Userland/Libraries/LibWeb/DOM/Document.cpp index 5c30e9da92..59617f0199 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.cpp +++ b/Userland/Libraries/LibWeb/DOM/Document.cpp @@ -563,12 +563,16 @@ void Document::update_layout() m_layout_root = static_ptr_cast<Layout::InitialContainingBlock>(tree_builder.build(*this)); } - Layout::FormattingState root_formatting_state; - Layout::BlockFormattingContext root_formatting_context(root_formatting_state, *m_layout_root, nullptr); + Layout::FormattingState formatting_state; + Layout::BlockFormattingContext root_formatting_context(formatting_state, *m_layout_root, nullptr); m_layout_root->build_stacking_context_tree(); - m_layout_root->set_content_size(viewport_rect.size().to_type<float>()); + + auto& icb_state = formatting_state.ensure(*m_layout_root); + icb_state.content_width = viewport_rect.width(); + icb_state.content_height = viewport_rect.height(); root_formatting_context.run(*m_layout_root, Layout::LayoutMode::Default); + formatting_state.commit(); browsing_context()->set_needs_display(); diff --git a/Userland/Libraries/LibWeb/Layout/BlockContainer.cpp b/Userland/Libraries/LibWeb/Layout/BlockContainer.cpp index 6cd636fa5a..f58214fc5b 100644 --- a/Userland/Libraries/LibWeb/Layout/BlockContainer.cpp +++ b/Userland/Libraries/LibWeb/Layout/BlockContainer.cpp @@ -130,17 +130,4 @@ bool BlockContainer::handle_mousewheel(Badge<EventHandler>, const Gfx::IntPoint& return true; } -LineBox& BlockContainer::ensure_last_line_box() -{ - if (m_line_boxes.is_empty()) - return add_line_box(); - return m_line_boxes.last(); -} - -LineBox& BlockContainer::add_line_box() -{ - m_line_boxes.append(LineBox()); - return m_line_boxes.last(); -} - } diff --git a/Userland/Libraries/LibWeb/Layout/BlockContainer.h b/Userland/Libraries/LibWeb/Layout/BlockContainer.h index 8c68f14082..d05874b81e 100644 --- a/Userland/Libraries/LibWeb/Layout/BlockContainer.h +++ b/Userland/Libraries/LibWeb/Layout/BlockContainer.h @@ -36,11 +36,9 @@ public: const Gfx::FloatPoint& scroll_offset() const { return m_scroll_offset; } void set_scroll_offset(const Gfx::FloatPoint&); - Vector<LineBox>& line_boxes() { return m_line_boxes; } const Vector<LineBox>& line_boxes() const { return m_line_boxes; } - LineBox& ensure_last_line_box(); - LineBox& add_line_box(); + void set_line_boxes(Vector<LineBox>&& line_boxes) { m_line_boxes = move(line_boxes); } protected: Vector<LineBox> m_line_boxes; diff --git a/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.cpp index a5d7ebfa84..1105aaec00 100644 --- a/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.cpp @@ -18,7 +18,7 @@ namespace Web::Layout { -BlockFormattingContext::BlockFormattingContext(FormattingState& state, BlockContainer& root, FormattingContext* parent) +BlockFormattingContext::BlockFormattingContext(FormattingState& state, BlockContainer const& root, FormattingContext* parent) : FormattingContext(Type::Block, state, root, parent) { } @@ -38,7 +38,7 @@ bool BlockFormattingContext::is_initial() const return is<InitialContainingBlock>(root()); } -void BlockFormattingContext::run(Box&, LayoutMode layout_mode) +void BlockFormattingContext::run(Box const&, LayoutMode layout_mode) { if (is_initial()) { layout_initial_containing_block(layout_mode); @@ -61,7 +61,7 @@ void BlockFormattingContext::parent_context_did_dimension_child_root_box() apply_transformations_to_children(root()); } -void BlockFormattingContext::apply_transformations_to_children(Box& box) +void BlockFormattingContext::apply_transformations_to_children(Box const& box) { box.for_each_child_of_type<Box>([&](auto& child_box) { float transform_y_offset = 0.0f; @@ -90,12 +90,13 @@ void BlockFormattingContext::apply_transformations_to_children(Box& box) } } - auto untransformed_offset = child_box.effective_offset(); - child_box.set_offset(untransformed_offset.x(), untransformed_offset.y() + transform_y_offset); + auto& child_box_state = m_state.ensure(child_box); + auto untransformed_offset = child_box_state.offset; + child_box_state.offset = Gfx::FloatPoint { untransformed_offset.x(), untransformed_offset.y() + transform_y_offset }; }); } -void BlockFormattingContext::compute_width(Box& box) +void BlockFormattingContext::compute_width(Box const& box) { if (box.is_absolutely_positioned()) { compute_width_for_absolutely_positioned_element(box); @@ -105,7 +106,8 @@ void BlockFormattingContext::compute_width(Box& box) if (is<ReplacedBox>(box)) { // FIXME: This should not be done *by* ReplacedBox auto& replaced = verify_cast<ReplacedBox>(box); - replaced.prepare_for_replaced_layout(); + // 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); return; } @@ -115,8 +117,8 @@ void BlockFormattingContext::compute_width(Box& box) return; } - auto& computed_values = box.computed_values(); - float width_of_containing_block = box.containing_block()->content_width(); + auto const& computed_values = box.computed_values(); + float width_of_containing_block = m_state.get(*box.containing_block()).content_width; auto width_of_containing_block_as_length = CSS::Length::make_px(width_of_containing_block); auto zero_value = CSS::Length::make_px(0); @@ -226,20 +228,22 @@ void BlockFormattingContext::compute_width(Box& box) } } - box.set_content_width(used_width.to_px(box)); - box.box_model().margin.left = margin_left.to_px(box); - box.box_model().margin.right = margin_right.to_px(box); - box.box_model().border.left = computed_values.border_left().width; - box.box_model().border.right = computed_values.border_right().width; - box.box_model().padding.left = padding_left.to_px(box); - box.box_model().padding.right = padding_right.to_px(box); + auto& box_state = m_state.ensure(box); + box_state.content_width = used_width.to_px(box); + box_state.margin_left = margin_left.to_px(box); + box_state.margin_right = margin_right.to_px(box); + box_state.border_left = computed_values.border_left().width; + box_state.border_right = computed_values.border_right().width; + box_state.padding_left = padding_left.to_px(box); + box_state.padding_right = padding_right.to_px(box); } -void BlockFormattingContext::compute_width_for_floating_box(Box& box) +void BlockFormattingContext::compute_width_for_floating_box(Box const& box) { // 10.3.5 Floating, non-replaced elements auto& computed_values = box.computed_values(); - float width_of_containing_block = box.containing_block()->content_width(); + auto& containing_block = *box.containing_block(); + float width_of_containing_block = m_state.get(containing_block).content_width; auto width_of_containing_block_as_length = CSS::Length::make_px(width_of_containing_block); auto zero_value = CSS::Length::make_px(0); @@ -272,26 +276,27 @@ void BlockFormattingContext::compute_width_for_floating_box(Box& box) width = CSS::Length(min(max(result.preferred_minimum_width, available_width), result.preferred_width), CSS::Length::Type::Px); } - float final_width = width.to_px(box); - box.set_content_width(final_width); - box.box_model().margin.left = margin_left.to_px(box); - box.box_model().margin.right = margin_right.to_px(box); - box.box_model().border.left = computed_values.border_left().width; - box.box_model().border.right = computed_values.border_right().width; - box.box_model().padding.left = padding_left.to_px(box); - box.box_model().padding.right = padding_right.to_px(box); + auto& box_state = m_state.ensure(box); + box_state.content_width = width.to_px(box); + box_state.margin_left = margin_left.to_px(box); + box_state.margin_right = margin_right.to_px(box); + box_state.border_left = computed_values.border_left().width; + box_state.border_right = computed_values.border_right().width; + box_state.padding_left = padding_left.to_px(box); + box_state.padding_right = padding_right.to_px(box); } -void BlockFormattingContext::compute_width_for_block_level_replaced_element_in_normal_flow(ReplacedBox& box) +void BlockFormattingContext::compute_width_for_block_level_replaced_element_in_normal_flow(ReplacedBox const& box) { - box.set_content_width(compute_width_for_replaced_element(box)); + m_state.ensure(box).content_width = compute_width_for_replaced_element(m_state, box); } -float BlockFormattingContext::compute_theoretical_height(Box const& box) +float BlockFormattingContext::compute_theoretical_height(FormattingState const& state, Box const& box) { - auto& computed_values = box.computed_values(); - auto& containing_block = *box.containing_block(); - auto containing_block_height = CSS::Length::make_px(containing_block.content_height()); + auto const& computed_values = box.computed_values(); + auto const& containing_block = *box.containing_block(); + auto const& containing_block_state = state.get(containing_block); + auto containing_block_height = CSS::Length::make_px(containing_block_state.content_height); auto is_absolute = [](Optional<CSS::LengthPercentage> const& length_percentage) { return length_percentage.has_value() && length_percentage->is_length() && length_percentage->length().is_absolute(); @@ -300,12 +305,12 @@ float BlockFormattingContext::compute_theoretical_height(Box const& box) // 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(verify_cast<ReplacedBox>(box)); + height = compute_height_for_replaced_element(state, verify_cast<ReplacedBox>(box)); } else { if (!box.computed_values().height().has_value() || (box.computed_values().height()->is_length() && box.computed_values().height()->length().is_auto()) || (computed_values.height().has_value() && computed_values.height()->is_percentage() && !is_absolute(containing_block.computed_values().height()))) { - height = compute_auto_height_for_block_level_element(box, ConsiderFloats::No); + height = compute_auto_height_for_block_level_element(state, box, ConsiderFloats::No); } else { height = computed_values.height().has_value() ? computed_values.height()->resolved(box, containing_block_height).to_px(box) : 0; } @@ -322,63 +327,29 @@ float BlockFormattingContext::compute_theoretical_height(Box const& box) return height; } -void BlockFormattingContext::compute_height(Box& box) +void BlockFormattingContext::compute_height(Box const& box, FormattingState& state) { - auto& computed_values = box.computed_values(); - auto& containing_block = *box.containing_block(); - auto width_of_containing_block_as_length = CSS::Length::make_px(containing_block.content_width()); + auto const& computed_values = box.computed_values(); + auto const& containing_block = *box.containing_block(); + auto width_of_containing_block_as_length = CSS::Length::make_px(state.get(containing_block).content_width); // First, resolve the top/bottom parts of the surrounding box model. - // FIXME: While negative values are generally allowed for margins, for now just ignore those for height calculation - box.box_model().margin.top = max(computed_values.margin().top.resolved(box, width_of_containing_block_as_length).to_px(box), 0); - box.box_model().margin.bottom = max(computed_values.margin().bottom.resolved(box, width_of_containing_block_as_length).to_px(box), 0); - - box.box_model().border.top = computed_values.border_top().width; - box.box_model().border.bottom = computed_values.border_bottom().width; - box.box_model().padding.top = computed_values.padding().top.resolved(box, width_of_containing_block_as_length).to_px(box); - box.box_model().padding.bottom = computed_values.padding().bottom.resolved(box, width_of_containing_block_as_length).to_px(box); + auto& box_state = state.ensure(box); - auto height = compute_theoretical_height(box); - box.set_content_height(height); -} - -void BlockFormattingContext::compute_position(Box& box) -{ - // 9.4.3 Relative positioning - // Once a box has been laid out according to the normal flow or floated, it may be shifted relative to this position. + // FIXME: While negative values are generally allowed for margins, for now just ignore those for height calculation + box_state.margin_top = max(computed_values.margin().top.resolved(box, width_of_containing_block_as_length).to_px(box), 0); + box_state.margin_bottom = max(computed_values.margin().bottom.resolved(box, width_of_containing_block_as_length).to_px(box), 0); - auto& box_model = box.box_model(); - auto& computed_values = box.computed_values(); - float width_of_containing_block = box.containing_block()->content_width(); - auto width_of_containing_block_as_length = CSS::Length::make_px(width_of_containing_block); + 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_as_length).to_px(box); + box_state.padding_bottom = computed_values.padding().bottom.resolved(box, width_of_containing_block_as_length).to_px(box); - auto specified_left = computed_values.offset().left.resolved(box, width_of_containing_block_as_length).resolved(box); - auto specified_right = computed_values.offset().right.resolved(box, width_of_containing_block_as_length).resolved(box); - - if (specified_left.is_auto() && specified_right.is_auto()) { - // If both 'left' and 'right' are 'auto' (their initial values), the used values are '0' (i.e., the boxes stay in their original position). - box_model.offset.left = 0; - box_model.offset.right = 0; - } else if (specified_left.is_auto()) { - // If 'left' is 'auto', its used value is minus the value of 'right' (i.e., the boxes move to the left by the value of 'right'). - box_model.offset.right = specified_right.to_px(box); - box_model.offset.left = 0 - box_model.offset.right; - } else if (specified_right.is_auto()) { - // If 'right' is specified as 'auto', its used value is minus the value of 'left'. - box_model.offset.left = specified_left.to_px(box); - box_model.offset.right = 0 - box_model.offset.left; - } else { - // If neither 'left' nor 'right' is 'auto', the position is over-constrained, and one of them has to be ignored. - // If the 'direction' property of the containing block is 'ltr', the value of 'left' wins and 'right' becomes -'left'. - // If 'direction' of the containing block is 'rtl', 'right' wins and 'left' is ignored. - // FIXME: Check direction (assuming 'ltr' for now). - box_model.offset.left = specified_left.to_px(box); - box_model.offset.right = 0 - box_model.offset.left; - } + box_state.content_height = compute_theoretical_height(state, box); } -void BlockFormattingContext::layout_inline_children(BlockContainer& block_container, LayoutMode layout_mode) +void BlockFormattingContext::layout_inline_children(BlockContainer const& block_container, LayoutMode layout_mode) { VERIFY(block_container.children_are_inline()); @@ -386,7 +357,7 @@ void BlockFormattingContext::layout_inline_children(BlockContainer& block_contai context.run(block_container, layout_mode); } -void BlockFormattingContext::layout_block_level_children(BlockContainer& block_container, LayoutMode layout_mode) +void BlockFormattingContext::layout_block_level_children(BlockContainer const& block_container, LayoutMode layout_mode) { VERIFY(!block_container.children_are_inline()); @@ -394,6 +365,8 @@ void BlockFormattingContext::layout_block_level_children(BlockContainer& block_c float content_width = 0; block_container.for_each_child_of_type<Box>([&](Box& child_box) { + auto& box_state = m_state.ensure(child_box); + if (child_box.is_absolutely_positioned()) { m_absolutely_positioned_boxes.append(child_box); return IterationDecision::Continue; @@ -413,7 +386,7 @@ void BlockFormattingContext::layout_block_level_children(BlockContainer& block_c place_block_level_element_in_normal_flow_vertically(child_box, block_container); if (child_box.has_definite_height()) { - compute_height(child_box); + compute_height(child_box, m_state); } OwnPtr<FormattingContext> independent_formatting_context; @@ -425,10 +398,9 @@ void BlockFormattingContext::layout_block_level_children(BlockContainer& block_c layout_block_level_children(verify_cast<BlockContainer>(child_box), layout_mode); } - compute_height(child_box); + compute_height(child_box, m_state); - if (child_box.computed_values().position() == CSS::Position::Relative) - compute_position(child_box); + compute_position(child_box); if (is<ReplacedBox>(child_box) || is<BlockContainer>(child_box)) place_block_level_element_in_normal_flow_horizontally(child_box, block_container); @@ -438,8 +410,8 @@ void BlockFormattingContext::layout_block_level_children(BlockContainer& block_c if (is<ListItemBox>(child_box)) verify_cast<ListItemBox>(child_box).layout_marker(); - content_height = max(content_height, child_box.effective_offset().y() + child_box.content_height() + child_box.box_model().margin_box().bottom); - content_width = max(content_width, child_box.content_width()); + content_height = max(content_height, box_state.offset.y() + box_state.content_height + box_state.margin_box_bottom()); + content_width = max(content_width, box_state.content_width); if (independent_formatting_context) independent_formatting_context->parent_context_did_dimension_child_root_box(); @@ -449,34 +421,36 @@ void BlockFormattingContext::layout_block_level_children(BlockContainer& block_c if (layout_mode != LayoutMode::Default) { auto& width = block_container.computed_values().width(); - if (!width.has_value() || (width->is_length() && width->length().is_auto())) - block_container.set_content_width(content_width); + if (!width.has_value() || (width->is_length() && width->length().is_auto())) { + auto& block_container_state = m_state.ensure(block_container); + block_container_state.content_width = content_width; + } } } -void BlockFormattingContext::compute_vertical_box_model_metrics(Box& child_box, BlockContainer const& containing_block) +void BlockFormattingContext::compute_vertical_box_model_metrics(Box const& box, BlockContainer const& containing_block) { - auto& box_model = child_box.box_model(); - auto const& computed_values = child_box.computed_values(); - auto width_of_containing_block = CSS::Length::make_px(containing_block.content_width()); - - box_model.margin.top = computed_values.margin().top.resolved(child_box, width_of_containing_block).resolved(containing_block).to_px(child_box); - box_model.margin.bottom = computed_values.margin().bottom.resolved(child_box, width_of_containing_block).resolved(containing_block).to_px(child_box); - box_model.border.top = computed_values.border_top().width; - box_model.border.bottom = computed_values.border_bottom().width; - box_model.padding.top = computed_values.padding().top.resolved(child_box, width_of_containing_block).resolved(containing_block).to_px(child_box); - box_model.padding.bottom = computed_values.padding().bottom.resolved(child_box, width_of_containing_block).resolved(containing_block).to_px(child_box); + auto& box_state = m_state.ensure(box); + auto const& computed_values = box.computed_values(); + auto width_of_containing_block = CSS::Length::make_px(m_state.get(containing_block).content_width); + + 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.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); } -void BlockFormattingContext::place_block_level_element_in_normal_flow_vertically(Box& child_box, BlockContainer const& containing_block) +void BlockFormattingContext::place_block_level_element_in_normal_flow_vertically(Box const& child_box, BlockContainer const& containing_block) { - auto& box_model = child_box.box_model(); + auto& box_state = m_state.ensure(child_box); auto const& computed_values = child_box.computed_values(); compute_vertical_box_model_metrics(child_box, containing_block); - float y = box_model.margin_box().top - + box_model.offset.top; + float y = box_state.margin_box_top() + + box_state.offset_top; // NOTE: Empty (0-height) preceding siblings have their margins collapsed with *their* preceding sibling, etc. float collapsed_bottom_margin_of_preceding_siblings = 0; @@ -484,20 +458,22 @@ void BlockFormattingContext::place_block_level_element_in_normal_flow_vertically auto* relevant_sibling = child_box.previous_sibling_of_type<Layout::BlockContainer>(); while (relevant_sibling != nullptr) { if (!relevant_sibling->is_absolutely_positioned() && !relevant_sibling->is_floating()) { - collapsed_bottom_margin_of_preceding_siblings = max(collapsed_bottom_margin_of_preceding_siblings, relevant_sibling->box_model().margin.bottom); - if (relevant_sibling->border_box_height() > 0) + auto const& relevant_sibling_state = m_state.get(*relevant_sibling); + collapsed_bottom_margin_of_preceding_siblings = max(collapsed_bottom_margin_of_preceding_siblings, relevant_sibling_state.margin_bottom); + if (relevant_sibling_state.border_box_height() > 0) break; } relevant_sibling = relevant_sibling->previous_sibling(); } if (relevant_sibling) { - y += relevant_sibling->effective_offset().y() - + relevant_sibling->content_height() - + relevant_sibling->box_model().border_box().bottom; + auto const& relevant_sibling_state = m_state.get(*relevant_sibling); + y += relevant_sibling_state.offset.y() + + relevant_sibling_state.content_height + + relevant_sibling_state.border_box_bottom(); // Collapse top margin with bottom margin of preceding siblings if needed - float my_margin_top = box_model.margin.top; + float my_margin_top = box_state.margin_top; if (my_margin_top < 0 || collapsed_bottom_margin_of_preceding_siblings < 0) { // Negative margins present. @@ -514,8 +490,9 @@ void BlockFormattingContext::place_block_level_element_in_normal_flow_vertically auto clear_floating_boxes = [&](FloatSideData& float_side) { if (!float_side.boxes.is_empty()) { float clearance_y = 0; - for (auto& floating_box : float_side.boxes) { - clearance_y = max(clearance_y, floating_box.effective_offset().y() + floating_box.border_box_height()); + for (auto const& floating_box : float_side.boxes) { + auto const& floating_box_state = m_state.get(floating_box); + clearance_y = max(clearance_y, floating_box_state.offset.y() + floating_box_state.border_box_height()); } y = max(y, clearance_y); float_side.boxes.clear(); @@ -529,21 +506,22 @@ 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); - child_box.set_offset(child_box.effective_offset().x(), y); + box_state.offset = Gfx::FloatPoint { box_state.offset.x(), y }; } -void BlockFormattingContext::place_block_level_element_in_normal_flow_horizontally(Box& child_box, BlockContainer const& containing_block) +void BlockFormattingContext::place_block_level_element_in_normal_flow_horizontally(Box const& child_box, BlockContainer const& containing_block) { - auto& box_model = child_box.box_model(); + auto& box_state = m_state.ensure(child_box); + auto const& containing_block_state = m_state.get(containing_block); float x = 0; if (containing_block.computed_values().text_align() == CSS::TextAlign::LibwebCenter) { - x = (containing_block.content_width() / 2) - child_box.content_width() / 2; + x = (containing_block_state.content_width / 2) - box_state.content_width / 2; } else { - x = box_model.margin_box().left + box_model.offset.left; + x = box_state.margin_box_left() + box_state.offset_left; } - child_box.set_offset(x, child_box.effective_offset().y()); + box_state.offset = Gfx::FloatPoint { x, box_state.offset.y() }; } void BlockFormattingContext::layout_initial_containing_block(LayoutMode layout_mode) @@ -558,83 +536,92 @@ void BlockFormattingContext::layout_initial_containing_block(LayoutMode layout_m // Compute scrollable overflow. float bottom_edge = 0; float right_edge = 0; - icb.for_each_in_subtree_of_type<Box>([&](Box& child) { - auto child_rect = child.absolute_border_box_rect(); + icb.for_each_in_subtree_of_type<Box>([&](Box const& child) { + auto const& child_state = m_state.get(child); + auto child_rect = absolute_content_rect(child, m_state); + child_rect.inflate(child_state.border_box_top(), child_state.border_box_right(), child_state.border_box_bottom(), child_state.border_box_left()); bottom_edge = max(bottom_edge, child_rect.bottom()); right_edge = max(right_edge, child_rect.right()); return IterationDecision::Continue; }); if (bottom_edge >= viewport_rect.height() || right_edge >= viewport_rect.width()) { - auto& overflow_data = icb.ensure_overflow_data(); + // FIXME: Move overflow data to FormattingState! + auto& overflow_data = const_cast<InitialContainingBlock&>(icb).ensure_overflow_data(); overflow_data.scrollable_overflow_rect = viewport_rect.to_type<float>(); // NOTE: The edges are *within* the rectangle, so we add 1 to get the width and height. overflow_data.scrollable_overflow_rect.set_size(right_edge + 1, bottom_edge + 1); } else { - icb.clear_overflow_data(); + // FIXME: Move overflow data to FormattingState! + const_cast<InitialContainingBlock&>(icb).clear_overflow_data(); } } -void BlockFormattingContext::layout_floating_child(Box& box, BlockContainer const& containing_block) +void BlockFormattingContext::layout_floating_child(Box const& box, BlockContainer const& containing_block) { VERIFY(box.is_floating()); + auto& box_state = m_state.ensure(box); + auto containing_block_content_width = m_state.get(containing_block).content_width; + compute_width(box); (void)layout_inside(box, LayoutMode::Default); - compute_height(box); + compute_height(box, m_state); // First we place the box normally (to get the right y coordinate.) place_block_level_element_in_normal_flow_vertically(box, containing_block); place_block_level_element_in_normal_flow_horizontally(box, containing_block); auto float_box = [&](FloatSide side, FloatSideData& side_data) { - auto first_edge = [&](PixelBox const& thing) { return side == FloatSide::Left ? thing.left : thing.right; }; - auto second_edge = [&](PixelBox const& thing) { return side == FloatSide::Right ? thing.left : thing.right; }; + auto first_edge = [&](FormattingState::NodeState const& thing) { return side == FloatSide::Left ? thing.margin_left : thing.margin_right; }; + auto second_edge = [&](FormattingState::NodeState const& thing) { return side == FloatSide::Right ? thing.margin_left : thing.margin_right; }; + auto first_edge_of_margin_box = [&](FormattingState::NodeState const& thing) { return side == FloatSide::Left ? thing.margin_box_left() : thing.margin_box_right(); }; // Then we float it to the left or right. - float x = box.effective_offset().x(); + float x = box_state.offset.x(); - auto box_in_root_rect = box.margin_box_rect_in_ancestor_coordinate_space(root()); + auto box_in_root_rect = margin_box_rect_in_ancestor_coordinate_space(box, root(), m_state); float y_in_root = box_in_root_rect.y(); - float y = box.effective_offset().y(); + float y = box_state.offset.y(); if (side_data.boxes.is_empty()) { // This is the first floating box on this side. Go all the way to the edge. if (side == FloatSide::Left) - x = box.box_model().margin_box().left; + x = box_state.margin_box_left(); else - x = containing_block.content_width() - box.box_model().margin_box().right - box.content_width(); + x = containing_block_content_width - box_state.margin_box_right() - box_state.content_width; side_data.y_offset = 0; } else { auto& previous_box = side_data.boxes.last(); - auto previous_rect = previous_box.margin_box_rect_in_ancestor_coordinate_space(root()); + auto const& previous_box_state = m_state.get(previous_box); + auto previous_rect = margin_box_rect_in_ancestor_coordinate_space(previous_box, root(), m_state); auto margin_collapsed_with_previous = max( - second_edge(previous_box.box_model().margin), - first_edge(box.box_model().margin)); + second_edge(previous_box_state), + first_edge(box_state)); float wanted_x = 0; bool fits_on_line = false; if (side == FloatSide::Left) { - auto previous_right_border_edge = previous_box.effective_offset().x() - + previous_box.content_width() - + previous_box.box_model().padding.right - + previous_box.box_model().border.right + auto previous_right_border_edge = previous_box_state.offset.x() + + previous_box_state.content_width + + previous_box_state.padding_right + + previous_box_state.border_right + margin_collapsed_with_previous; - wanted_x = previous_right_border_edge + box.box_model().border.left + box.box_model().padding.left; - fits_on_line = (wanted_x + box.content_width() + box.box_model().padding.right + box.box_model().border.right + box.box_model().margin.right) <= containing_block.content_width(); + wanted_x = previous_right_border_edge + box_state.border_left + box_state.padding_left; + fits_on_line = (wanted_x + box_state.content_width + box_state.padding_right + box_state.border_right + box_state.margin_right) <= containing_block_content_width; } else { - auto previous_left_border_edge = previous_box.effective_offset().x() - - previous_box.content_width() - - previous_box.box_model().padding.left - - previous_box.box_model().border.left + auto previous_left_border_edge = previous_box_state.offset.x() + - previous_box_state.content_width + - previous_box_state.padding_left + - previous_box_state.border_left - margin_collapsed_with_previous; - wanted_x = previous_left_border_edge - box.box_model().border.right - box.box_model().padding.right - box.content_width(); - fits_on_line = (wanted_x - box.box_model().padding.left - box.box_model().border.left - box.box_model().margin.left) >= 0; + wanted_x = previous_left_border_edge - box_state.border_right - box_state.padding_right - box_state.content_width; + fits_on_line = (wanted_x - box_state.padding_left - box_state.border_left - box_state.margin_left) >= 0; } if (fits_on_line) { @@ -643,17 +630,19 @@ void BlockFormattingContext::layout_floating_child(Box& box, BlockContainer cons x = wanted_x; } else { // This box does not touch another floating box, go all the way to the first edge. - x = first_edge(box.box_model().margin_box()); + x = first_edge_of_margin_box(box_state); // Also, forget all previous boxes floated to this side while since they're no longer relevant. side_data.boxes.clear(); } } else { // We ran out of horizontal space on this "float line", and need to break. - x = first_edge(box.box_model().margin_box()); + x = first_edge_of_margin_box(box_state); float lowest_border_edge = 0; - for (auto const& box : side_data.boxes) - lowest_border_edge = max(lowest_border_edge, box.border_box_height()); + for (auto const& box : side_data.boxes) { + auto const& box_state = m_state.get(box); + lowest_border_edge = max(lowest_border_edge, box_state.border_box_height()); + } side_data.y_offset += lowest_border_edge; @@ -664,7 +653,7 @@ void BlockFormattingContext::layout_floating_child(Box& box, BlockContainer cons y += side_data.y_offset; side_data.boxes.append(box); - box.set_offset(x, y); + box_state.offset = Gfx::FloatPoint { x, y }; }; // Next, float to the left and/or right diff --git a/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.h b/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.h index 05b584fd0b..ef8371969b 100644 --- a/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.h +++ b/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.h @@ -16,51 +16,47 @@ namespace Web::Layout { // https://www.w3.org/TR/css-display/#block-formatting-context class BlockFormattingContext : public FormattingContext { public: - explicit BlockFormattingContext(FormattingState&, BlockContainer&, FormattingContext* parent); + explicit BlockFormattingContext(FormattingState&, BlockContainer const&, FormattingContext* parent); ~BlockFormattingContext(); - virtual void run(Box&, LayoutMode) override; + virtual void run(Box const&, LayoutMode) override; bool is_initial() const; auto const& left_side_floats() const { return m_left_floats; } auto const& right_side_floats() const { return m_right_floats; } - static float compute_theoretical_height(Box const&); - void compute_width(Box&); + static float compute_theoretical_height(FormattingState const&, Box const&); + void compute_width(Box const&); // https://www.w3.org/TR/css-display/#block-formatting-context-root - BlockContainer& root() { return static_cast<BlockContainer&>(context_box()); } 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&); + static void compute_height(Box const&, FormattingState&); - void add_absolutely_positioned_box(Box& box) { m_absolutely_positioned_boxes.append(box); } - -protected: - void compute_position(Box&); + void add_absolutely_positioned_box(Box const& box) { m_absolutely_positioned_boxes.append(box); } private: virtual bool is_block_formatting_context() const final { return true; } - void compute_width_for_floating_box(Box&); + void compute_width_for_floating_box(Box const&); - void compute_width_for_block_level_replaced_element_in_normal_flow(ReplacedBox&); + void compute_width_for_block_level_replaced_element_in_normal_flow(ReplacedBox const&); void layout_initial_containing_block(LayoutMode); - void layout_block_level_children(BlockContainer&, LayoutMode); - void layout_inline_children(BlockContainer&, LayoutMode); + void layout_block_level_children(BlockContainer const&, LayoutMode); + void layout_inline_children(BlockContainer const&, LayoutMode); - void compute_vertical_box_model_metrics(Box& child_box, BlockContainer const& containing_block); - void place_block_level_element_in_normal_flow_horizontally(Box& child_box, BlockContainer const&); - void place_block_level_element_in_normal_flow_vertically(Box& child_box, BlockContainer const&); + void compute_vertical_box_model_metrics(Box const& box, BlockContainer const& containing_block); + 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&); - void layout_floating_child(Box& child, BlockContainer const& containing_block); + void layout_floating_child(Box const& child, BlockContainer const& containing_block); - void apply_transformations_to_children(Box&); + void apply_transformations_to_children(Box const&); enum class FloatSide { Left, @@ -68,14 +64,14 @@ private: }; struct FloatSideData { - Vector<Box&> boxes; + Vector<Box const&> boxes; float y_offset { 0 }; }; FloatSideData m_left_floats; FloatSideData m_right_floats; - Vector<Box&> m_absolutely_positioned_boxes; + Vector<Box const&> m_absolutely_positioned_boxes; bool m_was_notified_after_parent_dimensioned_my_root_box { false }; }; diff --git a/Userland/Libraries/LibWeb/Layout/Box.h b/Userland/Libraries/LibWeb/Layout/Box.h index 8db420581c..a46fff8bbd 100644 --- a/Userland/Libraries/LibWeb/Layout/Box.h +++ b/Userland/Libraries/LibWeb/Layout/Box.h @@ -71,22 +71,6 @@ public: return content_height() + border_box.top + border_box.bottom; } - Gfx::FloatRect content_box_as_relative_rect() const - { - return { m_offset, m_content_size }; - } - - Gfx::FloatRect margin_box_as_relative_rect() const - { - auto rect = content_box_as_relative_rect(); - auto margin_box = box_model().margin_box(); - rect.set_x(rect.x() - margin_box.left); - rect.set_width(rect.width() + margin_box.left + margin_box.right); - rect.set_y(rect.y() - margin_box.top); - rect.set_height(rect.height() + margin_box.top + margin_box.bottom); - return rect; - } - float absolute_x() const { return absolute_rect().x(); } float absolute_y() const { return absolute_rect().y(); } Gfx::FloatPoint absolute_position() const { return absolute_rect().location(); } @@ -141,20 +125,6 @@ public: virtual void before_children_paint(PaintContext&, PaintPhase) override; virtual void after_children_paint(PaintContext&, PaintPhase) override; - Gfx::FloatRect margin_box_rect_in_ancestor_coordinate_space(Box const& ancestor_box) const - { - auto rect = margin_box_as_relative_rect(); - for (auto const* current = parent(); current; current = current->parent()) { - if (current == &ancestor_box) - break; - if (is<Box>(*current)) { - auto offset = static_cast<Box const&>(*current).effective_offset(); - rect.translate_by(offset); - } - } - return rect; - } - protected: Box(DOM::Document& document, DOM::Node* node, NonnullRefPtr<CSS::StyleProperties> style) : NodeWithStyleAndBoxModelMetrics(document, node, move(style)) diff --git a/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.cpp index 72a018baa3..9b5316ab44 100644 --- a/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.cpp @@ -17,11 +17,11 @@ namespace Web::Layout { -static float get_pixel_size(Box const& box, Optional<CSS::LengthPercentage> const& length_percentage) +static float get_pixel_size(FormattingState const& state, Box const& box, Optional<CSS::LengthPercentage> const& length_percentage) { if (!length_percentage.has_value()) return 0; - auto inner_main_size = CSS::Length::make_px(box.containing_block()->content_width()); + auto inner_main_size = CSS::Length::make_px(state.get(*box.containing_block()).content_width); return length_percentage->resolved(box, inner_main_size).to_px(box); } @@ -32,8 +32,9 @@ static bool is_undefined_or_auto(Optional<CSS::LengthPercentage> const& length_p return length_percentage->is_length() && length_percentage->length().is_auto(); } -FlexFormattingContext::FlexFormattingContext(FormattingState& state, Box& flex_container, FormattingContext* parent) +FlexFormattingContext::FlexFormattingContext(FormattingState& state, Box const& flex_container, FormattingContext* parent) : FormattingContext(Type::Flex, state, flex_container, parent) + , m_flex_container_state(m_state.ensure(flex_container)) , m_flex_direction(flex_container.computed_values().flex_direction()) { } @@ -42,7 +43,7 @@ FlexFormattingContext::~FlexFormattingContext() { } -void FlexFormattingContext::run(Box& run_box, LayoutMode) +void FlexFormattingContext::run(Box const& run_box, LayoutMode) { VERIFY(&run_box == &flex_container()); @@ -116,7 +117,7 @@ void FlexFormattingContext::run(Box& run_box, LayoutMode) void FlexFormattingContext::populate_specified_margins(FlexItem& item, CSS::FlexDirection flex_direction) const { - auto width_of_containing_block = item.box.containing_block()->content_width(); + auto width_of_containing_block = m_state.get(*item.box.containing_block()).content_width; auto width_of_containing_block_as_length = CSS::Length::make_px(width_of_containing_block); // FIXME: This should also take reverse-ness into account if (flex_direction == CSS::FlexDirection::Row || flex_direction == CSS::FlexDirection::RowReverse) { @@ -135,6 +136,7 @@ void FlexFormattingContext::populate_specified_margins(FlexItem& item, CSS::Flex // https://www.w3.org/TR/css-flexbox-1/#flex-items void FlexFormattingContext::generate_anonymous_flex_items() { + auto& containing_block_state = m_state.get(*flex_container().containing_block()); // More like, sift through the already generated items. // After this step no items are to be added or removed from flex_items! // It holds every item we need to consider and there should be nothing in the following @@ -142,29 +144,29 @@ void FlexFormattingContext::generate_anonymous_flex_items() // This is particularly important since we take references to the items stored in flex_items // later, whose addresses won't be stable if we added or removed any items. if (!flex_container().has_definite_width()) { - flex_container().set_content_width(flex_container().containing_block()->content_width()); + m_flex_container_state.content_width = containing_block_state.content_width; } else { - auto container_width = flex_container().containing_block()->content_width(); + auto container_width = containing_block_state.content_width; auto& maybe_width = flex_container().computed_values().width(); if (maybe_width.has_value()) { auto width = maybe_width->resolved(flex_container(), CSS::Length::make_px(container_width)).to_px(flex_container()); - flex_container().set_content_width(width); + m_flex_container_state.content_width = width; } else { - flex_container().set_content_width(0); + m_flex_container_state.content_width = 0; } } if (!flex_container().has_definite_height()) { - flex_container().set_content_height(flex_container().containing_block()->content_height()); + m_flex_container_state.content_height = containing_block_state.content_height; } else { - auto container_height = flex_container().containing_block()->content_height(); + auto container_height = containing_block_state.content_height; auto& maybe_height = flex_container().computed_values().height(); if (maybe_height.has_value()) { auto height = maybe_height->resolved(flex_container(), CSS::Length::make_px(container_height)).to_px(flex_container()); - flex_container().set_content_height(height); + m_flex_container_state.content_height = height; } else { - flex_container().set_content_height(0); + m_flex_container_state.content_height = 0; } } @@ -203,12 +205,14 @@ bool FlexFormattingContext::has_definite_main_size(Box const& box) const float FlexFormattingContext::specified_main_size(Box const& box) const { - return is_row_layout() ? box.content_width() : box.content_height(); + auto const& box_state = m_state.get(box); + return is_row_layout() ? box_state.content_width : box_state.content_height; } float FlexFormattingContext::specified_cross_size(Box const& box) const { - return is_row_layout() ? box.content_height() : box.content_width(); + auto const& box_state = m_state.get(box); + return is_row_layout() ? box_state.content_height : box_state.content_width; } bool FlexFormattingContext::has_main_min_size(Box const& box) const @@ -263,15 +267,15 @@ float FlexFormattingContext::specified_main_size_of_child_box(Box const& child_b float FlexFormattingContext::specified_main_min_size(Box const& box) const { return is_row_layout() - ? get_pixel_size(box, box.computed_values().min_width()) - : get_pixel_size(box, box.computed_values().min_height()); + ? get_pixel_size(m_state, box, box.computed_values().min_width()) + : get_pixel_size(m_state, box, box.computed_values().min_height()); } float FlexFormattingContext::specified_cross_min_size(Box const& box) const { return is_row_layout() - ? get_pixel_size(box, box.computed_values().min_height()) - : get_pixel_size(box, box.computed_values().min_width()); + ? get_pixel_size(m_state, box, box.computed_values().min_height()) + : get_pixel_size(m_state, box, box.computed_values().min_width()); } bool FlexFormattingContext::has_main_max_size(Box const& box) const @@ -291,20 +295,21 @@ bool FlexFormattingContext::has_cross_max_size(Box const& box) const float FlexFormattingContext::specified_main_max_size(Box const& box) const { return is_row_layout() - ? get_pixel_size(box, box.computed_values().max_width()) - : get_pixel_size(box, box.computed_values().max_height()); + ? get_pixel_size(m_state, box, box.computed_values().max_width()) + : get_pixel_size(m_state, box, box.computed_values().max_height()); } float FlexFormattingContext::specified_cross_max_size(Box const& box) const { return is_row_layout() - ? get_pixel_size(box, box.computed_values().max_height()) - : get_pixel_size(box, box.computed_values().max_width()); + ? get_pixel_size(m_state, box, box.computed_values().max_height()) + : get_pixel_size(m_state, box, box.computed_values().max_width()); } float FlexFormattingContext::calculated_main_size(Box const& box) const { - return is_row_layout() ? box.content_width() : box.content_height(); + 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 @@ -327,60 +332,58 @@ bool FlexFormattingContext::is_main_axis_margin_second_auto(Box const& box) cons return box.computed_values().margin().bottom.is_length() && box.computed_values().margin().bottom.length().is_auto(); } -void FlexFormattingContext::set_main_size(Box& box, float size) +void FlexFormattingContext::set_main_size(Box const& box, float size) { if (is_row_layout()) - box.set_content_width(size); + m_state.ensure(box).content_width = size; else - box.set_content_height(size); + m_state.ensure(box).content_height = size; } -void FlexFormattingContext::set_cross_size(Box& box, float size) +void FlexFormattingContext::set_cross_size(Box const& box, float size) { if (is_row_layout()) - box.set_content_height(size); + m_state.ensure(box).content_height = size; else - box.set_content_width(size); + m_state.ensure(box).content_width = size; } -void FlexFormattingContext::set_offset(Box& box, float main_offset, float cross_offset) +void FlexFormattingContext::set_offset(Box const& box, float main_offset, float cross_offset) { if (is_row_layout()) - box.set_offset(main_offset, cross_offset); + m_state.ensure(box).offset = Gfx::FloatPoint { main_offset, cross_offset }; else - box.set_offset(cross_offset, main_offset); + m_state.ensure(box).offset = Gfx::FloatPoint { cross_offset, main_offset }; } -void FlexFormattingContext::set_main_axis_first_margin(Box& box, float margin) +void FlexFormattingContext::set_main_axis_first_margin(Box const& box, float margin) { if (is_row_layout()) - box.box_model().margin.left = margin; + m_state.ensure(box).margin_left = margin; else - box.box_model().margin.top = margin; + m_state.ensure(box).margin_top = margin; } -void FlexFormattingContext::set_main_axis_second_margin(Box& box, float margin) +void FlexFormattingContext::set_main_axis_second_margin(Box const& box, float margin) { if (is_row_layout()) - box.box_model().margin.right = margin; + m_state.ensure(box).margin_right = margin; else - box.box_model().margin.bottom = margin; + m_state.ensure(box).margin_bottom = margin; } float FlexFormattingContext::sum_of_margin_padding_border_in_main_axis(Box const& box) const { - auto& margin = box.box_model().margin; - auto& padding = box.box_model().padding; - auto& border = box.box_model().border; + auto const& box_state = m_state.get(box); if (is_row_layout()) { - return margin.left + margin.right - + padding.left + padding.right - + border.left + border.right; + 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; } else { - return margin.top + margin.bottom - + padding.top + padding.bottom - + border.top + border.bottom; + 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; } } @@ -388,14 +391,15 @@ float FlexFormattingContext::sum_of_margin_padding_border_in_main_axis(Box const FlexFormattingContext::AvailableSpace FlexFormattingContext::determine_available_main_and_cross_space(bool& main_size_is_infinite, bool& main_is_constrained, bool& cross_is_constrained, float& main_min_size, float& main_max_size, float& cross_min_size, float& cross_max_size) const { auto containing_block_effective_main_size = [&](Box const& box) { + auto& containing_block = *box.containing_block(); if (is_row_layout()) { - if (box.containing_block()->has_definite_width()) - return box.containing_block()->content_width(); + if (containing_block.has_definite_width()) + return m_state.get(containing_block).content_width; main_size_is_infinite = true; return NumericLimits<float>::max(); } else { - if (box.containing_block()->has_definite_height()) - return box.containing_block()->content_height(); + if (containing_block.has_definite_height()) + return m_state.get(containing_block).content_height; main_size_is_infinite = true; return NumericLimits<float>::max(); } @@ -456,7 +460,7 @@ FlexFormattingContext::AvailableSpace FlexFormattingContext::determine_available return AvailableSpace { .main = main_available_space, .cross = cross_available_space }; } -float FlexFormattingContext::layout_for_maximum_main_size(Box& box) +float FlexFormattingContext::layout_for_maximum_main_size(Box const& box) { bool main_constrained = false; if (is_row_layout()) { @@ -477,17 +481,17 @@ float FlexFormattingContext::layout_for_maximum_main_size(Box& box) if (is_row_layout()) { ifc.run(box, LayoutMode::OnlyRequiredLineBreaks); - return box.content_width(); + return m_state.get(box).content_width; } else { ifc.run(box, LayoutMode::AllPossibleLineBreaks); - return box.content_height(); + return m_state.get(box).content_height; } } if (is_row_layout()) { (void)layout_inside(box, LayoutMode::OnlyRequiredLineBreaks); - return box.content_width(); + return m_state.get(box).content_width; } else { - return BlockFormattingContext::compute_theoretical_height(box); + return BlockFormattingContext::compute_theoretical_height(m_state, box); } } @@ -501,7 +505,7 @@ void FlexFormattingContext::determine_flex_base_size_and_hypothetical_main_size( // A. If the item has a definite used flex basis, that’s the flex base size. if (used_flex_basis.is_definite()) { - auto specified_base_size = get_pixel_size(child_box, used_flex_basis.length_percentage.value()); + auto specified_base_size = get_pixel_size(m_state, child_box, used_flex_basis.length_percentage.value()); if (specified_base_size == 0) return calculated_main_size(flex_item.box); return specified_base_size; @@ -806,7 +810,7 @@ void FlexFormattingContext::resolve_flexible_lengths(float const main_available_ } // https://www.w3.org/TR/css-flexbox-1/#algo-cross-item -float FlexFormattingContext::determine_hypothetical_cross_size_of_item(Box& box) +float FlexFormattingContext::determine_hypothetical_cross_size_of_item(Box const& box) { bool cross_constrained = false; if (is_row_layout()) { @@ -826,14 +830,15 @@ float FlexFormattingContext::determine_hypothetical_cross_size_of_item(Box& box) InlineFormattingContext ifc(m_state, block_container, bfc); ifc.run(box, LayoutMode::OnlyRequiredLineBreaks); - return is_row_layout() ? box.content_height() : box.content_width(); + auto const& box_state = m_state.get(box); + return is_row_layout() ? box_state.content_height : box_state.content_width; } if (is_row_layout()) - return BlockFormattingContext::compute_theoretical_height(box); + return BlockFormattingContext::compute_theoretical_height(m_state, box); BlockFormattingContext context(m_state, verify_cast<BlockContainer>(box), this); context.compute_width(box); - return box.content_width(); + return m_state.get(box).content_width; } // https://www.w3.org/TR/css-flexbox-1/#algo-cross-line diff --git a/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.h b/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.h index 111a146ccc..7e5e994eb4 100644 --- a/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.h +++ b/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.h @@ -13,14 +13,13 @@ namespace Web::Layout { class FlexFormattingContext final : public FormattingContext { public: - FlexFormattingContext(FormattingState&, Box& flex_container, FormattingContext* parent); + FlexFormattingContext(FormattingState&, Box const& flex_container, FormattingContext* parent); ~FlexFormattingContext(); virtual bool inhibits_floating() const override { return true; } - virtual void run(Box&, LayoutMode) override; + virtual void run(Box const&, LayoutMode) override; - Box& flex_container() { return context_box(); } Box const& flex_container() const { return context_box(); } private: @@ -76,11 +75,11 @@ private: bool has_cross_max_size(Box const&) const; float sum_of_margin_padding_border_in_main_axis(Box const&) const; - void set_main_size(Box&, float size); - void set_cross_size(Box&, float size); - void set_offset(Box&, float main_offset, float cross_offset); - void set_main_axis_first_margin(Box&, float margin); - void set_main_axis_second_margin(Box&, float margin); + void set_main_size(Box const&, float size); + void set_cross_size(Box const&, float size); + void set_offset(Box const&, float main_offset, float cross_offset); + void set_main_axis_first_margin(Box const&, float margin); + void set_main_axis_second_margin(Box const&, float margin); void generate_anonymous_flex_items(); @@ -90,7 +89,7 @@ private: }; AvailableSpace determine_available_main_and_cross_space(bool& main_size_is_infinite, bool& main_is_constrained, bool& cross_is_constrained, float& main_min_size, float& main_max_size, float& cross_min_size, float& cross_max_size) const; - float layout_for_maximum_main_size(Box&); + float layout_for_maximum_main_size(Box const&); void determine_flex_base_size_and_hypothetical_main_size(FlexItem&); void determine_main_size_of_flex_container(bool main_is_constrained, bool main_size_is_infinite, float& main_available_size, float main_min_size, float main_max_size); @@ -99,7 +98,7 @@ private: void resolve_flexible_lengths(float main_available_size); - float determine_hypothetical_cross_size_of_item(Box&); + float determine_hypothetical_cross_size_of_item(Box const&); void calculate_cross_size_of_each_flex_line(float cross_min_size, float cross_max_size); @@ -118,6 +117,8 @@ private: void populate_specified_margins(FlexItem&, CSS::FlexDirection) const; + FormattingState::NodeState& m_flex_container_state; + Vector<FlexLine> m_flex_lines; Vector<FlexItem> m_flex_items; CSS::FlexDirection m_flex_direction {}; diff --git a/Userland/Libraries/LibWeb/Layout/FormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/FormattingContext.cpp index 68fc5c4a4c..4a2cfd84be 100644 --- a/Userland/Libraries/LibWeb/Layout/FormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/FormattingContext.cpp @@ -19,7 +19,7 @@ namespace Web::Layout { -FormattingContext::FormattingContext(Type type, FormattingState& state, Box& context_box, FormattingContext* parent) +FormattingContext::FormattingContext(Type type, FormattingState& state, Box const& context_box, FormattingContext* parent) : m_type(type) , m_parent(parent) , m_context_box(context_box) @@ -75,7 +75,7 @@ bool FormattingContext::creates_block_formatting_context(const Box& box) return false; } -OwnPtr<FormattingContext> FormattingContext::create_independent_formatting_context_if_needed(Box& child_box) +OwnPtr<FormattingContext> FormattingContext::create_independent_formatting_context_if_needed(Box const& child_box) { if (!child_box.can_have_children()) return {}; @@ -105,7 +105,7 @@ OwnPtr<FormattingContext> FormattingContext::create_independent_formatting_conte return {}; } -OwnPtr<FormattingContext> FormattingContext::layout_inside(Box& child_box, LayoutMode layout_mode) +OwnPtr<FormattingContext> FormattingContext::layout_inside(Box const& child_box, LayoutMode layout_mode) { if (!child_box.can_have_children()) return {}; @@ -119,44 +119,45 @@ OwnPtr<FormattingContext> FormattingContext::layout_inside(Box& child_box, Layou return independent_formatting_context; } -static float greatest_child_width(Box const& box) +static float greatest_child_width(FormattingState const& state, Box const& box) { float max_width = 0; if (box.children_are_inline()) { - for (auto& child : verify_cast<BlockContainer>(box).line_boxes()) { + for (auto& child : state.get(verify_cast<BlockContainer>(box)).line_boxes) { max_width = max(max_width, child.width()); } } else { box.for_each_child_of_type<Box>([&](auto& child) { - max_width = max(max_width, child.border_box_width()); + max_width = max(max_width, state.get(child).border_box_width()); }); } return max_width; } -FormattingContext::ShrinkToFitResult FormattingContext::calculate_shrink_to_fit_widths(Box& box) +FormattingContext::ShrinkToFitResult FormattingContext::calculate_shrink_to_fit_widths(Box const& box) { // Calculate the preferred width by formatting the content without breaking lines // other than where explicit line breaks occur. (void)layout_inside(box, LayoutMode::OnlyRequiredLineBreaks); - float preferred_width = greatest_child_width(box); + float preferred_width = greatest_child_width(m_state, box); // Also calculate the preferred minimum width, e.g., by trying all possible line breaks. // CSS 2.2 does not define the exact algorithm. (void)layout_inside(box, LayoutMode::AllPossibleLineBreaks); - float preferred_minimum_width = greatest_child_width(box); + float preferred_minimum_width = greatest_child_width(m_state, box); return { preferred_width, preferred_minimum_width }; } -static Gfx::FloatSize solve_replaced_size_constraint(float w, float h, const ReplacedBox& box) +static Gfx::FloatSize solve_replaced_size_constraint(FormattingState const& state, float w, float h, ReplacedBox const& box) { // 10.4 Minimum and maximum widths: 'min-width' and 'max-width' - auto& containing_block = *box.containing_block(); - auto width_of_containing_block = CSS::Length::make_px(containing_block.content_width()); - auto height_of_containing_block = CSS::Length::make_px(containing_block.content_height()); + auto const& containing_block = *box.containing_block(); + auto const& containing_block_state = state.get(containing_block); + auto width_of_containing_block = CSS::Length::make_px(containing_block_state.content_width); + auto height_of_containing_block = CSS::Length::make_px(containing_block_state.content_height); auto specified_min_width = box.computed_values().min_width().has_value() ? box.computed_values().min_width()->resolved(box, width_of_containing_block).to_px(box) : 0; auto specified_max_width = box.computed_values().max_width().has_value() ? box.computed_values().max_width()->resolved(box, width_of_containing_block).to_px(box) : w; @@ -191,7 +192,7 @@ static Gfx::FloatSize solve_replaced_size_constraint(float w, float h, const Rep return { w, h }; } -float FormattingContext::compute_auto_height_for_block_level_element(Box const& box, ConsiderFloats consider_floats) +float FormattingContext::compute_auto_height_for_block_level_element(FormattingState const& state, Box const& box, ConsiderFloats consider_floats) { Optional<float> top; Optional<float> bottom; @@ -199,10 +200,11 @@ float FormattingContext::compute_auto_height_for_block_level_element(Box const& if (box.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& block_container = verify_cast<BlockContainer>(box); + auto const& block_container = verify_cast<BlockContainer>(box); + auto const& line_boxes = state.get(block_container).line_boxes; top = 0; - if (!block_container.line_boxes().is_empty()) { - for (auto& fragment : block_container.line_boxes().last().fragments()) { + if (!line_boxes.is_empty()) { + for (auto& fragment : line_boxes.last().fragments()) { if (!bottom.has_value() || (fragment.offset().y() + fragment.height()) > bottom.value()) bottom = fragment.offset().y() + fragment.height(); } @@ -217,8 +219,10 @@ float FormattingContext::compute_auto_height_for_block_level_element(Box const& if ((box.computed_values().overflow_y() == CSS::Overflow::Visible) && child_box.is_floating()) return IterationDecision::Continue; - float child_box_top = child_box.effective_offset().y() - child_box.box_model().margin_box().top; - float child_box_bottom = child_box.effective_offset().y() + child_box.content_height() + child_box.box_model().margin_box().bottom; + auto const& child_box_state = 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(); if (!top.has_value() || child_box_top < top.value()) top = child_box_top; @@ -236,7 +240,9 @@ float FormattingContext::compute_auto_height_for_block_level_element(Box const& if (!child_box.is_floating()) return IterationDecision::Continue; - float child_box_bottom = child_box.effective_offset().y() + child_box.content_height(); + auto const& child_box_state = state.get(child_box); + + float child_box_bottom = child_box_state.offset.y() + child_box_state.content_height; if (!bottom.has_value() || child_box_bottom > bottom.value()) bottom = child_box_bottom; @@ -249,10 +255,10 @@ float FormattingContext::compute_auto_height_for_block_level_element(Box const& } // 10.3.2 Inline, replaced elements, https://www.w3.org/TR/CSS22/visudet.html#inline-replaced-width -float FormattingContext::tentative_width_for_replaced_element(ReplacedBox const& box, CSS::Length const& computed_width) +float FormattingContext::tentative_width_for_replaced_element(FormattingState const& state, ReplacedBox const& box, CSS::Length const& computed_width) { - auto& containing_block = *box.containing_block(); - auto height_of_containing_block = CSS::Length::make_px(containing_block.content_height()); + auto const& containing_block = *box.containing_block(); + auto height_of_containing_block = CSS::Length::make_px(state.get(containing_block).content_height); auto computed_height = box.computed_values().height().has_value() ? box.computed_values().height()->resolved(box, height_of_containing_block).resolved(box) : CSS::Length::make_auto(); float used_width = computed_width.to_px(box); @@ -270,7 +276,7 @@ float FormattingContext::tentative_width_for_replaced_element(ReplacedBox 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() && box.has_intrinsic_aspect_ratio())) { - return compute_height_for_replaced_element(box) * box.intrinsic_aspect_ratio().value(); + return compute_height_for_replaced_element(state, box) * 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, @@ -290,7 +296,7 @@ float FormattingContext::tentative_width_for_replaced_element(ReplacedBox const& return used_width; } -void FormattingContext::compute_width_for_absolutely_positioned_element(Box& box) +void FormattingContext::compute_width_for_absolutely_positioned_element(Box const& box) { if (is<ReplacedBox>(box)) compute_width_for_absolutely_positioned_replaced_element(verify_cast<ReplacedBox>(box)); @@ -298,7 +304,7 @@ void FormattingContext::compute_width_for_absolutely_positioned_element(Box& box compute_width_for_absolutely_positioned_non_replaced_element(box); } -void FormattingContext::compute_height_for_absolutely_positioned_element(Box& box) +void FormattingContext::compute_height_for_absolutely_positioned_element(Box const& box) { if (is<ReplacedBox>(box)) compute_height_for_absolutely_positioned_replaced_element(verify_cast<ReplacedBox>(box)); @@ -306,14 +312,14 @@ void FormattingContext::compute_height_for_absolutely_positioned_element(Box& bo compute_height_for_absolutely_positioned_non_replaced_element(box); } -float FormattingContext::compute_width_for_replaced_element(const ReplacedBox& box) +float FormattingContext::compute_width_for_replaced_element(FormattingState const& state, ReplacedBox const& box) { // 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& containing_block = *box.containing_block(); - auto width_of_containing_block = CSS::Length::make_px(containing_block.content_width()); + auto const& containing_block = *box.containing_block(); + auto width_of_containing_block = CSS::Length::make_px(state.get(containing_block).content_width); auto margin_left = box.computed_values().margin().left.resolved(box, width_of_containing_block).resolved(box); auto margin_right = box.computed_values().margin().right.resolved(box, width_of_containing_block).resolved(box); @@ -327,14 +333,14 @@ float FormattingContext::compute_width_for_replaced_element(const ReplacedBox& b auto specified_width = box.computed_values().width().has_value() ? box.computed_values().width()->resolved(box, width_of_containing_block).resolved(box) : CSS::Length::make_auto(); // 1. The tentative used width is calculated (without 'min-width' and 'max-width') - auto used_width = tentative_width_for_replaced_element(box, specified_width); + auto used_width = tentative_width_for_replaced_element(state, box, specified_width); // 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 specified_max_width = box.computed_values().max_width().has_value() ? box.computed_values().max_width()->resolved(box, width_of_containing_block).resolved(box) : CSS::Length::make_auto(); if (!specified_max_width.is_auto()) { if (used_width > specified_max_width.to_px(box)) { - used_width = tentative_width_for_replaced_element(box, specified_max_width); + used_width = tentative_width_for_replaced_element(state, box, specified_max_width); } } @@ -343,7 +349,7 @@ float FormattingContext::compute_width_for_replaced_element(const ReplacedBox& b auto specified_min_width = box.computed_values().min_width().has_value() ? box.computed_values().min_width()->resolved(box, width_of_containing_block).resolved(box) : CSS::Length::make_auto(); if (!specified_min_width.is_auto()) { if (used_width < specified_min_width.to_px(box)) { - used_width = tentative_width_for_replaced_element(box, specified_min_width); + used_width = tentative_width_for_replaced_element(state, box, specified_min_width); } } @@ -352,10 +358,10 @@ float FormattingContext::compute_width_for_replaced_element(const ReplacedBox& b // 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(ReplacedBox const& box, CSS::Length const& computed_height) +float FormattingContext::tentative_height_for_replaced_element(FormattingState const& state, ReplacedBox const& box, CSS::Length const& computed_height) { - auto& containing_block = *box.containing_block(); - auto width_of_containing_block = CSS::Length::make_px(containing_block.content_width()); + auto const& containing_block = *box.containing_block(); + auto width_of_containing_block = CSS::Length::make_px(state.get(containing_block).content_width); auto computed_width = box.computed_values().width().has_value() ? box.computed_values().width()->resolved(box, width_of_containing_block).resolved(box) : CSS::Length::make_auto(); // If 'height' and 'width' both have computed values of 'auto' and the element also has @@ -367,7 +373,7 @@ float FormattingContext::tentative_height_for_replaced_element(ReplacedBox const // // (used width) / (intrinsic ratio) if (computed_height.is_auto() && box.has_intrinsic_aspect_ratio()) - return compute_width_for_replaced_element(box) / box.intrinsic_aspect_ratio().value(); + return compute_width_for_replaced_element(state, box) / 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()) @@ -382,32 +388,35 @@ float FormattingContext::tentative_height_for_replaced_element(ReplacedBox const return computed_height.to_px(box); } -float FormattingContext::compute_height_for_replaced_element(const ReplacedBox& box) +float FormattingContext::compute_height_for_replaced_element(FormattingState const& state, ReplacedBox const& box) { // 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& containing_block = *box.containing_block(); - auto width_of_containing_block = CSS::Length::make_px(containing_block.content_width()); - auto height_of_containing_block = CSS::Length::make_px(containing_block.content_height()); + auto const& containing_block = *box.containing_block(); + auto const& containing_block_state = state.get(containing_block); + auto width_of_containing_block = CSS::Length::make_px(containing_block_state.content_width); + auto height_of_containing_block = CSS::Length::make_px(containing_block_state.content_height); auto specified_width = box.computed_values().width().has_value() ? box.computed_values().width()->resolved(box, width_of_containing_block).resolved(box) : CSS::Length::make_auto(); auto specified_height = box.computed_values().height().has_value() ? box.computed_values().height()->resolved(box, height_of_containing_block).resolved(box) : CSS::Length::make_auto(); - float used_height = tentative_height_for_replaced_element(box, specified_height); + float used_height = tentative_height_for_replaced_element(state, box, specified_height); if (specified_width.is_auto() && specified_height.is_auto() && box.has_intrinsic_aspect_ratio()) { - float w = tentative_width_for_replaced_element(box, specified_width); + float w = tentative_width_for_replaced_element(state, box, specified_width); float h = used_height; - used_height = solve_replaced_size_constraint(w, h, box).height(); + used_height = solve_replaced_size_constraint(state, w, h, box).height(); } return used_height; } -void FormattingContext::compute_width_for_absolutely_positioned_non_replaced_element(Box& box) +void FormattingContext::compute_width_for_absolutely_positioned_non_replaced_element(Box const& box) { - auto& containing_block = *box.containing_block(); - auto width_of_containing_block = CSS::Length::make_px(containing_block.content_width()); + auto& containing_block_state = m_state.get(*box.containing_block()); + auto& box_state = m_state.ensure(box); + + auto width_of_containing_block = CSS::Length::make_px(containing_block_state.content_width); auto& computed_values = box.computed_values(); auto zero_value = CSS::Length::make_px(0); @@ -427,15 +436,15 @@ void FormattingContext::compute_width_for_absolutely_positioned_non_replaced_ele auto width = a_width; auto solve_for_left = [&] { - return CSS::Length(containing_block.content_width() - margin_left.to_px(box) - border_left - padding_left - width.to_px(box) - padding_right - border_right - margin_right.to_px(box) - right.to_px(box), CSS::Length::Type::Px); + return CSS::Length(containing_block_state.content_width - margin_left.to_px(box) - border_left - padding_left - width.to_px(box) - padding_right - border_right - margin_right.to_px(box) - right.to_px(box), CSS::Length::Type::Px); }; auto solve_for_width = [&] { - return CSS::Length(containing_block.content_width() - left.to_px(box) - margin_left.to_px(box) - border_left - padding_left - padding_right - border_right - margin_right.to_px(box) - right.to_px(box), CSS::Length::Type::Px); + return CSS::Length(containing_block_state.content_width - left.to_px(box) - margin_left.to_px(box) - border_left - padding_left - padding_right - border_right - margin_right.to_px(box) - right.to_px(box), CSS::Length::Type::Px); }; auto solve_for_right = [&] { - return CSS::Length(containing_block.content_width() - left.to_px(box) - margin_left.to_px(box) - border_left - padding_left - width.to_px(box) - padding_right - border_right - margin_right.to_px(box), CSS::Length::Type::Px); + return CSS::Length(containing_block_state.content_width - left.to_px(box) - margin_left.to_px(box) - border_left - padding_left - width.to_px(box) - padding_right - border_right - margin_right.to_px(box), CSS::Length::Type::Px); }; // If all three of 'left', 'width', and 'right' are 'auto': @@ -535,30 +544,33 @@ void FormattingContext::compute_width_for_absolutely_positioned_non_replaced_ele } } - box.set_content_width(used_width.to_px(box)); + box_state.content_width = used_width.to_px(box); - box.box_model().margin.left = margin_left.to_px(box); - box.box_model().margin.right = margin_right.to_px(box); - box.box_model().border.left = border_left; - box.box_model().border.right = border_right; - box.box_model().padding.left = padding_left; - box.box_model().padding.right = padding_right; + box_state.margin_left = margin_left.to_px(box); + box_state.margin_right = margin_right.to_px(box); + box_state.border_left = border_left; + box_state.border_right = border_right; + box_state.padding_left = padding_left; + box_state.padding_right = padding_right; } -void FormattingContext::compute_width_for_absolutely_positioned_replaced_element(ReplacedBox& box) +void FormattingContext::compute_width_for_absolutely_positioned_replaced_element(ReplacedBox const& box) { // 10.3.8 Absolutely positioned, replaced elements // The used value of 'width' is determined as for inline replaced elements. - box.prepare_for_replaced_layout(); - box.set_content_width(compute_width_for_replaced_element(box)); + // FIXME: This const_cast is gross. + const_cast<ReplacedBox&>(box).prepare_for_replaced_layout(); + m_state.ensure(box).content_width = compute_width_for_replaced_element(m_state, box); } -void FormattingContext::compute_height_for_absolutely_positioned_non_replaced_element(Box& box) +void FormattingContext::compute_height_for_absolutely_positioned_non_replaced_element(Box const& box) { auto& computed_values = box.computed_values(); - auto& containing_block = *box.containing_block(); - auto width_of_containing_block = CSS::Length::make_px(containing_block.content_width()); - auto height_of_containing_block = CSS::Length::make_px(containing_block.content_height()); + auto const& containing_block = *box.containing_block(); + auto const& containing_block_state = m_state.get(containing_block); + auto& box_state = m_state.ensure(box); + auto width_of_containing_block = CSS::Length::make_px(containing_block_state.content_width); + auto height_of_containing_block = CSS::Length::make_px(containing_block_state.content_height); CSS::Length specified_top = computed_values.offset().top.resolved(box, height_of_containing_block).resolved(box); CSS::Length specified_bottom = computed_values.offset().bottom.resolved(box, height_of_containing_block).resolved(box); @@ -574,28 +586,20 @@ void FormattingContext::compute_height_for_absolutely_positioned_non_replaced_el auto specified_max_height = computed_values.max_height().has_value() ? computed_values.max_height()->resolved(box, height_of_containing_block).resolved(box) : CSS::Length::make_auto(); auto specified_min_height = computed_values.min_height().has_value() ? computed_values.min_height()->resolved(box, height_of_containing_block).resolved(box) : CSS::Length::make_auto(); - box.box_model().margin.top = computed_values.margin().top.resolved(box, width_of_containing_block).to_px(box); - box.box_model().margin.bottom = computed_values.margin().bottom.resolved(box, width_of_containing_block).to_px(box); - box.box_model().border.top = computed_values.border_top().width; - box.box_model().border.bottom = computed_values.border_bottom().width; - box.box_model().padding.top = computed_values.padding().top.resolved(box, width_of_containing_block).to_px(box); - box.box_model().padding.bottom = computed_values.padding().bottom.resolved(box, width_of_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).to_px(box); + box_state.padding_bottom = computed_values.padding().bottom.resolved(box, width_of_containing_block).to_px(box); if (specified_height.is_auto() && !specified_top.is_auto() && specified_bottom.is_auto()) { - const auto& margin = box.box_model().margin; - const auto& padding = box.box_model().padding; - const auto& border = box.box_model().border; - - specified_height = CSS::Length(compute_auto_height_for_block_level_element(box), CSS::Length::Type::Px); - box.box_model().offset.bottom = containing_block.content_height() - specified_height.to_px(box) - specified_top.to_px(box) - margin.top - padding.top - border.top - margin.bottom - padding.bottom - border.bottom; + specified_height = CSS::Length(compute_auto_height_for_block_level_element(m_state, box), CSS::Length::Type::Px); + box_state.offset_bottom = containing_block_state.content_height - specified_height.to_px(box) - specified_top.to_px(box) - box_state.margin_top - box_state.padding_top - box_state.border_top - box_state.margin_bottom - box_state.padding_bottom - box_state.border_bottom; } else if (specified_height.is_auto() && !specified_top.is_auto() && !specified_bottom.is_auto()) { - const auto& margin = box.box_model().margin; - const auto& padding = box.box_model().padding; - const auto& border = box.box_model().border; - - specified_height = CSS::Length(containing_block.content_height() - specified_top.to_px(box) - margin.top - padding.top - border.top - specified_bottom.to_px(box) - margin.bottom - padding.bottom - border.bottom, CSS::Length::Type::Px); + specified_height = CSS::Length(containing_block_state.content_height - specified_top.to_px(box) - box_state.margin_top - box_state.padding_top - box_state.border_top - specified_bottom.to_px(box) - box_state.margin_bottom - box_state.padding_bottom - box_state.border_bottom, CSS::Length::Type::Px); } if (!specified_height.is_auto()) { @@ -604,16 +608,16 @@ void FormattingContext::compute_height_for_absolutely_positioned_non_replaced_el used_height = min(used_height, specified_max_height.to_px(box)); if (!specified_min_height.is_auto()) used_height = max(used_height, specified_min_height.to_px(box)); - box.set_content_height(used_height); + box_state.content_height = used_height; } } -void FormattingContext::layout_absolutely_positioned_element(Box& box) +void FormattingContext::layout_absolutely_positioned_element(Box const& box) { - auto& containing_block = *box.containing_block(); - auto width_of_containing_block = CSS::Length::make_px(containing_block.content_width()); - auto height_of_containing_block = CSS::Length::make_px(containing_block.content_height()); - auto& box_model = box.box_model(); + auto const& containing_block_state = m_state.get(*box.containing_block()); + auto width_of_containing_block = CSS::Length::make_px(containing_block_state.content_width); + auto height_of_containing_block = CSS::Length::make_px(containing_block_state.content_height); + auto& box_state = m_state.ensure(box); auto specified_width = box.computed_values().width().has_value() ? box.computed_values().width()->resolved(box, width_of_containing_block).resolved(box) : CSS::Length::make_auto(); @@ -621,20 +625,20 @@ void FormattingContext::layout_absolutely_positioned_element(Box& box) auto independent_formatting_context = layout_inside(box, LayoutMode::Default); compute_height_for_absolutely_positioned_element(box); - box_model.margin.left = box.computed_values().margin().left.resolved(box, width_of_containing_block).to_px(box); - box_model.margin.top = box.computed_values().margin().top.resolved(box, height_of_containing_block).to_px(box); - box_model.margin.right = box.computed_values().margin().right.resolved(box, width_of_containing_block).to_px(box); - box_model.margin.bottom = box.computed_values().margin().bottom.resolved(box, height_of_containing_block).to_px(box); + box_state.margin_left = box.computed_values().margin().left.resolved(box, width_of_containing_block).to_px(box); + box_state.margin_top = box.computed_values().margin().top.resolved(box, height_of_containing_block).to_px(box); + box_state.margin_right = box.computed_values().margin().right.resolved(box, width_of_containing_block).to_px(box); + box_state.margin_bottom = box.computed_values().margin().bottom.resolved(box, height_of_containing_block).to_px(box); - box_model.border.left = box.computed_values().border_left().width; - box_model.border.right = box.computed_values().border_right().width; - box_model.border.top = box.computed_values().border_top().width; - box_model.border.bottom = box.computed_values().border_bottom().width; + box_state.border_left = box.computed_values().border_left().width; + box_state.border_right = box.computed_values().border_right().width; + box_state.border_top = box.computed_values().border_top().width; + box_state.border_bottom = box.computed_values().border_bottom().width; - box_model.offset.left = box.computed_values().offset().left.resolved(box, width_of_containing_block).to_px(box); - box_model.offset.top = box.computed_values().offset().top.resolved(box, height_of_containing_block).to_px(box); - box_model.offset.right = box.computed_values().offset().right.resolved(box, width_of_containing_block).to_px(box); - box_model.offset.bottom = box.computed_values().offset().bottom.resolved(box, height_of_containing_block).to_px(box); + box_state.offset_left = box.computed_values().offset().left.resolved(box, width_of_containing_block).to_px(box); + box_state.offset_top = box.computed_values().offset().top.resolved(box, height_of_containing_block).to_px(box); + box_state.offset_right = box.computed_values().offset().right.resolved(box, width_of_containing_block).to_px(box); + box_state.offset_bottom = box.computed_values().offset().bottom.resolved(box, height_of_containing_block).to_px(box); auto is_auto = [](auto const& length_percentage) { return length_percentage.is_length() && length_percentage.length().is_auto(); @@ -642,52 +646,90 @@ void FormattingContext::layout_absolutely_positioned_element(Box& box) if (is_auto(box.computed_values().offset().left) && specified_width.is_auto() && is_auto(box.computed_values().offset().right)) { if (is_auto(box.computed_values().margin().left)) - box_model.margin.left = 0; + box_state.margin_left = 0; if (is_auto(box.computed_values().margin().right)) - box_model.margin.right = 0; + box_state.margin_right = 0; } Gfx::FloatPoint used_offset; if (!is_auto(box.computed_values().offset().left)) { - float x_offset = box_model.offset.left - + box_model.border_box().left; - used_offset.set_x(x_offset + box_model.margin.left); + float x_offset = box_state.offset_left + + box_state.border_box_left(); + used_offset.set_x(x_offset + box_state.margin_left); } else if (!is_auto(box.computed_values().offset().right)) { float x_offset = 0 - - box_model.offset.right - - box_model.border_box().right; - used_offset.set_x(containing_block.content_width() + x_offset - box.content_width() - box_model.margin.right); + - box_state.offset_right + - box_state.border_box_right(); + used_offset.set_x(containing_block_state.content_width + x_offset - box_state.content_width - box_state.margin_right); } else { - float x_offset = box_model.margin_box().left; + float x_offset = box_state.margin_box_left(); used_offset.set_x(x_offset); } if (!is_auto(box.computed_values().offset().top)) { - float y_offset = box_model.offset.top - + box_model.border_box().top; - used_offset.set_y(y_offset + box_model.margin.top); + float y_offset = box_state.offset_top + + box_state.border_box_top(); + used_offset.set_y(y_offset + box_state.margin_top); } else if (!is_auto(box.computed_values().offset().bottom)) { float y_offset = 0 - - box_model.offset.bottom - - box_model.border_box().bottom; - used_offset.set_y(containing_block.content_height() + y_offset - box.content_height() - box_model.margin.bottom); + - box_state.offset_bottom + - box_state.border_box_bottom(); + used_offset.set_y(containing_block_state.content_height + y_offset - box_state.content_height - box_state.margin_bottom); } else { - float y_offset = box_model.margin_box().top; + float y_offset = box_state.margin_box_top(); used_offset.set_y(y_offset); } - box.set_offset(used_offset); + box_state.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& box) +void FormattingContext::compute_height_for_absolutely_positioned_replaced_element(ReplacedBox const& box) { // 10.6.5 Absolutely positioned, replaced elements // The used value of 'height' is determined as for inline replaced elements. - box.set_content_height(compute_height_for_replaced_element(box)); + m_state.ensure(box).content_height = compute_height_for_replaced_element(m_state, box); +} + +void FormattingContext::compute_position(Box const& box) +{ + // 9.4.3 Relative positioning + // Once a box has been laid out according to the normal flow or floated, it may be shifted relative to this position. + + if (box.computed_values().position() != CSS::Position::Relative) + return; + + auto& box_state = m_state.ensure(box); + auto const& computed_values = box.computed_values(); + float width_of_containing_block = m_state.get(*box.containing_block()).content_width; + auto width_of_containing_block_as_length = CSS::Length::make_px(width_of_containing_block); + + auto specified_left = computed_values.offset().left.resolved(box, width_of_containing_block_as_length).resolved(box); + auto specified_right = computed_values.offset().right.resolved(box, width_of_containing_block_as_length).resolved(box); + + if (specified_left.is_auto() && specified_right.is_auto()) { + // If both 'left' and 'right' are 'auto' (their initial values), the used values are '0' (i.e., the boxes stay in their original position). + box_state.offset_left = 0; + box_state.offset_right = 0; + } else if (specified_left.is_auto()) { + // If 'left' is 'auto', its used value is minus the value of 'right' (i.e., the boxes move to the left by the value of 'right'). + box_state.offset_right = specified_right.to_px(box); + box_state.offset_left = 0 - box_state.offset_right; + } else if (specified_right.is_auto()) { + // If 'right' is specified as 'auto', its used value is minus the value of 'left'. + box_state.offset_left = specified_left.to_px(box); + box_state.offset_right = 0 - box_state.offset_left; + } else { + // If neither 'left' nor 'right' is 'auto', the position is over-constrained, and one of them has to be ignored. + // If the 'direction' property of the containing block is 'ltr', the value of 'left' wins and 'right' becomes -'left'. + // If 'direction' of the containing block is 'rtl', 'right' wins and 'left' is ignored. + // FIXME: Check direction (assuming 'ltr' for now). + box_state.offset_left = specified_left.to_px(box); + box_state.offset_right = 0 - box_state.offset_left; + } } } diff --git a/Userland/Libraries/LibWeb/Layout/FormattingContext.h b/Userland/Libraries/LibWeb/Layout/FormattingContext.h index 13e4f25fa9..bdcc19f22b 100644 --- a/Userland/Libraries/LibWeb/Layout/FormattingContext.h +++ b/Userland/Libraries/LibWeb/Layout/FormattingContext.h @@ -24,10 +24,9 @@ public: SVG, }; - virtual void run(Box&, LayoutMode) = 0; + virtual void run(Box const&, LayoutMode) = 0; - Box& context_box() { return m_context_box; } - const Box& context_box() const { return m_context_box; } + Box const& context_box() const { return m_context_box; } FormattingContext* parent() { return m_parent; } const FormattingContext* parent() const { return m_parent; } @@ -39,45 +38,46 @@ public: static bool creates_block_formatting_context(const Box&); - static float compute_width_for_replaced_element(const ReplacedBox&); - static float compute_height_for_replaced_element(const ReplacedBox&); + static float compute_width_for_replaced_element(FormattingState const&, ReplacedBox const&); + static float compute_height_for_replaced_element(FormattingState const&, ReplacedBox const&); - OwnPtr<FormattingContext> create_independent_formatting_context_if_needed(Box& child_box); + OwnPtr<FormattingContext> create_independent_formatting_context_if_needed(Box const& child_box); virtual void parent_context_did_dimension_child_root_box() { } protected: - FormattingContext(Type, FormattingState&, Box&, FormattingContext* parent = nullptr); + FormattingContext(Type, FormattingState&, Box const&, FormattingContext* parent = nullptr); - OwnPtr<FormattingContext> layout_inside(Box&, LayoutMode); + OwnPtr<FormattingContext> layout_inside(Box const&, LayoutMode); + void compute_position(Box const&); struct ShrinkToFitResult { float preferred_width { 0 }; float preferred_minimum_width { 0 }; }; - static float tentative_width_for_replaced_element(const ReplacedBox&, const CSS::Length& width); - static float tentative_height_for_replaced_element(const ReplacedBox&, const CSS::Length& height); + static float tentative_width_for_replaced_element(FormattingState const&, ReplacedBox const&, CSS::Length const& width); + static float tentative_height_for_replaced_element(FormattingState const&, ReplacedBox const&, CSS::Length const& height); enum ConsiderFloats { Yes, No, }; - static float compute_auto_height_for_block_level_element(Box const&, ConsiderFloats consider_floats = ConsiderFloats::Yes); + static float compute_auto_height_for_block_level_element(FormattingState const&, Box const&, ConsiderFloats consider_floats = ConsiderFloats::Yes); - ShrinkToFitResult calculate_shrink_to_fit_widths(Box&); + ShrinkToFitResult calculate_shrink_to_fit_widths(Box const&); - void layout_absolutely_positioned_element(Box&); - void compute_width_for_absolutely_positioned_element(Box&); - void compute_width_for_absolutely_positioned_non_replaced_element(Box&); - void compute_width_for_absolutely_positioned_replaced_element(ReplacedBox&); - void compute_height_for_absolutely_positioned_element(Box&); - void compute_height_for_absolutely_positioned_non_replaced_element(Box&); - void compute_height_for_absolutely_positioned_replaced_element(ReplacedBox&); + 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&); Type m_type {}; FormattingContext* m_parent { nullptr }; - Box& m_context_box; + Box const& m_context_box; FormattingState& m_state; }; diff --git a/Userland/Libraries/LibWeb/Layout/FormattingState.cpp b/Userland/Libraries/LibWeb/Layout/FormattingState.cpp new file mode 100644 index 0000000000..f293c07e1b --- /dev/null +++ b/Userland/Libraries/LibWeb/Layout/FormattingState.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2022, Andreas Kling <kling@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <LibWeb/Layout/BlockContainer.h> +#include <LibWeb/Layout/FormattingState.h> + +namespace Web::Layout { + +void FormattingState::commit() +{ + for (auto& it : nodes) { + auto& node = const_cast<Layout::NodeWithStyleAndBoxModelMetrics&>(*it.key); + auto& node_state = *it.value; + + // Transfer box model metrics. + node.box_model().offset = { node_state.offset_top, node_state.offset_right, node_state.offset_bottom, node_state.offset_left }; + node.box_model().padding = { node_state.padding_top, node_state.padding_right, node_state.padding_bottom, node_state.padding_left }; + node.box_model().border = { node_state.border_top, node_state.border_right, node_state.border_bottom, node_state.border_left }; + node.box_model().margin = { node_state.margin_top, node_state.margin_right, node_state.margin_bottom, node_state.margin_left }; + + // For boxes, transfer relative offset and size. + if (is<Layout::Box>(node)) { + auto& box = static_cast<Layout::Box&>(node); + box.set_offset(node_state.offset); + box.set_content_size(node_state.content_width, node_state.content_height); + } + + // For block containers, transfer line boxes. + if (is<Layout::BlockContainer>(node)) { + auto& block_container = static_cast<Layout::BlockContainer&>(node); + block_container.set_line_boxes(move(node_state.line_boxes)); + } + } +} + +Gfx::FloatRect margin_box_rect(Box const& box, FormattingState const& state) +{ + auto const& box_state = state.get(box); + auto rect = Gfx::FloatRect { box_state.offset, { box_state.content_width, 0 } }; + rect.set_x(rect.x() - box_state.margin_box_left()); + rect.set_width(rect.width() + box_state.margin_box_left() + box_state.margin_box_right()); + rect.set_y(rect.y() - box_state.margin_box_top()); + rect.set_height(rect.height() + box_state.margin_box_top() + box_state.margin_box_bottom()); + return rect; +} + +Gfx::FloatRect margin_box_rect_in_ancestor_coordinate_space(Box const& box, Box const& ancestor_box, FormattingState const& state) +{ + auto rect = margin_box_rect(box, state); + for (auto const* current = box.parent(); current; current = current->parent()) { + if (current == &ancestor_box) + break; + if (is<Box>(*current)) { + auto const& current_state = state.get(static_cast<Box const&>(*current)); + rect.translate_by(current_state.offset); + } + } + return rect; +} + +Gfx::FloatRect absolute_content_rect(Box const& box, FormattingState const& state) +{ + auto const& box_state = state.get(box); + Gfx::FloatRect rect { box_state.offset, { box_state.content_width, box_state.content_height } }; + for (auto* block = box.containing_block(); block; block = block->containing_block()) + rect.translate_by(state.get(*block).offset); + return rect; +} + +} diff --git a/Userland/Libraries/LibWeb/Layout/FormattingState.h b/Userland/Libraries/LibWeb/Layout/FormattingState.h index f90ffa29f9..23956ee8b0 100644 --- a/Userland/Libraries/LibWeb/Layout/FormattingState.h +++ b/Userland/Libraries/LibWeb/Layout/FormattingState.h @@ -6,9 +6,74 @@ #pragma once +#include <AK/HashMap.h> +#include <LibGfx/Point.h> +#include <LibWeb/Layout/LineBox.h> +#include <LibWeb/Layout/Node.h> + namespace Web::Layout { struct FormattingState { + struct NodeState { + float content_width { 0 }; + float content_height { 0 }; + Gfx::FloatPoint offset; + + float margin_left { 0 }; + float margin_right { 0 }; + float margin_top { 0 }; + float margin_bottom { 0 }; + + float border_left { 0 }; + float border_right { 0 }; + float border_top { 0 }; + float border_bottom { 0 }; + + float padding_left { 0 }; + float padding_right { 0 }; + float padding_top { 0 }; + float padding_bottom { 0 }; + + float offset_left { 0 }; + float offset_right { 0 }; + float offset_top { 0 }; + float offset_bottom { 0 }; + + Vector<LineBox> line_boxes; + + float margin_box_left() const { return margin_left + border_left + padding_left; } + float margin_box_right() const { return margin_right + border_right + padding_right; } + float margin_box_top() const { return margin_top + border_top + padding_top; } + float margin_box_bottom() const { return margin_bottom + border_bottom + padding_bottom; } + + float border_box_left() const { return border_left + padding_left; } + float border_box_right() const { return border_right + padding_right; } + float border_box_top() const { return border_top + padding_top; } + float border_box_bottom() const { return border_bottom + padding_bottom; } + + float border_box_width() const { return border_box_left() + content_width + border_box_right(); } + float border_box_height() const { return border_box_top() + content_height + border_box_bottom(); } + }; + + void commit(); + + NodeState& ensure(NodeWithStyleAndBoxModelMetrics const& box) + { + return *nodes.ensure(&box, [] { return make<NodeState>(); }); + } + + NodeState const& get(NodeWithStyleAndBoxModelMetrics const& box) const + { + if (!nodes.contains(&box)) + return const_cast<FormattingState&>(*this).ensure(box); + return *nodes.get(&box).value(); + } + + HashMap<NodeWithStyleAndBoxModelMetrics const*, NonnullOwnPtr<NodeState>> nodes; }; +Gfx::FloatRect absolute_content_rect(Box const&, FormattingState const&); +Gfx::FloatRect margin_box_rect(Box const&, FormattingState const&); +Gfx::FloatRect margin_box_rect_in_ancestor_coordinate_space(Box const& box, Box const& ancestor_box, FormattingState const&); + } diff --git a/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.cpp index 803b8bf596..5cc766239e 100644 --- a/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.cpp @@ -17,7 +17,7 @@ namespace Web::Layout { -InlineFormattingContext::InlineFormattingContext(FormattingState& state, BlockContainer& containing_block, BlockFormattingContext& parent) +InlineFormattingContext::InlineFormattingContext(FormattingState& state, BlockContainer const& containing_block, BlockFormattingContext& parent) : FormattingContext(Type::Inline, state, containing_block, &parent) { } @@ -39,7 +39,7 @@ BlockFormattingContext const& InlineFormattingContext::parent() const InlineFormattingContext::AvailableSpaceForLineInfo InlineFormattingContext::available_space_for_line(float y) const { // NOTE: Floats are relative to the BFC root box, not necessarily the containing block of this IFC. - auto box_in_root_rect = containing_block().margin_box_rect_in_ancestor_coordinate_space(parent().root()); + auto box_in_root_rect = margin_box_rect_in_ancestor_coordinate_space(containing_block(), parent().root(), m_state); float y_in_root = box_in_root_rect.y() + y; AvailableSpaceForLineInfo info; @@ -48,18 +48,18 @@ InlineFormattingContext::AvailableSpaceForLineInfo InlineFormattingContext::avai for (ssize_t i = bfc.left_side_floats().boxes.size() - 1; i >= 0; --i) { auto const& floating_box = bfc.left_side_floats().boxes.at(i); - auto rect = floating_box.margin_box_as_relative_rect(); + auto rect = margin_box_rect(floating_box, m_state); if (rect.contains_vertically(y_in_root)) { info.left = rect.right() + 1; break; } } - info.right = containing_block().content_width(); + info.right = m_state.get(containing_block()).content_width; for (ssize_t i = bfc.right_side_floats().boxes.size() - 1; i >= 0; --i) { auto const& floating_box = bfc.right_side_floats().boxes.at(i); - auto rect = floating_box.margin_box_as_relative_rect(); + auto rect = margin_box_rect(floating_box, m_state); if (rect.contains_vertically(y_in_root)) { info.right = rect.left() - 1; break; @@ -69,7 +69,7 @@ InlineFormattingContext::AvailableSpaceForLineInfo InlineFormattingContext::avai return info; } -void InlineFormattingContext::run(Box&, LayoutMode layout_mode) +void InlineFormattingContext::run(Box const&, LayoutMode layout_mode) { VERIFY(containing_block().children_are_inline()); @@ -87,7 +87,7 @@ void InlineFormattingContext::run(Box&, LayoutMode layout_mode) float max_line_width = 0; float content_height = 0; - for (auto& line_box : containing_block().line_boxes()) { + for (auto& line_box : m_state.get(containing_block()).line_boxes) { float max_height = min_line_height; for (auto& fragment : line_box.fragments()) { max_height = max(max_height, fragment.height()); @@ -96,62 +96,66 @@ void InlineFormattingContext::run(Box&, LayoutMode layout_mode) content_height += max_height; } + auto& containing_block_state = m_state.ensure(containing_block()); + if (layout_mode != LayoutMode::Default) { - containing_block().set_content_width(max_line_width); + containing_block_state.content_width = max_line_width; } - containing_block().set_content_height(content_height); + containing_block_state.content_height = content_height; } -void InlineFormattingContext::dimension_box_on_line(Box& box, LayoutMode layout_mode) +void InlineFormattingContext::dimension_box_on_line(Box const& box, LayoutMode layout_mode) { - auto width_of_containing_block = CSS::Length::make_px(containing_block().content_width()); - auto& box_model = box.box_model(); + auto width_of_containing_block = CSS::Length::make_px(m_state.get(containing_block()).content_width); + auto& box_state = m_state.ensure(box); + auto const& computed_values = box.computed_values(); - box_model.margin.left = box.computed_values().margin().left.resolved(box, width_of_containing_block).to_px(box); - box_model.border.left = box.computed_values().border_left().width; - box_model.padding.left = box.computed_values().padding().left.resolved(box, width_of_containing_block).to_px(box); - box_model.margin.right = box.computed_values().margin().right.resolved(box, width_of_containing_block).to_px(box); - box_model.border.right = box.computed_values().border_right().width; - box_model.padding.right = box.computed_values().padding().right.resolved(box, width_of_containing_block).to_px(box); + box_state.margin_left = computed_values.margin().left.resolved(box, width_of_containing_block).to_px(box); + box_state.border_left = computed_values.border_left().width; + box_state.padding_left = computed_values.padding().left.resolved(box, width_of_containing_block).to_px(box); + box_state.margin_right = computed_values.margin().right.resolved(box, width_of_containing_block).to_px(box); + box_state.border_right = computed_values.border_right().width; + box_state.padding_right = computed_values.padding().right.resolved(box, width_of_containing_block).to_px(box); if (is<ReplacedBox>(box)) { auto& replaced = verify_cast<ReplacedBox>(box); - replaced.set_content_width(compute_width_for_replaced_element(replaced)); - replaced.set_content_height(compute_height_for_replaced_element(replaced)); + box_state.content_width = compute_width_for_replaced_element(m_state, replaced); + box_state.content_height = compute_height_for_replaced_element(m_state, replaced); return; } if (box.is_inline_block()) { - auto& inline_block = const_cast<BlockContainer&>(verify_cast<BlockContainer>(box)); + auto const& inline_block = verify_cast<BlockContainer>(box); + auto const& containing_block_state = m_state.get(containing_block()); auto& width_value = inline_block.computed_values().width(); if (!width_value.has_value() || (width_value->is_length() && width_value->length().is_auto())) { auto result = calculate_shrink_to_fit_widths(inline_block); - auto available_width = containing_block().content_width() - - box_model.margin.left - - box_model.border.left - - box_model.padding.left - - box_model.padding.right - - box_model.border.right - - box_model.margin.right; + auto available_width = containing_block_state.content_width + - box_state.margin_left + - box_state.border_left + - box_state.padding_left + - box_state.padding_right + - box_state.border_right + - box_state.margin_right; auto width = min(max(result.preferred_minimum_width, available_width), result.preferred_width); - inline_block.set_content_width(width); + box_state.content_width = width; } else { - auto container_width = CSS::Length::make_px(containing_block().content_width()); - inline_block.set_content_width(width_value->resolved(box, container_width).to_px(inline_block)); + auto container_width = CSS::Length::make_px(containing_block_state.content_width); + box_state.content_width = width_value->resolved(box, container_width).to_px(inline_block); } auto independent_formatting_context = layout_inside(inline_block, layout_mode); auto& height_value = inline_block.computed_values().height(); if (!height_value.has_value() || (height_value->is_length() && height_value->length().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); + BlockFormattingContext::compute_height(inline_block, m_state); } else { - auto container_height = CSS::Length::make_px(containing_block().content_height()); - inline_block.set_content_height(height_value->resolved(box, container_height).to_px(inline_block)); + auto container_height = CSS::Length::make_px(containing_block_state.content_height); + box_state.content_height = height_value->resolved(box, container_height).to_px(inline_block); } independent_formatting_context->parent_context_did_dimension_child_root_box(); @@ -166,10 +170,12 @@ void InlineFormattingContext::dimension_box_on_line(Box& box, LayoutMode layout_ void InlineFormattingContext::generate_line_boxes(LayoutMode layout_mode) { - containing_block().line_boxes().clear(); + auto& containing_block_state = m_state.ensure(containing_block()); + auto& line_boxes = containing_block_state.line_boxes; + line_boxes.clear_with_capacity(); - InlineLevelIterator iterator(*this, containing_block(), layout_mode); - LineBuilder line_builder(*this); + InlineLevelIterator iterator(*this, m_state, containing_block(), layout_mode); + LineBuilder line_builder(*this, m_state); for (;;) { auto item_opt = iterator.next(line_builder.available_width_for_current_line()); @@ -178,7 +184,7 @@ void InlineFormattingContext::generate_line_boxes(LayoutMode layout_mode) auto& item = item_opt.value(); // Ignore collapsible whitespace chunks at the start of line, and if the last fragment already ends in whitespace. - if (item.is_collapsible_whitespace && containing_block().line_boxes().last().is_empty_or_ends_in_whitespace()) + if (item.is_collapsible_whitespace && line_boxes.last().is_empty_or_ends_in_whitespace()) continue; switch (item.type) { @@ -207,7 +213,7 @@ void InlineFormattingContext::generate_line_boxes(LayoutMode layout_mode) } } - for (auto& line_box : containing_block().line_boxes()) { + for (auto& line_box : line_boxes) { line_box.trim_trailing_whitespace(); } diff --git a/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.h b/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.h index 8f68362103..c449bdd98e 100644 --- a/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.h +++ b/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.h @@ -15,18 +15,17 @@ namespace Web::Layout { class InlineFormattingContext final : public FormattingContext { public: - InlineFormattingContext(FormattingState&, BlockContainer& containing_block, BlockFormattingContext& parent); + InlineFormattingContext(FormattingState&, BlockContainer const& containing_block, BlockFormattingContext& parent); ~InlineFormattingContext(); BlockFormattingContext& parent(); BlockFormattingContext const& parent() const; - BlockContainer& containing_block() { return static_cast<BlockContainer&>(context_box()); } BlockContainer const& containing_block() const { return static_cast<BlockContainer const&>(context_box()); } - virtual void run(Box&, LayoutMode) override; + virtual void run(Box const&, LayoutMode) override; - void dimension_box_on_line(Box&, LayoutMode); + void dimension_box_on_line(Box const&, LayoutMode); struct AvailableSpaceForLineInfo { float left { 0 }; diff --git a/Userland/Libraries/LibWeb/Layout/InlineLevelIterator.cpp b/Userland/Libraries/LibWeb/Layout/InlineLevelIterator.cpp index 13c14f75ff..5c63e7ba52 100644 --- a/Userland/Libraries/LibWeb/Layout/InlineLevelIterator.cpp +++ b/Userland/Libraries/LibWeb/Layout/InlineLevelIterator.cpp @@ -13,8 +13,9 @@ namespace Web::Layout { -InlineLevelIterator::InlineLevelIterator(Layout::InlineFormattingContext& inline_formatting_context, Layout::BlockContainer& container, LayoutMode layout_mode) +InlineLevelIterator::InlineLevelIterator(Layout::InlineFormattingContext& inline_formatting_context, Layout::FormattingState& formatting_state, Layout::BlockContainer const& container, LayoutMode layout_mode) : m_inline_formatting_context(inline_formatting_context) + , m_formatting_state(formatting_state) , m_container(container) , m_next_node(container.first_child()) , m_layout_mode(layout_mode) @@ -22,18 +23,24 @@ InlineLevelIterator::InlineLevelIterator(Layout::InlineFormattingContext& inline skip_to_next(); } -void InlineLevelIterator::enter_node_with_box_model_metrics(Layout::NodeWithStyleAndBoxModelMetrics& node) +void InlineLevelIterator::enter_node_with_box_model_metrics(Layout::NodeWithStyleAndBoxModelMetrics const& node) { if (!m_extra_leading_metrics.has_value()) m_extra_leading_metrics = ExtraBoxMetrics {}; - node.box_model().margin.left = node.computed_values().margin().left.resolved(node, CSS::Length::make_px(m_container.content_width())).to_px(node); - node.box_model().border.left = node.computed_values().border_left().width; - node.box_model().padding.left = node.computed_values().padding().left.resolved(node, CSS::Length::make_px(m_container.content_width())).to_px(node); + // FIXME: It's really weird that *this* is where we assign box model metrics for these layout nodes.. - m_extra_leading_metrics->margin += node.box_model().margin.left; - m_extra_leading_metrics->border += node.box_model().border.left; - m_extra_leading_metrics->padding += node.box_model().padding.left; + auto& node_state = m_formatting_state.ensure(node); + auto const& container_state = m_formatting_state.get(m_container); + auto const& computed_values = node.computed_values(); + + node_state.margin_left = computed_values.margin().left.resolved(node, CSS::Length::make_px(container_state.content_width)).to_px(node); + node_state.border_left = computed_values.border_left().width; + node_state.padding_left = computed_values.padding().left.resolved(node, CSS::Length::make_px(container_state.content_width)).to_px(node); + + m_extra_leading_metrics->margin += node_state.margin_left; + m_extra_leading_metrics->border += node_state.border_left; + m_extra_leading_metrics->padding += node_state.padding_left; m_box_model_node_stack.append(node); } @@ -44,26 +51,29 @@ void InlineLevelIterator::exit_node_with_box_model_metrics() m_extra_trailing_metrics = ExtraBoxMetrics {}; auto& node = m_box_model_node_stack.last(); + auto& node_state = m_formatting_state.ensure(node); + auto const& container_state = m_formatting_state.get(m_container); + auto const& computed_values = node.computed_values(); - node.box_model().margin.right = node.computed_values().margin().right.resolved(node, CSS::Length::make_px(m_container.content_width())).to_px(node); - node.box_model().border.right = node.computed_values().border_right().width; - node.box_model().padding.right = node.computed_values().padding().right.resolved(node, CSS::Length::make_px(m_container.content_width())).to_px(node); + node_state.margin_right = computed_values.margin().right.resolved(node, CSS::Length::make_px(container_state.content_width)).to_px(node); + node_state.border_right = computed_values.border_right().width; + node_state.padding_right = computed_values.padding().right.resolved(node, CSS::Length::make_px(container_state.content_width)).to_px(node); - m_extra_trailing_metrics->margin += node.box_model().margin.right; - m_extra_trailing_metrics->border += node.box_model().border.right; - m_extra_trailing_metrics->padding += node.box_model().padding.right; + m_extra_trailing_metrics->margin += node_state.margin_right; + m_extra_trailing_metrics->border += node_state.border_right; + m_extra_trailing_metrics->padding += node_state.padding_right; m_box_model_node_stack.take_last(); } // This is similar to Layout::Node::next_in_pre_order() but will not descend into inline-block nodes. -Layout::Node* InlineLevelIterator::next_inline_node_in_pre_order(Layout::Node& current, Layout::Node const* stay_within) +Layout::Node const* InlineLevelIterator::next_inline_node_in_pre_order(Layout::Node const& current, Layout::Node const* stay_within) { if (current.first_child() && current.first_child()->is_inline() && !current.is_inline_block()) return current.first_child(); - Layout::Node* node = ¤t; - Layout::Node* next = nullptr; + Layout::Node const* node = ¤t; + Layout::Node const* next = nullptr; while (!(next = node->next_sibling())) { node = node->parent(); @@ -91,7 +101,7 @@ void InlineLevelIterator::compute_next() void InlineLevelIterator::skip_to_next() { if (m_next_node && is<Layout::NodeWithStyleAndBoxModelMetrics>(*m_next_node)) - enter_node_with_box_model_metrics(static_cast<Layout::NodeWithStyleAndBoxModelMetrics&>(*m_next_node)); + enter_node_with_box_model_metrics(static_cast<Layout::NodeWithStyleAndBoxModelMetrics const&>(*m_next_node)); m_current_node = m_next_node; compute_next(); @@ -103,10 +113,11 @@ Optional<InlineLevelIterator::Item> InlineLevelIterator::next(float available_wi return {}; if (is<Layout::TextNode>(*m_current_node)) { - auto& text_node = static_cast<Layout::TextNode&>(*m_current_node); + auto& text_node = static_cast<Layout::TextNode const&>(*m_current_node); if (!m_text_node_context.has_value()) { - bool previous_is_empty_or_ends_in_whitespace = m_container.line_boxes().is_empty() || m_container.line_boxes().last().is_empty_or_ends_in_whitespace(); + auto& line_boxes = m_formatting_state.get(m_container).line_boxes; + bool previous_is_empty_or_ends_in_whitespace = line_boxes.is_empty() || line_boxes.last().is_empty_or_ends_in_whitespace(); enter_text_node(text_node, previous_is_empty_or_ends_in_whitespace); } @@ -160,11 +171,13 @@ Optional<InlineLevelIterator::Item> InlineLevelIterator::next(float available_wi } if (is<Layout::ReplacedBox>(*m_current_node)) { - auto& replaced_box = static_cast<Layout::ReplacedBox&>(*m_current_node); - replaced_box.prepare_for_replaced_layout(); + auto& replaced_box = static_cast<Layout::ReplacedBox const&>(*m_current_node); + // FIXME: This const_cast is gross. + const_cast<Layout::ReplacedBox&>(replaced_box).prepare_for_replaced_layout(); } auto& box = verify_cast<Layout::Box>(*m_current_node); + auto& box_state = m_formatting_state.get(box); m_inline_formatting_context.dimension_box_on_line(box, m_layout_mode); skip_to_next(); @@ -173,19 +186,19 @@ Optional<InlineLevelIterator::Item> InlineLevelIterator::next(float available_wi .node = &box, .offset_in_node = 0, .length_in_node = 0, - .width = box.content_width(), - .padding_start = box.box_model().padding.left, - .padding_end = box.box_model().padding.right, - .border_start = box.box_model().border.left, - .border_end = box.box_model().border.right, - .margin_start = box.box_model().margin.left, - .margin_end = box.box_model().margin.right, + .width = box_state.content_width, + .padding_start = box_state.padding_left, + .padding_end = box_state.padding_right, + .border_start = box_state.border_left, + .border_end = box_state.border_right, + .margin_start = box_state.margin_left, + .margin_end = box_state.margin_right, }; add_extra_box_model_metrics_to_item(item, true, true); return item; } -void InlineLevelIterator::enter_text_node(Layout::TextNode& text_node, bool previous_is_empty_or_ends_in_whitespace) +void InlineLevelIterator::enter_text_node(Layout::TextNode const& text_node, bool previous_is_empty_or_ends_in_whitespace) { bool do_collapse = true; bool do_wrap_lines = true; @@ -209,7 +222,8 @@ void InlineLevelIterator::enter_text_node(Layout::TextNode& text_node, bool prev do_respect_linebreaks = true; } - text_node.compute_text_for_rendering(do_collapse, previous_is_empty_or_ends_in_whitespace); + // FIXME: The const_cast here is gross. + const_cast<TextNode&>(text_node).compute_text_for_rendering(do_collapse, previous_is_empty_or_ends_in_whitespace); m_text_node_context = TextNodeContext { .do_collapse = do_collapse, diff --git a/Userland/Libraries/LibWeb/Layout/InlineLevelIterator.h b/Userland/Libraries/LibWeb/Layout/InlineLevelIterator.h index b7039c7db5..bfc29421c7 100644 --- a/Userland/Libraries/LibWeb/Layout/InlineLevelIterator.h +++ b/Userland/Libraries/LibWeb/Layout/InlineLevelIterator.h @@ -28,7 +28,7 @@ public: ForcedBreak, }; Type type {}; - Layout::Node* node { nullptr }; + Layout::Node const* node { nullptr }; size_t offset_in_node { 0 }; size_t length_in_node { 0 }; float width { 0.0f }; @@ -47,7 +47,7 @@ public: } }; - InlineLevelIterator(Layout::InlineFormattingContext&, Layout::BlockContainer&, LayoutMode); + InlineLevelIterator(Layout::InlineFormattingContext&, FormattingState&, Layout::BlockContainer const&, LayoutMode); Optional<Item> next(float available_width); @@ -55,19 +55,20 @@ private: void skip_to_next(); void compute_next(); - void enter_text_node(Layout::TextNode&, bool previous_is_empty_or_ends_in_whitespace); + void enter_text_node(Layout::TextNode const&, bool previous_is_empty_or_ends_in_whitespace); - void enter_node_with_box_model_metrics(Layout::NodeWithStyleAndBoxModelMetrics&); + void enter_node_with_box_model_metrics(Layout::NodeWithStyleAndBoxModelMetrics const&); void exit_node_with_box_model_metrics(); void add_extra_box_model_metrics_to_item(Item&, bool add_leading_metrics, bool add_trailing_metrics); - Layout::Node* next_inline_node_in_pre_order(Layout::Node& current, Layout::Node const* stay_within); + Layout::Node const* next_inline_node_in_pre_order(Layout::Node const& current, Layout::Node const* stay_within); Layout::InlineFormattingContext& m_inline_formatting_context; - Layout::BlockContainer& m_container; - Layout::Node* m_current_node { nullptr }; - Layout::Node* m_next_node { nullptr }; + Layout::FormattingState& m_formatting_state; + Layout::BlockContainer const& m_container; + Layout::Node const* m_current_node { nullptr }; + Layout::Node const* m_next_node { nullptr }; LayoutMode const m_layout_mode; struct TextNodeContext { @@ -91,7 +92,7 @@ private: Optional<ExtraBoxMetrics> m_extra_leading_metrics; Optional<ExtraBoxMetrics> m_extra_trailing_metrics; - Vector<NodeWithStyleAndBoxModelMetrics&> m_box_model_node_stack; + Vector<NodeWithStyleAndBoxModelMetrics const&> m_box_model_node_stack; }; } diff --git a/Userland/Libraries/LibWeb/Layout/LineBox.cpp b/Userland/Libraries/LibWeb/Layout/LineBox.cpp index cbec544844..f22b0beea9 100644 --- a/Userland/Libraries/LibWeb/Layout/LineBox.cpp +++ b/Userland/Libraries/LibWeb/Layout/LineBox.cpp @@ -15,7 +15,7 @@ namespace Web::Layout { -void LineBox::add_fragment(Node& layout_node, int start, int length, float leading_size, float trailing_size, float content_width, float content_height, LineBoxFragment::Type fragment_type) +void LineBox::add_fragment(Node const& layout_node, int start, int length, float leading_size, float trailing_size, float content_width, float content_height, LineBoxFragment::Type fragment_type) { bool text_align_is_justify = layout_node.computed_values().text_align() == CSS::TextAlign::Justify; if (!text_align_is_justify && !m_fragments.is_empty() && &m_fragments.last().layout_node() == &layout_node) { @@ -28,8 +28,10 @@ void LineBox::add_fragment(Node& layout_node, int start, int length, float leadi } m_width += content_width + leading_size + trailing_size; - if (is<Box>(layout_node)) - verify_cast<Box>(layout_node).set_containing_line_box_fragment(m_fragments.last()); + if (is<Box>(layout_node)) { + // FIXME: Move this to FormattingContext! + const_cast<Box&>(static_cast<Box const&>(layout_node)).set_containing_line_box_fragment(m_fragments.last()); + } } void LineBox::trim_trailing_whitespace() diff --git a/Userland/Libraries/LibWeb/Layout/LineBox.h b/Userland/Libraries/LibWeb/Layout/LineBox.h index e0503aacf2..76cf5442fc 100644 --- a/Userland/Libraries/LibWeb/Layout/LineBox.h +++ b/Userland/Libraries/LibWeb/Layout/LineBox.h @@ -18,7 +18,7 @@ public: float width() const { return m_width; } - void add_fragment(Node& layout_node, int start, int length, float leading_size, float trailing_size, float content_width, float content_height, LineBoxFragment::Type = LineBoxFragment::Type::Normal); + void add_fragment(Node const& layout_node, int start, int length, float leading_size, float trailing_size, float content_width, float content_height, LineBoxFragment::Type = LineBoxFragment::Type::Normal); const NonnullOwnPtrVector<LineBoxFragment>& fragments() const { return m_fragments; } NonnullOwnPtrVector<LineBoxFragment>& fragments() { return m_fragments; } diff --git a/Userland/Libraries/LibWeb/Layout/LineBoxFragment.h b/Userland/Libraries/LibWeb/Layout/LineBoxFragment.h index 3ada26382c..6d9d72338e 100644 --- a/Userland/Libraries/LibWeb/Layout/LineBoxFragment.h +++ b/Userland/Libraries/LibWeb/Layout/LineBoxFragment.h @@ -23,7 +23,7 @@ public: Trailing, }; - LineBoxFragment(Node& layout_node, int start, int length, const Gfx::FloatPoint& offset, const Gfx::FloatSize& size, Type type) + LineBoxFragment(Node const& layout_node, int start, int length, const Gfx::FloatPoint& offset, const Gfx::FloatSize& size, Type type) : m_layout_node(layout_node) , m_start(start) , m_length(length) @@ -33,7 +33,7 @@ public: { } - Node& layout_node() const { return m_layout_node; } + Node const& layout_node() const { return m_layout_node; } int start() const { return m_start; } int length() const { return m_length; } const Gfx::FloatRect absolute_rect() const; @@ -61,7 +61,7 @@ public: Gfx::FloatRect selection_rect(const Gfx::Font&) const; private: - Node& m_layout_node; + Node const& m_layout_node; int m_start { 0 }; int m_length { 0 }; Gfx::FloatPoint m_offset; diff --git a/Userland/Libraries/LibWeb/Layout/LineBuilder.cpp b/Userland/Libraries/LibWeb/Layout/LineBuilder.cpp index 73884f4e6e..bf25487309 100644 --- a/Userland/Libraries/LibWeb/Layout/LineBuilder.cpp +++ b/Userland/Libraries/LibWeb/Layout/LineBuilder.cpp @@ -9,8 +9,10 @@ namespace Web::Layout { -LineBuilder::LineBuilder(InlineFormattingContext& context) +LineBuilder::LineBuilder(InlineFormattingContext& context, FormattingState& formatting_state) : m_context(context) + , m_formatting_state(formatting_state) + , m_containing_block_state(formatting_state.ensure(context.containing_block())) { begin_new_line(false); } @@ -24,7 +26,7 @@ LineBuilder::~LineBuilder() void LineBuilder::break_line() { update_last_line(); - m_context.containing_block().line_boxes().append(LineBox()); + m_containing_block_state.line_boxes.append(LineBox()); begin_new_line(true); } @@ -39,15 +41,24 @@ void LineBuilder::begin_new_line(bool increment_y) m_last_line_needs_update = true; } -void LineBuilder::append_box(Box& box, float leading_size, float trailing_size) +LineBox& LineBuilder::ensure_last_line_box() { - m_context.containing_block().ensure_last_line_box().add_fragment(box, 0, 0, leading_size, trailing_size, box.content_width(), box.content_height()); - m_max_height_on_current_line = max(m_max_height_on_current_line, box.content_height()); + auto& line_boxes = m_containing_block_state.line_boxes; + if (line_boxes.is_empty()) + line_boxes.append(LineBox {}); + return line_boxes.last(); } -void LineBuilder::append_text_chunk(TextNode& text_node, size_t offset_in_node, size_t length_in_node, float leading_size, float trailing_size, float content_width, float content_height) +void LineBuilder::append_box(Box const& box, float leading_size, float trailing_size) { - m_context.containing_block().ensure_last_line_box().add_fragment(text_node, offset_in_node, length_in_node, leading_size, trailing_size, content_width, content_height); + auto const& box_state = m_formatting_state.get(box); + ensure_last_line_box().add_fragment(box, 0, 0, leading_size, trailing_size, box_state.content_width, box_state.content_height); + m_max_height_on_current_line = max(m_max_height_on_current_line, box_state.content_height); +} + +void LineBuilder::append_text_chunk(TextNode const& text_node, size_t offset_in_node, size_t length_in_node, float leading_size, float trailing_size, float content_width, float content_height) +{ + ensure_last_line_box().add_fragment(text_node, offset_in_node, length_in_node, leading_size, trailing_size, content_width, content_height); m_max_height_on_current_line = max(m_max_height_on_current_line, content_height); } @@ -59,26 +70,27 @@ bool LineBuilder::should_break(LayoutMode layout_mode, float next_item_width, bo return true; if (layout_mode == LayoutMode::OnlyRequiredLineBreaks) return false; - auto const& line_boxes = m_context.containing_block().line_boxes(); + auto const& line_boxes = m_containing_block_state.line_boxes; if (line_boxes.is_empty() || line_boxes.last().is_empty()) return false; - auto current_line_width = m_context.containing_block().line_boxes().last().width(); + auto current_line_width = line_boxes.last().width(); return (current_line_width + next_item_width) > m_available_width_for_current_line; } void LineBuilder::update_last_line() { m_last_line_needs_update = false; + auto& line_boxes = m_containing_block_state.line_boxes; - if (m_context.containing_block().line_boxes().is_empty()) + if (line_boxes.is_empty()) return; - auto& line_box = m_context.containing_block().line_boxes().last(); + auto& line_box = line_boxes.last(); auto text_align = m_context.containing_block().computed_values().text_align(); float x_offset = m_context.available_space_for_line(m_current_y).left; - float excess_horizontal_space = m_context.containing_block().content_width() - line_box.width(); + float excess_horizontal_space = m_containing_block_state.content_width - line_box.width(); switch (text_align) { case CSS::TextAlign::Center: @@ -115,7 +127,7 @@ void LineBuilder::update_last_line() for (auto& fragment : line_box.fragments()) { float fragment_baseline; if (fragment.layout_node().is_box()) { - fragment_baseline = static_cast<Box const&>(fragment.layout_node()).content_height(); + fragment_baseline = m_formatting_state.get(static_cast<Box const&>(fragment.layout_node())).content_height; } else { float font_baseline = fragment.layout_node().font().baseline(); fragment_baseline = (max_height / 2.0f) + (font_baseline / 2.0f); @@ -155,11 +167,10 @@ void LineBuilder::update_last_line() void LineBuilder::remove_last_line_if_empty() { // If there's an empty line box at the bottom, just remove it instead of giving it height. - auto& line_boxes = m_context.containing_block().line_boxes(); + auto& line_boxes = m_containing_block_state.line_boxes; if (!line_boxes.is_empty() && line_boxes.last().fragments().is_empty()) { line_boxes.take_last(); m_last_line_needs_update = false; } } - } diff --git a/Userland/Libraries/LibWeb/Layout/LineBuilder.h b/Userland/Libraries/LibWeb/Layout/LineBuilder.h index 9ccb0ed233..b4bf74c868 100644 --- a/Userland/Libraries/LibWeb/Layout/LineBuilder.h +++ b/Userland/Libraries/LibWeb/Layout/LineBuilder.h @@ -15,12 +15,12 @@ class LineBuilder { AK_MAKE_NONMOVABLE(LineBuilder); public: - explicit LineBuilder(InlineFormattingContext&); + LineBuilder(InlineFormattingContext&, FormattingState&); ~LineBuilder(); void break_line(); - void append_box(Box&, float leading_size, float trailing_size); - void append_text_chunk(TextNode&, size_t offset_in_node, size_t length_in_node, float leading_size, float trailing_size, float content_width, float content_height); + void append_box(Box const&, float leading_size, float trailing_size); + void append_text_chunk(TextNode const&, size_t offset_in_node, size_t length_in_node, float leading_size, float trailing_size, float content_width, float content_height); void break_if_needed(LayoutMode layout_mode, float next_item_width, bool should_force_break) { @@ -39,7 +39,11 @@ private: bool should_break(LayoutMode, float next_item_width, bool should_force_break); + LineBox& ensure_last_line_box(); + InlineFormattingContext& m_context; + FormattingState& m_formatting_state; + FormattingState::NodeState& m_containing_block_state; float m_available_width_for_current_line { 0 }; float m_current_y { 0 }; float m_max_height_on_current_line { 0 }; diff --git a/Userland/Libraries/LibWeb/Layout/Node.h b/Userland/Libraries/LibWeb/Layout/Node.h index cb9e645456..9d88ebc79c 100644 --- a/Userland/Libraries/LibWeb/Layout/Node.h +++ b/Userland/Libraries/LibWeb/Layout/Node.h @@ -258,6 +258,9 @@ public: bool has_definite_height() const { return m_has_definite_height; } bool has_definite_width() const { return m_has_definite_width; } + void set_has_definite_height(bool b) { m_has_definite_height = b; } + void set_has_definite_width(bool b) { m_has_definite_width = b; } + protected: NodeWithStyle(DOM::Document&, DOM::Node*, NonnullRefPtr<CSS::StyleProperties>); NodeWithStyle(DOM::Document&, DOM::Node*, CSS::ComputedValues); diff --git a/Userland/Libraries/LibWeb/Layout/SVGFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/SVGFormattingContext.cpp index ecb46df05d..2c4624c6f3 100644 --- a/Userland/Libraries/LibWeb/Layout/SVGFormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/SVGFormattingContext.cpp @@ -11,7 +11,7 @@ namespace Web::Layout { -SVGFormattingContext::SVGFormattingContext(FormattingState& state, Box& box, FormattingContext* parent) +SVGFormattingContext::SVGFormattingContext(FormattingState& state, Box const& box, FormattingContext* parent) : FormattingContext(Type::SVG, state, box, parent) { } @@ -20,20 +20,22 @@ SVGFormattingContext::~SVGFormattingContext() { } -void SVGFormattingContext::run(Box& box, LayoutMode) +void SVGFormattingContext::run(Box const& box, LayoutMode) { - box.for_each_in_subtree_of_type<SVGBox>([&](auto& descendant) { + box.for_each_in_subtree_of_type<SVGBox>([&](auto const& descendant) { if (is<SVGGeometryBox>(descendant)) { - auto& geometry_box = static_cast<SVGGeometryBox&>(descendant); - auto& path = geometry_box.dom_node().get_path(); + auto const& geometry_box = static_cast<SVGGeometryBox const&>(descendant); + auto& path = const_cast<SVGGeometryBox&>(geometry_box).dom_node().get_path(); auto bounding_box = path.bounding_box(); // Stroke increases the path's size by stroke_width/2 per side. auto stroke_width = geometry_box.dom_node().stroke_width().value_or(0); bounding_box.inflate(stroke_width, stroke_width); - geometry_box.set_offset(bounding_box.top_left()); - geometry_box.set_content_size(bounding_box.size()); + auto& geometry_box_state = m_state.ensure(geometry_box); + geometry_box_state.offset = bounding_box.top_left(); + geometry_box_state.content_width = bounding_box.width(); + geometry_box_state.content_height = bounding_box.height(); } return IterationDecision::Continue; diff --git a/Userland/Libraries/LibWeb/Layout/SVGFormattingContext.h b/Userland/Libraries/LibWeb/Layout/SVGFormattingContext.h index 640b401233..53c943c784 100644 --- a/Userland/Libraries/LibWeb/Layout/SVGFormattingContext.h +++ b/Userland/Libraries/LibWeb/Layout/SVGFormattingContext.h @@ -13,10 +13,10 @@ namespace Web::Layout { class SVGFormattingContext : public FormattingContext { public: - explicit SVGFormattingContext(FormattingState&, Box&, FormattingContext* parent); + explicit SVGFormattingContext(FormattingState&, Box const&, FormattingContext* parent); ~SVGFormattingContext(); - virtual void run(Box&, LayoutMode) override; + virtual void run(Box const&, LayoutMode) override; }; } diff --git a/Userland/Libraries/LibWeb/Layout/TableFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/TableFormattingContext.cpp index 0b1d28e394..d7aad2777a 100644 --- a/Userland/Libraries/LibWeb/Layout/TableFormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/TableFormattingContext.cpp @@ -16,7 +16,7 @@ namespace Web::Layout { -TableFormattingContext::TableFormattingContext(FormattingState& state, BlockContainer& block_container, FormattingContext* parent) +TableFormattingContext::TableFormattingContext(FormattingState& state, BlockContainer const& block_container, FormattingContext* parent) : BlockFormattingContext(state, block_container, parent) { } @@ -25,14 +25,18 @@ TableFormattingContext::~TableFormattingContext() { } -void TableFormattingContext::run(Box& box, LayoutMode) +void TableFormattingContext::run(Box const& box, LayoutMode) { + auto& box_state = m_state.ensure(box); + compute_width(box); float total_content_width = 0; float total_content_height = 0; box.for_each_child_of_type<TableRowGroupBox>([&](auto& row_group_box) { + auto& row_group_box_state = m_state.ensure(row_group_box); + compute_width(row_group_box); auto column_count = row_group_box.column_count(); Vector<float> column_widths; @@ -46,47 +50,51 @@ void TableFormattingContext::run(Box& box, LayoutMode) float content_height = 0; row_group_box.template for_each_child_of_type<TableRowBox>([&](auto& row) { - row.set_offset(0, content_height); + auto& row_state = m_state.ensure(row); + row_state.offset = { 0, content_height }; layout_row(row, column_widths); - content_width = max(content_width, row.content_width()); - content_height += row.content_height(); + content_width = max(content_width, row_state.content_width); + content_height += row_state.content_height; }); if (row_group_box.computed_values().width().has_value() && row_group_box.computed_values().width()->is_length() && row_group_box.computed_values().width()->length().is_auto()) - row_group_box.set_content_width(content_width); - row_group_box.set_content_height(content_height); + row_group_box_state.content_width = content_width; + row_group_box_state.content_height = content_height; - row_group_box.set_offset(0, total_content_height); + row_group_box_state.offset = { 0, total_content_height }; total_content_height += content_height; - total_content_width = max(total_content_width, row_group_box.content_width()); + total_content_width = max(total_content_width, row_group_box_state.content_width); }); if (box.computed_values().width().has_value() && box.computed_values().width()->is_length() && box.computed_values().width()->length().is_auto()) - box.set_content_width(total_content_width); + box_state.content_width = total_content_width; // FIXME: This is a total hack, we should respect the 'height' property. - box.set_content_height(total_content_height); + box_state.content_height = total_content_height; } -void TableFormattingContext::calculate_column_widths(Box& row, Vector<float>& column_widths) +void TableFormattingContext::calculate_column_widths(Box const& row, Vector<float>& column_widths) { + m_state.ensure(row); size_t column_index = 0; auto* table = row.first_ancestor_of_type<TableBox>(); bool use_auto_layout = !table || (!table->computed_values().width().has_value() || (table->computed_values().width()->is_length() && table->computed_values().width()->length().is_auto())); row.for_each_child_of_type<TableCellBox>([&](auto& cell) { + auto& cell_state = m_state.ensure(cell); compute_width(cell); if (use_auto_layout) { (void)layout_inside(cell, LayoutMode::OnlyRequiredLineBreaks); } else { (void)layout_inside(cell, LayoutMode::Default); } - column_widths[column_index] = max(column_widths[column_index], cell.content_width()); + column_widths[column_index] = max(column_widths[column_index], cell_state.content_width); column_index += cell.colspan(); }); } -void TableFormattingContext::layout_row(Box& row, Vector<float>& column_widths) +void TableFormattingContext::layout_row(Box const& row, Vector<float>& column_widths) { + auto& row_state = m_state.ensure(row); size_t column_index = 0; float tallest_cell_height = 0; float content_width = 0; @@ -94,7 +102,8 @@ void TableFormattingContext::layout_row(Box& row, Vector<float>& column_widths) bool use_auto_layout = !table || (!table->computed_values().width().has_value() || (table->computed_values().width()->is_length() && table->computed_values().width()->length().is_auto())); row.for_each_child_of_type<TableCellBox>([&](auto& cell) { - cell.set_offset(row.effective_offset().translated(content_width, 0)); + auto& cell_state = m_state.ensure(cell); + cell_state.offset = row_state.offset.translated(content_width, 0); // Layout the cell contents a second time, now that we know its final width. if (use_auto_layout) { @@ -106,16 +115,17 @@ void TableFormattingContext::layout_row(Box& row, Vector<float>& column_widths) size_t cell_colspan = cell.colspan(); for (size_t i = 0; i < cell_colspan; ++i) content_width += column_widths[column_index++]; - tallest_cell_height = max(tallest_cell_height, cell.content_height()); + tallest_cell_height = max(tallest_cell_height, cell_state.content_height); }); if (use_auto_layout) { - row.set_content_width(content_width); + row_state.content_width = content_width; } else { - row.set_content_width(table->content_width()); + auto& table_state = m_state.ensure(*table); + row_state.content_width = table_state.content_width; } - row.set_content_height(tallest_cell_height); + row_state.content_height = tallest_cell_height; } } diff --git a/Userland/Libraries/LibWeb/Layout/TableFormattingContext.h b/Userland/Libraries/LibWeb/Layout/TableFormattingContext.h index bf9576001c..39955ad0b9 100644 --- a/Userland/Libraries/LibWeb/Layout/TableFormattingContext.h +++ b/Userland/Libraries/LibWeb/Layout/TableFormattingContext.h @@ -13,14 +13,14 @@ namespace Web::Layout { class TableFormattingContext final : public BlockFormattingContext { public: - explicit TableFormattingContext(FormattingState&, BlockContainer&, FormattingContext* parent); + explicit TableFormattingContext(FormattingState&, BlockContainer const&, FormattingContext* parent); ~TableFormattingContext(); - virtual void run(Box&, LayoutMode) override; + virtual void run(Box const&, LayoutMode) override; private: - void calculate_column_widths(Box& row, Vector<float>& column_widths); - void layout_row(Box& row, Vector<float>& column_widths); + void calculate_column_widths(Box const& row, Vector<float>& column_widths); + void layout_row(Box const& row, Vector<float>& column_widths); }; } |