字体预览工具 v1.0.0 初始版本
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user