#1736·wpfui

SymbolIcon renders the wrong glyph for all SymbolRegular/SymbolFilled members above U+FFFF (supplementary plane)

Author: ccondottiCreated Jun 25, 2026Updated Sep 1, 2026
Labelsbug

Describe the bug

SymbolIcon (and any consumer of SymbolExtensions.GetString) renders the wrong glyph for every SymbolRegular/SymbolFilled member whose codepoint is in the Unicode supplementary plane (> U+FFFF), e.g. SymbolRegular.CoinStack24, the whole CoinMultiple* family, and many newer Fluent icons.

The conversion from the enum value to the glyph string does not compose a UTF-16 surrogate pair for scalar values above U+FFFF, so the font's cmap can never select the intended glyph. The glyph does exist in the bundled font — it simply can't be addressed.

This affects 2863 of 9235 SymbolRegular members (~31%), since Fluent System Icons assigns its newer icons to the Supplementary Private Use Area-A (U+F0000–U+FFFFD).

To Reproduce

Place any supplementary-plane symbol in XAML:

xml
<ui:SymbolIcon Symbol="CoinStack24" FontSize="28" />

Run the app and look at the rendered icon.

Expected behavior

The "coin stack" glyph is displayed (it is present in the bundled FluentSystemIcons-Regular.ttf, glyphIndex 2368).

Screenshots

CoinStack24 (= U+F0665) renders as the Arabic-Indic digit five ٥ plus a control character, instead of the coin-stack glyph. Any BMP symbol such as Savings24 (= U+F686) renders correctly in the same place.

OS version

Windows 11 Pro 10.0.26200

.NET version

.NET 10 (net10.0-windows) — also reproduces on net8.0-windows

WPF-UI NuGet version

WPF-UI 4.3.0

Additional context

Root cause

src/Wpf.Ui/Extensions/SymbolExtensions.cs (both the SymbolRegular and SymbolFilled overloads); used by SymbolIcon via SetCurrentValue(GlyphProperty, Symbol.GetString()):

csharp
public static string GetString(this SymbolRegular icon)
{
    return Encoding.Unicode.GetString(BitConverter.GetBytes((int)icon)).TrimEnd('\0');
}

This reinterprets the 4 raw bytes of the 32-bit enum value as two independent UTF-16LE code units instead of encoding a UTF-32 scalar as a surrogate pair:

  • BMP (≤ U+FFFF), Money24 = 0xF550: bytes 50 F5 00 00[U+F550, U+0000]TrimEnd('\0')[U+F550]. ✅ Works (by accident).
  • Supplementary, CoinStack24 = 0xF0665: bytes 65 06 0F 00[U+0665, U+000F]. The correct encoding of U+F0665 is the surrogate pair [U+DB81, U+DE65]. TrimEnd('\0') does not strip U+000F. ❌ The format-12 cmap entry is never reached.

Verification (GetString invoked via reflection on Wpf.Ui.dll 4.3.0)

Symbol enum value GetString result Correct value
Money24 0xF550 (BMP) [U+F550] (len 1) ✅ [U+F550]
CoinStack24 0xF0665 (supp.) [U+0665, U+000F] (len 2) ❌ [U+DB81, U+DE65]

The bundled font maps 2869 codepoints above U+FFFF via a cmap format-12 subtable, so the font is correct; the defect is purely in GetString.

Proposed fix

char.ConvertFromUtf32 handles both BMP and supplementary scalars correctly (and makes TrimEnd unnecessary):

diff
- return Encoding.Unicode.GetString(BitConverter.GetBytes((int)icon)).TrimEnd('\0');
+ return char.ConvertFromUtf32((int)icon);

(apply to the SymbolFilled overload too)

Workaround

Use a BMP symbol, or render the glyph explicitly via FontIcon + an XML character reference (parsed into a correct surrogate pair):

xml
<ui:FontIcon FontFamily="pack://application:,,,/Wpf.Ui;component/Resources/Fonts/#FluentSystemIcons-Regular"
             Glyph="&#xF0665;" FontSize="28" />