diff --git a/Tests/LibWeb/Layout/expected/nowrap-and-no-line-break-opportunity.txt b/Tests/LibWeb/Layout/expected/nowrap-and-no-line-break-opportunity.txt new file mode 100644 index 0000000000..3bdbfa2df4 --- /dev/null +++ b/Tests/LibWeb/Layout/expected/nowrap-and-no-line-break-opportunity.txt @@ -0,0 +1,30 @@ +Viewport <#document> at (0,0) content-size 800x600 children: not-inline + BlockContainer at (0,0) content-size 800x600 [BFC] children: not-inline + BlockContainer at (8,8) content-size 784x19.46875 children: not-inline + BlockContainer at (9,9) content-size 50x17.46875 children: inline + line 0 width: 79.40625, height: 17.46875, bottom: 17.46875, baseline: 13.53125 + frag 0 from TextNode start: 0, length: 3, rect: [9,9 33.921875x17.46875] + "ABC" + frag 1 from TextNode start: 0, length: 1, rect: [43,9 11.5625x17.46875] + "X" + frag 2 from TextNode start: 0, length: 3, rect: [54,9 33.921875x17.46875] + "ABC" + TextNode <#text> + InlineNode + TextNode <#text> + InlineNode + TextNode <#text> + InlineNode + TextNode <#text> + TextNode <#text> + +ViewportPaintable (Viewport<#document>) [0,0 800x600] + PaintableWithLines (BlockContainer) [0,0 800x600] + PaintableWithLines (BlockContainer) [8,8 784x19.46875] + PaintableWithLines (BlockContainer
.fixed_width) [8,8 52x19.46875] overflow: [9,9 78.921875x17.46875] + InlinePaintable (InlineNode.nowrap) + TextPaintable (TextNode<#text>) + InlinePaintable (InlineNode) + TextPaintable (TextNode<#text>) + InlinePaintable (InlineNode.nowrap) + TextPaintable (TextNode<#text>) diff --git a/Tests/LibWeb/Layout/expected/space-is-soft-line-break-opportunity.txt b/Tests/LibWeb/Layout/expected/space-is-soft-line-break-opportunity.txt new file mode 100644 index 0000000000..e91ee892d6 --- /dev/null +++ b/Tests/LibWeb/Layout/expected/space-is-soft-line-break-opportunity.txt @@ -0,0 +1,28 @@ +Viewport <#document> at (0,0) content-size 800x600 children: not-inline + BlockContainer at (0,0) content-size 800x600 [BFC] children: not-inline + BlockContainer at (8,8) content-size 784x37.40625 children: not-inline + BlockContainer at (9,9) content-size 50x35.40625 children: inline + line 0 width: 33.921875, height: 17.46875, bottom: 17.46875, baseline: 13.53125 + frag 0 from TextNode start: 0, length: 3, rect: [9,9 33.921875x17.46875] + "ABC" + line 1 width: 33.921875, height: 17.9375, bottom: 35.40625, baseline: 13.53125 + frag 0 from TextNode start: 0, length: 3, rect: [9,26 33.921875x17.46875] + "ABC" + TextNode <#text> + InlineNode + TextNode <#text> + InlineNode + TextNode <#text> + InlineNode + TextNode <#text> + TextNode <#text> + +ViewportPaintable (Viewport<#document>) [0,0 800x600] + PaintableWithLines (BlockContainer) [0,0 800x600] + PaintableWithLines (BlockContainer) [8,8 784x37.40625] + PaintableWithLines (BlockContainer
.fixed_width) [8,8 52x37.40625] + InlinePaintable (InlineNode.nowrap) + TextPaintable (TextNode<#text>) + InlinePaintable (InlineNode) + InlinePaintable (InlineNode.nowrap) + TextPaintable (TextNode<#text>) diff --git a/Tests/LibWeb/Layout/expected/table/line-breaking-in-cells.txt b/Tests/LibWeb/Layout/expected/table/line-breaking-in-cells.txt new file mode 100644 index 0000000000..38e8287ce3 --- /dev/null +++ b/Tests/LibWeb/Layout/expected/table/line-breaking-in-cells.txt @@ -0,0 +1,58 @@ +Viewport <#document> at (0,0) content-size 800x600 children: not-inline + BlockContainer at (0,0) content-size 800x600 [BFC] children: not-inline + BlockContainer at (8,8) content-size 784x39.40625 children: not-inline + TableWrapper <(anonymous)> at (8,8) content-size 61x39.40625 [BFC] children: not-inline + Box at (8,8) content-size 61x39.40625 table-box [TFC] children: not-inline + BlockContainer <(anonymous)> (not painted) children: inline + TextNode <#text> + Box at (8,8) content-size 61x39.40625 table-row-group children: not-inline + BlockContainer <(anonymous)> (not painted) children: inline + TextNode <#text> + Box at (8,8) content-size 61x39.40625 table-row children: not-inline + BlockContainer <(anonymous)> (not painted) children: inline + TextNode <#text> + BlockContainer
at (10,18.96875) content-size 14.296875x17.46875 table-cell [BFC] children: inline + line 0 width: 14.265625, height: 17.46875, bottom: 17.46875, baseline: 13.53125 + frag 0 from TextNode start: 0, length: 1, rect: [10,18.96875 14.265625x17.46875] + "A" + TextNode <#text> + BlockContainer <(anonymous)> (not painted) children: inline + TextNode <#text> + BlockContainer at (28.296875,10) content-size 20.40625x35.40625 table-cell [BFC] children: inline + line 0 width: 9.34375, height: 17.46875, bottom: 17.46875, baseline: 13.53125 + frag 0 from TextNode start: 1, length: 1, rect: [28.296875,10 9.34375x17.46875] + "B" + line 1 width: 10.3125, height: 17.9375, bottom: 35.40625, baseline: 13.53125 + frag 0 from TextNode start: 0, length: 1, rect: [28.296875,27 10.3125x17.46875] + "C" + TextNode <#text> + BreakNode
+ TextNode <#text> + BlockContainer <(anonymous)> (not painted) children: inline + TextNode <#text> + BlockContainer
at (52.703125,18.96875) content-size 14.296875x17.46875 table-cell [BFC] children: inline + line 0 width: 11.140625, height: 17.46875, bottom: 17.46875, baseline: 13.53125 + frag 0 from TextNode start: 1, length: 1, rect: [52.703125,18.96875 11.140625x17.46875] + "D" + TextNode <#text> + BlockContainer <(anonymous)> (not painted) children: inline + TextNode <#text> + BlockContainer <(anonymous)> (not painted) children: inline + TextNode <#text> + BlockContainer <(anonymous)> (not painted) children: inline + TextNode <#text> + +ViewportPaintable (Viewport<#document>) [0,0 800x600] + PaintableWithLines (BlockContainer) [0,0 800x600] + PaintableWithLines (BlockContainer) [8,8 784x39.40625] + PaintableWithLines (TableWrapper(anonymous)) [8,8 61x39.40625] + PaintableBox (Box) [8,8 61x39.40625] + PaintableBox (Box) [8,8 61x39.40625] + PaintableBox (Box) [8,8 61x39.40625] + PaintableWithLines (BlockContainer
) [8,8 18.296875x39.40625] + TextPaintable (TextNode<#text>) + PaintableWithLines (BlockContainer) [26.296875,8 24.40625x39.40625] + TextPaintable (TextNode<#text>) + TextPaintable (TextNode<#text>) + PaintableWithLines (BlockContainer) [50.703125,8 18.296875x39.40625] + TextPaintable (TextNode<#text>) diff --git a/Tests/LibWeb/Layout/input/nowrap-and-no-line-break-opportunity.html b/Tests/LibWeb/Layout/input/nowrap-and-no-line-break-opportunity.html new file mode 100644 index 0000000000..3895659553 --- /dev/null +++ b/Tests/LibWeb/Layout/input/nowrap-and-no-line-break-opportunity.html @@ -0,0 +1,13 @@ + +
+ ABCXABC +
\ No newline at end of file diff --git a/Tests/LibWeb/Layout/input/space-is-soft-line-break-opportunity.html b/Tests/LibWeb/Layout/input/space-is-soft-line-break-opportunity.html new file mode 100644 index 0000000000..f6831a013a --- /dev/null +++ b/Tests/LibWeb/Layout/input/space-is-soft-line-break-opportunity.html @@ -0,0 +1,13 @@ + +
+ ABC ABC +
\ No newline at end of file diff --git a/Tests/LibWeb/Layout/input/table/line-breaking-in-cells.html b/Tests/LibWeb/Layout/input/table/line-breaking-in-cells.html new file mode 100644 index 0000000000..b9cb8242af --- /dev/null +++ b/Tests/LibWeb/Layout/input/table/line-breaking-in-cells.html @@ -0,0 +1,25 @@ + + + + + + + + + + +
A + + B +
C +
+ D +
\ No newline at end of file diff --git a/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.cpp index ef211d7efd..1a544056f4 100644 --- a/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.cpp @@ -250,8 +250,14 @@ void InlineFormattingContext::generate_line_boxes(LayoutMode layout_mode) auto& item = item_opt.value(); // Ignore collapsible whitespace chunks at the start of line, and if the last fragment already ends in whitespace. - if (item.is_collapsible_whitespace && (line_boxes.is_empty() || line_boxes.last().is_empty_or_ends_in_whitespace())) + if (item.is_collapsible_whitespace && (line_boxes.is_empty() || line_boxes.last().is_empty_or_ends_in_whitespace())) { + if (item.node->computed_values().white_space() != CSS::WhiteSpace::Nowrap) { + auto next_width = iterator.next_non_whitespace_sequence_width(); + if (next_width > 0) + line_builder.break_if_needed(next_width); + } continue; + } switch (item.type) { case InlineLevelIterator::Item::Type::ForcedBreak: { @@ -287,17 +293,24 @@ void InlineFormattingContext::generate_line_boxes(LayoutMode layout_mode) case InlineLevelIterator::Item::Type::Text: { auto& text_node = verify_cast(*item.node); - if (text_node.computed_values().white_space() != CSS::WhiteSpace::Nowrap && line_builder.break_if_needed(item.border_box_width())) { + if (text_node.computed_values().white_space() != CSS::WhiteSpace::Nowrap) { + bool is_whitespace = false; + CSSPixels next_width = 0; + // If we're in a whitespace-collapsing context, we can simply check the flag. + if (item.is_collapsible_whitespace) { + is_whitespace = true; + next_width = iterator.next_non_whitespace_sequence_width(); + } else { + // In whitespace-preserving contexts (white-space: pre*), we have to check manually. + auto view = text_node.text_for_rendering().substring_view(item.offset_in_node, item.length_in_node); + is_whitespace = view.is_whitespace(); + if (is_whitespace) + next_width = iterator.next_non_whitespace_sequence_width(); + } + // If whitespace caused us to break, we swallow the whitespace instead of // putting it on the next line. - - // If we're in a whitespace-collapsing context, we can simply check the flag. - if (item.is_collapsible_whitespace) - break; - - // In whitespace-preserving contexts (white-space: pre*), we have to check manually. - auto view = text_node.text_for_rendering().substring_view(item.offset_in_node, item.length_in_node); - if (view.is_whitespace()) + if (is_whitespace && next_width > 0 && line_builder.break_if_needed(item.border_box_width() + next_width)) break; } line_builder.append_text_chunk( diff --git a/Userland/Libraries/LibWeb/Layout/InlineLevelIterator.cpp b/Userland/Libraries/LibWeb/Layout/InlineLevelIterator.cpp index 368df00247..daaf49664c 100644 --- a/Userland/Libraries/LibWeb/Layout/InlineLevelIterator.cpp +++ b/Userland/Libraries/LibWeb/Layout/InlineLevelIterator.cpp @@ -125,6 +125,37 @@ void InlineLevelIterator::skip_to_next() } Optional InlineLevelIterator::next() +{ + if (m_lookahead_items.is_empty()) + return next_without_lookahead(); + return m_lookahead_items.dequeue(); +} + +CSSPixels InlineLevelIterator::next_non_whitespace_sequence_width() +{ + CSSPixels next_width = 0; + for (;;) { + auto next_item_opt = next_without_lookahead(); + if (!next_item_opt.has_value()) + break; + m_lookahead_items.enqueue(next_item_opt.release_value()); + auto& next_item = m_lookahead_items.tail(); + if (next_item.node->computed_values().white_space() != CSS::WhiteSpace::Nowrap) { + if (next_item.type != InlineLevelIterator::Item::Type::Text) + break; + if (next_item.is_collapsible_whitespace) + break; + auto& next_text_node = verify_cast(*(next_item.node)); + auto next_view = next_text_node.text_for_rendering().substring_view(next_item.offset_in_node, next_item.length_in_node); + if (next_view.is_whitespace()) + break; + } + next_width += next_item.border_box_width(); + } + return next_width; +} + +Optional InlineLevelIterator::next_without_lookahead() { if (!m_current_node) return {}; @@ -139,7 +170,7 @@ Optional InlineLevelIterator::next() if (!chunk_opt.has_value()) { m_text_node_context = {}; skip_to_next(); - return next(); + return next_without_lookahead(); } m_text_node_context->next_chunk = m_text_node_context->chunk_iterator.next(); @@ -200,12 +231,12 @@ Optional InlineLevelIterator::next() if (is(*m_current_node)) { skip_to_next(); - return next(); + return next_without_lookahead(); } if (!is(*m_current_node)) { skip_to_next(); - return next(); + return next_without_lookahead(); } if (is(*m_current_node)) { diff --git a/Userland/Libraries/LibWeb/Layout/InlineLevelIterator.h b/Userland/Libraries/LibWeb/Layout/InlineLevelIterator.h index a8f15f48fb..0f194923c0 100644 --- a/Userland/Libraries/LibWeb/Layout/InlineLevelIterator.h +++ b/Userland/Libraries/LibWeb/Layout/InlineLevelIterator.h @@ -52,8 +52,10 @@ public: InlineLevelIterator(Layout::InlineFormattingContext&, LayoutState&, Layout::BlockContainer const&, LayoutMode); Optional next(); + CSSPixels next_non_whitespace_sequence_width(); private: + Optional next_without_lookahead(); void skip_to_next(); void compute_next(); @@ -96,6 +98,7 @@ private: Optional m_extra_trailing_metrics; Vector> m_box_model_node_stack; + Queue m_lookahead_items; }; }