Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
2d882e0
Add CJK font support and fallback mechanism in AFont class
luadebug Jul 28, 2026
2e2d977
Refactor font handling to improve CJK fallback support in AFont and A…
luadebug Jul 28, 2026
1e90f5b
Move
luadebug Jul 29, 2026
03e8bad
Enhance AFontManager to use scoped locks for thread safety and manage…
luadebug Jul 29, 2026
c16f971
Implement CJK fallback font handling and add mutex locking in AFontMa…
luadebug Jul 29, 2026
8a7c36d
Refactor glyph rendering to improve fallback font handling and ensure…
luadebug Jul 29, 2026
12e7161
Refactor AFontManager to improve CJK fallback font loading and introd…
luadebug Jul 29, 2026
ccbb2bc
Refactor AFontManager to rename ensureFallbackFace to ensureFallbackF…
luadebug Jul 29, 2026
ceb2152
Refactor renderGlyph to improve glyph data handling and ensure metric…
luadebug Jul 30, 2026
efe76b6
Refactor AFontManager and AFont to improve CJK fallback font handling…
luadebug Jul 30, 2026
fc37584
Refactor AFontManager and AFont to enhance CJK fallback font handling…
luadebug Jul 30, 2026
b965192
Refactor AFontManager and AFont to improve CJK fallback font handling…
luadebug Jul 30, 2026
8eeac0e
Refactor AFont to improve glyph rendering and character advance calcu…
luadebug Jul 30, 2026
fa0fdf1
Refactor AFontManager and AFont to enhance CJK fallback font handling…
luadebug Jul 30, 2026
4031be4
Refactor AFontManager to streamline CJK fallback font handling by rem…
luadebug Jul 30, 2026
71b0f80
Refactor AFont and AFontManager to optimize glyph rendering and fallb…
luadebug Jul 30, 2026
fefa1b6
Enhance glyph rendering logic in AFont and SoftwareRenderer to improv…
luadebug Jul 30, 2026
8e31503
Refactor glyph advance calculations in AFont, OpenGLRenderer, and Sof…
luadebug Jul 30, 2026
62ead79
Refactor getKerning method in AFont to include size parameter and upd…
luadebug Jul 30, 2026
7bb9941
Refactor font loading and rendering in AFont, FreeType, AFontManager,…
luadebug Jul 30, 2026
e1f850c
Implement fallback font loading mechanism in AFontManager for improve…
luadebug Jul 30, 2026
f124351
Enhance thread safety in AFont and AFontManager by refining mutex usa…
luadebug Jul 30, 2026
94e1ec9
Refactor font rendering and fallback face handling for improved threa…
luadebug Jul 30, 2026
eca2f01
Refactor getKerning method in AFont to use char32_t for improved Unic…
luadebug Jul 31, 2026
189331c
Enhance thread safety in AFont, AFontManager, and FreeType by refinin…
luadebug Jul 31, 2026
147ca16
Improve glyph rendering by preventing caching of failed glyphs and ad…
luadebug Jul 31, 2026
5e8022c
Enhance font rendering by improving glyph caching and fallback face h…
luadebug Jul 31, 2026
84e7ac8
Enhance glyph rendering and fallback face handling by introducing pro…
luadebug Jul 31, 2026
a21d0bd
Enhance glyph handling and fallback discovery by improving exception …
luadebug Jul 31, 2026
d97d522
Enhance glyph caching and rendering by improving lock management in A…
luadebug Jul 31, 2026
85be0bf
Disable assembly tests for Google Benchmark during import in CMake co…
luadebug Jul 31, 2026
ffb99fc
Improve fallback discovery error handling and memory allocation check…
luadebug Jul 31, 2026
71f2caf
Enhance fallback generation handling in AFontManager and AFont, ensur…
luadebug Aug 1, 2026
b93f1b0
Enhance glyph rendering efficiency by avoiding unnecessary pixel size…
luadebug Aug 1, 2026
07c81f6
Refactor font handling and fallback discovery
luadebug Aug 1, 2026
4398d41
Refactor AFont character handling to return immutable snapshots, enha…
luadebug Aug 1, 2026
41b80f1
Enhance AFontManager thread safety by ensuring fallback thread joins …
luadebug Aug 1, 2026
82e337e
Enhance font handling and rendering efficiency by preventing drawable…
luadebug Aug 2, 2026
d8b6452
Refactor AFont character retrieval to enhance thread safety and effic…
luadebug Aug 2, 2026
5b118af
Enhance font rendering by improving advance calculations and ensuring…
luadebug Aug 2, 2026
937f2d9
Enhance AFontManager thread safety by introducing mFallbackShuttingDo…
luadebug Aug 2, 2026
6b605bd
Refactor text rendering in OpenGLRenderer to ensure pixel positions a…
luadebug Aug 2, 2026
7bdd064
Enhance font rendering and management by allowing selection of bitmap…
luadebug Aug 2, 2026
4430ecb
Restrict bitmap strike selection to exact matches to prevent UI layou…
luadebug Aug 3, 2026
566b946
Refactor string rendering in OpenGLRenderer and SoftwareRenderer to u…
luadebug Aug 3, 2026
444ca87
Add CoreText and CoreFoundation libraries for iOS platform support in…
luadebug Aug 3, 2026
d6129bf
Enhance iOS font management by adding CoreGraphics and improving fall…
luadebug Aug 3, 2026
6193693
Refactor FallbackCandidate structure to include faceIndex initializat…
luadebug Aug 3, 2026
6d8327d
Enhance fallback face handling by adding condition variable for notif…
luadebug Aug 3, 2026
b7c606c
Refactor CMakeLists.txt to link libraries without file extensions. Up…
luadebug Aug 3, 2026
4db4c85
Enhance font rendering by improving kerning error handling, optimizin…
luadebug Aug 3, 2026
6fabcba
Enhance font width trimming by incorporating kerning adjustments and …
luadebug Aug 3, 2026
22303db
Refactor font width calculation to remove unnecessary space check in …
luadebug Aug 3, 2026
0d99207
Introduced ACFPtr for automatic memory management of CoreFoundation o…
luadebug Aug 26, 2026
1d1bc15
fixup
luadebug Aug 26, 2026
b8f0195
Update font path retrieval to improve pattern substitution and match …
luadebug Aug 26, 2026
65811ee
Remove unnecessary CoreGraphics include and update CMakeLists to link…
luadebug Aug 26, 2026
7f94402
Refactor font path retrieval to utilize FcFontSet for improved memory…
luadebug Aug 26, 2026
77ea00f
Merge branch 'aui-framework:master' into cjk
luadebug Aug 26, 2026
a191b45
trigger CI
luadebug Aug 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
206 changes: 201 additions & 5 deletions aui.views/src/AUI/Font/AFont.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/*
* AUI Framework - Declarative UI toolkit for modern C++20
* Copyright (C) 2020-2025 Alex2772 and Contributors
*
Expand All @@ -14,11 +14,18 @@
#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) {
Expand All @@ -43,6 +50,12 @@ AFont::AFont(AFontManager* fm, const AUrl& url) :
}
}

AFont::~AFont() {
if (mFace) {
FT_Done_Face(mFace);
}
}

AString AFont::getFontFamilyName() const {
FT_SfntName name;
FT_Get_Sfnt_Name(mFace, 0, &name);
Expand All @@ -57,6 +70,171 @@ 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() {
Comment thread
Alex2772 marked this conversation as resolved.
Outdated
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;
Expand All @@ -69,7 +247,22 @@ AFont::Character AFont::renderGlyph(const FontEntry& fs, AChar glyph) {
int size = fs.first.size;
FontRendering fr = fs.first.fr;

FT_Set_Pixel_Sizes(mFace, 0, size);
// 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;

Expand All @@ -78,11 +271,14 @@ AFont::Character AFont::renderGlyph(const FontEntry& fs, AChar glyph) {
if (fr == FontRendering::NEAREST)
flags |= FT_LOAD_TARGET_MONO;

FT_Error e = FT_Load_Char(mFace, glyph.codepoint(), flags);
FT_Error e = FT_Load_Char(face, glyph.codepoint(), flags);
if (e) {
throw std::runtime_error(("Cannot load char: error code" + AString::number(e)).toStdString());
// 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 = mFace->glyph;
FT_GlyphSlot g = face->glyph;
if (g->bitmap.width && g->bitmap.rows) {
const float div = 1.f / 64.f;
int width = g->bitmap.width;
Expand Down
12 changes: 11 additions & 1 deletion aui.views/src/AUI/Font/AFont.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/*
* AUI Framework - Declarative UI toolkit for modern C++20
* Copyright (C) 2020-2025 Alex2772 and Contributors
*
Expand Down Expand Up @@ -115,12 +115,20 @@ class API_AUI_VIEWS AFont {
AByteBuffer mFontDataBuffer;
FT_FaceRec_* mFace;


AMap<FontKey, FontData> mCharData;

FontData& getFontEntry(unsigned size, FontRendering fr) {
return mCharData[FontKey{size, fr}];
}

/**
* @brief Checks if the primary font contains the given glyph.
* @return true if the glyph is available in the font.
*/
bool hasGlyph(char32_t codepoint) const;


Character renderGlyph(const FontEntry& fs, AChar glyph);

public:
Expand All @@ -134,6 +142,8 @@ class API_AUI_VIEWS AFont {

glm::vec2 getKerning(wchar_t left, wchar_t right);

~AFont();

AFont(const AFont&) = delete;

Character& getCharacter(const FontEntry& charset, AChar glyph);
Expand Down
8 changes: 5 additions & 3 deletions aui.views/src/AUI/Platform/AFontManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@
#include "AUI/Platform/APlatform.h"
#include "AUI/Font/FreeType.h"



AFontManager::~AFontManager() = default;
AFontManager::~AFontManager() {
if (mFallbackFace) {
FT_Done_Face(mFallbackFace);
}
}


_<AFont> AFontManager::loadFont(const AUrl& url) {
Expand Down
13 changes: 13 additions & 0 deletions aui.views/src/AUI/Platform/AFontManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,19 @@ class API_AUI_VIEWS AFontManager {
_<AFontFamily> mDefaultFamily;
_<AFont> mDefaultFont;

AByteBuffer mFallbackFontDataBuffer;
FT_FaceRec_* mFallbackFace = nullptr;
AString mFallbackFontPath;
bool mFallbackAttempted = false;

void ensureFallbackFace();

public:
FT_FaceRec_* getFallbackFace() {
if (!mFallbackAttempted) ensureFallbackFace();
return mFallbackFace;
}

AString getPathToFont(const AString& family);

_<AFont> loadFont(const AUrl& url);
Expand Down
9 changes: 9 additions & 0 deletions examples/ui/views/src/ExampleWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,15 @@ ExampleWindow::ExampleWindow() : AWindow("Examples", 800_dp, 700_dp) {
AScrollArea::Builder().withContents(std::conditional_t<
aui::platform::current::is_mobile(), Vertical, Horizontal> {
Vertical {
// CJK font test
GroupBox {
Label { "CJK Font Test" },
Vertical {
Label { "Chinese: 中文测试" },
Label { "Japanese: 日本語テスト" },
Label { "Korean: 한국어 테스트" },
} AUI_OVERRIDE_STYLE { LayoutSpacing { 4_dp } },
},
// buttons
GroupBox {
Label { "Buttons" },
Expand Down
Loading