Compare commits

..

4 Commits

Author SHA1 Message Date
Mislav Marohnić
6a39782a46 gemoji 2.0.1 2014-08-02 09:24:58 -07:00
Adam Roben
c6a9db29bd Fix tests 2014-08-02 09:24:09 -07:00
Adam Roben
3ec375d299 Remove incorrect unicode aliases
db/aliases.html now clears out any incorrect aliases it finds.
2014-08-02 09:24:09 -07:00
Adam Roben
34cd8356bb Use the same unicode aliases as Safari 7.0.5 on OS X 10.9.4
Category-Emoji.json contains many code point sequences ending in U+FE0F
VARIATION SELECTOR-16. OS X renders some of these code point sequences
as color emoji even if you strip off VARIATION SELECTOR-16. (And in fact
it even renders some of them as color emoji if you replace VARIATION
SELECTOR-16 with VARIATION SELECTOR-15, even though that is explicitly
requesting a "text" variant of the character).

We used to assume that VARIATION SELECTOR-16 was optional for all emoji.
But this doesn't match OS X's behavior. This could cause problems, e.g.,
if you were to use the unicode_aliases property to detect Unicode emoji
and replace them with images for browsers that don't support emoji
natively.

The Ruby code no longer does anything special with VARIATION SELECTOR-16
other than to remove it from the description of each emoji. The new
db/aliases.html file can be used to determine which the correct Unicode
aliases for each emoji are. That page takes each emoji, generates a set
of variants using VARIATION SELECTOR-16 and VARIATION SELECTOR-15,
and renders each variant into a <canvas> element twice: once with a red
font color, and once with a green font color. If the variant renders the
same way, the OS is rendering the variant as a color emoji, and we add
it to the "unicodes" array for that emoji.

So the new process for generating db/emoji.json is:

1. rake db:dump > tmp
2. mv tmp db/emoji.json
3. open -a Safari db/aliases.html
4. Copy the resulting JSON from Safari
5. pbpaste > db/emoji.json

This ensures that unicode_aliases only contains those code point
sequences which actually render as color emoji on OS X.
2014-08-02 09:24:09 -07:00
7 changed files with 322 additions and 50 deletions

View File

@@ -1,7 +1,7 @@
PATH
remote: .
specs:
gemoji (1.5.0)
gemoji (2.0.1)
GEM
remote: https://rubygems.org/

87
db/aliases.html Normal file
View File

@@ -0,0 +1,87 @@
<!DOCTYPE html>
<title>Emoji alias detection</title>
<style>
textarea {
font-family: monospace;
}
</style>
<p>Save the following as <tt>emoji.json</tt>:</p>
<textarea id="output" rows="50" cols="80"></textarea>
<script>
const VARIATION_SELECTOR_15 = String.fromCharCode(0xfe0e);
const VARIATION_SELECTOR_16 = String.fromCharCode(0xfe0f);
function detectAliases(db) {
for (var i = 0; i < db.length; ++i) {
var emoji = db[i];
var raw = emoji.emoji;
if (!raw) {
continue;
}
var candidates = [];
if (raw.length === 1) {
candidates.push(raw + VARIATION_SELECTOR_15);
candidates.push(raw + VARIATION_SELECTOR_16);
} else if (raw[raw.length - 1] === VARIATION_SELECTOR_16) {
var base = raw.substr(0, raw.length - 1);
candidates.push(base);
candidates.push(base + VARIATION_SELECTOR_15);
}
var aliases = candidates.filter(isColorEmoji);
if (aliases.length) {
emoji.unicodes = aliases;
} else {
delete emoji.unicodes;
}
}
dump(db);
}
function isColorEmoji(candidate) {
// Draw the emoji twice using a different color each time. If the emoji
// draws as the same color regardless of what color we set, it's a color
// emoji.
return color(candidate, "#f00") === color(candidate, "#0f0");
}
var canvas = document.createElement("canvas");
function color(emoji, rgb) {
var context = canvas.getContext("2d");
context.clearRect(0, 0, 32, 32);
context.fillStyle = rgb;
context.textBaseline = "top";
context.font = "32px Arial";
context.fillText(emoji, 0, 0);
var data = context.getImageData(0, 0, 32, 32).data;
for (var i = 0; i < data.length; i += 4) {
if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0) {
continue;
}
return data[i].toString(16)
+ data[i + 1].toString(16)
+ data[i + 2].toString(16);
}
return "no colored pixel found";
}
function dump(db) {
var json = JSON.stringify(db, null, " ")
.replace(/^( +)(.+)\[\](,?)$/mg, "$1$2[\n$1]$3")
.replace(/,\n( *) /g, "\n$1, ");
document.getElementById("output").value = json + "\n";
}
var xhr = new XMLHttpRequest;
xhr.onload = function() {
detectAliases(JSON.parse(this.responseText));
};
xhr.open("GET", "emoji.json", false);
xhr.send(null);
</script>

