diff --git a/Libraries/LibWeb/CSS/ComputedValues.h b/Libraries/LibWeb/CSS/ComputedValues.h index 1c70f38699..35670644c9 100644 --- a/Libraries/LibWeb/CSS/ComputedValues.h +++ b/Libraries/LibWeb/CSS/ComputedValues.h @@ -182,6 +182,7 @@ public: static CursorData cursor() { return { CursorPredefined::Auto }; } static WhiteSpaceCollapse white_space_collapse() { return WhiteSpaceCollapse::Collapse; } static WordBreak word_break() { return WordBreak::Normal; } + static FontVariantEmoji font_variant_emoji() { return FontVariantEmoji::Normal; } static CSSPixels word_spacing() { return 0; } static CSSPixels letter_spacing() { return 0; } static Variant tab_size() { return 8; } @@ -574,6 +575,7 @@ public: WhiteSpaceCollapse white_space_collapse() const { return m_inherited.white_space_collapse; } WhiteSpaceTrimData white_space_trim() const { return m_noninherited.white_space_trim; } WordBreak word_break() const { return m_inherited.word_break; } + FontVariantEmoji font_variant_emoji() const { return m_inherited.font_variant_emoji; } CSSPixels const& word_spacing() const { return m_inherited.word_spacing; } CSSPixels letter_spacing() const { return m_inherited.letter_spacing; } FlexDirection flex_direction() const { return m_noninherited.flex_direction; } @@ -805,6 +807,7 @@ protected: CSSPixels text_underline_offset { InitialValues::text_underline_offset() }; WhiteSpaceCollapse white_space_collapse { InitialValues::white_space_collapse() }; WordBreak word_break { InitialValues::word_break() }; + FontVariantEmoji font_variant_emoji { InitialValues::font_variant_emoji() }; ListStylePosition list_style_position { InitialValues::list_style_position() }; Visibility visibility { InitialValues::visibility() }; CSSPixels word_spacing { InitialValues::word_spacing() }; @@ -1027,6 +1030,7 @@ public: void set_white_space_trim(WhiteSpaceTrimData value) { m_noninherited.white_space_trim = value; } void set_word_spacing(CSSPixels value) { m_inherited.word_spacing = value; } void set_word_break(WordBreak value) { m_inherited.word_break = value; } + void set_font_variant_emoji(FontVariantEmoji value) { m_inherited.font_variant_emoji = value; } void set_letter_spacing(CSSPixels value) { m_inherited.letter_spacing = value; } void set_width(Size const& width) { m_noninherited.width = width; } void set_min_width(Size const& width) { m_noninherited.min_width = width; } diff --git a/Libraries/LibWeb/Layout/Node.cpp b/Libraries/LibWeb/Layout/Node.cpp index 7ecdfa95ab..442ba0c590 100644 --- a/Libraries/LibWeb/Layout/Node.cpp +++ b/Libraries/LibWeb/Layout/Node.cpp @@ -709,6 +709,7 @@ void NodeWithStyle::apply_style(CSS::ComputedProperties const& computed_style) computed_values.set_font_size(computed_style.font_size()); computed_values.set_font_weight(computed_style.font_weight()); computed_values.set_line_height(computed_style.line_height()); + computed_values.set_font_variant_emoji(computed_style.font_variant_emoji()); // NOTE: color must be set after color-scheme to ensure currentColor can be resolved in other properties (e.g. background-color). // NOTE: color must be set after font_size as `CalculatedStyleValue`s can rely on it being set for resolving lengths. diff --git a/Libraries/LibWeb/Layout/TextNode.cpp b/Libraries/LibWeb/Layout/TextNode.cpp index b92ce02bfb..5959ab8baf 100644 --- a/Libraries/LibWeb/Layout/TextNode.cpp +++ b/Libraries/LibWeb/Layout/TextNode.cpp @@ -513,6 +513,7 @@ TextNode::ChunkList const& TextNode::chunks_for_layout(bool should_wrap_lines, b .should_respect_linebreaks = should_respect_linebreaks, .white_space_collapse = computed_values.white_space_collapse(), .word_break = computed_values.word_break(), + .font_variant_emoji = computed_values.font_variant_emoji(), .font_cascade_list = computed_values.font_list(), }; @@ -558,6 +559,7 @@ TextNode::ChunkIterator::ChunkIterator(TextNode const& text_node, Utf16View cons , m_grapheme_segmenter(grapheme_segmenter) , m_line_segmenter(line_segmenter) , m_word_break(word_break) + , m_font_variant_emoji(text_node.computed_values().font_variant_emoji()) { m_should_collapse_whitespace = first_is_one_of(text_node.computed_values().white_space_collapse(), CSS::WhiteSpaceCollapse::Collapse, CSS::WhiteSpaceCollapse::PreserveBreaks); } @@ -778,7 +780,22 @@ Gfx::EmojiPresentationResult TextNode::ChunkIterator::emoji_presentation_at(size if (next_offset < m_view.length_in_code_units()) next_code_point = m_view.code_point_at(next_offset); - return Gfx::emoji_presentation_for_code_point(code_point, next_code_point); + auto default_presentation = Gfx::emoji_presentation_for_code_point(code_point, next_code_point); + + if (default_presentation.forced == Gfx::ForcedPresentation::Yes || !Unicode::code_point_has_emoji_property(code_point)) + return default_presentation; + + switch (m_font_variant_emoji) { + case CSS::FontVariantEmoji::Text: + return { Gfx::EmojiPresentation::Text, Gfx::ForcedPresentation::Yes }; + case CSS::FontVariantEmoji::Emoji: + return { Gfx::EmojiPresentation::Emoji, Gfx::ForcedPresentation::Yes }; + case CSS::FontVariantEmoji::Unicode: + return { default_presentation.presentation, Gfx::ForcedPresentation::Yes }; + case CSS::FontVariantEmoji::Normal: + return default_presentation; + } + VERIFY_NOT_REACHED(); } Optional TextNode::ChunkIterator::next_without_peek() diff --git a/Libraries/LibWeb/Layout/TextNode.h b/Libraries/LibWeb/Layout/TextNode.h index 6ec17839a5..6c1076cdac 100644 --- a/Libraries/LibWeb/Layout/TextNode.h +++ b/Libraries/LibWeb/Layout/TextNode.h @@ -83,6 +83,7 @@ public: 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 m_peek_queue; @@ -130,6 +131,7 @@ private: 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 font_cascade_list; bool operator==(ChunkCacheKey const&) const = default; diff --git a/Tests/LibWeb/Ref/data/fonts/NotoColorEmoji-Regular_subset.ttf b/Tests/LibWeb/Ref/data/fonts/NotoColorEmoji-Regular_subset.ttf new file mode 100644 index 0000000000..8754e127d7 Binary files /dev/null and b/Tests/LibWeb/Ref/data/fonts/NotoColorEmoji-Regular_subset.ttf differ diff --git a/Tests/LibWeb/Ref/data/fonts/NotoEmoji-Regular_subset.ttf b/Tests/LibWeb/Ref/data/fonts/NotoEmoji-Regular_subset.ttf new file mode 100644 index 0000000000..f9996293bf Binary files /dev/null and b/Tests/LibWeb/Ref/data/fonts/NotoEmoji-Regular_subset.ttf differ diff --git a/Tests/LibWeb/Ref/expected/css-font-variant-emoji-presentation-ref.html b/Tests/LibWeb/Ref/expected/css-font-variant-emoji-presentation-ref.html new file mode 100644 index 0000000000..6062f801c2 --- /dev/null +++ b/Tests/LibWeb/Ref/expected/css-font-variant-emoji-presentation-ref.html @@ -0,0 +1,16 @@ + + +
+
+
🫨
+
🫨
diff --git a/Tests/LibWeb/Ref/input/css-font-variant-emoji-presentation.html b/Tests/LibWeb/Ref/input/css-font-variant-emoji-presentation.html new file mode 100644 index 0000000000..1943f1453c --- /dev/null +++ b/Tests/LibWeb/Ref/input/css-font-variant-emoji-presentation.html @@ -0,0 +1,17 @@ + + + +
+
+
🫨
+
🫨