kinda/scripts/generate-fonts-ftmono.c

300 lines
8.6 KiB
C

// 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 <ft2build.h>
#include FT_FREETYPE_H
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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;
}