using System; using System.Collections.Generic; using System.Drawing; using System.Text; using System.Windows.Forms; namespace FontPreview { /// /// 缺字检测对话框:选一款已加载字体,输入测试文本, /// 用 cmap 表判断哪些字符该字体没有字形。 /// public class GlyphCoverageDialog : Form { private List items; private ComboBox cboFont; private TextBox txtTest; private TextBox txtResult; private Label lblStat; public GlyphCoverageDialog(List 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 seen = new HashSet(); List missing = new List(); 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(); } } } }