From 5074aaea69f7cec9673aa57ba277686c153d7862 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Tue, 27 Apr 2021 13:05:50 +0200 Subject: [PATCH] LibWeb: Refactor Layout::TextNode splitting into a chunk iterator Creating a ChunkIterator allows you to iterate over the text in a Layout::TextNode at your leisure by calling next() when you want another chunk. This is one of many steps towards improving inline layout. --- Userland/Libraries/LibWeb/Layout/TextNode.cpp | 162 ++++++++++-------- Userland/Libraries/LibWeb/Layout/TextNode.h | 30 +++- 2 files changed, 115 insertions(+), 77 deletions(-) diff --git a/Userland/Libraries/LibWeb/Layout/TextNode.cpp b/Userland/Libraries/LibWeb/Layout/TextNode.cpp index df362475e22..e10a57bd8ae 100644 --- a/Userland/Libraries/LibWeb/Layout/TextNode.cpp +++ b/Userland/Libraries/LibWeb/Layout/TextNode.cpp @@ -1,11 +1,11 @@ /* - * Copyright (c) 2018-2020, Andreas Kling + * Copyright (c) 2018-2021, Andreas Kling * * SPDX-License-Identifier: BSD-2-Clause */ +#include #include -#include #include #include #include @@ -102,59 +102,6 @@ void TextNode::paint_cursor_if_needed(PaintContext& context, const LineBoxFragme context.painter().draw_rect(cursor_rect, computed_values().color()); } -template -void TextNode::for_each_chunk(Callback callback, LayoutMode layout_mode, bool do_wrap_lines, bool do_wrap_breaks) const -{ - Utf8View view(m_text_for_rendering); - if (view.is_empty()) - return; - - auto start_of_chunk = view.begin(); - - auto commit_chunk = [&](auto it, bool has_breaking_newline, bool must_commit = false) { - if (layout_mode == LayoutMode::OnlyRequiredLineBreaks && !must_commit) - return; - - int start = view.byte_offset_of(start_of_chunk); - int length = view.byte_offset_of(it) - view.byte_offset_of(start_of_chunk); - - if (has_breaking_newline || length > 0) { - auto chunk_view = view.substring_view(start, length); - callback(chunk_view, start, length, has_breaking_newline, is_all_whitespace(chunk_view.as_string())); - } - - start_of_chunk = it; - }; - - bool last_was_space = isspace(*view.begin()); - bool last_was_newline = false; - for (auto it = view.begin(); it != view.end();) { - if (layout_mode == LayoutMode::AllPossibleLineBreaks) { - commit_chunk(it, false); - } - if (last_was_newline) { - last_was_newline = false; - commit_chunk(it, true); - } - if (do_wrap_breaks && *it == '\n') { - last_was_newline = true; - commit_chunk(it, false); - } - if (do_wrap_lines) { - bool is_space = isspace(*it); - if (is_space != last_was_space) { - last_was_space = is_space; - commit_chunk(it, false); - } - } - ++it; - } - if (last_was_newline) - commit_chunk(view.end(), true); - if (start_of_chunk != view.end()) - commit_chunk(view.end(), false, true); -} - void TextNode::split_into_lines_by_rules(InlineFormattingContext& context, LayoutMode layout_mode, bool do_collapse, bool do_wrap_lines, bool do_wrap_breaks) { auto& containing_block = context.containing_block(); @@ -193,22 +140,15 @@ void TextNode::split_into_lines_by_rules(InlineFormattingContext& context, Layou m_text_for_rendering = dom_node().data(); } - // do_wrap_lines => chunks_are_words - // !do_wrap_lines => chunks_are_lines - struct Chunk { - Utf8View view; - int start { 0 }; - int length { 0 }; - bool is_break { false }; - bool is_all_whitespace { false }; - }; Vector chunks; + ChunkIterator iterator(m_text_for_rendering, layout_mode, do_wrap_lines, do_wrap_breaks); - for_each_chunk( - [&](const Utf8View& view, int start, int length, bool is_break, bool is_all_whitespace) { - chunks.append({ Utf8View(view), start, length, is_break, is_all_whitespace }); - }, - layout_mode, do_wrap_lines, do_wrap_breaks); + for (;;) { + auto chunk = iterator.next(); + if (!chunk.has_value()) + break; + chunks.append(chunk.release_value()); + } for (size_t i = 0; i < chunks.size(); ++i) { auto& chunk = chunks[i]; @@ -251,11 +191,9 @@ void TextNode::split_into_lines_by_rules(InlineFormattingContext& context, Layou } } - if (do_wrap_breaks) { - if (chunk.is_break) { - containing_block.add_line_box(); - available_width = context.available_width_at_line(line_boxes.size() - 1); - } + if (do_wrap_breaks && chunk.has_breaking_newline) { + containing_block.add_line_box(); + available_width = context.available_width_at_line(line_boxes.size() - 1); } } } @@ -319,4 +257,80 @@ void TextNode::handle_mousemove(Badge, const Gfx::IntPoint& positi downcast