From eb3ca4325621a2508050a627b28846c9e562a0bd Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Fri, 27 Oct 2017 10:25:49 -0400 Subject: [PATCH 1/7] Jumbomoji. // FREEBIE --- .../ConversationView/Cells/OWSMessageCell.m | 21 +++++- .../ConversationView/ConversationViewItem.m | 8 ++- Signal/src/util/DisplayableText.swift | 72 ++++++++++++++++++- 3 files changed, 96 insertions(+), 5 deletions(-) diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m index b947603633..387631db5e 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m @@ -132,8 +132,6 @@ NS_ASSUME_NONNULL_BEGIN [self.bubbleImageView autoPinToSuperviewEdges]; self.textView = [UITextView new]; - // Honor dynamic type in the message bodies. - self.textView.font = [self textMessageFont]; self.textView.backgroundColor = [UIColor clearColor]; self.textView.opaque = NO; self.textView.editable = NO; @@ -182,6 +180,23 @@ NS_ASSUME_NONNULL_BEGIN - (UIFont *)textMessageFont { + OWSAssert(DisplayableText.kMaxJumbomojiCount == 5); + + if (self.displayableText.jumbomojiCount) { + switch (self.displayableText.jumbomojiCount.integerValue) { + case 1: + return [UIFont ows_regularFontWithSize:35.f]; + case 2: + return [UIFont ows_regularFontWithSize:30.f]; + case 3: + case 4: + case 5: + return [UIFont ows_regularFontWithSize:25.f]; + default: + break; + } + } + return [UIFont ows_dynamicTypeBodyFont]; } @@ -627,6 +642,7 @@ NS_ASSUME_NONNULL_BEGIN self.textView.text = self.displayableText.displayText; UIColor *textColor = [self textColor]; self.textView.textColor = textColor; + // Honor dynamic type in the message bodies. self.textView.font = [self textMessageFont]; // Don't link outgoing messages that haven't been sent yet, as @@ -848,6 +864,7 @@ NS_ASSUME_NONNULL_BEGIN const int maxTextWidth = (int)floor(maxMessageWidth - (leftMargin + rightMargin)); self.textView.text = self.displayableText.displayText; + // Honor dynamic type in the message bodies. self.textView.font = [self textMessageFont]; CGSize textSize = [self.textView sizeThatFits:CGSizeMake(maxTextWidth, CGFLOAT_MAX)]; CGFloat tapForMoreHeight = (self.displayableText.isTextTruncated ? [self tapForMoreHeight] : 0.f); diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m index a3f54ef18d..afc20b6171 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m @@ -323,8 +323,12 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) displayText = @""; } - displayableText = - [[DisplayableText alloc] initWithFullText:fullText displayText:displayText isTextTruncated:isTextTruncated]; + NSNumber *_Nullable jumbomojiCount = [DisplayableText jumbomojiCountTo:fullText]; + + displayableText = [[DisplayableText alloc] initWithFullText:fullText + displayText:displayText + isTextTruncated:isTextTruncated + jumbomojiCount:jumbomojiCount]; [[self displayableTextCache] setObject:displayableText forKey:interactionId]; } diff --git a/Signal/src/util/DisplayableText.swift b/Signal/src/util/DisplayableText.swift index 87a558b6b1..bbc37ce832 100644 --- a/Signal/src/util/DisplayableText.swift +++ b/Signal/src/util/DisplayableText.swift @@ -11,13 +11,83 @@ import Foundation let fullText: String let displayText: String let isTextTruncated: Bool + let jumbomojiCount: NSNumber? + + static let kMaxJumbomojiCount: UInt = 5 // MARK: Initializers - init(fullText: String, displayText: String, isTextTruncated: Bool) { + init(fullText: String, displayText: String, isTextTruncated: Bool, jumbomojiCount: NSNumber?) { self.fullText = fullText self.displayText = displayText self.isTextTruncated = isTextTruncated + self.jumbomojiCount = jumbomojiCount + } + + // MARK: Emoji + + private class func canDetectEmoji() -> Bool { + if #available(iOS 10.0, *) { + return true + } else { + return false + } + } + + // If the string is... + // + // * Non-empty + // * Only contains emoji + // * Contains <= maxEmojiCount emoji + // + // ...return the number of emoji in the string. Otherwise return nil. + // + // On iOS 9 and earler, always returns nil. + @objc public class func jumbomojiCount(to string: String) -> NSNumber? { + if string == "" { + return nil + } + if string.characters.count > Int(kMaxJumbomojiCount) { + return nil + } + if !canDetectEmoji() { + return nil + } + var didFail = false + var emojiCount: UInt = 0 + let attributes = [NSFontAttributeName: UIFont.systemFont(ofSize:12)] + let attributedString = NSMutableAttributedString(string: string, attributes: attributes) + let range = NSRange(location: 0, length: string.characters.count) + attributedString.fixAttributes(in: range) + attributedString.enumerateAttribute(NSFontAttributeName, + in: range, + options: [], + using: {(_ value: Any?, range: NSRange, stop: UnsafeMutablePointer) -> Void in + guard emojiCount < kMaxJumbomojiCount else { + didFail = true + stop.pointee = true + return + } + guard let rangeFont = value as? UIFont else { + didFail = true + stop.pointee = true + return + } + guard rangeFont.fontName == ".AppleColorEmojiUI" else { + didFail = true + stop.pointee = true + return + } + if rangeFont.fontName == ".AppleColorEmojiUI" { + Logger.verbose("Detected Emoji at location: \(range.location), for length: \(range.length)") + emojiCount += UInt(range.length) + } + }) + + guard !didFail else { + return nil + } + return NSNumber(value: emojiCount) } // MARK: Filter Methods From 841a2333e3b3c278f8b796d77f1a4599b448bfdf Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Mon, 30 Oct 2017 12:11:42 -0400 Subject: [PATCH 2/7] Respond to CR. // FREEBIE --- Scripts/emoji-data.txt | 679 ++++++++++++++++++ Scripts/emoji_ranges.py | 83 +++ .../ConversationView/Cells/OWSMessageCell.m | 27 +- .../ConversationView/ConversationViewItem.m | 8 +- Signal/src/util/DisplayableText.swift | 321 +++++++-- 5 files changed, 1044 insertions(+), 74 deletions(-) create mode 100644 Scripts/emoji-data.txt create mode 100755 Scripts/emoji_ranges.py diff --git a/Scripts/emoji-data.txt b/Scripts/emoji-data.txt new file mode 100644 index 0000000000..dadf631736 --- /dev/null +++ b/Scripts/emoji-data.txt @@ -0,0 +1,679 @@ +# emoji-data.txt +# Date: 2017-08-10, 11:51:32 GMT +# © 2017 Unicode®, Inc. +# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. +# For terms of use, see http://www.unicode.org/terms_of_use.html +# +# Emoji Data for UTR #51 +# Version: 6.0 +# +# For documentation and usage, see http://www.unicode.org/reports/tr51 +# +# Format: +# ; # +# Note: there is no guarantee as to the structure of whitespace or comments +# +# Characters and sequences are listed in code point order. Users should be shown a more natural order. +# See the CLDR collation order for Emoji. + + +# ================================================ + +# All omitted code points have Emoji=No +# @missing: 0000..10FFFF ; Emoji ; No + +0023 ; Emoji # 1.1 [1] (#️) number sign +002A ; Emoji # 1.1 [1] (*️) asterisk +0030..0039 ; Emoji # 1.1 [10] (0️..9️) digit zero..digit nine +00A9 ; Emoji # 1.1 [1] (©️) copyright +00AE ; Emoji # 1.1 [1] (®️) registered +203C ; Emoji # 1.1 [1] (‼️) double exclamation mark +2049 ; Emoji # 3.0 [1] (⁉️) exclamation question mark +2122 ; Emoji # 1.1 [1] (™️) trade mark +2139 ; Emoji # 3.0 [1] (ℹ️) information +2194..2199 ; Emoji # 1.1 [6] (↔️..↙️) left-right arrow..down-left arrow +21A9..21AA ; Emoji # 1.1 [2] (↩️..↪️) right arrow curving left..left arrow curving right +231A..231B ; Emoji # 1.1 [2] (⌚..⌛) watch..hourglass done +2328 ; Emoji # 1.1 [1] (⌨️) keyboard +23CF ; Emoji # 4.0 [1] (⏏️) eject button +23E9..23F3 ; Emoji # 6.0 [11] (⏩..⏳) fast-forward button..hourglass not done +23F8..23FA ; Emoji # 7.0 [3] (⏸️..⏺️) pause button..record button +24C2 ; Emoji # 1.1 [1] (Ⓜ️) circled M +25AA..25AB ; Emoji # 1.1 [2] (▪️..▫️) black small square..white small square +25B6 ; Emoji # 1.1 [1] (▶️) play button +25C0 ; Emoji # 1.1 [1] (◀️) reverse button +25FB..25FE ; Emoji # 3.2 [4] (◻️..◾) white medium square..black medium-small square +2600..2604 ; Emoji # 1.1 [5] (☀️..☄️) sun..comet +260E ; Emoji # 1.1 [1] (☎️) telephone +2611 ; Emoji # 1.1 [1] (☑️) ballot box with check +2614..2615 ; Emoji # 4.0 [2] (☔..☕) umbrella with rain drops..hot beverage +2618 ; Emoji # 4.1 [1] (☘️) shamrock +261D ; Emoji # 1.1 [1] (☝️) index pointing up +2620 ; Emoji # 1.1 [1] (☠️) skull and crossbones +2622..2623 ; Emoji # 1.1 [2] (☢️..☣️) radioactive..biohazard +2626 ; Emoji # 1.1 [1] (☦️) orthodox cross +262A ; Emoji # 1.1 [1] (☪️) star and crescent +262E..262F ; Emoji # 1.1 [2] (☮️..☯️) peace symbol..yin yang +2638..263A ; Emoji # 1.1 [3] (☸️..☺️) wheel of dharma..smiling face +2640 ; Emoji # 1.1 [1] (♀️) female sign +2642 ; Emoji # 1.1 [1] (♂️) male sign +2648..2653 ; Emoji # 1.1 [12] (♈..♓) Aries..Pisces +2660 ; Emoji # 1.1 [1] (♠️) spade suit +2663 ; Emoji # 1.1 [1] (♣️) club suit +2665..2666 ; Emoji # 1.1 [2] (♥️..♦️) heart suit..diamond suit +2668 ; Emoji # 1.1 [1] (♨️) hot springs +267B ; Emoji # 3.2 [1] (♻️) recycling symbol +267F ; Emoji # 4.1 [1] (♿) wheelchair symbol +2692..2697 ; Emoji # 4.1 [6] (⚒️..⚗️) hammer and pick..alembic +2699 ; Emoji # 4.1 [1] (⚙️) gear +269B..269C ; Emoji # 4.1 [2] (⚛️..⚜️) atom symbol..fleur-de-lis +26A0..26A1 ; Emoji # 4.0 [2] (⚠️..⚡) warning..high voltage +26AA..26AB ; Emoji # 4.1 [2] (⚪..⚫) white circle..black circle +26B0..26B1 ; Emoji # 4.1 [2] (⚰️..⚱️) coffin..funeral urn +26BD..26BE ; Emoji # 5.2 [2] (⚽..⚾) soccer ball..baseball +26C4..26C5 ; Emoji # 5.2 [2] (⛄..⛅) snowman without snow..sun behind cloud +26C8 ; Emoji # 5.2 [1] (⛈️) cloud with lightning and rain +26CE ; Emoji # 6.0 [1] (⛎) Ophiuchus +26CF ; Emoji # 5.2 [1] (⛏️) pick +26D1 ; Emoji # 5.2 [1] (⛑️) rescue worker’s helmet +26D3..26D4 ; Emoji # 5.2 [2] (⛓️..⛔) chains..no entry +26E9..26EA ; Emoji # 5.2 [2] (⛩️..⛪) shinto shrine..church +26F0..26F5 ; Emoji # 5.2 [6] (⛰️..⛵) mountain..sailboat +26F7..26FA ; Emoji # 5.2 [4] (⛷️..⛺) skier..tent +26FD ; Emoji # 5.2 [1] (⛽) fuel pump +2702 ; Emoji # 1.1 [1] (✂️) scissors +2705 ; Emoji # 6.0 [1] (✅) white heavy check mark +2708..2709 ; Emoji # 1.1 [2] (✈️..✉️) airplane..envelope +270A..270B ; Emoji # 6.0 [2] (✊..✋) raised fist..raised hand +270C..270D ; Emoji # 1.1 [2] (✌️..✍️) victory hand..writing hand +270F ; Emoji # 1.1 [1] (✏️) pencil +2712 ; Emoji # 1.1 [1] (✒️) black nib +2714 ; Emoji # 1.1 [1] (✔️) heavy check mark +2716 ; Emoji # 1.1 [1] (✖️) heavy multiplication x +271D ; Emoji # 1.1 [1] (✝️) latin cross +2721 ; Emoji # 1.1 [1] (✡️) star of David +2728 ; Emoji # 6.0 [1] (✨) sparkles +2733..2734 ; Emoji # 1.1 [2] (✳️..✴️) eight-spoked asterisk..eight-pointed star +2744 ; Emoji # 1.1 [1] (❄️) snowflake +2747 ; Emoji # 1.1 [1] (❇️) sparkle +274C ; Emoji # 6.0 [1] (❌) cross mark +274E ; Emoji # 6.0 [1] (❎) cross mark button +2753..2755 ; Emoji # 6.0 [3] (❓..❕) question mark..white exclamation mark +2757 ; Emoji # 5.2 [1] (❗) exclamation mark +2763..2764 ; Emoji # 1.1 [2] (❣️..❤️) heavy heart exclamation..red heart +2795..2797 ; Emoji # 6.0 [3] (➕..➗) heavy plus sign..heavy division sign +27A1 ; Emoji # 1.1 [1] (➡️) right arrow +27B0 ; Emoji # 6.0 [1] (➰) curly loop +27BF ; Emoji # 6.0 [1] (➿) double curly loop +2934..2935 ; Emoji # 3.2 [2] (⤴️..⤵️) right arrow curving up..right arrow curving down +2B05..2B07 ; Emoji # 4.0 [3] (⬅️..⬇️) left arrow..down arrow +2B1B..2B1C ; Emoji # 5.1 [2] (⬛..⬜) black large square..white large square +2B50 ; Emoji # 5.1 [1] (⭐) white medium star +2B55 ; Emoji # 5.2 [1] (⭕) heavy large circle +3030 ; Emoji # 1.1 [1] (〰️) wavy dash +303D ; Emoji # 3.2 [1] (〽️) part alternation mark +3297 ; Emoji # 1.1 [1] (㊗️) Japanese “congratulations” button +3299 ; Emoji # 1.1 [1] (㊙️) Japanese “secret” button +1F004 ; Emoji # 5.1 [1] (🀄) mahjong red dragon +1F0CF ; Emoji # 6.0 [1] (🃏) joker +1F170..1F171 ; Emoji # 6.0 [2] (🅰️..🅱️) A button (blood type)..B button (blood type) +1F17E ; Emoji # 6.0 [1] (🅾️) O button (blood type) +1F17F ; Emoji # 5.2 [1] (🅿️) P button +1F18E ; Emoji # 6.0 [1] (🆎) AB button (blood type) +1F191..1F19A ; Emoji # 6.0 [10] (🆑..🆚) CL button..VS button +1F1E6..1F1FF ; Emoji # 6.0 [26] (🇦..🇿) regional indicator symbol letter a..regional indicator symbol letter z +1F201..1F202 ; Emoji # 6.0 [2] (🈁..🈂️) Japanese “here” button..Japanese “service charge” button +1F21A ; Emoji # 5.2 [1] (🈚) Japanese “free of charge” button +1F22F ; Emoji # 5.2 [1] (🈯) Japanese “reserved” button +1F232..1F23A ; Emoji # 6.0 [9] (🈲..🈺) Japanese “prohibited” button..Japanese “open for business” button +1F250..1F251 ; Emoji # 6.0 [2] (🉐..🉑) Japanese “bargain” button..Japanese “acceptable” button +1F300..1F320 ; Emoji # 6.0 [33] (🌀..🌠) cyclone..shooting star +1F321 ; Emoji # 7.0 [1] (🌡️) thermometer +1F324..1F32C ; Emoji # 7.0 [9] (🌤️..🌬️) sun behind small cloud..wind face +1F32D..1F32F ; Emoji # 8.0 [3] (🌭..🌯) hot dog..burrito +1F330..1F335 ; Emoji # 6.0 [6] (🌰..🌵) chestnut..cactus +1F336 ; Emoji # 7.0 [1] (🌶️) hot pepper +1F337..1F37C ; Emoji # 6.0 [70] (🌷..🍼) tulip..baby bottle +1F37D ; Emoji # 7.0 [1] (🍽️) fork and knife with plate +1F37E..1F37F ; Emoji # 8.0 [2] (🍾..🍿) bottle with popping cork..popcorn +1F380..1F393 ; Emoji # 6.0 [20] (🎀..🎓) ribbon..graduation cap +1F396..1F397 ; Emoji # 7.0 [2] (🎖️..🎗️) military medal..reminder ribbon +1F399..1F39B ; Emoji # 7.0 [3] (🎙️..🎛️) studio microphone..control knobs +1F39E..1F39F ; Emoji # 7.0 [2] (🎞️..🎟️) film frames..admission tickets +1F3A0..1F3C4 ; Emoji # 6.0 [37] (🎠..🏄) carousel horse..person surfing +1F3C5 ; Emoji # 7.0 [1] (🏅) sports medal +1F3C6..1F3CA ; Emoji # 6.0 [5] (🏆..🏊) trophy..person swimming +1F3CB..1F3CE ; Emoji # 7.0 [4] (🏋️..🏎️) person lifting weights..racing car +1F3CF..1F3D3 ; Emoji # 8.0 [5] (🏏..🏓) cricket game..ping pong +1F3D4..1F3DF ; Emoji # 7.0 [12] (🏔️..🏟️) snow-capped mountain..stadium +1F3E0..1F3F0 ; Emoji # 6.0 [17] (🏠..🏰) house..castle +1F3F3..1F3F5 ; Emoji # 7.0 [3] (🏳️..🏵️) white flag..rosette +1F3F7 ; Emoji # 7.0 [1] (🏷️) label +1F3F8..1F3FF ; Emoji # 8.0 [8] (🏸..🏿) badminton..dark skin tone +1F400..1F43E ; Emoji # 6.0 [63] (🐀..🐾) rat..paw prints +1F43F ; Emoji # 7.0 [1] (🐿️) chipmunk +1F440 ; Emoji # 6.0 [1] (👀) eyes +1F441 ; Emoji # 7.0 [1] (👁️) eye +1F442..1F4F7 ; Emoji # 6.0[182] (👂..📷) ear..camera +1F4F8 ; Emoji # 7.0 [1] (📸) camera with flash +1F4F9..1F4FC ; Emoji # 6.0 [4] (📹..📼) video camera..videocassette +1F4FD ; Emoji # 7.0 [1] (📽️) film projector +1F4FF ; Emoji # 8.0 [1] (📿) prayer beads +1F500..1F53D ; Emoji # 6.0 [62] (🔀..🔽) shuffle tracks button..down button +1F549..1F54A ; Emoji # 7.0 [2] (🕉️..🕊️) om..dove +1F54B..1F54E ; Emoji # 8.0 [4] (🕋..🕎) kaaba..menorah +1F550..1F567 ; Emoji # 6.0 [24] (🕐..🕧) one o’clock..twelve-thirty +1F56F..1F570 ; Emoji # 7.0 [2] (🕯️..🕰️) candle..mantelpiece clock +1F573..1F579 ; Emoji # 7.0 [7] (🕳️..🕹️) hole..joystick +1F57A ; Emoji # 9.0 [1] (🕺) man dancing +1F587 ; Emoji # 7.0 [1] (🖇️) linked paperclips +1F58A..1F58D ; Emoji # 7.0 [4] (🖊️..🖍️) pen..crayon +1F590 ; Emoji # 7.0 [1] (🖐️) hand with fingers splayed +1F595..1F596 ; Emoji # 7.0 [2] (🖕..🖖) middle finger..vulcan salute +1F5A4 ; Emoji # 9.0 [1] (🖤) black heart +1F5A5 ; Emoji # 7.0 [1] (🖥️) desktop computer +1F5A8 ; Emoji # 7.0 [1] (🖨️) printer +1F5B1..1F5B2 ; Emoji # 7.0 [2] (🖱️..🖲️) computer mouse..trackball +1F5BC ; Emoji # 7.0 [1] (🖼️) framed picture +1F5C2..1F5C4 ; Emoji # 7.0 [3] (🗂️..🗄️) card index dividers..file cabinet +1F5D1..1F5D3 ; Emoji # 7.0 [3] (🗑️..🗓️) wastebasket..spiral calendar +1F5DC..1F5DE ; Emoji # 7.0 [3] (🗜️..🗞️) clamp..rolled-up newspaper +1F5E1 ; Emoji # 7.0 [1] (🗡️) dagger +1F5E3 ; Emoji # 7.0 [1] (🗣️) speaking head +1F5E8 ; Emoji # 7.0 [1] (🗨️) left speech bubble +1F5EF ; Emoji # 7.0 [1] (🗯️) right anger bubble +1F5F3 ; Emoji # 7.0 [1] (🗳️) ballot box with ballot +1F5FA ; Emoji # 7.0 [1] (🗺️) world map +1F5FB..1F5FF ; Emoji # 6.0 [5] (🗻..🗿) mount fuji..moai +1F600 ; Emoji # 6.1 [1] (😀) grinning face +1F601..1F610 ; Emoji # 6.0 [16] (😁..😐) beaming face with smiling eyes..neutral face +1F611 ; Emoji # 6.1 [1] (😑) expressionless face +1F612..1F614 ; Emoji # 6.0 [3] (😒..😔) unamused face..pensive face +1F615 ; Emoji # 6.1 [1] (😕) confused face +1F616 ; Emoji # 6.0 [1] (😖) confounded face +1F617 ; Emoji # 6.1 [1] (😗) kissing face +1F618 ; Emoji # 6.0 [1] (😘) face blowing a kiss +1F619 ; Emoji # 6.1 [1] (😙) kissing face with smiling eyes +1F61A ; Emoji # 6.0 [1] (😚) kissing face with closed eyes +1F61B ; Emoji # 6.1 [1] (😛) face with tongue +1F61C..1F61E ; Emoji # 6.0 [3] (😜..😞) winking face with tongue..disappointed face +1F61F ; Emoji # 6.1 [1] (😟) worried face +1F620..1F625 ; Emoji # 6.0 [6] (😠..😥) angry face..sad but relieved face +1F626..1F627 ; Emoji # 6.1 [2] (😦..😧) frowning face with open mouth..anguished face +1F628..1F62B ; Emoji # 6.0 [4] (😨..😫) fearful face..tired face +1F62C ; Emoji # 6.1 [1] (😬) grimacing face +1F62D ; Emoji # 6.0 [1] (😭) loudly crying face +1F62E..1F62F ; Emoji # 6.1 [2] (😮..😯) face with open mouth..hushed face +1F630..1F633 ; Emoji # 6.0 [4] (😰..😳) anxious face with sweat..flushed face +1F634 ; Emoji # 6.1 [1] (😴) sleeping face +1F635..1F640 ; Emoji # 6.0 [12] (😵..🙀) dizzy face..weary cat face +1F641..1F642 ; Emoji # 7.0 [2] (🙁..🙂) slightly frowning face..slightly smiling face +1F643..1F644 ; Emoji # 8.0 [2] (🙃..🙄) upside-down face..face with rolling eyes +1F645..1F64F ; Emoji # 6.0 [11] (🙅..🙏) person gesturing NO..folded hands +1F680..1F6C5 ; Emoji # 6.0 [70] (🚀..🛅) rocket..left luggage +1F6CB..1F6CF ; Emoji # 7.0 [5] (🛋️..🛏️) couch and lamp..bed +1F6D0 ; Emoji # 8.0 [1] (🛐) place of worship +1F6D1..1F6D2 ; Emoji # 9.0 [2] (🛑..🛒) stop sign..shopping cart +1F6E0..1F6E5 ; Emoji # 7.0 [6] (🛠️..🛥️) hammer and wrench..motor boat +1F6E9 ; Emoji # 7.0 [1] (🛩️) small airplane +1F6EB..1F6EC ; Emoji # 7.0 [2] (🛫..🛬) airplane departure..airplane arrival +1F6F0 ; Emoji # 7.0 [1] (🛰️) satellite +1F6F3 ; Emoji # 7.0 [1] (🛳️) passenger ship +1F6F4..1F6F6 ; Emoji # 9.0 [3] (🛴..🛶) kick scooter..canoe +1F6F7..1F6F8 ; Emoji # 10.0 [2] (🛷..🛸) sled..flying saucer +1F910..1F918 ; Emoji # 8.0 [9] (🤐..🤘) zipper-mouth face..sign of the horns +1F919..1F91E ; Emoji # 9.0 [6] (🤙..🤞) call me hand..crossed fingers +1F91F ; Emoji # 10.0 [1] (🤟) love-you gesture +1F920..1F927 ; Emoji # 9.0 [8] (🤠..🤧) cowboy hat face..sneezing face +1F928..1F92F ; Emoji # 10.0 [8] (🤨..🤯) face with raised eyebrow..exploding head +1F930 ; Emoji # 9.0 [1] (🤰) pregnant woman +1F931..1F932 ; Emoji # 10.0 [2] (🤱..🤲) breast-feeding..palms up together +1F933..1F93A ; Emoji # 9.0 [8] (🤳..🤺) selfie..person fencing +1F93C..1F93E ; Emoji # 9.0 [3] (🤼..🤾) people wrestling..person playing handball +1F940..1F945 ; Emoji # 9.0 [6] (🥀..🥅) wilted flower..goal net +1F947..1F94B ; Emoji # 9.0 [5] (🥇..🥋) 1st place medal..martial arts uniform +1F94C ; Emoji # 10.0 [1] (🥌) curling stone +1F950..1F95E ; Emoji # 9.0 [15] (🥐..🥞) croissant..pancakes +1F95F..1F96B ; Emoji # 10.0 [13] (🥟..🥫) dumpling..canned food +1F980..1F984 ; Emoji # 8.0 [5] (🦀..🦄) crab..unicorn face +1F985..1F991 ; Emoji # 9.0 [13] (🦅..🦑) eagle..squid +1F992..1F997 ; Emoji # 10.0 [6] (🦒..🦗) giraffe..cricket +1F9C0 ; Emoji # 8.0 [1] (🧀) cheese wedge +1F9D0..1F9E6 ; Emoji # 10.0 [23] (🧐..🧦) face with monocle..socks + +# Total elements: 1182 + +# ================================================ + +# All omitted code points have Emoji_Presentation=No +# @missing: 0000..10FFFF ; Emoji_Presentation ; No + +231A..231B ; Emoji_Presentation # 1.1 [2] (⌚..⌛) watch..hourglass done +23E9..23EC ; Emoji_Presentation # 6.0 [4] (⏩..⏬) fast-forward button..fast down button +23F0 ; Emoji_Presentation # 6.0 [1] (⏰) alarm clock +23F3 ; Emoji_Presentation # 6.0 [1] (⏳) hourglass not done +25FD..25FE ; Emoji_Presentation # 3.2 [2] (◽..◾) white medium-small square..black medium-small square +2614..2615 ; Emoji_Presentation # 4.0 [2] (☔..☕) umbrella with rain drops..hot beverage +2648..2653 ; Emoji_Presentation # 1.1 [12] (♈..♓) Aries..Pisces +267F ; Emoji_Presentation # 4.1 [1] (♿) wheelchair symbol +2693 ; Emoji_Presentation # 4.1 [1] (⚓) anchor +26A1 ; Emoji_Presentation # 4.0 [1] (⚡) high voltage +26AA..26AB ; Emoji_Presentation # 4.1 [2] (⚪..⚫) white circle..black circle +26BD..26BE ; Emoji_Presentation # 5.2 [2] (⚽..⚾) soccer ball..baseball +26C4..26C5 ; Emoji_Presentation # 5.2 [2] (⛄..⛅) snowman without snow..sun behind cloud +26CE ; Emoji_Presentation # 6.0 [1] (⛎) Ophiuchus +26D4 ; Emoji_Presentation # 5.2 [1] (⛔) no entry +26EA ; Emoji_Presentation # 5.2 [1] (⛪) church +26F2..26F3 ; Emoji_Presentation # 5.2 [2] (⛲..⛳) fountain..flag in hole +26F5 ; Emoji_Presentation # 5.2 [1] (⛵) sailboat +26FA ; Emoji_Presentation # 5.2 [1] (⛺) tent +26FD ; Emoji_Presentation # 5.2 [1] (⛽) fuel pump +2705 ; Emoji_Presentation # 6.0 [1] (✅) white heavy check mark +270A..270B ; Emoji_Presentation # 6.0 [2] (✊..✋) raised fist..raised hand +2728 ; Emoji_Presentation # 6.0 [1] (✨) sparkles +274C ; Emoji_Presentation # 6.0 [1] (❌) cross mark +274E ; Emoji_Presentation # 6.0 [1] (❎) cross mark button +2753..2755 ; Emoji_Presentation # 6.0 [3] (❓..❕) question mark..white exclamation mark +2757 ; Emoji_Presentation # 5.2 [1] (❗) exclamation mark +2795..2797 ; Emoji_Presentation # 6.0 [3] (➕..➗) heavy plus sign..heavy division sign +27B0 ; Emoji_Presentation # 6.0 [1] (➰) curly loop +27BF ; Emoji_Presentation # 6.0 [1] (➿) double curly loop +2B1B..2B1C ; Emoji_Presentation # 5.1 [2] (⬛..⬜) black large square..white large square +2B50 ; Emoji_Presentation # 5.1 [1] (⭐) white medium star +2B55 ; Emoji_Presentation # 5.2 [1] (⭕) heavy large circle +1F004 ; Emoji_Presentation # 5.1 [1] (🀄) mahjong red dragon +1F0CF ; Emoji_Presentation # 6.0 [1] (🃏) joker +1F18E ; Emoji_Presentation # 6.0 [1] (🆎) AB button (blood type) +1F191..1F19A ; Emoji_Presentation # 6.0 [10] (🆑..🆚) CL button..VS button +1F1E6..1F1FF ; Emoji_Presentation # 6.0 [26] (🇦..🇿) regional indicator symbol letter a..regional indicator symbol letter z +1F201 ; Emoji_Presentation # 6.0 [1] (🈁) Japanese “here” button +1F21A ; Emoji_Presentation # 5.2 [1] (🈚) Japanese “free of charge” button +1F22F ; Emoji_Presentation # 5.2 [1] (🈯) Japanese “reserved” button +1F232..1F236 ; Emoji_Presentation # 6.0 [5] (🈲..🈶) Japanese “prohibited” button..Japanese “not free of charge” button +1F238..1F23A ; Emoji_Presentation # 6.0 [3] (🈸..🈺) Japanese “application” button..Japanese “open for business” button +1F250..1F251 ; Emoji_Presentation # 6.0 [2] (🉐..🉑) Japanese “bargain” button..Japanese “acceptable” button +1F300..1F320 ; Emoji_Presentation # 6.0 [33] (🌀..🌠) cyclone..shooting star +1F32D..1F32F ; Emoji_Presentation # 8.0 [3] (🌭..🌯) hot dog..burrito +1F330..1F335 ; Emoji_Presentation # 6.0 [6] (🌰..🌵) chestnut..cactus +1F337..1F37C ; Emoji_Presentation # 6.0 [70] (🌷..🍼) tulip..baby bottle +1F37E..1F37F ; Emoji_Presentation # 8.0 [2] (🍾..🍿) bottle with popping cork..popcorn +1F380..1F393 ; Emoji_Presentation # 6.0 [20] (🎀..🎓) ribbon..graduation cap +1F3A0..1F3C4 ; Emoji_Presentation # 6.0 [37] (🎠..🏄) carousel horse..person surfing +1F3C5 ; Emoji_Presentation # 7.0 [1] (🏅) sports medal +1F3C6..1F3CA ; Emoji_Presentation # 6.0 [5] (🏆..🏊) trophy..person swimming +1F3CF..1F3D3 ; Emoji_Presentation # 8.0 [5] (🏏..🏓) cricket game..ping pong +1F3E0..1F3F0 ; Emoji_Presentation # 6.0 [17] (🏠..🏰) house..castle +1F3F4 ; Emoji_Presentation # 7.0 [1] (🏴) black flag +1F3F8..1F3FF ; Emoji_Presentation # 8.0 [8] (🏸..🏿) badminton..dark skin tone +1F400..1F43E ; Emoji_Presentation # 6.0 [63] (🐀..🐾) rat..paw prints +1F440 ; Emoji_Presentation # 6.0 [1] (👀) eyes +1F442..1F4F7 ; Emoji_Presentation # 6.0[182] (👂..📷) ear..camera +1F4F8 ; Emoji_Presentation # 7.0 [1] (📸) camera with flash +1F4F9..1F4FC ; Emoji_Presentation # 6.0 [4] (📹..📼) video camera..videocassette +1F4FF ; Emoji_Presentation # 8.0 [1] (📿) prayer beads +1F500..1F53D ; Emoji_Presentation # 6.0 [62] (🔀..🔽) shuffle tracks button..down button +1F54B..1F54E ; Emoji_Presentation # 8.0 [4] (🕋..🕎) kaaba..menorah +1F550..1F567 ; Emoji_Presentation # 6.0 [24] (🕐..🕧) one o’clock..twelve-thirty +1F57A ; Emoji_Presentation # 9.0 [1] (🕺) man dancing +1F595..1F596 ; Emoji_Presentation # 7.0 [2] (🖕..🖖) middle finger..vulcan salute +1F5A4 ; Emoji_Presentation # 9.0 [1] (🖤) black heart +1F5FB..1F5FF ; Emoji_Presentation # 6.0 [5] (🗻..🗿) mount fuji..moai +1F600 ; Emoji_Presentation # 6.1 [1] (😀) grinning face +1F601..1F610 ; Emoji_Presentation # 6.0 [16] (😁..😐) beaming face with smiling eyes..neutral face +1F611 ; Emoji_Presentation # 6.1 [1] (😑) expressionless face +1F612..1F614 ; Emoji_Presentation # 6.0 [3] (😒..😔) unamused face..pensive face +1F615 ; Emoji_Presentation # 6.1 [1] (😕) confused face +1F616 ; Emoji_Presentation # 6.0 [1] (😖) confounded face +1F617 ; Emoji_Presentation # 6.1 [1] (😗) kissing face +1F618 ; Emoji_Presentation # 6.0 [1] (😘) face blowing a kiss +1F619 ; Emoji_Presentation # 6.1 [1] (😙) kissing face with smiling eyes +1F61A ; Emoji_Presentation # 6.0 [1] (😚) kissing face with closed eyes +1F61B ; Emoji_Presentation # 6.1 [1] (😛) face with tongue +1F61C..1F61E ; Emoji_Presentation # 6.0 [3] (😜..😞) winking face with tongue..disappointed face +1F61F ; Emoji_Presentation # 6.1 [1] (😟) worried face +1F620..1F625 ; Emoji_Presentation # 6.0 [6] (😠..😥) angry face..sad but relieved face +1F626..1F627 ; Emoji_Presentation # 6.1 [2] (😦..😧) frowning face with open mouth..anguished face +1F628..1F62B ; Emoji_Presentation # 6.0 [4] (😨..😫) fearful face..tired face +1F62C ; Emoji_Presentation # 6.1 [1] (😬) grimacing face +1F62D ; Emoji_Presentation # 6.0 [1] (😭) loudly crying face +1F62E..1F62F ; Emoji_Presentation # 6.1 [2] (😮..😯) face with open mouth..hushed face +1F630..1F633 ; Emoji_Presentation # 6.0 [4] (😰..😳) anxious face with sweat..flushed face +1F634 ; Emoji_Presentation # 6.1 [1] (😴) sleeping face +1F635..1F640 ; Emoji_Presentation # 6.0 [12] (😵..🙀) dizzy face..weary cat face +1F641..1F642 ; Emoji_Presentation # 7.0 [2] (🙁..🙂) slightly frowning face..slightly smiling face +1F643..1F644 ; Emoji_Presentation # 8.0 [2] (🙃..🙄) upside-down face..face with rolling eyes +1F645..1F64F ; Emoji_Presentation # 6.0 [11] (🙅..🙏) person gesturing NO..folded hands +1F680..1F6C5 ; Emoji_Presentation # 6.0 [70] (🚀..🛅) rocket..left luggage +1F6CC ; Emoji_Presentation # 7.0 [1] (🛌) person in bed +1F6D0 ; Emoji_Presentation # 8.0 [1] (🛐) place of worship +1F6D1..1F6D2 ; Emoji_Presentation # 9.0 [2] (🛑..🛒) stop sign..shopping cart +1F6EB..1F6EC ; Emoji_Presentation # 7.0 [2] (🛫..🛬) airplane departure..airplane arrival +1F6F4..1F6F6 ; Emoji_Presentation # 9.0 [3] (🛴..🛶) kick scooter..canoe +1F6F7..1F6F8 ; Emoji_Presentation # 10.0 [2] (🛷..🛸) sled..flying saucer +1F910..1F918 ; Emoji_Presentation # 8.0 [9] (🤐..🤘) zipper-mouth face..sign of the horns +1F919..1F91E ; Emoji_Presentation # 9.0 [6] (🤙..🤞) call me hand..crossed fingers +1F91F ; Emoji_Presentation # 10.0 [1] (🤟) love-you gesture +1F920..1F927 ; Emoji_Presentation # 9.0 [8] (🤠..🤧) cowboy hat face..sneezing face +1F928..1F92F ; Emoji_Presentation # 10.0 [8] (🤨..🤯) face with raised eyebrow..exploding head +1F930 ; Emoji_Presentation # 9.0 [1] (🤰) pregnant woman +1F931..1F932 ; Emoji_Presentation # 10.0 [2] (🤱..🤲) breast-feeding..palms up together +1F933..1F93A ; Emoji_Presentation # 9.0 [8] (🤳..🤺) selfie..person fencing +1F93C..1F93E ; Emoji_Presentation # 9.0 [3] (🤼..🤾) people wrestling..person playing handball +1F940..1F945 ; Emoji_Presentation # 9.0 [6] (🥀..🥅) wilted flower..goal net +1F947..1F94B ; Emoji_Presentation # 9.0 [5] (🥇..🥋) 1st place medal..martial arts uniform +1F94C ; Emoji_Presentation # 10.0 [1] (🥌) curling stone +1F950..1F95E ; Emoji_Presentation # 9.0 [15] (🥐..🥞) croissant..pancakes +1F95F..1F96B ; Emoji_Presentation # 10.0 [13] (🥟..🥫) dumpling..canned food +1F980..1F984 ; Emoji_Presentation # 8.0 [5] (🦀..🦄) crab..unicorn face +1F985..1F991 ; Emoji_Presentation # 9.0 [13] (🦅..🦑) eagle..squid +1F992..1F997 ; Emoji_Presentation # 10.0 [6] (🦒..🦗) giraffe..cricket +1F9C0 ; Emoji_Presentation # 8.0 [1] (🧀) cheese wedge +1F9D0..1F9E6 ; Emoji_Presentation # 10.0 [23] (🧐..🧦) face with monocle..socks + +# Total elements: 966 + +# ================================================ + +# All omitted code points have Emoji_Modifier=No +# @missing: 0000..10FFFF ; Emoji_Modifier ; No + +1F3FB..1F3FF ; Emoji_Modifier # 8.0 [5] (🏻..🏿) light skin tone..dark skin tone + +# Total elements: 5 + +# ================================================ + +# All omitted code points have Emoji_Modifier_Base=No +# @missing: 0000..10FFFF ; Emoji_Modifier_Base ; No + +261D ; Emoji_Modifier_Base # 1.1 [1] (☝️) index pointing up +26F9 ; Emoji_Modifier_Base # 5.2 [1] (⛹️) person bouncing ball +270A..270B ; Emoji_Modifier_Base # 6.0 [2] (✊..✋) raised fist..raised hand +270C..270D ; Emoji_Modifier_Base # 1.1 [2] (✌️..✍️) victory hand..writing hand +1F385 ; Emoji_Modifier_Base # 6.0 [1] (🎅) Santa Claus +1F3C2..1F3C4 ; Emoji_Modifier_Base # 6.0 [3] (🏂..🏄) snowboarder..person surfing +1F3C7 ; Emoji_Modifier_Base # 6.0 [1] (🏇) horse racing +1F3CA ; Emoji_Modifier_Base # 6.0 [1] (🏊) person swimming +1F3CB..1F3CC ; Emoji_Modifier_Base # 7.0 [2] (🏋️..🏌️) person lifting weights..person golfing +1F442..1F443 ; Emoji_Modifier_Base # 6.0 [2] (👂..👃) ear..nose +1F446..1F450 ; Emoji_Modifier_Base # 6.0 [11] (👆..👐) backhand index pointing up..open hands +1F466..1F469 ; Emoji_Modifier_Base # 6.0 [4] (👦..👩) boy..woman +1F46E ; Emoji_Modifier_Base # 6.0 [1] (👮) police officer +1F470..1F478 ; Emoji_Modifier_Base # 6.0 [9] (👰..👸) bride with veil..princess +1F47C ; Emoji_Modifier_Base # 6.0 [1] (👼) baby angel +1F481..1F483 ; Emoji_Modifier_Base # 6.0 [3] (💁..💃) person tipping hand..woman dancing +1F485..1F487 ; Emoji_Modifier_Base # 6.0 [3] (💅..💇) nail polish..person getting haircut +1F4AA ; Emoji_Modifier_Base # 6.0 [1] (💪) flexed biceps +1F574..1F575 ; Emoji_Modifier_Base # 7.0 [2] (🕴️..🕵️) man in suit levitating..detective +1F57A ; Emoji_Modifier_Base # 9.0 [1] (🕺) man dancing +1F590 ; Emoji_Modifier_Base # 7.0 [1] (🖐️) hand with fingers splayed +1F595..1F596 ; Emoji_Modifier_Base # 7.0 [2] (🖕..🖖) middle finger..vulcan salute +1F645..1F647 ; Emoji_Modifier_Base # 6.0 [3] (🙅..🙇) person gesturing NO..person bowing +1F64B..1F64F ; Emoji_Modifier_Base # 6.0 [5] (🙋..🙏) person raising hand..folded hands +1F6A3 ; Emoji_Modifier_Base # 6.0 [1] (🚣) person rowing boat +1F6B4..1F6B6 ; Emoji_Modifier_Base # 6.0 [3] (🚴..🚶) person biking..person walking +1F6C0 ; Emoji_Modifier_Base # 6.0 [1] (🛀) person taking bath +1F6CC ; Emoji_Modifier_Base # 7.0 [1] (🛌) person in bed +1F918 ; Emoji_Modifier_Base # 8.0 [1] (🤘) sign of the horns +1F919..1F91C ; Emoji_Modifier_Base # 9.0 [4] (🤙..🤜) call me hand..right-facing fist +1F91E ; Emoji_Modifier_Base # 9.0 [1] (🤞) crossed fingers +1F91F ; Emoji_Modifier_Base # 10.0 [1] (🤟) love-you gesture +1F926 ; Emoji_Modifier_Base # 9.0 [1] (🤦) person facepalming +1F930 ; Emoji_Modifier_Base # 9.0 [1] (🤰) pregnant woman +1F931..1F932 ; Emoji_Modifier_Base # 10.0 [2] (🤱..🤲) breast-feeding..palms up together +1F933..1F939 ; Emoji_Modifier_Base # 9.0 [7] (🤳..🤹) selfie..person juggling +1F93D..1F93E ; Emoji_Modifier_Base # 9.0 [2] (🤽..🤾) person playing water polo..person playing handball +1F9D1..1F9DD ; Emoji_Modifier_Base # 10.0 [13] (🧑..🧝) adult..elf + +# Total elements: 102 + +# ================================================ + +# All omitted code points have Emoji_Component=No +# @missing: 0000..10FFFF ; Emoji_Component ; No + +0023 ; Emoji_Component # 1.1 [1] (#️) number sign +002A ; Emoji_Component # 1.1 [1] (*️) asterisk +0030..0039 ; Emoji_Component # 1.1 [10] (0️..9️) digit zero..digit nine +200D ; Emoji_Component # 1.1 [1] (‍) zero width joiner +20E3 ; Emoji_Component # 3.0 [1] (⃣) combining enclosing keycap +FE0F ; Emoji_Component # 3.2 [1] () VARIATION SELECTOR-16 +1F1E6..1F1FF ; Emoji_Component # 6.0 [26] (🇦..🇿) regional indicator symbol letter a..regional indicator symbol letter z +1F3FB..1F3FF ; Emoji_Component # 8.0 [5] (🏻..🏿) light skin tone..dark skin tone +E0020..E007F ; Emoji_Component # 3.1 [96] (󠀠..󠁿) tag space..cancel tag + +# Total elements: 142 + +# ================================================ + +# All omitted code points have Extended_Pictographic=No +# @missing: 0000..10FFFF ; Extended_Pictographic ; No + +00A9 ; Extended_Pictographic# 1.1 [1] (©️) copyright +00AE ; Extended_Pictographic# 1.1 [1] (®️) registered +203C ; Extended_Pictographic# 1.1 [1] (‼️) double exclamation mark +2049 ; Extended_Pictographic# 3.0 [1] (⁉️) exclamation question mark +2122 ; Extended_Pictographic# 1.1 [1] (™️) trade mark +2139 ; Extended_Pictographic# 3.0 [1] (ℹ️) information +2194..2199 ; Extended_Pictographic# 1.1 [6] (↔️..↙️) left-right arrow..down-left arrow +21A9..21AA ; Extended_Pictographic# 1.1 [2] (↩️..↪️) right arrow curving left..left arrow curving right +231A..231B ; Extended_Pictographic# 1.1 [2] (⌚..⌛) watch..hourglass done +2328 ; Extended_Pictographic# 1.1 [1] (⌨️) keyboard +2388 ; Extended_Pictographic# 3.0 [1] (⎈️) HELM SYMBOL +23CF ; Extended_Pictographic# 4.0 [1] (⏏️) eject button +23E9..23F3 ; Extended_Pictographic# 6.0 [11] (⏩..⏳) fast-forward button..hourglass not done +23F8..23FA ; Extended_Pictographic# 7.0 [3] (⏸️..⏺️) pause button..record button +24C2 ; Extended_Pictographic# 1.1 [1] (Ⓜ️) circled M +25AA..25AB ; Extended_Pictographic# 1.1 [2] (▪️..▫️) black small square..white small square +25B6 ; Extended_Pictographic# 1.1 [1] (▶️) play button +25C0 ; Extended_Pictographic# 1.1 [1] (◀️) reverse button +25FB..25FE ; Extended_Pictographic# 3.2 [4] (◻️..◾) white medium square..black medium-small square +2600..2605 ; Extended_Pictographic# 1.1 [6] (☀️..★️) sun..BLACK STAR +2607..2612 ; Extended_Pictographic# 1.1 [12] (☇️..☒️) LIGHTNING..BALLOT BOX WITH X +2614..2615 ; Extended_Pictographic# 4.0 [2] (☔..☕) umbrella with rain drops..hot beverage +2616..2617 ; Extended_Pictographic# 3.2 [2] (☖️..☗️) WHITE SHOGI PIECE..BLACK SHOGI PIECE +2618 ; Extended_Pictographic# 4.1 [1] (☘️) shamrock +2619 ; Extended_Pictographic# 3.0 [1] (☙️) REVERSED ROTATED FLORAL HEART BULLET +261A..266F ; Extended_Pictographic# 1.1 [86] (☚️..♯️) BLACK LEFT POINTING INDEX..MUSIC SHARP SIGN +2670..2671 ; Extended_Pictographic# 3.0 [2] (♰️..♱️) WEST SYRIAC CROSS..EAST SYRIAC CROSS +2672..267D ; Extended_Pictographic# 3.2 [12] (♲️..♽️) UNIVERSAL RECYCLING SYMBOL..PARTIALLY-RECYCLED PAPER SYMBOL +267E..267F ; Extended_Pictographic# 4.1 [2] (♾️..♿) PERMANENT PAPER SIGN..wheelchair symbol +2680..2689 ; Extended_Pictographic# 3.2 [10] (⚀️..⚉️) DIE FACE-1..BLACK CIRCLE WITH TWO WHITE DOTS +268A..2691 ; Extended_Pictographic# 4.0 [8] (⚊️..⚑️) MONOGRAM FOR YANG..BLACK FLAG +2692..269C ; Extended_Pictographic# 4.1 [11] (⚒️..⚜️) hammer and pick..fleur-de-lis +269D ; Extended_Pictographic# 5.1 [1] (⚝️) OUTLINED WHITE STAR +269E..269F ; Extended_Pictographic# 5.2 [2] (⚞️..⚟️) THREE LINES CONVERGING RIGHT..THREE LINES CONVERGING LEFT +26A0..26A1 ; Extended_Pictographic# 4.0 [2] (⚠️..⚡) warning..high voltage +26A2..26B1 ; Extended_Pictographic# 4.1 [16] (⚢️..⚱️) DOUBLED FEMALE SIGN..funeral urn +26B2 ; Extended_Pictographic# 5.0 [1] (⚲️) NEUTER +26B3..26BC ; Extended_Pictographic# 5.1 [10] (⚳️..⚼️) CERES..SESQUIQUADRATE +26BD..26BF ; Extended_Pictographic# 5.2 [3] (⚽..⚿️) soccer ball..SQUARED KEY +26C0..26C3 ; Extended_Pictographic# 5.1 [4] (⛀️..⛃️) WHITE DRAUGHTS MAN..BLACK DRAUGHTS KING +26C4..26CD ; Extended_Pictographic# 5.2 [10] (⛄..⛍️) snowman without snow..DISABLED CAR +26CE ; Extended_Pictographic# 6.0 [1] (⛎) Ophiuchus +26CF..26E1 ; Extended_Pictographic# 5.2 [19] (⛏️..⛡️) pick..RESTRICTED LEFT ENTRY-2 +26E2 ; Extended_Pictographic# 6.0 [1] (⛢️) ASTRONOMICAL SYMBOL FOR URANUS +26E3 ; Extended_Pictographic# 5.2 [1] (⛣️) HEAVY CIRCLE WITH STROKE AND TWO DOTS ABOVE +26E4..26E7 ; Extended_Pictographic# 6.0 [4] (⛤️..⛧️) PENTAGRAM..INVERTED PENTAGRAM +26E8..26FF ; Extended_Pictographic# 5.2 [24] (⛨️..⛿️) BLACK CROSS ON SHIELD..WHITE FLAG WITH HORIZONTAL MIDDLE BLACK STRIPE +2700 ; Extended_Pictographic# 7.0 [1] (✀️) BLACK SAFETY SCISSORS +2701..2704 ; Extended_Pictographic# 1.1 [4] (✁️..✄️) UPPER BLADE SCISSORS..WHITE SCISSORS +2705 ; Extended_Pictographic# 6.0 [1] (✅) white heavy check mark +2708..2709 ; Extended_Pictographic# 1.1 [2] (✈️..✉️) airplane..envelope +270A..270B ; Extended_Pictographic# 6.0 [2] (✊..✋) raised fist..raised hand +270C..2712 ; Extended_Pictographic# 1.1 [7] (✌️..✒️) victory hand..black nib +2714 ; Extended_Pictographic# 1.1 [1] (✔️) heavy check mark +2716 ; Extended_Pictographic# 1.1 [1] (✖️) heavy multiplication x +271D ; Extended_Pictographic# 1.1 [1] (✝️) latin cross +2721 ; Extended_Pictographic# 1.1 [1] (✡️) star of David +2728 ; Extended_Pictographic# 6.0 [1] (✨) sparkles +2733..2734 ; Extended_Pictographic# 1.1 [2] (✳️..✴️) eight-spoked asterisk..eight-pointed star +2744 ; Extended_Pictographic# 1.1 [1] (❄️) snowflake +2747 ; Extended_Pictographic# 1.1 [1] (❇️) sparkle +274C ; Extended_Pictographic# 6.0 [1] (❌) cross mark +274E ; Extended_Pictographic# 6.0 [1] (❎) cross mark button +2753..2755 ; Extended_Pictographic# 6.0 [3] (❓..❕) question mark..white exclamation mark +2757 ; Extended_Pictographic# 5.2 [1] (❗) exclamation mark +2763..2767 ; Extended_Pictographic# 1.1 [5] (❣️..❧️) heavy heart exclamation..ROTATED FLORAL HEART BULLET +2795..2797 ; Extended_Pictographic# 6.0 [3] (➕..➗) heavy plus sign..heavy division sign +27A1 ; Extended_Pictographic# 1.1 [1] (➡️) right arrow +27B0 ; Extended_Pictographic# 6.0 [1] (➰) curly loop +27BF ; Extended_Pictographic# 6.0 [1] (➿) double curly loop +2934..2935 ; Extended_Pictographic# 3.2 [2] (⤴️..⤵️) right arrow curving up..right arrow curving down +2B05..2B07 ; Extended_Pictographic# 4.0 [3] (⬅️..⬇️) left arrow..down arrow +2B1B..2B1C ; Extended_Pictographic# 5.1 [2] (⬛..⬜) black large square..white large square +2B50 ; Extended_Pictographic# 5.1 [1] (⭐) white medium star +2B55 ; Extended_Pictographic# 5.2 [1] (⭕) heavy large circle +3030 ; Extended_Pictographic# 1.1 [1] (〰️) wavy dash +303D ; Extended_Pictographic# 3.2 [1] (〽️) part alternation mark +3297 ; Extended_Pictographic# 1.1 [1] (㊗️) Japanese “congratulations” button +3299 ; Extended_Pictographic# 1.1 [1] (㊙️) Japanese “secret” button +1F000..1F02B ; Extended_Pictographic# 5.1 [44] (🀀️..🀫️) MAHJONG TILE EAST WIND..MAHJONG TILE BACK +1F02C..1F02F ; Extended_Pictographic# 10.0 [4] (🀬️..🀯️) .. +1F030..1F093 ; Extended_Pictographic# 5.1[100] (🀰️..🂓️) DOMINO TILE HORIZONTAL BACK..DOMINO TILE VERTICAL-06-06 +1F094..1F09F ; Extended_Pictographic# 10.0 [12] (🂔️..🂟️) .. +1F0A0..1F0AE ; Extended_Pictographic# 6.0 [15] (🂠️..🂮️) PLAYING CARD BACK..PLAYING CARD KING OF SPADES +1F0AF..1F0B0 ; Extended_Pictographic# 10.0 [2] (🂯️..🂰️) .. +1F0B1..1F0BE ; Extended_Pictographic# 6.0 [14] (🂱️..🂾️) PLAYING CARD ACE OF HEARTS..PLAYING CARD KING OF HEARTS +1F0BF ; Extended_Pictographic# 7.0 [1] (🂿️) PLAYING CARD RED JOKER +1F0C0 ; Extended_Pictographic# 10.0 [1] (🃀️) +1F0C1..1F0CF ; Extended_Pictographic# 6.0 [15] (🃁️..🃏) PLAYING CARD ACE OF DIAMONDS..joker +1F0D0 ; Extended_Pictographic# 10.0 [1] (🃐️) +1F0D1..1F0DF ; Extended_Pictographic# 6.0 [15] (🃑️..🃟️) PLAYING CARD ACE OF CLUBS..PLAYING CARD WHITE JOKER +1F0E0..1F0F5 ; Extended_Pictographic# 7.0 [22] (🃠️..🃵️) PLAYING CARD FOOL..PLAYING CARD TRUMP-21 +1F0F6..1F0FF ; Extended_Pictographic# 10.0 [10] (🃶️..🃿️) .. +1F10D..1F10F ; Extended_Pictographic# 10.0 [3] (🄍️..🄏️) .. +1F12F ; Extended_Pictographic# 10.0 [1] (🄯️) +1F16C..1F16F ; Extended_Pictographic# 10.0 [4] (🅬️..🅯️) .. +1F170..1F171 ; Extended_Pictographic# 6.0 [2] (🅰️..🅱️) A button (blood type)..B button (blood type) +1F17E ; Extended_Pictographic# 6.0 [1] (🅾️) O button (blood type) +1F17F ; Extended_Pictographic# 5.2 [1] (🅿️) P button +1F18E ; Extended_Pictographic# 6.0 [1] (🆎) AB button (blood type) +1F191..1F19A ; Extended_Pictographic# 6.0 [10] (🆑..🆚) CL button..VS button +1F1AD..1F1E5 ; Extended_Pictographic# 10.0 [57] (🆭️..🇥️) .. +1F201..1F202 ; Extended_Pictographic# 6.0 [2] (🈁..🈂️) Japanese “here” button..Japanese “service charge” button +1F203..1F20F ; Extended_Pictographic# 10.0 [13] (🈃️..🈏️) .. +1F21A ; Extended_Pictographic# 5.2 [1] (🈚) Japanese “free of charge” button +1F22F ; Extended_Pictographic# 5.2 [1] (🈯) Japanese “reserved” button +1F232..1F23A ; Extended_Pictographic# 6.0 [9] (🈲..🈺) Japanese “prohibited” button..Japanese “open for business” button +1F23C..1F23F ; Extended_Pictographic# 10.0 [4] (🈼️..🈿️) .. +1F249..1F24F ; Extended_Pictographic# 10.0 [7] (🉉️..🉏️) .. +1F250..1F251 ; Extended_Pictographic# 6.0 [2] (🉐..🉑) Japanese “bargain” button..Japanese “acceptable” button +1F252..1F25F ; Extended_Pictographic# 10.0 [14] (🉒️..🉟️) .. +1F260..1F265 ; Extended_Pictographic# 10.0 [6] (🉠️..🉥️) ROUNDED SYMBOL FOR FU..ROUNDED SYMBOL FOR CAI +1F266..1F2FF ; Extended_Pictographic# 10.0[154] (🉦️..🋿️) .. +1F300..1F320 ; Extended_Pictographic# 6.0 [33] (🌀..🌠) cyclone..shooting star +1F321..1F32C ; Extended_Pictographic# 7.0 [12] (🌡️..🌬️) thermometer..wind face +1F32D..1F32F ; Extended_Pictographic# 8.0 [3] (🌭..🌯) hot dog..burrito +1F330..1F335 ; Extended_Pictographic# 6.0 [6] (🌰..🌵) chestnut..cactus +1F336 ; Extended_Pictographic# 7.0 [1] (🌶️) hot pepper +1F337..1F37C ; Extended_Pictographic# 6.0 [70] (🌷..🍼) tulip..baby bottle +1F37D ; Extended_Pictographic# 7.0 [1] (🍽️) fork and knife with plate +1F37E..1F37F ; Extended_Pictographic# 8.0 [2] (🍾..🍿) bottle with popping cork..popcorn +1F380..1F393 ; Extended_Pictographic# 6.0 [20] (🎀..🎓) ribbon..graduation cap +1F394..1F39F ; Extended_Pictographic# 7.0 [12] (🎔️..🎟️) HEART WITH TIP ON THE LEFT..admission tickets +1F3A0..1F3C4 ; Extended_Pictographic# 6.0 [37] (🎠..🏄) carousel horse..person surfing +1F3C5 ; Extended_Pictographic# 7.0 [1] (🏅) sports medal +1F3C6..1F3CA ; Extended_Pictographic# 6.0 [5] (🏆..🏊) trophy..person swimming +1F3CB..1F3CE ; Extended_Pictographic# 7.0 [4] (🏋️..🏎️) person lifting weights..racing car +1F3CF..1F3D3 ; Extended_Pictographic# 8.0 [5] (🏏..🏓) cricket game..ping pong +1F3D4..1F3DF ; Extended_Pictographic# 7.0 [12] (🏔️..🏟️) snow-capped mountain..stadium +1F3E0..1F3F0 ; Extended_Pictographic# 6.0 [17] (🏠..🏰) house..castle +1F3F1..1F3F7 ; Extended_Pictographic# 7.0 [7] (🏱️..🏷️) WHITE PENNANT..label +1F3F8..1F3FA ; Extended_Pictographic# 8.0 [3] (🏸..🏺) badminton..amphora +1F400..1F43E ; Extended_Pictographic# 6.0 [63] (🐀..🐾) rat..paw prints +1F43F ; Extended_Pictographic# 7.0 [1] (🐿️) chipmunk +1F440 ; Extended_Pictographic# 6.0 [1] (👀) eyes +1F441 ; Extended_Pictographic# 7.0 [1] (👁️) eye +1F442..1F4F7 ; Extended_Pictographic# 6.0[182] (👂..📷) ear..camera +1F4F8 ; Extended_Pictographic# 7.0 [1] (📸) camera with flash +1F4F9..1F4FC ; Extended_Pictographic# 6.0 [4] (📹..📼) video camera..videocassette +1F4FD..1F4FE ; Extended_Pictographic# 7.0 [2] (📽️..📾️) film projector..PORTABLE STEREO +1F4FF ; Extended_Pictographic# 8.0 [1] (📿) prayer beads +1F500..1F53D ; Extended_Pictographic# 6.0 [62] (🔀..🔽) shuffle tracks button..down button +1F53E..1F53F ; Extended_Pictographic# 7.0 [2] (🔾️..🔿️) LOWER RIGHT SHADOWED WHITE CIRCLE..UPPER RIGHT SHADOWED WHITE CIRCLE +1F540..1F543 ; Extended_Pictographic# 6.1 [4] (🕀️..🕃️) CIRCLED CROSS POMMEE..NOTCHED LEFT SEMICIRCLE WITH THREE DOTS +1F544..1F54A ; Extended_Pictographic# 7.0 [7] (🕄️..🕊️) NOTCHED RIGHT SEMICIRCLE WITH THREE DOTS..dove +1F54B..1F54F ; Extended_Pictographic# 8.0 [5] (🕋..🕏️) kaaba..BOWL OF HYGIEIA +1F550..1F567 ; Extended_Pictographic# 6.0 [24] (🕐..🕧) one o’clock..twelve-thirty +1F568..1F579 ; Extended_Pictographic# 7.0 [18] (🕨️..🕹️) RIGHT SPEAKER..joystick +1F57A ; Extended_Pictographic# 9.0 [1] (🕺) man dancing +1F57B..1F5A3 ; Extended_Pictographic# 7.0 [41] (🕻️..🖣️) LEFT HAND TELEPHONE RECEIVER..BLACK DOWN POINTING BACKHAND INDEX +1F5A4 ; Extended_Pictographic# 9.0 [1] (🖤) black heart +1F5A5..1F5FA ; Extended_Pictographic# 7.0 [86] (🖥️..🗺️) desktop computer..world map +1F5FB..1F5FF ; Extended_Pictographic# 6.0 [5] (🗻..🗿) mount fuji..moai +1F600 ; Extended_Pictographic# 6.1 [1] (😀) grinning face +1F601..1F610 ; Extended_Pictographic# 6.0 [16] (😁..😐) beaming face with smiling eyes..neutral face +1F611 ; Extended_Pictographic# 6.1 [1] (😑) expressionless face +1F612..1F614 ; Extended_Pictographic# 6.0 [3] (😒..😔) unamused face..pensive face +1F615 ; Extended_Pictographic# 6.1 [1] (😕) confused face +1F616 ; Extended_Pictographic# 6.0 [1] (😖) confounded face +1F617 ; Extended_Pictographic# 6.1 [1] (😗) kissing face +1F618 ; Extended_Pictographic# 6.0 [1] (😘) face blowing a kiss +1F619 ; Extended_Pictographic# 6.1 [1] (😙) kissing face with smiling eyes +1F61A ; Extended_Pictographic# 6.0 [1] (😚) kissing face with closed eyes +1F61B ; Extended_Pictographic# 6.1 [1] (😛) face with tongue +1F61C..1F61E ; Extended_Pictographic# 6.0 [3] (😜..😞) winking face with tongue..disappointed face +1F61F ; Extended_Pictographic# 6.1 [1] (😟) worried face +1F620..1F625 ; Extended_Pictographic# 6.0 [6] (😠..😥) angry face..sad but relieved face +1F626..1F627 ; Extended_Pictographic# 6.1 [2] (😦..😧) frowning face with open mouth..anguished face +1F628..1F62B ; Extended_Pictographic# 6.0 [4] (😨..😫) fearful face..tired face +1F62C ; Extended_Pictographic# 6.1 [1] (😬) grimacing face +1F62D ; Extended_Pictographic# 6.0 [1] (😭) loudly crying face +1F62E..1F62F ; Extended_Pictographic# 6.1 [2] (😮..😯) face with open mouth..hushed face +1F630..1F633 ; Extended_Pictographic# 6.0 [4] (😰..😳) anxious face with sweat..flushed face +1F634 ; Extended_Pictographic# 6.1 [1] (😴) sleeping face +1F635..1F640 ; Extended_Pictographic# 6.0 [12] (😵..🙀) dizzy face..weary cat face +1F641..1F642 ; Extended_Pictographic# 7.0 [2] (🙁..🙂) slightly frowning face..slightly smiling face +1F643..1F644 ; Extended_Pictographic# 8.0 [2] (🙃..🙄) upside-down face..face with rolling eyes +1F645..1F64F ; Extended_Pictographic# 6.0 [11] (🙅..🙏) person gesturing NO..folded hands +1F680..1F6C5 ; Extended_Pictographic# 6.0 [70] (🚀..🛅) rocket..left luggage +1F6C6..1F6CF ; Extended_Pictographic# 7.0 [10] (🛆️..🛏️) TRIANGLE WITH ROUNDED CORNERS..bed +1F6D0 ; Extended_Pictographic# 8.0 [1] (🛐) place of worship +1F6D1..1F6D2 ; Extended_Pictographic# 9.0 [2] (🛑..🛒) stop sign..shopping cart +1F6D3..1F6D4 ; Extended_Pictographic# 10.0 [2] (🛓️..🛔️) STUPA..PAGODA +1F6D5..1F6DF ; Extended_Pictographic# 10.0 [11] (🛕️..🛟️) .. +1F6E0..1F6EC ; Extended_Pictographic# 7.0 [13] (🛠️..🛬) hammer and wrench..airplane arrival +1F6ED..1F6EF ; Extended_Pictographic# 10.0 [3] (🛭️..🛯️) .. +1F6F0..1F6F3 ; Extended_Pictographic# 7.0 [4] (🛰️..🛳️) satellite..passenger ship +1F6F4..1F6F6 ; Extended_Pictographic# 9.0 [3] (🛴..🛶) kick scooter..canoe +1F6F7..1F6F8 ; Extended_Pictographic# 10.0 [2] (🛷..🛸) sled..flying saucer +1F6F9..1F6FF ; Extended_Pictographic# 10.0 [7] (🛹️..🛿️) .. +1F774..1F77F ; Extended_Pictographic# 10.0 [12] (🝴️..🝿️) .. +1F7D5..1F7FF ; Extended_Pictographic# 10.0 [43] (🟕️..🟿️) .. +1F80C..1F80F ; Extended_Pictographic# 10.0 [4] (🠌️..🠏️) .. +1F848..1F84F ; Extended_Pictographic# 10.0 [8] (🡈️..🡏️) .. +1F85A..1F85F ; Extended_Pictographic# 10.0 [6] (🡚️..🡟️) .. +1F888..1F88F ; Extended_Pictographic# 10.0 [8] (🢈️..🢏️) .. +1F8AE..1F8FF ; Extended_Pictographic# 10.0 [82] (🢮️..🣿️) .. +1F900..1F90B ; Extended_Pictographic# 10.0 [12] (🤀️..🤋️) CIRCLED CROSS FORMEE WITH FOUR DOTS..DOWNWARD FACING NOTCHED HOOK WITH DOT +1F90C..1F90F ; Extended_Pictographic# 10.0 [4] (🤌️..🤏️) .. +1F910..1F918 ; Extended_Pictographic# 8.0 [9] (🤐..🤘) zipper-mouth face..sign of the horns +1F919..1F91E ; Extended_Pictographic# 9.0 [6] (🤙..🤞) call me hand..crossed fingers +1F91F ; Extended_Pictographic# 10.0 [1] (🤟) love-you gesture +1F920..1F927 ; Extended_Pictographic# 9.0 [8] (🤠..🤧) cowboy hat face..sneezing face +1F928..1F92F ; Extended_Pictographic# 10.0 [8] (🤨..🤯) face with raised eyebrow..exploding head +1F930 ; Extended_Pictographic# 9.0 [1] (🤰) pregnant woman +1F931..1F932 ; Extended_Pictographic# 10.0 [2] (🤱..🤲) breast-feeding..palms up together +1F933..1F93A ; Extended_Pictographic# 9.0 [8] (🤳..🤺) selfie..person fencing +1F93C..1F93E ; Extended_Pictographic# 9.0 [3] (🤼..🤾) people wrestling..person playing handball +1F93F ; Extended_Pictographic# 10.0 [1] (🤿️) +1F940..1F945 ; Extended_Pictographic# 9.0 [6] (🥀..🥅) wilted flower..goal net +1F947..1F94B ; Extended_Pictographic# 9.0 [5] (🥇..🥋) 1st place medal..martial arts uniform +1F94C ; Extended_Pictographic# 10.0 [1] (🥌) curling stone +1F94D..1F94F ; Extended_Pictographic# 10.0 [3] (🥍️..🥏️) .. +1F950..1F95E ; Extended_Pictographic# 9.0 [15] (🥐..🥞) croissant..pancakes +1F95F..1F96B ; Extended_Pictographic# 10.0 [13] (🥟..🥫) dumpling..canned food +1F96C..1F97F ; Extended_Pictographic# 10.0 [20] (🥬️..🥿️) .. +1F980..1F984 ; Extended_Pictographic# 8.0 [5] (🦀..🦄) crab..unicorn face +1F985..1F991 ; Extended_Pictographic# 9.0 [13] (🦅..🦑) eagle..squid +1F992..1F997 ; Extended_Pictographic# 10.0 [6] (🦒..🦗) giraffe..cricket +1F998..1F9BF ; Extended_Pictographic# 10.0 [40] (🦘️..🦿️) .. +1F9C0 ; Extended_Pictographic# 8.0 [1] (🧀) cheese wedge +1F9C1..1F9CF ; Extended_Pictographic# 10.0 [15] (🧁️..🧏️) .. +1F9D0..1F9E6 ; Extended_Pictographic# 10.0 [23] (🧐..🧦) face with monocle..socks +1F9E7..1FFFD ; Extended_Pictographic# 10.0[1559] (🧧️..🿽️) .. + +# Total elements: 3823 + +#EOF diff --git a/Scripts/emoji_ranges.py b/Scripts/emoji_ranges.py new file mode 100755 index 0000000000..be8adceb1d --- /dev/null +++ b/Scripts/emoji_ranges.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python +import sys +import os +import re +import commands +import subprocess +import io + +def fail(message): + print message + sys.exit(1) + +big_ranges = [ + (0x1F600,0x1F64F, ), + (0x1F300,0x1F5FF, ), + (0x1F680,0x1F6FF, ), + (0x2600,0x26FF, ), + (0x2700,0x27BF, ), + (0xFE00,0xFE0F, ), + (0x1F900,0x1F9FF, ), + (65024,65039,), + (8400, 8447,) + ] + +# def is_in_big_range(range_start, range_end) + +if __name__ == '__main__': + src_filename = "emoji-data.txt" + src_dir_path = os.path.dirname(__file__) + src_file_path = os.path.join(src_dir_path, src_filename) + print 'src_file_path', src_file_path + if not os.path.exists(src_file_path): + fail("Could not find input file") + + with io.open(src_file_path, "r", encoding="utf-8") as f: + text = f.read() + + lines = text.split('\n') + raw_ranges = [] + for line in lines: + if '#' in line: + line = line[:line.index('#')].strip() + if ';' not in line: + continue + print 'line:', line + range_text = line[:line.index(';')] + print '\t:', range_text + if '..' in range_text: + range_start_hex_string, range_end_hex_string = range_text.split('..') + else: + range_start_hex_string = range_end_hex_string = range_text.strip() + range_start = int(range_start_hex_string.strip(), 16) + range_end = int(range_end_hex_string.strip(), 16) + print '\t', range_start, range_end + + raw_ranges.append((range_start, range_end,)) + + raw_ranges += big_ranges + + raw_ranges.sort(key=lambda a:a[0]) + + + new_ranges = [] + for range_start, range_end in raw_ranges: + if len(new_ranges) > 0: + last_range = new_ranges[-1] + # print 'last_range', last_range + last_range_start, last_range_end = last_range + if range_start >= last_range_start and range_start <= last_range_end + 1: + # if last_range_end + 1 == range_start: + new_ranges = new_ranges[:-1] + print 'merging', last_range_start, last_range_end, 'and', range_start, range_end + new_ranges.append((last_range_start, range_end,)) + continue + + new_ranges.append((range_start, range_end,)) + + print + for range_start, range_end in new_ranges: + print '0x%X...0x%X, // %d Emotions' % (range_start, range_end, (1 + range_end - range_start), ) + print 'new_ranges:', len(new_ranges) + + diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m index 387631db5e..3f1201c38c 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m @@ -182,19 +182,20 @@ NS_ASSUME_NONNULL_BEGIN { OWSAssert(DisplayableText.kMaxJumbomojiCount == 5); - if (self.displayableText.jumbomojiCount) { - switch (self.displayableText.jumbomojiCount.integerValue) { - case 1: - return [UIFont ows_regularFontWithSize:35.f]; - case 2: - return [UIFont ows_regularFontWithSize:30.f]; - case 3: - case 4: - case 5: - return [UIFont ows_regularFontWithSize:25.f]; - default: - break; - } + switch (self.displayableText.jumbomojiCount) { + case 0: + break; + case 1: + return [UIFont ows_regularFontWithSize:35.f]; + case 2: + return [UIFont ows_regularFontWithSize:30.f]; + case 3: + case 4: + case 5: + return [UIFont ows_regularFontWithSize:25.f]; + default: + OWSFail(@"%@ Unexpected jumbomoji count: %zd", self.logTag, self.displayableText.jumbomojiCount); + break; } return [UIFont ows_dynamicTypeBodyFont]; diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m index afc20b6171..a3f54ef18d 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m @@ -323,12 +323,8 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) displayText = @""; } - NSNumber *_Nullable jumbomojiCount = [DisplayableText jumbomojiCountTo:fullText]; - - displayableText = [[DisplayableText alloc] initWithFullText:fullText - displayText:displayText - isTextTruncated:isTextTruncated - jumbomojiCount:jumbomojiCount]; + displayableText = + [[DisplayableText alloc] initWithFullText:fullText displayText:displayText isTextTruncated:isTextTruncated]; [[self displayableTextCache] setObject:displayableText forKey:interactionId]; } diff --git a/Signal/src/util/DisplayableText.swift b/Signal/src/util/DisplayableText.swift index bbc37ce832..fe7843ce1c 100644 --- a/Signal/src/util/DisplayableText.swift +++ b/Signal/src/util/DisplayableText.swift @@ -4,6 +4,253 @@ import Foundation +extension UnicodeScalar { + + // From: + // https://www.unicode.org/Public/emoji/ + // Current Version: + // https://www.unicode.org/Public/emoji/6.0/emoji-data.txt + var isEmoji: Bool { + + switch value { + case + 0x23...0x23, // 1 Emotions + 0x2A...0x2A, // 1 Emotions + 0x30...0x39, // 10 Emotions + 0xA9...0xA9, // 1 Emotions + 0xAE...0xAE, // 1 Emotions + 0x200D...0x200D, // 1 Emotions + 0x203C...0x203C, // 1 Emotions + 0x2049...0x2049, // 1 Emotions + 0x20D0...0x20E3, // 20 Emotions + 0x2122...0x2122, // 1 Emotions + 0x2139...0x2139, // 1 Emotions + 0x2194...0x2199, // 6 Emotions + 0x21A9...0x21AA, // 2 Emotions + 0x231A...0x231B, // 2 Emotions + 0x2328...0x2328, // 1 Emotions + 0x2388...0x2388, // 1 Emotions + 0x23CF...0x23CF, // 1 Emotions + 0x23E9...0x23F0, // 8 Emotions + 0x23F3...0x23F3, // 1 Emotions + 0x23F8...0x23FA, // 3 Emotions + 0x24C2...0x24C2, // 1 Emotions + 0x25AA...0x25AB, // 2 Emotions + 0x25B6...0x25B6, // 1 Emotions + 0x25C0...0x25C0, // 1 Emotions + 0x25FB...0x25FE, // 4 Emotions + 0x2600...0x260E, // 15 Emotions + 0x2611...0x2611, // 1 Emotions + 0x2614...0x261D, // 10 Emotions + 0x2620...0x2620, // 1 Emotions + 0x2622...0x2623, // 2 Emotions + 0x2626...0x2626, // 1 Emotions + 0x262A...0x262A, // 1 Emotions + 0x262E...0x262F, // 2 Emotions + 0x2638...0x263A, // 3 Emotions + 0x2640...0x2640, // 1 Emotions + 0x2642...0x2642, // 1 Emotions + 0x2648...0x2653, // 12 Emotions + 0x2660...0x2660, // 1 Emotions + 0x2663...0x2663, // 1 Emotions + 0x2665...0x2666, // 2 Emotions + 0x2668...0x2668, // 1 Emotions + 0x2670...0x267B, // 12 Emotions + 0x267E...0x2693, // 22 Emotions + 0x2699...0x2699, // 1 Emotions + 0x269B...0x26AB, // 17 Emotions + 0x26B0...0x26C8, // 25 Emotions + 0x26CE...0x26D1, // 4 Emotions + 0x26D3...0x26D4, // 2 Emotions + 0x26E2...0x26EA, // 9 Emotions + 0x26F0...0x26F3, // 4 Emotions + 0x26F5...0x26F5, // 1 Emotions + 0x26F7...0x26FA, // 4 Emotions + 0x26FD...0x26FD, // 1 Emotions + 0x2700...0x2702, // 3 Emotions + 0x2705...0x2705, // 1 Emotions + 0x2708...0x270F, // 8 Emotions + 0x2712...0x2712, // 1 Emotions + 0x2714...0x2714, // 1 Emotions + 0x2716...0x2716, // 1 Emotions + 0x271D...0x271D, // 1 Emotions + 0x2721...0x2721, // 1 Emotions + 0x2728...0x2728, // 1 Emotions + 0x2733...0x2734, // 2 Emotions + 0x2744...0x2744, // 1 Emotions + 0x2747...0x2747, // 1 Emotions + 0x274C...0x274C, // 1 Emotions + 0x274E...0x274E, // 1 Emotions + 0x2753...0x2755, // 3 Emotions + 0x2757...0x2757, // 1 Emotions + 0x2763...0x2767, // 5 Emotions + 0x2795...0x2797, // 3 Emotions + 0x27A1...0x27A1, // 1 Emotions + 0x27B0...0x27B0, // 1 Emotions + 0x27BF...0x27BF, // 1 Emotions + 0x2934...0x2935, // 2 Emotions + 0x2B05...0x2B07, // 3 Emotions + 0x2B1B...0x2B1C, // 2 Emotions + 0x2B50...0x2B50, // 1 Emotions + 0x2B55...0x2B55, // 1 Emotions + 0x3030...0x3030, // 1 Emotions + 0x303D...0x303D, // 1 Emotions + 0x3297...0x3297, // 1 Emotions + 0x3299...0x3299, // 1 Emotions + 0xFE00...0xFE0F, // 16 Emotions + 0x1F000...0x1F004, // 5 Emotions + 0x1F02C...0x1F0FF, // 212 Emotions + 0x1F10D...0x1F10F, // 3 Emotions + 0x1F12F...0x1F12F, // 1 Emotions + 0x1F16C...0x1F171, // 6 Emotions + 0x1F17E...0x1F17F, // 2 Emotions + 0x1F18E...0x1F18E, // 1 Emotions + 0x1F191...0x1F19A, // 10 Emotions + 0x1F1AD...0x1F1FF, // 83 Emotions + 0x1F201...0x1F20F, // 15 Emotions + 0x1F21A...0x1F21A, // 1 Emotions + 0x1F22F...0x1F22F, // 1 Emotions + 0x1F232...0x1F23A, // 9 Emotions + 0x1F23C...0x1F23F, // 4 Emotions + 0x1F249...0x1F385, // 317 Emotions + 0x1F394...0x1F397, // 4 Emotions + 0x1F399...0x1F39B, // 3 Emotions + 0x1F39E...0x1F3C7, // 42 Emotions + 0x1F3CA...0x1F3F4, // 43 Emotions + 0x1F3F7...0x1F450, // 90 Emotions + 0x1F466...0x1F469, // 4 Emotions + 0x1F46E...0x1F46E, // 1 Emotions + 0x1F470...0x1F478, // 9 Emotions + 0x1F47C...0x1F47C, // 1 Emotions + 0x1F481...0x1F483, // 3 Emotions + 0x1F485...0x1F487, // 3 Emotions + 0x1F4AA...0x1F4AA, // 1 Emotions + 0x1F4F8...0x1F570, // 121 Emotions + 0x1F573...0x1F575, // 3 Emotions + 0x1F57A...0x1F587, // 14 Emotions + 0x1F58A...0x1F58D, // 4 Emotions + 0x1F590...0x1F590, // 1 Emotions + 0x1F595...0x1F596, // 2 Emotions + 0x1F5A4...0x1F5A8, // 5 Emotions + 0x1F5B1...0x1F5B2, // 2 Emotions + 0x1F5BC...0x1F5BC, // 1 Emotions + 0x1F5C2...0x1F5C4, // 3 Emotions + 0x1F5D1...0x1F5D3, // 3 Emotions + 0x1F5DC...0x1F5DE, // 3 Emotions + 0x1F5E1...0x1F5E1, // 1 Emotions + 0x1F5E3...0x1F5E3, // 1 Emotions + 0x1F5E8...0x1F5E8, // 1 Emotions + 0x1F5EF...0x1F5EF, // 1 Emotions + 0x1F5F3...0x1F5F3, // 1 Emotions + 0x1F5FA...0x1F64F, // 86 Emotions + 0x1F680...0x1F6A3, // 36 Emotions + 0x1F6B4...0x1F6B6, // 3 Emotions + 0x1F6C0...0x1F6C0, // 1 Emotions + 0x1F6C6...0x1F6CC, // 7 Emotions + 0x1F6D0...0x1F6E9, // 26 Emotions + 0x1F6EB...0x1F6FF, // 21 Emotions + 0x1F774...0x1F77F, // 12 Emotions + 0x1F7D5...0x1F7FF, // 43 Emotions + 0x1F80C...0x1F80F, // 4 Emotions + 0x1F848...0x1F84F, // 8 Emotions + 0x1F85A...0x1F85F, // 6 Emotions + 0x1F888...0x1F88F, // 8 Emotions + 0x1F8AE...0x1F926, // 121 Emotions + 0x1F928...0x1F93A, // 19 Emotions + 0x1F93C...0x1F945, // 10 Emotions + 0x1F947...0x1F9DD, // 151 Emotions + 0x1F9E7...0x1FFFD, // 1559 Emotions + 0xE0020...0xE007F: // 96 Emotions + return true + + default: return false + } + } + + var isZeroWidthJoiner: Bool { + + return value == 8205 + } +} + +extension String { + + var glyphCount: Int { + let richText = NSAttributedString(string: self) + let line = CTLineCreateWithAttributedString(richText) + return CTLineGetGlyphCount(line) + } + + var isSingleEmoji: Bool { + + return glyphCount == 1 && containsEmoji + } + + var containsEmoji: Bool { + + return unicodeScalars.contains { $0.isEmoji } + } + + var containsOnlyEmoji: Bool { + + return !isEmpty + && !unicodeScalars.contains(where: { + !$0.isEmoji + && !$0.isZeroWidthJoiner + }) + } + + // The next tricks are mostly to demonstrate how tricky it can be to determine emoji's + // If anyone has suggestions how to improve this, please let me know + var emojiString: String { + + return emojiScalars.map { String($0) }.reduce("", +) + } + + var emojis: [String] { + + var scalars: [[UnicodeScalar]] = [] + var currentScalarSet: [UnicodeScalar] = [] + var previousScalar: UnicodeScalar? + + for scalar in emojiScalars { + + if let prev = previousScalar, !prev.isZeroWidthJoiner && !scalar.isZeroWidthJoiner { + + scalars.append(currentScalarSet) + currentScalarSet = [] + } + currentScalarSet.append(scalar) + + previousScalar = scalar + } + + scalars.append(currentScalarSet) + + return scalars.map { $0.map { String($0) } .reduce("", +) } + } + + fileprivate var emojiScalars: [UnicodeScalar] { + + var chars: [UnicodeScalar] = [] + var previous: UnicodeScalar? + for cur in unicodeScalars { + + if let previous = previous, previous.isZeroWidthJoiner && cur.isEmoji { + chars.append(previous) + chars.append(cur) + + } else if cur.isEmoji { + chars.append(cur) + } + + previous = cur + } + + return chars + } +} + @objc class DisplayableText: NSObject { static let TAG = "[DisplayableText]" @@ -11,83 +258,47 @@ import Foundation let fullText: String let displayText: String let isTextTruncated: Bool - let jumbomojiCount: NSNumber? + let jumbomojiCount: UInt static let kMaxJumbomojiCount: UInt = 5 + // This value is a bit arbitrary since we don't need to be 100% correct about + // rendering "Jumbomoji". It allows us to place an upper bound on worst-case + // performacne. + static let kMaxCharactersPerEmojiCount: UInt = 10 // MARK: Initializers - init(fullText: String, displayText: String, isTextTruncated: Bool, jumbomojiCount: NSNumber?) { + init(fullText: String, displayText: String, isTextTruncated: Bool) { self.fullText = fullText self.displayText = displayText self.isTextTruncated = isTextTruncated - self.jumbomojiCount = jumbomojiCount + self.jumbomojiCount = DisplayableText.jumbomojiCount(in:fullText) } // MARK: Emoji - private class func canDetectEmoji() -> Bool { - if #available(iOS 10.0, *) { - return true - } else { - return false - } - } - // If the string is... // // * Non-empty // * Only contains emoji - // * Contains <= maxEmojiCount emoji + // * Contains <= kMaxJumbomojiCount emoji // - // ...return the number of emoji in the string. Otherwise return nil. - // - // On iOS 9 and earler, always returns nil. - @objc public class func jumbomojiCount(to string: String) -> NSNumber? { + // ...return the number of emoji (to be treated as "Jumbomoji") in the string. + private class func jumbomojiCount(in string: String) -> UInt { if string == "" { - return nil + return 0 } - if string.characters.count > Int(kMaxJumbomojiCount) { - return nil + if string.characters.count > Int(kMaxJumbomojiCount * kMaxCharactersPerEmojiCount) { + return 0 } - if !canDetectEmoji() { - return nil + guard string.containsOnlyEmoji else { + return 0 } - var didFail = false - var emojiCount: UInt = 0 - let attributes = [NSFontAttributeName: UIFont.systemFont(ofSize:12)] - let attributedString = NSMutableAttributedString(string: string, attributes: attributes) - let range = NSRange(location: 0, length: string.characters.count) - attributedString.fixAttributes(in: range) - attributedString.enumerateAttribute(NSFontAttributeName, - in: range, - options: [], - using: {(_ value: Any?, range: NSRange, stop: UnsafeMutablePointer) -> Void in - guard emojiCount < kMaxJumbomojiCount else { - didFail = true - stop.pointee = true - return - } - guard let rangeFont = value as? UIFont else { - didFail = true - stop.pointee = true - return - } - guard rangeFont.fontName == ".AppleColorEmojiUI" else { - didFail = true - stop.pointee = true - return - } - if rangeFont.fontName == ".AppleColorEmojiUI" { - Logger.verbose("Detected Emoji at location: \(range.location), for length: \(range.length)") - emojiCount += UInt(range.length) - } - }) - - guard !didFail else { - return nil + let emojiCount = string.glyphCount + if UInt(emojiCount) > kMaxJumbomojiCount { + return 0 } - return NSNumber(value: emojiCount) + return UInt(emojiCount) } // MARK: Filter Methods From 530b70d7060ec4850355acc53860a899c5bbf027 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Mon, 30 Oct 2017 12:23:13 -0400 Subject: [PATCH 3/7] Respond to CR. // FREEBIE --- Scripts/emoji_ranges.py | 3 +- Signal/src/util/DisplayableText.swift | 333 ++++++++++++++------------ 2 files changed, 183 insertions(+), 153 deletions(-) diff --git a/Scripts/emoji_ranges.py b/Scripts/emoji_ranges.py index be8adceb1d..9a4263b676 100755 --- a/Scripts/emoji_ranges.py +++ b/Scripts/emoji_ranges.py @@ -77,7 +77,8 @@ if __name__ == '__main__': print for range_start, range_end in new_ranges: - print '0x%X...0x%X, // %d Emotions' % (range_start, range_end, (1 + range_end - range_start), ) + # print '0x%X...0x%X, // %d Emotions' % (range_start, range_end, (1 + range_end - range_start), ) + print 'EmojiRange(rangeStart:0x%X, rangeEnd:0x%X),' % (range_start, range_end, ) print 'new_ranges:', len(new_ranges) diff --git a/Signal/src/util/DisplayableText.swift b/Signal/src/util/DisplayableText.swift index fe7843ce1c..ac330c1bca 100644 --- a/Signal/src/util/DisplayableText.swift +++ b/Signal/src/util/DisplayableText.swift @@ -5,6 +5,168 @@ import Foundation extension UnicodeScalar { + class EmojiRange { + // rangeStart and rangeEnd are inclusive. + let rangeStart: UInt32 + let rangeEnd: UInt32 + + // MARK: Initializers + + init(rangeStart: UInt32, rangeEnd: UInt32) { + self.rangeStart = rangeStart + self.rangeEnd = rangeEnd + } + } + + static let kEmojiRanges = [ + EmojiRange(rangeStart:0x23, rangeEnd:0x23), + EmojiRange(rangeStart:0x2A, rangeEnd:0x2A), + EmojiRange(rangeStart:0x30, rangeEnd:0x39), + EmojiRange(rangeStart:0xA9, rangeEnd:0xA9), + EmojiRange(rangeStart:0xAE, rangeEnd:0xAE), + EmojiRange(rangeStart:0x200D, rangeEnd:0x200D), + EmojiRange(rangeStart:0x203C, rangeEnd:0x203C), + EmojiRange(rangeStart:0x2049, rangeEnd:0x2049), + EmojiRange(rangeStart:0x20D0, rangeEnd:0x20E3), + EmojiRange(rangeStart:0x2122, rangeEnd:0x2122), + EmojiRange(rangeStart:0x2139, rangeEnd:0x2139), + EmojiRange(rangeStart:0x2194, rangeEnd:0x2199), + EmojiRange(rangeStart:0x21A9, rangeEnd:0x21AA), + EmojiRange(rangeStart:0x231A, rangeEnd:0x231B), + EmojiRange(rangeStart:0x2328, rangeEnd:0x2328), + EmojiRange(rangeStart:0x2388, rangeEnd:0x2388), + EmojiRange(rangeStart:0x23CF, rangeEnd:0x23CF), + EmojiRange(rangeStart:0x23E9, rangeEnd:0x23F0), + EmojiRange(rangeStart:0x23F3, rangeEnd:0x23F3), + EmojiRange(rangeStart:0x23F8, rangeEnd:0x23FA), + EmojiRange(rangeStart:0x24C2, rangeEnd:0x24C2), + EmojiRange(rangeStart:0x25AA, rangeEnd:0x25AB), + EmojiRange(rangeStart:0x25B6, rangeEnd:0x25B6), + EmojiRange(rangeStart:0x25C0, rangeEnd:0x25C0), + EmojiRange(rangeStart:0x25FB, rangeEnd:0x25FE), + EmojiRange(rangeStart:0x2600, rangeEnd:0x260E), + EmojiRange(rangeStart:0x2611, rangeEnd:0x2611), + EmojiRange(rangeStart:0x2614, rangeEnd:0x261D), + EmojiRange(rangeStart:0x2620, rangeEnd:0x2620), + EmojiRange(rangeStart:0x2622, rangeEnd:0x2623), + EmojiRange(rangeStart:0x2626, rangeEnd:0x2626), + EmojiRange(rangeStart:0x262A, rangeEnd:0x262A), + EmojiRange(rangeStart:0x262E, rangeEnd:0x262F), + EmojiRange(rangeStart:0x2638, rangeEnd:0x263A), + EmojiRange(rangeStart:0x2640, rangeEnd:0x2640), + EmojiRange(rangeStart:0x2642, rangeEnd:0x2642), + EmojiRange(rangeStart:0x2648, rangeEnd:0x2653), + EmojiRange(rangeStart:0x2660, rangeEnd:0x2660), + EmojiRange(rangeStart:0x2663, rangeEnd:0x2663), + EmojiRange(rangeStart:0x2665, rangeEnd:0x2666), + EmojiRange(rangeStart:0x2668, rangeEnd:0x2668), + EmojiRange(rangeStart:0x2670, rangeEnd:0x267B), + EmojiRange(rangeStart:0x267E, rangeEnd:0x2693), + EmojiRange(rangeStart:0x2699, rangeEnd:0x2699), + EmojiRange(rangeStart:0x269B, rangeEnd:0x26AB), + EmojiRange(rangeStart:0x26B0, rangeEnd:0x26C8), + EmojiRange(rangeStart:0x26CE, rangeEnd:0x26D1), + EmojiRange(rangeStart:0x26D3, rangeEnd:0x26D4), + EmojiRange(rangeStart:0x26E2, rangeEnd:0x26EA), + EmojiRange(rangeStart:0x26F0, rangeEnd:0x26F3), + EmojiRange(rangeStart:0x26F5, rangeEnd:0x26F5), + EmojiRange(rangeStart:0x26F7, rangeEnd:0x26FA), + EmojiRange(rangeStart:0x26FD, rangeEnd:0x26FD), + EmojiRange(rangeStart:0x2700, rangeEnd:0x2702), + EmojiRange(rangeStart:0x2705, rangeEnd:0x2705), + EmojiRange(rangeStart:0x2708, rangeEnd:0x270F), + EmojiRange(rangeStart:0x2712, rangeEnd:0x2712), + EmojiRange(rangeStart:0x2714, rangeEnd:0x2714), + EmojiRange(rangeStart:0x2716, rangeEnd:0x2716), + EmojiRange(rangeStart:0x271D, rangeEnd:0x271D), + EmojiRange(rangeStart:0x2721, rangeEnd:0x2721), + EmojiRange(rangeStart:0x2728, rangeEnd:0x2728), + EmojiRange(rangeStart:0x2733, rangeEnd:0x2734), + EmojiRange(rangeStart:0x2744, rangeEnd:0x2744), + EmojiRange(rangeStart:0x2747, rangeEnd:0x2747), + EmojiRange(rangeStart:0x274C, rangeEnd:0x274C), + EmojiRange(rangeStart:0x274E, rangeEnd:0x274E), + EmojiRange(rangeStart:0x2753, rangeEnd:0x2755), + EmojiRange(rangeStart:0x2757, rangeEnd:0x2757), + EmojiRange(rangeStart:0x2763, rangeEnd:0x2767), + EmojiRange(rangeStart:0x2795, rangeEnd:0x2797), + EmojiRange(rangeStart:0x27A1, rangeEnd:0x27A1), + EmojiRange(rangeStart:0x27B0, rangeEnd:0x27B0), + EmojiRange(rangeStart:0x27BF, rangeEnd:0x27BF), + EmojiRange(rangeStart:0x2934, rangeEnd:0x2935), + EmojiRange(rangeStart:0x2B05, rangeEnd:0x2B07), + EmojiRange(rangeStart:0x2B1B, rangeEnd:0x2B1C), + EmojiRange(rangeStart:0x2B50, rangeEnd:0x2B50), + EmojiRange(rangeStart:0x2B55, rangeEnd:0x2B55), + EmojiRange(rangeStart:0x3030, rangeEnd:0x3030), + EmojiRange(rangeStart:0x303D, rangeEnd:0x303D), + EmojiRange(rangeStart:0x3297, rangeEnd:0x3297), + EmojiRange(rangeStart:0x3299, rangeEnd:0x3299), + EmojiRange(rangeStart:0xFE00, rangeEnd:0xFE0F), + EmojiRange(rangeStart:0x1F000, rangeEnd:0x1F004), + EmojiRange(rangeStart:0x1F02C, rangeEnd:0x1F0FF), + EmojiRange(rangeStart:0x1F10D, rangeEnd:0x1F10F), + EmojiRange(rangeStart:0x1F12F, rangeEnd:0x1F12F), + EmojiRange(rangeStart:0x1F16C, rangeEnd:0x1F171), + EmojiRange(rangeStart:0x1F17E, rangeEnd:0x1F17F), + EmojiRange(rangeStart:0x1F18E, rangeEnd:0x1F18E), + EmojiRange(rangeStart:0x1F191, rangeEnd:0x1F19A), + EmojiRange(rangeStart:0x1F1AD, rangeEnd:0x1F1FF), + EmojiRange(rangeStart:0x1F201, rangeEnd:0x1F20F), + EmojiRange(rangeStart:0x1F21A, rangeEnd:0x1F21A), + EmojiRange(rangeStart:0x1F22F, rangeEnd:0x1F22F), + EmojiRange(rangeStart:0x1F232, rangeEnd:0x1F23A), + EmojiRange(rangeStart:0x1F23C, rangeEnd:0x1F23F), + EmojiRange(rangeStart:0x1F249, rangeEnd:0x1F385), + EmojiRange(rangeStart:0x1F394, rangeEnd:0x1F397), + EmojiRange(rangeStart:0x1F399, rangeEnd:0x1F39B), + EmojiRange(rangeStart:0x1F39E, rangeEnd:0x1F3C7), + EmojiRange(rangeStart:0x1F3CA, rangeEnd:0x1F3F4), + EmojiRange(rangeStart:0x1F3F7, rangeEnd:0x1F450), + EmojiRange(rangeStart:0x1F466, rangeEnd:0x1F469), + EmojiRange(rangeStart:0x1F46E, rangeEnd:0x1F46E), + EmojiRange(rangeStart:0x1F470, rangeEnd:0x1F478), + EmojiRange(rangeStart:0x1F47C, rangeEnd:0x1F47C), + EmojiRange(rangeStart:0x1F481, rangeEnd:0x1F483), + EmojiRange(rangeStart:0x1F485, rangeEnd:0x1F487), + EmojiRange(rangeStart:0x1F4AA, rangeEnd:0x1F4AA), + EmojiRange(rangeStart:0x1F4F8, rangeEnd:0x1F570), + EmojiRange(rangeStart:0x1F573, rangeEnd:0x1F575), + EmojiRange(rangeStart:0x1F57A, rangeEnd:0x1F587), + EmojiRange(rangeStart:0x1F58A, rangeEnd:0x1F58D), + EmojiRange(rangeStart:0x1F590, rangeEnd:0x1F590), + EmojiRange(rangeStart:0x1F595, rangeEnd:0x1F596), + EmojiRange(rangeStart:0x1F5A4, rangeEnd:0x1F5A8), + EmojiRange(rangeStart:0x1F5B1, rangeEnd:0x1F5B2), + EmojiRange(rangeStart:0x1F5BC, rangeEnd:0x1F5BC), + EmojiRange(rangeStart:0x1F5C2, rangeEnd:0x1F5C4), + EmojiRange(rangeStart:0x1F5D1, rangeEnd:0x1F5D3), + EmojiRange(rangeStart:0x1F5DC, rangeEnd:0x1F5DE), + EmojiRange(rangeStart:0x1F5E1, rangeEnd:0x1F5E1), + EmojiRange(rangeStart:0x1F5E3, rangeEnd:0x1F5E3), + EmojiRange(rangeStart:0x1F5E8, rangeEnd:0x1F5E8), + EmojiRange(rangeStart:0x1F5EF, rangeEnd:0x1F5EF), + EmojiRange(rangeStart:0x1F5F3, rangeEnd:0x1F5F3), + EmojiRange(rangeStart:0x1F5FA, rangeEnd:0x1F64F), + EmojiRange(rangeStart:0x1F680, rangeEnd:0x1F6A3), + EmojiRange(rangeStart:0x1F6B4, rangeEnd:0x1F6B6), + EmojiRange(rangeStart:0x1F6C0, rangeEnd:0x1F6C0), + EmojiRange(rangeStart:0x1F6C6, rangeEnd:0x1F6CC), + EmojiRange(rangeStart:0x1F6D0, rangeEnd:0x1F6E9), + EmojiRange(rangeStart:0x1F6EB, rangeEnd:0x1F6FF), + EmojiRange(rangeStart:0x1F774, rangeEnd:0x1F77F), + EmojiRange(rangeStart:0x1F7D5, rangeEnd:0x1F7FF), + EmojiRange(rangeStart:0x1F80C, rangeEnd:0x1F80F), + EmojiRange(rangeStart:0x1F848, rangeEnd:0x1F84F), + EmojiRange(rangeStart:0x1F85A, rangeEnd:0x1F85F), + EmojiRange(rangeStart:0x1F888, rangeEnd:0x1F88F), + EmojiRange(rangeStart:0x1F8AE, rangeEnd:0x1F926), + EmojiRange(rangeStart:0x1F928, rangeEnd:0x1F93A), + EmojiRange(rangeStart:0x1F93C, rangeEnd:0x1F945), + EmojiRange(rangeStart:0x1F947, rangeEnd:0x1F9DD), + EmojiRange(rangeStart:0x1F9E7, rangeEnd:0x1FFFD), + EmojiRange(rangeStart:0xE0020, rangeEnd:0xE007F) + ] // From: // https://www.unicode.org/Public/emoji/ @@ -12,158 +174,25 @@ extension UnicodeScalar { // https://www.unicode.org/Public/emoji/6.0/emoji-data.txt var isEmoji: Bool { - switch value { - case - 0x23...0x23, // 1 Emotions - 0x2A...0x2A, // 1 Emotions - 0x30...0x39, // 10 Emotions - 0xA9...0xA9, // 1 Emotions - 0xAE...0xAE, // 1 Emotions - 0x200D...0x200D, // 1 Emotions - 0x203C...0x203C, // 1 Emotions - 0x2049...0x2049, // 1 Emotions - 0x20D0...0x20E3, // 20 Emotions - 0x2122...0x2122, // 1 Emotions - 0x2139...0x2139, // 1 Emotions - 0x2194...0x2199, // 6 Emotions - 0x21A9...0x21AA, // 2 Emotions - 0x231A...0x231B, // 2 Emotions - 0x2328...0x2328, // 1 Emotions - 0x2388...0x2388, // 1 Emotions - 0x23CF...0x23CF, // 1 Emotions - 0x23E9...0x23F0, // 8 Emotions - 0x23F3...0x23F3, // 1 Emotions - 0x23F8...0x23FA, // 3 Emotions - 0x24C2...0x24C2, // 1 Emotions - 0x25AA...0x25AB, // 2 Emotions - 0x25B6...0x25B6, // 1 Emotions - 0x25C0...0x25C0, // 1 Emotions - 0x25FB...0x25FE, // 4 Emotions - 0x2600...0x260E, // 15 Emotions - 0x2611...0x2611, // 1 Emotions - 0x2614...0x261D, // 10 Emotions - 0x2620...0x2620, // 1 Emotions - 0x2622...0x2623, // 2 Emotions - 0x2626...0x2626, // 1 Emotions - 0x262A...0x262A, // 1 Emotions - 0x262E...0x262F, // 2 Emotions - 0x2638...0x263A, // 3 Emotions - 0x2640...0x2640, // 1 Emotions - 0x2642...0x2642, // 1 Emotions - 0x2648...0x2653, // 12 Emotions - 0x2660...0x2660, // 1 Emotions - 0x2663...0x2663, // 1 Emotions - 0x2665...0x2666, // 2 Emotions - 0x2668...0x2668, // 1 Emotions - 0x2670...0x267B, // 12 Emotions - 0x267E...0x2693, // 22 Emotions - 0x2699...0x2699, // 1 Emotions - 0x269B...0x26AB, // 17 Emotions - 0x26B0...0x26C8, // 25 Emotions - 0x26CE...0x26D1, // 4 Emotions - 0x26D3...0x26D4, // 2 Emotions - 0x26E2...0x26EA, // 9 Emotions - 0x26F0...0x26F3, // 4 Emotions - 0x26F5...0x26F5, // 1 Emotions - 0x26F7...0x26FA, // 4 Emotions - 0x26FD...0x26FD, // 1 Emotions - 0x2700...0x2702, // 3 Emotions - 0x2705...0x2705, // 1 Emotions - 0x2708...0x270F, // 8 Emotions - 0x2712...0x2712, // 1 Emotions - 0x2714...0x2714, // 1 Emotions - 0x2716...0x2716, // 1 Emotions - 0x271D...0x271D, // 1 Emotions - 0x2721...0x2721, // 1 Emotions - 0x2728...0x2728, // 1 Emotions - 0x2733...0x2734, // 2 Emotions - 0x2744...0x2744, // 1 Emotions - 0x2747...0x2747, // 1 Emotions - 0x274C...0x274C, // 1 Emotions - 0x274E...0x274E, // 1 Emotions - 0x2753...0x2755, // 3 Emotions - 0x2757...0x2757, // 1 Emotions - 0x2763...0x2767, // 5 Emotions - 0x2795...0x2797, // 3 Emotions - 0x27A1...0x27A1, // 1 Emotions - 0x27B0...0x27B0, // 1 Emotions - 0x27BF...0x27BF, // 1 Emotions - 0x2934...0x2935, // 2 Emotions - 0x2B05...0x2B07, // 3 Emotions - 0x2B1B...0x2B1C, // 2 Emotions - 0x2B50...0x2B50, // 1 Emotions - 0x2B55...0x2B55, // 1 Emotions - 0x3030...0x3030, // 1 Emotions - 0x303D...0x303D, // 1 Emotions - 0x3297...0x3297, // 1 Emotions - 0x3299...0x3299, // 1 Emotions - 0xFE00...0xFE0F, // 16 Emotions - 0x1F000...0x1F004, // 5 Emotions - 0x1F02C...0x1F0FF, // 212 Emotions - 0x1F10D...0x1F10F, // 3 Emotions - 0x1F12F...0x1F12F, // 1 Emotions - 0x1F16C...0x1F171, // 6 Emotions - 0x1F17E...0x1F17F, // 2 Emotions - 0x1F18E...0x1F18E, // 1 Emotions - 0x1F191...0x1F19A, // 10 Emotions - 0x1F1AD...0x1F1FF, // 83 Emotions - 0x1F201...0x1F20F, // 15 Emotions - 0x1F21A...0x1F21A, // 1 Emotions - 0x1F22F...0x1F22F, // 1 Emotions - 0x1F232...0x1F23A, // 9 Emotions - 0x1F23C...0x1F23F, // 4 Emotions - 0x1F249...0x1F385, // 317 Emotions - 0x1F394...0x1F397, // 4 Emotions - 0x1F399...0x1F39B, // 3 Emotions - 0x1F39E...0x1F3C7, // 42 Emotions - 0x1F3CA...0x1F3F4, // 43 Emotions - 0x1F3F7...0x1F450, // 90 Emotions - 0x1F466...0x1F469, // 4 Emotions - 0x1F46E...0x1F46E, // 1 Emotions - 0x1F470...0x1F478, // 9 Emotions - 0x1F47C...0x1F47C, // 1 Emotions - 0x1F481...0x1F483, // 3 Emotions - 0x1F485...0x1F487, // 3 Emotions - 0x1F4AA...0x1F4AA, // 1 Emotions - 0x1F4F8...0x1F570, // 121 Emotions - 0x1F573...0x1F575, // 3 Emotions - 0x1F57A...0x1F587, // 14 Emotions - 0x1F58A...0x1F58D, // 4 Emotions - 0x1F590...0x1F590, // 1 Emotions - 0x1F595...0x1F596, // 2 Emotions - 0x1F5A4...0x1F5A8, // 5 Emotions - 0x1F5B1...0x1F5B2, // 2 Emotions - 0x1F5BC...0x1F5BC, // 1 Emotions - 0x1F5C2...0x1F5C4, // 3 Emotions - 0x1F5D1...0x1F5D3, // 3 Emotions - 0x1F5DC...0x1F5DE, // 3 Emotions - 0x1F5E1...0x1F5E1, // 1 Emotions - 0x1F5E3...0x1F5E3, // 1 Emotions - 0x1F5E8...0x1F5E8, // 1 Emotions - 0x1F5EF...0x1F5EF, // 1 Emotions - 0x1F5F3...0x1F5F3, // 1 Emotions - 0x1F5FA...0x1F64F, // 86 Emotions - 0x1F680...0x1F6A3, // 36 Emotions - 0x1F6B4...0x1F6B6, // 3 Emotions - 0x1F6C0...0x1F6C0, // 1 Emotions - 0x1F6C6...0x1F6CC, // 7 Emotions - 0x1F6D0...0x1F6E9, // 26 Emotions - 0x1F6EB...0x1F6FF, // 21 Emotions - 0x1F774...0x1F77F, // 12 Emotions - 0x1F7D5...0x1F7FF, // 43 Emotions - 0x1F80C...0x1F80F, // 4 Emotions - 0x1F848...0x1F84F, // 8 Emotions - 0x1F85A...0x1F85F, // 6 Emotions - 0x1F888...0x1F88F, // 8 Emotions - 0x1F8AE...0x1F926, // 121 Emotions - 0x1F928...0x1F93A, // 19 Emotions - 0x1F93C...0x1F945, // 10 Emotions - 0x1F947...0x1F9DD, // 151 Emotions - 0x1F9E7...0x1FFFD, // 1559 Emotions - 0xE0020...0xE007F: // 96 Emotions - return true - - default: return false + // Binary search. + var left: Int = 0 + var right = Int(UnicodeScalar.kEmojiRanges.count - 1) + while true { + let mid = (left + right) / 2 + let midRange = UnicodeScalar.kEmojiRanges[mid] + if value < midRange.rangeStart { + if mid == left { + return false + } + right = mid - 1 + } else if value > midRange.rangeEnd { + if mid == right { + return false + } + left = mid + 1 + } else { + return true + } } } From 563eed6c6eae865e7460bb30ed24bb5ee64eaf49 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Mon, 30 Oct 2017 12:25:38 -0400 Subject: [PATCH 4/7] Respond to CR. // FREEBIE --- Signal/src/util/DisplayableText.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Signal/src/util/DisplayableText.swift b/Signal/src/util/DisplayableText.swift index ac330c1bca..851a9c8159 100644 --- a/Signal/src/util/DisplayableText.swift +++ b/Signal/src/util/DisplayableText.swift @@ -18,6 +18,10 @@ extension UnicodeScalar { } } + // From: + // https://www.unicode.org/Public/emoji/ + // Current Version: + // https://www.unicode.org/Public/emoji/6.0/emoji-data.txt static let kEmojiRanges = [ EmojiRange(rangeStart:0x23, rangeEnd:0x23), EmojiRange(rangeStart:0x2A, rangeEnd:0x2A), @@ -168,10 +172,6 @@ extension UnicodeScalar { EmojiRange(rangeStart:0xE0020, rangeEnd:0xE007F) ] - // From: - // https://www.unicode.org/Public/emoji/ - // Current Version: - // https://www.unicode.org/Public/emoji/6.0/emoji-data.txt var isEmoji: Bool { // Binary search. From c6047b72b4ae65eeb9069e99eba8e7469db467ee Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Mon, 30 Oct 2017 12:43:39 -0400 Subject: [PATCH 5/7] Respond to CR. // FREEBIE --- Signal/src/util/DisplayableText.swift | 50 --------------------------- 1 file changed, 50 deletions(-) diff --git a/Signal/src/util/DisplayableText.swift b/Signal/src/util/DisplayableText.swift index 851a9c8159..9cfbd11c93 100644 --- a/Signal/src/util/DisplayableText.swift +++ b/Signal/src/util/DisplayableText.swift @@ -228,56 +228,6 @@ extension String { && !$0.isZeroWidthJoiner }) } - - // The next tricks are mostly to demonstrate how tricky it can be to determine emoji's - // If anyone has suggestions how to improve this, please let me know - var emojiString: String { - - return emojiScalars.map { String($0) }.reduce("", +) - } - - var emojis: [String] { - - var scalars: [[UnicodeScalar]] = [] - var currentScalarSet: [UnicodeScalar] = [] - var previousScalar: UnicodeScalar? - - for scalar in emojiScalars { - - if let prev = previousScalar, !prev.isZeroWidthJoiner && !scalar.isZeroWidthJoiner { - - scalars.append(currentScalarSet) - currentScalarSet = [] - } - currentScalarSet.append(scalar) - - previousScalar = scalar - } - - scalars.append(currentScalarSet) - - return scalars.map { $0.map { String($0) } .reduce("", +) } - } - - fileprivate var emojiScalars: [UnicodeScalar] { - - var chars: [UnicodeScalar] = [] - var previous: UnicodeScalar? - for cur in unicodeScalars { - - if let previous = previous, previous.isZeroWidthJoiner && cur.isEmoji { - chars.append(previous) - chars.append(cur) - - } else if cur.isEmoji { - chars.append(cur) - } - - previous = cur - } - - return chars - } } @objc class DisplayableText: NSObject { From 0a926567ed5c77326ed925b2762324816760c181 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Mon, 30 Oct 2017 13:05:11 -0400 Subject: [PATCH 6/7] Respond to CR. // FREEBIE --- Scripts/emoji_ranges.py | 2 +- Signal/src/util/DisplayableText.swift | 208 +++++------------- .../test/util/DisplayableTextFilterTest.swift | 76 ++++++- 3 files changed, 127 insertions(+), 159 deletions(-) diff --git a/Scripts/emoji_ranges.py b/Scripts/emoji_ranges.py index 9a4263b676..d25e0e84c7 100755 --- a/Scripts/emoji_ranges.py +++ b/Scripts/emoji_ranges.py @@ -70,7 +70,7 @@ if __name__ == '__main__': # if last_range_end + 1 == range_start: new_ranges = new_ranges[:-1] print 'merging', last_range_start, last_range_end, 'and', range_start, range_end - new_ranges.append((last_range_start, range_end,)) + new_ranges.append((last_range_start, max(range_end, last_range_end),)) continue new_ranges.append((range_start, range_end,)) diff --git a/Signal/src/util/DisplayableText.swift b/Signal/src/util/DisplayableText.swift index 9cfbd11c93..dd56ec71b2 100644 --- a/Signal/src/util/DisplayableText.swift +++ b/Signal/src/util/DisplayableText.swift @@ -23,153 +23,64 @@ extension UnicodeScalar { // Current Version: // https://www.unicode.org/Public/emoji/6.0/emoji-data.txt static let kEmojiRanges = [ - EmojiRange(rangeStart:0x23, rangeEnd:0x23), - EmojiRange(rangeStart:0x2A, rangeEnd:0x2A), - EmojiRange(rangeStart:0x30, rangeEnd:0x39), - EmojiRange(rangeStart:0xA9, rangeEnd:0xA9), - EmojiRange(rangeStart:0xAE, rangeEnd:0xAE), - EmojiRange(rangeStart:0x200D, rangeEnd:0x200D), - EmojiRange(rangeStart:0x203C, rangeEnd:0x203C), - EmojiRange(rangeStart:0x2049, rangeEnd:0x2049), - EmojiRange(rangeStart:0x20D0, rangeEnd:0x20E3), - EmojiRange(rangeStart:0x2122, rangeEnd:0x2122), - EmojiRange(rangeStart:0x2139, rangeEnd:0x2139), - EmojiRange(rangeStart:0x2194, rangeEnd:0x2199), - EmojiRange(rangeStart:0x21A9, rangeEnd:0x21AA), - EmojiRange(rangeStart:0x231A, rangeEnd:0x231B), - EmojiRange(rangeStart:0x2328, rangeEnd:0x2328), - EmojiRange(rangeStart:0x2388, rangeEnd:0x2388), - EmojiRange(rangeStart:0x23CF, rangeEnd:0x23CF), - EmojiRange(rangeStart:0x23E9, rangeEnd:0x23F0), - EmojiRange(rangeStart:0x23F3, rangeEnd:0x23F3), - EmojiRange(rangeStart:0x23F8, rangeEnd:0x23FA), - EmojiRange(rangeStart:0x24C2, rangeEnd:0x24C2), - EmojiRange(rangeStart:0x25AA, rangeEnd:0x25AB), - EmojiRange(rangeStart:0x25B6, rangeEnd:0x25B6), - EmojiRange(rangeStart:0x25C0, rangeEnd:0x25C0), - EmojiRange(rangeStart:0x25FB, rangeEnd:0x25FE), - EmojiRange(rangeStart:0x2600, rangeEnd:0x260E), - EmojiRange(rangeStart:0x2611, rangeEnd:0x2611), - EmojiRange(rangeStart:0x2614, rangeEnd:0x261D), - EmojiRange(rangeStart:0x2620, rangeEnd:0x2620), - EmojiRange(rangeStart:0x2622, rangeEnd:0x2623), - EmojiRange(rangeStart:0x2626, rangeEnd:0x2626), - EmojiRange(rangeStart:0x262A, rangeEnd:0x262A), - EmojiRange(rangeStart:0x262E, rangeEnd:0x262F), - EmojiRange(rangeStart:0x2638, rangeEnd:0x263A), - EmojiRange(rangeStart:0x2640, rangeEnd:0x2640), - EmojiRange(rangeStart:0x2642, rangeEnd:0x2642), - EmojiRange(rangeStart:0x2648, rangeEnd:0x2653), - EmojiRange(rangeStart:0x2660, rangeEnd:0x2660), - EmojiRange(rangeStart:0x2663, rangeEnd:0x2663), - EmojiRange(rangeStart:0x2665, rangeEnd:0x2666), - EmojiRange(rangeStart:0x2668, rangeEnd:0x2668), - EmojiRange(rangeStart:0x2670, rangeEnd:0x267B), - EmojiRange(rangeStart:0x267E, rangeEnd:0x2693), - EmojiRange(rangeStart:0x2699, rangeEnd:0x2699), - EmojiRange(rangeStart:0x269B, rangeEnd:0x26AB), - EmojiRange(rangeStart:0x26B0, rangeEnd:0x26C8), - EmojiRange(rangeStart:0x26CE, rangeEnd:0x26D1), - EmojiRange(rangeStart:0x26D3, rangeEnd:0x26D4), - EmojiRange(rangeStart:0x26E2, rangeEnd:0x26EA), - EmojiRange(rangeStart:0x26F0, rangeEnd:0x26F3), - EmojiRange(rangeStart:0x26F5, rangeEnd:0x26F5), - EmojiRange(rangeStart:0x26F7, rangeEnd:0x26FA), - EmojiRange(rangeStart:0x26FD, rangeEnd:0x26FD), - EmojiRange(rangeStart:0x2700, rangeEnd:0x2702), - EmojiRange(rangeStart:0x2705, rangeEnd:0x2705), - EmojiRange(rangeStart:0x2708, rangeEnd:0x270F), - EmojiRange(rangeStart:0x2712, rangeEnd:0x2712), - EmojiRange(rangeStart:0x2714, rangeEnd:0x2714), - EmojiRange(rangeStart:0x2716, rangeEnd:0x2716), - EmojiRange(rangeStart:0x271D, rangeEnd:0x271D), - EmojiRange(rangeStart:0x2721, rangeEnd:0x2721), - EmojiRange(rangeStart:0x2728, rangeEnd:0x2728), - EmojiRange(rangeStart:0x2733, rangeEnd:0x2734), - EmojiRange(rangeStart:0x2744, rangeEnd:0x2744), - EmojiRange(rangeStart:0x2747, rangeEnd:0x2747), - EmojiRange(rangeStart:0x274C, rangeEnd:0x274C), - EmojiRange(rangeStart:0x274E, rangeEnd:0x274E), - EmojiRange(rangeStart:0x2753, rangeEnd:0x2755), - EmojiRange(rangeStart:0x2757, rangeEnd:0x2757), - EmojiRange(rangeStart:0x2763, rangeEnd:0x2767), - EmojiRange(rangeStart:0x2795, rangeEnd:0x2797), - EmojiRange(rangeStart:0x27A1, rangeEnd:0x27A1), - EmojiRange(rangeStart:0x27B0, rangeEnd:0x27B0), - EmojiRange(rangeStart:0x27BF, rangeEnd:0x27BF), - EmojiRange(rangeStart:0x2934, rangeEnd:0x2935), - EmojiRange(rangeStart:0x2B05, rangeEnd:0x2B07), - EmojiRange(rangeStart:0x2B1B, rangeEnd:0x2B1C), - EmojiRange(rangeStart:0x2B50, rangeEnd:0x2B50), - EmojiRange(rangeStart:0x2B55, rangeEnd:0x2B55), - EmojiRange(rangeStart:0x3030, rangeEnd:0x3030), - EmojiRange(rangeStart:0x303D, rangeEnd:0x303D), - EmojiRange(rangeStart:0x3297, rangeEnd:0x3297), - EmojiRange(rangeStart:0x3299, rangeEnd:0x3299), - EmojiRange(rangeStart:0xFE00, rangeEnd:0xFE0F), - EmojiRange(rangeStart:0x1F000, rangeEnd:0x1F004), - EmojiRange(rangeStart:0x1F02C, rangeEnd:0x1F0FF), - EmojiRange(rangeStart:0x1F10D, rangeEnd:0x1F10F), - EmojiRange(rangeStart:0x1F12F, rangeEnd:0x1F12F), - EmojiRange(rangeStart:0x1F16C, rangeEnd:0x1F171), - EmojiRange(rangeStart:0x1F17E, rangeEnd:0x1F17F), - EmojiRange(rangeStart:0x1F18E, rangeEnd:0x1F18E), - EmojiRange(rangeStart:0x1F191, rangeEnd:0x1F19A), - EmojiRange(rangeStart:0x1F1AD, rangeEnd:0x1F1FF), - EmojiRange(rangeStart:0x1F201, rangeEnd:0x1F20F), - EmojiRange(rangeStart:0x1F21A, rangeEnd:0x1F21A), - EmojiRange(rangeStart:0x1F22F, rangeEnd:0x1F22F), - EmojiRange(rangeStart:0x1F232, rangeEnd:0x1F23A), - EmojiRange(rangeStart:0x1F23C, rangeEnd:0x1F23F), - EmojiRange(rangeStart:0x1F249, rangeEnd:0x1F385), - EmojiRange(rangeStart:0x1F394, rangeEnd:0x1F397), - EmojiRange(rangeStart:0x1F399, rangeEnd:0x1F39B), - EmojiRange(rangeStart:0x1F39E, rangeEnd:0x1F3C7), - EmojiRange(rangeStart:0x1F3CA, rangeEnd:0x1F3F4), - EmojiRange(rangeStart:0x1F3F7, rangeEnd:0x1F450), - EmojiRange(rangeStart:0x1F466, rangeEnd:0x1F469), - EmojiRange(rangeStart:0x1F46E, rangeEnd:0x1F46E), - EmojiRange(rangeStart:0x1F470, rangeEnd:0x1F478), - EmojiRange(rangeStart:0x1F47C, rangeEnd:0x1F47C), - EmojiRange(rangeStart:0x1F481, rangeEnd:0x1F483), - EmojiRange(rangeStart:0x1F485, rangeEnd:0x1F487), - EmojiRange(rangeStart:0x1F4AA, rangeEnd:0x1F4AA), - EmojiRange(rangeStart:0x1F4F8, rangeEnd:0x1F570), - EmojiRange(rangeStart:0x1F573, rangeEnd:0x1F575), - EmojiRange(rangeStart:0x1F57A, rangeEnd:0x1F587), - EmojiRange(rangeStart:0x1F58A, rangeEnd:0x1F58D), - EmojiRange(rangeStart:0x1F590, rangeEnd:0x1F590), - EmojiRange(rangeStart:0x1F595, rangeEnd:0x1F596), - EmojiRange(rangeStart:0x1F5A4, rangeEnd:0x1F5A8), - EmojiRange(rangeStart:0x1F5B1, rangeEnd:0x1F5B2), - EmojiRange(rangeStart:0x1F5BC, rangeEnd:0x1F5BC), - EmojiRange(rangeStart:0x1F5C2, rangeEnd:0x1F5C4), - EmojiRange(rangeStart:0x1F5D1, rangeEnd:0x1F5D3), - EmojiRange(rangeStart:0x1F5DC, rangeEnd:0x1F5DE), - EmojiRange(rangeStart:0x1F5E1, rangeEnd:0x1F5E1), - EmojiRange(rangeStart:0x1F5E3, rangeEnd:0x1F5E3), - EmojiRange(rangeStart:0x1F5E8, rangeEnd:0x1F5E8), - EmojiRange(rangeStart:0x1F5EF, rangeEnd:0x1F5EF), - EmojiRange(rangeStart:0x1F5F3, rangeEnd:0x1F5F3), - EmojiRange(rangeStart:0x1F5FA, rangeEnd:0x1F64F), - EmojiRange(rangeStart:0x1F680, rangeEnd:0x1F6A3), - EmojiRange(rangeStart:0x1F6B4, rangeEnd:0x1F6B6), - EmojiRange(rangeStart:0x1F6C0, rangeEnd:0x1F6C0), - EmojiRange(rangeStart:0x1F6C6, rangeEnd:0x1F6CC), - EmojiRange(rangeStart:0x1F6D0, rangeEnd:0x1F6E9), - EmojiRange(rangeStart:0x1F6EB, rangeEnd:0x1F6FF), - EmojiRange(rangeStart:0x1F774, rangeEnd:0x1F77F), - EmojiRange(rangeStart:0x1F7D5, rangeEnd:0x1F7FF), - EmojiRange(rangeStart:0x1F80C, rangeEnd:0x1F80F), - EmojiRange(rangeStart:0x1F848, rangeEnd:0x1F84F), - EmojiRange(rangeStart:0x1F85A, rangeEnd:0x1F85F), - EmojiRange(rangeStart:0x1F888, rangeEnd:0x1F88F), - EmojiRange(rangeStart:0x1F8AE, rangeEnd:0x1F926), - EmojiRange(rangeStart:0x1F928, rangeEnd:0x1F93A), - EmojiRange(rangeStart:0x1F93C, rangeEnd:0x1F945), - EmojiRange(rangeStart:0x1F947, rangeEnd:0x1F9DD), - EmojiRange(rangeStart:0x1F9E7, rangeEnd:0x1FFFD), - EmojiRange(rangeStart:0xE0020, rangeEnd:0xE007F) + EmojiRange(rangeStart:0x23, rangeEnd:0x23), + EmojiRange(rangeStart:0x2A, rangeEnd:0x2A), + EmojiRange(rangeStart:0x30, rangeEnd:0x39), + EmojiRange(rangeStart:0xA9, rangeEnd:0xA9), + EmojiRange(rangeStart:0xAE, rangeEnd:0xAE), + EmojiRange(rangeStart:0x200D, rangeEnd:0x200D), + EmojiRange(rangeStart:0x203C, rangeEnd:0x203C), + EmojiRange(rangeStart:0x2049, rangeEnd:0x2049), + EmojiRange(rangeStart:0x20D0, rangeEnd:0x20FF), + EmojiRange(rangeStart:0x2122, rangeEnd:0x2122), + EmojiRange(rangeStart:0x2139, rangeEnd:0x2139), + EmojiRange(rangeStart:0x2194, rangeEnd:0x2199), + EmojiRange(rangeStart:0x21A9, rangeEnd:0x21AA), + EmojiRange(rangeStart:0x231A, rangeEnd:0x231B), + EmojiRange(rangeStart:0x2328, rangeEnd:0x2328), + EmojiRange(rangeStart:0x2388, rangeEnd:0x2388), + EmojiRange(rangeStart:0x23CF, rangeEnd:0x23CF), + EmojiRange(rangeStart:0x23E9, rangeEnd:0x23F3), + EmojiRange(rangeStart:0x23F8, rangeEnd:0x23FA), + EmojiRange(rangeStart:0x24C2, rangeEnd:0x24C2), + EmojiRange(rangeStart:0x25AA, rangeEnd:0x25AB), + EmojiRange(rangeStart:0x25B6, rangeEnd:0x25B6), + EmojiRange(rangeStart:0x25C0, rangeEnd:0x25C0), + EmojiRange(rangeStart:0x25FB, rangeEnd:0x25FE), + EmojiRange(rangeStart:0x2600, rangeEnd:0x27BF), + EmojiRange(rangeStart:0x2934, rangeEnd:0x2935), + EmojiRange(rangeStart:0x2B05, rangeEnd:0x2B07), + EmojiRange(rangeStart:0x2B1B, rangeEnd:0x2B1C), + EmojiRange(rangeStart:0x2B50, rangeEnd:0x2B50), + EmojiRange(rangeStart:0x2B55, rangeEnd:0x2B55), + EmojiRange(rangeStart:0x3030, rangeEnd:0x3030), + EmojiRange(rangeStart:0x303D, rangeEnd:0x303D), + EmojiRange(rangeStart:0x3297, rangeEnd:0x3297), + EmojiRange(rangeStart:0x3299, rangeEnd:0x3299), + EmojiRange(rangeStart:0xFE00, rangeEnd:0xFE0F), + EmojiRange(rangeStart:0x1F000, rangeEnd:0x1F0FF), + EmojiRange(rangeStart:0x1F10D, rangeEnd:0x1F10F), + EmojiRange(rangeStart:0x1F12F, rangeEnd:0x1F12F), + EmojiRange(rangeStart:0x1F16C, rangeEnd:0x1F171), + EmojiRange(rangeStart:0x1F17E, rangeEnd:0x1F17F), + EmojiRange(rangeStart:0x1F18E, rangeEnd:0x1F18E), + EmojiRange(rangeStart:0x1F191, rangeEnd:0x1F19A), + EmojiRange(rangeStart:0x1F1AD, rangeEnd:0x1F1FF), + EmojiRange(rangeStart:0x1F201, rangeEnd:0x1F20F), + EmojiRange(rangeStart:0x1F21A, rangeEnd:0x1F21A), + EmojiRange(rangeStart:0x1F22F, rangeEnd:0x1F22F), + EmojiRange(rangeStart:0x1F232, rangeEnd:0x1F23A), + EmojiRange(rangeStart:0x1F23C, rangeEnd:0x1F23F), + EmojiRange(rangeStart:0x1F249, rangeEnd:0x1F64F), + EmojiRange(rangeStart:0x1F680, rangeEnd:0x1F6FF), + EmojiRange(rangeStart:0x1F774, rangeEnd:0x1F77F), + EmojiRange(rangeStart:0x1F7D5, rangeEnd:0x1F7FF), + EmojiRange(rangeStart:0x1F80C, rangeEnd:0x1F80F), + EmojiRange(rangeStart:0x1F848, rangeEnd:0x1F84F), + EmojiRange(rangeStart:0x1F85A, rangeEnd:0x1F85F), + EmojiRange(rangeStart:0x1F888, rangeEnd:0x1F88F), + EmojiRange(rangeStart:0x1F8AE, rangeEnd:0x1FFFD), + EmojiRange(rangeStart:0xE0020, rangeEnd:0xE007F) ] var isEmoji: Bool { @@ -211,17 +122,14 @@ extension String { } var isSingleEmoji: Bool { - return glyphCount == 1 && containsEmoji } var containsEmoji: Bool { - return unicodeScalars.contains { $0.isEmoji } } var containsOnlyEmoji: Bool { - return !isEmpty && !unicodeScalars.contains(where: { !$0.isEmoji diff --git a/Signal/test/util/DisplayableTextFilterTest.swift b/Signal/test/util/DisplayableTextFilterTest.swift index 39dd329acb..9fbcf481b0 100644 --- a/Signal/test/util/DisplayableTextFilterTest.swift +++ b/Signal/test/util/DisplayableTextFilterTest.swift @@ -17,24 +17,84 @@ class DisplayableTextTest: XCTestCase { } func testDisplayableText() { - // Ignore default byte size limitations to test other filtering behaviors - let filter = DisplayableText() - // show plain text let boringText = "boring text" - XCTAssertEqual(boringText, filter.displayableText(boringText)) + XCTAssertEqual(boringText, DisplayableText.displayableText(boringText)) // show high byte emojis let emojiText = "🇹🇹🌼🇹🇹🌼🇹🇹" - XCTAssertEqual(emojiText, filter.displayableText(emojiText)) + XCTAssertEqual(emojiText, DisplayableText.displayableText(emojiText)) // show normal diacritic usage let diacriticalText = "Příliš žluťoučký kůň úpěl ďábelské ódy." - XCTAssertEqual(diacriticalText, filter.displayableText(diacriticalText)) + XCTAssertEqual(diacriticalText, DisplayableText.displayableText(diacriticalText)) // filter excessive diacritics - XCTAssertEqual("HAVING TROUBLE READING TEXT?", filter.displayableText("H҉̸̧͘͠A͢͞V̛̛I̴̸N͏̕͏G҉̵͜͏͢ ̧̧́T̶̛͘͡R̸̵̨̢̀O̷̡U͡҉B̶̛͢͞L̸̸͘͢͟É̸ ̸̛͘͏R͟È͠͞A̸͝Ḑ̕͘͜I̵͘҉͜͞N̷̡̢͠G̴͘͠ ͟͞T͏̢́͡È̀X̕҉̢̀T̢͠?̕͏̢͘͢") ) + XCTAssertEqual("HAVING TROUBLE READING TEXT?", DisplayableText.displayableText("H҉̸̧͘͠A͢͞V̛̛I̴̸N͏̕͏G҉̵͜͏͢ ̧̧́T̶̛͘͡R̸̵̨̢̀O̷̡U͡҉B̶̛͢͞L̸̸͘͢͟É̸ ̸̛͘͏R͟È͠͞A̸͝Ḑ̕͘͜I̵͘҉͜͞N̷̡̢͠G̴͘͠ ͟͞T͏̢́͡È̀X̕҉̢̀T̢͠?̕͏̢͘͢") ) - XCTAssertEqual("LGO!", filter.displayableText("L̷̳͔̲͝Ģ̵̮̯̤̩̙͍̬̟͉̹̘̹͍͈̮̦̰̣͟͝O̶̴̮̻̮̗͘͡!̴̷̟͓͓")) + XCTAssertEqual("LGO!", DisplayableText.displayableText("L̷̳͔̲͝Ģ̵̮̯̤̩̙͍̬̟͉̹̘̹͍͈̮̦̰̣͟͝O̶̴̮̻̮̗͘͡!̴̷̟͓͓")) + } + + func testGlyphCount() { + // Plain text + XCTAssertEqual("boring text".glyphCount, 11) + + // Emojis + XCTAssertEqual("🇹🇹🌼🇹🇹🌼🇹🇹".glyphCount, 5) + XCTAssertEqual("🇹🇹".glyphCount, 1) + XCTAssertEqual("🇹🇹 ".glyphCount, 2) + XCTAssertEqual("👌🏽👌🏾👌🏿".glyphCount, 3) + XCTAssertEqual("😍".glyphCount, 1) + XCTAssertEqual("👩🏽".glyphCount, 1) + XCTAssertEqual("👾🙇💁🙅🙆🙋🙎🙍".glyphCount, 8) + XCTAssertEqual("🐵🙈🙉🙊".glyphCount, 4) + XCTAssertEqual("❤️💔💌💕💞💓💗💖💘💝💟💜💛💚💙".glyphCount, 15) + XCTAssertEqual("✋🏿💪🏿👐🏿🙌🏿👏🏿🙏🏿".glyphCount, 6) + XCTAssertEqual("🚾🆒🆓🆕🆖🆗🆙🏧".glyphCount, 8) + XCTAssertEqual("0️⃣1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣8️⃣9️⃣🔟".glyphCount, 11) + XCTAssertEqual("🇺🇸🇷🇺🇦🇫🇦🇲".glyphCount, 4) + XCTAssertEqual("🇺🇸🇷🇺🇸 🇦🇫🇦🇲🇸".glyphCount, 7) + XCTAssertEqual("🇺🇸🇷🇺🇸🇦🇫🇦🇲".glyphCount, 5) + XCTAssertEqual("🇺🇸🇷🇺🇸🇦".glyphCount, 3) + XCTAssertEqual("123".glyphCount, 3) + + // Normal diacritic usage + XCTAssertEqual("Příliš žluťoučký kůň úpěl ďábelské ódy.".glyphCount, 39) + + // Excessive diacritics + XCTAssertEqual("H҉̸̧͘͠A͢͞V̛̛I̴̸N͏̕͏G҉̵͜͏͢ ̧̧́T̶̛͘͡R̸̵̨̢̀O̷̡U͡҉B̶̛͢͞L̸̸͘͢͟É̸ ̸̛͘͏R͟È͠͞A̸͝Ḑ̕͘͜I̵͘҉͜͞N̷̡̢͠G̴͘͠ ͟͞T͏̢́͡È̀X̕҉̢̀T̢͠?̕͏̢͘͢".glyphCount, 115) + XCTAssertEqual("L̷̳͔̲͝Ģ̵̮̯̤̩̙͍̬̟͉̹̘̹͍͈̮̦̰̣͟͝O̶̴̮̻̮̗͘͡!̴̷̟͓͓".glyphCount, 43) + } + + func testContainsOnlyEmoji() { + // Plain text + XCTAssertFalse("boring text".containsOnlyEmoji) + + // Emojis + XCTAssertTrue("🇹🇹🌼🇹🇹🌼🇹🇹".containsOnlyEmoji) + XCTAssertTrue("🇹🇹".containsOnlyEmoji) + XCTAssertFalse("🇹🇹 ".containsOnlyEmoji) + XCTAssertTrue("👌🏽👌🏾👌🏿".containsOnlyEmoji) + XCTAssertTrue("😍".containsOnlyEmoji) + XCTAssertTrue("👩🏽".containsOnlyEmoji) + XCTAssertTrue("👾🙇💁🙅🙆🙋🙎🙍".containsOnlyEmoji) + XCTAssertTrue("🐵🙈🙉🙊".containsOnlyEmoji) + XCTAssertTrue("❤️💔💌💕💞💓💗💖💘💝💟💜💛💚💙".containsOnlyEmoji) + XCTAssertTrue("✋🏿💪🏿👐🏿🙌🏿👏🏿🙏🏿".containsOnlyEmoji) + XCTAssertTrue("🚾🆒🆓🆕🆖🆗🆙🏧".containsOnlyEmoji) + XCTAssertTrue("0️⃣1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣8️⃣9️⃣🔟".containsOnlyEmoji) + XCTAssertTrue("🇺🇸🇷🇺🇦🇫🇦🇲".containsOnlyEmoji) + XCTAssertFalse("🇺🇸🇷🇺🇸 🇦🇫🇦🇲🇸".containsOnlyEmoji) + XCTAssertTrue("🇺🇸🇷🇺🇸🇦🇫🇦🇲".containsOnlyEmoji) + XCTAssertTrue("🇺🇸🇷🇺🇸🇦".containsOnlyEmoji) + // Unicode standard doesn't consider these to be Emoji. + XCTAssertFalse("123".containsOnlyEmoji) + + // Normal diacritic usage + XCTAssertFalse("Příliš žluťoučký kůň úpěl ďábelské ódy.".containsOnlyEmoji) + + // Excessive diacritics + XCTAssertFalse("H҉̸̧͘͠A͢͞V̛̛I̴̸N͏̕͏G҉̵͜͏͢ ̧̧́T̶̛͘͡R̸̵̨̢̀O̷̡U͡҉B̶̛͢͞L̸̸͘͢͟É̸ ̸̛͘͏R͟È͠͞A̸͝Ḑ̕͘͜I̵͘҉͜͞N̷̡̢͠G̴͘͠ ͟͞T͏̢́͡È̀X̕҉̢̀T̢͠?̕͏̢͘͢".containsOnlyEmoji) + XCTAssertFalse("L̷̳͔̲͝Ģ̵̮̯̤̩̙͍̬̟͉̹̘̹͍͈̮̦̰̣͟͝O̶̴̮̻̮̗͘͡!̴̷̟͓͓".containsOnlyEmoji) } } From e3d8421b9ed220e3a6e858cef4382b40987e7786 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 31 Oct 2017 13:56:30 -0400 Subject: [PATCH 7/7] Respond to CR. // FREEBIE --- Scripts/emoji_ranges.py | 8 ++++++-- Signal/src/util/DisplayableText.swift | 5 +++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Scripts/emoji_ranges.py b/Scripts/emoji_ranges.py index d25e0e84c7..32cc9b6985 100755 --- a/Scripts/emoji_ranges.py +++ b/Scripts/emoji_ranges.py @@ -10,6 +10,9 @@ def fail(message): print message sys.exit(1) +# For simplicity and compactness, we pre-define the +# emoji code planes to ensure that all of the currently-used +# emoji ranges within them are combined. big_ranges = [ (0x1F600,0x1F64F, ), (0x1F300,0x1F5FF, ), @@ -22,8 +25,6 @@ big_ranges = [ (8400, 8447,) ] -# def is_in_big_range(range_start, range_end) - if __name__ == '__main__': src_filename = "emoji-data.txt" src_dir_path = os.path.dirname(__file__) @@ -80,5 +81,8 @@ if __name__ == '__main__': # print '0x%X...0x%X, // %d Emotions' % (range_start, range_end, (1 + range_end - range_start), ) print 'EmojiRange(rangeStart:0x%X, rangeEnd:0x%X),' % (range_start, range_end, ) print 'new_ranges:', len(new_ranges) + print + print 'Copy and paste the code above into DisplayableText.swift' + print diff --git a/Signal/src/util/DisplayableText.swift b/Signal/src/util/DisplayableText.swift index dd56ec71b2..32c54b4933 100644 --- a/Signal/src/util/DisplayableText.swift +++ b/Signal/src/util/DisplayableText.swift @@ -22,6 +22,11 @@ extension UnicodeScalar { // https://www.unicode.org/Public/emoji/ // Current Version: // https://www.unicode.org/Public/emoji/6.0/emoji-data.txt + // + // These ranges can be code-generated using: + // + // * Scripts/emoji-data.txt + // * Scripts/emoji_ranges.py static let kEmojiRanges = [ EmojiRange(rangeStart:0x23, rangeEnd:0x23), EmojiRange(rangeStart:0x2A, rangeEnd:0x2A),