From e8910bb3d7cbf96aa0f9ce5cb97a047c93ce2e4b Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Thu, 22 Jun 2023 19:43:07 +0200 Subject: [PATCH] Do not crash on missing fonts --- Source/engine/render/text_render.cpp | 108 +++++++++++++++++---------- 1 file changed, 68 insertions(+), 40 deletions(-) diff --git a/Source/engine/render/text_render.cpp b/Source/engine/render/text_render.cpp index 9be66d642..94b40e711 100644 --- a/Source/engine/render/text_render.cpp +++ b/Source/engine/render/text_render.cpp @@ -37,7 +37,6 @@ namespace { constexpr char32_t ZWSP = U'\u200B'; // Zero-width space -using Font = const OwnedClxSpriteList; std::unordered_map Fonts; std::unordered_map> FontKerns; @@ -144,7 +143,7 @@ bool IsSmallFontTallRow(uint16_t row) void GetFontPath(GameFontTables size, uint16_t row, string_view ext, char *out) { - *fmt::format_to(out, R"(fonts\{}-{:02x}.{})", FontSizes[size], row, ext) = '\0'; + *fmt::format_to(out, R"(fonts\{}-{:02x}{})", FontSizes[size], row, ext) = '\0'; } uint32_t GetFontId(GameFontTables size, uint16_t row) @@ -162,7 +161,7 @@ std::array *LoadFontKerning(GameFontTables size, uint16_t row) } char path[32]; - GetFontPath(size, row, "bin", &path[0]); + GetFontPath(size, row, ".bin", &path[0]); auto *kerning = &FontKerns[fontId]; @@ -181,7 +180,7 @@ std::array *LoadFontKerning(GameFontTables size, uint16_t row) return kerning; } -const OwnedClxSpriteList *LoadFont(GameFontTables size, text_color color, uint16_t row) +OptionalClxSpriteList LoadFont(GameFontTables size, text_color color, uint16_t row) { if (ColorTranslations[color] != nullptr && !ColorTranslationsData[color]) { ColorTranslationsData[color].emplace(); @@ -191,11 +190,11 @@ const OwnedClxSpriteList *LoadFont(GameFontTables size, text_color color, uint16 const uint32_t fontId = GetFontId(size, row); auto hotFont = Fonts.find(fontId); if (hotFont != Fonts.end()) { - return &*hotFont->second; + return OptionalClxSpriteList(*hotFont->second); } char path[32]; - GetFontPath(size, row, "clx", &path[0]); + GetFontPath(size, row, ".clx", &path[0]); OptionalOwnedClxSpriteList &font = Fonts[fontId]; font = LoadOptionalClx(path); @@ -205,21 +204,50 @@ const OwnedClxSpriteList *LoadFont(GameFontTables size, text_color color, uint16 // 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, "pcx", &pcxPath[0]); + GetFontPath(size, row, "", &pcxPath[0]); font = LoadPcxSpriteList(pcxPath, /*numFramesOrFrameHeight=*/256, /*transparentColor=*/1); } if (!font) { LogError("Error loading font: {}", path); - return nullptr; } - return &(*font); + return OptionalClxSpriteList(*font); } -void DrawFont(const Surface &out, Point position, const OwnedClxSpriteList *font, text_color color, int frame, bool outline) +class CurrentFont { +public: + OptionalClxSpriteList sprite; + std::array *kerning = nullptr; + + bool load(GameFontTables size, text_color color, char32_t next) + { + const uint32_t unicodeRow = GetUnicodeRow(next); + if (unicodeRow == currentUnicodeRow_ && hasAttemptedLoad_) { + return true; + } + + sprite = LoadFont(size, color, unicodeRow); + kerning = LoadFontKerning(size, unicodeRow); + hasAttemptedLoad_ = true; + currentUnicodeRow_ = unicodeRow; + + return sprite; + } + + void clear() + { + hasAttemptedLoad_ = false; + } + +private: + bool hasAttemptedLoad_ = false; + uint32_t currentUnicodeRow_ = 0; +}; + +void DrawFont(const Surface &out, Point position, const ClxSpriteList font, text_color color, int frame, bool outline) { - ClxSprite glyph = (*font)[frame]; + ClxSprite glyph = font[frame]; if (outline) { ClxDrawOutlineSkipColorZero(out, 0, { position.x, position.y + glyph.height() - 1 }, glyph); } @@ -398,9 +426,7 @@ uint32_t DoDrawString(const Surface &out, string_view text, Rectangle rect, Poin int spacing, int lineHeight, int lineWidth, int rightMargin, int bottomMargin, UiFlags flags, GameFontTables size, text_color color, bool outline) { - Font *font = nullptr; - std::array *kerning = nullptr; - uint32_t currentUnicodeRow = 0; + CurrentFont currentFont; char32_t next; string_view remaining = text; @@ -411,33 +437,33 @@ uint32_t DoDrawString(const Surface &out, string_view text, Rectangle rect, Poin if (next == ZWSP) continue; - const uint32_t unicodeRow = GetUnicodeRow(next); - if (unicodeRow != currentUnicodeRow || font == nullptr) { - kerning = LoadFontKerning(size, unicodeRow); - font = LoadFont(size, color, unicodeRow); - currentUnicodeRow = unicodeRow; + if (!currentFont.load(size, color, next)) { + next = U'?'; + if (!currentFont.load(size, color, next)) { + app_fatal("Missing fonts"); + } } const uint8_t frame = next & 0xFF; - if (next == '\n' || characterPosition.x + (*kerning)[frame] > rightMargin) { + if (next == U'\n' || characterPosition.x + (*currentFont.kerning)[frame] > rightMargin) { const int nextLineY = characterPosition.y + lineHeight; if (nextLineY >= bottomMargin) break; characterPosition.y = nextLineY; if (HasAnyOf(flags, (UiFlags::AlignCenter | UiFlags::AlignRight))) { - lineWidth = (*kerning)[frame]; + lineWidth = (*currentFont.kerning)[frame]; if (remaining.size() > cpLen) lineWidth += spacing + GetLineWidth(remaining.substr(cpLen), size, spacing); } characterPosition.x = GetLineStartX(flags, rect, lineWidth); - if (next == '\n') + if (next == U'\n') continue; } - DrawFont(out, characterPosition, font, color, frame, outline); - characterPosition.x += (*kerning)[frame] + spacing; + DrawFont(out, characterPosition, *currentFont.sprite, color, frame, outline); + characterPosition.x += (*currentFont.kerning)[frame] + spacing; } return remaining.data() - text.data(); } @@ -470,7 +496,7 @@ int GetLineWidth(string_view text, GameFontTables size, int spacing, int *charac if (next == ZWSP) continue; - if (next == '\n') + if (next == U'\n') break; uint8_t frame = next & 0xFF; @@ -686,7 +712,9 @@ uint32_t DrawString(const Surface &out, string_view text, const Rectangle &rect, ClxDraw(clippedOut, characterPosition + Displacement { 0, lineHeight - BaseLineOffset[size] }, sprite); } else if (HasAnyOf(flags, UiFlags::TextCursor) && GetAnimationFrame(2, 500) != 0) { MaybeWrap(characterPosition, 2, rightMargin, initialX, lineHeight); - DrawFont(clippedOut, characterPosition, LoadFont(size, color, 0), color, '|', outlined); + OptionalClxSpriteList baseFont = LoadFont(size, color, 0); + if (baseFont) + DrawFont(clippedOut, characterPosition, *baseFont, color, '|', outlined); } return bytesDrawn; @@ -726,12 +754,10 @@ void DrawStringWithColors(const Surface &out, string_view fmt, DrawStringFormatA const Surface clippedOut = ClipSurface(out, rect); - Font *font = nullptr; - std::array *kerning = nullptr; + CurrentFont currentFont; char32_t prev = U'\0'; char32_t next; - uint32_t currentUnicodeRow = 0; string_view remaining = fmt; FmtArgParser fmtArgParser { fmt, args, argsLen }; size_t cpLen; @@ -750,26 +776,26 @@ void DrawStringWithColors(const Surface &out, string_view fmt, DrawStringFormatA cpLen = 0; // The loop assigns `prev = next`. We want `prev` to be `\0` after this. next = U'\0'; - font = nullptr; + currentFont.clear(); continue; } - const uint32_t unicodeRow = GetUnicodeRow(next); - if (unicodeRow != currentUnicodeRow || font == nullptr) { - kerning = LoadFontKerning(size, unicodeRow); - font = LoadFont(size, color, unicodeRow); - currentUnicodeRow = unicodeRow; + if (!currentFont.load(size, color, next)) { + next = U'?'; + if (!currentFont.load(size, color, next)) { + app_fatal("Missing fonts"); + } } const uint8_t frame = next & 0xFF; - if (next == U'\n' || characterPosition.x + (*kerning)[frame] > rightMargin) { + if (next == U'\n' || characterPosition.x + (*currentFont.kerning)[frame] > rightMargin) { const int nextLineY = characterPosition.y + lineHeight; if (nextLineY >= bottomMargin) break; characterPosition.y = nextLineY; if (HasAnyOf(flags, (UiFlags::AlignCenter | UiFlags::AlignRight))) { - lineWidth = (*kerning)[frame]; + lineWidth = (*currentFont.kerning)[frame]; if (remaining.size() > cpLen) lineWidth += spacing + GetLineWidth(remaining.substr(cpLen), args, argsLen, fmtArgParser.offset(), size, spacing); } @@ -779,8 +805,8 @@ void DrawStringWithColors(const Surface &out, string_view fmt, DrawStringFormatA continue; } - DrawFont(clippedOut, characterPosition, font, color, frame, outlined); - characterPosition.x += (*kerning)[frame] + spacing; + DrawFont(clippedOut, characterPosition, *currentFont.sprite, color, frame, outlined); + characterPosition.x += (*currentFont.kerning)[frame] + spacing; } if (HasAnyOf(flags, UiFlags::PentaCursor)) { @@ -789,7 +815,9 @@ void DrawStringWithColors(const Surface &out, string_view fmt, DrawStringFormatA ClxDraw(clippedOut, characterPosition + Displacement { 0, lineHeight - BaseLineOffset[size] }, sprite); } else if (HasAnyOf(flags, UiFlags::TextCursor) && GetAnimationFrame(2, 500) != 0) { MaybeWrap(characterPosition, 2, rightMargin, initialX, lineHeight); - DrawFont(clippedOut, characterPosition, LoadFont(size, color, 0), color, '|', outlined); + OptionalClxSpriteList baseFont = LoadFont(size, color, 0); + if (baseFont) + DrawFont(clippedOut, characterPosition, *baseFont, color, '|', outlined); } }