/* 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 #include #include #include #include #include #include #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(data); quint32 crc = 0xffffffff; for (int i = 0; i != len; ++i) { crc = (crc >> 8) ^ table[(crc & 0xFF) ^ buffer[i]]; } return static_cast(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(&ch)) & 0x0F); } char hexFirstChar(char ch) { return hexChar((*reinterpret_cast(&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(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(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(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(_data) + index;\n\ }\n\ \n\ const internal::ColorData *data(int index) const {\n\ return reinterpret_cast(_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 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 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(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 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(); auto checkLengthHistory = QVector(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(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(data(i)->c.red());\n\ result[index++] = static_cast(data(i)->c.green());\n\ result[index++] = static_cast(data(i)->c.blue());\n\ result[index++] = static_cast(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(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 data() {\n\ auto result = QList();\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 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