summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibWeb/Layout
diff options
context:
space:
mode:
authorAndreas Kling <kling@serenityos.org>2021-04-27 13:05:50 +0200
committerAndreas Kling <kling@serenityos.org>2021-04-27 19:11:59 +0200
commit5074aaea69f7cec9673aa57ba277686c153d7862 (patch)
tree2d9f15065ae169087ba3920cc4c97c62d7736a5b /Userland/Libraries/LibWeb/Layout
parent03d8ee1082d11fafae58f6697edb7e7a04ec6b82 (diff)
downloadserenity-5074aaea69f7cec9673aa57ba277686c153d7862.zip
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.
Diffstat (limited to 'Userland/Libraries/LibWeb/Layout')
-rw-r--r--Userland/Libraries/LibWeb/Layout/TextNode.cpp162
-rw-r--r--Userland/Libraries/LibWeb/Layout/TextNode.h30
2 files changed, 115 insertions, 77 deletions
diff --git a/Userland/Libraries/LibWeb/Layout/TextNode.cpp b/Userland/Libraries/LibWeb/Layout/TextNode.cpp
index df362475e2..e10a57bd8a 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 <kling@serenityos.org>
+ * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
+#include <AK/ScopeGuard.h>
#include <AK/StringBuilder.h>
-#include <AK/Utf8View.h>
#include <LibGfx/Painter.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/Layout/BlockBox.h>
@@ -102,59 +102,6 @@ void TextNode::paint_cursor_if_needed(PaintContext& context, const LineBoxFragme
context.painter().draw_rect(cursor_rect, computed_values().color());
}
-template<typename Callback>
-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<Chunk, 128> 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<EventHandler>, const Gfx::IntPoint& positi
downcast<Label>(*parent()).handle_mousemove_on_label({}, position, button);
}
+TextNode::ChunkIterator::ChunkIterator(StringView const& text, LayoutMode layout_mode, bool wrap_lines, bool wrap_breaks)
+ : m_layout_mode(layout_mode)
+ , m_wrap_lines(wrap_lines)
+ , m_wrap_breaks(wrap_breaks)
+ , m_utf8_view(text)
+ , m_start_of_chunk(m_utf8_view.begin())
+ , m_iterator(m_utf8_view.begin())
+{
+ m_last_was_space = !text.is_empty() && isspace(*m_utf8_view.begin());
+}
+
+Optional<TextNode::Chunk> TextNode::ChunkIterator::next()
+{
+ while (m_iterator != m_utf8_view.end()) {
+ auto guard = ScopeGuard([&] { ++m_iterator; });
+ if (m_layout_mode == LayoutMode::AllPossibleLineBreaks) {
+ if (auto result = try_commit_chunk(m_iterator, false); result.has_value())
+ return result.release_value();
+ }
+ if (m_last_was_newline) {
+ m_last_was_newline = false;
+ if (auto result = try_commit_chunk(m_iterator, true); result.has_value())
+ return result.release_value();
+ }
+ if (m_wrap_breaks && *m_iterator == '\n') {
+ m_last_was_newline = true;
+ if (auto result = try_commit_chunk(m_iterator, false); result.has_value())
+ return result.release_value();
+ }
+ if (m_wrap_lines) {
+ bool is_space = isspace(*m_iterator);
+ if (is_space != m_last_was_space) {
+ m_last_was_space = is_space;
+ if (auto result = try_commit_chunk(m_iterator, false); result.has_value())
+ return result.release_value();
+ }
+ }
+ }
+
+ if (m_last_was_newline) {
+ m_last_was_newline = false;
+ if (auto result = try_commit_chunk(m_utf8_view.end(), true); result.has_value())
+ return result.release_value();
+ }
+ if (m_start_of_chunk != m_utf8_view.end()) {
+ if (auto result = try_commit_chunk(m_utf8_view.end(), false, true); result.has_value())
+ return result.release_value();
+ }
+
+ return {};
+}
+
+Optional<TextNode::Chunk> TextNode::ChunkIterator::try_commit_chunk(Utf8View::Iterator const& it, bool has_breaking_newline, bool must_commit)
+{
+ if (m_layout_mode == LayoutMode::OnlyRequiredLineBreaks && !must_commit)
+ return {};
+
+ auto start = m_utf8_view.byte_offset_of(m_start_of_chunk);
+ auto length = m_utf8_view.byte_offset_of(it) - m_utf8_view.byte_offset_of(m_start_of_chunk);
+
+ if (has_breaking_newline || length > 0) {
+ auto chunk_view = m_utf8_view.substring_view(start, length);
+ m_start_of_chunk = it;
+ return Chunk {
+ .view = chunk_view,
+ .start = start,
+ .length = length,
+ .has_breaking_newline = has_breaking_newline,
+ .is_all_whitespace = is_all_whitespace(chunk_view.as_string()),
+ };
+ }
+
+ m_start_of_chunk = it;
+ return {};
+}
+
}
diff --git a/Userland/Libraries/LibWeb/Layout/TextNode.h b/Userland/Libraries/LibWeb/Layout/TextNode.h
index ded15114d3..5597f42df0 100644
--- a/Userland/Libraries/LibWeb/Layout/TextNode.h
+++ b/Userland/Libraries/LibWeb/Layout/TextNode.h
@@ -6,6 +6,7 @@
#pragma once
+#include <AK/Utf8View.h>
#include <LibWeb/DOM/Text.h>
#include <LibWeb/Layout/Node.h>
@@ -26,6 +27,32 @@ public:
virtual void split_into_lines(InlineFormattingContext&, LayoutMode) override;
+ struct Chunk {
+ Utf8View view;
+ size_t start { 0 };
+ size_t length { 0 };
+ bool has_breaking_newline { false };
+ bool is_all_whitespace { false };
+ };
+
+ class ChunkIterator {
+ public:
+ ChunkIterator(StringView const& text, LayoutMode, bool wrap_lines, bool wrap_breaks);
+ Optional<Chunk> next();
+
+ private:
+ Optional<Chunk> try_commit_chunk(Utf8View::Iterator const&, bool has_breaking_newline, bool must_commit = false);
+
+ const LayoutMode m_layout_mode;
+ const bool m_wrap_lines;
+ const bool m_wrap_breaks;
+ bool m_last_was_space { false };
+ bool m_last_was_newline { false };
+ Utf8View m_utf8_view;
+ Utf8View::Iterator m_start_of_chunk;
+ Utf8View::Iterator m_iterator;
+ };
+
private:
virtual bool is_text_node() const final { return true; }
virtual bool wants_mouse_events() const override;
@@ -35,9 +62,6 @@ private:
void split_into_lines_by_rules(InlineFormattingContext&, LayoutMode, bool do_collapse, bool do_wrap_lines, bool do_wrap_breaks);
void paint_cursor_if_needed(PaintContext&, const LineBoxFragment&) const;
- template<typename Callback>
- void for_each_chunk(Callback, LayoutMode, bool do_wrap_lines, bool do_wrap_breaks) const;
-
String m_text_for_rendering;
};