Emoticons.java

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