public void Serialize(BinaryWriter writer, OpenTypeFont font)
        {
            DataTypeConverter.WriteFixed(writer, font.SfntVersion);
            DataTypeConverter.WriteUShort(writer, (ushort)font.Tables.Count);

            var pow         = Math.Floor(Math.Log(font.Tables.Count, 2));
            var searchRange = Math.Pow(2, pow) * 16;

            DataTypeConverter.WriteUShort(writer, (ushort)searchRange);
            DataTypeConverter.WriteUShort(writer, (ushort)pow);
            DataTypeConverter.WriteUShort(writer, (ushort)(font.Tables.Count * 16 - searchRange));

            var tableDirectoryStartOffset = writer.BaseStream.Position;

            writer.BaseStream.Position += 4 * DataTypeLength.ULong * font.Tables.Count;

            var entryList      = new List <TableDirectoryEntry>(font.Tables.Count);
            var additionalInfo = new SerializationInfo(); // Shared info among all IOpenTypeFontSerializable class

            foreach (
                var table in font.Tables.OrderByDescending(table => TableDirectoryEntry.GetPriorityOfTag(table.Tag)))
            {
                var startOffsetOfThisTable = writer.BaseStream.Position;

                table.Serialize(writer, startOffsetOfThisTable, additionalInfo);

                entryList.Add(new TableDirectoryEntry
                {
                    Tag    = table.Tag,
                    Offset = (uint)startOffsetOfThisTable,
                    Length = (uint)(writer.BaseStream.Position - startOffsetOfThisTable)
                });

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

            // Write table directory
            var reader = new BinaryReader(writer.BaseStream);

            writer.BaseStream.Position = tableDirectoryStartOffset;
            foreach (var entry in entryList.OrderBy(entry => entry.Tag, StringComparer.Ordinal))
            {
                DataTypeConverter.WriteTag(writer, entry.Tag);
                DataTypeConverter.WriteULong(writer,
                                             EnableChecksum ? CalculateChecksum(reader, entry.Offset, entry.Length) : 0);
                DataTypeConverter.WriteULong(writer, entry.Offset);
                DataTypeConverter.WriteULong(writer, entry.Length);
            }

            // Calculate checksum for the entire font
            if (EnableChecksum)
            {
                writer.BaseStream.Position = entryList.Single(entry => entry.Tag == "head").Offset +
                                             2 * DataTypeLength.Fixed;
                DataTypeConverter.WriteULong(writer,
                                             0xB1B0AFBA - CalculateChecksum(reader, 0, (uint)reader.BaseStream.Length));
            }
        }
        /// <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);
        }