using System; using System.Collections.Generic; using System.IO; using System.Text; #pragma warning disable IDE0049 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(); #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 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; #pragma warning disable IDE0052 // Reserved readonly UInt16 searchRange; readonly UInt16 entrySelector; readonly UInt16 rangeShift; #pragma warning restore IDE0052 // Reserved readonly List tableRecords = new List(); 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 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( 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 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 { readonly UInt32 version; #pragma warning disable IDE0052 // Reserved readonly UInt32 flags; #pragma warning restore IDE0052 // Reserved readonly UInt32 dataMapCount; readonly List dataMaps = new List(); 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 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)); } } }