LibUnicode+LibWeb: Add fast path grapheme segmenter for ASCII text
For ASCII text, every character is its own grapheme - there are no combining characters or emoji sequences. This means grapheme boundary detection is trivial: next_boundary(i) is simply i+1. This commit adds AsciiGraphemeSegmenter, a simple Segmenter subclass that performs O(1) boundary lookups without any ICU overhead. TextNode::grapheme_segmenter() now checks if the text is ASCII and uses this fast path, avoiding expensive ICU BreakIterator cloning and boundary detection for the common case of ASCII-only text.
This commit is contained in:
parent
10737e22d1
commit
8cdfbfed49
3 changed files with 101 additions and 2 deletions
|
|
@ -41,6 +41,92 @@ StringView segmenter_granularity_to_string(SegmenterGranularity segmenter_granul
|
|||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
// Fast path segmenter for ASCII text where every character is its own grapheme.
|
||||
// This avoids all ICU overhead for the common case of ASCII-only text.
|
||||
class AsciiGraphemeSegmenter : public Segmenter {
|
||||
public:
|
||||
explicit AsciiGraphemeSegmenter(size_t length)
|
||||
: Segmenter(SegmenterGranularity::Grapheme)
|
||||
, m_length(length)
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~AsciiGraphemeSegmenter() override = default;
|
||||
|
||||
virtual NonnullOwnPtr<Segmenter> clone() const override
|
||||
{
|
||||
return make<AsciiGraphemeSegmenter>(m_length);
|
||||
}
|
||||
|
||||
virtual void set_segmented_text(String text) override
|
||||
{
|
||||
m_length = text.byte_count();
|
||||
}
|
||||
|
||||
virtual void set_segmented_text(Utf16View const& text) override
|
||||
{
|
||||
m_length = text.length_in_code_units();
|
||||
}
|
||||
|
||||
virtual size_t current_boundary() override
|
||||
{
|
||||
return m_current;
|
||||
}
|
||||
|
||||
virtual Optional<size_t> previous_boundary(size_t index, Inclusive inclusive) override
|
||||
{
|
||||
if (inclusive == Inclusive::Yes && index <= m_length)
|
||||
return index;
|
||||
if (index == 0)
|
||||
return {};
|
||||
return index - 1;
|
||||
}
|
||||
|
||||
virtual Optional<size_t> next_boundary(size_t index, Inclusive inclusive) override
|
||||
{
|
||||
if (inclusive == Inclusive::Yes && index <= m_length)
|
||||
return index;
|
||||
if (index >= m_length)
|
||||
return {};
|
||||
return index + 1;
|
||||
}
|
||||
|
||||
virtual void for_each_boundary(String text, SegmentationCallback callback) override
|
||||
{
|
||||
set_segmented_text(move(text));
|
||||
for_each_boundary_impl(callback);
|
||||
}
|
||||
|
||||
virtual void for_each_boundary(Utf16View const& text, SegmentationCallback callback) override
|
||||
{
|
||||
set_segmented_text(text);
|
||||
for_each_boundary_impl(callback);
|
||||
}
|
||||
|
||||
virtual void for_each_boundary(Utf32View const& text, SegmentationCallback callback) override
|
||||
{
|
||||
m_length = text.length();
|
||||
for_each_boundary_impl(callback);
|
||||
}
|
||||
|
||||
virtual bool is_current_boundary_word_like() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
void for_each_boundary_impl(SegmentationCallback& callback)
|
||||
{
|
||||
for (size_t i = 0; i <= m_length; ++i) {
|
||||
if (callback(i) == IterationDecision::Break)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
size_t m_length { 0 };
|
||||
size_t m_current { 0 };
|
||||
};
|
||||
|
||||
class SegmenterImpl : public Segmenter {
|
||||
public:
|
||||
SegmenterImpl(NonnullOwnPtr<icu::BreakIterator> segmenter, SegmenterGranularity segmenter_granularity)
|
||||
|
|
@ -247,6 +333,11 @@ NonnullOwnPtr<Segmenter> Segmenter::create(StringView locale, SegmenterGranulari
|
|||
return make<SegmenterImpl>(segmenter.release_nonnull(), segmenter_granularity);
|
||||
}
|
||||
|
||||
NonnullOwnPtr<Segmenter> Segmenter::create_for_ascii_grapheme(size_t length)
|
||||
{
|
||||
return make<AsciiGraphemeSegmenter>(length);
|
||||
}
|
||||
|
||||
bool Segmenter::should_continue_beyond_word(Utf16View const& word)
|
||||
{
|
||||
for (auto code_point : word) {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ class Segmenter {
|
|||
public:
|
||||
static NonnullOwnPtr<Segmenter> create(SegmenterGranularity segmenter_granularity);
|
||||
static NonnullOwnPtr<Segmenter> create(StringView locale, SegmenterGranularity segmenter_granularity);
|
||||
static NonnullOwnPtr<Segmenter> create_for_ascii_grapheme(size_t length);
|
||||
virtual ~Segmenter() = default;
|
||||
|
||||
static bool should_continue_beyond_word(Utf16View const&);
|
||||
|
|
|
|||
|
|
@ -396,8 +396,15 @@ void TextNode::compute_text_for_rendering()
|
|||
Unicode::Segmenter& TextNode::grapheme_segmenter() const
|
||||
{
|
||||
if (!m_grapheme_segmenter) {
|
||||
m_grapheme_segmenter = document().grapheme_segmenter().clone();
|
||||
m_grapheme_segmenter->set_segmented_text(text_for_rendering());
|
||||
auto const& text = text_for_rendering();
|
||||
// Fast path: For ASCII text, every character is its own grapheme.
|
||||
// We can use a trivial segmenter that avoids all ICU overhead.
|
||||
if (text.is_ascii()) {
|
||||
m_grapheme_segmenter = Unicode::Segmenter::create_for_ascii_grapheme(text.length_in_code_units());
|
||||
} else {
|
||||
m_grapheme_segmenter = document().grapheme_segmenter().clone();
|
||||
m_grapheme_segmenter->set_segmented_text(text);
|
||||
}
|
||||
}
|
||||
|
||||
return *m_grapheme_segmenter;
|
||||
|
|
|
|||
Loading…
Reference in a new issue