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;
}
}
}