/src/serenity/Userland/Libraries/LibUnicode/Emoji.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2022-2023, Tim Flynn <trflynn89@serenityos.org> |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #include <AK/CharacterTypes.h> |
8 | | #include <AK/Utf32View.h> |
9 | | #include <AK/Utf8View.h> |
10 | | #include <LibUnicode/CharacterTypes.h> |
11 | | #include <LibUnicode/Emoji.h> |
12 | | |
13 | | namespace Unicode { |
14 | | |
15 | 0 | Optional<Emoji> __attribute__((weak)) find_emoji_for_code_points(ReadonlySpan<u32>) { return {}; } |
16 | | |
17 | | // https://unicode.org/reports/tr51/#def_emoji_core_sequence |
18 | | static bool could_be_start_of_emoji_core_sequence(u32 code_point, Optional<u32> const& next_code_point, SequenceType type) |
19 | 0 | { |
20 | | // emoji_core_sequence := emoji_character | emoji_presentation_sequence | emoji_keycap_sequence | emoji_modifier_sequence | emoji_flag_sequence |
21 | |
|
22 | 0 | static constexpr auto emoji_presentation_selector = 0xFE0Fu; |
23 | 0 | static constexpr auto combining_enclosing_keycap = 0x20E3u; |
24 | 0 | static constexpr auto zero_width_joiner = 0x200Du; |
25 | | |
26 | | // https://unicode.org/reports/tr51/#def_emoji_keycap_sequence |
27 | | // emoji_keycap_sequence := [0-9#*] \x{FE0F 20E3} |
28 | 0 | if (is_ascii_digit(code_point) || code_point == '#' || code_point == '*') |
29 | 0 | return next_code_point == emoji_presentation_selector || next_code_point == combining_enclosing_keycap; |
30 | | |
31 | | // A little non-standard, but all other ASCII code points are not the beginning of any emoji sequence. |
32 | 0 | if (is_ascii(code_point)) |
33 | 0 | return false; |
34 | | |
35 | | // https://unicode.org/reports/tr51/#def_emoji_character |
36 | 0 | switch (type) { |
37 | 0 | case SequenceType::Any: |
38 | 0 | if (code_point_has_emoji_property(code_point)) |
39 | 0 | return true; |
40 | 0 | break; |
41 | 0 | case SequenceType::EmojiPresentation: |
42 | 0 | if (code_point_has_emoji_presentation_property(code_point)) |
43 | 0 | return true; |
44 | 0 | if (next_code_point == zero_width_joiner && code_point_has_emoji_property(code_point)) |
45 | 0 | return true; |
46 | 0 | break; |
47 | 0 | } |
48 | | |
49 | | // https://unicode.org/reports/tr51/#def_emoji_presentation_sequence |
50 | | // emoji_presentation_sequence := emoji_character emoji_presentation_selector |
51 | 0 | if (next_code_point == emoji_presentation_selector) |
52 | 0 | return true; |
53 | | |
54 | | // https://unicode.org/reports/tr51/#def_emoji_modifier_sequence |
55 | | // emoji_modifier_sequence := emoji_modifier_base emoji_modifier |
56 | 0 | if (code_point_has_emoji_modifier_base_property(code_point)) |
57 | 0 | return true; |
58 | | |
59 | | // https://unicode.org/reports/tr51/#def_emoji_flag_sequence |
60 | | // emoji_flag_sequence := regional_indicator regional_indicator |
61 | 0 | if (code_point_has_regional_indicator_property(code_point)) |
62 | 0 | return true; |
63 | | |
64 | 0 | return false; |
65 | 0 | } |
66 | | |
67 | | static bool could_be_start_of_serenity_emoji(u32 code_point) |
68 | 0 | { |
69 | | // We use Supplementary Private Use Area-B for custom Serenity emoji, starting at U+10CD00. |
70 | 0 | static constexpr auto first_custom_serenity_emoji_code_point = 0x10CD00u; |
71 | |
|
72 | 0 | return code_point >= first_custom_serenity_emoji_code_point; |
73 | 0 | } |
74 | | |
75 | | // https://unicode.org/reports/tr51/#def_emoji_sequence |
76 | | template<typename CodePointIterator> |
77 | | static bool could_be_start_of_emoji_sequence_impl(CodePointIterator const& it, SequenceType type) |
78 | 0 | { |
79 | | // emoji_sequence := emoji_core_sequence | emoji_zwj_sequence | emoji_tag_sequence |
80 | |
|
81 | 0 | if (it.done()) |
82 | 0 | return false; |
83 | | |
84 | | // The purpose of this method is to quickly filter out code points that cannot be the start of |
85 | | // an emoji. The emoji_core_sequence definition alone captures the start of all possible |
86 | | // emoji_zwj_sequence and emoji_tag_sequence emojis, because: |
87 | | // |
88 | | // * emoji_zwj_sequence must begin with emoji_zwj_element, which is: |
89 | | // emoji_zwj_element := emoji_core_sequence | emoji_tag_sequence |
90 | | // |
91 | | // * emoji_tag_sequence must begin with tag_base, which is: |
92 | | // tag_base := emoji_character | emoji_modifier_sequence | emoji_presentation_sequence |
93 | | // Note that this is a subset of emoji_core_sequence. |
94 | 0 | auto code_point = *it; |
95 | 0 | auto next_code_point = it.peek(1); |
96 | |
|
97 | 0 | if (could_be_start_of_emoji_core_sequence(code_point, next_code_point, type)) |
98 | 0 | return true; |
99 | 0 | if (could_be_start_of_serenity_emoji(code_point)) |
100 | 0 | return true; |
101 | 0 | return false; |
102 | 0 | } Unexecuted instantiation: Emoji.cpp:bool Unicode::could_be_start_of_emoji_sequence_impl<AK::Utf8CodePointIterator>(AK::Utf8CodePointIterator const&, Unicode::SequenceType) Unexecuted instantiation: Emoji.cpp:bool Unicode::could_be_start_of_emoji_sequence_impl<AK::Utf32CodePointIterator>(AK::Utf32CodePointIterator const&, Unicode::SequenceType) |
103 | | |
104 | | bool could_be_start_of_emoji_sequence(Utf8CodePointIterator const& it, SequenceType type) |
105 | 0 | { |
106 | 0 | return could_be_start_of_emoji_sequence_impl(it, type); |
107 | 0 | } |
108 | | |
109 | | bool could_be_start_of_emoji_sequence(Utf32CodePointIterator const& it, SequenceType type) |
110 | 0 | { |
111 | 0 | return could_be_start_of_emoji_sequence_impl(it, type); |
112 | 0 | } |
113 | | |
114 | | } |