1+ /*************************************************************************************************
2+ Font Provider Unit Tests
3+ Tests for automatic emoji fallback functionality
4+ *************************************************************************************************/
5+ using EPPlus . Fonts . OpenType . TextShaping ;
6+ using Microsoft . VisualStudio . TestTools . UnitTesting ;
7+ using System . Linq ;
8+
9+ namespace EPPlus . Fonts . OpenType . Tests . FallbackFonts
10+ {
11+ [ TestClass ]
12+ public class FontProviderTests : FontTestBase
13+ {
14+ public override TestContext ? TestContext { get ; set ; }
15+
16+ private OpenTypeFont _robotoFont ;
17+
18+ [ TestInitialize ]
19+ public void TestSetup ( )
20+ {
21+ _robotoFont = OpenTypeFonts . GetFontData ( FontFolders , "Roboto" , FontSubFamily . Regular ) ;
22+ }
23+
24+ [ TestMethod ]
25+ public void DefaultFontProvider_EmojiGlyph_ShouldUseFallbackFont ( )
26+ {
27+ // Arrange
28+ var shaper = new TextShaper ( _robotoFont ) ;
29+
30+ // Act
31+ var shaped = shaper . Shape ( "😀" ) ;
32+ var usedFonts = shaper . GetUsedFonts ( ) . ToList ( ) ;
33+
34+ // Assert
35+ Assert . AreEqual ( 1 , shaped . Glyphs . Length , "Should have 1 glyph" ) ;
36+ Assert . AreNotEqual ( ( ushort ) 0 , shaped . Glyphs [ 0 ] . GlyphId , "Emoji should not be .notdef" ) ;
37+ Assert . AreEqual ( ( byte ) 0 , shaped . Glyphs [ 0 ] . FontId , "Emoji is the only font used (FontId=0)" ) ;
38+
39+ // Verify it's NOT the primary font
40+ Assert . AreEqual ( 1 , usedFonts . Count , "Should only use one font (emoji fallback)" ) ;
41+ Assert . AreNotEqual ( _robotoFont , usedFonts [ 0 ] , "Should be emoji font, not Roboto" ) ;
42+ }
43+
44+ [ TestMethod ]
45+ public void DefaultFontProvider_LatinText_ShouldUsePrimaryFont ( )
46+ {
47+ // Arrange
48+ var shaper = new TextShaper ( _robotoFont ) ;
49+
50+ // Act
51+ var shaped = shaper . Shape ( "Hello World" ) ;
52+
53+ // Assert
54+ foreach ( var glyph in shaped . Glyphs )
55+ {
56+ Assert . AreEqual ( ( byte ) 0 , glyph . FontId , "All glyphs should be from primary font" ) ;
57+ }
58+ }
59+
60+ [ TestMethod ]
61+ public void DefaultFontProvider_MixedTextAndEmoji_ShouldUseMultipleFonts ( )
62+ {
63+ // Arrange
64+ var shaper = new TextShaper ( _robotoFont ) ;
65+
66+ // Act
67+ var shaped = shaper . Shape ( "Hello 😀 World" ) ;
68+ var usedFonts = shaper . GetUsedFonts ( ) . ToList ( ) ;
69+
70+ // Assert
71+ Assert . AreEqual ( 2 , usedFonts . Count , "Should use 2 fonts (primary + emoji fallback)" ) ;
72+ Assert . AreEqual ( _robotoFont , usedFonts [ 0 ] , "First font should be primary" ) ;
73+ Assert . AreNotEqual ( _robotoFont , usedFonts [ 1 ] , "Second font should be emoji fallback" ) ;
74+ }
75+
76+ [ TestMethod ]
77+ public void TextShaper_SurrogatePair_ShouldMapToSingleGlyph ( )
78+ {
79+ // Arrange
80+ var shaper = new TextShaper ( _robotoFont ) ;
81+ string text = "😀" ; // U+1F600 = 2 chars in UTF-16
82+
83+ // Act
84+ var shaped = shaper . Shape ( text ) ;
85+
86+ // Assert
87+ Assert . AreEqual ( 2 , text . Length , "Emoji should be 2 chars in .NET" ) ;
88+ Assert . AreEqual ( 1 , shaped . Glyphs . Length , "Should map to 1 glyph" ) ;
89+ Assert . AreEqual ( ( byte ) 2 , shaped . Glyphs [ 0 ] . CharCount , "Glyph should span 2 chars" ) ;
90+ Assert . AreEqual ( ( ushort ) 0 , shaped . Glyphs [ 0 ] . ClusterIndex , "Should start at char 0" ) ;
91+ }
92+
93+ [ TestMethod ]
94+ public void TextShaper_MultipleEmoji_ShouldMapCorrectly ( )
95+ {
96+ // Arrange
97+ var shaper = new TextShaper ( _robotoFont ) ;
98+ string text = "😀😁😂" ; // 3 emoji = 6 chars in UTF-16
99+
100+ // Act
101+ var shaped = shaper . Shape ( text ) ;
102+
103+ // Assert
104+ Assert . AreEqual ( 6 , text . Length , "3 emoji = 6 chars" ) ;
105+ Assert . AreEqual ( 3 , shaped . Glyphs . Length , "Should map to 3 glyphs" ) ;
106+
107+ Assert . AreEqual ( ( ushort ) 0 , shaped . Glyphs [ 0 ] . ClusterIndex , "First emoji at char 0" ) ;
108+ Assert . AreEqual ( ( byte ) 2 , shaped . Glyphs [ 0 ] . CharCount , "First emoji spans 2 chars" ) ;
109+
110+ Assert . AreEqual ( ( ushort ) 2 , shaped . Glyphs [ 1 ] . ClusterIndex , "Second emoji at char 2" ) ;
111+ Assert . AreEqual ( ( byte ) 2 , shaped . Glyphs [ 1 ] . CharCount , "Second emoji spans 2 chars" ) ;
112+
113+ Assert . AreEqual ( ( ushort ) 4 , shaped . Glyphs [ 2 ] . ClusterIndex , "Third emoji at char 4" ) ;
114+ Assert . AreEqual ( ( byte ) 2 , shaped . Glyphs [ 2 ] . CharCount , "Third emoji spans 2 chars" ) ;
115+ }
116+ }
117+ }
0 commit comments