public OpenTypeFont Deserialize(BinaryReader reader)
        {
            var font = new OpenTypeFont {
                SfntVersion = DataTypeConverter.ReadFixed(reader)
            };

            if (!_supportedSfntVersions.Contains(font.SfntVersion))
            {
                throw new NotSupportedException("Bad sfnt version.");
            }

            // Table directory
            var numberOfTables = DataTypeConverter.ReadUShort(reader);

            reader.BaseStream.Position += 3 * DataTypeLength.UShort; // searchRange, entrySelector, rangeShift
            var entryList = Enumerable.Range(0, numberOfTables).Select(i =>
            {
                var entry = new TableDirectoryEntry {
                    Tag = DataTypeConverter.ReadTag(reader)
                };
                reader.BaseStream.Position += DataTypeLength.ULong; // checksum
                entry.Offset = DataTypeConverter.ReadULong(reader);
                entry.Length = DataTypeConverter.ReadULong(reader);
                return(entry);
            }).ToList();

            // Tables
            font.Tables.AddRange(
                entryList.OrderBy(entry => entry.Priority).Select <TableDirectoryEntry, IOpenTypeFontTable>(entry =>
            {
                switch (entry.Tag)
                {
                case "cmap":
                    return(CmapTable.Deserialize(reader, entry.Offset));

                case "head":
                    return(HeadTable.Deserialize(reader, entry.Offset));

                case "maxp":
                    return(MaxpTable.Deserialize(reader, entry.Offset));

                case "loca":
                    return(LocaTable.Deserialize(reader, entry.Offset,
                                                 font.Get <MaxpTable>().NumberOfGlyphs,
                                                 font.Get <HeadTable>().LocaTableVersion));

                case "glyf":
                    return(GlyfTable.Deserialize(reader, entry.Offset, entry.Length,
                                                 font.Get <LocaTable>()));

                case "hhea":
                    return(HheaTable.Deserialize(reader, entry.Offset));

                case "hmtx":
                    return(HmtxTable.Deserialize(reader, entry.Offset, font.Get <HheaTable>().NumberOfHMetrics,
                                                 font.Get <MaxpTable>().NumberOfGlyphs));

                case "post":
                    return(PostTable.Deserialize(reader, entry.Offset));

                default:
                    return(BinaryDataTable.Deserialize(reader, entry.Offset, entry.Length, entry.Tag));
                }
            }));

            return(font);
        }
        /// <summary>
        /// Convert sfnt file to woff file.
        /// </summary>
        /// <param name="reader">BinaryReader to read the sfnt data stream.</param>
        /// <param name="writer">BinaryWriter to write the woff data.</param>
        /// <param name="compression">Set to true to compress data.</param>
        public static void SfntToWoff(BinaryReader reader, BinaryWriter writer, bool compression = false)
        {
            reader.BaseStream.Position = 0;
            writer.BaseStream.Position = 0;

            // Header
            DataTypeConverter.WriteULong(writer, 0x774F4646);     // signature
            writer.Write(reader.ReadBytes(DataTypeLength.Fixed)); // flavor
            writer.BaseStream.Position += DataTypeLength.ULong;   // length
            var numberOfTables = DataTypeConverter.ReadUShort(reader);

            DataTypeConverter.WriteUShort(writer, numberOfTables);                // numTables
            DataTypeConverter.WriteUShort(writer, 0);                             // reserved
            DataTypeConverter.WriteULong(writer, (uint)reader.BaseStream.Length); // totalSfntSize
            DataTypeConverter.WriteUShort(writer, 1);                             // majorVision
            DataTypeConverter.WriteUShort(writer, 0);                             // minorVision
            DataTypeConverter.WriteULong(writer, 0);                              // metaOffset
            DataTypeConverter.WriteULong(writer, 0);                              // metaLength
            DataTypeConverter.WriteULong(writer, 0);                              // metaOriLength
            DataTypeConverter.WriteULong(writer, 0);                              // privOffset
            DataTypeConverter.WriteULong(writer, 0);                              // privLength

            // Read original table directory
            reader.BaseStream.Position += 3 * DataTypeLength.UShort;
            var tags         = new uint[numberOfTables];
            var offsets      = new uint[numberOfTables];
            var compLengths  = new uint[numberOfTables];
            var origLengths  = new uint[numberOfTables];
            var origChecksum = new uint[numberOfTables];
            var origOffsets  = new uint[numberOfTables];
            var startOffsetOfTableDirectory = writer.BaseStream.Position;

            for (var i = 0; i < numberOfTables; i++)
            {
                tags[i]         = DataTypeConverter.ReadULong(reader);
                origChecksum[i] = DataTypeConverter.ReadULong(reader);
                origOffsets[i]  = DataTypeConverter.ReadULong(reader);
                origLengths[i]  = DataTypeConverter.ReadULong(reader);
            }

            // Table data
            writer.BaseStream.Position += numberOfTables * 5 * DataTypeLength.ULong;
            for (var i = 0; i < numberOfTables; i++)
            {
                offsets[i] = (uint)writer.BaseStream.Position;

                reader.BaseStream.Position = origOffsets[i];
                var tableData = reader.ReadBytes((int)origLengths[i]);
                if (compression)
                {
                    using (var compressedStream = new MemoryStream())
                    {
                        var zOutputStream = new ZOutputStream(compressedStream, zlibConst.Z_DEFAULT_COMPRESSION);
                        zOutputStream.Write(tableData, 0, (int)origLengths[i]);
                        zOutputStream.finish();

                        if (compressedStream.Length >= origLengths[i])
                        {
                            writer.Write(tableData);
                        }
                        else
                        {
                            compressedStream.WriteTo(writer.BaseStream);
                        }
                    }
                }
                else
                {
                    writer.Write(tableData);
                }

                compLengths[i] = (uint)(writer.BaseStream.Position - offsets[i]);

                // 4byte padding
                if (writer.BaseStream.Position % 4 != 0)
                {
                    var zeroCount = 4 - writer.BaseStream.Position % 4;
                    for (var j = 0; j < zeroCount; j++)
                    {
                        writer.Write((byte)0);
                    }
                }
            }

            // Write table directory
            writer.BaseStream.Position = startOffsetOfTableDirectory;
            for (var i = 0; i < numberOfTables; i++)
            {
                DataTypeConverter.WriteULong(writer, tags[i]);
                DataTypeConverter.WriteULong(writer, offsets[i]);
                DataTypeConverter.WriteULong(writer, compLengths[i]);
                DataTypeConverter.WriteULong(writer, origLengths[i]);
                DataTypeConverter.WriteULong(writer, origChecksum[i]);
            }

            // Write length in header
            writer.BaseStream.Position = 2 * DataTypeLength.ULong;
            DataTypeConverter.WriteULong(writer, (uint)writer.BaseStream.Length);
        }