File: FontManager.cpp

package info (click to toggle)
freespace2 25.0.0~rc11%2Brepack-1
  • links: PTS, VCS
  • area: non-free
  • in suites: forky, sid
  • size: 47,232 kB
  • sloc: cpp: 657,500; ansic: 22,305; sh: 293; python: 200; makefile: 198; xml: 181
file content (531 lines) | stat: -rw-r--r-- 15,776 bytes parent folder | download
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
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531


#include "graphics/software/FontManager.h"
#include "graphics/software/VFNTFont.h"
#include "graphics/software/NVGFont.h"
#include "graphics/software/font.h"
#include "graphics/software/font_internal.h"

#include "graphics/paths/PathRenderer.h"

#include "bmpman/bmpman.h"
#include "cfile/cfile.h"
#include "localization/localize.h"
#include "options/Option.h"

namespace font
{
	SCP_map<SCP_string, TrueTypeFontData> FontManager::allocatedData;
	SCP_map<SCP_string, std::unique_ptr<font>> FontManager::vfntFontData;
	SCP_vector<std::unique_ptr<FSFont>> FontManager::fonts;

	int FontManager::currentFontIndex = -1;

	FSFont* FontManager::getFont(const SCP_string& name)
	{
		for (auto &iter: fonts)
		{
			if (lcase_equal(iter->getName(), name))
				return iter.get();
		}

		return nullptr;
	}

	FSFont* FontManager::getFontByFilename(const SCP_string& filename)
	{
		for (auto &iter: fonts)
		{
			if (lcase_equal(iter->getFilename(), filename))
				return iter.get();
		}

		return nullptr;
	}

	FSFont* FontManager::getFont(int index)
	{
		if (!isFontNumberValid(index))
			return nullptr;

		return fonts[index].get();
	}

	FSFont *FontManager::getCurrentFont()
	{
		int id = gr_lua_context_active() ? gr_lua_screen.current_font_index : currentFontIndex;
		return getFont(id);
	}

	int FontManager::getCurrentFontIndex()
	{
		int id = gr_lua_context_active() ? gr_lua_screen.current_font_index : currentFontIndex;
		if (!isFontNumberValid(id))
			return -1;

		return id;
	}

	int FontManager::getFontIndex(const SCP_string& name)
	{
		int index = 0;

		for (const auto &iter: fonts)
		{
			if (lcase_equal(iter->getName(), name))
				return index;
			index++;
		}

		return -1;
	}

	int FontManager::getFontIndexByFilename(const SCP_string& filename)
	{
		int index = 0;

		for (const auto &iter: fonts)
		{
			if (lcase_equal(iter->getFilename(), filename))
				return index;
			index++;
		}

		return -1;
	}

	bool FontManager::hasScalingFonts()
	{
		return std::any_of(fonts.begin(), fonts.end(), [](const std::unique_ptr<FSFont>& font) {
			const auto& thisFont = font.get();
			return thisFont->getScaleBehavior();
		});
	}

	int FontManager::numberOfFonts()
	{
		return static_cast<int>(fonts.size());
	}

	bool FontManager::isReady()
	{
		return isFontNumberValid(currentFontIndex);
	}

	bool FontManager::isFontNumberValid(int id)
	{
		return SCP_vector_inbounds(fonts, id);
	}

	void FontManager::setCurrentFontIndex(int id)
	{
		Assertion(isFontNumberValid(id), "New font index must be valid!");
		if (gr_lua_context_active()) {
			gr_lua_screen.current_font_index = id;
		} else {
			currentFontIndex = id;
		}
	}

	font* FontManager::loadFontOld(const SCP_string& typeface)
	{
		auto iter = vfntFontData.find(typeface);
		if (iter != vfntFontData.end())
		{
			font* data = iter->second.get();

			Assert(data != nullptr);

			return data;
		}

		// try localized version first
		CFILE* fp = nullptr;
		SCP_string typeface_lcl = typeface;

		lcl_add_dir_to_path_with_filename(typeface_lcl);

		fp = cfopen(typeface_lcl.c_str(), "rb", CF_TYPE_ANY);

		// fallback if not found
		if ( !fp )
		{
			fp = cfopen(typeface.c_str(), "rb", CF_TYPE_ANY);
		}

		if ( !fp )
		{
			mprintf(("Unable to find font file \"%s\"\n", typeface.c_str()));
			return nullptr;
		}

		std::unique_ptr<font> fnt(new font());

		if (typeface.size() <= MAX_FILENAME_LEN - 1)
			strcpy_s(fnt->filename, typeface.c_str());
		else
		{
			strncpy_s(fnt->filename, typeface.c_str(), MAX_FILENAME_LEN - 1);
			fnt->filename[MAX_FILENAME_LEN - 1] = '\0';
		}

		cfread(&fnt->id, 4, 1, fp);
		cfread(&fnt->version, sizeof(int), 1, fp);
		cfread(&fnt->num_chars, sizeof(int), 1, fp);
		cfread(&fnt->first_ascii, sizeof(int), 1, fp);
		cfread(&fnt->w, sizeof(int), 1, fp);
		cfread(&fnt->h, sizeof(int), 1, fp);
		cfread(&fnt->num_kern_pairs, sizeof(int), 1, fp);
		cfread(&fnt->kern_data_size, sizeof(int), 1, fp);
		cfread(&fnt->char_data_size, sizeof(int), 1, fp);
		cfread(&fnt->pixel_data_size, sizeof(int), 1, fp);

		fnt->id = INTEL_SHORT(fnt->id); //-V570
		fnt->version = INTEL_INT(fnt->version); //-V570
		fnt->num_chars = INTEL_INT(fnt->num_chars); //-V570
		fnt->first_ascii = INTEL_INT(fnt->first_ascii); //-V570
		fnt->w = INTEL_INT(fnt->w); //-V570
		fnt->h = INTEL_INT(fnt->h); //-V570
		fnt->num_kern_pairs = INTEL_INT(fnt->num_kern_pairs); //-V570
		fnt->kern_data_size = INTEL_INT(fnt->kern_data_size); //-V570
		fnt->char_data_size = INTEL_INT(fnt->char_data_size); //-V570
		fnt->pixel_data_size = INTEL_INT(fnt->pixel_data_size); //-V570

		if (fnt->kern_data_size)	{
			fnt->kern_data = new font_kernpair[fnt->kern_data_size];
			cfread(fnt->kern_data, fnt->kern_data_size, 1, fp);
		}
		else {
			fnt->kern_data = nullptr;
		}
		if (fnt->char_data_size)	{
			fnt->char_data = new font_char[fnt->char_data_size];
			cfread(fnt->char_data, fnt->char_data_size, 1, fp);

			for (int i = 0; i<fnt->num_chars; i++) {
				fnt->char_data[i].spacing = INTEL_INT(fnt->char_data[i].spacing); //-V570
				fnt->char_data[i].byte_width = INTEL_INT(fnt->char_data[i].byte_width); //-V570
				fnt->char_data[i].offset = INTEL_INT(fnt->char_data[i].offset); //-V570
				fnt->char_data[i].kerning_entry = INTEL_INT(fnt->char_data[i].kerning_entry); //-V570
				fnt->char_data[i].user_data = INTEL_SHORT(fnt->char_data[i].user_data); //-V570
			}
		}
		else {
			fnt->char_data = nullptr;
		}
		if (fnt->pixel_data_size)	{
			fnt->pixel_data = new ubyte[fnt->pixel_data_size];
			cfread(fnt->pixel_data, fnt->pixel_data_size, 1, fp);
		}
		else {
			fnt->pixel_data = nullptr;
		}
		cfclose(fp);

		// Create a bitmap for hardware cards.
		// JAS:  Try to squeeze this into the smallest square power of two texture.
		// This should probably be done at font generation time, not here.
		int w, h;
		if (fnt->pixel_data_size * 4 < 64 * 64) {
			w = h = 64;
		}
		else if (fnt->pixel_data_size * 4 < 128 * 128) {
			w = h = 128;
		}
		else if (fnt->pixel_data_size * 4 < 256 * 256) {
			w = h = 256;
		}
		else if (fnt->pixel_data_size * 4 < 512 * 512) {
			w = h = 512;
		}
		else {
			w = h = 1024;
		}

		fnt->bm_w = w;
		fnt->bm_h = h;
		fnt->bm_data = new ubyte[fnt->bm_w * fnt->bm_h];
		fnt->bm_u = new int[fnt->num_chars];
		fnt->bm_v = new int[fnt->num_chars];

		memset(fnt->bm_data, 0, fnt->bm_w * fnt->bm_h);

		int i, x, y;
		x = y = 0;
		for (i = 0; i<fnt->num_chars; i++)	{
			ubyte * ubp;
			int x1, y1;
			ubp = &fnt->pixel_data[fnt->char_data[i].offset];
			if (x + fnt->char_data[i].byte_width >= fnt->bm_w)	{
				x = 0;
				y += fnt->h + 2;
				if (y + fnt->h > fnt->bm_h) {
					Warning(LOCATION, "Font %s too big!\n", fnt->filename);
					return nullptr;
				}
			}
			fnt->bm_u[i] = x;
			fnt->bm_v[i] = y;

			for (y1 = 0; y1<fnt->h; y1++)	{
				for (x1 = 0; x1<fnt->char_data[i].byte_width; x1++)	{
					uint c = *ubp++;
					if (c > 14) c = 14;
					// The font pixels only have ~4 bits of information in them (since the value is at maximum 14) but
					// the bmpman code expects 8 bits of pixel information. To fix that we simply rescale this value to
					// fit into the [0, 255] range (15 * 17 is 255). This was adapted from the previous version where
					// the graphics code used an internal array for converting these values
					fnt->bm_data[(x + x1) + (y + y1)*fnt->bm_w] = (unsigned char)(c * 17);
				}
			}
			x += fnt->char_data[i].byte_width + 2;
		}

		fnt->bitmap_id = bm_create(8, fnt->bm_w, fnt->bm_h, fnt->bm_data, BMP_AABITMAP);

		auto ptr = fnt.get();

		vfntFontData.emplace(typeface, std::move(fnt));

		return ptr;
	}

