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