208 lines
7.2 KiB
C++
208 lines
7.2 KiB
C++
/*
|
|
* Copyright (c) 2018-2020, Andreas Kling <andreas@ladybird.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <AK/String.h>
|
|
#include <AK/Utf16String.h>
|
|
#include <AK/Utf16View.h>
|
|
#include <AK/WeakPtr.h>
|
|
#include <LibGfx/TextLayout.h>
|
|
#include <LibUnicode/Segmenter.h>
|
|
#include <LibWeb/CSS/Enums.h>
|
|
#include <LibWeb/DOM/Text.h>
|
|
#include <LibWeb/Layout/Node.h>
|
|
|
|
namespace Web::Layout {
|
|
|
|
class LineBoxFragment;
|
|
class GeneratedTextNode;
|
|
class TextSliceNode;
|
|
|
|
class TextNode : public Node {
|
|
LAYOUT_NODE(TextNode, Node);
|
|
|
|
public:
|
|
TextNode(DOM::Document&, DOM::Text&);
|
|
virtual ~TextNode() override;
|
|
|
|
DOM::Text const& dom_node() const { return static_cast<DOM::Text const&>(*Node::dom_node()); }
|
|
virtual DOM::Text const* dom_text() const { return &dom_node(); }
|
|
|
|
virtual size_t dom_start_offset() const { return 0; }
|
|
|
|
virtual size_t dom_length() const { return text().length_in_code_units(); }
|
|
|
|
virtual Utf16String const& text() const { return dom_node().data(); }
|
|
|
|
Utf16String const& text_for_rendering() const;
|
|
|
|
struct Chunk {
|
|
Utf16View view;
|
|
NonnullRefPtr<Gfx::Font const> font;
|
|
size_t start { 0 };
|
|
size_t length { 0 };
|
|
bool has_breaking_newline { false };
|
|
bool has_breaking_tab { false };
|
|
bool is_all_whitespace { false };
|
|
bool can_break_after { false };
|
|
Gfx::GlyphRun::TextType text_type;
|
|
};
|
|
|
|
class ChunkIterator {
|
|
public:
|
|
ChunkIterator(TextNode const&, bool should_wrap_lines, bool should_respect_linebreaks);
|
|
ChunkIterator(TextNode const&, Utf16View const&, Unicode::Segmenter& grapheme_segmenter, Unicode::Segmenter& line_segmenter, CSS::WordBreak, bool should_wrap_lines, bool should_respect_linebreaks);
|
|
|
|
bool should_wrap_lines() const { return m_should_wrap_lines; }
|
|
bool should_respect_linebreaks() const { return m_should_respect_linebreaks; }
|
|
bool should_collapse_whitespace() const { return m_should_collapse_whitespace; }
|
|
|
|
Optional<Chunk> next();
|
|
Optional<Chunk> peek(size_t);
|
|
|
|
Chunk create_empty_chunk();
|
|
|
|
private:
|
|
Optional<Chunk> next_without_peek();
|
|
Optional<Chunk> try_commit_chunk(size_t start, size_t end, bool has_breaking_newline, bool has_breaking_tab, bool can_break_after, Gfx::Font const&, Gfx::GlyphRun::TextType) const;
|
|
|
|
[[nodiscard]] bool is_at_line_break_opportunity() const;
|
|
[[nodiscard]] Gfx::Font const& font_for_space(size_t at_index, u32 space_code_point) const;
|
|
[[nodiscard]] Gfx::EmojiPresentationResult emoji_presentation_at(size_t code_unit_offset, u32 code_point) const;
|
|
|
|
bool const m_should_wrap_lines;
|
|
bool const m_should_respect_linebreaks;
|
|
bool m_should_collapse_whitespace;
|
|
Utf16View m_view;
|
|
Gfx::FontCascadeList const& m_font_cascade_list;
|
|
|
|
Unicode::Segmenter& m_grapheme_segmenter;
|
|
Unicode::Segmenter& m_line_segmenter;
|
|
CSS::WordBreak m_word_break;
|
|
CSS::FontVariantEmoji m_font_variant_emoji;
|
|
size_t m_current_index { 0 };
|
|
|
|
Vector<Chunk> m_peek_queue;
|
|
|
|
mutable RefPtr<Gfx::Font const> m_last_non_whitespace_font;
|
|
};
|
|
|
|
struct ChunkList {
|
|
Vector<Chunk> chunks;
|
|
bool should_collapse_whitespace { false };
|
|
};
|
|
|
|
ChunkList const& chunks_for_layout(bool should_wrap_lines, bool should_respect_linebreaks) const;
|
|
|
|
void invalidate_text_for_rendering();
|
|
|
|
Unicode::Segmenter& grapheme_segmenter() const;
|
|
Unicode::Segmenter& line_segmenter() const;
|
|
|
|
virtual RefPtr<Painting::Paintable> create_paintable() const override;
|
|
|
|
protected:
|
|
TextNode(DOM::Document&, DOM::Text&, AttachToDOMNode);
|
|
explicit TextNode(DOM::Document&);
|
|
|
|
virtual DOM::Element const* parent_element_for_text_transform() const;
|
|
virtual bool is_password_input() const;
|
|
|
|
private:
|
|
virtual bool is_text_node() const final { return true; }
|
|
|
|
struct TextForRenderingCacheKey {
|
|
CSS::TextTransform text_transform { CSS::TextTransform::None };
|
|
CSS::WhiteSpaceCollapse white_space_collapse { CSS::WhiteSpaceCollapse::Collapse };
|
|
Optional<String> lang;
|
|
bool is_password_input { false };
|
|
size_t dom_start_offset { 0 };
|
|
size_t dom_length { 0 };
|
|
|
|
bool operator==(TextForRenderingCacheKey const&) const = default;
|
|
};
|
|
|
|
struct ChunkCacheKey {
|
|
bool should_wrap_lines { false };
|
|
bool should_respect_linebreaks { false };
|
|
CSS::WhiteSpaceCollapse white_space_collapse { CSS::WhiteSpaceCollapse::Collapse };
|
|
CSS::WordBreak word_break { CSS::WordBreak::Normal };
|
|
CSS::FontVariantEmoji font_variant_emoji { CSS::FontVariantEmoji::Normal };
|
|
RefPtr<Gfx::FontCascadeList const> font_cascade_list;
|
|
|
|
bool operator==(ChunkCacheKey const&) const = default;
|
|
};
|
|
|
|
struct ChunkCacheEntry {
|
|
ChunkCacheKey key;
|
|
ChunkList chunk_list;
|
|
};
|
|
|
|
struct TextDependentCache {
|
|
TextForRenderingCacheKey key;
|
|
Utf16String text_for_rendering;
|
|
mutable OwnPtr<Unicode::Segmenter> grapheme_segmenter;
|
|
mutable OwnPtr<Unicode::Segmenter> line_segmenter;
|
|
mutable Optional<ChunkCacheEntry> chunk_cache;
|
|
};
|
|
|
|
TextForRenderingCacheKey create_text_for_rendering_cache_key() const;
|
|
Utf16String compute_text_for_rendering(TextForRenderingCacheKey const&) const;
|
|
TextDependentCache const& ensure_text_dependent_cache() const;
|
|
|
|
mutable Optional<TextDependentCache> m_text_dependent_cache;
|
|
};
|
|
|
|
class GeneratedTextNode final : public TextNode {
|
|
LAYOUT_NODE(GeneratedTextNode, TextNode);
|
|
|
|
public:
|
|
GeneratedTextNode(DOM::Document&, Utf16String);
|
|
virtual ~GeneratedTextNode() override;
|
|
|
|
virtual DOM::Text const* dom_text() const override { return nullptr; }
|
|
virtual Utf16String const& text() const override { return m_text; }
|
|
|
|
private:
|
|
virtual DOM::Element const* parent_element_for_text_transform() const override;
|
|
virtual bool is_password_input() const override { return false; }
|
|
|
|
Utf16String m_text;
|
|
};
|
|
|
|
class TextSliceNode final : public TextNode {
|
|
LAYOUT_NODE(TextSliceNode, TextNode);
|
|
|
|
public:
|
|
TextSliceNode(DOM::Document&, DOM::Text&, AttachToDOMNode, size_t dom_start_offset, size_t dom_length);
|
|
virtual ~TextSliceNode() override;
|
|
|
|
virtual size_t dom_start_offset() const override { return m_dom_start_offset; }
|
|
virtual size_t dom_length() const override { return m_dom_length_in_code_units; }
|
|
|
|
// Only meaningful on a remainder slice. Returns the first-letter slice that renders the leading
|
|
// sub-range of the same DOM::Text, or nullptr if first-letter is not active for this DOM::Text.
|
|
TextSliceNode const* first_letter_slice() const { return m_first_letter_slice.ptr(); }
|
|
TextSliceNode* first_letter_slice() { return m_first_letter_slice.ptr(); }
|
|
|
|
void set_first_letter_slice(TextSliceNode& slice) { m_first_letter_slice = slice; }
|
|
|
|
private:
|
|
virtual bool is_text_slice_node() const override { return true; }
|
|
|
|
size_t m_dom_start_offset { 0 };
|
|
size_t m_dom_length_in_code_units { 0 };
|
|
WeakPtr<TextSliceNode> m_first_letter_slice;
|
|
};
|
|
|
|
template<>
|
|
inline bool Node::fast_is<TextNode>() const { return is_text_node(); }
|
|
|
|
template<>
|
|
inline bool Node::fast_is<TextSliceNode>() const { return is_text_slice_node(); }
|
|
|
|
}
|