Last active
January 26, 2022 15:01
-
-
Save inaniwaudon/70fe903bacf358e5934053f45971ffd0 to your computer and use it in GitHub Desktop.
cff-subsetter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
public class CffOperator | |
{ | |
private CffTable _table; | |
public CffOperator(CFFTable table) | |
{ | |
_table = table; | |
} | |
public byte[] CreateSubset(HashSet<int> cids) | |
{ | |
var stream = new MemoryStream(); | |
var writer = new FontBinaryWriter(stream); | |
// Copy the range of Header and Name INDEX | |
var firstBytes = new byte[_table.TopDICTIndexOffset]; | |
Buffer.BlockCopy(_table.Bytes, 0, firstBytes, 0, _table.TopDICTIndexOffset); | |
// Copy the range from String INDEX to Encodings | |
int secondLength = _table.CharsetsOffset - _table.StringIndexOffset; | |
var secondBytes = new byte[secondLength]; | |
Buffer.BlockCopy(_table.Bytes, _table.StringIndexOffset, secondBytes, 0, secondLength); | |
// Convert old GIDs to new CIDs | |
var oldGidToCid = new SortedDictionary<int, int>(); | |
for (int gid = 0; gid < _table.Charsets.Glyph.Count; gid++) | |
{ | |
int cid = _table.Charsets.Glyph[gid]; | |
if (cids.Contains(cid)) | |
{ | |
oldGidToCid.Add(gid + 1, cid); | |
} | |
} | |
// Convert old GIDs to new GIDs | |
var newGidToOldGid = new SortedDictionary<int, int>() { { 0, 0 } }; | |
int index = 1; | |
foreach (var item in oldGidToCid) | |
{ | |
newGidToOldGid.Add(index, item.Key); | |
index++; | |
} | |
// Create new Charsets, CharStrings, and FDSelect | |
var charsets = new Charsets(); | |
var charstrings = new IndexData(_table.CharStrings.Offsize); | |
var fdSelect = new FDSelect(); | |
foreach ((int newGid, int oldGid) in newGidToOldGid) | |
{ | |
if (oldGid > 0) | |
{ | |
charsets.Glyph.Add((ushort)oldGidToCid[oldGid]); | |
} | |
fdSelect.Fds.Add(_table.FdSelect.Fds[oldGid]); | |
charstrings.Data.Add(_table.CharStrings.Data[oldGid]); | |
} | |
byte[] charsetsBytes = charsets.ToArray(); | |
byte[] charstringsBytes = charstrings.ToArray(); | |
byte[] fdSelectBytes = fdSelect.ToArray(); | |
// Copy the range from Font DICT INDEX to end. | |
int lastLength = _table.Bytes.Length - _table.LocalSubrIndexOffset; | |
var lastBytes = new byte[lastLength]; | |
Buffer.BlockCopy(_table.Bytes, _table.LocalSubrIndexOffset, lastBytes, 0, lastLength); | |
// Calculate the offsets. | |
int topDictIndexLength = _table.TopDictIndex.ToArray().Length; | |
int charsetOffset = firstBytes.Length + topDictIndexLength + secondBytes.Length; | |
int fdSelectOffset = charsetOffset + charsetsBytes.Length; | |
int charStringsOffset = fdSelectOffset + fdSelectBytes.Length; | |
int fdArrayOffset = charStringsOffset + charstringsBytes.Length; | |
int deltaTopDictLength = | |
DictData.GetDeltaIntegerLength((int)_table.TopDict["15"][0]!, charsetOffset) + | |
DictData.GetDeltaIntegerLength((int)_table.TopDict["17"][0]!, charStringsOffset) + | |
DictData.GetDeltaIntegerLength((int)_table.TopDict["12 36"][0]!, fdArrayOffset) + | |
DictData.GetDeltaIntegerLength((int)_table.TopDict["12 37"][0]!, fdSelectOffset); | |
// Create new Top DICT INDEX. | |
var topDictIndex = new IndexData(_table.TopDictIndex.Offsize); | |
var topDict = new DictData(_table.TopDict); | |
topDict["15"] = new() { charsetOffset + deltaTopDictLength }; | |
topDict["17"] = new() { charStringsOffset + deltaTopDictLength }; | |
topDict["12 36"] = new() { fdArrayOffset + deltaTopDictLength }; | |
topDict["12 37"] = new() { fdSelectOffset + deltaTopDictLength }; | |
var topDictBytes = topDict.ToArray(); | |
topDictIndex.Data.Add(topDictBytes); | |
writer.Write(firstBytes); | |
writer.Write(topDictIndex.ToArray()); | |
writer.Write(secondBytes); | |
writer.Write(charsetsBytes); | |
writer.Write(fdSelectBytes); | |
writer.Write(charstringsBytes); | |
// Font DICT Index | |
fdArrayOffset = (int)stream.Position; | |
var fdArray = new IndexData(_table.FdArray.Offsize); | |
foreach (DictData dict in _table.FdArrayDict) | |
{ | |
var fdArrayDict = new DictData(dict); | |
fdArrayDict["18"] = new() | |
{ | |
dict["18"][0], | |
(int)(dict["18"][1])! + (fdArrayOffset - _table.FdArrayOffset) | |
}; | |
fdArray.Data.Add(fdArrayDict.ToArray()); | |
} | |
writer.Write(fdArray.ToArray()); | |
writer.Write(lastBytes); | |
return stream.ToArray(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Text; | |
public class CffTable : Table | |
{ | |
internal int TopDICTIndexOffset { get; } | |
internal int StringIndexOffset { get; } | |
internal int CharsetsOffset { get; } | |
internal int FdSelectOffset { get; } | |
internal int CharStringsOffset { get; } | |
internal int FdArrayOffset { get; } | |
internal int LocalSubrIndexOffset { get; } | |
internal byte[] Bytes { get; } | |
internal string[] NameIndex { get; } | |
internal DictData TopDict { get; } = null!; | |
internal IndexData TopDictIndex { get; } = null!; | |
internal IndexData CharStrings { get; } | |
internal IndexData FdArray { get; } | |
internal Charsets Charsets { get; } | |
internal FDSelect FdSelect { get; } | |
internal List<DictData> FdArrayDict { get; } = new(); | |
internal CFFTable(FontBinaryReader reader, uint offset, uint length) : base(reader, offset, length) | |
{ | |
this.Bytes = _reader.AllBytes; | |
// Header | |
_reader.ReadUInt8(); // major | |
_reader.ReadUInt8(); // minor | |
_reader.ReadUInt8(); // hdrSize | |
_reader.ReadUInt8(); // offSize | |
// Name INDEX | |
var nameIndexData = IndexData.Parse(_reader); | |
NameIndex = new string[nameIndexData.Data.Count]; | |
for (int i = 0; i < nameIndexData.Data.Count; i++) | |
{ | |
var sb = new StringBuilder(); | |
foreach (byte item in nameIndexData.Data[i]) | |
{ | |
sb.Append(Convert.ToChar(item)); | |
} | |
NameIndex[i] = sb.ToString(); | |
} | |
// Top DICT index | |
TopDICTIndexOffset = _reader.Position; | |
TopDictIndex = IndexData.Parse(_reader); | |
foreach (Byte[] bytes in TopDictIndex.Data) | |
{ | |
TopDict = new DictData(); | |
TopDict.Parse(bytes); | |
} | |
FdArrayOffset = (int)TopDict["12 36"][0]!; | |
FdSelectOffset = (int)TopDict["12 37"][0]!; | |
CharsetsOffset = (int)TopDict["15"][0]!; | |
CharStringsOffset = (int)TopDict["17"][0]!; | |
StringIndexOffset = _reader.Position; | |
// CharStrings: GID to CharString | |
_reader.Position = CharStringsOffset; | |
CharStrings = IndexData.Parse(_reader); | |
int nGlyphs = CharStrings.Data.Count; | |
// Charsets: GID to SID/CID | |
_reader.Position = CharsetsOffset; | |
Charsets = new(); | |
Charsets.Parse(_reader, nGlyphs, FdSelectOffset); | |
// FDSelect: GID to FDIndex | |
_reader.Position = FdSelectOffset; | |
FdSelect = new(); | |
FdSelect.Parse(_reader, nGlyphs); | |
// Font DICT Index | |
_reader.Position = FdArrayOffset; | |
FdArray = IndexData.Parse(_reader); | |
for (int i = 0; i < FdArray.Data.Count; i++) | |
{ | |
FdArrayDict.Add(new DictData()); | |
FdArrayDict.Last().Parse(FdArray.Data[i]); | |
} | |
LocalSubrIndexOffset = _reader.Position; | |
} | |
} | |
public class DictData : Dictionary<string, List<object?>> | |
{ | |
internal DictData() { } | |
internal DictData(DictData dict) : base(dict) { } | |
internal void Parse(byte[] bytes) | |
{ | |
var stream = new MemoryStream(bytes); | |
using var reader = new FontBinaryReader(stream); | |
var values = new List<object?>(); | |
int pos = reader.Position; | |
while (reader.Position < pos + bytes.Length) | |
{ | |
byte b0 = reader.ReadByte(); | |
// Key | |
if (b0 is 12) | |
{ | |
byte b1 = reader.ReadByte(); | |
Add(b0 + " " + b1, values); | |
values = new(); | |
} | |
else if (b0 is >= 0 and <= 21) | |
{ | |
Add(b0.ToString(), values); | |
values = new(); | |
} | |
// Integer | |
if (b0 is 28 or 29 or (>= 32 and <= 254)) | |
{ | |
values.Add(ParseInteger(reader, b0)); | |
} | |
// Real number | |
if (b0 is 30) | |
{ | |
var value = new List<byte>() { b0 }; | |
while ((b0 & 0x0f) is not 15 || (b0 >> 4 & 0x0f) is 15) | |
{ | |
b0 = reader.ReadByte(); | |
value.Add(b0); | |
// value += b0 is >= 0 and <= 9 ? b0.ToString() | |
// : b0 is 10 ? "." : ""; | |
// if (b0 is 14) | |
// { | |
// value = "-" + value; | |
// } | |
// value += b0; | |
} | |
values.Add(value); | |
} | |
} | |
} | |
internal byte[] ToArray() | |
{ | |
var stream = new MemoryStream(); | |
using var writer = new FontBinaryWriter(stream); | |
foreach ((string key, List<object?> valueList) in this) | |
{ | |
foreach (object? value in valueList) | |
{ | |
if (value is int intNo) | |
{ | |
WriteInteger(writer, intNo); | |
} | |
else if (value is List<byte> byteList) | |
{ | |
foreach (byte item in byteList) | |
{ | |
writer.WriteUInt8(item); | |
} | |
} | |
else | |
{ | |
// TODO: | |
throw new NotImplementedException(); | |
} | |
} | |
string[] keys = key.Split(" "); | |
writer.WriteUInt8(Byte.Parse(keys[0])); | |
if (keys.Length > 1) | |
{ | |
writer.WriteUInt8(Byte.Parse(keys[1])); | |
} | |
} | |
return stream.ToArray(); | |
} | |
private int ParseInteger(FontBinaryReader reader, int b0) | |
{ | |
if (b0 is >= 32 and <= 246) | |
{ | |
return b0 - 139; | |
} | |
byte b1 = reader.ReadByte(); | |
if (b0 is >= 247 and <= 250) | |
{ | |
return (b0 - 247) * 256 + b1 + 108; | |
} | |
if (b0 is >= 251 and <= 254) | |
{ | |
return -(b0 - 251) * 256 - b1 - 108; | |
} | |
byte b2 = reader.ReadByte(); | |
if (b0 is 28) | |
{ | |
return b1 << 8 | b2; | |
} | |
byte b3 = reader.ReadByte(); | |
byte b4 = reader.ReadByte(); | |
// b0 is 29 | |
return b1 << 24 | b2 << 16 | b3 << 8 | b4; | |
} | |
private void WriteInteger(FontBinaryWriter writer, int no) | |
{ | |
if (no is >= -107 and <= 107) | |
{ | |
writer.WriteUInt8((byte)(no + 139)); | |
} | |
else if (no is >= 108 and <= 1131) | |
{ | |
writer.WriteUInt8((byte)(((int)(no - 108) / 256) + 247)); | |
writer.WriteUInt8((byte)((no - 108) % 256)); | |
} | |
else if (no is >= -1131 and <= -108) | |
{ | |
writer.WriteUInt8((byte)(((int)(no + 108) / -256) + 251)); | |
writer.WriteUInt8((byte)((-no - 108) % -256)); | |
} | |
else if (no is >= -32768 and <= 32767) | |
{ | |
writer.WriteUInt8(28); | |
writer.WriteUInt8((byte)(no >> 8)); | |
writer.WriteUInt8((byte)(no & 0x00ff)); | |
} | |
else | |
{ | |
writer.WriteUInt8(29); | |
writer.WriteUInt8((byte)(no >> 24)); | |
writer.WriteUInt8((byte)((no >> 16) & 0x000000ff)); | |
writer.WriteUInt8((byte)((no >> 8) & 0x000000ff)); | |
writer.WriteUInt8((byte)(no & 0x000000ff)); | |
} | |
} | |
internal static int GetDeltaIntegerLength(int before, int after) | |
{ | |
var getLength = (int no) => | |
no is >= -107 and <= 107 ? 1 | |
: no is (>= 108 and <= 1131) or (-1131 and <= -108) ? 2 | |
: no is >= -32768 and <= 32767 ? 3 : 5; | |
return getLength(after) - getLength(before); | |
} | |
} | |
public class IndexData | |
{ | |
internal byte Offsize; | |
internal List<byte[]> Data { get; set; } = new(); | |
internal IndexData(byte offsize) | |
{ | |
Offsize = offsize; | |
} | |
internal static IndexData Parse(FontBinaryReader reader) | |
{ | |
ushort count = reader.ReadUInt16(); | |
byte offsize = reader.ReadUInt8(); | |
var offset = new uint[count + 1]; | |
var indexData = new IndexData(offsize); | |
for (int i = 0; i < count + 1; i++) | |
{ | |
offset[i] = | |
offsize == 1 ? (uint)reader.ReadUInt8() : | |
offsize == 2 ? reader.ReadUInt16() : | |
offsize == 3 ? reader.ReadUInt24() : reader.ReadUInt32(); | |
} | |
for (int i = 0; i < count; i++) | |
{ | |
indexData.Data.Add(reader.ReadBytes((int)(offset[i + 1] - offset[i]))); | |
} | |
return indexData; | |
} | |
internal byte[] ToArray() | |
{ | |
var stream = new MemoryStream(); | |
using var writer = new FontBinaryWriter(stream); | |
writer.WriteUInt16((ushort)Data.Count); | |
writer.WriteUInt8(Offsize); | |
int dataSize = 1; | |
for (int i = 0; i < Data.Count + 1; i++) | |
{ | |
if (Offsize == 1) | |
{ | |
writer.WriteUInt8((byte)dataSize); | |
} | |
if (Offsize == 2) | |
{ | |
writer.WriteUInt16((ushort)dataSize); | |
} | |
if (Offsize == 3) | |
{ | |
writer.WriteUInt24((uint)dataSize); | |
} | |
if (Offsize == 4) | |
{ | |
writer.WriteUInt32((uint)dataSize); | |
} | |
if (i < Data.Count) | |
{ | |
dataSize += Data[i].Length; | |
} | |
} | |
foreach (Byte[] bytes in Data) | |
{ | |
writer.Write(bytes); | |
} | |
return stream.ToArray(); | |
} | |
} | |
public class Charsets | |
{ | |
internal List<ushort> Glyph { get; } = new(); | |
internal void Parse(FontBinaryReader reader, int nGlyphs, int fdSelectOffset) | |
{ | |
byte format = reader.ReadUInt8(); | |
// Format 0 | |
if (format is 0) | |
{ | |
for (int i = 0; i < nGlyphs - 1; i++) | |
{ | |
Glyph[i] = reader.ReadUInt16(); | |
} | |
} | |
// Format 1, 2 | |
if (format is 1 or 2) | |
{ | |
while (reader.Position < fdSelectOffset) | |
{ | |
ushort first = reader.ReadUInt16(); | |
ushort nLeft = format == 1 ? (ushort)reader.ReadUInt8() : reader.ReadUInt16(); | |
for (int i = 0; i <= nLeft; i++) | |
{ | |
Glyph.Add((ushort)(first + i)); | |
} | |
} | |
} | |
} | |
internal byte[] ToArray() | |
{ | |
var stream = new MemoryStream(); | |
using var writer = new FontBinaryWriter(stream); | |
// Format 0 only | |
writer.WriteUInt8(0); | |
foreach (ushort cid in Glyph) | |
{ | |
writer.WriteUInt16(cid); | |
} | |
return stream.ToArray(); | |
} | |
} | |
public class FDSelect | |
{ | |
internal List<byte> Fds { get; } = new(); | |
internal void Parse(FontBinaryReader reader, int nGlyphs) | |
{ | |
byte format = reader.ReadUInt8(); | |
// Format 0 | |
if (format == 0) | |
{ | |
for (int i = 0; i < nGlyphs; i++) | |
{ | |
Fds.Add(reader.ReadUInt8()); | |
} | |
} | |
// Format 3 | |
if (format == 3) | |
{ | |
ushort nRanges = reader.ReadUInt16(); | |
ushort first = reader.ReadUInt16(); | |
for (int i = 0; i < nRanges; i++) | |
{ | |
byte fd = reader.ReadUInt8(); | |
ushort firstOrSentinel = reader.ReadUInt16(); | |
for (int gid = first; gid < firstOrSentinel; gid++) | |
{ | |
Fds.Add(fd); | |
} | |
first = firstOrSentinel; | |
} | |
} | |
} | |
internal byte[] ToArray() | |
{ | |
var stream = new MemoryStream(); | |
using var writer = new FontBinaryWriter(stream); | |
// Format 0 only | |
writer.WriteUInt8(0); | |
foreach (byte fd in Fds) | |
{ | |
writer.WriteUInt8(fd); | |
} | |
return stream.ToArray(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
public class FontBinaryReader : BinaryReader | |
{ | |
public FontBinaryReader(Stream stream) | |
{ | |
_stream = stream; | |
} | |
public int Length | |
{ | |
get { return (int)_stream.Length; } | |
} | |
public int Position | |
{ | |
get { return (int)_stream.Position; } | |
set { _stream.Position = value; } | |
} | |
public byte[] AllBytes | |
{ | |
get | |
{ | |
var pos = Position; | |
Position = 0; | |
byte[] bytes = ReadBytes((int)_stream.Length); | |
Position = pos; | |
return bytes; | |
} | |
} | |
public static FontBinaryReader FromReader(FontBinaryReader reader, int offset, int length) | |
{ | |
var slicedBytes = new Byte[length]; | |
int position = reader.Position; | |
Buffer.BlockCopy(reader.AllBytes, offset, slicedBytes, 0, length); | |
reader.Position = position; | |
var stream = new MemoryStream(slicedBytes); | |
return new FontBinaryReader(stream); | |
} | |
public byte ReadUInt8() | |
{ | |
return ReadByte(); | |
} | |
public override ushort ReadUInt16() | |
{ | |
var bytes = BitConverter.GetBytes(base.ReadUInt16()); | |
return BitConverter.ToUInt16(SwapBytes(bytes)); | |
} | |
public uint ReadUInt24() | |
{ | |
uint no = 0; | |
for (int i = 0; i < 3; i++) | |
{ | |
no = (no << 8) + (uint)(ReadByte() & 0xff); | |
} | |
return no; | |
} | |
public override uint ReadUInt32() | |
{ | |
var bytes = BitConverter.GetBytes(base.ReadUInt32()); | |
return BitConverter.ToUInt32(SwapBytes(bytes)); | |
} | |
public new string ReadChars(int length) | |
{ | |
var sb = new StringBuilder(); | |
for (int i = 0; i < length; i++) | |
{ | |
sb.Append(ReadChar()); | |
} | |
return sb.ToString(); | |
} | |
public void Skip(int length) | |
{ | |
Position += length; | |
} | |
internal static byte[] SwapBytes(byte[] bytes) | |
{ | |
Array.Reverse(bytes); | |
return bytes; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
public class FontBinaryWriter : BinaryWriter | |
{ | |
public FontBinaryWriter(Stream stream) : base(stream) { } | |
public void WriteUInt8(byte value) | |
{ | |
Write(value); | |
} | |
public void WriteUInt16(ushort value) | |
{ | |
Write((byte)(value >> 8 & 0xff)); | |
Write((byte)(value & 0xff)); | |
} | |
public void WriteUInt24(uint value) | |
{ | |
Write((byte)(value >> 16 & 0xff)); | |
Write((byte)(value >> 8 & 0xff)); | |
Write((byte)(value & 0xff)); | |
} | |
public void WriteUInt32(uint value) | |
{ | |
Write((byte)(value >> 24 & 0xff)); | |
Write((byte)(value >> 16 & 0xff)); | |
Write((byte)(value >> 8 & 0xff)); | |
Write((byte)(value & 0xff)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment