# AtomUI 字体系统 — 制作自定义字体包 > 本文档面向希望制作自己的字体包的 **AtomUI 应用开发者**和 **库开发者**,提供从零开始创建字体包的完整步骤指南。模式完全对标 `AtomUI.Fonts.AlibabaSans` 和 Avalonia 官方的 `Avalonia.Fonts.Inter`。 --- ## 1. 概述 自定义字体包将一组字体文件(`.ttf` / `.otf`)打包为独立的 NuGet 包或项目,使其可以: - 在多个项目间共享。 - 通过一行代码注册到 `FontManager`。 - 与 AtomUI 主题系统无缝集成。 - 在 XAML 中通过自定义 URI Scheme 引用。 --- ## 2. 步骤一:创建项目 ### 2.1 创建类库项目 ```bash dotnet new classlib -n MyApp.Fonts.CustomFont ``` ### 2.2 配置项目文件 编辑 `.csproj` 文件,添加 Avalonia 依赖和字体资源嵌入配置: ```xml net8.0;net10.0 enable enable ``` > **⚠️ 重要**:`` 是必须的。没有这个配置,字体文件不会被嵌入到程序集中。 --- ## 3. 步骤二:添加字体文件 在项目根目录创建 `Assets/` 文件夹,将字体文件放入: ``` MyApp.Fonts.CustomFont/ └── Assets/ ├── CustomFont-Regular.ttf ├── CustomFont-Bold.ttf ├── CustomFont-Light.ttf └── CustomFont-Medium.ttf ``` **支持的格式:** - TrueType Font (`.ttf`) ✅ - OpenType Font (`.otf`) ✅ - Variable Fonts — 暂不支持(参见 [Avalonia Issue #11092](https://github.com/AvaloniaUI/Avalonia/issues/11092)) **确认字体族内部名称:** 用字体查看工具(如 macOS 的 Font Book、Windows 的字体查看器)打开字体文件,查看其 **Family Name** 元数据。这个名称将用于 URI 中的 `#` 后缀,必须**精确匹配**。 --- ## 4. 步骤三:创建字体集合类 创建 `CustomFontCollection.cs`: ```csharp using Avalonia.Media.Fonts; namespace MyApp.Fonts.CustomFont; public sealed class CustomFontCollection : EmbeddedFontCollection { public CustomFontCollection() : base( new Uri("fonts:CustomFont", UriKind.Absolute), new Uri("avares://MyApp.Fonts.CustomFont/Assets", UriKind.Absolute)) { } } ``` **参数说明:** | 参数 | 说明 | 示例 | |------|------|------| | 第一个 URI(自定义 Scheme) | 字体集合的标识键,用于 XAML 中引用 | `fonts:CustomFont` | | 第二个 URI(资源路径) | 嵌入字体文件的 Avalonia 资源路径 | `avares://MyApp.Fonts.CustomFont/Assets` | **命名约定:** - URI Scheme 统一使用 `fonts:` 前缀。 - 集合键名推荐使用字体族名称(去空格),如 `fonts:SourceHanSans`、`fonts:Roboto`。 - 资源路径中的程序集名称必须与 `.csproj` 中的 ``(默认为项目名)完全匹配。 --- ## 5. 步骤四:创建 AppBuilder 扩展方法 创建 `AppBuilderExtension.cs`,用于纯 Avalonia 场景: ```csharp using Avalonia; using MyApp.Fonts.CustomFont; namespace MyApp.Fonts; public static class AppBuilderExtension { public static AppBuilder WithCustomFont(this AppBuilder appBuilder) { return appBuilder.ConfigureFonts(fontManager => { fontManager.AddFontCollection(new CustomFontCollection()); }); } } ``` **使用方式:** ```csharp public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure() .UsePlatformDetect() .WithCustomFont() .LogToTrace(); ``` --- ## 6. 步骤五:创建 ThemeManagerBuilder 扩展方法(AtomUI 集成) 如果你的字体包需要与 AtomUI 主题系统集成,创建 `ThemeManagerBuilderExtensions.cs`: ```csharp using AtomUI.Theme; using Avalonia.Media; using MyApp.Fonts.CustomFont; public static class CustomFontThemeManagerBuilderExtensions { public static IThemeManagerBuilder UseCustomFont(this IThemeManagerBuilder themeManagerBuilder) { FontManager.Current.AddFontCollection(new CustomFontCollection()); return themeManagerBuilder; } } ``` **使用方式:** ```csharp app.UseAtomUI(builder => { builder.UseCustomFont(); // 注册自定义字体包 builder.UseDesktopControls(); }); ``` > **注意**:添加此扩展方法需要项目引用 `AtomUI.Core`。如果字体包是纯 Avalonia 包(不依赖 AtomUI),可以省略此步骤。 --- ## 7. 步骤六:添加 XAML 命名空间注册(可选) 如果希望字体包的类型能在 AXAML 中通过 `atom:` 或自定义前缀访问,创建 `Properties/AssemblyInfo.cs`: ```csharp using Avalonia.Metadata; [assembly: XmlnsDefinition("https://atomui.net", "MyApp.Fonts.CustomFont")] ``` 对于纯 Avalonia 场景,可以映射到 Avalonia 命名空间: ```csharp [assembly: XmlnsDefinition("https://github.com/avaloniaui", "MyApp.Fonts.CustomFont")] ``` --- ## 8. 步骤七:使用自定义字体 ### 8.1 在 XAML 中直接引用 ```xml ``` `#` 后面的名称必须与字体文件中的 Family Name 元数据一致。 ### 8.2 设置为 AtomUI 默认字体 如果想将自定义字体设置为整个应用的默认字体(替代 Alibaba Sans),使用 `WithDefaultFontFamily()`: ```csharp app.UseAtomUI(builder => { builder.UseCustomFont(); // 注册字体包 builder.UseDesktopControls(); // 将自定义字体设置为默认字体族 builder.WithDefaultFontFamily( "fonts:CustomFont#My Custom Font, " + "Segoe UI, PingFang SC, Microsoft YaHei, $Default" ); }); ``` 这会在主题加载时将 `SharedTokenKind.FontFamily` Token 替换为你指定的字体族,所有使用 `{atom:SharedTokenResource FontFamily}` 的控件都会自动使用新字体。 ### 8.3 同时注册多个字体包 ```csharp app.UseAtomUI(builder => { builder.UseAlibabaSansFont(); // 内置字体 builder.UseCustomFont(); // 自定义字体 builder.UseDesktopControls(); // 选择其中一个作为默认字体 builder.WithDefaultFontFamily("fonts:CustomFont#My Custom Font, $Default"); }); ``` --- ## 9. 完整文件结构 ``` MyApp.Fonts.CustomFont/ ├── CustomFontCollection.cs # 字体集合(EmbeddedFontCollection 子类) ├── AppBuilderExtension.cs # Avalonia AppBuilder 扩展方法 ├── ThemeManagerBuilderExtensions.cs # AtomUI ThemeManagerBuilder 扩展方法(可选) ├── MyApp.Fonts.CustomFont.csproj # 项目文件 ├── Properties/ │ └── AssemblyInfo.cs # XAML 命名空间注册(可选) └── Assets/ ├── CustomFont-Regular.ttf ├── CustomFont-Bold.ttf ├── CustomFont-Light.ttf └── CustomFont-Medium.ttf ``` --- ## 10. 发布为 NuGet 包(可选) 如果需要将字体包发布为 NuGet 包供多个项目使用: ### 10.1 配置包元数据 在 `.csproj` 中添加 NuGet 包配置: ```xml MyApp.Fonts.CustomFont 1.0.0 YourName Custom font package for Avalonia / AtomUI applications Avalonia;AtomUI;Fonts ``` ### 10.2 打包发布 ```bash dotnet pack -c Release dotnet nuget push bin/Release/MyApp.Fonts.CustomFont.1.0.0.nupkg -s https://api.nuget.org/v3/index.json ``` --- ## 11. 高级:自定义字符匹配 如果你的字体包需要控制字符回退行为(例如,为特定语言的字符优先使用特定字体),可以重写 `TryMatchCharacter` 方法: ```csharp using System.Globalization; using Avalonia.Media; using Avalonia.Media.Fonts; public sealed class CustomFontCollection : EmbeddedFontCollection { public CustomFontCollection() : base( new Uri("fonts:CustomFont", UriKind.Absolute), new Uri("avares://MyApp.Fonts.CustomFont/Assets", UriKind.Absolute)) { } public override bool TryMatchCharacter( int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontStretch fontStretch, CultureInfo? culture, out Typeface typeface) { // 自定义字符匹配逻辑 // 例如:为 CJK 字符使用特定字体 return base.TryMatchCharacter( codepoint, fontStyle, fontWeight, fontStretch, culture, out typeface); } } ``` --- ## 12. 检查清单 创建自定义字体包时,确认以下事项: - [ ] `.csproj` 中包含 `` - [ ] 字体文件格式为 `.ttf` 或 `.otf`(不支持 Variable Fonts) - [ ] `EmbeddedFontCollection` 中的 `avares://` URI 程序集名称正确 - [ ] `#` 后的字体族名称与字体文件元数据中的 Family Name 一致 - [ ] 提供了 `AppBuilder` 扩展方法(纯 Avalonia 场景) - [ ] 提供了 `IThemeManagerBuilder` 扩展方法(AtomUI 场景,可选) - [ ] 如果发布为 NuGet 包,字体许可证允许嵌入分发 - [ ] 在目标平台(Windows/macOS/Linux)上测试字体渲染效果