From 7fe3f2d970304207bf22c7ea8db9bfe425281926 Mon Sep 17 00:00:00 2001 From: sin-ack Date: Sat, 12 Mar 2022 17:01:06 +0000 Subject: [PATCH] LibWeb: Refactor text justification code + only justify below threshold All the justification-related code is now in InlineFormattingContext::apply_justification_to_fragments and is performed after all the line boxes have been added. Text justification now only happens on the last line if the excess space including whitespace is below a certain threshold. 10% seemed reasonable since it prevents the "over-justification" of text. Note that fragments in line boxes before the last one are always justified. --- .../LibWeb/Layout/InlineFormattingContext.cpp | 52 +++++++++++++++++++ .../LibWeb/Layout/InlineFormattingContext.h | 1 + .../Libraries/LibWeb/Layout/LineBuilder.cpp | 26 ---------- 3 files changed, 53 insertions(+), 26 deletions(-) diff --git a/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.cpp index 28fd861989..ef367604cc 100644 --- a/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.cpp @@ -17,6 +17,8 @@ namespace Web::Layout { +constexpr float text_justification_threshold = 0.1; + InlineFormattingContext::InlineFormattingContext(FormattingState& state, BlockContainer const& containing_block, BlockFormattingContext& parent) : FormattingContext(Type::Inline, state, containing_block, &parent) { @@ -164,6 +166,45 @@ void InlineFormattingContext::dimension_box_on_line(Box const& box, LayoutMode l dump_tree(box); } +void InlineFormattingContext::apply_justification_to_fragments(FormattingState::NodeState const& containing_block_state, LineBox& line_box, bool is_last_line) +{ + float excess_horizontal_space = containing_block_state.content_width - line_box.width(); + + // Only justify the text if the excess horizontal space is less than or + // equal to 10%, or if we are not looking at the last line box. + if (is_last_line && excess_horizontal_space / containing_block_state.content_width > text_justification_threshold) + return; + + float excess_horizontal_space_including_whitespace = excess_horizontal_space; + size_t whitespace_count = 0; + 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; + + // This is the amount that each fragment will be offset by. If a whitespace + // fragment is shorter than the justified space width, it increases to push + // subsequent fragments, and decreases to pull them back otherwise. + float running_diff = 0; + for (size_t i = 0; i < line_box.fragments().size(); ++i) { + auto& fragment = line_box.fragments()[i]; + + auto offset = fragment.offset(); + offset.translate_by(running_diff, 0); + fragment.set_offset(offset); + + if (fragment.is_justifiable_whitespace() + && fragment.width() != justified_space_width) { + running_diff += justified_space_width - fragment.width(); + fragment.set_width(justified_space_width); + } + } +} + void InlineFormattingContext::generate_line_boxes(LayoutMode layout_mode) { auto& containing_block_state = m_state.get_mutable(containing_block()); @@ -221,6 +262,17 @@ void InlineFormattingContext::generate_line_boxes(LayoutMode layout_mode) } line_builder.remove_last_line_if_empty(); + + auto const& containing_block = this->containing_block(); + auto text_align = containing_block.computed_values().text_align(); + if (text_align == CSS::TextAlign::Justify) { + auto const& containing_block_state = m_state.get(containing_block); + for (size_t i = 0; i < line_boxes.size(); i++) { + auto& line_box = line_boxes[i]; + auto is_last_line = i == line_boxes.size() - 1; + apply_justification_to_fragments(containing_block_state, line_box, is_last_line); + } + } } } diff --git a/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.h b/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.h index c449bdd98e..5d2c9b596e 100644 --- a/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.h +++ b/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.h @@ -36,6 +36,7 @@ public: private: void generate_line_boxes(LayoutMode); + void apply_justification_to_fragments(FormattingState::NodeState const& containing_block_state, LineBox&, bool is_last_line); }; } diff --git a/Userland/Libraries/LibWeb/Layout/LineBuilder.cpp b/Userland/Libraries/LibWeb/Layout/LineBuilder.cpp index 65c4db3880..750b73c89b 100644 --- a/Userland/Libraries/LibWeb/Layout/LineBuilder.cpp +++ b/Userland/Libraries/LibWeb/Layout/LineBuilder.cpp @@ -125,19 +125,6 @@ void LineBuilder::update_last_line() 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; - auto fragment_baseline = [&](auto const& fragment) -> float { if (fragment.layout_node().is_text_node()) return fragment.layout_node().font().baseline(); @@ -193,19 +180,6 @@ void LineBuilder::update_last_line() fragment.set_offset({ new_fragment_x, new_fragment_y }); bottom = max(bottom, new_fragment_y + fragment.height() + fragment.border_box_bottom()); - - 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); - } - } } line_box.m_bottom = bottom;