diff options
| author | Andrey Lihatskiy <118752495+marchcat@users.noreply.github.com> | 2026-07-24 12:26:16 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-07-24 12:26:16 +0300 |
| commit | e32d763ccbe1b02c7d74a5b63712ec946a24b42e (patch) | |
| tree | f09286325dfcda29510115ff6d8c2fdb71e7ff5c | |
| parent | a6a2f98070cd55fcaadafc237bd0ffba9d466655 (diff) | |
| parent | cd7295e09b354a99c29f2bb80da7c0a77ccb0457 (diff) | |
Merge pull request #5993 from nibbbl/cjk-lazy-font-fallback
Fix missing CJK/Indic text on Linux with lazy font fallback
| -rw-r--r-- | indra/llrender/llfontfreetype.cpp | 47 | ||||
| -rw-r--r-- | indra/llrender/llfontfreetype.h | 12 | ||||
| -rw-r--r-- | indra/llwindow/llwindow.cpp | 14 | ||||
| -rw-r--r-- | indra/llwindow/llwindow.h | 10 | ||||
| -rw-r--r-- | indra/llwindow/llwindowmacosx.cpp | 6 | ||||
| -rw-r--r-- | indra/llwindow/llwindowmacosx.h | 1 | ||||
| -rw-r--r-- | indra/llwindow/llwindowsdl.cpp | 120 | ||||
| -rw-r--r-- | indra/llwindow/llwindowsdl.h | 1 | ||||
| -rw-r--r-- | indra/llwindow/llwindowwin32.cpp | 6 | ||||
| -rw-r--r-- | indra/llwindow/llwindowwin32.h | 1 |
10 files changed, 135 insertions, 83 deletions
diff --git a/indra/llrender/llfontfreetype.cpp b/indra/llrender/llfontfreetype.cpp index 075c496ec5..cc4d6ad5ef 100644 --- a/indra/llrender/llfontfreetype.cpp +++ b/indra/llrender/llfontfreetype.cpp @@ -52,6 +52,7 @@ //#include "imdebug.h" #include "llfontbitmapcache.h" #include "llgl.h" +#include "llwindow.h" #define ENABLE_OT_SVG_SUPPORT @@ -188,7 +189,7 @@ bool LLFontFreetype::loadFace(const std::string& filename, F32 point_size, F32 v return false; openArgs.flags = FT_OPEN_MEMORY; - int error = FT_Open_Face( gFTLibrary, &openArgs, 0, &mFTFace ); + int error = FT_Open_Face( gFTLibrary, &openArgs, face_n, &mFTFace ); if (error) return false; @@ -197,6 +198,9 @@ bool LLFontFreetype::loadFace(const std::string& filename, F32 point_size, F32 v mHinting = hinting; mFontFlags = flags; mWeight = weight; + mFaceIndex = face_n; + mVertDPI = vert_dpi; + mHorzDPI = horz_dpi; bool variable_font = false; if (weight >= 0) @@ -314,7 +318,7 @@ S32 LLFontFreetype::getNumFaces(const std::string& filename) } void LLFontFreetype::addFallbackFont(const LLPointer<LLFontFreetype>& fallback_font, - const char_functor_t& functor) + const char_functor_t& functor) const { mFallbackFonts.emplace_back(fallback_font, functor); } @@ -419,6 +423,18 @@ bool LLFontFreetype::hasGlyph(llwchar wch) const return(mCharGlyphInfoMap.find(wch) != mCharGlyphInfoMap.end()); } +bool LLFontFreetype::hasFallbackPath(const std::string& path) const +{ + for (const fallback_font_t& pair : mFallbackFonts) + { + if (pair.first->getName() == path) + { + return true; + } + } + return false; +} + LLFontGlyphInfo* LLFontFreetype::addGlyph(llwchar wch, EFontGlyphType glyph_type) const { if (!mFTFace) @@ -501,6 +517,31 @@ LLFontGlyphInfo* LLFontFreetype::addGlyph(llwchar wch, EFontGlyphType glyph_type glyph_type); } } + + // Nothing above covers this char: ask the OS for a font that does, + // load it and attach it. + if (mAttemptedFallbackChars.insert(wch).second) + { + LLFontFallbackMatch match = LLWindow::findFallbackFontForChar(wch); + if (!match.mPath.empty() && !hasFallbackPath(match.mPath)) + { + LLPointer<LLFontFreetype> fallback = new LLFontFreetype; + if (fallback->loadFace(match.mPath, mPointSize, mVertDPI, mHorzDPI, + /*weight*/ -1, /*is_fallback*/ true, + match.mFaceIndex, mHinting, mFontFlags)) + { + glyph_index = FT_Get_Char_Index(fallback->mFTFace, wch); + if (glyph_index) + { + LL_DEBUGS("Font") << "Lazy OS fallback for U+" << std::hex << (U32)wch << std::dec + << ": " << match.mPath << " (face " << match.mFaceIndex << ")" << LL_ENDL; + addFallbackFont(fallback, nullptr); + return addGlyphFromFont(fallback, wch, glyph_index, glyph_type); + } + // Matched font doesn't actually cover wch: discard it. + } + } + } } auto range_it = mCharGlyphInfoMap.equal_range(wch); @@ -728,7 +769,7 @@ void LLFontFreetype::renderGlyph(EFontGlyphType bitmap_type, U32 glyph_index, ll void LLFontFreetype::reset(F32 vert_dpi, F32 horz_dpi) { resetBitmapCache(); - loadFace(mName, mPointSize, vert_dpi ,horz_dpi, mWeight, mIsFallback, 0, mHinting, mFontFlags); + loadFace(mName, mPointSize, vert_dpi ,horz_dpi, mWeight, mIsFallback, mFaceIndex, mHinting, mFontFlags); if (!mIsFallback) { // This is the head of the list - need to rebuild ourself and all fallbacks. diff --git a/indra/llrender/llfontfreetype.h b/indra/llrender/llfontfreetype.h index d2164e8fa2..12a4052cae 100644 --- a/indra/llrender/llfontfreetype.h +++ b/indra/llrender/llfontfreetype.h @@ -34,6 +34,7 @@ #include "llfontbitmapcache.h" #include <unordered_map> +#include <unordered_set> // Hack. FT_Face is just a typedef for a pointer to a struct, // but there's no simple forward declarations file for FreeType, @@ -108,7 +109,7 @@ public: S32 getNumFaces(const std::string& filename); typedef std::function<bool(llwchar)> char_functor_t; - void addFallbackFont(const LLPointer<LLFontFreetype>& fallback_font, const char_functor_t& functor = nullptr); + void addFallbackFont(const LLPointer<LLFontFreetype>& fallback_font, const char_functor_t& functor = nullptr) const; // Global font metrics - in units of pixels F32 getLineHeight() const; @@ -167,6 +168,7 @@ private: bool setSubImageBGRA(U32 x, U32 y, U32 bitmap_num, U16 width, U16 height, const U8* data, U32 stride) const; bool setVariationAxis(const std::string& axis_tag, F32 value); bool hasGlyph(llwchar wch) const; // Has a glyph for this character + bool hasFallbackPath(const std::string& path) const; // Is a fallback font with this file path already attached? LLFontGlyphInfo* addGlyph(llwchar wch, EFontGlyphType glyph_type) const; // Add a new character to the font if necessary LLFontGlyphInfo* addGlyphFromFont( const LLFontFreetype *fontp, @@ -191,9 +193,15 @@ private: EFontHinting mHinting; S32 mFontFlags; S32 mWeight = -1; + S32 mFaceIndex = 0; // Face index within the (possibly collection) font file + F32 mVertDPI = 0.f; // Kept so lazily-discovered fallback faces can be + F32 mHorzDPI = 0.f; // opened at this font's size (see addGlyph) typedef std::pair<LLPointer<LLFontFreetype>, char_functor_t> fallback_font_t; typedef std::vector<fallback_font_t> fallback_font_vector_t; - fallback_font_vector_t mFallbackFonts; // A list of fallback fonts to look for glyphs in (for Unicode chars) + // mutable: fallback fonts are also discovered lazily in addGlyph (const) + mutable fallback_font_vector_t mFallbackFonts; // A list of fallback fonts to look for glyphs in (for Unicode chars) + // Codepoints we've already asked the OS about, so we only query once each + mutable std::unordered_set<llwchar> mAttemptedFallbackChars; // *NOTE: the same glyph can be present with multiple representations (but the pointer is always unique) typedef std::unordered_multimap<llwchar, LLFontGlyphInfo*> char_glyph_info_map_t; diff --git a/indra/llwindow/llwindow.cpp b/indra/llwindow/llwindow.cpp index 2313aeda50..b6177abfc7 100644 --- a/indra/llwindow/llwindow.cpp +++ b/indra/llwindow/llwindow.cpp @@ -268,6 +268,20 @@ std::vector<std::string> LLWindow::getDynamicFallbackFontList() } // static +LLFontFallbackMatch LLWindow::findFallbackFontForChar(llwchar wch) +{ +#if LL_SDL_WINDOW && !LL_MESA_HEADLESS + return LLWindowSDL::findFallbackFontForChar(wch); +#elif LL_WINDOWS + return LLWindowWin32::findFallbackFontForChar(wch); +#elif LL_DARWIN + return LLWindowMacOSX::findFallbackFontForChar(wch); +#else + return LLFontFallbackMatch(); +#endif +} + +// static std::vector<std::string> LLWindow::getDisplaysResolutionList() { #if LL_SDL_WINDOW && !LL_MESA_HEADLESS diff --git a/indra/llwindow/llwindow.h b/indra/llwindow/llwindow.h index e9dfa3aba4..327323b018 100644 --- a/indra/llwindow/llwindow.h +++ b/indra/llwindow/llwindow.h @@ -38,6 +38,13 @@ class LLSplashScreen; class LLPreeditor; class LLWindowCallbacks; +// Result of an OS font-fallback query; empty mPath means no font was found. +struct LLFontFallbackMatch +{ + std::string mPath; + S32 mFaceIndex = 0; +}; + // Refer to llwindow_test in test/common/llwindow for usage example class LLWindow : public LLInstanceTracker<LLWindow> @@ -183,6 +190,9 @@ public: static std::vector<std::string> getDynamicFallbackFontList(); + // Ask the OS for a font file covering the given codepoint (lazy fallback). + static LLFontFallbackMatch findFallbackFontForChar(llwchar wch); + // Provide native key event data virtual LLSD getNativeKeyData() { return LLSD::emptyMap(); } diff --git a/indra/llwindow/llwindowmacosx.cpp b/indra/llwindow/llwindowmacosx.cpp index f8920318d3..b5dc841c45 100644 --- a/indra/llwindow/llwindowmacosx.cpp +++ b/indra/llwindow/llwindowmacosx.cpp @@ -2633,6 +2633,12 @@ std::vector<std::string> LLWindowMacOSX::getDynamicFallbackFontList() return std::vector<std::string>(); } +LLFontFallbackMatch LLWindowMacOSX::findFallbackFontForChar(llwchar wch) +{ + // Not implemented on macOS; would use CoreText (CTFontCreateForString). + return LLFontFallbackMatch(); +} + // static MASK LLWindowMacOSX::modifiersToMask(S16 modifiers) { diff --git a/indra/llwindow/llwindowmacosx.h b/indra/llwindow/llwindowmacosx.h index dc8b7504c9..9534eadf8e 100644 --- a/indra/llwindow/llwindowmacosx.h +++ b/indra/llwindow/llwindowmacosx.h @@ -117,6 +117,7 @@ public: static std::vector<std::string> getDisplaysResolutionList(); static std::vector<std::string> getDynamicFallbackFontList(); + static LLFontFallbackMatch findFallbackFontForChar(llwchar wch); // Provide native key event data LLSD getNativeKeyData() override; diff --git a/indra/llwindow/llwindowsdl.cpp b/indra/llwindow/llwindowsdl.cpp index 2d8a74c782..e3ae9493a6 100644 --- a/indra/llwindow/llwindowsdl.cpp +++ b/indra/llwindow/llwindowsdl.cpp @@ -1879,100 +1879,64 @@ void LLWindowSDL::bringToFront() //static std::vector<std::string> LLWindowSDL::getDynamicFallbackFontList() { - std::vector<std::string> rtns; -#if LL_LINUX - // Use libfontconfig to find us a nice ordered list of fallback fonts - // specific to this system. - std::string final_fallback("/usr/share/fonts/truetype/kochi/kochi-gothic.ttf"); - const int max_font_count_cutoff = 40; // fonts are expensive in the current system, don't enumerate an arbitrary number of them - // Our 'ideal' font properties which define the sorting results. - // slant=0 means Roman, index=0 means the first face in a font file - // (the one we actually use), weight=80 means medium weight, - // spacing=0 means proportional spacing. - std::string sort_order("slant=0:index=0:weight=80:spacing=0"); - // elide_unicode_coverage removes fonts from the list whose unicode - // range is covered by fonts earlier in the list. This usually - // removes ~90% of the fonts as redundant (which is great because - // the font list can be huge), but might unnecessarily reduce the - // renderable range if for some reason our FreeType actually fails - // to use some of the fonts we want it to. - const bool elide_unicode_coverage = true; - - FcFontSet *fs = nullptr; - FcPattern *sortpat = nullptr; - - LL_INFOS() << "Getting system font list from FontConfig..." << LL_ENDL; + // Lazy-loaded fonts, seeded with fonts.xml + return std::vector<std::string>(); +} - // If the user has a system-wide language preference, then favor - // fonts from that language group. This doesn't affect the types - // of languages that can be displayed, but ensures that their - // preferred language is rendered from a single consistent font where - // possible. - FL_Locale *locale = nullptr; - FL_Success success = FL_FindLocale(&locale, FL_MESSAGES); - if (success != 0) +LLFontFallbackMatch LLWindowSDL::findFallbackFontForChar(llwchar wch) +{ + LLFontFallbackMatch result; +#if LL_LINUX + // FcInit() is idempotent, but this runs per missing codepoint, so only + // attempt initialization once. + static bool fc_ready = FcInit(); + if (!fc_ready) { - if (success >= 2 && locale->lang) // confident! - { - LL_INFOS("AppInit") << "Language " << locale->lang << LL_ENDL; - LL_INFOS("AppInit") << "Location " << locale->country << LL_ENDL; - LL_INFOS("AppInit") << "Variant " << locale->variant << LL_ENDL; - - LL_INFOS() << "Preferring fonts of language: " - << locale->lang - << LL_ENDL; - sort_order = "lang=" + std::string(locale->lang) + ":" - + sort_order; - } + LL_WARNS_ONCE() << "FontConfig failed to initialize." << LL_ENDL; + return result; } - FL_FreeLocale(&locale); - if (!FcInit()) + // Ask FontConfig for the best font covering this codepoint. + FcCharSet* charset = FcCharSetCreate(); + FcPattern* pat = FcPatternCreate(); + if (!charset || !pat) { - LL_WARNS() << "FontConfig failed to initialize." << LL_ENDL; - rtns.push_back(final_fallback); - return rtns; + if (charset) FcCharSetDestroy(charset); + if (pat) FcPatternDestroy(pat); + return result; } - sortpat = FcNameParse((FcChar8*) sort_order.c_str()); - if (sortpat) - { - // Sort the list of system fonts from most-to-least-desirable. - FcResult result; - fs = FcFontSort(nullptr, sortpat, elide_unicode_coverage, nullptr, &result); - FcPatternDestroy(sortpat); - } + FcCharSetAddChar(charset, (FcChar32)wch); + + FcPatternAddCharSet(pat, FC_CHARSET, charset); + FcPatternAddBool(pat, FC_SCALABLE, FcTrue); + + FcConfigSubstitute(nullptr, pat, FcMatchPattern); + FcDefaultSubstitute(pat); - int found_font_count = 0; - if (fs) + FcResult fc_result; + FcPattern* match = FcFontMatch(nullptr, pat, &fc_result); + if (match) { - // Get the full pathnames to the fonts, where available, - // which is what we really want. - found_font_count = fs->nfont; - for (int i=0; i<fs->nfont; ++i) + FcChar8* filename = nullptr; + if (FcResultMatch == FcPatternGetString(match, FC_FILE, 0, &filename) && filename) { - FcChar8 *filename; - if (FcResultMatch == FcPatternGetString(fs->fonts[i], FC_FILE, 0, &filename) && filename) + result.mPath = (const char*)filename; + + // .ttc/.otc collections carry several faces; get the right one. + int index = 0; + if (FcResultMatch == FcPatternGetInteger(match, FC_INDEX, 0, &index)) { - rtns.push_back(std::string((const char*)filename)); - if (rtns.size() >= max_font_count_cutoff) - break; // hit limit + result.mFaceIndex = index; } } - FcFontSetDestroy (fs); + FcPatternDestroy(match); } - LL_DEBUGS() << "Using font list: " << LL_ENDL; - for (auto it = rtns.begin(); it != rtns.end(); ++it) - { - LL_DEBUGS() << " file: " << *it << LL_ENDL; - } - - LL_INFOS() << "Using " << rtns.size() << "/" << found_font_count << " system fonts." << LL_ENDL; - - rtns.push_back(final_fallback); + FcPatternDestroy(pat); + FcCharSetDestroy(charset); #endif - return rtns; + return result; } void LLWindowSDL::setLanguageTextInput(const LLCoordGL& position) diff --git a/indra/llwindow/llwindowsdl.h b/indra/llwindow/llwindowsdl.h index 4d4a7a5f65..7156ad66b7 100644 --- a/indra/llwindow/llwindowsdl.h +++ b/indra/llwindow/llwindowsdl.h @@ -157,6 +157,7 @@ public: void setTitle(const std::string title) override; static std::vector<std::string> getDynamicFallbackFontList(); + static LLFontFallbackMatch findFallbackFontForChar(llwchar wch); void *createSharedContext() override; void makeContextCurrent(void *context) override; diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp index 07caeff11d..6ac17f8779 100644 --- a/indra/llwindow/llwindowwin32.cpp +++ b/indra/llwindow/llwindowwin32.cpp @@ -5023,6 +5023,12 @@ std::vector<std::string> LLWindowWin32::getDynamicFallbackFontList() // Fonts previously in getFontListSans() have moved to fonts.xml. return std::vector<std::string>(); } + +LLFontFallbackMatch LLWindowWin32::findFallbackFontForChar(llwchar wch) +{ + // Not implemented on Windows; would use DirectWrite (IDWriteFontFallback::MapCharacters). + return LLFontFallbackMatch(); +} #endif // LL_WINDOWS inline LLWindowWin32::LLWindowWin32Thread::LLWindowWin32Thread() diff --git a/indra/llwindow/llwindowwin32.h b/indra/llwindow/llwindowwin32.h index 6e7176c5f1..c0a1816cf3 100644 --- a/indra/llwindow/llwindowwin32.h +++ b/indra/llwindow/llwindowwin32.h @@ -127,6 +127,7 @@ public: static PROC WINAPI getProcAddress(const char* func); static std::vector<std::string> getDisplaysResolutionList(); static std::vector<std::string> getDynamicFallbackFontList(); + static LLFontFallbackMatch findFallbackFontForChar(llwchar wch); static void setDPIAwareness(); void* getDirectInput8() override; |
