From 00bde9ca51c3dbebe5a162ed82f8f68dca0cd58a Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Wed, 19 Jan 2022 11:57:58 +0100 Subject: [PATCH] LibWeb: Add Layout::LineBuilder class for incremental line box layout This class will be used to place items on lines incrementally instead of the current two-phase approach. --- Userland/Libraries/LibWeb/CMakeLists.txt | 1 + Userland/Libraries/LibWeb/Layout/LineBox.h | 2 + .../Libraries/LibWeb/Layout/LineBuilder.cpp | 124 ++++++++++++++++++ .../Libraries/LibWeb/Layout/LineBuilder.h | 39 ++++++ 4 files changed, 166 insertions(+) create mode 100644 Userland/Libraries/LibWeb/Layout/LineBuilder.cpp create mode 100644 Userland/Libraries/LibWeb/Layout/LineBuilder.h diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index f5aff4bdd2..928734fbbf 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -221,6 +221,7 @@ set(SOURCES Layout/LayoutPosition.cpp Layout/LineBox.cpp Layout/LineBoxFragment.cpp + Layout/LineBuilder.cpp Layout/ListItemBox.cpp Layout/ListItemMarkerBox.cpp Layout/Node.cpp diff --git a/Userland/Libraries/LibWeb/Layout/LineBox.h b/Userland/Libraries/LibWeb/Layout/LineBox.h index d28f2181a5..fa786b0a54 100644 --- a/Userland/Libraries/LibWeb/Layout/LineBox.h +++ b/Userland/Libraries/LibWeb/Layout/LineBox.h @@ -32,6 +32,8 @@ public: private: friend class BlockContainer; friend class InlineFormattingContext; + friend class LineBuilder; + NonnullOwnPtrVector m_fragments; float m_width { 0 }; }; diff --git a/Userland/Libraries/LibWeb/Layout/LineBuilder.cpp b/Userland/Libraries/LibWeb/Layout/LineBuilder.cpp new file mode 100644 index 0000000000..a80d6b7bd4 --- /dev/null +++ b/Userland/Libraries/LibWeb/Layout/LineBuilder.cpp @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2022, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace Web::Layout { + +LineBuilder::LineBuilder(InlineFormattingContext& context) + : m_context(context) +{ +} + +LineBuilder::~LineBuilder() +{ + update_last_line(); +} + +void LineBuilder::break_line() +{ + update_last_line(); + m_context.containing_block().line_boxes().append(LineBox()); + begin_new_line(); +} + +void LineBuilder::begin_new_line() +{ + m_current_y += m_max_height_on_current_line; + auto space = m_context.available_space_for_line(m_current_y); + m_available_width_for_current_line = space.right - space.left; + m_max_height_on_current_line = 0; +} + +void LineBuilder::append_box(Box& box) +{ + m_context.containing_block().line_boxes().last().add_fragment(box, 0, 0, box.width(), box.height()); + m_max_height_on_current_line = max(m_max_height_on_current_line, box.height()); +} + +void LineBuilder::append_text_chunk(TextNode& text_node, size_t offset_in_node, size_t length_in_node, float width, float height) +{ + m_context.containing_block().line_boxes().last().add_fragment(text_node, offset_in_node, length_in_node, width, height); + m_max_height_on_current_line = max(m_max_height_on_current_line, height); +} + +void LineBuilder::break_if_needed(LayoutMode layout_mode, float next_item_width) +{ + if (layout_mode == LayoutMode::AllPossibleLineBreaks + || (m_context.containing_block().line_boxes().last().width() + next_item_width) > m_available_width_for_current_line) + break_line(); +} + +void LineBuilder::update_last_line() +{ + if (m_context.containing_block().line_boxes().is_empty()) + return; + + auto& line_box = m_context.containing_block().line_boxes().last(); + + auto text_align = m_context.containing_block().computed_values().text_align(); + float x_offset = m_context.available_space_for_line(m_current_y).left; + + float excess_horizontal_space = m_context.containing_block().width() - line_box.width(); + + switch (text_align) { + case CSS::TextAlign::Center: + case CSS::TextAlign::LibwebCenter: + 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; + size_t 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 > 0 ? (excess_horizontal_space_including_whitespace / static_cast(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()), m_current_y + (m_max_height_on_current_line - fragment.height()) }); + + 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.translate_by(diff, 0); + line_box.fragments()[j].set_offset(offset); + } + } + } + + if (!line_box.fragments().is_empty()) { + float left_edge = line_box.fragments().first().offset().x(); + float right_edge = line_box.fragments().last().offset().x() + line_box.fragments().last().width(); + float final_line_box_width = right_edge - left_edge; + line_box.m_width = final_line_box_width; + } +} + +} diff --git a/Userland/Libraries/LibWeb/Layout/LineBuilder.h b/Userland/Libraries/LibWeb/Layout/LineBuilder.h new file mode 100644 index 0000000000..7dfe7bed8c --- /dev/null +++ b/Userland/Libraries/LibWeb/Layout/LineBuilder.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2022, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace Web::Layout { + +class LineBuilder { + AK_MAKE_NONCOPYABLE(LineBuilder); + AK_MAKE_NONMOVABLE(LineBuilder); + +public: + explicit LineBuilder(InlineFormattingContext&); + ~LineBuilder(); + + void break_line(); + void begin_new_line(); + void append_box(Box&); + void append_text_chunk(TextNode&, size_t offset_in_node, size_t length_in_node, float width, float height); + + void break_if_needed(LayoutMode, float next_item_width); + + float available_width_for_current_line() const { return m_available_width_for_current_line; } + + void update_last_line(); + +private: + InlineFormattingContext& m_context; + float m_available_width_for_current_line { 0 }; + float m_current_y { 0 }; + float m_max_height_on_current_line { 0 }; +}; + +}