Skip to content

Commit

Permalink
Implement emoji parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
pajlada committed Jul 2, 2017
1 parent 17f8cc2 commit a58cd33
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 68 deletions.
5 changes: 5 additions & 0 deletions src/emojis.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
134 changes: 81 additions & 53 deletions src/emotemanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -188,74 +188,102 @@ void EmoteManager::loadEmojis()
EmojiData emojiData{
QString::fromUcs4(unicodeBytes, numUnicodeBytes), //
code, //
shortCode, //
};

shortCodeToEmoji.insert(shortCode, emojiData);
emojiToShortCode.insert(emojiData.value, shortCode);
}

/*
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->emojiShortCodeToEmoji.insert(shortCode, emojiData);

firstEmojiChars.insert(emoji.first.at(0),
QMap<QString, QString>{{emoji.second.value, emoji.second.code}});
this->emojiFirstByte[emojiData.value.at(0)].append(emojiData);
}
*/
}

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));

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)));
}

vector.push_back(std::tuple<messages::LazyLoadedImage *, QString>(
emojis.getOrAdd(url,
[this, &url] {
return new LazyLoadedImage(
*this, this->windowManager, url, 0.35); //
}),
QString()));

i += j - 1;

lastSlice = i + 1;

break;
}
const QChar character = text.at(i);

if (character.isLowSurrogate()) {
continue;
}

auto it = this->emojiFirstByte.find(character);
if (it == this->emojiFirstByte.end()) {
// No emoji starts with this character
continue;
}

const QVector<EmojiData> possibleEmojis = it.value();

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 (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()) {
vector.push_back(
std::tuple<messages::LazyLoadedImage *, QString>(nullptr, text.mid(lastSlice)));
if (lastParsedEmojiEndIndex < text.length()) {
// Add remaining characters
parsedWords.push_back(std::tuple<messages::LazyLoadedImage *, QString>(
nullptr, text.mid(lastParsedEmojiEndIndex)));
}
}

Expand Down
26 changes: 12 additions & 14 deletions src/emotemanager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,44 +62,42 @@ class EmoteManager
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;

// ???
Expand Down
2 changes: 1 addition & 1 deletion src/twitch/twitchmessagebuilder.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#include "twitch/twitchmessagebuilder.hpp"
#include "colorscheme.hpp"
#include "emojis.hpp"
#include "emotemanager.hpp"
#include "ircmanager.hpp"
#include "resources.hpp"
Expand Down Expand Up @@ -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) {
Expand Down

0 comments on commit a58cd33

Please sign in to comment.