diff options
Diffstat (limited to 'Libraries/LibWeb/Layout')
43 files changed, 1338 insertions, 880 deletions
diff --git a/Libraries/LibWeb/Layout/BlockFormattingContext.cpp b/Libraries/LibWeb/Layout/BlockFormattingContext.cpp new file mode 100644 index 0000000000..3c22fc1d52 --- /dev/null +++ b/Libraries/LibWeb/Layout/BlockFormattingContext.cpp @@ -0,0 +1,632 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <LibWeb/CSS/Length.h> +#include <LibWeb/DOM/Node.h> +#include <LibWeb/Layout/BlockFormattingContext.h> +#include <LibWeb/Layout/InlineFormattingContext.h> +#include <LibWeb/Layout/LayoutBlock.h> +#include <LibWeb/Layout/LayoutBox.h> +#include <LibWeb/Layout/LayoutDocument.h> +#include <LibWeb/Layout/LayoutListItem.h> +#include <LibWeb/Layout/LayoutWidget.h> +#include <LibWeb/Page/Frame.h> + +namespace Web::Layout { + +BlockFormattingContext::BlockFormattingContext(LayoutBox& context_box) + : FormattingContext(context_box) +{ +} + +BlockFormattingContext::~BlockFormattingContext() +{ +} + +bool BlockFormattingContext::is_initial() const +{ + return context_box().is_root(); +} + +void BlockFormattingContext::run(LayoutMode layout_mode) +{ + if (is_initial()) { + layout_initial_containing_block(layout_mode); + return; + } + + // FIXME: BFC currently computes the width+height of the context box. + // This is necessary to be able to place absolutely positioned descendants. + // The same work is also done by the parent BFC for each of its blocks.. + + if (layout_mode == LayoutMode::Default) + compute_width(context_box()); + + if (context_box().children_are_inline()) { + layout_inline_children(layout_mode); + } else { + layout_block_level_children(layout_mode); + } + + if (layout_mode == LayoutMode::Default) + compute_height(context_box()); + + // No need to layout absolute positioned boxes during shrink-to-fit layouts. + if (layout_mode == LayoutMode::Default) + layout_absolutely_positioned_descendants(); +} + +void BlockFormattingContext::compute_width(LayoutBox& box) +{ + if (box.is_replaced()) { + // FIXME: This should not be done *by* LayoutReplaced + auto& replaced = downcast<LayoutReplaced>(box); + replaced.prepare_for_replaced_layout(); + auto width = replaced.calculate_width(); + replaced.set_width(width); + return; + } + + if (box.is_absolutely_positioned()) { + compute_width_for_absolutely_positioned_block(box); + return; + } + + auto& style = box.style(); + float width_of_containing_block = box.width_of_logical_containing_block(); + + auto zero_value = CSS::Length::make_px(0); + + auto margin_left = CSS::Length::make_auto(); + auto margin_right = CSS::Length::make_auto(); + const auto padding_left = style.padding().left.resolved_or_zero(box, width_of_containing_block); + const auto padding_right = style.padding().right.resolved_or_zero(box, width_of_containing_block); + + auto try_compute_width = [&](const auto& a_width) { + CSS::Length width = a_width; + margin_left = style.margin().left.resolved_or_zero(box, width_of_containing_block); + margin_right = style.margin().right.resolved_or_zero(box, width_of_containing_block); + + float total_px = style.border_left().width + style.border_right().width; + for (auto& value : { margin_left, padding_left, width, padding_right, margin_right }) { + total_px += value.to_px(box); + } + + if (!box.is_replaced() && !box.is_inline()) { + // 10.3.3 Block-level, non-replaced elements in normal flow + // If 'width' is not 'auto' and 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width' (plus any of 'margin-left' or 'margin-right' that are not 'auto') is larger than the width of the containing block, then any 'auto' values for 'margin-left' or 'margin-right' are, for the following rules, treated as zero. + if (width.is_auto() && total_px > width_of_containing_block) { + if (margin_left.is_auto()) + margin_left = zero_value; + if (margin_right.is_auto()) + margin_right = zero_value; + } + + // 10.3.3 cont'd. + auto underflow_px = width_of_containing_block - total_px; + + if (width.is_auto()) { + if (margin_left.is_auto()) + margin_left = zero_value; + if (margin_right.is_auto()) + margin_right = zero_value; + if (underflow_px >= 0) { + width = CSS::Length(underflow_px, CSS::Length::Type::Px); + } else { + width = zero_value; + margin_right = CSS::Length(margin_right.to_px(box) + underflow_px, CSS::Length::Type::Px); + } + } else { + if (!margin_left.is_auto() && !margin_right.is_auto()) { + margin_right = CSS::Length(margin_right.to_px(box) + underflow_px, CSS::Length::Type::Px); + } else if (!margin_left.is_auto() && margin_right.is_auto()) { + margin_right = CSS::Length(underflow_px, CSS::Length::Type::Px); + } else if (margin_left.is_auto() && !margin_right.is_auto()) { + margin_left = CSS::Length(underflow_px, CSS::Length::Type::Px); + } else { // margin_left.is_auto() && margin_right.is_auto() + auto half_of_the_underflow = CSS::Length(underflow_px / 2, CSS::Length::Type::Px); + margin_left = half_of_the_underflow; + margin_right = half_of_the_underflow; + } + } + } else if (!box.is_replaced() && box.is_inline_block()) { + + // 10.3.9 'Inline-block', non-replaced elements in normal flow + + // A computed value of 'auto' for 'margin-left' or 'margin-right' becomes a used value of '0'. + if (margin_left.is_auto()) + margin_left = zero_value; + if (margin_right.is_auto()) + margin_right = zero_value; + + // If 'width' is 'auto', the used value is the shrink-to-fit width as for floating elements. + if (width.is_auto()) { + + // Find the available width: in this case, this is the width of the containing + // block minus the used values of 'margin-left', 'border-left-width', 'padding-left', + // 'padding-right', 'border-right-width', 'margin-right', and the widths of any relevant scroll bars. + float available_width = width_of_containing_block + - margin_left.to_px(box) - style.border_left().width - padding_left.to_px(box) + - padding_right.to_px(box) - style.border_right().width - margin_right.to_px(box); + + auto result = calculate_shrink_to_fit_widths(box); + + // Then the shrink-to-fit width is: min(max(preferred minimum width, available width), preferred width). + width = CSS::Length(min(max(result.preferred_minimum_width, available_width), result.preferred_width), CSS::Length::Type::Px); + } + } + + return width; + }; + + auto specified_width = style.width().resolved_or_auto(box, width_of_containing_block); + + // 1. The tentative used width is calculated (without 'min-width' and 'max-width') + auto used_width = try_compute_width(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 = style.max_width().resolved_or_auto(box, width_of_containing_block); + if (!specified_max_width.is_auto()) { + if (used_width.to_px(box) > specified_max_width.to_px(box)) { + used_width = try_compute_width(specified_max_width); + } + } + + // 3. If the resulting width is smaller than 'min-width', the rules above are applied again, + // but this time using the value of 'min-width' as the computed value for 'width'. + auto specified_min_width = style.min_width().resolved_or_auto(box, width_of_containing_block); + if (!specified_min_width.is_auto()) { + if (used_width.to_px(box) < specified_min_width.to_px(box)) { + used_width = try_compute_width(specified_min_width); + } + } + + box.set_width(used_width.to_px(box)); + box.box_model().margin.left = margin_left; + box.box_model().margin.right = margin_right; + box.box_model().border.left = CSS::Length::make_px(style.border_left().width); + box.box_model().border.right = CSS::Length::make_px(style.border_right().width); + box.box_model().padding.left = padding_left; + box.box_model().padding.right = padding_right; +} + +void BlockFormattingContext::compute_width_for_absolutely_positioned_block(LayoutBox& box) +{ + auto& containing_block = context_box(); + auto& style = box.style(); + auto zero_value = CSS::Length::make_px(0); + + auto margin_left = CSS::Length::make_auto(); + auto margin_right = CSS::Length::make_auto(); + const auto border_left = style.border_left().width; + const auto border_right = style.border_right().width; + const auto padding_left = style.padding().left.resolved(zero_value, box, containing_block.width()); + const auto padding_right = style.padding().right.resolved(zero_value, box, containing_block.width()); + + auto try_compute_width = [&](const auto& a_width) { + margin_left = style.margin().left.resolved(zero_value, box, containing_block.width()); + margin_right = style.margin().right.resolved(zero_value, box, containing_block.width()); + + auto left = style.offset().left.resolved_or_auto(box, containing_block.width()); + auto right = style.offset().right.resolved_or_auto(box, containing_block.width()); + auto width = a_width; + + auto solve_for_left = [&] { + return CSS::Length(containing_block.width() - margin_left.to_px(box) - border_left - padding_left.to_px(box) - width.to_px(box) - padding_right.to_px(box) - border_right - margin_right.to_px(box) - right.to_px(box), CSS::Length::Type::Px); + }; + + auto solve_for_width = [&] { + return CSS::Length(containing_block.width() - left.to_px(box) - margin_left.to_px(box) - border_left - padding_left.to_px(box) - padding_right.to_px(box) - border_right - margin_right.to_px(box) - right.to_px(box), CSS::Length::Type::Px); + }; + + auto solve_for_right = [&] { + return CSS::Length(containing_block.width() - left.to_px(box) - margin_left.to_px(box) - border_left - padding_left.to_px(box) - width.to_px(box) - padding_right.to_px(box) - border_right - margin_right.to_px(box), CSS::Length::Type::Px); + }; + + // If all three of 'left', 'width', and 'right' are 'auto': + if (left.is_auto() && width.is_auto() && right.is_auto()) { + // First set any 'auto' values for 'margin-left' and 'margin-right' to 0. + if (margin_left.is_auto()) + margin_left = CSS::Length::make_px(0); + if (margin_right.is_auto()) + margin_right = CSS::Length::make_px(0); + // Then, if the 'direction' property of the element establishing the static-position containing block + // is 'ltr' set 'left' to the static position and apply rule number three below; + // otherwise, set 'right' to the static position and apply rule number one below. + // FIXME: This is very hackish. + left = CSS::Length::make_px(0); + goto Rule3; + } + + if (!left.is_auto() && !width.is_auto() && !right.is_auto()) { + // FIXME: This should be solved in a more complicated way. + return width; + } + + if (margin_left.is_auto()) + margin_left = CSS::Length::make_px(0); + if (margin_right.is_auto()) + margin_right = CSS::Length::make_px(0); + + // 1. 'left' and 'width' are 'auto' and 'right' is not 'auto', + // then the width is shrink-to-fit. Then solve for 'left' + if (left.is_auto() && width.is_auto() && !right.is_auto()) { + auto result = calculate_shrink_to_fit_widths(box); + solve_for_left(); + auto available_width = solve_for_width(); + width = CSS::Length(min(max(result.preferred_minimum_width, available_width.to_px(box)), result.preferred_width), CSS::Length::Type::Px); + } + + // 2. 'left' and 'right' are 'auto' and 'width' is not 'auto', + // then if the 'direction' property of the element establishing + // the static-position containing block is 'ltr' set 'left' + // to the static position, otherwise set 'right' to the static position. + // Then solve for 'left' (if 'direction is 'rtl') or 'right' (if 'direction' is 'ltr'). + else if (left.is_auto() && right.is_auto() && !width.is_auto()) { + // FIXME: Check direction + // FIXME: Use the static-position containing block + left = zero_value; + right = solve_for_right(); + } + + // 3. 'width' and 'right' are 'auto' and 'left' is not 'auto', + // then the width is shrink-to-fit. Then solve for 'right' + else if (width.is_auto() && right.is_auto() && !left.is_auto()) { + Rule3: + auto result = calculate_shrink_to_fit_widths(box); + right = solve_for_right(); + auto available_width = solve_for_width(); + width = CSS::Length(min(max(result.preferred_minimum_width, available_width.to_px(box)), result.preferred_width), CSS::Length::Type::Px); + } + + // 4. 'left' is 'auto', 'width' and 'right' are not 'auto', then solve for 'left' + else if (left.is_auto() && !width.is_auto() && !right.is_auto()) { + left = solve_for_left(); + } + + // 5. 'width' is 'auto', 'left' and 'right' are not 'auto', then solve for 'width' + else if (width.is_auto() && !left.is_auto() && !right.is_auto()) { + width = solve_for_width(); + } + + // 6. 'right' is 'auto', 'left' and 'width' are not 'auto', then solve for 'right' + else if (right.is_auto() && !left.is_auto() && !width.is_auto()) { + right = solve_for_right(); + } + + return width; + }; + + auto specified_width = style.width().resolved_or_auto(box, containing_block.width()); + + // 1. The tentative used width is calculated (without 'min-width' and 'max-width') + auto used_width = try_compute_width(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 = style.max_width().resolved_or_auto(box, containing_block.width()); + if (!specified_max_width.is_auto()) { + if (used_width.to_px(box) > specified_max_width.to_px(box)) { + used_width = try_compute_width(specified_max_width); + } + } + + // 3. If the resulting width is smaller than 'min-width', the rules above are applied again, + // but this time using the value of 'min-width' as the computed value for 'width'. + auto specified_min_width = style.min_width().resolved_or_auto(box, containing_block.width()); + if (!specified_min_width.is_auto()) { + if (used_width.to_px(box) < specified_min_width.to_px(box)) { + used_width = try_compute_width(specified_min_width); + } + } + + box.set_width(used_width.to_px(box)); + + box.box_model().margin.left = margin_left; + box.box_model().margin.right = margin_right; + box.box_model().border.left = CSS::Length::make_px(border_left); + box.box_model().border.right = CSS::Length::make_px(border_right); + box.box_model().padding.left = padding_left; + box.box_model().padding.right = padding_right; +} + +void BlockFormattingContext::compute_height(LayoutBox& box) +{ + if (box.is_replaced()) { + // FIXME: This should not be done *by* LayoutReplaced + auto height = downcast<LayoutReplaced>(box).calculate_height(); + box.set_height(height); + return; + } + + auto& style = box.style(); + auto& containing_block = context_box(); + + CSS::Length specified_height; + + if (style.height().is_percentage() && !containing_block.style().height().is_absolute()) { + specified_height = CSS::Length::make_auto(); + } else { + specified_height = style.height().resolved_or_auto(box, containing_block.height()); + } + + auto specified_max_height = style.max_height().resolved_or_auto(box, containing_block.height()); + + box.box_model().margin.top = style.margin().top.resolved_or_zero(box, containing_block.width()); + box.box_model().margin.bottom = style.margin().bottom.resolved_or_zero(box, containing_block.width()); + box.box_model().border.top = CSS::Length::make_px(style.border_top().width); + box.box_model().border.bottom = CSS::Length::make_px(style.border_bottom().width); + box.box_model().padding.top = style.padding().top.resolved_or_zero(box, containing_block.width()); + box.box_model().padding.bottom = style.padding().bottom.resolved_or_zero(box, containing_block.width()); + + if (!specified_height.is_auto()) { + float used_height = specified_height.to_px(box); + if (!specified_max_height.is_auto()) + used_height = min(used_height, specified_max_height.to_px(box)); + box.set_height(used_height); + } +} + +void BlockFormattingContext::layout_inline_children(LayoutMode layout_mode) +{ + InlineFormattingContext context(context_box()); + context.run(layout_mode); +} + +void BlockFormattingContext::layout_block_level_children(LayoutMode layout_mode) +{ + float content_height = 0; + float content_width = 0; + + context_box().for_each_in_subtree_of_type<LayoutBox>([&](auto& box) { + if (box.is_absolutely_positioned() || box.containing_block() != &context_box()) + return IterationDecision::Continue; + + compute_width(box); + layout_inside(box, layout_mode); + compute_height(box); + + if (box.is_replaced()) + place_block_level_replaced_element_in_normal_flow(box); + else if (box.is_block()) + place_block_level_non_replaced_element_in_normal_flow(box); + else + dbgln("FIXME: LayoutBlock::layout_contained_boxes doesn't know how to place a {}", box.class_name()); + + // FIXME: This should be factored differently. It's uncool that we mutate the tree *during* layout! + // Instead, we should generate the marker box during the tree build. + if (is<LayoutListItem>(box)) + downcast<LayoutListItem>(box).layout_marker(); + + content_height = max(content_height, box.effective_offset().y() + box.height() + box.box_model().margin_box(box).bottom); + content_width = max(content_width, downcast<LayoutBox>(box).width()); + return IterationDecision::Continue; + }); + + if (layout_mode != LayoutMode::Default) { + if (context_box().style().width().is_undefined() || context_box().style().width().is_auto()) + context_box().set_width(content_width); + } + + // FIXME: It's not right to always shrink-wrap the context box to the content here. + context_box().set_height(content_height); +} + +void BlockFormattingContext::place_block_level_replaced_element_in_normal_flow(LayoutBox& box) +{ + auto& containing_block = context_box(); + ASSERT(!containing_block.is_absolutely_positioned()); + auto& replaced_element_box_model = box.box_model(); + + replaced_element_box_model.margin.top = box.style().margin().top.resolved_or_zero(context_box(), containing_block.width()); + replaced_element_box_model.margin.bottom = box.style().margin().bottom.resolved_or_zero(context_box(), containing_block.width()); + replaced_element_box_model.border.top = CSS::Length::make_px(box.style().border_top().width); + replaced_element_box_model.border.bottom = CSS::Length::make_px(box.style().border_bottom().width); + replaced_element_box_model.padding.top = box.style().padding().top.resolved_or_zero(context_box(), containing_block.width()); + replaced_element_box_model.padding.bottom = box.style().padding().bottom.resolved_or_zero(context_box(), containing_block.width()); + + float x = replaced_element_box_model.margin.left.to_px(context_box()) + + replaced_element_box_model.border.left.to_px(context_box()) + + replaced_element_box_model.padding.left.to_px(context_box()) + + replaced_element_box_model.offset.left.to_px(context_box()); + + float y = replaced_element_box_model.margin_box(context_box()).top + context_box().box_model().offset.top.to_px(context_box()); + + box.set_offset(x, y); +} + +void BlockFormattingContext::place_block_level_non_replaced_element_in_normal_flow(LayoutBox& box) +{ + auto zero_value = CSS::Length::make_px(0); + auto& containing_block = context_box(); + auto& box_model = box.box_model(); + auto& style = box.style(); + + box_model.margin.top = style.margin().top.resolved(zero_value, containing_block, containing_block.width()); + box_model.margin.bottom = style.margin().bottom.resolved(zero_value, containing_block, containing_block.width()); + box_model.border.top = CSS::Length::make_px(style.border_top().width); + box_model.border.bottom = CSS::Length::make_px(style.border_bottom().width); + box_model.padding.top = style.padding().top.resolved(zero_value, containing_block, containing_block.width()); + box_model.padding.bottom = style.padding().bottom.resolved(zero_value, containing_block, containing_block.width()); + + float x = box_model.margin.left.to_px(containing_block) + + box_model.border.left.to_px(containing_block) + + box_model.padding.left.to_px(containing_block) + + box_model.offset.left.to_px(containing_block); + + if (containing_block.style().text_align() == CSS::TextAlign::VendorSpecificCenter) { + x = (containing_block.width() / 2) - box.width() / 2; + } + + float y = box_model.margin_box(containing_block).top + + box_model.offset.top.to_px(containing_block); + + // NOTE: Empty (0-height) preceding siblings have their margins collapsed with *their* preceding sibling, etc. + float collapsed_bottom_margin_of_preceding_siblings = 0; + + auto* relevant_sibling = box.previous_sibling_of_type<LayoutBlock>(); + 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.to_px(*relevant_sibling)); + if (relevant_sibling->height() > 0) + break; + } + relevant_sibling = relevant_sibling->previous_sibling(); + } + + if (relevant_sibling) { + y += relevant_sibling->effective_offset().y() + relevant_sibling->height() + relevant_sibling->box_model().padding.bottom.to_px(*relevant_sibling); + + // Collapse top margin with bottom margin of preceding siblings if needed + float my_margin_top = box_model.margin.top.to_px(containing_block); + + if (my_margin_top < 0 || collapsed_bottom_margin_of_preceding_siblings < 0) { + // Negative margins present. + float largest_negative_margin = -min(my_margin_top, collapsed_bottom_margin_of_preceding_siblings); + float largest_positive_margin = (my_margin_top < 0 && collapsed_bottom_margin_of_preceding_siblings < 0) ? 0 : max(my_margin_top, collapsed_bottom_margin_of_preceding_siblings); + float final_margin = largest_positive_margin - largest_negative_margin; + y += final_margin - my_margin_top; + } else if (collapsed_bottom_margin_of_preceding_siblings > my_margin_top) { + // Sibling's margin is larger than mine, adjust so we use sibling's. + y += collapsed_bottom_margin_of_preceding_siblings - my_margin_top; + } + } + + box.set_offset(x, y); +} + +void BlockFormattingContext::layout_initial_containing_block(LayoutMode layout_mode) +{ + auto viewport_rect = context_box().frame().viewport_rect(); + + auto& icb = downcast<LayoutDocument>(context_box()); + icb.build_stacking_context_tree(); + + icb.set_width(viewport_rect.width()); + + layout_block_level_children(layout_mode); + + ASSERT(!icb.children_are_inline()); + + // FIXME: The ICB should have the height of the viewport. + // Instead of auto-sizing the ICB, we should spill into overflow. + float lowest_bottom = 0; + icb.for_each_child_of_type<LayoutBox>([&](auto& child) { + lowest_bottom = max(lowest_bottom, child.absolute_rect().bottom()); + }); + icb.set_height(lowest_bottom); + + // No need to layout absolute positioned boxes during shrink-to-fit layouts. + if (layout_mode == LayoutMode::Default) + layout_absolutely_positioned_descendants(); + + // FIXME: This is a total hack. Make sure any GUI::Widgets are moved into place after layout. + // We should stop embedding GUI::Widgets entirely, since that won't work out-of-process. + icb.for_each_in_subtree_of_type<LayoutWidget>([&](auto& widget) { + widget.update_widget(); + return IterationDecision::Continue; + }); +} + +void BlockFormattingContext::layout_absolutely_positioned_descendants() +{ + context_box().for_each_in_subtree_of_type<LayoutBox>([&](auto& box) { + if (box.is_absolutely_positioned() && box.containing_block() == &context_box()) { + layout_absolutely_positioned_descendant(box); + } + return IterationDecision::Continue; + }); +} + +void BlockFormattingContext::layout_absolutely_positioned_descendant(LayoutBox& box) +{ + auto& containing_block = context_box(); + auto& box_model = box.box_model(); + auto zero_value = CSS::Length::make_px(0); + + auto specified_width = box.style().width().resolved_or_auto(box, containing_block.width()); + + compute_width(box); + layout_inside(box, LayoutMode::Default); + compute_height(box); + + box_model.margin.left = box.style().margin().left.resolved_or_auto(box, containing_block.width()); + box_model.margin.top = box.style().margin().top.resolved_or_auto(box, containing_block.height()); + box_model.margin.right = box.style().margin().right.resolved_or_auto(box, containing_block.width()); + box_model.margin.bottom = box.style().margin().bottom.resolved_or_auto(box, containing_block.height()); + + box_model.border.left = CSS::Length::make_px(box.style().border_left().width); + box_model.border.right = CSS::Length::make_px(box.style().border_right().width); + box_model.border.top = CSS::Length::make_px(box.style().border_top().width); + box_model.border.bottom = CSS::Length::make_px(box.style().border_bottom().width); + + box_model.offset.left = box.style().offset().left.resolved_or_auto(box, containing_block.width()); + box_model.offset.top = box.style().offset().top.resolved_or_auto(box, containing_block.height()); + box_model.offset.right = box.style().offset().right.resolved_or_auto(box, containing_block.width()); + box_model.offset.bottom = box.style().offset().bottom.resolved_or_auto(box, containing_block.height()); + + if (box_model.offset.left.is_auto() && specified_width.is_auto() && box_model.offset.right.is_auto()) { + if (box_model.margin.left.is_auto()) + box_model.margin.left = zero_value; + if (box_model.margin.right.is_auto()) + box_model.margin.right = zero_value; + } + + Gfx::FloatPoint used_offset; + + if (!box_model.offset.left.is_auto()) { + float x_offset = box_model.offset.left.to_px(box) + + box_model.border_box(box).left; + used_offset.set_x(x_offset + box_model.margin.left.to_px(box)); + } else if (!box_model.offset.right.is_auto()) { + float x_offset = 0 + - box_model.offset.right.to_px(box) + - box_model.border_box(box).right; + used_offset.set_x(containing_block.width() + x_offset - box.width() - box_model.margin.right.to_px(box)); + } else { + float x_offset = box_model.margin_box(box).left; + used_offset.set_x(x_offset); + } + + if (!box_model.offset.top.is_auto()) { + float y_offset = box_model.offset.top.to_px(box) + + box_model.border_box(box).top; + used_offset.set_y(y_offset + box_model.margin.top.to_px(box)); + } else if (!box_model.offset.bottom.is_auto()) { + float y_offset = 0 + - box_model.offset.bottom.to_px(box) + - box_model.border_box(box).bottom; + used_offset.set_y(containing_block.height() + y_offset - box.height() - box_model.margin.bottom.to_px(box)); + } else { + float y_offset = box_model.margin_box(box).top; + used_offset.set_y(y_offset); + } + + box.set_offset(used_offset); +} + +} diff --git a/Libraries/LibWeb/Layout/BlockFormattingContext.h b/Libraries/LibWeb/Layout/BlockFormattingContext.h new file mode 100644 index 0000000000..88b2773ecd --- /dev/null +++ b/Libraries/LibWeb/Layout/BlockFormattingContext.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include <LibWeb/Forward.h> +#include <LibWeb/Layout/FormattingContext.h> + +namespace Web::Layout { + +class BlockFormattingContext : public FormattingContext { +public: + explicit BlockFormattingContext(LayoutBox& containing_block); + ~BlockFormattingContext(); + + virtual void run(LayoutMode) override; + + bool is_initial() const; + +protected: + void compute_width(LayoutBox&); + void compute_height(LayoutBox&); + +private: + void compute_width_for_absolutely_positioned_block(LayoutBox&); + + void layout_initial_containing_block(LayoutMode); + void layout_block_level_children(LayoutMode); + void layout_inline_children(LayoutMode); + void layout_absolutely_positioned_descendants(); + + void place_block_level_replaced_element_in_normal_flow(LayoutBox&); + void place_block_level_non_replaced_element_in_normal_flow(LayoutBox&); + + void layout_absolutely_positioned_descendant(LayoutBox&); +}; + +} diff --git a/Libraries/LibWeb/Layout/FormattingContext.cpp b/Libraries/LibWeb/Layout/FormattingContext.cpp new file mode 100644 index 0000000000..1b52502813 --- /dev/null +++ b/Libraries/LibWeb/Layout/FormattingContext.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <LibWeb/Layout/BlockFormattingContext.h> +#include <LibWeb/Layout/FormattingContext.h> +#include <LibWeb/Layout/InlineFormattingContext.h> +#include <LibWeb/Layout/LayoutBox.h> +#include <LibWeb/Layout/TableFormattingContext.h> + +namespace Web::Layout { + +FormattingContext::FormattingContext(LayoutBox& context_box) + : m_context_box(context_box) +{ +} + +FormattingContext::~FormattingContext() +{ +} + +void FormattingContext::layout_inside(LayoutBox& box, LayoutMode layout_mode) +{ + if (box.is_table()) { + TableFormattingContext context(box); + context.run(layout_mode); + } else if (box.children_are_inline()) { + InlineFormattingContext context(box); + context.run(layout_mode); + } else { + BlockFormattingContext context(box); + context.run(layout_mode); + } +} + +static float greatest_child_width(const LayoutBox& box) +{ + float max_width = 0; + if (box.children_are_inline()) { + for (auto& child : box.line_boxes()) { + max_width = max(max_width, child.width()); + } + } else { + box.for_each_child_of_type<LayoutBox>([&](auto& child) { + max_width = max(max_width, child.width()); + }); + } + return max_width; +} + +FormattingContext::ShrinkToFitResult FormattingContext::calculate_shrink_to_fit_widths(LayoutBox& box) +{ + // Calculate the preferred width by formatting the content without breaking lines + // other than where explicit line breaks occur. + layout_inside(box, LayoutMode::OnlyRequiredLineBreaks); + float preferred_width = greatest_child_width(box); + + // Also calculate the preferred minimum width, e.g., by trying all possible line breaks. + // CSS 2.2 does not define the exact algorithm. + + layout_inside(box, LayoutMode::AllPossibleLineBreaks); + float preferred_minimum_width = greatest_child_width(box); + + return { preferred_width, preferred_minimum_width }; +} + +} diff --git a/Libraries/LibWeb/Layout/FormattingContext.h b/Libraries/LibWeb/Layout/FormattingContext.h new file mode 100644 index 0000000000..0b681b88f0 --- /dev/null +++ b/Libraries/LibWeb/Layout/FormattingContext.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include <LibWeb/Forward.h> + +namespace Web::Layout { + +class FormattingContext { +public: + virtual void run(LayoutMode) = 0; + + LayoutBox& context_box() { return m_context_box; } + const LayoutBox& context_box() const { return m_context_box; } + + +protected: + FormattingContext(LayoutBox&); + virtual ~FormattingContext(); + + static void layout_inside(LayoutBox&, LayoutMode); + + struct ShrinkToFitResult { + float preferred_width { 0 }; + float preferred_minimum_width { 0 }; + }; + + ShrinkToFitResult calculate_shrink_to_fit_widths(LayoutBox&); + + LayoutBox& m_context_box; +}; + +} diff --git a/Libraries/LibWeb/Layout/InlineFormattingContext.cpp b/Libraries/LibWeb/Layout/InlineFormattingContext.cpp new file mode 100644 index 0000000000..5edcce8192 --- /dev/null +++ b/Libraries/LibWeb/Layout/InlineFormattingContext.cpp @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <LibWeb/CSS/Length.h> +#include <LibWeb/DOM/Node.h> +#include <LibWeb/Dump.h> +#include <LibWeb/Layout/InlineFormattingContext.h> +#include <LibWeb/Layout/LayoutBlock.h> +#include <LibWeb/Layout/LayoutBox.h> +#include <LibWeb/Layout/LayoutInline.h> +#include <LibWeb/Layout/LayoutReplaced.h> + +namespace Web::Layout { + +InlineFormattingContext::InlineFormattingContext(LayoutBox& containing_block) + : FormattingContext(containing_block) +{ +} + +InlineFormattingContext::~InlineFormattingContext() +{ +} + +void InlineFormattingContext::run(LayoutMode layout_mode) +{ + auto& containing_block = downcast<LayoutBlock>(context_box()); + + ASSERT(containing_block.children_are_inline()); + containing_block.line_boxes().clear(); + containing_block.for_each_child([&](auto& child) { + ASSERT(child.is_inline()); + if (child.is_absolutely_positioned()) + return; + + child.split_into_lines(containing_block, layout_mode); + }); + + for (auto& line_box : containing_block.line_boxes()) { + line_box.trim_trailing_whitespace(); + } + + // If there's an empty line box at the bottom, just remove it instead of giving it height. + if (!containing_block.line_boxes().is_empty() && containing_block.line_boxes().last().fragments().is_empty()) + containing_block.line_boxes().take_last(); + + auto text_align = containing_block.style().text_align(); + float min_line_height = containing_block.specified_style().line_height(containing_block); + float line_spacing = min_line_height - containing_block.specified_style().font().glyph_height(); + float content_height = 0; + float max_linebox_width = 0; + + for (auto& line_box : containing_block.line_boxes()) { + float max_height = min_line_height; + for (auto& fragment : line_box.fragments()) { + max_height = max(max_height, fragment.height()); + } + + float x_offset = 0; + float excess_horizontal_space = (float)containing_block.width() - line_box.width(); + + switch (text_align) { + case CSS::TextAlign::Center: + case CSS::TextAlign::VendorSpecificCenter: + x_offset += excess_horizontal_space / 2; + break; + case CSS::TextAlign::Right: + x_offset += excess_horizontal_space; + break; + case CSS::TextAlign::Left: + case CSS::TextAlign::Justify: + default: + break; + } + + float excess_horizontal_space_including_whitespace = excess_horizontal_space; + int whitespace_count = 0; + if (text_align == CSS::TextAlign::Justify) { + for (auto& fragment : line_box.fragments()) { + if (fragment.is_justifiable_whitespace()) { + ++whitespace_count; + excess_horizontal_space_including_whitespace += fragment.width(); + } + } + } + + float justified_space_width = whitespace_count ? (excess_horizontal_space_including_whitespace / (float)whitespace_count) : 0; + + for (size_t i = 0; i < line_box.fragments().size(); ++i) { + auto& fragment = line_box.fragments()[i]; + + // Vertically align everyone's bottom to the line. + // FIXME: Support other kinds of vertical alignment. + fragment.set_offset({ roundf(x_offset + fragment.offset().x()), content_height + (max_height - fragment.height()) - (line_spacing / 2) }); + + if (text_align == CSS::TextAlign::Justify + && fragment.is_justifiable_whitespace() + && fragment.width() != justified_space_width) { + float diff = justified_space_width - fragment.width(); + fragment.set_width(justified_space_width); + // Shift subsequent sibling fragments to the right to adjust for change in width. + for (size_t j = i + 1; j < line_box.fragments().size(); ++j) { + auto offset = line_box.fragments()[j].offset(); + offset.move_by(diff, 0); + line_box.fragments()[j].set_offset(offset); + } + } + + if (fragment.layout_node().is_box()) + dimension_box_on_line(const_cast<LayoutBox&>(downcast<LayoutBox>(fragment.layout_node())), layout_mode); + + float final_line_box_width = 0; + for (auto& fragment : line_box.fragments()) + final_line_box_width += fragment.width(); + line_box.m_width = final_line_box_width; + + max_linebox_width = max(max_linebox_width, final_line_box_width); + } + + content_height += max_height; + } + + if (layout_mode != LayoutMode::Default) { + containing_block.set_width(max_linebox_width); + } + + containing_block.set_height(content_height); +} + +void InlineFormattingContext::dimension_box_on_line(LayoutBox& box, LayoutMode layout_mode) +{ + auto& containing_block = downcast<LayoutBlock>(context_box()); + + if (box.is_replaced()) { + auto& replaced = const_cast<LayoutReplaced&>(downcast<LayoutReplaced>(box)); + replaced.set_width(replaced.calculate_width()); + replaced.set_height(replaced.calculate_height()); + return; + } + + if (box.is_inline_block()) { + auto& inline_block = const_cast<LayoutBlock&>(downcast<LayoutBlock>(box)); + + if (inline_block.style().width().is_undefined_or_auto()) { + auto result = calculate_shrink_to_fit_widths(inline_block); + + // FIXME: (10.3.5) find the available width: in this case, this is the width of the containing + // block minus the used values of 'margin-left', 'border-left-width', 'padding-left', + // 'padding-right', 'border-right-width', 'margin-right', and the widths of any + // relevant scroll bars. + auto available_width = containing_block.width(); + + auto width = min(max(result.preferred_minimum_width, available_width), result.preferred_width); + inline_block.set_width(width); + } else { + inline_block.set_width(inline_block.style().width().to_px(inline_block)); + } + + FormattingContext::layout_inside(inline_block, layout_mode); + + if (inline_block.style().height().is_undefined_or_auto()) { + // FIXME: (10.6.6) If 'height' is 'auto', the height depends on the element's descendants per 10.6.7. + } else { + inline_block.set_height(inline_block.style().height().to_px(inline_block)); + } + return; + } + + // Non-replaced, non-inline-block, box on a line!? + // I don't think we should be here. Dump the box tree so we can take a look at it. + dbgln("FIXME: I've been asked to dimension a non-replaced, non-inline-block box on a line:"); + dump_tree(box); +} + +} diff --git a/Libraries/LibWeb/Layout/InlineFormattingContext.h b/Libraries/LibWeb/Layout/InlineFormattingContext.h new file mode 100644 index 0000000000..6534b6d6be --- /dev/null +++ b/Libraries/LibWeb/Layout/InlineFormattingContext.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include <LibWeb/Forward.h> +#include <LibWeb/Layout/FormattingContext.h> + +namespace Web::Layout { + +class InlineFormattingContext final : public FormattingContext { +public: + InlineFormattingContext(LayoutBox& containing_block); + ~InlineFormattingContext(); + + virtual void run(LayoutMode) override; + +private: + void dimension_box_on_line(LayoutBox&, LayoutMode); +}; + +} diff --git a/Libraries/LibWeb/Layout/LayoutBlock.cpp b/Libraries/LibWeb/Layout/LayoutBlock.cpp index d0acaa4e89..e673c31a58 100644 --- a/Libraries/LibWeb/Layout/LayoutBlock.cpp +++ b/Libraries/LibWeb/Layout/LayoutBlock.cpp @@ -56,642 +56,6 @@ LayoutNode& LayoutBlock::inline_wrapper() return *last_child(); } -void LayoutBlock::layout(LayoutMode layout_mode) -{ - compute_width(); - layout_inside(layout_mode); - compute_height(); - - layout_absolutely_positioned_descendants(); -} - -void LayoutBlock::layout_absolutely_positioned_descendant(LayoutBox& box) -{ - box.layout(LayoutMode::Default); - auto& box_model = box.box_model(); - auto zero_value = CSS::Length::make_px(0); - - auto specified_width = box.style().width().resolved_or_auto(box, width()); - - box_model.margin.left = box.style().margin().left.resolved_or_auto(box, width()); - box_model.margin.top = box.style().margin().top.resolved_or_auto(box, height()); - box_model.margin.right = box.style().margin().right.resolved_or_auto(box, width()); - box_model.margin.bottom = box.style().margin().bottom.resolved_or_auto(box, height()); - - box_model.border.left = CSS::Length::make_px(box.style().border_left().width); - box_model.border.right = CSS::Length::make_px(box.style().border_right().width); - box_model.border.top = CSS::Length::make_px(box.style().border_top().width); - box_model.border.bottom = CSS::Length::make_px(box.style().border_bottom().width); - - box_model.offset.left = box.style().offset().left.resolved_or_auto(box, width()); - box_model.offset.top = box.style().offset().top.resolved_or_auto(box, height()); - box_model.offset.right = box.style().offset().right.resolved_or_auto(box, width()); - box_model.offset.bottom = box.style().offset().bottom.resolved_or_auto(box, height()); - - if (box_model.offset.left.is_auto() && specified_width.is_auto() && box_model.offset.right.is_auto()) { - if (box_model.margin.left.is_auto()) - box_model.margin.left = zero_value; - if (box_model.margin.right.is_auto()) - box_model.margin.right = zero_value; - } - - Gfx::FloatPoint used_offset; - - if (!box_model.offset.left.is_auto()) { - float x_offset = box_model.offset.left.to_px(box) - + box_model.border_box(box).left; - used_offset.set_x(x_offset + box_model.margin.left.to_px(box)); - } else if (!box_model.offset.right.is_auto()) { - float x_offset = 0 - - box_model.offset.right.to_px(box) - - box_model.border_box(box).right; - used_offset.set_x(width() + x_offset - box.width() - box_model.margin.right.to_px(box)); - } else { - float x_offset = box_model.margin_box(box).left; - used_offset.set_x(x_offset); - } - - if (!box_model.offset.top.is_auto()) { - float y_offset = box_model.offset.top.to_px(box) - + box_model.border_box(box).top; - used_offset.set_y(y_offset + box_model.margin.top.to_px(box)); - } else if (!box_model.offset.bottom.is_auto()) { - float y_offset = 0 - - box_model.offset.bottom.to_px(box) - - box_model.border_box(box).bottom; - used_offset.set_y(height() + y_offset - box.height() - box_model.margin.bottom.to_px(box)); - } else { - float y_offset = box_model.margin_box(box).top; - used_offset.set_y(y_offset); - } - - box.set_offset(used_offset); -} - -void LayoutBlock::layout_inside(LayoutMode layout_mode) -{ - if (children_are_inline()) - layout_inline_children(layout_mode); - else - layout_contained_boxes(layout_mode); -} - -void LayoutBlock::layout_absolutely_positioned_descendants() -{ - for_each_in_subtree_of_type<LayoutBox>([&](auto& box) { - if (box.is_absolutely_positioned() && box.containing_block() == this) { - layout_absolutely_positioned_descendant(box); - } - return IterationDecision::Continue; - }); -} - -void LayoutBlock::layout_contained_boxes(LayoutMode layout_mode) -{ - float content_height = 0; - float content_width = 0; - for_each_in_subtree_of_type<LayoutBox>([&](auto& box) { - if (box.is_absolutely_positioned() || box.containing_block() != this) - return IterationDecision::Continue; - box.layout(layout_mode); - if (box.is_replaced()) - place_block_level_replaced_element_in_normal_flow(downcast<LayoutReplaced>(box)); - else if (box.is_block()) - place_block_level_non_replaced_element_in_normal_flow(downcast<LayoutBlock>(box)); - else - dbg() << "FIXME: LayoutBlock::layout_contained_boxes doesn't know how to place a " << box.class_name(); - content_height = max(content_height, box.effective_offset().y() + box.height() + box.box_model().margin_box(*this).bottom); - content_width = max(content_width, downcast<LayoutBox>(box).width()); - return IterationDecision::Continue; - }); - - if (layout_mode != LayoutMode::Default) { - if (style().width().is_undefined() || style().width().is_auto()) - set_width(content_width); - } - - set_height(content_height); -} - -void LayoutBlock::layout_inline_children(LayoutMode layout_mode) -{ - ASSERT(children_are_inline()); - m_line_boxes.clear(); - for_each_child([&](auto& child) { - ASSERT(child.is_inline()); - if (child.is_absolutely_positioned()) - return; - child.split_into_lines(*this, layout_mode); - }); - - for (auto& line_box : m_line_boxes) { - line_box.trim_trailing_whitespace(); - } - - // If there's an empty line box at the bottom, just remove it instead of giving it height. - if (!m_line_boxes.is_empty() && m_line_boxes.last().fragments().is_empty()) - m_line_boxes.take_last(); - - auto text_align = style().text_align(); - float min_line_height = specified_style().line_height(*this); - float line_spacing = min_line_height - specified_style().font().glyph_height(); - float content_height = 0; - float max_linebox_width = 0; - - for (auto& line_box : m_line_boxes) { - float max_height = min_line_height; - for (auto& fragment : line_box.fragments()) { - max_height = max(max_height, fragment.height()); - } - - float x_offset = 0; - float excess_horizontal_space = (float)width() - line_box.width(); - - switch (text_align) { - case CSS::TextAlign::Center: - case CSS::TextAlign::VendorSpecificCenter: - x_offset += excess_horizontal_space / 2; - break; - case CSS::TextAlign::Right: - x_offset += excess_horizontal_space; - break; - case CSS::TextAlign::Left: - case CSS::TextAlign::Justify: - default: - break; - } - - float excess_horizontal_space_including_whitespace = excess_horizontal_space; - int whitespace_count = 0; - if (text_align == CSS::TextAlign::Justify) { - for (auto& fragment : line_box.fragments()) { - if (fragment.is_justifiable_whitespace()) { - ++whitespace_count; - excess_horizontal_space_including_whitespace += fragment.width(); - } - } - } - - float justified_space_width = whitespace_count ? (excess_horizontal_space_including_whitespace / (float)whitespace_count) : 0; - - for (size_t i = 0; i < line_box.fragments().size(); ++i) { - auto& fragment = line_box.fragments()[i]; - - // Vertically align everyone's bottom to the line. - // FIXME: Support other kinds of vertical alignment. - fragment.set_offset({ roundf(x_offset + fragment.offset().x()), content_height + (max_height - fragment.height()) - (line_spacing / 2) }); - - if (text_align == CSS::TextAlign::Justify) { - if (fragment.is_justifiable_whitespace()) { - if (fragment.width() != justified_space_width) { - float diff = justified_space_width - fragment.width(); - fragment.set_width(justified_space_width); - // Shift subsequent sibling fragments to the right to adjust for change in width. - for (size_t j = i + 1; j < line_box.fragments().size(); ++j) { - auto offset = line_box.fragments()[j].offset(); - offset.move_by(diff, 0); - line_box.fragments()[j].set_offset(offset); - } - } - } - } - - if (fragment.layout_node().is_inline_block()) { - auto& inline_block = const_cast<LayoutBlock&>(downcast<LayoutBlock>(fragment.layout_node())); - inline_block.set_size(fragment.size()); - inline_block.layout(layout_mode); - } - - float final_line_box_width = 0; - for (auto& fragment : line_box.fragments()) - final_line_box_width += fragment.width(); - line_box.m_width = final_line_box_width; - - max_linebox_width = max(max_linebox_width, final_line_box_width); - } - - content_height += max_height; - } - - if (layout_mode != LayoutMode::Default) { - set_width(max_linebox_width); - } - - set_height(content_height); -} - -void LayoutBlock::compute_width_for_absolutely_positioned_block() -{ - auto& containing_block = *this->containing_block(); - auto zero_value = CSS::Length::make_px(0); - - auto margin_left = CSS::Length::make_auto(); - auto margin_right = CSS::Length::make_auto(); - const auto border_left = style().border_left().width; - const auto border_right = style().border_right().width; - const auto padding_left = style().padding().left.resolved(zero_value, *this, containing_block.width()); - const auto padding_right = style().padding().right.resolved(zero_value, *this, containing_block.width()); - - auto try_compute_width = [&](const auto& a_width) { - margin_left = style().margin().left.resolved(zero_value, *this, containing_block.width()); - margin_right = style().margin().right.resolved(zero_value, *this, containing_block.width()); - - auto left = style().offset().left.resolved_or_auto(*this, containing_block.width()); - auto right = style().offset().right.resolved_or_auto(*this, containing_block.width()); - auto width = a_width; - - auto solve_for_left = [&] { - return CSS::Length(containing_block.width() - margin_left.to_px(*this) - border_left - padding_left.to_px(*this) - width.to_px(*this) - padding_right.to_px(*this) - border_right - margin_right.to_px(*this) - right.to_px(*this), CSS::Length::Type::Px); - }; - - auto solve_for_width = [&] { - return CSS::Length(containing_block.width() - left.to_px(*this) - margin_left.to_px(*this) - border_left - padding_left.to_px(*this) - padding_right.to_px(*this) - border_right - margin_right.to_px(*this) - right.to_px(*this), CSS::Length::Type::Px); - }; - - auto solve_for_right = [&] { - return CSS::Length(containing_block.width() - left.to_px(*this) - margin_left.to_px(*this) - border_left - padding_left.to_px(*this) - width.to_px(*this) - padding_right.to_px(*this) - border_right - margin_right.to_px(*this), CSS::Length::Type::Px); - }; - - // If all three of 'left', 'width', and 'right' are 'auto': - if (left.is_auto() && width.is_auto() && right.is_auto()) { - // First set any 'auto' values for 'margin-left' and 'margin-right' to 0. - if (margin_left.is_auto()) - margin_left = CSS::Length::make_px(0); - if (margin_right.is_auto()) - margin_right = CSS::Length::make_px(0); - // Then, if the 'direction' property of the element establishing the static-position containing block - // is 'ltr' set 'left' to the static position and apply rule number three below; - // otherwise, set 'right' to the static position and apply rule number one below. - // FIXME: This is very hackish. - left = CSS::Length::make_px(0); - goto Rule3; - } - - if (!left.is_auto() && !width.is_auto() && !right.is_auto()) { - // FIXME: This should be solved in a more complicated way. - return width; - } - - if (margin_left.is_auto()) - margin_left = CSS::Length::make_px(0); - if (margin_right.is_auto()) - margin_right = CSS::Length::make_px(0); - - // 1. 'left' and 'width' are 'auto' and 'right' is not 'auto', - // then the width is shrink-to-fit. Then solve for 'left' - if (left.is_auto() && width.is_auto() && !right.is_auto()) { - auto result = calculate_shrink_to_fit_width(); - solve_for_left(); - auto available_width = solve_for_width(); - width = CSS::Length(min(max(result.preferred_minimum_width, available_width.to_px(*this)), result.preferred_width), CSS::Length::Type::Px); - } - - // 2. 'left' and 'right' are 'auto' and 'width' is not 'auto', - // then if the 'direction' property of the element establishing - // the static-position containing block is 'ltr' set 'left' - // to the static position, otherwise set 'right' to the static position. - // Then solve for 'left' (if 'direction is 'rtl') or 'right' (if 'direction' is 'ltr'). - else if (left.is_auto() && right.is_auto() && !width.is_auto()) { - // FIXME: Check direction - // FIXME: Use the static-position containing block - left = zero_value; - right = solve_for_right(); - } - - // 3. 'width' and 'right' are 'auto' and 'left' is not 'auto', - // then the width is shrink-to-fit. Then solve for 'right' - else if (width.is_auto() && right.is_auto() && !left.is_auto()) { - Rule3: - auto result = calculate_shrink_to_fit_width(); - right = solve_for_right(); - auto available_width = solve_for_width(); - width = CSS::Length(min(max(result.preferred_minimum_width, available_width.to_px(*this)), result.preferred_width), CSS::Length::Type::Px); - } - - // 4. 'left' is 'auto', 'width' and 'right' are not 'auto', then solve for 'left' - else if (left.is_auto() && !width.is_auto() && !right.is_auto()) { - left = solve_for_left(); - } - - // 5. 'width' is 'auto', 'left' and 'right' are not 'auto', then solve for 'width' - else if (width.is_auto() && !left.is_auto() && !right.is_auto()) { - width = solve_for_width(); - } - - // 6. 'right' is 'auto', 'left' and 'width' are not 'auto', then solve for 'right' - else if (right.is_auto() && !left.is_auto() && !width.is_auto()) { - right = solve_for_right(); - } - - return width; - }; - - auto specified_width = style().width().resolved_or_auto(*this, containing_block.width()); - - // 1. The tentative used width is calculated (without 'min-width' and 'max-width') - auto used_width = try_compute_width(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 = style().max_width().resolved_or_auto(*this, containing_block.width()); - if (!specified_max_width.is_auto()) { - if (used_width.to_px(*this) > specified_max_width.to_px(*this)) { - used_width = try_compute_width(specified_max_width); - } - } - - // 3. If the resulting width is smaller than 'min-width', the rules above are applied again, - // but this time using the value of 'min-width' as the computed value for 'width'. - auto specified_min_width = style().min_width().resolved_or_auto(*this, containing_block.width()); - if (!specified_min_width.is_auto()) { - if (used_width.to_px(*this) < specified_min_width.to_px(*this)) { - used_width = try_compute_width(specified_min_width); - } - } - - set_width(used_width.to_px(*this)); - - box_model().margin.left = margin_left; - box_model().margin.right = margin_right; - box_model().border.left = CSS::Length::make_px(border_left); - box_model().border.right = CSS::Length::make_px(border_right); - box_model().padding.left = padding_left; - box_model().padding.right = padding_right; -} - -float LayoutBlock::width_of_logical_containing_block() const -{ - auto* containing_block = this->containing_block(); - ASSERT(containing_block); - return containing_block->width(); -} - -void LayoutBlock::compute_width() -{ - if (is_absolutely_positioned()) - return compute_width_for_absolutely_positioned_block(); - - float width_of_containing_block = this->width_of_logical_containing_block(); - - auto zero_value = CSS::Length::make_px(0); - - auto margin_left = CSS::Length::make_auto(); - auto margin_right = CSS::Length::make_auto(); - const auto padding_left = style().padding().left.resolved_or_zero(*this, width_of_containing_block); - const auto padding_right = style().padding().right.resolved_or_zero(*this, width_of_containing_block); - - auto try_compute_width = [&](const auto& a_width) { - CSS::Length width = a_width; - margin_left = style().margin().left.resolved_or_zero(*this, width_of_containing_block); - margin_right = style().margin().right.resolved_or_zero(*this, width_of_containing_block); - - float total_px = style().border_left().width + style().border_right().width; - for (auto& value : { margin_left, padding_left, width, padding_right, margin_right }) { - total_px += value.to_px(*this); - } - - if (!is_replaced() && !is_inline()) { - // 10.3.3 Block-level, non-replaced elements in normal flow - // If 'width' is not 'auto' and 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width' (plus any of 'margin-left' or 'margin-right' that are not 'auto') is larger than the width of the containing block, then any 'auto' values for 'margin-left' or 'margin-right' are, for the following rules, treated as zero. - if (width.is_auto() && total_px > width_of_containing_block) { - if (margin_left.is_auto()) - margin_left = zero_value; - if (margin_right.is_auto()) - margin_right = zero_value; - } - - // 10.3.3 cont'd. - auto underflow_px = width_of_containing_block - total_px; - - if (width.is_auto()) { - if (margin_left.is_auto()) - margin_left = zero_value; - if (margin_right.is_auto()) - margin_right = zero_value; - if (underflow_px >= 0) { - width = CSS::Length(underflow_px, CSS::Length::Type::Px); - } else { - width = zero_value; - margin_right = CSS::Length(margin_right.to_px(*this) + underflow_px, CSS::Length::Type::Px); - } - } else { - if (!margin_left.is_auto() && !margin_right.is_auto()) { - margin_right = CSS::Length(margin_right.to_px(*this) + underflow_px, CSS::Length::Type::Px); - } else if (!margin_left.is_auto() && margin_right.is_auto()) { - margin_right = CSS::Length(underflow_px, CSS::Length::Type::Px); - } else if (margin_left.is_auto() && !margin_right.is_auto()) { - margin_left = CSS::Length(underflow_px, CSS::Length::Type::Px); - } else { // margin_left.is_auto() && margin_right.is_auto() - auto half_of_the_underflow = CSS::Length(underflow_px / 2, CSS::Length::Type::Px); - margin_left = half_of_the_underflow; - margin_right = half_of_the_underflow; - } - } - } else if (!is_replaced() && is_inline_block()) { - - // 10.3.9 'Inline-block', non-replaced elements in normal flow - - // A computed value of 'auto' for 'margin-left' or 'margin-right' becomes a used value of '0'. - if (margin_left.is_auto()) - margin_left = zero_value; - if (margin_right.is_auto()) - margin_right = zero_value; - - // If 'width' is 'auto', the used value is the shrink-to-fit width as for floating elements. - if (width.is_auto()) { - - // Find the available width: in this case, this is the width of the containing - // block minus the used values of 'margin-left', 'border-left-width', 'padding-left', - // 'padding-right', 'border-right-width', 'margin-right', and the widths of any relevant scroll bars. - float available_width = width_of_containing_block - - margin_left.to_px(*this) - style().border_left().width - padding_left.to_px(*this) - - padding_right.to_px(*this) - style().border_right().width - margin_right.to_px(*this); - - auto result = calculate_shrink_to_fit_width(); - - // Then the shrink-to-fit width is: min(max(preferred minimum width, available width), preferred width). - width = CSS::Length(min(max(result.preferred_minimum_width, available_width), result.preferred_width), CSS::Length::Type::Px); - } - } - - return width; - }; - - auto specified_width = style().width().resolved_or_auto(*this, width_of_containing_block); - - // 1. The tentative used width is calculated (without 'min-width' and 'max-width') - auto used_width = try_compute_width(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 = style().max_width().resolved_or_auto(*this, width_of_containing_block); - if (!specified_max_width.is_auto()) { - if (used_width.to_px(*this) > specified_max_width.to_px(*this)) { - used_width = try_compute_width(specified_max_width); - } - } - - // 3. If the resulting width is smaller than 'min-width', the rules above are applied again, - // but this time using the value of 'min-width' as the computed value for 'width'. - auto specified_min_width = style().min_width().resolved_or_auto(*this, width_of_containing_block); - if (!specified_min_width.is_auto()) { - if (used_width.to_px(*this) < specified_min_width.to_px(*this)) { - used_width = try_compute_width(specified_min_width); - } - } - - set_width(used_width.to_px(*this)); - box_model().margin.left = margin_left; - box_model().margin.right = margin_right; - box_model().border.left = CSS::Length::make_px(style().border_left().width); - box_model().border.right = CSS::Length::make_px(style().border_right().width); - box_model().padding.left = padding_left; - box_model().padding.right = padding_right; -} - -void LayoutBlock::place_block_level_replaced_element_in_normal_flow(LayoutReplaced& box) -{ - ASSERT(!is_absolutely_positioned()); - auto& containing_block = *this; - auto& replaced_element_box_model = box.box_model(); - - replaced_element_box_model.margin.top = box.style().margin().top.resolved_or_zero(*this, containing_block.width()); - replaced_element_box_model.margin.bottom = box.style().margin().bottom.resolved_or_zero(*this, containing_block.width()); - replaced_element_box_model.border.top = CSS::Length::make_px(box.style().border_top().width); - replaced_element_box_model.border.bottom = CSS::Length::make_px(box.style().border_bottom().width); - replaced_element_box_model.padding.top = box.style().padding().top.resolved_or_zero(*this, containing_block.width()); - replaced_element_box_model.padding.bottom = box.style().padding().bottom.resolved_or_zero(*this, containing_block.width()); - - float x = replaced_element_box_model.margin.left.to_px(*this) - + replaced_element_box_model.border.left.to_px(*this) - + replaced_element_box_model.padding.left.to_px(*this) - + replaced_element_box_model.offset.left.to_px(*this); - - float y = replaced_element_box_model.margin_box(*this).top + box_model().offset.top.to_px(*this); - - box.set_offset(x, y); -} - -LayoutBlock::ShrinkToFitResult LayoutBlock::calculate_shrink_to_fit_width() -{ - auto greatest_child_width = [&] { - float max_width = 0; - if (children_are_inline()) { - for (auto& box : line_boxes()) { - max_width = max(max_width, box.width()); - } - } else { - for_each_child([&](auto& child) { - if (child.is_box()) - max_width = max(max_width, downcast<LayoutBox>(child).width()); - }); - } - return max_width; - }; - - // Calculate the preferred width by formatting the content without breaking lines - // other than where explicit line breaks occur. - layout_inside(LayoutMode::OnlyRequiredLineBreaks); - float preferred_width = greatest_child_width(); - - // Also calculate the preferred minimum width, e.g., by trying all possible line breaks. - // CSS 2.2 does not define the exact algorithm. - - layout_inside(LayoutMode::AllPossibleLineBreaks); - float preferred_minimum_width = greatest_child_width(); - - return { preferred_width, preferred_minimum_width }; -} - -void LayoutBlock::place_block_level_non_replaced_element_in_normal_flow(LayoutBlock& block) -{ - auto zero_value = CSS::Length::make_px(0); - auto& containing_block = *this; - auto& box = block.box_model(); - auto& style = block.style(); - - box.margin.top = style.margin().top.resolved(zero_value, *this, containing_block.width()); - box.margin.bottom = style.margin().bottom.resolved(zero_value, *this, containing_block.width()); - box.border.top = CSS::Length::make_px(style.border_top().width); - box.border.bottom = CSS::Length::make_px(style.border_bottom().width); - box.padding.top = style.padding().top.resolved(zero_value, *this, containing_block.width()); - box.padding.bottom = style.padding().bottom.resolved(zero_value, *this, containing_block.width()); - - float x = box.margin.left.to_px(*this) - + box.border.left.to_px(*this) - + box.padding.left.to_px(*this) - + box.offset.left.to_px(*this); - - if (this->style().text_align() == CSS::TextAlign::VendorSpecificCenter) { - x = (containing_block.width() / 2) - block.width() / 2; - } - - float y = box.margin_box(*this).top - + box.offset.top.to_px(*this); - - // NOTE: Empty (0-height) preceding siblings have their margins collapsed with *their* preceding sibling, etc. - float collapsed_bottom_margin_of_preceding_siblings = 0; - - auto* relevant_sibling = block.previous_sibling(); - 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.to_px(*relevant_sibling)); - if (relevant_sibling->height() > 0) - break; - } - relevant_sibling = relevant_sibling->previous_sibling(); - } - - if (relevant_sibling) { - y += relevant_sibling->effective_offset().y() + relevant_sibling->height() + relevant_sibling->box_model().padding.bottom.to_px(*relevant_sibling); - - // Collapse top margin with bottom margin of preceding siblings if needed - float my_margin_top = box.margin.top.to_px(*this); - - if (my_margin_top < 0 || collapsed_bottom_margin_of_preceding_siblings < 0) { - // Negative margins present. - float largest_negative_margin = -min(my_margin_top, collapsed_bottom_margin_of_preceding_siblings); - float largest_positive_margin = (my_margin_top < 0 && collapsed_bottom_margin_of_preceding_siblings < 0) ? 0 : max(my_margin_top, collapsed_bottom_margin_of_preceding_siblings); - float final_margin = largest_positive_margin - largest_negative_margin; - y += final_margin - my_margin_top; - } else if (collapsed_bottom_margin_of_preceding_siblings > my_margin_top) { - // Sibling's margin is larger than mine, adjust so we use sibling's. - y += collapsed_bottom_margin_of_preceding_siblings - my_margin_top; - } - } - - block.set_offset(x, y); -} - -void LayoutBlock::compute_height() -{ - auto& containing_block = *this->containing_block(); - - CSS::Length specified_height; - - if (style().height().is_percentage() && !containing_block.style().height().is_absolute()) { - specified_height = CSS::Length::make_auto(); - } else { - specified_height = style().height().resolved_or_auto(*this, containing_block.height()); - } - - auto specified_max_height = style().max_height().resolved_or_auto(*this, containing_block.height()); - - box_model().margin.top = style().margin().top.resolved_or_zero(*this, containing_block.width()); - box_model().margin.bottom = style().margin().bottom.resolved_or_zero(*this, containing_block.width()); - box_model().border.top = CSS::Length::make_px(style().border_top().width); - box_model().border.bottom = CSS::Length::make_px(style().border_bottom().width); - box_model().padding.top = style().padding().top.resolved_or_zero(*this, containing_block.width()); - box_model().padding.bottom = style().padding().bottom.resolved_or_zero(*this, containing_block.width()); - - if (!specified_height.is_auto()) { - float used_height = specified_height.to_px(*this); - if (!specified_max_height.is_auto()) - used_height = min(used_height, specified_max_height.to_px(*this)); - set_height(used_height); - } -} - void LayoutBlock::paint(PaintContext& context, PaintPhase phase) { if (!is_visible()) @@ -767,23 +131,8 @@ NonnullRefPtr<CSS::StyleProperties> LayoutBlock::style_for_anonymous_block() con return new_style; } -LineBox& LayoutBlock::ensure_last_line_box() -{ - if (m_line_boxes.is_empty()) - return add_line_box(); - return m_line_boxes.last(); -} - -LineBox& LayoutBlock::add_line_box() -{ - m_line_boxes.append(LineBox()); - return m_line_boxes.last(); -} - void LayoutBlock::split_into_lines(LayoutBlock& container, LayoutMode layout_mode) { - layout(layout_mode); - auto* line_box = &container.ensure_last_line_box(); if (layout_mode != LayoutMode::OnlyRequiredLineBreaks && line_box->width() > 0 && line_box->width() + width() > container.width()) { line_box = &container.add_line_box(); diff --git a/Libraries/LibWeb/Layout/LayoutBlock.h b/Libraries/LibWeb/Layout/LayoutBlock.h index aa663628b7..7e5c4341c2 100644 --- a/Libraries/LibWeb/Layout/LayoutBlock.h +++ b/Libraries/LibWeb/Layout/LayoutBlock.h @@ -38,17 +38,10 @@ public: virtual const char* class_name() const override { return "LayoutBlock"; } - virtual void layout(LayoutMode = LayoutMode::Default) override; virtual void paint(PaintContext&, PaintPhase) override; virtual LayoutNode& inline_wrapper() override; - Vector<LineBox>& line_boxes() { return m_line_boxes; } - const Vector<LineBox>& line_boxes() const { return m_line_boxes; } - - LineBox& ensure_last_line_box(); - LineBox& add_line_box(); - virtual HitTestResult hit_test(const Gfx::IntPoint&, HitTestType) const override; LayoutBlock* previous_sibling() { return downcast<LayoutBlock>(LayoutNode::previous_sibling()); } @@ -63,36 +56,10 @@ public: virtual void split_into_lines(LayoutBlock& container, LayoutMode) override; - void layout_inside(LayoutMode); - -protected: - void compute_width(); - void compute_height(); - void layout_absolutely_positioned_descendants(); - - virtual float width_of_logical_containing_block() const; - private: virtual bool is_block() const override { return true; } - struct ShrinkToFitResult { - float preferred_width { 0 }; - float preferred_minimum_width { 0 }; - }; - ShrinkToFitResult calculate_shrink_to_fit_width(); - - void compute_width_for_absolutely_positioned_block(); - - void place_block_level_non_replaced_element_in_normal_flow(LayoutBlock&); - void place_block_level_replaced_element_in_normal_flow(LayoutReplaced&); - void layout_absolutely_positioned_descendant(LayoutBox&); - NonnullRefPtr<CSS::StyleProperties> style_for_anonymous_block() const; - - void layout_inline_children(LayoutMode); - void layout_contained_boxes(LayoutMode); - - Vector<LineBox> m_line_boxes; }; template<typename Callback> diff --git a/Libraries/LibWeb/Layout/LayoutBox.cpp b/Libraries/LibWeb/Layout/LayoutBox.cpp index 9cfed06ff4..80ee295d02 100644 --- a/Libraries/LibWeb/Layout/LayoutBox.cpp +++ b/Libraries/LibWeb/Layout/LayoutBox.cpp @@ -328,4 +328,24 @@ bool LayoutBox::establishes_stacking_context() const return false; } +LineBox& LayoutBox::ensure_last_line_box() +{ + if (m_line_boxes.is_empty()) + return add_line_box(); + return m_line_boxes.last(); +} + +LineBox& LayoutBox::add_line_box() +{ + m_line_boxes.append(LineBox()); + return m_line_boxes.last(); +} + +float LayoutBox::width_of_logical_containing_block() const +{ + auto* containing_block = this->containing_block(); + ASSERT(containing_block); + return containing_block->width(); +} + } diff --git a/Libraries/LibWeb/Layout/LayoutBox.h b/Libraries/LibWeb/Layout/LayoutBox.h index 004175ee99..f5e971e6b0 100644 --- a/Libraries/LibWeb/Layout/LayoutBox.h +++ b/Libraries/LibWeb/Layout/LayoutBox.h @@ -29,6 +29,7 @@ #include <AK/OwnPtr.h> #include <LibGfx/Rect.h> #include <LibWeb/Layout/LayoutNode.h> +#include <LibWeb/Layout/LineBox.h> #include <LibWeb/Painting/StackingContext.h> namespace Web { @@ -70,6 +71,14 @@ public: virtual void paint(PaintContext&, PaintPhase) override; + Vector<LineBox>& line_boxes() { return m_line_boxes; } + const Vector<LineBox>& line_boxes() const { return m_line_boxes; } + + LineBox& ensure_last_line_box(); + LineBox& add_line_box(); + + virtual float width_of_logical_containing_block() const; + protected: LayoutBox(DOM::Document& document, DOM::Node* node, NonnullRefPtr<CSS::StyleProperties> style) : LayoutNodeWithStyleAndBoxModelMetrics(document, node, move(style)) @@ -78,6 +87,8 @@ protected: virtual void did_set_rect() { } + Vector<LineBox> m_line_boxes; + private: virtual bool is_box() const final { return true; } diff --git a/Libraries/LibWeb/Layout/LayoutButton.cpp b/Libraries/LibWeb/Layout/LayoutButton.cpp index 653de61a8e..3322894f64 100644 --- a/Libraries/LibWeb/Layout/LayoutButton.cpp +++ b/Libraries/LibWeb/Layout/LayoutButton.cpp @@ -43,7 +43,7 @@ LayoutButton::~LayoutButton() { } -void LayoutButton::layout(LayoutMode layout_mode) +void LayoutButton::prepare_for_replaced_layout() { auto& font = specified_style().font(); set_intrinsic_width(font.width(node().value()) + 20); @@ -51,7 +51,6 @@ void LayoutButton::layout(LayoutMode layout_mode) set_intrinsic_height(20); set_has_intrinsic_height(true); - LayoutReplaced::layout(layout_mode); } void LayoutButton::paint(PaintContext& context, PaintPhase phase) diff --git a/Libraries/LibWeb/Layout/LayoutButton.h b/Libraries/LibWeb/Layout/LayoutButton.h index 29153eb054..685e4d6722 100644 --- a/Libraries/LibWeb/Layout/LayoutButton.h +++ b/Libraries/LibWeb/Layout/LayoutButton.h @@ -36,7 +36,7 @@ public: LayoutButton(DOM::Document&, HTML::HTMLInputElement&, NonnullRefPtr<CSS::StyleProperties>); virtual ~LayoutButton() override; - virtual void layout(LayoutMode = LayoutMode::Default) override; + virtual void prepare_for_replaced_layout() override; virtual void paint(PaintContext&, PaintPhase) override; const HTML::HTMLInputElement& node() const { return static_cast<const HTML::HTMLInputElement&>(LayoutReplaced::node()); } diff --git a/Libraries/LibWeb/Layout/LayoutCanvas.cpp b/Libraries/LibWeb/Layout/LayoutCanvas.cpp index cca04e6ac0..e78b17c456 100644 --- a/Libraries/LibWeb/Layout/LayoutCanvas.cpp +++ b/Libraries/LibWeb/Layout/LayoutCanvas.cpp @@ -40,13 +40,12 @@ LayoutCanvas::~LayoutCanvas() { } -void LayoutCanvas::layout(LayoutMode layout_mode) +void LayoutCanvas::prepare_for_replaced_layout() { set_has_intrinsic_width(true); set_has_intrinsic_height(true); set_intrinsic_width(node().width()); set_intrinsic_height(node().height()); - LayoutReplaced::layout(layout_mode); } void LayoutCanvas::paint(PaintContext& context, PaintPhase phase) diff --git a/Libraries/LibWeb/Layout/LayoutCanvas.h b/Libraries/LibWeb/Layout/LayoutCanvas.h index b42f0f463b..09a4d634ba 100644 --- a/Libraries/LibWeb/Layout/LayoutCanvas.h +++ b/Libraries/LibWeb/Layout/LayoutCanvas.h @@ -36,7 +36,7 @@ public: LayoutCanvas(DOM::Document&, HTML::HTMLCanvasElement&, NonnullRefPtr<CSS::StyleProperties>); virtual ~LayoutCanvas() override; - virtual void layout(LayoutMode = LayoutMode::Default) override; + virtual void prepare_for_replaced_layout() override; virtual void paint(PaintContext&, PaintPhase) override; const HTML::HTMLCanvasElement& node() const { return static_cast<const HTML::HTMLCanvasElement&>(LayoutReplaced::node()); } diff --git a/Libraries/LibWeb/Layout/LayoutCheckBox.cpp b/Libraries/LibWeb/Layout/LayoutCheckBox.cpp index b63137b2b0..3195044057 100644 --- a/Libraries/LibWeb/Layout/LayoutCheckBox.cpp +++ b/Libraries/LibWeb/Layout/LayoutCheckBox.cpp @@ -36,19 +36,14 @@ namespace Web { LayoutCheckBox::LayoutCheckBox(DOM::Document& document, HTML::HTMLInputElement& element, NonnullRefPtr<CSS::StyleProperties> style) : LayoutReplaced(document, element, move(style)) { -} - -LayoutCheckBox::~LayoutCheckBox() -{ -} - -void LayoutCheckBox::layout(LayoutMode layout_mode) -{ set_has_intrinsic_width(true); set_has_intrinsic_height(true); set_intrinsic_width(13); set_intrinsic_height(13); - LayoutReplaced::layout(layout_mode); +} + +LayoutCheckBox::~LayoutCheckBox() +{ } void LayoutCheckBox::paint(PaintContext& context, PaintPhase phase) diff --git a/Libraries/LibWeb/Layout/LayoutCheckBox.h b/Libraries/LibWeb/Layout/LayoutCheckBox.h index 8b9644a0d6..ed495e35f7 100644 --- a/Libraries/LibWeb/Layout/LayoutCheckBox.h +++ b/Libraries/LibWeb/Layout/LayoutCheckBox.h @@ -36,7 +36,6 @@ public: LayoutCheckBox(DOM::Document&, HTML::HTMLInputElement&, NonnullRefPtr<CSS::StyleProperties>); virtual ~LayoutCheckBox() override; - virtual void layout(LayoutMode = LayoutMode::Default) override; virtual void paint(PaintContext&, PaintPhase) override; const HTML::HTMLInputElement& node() const { return static_cast<const HTML::HTMLInputElement&>(LayoutReplaced::node()); } diff --git a/Libraries/LibWeb/Layout/LayoutDocument.cpp b/Libraries/LibWeb/Layout/LayoutDocument.cpp index 772ca83df9..9a5b1b0632 100644 --- a/Libraries/LibWeb/Layout/LayoutDocument.cpp +++ b/Libraries/LibWeb/Layout/LayoutDocument.cpp @@ -63,34 +63,6 @@ void LayoutDocument::build_stacking_context_tree() }); } -void LayoutDocument::layout(LayoutMode layout_mode) -{ - build_stacking_context_tree(); - - set_width(frame().size().width()); - - LayoutNode::layout(layout_mode); - - ASSERT(!children_are_inline()); - - float lowest_bottom = 0; - for_each_child([&](auto& child) { - ASSERT(is<LayoutBlock>(child)); - auto& child_block = downcast<LayoutBlock>(child); - lowest_bottom = max(lowest_bottom, child_block.absolute_rect().bottom()); - }); - set_height(lowest_bottom); - - layout_absolutely_positioned_descendants(); - - // FIXME: This is a total hack. Make sure any GUI::Widgets are moved into place after layout. - // We should stop embedding GUI::Widgets entirely, since that won't work out-of-process. - for_each_in_subtree_of_type<LayoutWidget>([&](auto& widget) { - widget.update_widget(); - return IterationDecision::Continue; - }); -} - void LayoutDocument::did_set_viewport_rect(Badge<Frame>, const Gfx::IntRect& a_viewport_rect) { Gfx::FloatRect viewport_rect(a_viewport_rect.x(), a_viewport_rect.y(), a_viewport_rect.width(), a_viewport_rect.height()); diff --git a/Libraries/LibWeb/Layout/LayoutDocument.h b/Libraries/LibWeb/Layout/LayoutDocument.h index 0e5d39aebe..96d6a4c425 100644 --- a/Libraries/LibWeb/Layout/LayoutDocument.h +++ b/Libraries/LibWeb/Layout/LayoutDocument.h @@ -38,7 +38,6 @@ public: const DOM::Document& node() const { return static_cast<const DOM::Document&>(*LayoutNode::node()); } virtual const char* class_name() const override { return "LayoutDocument"; } - virtual void layout(LayoutMode = LayoutMode::Default) override; void paint_all_phases(PaintContext&); virtual void paint(PaintContext&, PaintPhase) override; diff --git a/Libraries/LibWeb/Layout/LayoutFrame.cpp b/Libraries/LibWeb/Layout/LayoutFrame.cpp index 59dec1ea02..5f9b3b0236 100644 --- a/Libraries/LibWeb/Layout/LayoutFrame.cpp +++ b/Libraries/LibWeb/Layout/LayoutFrame.cpp @@ -48,7 +48,7 @@ LayoutFrame::~LayoutFrame() { } -void LayoutFrame::layout(LayoutMode layout_mode) +void LayoutFrame::prepare_for_replaced_layout() { ASSERT(node().content_frame()); @@ -57,8 +57,6 @@ void LayoutFrame::layout(LayoutMode layout_mode) // FIXME: Do proper error checking, etc. set_intrinsic_width(node().attribute(HTML::AttributeNames::width).to_int().value_or(300)); set_intrinsic_height(node().attribute(HTML::AttributeNames::height).to_int().value_or(150)); - - LayoutReplaced::layout(layout_mode); } void LayoutFrame::paint(PaintContext& context, PaintPhase phase) diff --git a/Libraries/LibWeb/Layout/LayoutFrame.h b/Libraries/LibWeb/Layout/LayoutFrame.h index 09a1e0e332..4dbc134fe8 100644 --- a/Libraries/LibWeb/Layout/LayoutFrame.h +++ b/Libraries/LibWeb/Layout/LayoutFrame.h @@ -37,7 +37,7 @@ public: virtual ~LayoutFrame() override; virtual void paint(PaintContext&, PaintPhase) override; - virtual void layout(LayoutMode) override; + virtual void prepare_for_replaced_layout() override; const HTML::HTMLIFrameElement& node() const { return downcast<HTML::HTMLIFrameElement>(LayoutReplaced::node()); } HTML::HTMLIFrameElement& node() { return downcast<HTML::HTMLIFrameElement>(LayoutReplaced::node()); } diff --git a/Libraries/LibWeb/Layout/LayoutImage.cpp b/Libraries/LibWeb/Layout/LayoutImage.cpp index dc76527722..b05d528f69 100644 --- a/Libraries/LibWeb/Layout/LayoutImage.cpp +++ b/Libraries/LibWeb/Layout/LayoutImage.cpp @@ -52,7 +52,7 @@ int LayoutImage::preferred_height() const return node().attribute(HTML::AttributeNames::height).to_int().value_or(m_image_loader.height()); } -void LayoutImage::layout(LayoutMode layout_mode) +void LayoutImage::prepare_for_replaced_layout() { if (!m_image_loader.has_loaded_or_failed()) { set_has_intrinsic_width(true); @@ -91,8 +91,6 @@ void LayoutImage::layout(LayoutMode layout_mode) set_width(16); set_height(16); } - - LayoutReplaced::layout(layout_mode); } void LayoutImage::paint(PaintContext& context, PaintPhase phase) diff --git a/Libraries/LibWeb/Layout/LayoutImage.h b/Libraries/LibWeb/Layout/LayoutImage.h index 6eefa962a9..740be79e8c 100644 --- a/Libraries/LibWeb/Layout/LayoutImage.h +++ b/Libraries/LibWeb/Layout/LayoutImage.h @@ -36,7 +36,7 @@ public: LayoutImage(DOM::Document&, DOM::Element&, NonnullRefPtr<CSS::StyleProperties>, const ImageLoader&); virtual ~LayoutImage() override; - virtual void layout(LayoutMode = LayoutMode::Default) override; + virtual void prepare_for_replaced_layout() override; virtual void paint(PaintContext&, PaintPhase) override; const DOM::Element& node() const { return static_cast<const DOM::Element&>(LayoutReplaced::node()); } diff --git a/Libraries/LibWeb/Layout/LayoutListItem.cpp b/Libraries/LibWeb/Layout/LayoutListItem.cpp index 67e1b506f8..5b8cc5165e 100644 --- a/Libraries/LibWeb/Layout/LayoutListItem.cpp +++ b/Libraries/LibWeb/Layout/LayoutListItem.cpp @@ -38,18 +38,15 @@ LayoutListItem::~LayoutListItem() { } -void LayoutListItem::layout(LayoutMode layout_mode) +void LayoutListItem::layout_marker() { if (m_marker) { remove_child(*m_marker); m_marker = nullptr; } - LayoutBlock::layout(layout_mode); - - if (specified_style().string_or_fallback(CSS::PropertyID::ListStyleType, "disc") == "none") { + if (specified_style().string_or_fallback(CSS::PropertyID::ListStyleType, "disc") == "none") return; - } if (!m_marker) { m_marker = adopt(*new LayoutListItemMarker(document())); diff --git a/Libraries/LibWeb/Layout/LayoutListItem.h b/Libraries/LibWeb/Layout/LayoutListItem.h index bb5cff174e..2bb8dcb80f 100644 --- a/Libraries/LibWeb/Layout/LayoutListItem.h +++ b/Libraries/LibWeb/Layout/LayoutListItem.h @@ -38,12 +38,17 @@ public: LayoutListItem(DOM::Document&, DOM::Element&, NonnullRefPtr<CSS::StyleProperties>); virtual ~LayoutListItem() override; - virtual void layout(LayoutMode = LayoutMode::Default) override; + void layout_marker(); private: virtual const char* class_name() const override { return "LayoutListItem"; } + virtual bool is_list_item() const override { return true; } RefPtr<LayoutListItemMarker> m_marker; }; } + +AK_BEGIN_TYPE_TRAITS(Web::LayoutListItem) +static bool is_type(const Web::LayoutNode& layout_node) { return layout_node.is_list_item(); } +AK_END_TYPE_TRAITS() diff --git a/Libraries/LibWeb/Layout/LayoutNode.cpp b/Libraries/LibWeb/Layout/LayoutNode.cpp index 3bd10abeea..2686904767 100644 --- a/Libraries/LibWeb/Layout/LayoutNode.cpp +++ b/Libraries/LibWeb/Layout/LayoutNode.cpp @@ -49,13 +49,6 @@ LayoutNode::~LayoutNode() m_node->set_layout_node({}, nullptr); } -void LayoutNode::layout(LayoutMode layout_mode) -{ - for_each_child([&](auto& child) { - child.layout(layout_mode); - }); -} - bool LayoutNode::can_contain_boxes_with_position_absolute() const { return style().position() != CSS::Position::Static || is_root(); diff --git a/Libraries/LibWeb/Layout/LayoutNode.h b/Libraries/LibWeb/Layout/LayoutNode.h index 8b955288af..12e8702c83 100644 --- a/Libraries/LibWeb/Layout/LayoutNode.h +++ b/Libraries/LibWeb/Layout/LayoutNode.h @@ -40,6 +40,12 @@ namespace Web { +enum class LayoutMode { + Default, + AllPossibleLineBreaks, + OnlyRequiredLineBreaks, +}; + struct HitTestResult { RefPtr<LayoutNode> layout_node; int index_in_node { 0 }; @@ -94,6 +100,7 @@ public: virtual bool is_break() const { return false; } virtual bool is_check_box() const { return false; } virtual bool is_button() const { return false; } + virtual bool is_list_item() const { return false; } bool has_style() const { return m_has_style; } bool is_inline() const { return m_inline; } @@ -107,14 +114,6 @@ public: virtual void handle_mouseup(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned modifiers); virtual void handle_mousemove(Badge<EventHandler>, const Gfx::IntPoint&, unsigned buttons, unsigned modifiers); - enum class LayoutMode { - Default, - AllPossibleLineBreaks, - OnlyRequiredLineBreaks, - }; - - virtual void layout(LayoutMode); - enum class PaintPhase { Background, Border, diff --git a/Libraries/LibWeb/Layout/LayoutReplaced.cpp b/Libraries/LibWeb/Layout/LayoutReplaced.cpp index a29d07a731..2e7e22bea5 100644 --- a/Libraries/LibWeb/Layout/LayoutReplaced.cpp +++ b/Libraries/LibWeb/Layout/LayoutReplaced.cpp @@ -117,20 +117,18 @@ float LayoutReplaced::calculate_height() const return used_height; } -void LayoutReplaced::layout(LayoutMode) +void LayoutReplaced::split_into_lines(LayoutBlock& container, LayoutMode) { - set_width(calculate_width()); - set_height(calculate_height()); -} - -void LayoutReplaced::split_into_lines(LayoutBlock& container, LayoutMode layout_mode) -{ - layout(layout_mode); + // FIXME: This feels out of place. It would be nice if someone at a higher level + // made sure we had usable geometry by the time we start splitting. + prepare_for_replaced_layout(); + auto width = calculate_width(); + auto height = calculate_height(); auto* line_box = &container.ensure_last_line_box(); - if (line_box->width() > 0 && line_box->width() + width() > container.width()) + if (line_box->width() > 0 && line_box->width() + width > container.width()) line_box = &container.add_line_box(); - line_box->add_fragment(*this, 0, 0, width(), height()); + line_box->add_fragment(*this, 0, 0, width, height); } } diff --git a/Libraries/LibWeb/Layout/LayoutReplaced.h b/Libraries/LibWeb/Layout/LayoutReplaced.h index e77c3b866a..a474534cec 100644 --- a/Libraries/LibWeb/Layout/LayoutReplaced.h +++ b/Libraries/LibWeb/Layout/LayoutReplaced.h @@ -57,14 +57,15 @@ public: void set_intrinsic_height(float height) { m_intrinsic_height = height; } void set_intrinsic_ratio(float ratio) { m_intrinsic_ratio = ratio; } + float calculate_width() const; + float calculate_height() const; + + virtual void prepare_for_replaced_layout() { } + protected: - virtual void layout(LayoutMode) override; virtual void split_into_lines(LayoutBlock& container, LayoutMode) override; private: - float calculate_width() const; - float calculate_height() const; - virtual const char* class_name() const override { return "LayoutReplaced"; } bool m_has_intrinsic_width { false }; diff --git a/Libraries/LibWeb/Layout/LayoutSVGGraphics.cpp b/Libraries/LibWeb/Layout/LayoutSVGGraphics.cpp index 91d3c9e9b7..787b7e16fd 100644 --- a/Libraries/LibWeb/Layout/LayoutSVGGraphics.cpp +++ b/Libraries/LibWeb/Layout/LayoutSVGGraphics.cpp @@ -49,11 +49,4 @@ void LayoutSVGGraphics::before_children_paint(PaintContext& context, LayoutNode: context.svg_context().set_stroke_width(graphics_element.stroke_width().value()); } -void LayoutSVGGraphics::layout(LayoutNode::LayoutMode mode) -{ - LayoutReplaced::layout(mode); - - for_each_child([&](auto& child) { child.layout(mode); }); -} - } diff --git a/Libraries/LibWeb/Layout/LayoutSVGGraphics.h b/Libraries/LibWeb/Layout/LayoutSVGGraphics.h index 36cdd8e025..a6e3352752 100644 --- a/Libraries/LibWeb/Layout/LayoutSVGGraphics.h +++ b/Libraries/LibWeb/Layout/LayoutSVGGraphics.h @@ -37,7 +37,6 @@ public: LayoutSVGGraphics(DOM::Document&, SVG::SVGGraphicsElement&, NonnullRefPtr<CSS::StyleProperties>); virtual ~LayoutSVGGraphics() override = default; - virtual void layout(LayoutMode mode) override; virtual void before_children_paint(PaintContext& context, LayoutNode::PaintPhase phase) override; private: diff --git a/Libraries/LibWeb/Layout/LayoutSVGPath.cpp b/Libraries/LibWeb/Layout/LayoutSVGPath.cpp index 2d6e554f8a..b33190c73b 100644 --- a/Libraries/LibWeb/Layout/LayoutSVGPath.cpp +++ b/Libraries/LibWeb/Layout/LayoutSVGPath.cpp @@ -35,15 +35,16 @@ LayoutSVGPath::LayoutSVGPath(DOM::Document& document, SVG::SVGPathElement& eleme { } -void LayoutSVGPath::layout(LayoutNode::LayoutMode mode) +void LayoutSVGPath::prepare_for_replaced_layout() { auto& bounding_box = node().get_path().bounding_box(); set_has_intrinsic_width(true); set_has_intrinsic_height(true); set_intrinsic_width(bounding_box.width()); set_intrinsic_height(bounding_box.height()); + + // FIXME: This does not belong here! Someone at a higher level should place this box. set_offset(bounding_box.top_left()); - LayoutSVGGraphics::layout(mode); } void LayoutSVGPath::paint(PaintContext& context, LayoutNode::PaintPhase phase) diff --git a/Libraries/LibWeb/Layout/LayoutSVGPath.h b/Libraries/LibWeb/Layout/LayoutSVGPath.h index a8ed8c725c..0ed38f34d1 100644 --- a/Libraries/LibWeb/Layout/LayoutSVGPath.h +++ b/Libraries/LibWeb/Layout/LayoutSVGPath.h @@ -37,8 +37,8 @@ public: SVG::SVGPathElement& node() { return downcast<SVG::SVGPathElement>(LayoutSVGGraphics::node()); } - void layout(LayoutMode mode) override; - void paint(PaintContext& context, PaintPhase phase) override; + virtual void prepare_for_replaced_layout() override; + virtual void paint(PaintContext& context, PaintPhase phase) override; private: virtual const char* class_name() const override { return "LayoutSVGPath"; } diff --git a/Libraries/LibWeb/Layout/LayoutSVGSVG.cpp b/Libraries/LibWeb/Layout/LayoutSVGSVG.cpp index c67fac9320..5b114c4b75 100644 --- a/Libraries/LibWeb/Layout/LayoutSVGSVG.cpp +++ b/Libraries/LibWeb/Layout/LayoutSVGSVG.cpp @@ -33,13 +33,12 @@ LayoutSVGSVG::LayoutSVGSVG(DOM::Document& document, SVG::SVGSVGElement& element, { } -void LayoutSVGSVG::layout(LayoutMode layout_mode) +void LayoutSVGSVG::prepare_for_replaced_layout() { set_has_intrinsic_width(true); set_has_intrinsic_height(true); set_intrinsic_width(node().width()); set_intrinsic_height(node().height()); - LayoutSVGGraphics::layout(layout_mode); } void LayoutSVGSVG::before_children_paint(PaintContext& context, LayoutNode::PaintPhase phase) diff --git a/Libraries/LibWeb/Layout/LayoutSVGSVG.h b/Libraries/LibWeb/Layout/LayoutSVGSVG.h index ed0aae5328..ea321d5a36 100644 --- a/Libraries/LibWeb/Layout/LayoutSVGSVG.h +++ b/Libraries/LibWeb/Layout/LayoutSVGSVG.h @@ -34,14 +34,14 @@ namespace Web { class LayoutSVGSVG final : public LayoutSVGGraphics { public: LayoutSVGSVG(DOM::Document&, SVG::SVGSVGElement&, NonnullRefPtr<CSS::StyleProperties>); - ~LayoutSVGSVG() override = default; + virtual ~LayoutSVGSVG() override = default; SVG::SVGSVGElement& node() { return downcast<SVG::SVGSVGElement>(LayoutSVGGraphics::node()); } - void layout(LayoutMode = LayoutMode::Default) override; + virtual void prepare_for_replaced_layout() override; - void before_children_paint(PaintContext& context, LayoutNode::PaintPhase phase) override; - void after_children_paint(PaintContext& context, PaintPhase phase) override; + virtual void before_children_paint(PaintContext& context, LayoutNode::PaintPhase phase) override; + virtual void after_children_paint(PaintContext& context, PaintPhase phase) override; private: const char* class_name() const override { return "LayoutSVGSVG"; } diff --git a/Libraries/LibWeb/Layout/LayoutTable.cpp b/Libraries/LibWeb/Layout/LayoutTable.cpp index 19b46a1461..721aa7daa9 100644 --- a/Libraries/LibWeb/Layout/LayoutTable.cpp +++ b/Libraries/LibWeb/Layout/LayoutTable.cpp @@ -39,9 +39,4 @@ LayoutTable::~LayoutTable() { } -void LayoutTable::layout(LayoutMode layout_mode) -{ - LayoutBlock::layout(layout_mode); -} - } diff --git a/Libraries/LibWeb/Layout/LayoutTable.h b/Libraries/LibWeb/Layout/LayoutTable.h index 8134bcf792..b94874a0b1 100644 --- a/Libraries/LibWeb/Layout/LayoutTable.h +++ b/Libraries/LibWeb/Layout/LayoutTable.h @@ -35,8 +35,6 @@ public: LayoutTable(DOM::Document&, DOM::Element&, NonnullRefPtr<CSS::StyleProperties>); virtual ~LayoutTable() override; - virtual void layout(LayoutMode = LayoutMode::Default) override; - private: virtual bool is_table() const override { return true; } virtual const char* class_name() const override { return "LayoutTable"; } diff --git a/Libraries/LibWeb/Layout/LayoutTableRow.cpp b/Libraries/LibWeb/Layout/LayoutTableRow.cpp index c2110db37e..a94e522c47 100644 --- a/Libraries/LibWeb/Layout/LayoutTableRow.cpp +++ b/Libraries/LibWeb/Layout/LayoutTableRow.cpp @@ -40,57 +40,4 @@ LayoutTableRow::~LayoutTableRow() { } -void LayoutTableRow::layout(LayoutMode) -{ -} - -void LayoutTableRow::calculate_column_widths(Vector<float>& column_widths) -{ - size_t column_index = 0; - auto* table = first_ancestor_of_type<LayoutTable>(); - bool use_auto_layout = !table || table->style().width().is_undefined_or_auto(); - for_each_child_of_type<LayoutTableCell>([&](auto& cell) { - if (use_auto_layout) { - cell.layout(LayoutMode::OnlyRequiredLineBreaks); - } else { - cell.layout(LayoutMode::Default); - } - column_widths[column_index] = max(column_widths[column_index], cell.width()); - column_index += cell.colspan(); - }); -} - -void LayoutTableRow::layout_row(const Vector<float>& column_widths) -{ - size_t column_index = 0; - float tallest_cell_height = 0; - float content_width = 0; - auto* table = first_ancestor_of_type<LayoutTable>(); - bool use_auto_layout = !table || table->style().width().is_undefined_or_auto(); - - for_each_child_of_type<LayoutTableCell>([&](auto& cell) { - cell.set_offset(effective_offset().translated(content_width, 0)); - - // Layout the cell contents a second time, now that we know its final width. - if (use_auto_layout) { - cell.layout_inside(LayoutMode::OnlyRequiredLineBreaks); - } else { - cell.layout_inside(LayoutMode::Default); - } - - 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.height()); - }); - - if (use_auto_layout) { - set_width(content_width); - } else { - set_width(table->width()); - } - - set_height(tallest_cell_height); -} - } diff --git a/Libraries/LibWeb/Layout/LayoutTableRow.h b/Libraries/LibWeb/Layout/LayoutTableRow.h index 41dbe6c734..a1855abe32 100644 --- a/Libraries/LibWeb/Layout/LayoutTableRow.h +++ b/Libraries/LibWeb/Layout/LayoutTableRow.h @@ -37,11 +37,7 @@ public: LayoutTableRow(DOM::Document&, DOM::Element&, NonnullRefPtr<CSS::StyleProperties>); virtual ~LayoutTableRow() override; - void layout_row(const Vector<float>& column_widths); - void calculate_column_widths(Vector<float>& column_widths); - private: - virtual void layout(LayoutMode) override; virtual bool is_table_row() const override { return true; } virtual const char* class_name() const override { return "LayoutTableRow"; } }; diff --git a/Libraries/LibWeb/Layout/LayoutTableRowGroup.cpp b/Libraries/LibWeb/Layout/LayoutTableRowGroup.cpp index 2b12e8e523..c35b775634 100644 --- a/Libraries/LibWeb/Layout/LayoutTableRowGroup.cpp +++ b/Libraries/LibWeb/Layout/LayoutTableRowGroup.cpp @@ -53,27 +53,4 @@ size_t LayoutTableRowGroup::column_count() const return table_column_count; } -void LayoutTableRowGroup::layout(LayoutMode) -{ - compute_width(); - - auto column_count = this->column_count(); - Vector<float> column_widths; - column_widths.resize(column_count); - - for_each_child_of_type<LayoutTableRow>([&](auto& row) { - row.calculate_column_widths(column_widths); - }); - - float content_height = 0; - - for_each_child_of_type<LayoutTableRow>([&](auto& row) { - row.set_offset(0, content_height); - row.layout_row(column_widths); - content_height += row.height(); - }); - - set_height(content_height); -} - } diff --git a/Libraries/LibWeb/Layout/LayoutTableRowGroup.h b/Libraries/LibWeb/Layout/LayoutTableRowGroup.h index 5cb15a33d3..a07ced75cb 100644 --- a/Libraries/LibWeb/Layout/LayoutTableRowGroup.h +++ b/Libraries/LibWeb/Layout/LayoutTableRowGroup.h @@ -35,11 +35,9 @@ public: LayoutTableRowGroup(DOM::Document&, DOM::Element&, NonnullRefPtr<CSS::StyleProperties>); virtual ~LayoutTableRowGroup() override; - virtual void layout(LayoutMode = LayoutMode::Default) override; - -private: size_t column_count() const; +private: virtual bool is_table_row_group() const override { return true; } virtual const char* class_name() const override { return "LayoutTableRowGroup"; } }; diff --git a/Libraries/LibWeb/Layout/LineBox.h b/Libraries/LibWeb/Layout/LineBox.h index cb28084e75..eeb177d638 100644 --- a/Libraries/LibWeb/Layout/LineBox.h +++ b/Libraries/LibWeb/Layout/LineBox.h @@ -49,6 +49,7 @@ public: private: friend class LayoutBlock; + friend class Layout::InlineFormattingContext; NonnullOwnPtrVector<LineBoxFragment> m_fragments; float m_width { 0 }; }; diff --git a/Libraries/LibWeb/Layout/TableFormattingContext.cpp b/Libraries/LibWeb/Layout/TableFormattingContext.cpp new file mode 100644 index 0000000000..a23d301d19 --- /dev/null +++ b/Libraries/LibWeb/Layout/TableFormattingContext.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <LibWeb/CSS/Length.h> +#include <LibWeb/DOM/Node.h> +#include <LibWeb/Layout/InlineFormattingContext.h> +#include <LibWeb/Layout/LayoutBlock.h> +#include <LibWeb/Layout/LayoutBox.h> +#include <LibWeb/Layout/LayoutTable.h> +#include <LibWeb/Layout/LayoutTableCell.h> +#include <LibWeb/Layout/LayoutTableRow.h> +#include <LibWeb/Layout/LayoutTableRowGroup.h> +#include <LibWeb/Layout/TableFormattingContext.h> +#include <LibWeb/Page/Frame.h> + +namespace Web::Layout { + +TableFormattingContext::TableFormattingContext(LayoutBox& context_box) + : BlockFormattingContext(context_box) +{ +} + +TableFormattingContext::~TableFormattingContext() +{ +} + +void TableFormattingContext::run(LayoutMode) +{ + compute_width(context_box()); + + context_box().for_each_child_of_type<LayoutTableRowGroup>([&](auto& box) { + compute_width(box); + auto column_count = box.column_count(); + Vector<float> column_widths; + column_widths.resize(column_count); + + box.template for_each_child_of_type<LayoutTableRow>([&](auto& row) { + calculate_column_widths(row, column_widths); + }); + + float content_height = 0; + + box.template for_each_child_of_type<LayoutTableRow>([&](auto& row) { + row.set_offset(0, content_height); + layout_row(row, column_widths); + content_height += row.height(); + }); + + box.set_height(content_height); + }); + + compute_height(context_box()); +} + +void TableFormattingContext::calculate_column_widths(LayoutBox& row, Vector<float>& column_widths) +{ + size_t column_index = 0; + auto* table = row.first_ancestor_of_type<LayoutTable>(); + bool use_auto_layout = !table || table->style().width().is_undefined_or_auto(); + row.for_each_child_of_type<LayoutTableCell>([&](auto& cell) { + compute_width(cell); + if (use_auto_layout) { + layout_inside(cell, LayoutMode::OnlyRequiredLineBreaks); + } else { + layout_inside(cell, LayoutMode::Default); + } + column_widths[column_index] = max(column_widths[column_index], cell.width()); + column_index += cell.colspan(); + }); +} + +void TableFormattingContext::layout_row(LayoutBox& row, Vector<float>& column_widths) +{ + size_t column_index = 0; + float tallest_cell_height = 0; + float content_width = 0; + auto* table = row.first_ancestor_of_type<LayoutTable>(); + bool use_auto_layout = !table || table->style().width().is_undefined_or_auto(); + + row.for_each_child_of_type<LayoutTableCell>([&](auto& cell) { + cell.set_offset(row.effective_offset().translated(content_width, 0)); + + // Layout the cell contents a second time, now that we know its final width. + if (use_auto_layout) { + layout_inside(cell, LayoutMode::OnlyRequiredLineBreaks); + } else { + layout_inside(cell, LayoutMode::Default); + } + + 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.height()); + }); + + if (use_auto_layout) { + row.set_width(content_width); + } else { + row.set_width(table->width()); + } + + row.set_height(tallest_cell_height); +} + +} diff --git a/Libraries/LibWeb/Layout/TableFormattingContext.h b/Libraries/LibWeb/Layout/TableFormattingContext.h new file mode 100644 index 0000000000..5ab4db75eb --- /dev/null +++ b/Libraries/LibWeb/Layout/TableFormattingContext.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include <AK/Forward.h> +#include <LibWeb/Layout/BlockFormattingContext.h> + +namespace Web::Layout { + +class TableFormattingContext final : public BlockFormattingContext { +public: + explicit TableFormattingContext(LayoutBox& containing_block); + ~TableFormattingContext(); + + virtual void run(LayoutMode) override; + +private: + void calculate_column_widths(LayoutBox& row, Vector<float>& column_widths); + void layout_row(LayoutBox& row, Vector<float>& column_widths); +}; + +} |