using System; using System.Collections.Generic; using System.IO; using System.Text; namespace Cryville.Common.Font { public abstract class FontTable { 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 GetItems(); } public abstract class FontTable : FontTable { protected FontTable(BinaryReader reader, UInt32 offset) : base(reader, offset) { } public abstract U GetSubTable(T item); } public sealed class TTCHeader : FontTable { readonly String ttcTag; readonly UInt16 majorVersion; readonly UInt16 minorVersion; readonly UInt32 numFonts; readonly List tableDirectoryOffsets = new List(); 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 GetItems() { return tableDirectoryOffsets; } public override TableDirectory GetSubTable(UInt32 item) { var i = item; return new TableDirectory(Reader, i); } } public sealed class TableDirectory : FontTable { readonly UInt32 sfntVersion; readonly UInt16 numTables; readonly UInt16 searchRange; readonly UInt16 entrySelector; readonly UInt16 rangeShift; readonly List tableRecords = new List(); 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 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 { readonly UInt16 version; readonly UInt16 count; readonly UInt16 storageOffset; readonly List nameRecord = new List(); readonly UInt16 langTagCount; readonly List langTagRecord = new List(); 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 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 { readonly UInt32 version; readonly UInt32 flags; readonly UInt32 dataMapCount; readonly List dataMaps = new List(); 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 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)); } } }