Implement emoji parsing

Fix #60
This commit is contained in:
Rasmus Karlsson
2017-07-02 17:37:17 +02:00
parent 17f8cc2293
commit a58cd3333e
4 changed files with 93 additions and 62 deletions
+5
View File
@@ -13,7 +13,12 @@ namespace chatterino {
struct EmojiData { struct EmojiData {
QString value; QString value;
// what's used in the emoji-one url
QString code; QString code;
// i.e. thinking
QString shortCode;
}; };
class Emojis class Emojis
+75 -47
View File
@@ -188,74 +188,102 @@ void EmoteManager::loadEmojis()
EmojiData emojiData{ EmojiData emojiData{
QString::fromUcs4(unicodeBytes, numUnicodeBytes), // QString::fromUcs4(unicodeBytes, numUnicodeBytes), //
code, // code, //
shortCode, //
}; };
shortCodeToEmoji.insert(shortCode, emojiData); this->emojiShortCodeToEmoji.insert(shortCode, emojiData);
emojiToShortCode.insert(emojiData.value, shortCode);
this->emojiFirstByte[emojiData.value.at(0)].append(emojiData);
} }
/*
for (auto const &emoji : shortCodeToEmoji.toStdMap()) {
auto iter = firstEmojiChars.find(emoji.first.at(0));
if (iter != firstEmojiChars.end()) {
iter.value().insert(emoji.second.value, emoji.second.value);
continue;
}
firstEmojiChars.insert(emoji.first.at(0),
QMap<QString, QString>{{emoji.second.value, emoji.second.code}});
}
*/
} }
void EmoteManager::parseEmojis( void EmoteManager::parseEmojis(
std::vector<std::tuple<messages::LazyLoadedImage *, QString>> &vector, const QString &text) std::vector<std::tuple<messages::LazyLoadedImage *, QString>> &parsedWords, const QString &text)
{ {
// TODO(pajlada): Add this method to EmoteManager instead int lastParsedEmojiEndIndex = 0;
long lastSlice = 0;
for (auto i = 0; i < text.length() - 1; i++) { for (auto i = 0; i < text.length() - 1; i++) {
if (!text.at(i).isLowSurrogate()) { const QChar character = text.at(i);
auto iter = firstEmojiChars.find(text.at(i));
if (iter != firstEmojiChars.end()) { if (character.isLowSurrogate()) {
for (auto j = std::min(8, text.length() - i); j > 0; j--) { continue;
QString emojiString = text.mid(i, 2); }
auto emojiIter = iter.value().find(emojiString);
if (emojiIter != iter.value().end()) { auto it = this->emojiFirstByte.find(character);
QString url = "https://cdnjs.cloudflare.com/ajax/libs/" if (it == this->emojiFirstByte.end()) {
"emojione/2.2.6/assets/png/" + // No emoji starts with this character
emojiIter.value() + ".png"; continue;
}
if (i - lastSlice != 0) { const QVector<EmojiData> possibleEmojis = it.value();
vector.push_back(std::tuple<messages::LazyLoadedImage *, QString>(
nullptr, text.mid(lastSlice, i - lastSlice)));
}
vector.push_back(std::tuple<messages::LazyLoadedImage *, QString>( int remainingCharacters = text.length() - i;
emojis.getOrAdd(url,
[this, &url] {
return new LazyLoadedImage(
*this, this->windowManager, url, 0.35); //
}),
QString()));
i += j - 1; EmojiData matchedEmoji;
lastSlice = i + 1; int matchedEmojiLength = 0;
break; for (const EmojiData &emoji : possibleEmojis) {
} if (remainingCharacters < emoji.value.length()) {
// It cannot be this emoji, there's not enough space for it
continue;
}
bool match = true;
for (int j = 1; j < emoji.value.length(); ++j) {
if (text.at(i + j) != emoji.value.at(j)) {
match = false;
break;
} }
} }
if (match) {
matchedEmoji = emoji;
matchedEmojiLength = emoji.value.length();
break;
}
} }
if (matchedEmojiLength == 0) {
continue;
}
int currentParsedEmojiFirstIndex = i;
int currentParsedEmojiEndIndex = i + (matchedEmojiLength);
int charactersFromLastParsedEmoji = currentParsedEmojiFirstIndex - lastParsedEmojiEndIndex;
if (charactersFromLastParsedEmoji > 0) {
// Add characters inbetween emojis
parsedWords.push_back(std::tuple<messages::LazyLoadedImage *, QString>(
nullptr, text.mid(lastParsedEmojiEndIndex, charactersFromLastParsedEmoji)));
}
QString url = "https://cdnjs.cloudflare.com/ajax/libs/"
"emojione/2.2.6/assets/png/" +
matchedEmoji.code + ".png";
// Create or fetch cached emoji image
auto emojiImage = this->emojiCache.getOrAdd(url, [this, &url] {
return new LazyLoadedImage(*this, this->windowManager, url, 0.35); //
});
// Push the emoji as a word to parsedWords
parsedWords.push_back(
std::tuple<messages::LazyLoadedImage *, QString>(emojiImage, QString()));
lastParsedEmojiEndIndex = currentParsedEmojiEndIndex;
i += matchedEmojiLength - 1;
} }
if (lastSlice < text.length()) { if (lastParsedEmojiEndIndex < text.length()) {
vector.push_back( // Add remaining characters
std::tuple<messages::LazyLoadedImage *, QString>(nullptr, text.mid(lastSlice))); parsedWords.push_back(std::tuple<messages::LazyLoadedImage *, QString>(
nullptr, text.mid(lastParsedEmojiEndIndex)));
} }
} }
+12 -14
View File
@@ -62,44 +62,42 @@ private:
WindowManager &windowManager; WindowManager &windowManager;
Resources &resources; Resources &resources;
// Emojis /// Emojis
// shortCodeToEmoji maps strings like ":sunglasses:" to the unicode character // shortCodeToEmoji maps strings like "sunglasses" to its emoji
QMap<QString, EmojiData> shortCodeToEmoji; QMap<QString, EmojiData> emojiShortCodeToEmoji;
// emojiToShortCode maps the unicode character to the shortcode like ":sunglasses:" // Maps the first character of the emoji unicode string to a vector of possible emojis
QMap<QString, QString> emojiToShortCode; QMap<QChar, QVector<EmojiData>> emojiFirstByte;
// TODO(pajlada): Figure out what this is for // url Emoji-one image
QMap<QChar, QMap<QString, QString>> firstEmojiChars; ConcurrentMap<QString, messages::LazyLoadedImage *> emojiCache;
ConcurrentMap<QString, messages::LazyLoadedImage *> emojis;
void loadEmojis(); void loadEmojis();
public: public:
void parseEmojis(std::vector<std::tuple<messages::LazyLoadedImage *, QString>> &vector, void parseEmojis(std::vector<std::tuple<messages::LazyLoadedImage *, QString>> &parsedWords,
const QString &text); const QString &text);
private: private:
// Twitch emotes /// Twitch emotes
ConcurrentMap<QString, twitch::EmoteValue *> _twitchEmotes; ConcurrentMap<QString, twitch::EmoteValue *> _twitchEmotes;
ConcurrentMap<long, messages::LazyLoadedImage *> _twitchEmoteFromCache; ConcurrentMap<long, messages::LazyLoadedImage *> _twitchEmoteFromCache;
// BTTV emotes /// BTTV emotes
ConcurrentMap<QString, messages::LazyLoadedImage *> bttvChannelEmotes; ConcurrentMap<QString, messages::LazyLoadedImage *> bttvChannelEmotes;
ConcurrentMap<QString, messages::LazyLoadedImage *> _bttvEmotes; ConcurrentMap<QString, messages::LazyLoadedImage *> _bttvEmotes;
ConcurrentMap<QString, messages::LazyLoadedImage *> _bttvChannelEmoteFromCaches; ConcurrentMap<QString, messages::LazyLoadedImage *> _bttvChannelEmoteFromCaches;
void loadBTTVEmotes(); void loadBTTVEmotes();
// FFZ emotes /// FFZ emotes
ConcurrentMap<QString, messages::LazyLoadedImage *> ffzChannelEmotes; ConcurrentMap<QString, messages::LazyLoadedImage *> ffzChannelEmotes;
ConcurrentMap<QString, messages::LazyLoadedImage *> _ffzEmotes; ConcurrentMap<QString, messages::LazyLoadedImage *> _ffzEmotes;
ConcurrentMap<int, messages::LazyLoadedImage *> _ffzChannelEmoteFromCaches; ConcurrentMap<int, messages::LazyLoadedImage *> _ffzChannelEmoteFromCaches;
void loadFFZEmotes(); void loadFFZEmotes();
// Chatterino emotes /// Chatterino emotes
ConcurrentMap<QString, messages::LazyLoadedImage *> _chatterinoEmotes; ConcurrentMap<QString, messages::LazyLoadedImage *> _chatterinoEmotes;
// ??? // ???
+1 -1
View File
@@ -1,6 +1,5 @@
#include "twitch/twitchmessagebuilder.hpp" #include "twitch/twitchmessagebuilder.hpp"
#include "colorscheme.hpp" #include "colorscheme.hpp"
#include "emojis.hpp"
#include "emotemanager.hpp" #include "emotemanager.hpp"
#include "ircmanager.hpp" #include "ircmanager.hpp"
#include "resources.hpp" #include "resources.hpp"
@@ -190,6 +189,7 @@ SharedMessage TwitchMessageBuilder::parse(const Communi::IrcPrivateMessage *ircM
// split words // split words
std::vector<std::tuple<LazyLoadedImage *, QString>> parsed; std::vector<std::tuple<LazyLoadedImage *, QString>> parsed;
// Parse emojis and take all non-emojis and put them in parsed as full text-words
emoteManager.parseEmojis(parsed, split); emoteManager.parseEmojis(parsed, split);
for (const std::tuple<LazyLoadedImage *, QString> &tuple : parsed) { for (const std::tuple<LazyLoadedImage *, QString> &tuple : parsed) {