	std::pair<VFNTFont*, int> FontManager::loadVFNTFont(const SCP_string& name)
	{
		font* font = FontManager::loadFontOld(name);

		if (font == nullptr)
		{
			return { nullptr, -1 };
		}
		else
		{
			std::unique_ptr<VFNTFont> vfnt(new VFNTFont(font));

			auto ptr = vfnt.get();

			fonts.emplace_back(std::move(vfnt));

			return { ptr, static_cast<int>(fonts.size() - 1) };
		}
	}

	// This function will extract the font family name (Name ID 1) from TrueType font data.
	// It handles UCS-2 (UTF-16BE) to UTF-8 conversion.
	static SCP_string extractFamilyNameFromTTF(const TrueTypeFontData& fontData)
	{
		try {
			// TTF/OTF fonts start the table directory at byte 12.
			constexpr size_t table_offset = 12;

			const ubyte* data = fontData.data.get();
			const size_t size = fontData.size;

			if (size < table_offset) {
				throw std::runtime_error("Font data too small for table offset");
			}

			// Offset to the start of the table directory (after SFNT header)
			const uint8_t* tableDir = data + table_offset;

			// Read numTables (ushort at offset 4 in SFNT header)
			// The SFNT header bytes are big-endian
			uint16_t numTables = (static_cast<uint16_t>(data[4]) << 8) | static_cast<uint16_t>(data[5]);

			const uint8_t* nameTable = nullptr;

			// Each table entry is 16 bytes
			constexpr size_t tableEntrySize = 16;
			const size_t tableDirSize = static_cast<size_t>(numTables) * tableEntrySize;
			if (size < table_offset + tableDirSize) {
				throw std::runtime_error("Table directory extends past file size");
			}

			// Iterate through table directory entries to find the 'name' table
			for (int i = 0; i < numTables; ++i) {
				const uint8_t* entry = tableDir + i * tableEntrySize;

				// Ensure entry access is within range
				if (entry + 12 >= data + size) {
					throw std::runtime_error("Table entry access out of bounds");
				}

				// Read table tag (4 bytes)
				uint32_t tag = (static_cast<uint32_t>(entry[0]) << 24) | (static_cast<uint32_t>(entry[1]) << 16) |
							   (static_cast<uint32_t>(entry[2]) << 8) | static_cast<uint32_t>(entry[3]);

				// Check if it's the 'name' table (tag 0x6E616D65)
				if (tag == 0x6E616D65) {
					// Read offset to the 'name' table (4 bytes, big-endian)
					uint32_t offset = (static_cast<uint32_t>(entry[8]) << 24) |
									  (static_cast<uint32_t>(entry[9]) << 16) |
									  (static_cast<uint32_t>(entry[10]) << 8) | static_cast<uint32_t>(entry[11]);

					if (offset >= static_cast<uint32_t>(size)) {
						throw std::runtime_error("Name table offset beyond file size");
					}

					nameTable = data + offset;
					break;
				}
			}

			if (!nameTable || nameTable + 6 > data + size) {
				throw std::runtime_error("Name table header is missing or truncated");
			}

			// Name table header - All values are big-endian
			uint16_t count = (static_cast<uint16_t>(nameTable[2]) << 8) | static_cast<uint16_t>(nameTable[3]);
			uint16_t stringOffset = (static_cast<uint16_t>(nameTable[4]) << 8) | static_cast<uint16_t>(nameTable[5]);

			constexpr size_t recordSize = 12;
			const uint8_t* recordBase = nameTable + 6;

			if (recordBase + count * recordSize > data + size) {
				throw std::runtime_error("Name records extend beyond font data");
			}

			// Iterate through name records
			for (int i = 0; i < count; ++i) {
				const uint8_t* record =
					recordBase + i * recordSize; // 6 bytes for name table header, 12 bytes per record

				// Read metadata for the record
				uint16_t platformID = (static_cast<uint16_t>(record[0]) << 8) | static_cast<uint16_t>(record[1]);
				uint16_t encodingID = (static_cast<uint16_t>(record[2]) << 8) | static_cast<uint16_t>(record[3]);
				uint16_t nameID = (static_cast<uint16_t>(record[6]) << 8) | static_cast<uint16_t>(record[7]);
				uint16_t length = (static_cast<uint16_t>(record[8]) << 8) | static_cast<uint16_t>(record[9]);
				uint16_t offset = (static_cast<uint16_t>(record[10]) << 8) | static_cast<uint16_t>(record[11]);

				// We are looking for Name ID 1 (Font Family Name)
				// Prefer Unicode (Platform ID 0, 3) or Apple Roman (Platform ID 1) if available
				if (nameID == 1) {
					// Check for Unicode (Platform ID 0, Encoding ID 3 or 4) or (Platform ID 3, Encoding ID 1)
					// or Mac Roman (Platform ID 1, Encoding ID 0)
					bool isUnicode = (platformID == 0 && (encodingID == 3 || encodingID == 4)) ||
									 (platformID == 3 && encodingID == 1);
					bool isMacRoman = (platformID == 1 && encodingID == 0);

					if (isUnicode || isMacRoman) {
						const uint8_t* nameString = nameTable + stringOffset + offset;
						SCP_string familyName;

						// Bounds check to prevent reading past the end of the font metadata
						if (nameString + length > data + size) {
							throw std::runtime_error("Name string extends past font data");
						}

						if (isUnicode) {
							// UCS-2 (UTF-16BE) to UTF-8 conversion
							// This assumes standard UCS-2, which is UTF-16BE for basic multilingual plane.
							for (int j = 0; j < length; j += 2) {
								uint16_t unicodeChar = (static_cast<uint16_t>(nameString[j]) << 8) |
													   static_cast<uint16_t>(nameString[j + 1]);

								if (unicodeChar <= 0x7F) { // 1-byte UTF-8
									familyName += static_cast<char>(unicodeChar);
								} else if (unicodeChar <= 0x7FF) { // 2-byte UTF-8
									familyName += static_cast<char>(0xC0 | (unicodeChar >> 6));
									familyName += static_cast<char>(0x80 | (unicodeChar & 0x3F));
								} else { // 3-byte UTF-8
									familyName += static_cast<char>(0xE0 | (unicodeChar >> 12));
									familyName += static_cast<char>(0x80 | ((unicodeChar >> 6) & 0x3F));
									familyName += static_cast<char>(0x80 | (unicodeChar & 0x3F));
								}
								// For supplementary planes (4-byte UTF-8, characters > 0xFFFF),
								// this simple conversion is not sufficient and would require surrogate pair handling.
								// Most common font names will be in BMP (Basic Multilingual Plane) according to my
								// research - Mjn
							}
						} else { // Mac Roman (single byte) - this encoding is less common but supported easily enough
							for (int j = 0; j < length; ++j) {
								familyName += static_cast<char>(nameString[j]);
							}
						}
						return familyName;
					}
				}
			}

			throw std::runtime_error("No suitable family name found");
		} catch (const std::exception& e) {
			mprintf(("Failed to extract font name: %s\n", e.what()));
			return "";
		} catch (...) {
			mprintf(("Failed to extract font name: Unknown exception\n"));
			return "";
		}
	}

