// // Created by EmsiaetKadosh on 25-1-16. // #pragma once #include #include "Chars.h" #include "def.h" #include "InteractManager.h" class LiteralText; class Translator; // class Font; class FontManager; using FontStyle = int; using FontID = unsigned short; class RenderableString { friend class LiteralText; friend class Font; struct StringConfig { String text; unsigned int color = -1; unsigned int background = -1; /** * 位意义 * 1 - bold * 2 - italic * 4 - underline * 8 - strikeThrough * 16 - default bg * 32 - default clr */ FontStyle style = 0; FontID idFont = 0; explicit StringConfig() = default; void reset() noexcept { idFont = 0; color = -1; background = -1; style = 0; } [[nodiscard]] bool isBold() const noexcept { return style & 1; } [[nodiscard]] bool isItalic() const noexcept { return style & 2; } [[nodiscard]] bool isUnderline() const noexcept { return style & 4; } [[nodiscard]] bool isStrikeThrough() const noexcept { return style & 8; } [[nodiscard]] bool isDefaultBackground() const noexcept { return (style & 16) == 0; } [[nodiscard]] bool isDefaultColor() const noexcept { return (style & 32) == 0; } void setBold(const bool value) noexcept { style = value ? style | 1 : style & ~1; } void setItalic(const bool value) noexcept { style = value ? style | 2 : style & ~2; } void setUnderline(const bool value) noexcept { style = value ? style | 4 : style & ~4; } void setStrikeThrough(const bool value) noexcept { style = value ? style | 8 : style & ~8; } void useDefaultBackground(const bool value) noexcept { style = value ? style & ~16 : style | 16; } void useDefaultColor(const bool value) noexcept { style = value ? style & ~32 : style | 32; } [[nodiscard]] StringConfig copyConfig() const noexcept { StringConfig ret; ret.idFont = idFont; ret.color = color; ret.background = background; ret.style = style; return ret; } [[nodiscard]] StringConfig copy() const noexcept { StringConfig ret; ret.idFont = idFont; ret.text = text; ret.idFont = idFont; ret.color = color; ret.background = background; ret.style = style; return ret; } [[nodiscard]] String toString() const noexcept { String ret; ret.append(L"#"); ret.append(isDefaultColor() ? L"~~" : uitowb16(color)); ret.append(L"."); ret.append(isDefaultBackground() ? L"~~" : uitowb16(background)); ret.append(L",F"); ret.append(std::to_wstring(idFont)); ret.append(L","); if (isBold()) ret.append(L"b"); if (isItalic()) ret.append(L"i"); if (isUnderline()) ret.append(L"u"); if (isStrikeThrough()) ret.append(L"s"); ret.append(L":"); ret.append(text); return ret; } }; struct RenderConfig { const Font* font; const StringConfig* config; int width; }; List configs; using Iterator = List::iterator; using ConstIterator = List::const_iterator; public: explicit RenderableString(const String& string): RenderableString(string.c_str(), string.length()) {} explicit RenderableString(String&& string) : RenderableString(string.c_str(), string.length()) {} explicit RenderableString(const wchar* const string, const QWORD length = static_cast(-1)) { if (length == -1) parseAppend(string); else parseAppend(string, length); } RenderableString(const RenderableString&) = default; RenderableString(RenderableString&&) = default; ~RenderableString() = default; [[nodiscard]] String toString() const noexcept { String ret; for (const auto& config : configs) { ret.append(config.toString()); ret.append(L";\n"); } return ret; } RenderableString& append(const RenderableString& other) { auto iterator = other.configs.cbegin(); int flags = 0; bool clr = true, bg = true, font = true; const unsigned int color = configs.back().color; const unsigned int background = configs.back().background; const FontID idFont = configs.back().idFont; while (iterator != other.configs.cend()) { if (flags >= 3) break; StringConfig config = iterator->copy(); if (font) { if (config.idFont == idFont || config.idFont != 0) { font = false; ++flags; } else config.idFont = idFont; } if (clr) { if (config.color == color || config.color != 0xffffffff) { clr = false; ++flags; } else config.color = color; } if (bg) { if (config.background == background || config.background != 0xffffffff) { bg = false; ++flags; } else config.background = background; } configs.push_back(std::move(config)); ++iterator; } while (iterator != other.configs.cend()) configs.emplace_back(*iterator); return *this; } RenderableString& append(const String& other) { parseAppend(other.c_str(), other.length()); return *this; } int getHeight() const noexcept; int getWidth(FontID defaultID = 1) const noexcept; int getWidth(RenderConfig* renderConfigs, FontID defaultID) const noexcept; private: void parseAppend(const wchar* string) noexcept { StringConfig config; if (!configs.empty()) { config = configs.back(); configs.pop_back(); } while (*string != L'\0') { const wchar* start = string; if (!config.text.empty()) { configs.push_back(std::move(config)); config = config.copyConfig(); } while (*string != L'\0' && *string != '\\') ++string; if (*string == L'\0') { if (*start != L'\0') config.text.append(start, string); configs.push_back(std::move(config)); return; } if (start != string) config.text.append(start, string); if (*++string == L'\0') { config.text.append(start, string); configs.push_back(std::move(config)); return; } if (!config.text.empty()) { configs.push_back(std::move(config)); config = config.copyConfig(); } switch (*string) { case L'\\': config.text.append(1, L'\\'); ++string; continue; case L'#': { unsigned long long i = 0; while (i < 9) if (string[i++] == L'\0') goto end; config.color = wtouib16(++string, 8); config.useDefaultColor(false); string += 8; continue; } case L'.': { unsigned long long i = 0; while (i < 9) if (string[i++] == L'\0') goto end; config.background = wtouib16(++string, 8); config.useDefaultBackground(false); string += 8; continue; } case L'F': case L'f': if (*++string == L'\0') goto end; config.idFont = *string; break; case L'-': case L's': case L'S': config.setStrikeThrough(true); break; case L'_': case L'u': case L'U': config.setUnderline(true); break; case L'/': case L'i': case L'I': config.setItalic(true); break; case L'=': case L'b': case L'B': config.setBold(true); break; case L'r': case L'R': if (!config.text.empty()) configs.push_back(std::move(config)); config = StringConfig(); break; default: config.text.append(string - 1, 2); break; } ++string; } end: configs.push_back(std::move(config)); } void parseAppend(const wchar* string, const QWORD length) noexcept { StringConfig config = StringConfig(); if (!configs.empty()) { config = configs.back(); configs.pop_back(); } const wchar* const end = string + length; while (string < end) { const wchar* start = string; if (!config.text.empty()) { configs.push_back(std::move(config)); config = config.copyConfig(); } while (string < end && *string != '\\') ++string; if (string >= end) { if (start != end) config.text.append(start, end); configs.push_back(std::move(config)); return; } if (start != string) config.text.append(start, string); if (++string == end) { config.text.append(start, end); configs.push_back(std::move(config)); return; } if (!config.text.empty()) { configs.push_back(std::move(config)); config = config.copyConfig(); } switch (*string) { case L'\\': config.text.append(1, L'\\'); ++string; continue; case L'#': { if (end - string < 9) break; config.color = wtouib16(++string, 8); config.useDefaultColor(false); string += 8; continue; } case L'.': { if (end - string < 9) break; config.background = wtouib16(++string, 8); config.useDefaultBackground(false); string += 8; continue; } case L'F': case L'f': if (++string == end) break; config.idFont = *string; break; case L'-': case L's': case L'S': config.setStrikeThrough(true); break; case L'_': case L'u': case L'U': config.setUnderline(true); break; case L'/': case L'i': case L'I': config.setItalic(true); break; case L'=': case L'b': case L'B': config.setBold(true); break; case L'r': case L'R': if (!config.text.empty()) configs.push_back(std::move(config)); config = StringConfig(); break; default: config.text.append(string - 1, 2); break; } ++string; } configs.push_back(std::move(config)); } }; inline RenderableString operator""_renderable(const wchar* const text, const QWORD len) noexcept { return RenderableString(text); } class Font { public: Function resize; private: friend class ObjectHolder; friend class FontManager; friend class RenderableString; const String name; mutable Map fonts{}; double yOffset; double heightModifier; long height; long escapement; long orientation; long yOffsetPx; const FontID id; bool adaptAllSize = false; explicit Font(const FontID id, const String& name, const double heightModifier, const double yOffset, const long escapement, const long orientation, const bool adaptAllSize) : name{ name }, yOffset(yOffset), heightModifier(heightModifier), height(interactSettings.actual.fontHeight * heightModifier), escapement(escapement), orientation(orientation), yOffsetPx(yOffset * height), id(id), adaptAllSize(adaptAllSize) {} explicit Font(const FontID id, String&& name, const double heightModifier, const double yOffset, const long escapement, const long orientation, const bool adaptAllSize) : name{ std::move(name) }, yOffset(yOffset), heightModifier(heightModifier), height(interactSettings.actual.fontHeight * heightModifier), escapement(escapement), orientation(orientation), yOffsetPx(yOffset * height), id(id), adaptAllSize(adaptAllSize) {} public: Font(const Font&) = default; Font(Font&&) = default; ~Font() { clear(); } private: HFONT tryCreate(const RenderableString::StringConfig& config) const { if (const auto iter = fonts.find(config.style); iter != fonts.end()) return iter->second; LOGFONTW f{ .lfHeight = height, .lfWidth = 0, .lfEscapement = escapement, .lfOrientation = orientation, .lfWeight = config.isBold() ? FW_BOLD : FW_NORMAL, .lfItalic = config.isItalic(), .lfUnderline = config.isUnderline(), .lfStrikeOut = config.isStrikeThrough(), .lfCharSet = DEFAULT_CHARSET, .lfOutPrecision = OUT_DEFAULT_PRECIS, .lfClipPrecision = CLIP_DEFAULT_PRECIS, .lfQuality = static_cast(adaptAllSize ? DEFAULT_QUALITY : PROOF_QUALITY), .lfPitchAndFamily = FF_DONTCARE, .lfFaceName{} }; memcpy(f.lfFaceName, name.c_str(), 64); HFONT fnt = CreateFontIndirectW(&f); fonts.emplace(std::make_pair(config.style, fnt)); return fnt; } int getWidth(const RenderableString::StringConfig& config) const; int drawSingle(const RenderableString::StringConfig& config, int x, int y, unsigned int defaultColor) const; void drawDirect(const RenderableString::StringConfig& config, int x, int y, unsigned int defaultColor) const; void clear() const; public: void draw(const RenderableString& text, int x, int y, unsigned int color = 0xffeeeeee) const; void drawCenter(const RenderableString& text, int x, int y, int w, int h, unsigned int color = 0xffeeeeee) const; [[nodiscard]] int getHeight() const noexcept { return height; } [[nodiscard]] int getEscapement() const noexcept { return escapement; } [[nodiscard]] int getOrientation() const noexcept { return orientation; } [[nodiscard]] FontID getID() const noexcept { return id; } }; class FontManager { Map fonts; Font *defaultFont, *captionFont; FontID assigned = 1; using IterFonts = Map::const_iterator; public: explicit FontManager() { captionFont = &newFont(L"Microsoft YaHei UI Light"); defaultFont = &newFont(L"Microsoft YaHei UI Light"); captionFont->height = interactSettings.actual.captionHeight >> 1; captionFont->resize = [this](int, int) { if (interactSettings.actual.captionHeight != captionFont->height) { captionFont->height = interactSettings.actual.captionHeight >> 1; captionFont->clear(); } }; newFont(L"Jetbrains Mono", 1.0, -0.078); newFont(L"STSong", 1.0, -0.12); newFont(L"Arial", 1.0, -0.13); newFont(L"", 1.0, -0.05); } [[nodiscard]] const Font& get(const FontID id) const noexcept { const IterFonts iter = fonts.find(id); if (iter == fonts.cend()) return *defaultFont; return iter->second; } const Font& newFont(const String& name, const double heightModifier = 1.0, const double yOffset = 0.0, const bool adaptAllSize = true, const long escapement = 0, const long orientation = 0) noexcept { ++assigned; return fonts.emplace(assigned, std::move(Font(assigned, name, heightModifier, yOffset, adaptAllSize, escapement, orientation))).first->second; } Font& newFont(String&& name, const double heightModifier = 1.0, const double yOffset = 0.0, const bool adaptAllSize = true, const long escapement = 0, const long orientation = 0) { ++assigned; return fonts.emplace(assigned, std::move(Font(assigned, std::move(name), heightModifier, yOffset, adaptAllSize, escapement, orientation))).first->second; } Font& getDefault() const noexcept { return *defaultFont; } void resize(const int width, const int height) { for (auto& font : fonts | std::views::values) if (font.resize) font.resize(width, height); else { font.height = interactSettings.actual.fontHeight * font.heightModifier; font.yOffsetPx = font.height * font.yOffset; font.clear(); } } }; interface IText { virtual ~IText() = default; [[nodiscard]] virtual const String& getText() const noexcept = 0; [[nodiscard]] virtual const RenderableString& getRenderableString() const noexcept = 0; }; typedef class LiteralText final : public IText { const String string; mutable RenderableString renderableString; public: explicit LiteralText(const String& string): string(string), renderableString(string) {} explicit LiteralText(String&& string): string(std::move(string)), renderableString(this->string) {} const String& getText() const noexcept override { return string; } const RenderableString& getRenderableString() const noexcept override { return renderableString; } } TranslatedText; class TranslatableText final : public IText { const String idSrc; mutable const LiteralText* target = nullptr; mutable QWORD langConfig = 0; public: explicit TranslatableText(const String& id) : idSrc(id) {} explicit TranslatableText(String&& id) : idSrc(std::move(id)) {} const String& getText() const noexcept override; const RenderableString& getRenderableString() const noexcept override; void refreshText() const noexcept; }; struct Language { Map translateTable; int id = 1; }; class Translator { Map langMap{}; List langList{}; TranslatedText nullText{ L"\\#FF""EE0000" }; String lang = L"zh-cn"; int langConfig = 1; int idLangMax = 1; using IterID = Map::const_iterator; using IterLang = List::const_iterator; public: explicit Translator() { langMap.insert(std::make_pair(String(L"zh-cn"), 1)); } void addLang(const String& lang) noexcept { langMap.insert(std::make_pair(lang, ++idLangMax)); } void addLang(String&& lang) noexcept { langMap.insert(std::make_pair(lang, ++idLangMax)); } void loadLang(); [[nodiscard]] const TranslatedText* getText(const String& id) const noexcept { for (const auto& [translateTable, _] : langList) { IterID iterator = translateTable.find(id); if (iterator != translateTable.cend()) return &iterator->second; } return &nullText; } [[nodiscard]] int getConfigVersion() const noexcept { return langConfig; } }; extern Translator translator; extern FontManager fontManager;