diff --git a/Terminal.Gui/Views/CharMap/CharMap.cs b/Terminal.Gui/Views/CharMap/CharMap.cs index 03439e3529..2fb7a2d874 100644 --- a/Terminal.Gui/Views/CharMap/CharMap.cs +++ b/Terminal.Gui/Views/CharMap/CharMap.cs @@ -822,6 +822,22 @@ void RenderGrapheme (string grapheme, int width, int scalar) break; + // Modifier symbol (e.g. emoji skin tone modifiers U+1F3FB-U+1F3FF) that reports + // width of 0 because it is designed to combine with a preceding base character. + case UnicodeCategory.ModifierSymbol: + if (width > 0) + { + AddStr (grapheme); + } + else + { + SetAttributeForRole (VisualRole.Highlight); + AddStr ("M"); + SetAttributeForRole (VisualRole.Normal); + } + + break; + case UnicodeCategory.OtherLetter: AddStr (grapheme); @@ -841,7 +857,11 @@ void RenderGrapheme (string grapheme, int width, int scalar) } else { - throw new InvalidOperationException ($"The Rune \"{grapheme}\" (U+{Rune.GetRuneAt (grapheme, 0).Value:x6}) has zero width and no special-case UnicodeCategory logic applies."); + // Fallback for any zero-width rune whose UnicodeCategory is not explicitly handled above. + // Display a highlighted placeholder to avoid crashes for future Unicode categories. + SetAttributeForRole (VisualRole.Highlight); + AddStr ("?"); + SetAttributeForRole (VisualRole.Normal); } break; diff --git a/Tests/UnitTestsParallelizable/Views/CharMapTests.cs b/Tests/UnitTestsParallelizable/Views/CharMapTests.cs index 990d276501..223f176186 100644 --- a/Tests/UnitTestsParallelizable/Views/CharMapTests.cs +++ b/Tests/UnitTestsParallelizable/Views/CharMapTests.cs @@ -1,12 +1,13 @@ using System.Diagnostics.CodeAnalysis; using System.Text; +using UnitTests; namespace ViewsTests; /// /// Tests for command handling and IValue implementation. /// -public class CharMapTests +public class CharMapTests : TestDriverBase { /// /// Verifies that is raised when changes. @@ -129,4 +130,43 @@ public void Value_Property_Change_Raises_ValueChangedUntyped () charMap.Dispose (); } + + /// + /// Verifies that drawing a with a ModifierSymbol code point (e.g. U+1F3FB, emoji skin tone + /// modifier) does not throw . + /// + [Fact] + [RequiresUnreferencedCode ("AOT")] + [RequiresDynamicCode ("AOT")] + public void Draw_ModifierSymbol_CodePoint_Does_Not_Throw () + { + // U+1F3FB is EMOJI MODIFIER FITZPATRICK TYPE-1-2, classified as UnicodeCategory.ModifierSymbol + // with a column width of 0 (designed to combine with a preceding base emoji). + const int modifierSymbolCodePoint = 0x1F3FB; + + IDriver driver = CreateTestDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + CharMap charMap = new () + { + Driver = driver, + X = 0, + Y = 0, + Width = 80, + Height = 25, + StartCodePoint = modifierSymbolCodePoint & ~0xF // row containing U+1F3FB + }; + + charMap.SelectedCodePoint = modifierSymbolCodePoint; + + charMap.BeginInit (); + charMap.EndInit (); + charMap.LayoutSubViews (); + + // This must not throw InvalidOperationException + Exception? exception = Record.Exception (() => charMap.Draw ()); + Assert.Null (exception); + + charMap.Dispose (); + } }