172 lines
4.1 KiB
C#
172 lines
4.1 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Text;
|
||
|
||
namespace FontPreview
|
||
{
|
||
/// <summary>
|
||
/// 轻量 cmap 表解析器:纯字节读取字体文件,判断某 Unicode 码点是否有字形。
|
||
/// 支持 TTF/OTF/TTC,覆盖 cmap 格式 4(BMP)与格式 12(全平面)。
|
||
/// 不依赖 GDI+/DirectWrite,跨平台可编译。
|
||
/// </summary>
|
||
public class FontCmap
|
||
{
|
||
private List<int[]> ranges = new List<int[]>(); // 排序的 [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<int[]> merged = new List<int[]>();
|
||
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;
|
||
}
|
||
}
|
||
}
|