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

View file

@ -13,7 +13,12 @@ namespace chatterino {
struct EmojiData {
QString value;
// what's used in the emoji-one url
QString code;
// i.e. thinking
QString shortCode;
};
class Emojis

View file

@ -188,74 +188,102 @@ void EmoteManager::loadEmojis()
EmojiData emojiData{
QString::fromUcs4(unicodeBytes, numUnicodeBytes), //
code, //
shortCode, //
};
shortCodeToEmoji.insert(shortCode, emojiData);
emojiToShortCode.insert(emojiData.value, shortCode);
}
this->emojiShortCodeToEmoji.insert(shortCode, 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;
this->emojiFirstByte[emojiData.value.at(0)].append(emojiData);
}
firstEmojiChars.insert(emoji.first.at(0),
QMap<QString, QString>{{emoji.second.value, emoji.second.code}});
}
*/
}
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
long lastSlice = 0;
int lastParsedEmojiEndIndex = 0;
for (auto i = 0; i < text.length() - 1; i++) {
if (!text.at(i).isLowSurrogate()) {
auto iter = firstEmojiChars.find(text.at(i));
const QChar character = text.at(i);
if (iter != firstEmojiChars.end()) {
for (auto j = std::min(8, text.length() - i); j > 0; j--) {
QString emojiString = text.mid(i, 2);
auto emojiIter = iter.value().find(emojiString);
if (emojiIter != iter.value().end()) {
QString url = "https://cdnjs.cloudflare.com/ajax/libs/"
"emojione/2.2.6/assets/png/" +
emojiIter.value() + ".png";
if (i - lastSlice != 0) {
vector.push_back(std::tuple<messages::LazyLoadedImage *, QString>(
nullptr, text.mid(lastSlice, i - lastSlice)));
if (character.isLowSurrogate()) {
continue;
}
vector.push_back(std::tuple<messages::LazyLoadedImage *, QString>(
emojis.getOrAdd(url,
[this, &url] {
return new LazyLoadedImage(
*this, this->windowManager, url, 0.35); //
}),
QString()));
auto it = this->emojiFirstByte.find(character);
if (it == this->emojiFirstByte.end()) {
// No emoji starts with this character
continue;
}
i += j - 1;
const QVector<EmojiData> possibleEmojis = it.value();
lastSlice = i + 1;
int remainingCharacters = text.length() - i;
EmojiData matchedEmoji;
int matchedEmojiLength = 0;
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 (lastSlice < text.length()) {
vector.push_back(
std::tuple<messages::LazyLoadedImage *, QString>(nullptr, text.mid(lastSlice)));
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 (lastParsedEmojiEndIndex < text.length()) {
// Add remaining characters
parsedWords.push_back(std::tuple<messages::LazyLoadedImage *, QString>(
nullptr, text.mid(lastParsedEmojiEndIndex)));
}
}

View file

@ -62,44 +62,42 @@ private:
WindowManager &windowManager;
Resources &resources;
// Emojis
// shortCodeToEmoji maps strings like ":sunglasses:" to the unicode character
QMap<QString, EmojiData> shortCodeToEmoji;
/// Emojis
// shortCodeToEmoji maps strings like "sunglasses" to its emoji
QMap<QString, EmojiData> emojiShortCodeToEmoji;
// emojiToShortCode maps the unicode character to the shortcode like ":sunglasses:"
QMap<QString, QString> emojiToShortCode;
// Maps the first character of the emoji unicode string to a vector of possible emojis
QMap<QChar, QVector<EmojiData>> emojiFirstByte;
// TODO(pajlada): Figure out what this is for
QMap<QChar, QMap<QString, QString>> firstEmojiChars;
ConcurrentMap<QString, messages::LazyLoadedImage *> emojis;
// url Emoji-one image
ConcurrentMap<QString, messages::LazyLoadedImage *> emojiCache;
void loadEmojis();
public:
void parseEmojis(std::vector<std::tuple<messages::LazyLoadedImage *, QString>> &vector,
void parseEmojis(std::vector<std::tuple<messages::LazyLoadedImage *, QString>> &parsedWords,
const QString &text);
private:
// Twitch emotes
/// Twitch emotes
ConcurrentMap<QString, twitch::EmoteValue *> _twitchEmotes;
ConcurrentMap<long, messages::LazyLoadedImage *> _twitchEmoteFromCache;
// BTTV emotes
/// BTTV emotes
ConcurrentMap<QString, messages::LazyLoadedImage *> bttvChannelEmotes;
ConcurrentMap<QString, messages::LazyLoadedImage *> _bttvEmotes;
ConcurrentMap<QString, messages::LazyLoadedImage *> _bttvChannelEmoteFromCaches;
void loadBTTVEmotes();
// FFZ emotes
/// FFZ emotes
ConcurrentMap<QString, messages::LazyLoadedImage *> ffzChannelEmotes;
ConcurrentMap<QString, messages::LazyLoadedImage *> _ffzEmotes;
ConcurrentMap<int, messages::LazyLoadedImage *> _ffzChannelEmoteFromCaches;
void loadFFZEmotes();
// Chatterino emotes
/// Chatterino emotes
ConcurrentMap<QString, messages::LazyLoadedImage *> _chatterinoEmotes;
// ???

View file

@ -1,6 +1,5 @@
#include "twitch/twitchmessagebuilder.hpp"
#include "colorscheme.hpp"
#include "emojis.hpp"
#include "emotemanager.hpp"
#include "ircmanager.hpp"
#include "resources.hpp"
@ -190,6 +189,7 @@ SharedMessage TwitchMessageBuilder::parse(const Communi::IrcPrivateMessage *ircM
// split words
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);
for (const std::tuple<LazyLoadedImage *, QString> &tuple : parsed) {