From a58cd3333eb7964bec4e74a2989b9ec85ad6b4a3 Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sun, 2 Jul 2017 17:37:17 +0200 Subject: [PATCH] Implement emoji parsing Fix #60 --- src/emojis.hpp | 5 ++ src/emotemanager.cpp | 122 +++++++++++++++++----------- src/emotemanager.hpp | 26 +++--- src/twitch/twitchmessagebuilder.cpp | 2 +- 4 files changed, 93 insertions(+), 62 deletions(-) diff --git a/src/emojis.hpp b/src/emojis.hpp index 9418daa3f..6a2a005c1 100644 --- a/src/emojis.hpp +++ b/src/emojis.hpp @@ -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 diff --git a/src/emotemanager.cpp b/src/emotemanager.cpp index 727dd5bb9..438b1874d 100644 --- a/src/emotemanager.cpp +++ b/src/emotemanager.cpp @@ -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); + + 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{{emoji.second.value, emoji.second.code}}); - } - */ } void EmoteManager::parseEmojis( - std::vector> &vector, const QString &text) + std::vector> &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 (character.isLowSurrogate()) { + continue; + } - if (emojiIter != iter.value().end()) { - QString url = "https://cdnjs.cloudflare.com/ajax/libs/" - "emojione/2.2.6/assets/png/" + - emojiIter.value() + ".png"; + auto it = this->emojiFirstByte.find(character); + if (it == this->emojiFirstByte.end()) { + // No emoji starts with this character + continue; + } - if (i - lastSlice != 0) { - vector.push_back(std::tuple( - nullptr, text.mid(lastSlice, i - lastSlice))); - } + const QVector possibleEmojis = it.value(); - vector.push_back(std::tuple( - emojis.getOrAdd(url, - [this, &url] { - return new LazyLoadedImage( - *this, this->windowManager, url, 0.35); // - }), - QString())); + int remainingCharacters = text.length() - i; - 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( + 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(emojiImage, QString())); + + lastParsedEmojiEndIndex = currentParsedEmojiEndIndex; + + i += matchedEmojiLength - 1; } - if (lastSlice < text.length()) { - vector.push_back( - std::tuple(nullptr, text.mid(lastSlice))); + if (lastParsedEmojiEndIndex < text.length()) { + // Add remaining characters + parsedWords.push_back(std::tuple( + nullptr, text.mid(lastParsedEmojiEndIndex))); } } diff --git a/src/emotemanager.hpp b/src/emotemanager.hpp index 3f5ee5814..686956b03 100644 --- a/src/emotemanager.hpp +++ b/src/emotemanager.hpp @@ -62,44 +62,42 @@ private: WindowManager &windowManager; Resources &resources; - // Emojis - // shortCodeToEmoji maps strings like ":sunglasses:" to the unicode character - QMap shortCodeToEmoji; + /// Emojis + // shortCodeToEmoji maps strings like "sunglasses" to its emoji + QMap emojiShortCodeToEmoji; - // emojiToShortCode maps the unicode character to the shortcode like ":sunglasses:" - QMap emojiToShortCode; + // Maps the first character of the emoji unicode string to a vector of possible emojis + QMap> emojiFirstByte; - // TODO(pajlada): Figure out what this is for - QMap> firstEmojiChars; - - ConcurrentMap emojis; + // url Emoji-one image + ConcurrentMap emojiCache; void loadEmojis(); public: - void parseEmojis(std::vector> &vector, + void parseEmojis(std::vector> &parsedWords, const QString &text); private: - // Twitch emotes + /// Twitch emotes ConcurrentMap _twitchEmotes; ConcurrentMap _twitchEmoteFromCache; - // BTTV emotes + /// BTTV emotes ConcurrentMap bttvChannelEmotes; ConcurrentMap _bttvEmotes; ConcurrentMap _bttvChannelEmoteFromCaches; void loadBTTVEmotes(); - // FFZ emotes + /// FFZ emotes ConcurrentMap ffzChannelEmotes; ConcurrentMap _ffzEmotes; ConcurrentMap _ffzChannelEmoteFromCaches; void loadFFZEmotes(); - // Chatterino emotes + /// Chatterino emotes ConcurrentMap _chatterinoEmotes; // ??? diff --git a/src/twitch/twitchmessagebuilder.cpp b/src/twitch/twitchmessagebuilder.cpp index b1d476f89..d5821903c 100644 --- a/src/twitch/twitchmessagebuilder.cpp +++ b/src/twitch/twitchmessagebuilder.cpp @@ -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> 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 &tuple : parsed) {