#include "providers/emoji/emojis.hpp"
namespace chatterino {
namespace providers {
namespace emoji {
void Emojis::load()
{
QFile file(":/emojidata.txt");
file.open(QFile::ReadOnly);
QTextStream in(&file);
uint unicodeBytes[4];
while (!in.atEnd()) {
// Line example: sunglasses 1f60e
QString line = in.readLine();
if (line.at(0) == '#') {
// Ignore lines starting with # (comments)
continue;
}
QStringList parts = line.split(' ');
if (parts.length() < 2) {
continue;
}
QString shortCode = parts[0];
QString code = parts[1];
QStringList unicodeCharacters = code.split('-');
if (unicodeCharacters.length() < 1) {
continue;
}
int numUnicodeBytes = 0;
for (const QString &unicodeCharacter : unicodeCharacters) {
unicodeBytes[numUnicodeBytes++] = QString(unicodeCharacter).toUInt(nullptr, 16);
}
QString unicodeString = QString::fromUcs4(unicodeBytes, numUnicodeBytes);
QString url = "https://cdnjs.cloudflare.com/ajax/libs/"
"emojione/2.2.6/assets/png/" +
code + ".png";
EmojiData emojiData{
unicodeString, //
code, //
shortCode, //
{new messages::Image(url, 0.35, unicodeString, ":" + shortCode + ":
Emoji")},
};
this->emojiShortCodeToEmoji.insert(shortCode, emojiData);
this->shortCodes.push_back(shortCode.toStdString());
this->emojiFirstByte[emojiData.value.at(0)].append(emojiData);
this->emojis.insert(code, emojiData);
}
for (auto &p : this->emojiFirstByte) {
std::stable_sort(p.begin(), p.end(), [](const auto &lhs, const auto &rhs) {
return lhs.value.length() > rhs.value.length();
});
}
}
void Emojis::parse(std::vector> &parsedWords,
const QString &text)
{
int lastParsedEmojiEndIndex = 0;
for (auto i = 0; i < text.length(); ++i) {
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 possibleEmojis = it.value();
int remainingCharacters = text.length() - i - 1;
EmojiData matchedEmoji;
int matchedEmojiLength = 0;
for (const EmojiData &emoji : possibleEmojis) {
int emojiExtraCharacters = emoji.value.length() - 1;
if (emojiExtraCharacters > remainingCharacters) {
// 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.emplace_back(util::EmoteData(), text.mid(lastParsedEmojiEndIndex,
charactersFromLastParsedEmoji));
}
// Push the emoji as a word to parsedWords
parsedWords.push_back(
std::tuple(matchedEmoji.emoteData, QString()));
lastParsedEmojiEndIndex = currentParsedEmojiEndIndex;
i += matchedEmojiLength - 1;
}
if (lastParsedEmojiEndIndex < text.length()) {
// Add remaining characters
parsedWords.emplace_back(util::EmoteData(), text.mid(lastParsedEmojiEndIndex));
}
}
QString Emojis::replaceShortCodes(const QString &text)
{
QString ret(text);
auto it = this->findShortCodesRegex.globalMatch(text);
int32_t offset = 0;
while (it.hasNext()) {
auto match = it.next();
auto capturedString = match.captured();
QString matchString = capturedString.toLower().mid(1, capturedString.size() - 2);
auto emojiIt = this->emojiShortCodeToEmoji.constFind(matchString);
if (emojiIt == this->emojiShortCodeToEmoji.constEnd()) {
continue;
}
auto emojiData = emojiIt.value();
ret.replace(offset + match.capturedStart(), match.capturedLength(), emojiData.value);
offset += emojiData.value.size() - match.capturedLength();
}
return ret;
}
} // namespace emoji
} // namespace providers
} // namespace chatterino