diff --git a/Telegram/SourceFiles/chat_helpers/emoji_suggestions.cpp b/Telegram/SourceFiles/chat_helpers/emoji_suggestions.cpp deleted file mode 100644 index 9918f2336..000000000 --- a/Telegram/SourceFiles/chat_helpers/emoji_suggestions.cpp +++ /dev/null @@ -1,302 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop version of Telegram messaging app, see https://telegram.org - -Telegram Desktop is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -It is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -In addition, as a special exception, the copyright holders give permission -to link the code of portions of this program with the OpenSSL library. - -Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org -*/ -#include "chat_helpers/emoji_suggestions.h" - -namespace Ui { -namespace Emoji { -namespace { - -class Completer { -public: - Completer(const QString &query); - - QVector resolve(); - -private: - struct Result { - gsl::not_null replacement; - int wordsUsed; - }; - - static QString NormalizeQuery(const QString &query); - void addResult(gsl::not_null replacement); - bool isDuplicateOfLastResult(gsl::not_null replacement) const; - bool isBetterThanLastResult(gsl::not_null replacement) const; - void processInitialList(); - void filterInitialList(); - void initWordsTracking(); - bool matchQueryForCurrentItem(); - bool matchQueryTailStartingFrom(int position); - gsl::span findWordsStartingWith(QChar ch); - int findEqualCharsCount(int position, const QString *word); - QVector prepareResult(); - - std::vector _result; - - const QString _query; - const QChar *_queryBegin = nullptr; - int _querySize = 0; - - const std::vector> *_initialList = nullptr; - - gsl::span _currentItemWords; - int _currentItemWordsUsedCount = 0; - - class UsedWordGuard { - public: - UsedWordGuard(QVector &map, int index); - UsedWordGuard(const UsedWordGuard &other) = delete; - UsedWordGuard(UsedWordGuard &&other); - UsedWordGuard &operator=(const UsedWordGuard &other) = delete; - UsedWordGuard &operator=(UsedWordGuard &&other) = delete; - explicit operator bool() const; - ~UsedWordGuard(); - - private: - QVector &_map; - int _index = 0; - bool _guarded = false; - - }; - QVector _currentItemWordsUsedMap; - -}; - -Completer::UsedWordGuard::UsedWordGuard(QVector &map, int index) : _map(map), _index(index) { - Expects(_map.size() > _index); - if (!_map[_index]) { - _guarded = _map[_index] = true; - } -} - -Completer::UsedWordGuard::UsedWordGuard(UsedWordGuard &&other) : _map(other._map), _index(other._index), _guarded(base::take(other._guarded)) { -} - -Completer::UsedWordGuard::operator bool() const { - return _guarded; -} - -Completer::UsedWordGuard::~UsedWordGuard() { - if (_guarded) { - _map[_index] = false; - } -} - -Completer::Completer(const QString &query) : _query(NormalizeQuery(query)) { -} - -// Remove all non-letters-or-numbers. -// Leave '-' and '+' only if they're followed by a number or -// at the end of the query (so it is possibly followed by a number). -QString Completer::NormalizeQuery(const QString &query) { - auto result = query; - auto copyFrom = query.constData(); - auto e = copyFrom + query.size(); - auto copyTo = (QChar*)nullptr; - for (auto i = query.constData(); i != e; ++i) { - if (i->isLetterOrNumber()) { - continue; - } else if (*i == '-' || *i == '+') { - if (i + 1 == e || (i + 1)->isNumber()) { - continue; - } - } - if (i > copyFrom) { - if (!copyTo) copyTo = result.data(); - memcpy(copyTo, copyFrom, (i - copyFrom) * sizeof(QChar)); - copyTo += (i - copyFrom); - } - copyFrom = i + 1; - } - if (copyFrom == query.constData()) { - return query; - } else if (e > copyFrom) { - if (!copyTo) copyTo = result.data(); - memcpy(copyTo, copyFrom, (e - copyFrom) * sizeof(QChar)); - copyTo += (e - copyFrom); - } - result.chop(result.constData() + result.size() - copyTo); - return result; -} - -QVector Completer::resolve() { - _queryBegin = _query.constData(); - _querySize = _query.size(); - if (!_querySize) { - return QVector(); - } - _initialList = Ui::Emoji::GetReplacements(*_queryBegin); - if (!_initialList) { - return QVector(); - } - _result.reserve(_initialList->size()); - processInitialList(); - return prepareResult(); -} - -bool Completer::isDuplicateOfLastResult(gsl::not_null item) const { - if (_result.empty()) { - return false; - } - return (_result.back().replacement->id == item->id); -} - -bool Completer::isBetterThanLastResult(gsl::not_null item) const { - Expects(!_result.empty()); - auto &last = _result.back(); - if (_currentItemWordsUsedCount < last.wordsUsed) { - return true; - } - - auto firstCharOfQuery = _query[0]; - auto firstCharAfterColonLast = last.replacement->replacement[1]; - auto firstCharAfterColonCurrent = item->replacement[1]; - auto goodLast = (firstCharAfterColonLast == firstCharOfQuery); - auto goodCurrent = (firstCharAfterColonCurrent == firstCharOfQuery); - return !goodLast && goodCurrent; -} - -void Completer::addResult(gsl::not_null item) { - if (!isDuplicateOfLastResult(item)) { - _result.push_back({ item, _currentItemWordsUsedCount }); - } else if (isBetterThanLastResult(item)) { - _result.back() = { item, _currentItemWordsUsedCount }; - } -} - -void Completer::processInitialList() { - if (_querySize > 1) { - filterInitialList(); - } else { - _currentItemWordsUsedCount = 1; - for (auto item : *_initialList) { - addResult(item); - } - } -} - -void Completer::initWordsTracking() { - auto maxWordsCount = 0; - for (auto item : *_initialList) { - accumulate_max(maxWordsCount, item->words.size()); - } - _currentItemWordsUsedMap = QVector(maxWordsCount, false); -} - -void Completer::filterInitialList() { - initWordsTracking(); - for (auto item : *_initialList) { - _currentItemWords = gsl::make_span(item->words); - _currentItemWordsUsedCount = 1; - if (matchQueryForCurrentItem()) { - addResult(item); - } - _currentItemWordsUsedCount = 0; - } -} - -bool Completer::matchQueryForCurrentItem() { - Expects(!_currentItemWords.empty()); - if (_currentItemWords.size() < 2) { - return _currentItemWords.data()->startsWith(_query); - } - return matchQueryTailStartingFrom(0); -} - -bool Completer::matchQueryTailStartingFrom(int position) { - auto charsLeftToMatch = (_querySize - position); - if (!charsLeftToMatch) { - return true; - } - - auto firstCharToMatch = *(_queryBegin + position); - auto foundWords = findWordsStartingWith(firstCharToMatch); - - for (auto word = foundWords.data(), foundWordsEnd = word + foundWords.size(); word != foundWordsEnd; ++word) { - auto wordIndex = word - _currentItemWords.data(); - if (auto guard = UsedWordGuard(_currentItemWordsUsedMap, wordIndex)) { - ++_currentItemWordsUsedCount; - auto equalCharsCount = findEqualCharsCount(position, word); - for (auto check = equalCharsCount; check != 0; --check) { - if (matchQueryTailStartingFrom(position + check)) { - return true; - } - } - --_currentItemWordsUsedCount; - } - } - return false; -} - -int Completer::findEqualCharsCount(int position, const QString *word) { - auto charsLeft = (_querySize - position); - auto wordBegin = word->constData(); - auto wordSize = word->size(); - auto possibleEqualCharsCount = qMin(charsLeft, wordSize); - for (auto equalTill = 1; equalTill != possibleEqualCharsCount; ++equalTill) { - auto wordCh = *(wordBegin + equalTill); - auto queryCh = *(_queryBegin + position + equalTill); - if (wordCh != queryCh) { - return equalTill; - } - } - return possibleEqualCharsCount; -} - -QVector Completer::prepareResult() { - auto firstCharOfQuery = _query[0]; - std::stable_partition(_result.begin(), _result.end(), [firstCharOfQuery](Result &result) { - auto firstCharAfterColon = result.replacement->replacement[1]; - return (firstCharAfterColon == firstCharOfQuery); - }); - std::stable_partition(_result.begin(), _result.end(), [](Result &result) { - return (result.wordsUsed < 2); - }); - std::stable_partition(_result.begin(), _result.end(), [](Result &result) { - return (result.wordsUsed < 3); - }); - - auto result = QVector(); - result.reserve(_result.size()); - for (auto &item : _result) { - result.push_back({ item.replacement->id, item.replacement->label, item.replacement->replacement }); - } - return result; -} - -gsl::span Completer::findWordsStartingWith(QChar ch) { - auto begin = std::lower_bound(_currentItemWords.cbegin(), _currentItemWords.cend(), ch, [](const QString &word, QChar ch) { - return word[0] < ch; - }); - auto end = std::upper_bound(_currentItemWords.cbegin(), _currentItemWords.cend(), ch, [](QChar ch, const QString &word) { - return ch < word[0]; - }); - return _currentItemWords.subspan(begin - _currentItemWords.cbegin(), end - begin); -} - -} // namespace - -QVector GetSuggestions(const QString &query) { - return Completer(query).resolve(); -} - -} // namespace Emoji -} // namespace Ui diff --git a/Telegram/SourceFiles/chat_helpers/emoji_suggestions.h b/Telegram/SourceFiles/chat_helpers/emoji_suggestions.h deleted file mode 100644 index c57b4c255..000000000 --- a/Telegram/SourceFiles/chat_helpers/emoji_suggestions.h +++ /dev/null @@ -1,35 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop version of Telegram messaging app, see https://telegram.org - -Telegram Desktop is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -It is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -In addition, as a special exception, the copyright holders give permission -to link the code of portions of this program with the OpenSSL library. - -Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org -*/ -#pragma once - -namespace Ui { -namespace Emoji { - -struct Suggestion { - QString id; - QString label; - QString replacement; -}; - -QVector GetSuggestions(const QString &query); - -} // namespace Emoji -} // namespace Ui diff --git a/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp index 47321959c..26e3cd770 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp @@ -20,6 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "chat_helpers/emoji_suggestions_widget.h" +#include "emoji_suggestions.h" #include "ui/effects/ripple_animation.h" #include "ui/widgets/shadow.h" #include "platform/platform_specific.h" @@ -33,6 +34,14 @@ namespace { constexpr auto kRowLimit = 5; constexpr auto kLargestReplacementLength = 128; +utf16string QStringToUTF16(const QString &string) { + return utf16string(reinterpret_cast(string.constData()), string.size()); +} + +QString QStringFromUTF16(utf16string string) { + return QString::fromRawData(reinterpret_cast(string.data()), string.size()); +} + } // namespace class SuggestionsWidget::Row { @@ -93,9 +102,9 @@ void SuggestionsWidget::showWithQuery(const QString &query) { _query = query; if (!_query.isEmpty()) { rows.reserve(kRowLimit); - for (auto &item : GetSuggestions(_query)) { - if (auto emoji = Find(item.id)) { - rows.emplace_back(emoji, item.label, item.replacement); + for (auto &item : GetSuggestions(QStringToUTF16(_query))) { + if (auto emoji = Find(QStringFromUTF16(item.emoji()))) { + rows.emplace_back(emoji, QStringFromUTF16(item.label()), QStringFromUTF16(item.replacement())); if (rows.size() == kRowLimit) { break; } diff --git a/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.h b/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.h index 24500b4f5..23b82c437 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.h +++ b/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.h @@ -20,7 +20,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #pragma once -#include "chat_helpers/emoji_suggestions.h" #include "ui/effects/panel_animation.h" namespace Ui { diff --git a/Telegram/SourceFiles/codegen/emoji/data.cpp b/Telegram/SourceFiles/codegen/emoji/data.cpp index d71966d81..07557a0d3 100644 --- a/Telegram/SourceFiles/codegen/emoji/data.cpp +++ b/Telegram/SourceFiles/codegen/emoji/data.cpp @@ -49,16 +49,16 @@ Replace Replaces[] = { { { 0xD83DDE0AU }, ":-)" }, { { 0xD83DDE0DU }, "8-)" }, { { 0x2764U }, "<3" }, - { { 0xD83DDC8BU }, ":kiss:" }, - { { 0xD83DDE01U }, ":grin:" }, - { { 0xD83DDE02U }, ":joy:" }, +// { { 0xD83DDC8BU }, ":kiss:" }, +// { { 0xD83DDE01U }, ":grin:" }, +// { { 0xD83DDE02U }, ":joy:" }, { { 0xD83DDE1AU }, ":-*" }, { { 0xD83DDE06U }, "xD" }, - { { 0xD83DDC4DU }, ":like:" }, - { { 0xD83DDC4EU }, ":dislike:" }, - { { 0x261DU }, ":up:" }, - { { 0x270CU }, ":v:" }, - { { 0xD83DDC4CU }, ":ok:" }, +// { { 0xD83DDC4DU }, ":like:" }, +// { { 0xD83DDC4EU }, ":dislike:" }, +// { { 0x261DU }, ":up:" }, +// { { 0x270CU }, ":v:" }, +// { { 0xD83DDC4CU }, ":ok:" }, { { 0xD83DDE0EU }, "B-)" }, { { 0xD83DDE03U }, ":-D" }, { { 0xD83DDE09U }, ";-)" }, diff --git a/Telegram/SourceFiles/codegen/emoji/generator.cpp b/Telegram/SourceFiles/codegen/emoji/generator.cpp index 6ecaafe3b..55c0f3a1f 100644 --- a/Telegram/SourceFiles/codegen/emoji/generator.cpp +++ b/Telegram/SourceFiles/codegen/emoji/generator.cpp @@ -123,6 +123,45 @@ QRect computeSourceRect(const QImage &image) { return result; } +uint32 Crc32Table[256]; +class Crc32Initializer { +public: + Crc32Initializer() { + uint32 poly = 0x04C11DB7U; + for (auto i = 0; i != 256; ++i) { + Crc32Table[i] = reflect(i, 8) << 24; + for (auto j = 0; j != 8; ++j) { + Crc32Table[i] = (Crc32Table[i] << 1) ^ (Crc32Table[i] & (1 << 31) ? poly : 0); + } + Crc32Table[i] = reflect(Crc32Table[i], 32); + } + } + +private: + uint32 reflect(uint32 val, char ch) { + uint32 result = 0; + for (int i = 1; i < (ch + 1); ++i) { + if (val & 1) { + result |= 1 << (ch - i); + } + val >>= 1; + } + return result; + } + +}; + +uint32 countCrc32(const void *data, std::size_t size) { + static Crc32Initializer InitTable; + + auto buffer = static_cast(data); + auto result = uint32(0xFFFFFFFFU); + for (auto i = std::size_t(0); i != size; ++i) { + result = (result >> 8) ^ Crc32Table[(result & 0xFFU) ^ buffer[i]]; + } + return (result ^ 0xFFFFFFFFU); +} + } // namespace Generator::Generator(const Options &options) : project_(Project) @@ -142,6 +181,7 @@ Generator::Generator(const Options &options) : project_(Project) outputPath_ = dir.absolutePath() + "/emoji"; spritePath_ = dir.absolutePath() + "/emoji"; + suggestionsPath_ = dir.absolutePath() + "/emoji_suggestions_data"; } int Generator::generate() { @@ -161,6 +201,12 @@ int Generator::generate() { if (!writeHeader()) { return -1; } + if (!writeSuggestionsSource()) { + return -1; + } + if (!writeSuggestionsHeader()) { + return -1; + } return 0; } @@ -272,6 +318,7 @@ bool Generator::writeImages() { bool Generator::writeSource() { source_ = std::make_unique(outputPath_ + ".cpp", project_); + source_->include("emoji_suggestions_data.h").newline(); source_->pushNamespace("Ui").pushNamespace("Emoji").pushNamespace(); source_->stream() << "\ \n\ @@ -283,9 +330,6 @@ std::vector Items;\n\ if (!writeSections()) { return false; } - if (!writeReplacements()) { - return false; - } if (!writeFindReplace()) { return false; } @@ -329,9 +373,6 @@ void Init() {\n\ if (!writeGetSections()) { return false; } - if (!writeGetReplacements()) { - return false; - } return source_->finalize(); } @@ -380,15 +421,6 @@ int Index();\n\ \n\ int GetSectionCount(Section section);\n\ EmojiPack GetSection(Section section);\n\ -\n\ -struct Replacement {\n\ - QString id;\n\ - QString replacement;\n\ - QString label;\n\ - QVector words;\n\ -};\n\ -\n\ -const std::vector> *GetReplacements(QChar first);\n\ \n"; return header->finalize(); } @@ -447,7 +479,7 @@ struct DataStruct {\n\ const ushort IdData[] = {"; startBinary(); if (!enumerateWholeList([this](Id id, int column, int row, bool isPostfixed, bool isVariated, bool isColored, int original) { - return writeStringBinary(id); + return writeStringBinary(source_.get(), id); })) { return false; } @@ -490,7 +522,7 @@ const ushort SectionData[] = {"; startBinary(); for (auto &category : data_.categories) { for (auto index : category) { - writeIntBinary(index); + writeIntBinary(source_.get(), index); } } source_->stream() << " };\n\ @@ -506,115 +538,6 @@ EmojiPack FillSection(int offset, int size) {\n\ return true; } -bool Generator::writeReplacements() { - QMap> byCharIndices; - source_->stream() << "\ -struct ReplacementStruct {\n\ - uchar idSize;\n\ - uchar replacementSize;\n\ - uchar wordsCount;\n\ -};\n\ -\n\ -const ushort ReplacementData[] = {"; - startBinary(); - for (auto i = 0, size = replaces_.list.size(); i != size; ++i) { - auto &replace = replaces_.list[i]; - if (!writeStringBinary(replace.id)) { - return false; - } - if (!writeStringBinary(replace.replacement)) { - return false; - } - for (auto &word : replace.words) { - if (!writeStringBinary(word)) { - return false; - } - auto &index = byCharIndices[word[0]]; - if (index.isEmpty() || index.back() != i) { - index.push_back(i); - } - } - } - source_->stream() << " };\n\ -\n\ -const uchar ReplacementWordLengths[] = {"; - startBinary(); - for (auto &replace : replaces_.list) { - auto wordLengths = QStringList(); - for (auto &word : replace.words) { - writeIntBinary(word.size()); - } - } - source_->stream() << " };\n\ -\n\ -const ReplacementStruct ReplacementInitData[] = {\n"; - for (auto &replace : replaces_.list) { - source_->stream() << "\ - { uchar(" << replace.id.size() << "), uchar(" << replace.replacement.size() << "), uchar(" << replace.words.size() << ") },\n"; - } - source_->stream() << "};\n\ -\n\ -const ushort ReplacementIndices[] = {"; - startBinary(); - for (auto &byCharIndex : byCharIndices) { - for (auto index : byCharIndex) { - writeIntBinary(index); - } - } - source_->stream() << " };\n\ -\n\ -struct ReplacementIndexStruct {\n\ - ushort ch;\n\ - ushort count;\n\ -};\n\ -\n\ -const ReplacementIndexStruct ReplacementIndexData[] = {\n"; - startBinary(); - for (auto i = byCharIndices.cbegin(), e = byCharIndices.cend(); i != e; ++i) { - source_->stream() << "\ - { ushort(" << i.key().unicode() << "), ushort(" << i.value().size() << ") },\n"; - } - source_->stream() << "};\n\ -\n\ -std::vector Replacements;\n\ -std::map>> ReplacementsMap;\n\ -\n\ -void InitReplacements() {\n\ - auto data = ReplacementData;\n\ - auto takeString = [&data](int size) {\n\ - auto result = QString::fromRawData(reinterpret_cast(data), size);\n\ - data += size;\n\ - return result;\n\ - };\n\ - auto wordSize = ReplacementWordLengths;\n\ -\n\ - Replacements.reserve(base::array_size(ReplacementInitData));\n\ - for (auto item : ReplacementInitData) {\n\ - auto id = takeString(item.idSize);\n\ - auto replacement = takeString(item.replacementSize);\n\ - auto label = replacement;\n\ - auto words = QVector();\n\ - words.reserve(item.wordsCount);\n\ - for (auto i = 0; i != item.wordsCount; ++i) {\n\ - words.push_back(takeString(*wordSize++));\n\ - }\n\ - Replacements.push_back({ std::move(id), std::move(replacement), std::move(label), std::move(words) });\n\ - }\n\ -\n\ - auto indices = ReplacementIndices;\n\ - auto items = &Replacements[0];\n\ - for (auto item : ReplacementIndexData) {\n\ - auto index = std::vector>();\n\ - index.reserve(item.count);\n\ - for (auto i = 0; i != item.count; ++i) {\n\ - index.push_back(items + (*indices++));\n\ - }\n\ - ReplacementsMap.emplace(item.ch, std::move(index));\n\ - }\n\ -}\n"; - return true; -} - bool Generator::writeGetSections() { constexpr const char *sectionNames[] = { "Section::People", @@ -833,12 +756,191 @@ bool Generator::writeFindFromDictionary(const std::map(suggestionsPath_ + ".cpp", project_); + suggestionsSource_->stream() << "\ +#include \n\ +\n"; + suggestionsSource_->pushNamespace("Ui").pushNamespace("Emoji").pushNamespace("internal").pushNamespace(); + suggestionsSource_->stream() << "\ +\n"; + if (!writeReplacements()) { + return false; + } + suggestionsSource_->popNamespace().newline(); + if (!writeGetReplacements()) { + return false; + } + + return suggestionsSource_->finalize(); +} + +bool Generator::writeSuggestionsHeader() { + auto maxLength = 0; + for (auto &replace : replaces_.list) { + if (maxLength < replace.replacement.size()) { + maxLength = replace.replacement.size(); + } + } + auto header = std::make_unique(suggestionsPath_ + ".h", project_); + header->include("emoji_suggestions.h").newline(); + header->pushNamespace("Ui").pushNamespace("Emoji").pushNamespace("internal"); + header->stream() << "\ +\n\ +struct Replacement {\n\ + utf16string emoji;\n\ + utf16string replacement;\n\ + std::vector words;\n\ +};\n\ +\n\ +constexpr auto kReplacementMaxLength = " << maxLength << ";\n\ +\n\ +void InitReplacements();\n\ +const std::vector *GetReplacements(utf16char first);\n\ +utf16string GetReplacementEmoji(utf16string replacement);\n\ +\n"; + return header->finalize(); +} + +bool Generator::writeReplacements() { + QMap> byCharIndices; + suggestionsSource_->stream() << "\ +struct ReplacementStruct {\n\ + small emojiSize;\n\ + small replacementSize;\n\ + small wordsCount;\n\ +};\n\ +\n\ +const utf16char ReplacementData[] = {"; + startBinary(); + for (auto i = 0, size = replaces_.list.size(); i != size; ++i) { + auto &replace = replaces_.list[i]; + if (!writeStringBinary(suggestionsSource_.get(), replace.id)) { + return false; + } + if (!writeStringBinary(suggestionsSource_.get(), replace.replacement)) { + return false; + } + for (auto &word : replace.words) { + if (!writeStringBinary(suggestionsSource_.get(), word)) { + return false; + } + auto &index = byCharIndices[word[0]]; + if (index.isEmpty() || index.back() != i) { + index.push_back(i); + } + } + } + suggestionsSource_->stream() << " };\n\ +\n\ +const small ReplacementWordLengths[] = {"; + startBinary(); + for (auto &replace : replaces_.list) { + auto wordLengths = QStringList(); + for (auto &word : replace.words) { + writeIntBinary(suggestionsSource_.get(), word.size()); + } + } + suggestionsSource_->stream() << " };\n\ +\n\ +const ReplacementStruct ReplacementInitData[] = {\n"; + for (auto &replace : replaces_.list) { + suggestionsSource_->stream() << "\ + { small(" << replace.id.size() << "), small(" << replace.replacement.size() << "), small(" << replace.words.size() << ") },\n"; + } + suggestionsSource_->stream() << "};\n\ +\n\ +const medium ReplacementIndices[] = {"; + startBinary(); + for (auto &byCharIndex : byCharIndices) { + for (auto index : byCharIndex) { + writeIntBinary(suggestionsSource_.get(), index); + } + } + suggestionsSource_->stream() << " };\n\ +\n\ +struct ReplacementIndexStruct {\n\ + utf16char ch;\n\ + medium count;\n\ +};\n\ +\n\ +const internal::checksum ReplacementChecksums[] = {\n"; + startBinary(); + for (auto &replace : replaces_.list) { + writeUintBinary(suggestionsSource_.get(), countCrc32(replace.replacement.constData(), replace.replacement.size() * sizeof(QChar))); + } + suggestionsSource_->stream() << " };\n\ +\n\ +const ReplacementIndexStruct ReplacementIndexData[] = {\n"; + startBinary(); + for (auto i = byCharIndices.cbegin(), e = byCharIndices.cend(); i != e; ++i) { + suggestionsSource_->stream() << "\ + { utf16char(" << i.key().unicode() << "), medium(" << i.value().size() << ") },\n"; + } + suggestionsSource_->stream() << "};\n\ +\n\ +std::vector Replacements;\n\ +std::map> ReplacementsMap;\n\ +std::map ReplacementsHash;\n\ +\n"; + return true; +} + bool Generator::writeGetReplacements() { - source_->stream() << "\ -const std::vector> *GetReplacements(QChar first) {\n\ - auto it = ReplacementsMap.find(first.unicode());\n\ + suggestionsSource_->stream() << "\ +void InitReplacements() {\n\ + if (!Replacements.empty()) {\n\ + return;\n\ + }\n\ + auto data = ReplacementData;\n\ + auto takeString = [&data](int size) {\n\ + auto result = utf16string(data, size);\n\ + data += size;\n\ + return result;\n\ + };\n\ + auto wordSize = ReplacementWordLengths;\n\ +\n\ + Replacements.reserve(" << replaces_.list.size() << ");\n\ + for (auto item : ReplacementInitData) {\n\ + auto emoji = takeString(item.emojiSize);\n\ + auto replacement = takeString(item.replacementSize);\n\ + auto words = std::vector();\n\ + words.reserve(item.wordsCount);\n\ + for (auto i = 0; i != item.wordsCount; ++i) {\n\ + words.push_back(takeString(*wordSize++));\n\ + }\n\ + Replacements.push_back({ std::move(emoji), std::move(replacement), std::move(words) });\n\ + }\n\ +\n\ + auto indices = ReplacementIndices;\n\ + auto items = &Replacements[0];\n\ + for (auto item : ReplacementIndexData) {\n\ + auto index = std::vector();\n\ + index.reserve(item.count);\n\ + for (auto i = 0; i != item.count; ++i) {\n\ + index.push_back(items + (*indices++));\n\ + }\n\ + ReplacementsMap.emplace(item.ch, std::move(index));\n\ + }\n\ +\n\ + for (auto checksum : ReplacementChecksums) {\n\ + ReplacementsHash.emplace(checksum, items++);\n\ + }\n\ +}\n\ +\n\ +const std::vector *GetReplacements(utf16char first) {\n\ + if (ReplacementsMap.empty()) {\n\ + InitReplacements();\n\ + }\n\ + auto it = ReplacementsMap.find(first);\n\ return (it == ReplacementsMap.cend()) ? nullptr : &it->second;\n\ }\n\ +\n\ +utf16string GetReplacementEmoji(utf16string replacement) {\n\ + auto code = internal::countChecksum(replacement.data(), replacement.size() * sizeof(utf16char));\n\ + auto it = ReplacementsHash.find(code);\n\ + return (it == ReplacementsHash.cend()) ? utf16string() : it->second->emoji;\n\ +}\n\ \n"; return true; } @@ -847,38 +949,52 @@ void Generator::startBinary() { _binaryFullLength = _binaryCount = 0; } -bool Generator::writeStringBinary(const QString &string) { +bool Generator::writeStringBinary(common::CppFile *source, const QString &string) { if (string.size() >= 256) { logDataError() << "Too long string: " << string.toStdString(); return false; } for (auto ch : string) { - if (_binaryFullLength > 0) source_->stream() << ","; + if (_binaryFullLength > 0) source->stream() << ","; if (!_binaryCount++) { - source_->stream() << "\n"; + source->stream() << "\n"; } else { if (_binaryCount == 12) { _binaryCount = 0; } - source_->stream() << " "; + source->stream() << " "; } - source_->stream() << "0x" << QString::number(ch.unicode(), 16); + source->stream() << "0x" << QString::number(ch.unicode(), 16); ++_binaryFullLength; } return true; } -void Generator::writeIntBinary(int data) { - if (_binaryFullLength > 0) source_->stream() << ","; +void Generator::writeIntBinary(common::CppFile *source, int data) { + if (_binaryFullLength > 0) source->stream() << ","; if (!_binaryCount++) { - source_->stream() << "\n"; + source->stream() << "\n"; } else { if (_binaryCount == 12) { _binaryCount = 0; } - source_->stream() << " "; + source->stream() << " "; } - source_->stream() << data; + source->stream() << data; + ++_binaryFullLength; +} + +void Generator::writeUintBinary(common::CppFile *source, uint32 data) { + if (_binaryFullLength > 0) source->stream() << ","; + if (!_binaryCount++) { + source->stream() << "\n"; + } else { + if (_binaryCount == 12) { + _binaryCount = 0; + } + source->stream() << " "; + } + source->stream() << "0x" << QString::number(data, 16).toUpper() << "U"; ++_binaryFullLength; } diff --git a/Telegram/SourceFiles/codegen/emoji/generator.h b/Telegram/SourceFiles/codegen/emoji/generator.h index a963855f8..974ad998e 100644 --- a/Telegram/SourceFiles/codegen/emoji/generator.h +++ b/Telegram/SourceFiles/codegen/emoji/generator.h @@ -31,6 +31,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org namespace codegen { namespace emoji { +using uint32 = unsigned int; + class Generator { public: Generator(const Options &options); @@ -47,6 +49,8 @@ private: bool writeSource(); bool writeHeader(); + bool writeSuggestionsSource(); + bool writeSuggestionsHeader(); template bool enumerateWholeList(Callback callback); @@ -60,8 +64,9 @@ private: bool writeFindFromDictionary(const std::map> &dictionary, bool skipPostfixes = false); bool writeGetReplacements(); void startBinary(); - bool writeStringBinary(const QString &string); - void writeIntBinary(int data); + bool writeStringBinary(common::CppFile *source, const QString &string); + void writeIntBinary(common::CppFile *source, int data); + void writeUintBinary(common::CppFile *source, uint32 data); const common::ProjectInfo &project_; int colorsCount_ = 0; @@ -72,6 +77,9 @@ private: QString spritePath_; std::unique_ptr source_; Data data_; + + QString suggestionsPath_; + std::unique_ptr suggestionsSource_; Replaces replaces_; int _binaryFullLength = 0; diff --git a/Telegram/SourceFiles/codegen/emoji/replaces.cpp b/Telegram/SourceFiles/codegen/emoji/replaces.cpp index 2b43bb4ef..dedba9de1 100644 --- a/Telegram/SourceFiles/codegen/emoji/replaces.cpp +++ b/Telegram/SourceFiles/codegen/emoji/replaces.cpp @@ -281,9 +281,6 @@ QString ConvertEmojiId(const Id &id, const QString &replacement) { if (NotSupported.contains(id)) { return QString(); } - if (QRegularExpression("_tone").match(replacement).hasMatch()) { - int a = 0; - } return ConvertMap.value(id, id); } @@ -403,15 +400,20 @@ bool CheckAndConvertReplaces(Replaces &replaces, const Data &data) { auto findId = [&data](const Id &id) { return data.map.find(id) != data.map.cend(); }; - auto findAndSort = [findId, &sorted](Id id, const Replace &replace) { + auto findAndSort = [findId, &data, &sorted](Id id, const Replace &replace) { if (!findId(id)) { id.replace(QChar(0xFE0F), QString()); if (!findId(id)) { return false; } } - auto it = sorted.insertMulti(id, replace); - it.value().id = id; + auto it = data.map.find(id); + id = data.list[it->second].id; + if (data.list[it->second].postfixed) { + id += QChar(kPostfix); + } + auto inserted = sorted.insertMulti(id, replace); + inserted.value().id = id; return true; }; @@ -453,17 +455,10 @@ bool CheckAndConvertReplaces(Replaces &replaces, const Data &data) { for (auto &category : data.categories) { for (auto index : category) { auto id = data.list[index].id; - auto found = false; - for (auto it = sorted.find(id); it != sorted.cend(); sorted.erase(it), it = sorted.find(id)) { - found = true; - result.list.push_back(it.value()); + if (data.list[index].postfixed) { + id += QChar(kPostfix); } - id.replace(QChar(0xFE0F), QString()); for (auto it = sorted.find(id); it != sorted.cend(); sorted.erase(it), it = sorted.find(id)) { - if (found) { - logReplacesError(replaces.filename) << "Strange emoji, found in both ways: " << it->replacement.toStdString(); - return false; - } result.list.push_back(it.value()); } } diff --git a/Telegram/ThirdParty/emoji_suggestions/emoji_suggestions.cpp b/Telegram/ThirdParty/emoji_suggestions/emoji_suggestions.cpp new file mode 100644 index 000000000..b0b7d4021 --- /dev/null +++ b/Telegram/ThirdParty/emoji_suggestions/emoji_suggestions.cpp @@ -0,0 +1,432 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org +*/ +#include "emoji_suggestions.h" + +#include +#include "emoji_suggestions_data.h" + +#ifndef Expects +#include +#define Expects(condition) assert(condition) +#endif // Expects + +namespace Ui { +namespace Emoji { +namespace internal { +namespace { + +checksum Crc32Table[256]; +class Crc32Initializer { +public: + Crc32Initializer() { + checksum poly = 0x04C11DB7U; + for (auto i = 0; i != 256; ++i) { + Crc32Table[i] = reflect(i, 8) << 24; + for (auto j = 0; j != 8; ++j) { + Crc32Table[i] = (Crc32Table[i] << 1) ^ (Crc32Table[i] & (1 << 31) ? poly : 0); + } + Crc32Table[i] = reflect(Crc32Table[i], 32); + } + } + +private: + checksum reflect(checksum val, char ch) { + checksum result = 0; + for (int i = 1; i < (ch + 1); ++i) { + if (val & 1) { + result |= 1 << (ch - i); + } + val >>= 1; + } + return result; + } + +}; + +} // namespace + +checksum countChecksum(const void *data, std::size_t size) { + static Crc32Initializer InitTable; + + auto buffer = static_cast(data); + auto result = checksum(0xFFFFFFFFU); + for (auto i = std::size_t(0); i != size; ++i) { + result = (result >> 8) ^ Crc32Table[(result & 0xFFU) ^ buffer[i]]; + } + return (result ^ 0xFFFFFFFFU); +} + +} // namespace internal + +namespace { + +class string_span { +public: + string_span() = default; + string_span(const utf16string *data, std::size_t size) : begin_(data), size_(size) { + } + string_span(const std::vector &data) : begin_(data.data()), size_(data.size()) { + } + string_span(const string_span &other) = default; + string_span &operator=(const string_span &other) = default; + + const utf16string *begin() const { + return begin_; + } + const utf16string *end() const { + return begin_ + size_; + } + std::size_t size() const { + return size_; + } + + string_span subspan(std::size_t offset, std::size_t size) { + return string_span(begin_ + offset, size); + } + +private: + const utf16string *begin_ = nullptr; + std::size_t size_ = 0; + +}; + +bool IsNumber(utf16char ch) { + return (ch >= '0' && ch <= '9'); +} + +bool IsLetterOrNumber(utf16char ch) { + return (ch >= 'a' && ch <= 'z') || IsNumber(ch); +} + +using Replacement = internal::Replacement; + +class Completer { +public: + Completer(utf16string query); + + std::vector resolve(); + +private: + struct Result { + const Replacement *replacement; + int wordsUsed; + }; + + static std::vector NormalizeQuery(utf16string query); + void addResult(const Replacement *replacement); + bool isDuplicateOfLastResult(const Replacement *replacement) const; + bool isBetterThanLastResult(const Replacement *replacement) const; + void processInitialList(); + void filterInitialList(); + void initWordsTracking(); + bool matchQueryForCurrentItem(); + bool matchQueryTailStartingFrom(int position); + string_span findWordsStartingWith(utf16char ch); + int findEqualCharsCount(int position, const utf16string *word); + std::vector prepareResult(); + bool startsWithQuery(utf16string word); + bool isExactMatch(utf16string replacement); + + std::vector _result; + + utf16string _initialQuery; + const std::vector _query; + const utf16char *_queryBegin = nullptr; + int _querySize = 0; + + const std::vector *_initialList = nullptr; + + string_span _currentItemWords; + int _currentItemWordsUsedCount = 0; + + class UsedWordGuard { + public: + UsedWordGuard(std::vector &map, int index); + UsedWordGuard(const UsedWordGuard &other) = delete; + UsedWordGuard(UsedWordGuard &&other); + UsedWordGuard &operator=(const UsedWordGuard &other) = delete; + UsedWordGuard &operator=(UsedWordGuard &&other) = delete; + explicit operator bool() const; + ~UsedWordGuard(); + + private: + std::vector &_map; + int _index = 0; + bool _guarded = false; + + }; + std::vector _currentItemWordsUsedMap; + +}; + +Completer::UsedWordGuard::UsedWordGuard(std::vector &map, int index) : _map(map), _index(index) { + Expects(_map.size() > _index); + if (!_map[_index]) { + _guarded = _map[_index] = 1; + } +} + +Completer::UsedWordGuard::UsedWordGuard(UsedWordGuard &&other) : _map(other._map), _index(other._index), _guarded(other._guarded) { + other._guarded = 0; +} + +Completer::UsedWordGuard::operator bool() const { + return _guarded; +} + +Completer::UsedWordGuard::~UsedWordGuard() { + if (_guarded) { + _map[_index] = 0; + } +} + +Completer::Completer(utf16string query) : _initialQuery(query), _query(NormalizeQuery(query)) { +} + +// Remove all non-letters-or-numbers. +// Leave '-' and '+' only if they're followed by a number or +// at the end of the query (so it is possibly followed by a number). +std::vector Completer::NormalizeQuery(utf16string query) { + auto result = std::vector(); + result.reserve(query.size()); + auto copyFrom = query.data(); + auto e = copyFrom + query.size(); + auto copyTo = result.data(); + for (auto i = query.data(); i != e; ++i) { + if (IsLetterOrNumber(*i)) { + continue; + } else if (*i == '-' || *i == '+') { + if (i + 1 == e || IsNumber(*(i + 1))) { + continue; + } + } + if (i > copyFrom) { + result.resize(result.size() + (i - copyFrom)); + memcpy(copyTo, copyFrom, (i - copyFrom) * sizeof(utf16char)); + copyTo += (i - copyFrom); + } + copyFrom = i + 1; + } + if (e > copyFrom) { + result.resize(result.size() + (e - copyFrom)); + memcpy(copyTo, copyFrom, (e - copyFrom) * sizeof(utf16char)); + copyTo += (e - copyFrom); + } + return result; +} + +std::vector Completer::resolve() { + _queryBegin = _query.data(); + _querySize = _query.size(); + if (!_querySize) { + return std::vector(); + } + _initialList = Ui::Emoji::internal::GetReplacements(*_queryBegin); + if (!_initialList) { + return std::vector(); + } + _result.reserve(_initialList->size()); + processInitialList(); + return prepareResult(); +} + +bool Completer::isDuplicateOfLastResult(const Replacement *item) const { + if (_result.empty()) { + return false; + } + return (_result.back().replacement->emoji == item->emoji); +} + +bool Completer::isBetterThanLastResult(const Replacement *item) const { + Expects(!_result.empty()); + auto &last = _result.back(); + if (_currentItemWordsUsedCount < last.wordsUsed) { + return true; + } + + auto firstCharOfQuery = _query[0]; + auto firstCharAfterColonLast = last.replacement->replacement[1]; + auto firstCharAfterColonCurrent = item->replacement[1]; + auto goodLast = (firstCharAfterColonLast == firstCharOfQuery); + auto goodCurrent = (firstCharAfterColonCurrent == firstCharOfQuery); + return !goodLast && goodCurrent; +} + +void Completer::addResult(const Replacement *item) { + if (!isDuplicateOfLastResult(item)) { + _result.push_back({ item, _currentItemWordsUsedCount }); + } else if (isBetterThanLastResult(item)) { + _result.back() = { item, _currentItemWordsUsedCount }; + } +} + +void Completer::processInitialList() { + if (_querySize > 1) { + filterInitialList(); + } else { + _currentItemWordsUsedCount = 1; + for (auto item : *_initialList) { + addResult(item); + } + } +} + +void Completer::initWordsTracking() { + auto maxWordsCount = 0; + for (auto item : *_initialList) { + auto wordsCount = item->words.size(); + if (maxWordsCount < wordsCount) { + maxWordsCount = wordsCount; + } + } + _currentItemWordsUsedMap = std::vector(maxWordsCount, 0); +} + +void Completer::filterInitialList() { + initWordsTracking(); + for (auto item : *_initialList) { + _currentItemWords = string_span(item->words); + _currentItemWordsUsedCount = 1; + if (matchQueryForCurrentItem()) { + addResult(item); + } + _currentItemWordsUsedCount = 0; + } +} + +bool Completer::matchQueryForCurrentItem() { + Expects(_currentItemWords.size() != 0); + if (_currentItemWords.size() < 2) { + return startsWithQuery(*_currentItemWords.begin()); + } + return matchQueryTailStartingFrom(0); +} + +bool Completer::startsWithQuery(utf16string word) { + if (word.size() < _query.size()) { + return false; + } + for (auto i = std::size_t(0), size = _query.size(); i != size; ++i) { + if (word[i] != _query[i]) { + return false; + } + } + return true; +} + +bool Completer::isExactMatch(utf16string replacement) { + if (replacement.size() != _initialQuery.size() + 1) { + return false; + } + for (auto i = std::size_t(0), size = _initialQuery.size(); i != size; ++i) { + if (replacement[i] != _initialQuery[i]) { + return false; + } + } + return true; +} + +bool Completer::matchQueryTailStartingFrom(int position) { + auto charsLeftToMatch = (_querySize - position); + if (!charsLeftToMatch) { + return true; + } + + auto firstCharToMatch = *(_queryBegin + position); + auto foundWords = findWordsStartingWith(firstCharToMatch); + + for (auto word = foundWords.begin(), foundWordsEnd = word + foundWords.size(); word != foundWordsEnd; ++word) { + auto wordIndex = word - _currentItemWords.begin(); + if (auto guard = UsedWordGuard(_currentItemWordsUsedMap, wordIndex)) { + ++_currentItemWordsUsedCount; + auto equalCharsCount = findEqualCharsCount(position, word); + for (auto check = equalCharsCount; check != 0; --check) { + if (matchQueryTailStartingFrom(position + check)) { + return true; + } + } + --_currentItemWordsUsedCount; + } + } + return false; +} + +int Completer::findEqualCharsCount(int position, const utf16string *word) { + auto charsLeft = (_querySize - position); + auto wordBegin = word->data(); + auto wordSize = word->size(); + auto possibleEqualCharsCount = (charsLeft > wordSize ? wordSize : charsLeft); + for (auto equalTill = 1; equalTill != possibleEqualCharsCount; ++equalTill) { + auto wordCh = *(wordBegin + equalTill); + auto queryCh = *(_queryBegin + position + equalTill); + if (wordCh != queryCh) { + return equalTill; + } + } + return possibleEqualCharsCount; +} + +std::vector Completer::prepareResult() { + auto firstCharOfQuery = _query[0]; + std::stable_partition(_result.begin(), _result.end(), [firstCharOfQuery](Result &result) { + auto firstCharAfterColon = result.replacement->replacement[1]; + return (firstCharAfterColon == firstCharOfQuery); + }); + std::stable_partition(_result.begin(), _result.end(), [](Result &result) { + return (result.wordsUsed < 2); + }); + std::stable_partition(_result.begin(), _result.end(), [](Result &result) { + return (result.wordsUsed < 3); + }); + std::stable_partition(_result.begin(), _result.end(), [this](Result &result) { + return isExactMatch(result.replacement->replacement); + }); + + auto result = std::vector(); + result.reserve(_result.size()); + for (auto &item : _result) { + result.emplace_back(item.replacement->emoji, item.replacement->replacement, item.replacement->replacement); + } + return result; +} + +string_span Completer::findWordsStartingWith(utf16char ch) { + auto begin = std::lower_bound(_currentItemWords.begin(), _currentItemWords.end(), ch, [](utf16string word, utf16char ch) { + return word[0] < ch; + }); + auto end = std::upper_bound(_currentItemWords.begin(), _currentItemWords.end(), ch, [](utf16char ch, utf16string word) { + return ch < word[0]; + }); + return _currentItemWords.subspan(begin - _currentItemWords.begin(), end - begin); +} + +} // namespace + +std::vector GetSuggestions(utf16string query) { + return Completer(query).resolve(); +} + +int GetSuggestionMaxLength() { + return internal::kReplacementMaxLength; +} + +} // namespace Emoji +} // namespace Ui diff --git a/Telegram/ThirdParty/emoji_suggestions/emoji_suggestions.h b/Telegram/ThirdParty/emoji_suggestions/emoji_suggestions.h new file mode 100644 index 000000000..8a593d1d9 --- /dev/null +++ b/Telegram/ThirdParty/emoji_suggestions/emoji_suggestions.h @@ -0,0 +1,106 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include + +namespace Ui { +namespace Emoji { + +using small = unsigned char; +using medium = unsigned short; +using utf16char = unsigned short; + +static_assert(sizeof(utf16char) == 2, "Bad UTF-16 character size."); + +class utf16string { +public: + utf16string() = default; + utf16string(const utf16char *data, std::size_t size) : data_(data), size_(size) { + } + utf16string(const utf16string &other) = default; + utf16string &operator=(const utf16string &other) = default; + + const utf16char *data() const { + return data_; + } + std::size_t size() const { + return size_; + } + + utf16char operator[](int index) const { + return data_[index]; + } + +private: + const utf16char *data_ = nullptr; + std::size_t size_ = 0; + +}; + +inline bool operator==(utf16string a, utf16string b) { + return (a.size() == b.size()) && (!a.size() || !memcmp(a.data(), b.data(), a.size() * sizeof(utf16char))); +} + +namespace internal { + +using checksum = unsigned int; +checksum countChecksum(const void *data, std::size_t size); + +utf16string GetReplacementEmoji(utf16string replacement); + +} // namespace internal + +class Suggestion { +public: + Suggestion() = default; + Suggestion(utf16string emoji, utf16string label, utf16string replacement) : emoji_(emoji), label_(label), replacement_(replacement) { + } + Suggestion(const Suggestion &other) = default; + Suggestion &operator=(const Suggestion &other) = default; + + utf16string emoji() const { + return emoji_; + } + utf16string label() const { + return label_; + } + utf16string replacement() const { + return replacement_; + } + +private: + utf16string emoji_; + utf16string label_; + utf16string replacement_; + +}; + +std::vector GetSuggestions(utf16string query); + +inline utf16string GetSuggestionEmoji(utf16string replacement) { + return internal::GetReplacementEmoji(replacement); +} + +int GetSuggestionMaxLength(); + +} // namespace Emoji +} // namespace Ui diff --git a/Telegram/gyp/Telegram.gyp b/Telegram/gyp/Telegram.gyp index 80160d161..9d92b817d 100644 --- a/Telegram/gyp/Telegram.gyp +++ b/Telegram/gyp/Telegram.gyp @@ -33,6 +33,7 @@ 'submodules_loc': '../ThirdParty', 'minizip_loc': '<(submodules_loc)/minizip', 'sp_media_key_tap_loc': '<(submodules_loc)/SPMediaKeyTap', + 'emoji_suggestions_loc': '<(submodules_loc)/emoji_suggestions', 'style_files': [ '<(res_loc)/colors.palette', '<(res_loc)/basic.style', @@ -101,6 +102,7 @@ '<(libs_loc)/opus/include', '<(minizip_loc)', '<(sp_media_key_tap_loc)', + '<(emoji_suggestions_loc)', '<(submodules_loc)/GSL/include', '<(submodules_loc)/variant/include', ], diff --git a/Telegram/gyp/codegen_rules.gypi b/Telegram/gyp/codegen_rules.gypi index d56283401..b12a1439b 100644 --- a/Telegram/gyp/codegen_rules.gypi +++ b/Telegram/gyp/codegen_rules.gypi @@ -136,6 +136,8 @@ 'outputs': [ '<(SHARED_INTERMEDIATE_DIR)/emoji.cpp', '<(SHARED_INTERMEDIATE_DIR)/emoji.h', + '<(SHARED_INTERMEDIATE_DIR)/emoji_suggestions_data.cpp', + '<(SHARED_INTERMEDIATE_DIR)/emoji_suggestions_data.h', ], 'action': [ '<(PRODUCT_DIR)/codegen_emoji<(exe_ext)', diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index 00463d6fc..86dbbc666 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -98,8 +98,6 @@ <(src_loc)/chat_helpers/bot_keyboard.h <(src_loc)/chat_helpers/emoji_list_widget.cpp <(src_loc)/chat_helpers/emoji_list_widget.h -<(src_loc)/chat_helpers/emoji_suggestions.cpp -<(src_loc)/chat_helpers/emoji_suggestions.h <(src_loc)/chat_helpers/emoji_suggestions_widget.cpp <(src_loc)/chat_helpers/emoji_suggestions_widget.h <(src_loc)/chat_helpers/field_autocomplete.cpp @@ -581,6 +579,8 @@ <(src_loc)/stdafx.h <(src_loc)/structs.cpp <(src_loc)/structs.h +<(emoji_suggestions_loc)/emoji_suggestions.cpp +<(emoji_suggestions_loc)/emoji_suggestions.h platforms: !win <(minizip_loc)/crypt.h