View File

@@ -52,7 +52,8 @@ end
trap(:PIPE) { abort }
items = []
variation = Emoji::VARIATION_SELECTOR_16
variation = "\u{fe0f}".freeze
for emoji in Emoji.all
unicodes = emoji.unicode_aliases.dup
@@ -62,7 +63,6 @@ for emoji in Emoji.all
unless emoji.custom?
variation_codepoint = variation.codepoints[0]
chars = emoji.raw.codepoints.map { |code| UnicodeCharacter.fetch(code) unless code == variation_codepoint }.compact
unicodes.select { |u| u.index(variation) }.each { |u| unicodes.delete(u.sub(variation, '')) }
item[:emoji] = unicodes.shift
item[:unicodes] = unicodes if unicodes.any?
item[:description] = chars.map(&:description).join(' + ')

View File

@@ -931,6 +931,10 @@
}
, {
"emoji": "✨"
, "unicodes": [
"✨︎"
, "✨️"
]
, "description": "sparkles"
, "aliases": [
"sparkles"
@@ -1122,6 +1126,10 @@
}
, {
"emoji": "✊"
, "unicodes": [
"✊︎"
, "✊️"
]
, "description": "raised fist"
, "aliases": [
"fist"
@@ -1153,6 +1161,10 @@
}
, {
"emoji": "✋"
, "unicodes": [
"✋︎"
, "✋️"
]
, "description": "raised hand"
, "aliases": [
"hand"
@@ -2905,6 +2917,9 @@
}
, {
"emoji": "⭐️"
, "unicodes": [
"⭐"
]
, "description": "white medium star"
, "aliases": [
"star"
@@ -2924,6 +2939,10 @@
}
, {
"emoji": "⛅️"
, "unicodes": [
"⛅"
, "⛅︎"
]
, "description": "sun behind cloud"
, "aliases": [
"partly_sunny"
@@ -2944,6 +2963,9 @@
}
, {
"emoji": "⚡️"
, "unicodes": [
"⚡"
]
, "description": "high voltage sign"
, "aliases": [
"zap"
@@ -2955,6 +2977,9 @@
}
, {
"emoji": "☔️"
, "unicodes": [
"☔"
]
, "description": "umbrella with rain drops"
, "aliases": [
"umbrella"
@@ -2978,6 +3003,10 @@
}
, {
"emoji": "⛄️"
, "unicodes": [
"⛄"
, "⛄︎"
]
, "description": "snowman without snow"
, "aliases": [
"snowman"
@@ -3482,6 +3511,10 @@
}
, {
"emoji": "⏳"
, "unicodes": [
"⏳︎"
, "⏳️"
]
, "description": "hourglass with flowing sand"
, "aliases": [
"hourglass_flowing_sand"
@@ -3492,6 +3525,9 @@
}
, {
"emoji": "⌛️"
, "unicodes": [
"⌛"
]
, "description": "hourglass"
, "aliases": [
"hourglass"
@@ -3502,6 +3538,10 @@
}
, {
"emoji": "⏰"
, "unicodes": [
"⏰︎"
, "⏰️"
]
, "description": "alarm clock"
, "aliases": [
"alarm_clock"
@@ -3512,6 +3552,9 @@
}
, {
"emoji": "⌚️"
, "unicodes": [
"⌚"
]
, "description": "watch"
, "aliases": [
"watch"
@@ -4488,6 +4531,10 @@
}
, {
"emoji": "🀄️"
, "unicodes": [
"🀄"
, "🀄︎"
]
, "description": "mahjong tile red dragon"
, "aliases": [
"mahjong"
@@ -4538,6 +4585,9 @@
}
, {
"emoji": "⚽️"
, "unicodes": [
"⚽"
]
, "description": "soccer ball"
, "aliases": [
"soccer"
@@ -4597,6 +4647,10 @@
}
, {
"emoji": "⛳️"
, "unicodes": [
"⛳"
, "⛳︎"
]
, "description": "flag in hole"
, "aliases": [
"golf"
@@ -4701,6 +4755,9 @@
}
, {
"emoji": "☕️"
, "unicodes": [
"☕"
]
, "description": "hot beverage"
, "aliases": [
"coffee"
@@ -5359,6 +5416,10 @@
}
, {
"emoji": "⛪️"
, "unicodes": [
"⛪"
, "⛪︎"
]
, "description": "church"
, "aliases": [
"church"
@@ -5422,6 +5483,10 @@
}
, {
"emoji": "⛺️"
, "unicodes": [
"⛺"
, "⛺︎"
]
, "description": "tent"
, "aliases": [
"tent"
@@ -5531,6 +5596,10 @@
}
, {
"emoji": "⛲️"
, "unicodes": [
"⛲"
, "⛲︎"
]
, "description": "fountain"
, "aliases": [
"fountain"
@@ -5558,6 +5627,10 @@
}
, {
"emoji": "⛵️"
, "unicodes": [
"⛵"
, "⛵︎"
]
, "description": "sailboat"
, "aliases": [
"boat"
@@ -5587,6 +5660,9 @@
}
, {
"emoji": "⚓️"
, "unicodes": [
"⚓"
]
, "description": "anchor"
, "aliases": [
"anchor"
@@ -6015,6 +6091,10 @@
}
, {
"emoji": "⛽️"
, "unicodes": [
"⛽"
, "⛽︎"
]
, "description": "fuel pump"
, "aliases": [
"fuelpump"
@@ -6528,6 +6608,10 @@
}
, {
"emoji": "⏪"
, "unicodes": [
"⏪︎"
, "⏪️"
]
, "description": "black left-pointing double triangle"
, "aliases": [
"rewind"
@@ -6537,6 +6621,10 @@
}
, {
"emoji": "⏩"
, "unicodes": [
"⏩︎"
, "⏩️"
]
, "description": "black right-pointing double triangle"
, "aliases": [
"fast_forward"
@@ -6546,6 +6634,10 @@
}
, {
"emoji": "⏫"
, "unicodes": [
"⏫︎"
, "⏫️"
]
, "description": "black up-pointing double triangle"
, "aliases": [
"arrow_double_up"
@@ -6555,6 +6647,10 @@
}
, {
"emoji": "⏬"
, "unicodes": [
"⏬︎"
, "⏬️"
]
, "description": "black down-pointing double triangle"
, "aliases": [
"arrow_double_down"
@@ -6698,7 +6794,8 @@
, {
"emoji": "🈯️"
, "unicodes": [
""
"🈯"
, "🈯︎"
]
, "description": "squared cjk unified ideograph-6307"
, "aliases": [
@@ -6709,9 +6806,6 @@
}
, {
"emoji": "🈳"
, "unicodes": [
"空"
]
, "description": "squared cjk unified ideograph-7a7a"
, "aliases": [
"u7a7a"
@@ -6721,9 +6815,6 @@
}
, {
"emoji": "🈵"
, "unicodes": [
"満"
]
, "description": "squared cjk unified ideograph-6e80"
, "aliases": [
"u6e80"
@@ -6733,9 +6824,6 @@
}
, {
"emoji": "🈴"
, "unicodes": [
"合"
]
, "description": "squared cjk unified ideograph-5408"
, "aliases": [
"u5408"
@@ -6745,9 +6833,6 @@
}
, {
"emoji": "🈲"
, "unicodes": [
"禁"
]
, "description": "squared cjk unified ideograph-7981"
, "aliases": [
"u7981"
@@ -6766,9 +6851,6 @@
}
, {
"emoji": "🈹"
, "unicodes": [
"割"
]
, "description": "squared cjk unified ideograph-5272"
, "aliases": [
"u5272"
@@ -6778,9 +6860,6 @@
}
, {
"emoji": "🈺"
, "unicodes": [
"営"
]
, "description": "squared cjk unified ideograph-55b6"
, "aliases": [
"u55b6"
@@ -6790,9 +6869,6 @@
}
, {
"emoji": "🈶"
, "unicodes": [
"有"
]
, "description": "squared cjk unified ideograph-6709"
, "aliases": [
"u6709"
@@ -6803,7 +6879,8 @@
, {
"emoji": "🈚️"
, "unicodes": [
""
"🈚"
, "🈚︎"
]
, "description": "squared cjk unified ideograph-7121"
, "aliases": [
@@ -6889,6 +6966,9 @@
}
, {
"emoji": "♿️"
, "unicodes": [
"♿"
]
, "description": "wheelchair symbol"
, "aliases": [
"wheelchair"
@@ -6908,9 +6988,6 @@
}
, {
"emoji": "🈷"
, "unicodes": [
"月"
]
, "description": "squared cjk unified ideograph-6708"
, "aliases": [
"u6708"
@@ -6920,9 +6997,6 @@
}
, {
"emoji": "🈸"
, "unicodes": [
"申"
]
, "description": "squared cjk unified ideograph-7533"
, "aliases": [
"u7533"
@@ -7117,6 +7191,10 @@
}
, {
"emoji": "⛔️"
, "unicodes": [
"⛔"
, "⛔︎"
]
, "description": "no entry"
, "aliases": [
"no_entry"
@@ -7145,6 +7223,10 @@
}
, {
"emoji": "❎"
, "unicodes": [
"❎︎"
, "❎️"
]
, "description": "negative squared cross mark"
, "aliases": [
"negative_squared_cross_mark"
@@ -7154,6 +7236,10 @@
}
, {
"emoji": "✅"
, "unicodes": [
"✅︎"
, "✅️"
]
, "description": "white heavy check mark"
, "aliases": [
"white_check_mark"
@@ -7255,6 +7341,9 @@
}
, {
"emoji": "➿"
, "unicodes": [
"➿️"
]
, "description": "double curly loop"
, "aliases": [
"loop"
@@ -7275,6 +7364,9 @@
}
, {
"emoji": "♈️"
, "unicodes": [
"♈"
]
, "description": "aries"
, "aliases": [
"aries"
@@ -7284,6 +7376,9 @@
}
, {
"emoji": "♉️"
, "unicodes": [
"♉"
]
, "description": "taurus"
, "aliases": [
"taurus"
@@ -7293,6 +7388,9 @@
}
, {
"emoji": "♊️"
, "unicodes": [
"♊"
]
, "description": "gemini"
, "aliases": [
"gemini"
@@ -7302,6 +7400,9 @@
}
, {
"emoji": "♋️"
, "unicodes": [
"♋"
]
, "description": "cancer"
, "aliases": [
"cancer"
@@ -7311,6 +7412,9 @@
}
, {
"emoji": "♌️"
, "unicodes": [
"♌"
]
, "description": "leo"
, "aliases": [
"leo"
@@ -7320,6 +7424,9 @@
}
, {
"emoji": "♍️"
, "unicodes": [
"♍"
]
, "description": "virgo"
, "aliases": [
"virgo"
@@ -7329,6 +7436,9 @@
}
, {
"emoji": "♎️"
, "unicodes": [
"♎"
]
, "description": "libra"
, "aliases": [
"libra"
@@ -7338,6 +7448,9 @@
}
, {
"emoji": "♏️"
, "unicodes": [
"♏"
]
, "description": "scorpius"
, "aliases": [
"scorpius"
@@ -7347,6 +7460,9 @@
}
, {
"emoji": "♐️"
, "unicodes": [
"♐"
]
, "description": "sagittarius"
, "aliases": [
"sagittarius"
@@ -7356,6 +7472,9 @@
}
, {
"emoji": "♑️"
, "unicodes": [
"♑"
]
, "description": "capricorn"
, "aliases": [
"capricorn"
@@ -7365,6 +7484,9 @@
}
, {
"emoji": "♒️"
, "unicodes": [
"♒"
]
, "description": "aquarius"
, "aliases": [
"aquarius"
@@ -7374,6 +7496,9 @@
}
, {
"emoji": "♓️"
, "unicodes": [
"♓"
]
, "description": "pisces"
, "aliases": [
"pisces"
@@ -7383,6 +7508,10 @@
}
, {
"emoji": "⛎"
, "unicodes": [
"⛎︎"
, "⛎️"
]
, "description": "ophiuchus"
, "aliases": [
"ophiuchus"
@@ -7437,6 +7566,9 @@
}
, {
"emoji": "©"
, "unicodes": [
"©️"
]
, "description": "copyright sign"
, "aliases": [
"copyright"
@@ -7446,6 +7578,9 @@
}
, {
"emoji": "®"
, "unicodes": [
"®️"
]
, "description": "registered sign"
, "aliases": [
"registered"
@@ -7455,6 +7590,9 @@
}
, {
"emoji": "™"
, "unicodes": [
"™️"
]
, "description": "trade mark sign"
, "aliases": [
"tm"
@@ -7465,6 +7603,10 @@
}
, {
"emoji": "❌"
, "unicodes": [
"❌︎"
, "❌️"
]
, "description": "cross mark"
, "aliases": [
"x"
@@ -7492,6 +7634,10 @@
}
, {
"emoji": "❗️"
, "unicodes": [
"❗"
, "❗︎"
]
, "description": "heavy exclamation mark symbol"
, "aliases": [
"exclamation"
@@ -7503,6 +7649,10 @@
}
, {
"emoji": "❓"
, "unicodes": [
"❓︎"
, "❓️"
]
, "description": "black question mark ornament"
, "aliases": [
"question"
@@ -7513,6 +7663,10 @@
}
, {
"emoji": "❕"
, "unicodes": [
"❕︎"
, "❕️"
]
, "description": "white exclamation mark ornament"
, "aliases": [
"grey_exclamation"
@@ -7522,6 +7676,10 @@
}
, {
"emoji": "❔"
, "unicodes": [
"❔︎"
, "❔️"
]
, "description": "white question mark ornament"
, "aliases": [
"grey_question"
@@ -7531,6 +7689,10 @@
}
, {
"emoji": "⭕️"
, "unicodes": [
"⭕"
, "⭕︎"
]
, "description": "heavy large circle"
, "aliases": [
"o"
@@ -7819,6 +7981,10 @@
}
, {
"emoji": ""
, "unicodes": [
""
, ""
]
, "description": "heavy plus sign"
, "aliases": [
"heavy_plus_sign"
@@ -7828,6 +7994,10 @@
}
, {
"emoji": ""
, "unicodes": [
""
, ""
]
, "description": "heavy minus sign"
, "aliases": [
"heavy_minus_sign"
@@ -7837,6 +8007,10 @@
}
, {
"emoji": "➗"
, "unicodes": [
"➗︎"
, "➗️"
]
, "description": "heavy division sign"
, "aliases": [
"heavy_division_sign"
@@ -7938,6 +8112,10 @@
}
, {
"emoji": "➰"
, "unicodes": [
"➰︎"
, "➰️"
]
, "description": "curly loop"
, "aliases": [
"curly_loop"
@@ -7947,6 +8125,9 @@
}
, {
"emoji": "〰"
, "unicodes": [
"〰️"
]
, "description": "wavy dash"
, "aliases": [
"wavy_dash"
@@ -7992,6 +8173,9 @@
}
, {
"emoji": "◾️"
, "unicodes": [
"◾"
]
, "description": "black medium small square"
, "aliases": [
"black_medium_small_square"
@@ -8001,6 +8185,9 @@
}
, {
"emoji": "◽️"
, "unicodes": [
"◽"
]
, "description": "white medium small square"
, "aliases": [
"white_medium_small_square"
@@ -8055,6 +8242,9 @@
}
, {
"emoji": "⚫️"
, "unicodes": [
"⚫"
]
, "description": "medium black circle"
, "aliases": [
"black_circle"
@@ -8064,6 +8254,9 @@
}
, {
"emoji": "⚪️"
, "unicodes": [
"⚪"
]
, "description": "medium white circle"
, "aliases": [
"white_circle"
@@ -8100,6 +8293,9 @@
}
, {
"emoji": "⬜️"
, "unicodes": [
"⬜"
]
, "description": "white large square"
, "aliases": [
"white_large_square"
@@ -8109,6 +8305,9 @@
}
, {
"emoji": "⬛️"
, "unicodes": [
"⬛"
]
, "description": "black large square"
, "aliases": [
"black_large_square"

View File

@@ -1,6 +1,6 @@
Gem::Specification.new do |s|
s.name = "gemoji"
s.version = "2.0.0"
s.version = "2.0.1"
s.summary = "Emoji conversion and image assets"
s.description = "Image assets and character information for emoji."

View File

@@ -64,22 +64,13 @@ module Emoji
end
private
VARIATION_SELECTOR_16 = "\u{fe0f}".freeze
def parse_data_file
raw = File.open(data_file, 'r:UTF-8') { |data| JSON.parse(data.read) }
raw.each do |raw_emoji|
self.create(nil) do |emoji|
raw_emoji.fetch('aliases').each { |name| emoji.add_alias(name) }
unicodes = Array(raw_emoji['emoji']) + raw_emoji.fetch('unicodes', [])
unicodes.each { |uni|
emoji.add_unicode_alias(uni)
# Automatically add a representation of this emoji without the variation
# selector to unicode aliases:
if uni.index(VARIATION_SELECTOR_16)
emoji.add_unicode_alias(uni.sub(VARIATION_SELECTOR_16, ''))
end
}
unicodes.each { |uni| emoji.add_unicode_alias(uni) }
raw_emoji.fetch('tags').each { |tag| emoji.add_tag(tag) }
end
end

View File

@@ -47,13 +47,8 @@ class EmojiTest < TestCase
end
test "unicode_aliases" do
emoji = Emoji.find_by_unicode("\u{1f237}")
assert_equal ["\u{1f237}", "\u{6708}"], emoji.unicode_aliases
end
test "unicode_aliases includes form without variation selector" do
emoji = Emoji.find_by_alias("heart")
assert_equal ["\u{2764}\u{fe0f}", "\u{2764}"], emoji.unicode_aliases
emoji = Emoji.find_by_unicode("\u{2728}")
assert_equal ["\u{2728}", "\u{2728}\u{fe0e}", "\u{2728}\u{fe0f}"], emoji.unicode_aliases
end
test "emojis have tags" do