Moved to the latest emoji set.

Also the old MetaEmoji project was converted to codegen_emoji.
All emoji now use full string identifiers for local storage.
This commit is contained in:
John Preston 2017-02-15 11:50:11 +03:00
parent 902dee0c2a
commit 9757489645
44 changed files with 17427 additions and 8035 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 762 KiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

After

Width:  |  Height:  |  Size: 3.2 MiB

File diff suppressed because it is too large Load diff

View file

@ -1,81 +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 <QtCore/QMap>
#include <QtCore/QVector>
#include <QtCore/QFile>
#include <QtCore/QFileInfo>
#include <QtCore/QBuffer>
#include <QtCore/QDir>
#include <QtCore/QRegularExpression>
#include <QtGui/QImage>
#include <QtGui/QPainter>
#include <iostream>
#include <exception>
#include <QtCore/QTextStream>
#include <QtCore/QString>
#include <QtCore/QCoreApplication>
#include <QtGui/QGuiApplication>
using std::string;
using std::cout;
using std::cerr;
using std::exception;
class Exception : public exception {
public:
Exception(const QString &msg) : _msg(msg.toUtf8()) {
}
virtual const char *what() const throw() {
return _msg.constData();
}
virtual ~Exception() throw() {
}
private:
QByteArray _msg;
};
bool genEmoji(QString emoji_in, const QString &emoji_out, const QString &emoji_png);
class GenEmoji : public QObject {
Q_OBJECT
public:
GenEmoji(const QString &emoji_in, const QString &emoji_out, const QString &emoji_png) : QObject(0),
_emoji_in(emoji_in), _emoji_out(emoji_out), _emoji_png(emoji_png) {
}
public slots :
void run() {
if (genEmoji(_emoji_in, _emoji_out, _emoji_png)) {
emit finished();
}
}
signals:
void finished();
private:
QString _emoji_in, _emoji_out, _emoji_png;
};

View file

@ -1,57 +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 "memain.h"
int main(int argc, char *argv[]) {
QString emoji_in("./Resources/art/emojisprite_"), emoji_out("./SourceFiles/gui/emoji_config.cpp"), emoji_png("./Resources/art/emoji");
for (int i = 0; i < argc; ++i) {
if (string("-emoji_in") == argv[i]) {
if (++i < argc) emoji_in = argv[i];
} else if (string("-emoji_out") == argv[i]) {
if (++i < argc) emoji_out = argv[i];
} else if (string("-emoji_png") == argv[i]) {
if (++i < argc) emoji_png = argv[i];
}
}
#ifdef Q_OS_MAC
if (QDir(QString()).absolutePath() == "/") {
QString first = argc ? QString::fromLocal8Bit(argv[0]) : QString();
if (!first.isEmpty()) {
QFileInfo info(first);
if (info.exists()) {
QDir result(info.absolutePath() + "/../../..");
QString basePath = result.absolutePath() + '/';
emoji_in = basePath + emoji_in;
emoji_out = basePath + emoji_out;
emoji_png = basePath + emoji_png;
}
}
}
#endif
QObject *taskImpl = new GenEmoji(emoji_in, emoji_out, emoji_png);
QGuiApplication a(argc, argv);
QObject::connect(taskImpl, SIGNAL(finished()), &a, SLOT(quit()));
QTimer::singleShot(0, taskImpl, SLOT(run()));
return a.exec();
}

View file

