Repository navigation
Expand file tree
/
Copy pathFont.cs
More file actions
311 lines (298 loc) · 14.1 KB
/
Copy pathFont.cs
File metadata and controls
311 lines (298 loc) · 14.1 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
/*
* Idmr.LfdReader.dll, Library file to read and write LFD resource files
* Copyright (C) 2009-2026 Michael Gaisser (mjgaisser@gmail.com)
* Licensed under the MPL v2.0 or later
*
* Full notice in help/Idmr.LfdReader.chm
* Version: 3.0
*/
/* CHANGE LOG
* v3.0, 260821
* [ADD] Dispose
* [UPD] redid _glyphs as a List instead of an Array
* [UPD] Height and NumberOfGlyphs now write-enabled
* [FIX] Missing type assignment in general ctors
* v2.0, 210309
* [UPD] TotalChars renamed to NumberOfGlyphs
* v1.2, 160712
* [FIX] Fixed EncodeResource writing to array due to large Stride
* [ADD] _isModified edits
* [ADD] _baseLine
* v1.1, 141215
* [UPD] changed license to MPL
* v1.0
*/
using Idmr.Common;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
namespace Idmr.LfdReader
{
/// <summary>Object for "FONT" typeface resources</summary>
/// <remarks>The Font resource controls all of the typefaces used outside of the flight engine. Menus, briefing text, even the scrolling text during the intro.</remarks>
/// <example><h4>Raw Data definition</h4>
/// <code>
/// RawData
/// {
/// /* 0x00 */ short StartingChar; // typically 0x20 (space)
/// /* 0x02 */ short NumberOfGlyphs;
/// /* 0x04 */ short BitsPerScanLine; // multiple of 8
/// /* 0x06 */ short Height;
/// /* 0x08 */ short BaseLine;
/// /* 0x0A */ short Reserved = 0x00;
/// /* 0x0C */ byte[NumberOfGlyphs] GlyphWidths;
/// /* 0x0C + NumberOfGlyphs */ Glyph[NumberOfGlyphs] Glyphs;
/// }
///
/// Glyph
/// {
/// /* 0x00 */ byte[BitsPerScanLine * Height / 8] Rows;
/// }</code>
/// <para>In TIE95 there are a total of seven FONT resources, all of which have a <see cref="StartingChar"/> of <b>0x20</b>, which makes sense since you kinda need a space, and there's nothing really before that anyway.
/// Only "TITLE.LFD:FONThelv-20" has a non-0x60 value for <see cref="NumberOfGlyphs"/>, which is the scrolling text in the title crawl.</para>
/// <para><see cref="BitsPerScanLine"/> tells you how much you have to read before going on to the next line.
/// <u>Must be a multiple of 8</u>.
/// <see cref="Height"/> is just that, how many rows per glyph.
/// <see cref="BaseLine"/> is a zero-indexed row that is defined as the bottom of the glyphs.
/// This is so letters such as 'j' and 'g' hang below the "bottom" as they should.</para>
/// <para>The <i>GlyphWidth</i> values are in pixels, starting from the left of the glyph.</para>
/// <h4>-- Row --</h4>
/// <para>The Row values within the actual glyph data are bit fields. Monochrome bitmaps, so '0' is transparent, '1' is solid.</para>
/// <para>EMPIRE.LFD:FONTfont6 has <i>BitsPerScanLine</i> = <b>0x08</b>, <i>Height</i> = <b>0x06</b> and <i>BaseLine</i> = <b>0x04</b>.
/// The letter 'A' in that resource has its <i>Rows</i> array as <c>E0 A0 E0 A0 A0 00</c>.</para>
/// <para><c>XXX.....</c> E0 = b11100000<br/>
/// <c>X.X.....</c> A0 = b10100000<br/>
/// <c>XXX.....</c> b11100000<br/>
/// <c>X.X.....</c> b10100000<br/>
/// <c>X.X.....</c> b10100000<br/>
/// <c>........</c> b00000000</para>
/// <para>If <i>BitsPerScanLine</i> is <b>0x10</b>, then the rows are 16 pixels wide, <b>0x18</b> is 24 pix wide, etc.
/// The Width value for the letter 'A' in this case is <b>0x03</b>, which as shown above is the occupied width of the character.
/// Spaces between glyphs are automatically counted as 1 pixel.</para></example>
public partial class Font : Resource
{
short _startingChar;
short _bitsPerScanLine;
short _height;
short _baseLine;
List<Bitmap> _glyphs = new List<Bitmap>();
GlyphIndexer _glyphIndexer;
#region constructors
/// <summary>Creates a new instance and prepares for a new character set.</summary>
/// <param name="startChar">First defined ASCII value (normally <b>32</b>).</param>
/// <param name="numberOfChars">Number of characters to be defined.</param>
/// <param name="height">Height of the character set in pixels.</param>
/// <remarks><see cref="BaseLine"/> is set between 2/3 and 3/4 of <paramref name="height"/>.<br/>
/// <see cref="BitsPerScanLine"/> is set to round up from square characters.
/// A <paramref name="height"/> of <b>8</b> produces a maximum width of <b>8</b>,
/// while a <paramref name="height"/> of <b>12</b> produces a width of <b>16</b>.<br/>
/// All images in <see cref="Glyphs"/> are initialized to blank <see cref="PixelFormat.Format1bppIndexed"/> images,
/// (<see cref="BitsPerScanLine"/>, <see cref="Height"/>) in size.</remarks>
public Font(short startChar, short numberOfChars, short height)
{
_type = ResourceType.Font;
_startingChar = startChar;
_height = height;
BaseLine = (short)Math.Ceiling((double)_height * .67);
_bitsPerScanLine = (short)(_height + (_height % 8 == 0 ? 0 : 8 - (_height % 8)));
_glyphs.Capacity = numberOfChars;
for (int i = 0; i < numberOfChars; i++) _glyphs.Add(new Bitmap(_bitsPerScanLine, _height, PixelFormat.Format1bppIndexed));
_glyphIndexer = new GlyphIndexer(this);
}
/// <summary>Creates a new instance and prepares for a new character set starting from ASCII 32 (space).</summary>
/// <param name="numberOfChars">Number of characters to be defined.</param>
/// <param name="height">Height of the character set in pixels.</param>
/// <remarks><see cref="BaseLine"/> is set between 2/3 and 3/4 of <paramref name="height"/>.<br/>
/// <see cref="BitsPerScanLine"/> is set to round up from square characters.
/// A <paramref name="height"/> of <b>8</b> produces a maximum width of <b>8</b>,
/// while a <paramref name="height"/> of <b>12</b> produces a width of <b>16</b>.<br/>
/// All images in <see cref="Glyphs"/> are initialized to blank <see cref="PixelFormat.Format1bppIndexed"/> images,
/// (<see cref="BitsPerScanLine"/>, <see cref="Height"/>) in size.<br/>
/// <see cref="StartingChar"/> defaults to <b>32</b>.</remarks>
public Font(short numberOfChars, short height) : this(32, numberOfChars, height) { }
/// <summary>Creates a new instance from an existing opened file.</summary>
/// <param name="stream">The opened LFD file.</param>
/// <param name="filePosition">The offset of the beginning of the resource.</param>
/// <exception cref="LoadFileException">Typically due to file corruption.</exception>
public Font(FileStream stream, long filePosition) => read(stream, filePosition);
/// <summary>Creates a new instance from an exsiting file.</summary>
/// <param name="path">The full path to the unopened LFD file.</param>
/// <param name="filePosition">The offset of the beginning of the resource.</param>
/// <exception cref="LoadFileException">Typically due to file corruption.</exception>
public Font(string path, long filePosition)
{
FileStream fsLFD = File.OpenRead(path);
read(fsLFD, filePosition);
fsLFD.Close();
}
#endregion constructors
void read(FileStream stream, long filePosition)
{
try { _process(stream, filePosition); }
catch (Exception x) { throw new LoadFileException(x); }
}
/// <summary>Clean up any resources being used.</summary>
/// <param name="disposing"><see langword="true"/> if managed resources should be disposed; otherwise, <see langword="false"/>.</param>
protected override void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing)
{
for (int i = 0; i < _glyphs.Count; i++) _glyphs[i].Dispose();
_glyphs.Clear();
}
_glyphs = null;
_glyphIndexer = null;
base.Dispose(disposing);
}
#region public methods
/// <summary>Processes raw data to populate the resource.</summary>
/// <param name="raw">Raw byte data.</param>
/// <param name="containsHeader">Whether or not <paramref name="raw"/> contains the resource Header information.</param>
/// <exception cref="ArgumentException">Header-defined <see cref="Type"/> is not <see cref="Resource.ResourceType.Font"/>.</exception>
public override void DecodeResource(byte[] raw, bool containsHeader)
{
_decodeResource(raw, containsHeader);
if (_type != ResourceType.Font) throw new ArgumentException("Raw header is not for a Font resource");
int offset = 0;
_startingChar = BitConverter.ToInt16(_rawData, offset);
_glyphs.Clear();
_glyphs.Capacity = BitConverter.ToInt16(_rawData, offset + 2);
_bitsPerScanLine = BitConverter.ToInt16(_rawData, offset + 4);
_height = BitConverter.ToInt16(_rawData, offset + 6);
_baseLine = BitConverter.ToInt16(_rawData, offset + 8);
offset += 12;
for (int i = 0; i < _glyphs.Capacity; i++)
_glyphs.Add(new Bitmap(_rawData[offset++], _height, PixelFormat.Format1bppIndexed));
foreach (var g in _glyphs)
{
BitmapData bd1 = GraphicsFunctions.GetBitmapData(g);
byte[] pix1 = new byte[bd1.Stride * bd1.Height];
for (int y = 0; y < _height; y++)
for (int s = 0; s < (_bitsPerScanLine / 8); s++)
pix1[y * bd1.Stride + s] = _rawData[offset++];
GraphicsFunctions.CopyBytesToImage(pix1, bd1);
g.UnlockBits(bd1);
}
_glyphIndexer = new GlyphIndexer(this);
_isModified = false;
}
/// <summary>Prepares the resource for writing and updates <see cref="Resource.RawData"/>.</summary>
public override void EncodeResource()
{
System.Diagnostics.Debug.WriteLine("bit width: " + _bitsPerScanLine + ", height: " + _height + "num glyphs: " + _glyphs.Count);
byte[] raw = new byte[12 + (_bitsPerScanLine / 8 * _height + 1) * _glyphs.Count];
ArrayFunctions.WriteToArray(_startingChar, raw, 0);
ArrayFunctions.WriteToArray((short)_glyphs.Count, raw, 2);
ArrayFunctions.WriteToArray(_bitsPerScanLine, raw, 4);
ArrayFunctions.WriteToArray(_height, raw, 6);
ArrayFunctions.WriteToArray(BaseLine, raw, 8);
for (int i = 0; i < _glyphs.Count; i++) raw[12 + i] = (byte)_glyphs[i].Width;
for (int i = 0, offset = 12 + _glyphs.Count; i < _glyphs.Count; i++)
{
BitmapData bd1 = GraphicsFunctions.GetBitmapData(_glyphs[i]);
byte[] pix1 = new byte[bd1.Stride * bd1.Height];
GraphicsFunctions.CopyImageToBytes(bd1, pix1);
// BitmapData.Stride rounds up to 4, so we have to trim off the excess bytes
byte[] trimmed = new byte[_height * _bitsPerScanLine / 8];
for (int y = 0; y < _height; y++)
for (int x = 0; x < (_bitsPerScanLine / 8); x++)
trimmed[x + y * (_bitsPerScanLine / 8)] = pix1[x + y * bd1.Stride];
System.Diagnostics.Debug.WriteLine("pix1.Length: " + pix1.Length + ", trimmed.Length: " + trimmed.Length + ", offset: " + offset);
ArrayFunctions.WriteToArray(trimmed, raw, ref offset);
_glyphs[i].UnlockBits(bd1);
}
_rawData = raw;
}
/// <summary>Changes the primary color of the font.</summary>
/// <param name="glyphColor">The new font color.</param>
/// <remarks>Background color will be <see cref="Color.Transparent"/>.<br/>
/// These colors are for display purposes only and do not affect the data.</remarks>
public void SetColor(Color glyphColor) => SetColor(glyphColor, true);
/// <summary>Changes the primary color of the font.</summary>
/// <param name="glyphColor">The new font color.</param>
/// <param name="transparent">If the background will be <see cref="Color.Transparent"/>.</param>
/// <remarks>If <paramref name="transparent"/> is <see langword="false"/>, the background color will be <see cref="Color.Black"/>.<br/>
/// If <paramref name="glyphColor"/> is Black, then the background will be <see cref="Color.White"/>.<br/>
/// These colors are for display purposes only and do not affect the data.</remarks>
public void SetColor(Color glyphColor, bool transparent)
{
ColorPalette newpal = _glyphs[0].Palette;
if (transparent) newpal.Entries[0] = Color.Transparent;
else
{
if (glyphColor == Color.Black) newpal.Entries[0] = Color.White;
else newpal.Entries[0] = Color.Black;
}
newpal.Entries[1] = glyphColor;
foreach (var g in _glyphs) g.Palette = newpal;
}
#endregion public methods
#region public properties
/// <summary>Gets the indexer for the glyphs.</summary>
public GlyphIndexer Glyphs => _glyphIndexer;
/// <summary>Gets the ASCII value of the first character within the resource.</summary>
/// <remarks>Realistically should always be <b>32</b> (space).</remarks>
public short StartingChar => _startingChar;
/// <summary>Gets the number of characters contained within the resource.</summary>
public short NumberOfGlyphs
{
get => (short)_glyphs.Count;
set
{
// only one of these will fire
for (int i = _glyphs.Count; i < value; i++) _glyphs.Add(new Bitmap(_bitsPerScanLine, _height, PixelFormat.Format1bppIndexed));
for (int i = _glyphs.Count; i > value; i--) _glyphs.RemoveAt(i - 1);
_isModified = true;
}
}
/// <summary>Gets or sets the length of bits required per scanline.</summary>
/// <remarks>Must be a multiple of <b>8</b>.</remarks>
/// <exception cref="ArgumentException"><i>value</i> is not a positive multiple of 8.</exception>
public short BitsPerScanLine
{
get => _bitsPerScanLine;
set
{
if ((value % 8) != 0 || value <= 0) throw new ArgumentException("Value must be a positive multiple of 8", "value");
_bitsPerScanLine = value;
_isModified = true;
} // this is left as write-enabled to allow wider characters
}
/// <summary>Gets or sets the total height of the font, also number of ScanLines.</summary>
/// <remarks>Does replace all images due to the global height change.</remarks>
public short Height
{
get => _height;
set
{
_height = value;
Bitmap newGlyph;
Graphics g;
for (int i = 0; i < NumberOfGlyphs; i++)
{
newGlyph = new Bitmap(_glyphs[i].Width, _height);
g = Graphics.FromImage(newGlyph);
g.Clear(Color.Black);
g.DrawImageUnscaled(_glyphs[i], 0, 0);
_glyphs[i] = newGlyph;
// that ^ sets _isModified
}
}
}
/// <summary>Gets or sets the zero-indexed ScanLine that is used as the "bottom" of the font.</summary>
/// <remarks>Characters such as 'j' typically drop below this line. Is typically 2/3 to 3/4 the value of <see cref="Height"/>.</remarks>
public short BaseLine
{
get => _baseLine;
set
{
_baseLine = value;
_isModified = true;
}
}
#endregion public properties
}
}