mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-21 03:55:24 +00:00
LibWeb: Teach line layout to collapse whitespace across fragments
This kind of HTML now produces a single piece of whitespace: <span> </span> <span> </span> <span> </span> We achieve this by checking if the last fragment on the last line ends in whitespace. If so, we either don't add a fragment at all (for the current chunk) or we simply skip over all whitespace at the head of the current chunk (instead of collapsing it to a single ' '.)
This commit is contained in:
parent
4ab1b0b436
commit
07ccaa1934
Notes:
sideshowbarker
2024-07-19 05:40:25 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/07ccaa19343
5 changed files with 46 additions and 15 deletions
|
@ -46,7 +46,7 @@ LayoutText::~LayoutText()
|
|||
{
|
||||
}
|
||||
|
||||
static bool is_all_whitespace(const String& string)
|
||||
static bool is_all_whitespace(const StringView& string)
|
||||
{
|
||||
for (size_t i = 0; i < string.length(); ++i) {
|
||||
if (!isspace(string[i]))
|
||||
|
@ -111,7 +111,8 @@ void LayoutText::for_each_chunk(Callback callback, LayoutMode layout_mode, bool
|
|||
int length = view.byte_offset_of(it) - view.byte_offset_of(start_of_chunk);
|
||||
|
||||
if (has_breaking_newline || length > 0) {
|
||||
callback(view.substring_view(start, length), start, length, has_breaking_newline);
|
||||
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;
|
||||
|
@ -160,17 +161,23 @@ void LayoutText::split_into_lines_by_rules(LayoutBlock& container, LayoutMode la
|
|||
if (do_collapse) {
|
||||
auto utf8_view = Utf8View(node().data());
|
||||
StringBuilder builder(node().data().length());
|
||||
for (auto it = utf8_view.begin(); it != utf8_view.end(); ++it) {
|
||||
auto it = utf8_view.begin();
|
||||
auto skip_over_whitespace = [&] {
|
||||
auto prev = it;
|
||||
while (it != utf8_view.end() && isspace(*it)) {
|
||||
prev = it;
|
||||
++it;
|
||||
}
|
||||
it = prev;
|
||||
};
|
||||
if (line_boxes.last().ends_in_whitespace())
|
||||
skip_over_whitespace();
|
||||
for (; it != utf8_view.end(); ++it) {
|
||||
if (!isspace(*it)) {
|
||||
builder.append(utf8_view.as_string().characters_without_null_termination() + utf8_view.byte_offset_of(it), it.codepoint_length_in_bytes());
|
||||
} else {
|
||||
builder.append(' ');
|
||||
auto prev = it;
|
||||
while (it != utf8_view.end() && isspace(*it)) {
|
||||
prev = it;
|
||||
++it;
|
||||
}
|
||||
it = prev;
|
||||
skip_over_whitespace();
|
||||
}
|
||||
}
|
||||
m_text_for_rendering = builder.to_string();
|
||||
|
@ -182,25 +189,30 @@ void LayoutText::split_into_lines_by_rules(LayoutBlock& container, LayoutMode la
|
|||
// !do_wrap_lines => chunks_are_lines
|
||||
struct Chunk {
|
||||
Utf8View view;
|
||||
int start;
|
||||
int length;
|
||||
bool is_break;
|
||||
int start { 0 };
|
||||
int length { 0 };
|
||||
bool is_break { false };
|
||||
bool is_all_whitespace { false };
|
||||
};
|
||||
Vector<Chunk> chunks;
|
||||
|
||||
for_each_chunk(
|
||||
[&](const Utf8View& view, int start, int length, bool is_break) {
|
||||
chunks.append({ Utf8View(view), start, length, is_break });
|
||||
[&](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 (size_t i = 0; i < chunks.size(); ++i) {
|
||||
auto& chunk = chunks[i];
|
||||
|
||||
// Collapse entire fragment into non-existence if previous fragment on line ended in whitespace.
|
||||
if (do_collapse && line_boxes.last().ends_in_whitespace() && chunk.is_all_whitespace)
|
||||
continue;
|
||||
|
||||
float chunk_width;
|
||||
bool need_collapse = false;
|
||||
if (do_wrap_lines) {
|
||||
bool need_collapse = do_collapse && isspace(*chunk.view.begin());
|
||||
need_collapse = do_collapse && isspace(*chunk.view.begin()) && line_boxes.last().ends_in_whitespace();
|
||||
|
||||
if (need_collapse)
|
||||
chunk_width = space_width;
|
||||
|
|
|
@ -73,4 +73,11 @@ void LineBox::trim_trailing_whitespace()
|
|||
}
|
||||
}
|
||||
|
||||
bool LineBox::ends_in_whitespace() const
|
||||
{
|
||||
if (m_fragments.is_empty())
|
||||
return false;
|
||||
return m_fragments.last().ends_in_whitespace();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -45,6 +45,8 @@ public:
|
|||
|
||||
void trim_trailing_whitespace();
|
||||
|
||||
bool ends_in_whitespace() const;
|
||||
|
||||
private:
|
||||
friend class LayoutBlock;
|
||||
NonnullOwnPtrVector<LineBoxFragment> m_fragments;
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include <LibWeb/Layout/LayoutText.h>
|
||||
#include <LibWeb/Layout/LineBoxFragment.h>
|
||||
#include <LibWeb/RenderingContext.h>
|
||||
#include <ctype.h>
|
||||
|
||||
namespace Web {
|
||||
|
||||
|
@ -45,6 +46,14 @@ void LineBoxFragment::render(RenderingContext& context)
|
|||
}
|
||||
}
|
||||
|
||||
bool LineBoxFragment::ends_in_whitespace() const
|
||||
{
|
||||
auto text = this->text();
|
||||
if (text.is_empty())
|
||||
return false;
|
||||
return isspace(text[text.length() - 1]);
|
||||
}
|
||||
|
||||
bool LineBoxFragment::is_justifiable_whitespace() const
|
||||
{
|
||||
return text() == " ";
|
||||
|
|
|
@ -62,6 +62,7 @@ public:
|
|||
|
||||
void render(RenderingContext&);
|
||||
|
||||
bool ends_in_whitespace() const;
|
||||
bool is_justifiable_whitespace() const;
|
||||
StringView text() const;
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue