17 KiB
AtomUI 字体系统 — 字体与 Design Token 系统的集成
本文档详细描述字体相关属性如何融入 AtomUI 的 Design Token 四层架构(Seed → Map → Alias → Component),包括字号/行高的自动派生算法、字体相关 Token 的完整列表,以及控件主题中的字体绑定机制。
1. 字体在 Token 推导链中的位置
AtomUI Design Token 系统的四层推导链中,字体相关 Token 分布在前三层:
┌─────────────────────────────────────────────────────────────────────────┐
│ Seed Token(种子变量) │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ FontFamily = "fonts:AlibabaSans#Alibaba Sans, ..." │ │
│ │ FontSize = 14 │ │
│ └──────────────────────────┬──────────────────────────────────────┘ │
│ │ │
│ IThemeVariantCalculator.Calculate() │
│ → CalculatorUtils.CalculateFontMapTokenValues() │
│ │ │
│ ┌──────────────────────────▼──────────────────────────────────────┐ │
│ │ Map Token(梯度变量) │ │
│ │ FontSizeSM, FontSizeLG, FontSizeXL │ │
│ │ FontSizeHeading1 ~ FontSizeHeading5 │ │
│ │ RelativeLineHeight, RelativeLineHeightSM, RelativeLineHeightLG │ │
│ │ RelativeLineHeightHeading1 ~ RelativeLineHeightHeading5 │ │
│ │ FontHeight, FontHeightSM, FontHeightLG │ │
│ └──────────────────────────┬──────────────────────────────────────┘ │
│ │ │
│ DesignToken.CalculateAliasTokenValues() │
│ │ │
│ ┌──────────────────────────▼──────────────────────────────────────┐ │
│ │ Alias Token(别名变量) │ │
│ │ FontSizeIcon = FontSizeSM │ │
│ │ FontWeightStrong = FontWeight.SemiBold │ │
│ └─────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
2. Seed Token:字体种子
2.1 FontFamily(字体族)
源文件: DesignToken.Seed.cs
/// <summary>
/// 字体
/// Ant Design 的字体家族中优先使用系统默认的界面字体,同时提供了一套利于屏显的备用字体库,
/// 来维护在不同平台以及浏览器的显示下,字体始终保持良好的易读性和可读性,体现了友好、稳定和专业的特性。
/// </summary>
[DesignTokenKind(DesignTokenKind.Seed)]
public FontFamily? FontFamily { get; set; }
默认值初始化: DesignToken.cs → InitSeedTokenValues()
FontFamily = FontFamily.Parse(
"fonts:AlibabaSans#Alibaba Sans, Segoe UI, Segoe UI Symbol, " +
"Helvetica Neue, Noto Sans, Noto Sans CJK SC, 文泉驿正黑, " +
"Microsoft YaHei, PingFang SC, $Default"
);
FontFamily 是一个特殊的 Seed Token — 它不参与主题算法的自动推导(不像 ColorPrimary 会被色板算法展开为 10 级梯度),而是直接作为资源值发布到 ResourceDictionary 中,供控件主题通过 {atom:SharedTokenResource FontFamily} 消费。
2.2 FontSize(基准字号)
/// <summary>
/// 默认字号
/// 设计系统中使用最广泛的字体大小,文本梯度也将基于该字号进行派生。
/// </summary>
[DesignTokenKind(DesignTokenKind.Seed)]
public double FontSize { get; set; } = 14;
FontSize = 14 是 Ant Design 5.0 的标准基准字号。所有字号梯度(FontSizeSM、FontSizeLG、FontSizeHeading1~5 等)都以此为基础,通过自然对数函数自动派生。
3. Map Token:字号梯度派生
3.1 字号梯度派生算法
源文件: CalculatorUtils.cs → CalculateFontMapTokenValues() + CalculateFontSize()
字号梯度的核心算法基于自然对数(e ≈ 2.71828)的指数增长曲线:
public static IList<FontSizeInfo> CalculateFontSize(double baseValue)
{
var fontSizes = new List<double>(10);
for (var index = 0; index < 10; ++index)
{
var i = index - 1;
var baseSize = baseValue * Math.Pow(2.71828, i / 5.0);
var intSize = index > 1 ? Math.Floor(baseSize) : Math.Ceiling(baseSize);
// 转换为偶数
fontSizes.Add((int)(Math.Floor(intSize / 2.0d) * 2));
}
fontSizes[1] = baseValue; // 确保 index=1 为精确的基准字号
// 计算每个字号对应的行高
var results = new List<FontSizeInfo>();
foreach (var size in fontSizes)
{
results.Add(new FontSizeInfo
{
Size = size,
LineHeight = CalculateLineHeight(size)
});
}
return results;
}
算法特点:
- 以基准字号(14px)为中心,使用
e^(i/5)生成对数增长序列。 - 将结果取整为偶数(
Floor(x/2)*2),确保像素对齐。 - 保证
index=1位置严格等于基准字号。 - 生成 10 个字号值(index 0~9),取前 7 个分配给 Map Token。
3.2 行高计算公式
public static double CalculateLineHeight(double fontSize)
{
return (fontSize + 8) / fontSize;
}
这是 Ant Design 5.0 的标准行高公式。例如:
fontSize = 14→lineHeight = (14+8)/14 ≈ 1.571fontSize = 12→lineHeight = (12+8)/12 ≈ 1.667fontSize = 16→lineHeight = (16+8)/16 = 1.500
字号越小,行高比例越大,确保小字文本也有足够的行间距。
3.3 Map Token 分配
public static void CalculateFontMapTokenValues(DesignToken designToken)
{
var fontSizePairs = CalculateFontSize(designToken.FontSize);
IList<double> fontSizes = fontSizePairs.Select(item => item.Size).ToList();
IList<double> lineHeights = fontSizePairs.Select(item => item.LineHeight).ToList();
// 字号分配
designToken.FontSizeSM = fontSizes[0]; // 小号字号
designToken.FontSize = fontSizes[1]; // 基准字号(保持不变)
designToken.FontSizeLG = fontSizes[2]; // 大号字号
designToken.FontSizeXL = fontSizes[3]; // 超大号字号
designToken.FontSizeHeading5 = fontSizes[2]; // h5 标题字号
designToken.FontSizeHeading4 = fontSizes[3]; // h4 标题字号
designToken.FontSizeHeading3 = fontSizes[4]; // h3 标题字号
designToken.FontSizeHeading2 = fontSizes[5]; // h2 标题字号
designToken.FontSizeHeading1 = fontSizes[6]; // h1 标题字号
// 行高分配
designToken.RelativeLineHeight = lineHeights[1]; // 基准行高
designToken.RelativeLineHeightLG = lineHeights[2]; // 大号行高
designToken.RelativeLineHeightSM = lineHeights[0]; // 小号行高
designToken.RelativeLineHeightHeading5 = lineHeights[2]; // h5 行高
designToken.RelativeLineHeightHeading4 = lineHeights[3]; // h4 行高
designToken.RelativeLineHeightHeading3 = lineHeights[4]; // h3 行高
designToken.RelativeLineHeightHeading2 = lineHeights[5]; // h2 行高
designToken.RelativeLineHeightHeading1 = lineHeights[6]; // h1 行高
// 文字高度(FontHeight = Round(fontSize * lineHeight))
designToken.FontHeight = Math.Round(lineHeights[1] * fontSizes[1]);
designToken.FontHeightLG = Math.Round(lineHeights[2] * fontSizes[2]);
designToken.FontHeightSM = Math.Round(lineHeights[0] * fontSizes[0]);
}
3.4 默认字号梯度值(FontSize = 14 时)
| Index | FontSize (px) | LineHeight (ratio) | 分配给 Token |
|---|---|---|---|
| 0 | 12 | 1.667 | FontSizeSM |
| 1 | 14 | 1.571 | FontSize(基准) |
| 2 | 16 | 1.500 | FontSizeLG, FontSizeHeading5 |
| 3 | 20 | 1.400 | FontSizeXL, FontSizeHeading4 |
| 4 | 24 | 1.333 | FontSizeHeading3 |
| 5 | 30 | 1.267 | FontSizeHeading2 |
| 6 | 38 | 1.211 | FontSizeHeading1 |
| Token | 值 | 说明 |
|---|---|---|
FontHeight |
22 | Round(14 × 1.571) |
FontHeightSM |
20 | Round(12 × 1.667) |
FontHeightLG |
24 | Round(16 × 1.500) |
4. Map Token 完整列表
源文件: DesignToken.FontMap.cs
4.1 字号 Token
| Token | 类型 | 说明 | 默认值 (px) |
|---|---|---|---|
FontSizeSM |
double |
小号字体大小 | 12 |
FontSizeLG |
double |
大号字体大小 | 16 |
FontSizeXL |
double |
超大号字体大小 | 20 |
FontSizeHeading1 |
double |
一级标题字号 (H1) | 38 |
FontSizeHeading2 |
double |
二级标题字号 (H2) | 30 |
FontSizeHeading3 |
double |
三级标题字号 (H3) | 24 |
FontSizeHeading4 |
double |
四级标题字号 (H4) | 20 |
FontSizeHeading5 |
double |
五级标题字号 (H5) | 16 |
4.2 行高 Token
| Token | 类型 | 说明 | 默认值 |
|---|---|---|---|
RelativeLineHeight |
double |
基准文本行高 | ≈1.571 |
RelativeLineHeightSM |
double |
小型文本行高 | ≈1.667 |
RelativeLineHeightLG |
double |
大型文本行高 | 1.500 |
RelativeLineHeightHeading1 |
double |
H1 行高 | ≈1.211 |
RelativeLineHeightHeading2 |
double |
H2 行高 | ≈1.267 |
RelativeLineHeightHeading3 |
double |
H3 行高 | ≈1.333 |
RelativeLineHeightHeading4 |
double |
H4 行高 | 1.400 |
RelativeLineHeightHeading5 |
double |
H5 行高 | 1.500 |
4.3 文字高度 Token
| Token | 类型 | 说明 | 默认值 (px) |
|---|---|---|---|
FontHeight |
double |
Round(FontSize × RelativeLineHeight) | 22 |
FontHeightSM |
double |
Round(FontSizeSM × RelativeLineHeightSM) | 20 |
FontHeightLG |
double |
Round(FontSizeLG × RelativeLineHeightLG) | 24 |
5. Alias Token:语义化字体 Token
源文件: DesignToken.Alias.cs + DesignToken.cs → CalculateAliasTokenValues()
5.1 Alias Token 列表
| Token | 类型 | 计算逻辑 | 说明 |
|---|---|---|---|
FontSizeIcon |
double |
= FontSizeSM |
操作图标字体大小(如选择器中的箭头图标) |
FontWeightStrong |
FontWeight |
= FontWeight.SemiBold |
标题类组件或选中项的字体粗细 |
5.2 Alias 计算代码
internal void CalculateAliasTokenValues()
{
// ...
// Font
FontSizeIcon = FontSizeSM;
// ...
FontWeightStrong = FontWeight.SemiBold;
// ...
}
6. Token 算法调用链
字体 Token 的计算发生在主题算法的 Calculate() 方法中:
DefaultThemeVariantCalculator.Calculate(designToken)
│
├── CalculatorUtils.CalculateFontMapTokenValues(designToken)
│ ├── CalculateFontSize(designToken.FontSize) // 从基准字号派生 10 级梯度
│ ├── 分配 FontSizeSM/LG/XL/Heading1~5 // Map Token 赋值
│ ├── 分配 RelativeLineHeight/SM/LG/Heading1~5 // 行高 Map Token 赋值
│ └── 计算 FontHeight/SM/LG // 文字高度 Map Token 赋值
│
└── ... (其他 Map Token 计算)
DesignToken.CalculateAliasTokenValues()
│
├── FontSizeIcon = FontSizeSM // Alias Token 赋值
└── FontWeightStrong = FontWeight.SemiBold // Alias Token 赋值
在 紧凑模式(Compact) 下,CompactThemeVariantCalculator 同样调用 CalculatorUtils.CalculateFontMapTokenValues(),但紧凑模式可能修改了 FontSize Seed Token 的值(使用更小的基准字号),从而自动级联影响所有派生 Token。
7. 控件主题中的字体绑定
7.1 根级字体设置
字体族和基准字号在 根级控件主题(Window、PopupRoot、OverlayPopupHost)中设置,通过 CSS 继承机制向下级控件传播:
WindowTheme.axaml:
<Setter Property="FontFamily" Value="{atom:SharedTokenResource FontFamily}" />
<Setter Property="FontSize" Value="{atom:SharedTokenResource FontSize}" />
PopupRootTheme.axaml:
<Setter Property="FontFamily" Value="{atom:SharedTokenResource FontFamily}" />
OverlayPopupHostTheme.axaml:
<Setter Property="FontFamily" Value="{atom:SharedTokenResource FontFamily}" />
7.2 Token 资源键
字体相关 Token 通过 Source Generator 自动生成资源键枚举:
// 自动生成文件:TokenResourceConst.g.cs
public enum SharedTokenKind
{
// ...
FontFamily, // Seed Token
FontSize, // Seed Token (会被 Map 计算覆盖)
FontSizeSM, // Map Token
FontSizeLG, // Map Token
FontSizeXL, // Map Token
FontSizeHeading1, // Map Token
// ... 更多 Map Token ...
FontSizeIcon, // Alias Token
FontWeightStrong, // Alias Token
// ...
}
7.3 在 AXAML 中消费字体 Token
<!-- 全局 Token:使用 SharedTokenResource -->
<Setter Property="FontFamily" Value="{atom:SharedTokenResource FontFamily}" />
<Setter Property="FontSize" Value="{atom:SharedTokenResource FontSize}" />
<Setter Property="FontSize" Value="{atom:SharedTokenResource FontSizeSM}" />
<Setter Property="FontSize" Value="{atom:SharedTokenResource FontSizeLG}" />
8. 与 Ant Design 5.0 TypeScript 版本的对应关系
| Ant Design (TypeScript) | AtomUI (C#) |
|---|---|
theme.token.fontFamily |
DesignToken.FontFamily (Seed) |
theme.token.fontSize |
DesignToken.FontSize (Seed) |
theme.token.fontSizeSM |
DesignToken.FontSizeSM (Map) |
theme.token.fontSizeLG |
DesignToken.FontSizeLG (Map) |
theme.token.fontSizeHeading1 |
DesignToken.FontSizeHeading1 (Map) |
theme.token.lineHeight |
DesignToken.RelativeLineHeight (Map) |
theme.token.fontSizeIcon |
DesignToken.FontSizeIcon (Alias) |
theme.token.fontWeightStrong |
DesignToken.FontWeightStrong (Alias) |
genFontSizes() (TypeScript) |
CalculatorUtils.CalculateFontSize() (C#) |
getLineHeight() (TypeScript) |
CalculatorUtils.CalculateLineHeight() (C#) |
9. 字体工具类
9.1 FontUtils — em 到像素转换
源文件: AtomUI.Core/Media/FontUtils.cs
public static class FontUtils
{
public static double ConvertEmToPixel(double value, double fontSize, double renderScaling = 1.0)
{
var fontSizePx = fontSize * value * renderScaling;
return fontSizePx * value;
}
}
9.2 TextUtils — 文本尺寸计算
源文件: AtomUI.Core/Media/TextUtils.cs
public static class TextUtils
{
public static Size CalculateTextSize(string text,
double fontSize,
FontFamily fontFamily,
FontStyle fontStyle = FontStyle.Normal,
FontWeight fontWeight = FontWeight.Normal)
{
var typeface = new Typeface(fontFamily, fontStyle, fontWeight);
using var textLayout = new TextLayout(text, typeface, fontSize, null);
return new Size(textLayout.Width, textLayout.Height);
}
}
这些工具类在控件实现中用于测量文本尺寸、计算布局空间等场景。