diff --git a/Source/engine/render/text_render.cpp b/Source/engine/render/text_render.cpp index 002f313e9..c2abebd31 100644 --- a/Source/engine/render/text_render.cpp +++ b/Source/engine/render/text_render.cpp @@ -45,7 +45,39 @@ namespace { constexpr char32_t ZWSP = U'\u200B'; // Zero-width space -ankerl::unordered_dense::map Fonts; +struct OwnedFontStack { + OptionalOwnedClxSpriteList baseFont; + OptionalOwnedClxSpriteList overrideFont; +}; + +struct FontStack { + OptionalClxSpriteList baseFont; + OptionalClxSpriteList overrideFont; + + FontStack() = default; + + explicit FontStack(const OwnedFontStack &owned) + { + if (owned.baseFont.has_value()) baseFont.emplace(*owned.baseFont); + if (owned.overrideFont.has_value()) overrideFont.emplace(*owned.overrideFont); + } + + [[nodiscard]] bool has_value() const // NOLINT(readability-identifier-naming) + { + return baseFont.has_value() || overrideFont.has_value(); + } + + [[nodiscard]] ClxSprite glyph(size_t i) const + { + if (overrideFont.has_value()) { + ClxSprite overrideGlyph = (*overrideFont)[i]; + if (overrideGlyph.width() != 0) return overrideGlyph; + } + return (*baseFont)[i]; + } +}; + +ankerl::unordered_dense::map Fonts; std::array FontSizes = { 12, 24, 30, 42, 46, 22 }; constexpr std::array LineHeights = { 12, 26, 38, 42, 50, 22 }; @@ -153,7 +185,7 @@ uint32_t GetFontId(GameFontTables size, uint16_t row) return (size << 16) | row; } -OptionalClxSpriteList LoadFont(GameFontTables size, text_color color, uint16_t row) +FontStack LoadFont(GameFontTables size, text_color color, uint16_t row) { if (ColorTranslations[color] != nullptr && !ColorTranslationsData[color]) { ColorTranslationsData[color].emplace(); @@ -163,48 +195,52 @@ OptionalClxSpriteList LoadFont(GameFontTables size, text_color color, uint16_t r const uint32_t fontId = GetFontId(size, row); auto hotFont = Fonts.find(fontId); if (hotFont != Fonts.end()) { - return OptionalClxSpriteList(*hotFont->second); + return FontStack(hotFont->second); } - OptionalOwnedClxSpriteList &font = Fonts[fontId]; + OwnedFontStack &font = Fonts[fontId]; char path[32]; - // Try loading the language-specific variant first: - const std::string_view language_code = GetLanguageCode(); - const std::string_view language_tag = language_code.substr(0, 2); - if (language_tag == "zh" || language_tag == "ja" || language_tag == "ko" - || (language_tag == "tr" && row == 0)) { - GetFontPath(language_code, size, row, ".clx", &path[0]); - font = LoadOptionalClx(path); - } - if (!font) { - // Fall back to the base variant: - GetFontPath(size, row, ".clx", &path[0]); - font = LoadOptionalClx(path); + // Load language-specific glyphs: + const std::string_view languageCode = GetLanguageCode(); + const std::string_view lang = languageCode.substr(0, 2); + if (lang == "zh" || lang == "ja" || lang == "ko" + || (lang == "tr" && row == 0)) { + GetFontPath(languageCode, size, row, ".clx", &path[0]); + font.overrideFont = LoadOptionalClx(path); } + // Load the base glyphs: + GetFontPath(size, row, ".clx", &path[0]); + font.baseFont = LoadOptionalClx(path); + #ifndef UNPACKED_MPQS - if (!font) { + if (!font.baseFont.has_value()) { // Could be an old devilutionx.mpq or fonts.mpq with PCX instead of CLX. // // We'll show an error elsewhere (in `CheckArchivesUpToDate`) and we need to load // the font files to display it. char pcxPath[32]; GetFontPath(size, row, "", &pcxPath[0]); - font = LoadPcxSpriteList(pcxPath, /*numFramesOrFrameHeight=*/256, /*transparentColor=*/1); + font.baseFont = LoadPcxSpriteList(pcxPath, /*numFramesOrFrameHeight=*/256, /*transparentColor=*/1); } #endif - if (!font) { + if (!font.baseFont.has_value()) { LogError("Error loading font: {}", path); } - return OptionalClxSpriteList(*font); + return FontStack(font); } class CurrentFont { public: - OptionalClxSpriteList sprite; + FontStack fontStack; + + [[nodiscard]] ClxSprite glyph(size_t i) const + { + return fontStack.glyph(i); + } bool load(GameFontTables size, text_color color, char32_t next) { @@ -213,11 +249,11 @@ public: return true; } - sprite = LoadFont(size, color, unicodeRow); + fontStack = LoadFont(size, color, unicodeRow); hasAttemptedLoad_ = true; currentUnicodeRow_ = unicodeRow; - return sprite; + return fontStack.has_value(); } void clear() @@ -424,9 +460,10 @@ uint32_t DoDrawString(const Surface &out, std::string_view text, Rectangle rect, Point position = characterPosition; MaybeWrap(position, 2, rightMargin, position.x, opts.lineHeight); if (GetAnimationFrame(2, 500) != 0) { - OptionalClxSpriteList baseFont = LoadFont(size, color, 0); - if (baseFont) - DrawFont(out, position, (*baseFont)['|'], color, outline); + FontStack baseFont = LoadFont(size, color, 0); + if (baseFont.has_value()) { + DrawFont(out, position, baseFont.glyph('|'), color, outline); + } } if (opts.renderedCursorPositionOut != nullptr) { *opts.renderedCursorPositionOut = position; @@ -448,7 +485,7 @@ uint32_t DoDrawString(const Surface &out, std::string_view text, Rectangle rect, } const uint8_t frame = next & 0xFF; - const uint16_t width = (*currentFont.sprite)[frame].width(); + const uint16_t width = currentFont.glyph(frame).width(); if (next == U'\n' || characterPosition.x + width > rightMargin) { if (next == '\n') maybeDrawCursor(); @@ -473,7 +510,7 @@ uint32_t DoDrawString(const Surface &out, std::string_view text, Rectangle rect, continue; } - const ClxSprite glyph = (*currentFont.sprite)[frame]; + const ClxSprite glyph = currentFont.glyph(frame); const auto byteIndex = static_cast(text.size() - remaining.size()); // Draw highlight @@ -528,7 +565,7 @@ int GetLineWidth(std::string_view text, GameFontTables size, int spacing, int *c } const uint8_t frame = next & 0xFF; - lineWidth += (*currentFont.sprite)[frame].width() + spacing; + lineWidth += currentFont.glyph(frame).width() + spacing; ++codepoints; } if (charactersInLine != nullptr) @@ -599,7 +636,7 @@ int GetLineWidth(std::string_view fmt, DrawStringFormatArg *args, std::size_t ar } const uint8_t frame = next & 0xFF; - lineWidth += (*currentFont.sprite)[frame].width() + spacing; + lineWidth += currentFont.glyph(frame).width() + spacing; ++codepoints; } if (charactersInLine != nullptr) @@ -660,7 +697,7 @@ std::string WordWrapString(std::string_view text, unsigned width, GameFontTables } } - lineWidth += (*currentFont.sprite)[frame].width() + spacing; + lineWidth += currentFont.glyph(frame).width() + spacing; } if (IsBreakableWhitespace(codepoint)) { @@ -840,7 +877,7 @@ void DrawStringWithColors(const Surface &out, std::string_view fmt, DrawStringFo } const uint8_t frame = next & 0xFF; - const uint16_t width = (*currentFont.sprite)[frame].width(); + const uint16_t width = currentFont.glyph(frame).width(); if (next == U'\n' || characterPosition.x + width > rightMargin) { const int nextLineY = characterPosition.y + opts.lineHeight; if (nextLineY >= bottomMargin) @@ -871,7 +908,7 @@ void DrawStringWithColors(const Surface &out, std::string_view fmt, DrawStringFo continue; } - DrawFont(clippedOut, characterPosition, (*currentFont.sprite)[frame], curColor, outlined); + DrawFont(clippedOut, characterPosition, currentFont.glyph(frame), curColor, outlined); characterPosition.x += width + opts.spacing; }