mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-02-15 11:36:54 +00:00
If we start logging in and we know, that some of the authorization keys were read from the hard drive, not generated, we destroy all the existing authorization keys and start generating new keys.
1345 lines
38 KiB
C++
1345 lines
38 KiB
C++
/*
|
|
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/style/generator.h"
|
|
|
|
#include <memory>
|
|
#include <functional>
|
|
#include <QtCore/QDir>
|
|
#include <QtCore/QSet>
|
|
#include <QtCore/QBuffer>
|
|
#include <QtGui/QImage>
|
|
#include <QtGui/QPainter>
|
|
#include "codegen/style/parsed_file.h"
|
|
|
|
using Module = codegen::style::structure::Module;
|
|
using Struct = codegen::style::structure::Struct;
|
|
using Variable = codegen::style::structure::Variable;
|
|
using Tag = codegen::style::structure::TypeTag;
|
|
|
|
namespace codegen {
|
|
namespace style {
|
|
namespace {
|
|
|
|
constexpr int kErrorBadIconSize = 861;
|
|
constexpr int kErrorBadIconFormat = 862;
|
|
|
|
// crc32 hash, taken somewhere from the internet
|
|
|
|
class Crc32Table {
|
|
public:
|
|
Crc32Table() {
|
|
quint32 poly = 0x04c11db7;
|
|
for (auto i = 0; i != 256; ++i) {
|
|
_data[i] = reflect(i, 8) << 24;
|
|
for (auto j = 0; j != 8; ++j) {
|
|
_data[i] = (_data[i] << 1) ^ (_data[i] & (1 << 31) ? poly : 0);
|
|
}
|
|
_data[i] = reflect(_data[i], 32);
|
|
}
|
|
}
|
|
|
|
inline quint32 operator[](int index) const {
|
|
return _data[index];
|
|
}
|
|
|
|
private:
|
|
quint32 reflect(quint32 val, char ch) {
|
|
quint32 result = 0;
|
|
for (int i = 1; i < (ch + 1); ++i) {
|
|
if (val & 1) {
|
|
result |= 1 << (ch - i);
|
|
}
|
|
val >>= 1;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
quint32 _data[256];
|
|
|
|
};
|
|
|
|
qint32 hashCrc32(const void *data, int len) {
|
|
static Crc32Table table;
|
|
|
|
const uchar *buffer = static_cast<const uchar *>(data);
|
|
|
|
quint32 crc = 0xffffffff;
|
|
for (int i = 0; i != len; ++i) {
|
|
crc = (crc >> 8) ^ table[(crc & 0xFF) ^ buffer[i]];
|
|
}
|
|
|
|
return static_cast<qint32>(crc ^ 0xffffffff);
|
|
}
|
|
|
|
char hexChar(uchar ch) {
|
|
if (ch < 10) {
|
|
return '0' + ch;
|
|
} else if (ch < 16) {
|
|
return 'a' + (ch - 10);
|
|
}
|
|
return '0';
|
|
}
|
|
|
|
char hexSecondChar(char ch) {
|
|
return hexChar((*reinterpret_cast<uchar*>(&ch)) & 0x0F);
|
|
}
|
|
|
|
char hexFirstChar(char ch) {
|
|
return hexChar((*reinterpret_cast<uchar*>(&ch)) >> 4);
|
|
}
|
|
|
|
QString stringToEncodedString(const QString &str) {
|
|
QString result, lineBreak = "\\\n";
|
|
result.reserve(str.size() * 8);
|
|
bool writingHexEscapedCharacters = false, startOnNewLine = false;
|
|
int lastCutSize = 0;
|
|
auto utf = str.toUtf8();
|
|
for (auto ch : utf) {
|
|
if (result.size() - lastCutSize > 80) {
|
|
startOnNewLine = true;
|
|
result.append(lineBreak);
|
|
lastCutSize = result.size();
|
|
}
|
|
if (ch == '\n') {
|
|
writingHexEscapedCharacters = false;
|
|
result.append("\\n");
|
|
} else if (ch == '\t') {
|
|
writingHexEscapedCharacters = false;
|
|
result.append("\\t");
|
|
} else if (ch == '"' || ch == '\\') {
|
|
writingHexEscapedCharacters = false;
|
|
result.append('\\').append(ch);
|
|
} else if (ch < 32 || static_cast<uchar>(ch) > 127) {
|
|
writingHexEscapedCharacters = true;
|
|
result.append("\\x").append(hexFirstChar(ch)).append(hexSecondChar(ch));
|
|
} else {
|
|
if (writingHexEscapedCharacters) {
|
|
writingHexEscapedCharacters = false;
|
|
result.append("\"\"");
|
|
}
|
|
result.append(ch);
|
|
}
|
|
}
|
|
return '"' + (startOnNewLine ? lineBreak : QString()) + result + '"';
|
|
}
|
|
|
|
QString stringToEncodedString(const std::string &str) {
|
|
return stringToEncodedString(QString::fromStdString(str));
|
|
}
|
|
|
|
QString stringToBinaryArray(const std::string &str) {
|
|
QStringList rows, chars;
|
|
chars.reserve(13);
|
|
rows.reserve(1 + (str.size() / 13));
|
|
for (uchar ch : str) {
|
|
if (chars.size() > 12) {
|
|
rows.push_back(chars.join(", "));
|
|
chars.clear();
|
|
}
|
|
chars.push_back(QString("0x") + hexFirstChar(ch) + hexSecondChar(ch));
|
|
}
|
|
if (!chars.isEmpty()) {
|
|
rows.push_back(chars.join(", "));
|
|
}
|
|
return QString("{") + ((rows.size() > 1) ? '\n' : ' ') + rows.join(",\n") + " }";
|
|
}
|
|
|
|
QString pxValueName(int value) {
|
|
QString result = "px";
|
|
if (value < 0) {
|
|
value = -value;
|
|
result += 'm';
|
|
}
|
|
return result + QString::number(value);
|
|
}
|
|
|
|
QString moduleBaseName(const structure::Module &module) {
|
|
auto moduleInfo = QFileInfo(module.filepath());
|
|
auto moduleIsPalette = (moduleInfo.suffix() == "palette");
|
|
return moduleIsPalette ? "palette" : "style_" + moduleInfo.baseName();
|
|
}
|
|
|
|
QString colorFallbackName(structure::Value value) {
|
|
auto copy = value.copyOf();
|
|
if (!copy.isEmpty()) {
|
|
return copy.back();
|
|
}
|
|
return value.Color().fallback;
|
|
}
|
|
|
|
QChar paletteColorPart(uchar part) {
|
|
part = (part & 0x0F);
|
|
if (part >= 10) {
|
|
return 'a' + (part - 10);
|
|
}
|
|
return '0' + part;
|
|
}
|
|
|
|
QString paletteColorComponent(uchar value) {
|
|
return QString() + paletteColorPart(value >> 4) + paletteColorPart(value);
|
|
}
|
|
|
|
QString paletteColorValue(const structure::data::color &value) {
|
|
auto result = paletteColorComponent(value.red) + paletteColorComponent(value.green) + paletteColorComponent(value.blue);
|
|
if (value.alpha != 255) result += paletteColorComponent(value.alpha);
|
|
return result;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
Generator::Generator(const structure::Module &module, const QString &destBasePath, const common::ProjectInfo &project, bool isPalette)
|
|
: module_(module)
|
|
, basePath_(destBasePath)
|
|
, baseName_(QFileInfo(basePath_).baseName())
|
|
, project_(project)
|
|
, isPalette_(isPalette) {
|
|
}
|
|
|
|
bool Generator::writeHeader() {
|
|
header_ = std::make_unique<common::CppFile>(basePath_ + ".h", project_);
|
|
|
|
header_->include("ui/style/style_core.h").newline();
|
|
|
|
if (!writeHeaderStyleNamespace()) {
|
|
return false;
|
|
}
|
|
if (!writeRefsDeclarations()) {
|
|
return false;
|
|
}
|
|
|
|
return header_->finalize();
|
|
}
|
|
|
|
bool Generator::writeSource() {
|
|
source_ = std::make_unique<common::CppFile>(basePath_ + ".cpp", project_);
|
|
|
|
writeIncludesInSource();
|
|
|
|
if (module_.hasVariables()) {
|
|
source_->pushNamespace().newline();
|
|
source_->stream() << "\
|
|
bool inited = false;\n\
|
|
\n\
|
|
class Module_" << baseName_ << " : public style::internal::ModuleBase {\n\
|
|
public:\n\
|
|
Module_" << baseName_ << "() { style::internal::registerModule(this); }\n\
|
|
~Module_" << baseName_ << "() { style::internal::unregisterModule(this); }\n\
|
|
\n\
|
|
void start() override {\n\
|
|
style::internal::init_" << baseName_ << "();\n\
|
|
}\n\
|
|
void stop() override {\n\
|
|
}\n\
|
|
};\n\
|
|
Module_" << baseName_ << " registrator;\n";
|
|
if (isPalette_) {
|
|
source_->newline();
|
|
source_->stream() << "style::palette _palette;\n";
|
|
} else {
|
|
if (!writeVariableDefinitions()) {
|
|
return false;
|
|
}
|
|
}
|
|
source_->newline().popNamespace();
|
|
|
|
source_->newline().pushNamespace("st");
|
|
if (!writeRefsDefinition()) {
|
|
return false;
|
|
}
|
|
|
|
source_->popNamespace().newline().pushNamespace("style");
|
|
if (isPalette_) {
|
|
writeSetPaletteColor();
|
|
}
|
|
source_->pushNamespace("internal").newline();
|
|
if (!writeVariableInit()) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return source_->finalize();
|
|
}
|
|
|
|
// Empty result means an error.
|
|
QString Generator::typeToString(structure::Type type) const {
|
|
switch (type.tag) {
|
|
case Tag::Invalid: return QString();
|
|
case Tag::Int: return "int";
|
|
case Tag::Double: return "double";
|
|
case Tag::Pixels: return "int";
|
|
case Tag::String: return "QString";
|
|
case Tag::Color: return "style::color";
|
|
case Tag::Point: return "style::point";
|
|
case Tag::Size: return "style::size";
|
|
case Tag::Align: return "style::align";
|
|
case Tag::Margins: return "style::margins";
|
|
case Tag::Font: return "style::font";
|
|
case Tag::Icon: return "style::icon";
|
|
case Tag::Struct: return "style::" + type.name.back();
|
|
}
|
|
return QString();
|
|
}
|
|
|
|
// Empty result means an error.
|
|
QString Generator::typeToDefaultValue(structure::Type type) const {
|
|
switch (type.tag) {
|
|
case Tag::Invalid: return QString();
|
|
case Tag::Int: return "0";
|
|
case Tag::Double: return "0.";
|
|
case Tag::Pixels: return "0";
|
|
case Tag::String: return "QString()";
|
|
case Tag::Color: return "{ Qt::Uninitialized }";
|
|
case Tag::Point: return "{ 0, 0 }";
|
|
case Tag::Size: return "{ 0, 0 }";
|
|
case Tag::Align: return "style::al_topleft";
|
|
case Tag::Margins: return "{ 0, 0, 0, 0 }";
|
|
case Tag::Font: return "{ Qt::Uninitialized }";
|
|
case Tag::Icon: return "{ Qt::Uninitialized }";
|
|
case Tag::Struct: {
|
|
if (auto realType = module_.findStruct(type.name)) {
|
|
QStringList fields;
|
|
for (auto field : realType->fields) {
|
|
fields.push_back(typeToDefaultValue(field.type));
|
|
}
|
|
return "{ " + fields.join(", ") + " }";
|
|
}
|
|
return QString();
|
|
} break;
|
|
}
|
|
return QString();
|
|
}
|
|
|
|
// Empty result means an error.
|
|
QString Generator::valueAssignmentCode(structure::Value value) const {
|
|
auto copy = value.copyOf();
|
|
if (!copy.isEmpty()) {
|
|
return "st::" + copy.back();
|
|
}
|
|
|
|
switch (value.type().tag) {
|
|
case Tag::Invalid: return QString();
|
|
case Tag::Int: return QString("%1").arg(value.Int());
|
|
case Tag::Double: return QString("%1").arg(value.Double());
|
|
case Tag::Pixels: return pxValueName(value.Int());
|
|
case Tag::String: return QString("QString::fromUtf8(%1)").arg(stringToEncodedString(value.String()));
|
|
case Tag::Color: {
|
|
auto v(value.Color());
|
|
if (v.red == v.green && v.red == v.blue && v.red == 0 && v.alpha == 255) {
|
|
return QString("st::windowFg");
|
|
} else if (v.red == v.green && v.red == v.blue && v.red == 255 && v.alpha == 0) {
|
|
return QString("st::transparent");
|
|
} else {
|
|
common::logError(common::kErrorInternal, "") << "bad color value";
|
|
return QString();
|
|
}
|
|
} break;
|
|
case Tag::Point: {
|
|
auto v(value.Point());
|
|
return QString("{ %1, %2 }").arg(pxValueName(v.x)).arg(pxValueName(v.y));
|
|
} break;
|
|
case Tag::Size: {
|
|
auto v(value.Size());
|
|
return QString("{ %1, %2 }").arg(pxValueName(v.width)).arg(pxValueName(v.height));
|
|
} break;
|
|
case Tag::Align: return QString("style::al_%1").arg(value.String().c_str());
|
|
case Tag::Margins: {
|
|
auto v(value.Margins());
|
|
return QString("{ %1, %2, %3, %4 }").arg(pxValueName(v.left)).arg(pxValueName(v.top)).arg(pxValueName(v.right)).arg(pxValueName(v.bottom));
|
|
} break;
|
|
case Tag::Font: {
|
|
auto v(value.Font());
|
|
QString family = "0";
|
|
if (!v.family.empty()) {
|
|
auto familyIndex = fontFamilies_.value(v.family, -1);
|
|
if (familyIndex < 0) {
|
|
return QString();
|
|
}
|
|
family = QString("font%1index").arg(familyIndex);
|
|
}
|
|
return QString("{ %1, %2, %3 }").arg(pxValueName(v.size)).arg(v.flags).arg(family);
|
|
} break;
|
|
case Tag::Icon: {
|
|
auto v(value.Icon());
|
|
if (v.parts.empty()) return QString("{}");
|
|
|
|
QStringList parts;
|
|
for (const auto &part : v.parts) {
|
|
auto maskIndex = iconMasks_.value(part.filename, -1);
|
|
if (maskIndex < 0) {
|
|
return QString();
|
|
}
|
|
auto color = valueAssignmentCode(part.color);
|
|
auto offset = valueAssignmentCode(part.offset);
|
|
parts.push_back(QString("MonoIcon{ &iconMask%1, %2, %3 }").arg(maskIndex).arg(color).arg(offset));
|
|
}
|
|
return QString("{ %1 }").arg(parts.join(", "));
|
|
} break;
|
|
case Tag::Struct: {
|
|
if (!value.Fields()) return QString();
|
|
|
|
QStringList fields;
|
|
for (auto field : *value.Fields()) {
|
|
fields.push_back(valueAssignmentCode(field.variable.value));
|
|
}
|
|
return "{ " + fields.join(", ") + " }";
|
|
} break;
|
|
}
|
|
return QString();
|
|
}
|
|
|
|
bool Generator::writeHeaderStyleNamespace() {
|
|
if (!module_.hasStructs() && !module_.hasVariables()) {
|
|
return true;
|
|
}
|
|
header_->pushNamespace("style");
|
|
|
|
if (module_.hasVariables()) {
|
|
header_->pushNamespace("internal").newline();
|
|
header_->stream() << "void init_" << baseName_ << "();\n\n";
|
|
header_->popNamespace();
|
|
}
|
|
bool wroteForwardDeclarations = writeStructsForwardDeclarations();
|
|
if (module_.hasStructs()) {
|
|
if (!wroteForwardDeclarations) {
|
|
header_->newline();
|
|
}
|
|
if (!writeStructsDefinitions()) {
|
|
return false;
|
|
}
|
|
} else if (isPalette_) {
|
|
if (!wroteForwardDeclarations) {
|
|
header_->newline();
|
|
}
|
|
if (!writePaletteDefinition()) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
header_->popNamespace().newline();
|
|
return true;
|
|
}
|
|
|
|
bool Generator::writePaletteDefinition() {
|
|
header_->stream() << "\
|
|
class palette {\n\
|
|
public:\n\
|
|
palette() = default;\n\
|
|
palette(const palette &other) = delete;\n\
|
|
\n\
|
|
QByteArray save() const;\n\
|
|
bool load(const QByteArray &cache);\n\
|
|
\n\
|
|
enum class SetResult {\n\
|
|
Ok,\n\
|
|
KeyNotFound,\n\
|
|
ValueNotFound,\n\
|
|
Duplicate,\n\
|
|
};\n\
|
|
SetResult setColor(QLatin1String name, uchar r, uchar g, uchar b, uchar a);\n\
|
|
SetResult setColor(QLatin1String name, QLatin1String from);\n\
|
|
void reset() {\n\
|
|
clear();\n\
|
|
finalize();\n\
|
|
}\n\
|
|
\n\
|
|
// Created not inited, should be finalized before usage.\n\
|
|
void finalize();\n\
|
|
\n\
|
|
int indexOfColor(color c) const;\n\
|
|
color colorAtIndex(int index) const;\n\
|
|
\n\
|
|
inline const color &get_transparent() const { return _colors[0]; }; // special color\n";
|
|
|
|
int indexInPalette = 1;
|
|
if (!module_.enumVariables([this, &indexInPalette](const Variable &variable) -> bool {
|
|
auto name = variable.name.back();
|
|
if (variable.value.type().tag != structure::TypeTag::Color) {
|
|
return false;
|
|
}
|
|
|
|
auto index = (indexInPalette++);
|
|
header_->stream() << "\tinline const color &get_" << name << "() const { return _colors[" << index << "]; };\n";
|
|
return true;
|
|
})) return false;
|
|
|
|
auto count = indexInPalette;
|
|
header_->stream() << "\
|
|
\n\
|
|
palette &operator=(const palette &other) {\n\
|
|
auto wasReady = _ready;\n\
|
|
for (int i = 0; i != kCount; ++i) {\n\
|
|
if (other._status[i] == Status::Loaded) {\n\
|
|
if (_status[i] == Status::Initial) {\n\
|
|
new (data(i)) internal::ColorData(*other.data(i));\n\
|
|
} else {\n\
|
|
*data(i) = *other.data(i);\n\
|
|
}\n\
|
|
} else if (_status[i] != Status::Initial) {\n\
|
|
data(i)->~ColorData();\n\
|
|
_status[i] = Status::Initial;\n\
|
|
_ready = false;\n\
|
|
}\n\
|
|
}\n\
|
|
if (wasReady && !_ready) {\n\
|
|
finalize();\n\
|
|
}\n\
|
|
return *this;\n\
|
|
}\n\
|
|
\n\
|
|
static int32 Checksum();\n\
|
|
\n\
|
|
~palette() {\n\
|
|
clear();\n\
|
|
}\n\
|
|
\n\
|
|
private:\n\
|
|
static constexpr auto kCount = " << count << ";\n\
|
|
\n\
|
|
void clear() {\n\
|
|
for (int i = 0; i != kCount; ++i) {\n\
|
|
if (_status[i] != Status::Initial) {\n\
|
|
data(i)->~ColorData();\n\
|
|
_status[i] = Status::Initial;\n\
|
|
_ready = false;\n\
|
|
}\n\
|
|
}\n\
|
|
}\n\
|
|
\n\
|
|
struct TempColorData { uchar r, g, b, a; };\n\
|
|
void compute(int index, int fallbackIndex, TempColorData value) {\n\
|
|
if (_status[index] == Status::Initial) {\n\
|
|
if (fallbackIndex >= 0 && _status[fallbackIndex] == Status::Loaded) {\n\
|
|
_status[index] = Status::Loaded;\n\
|
|
new (data(index)) internal::ColorData(*data(fallbackIndex));\n\
|
|
} else {\n\
|
|
_status[index] = Status::Created;\n\
|
|
new (data(index)) internal::ColorData(value.r, value.g, value.b, value.a);\n\
|
|
}\n\
|
|
}\n\
|
|
}\n\
|
|
\n\
|
|
internal::ColorData *data(int index) {\n\
|
|
return reinterpret_cast<internal::ColorData*>(_data) + index;\n\
|
|
}\n\
|
|
\n\
|
|
const internal::ColorData *data(int index) const {\n\
|
|
return reinterpret_cast<const internal::ColorData*>(_data) + index;\n\
|
|
}\n\
|
|
\n\
|
|
void setData(int index, const internal::ColorData &value) {\n\
|
|
if (_status[index] == Status::Initial) {\n\
|
|
new (data(index)) internal::ColorData(value);\n\
|
|
} else {\n\
|
|
*data(index) = value;\n\
|
|
}\n\
|
|
_status[index] = Status::Loaded;\n\
|
|
}\n\
|
|
\n\
|
|
enum class Status {\n\
|
|
Initial,\n\
|
|
Created,\n\
|
|
Loaded,\n\
|
|
};\n\
|
|
\n\
|
|
alignas(alignof(internal::ColorData)) char _data[sizeof(internal::ColorData) * kCount];\n\
|
|
\n\
|
|
color _colors[kCount] = {\n";
|
|
for (int i = 0; i != count; ++i) {
|
|
header_->stream() << "\t\tdata(" << i << "),\n";
|
|
}
|
|
header_->stream() << "\
|
|
};\n\
|
|
Status _status[kCount] = { Status::Initial };\n\
|
|
bool _ready = false;\n\
|
|
\n\
|
|
};\n\
|
|
\n\
|
|
namespace main_palette {\n\
|
|
\n\
|
|
QByteArray save();\n\
|
|
bool load(const QByteArray &cache);\n\
|
|
palette::SetResult setColor(QLatin1String name, uchar r, uchar g, uchar b, uchar a);\n\
|
|
palette::SetResult setColor(QLatin1String name, QLatin1String from);\n\
|
|
void apply(const palette &other);\n\
|
|
void reset();\n\
|
|
int indexOfColor(color c);\n\
|
|
\n\
|
|
struct row {\n\
|
|
\tQLatin1String name;\n\
|
|
\tQLatin1String value;\n\
|
|
\tQLatin1String fallback;\n\
|
|
\tQLatin1String description;\n\
|
|
};\n\
|
|
QList<row> data();\n\
|
|
\n\
|
|
} // namespace main_palette\n";
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Generator::writeStructsForwardDeclarations() {
|
|
bool hasNoExternalStructs = module_.enumVariables([this](const Variable &value) -> bool {
|
|
if (value.value.type().tag == structure::TypeTag::Struct) {
|
|
if (!module_.findStructInModule(value.value.type().name, module_)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
});
|
|
if (hasNoExternalStructs) {
|
|
return false;
|
|
}
|
|
|
|
header_->newline();
|
|
bool result = module_.enumVariables([this](const Variable &value) -> bool {
|
|
if (value.value.type().tag == structure::TypeTag::Struct) {
|
|
if (!module_.findStructInModule(value.value.type().name, module_)) {
|
|
header_->stream() << "struct " << value.value.type().name.back() << ";\n";
|
|
}
|
|
}
|
|
return true;
|
|
});
|
|
header_->newline();
|
|
return result;
|
|
}
|
|
|
|
bool Generator::writeStructsDefinitions() {
|
|
if (!module_.hasStructs()) {
|
|
return true;
|
|
}
|
|
|
|
bool result = module_.enumStructs([this](const Struct &value) -> bool {
|
|
header_->stream() << "\
|
|
struct " << value.name.back() << " {\n";
|
|
for (auto &field : value.fields) {
|
|
auto type = typeToString(field.type);
|
|
if (type.isEmpty()) {
|
|
return false;
|
|
}
|
|
header_->stream() << "\t" << type << " " << field.name.back() << ";\n";
|
|
}
|
|
header_->stream() << "\
|
|
};\n\n";
|
|
return true;
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
bool Generator::writeRefsDeclarations() {
|
|
if (!module_.hasVariables()) {
|
|
return true;
|
|
}
|
|
|
|
header_->pushNamespace("st");
|
|
|
|
if (isPalette_) {
|
|
header_->stream() << "extern const style::color &transparent; // special color\n";
|
|
}
|
|
bool result = module_.enumVariables([this](const Variable &value) -> bool {
|
|
auto name = value.name.back();
|
|
auto type = typeToString(value.value.type());
|
|
if (type.isEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
header_->stream() << "extern const " << type << " &" << name << ";\n";
|
|
return true;
|
|
});
|
|
|
|
header_->popNamespace();
|
|
|
|
return result;
|
|
}
|
|
|
|
bool Generator::writeIncludesInSource() {
|
|
if (!module_.hasIncludes()) {
|
|
return true;
|
|
}
|
|
|
|
auto includes = QStringList();
|
|
std::function<bool(const Module&)> collector = [this, &collector, &includes](const Module &module) {
|
|
module.enumIncludes(collector);
|
|
auto base = moduleBaseName(module);
|
|
if (!includes.contains(base)) {
|
|
includes.push_back(base);
|
|
}
|
|
return true;
|
|
};
|
|
auto result = module_.enumIncludes(collector);
|
|
for (auto base : includes) {
|
|
source_->include(base + ".h");
|
|
}
|
|
source_->newline();
|
|
return result;
|
|
}
|
|
|
|
bool Generator::writeVariableDefinitions() {
|
|
if (!module_.hasVariables()) {
|
|
return true;
|
|
}
|
|
|
|
source_->newline();
|
|
bool result = module_.enumVariables([this](const Variable &variable) -> bool {
|
|
auto name = variable.name.back();
|
|
auto type = typeToString(variable.value.type());
|
|
if (type.isEmpty()) {
|
|
return false;
|
|
}
|
|
source_->stream() << type << " _" << name << " = " << typeToDefaultValue(variable.value.type()) << ";\n";
|
|
return true;
|
|
});
|
|
return result;
|
|
}
|
|
|
|
bool Generator::writeRefsDefinition() {
|
|
if (!module_.hasVariables()) {
|
|
return true;
|
|
}
|
|
|
|
if (isPalette_) {
|
|
source_->stream() << "const style::color &transparent(_palette.get_transparent()); // special color\n";
|
|
}
|
|
bool result = module_.enumVariables([this](const Variable &variable) -> bool {
|
|
auto name = variable.name.back();
|
|
auto type = typeToString(variable.value.type());
|
|
if (type.isEmpty()) {
|
|
return false;
|
|
}
|
|
source_->stream() << "const " << type << " &" << name << "(";
|
|
if (isPalette_) {
|
|
source_->stream() << "_palette.get_" << name << "()";
|
|
} else {
|
|
source_->stream() << "_" << name;
|
|
}
|
|
source_->stream() << ");\n";
|
|
return true;
|
|
});
|
|
return result;
|
|
}
|
|
|
|
bool Generator::writeSetPaletteColor() {
|
|
source_->newline();
|
|
source_->stream() << "\n\
|
|
int palette::indexOfColor(style::color c) const {\n\
|
|
auto start = data(0);\n\
|
|
if (c._data >= start && c._data < start + kCount) {\n\
|
|
return static_cast<int>(c._data - start);\n\
|
|
}\n\
|
|
return -1;\n\
|
|
}\n\
|
|
\n\
|
|
color palette::colorAtIndex(int index) const {\n\
|
|
t_assert(_ready);\n\
|
|
t_assert(index >= 0 && index < kCount);\n\
|
|
return _colors[index];\n\
|
|
}\n\
|
|
\n\
|
|
void palette::finalize() {\n\
|
|
if (_ready) return;\n\
|
|
_ready = true;\n\
|
|
\n\
|
|
compute(0, -1, { 255, 255, 255, 0}); // special color\n";
|
|
|
|
QList<structure::FullName> names;
|
|
module_.enumVariables([this, &names](const Variable &variable) -> bool {
|
|
names.push_back(variable.name);
|
|
return true;
|
|
});
|
|
|
|
QString dataRows;
|
|
int indexInPalette = 1;
|
|
QByteArray checksumString;
|
|
checksumString.append("&transparent:{ 255, 255, 255, 0 }");
|
|
auto result = module_.enumVariables([this, &indexInPalette, &checksumString, &dataRows, &names](const Variable &variable) -> bool {
|
|
auto name = variable.name.back();
|
|
auto index = indexInPalette++;
|
|
paletteIndices_.emplace(name, index);
|
|
if (variable.value.type().tag != structure::TypeTag::Color) {
|
|
return false;
|
|
}
|
|
auto color = variable.value.Color();
|
|
auto fallbackIterator = paletteIndices_.find(colorFallbackName(variable.value));
|
|
auto fallbackIndex = (fallbackIterator == paletteIndices_.end()) ? -1 : fallbackIterator->second;
|
|
auto assignment = QString("{ %1, %2, %3, %4 }").arg(color.red).arg(color.green).arg(color.blue).arg(color.alpha);
|
|
source_->stream() << "\tcompute(" << index << ", " << fallbackIndex << ", " << assignment << ");\n";
|
|
checksumString.append('&' + name + ':' + assignment);
|
|
|
|
auto isCopy = !variable.value.copyOf().isEmpty();
|
|
auto colorString = paletteColorValue(color);
|
|
auto fallbackName = QString();
|
|
if (fallbackIndex > 0) {
|
|
auto fallbackVariable = module_.findVariableInModule(names[fallbackIndex - 1], module_);
|
|
if (fallbackVariable && fallbackVariable->value.type().tag == structure::TypeTag::Color) {
|
|
fallbackName = fallbackVariable->name.back();
|
|
}
|
|
}
|
|
auto value = isCopy ? fallbackName : '#' + colorString;
|
|
if (value.isEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
dataRows.append("\tresult.push_back({ qstr(\"" + name + "\"), qstr(\"" + value + "\"), qstr(\"" + (isCopy ? QString() : fallbackName) + "\"), qstr(" + stringToEncodedString(variable.description.toStdString()) + ") });\n");
|
|
return true;
|
|
});
|
|
if (!result) {
|
|
return false;
|
|
}
|
|
auto count = indexInPalette;
|
|
auto checksum = hashCrc32(checksumString.constData(), checksumString.size());
|
|
|
|
source_->stream() << "\
|
|
}\n\
|
|
\n\
|
|
int32 palette::Checksum() {\n\
|
|
return " << checksum << ";\n\
|
|
}\n";
|
|
|
|
source_->newline().pushNamespace().newline();
|
|
source_->stream() << "\
|
|
int getPaletteIndex(QLatin1String name) {\n\
|
|
auto size = name.size();\n\
|
|
auto data = name.data();\n";
|
|
|
|
auto tabs = [](int size) {
|
|
return QString(size, '\t');
|
|
};
|
|
|
|
enum class UsedCheckType {
|
|
Switch,
|
|
If,
|
|
UpcomingIf,
|
|
};
|
|
auto checkTypes = QVector<UsedCheckType>();
|
|
auto checkLengthHistory = QVector<int>(1, 0);
|
|
auto chars = QString();
|
|
auto tabsUsed = 1;
|
|
|
|
// Returns true if at least one check was finished.
|
|
auto finishChecksTillKey = [this, &chars, &checkTypes, &checkLengthHistory, &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();
|
|
checkLengthHistory.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;
|
|
};
|
|
|
|
auto countMinimalLength = [](auto it, auto end, int charIndex) {
|
|
auto key = it->first;
|
|
auto i = it;
|
|
auto keyStart = key.mid(0, charIndex);
|
|
auto result = key.size();
|
|
for (++i; i != end; ++i) {
|
|
auto nextKey = i->first;
|
|
if (nextKey.mid(0, charIndex) != keyStart) {
|
|
break;
|
|
} else if (nextKey.size() > charIndex && result > nextKey.size()) {
|
|
result = nextKey.size();
|
|
}
|
|
}
|
|
return result;
|
|
};
|
|
|
|
for (auto i = paletteIndices_.begin(), e = paletteIndices_.end(); i != e; ++i) {
|
|
auto name = i->first;
|
|
auto index = i->second;
|
|
|
|
auto weContinueOldSwitch = finishChecksTillKey(name);
|
|
while (chars.size() != name.size()) {
|
|
auto checking = chars.size();
|
|
auto partialKey = name.mid(0, checking);
|
|
|
|
auto keyChar = name[checking];
|
|
auto usedIfForCheckCount = 0;
|
|
auto minimalLengthCheck = countMinimalLength(i, e, checking);
|
|
for (; checking + usedIfForCheckCount != name.size(); ++usedIfForCheckCount) {
|
|
if (!canUseIfForCheck(i, e, checking + usedIfForCheckCount)
|
|
|| countMinimalLength(i, e, checking + usedIfForCheckCount) != minimalLengthCheck) {
|
|
break;
|
|
}
|
|
}
|
|
auto usedIfForCheck = !weContinueOldSwitch && (usedIfForCheckCount > 0);
|
|
auto checkLengthCondition = QString();
|
|
if (weContinueOldSwitch) {
|
|
weContinueOldSwitch = false;
|
|
} else {
|
|
checkLengthCondition = (minimalLengthCheck > checkLengthHistory.back()) ? ("size >= " + QString::number(minimalLengthCheck)) : QString();
|
|
if (!usedIfForCheck) {
|
|
source_->stream() << tabs(tabsUsed) << (checkLengthCondition.isEmpty() ? QString() : ("if (" + checkLengthCondition + ") ")) << "switch (data[" << checking << "]) {\n";
|
|
}
|
|
}
|
|
if (usedIfForCheck) {
|
|
auto conditions = QStringList();
|
|
if (usedIfForCheckCount > 1) {
|
|
conditions.push_back("!memcmp(data + " + QString::number(checking) + ", \"" + name.mid(checking, usedIfForCheckCount) + "\", " + QString::number(usedIfForCheckCount) + ")");
|
|
} else {
|
|
conditions.push_back("data[" + QString::number(checking) + "] == '" + keyChar + "'");
|
|
}
|
|
if (!checkLengthCondition.isEmpty()) {
|
|
conditions.push_front(checkLengthCondition);
|
|
}
|
|
source_->stream() << tabs(tabsUsed) << "if (" << conditions.join(" && ") << ") {\n";
|
|
checkTypes.push_back(UsedCheckType::If);
|
|
for (auto i = 1; i != usedIfForCheckCount; ++i) {
|
|
checkTypes.push_back(UsedCheckType::UpcomingIf);
|
|
chars.push_back(keyChar);
|
|
checkLengthHistory.push_back(qMax(minimalLengthCheck, checkLengthHistory.back()));
|
|
keyChar = name[checking + i];
|
|
}
|
|
} else {
|
|
source_->stream() << tabs(tabsUsed) << "case '" << keyChar << "':\n";
|
|
checkTypes.push_back(UsedCheckType::Switch);
|
|
}
|
|
++tabsUsed;
|
|
chars.push_back(keyChar);
|
|
checkLengthHistory.push_back(qMax(minimalLengthCheck, checkLengthHistory.back()));
|
|
}
|
|
source_->stream() << tabs(tabsUsed) << "return (size == " << chars.size() << ") ? " << index << " : -1;\n";
|
|
}
|
|
finishChecksTillKey(QString());
|
|
|
|
source_->stream() << "\
|
|
\n\
|
|
return -1;\n\
|
|
}\n";
|
|
|
|
source_->newline().popNamespace().newline();
|
|
source_->stream() << "\
|
|
QByteArray palette::save() const {\n\
|
|
if (!_ready) const_cast<palette*>(this)->finalize();\n\
|
|
\n\
|
|
auto result = QByteArray(" << (count * 4) << ", Qt::Uninitialized);\n\
|
|
for (auto i = 0, index = 0; i != " << count << "; ++i) {\n\
|
|
result[index++] = static_cast<uchar>(data(i)->c.red());\n\
|
|
result[index++] = static_cast<uchar>(data(i)->c.green());\n\
|
|
result[index++] = static_cast<uchar>(data(i)->c.blue());\n\
|
|
result[index++] = static_cast<uchar>(data(i)->c.alpha());\n\
|
|
}\n\
|
|
return result;\n\
|
|
}\n\
|
|
\n\
|
|
bool palette::load(const QByteArray &cache) {\n\
|
|
if (cache.size() != " << (count * 4) << ") return false;\n\
|
|
\n\
|
|
auto p = reinterpret_cast<const uchar*>(cache.constData());\n\
|
|
for (auto i = 0; i != " << count << "; ++i) {\n\
|
|
setData(i, { p[i * 4 + 0], p[i * 4 + 1], p[i * 4 + 2], p[i * 4 + 3] });\n\
|
|
}\n\
|
|
return true;\n\
|
|
}\n\
|
|
\n\
|
|
palette::SetResult palette::setColor(QLatin1String name, uchar r, uchar g, uchar b, uchar a) {\n\
|
|
auto nameIndex = getPaletteIndex(name);\n\
|
|
if (nameIndex < 0) return SetResult::KeyNotFound;\n\
|
|
auto duplicate = (_status[nameIndex] != Status::Initial);\n\
|
|
\n\
|
|
setData(nameIndex, { r, g, b, a });\n\
|
|
return duplicate ? SetResult::Duplicate : SetResult::Ok;\n\
|
|
}\n\
|
|
\n\
|
|
palette::SetResult palette::setColor(QLatin1String name, QLatin1String from) {\n\
|
|
auto nameIndex = getPaletteIndex(name);\n\
|
|
if (nameIndex < 0) return SetResult::KeyNotFound;\n\
|
|
auto duplicate = (_status[nameIndex] != Status::Initial);\n\
|
|
\n\
|
|
auto fromIndex = getPaletteIndex(from);\n\
|
|
if (fromIndex < 0 || _status[fromIndex] != Status::Loaded) return SetResult::ValueNotFound;\n\
|
|
\n\
|
|
setData(nameIndex, *data(fromIndex));\n\
|
|
return duplicate ? SetResult::Duplicate : SetResult::Ok;\n\
|
|
}\n\
|
|
\n\
|
|
namespace main_palette {\n\
|
|
\n\
|
|
QByteArray save() {\n\
|
|
return _palette.save();\n\
|
|
}\n\
|
|
\n\
|
|
bool load(const QByteArray &cache) {\n\
|
|
if (_palette.load(cache)) {\n\
|
|
style::internal::resetIcons();\n\
|
|
return true;\n\
|
|
}\n\
|
|
return false;\n\
|
|
}\n\
|
|
\n\
|
|
palette::SetResult setColor(QLatin1String name, uchar r, uchar g, uchar b, uchar a) {\n\
|
|
return _palette.setColor(name, r, g, b, a);\n\
|
|
}\n\
|
|
\n\
|
|
palette::SetResult setColor(QLatin1String name, QLatin1String from) {\n\
|
|
return _palette.setColor(name, from);\n\
|
|
}\n\
|
|
\n\
|
|
void apply(const palette &other) {\n\
|
|
_palette = other;\n\
|
|
style::internal::resetIcons();\n\
|
|
}\n\
|
|
\n\
|
|
void reset() {\n\
|
|
_palette.reset();\n\
|
|
style::internal::resetIcons();\n\
|
|
}\n\
|
|
\n\
|
|
int indexOfColor(color c) {\n\
|
|
return _palette.indexOfColor(c);\n\
|
|
}\n\
|
|
\n\
|
|
QList<row> data() {\n\
|
|
auto result = QList<row>();\n\
|
|
result.reserve(" << count << ");\n\
|
|
\n\
|
|
" << dataRows << "\n\
|
|
return result;\n\
|
|
}\n\
|
|
\n\
|
|
} // namespace main_palette\n\
|
|
\n";
|
|
|
|
return result;
|
|
}
|
|
|
|
bool Generator::writeVariableInit() {
|
|
if (!module_.hasVariables()) {
|
|
return true;
|
|
}
|
|
|
|
if (!collectUniqueValues()) {
|
|
return false;
|
|
}
|
|
bool hasUniqueValues = (!pxValues_.isEmpty() || !fontFamilies_.isEmpty() || !iconMasks_.isEmpty());
|
|
if (hasUniqueValues) {
|
|
source_->pushNamespace();
|
|
if (!writePxValuesInit()) {
|
|
return false;
|
|
}
|
|
if (!writeFontFamiliesInit()) {
|
|
return false;
|
|
}
|
|
if (!writeIconValues()) {
|
|
return false;
|
|
}
|
|
source_->popNamespace().newline();
|
|
}
|
|
|
|
source_->stream() << "\
|
|
void init_" << baseName_ << "() {\n\
|
|
if (inited) return;\n\
|
|
inited = true;\n\n";
|
|
|
|
if (module_.hasIncludes()) {
|
|
bool writtenAtLeastOne = false;
|
|
bool result = module_.enumIncludes([this,&writtenAtLeastOne](const Module &module) -> bool {
|
|
if (module.hasVariables()) {
|
|
source_->stream() << "\tinit_" + moduleBaseName(module) + "();\n";
|
|
writtenAtLeastOne = true;
|
|
}
|
|
return true;
|
|
});
|
|
if (!result) {
|
|
return false;
|
|
}
|
|
if (writtenAtLeastOne) {
|
|
source_->newline();
|
|
}
|
|
}
|
|
|
|
if (!pxValues_.isEmpty() || !fontFamilies_.isEmpty()) {
|
|
if (!pxValues_.isEmpty()) {
|
|
source_->stream() << "\tinitPxValues();\n";
|
|
}
|
|
if (!fontFamilies_.isEmpty()) {
|
|
source_->stream() << "\tinitFontFamilies();\n";
|
|
}
|
|
source_->newline();
|
|
}
|
|
|
|
if (isPalette_) {
|
|
source_->stream() << "\t_palette.finalize();\n";
|
|
} else if (!module_.enumVariables([this](const Variable &variable) -> bool {
|
|
auto name = variable.name.back();
|
|
auto value = valueAssignmentCode(variable.value);
|
|
if (value.isEmpty()) {
|
|
return false;
|
|
}
|
|
source_->stream() << "\t_" << name << " = " << value << ";\n";
|
|
return true;
|
|
})) {
|
|
return false;
|
|
}
|
|
source_->stream() << "\
|
|
}\n\n";
|
|
return true;
|
|
}
|
|
|
|
bool Generator::writePxValuesInit() {
|
|
if (pxValues_.isEmpty()) {
|
|
return true;
|
|
}
|
|
|
|
for (auto i = pxValues_.cbegin(), e = pxValues_.cend(); i != e; ++i) {
|
|
source_->stream() << "int " << pxValueName(i.key()) << " = " << i.key() << ";\n";
|
|
}
|
|
source_->stream() << "\
|
|
void initPxValues() {\n\
|
|
if (cRetina()) return;\n\
|
|
\n\
|
|
switch (cScale()) {\n";
|
|
for (int i = 1, scalesCount = scales.size(); i < scalesCount; ++i) {
|
|
source_->stream() << "\tcase " << scaleNames.at(i) << ":\n";
|
|
for (auto it = pxValues_.cbegin(), e = pxValues_.cend(); it != e; ++it) {
|
|
auto value = it.key();
|
|
int adjusted = structure::data::pxAdjust(value, scales.at(i));
|
|
if (adjusted != value) {
|
|
source_->stream() << "\t\t" << pxValueName(value) << " = " << adjusted << ";\n";
|
|
}
|
|
}
|
|
source_->stream() << "\tbreak;\n";
|
|
}
|
|
source_->stream() << "\
|
|
}\n\
|
|
}\n\n";
|
|
return true;
|
|
}
|
|
|
|
bool Generator::writeFontFamiliesInit() {
|
|
if (fontFamilies_.isEmpty()) {
|
|
return true;
|
|
}
|
|
|
|
for (auto familyIndex : fontFamilies_) {
|
|
source_->stream() << "int font" << familyIndex << "index;\n";
|
|
}
|
|
source_->stream() << "void initFontFamilies() {\n";
|
|
for (auto i = fontFamilies_.cbegin(), e = fontFamilies_.cend(); i != e; ++i) {
|
|
auto family = stringToEncodedString(i.key());
|
|
source_->stream() << "\tfont" << i.value() << "index = style::internal::registerFontFamily(" << family << ");\n";
|
|
}
|
|
source_->stream() << "}\n\n";
|
|
return true;
|
|
}
|
|
|
|
namespace {
|
|
|
|
QByteArray iconMaskValueSize(int width, int height) {
|
|
QByteArray result;
|
|
QLatin1String generateTag("GENERATE:");
|
|
result.append(generateTag.data(), generateTag.size());
|
|
QLatin1String sizeTag("SIZE:");
|
|
result.append(sizeTag.data(), sizeTag.size());
|
|
{
|
|
QBuffer buffer(&result);
|
|
buffer.open(QIODevice::Append);
|
|
|
|
QDataStream stream(&buffer);
|
|
stream.setVersion(QDataStream::Qt_5_1);
|
|
stream << qint32(width) << qint32(height);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
QByteArray iconMaskValuePng(QString filepath) {
|
|
QByteArray result;
|
|
|
|
auto pathAndModifiers = filepath.split('-');
|
|
filepath = pathAndModifiers[0];
|
|
auto modifiers = pathAndModifiers.mid(1);
|
|
|
|
QImage png100x(filepath + ".png");
|
|
QImage png200x(filepath + "@2x.png");
|
|
png100x.setDevicePixelRatio(1.);
|
|
png200x.setDevicePixelRatio(1.);
|
|
if (png100x.isNull()) {
|
|
common::logError(common::kErrorFileNotOpened, filepath + ".png") << "could not open icon file";
|
|
return result;
|
|
}
|
|
if (png200x.isNull()) {
|
|
common::logError(common::kErrorFileNotOpened, filepath + "@2x.png") << "could not open icon file";
|
|
return result;
|
|
}
|
|
if (png100x.format() != png200x.format()) {
|
|
common::logError(kErrorBadIconFormat, filepath + ".png") << "1x and 2x icons have different format";
|
|
return result;
|
|
}
|
|
if (png100x.width() * 2 != png200x.width() || png100x.height() * 2 != png200x.height()) {
|
|
common::logError(kErrorBadIconSize, filepath + ".png") << "bad icons size, 1x: " << png100x.width() << "x" << png100x.height() << ", 2x: " << png200x.width() << "x" << png200x.height();
|
|
return result;
|
|
}
|
|
for (auto modifierName : modifiers) {
|
|
if (auto modifier = GetModifier(modifierName)) {
|
|
modifier(png100x, png200x);
|
|
} else {
|
|
common::logError(common::kErrorInternal, filepath) << "modifier should be valid here, name: " << modifierName.toStdString();
|
|
return result;
|
|
}
|
|
}
|
|
QImage png125x = png200x.scaled(structure::data::pxAdjust(png100x.width(), 5), structure::data::pxAdjust(png100x.height(), 5), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
|
|
QImage png150x = png200x.scaled(structure::data::pxAdjust(png100x.width(), 6), structure::data::pxAdjust(png100x.height(), 6), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
|
|
|
|
QImage composed(png200x.width() + png100x.width(), png200x.height() + png150x.height(), png100x.format());
|
|
{
|
|
QPainter p(&composed);
|
|
p.setCompositionMode(QPainter::CompositionMode_Source);
|
|
p.fillRect(0, 0, composed.width(), composed.height(), QColor(0, 0, 0, 255));
|
|
p.drawImage(0, 0, png200x);
|
|
p.drawImage(png200x.width(), 0, png100x);
|
|
p.drawImage(0, png200x.height(), png150x);
|
|
p.drawImage(png150x.width(), png200x.height(), png125x);
|
|
}
|
|
{
|
|
QBuffer buffer(&result);
|
|
composed.save(&buffer, "PNG");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
bool Generator::writeIconValues() {
|
|
if (iconMasks_.isEmpty()) {
|
|
return true;
|
|
}
|
|
|
|
for (auto i = iconMasks_.cbegin(), e = iconMasks_.cend(); i != e; ++i) {
|
|
QString filePath = i.key();
|
|
QByteArray maskData;
|
|
QImage png100x, png200x;
|
|
if (filePath.startsWith("size://")) {
|
|
QStringList dimensions = filePath.mid(7).split(',');
|
|
if (dimensions.size() < 2 || dimensions.at(0).toInt() <= 0 || dimensions.at(1).toInt() <= 0) {
|
|
common::logError(common::kErrorFileNotOpened, filePath) << "bad dimensions";
|
|
return false;
|
|
}
|
|
maskData = iconMaskValueSize(dimensions.at(0).toInt(), dimensions.at(1).toInt());
|
|
} else {
|
|
maskData = iconMaskValuePng(filePath);
|
|
}
|
|
if (maskData.isEmpty()) {
|
|
return false;
|
|
}
|
|
source_->stream() << "const uchar iconMask" << i.value() << "Data[] = " << stringToBinaryArray(std::string(maskData.constData(), maskData.size())) << ";\n";
|
|
source_->stream() << "IconMask iconMask" << i.value() << "(iconMask" << i.value() << "Data);\n\n";
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Generator::collectUniqueValues() {
|
|
int fontFamilyIndex = 0;
|
|
int iconMaskIndex = 0;
|
|
std::function<bool(const Variable&)> collector = [this, &collector, &fontFamilyIndex, &iconMaskIndex](const Variable &variable) {
|
|
auto value = variable.value;
|
|
if (!value.copyOf().isEmpty()) {
|
|
return true;
|
|
}
|
|
|
|
switch (value.type().tag) {
|
|
case Tag::Invalid:
|
|
case Tag::Int:
|
|
case Tag::Double:
|
|
case Tag::String:
|
|
case Tag::Color:
|
|
case Tag::Align: break;
|
|
case Tag::Pixels: pxValues_.insert(value.Int(), true); break;
|
|
case Tag::Point: {
|
|
auto v(value.Point());
|
|
pxValues_.insert(v.x, true);
|
|
pxValues_.insert(v.y, true);
|
|
} break;
|
|
case Tag::Size: {
|
|
auto v(value.Size());
|
|
pxValues_.insert(v.width, true);
|
|
pxValues_.insert(v.height, true);
|
|
} break;
|
|
case Tag::Margins: {
|
|
auto v(value.Margins());
|
|
pxValues_.insert(v.left, true);
|
|
pxValues_.insert(v.top, true);
|
|
pxValues_.insert(v.right, true);
|
|
pxValues_.insert(v.bottom, true);
|
|
} break;
|
|
case Tag::Font: {
|
|
auto v(value.Font());
|
|
pxValues_.insert(v.size, true);
|
|
if (!v.family.empty() && !fontFamilies_.contains(v.family)) {
|
|
fontFamilies_.insert(v.family, ++fontFamilyIndex);
|
|
}
|
|
} break;
|
|
case Tag::Icon: {
|
|
auto v(value.Icon());
|
|
for (auto &part : v.parts) {
|
|
pxValues_.insert(part.offset.Point().x, true);
|
|
pxValues_.insert(part.offset.Point().y, true);
|
|
if (!iconMasks_.contains(part.filename)) {
|
|
iconMasks_.insert(part.filename, ++iconMaskIndex);
|
|
}
|
|
}
|
|
} break;
|
|
case Tag::Struct: {
|
|
auto fields = variable.value.Fields();
|
|
if (!fields) {
|
|
return false;
|
|
}
|
|
|
|
for (auto field : *fields) {
|
|
if (!collector(field.variable)) {
|
|
return false;
|
|
}
|
|
}
|
|
} break;
|
|
}
|
|
return true;
|
|
};
|
|
return module_.enumVariables(collector);
|
|
}
|
|
|
|
} // namespace style
|
|
} // namespace codegen
|