From 43576b75cac8d9b645a6f8cee252896a7063704c Mon Sep 17 00:00:00 2001 From: fanlusky <1730976608@qq.com> Date: Tue, 8 Sep 2026 16:02:56 +0800 Subject: [PATCH 1/2] fix(ui): make the icon font resolver recover from DirectWrite failures ResolveIconGlyph() guarded its cache with a mutex, but the work under that lock reached SharedFactory(), whose lazy DWriteCreateFactory() call was unsynchronized -- and UiFontFamily()/ApplyUiFontFallback() call the same helper without that mutex from render paths. Two window threads could both see a null factory, both create one into the same ComPtr, leak one and race on the raw pointer. Give SharedFactory() a lock of its own. IconFonts() memoized its result in a function-local static initialized exactly once. If SharedFactory() returned null or GetSystemFontCollection() failed on that first call, the empty list was cached for the process lifetime and every icon degraded to its text label permanently, with no retry -- while SharedFactory() itself retried on each call, making the failure mode asymmetric and hard to diagnose. Keep the list only once the probe finds a font, and skip the ResolveIconGlyph cache while it is empty so the retry is actually reachable. --- ui/src/Fonts.cpp | 47 +++++++++++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/ui/src/Fonts.cpp b/ui/src/Fonts.cpp index dcf5160c..cb75d873 100644 --- a/ui/src/Fonts.cpp +++ b/ui/src/Fonts.cpp @@ -31,7 +31,11 @@ bool FontFamilyExists(IDWriteFactory *factory, const wchar_t *name) IDWriteFactory *SharedFactory() { + // Called from several window threads, so the lazy creation needs a lock of + // its own; the factory is never reset, so the returned pointer stays valid. + static std::mutex mutex; static ComPtr factory; + std::lock_guard lock(mutex); if (!factory) { DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), @@ -81,23 +85,27 @@ struct IconFontEntry ComPtr font; }; -// Installed icon fonts, in preference order. Built once per process, so a font -// installed while the process runs is only picked up after a restart. +// Installed icon fonts, in preference order. Kept once the probe finds one, so +// a font installed while the process runs is only picked up after a restart. An +// empty result is not kept: a transient DirectWrite failure on the first call +// would otherwise degrade every icon to its text label for the whole process. +// Callers must hold the ResolveIconGlyph mutex, its only caller. const std::vector &IconFonts() { - static const std::vector entries = []() { - std::vector list; - IDWriteFactory *factory = SharedFactory(); - if (ComPtr fluent = FindFont(factory, kFluentIconsFamily)) - { - list.push_back({kFluentIconsFamily, false, std::move(fluent)}); - } - if (ComPtr mdl2 = FindFont(factory, kMdl2IconsFamily)) - { - list.push_back({kMdl2IconsFamily, true, std::move(mdl2)}); - } - return list; - }(); + static std::vector entries; + if (!entries.empty()) + { + return entries; + } + IDWriteFactory *factory = SharedFactory(); + if (ComPtr fluent = FindFont(factory, kFluentIconsFamily)) + { + entries.push_back({kFluentIconsFamily, false, std::move(fluent)}); + } + if (ComPtr mdl2 = FindFont(factory, kMdl2IconsFamily)) + { + entries.push_back({kMdl2IconsFamily, true, std::move(mdl2)}); + } return entries; } @@ -139,7 +147,14 @@ IconGlyph ResolveIconGlyph(wchar_t fluentCodepoint, wchar_t mdl2Codepoint) } IconGlyph resolved; - for (const IconFontEntry &entry : IconFonts()) + const std::vector &fonts = IconFonts(); + if (fonts.empty()) + { + // No icon font found yet. Report the text fallback but do not cache it, + // so a later call can still pick one up once DirectWrite recovers. + return resolved; + } + for (const IconFontEntry &entry : fonts) { const wchar_t codepoint = (entry.isMdl2 && mdl2Codepoint != 0) ? mdl2Codepoint : fluentCodepoint; if (FontHasCodepoint(entry.font.Get(), codepoint)) From 4b3da57134625d3c472d597cb903e373a6dc4d65 Mon Sep 17 00:00:00 2001 From: fanlusky <1730976608@qq.com> Date: Tue, 8 Sep 2026 16:06:38 +0800 Subject: [PATCH 2/2] fix(emoji-panel): use Chinese nav titles to match the tab labels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sub-page nav titles were English ("Sticker", "GIF", "Kaomoji", "Symbols") while the Clipboard page was already Chinese, and the text labels the tabs fall back to when no icon font carries the glyph are all Chinese. On a Windows 10 machine that actually hits that fallback, the user taps a tab labelled 贴纸 / 颜文字 / 符号 and lands on a page headed "Sticker" / "Kaomoji" / "Symbols". Translate the titles to match the tab labels and drop the per-page font and size branch, which only existed because Clipboard was the one Chinese title. Rename kClipboardTitleFontSize to kNavTitleFontSize now that every page uses it, and move it next to the other nav metrics. "GIF" stays as is: it is an acronym and the tab label already reads GIF. --- server/src/emoji-panel/EmojiPanel.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/server/src/emoji-panel/EmojiPanel.cpp b/server/src/emoji-panel/EmojiPanel.cpp index e746f1cc..cfa6ec39 100644 --- a/server/src/emoji-panel/EmojiPanel.cpp +++ b/server/src/emoji-panel/EmojiPanel.cpp @@ -26,6 +26,7 @@ constexpr float kPanelScale = 2.0f / 3.0f; constexpr float kHeaderHeight = 58.0f; constexpr float kNavTop = 66.0f; constexpr float kNavHeight = 58.0f; +constexpr float kNavTitleFontSize = 24.0f; constexpr float kSearchTop = 142.0f; constexpr float kSearchHeight = 52.0f; constexpr float kContentTop = 218.0f; @@ -48,7 +49,6 @@ constexpr UINT_PTR kClipboardPollTimerId = 44; constexpr UINT kClipboardPollMs = 400; constexpr float kClipboardRowHeight = 80.0f; constexpr float kClipboardRowGap = 8.0f; -constexpr float kClipboardTitleFontSize = 24.0f; constexpr float kClipboardHintFontSize = 24.0f; constexpr float kClipboardButtonFontSize = 22.0f; constexpr float kClipboardItemFontSize = 22.0f; @@ -2235,21 +2235,21 @@ void EmojiPanel::Render(DeviceResources &resources) } else { + // Keep these in step with the tab labels in emoji_panel_icons.cpp, + // which the tabs fall back to when no icon font has the glyph. const wchar_t *title = L""; if (page_ == Page::Sticker) - title = L"Sticker"; + title = L"贴纸"; else if (page_ == Page::Gif) title = L"GIF"; else if (page_ == Page::Kaomoji) - title = L"Kaomoji"; + title = L"颜文字"; else if (page_ == Page::Symbols) - title = L"Symbols"; + title = L"符号"; else if (page_ == Page::Clipboard) title = L"剪贴板"; - DrawText(resources, title, {bounds_.x + 70.0f, bounds_.y + kNavTop, 280.0f, kNavHeight}, - page_ == Page::Clipboard ? kClipboardTitleFontSize : 18.0f, text, - page_ == Page::Clipboard ? CjkUiFont() : L"Segoe UI", DWRITE_TEXT_ALIGNMENT_LEADING, - DWRITE_FONT_WEIGHT_SEMI_BOLD); + DrawText(resources, title, {bounds_.x + 70.0f, bounds_.y + kNavTop, 280.0f, kNavHeight}, kNavTitleFontSize, + text, CjkUiFont(), DWRITE_TEXT_ALIGNMENT_LEADING, DWRITE_FONT_WEIGHT_SEMI_BOLD); } } else