diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index d16ce775e2e..946413fdadf 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 5c30e9da923..59617f0199b 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(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()); + + 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 6cd636fa5a7..f58214fc5b5 100644 --- a/Userland/Libraries/LibWeb/Layout/BlockContainer.cpp +++ b/Userland/Libraries/LibWeb/Layout/BlockContainer.cpp @@ -130,17 +130,4 @@ bool BlockContainer::handle_mousewheel(Badge, 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 8c68f140820..d05874b81e2 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& line_boxes() { return m_line_boxes; } const Vector& line_boxes() const { return m_line_boxes; } - LineBox& ensure_last_line_box(); - LineBox& add_line_box(); + void set_line_boxes(Vector&& line_boxes) { m_line_boxes = move(line_boxes); } protected: Vector m_line_boxes; diff --git a/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.cpp index a5d7ebfa845..1105aaec007 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(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([&](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(box)) { // FIXME: This should not be done *by* ReplacedBox auto& replaced = verify_cast(box); - replaced.prepare_for_replaced_layout(); + // FIXME: This const_cast is gross. + const_cast(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 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(box)) { - height = compute_height_for_replaced_element(verify_cast(box)); + height = compute_height_for_replaced_element(state, verify_cast(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. + auto& box_state = state.ensure(box); + // 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_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); - 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); + 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 height = compute_theoretical_height(box); - box.set_content_height(height); + box_state.content_height = compute_theoretical_height(state, box); } -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. - - 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); - - 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; - } -} - -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& 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 independent_formatting_context; @@ -425,10 +398,9 @@ void BlockFormattingContext::layout_block_level_children(BlockContainer& block_c layout_block_level_children(verify_cast(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(child_box) || is(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(child_box)) verify_cast(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()); + 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_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); + 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(); 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& child) { - auto child_rect = child.absolute_border_box_rect(); + icb.for_each_in_subtree_of_type([&](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(icb).ensure_overflow_data(); overflow_data.scrollable_overflow_rect = viewport_rect.to_type(); // 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(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 05b584fd0b9..ef8371969b6 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(context_box()); } BlockContainer const& root() const { return static_cast(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 boxes; + Vector boxes; float y_offset { 0 }; }; FloatSideData m_left_floats; FloatSideData m_right_floats; - Vector m_absolutely_positioned_boxes; + Vector 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 8db420581c2..a46fff8bbd7 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(*current)) { - auto offset = static_cast(*current).effective_offset(); - rect.translate_by(offset); - } - } - return rect; - } - protected: Box(DOM::Document& document, DOM::Node* node, NonnullRefPtr style) : NodeWithStyleAndBoxModelMetrics(document, node, move(style)) diff --git a/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.cpp index 72a018baa32..9b5316ab440 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 const& length_percentage) +static float get_pixel_size(FormattingState const& state, Box const& box, Optional 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 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::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::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(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 111a146ccc3..7e5e994eb4e 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 m_flex_lines; Vector 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 68fc5c4a4ce..4a2cfd84be7 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::create_independent_formatting_context_if_needed(Box& child_box) +OwnPtr FormattingContext::create_independent_formatting_context_if_needed(Box const& child_box) { if (!child_box.can_have_children()) return {}; @@ -105,7 +105,7 @@ OwnPtr FormattingContext::create_independent_formatting_conte return {}; } -OwnPtr FormattingContext::layout_inside(Box& child_box, LayoutMode layout_mode) +OwnPtr FormattingContext::layout_inside(Box const& child_box, LayoutMode layout_mode) { if (!child_box.can_have_children()) return {}; @@ -119,44 +119,45 @@ OwnPtr 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(box).line_boxes()) { + for (auto& child : state.get(verify_cast(box)).line_boxes) { max_width = max(max_width, child.width()); } } else { box.for_each_child_of_type([&](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 top; Optional 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(box); + auto const& block_container = verify_cast(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(box)) compute_width_for_absolutely_positioned_replaced_element(verify_cast(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(box)) compute_height_for_absolutely_positioned_replaced_element(verify_cast(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(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 13e4f25fa96..bdcc19f22bb 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 create_independent_formatting_context_if_needed(Box& child_box); + OwnPtr 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 layout_inside(Box&, LayoutMode); + OwnPtr 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 00000000000..f293c07e1b9 --- /dev/null +++ b/Userland/Libraries/LibWeb/Layout/FormattingState.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2022, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace Web::Layout { + +void FormattingState::commit() +{ + for (auto& it : nodes) { + auto& node = const_cast(*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(node)) { + auto& box = static_cast(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(node)) { + auto& block_container = static_cast(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(*current)) { + auto const& current_state = state.get(static_cast(*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 f90ffa29f9c..23956ee8b0f 100644 --- a/Userland/Libraries/LibWeb/Layout/FormattingState.h +++ b/Userland/Libraries/LibWeb/Layout/FormattingState.h @@ -6,9 +6,74 @@ #pragma once +#include +#include +#include +#include + 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 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 const& get(NodeWithStyleAndBoxModelMetrics const& box) const + { + if (!nodes.contains(&box)) + return const_cast(*this).ensure(box); + return *nodes.get(&box).value(); + } + + HashMap> 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 803b8bf5964..5cc766239e5 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(box)) { auto& replaced = verify_cast(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(verify_cast(box)); + auto const& inline_block = verify_cast(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 8f683621036..c449bdd98ed 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(context_box()); } BlockContainer const& containing_block() const { return static_cast(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 13c14f75ff7..5c63e7ba529 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(*m_next_node)) - enter_node_with_box_model_metrics(static_cast(*m_next_node)); + enter_node_with_box_model_metrics(static_cast(*m_next_node)); m_current_node = m_next_node; compute_next(); @@ -103,10 +113,11 @@ Optional InlineLevelIterator::next(float available_wi return {}; if (is(*m_current_node)) { - auto& text_node = static_cast(*m_current_node); + auto& text_node = static_cast(*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::next(float available_wi } if (is(*m_current_node)) { - auto& replaced_box = static_cast(*m_current_node); - replaced_box.prepare_for_replaced_layout(); + auto& replaced_box = static_cast(*m_current_node); + // FIXME: This const_cast is gross. + const_cast(replaced_box).prepare_for_replaced_layout(); } auto& box = verify_cast(*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::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(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 b7039c7db5a..bfc29421c76 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 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 m_extra_leading_metrics; Optional m_extra_trailing_metrics; - Vector m_box_model_node_stack; + Vector m_box_model_node_stack; }; } diff --git a/Userland/Libraries/LibWeb/Layout/LineBox.cpp b/Userland/Libraries/LibWeb/Layout/LineBox.cpp index cbec544844f..f22b0beea90 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(layout_node)) - verify_cast(layout_node).set_containing_line_box_fragment(m_fragments.last()); + if (is(layout_node)) { + // FIXME: Move this to FormattingContext! + const_cast(static_cast(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 e0503aacf25..76cf5442fc1 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& fragments() const { return m_fragments; } NonnullOwnPtrVector& fragments() { return m_fragments; } diff --git a/Userland/Libraries/LibWeb/Layout/LineBoxFragment.h b/Userland/Libraries/LibWeb/Layout/LineBoxFragment.h index 3ada26382c0..6d9d72338ee 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 73884f4e6e2..bf254873095 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(fragment.layout_node()).content_height(); + fragment_baseline = m_formatting_state.get(static_cast(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 9ccb0ed233a..b4bf74c8684 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 cb9e645456e..9d88ebc79c1 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); NodeWithStyle(DOM::Document&, DOM::Node*, CSS::ComputedValues); diff --git a/Userland/Libraries/LibWeb/Layout/SVGFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/SVGFormattingContext.cpp index ecb46df05d3..2c4624c6f3b 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([&](auto& descendant) { + box.for_each_in_subtree_of_type([&](auto const& descendant) { if (is(descendant)) { - auto& geometry_box = static_cast(descendant); - auto& path = geometry_box.dom_node().get_path(); + auto const& geometry_box = static_cast(descendant); + auto& path = const_cast(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 640b401233a..53c943c784c 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 0b1d28e394f..d7aad2777a3 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([&](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 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([&](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& column_widths) +void TableFormattingContext::calculate_column_widths(Box const& row, Vector& column_widths) { + m_state.ensure(row); size_t column_index = 0; auto* table = row.first_ancestor_of_type(); 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([&](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& column_widths) +void TableFormattingContext::layout_row(Box const& row, Vector& 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& 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([&](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& 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 bf9576001c1..39955ad0b92 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& column_widths); - void layout_row(Box& row, Vector& column_widths); + void calculate_column_widths(Box const& row, Vector& column_widths); + void layout_row(Box const& row, Vector& column_widths); }; }