Files
crtr/Assets/Cryville/Common/Font/FontTable.cs

255 lines
8.5 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
#pragma warning disable IDE0049
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();
#pragma warning disable IDE0052 // Reserved
readonly String dsigTag;
readonly UInt32 dsigLength;
readonly UInt32 dsigOffset;
#pragma warning restore IDE0052 // Reserved
public TTCHeader(BinaryReader reader, UInt32 offset) : base(reader, offset) {
ttcTag = reader.ReadTag();
if (ttcTag != "ttcf") throw new NotSupportedException();
majorVersion = reader.ReadUInt16();
minorVersion = reader.ReadUInt16();
if (minorVersion != 0) throw new NotSupportedException();
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;
#pragma warning disable IDE0052 // Reserved
readonly UInt16 searchRange;
readonly UInt16 entrySelector;
readonly UInt16 rangeShift;
#pragma warning restore IDE0052 // Reserved
readonly List<TableRecord> tableRecords = new();
public TableDirectory(BinaryReader reader, UInt32 offset) : base(reader, offset) {
sfntVersion = reader.ReadUInt32();
if (sfntVersion != 0x00010000 && sfntVersion != 0x4F54544F &&
sfntVersion != 0x74727565 && sfntVersion != 0x74797031) throw new NotSupportedException();
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) => item.tableTag switch {
"name" => new NameTable(Reader, item.offset),
"meta" => new MetaTable(Reader, item.offset),
_ => 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();
readonly UInt16 langTagCount;
readonly List<LangTagRecord> langTagRecord = new();
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(
reader.ReadUInt16(),
reader.ReadUInt16(),
reader.ReadUInt16(),
(NameID)reader.ReadUInt16(),
reader.ReadUInt16(),
reader.ReadUInt16()
));
if (version == 1) {
langTagCount = reader.ReadUInt16();
for (UInt16 i = 0; i < langTagCount; i++)
langTagRecord.Add(new LangTagRecord(
reader.ReadUInt16(),
reader.ReadUInt16()
));
}
foreach (var i in nameRecord)
i.Load(reader, offset + storageOffset);
if (version == 1) {
foreach (var i in langTagRecord)
i.Load(reader, offset + storageOffset);
}
}
public sealed override IReadOnlyList<NameRecord> GetItems() {
return nameRecord;
}
}
public class NameRecord {
public UInt16 PlatformID { get; private set; }
public UInt16 EncodingID { get; private set; }
public UInt16 LanguageID { get; private set; }
public NameID NameID { get; private set; }
public UInt16 Length { get; private set; }
public UInt16 StringOffset { get; private set; }
public String Value { get; private set; }
public NameRecord(UInt16 platformID, UInt16 encodingID, UInt16 languageID, NameID nameID, UInt16 length, UInt16 stringOffset) {
PlatformID = platformID;
EncodingID = encodingID;
LanguageID = languageID;
NameID = nameID;
Length = length;
StringOffset = stringOffset;
}
public void Load(BinaryReader reader, UInt32 origin) {
reader.BaseStream.Position = origin + StringOffset;
Encoding encoding;
try {
switch (PlatformID) {
case 0: encoding = Encoding.BigEndianUnicode; break;
case 1: encoding = Encoding.GetEncoding(10000 + EncodingID); break;
case 3: encoding = Encoding.BigEndianUnicode; break;
default: return;
}
}
catch (NotSupportedException) { return; }
catch (ArgumentException) { return; }
Value = encoding.GetString(reader.ReadBytes(Length));
}
}
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 class LangTagRecord {
public UInt16 Length { get; private set; }
public UInt16 LangTagOffset { get; private set; }
public String Value { get; private set; }
public LangTagRecord(UInt16 length, UInt16 langTagOffset) {
Length = length;
LangTagOffset = langTagOffset;
}
public void Load(BinaryReader reader, UInt32 origin) {
reader.BaseStream.Position = origin + LangTagOffset;
Value = Encoding.BigEndianUnicode.GetString(reader.ReadBytes(Length));
}
}
public sealed class MetaTable : FontTable<DataMap> {
readonly UInt32 version;
#pragma warning disable IDE0052 // Reserved
readonly UInt32 flags;
#pragma warning restore IDE0052 // Reserved
readonly UInt32 dataMapCount;
readonly List<DataMap> dataMaps = new();
public MetaTable(BinaryReader reader, UInt32 offset) : base(reader, offset) {
version = reader.ReadUInt32();
if (version != 1) throw new NotSupportedException();
flags = reader.ReadUInt32();
reader.ReadUInt32();
dataMapCount = reader.ReadUInt32();
for (UInt32 i = 0; i < dataMapCount; i++)
dataMaps.Add(new DataMap (
reader.ReadTag(),
reader.ReadUInt32(),
reader.ReadUInt32()
));
foreach (var i in dataMaps)
i.Load(reader, offset);
}
public sealed override IReadOnlyList<DataMap> GetItems() {
return dataMaps;
}
}
public class DataMap {
public String Tag { get; private set; }
public UInt32 DataOffset { get; private set; }
public UInt32 DataLength { get; private set; }
public String Value { get; private set; }
public DataMap(String tag, UInt32 dataOffset, UInt32 dataLength) {
Tag = tag;
DataOffset = dataOffset;
DataLength = dataLength;
}
public void Load(BinaryReader reader, UInt32 origin) {
reader.BaseStream.Position = origin + DataOffset;
Value = Encoding.ASCII.GetString(reader.ReadBytes((int)DataLength));
}
}
public static class BinaryReaderExtensions {
public static string ReadTag(this BinaryReader reader) {
return Encoding.ASCII.GetString(reader.ReadBytes(4));
}
}
}