Rendering Engine 0.2.9
Modular Graphics Rendering Engine | v0.2.9
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"}, {0x0020, 0x007E}},
11 {{"Cyrillic"}, {0x0400, 0x04FF}},
12 {{"Greek"}, {0x0370, 0x03FF}},
13
14 // Asian
15 {{"Han"}, {0x4E00, 0x9FFF}},
16 {{"HanExtensionA"}, {0x3400, 0x4DBF}},
17
18 {{"Hiragana"}, {0x3040, 0x309F}},
19 {{"Katakana"}, {0x30A0, 0x30FF}},
20 {{"KatakanaPhoneticExtensions"}, {0x31F0, 0x31FF}},
21
22 {{"Hangul"}, {0xAC00, 0xD7AF}},
23
24 // Requires shaping
25 // East
26 {{"Hebrew"}, {0x0590, 0x05FF}},
27 {{"Arabic"}, {0x0600, 0x06FF}},
28 {{"ArabicSupplement"}, {0x0750, 0x077F}},
29 {{"ArabicExtended-A"}, {0x08A0, 0x08FF}},
30 {{"Aramaic"}, {0x0700, 0x074F}},
31 {{"Thaana"}, {0x0780, 0x07BF}},
32
33 // Indic
34 {{"Devanagari"}, {0x0900, 0x097F}},
35 {{"Bengali"}, {0x0980, 0x09FF}},
36 {{"Gurmukhi"}, {0x0A00, 0x0A7F}},
37 {{"Gujarati"}, {0x0A80, 0x0AFF}},
38 {{"Oriya"}, {0x0B00, 0x0B7F}},
39 {{"Tamil"}, {0x0B80, 0x0BFF}},
40 {{"Telugu"}, {0x0C00, 0x0C7F}},
41 {{"Kannada"}, {0x0C80, 0x0CFF}},
42 {{"Malayalam"}, {0x0D00, 0x0D7F}},
43 {{"Sinhala"}, {0x0D80, 0x0DFF}},
44
45 // Southeast Asian
46 {{"Thai"}, {0x0E00, 0x0E7F}},
47 {{"Lao"}, {0x0E80, 0x0EFF}},
48 {{"Myanmar"}, {0x1000, 0x109F}},
49 {{"Khmer"}, {0x1780, 0x17FF}},
50
51 // Tibetan
52 {{"Tibetan"}, {0x0F00, 0x0FFF}}
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
116{
117}
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
149{
151 LoadPreloadableFontAtlasesFromPackage(mAvailableFontsInPackage);
152}
153
155{
156 return mRenderResourceContext;
157}
158
159std::shared_ptr<FontResources> TextRenderer::GetFontResources(const std::string& fontName, 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 auto key = std::make_pair(fontName, fontSize);
197 mFontResources[key] = std::make_shared<FontResources>(mRenderResourceContext, this, mAvailableFontsInFolder[fontName], fontSize);
198
199 return mFontResources[key];
200 }
201 else
202 {
203 // Log Font {fontName} is not avalable in asset folder
204 }
205 }
206 }
207 }
208
209 return nullptr;
210}
211
213{
214 bStoreFontAtlasesInFiles = in;
215}
216
217const std::vector<std::string>& TextRenderer::GetScriptsRequiredShaping() const
218{
219 return sScriptsRequiresShaping;
220}
221
222std::pair<std::uint32_t, std::uint32_t> TextRenderer::GetScriptRange(std::string script)
223{
224 std::pair<std::uint32_t, std::uint32_t> result{0, 0};
225
226 auto search = sScriptRanges.find(script);
227 {
228 if (search != sScriptRanges.end())
229 return sScriptRanges[script];
230 }
231 return result;
232}
233
234void TextRenderer::LoadFontsAvailableInFolder(std::string pathToFolder)
235{
236 LOG_INFO("Scanning font folder: " + pathToFolder);
237 // 1. Check if path is valid and exist
238 boost::filesystem::path pathToDirectory = boost::filesystem::path(pathToFolder);
239 const bool isValidFolderPath = boost::filesystem::exists(boost::filesystem::path(pathToFolder)) && boost::filesystem::is_directory(boost::filesystem::path(pathToFolder));
240 if (!isValidFolderPath)
241 {
242 return;
243 }
244 // 2. Iterate through files in the folder.
245 // if file is in the list of supported extensions
246 for (boost::filesystem::recursive_directory_iterator it(pathToDirectory), end;
247 it != end;
248 ++it)
249 {
250 const boost::filesystem::path& filePath = it->path();
251
252 if (!boost::filesystem::is_regular_file(filePath))
253 continue;
254
255 const std::string ext = filePath.extension().string();
256 if (ext != ".ttf" && ext != ".otf")
257 continue;
258
259 // Fonts name and file path stored for future runtime loading
260 std::string fontName = filePath.stem().string();
261 mAvailableFontsInFolder[fontName] = filePath.string();
262 }
263 LOG_INFO("Discovered " +
264 std::to_string(mAvailableFontsInFolder.size()) +
265 " fonts in folder.");
266}
267
268void TextRenderer::LoadPreloadableFontAtlasesFromFolder(const std::unordered_map<std::string, std::string>& availableFontsInFolder)
269{
270 LOG_INFO("Preloading font atlases from folder...");
271 auto start = std::chrono::steady_clock::now();
273
274 for (const auto& [fontName, filePath] : availableFontsInFolder)
275 {
276 for (auto requestedScript : appConfig.textScripts)
277 {
278 auto preloadableScriptIt = std::find(sFontAtlasPreloadableScripts.begin(), sFontAtlasPreloadableScripts.end(), requestedScript);
279 if (preloadableScriptIt != sFontAtlasPreloadableScripts.end())
280 {
281 for (auto fontSize : appConfig.fontSizePreload)
282 {
283 auto key = std::make_pair(fontName, fontSize);
284 if (mFontResources.find(key) == mFontResources.end())
285 {
286 LOG_DEBUG("Creating font resource: " + fontName +
287 " size " + std::to_string(fontSize));
288 mFontResources[key] = std::make_shared<FontResources>(mRenderResourceContext, this, filePath, fontSize);
289 mFontResources[key]->StoreFontAtlasesInFiles(bStoreFontAtlasesInFiles);
290 }
291 LOG_DEBUG("Preloading script '" + requestedScript +
292 "' for font " + fontName +
293 " size " + std::to_string(fontSize));
294 const std::uint32_t rangeBegin = sScriptRanges[requestedScript].first;
295 const std::uint32_t rangeEnd = sScriptRanges[requestedScript].second;
296 mFontResources[key]->LoadGlyphsFromCodePointRange(rangeBegin, rangeEnd);
297 }
298 }
299 }
300 }
301
302 auto end = std::chrono::steady_clock::now();
303 float ms = std::chrono::duration<float, std::milli>(end - start).count();
304
305 LOG_INFO("Font atlas preloading (folder) completed in " +
306 std::to_string(ms) + " ms.");
307}
308
310{
311 LOG_INFO("Scanning fonts in package...");
312 const auto& entries = Utility::GetPackEntries();
313
314 std::string folderEntry = { "Fonts/" };
315 for (auto& entry : entries)
316 {
317 const std::string& virtualPath = entry.first;
318 if (virtualPath.rfind(folderEntry, 0) == 0) // starts with Fonts/
319 {
320 auto fontFilePath = boost::filesystem::path(virtualPath);
321 const std::string ext = fontFilePath.extension().string();
322 if (ext != ".ttf" && ext != ".otf")
323 continue;
324
325 // Fonts name and virtual path stored for future runtime loading
326 std::string fontName = fontFilePath.stem().string();
327 mAvailableFontsInPackage[fontName] = fontFilePath.string();
328 }
329 }
330 LOG_INFO("Discovered " +
331 std::to_string(mAvailableFontsInPackage.size()) +
332 " fonts in package.");
333}
334
335void TextRenderer::LoadPreloadableFontAtlasesFromPackage(const std::unordered_map<std::string, std::string>& availableFontsInPackage)
336{
337 LOG_INFO("Preloading font atlases from package...");
338 auto start = std::chrono::steady_clock::now();
340
341 for (const auto& [fontName, virtualFilePath] : availableFontsInPackage)
342 {
343 for (auto requestedScript : appConfig.textScripts)
344 {
345 auto preloadableScriptIt = std::find(sFontAtlasPreloadableScripts.begin(), sFontAtlasPreloadableScripts.end(), requestedScript);
346 if (preloadableScriptIt != sFontAtlasPreloadableScripts.end())
347 {
348 for (auto fontSize : appConfig.fontSizePreload)
349 {
350 auto key = std::make_pair(fontName, fontSize);
351 if (mFontResources.find(key) == mFontResources.end())
352 {
353 std::vector<uint8_t> binaryFileData = Utility::ReadPackedFile(virtualFilePath);
354 LOG_DEBUG("Creating font resource: " + fontName +
355 " size " + std::to_string(fontSize));
356 mFontResources[key] = std::make_shared<FontResources>(mRenderResourceContext, this, fontName, binaryFileData, fontSize);
357 mFontResources[key]->StoreFontAtlasesInFiles(bStoreFontAtlasesInFiles);
358 }
359 LOG_DEBUG("Preloading script '" + requestedScript +
360 "' for font " + fontName +
361 " size " + std::to_string(fontSize));
362 const std::uint32_t rangeBegin = sScriptRanges[requestedScript].first;
363 const std::uint32_t rangeEnd = sScriptRanges[requestedScript].second;
364 mFontResources[key]->LoadGlyphsFromCodePointRange(rangeBegin, rangeEnd);
365 }
366 }
367 }
368 }
369
370 auto end = std::chrono::steady_clock::now();
371 float ms = std::chrono::duration<float, std::milli>(end - start).count();
372
373 LOG_INFO("Font atlas preloading (package) completed in " +
374 std::to_string(ms) + " ms.");
375}
376
377} //namespace rendering_engine
TextRenderer(RenderResourceContext rrc)
Constructs the text renderer and initializes internal font systems.
void LoadFontsAvailableInFolder(std::string pathToFolder)
Discovers fonts available in a filesystem folder.
std::shared_ptr< FontResources > GetFontResources(const std::string &fontName, int fontSize)
Retrieves or creates font resources for a font and size.
~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:281
static AppConfig ReadConfigFile()
Reads application settings from the JSON config file.
Definition: utility.cpp:34
static std::vector< uint8_t > ReadPackedFile(const std::string &entryPath)
Reads raw bytes of a file stored inside Pack.bin.
Definition: utility.cpp:322
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:27
std::vector< std::string > textScripts
Unicode scripts to preload for text rendering.
Definition: utility.hpp:37
std::vector< int > fontSizePreload
Font sizes to preload at startup.
Definition: utility.hpp:39
Aggregates pointers to global rendering resource managers.