1 /** ttf-to-distfield - Render a distance field from a TrueType font
4 This code is released into the public domain via the CC0 Public
22 const auto SCALE
= 16u;
23 const auto PADDING
= 4u;
24 const auto SCALED_PADDING
= SCALE
* PADDING
;
29 std::vector
<uint8_t> buffer
;
31 Bitmap(unsigned width
, unsigned height
)
34 , buffer(width
* height
)
37 Bitmap(const FT_Bitmap
&bitmap
)
38 : width((unsigned)bitmap
.width
+ 2 * SCALED_PADDING
)
39 , height((unsigned)bitmap
.rows
+ 2 * SCALED_PADDING
)
40 , buffer(width
* height
)
42 for (auto y
= 0u; y
< (unsigned)bitmap
.rows
; ++y
) {
43 for (auto x
= 0u; x
< (unsigned)bitmap
.width
; ++x
) {
44 auto c
= bitmap
.buffer
[y
* (unsigned)bitmap
.pitch
+ x
/ 8];
45 auto set
= !!(c
>> (7 - (x
& 7)));
46 (*this)(x
+ SCALED_PADDING
, y
+ SCALED_PADDING
) = set
;
51 inline uint8_t &operator()(unsigned x
, unsigned y
) {
52 auto idx
= y
* width
+ x
;
56 inline uint8_t operator()(unsigned x
, unsigned y
) const {
57 if (x
>= width
|| y
>= height
)
59 auto idx
= y
* width
+ x
;
63 void blit(const Bitmap
&src
, unsigned x
, unsigned y
) {
64 for (auto dy
= 0u; dy
< src
.height
; ++dy
)
65 for (auto dx
= 0u; dx
< src
.width
; ++dx
)
66 (*this)(x
+ dx
, y
+ dy
) = src(dx
, dy
);
69 uint8_t sdf(unsigned x
, unsigned y
, unsigned max_r
) const {
70 auto dsq
= max_r
* max_r
;
71 auto v
= (*this)(x
, y
);
73 for (unsigned r
= 1, rsq
; r
<= max_r
&& (rsq
= r
* r
) < dsq
; ++r
) {
74 for (unsigned d
= -r
; (int)d
<= (int)r
; ++d
) {
75 if ((*this)(x
+ d
, y
- r
) != v
)
76 dsq
= std::min(d
* d
+ rsq
, dsq
);
77 else if ((*this)(x
+ d
, y
+ r
) != v
)
78 dsq
= std::min(d
* d
+ rsq
, dsq
);
79 else if ((*this)(x
- r
, y
+ d
) != v
)
80 dsq
= std::min(rsq
+ d
* d
, dsq
);
81 else if ((*this)(x
+ r
, y
+ d
) != v
)
82 dsq
= std::min(rsq
+ d
* d
, dsq
);
86 // Map distance onto to [0, 255].
87 auto d
= std::sqrt((float)dsq
);
90 d
*= (255.f
/ 2.f
) / max_r
;
92 d
= std::min(255.f
, std::max(0.f
, d
));
93 return (uint8_t)(d
+ 0.5);
96 void save(const std::string
&filename
) const {
97 auto f
= fopen(filename
.c_str(), "wb");
99 err(1, "unable to write image: %s", filename
.c_str());
100 const uint8_t header
[] = {
103 3, // image type = greyscale
104 0, 0, 0, 0, 0, // color map specification
107 (uint8_t)(width
& 0xFF),
108 (uint8_t)((width
>> 8) & 0xFF),
109 (uint8_t)(height
& 0xFF),
110 (uint8_t)((height
>> 8) & 0xFF),
112 1 << 5, // scanlines are from top-left
114 fwrite(header
, sizeof(header
[0]), sizeof(header
), f
);
115 fwrite(&buffer
[0], sizeof(buffer
[0]), buffer
.size(), f
);
136 Glyph(unsigned codepoint
, const FT_BitmapGlyph
&glyph
)
137 : pixels(glyph
->bitmap
.width
> 0
138 ? ((unsigned)glyph
->bitmap
.width
/ SCALE
+ 2 * PADDING
)
140 glyph
->bitmap
.rows
> 0
141 ? ((unsigned)glyph
->bitmap
.rows
/ SCALE
+ 2 * PADDING
)
143 , codepoint(codepoint
)
144 , width(pixels
.width
)
145 , height(pixels
.height
)
147 , u0(0), v0(0), u1(0), v1(0)
148 , advance_x((glyph
->root
.advance
.x
>> 16) / (float)SCALE
)
149 , advance_y((glyph
->root
.advance
.y
>> 16) / (float)SCALE
)
150 , left(glyph
->left
/ (float)SCALE
)
151 , top(glyph
->top
/ (float)SCALE
)
153 const Bitmap
bmap(glyph
->bitmap
);
155 for (auto y
= 0u; y
< height
; y
++) {
156 for (auto x
= 0u; x
< width
; x
++) {
157 // Sample from center of high-res area.
158 auto xs
= x
* SCALE
+ SCALE
/ 2;
159 auto ys
= y
* SCALE
+ SCALE
/ 2;
160 pixels(x
, y
) = bmap
.sdf(xs
, ys
, 2 * SCALE
);
166 static bool glyph_codepoint_sort(const Glyph
&a
, const Glyph
&b
) {
167 return a
.codepoint
< b
.codepoint
;
170 static bool glyph_height_sort(const Glyph
&a
, const Glyph
&b
) {
171 // sort high to low, break ties by codepoint for stable output.
172 return a
.height
> b
.height
173 || (a
.height
== b
.height
&& glyph_codepoint_sort(a
, b
));
176 static std::vector
<Glyph
> get_glyphs(
177 FT_Face face
, const std::vector
<unsigned> &codepoints
)
179 std::vector
<Glyph
> glyphs
;
180 // A number of fonts provide two different glyph indices for
181 // "invalid" vs. "undisplayable" characters. Invalid characters
182 // should be ignored. Undisplayable characters should be ignored
183 // unless the character requested is the special undisplayable
185 const unsigned REPLACEMENT
= 0xFFFD;
186 auto invalid
= FT_Get_Char_Index(face
, 0);
187 auto missing
= FT_Get_Char_Index(face
, REPLACEMENT
);
188 auto warning
= false;
189 glyphs
.reserve(codepoints
.size());
191 for (auto i
: codepoints
) {
193 if (FT_Get_Char_Index(face
, i
) != invalid
194 && (i
== REPLACEMENT
|| FT_Get_Char_Index(face
, i
) != missing
)
195 && !FT_Load_Char(face
, i
, FT_LOAD_MONOCHROME
)
196 && !FT_Get_Glyph(face
->glyph
, &glyph
)
197 && !FT_Glyph_To_Bitmap(&glyph
, FT_RENDER_MODE_MONO
, 0, 1)
198 && ((FT_BitmapGlyph
)glyph
)->bitmap
.pixel_mode
== FT_PIXEL_MODE_MONO
201 auto bmp_glyph
= (FT_BitmapGlyph
)glyph
;
202 glyphs
.push_back(Glyph(i
, bmp_glyph
));
204 warnx("Resuming generation at codepoint %d.", i
);
206 } else if (!warning
) {
207 warnx("Can't load codepoint %d and onwards.", i
);
215 static unsigned get_texture_size(unsigned &width
, unsigned &height
, std::vector
<Glyph
> &glyphs
)
217 // Sort glyphs by height to minimize vertically wasted space.
218 std::sort(glyphs
.begin(), glyphs
.end(), glyph_height_sort
);
220 // Get an estimated size by assuming all characters are the same
221 // height, and that we can lay them out exactly in a square. This
222 // drastically overestimates the space required unless the
223 // characters are actually all the max height, then it slightly
224 // underestimates it.
225 auto max_height
= glyphs
.front().height
;
226 auto total_width
= 0u;
227 for (auto &glyph
: glyphs
)
228 total_width
+= glyph
.width
;
229 auto side
= ceil(sqrt(total_width
* max_height
));
231 // Round width up to the nearest power of 2.
232 for (width
= 1; width
< side
; width
*= 2);
234 // Now, lay glyphs out horizontally in fixed rows, to figure out
235 // how tall it *really* needs to be. While we're at it, figure out
236 // what the optimum space usage would be.
238 // This is a naive algorithm that leaves "a lot" of space empty,
239 // like 30-50% of the texture. The problem is unless the space is
240 // at least 50%, no amount of smart packing is going to improve it
241 // anyway, and will just make the texture harder to spot-check.
243 auto total_height
= 0u;
244 auto width_accum
= width
+ 1;
247 for (auto &glyph
: glyphs
) {
248 if (width_accum
+ glyph
.width
> width
) {
250 row_start
= total_height
;
251 total_height
+= glyph
.height
;
253 glyph
.x
= width_accum
;
255 width_accum
+= glyph
.width
;
256 real_area
+= glyph
.width
* glyph
.height
;
259 // Now round height up to nearest power of 2.
260 for (height
= 1; height
< total_height
; height
*= 2);
262 // With the height known, texture coordinates can be set.
263 for (auto &glyph
: glyphs
) {
265 glyph
.u0
= (float)(glyph
.x
) / width
;
266 glyph
.v0
= (float)(glyph
.y
) / height
;
267 glyph
.u1
= (float)(glyph
.x
+ glyph
.width
) / width
;
268 glyph
.v1
= (float)(glyph
.y
+ glyph
.height
) / height
;
270 glyph
.u0
= glyph
.v0
= glyph
.u1
= glyph
.v1
= 0.f
;
277 static std::vector
<unsigned> parse_ranges(const char *s
) {
278 std::vector
<unsigned> v
;
279 for (; *s
; s
+= !!*s
) {
280 auto start
= std::strtoul(s
, (char **)&s
, 0);
282 auto end
= std::strtoul(++s
, (char **)&s
, 0);
284 std::swap(start
, end
);
285 for (auto i
= start
; i
<= end
; ++i
)
286 v
.push_back((unsigned)i
);
288 v
.push_back((unsigned)start
);
294 int main(int argc
, char **argv
) {
299 if (argc
< 2 || argc
> 4)
300 errx(1, "Usage: %s font size char-range", argv
[0]);
302 auto size
= argc
> 2 ? (unsigned)atoi(argv
[2]) : 64u;
303 auto codepoints
= parse_ranges(argc
> 3 ? argv
[3] : "0-16777216");
305 if (codepoints
.size() == 0)
306 errx(1, "Invalid codepoint range: %s", argv
[3]);
308 errx(1, "Invalid size %s", argv
[2]);
310 if ((error
= FT_Init_FreeType(&library
)))
311 errx(1, "Can't initialize FreeType: %d", error
);
312 if ((error
= FT_New_Face(library
, argv
[1], 0, &face
)))
313 errx(1, "Can't load %s: %d", argv
[1], error
);
314 if ((error
= FT_Set_Pixel_Sizes(face
, 0, size
* SCALE
)))
315 errx(1, "Unable to set pixel size.");
317 auto glyphs
= get_glyphs(face
, codepoints
);
318 if (glyphs
.size() == 0)
319 errx(1, "No glyphs found.");
321 unsigned width
, height
;
322 auto real_area
= get_texture_size(width
, height
, glyphs
);
323 auto wasted
= 1.f
- float(real_area
) / (width
* height
);
325 "%ux%u texture for %zi glyphs (%u%% wasted).\n", width
, height
,
326 glyphs
.size(), (unsigned)(wasted
* 100));
328 std::string
name(argv
[1]);
330 name
.erase(name
.rfind("."));
331 if ((sep
= name
.rfind("/")) != std::string::npos
)
332 name
.erase(0, sep
+ 1);
333 if ((sep
= name
.rfind("\\")) != std::string::npos
)
334 name
.erase(0, sep
+ 1);
336 Bitmap
total(width
, height
);
337 std::sort(glyphs
.begin(), glyphs
.end(), glyph_codepoint_sort
);
338 auto json_filename
= name
+ ".font.json";
339 auto f
= fopen(json_filename
.c_str(), "w");
341 errx(1, "unable to write JSON data: %s", json_filename
.c_str());
342 fprintf(f
, "{\n \"height\": %u,\n", size
);
343 fprintf(f
, " \"scale\": %u,\n", SCALE
);
344 fprintf(f
, " \"padding\": %u,\n", PADDING
);
345 fprintf(f
, " \"glyphs\": {");
347 for (auto &glyph
: glyphs
) {
351 fprintf(f
, "\n \"%u\": {\n", glyph
.codepoint
);
352 fprintf(f
, " \"size\": [%u, %u],\n",
353 glyph
.width
, glyph
.height
);
354 fprintf(f
, " \"advance\": [%g, %g],\n",
355 glyph
.advance_x
, glyph
.advance_y
);
356 fprintf(f
, " \"offset\": [%g, %g],\n",
357 glyph
.left
, glyph
.top
);
358 fprintf(f
, " \"texCoords\": [%g, %g, %g, %g]\n",
359 glyph
.u0
, glyph
.v0
, glyph
.u1
, glyph
.v1
);
361 total
.blit(glyph
.pixels
, glyph
.x
, glyph
.y
);
363 fprintf(f
, "\n }\n}\n");
364 total
.save(name
+ ".tga");