1 //**************************************************************************
3 //** ## ## ## ## ## #### #### ### ###
4 //** ## ## ## ## ## ## ## ## ## ## #### ####
5 //** ## ## ## ## ## ## ## ## ## ## ## ## ## ##
6 //** ## ## ######## ## ## ## ## ## ## ## ### ##
7 //** ### ## ## ### ## ## ## ## ## ##
8 //** # ## ## # #### #### ## ##
10 //** Copyright (C) 1999-2006 Jānis Legzdiņš
11 //** Copyright (C) 2018-2023 Ketmar Dark
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.
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.
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/>.
25 //**************************************************************************
26 #include "../gamedefs.h"
27 #include "../textures/r_tex.h"
30 #define VAVOOM_NAME_FONT_TEXTURES
32 #define VAVOOM_CON_FONT_PATH "k8vavoom/fonts/consolefont.fnt"
35 struct VColTranslationDef
{
42 struct VTextColorDef
{
43 TArray
<VColTranslationDef
> Translations
;
44 TArray
<VColTranslationDef
> ConsoleTranslations
;
55 // like regular font, but initialised using explicit list of patches
56 class VSpecialFont
: public VFont
{
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
{
64 TArray
<VTexture
*> atlaslist
;
65 TArray
<VTexture
*> tratlaslist
;
69 VTexAtlas8bit
*currAtlas
;
71 int asize
; // atlas size
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
);
79 void CalculateAtlasSize (int TotalDataSize
) noexcept
;
80 void ReadFONxChar (VStream
&Strm
, VName AName
, int LumpNum
, int Chr
, int CharWidth
, int CharHeight
);
81 void DoneReadingFONx ();
85 virtual ~VFontBitmapBase () override
;
88 // font in FON1 format
89 class VFon1Font
: public VFontBitmapBase
{
91 VFon1Font (VName
, int);
94 // font in FON2 format
95 class VFon2Font
: public VFontBitmapBase
{
97 VFon2Font (VName
, int);
100 // font consisting of a single texture for character 'A'
101 class VSingleTextureFont
: public VFont
{
103 VSingleTextureFont (VName
, int);
104 bool IsSingleTextureFont () const noexcept override
;
107 // texture class for regular font characters
108 class VFontChar
: public VTexture
{
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;
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
++));
155 //==========================================================================
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;
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
);
183 //==========================================================================
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
);
194 vint32 Code
= Streamer
<vint8
>(Strm
);
197 while (Code
-- >= 0) {
198 *pDst
= Streamer
<vuint8
>(Strm
);
199 *pDst
= min2(*pDst
, maxcol
);
202 } else if (Code
!= -128) {
205 vuint8 Val
= Streamer
<vuint8
>(Strm
);
206 Val
= min2(Val
, maxcol
);
207 while (Code
-- > 0) *pDst
++ = Val
;
211 if (Strm
.IsError()) { delete[] Pixels
; return nullptr; }
217 //**************************************************************************
221 //**************************************************************************
223 //==========================================================================
225 // VFontBitmapBase::VFontBitmapBase
227 //==========================================================================
228 VFontBitmapBase::VFontBitmapBase () {
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!
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
);
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
);
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
{
305 if (TotalDataSize
<= 0) return;
306 while (asize
*asize
< TotalDataSize
) {
307 if (asize
== 1024) break;
313 //==========================================================================
315 // VFontBitmapBase::DoneReadingFONx
317 //==========================================================================
318 void VFontBitmapBase::DoneReadingFONx () {
319 if (!currAtlas
) return;
320 CreateTranslatedAtlases(currAtlas
, nextToUpdate
);
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;
335 vassert(nextToUpdate
== 0);
336 vassert(Chars
.length() == 0);
337 currAtlas
= allocAtlas(Translation
, asize
, asize
);
340 VTexAtlas8bit::Rect rc
;
342 // allocate char in atlas
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);
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
++);
364 FFontChar
&FChar
= Chars
.Alloc();
365 if (Chr
>= 0 && Chr
< 128) AsciiChars
[Chr
] = Chars
.length()-1;
368 FChar
.BaseTex
= currAtlas
;
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 //**************************************************************************
382 //**************************************************************************
384 //==========================================================================
386 // VFont::RegisterFont
388 // set font name, and register it as known font
390 //==========================================================================
391 void VFont::RegisterFont (VFont
*font
, VName aname
) {
395 if (aname
== NAME_None
) { font
->Next
= nullptr; return; }
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;
410 if (curr
!= font
&& VStr::strEquCI(*curr
->Name
, *aname
)) break;
415 GCon
->Log(NAME_Error
, "internal error: old font not found (this is harmless, but still a bug!)");
417 if (prev
) prev
->Next
= curr
->Next
; else Fonts
= curr
->Next
;
418 curr
->Next
= nullptr;
422 GCon
->Logf(NAME_Init
, "registering font '%s'", *aname
);
424 FontMap
.put(VStrCI(aname
), font
);
428 //==========================================================================
432 //==========================================================================
433 void VFont::StaticInit () {
436 // load custom fonts (they can override standard fonts)
439 // initialise standard fonts
440 GCon
->Log(NAME_Init
, "creading default fonts");
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);
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
) {
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);
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");
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");
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");
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");
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 () {
522 VFont
*Next
= F
->Next
;
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();
549 Col
.FlatColor
.a
= 255;
553 VColTranslationDef TDef
;
557 // name for this color
559 Names
.Append(*sc
.String
.ToLower());
561 while (!sc
.Check("{")) {
562 if (Names
[0] == "untranslated") sc
.Error("Color \"untranslated\" cannot have any other names");
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");
574 } else if (sc
.Check("Flat:")) {
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;
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;
592 C
= M_ParseColor(*sc
.String
);
593 TDef
.To
.r
= (C
>> 16) & 255;
594 TDef
.To
.g
= (C
>> 8) & 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
;
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
;
610 // set default luminosity range
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");
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
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
646 for (CIdx
= 0; CIdx
< TempColors
.length(); ++CIdx
) {
647 if (TempColors
[CIdx
].Name
== Names
[i
]) {
648 TempColors
[CIdx
].Index
= TempDefs
.length()-1;
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()) {
695 VName
FontName(*sc
.String
, VName::AddLower
);
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")) {
713 Template
= sc
.String
;
715 //GCon->Logf(NAME_Debug, "*** TPL: <%s>", *Template);
716 } else if (sc
.Check("first")) {
721 } else if (sc
.Check("count")) {
726 } else if (sc
.Check("base")) {
731 } else if (sc
.Check("notranslate") || sc
.Check("notranslation")) {
733 while (sc
.CheckNumber() && !sc
.Crossed
) {
734 if (sc
.Number
>= 0 && sc
.Number
< 256) NoTranslate
[sc
.Number
] = true;
737 } else if (sc
.Check("cursor")) {
738 sc
.Message("fontdef 'CURSOR' property is not supported");
740 } else if (sc
.Check("spacewidth")) {
742 SpaceWidth
= sc
.Number
;
746 const char *CPtr
= *sc
.String
;
747 int CharIdx
= VStr::Utf8GetChar(CPtr
);
749 VName
LumpName(*sc
.String
, VName::AddLower8
);
750 if (W_CheckNumForName(LumpName
, WADNS_Graphics
) >= 0) {
751 CharIndexes
.Append(CharIdx
);
752 CharLumps
.Append(LumpName
);
754 GCon
->Logf(NAME_Error
, "font '%s': char #%d not found (%s)", *FontName
, CharIdx
, *LumpName
);
760 if (FontName
!= NAME_None
) {
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
);
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
);
774 sc
.Error("Font has no attributes");
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);
793 if (res
.isEmpty()) return aname
;
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
);
808 if (res
.isEmpty()) return aname
;
813 //==========================================================================
817 //==========================================================================
818 VFont
*VFont::FindFont (VStr AName
) {
820 for (VFont
*F
= Fonts
; F
; F
= F
->Next
) {
821 if (F
->Name
== AName
|| VStr::strEquCI(F
->Name
, *AName
)) return F
;
824 if (!AName
.isEmpty()) {
825 VStr n
= TrimPathFromFontName(AName
);
826 auto fpp
= FontMap
.get(n
);
827 if (fpp
) return *fpp
;
834 //==========================================================================
838 //==========================================================================
839 VFont
*VFont::FindFont (VName AName
) {
840 if (AName
!= NAME_None
) return FindFont(VStr(AName
)); // this doesn't allocate
845 //==========================================================================
847 // VFont::FindAndLoadFontFromLumpIdx
849 //==========================================================================
850 VFont
*VFont::FindAndLoadFontFromLumpIdx (VStr AName
, int LumpIdx
) {
851 if (LumpIdx
< 0 || AName
.isEmpty()) return nullptr;
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
);
869 //==========================================================================
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
);
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
);
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
);
906 //==========================================================================
910 //==========================================================================
911 VFont
*VFont::GetFont (VStr AName
) {
912 VStr LumpName
= GetPathFromFontName(AName
);
913 AName
= TrimPathFromFontName(AName
);
914 return GetFont(AName
, LumpName
);
919 //**************************************************************************
923 //**************************************************************************
925 //==========================================================================
929 //==========================================================================
930 VFont::VFont () : Name(NAME_None
) {
934 //==========================================================================
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;
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
) {
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);
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') {
986 snprintf(tb
, sizeof(tb
), "%s", *LumpName
);
987 if (strlen(tb
) > 5) {
993 VName
ln2(tb
, VName::FindLower8
);
994 if (ln2
!= NAME_None
) {
995 Lump
= W_CheckNumForName(ln2
, WADNS_Graphics
);
996 if (Lump
>= 0) LumpName
= ln2
;
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))
1013 if (Lump
>= 0 || knownTexture
) {
1014 VTexture
*Tex
= (knownTexture
? knownTexture
: GTextureManager
[GTextureManager
.AddPatchLump(Lump
, LumpName
, TEXTYPE_Pic
)]);
1016 FFontChar
&FChar
= Chars
.Alloc();
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);
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
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');
1064 SpaceWidth
= max2(1, (Chars
[NIdx
].Width
+1)/2);
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 //==========================================================================
1090 //==========================================================================
1092 for (auto &&cc
: Chars
) {
1094 delete[] cc
.Textures
;
1095 cc
.Textures
= nullptr;
1100 delete[] Translation
;
1101 Translation
= nullptr;
1106 //==========================================================================
1108 // VFont::IsSingleTextureFont
1110 //==========================================================================
1111 bool VFont::IsSingleTextureFont () const noexcept
{
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);
1151 for (int i
= 1; i
< 256; ++i
) {
1152 int ILum
= (int)(Luminosity
[i
]*256);
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
);
1171 //==========================================================================
1173 // VFont::BuildCharMap
1175 //==========================================================================
1176 void VFont::BuildCharMap () {
1177 if (CharMap
.length() || !Chars
.length()) return;
1179 for (auto &&it
: Chars
.itemsIdx()) {
1180 CharMap
.put(it
.value().Char
, it
.index());
1185 //==========================================================================
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
];
1196 auto pp
= CharMap
.get(Chr
);
1197 return (pp
? *pp
: -1);
1201 //==========================================================================
1205 //==========================================================================
1206 VTexture
*VFont::GetChar (int Chr
, CharRect
*rect
, int *pWidth
, int Color
) {
1207 int Idx
= FindChar(Chr
);
1209 // try upper-case letter
1210 Chr
= VStr::ToUpper(Chr
);
1211 Idx
= FindChar(Chr
);
1213 if (pWidth
) *pWidth
= SpaceWidth
;
1214 if (rect
) rect
->clear();
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
) :
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
;
1232 //==========================================================================
1234 // VFont::GetCharWidth
1236 //==========================================================================
1237 int VFont::GetCharWidth (int Chr
) {
1238 int Idx
= FindChar(Chr
);
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';
1291 const char ch
= *pColor
++;
1292 if (!ch
) { --pColor
; break; }
1293 if (ch
== ']') break;
1294 if (nbpos
< sizeof(nbuf
)-1) nbuf
[nbpos
++] = ch
;
1297 Col
= FindTextColor(nbuf
, CR_UNDEFINED
);
1299 if (Col
!= CR_UNDEFINED
) {
1301 (*escstr
) += VStr(nbuf
);
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
1319 if (escstr
) escstr
->clear();
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
;
1348 //==========================================================================
1350 // VFont::StringWidth
1352 //==========================================================================
1353 int VFont::StringWidth (VStr String
) {
1355 if (!String
.isEmpty()) {
1356 const char *SPtr
= *String
;
1358 const int c
= VStr::Utf8GetChar(SPtr
);
1360 // check for color escape
1361 if (c
== TEXT_COLOR_ESCAPE
) {
1362 ParseColorEscape(SPtr
, CR_UNDEFINED
, CR_UNDEFINED
);
1364 w
+= GetCharWidth(c
)+GetKerning();
1372 //==========================================================================
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;
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;
1395 //==========================================================================
1397 // VFont::TextHeight
1399 //==========================================================================
1400 int VFont::TextHeight (VStr String
) {
1402 const int len
= String
.length();
1404 const char *s
= *String
;
1405 for (int f
= len
; f
> 0; --f
, ++s
) if (*s
== '\n') h
+= FontHeight
;
1411 //==========================================================================
1415 //==========================================================================
1416 int VFont::SplitText (VStr Text
, TArray
<VSplitLine
> &Lines
, int MaxWidth
, bool trimRight
) {
1418 const char *Start
= *Text
;
1419 bool WordStart
= true;
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
));
1438 // newline clears all color sequences
1441 } else if (WordStart
&& c
> ' ') {
1442 const char *SPtr2
= SPtr
;
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
));
1457 lineCEsc
= lastCEsc
;
1460 CurW
+= GetCharWidth(c
);
1461 } else if (c
<= ' ') {
1463 CurW
+= GetCharWidth(c
);
1465 CurW
+= GetCharWidth(c
);
1468 if (!*SPtr
&& Start
!= SPtr
) {
1469 VSplitLine
&L
= Lines
.Alloc();
1470 L
.Text
= lineCEsc
+Start
;
1472 lineCEsc
= lastCEsc
;
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()) {
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
);
1500 for (int i
= 0; i
< Lines
.length(); ++i
) {
1501 if (i
!= 0) Ret
+= "\n";
1502 Ret
+= Lines
[i
].Text
;
1509 //**************************************************************************
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;
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
);
1540 GCon
->Logf(NAME_Error
, "font '%s': missing patch '%s' for char #%d", *AName
, *LumpName
, Char
);
1544 VTexture
*Tex
= GTextureManager
[texid
];
1546 FFontChar
&FChar
= Chars
.Alloc();
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');
1583 SpaceWidth
= max2(1, (Chars
[NIdx
].Width
+1)/2);
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 //**************************************************************************
1617 //**************************************************************************
1619 //==========================================================================
1621 // VFon1Font::VFon1Font
1623 //==========================================================================
1624 VFon1Font::VFon1Font (VName AName
, int LumpNum
) :
1627 RegisterFont(this, AName
);
1629 VStream
*lumpstream
= W_CreateLumpReaderNum(LumpNum
);
1630 VCheckedStream
Strm(lumpstream
);
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
));
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];
1651 ColorsUsed
[0] = false;
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
;
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
);
1677 //**************************************************************************
1681 //**************************************************************************
1683 //==========================================================================
1685 // VFon2Font::VFon2Font
1691 // 1 - fixed width flag
1693 // 1 - active colors
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
);
1707 FontHeight
= Streamer
<vuint16
>(Strm
);
1708 FirstChar
= Streamer
<vuint8
>(Strm
);
1709 LastChar
= Streamer
<vuint8
>(Strm
);
1710 vuint8 FixedWidthFlag
;
1712 vuint8 ActiveColors
;
1714 Strm
<< FixedWidthFlag
<< RescalePal
<< ActiveColors
<< KerningFlag
;
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
];
1725 int TotalWidth2
= 0;
1727 if (FixedWidthFlag
) {
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];
1734 for (int i
= 0; i
< Count
; ++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
));
1750 bool ColorsUsed
[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
);
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;
1797 SpaceWidth
= Tex
->GetScaledWidthI();
1798 FontHeight
= Tex
->GetScaledHeightI();
1800 Translation
= nullptr;
1802 FFontChar
&FChar
= Chars
.Alloc();
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
{
1827 //**************************************************************************
1831 //**************************************************************************
1833 //==========================================================================
1835 // VFontChar::VFontChar
1837 //==========================================================================
1838 VFontChar::VFontChar (VTexture
*ATex
, rgba_t
*APalette
)
1842 Type
= TEXTYPE_FontChar
;
1843 mFormat
= mOrigFormat
= TEXFMT_8Pal
;
1844 #ifdef VAVOOM_NAME_FONT_TEXTURES
1845 Name
= VName(va("\x7f_fontchar_%d ", vfontcharTxCount
++));
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 () {
1892 //==========================================================================
1894 // VFontChar::GetHighResolutionTexture
1896 //==========================================================================
1897 VTexture
*VFontChar::GetHighResolutionTexture () {
1898 if (!HiResTexture
) {
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());
1904 if (!r_hirestex
) return nullptr;
1905 VTexture
*Tex
= BaseTex
->GetHighResolutionTexture();
1906 if (Tex
) HiResTexture
= new VFontChar(Tex
, Palette
);
1908 return HiResTexture
;