Rendering Engine 0.2.17
Modular Graphics Rendering Engine | v0.2.17
Loading...
Searching...
No Matches
text_renderer.cpp
Go to the documentation of this file.
1#include "text_renderer.hpp"
3#include "font_resources.hpp"
4#include "logger.hpp"
5
6namespace rendering_engine
7{
8std::unordered_map<std::string, std::pair<std::uint32_t, std::uint32_t>> TextRenderer::sScriptRanges{
9 // European
10 {{"Latin"}, {0x0020U, 0x007EU}},
11 {{"Cyrillic"}, {0x0400U, 0x04FFU}},
12 {{"Greek"}, {0x0370U, 0x03FFU}},
13
14 // Asian
15 {{"Han"}, {0x4E00U, 0x9FFFU}},
16 {{"HanExtensionA"}, {0x3400U, 0x4DBFU}},
17
18 {{"Hiragana"}, {0x3040U, 0x309FU}},
19 {{"Katakana"}, {0x30A0U, 0x30FFU}},
20 {{"KatakanaPhoneticExtensions"}, {0x31F0U, 0x31FFU}},
21
22 {{"Hangul"}, {0xAC00U, 0xD7AFU}},
23
24 // Requires shaping
25 // East
26 {{"Hebrew"}, {0x0590U, 0x05FFU}},
27 {{"Arabic"}, {0x0600U, 0x06FFU}},
28 {{"ArabicSupplement"}, {0x0750U, 0x077FU}},
29 {{"ArabicExtended-A"}, {0x08A0U, 0x08FFU}},
30 {{"Aramaic"}, {0x0700U, 0x074FU}},
31 {{"Thaana"}, {0x0780U, 0x07BFU}},
32
33 // Indic
34 {{"Devanagari"}, {0x0900U, 0x097FU}},
35 {{"Bengali"}, {0x0980U, 0x09FFU}},
36 {{"Gurmukhi"}, {0x0A00U, 0x0A7FU}},
37 {{"Gujarati"}, {0x0A80U, 0x0AFFU}},
38 {{"Oriya"}, {0x0B00U, 0x0B7FU}},
39 {{"Tamil"}, {0x0B80U, 0x0BFFU}},
40 {{"Telugu"}, {0x0C00U, 0x0C7FU}},
41 {{"Kannada"}, {0x0C80U, 0x0CFFU}},
42 {{"Malayalam"}, {0x0D00U, 0x0D7FU}},
43 {{"Sinhala"}, {0x0D80U, 0x0DFFU}},
44
45 // Southeast Asian
46 {{"Thai"}, {0x0E00U, 0x0E7FU}},
47 {{"Lao"}, {0x0E80U, 0x0EFFU}},
48 {{"Myanmar"}, {0x1000U, 0x109FU}},
49 {{"Khmer"}, {0x1780U, 0x17FFU}},
50
51 // Tibetan
52 {{"Tibetan"}, {0x0F00U, 0x0FFFU}}
53};
54std::vector<std::string> TextRenderer::sFontAtlasPreloadableScripts{
55 // European
56 {"Latin"},
57 {"Cyrillic"},
58 {"Greek"},
59
60 // Asian
61 {"Han"},
62 {"HanExtensionA"},
63
64 {"Hiragana"},
65 {"Katakana"},
66 {"KatakanaPhoneticExtensions"},
67
68 {"Hangul"}
69};
70
71std::vector<std::string> TextRenderer::sScriptsRequiresShaping{
72 {"Hebrew"},
73 {"Arabic"},
74 {"ArabicSupplement"},
75 {"ArabicExtended-A"},
76 {"Aramaic"},
77 {"Thaana"},
78 {"Devanagari"},
79 {"Bengali"},
80 {"Gurmukhi"},
81 {"Gujarati"},
82 {"Oriya"},
83 {"Tamil"},
84 {"Telugu"},
85 {"Kannada"},
86 {"Malayalam"},
87 {"Sinhala"},
88 {"Thai"},
89 {"Lao"},
90 {"Myanmar"},
91 {"Khmer"},
92 {"Tibetan"}
93};
94
96 :
97 mRenderResourceContext(rrc)
98{
99 LOG_INFO("Initializing TextRenderer...");
100 auto start = std::chrono::steady_clock::now();
101
102 mErrorResult = FT_Init_FreeType(&mLibrary);
103 if (mErrorResult)
104 {
105 LOG_ERROR("Failed to initialize FreeType library!");
106 throw std::runtime_error{ "Failed to initialize FreeType library!" };
107 }
108
109 auto end = std::chrono::steady_clock::now();
110 float ms = std::chrono::duration<float, std::milli>(end - start).count();
111
112 LOG_INFO("FreeType initialized in " + std::to_string(ms) + " ms.");
113}
114
118
120{
121 LOG_INFO("Shutting down TextRenderer...");
122 if (!mLibrary)
123 return;
124
125 for (auto& fontResource : mFontResources)
126 fontResource.second.reset();
127
128 mFontResources.clear();
129
130 FT_Done_FreeType(mLibrary);
131 mLibrary = nullptr;
132 LOG_INFO("TextRenderer shutdown complete.");
133}
134
135void TextRenderer::LoadFontsFromFolder(std::string pathToFolder)
136{
137 LOG_INFO("Loading fonts from folder: " + pathToFolder);
138 auto start = std::chrono::steady_clock::now();
139 LoadFontsAvailableInFolder(pathToFolder);
140 LoadPreloadableFontAtlasesFromFolder(mAvailableFontsInFolder);
141 auto end = std::chrono::steady_clock::now();
142 float ms = std::chrono::duration<float, std::milli>(end - start).count();
143
144 LOG_INFO("Font discovery and preload complete in " +
145 std::to_string(ms) + " ms.");
146}
147
153
155{
156 return mRenderResourceContext;
157}
158
159std::shared_ptr<FontResources> TextRenderer::GetFontResources(const std::string& fontName, unsigned int fontSize)
160{
161 auto key = std::make_pair(fontName, fontSize);
162 if (auto search = mFontResources.find(key); search != mFontResources.end())
163 {
164 return search->second;
165 }
166 else
167 {
168 if (!mAvailableFontsInPackage.empty())
169 {
170 if (!mAvailableFontsInPackage.empty())
171 {
172 auto foundInPackage = mAvailableFontsInPackage.find(fontName);
173 if (foundInPackage != mAvailableFontsInPackage.end())
174 {
175 const std::string virtualFilePath = mAvailableFontsInPackage[fontName];
176 std::vector<uint8_t> binaryFileData = Utility::ReadPackedFile(virtualFilePath);
177
178 mFontResources[key] = std::make_shared<FontResources>(mRenderResourceContext, this, fontName, binaryFileData, fontSize);
179 mFontResources[key]->StoreFontAtlasesInFiles(bStoreFontAtlasesInFiles);
180
181 return mFontResources[key];
182 }
183 else
184 {
185 // Log Font {fontName} is not avalable in asset package
186 }
187 }
188 }
189 else
190 {
191 if (!mAvailableFontsInFolder.empty())
192 {
193 auto foundInFolder = mAvailableFontsInFolder.find(fontName);
194 if (foundInFolder != mAvailableFontsInFolder.end())
195 {
196 mFontResources[key] = std::make_shared<FontResources>(mRenderResourceContext, this, mAvailableFontsInFolder[fontName], fontSize);
197
198 return mFontResources[key];
199 }
200 else
201 {
202 // Log Font {fontName} is not avalable in asset folder
203 }
204 }
205 }
206 }
207
208 return nullptr;
209}
210
212{
213 bStoreFontAtlasesInFiles = in;
214}
215
216const std::vector<std::string>& TextRenderer::GetScriptsRequiredShaping() const
217{
218 return sScriptsRequiresShaping;
219}
220
221std::pair<std::uint32_t, std::uint32_t> TextRenderer::GetScriptRange(std::string script)
222{
223 std::pair<std::uint32_t, std::uint32_t> result{ 0U, 0U };
224
225 auto search = sScriptRanges.find(script);
226 {
227 if (search != sScriptRanges.end())
228 return sScriptRanges[script];
229 }
230 return result;
231}
232
233void TextRenderer::LoadFontsAvailableInFolder(std::string pathToFolder)
234{
235 LOG_INFO("Scanning font folder: " + pathToFolder);
236 // 1. Check if path is valid and exist
237 std::filesystem::path pathToDirectory = std::filesystem::path(pathToFolder);
238 const bool isValidFolderPath = std::filesystem::exists(std::filesystem::path(pathToFolder)) && std::filesystem::is_directory(std::filesystem::path(pathToFolder));
239 if (!isValidFolderPath)
240 {
241 return;
242 }
243 // 2. Iterate through files in the folder.
244 // if file is in the list of supported extensions
245 for (std::filesystem::recursive_directory_iterator it(pathToDirectory), end;
246 it != end;
247 ++it)
248 {
249 const std::filesystem::path& filePath = it->path();
250
251 if (!std::filesystem::is_regular_file(filePath))
252 continue;
253
254 const std::string ext = filePath.extension().string();
255 if (ext != ".ttf" && ext != ".otf")
256 continue;
257
258 // Fonts name and file path stored for future runtime loading
259 std::string fontName = filePath.stem().string();
260 mAvailableFontsInFolder[fontName] = filePath.string();
261 }
262 LOG_INFO("Discovered " +
263 std::to_string(mAvailableFontsInFolder.size()) +
264 " fonts in folder.");
265}
266
267void TextRenderer::LoadPreloadableFontAtlasesFromFolder(const std::unordered_map<std::string, std::string>& availableFontsInFolder)
268{
269 LOG_INFO("Preloading font atlases from folder...");
270 auto start = std::chrono::steady_clock::now();
272
273 for (const auto& [fontName, filePath] : availableFontsInFolder)
274 {
275 for (auto requestedScript : appConfig.textScripts)
276 {
277 auto preloadableScriptIt = std::find(sFontAtlasPreloadableScripts.begin(), sFontAtlasPreloadableScripts.end(), requestedScript);
278 if (preloadableScriptIt != sFontAtlasPreloadableScripts.end())
279 {
280 for (auto fontSize : appConfig.fontSizePreload)
281 {
282 auto key = std::make_pair(fontName, fontSize);
283 if (mFontResources.find(key) == mFontResources.end())
284 {
285 LOG_DEBUG("Creating font resource: " + fontName +
286 " size " + std::to_string(fontSize));
287 mFontResources[key] = std::make_shared<FontResources>(mRenderResourceContext, this, filePath, fontSize);
288 mFontResources[key]->StoreFontAtlasesInFiles(bStoreFontAtlasesInFiles);
289 }
290 LOG_DEBUG("Preloading script '" + requestedScript +
291 "' for font " + fontName +
292 " size " + std::to_string(fontSize));
293 const std::uint32_t rangeBegin = sScriptRanges[requestedScript].first;
294 const std::uint32_t rangeEnd = sScriptRanges[requestedScript].second;
295 mFontResources[key]->LoadGlyphsFromCodePointRange(rangeBegin, rangeEnd);
296 }
297 }
298 }
299 }
300
301 auto end = std::chrono::steady_clock::now();
302 float ms = std::chrono::duration<float, std::milli>(end - start).count();
303
304 LOG_INFO("Font atlas preloading (folder) completed in " +
305 std::to_string(ms) + " ms.");
306}
307
309{
310 LOG_INFO("Scanning fonts in package...");
311 const auto& entries = Utility::GetPackEntries();
312
313 std::string folderEntry = { "Fonts/" };
314 for (auto& entry : entries)
315 {
316 const std::string& virtualPath = entry.first;
317 if (virtualPath.rfind(folderEntry, 0) == 0) // starts with Fonts/
318 {
319 auto fontFilePath = std::filesystem::path(virtualPath);
320 const std::string ext = fontFilePath.extension().string();
321 if (ext != ".ttf" && ext != ".otf")
322 continue;
323
324 // Fonts name and virtual path stored for future runtime loading
325 std::string fontName = fontFilePath.stem().string();
326 mAvailableFontsInPackage[fontName] = fontFilePath.string();
327 }
328 }
329 LOG_INFO("Discovered " +
330 std::to_string(mAvailableFontsInPackage.size()) +
331 " fonts in package.");
332}
333
334void TextRenderer::LoadPreloadableFontAtlasesFromPackage(const std::unordered_map<std::string, std::string>& availableFontsInPackage)
335{
336 LOG_INFO("Preloading font atlases from package...");
337 auto start = std::chrono::steady_clock::now();
339
340 for (const auto& [fontName, virtualFilePath] : availableFontsInPackage)
341 {
342 for (auto requestedScript : appConfig.textScripts)
343 {
344 auto preloadableScriptIt = std::find(sFontAtlasPreloadableScripts.begin(), sFontAtlasPreloadableScripts.end(), requestedScript);
345 if (preloadableScriptIt != sFontAtlasPreloadableScripts.end())
346 {
347 for (auto fontSize : appConfig.fontSizePreload)
348 {
349 auto key = std::make_pair(fontName, fontSize);
350 if (mFontResources.find(key) == mFontResources.end())
351 {
352 std::vector<uint8_t> binaryFileData = Utility::ReadPackedFile(virtualFilePath);
353 LOG_DEBUG("Creating font resource: " + fontName +
354 " size " + std::to_string(fontSize));
355 mFontResources[key] = std::make_shared<FontResources>(mRenderResourceContext, this, fontName, binaryFileData, fontSize);
356 mFontResources[key]->StoreFontAtlasesInFiles(bStoreFontAtlasesInFiles);
357 }
358 LOG_DEBUG("Preloading script '" + requestedScript +
359 "' for font " + fontName +
360 " size " + std::to_string(fontSize));
361 const std::uint32_t rangeBegin = sScriptRanges[requestedScript].first;
362 const std::uint32_t rangeEnd = sScriptRanges[requestedScript].second;
363 mFontResources[key]->LoadGlyphsFromCodePointRange(rangeBegin, rangeEnd);
364 }
365 }
366 }
367 }
368
369 auto end = std::chrono::steady_clock::now();
370 float ms = std::chrono::duration<float, std::milli>(end - start).count();
371
372 LOG_INFO("Font atlas preloading (package) completed in " +
373 std::to_string(ms) + " ms.");
374}
375
376} //namespace rendering_engine
std::shared_ptr< FontResources > GetFontResources(const std::string &fontName, unsigned int fontSize)
Retrieves or creates font resources for a font and size.
TextRenderer(RenderResourceContext rrc)
Constructs the text renderer and initializes internal font systems.
void LoadFontsAvailableInFolder(std::string pathToFolder)
Discovers fonts available in a filesystem folder.
~TextRenderer()
Shuts down the text system and releases font backend resources.
void StoreFontAtlasesInFiles(bool in)
Enables or disables storing generated font atlases to files.
const RenderResourceContext & GetRenderResourceContext() const
Returns the rendering resource context.
void LoadFontsFromFolder(std::string pathToFolder)
Loads fonts from a filesystem folder.
void LoadFontsAvailableInPackage()
Discovers fonts available in the application package.
void LoadPreloadableFontAtlasesFromFolder(const std::unordered_map< std::string, std::string > &availableFontsInFolder)
Loads preloadable font atlases from a folder.
void LoadFontsFromPackage()
Loads fonts bundled inside an application package.
const std::vector< std::string > & GetScriptsRequiredShaping() const
Returns scripts that require shaping before rendering.
void LoadPreloadableFontAtlasesFromPackage(const std::unordered_map< std::string, std::string > &availableFontsInPackage)
Loads preloadable font atlases from the application package.
std::pair< std::uint32_t, std::uint32_t > GetScriptRange(std::string script)
Returns the Unicode codepoint range for a script.
static const PackEntries & GetPackEntries()
Returns the manifest of packed files.
Definition utility.cpp:290
static AppConfig ReadConfigFile()
Reads application settings from the JSON config file.
Definition utility.cpp:37
static std::vector< uint8_t > ReadPackedFile(const std::string &entryPath)
Reads raw bytes of a file stored inside Pack.bin.
Definition utility.cpp:331
Engine-wide logging system for runtime diagnostics and performance tracking.
#define LOG_DEBUG(msg)
Definition logger.hpp:38
#define LOG_ERROR(msg)
Definition logger.hpp:41
#define LOG_INFO(msg)
Definition logger.hpp:39
Basic application settings loaded from a configuration file.
Definition utility.hpp:28
std::vector< unsigned int > fontSizePreload
Font sizes to preload at startup.
Definition utility.hpp:40
std::vector< std::string > textScripts
Unicode scripts to preload for text rendering.
Definition utility.hpp:38
Aggregates pointers to global rendering resource managers.