镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

DirectWrite text layout cannot resolve font families whose names contain spaces — 'Material Icons' renders tofu, same font renamed 'MaterialIcons' renders #16306

Open

Description

Problem Description

On the new architecture, a fontFamily whose name contains a space (e.g. 'Material Icons') fails to resolve even though the font is registered and enumerable in DirectWrite — glyphs render as tofu / fall back to Segoe UI. The identical TTF re-emitted with a space-less family name ('MaterialIcons', name table IDs 1/16 rewritten, glyph outlines untouched) resolves and renders. The space in the requested family name is the only variable.

What we verified empirically (production app):

  • Fonts registered via the packaged windows.sharedFonts manifest extension land in DirectWrite's per-user system font collection; WPF/DirectWrite enumeration shows the family present under its spaced name (Material Icons), correct cmap/name records.
  • fontFamily: 'Material Icons' → tofu.
  • Same file, family renamed to MaterialIcons (only the OpenType name table changed) → renders correctly.

Root cause — diagnosed, NOT traced to a specific line (filing for visibility; confidence level stated honestly). The resolution lives in the DirectWrite text-layout path — vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/textlayoutmanager/WindowsTextLayoutManager and/or the font-collection/loader it uses — i.e. how the requested family string reaches CreateTextFormat / FindFamilyName. Plain DirectWrite handles spaced family names fine (Segoe UI is the platform default), so we suspect RNW-side handling of the fontFamily string (tokenizing/trimming, or the custom collection lookup) rather than DirectWrite itself. We could not pin the failing lookup with confidence from a shallow source read, so we did not attempt a patch.

Steps To Reproduce

  1. Take a TTF whose family name contains a space — e.g. stock MaterialIcons.ttf from react-native-vector-icons (family name Material Icons).
  2. Register it so DirectWrite can see it (we use the windows.sharedFonts package-manifest extension; confirm via any DirectWrite/WPF font enumeration that the family is present).
  3. Render <Text style={{ fontFamily: 'Material Icons' }}>{''}</Text> (any mapped codepoint).
  4. Observe tofu / Segoe UI fallback.
  5. Control: rewrite the font's name table so the family is MaterialIcons (no other change), request fontFamily: 'MaterialIcons' → glyphs render.

Expected Results

Spaced family names resolve exactly as space-less ones do (and as they do on iOS/Android, and in DirectWrite generally).

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.8

Community Modules

react-native-vector-icons 10.3.0 / @expo/vector-icons 15.1.1 are where the spaced-name fonts come from, but the bug is core: it reproduces with any registered TTF whose family name contains a space, rendered through core <Text>.

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

Minimal probe is the two-step control above (steps 3 vs 5) — same file, same glyphs, only the family name differs.

Our production workaround: a dependency-free Node script re-emits the Windows copies of all affected fonts with space-less family names (rewriting name IDs 1/16 and recomputing table checksums), registered via windows.sharedFonts; a platform shim makes JS request the space-less names on Windows only.

Related issues: #15316 (umbrella: custom .ttf fontFamily support on new arch), #3463 (old-arch custom-font parity), #15750 (IProvideFontInfo font-loading unification). Related but distinct, filed separately: #16308 — registered stock icon TTFs render blank glyphs until table checksums/table directory are recomputed (loader validation vs. this issue's family-name lookup).

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 build a minimal repro repo or test candidate fixes in our app if useful.

