Files
font-preview/FontPreview/GlyphCoverageDialog.cs
T

136 lines
4.0 KiB
C#

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace FontPreview
{
/// <summary>
/// 缺字检测对话框:选一款已加载字体,输入测试文本,
/// 用 cmap 表判断哪些字符该字体没有字形。
/// </summary>
public class GlyphCoverageDialog : Form
{
private List<FontItem> items;
private ComboBox cboFont;
private TextBox txtTest;
private TextBox txtResult;
private Label lblStat;
public GlyphCoverageDialog(List<FontItem> items)
{
this.items = items;
Text = "缺字检测";
FormBorderStyle = FormBorderStyle.SizableToolWindow;
StartPosition = FormStartPosition.CenterParent;
ClientSize = new Size(560, 460);
Font = new Font("Microsoft YaHei UI", 9f);
MinimumSize = new Size(480, 400);
Label l1 = new Label { Text = "字体:", Location = new Point(12, 15), Size = new Size(40, 20) };
cboFont = new ComboBox { DropDownStyle = ComboBoxStyle.DropDownList, Location = new Point(56, 12), Size = new Size(480, 24) };
foreach (FontItem it in items)
{
cboFont.Items.Add(it.DisplayName + " —— " + it.Name);
}
if (cboFont.Items.Count > 0) cboFont.SelectedIndex = 0;
Label l2 = new Label { Text = "测试文字(逐字检查是否缺字):", Location = new Point(12, 48), Size = new Size(300, 20) };
txtTest = new TextBox
{
Multiline = true,
ScrollBars = ScrollBars.Vertical,
Location = new Point(12, 72),
Size = new Size(524, 110),
Text = SampleTexts.All[0].Text
};
Button btnGo = new Button { Text = "开始检测", Location = new Point(12, 190), Size = new Size(100, 28) };
btnGo.Click += OnGo;
lblStat = new Label { Text = "选择字体后点击“开始检测”", Location = new Point(122, 195), Size = new Size(414, 20) };
txtResult = new TextBox
{
Multiline = true,
ReadOnly = true,
ScrollBars = ScrollBars.Vertical,
Location = new Point(12, 226),
Size = new Size(524, 222),
BackColor = Color.White,
Font = new Font("Microsoft YaHei UI", 10f)
};
Controls.Add(l1); Controls.Add(cboFont);
Controls.Add(l2); Controls.Add(txtTest);
Controls.Add(btnGo); Controls.Add(lblStat);
Controls.Add(txtResult);
}
private void OnGo(object sender, EventArgs e)
{
if (cboFont.SelectedIndex < 0) { lblStat.Text = "请先选择字体"; return; }
FontItem it = items[cboFont.SelectedIndex];
FontCmap cmap = FontCmap.Load(it.Path);
if (cmap == null)
{
lblStat.Text = "无法解析该字体(可能不是 TTF/OTF)";
txtResult.Text = "";
return;
}
string test = txtTest.Text ?? "";
HashSet<int> seen = new HashSet<int>();
List<string> missing = new List<string>();
int total = 0;
// 按 UTF-32 码点遍历,正确处理 emoji/代理对,避免孤立高代理项报错
for (int i = 0; i < test.Length; i++)
{
char ch = test[i];
if (char.IsWhiteSpace(ch) || char.IsPunctuation(ch)) continue;
int cp;
string chStr;
if (char.IsHighSurrogate(ch) && i + 1 < test.Length && char.IsLowSurrogate(test[i + 1]))
{
cp = char.ConvertToUtf32(ch, test[i + 1]);
chStr = new string(new char[] { ch, test[i + 1] });
i++;
}
else if (char.IsSurrogate(ch))
{
continue; // 孤立代理项,跳过
}
else
{
cp = ch;
chStr = ch.ToString();
}
if (!seen.Add(cp)) continue;
total++;
if (!cmap.HasGlyph(cp))
{
missing.Add(chStr);
}
}
int cover = total - missing.Count;
lblStat.Text = string.Format("共检查 {0} 个不同字符,覆盖 {1},缺字 {2} 个", total, cover, missing.Count);
if (missing.Count == 0)
{
txtResult.Text = "✓ 该字体覆盖测试文本中的全部字符,无缺字。";
}
else
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("以下字符该字体缺字(无字形):");
sb.AppendLine();
sb.AppendLine(string.Join(" ", missing));
sb.AppendLine();
sb.AppendLine("建议:换字体或减少这些用字。");
txtResult.Text = sb.ToString();
}
}
}
}