diff --git a/db/emoji.json b/db/emoji.json index 6e425ca..5cd0d26 100644 --- a/db/emoji.json +++ b/db/emoji.json @@ -6416,7 +6416,7 @@ ] , "unicode_version": "12.0" , "ios_version": "13.0" - , "skin_tones": true + , "skin_tones": false } , { "emoji": "👭" diff --git a/lib/emoji.rb b/lib/emoji.rb index 7d3d801..d810a25 100644 --- a/lib/emoji.rb +++ b/lib/emoji.rb @@ -51,11 +51,12 @@ module Emoji # Public: Find an emoji by its unicode character. Return nil if missing. def find_by_unicode(unicode) - unicodes_index[unicode] + unicodes_index[unicode] || unicodes_index[unicode.sub(SKIN_TONE_RE, "")] end private VARIATION_SELECTOR_16 = "\u{fe0f}".freeze + SKIN_TONE_RE = /[\u{1F3FB}-\u{1F3FF}]/ # Characters which must have VARIATION_SELECTOR_16 to render as color emoji: TEXT_GLYPHS = [ @@ -70,7 +71,7 @@ module Emoji "\u{3030}", # wavy dash ].freeze - private_constant :VARIATION_SELECTOR_16, :TEXT_GLYPHS + private_constant :VARIATION_SELECTOR_16, :TEXT_GLYPHS, :SKIN_TONE_RE def parse_data_file data = File.open(data_file, 'r:UTF-8') do |file| @@ -118,6 +119,7 @@ module Emoji emoji.description = dedup.call(raw_emoji[:description]) emoji.unicode_version = dedup.call(raw_emoji[:unicode_version]) emoji.ios_version = dedup.call(raw_emoji[:ios_version]) + emoji.skin_tones = raw_emoji.fetch(:skin_tones, false) end end end diff --git a/lib/emoji/character.rb b/lib/emoji/character.rb index 4207939..25504aa 100644 --- a/lib/emoji/character.rb +++ b/lib/emoji/character.rb @@ -11,6 +11,11 @@ module Emoji # True if the emoji is not a standard Emoji character. def custom?() !raw end + # True if the emoji supports Fitzpatrick scale skin tone modifiers + def skin_tones?() @skin_tones end + + attr_writer :skin_tones + # A list of names uniquely referring to this emoji. attr_reader :aliases @@ -38,6 +43,21 @@ module Emoji # Raw Unicode string for an emoji. Nil if emoji is non-standard. def raw() unicode_aliases.first end + # Raw Unicode strings for each skin tone variant of this emoji. + def raw_skin_tone_variants + return [] if custom? || !skin_tones? + raw_normalized = raw.sub("\u{fe0f}", "") # strip VARIATION_SELECTOR_16 + idx = raw_normalized.index("\u{200d}") # detect zero-width joiner + SKIN_TONES.map do |modifier| + if idx + # insert modifier before zero-width joiner + raw_normalized[...idx] + modifier + raw_normalized[idx..] + else + raw_normalized + modifier + end + end + end + def add_unicode_alias(str) unicode_aliases << str end @@ -54,6 +74,7 @@ module Emoji @aliases = Array(name) @unicode_aliases = [] @tags = [] + @skin_tones = false end def inspect @@ -76,6 +97,16 @@ module Emoji end private + + SKIN_TONES = [ + "\u{1F3FB}", # light skin tone + "\u{1F3FC}", # medium-light skin tone + "\u{1F3FD}", # medium skin tone + "\u{1F3FE}", # medium-dark skin tone + "\u{1F3FF}", # dark skin tone + ] + + private_constant :SKIN_TONES def default_image_filename if custom? diff --git a/test/emoji_test.rb b/test/emoji_test.rb index 8f75d23..64818d6 100644 --- a/test/emoji_test.rb +++ b/test/emoji_test.rb @@ -157,6 +157,27 @@ class EmojiTest < TestCase assert_equal '8.3', emoji.ios_version end + test "skin tones" do + smiley = Emoji.find_by_alias("smiley") + assert_equal false, smiley.skin_tones? + assert_equal [], smiley.raw_skin_tone_variants + + wave = Emoji.find_by_alias("wave") + assert_equal true, wave.skin_tones? + + wave = Emoji.find_by_unicode("\u{1f44b}\u{1f3ff}") # wave + dark skin tone + assert_equal "wave", wave.name + + woman_with_beard = Emoji.find_by_unicode("\u{1f9d4}\u{200d}\u{2640}\u{fe0f}") + assert_equal [ + "1f9d4-1f3fb-200d-2640", + "1f9d4-1f3fc-200d-2640", + "1f9d4-1f3fd-200d-2640", + "1f9d4-1f3fe-200d-2640", + "1f9d4-1f3ff-200d-2640", + ], woman_with_beard.raw_skin_tone_variants.map { |u| Emoji::Character.hex_inspect(u) } + end + test "no custom emojis" do custom = Emoji.all.select(&:custom?) assert 0, custom.size