171 lines
6.4 KiB
C#
171 lines
6.4 KiB
C#
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<char, byte[]> characterMap = new Dictionary<char, byte[]>();
|
|
|
|
public int Count => characterMap.Count;
|
|
public IReadOnlyCollection<char> 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<string> tempFiles = new List<string>();
|
|
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<Tuple<char, ushort>> encodedMapping = new List<Tuple<char, ushort>>();
|
|
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<char, ushort>(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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|