@ -1089,22 +1089,24 @@ void ApiWrap::gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result)
} else {
it->stickers = pack;
it->emoji.clear();
const auto &v(d.vpacks.c_vector().v);
for (int32 i = 0, l = v.size(); i < l; ++i) {
if (v.at(i).type() != mtpc_stickerPack) continue;
auto &v = d.vpacks.c_vector().v;
for (auto i = 0, l = v.size(); i != l; ++i) {
if (v[i].type() != mtpc_stickerPack) continue;
const auto &pack(v.at(i).c_stickerPack());
if (EmojiPtr e = emojiGetNoColor(emojiFromText(qs(pack.vemoticon)))) {
const auto &stickers(pack.vdocuments.c_vector().v);
auto &pack = v[i].c_stickerPack();
if (auto emoji = Ui::Emoji::Find(qs(pack.vemoticon))) {
emoji = emoji->original();
auto &stickers = pack.vdocuments.c_vector().v;
StickerPack p;
p.reserve(stickers.size());
for (int32 j = 0, c = stickers.size(); j < c; ++j) {
DocumentData *doc = App::document(stickers.at(j).v);
for (auto j = 0, c = stickers.size(); j != c; ++j) {
auto doc = App::document(stickers[j].v);
if (!doc || !doc->sticker()) continue;
p.push_back(doc);
}
it->emoji.insert(e, p);
it->emoji.insert(emoji, p);
}
}
}

View file

@ -119,9 +119,9 @@ namespace {
CornersMap cornersMap;
QImage *cornersMaskLarge[4] = { nullptr }, *cornersMaskSmall[4] = { nullptr };
using EmojiMap = QMap<uint64, QPixmap>;
EmojiMap mainEmojiMap;
QMap<int32, EmojiMap> otherEmojiMap;
using EmojiImagesMap = QMap<int, QPixmap>;
EmojiImagesMap MainEmojiMap;
QMap<int, EmojiImagesMap> OtherEmojiMap;
int32 serviceImageCacheSize = 0;
@ -2278,13 +2278,13 @@ namespace {
if (family.isEmpty()) family = QFontDatabase::systemFont(QFontDatabase::FixedFont).family();
::monofont = style::font(st::normalFont->f.pixelSize(), 0, family);
}
emojiInit();
Ui::Emoji::Init();
if (!::emoji) {
::emoji = new QPixmap(QLatin1String(EName));
::emoji = new QPixmap(Ui::Emoji::Filename(Ui::Emoji::Index()));
if (cRetina()) ::emoji->setDevicePixelRatio(cRetinaFactor());
}
if (!::emojiLarge) {
::emojiLarge = new QPixmap(QLatin1String(EmojiNames[EIndex + 1]));
::emojiLarge = new QPixmap(Ui::Emoji::Filename(Ui::Emoji::Index() + 1));
if (cRetina()) ::emojiLarge->setDevicePixelRatio(cRetinaFactor());
}
@ -2336,8 +2336,8 @@ namespace {
clearCorners();
mainEmojiMap.clear();
otherEmojiMap.clear();
MainEmojiMap.clear();
OtherEmojiMap.clear();
Data::clearGlobalStructures();
@ -2414,20 +2414,17 @@ namespace {
}
const QPixmap &emojiSingle(EmojiPtr emoji, int32 fontHeight) {
EmojiMap *map = &(fontHeight == st::msgFont->height ? mainEmojiMap : otherEmojiMap[fontHeight]);
EmojiMap::const_iterator i = map->constFind(emojiKey(emoji));
if (i == map->cend()) {
QImage img(ESize + st::emojiPadding * cIntRetinaFactor() * 2, fontHeight * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
if (cRetina()) img.setDevicePixelRatio(cRetinaFactor());
auto &map = (fontHeight == st::msgFont->height) ? MainEmojiMap : OtherEmojiMap[fontHeight];
auto i = map.constFind(emoji->index());
if (i == map.cend()) {
auto image = QImage(Ui::Emoji::Size() + st::emojiPadding * cIntRetinaFactor() * 2, fontHeight * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
if (cRetina()) image.setDevicePixelRatio(cRetinaFactor());
image.fill(Qt::transparent);
{
QPainter p(&img);
QPainter::CompositionMode m = p.compositionMode();
p.setCompositionMode(QPainter::CompositionMode_Source);
p.fillRect(0, 0, img.width(), img.height(), Qt::transparent);
p.setCompositionMode(m);
emojiDraw(p, emoji, st::emojiPadding * cIntRetinaFactor(), (fontHeight * cIntRetinaFactor() - ESize) / 2);
QPainter p(&image);
emojiDraw(p, emoji, st::emojiPadding * cIntRetinaFactor(), (fontHeight * cIntRetinaFactor() - Ui::Emoji::Size()) / 2);
}
i = map->insert(emojiKey(emoji), App::pixmapFromImageInPlace(std_::move(img)));
i = map.insert(emoji->index(), App::pixmapFromImageInPlace(std_::move(image)));
}
return i.value();
}

View file

@ -26,52 +26,57 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "mainwindow.h"
namespace {
// copied from genemoji.cpp
struct EmojiReplace {
uint32 code;
const char *replace;
};
EmojiReplace replaces[] = {
{ 0xD83DDE0AU, ":-)" },
{ 0xD83DDE0DU, "8-)" },
{ 0x2764U, "<3" },
{ 0xD83DDC8BU, ":kiss:" },
{ 0xD83DDE01U, ":grin:" },
{ 0xD83DDE02U, ":joy:" },
{ 0xD83DDE1AU, ":-*" },
{ 0xD83DDE06U, "xD" },
{ 0xD83DDC4DU, ":like:" },
{ 0xD83DDC4EU, ":dislike:" },
{ 0x261DU, ":up:" },
{ 0x270CU, ":v:" },
{ 0xD83DDC4CU, ":ok:" },
{ 0xD83DDE0EU, "B-)" },
{ 0xD83DDE03U, ":-D" },
{ 0xD83DDE09U, ";-)" },
{ 0xD83DDE1CU, ";-P" },
{ 0xD83DDE0BU, ":-p" },
{ 0xD83DDE14U, "3(" },
{ 0xD83DDE1EU, ":-(" },
{ 0xD83DDE0FU, ":]" },
{ 0xD83DDE22U, ":'(" },
{ 0xD83DDE2DU, ":_(" },
{ 0xD83DDE29U, ":((" },
{ 0xD83DDE28U, ":o" },
{ 0xD83DDE10U, ":|" },
{ 0xD83DDE0CU, "3-)" },
{ 0xD83DDE20U, ">(" },
{ 0xD83DDE21U, ">((" },
{ 0xD83DDE07U, "O:)" },
{ 0xD83DDE30U, ";o" },
{ 0xD83DDE33U, "8|" },
{ 0xD83DDE32U, "8o" },
{ 0xD83DDE37U, ":X" },
{ 0xD83DDE08U, "}:)" },
};
const uint32 replacesCount = sizeof(replaces) / sizeof(EmojiReplace), replacesInRow = 7;
}
EmojiBox::EmojiBox(QWidget*) : _esize(EmojiSizes[EIndex + 1]) {
struct EmojiReplace {
uint32 code;
const char *replace;
};
// copied from codegen_emoji
EmojiReplace Replaces[] = {
{ 0xD83DDE0AU, ":-)" },
{ 0xD83DDE0DU, "8-)" },
{ 0x2764U, "<3" },
{ 0xD83DDC8BU, ":kiss:" },
{ 0xD83DDE01U, ":grin:" },
{ 0xD83DDE02U, ":joy:" },
{ 0xD83DDE1AU, ":-*" },
{ 0xD83DDE06U, "xD" },
{ 0xD83DDC4DU, ":like:" },
{ 0xD83DDC4EU, ":dislike:" },
{ 0x261DU, ":up:" },
{ 0x270CU, ":v:" },
{ 0xD83DDC4CU, ":ok:" },
{ 0xD83DDE0EU, "B-)" },
{ 0xD83DDE03U, ":-D" },
{ 0xD83DDE09U, ";-)" },
{ 0xD83DDE1CU, ";-P" },
{ 0xD83DDE0BU, ":-p" },
{ 0xD83DDE14U, "3(" },
{ 0xD83DDE1EU, ":-(" },
{ 0xD83DDE0FU, ":]" },
{ 0xD83DDE22U, ":'(" },
{ 0xD83DDE2DU, ":_(" },
{ 0xD83DDE29U, ":((" },
{ 0xD83DDE28U, ":o" },
{ 0xD83DDE10U, ":|" },
{ 0xD83DDE0CU, "3-)" },
{ 0xD83DDE20U, ">(" },
{ 0xD83DDE21U, ">((" },
{ 0xD83DDE07U, "O:)" },
{ 0xD83DDE30U, ";o" },
{ 0xD83DDE33U, "8|" },
{ 0xD83DDE32U, "8o" },
{ 0xD83DDE37U, ":X" },
{ 0xD83DDE08U, "}:)" },
};
constexpr auto kReplacesCount = base::array_size(Replaces);
constexpr auto kReplacesInRow = 7;
} // namespace
EmojiBox::EmojiBox(QWidget*) : _esize(Ui::Emoji::Size(Ui::Emoji::Index() + 1)) {
}
void EmojiBox::prepare() {
@ -87,25 +92,21 @@ void EmojiBox::prepare() {
void EmojiBox::fillBlocks() {
BlockRow currentRow;
currentRow.reserve(replacesInRow);
for (uint32 i = 0; i < replacesCount; ++i) {
EmojiPtr emoji = emojiGet(replaces[i].code);
if (!emoji || emoji == TwoSymbolEmoji) continue;
if (emoji->color) {
EmojiColorVariants::const_iterator it = cEmojiVariants().constFind(emoji->code);
currentRow.reserve(kReplacesInRow);
for (uint32 i = 0; i < kReplacesCount; ++i) {
auto emoji = Ui::Emoji::FromOldKey(Replaces[i].code);
if (!emoji) continue;
if (emoji->hasVariants()) {
auto it = cEmojiVariants().constFind(emoji->nonColoredId());
if (it != cEmojiVariants().cend()) {
EmojiPtr replace = emojiFromKey(it.value());
if (replace) {
if (replace != TwoSymbolEmoji && replace->code == emoji->code && replace->code2 == emoji->code2) {
emoji = replace;
}
}
emoji = emoji->variant(it.value());
}
}
Block block(emoji, QString::fromUtf8(replaces[i].replace));
Block block(emoji, QString::fromUtf8(Replaces[i].replace));
currentRow.push_back(block);
if (uint32(currentRow.size()) == replacesInRow) {
if (uint32(currentRow.size()) == kReplacesInRow) {
_blocks.push_back(currentRow);
currentRow.resize(0);
}
@ -135,7 +136,7 @@ void EmojiBox::paintEvent(QPaintEvent *e) {
int32 rowSize = i->size(), left = (width() - rowSize * st::emojiReplaceWidth) / 2;
for (BlockRow::const_iterator j = i->cbegin(), en = i->cend(); j != en; ++j) {
if (j->emoji) {
p.drawPixmap(QPoint(left + (st::emojiReplaceWidth - (_esize / cIntRetinaFactor())) / 2, top + (st::emojiReplaceHeight - _blockHeight) / 2), App::emojiLarge(), QRect(j->emoji->x * _esize, j->emoji->y * _esize, _esize, _esize));
p.drawPixmap(QPoint(left + (st::emojiReplaceWidth - (_esize / cIntRetinaFactor())) / 2, top + (st::emojiReplaceHeight - _blockHeight) / 2), App::emojiLarge(), QRect(j->emoji->x() * _esize, j->emoji->y() * _esize, _esize, _esize));
}
QRect trect(left, top + (st::emojiReplaceHeight + _blockHeight) / 2 - st::emojiTextFont->height, st::emojiReplaceWidth, st::emojiTextFont->height);
p.drawText(trect, j->text, QTextOption(Qt::AlignHCenter | Qt::AlignTop));

View file

@ -39,9 +39,9 @@ private:
int32 _blockHeight;
struct Block {
Block(const EmojiData *emoji = 0, const QString &text = QString()) : emoji(emoji), text(text) {
Block(EmojiPtr emoji = nullptr, const QString &text = QString()) : emoji(emoji), text(text) {
}
const EmojiData *emoji;
EmojiPtr emoji;
QString text;
};
typedef QVector<Block> BlockRow;

View file

@ -136,21 +136,23 @@ void StickerSetBox::Inner::gotSet(const MTPmessages_StickerSet &set) {
_pack.push_back(doc);
_packOvers.push_back(Animation());
}
auto &packs(d.vpacks.c_vector().v);
for (int i = 0, l = packs.size(); i < l; ++i) {
auto &packs = d.vpacks.c_vector().v;
for (auto i = 0, l = packs.size(); i != l; ++i) {
if (packs.at(i).type() != mtpc_stickerPack) continue;
auto &pack(packs.at(i).c_stickerPack());
if (auto e = emojiGetNoColor(emojiFromText(qs(pack.vemoticon)))) {
auto &stickers(pack.vdocuments.c_vector().v);
auto &pack = packs.at(i).c_stickerPack();
if (auto emoji = Ui::Emoji::Find(qs(pack.vemoticon))) {
emoji = emoji->original();
auto &stickers = pack.vdocuments.c_vector().v;
StickerPack p;
p.reserve(stickers.size());
for (int32 j = 0, c = stickers.size(); j < c; ++j) {
DocumentData *doc = App::document(stickers.at(j).v);
for (auto j = 0, c = stickers.size(); j != c; ++j) {
auto doc = App::document(stickers[j].v);
if (!doc || !doc->sticker()) continue;
p.push_back(doc);
}
_emoji.insert(e, p);
_emoji.insert(emoji, p);
}
}
if (d.vset.type() == mtpc_stickerSet) {

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,56 @@
/*
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 "codegen/common/logging.h"
#include <vector>
#include <map>
#include <memory>
#include <functional>
#include <QtCore/QString>
#include <QtCore/QSet>
#include <QtCore/QMap>
namespace codegen {
namespace emoji {
using Id = QString;
struct Emoji {
Id id;
bool postfixed = false;
bool variated = false;
bool colored = false;
};
struct Data {
std::vector<Emoji> list;
std::map<Id, int, std::greater<Id>> map;
std::vector<std::vector<int>> categories;
std::map<QString, int, std::greater<QString>> replaces;
};
Data PrepareData();
constexpr auto kPostfix = 0xFE0FU;
common::LogStream logDataError();
} // namespace emoji
} // namespace codegen

View file

@ -0,0 +1,745 @@
/*
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 "codegen/emoji/generator.h"
#include <QtCore/QtPlugin>
#include <QtCore/QBuffer>
#include <QtGui/QFontDatabase>
#include <QtGui/QGuiApplication>
#include <QtGui/QImage>
#include <QtGui/QPainter>
#include <QtCore/QDir>
Q_IMPORT_PLUGIN(QWebpPlugin)
#ifdef Q_OS_MAC
Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin)
#elif defined Q_OS_WIN
Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin)
#else // !Q_OS_MAC && !Q_OS_WIN
Q_IMPORT_PLUGIN(QXcbIntegrationPlugin)
#endif // !Q_OS_MAC && !Q_OS_WIN
namespace codegen {
namespace emoji {
namespace {
constexpr int kErrorCantWritePath = 851;
common::ProjectInfo Project = {
"codegen_emoji",
"empty",
"stdafx.h",
true, // forceReGenerate
};
QRect computeSourceRect(const QImage &image) {
auto size = image.width();
auto result = QRect(2, 2, size - 4, size - 4);
auto top = 1, bottom = 1, left = 1, right = 1;
auto rgbBits = reinterpret_cast<const QRgb*>(image.constBits());
for (auto i = 0; i != size; ++i) {
if (rgbBits[i] > 0
|| rgbBits[(size - 1) * size + i] > 0
|| rgbBits[i * size] > 0
|| rgbBits[i * size + (size - 1)] > 0) {
logDataError() << "Bad border.";
return QRect();
}
if (rgbBits[1 * size + i] > 0) {
top = -1;
} else if (top > 0 && rgbBits[2 * size + i] > 0) {
top = 0;
}
if (rgbBits[(size - 2) * size + i] > 0) {
bottom = -1;
} else if (bottom > 0 && rgbBits[(size - 3) * size + i] > 0) {
bottom = 0;
}
if (rgbBits[i * size + 1] > 0) {
left = -1;
} else if (left > 0 && rgbBits[i * size + 2] > 0) {
left = 0;
}
if (rgbBits[i * size + (size - 2)] > 0) {
right = -1;
} else if (right > 0 && rgbBits[i * size + (size - 3)] > 0) {
right = 0;
}
}
if (top < 0) {
if (bottom <= 0) {
logDataError() << "Bad vertical :(";
return QRect();
} else {
result.setY(result.y() + 1);
}
} else if (bottom < 0) {
if (top <= 0) {
logDataError() << "Bad vertical :(";
return QRect();
} else {
result.setY(result.y() - 1);
}
}
if (left < 0) {
if (right <= 0) {
logDataError() << "Bad horizontal :(";
return QRect();
} else {
result.setX(result.x() + 1);
}
} else if (right < 0) {
if (left <= 0) {
logDataError() << "Bad horizontal :(";
return QRect();
} else {
result.setX(result.x() - 1);
}
}
return result;
}
QString computeId(Id id) {
auto idAsParams = QStringList();
for (auto i = 0, size = id.size(); i != size; ++i) {
idAsParams.push_back("0x" + QString::number(id[i].unicode(), 16));
}
return "internal::ComputeId(" + idAsParams.join(", ") + ")";
}
} // namespace
Generator::Generator(const Options &options) : project_(Project), data_(PrepareData()) {
QDir dir(options.outputPath);
if (!dir.mkpath(".")) {
common::logError(kErrorCantWritePath, "Command Line") << "can not open path for writing: " << dir.absolutePath().toStdString();
data_ = Data();
}
outputPath_ = dir.absolutePath() + "/emoji_config";
spritePath_ = dir.absolutePath() + "/emoji";
}
int Generator::generate() {
if (data_.list.empty()) {
return -1;
}
#ifdef Q_OS_MAC
if (!writeImages()) {
return -1;
}
#endif // Q_OS_MAC
if (!writeSource()) {
return -1;
}
return 0;
}
constexpr auto kVariantsCount = 5;
constexpr auto kEmojiInRow = 40;
QImage Generator::generateImage(int variantIndex) {
constexpr int kEmojiSizes[kVariantsCount + 1] = { 18, 22, 27, 36, 45, 180 };
constexpr bool kBadSizes[kVariantsCount] = { true, true, false, false, false };
constexpr int kEmojiFontSizes[kVariantsCount + 1] = { 14, 20, 27, 36, 45, 180 };
constexpr int kEmojiDeltas[kVariantsCount + 1] = { 15, 20, 25, 34, 42, 167 };
auto emojiCount = data_.list.size();
auto columnsCount = kEmojiInRow;
auto rowsCount = (emojiCount / columnsCount) + ((emojiCount % columnsCount) ? 1 : 0);
auto emojiSize = kEmojiSizes[variantIndex];
auto isBad = kBadSizes[variantIndex];
auto sourceSize = (isBad ? kEmojiSizes[kVariantsCount] : emojiSize);
auto font = QGuiApplication::font();
font.setFamily(QStringLiteral("Apple Color Emoji"));
font.setPixelSize(kEmojiFontSizes[isBad ? kVariantsCount : variantIndex]);
auto singleSize = 4 + sourceSize;
auto emojiImage = QImage(columnsCount * emojiSize, rowsCount * emojiSize, QImage::Format_ARGB32);
emojiImage.fill(Qt::transparent);
auto singleImage = QImage(singleSize, singleSize, QImage::Format_ARGB32);
{
QPainter p(&emojiImage);
p.setRenderHint(QPainter::SmoothPixmapTransform);
auto column = 0;
auto row = 0;
for (auto &emoji : data_.list) {
{
singleImage.fill(Qt::transparent);
QPainter q(&singleImage);
q.setPen(QColor(0, 0, 0, 255));
q.setFont(font);
q.drawText(2, 2 + kEmojiDeltas[isBad ? kVariantsCount : variantIndex], emoji.id);
}
auto sourceRect = computeSourceRect(singleImage);
if (sourceRect.isEmpty()) {
return QImage();
}
auto targetRect = QRect(column * emojiSize, row * emojiSize, emojiSize, emojiSize);
if (isBad) {
p.drawImage(targetRect, singleImage.copy(sourceRect).scaled(emojiSize, emojiSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
} else {
p.drawImage(targetRect, singleImage, sourceRect);
}
++column;
if (column == columnsCount) {
column = 0;
++row;
}
}
}
return emojiImage;
}
bool Generator::writeImages() {
constexpr const char *variantPostfix[] = { "", "_125x", "_150x", "_200x", "_250x" };
for (auto variantIndex = 0; variantIndex != kVariantsCount; variantIndex++) {
auto image = generateImage(variantIndex);
auto postfix = variantPostfix[variantIndex];
auto filename = spritePath_ + postfix + ".webp";
auto bytes = QByteArray();
{
QBuffer buffer(&bytes);
if (!image.save(&buffer, "WEBP", (variantIndex < 3) ? 100 : 99)) {
logDataError() << "Could not save 'emoji" << postfix << ".webp'.";
return false;
}
}
auto needResave = !QFileInfo(filename).exists();
if (!needResave) {
QFile file(filename);
if (!file.open(QIODevice::ReadOnly)) {
needResave = true;
} else {
auto already = file.readAll();
if (already.size() != bytes.size() || memcmp(already.constData(), bytes.constData(), already.size())) {
needResave = true;
}
}
}
if (needResave) {
QFile file(filename);
if (!file.open(QIODevice::WriteOnly)) {
logDataError() << "Could not open 'emoji" << postfix << ".png'.";
return false;
} else {
if (file.write(bytes) != bytes.size()) {
logDataError() << "Could not write 'emoji" << postfix << ".png'.";
return false;
}
}
}
}
return true;
}
bool Generator::writeSource() {
source_ = std::make_unique<common::CppFile>(outputPath_ + ".cpp", project_);
source_->pushNamespace("Ui").pushNamespace("Emoji").pushNamespace();
source_->stream() << "\
\n\
constexpr auto kCount = " << data_.list.size() << ";\n\
auto WorkingIndex = -1;\n\
\n\
QVector<One> Items;\n\
\n";
source_->popNamespace().newline().pushNamespace("internal");
source_->stream() << "\
\n\
EmojiPtr ByIndex(int index) {\n\
return (index >= 0 && index < Items.size()) ? &Items[index] : nullptr;\n\
}\n\
\n\
inline void AppendChars(QString &result) {\n\
}\n\
\n\
template <typename ...Args>\n\
inline void AppendChars(QString &result, ushort unicode, Args... args) {\n\
result.append(QChar(unicode));\n\
AppendChars(result, args...);\n\
}\n\
\n\
template <typename ...Args>\n\
inline QString ComputeId(Args... args) {\n\
auto result = QString();\n\
result.reserve(sizeof...(args));\n\
AppendChars(result, args...);\n\
return result;\n\
}\n";
if (!writeFindReplace()) {
return false;
}
if (!writeFind()) {
return false;
}
source_->popNamespace();
if (!writeInitCode()) {
return false;
}
if (!writePacks()) {
return false;
}
source_->stream() << "\
\n\
int Index() {\n\
return WorkingIndex;\n\
}\n\
\n\
int One::variantsCount() const {\n\
return hasVariants() ? " << colorsCount_ << " : 0;\n\
}\n\
\n\
int One::variantIndex(EmojiPtr variant) const {\n\
return (variant - original());\n\
}\n\
\n\
EmojiPtr One::variant(int index) const {\n\
return (index >= 0 && index <= variantsCount()) ? (original() + index) : this;\n\
}\n\
\n\
int One::index() const {\n\
return (this - &Items[0]);\n\
}\n\
\n";
return source_->finalize();
}
bool Generator::writeInitCode() {
constexpr const char *variantNames[] = {
"dbisOne",
"dbisOneAndQuarter",
"dbisOneAndHalf",
"dbisTwo"
};
source_->stream() << "\
\n\
void Init() {\n\
auto scaleForEmoji = cRetina() ? dbisTwo : cScale();\n\
\n\
switch (scaleForEmoji) {\n";
auto variantIndex = 0;
for (auto name : variantNames) {
source_->stream() << "\
case " << name << ": WorkingIndex = " << variantIndex++ << "; break;\n";
}
source_->stream() << "\
};\n\
\n\
Items.reserve(kCount);\n\
\n";
auto column = 0;
auto row = 0;
auto index = 0;
auto variated = -1;
auto coloredCount = 0;
for (auto &item : data_.list) {
source_->stream() << "\
Items.push_back({ " << computeId(item.id) << ", " << column << ", " << row << ", " << (item.postfixed ? "true" : "false") << ", " << (item.variated ? "true" : "false") << ", " << (item.colored ? "&Items[" + QString::number(variated) + "]" : "nullptr") << " });\n";
if (coloredCount > 0 && (item.variated || !item.colored)) {
if (!colorsCount_) {
colorsCount_ = coloredCount;
} else if (colorsCount_ != coloredCount) {
logDataError() << "different colored emoji count exist.";
return false;
}
coloredCount = 0;
}
if (item.variated) {
variated = index;
} else if (item.colored) {
if (variated <= 0) {
logDataError() << "wrong order of colored items.";
return false;
}
++coloredCount;
} else if (variated >= 0) {
variated = -1;
}
if (++column == kEmojiInRow) {
column = 0;
++row;
}
++index;
}
source_->stream() << "\
}\n";
return true;
}
bool Generator::writePacks() {
constexpr const char *packNames[] = {
"dbietPeople",
"dbietNature",
"dbietFood",
"dbietActivity",
"dbietTravel",
"dbietObjects",
"dbietSymbols",
};
source_->stream() << "\
\n\
int GetPackCount(DBIEmojiTab tab) {\n\
switch (tab) {\n";
auto countIndex = 0;
for (auto name : packNames) {
if (countIndex >= int(data_.categories.size())) {
logDataError() << "category " << countIndex << " not found.";
return false;
}
source_->stream() << "\
case " << name << ": return " << data_.categories[countIndex++].size() << ";\n";
}
source_->stream() << "\
case dbietRecent: return cGetRecentEmoji().size();\n\
}\n\
return 0;\n\
}\n\
\n\
EmojiPack GetPack(DBIEmojiTab tab) {\n\
switch (tab) {\n";
auto index = 0;
for (auto name : packNames) {
if (index >= int(data_.categories.size())) {
logDataError() << "category " << index << " not found.";
return false;
}
auto &category = data_.categories[index++];
source_->stream() << "\
case " << name << ": {\n\
static auto result = EmojiPack();\n\
if (result.isEmpty()) {\n\
result.reserve(" << category.size() << ");\n";
for (auto index : category) {
source_->stream() << "\
result.push_back(&Items[" << index << "]);\n";
}
source_->stream() << "\
}\n\
return result;\n\
} break;\n\n";
}
source_->stream() << "\
case dbietRecent: {\n\
auto result = EmojiPack();\n\
result.reserve(cGetRecentEmoji().size());\n\
for (auto &item : cGetRecentEmoji()) {\n\
result.push_back(item.first);\n\
}\n\
return result;\n\
} break;\n\
}\n\
return EmojiPack();\n\
}\n";
return true;
}
bool Generator::writeFindReplace() {
source_->stream() << "\
\n\
EmojiPtr FindReplace(const QChar *ch, const QChar *end, int *outLength) {\n";
if (!writeFindFromDictionary(data_.replaces)) {
return false;
}
source_->stream() << "\
}\n";
return true;
}
bool Generator::writeFind() {
source_->stream() << "\
\n\
EmojiPtr Find(const QChar *ch, const QChar *end, int *outLength) {\n";
if (!writeFindFromDictionary(data_.map)) {
return false;
}
source_->stream() << "\
}\n\
\n";
return true;
}
bool Generator::writeFindFromDictionary(const std::map<QString, int, std::greater<QString>> &dictionary) {
// That one was slower..
//
//using Map = std::map<QString, int, std::greater<QString>>;
//Map small; // 0-127
//Map medium; // 128-255
//Map large; // 256-65535
//Map other; // surrogates
//for (auto &item : dictionary) {
// auto key = item.first;
// auto first = key.isEmpty() ? QChar(0) : QChar(key[0]);
// if (!first.unicode() || first.isLowSurrogate() || (first.isHighSurrogate() && (key.size() < 2 || !QChar(key[1]).isLowSurrogate()))) {
// logDataError() << "bad key.";
// return false;
// }
// if (first.isHighSurrogate()) {
// other.insert(item);
// } else if (first.unicode() >= 256) {
// if (first.unicode() >= 0xE000) {
// // Currently if we'll have codes from both below and above the surrogates
// // we'll return nullptr without checking the surrogates, because we first
// // check those codes, applying the min-max range of codes from "large".
// logDataError() << "codes after the surrogates are not supported.";
// return false;
// }
// large.insert(item);
// } else if (first.unicode() >= 128) {
// medium.insert(item);
// } else {
// small.insert(item);
// }
//}
//auto smallMinCheck = (medium.empty() && large.empty() && other.empty()) ? -1 : 0;
//auto smallMaxCheck = (medium.empty() && large.empty() && other.empty()) ? -1 : 128;
//if (!writeFindFromOneDictionary(small, smallMinCheck, smallMaxCheck)) {
// return false;
//}
//auto mediumMinCheck = (large.empty() && other.empty()) ? -1 : 128;
//auto mediumMaxCheck = (large.empty() && other.empty()) ? -1 : 256;
//if (!writeFindFromOneDictionary(medium, mediumMinCheck, mediumMaxCheck)) {
// return false;
//}
//if (!writeFindFromOneDictionary(large, other.empty() ? -1 : 0)) {
// return false;
//}
//if (!writeFindFromOneDictionary(other)) {
// return false;
//}
if (!writeFindFromOneDictionary(dictionary)) {
return false;
}
source_->stream() << "\
return nullptr;\n";
return true;
}
// min < 0 - no outer min-max check
// max < 0 - this is last checked dictionary
bool Generator::writeFindFromOneDictionary(const std::map<QString, int, std::greater<QString>> &dictionary, int min, int max) {
if (dictionary.empty()) {
return true;
}
auto tabs = [](int size) {
return QString(size, '\t');
};
std::map<int, int> uniqueFirstChars;
auto foundMax = 0, foundMin = 65535;
for (auto &item : dictionary) {
auto ch = item.first[0].unicode();
if (foundMax < ch) foundMax = ch;
if (foundMin > ch) foundMin = ch;
uniqueFirstChars[ch] = 0;
}
auto writeBoundsCondition = false;//(uniqueFirstChars.size() > 4);
auto haveOuterCondition = false;
if (min >= 0 && max > min) {
haveOuterCondition = true;
source_->stream() << "\
if (ch->unicode() >= " << min << " && ch->unicode() < " << max << ") {\n";
if (writeBoundsCondition) {
source_->stream() << "\
if (ch->unicode() < " << foundMin << " || ch->unicode() > " << foundMax << ") {\n\
return nullptr;\n\
}\n\n";
}
} else if (writeBoundsCondition) {
haveOuterCondition = true;
source_->stream() << "\
if (ch->unicode() >= " << foundMin << " && ch->unicode() <= " << foundMax << ") {\n";
}
enum class UsedCheckType {
Switch,
If,
UpcomingIf,
};
auto checkTypes = QVector<UsedCheckType>();
auto existsTill = QVector<int>(1, 1);
auto chars = QString();
auto tabsUsed = haveOuterCondition ? 2 : 1;
// Returns true if at least one check was finished.
auto finishChecksTillKey = [this, &chars, &checkTypes, &existsTill, &tabsUsed, tabs](const QString &key) {
auto result = false;
while (!chars.isEmpty() && key.midRef(0, chars.size()) != chars) {
result = true;
auto wasType = checkTypes.back();
chars.resize(chars.size() - 1);
checkTypes.pop_back();
if (wasType == UsedCheckType::Switch || wasType == UsedCheckType::If) {
--tabsUsed;
if (wasType == UsedCheckType::Switch) {
source_->stream() << tabs(tabsUsed) << "break;\n";
}
if ((!chars.isEmpty() && key.midRef(0, chars.size()) != chars) || key == chars) {
source_->stream() << tabs(tabsUsed) << "}\n";
existsTill.pop_back();
}
}
}
return result;
};
// Check if we can use "if" for a check on "charIndex" in "it" (otherwise only "switch")
auto canUseIfForCheck = [](auto it, auto end, int charIndex) {
auto key = it->first;
auto i = it;
auto keyStart = key.mid(0, charIndex);
for (++i; i != end; ++i) {
auto nextKey = i->first;
if (nextKey.mid(0, charIndex) != keyStart) {
return true;
} else if (nextKey.size() > charIndex && nextKey[charIndex] != key[charIndex]) {
return false;
}
}
return true;
};
// Get minimal length of key that has first "charIndex" chars same as it
// and has at least one more char after them.
auto getMinimalLength = [](auto it, auto end, int charIndex) {
auto key = it->first;
auto result = key.size();
auto i = it;
auto keyStart = key.mid(0, charIndex);
for (++i; i != end; ++i) {
auto nextKey = i->first;
if (nextKey.mid(0, charIndex) != keyStart || nextKey.size() <= charIndex) {
break;
}
if (result > nextKey.size()) {
result = nextKey.size();
}
}
return result;
};
auto getUnicodePointer = [](int index) {
if (index > 0) {
return "(ch + " + QString::number(index) + ')';
}
return QString("ch");
};
for (auto i = dictionary.cbegin(), e = dictionary.cend(); i != e; ++i) {
auto &item = *i;
auto key = item.first;
auto weContinueOldSwitch = finishChecksTillKey(key);
while (chars.size() != key.size()) {
auto checking = chars.size();
auto keyChar = key[checking];
auto checkedAlready = (checkTypes.size() > checking);
if (!checkedAlready) {
auto keyCharString = "0x" + QString::number(keyChar.unicode(), 16);
auto usedIfForCheck = false;
if (weContinueOldSwitch) {
weContinueOldSwitch = false;
source_->stream() << tabs(tabsUsed) << "case " << keyCharString << ":\n";
} else {
auto canCheckByIfCount = 0;
for (; checking + canCheckByIfCount != key.size(); ++canCheckByIfCount) {
if (!canUseIfForCheck(i, e, checking + canCheckByIfCount)) {
break;
}
}
auto canCheckTill = getMinimalLength(i, e, checking);
auto checkedAlready = !existsTill.isEmpty() && (existsTill.back() == canCheckTill);
if (checking + canCheckByIfCount - 1 > canCheckTill
|| checking > canCheckTill
|| (!existsTill.isEmpty() && existsTill.back() > canCheckTill)) {
logDataError() << "something wrong with the algo.";
return false;
}
auto condition = checkedAlready ? QString() : ("ch + " + QString::number(canCheckTill - 1) + " " + (canCheckTill == checking + 1 ? "!=" : "<") + " end");
existsTill.push_back(canCheckTill);
if (canCheckByIfCount > 0) {
auto checkStrings = QStringList();
for (auto checkByIf = 0; checkByIf != canCheckByIfCount; ++checkByIf) {
checkStrings.push_back(getUnicodePointer(checking + checkByIf) + "->unicode() == 0x" + QString::number(key[checking + checkByIf].unicode(), 16));
}
if (!condition.isEmpty()) {
checkStrings.push_front(condition);
}
for (auto upcomingChecked = 1; upcomingChecked != canCheckByIfCount; ++upcomingChecked) {
checkTypes.push_back(UsedCheckType::UpcomingIf);
}
source_->stream() << tabs(tabsUsed) << "if (" << checkStrings.join(" && ") << ") {\n";
usedIfForCheck = true;
} else {
source_->stream() << tabs(tabsUsed) << (condition.isEmpty() ? "" : "if (" + condition + ") ") << "switch (" << getUnicodePointer(checking) << "->unicode()) {\n";
source_->stream() << tabs(tabsUsed) << "case " << keyCharString << ":\n";
}
}
checkTypes.push_back(usedIfForCheck ? UsedCheckType::If : UsedCheckType::Switch);
++tabsUsed;
}
chars.push_back(keyChar);
}
source_->stream() << tabs(tabsUsed) << "if (outLength) *outLength = " << chars.size() << ";\n";
// While IsReplaceEdge() currently is always true we just return the value.
//source_->stream() << tabs(1 + chars.size()) << "if (ch + " << chars.size() << " == end || IsReplaceEdge(*(ch + " << chars.size() << ")) || (ch + " << chars.size() << ")->unicode() == ' ') {\n";
//source_->stream() << tabs(1 + chars.size()) << "\treturn &Items[" << item.second << "];\n";
//source_->stream() << tabs(1 + chars.size()) << "}\n";
source_->stream() << tabs(tabsUsed) << "return &Items[" << item.second << "];\n";
}
finishChecksTillKey(QString());
if (min >= 0) { // not the last dictionary
source_->stream() << tabs(tabsUsed) << "return nullptr;\n";
}
if (haveOuterCondition) {
source_->stream() << "\
}\n";
}
source_->stream() << "\n";
return true;
}
} // namespace emoji
} // namespace codegen

View file

@ -0,0 +1,66 @@
/*
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 <memory>
#include <QtCore/QString>
#include <QtCore/QSet>
#include "codegen/common/cpp_file.h"
#include "codegen/emoji/options.h"
#include "codegen/emoji/data.h"
namespace codegen {
namespace emoji {
class Generator {
public:
Generator(const Options &options);
Generator(const Generator &other) = delete;
Generator &operator=(const Generator &other) = delete;
int generate();
private:
QImage generateImage(int variantIndex);
bool writeImages();
bool writeSource();
bool writeInitCode();
bool writePacks();
bool writeFindReplace();
bool writeFind();
bool writeFindFromDictionary(const std::map<QString, int, std::greater<QString>> &dictionary);
// min < 0 - this is last checked dictionary
// max < 0 - no outer min-max check
bool writeFindFromOneDictionary(const std::map<QString, int, std::greater<QString>> &dictionary, int min = -1, int max = -1);
const common::ProjectInfo &project_;
int colorsCount_ = 0;
QString outputPath_;
QString spritePath_;
std::unique_ptr<common::CppFile> source_;
Data data_;
};
} // namespace emoji
} // namespace codegen

View file

@ -0,0 +1,33 @@
/*
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 <QtGui/QGuiApplication>
#include "codegen/emoji/options.h"
#include "codegen/emoji/generator.h"
int main(int argc, char *argv[]) {
QGuiApplication app(argc, argv);
auto options = codegen::emoji::parseOptions();
codegen::emoji::Generator generator(options);
return generator.generate();
}

View file

@ -0,0 +1,63 @@
/*
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 "codegen/emoji/options.h"
#include <ostream>
#include <QtCore/QCoreApplication>
#include "codegen/common/logging.h"
namespace codegen {
namespace emoji {
namespace {
constexpr int kErrorOutputPathExpected = 902;
} // namespace
using common::logError;
Options parseOptions() {
Options result;
auto args = QCoreApplication::instance()->arguments();
for (int i = 1, count = args.size(); i < count; ++i) { // skip first
auto &arg = args.at(i);
// Output path
if (arg == "-o") {
if (++i == count) {
logError(kErrorOutputPathExpected, "Command Line") << "output path expected after -o";
return Options();
} else {
result.outputPath = args.at(i);
}
} else if (arg.startsWith("-o")) {
result.outputPath = arg.mid(2);
}
}
if (result.outputPath.isEmpty()) {
logError(kErrorOutputPathExpected, "Command Line") << "output path expected";
return Options();
}
return result;
}
} // namespace emoji
} // namespace codegen

View file

@ -18,6 +18,20 @@ 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 <QtCore/QTimer>
#pragma once
#include "genemoji.h"
#include <QtCore/QString>
#include <QtCore/QStringList>
namespace codegen {
namespace emoji {
struct Options {
QString outputPath = ".";
};
// Parsing failed if inputPath is empty in the result.
Options parseOptions();
} // namespace emoji
} // namespace codegen

View file

@ -38,9 +38,9 @@ using common::logError;
Options parseOptions() {
Options result;
auto args(QCoreApplication::instance()->arguments());
for (int i = 1, count = args.size(); i < count; ++i) { // skip first
const auto &arg(args.at(i));
auto args = QCoreApplication::instance()->arguments();
for (auto i = 1, count = args.size(); i < count; ++i) { // skip first
auto &arg = args.at(i);
// Output path
if (arg == "-o") {

View file

@ -87,7 +87,6 @@ enum {
AVBlockSize = 4096, // 4Kb for ffmpeg blocksize
SaveRecentEmojisTimeout = 3000, // 3 secs
SaveWindowPositionTimeout = 1000, // 1 sec
AutoSearchTimeout = 900, // 0.9 secs

View file

@ -77,7 +77,7 @@ void FieldAutocomplete::showFiltered(PeerData *peer, QString query, bool addInli
return;
}
_emoji = EmojiPtr();
_emoji = nullptr;
query = query.toLower();
auto type = Type::Stickers;
@ -147,17 +147,18 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
internal::BotCommandRows brows;
StickerPack srows;
if (_emoji) {
auto original = _emoji->original();
QMap<uint64, uint64> setsToRequest;
auto &sets = Global::RefStickerSets();
auto &order = Global::StickerSetsOrder();
for (int i = 0, l = order.size(); i < l; ++i) {
auto it = sets.find(order.at(i));
for (auto i = 0, l = order.size(); i != l; ++i) {
auto it = sets.find(order[i]);
if (it != sets.cend()) {
if (it->emoji.isEmpty()) {
setsToRequest.insert(it->id, it->access);
it->flags |= MTPDstickerSet_ClientFlag::f_not_loaded;
} else if (!(it->flags & MTPDstickerSet::Flag::f_archived)) {
StickersByEmojiMap::const_iterator i = it->emoji.constFind(emojiGetNoColor(_emoji));
auto i = it->emoji.constFind(original);
if (i != it->emoji.cend()) {
srows += *i;
}

View file

@ -2048,8 +2048,8 @@ HistorySticker::HistorySticker(HistoryItem *parent, DocumentData *document) : Hi
, _data(document)
, _emoji(_data->sticker()->alt) {
_data->thumb->load();
if (auto e = emojiFromText(_emoji)) {
_emoji = emojiString(e);
if (auto emoji = Ui::Emoji::Find(_emoji)) {
_emoji = emoji->text();
}
}

View file

@ -131,11 +131,6 @@ HistoryInner::HistoryInner(HistoryWidget *historyWidget, Ui::ScrollArea *scroll,
_touchSelectTimer.setSingleShot(true);
connect(&_touchSelectTimer, SIGNAL(timeout()), this, SLOT(onTouchSelect()));
auto tmp = App::LambdaDelayed(200, this, [this] {
int a = 0;
});
tmp();
setAttribute(Qt::WA_AcceptTouchEvents);
connect(&_touchScrollTimer, SIGNAL(timeout()), this, SLOT(onTouchScrollTimer()));
@ -3319,7 +3314,7 @@ void HistoryWidget::updateStickersByEmoji() {
int len = 0;
if (!_editMsgId) {
auto &text = _field->getTextWithTags().text;
if (auto emoji = emojiFromText(text, &len)) {
if (auto emoji = Ui::Emoji::Find(text, &len)) {
if (text.size() > len) {
len = 0;
} else {

View file

@ -719,7 +719,7 @@ LayerStackWidget::~LayerStackWidget() {
}
MediaPreviewWidget::MediaPreviewWidget(QWidget *parent) : TWidget(parent)
, _emojiSize(EmojiSizes[EIndex + 1] / cIntRetinaFactor()) {
, _emojiSize(Ui::Emoji::Size(Ui::Emoji::Index() + 1) / cIntRetinaFactor()) {
setAttribute(Qt::WA_TransparentForMouseEvents);
subscribe(FileDownload::ImageLoaded(), [this] { update(); });
}
@ -744,12 +744,12 @@ void MediaPreviewWidget::paintEvent(QPaintEvent *e) {
p.fillRect(r, st::stickerPreviewBg);
p.drawPixmap((width() - w) / 2, (height() - h) / 2, image);
if (!_emojiList.isEmpty()) {
int emojiCount = _emojiList.size();
int emojiWidth = (emojiCount * _emojiSize) + (emojiCount - 1) * st::stickerEmojiSkip;
int emojiLeft = (width() - emojiWidth) / 2;
int esize = EmojiSizes[EIndex + 1];
auto emojiCount = _emojiList.size();
auto emojiWidth = (emojiCount * _emojiSize) + (emojiCount - 1) * st::stickerEmojiSkip;
auto emojiLeft = (width() - emojiWidth) / 2;
auto esize = Ui::Emoji::Size(Ui::Emoji::Index() + 1);
for_const (auto emoji, _emojiList) {
p.drawPixmapLeft(emojiLeft, (height() - h) / 2 - (_emojiSize * 2), width(), App::emojiLarge(), QRect(emoji->x * esize, emoji->y * esize, esize, esize));
p.drawPixmapLeft(emojiLeft, (height() - h) / 2 - (_emojiSize * 2), width(), App::emojiLarge(), QRect(emoji->x() * esize, emoji->y() * esize, esize, esize));
emojiLeft += _emojiSize + st::stickerEmojiSkip;
}
}
@ -837,7 +837,7 @@ void MediaPreviewWidget::fillEmojiString() {
_emojiList = getStickerEmojiList(inputSet.c_inputStickerSetID().vid.v);
} else {
_emojiList.clear();
if (auto emoji = emojiFromText(sticker->alt)) {
if (auto emoji = Ui::Emoji::Find(sticker->alt)) {
_emojiList.append(emoji);
}
}

View file

@ -522,7 +522,7 @@ enum {
dbiDownloadPathOld = 0x15,
dbiScale = 0x16,
dbiEmojiTabOld = 0x17,
dbiRecentEmojisOld = 0x18,
dbiRecentEmojiOldOld = 0x18,
dbiLoggedPhoneNumber = 0x19,
dbiMutedPeers = 0x1a,
// 0x1b reserved
@ -534,8 +534,8 @@ enum {
dbiTileBackground = 0x21,
dbiAutoLock = 0x22,
dbiDialogLastPath = 0x23,
dbiRecentEmojis = 0x24,
dbiEmojiVariants = 0x25,
dbiRecentEmojiOld = 0x24,
dbiEmojiVariantsOld = 0x25,
dbiRecentStickers = 0x26,
dbiDcOption = 0x27,
dbiTryIPv6 = 0x28,
@ -550,6 +550,8 @@ enum {
dbiAutoPlay = 0x37,
dbiAdaptiveForWide = 0x38,
dbiHiddenPinnedMessages = 0x39,
dbiRecentEmoji = 0x3a,
dbiEmojiVariants = 0x3b,
dbiDialogsMode = 0x40,
dbiModerateMode = 0x41,
dbiVideoVolume = 0x42,
@ -1308,40 +1310,61 @@ bool _readSetting(quint32 blockId, QDataStream &stream, int version) {
// deprecated
} break;
case dbiRecentEmojisOld: {
RecentEmojisPreloadOld v;
case dbiRecentEmojiOldOld: {
RecentEmojiPreloadOldOld v;
stream >> v;
if (!_checkStreamStatus(stream)) return false;
if (!v.isEmpty()) {
RecentEmojisPreload p;
RecentEmojiPreload p;
p.reserve(v.size());
for (int i = 0; i < v.size(); ++i) {
uint64 e(v.at(i).first);
switch (e) {
case 0xD83CDDEFLLU: e = 0xD83CDDEFD83CDDF5LLU; break;
case 0xD83CDDF0LLU: e = 0xD83CDDF0D83CDDF7LLU; break;
case 0xD83CDDE9LLU: e = 0xD83CDDE9D83CDDEALLU; break;
case 0xD83CDDE8LLU: e = 0xD83CDDE8D83CDDF3LLU; break;
case 0xD83CDDFALLU: e = 0xD83CDDFAD83CDDF8LLU; break;
case 0xD83CDDEBLLU: e = 0xD83CDDEBD83CDDF7LLU; break;
case 0xD83CDDEALLU: e = 0xD83CDDEAD83CDDF8LLU; break;
case 0xD83CDDEELLU: e = 0xD83CDDEED83CDDF9LLU; break;
case 0xD83CDDF7LLU: e = 0xD83CDDF7D83CDDFALLU; break;
case 0xD83CDDECLLU: e = 0xD83CDDECD83CDDE7LLU; break;
for (auto &item : v) {
auto oldKey = uint64(item.first);
switch (oldKey) {
case 0xD83CDDEFLLU: oldKey = 0xD83CDDEFD83CDDF5LLU; break;
case 0xD83CDDF0LLU: oldKey = 0xD83CDDF0D83CDDF7LLU; break;
case 0xD83CDDE9LLU: oldKey = 0xD83CDDE9D83CDDEALLU; break;
case 0xD83CDDE8LLU: oldKey = 0xD83CDDE8D83CDDF3LLU; break;
case 0xD83CDDFALLU: oldKey = 0xD83CDDFAD83CDDF8LLU; break;
case 0xD83CDDEBLLU: oldKey = 0xD83CDDEBD83CDDF7LLU; break;
case 0xD83CDDEALLU: oldKey = 0xD83CDDEAD83CDDF8LLU; break;
case 0xD83CDDEELLU: oldKey = 0xD83CDDEED83CDDF9LLU; break;
case 0xD83CDDF7LLU: oldKey = 0xD83CDDF7D83CDDFALLU; break;
case 0xD83CDDECLLU: oldKey = 0xD83CDDECD83CDDE7LLU; break;
}
auto id = Ui::Emoji::IdFromOldKey(oldKey);
if (!id.isEmpty()) {
p.push_back(qMakePair(id, item.second));
}
p.push_back(qMakePair(e, v.at(i).second));
}
cSetRecentEmojisPreload(p);
cSetRecentEmojiPreload(p);
}
} break;
case dbiRecentEmojis: {
RecentEmojisPreload v;
case dbiRecentEmojiOld: {
RecentEmojiPreloadOld v;
stream >> v;
if (!_checkStreamStatus(stream)) return false;
cSetRecentEmojisPreload(v);
if (!v.isEmpty()) {
RecentEmojiPreload p;
p.reserve(v.size());
for (auto &item : v) {
auto id = Ui::Emoji::IdFromOldKey(item.first);
if (!id.isEmpty()) {
p.push_back(qMakePair(id, item.second));
}
}
cSetRecentEmojiPreload(p);
}
} break;
case dbiRecentEmoji: {
RecentEmojiPreload v;
stream >> v;
if (!_checkStreamStatus(stream)) return false;
cSetRecentEmojiPreload(v);
} break;
case dbiRecentStickers: {
@ -1352,6 +1375,24 @@ bool _readSetting(quint32 blockId, QDataStream &stream, int version) {
cSetRecentStickersPreload(v);
} break;
case dbiEmojiVariantsOld: {
EmojiColorVariantsOld v;
stream >> v;
if (!_checkStreamStatus(stream)) return false;
EmojiColorVariants variants;
for (auto i = v.cbegin(), e = v.cend(); i != e; ++i) {
auto id = Ui::Emoji::IdFromOldKey(static_cast<uint64>(i.key()));
if (!id.isEmpty()) {
auto index = Ui::Emoji::ColorIndexFromOldKey(i.value());
if (index >= 0) {
variants.insert(id, index);
}
}
}
cSetEmojiVariants(variants);
} break;
case dbiEmojiVariants: {
EmojiColorVariants v;
stream >> v;
@ -1360,7 +1401,6 @@ bool _readSetting(quint32 blockId, QDataStream &stream, int version) {
cSetEmojiVariants(v);
} break;
case dbiHiddenPinnedMessages: {
Global::HiddenPinnedMessagesMap v;
stream >> v;
@ -1620,9 +1660,22 @@ void _writeUserSettings() {
_writeMap(WriteMapFast);
}
auto recentEmojiPreloadData = cRecentEmojiPreload();
if (recentEmojiPreloadData.isEmpty()) {
recentEmojiPreloadData.reserve(cGetRecentEmoji().size());
for (auto &item : cGetRecentEmoji()) {
recentEmojiPreloadData.push_back(qMakePair(item.first->id(), item.second));
}
}
uint32 size = 21 * (sizeof(quint32) + sizeof(qint32));
size += sizeof(quint32) + Serialize::stringSize(Global::AskDownloadPath() ? QString() : Global::DownloadPath()) + Serialize::bytearraySize(Global::AskDownloadPath() ? QByteArray() : Global::DownloadPathBookmark());
size += sizeof(quint32) + sizeof(qint32) + (cRecentEmojisPreload().isEmpty() ? cGetRecentEmojis().size() : cRecentEmojisPreload().size()) * (sizeof(uint64) + sizeof(ushort));
size += sizeof(quint32) + sizeof(qint32);
for (auto &item : recentEmojiPreloadData) {
size += Serialize::stringSize(item.first) + sizeof(item.second);
}
size += sizeof(quint32) + sizeof(qint32) + cEmojiVariants().size() * (sizeof(uint32) + sizeof(uint64));
size += sizeof(quint32) + sizeof(qint32) + (cRecentStickersPreload().isEmpty() ? cGetRecentStickers().size() : cRecentStickersPreload().size()) * (sizeof(uint64) + sizeof(ushort));
size += sizeof(quint32) + Serialize::stringSize(cDialogLastPath());
@ -1659,14 +1712,7 @@ void _writeUserSettings() {
data.stream << quint32(dbiDialogsWidthRatio) << qint32(snap(qRound(Global::DialogsWidthRatio() * 1000000), 0, 1000000));
{
RecentEmojisPreload v(cRecentEmojisPreload());
if (v.isEmpty()) {
v.reserve(cGetRecentEmojis().size());
for (RecentEmojiPack::const_iterator i = cGetRecentEmojis().cbegin(), e = cGetRecentEmojis().cend(); i != e; ++i) {
v.push_back(qMakePair(emojiKey(i->first), i->second));
}
}
data.stream << quint32(dbiRecentEmojis) << v;
data.stream << quint32(dbiRecentEmoji) << recentEmojiPreloadData;
}
data.stream << quint32(dbiEmojiVariants) << cEmojiVariants();
{
@ -3097,8 +3143,8 @@ void _writeStickerSet(QDataStream &stream, const Stickers::Set &set) {
if (AppVersion > 9018) {
stream << qint32(set.emoji.size());
for (StickersByEmojiMap::const_iterator j = set.emoji.cbegin(), e = set.emoji.cend(); j != e; ++j) {
stream << emojiString(j.key()) << qint32(j->size());
for (auto j = set.emoji.cbegin(), e = set.emoji.cend(); j != e; ++j) {
stream << j.key()->id() << qint32(j->size());
for (int32 k = 0, l = j->size(); k < l; ++k) {
stream << quint64(j->at(k)->id);
}
@ -3148,7 +3194,7 @@ void _writeStickerSets(FileKey &stickersKey, CheckSet checkSet, const Stickers::
size += sizeof(qint32); // emojiCount
for (auto j = set.emoji.cbegin(), e = set.emoji.cend(); j != e; ++j) {
size += Serialize::stringSize(emojiString(j.key())) + sizeof(qint32) + (j->size() * sizeof(quint64));
size += Serialize::stringSize(j.key()->id()) + sizeof(qint32) + (j->size() * sizeof(quint64));
}
++setsCount;
@ -3304,8 +3350,9 @@ void _readStickerSets(FileKey &stickersKey, Stickers::Order *outOrder = nullptr,
pack.push_back(doc);
}
if (fillStickers) {
if (auto e = emojiGetNoColor(emojiFromText(emojiString))) {
set.emoji.insert(e, pack);
if (auto emoji = Ui::Emoji::Find(emojiString)) {
emoji = emoji->original();
set.emoji.insert(emoji, pack);
}
}
}

View file

@ -5203,20 +5203,22 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
}
it->emoji.clear();
auto &packs = set.vpacks.c_vector().v;
for (int i = 0, l = packs.size(); i < l; ++i) {
if (packs.at(i).type() != mtpc_stickerPack) continue;
for (auto i = 0, l = packs.size(); i != l; ++i) {
if (packs[i].type() != mtpc_stickerPack) continue;
auto &pack = packs.at(i).c_stickerPack();
if (auto e = emojiGetNoColor(emojiFromText(qs(pack.vemoticon)))) {
if (auto emoji = Ui::Emoji::Find(qs(pack.vemoticon))) {
emoji = emoji->original();
auto &stickers = pack.vdocuments.c_vector().v;
StickerPack p;
p.reserve(stickers.size());
for (int j = 0, c = stickers.size(); j < c; ++j) {
auto doc = App::document(stickers.at(j).v);
for (auto j = 0, c = stickers.size(); j != c; ++j) {
auto doc = App::document(stickers[j].v);
if (!doc || !doc->sticker()) continue;
p.push_back(doc);
}
it->emoji.insert(e, p);
it->emoji.insert(emoji, p);
}
}

View file

@ -74,8 +74,8 @@ bool gCompressPastedImage = true;
QString gTimeFormat = qsl("hh:mm");
RecentEmojiPack gRecentEmojis;
RecentEmojisPreload gRecentEmojisPreload;
RecentEmojiPack gRecentEmoji;
RecentEmojiPreload gRecentEmojiPreload;
EmojiColorVariants gEmojiVariants;
RecentStickerPreload gRecentStickersPreload;
@ -223,30 +223,27 @@ void settingsParseArgs(int argc, char *argv[]) {
}
}
RecentEmojiPack &cGetRecentEmojis() {
if (cRecentEmojis().isEmpty()) {
RecentEmojiPack r;
if (!cRecentEmojisPreload().isEmpty()) {
RecentEmojisPreload p(cRecentEmojisPreload());
cSetRecentEmojisPreload(RecentEmojisPreload());
r.reserve(p.size());
for (RecentEmojisPreload::const_iterator i = p.cbegin(), e = p.cend(); i != e; ++i) {
uint64 code = ((!(i->first & 0xFFFFFFFF00000000LLU) && (i->first & 0xFFFFU) == 0xFE0FU)) ? ((i->first >> 16) & 0xFFFFU) : i->first;
EmojiPtr ep(emojiFromKey(code));
if (!ep) continue;
if (ep->postfix) {
int32 j = 0, l = r.size();
for (; j < l; ++j) {
if (emojiKey(r[j].first) == code) {
break;
}
}
if (j < l) {
continue;
RecentEmojiPack &cGetRecentEmoji() {
if (cRecentEmoji().isEmpty()) {
RecentEmojiPack result;
auto haveAlready = [&result](EmojiPtr emoji) {
for (auto &row : result) {
if (row.first->id() == emoji->id()) {
return true;
}
}
return false;
};
if (!cRecentEmojiPreload().isEmpty()) {
auto preload = cRecentEmojiPreload();
cSetRecentEmojiPreload(RecentEmojiPreload());
result.reserve(preload.size());
for (auto i = preload.cbegin(), e = preload.cend(); i != e; ++i) {
if (auto emoji = Ui::Emoji::Find(i->first)) {
if (!haveAlready(emoji)) {
result.push_back(qMakePair(emoji, i->second));
}
}
r.push_back(qMakePair(ep, i->second));
}
}
uint64 defaultRecent[] = {
@ -285,25 +282,18 @@ RecentEmojiPack &cGetRecentEmojis() {
0xD83DDE10LLU,
0xD83DDE15LLU,
};
for (int32 i = 0, s = sizeof(defaultRecent) / sizeof(defaultRecent[0]); i < s; ++i) {
if (r.size() >= EmojiPanPerRow * EmojiPanRowsPerPage) break;
for (auto oldKey : defaultRecent) {
if (result.size() >= EmojiPanPerRow * EmojiPanRowsPerPage) break;
EmojiPtr ep(emojiGet(defaultRecent[i]));
if (!ep || ep == TwoSymbolEmoji) continue;
int32 j = 0, l = r.size();
for (; j < l; ++j) {
if (r[j].first == ep) {
break;
if (auto emoji = Ui::Emoji::FromOldKey(oldKey)) {
if (!haveAlready(emoji)) {
result.push_back(qMakePair(emoji, 1));
}
}
if (j < l) continue;
r.push_back(qMakePair(ep, 1));
}
cSetRecentEmojis(r);
cSetRecentEmoji(result);
}
return cRefRecentEmojis();
return cRefRecentEmoji();
}
RecentStickerPack &cGetRecentStickers() {

View file

@ -141,29 +141,26 @@ T convertScale(T v) {
return v;
}
struct EmojiData {
EmojiData(uint16 x, uint16 y, uint32 code, uint32 code2, uint16 len, uint16 postfix, uint32 color) : x(x), y(y), code(code), code2(code2), len(len), postfix(postfix), color(color) {
}
uint16 x, y;
uint32 code, code2;
uint16 len;
uint16 postfix;
uint32 color;
};
namespace Ui {
namespace Emoji {
class One;
} // namespace Emoji
} // namespace Ui
typedef const EmojiData *EmojiPtr;
static EmojiPtr TwoSymbolEmoji = EmojiPtr(0x01);
using EmojiPtr = const Ui::Emoji::One*;
typedef QVector<EmojiPtr> EmojiPack;
typedef QVector<QPair<uint32, ushort> > RecentEmojisPreloadOld;
typedef QVector<QPair<uint64, ushort> > RecentEmojisPreload;
typedef QVector<QPair<EmojiPtr, ushort> > RecentEmojiPack;
typedef QMap<uint32, uint64> EmojiColorVariants;
DeclareRefSetting(RecentEmojiPack, RecentEmojis);
DeclareSetting(RecentEmojisPreload, RecentEmojisPreload);
using EmojiPack = QVector<EmojiPtr>;
using RecentEmojiPreloadOldOld = QVector<QPair<uint32, ushort>>;
using RecentEmojiPreloadOld = QVector<QPair<uint64, ushort>>;
using RecentEmojiPreload = QVector<QPair<QString, ushort>>;
using RecentEmojiPack = QVector<QPair<EmojiPtr, ushort>>;
using EmojiColorVariantsOld = QMap<uint32, uint64>;
using EmojiColorVariants = QMap<QString, int>;
DeclareRefSetting(RecentEmojiPack, RecentEmoji);
DeclareSetting(RecentEmojiPreload, RecentEmojiPreload);
DeclareRefSetting(EmojiColorVariants, EmojiVariants);
RecentEmojiPack &cGetRecentEmojis();
RecentEmojiPack &cGetRecentEmoji();
class DocumentData;
typedef QVector<DocumentData*> StickerPack;

View file

@ -40,13 +40,16 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "mainwidget.h"
namespace internal {
namespace {
constexpr auto kSaveRecentEmojiTimeout = 3000;
} // namespace
EmojiColorPicker::EmojiColorPicker(QWidget *parent) : TWidget(parent) {
memset(_variants, 0, sizeof(_variants));
setMouseTracking(true);
auto w = st::emojiPanMargins.left() + st::emojiPanSize.width() * (EmojiColorsCount + 1) + 4 * st::emojiColorsPadding + st::emojiColorsSep + st::emojiPanMargins.right();
auto w = st::emojiPanMargins.left() + st::emojiPanSize.width() + st::emojiColorsSep + st::emojiPanMargins.right();
auto h = st::emojiPanMargins.top() + 2 * st::emojiColorsPadding + st::emojiPanSize.height() + st::emojiPanMargins.bottom();
resize(w, h);
@ -54,19 +57,20 @@ EmojiColorPicker::EmojiColorPicker(QWidget *parent) : TWidget(parent) {
connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(hideAnimated()));
}
void EmojiColorPicker::showEmoji(uint32 code) {
EmojiPtr e = emojiGet(code);
if (!e || e == TwoSymbolEmoji || !e->color) {
void EmojiColorPicker::showEmoji(EmojiPtr emoji) {
if (!emoji || !emoji->hasVariants()) {
return;
}
_ignoreShow = false;
_variants[0] = e;
_variants[1] = emojiGet(e, 0xD83CDFFB);
_variants[2] = emojiGet(e, 0xD83CDFFC);
_variants[3] = emojiGet(e, 0xD83CDFFD);
_variants[4] = emojiGet(e, 0xD83CDFFE);
_variants[5] = emojiGet(e, 0xD83CDFFF);
_variants.resize(emoji->variantsCount() + 1);
for (auto i = 0, size = _variants.size(); i != size; ++i) {
_variants[i] = emoji->variant(i);
}
auto w = st::emojiPanMargins.left() + st::emojiPanSize.width() * _variants.size() + (_variants.size() - 2) * st::emojiColorsPadding + st::emojiColorsSep + st::emojiPanMargins.right();
auto h = st::emojiPanMargins.top() + 2 * st::emojiColorsPadding + st::emojiPanSize.height() + st::emojiPanMargins.bottom();
resize(w, h);
if (!_cache.isNull()) _cache = QPixmap();
showAnimated();
@ -99,8 +103,8 @@ void EmojiColorPicker::paintEvent(QPaintEvent *e) {
if (rtl()) x = width() - x - st::emojiColorsSep;
p.fillRect(x, st::emojiPanMargins.top() + st::emojiColorsPadding, st::emojiColorsSep, inner.height() - st::emojiColorsPadding * 2, st::emojiColorsSepColor);
if (!_variants[0]) return;
for (int i = 0; i < EmojiColorsCount + 1; ++i) {
if (_variants.isEmpty()) return;
for (auto i = 0, count = _variants.size(); i != count; ++i) {
drawVariant(p, i);
}
}
@ -212,7 +216,7 @@ void EmojiColorPicker::updateSelected() {
newSelected = 0;
} else {
x -= st::emojiPanSize.width() + 2 * st::emojiColorsPadding + st::emojiColorsSep;
if (x >= 0 && x < st::emojiPanSize.width() * EmojiColorsCount) {
if (x >= 0 && x < st::emojiPanSize.width() * (_variants.size() - 1)) {
newSelected = (x / st::emojiPanSize.width()) + 1;
}
}
@ -242,8 +246,8 @@ void EmojiColorPicker::drawVariant(Painter &p, int variant) {
if (rtl()) tl.setX(width() - tl.x() - st::emojiPanSize.width());
App::roundRect(p, QRect(tl, st::emojiPanSize), st::emojiPanHover, StickerHoverCorners);
}
int esize = EmojiSizes[EIndex + 1];
p.drawPixmapLeft(w.x() + (st::emojiPanSize.width() - (esize / cIntRetinaFactor())) / 2, w.y() + (st::emojiPanSize.height() - (esize / cIntRetinaFactor())) / 2, width(), App::emojiLarge(), QRect(_variants[variant]->x * esize, _variants[variant]->y * esize, esize, esize));
auto esize = Ui::Emoji::Size(Ui::Emoji::Index() + 1);
p.drawPixmapLeft(w.x() + (st::emojiPanSize.width() - (esize / cIntRetinaFactor())) / 2, w.y() + (st::emojiPanSize.height() - (esize / cIntRetinaFactor())) / 2, width(), App::emojiLarge(), QRect(_variants[variant]->x() * esize, _variants[variant]->y() * esize, esize, esize));
}
EmojiPanInner::EmojiPanInner(QWidget *parent) : TWidget(parent)
@ -256,10 +260,10 @@ EmojiPanInner::EmojiPanInner(QWidget *parent) : TWidget(parent)
_picker->hide();
_esize = EmojiSizes[EIndex + 1];
_esize = Ui::Emoji::Size(Ui::Emoji::Index() + 1);
for (auto i = 0; i != emojiTabCount; ++i) {
_counts[i] = emojiPackCount(emojiTabAtIndex(i));
_counts[i] = Ui::Emoji::GetPackCount(emojiTabAtIndex(i));
}
_showPickerTimer.setSingleShot(true);
@ -279,9 +283,10 @@ void EmojiPanInner::setVisibleTopBottom(int visibleTop, int visibleBottom) {
}
int EmojiPanInner::countHeight() {
int result = 0;
for (int i = 0; i < emojiTabCount; ++i) {
int cnt = emojiPackCount(emojiTabAtIndex(i)), rows = (cnt / EmojiPanPerRow) + ((cnt % EmojiPanPerRow) ? 1 : 0);
auto result = 0;
for (auto i = 0; i != emojiTabCount; ++i) {
auto cnt = Ui::Emoji::GetPackCount(emojiTabAtIndex(i));
auto rows = (cnt / EmojiPanPerRow) + ((cnt % EmojiPanPerRow) ? 1 : 0);
result += st::emojiPanHeader + rows * st::emojiPanSize.height();
}
@ -314,18 +319,13 @@ void EmojiPanInner::paintEvent(QPaintEvent *e) {
y += st::emojiPanHeader;
if (_emojis[c].isEmpty()) {
_emojis[c] = emojiPack(emojiTabAtIndex(c));
_emojis[c] = Ui::Emoji::GetPack(emojiTabAtIndex(c));
if (emojiTabAtIndex(c) != dbietRecent) {
for (EmojiPack::iterator i = _emojis[c].begin(), e = _emojis[c].end(); i != e; ++i) {
if ((*i)->color) {
EmojiColorVariants::const_iterator j = cEmojiVariants().constFind((*i)->code);
for (auto &emoji : _emojis[c]) {
if (emoji->hasVariants()) {
auto j = cEmojiVariants().constFind(emoji->nonColoredId());
if (j != cEmojiVariants().cend()) {
EmojiPtr replace = emojiFromKey(j.value());
if (replace) {
if (replace != TwoSymbolEmoji && replace->code == (*i)->code && replace->code2 == (*i)->code2) {
*i = replace;
}
}
emoji = emoji->variant(j.value());
}
}
}
@ -347,7 +347,7 @@ void EmojiPanInner::paintEvent(QPaintEvent *e) {
if (rtl()) tl.setX(width() - tl.x() - st::emojiPanSize.width());
App::roundRect(p, QRect(tl, st::emojiPanSize), st::emojiPanHover, StickerHoverCorners);
}
p.drawPixmapLeft(w.x() + (st::emojiPanSize.width() - (_esize / cIntRetinaFactor())) / 2, w.y() + (st::emojiPanSize.height() - (_esize / cIntRetinaFactor())) / 2, width(), App::emojiLarge(), QRect(_emojis[c][index]->x * _esize, _emojis[c][index]->y * _esize, _esize, _esize));
p.drawPixmapLeft(w.x() + (st::emojiPanSize.width() - (_esize / cIntRetinaFactor())) / 2, w.y() + (st::emojiPanSize.height() - (_esize / cIntRetinaFactor())) / 2, width(), App::emojiLarge(), QRect(_emojis[c][index]->x() * _esize, _emojis[c][index]->y() * _esize, _esize, _esize));
}
}
}
@ -373,10 +373,10 @@ void EmojiPanInner::mousePressEvent(QMouseEvent *e) {
if (_selected >= 0) {
int tab = (_selected / MatrixRowShift), sel = _selected % MatrixRowShift;
if (tab < emojiTabCount && sel < _emojis[tab].size() && _emojis[tab][sel]->color) {
if (tab < emojiTabCount && sel < _emojis[tab].size() && _emojis[tab][sel]->hasVariants()) {
_pickerSel = _selected;
setCursor(style::cur_default);
if (cEmojiVariants().constFind(_emojis[tab][sel]->code) == cEmojiVariants().cend()) {
if (!cEmojiVariants().contains(_emojis[tab][sel]->nonColoredId())) {
onShowPicker();
} else {
_showPickerTimer.start(500);
@ -395,8 +395,8 @@ void EmojiPanInner::mouseReleaseEvent(QMouseEvent *e) {
return _picker->handleMouseRelease(QCursor::pos());
} else if (_pickerSel >= 0) {
int tab = (_pickerSel / MatrixRowShift), sel = _pickerSel % MatrixRowShift;
if (tab < emojiTabCount && sel < _emojis[tab].size() && _emojis[tab][sel]->color) {
if (cEmojiVariants().constFind(_emojis[tab][sel]->code) != cEmojiVariants().cend()) {
if (tab < emojiTabCount && sel < _emojis[tab].size() && _emojis[tab][sel]->hasVariants()) {
if (cEmojiVariants().contains(_emojis[tab][sel]->nonColoredId())) {
_picker->hideAnimated();
_pickerSel = -1;
}
@ -420,15 +420,15 @@ void EmojiPanInner::mouseReleaseEvent(QMouseEvent *e) {
int tab = (_selected / MatrixRowShift), sel = _selected % MatrixRowShift;
if (sel < _emojis[tab].size()) {
EmojiPtr emoji(_emojis[tab][sel]);
if (emoji->color && !_picker->isHidden()) return;
if (emoji->hasVariants() && !_picker->isHidden()) return;
selectEmoji(emoji);
}
}
void EmojiPanInner::selectEmoji(EmojiPtr emoji) {
RecentEmojiPack &recent(cGetRecentEmojis());
RecentEmojiPack::iterator i = recent.begin(), e = recent.end();
auto &recent = cGetRecentEmoji();
auto i = recent.begin(), e = recent.end();
for (; i != e; ++i) {
if (i->first == emoji) {
++i->second;
@ -460,7 +460,7 @@ void EmojiPanInner::selectEmoji(EmojiPtr emoji) {
qSwap(*i, *(i - 1));
}
}
emit saveConfigDelayed(SaveRecentEmojisTimeout);
emit saveConfigDelayed(kSaveRecentEmojiTimeout);
emit selected(emoji);
}
@ -469,14 +469,16 @@ void EmojiPanInner::onShowPicker() {
if (_pickerSel < 0) return;
int tab = (_pickerSel / MatrixRowShift), sel = _pickerSel % MatrixRowShift;
if (tab < emojiTabCount && sel < _emojis[tab].size() && _emojis[tab][sel]->color) {
if (tab < emojiTabCount && sel < _emojis[tab].size() && _emojis[tab][sel]->hasVariants()) {
_picker->showEmoji(_emojis[tab][sel]);
int32 y = 0;
for (int c = 0; c <= tab; ++c) {
int32 size = (c == tab) ? (sel - (sel % EmojiPanPerRow)) : _counts[c], rows = (size / EmojiPanPerRow) + ((size % EmojiPanPerRow) ? 1 : 0);
y += st::emojiPanHeader + (rows * st::emojiPanSize.height());
}
y -= _picker->height() - st::buttonRadius + _visibleTop;
if (y < 0) {
if (y < st::emojiPanHeader) {
y += _picker->height() - st::buttonRadius + st::emojiPanSize.height() - st::buttonRadius;
}
int xmax = width() - _picker->width();
@ -484,7 +486,6 @@ void EmojiPanInner::onShowPicker() {
if (rtl()) coef = 1. - coef;
_picker->move(qRound(xmax * coef), y);
_picker->showEmoji(_emojis[tab][sel]->code);
emit disableScroll(true);
}
}
@ -516,8 +517,8 @@ QRect EmojiPanInner::emojiRect(int tab, int sel) {
}
void EmojiPanInner::onColorSelected(EmojiPtr emoji) {
if (emoji->color) {
cRefEmojiVariants().insert(emoji->code, emojiKey(emoji));
if (emoji->colored()) {
cRefEmojiVariants().insert(emoji->nonColoredId(), emoji->variantIndex(emoji));
}
if (_pickerSel >= 0) {
int tab = (_pickerSel / MatrixRowShift), sel = _pickerSel % MatrixRowShift;
@ -584,8 +585,8 @@ void EmojiPanInner::hideFinish() {
void EmojiPanInner::refreshRecent() {
clearSelection();
_counts[0] = emojiPackCount(dbietRecent);
_emojis[0] = emojiPack(dbietRecent);
_counts[0] = Ui::Emoji::GetPackCount(dbietRecent);
_emojis[0] = Ui::Emoji::GetPack(dbietRecent);
int32 h = countHeight();
if (h != height()) {
resize(width(), h);
@ -2199,7 +2200,7 @@ void StickerPanInner::showStickerSet(uint64 setId) {
if (!showingInlineItems()) {
_section = Section::Gifs;
cSetShowingSavedGifs(true);
emit saveConfigDelayed(SaveRecentEmojisTimeout);
emit saveConfigDelayed(kSaveRecentEmojiTimeout);
}
refreshSavedGifs();
emit scrollToY(0);
@ -2215,7 +2216,7 @@ void StickerPanInner::showStickerSet(uint64 setId) {
_setGifCommand = false;
cSetShowingSavedGifs(false);
emit saveConfigDelayed(SaveRecentEmojisTimeout);
emit saveConfigDelayed(kSaveRecentEmojiTimeout);
Notify::clipStopperHidden(ClipStopperSavedGifsPanel);
}

View file

@ -42,7 +42,6 @@ class RippleAnimation;
namespace internal {
constexpr int InlineItemsMaxPerRow = 5;
constexpr int EmojiColorsCount = 5;
using InlineResult = InlineBots::Result;
using InlineResults = QList<InlineBots::Result*>;
@ -64,7 +63,7 @@ class EmojiColorPicker : public TWidget {
public:
EmojiColorPicker(QWidget *parent);
void showEmoji(uint32 code);
void showEmoji(EmojiPtr emoji);
void clearSelection();
void handleMouseMove(QPoint globalPos);
@ -98,7 +97,7 @@ private:
bool _ignoreShow = false;
EmojiPtr _variants[EmojiColorsCount + 1];
QVector<EmojiPtr> _variants;
int _selected = -1;
int _pressedSel = -1;

View file

@ -139,7 +139,7 @@ void EmptyUserpic::Impl::fillString(const QString &name) {
auto ch = name.constData(), end = ch + name.size();
while (ch != end) {
auto emojiLength = 0;
if (auto emoji = emojiFromText(ch, end, &emojiLength)) {
if (auto emoji = Ui::Emoji::Find(ch, end, &emojiLength)) {
ch += emojiLength;
} else if (ch->isHighSurrogate()) {
++ch;

File diff suppressed because it is too large Load diff

View file

@ -22,149 +22,219 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "ui/text/text.h"
void emojiInit();
EmojiPtr emojiGet(uint32 code);
EmojiPtr emojiGet(uint32 code, uint32 code2);
EmojiPtr emojiGet(EmojiPtr emoji, uint32 color);
EmojiPtr emojiGet(const QChar *from, const QChar *end);
QString emojiGetSequence(int index);
namespace Ui {
namespace Emoji {
namespace internal {
inline QString emojiString(EmojiPtr emoji) {
if ((emoji->code & 0xFFFF0000U) == 0xFFFF0000U) { // sequence
return emojiGetSequence(emoji->code & 0xFFFFU);
}
EmojiPtr ByIndex(int index);
QString result;
result.reserve(emoji->len + (emoji->postfix ? 1 : 0));
if (!(emoji->code >> 16)) {
result.append(QChar(emoji->code & 0xFFFF));
} else {
result.append(QChar((emoji->code >> 16) & 0xFFFF));
result.append(QChar(emoji->code & 0xFFFF));
if (emoji->code2) {
result.append(QChar((emoji->code2 >> 16) & 0xFFFF));
result.append(QChar(emoji->code2 & 0xFFFF));
}
}
if (emoji->color && ((emoji->color & 0xFFFF0000U) != 0xFFFF0000U)) {
result.append(QChar((emoji->color >> 16) & 0xFFFF));
result.append(QChar(emoji->color & 0xFFFF));
}
if (emoji->postfix) result.append(QChar(emoji->postfix));
return result;
}
EmojiPtr Find(const QChar *ch, const QChar *end, int *outLength = nullptr);
inline uint64 emojiKey(EmojiPtr emoji) {
uint64 key = emoji->code;
if (emoji->code2) {
key = (key << 32) | uint64(emoji->code2);
} else if (emoji->color && ((emoji->color & 0xFFFF0000U) != 0xFFFF0000U)) {
key = (key << 32) | uint64(emoji->color);
}
return key;
}
inline EmojiPtr emojiFromKey(uint64 key) {
uint32 code = uint32(key >> 32), code2 = uint32(key & 0xFFFFFFFFLLU);
if (!code && code2) {
code = code2;
code2 = 0;
}
EmojiPtr emoji = emojiGet(code);
if (emoji == TwoSymbolEmoji) {
return emojiGet(code, code2);
} else if (emoji && emoji->color && code2) {
return emojiGet(emoji, code2);
}
return emoji;
}
inline EmojiPtr emojiFromUrl(const QString &url) {
return emojiFromKey(url.midRef(10).toULongLong(0, 16)); // skip emoji://e.
}
inline EmojiPtr emojiFromText(const QChar *ch, const QChar *end, int *outLength = nullptr) {
EmojiPtr emoji = nullptr;
if (ch + 1 < end && ((ch->isHighSurrogate() && (ch + 1)->isLowSurrogate()) || (((ch->unicode() >= 0x30 && ch->unicode() < 0x3A) || ch->unicode() == 0x23 || ch->unicode() == 0x2A) && (ch + 1)->unicode() == 0x20E3))) {
uint32 code = (ch->unicode() << 16) | (ch + 1)->unicode();
emoji = emojiGet(code);
if (emoji) {
if (emoji == TwoSymbolEmoji) { // check two symbol
if (ch + 3 >= end) {
emoji = 0;
} else {
uint32 code2 = ((uint32((ch + 2)->unicode()) << 16) | uint32((ch + 3)->unicode()));
emoji = emojiGet(code, code2);
}
} else {
if (ch + 2 < end && (ch + 2)->unicode() == 0x200D) { // check sequence
EmojiPtr seq = emojiGet(ch, end);
if (seq) {
emoji = seq;
}
}
}
}
} else if (ch + 2 < end && ((ch->unicode() >= 0x30 && ch->unicode() < 0x3A) || ch->unicode() == 0x23 || ch->unicode() == 0x2A) && (ch + 1)->unicode() == 0xFE0F && (ch + 2)->unicode() == 0x20E3) {
uint32 code = (ch->unicode() << 16) | (ch + 2)->unicode();
emoji = emojiGet(code);
if (outLength) *outLength = emoji->len + 1;
return emoji;
} else if (ch < end) {
emoji = emojiGet(ch->unicode());
t_assert(emoji != TwoSymbolEmoji);
}
if (emoji) {
int32 len = emoji->len + ((ch + emoji->len < end && (ch + emoji->len)->unicode() == 0xFE0F) ? 1 : 0);
if (emoji->color && (ch + len + 1 < end && (ch + len)->isHighSurrogate() && (ch + len + 1)->isLowSurrogate())) { // color
uint32 color = ((uint32((ch + len)->unicode()) << 16) | uint32((ch + len + 1)->unicode()));
EmojiPtr col = emojiGet(emoji, color);
if (col && col != emoji) {
len += col->len - emoji->len;
emoji = col;
if (ch + len < end && (ch + len)->unicode() == 0xFE0F) {
++len;
}
}
}
if (outLength) *outLength = len;
}
return emoji;
}
inline EmojiPtr emojiFromText(const QString &text, int32 *plen = 0) {
return text.isEmpty() ? EmojiPtr(0) : emojiFromText(text.constBegin(), text.constEnd(), plen);
}
inline EmojiPtr emojiGetNoColor(EmojiPtr emoji) {
if (emoji && emoji->color && (emoji->color & 0xFFFF0000U) != 0xFFFF0000U) {
EmojiPtr result = emojiGet(emoji->code);
return (result == TwoSymbolEmoji) ? emojiGet(emoji->code, emoji->code2) : result;
}
return emoji;
}
extern int EmojiSizes[5], EIndex, ESize;
extern const char *EmojiNames[5], *EName;
void emojiFind(const QChar *ch, const QChar *e, const QChar *&newEmojiEnd, uint32 &emojiCode);
inline bool emojiEdge(const QChar *ch) {
inline bool IsReplaceEdge(const QChar *ch) {
return true;
switch (ch->unicode()) {
case '.': case ',': case ':': case ';': case '!': case '?': case '#': case '@':
case '(': case ')': case '[': case ']': case '{': case '}': case '<': case '>':
case '+': case '=': case '-': case '_': case '*': case '/': case '\\': case '^': case '$':
case '"': case '\'':
case 8212: case 171: case 187: // --, <<, >>
return true;
}
return false;
// switch (ch->unicode()) {
// case '.': case ',': case ':': case ';': case '!': case '?': case '#': case '@':
// case '(': case ')': case '[': case ']': case '{': case '}': case '<': case '>':
// case '+': case '=': case '-': case '_': case '*': case '/': case '\\': case '^': case '$':
// case '"': case '\'':
// case 8212: case 171: case 187: // --, <<, >>
// return true;
// }
// return false;
}
EmojiPtr FindReplace(const QChar *ch, const QChar *end, int *outLength = nullptr);
} // namespace internal
void Init();
constexpr auto kPostfix = static_cast<ushort>(0xFE0F);
class One {
public:
QString id() const {
return _id;
}
QString text() const {
return hasPostfix() ? (_id + QChar(kPostfix)) : _id;
}
bool colored() const {
return (_original != nullptr);
}
EmojiPtr original() const {
return _original ? _original : this;
}
QString nonColoredId() const {
return original()->id();
}
bool hasPostfix() const {
return _hasPostfix;
}
bool hasVariants() const {
return _colorizable || colored();
}
int variantsCount() const;
int variantIndex(EmojiPtr variant) const;
EmojiPtr variant(int index) const;
int index() const;
QString toUrl() const {
return qsl("emoji://e.") + QString::number(index());
}
int x() const {
return _x;
}
int y() const {
return _y;
}
private:
One() = default; // For QVector<> to compile.
One(const One &other) = default;
One(const QString &id, uint16 x, uint16 y, bool hasPostfix, bool colorizable, EmojiPtr original)
: _id(id)
, _x(x)
, _y(y)
, _hasPostfix(hasPostfix)
, _colorizable(colorizable)
, _original(original) {
t_assert(!_colorizable || !colored());
}
const QString _id;
const uint16 _x = 0;
const uint16 _y = 0;
const bool _hasPostfix = false;
const bool _colorizable = false;
const EmojiPtr _original = nullptr;
friend void Init();
friend class QVector<One>;
};
inline EmojiPtr FromUrl(const QString &url) {
auto start = qstr("emoji://e.");
if (url.startsWith(start)) {
return internal::ByIndex(url.midRef(start.size()).toInt()); // skip emoji://e.
}
return nullptr;
}
inline EmojiPtr Find(const QChar *ch, const QChar *end, int *outLength = nullptr) {
if (ch != end) {
if (auto result = internal::Find(ch, end, outLength)) {
if (outLength && result->hasPostfix()) {
// Try to consume a pending 0xFE0F postfix.
// Comment out hasPostfix() check if you want to consume it anyway.
auto resultEnd = ch + *outLength;
if (resultEnd != end && resultEnd->unicode() == kPostfix) {
++*outLength;
}
}
return result;
}
}
return nullptr;
}
inline EmojiPtr Find(const QString &text, int *outLength = nullptr) {
return Find(text.constBegin(), text.constEnd(), outLength);
}
inline QString IdFromOldKey(uint64 oldKey) {
auto code = uint32(oldKey >> 32);
auto code2 = uint32(oldKey & 0xFFFFFFFFLLU);
if (!code && code2) {
code = base::take(code2);
}
if ((code & 0xFFFF0000U) != 0xFFFF0000U) { // code and code2 contain the whole id
auto result = QString();
result.reserve(4);
auto addCode = [&result](uint32 code) {
if (auto high = (code >> 16)) {
result.append(QChar(static_cast<ushort>(high & 0xFFFFU)));
}
result.append(QChar(static_cast<ushort>(code & 0xFFFFU)));
};
addCode(code);
if (code2) addCode(code2);
return result;
}
// old sequence
auto sequenceIndex = int(code & 0xFFFFU);
switch (sequenceIndex) {
case 0: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa7");
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");
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");
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");
case 4: return QString::fromUtf8("\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa6");
case 5: return QString::fromUtf8("\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa7");
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");
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");
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");
case 9: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa6");
case 10: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa7");
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");
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");
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");
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");
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");
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");
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");
case 18: return QString::fromUtf8("\xf0\x9f\x91\x81\xe2\x80\x8d\xf0\x9f\x97\xa8");
}
return QString();
}
inline EmojiPtr FromOldKey(uint64 oldKey) {
return Find(IdFromOldKey(oldKey));
}
inline int ColorIndexFromCode(uint32 code) {
switch (code) {
case 0xD83CDFFB: return 1;
case 0xD83CDFFC: return 2;
case 0xD83CDFFD: return 3;
case 0xD83CDFFE: return 4;
case 0xD83CDFFF: return 5;
}
return 0;
}
inline int ColorIndexFromOldKey(uint64 oldKey) {
return ColorIndexFromCode(uint32(oldKey & 0xFFFFFFFFLLU));
}
int Index();
inline int Size(int index = Index()) {
int sizes[] = { 18, 22, 27, 36, 45 };
return sizes[index];
}
inline QString Filename(int index = Index()) {
const char *EmojiNames[] = {
":/gui/art/emoji.webp",
":/gui/art/emoji_125x.webp",
":/gui/art/emoji_150x.webp",
":/gui/art/emoji_200x.webp",
":/gui/art/emoji_250x.webp",
};
return QString::fromLatin1(EmojiNames[index]);
}
int GetPackCount(DBIEmojiTab tab);
EmojiPack GetPack(DBIEmojiTab tab);
inline void appendPartToResult(QString &result, const QChar *start, const QChar *from, const QChar *to, EntitiesInText *inOutEntities) {
if (to > from) {
for (auto &entity : *inOutEntities) {
@ -181,48 +251,43 @@ inline void appendPartToResult(QString &result, const QChar *start, const QChar
}
}
inline QString replaceEmojis(const QString &text, EntitiesInText *inOutEntities) {
QString result;
auto currentEntity = inOutEntities->begin(), entitiesEnd = inOutEntities->end();
const QChar *emojiStart = text.constData(), *emojiEnd = emojiStart, *e = text.constData() + text.size();
bool canFindEmoji = true;
for (const QChar *ch = emojiEnd; ch != e;) {
uint32 emojiCode = 0;
const QChar *newEmojiEnd = 0;
if (canFindEmoji) {
emojiFind(ch, e, newEmojiEnd, emojiCode);
}
inline QString ReplaceInText(const QString &text, EntitiesInText *inOutEntities) {
auto result = QString();
auto currentEntity = inOutEntities->begin();
auto entitiesEnd = inOutEntities->end();
auto emojiStart = text.constData();
auto emojiEnd = emojiStart;
auto end = emojiStart + text.size();
auto canFindEmoji = true;
for (auto ch = emojiEnd; ch != end;) {
auto emojiLength = 0;
auto emoji = canFindEmoji ? internal::FindReplace(ch, end, &emojiLength) : nullptr;
auto newEmojiEnd = ch + emojiLength;
while (currentEntity != entitiesEnd && ch >= emojiStart + currentEntity->offset() + currentEntity->length()) {
++currentEntity;
}
EmojiPtr emoji = emojiCode ? emojiGet(emojiCode) : 0;
if (emoji && emoji != TwoSymbolEmoji &&
if (emoji &&
(ch == emojiStart || !ch->isLetterOrNumber() || !(ch - 1)->isLetterOrNumber()) &&
(newEmojiEnd == e || !newEmojiEnd->isLetterOrNumber() || newEmojiEnd == emojiStart || !(newEmojiEnd - 1)->isLetterOrNumber()) &&
(newEmojiEnd == end || !newEmojiEnd->isLetterOrNumber() || newEmojiEnd == emojiStart || !(newEmojiEnd - 1)->isLetterOrNumber()) &&
(currentEntity == entitiesEnd || (ch < emojiStart + currentEntity->offset() && newEmojiEnd <= emojiStart + currentEntity->offset()) || (ch >= emojiStart + currentEntity->offset() + currentEntity->length() && newEmojiEnd > emojiStart + currentEntity->offset() + currentEntity->length()))
) {
if (result.isEmpty()) result.reserve(text.size());
appendPartToResult(result, emojiStart, emojiEnd, ch, inOutEntities);
if (emoji->color) {
EmojiColorVariants::const_iterator it = cEmojiVariants().constFind(emoji->code);
if (emoji->hasVariants()) {
auto it = cEmojiVariants().constFind(emoji->nonColoredId());
if (it != cEmojiVariants().cend()) {
EmojiPtr replace = emojiFromKey(it.value());
if (replace) {
if (replace != TwoSymbolEmoji && replace->code == emoji->code && replace->code2 == emoji->code2) {
emoji = replace;
}
}
emoji = emoji->variant(it.value());
}
}
result.append(emojiString(emoji));
result.append(emoji->text());
ch = emojiEnd = newEmojiEnd;
canFindEmoji = true;
} else {
if (emojiEdge(ch)) {
if (internal::IsReplaceEdge(ch)) {
canFindEmoji = true;
} else {
canFindEmoji = false;
@ -232,10 +297,10 @@ inline QString replaceEmojis(const QString &text, EntitiesInText *inOutEntities)
}
if (result.isEmpty()) return text;
appendPartToResult(result, emojiStart, emojiEnd, e, inOutEntities);
appendPartToResult(result, emojiStart, emojiEnd, end, inOutEntities);
return result;
}
int emojiPackCount(DBIEmojiTab tab);
EmojiPack emojiPack(DBIEmojiTab tab);
} // namespace Emoji
} // namespace Ui

View file

@ -132,7 +132,6 @@ const QChar *textSkipCommand(const QChar *from, const QChar *end, bool canLink)
class TextParser {
public:
static Qt::LayoutDirection stringDirection(const QString &str, int32 from, int32 to) {
const ushort *p = reinterpret_cast<const ushort*>(str.unicode()) + from;
const ushort *end = p + (to - from);
@ -479,14 +478,14 @@ public:
void parseEmojiFromCurrent() {
int len = 0;
EmojiPtr e = emojiFromText(ptr - emojiLookback, end, &len);
auto e = Ui::Emoji::Find(ptr - emojiLookback, end, &len);
if (!e) return;
for (int l = len - emojiLookback - 1; l > 0; --l) {
_t->_text.push_back(*++ptr);
}
if (e->postfix && _t->_text.at(_t->_text.size() - 1).unicode() != e->postfix) {
_t->_text.push_back(e->postfix);
if (e->hasPostfix() && _t->_text.at(_t->_text.size() - 1).unicode() != Ui::Emoji::kPostfix) {
_t->_text.push_back(QChar(Ui::Emoji::kPostfix));
++len;
}
@ -498,9 +497,6 @@ public:
src(text),
rich(options.flags & TextParseRichText),
multiline(options.flags & TextParseMultiline),
maxLnkIndex(0),
flags(0),
lnkIndex(0),
stopAfterWidth(QFIXED_MAX) {
if (options.flags & TextParseLinks) {
textParseEntities(src, options.flags, &entities, rich);
@ -511,9 +507,6 @@ public:
src(textWithEntities.text),
rich(options.flags & TextParseRichText),
multiline(options.flags & TextParseMultiline),
maxLnkIndex(0),
flags(0),
lnkIndex(0),
stopAfterWidth(QFIXED_MAX) {
auto preparsed = textWithEntities.entities;
if ((options.flags & TextParseLinks) && !preparsed.isEmpty()) {
@ -686,16 +679,17 @@ private:
typedef QMap<const QChar*, QList<int32> > RemoveFlagsMap;
RemoveFlagsMap removeFlags;
uint16 maxLnkIndex;
uint16 maxLnkIndex = 0;
// current state
int32 flags;
uint16 lnkIndex;
const EmojiData *emoji; // current emoji, if current word is an emoji, or zero
int32 blockStart; // offset in result, from which current parsed block is started
int32 diacs; // diac chars skipped without good char
int32 flags = 0;
uint16 lnkIndex = 0;
EmojiPtr emoji = nullptr; // current emoji, if current word is an emoji, or zero
int32 blockStart = 0; // offset in result, from which current parsed block is started
int32 diacs = 0; // diac chars skipped without good char
QFixed sumWidth, stopAfterWidth; // summary width of all added words
bool sumFinished, newlineAwaited;
bool sumFinished = false;
bool newlineAwaited = false;
// current char data
QChar ch; // current char (low surrogate, if current char is surrogate pair)
@ -2516,35 +2510,31 @@ void Text::setMarkedText(const style::TextStyle &st, const TextWithEntities &tex
_st = &st;
clear();
{
// QString newText; // utf16 of the text for emoji
// utf codes of the text display for emoji extraction
// auto text = textWithEntities.text;
// auto newText = QString();
// newText.reserve(8 * text.size());
// newText.append("\t{ ");
// for (const QChar *ch = text.constData(), *e = ch + text.size(); ch != e; ++ch) {
// if (chIsNewline(*ch)) {
// newText.append(*ch);
// if (*ch == TextCommand) {
// break;
// } else if (chIsNewline(*ch)) {
// newText.append("},").append(*ch).append("\t{ ");
// } else {
// if (ch->isHighSurrogate() || ch->isLowSurrogate()) {
// if (ch->isHighSurrogate() && (ch + 1 != e) && ((ch + 1)->isLowSurrogate())) {
// newText.append("0x").append(QString::number((uint32(ch->unicode()) << 16) | uint32((ch + 1)->unicode()), 16).toUpper()).append("LLU,");
// newText.append("0x").append(QString::number((uint32(ch->unicode()) << 16) | uint32((ch + 1)->unicode()), 16).toUpper()).append("U, ");
// ++ch;
// } else {
// newText.append("BADx").append(QString::number(ch->unicode(), 16).toUpper()).append("LLU,");
// newText.append("BADx").append(QString::number(ch->unicode(), 16).toUpper()).append("U, ");
// }
// } else {
// newText.append("0x").append(QString::number(ch->unicode(), 16).toUpper()).append("LLU,");
// newText.append("0x").append(QString::number(ch->unicode(), 16).toUpper()).append("U, ");
// }
// }
// }
// newText.append("\n\n").append(text);
// TextParser parser(this, newText, EntitiesInText(), options);
// QString newText; // utf8 of the text for emoji sequences
// newText.reserve(8 * text.size());
// QByteArray ba = text.toUtf8();
// for (int32 i = 0, l = ba.size(); i < l; ++i) {
// newText.append("\\x").append(QString::number(uchar(ba.at(i)), 16).toLower());
// }
// newText.append("\n\n").append(text);
// TextParser parser(this, newText, EntitiesInText(), options);
// newText.append("},\n\n").append(text);
// TextParser parser(this, { newText, EntitiesInText() }, options);
TextParser parser(this, textWithEntities, options);
}
@ -3016,5 +3006,6 @@ void Text::clearFields() {
}
void emojiDraw(QPainter &p, EmojiPtr e, int x, int y) {
p.drawPixmap(QPoint(x, y), App::emoji(), QRect(e->x * ESize, e->y * ESize, ESize, ESize));
auto size = Ui::Emoji::Size();
p.drawPixmap(QPoint(x, y), App::emoji(), QRect(e->x() * size, e->y() * size, size, size));
}

View file

@ -347,7 +347,7 @@ TextBlock::TextBlock(const style::font &font, const QString &str, QFixed minResi
}
}
EmojiBlock::EmojiBlock(const style::font &font, const QString &str, uint16 from, uint16 length, uchar flags, uint16 lnkIndex, const EmojiData *emoji) : ITextBlock(font, str, from, length, flags, lnkIndex), emoji(emoji) {
EmojiBlock::EmojiBlock(const style::font &font, const QString &str, uint16 from, uint16 length, uchar flags, uint16 lnkIndex, EmojiPtr emoji) : ITextBlock(font, str, from, length, flags, lnkIndex), emoji(emoji) {
_flags |= ((TextBlockTEmoji & 0x0F) << 8);
_width = int(st::emojiSize + 2 * st::emojiPadding);
}

View file

@ -196,9 +196,9 @@ public:
private:
EmojiBlock(const style::font &font, const QString &str, uint16 from, uint16 length, uchar flags, uint16 lnkIndex, const EmojiData *emoji);
EmojiBlock(const style::font &font, const QString &str, uint16 from, uint16 length, uchar flags, uint16 lnkIndex, EmojiPtr emoji);
const EmojiData *emoji;
EmojiPtr emoji = nullptr;
friend class Text;
friend class TextParser;

View file

@ -1215,59 +1215,60 @@ bool textSplit(QString &sendingText, EntitiesInText &sendingEntities, QString &l
++currentEntity;
}
#define MARK_GOOD_AS_LEVEL(level) \
if (goodLevel <= (level)) {\
goodLevel = (level);\
good = ch;\
goodEntity = currentEntity;\
goodInEntity = inEntity;\
goodCanBreakEntity = canBreakEntity;\
}
if (s > half) {
bool inEntity = (currentEntity < entityCount) && (ch > start + leftEntities.at(currentEntity).offset()) && (ch < start + leftEntities.at(currentEntity).offset() + leftEntities.at(currentEntity).length());
EntityInTextType entityType = (currentEntity < entityCount) ? leftEntities.at(currentEntity).type() : EntityInTextInvalid;
bool canBreakEntity = (entityType == EntityInTextPre || entityType == EntityInTextCode);
int32 noEntityLevel = inEntity ? 0 : 1;
auto markGoodAsLevel = [&](int newLevel) {
if (goodLevel > newLevel) {
return;
}
goodLevel = newLevel;
good = ch;
goodEntity = currentEntity;
goodInEntity = inEntity;
goodCanBreakEntity = canBreakEntity;
};
if (inEntity && !canBreakEntity) {
MARK_GOOD_AS_LEVEL(0);
markGoodAsLevel(0);
} else {
if (chIsNewline(*ch)) {
if (inEntity) {
if (ch + 1 < end && chIsNewline(*(ch + 1))) {
MARK_GOOD_AS_LEVEL(12);
markGoodAsLevel(12);
} else {
MARK_GOOD_AS_LEVEL(11);
markGoodAsLevel(11);
}
} else if (ch + 1 < end && chIsNewline(*(ch + 1))) {
MARK_GOOD_AS_LEVEL(15);
markGoodAsLevel(15);
} else if (currentEntity < entityCount && ch + 1 == start + leftEntities.at(currentEntity).offset() && leftEntities.at(currentEntity).type() == EntityInTextPre) {
MARK_GOOD_AS_LEVEL(14);
markGoodAsLevel(14);
} else if (currentEntity > 0 && ch == start + leftEntities.at(currentEntity - 1).offset() + leftEntities.at(currentEntity - 1).length() && leftEntities.at(currentEntity - 1).type() == EntityInTextPre) {
MARK_GOOD_AS_LEVEL(14);
markGoodAsLevel(14);
} else {
MARK_GOOD_AS_LEVEL(13);
markGoodAsLevel(13);
}
} else if (chIsSpace(*ch)) {
if (chIsSentenceEnd(*(ch - 1))) {
MARK_GOOD_AS_LEVEL(9 + noEntityLevel);
markGoodAsLevel(9 + noEntityLevel);
} else if (chIsSentencePartEnd(*(ch - 1))) {
MARK_GOOD_AS_LEVEL(7 + noEntityLevel);
markGoodAsLevel(7 + noEntityLevel);
} else {
MARK_GOOD_AS_LEVEL(5 + noEntityLevel);
markGoodAsLevel(5 + noEntityLevel);
}
} else if (chIsWordSeparator(*(ch - 1))) {
MARK_GOOD_AS_LEVEL(3 + noEntityLevel);
markGoodAsLevel(3 + noEntityLevel);
} else {
MARK_GOOD_AS_LEVEL(1 + noEntityLevel);
markGoodAsLevel(1 + noEntityLevel);
}
}
}
#undef MARK_GOOD_AS_LEVEL
int elen = 0;
if (EmojiPtr e = emojiFromText(ch, end, &elen)) {
if (auto e = Ui::Emoji::Find(ch, end, &elen)) {
for (int i = 0; i < elen; ++i, ++ch, ++s) {
if (ch->isHighSurrogate() && i + 1 < elen && (ch + 1)->isLowSurrogate()) {
++ch;
@ -1927,7 +1928,7 @@ QString prepareTextWithEntities(QString result, int32 flags, EntitiesInText *inO
replaceStringWithEntities(qstr(">>"), QChar(187), result, inOutEntities);
if (cReplaceEmojis()) {
result = replaceEmojis(result, inOutEntities);
result = Ui::Emoji::ReplaceInText(result, inOutEntities);
}
trimTextWithEntities(result, inOutEntities);

View file

@ -398,11 +398,9 @@ EmojiPtr FlatTextarea::getSingleEmoji() const {
getSingleEmojiFragment(text, fragment);
if (!text.isEmpty()) {
QTextCharFormat format = fragment.charFormat();
QString imageName = static_cast<QTextImageFormat*>(&format)->name();
if (imageName.startsWith(qstr("emoji://e."))) {
return emojiFromUrl(imageName);
}
auto format = fragment.charFormat();
auto imageName = static_cast<QTextImageFormat*>(&format)->name();
return Ui::Emoji::FromUrl(imageName);
}
return nullptr;
}
@ -617,8 +615,8 @@ void FlatTextarea::getSingleEmojiFragment(QString &text, QTextFragment &fragment
t = t.mid(0, end - p);
}
if (f.isImageFormat() && !t.isEmpty() && t.at(0).unicode() == QChar::ObjectReplacementCharacter) {
QString imageName = static_cast<QTextImageFormat*>(&f)->name();
if (imageName.startsWith(qstr("emoji://e."))) {
auto imageName = static_cast<QTextImageFormat*>(&f)->name();
if (Ui::Emoji::FromUrl(imageName)) {
fragment = fr;
text = t;
return;
@ -771,25 +769,23 @@ QString FlatTextarea::getTextPart(int start, int end, TagList *outTagsList, bool
case 0xfdd0: // QTextBeginningOfFrame
case 0xfdd1: // QTextEndOfFrame
case QChar::ParagraphSeparator:
case QChar::LineSeparator:
*uc = QLatin1Char('\n');
break;
case QChar::Nbsp:
*uc = QLatin1Char(' ');
break;
case QChar::ObjectReplacementCharacter:
if (emojiText.isEmpty() && f.isImageFormat()) {
QString imageName = static_cast<QTextImageFormat*>(&f)->name();
if (imageName.startsWith(qstr("emoji://e."))) {
if (EmojiPtr emoji = emojiFromUrl(imageName)) {
emojiText = emojiString(emoji);
case QChar::LineSeparator: {
*uc = QLatin1Char('\n');
} break;
case QChar::Nbsp: {
*uc = QLatin1Char(' ');
} break;
case QChar::ObjectReplacementCharacter: {
if (emojiText.isEmpty() && f.isImageFormat()) {
auto imageName = static_cast<QTextImageFormat*>(&f)->name();
if (auto emoji = Ui::Emoji::FromUrl(imageName)) {
emojiText = emoji->text();
}
}
}
if (uc > ub) result.append(ub, uc - ub);
if (!emojiText.isEmpty()) result.append(emojiText);
ub = uc + 1;
break;
if (uc > ub) result.append(ub, uc - ub);
if (!emojiText.isEmpty()) result.append(emojiText);
ub = uc + 1;
} break;
}
}
if (uc > ub) result.append(ub, uc - ub);
@ -947,10 +943,11 @@ void FlatTextarea::insertFromMimeData(const QMimeData *source) {
void FlatTextarea::insertEmoji(EmojiPtr emoji, QTextCursor c) {
QTextImageFormat imageFormat;
int32 ew = ESize + st::emojiPadding * cIntRetinaFactor() * 2, eh = _st.font->height * cIntRetinaFactor();
auto ew = Ui::Emoji::Size() + st::emojiPadding * cIntRetinaFactor() * 2;
auto eh = _st.font->height * cIntRetinaFactor();
imageFormat.setWidth(ew / cIntRetinaFactor());
imageFormat.setHeight(eh / cIntRetinaFactor());
imageFormat.setName(qsl("emoji://e.") + QString::number(emojiKey(emoji), 16));
imageFormat.setName(emoji->toUrl());
imageFormat.setVerticalAlignment(QTextCharFormat::AlignBaseline);
if (c.charFormat().isAnchor()) {
imageFormat.setAnchor(true);
@ -962,11 +959,9 @@ void FlatTextarea::insertEmoji(EmojiPtr emoji, QTextCursor c) {
}
QVariant FlatTextarea::loadResource(int type, const QUrl &name) {
QString imageName = name.toDisplayString();
if (imageName.startsWith(qstr("emoji://e."))) {
if (EmojiPtr emoji = emojiFromUrl(imageName)) {
return QVariant(App::emojiSingle(emoji, _st.font->height));
}
auto imageName = name.toDisplayString();
if (auto emoji = Ui::Emoji::FromUrl(imageName)) {
return QVariant(App::emojiSingle(emoji, _st.font->height));
}
return QVariant();
}
@ -1141,7 +1136,7 @@ void FlatTextarea::processFormatting(int insertPosition, int insertEnd) {
auto *ch = textStart + qMax(changedPositionInFragment, 0);
for (; ch < textEnd; ++ch) {
int emojiLength = 0;
if (auto emoji = emojiFromText(ch, textEnd, &emojiLength)) {
if (auto emoji = Ui::Emoji::Find(ch, textEnd, &emojiLength)) {
// Replace emoji if no current action is prepared.
if (action.type == ActionType::Invalid) {
action.type = ActionType::InsertEmoji;
@ -2057,25 +2052,23 @@ QString InputArea::getText(int32 start, int32 end) const {
case 0xfdd0: // QTextBeginningOfFrame
case 0xfdd1: // QTextEndOfFrame
case QChar::ParagraphSeparator:
case QChar::LineSeparator:
case QChar::LineSeparator: {
*uc = QLatin1Char('\n');
break;
case QChar::Nbsp:
} break;
case QChar::Nbsp: {
*uc = QLatin1Char(' ');
break;
case QChar::ObjectReplacementCharacter:
} break;
case QChar::ObjectReplacementCharacter: {
if (emojiText.isEmpty() && f.isImageFormat()) {
QString imageName = static_cast<QTextImageFormat*>(&f)->name();
if (imageName.startsWith(qstr("emoji://e."))) {
if (EmojiPtr emoji = emojiFromUrl(imageName)) {
emojiText = emojiString(emoji);
}
auto imageName = static_cast<QTextImageFormat*>(&f)->name();
if (auto emoji = Ui::Emoji::FromUrl(imageName)) {
emojiText = emoji->text();
}
}
if (uc > ub) result.append(ub, uc - ub);
if (!emojiText.isEmpty()) result.append(emojiText);
ub = uc + 1;
break;
} break;
}
}
if (uc > ub) result.append(ub, uc - ub);
@ -2110,10 +2103,11 @@ bool InputArea::isRedoAvailable() const {
void InputArea::insertEmoji(EmojiPtr emoji, QTextCursor c) {
QTextImageFormat imageFormat;
int32 ew = ESize + st::emojiPadding * cIntRetinaFactor() * 2, eh = _st.font->height * cIntRetinaFactor();
auto ew = Ui::Emoji::Size() + st::emojiPadding * cIntRetinaFactor() * 2;
auto eh = _st.font->height * cIntRetinaFactor();
imageFormat.setWidth(ew / cIntRetinaFactor());
imageFormat.setHeight(eh / cIntRetinaFactor());
imageFormat.setName(qsl("emoji://e.") + QString::number(emojiKey(emoji), 16));
imageFormat.setName(emoji->toUrl());
imageFormat.setVerticalAlignment(QTextCharFormat::AlignBaseline);
static QString objectReplacement(QChar::ObjectReplacementCharacter);
@ -2121,18 +2115,16 @@ void InputArea::insertEmoji(EmojiPtr emoji, QTextCursor c) {
}
QVariant InputArea::Inner::loadResource(int type, const QUrl &name) {
QString imageName = name.toDisplayString();
if (imageName.startsWith(qstr("emoji://e."))) {
if (EmojiPtr emoji = emojiFromUrl(imageName)) {
return QVariant(App::emojiSingle(emoji, f()->_st.font->height));
}
auto imageName = name.toDisplayString();
if (auto emoji = Ui::Emoji::FromUrl(imageName)) {
return QVariant(App::emojiSingle(emoji, f()->_st.font->height));
}
return QVariant();
}
void InputArea::processDocumentContentsChange(int position, int charsAdded) {
int32 replacePosition = -1, replaceLen = 0;
const EmojiData *emoji = 0;
EmojiPtr emoji = nullptr;
static QString regular = qsl("Open Sans"), semibold = qsl("Open Sans Semibold");
bool checkTilde = !cRetina() && (_inner->font().pixelSize() == 13) && (_inner->font().family() == regular);
@ -2164,7 +2156,7 @@ void InputArea::processDocumentContentsChange(int position, int charsAdded) {
const QChar *ch = t.constData(), *e = ch + t.size();
for (; ch != e; ++ch, ++fp) {
int32 emojiLen = 0;
emoji = emojiFromText(ch, e, &emojiLen);
emoji = Ui::Emoji::Find(ch, e, &emojiLen);
if (emoji) {
if (replacePosition >= 0) {
emoji = 0; // replace tilde char format first
@ -2791,25 +2783,23 @@ QString InputField::getText(int32 start, int32 end) const {
case 0xfdd0: // QTextBeginningOfFrame
case 0xfdd1: // QTextEndOfFrame
case QChar::ParagraphSeparator:
case QChar::LineSeparator:
case QChar::LineSeparator: {
*uc = QLatin1Char('\n');
break;
case QChar::Nbsp:
} break;
case QChar::Nbsp: {
*uc = QLatin1Char(' ');
break;
case QChar::ObjectReplacementCharacter:
} break;
case QChar::ObjectReplacementCharacter: {
if (emojiText.isEmpty() && f.isImageFormat()) {
QString imageName = static_cast<QTextImageFormat*>(&f)->name();
if (imageName.startsWith(qstr("emoji://e."))) {
if (EmojiPtr emoji = emojiFromUrl(imageName)) {
emojiText = emojiString(emoji);
}
auto imageName = static_cast<QTextImageFormat*>(&f)->name();
if (auto emoji = Ui::Emoji::FromUrl(imageName)) {
emojiText = emoji->text();
}
}
if (uc > ub) result.append(ub, uc - ub);
if (!emojiText.isEmpty()) result.append(emojiText);
ub = uc + 1;
break;
} break;
}
}
if (uc > ub) result.append(ub, uc - ub);
@ -2844,10 +2834,10 @@ bool InputField::isRedoAvailable() const {
void InputField::insertEmoji(EmojiPtr emoji, QTextCursor c) {
QTextImageFormat imageFormat;
int32 ew = ESize + st::emojiPadding * cIntRetinaFactor() * 2, eh = _st.font->height * cIntRetinaFactor();
auto ew = Ui::Emoji::Size() + st::emojiPadding * cIntRetinaFactor() * 2, eh = _st.font->height * cIntRetinaFactor();
imageFormat.setWidth(ew / cIntRetinaFactor());
imageFormat.setHeight(eh / cIntRetinaFactor());
imageFormat.setName(qsl("emoji://e.") + QString::number(emojiKey(emoji), 16));
imageFormat.setName(emoji->toUrl());
imageFormat.setVerticalAlignment(QTextCharFormat::AlignBaseline);
static QString objectReplacement(QChar::ObjectReplacementCharacter);
@ -2856,17 +2846,15 @@ void InputField::insertEmoji(EmojiPtr emoji, QTextCursor c) {
QVariant InputField::Inner::loadResource(int type, const QUrl &name) {
QString imageName = name.toDisplayString();
if (imageName.startsWith(qstr("emoji://e."))) {
if (EmojiPtr emoji = emojiFromUrl(imageName)) {
return QVariant(App::emojiSingle(emoji, f()->_st.font->height));
}
if (auto emoji = Ui::Emoji::FromUrl(imageName)) {
return QVariant(App::emojiSingle(emoji, f()->_st.font->height));
}
return QVariant();
}
void InputField::processDocumentContentsChange(int position, int charsAdded) {
int32 replacePosition = -1, replaceLen = 0;
const EmojiData *emoji = 0;
EmojiPtr emoji = nullptr;
bool newlineFound = false;
static QString regular = qsl("Open Sans"), semibold = qsl("Open Sans Semibold"), space(' ');
@ -2910,8 +2898,8 @@ void InputField::processDocumentContentsChange(int position, int charsAdded) {
break;
}
int32 emojiLen = 0;
emoji = emojiFromText(ch, e, &emojiLen);
auto emojiLen = 0;
emoji = Ui::Emoji::Find(ch, e, &emojiLen);
if (emoji) {
if (replacePosition >= 0) {
emoji = 0; // replace tilde char format first

View file

@ -42,7 +42,7 @@ QString fillLetters(const QString &name) {
auto ch = name.constData(), end = ch + name.size();
while (ch != end) {
auto emojiLength = 0;
if (auto emoji = emojiFromText(ch, end, &emojiLength)) {
if (auto emoji = Ui::Emoji::Find(ch, end, &emojiLength)) {
ch += emojiLength;
} else if (ch->isHighSurrogate()) {
++ch;

View file

@ -124,5 +124,33 @@
'<(src_loc)/codegen/numbers/processor.cpp',
'<(src_loc)/codegen/numbers/processor.h',
],
}, {
'target_name': 'codegen_emoji',
'variables': {
'libs_loc': '../../../Libraries',
'src_loc': '../SourceFiles',
'mac_target': '10.10',
},
'includes': [
'common_executable.gypi',
'qt.gypi',
],
'include_dirs': [
'<(src_loc)',
],
'sources': [
'<(src_loc)/codegen/common/cpp_file.cpp',
'<(src_loc)/codegen/common/cpp_file.h',
'<(src_loc)/codegen/common/logging.cpp',
'<(src_loc)/codegen/common/logging.h',
'<(src_loc)/codegen/emoji/data.cpp',
'<(src_loc)/codegen/emoji/data.h',
'<(src_loc)/codegen/emoji/generator.cpp',
'<(src_loc)/codegen/emoji/generator.h',
'<(src_loc)/codegen/emoji/main.cpp',
'<(src_loc)/codegen/emoji/options.cpp',
'<(src_loc)/codegen/emoji/options.h',
],
}],
}

View file

@ -232,6 +232,13 @@
'-rdynamic',
],
}],
[ 'build_mac', {
'xcode_settings': {
'OTHER_LDFLAGS': [
'-lcups',
],
},
}],
],
'rules': [{

View file

@ -26,7 +26,6 @@
'CURRENT_PROJECT_VERSION': '<!(./print_version.sh)',
'ASSETCATALOG_COMPILER_APPICON_NAME': 'AppIcon',
'OTHER_LDFLAGS': [
'-lcups',
'-lbsm',
'-lm',
'-lssl',