1package eu.siacs.conversations.utils;
2
3import java.util.ArrayList;
4import java.util.List;
5
6public class Emoticons {
7
8 private static final UnicodeRange MISC_SYMBOLS_AND_PICTOGRAPHS = new UnicodeRange(0x1F300,0x1F5FF);
9 private static final UnicodeRange SUPPLEMENTAL_SYMBOLS = new UnicodeRange(0x1F900,0x1F9FF);
10 private static final UnicodeRange EMOTICONS = new UnicodeRange(0x1F600,0x1F64F);
11 private static final UnicodeRange TRANSPORT_SYMBOLS = new UnicodeRange(0x1F680,0x1F6FF);
12 private static final UnicodeRange MISC_SYMBOLS = new UnicodeRange(0x2600,0x26FF);
13 private static final UnicodeRange DINGBATS = new UnicodeRange(0x2700,0x27BF);
14 private static final UnicodeRange REGIONAL_INDICATORS = new UnicodeRange(0x1F1E6,0x1F1FF);
15 private static final UnicodeBlocks EMOJIS = new UnicodeBlocks(MISC_SYMBOLS_AND_PICTOGRAPHS,SUPPLEMENTAL_SYMBOLS,EMOTICONS,TRANSPORT_SYMBOLS,MISC_SYMBOLS,DINGBATS);
16 private static final int ZWJ = 0x200D;
17 private static final int VARIATION_16 = 0xFE0F;
18 private static final UnicodeRange FITZPATRICK = new UnicodeRange(0x1F3FB,0x1F3FF);
19
20 private static List<Symbol> parse(String input) {
21 List<Symbol> symbols = new ArrayList<>();
22 Builder builder = new Builder();
23 boolean needsFinalBuild = false;
24 for (int cp, i = 0; i < input.length(); i += Character.charCount(cp)) {
25 cp = input.codePointAt(i);
26 if (builder.offer(cp)) {
27 needsFinalBuild = true;
28 } else {
29 symbols.add(builder.build());
30 builder = new Builder();
31 if (builder.offer(cp)) {
32 needsFinalBuild = true;
33 }
34 }
35 }
36 if (needsFinalBuild) {
37 symbols.add(builder.build());
38 }
39 return symbols;
40 }
41
42 public static boolean isEmoji(String input) {
43 List<Symbol> symbols = parse(input);
44 return symbols.size() == 1 && symbols.get(0) == Symbol.EMOJI;
45 }
46
47 public static boolean isOnlyEmoji(String input) {
48 List<Symbol> symbols = parse(input);
49 for(Symbol symbol : symbols) {
50 if (symbol == Symbol.NON_EMOJI) {
51 return false;
52 }
53 }
54 return symbols.size() > 0;
55 }
56
57 private enum Symbol {
58 EMOJI, NON_EMOJI
59 }
60
61
62 private static class Builder {
63 private final List<Integer> codepoints = new ArrayList<>();
64
65
66 public boolean offer(int codepoint) {
67 boolean add = false;
68 if (this.codepoints.size() == 0) {
69 if (REGIONAL_INDICATORS.contains(codepoint)) {
70 add = true;
71 } else if (EMOJIS.contains(codepoint) && !FITZPATRICK.contains(codepoint) && codepoint != ZWJ) {
72 add = true;
73 }
74 } else {
75 int previous = codepoints.get(codepoints.size() -1);
76 if (REGIONAL_INDICATORS.contains(previous) && REGIONAL_INDICATORS.contains(codepoint)) {
77 if (codepoints.size() == 1) {
78 add = true;
79 }
80 } else if (previous == VARIATION_16) {
81 if (isMerger(codepoint)) {
82 add = true;
83 }
84 } else if (FITZPATRICK.contains(previous)) {
85 if (codepoint == ZWJ || EMOJIS.contains(codepoint)) {
86 add = true;
87 }
88 } else if (ZWJ == previous) {
89 if (EMOJIS.contains(codepoint) || FITZPATRICK.contains(codepoint)) {
90 add = true;
91 }
92 } else if (isMerger(codepoint)) {
93 add = true;
94 } else if (codepoint == VARIATION_16 && EMOJIS.contains(previous)) {
95 add = true;
96 }
97 }
98 if (add) {
99 codepoints.add(codepoint);
100 return true;
101 } else {
102 return false;
103 }
104 }
105
106 private static boolean isMerger(int codepoint) {
107 return codepoint == ZWJ || FITZPATRICK.contains(codepoint);
108 }
109
110 public Symbol build() {
111 return codepoints.size() == 0 ? Symbol.NON_EMOJI : Symbol.EMOJI;
112 }
113 }
114
115 public static class UnicodeBlocks {
116 final UnicodeRange[] ranges;
117
118 public UnicodeBlocks(UnicodeRange... ranges) {
119 this.ranges = ranges;
120 }
121
122 public boolean contains(int codepoint) {
123 for(UnicodeRange range : ranges) {
124 if (range.contains(codepoint)) {
125 return true;
126 }
127 }
128 return false;
129 }
130 }
131
132
133 public static class UnicodeRange {
134
135 private final int lower;
136 private final int upper;
137
138 UnicodeRange(int lower, int upper) {
139 this.lower = lower;
140 this.upper = upper;
141 }
142
143 public boolean contains(int codePoint) {
144 return codePoint >= lower && codePoint <= upper;
145 }
146 }
147}