SymbolIcon renders the wrong glyph for all SymbolRegular/SymbolFilled members above U+FFFF (supplementary plane)
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 88/100
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
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: bytes50 F5 00 00→[U+F550, U+0000]→
TrimEnd('\0')→[U+F550]. ✅ Works (by accident). - Supplementary,
CoinStack24=0xF0665: bytes65 06 0F 00→[U+0665, U+000F].
The correct encoding ofU+F0665is the surrogate pair[U+DB81, U+DE65].
TrimEnd('\0')does not stripU+000F. ❌ The format-12cmapentry 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="󰙥" FontSize="28" />
- Dominant language
- C#
- Stars
- 9.6k
- Forks
- 1k
- PR merge metrics
- No merged PRs in 30d
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from lepoco/wpfui
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
-
bug
Difficulty 1/5 Under an hour Newbie friendliness 78/100
-
bug
Difficulty 4/5 3-5 days Newbie friendliness 52/100
-
bug
Difficulty 3/5 1-2 days Newbie friendliness 55/100
-
:star: top bug :star: top issue bug
Difficulty 3/5 1-2 days Newbie friendliness 72/100
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 86/100
-
:watch: Not Triaged 11.0 fundamentals/subsvc
Difficulty 2/5 1-3 hours Newbie friendliness 92/100
dotnet/AspNetCore.Docs#37699 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
SubtitleEdit/subtitleedit#15108 · 1 comment ·
-
area/docs-content Bug pulumi/docs
Difficulty 1/5 1-3 hours Newbie friendliness 94/100
-
agentic-workflows untriaged
Difficulty 2/5 1-3 hours Newbie friendliness 76/100