2017-02-15 08:50:11 +00:00
|
|
|
/*
|
|
|
|
This file is part of Telegram Desktop,
|
2018-01-03 10:23:14 +00:00
|
|
|
the official desktop application for the Telegram messaging service.
|
2017-02-15 08:50:11 +00:00
|
|
|
|
2018-01-03 10:23:14 +00:00
|
|
|
For license and copyright information please follow this link:
|
|
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
2017-02-15 08:50:11 +00:00
|
|
|
*/
|
|
|
|
#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>
|
|
|
|
|
2017-04-03 18:49:07 +00:00
|
|
|
#ifdef SUPPORT_IMAGE_GENERATION
|
2017-02-15 08:50:11 +00:00
|
|
|
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
|
2017-04-03 18:49:07 +00:00
|
|
|
#endif // SUPPORT_IMAGE_GENERATION
|
2017-02-15 08:50:11 +00:00
|
|
|
|
|
|
|
namespace codegen {
|
|
|
|
namespace emoji {
|
|
|
|
namespace {
|
|
|
|
|
2017-07-19 18:37:49 +00:00
|
|
|
constexpr auto kErrorCantWritePath = 851;
|
|
|
|
|
|
|
|
constexpr auto kOriginalBits = 12;
|
|
|
|
constexpr auto kIdSizeBits = 6;
|
|
|
|
constexpr auto kColumnBits = 6;
|
|
|
|
constexpr auto kRowBits = 6;
|
2017-02-15 08:50:11 +00:00
|
|
|
|
|
|
|
common::ProjectInfo Project = {
|
|
|
|
"codegen_emoji",
|
|
|
|
"empty",
|
2017-03-31 19:08:25 +00:00
|
|
|
false, // forceReGenerate
|
2017-02-15 08:50:11 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-07-24 16:18:39 +00:00
|
|
|
uint32 Crc32Table[256];
|
|
|
|
class Crc32Initializer {
|
|
|
|
public:
|
|
|
|
Crc32Initializer() {
|
|
|
|
uint32 poly = 0x04C11DB7U;
|
|
|
|
for (auto i = 0; i != 256; ++i) {
|
|
|
|
Crc32Table[i] = reflect(i, 8) << 24;
|
|
|
|
for (auto j = 0; j != 8; ++j) {
|
|
|
|
Crc32Table[i] = (Crc32Table[i] << 1) ^ (Crc32Table[i] & (1 << 31) ? poly : 0);
|
|
|
|
}
|
|
|
|
Crc32Table[i] = reflect(Crc32Table[i], 32);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
uint32 reflect(uint32 val, char ch) {
|
|
|
|
uint32 result = 0;
|
|
|
|
for (int i = 1; i < (ch + 1); ++i) {
|
|
|
|
if (val & 1) {
|
|
|
|
result |= 1 << (ch - i);
|
|
|
|
}
|
|
|
|
val >>= 1;
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
uint32 countCrc32(const void *data, std::size_t size) {
|
|
|
|
static Crc32Initializer InitTable;
|
|
|
|
|
|
|
|
auto buffer = static_cast<const unsigned char*>(data);
|
|
|
|
auto result = uint32(0xFFFFFFFFU);
|
|
|
|
for (auto i = std::size_t(0); i != size; ++i) {
|
|
|
|
result = (result >> 8) ^ Crc32Table[(result & 0xFFU) ^ buffer[i]];
|
|
|
|
}
|
|
|
|
return (result ^ 0xFFFFFFFFU);
|
|
|
|
}
|
|
|
|
|
2017-02-15 08:50:11 +00:00
|
|
|
} // namespace
|
|
|
|
|
2017-04-03 18:49:07 +00:00
|
|
|
Generator::Generator(const Options &options) : project_(Project)
|
|
|
|
#ifdef SUPPORT_IMAGE_GENERATION
|
|
|
|
, writeImages_(options.writeImages)
|
|
|
|
#endif // SUPPORT_IMAGE_GENERATION
|
2017-07-19 18:37:49 +00:00
|
|
|
, data_(PrepareData())
|
|
|
|
, replaces_(PrepareReplaces(options.replacesPath)) {
|
2017-02-15 08:50:11 +00:00
|
|
|
QDir dir(options.outputPath);
|
|
|
|
if (!dir.mkpath(".")) {
|
|
|
|
common::logError(kErrorCantWritePath, "Command Line") << "can not open path for writing: " << dir.absolutePath().toStdString();
|
|
|
|
data_ = Data();
|
|
|
|
}
|
2017-07-19 18:37:49 +00:00
|
|
|
if (!CheckAndConvertReplaces(replaces_, data_)) {
|
|
|
|
replaces_ = Replaces(replaces_.filename);
|
|
|
|
}
|
2017-02-15 08:50:11 +00:00
|
|
|
|
2017-03-31 19:08:25 +00:00
|
|
|
outputPath_ = dir.absolutePath() + "/emoji";
|
2017-02-15 08:50:11 +00:00
|
|
|
spritePath_ = dir.absolutePath() + "/emoji";
|
2017-07-24 16:18:39 +00:00
|
|
|
suggestionsPath_ = dir.absolutePath() + "/emoji_suggestions_data";
|
2017-02-15 08:50:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int Generator::generate() {
|
2017-07-19 18:37:49 +00:00
|
|
|
if (data_.list.empty() || replaces_.list.isEmpty()) {
|
2017-02-15 08:50:11 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2017-04-03 18:49:07 +00:00
|
|
|
#ifdef SUPPORT_IMAGE_GENERATION
|
2017-03-31 19:08:25 +00:00
|
|
|
if (writeImages_) {
|
|
|
|
return writeImages() ? 0 : -1;
|
|
|
|
}
|
2017-04-03 18:49:07 +00:00
|
|
|
#endif // SUPPORT_IMAGE_GENERATION
|
2017-02-15 08:50:11 +00:00
|
|
|
|
|
|
|
if (!writeSource()) {
|
|
|
|
return -1;
|
|
|
|
}
|
2017-03-31 19:08:25 +00:00
|
|
|
if (!writeHeader()) {
|
|
|
|
return -1;
|
|
|
|
}
|
2017-07-24 16:18:39 +00:00
|
|
|
if (!writeSuggestionsSource()) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (!writeSuggestionsHeader()) {
|
|
|
|
return -1;
|
|
|
|
}
|
2017-02-15 08:50:11 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
constexpr auto kVariantsCount = 5;
|
|
|
|
constexpr auto kEmojiInRow = 40;
|
|
|
|
|
2017-04-03 18:49:07 +00:00
|
|
|
#ifdef SUPPORT_IMAGE_GENERATION
|
2017-02-15 08:50:11 +00:00
|
|
|
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;
|
|
|
|
}
|
2017-04-03 18:49:07 +00:00
|
|
|
#endif // SUPPORT_IMAGE_GENERATION
|
2017-02-15 08:50:11 +00:00
|
|
|
|
|
|
|
bool Generator::writeSource() {
|
|
|
|
source_ = std::make_unique<common::CppFile>(outputPath_ + ".cpp", project_);
|
|
|
|
|
2017-07-24 16:18:39 +00:00
|
|
|
source_->include("emoji_suggestions_data.h").newline();
|
2017-02-15 08:50:11 +00:00
|
|
|
source_->pushNamespace("Ui").pushNamespace("Emoji").pushNamespace();
|
|
|
|
source_->stream() << "\
|
|
|
|
\n\
|
2017-02-25 20:35:55 +00:00
|
|
|
std::vector<One> Items;\n\
|
2017-02-15 08:50:11 +00:00
|
|
|
\n";
|
2017-03-31 19:08:25 +00:00
|
|
|
if (!writeInitCode()) {
|
2017-02-15 08:50:11 +00:00
|
|
|
return false;
|
|
|
|
}
|
2017-03-31 19:08:25 +00:00
|
|
|
if (!writeSections()) {
|
2017-02-15 08:50:11 +00:00
|
|
|
return false;
|
|
|
|
}
|
2017-03-31 19:08:25 +00:00
|
|
|
if (!writeFindReplace()) {
|
2017-02-15 08:50:11 +00:00
|
|
|
return false;
|
|
|
|
}
|
2017-03-31 19:08:25 +00:00
|
|
|
if (!writeFind()) {
|
2017-02-15 08:50:11 +00:00
|
|
|
return false;
|
|
|
|
}
|
2017-03-31 19:08:25 +00:00
|
|
|
source_->popNamespace().newline().pushNamespace("internal");
|
2017-02-15 08:50:11 +00:00
|
|
|
source_->stream() << "\
|
|
|
|
\n\
|
2017-03-31 19:08:25 +00:00
|
|
|
EmojiPtr ByIndex(int index) {\n\
|
|
|
|
return (index >= 0 && index < Items.size()) ? &Items[index] : nullptr;\n\
|
2017-02-15 08:50:11 +00:00
|
|
|
}\n\
|
|
|
|
\n\
|
2017-03-31 19:08:25 +00:00
|
|
|
EmojiPtr FindReplace(const QChar *start, const QChar *end, int *outLength) {\n\
|
|
|
|
auto index = FindReplaceIndex(start, end, outLength);\n\
|
|
|
|
return index ? &Items[index - 1] : nullptr;\n\
|
2017-02-15 08:50:11 +00:00
|
|
|
}\n\
|
|
|
|
\n\
|
2018-05-13 15:14:02 +00:00
|
|
|
const std::vector<std::pair<QString, int>> GetReplacementPairs() {\n\
|
|
|
|
return ReplacementPairs;\n\
|
|
|
|
}\n\
|
|
|
|
\n\
|
2017-03-31 19:08:25 +00:00
|
|
|
EmojiPtr Find(const QChar *start, const QChar *end, int *outLength) {\n\
|
|
|
|
auto index = FindIndex(start, end, outLength);\n\
|
|
|
|
return index ? &Items[index - 1] : nullptr;\n\
|
2017-02-15 08:50:11 +00:00
|
|
|
}\n\
|
|
|
|
\n\
|
2017-03-31 19:08:25 +00:00
|
|
|
void Init() {\n\
|
|
|
|
auto id = IdData;\n\
|
2017-07-19 18:37:49 +00:00
|
|
|
auto takeString = [&id](int size) {\n\
|
|
|
|
auto result = QString::fromRawData(reinterpret_cast<const QChar*>(id), size);\n\
|
|
|
|
id += size;\n\
|
|
|
|
return result;\n\
|
|
|
|
};\n\
|
|
|
|
\n\
|
2017-03-31 19:08:25 +00:00
|
|
|
Items.reserve(base::array_size(Data));\n\
|
|
|
|
for (auto &data : Data) {\n\
|
2017-07-19 18:37:49 +00:00
|
|
|
Items.emplace_back(takeString(data.idSize), uint16(data.column), uint16(data.row), bool(data.postfixed), bool(data.variated), data.original ? &Items[data.original - 1] : nullptr, One::CreationTag());\n\
|
2017-03-31 19:08:25 +00:00
|
|
|
}\n\
|
2017-07-19 18:37:49 +00:00
|
|
|
InitReplacements();\n\
|
2017-02-15 08:50:11 +00:00
|
|
|
}\n\
|
|
|
|
\n";
|
2017-03-31 19:08:25 +00:00
|
|
|
source_->popNamespace();
|
|
|
|
|
|
|
|
if (!writeGetSections()) {
|
|
|
|
return false;
|
|
|
|
}
|
2017-02-15 08:50:11 +00:00
|
|
|
|
|
|
|
return source_->finalize();
|
|
|
|
}
|
|
|
|
|
2017-03-31 19:08:25 +00:00
|
|
|
bool Generator::writeHeader() {
|
|
|
|
auto header = std::make_unique<common::CppFile>(outputPath_ + ".h", project_);
|
|
|
|
header->pushNamespace("Ui").pushNamespace("Emoji").pushNamespace("internal");
|
|
|
|
header->stream() << "\
|
2017-02-15 08:50:11 +00:00
|
|
|
\n\
|
2017-03-31 19:08:25 +00:00
|
|
|
void Init();\n\
|
2017-02-15 08:50:11 +00:00
|
|
|
\n\
|
2017-03-31 19:08:25 +00:00
|
|
|
EmojiPtr ByIndex(int index);\n\
|
|
|
|
\n\
|
|
|
|
EmojiPtr Find(const QChar *ch, const QChar *end, int *outLength = nullptr);\n\
|
|
|
|
\n\
|
|
|
|
inline bool IsReplaceEdge(const QChar *ch) {\n\
|
|
|
|
return true;\n\
|
|
|
|
\n\
|
|
|
|
// switch (ch->unicode()) {\n\
|
|
|
|
// case '.': case ',': case ':': case ';': case '!': case '?': case '#': case '@':\n\
|
|
|
|
// case '(': case ')': case '[': case ']': case '{': case '}': case '<': case '>':\n\
|
|
|
|
// case '+': case '=': case '-': case '_': case '*': case '/': case '\\\\': case '^': case '$':\n\
|
|
|
|
// case '\"': case '\\'':\n\
|
|
|
|
// case 8212: case 171: case 187: // --, <<, >>\n\
|
|
|
|
// return true;\n\
|
|
|
|
// }\n\
|
|
|
|
// return false;\n\
|
|
|
|
}\n\
|
|
|
|
\n\
|
2018-05-13 15:14:02 +00:00
|
|
|
const std::vector<std::pair<QString, int>> GetReplacementPairs();\n\
|
2017-03-31 19:08:25 +00:00
|
|
|
EmojiPtr FindReplace(const QChar *ch, const QChar *end, int *outLength = nullptr);\n\
|
|
|
|
\n";
|
|
|
|
header->popNamespace().stream() << "\
|
2017-02-15 08:50:11 +00:00
|
|
|
\n\
|
2017-03-31 19:08:25 +00:00
|
|
|
enum class Section {\n\
|
|
|
|
Recent,\n\
|
|
|
|
People,\n\
|
|
|
|
Nature,\n\
|
|
|
|
Food,\n\
|
|
|
|
Activity,\n\
|
|
|
|
Travel,\n\
|
|
|
|
Objects,\n\
|
|
|
|
Symbols,\n\
|
|
|
|
};\n\
|
|
|
|
\n\
|
|
|
|
int Index();\n\
|
|
|
|
\n\
|
|
|
|
int GetSectionCount(Section section);\n\
|
|
|
|
EmojiPack GetSection(Section section);\n\
|
2017-02-15 08:50:11 +00:00
|
|
|
\n";
|
2017-03-31 19:08:25 +00:00
|
|
|
return header->finalize();
|
|
|
|
}
|
2017-02-15 08:50:11 +00:00
|
|
|
|
2017-03-31 19:08:25 +00:00
|
|
|
template <typename Callback>
|
|
|
|
bool Generator::enumerateWholeList(Callback callback) {
|
2017-02-15 08:50:11 +00:00
|
|
|
auto column = 0;
|
|
|
|
auto row = 0;
|
|
|
|
auto index = 0;
|
|
|
|
auto variated = -1;
|
|
|
|
auto coloredCount = 0;
|
|
|
|
for (auto &item : data_.list) {
|
2017-07-19 18:37:49 +00:00
|
|
|
if (!callback(item.id, column, row, item.postfixed, item.variated, item.colored, variated)) {
|
|
|
|
return false;
|
|
|
|
}
|
2017-02-15 08:50:11 +00:00
|
|
|
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;
|
|
|
|
}
|
2017-03-31 19:08:25 +00:00
|
|
|
return true;
|
|
|
|
}
|
2017-02-15 08:50:11 +00:00
|
|
|
|
2017-03-31 19:08:25 +00:00
|
|
|
bool Generator::writeInitCode() {
|
2017-02-15 08:50:11 +00:00
|
|
|
source_->stream() << "\
|
2017-03-31 19:08:25 +00:00
|
|
|
struct DataStruct {\n\
|
2017-07-19 18:37:49 +00:00
|
|
|
ushort original : " << kOriginalBits << ";\n\
|
|
|
|
uchar idSize : " << kIdSizeBits << ";\n\
|
|
|
|
uchar column : " << kColumnBits << ";\n\
|
|
|
|
uchar row : " << kRowBits << ";\n\
|
|
|
|
bool postfixed : 1;\n\
|
|
|
|
bool variated : 1;\n\
|
2017-03-31 19:08:25 +00:00
|
|
|
};\n\
|
|
|
|
\n\
|
2017-07-19 18:37:49 +00:00
|
|
|
const ushort IdData[] = {";
|
|
|
|
startBinary();
|
|
|
|
if (!enumerateWholeList([this](Id id, int column, int row, bool isPostfixed, bool isVariated, bool isColored, int original) {
|
2017-07-24 16:18:39 +00:00
|
|
|
return writeStringBinary(source_.get(), id);
|
2017-03-31 19:08:25 +00:00
|
|
|
})) {
|
|
|
|
return false;
|
|
|
|
}
|
2017-07-19 18:37:49 +00:00
|
|
|
if (_binaryFullLength >= std::numeric_limits<ushort>::max()) {
|
2017-03-31 19:08:25 +00:00
|
|
|
logDataError() << "Too many IdData elements.";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
source_->stream() << " };\n\
|
|
|
|
\n\
|
2017-07-19 18:37:49 +00:00
|
|
|
const DataStruct Data[] = {\n";
|
2017-03-31 19:08:25 +00:00
|
|
|
if (!enumerateWholeList([this](Id id, int column, int row, bool isPostfixed, bool isVariated, bool isColored, int original) {
|
2017-07-19 18:37:49 +00:00
|
|
|
if (original + 1 >= (1 << kOriginalBits)) {
|
|
|
|
logDataError() << "Too many entries.";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (id.size() >= (1 << kIdSizeBits)) {
|
|
|
|
logDataError() << "Too large id.";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (column >= (1 << kColumnBits) || row >= (1 << kRowBits)) {
|
|
|
|
logDataError() << "Bad row-column.";
|
|
|
|
return false;
|
|
|
|
}
|
2017-03-31 19:08:25 +00:00
|
|
|
source_->stream() << "\
|
2017-07-19 18:37:49 +00:00
|
|
|
{ ushort(" << (isColored ? (original + 1) : 0) << "), uchar(" << id.size() << "), uchar(" << column << "), uchar(" << row << "), " << (isPostfixed ? "true" : "false") << ", " << (isVariated ? "true" : "false") << " },\n";
|
|
|
|
return true;
|
2017-03-31 19:08:25 +00:00
|
|
|
})) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
source_->stream() << "\
|
|
|
|
};\n";
|
|
|
|
|
2017-02-15 08:50:11 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-03-29 15:09:16 +00:00
|
|
|
bool Generator::writeSections() {
|
2017-03-31 19:08:25 +00:00
|
|
|
source_->stream() << "\
|
2017-07-19 18:37:49 +00:00
|
|
|
const ushort SectionData[] = {";
|
|
|
|
startBinary();
|
2017-03-31 19:08:25 +00:00
|
|
|
for (auto &category : data_.categories) {
|
|
|
|
for (auto index : category) {
|
2017-07-24 16:18:39 +00:00
|
|
|
writeIntBinary(source_.get(), index);
|
2017-03-31 19:08:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
source_->stream() << " };\n\
|
|
|
|
\n\
|
2017-07-19 18:37:49 +00:00
|
|
|
EmojiPack FillSection(int offset, int size) {\n\
|
2017-03-31 19:08:25 +00:00
|
|
|
auto result = EmojiPack();\n\
|
|
|
|
result.reserve(size);\n\
|
|
|
|
for (auto index : gsl::make_span(SectionData + offset, size)) {\n\
|
|
|
|
result.push_back(&Items[index]);\n\
|
|
|
|
}\n\
|
|
|
|
return result;\n\
|
|
|
|
}\n\n";
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Generator::writeGetSections() {
|
2017-03-29 15:09:16 +00:00
|
|
|
constexpr const char *sectionNames[] = {
|
|
|
|
"Section::People",
|
|
|
|
"Section::Nature",
|
|
|
|
"Section::Food",
|
|
|
|
"Section::Activity",
|
|
|
|
"Section::Travel",
|
|
|
|
"Section::Objects",
|
|
|
|
"Section::Symbols",
|
2017-02-15 08:50:11 +00:00
|
|
|
};
|
|
|
|
source_->stream() << "\
|
|
|
|
\n\
|
2017-03-29 15:09:16 +00:00
|
|
|
int GetSectionCount(Section section) {\n\
|
|
|
|
switch (section) {\n\
|
|
|
|
case Section::Recent: return GetRecent().size();\n";
|
2017-02-15 08:50:11 +00:00
|
|
|
auto countIndex = 0;
|
2017-03-29 15:09:16 +00:00
|
|
|
for (auto name : sectionNames) {
|
2017-02-15 08:50:11 +00:00
|
|
|
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() << "\
|
|
|
|
}\n\
|
|
|
|
return 0;\n\
|
|
|
|
}\n\
|
|
|
|
\n\
|
2017-03-29 15:09:16 +00:00
|
|
|
EmojiPack GetSection(Section section) {\n\
|
|
|
|
switch (section) {\n\
|
|
|
|
case Section::Recent: {\n\
|
|
|
|
auto result = EmojiPack();\n\
|
|
|
|
result.reserve(GetRecent().size());\n\
|
|
|
|
for (auto &item : GetRecent()) {\n\
|
|
|
|
result.push_back(item.first);\n\
|
|
|
|
}\n\
|
|
|
|
return result;\n\
|
|
|
|
} break;\n";
|
2017-02-15 08:50:11 +00:00
|
|
|
auto index = 0;
|
2017-03-31 19:08:25 +00:00
|
|
|
auto offset = 0;
|
2017-03-29 15:09:16 +00:00
|
|
|
for (auto name : sectionNames) {
|
2017-02-15 08:50:11 +00:00
|
|
|
if (index >= int(data_.categories.size())) {
|
|
|
|
logDataError() << "category " << index << " not found.";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
auto &category = data_.categories[index++];
|
|
|
|
source_->stream() << "\
|
2017-03-29 15:09:16 +00:00
|
|
|
\n\
|
2017-02-15 08:50:11 +00:00
|
|
|
case " << name << ": {\n\
|
2017-07-19 18:37:49 +00:00
|
|
|
static auto result = FillSection(" << offset << ", " << category.size() << ");\n\
|
2017-02-15 08:50:11 +00:00
|
|
|
return result;\n\
|
2017-03-29 15:09:16 +00:00
|
|
|
} break;\n";
|
2017-03-31 19:08:25 +00:00
|
|
|
offset += category.size();
|
2017-02-15 08:50:11 +00:00
|
|
|
}
|
|
|
|
source_->stream() << "\
|
|
|
|
}\n\
|
|
|
|
return EmojiPack();\n\
|
2017-03-31 19:08:25 +00:00
|
|
|
}\n\
|
|
|
|
\n";
|
2017-02-15 08:50:11 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Generator::writeFindReplace() {
|
|
|
|
source_->stream() << "\
|
|
|
|
\n\
|
2018-05-13 15:14:02 +00:00
|
|
|
const std::vector<std::pair<QString, int>> ReplacementPairs = {\n";
|
|
|
|
for (const auto &[what, index] : data_.replaces) {
|
|
|
|
source_->stream() << "\
|
|
|
|
{ qsl(\"" << what << "\"), " << index << " },\n";
|
|
|
|
}
|
|
|
|
source_->stream() << "\
|
|
|
|
};\n\
|
|
|
|
\n\
|
2017-03-31 19:08:25 +00:00
|
|
|
int FindReplaceIndex(const QChar *start, const QChar *end, int *outLength) {\n\
|
2017-02-17 17:31:46 +00:00
|
|
|
auto ch = start;\n\
|
|
|
|
\n";
|
2017-02-15 08:50:11 +00:00
|
|
|
|
|
|
|
if (!writeFindFromDictionary(data_.replaces)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
source_->stream() << "\
|
|
|
|
}\n";
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Generator::writeFind() {
|
|
|
|
source_->stream() << "\
|
|
|
|
\n\
|
2017-03-31 19:08:25 +00:00
|
|
|
int FindIndex(const QChar *start, const QChar *end, int *outLength) {\n\
|
2017-02-17 17:31:46 +00:00
|
|
|
auto ch = start;\n\
|
|
|
|
\n";
|
2017-02-15 08:50:11 +00:00
|
|
|
|
2017-02-17 17:31:46 +00:00
|
|
|
if (!writeFindFromDictionary(data_.map, true)) {
|
2017-02-15 08:50:11 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
source_->stream() << "\
|
|
|
|
}\n\
|
|
|
|
\n";
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-02-17 17:31:46 +00:00
|
|
|
bool Generator::writeFindFromDictionary(const std::map<QString, int, std::greater<QString>> &dictionary, bool skipPostfixes) {
|
2017-02-15 08:50:11 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
enum class UsedCheckType {
|
|
|
|
Switch,
|
|
|
|
If,
|
|
|
|
};
|
|
|
|
auto checkTypes = QVector<UsedCheckType>();
|
|
|
|
auto chars = QString();
|
2017-02-17 17:31:46 +00:00
|
|
|
auto tabsUsed = 1;
|
2017-02-17 18:57:21 +00:00
|
|
|
auto lengthsCounted = std::map<QString, bool>();
|
2017-02-17 17:31:46 +00:00
|
|
|
|
|
|
|
auto writeSkipPostfix = [this, &tabs, skipPostfixes](int tabsCount) {
|
|
|
|
if (skipPostfixes) {
|
|
|
|
source_->stream() << tabs(tabsCount) << "if (++ch != end && ch->unicode() == kPostfix) ++ch;\n";
|
|
|
|
} else {
|
|
|
|
source_->stream() << tabs(tabsCount) << "++ch;\n";
|
|
|
|
}
|
|
|
|
};
|
2017-02-15 08:50:11 +00:00
|
|
|
|
|
|
|
// Returns true if at least one check was finished.
|
2017-02-17 17:31:46 +00:00
|
|
|
auto finishChecksTillKey = [this, &chars, &checkTypes, &tabsUsed, tabs](const QString &key) {
|
2017-02-15 08:50:11 +00:00
|
|
|
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";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
|
|
|
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();
|
2017-02-17 18:57:21 +00:00
|
|
|
auto partialKey = key.mid(0, checking);
|
|
|
|
if (dictionary.find(partialKey) != dictionary.cend()) {
|
|
|
|
if (lengthsCounted.find(partialKey) == lengthsCounted.cend()) {
|
|
|
|
lengthsCounted.insert(std::make_pair(partialKey, true));
|
|
|
|
source_->stream() << tabs(tabsUsed) << "if (outLength) *outLength = (ch - start);\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-15 08:50:11 +00:00
|
|
|
auto keyChar = key[checking];
|
2017-02-17 17:31:46 +00:00
|
|
|
auto keyCharString = "0x" + QString::number(keyChar.unicode(), 16);
|
|
|
|
auto usedIfForCheck = !weContinueOldSwitch && canUseIfForCheck(i, e, checking);
|
|
|
|
if (weContinueOldSwitch) {
|
|
|
|
weContinueOldSwitch = false;
|
|
|
|
} else if (!usedIfForCheck) {
|
|
|
|
source_->stream() << tabs(tabsUsed) << "if (ch != end) switch (ch->unicode()) {\n";
|
|
|
|
}
|
|
|
|
if (usedIfForCheck) {
|
|
|
|
source_->stream() << tabs(tabsUsed) << "if (ch != end && ch->unicode() == " << keyCharString << ") {\n";
|
|
|
|
checkTypes.push_back(UsedCheckType::If);
|
|
|
|
} else {
|
|
|
|
source_->stream() << tabs(tabsUsed) << "case " << keyCharString << ":\n";
|
|
|
|
checkTypes.push_back(UsedCheckType::Switch);
|
2017-02-15 08:50:11 +00:00
|
|
|
}
|
2017-02-17 17:31:46 +00:00
|
|
|
writeSkipPostfix(++tabsUsed);
|
2017-02-15 08:50:11 +00:00
|
|
|
chars.push_back(keyChar);
|
|
|
|
}
|
2017-02-17 18:57:21 +00:00
|
|
|
if (lengthsCounted.find(key) == lengthsCounted.cend()) {
|
|
|
|
lengthsCounted.insert(std::make_pair(key, true));
|
|
|
|
source_->stream() << tabs(tabsUsed) << "if (outLength) *outLength = (ch - start);\n";
|
|
|
|
}
|
2017-02-15 08:50:11 +00:00
|
|
|
|
|
|
|
// 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";
|
2017-03-31 19:08:25 +00:00
|
|
|
source_->stream() << tabs(tabsUsed) << "return " << (item.second + 1) << ";\n";
|
2017-02-15 08:50:11 +00:00
|
|
|
}
|
|
|
|
finishChecksTillKey(QString());
|
|
|
|
|
2017-02-17 17:31:46 +00:00
|
|
|
source_->stream() << "\
|
|
|
|
\n\
|
2017-03-31 19:08:25 +00:00
|
|
|
return 0;\n";
|
2017-02-15 08:50:11 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-07-24 16:18:39 +00:00
|
|
|
bool Generator::writeSuggestionsSource() {
|
|
|
|
suggestionsSource_ = std::make_unique<common::CppFile>(suggestionsPath_ + ".cpp", project_);
|
|
|
|
suggestionsSource_->stream() << "\
|
|
|
|
#include <map>\n\
|
|
|
|
\n";
|
|
|
|
suggestionsSource_->pushNamespace("Ui").pushNamespace("Emoji").pushNamespace("internal").pushNamespace();
|
|
|
|
suggestionsSource_->stream() << "\
|
|
|
|
\n";
|
|
|
|
if (!writeReplacements()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
suggestionsSource_->popNamespace().newline();
|
|
|
|
if (!writeGetReplacements()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return suggestionsSource_->finalize();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Generator::writeSuggestionsHeader() {
|
|
|
|
auto maxLength = 0;
|
|
|
|
for (auto &replace : replaces_.list) {
|
|
|
|
if (maxLength < replace.replacement.size()) {
|
|
|
|
maxLength = replace.replacement.size();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
auto header = std::make_unique<common::CppFile>(suggestionsPath_ + ".h", project_);
|
|
|
|
header->include("emoji_suggestions.h").newline();
|
|
|
|
header->pushNamespace("Ui").pushNamespace("Emoji").pushNamespace("internal");
|
|
|
|
header->stream() << "\
|
|
|
|
\n\
|
|
|
|
struct Replacement {\n\
|
|
|
|
utf16string emoji;\n\
|
|
|
|
utf16string replacement;\n\
|
|
|
|
std::vector<utf16string> words;\n\
|
|
|
|
};\n\
|
|
|
|
\n\
|
|
|
|
constexpr auto kReplacementMaxLength = " << maxLength << ";\n\
|
|
|
|
\n\
|
|
|
|
void InitReplacements();\n\
|
2018-05-13 15:14:02 +00:00
|
|
|
const std::vector<Replacement> &GetAllReplacements();\n\
|
2017-07-24 16:18:39 +00:00
|
|
|
const std::vector<const Replacement*> *GetReplacements(utf16char first);\n\
|
|
|
|
utf16string GetReplacementEmoji(utf16string replacement);\n\
|
|
|
|
\n";
|
|
|
|
return header->finalize();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Generator::writeReplacements() {
|
|
|
|
QMap<QChar, QVector<int>> byCharIndices;
|
|
|
|
suggestionsSource_->stream() << "\
|
|
|
|
struct ReplacementStruct {\n\
|
|
|
|
small emojiSize;\n\
|
|
|
|
small replacementSize;\n\
|
|
|
|
small wordsCount;\n\
|
|
|
|
};\n\
|
|
|
|
\n\
|
|
|
|
const utf16char ReplacementData[] = {";
|
|
|
|
startBinary();
|
|
|
|
for (auto i = 0, size = replaces_.list.size(); i != size; ++i) {
|
|
|
|
auto &replace = replaces_.list[i];
|
|
|
|
if (!writeStringBinary(suggestionsSource_.get(), replace.id)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (!writeStringBinary(suggestionsSource_.get(), replace.replacement)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
for (auto &word : replace.words) {
|
|
|
|
if (!writeStringBinary(suggestionsSource_.get(), word)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
auto &index = byCharIndices[word[0]];
|
|
|
|
if (index.isEmpty() || index.back() != i) {
|
|
|
|
index.push_back(i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
suggestionsSource_->stream() << " };\n\
|
|
|
|
\n\
|
|
|
|
const small ReplacementWordLengths[] = {";
|
|
|
|
startBinary();
|
|
|
|
for (auto &replace : replaces_.list) {
|
|
|
|
auto wordLengths = QStringList();
|
|
|
|
for (auto &word : replace.words) {
|
|
|
|
writeIntBinary(suggestionsSource_.get(), word.size());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
suggestionsSource_->stream() << " };\n\
|
|
|
|
\n\
|
|
|
|
const ReplacementStruct ReplacementInitData[] = {\n";
|
|
|
|
for (auto &replace : replaces_.list) {
|
|
|
|
suggestionsSource_->stream() << "\
|
|
|
|
{ small(" << replace.id.size() << "), small(" << replace.replacement.size() << "), small(" << replace.words.size() << ") },\n";
|
|
|
|
}
|
|
|
|
suggestionsSource_->stream() << "};\n\
|
|
|
|
\n\
|
|
|
|
const medium ReplacementIndices[] = {";
|
|
|
|
startBinary();
|
|
|
|
for (auto &byCharIndex : byCharIndices) {
|
|
|
|
for (auto index : byCharIndex) {
|
|
|
|
writeIntBinary(suggestionsSource_.get(), index);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
suggestionsSource_->stream() << " };\n\
|
|
|
|
\n\
|
|
|
|
struct ReplacementIndexStruct {\n\
|
|
|
|
utf16char ch;\n\
|
|
|
|
medium count;\n\
|
|
|
|
};\n\
|
|
|
|
\n\
|
|
|
|
const internal::checksum ReplacementChecksums[] = {\n";
|
|
|
|
startBinary();
|
|
|
|
for (auto &replace : replaces_.list) {
|
|
|
|
writeUintBinary(suggestionsSource_.get(), countCrc32(replace.replacement.constData(), replace.replacement.size() * sizeof(QChar)));
|
|
|
|
}
|
|
|
|
suggestionsSource_->stream() << " };\n\
|
|
|
|
\n\
|
|
|
|
const ReplacementIndexStruct ReplacementIndexData[] = {\n";
|
|
|
|
startBinary();
|
|
|
|
for (auto i = byCharIndices.cbegin(), e = byCharIndices.cend(); i != e; ++i) {
|
|
|
|
suggestionsSource_->stream() << "\
|
|
|
|
{ utf16char(" << i.key().unicode() << "), medium(" << i.value().size() << ") },\n";
|
|
|
|
}
|
|
|
|
suggestionsSource_->stream() << "};\n\
|
|
|
|
\n\
|
|
|
|
std::vector<Replacement> Replacements;\n\
|
|
|
|
std::map<utf16char, std::vector<const Replacement*>> ReplacementsMap;\n\
|
|
|
|
std::map<internal::checksum, const Replacement*> ReplacementsHash;\n\
|
|
|
|
\n";
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-07-19 18:37:49 +00:00
|
|
|
bool Generator::writeGetReplacements() {
|
2017-07-24 16:18:39 +00:00
|
|
|
suggestionsSource_->stream() << "\
|
|
|
|
void InitReplacements() {\n\
|
|
|
|
if (!Replacements.empty()) {\n\
|
|
|
|
return;\n\
|
|
|
|
}\n\
|
|
|
|
auto data = ReplacementData;\n\
|
|
|
|
auto takeString = [&data](int size) {\n\
|
|
|
|
auto result = utf16string(data, size);\n\
|
|
|
|
data += size;\n\
|
|
|
|
return result;\n\
|
|
|
|
};\n\
|
|
|
|
auto wordSize = ReplacementWordLengths;\n\
|
|
|
|
\n\
|
|
|
|
Replacements.reserve(" << replaces_.list.size() << ");\n\
|
|
|
|
for (auto item : ReplacementInitData) {\n\
|
|
|
|
auto emoji = takeString(item.emojiSize);\n\
|
|
|
|
auto replacement = takeString(item.replacementSize);\n\
|
|
|
|
auto words = std::vector<utf16string>();\n\
|
|
|
|
words.reserve(item.wordsCount);\n\
|
|
|
|
for (auto i = 0; i != item.wordsCount; ++i) {\n\
|
|
|
|
words.push_back(takeString(*wordSize++));\n\
|
|
|
|
}\n\
|
|
|
|
Replacements.push_back({ std::move(emoji), std::move(replacement), std::move(words) });\n\
|
|
|
|
}\n\
|
|
|
|
\n\
|
|
|
|
auto indices = ReplacementIndices;\n\
|
|
|
|
auto items = &Replacements[0];\n\
|
|
|
|
for (auto item : ReplacementIndexData) {\n\
|
|
|
|
auto index = std::vector<const Replacement*>();\n\
|
|
|
|
index.reserve(item.count);\n\
|
|
|
|
for (auto i = 0; i != item.count; ++i) {\n\
|
|
|
|
index.push_back(items + (*indices++));\n\
|
|
|
|
}\n\
|
|
|
|
ReplacementsMap.emplace(item.ch, std::move(index));\n\
|
|
|
|
}\n\
|
|
|
|
\n\
|
|
|
|
for (auto checksum : ReplacementChecksums) {\n\
|
|
|
|
ReplacementsHash.emplace(checksum, items++);\n\
|
|
|
|
}\n\
|
|
|
|
}\n\
|
|
|
|
\n\
|
|
|
|
const std::vector<const Replacement*> *GetReplacements(utf16char first) {\n\
|
|
|
|
if (ReplacementsMap.empty()) {\n\
|
|
|
|
InitReplacements();\n\
|
|
|
|
}\n\
|
|
|
|
auto it = ReplacementsMap.find(first);\n\
|
2017-07-19 18:37:49 +00:00
|
|
|
return (it == ReplacementsMap.cend()) ? nullptr : &it->second;\n\
|
|
|
|
}\n\
|
2017-07-24 16:18:39 +00:00
|
|
|
\n\
|
2018-05-13 15:14:02 +00:00
|
|
|
const std::vector<Replacement> &GetAllReplacements() {\n\
|
|
|
|
return Replacements;\n\
|
|
|
|
}\n\
|
|
|
|
\n\
|
2017-07-24 16:18:39 +00:00
|
|
|
utf16string GetReplacementEmoji(utf16string replacement) {\n\
|
|
|
|
auto code = internal::countChecksum(replacement.data(), replacement.size() * sizeof(utf16char));\n\
|
|
|
|
auto it = ReplacementsHash.find(code);\n\
|
|
|
|
return (it == ReplacementsHash.cend()) ? utf16string() : it->second->emoji;\n\
|
|
|
|
}\n\
|
2017-07-19 18:37:49 +00:00
|
|
|
\n";
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Generator::startBinary() {
|
|
|
|
_binaryFullLength = _binaryCount = 0;
|
|
|
|
}
|
|
|
|
|
2017-07-24 16:18:39 +00:00
|
|
|
bool Generator::writeStringBinary(common::CppFile *source, const QString &string) {
|
2017-07-19 18:37:49 +00:00
|
|
|
if (string.size() >= 256) {
|
|
|
|
logDataError() << "Too long string: " << string.toStdString();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
for (auto ch : string) {
|
2017-07-24 16:18:39 +00:00
|
|
|
if (_binaryFullLength > 0) source->stream() << ",";
|
2017-07-19 18:37:49 +00:00
|
|
|
if (!_binaryCount++) {
|
2017-07-24 16:18:39 +00:00
|
|
|
source->stream() << "\n";
|
2017-07-19 18:37:49 +00:00
|
|
|
} else {
|
|
|
|
if (_binaryCount == 12) {
|
|
|
|
_binaryCount = 0;
|
|
|
|
}
|
2017-07-24 16:18:39 +00:00
|
|
|
source->stream() << " ";
|
2017-07-19 18:37:49 +00:00
|
|
|
}
|
2017-07-24 16:18:39 +00:00
|
|
|
source->stream() << "0x" << QString::number(ch.unicode(), 16);
|
2017-07-19 18:37:49 +00:00
|
|
|
++_binaryFullLength;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-07-24 16:18:39 +00:00
|
|
|
void Generator::writeIntBinary(common::CppFile *source, int data) {
|
|
|
|
if (_binaryFullLength > 0) source->stream() << ",";
|
|
|
|
if (!_binaryCount++) {
|
|
|
|
source->stream() << "\n";
|
|
|
|
} else {
|
|
|
|
if (_binaryCount == 12) {
|
|
|
|
_binaryCount = 0;
|
|
|
|
}
|
|
|
|
source->stream() << " ";
|
|
|
|
}
|
|
|
|
source->stream() << data;
|
|
|
|
++_binaryFullLength;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Generator::writeUintBinary(common::CppFile *source, uint32 data) {
|
|
|
|
if (_binaryFullLength > 0) source->stream() << ",";
|
2017-07-19 18:37:49 +00:00
|
|
|
if (!_binaryCount++) {
|
2017-07-24 16:18:39 +00:00
|
|
|
source->stream() << "\n";
|
2017-07-19 18:37:49 +00:00
|
|
|
} else {
|
|
|
|
if (_binaryCount == 12) {
|
|
|
|
_binaryCount = 0;
|
|
|
|
}
|
2017-07-24 16:18:39 +00:00
|
|
|
source->stream() << " ";
|
2017-07-19 18:37:49 +00:00
|
|
|
}
|
2017-07-24 16:18:39 +00:00
|
|
|
source->stream() << "0x" << QString::number(data, 16).toUpper() << "U";
|
2017-07-19 18:37:49 +00:00
|
|
|
++_binaryFullLength;
|
|
|
|
}
|
|
|
|
|
2017-02-15 08:50:11 +00:00
|
|
|
} // namespace emoji
|
|
|
|
} // namespace codegen
|