diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index f97c09b143..a9daf106e2 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -419,6 +419,8 @@ PRIVATE data/notify/data_notify_settings.h data/notify/data_peer_notify_settings.cpp data/notify/data_peer_notify_settings.h + data/stickers/data_custom_emoji.cpp + data/stickers/data_custom_emoji.h data/stickers/data_stickers_set.cpp data/stickers/data_stickers_set.h data/stickers/data_stickers.cpp diff --git a/Telegram/SourceFiles/api/api_text_entities.cpp b/Telegram/SourceFiles/api/api_text_entities.cpp index 9b9d5e5ee7..a867445948 100644 --- a/Telegram/SourceFiles/api/api_text_entities.cpp +++ b/Telegram/SourceFiles/api/api_text_entities.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_user.h" + namespace Api { namespace { diff --git a/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp b/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp index 82846c1f90..6322ccd2b1 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp @@ -142,28 +142,32 @@ void EmojiPack::remove(not_null item) { } } -auto EmojiPack::stickerForEmoji(const IsolatedEmoji &emoji) -> Sticker { - Expects(!emoji.empty()); +auto EmojiPack::stickerForEmoji(EmojiPtr emoji) -> Sticker { + Expects(emoji != nullptr); - if (emoji.items[1] != nullptr) { - return Sticker(); - } - const auto first = emoji.items[0]; - const auto i = _map.find(first); + const auto i = _map.find(emoji); if (i != end(_map)) { return { i->second.get(), nullptr }; } - if (!first->colored()) { + if (!emoji->colored()) { return Sticker(); } - const auto j = _map.find(first->original()); + const auto j = _map.find(emoji->original()); if (j != end(_map)) { - const auto index = first->variantIndex(first); + const auto index = emoji->variantIndex(emoji); return { j->second.get(), ColorReplacements(index) }; } return Sticker(); } +auto EmojiPack::stickerForEmoji(const IsolatedEmoji &emoji) -> Sticker { + Expects(!emoji.empty()); + + return (emoji.items[1] != nullptr) + ? Sticker() + : stickerForEmoji(emoji.items[0]); +} + std::shared_ptr EmojiPack::image(EmojiPtr emoji) { const auto i = _images.emplace( emoji, diff --git a/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.h b/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.h index 3170ca1100..67014ec123 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.h @@ -66,6 +66,7 @@ public: bool add(not_null item); void remove(not_null item); + [[nodiscard]] Sticker stickerForEmoji(EmojiPtr emoji); [[nodiscard]] Sticker stickerForEmoji(const IsolatedEmoji &emoji); [[nodiscard]] std::shared_ptr image(EmojiPtr emoji); diff --git a/Telegram/SourceFiles/core/ui_integration.cpp b/Telegram/SourceFiles/core/ui_integration.cpp index 7310b2df4f..24c32c56ea 100644 --- a/Telegram/SourceFiles/core/ui_integration.cpp +++ b/Telegram/SourceFiles/core/ui_integration.cpp @@ -7,11 +7,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "core/ui_integration.h" +#include "api/api_text_entities.h" #include "core/local_url_handlers.h" #include "core/file_utilities.h" #include "core/application.h" #include "core/sandbox.h" #include "core/click_handler_types.h" +#include "data/stickers/data_custom_emoji.h" +#include "data/data_session.h" #include "ui/basic_click_handlers.h" #include "ui/emoji_config.h" #include "lang/lang_keys.h" @@ -243,7 +246,10 @@ std::unique_ptr UiIntegration::createCustomEmoji( const QString &data, const std::any &context) { const auto my = std::any_cast(&context); - return nullptr; + if (!my || !my->session) { + return nullptr; + } + return my->session->data().customEmojiManager().create(data); } rpl::producer<> UiIntegration::forcePopupMenuHideRequests() { diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index fec3b1b932..ba9242c07b 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -65,6 +65,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_histories.h" #include "data/data_peer_values.h" #include "data/data_premium_limits.h" +#include "data/stickers/data_custom_emoji.h" #include "base/platform/base_platform_info.h" #include "base/unixtime.h" #include "base/call_delayed.h" @@ -246,7 +247,8 @@ Session::Session(not_null session) , _stickers(std::make_unique(this)) , _sponsoredMessages(std::make_unique(this)) , _reactions(std::make_unique(this)) -, _notifySettings(std::make_unique(this)) { +, _notifySettings(std::make_unique(this)) +, _customEmojiManager(std::make_unique(this)) { _cache->open(_session->local().cacheKey()); _bigFileCache->open(_session->local().cacheBigFileKey()); diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index 631eec61af..b8173c8f7a 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -62,6 +62,7 @@ class PhotoMedia; class Stickers; class GroupCall; class NotifySettings; +class CustomEmojiManager; class Session final { public: @@ -120,6 +121,9 @@ public: [[nodiscard]] NotifySettings ¬ifySettings() const { return *_notifySettings; } + [[nodiscard]] CustomEmojiManager &customEmojiManager() const { + return *_customEmojiManager; + } [[nodiscard]] MsgId nextNonHistoryEntryId() { return ++_nonHistoryEntryId; @@ -978,6 +982,7 @@ private: std::unique_ptr _sponsoredMessages; const std::unique_ptr _reactions; const std::unique_ptr _notifySettings; + const std::unique_ptr _customEmojiManager; MsgId _nonHistoryEntryId = ServerMaxMsgId.bare + ScheduledMsgIdsRange; diff --git a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp new file mode 100644 index 0000000000..ce8c52659c --- /dev/null +++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp @@ -0,0 +1,182 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "data/stickers/data_custom_emoji.h" + +#include "chat_helpers/stickers_emoji_pack.h" +#include "main/main_session.h" +#include "data/data_session.h" +#include "data/data_document.h" +#include "ui/text/text_block.h" + +namespace Data { +namespace { + +struct CustomEmojiId { + StickerSetIdentifier set; + uint64 id = 0; +}; +[[nodiscard]] QString SerializeCustomEmojiId(const CustomEmojiId &id) { + const auto &info = id.set; + const auto set = info.id + ? (QString::number(info.id) + ':' + QString::number(info.accessHash)) + : info.shortName; + return QString::number(id.id) + '@' + set; +} + +[[nodiscard]] CustomEmojiId ParseCustomEmojiData(const QString &data) { + const auto parts = data.split('@'); + if (parts.size() != 2) { + return {}; + } + const auto id = parts[0].toULongLong(); + if (!id) { + return {}; + } + const auto second = parts[1].split(':'); + if (const auto set = second[0].toULongLong()) { + return { + .set = {.id = set, .accessHash = second[1].toULongLong() }, + .id = id + }; + } + return { + .set = {.shortName = second[1] }, + .id = id + }; +} + +class CustomEmojiWithData : public Ui::Text::CustomEmoji { +public: + explicit CustomEmojiWithData(const QString &data); + + QString entityData() final override; + +private: + const QString _data; + +}; + +CustomEmojiWithData::CustomEmojiWithData(const QString &data) : _data(data) { +} + +QString CustomEmojiWithData::entityData() { + return _data; +} + +class DocumentCustomEmoji final : public CustomEmojiWithData { +public: + DocumentCustomEmoji( + const QString &data, + not_null document); + + void paint(QPainter &p, int x, int y) override; + +private: + not_null _document; + +}; + +DocumentCustomEmoji::DocumentCustomEmoji( + const QString &data, + not_null document) +: CustomEmojiWithData(data) +, _document(document) { +} + +void DocumentCustomEmoji::paint(QPainter &p, int x, int y) { + const auto size = Ui::Emoji::GetSizeNormal() / style::DevicePixelRatio(); + p.fillRect(QRect{ x, y, size, size }, Qt::red); +} + +class ResolvingCustomEmoji final : public CustomEmojiWithData { +public: + explicit ResolvingCustomEmoji(const QString &data); + + void paint(QPainter &p, int x, int y) override; + +private: + std::optional _resolved; + +}; + +ResolvingCustomEmoji::ResolvingCustomEmoji(const QString &data) +: CustomEmojiWithData(data) { +} + +void ResolvingCustomEmoji::paint(QPainter &p, int x, int y) { + if (_resolved) { + _resolved->paint(p, x, y); + } +} + +} // namespace + +CustomEmojiManager::CustomEmojiManager(not_null owner) +: _owner(owner) { +} + +CustomEmojiManager::~CustomEmojiManager() = default; + +std::unique_ptr CustomEmojiManager::create( + const QString &data) { + const auto parsed = ParseCustomEmojiData(data); + if (!parsed.id) { + return nullptr; + } + const auto document = _owner->document(parsed.id); + if (!document->isNull()) { + return std::make_unique(data, document); + } + return std::make_unique(data); +} + +Main::Session &CustomEmojiManager::session() const { + return _owner->session(); +} + +Session &CustomEmojiManager::owner() const { + return *_owner; +} + +void FillTestCustomEmoji( + not_null session, + TextWithEntities &text) { + const auto pack = &session->emojiStickersPack(); + const auto begin = text.text.constData(), end = begin + text.text.size(); + for (auto ch = begin; ch != end;) { + auto length = 0; + if (const auto emoji = Ui::Emoji::Find(ch, end, &length)) { + if (const auto found = pack->stickerForEmoji(emoji)) { + Expects(found.document->sticker() != nullptr); + + text.entities.push_back({ + EntityType::CustomEmoji, + (ch - begin), + length, + SerializeCustomEmojiId({ + found.document->sticker()->set, + found.document->id, + }), + }); + } + ch += length; + } else if (ch->isHighSurrogate() + && (ch + 1 != end) + && (ch + 1)->isLowSurrogate()) { + ch += 2; + } else { + ++ch; + } + } + ranges::stable_sort( + text.entities, + ranges::less(), + &EntityInText::offset); +} + +} // namespace Data diff --git a/Telegram/SourceFiles/data/stickers/data_custom_emoji.h b/Telegram/SourceFiles/data/stickers/data_custom_emoji.h new file mode 100644 index 0000000000..22c36ac811 --- /dev/null +++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.h @@ -0,0 +1,38 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +namespace Main { +class Session; +} // namespace Main + +namespace Data { + +class Session; + +class CustomEmojiManager final { +public: + CustomEmojiManager(not_null owner); + ~CustomEmojiManager(); + + [[nodiscard]] std::unique_ptr create( + const QString &data); + + [[nodiscard]] Main::Session &session() const; + [[nodiscard]] Session &owner() const; + +private: + const not_null _owner; + +}; + +void FillTestCustomEmoji( + not_null session, + TextWithEntities &text); + +} // namespace Data diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp index 490696d3e4..c5a5e115b0 100644 --- a/Telegram/SourceFiles/history/history_message.cpp +++ b/Telegram/SourceFiles/history/history_message.cpp @@ -42,6 +42,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_chat.h" #include "styles/style_window.h" +#include "data/stickers/data_custom_emoji.h" + namespace { [[nodiscard]] MessageFlags NewForwardedFlags( @@ -329,12 +331,13 @@ HistoryMessage::HistoryMessage( if (const auto media = data.vmedia()) { setMedia(*media); } - const auto textWithEntities = TextWithEntities{ + auto textWithEntities = TextWithEntities{ qs(data.vmessage()), Api::EntitiesFromMTP( &history->session(), data.ventities().value_or_empty()) }; + Data::FillTestCustomEmoji(&history->session(), textWithEntities); setText(_media ? textWithEntities : EnsureNonEmpty(textWithEntities)); if (const auto groupedId = data.vgrouped_id()) { setGroupId(