// Generate embedded 4-bit-alpha font tables from FreeType's native monochrome renderer. // // Build/run example: // cc scripts/generate-fonts-ftmono.c $(freetype-config --cflags --libs) -o /tmp/kinda-fontgen-ftmono // /tmp/kinda-fontgen-ftmono /System/Library/Fonts/Supplemental/Verdana.ttf src/ui/generated_fonts.rs // // This path is intended for 1-bit e-paper output. It preserves TrueType mono // hinting instead of thresholding an antialiased outline after rasterization. #include #include FT_FREETYPE_H #include #include #include #include struct FontSize { const char *name; int pixels; }; struct Glyph { uint32_t codepoint; uint8_t width; uint8_t height; uint32_t bitmap_offset; }; struct Buffer { uint8_t *data; size_t len; size_t cap; }; static const struct FontSize SIZES[] = { {"QUATTRO_SMALL", 24}, {"QUATTRO_BODY", 30}, {"QUATTRO_READER", 34}, {"QUATTRO_TITLE", 38}, }; static const uint32_t EXTRA_CODEPOINTS[] = { 0x00A0, 0x2018, 0x2019, 0x201C, 0x201D, 0x2013, 0x2014, 0x2026, }; static int div_ceil_int(int value, int divisor) { return (value + divisor - 1) / divisor; } static int round_26_6(FT_Pos value) { if (value >= 0) { return (int)((value + 32) >> 6); } return -(int)(((-value) + 32) >> 6); } static int ceil_26_6(FT_Pos value) { if (value >= 0) { return (int)((value + 63) >> 6); } return -(int)((-value) >> 6); } static void buffer_push(struct Buffer *buffer, uint8_t value) { if (buffer->len == buffer->cap) { size_t next_cap = buffer->cap == 0 ? 4096 : buffer->cap * 2; uint8_t *next = realloc(buffer->data, next_cap); if (next == NULL) { fprintf(stderr, "out of memory\n"); exit(2); } buffer->data = next; buffer->cap = next_cap; } buffer->data[buffer->len++] = value; } static int mono_bitmap_on(const FT_Bitmap *bitmap, unsigned int x, unsigned int y) { int pitch = bitmap->pitch; const uint8_t *row; if (pitch < 0) { row = bitmap->buffer + (bitmap->rows - 1 - y) * (unsigned int)(-pitch); } else { row = bitmap->buffer + y * (unsigned int)pitch; } return (row[x / 8] & (0x80 >> (x & 7))) != 0; } static void set_alpha4(uint8_t *cell, int width, int x, int y, uint8_t alpha) { int pixel = y * width + x; int byte = pixel / 2; if ((pixel & 1) == 0) { cell[byte] = (uint8_t)((cell[byte] & 0x0F) | (alpha << 4)); } else { cell[byte] = (uint8_t)((cell[byte] & 0xF0) | alpha); } } static int load_codepoints(uint32_t *codepoints, int max_codepoints) { int count = 0; for (uint32_t codepoint = 32; codepoint < 127; codepoint++) { if (count >= max_codepoints) { return count; } codepoints[count++] = codepoint; } for (size_t i = 0; i < sizeof(EXTRA_CODEPOINTS) / sizeof(EXTRA_CODEPOINTS[0]); i++) { if (count >= max_codepoints) { return count; } codepoints[count++] = EXTRA_CODEPOINTS[i]; } return count; } static void emit_bytes(FILE *out, const struct Buffer *bitmap) { for (size_t i = 0; i < bitmap->len; i++) { if (i % 16 == 0) { fputs(" ", out); } fprintf(out, "0x%02x,", bitmap->data[i]); if (i % 16 == 15 || i + 1 == bitmap->len) { fputc('\n', out); } else { fputc(' ', out); } } } static void emit_font(FILE *out, FT_Face face, const char *name, int pixels, const uint32_t *codepoints, int codepoint_count) { if (FT_Set_Pixel_Sizes(face, 0, (FT_UInt)pixels) != 0) { fprintf(stderr, "could not set pixel size %d\n", pixels); exit(3); } int cell_height = ceil_26_6(face->size->metrics.height); int baseline = ceil_26_6(face->size->metrics.ascender); if (cell_height <= 0) { cell_height = pixels; } struct Glyph *glyphs = calloc((size_t)codepoint_count, sizeof(struct Glyph)); if (glyphs == NULL) { fprintf(stderr, "out of memory\n"); exit(2); } struct Buffer bitmap = {0}; int glyph_count = 0; for (int i = 0; i < codepoint_count; i++) { uint32_t codepoint = codepoints[i]; FT_UInt glyph_index = FT_Get_Char_Index(face, codepoint); if (glyph_index == 0) { continue; } if (FT_Load_Glyph(face, glyph_index, FT_LOAD_RENDER | FT_LOAD_TARGET_MONO) != 0) { continue; } FT_GlyphSlot slot = face->glyph; FT_Bitmap *source = &slot->bitmap; int advance = round_26_6(slot->advance.x); int left = slot->bitmap_left; int top = slot->bitmap_top; int right = left + (int)source->width; int cell_width = advance; if (right > cell_width) { cell_width = right; } if (cell_width <= 0) { cell_width = (int)source->width; } if (cell_width <= 0) { cell_width = 1; } if (cell_width > 255 || cell_height > 255) { fprintf(stderr, "glyph U+%04X too large at %dpx\n", codepoint, pixels); exit(4); } int cell_len = div_ceil_int(cell_width * cell_height, 2); uint8_t *cell = calloc((size_t)cell_len, 1); if (cell == NULL) { fprintf(stderr, "out of memory\n"); exit(2); } int x_origin = left < 0 ? 0 : left; int y_origin = baseline - top; for (unsigned int y = 0; y < source->rows; y++) { int target_y = y_origin + (int)y; if (target_y < 0 || target_y >= cell_height) { continue; } for (unsigned int x = 0; x < source->width; x++) { int target_x = x_origin + (int)x; if (target_x < 0 || target_x >= cell_width) { continue; } if (mono_bitmap_on(source, x, y)) { set_alpha4(cell, cell_width, target_x, target_y, 15); } } } uint32_t offset = (uint32_t)bitmap.len; for (int j = 0; j < cell_len; j++) { buffer_push(&bitmap, cell[j]); } free(cell); glyphs[glyph_count++] = (struct Glyph){ .codepoint = codepoint, .width = (uint8_t)cell_width, .height = (uint8_t)cell_height, .bitmap_offset = offset, }; } fprintf(out, "// %d px; cell height %d px; %d glyphs; %zu mono alpha bytes.\n", pixels, cell_height, glyph_count, bitmap.len); fprintf(out, "pub(crate) static %s: BitmapFont = BitmapFont {\n", name); fprintf(out, " cell_height: %d,\n", cell_height); fprintf(out, " glyphs: &%s_GLYPHS,\n", name); fprintf(out, " bitmap: &%s_BITMAP,\n", name); fprintf(out, "};\n\n"); fprintf(out, "#[rustfmt::skip]\n"); fprintf(out, "static %s_GLYPHS: [Glyph; %d] = [\n", name, glyph_count); for (int i = 0; i < glyph_count; i++) { fprintf(out, " Glyph { codepoint: 0x%04x, width: %u, height: %u, bitmap_offset: %u },\n", glyphs[i].codepoint, glyphs[i].width, glyphs[i].height, glyphs[i].bitmap_offset); } fprintf(out, "];\n\n"); fprintf(out, "#[rustfmt::skip]\n"); fprintf(out, "static %s_BITMAP: [u8; %zu] = [\n", name, bitmap.len); emit_bytes(out, &bitmap); fprintf(out, "];\n\n"); free(glyphs); free(bitmap.data); } int main(int argc, char **argv) { if (argc != 3) { fprintf(stderr, "usage: %s FONT.ttf OUTPUT.rs\n", argv[0]); return 2; } FT_Library library; FT_Face face; if (FT_Init_FreeType(&library) != 0) { fprintf(stderr, "could not init freetype\n"); return 3; } if (FT_New_Face(library, argv[1], 0, &face) != 0) { fprintf(stderr, "could not open font %s\n", argv[1]); return 3; } uint32_t codepoints[128]; int codepoint_count = load_codepoints(codepoints, 128); FILE *out = fopen(argv[2], "w"); if (out == NULL) { perror("fopen"); return 3; } fprintf(out, "// @generated by scripts/generate-fonts-ftmono.c. Do not edit by hand.\n"); fprintf(out, "// Source font: %s\n", argv[1]); fprintf(out, "// Rasterizer: FreeType mono hinting\n\n"); fprintf(out, "use crate::ui::typography::{BitmapFont, Glyph};\n\n"); for (size_t i = 0; i < sizeof(SIZES) / sizeof(SIZES[0]); i++) { emit_font(out, face, SIZES[i].name, SIZES[i].pixels, codepoints, codepoint_count); } fclose(out); FT_Done_Face(face); FT_Done_FreeType(library); return 0; }