Chesto 0.9
A declarative and element-based library for creating GUIs on homebrew'd consoles
TextElement.cpp
1#include "TextElement.hpp"
2#include "RootDisplay.hpp"
3#include <fstream>
4#include <ctime> // std::time
5#include <dirent.h> // for directory reading
6#include <map>
7#include <algorithm>
8
9namespace Chesto {
10
11const char *TextElement::fontPaths[] = {
12 RAMFS "./res/fonts/OpenSans-Regular.ttf", // 0 = NORMAL
13 RAMFS "./res/fonts/UbuntuMono-Regular.ttf", // 1 = MONOSPACED
14 RAMFS "./res/fonts/oldmono.ttf", // 2 = OLD_MONOSPACED
15 RAMFS "./res/fonts/PTSerif-Regular.ttf", // 3 = SERIF
16 RAMFS "./res/fonts/NotoSansSC-Regular.ttf", // 4 = SIMPLIFIED_CHINESE
17 RAMFS "./res/fonts/NotoSansKR-Regular.ttf", // 5 = KOREAN
18 RAMFS "./res/fonts/NotoSansJP-Regular.ttf", // 6 = JAPANESE
19};
20
21std::map<std::string, std::string> TextElement::i18nCache = {};
22std::string TextElement::curLang = "en-us";
23
24bool TextElement::useSimplifiedChineseFont = false;
25bool TextElement::useKoreanFont = false;
26bool TextElement::useJapaneseFont = false;
27
28// map of specific text strings to force a specific font type
29std::map<std::string, int> TextElement::forcedLangFonts = {};
30
31TextElement::TextElement()
32{
33}
34
35// static method to enumerate all languages into a vector of pairs
36std::vector<std::pair<std::string, std::string>> TextElement::getAvailableLanguages() {
37 std::vector<std::pair<std::string, std::string>> languages;
38 // read all files in RAMFS res/i18n and their 'meta.lang.name' entry
39 std::string i18nPath = RAMFS "res/i18n/";
40 DIR* dir = opendir(i18nPath.c_str());
41 if (dir) {
42 struct dirent* entry;
43 while ((entry = readdir(dir)) != NULL) {
44 std::string fileName = entry->d_name;;
45 if (fileName.length() > 4 && fileName.substr(fileName.length() - 4) == ".ini") {
46 std::string locale = fileName.substr(0, fileName.length() - 4);
47 // to lower case
48 std::transform(locale.begin(), locale.end(), locale.begin(), ::tolower);
49 // read the file to find meta.lang.name
50 std::ifstream file(i18nPath + fileName);
51 if (file.is_open()) {
52 std::string line;
53 while (std::getline(file, line)) {
54 if (line.find("meta.lang.name = ") == 0) {
55 std::string langName = line.substr(strlen("meta.lang.name = "));
56 languages.push_back({locale, langName});
57
58 // also store the language name with its forced font face
59 if (locale == "zh-cn") {
60 forcedLangFonts[langName] = SIMPLIFIED_CHINESE;
61 } else if (locale == "ko-kr") {
62 forcedLangFonts[langName] = KOREAN;
63 } else if (locale == "ja-jp") {
64 forcedLangFonts[langName] = JAPANESE;
65 } else {
66 forcedLangFonts[langName] = NORMAL;
67 }
68 break;
69 }
70 }
71 file.close();
72 }
73 }
74 }
75 closedir(dir);
76 }
77
78 return languages;
79}
80// Helper function to load i18n file into cache
81static void loadI18nFile(const std::string& filePath, std::map<std::string, std::string>& cache) {
82 std::ifstream file(filePath);
83 if (file.is_open()) {
84 std::string line;
85 while (std::getline(file, line)) {
86 size_t pos = line.find(" =");
87 if (pos == std::string::npos) {
88 continue; // bad format
89 }
90 std::string key = line.substr(0, pos);
91 pos = line.find("= ");
92 if (pos == std::string::npos) {
93 continue;
94 }
95 std::string value = line.substr(pos + 2);
96 cache[key] = value;
97 }
98 file.close();
99 }
100}
101
102// static method to load i18n cache
103void TextElement::loadI18nCache(std::string locale) {
104 std::transform(locale.begin(), locale.end(), locale.begin(), ::tolower);
105 TextElement::curLang = locale;
106
107 // clear existing cache
108 TextElement::i18nCache.clear();
109
110 // always use English as the base (fallback for missing translations)
111 std::string englishPath = RAMFS "res/i18n/en-us.ini";
112 loadI18nFile(englishPath, TextElement::i18nCache);
113
114 // overlay the target locale (if not English)
115 if (locale != "en-us") {
116 std::string localePath = RAMFS "res/i18n/" + locale + ".ini";
117 loadI18nFile(localePath, TextElement::i18nCache);
118 }
119
120 TextElement::useSimplifiedChineseFont = false;
121 TextElement::useKoreanFont = false;
122 TextElement::useJapaneseFont = false;
123
124 if (locale == "zh-cn") {
125 printf("Overriding font choice for Simplified Chinese\n");
126 TextElement::useSimplifiedChineseFont = true;
127 }
128 if (locale == "ko-kr") {
129 printf("Overriding font choice for Korean\n");
130 TextElement::useKoreanFont = true;
131 }
132 if (locale == "ja-jp") {
133 printf("Overriding font choice for Japanese\n");
134 TextElement::useJapaneseFont = true;
135 }
136}
137
138TextElement::TextElement(std::string text, int size, CST_Color* color, int font_type, int wrapped_width)
139{
140 std::string sText = text;
141 setText(sText);
142 setSize(size);
143 if (color) setColor(*color);
144 setFont(font_type);
145 setWrappedWidth(wrapped_width);
146 update();
147}
148
149void TextElement::setText(const std::string& text)
150{
151 this->text = text;
152}
153
154void TextElement::setSize(int size)
155{
156 this->textSize = size;
157}
158
159void TextElement::setColor(const CST_Color& color)
160{
161 this->textColor = color;
162}
163
164void TextElement::setFont(int font_type)
165{
166 this->textFont = font_type;
167}
168
169void TextElement::setWrappedWidth(int wrapped_width)
170{
171 this->textWrappedWidth = wrapped_width;
172}
173
174void TextElement::update(bool forceUpdate)
175{
176 std::string key = Texture::textElemPrefix + text + std::to_string(textSize);
177
178 clear();
179
180 if (!loadFromCache(key) || forceUpdate)
181 {
182 int actualFont = textFont;
183 if (TextElement::useSimplifiedChineseFont && textFont == NORMAL) {
184 actualFont = SIMPLIFIED_CHINESE;
185 }
186 if (TextElement::useKoreanFont && textFont == NORMAL) {
187 actualFont = KOREAN;
188 }
189 if (TextElement::useJapaneseFont && textFont == NORMAL) {
190 actualFont = JAPANESE;
191 }
192
193 // also, if the specific text string is in the map of force-lang strings, always use that font instead
194 if (forcedLangFonts.find(text) != forcedLangFonts.end()) {
195 actualFont = forcedLangFonts[text];
196 }
197
198 auto fontPath = fontPaths[actualFont % 7];
199 if (customFontPath != "") {
200 fontPath = customFontPath.c_str();
201 }
202
203 TTF_Font* font = TTF_OpenFont(fontPath, textSize);
204
205 if (font == NULL) {
206 printf("TTF_OpenFont failed for '%s' at size %d: %s\n", fontPath, textSize, TTF_GetError());
207 width = 0;
208 height = 0;
209 return;
210 }
211
212 CST_Surface *textSurface = ((actualFont == ICON) || (textWrappedWidth == 0)) ?
213 TTF_RenderUTF8_Blended(font, text.c_str(), textColor) :
214 TTF_RenderUTF8_Blended_Wrapped(font, text.c_str(), textColor, textWrappedWidth);
215 if(textSurface==NULL) printf("TTF_GetError: %s\n", TTF_GetError());
216
217 loadFromSurfaceSaveToCache(key, textSurface);
218
219 CST_FreeSurface(textSurface);
220 TTF_CloseFont(font);
221 }
222
223 getTextureSize(&width, &height);
224}
225
226std::string i18n(std::string key) {
227 if (const auto& keyItr = TextElement::i18nCache.find(key); keyItr != TextElement::i18nCache.end()) {
228 return keyItr->second;
229 }
230 return key;
231}
232
233std::string i18n_number(int number) {
234 std::string decimalSeparator = i18n("number.decimal");
235 std::string thousandsSeparator = i18n("number.thousands");
236 if (decimalSeparator.empty()) {
237 decimalSeparator = ".";
238 }
239 if (thousandsSeparator.empty()) {
240 thousandsSeparator = ",";
241 }
242 std::string numberString = std::to_string(number);
243 size_t decimalPos = numberString.find(".");
244 if (decimalPos == std::string::npos) {
245 decimalPos = numberString.length();
246 }
247 std::string integerPart = numberString.substr(0, decimalPos);
248 std::string decimalPart = numberString.substr(decimalPos);
249 if (decimalPart.length() > 0) {
250 decimalPart = decimalSeparator + decimalPart.substr(1);
251 }
252 for (int i = integerPart.length() - 3; i > 0; i -= 3) {
253 integerPart.insert(i, thousandsSeparator);
254 }
255 return integerPart + decimalPart;
256}
257
258std::string i18n_date(int timestamp) {
259 std::string dateFormatString = i18n("date.format");
260 if (dateFormatString.empty()) {
261 dateFormatString = "%Y-%m-%d";
262 }
263 // convert int to time_t
264 time_t timestamp2 = static_cast<time_t>(timestamp);
265
266 struct tm* timeinfo = localtime(&timestamp2);
267 char buffer[256];
268 strftime(buffer, sizeof(buffer), dateFormatString.c_str(), timeinfo);
269 return std::string(buffer);
270}
271
272} // namespace Chesto
int width
width and height of this element (must be manually set, isn't usually calculated (but is in some case...
Definition: Element.hpp:132
void update(bool forceUpdate=false)
update TextElement with changes
bool loadFromSurfaceSaveToCache(std::string &key, CST_Surface *surface)
Loads the texture from a surface and saves the results in caches Returns true if successful.
Definition: Texture.cpp:96
void clear(void)
Reinitialize Texture Resets texture content, size and color.
Definition: Texture.cpp:13
bool loadFromCache(std::string &key)
Loads the texture from caches Returns true if successful.
Definition: Texture.cpp:81
void getTextureSize(int *w, int *h)
Return texture's original size.
Definition: Texture.cpp:230