223 lines
7.4 KiB
C#
223 lines
7.4 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Text;
|
|
|
|
namespace Cryville.Common.Font {
|
|
public abstract class FontTable<T> {
|
|
protected UInt32 Offset { get; private set; }
|
|
protected BinaryReader Reader { get; private set; }
|
|
|
|
protected FontTable(BinaryReader reader, UInt32 offset) {
|
|
Reader = reader;
|
|
Offset = offset;
|
|
reader.BaseStream.Position = offset;
|
|
}
|
|
public abstract IReadOnlyList<T> GetItems();
|
|
}
|
|
public abstract class FontTable<T, U> : FontTable<T> {
|
|
protected FontTable(BinaryReader reader, UInt32 offset) : base(reader, offset) { }
|
|
public abstract U GetSubTable(T item);
|
|
}
|
|
public sealed class TTCHeader : FontTable<UInt32, TableDirectory> {
|
|
readonly String ttcTag;
|
|
readonly UInt16 majorVersion;
|
|
readonly UInt16 minorVersion;
|
|
readonly UInt32 numFonts;
|
|
readonly List<UInt32> tableDirectoryOffsets = new List<UInt32>();
|
|
readonly String dsigTag;
|
|
readonly UInt32 dsigLength;
|
|
readonly UInt32 dsigOffset;
|
|
public TTCHeader(BinaryReader reader, UInt32 offset) : base(reader, offset) {
|
|
ttcTag = reader.ReadTag();
|
|
if (ttcTag != "ttcf") throw new NotImplementedException();
|
|
majorVersion = reader.ReadUInt16();
|
|
minorVersion = reader.ReadUInt16();
|
|
numFonts = reader.ReadUInt32();
|
|
for (UInt32 i = 0; i < numFonts; i++) tableDirectoryOffsets.Add(reader.ReadUInt32());
|
|
if (majorVersion == 2) {
|
|
dsigTag = reader.ReadTag();
|
|
dsigLength = reader.ReadUInt32();
|
|
dsigOffset = reader.ReadUInt32();
|
|
}
|
|
}
|
|
public override IReadOnlyList<UInt32> GetItems() {
|
|
return tableDirectoryOffsets;
|
|
}
|
|
public override TableDirectory GetSubTable(UInt32 item) {
|
|
var i = item;
|
|
return new TableDirectory(Reader, i);
|
|
}
|
|
}
|
|
public sealed class TableDirectory : FontTable<TableRecord, object> {
|
|
readonly UInt32 sfntVersion;
|
|
readonly UInt16 numTables;
|
|
readonly UInt16 searchRange;
|
|
readonly UInt16 entrySelector;
|
|
readonly UInt16 rangeShift;
|
|
readonly List<TableRecord> tableRecords = new List<TableRecord>();
|
|
public TableDirectory(BinaryReader reader, UInt32 offset) : base(reader, offset) {
|
|
sfntVersion = reader.ReadUInt32();
|
|
numTables = reader.ReadUInt16();
|
|
searchRange = reader.ReadUInt16();
|
|
entrySelector = reader.ReadUInt16();
|
|
rangeShift = reader.ReadUInt16();
|
|
for (int i = 0; i < numTables; i++)
|
|
tableRecords.Add(new TableRecord {
|
|
tableTag = reader.ReadTag(),
|
|
checksum = reader.ReadUInt32(),
|
|
offset = reader.ReadUInt32(),
|
|
length = reader.ReadUInt32(),
|
|
});
|
|
}
|
|
public override IReadOnlyList<TableRecord> GetItems() {
|
|
return tableRecords;
|
|
}
|
|
public override object GetSubTable(TableRecord item) {
|
|
switch (item.tableTag) {
|
|
case "name": return new NameTable(Reader, item.offset);
|
|
case "meta": return new MetaTable(Reader, item.offset);
|
|
default: throw new NotImplementedException();
|
|
}
|
|
}
|
|
}
|
|
public struct TableRecord {
|
|
public string tableTag;
|
|
public UInt32 checksum;
|
|
public UInt32 offset;
|
|
public UInt32 length;
|
|
}
|
|
public sealed class NameTable : FontTable<NameRecord> {
|
|
readonly UInt16 version;
|
|
readonly UInt16 count;
|
|
readonly UInt16 storageOffset;
|
|
readonly List<NameRecord> nameRecord = new List<NameRecord>();
|
|
readonly UInt16 langTagCount;
|
|
readonly List<LangTagRecord> langTagRecord = new List<LangTagRecord>();
|
|
public NameTable(BinaryReader reader, UInt32 offset) : base(reader, offset) {
|
|
version = reader.ReadUInt16();
|
|
count = reader.ReadUInt16();
|
|
storageOffset = reader.ReadUInt16();
|
|
for (UInt16 i = 0; i < count; i++)
|
|
nameRecord.Add(new NameRecord {
|
|
platformID = reader.ReadUInt16(),
|
|
encodingID = reader.ReadUInt16(),
|
|
languageID = reader.ReadUInt16(),
|
|
nameID = (NameID)reader.ReadUInt16(),
|
|
length = reader.ReadUInt16(),
|
|
stringOffset = reader.ReadUInt16(),
|
|
});
|
|
if (version == 1) {
|
|
langTagCount = reader.ReadUInt16();
|
|
for (UInt16 i = 0; i < langTagCount; i++)
|
|
langTagRecord.Add(new LangTagRecord {
|
|
length = reader.ReadUInt16(),
|
|
langTagOffset = reader.ReadUInt16(),
|
|
});
|
|
}
|
|
UInt32 origin = (UInt32)reader.BaseStream.Position;
|
|
for (int i = 0; i < nameRecord.Count; i++) nameRecord[i] = nameRecord[i].Load(reader, origin);
|
|
for (int i = 0; i < langTagRecord.Count; i++) langTagRecord[i] = langTagRecord[i].Load(reader, origin);
|
|
}
|
|
public sealed override IReadOnlyList<NameRecord> GetItems() {
|
|
return nameRecord;
|
|
}
|
|
}
|
|
public struct NameRecord {
|
|
public UInt16 platformID;
|
|
public UInt16 encodingID;
|
|
public UInt16 languageID;
|
|
public NameID nameID;
|
|
public UInt16 length;
|
|
public UInt16 stringOffset;
|
|
public String value { get; private set; }
|
|
public NameRecord Load(BinaryReader reader, UInt32 origin) {
|
|
reader.BaseStream.Position = origin + stringOffset;
|
|
Encoding encoding;
|
|
switch (platformID) {
|
|
case 0: encoding = Encoding.BigEndianUnicode; break;
|
|
case 3: encoding = Encoding.BigEndianUnicode; break;
|
|
default: return this;
|
|
}
|
|
value = encoding.GetString(reader.ReadBytes(length));
|
|
return this;
|
|
}
|
|
}
|
|
public enum NameID : UInt16 {
|
|
CopyrightNotice = 0,
|
|
FontFamilyName = 1,
|
|
FontSubfamilyName = 2,
|
|
UniqueFontIdentifier = 3,
|
|
FullFontName = 4,
|
|
VersionString = 5,
|
|
PostScriptName = 6,
|
|
Trademark = 7,
|
|
ManufacturerName = 8,
|
|
Designer = 9,
|
|
Description = 10,
|
|
URLVendor = 11,
|
|
URLDesigner = 12,
|
|
LicenseDescription = 13,
|
|
LicenseInfoURL = 14,
|
|
|
|
TypographicFamilyName = 16,
|
|
TypographicSubfamilyName = 17,
|
|
CompatibleFull = 18,
|
|
SampleText = 19,
|
|
PostScriptCIDFindfontName = 20,
|
|
WWSFamilyName = 21,
|
|
WWSSubfamilyName = 22,
|
|
LightBackgroundPalette = 23,
|
|
DarkBackgroundPalette = 24,
|
|
VariationsPostScriptNamePrefix = 25,
|
|
}
|
|
public struct LangTagRecord {
|
|
public UInt16 length;
|
|
public UInt16 langTagOffset;
|
|
public String value { get; private set; }
|
|
public LangTagRecord Load(BinaryReader reader, UInt32 origin) {
|
|
reader.BaseStream.Position = origin + langTagOffset;
|
|
value = Encoding.BigEndianUnicode.GetString(reader.ReadBytes(length));
|
|
return this;
|
|
}
|
|
}
|
|
public sealed class MetaTable : FontTable<DataMap> {
|
|
readonly UInt32 version;
|
|
readonly UInt32 flags;
|
|
readonly UInt32 dataMapCount;
|
|
readonly List<DataMap> dataMaps = new List<DataMap>();
|
|
public MetaTable(BinaryReader reader, UInt32 offset) : base(reader, offset) {
|
|
version = reader.ReadUInt32();
|
|
flags = reader.ReadUInt32();
|
|
reader.ReadUInt32();
|
|
dataMapCount = reader.ReadUInt32();
|
|
for (UInt32 i = 0; i < dataMapCount; i++)
|
|
dataMaps.Add(new DataMap {
|
|
tag = reader.ReadTag(),
|
|
dataOffset = reader.ReadUInt32(),
|
|
dataLength = reader.ReadUInt32(),
|
|
});
|
|
for (int i = 0; i < dataMaps.Count; i++) dataMaps[i] = dataMaps[i].Load(reader, offset);
|
|
}
|
|
public sealed override IReadOnlyList<DataMap> GetItems() {
|
|
return dataMaps;
|
|
}
|
|
}
|
|
public struct DataMap {
|
|
public String tag;
|
|
public UInt32 dataOffset;
|
|
public UInt32 dataLength;
|
|
public String value { get; private set; }
|
|
public DataMap Load(BinaryReader reader, UInt32 origin) {
|
|
reader.BaseStream.Position = origin + dataOffset;
|
|
value = Encoding.ASCII.GetString(reader.ReadBytes((int)dataLength));
|
|
return this;
|
|
}
|
|
}
|
|
public static class BinaryReaderExtensions {
|
|
public static string ReadTag(this BinaryReader reader) {
|
|
return Encoding.ASCII.GetString(reader.ReadBytes(4));
|
|
}
|
|
}
|
|
}
|