Registered icon-font TTFs render blank glyphs until table checksums/table directory are recomputed — silent failure in the DWrite font path #16308
Description
Problem Description
Several stock icon-font TTFs render blank glyphs through RNW's new-arch text stack, even though they register into DirectWrite successfully and enumerate with correct family/cmap/name records. Re-emitting the same fonts with byte-identical glyph data through a minimal OpenType writer that only recomputes table checksums (incl. head.checkSumAdjustment) and rebuilds the table directory makes the very same fonts render. No outlines, metrics, or name records changed in the control case — only checksums/table-directory layout.
Fonts affected in our app (all stock files shipped in react-native-vector-icons 10.3.0): Ionicons, FontAwesome, Feather, Octicons, EvilIcons, AntDesign. These same files render fine in other Windows apps and on iOS/Android — icon-font-generator output frequently carries technically-stale checksums that everything else tolerates.
Root cause — diagnosed from behavior, NOT traced to a specific line (confidence stated honestly). Some layer of the RNW/DWrite font path applies stricter TTF validation than the rest of Windows: the fonts register and enumerate, but glyph rendering silently produces blanks until the checksums/table directory are made strictly valid. We have not identified whether the rejection happens in an RNW-side loader/validation step or in how DirectWrite validates faces the way RNW loads/uses them. The failure is completely silent — no warning, no error, just blank glyphs — which made this extremely expensive to diagnose.
Suggested fix direction: either align validation tolerance with what DirectWrite/Windows accepts elsewhere (these fonts work everywhere else), or keep the strictness but surface a diagnostic when a face fails validation, and document the requirement.
Related but distinct from #16306 (spaced family names fail to resolve — a lookup problem). This issue is about faces that resolve/enumerate but whose glyphs never render — a loader/validation problem. We hit both in the same integration; fixing one does not fix the other.
Steps To Reproduce
- Take stock
Ionicons.ttffromreact-native-vector-icons10.3.0 (unmodified). - Register it so DirectWrite sees it (we use the
windows.sharedFontspackage-manifest extension) and confirm via DirectWrite/WPF enumeration that the family is present with correct records. - Render
<Text style={{ fontFamily: 'Ionicons' }}>{glyph}</Text>with a mapped codepoint. - Observe: blank output (not tofu boxes — blank), no diagnostics.
- Control: re-emit the same TTF recomputing all table checksums and rebuilding the table directory (glyph data untouched); replace the registered file.
- Glyphs now render.
Expected Results
Fonts that install and render everywhere else on Windows (and on iOS/Android) render in RNW — or, at minimum, a visible diagnostic is emitted when the font path refuses a face.
CLI version
18.0.0
Environment
System:
OS: Windows 11 10.0.26200 (ARM64 device; app builds and runs ARM64)
CPU: (6) x64 Apple Silicon (Windows-on-ARM)
Memory: 6.49 GB / 15.99 GB
Binaries:
Node: 22.15.0
Yarn: 4.5.1
npm: 10.9.2
SDKs:
Windows SDK versions: 10.0.19041.0, 10.0.22621.0, 10.0.26100.0
IDEs:
Visual Studio: 18.6.11822.322 (Community 2026), 17.14.37314.3 (Community 2022)
npmPackages (yarn 4 workspaces — `cli info` reports Not Found in-workspace):
react-native: 0.83.2
react-native-windows: 0.83.2 (New Architecture / Fabric composition)
Microsoft.WindowsAppSDK: 1.8Community Modules
react-native-vector-icons 10.3.0 / @expo/vector-icons 15.1.1 supply the affected TTFs, but the rendering path is core <Text> + DirectWrite; any TTF with stale table checksums should reproduce.
Target React Native Architecture
New Architecture (WinAppSDK) Only
Target Platform Version
10.0.22621
Visual Studio Version
Visual Studio 2026
Build Configuration
Debug
Snack, code example, screenshot, or link to a repository
The decisive experiment is steps 3–6 above: identical glyph data, only checksums/table directory recomputed, blank → rendering.
Our production workaround: a dependency-free build-time Node script re-emits the Windows copies of all bundled icon fonts with recomputed checksums and a rebuilt table directory (the same pass that renames spaced families for #16306), registered via windows.sharedFonts.
Related: #16306 (spaced family-name resolution), #15750 (IProvideFontInfo font-loading unification), #15316 (custom .ttf support on new arch).
Context: found during a Windows hardening pass of a production RNW app (Facilitron FIT — RNW 0.83.2 new-arch, Windows 11 ARM64, WinAppSDK 1.8, 250% display scale). Sibling PRs from the same investigation: #16302, #16303, #16304. Happy to provide before/after font binaries or a minimal repro repo, and to test candidate fixes in our app.
Activity
FaithfulAudio commentedon Jul 26, 2026
Reporter here, with a correction and a fix.
My diagnosis in this issue is wrong. Checksums are not the problem, and nothing in RNW or DirectWrite
rejects these files. I built a DirectWrite probe and measured it. Here is what is actually happening.
Measured: DirectWrite accepts every stock TTF as-is
Standalone probe, Windows 11 10.0.26200, VS 2022 x64. For each byte-unmodified font from
react-native-vector-icons@10.3.0it reportsIDWriteFontFile::Analyze, the HRESULT fromIDWriteFontSetBuilder1::AddFontFile, the real family name from the name table, and then replays RNW'scall sequence (
CreateTextFormatwith the same emptylocaleNamethe real code passes →CreateTextLayout→Draw) with a recordingIDWriteTextRendererthat reports the resolved face andglyph index. The drawn codepoint is taken from each face's own
IDWriteFontFace1::GetUnicodeRanges, soit is guaranteed to be one that font claims to map.
AnalyzesupportedAddFontFilenullptr(today)hr=0acceptedMaterial Iconshr=0acceptedMaterial Design Iconshr=0acceptedIoniconshr=0acceptedFontAwesomehr=0acceptedFeatherhr=0acceptedOcticonshr=0acceptedEvilIconshr=0acceptedanticonEvery one of these is a font I listed in this issue as "affected". Stale checksums and all, DirectWrite
loads them,
AddFontFileaccepts them withhr=0, and they draw real glyph indices. There is novalidation rejection to loosen — in RNW or below it.
There is also no RNW-side validation layer to look for. At
c69cf55f67f9b03f467502dac1007ac2d9ebe209,CreateFontFileReference,IDWriteFontCollection,IDWriteFontSetBuilder,IDWriteFontFace(outside a comment),AddFontResourceandsharedFontshavezero hits in the repo, and the git tree (3956 blobs) contains no source file with "Font" in its
name. RNW never opens a font file, never builds a collection, and never inspects a face.
The actual root cause, and why "blank" fooled me
WindowsTextLayoutManager::GetTextLayoutpassesnullptras the font collection(
WindowsTextLayoutManager.cppL119):System-collection-only, so no app-bundled font family resolves at all, and every icon codepoint
falls back to Segoe UI glyph 0 (
.notdef) — the right-hand column above.I made a point in this issue of "blank, not tofu boxes — blank" and treated that as evidence of a second,
distinct bug. It isn't. Both symptoms are the same
Segoe UIglyph 0. Whether.notdefpaints a hollowbox or nothing at all is a property of the fallback face, not a signal about where the failure is. That
false distinction is the reason I filed this separately from #16306, and it cost me time. There is one
bug, not two.
My control experiment was confounded the same way as #16306's: the re-emit pass recomputed checksums,
rebuilt the table directory (which also reorders, re-pads and re-aligns tables), and produced a new
file that had to be re-registered. I attributed the fix to checksums. Nothing supports that.
Fix
Fix in #16339: add
DWriteAppFontCollection()toDWriteHelpers— system font set merged with every*.ttf/*.otfunder the app'sAssets\andAssets\Fonts\, built once, failing closed tonullptrso behaviour is unchanged for apps that bundle no fonts — and pass it at that
CreateTextFormatcallinstead of
nullptr. The same PR (#16339) fixes #16306.What I could not verify, stated plainly
nullptrrows fail because the font isgenuinely absent. I did not reproduce the
windows.sharedFonts-registered state from my originalreport. What is established is that DirectWrite accepts these exact bytes and renders real glyphs
from them, so "RNW/DWrite validates stale checksums strictly" is false regardless.
probe is separate standalone code.
chase it, I still have both binaries; but with the collection fix in place I do not think it matters.
Two incidental traps worth recording
AntDesign.ttf's real family name isanticon, notAntDesign. RequestingAntDesigngives.notdefeven with the font in the collection. Anyone debugging icon fonts on Windows should read thename table rather than trusting the filename or the JS-side family constant.
not trim the string.
fontFamily: 'Ionicons 'silently renders.notdef.The one thing I originally asked for that still stands
"Or, at minimum, surface a diagnostic when the font path refuses a face." The refusal turned out not to
exist, but the silence did cost days: unresolved family → Segoe UI
.notdef→ blank pixels, with nowarning anywhere. A dev-build warning when a requested
fontFamilyis not present in the collectionwould have made both of these issues five minutes of work instead of a week. I have not authored that —
it needs a
FindFamilyNamecall plus whichever logging facility is right for this layer, and I could notverify either compiles without a build. Happy to write it if a maintainer points me at the logging helper.
Suggested retitle
"App-bundled icon fonts render .notdef: WindowsTextLayoutManager passes nullptr as the DirectWrite font
collection" — or just close as a duplicate of #16306 once the PR lands, since they are the same bug. The
current title will send the next person on a font-binary goose chase.