From b9a83b97a937f6404ff6932a3ac9e2cc8f797456 Mon Sep 17 00:00:00 2001 From: nibbbl <63519528+nibbbl@users.noreply.github.com> Date: Sun, 12 Jul 2026 16:29:00 -0700 Subject: Fix missing CJK/Indic text on Linux with lazy font fallback Resolve fallback fonts per-glyph from FontConfig on demand instead of a truncated static list, so CJK/Thai/Indic scripts render. --- indra/llrender/llfontfreetype.cpp | 47 ++++++++++++++++++++++++++++++++++++--- indra/llrender/llfontfreetype.h | 12 ++++++++-- 2 files changed, 54 insertions(+), 5 deletions(-) (limited to 'indra/llrender') diff --git a/indra/llrender/llfontfreetype.cpp b/indra/llrender/llfontfreetype.cpp index 19326d301e..5c1b82b230 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& 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 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 +#include // 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 char_functor_t; - void addFallbackFont(const LLPointer& fallback_font, const char_functor_t& functor = nullptr); + void addFallbackFont(const LLPointer& 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, char_functor_t> fallback_font_t; typedef std::vector 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 mAttemptedFallbackChars; // *NOTE: the same glyph can be present with multiple representations (but the pointer is always unique) typedef std::unordered_multimap char_glyph_info_map_t; -- cgit v1.3