private static TrueTypeFont ParseTables(decimal version, IReadOnlyDictionary <string, TrueTypeHeaderTable> tables, TrueTypeDataBytes data) { var isPostScript = tables.ContainsKey(TrueTypeHeaderTable.Cff); var tableRegister = new TableRegister(); if (!tables.TryGetValue(TrueTypeHeaderTable.Head, out var table)) { throw new InvalidOperationException($"The {TrueTypeHeaderTable.Head} table is required."); } // head tableRegister.HeaderTable = HeaderTable.Load(data, table); if (!tables.TryGetValue(TrueTypeHeaderTable.Hhea, out var hHead)) { throw new InvalidOperationException("The horizontal header table is required."); } // hhea tableRegister.HorizontalHeaderTable = HorizontalHeaderTable.Load(data, hHead); if (!tables.TryGetValue(TrueTypeHeaderTable.Maxp, out var maxHeaderTable)) { throw new InvalidOperationException("The maximum profile table is required."); } // maxp tableRegister.MaximumProfileTable = BasicMaximumProfileTable.Load(data, maxHeaderTable); // post if (tables.TryGetValue(TrueTypeHeaderTable.Post, out var postscriptHeaderTable)) { tableRegister.PostScriptTable = PostScriptTable.Load(data, table, tableRegister.MaximumProfileTable); } if (!isPostScript) { if (!tables.TryGetValue(TrueTypeHeaderTable.Loca, out var indexToLocationHeaderTable)) { throw new InvalidOperationException("The location to index table is required for non-PostScript fonts."); } // loca tableRegister.IndexToLocationTable = IndexToLocationTable.Load(data, indexToLocationHeaderTable, tableRegister); if (!tables.TryGetValue(TrueTypeHeaderTable.Glyf, out var glyphHeaderTable)) { throw new InvalidOperationException("The glpyh table is required for non-PostScript fonts."); } // glyf tableRegister.GlyphDataTable = GlyphDataTable.Load(data, glyphHeaderTable, tableRegister); OptionallyParseTables(tables, data, tableRegister); } return(new TrueTypeFont(version, tables, tableRegister)); }
/// <summary> /// Generate a subset of the input font containing only the data required for the glyphs specified in the encoding. /// </summary> public static byte[] Subset(byte[] fontBytes, TrueTypeSubsetEncoding newEncoding) { if (fontBytes == null) { throw new ArgumentNullException(nameof(fontBytes)); } if (newEncoding == null) { throw new ArgumentNullException(nameof(newEncoding)); } var font = TrueTypeFontParser.Parse(new TrueTypeDataBytes(fontBytes)); var indexMapping = GetIndexMapping(font, newEncoding); using (var stream = new MemoryStream()) { var copiedTableTags = new SortedSet <string>(StringComparer.Ordinal); for (var i = 0; i < RequiredTags.Count; i++) { var tag = RequiredTags[i]; if (!font.TableHeaders.ContainsKey(tag)) { throw new InvalidFontFormatException($"Font does not contain table required for subsetting: {tag}."); } copiedTableTags.Add(tag); } for (var i = 0; i < OptionalTags.Count; i++) { var tag = OptionalTags[i]; if (!font.TableHeaders.ContainsKey(tag)) { continue; } copiedTableTags.Add(tag); } var offsetSubtable = new TrueTypeOffsetSubtable((byte)copiedTableTags.Count); offsetSubtable.Write(stream); // The table directory follows the offset subtable. var directoryEntries = new DirectoryEntry[copiedTableTags.Count]; // Entries in the table directory must be sorted in ascending order by tag (case sensitive). // Each table in the font file must have its own table directory entry. var index = 0; foreach (var tag in copiedTableTags) { var entry = new DirectoryEntry(tag, stream.Position, font.TableHeaders[tag]); entry.DummyHeader.Write(stream); directoryEntries[index++] = entry; } // Generate the glyph subset. TrueTypeSubsetGlyphTable trueTypeSubsetGlyphTable = TrueTypeGlyphTableSubsetter.SubsetGlyphTable(font, fontBytes, indexMapping); // Write the actual tables. for (var i = 0; i < directoryEntries.Length; i++) { var entry = directoryEntries[i]; entry.OutputTableOffset = stream.Position; if (entry.Tag == TrueTypeHeaderTable.Cmap) { var cmapTable = GetCMapTable(font, entry, indexMapping); cmapTable.Write(stream); } else if (entry.Tag == TrueTypeHeaderTable.Glyf) { stream.Write(trueTypeSubsetGlyphTable.Bytes, 0, trueTypeSubsetGlyphTable.Bytes.Length); } else if (entry.Tag == TrueTypeHeaderTable.Hmtx) { var hmtx = GetHorizontalMetricsTable(entry, trueTypeSubsetGlyphTable); hmtx.Write(stream); } else if (entry.Tag == TrueTypeHeaderTable.Loca) { var table = new IndexToLocationTable(entry.DummyHeader, IndexToLocationTable.EntryFormat.Long, trueTypeSubsetGlyphTable.GlyphOffsets); table.Write(stream); } else if (entry.Tag == TrueTypeHeaderTable.Head) { // Update indexToLoc format. var headBytes = GetRawInputTableBytes(fontBytes, entry); WriteUShort(headBytes, headBytes.Length - 4, IndexToLocLong); stream.Write(headBytes, 0, headBytes.Length); // TODO: zero out checksum adjustment bytes. } else if (entry.Tag == TrueTypeHeaderTable.Hhea) { // Update number of h metrics. var hheaBytes = GetRawInputTableBytes(fontBytes, entry); WriteUShort(hheaBytes, hheaBytes.Length - 2, (ushort)trueTypeSubsetGlyphTable.HorizontalMetrics.Length); stream.Write(hheaBytes, 0, hheaBytes.Length); } else if (entry.Tag == TrueTypeHeaderTable.Maxp) { // Update number of glyphs. var maxpBytes = GetRawInputTableBytes(fontBytes, entry); WriteUShort(maxpBytes, 4, trueTypeSubsetGlyphTable.GlyphCount); stream.Write(maxpBytes, 0, maxpBytes.Length); } else { // Copy table as-is. var buffer = GetRawInputTableBytes(fontBytes, entry); stream.Write(buffer, 0, buffer.Length); } entry.Length = (uint)(stream.Position - entry.OutputTableOffset); // Tables must start on 4 byte boundaries. var remainder = stream.Position % 4; if (remainder > 0) { var toAppend = 4 - remainder; stream.Write(PaddingBytes, 0, (int)toAppend); } } using (var inputBytes = new StreamInputBytes(stream, false)) { // Update the table directory. for (var i = 0; i < directoryEntries.Length; i++) { var entry = directoryEntries[i]; var actualHeaderExceptChecksum = new TrueTypeHeaderTable(entry.Tag, 0, (uint)entry.OutputTableOffset, entry.Length); var checksum = TrueTypeChecksumCalculator.Calculate(inputBytes, actualHeaderExceptChecksum); var actualHeader = new TrueTypeHeaderTable(entry.Tag, checksum, (uint)entry.OutputTableOffset, entry.Length); stream.Seek(entry.OutputEntryOffset, SeekOrigin.Begin); actualHeader.Write(stream); } } // TODO: whole font checksum. var result = stream.ToArray(); return(result); } }
public GlyphReader(FontFileReader reader) { this.reader = reader; this.glyfEntry = reader.GetDictionaryEntry(TableNames.Glyf); this.loca = reader.GetIndexToLocationTable(); }
private static TrueTypeFont ParseTables(float version, IReadOnlyDictionary <string, TrueTypeHeaderTable> tables, TrueTypeDataBytes data) { var isPostScript = tables.ContainsKey(TrueTypeHeaderTable.Cff); var builder = new TableRegister.Builder(); if (!tables.TryGetValue(TrueTypeHeaderTable.Head, out var table)) { throw new InvalidFontFormatException($"The {TrueTypeHeaderTable.Head} table is required."); } // head builder.HeaderTable = HeaderTable.Load(data, table); if (!tables.TryGetValue(TrueTypeHeaderTable.Hhea, out var hHead)) { throw new InvalidFontFormatException("The horizontal header table is required."); } // hhea builder.HorizontalHeaderTable = TableParser.Parse <HorizontalHeaderTable>(hHead, data, builder); if (!tables.TryGetValue(TrueTypeHeaderTable.Maxp, out var maxHeaderTable)) { throw new InvalidFontFormatException("The maximum profile table is required."); } // maxp builder.MaximumProfileTable = BasicMaximumProfileTable.Load(data, maxHeaderTable); // post if (tables.TryGetValue(TrueTypeHeaderTable.Post, out var postscriptHeaderTable)) { builder.PostScriptTable = PostScriptTable.Load(data, postscriptHeaderTable, builder.MaximumProfileTable); } if (tables.TryGetValue(TrueTypeHeaderTable.Name, out var nameTable)) { builder.NameTable = TableParser.Parse <NameTable>(nameTable, data, builder); } if (tables.TryGetValue(TrueTypeHeaderTable.Os2, out var os2Table)) { builder.Os2Table = TableParser.Parse <Os2Table>(os2Table, data, builder); } if (!isPostScript) { if (!tables.TryGetValue(TrueTypeHeaderTable.Loca, out var indexToLocationHeaderTable)) { throw new InvalidFontFormatException("The location to index table is required for non-PostScript fonts."); } // loca builder.IndexToLocationTable = IndexToLocationTable.Load(data, indexToLocationHeaderTable, builder); if (!tables.TryGetValue(TrueTypeHeaderTable.Glyf, out var glyphHeaderTable)) { throw new InvalidFontFormatException("The glyph table is required for non-PostScript fonts."); } // glyf builder.GlyphDataTable = GlyphDataTable.Load(data, glyphHeaderTable, builder); OptionallyParseTables(tables, data, builder); } return(new TrueTypeFont(version, tables, builder.Build())); }