1 /*
2 * Copyright (C) Volition, Inc. 1999. All rights reserved.
3 *
4 * All source code herein is the property of Volition, Inc. You may not sell
5 * or otherwise commercially exploit the source or things you created based on the
6 * source.
7 *
8 */
9
10 #include <cstdio>
11 #include <cstdarg>
12 #include <string>
13 #include <sstream>
14 #include <climits>
15
16 #include "graphics/software/font_internal.h"
17 #include "graphics/software/FontManager.h"
18 #include "graphics/software/FSFont.h"
19 #include "graphics/software/VFNTFont.h"
20 #include "graphics/software/NVGFont.h"
21
22 #include "graphics/2d.h"
23
24 #include "mod_table/mod_table.h"
25
26 #include "def_files/def_files.h"
27 #include "bmpman/bmpman.h"
28 #include "cfile/cfile.h"
29 #include "localization/localize.h"
30 #include "parse/parselo.h"
31 #include "tracing/Monitor.h"
32
33 namespace
34 {
35 namespace fo = font;
36 using namespace font;
37
38 bool font_initialized = false;
39
40 constexpr ubyte DEFAULT_SPECIAL_CHAR_INDEX = 0;
41
42 // max allowed special char index; i.e. 7 special chars in retail fonts 1 & 3
43 const int MAX_SPECIAL_CHAR_IDX = UCHAR_MAX - 6;
44
45
parse_type(FontType & type,SCP_string & fileName)46 bool parse_type(FontType &type, SCP_string &fileName)
47 {
48 int num = optional_string_either("$TrueType:", "$Font:");
49 if (num == 0)
50 {
51 type = NVG_FONT;
52 }
53 else if (num == 1)
54 {
55 type = VFNT_FONT;
56 }
57 else
58 {
59 type = UNKNOWN_FONT;
60 }
61
62
63 if (type != UNKNOWN_FONT)
64 {
65 stuff_string(fileName, F_NAME);
66 return true;
67 }
68 else
69 {
70 return false;
71 }
72 }
73
parse_nvg_font(const SCP_string & fontFilename)74 void parse_nvg_font(const SCP_string& fontFilename)
75 {
76 float size = 8.0f;
77 SCP_string fontStr;
78 bool hasName = false;
79
80 if (optional_string("+Name:"))
81 {
82 stuff_string(fontStr, F_NAME);
83
84 hasName = true;
85 }
86
87 if (optional_string("+Size:"))
88 {
89 stuff_float(&size);
90
91 if (size <= 0.0f)
92 {
93 error_display(0, "+Size has to be bigger than 0 for font \"%s\".", fontFilename.c_str());
94 size = 8;
95 }
96 }
97
98 // Build name from existing values if no name is specified
99 if (!hasName)
100 {
101 SCP_stringstream ss;
102
103 ss << fontFilename << "-";
104
105 ss << size;
106
107 fontStr = ss.str();
108 }
109
110 if (FontManager::getFont(fontStr) != NULL)
111 {
112 if (hasName)
113 {
114 error_display(0, "Font with name \"%s\" is already present! Font names have to be unique!", fontStr.c_str());
115 return;
116 }
117 else
118 {
119 error_display(0, "Found font with same default name (\"%s\"). This is most likely a duplicate.", fontStr.c_str());
120 return;
121 }
122 }
123
124 NVGFont *nvgFont = FontManager::loadNVGFont(fontFilename, size);
125
126 if (nvgFont == NULL)
127 {
128 error_display(0, "Couldn't load font \"%s\".", fontFilename.c_str());
129 return;
130 }
131
132 if (optional_string("+Top offset:"))
133 {
134 float temp;
135
136 stuff_float(&temp);
137
138 nvgFont->setTopOffset(temp);
139 }
140
141 if (optional_string("+Bottom offset:"))
142 {
143 float temp;
144
145 stuff_float(&temp);
146
147 nvgFont->setBottomOffset(temp);
148 }
149
150 if (optional_string("+Tab width:"))
151 {
152 float temp;
153 stuff_float(&temp);
154
155 if (temp < 0.0f)
156 {
157 error_display(0, "Invalid tab spacing %f. Has to be greater or equal to zero.", temp);
158 }
159 else
160 {
161 nvgFont->setTabWidth(temp);
162 }
163 }
164
165 if (optional_string("+Letter spacing:"))
166 {
167 float temp;
168 stuff_float(&temp);
169
170 if (temp < 0.0f)
171 {
172 error_display(0, "Invalid letter spacing %f! Has to be greater or equal to zero.", temp);
173 }
174 else
175 {
176 nvgFont->setLetterSpacing(temp);
177 }
178 }
179
180 if (!Unicode_text_mode) {
181 int special_char_index = DEFAULT_SPECIAL_CHAR_INDEX;
182 // Special characters only exist in non-Unicode mode
183 if (optional_string("+Special Character Font:"))
184 {
185 SCP_string fontName;
186 stuff_string(fontName, F_NAME);
187
188 fo::font* fontData = FontManager::loadFontOld(fontName.c_str());
189
190 if (fontData == NULL)
191 {
192 error_display(0, "Failed to load font \"%s\" for special characters of font \"%s\"!", fontName.c_str(), fontFilename.c_str());
193 }
194 else
195 {
196 nvgFont->setSpecialCharacterFont(fontData);
197 }
198
199 if (optional_string("+Special Character Index:")) {
200 stuff_int(&special_char_index);
201 if (special_char_index < 0 || special_char_index >= MAX_SPECIAL_CHAR_IDX) {
202 Warning(LOCATION, "Special character index (%d) for font (%s) is invalid, must be 0 - %u. Defaulting to 0.",
203 special_char_index, fontName.c_str(), MAX_SPECIAL_CHAR_IDX);
204 special_char_index = DEFAULT_SPECIAL_CHAR_INDEX;
205 }
206 }
207 else {
208 auto old_entry = FontManager::getFontByFilename(fontName);
209
210 if (old_entry != nullptr) {
211 int old_index = FontManager::getFontIndex(old_entry);
212 special_char_index = Lcl_languages[0].special_char_indexes[old_index];
213 } else {
214
215 Warning(LOCATION,
216 "A \"+Special Character Index:\" was not specified after the \"+Special Character Font:\" entry for the %s font. And a previous entry with a \"+Special Character Index:\" was not found."
217 "\n\n This entry must be specified directly or indirectly to avoid this warning. Defaulting to 0.",
218 fontStr.c_str());
219 }
220 }
221
222 }
223 else
224 {
225 fo::font* fontData = FontManager::loadFontOld("font01.vf");
226
227 if (fontData == nullptr) {
228 error_display(0,
229 "Failed to load default font \"%s\" for special characters of font \"%s\"! "
230 "This font is required for rendering special characters, if another font is not defined, and will cause an error later.",
231 "font01.vf",
232 fontFilename.c_str());
233 } else {
234 nvgFont->setSpecialCharacterFont(fontData);
235 }
236 }
237
238 auto font_id = FontManager::getFontIndex(nvgFont);
239
240 // add the index specified to all languages
241 for (auto & Lcl_language : Lcl_languages) {
242
243 // if there wasn't already something specified for this font.
244 if (font_id >= (int)Lcl_language.special_char_indexes.size()) {
245
246 // if there were absolutely no special characters defined, put the default value as the language default.
247 if (Lcl_language.special_char_indexes.empty() == 0) {
248 Lcl_language.special_char_indexes.push_back(DEFAULT_SPECIAL_CHAR_INDEX);
249 }
250
251 // if somehow a lot of indexes were missing, add the language default to fill in the gaps until ...
252 while (font_id > (int)Lcl_language.special_char_indexes.size()) {
253 Lcl_language.special_char_indexes.push_back(Lcl_language.special_char_indexes[0]);
254 }
255
256 // we're at just the right place in the vector to add the index we just read from the table.
257 Lcl_language.special_char_indexes.push_back((ubyte)special_char_index);
258
259 } // replace if there was something already specified, except if nothing was specified in the table
260 else if (special_char_index != DEFAULT_SPECIAL_CHAR_INDEX) {
261 Lcl_language.special_char_indexes[font_id] = (ubyte)special_char_index;
262 }
263 }
264 }
265
266 nvgFont->setName(fontStr);
267 nvgFont->setFilename(fontFilename);
268
269 // Make sure that the height is not invalid
270 nvgFont->computeFontMetrics();
271 }
272
parse_vfnt_font(const SCP_string & fontFilename)273 void parse_vfnt_font(const SCP_string& fontFilename)
274 {
275 VFNTFont *font = FontManager::loadVFNTFont(fontFilename);
276
277 if (font == NULL)
278 {
279 error_display(0, "Couldn't load font\"%s\".", fontFilename.c_str());
280 return;
281 }
282
283 SCP_string fontName;
284
285 if (optional_string("+Name:"))
286 {
287 stuff_string(fontName, F_NAME);
288 }
289 else
290 {
291 fontName.assign(fontFilename);
292 }
293
294 font->setName(fontName);
295 font->setFilename(fontFilename);
296
297 auto font_id = FontManager::getFontIndex(font);
298
299 int user_defined_default_special_char_index = (int)DEFAULT_SPECIAL_CHAR_INDEX;
300 // 'default' special char index for all languages using this font
301 if (optional_string("+Default Special Character Index:")) {
302 stuff_int(&user_defined_default_special_char_index);
303
304 if (user_defined_default_special_char_index < 0 || user_defined_default_special_char_index >= MAX_SPECIAL_CHAR_IDX) {
305 Warning(LOCATION, "Default special character index (%d) for font (%s), must be 0 - %u. Defaulting to 0.", user_defined_default_special_char_index,
306 fontName.c_str(), MAX_SPECIAL_CHAR_IDX);
307 }
308
309 }
310
311 // add the index specified to all the languages
312 for (auto & Lcl_language : Lcl_languages) {
313
314 // if there wasn't already something specified for this font.
315 if (font_id >= (int)Lcl_language.special_char_indexes.size()) {
316
317 // if the default for the language was not already specified, set that default to the general default
318 if (Lcl_language.special_char_indexes.empty()) {
319 Lcl_language.special_char_indexes.push_back(DEFAULT_SPECIAL_CHAR_INDEX);
320 }
321
322 // if a lot of indexes were missing, add some defaults to fill in the gaps until ...
323 while (font_id > (int)Lcl_language.special_char_indexes.size()) {
324 // take into account retail special character settings for font index 2 (size is 1 indexed)
325 if (Lcl_language.special_char_indexes.size() == FONT3 - 1) {
326 Lcl_language.special_char_indexes.push_back(176);
327 }
328 else {
329 Lcl_language.special_char_indexes.push_back(Lcl_language.special_char_indexes[0]);
330 }
331 }
332 // we're at just the right place in the vector to add the index we just read from the table.
333 Lcl_language.special_char_indexes.push_back((ubyte)DEFAULT_SPECIAL_CHAR_INDEX);
334 } // if there was something already specified that's being replaced.
335 else if (user_defined_default_special_char_index != DEFAULT_SPECIAL_CHAR_INDEX) {
336 Lcl_language.special_char_indexes[font_id] = (ubyte)user_defined_default_special_char_index;
337 }
338 }
339
340 while (optional_string("+Language:")) {
341 char lang_name[LCL_LANG_NAME_LEN + 1];
342 int special_char_index, lang_idx = -1;
343
344 stuff_string(lang_name, F_NAME, LCL_LANG_NAME_LEN + 1);
345
346 // find language and set the index, or if not found move to the next one
347 for (auto i = 0; i < (int)Lcl_languages.size(); ++i) {
348 if (!strcmp(Lcl_languages[i].lang_name, lang_name)) {
349 lang_idx = i;
350 break;
351 }
352 }
353
354 if (lang_idx == -1) {
355 Warning(LOCATION, "Ignoring invalid language (%s) specified by font (%s); not built-in or in strings.tbl",
356 lang_name, fontName.c_str());
357 skip_to_start_of_string_either("+Language:", "$Font:", "#End");
358 continue;
359 }
360
361 if (optional_string("+Special Character Index:")) {
362 stuff_int(&special_char_index);
363
364 if (special_char_index < 0 || special_char_index >= MAX_SPECIAL_CHAR_IDX) {
365 Error(LOCATION, "Special character index (%d) for font (%s), language (%s) is invalid, must be 0 - %u",
366 special_char_index, fontName.c_str(), lang_name, MAX_SPECIAL_CHAR_IDX);
367 }
368
369 Lcl_languages[lang_idx].special_char_indexes[font_id] = (ubyte)special_char_index;
370 }
371 }
372
373 if (optional_string("+Top offset:"))
374 {
375 float temp;
376
377 stuff_float(&temp);
378
379 font->setTopOffset(temp);
380 }
381
382 if (optional_string("+Bottom offset:"))
383 {
384 float temp;
385
386 stuff_float(&temp);
387
388 font->setBottomOffset(temp);
389 }
390 // Make sure that the height is not invalid
391 font->computeFontMetrics();
392 }
393
font_parse_setup(const char * fileName)394 void font_parse_setup(const char *fileName)
395 {
396 bool noTable = false;
397 if (!strcmp(fileName, "fonts.tbl"))
398 {
399 if (!cf_exists_full(fileName, CF_TYPE_TABLES))
400 {
401 noTable = true;
402 }
403 }
404
405 if (noTable)
406 {
407 read_file_text_from_default(defaults_get_file("fonts.tbl"));
408 }
409 else
410 {
411 read_file_text(fileName, CF_TYPE_TABLES);
412 }
413
414 reset_parse();
415
416 // start parsing
417 required_string("#Fonts");
418 }
419
parse_font_tbl(const char * fileName)420 void parse_font_tbl(const char *fileName)
421 {
422 try
423 {
424 font_parse_setup(fileName);
425
426 FontType type;
427 SCP_string fontName;
428
429 while (parse_type(type, fontName))
430 {
431 switch (type)
432 {
433 case VFNT_FONT:
434 if (Unicode_text_mode) {
435 error_display(1, "Bitmap fonts are not supported in Unicode text mode!");
436 }
437 parse_vfnt_font(fontName);
438 break;
439 case NVG_FONT:
440 parse_nvg_font(fontName);
441 break;
442 default:
443 error_display(0, "Unknown font type %d! Get a coder!", (int)type);
444 break;
445 }
446 }
447
448 // done parsing
449 required_string("#End");
450 }
451 catch (const parse::ParseException& e)
452 {
453 mprintf(("TABLES: Unable to parse '%s'! Error message = %s.\n", fileName, e.what()));
454 }
455 }
456
parse_fonts_tbl()457 void parse_fonts_tbl()
458 {
459 //Parse main TBL first
460 parse_font_tbl("fonts.tbl");
461
462 //Then other ones
463 parse_modular_table("*-fnt.tbm", parse_font_tbl);
464
465 // double check
466 if (FontManager::numberOfFonts() < 3) {
467 Error(LOCATION, "At least three fonts have to be loaded but only %d valid entries were found!", FontManager::numberOfFonts());
468 }
469 }
470 }
471
472 namespace font
473 {
init()474 void init()
475 {
476 if (font_initialized) {
477 // Already initialized
478 return;
479 }
480
481 FontManager::init();
482
483 parse_fonts_tbl();
484
485 set_font(0);
486
487 font_initialized = true;
488 }
489
close()490 void close()
491 {
492 if (!font_initialized) {
493 return;
494 }
495
496 FontManager::close();
497
498 font_initialized = false;
499 }
500
force_fit_string(char * str,int max_str,int max_width)501 int force_fit_string(char *str, int max_str, int max_width)
502 {
503 int w;
504
505 gr_get_string_size(&w, NULL, str);
506 if (w > max_width) {
507 if ((int)strlen(str) > max_str - 3) {
508 Assert(max_str >= 3);
509 str[max_str - 3] = 0;
510 }
511
512 strcpy(str + strlen(str) - 1, "...");
513 gr_get_string_size(&w, NULL, str);
514 while (w > max_width) {
515 Assert(strlen(str) >= 4); // if this is hit, a bad max_width was passed in and the calling function needs fixing.
516 strcpy(str + strlen(str) - 4, "...");
517 gr_get_string_size(&w, NULL, str);
518 }
519 }
520
521 return w;
522 }
523
stuff_first(SCP_string & firstFont)524 void stuff_first(SCP_string &firstFont)
525 {
526 try
527 {
528 font_parse_setup("fonts.tbl");
529
530 FontType type;
531 parse_type(type, firstFont);
532 }
533 catch (const parse::ParseException& e)
534 {
535 Error(LOCATION, "Failed to setup font parsing. This may be caused by an empty fonts.tbl file.\nError message: %s", e.what());
536 firstFont = "";
537 }
538 }
539
parse_font()540 int parse_font()
541 {
542 int font_idx;
543
544 SCP_string input;
545 stuff_string(input, F_NAME);
546 SCP_stringstream ss(input);
547
548 int fontNum;
549 ss >> fontNum;
550
551 if (ss.fail())
552 {
553 fontNum = FontManager::getFontIndex(input);
554
555 if (fontNum < 0)
556 {
557 error_display(0, "Invalid font name \"%s\"!", input.c_str());
558 font_idx = -1;
559 }
560 else
561 {
562 font_idx = fontNum;
563 }
564 }
565 else
566 {
567 if (fontNum < 0 || fontNum >= FontManager::numberOfFonts())
568 {
569 error_display(0, "Invalid font number %d! must be greater or equal to zero and smaller than %d.", fontNum, FontManager::numberOfFonts());
570 font_idx = -1;
571 }
572 else
573 {
574 font_idx = fontNum;
575 }
576 }
577
578 return font_idx;
579 }
580
get_current_font()581 FSFont *get_current_font()
582 {
583 return FontManager::getCurrentFont();
584 }
585
get_font(const SCP_string & name)586 FSFont *get_font(const SCP_string& name)
587 {
588 return FontManager::getFont(name);
589 }
590
get_font_by_filename(const SCP_string & name)591 FSFont *get_font_by_filename(const SCP_string& name)
592 {
593 return FontManager::getFontByFilename(name);
594 }
595
596
597
598 /**
599 * @brief Gets the width of an character.
600 *
601 * Returns the width of the specified charachter also taking account of kerning.
602 *
603 * @param fnt The font data
604 * @param c1 The character that should be checked.
605 * @param c2 The character which follows this character. Used to compute the kerning
606 * @param [out] width If non-null, the width.
607 * @param [out] spaceing If non-null, the spaceing.
608 *
609 * @return The character width.
610 */
get_char_width_old(fo::font * fnt,ubyte c1,ubyte c2,int * width,int * spacing)611 int get_char_width_old(fo::font* fnt, ubyte c1, ubyte c2, int *width, int* spacing)
612 {
613 int i, letter;
614
615 letter = c1 - fnt->first_ascii;
616
617 //not in font, draw as space
618 if (letter < 0 || letter >= fnt->num_chars)
619 {
620 *width = 0;
621 *spacing = fnt->w;
622 return -1;
623 }
624
625 *width = fnt->char_data[letter].byte_width;
626 *spacing = fnt->char_data[letter].spacing;
627
628 i = fnt->char_data[letter].kerning_entry;
629 if (i > -1)
630 {
631 if (!(c2 == 0 || c2 == '\n'))
632 {
633 int letter2;
634
635 letter2 = c2 - fnt->first_ascii;
636
637 if ((letter2 >= 0) && (letter2 < fnt->num_chars) && (i < fnt->num_kern_pairs))
638 {
639 font_kernpair *k = &fnt->kern_data[i];
640 while ((i < fnt->num_kern_pairs) && (k->c1 == (char)letter) && (k->c2 < (char)letter2))
641 {
642 i++;
643 k++;
644 }
645
646 if ((i < fnt->num_kern_pairs) && (k->c2 == (char)letter2))
647 {
648 *spacing += k->offset;
649 }
650 }
651 }
652 }
653
654 return letter;
655 }
656 }
657
gr_get_font_height()658 int gr_get_font_height()
659 {
660 if (FontManager::isReady())
661 {
662 return fl2i(FontManager::getCurrentFont()->getHeight());
663 }
664 else
665 {
666 return 16;
667 }
668 }
669
gr_get_string_size(int * w1,int * h1,const char * text,int len)670 void gr_get_string_size(int *w1, int *h1, const char *text, int len)
671 {
672 if (!FontManager::isReady())
673 {
674 if (w1)
675 *w1 = 16;
676
677 if (h1)
678 *h1 = 16;
679
680 return;
681 }
682
683 float w = 0.0f;
684 float h = 0.0f;
685
686 FontManager::getCurrentFont()->getStringSize(text, static_cast<size_t>(len), -1, &w, &h);
687
688 if (w1)
689 {
690 *w1 = fl2i(ceil(w));
691 }
692 if (h1)
693 {
694 *h1 = fl2i(ceil(h));
695 }
696 }
697
MONITOR(FontChars)698 MONITOR(FontChars)
699
700 #ifdef _WIN32
701
702 void gr_string_win(int x, int y, char *s)
703 {
704 using namespace font;
705
706 int old_bitmap = gr_screen.current_bitmap;
707 set_font(FONT1);
708 gr_string(x, y, s);
709 gr_screen.current_bitmap = old_bitmap;
710 }
711
712 #endif // ifdef _WIN32
713
714 char grx_printf_text[2048];
715
gr_printf(int x,int y,const char * format,...)716 void gr_printf(int x, int y, const char * format, ...)
717 {
718 va_list args;
719
720 if (!FontManager::isReady()) return;
721
722 va_start(args, format);
723 vsnprintf(grx_printf_text, sizeof(grx_printf_text) - 1, format, args);
724 va_end(args);
725 grx_printf_text[sizeof(grx_printf_text) - 1] = '\0';
726
727 gr_string(x, y, grx_printf_text);
728 }
729
gr_printf_menu(int x,int y,const char * format,...)730 void gr_printf_menu(int x, int y, const char * format, ...)
731 {
732 va_list args;
733
734 if (!FontManager::isReady()) return;
735
736 va_start(args, format);
737 vsnprintf(grx_printf_text, sizeof(grx_printf_text) - 1, format, args);
738 va_end(args);
739 grx_printf_text[sizeof(grx_printf_text) - 1] = '\0';
740
741 gr_string(x, y, grx_printf_text, GR_RESIZE_MENU);
742 }
743
gr_printf_menu_zoomed(int x,int y,const char * format,...)744 void gr_printf_menu_zoomed(int x, int y, const char * format, ...)
745 {
746 va_list args;
747
748 if (!FontManager::isReady()) return;
749
750 va_start(args, format);
751 vsnprintf(grx_printf_text, sizeof(grx_printf_text) - 1, format, args);
752 va_end(args);
753 grx_printf_text[sizeof(grx_printf_text) - 1] = '\0';
754
755 gr_string(x, y, grx_printf_text, GR_RESIZE_MENU_ZOOMED);
756 }
757
gr_printf_no_resize(int x,int y,const char * format,...)758 void gr_printf_no_resize(int x, int y, const char * format, ...)
759 {
760 va_list args;
761
762 if (!FontManager::isReady()) return;
763
764 va_start(args, format);
765 vsnprintf(grx_printf_text, sizeof(grx_printf_text) - 1, format, args);
766 va_end(args);
767 grx_printf_text[sizeof(grx_printf_text) - 1] = '\0';
768
769 gr_string(x, y, grx_printf_text, GR_RESIZE_NONE);
770 }
771