1/*
2 * LegacyClonk
3 *
4 * Copyright (c) RedWolf Design
5 * Copyright (c) 2001, Sven2
6 * Copyright (c) 2017-2021, The LegacyClonk Team and contributors
7 *
8 * Distributed under the terms of the ISC license; see accompanying file
9 * "COPYING" for details.
10 *
11 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
12 * See accompanying file "TRADEMARK" for details.
13 *
14 * To redistribute this file separately, substitute the full license texts
15 * for the above references.
16 */
17
18// engine font loading
19
20#include <C4Include.h>
21#include <C4Fonts.h>
22
23#include <C4Config.h>
24#include <C4Components.h>
25#include <C4Log.h>
26#include <C4Surface.h>
27#include <C4Wrappers.h>
28
29#include <stdexcept>
30
31void C4FontDef::CompileFunc(StdCompiler *pComp)
32{
33 pComp->Value(rStruct: mkNamingAdapt(toC4CStrBuf(Name), szName: "Name", rDefault: ""));
34 pComp->Value(rStruct: mkNamingAdapt(rValue&: iSize, szName: "Size", rDefault: 1));
35 pComp->Value(rStruct: mkNamingAdapt(toC4CStrBuf(LogFont), szName: "LogFont", rDefault: ""));
36 pComp->Value(rStruct: mkNamingAdapt(toC4CStrBuf(SmallFont), szName: "SmallFont", rDefault: ""));
37 pComp->Value(rStruct: mkNamingAdapt(toC4CStrBuf(Font), szName: "Font", rDefault: ""));
38 pComp->Value(rStruct: mkNamingAdapt(toC4CStrBuf(CaptionFont), szName: "CaptionFont", rDefault: ""));
39 pComp->Value(rStruct: mkNamingAdapt(toC4CStrBuf(TitleFont), szName: "TitleFont", rDefault: ""));
40}
41
42C4VectorFont::~C4VectorFont()
43{
44 CStdFont::DestroyFont(pFont);
45 // temp file loaded?
46 if (*FileName)
47 {
48 // release font
49 if (fIsTempFile) EraseFile(szFileName: FileName);
50 }
51}
52
53bool C4VectorFont::Init(C4Group &hGrp, const char *szFilename, C4Config &rCfg)
54{
55 // name by file
56 Name.Copy(pnData: GetFilenameOnly(strFilename: szFilename));
57 if (!hGrp.LoadEntry(szEntryName: szFilename, Buf&: Data)) return false;
58 // success
59 return true;
60}
61
62bool C4VectorFont::Init(const char *szFacename, int32_t iSize, uint32_t dwWeight, const char *szCharSet)
63{
64 // name by face
65 Name.Copy(pnData: szFacename);
66#if defined(_WIN32) && defined(HAVE_FREETYPE)
67 // Win32 using freetype: Load TrueType-data from WinGDI into Data-buffer to be used by FreeType
68 bool fSuccess = false;
69 HDC hDC = ::CreateCompatibleDC(nullptr);
70 if (hDC)
71 {
72 HFONT hFont = ::CreateFontA(iSize, 0, 0, 0, dwWeight, FALSE,
73 FALSE, FALSE, C4Config::GetCharsetCode(szCharSet), OUT_DEFAULT_PRECIS,
74 CLIP_DEFAULT_PRECIS, 5,
75 VARIABLE_PITCH, szFacename);
76 if (hFont)
77 {
78 SelectObject(hDC, hFont);
79 uint32_t dwTTFSize = ::GetFontData(hDC, 0, 0, nullptr, 0);
80 if (dwTTFSize && dwTTFSize != GDI_ERROR)
81 {
82 Data.SetSize(dwTTFSize);
83 uint32_t dwRealTTFSize = ::GetFontData(hDC, 0, 0, Data.getMData(), dwTTFSize);
84 if (dwRealTTFSize == dwTTFSize)
85 {
86 fSuccess = true;
87 }
88 else
89 Data.Clear();
90 }
91 DeleteObject(hFont);
92 }
93 DeleteDC(hDC);
94 }
95 if (!fSuccess) return false;
96#else
97 // otherwise, just assume that font can be created by face name
98#endif
99 // success
100 return true;
101}
102
103void C4FontLoader::Clear()
104{
105 // clear loaded defs
106 FontDefs.clear();
107 // delete loaded vector fonts
108 C4VectorFont *pVecFont, *pNextVecFont = pVectorFonts;
109 while (pVecFont = pNextVecFont)
110 {
111 pNextVecFont = pVecFont->pNext;
112 delete pVecFont;
113 }
114 pVectorFonts = nullptr;
115}
116
117void C4FontLoader::AddVectorFont(C4VectorFont *pNewFont)
118{
119 // add as last to chain
120 C4VectorFont **ppFont = &pVectorFonts;
121 while (*ppFont) ppFont = &((*ppFont)->pNext);
122 *ppFont = pNewFont;
123}
124
125size_t C4FontLoader::LoadDefs(C4Group &hGroup, C4Config &rCfg)
126{
127 // load vector fonts
128 char fn[_MAX_PATH + 1], fnDef[32]; int32_t i = 0;
129 while (SCopySegment(C4CFN_FontFiles, segn: i++, tstr: fnDef, sepa: '|', iMaxL: 31))
130 {
131 hGroup.ResetSearch();
132 while (hGroup.FindNextEntry(szWildCard: fnDef, sFileName: fn))
133 {
134 C4VectorFont *pVecFon = new C4VectorFont();
135 if (pVecFon->Init(hGrp&: hGroup, szFilename: fn, rCfg))
136 {
137 pVecFon->pNext = pVectorFonts;
138 pVectorFonts = pVecFon;
139 }
140 else delete pVecFon;
141 }
142 }
143 // load definition file
144 StdStrBuf DefFileContent;
145 if (!hGroup.LoadEntryString(C4CFN_FontDefs, Buf&: DefFileContent)) return 0;
146 std::vector<C4FontDef> NewFontDefs;
147 if (!CompileFromBuf_LogWarn<StdCompilerINIRead>(
148 TargetStruct: mkNamingAdapt(rValue: mkSTLContainerAdapt(rTarget&: NewFontDefs), szName: "Font"),
149 SrcBuf: DefFileContent,
150 C4CFN_FontDefs))
151 return 0;
152 // Copy the new FontDefs into the list
153 FontDefs.insert(position: FontDefs.end(), first: NewFontDefs.begin(), last: NewFontDefs.end());
154 // done
155 return NewFontDefs.size();
156}
157
158bool C4FontLoader::InitFont(CStdFont &rFont, C4VectorFont *pFont, int32_t iSize, uint32_t dwWeight, bool fDoShadow)
159{
160 if (!pFont->pFont)
161 {
162#ifdef HAVE_FREETYPE
163 // Creation from binary font data
164 if (!pFont->Data.isNull()) pFont->pFont = CStdFont::CreateFont(Data: pFont->Data);
165 // creation from filename
166 if (!pFont->pFont) pFont->pFont = CStdFont::CreateFont(szFaceName: pFont->FileName);
167#endif
168 // WinGDI: Try creation from font face name only
169 if (!pFont->pFont) pFont->pFont = CStdFont::CreateFont(szFaceName: pFont->Name.getData());
170 if (!pFont->pFont) return false; // this font can't be used
171 }
172 rFont.Init(VectorFont&: *(pFont->pFont), dwHeight: iSize, dwFontWeight: dwWeight, szCharset: Config.General.LanguageCharset, fDoShadow, scale: Application.GetScale()); // throws exception on error
173 return true;
174}
175
176bool C4FontLoader::InitFont(CStdFont &rFont, const char *szFontName, FontType eType, int32_t iSize, C4GroupSet *pGfxGroups, bool fDoShadow)
177{
178 // safety
179 if (!szFontName || !*szFontName)
180 {
181 LogFatal(id: C4ResStrTableKey::IDS_ERR_INITFONTS);
182 return false;
183 }
184 // get font to load
185 // pFontDefs may be nullptr if no fonts are loaded; but then iFontDefCount is zero as well
186 // the function must not be aborted, because a standard windows font may be loaded
187 std::vector<C4FontDef>::iterator pFontDefC = FontDefs.begin(), pFontDef = FontDefs.end();
188 while (pFontDefC != FontDefs.end())
189 {
190 // check font
191 if (pFontDefC->Name == szFontName)
192 {
193 int32_t iSizeDiff = Abs(val: pFontDefC->iSize - iSize);
194 // better match than last font?
195 if (pFontDef == FontDefs.end() || Abs(val: pFontDef->iSize - iSize) >= iSizeDiff)
196 pFontDef = pFontDefC;
197 }
198 // check next one
199 ++pFontDefC;
200 }
201 // if def has not been found, use the def as font name
202 // determine font def string
203 const char *szFontString = szFontName;
204 // special: Fonts without shadow are always newly rendered
205 if (!fDoShadow) { pFontDef = FontDefs.end(); }
206 if (pFontDef != FontDefs.end()) switch (eType)
207 {
208 case C4FT_Log: szFontString = pFontDef->LogFont.getData(); break;
209 case C4FT_MainSmall: szFontString = pFontDef->SmallFont.getData(); break;
210 case C4FT_Main: szFontString = pFontDef->Font.getData(); break;
211 case C4FT_Caption: szFontString = pFontDef->CaptionFont.getData(); break;
212 case C4FT_Title: szFontString = pFontDef->TitleFont.getData(); break;
213 default: LogFatal(id: C4ResStrTableKey::IDS_ERR_INITFONTS); return false; // invalid call
214 }
215 // font not assigned?
216 if (!*szFontString)
217 {
218 // invalid call or spec
219 LogFatal(id: C4ResStrTableKey::IDS_ERR_INITFONTS); return false;
220 }
221 // get font name
222 char FontFaceName[C4MaxName + 1], FontParam[C4MaxName + 1];
223 SCopyUntil(szSource: szFontString, sTarget: FontFaceName, cUntil: ',', iMaxL: C4MaxName);
224 // is it an image file?
225 const char *szExt = GetExtension(fname: FontFaceName);
226 if (SEqualNoCase(szStr1: szExt, szStr2: "png") || SEqualNoCase(szStr1: szExt, szStr2: "bmp"))
227 {
228 // image file name: load bitmap font from image file
229 // if no graphics group is given, do not load yet
230 if (!pGfxGroups)
231 {
232 LogFatal(id: C4ResStrTableKey::IDS_ERR_INITFONTS);
233 return false;
234 }
235 // indent given?
236 int32_t iIndent = 0;
237 if (SCopySegment(fstr: szFontString, segn: 1, tstr: FontParam, sepa: ',', iMaxL: C4MaxName))
238 sscanf(s: FontParam, format: "%i", &iIndent);
239 // load font face from gfx group
240 int32_t iGrpId;
241 C4Group *pGrp = pGfxGroups->FindEntry(szWildcard: FontFaceName, pPriority: nullptr, pID: &iGrpId);
242 if (!pGrp)
243 {
244 LogFatal(id: C4ResStrTableKey::IDS_ERR_INITFONTS);
245 return false;
246 }
247 // check if it's already loaded from that group with that parameters
248 if (!rFont.IsSameAsID(szCFontName: FontFaceName, iCID: iGrpId, iCIndent: iIndent))
249 {
250 // it's not; so (re-)load it now!
251 if (rFont.IsInitialized())
252 {
253 // reloading
254 rFont.Clear();
255 Log(id: C4ResStrTableKey::IDS_PRC_UPDATEFONT, args: +FontFaceName, args&: iIndent, args: 0);
256 }
257 C4Surface sfc;
258 if (!sfc.Load(hGroup&: *pGrp, szFilename: FontFaceName))
259 {
260 LogFatal(id: C4ResStrTableKey::IDS_ERR_INITFONTS);
261 return false;
262 }
263 // init font from face
264 try
265 {
266 rFont.Init(szFontName: GetFilenameOnly(strFilename: FontFaceName), psfcFontSfc: &sfc, iIndent);
267 }
268 catch (std::runtime_error &e)
269 {
270 LogFatalNTr(message: e.what());
271 LogFatal(id: C4ResStrTableKey::IDS_ERR_INITFONTS);
272 return false;
273 }
274 rFont.id = iGrpId;
275 }
276 }
277 else
278 {
279 int32_t iDefFontSize; uint32_t dwDefWeight = FW_NORMAL;
280 switch (eType)
281 {
282 case C4FT_Log: iDefFontSize = iSize * 12 / 14; break;
283 case C4FT_MainSmall: iDefFontSize = iSize * 13 / 14; break;
284 case C4FT_Main: iDefFontSize = iSize; break;
285 case C4FT_Caption: iDefFontSize = iSize * 16 / 14; break;
286 case C4FT_Title: iDefFontSize = iSize * 22 / 14; break;
287 default: LogFatal(id: C4ResStrTableKey::IDS_ERR_INITFONTS); return false; // invalid call
288 }
289 // regular font name: let WinGDI or Freetype draw a font with the given parameters
290 // font size given?
291 if (SCopySegment(fstr: szFontString, segn: 1, tstr: FontParam, sepa: ',', iMaxL: C4MaxName))
292 sscanf(s: FontParam, format: "%i", &iDefFontSize);
293 // font weight given?
294 if (SCopySegment(fstr: szFontString, segn: 2, tstr: FontParam, sepa: ',', iMaxL: C4MaxName))
295 sscanf(s: FontParam, format: "%i", &dwDefWeight);
296 // check if it's already loaded from that group with that parameters
297 if (!rFont.IsSameAs(szCFontName: FontFaceName, iCHeight: iDefFontSize, dwCWeight: dwDefWeight))
298 {
299 // it's not; so (re-)load it now!
300 if (rFont.IsInitialized())
301 {
302 // reloading
303 rFont.Clear();
304 Log(id: C4ResStrTableKey::IDS_PRC_UPDATEFONT, args: +FontFaceName, args&: iDefFontSize, args&: dwDefWeight);
305 }
306 // init with given font name
307 try
308 {
309 // check if one of the internally listed fonts should be used
310 C4VectorFont *pFont = pVectorFonts;
311 while (pFont)
312 {
313 if (SEqual(szStr1: pFont->Name.getData(), szStr2: FontFaceName))
314 {
315 if (InitFont(rFont, pFont, iSize: iDefFontSize, dwWeight: dwDefWeight, fDoShadow)) break;
316 }
317 pFont = pFont->pNext;
318 }
319 // no internal font matching? Then create one using the given face/filename (using a system font)
320 if (!pFont)
321 {
322 pFont = new C4VectorFont();
323 if (pFont->Init(szFacename: FontFaceName, iSize: iDefFontSize, dwWeight: dwDefWeight, szCharSet: Config.General.LanguageCharset))
324 {
325 AddVectorFont(pNewFont: pFont);
326 if (!InitFont(rFont, pFont, iSize: iDefFontSize, dwWeight: dwDefWeight, fDoShadow))
327 throw std::runtime_error(std::format(fmt: "Error initializing font {}", args: +FontFaceName));
328 }
329 else
330 {
331 delete pFont;
332 // no match for font face found
333 throw std::runtime_error(std::format(fmt: "Font face {} undefined", args: +FontFaceName));
334 }
335 }
336 }
337 catch (std::runtime_error &e)
338 {
339 LogFatalNTr(message: e.what());
340 LogFatal(id: C4ResStrTableKey::IDS_ERR_INITFONTS);
341 return false;
342 }
343 rFont.id = 0;
344 }
345 }
346 // done, success
347 return true;
348}
349