906c4c9edda748cf6ff9d2c68b2371d1ec6723c3
[ttf-to-distfield.git] / ttf-to-distfield.cpp
1 /** ttf-to-distfield - Render a distance field from a TrueType font
2 by Joe Wreschnig
3
4 This code is released into the public domain via the CC0 Public
5 Domain Dedication.
6 */
7
8 #include <ft2build.h>
9 #include FT_FREETYPE_H
10 #include FT_GLYPH_H
11
12 #include <err.h>
13 #include <stdint.h>
14
15 #include <algorithm>
16 #include <cmath>
17 #include <cstdlib>
18 #include <string>
19 #include <utility>
20 #include <vector>
21
22 const auto SCALE = 16u;
23 const auto PADDING = 4u;
24 const auto SCALED_PADDING = SCALE * PADDING;
25
26 struct Bitmap {
27 unsigned width;
28 unsigned height;
29 std::vector<uint8_t> buffer;
30
31 Bitmap(unsigned width, unsigned height)
32 : width(width)
33 , height(height)
34 , buffer(width * height)
35 {}
36
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)
41 {
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;
47 }
48 }
49 }
50
51 inline uint8_t &operator()(unsigned x, unsigned y) {
52 auto idx = y * width + x;
53 return buffer[idx];
54 }
55
56 inline uint8_t operator()(unsigned x, unsigned y) const {
57 if (x >= width || y >= height)
58 return 0;
59 auto idx = y * width + x;
60 return buffer[idx];
61 }
62
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);
67 }
68
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);
72
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);
83 }
84 }
85
86 // Map distance onto to [0, 255].
87 auto d = std::sqrt((float)dsq);
88 if (v == 0)
89 d = -d;
90 d *= (255.f / 2.f) / max_r;
91 d += (255.f / 2.f);
92 d = std::min(255.f, std::max(0.f, d));
93 return (uint8_t)(d + 0.5);
94 }
95
96 void save(const std::string &filename) const {
97 auto f = fopen(filename.c_str(), "wb");
98 if (!f)
99 err(1, "unable to write image: %s", filename.c_str());
100 const uint8_t header[] = {
101 0, // ID length
102 0, // color map
103 3, // image type = greyscale
104 0, 0, 0, 0, 0, // color map specification
105 0, 0, // X origin
106 0, 0, // Y origin
107 (uint8_t)(width & 0xFF),
108 (uint8_t)((width >> 8) & 0xFF),
109 (uint8_t)(height & 0xFF),
110 (uint8_t)((height >> 8) & 0xFF),
111 8, // bits per pixel
112 1 << 5, // scanlines are from top-left
113 };
114 fwrite(header, sizeof(header[0]), sizeof(header), f);
115 fwrite(&buffer[0], sizeof(buffer[0]), buffer.size(), f);
116 fclose(f);
117 }
118 };
119
120 struct Glyph {
121 Bitmap pixels;
122 unsigned codepoint;
123 unsigned width;
124 unsigned height;
125 unsigned x;
126 unsigned y;
127 float u0;
128 float v0;
129 float u1;
130 float v1;
131 float advance_x;
132 float advance_y;
133 float left;
134 float top;
135
136 Glyph(unsigned codepoint, const FT_BitmapGlyph &glyph)
137 : pixels(glyph->bitmap.width > 0
138 ? ((unsigned)glyph->bitmap.width / SCALE + 2 * PADDING)
139 : 0,
140 glyph->bitmap.rows > 0
141 ? ((unsigned)glyph->bitmap.rows / SCALE + 2 * PADDING)
142 : 0)
143 , codepoint(codepoint)
144 , width(pixels.width)
145 , height(pixels.height)
146 , x(0), y(0)
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)
152 {
153 const Bitmap bmap(glyph->bitmap);
154
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);
161 }
162 }
163 }
164 };
165
166 static bool glyph_codepoint_sort(const Glyph &a, const Glyph &b) {
167 return a.codepoint < b.codepoint;
168 }
169
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));
174 }
175
176 static std::vector<Glyph> get_glyphs(
177 FT_Face face, const std::vector<unsigned> &codepoints)
178 {
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
184 // character.
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());
190
191 for (auto i : codepoints) {
192 FT_Glyph glyph;
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
199 )
200 {
201 auto bmp_glyph = (FT_BitmapGlyph)glyph;
202 glyphs.push_back(Glyph(i, bmp_glyph));
203 if (warning)
204 warnx("Resuming generation at codepoint %d.", i);
205 warning = false;
206 } else if (!warning) {
207 warnx("Can't load codepoint %d and onwards.", i);
208 warning = true;
209 }
210 }
211
212 return glyphs;
213 }
214
215 static unsigned get_texture_size(unsigned &width, unsigned &height, std::vector<Glyph> &glyphs)
216 {
217 // Sort glyphs by height to minimize vertically wasted space.
218 std::sort(glyphs.begin(), glyphs.end(), glyph_height_sort);
219
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));
230
231 // Round width up to the nearest power of 2.
232 for (width = 1; width < side; width *= 2);
233
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.
237 //
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.
242
243 auto total_height = 0u;
244 auto width_accum = width + 1;
245 auto real_area = 0u;
246 auto row_start = 0u;
247 for (auto &glyph : glyphs) {
248 if (width_accum + glyph.width > width) {
249 width_accum = 0;
250 row_start = total_height;
251 total_height += glyph.height;
252 }
253 glyph.x = width_accum;
254 glyph.y = row_start;
255 width_accum += glyph.width;
256 real_area += glyph.width * glyph.height;
257 }
258
259 // Now round height up to nearest power of 2.
260 for (height = 1; height < total_height; height *= 2);
261
262 // With the height known, texture coordinates can be set.
263 for (auto &glyph : glyphs) {
264 if (glyph.width) {
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;
269 } else {
270 glyph.u0 = glyph.v0 = glyph.u1 = glyph.v1 = 0.f;
271 }
272 }
273
274 return real_area;
275 }
276
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);
281 if (*s == '-') {
282 auto end = std::strtoul(++s, (char **)&s, 0);
283 if (start > end)
284 std::swap(start, end);
285 for (auto i = start; i <= end; ++i)
286 v.push_back((unsigned)i);
287 } else {
288 v.push_back((unsigned)start);
289 }
290 }
291 return v;
292 }
293
294 int main(int argc, char **argv) {
295 FT_Library library;
296 FT_Face face;
297 int error;
298
299 if (argc < 2 || argc > 4)
300 errx(1, "Usage: %s font size char-range", argv[0]);
301
302 auto size = argc > 2 ? (unsigned)atoi(argv[2]) : 64u;
303 auto codepoints = parse_ranges(argc > 3 ? argv[3] : "0-16777216");
304
305 if (codepoints.size() == 0)
306 errx(1, "Invalid codepoint range: %s", argv[3]);
307 if (size < 8)
308 errx(1, "Invalid size %s", argv[2]);
309
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.");
316
317 auto glyphs = get_glyphs(face, codepoints);
318 if (glyphs.size() == 0)
319 errx(1, "No glyphs found.");
320
321 unsigned width, height;
322 auto real_area = get_texture_size(width, height, glyphs);
323 auto wasted = 1.f - float(real_area) / (width * height);
324 fprintf(stderr,
325 "%ux%u texture for %zi glyphs (%u%% wasted).\n", width, height,
326 glyphs.size(), (unsigned)(wasted * 100));
327
328 std::string name(argv[1]);
329 size_t sep;
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);
335
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");
340 if (!f)
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\": {");
346 auto first = true;
347 for (auto &glyph : glyphs) {
348 if (!first)
349 fprintf(f, ",");
350 first = false;
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);
360 fprintf(f, " }");
361 total.blit(glyph.pixels, glyph.x, glyph.y);
362 }
363 fprintf(f, "\n }\n}\n");
364 total.save(name + ".tga");
365 }