11using LVGLSharp . Interop ;
22using SixLabors . Fonts ;
3+ using SixLabors . Fonts . Unicode ;
4+ using System . Numerics ;
5+ using System . Reflection ;
6+ using System . Text ;
37
48namespace LVGLSharp ;
59
610public static unsafe class LvglManagedFontHelper
711{
812 private const string EnableManagedFontEnvironmentVariable = "LVGLSHARP_ENABLE_MANAGED_FONT" ;
913 private const string DisableManagedFontEnvironmentVariable = "LVGLSHARP_DISABLE_CUSTOM_FONT" ;
14+ private const string EmbeddedFallbackFontResourceName = "LVGLSharp.Core.Assets.Fonts.NotoSansSC-Regular.otf" ;
15+ private const string EmbeddedFallbackFontFileName = "NotoSansSC-Regular.otf" ;
1016
1117 /// <summary>
1218 /// Determines whether managed font rendering should be enabled for the current process.
@@ -62,8 +68,38 @@ public static LvglManagedFontState InitializeManagedFont(
6268 bool enabled )
6369 {
6470 var fallbackFont = LvglFontHelper . GetEffectiveTextFont ( root , lv_part_t . LV_PART_MAIN ) ;
65- var fontManager = TryApplyManagedFont ( root , fontPath , size , dpi , fallbackFont , out var font , out var style , enabled ) ;
66- return new LvglManagedFontState ( fallbackFont , font , fontManager , diagnostics , style ) ;
71+ if ( ! enabled )
72+ {
73+ return new LvglManagedFontState (
74+ fallbackFont ,
75+ null ,
76+ null ,
77+ LvglFontDiagnostics . FromPath ( null , "Source=LvglNativeFont; ManagedFontDisabled" , null ) ,
78+ null ) ;
79+ }
80+
81+ var fontManager = TryApplyManagedFont ( root , fontPath , size , dpi , fallbackFont , out var font , out var style , enabled : true ) ;
82+ if ( fontManager is not null )
83+ {
84+ return new LvglManagedFontState ( fallbackFont , font , fontManager , diagnostics , style ) ;
85+ }
86+
87+ if ( TryResolveEmbeddedFallbackFontPath ( out var embeddedFallbackFontPath ) )
88+ {
89+ var embeddedFallbackDiagnostics = CreateEmbeddedFallbackDiagnostics ( embeddedFallbackFontPath , diagnostics . DiagnosticSummary ) ;
90+ var embeddedFallbackFontManager = TryApplyManagedFont ( root , embeddedFallbackFontPath , size , dpi , fallbackFont , out font , out style , enabled : true ) ;
91+ if ( embeddedFallbackFontManager is not null )
92+ {
93+ return new LvglManagedFontState ( fallbackFont , font , embeddedFallbackFontManager , embeddedFallbackDiagnostics , style ) ;
94+ }
95+ }
96+
97+ return new LvglManagedFontState (
98+ fallbackFont ,
99+ null ,
100+ null ,
101+ LvglFontDiagnostics . FromPath ( null , $ "{ diagnostics . DisplaySummary } ; Source=LvglNativeFont; EmbeddedFallback=<none>", diagnostics . GlyphDiagnosticSummary ) ,
102+ null ) ;
67103 }
68104
69105 /// <summary>
@@ -79,8 +115,33 @@ public static LvglManagedFontState InitializeManagedFont(
79115 {
80116 var diagnostics = CreateFontDiagnostics ( fontFamily , fontFamilyNames , enabled ) ;
81117 var fallbackFont = LvglFontHelper . GetEffectiveTextFont ( root , lv_part_t . LV_PART_MAIN ) ;
82- var fontManager = TryApplyManagedFont ( root , fontFamily , size , dpi , fallbackFont , out var font , out var style , enabled ) ;
83- return new LvglManagedFontState ( fallbackFont , font , fontManager , diagnostics , style ) ;
118+ if ( ! enabled )
119+ {
120+ return new LvglManagedFontState ( fallbackFont , null , null , diagnostics , null ) ;
121+ }
122+
123+ var fontManager = TryApplyManagedFont ( root , fontFamily , size , dpi , fallbackFont , out var font , out var style , enabled : true ) ;
124+ if ( fontManager is not null )
125+ {
126+ return new LvglManagedFontState ( fallbackFont , font , fontManager , diagnostics , style ) ;
127+ }
128+
129+ if ( TryResolveEmbeddedFallbackFontPath ( out var embeddedFallbackFontPath ) )
130+ {
131+ var embeddedFallbackDiagnostics = CreateEmbeddedFallbackDiagnostics ( embeddedFallbackFontPath , diagnostics . DiagnosticSummary ) ;
132+ var embeddedFallbackFontManager = TryApplyManagedFont ( root , embeddedFallbackFontPath , size , dpi , fallbackFont , out font , out style , enabled : true ) ;
133+ if ( embeddedFallbackFontManager is not null )
134+ {
135+ return new LvglManagedFontState ( fallbackFont , font , embeddedFallbackFontManager , embeddedFallbackDiagnostics , style ) ;
136+ }
137+ }
138+
139+ return new LvglManagedFontState (
140+ fallbackFont ,
141+ null ,
142+ null ,
143+ LvglFontDiagnostics . FromPath ( null , $ "{ diagnostics . DisplaySummary } ; Source=LvglNativeFont; EmbeddedFallback=<none>", diagnostics . GlyphDiagnosticSummary ) ,
144+ null ) ;
84145 }
85146
86147 /// <summary>
@@ -91,6 +152,92 @@ public static LvglFontDiagnostics CreateFontDiagnostics(FontFamily? fontFamily,
91152 return LvglFontDiagnostics . FromFontFamily ( fontFamily , fontFamilyNames , enabled ) ;
92153 }
93154
155+ /// <summary>
156+ /// Resolves the shared embedded fallback font path extracted from the Core assembly resources.
157+ /// </summary>
158+ public static bool TryResolveEmbeddedFallbackFontPath ( out string resolvedFontPath )
159+ {
160+ Assembly assembly = typeof ( LvglManagedFontHelper ) . Assembly ;
161+ using Stream ? resourceStream = assembly . GetManifestResourceStream ( EmbeddedFallbackFontResourceName ) ;
162+ if ( resourceStream is null )
163+ {
164+ resolvedFontPath = string . Empty ;
165+ return false ;
166+ }
167+
168+ string targetDirectory = Path . Combine ( Path . GetTempPath ( ) , "LVGLSharp" , "fonts" ) ;
169+ Directory . CreateDirectory ( targetDirectory ) ;
170+
171+ resolvedFontPath = Path . Combine ( targetDirectory , EmbeddedFallbackFontFileName ) ;
172+ if ( File . Exists ( resolvedFontPath ) && new FileInfo ( resolvedFontPath ) . Length == resourceStream . Length )
173+ {
174+ return true ;
175+ }
176+
177+ using FileStream outputStream = File . Create ( resolvedFontPath ) ;
178+ resourceStream . CopyTo ( outputStream ) ;
179+ return true ;
180+ }
181+
182+ /// <summary>
183+ /// Creates diagnostics for the shared embedded fallback font.
184+ /// </summary>
185+ public static LvglFontDiagnostics CreateEmbeddedFallbackDiagnostics ( string fontPath , string ? previousSummary = null )
186+ {
187+ ArgumentException . ThrowIfNullOrWhiteSpace ( fontPath ) ;
188+
189+ string summary = string . IsNullOrWhiteSpace ( previousSummary )
190+ ? $ "Source=EmbeddedFallbackFont; Path={ fontPath } "
191+ : $ "{ previousSummary } ; Source=EmbeddedFallbackFont; Path={ fontPath } ";
192+
193+ return LvglFontDiagnostics . FromPath ( fontPath , summary , CreateGlyphDiagnosticSummary ( fontPath ) ) ;
194+ }
195+
196+ /// <summary>
197+ /// Creates a glyph diagnostics summary for a font file path.
198+ /// </summary>
199+ public static string CreateGlyphDiagnosticSummary ( string fontPath )
200+ {
201+ ArgumentException . ThrowIfNullOrWhiteSpace ( fontPath ) ;
202+
203+ try
204+ {
205+ FontCollection collection = new ( ) ;
206+ FontFamily family = collection . Add ( fontPath ) ;
207+ Font font = family . CreateFont ( 12 ) ;
208+ Rune [ ] sampleRunes = [ new Rune ( '图' ) , new Rune ( '像' ) , new Rune ( '路' ) , new Rune ( '径' ) ] ;
209+ Dictionary < string , int > signatureCounts = new ( StringComparer . Ordinal ) ;
210+ List < string > parts = new ( sampleRunes . Length ) ;
211+
212+ foreach ( var rune in sampleRunes )
213+ {
214+ if ( ! TryGetGlyphSignature ( font , rune , out var signature ) )
215+ {
216+ parts . Add ( $ "{ rune } :Missing") ;
217+ continue ;
218+ }
219+
220+ parts . Add ( $ "{ rune } :{ signature } ") ;
221+ signatureCounts [ signature ] = signatureCounts . TryGetValue ( signature , out var count ) ? count + 1 : 1 ;
222+ }
223+
224+ bool uniformSignature = signatureCounts . Count == 1 && signatureCounts . Values . First ( ) == sampleRunes . Length ;
225+ return $ "Uniform={ uniformSignature } ; Distinct={ signatureCounts . Count } ; Samples={ string . Join ( ", " , parts ) } ";
226+ }
227+ catch ( IOException ex )
228+ {
229+ return $ "GlyphDiagError={ ex . GetType ( ) . Name } :{ ex . Message } ";
230+ }
231+ catch ( UnauthorizedAccessException ex )
232+ {
233+ return $ "GlyphDiagError={ ex . GetType ( ) . Name } :{ ex . Message } ";
234+ }
235+ catch ( InvalidOperationException ex )
236+ {
237+ return $ "GlyphDiagError={ ex . GetType ( ) . Name } :{ ex . Message } ";
238+ }
239+ }
240+
94241 /// <summary>
95242 /// Applies a managed font from the specified font file path when managed fonts are enabled.
96243 /// </summary>
@@ -195,4 +342,18 @@ public static void ReleaseManagedFont(
195342 lv_font_t * font = null ;
196343 ReleaseManagedFont ( ref fallbackFont , ref font , ref fontManager , ref diagnostics , ref defaultFontStyle ) ;
197344 }
345+
346+ private static bool TryGetGlyphSignature ( Font font , Rune rune , out string signature )
347+ {
348+ signature = string . Empty ;
349+
350+ if ( ! font . TryGetGlyphs ( new CodePoint ( rune . Value ) , out var glyphs ) || glyphs . Count == 0 )
351+ {
352+ return false ;
353+ }
354+
355+ FontRectangle bbox = glyphs [ 0 ] . BoundingBox ( GlyphLayoutMode . Horizontal , Vector2 . Zero , 72f ) ;
356+ signature = $ "Count={ glyphs . Count } ;Adv={ Math . Round ( ( double ) glyphs [ 0 ] . GlyphMetrics . AdvanceWidth , 2 ) } ;BBox={ Math . Round ( ( double ) bbox . Width , 2 ) } x{ Math . Round ( ( double ) bbox . Height , 2 ) } @{ Math . Round ( ( double ) bbox . Left , 2 ) } ,{ Math . Round ( ( double ) bbox . Top , 2 ) } ";
357+ return true ;
358+ }
198359}
0 commit comments