emoji_config.cpp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917
  1. // This file is part of Desktop App Toolkit,
  2. // a set of libraries for developing nice desktop applications.
  3. //
  4. // For license and copyright information please follow this link:
  5. // https://github.com/desktop-app/legal/blob/master/LEGAL
  6. //
  7. #include "emoji_config.h"
  8. #include "emoji_suggestions_helper.h"
  9. #include "base/bytes.h"
  10. #include "base/openssl_help.h"
  11. #include "base/parse_helper.h"
  12. #include "base/debug_log.h"
  13. #include "ui/style/style_core.h"
  14. #include "ui/integration.h"
  15. #include "ui/painter.h"
  16. #include "ui/ui_utility.h"
  17. #include "styles/style_basic.h"
  18. #include <QtCore/QJsonDocument>
  19. #include <QtCore/QJsonObject>
  20. #include <QtCore/QFile>
  21. #include <QtCore/QDir>
  22. #include <crl/crl_async.h>
  23. namespace Ui {
  24. namespace Emoji {
  25. namespace {
  26. constexpr auto kUniversalSize = 72;
  27. constexpr auto kImagesPerRow = 32;
  28. constexpr auto kImageRowsPerSprite = 16;
  29. constexpr auto kSetVersion = uint32(6);
  30. constexpr auto kCacheVersion = uint32(8);
  31. constexpr auto kMaxId = uint32(1 << 8);
  32. #ifdef Q_OS_MAC
  33. constexpr auto kScaleForTouchBar = 150;
  34. #endif
  35. enum class ConfigResult {
  36. Invalid,
  37. BadVersion,
  38. Good,
  39. };
  40. // Right now we can't allow users of Ui::Emoji to create custom sizes.
  41. // Any Instance::Instance() can invalidate Universal.id() and sprites.
  42. // So all Instance::Instance() should happen before async generations.
  43. class Instance {
  44. public:
  45. explicit Instance(int size);
  46. bool cached() const;
  47. void draw(QPainter &p, EmojiPtr emoji, int x, int y);
  48. private:
  49. void readCache();
  50. void generateCache();
  51. void checkUniversalImages();
  52. void pushSprite(QImage &&data);
  53. int _id = 0;
  54. int _size = 0;
  55. std::vector<QPixmap> _sprites;
  56. base::binary_guard _generating;
  57. bool _unsupported = false;
  58. };
  59. auto SizeNormal = -1;
  60. auto SizeLarge = -1;
  61. auto SpritesCount = -1;
  62. auto InstanceNormal = std::unique_ptr<Instance>();
  63. auto InstanceLarge = std::unique_ptr<Instance>();
  64. auto Universal = std::shared_ptr<UniversalImages>();
  65. auto CanClearUniversal = false;
  66. auto WaitingToSwitchBackToId = 0;
  67. auto Updates = rpl::event_stream<>();
  68. #ifdef Q_OS_MAC
  69. auto TouchbarSize = -1;
  70. auto TouchbarInstance = std::unique_ptr<Instance>();
  71. auto TouchbarEmoji = (Instance*)nullptr;
  72. #endif
  73. auto MainEmojiMap = std::map<int, QPixmap>();
  74. auto OtherEmojiMap = base::flat_map<int, std::map<int, QPixmap>>();
  75. int RowsCount(int index) {
  76. if (index + 1 < SpritesCount) {
  77. return kImageRowsPerSprite;
  78. }
  79. const auto count = internal::FullCount()
  80. - (index * kImagesPerRow * kImageRowsPerSprite);
  81. return (count / kImagesPerRow)
  82. + ((count % kImagesPerRow) ? 1 : 0);
  83. }
  84. QString CacheFileNameMask(int size) {
  85. return "cache_" + QString::number(size) + '_';
  86. }
  87. QString CacheFilePath(int size, int index) {
  88. return internal::CacheFileFolder()
  89. + '/'
  90. + CacheFileNameMask(size)
  91. + QString::number(index);
  92. }
  93. QString CurrentSettingPath() {
  94. return internal::CacheFileFolder() + "/current";
  95. }
  96. bool IsValidSetId(int id) {
  97. return (id == 0) || (id > 0 && id < kMaxId);
  98. }
  99. uint32 ComputeVersion(int id) {
  100. Expects(IsValidSetId(id));
  101. static_assert(kCacheVersion > 0 && kCacheVersion < (1 << 16));
  102. static_assert(kSetVersion > 0 && kSetVersion < (1 << 8));
  103. return uint32(kCacheVersion)
  104. | (uint32(kSetVersion) << 16)
  105. | (uint32(id) << 24);
  106. }
  107. int ReadCurrentSetId() {
  108. const auto path = CurrentSettingPath();
  109. auto file = QFile(path);
  110. if (!file.open(QIODevice::ReadOnly)) {
  111. return 0;
  112. }
  113. auto stream = QDataStream(&file);
  114. stream.setVersion(QDataStream::Qt_5_1);
  115. auto id = qint32(0);
  116. stream >> id;
  117. return (stream.status() == QDataStream::Ok && IsValidSetId(id))
  118. ? id
  119. : 0;
  120. }
  121. void ApplyUniversalImages(std::shared_ptr<UniversalImages> images) {
  122. Universal = std::move(images);
  123. CanClearUniversal = false;
  124. MainEmojiMap.clear();
  125. OtherEmojiMap.clear();
  126. Updates.fire({});
  127. }
  128. void SwitchToSetPrepared(int id, std::shared_ptr<UniversalImages> images) {
  129. WaitingToSwitchBackToId = 0;
  130. auto setting = QFile(CurrentSettingPath());
  131. if (!id) {
  132. setting.remove();
  133. } else if (setting.open(QIODevice::WriteOnly)) {
  134. auto stream = QDataStream(&setting);
  135. stream.setVersion(QDataStream::Qt_5_1);
  136. stream << qint32(id);
  137. }
  138. ApplyUniversalImages(std::move(images));
  139. }
  140. [[nodiscard]] ConfigResult ValidateConfig(int id) {
  141. Expects(IsValidSetId(id));
  142. if (!id) {
  143. return ConfigResult::Good;
  144. }
  145. constexpr auto kSizeLimit = 65536;
  146. auto config = QFile(internal::SetDataPath(id) + "/config.json");
  147. if (!config.open(QIODevice::ReadOnly) || config.size() > kSizeLimit) {
  148. return ConfigResult::Invalid;
  149. }
  150. auto error = QJsonParseError{ 0, QJsonParseError::NoError };
  151. const auto document = QJsonDocument::fromJson(
  152. base::parse::stripComments(config.readAll()),
  153. &error);
  154. config.close();
  155. if (error.error != QJsonParseError::NoError) {
  156. return ConfigResult::Invalid;
  157. }
  158. if (document.object()["id"].toInt() != id) {
  159. return ConfigResult::Invalid;
  160. } else if (document.object()["version"].toInt() != kSetVersion) {
  161. return ConfigResult::BadVersion;
  162. }
  163. return ConfigResult::Good;
  164. }
  165. void ClearCurrentSetIdSync() {
  166. Expects(Universal != nullptr);
  167. const auto id = Universal->id();
  168. if (!id) {
  169. return;
  170. }
  171. const auto newId = 0;
  172. auto universal = std::make_shared<UniversalImages>(newId);
  173. universal->ensureLoaded();
  174. // Start loading the set when possible.
  175. ApplyUniversalImages(std::move(universal));
  176. WaitingToSwitchBackToId = id;
  177. }
  178. void SaveToFile(int id, const QImage &image, int size, int index) {
  179. Expects(image.bytesPerLine() == image.width() * 4);
  180. QFile f(CacheFilePath(size, index));
  181. if (!f.open(QIODevice::WriteOnly)) {
  182. if (!QDir::current().mkpath(internal::CacheFileFolder())
  183. || !f.open(QIODevice::WriteOnly)) {
  184. LOG(("App Error: Could not open emoji cache '%1' for size %2_%3"
  185. ).arg(f.fileName()
  186. ).arg(size
  187. ).arg(index));
  188. return;
  189. }
  190. }
  191. const auto write = [&](bytes::const_span data) {
  192. return f.write(
  193. reinterpret_cast<const char*>(data.data()),
  194. data.size()
  195. ) == data.size();
  196. };
  197. const uint32 header[] = {
  198. uint32(ComputeVersion(id)),
  199. uint32(size),
  200. uint32(image.width()),
  201. uint32(image.height()),
  202. };
  203. const auto data = bytes::const_span(
  204. reinterpret_cast<const bytes::type*>(image.bits()),
  205. image.width() * image.height() * 4);
  206. if (!write(bytes::make_span(header))
  207. || !write(data)
  208. || !write(openssl::Sha256(bytes::make_span(header), data))
  209. || false) {
  210. LOG(("App Error: Could not write emoji cache '%1' for size %2"
  211. ).arg(f.fileName()
  212. ).arg(size));
  213. }
  214. }
  215. QImage LoadFromFile(int id, int size, int index) {
  216. const auto rows = RowsCount(index);
  217. const auto width = kImagesPerRow * size;
  218. const auto height = rows * size;
  219. const auto fileSize = 4 * sizeof(uint32)
  220. + (width * height * 4)
  221. + openssl::kSha256Size;
  222. QFile f(CacheFilePath(size, index));
  223. if (!f.exists()
  224. || f.size() != fileSize
  225. || !f.open(QIODevice::ReadOnly)) {
  226. return QImage();
  227. }
  228. const auto read = [&](bytes::span data) {
  229. return f.read(
  230. reinterpret_cast<char*>(data.data()),
  231. data.size()
  232. ) == data.size();
  233. };
  234. uint32 header[4] = { 0 };
  235. if (!read(bytes::make_span(header))
  236. || header[0] != ComputeVersion(id)
  237. || header[1] != size
  238. || header[2] != width
  239. || header[3] != height) {
  240. return QImage();
  241. }
  242. auto result = QImage(
  243. width,
  244. height,
  245. QImage::Format_ARGB32_Premultiplied);
  246. Assert(result.bytesPerLine() == width * 4);
  247. const auto data = bytes::make_span(
  248. reinterpret_cast<bytes::type*>(result.bits()),
  249. width * height * 4);
  250. auto signature = bytes::vector(openssl::kSha256Size);
  251. if (!read(data)
  252. || !read(signature)
  253. //|| (bytes::compare(
  254. // signature,
  255. // openssl::Sha256(bytes::make_span(header), data)) != 0)
  256. || false) {
  257. return QImage();
  258. }
  259. // This should remove a non necessary detach on Retina screens later.
  260. result.setDevicePixelRatio(style::DevicePixelRatio());
  261. crl::async([=, signature = std::move(signature)] {
  262. // This should not happen (invalid signature),
  263. // so we delay this check and fix only the next launch.
  264. const auto data = bytes::make_span(
  265. reinterpret_cast<const bytes::type*>(result.bits()),
  266. width * height * 4);
  267. const auto result = bytes::compare(
  268. signature,
  269. openssl::Sha256(bytes::make_span(header), data));
  270. if (result != 0) {
  271. QFile(CacheFilePath(size, index)).remove();
  272. }
  273. });
  274. return result;
  275. }
  276. std::vector<QImage> LoadSprites(int id) {
  277. Expects(IsValidSetId(id));
  278. Expects(SpritesCount > 0);
  279. auto result = std::vector<QImage>();
  280. const auto folder = (id != 0)
  281. ? internal::SetDataPath(id) + '/'
  282. : QStringLiteral(":/gui/emoji/");
  283. const auto base = folder + "emoji_";
  284. return ranges::views::ints(
  285. 0,
  286. SpritesCount
  287. ) | ranges::views::transform([&](int index) {
  288. return base + QString::number(index + 1) + ".webp";
  289. }) | ranges::views::transform([](const QString &path) {
  290. return QImage(path, "WEBP").convertToFormat(
  291. QImage::Format_ARGB32_Premultiplied);
  292. }) | ranges::to_vector;
  293. }
  294. std::vector<QImage> LoadAndValidateSprites(int id) {
  295. Expects(IsValidSetId(id));
  296. Expects(SpritesCount > 0);
  297. const auto config = ValidateConfig(id);
  298. if (config != ConfigResult::Good) {
  299. return {};
  300. }
  301. auto result = LoadSprites(id);
  302. const auto sizes = ranges::views::ints(
  303. 0,
  304. SpritesCount
  305. ) | ranges::views::transform([](int index) {
  306. return QSize(
  307. kImagesPerRow * kUniversalSize,
  308. RowsCount(index) * kUniversalSize);
  309. });
  310. const auto good = ranges::views::zip_with(
  311. [](const QImage &data, QSize size) { return data.size() == size; },
  312. result,
  313. sizes);
  314. if (ranges::find(good, false) != end(good)) {
  315. return {};
  316. }
  317. return result;
  318. }
  319. void ClearUniversalChecked() {
  320. Expects(InstanceNormal != nullptr && InstanceLarge != nullptr);
  321. if (Universal
  322. && InstanceNormal->cached()
  323. && InstanceLarge->cached()) {
  324. if (CanClearUniversal) {
  325. Universal->clear();
  326. }
  327. ClearIrrelevantCache();
  328. }
  329. }
  330. } // namespace
  331. namespace internal {
  332. QString CacheFileFolder() {
  333. return Integration::Instance().emojiCacheFolder();
  334. }
  335. QString SetDataPath(int id) {
  336. Expects(IsValidSetId(id) && id != 0);
  337. return CacheFileFolder() + "/set" + QString::number(id);
  338. }
  339. } // namespace internal
  340. UniversalImages::UniversalImages(int id) : _id(id) {
  341. Expects(IsValidSetId(id));
  342. }
  343. int UniversalImages::id() const {
  344. return _id;
  345. }
  346. bool UniversalImages::ensureLoaded() {
  347. Expects(SpritesCount > 0);
  348. if (!_sprites.empty()) {
  349. return true;
  350. }
  351. _sprites = LoadAndValidateSprites(_id);
  352. return !_sprites.empty();
  353. }
  354. void UniversalImages::clear() {
  355. _sprites.clear();
  356. }
  357. void UniversalImages::draw(
  358. QPainter &p,
  359. EmojiPtr emoji,
  360. int size,
  361. int x,
  362. int y) const {
  363. Expects(emoji->sprite() < _sprites.size());
  364. const auto large = kUniversalSize;
  365. const auto &original = _sprites[emoji->sprite()];
  366. const auto data = original.bits();
  367. const auto stride = original.bytesPerLine();
  368. const auto format = original.format();
  369. const auto row = emoji->row();
  370. const auto column = emoji->column();
  371. auto single = QImage(
  372. data + (row * kImagesPerRow * large + column) * large * 4,
  373. large,
  374. large,
  375. stride,
  376. format
  377. ).scaled(
  378. size,
  379. size,
  380. Qt::IgnoreAspectRatio,
  381. Qt::SmoothTransformation);
  382. single.setDevicePixelRatio(p.device()->devicePixelRatio());
  383. p.drawImage(x, y, single);
  384. }
  385. QImage UniversalImages::generate(int size, int index) const {
  386. Expects(size > 0);
  387. Expects(index < _sprites.size());
  388. const auto rows = RowsCount(index);
  389. const auto large = kUniversalSize;
  390. const auto &original = _sprites[index];
  391. const auto data = original.bits();
  392. const auto stride = original.bytesPerLine();
  393. const auto format = original.format();
  394. auto result = QImage(
  395. size * kImagesPerRow,
  396. size * rows,
  397. QImage::Format_ARGB32_Premultiplied);
  398. result.fill(Qt::transparent);
  399. {
  400. QPainter p(&result);
  401. for (auto y = 0; y != rows; ++y) {
  402. for (auto x = 0; x != kImagesPerRow; ++x) {
  403. const auto single = QImage(
  404. data + (y * kImagesPerRow * large + x) * large * 4,
  405. large,
  406. large,
  407. stride,
  408. format
  409. ).scaled(
  410. size,
  411. size,
  412. Qt::IgnoreAspectRatio,
  413. Qt::SmoothTransformation);
  414. p.drawImage(
  415. x * size,
  416. y * size,
  417. single);
  418. }
  419. }
  420. }
  421. SaveToFile(_id, result, size, index);
  422. return result;
  423. }
  424. void Init() {
  425. internal::Init();
  426. const auto count = internal::FullCount();
  427. const auto persprite = kImagesPerRow * kImageRowsPerSprite;
  428. SpritesCount = (count / persprite) + ((count % persprite) ? 1 : 0);
  429. SizeNormal = st::emojiSize * style::DevicePixelRatio();
  430. SizeLarge = int(style::ConvertScale(18 * 4 / 3., style::Scale())) * style::DevicePixelRatio();
  431. Universal = std::make_shared<UniversalImages>(ReadCurrentSetId());
  432. CanClearUniversal = false;
  433. InstanceNormal = std::make_unique<Instance>(SizeNormal);
  434. InstanceLarge = std::make_unique<Instance>(SizeLarge);
  435. #ifdef Q_OS_MAC
  436. if (style::Scale() != kScaleForTouchBar) {
  437. TouchbarSize = int(style::ConvertScale(18 * 4 / 3.,
  438. kScaleForTouchBar * style::DevicePixelRatio()));
  439. TouchbarInstance = std::make_unique<Instance>(TouchbarSize);
  440. TouchbarEmoji = TouchbarInstance.get();
  441. } else {
  442. TouchbarEmoji = InstanceLarge.get();
  443. }
  444. #endif
  445. }
  446. void Clear() {
  447. MainEmojiMap.clear();
  448. OtherEmojiMap.clear();
  449. InstanceNormal = nullptr;
  450. InstanceLarge = nullptr;
  451. #ifdef Q_OS_MAC
  452. TouchbarInstance = nullptr;
  453. TouchbarEmoji = nullptr;
  454. #endif
  455. }
  456. void ClearIrrelevantCache() {
  457. Expects(SizeNormal > 0);
  458. Expects(SizeLarge > 0);
  459. crl::async([] {
  460. const auto folder = internal::CacheFileFolder();
  461. const auto list = QDir(folder).entryList(QDir::Files);
  462. const auto good1 = CacheFileNameMask(SizeNormal);
  463. const auto good2 = CacheFileNameMask(SizeLarge);
  464. const auto good3full = CurrentSettingPath();
  465. for (const auto &name : list) {
  466. if (!name.startsWith(good1) && !name.startsWith(good2)) {
  467. const auto full = folder + '/' + name;
  468. if (full != good3full) {
  469. QFile(full).remove();
  470. }
  471. }
  472. }
  473. });
  474. }
  475. int CurrentSetId() {
  476. Expects(Universal != nullptr);
  477. return Universal->id();
  478. }
  479. int NeedToSwitchBackToId() {
  480. return WaitingToSwitchBackToId;
  481. }
  482. void ClearNeedSwitchToId() {
  483. if (!WaitingToSwitchBackToId) {
  484. return;
  485. }
  486. WaitingToSwitchBackToId = 0;
  487. QFile(CurrentSettingPath()).remove();
  488. }
  489. void SwitchToSet(int id, Fn<void(bool)> callback) {
  490. Expects(IsValidSetId(id));
  491. if (Universal && Universal->id() == id) {
  492. callback(true);
  493. return;
  494. }
  495. crl::async([=] {
  496. auto universal = std::make_shared<UniversalImages>(id);
  497. if (!universal->ensureLoaded()) {
  498. crl::on_main([=] {
  499. callback(false);
  500. });
  501. } else {
  502. crl::on_main([=, universal = std::move(universal)]() mutable {
  503. SwitchToSetPrepared(id, std::move(universal));
  504. callback(true);
  505. });
  506. }
  507. });
  508. }
  509. bool SetIsReady(int id) {
  510. Expects(IsValidSetId(id));
  511. if (!id) {
  512. return true;
  513. }
  514. const auto folder = internal::SetDataPath(id) + '/';
  515. auto names = ranges::views::ints(
  516. 0,
  517. SpritesCount + 1
  518. ) | ranges::views::transform([](int index) {
  519. return index
  520. ? "emoji_" + QString::number(index) + ".webp"
  521. : QString("config.json");
  522. });
  523. const auto bad = ranges::find_if(names, [&](const QString &name) {
  524. return !QFile(folder + name).exists();
  525. });
  526. return (bad == names.end());
  527. }
  528. rpl::producer<> Updated() {
  529. return Updates.events();
  530. }
  531. int GetSizeNormal() {
  532. Expects(SizeNormal > 0);
  533. return SizeNormal;
  534. }
  535. int GetSizeLarge() {
  536. Expects(SizeLarge > 0);
  537. return SizeLarge;
  538. }
  539. #ifdef Q_OS_MAC
  540. int GetSizeTouchbar() {
  541. return (style::Scale() == kScaleForTouchBar)
  542. ? GetSizeLarge()
  543. : TouchbarSize;
  544. }
  545. #endif
  546. One::One(
  547. const QString &id,
  548. EmojiPtr original,
  549. uint32 index,
  550. bool hasPostfix,
  551. bool colorizable,
  552. const CreationTag &)
  553. : _id(id)
  554. , _original(original)
  555. , _index(index)
  556. , _hasPostfix(hasPostfix)
  557. , _colorizable(colorizable) {
  558. Expects(!_colorizable || !colored());
  559. }
  560. int One::variantsCount() const {
  561. return hasVariants() ? 5 : 0;
  562. }
  563. int One::variantIndex(EmojiPtr variant) const {
  564. return (variant - original());
  565. }
  566. EmojiPtr One::variant(int index) const {
  567. return (index >= 0 && index <= variantsCount()) ? (original() + index) : this;
  568. }
  569. QString IdFromOldKey(uint64 oldKey) {
  570. auto code = uint32(oldKey >> 32);
  571. auto code2 = uint32(oldKey & 0xFFFFFFFFLLU);
  572. if (!code && code2) {
  573. code = base::take(code2);
  574. }
  575. if ((code & 0xFFFF0000U) != 0xFFFF0000U) { // code and code2 contain the whole id
  576. auto result = QString();
  577. result.reserve(4);
  578. auto addCode = [&result](uint32 code) {
  579. if (auto high = (code >> 16)) {
  580. result.append(QChar(static_cast<ushort>(high & 0xFFFFU)));
  581. }
  582. result.append(QChar(static_cast<ushort>(code & 0xFFFFU)));
  583. };
  584. addCode(code);
  585. if (code2) addCode(code2);
  586. return result;
  587. }
  588. // old sequence
  589. auto sequenceIndex = int(code & 0xFFFFU);
  590. switch (sequenceIndex) {
  591. case 0: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa7");
  592. case 1: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa7\xe2\x80\x8d\xf0\x9f\x91\xa6");
  593. case 2: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa6\xe2\x80\x8d\xf0\x9f\x91\xa6");
  594. case 3: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa7\xe2\x80\x8d\xf0\x9f\x91\xa7");
  595. case 4: return QString::fromUtf8("\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa6");
  596. case 5: return QString::fromUtf8("\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa7");
  597. case 6: return QString::fromUtf8("\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa7\xe2\x80\x8d\xf0\x9f\x91\xa6");
  598. case 7: return QString::fromUtf8("\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa6\xe2\x80\x8d\xf0\x9f\x91\xa6");
  599. case 8: return QString::fromUtf8("\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa7\xe2\x80\x8d\xf0\x9f\x91\xa7");
  600. case 9: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa6");
  601. case 10: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa7");
  602. case 11: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa7\xe2\x80\x8d\xf0\x9f\x91\xa6");
  603. case 12: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa6\xe2\x80\x8d\xf0\x9f\x91\xa6");
  604. case 13: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa7\xe2\x80\x8d\xf0\x9f\x91\xa7");
  605. case 14: return QString::fromUtf8("\xf0\x9f\x91\xa9\xe2\x80\x8d\xe2\x9d\xa4\xef\xb8\x8f\xe2\x80\x8d\xf0\x9f\x91\xa9");
  606. case 15: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xe2\x9d\xa4\xef\xb8\x8f\xe2\x80\x8d\xf0\x9f\x91\xa8");
  607. case 16: return QString::fromUtf8("\xf0\x9f\x91\xa9\xe2\x80\x8d\xe2\x9d\xa4\xef\xb8\x8f\xe2\x80\x8d\xf0\x9f\x92\x8b\xe2\x80\x8d\xf0\x9f\x91\xa9");
  608. case 17: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xe2\x9d\xa4\xef\xb8\x8f\xe2\x80\x8d\xf0\x9f\x92\x8b\xe2\x80\x8d\xf0\x9f\x91\xa8");
  609. case 18: return QString::fromUtf8("\xf0\x9f\x91\x81\xe2\x80\x8d\xf0\x9f\x97\xa8");
  610. }
  611. return QString();
  612. }
  613. QVector<EmojiPtr> GetDefaultRecent() {
  614. const auto defaultRecent = {
  615. 0xD83DDE02LLU,
  616. 0xD83DDE18LLU,
  617. 0x2764LLU,
  618. 0xD83DDE0DLLU,
  619. 0xD83DDE0ALLU,
  620. 0xD83DDE01LLU,
  621. 0xD83DDC4DLLU,
  622. 0x263ALLU,
  623. 0xD83DDE14LLU,
  624. 0xD83DDE04LLU,
  625. 0xD83DDE2DLLU,
  626. 0xD83DDC8BLLU,
  627. 0xD83DDE12LLU,
  628. 0xD83DDE33LLU,
  629. 0xD83DDE1CLLU,
  630. 0xD83DDE48LLU,
  631. 0xD83DDE09LLU,
  632. 0xD83DDE03LLU,
  633. 0xD83DDE22LLU,
  634. 0xD83DDE1DLLU,
  635. 0xD83DDE31LLU,
  636. 0xD83DDE21LLU,
  637. 0xD83DDE0FLLU,
  638. 0xD83DDE1ELLU,
  639. 0xD83DDE05LLU,
  640. 0xD83DDE1ALLU,
  641. 0xD83DDE4ALLU,
  642. 0xD83DDE0CLLU,
  643. 0xD83DDE00LLU,
  644. 0xD83DDE0BLLU,
  645. 0xD83DDE06LLU,
  646. 0xD83DDC4CLLU,
  647. 0xD83DDE10LLU,
  648. 0xD83DDE15LLU,
  649. };
  650. auto result = QVector<EmojiPtr>();
  651. for (const auto oldKey : defaultRecent) {
  652. if (const auto emoji = FromOldKey(oldKey)) {
  653. result.push_back(emoji);
  654. }
  655. }
  656. return result;
  657. }
  658. const QPixmap &SinglePixmap(EmojiPtr emoji, int fontHeight) {
  659. const auto factor = style::DevicePixelRatio();
  660. auto &map = (fontHeight == st::normalFont->height * factor)
  661. ? MainEmojiMap
  662. : OtherEmojiMap[fontHeight];
  663. auto i = map.find(emoji->index());
  664. if (i != end(map)) {
  665. return i->second;
  666. }
  667. auto image = QImage(
  668. SizeNormal + st::emojiPadding * factor * 2,
  669. fontHeight,
  670. QImage::Format_ARGB32_Premultiplied);
  671. image.setDevicePixelRatio(factor);
  672. image.fill(Qt::transparent);
  673. {
  674. QPainter p(&image);
  675. PainterHighQualityEnabler hq(p);
  676. Draw(
  677. p,
  678. emoji,
  679. SizeNormal,
  680. st::emojiPadding,
  681. (fontHeight - SizeNormal) / (2 * factor));
  682. }
  683. return map.emplace(
  684. emoji->index(),
  685. PixmapFromImage(std::move(image))
  686. ).first->second;
  687. }
  688. void Draw(QPainter &p, EmojiPtr emoji, int size, int x, int y) {
  689. #ifdef Q_OS_MAC
  690. const auto s = (style::Scale() == kScaleForTouchBar)
  691. ? SizeLarge
  692. : TouchbarSize;
  693. if (size == s) {
  694. TouchbarEmoji->draw(p, emoji, x, y);
  695. return;
  696. }
  697. #endif
  698. if (size == SizeNormal) {
  699. InstanceNormal->draw(p, emoji, x, y);
  700. } else if (size == SizeLarge) {
  701. InstanceLarge->draw(p, emoji, x, y);
  702. } else {
  703. Unexpected("Size in Ui::Emoji::Draw.");
  704. }
  705. }
  706. Instance::Instance(int size) : _id(Universal->id()), _size(size) {
  707. Expects(Universal != nullptr);
  708. readCache();
  709. if (!cached()) {
  710. generateCache();
  711. }
  712. }
  713. bool Instance::cached() const {
  714. Expects(Universal != nullptr);
  715. return (Universal->id() == _id) && (_sprites.size() == SpritesCount);
  716. }
  717. void Instance::draw(QPainter &p, EmojiPtr emoji, int x, int y) {
  718. if (_unsupported) {
  719. return;
  720. } else if (Universal && Universal->id() != _id) {
  721. generateCache();
  722. }
  723. const auto sprite = emoji->sprite();
  724. if (sprite >= _sprites.size()) {
  725. Assert(Universal != nullptr);
  726. Universal->draw(p, emoji, _size, x, y);
  727. return;
  728. }
  729. p.drawPixmap(
  730. QPoint(x, y),
  731. _sprites[sprite],
  732. QRect(emoji->column() * _size, emoji->row() * _size, _size, _size));
  733. }
  734. void Instance::readCache() {
  735. for (auto i = 0; i != SpritesCount; ++i) {
  736. auto image = LoadFromFile(_id, _size, i);
  737. if (image.isNull()) {
  738. return;
  739. }
  740. pushSprite(std::move(image));
  741. }
  742. }
  743. void Instance::checkUniversalImages() {
  744. Expects(Universal != nullptr);
  745. if (_id != Universal->id()) {
  746. _id = Universal->id();
  747. _generating = nullptr;
  748. _sprites.clear();
  749. }
  750. if (!Universal->ensureLoaded()) {
  751. if (Universal->id() != 0) {
  752. ClearCurrentSetIdSync();
  753. } else {
  754. _unsupported = true;
  755. }
  756. }
  757. }
  758. void Instance::generateCache() {
  759. checkUniversalImages();
  760. const auto cachePath = internal::CacheFileFolder();
  761. if (cachePath.isEmpty()) {
  762. return;
  763. }
  764. const auto size = _size;
  765. const auto index = _sprites.size();
  766. crl::async([
  767. =,
  768. universal = Universal,
  769. guard = _generating.make_guard()
  770. ]() mutable {
  771. auto image = universal->generate(size, index);
  772. crl::on_main(std::move(guard), [
  773. =,
  774. image = std::move(image)
  775. ]() mutable {
  776. if (universal != Universal) {
  777. return;
  778. }
  779. pushSprite(std::move(image));
  780. if (cached()) {
  781. ClearUniversalChecked();
  782. } else {
  783. generateCache();
  784. }
  785. });
  786. });
  787. }
  788. void Instance::pushSprite(QImage &&data) {
  789. _sprites.push_back(PixmapFromImage(std::move(data)));
  790. _sprites.back().setDevicePixelRatio(style::DevicePixelRatio());
  791. }
  792. const std::shared_ptr<UniversalImages> &SourceImages() {
  793. return Universal;
  794. }
  795. void ClearSourceImages(const std::shared_ptr<UniversalImages> &images) {
  796. if (images == Universal) {
  797. CanClearUniversal = true;
  798. ClearUniversalChecked();
  799. }
  800. }
  801. void ReplaceSourceImages(std::shared_ptr<UniversalImages> images) {
  802. Expects(images != nullptr);
  803. if (Universal->id() == images->id()) {
  804. Universal = std::move(images);
  805. }
  806. }
  807. } // namespace Emoji
  808. } // namespace Ui