Repository navigation
Expand file tree
/
Copy pathtruetype.cpp
More file actions
310 lines (241 loc) · 10.1 KB
/
Copy pathtruetype.cpp
File metadata and controls
310 lines (241 loc) · 10.1 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
#include "test.h"
// Note: fl/stl/cstdio.h intentionally NOT included — workaround for
// zackees/zccache#619 (Windows PCH path-spelling drift defeats #pragma
// once across the PCH boundary). The PCH provides cstdio.h transitively;
// this TU doesn't use any cstdio.h symbol directly.
#include "fl/stl/vector.h"
#include "fl/stl/memory.h"
// Include the stb_truetype header (declarations only)
// Implementation is in libfastled.a via third_party/_build.cpp
#include "third_party/stb/truetype/stb_truetype.h"
#include "fl/fs/fs.h"
FL_TEST_FILE(FL_FILEPATH) {
using namespace fl::third_party::truetype;
namespace {
// Helper function to load a font file
fl::vector<unsigned char> loadFontFile(const char* filename) {
fl::setTestFileSystemRoot("tests/fl/font/data");
fl::FileSystem fs;
// Initialize the filesystem (required for stub platform)
if (!fs.beginSd(0)) {
return fl::vector<unsigned char>();
}
auto file_handle = fs.openRead(filename);
if (!file_handle.is_open()) {
return fl::vector<unsigned char>();
}
// Get file size
fl::size size = file_handle.size();
// Read file into buffer
fl::vector<unsigned char> buffer(size);
fl::size bytes_read = file_handle.read(buffer.data(), size);
if (bytes_read != size) {
return fl::vector<unsigned char>();
}
return buffer;
}
} // anonymous namespace
FL_TEST_CASE("stbtt_truetype - Font loading") {
FL_SUBCASE("Load Covenant5x5.ttf (default embedded font)") {
auto font_data = loadFontFile("Covenant5x5.ttf");
FL_REQUIRE(!font_data.empty());
// Verify it's a valid font file
int32_t num_fonts = stbtt_GetNumberOfFonts(font_data.data());
FL_CHECK_GT(num_fonts, 0);
FL_CHECK_LE(num_fonts, 10); // Reasonable upper bound
}
FL_SUBCASE("Get font offset") {
auto font_data = loadFontFile("Covenant5x5.ttf");
FL_REQUIRE(!font_data.empty());
int32_t offset = stbtt_GetFontOffsetForIndex(font_data.data(), 0);
FL_CHECK_GE(offset, 0);
}
FL_SUBCASE("Initialize font info") {
auto font_data = loadFontFile("Covenant5x5.ttf");
FL_REQUIRE(!font_data.empty());
stbtt_fontinfo font;
int32_t result = stbtt_InitFont(&font, font_data.data(), 0);
FL_CHECK_NE(result, 0); // Non-zero means success
}
}
FL_TEST_CASE("stbtt_truetype - Font metrics") {
auto font_data = loadFontFile("Covenant5x5.ttf");
FL_REQUIRE(!font_data.empty());
stbtt_fontinfo font;
FL_REQUIRE(stbtt_InitFont(&font, font_data.data(), 0) != 0);
FL_SUBCASE("Get vertical metrics") {
int32_t ascent, descent, line_gap;
stbtt_GetFontVMetrics(&font, &ascent, &descent, &line_gap);
// Ascent should be positive, descent negative
FL_CHECK_GT(ascent, 0);
FL_CHECK_LT(descent, 0);
FL_CHECK_GE(line_gap, 0);
}
FL_SUBCASE("Get bounding box") {
int32_t x0, y0, x1, y1;
stbtt_GetFontBoundingBox(&font, &x0, &y0, &x1, &y1);
// Bounding box should be valid
FL_CHECK_LT(x0, x1);
FL_CHECK_LT(y0, y1);
}
FL_SUBCASE("Scale for pixel height") {
float scale = stbtt_ScaleForPixelHeight(&font, 32.0f);
FL_CHECK_GT(scale, 0.0f);
FL_CHECK_LT(scale, 1.0f); // Scale should be less than 1 for typical fonts
}
}
FL_TEST_CASE("stbtt_truetype - Glyph queries") {
auto font_data = loadFontFile("Covenant5x5.ttf");
FL_REQUIRE(!font_data.empty());
stbtt_fontinfo font;
FL_REQUIRE(stbtt_InitFont(&font, font_data.data(), 0) != 0);
FL_SUBCASE("Find glyph index for period") {
int32_t glyph_index = stbtt_FindGlyphIndex(&font, '.');
FL_CHECK_GT(glyph_index, 0); // Should find the period glyph
}
FL_SUBCASE("Find glyph index for '8'") {
int32_t glyph_index = stbtt_FindGlyphIndex(&font, '8');
FL_CHECK_GT(glyph_index, 0); // Should find the '8' glyph
}
FL_SUBCASE("Get codepoint metrics") {
int32_t advance_width, left_side_bearing;
stbtt_GetCodepointHMetrics(&font, 'A', &advance_width, &left_side_bearing);
FL_CHECK_GT(advance_width, 0);
}
FL_SUBCASE("Check glyph is not empty") {
int32_t glyph_index = stbtt_FindGlyphIndex(&font, '.');
FL_REQUIRE(glyph_index > 0);
int32_t is_empty = stbtt_IsGlyphEmpty(&font, glyph_index);
FL_CHECK_EQ(is_empty, 0); // Period should not be empty
}
}
FL_TEST_CASE("stbtt_truetype - Bitmap rendering - period") {
auto font_data = loadFontFile("Covenant5x5.ttf");
FL_REQUIRE(!font_data.empty());
stbtt_fontinfo font;
FL_REQUIRE(stbtt_InitFont(&font, font_data.data(), 0) != 0);
float scale = stbtt_ScaleForPixelHeight(&font, 32.0f);
FL_SUBCASE("Render period (.) with antialiasing") {
int32_t width, height, xoff, yoff;
unsigned char* bitmap = stbtt_GetCodepointBitmap(&font, scale, scale, '.',
&width, &height, &xoff, &yoff);
FL_REQUIRE(bitmap != nullptr);
FL_CHECK_GT(width, 0);
FL_CHECK_GT(height, 0);
// Verify bitmap contains non-zero pixels (antialiasing)
bool has_nonzero = false;
for (int32_t y = 0; y < height; ++y) {
for (int32_t x = 0; x < width; ++x) {
if (bitmap[y * width + x] > 0) {
has_nonzero = true;
break;
}
}
if (has_nonzero) break;
}
FL_CHECK(has_nonzero);
stbtt_FreeBitmap(bitmap, nullptr);
}
FL_SUBCASE("Get bitmap box for period") {
int32_t ix0, iy0, ix1, iy1;
stbtt_GetCodepointBitmapBox(&font, '.', scale, scale, &ix0, &iy0, &ix1, &iy1);
// Period should have a valid bounding box
FL_CHECK_LT(ix0, ix1);
FL_CHECK_LT(iy0, iy1);
// Period is small, so box should be reasonable
FL_CHECK_LT(ix1 - ix0, 32);
FL_CHECK_LT(iy1 - iy0, 32);
}
}
FL_TEST_CASE("stbtt_truetype - Bitmap rendering - digit 8") {
auto font_data = loadFontFile("Covenant5x5.ttf");
FL_REQUIRE(!font_data.empty());
stbtt_fontinfo font;
FL_REQUIRE(stbtt_InitFont(&font, font_data.data(), 0) != 0);
float scale = stbtt_ScaleForPixelHeight(&font, 32.0f);
FL_SUBCASE("Render '8' with antialiasing") {
int32_t width, height, xoff, yoff;
unsigned char* bitmap = stbtt_GetCodepointBitmap(&font, scale, scale, '8',
&width, &height, &xoff, &yoff);
FL_REQUIRE(bitmap != nullptr);
FL_CHECK_GT(width, 0);
FL_CHECK_GT(height, 0);
// Verify bitmap contains pixels (either fully opaque or antialiased)
// Note: Pixel fonts like Covenant5x5 may only have 0 or 255 values
int32_t opaque_pixels = 0;
int32_t antialiased_pixels = 0;
for (int32_t y = 0; y < height; ++y) {
for (int32_t x = 0; x < width; ++x) {
unsigned char pixel = bitmap[y * width + x];
if (pixel == 255) {
opaque_pixels++;
} else if (pixel > 0) {
antialiased_pixels++;
}
}
}
// '8' should have some visible pixels
FL_CHECK_GT(opaque_pixels + antialiased_pixels, 0);
stbtt_FreeBitmap(bitmap, nullptr);
}
FL_SUBCASE("Verify '8' shape characteristics") {
int32_t width, height, xoff, yoff;
unsigned char* bitmap = stbtt_GetCodepointBitmap(&font, scale, scale, '8',
&width, &height, &xoff, &yoff);
FL_REQUIRE(bitmap != nullptr);
// '8' has two enclosed loops, so there should be white space in the middle
// Check that not all pixels are solid
int32_t solid_pixels = 0;
int32_t total_pixels = width * height;
for (int32_t i = 0; i < total_pixels; ++i) {
if (bitmap[i] == 255) {
solid_pixels++;
}
}
// Not all pixels should be solid (8 has holes)
FL_CHECK_LT(solid_pixels, total_pixels);
// Should have some solid pixels though
FL_CHECK_GT(solid_pixels, 0);
stbtt_FreeBitmap(bitmap, nullptr);
}
}
FL_TEST_CASE("stbtt_truetype - Rendering without antialiasing") {
auto font_data = loadFontFile("Covenant5x5.ttf");
FL_REQUIRE(!font_data.empty());
stbtt_fontinfo font;
FL_REQUIRE(stbtt_InitFont(&font, font_data.data(), 0) != 0);
// Verify we can compute a scale (just test it works)
float scale = stbtt_ScaleForPixelHeight(&font, 32.0f);
(void)scale;
FL_SUBCASE("Bake font bitmap (simplified packing)") {
// Baking creates a bitmap atlas without antialiasing control,
// but we can verify it works
const int32_t bitmap_w = 512;
const int32_t bitmap_h = 512;
fl::vector<unsigned char> bitmap(bitmap_w * bitmap_h, 0);
fl::vector<stbtt_bakedchar> chardata(96); // ASCII printable chars
int32_t result = stbtt_BakeFontBitmap(font_data.data(), 0, 32.0f,
bitmap.data(), bitmap_w, bitmap_h,
32, 96, // Start at space, 96 chars
chardata.data());
// Should successfully bake some characters
FL_CHECK_GT(result, 0);
}
}
FL_TEST_CASE("stbtt_truetype - Kerning") {
auto font_data = loadFontFile("Covenant5x5.ttf");
FL_REQUIRE(!font_data.empty());
stbtt_fontinfo font;
FL_REQUIRE(stbtt_InitFont(&font, font_data.data(), 0) != 0);
FL_SUBCASE("Get kerning advance") {
// Test kerning between common pairs
int32_t kern_AV = stbtt_GetCodepointKernAdvance(&font, 'A', 'V');
int32_t kern_AA = stbtt_GetCodepointKernAdvance(&font, 'A', 'A');
// AV pair typically has negative kerning, AA typically zero
// But font may not have kerning table, so just check it returns
(void)kern_AV;
(void)kern_AA;
FL_CHECK(true); // If we got here, functions work
}
}
} // FL_TEST_FILE