libvwad: updated -- vwadwrite: free file buffers on close (otherwise archive creation...
[k8vavoom.git] / source / widgets / ui_font.cpp
blob1392cb754125f78d17e17b2d0905b9c20827d069
1 //**************************************************************************
2 //**
3 //** ## ## ## ## ## #### #### ### ###
4 //** ## ## ## ## ## ## ## ## ## ## #### ####
5 //** ## ## ## ## ## ## ## ## ## ## ## ## ## ##
6 //** ## ## ######## ## ## ## ## ## ## ## ### ##
7 //** ### ## ## ### ## ## ## ## ## ##
8 //** # ## ## # #### #### ## ##
9 //**
10 //** Copyright (C) 1999-2006 Jānis Legzdiņš
11 //** Copyright (C) 2018-2023 Ketmar Dark
12 //**
13 //** This program is free software: you can redistribute it and/or modify
14 //** it under the terms of the GNU General Public License as published by
15 //** the Free Software Foundation, version 3 of the License ONLY.
16 //**
17 //** This program is distributed in the hope that it will be useful,
18 //** but WITHOUT ANY WARRANTY; without even the implied warranty of
19 //** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 //** GNU General Public License for more details.
21 //**
22 //** You should have received a copy of the GNU General Public License
23 //** along with this program. If not, see <http://www.gnu.org/licenses/>.
24 //**
25 //**************************************************************************
26 #include "../gamedefs.h"
27 #include "../textures/r_tex.h"
28 #include "ui.h"
30 #define VAVOOM_NAME_FONT_TEXTURES
32 #define VAVOOM_CON_FONT_PATH "k8vavoom/fonts/consolefont.fnt"
35 struct VColTranslationDef {
36 rgba_t From;
37 rgba_t To;
38 int LumFrom;
39 int LumTo;
42 struct VTextColorDef {
43 TArray<VColTranslationDef> Translations;
44 TArray<VColTranslationDef> ConsoleTranslations;
45 rgba_t FlatColor;
46 int Index;
49 struct VColTransMap {
50 VName Name;
51 int Index;
55 // like regular font, but initialised using explicit list of patches
56 class VSpecialFont : public VFont {
57 public:
58 VSpecialFont (VName AName, const TArray<int> &CharIndexes, const TArray<VName> &CharLumps, const bool *NoTranslate, int ASpaceWidth);
61 // font in bitmap format
62 class VFontBitmapBase : public VFont {
63 protected:
64 TArray<VTexture *> atlaslist;
65 TArray<VTexture *> tratlaslist;
67 protected:
68 // for FONx
69 VTexAtlas8bit *currAtlas;
70 int nextToUpdate;
71 int asize; // atlas size
73 protected:
74 VTexAtlas8bit *allocAtlas (rgba_t *APalette, int awdt=1024, int ahgt=1024);
75 VTexAtlas8bit *allocTranslated (rgba_t *APalette, VTexAtlas8bit *currAtlas);
76 void CreateTranslatedAtlases (VTexAtlas8bit *currAtlas, int nextToUpdate);
78 // for FONx
79 void CalculateAtlasSize (int TotalDataSize) noexcept;
80 void ReadFONxChar (VStream &Strm, VName AName, int LumpNum, int Chr, int CharWidth, int CharHeight);
81 void DoneReadingFONx ();
83 public:
84 VFontBitmapBase ();
85 virtual ~VFontBitmapBase () override;
88 // font in FON1 format
89 class VFon1Font : public VFontBitmapBase {
90 public:
91 VFon1Font (VName, int);
94 // font in FON2 format
95 class VFon2Font : public VFontBitmapBase {
96 public:
97 VFon2Font (VName, int);
100 // font consisting of a single texture for character 'A'
101 class VSingleTextureFont : public VFont {
102 public:
103 VSingleTextureFont (VName, int);
104 bool IsSingleTextureFont () const noexcept override;
107 // texture class for regular font characters
108 class VFontChar : public VTexture {
109 private:
110 VTexture *BaseTex;
111 rgba_t *Palette;
113 public:
114 VFontChar (VTexture *ATex, rgba_t *APalette);
115 virtual ~VFontChar () override;
116 virtual vuint8 *GetPixels () override;
117 virtual rgba_t *GetPalette () override;
118 virtual VTexture *GetHighResolutionTexture () override;
122 // ////////////////////////////////////////////////////////////////////////// //
123 #include "ui_pcf_parse.cpp"
126 // ////////////////////////////////////////////////////////////////////////// //
127 VFont *SmallFont = nullptr;
128 VFont *ConFont = nullptr;
129 VFont *VFont::Fonts = nullptr;
130 TMap<VStrCI, VFont *> VFont::FontMap;
132 static TArray<VTextColorDef> TextColors;
133 static TArray<VColTransMap> TextColorLookup;
134 #ifdef VAVOOM_NAME_FONT_TEXTURES
135 static int vfontcharTxCount = 0;
136 #endif
138 static_assert(NUM_TEXT_COLORS >= 26, "we should have at least 26 user text colors!");
141 //==========================================================================
143 // GenFontTextureName
145 //==========================================================================
146 static inline VName GenFontTextureName () noexcept {
147 #ifdef VAVOOM_NAME_FONT_TEXTURES
148 return VName(va("\x7f_fontchar2_%d ", vfontcharTxCount++));
149 #else
150 return NAME_None;
151 #endif
155 //==========================================================================
157 // safeFormat
159 // should have only one format for decimal
161 //==========================================================================
162 static bool safeFormat (VStr fmt) noexcept {
163 bool seenFormat = false;
164 int prcpos = fmt.indexOf('%');
165 while (prcpos >= 0) {
166 if (prcpos+1 >= fmt.length()) return false;
167 if (fmt[prcpos+1] == '%') { prcpos = fmt.indexOf('%', prcpos+2); continue; }
168 if (seenFormat) return false;
169 seenFormat = true;
170 ++prcpos;
171 if (fmt[prcpos] == '-' || fmt[prcpos] == '+') ++prcpos;
172 while (prcpos < fmt.length() && VStr::digitInBase(fmt[prcpos], 10) >= 0) ++prcpos;
173 if (prcpos >= fmt.length()) return false;
174 const char fc = fmt[prcpos++];
175 if (fc != 'x' && fc != 'X' && fc != 'd' && fc != 'i') return false;
176 if (prcpos+1 >= fmt.length()) break;
177 prcpos = fmt.indexOf('%', prcpos);
179 return true;
183 //==========================================================================
185 // UnpackFONx
187 //==========================================================================
188 static vuint8 *UnpackFONx (VStream &Strm, int Count, const int MaxCol) {
189 if (Count <= 0) return nullptr;
190 vuint8 *Pixels = new vuint8[Count];
191 vuint8 *pDst = Pixels;
192 vuint8 maxcol = clampToByte(MaxCol);
193 do {
194 vint32 Code = Streamer<vint8>(Strm);
195 if (Code >= 0) {
196 Count -= Code+1;
197 while (Code-- >= 0) {
198 *pDst = Streamer<vuint8>(Strm);
199 *pDst = min2(*pDst, maxcol);
200 ++pDst;
202 } else if (Code != -128) {
203 Code = 1-Code;
204 Count -= Code;
205 vuint8 Val = Streamer<vuint8>(Strm);
206 Val = min2(Val, maxcol);
207 while (Code-- > 0) *pDst++ = Val;
209 } while (Count > 0);
211 if (Strm.IsError()) { delete[] Pixels; return nullptr; }
212 return Pixels;
217 //**************************************************************************
219 // VFontBitmapBase
221 //**************************************************************************
223 //==========================================================================
225 // VFontBitmapBase::VFontBitmapBase
227 //==========================================================================
228 VFontBitmapBase::VFontBitmapBase () {
229 currAtlas = nullptr;
230 nextToUpdate = 0;
231 asize = 0;
235 //==========================================================================
237 // VFontBitmapBase::~VFontBitmapBase
239 //==========================================================================
240 VFontBitmapBase::~VFontBitmapBase () {
241 //for (auto &&a : atlaslist) delete a;
243 for (auto &&cc : Chars) cc.Textures = nullptr; //FIXME: memleak!
244 Chars.Clear();
249 //==========================================================================
251 // VFontBitmapBase::allocAtlas
253 //==========================================================================
254 VTexAtlas8bit *VFontBitmapBase::allocAtlas (rgba_t *APalette, int awdt, int ahgt) {
255 VTexAtlas8bit *currAtlas = new VTexAtlas8bit(GenFontTextureName(), APalette, awdt, ahgt);
256 atlaslist.append(currAtlas);
257 // currently all render drivers expect all textures to be registered in texture manager
258 GTextureManager.AddTexture(currAtlas);
259 return currAtlas;
263 //==========================================================================
265 // VFontBitmapBase::allocTranslated
267 //==========================================================================
268 VTexAtlas8bit *VFontBitmapBase::allocTranslated (rgba_t *APalette, VTexAtlas8bit *currAtlas) {
269 VTexAtlas8bit *res = currAtlas->Clone(GenFontTextureName(), APalette);
270 tratlaslist.append(res);
271 // currently all render drivers expect all textures to be registered in texture manager
272 GTextureManager.AddTexture(res);
273 return res;
277 //==========================================================================
279 // VFontBitmapBase::CreateTranslatedAtlases
281 // create translated atlases, from `nextToUpdate` FChar
283 //==========================================================================
284 void VFontBitmapBase::CreateTranslatedAtlases (VTexAtlas8bit *currAtlas, int nextToUpdate) {
285 if (nextToUpdate >= Chars.length()) return;
286 (void)currAtlas->GetMaxIntensity(); // cache it
287 for (int f = nextToUpdate; f < Chars.length(); ++f) {
288 Chars[f].Textures = new VTexture*[TextColors.length()];
290 for (int j = 0; j < TextColors.length(); ++j) {
291 //VTexture *tt = (j == CR_UNTRANSLATED ? currAtlas : allocTranslated(Translation+j*256, currAtlas));
292 VTexture *tt = allocTranslated(Translation+j*256, currAtlas);
293 for (int f = nextToUpdate; f < Chars.length(); ++f) Chars[f].Textures[j] = tt;
298 //==========================================================================
300 // VFontBitmapBase::CalculateAtlasSize
302 //==========================================================================
303 void VFontBitmapBase::CalculateAtlasSize (int TotalDataSize) noexcept {
304 asize = 2;
305 if (TotalDataSize <= 0) return;
306 while (asize*asize < TotalDataSize) {
307 if (asize == 1024) break;
308 asize *= 2;
313 //==========================================================================
315 // VFontBitmapBase::DoneReadingFONx
317 //==========================================================================
318 void VFontBitmapBase::DoneReadingFONx () {
319 if (!currAtlas) return;
320 CreateTranslatedAtlases(currAtlas, nextToUpdate);
321 currAtlas = nullptr;
325 //==========================================================================
327 // VFontBitmapBase::ReadFONxChar
329 //==========================================================================
330 void VFontBitmapBase::ReadFONxChar (VStream &Strm, VName AName, int LumpNum, int Chr, int CharWidth, int CharHeight) {
331 if (CharWidth < 1 || CharHeight < 1) return;
333 if (!currAtlas) {
334 vassert(asize);
335 vassert(nextToUpdate == 0);
336 vassert(Chars.length() == 0);
337 currAtlas = allocAtlas(Translation, asize, asize);
340 VTexAtlas8bit::Rect rc;
342 // allocate char in atlas
343 for (;;) {
344 rc = currAtlas->allocSubImage(CharWidth+2, CharHeight+2);
345 if (rc.isValid()) break;
346 CreateTranslatedAtlases(currAtlas, nextToUpdate);
347 nextToUpdate = Chars.length();
348 currAtlas = allocAtlas(Translation, asize, asize);
351 //GCon->Logf(NAME_Debug, "char #%d (%dx%d): rect=(%d,%d)-(%dx%d)", Chars.length(), SpaceWidth, FontHeight, rc.x, rc.y, rc.w, rc.h);
353 // load char data
354 vuint8 *cdata = UnpackFONx(Strm, CharWidth*CharHeight, 255);
355 if (!cdata) Sys_Error("error loading font '%s' from '%s'", *AName, *W_FullLumpName(LumpNum));
356 const vuint8 *csrc = cdata;
357 for (int cy = 0; cy < CharHeight; ++cy) {
358 for (int cx = 0; cx < CharWidth; ++cx) {
359 currAtlas->setPixel(rc, cx+1, cy+1, *csrc++);
362 delete[] cdata;
364 FFontChar &FChar = Chars.Alloc();
365 if (Chr >= 0 && Chr < 128) AsciiChars[Chr] = Chars.length()-1;
366 FChar.Char = Chr;
367 FChar.TexNum = -1;
368 FChar.BaseTex = currAtlas;
369 rc.shrinkBy(1);
370 FChar.Rect = CharRect(currAtlas->convertRect(rc));
371 //GCon->Logf(NAME_Debug, " frc=(%g,%g)-(%g,%g)", FChar.Rect.x0, FChar.Rect.y0, FChar.Rect.x1, FChar.Rect.y1);
372 FChar.Width = CharWidth;
373 FChar.Height = CharHeight;
378 //**************************************************************************
380 // VFont Statics
382 //**************************************************************************
384 //==========================================================================
386 // VFont::RegisterFont
388 // set font name, and register it as known font
390 //==========================================================================
391 void VFont::RegisterFont (VFont *font, VName aname) {
392 if (!font) return;
394 font->Name = aname;
395 if (aname == NAME_None) { font->Next = nullptr; return; }
397 // register in list
398 font->Name = aname;
399 font->Next = Fonts;
400 Fonts = font;
402 // add to font map
403 if (FontMap.has(VStrCI(aname))) {
404 GCon->Logf(NAME_Init, "replacing font '%s'", *aname);
405 // remove old font from the list, just for fun
406 //FIXME: memory leak!
407 VFont *prev = nullptr;
408 VFont *curr = Fonts;
409 while (curr) {
410 if (curr != font && VStr::strEquCI(*curr->Name, *aname)) break;
411 prev = curr;
412 curr = curr->Next;
414 if (!curr) {
415 GCon->Log(NAME_Error, "internal error: old font not found (this is harmless, but still a bug!)");
416 } else {
417 if (prev) prev->Next = curr->Next; else Fonts = curr->Next;
418 curr->Next = nullptr;
419 delete curr;
421 } else {
422 GCon->Logf(NAME_Init, "registering font '%s'", *aname);
424 FontMap.put(VStrCI(aname), font);
428 //==========================================================================
430 // VFont::StaticInit
432 //==========================================================================
433 void VFont::StaticInit () {
434 ParseTextColors();
436 // load custom fonts (they can override standard fonts)
437 ParseFontDefs();
439 // initialise standard fonts
440 GCon->Log(NAME_Init, "creading default fonts");
442 // small font
443 //GCon->Logf(NAME_Debug, "'%s' : '%s' : %d : '%s' : '%s'", *VName(NAME_smallfont), *VStrCI(NAME_smallfont), (int)FontMap.has(VStrCI(NAME_smallfont)), *VStrCI(69), *VStrCI(666u));
444 if (!FontMap.has(VStrCI(NAME_smallfont))) {
445 for (VFont *f = Fonts; f; f = f->Next) GCon->Logf(NAME_Debug, "%p: '%s'", f, *f->Name);
446 //GCon->Logf(NAME_Debug, "'%s' : '%s' : %d", *VName(NAME_smallfont), *VStrCI(NAME_smallfont), (int)FontMap.has(VStrCI(NAME_smallfont)));
447 if (W_CheckNumForName(NAME_fonta_s) >= 0) {
448 GCon->Log(NAME_Init, " SmallFont: from 'fonta' lumps");
449 SmallFont = new VFont(NAME_smallfont, "fonta%02d", 33, 95, 1, -666);
450 } else {
451 GCon->Log(NAME_Init, " SmallFont: from 'stcfn' lumps");
452 SmallFont = new VFont(NAME_smallfont, "stcfn%03d", 33, 95, 33, -666);
455 if (SmallFont->GetFontName() == NAME_None) {
456 delete SmallFont;
457 GCon->Log(NAME_Init, " SmallFont: cannot create it, using ConsoleFont instead");
458 SmallFont = GetFont(VStr(VName(NAME_smallfont)), VStr(VAVOOM_CON_FONT_PATH));
459 if (!SmallFont) Sys_Error("cannot create console font");
463 // strife's second small font
464 if (!FontMap.has(VStrCI(NAME_smallfont2))) {
465 if (W_CheckNumForName(NAME_stbfn033) >= 0) {
466 GCon->Log(NAME_Init, " Strife secondary small font");
467 new VFont(NAME_smallfont2, "stbfn%03d", 33, 95, 33, -666);
471 // big font
472 if (!FontMap.has(VStrCI(NAME_bigfont))) {
473 bool haveBigFont = false;
474 if (W_CheckNumForName(NAME_bigfont) >= 0) {
475 GCon->Log(NAME_Init, " BigFont: from FON lump");
476 if (!GetFont(VStr(VName(NAME_bigfont)), VStr(VName(NAME_bigfont)))) {
477 GCon->Log(NAME_Init, " BigFont: cannot create");
478 } else {
479 haveBigFont = true;
482 if (!haveBigFont) {
483 GCon->Log(NAME_Init, " BigFont: from 'fontb' lumps");
484 auto fnt = new VFont(NAME_bigfont, "fontb%02d", 33, 95, 1, -666);
485 if (fnt->GetFontName() == NAME_None) {
486 GCon->Log(NAME_Init, " BigFont: cannot create");
487 } else {
488 haveBigFont = true;
491 if (!haveBigFont && W_CheckNumForName("dbigfont") >= 0) {
492 GCon->Log(NAME_Init, " BigFont: from FON lump");
493 if (!GetFont(VStr(VName(NAME_bigfont)), VStr("dbigfont"))) {
494 GCon->Log(NAME_Init, " BigFont: cannot create");
495 } else {
496 haveBigFont = true;
499 if (!haveBigFont) {
500 GCon->Log(NAME_Init, " BigFont: from console font");
501 if (!GetFont(VStr(VName(NAME_bigfont)), VStr(VAVOOM_CON_FONT_PATH))) Sys_Error("cannot create console font");
505 // console font
506 if (!FontMap.has(VStrCI(NAME_consolefont))) {
507 GCon->Log(NAME_Init, " ConsoleFont: from FON lump");
508 ConFont = GetFont(VStr(VName(NAME_consolefont)), /*NAME_confont*/VStr(VAVOOM_CON_FONT_PATH));
509 if (!ConFont) Sys_Error("cannot create console font");
514 //==========================================================================
516 // VFont::StaticShutdown
518 //==========================================================================
519 void VFont::StaticShutdown () {
520 VFont *F = Fonts;
521 while (F) {
522 VFont *Next = F->Next;
523 delete F;
524 F = Next;
526 Fonts = nullptr;
527 FontMap.clear();
528 TextColors.Clear();
529 TextColorLookup.Clear();
533 //==========================================================================
535 // VFont::ParseTextColors
537 //==========================================================================
538 void VFont::ParseTextColors () {
539 TArray<VTextColorDef> TempDefs;
540 TArray<VColTransMap> TempColors;
541 for (auto &&it : WadNSNameIterator(NAME_textcolo, WADNS_Global)) {
542 const int Lump = it.lump;
543 VScriptParser sc(*W_LumpName(Lump), W_CreateLumpReaderNum(Lump));
544 while (!sc.AtEnd()) {
545 VTextColorDef &Col = TempDefs.Alloc();
546 Col.FlatColor.r = 0;
547 Col.FlatColor.g = 0;
548 Col.FlatColor.b = 0;
549 Col.FlatColor.a = 255;
550 Col.Index = -1;
552 TArray<VName> Names;
553 VColTranslationDef TDef;
554 TDef.LumFrom = -1;
555 TDef.LumTo = -1;
557 // name for this color
558 sc.ExpectString();
559 Names.Append(*sc.String.ToLower());
560 // additional names
561 while (!sc.Check("{")) {
562 if (Names[0] == "untranslated") sc.Error("Color \"untranslated\" cannot have any other names");
563 sc.ExpectString();
564 Names.Append(*sc.String.ToLower());
567 int TranslationMode = 0;
568 while (!sc.Check("}")) {
569 if (sc.Check("Console:")) {
570 if (TranslationMode == 1) sc.Error("Only one console text color definition allowed");
571 TranslationMode = 1;
572 TDef.LumFrom = -1;
573 TDef.LumTo = -1;
574 } else if (sc.Check("Flat:")) {
575 sc.ExpectString();
576 vuint32 C = M_ParseColor(*sc.String);
577 Col.FlatColor.r = (C >> 16) & 255;
578 Col.FlatColor.g = (C >> 8) & 255;
579 Col.FlatColor.b = C & 255;
580 Col.FlatColor.a = 255;
581 } else {
582 // from color
583 sc.ExpectString();
584 vuint32 C = M_ParseColor(*sc.String);
585 TDef.From.r = (C >> 16) & 255;
586 TDef.From.g = (C >> 8) & 255;
587 TDef.From.b = C & 255;
588 TDef.From.a = 255;
590 // to color
591 sc.ExpectString();
592 C = M_ParseColor(*sc.String);
593 TDef.To.r = (C >> 16) & 255;
594 TDef.To.g = (C >> 8) & 255;
595 TDef.To.b = C & 255;
596 TDef.To.a = 255;
598 if (sc.CheckNumber()) {
599 // optional luminosity ranges
600 if (TDef.LumFrom == -1 && sc.Number != 0) sc.Error("First color range must start at position 0");
601 if (sc.Number < 0 || sc.Number > 256) sc.Error("Color index must be in range from 0 to 256");
602 if (sc.Number <= TDef.LumTo) sc.Error("Color range must start after end of previous range");
603 TDef.LumFrom = sc.Number;
605 sc.ExpectNumber();
606 if (sc.Number < 0 || sc.Number > 256) sc.Error("Color index must be in range from 0 to 256");
607 if (sc.Number <= TDef.LumFrom) sc.Error("Ending color index must be greater than start index");
608 TDef.LumTo = sc.Number;
609 } else {
610 // set default luminosity range
611 TDef.LumFrom = 0;
612 TDef.LumTo = 256;
615 if (TranslationMode == 0) Col.Translations.Append(TDef);
616 else if (TranslationMode == 1) Col.ConsoleTranslations.Append(TDef);
620 if (Names[0] == "untranslated") {
621 if (Col.Translations.length() != 0 || Col.ConsoleTranslations.length() != 0) {
622 sc.Error("The \"untranslated\" color must be left undefined");
624 } else {
625 if (Col.Translations.length() == 0) sc.Error("There must be at least one normal range for a color");
626 if (Col.ConsoleTranslations.length() == 0) {
627 // if console color translation is not defined, make it white
628 TDef.From.r = 0;
629 TDef.From.g = 0;
630 TDef.From.b = 0;
631 TDef.From.a = 255;
632 TDef.To.r = 255;
633 TDef.To.g = 255;
634 TDef.To.b = 255;
635 TDef.To.a = 255;
636 TDef.LumFrom = 0;
637 TDef.LumTo = 256;
638 Col.ConsoleTranslations.Append(TDef);
642 // add all names to the list of colors
643 for (int i = 0; i < Names.length(); ++i) {
644 // check for redefined colors
645 int CIdx;
646 for (CIdx = 0; CIdx < TempColors.length(); ++CIdx) {
647 if (TempColors[CIdx].Name == Names[i]) {
648 TempColors[CIdx].Index = TempDefs.length()-1;
649 break;
652 if (CIdx == TempColors.length()) {
653 VColTransMap &CMap = TempColors.Alloc();
654 CMap.Name = Names[i];
655 CMap.Index = TempDefs.length()-1;
661 // put color definitions in it's final location
662 for (int i = 0; i < TempColors.length(); ++i) {
663 VColTransMap &TmpCol = TempColors[i];
664 VTextColorDef &TmpDef = TempDefs[TmpCol.Index];
665 if (TmpDef.Index == -1) {
666 TmpDef.Index = TextColors.length();
667 TextColors.Append(TmpDef);
669 VColTransMap &Col = TextColorLookup.Alloc();
670 Col.Name = TmpCol.Name;
671 Col.Index = TmpDef.Index;
674 // make sure all built-in colors are defined
675 vassert(TextColors.length() >= NUM_TEXT_COLORS);
679 //==========================================================================
681 // VFont::ParseFontDefs
683 //==========================================================================
684 void VFont::ParseFontDefs () {
685 #define CHECK_TYPE(Id) if (FontType == Id) \
686 sc.Error(va("Invalid combination of properties in font '%s'", *FontName))
688 for (auto &&it : WadNSNameIterator(NAME_fontdefs, WADNS_Global)) {
689 const int Lump = it.lump;
690 VScriptParser sc(*W_LumpName(Lump), W_CreateLumpReaderNum(Lump));
691 GCon->Logf(NAME_Init, "parsing font definition '%s'", *W_FullLumpName(Lump));
692 while (!sc.AtEnd()) {
693 // name of the font
694 sc.ExpectString();
695 VName FontName(*sc.String, VName::AddLower);
696 sc.Expect("{");
698 int FontType = 0;
699 VStr Template;
700 int First = 33;
701 int Count = 223;
702 int Start = 33;
703 int SpaceWidth = -666;
704 TArray<int> CharIndexes;
705 TArray<VName> CharLumps;
706 bool NoTranslate[256];
707 memset(NoTranslate, 0, sizeof(NoTranslate));
709 while (!sc.Check("}")) {
710 if (sc.Check("template")) {
711 CHECK_TYPE(2);
712 sc.ExpectString();
713 Template = sc.String;
714 FontType = 1;
715 //GCon->Logf(NAME_Debug, "*** TPL: <%s>", *Template);
716 } else if (sc.Check("first")) {
717 CHECK_TYPE(2);
718 sc.ExpectNumber();
719 First = sc.Number;
720 FontType = 1;
721 } else if (sc.Check("count")) {
722 CHECK_TYPE(2);
723 sc.ExpectNumber();
724 Count = sc.Number;
725 FontType = 1;
726 } else if (sc.Check("base")) {
727 CHECK_TYPE(2);
728 sc.ExpectNumber();
729 Start = sc.Number;
730 FontType = 1;
731 } else if (sc.Check("notranslate") || sc.Check("notranslation")) {
732 CHECK_TYPE(1);
733 while (sc.CheckNumber() && !sc.Crossed) {
734 if (sc.Number >= 0 && sc.Number < 256) NoTranslate[sc.Number] = true;
736 FontType = 2;
737 } else if (sc.Check("cursor")) {
738 sc.Message("fontdef 'CURSOR' property is not supported");
739 sc.ExpectString();
740 } else if (sc.Check("spacewidth")) {
741 sc.ExpectNumber();
742 SpaceWidth = sc.Number;
743 } else {
744 CHECK_TYPE(1);
745 sc.ExpectLoneChar();
746 const char *CPtr = *sc.String;
747 int CharIdx = VStr::Utf8GetChar(CPtr);
748 sc.ExpectString();
749 VName LumpName(*sc.String, VName::AddLower8);
750 if (W_CheckNumForName(LumpName, WADNS_Graphics) >= 0) {
751 CharIndexes.Append(CharIdx);
752 CharLumps.Append(LumpName);
753 } else {
754 GCon->Logf(NAME_Error, "font '%s': char #%d not found (%s)", *FontName, CharIdx, *LumpName);
756 FontType = 2;
760 if (FontName != NAME_None) {
761 if (FontType == 1) {
762 GCon->Logf(NAME_Init, "creating font '%s'...", *FontName);
763 auto fnt = new VFont(FontName, Template, First, Count, Start, SpaceWidth);
764 if (fnt->GetFontName() == NAME_None) {
765 GCon->Logf(NAME_Error, "FONT: cannot create font '%s'!", *FontName);
766 delete fnt;
768 } else if (FontType == 2) {
769 if (CharIndexes.length()) {
770 GCon->Logf(NAME_Init, "creating special font '%s'", *FontName);
771 new VSpecialFont(FontName, CharIndexes, CharLumps, NoTranslate, SpaceWidth);
773 } else {
774 sc.Error("Font has no attributes");
779 #undef CHECK_TYPE
783 //==========================================================================
785 // VFont::GetPathFromFontName
787 //==========================================================================
788 VStr VFont::GetPathFromFontName (VStr aname) {
789 int cp = aname.indexOf(':');
790 if (cp < 0) return aname;
791 VStr res(*aname+cp+1);
792 res = res.xstrip();
793 if (res.isEmpty()) return aname;
794 return res;
798 //==========================================================================
800 // VFont::TrimPathFromFontName
802 //==========================================================================
803 VStr VFont::TrimPathFromFontName (VStr aname) {
804 int cp = aname.indexOf(':');
805 if (cp < 0) return aname;
806 VStr res(*aname, cp);
807 res = res.xstrip();
808 if (res.isEmpty()) return aname;
809 return res;
813 //==========================================================================
815 // VFont::FindFont
817 //==========================================================================
818 VFont *VFont::FindFont (VStr AName) {
819 #if 0
820 for (VFont *F = Fonts; F; F = F->Next) {
821 if (F->Name == AName || VStr::strEquCI(F->Name, *AName)) return F;
823 #else
824 if (!AName.isEmpty()) {
825 VStr n = TrimPathFromFontName(AName);
826 auto fpp = FontMap.get(n);
827 if (fpp) return *fpp;
829 #endif
830 return nullptr;
834 //==========================================================================
836 // VFont::FindFont
838 //==========================================================================
839 VFont *VFont::FindFont (VName AName) {
840 if (AName != NAME_None) return FindFont(VStr(AName)); // this doesn't allocate
841 return nullptr;
845 //==========================================================================
847 // VFont::FindAndLoadFontFromLumpIdx
849 //==========================================================================
850 VFont *VFont::FindAndLoadFontFromLumpIdx (VStr AName, int LumpIdx) {
851 if (LumpIdx < 0 || AName.isEmpty()) return nullptr;
852 // read header
853 char Hdr[4];
854 { // so the stream will be destroyed automatically
855 VStream *lumpstream = W_CreateLumpReaderNum(LumpIdx);
856 VCheckedStream Strm(lumpstream);
857 if (Strm.TotalSize() < 8) return nullptr;
858 Strm.Serialise(Hdr, 4);
860 if (Hdr[0] == 'F' && Hdr[1] == 'O' && Hdr[2] == 'N') {
861 //GCon->Logf(NAME_Debug, "FONT%c: <%s>", Hdr[3], *AName);
862 if (Hdr[3] == '1') return new VFon1Font(VName(*AName, VName::AddLower), LumpIdx);
863 if (Hdr[3] == '2') return new VFon2Font(VName(*AName, VName::AddLower), LumpIdx);
865 return nullptr;
869 //==========================================================================
871 // VFont::GetFont
873 //==========================================================================
874 VFont *VFont::GetFont (VStr AName, VStr LumpName) {
875 //GCon->Logf(NAME_Debug, "VFont::GetFont: name=<%s>; lump=<%s>", *AName, *LumpName);
877 VFont *F = FindFont(AName);
878 if (F) return F;
880 if (AName.isEmpty()) return nullptr;
882 if (LumpName.isEmpty()) return nullptr;
884 VName ln(*LumpName, VName::FindLower);
886 if (ln == NAME_None) {
887 // try file path (there is no reason to try a lump, we don't have one)
888 int flump = W_CheckNumForFileName(LumpName);
889 return FindAndLoadFontFromLumpIdx(AName, flump);
892 // check for wad lump
893 const int Lump = W_CheckNumForName(ln);
894 F = FindAndLoadFontFromLumpIdx(AName, Lump);
895 if (F) return F;
897 // try a texture
898 int TexNum = GTextureManager.CheckNumForName(ln, TEXTYPE_Any);
899 if (TexNum <= 0) TexNum = GTextureManager.AddPatch(ln, TEXTYPE_Pic);
900 if (TexNum > 0) return new VSingleTextureFont(VName(*AName, VName::AddLower), TexNum);
902 return nullptr;
906 //==========================================================================
908 // VFont::GetFont
910 //==========================================================================
911 VFont *VFont::GetFont (VStr AName) {
912 VStr LumpName = GetPathFromFontName(AName);
913 AName = TrimPathFromFontName(AName);
914 return GetFont(AName, LumpName);
919 //**************************************************************************
921 // VFont
923 //**************************************************************************
925 //==========================================================================
927 // VFont::VFont
929 //==========================================================================
930 VFont::VFont () : Name(NAME_None) {
934 //==========================================================================
936 // VFont::VFont
938 //==========================================================================
939 VFont::VFont (VName AName, VStr FormatStr, int First, int Count, int StartIndex, int ASpaceWidth) {
940 for (unsigned i = 0; i < 128; ++i) AsciiChars[i] = -1;
941 FirstChar = -1;
942 LastChar = -1;
943 FontHeight = 0;
944 Kerning = 0;
945 Translation = nullptr;
946 bool ColorsUsed[256];
947 memset(ColorsUsed, 0, sizeof(ColorsUsed));
949 if (!safeFormat(FormatStr)) Sys_Error("font format string \"%s\" is not safe!", *FormatStr.quote());
950 //GCon->Logf(NAME_Debug, "**** format \"%s\" is SAFE!", *FormatStr.quote());
952 const bool allowCFonLower = FormatStr.startsWithCI("stcfn");
954 bool wasAnyChar = false;
956 //GCon->Logf(NAME_Debug, "creating font of type 1; Template=<%s>; First=%d; Count=%d; Start=%d", *FormatStr, First, Count, StartIndex);
957 for (int i = 0; i < Count; ++i) {
958 int Char = i+First;
959 char Buffer[10];
960 //FIXME: replace this with safe variant
961 int prc = snprintf(Buffer, sizeof(Buffer), *FormatStr, i+StartIndex);
962 if (!Buffer[0] || prc < 1 || prc > 9) continue;
963 VName LumpName(Buffer, VName::AddLower8);
965 int Lump = W_CheckNumForName(LumpName, WADNS_Graphics);
966 // our UI cannot support hires fonts without lores templates, and i will not implement that
968 if (Lump < 0) Lump = W_CheckNumForName(LumpName, WADNS_NewTextures);
969 if (Lump < 0) Lump = W_CheckNumForName(LumpName, WADNS_HiResTextures);
970 if (Lump < 0) Lump = W_CheckNumForName(LumpName, WADNS_HiResTexturesDDay);
971 //if (Lump < 0) = W_CheckNumForName(LumpName, WADNS_HiResFlatsDDay);
973 VTexture *knownTexture = nullptr;
975 int tid = GTextureManager.CheckNumForName(LumpName, TEXTYPE_Pic, true);
976 if (tid > 0) {
977 knownTexture = GTextureManager[tid];
978 //GCon->Logf(NAME_Debug, "*** CHAR %d: lump is <%s>; texture id is %d (%s)", Char, *LumpName, tid, *W_FullLumpName(knownTexture->SourceLump));
982 // try lowercased ("cfontXXX")
983 bool iscfont = false;
984 if (!knownTexture && Lump < 0 && allowCFonLower && i >= 'a' && i <= 'z') {
985 char tb[64];
986 snprintf(tb, sizeof(tb), "%s", *LumpName);
987 if (strlen(tb) > 5) {
988 tb[0] = 'c';
989 tb[1] = 'f';
990 tb[2] = 'o';
991 tb[3] = 'n';
992 tb[4] = 't';
993 VName ln2(tb, VName::FindLower8);
994 if (ln2 != NAME_None) {
995 Lump = W_CheckNumForName(ln2, WADNS_Graphics);
996 if (Lump >= 0) LumpName = ln2;
997 iscfont = true;
1002 //GCon->Logf(NAME_Debug, " char %d (%c): lump=%d (%s)", Char, (Char >= 32 && Char < 127 ? Char : '?'), Lump, (Lump >= 0 ? *W_FullLumpName(Lump) : "<oops>"));
1004 // in Doom, stcfn121 is actually a '|' and not 'y' and many wad
1005 // authors provide it as such, so put it in correct location
1006 if (!iscfont && LumpName == "stcfn121" &&
1007 (W_CheckNumForName("stcfn120", WADNS_Graphics) == -1 ||
1008 W_CheckNumForName("stcfn122", WADNS_Graphics) == -1))
1010 Char = '|';
1013 if (Lump >= 0 || knownTexture) {
1014 VTexture *Tex = (knownTexture ? knownTexture : GTextureManager[GTextureManager.AddPatchLump(Lump, LumpName, TEXTYPE_Pic)]);
1015 if (Tex) {
1016 FFontChar &FChar = Chars.Alloc();
1017 FChar.Char = Char;
1018 FChar.TexNum = -1;
1019 FChar.BaseTex = Tex;
1020 FChar.Rect.setFull();
1021 if (Char < 128) AsciiChars[Char] = Chars.length()-1;
1023 // calculate height of font character and adjust font height as needed
1024 int Height = Tex->GetScaledHeightI();
1025 FChar.Width = Tex->GetScaledWidthI();
1026 FChar.Height = Height;
1028 int TOffs = Tex->GetScaledTOffsetI();
1029 Height += abs(TOffs);
1030 if (FontHeight < Height) FontHeight = Height;
1033 // update first and last characters
1034 if (FirstChar == -1 || FirstChar > Char) FirstChar = Char;
1035 if (LastChar == -1 || LastChar < Char) LastChar = Char;
1037 // mark colors that are used by this texture
1038 MarkUsedColors(Tex, ColorsUsed);
1040 (void)Tex->GetMaxIntensity(); // cache it
1042 //if (developer) GCon->Logf(NAME_Dev, "loaded char lump '%s' as '%s' (%d) for char %d of font '%s'", *W_FullLumpName(Lump), *LumpName, Lump, Char, *AName);
1043 wasAnyChar = true;
1044 } else {
1045 GCon->Logf(NAME_Warning, "cannot load char lump for char %d of font '%s'", Char, *AName);
1050 // if we have no char textures, don't register this font
1051 // this way we will have standard fonts visible of override failed
1052 if (!wasAnyChar) {
1053 AName = NAME_None;
1054 return;
1057 RegisterFont(this, AName);
1059 // set up width of a space character as half width of N character
1060 // or 4 if character N has no graphic for it
1061 if (ASpaceWidth < 0) {
1062 int NIdx = FindChar('N');
1063 if (NIdx >= 0) {
1064 SpaceWidth = max2(1, (Chars[NIdx].Width+1)/2);
1065 } else {
1066 SpaceWidth = 4;
1068 } else {
1069 SpaceWidth = ASpaceWidth;
1072 BuildTranslations(ColorsUsed, r_palette, false, true);
1074 // create texture objects for all different colors
1075 for (auto &&cc : Chars) {
1076 cc.Textures = new VTexture*[TextColors.length()];
1077 for (int j = 0; j < TextColors.length(); ++j) {
1078 cc.Textures[j] = new VFontChar(cc.BaseTex, Translation+j*256);
1079 // currently all render drivers expect all textures to be registered in texture manager
1080 GTextureManager.AddTexture(cc.Textures[j]);
1086 //==========================================================================
1088 // VFont::~VFont
1090 //==========================================================================
1091 VFont::~VFont () {
1092 for (auto &&cc : Chars) {
1093 if (cc.Textures) {
1094 delete[] cc.Textures;
1095 cc.Textures = nullptr;
1098 Chars.Clear();
1099 if (Translation) {
1100 delete[] Translation;
1101 Translation = nullptr;
1106 //==========================================================================
1108 // VFont::IsSingleTextureFont
1110 //==========================================================================
1111 bool VFont::IsSingleTextureFont () const noexcept {
1112 return false;
1116 //==========================================================================
1118 // VFont::BuildTranslations
1120 //==========================================================================
1121 void VFont::BuildTranslations (const bool *ColorsUsed, rgba_t *Pal, bool ConsoleTrans, bool Rescale) {
1122 // calculate luminosity for all colors and find minimal and maximal values for used colors
1123 float Luminosity[256];
1124 float MinLum = 1000000.0f;
1125 float MaxLum = 0.0f;
1126 for (int i = 1; i < 256; ++i) {
1127 Luminosity[i] = Pal[i].r*0.299f+Pal[i].g*0.587f+Pal[i].b*0.114f;
1128 if (ColorsUsed[i]) {
1129 if (MinLum > Luminosity[i]) MinLum = Luminosity[i];
1130 if (MaxLum < Luminosity[i]) MaxLum = Luminosity[i];
1134 // create gradual luminosity values
1135 float Scale = 1.0f/(Rescale ? MaxLum-MinLum : 255.0f);
1136 for (int i = 1; i < 256; ++i) {
1137 Luminosity[i] = (Luminosity[i]-MinLum)*Scale;
1138 Luminosity[i] = midval(0.0f, Luminosity[i], 1.0f);
1141 Translation = new rgba_t[256*TextColors.length()];
1142 for (int ColIdx = 0; ColIdx < TextColors.length(); ++ColIdx) {
1143 rgba_t *pOut = Translation+ColIdx*256;
1144 const TArray<VColTranslationDef> &TList = (ConsoleTrans ? TextColors[ColIdx].ConsoleTranslations : TextColors[ColIdx].Translations);
1145 if (ColIdx == CR_UNTRANSLATED || !TList.length()) {
1146 memcpy(pOut, Pal, 4*256);
1147 continue;
1150 pOut[0] = Pal[0];
1151 for (int i = 1; i < 256; ++i) {
1152 int ILum = (int)(Luminosity[i]*256);
1153 int TDefIdx = 0;
1154 while (TDefIdx < TList.length()-1 && ILum > TList[TDefIdx].LumTo) ++TDefIdx;
1155 const VColTranslationDef &TDef = TList[TDefIdx];
1157 // linearly interpolate between colors
1158 float v = ((float)(ILum-TDef.LumFrom)/(float)(TDef.LumTo-TDef.LumFrom));
1159 int r = (int)((1.0f-v)*TDef.From.r+v*TDef.To.r);
1160 int g = (int)((1.0f-v)*TDef.From.g+v*TDef.To.g);
1161 int b = (int)((1.0f-v)*TDef.From.b+v*TDef.To.b);
1162 pOut[i].r = clampToByte(r);
1163 pOut[i].g = clampToByte(g);
1164 pOut[i].b = clampToByte(b);
1165 pOut[i].a = 255;
1171 //==========================================================================
1173 // VFont::BuildCharMap
1175 //==========================================================================
1176 void VFont::BuildCharMap () {
1177 if (CharMap.length() || !Chars.length()) return;
1178 CharMap.clear();
1179 for (auto &&it : Chars.itemsIdx()) {
1180 CharMap.put(it.value().Char, it.index());
1185 //==========================================================================
1187 // VFont::FindChar
1189 //==========================================================================
1190 int VFont::FindChar (int Chr) {
1191 // check if character is outside of available character range
1192 if (Chr < FirstChar || Chr > LastChar) return -1;
1193 // fast look-up for ASCII characters
1194 if (Chr >= 0 && Chr < 128) return AsciiChars[Chr];
1195 BuildCharMap();
1196 auto pp = CharMap.get(Chr);
1197 return (pp ? *pp : -1);
1201 //==========================================================================
1203 // VFont::GetChar
1205 //==========================================================================
1206 VTexture *VFont::GetChar (int Chr, CharRect *rect, int *pWidth, int Color) {
1207 int Idx = FindChar(Chr);
1208 if (Idx < 0) {
1209 // try upper-case letter
1210 Chr = VStr::ToUpper(Chr);
1211 Idx = FindChar(Chr);
1212 if (Idx < 0) {
1213 if (pWidth) *pWidth = SpaceWidth;
1214 if (rect) rect->clear();
1215 return nullptr;
1219 if (Color < 0 || Color >= TextColors.length()) Color = CR_UNTRANSLATED;
1220 VTexture *Tex = Chars[Idx].Textures ? Chars[Idx].Textures[Color] :
1221 Chars[Idx].TexNum > 0 ? GTextureManager(Chars[Idx].TexNum) :
1222 Chars[Idx].BaseTex;
1223 // need to do it here, so renderer could get the right dimensions
1224 VTexture *HiTex = (Tex ? Tex->GetHighResolutionTexture() : nullptr);
1225 if (HiTex) Tex = HiTex;
1226 if (pWidth) *pWidth = Chars[Idx].Width;
1227 if (rect) *rect = Chars[Idx].Rect;
1228 return Tex;
1232 //==========================================================================
1234 // VFont::GetCharWidth
1236 //==========================================================================
1237 int VFont::GetCharWidth (int Chr) {
1238 int Idx = FindChar(Chr);
1239 if (Idx < 0) {
1240 // try upper-case letter
1241 Chr = VStr::ToUpper(Chr);
1242 Idx = FindChar(Chr);
1243 if (Idx < 0) return SpaceWidth;
1245 return Chars[Idx].Width;
1249 //==========================================================================
1251 // VFont::MarkUsedColors
1253 //==========================================================================
1254 void VFont::MarkUsedColors (VTexture *Tex, bool *Used) {
1255 if (!Tex /*|| Tex->Format == TEXFMT_RGBA*/) return; // wtf?
1256 const vuint8 *Pixels = Tex->GetPixels8();
1257 int Count = Tex->GetWidth()*Tex->GetHeight();
1258 for (int i = 0; i < Count; ++i) Used[Pixels[i]] = true;
1262 //==========================================================================
1264 // VFont::ParseColorEscape
1266 // assumes that pColor points to the character right after the color
1267 // escape character.
1269 // if `escstr` is not `nullptr`, put escate into it (including escape char)
1271 // returns negative for RGB (see `VWidget::DrawCharPic()`)
1273 //==========================================================================
1274 int VFont::ParseColorEscape (const char *&pColor, int NormalColor, int BoldColor, VStr *escstr) {
1275 if (!pColor || !pColor[0]) {
1276 if (escstr) escstr->clear();
1277 return CR_UNDEFINED;
1280 if (escstr) (*escstr) = VStr((char)TEXT_COLOR_ESCAPE);
1282 int Col = *pColor++;
1284 if (Col >= 'a' && Col <= 'z') Col = Col-'a'+'A';
1286 if (Col == '[') {
1287 // named colors
1288 char nbuf[32];
1289 size_t nbpos = 0;
1290 for (;;) {
1291 const char ch = *pColor++;
1292 if (!ch) { --pColor; break; }
1293 if (ch == ']') break;
1294 if (nbpos < sizeof(nbuf)-1) nbuf[nbpos++] = ch;
1296 nbuf[nbpos] = 0;
1297 Col = FindTextColor(nbuf, CR_UNDEFINED);
1298 if (escstr) {
1299 if (Col != CR_UNDEFINED) {
1300 (*escstr) += '[';
1301 (*escstr) += VStr(nbuf);
1302 (*escstr) += ']';
1303 } else {
1304 escstr->clear();
1307 } else if (Col >= 'A' && Col <= 'Z' /*Col < 'A'+NUM_TEXT_COLORS*/) {
1308 if (escstr) (*escstr) += (char)Col;
1309 Col -= 'A'; // standard colors
1310 } else if (Col == '-') {
1311 if (escstr) (*escstr) += '-';
1312 Col = NormalColor; // normal color
1313 } else if (Col == '+') {
1314 if (escstr) (*escstr) += '+';
1315 Col = BoldColor; // bold color
1316 } else {
1317 --pColor;
1318 Col = CR_UNDEFINED;
1319 if (escstr) escstr->clear();
1322 return Col;
1326 //==========================================================================
1328 // VFont::FindTextColor
1330 //==========================================================================
1331 int VFont::FindTextColor (const char *Name, int defval) {
1332 if (!Name || !Name[0]) return defval;
1333 // "normal" color name
1334 if (Name[1] && (Name[0] == '!' || Name[0] == '#')) {
1335 if (Name[0] == '!') ++Name;
1336 vuint32 cc = M_ParseColor(Name, true/*retZeroIfInvalid*/);
1337 if (cc) { vassert(cc&0xff000000u); return (int)cc; }
1338 return defval; // translation name cannot start with '!' or '#'
1340 // translation names
1341 for (auto &&it : TextColorLookup) {
1342 if (VStr::strEquCI(*it.Name, Name)) return it.Index;
1344 return defval;
1348 //==========================================================================
1350 // VFont::StringWidth
1352 //==========================================================================
1353 int VFont::StringWidth (VStr String) {
1354 int w = 0;
1355 if (!String.isEmpty()) {
1356 const char *SPtr = *String;
1357 for (;;) {
1358 const int c = VStr::Utf8GetChar(SPtr);
1359 if (!c) break;
1360 // check for color escape
1361 if (c == TEXT_COLOR_ESCAPE) {
1362 ParseColorEscape(SPtr, CR_UNDEFINED, CR_UNDEFINED);
1363 } else {
1364 w += GetCharWidth(c)+GetKerning();
1368 return w;
1372 //==========================================================================
1374 // VFont::TextWidth
1376 //==========================================================================
1377 int VFont::TextWidth (VStr String) {
1378 const int len = String.length();
1379 if (len == 0) return 0;
1380 int res = 0, pos = 0, start = 0;
1381 for (;;) {
1382 if (pos >= len || String[pos] == '\n') {
1383 int curw = StringWidth(VStr(String, start, pos-start));
1384 if (res < curw) res = curw;
1385 if (++pos >= len) break;
1386 start = pos;
1387 } else {
1388 ++pos;
1391 return res;
1395 //==========================================================================
1397 // VFont::TextHeight
1399 //==========================================================================
1400 int VFont::TextHeight (VStr String) {
1401 int h = FontHeight;
1402 const int len = String.length();
1403 if (len > 0) {
1404 const char *s = *String;
1405 for (int f = len; f > 0; --f, ++s) if (*s == '\n') h += FontHeight;
1407 return h;
1411 //==========================================================================
1413 // VFont::SplitText
1415 //==========================================================================
1416 int VFont::SplitText (VStr Text, TArray<VSplitLine> &Lines, int MaxWidth, bool trimRight) {
1417 Lines.Clear();
1418 const char *Start = *Text;
1419 bool WordStart = true;
1420 int CurW = 0;
1421 VStr lineCEsc;
1422 VStr lastCEsc;
1423 for (const char *SPtr = *Text; *SPtr; ) {
1424 const char *PChar = SPtr;
1425 int c = VStr::Utf8GetChar(SPtr);
1427 // check for color escape
1428 if (c == TEXT_COLOR_ESCAPE) {
1429 ParseColorEscape(SPtr, CR_UNDEFINED, CR_UNDEFINED, &lastCEsc);
1430 //fprintf(stderr, "!!!!! <%s>\n", *VStr(SPtr).quote());
1431 } else if (c == '\n') {
1432 VSplitLine &L = Lines.Alloc();
1433 L.Text = lineCEsc+VStr(Text, (int)(ptrdiff_t)(Start-(*Text)), (int)(ptrdiff_t)(PChar-Start));
1434 L.Width = CurW;
1435 Start = SPtr;
1436 WordStart = true;
1437 CurW = 0;
1438 // newline clears all color sequences
1439 lineCEsc.clear();
1440 lastCEsc.clear();
1441 } else if (WordStart && c > ' ') {
1442 const char *SPtr2 = SPtr;
1443 int c2 = c;
1444 int NewW = CurW;
1445 while (c2 > ' ' || c2 == TEXT_COLOR_ESCAPE) {
1446 if (c2 != TEXT_COLOR_ESCAPE) NewW += GetCharWidth(c2);
1447 c2 = VStr::Utf8GetChar(SPtr2);
1448 // check for color escape
1449 if (c2 == TEXT_COLOR_ESCAPE) ParseColorEscape(SPtr2, CR_UNDEFINED, CR_UNDEFINED, &lastCEsc);
1451 if (NewW > MaxWidth && PChar != Start) {
1452 VSplitLine &L = Lines.Alloc();
1453 L.Text = lineCEsc+VStr(Text, (int)(ptrdiff_t)(Start-(*Text)), (int)(ptrdiff_t)(PChar-Start));
1454 L.Width = CurW;
1455 Start = PChar;
1456 CurW = 0;
1457 lineCEsc = lastCEsc;
1459 WordStart = false;
1460 CurW += GetCharWidth(c);
1461 } else if (c <= ' ') {
1462 WordStart = true;
1463 CurW += GetCharWidth(c);
1464 } else {
1465 CurW += GetCharWidth(c);
1468 if (!*SPtr && Start != SPtr) {
1469 VSplitLine &L = Lines.Alloc();
1470 L.Text = lineCEsc+Start;
1471 L.Width = CurW;
1472 lineCEsc = lastCEsc;
1476 if (trimRight) {
1477 for (int f = 0; f < Lines.length(); ++f) {
1478 VStr s = Lines[f].Text;
1479 while (s.length() && (vuint8)s[s.length()-1] <= 32) s.chopRight(1);
1480 if (Lines[f].Text.length() != s.length()) {
1481 Lines[f].Text = s;
1482 Lines[f].Width = StringWidth(s);
1487 return Lines.length()*FontHeight;
1491 //==========================================================================
1493 // VFont::SplitTextWithNewlines
1495 //==========================================================================
1496 VStr VFont::SplitTextWithNewlines (VStr Text, int MaxWidth, bool trimRight) {
1497 TArray<VSplitLine> Lines;
1498 SplitText(Text, Lines, MaxWidth, trimRight);
1499 VStr Ret;
1500 for (int i = 0; i < Lines.length(); ++i) {
1501 if (i != 0) Ret += "\n";
1502 Ret += Lines[i].Text;
1504 return Ret;
1509 //**************************************************************************
1511 // VSpecialFont
1513 //**************************************************************************
1515 //==========================================================================
1517 // VSpecialFont::VSpecialFont
1519 //==========================================================================
1520 VSpecialFont::VSpecialFont (VName AName, const TArray<int> &CharIndexes, const TArray<VName> &CharLumps, const bool *NoTranslate, int ASpaceWidth) {
1521 RegisterFont(this, AName);
1523 for (int i = 0; i < 128; ++i) AsciiChars[i] = -1;
1524 FirstChar = -1;
1525 LastChar = -1;
1526 FontHeight = 0;
1527 Kerning = 0;
1528 Translation = nullptr;
1529 bool ColorsUsed[256];
1530 memset(ColorsUsed, 0, sizeof(ColorsUsed));
1532 vassert(CharIndexes.length() == CharLumps.length());
1533 for (int i = 0; i < CharIndexes.length(); ++i) {
1534 int Char = CharIndexes[i];
1535 if (Char < 0) Char = (vuint8)(Char&0xff); // just in case
1536 VName LumpName = CharLumps[i];
1538 int texid = GTextureManager.AddPatch(LumpName, TEXTYPE_Pic);
1539 if (texid <= 0) {
1540 GCon->Logf(NAME_Error, "font '%s': missing patch '%s' for char #%d", *AName, *LumpName, Char);
1541 texid = -1;
1544 VTexture *Tex = GTextureManager[texid];
1545 if (Tex) {
1546 FFontChar &FChar = Chars.Alloc();
1547 FChar.Char = Char;
1548 FChar.TexNum = -1;
1549 FChar.BaseTex = Tex;
1550 FChar.Rect.setFull();
1551 if (Char < 128) AsciiChars[Char] = Chars.length()-1;
1553 //GCon->Logf(NAME_Debug, "font '%s': char #%d (%c): patch '%s', texture size:%dx%d; ofs:%d,%d; scale:%g,%g", *AName, Char, (Char > 32 && Char < 127 ? (char)Char : '?'), *LumpName, Tex->GetWidth(), Tex->GetHeight(), Tex->SOffset, Tex->TOffset, Tex->SScale, Tex->TScale);
1555 // calculate height of font character and adjust font height as needed
1556 int Height = Tex->GetScaledHeightI();
1557 FChar.Width = Tex->GetScaledWidthI();
1558 FChar.Height = Height;
1560 int TOffs = Tex->GetScaledTOffsetI();
1561 Height += abs(TOffs);
1562 if (FontHeight < Height) FontHeight = Height;
1564 // update first and last characters
1565 if (FirstChar == -1 || FirstChar > Char) FirstChar = Char;
1566 if (LastChar == -1 || LastChar < Char) LastChar = Char;
1568 // mark colors that are used by this texture
1569 MarkUsedColors(Tex, ColorsUsed);
1571 (void)Tex->GetMaxIntensity(); // cache it
1575 // exclude non-translated colors from calculations
1576 for (int i = 0; i < 256; ++i) if (NoTranslate[i]) ColorsUsed[i] = false;
1578 // set up width of a space character as half width of N character
1579 // or 4 if character N has no graphic for it
1580 if (ASpaceWidth < 0) {
1581 int NIdx = FindChar('N');
1582 if (NIdx >= 0) {
1583 SpaceWidth = max2(1, (Chars[NIdx].Width+1)/2);
1584 } else {
1585 SpaceWidth = 4;
1587 } else {
1588 SpaceWidth = ASpaceWidth;
1591 BuildTranslations(ColorsUsed, r_palette, false, true);
1593 // map non-translated colors to their original values
1594 for (int i = 0; i < TextColors.length(); ++i) {
1595 for (int j = 0; j < 256; ++j) {
1596 if (NoTranslate[j]) Translation[i*256+j] = r_palette[j];
1600 // create texture objects for all different colors
1601 for (auto &&cc : Chars) {
1602 cc.Textures = new VTexture*[TextColors.length()];
1603 for (int j = 0; j < TextColors.length(); ++j) {
1604 cc.Textures[j] = new VFontChar(cc.BaseTex, Translation+j*256);
1605 // currently all render drivers expect all textures to be registered in texture manager
1606 GTextureManager.AddTexture(cc.Textures[j]);
1613 //**************************************************************************
1615 // VFon1Font
1617 //**************************************************************************
1619 //==========================================================================
1621 // VFon1Font::VFon1Font
1623 //==========================================================================
1624 VFon1Font::VFon1Font (VName AName, int LumpNum) :
1625 VFontBitmapBase()
1627 RegisterFont(this, AName);
1629 VStream *lumpstream = W_CreateLumpReaderNum(LumpNum);
1630 VCheckedStream Strm(lumpstream);
1631 // skip ID
1632 Strm.Seek(4);
1633 vuint16 w;
1634 vuint16 h;
1635 Strm << w << h;
1636 SpaceWidth = w;
1637 FontHeight = h;
1639 if (SpaceWidth == 0 || FontHeight == 0) Sys_Error("font '%s' at '%s' has zero dimensions", *AName, *W_FullLumpName(LumpNum));
1640 if (SpaceWidth > 1024-2 || FontHeight > 1024-2) Sys_Error("font '%s' at '%s' has too big dimensions", *AName, *W_FullLumpName(LumpNum));
1642 FirstChar = 0;
1643 LastChar = 255;
1644 Kerning = 0;
1645 Translation = nullptr;
1646 for (int i = 0; i < 128; ++i) AsciiChars[i] = i;
1648 // mark all colors as used and construct a grayscale palette
1649 bool ColorsUsed[256];
1650 rgba_t Pal[256];
1651 ColorsUsed[0] = false;
1652 Pal[0].r = 0;
1653 Pal[0].g = 0;
1654 Pal[0].b = 0;
1655 Pal[0].a = 0;
1656 for (int i = 1; i < 256; ++i) {
1657 ColorsUsed[i] = true;
1658 Pal[i].r = (i-1)*255/254;
1659 Pal[i].g = Pal[i].r;
1660 Pal[i].b = Pal[i].r;
1661 Pal[i].a = 255;
1664 BuildTranslations(ColorsUsed, Pal, true, false);
1666 CalculateAtlasSize(((SpaceWidth+2)*(FontHeight+2))*256);
1667 GCon->Logf(NAME_Debug, "atlas size for font '%s' from '%s' is %dx%d", *AName, *W_FullLumpName(LumpNum), asize, asize);
1669 for (int i = 0; i < 256; ++i) {
1670 ReadFONxChar(Strm, AName, LumpNum, i, SpaceWidth, FontHeight);
1672 DoneReadingFONx();
1677 //**************************************************************************
1679 // VFon2Font
1681 //**************************************************************************
1683 //==========================================================================
1685 // VFon2Font::VFon2Font
1687 // 4 - header
1688 // 2 - height
1689 // 1 - first char
1690 // 1 - last char
1691 // 1 - fixed width flag
1692 // 1
1693 // 1 - active colors
1694 // 1 - kerning flag
1695 // 2 (if have flag) - kerning
1697 //==========================================================================
1698 VFon2Font::VFon2Font (VName AName, int LumpNum) {
1699 RegisterFont(this, AName);
1701 VStream *lumpstream = W_CreateLumpReaderNum(LumpNum);
1702 VCheckedStream Strm(lumpstream);
1703 // skip ID
1704 Strm.Seek(4);
1706 // read header
1707 FontHeight = Streamer<vuint16>(Strm);
1708 FirstChar = Streamer<vuint8>(Strm);
1709 LastChar = Streamer<vuint8>(Strm);
1710 vuint8 FixedWidthFlag;
1711 vuint8 RescalePal;
1712 vuint8 ActiveColors;
1713 vuint8 KerningFlag;
1714 Strm << FixedWidthFlag << RescalePal << ActiveColors << KerningFlag;
1715 Kerning = 0;
1716 if (KerningFlag&1) Kerning = Streamer<vint16>(Strm);
1718 Translation = nullptr;
1719 for (int i = 0; i < 128; ++i) AsciiChars[i] = -1;
1721 // read character widths
1722 int Count = LastChar-FirstChar+1;
1723 vuint16 *Widths = new vuint16[Count];
1724 int TotalWidth = 0;
1725 int TotalWidth2 = 0;
1726 int MaxWidth = 0;
1727 if (FixedWidthFlag) {
1728 Strm << Widths[0];
1729 for (int i = 1; i < Count; ++i) Widths[i] = Widths[0];
1730 TotalWidth = Widths[0]*Count;
1731 TotalWidth2 = (Widths[0]+2)*Count;
1732 MaxWidth = Widths[0];
1733 } else {
1734 for (int i = 0; i < Count; ++i) {
1735 Strm << Widths[i];
1736 TotalWidth += Widths[i];
1737 if (Widths[i]) TotalWidth2 += Widths[i]+2;
1738 if (Widths[i] > MaxWidth) MaxWidth = Widths[i];
1742 if (FirstChar <= ' ' && LastChar >= ' ') SpaceWidth = Widths[' '-FirstChar];
1743 else if (FirstChar <= 'N' && LastChar >= 'N') SpaceWidth = (Widths['N'-FirstChar]+1)/2;
1744 else SpaceWidth = TotalWidth*2/(3*Count);
1746 if (MaxWidth == 0 || FontHeight == 0) Sys_Error("font '%s' at '%s' has zero dimensions", *AName, *W_FullLumpName(LumpNum));
1747 if (MaxWidth > 1024-2 || FontHeight > 1024-2) Sys_Error("font '%s' at '%s' has too big dimensions", *AName, *W_FullLumpName(LumpNum));
1749 // read palette
1750 bool ColorsUsed[256];
1751 rgba_t Pal[256];
1752 memset(ColorsUsed, 0, sizeof(ColorsUsed));
1753 memset((void *)Pal, 0, sizeof(Pal));
1754 for (int i = 0; i <= ActiveColors; ++i) {
1755 ColorsUsed[i] = true;
1756 Strm << Pal[i].r << Pal[i].g << Pal[i].b;
1757 Pal[i].a = (i ? 255 : 0);
1760 BuildTranslations(ColorsUsed, Pal, false, !!RescalePal);
1762 CalculateAtlasSize(TotalWidth2*(FontHeight+2));
1763 GCon->Logf(NAME_Debug, "atlas size for font '%s' from '%s' is %dx%d", *AName, *W_FullLumpName(LumpNum), asize, asize);
1765 for (int i = 0; i < Count; ++i) {
1766 int DataSize = Widths[i]*FontHeight;
1767 if (DataSize <= 0) continue;
1768 ReadFONxChar(Strm, AName, LumpNum, FirstChar+i, Widths[i], FontHeight);
1770 DoneReadingFONx();
1772 delete[] Widths;
1777 //**************************************************************************
1779 // VSingleTextureFont
1781 //**************************************************************************
1783 //==========================================================================
1785 // VSingleTextureFont::VSingleTextureFont
1787 //==========================================================================
1788 VSingleTextureFont::VSingleTextureFont (VName AName, int TexNum) {
1789 RegisterFont(this, AName);
1791 VTexture *Tex = GTextureManager[TexNum];
1792 if (!Tex) Tex = GTextureManager[0];
1793 for (int i = 0; i < 128; ++i) AsciiChars[i] = -1;
1794 AsciiChars[(int)'A'] = 0;
1795 FirstChar = 'A';
1796 LastChar = 'A';
1797 SpaceWidth = Tex->GetScaledWidthI();
1798 FontHeight = Tex->GetScaledHeightI();
1799 Kerning = 0;
1800 Translation = nullptr;
1802 FFontChar &FChar = Chars.Alloc();
1803 FChar.Char = 'A';
1804 FChar.TexNum = TexNum;
1805 FChar.BaseTex = Tex;
1806 FChar.Textures = nullptr;
1807 FChar.Rect.setFull();
1809 FChar.Width = SpaceWidth;
1810 FChar.Height = FontHeight;
1812 (void)Tex->GetMaxIntensity(); // cache it
1816 //==========================================================================
1818 // VSingleTextureFont::IsSingleTextureFont
1820 //==========================================================================
1821 bool VSingleTextureFont::IsSingleTextureFont () const noexcept {
1822 return true;
1827 //**************************************************************************
1829 // VFontChar
1831 //**************************************************************************
1833 //==========================================================================
1835 // VFontChar::VFontChar
1837 //==========================================================================
1838 VFontChar::VFontChar (VTexture *ATex, rgba_t *APalette)
1839 : BaseTex(ATex)
1840 , Palette(APalette)
1842 Type = TEXTYPE_FontChar;
1843 mFormat = mOrigFormat = TEXFMT_8Pal;
1844 #ifdef VAVOOM_NAME_FONT_TEXTURES
1845 Name = VName(va("\x7f_fontchar_%d ", vfontcharTxCount++));
1846 #else
1847 Name = NAME_None;
1848 #endif
1849 Width = BaseTex->GetWidth();
1850 Height = BaseTex->GetHeight();
1851 SOffset = BaseTex->SOffset;
1852 TOffset = BaseTex->TOffset;
1853 SScale = fabsf(BaseTex->SScale);
1854 TScale = fabsf(BaseTex->TScale);
1855 if (!isFiniteF(SScale) || SScale <= 0.0f) SScale = 1.0f;
1856 if (!isFiniteF(TScale) || TScale <= 0.0f) TScale = 1.0f;
1857 //GCon->Logf("created font char with basetex %p (%s)", BaseTex, *BaseTex->Name);
1858 (void)BaseTex->GetMaxIntensity(); // cache it
1862 //==========================================================================
1864 // VFontChar::~VFontChar
1866 //==========================================================================
1867 VFontChar::~VFontChar () {
1871 //==========================================================================
1873 // VFontChar::GetPixels
1875 //==========================================================================
1876 vuint8 *VFontChar::GetPixels () {
1877 shadeColor = -1; //FIXME
1878 return BaseTex->GetPixels8();
1882 //==========================================================================
1884 // VFontChar::GetPalette
1886 //==========================================================================
1887 rgba_t *VFontChar::GetPalette () {
1888 return Palette;
1892 //==========================================================================
1894 // VFontChar::GetHighResolutionTexture
1896 //==========================================================================
1897 VTexture *VFontChar::GetHighResolutionTexture () {
1898 if (!HiResTexture) {
1899 #if 0
1900 GCon->Logf("getting hires texture for basetex %p", BaseTex);
1901 GCon->Logf(" basetex name: %s", *BaseTex->Name);
1902 GCon->Logf(" basetex hires: %p", BaseTex->GetHighResolutionTexture());
1903 #endif
1904 if (!r_hirestex) return nullptr;
1905 VTexture *Tex = BaseTex->GetHighResolutionTexture();
1906 if (Tex) HiResTexture = new VFontChar(Tex, Palette);
1908 return HiResTexture;