From 79d2c9f3e83008cb9ebad2ccd84a623f25c561e4 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Sat, 10 Jun 2023 15:49:17 +0200 Subject: [PATCH] LibWeb: Don't justify text lines that end in a forced break These are treated the same as the last line in a block, per CSS-TEXT-3. --- .../text-align-justify-with-forced-break.txt | 75 +++++++++++++++++++ .../text-align-justify-with-forced-break.html | 14 ++++ .../LibWeb/Layout/InlineFormattingContext.cpp | 14 ++-- Userland/Libraries/LibWeb/Layout/LineBox.h | 1 + .../Libraries/LibWeb/Layout/LineBuilder.cpp | 5 +- .../Libraries/LibWeb/Layout/LineBuilder.h | 9 ++- 6 files changed, 106 insertions(+), 12 deletions(-) create mode 100644 Tests/LibWeb/Layout/expected/text-align-justify-with-forced-break.txt create mode 100644 Tests/LibWeb/Layout/input/text-align-justify-with-forced-break.html diff --git a/Tests/LibWeb/Layout/expected/text-align-justify-with-forced-break.txt b/Tests/LibWeb/Layout/expected/text-align-justify-with-forced-break.txt new file mode 100644 index 00000000000..c1f1ba5f929 --- /dev/null +++ b/Tests/LibWeb/Layout/expected/text-align-justify-with-forced-break.txt @@ -0,0 +1,75 @@ +Viewport <#document> at (0,0) content-size 800x600 children: not-inline + BlockContainer at (0,0) content-size 800x118 [BFC] children: not-inline + BlockContainer at (8,8) content-size 784x102 children: not-inline + BlockContainer
at (9,9) content-size 100x100 children: inline + line 0 width: 98, height: 17.46875, bottom: 17.46875, baseline: 13.53125 + frag 0 from TextNode start: 1, length: 3, rect: [9,9 27.15625x17.46875] + "foo" + frag 1 from TextNode start: 4, length: 1, rect: [36,9 9x17.46875] + " " + frag 2 from TextNode start: 5, length: 3, rect: [45,9 27.640625x17.46875] + "bar" + frag 3 from TextNode start: 8, length: 1, rect: [73,9 9x17.46875] + " " + frag 4 from TextNode start: 9, length: 3, rect: [82,9 27.203125x17.46875] + "baz" + line 1 width: 98, height: 17.9375, bottom: 35.40625, baseline: 13.53125 + frag 0 from TextNode start: 13, length: 3, rect: [9,26 27.15625x17.46875] + "foo" + frag 1 from TextNode start: 16, length: 1, rect: [36,26 8x17.46875] + " " + frag 2 from TextNode start: 17, length: 3, rect: [44,26 27.640625x17.46875] + "bar" + frag 3 from TextNode start: 20, length: 1, rect: [72,26 8x17.46875] + " " + frag 4 from TextNode start: 21, length: 3, rect: [80,26 27.203125x17.46875] + "baz" + line 2 width: 98, height: 18.40625, bottom: 53.34375, baseline: 13.53125 + frag 0 from TextNode start: 1, length: 3, rect: [9,43 27.15625x17.46875] + "foo" + frag 1 from TextNode start: 4, length: 1, rect: [36,43 9x17.46875] + " " + frag 2 from TextNode start: 5, length: 3, rect: [45,43 27.640625x17.46875] + "bar" + frag 3 from TextNode start: 8, length: 1, rect: [73,43 9x17.46875] + " " + frag 4 from TextNode start: 9, length: 3, rect: [82,43 27.203125x17.46875] + "baz" + line 3 width: 98, height: 17.875, bottom: 70.28125, baseline: 13.53125 + frag 0 from TextNode start: 13, length: 3, rect: [9,61 27.15625x17.46875] + "foo" + frag 1 from TextNode start: 16, length: 1, rect: [36,61 9x17.46875] + " " + frag 2 from TextNode start: 17, length: 3, rect: [45,61 27.640625x17.46875] + "bar" + frag 3 from TextNode start: 20, length: 1, rect: [73,61 9x17.46875] + " " + frag 4 from TextNode start: 21, length: 3, rect: [82,61 27.203125x17.46875] + "baz" + line 4 width: 98, height: 18.34375, bottom: 88.21875, baseline: 13.53125 + frag 0 from TextNode start: 25, length: 3, rect: [9,78 27.15625x17.46875] + "foo" + frag 1 from TextNode start: 28, length: 1, rect: [36,78 8x17.46875] + " " + frag 2 from TextNode start: 29, length: 3, rect: [44,78 27.640625x17.46875] + "bar" + frag 3 from TextNode start: 32, length: 1, rect: [72,78 8x17.46875] + " " + frag 4 from TextNode start: 33, length: 3, rect: [80,78 27.203125x17.46875] + "baz" + line 5 width: 98, height: 17.8125, bottom: 105.15625, baseline: 13.53125 + frag 0 from TextNode start: 1, length: 3, rect: [9,96 27.15625x17.46875] + "foo" + frag 1 from TextNode start: 4, length: 1, rect: [36,96 8x17.46875] + " " + frag 2 from TextNode start: 5, length: 3, rect: [44,96 27.640625x17.46875] + "bar" + frag 3 from TextNode start: 8, length: 1, rect: [72,96 8x17.46875] + " " + frag 4 from TextNode start: 9, length: 3, rect: [80,96 27.203125x17.46875] + "baz" + TextNode <#text> + BreakNode
+ TextNode <#text> + BreakNode
+ TextNode <#text> diff --git a/Tests/LibWeb/Layout/input/text-align-justify-with-forced-break.html b/Tests/LibWeb/Layout/input/text-align-justify-with-forced-break.html new file mode 100644 index 00000000000..db61142aa2f --- /dev/null +++ b/Tests/LibWeb/Layout/input/text-align-justify-with-forced-break.html @@ -0,0 +1,14 @@ +
+ foo bar baz + foo bar baz
+ foo bar baz + foo bar baz + foo bar baz
+ foo bar baz diff --git a/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.cpp index 66e0c42f726..b9e1238b566 100644 --- a/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.cpp @@ -18,8 +18,6 @@ namespace Web::Layout { -constexpr double text_justification_threshold = 0.1; - InlineFormattingContext::InlineFormattingContext(LayoutState& state, BlockContainer const& containing_block, BlockFormattingContext& parent) : FormattingContext(Type::Inline, state, containing_block, &parent) , m_containing_block_state(state.get(containing_block)) @@ -191,13 +189,13 @@ void InlineFormattingContext::apply_justification_to_fragments(CSS::TextJustify break; } - CSSPixels excess_horizontal_space = line_box.original_available_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 / m_available_space->width.to_px().value() > text_justification_threshold) + // https://www.w3.org/TR/css-text-3/#text-align-property + // Unless otherwise specified by text-align-last, the last line before a forced break or the end of the block is start-aligned. + // FIXME: Support text-align-last. + if (is_last_line || line_box.m_has_forced_break) return; + CSSPixels excess_horizontal_space = line_box.original_available_width() - line_box.width(); CSSPixels excess_horizontal_space_including_whitespace = excess_horizontal_space; size_t whitespace_count = 0; for (auto& fragment : line_box.fragments()) { @@ -249,7 +247,7 @@ void InlineFormattingContext::generate_line_boxes(LayoutMode layout_mode) switch (item.type) { case InlineLevelIterator::Item::Type::ForcedBreak: - line_builder.break_line(); + line_builder.break_line(LineBuilder::ForcedBreak::Yes); break; case InlineLevelIterator::Item::Type::Element: { auto& box = verify_cast(*item.node); diff --git a/Userland/Libraries/LibWeb/Layout/LineBox.h b/Userland/Libraries/LibWeb/Layout/LineBox.h index 533a007ed52..31bf60fd03e 100644 --- a/Userland/Libraries/LibWeb/Layout/LineBox.h +++ b/Userland/Libraries/LibWeb/Layout/LineBox.h @@ -47,6 +47,7 @@ private: CSSPixels m_original_available_width { 0 }; bool m_has_break { false }; + bool m_has_forced_break { false }; }; } diff --git a/Userland/Libraries/LibWeb/Layout/LineBuilder.cpp b/Userland/Libraries/LibWeb/Layout/LineBuilder.cpp index 80e199c610f..ab621181b3a 100644 --- a/Userland/Libraries/LibWeb/Layout/LineBuilder.cpp +++ b/Userland/Libraries/LibWeb/Layout/LineBuilder.cpp @@ -25,10 +25,11 @@ LineBuilder::~LineBuilder() update_last_line(); } -void LineBuilder::break_line(Optional next_item_width) +void LineBuilder::break_line(ForcedBreak forced_break, Optional next_item_width) { - auto last_line_box = ensure_last_line_box(); + auto& last_line_box = ensure_last_line_box(); last_line_box.m_has_break = true; + last_line_box.m_has_forced_break = forced_break == ForcedBreak::Yes; update_last_line(); size_t break_count = 0; diff --git a/Userland/Libraries/LibWeb/Layout/LineBuilder.h b/Userland/Libraries/LibWeb/Layout/LineBuilder.h index 35964764d94..fc194872e3f 100644 --- a/Userland/Libraries/LibWeb/Layout/LineBuilder.h +++ b/Userland/Libraries/LibWeb/Layout/LineBuilder.h @@ -18,7 +18,12 @@ public: LineBuilder(InlineFormattingContext&, LayoutState&); ~LineBuilder(); - void break_line(Optional next_item_width = {}); + enum class ForcedBreak { + No, + Yes, + }; + + void break_line(ForcedBreak, Optional next_item_width = {}); void append_box(Box const&, CSSPixels leading_size, CSSPixels trailing_size, CSSPixels leading_margin, CSSPixels trailing_margin); void append_text_chunk(TextNode const&, size_t offset_in_node, size_t length_in_node, CSSPixels leading_size, CSSPixels trailing_size, CSSPixels leading_margin, CSSPixels trailing_margin, CSSPixels content_width, CSSPixels content_height); @@ -26,7 +31,7 @@ public: bool break_if_needed(CSSPixels next_item_width) { if (should_break(next_item_width)) { - break_line(next_item_width); + break_line(LineBuilder::ForcedBreak::No, next_item_width); return true; } return false;