fix: make popup rendering deterministic

This commit is contained in:
2026-08-12 15:59:52 +09:00
parent 9c43bcb75c
commit 4120f90736
13 changed files with 698 additions and 86 deletions

135
font.c
View File

@@ -4,97 +4,118 @@
#include "fn.h"
typedef struct Glyph Glyph;
typedef struct Font Font;
struct Glyph
{
uchar *bmp;
int w, h, ox, oy;
int w, h, ox, oy, base;
};
struct Font
{
stbtt_fontinfo info;
uchar *data;
float scale;
int base;
};
enum { Maxfonts = 4 };
static stbtt_fontinfo fonts[Maxfonts];
static uchar *fontdata[Maxfonts];
static float scale[Maxfonts];
static Font fonts[Maxfonts];
static int nfonts;
static Glyph cache[Nglyphs];
static u32int blendtab[2][256];
static u32int
blend(u32int bg, int a)
blend(u32int bg, u32int fg, int a)
{
int r, g, b, inv;
inv = 255 - a;
r = (bg >> 16 & 0xff) * inv / 255;
g = (bg >> 8 & 0xff) * inv / 255;
b = (bg & 0xff) * inv / 255;
r = ((bg >> 16 & 0xff) * inv + (fg >> 16 & 0xff) * a) / 255;
g = ((bg >> 8 & 0xff) * inv + (fg >> 8 & 0xff) * a) / 255;
b = ((bg & 0xff) * inv + (fg & 0xff) * a) / 255;
return (r << 16) | (g << 8) | b;
}
static void
static int
loadfont(char *path)
{
int fd;
Font *f;
int ascent, descent, gap, fd;
long sz, n;
if(nfonts >= Maxfonts)
die("too many fonts");
fd = open(path, OREAD);
if(fd < 0)
die("can't open font: %s", path);
if(fd < 0){
fprint(2, "strans: popup: can't open font: %s\n", path);
return 0;
}
sz = seek(fd, 0, 2);
seek(fd, 0, 0);
fontdata[nfonts] = emalloc(sz);
n = readn(fd, fontdata[nfonts], sz);
close(fd);
if(n != sz)
die("can't read font: %s", path);
if(!stbtt_InitFont(&fonts[nfonts], fontdata[nfonts], stbtt_GetFontOffsetForIndex(fontdata[nfonts], 0)))
die("can't init font: %s", path);
scale[nfonts] = stbtt_ScaleForPixelHeight(&fonts[nfonts], Fontsz);
nfonts++;
}
static int
isfont(char *name)
{
char *p;
p = strrchr(name, '.');
if(p == nil)
if(sz <= 0){
fprint(2, "strans: popup: empty font: %s\n", path);
close(fd);
return 0;
return strcmp(p, ".ttf") == 0 || strcmp(p, ".otf") == 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);
nfonts++;
return 1;
}
void
fontinit(char *dir)
{
int fd, n, i, a;
Dir *d;
static char *names[] = {
"NotoSans-Regular.ttf",
"NotoSansMonoCJKjp-Regular.otf",
"NotoEmoji-Regular.ttf",
};
int i, a;
char path[256];
fd = open(dir, OREAD);
if(fd < 0)
die("can't open font dir: %s", dir);
n = dirreadall(fd, &d);
close(fd);
if(n < 0)
die("can't read font dir: %s", dir);
for(i = 0; i < n; i++){
if(isfont(d[i].name)){
snprint(path, sizeof path, "%s/%s", dir, d[i].name);
loadfont(path);
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;
}
snprint(path, sizeof path, "%s/%s", dir, names[i]);
loadfont(path);
}
free(d);
if(nfonts == 0)
die("no fonts in %s", dir);
for(a = 0; a < 256; a++){
blendtab[0][a] = blend(Colbg, a);
blendtab[1][a] = blend(Colsel, a);
blendtab[0][a] = blend(Colbg, Colfg, a);
blendtab[1][a] = blend(Colsel, Colfg, a);
}
}
int
fontready(void)
{
return nfonts != 0;
}
void
putfont(u32int *buf, int w, int h, int px, int py, Rune r)
{
@@ -108,17 +129,21 @@ putfont(u32int *buf, int w, int h, int px, int py, Rune r)
g = &cache[r];
if(g->bmp == nil){
for(f = 0; f < nfonts; f++){
if(stbtt_FindGlyphIndex(&fonts[f], r) == 0)
if(stbtt_FindGlyphIndex(&fonts[f].info, r) == 0)
continue;
g->bmp = stbtt_GetCodepointBitmap(&fonts[f], scale[f], scale[f], r, &g->w, &g->h, &g->ox, &g->oy);
if(g->bmp != nil)
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)
return;
}
y0 = py + g->oy + Fontsz - Fontbase;
y0 = py + g->base + g->oy;
j0 = y0 < 0 ? -y0 : 0;
j1 = y0 + g->h > h ? h - y0 : g->h;
x0 = px + g->ox;