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

Open Beginner friendly
#1,736 1 comment 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
88/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
csharp
Domain
desktop

Research direction

Start in src/Wpf.Ui/Extensions/SymbolExtensions.cs and inspect both SymbolRegular and SymbolFilled GetString overloads, then trace their use through SymbolIcon. Verify the change with BMP symbols such as Money24 or Savings24 and supplementary symbols such as CoinStack24, confirming both produce the intended glyph strings without the workaround.

Written by the indexing model from the issue text.

Description

bug
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:

<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()):

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):

- 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):

<ui:FontIcon FontFamily="pack://application:,,,/Wpf.Ui;component/Resources/Fonts/#FluentSystemIcons-Regular"
             Glyph="&#xF0665;" FontSize="28" />
Dominant language
C#
Stars
9.6k
Forks
1k
PR merge metrics
No merged PRs in 30d

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from lepoco/wpfui

All issues in lepoco/wpfui

Similar issues

More C# issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.