300 lines
8.6 KiB
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;
|
|
}
|