using LibDgf.Dat; using System; using System.Collections.Generic; using System.IO; using System.Text; namespace LibDgf.Font { public class FontPack { const int WIDTH = 24; const int HEIGHT = 22; const int PADDING = 16; Dictionary characterMap = new Dictionary(); public int Count => characterMap.Count; public IReadOnlyCollection Characters => characterMap.Keys; public byte[] this[char ch] { get { return characterMap[ch]; } set { if (value == null) throw new ArgumentNullException(nameof(value)); if (value.Length != (WIDTH * HEIGHT + PADDING) / 2) throw new ArgumentException("Pixels has incorrect length.", nameof(value)); characterMap[ch] = value; } } public bool Remove(char ch) { return characterMap.Remove(ch); } public void Clear() { characterMap.Clear(); } public void Read(Stream stream) { Encoding shiftJisEncoding = Encoding.GetEncoding(932); Clear(); DatReader dat = new DatReader(stream); using (MemoryStream ms = new MemoryStream(dat.GetData(0))) { BinaryReader br = new BinaryReader(ms); uint infoSize = br.ReadUInt32(); uint[] numChars = new uint[4]; // numChars[0] is dummy for (int i = 1; i < numChars.Length; ++i) { numChars[i] = br.ReadUInt32(); } byte[] chBytes = new byte[2]; for (int i = 1; i < numChars.Length; ++i) { using MemoryStream graphicsMs = new MemoryStream(dat.GetData(i)); BinaryReader graphicsBr = new BinaryReader(graphicsMs); var numCharInSection = numChars[i]; for (int j = 0; j < numCharInSection; ++j) { // Characters are stored as 16-bit little-endian values chBytes[1] = br.ReadByte(); chBytes[0] = br.ReadByte(); char ch = shiftJisEncoding.GetChars(chBytes)[0]; byte[] pixels = graphicsBr.ReadBytes((WIDTH * HEIGHT + PADDING) / 2); // 4bpp characterMap.Add(ch, pixels); } } } } public void Write(Stream stream) { Encoding shiftJisEncoding = Encoding.GetEncoding(932); List tempFiles = new List(); try { ushort[] charRanges = new ushort[] { 0x0, 0x889f, 0x989f }; // General, Kanji 1, Kanji 2 uint[] charCounts = new uint[charRanges.Length]; // Build mapping from Unicode to Shift-JIS List> encodedMapping = new List>(); char[] chArray = new char[1]; foreach (var ch in Characters) { chArray[0] = ch; byte[] encodedBytes = shiftJisEncoding.GetBytes(chArray); ushort encoded = (ushort)((encodedBytes[0] << 8) | encodedBytes[1]); encodedMapping.Add(new Tuple(ch, encoded)); } encodedMapping.Sort((x, y) => x.Item2.CompareTo(y.Item2)); string infoFsPath = Path.GetTempFileName(); tempFiles.Add(infoFsPath); int currentRange = -1; FileStream currentRangeFs = null; BinaryWriter currentRangeBw = null; DatBuilder datBuilder = new DatBuilder(); datBuilder.ReplacementEntries.Add(new DatBuilder.ReplacementEntry { Index = 0, SourceFile = infoFsPath }); using (FileStream infoFs = File.Create(infoFsPath)) { BinaryWriter infoBw = new BinaryWriter(infoFs); infoBw.Write(new byte[4 + 4 * charCounts.Length]); // Dummy header try { foreach (var pair in encodedMapping) { // Advance range if char matches next range's start if (currentRange < charRanges.Length - 1 && pair.Item2 >= charRanges[currentRange + 1]) { string path = Path.GetTempFileName(); tempFiles.Add(path); if (currentRangeFs != null) currentRangeFs.Close(); currentRangeFs = File.Create(path); currentRangeBw = new BinaryWriter(currentRangeFs); ++currentRange; datBuilder.ReplacementEntries.Add(new DatBuilder.ReplacementEntry { Index = currentRange + 1, SourceFile = path }); } infoBw.Write(pair.Item2); currentRangeBw.Write(this[pair.Item1]); ++charCounts[currentRange]; } } finally { if (currentRangeFs != null) currentRangeFs.Close(); } uint length = (uint)infoFs.Length; // Pad to nearest 16 bytes var paddedLength = (length + 15) & ~15; infoBw.Write(new byte[paddedLength - length]); infoFs.Seek(0, SeekOrigin.Begin); infoBw.Write(length); foreach (var count in charCounts) { infoBw.Write(count); } } datBuilder.Build(stream); } finally { foreach (var file in tempFiles) { File.Delete(file); } } } } }