Ejemplo n.º 1
0
        private static OldToNewGlyphIndex[] GetIndexMapping(TrueTypeFont font, TrueTypeSubsetEncoding newEncoding)
        {
            var result = new OldToNewGlyphIndex[newEncoding.Characters.Count + 1];

            result[0] = new OldToNewGlyphIndex(0, 0, '\0');

            var previousCMap = font.WindowsUnicodeCMap ?? font.WindowsSymbolCMap ?? font.MacRomanCMap;

            if (previousCMap == null)
            {
                throw new InvalidOperationException("Cannot subset font due to missing cmap subtables.");
            }

            for (var i = 0; i < newEncoding.Characters.Count; i++)
            {
                var character = newEncoding.Characters[i];

                var oldIndex = (ushort)previousCMap.CharacterCodeToGlyphIndex(character);
                result[i + 1] = new OldToNewGlyphIndex(oldIndex, (ushort)(i + 1), character);
            }

            return(result);
        }
Ejemplo n.º 2
0
        /// <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);
            }
        }