	std::pair<NVGFont*, int> FontManager::loadNVGFont(const SCP_string& fileName, float fontSize)
	{
		auto iter = allocatedData.find(fileName);
		if (iter == allocatedData.end())
		{
			CFILE *fontFile = cfopen(fileName.c_str(), "rb", CF_TYPE_ANY);

			if (fontFile == nullptr)
			{
				mprintf(("Couldn't open font file \"%s\"\n", fileName.c_str()));
				return { nullptr, -1 };
			}

			int size = cfilelength(fontFile);

			std::unique_ptr<ubyte[]> fontData(new ubyte[size]);

			if (!cfread(fontData.get(), size, 1, fontFile))
			{
				mprintf(("Error while reading font data from \"%s\"\n", fileName.c_str()));
				cfclose(fontFile);
				return { nullptr, -1 };
			}

			cfclose(fontFile);

			TrueTypeFontData newData;

			newData.size = static_cast<size_t>(size);
			std::swap(newData.data, fontData);

			auto pair = allocatedData.emplace(fileName, std::move(newData));
			iter = pair.first;
		}

		auto data = &iter->second;

		auto path = graphics::paths::PathRenderer::instance();

		int handle = path->createFontMem(fileName.c_str(), data->data.get(), (int)data->size, 0);
		
		if (handle < 0)
		{
			mprintf(("Couldn't create font for file \"%s\"\n", fileName.c_str()));
			return { nullptr, -1 };
		}

		std::unique_ptr<NVGFont> nvgFont(new NVGFont());
		nvgFont->setHandle(handle);
		nvgFont->setSize(fontSize);
		nvgFont->setFamilyName(extractFamilyNameFromTTF(*data));

		auto ptr = nvgFont.get();

		fonts.emplace_back(std::move(nvgFont));

		return { ptr, static_cast<int>(fonts.size() - 1) };
	}

	void FontManager::init()
	{
	}

	void FontManager::close()
	{
		allocatedData.clear();
		vfntFontData.clear();
		fonts.clear();

		currentFontIndex = -1;
	}
}