15 const auto SCALE
= 16u;
16 const auto PADDING
= 4u;
17 const auto SCALED_PADDING
= SCALE
* PADDING
;
22 std::vector
<uint8_t> buffer
;
24 Bitmap(unsigned width
, unsigned height
)
27 , buffer(width
* height
)
30 Bitmap(const FT_Bitmap
&bitmap
)
31 : width((unsigned)bitmap
.width
+ 2 * SCALED_PADDING
)
32 , height((unsigned)bitmap
.rows
+ 2 * SCALED_PADDING
)
33 , buffer(width
* height
)
35 for (auto y
= 0u; y
< (unsigned)bitmap
.rows
; ++y
) {
36 for (auto x
= 0u; x
< (unsigned)bitmap
.width
; ++x
) {
37 auto c
= bitmap
.buffer
[y
* (unsigned)bitmap
.pitch
+ x
/ 8];
38 auto set
= !!(c
>> (7 - (x
& 7)));
39 (*this)(x
+ SCALED_PADDING
, y
+ SCALED_PADDING
) = set
;
44 inline uint8_t &operator()(unsigned x
, unsigned y
) {
45 auto idx
= y
* width
+ x
;
49 inline uint8_t operator()(unsigned x
, unsigned y
) const {
50 if (x
>= width
|| y
>= height
)
52 auto idx
= y
* width
+ x
;
56 void blit(const Bitmap
&src
, unsigned x
, unsigned y
) {
57 for (auto dy
= 0u; dy
< src
.height
; ++dy
)
58 for (auto dx
= 0u; dx
< src
.width
; ++dx
)
59 (*this)(x
+ dx
, y
+ dy
) = src(dx
, dy
);
62 uint8_t sdf(unsigned x
, unsigned y
, unsigned max_r
) const {
63 auto dsq
= max_r
* max_r
;
64 auto v
= (*this)(x
, y
);
66 for (unsigned r
= 1, rsq
; r
<= max_r
&& (rsq
= r
* r
) < dsq
; ++r
) {
67 for (unsigned d
= -r
; (int)d
<= (int)r
; ++d
) {
68 if ((*this)(x
+ d
, y
- r
) != v
)
69 dsq
= std::min(d
* d
+ rsq
, dsq
);
70 else if ((*this)(x
+ d
, y
+ r
) != v
)
71 dsq
= std::min(d
* d
+ rsq
, dsq
);
72 else if ((*this)(x
- r
, y
+ d
) != v
)
73 dsq
= std::min(rsq
+ d
* d
, dsq
);
74 else if ((*this)(x
+ r
, y
+ d
) != v
)
75 dsq
= std::min(rsq
+ d
* d
, dsq
);
79 // Map distance onto to [0, 255].
80 auto d
= std::sqrt((float)dsq
);
83 d
*= (255.f
/ 2.f
) / max_r
;
85 d
= std::min(255.f
, std::max(0.f
, d
));
86 return (uint8_t)(d
+ 0.5);
89 void save(const std::string
&filename
) const {
90 auto f
= fopen(filename
.c_str(), "wb");
92 err(1, "unable to write image: %s", filename
.c_str());
93 const uint8_t header
[] = {
96 3, // image type = greyscale
97 0, 0, 0, 0, 0, // color map specification
100 (uint8_t)(width
& 0xFF),
101 (uint8_t)((width
>> 8) & 0xFF),
102 (uint8_t)(height
& 0xFF),
103 (uint8_t)((height
>> 8) & 0xFF),
105 1 << 5, // scanlines are from top-left
107 fwrite(header
, sizeof(header
[0]), sizeof(header
), f
);
108 fwrite(&buffer
[0], sizeof(buffer
[0]), buffer
.size(), f
);
129 Glyph(unsigned codepoint
, const FT_BitmapGlyph
&glyph
)
130 : pixels(glyph
->bitmap
.width
> 0
131 ? ((unsigned)glyph
->bitmap
.width
/ SCALE
+ 2 * PADDING
)
133 glyph
->bitmap
.rows
> 0
134 ? ((unsigned)glyph
->bitmap
.rows
/ SCALE
+ 2 * PADDING
)
136 , codepoint(codepoint
)
137 , width(pixels
.width
)
138 , height(pixels
.height
)
140 , u0(0), v0(0), u1(0), v1(0)
141 , advance_x((glyph
->root
.advance
.x
>> 16) / (float)SCALE
)
142 , advance_y((glyph
->root
.advance
.y
>> 16) / (float)SCALE
)
143 , left(glyph
->left
/ (float)SCALE
)
144 , top(glyph
->top
/ (float)SCALE
)
146 const Bitmap
bmap(glyph
->bitmap
);
148 for (auto y
= 0u; y
< height
; y
++) {
149 for (auto x
= 0u; x
< width
; x
++) {
150 // Sample from center of high-res area.
151 auto xs
= x
* SCALE
+ SCALE
/ 2;
152 auto ys
= y
* SCALE
+ SCALE
/ 2;
153 pixels(x
, y
) = bmap
.sdf(xs
, ys
, 2 * SCALE
);
159 static bool glyph_codepoint_sort(const Glyph
&a
, const Glyph
&b
) {
160 return a
.codepoint
< b
.codepoint
;
163 static bool glyph_height_sort(const Glyph
&a
, const Glyph
&b
) {
164 // sort high to low, break ties by codepoint for stable output.
165 return a
.height
> b
.height
166 || (a
.height
== b
.height
&& glyph_codepoint_sort(a
, b
));
169 static std::vector
<Glyph
> get_glyphs(
170 FT_Face face
, const std::vector
<unsigned> &codepoints
)
172 std::vector
<Glyph
> glyphs
;
173 // A number of fonts provide two different glyph indices for
174 // "invalid" vs. "undisplayable" characters. Invalid characters
175 // should be ignored. Undisplayable characters should be ignored
176 // unless the character requested is the special undisplayable
178 const unsigned REPLACEMENT
= 0xFFFD;
179 auto invalid
= FT_Get_Char_Index(face
, 0);
180 auto missing
= FT_Get_Char_Index(face
, REPLACEMENT
);
181 auto warning
= false;
182 glyphs
.reserve(codepoints
.size());
184 for (auto i
: codepoints
) {
186 if (FT_Get_Char_Index(face
, i
) != invalid
187 && (i
== REPLACEMENT
|| FT_Get_Char_Index(face
, i
) != missing
)
188 && !FT_Load_Char(face
, i
, FT_LOAD_MONOCHROME
)
189 && !FT_Get_Glyph(face
->glyph
, &glyph
)
190 && !FT_Glyph_To_Bitmap(&glyph
, FT_RENDER_MODE_MONO
, 0, 1)
191 && ((FT_BitmapGlyph
)glyph
)->bitmap
.pixel_mode
== FT_PIXEL_MODE_MONO
194 auto bmp_glyph
= (FT_BitmapGlyph
)glyph
;
195 glyphs
.push_back(Glyph(i
, bmp_glyph
));
197 warnx("Resuming generation at codepoint %d.", i
);
199 } else if (!warning
) {
200 warnx("Can't load codepoint %d and onwards.", i
);
208 static unsigned get_texture_size(unsigned &width
, unsigned &height
, std::vector
<Glyph
> &glyphs
)
210 // Sort glyphs by height to minimize vertically wasted space.
211 std::sort(glyphs
.begin(), glyphs
.end(), glyph_height_sort
);
213 // Get an estimated size by assuming all characters are the same
214 // height, and that we can lay them out exactly in a square. This
215 // drastically overestimates the space required unless the
216 // characters are actually all the max height, then it slightly
217 // underestimates it.
218 auto max_height
= glyphs
.front().height
;
219 auto total_width
= 0u;
220 for (auto &glyph
: glyphs
)
221 total_width
+= glyph
.width
;
222 auto side
= ceil(sqrt(total_width
* max_height
));
224 // Round width up to the nearest power of 2.
225 for (width
= 1; width
< side
; width
*= 2);
227 // Now, lay glyphs out horizontally in fixed rows, to figure out
228 // how tall it *really* needs to be. While we're at it, figure out
229 // what the optimum space usage would be.
231 // This is a naive algorithm that leaves "a lot" of space empty,
232 // like 30-50% of the texture. The problem is unless the space is
233 // at least 50%, no amount of smart packing is going to improve it
234 // anyway, and will just make the texture harder to spot-check.
236 auto total_height
= 0u;
237 auto width_accum
= width
+ 1;
240 for (auto &glyph
: glyphs
) {
241 if (width_accum
+ glyph
.width
> width
) {
243 row_start
= total_height
;
244 total_height
+= glyph
.height
;
246 glyph
.x
= width_accum
;
248 width_accum
+= glyph
.width
;
249 real_area
+= glyph
.width
* glyph
.height
;
252 // Now round height up to nearest power of 2.
253 for (height
= 1; height
< total_height
; height
*= 2);
255 // With the height known, texture coordinates can be set.
256 for (auto &glyph
: glyphs
) {
258 glyph
.u0
= (float)(glyph
.x
) / width
;
259 glyph
.v0
= (float)(glyph
.y
) / height
;
260 glyph
.u1
= (float)(glyph
.x
+ glyph
.width
) / width
;
261 glyph
.v1
= (float)(glyph
.y
+ glyph
.height
) / height
;
263 glyph
.u0
= glyph
.v0
= glyph
.u1
= glyph
.v1
= 0.f
;
270 static std::vector
<unsigned> parse_ranges(const char *s
) {
271 std::vector
<unsigned> v
;
272 for (; *s
; s
+= !!*s
) {
273 auto start
= std::strtoul(s
, (char **)&s
, 0);
275 auto end
= std::strtoul(++s
, (char **)&s
, 0);
277 std::swap(start
, end
);
278 for (auto i
= start
; i
<= end
; ++i
)
279 v
.push_back((unsigned)i
);
281 v
.push_back((unsigned)start
);
287 int main(int argc
, char **argv
) {
292 if (argc
< 2 || argc
> 4)
293 errx(1, "Usage: %s font size char-range", argv
[0]);
295 auto size
= argc
> 2 ? (unsigned)atoi(argv
[2]) : 64u;
296 auto codepoints
= parse_ranges(argc
> 3 ? argv
[3] : "0-16777216");
298 if (codepoints
.size() == 0)
299 errx(1, "Invalid codepoint range: %s", argv
[3]);
301 errx(1, "Invalid size %s", argv
[2]);
303 if ((error
= FT_Init_FreeType(&library
)))
304 errx(1, "Can't initialize FreeType: %d", error
);
305 if ((error
= FT_New_Face(library
, argv
[1], 0, &face
)))
306 errx(1, "Can't load %s: %d", argv
[1], error
);
307 if ((error
= FT_Set_Pixel_Sizes(face
, 0, size
* SCALE
)))
308 errx(1, "Unable to set pixel size.");
310 auto glyphs
= get_glyphs(face
, codepoints
);
311 if (glyphs
.size() == 0)
312 errx(1, "No glyphs found.");
314 unsigned width
, height
;
315 auto real_area
= get_texture_size(width
, height
, glyphs
);
316 auto wasted
= 1.f
- float(real_area
) / (width
* height
);
318 "%ux%u texture for %zi glyphs (%u%% wasted).\n", width
, height
,
319 glyphs
.size(), (unsigned)(wasted
* 100));
321 std::string
name(argv
[1]);
323 name
.erase(name
.rfind("."));
324 if ((sep
= name
.rfind("/")) != std::string::npos
)
325 name
.erase(0, sep
+ 1);
326 if ((sep
= name
.rfind("\\")) != std::string::npos
)
327 name
.erase(0, sep
+ 1);
329 Bitmap
total(width
, height
);
330 std::sort(glyphs
.begin(), glyphs
.end(), glyph_codepoint_sort
);
331 auto json_filename
= name
+ ".font.json";
332 auto f
= fopen(json_filename
.c_str(), "w");
334 errx(1, "unable to write JSON data: %s", json_filename
.c_str());
335 fprintf(f
, "{\n \"height\": %u,\n", size
);
336 fprintf(f
, " \"scale\": %u,\n", SCALE
);
337 fprintf(f
, " \"padding\": %u,\n", PADDING
);
338 fprintf(f
, " \"glyphs\": {");
340 for (auto &glyph
: glyphs
) {
344 fprintf(f
, "\n \"%u\": {\n", glyph
.codepoint
);
345 fprintf(f
, " \"size\": [%u, %u],\n",
346 glyph
.width
, glyph
.height
);
347 fprintf(f
, " \"advance\": [%g, %g],\n",
348 glyph
.advance_x
, glyph
.advance_y
);
349 fprintf(f
, " \"offset\": [%g, %g],\n",
350 glyph
.left
, glyph
.top
);
351 fprintf(f
, " \"tex_coords\": [%g, %g, %g, %g]\n",
352 glyph
.u0
, glyph
.v0
, glyph
.u1
, glyph
.v1
);
354 total
.blit(glyph
.pixels
, glyph
.x
, glyph
.y
);
356 fprintf(f
, "\n }\n}\n");
357 total
.save(name
+ ".tga");