Skip to content

Commit 336bfb5

Browse files
authored
fix(font): Fix font scaling for large resolutions and implement new font scaling methods (#1466)
Implements 3 new font scaling methods, on top of the original classic: - CLASSIC - CLASSIC_NO_CEILING - STRICT - BALANCED The new scaling methods can be set in Language.ini with the ResolutionFontSizeMethod field. CLASSIC_NO_CEILING is the new default and scales alright in 4k. The CLASSIC scaling method is auto selected for the current generations of Control Bar Pro addons, because they do have custom font scaling for 2560 x 1440 and 3840 x 2160. The STRICT scaling method scales by the minimum growth of width or height. The BALANCED scaling method scales by the average growth of width and height, evenly weighted. Both STRICT and BALANCED scale well in different resolutions and aspect ratios.
1 parent d433899 commit 336bfb5

File tree

11 files changed

+291
-40
lines changed

11 files changed

+291
-40
lines changed

Core/GameEngine/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
set(GAMEENGINE_SRC
22
# Include/Common/AcademyStats.h
33
# Include/Common/ActionManager.h
4+
Include/Common/AddonCompat.h
45
Include/Common/ArchiveFile.h
56
Include/Common/ArchiveFileSystem.h
67
Include/Common/AsciiString.h
@@ -554,6 +555,7 @@ set(GAMEENGINE_SRC
554555
Include/GameNetwork/WOLBrowser/FEBDispatch.h
555556
Include/GameNetwork/WOLBrowser/WebBrowser.h
556557
# Include/Precompiled/PreRTS.h
558+
Source/Common/AddonCompat.cpp
557559
Source/Common/Audio/AudioEventRTS.cpp
558560
Source/Common/Audio/AudioRequest.cpp
559561
Source/Common/Audio/DynamicAudioEventInfo.cpp
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
** Command & Conquer Generals Zero Hour(tm)
3+
** Copyright 2025 TheSuperHackers
4+
**
5+
** This program is free software: you can redistribute it and/or modify
6+
** it under the terms of the GNU General Public License as published by
7+
** the Free Software Foundation, either version 3 of the License, or
8+
** (at your option) any later version.
9+
**
10+
** This program is distributed in the hope that it will be useful,
11+
** but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
** GNU General Public License for more details.
14+
**
15+
** You should have received a copy of the GNU General Public License
16+
** along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
19+
#pragma once
20+
21+
namespace addon
22+
{
23+
extern Bool HasFullviewportDat();
24+
25+
} // namespace addon
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
** Command & Conquer Generals Zero Hour(tm)
3+
** Copyright 2025 TheSuperHackers
4+
**
5+
** This program is free software: you can redistribute it and/or modify
6+
** it under the terms of the GNU General Public License as published by
7+
** the Free Software Foundation, either version 3 of the License, or
8+
** (at your option) any later version.
9+
**
10+
** This program is distributed in the hope that it will be useful,
11+
** but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
** GNU General Public License for more details.
14+
**
15+
** You should have received a copy of the GNU General Public License
16+
** along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
19+
#include "PreRTS.h"
20+
21+
#include "Common/AddonCompat.h"
22+
#include "Common/FileSystem.h"
23+
24+
namespace addon
25+
{
26+
Bool HasFullviewportDat()
27+
{
28+
Char value = '0';
29+
if (File* file = TheFileSystem->openFile("GenTool/fullviewport.dat", File::READ | File::BINARY))
30+
{
31+
file->read(&value, 1);
32+
}
33+
return value != '0';
34+
}
35+
36+
} // namespace addon

Generals/Code/GameEngine/Include/GameClient/GlobalLanguage.h

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,18 @@ class AsciiString;
6565
//-----------------------------------------------------------------------------
6666
class GlobalLanguage : public SubsystemInterface
6767
{
68+
public:
69+
70+
enum ResolutionFontSizeMethod
71+
{
72+
ResolutionFontSizeMethod_Classic, // Uses the original scaling method. Scales poorly on wide screens and large resolutions.
73+
ResolutionFontSizeMethod_ClassicNoCeiling, // Uses the original scaling method, but without ceiling. Works ok for the original Game UI and with large resolutions. Scales poorly on very wide screens.
74+
ResolutionFontSizeMethod_Strict, // Uses a strict scaling method. Width and height are strictly bounded on upscales. Works well for accurate UI layouts and with large resolutions.
75+
ResolutionFontSizeMethod_Balanced, // Uses a balanced scaling method. Width and height are evenly weighted for upscales. Works well for the original Game UI and with large resolutions.
76+
77+
ResolutionFontSizeMethod_Default = ResolutionFontSizeMethod_ClassicNoCeiling,
78+
};
79+
6880
public:
6981

7082
GlobalLanguage();
@@ -95,15 +107,15 @@ class GlobalLanguage : public SubsystemInterface
95107
FontDesc m_creditsTitleFont;
96108
FontDesc m_creditsPositionFont;
97109
FontDesc m_creditsNormalFont;
98-
99110
Real m_resolutionFontSizeAdjustment;
100111
Real m_userResolutionFontSizeAdjustment;
101-
102-
//UnicodeString m_unicodeFontNameUStr;
112+
ResolutionFontSizeMethod m_resolutionFontSizeMethod;
103113

104114
float getResolutionFontSizeAdjustment() const;
105115
Int adjustFontSize(Int theFontSize); // Adjusts font size for resolution. jba.
106116

117+
void parseCustomDefinition();
118+
107119
typedef std::list<AsciiString> StringList; // Used for our font file names that we want to load
108120
typedef StringList::iterator StringListIt;
109121

Generals/Code/GameEngine/Source/Common/GameEngine.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,7 @@ void GameEngine::init()
434434
initSubsystem(TheTerrainTypes,"TheTerrainTypes", MSGNEW("GameEngineSubsystem") TerrainTypeCollection(), &xferCRC, "Data\\INI\\Default\\Terrain", "Data\\INI\\Terrain");
435435
initSubsystem(TheTerrainRoads,"TheTerrainRoads", MSGNEW("GameEngineSubsystem") TerrainRoadCollection(), &xferCRC, "Data\\INI\\Default\\Roads", "Data\\INI\\Roads");
436436
initSubsystem(TheGlobalLanguageData,"TheGlobalLanguageData",MSGNEW("GameEngineSubsystem") GlobalLanguage, NULL); // must be before the game text
437+
TheGlobalLanguageData->parseCustomDefinition();
437438
initSubsystem(TheCDManager,"TheCDManager", CreateCDManager(), NULL);
438439
initSubsystem(TheAudio,"TheAudio", TheGlobalData->m_headless ? NEW AudioManagerDummy : createAudioManager(), NULL);
439440
if (!TheAudio->isMusicAlreadyLoaded())

Generals/Code/GameEngine/Source/Common/GlobalData.cpp

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,15 @@
3232
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
3333
#include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine
3434

35+
#include "Common/GlobalData.h"
36+
3537
#define DEFINE_TERRAIN_LOD_NAMES
3638
#define DEFINE_TIME_OF_DAY_NAMES
3739
#define DEFINE_WEATHER_NAMES
3840
#define DEFINE_BODYDAMAGETYPE_NAMES
3941
#define DEFINE_PANNING_NAMES
4042

43+
#include "Common/AddonCompat.h"
4144
#include "Common/crc.h"
4245
#include "Common/file.h"
4346
#include "Common/FileSystem.h"
@@ -1218,18 +1221,10 @@ void GlobalData::parseGameDataDefinition( INI* ini )
12181221

12191222
void GlobalData::parseCustomDefinition()
12201223
{
1224+
if (addon::HasFullviewportDat())
12211225
{
1222-
// TheSuperHackers @feature xezon 03/08/2025 Force full viewport for 'Control Bar Pro' Addons like GenTool did it.
1223-
File* file = TheFileSystem->openFile("GenTool/fullviewport.dat", File::READ | File::BINARY);
1224-
if (file != NULL)
1225-
{
1226-
Char value = '0';
1227-
file->read(&value, 1);
1228-
if (value != '0')
1229-
{
1230-
m_viewportHeightScale = 1.0f;
1231-
}
1232-
}
1226+
// TheSuperHackers @tweak xezon 03/08/2025 Force full viewport for 'Control Bar Pro' Addons like GenTool did it.
1227+
m_viewportHeightScale = 1.0f;
12331228
}
12341229
}
12351230

Generals/Code/GameEngine/Source/GameClient/GlobalLanguage.cpp

Lines changed: 92 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,17 +52,28 @@
5252
//-----------------------------------------------------------------------------
5353
#include "PreRTS.h"
5454

55+
#include "Common/AddonCompat.h"
5556
#include "Common/INI.h"
5657
#include "Common/Registry.h"
5758
#include "Common/FileSystem.h"
5859
#include "Common/UserPreferences.h"
5960

61+
#include "GameClient/Display.h"
6062
#include "GameClient/GlobalLanguage.h"
6163

6264
//-----------------------------------------------------------------------------
6365
// DEFINES ////////////////////////////////////////////////////////////////////
6466
//-----------------------------------------------------------------------------
65-
GlobalLanguage *TheGlobalLanguageData = NULL; ///< The global language singalton
67+
GlobalLanguage *TheGlobalLanguageData = NULL; ///< The global language singleton
68+
69+
static const LookupListRec ResolutionFontSizeMethodNames[] =
70+
{
71+
{ "CLASSIC", GlobalLanguage::ResolutionFontSizeMethod_Classic },
72+
{ "CLASSIC_NO_CEILING", GlobalLanguage::ResolutionFontSizeMethod_ClassicNoCeiling },
73+
{ "STRICT", GlobalLanguage::ResolutionFontSizeMethod_Strict },
74+
{ "BALANCED", GlobalLanguage::ResolutionFontSizeMethod_Balanced },
75+
{ NULL, 0 }
76+
};
6677

6778
static const FieldParse TheGlobalLanguageDataFieldParseTable[] =
6879
{
@@ -72,7 +83,7 @@ static const FieldParse TheGlobalLanguageDataFieldParseTable[] =
7283
{ "MilitaryCaptionSpeed", INI::parseInt, NULL, offsetof( GlobalLanguage, m_militaryCaptionSpeed ) },
7384
{ "UseHardWordWrap", INI::parseBool, NULL, offsetof( GlobalLanguage, m_useHardWrap) },
7485
{ "ResolutionFontAdjustment", INI::parseReal, NULL, offsetof( GlobalLanguage, m_resolutionFontSizeAdjustment) },
75-
86+
{ "ResolutionFontSizeMethod", INI::parseLookupList, ResolutionFontSizeMethodNames, offsetof( GlobalLanguage, m_resolutionFontSizeMethod) },
7687
{ "CopyrightFont", GlobalLanguage::parseFontDesc, NULL, offsetof( GlobalLanguage, m_copyrightFont ) },
7788
{ "MessageFont", GlobalLanguage::parseFontDesc, NULL, offsetof( GlobalLanguage, m_messageFont) },
7889
{ "MilitaryCaptionTitleFont", GlobalLanguage::parseFontDesc, NULL, offsetof( GlobalLanguage, m_militaryCaptionTitleFont) },
@@ -118,6 +129,7 @@ GlobalLanguage::GlobalLanguage()
118129
m_militaryCaptionSpeed = 0;
119130
m_useHardWrap = FALSE;
120131
m_resolutionFontSizeAdjustment = 0.7f;
132+
m_resolutionFontSizeMethod = ResolutionFontSizeMethod_Default;
121133
//End Add
122134

123135
m_userResolutionFontSizeAdjustment = -1.0f;
@@ -193,14 +205,88 @@ float GlobalLanguage::getResolutionFontSizeAdjustment( void ) const
193205

194206
Int GlobalLanguage::adjustFontSize(Int theFontSize)
195207
{
196-
Real adjustFactor = TheGlobalData->m_xResolution / (Real)DEFAULT_DISPLAY_WIDTH;
197-
adjustFactor = 1.0f + (adjustFactor-1.0f) * getResolutionFontSizeAdjustment();
198-
if (adjustFactor<1.0f) adjustFactor = 1.0f;
199-
if (adjustFactor>2.0f) adjustFactor = 2.0f;
208+
// TheSuperHackers @todo This function is called very often.
209+
// Therefore cache the adjustFactor on resolution change to not recompute it on every call.
210+
Real adjustFactor;
211+
212+
switch (m_resolutionFontSizeMethod)
213+
{
214+
default:
215+
case ResolutionFontSizeMethod_Classic:
216+
{
217+
// TheSuperHackers @info The original font scaling for this game.
218+
// Useful for not breaking legacy Addons and Mods. Scales poorly with large resolutions.
219+
adjustFactor = TheDisplay->getWidth() / (Real)DEFAULT_DISPLAY_WIDTH;
220+
adjustFactor = 1.0f + (adjustFactor - 1.0f) * getResolutionFontSizeAdjustment();
221+
if (adjustFactor > 2.0f)
222+
adjustFactor = 2.0f;
223+
break;
224+
}
225+
case ResolutionFontSizeMethod_ClassicNoCeiling:
226+
{
227+
// TheSuperHackers @feature The original font scaling, but without ceiling.
228+
// Useful for not changing the original look of the game. Scales alright with large resolutions.
229+
adjustFactor = TheDisplay->getWidth() / (Real)DEFAULT_DISPLAY_WIDTH;
230+
adjustFactor = 1.0f + (adjustFactor - 1.0f) * getResolutionFontSizeAdjustment();
231+
break;
232+
}
233+
case ResolutionFontSizeMethod_Strict:
234+
{
235+
// TheSuperHackers @feature The strict method scales fonts based on the smallest screen
236+
// dimension so they scale independent of aspect ratio.
237+
const Real wScale = TheDisplay->getWidth() / (Real)DEFAULT_DISPLAY_WIDTH;
238+
const Real hScale = TheDisplay->getHeight() / (Real)DEFAULT_DISPLAY_HEIGHT;
239+
adjustFactor = min(wScale, hScale);
240+
adjustFactor = 1.0f + (adjustFactor - 1.0f) * getResolutionFontSizeAdjustment();
241+
break;
242+
}
243+
case ResolutionFontSizeMethod_Balanced:
244+
{
245+
// TheSuperHackers @feature The balanced method evenly weighs the display width and height
246+
// for a balanced rescale on non 4:3 resolutions. The aspect ratio scaling is clamped to
247+
// prevent oversizing.
248+
constexpr const Real maxAspect = 1.8f;
249+
constexpr const Real minAspect = 1.0f;
250+
Real w = TheDisplay->getWidth();
251+
Real h = TheDisplay->getHeight();
252+
const Real aspect = w / h;
253+
Real wScale = w / (Real)DEFAULT_DISPLAY_WIDTH;
254+
Real hScale = h / (Real)DEFAULT_DISPLAY_HEIGHT;
255+
256+
if (aspect > maxAspect)
257+
{
258+
// Recompute width at max aspect
259+
w = maxAspect * h;
260+
wScale = w / (Real)DEFAULT_DISPLAY_WIDTH;
261+
}
262+
else if (aspect < minAspect)
263+
{
264+
// Recompute height at min aspect
265+
h = minAspect * w;
266+
hScale = h / (Real)DEFAULT_DISPLAY_HEIGHT;
267+
}
268+
adjustFactor = (wScale + hScale) * 0.5f;
269+
adjustFactor = 1.0f + (adjustFactor - 1.0f) * getResolutionFontSizeAdjustment();
270+
break;
271+
}
272+
}
273+
274+
if (adjustFactor < 1.0f)
275+
adjustFactor = 1.0f;
200276
Int pointSize = REAL_TO_INT_FLOOR(theFontSize*adjustFactor);
201277
return pointSize;
202278
}
203279

280+
void GlobalLanguage::parseCustomDefinition()
281+
{
282+
if (addon::HasFullviewportDat())
283+
{
284+
// TheSuperHackers @tweak xezon 19/08/2025 Force the classic font size adjustment for the old
285+
// 'Control Bar Pro' Addons because they use manual font upscaling in higher resolution packages.
286+
m_resolutionFontSizeMethod = ResolutionFontSizeMethod_Classic;
287+
}
288+
}
289+
204290
FontDesc::FontDesc(void)
205291
{
206292
name = "Arial Unicode MS"; ///<name of font

GeneralsMD/Code/GameEngine/Include/GameClient/GlobalLanguage.h

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,18 @@ class AsciiString;
6565
//-----------------------------------------------------------------------------
6666
class GlobalLanguage : public SubsystemInterface
6767
{
68+
public:
69+
70+
enum ResolutionFontSizeMethod
71+
{
72+
ResolutionFontSizeMethod_Classic, // Uses the original scaling method. Scales poorly on wide screens and large resolutions.
73+
ResolutionFontSizeMethod_ClassicNoCeiling, // Uses the original scaling method, but without ceiling. Works ok for the original Game UI and with large resolutions. Scales poorly on very wide screens.
74+
ResolutionFontSizeMethod_Strict, // Uses a strict scaling method. Width and height are strictly bounded on upscales. Works well for accurate UI layouts and with large resolutions.
75+
ResolutionFontSizeMethod_Balanced, // Uses a balanced scaling method. Width and height are evenly weighted for upscales. Works well for the original Game UI and with large resolutions.
76+
77+
ResolutionFontSizeMethod_Default = ResolutionFontSizeMethod_ClassicNoCeiling,
78+
};
79+
6880
public:
6981

7082
GlobalLanguage();
@@ -96,15 +108,15 @@ class GlobalLanguage : public SubsystemInterface
96108
FontDesc m_creditsTitleFont;
97109
FontDesc m_creditsPositionFont;
98110
FontDesc m_creditsNormalFont;
99-
100111
Real m_resolutionFontSizeAdjustment;
101112
Real m_userResolutionFontSizeAdjustment;
102-
103-
//UnicodeString m_unicodeFontNameUStr;
113+
ResolutionFontSizeMethod m_resolutionFontSizeMethod;
104114

105115
float getResolutionFontSizeAdjustment() const;
106116
Int adjustFontSize(Int theFontSize); // Adjusts font size for resolution. jba.
107117

118+
void parseCustomDefinition();
119+
108120
typedef std::list<AsciiString> StringList; // Used for our font file names that we want to load
109121
typedef StringList::iterator StringListIt;
110122

GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,7 @@ void GameEngine::init()
520520
initSubsystem(TheTerrainTypes,"TheTerrainTypes", MSGNEW("GameEngineSubsystem") TerrainTypeCollection(), &xferCRC, "Data\\INI\\Default\\Terrain", "Data\\INI\\Terrain");
521521
initSubsystem(TheTerrainRoads,"TheTerrainRoads", MSGNEW("GameEngineSubsystem") TerrainRoadCollection(), &xferCRC, "Data\\INI\\Default\\Roads", "Data\\INI\\Roads");
522522
initSubsystem(TheGlobalLanguageData,"TheGlobalLanguageData",MSGNEW("GameEngineSubsystem") GlobalLanguage, NULL); // must be before the game text
523+
TheGlobalLanguageData->parseCustomDefinition();
523524
initSubsystem(TheCDManager,"TheCDManager", CreateCDManager(), NULL);
524525
#ifdef DUMP_PERF_STATS///////////////////////////////////////////////////////////////////////////
525526
GetPrecisionTimer(&endTime64);//////////////////////////////////////////////////////////////////

GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,15 @@
3232
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
3333
#include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine
3434

35+
#include "Common/GlobalData.h"
36+
3537
#define DEFINE_TERRAIN_LOD_NAMES
3638
#define DEFINE_TIME_OF_DAY_NAMES
3739
#define DEFINE_WEATHER_NAMES
3840
#define DEFINE_BODYDAMAGETYPE_NAMES
3941
#define DEFINE_PANNING_NAMES
4042

43+
#include "Common/AddonCompat.h"
4144
#include "Common/crc.h"
4245
#include "Common/file.h"
4346
#include "Common/FileSystem.h"
@@ -1246,18 +1249,10 @@ void GlobalData::parseGameDataDefinition( INI* ini )
12461249

12471250
void GlobalData::parseCustomDefinition()
12481251
{
1252+
if (addon::HasFullviewportDat())
12491253
{
1250-
// TheSuperHackers @feature xezon 03/08/2025 Force full viewport for 'Control Bar Pro' Addons like GenTool did it.
1251-
File* file = TheFileSystem->openFile("GenTool/fullviewport.dat", File::READ | File::BINARY);
1252-
if (file != NULL)
1253-
{
1254-
Char value = '0';
1255-
file->read(&value, 1);
1256-
if (value != '0')
1257-
{
1258-
m_viewportHeightScale = 1.0f;
1259-
}
1260-
}
1254+
// TheSuperHackers @tweak xezon 03/08/2025 Force full viewport for 'Control Bar Pro' Addons like GenTool did it.
1255+
m_viewportHeightScale = 1.0f;
12611256
}
12621257
}
12631258

0 commit comments

Comments
 (0)