From e1a24edfa9e85213ea44b19f16f848841d1f4405 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Sun, 22 Nov 2020 13:38:18 +0100 Subject: [PATCH] LibWeb: Reorganize layout system in terms of formatting contexts This is a first (huge) step towards modernizing the layout architecture and bringing it closer to spec language. Layout is now performed by a stack of formatting contexts, operating on the box tree (or layout tree, if you will.) There are currently three types of formatting context: - BlockFormattingContext (BFC) - InlineFormattingContext (IFC) - TableFormattingContext (TFC) Document::layout() creates the initial BlockFormattingContext (BFC) which lays out the initial containing block (ICB), and then we recurse through the tree, creating BFC, IFC or TFC as appropriate and handing over control at the context boundaries. The majority of this patch is just refactoring the old logic spread out in LayoutBlock and LayoutTableRowGroup, and turning into these context classes instead. A lot more cleanup will be needed. There are many architectural wins here, the main one being that layout is no longer performed by boxes themselves, which gives us much greater flexibility in the outer/inner layout of a given box. --- Libraries/LibWeb/CMakeLists.txt | 4 + Libraries/LibWeb/DOM/Document.cpp | 7 +- Libraries/LibWeb/Forward.h | 11 + .../LibWeb/Layout/BlockFormattingContext.cpp | 632 +++++++++++++++++ .../LibWeb/Layout/BlockFormattingContext.h | 61 ++ Libraries/LibWeb/Layout/FormattingContext.cpp | 89 +++ Libraries/LibWeb/Layout/FormattingContext.h | 57 ++ .../LibWeb/Layout/InlineFormattingContext.cpp | 197 ++++++ .../LibWeb/Layout/InlineFormattingContext.h | 45 ++ Libraries/LibWeb/Layout/LayoutBlock.cpp | 651 ------------------ Libraries/LibWeb/Layout/LayoutBlock.h | 33 - Libraries/LibWeb/Layout/LayoutBox.cpp | 20 + Libraries/LibWeb/Layout/LayoutBox.h | 11 + Libraries/LibWeb/Layout/LayoutButton.cpp | 3 +- Libraries/LibWeb/Layout/LayoutButton.h | 2 +- Libraries/LibWeb/Layout/LayoutCanvas.cpp | 3 +- Libraries/LibWeb/Layout/LayoutCanvas.h | 2 +- Libraries/LibWeb/Layout/LayoutCheckBox.cpp | 13 +- Libraries/LibWeb/Layout/LayoutCheckBox.h | 1 - Libraries/LibWeb/Layout/LayoutDocument.cpp | 28 - Libraries/LibWeb/Layout/LayoutDocument.h | 1 - Libraries/LibWeb/Layout/LayoutFrame.cpp | 4 +- Libraries/LibWeb/Layout/LayoutFrame.h | 2 +- Libraries/LibWeb/Layout/LayoutImage.cpp | 4 +- Libraries/LibWeb/Layout/LayoutImage.h | 2 +- Libraries/LibWeb/Layout/LayoutListItem.cpp | 7 +- Libraries/LibWeb/Layout/LayoutListItem.h | 7 +- Libraries/LibWeb/Layout/LayoutNode.cpp | 7 - Libraries/LibWeb/Layout/LayoutNode.h | 15 +- Libraries/LibWeb/Layout/LayoutReplaced.cpp | 18 +- Libraries/LibWeb/Layout/LayoutReplaced.h | 11 +- Libraries/LibWeb/Layout/LayoutSVGGraphics.cpp | 7 - Libraries/LibWeb/Layout/LayoutSVGGraphics.h | 1 - Libraries/LibWeb/Layout/LayoutSVGPath.cpp | 5 +- Libraries/LibWeb/Layout/LayoutSVGPath.h | 4 +- Libraries/LibWeb/Layout/LayoutSVGSVG.cpp | 3 +- Libraries/LibWeb/Layout/LayoutSVGSVG.h | 8 +- Libraries/LibWeb/Layout/LayoutTable.cpp | 5 - Libraries/LibWeb/Layout/LayoutTable.h | 2 - Libraries/LibWeb/Layout/LayoutTableRow.cpp | 53 -- Libraries/LibWeb/Layout/LayoutTableRow.h | 4 - .../LibWeb/Layout/LayoutTableRowGroup.cpp | 23 - Libraries/LibWeb/Layout/LayoutTableRowGroup.h | 4 +- Libraries/LibWeb/Layout/LineBox.h | 1 + .../LibWeb/Layout/TableFormattingContext.cpp | 128 ++++ .../LibWeb/Layout/TableFormattingContext.h | 46 ++ 46 files changed, 1360 insertions(+), 882 deletions(-) create mode 100644 Libraries/LibWeb/Layout/BlockFormattingContext.cpp create mode 100644 Libraries/LibWeb/Layout/BlockFormattingContext.h create mode 100644 Libraries/LibWeb/Layout/FormattingContext.cpp create mode 100644 Libraries/LibWeb/Layout/FormattingContext.h create mode 100644 Libraries/LibWeb/Layout/InlineFormattingContext.cpp create mode 100644 Libraries/LibWeb/Layout/InlineFormattingContext.h create mode 100644 Libraries/LibWeb/Layout/TableFormattingContext.cpp create mode 100644 Libraries/LibWeb/Layout/TableFormattingContext.h diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index b97707bdfb6..a164f3046c9 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -133,7 +133,10 @@ set(SOURCES HTML/Parser/StackOfOpenElements.cpp HighResolutionTime/Performance.cpp InProcessWebView.cpp + Layout/BlockFormattingContext.cpp Layout/BoxModelMetrics.cpp + Layout/FormattingContext.cpp + Layout/InlineFormattingContext.cpp Layout/LayoutBlock.cpp Layout/LayoutBox.cpp Layout/LayoutBreak.cpp @@ -162,6 +165,7 @@ set(SOURCES Layout/LayoutWidget.cpp Layout/LineBox.cpp Layout/LineBoxFragment.cpp + Layout/TableFormattingContext.cpp LayoutTreeModel.cpp Loader/FrameLoader.cpp Loader/ImageLoader.cpp diff --git a/Libraries/LibWeb/DOM/Document.cpp b/Libraries/LibWeb/DOM/Document.cpp index d93ed285b0a..0a1fe0e506b 100644 --- a/Libraries/LibWeb/DOM/Document.cpp +++ b/Libraries/LibWeb/DOM/Document.cpp @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -52,6 +53,7 @@ #include #include #include +#include #include #include #include @@ -348,7 +350,10 @@ void Document::layout() LayoutTreeBuilder tree_builder; m_layout_root = static_ptr_cast(tree_builder.build(*this)); } - m_layout_root->layout(); + + Layout::BlockFormattingContext root_formatting_context(*m_layout_root); + root_formatting_context.run(LayoutMode::Default); + m_layout_root->set_needs_display(); if (frame()->is_main_frame()) { diff --git a/Libraries/LibWeb/Forward.h b/Libraries/LibWeb/Forward.h index 4ad2f6ba160..ed56031c91d 100644 --- a/Libraries/LibWeb/Forward.h +++ b/Libraries/LibWeb/Forward.h @@ -26,6 +26,10 @@ #pragma once +namespace Web { +enum class LayoutMode; +} + namespace Web::CSS { class Selector; class StyleProperties; @@ -146,12 +150,19 @@ class SVGPathElement; class SVGSVGElement; } +namespace Web::Layout { +class BlockFormattingContext; +class FormattingContext; +class InlineFormattingContext; +} + namespace Web { class EventHandler; class Frame; class FrameLoader; class InProcessWebView; class LayoutBlock; +class LayoutBox; class LayoutButton; class LayoutCheckBox; class LayoutDocument; diff --git a/Libraries/LibWeb/Layout/BlockFormattingContext.cpp b/Libraries/LibWeb/Layout/BlockFormattingContext.cpp new file mode 100644 index 00000000000..3c22fc1d529 --- /dev/null +++ b/Libraries/LibWeb/Layout/BlockFormattingContext.cpp @@ -0,0 +1,632 @@ +/* + * Copyright (c) 2020, Andreas Kling + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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(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(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([&](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(box)) + downcast(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(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(); + 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(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([&](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([&](auto& widget) { + widget.update_widget(); + return IterationDecision::Continue; + }); +} + +void BlockFormattingContext::layout_absolutely_positioned_descendants() +{ + context_box().for_each_in_subtree_of_type([&](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 00000000000..88b2773ecd5 --- /dev/null +++ b/Libraries/LibWeb/Layout/BlockFormattingContext.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2020, Andreas Kling + * 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 +#include + +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 00000000000..1b525028135 --- /dev/null +++ b/Libraries/LibWeb/Layout/FormattingContext.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2020, Andreas Kling + * 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 +#include +#include +#include +#include + +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([&](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 00000000000..0b681b88f06 --- /dev/null +++ b/Libraries/LibWeb/Layout/FormattingContext.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020, Andreas Kling + * 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 + +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 00000000000..5edcce81928 --- /dev/null +++ b/Libraries/LibWeb/Layout/InlineFormattingContext.cpp @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2020, Andreas Kling + * 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 +#include +#include +#include +#include +#include +#include +#include + +namespace Web::Layout { + +InlineFormattingContext::InlineFormattingContext(LayoutBox& containing_block) + : FormattingContext(containing_block) +{ +} + +InlineFormattingContext::~InlineFormattingContext() +{ +} + +void InlineFormattingContext::run(LayoutMode layout_mode) +{ + auto& containing_block = downcast(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(downcast(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(context_box()); + + if (box.is_replaced()) { + auto& replaced = const_cast(downcast(box)); + replaced.set_width(replaced.calculate_width()); + replaced.set_height(replaced.calculate_height()); + return; + } + + if (box.is_inline_block()) { + auto& inline_block = const_cast(downcast(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 00000000000..6534b6d6be4 --- /dev/null +++ b/Libraries/LibWeb/Layout/InlineFormattingContext.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020, Andreas Kling + * 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 +#include + +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 d0acaa4e896..e673c31a58c 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([&](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([&](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(box)); - else if (box.is_block()) - place_block_level_non_replaced_element_in_normal_flow(downcast(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(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(downcast(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(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 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 aa663628b7f..7e5c4341c24 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& line_boxes() { return m_line_boxes; } - const Vector& 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(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 style_for_anonymous_block() const; - - void layout_inline_children(LayoutMode); - void layout_contained_boxes(LayoutMode); - - Vector m_line_boxes; }; template diff --git a/Libraries/LibWeb/Layout/LayoutBox.cpp b/Libraries/LibWeb/Layout/LayoutBox.cpp index 9cfed06ff40..80ee295d02a 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 004175ee995..f5e971e6b0d 100644 --- a/Libraries/LibWeb/Layout/LayoutBox.h +++ b/Libraries/LibWeb/Layout/LayoutBox.h @@ -29,6 +29,7 @@ #include #include #include +#include #include namespace Web { @@ -70,6 +71,14 @@ public: virtual void paint(PaintContext&, PaintPhase) override; + Vector& line_boxes() { return m_line_boxes; } + const Vector& line_boxes() const { return m_line_boxes; } + + LineBox& ensure_last_line_box(); + LineBox& add_line_box(); + + virtual float width_of_logical_containing_block() const; + protected: LayoutBox(DOM::Document& document, DOM::Node* node, NonnullRefPtr style) : LayoutNodeWithStyleAndBoxModelMetrics(document, node, move(style)) @@ -78,6 +87,8 @@ protected: virtual void did_set_rect() { } + Vector 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 653de61a8ef..3322894f64c 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 29153eb0547..685e4d67225 100644 --- a/Libraries/LibWeb/Layout/LayoutButton.h +++ b/Libraries/LibWeb/Layout/LayoutButton.h @@ -36,7 +36,7 @@ public: LayoutButton(DOM::Document&, HTML::HTMLInputElement&, NonnullRefPtr); 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(LayoutReplaced::node()); } diff --git a/Libraries/LibWeb/Layout/LayoutCanvas.cpp b/Libraries/LibWeb/Layout/LayoutCanvas.cpp index cca04e6ac06..e78b17c456f 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 b42f0f463b6..09a4d634ba7 100644 --- a/Libraries/LibWeb/Layout/LayoutCanvas.h +++ b/Libraries/LibWeb/Layout/LayoutCanvas.h @@ -36,7 +36,7 @@ public: LayoutCanvas(DOM::Document&, HTML::HTMLCanvasElement&, NonnullRefPtr); 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(LayoutReplaced::node()); } diff --git a/Libraries/LibWeb/Layout/LayoutCheckBox.cpp b/Libraries/LibWeb/Layout/LayoutCheckBox.cpp index b63137b2b03..3195044057f 100644 --- a/Libraries/LibWeb/Layout/LayoutCheckBox.cpp +++ b/Libraries/LibWeb/Layout/LayoutCheckBox.cpp @@ -35,20 +35,15 @@ namespace Web { LayoutCheckBox::LayoutCheckBox(DOM::Document& document, HTML::HTMLInputElement& element, NonnullRefPtr 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 8b9644a0d67..ed495e35f7d 100644 --- a/Libraries/LibWeb/Layout/LayoutCheckBox.h +++ b/Libraries/LibWeb/Layout/LayoutCheckBox.h @@ -36,7 +36,6 @@ public: LayoutCheckBox(DOM::Document&, HTML::HTMLInputElement&, NonnullRefPtr); virtual ~LayoutCheckBox() override; - virtual void layout(LayoutMode = LayoutMode::Default) override; virtual void paint(PaintContext&, PaintPhase) override; const HTML::HTMLInputElement& node() const { return static_cast(LayoutReplaced::node()); } diff --git a/Libraries/LibWeb/Layout/LayoutDocument.cpp b/Libraries/LibWeb/Layout/LayoutDocument.cpp index 772ca83df9e..9a5b1b0632a 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(child)); - auto& child_block = downcast(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([&](auto& widget) { - widget.update_widget(); - return IterationDecision::Continue; - }); -} - void LayoutDocument::did_set_viewport_rect(Badge, 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 0e5d39aebeb..96d6a4c425e 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(*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 59dec1ea021..5f9b3b0236d 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 09a1e0e332e..4dbc134fe82 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(LayoutReplaced::node()); } HTML::HTMLIFrameElement& node() { return downcast(LayoutReplaced::node()); } diff --git a/Libraries/LibWeb/Layout/LayoutImage.cpp b/Libraries/LibWeb/Layout/LayoutImage.cpp index dc765277223..b05d528f698 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 6eefa962a98..740be79e8c7 100644 --- a/Libraries/LibWeb/Layout/LayoutImage.h +++ b/Libraries/LibWeb/Layout/LayoutImage.h @@ -36,7 +36,7 @@ public: LayoutImage(DOM::Document&, DOM::Element&, NonnullRefPtr, 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(LayoutReplaced::node()); } diff --git a/Libraries/LibWeb/Layout/LayoutListItem.cpp b/Libraries/LibWeb/Layout/LayoutListItem.cpp index 67e1b506f8a..5b8cc5165e8 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 bb5cff174e5..2bb8dcb80f2 100644 --- a/Libraries/LibWeb/Layout/LayoutListItem.h +++ b/Libraries/LibWeb/Layout/LayoutListItem.h @@ -38,12 +38,17 @@ public: LayoutListItem(DOM::Document&, DOM::Element&, NonnullRefPtr); 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 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 3bd10abeea0..26869047679 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 8b955288af9..12e8702c838 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 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, const Gfx::IntPoint&, unsigned button, unsigned modifiers); virtual void handle_mousemove(Badge, 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 a29d07a7316..2e7e22bea54 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 e77c3b866ae..a474534cec2 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; } -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 void prepare_for_replaced_layout() { } + +protected: + virtual void split_into_lines(LayoutBlock& container, LayoutMode) override; + +private: 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 91d3c9e9b72..787b7e16fde 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 36cdd8e0258..a6e33527523 100644 --- a/Libraries/LibWeb/Layout/LayoutSVGGraphics.h +++ b/Libraries/LibWeb/Layout/LayoutSVGGraphics.h @@ -37,7 +37,6 @@ public: LayoutSVGGraphics(DOM::Document&, SVG::SVGGraphicsElement&, NonnullRefPtr); 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 2d6e554f8aa..b33190c73ba 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 a8ed8c725c9..0ed38f34d16 100644 --- a/Libraries/LibWeb/Layout/LayoutSVGPath.h +++ b/Libraries/LibWeb/Layout/LayoutSVGPath.h @@ -37,8 +37,8 @@ public: SVG::SVGPathElement& node() { return downcast(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 c67fac9320e..5b114c4b751 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 ed0aae53280..ea321d5a364 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); - ~LayoutSVGSVG() override = default; + virtual ~LayoutSVGSVG() override = default; SVG::SVGSVGElement& node() { return downcast(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 19b46a1461d..721aa7daa97 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 8134bcf7920..b94874a0b1c 100644 --- a/Libraries/LibWeb/Layout/LayoutTable.h +++ b/Libraries/LibWeb/Layout/LayoutTable.h @@ -35,8 +35,6 @@ public: LayoutTable(DOM::Document&, DOM::Element&, NonnullRefPtr); 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 c2110db37ee..a94e522c476 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& column_widths) -{ - size_t column_index = 0; - auto* table = first_ancestor_of_type(); - bool use_auto_layout = !table || table->style().width().is_undefined_or_auto(); - for_each_child_of_type([&](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& column_widths) -{ - size_t column_index = 0; - float tallest_cell_height = 0; - float content_width = 0; - auto* table = first_ancestor_of_type(); - bool use_auto_layout = !table || table->style().width().is_undefined_or_auto(); - - for_each_child_of_type([&](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 41dbe6c734e..a1855abe326 100644 --- a/Libraries/LibWeb/Layout/LayoutTableRow.h +++ b/Libraries/LibWeb/Layout/LayoutTableRow.h @@ -37,11 +37,7 @@ public: LayoutTableRow(DOM::Document&, DOM::Element&, NonnullRefPtr); virtual ~LayoutTableRow() override; - void layout_row(const Vector& column_widths); - void calculate_column_widths(Vector& 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 2b12e8e5239..c35b7756349 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 column_widths; - column_widths.resize(column_count); - - for_each_child_of_type([&](auto& row) { - row.calculate_column_widths(column_widths); - }); - - float content_height = 0; - - for_each_child_of_type([&](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 5cb15a33d38..a07ced75cb0 100644 --- a/Libraries/LibWeb/Layout/LayoutTableRowGroup.h +++ b/Libraries/LibWeb/Layout/LayoutTableRowGroup.h @@ -35,11 +35,9 @@ public: LayoutTableRowGroup(DOM::Document&, DOM::Element&, NonnullRefPtr); 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 cb28084e750..eeb177d6380 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 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 00000000000..a23d301d19a --- /dev/null +++ b/Libraries/LibWeb/Layout/TableFormattingContext.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2020, Andreas Kling + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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([&](auto& box) { + compute_width(box); + auto column_count = box.column_count(); + Vector column_widths; + column_widths.resize(column_count); + + box.template for_each_child_of_type([&](auto& row) { + calculate_column_widths(row, column_widths); + }); + + float content_height = 0; + + box.template for_each_child_of_type([&](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& column_widths) +{ + size_t column_index = 0; + auto* table = row.first_ancestor_of_type(); + bool use_auto_layout = !table || table->style().width().is_undefined_or_auto(); + row.for_each_child_of_type([&](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& column_widths) +{ + size_t column_index = 0; + float tallest_cell_height = 0; + float content_width = 0; + auto* table = row.first_ancestor_of_type(); + bool use_auto_layout = !table || table->style().width().is_undefined_or_auto(); + + row.for_each_child_of_type([&](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 00000000000..5ab4db75eb4 --- /dev/null +++ b/Libraries/LibWeb/Layout/TableFormattingContext.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2020, Andreas Kling + * 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 +#include + +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& column_widths); + void layout_row(LayoutBox& row, Vector& column_widths); +}; + +}