mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-22 23:50:19 +00:00
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.
This commit is contained in:
parent
03d8ee1082
commit
5074aaea69
Notes:
sideshowbarker
2024-07-18 19:01:36 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/5074aaea69f
2 changed files with 115 additions and 77 deletions
|
@ -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 {};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue