Skip to content

Instantly share code, notes, and snippets.

@inaniwaudon
Last active January 26, 2022 15:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save inaniwaudon/70fe903bacf358e5934053f45971ffd0 to your computer and use it in GitHub Desktop.
Save inaniwaudon/70fe903bacf358e5934053f45971ffd0 to your computer and use it in GitHub Desktop.
cff-subsetter
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();
}
}
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();
}
}
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;
}
}
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