#include #include #include FT_FREETYPE_H #include #include #include #include #include #include static const FT_Int32 LoadFlags = FT_LOAD_DEFAULT; static void RenderGlyph( unsigned char c, const unsigned char* pixels, int width, int height) { assert(pixels); printf("Glyph (%c), %dx%d\n", c, width, height); const unsigned char* pixel = pixels; for (int y = 0; y < height; ++y) { printf("%02d ", y); for (int x = 0; x < width; ++x, ++pixel) { unsigned char p = ' '; if (*pixel >= 128) { p = '#'; } else if (*pixel >= 32) { p = '*'; } else if (*pixel > 0) { p = '.'; } printf("%c", p); } printf("\n"); } printf("\n"); } static void RenderText(const FontAtlas* atlas, const char* text) { assert(text); const int glyph_width = atlas->header.glyph_width; const int glyph_height = atlas->header.glyph_height; const int glyph_size = glyph_width * glyph_height; for (int y = 0; y < glyph_height; ++y) { printf("%02d ", y); for (size_t i = 0; i < strlen(text); ++i) { const char c = text[i]; const int glyph_offset = (c - FontGlyphStart) * glyph_size; const int row_offset = (y * glyph_width); const unsigned char* pixel = atlas->pixels + glyph_offset + row_offset; for (int x = 0; x < glyph_width; ++x, ++pixel) { unsigned char p = ' '; if (*pixel >= 128) { p = '#'; } else if (*pixel >= 32) { p = '*'; } else if (*pixel > 0) { p = '.'; } printf("%c", p); } } printf("\n"); } } static bool WriteAtlas(const FontAtlas* atlas, const char* output_path) { assert(atlas); assert(output_path); bool success = true; FILE* out = NULL; const int num_pixels = atlas->header.num_glyphs * atlas->header.glyph_width * atlas->header.glyph_height; out = fopen(output_path, "wb"); if (out == NULL) { fprintf(stderr, "Failed opening output file: %s\n", output_path); success = false; goto cleanup; } if (fwrite(&atlas->header, sizeof(atlas->header), 1, out) != 1) { fprintf(stderr, "Failed writing atlas header\n"); success = false; goto cleanup; } if (fwrite(atlas->pixels, num_pixels, 1, out) != 1) { fprintf(stderr, "Failed writing atlas\n"); success = false; goto cleanup; } cleanup: if (out != NULL) { fclose(out); } return success; } static int GetYOffset(FT_Face face, int font_size) { const int y_offset = font_size - face->glyph->bitmap_top; assert(y_offset >= 0); return y_offset; } static bool GetMaxGlyphDimensions( FT_Library ft, FT_Face face, int font_size, int* width, int* height) { assert(ft); assert(face); assert(width); assert(height); FT_Error error = 0; int max_width = 0; int max_height = 0; for (unsigned char c = FontGlyphStart; c < FontGlyphEnd; ++c) { const FT_UInt glyph_index = FT_Get_Char_Index(face, c); assert(glyph_index > 0); error = FT_Load_Glyph(face, glyph_index, LoadFlags); assert(error == 0); error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL); assert(error == 0); const int y_offset = GetYOffset(face, font_size); const int xmax = (int)face->glyph->bitmap.width; const int ymax = (int)face->glyph->bitmap.rows + y_offset; if (xmax > max_width) { max_width = xmax; } if (ymax > max_height) { max_height = ymax; } } *width = max_width; *height = max_height; return true; } static void GammaCorrect(unsigned char* pixels, int num_pixels) { assert(pixels); for (int i = 0; i < num_pixels; ++i) { pixels[i] = (unsigned char)(pow((double)pixels[i] / 255.0, 1.0 / 2.2) * 255.0); } } int main(int argc, const char** argv) { const int font_size = 26; bool success = true; if (argc < 3) { fprintf(stderr, "Usage: %s \n", argv[0]); return 0; } const char* font_path = argv[1]; const char* output_path = argv[2]; FT_Library ft = {0}; FT_Face face = {0}; FT_Error error = 0; FontAtlas* atlas = 0; if (FT_Init_FreeType(&ft) != 0) { fprintf(stderr, "FT_Init_FreeType() failed\n"); success = false; goto cleanup; } if (FT_New_Face(ft, font_path, 0, &face) != 0) { fprintf(stderr, "Failed loading font file: %s\n", font_path); success = false; goto cleanup; } if (FT_Set_Pixel_Sizes(face, 0, font_size) != 0) { fprintf(stderr, "Failed to set character size\n"); success = false; goto cleanup; } // Find the largest glyph dimensions. int glyph_width, glyph_height; GetMaxGlyphDimensions(ft, face, font_size, &glyph_width, &glyph_height); const int glyph_size = glyph_width * glyph_height; const int atlas_size = glyph_size * FontAtlasNumGlyphs; printf("Glyph: %dx%d (%d bytes)\n", glyph_width, glyph_height, glyph_size); printf( "Atlas: %dx%d (%d bytes)\n", FontAtlasNumGlyphs * glyph_width, glyph_height, atlas_size); // -1 because Atlas already includes 1 pixel. atlas = calloc(1, sizeof(FontAtlas) + atlas_size - 1); if (!atlas) { success = false; goto cleanup; } atlas->header = (FontHeader){ .glyph_width = glyph_width, .glyph_height = glyph_height, .num_glyphs = FontAtlasNumGlyphs, }; // Render into atlas in glyph-major order so that glyphs can later be rendered // by scanning memory linearly. for (unsigned char c = FontGlyphStart; c < FontGlyphEnd; ++c) { const FT_UInt glyph_index = FT_Get_Char_Index(face, c); assert(glyph_index > 0); error = FT_Load_Glyph(face, glyph_index, LoadFlags); assert(error == 0); error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL); assert(error == 0); const int this_width = (int)face->glyph->bitmap.width; const int this_height = (int)face->glyph->bitmap.rows; // For debugging. if ((c == 'A') || (c == 'B') || (c == 'C') || (c == 'e') || (c == '!')) { RenderGlyph(c, face->glyph->bitmap.buffer, this_width, this_height); } // Space gets no bitmap allocated, skip rendering. if (c == ' ') { continue; } // Render into atlas. const int glyph_offset = (c - FontGlyphStart) * glyph_size; assert(glyph_offset + glyph_size <= atlas_size); assert(this_width <= glyph_width); assert(this_height <= glyph_height); const int x_offset = (glyph_width - this_width) / 2; const int y_offset = GetYOffset(face, font_size); assert(y_offset >= 0); assert(x_offset >= 0); assert(y_offset + this_height <= glyph_height); assert(x_offset + this_width <= glyph_width); unsigned char* pAtlas = atlas->pixels + glyph_offset + (y_offset * glyph_width) + x_offset; const unsigned char* pixel = face->glyph->bitmap.buffer; for (int y = 0; y < this_height; ++y) { for (int x = 0; x < this_width; ++x, ++pixel, ++pAtlas) { *pAtlas = *pixel; } // Glyph may be narrower than the maximum width. if (glyph_width > this_width) { pAtlas += (glyph_width - this_width); } } } // FreeType renders glyphs in linear space. Gamma-correct the atlas so that // applications can directly render to screen. GammaCorrect(atlas->pixels, atlas_size); // Quick test. const char* test = "Good Stuff! \"g_g\", Baking Complete."; RenderText(atlas, test); if (!WriteAtlas(atlas, output_path)) { goto cleanup; } cleanup: if (atlas) { free(atlas); } if (face) { FT_Done_Face(face); } if (ft) { FT_Done_FreeType(ft); } return success ? 0 : 1; }