Skip to content

Commit 3dfb5a4

Browse files
committed
跨平台集成嵌入式字体兜底与诊断优化
集成 NotoSansSC-Regular.otf 作为嵌入式资源,统一各平台字体回退逻辑,提升中文等 CJK 字符显示的健壮性。优化托管字体初始化流程,自动在系统字体不可用时回退到嵌入式字体。增强字体诊断与字形分析能力,诊断信息中明确标识字体来源。重构 Linux 字体解析流程,移除重复代码,保证 Mac/Windows/Linux 行为一致。
1 parent 556ed2f commit 3dfb5a4

File tree

6 files changed

+196
-126
lines changed

6 files changed

+196
-126
lines changed

src/LVGLSharp.Core/LVGLSharp.Core.csproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@
2424
<ProjectReference Include="..\LVGLSharp.Interop\LVGLSharp.Interop.csproj" />
2525
</ItemGroup>
2626

27+
<ItemGroup>
28+
<EmbeddedResource Include="..\LVGLSharp.Runtime.Linux\Assets\Fonts\NotoSansSC-Regular.otf" Link="Assets\Fonts\NotoSansSC-Regular.otf" LogicalName="LVGLSharp.Core.Assets.Fonts.NotoSansSC-Regular.otf" />
29+
<None Include="..\LVGLSharp.Runtime.Linux\Assets\Fonts\NOTICE.txt" Pack="true" PackagePath="THIRD-PARTY-NOTICES\LVGLSharp.Core\" Visible="false" />
30+
</ItemGroup>
31+
2732
<ItemGroup>
2833
<Folder Include="Drawing\" />
2934
</ItemGroup>

src/LVGLSharp.Core/LvglFontDiagnostics.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,18 @@ public static LvglFontDiagnostics FromFontFamily(FontFamily? fontFamily, IEnumer
2626

2727
if (!enabled)
2828
{
29-
return new LvglFontDiagnostics(null, "ManagedFontDisabled", null);
29+
return new LvglFontDiagnostics(null, "Source=LvglNativeFont; ManagedFontDisabled", null);
3030
}
3131

3232
var candidateList = string.Join(", ", candidates.Where(static candidate => !string.IsNullOrWhiteSpace(candidate)));
3333
if (fontFamily is null)
3434
{
35-
return new LvglFontDiagnostics(null, $"ManagedFontEnabled; Family=<none>; Candidates={candidateList}", null);
35+
return new LvglFontDiagnostics(null, $"Source=PlatformSystemFont; ManagedFontEnabled; Family=<none>; Candidates={candidateList}", null);
3636
}
3737

3838
return new LvglFontDiagnostics(
3939
fontFamily.Value.Name,
40-
$"ManagedFontEnabled; Family={fontFamily.Value.Name}; Candidates={candidateList}",
40+
$"Source=PlatformSystemFont; ManagedFontEnabled; Family={fontFamily.Value.Name}; Candidates={candidateList}",
4141
null);
4242
}
4343
}

src/LVGLSharp.Core/LvglManagedFontHelper.cs

Lines changed: 165 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
using LVGLSharp.Interop;
22
using SixLabors.Fonts;
3+
using SixLabors.Fonts.Unicode;
4+
using System.Numerics;
5+
using System.Reflection;
6+
using System.Text;
37

48
namespace LVGLSharp;
59

610
public 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

Comments
 (0)