From 65dc96c4d60bb344b7ba6cad0071624d24e17694 Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Sun, 21 Feb 2021 01:56:23 -0700 Subject: [PATCH] Add font pack support --- LibDgf/Font/FontPack.cs | 171 ++++++++++++++++++++++++++++++++++++++++ README.md | 1 + 2 files changed, 172 insertions(+) create mode 100644 LibDgf/Font/FontPack.cs diff --git a/LibDgf/Font/FontPack.cs b/LibDgf/Font/FontPack.cs new file mode 100644 index 0000000..82e4905 --- /dev/null +++ b/LibDgf/Font/FontPack.cs @@ -0,0 +1,171 @@ +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); + } + } + } + } +} diff --git a/README.md b/README.md index 2f999c8..c9a2aad 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ Currently implemented **Final** - DAT: file packs +- font/*.pak: font pack - Sound effects catalog (read support only) - TXM: texture files