Activity

  1. FaithfulAudio commented on Jul 26, 2026

    @FaithfulAudio
    ContributorAuthor

    Reporter here, with a correction and a fix.

    My diagnosis in this issue is wrong. Spaces are not the problem. I built a DirectWrite probe that
    replays RNW's exact call sequence and measured it. Sorry for the noise — here is what is actually
    happening, and a PR.

    Measured: spaces in family names are fine

    Standalone probe, Windows 11 10.0.26200, VS 2022 x64, DWRITE_FACTORY_TYPE_SHARED. It replays
    CreateTextFormat (with the same empty localeName RNW passes) → CreateTextLayout → Draw with a
    recording IDWriteTextRenderer that reports which face and which glyph index the run resolved to.

    Holding the collection and the font constant (stock MaterialIcons.ttf from
    react-native-vector-icons@10.3.0, in a collection that contains it) and changing only the
    requested string, drawing U+E7FD:

    requested fontFamily FindFamilyName draw result
    Material Icons exists face Material Icons, glyph 782
    material icons exists face Material Icons, glyph 782
    MATERIAL ICONS exists face Material Icons, glyph 782
    MaterialIcons not found Segoe UI, glyph 0
    " Material Icons" (leading space) not found Segoe UI, glyph 0
    "Material Icons " (trailing space) not found Segoe UI, glyph 0
    Material Icons (doubled interior space) not found Segoe UI, glyph 0

    The spaced name is the one that works. The space-less name fails for the mundane reason that
    MaterialIcons is not this font's family name — its name table says Material Icons. Lookup is
    case-insensitive. Interior spaces are irrelevant.

    So the control experiment in my issue body was confounded: the rename script rewrote name IDs 1/16 and
    re-emitted the file and the new file had to be re-registered. I attributed the change to the space.
    It wasn't the space.

    The actual root cause

    WindowsTextLayoutManager::GetTextLayout passes nullptr as the font collection
    (WindowsTextLayoutManager.cpp L119):

    nullptr, // Font collection (nullptr sets it to use the system font collection).

    nullptr means system-collection-only, so no app-bundled font family resolves at all — every
    codepoint falls back to Segoe UI glyph 0 (.notdef). Measured for all eight stock
    react-native-vector-icons fonts: with a collection containing the font they draw real glyph indices
    (40, 1, 1, 13, 4, 4, 4, 2); with nullptr every single one gives Segoe UI glyph 0.

    There is no font-family parsing bug in RNW to find, either. I also checked that: the string goes
    std::string → Utf8ToUtf16 → .c_str() → DirectWrite, verbatim, with the sole transformation being
    "if empty, use Segoe UI". No tokenizer, no comma splitting, no trimming. And at
    c69cf55f67f9b03f467502dac1007ac2d9ebe209 the repo contains no font loader or font-collection code at
    all — FindFamilyName, CreateFontFileReference, IDWriteFontCollection, IDWriteFontSetBuilder,
    AddFontResource and sharedFonts have zero hits, and the git tree (3956 blobs) has no source file
    with "Font" in its name.

    Fix

    Fix in #16339: add DWriteAppFontCollection() to DWriteHelpers — system font set merged with every
    *.ttf/*.otf under the app's Assets\ and Assets\Fonts\, built once, failing closed to nullptr
    so behaviour is unchanged for apps that bundle no fonts — and pass it at that CreateTextFormat call
    instead of nullptr. The same PR (#16339) fixes #16308, which has the same root cause.

    What I could not verify, stated plainly

    • The probe machine has none of these fonts installed, so the nullptr rows there fail because the
      font is genuinely absent. I did not reproduce the windows.sharedFonts-registered state from my
      original report.
      What I have shown is that neither spaces nor the family-name string shape is the
      mechanism, and that nullptr cannot resolve an uninstalled font. My best explanation of the original
      observation is that the re-emit forced a re-registration that changed collection membership, and the
      rename rode along — but that is inference, not measurement.
    • The PR is not compiled and not run. Lint, clang-format, typecheck and build gates are unrun. The
      probe is separate standalone code.

    One genuinely new, small finding

    DirectWrite does not normalise leading, trailing or duplicated whitespace in a family name, and RNW
    does not trim the string before passing it on. fontFamily: 'Ionicons ' silently renders .notdef
    with no diagnostic. Not fixed in the PR — whether RN should trim is arguable and I did not want to
    smuggle it in. Flagging it because it is an easy trap.

    Suggested retitle

    "App-bundled font families never resolve: WindowsTextLayoutManager passes nullptr as the DirectWrite
    font collection" — the current title is misleading and will send the next person hunting for a string
    bug that does not exist.

    Note on the workaround in my original report

    Our build-time script that renames families space-less and recomputes checksums does work, but the
    reason it works is not the reason I gave. Anyone copying it should read the PR first; with the
    collection fix you should not need it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Needs: Triage 🔍New issue that needs to be reviewed by the issue management team (label applied by bot)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions