Skip to content

Commit 0d36ebc

Browse files
committed
feat: Migrate UI font system to SDF rendering
- Replace bitmap Font with SDFFont using FreeType's native SDF rasterizer - Add dynamic glyph loading with LRU cache for CJK character support - Update all widgets to use unified SDFFont API - Reduce theme font sizes (normal: 16→14, small: 14→12) - Fix text vertical alignment in drawTextInBounds using ascent/descent - Add text truncation with ellipsis for AssetGridItem - Add freetype as git submodule
1 parent 8fe5ec8 commit 0d36ebc

25 files changed

Lines changed: 1142 additions & 755 deletions

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,6 @@
77
[submodule "third_party/quickjs"]
88
path = third_party/quickjs
99
url = https://github.com/bellard/quickjs.git
10+
[submodule "third_party/freetype"]
11+
path = third_party/freetype
12+
url = https://github.com/freetype/freetype.git

CMakeLists.txt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ set(ESENGINE_SOURCES
4040
# UI System
4141
src/esengine/ui/UIContext.cpp
4242
src/esengine/ui/rendering/UIBatchRenderer.cpp
43-
src/esengine/ui/font/Font.cpp
43+
src/esengine/ui/font/SDFFont.cpp
4444
src/esengine/ui/layout/StackLayout.cpp
4545
src/esengine/ui/layout/WrapLayout.cpp
4646
src/esengine/ui/widgets/Widget.cpp
@@ -110,7 +110,7 @@ set(ESENGINE_HEADERS
110110
src/esengine/ui/layout/Layout.hpp
111111
src/esengine/ui/layout/StackLayout.hpp
112112
src/esengine/ui/rendering/UIBatchRenderer.hpp
113-
src/esengine/ui/font/Font.hpp
113+
src/esengine/ui/font/SDFFont.hpp
114114
src/esengine/ui/widgets/Widget.hpp
115115
src/esengine/ui/widgets/Panel.hpp
116116
src/esengine/ui/widgets/Label.hpp
@@ -252,12 +252,13 @@ if(ES_BUILD_WEB OR ES_BUILD_WXGAME)
252252
target_compile_definitions(esengine PUBLIC ES_PLATFORM_WXGAME)
253253
endif()
254254
else()
255-
# Native platform - link GLFW, GLAD, OpenGL, and QuickJS
255+
# Native platform - link GLFW, GLAD, OpenGL, QuickJS, and FreeType
256256
find_package(OpenGL REQUIRED)
257257
target_compile_definitions(esengine PUBLIC ES_PLATFORM_NATIVE)
258-
target_link_libraries(esengine PUBLIC glfw glad OpenGL::GL quickjs)
258+
target_link_libraries(esengine PUBLIC glfw glad OpenGL::GL quickjs freetype)
259259
target_include_directories(esengine PUBLIC
260260
${CMAKE_SOURCE_DIR}/third_party/glad/include
261+
${CMAKE_SOURCE_DIR}/third_party/freetype/include
261262
)
262263
endif()
263264

assets/.esengine/assets.db

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
{
2+
"version": "1.0",
3+
"assets": [
4+
{
5+
"guid": "cf70754c-26ba-346d-c01d-ff9fc29a6e9e",
6+
"path": "assets\\.esengine",
7+
"name": ".esengine",
8+
"type": 1,
9+
"fileSize": 0,
10+
"lastModified": 1769495856,
11+
"isDirectory": true
12+
},
13+
{
14+
"guid": "86ed9e0d-442c-a047-9b51-09011c881685",
15+
"path": "assets\\audio",
16+
"name": "audio",
17+
"type": 1,
18+
"fileSize": 0,
19+
"lastModified": 1769488063,
20+
"isDirectory": true
21+
},
22+
{
23+
"guid": "08964537-6809-2c47-8997-7bbf1c75029f",
24+
"path": "assets\\scripts",
25+
"name": "scripts",
26+
"type": 1,
27+
"fileSize": 0,
28+
"lastModified": 1769488063,
29+
"isDirectory": true
30+
},
31+
{
32+
"guid": "9db35f84-665c-e687-86b1-cc78a3934216",
33+
"path": "assets\\test.txt",
34+
"name": "test.txt",
35+
"type": 0,
36+
"fileSize": 5,
37+
"lastModified": 1769488063,
38+
"isDirectory": false
39+
},
40+
{
41+
"guid": "44aa95ad-d460-37e0-041f-c6a4449f8b17",
42+
"path": "assets\\textures\\test.png",
43+
"name": "test.png",
44+
"type": 2,
45+
"fileSize": 6,
46+
"lastModified": 1769488062,
47+
"isDirectory": false
48+
},
49+
{
50+
"guid": "a638dc81-b60d-567f-6820-13f4e9998407",
51+
"path": "assets\\.esengine\\assets.db",
52+
"name": "assets.db",
53+
"type": 0,
54+
"fileSize": 1220,
55+
"lastModified": 1769495857,
56+
"isDirectory": false
57+
},
58+
{
59+
"guid": "e0220f45-6261-175f-883b-ba80783788f2",
60+
"path": "assets\\textures",
61+
"name": "textures",
62+
"type": 1,
63+
"fileSize": 0,
64+
"lastModified": 1769488063,
65+
"isDirectory": true
66+
}
67+
]
68+
}

assets/test.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
test

assets/textures/test.png

Lines changed: 1 addition & 0 deletions
Loading

examples/hello_triangle/main.cpp

Lines changed: 118 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,131 +1,165 @@
11
#include <esengine/ESEngine.hpp>
2+
#include <esengine/ui/UIContext.hpp>
3+
#include <esengine/ui/rendering/UIBatchRenderer.hpp>
4+
#include <esengine/ui/font/SDFFont.hpp>
5+
#include <esengine/events/Dispatcher.hpp>
26

37
using namespace esengine;
48
using namespace esengine::ecs;
59

6-
// Simple movement system
7-
class MovementSystem : public System {
8-
public:
9-
void update(Registry& registry, f32 deltaTime) override {
10-
registry.each<LocalTransform, Velocity>([deltaTime](Entity entity, LocalTransform& transform, Velocity& velocity) {
11-
(void)entity;
12-
transform.position += velocity.linear * deltaTime;
13-
});
14-
}
15-
};
16-
17-
// Demo application
10+
// Demo application with SDF font testing
1811
class HelloTriangleApp : public Application {
1912
public:
2013
HelloTriangleApp() : Application(createConfig()) {}
2114

2215
private:
2316
static ApplicationConfig createConfig() {
2417
ApplicationConfig config;
25-
config.title = "Hello Triangle";
18+
config.title = "SDF Font Test - 中英文测试";
2619
config.width = 800;
2720
config.height = 600;
2821
return config;
2922
}
3023

3124
protected:
3225
void onInit() override {
33-
ES_LOG_INFO("Hello Triangle initialized!");
26+
ES_LOG_INFO("SDF Font Test initialized!");
27+
28+
// Create UI context for font rendering
29+
uiContext_ = makeUnique<ui::UIContext>(getRenderContext(), dispatcher_);
30+
uiContext_->init();
31+
uiContext_->setViewport(getWidth(), getHeight());
32+
33+
// Load SDF font with CJK support
34+
const char* fontPaths[] = {
35+
#ifdef _WIN32
36+
"C:/Windows/Fonts/msyh.ttc", // Microsoft YaHei (中文)
37+
"C:/Windows/Fonts/simhei.ttf", // SimHei
38+
"C:/Windows/Fonts/simsun.ttc", // SimSun
39+
"C:/Windows/Fonts/arial.ttf", // Arial (English fallback)
40+
#else
41+
"/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc",
42+
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
43+
#endif
44+
nullptr
45+
};
46+
47+
for (const char** path = fontPaths; *path != nullptr; ++path) {
48+
sdfFont_ = ui::SDFFont::create(*path, 48.0f, 8.0f);
49+
if (sdfFont_) {
50+
ES_LOG_INFO("Loaded SDF font: {}", *path);
51+
break;
52+
}
53+
}
3454

35-
// Create some entities with the ECS
36-
auto& registry = getRegistry();
55+
if (!sdfFont_) {
56+
ES_LOG_ERROR("Failed to load any SDF font!");
57+
}
3758

38-
// Create a triangle entity
59+
// Create some entities
60+
auto& registry = getRegistry();
3961
Entity triangle = registry.create();
40-
registry.emplace<LocalTransform>(triangle, glm::vec3(400.0f, 300.0f, 0.0f));
62+
registry.emplace<LocalTransform>(triangle, glm::vec3(400.0f, 450.0f, 0.0f));
4163
registry.emplace<Name>(triangle, "Triangle");
4264

43-
// Create moving entities
44-
for (int i = 0; i < 5; ++i) {
45-
Entity entity = registry.create();
46-
registry.emplace<LocalTransform>(entity, glm::vec3(100.0f + i * 120.0f, 100.0f, 0.0f));
47-
registry.emplace<Velocity>(entity, glm::vec3(50.0f * (i % 2 == 0 ? 1.0f : -1.0f), 0.0f, 0.0f));
48-
registry.emplace<Sprite>(entity);
49-
}
50-
51-
ES_LOG_INFO("Created {} entities", registry.entityCount());
65+
ES_LOG_INFO("Press ESC to exit");
5266
}
5367

5468
void onUpdate(f32 deltaTime) override {
55-
// Update entities manually (or use SystemGroup)
56-
auto& registry = getRegistry();
57-
registry.each<LocalTransform, Velocity>([deltaTime, this](LocalTransform& transform, Velocity& velocity) {
58-
transform.position += velocity.linear * deltaTime;
59-
60-
// Bounce off edges
61-
if (transform.position.x < 0 || transform.position.x > getWidth()) {
62-
velocity.linear.x *= -1.0f;
63-
}
64-
});
65-
66-
// Check for touch input
67-
if (getInput().isTouchPressed()) {
68-
auto pos = getInput().getTouchPosition();
69-
ES_LOG_DEBUG("Touch at ({}, {})", pos.x, pos.y);
70-
}
71-
72-
// Check for key input
69+
(void)deltaTime;
7370
if (getInput().isKeyPressed(KeyCode::Escape)) {
7471
quit();
7572
}
7673
}
7774

7875
void onRender() override {
79-
auto& registry = getRegistry();
8076
auto& renderer = getRenderer();
8177

82-
// Draw colored quads for entities with LocalTransform and Sprite
83-
auto view = registry.view<LocalTransform, Sprite>();
84-
for (auto entity : view) {
85-
auto& transform = view.get<LocalTransform>(entity);
86-
auto& sprite = view.get<Sprite>(entity);
78+
// Draw a simple quad
79+
renderer.drawQuad(
80+
glm::vec2(400.0f, 450.0f),
81+
glm::vec2(100.0f, 100.0f),
82+
glm::vec4(1.0f, 0.5f, 0.2f, 1.0f)
83+
);
84+
85+
// Draw SDF text
86+
if (sdfFont_ && uiContext_) {
87+
auto& uiRenderer = uiContext_->getRenderer();
88+
89+
glm::mat4 projection = glm::ortho(
90+
0.0f, static_cast<f32>(getWidth()),
91+
static_cast<f32>(getHeight()), 0.0f,
92+
-1.0f, 1.0f
93+
);
94+
95+
uiRenderer.begin(projection);
8796

88-
renderer.drawQuad(
89-
glm::vec2(transform.position.x, transform.position.y),
90-
sprite.size * 50.0f,
91-
sprite.color
97+
// English text
98+
uiRenderer.drawText(
99+
"Hello SDF Font!",
100+
{50.0f, 50.0f},
101+
*sdfFont_,
102+
32.0f,
103+
{1.0f, 1.0f, 1.0f, 1.0f}
92104
);
93-
}
94105

95-
// Draw the main triangle
96-
auto triangleView = registry.view<LocalTransform, Name>();
97-
for (auto entity : triangleView) {
98-
auto& transform = triangleView.get<LocalTransform>(entity);
99-
auto& name = triangleView.get<Name>(entity);
100-
101-
if (name.value == "Triangle") {
102-
renderer.drawQuad(
103-
glm::vec2(transform.position.x, transform.position.y),
104-
glm::vec2(100.0f, 100.0f),
105-
glm::vec4(1.0f, 0.5f, 0.2f, 1.0f)
106-
);
107-
}
108-
}
109-
}
106+
// Chinese text
107+
uiRenderer.drawText(
108+
"你好,世界!中文测试",
109+
{50.0f, 100.0f},
110+
*sdfFont_,
111+
32.0f,
112+
{1.0f, 1.0f, 0.0f, 1.0f}
113+
);
114+
115+
// Mixed text
116+
uiRenderer.drawText(
117+
"ESEngine 引擎 - SDF字体渲染",
118+
{50.0f, 150.0f},
119+
*sdfFont_,
120+
28.0f,
121+
{0.5f, 1.0f, 0.5f, 1.0f}
122+
);
123+
124+
// Different sizes to show SDF scaling
125+
uiRenderer.drawText(
126+
"Small 小字 16px",
127+
{50.0f, 200.0f},
128+
*sdfFont_,
129+
16.0f,
130+
{0.8f, 0.8f, 1.0f, 1.0f}
131+
);
132+
133+
uiRenderer.drawText(
134+
"Large 大字 48px",
135+
{50.0f, 240.0f},
136+
*sdfFont_,
137+
48.0f,
138+
{1.0f, 0.6f, 0.6f, 1.0f}
139+
);
110140

111-
void onTouch(TouchType type, const TouchPoint& point) override {
112-
if (type == TouchType::Begin) {
113-
// Create new entity at touch position
114-
auto& registry = getRegistry();
115-
Entity entity = registry.create();
116-
registry.emplace<Transform>(entity, glm::vec3(point.x, point.y, 0.0f));
117-
registry.emplace<Sprite>(entity);
118-
registry.emplace<Velocity>(entity, glm::vec3(
119-
(rand() % 200 - 100.0f),
120-
(rand() % 200 - 100.0f),
121-
0.0f
122-
));
141+
uiRenderer.drawText(
142+
"动态加载 Dynamic Loading",
143+
{50.0f, 320.0f},
144+
*sdfFont_,
145+
24.0f,
146+
{0.6f, 0.9f, 1.0f, 1.0f}
147+
);
148+
149+
uiRenderer.end();
123150
}
124151
}
125152

126153
void onShutdown() override {
127-
ES_LOG_INFO("Hello Triangle shutdown");
154+
sdfFont_.reset();
155+
uiContext_.reset();
156+
ES_LOG_INFO("SDF Font Test shutdown");
128157
}
158+
159+
private:
160+
Dispatcher dispatcher_;
161+
Unique<ui::UIContext> uiContext_;
162+
Unique<ui::SDFFont> sdfFont_;
129163
};
130164

131165
// Entry point
@@ -136,6 +170,5 @@ int main() {
136170
return 0;
137171
}
138172
#else
139-
// For web, entry is through the C API
140173
ES_MAIN(HelloTriangleApp)
141174
#endif

0 commit comments

Comments
 (0)