/// <summary> /// Convert the specified binary format into a font. /// </summary> /// <returns>The converted font.</returns> /// <param name="source">Binary format.</param> public GameFont Convert(BinaryFormat source) { if (source == null) { throw new ArgumentNullException(nameof(source)); } var reader = new DataReader(source.Stream); var font = new GameFont(); // Main header reader.ReadUInt32(); // header size reader.ReadBytes(4); // Size in memory of some sections reader.ReadBytes(6); // Flags var numGlyphs = reader.ReadUInt16(); font.CharWidth = reader.ReadUInt16(); font.CharHeight = reader.ReadUInt16(); reader.ReadUInt16(); // Pixels per glyph reader.ReadUInt16(); // Depth (1: 4bpp) reader.ReadBytes(8); // Padding // Palette var palette = new Colour[16]; for (int i = 0; i < palette.Length; i++) { palette[i] = new Colour( reader.ReadByte(), reader.ReadByte(), reader.ReadByte()); reader.ReadByte(); // Alpha } font.SetPalette(palette); // Variable Width Table (VWT) int numGlyphInfo = reader.ReadInt32() / 2; // Size var glyphs = new List <Glyph>(numGlyphs); for (int i = 0; i < numGlyphs; i++) { var glyph = new Glyph(); // There is no information about chars but it seems they starts at 0x20 glyph.Char = (char)(i + 0x20); // There may be not information for all the glyphs if (i < numGlyphInfo) { glyph.BearingX = reader.ReadByte(); glyph.Advance = reader.ReadByte(); glyph.Width = glyph.Advance - glyph.BearingX; } glyphs.Add(glyph); } font.SetGlyphs(glyphs); // Reserved space reader.ReadBytes(4 + (4 * numGlyphs)); // Glyphs // ..Header reader.ReadUInt32(); // header size var huffmanTreeSize = reader.ReadInt32(); var compressedDataSize = reader.ReadInt32(); reader.ReadUInt32(); // compressed size in bits var glyphSize = reader.ReadUInt32(); var numImages = reader.ReadUInt32(); reader.ReadInt32(); // glyph position table size reader.ReadUInt32(); // uncompressed font size // ..Huffman Tree reader.ReadBytes(2); // huffman header var huffmanTree = reader.ReadBytes(huffmanTreeSize - 2); // ..Glyph Position Table var glyphPositions = new int[numImages]; for (int i = 0; i < numImages; i++) { glyphPositions[i] = reader.ReadInt32(); } // ..Compressed data var compressedGlyphs = reader.ReadBytes(compressedDataSize); for (int i = 0; i < numImages && i < numGlyphs; i++) { var glyph = font.Glyphs[i]; // Get the decompressed bytes for the glyph var decompressed = new byte[glyphSize * 2]; Huffman.Decompress( huffmanTree, compressedGlyphs, decompressed, glyphPositions[i]); int position = 0; // Convert into array format int[,] glyphImg = new int[font.CharWidth, font.CharHeight]; for (int h = 0; h < font.CharHeight; h++) { for (int w = 0; w < font.CharWidth; w++) { glyphImg[w, h] = decompressed[position++]; } } glyph.SetImage(glyphImg); font.Glyphs[i] = glyph; } return(font); }
/// <summary> /// Convert the specified font into binary format.. /// </summary> /// <returns>Binary representation of the font.</returns> /// <param name="source">Font to convert.</param> public BinaryFormat Convert(GameFont source) { if (source == null) { throw new ArgumentNullException(nameof(source)); } var stream = new DataStream(new MemoryStream(), 0, 0); var writer = new DataWriter(stream); int numGlyphs = source.Glyphs.Count; int pixGlyph = source.CharWidth * source.CharHeight; // Main header // Hard-coded: 4 bpp with flags 0x010101 writer.Write(0x20); writer.Write(0x00); // will set later writer.Write(0x010101); writer.Write((ushort)0x00); writer.Write((ushort)numGlyphs); writer.Write((ushort)source.CharWidth); writer.Write((ushort)source.CharHeight); writer.Write((ushort)(pixGlyph / 2)); writer.Write((ushort)0x01); stream.WriteTimes(0x00, 8); // Palette foreach (var color in source.GetPalette()) { writer.Write((byte)color.Red); writer.Write((byte)color.Green); writer.Write((byte)color.Blue); if (color.Red > 0 && color.Green > 0 && color.Blue > 0) { writer.Write((byte)0x80); } else { writer.Write((byte)0x00); } } // Variable Width Table (VWT) int vwtSize = numGlyphs * 2; writer.Write(vwtSize); foreach (var glyph in source.Glyphs) { writer.Write((byte)glyph.BearingX); writer.Write((byte)glyph.Advance); } // Reserved space stream.WriteTimes(0x00, 4 + (4 * numGlyphs)); // Glyphs long glyphSection = stream.RelativePosition; stream.WriteTimes(0x00, 0x20); // header // Flatten the glyph bytes var rawData = new byte[numGlyphs * pixGlyph / 2]; int rawDataIdx = 0; foreach (var glyph in source.Glyphs) { int[,] glyphImg = glyph.GetImage(); for (int h = 0; h < source.CharHeight; h++) { for (int w = 0; w < source.CharWidth; w++) { if (rawDataIdx % 2 == 0) { rawData[rawDataIdx / 2] = (byte)glyphImg[w, h]; } else { rawData[rawDataIdx / 2] |= (byte)(glyphImg[w, h] << 4); } rawDataIdx++; } } } // Get Huffman tree HuffmanNode huffmanTree = Huffman.MakeTree(rawData); int huffmanTreeSize = Huffman.WriteTree(huffmanTree, stream); var codewords = Huffman.GetCodewords(huffmanTree); // Write empty position table long positionTableSection = stream.RelativePosition; stream.WriteTimes(0x00, numGlyphs * 4L); var compressed = new DataStream(stream, stream.RelativePosition, 0); int compressedPos = 0; rawDataIdx = 0; for (int i = 0; i < numGlyphs; i++) { stream.Seek(positionTableSection + (i * 4L), SeekMode.Origin); writer.Write(compressedPos); Huffman.Compress(codewords, rawData, rawDataIdx, pixGlyph, compressed, ref compressedPos); rawDataIdx += pixGlyph; } // ..Header stream.Seek(glyphSection, SeekMode.Origin); writer.Write(0x20); writer.Write(huffmanTreeSize); writer.Write((uint)compressed.Length); writer.Write(compressedPos); writer.Write(pixGlyph / 2); writer.Write(numGlyphs + 1); writer.Write((numGlyphs + 1) * 4); writer.Write(rawData.Length); compressed.Dispose(); // Set the unknown field size stream.Seek(0x04, SeekMode.Origin); writer.Write((uint)(stream.Length - vwtSize - 7)); return(new BinaryFormat(stream)); }