You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Android] RiveFonts: loadFont({name}) fails for script fonts on OEM fonts.xml; systemFallback() is not script-aware (tofu for Thai/Hebrew/Arabic/CJK) #369
UPDATE (root cause found): the primary bug is that the fallback strategy is garbage-collected. See "Root cause" below; the original report about name lookup is kept as a secondary issue.
Root cause: stylePicker is assigned into a WeakReference and immediately GC'd
rive-android stores the fallback strategy weakly (FontFallbackStrategy.kt):
// Use a WeakReference so these can be automatically cleaned up by the JVM.privatevar stylePickerRef:WeakReference<FontFallbackStrategy>?=null
…but HybridRiveFontConfig.applyFallbackFonts() (both 0.4.10 and 0.4.19) assigns an anonymous object without retaining any strong reference to it:
The strategy becomes unreachable as soon as applyFallbackFonts returns, the next GC collects it, pickFont() starts returning emptyList(), and the engine falls back to the default system font — tofu for any non-Latin script. This explains why setFallbackFonts appears to succeed (JS promise resolves, fonts are loaded into byte arrays) while rendering still shows tofu, and why results look flaky (depends on GC timing).
Verified fix (we run this as a local patch)
Keep a strong reference alongside the weak assignment:
With this one-line retention, setFallbackFonts({ default: [loadedBytesFonts..., systemFallback()] }) works as documented on @rive-app/react-native 0.4.10 (rive-android 11.4.0): Thai/Hebrew/Arabic render correctly. Happy to send this as a PR.
App-side note for others hitting this
Because pickFont results are cached natively per weight, apply the new fonts before any text run is (re)shaped for the new locale — otherwise the tofu shaping result is cached until the strategy instance changes again.
Secondary issue (original report): font sourcing
1. loadFont({ name }) silently fails for script fonts on many devices — loadFontByName matches the name attribute of <family> entries in fonts.xml, but script families (Thai, Hebrew, Arabic, CJK, …) are typically declared without a name attribute (matched by lang). A lang-based lookup, or documented support for absolute font paths, would make sourcing reliable. Workaround that works: loadFont({ uri: 'file:///system/fonts/NotoSansThai-Regular.ttf' }) (routed to loadFontFromURL, which handles file://).
2. systemFallback() is not script-aware — HybridDefaultFallbackFont resolves to FontHelper.getFallbackFontBytes(FontOpts(weight)), i.e. the default family (Roboto), which cannot cover non-Latin scripts. Per-script resolution (like iOS does natively) would make systemFallback() a real safety net.
Environment
@rive-app/react-native 0.4.10 (rive-android 11.4.0) and 0.4.19 — same code in HybridRiveFontConfig.applyFallbackFonts
Updated the issue body with the actual root cause: the anonymous FontFallbackStrategy assigned in applyFallbackFonts() is only held by rive-android's WeakReference and gets garbage-collected right away, so pickFont() returns emptyList() and text falls back to the default font. Verified fix (strong reference retention) included in the body — happy to open a PR.
Still reproduces on 0.4.20 / rive-android 11.9.1: HybridRiveFontConfig.applyFallbackFonts() continues to assign an anonymous FontFallbackStrategy that is only retained by rive-android's WeakReference (FontFallbackStrategy.stylePickerRef is unchanged in 11.9.1), so the picker can be GC'd at any time and fallback silently degrades to tofu. The loadFont({ name }) OEM fonts.xml limitation also remains. We are working around it at the app level by installing a strongly-referenced FontFallbackStrategy from application code (serving pre-read font bytes from memory, which also avoids fonts.xml/disk I/O on the shaping thread).
Sorry for the very slow reply, and thanks for the thorough root-cause analysis. We reproduced it on both Android runtimes: rive-android keeps the fallback picker only in a WeakReference (still true in 11.12.1), so a GC shortly after setFallbackFonts drops it and non-Latin text stops rendering. Your strong-reference fix resolves it; it's in #405 (with credit to you) and will ship in the next 0.5.x release. The loadFont({ name }) lookup and the script-awareness of systemFallback() are fair points too; we'll track those separately.
Activity
edgartoomikw commentedon Aug 19, 2026
Updated the issue body with the actual root cause: the anonymous FontFallbackStrategy assigned in applyFallbackFonts() is only held by rive-android's WeakReference and gets garbage-collected right away, so pickFont() returns emptyList() and text falls back to the default font. Verified fix (strong reference retention) included in the body — happy to open a PR.
edgartoomikw commentedon Aug 20, 2026
Still reproduces on 0.4.20 / rive-android 11.9.1:
HybridRiveFontConfig.applyFallbackFonts()continues to assign an anonymousFontFallbackStrategythat is only retained by rive-android'sWeakReference(FontFallbackStrategy.stylePickerRefis unchanged in 11.9.1), so the picker can be GC'd at any time and fallback silently degrades to tofu. TheloadFont({ name })OEM fonts.xml limitation also remains. We are working around it at the app level by installing a strongly-referencedFontFallbackStrategyfrom application code (serving pre-read font bytes from memory, which also avoids fonts.xml/disk I/O on the shaping thread).mfazekas commentedon Oct 3, 2026
Sorry for the very slow reply, and thanks for the thorough root-cause analysis. We reproduced it on both Android runtimes: rive-android keeps the fallback picker only in a
WeakReference(still true in 11.12.1), so a GC shortly aftersetFallbackFontsdrops it and non-Latin text stops rendering. Your strong-reference fix resolves it; it's in #405 (with credit to you) and will ship in the next 0.5.x release. TheloadFont({ name })lookup and the script-awareness ofsystemFallback()are fair points too; we'll track those separately.fix(android): keep a strong reference to the fallback font picker
test: add a reproducer page for the fallback font picker GC (#369)
fix(android): keep a strong reference to the fallback font picker
test: add a reproducer page for the fallback font picker GC (#369)