forked from jefetienne/daggerfall-unity
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathFntFile.cs
More file actions
317 lines (269 loc) · 9.86 KB
/
FntFile.cs
File metadata and controls
317 lines (269 loc) · 9.86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
// Project: Daggerfall Tools For Unity
// Copyright: Copyright (C) 2009-2020 Daggerfall Workshop
// Web Site: http://www.dfworkshop.net
// License: MIT License (http://www.opensource.org/licenses/mit-license.php)
// Source Code: https://github.com/Interkarma/daggerfall-unity
// Original Author: Gavin Clayton (interkarma@dfworkshop.net)
// Contributors:
//
// Notes:
//
#region Using Statements
using System;
using System.IO;
using DaggerfallConnect.Utility;
#endregion
namespace DaggerfallConnect.Arena2
{
/// <summary>
/// Opens FONT000*.FNT files and reads glyph data.
/// </summary>
public class FntFile
{
#region Fields
public const int MaxGlyphCount = 240; // Fixed number of glyphs in every FNT file
public const int GlyphDataLength = 32; // Fixed data length of each glyph
public const int GlyphFixedDimension = 16; // Fixed pixel dimension of each glyph (i.e. 16x16 pixels)
readonly FileProxy managedFile = new FileProxy();
FntFileData fileData = new FntFileData();
#endregion
#region Structures
//
// .FNT Course File Format
// -Header (4 bytes)
// -Glyph table (4 bytes * 240)
// -Glyph data (32 bytes * 240)
//
/// <summary>
/// Structured FNT file data.
/// </summary>
public struct FntFileData
{
public FileHeader Header;
public GlyphTableEntry[] Glyphs;
}
/// <summary>
/// FNT header data.
/// </summary>
public struct FileHeader
{
public UInt16 FixedWidth; // Fixed width of each glyph
public UInt16 FixedHeight; // Fixed height of each glyph
}
/// <summary>
/// Glyph table entry.
/// Each glyph has 32-bytes of data in 16-bit bitfield format.
/// </summary>
public struct GlyphTableEntry
{
public UInt16 GlyphDataOffset; // Offset to data for this glyph
public UInt16 GlyphWidth; // Actual pixel width of this glyph
public Byte[] GlyphData; // Glyph data at offset
}
#endregion
#region Properties
/// <summary>
/// True if a file is loaded.
/// </summary>
public bool IsLoaded
{
get { return (managedFile.Length > 0); }
}
/// <summary>
/// Gets FNT file data.
/// </summary>
public FntFileData FileData
{
get { return fileData; }
}
/// <summary>
/// Gets fixed glyph width.
/// </summary>
public int FixedWidth
{
get { return fileData.Header.FixedWidth; }
}
/// <summary>
/// Gets fixed glyph height.
/// </summary>
public int FixedHeight
{
get { return fileData.Header.FixedHeight; }
}
#endregion
#region Constructors
/// <summary>
/// Default constructor.
/// </summary>
public FntFile()
{
}
/// <summary>
/// Load constructor.
/// </summary>
/// <param name="filePath">Absolute path to FONT000*.FNT file.</param>
/// <param name="usage">Determines if the FNT file will read from disk or memory.</param>
/// <param name="readOnly">File will be read-only if true, read-write if false.</param>
public FntFile(string filePath, FileUsage usage, bool readOnly)
{
Load(filePath, usage, readOnly);
}
#endregion
#region Public Methods
/// <summary>
/// Loads a FONT000*.FNT file.
/// </summary>
/// <param name="filePath">Absolute path to FONT000*.FNT file.</param>
/// <param name="usage">Determines if the FNT file will read from disk or memory.</param>
/// <param name="readOnly">File will be read-only if true, read-write if false.</param>
/// <returns>True if successful, otherwise false.</returns>
public bool Load(string filePath, FileUsage usage, bool readOnly)
{
const string prefix = "FONT000";
const string suffix = ".FNT";
// Validate filename
string fileName = Path.GetFileName(filePath);
if (!fileName.StartsWith(prefix, StringComparison.InvariantCultureIgnoreCase) ||
!fileName.EndsWith(suffix, StringComparison.InvariantCultureIgnoreCase))
return false;
// Load file
if (!managedFile.Load(filePath, usage, readOnly))
return false;
// Read file
if (!Read())
return false;
return true;
}
/// <summary>
/// Gets actual pixel width of glyph.
/// </summary>
/// <param name="index">Index of glyph.</param>
/// <returns>Pixel width of glyph or -1 on error.</returns>
public int GetGlyphWidth(int index)
{
if (!IsLoaded)
return -1;
if (index < 0 || index >= MaxGlyphCount)
return -1;
return fileData.Glyphs[index].GlyphWidth;
}
/// <summary>
/// Gets raw glyph data bytes.
/// </summary>
/// <param name="index">Index of glyph.</param>
/// <returns>Buffer of GlyphDataLength or null on error.</returns>
public byte[] GetGlyphBytes(int index)
{
if (!IsLoaded)
return null;
if (index < 0 || index >= MaxGlyphCount)
return null;
return fileData.Glyphs[index].GlyphData;
}
/// <summary>
/// Gets fixed glyph pixel 2D array where 0 is "off" and colorIndex (or just non-zero) is "on".
/// Always GlyphFixedDimension*GlyphFixedDimension bytes (i.e. 16x16 bytes).
/// Glyph data aligned to top-left.
/// </summary>
/// <param name="index">Index of glyph.</param>
/// <param name="colorIndex">Palette colour index for "on" pixels.</param>
/// <returns>Glyph pixel array or null on error.</returns>
public byte[] GetGlyphPixels(int index, int colorIndex = 127)
{
if (!IsLoaded)
return null;
if (index < 0 || index >= MaxGlyphCount)
return null;
byte[] bufferIn = fileData.Glyphs[index].GlyphData;
byte[] bufferOut = new byte[GlyphFixedDimension * GlyphFixedDimension];
for (int y = 0; y < GlyphFixedDimension; y++)
{
byte[] rowL = GetPixels(bufferIn[y * 2 + 1], (byte)colorIndex);
byte[] rowR = GetPixels(bufferIn[y * 2], (byte)colorIndex);
Array.Copy(rowL, 0, bufferOut, y * GlyphFixedDimension, rowL.Length);
Array.Copy(rowR, 0, bufferOut, y * GlyphFixedDimension + rowL.Length, rowR.Length);
}
return bufferOut;
}
#endregion
#region Private Methods
/// <summary>
/// Read file.
/// </summary>
/// <returns>True if succeeded, otherwise false.</returns>
private bool Read()
{
fileData = new FntFileData();
try
{
// Step through file
BinaryReader reader = managedFile.GetReader();
ReadHeader(reader);
ReadGlyphs(reader);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
return false;
}
return true;
}
/// <summary>
/// Read ARCH3D.BSA file header.
/// </summary>
/// <param name="reader">A binary reader to file.</param>
private void ReadHeader(BinaryReader reader)
{
reader.BaseStream.Position = 0;
fileData.Header.FixedWidth = reader.ReadUInt16();
fileData.Header.FixedHeight = reader.ReadUInt16();
}
/// <summary>
/// Read glyphs.
/// </summary>
/// <param name="reader">A binary reader to file.</param>
private void ReadGlyphs(BinaryReader reader)
{
fileData.Glyphs = new GlyphTableEntry[MaxGlyphCount];
for (int i = 0; i < MaxGlyphCount; i++)
{
fileData.Glyphs[i].GlyphDataOffset = reader.ReadUInt16();
fileData.Glyphs[i].GlyphWidth = reader.ReadUInt16();
fileData.Glyphs[i].GlyphData = ReadGlyphData(reader, fileData.Glyphs[i].GlyphDataOffset);
}
}
/// <summary>
/// Reads glyph data. This does not change reader position.
/// </summary>
/// <param name="reader">A binary reader to file.</param>
/// <param name="offset">Offset to glyph data.</param>
private byte[] ReadGlyphData(BinaryReader reader, int offset)
{
long savedPosition = reader.BaseStream.Position;
reader.BaseStream.Position = offset;
byte[] data = reader.ReadBytes(GlyphDataLength);
reader.BaseStream.Position = savedPosition;
return data;
}
/// <summary>
/// Gets 8 bytes of pixel data from byte bitfield.
/// </summary>
/// <param name="data">Source data byte for bitfield.</param>
/// <returns>8 bytes of pixel data.</returns>
private byte[] GetPixels(byte data, byte colorIndex)
{
int bit = 1;
byte[] row = new byte[8];
for (int i = 7; i >= 0; i--)
{
if ((data & bit) == bit)
row[i] = colorIndex;
else
row[i] = 0;
bit *= 2;
}
return row;
}
#endregion
}
}