renderer: use direct FreeType with explicit font files

This commit is contained in:
2026-08-13 20:40:38 +09:00
parent 125e9349c0
commit f46d51b539
16 changed files with 314 additions and 5201 deletions

209
font.c
View File

@@ -1,29 +1,18 @@
#define STB_TRUETYPE_IMPLEMENTATION
#include "stb_truetype.h"
#include "dat.h"
#include <ft2build.h>
#include FT_FREETYPE_H
#include "fn.h"
typedef struct Glyph Glyph;
typedef struct Font Font;
struct Glyph
{
uchar *bmp;
int w, h, ox, oy, base;
};
struct Font
{
stbtt_fontinfo info;
uchar *data;
float scale;
FT_Face face;
int base;
};
enum { Maxfonts = 4 };
static FT_Library lib;
static Font fonts[Maxfonts];
static int nfonts;
static Glyph cache[Nglyphs];
static u32int blendtab[2][256];
static u32int
@@ -41,117 +30,139 @@ blend(u32int bg, u32int fg, int a)
static int
loadfont(char *path)
{
FT_Face face;
Font *f;
int ascent, descent, gap, fd;
long sz, n;
vlong height, pixels;
fd = open(path, OREAD);
if(fd < 0){
fprint(2, "strans: popup: can't open font: %s\n", path);
if(nfonts >= nelem(fonts))
return 0;
if(FT_New_Face(lib, path, 0, &face) != 0){
fprint(2, "strans: popup: can't load font: %s\n", path);
return 0;
}
sz = seek(fd, 0, 2);
seek(fd, 0, 0);
if(sz <= 0){
fprint(2, "strans: popup: empty font: %s\n", path);
close(fd);
height = (vlong)face->ascender - face->descender;
if(height <= 0 || face->units_per_EM == 0){
fprint(2, "strans: popup: invalid font metrics: %s\n", path);
FT_Done_Face(face);
return 0;
}
pixels = (vlong)Fontsz * face->units_per_EM / height;
if(pixels < 1)
pixels = 1;
if(FT_Set_Pixel_Sizes(face, 0, pixels) != 0){
fprint(2, "strans: popup: can't size font: %s\n", path);
FT_Done_Face(face);
return 0;
}
f = &fonts[nfonts];
f->data = emalloc(sz);
n = readn(fd, f->data, sz);
close(fd);
if(n != sz){
fprint(2, "strans: popup: can't read font: %s\n", path);
free(f->data);
f->data = nil;
return 0;
}
if(!stbtt_InitFont(&f->info, f->data,
stbtt_GetFontOffsetForIndex(f->data, 0))){
fprint(2, "strans: popup: can't init font: %s\n", path);
free(f->data);
f->data = nil;
return 0;
}
f->scale = stbtt_ScaleForPixelHeight(&f->info, Fontsz);
stbtt_GetFontVMetrics(&f->info, &ascent, &descent, &gap);
(void)descent;
(void)gap;
f->base = (int)(ascent * f->scale);
f->face = face;
f->base = (vlong)Fontsz * face->ascender / height;
nfonts++;
return 1;
}
int
fontinit(char *dir)
static void
clearfonts(void)
{
static char *names[] = {
"NotoSans-Regular.ttf",
"NotoSansMonoCJKjp-Regular.otf",
"NotoEmoji-Regular.ttf",
};
int i, a;
char path[256];
int i;
for(i = 0; i < nfonts; i++){
FT_Done_Face(fonts[i].face);
fonts[i].face = nil;
}
nfonts = 0;
memset(cache, 0, sizeof cache);
for(i = 0; i < nelem(names); i++){
if(strlen(dir) + 1 + strlen(names[i]) + 1 > sizeof path){
fprint(2, "strans: popup: font path is too long\n");
continue;
}
int
fontinit(char **path, int npath)
{
int i, a;
if(path == nil || npath < 1 || npath > nelem(fonts)){
fprint(2, "strans: popup: need 1-%d font files\n", nelem(fonts));
clearfonts();
return 0;
}
if(lib == nil && FT_Init_FreeType(&lib) != 0){
fprint(2, "strans: popup: can't initialize FreeType\n");
return 0;
}
clearfonts();
for(i = 0; i < npath; i++){
if(path[i] == nil || !loadfont(path[i])){
clearfonts();
return 0;
}
snprint(path, sizeof path, "%s/%s", dir, names[i]);
loadfont(path);
}
for(a = 0; a < 256; a++){
blendtab[0][a] = blend(Colbg, Colfg, a);
blendtab[1][a] = blend(Colsel, Colfg, a);
}
return nfonts != 0;
return 1;
}
static int
drawglyph(Font *f, u32int *buf, int w, int h, int px, int py)
{
FT_Bitmap *b;
FT_GlyphSlot g;
uchar *row;
vlong pitch, x0, x1, y0, y1;
int a, sel, x, xa, xb, y, ya, yb;
u32int *p;
g = f->face->glyph;
b = &g->bitmap;
if(b->pixel_mode != FT_PIXEL_MODE_GRAY || b->num_grays != 256)
return 0;
if(b->width == 0 || b->rows == 0)
return 1;
pitch = b->pitch;
if(pitch < 0)
pitch = -pitch;
if(b->buffer == nil || pitch < b->width)
return 0;
x0 = (vlong)px + g->bitmap_left;
y0 = (vlong)py + f->base - g->bitmap_top;
x1 = x0 + b->width;
y1 = y0 + b->rows;
if(x1 <= 0 || y1 <= 0 || x0 >= w || y0 >= h)
return 1;
xa = x0 < 0 ? 0 : x0;
xb = x1 > w ? w : x1;
ya = y0 < 0 ? 0 : y0;
yb = y1 > h ? h : y1;
for(y = ya; y < yb; y++){
if(b->pitch < 0)
row = b->buffer + ((vlong)b->rows - 1 - (y - y0)) * pitch;
else
row = b->buffer + ((vlong)y - y0) * pitch;
for(x = xa; x < xb; x++){
a = row[(vlong)x - x0];
if(a != 0){
p = &buf[y * w + x];
sel = *p == Colsel;
*p = blendtab[sel][a];
}
}
}
return 1;
}
void
putfont(u32int *buf, int w, int h, int px, int py, Rune r)
{
Glyph *g;
int i, j, a, sel, f;
int y0, j0, j1, x0, i0, i1;
u32int *p;
FT_UInt g;
int f;
if(r >= Nglyphs)
if(buf == nil || w <= 0 || h <= 0)
return;
g = &cache[r];
if(g->bmp == nil){
for(f = 0; f < nfonts; f++){
if(stbtt_FindGlyphIndex(&fonts[f].info, r) == 0)
continue;
g->bmp = stbtt_GetCodepointBitmap(&fonts[f].info,
fonts[f].scale, fonts[f].scale, r,
&g->w, &g->h, &g->ox, &g->oy);
if(g->bmp != nil){
g->base = fonts[f].base;
break;
}
}
if(g->bmp == nil)
for(f = 0; f < nfonts; f++){
g = FT_Get_Char_Index(fonts[f].face, r);
if(g == 0 || FT_Load_Glyph(fonts[f].face, g, FT_LOAD_DEFAULT) != 0 ||
FT_Render_Glyph(fonts[f].face->glyph, FT_RENDER_MODE_NORMAL) != 0)
continue;
if(drawglyph(&fonts[f], buf, w, h, px, py))
return;
}
y0 = py + g->base + g->oy;
j0 = y0 < 0 ? -y0 : 0;
j1 = y0 + g->h > h ? h - y0 : g->h;
x0 = px + g->ox;
i0 = x0 < 0 ? -x0 : 0;
i1 = x0 + g->w > w ? w - x0 : g->w;
for(j = j0; j < j1; j++){
for(i = i0; i < i1; i++){
a = g->bmp[j * g->w + i];
if(a > 0){
p = &buf[(y0 + j) * w + x0 + i];
sel = (*p == Colsel) ? 1 : 0;
*p = blendtab[sel][a];
}
}
}
}