/// <summary> /// Combines multiple emoji with a zero-width-joiner to (potentially) create a new symbol /// </summary> /// <param name="emoji"></param> /// <returns></returns> public static string Combine(IEnumerable <SingleEmoji> emoji) { #if !NET20 return(string.Join(ZeroWidthJoiner.AsString(), emoji)); #else return(string.Join(ZeroWidthJoiner.AsString(), emoji.Select(e => e.ToString()).ToArray())); #endif }
/// <summary> /// Determines whether a string is comprised solely of emoji, optionally with a maximum number of drawn symbols. /// Can be used to determine whether a message consists of ≦ x emoji for purposes such as displaying at a larger size. Since one emoji symbol can be formed /// from many separate emoji "characters" combined with zero-width joiners or even non-emoji characters followed by a "use emoji representation" marker, this /// cannot be determined solely from the codepoints. /// </summary> /// <param name="message"></param> /// <param name="maxSymbolCount"></param> /// <returns></returns> public static bool IsEmoji(string message, int maxSymbolCount = int.MaxValue) { var codepoints = message.Codepoints(); bool nextMustBeVS = false; string zwj = ZeroWidthJoiner.AsString(); string variationSelector = VariationSelector.AsString(); bool ignoreNext = false; int count = 0; foreach (var cp in codepoints) { //we used to have message = message.trim() previously. This avoids the extra allocation, hepful in case of long messages. //this was not premature optimization, it came out of necessity. if (cp == "\n" || cp == "\r" || cp == "\t" || cp == " ") { continue; } if (nextMustBeVS) { nextMustBeVS = false; if (cp != variationSelector) { //a non-emoji codepoint was found and this (the codepoint after it) is not the variation selector return(false); } } if (cp.In(SkinTones.All)) { //don't count as part of the length continue; } if (cp == zwj) { ignoreNext = true; continue; } if (cp == variationSelector) { continue; } if (cp == Codepoints.ObjectReplacementCharacter) { //this is explicitly blacklisted for UI purposes return(false); } if (cp == Keycap) { //this is not in the UTR, but is used to box symbols in an icon //do not consider it part of the length continue; } if (!ignoreNext) { ++count; if (count > maxSymbolCount) { return(false); } //by default, the UTR lists numbers, the asterisk, and the number sign as emoji, but we won't consider them as such unless they are followed by a VS if (Languages.ArabicNumerals.Contains(cp) || cp == "#" || cp == "*") { nextMustBeVS = true; continue; } else if (Languages.Emoji.Contains(cp)) { continue; } else { //we've either encountered a non-emoji character OR a non-emoji codepoint that should be treated as an emoji if followed by the variation selector codepoint nextMustBeVS = true; continue; } } ignoreNext = false; } if (nextMustBeVS) { return(false); } return(count > 0 && count <= maxSymbolCount); }
/// <summary> /// Combines multiple emoji with a zero-width-joiner to (potentially) create a new symbol /// </summary> /// <param name="emoji"></param> /// <returns></returns> public static string Combine(IEnumerable <SingleEmoji> emoji) { //does not work on .NET 2.0 //return string.Join(ZeroWidthJoiner.AsString(), emoji); return(string.Join(ZeroWidthJoiner.AsString(), emoji.Select(e => e.ToString()).ToArray())); }