forked from aui-framework/aui
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAFont.cpp
More file actions
369 lines (330 loc) · 13.3 KB
/
Copy pathAFont.cpp
File metadata and controls
369 lines (330 loc) · 13.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
/*
* AUI Framework - Declarative UI toolkit for modern C++20
* Copyright (C) 2020-2025 Alex2772 and Contributors
*
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <ft2build.h>
#include <freetype/freetype.h>
#include <freetype/ftsnames.h>
#include "AUI/Font/FreeType.h"
#include "AUI/Platform/AFontManager.h"
#include "AUI/Logging/ALogger.h"
#include <fstream>
#include <string>
#include "AUI/Common/AStringVector.h"
#include "AFont.h"
#if AUI_PLATFORM_LINUX
#include <fontconfig/fontconfig.h>
#elif AUI_PLATFORM_WIN
#include <windows.h>
#endif
AFont::AFont(AFontManager* fm, const AString& path) :
ft(fm->mFreeType) {
if (FT_New_Face(fm->mFreeType->getFt(), path.toStdString().c_str(), 0, &mFace)) {
throw AException("Could not load font: " + path);
}
}
AFont::AFont(AFontManager* fm, const AUrl& url) :
ft(fm->mFreeType) {
if (url.schema() == "file") {
if (FT_New_Face(fm->mFreeType->getFt(), url.path().toStdString().c_str(), 0, &mFace)) {
throw AException("Could not load font: " + url.full());
}
return;
}
mFontDataBuffer = AByteBuffer::fromStream(url.open());
if (FT_New_Memory_Face(fm->mFreeType->getFt(), (const FT_Byte*) mFontDataBuffer.data(), mFontDataBuffer.getSize(),
0, &mFace)) {
throw AException("Could not load font: " + url.full());
}
}
AFont::~AFont() {
if (mFace) {
FT_Done_Face(mFace);
}
}
AString AFont::getFontFamilyName() const {
FT_SfntName name;
FT_Get_Sfnt_Name(mFace, 0, &name);
return std::string(name.string, name.string + name.string_len);
}
AFontFamily::Weight AFont::getFontWeight() const {
return AFontFamily::NORMAL;
}
bool AFont::isItalic() const {
return mFace->style_flags & FT_STYLE_FLAG_ITALIC;
}
bool AFont::hasGlyph(char32_t codepoint) const {
return FT_Get_Char_Index(mFace, codepoint) != 0;
}
void AFontManager::ensureFallbackFace() {
if (mFallbackFace) {
return; // already loaded
}
if (mFallbackAttempted) {
// Already tried and failed; don't retry every glyph
return;
}
mFallbackAttempted = true;
#if AUI_PLATFORM_LINUX
// Use fontconfig to find a CJK-capable sans-serif font.
// Ensure fontconfig is initialized before using default config (nullptr).
if (!FcInit()) {
ALogger::warn("Font") << "FcInit() failed; CJK fallback unavailable";
return;
}
// Use a charset-based query with a common CJK character to ensure the font actually has CJK glyphs.
FcPattern* pattern = FcPatternCreate();
FcCharSet* charset = FcCharSetCreate();
FcCharSetAddChar(charset, 0x4E2D); // U+4E2D = '中' (common CJK character)
FcPatternAddCharSet(pattern, FC_CHARSET, charset);
FcPatternAddString(pattern, FC_FAMILY, reinterpret_cast<const FcChar8*>("sans"));
FcConfigSubstitute(nullptr, pattern, FcMatchPattern);
FcDefaultSubstitute(pattern);
FcCharSetDestroy(charset);
FcResult result;
FcPattern* match = FcFontMatch(nullptr, pattern, &result);
if (match) {
if (result == FcResultMatch) {
FcChar8* path = nullptr;
if (FcPatternGetString(match, FC_FILE, 0, &path) == FcResultMatch) {
mFallbackFontPath = reinterpret_cast<const char*>(path);
// Try to load the fallback font face.
// TTC collections may need face_index > 0; try index 0 first (usually Regular).
if (FT_New_Face(mFreeType->getFt(), mFallbackFontPath.toStdString().c_str(), 0, &mFallbackFace)) {
ALogger::warn("Font") << "Failed to load fallback font: " << mFallbackFontPath;
mFallbackFace = nullptr;
mFallbackFontPath.clear();
} else {
ALogger::info("Font") << "Loaded CJK fallback font: " << mFallbackFontPath;
}
}
}
FcPatternDestroy(match);
}
FcPatternDestroy(pattern);
#elif AUI_PLATFORM_WIN
// Try known CJK font files from the Windows Fonts directory.
// Order: most-comprehensive first (Microsoft YaHei covers CJK + Kana,
// Malgun Gothic covers CJK + Hangul, then region-specific fallbacks).
const char* cjkFonts[] = {
"msyh.ttc", // Microsoft YaHei (Simplified Chinese, includes CJK + Kana)
"malgun.ttf", // Malgun Gothic (Korean, includes Hangul + CJK)
"simsun.ttc", // SimSun (Chinese Traditional)
"msgothic.ttc", // MS Gothic (Japanese)
"yugothic.ttf", // Yu Gothic (Japanese)
"meiryo.ttc", // Meiryo (Japanese)
};
static const AString fontsDir = [] {
wchar_t buf[MAX_PATH];
UINT len = GetWindowsDirectoryW(buf, MAX_PATH);
if (len > 0 && len < MAX_PATH) {
return AString(reinterpret_cast<char16_t*>(buf)) + "\\Fonts\\";
}
ALogger::warn("Font") << "GetWindowsDirectoryW() failed, falling back to C:\\Windows\\Fonts\\";
return AString("C:\\Windows\\Fonts\\");
}();
for (auto& f : cjkFonts) {
AString fontPath = fontsDir + f;
if (FT_New_Face(mFreeType->getFt(), fontPath.toStdString().c_str(), 0, &mFallbackFace) == 0) {
mFallbackFontPath = fontPath;
ALogger::info("Font") << "Loaded CJK fallback font: " << mFallbackFontPath;
break;
}
}
if (!mFallbackFace) {
ALogger::warn("Font") << "No CJK fallback font found on Windows";
}
#elif AUI_PLATFORM_MACOS
// macOS system CJK fonts (stable paths since OS X 10.11).
const char* cjkFontsMac[] = {
"/System/Library/Fonts/PingFang.ttc",
"/System/Library/Fonts/AppleSDGothicNeo.ttc",
"/System/Library/Fonts/Hiragino Sans.ttc",
"/System/Library/Fonts/Supplemental/AppleSDGothicNeo.ttc",
};
for (auto& f : cjkFontsMac) {
AString fontPath(f);
if (FT_New_Face(mFreeType->getFt(), fontPath.toStdString().c_str(), 0, &mFallbackFace) == 0) {
mFallbackFontPath = fontPath;
ALogger::info("Font") << "Loaded CJK fallback font: " << mFallbackFontPath;
break;
}
}
if (!mFallbackFace) {
ALogger::warn("Font") << "No CJK fallback font found on macOS";
}
#elif AUI_PLATFORM_ANDROID
// Android system CJK fonts (varies by API level).
const char* cjkFontsAndroid[] = {
"/system/fonts/NotoSansCJK-Regular.ttc", // Android 5-9
"/system/fonts/NotoSansSC-Regular.otf", // Android 10+ (Chinese)
"/system/fonts/NotoSansKR-Regular.otf", // Android 10+ (Korean)
"/system/fonts/NotoSansJP-Regular.otf", // Android 10+ (Japanese)
};
for (auto& f : cjkFontsAndroid) {
AString fontPath(f);
if (FT_New_Face(mFreeType->getFt(), fontPath.toStdString().c_str(), 0, &mFallbackFace) == 0) {
mFallbackFontPath = fontPath;
ALogger::info("Font") << "Loaded CJK fallback font: " << mFallbackFontPath;
break;
}
}
if (!mFallbackFace) {
ALogger::warn("Font") << "No CJK fallback font found on Android";
}
#elif AUI_PLATFORM_IOS
// iOS: try system font paths first (some iOS versions allow reading them),
// then fall back to a bundled resource if available.
const char* cjkFontsIos[] = {
"/System/Library/Fonts/PingFang.ttc",
"/System/Library/Fonts/AppleSDGothicNeo.ttc",
};
{
bool loaded = false;
for (auto& f : cjkFontsIos) {
AString fontPath(f);
if (FT_New_Face(mFreeType->getFt(), fontPath.toStdString().c_str(), 0, &mFallbackFace) == 0) {
mFallbackFontPath = fontPath;
ALogger::info("Font") << "Loaded CJK fallback font: " << mFallbackFontPath;
loaded = true;
break;
}
}
if (!loaded) {
// Try bundled resource
try {
mFallbackFontDataBuffer = AByteBuffer::fromStream(AUrl(":uni/font/NotoSansCJK-Fallback.ttf").open());
if (FT_New_Memory_Face(mFreeType->getFt(),
(const FT_Byte*) mFallbackFontDataBuffer.data(),
mFallbackFontDataBuffer.getSize(), 0, &mFallbackFace) == 0) {
mFallbackFontPath = AString(":uni/font/NotoSansCJK-Fallback.ttf");
ALogger::info("Font") << "Loaded CJK fallback font from bundle";
loaded = true;
}
} catch (...) {
// bundled font not present
}
}
if (!loaded) {
ALogger::warn("Font") << "No CJK fallback font found on iOS";
}
}
#else
// Unknown platform; mark fallback as unavailable.
ALogger::warn("Font") << "CJK font fallback not available on this platform";
#endif
}
glm::vec2 AFont::getKerning(wchar_t left, wchar_t right) {
FT_Vector vec2;
FT_Get_Kerning(mFace, left, right, FT_KERNING_DEFAULT, &vec2);
return {vec2.x >> 6, vec2.y >> 6};
}
AFont::Character AFont::renderGlyph(const FontEntry& fs, AChar glyph) {
int size = fs.first.size;
FontRendering fr = fs.first.fr;
// Determine which face to use: try primary, fall back to CJK font if glyph missing.
FT_Face face = nullptr;
if (hasGlyph(glyph.codepoint())) {
face = mFace;
} else {
FT_Face fallbackFace = AFontManager::inst().getFallbackFace();
if (fallbackFace) {
face = fallbackFace;
}
}
if (!face) {
// No font has this glyph; return empty character.
return Character{};
}
FT_Set_Pixel_Sizes(face, 0, size);
FT_Int32 flags = FT_LOAD_RENDER;
if (fr == FontRendering::SUBPIXEL)
flags |= FT_LOAD_TARGET_LCD;
if (fr == FontRendering::NEAREST)
flags |= FT_LOAD_TARGET_MONO;
FT_Error e = FT_Load_Char(face, glyph.codepoint(), flags);
if (e) {
// Neither face can render this glyph.
ALogger::debug("Font") << "FT_Load_Char failed for U+"
<< std::hex << std::uint32_t(glyph.codepoint()) << " (error " << std::dec << e << ")";
return Character{};
}
FT_GlyphSlot g = face->glyph;
if (g->bitmap.width && g->bitmap.rows) {
const float div = 1.f / 64.f;
int width = g->bitmap.width;
if (fr == FontRendering::SUBPIXEL)
width /= 3;
int height = g->bitmap.rows;
AByteBuffer data;
if (fr == FontRendering::NEAREST) {
// when nearest, freetype renders glyphs into the 1bit-depth image but OpenGL required at least8bit-depth,
// so we will convert it here
data.resize(g->bitmap.rows * g->bitmap.width);
for (unsigned r = 0; r < g->bitmap.rows; ++r) {
unsigned char* bufPtr = g->bitmap.buffer + r * g->bitmap.pitch;
for (unsigned c = 0; c < g->bitmap.width; ++c) {
data.at<std::uint8_t>(c + r * g->bitmap.width) = (bufPtr[c / 8] & (0b10000000 >> (c % 8))) ? 255
: 0;
}
}
} else {
data.reserve(g->bitmap.rows * g->bitmap.pitch);
for (unsigned r = 0; r < g->bitmap.rows; ++r) {
unsigned char* bufPtr = g->bitmap.buffer + r * g->bitmap.pitch;
data.write(reinterpret_cast<const char*>(bufPtr), g->bitmap.width);
}
}
int imageFormat = APixelFormat::BYTE;
if (fr == FontRendering::SUBPIXEL)
imageFormat |= APixelFormat::RGB;
else
imageFormat |= APixelFormat::R;
return Character{
.image = _new<AImage>(data, glm::uvec2(width, height), imageFormat),
.size = { div * g->metrics.width, div * g->metrics.height },
.horizontal = {
.bearing = { g->bitmap_left, g->bitmap_top },
.advance = div * g->metrics.horiAdvance,
},
.vertical = {
.bearing = { div * g->metrics.vertBearingX, div * g->metrics.vertBearingY },
.advance = div * g->metrics.vertAdvance,
},
};
}
return Character{};
}
AFont::Character& AFont::getCharacter(const FontEntry& charset, AChar glyph) {
auto& chars = charset.second.characters;
if (chars.size() > glyph && chars[glyph.codepoint()]) {
return *chars[glyph.codepoint()];
} else {
if (chars.size() <= glyph) {
chars.resize(glyph + 1, std::nullopt);
}
chars[glyph.codepoint()] = std::move(renderGlyph(charset, glyph));
return *chars[glyph.codepoint()];
}
}
int AFont::length(const FontEntry& charset, AStringView text) {
return length(charset, text.utf8().begin(), text.utf8().end());
}
int AFont::length(const FontEntry& charset, std::u32string_view text) {
return length(charset, text.begin(), text.end());
}
bool AFont::isHasKerning() {
return FT_HAS_KERNING(mFace);
}
int AFont::getAscenderHeight(unsigned size) const {
return int(mFace->ascender) * size / mFace->height;
}
int AFont::getDescenderHeight(unsigned size) const {
return -int(mFace->descender) * size / mFace->height;
}