From dc1e9e50fc4f6c498d5e447ff4d87875b7a2b559 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 27 Oct 2026 11:45:04 +0100 Subject: [PATCH] TextDrawer: Measure in a given style without switching fonts Measuring took the current font and scale, so UIContext switched to the style being measured and back. On first draw the font to switch back to could be an empty one, which the UWP drawer asserts on. MeasureString and MeasureStringRect now take the style and scale, and the backends' MeasureStringInternal measures in the style it's given. SetOrCreateFont, which sets the font drawing uses, is shared, and the backends only implement EnsureFont, which creates a font if needed. Co-Authored-By: Claude Opus 5.5 --- Common/Render/Text/draw_text.cpp | 42 ++++++++++++++---------- Common/Render/Text/draw_text.h | 33 +++++++++++++------ Common/Render/Text/draw_text_android.cpp | 11 +++---- Common/Render/Text/draw_text_android.h | 4 +-- Common/Render/Text/draw_text_cocoa.h | 4 +-- Common/Render/Text/draw_text_cocoa.mm | 11 +++---- Common/Render/Text/draw_text_sdl.cpp | 25 +++++--------- Common/Render/Text/draw_text_sdl.h | 6 ++-- Common/Render/Text/draw_text_uwp.cpp | 12 +++---- Common/Render/Text/draw_text_uwp.h | 4 +-- Common/Render/Text/draw_text_win.cpp | 12 +++---- Common/Render/Text/draw_text_win.h | 4 +-- Common/UI/Context.cpp | 25 ++------------ unittest/UnitTest.cpp | 16 +++++++-- 14 files changed, 98 insertions(+), 111 deletions(-) diff --git a/Common/Render/Text/draw_text.cpp b/Common/Render/Text/draw_text.cpp index b54c6c6a90..f2a88343f6 100644 --- a/Common/Render/Text/draw_text.cpp +++ b/Common/Render/Text/draw_text.cpp @@ -65,31 +65,36 @@ TextDrawer::TextDrawer(Draw::DrawContext *draw) : draw_(draw) { float TextDrawerWordWrapper::MeasureWidth(std::string_view str) { float w, h; - drawer_->MeasureString(str, &w, &h); + drawer_->MeasureString(style_, scale_, scale_, str, &w, &h); return w; } -const TextCacheKey &TextDrawer::LookupKey(std::string_view str, int flags, float maxWidth, float scale) { +const TextCacheKey &TextDrawer::LookupKey(const FontStyle &style, std::string_view str, int flags, float maxWidth, float scale) { lookupKey_.text.assign(str.data(), str.size()); - lookupKey_.style = fontStyle_; + lookupKey_.style = style; lookupKey_.flags = flags; lookupKey_.maxWidth = maxWidth; lookupKey_.scale = scale; return lookupKey_; } -const std::string &TextDrawer::WrapString(std::string_view str, float maxW, int flags) { +const std::string &TextDrawer::WrapString(const FontStyle &style, float scale, std::string_view str, float maxW, int flags) { // The wrapper measures in scaled units, so the result depends on the scale too. - auto [iter, inserted] = wrapCache_.try_emplace(LookupKey(str, flags, maxW, fontScaleX_)); + auto [iter, inserted] = wrapCache_.try_emplace(LookupKey(style, str, flags, maxW, scale)); TextWrapEntry &entry = iter->second; entry.lastUsedFrame = frameCount_; if (inserted) { - TextDrawerWordWrapper wrapper(this, str, maxW, flags); + TextDrawerWordWrapper wrapper(this, style, scale, str, maxW, flags); entry.wrapped = wrapper.Wrapped(); } return entry.wrapped; } +void TextDrawer::SetOrCreateFont(const FontStyle &style) { + EnsureFont(style); + fontStyle_ = style; +} + void TextDrawer::SetFontScale(float xscale, float yscale) { fontScaleX_ = xscale; fontScaleY_ = yscale; @@ -112,7 +117,7 @@ void TextDrawer::DrawString(DrawBuffer &target, std::string_view str, float x, f TextStringEntry *entry; - auto [iter, inserted] = cache_.try_emplace(LookupKey(str, align & FLAG_DROP_SHADOW)); + auto [iter, inserted] = cache_.try_emplace(LookupKey(fontStyle_, str, align & FLAG_DROP_SHADOW)); if (!inserted) { entry = iter->second.get(); entry->lastUsedFrame = frameCount_; @@ -186,7 +191,7 @@ void TextDrawer::DrawString(DrawBuffer &target, std::string_view str, float x, f target.Flush(true); } -void TextDrawer::MeasureString(std::string_view str, float *w, float *h) { +void TextDrawer::MeasureString(const FontStyle &style, float scaleX, float scaleY, std::string_view str, float *w, float *h) { if (str.empty()) { *w = 0.0f; *h = 0.0f; @@ -198,11 +203,12 @@ void TextDrawer::MeasureString(std::string_view str, float *w, float *h) { str = str.substr(0, MAX_TEXT_LENGTH); } - auto [iter, inserted] = sizeCache_.try_emplace(LookupKey(str)); + auto [iter, inserted] = sizeCache_.try_emplace(LookupKey(style, str)); TextMeasureEntry &entry = iter->second; if (inserted) { - float extW, extH; - MeasureStringInternal(str, &extW, &extH); + EnsureFont(style); + float extW = 0.0f, extH = 0.0f; + MeasureStringInternal(style, str, &extW, &extH); entry.width = extW; entry.height = extH; // Hm, use the old calculation? @@ -210,11 +216,11 @@ void TextDrawer::MeasureString(std::string_view str, float *w, float *h) { } entry.lastUsedFrame = frameCount_; - *w = entry.width * fontScaleX_ * dpiScale_; - *h = entry.height * fontScaleY_ * dpiScale_; + *w = entry.width * scaleX * dpiScale_; + *h = entry.height * scaleY * dpiScale_; } -void TextDrawer::MeasureStringRect(std::string_view str, float maxWidth, float *w, float *h, int align) { +void TextDrawer::MeasureStringRect(const FontStyle &style, float scaleX, float scaleY, std::string_view str, float maxWidth, float *w, float *h, int align) { const int wrap = align & (FLAG_WRAP_TEXT | FLAG_ELLIPSIZE_TEXT); // Clamp the size to something sane. @@ -223,10 +229,10 @@ void TextDrawer::MeasureStringRect(std::string_view str, float maxWidth, float * } float plainW, plainH; - MeasureString(str, &plainW, &plainH); + MeasureString(style, scaleX, scaleY, str, &plainW, &plainH); if (wrap && plainW > maxWidth) { - MeasureString(WrapString(str, maxWidth, wrap), w, h); + MeasureString(style, scaleX, scaleY, WrapString(style, scaleX, str, maxWidth, wrap), w, h); } else { *w = plainW; *h = plainH; @@ -256,7 +262,7 @@ void TextDrawer::DrawStringRect(DrawBuffer &target, std::string_view str, const } const int wrap = align & (FLAG_WRAP_TEXT | FLAG_ELLIPSIZE_TEXT); - DrawString(target, wrap ? std::string_view(WrapString(str, bounds.w, wrap)) : str, x, y, color, align); + DrawString(target, wrap ? std::string_view(WrapString(fontStyle_, fontScaleX_, str, bounds.w, wrap)) : str, x, y, color, align); } bool TextDrawer::DrawStringBitmapRect(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, const Bounds &bounds, int align, bool fullColor) { @@ -266,7 +272,7 @@ bool TextDrawer::DrawStringBitmapRect(std::vector &bitmapData, TextStri } const int wrap = align & (FLAG_WRAP_TEXT | FLAG_ELLIPSIZE_TEXT); - return DrawStringBitmap(bitmapData, entry, texFormat, wrap ? std::string_view(WrapString(str, bounds.w, wrap)) : str, align, fullColor); + return DrawStringBitmap(bitmapData, entry, texFormat, wrap ? std::string_view(WrapString(fontStyle_, fontScaleX_, str, bounds.w, wrap)) : str, align, fullColor); } void TextDrawer::AddDropShadow(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat srcFormat) const { diff --git a/Common/Render/Text/draw_text.h b/Common/Render/Text/draw_text.h index f3c250bf66..d5033aee12 100644 --- a/Common/Render/Text/draw_text.h +++ b/Common/Render/Text/draw_text.h @@ -71,11 +71,20 @@ public: virtual ~TextDrawer() = default; virtual bool IsReady() const { return true; } - virtual void SetOrCreateFont(const FontStyle &style) = 0; - + // Sets the font and scale that drawing uses. + void SetOrCreateFont(const FontStyle &style); void SetFontScale(float xscale, float yscale); - void MeasureString(std::string_view str, float *w, float *h); - void MeasureStringRect(std::string_view str, float maxWidth, float *w, float *h, int align = ALIGN_TOPLEFT); + + // Measuring doesn't depend on the current font and scale. + void MeasureString(const FontStyle &style, float scaleX, float scaleY, std::string_view str, float *w, float *h); + void MeasureStringRect(const FontStyle &style, float scaleX, float scaleY, std::string_view str, float maxWidth, float *w, float *h, int align = ALIGN_TOPLEFT); + // In the current font and scale. + void MeasureString(std::string_view str, float *w, float *h) { + MeasureString(fontStyle_, fontScaleX_, fontScaleY_, str, w, h); + } + void MeasureStringRect(std::string_view str, float maxWidth, float *w, float *h, int align = ALIGN_TOPLEFT) { + MeasureStringRect(fontStyle_, fontScaleX_, fontScaleY_, str, maxWidth, w, h, align); + } void DrawString(DrawBuffer &target, std::string_view str, float x, float y, uint32_t color, int align = ALIGN_TOPLEFT); void DrawStringRect(DrawBuffer &target, std::string_view str, const Bounds &bounds, uint32_t color, int align); @@ -102,8 +111,10 @@ public: protected: TextDrawer(Draw::DrawContext *draw); - // Implementations of this must handle multi-line strings. - virtual void MeasureStringInternal(std::string_view str, float *w, float *h) = 0; + // Creates the font for style, if it isn't already. + virtual void EnsureFont(const FontStyle &style) = 0; + // Implementations of this must handle multi-line strings. EnsureFont has been called for style. + virtual void MeasureStringInternal(const FontStyle &style, std::string_view str, float *w, float *h) = 0; void ClearCache(); @@ -111,7 +122,7 @@ protected: virtual void ClearFonts() = 0; // Wraps and/or ellipsizes, caching the result. The reference stays valid until the next OncePerFrame. - const std::string &WrapString(std::string_view str, float maxWidth, int flags); + const std::string &WrapString(const FontStyle &style, float scale, std::string_view str, float maxWidth, int flags); // v has the 8-bit alpha. static u16 AlphaToPremul4444(u32 v) { @@ -153,7 +164,7 @@ protected: static constexpr size_t MAX_TEXT_LENGTH = 16384; // Fills in and returns lookupKey_, which reuses its string buffer, so a cache hit doesn't allocate. - const TextCacheKey &LookupKey(std::string_view str, int flags = 0, float maxWidth = 0.0f, float scale = 0.0f); + const TextCacheKey &LookupKey(const FontStyle &style, std::string_view str, int flags = 0, float maxWidth = 0.0f, float scale = 0.0f); void AddDropShadow(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat srcFormat) const; TextCacheKey lookupKey_; @@ -165,13 +176,15 @@ protected: class TextDrawerWordWrapper : public WordWrapper { public: - TextDrawerWordWrapper(TextDrawer *drawer, std::string_view str, float maxW, int flags) - : WordWrapper(str, maxW, flags), drawer_(drawer) {} + TextDrawerWordWrapper(TextDrawer *drawer, const FontStyle &style, float scale, std::string_view str, float maxW, int flags) + : WordWrapper(str, maxW, flags), drawer_(drawer), style_(style), scale_(scale) {} protected: float MeasureWidth(std::string_view str) override; TextDrawer *drawer_; + FontStyle style_; + float scale_; }; // The backends can use this to query the filenames of the fonts. diff --git a/Common/Render/Text/draw_text_android.cpp b/Common/Render/Text/draw_text_android.cpp index 6623e0bd19..b95b81cccf 100644 --- a/Common/Render/Text/draw_text_android.cpp +++ b/Common/Render/Text/draw_text_android.cpp @@ -58,12 +58,10 @@ bool TextDrawerAndroid::IsReady() const { return cls_textRenderer != nullptr && method_measureText != nullptr && method_renderText != nullptr; } -void TextDrawerAndroid::SetOrCreateFont(const FontStyle &style) { +void TextDrawerAndroid::EnsureFont(const FontStyle &style) { // We will only use the default font but just for consistency let's still involve // the font name. - auto iter = fontMap_.find(style); - if (iter != fontMap_.end()) { - fontStyle_ = style; + if (fontMap_.find(style) != fontMap_.end()) { return; } @@ -98,11 +96,10 @@ void TextDrawerAndroid::SetOrCreateFont(const FontStyle &style) { // Just chose a factor that looks good, don't know what unit size is in anyway. fontMap_[style] = entry; - fontStyle_ = style; } -void TextDrawerAndroid::MeasureStringInternal(std::string_view str, float *w, float *h) { - auto iter = fontMap_.find(fontStyle_); +void TextDrawerAndroid::MeasureStringInternal(const FontStyle &style, std::string_view str, float *w, float *h) { + auto iter = fontMap_.find(style); if (iter == fontMap_.end()) { ERROR_LOG(Log::G3D, "Missing font"); *w = 1.0f; diff --git a/Common/Render/Text/draw_text_android.h b/Common/Render/Text/draw_text_android.h index 3262aa42ab..1156619929 100644 --- a/Common/Render/Text/draw_text_android.h +++ b/Common/Render/Text/draw_text_android.h @@ -20,11 +20,11 @@ public: ~TextDrawerAndroid(); bool IsReady() const override; - void SetOrCreateFont(const FontStyle &style) override; bool DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align, bool fullColor) override; protected: - void MeasureStringInternal(std::string_view str, float *w, float *h) override; + void EnsureFont(const FontStyle &style) override; + void MeasureStringInternal(const FontStyle &style, std::string_view str, float *w, float *h) override; bool SupportsColorEmoji() const override { return true; } void ClearFonts() override; diff --git a/Common/Render/Text/draw_text_cocoa.h b/Common/Render/Text/draw_text_cocoa.h index 2581946bb9..c61eec39ba 100644 --- a/Common/Render/Text/draw_text_cocoa.h +++ b/Common/Render/Text/draw_text_cocoa.h @@ -16,11 +16,11 @@ public: TextDrawerCocoa(Draw::DrawContext *draw); ~TextDrawerCocoa(); - void SetOrCreateFont(const FontStyle &style) override; bool DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align, bool fullColor) override; protected: - void MeasureStringInternal(std::string_view str, float *w, float *h) override; + void EnsureFont(const FontStyle &style) override; + void MeasureStringInternal(const FontStyle &style, std::string_view str, float *w, float *h) override; bool SupportsColorEmoji() const override { return true; } void ClearFonts() override; diff --git a/Common/Render/Text/draw_text_cocoa.mm b/Common/Render/Text/draw_text_cocoa.mm index 449ac404d1..3dfa3fff3e 100644 --- a/Common/Render/Text/draw_text_cocoa.mm +++ b/Common/Render/Text/draw_text_cocoa.mm @@ -137,21 +137,18 @@ TextDrawerCocoa::~TextDrawerCocoa() { } // TODO: Share with other backends. -void TextDrawerCocoa::SetOrCreateFont(const FontStyle &style) { +void TextDrawerCocoa::EnsureFont(const FontStyle &style) { if (style.sizePts <= 0) { return; } - auto iter = fontMap_.find(style); - if (iter != fontMap_.end()) { - fontStyle_ = style; + if (fontMap_.find(style) != fontMap_.end()) { return; } TextDrawerFontContext *font = new TextDrawerFontContext(style, dpiScale_); fontMap_[style] = std::unique_ptr(font); - fontStyle_ = style; } void TextDrawerCocoa::ClearFonts() { @@ -163,8 +160,8 @@ void TextDrawerCocoa::ClearFonts() { } // AI added some error checks here. -void TextDrawerCocoa::MeasureStringInternal(std::string_view str, float *w, float *h) { - auto iter = fontMap_.find(fontStyle_); +void TextDrawerCocoa::MeasureStringInternal(const FontStyle &style, std::string_view str, float *w, float *h) { + auto iter = fontMap_.find(style); NSDictionary *attributes = nil; if (iter != fontMap_.end()) { attributes = iter->second->attributes; diff --git a/Common/Render/Text/draw_text_sdl.cpp b/Common/Render/Text/draw_text_sdl.cpp index 0d6dd251a3..2cde9fae30 100644 --- a/Common/Render/Text/draw_text_sdl.cpp +++ b/Common/Render/Text/draw_text_sdl.cpp @@ -297,13 +297,7 @@ void TextDrawerSDL::PrepareFallbackFonts(std::string_view locale) { #endif } -uint32_t TextDrawerSDL::CheckMissingGlyph(std::string_view text) { - auto iter = fontMap_.find(fontStyle_); - if (iter == fontMap_.end()) { - return 0; - } - - TTF_Font *font = iter->second.first; +uint32_t TextDrawerSDL::CheckMissingGlyph(TTF_Font *font, std::string_view text) { if (!font) { return 0; } @@ -360,10 +354,8 @@ int TextDrawerSDL::FindFallbackFonts(uint32_t missingGlyph, int ptSize) { return -1; } -void TextDrawerSDL::SetOrCreateFont(const FontStyle &style) { - auto iter = fontMap_.find(style); - if (iter != fontMap_.end()) { - fontStyle_ = style; +void TextDrawerSDL::EnsureFont(const FontStyle &style) { + if (fontMap_.find(style) != fontMap_.end()) { return; } @@ -387,13 +379,12 @@ void TextDrawerSDL::SetOrCreateFont(const FontStyle &style) { } fontMap_[style] = std::make_pair(font, fileData); - fontStyle_ = style; } -void TextDrawerSDL::MeasureStringInternal(std::string_view str, float *w, float *h) { +void TextDrawerSDL::MeasureStringInternal(const FontStyle &style, std::string_view str, float *w, float *h) { *w = 1.0f; *h = 1.0f; - auto iter = fontMap_.find(fontStyle_); + auto iter = fontMap_.find(style); if (iter == fontMap_.end()) { return; } @@ -408,10 +399,10 @@ void TextDrawerSDL::MeasureStringInternal(std::string_view str, float *w, float str = reorderedStr; } - uint32_t missingGlyph = CheckMissingGlyph(str); + uint32_t missingGlyph = CheckMissingGlyph(font, str); if (missingGlyph) { - const int ptSize = static_cast(fontStyle_.sizePts / dpiScale_ * 1.25f); + const int ptSize = static_cast(style.sizePts / dpiScale_ * 1.25f); const int fallbackFont = FindFallbackFonts(missingGlyph, ptSize); if (fallbackFont >= 0 && fallbackFont < (int)fallbackFonts_.size()) { font = fallbackFonts_[fallbackFont]; @@ -478,7 +469,7 @@ bool TextDrawerSDL::DrawStringBitmap(std::vector &bitmapData, TextStrin processedStr.push_back(' '); } - uint32_t missingGlyph = CheckMissingGlyph(processedStr); + uint32_t missingGlyph = CheckMissingGlyph(font, processedStr); if (missingGlyph) { const int ptSize = static_cast(TTF_GetFontSize(font)); diff --git a/Common/Render/Text/draw_text_sdl.h b/Common/Render/Text/draw_text_sdl.h index 34c657dd82..9744002689 100644 --- a/Common/Render/Text/draw_text_sdl.h +++ b/Common/Render/Text/draw_text_sdl.h @@ -19,18 +19,18 @@ public: TextDrawerSDL(Draw::DrawContext *draw); ~TextDrawerSDL(); - void SetOrCreateFont(const FontStyle &style) override; bool DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align, bool fullColor) override; protected: - void MeasureStringInternal(std::string_view str, float *w, float *h) override; + void EnsureFont(const FontStyle &style) override; + void MeasureStringInternal(const FontStyle &style, std::string_view str, float *w, float *h) override; bool SupportsColorEmoji() const override { return false; } void ClearFonts() override; private: void PrepareFallbackFonts(std::string_view locale); - uint32_t CheckMissingGlyph(std::string_view text); + uint32_t CheckMissingGlyph(TTF_Font *font, std::string_view text); int FindFallbackFonts(uint32_t missingGlyph, int ptSize); std::map> fontMap_; diff --git a/Common/Render/Text/draw_text_uwp.cpp b/Common/Render/Text/draw_text_uwp.cpp index 890f3c1d51..9242fdbb7f 100644 --- a/Common/Render/Text/draw_text_uwp.cpp +++ b/Common/Render/Text/draw_text_uwp.cpp @@ -261,21 +261,17 @@ TextDrawerUWP::~TextDrawerUWP() { delete ctx_; } -void TextDrawerUWP::SetOrCreateFont(const FontStyle &style) { +void TextDrawerUWP::EnsureFont(const FontStyle &style) { _dbg_assert_(style.sizePts > 0); - auto iter = fontMap_.find(style); - if (iter != fontMap_.end()) { - fontStyle_ = style; + if (fontMap_.find(style) != fontMap_.end()) { return; } - fontMap_[style] = std::make_unique(style, dpiScale_, m_dwriteFactory, m_fontCollection); - fontStyle_ = style; } -void TextDrawerUWP::MeasureStringInternal(std::string_view str, float *w, float *h) { +void TextDrawerUWP::MeasureStringInternal(const FontStyle &style, std::string_view str, float *w, float *h) { IDWriteTextFormat *format = nullptr; - auto iter = fontMap_.find(fontStyle_); + auto iter = fontMap_.find(style); if (iter != fontMap_.end()) { format = iter->second->textFmt; } diff --git a/Common/Render/Text/draw_text_uwp.h b/Common/Render/Text/draw_text_uwp.h index 459008f136..95b7300078 100644 --- a/Common/Render/Text/draw_text_uwp.h +++ b/Common/Render/Text/draw_text_uwp.h @@ -20,11 +20,11 @@ public: TextDrawerUWP(Draw::DrawContext *draw); ~TextDrawerUWP(); - void SetOrCreateFont(const FontStyle &style) override; bool DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align, bool fullColor) override; protected: - void MeasureStringInternal(std::string_view str, float *w, float *h) override; + void EnsureFont(const FontStyle &style) override; + void MeasureStringInternal(const FontStyle &style, std::string_view str, float *w, float *h) override; bool SupportsColorEmoji() const override { return true; } void ClearFonts() override; diff --git a/Common/Render/Text/draw_text_win.cpp b/Common/Render/Text/draw_text_win.cpp index 8e2e9b8e07..9caf064660 100644 --- a/Common/Render/Text/draw_text_win.cpp +++ b/Common/Render/Text/draw_text_win.cpp @@ -117,19 +117,15 @@ TextDrawerWin32::~TextDrawerWin32() { delete ctx_; } -void TextDrawerWin32::SetOrCreateFont(const FontStyle &style) { - auto iter = fontMap_.find(style); - if (iter != fontMap_.end()) { - fontStyle_ = style; +void TextDrawerWin32::EnsureFont(const FontStyle &style) { + if (fontMap_.find(style) != fontMap_.end()) { return; } - fontMap_[style] = std::make_unique(style, dpiScale_); - fontStyle_ = style; } -void TextDrawerWin32::MeasureStringInternal(std::string_view str, float *w, float *h) { - auto iter = fontMap_.find(fontStyle_); +void TextDrawerWin32::MeasureStringInternal(const FontStyle &style, std::string_view str, float *w, float *h) { + auto iter = fontMap_.find(style); if (iter != fontMap_.end()) { SelectObject(ctx_->hDC, iter->second->hFont); } else { diff --git a/Common/Render/Text/draw_text_win.h b/Common/Render/Text/draw_text_win.h index 1e40528395..26ebc6c1ce 100644 --- a/Common/Render/Text/draw_text_win.h +++ b/Common/Render/Text/draw_text_win.h @@ -16,11 +16,11 @@ public: TextDrawerWin32(Draw::DrawContext *draw); ~TextDrawerWin32(); - void SetOrCreateFont(const FontStyle &style) override; bool DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align, bool fullColor) override; protected: - void MeasureStringInternal(std::string_view str, float *w, float *h) override; + void EnsureFont(const FontStyle &style) override; + void MeasureStringInternal(const FontStyle &style, std::string_view str, float *w, float *h) override; bool SupportsColorEmoji() const override { return false; } void ClearFonts() override; diff --git a/Common/UI/Context.cpp b/Common/UI/Context.cpp index 1e4832ebc7..26928a5a03 100644 --- a/Common/UI/Context.cpp +++ b/Common/UI/Context.cpp @@ -284,19 +284,7 @@ void UIContext::MeasureText(const FontStyle &style, float scaleX, float scaleY, Draw()->SetFontScale(scaleX * sizeFactor, scaleY * sizeFactor); Draw()->MeasureText(AtlasFontFromStyle(style), str, x, y); } else { - // Switching fonts costs two lookups, and most measuring is done in the current style. - const bool switchFont = !(style == *fontStyle_); - if (switchFont) { - textDrawer_->SetOrCreateFont(style); - } - textDrawer_->SetFontScale(scaleX, scaleY); - textDrawer_->MeasureString(str, x, y); - if (switchFont) { - // Check for bad "old font". - if (fontStyle_ && fontStyle_->sizePts > 0) { - textDrawer_->SetOrCreateFont(*fontStyle_); - } - } + textDrawer_->MeasureString(style, scaleX, scaleY, str, x, y); } } @@ -307,16 +295,7 @@ void UIContext::MeasureTextRect(const FontStyle &style, float scaleX, float scal Draw()->SetFontScale(scaleX * sizeFactor, scaleY * sizeFactor); Draw()->MeasureTextRect(AtlasFontFromStyle(style), str, maxWidth, x, y, align); } else { - // Switching fonts costs two lookups, and most measuring is done in the current style. - const bool switchFont = !(style == *fontStyle_); - if (switchFont) { - textDrawer_->SetOrCreateFont(style); - } - textDrawer_->SetFontScale(scaleX, scaleY); - textDrawer_->MeasureStringRect(str, maxWidth, x, y, align); - if (switchFont) { - textDrawer_->SetOrCreateFont(*fontStyle_); - } + textDrawer_->MeasureStringRect(style, scaleX, scaleY, str, maxWidth, x, y, align); } } diff --git a/unittest/UnitTest.cpp b/unittest/UnitTest.cpp index e3f2ec2c25..35fa45989a 100644 --- a/unittest/UnitTest.cpp +++ b/unittest/UnitTest.cpp @@ -3744,17 +3744,19 @@ bool TestFatShortNames() { class FakeTextDrawer : public TextDrawer { public: FakeTextDrawer() : TextDrawer(nullptr) {} - void SetOrCreateFont(const FontStyle &style) override { fontStyle_ = style; } bool DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align, bool fullColor) override { return false; } void TestDropShadow(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat srcFormat) { AddDropShadow(bitmapData, entry, srcFormat); } + const FontStyle &CurrentStyle() const { return fontStyle_; } + int measureCalls = 0; protected: - void MeasureStringInternal(std::string_view str, float *w, float *h) override { + void EnsureFont(const FontStyle &style) override {} + void MeasureStringInternal(const FontStyle &style, std::string_view str, float *w, float *h) override { measureCalls++; std::vector lines; SplitString(str, '\n', lines); @@ -3786,6 +3788,16 @@ bool TestTextDrawerCache() { drawer.MeasureString("hello", &w, &h); EXPECT_EQ_INT(drawer.measureCalls, 2); + // Measuring in another style and scale leaves the current ones alone. + const FontStyle small(FontFamily::SansSerif, 10, FontStyleFlags::Default); + drawer.MeasureString(small, 2.0f, 2.0f, "hello", &w, &h); + EXPECT_EQ_FLOAT(w, 100.0f); + EXPECT_EQ_INT(drawer.measureCalls, 3); + EXPECT_TRUE(drawer.CurrentStyle() == FontStyle(FontFamily::SansSerif, 30, FontStyleFlags::Default)); + drawer.MeasureString("hello", &w, &h); + EXPECT_EQ_FLOAT(w, 50.0f); + EXPECT_EQ_INT(drawer.measureCalls, 3); + const std::string_view para = "The quick brown fox jumps over the lazy dog, and then it does it again, and again, until the dog finally gets up and leaves."; drawer.MeasureStringRect(para, 200.0f, &w, &h, FLAG_WRAP_TEXT); // The trailing space of a wrapped line is measured too.