using System; using System.Collections.Generic; using System.IO; using System.Text; namespace FontPreview { /// /// 轻量 cmap 表解析器:纯字节读取字体文件,判断某 Unicode 码点是否有字形。 /// 支持 TTF/OTF/TTC,覆盖 cmap 格式 4(BMP)与格式 12(全平面)。 /// 不依赖 GDI+/DirectWrite,跨平台可编译。 /// public class FontCmap { private List ranges = new List(); // 排序的 [start, end] 闭区间 public static FontCmap Load(string path) { try { byte[] d = File.ReadAllBytes(path); FontCmap c = new FontCmap(); c.Parse(d); return c.ranges.Count > 0 ? c : null; } catch { return null; } } private static ushort RU16(byte[] d, uint o) { return (ushort)(d[o] * 256u + d[o + 1]); } private static uint RU32(byte[] d, uint o) { return (uint)(d[o] * 16777216u + d[o + 1] * 65536u + d[o + 2] * 256u + d[o + 3]); } private static string Tag(byte[] d, uint o) { try { return Encoding.ASCII.GetString(d, (int)o, 4); } catch { return ""; } } private void Parse(byte[] d) { uint sfntOff = 0; if (Tag(d, 0) == "ttcf") { sfntOff = RU32(d, 8); // TTC:第一个字体的偏移 } uint numTables = RU16(d, sfntOff + 4); uint cmapOff = 0; for (uint i = 0; i < numTables; i++) { uint rec = sfntOff + 12 + i * 16; if (Tag(d, rec) == "cmap") { cmapOff = RU32(d, rec + 8); break; } } if (cmapOff == 0) return; uint numSub = RU16(d, cmapOff + 2); uint best = 0; bool gotFull = false; for (uint i = 0; i < numSub; i++) { uint rec = cmapOff + 4 + i * 8; ushort platform = RU16(d, rec); ushort enc = RU16(d, rec + 2); uint off = cmapOff + RU32(d, rec + 4); // Windows: platformID=3;enc=10 为全 Unicode 平面,enc=1 为 BMP if (platform == 3 && (enc == 10 || enc == 1)) { if (enc == 10 && !gotFull) { best = off; gotFull = true; } else if (best == 0) { best = off; } } } if (best == 0) { // 退而求其次:任意子表 if (numSub > 0) best = cmapOff + RU32(d, cmapOff + 8); } if (best == 0) return; ushort fmt = RU16(d, best); if (fmt == 4) ParseFormat4(d, best); else if (fmt == 12) ParseFormat12(d, best); else if (fmt == 0) ParseFormat0(d, best); ranges.Sort((a, b) => a[0].CompareTo(b[0])); Merge(); } private void ParseFormat4(byte[] d, uint o) { int segCount = RU16(d, o + 6) / 2; uint endOff = o + 14; uint startOff = endOff + (uint)segCount * 2 + 2; // 跳过 endCode 与 reservedPad for (int i = 0; i < segCount; i++) { int s = RU16(d, startOff + (uint)i * 2); int e = RU16(d, endOff + (uint)i * 2); if (s != 0xFFFF && e >= s && s != 0) { ranges.Add(new int[] { s, e }); } } } private void ParseFormat12(byte[] d, uint o) { uint numGroups = RU32(d, o + 12); uint g = o + 16; for (uint i = 0; i < numGroups; i++) { int s = (int)RU32(d, g + i * 12); int e = (int)RU32(d, g + i * 12 + 4); if (e >= s) ranges.Add(new int[] { s, e }); } } private void ParseFormat0(byte[] d, uint o) { // 256 字节 glyph index 表,非 0 即有字形 int start = -1; for (int i = 0; i < 256; i++) { bool has = d[o + 6 + i] != 0; if (has && start < 0) start = i; if (!has && start >= 0) { ranges.Add(new int[] { start, i - 1 }); start = -1; } } if (start >= 0) ranges.Add(new int[] { start, 255 }); } private void Merge() { List merged = new List(); foreach (int[] r in ranges) { if (merged.Count > 0 && r[0] <= merged[merged.Count - 1][1] + 1) { if (r[1] > merged[merged.Count - 1][1]) merged[merged.Count - 1][1] = r[1]; } else merged.Add(new int[] { r[0], r[1] }); } ranges = merged; } public bool HasGlyph(int codepoint) { int lo = 0, hi = ranges.Count - 1; while (lo <= hi) { int mid = (lo + hi) / 2; if (codepoint < ranges[mid][0]) hi = mid - 1; else if (codepoint > ranges[mid][1]) lo = mid + 1; else return true; } return false; } } }