popup: use system fonts exclusively

This commit is contained in:
2026-08-14 15:17:00 +09:00
parent 54355b8190
commit 08369e718e
14 changed files with 34 additions and 116 deletions

View File

@@ -51,26 +51,22 @@ dependencies listed in [`Dockerfile`](Dockerfile) are installed.
## Run
The popup needs one to four font files. No fonts are bundled. Each file is
validated and registered with Pango as an application font. Pango shapes the
complete string and chooses glyph fallback from those fonts and the normal
system fonts; command-line file order is not an exact per-glyph fallback
contract.
`run.sh` uses installed Noto Color Emoji, DejaVu, Jigmo, or Noto CJK fonts when
available, or accepts explicit files. Supply a color emoji font such as Noto
Color Emoji to render emoji in color:
The popup uses the normal system fonts found by Pango and Fontconfig; no font
file argument or system-wide strans installation is required. Run it directly
from the build tree:
```sh
./run.sh
./run.sh /path/to/primary.ttf /path/to/fallback.ttc
# daemon only
./strans map
```
The popup renderer requires Pango/PangoCairo 1.56 or newer and Cairo 1.18 or
newer.
Pango shapes the complete string and selects glyph fallback from the system
font set.
The script starts the daemon and, when `DISPLAY` is set, the XIM frontend. It
restarts any existing `strans` processes owned by the current user.
The popup renderer requires Pango/PangoCairo 1.56 or newer and Cairo 1.18 or
newer. `run.sh` starts the daemon and, when `DISPLAY` is set, the XIM frontend.
It restarts existing `strans` processes owned by the current user.
Configure clients as needed:

1
dat.h
View File

@@ -18,7 +18,6 @@ enum
LangVI = 0x16,
Fontsz = 32,
Maxfonts = 4,
Maxrunes = 64,
Maxutf = Maxrunes * UTFmax + 1,
};

View File

@@ -46,14 +46,11 @@ new SKK revision.
## Fonts
No font binaries are bundled. The daemon receives one to four font file paths
on its command line, validates them, and registers them as Pango application
fonts. Pango shapes each complete string and selects glyph fallback from the
registered fonts and the normal system font set. Exact command-line file order
is deliberately not promised: preserving it would require a separate
Fontconfig policy alongside Pango's fallback. `font/PROVENANCE` retains the
exact checksums, source revisions, and license records for the three Noto faces
that were formerly stored in the repository.
No font binaries are bundled. The daemon asks Pango and Fontconfig to select
from the user's normal system font set. Pango shapes each complete string and
selects glyph fallback. `font/PROVENANCE` retains the exact checksums, source
revisions, and license records for the three Noto faces that were formerly
stored in the repository.
The Docker renderer tests obtain their fonts from signed Arch packages rather
than the source checkout. On August 14, 2026 the tested package contract was:

2
fn.h
View File

@@ -43,6 +43,6 @@ void ibusthread(void*);
void* emalloc(ulong);
void* erealloc(void*, ulong);
int textinit(char**, int);
int textinit(void);
int textwidth(Str*);
void textdraw(u32int*, int, int, int, int, Str*);

25
font.c
View File

@@ -43,37 +43,14 @@ setfont(void)
}
int
textinit(char **path, int npath)
textinit(void)
{
GError *err;
int i;
textclear();
if(path == nil || npath < 1 || npath > Maxfonts){
fprint(2, "strans: popup: need 1-%d font files\n", Maxfonts);
return 0;
}
fontmap = pango_cairo_font_map_new();
if(fontmap == nil){
fprint(2, "strans: popup: can't initialize PangoCairo\n");
return 0;
}
for(i = 0; i < npath; i++){
if(path[i] == nil){
fprint(2, "strans: popup: invalid font file\n");
textclear();
return 0;
}
err = nil;
if(!pango_font_map_add_font_file(fontmap, path[i], &err)){
fprint(2, "strans: popup: can't register font: %s: %s\n",
path[i], err == nil ? "unknown error" : err->message);
if(err != nil)
g_error_free(err);
textclear();
return 0;
}
}
pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(fontmap), 96);
context = pango_font_map_create_context(fontmap);
if(context != nil)

8
main.c
View File

@@ -5,13 +5,11 @@ Channel *drawc;
Channel *keyc;
Channel *dictreqc;
Channel *dictresc;
char **fontfiles;
int nfontfiles;
void
usage(void)
{
fprint(2, "usage: strans mapdir fontfile [fontfile ...]\n");
fprint(2, "usage: strans mapdir\n");
threadexitsall("usage");
}
@@ -51,11 +49,9 @@ erealloc(void *p, ulong n)
void
threadmain(int argc, char **argv)
{
if(argc < 3 || argc > Maxfonts + 2)
if(argc != 2)
usage();
fontfiles = argv + 2;
nfontfiles = argc - 2;
drawc = chancreate(sizeof(Drawcmd), 4);
keyc = chancreate(sizeof(Keyreq), 0);
dictreqc = chancreate(sizeof(Dictreq), 4);

21
run.sh
View File

@@ -2,22 +2,9 @@
cd "$(dirname "$0")" || exit 1
if test "$#" -eq 0; then
set --
for font in \
/usr/share/fonts/noto/NotoColorEmoji.ttf \
/usr/share/fonts/TTF/DejaVuSans.ttf \
/usr/share/fonts/TTF/Jigmo.ttf \
/usr/share/fonts/TTF/Jigmo2.ttf \
/usr/share/fonts/noto/NotoSansCJK-Regular.ttc
do
test "$#" -ge 4 && break
test -f "$font" && set -- "$@" "$font"
done
if test "$#" -eq 0; then
echo "run.sh: no default font found; pass a font file" >&2
exit 1
fi
if test "$#" -ne 0; then
echo "usage: run.sh" >&2
exit 1
fi
if ! test -x ./strans; then
@@ -40,7 +27,7 @@ sleep 0.1
pkill -KILL -u "$uid" -x strans-xim 2>/dev/null || :
pkill -KILL -u "$uid" -x strans 2>/dev/null || :
./strans map "$@" </dev/null &
./strans map </dev/null &
strans_pid=$!
if test -n "${DISPLAY-}"; then
xim/strans-xim </dev/null &

View File

@@ -52,7 +52,6 @@ struct Test
char socket[384];
char addrfile[512];
char address[512];
char missing[384];
char addrcontents[2048];
size_t naddrcontents;
struct stat socketst;
@@ -170,9 +169,7 @@ setup(Test *t)
snprintf(t->home, sizeof t->home, "%s/home", t->root)
>= (int)sizeof t->home ||
snprintf(t->socket, sizeof t->socket, "%s/strans.sock", t->runtime)
>= (int)sizeof t->socket ||
snprintf(t->missing, sizeof t->missing, "%s/missing-font.ttf", t->root)
>= (int)sizeof t->missing)
>= (int)sizeof t->socket)
return fail("temporary path is too long");
if(!makedir(t->runtime) || !makedir(t->config) || !makedir(t->ibus) ||
!makedir(t->bus) || !makedir(t->home))
@@ -224,7 +221,7 @@ startchild(Test *t, char *program, char *mapdir, pid_t *child, int *errfd)
strerror(errno));
_exit(126);
}
execl(program, program, mapdir, t->missing, (char*)0);
execl(program, program, mapdir, (char*)0);
dprintf(STDERR_FILENO, "exec %s: %s\n", program, strerror(errno));
_exit(127);
}

View File

@@ -40,7 +40,6 @@ struct Test
char home[320];
char socket[384];
char badmap[320];
char missingfont[384];
char expected[768];
char err[4096];
size_t nerr;
@@ -156,8 +155,6 @@ setup(Test *t)
>= (int)sizeof t->socket ||
snprintf(t->badmap, sizeof t->badmap, "%s/missing-map", t->root)
>= (int)sizeof t->badmap ||
snprintf(t->missingfont, sizeof t->missingfont,
"%s/missing-font.ttf", t->root) >= (int)sizeof t->missingfont ||
snprintf(t->expected, sizeof t->expected,
"strans: can't open: %s/hira.map\n", t->badmap)
>= (int)sizeof t->expected)
@@ -213,7 +210,7 @@ startchild(Test *t, char *program)
strerror(errno));
_exit(126);
}
execl(program, program, t->badmap, t->missingfont, (char*)0);
execl(program, program, t->badmap, (char*)0);
dprintf(STDERR_FILENO, "exec %s: %s\n", program, strerror(errno));
_exit(127);
}

View File

@@ -67,7 +67,6 @@ struct Test
char bus[448];
char home[320];
char socket[384];
char missing[384];
char addrfile[512];
char addrbase[256];
char firstaddress[512];
@@ -221,9 +220,7 @@ setup(Test *t)
snprintf(t->home, sizeof t->home, "%s/home", t->root)
>= (int)sizeof t->home ||
snprintf(t->socket, sizeof t->socket, "%s/strans.sock", t->runtime)
>= (int)sizeof t->socket ||
snprintf(t->missing, sizeof t->missing, "%s/missing-font.ttf", t->root)
>= (int)sizeof t->missing)
>= (int)sizeof t->socket)
return fail("temporary path is too long");
if(!makedir(t->runtime) || !makedir(t->config) || !makedir(t->ibus) ||
!makedir(t->bus) || !makedir(t->home))
@@ -292,7 +289,7 @@ startchild(Test *t, char *program, char *mapdir, pid_t *child, int *errfd)
strerror(errno));
_exit(126);
}
execl(program, program, mapdir, t->missing, (char*)0);
execl(program, program, mapdir, (char*)0);
dprintf(STDERR_FILENO, "exec %s: %s\n", program, strerror(errno));
_exit(127);
}

View File

@@ -10,15 +10,6 @@ enum
};
static u32int guardcolor = 0x5a5a5a5a;
static char *testfonts[] = {
"/usr/share/fonts/noto/NotoColorEmoji.ttf",
"/usr/share/fonts/TTF/DejaVuSans.ttf",
"/usr/share/fonts/TTF/Jigmo.ttf",
"/usr/share/fonts/TTF/Jigmo2.ttf",
};
static char *missingfonts[] = {
"/strans-test-no-such-font",
};
static void
fillpixels(u32int *p, int n, u32int color)
@@ -195,14 +186,7 @@ font_render(struct ct *t)
buf = mem + Guard;
fillpixels(buf, Testn, Colbg);
a = mkstr("A");
CT_CHECK(t, !textinit(missingfonts, nelem(missingfonts)));
CT_EQ_INT(t, 0, textwidth(&a));
textdraw(buf, Testw, Testh, 0, 0, &a);
for(i = 0; i < Testn; i++)
CT_EQ_UINT(t, Colbg, buf[i]);
checkguards(t, mem);
if(!CT_CHECK(t, textinit(testfonts, nelem(testfonts)))){
if(!CT_CHECK(t, textinit())){
free(mem);
return;
}

View File

@@ -228,7 +228,6 @@ waitaddress(Daemon *d, int notifyfd)
static int
startdaemon(Daemon *d, char *program, char *mapdir)
{
char missing[384];
int fd, notifyfd, watch, errpipe[2];
long maxfd;
pid_t pid;
@@ -250,9 +249,7 @@ startdaemon(Daemon *d, char *program, char *mapdir)
snprintf(d->home, sizeof d->home, "%s/home", d->root)
>= (int)sizeof d->home ||
snprintf(d->socket, sizeof d->socket, "%s/strans.sock", d->runtime)
>= (int)sizeof d->socket ||
snprintf(missing, sizeof missing, "%s/missing-font.ttf", d->root)
>= (int)sizeof missing)
>= (int)sizeof d->socket)
return fail("temporary path is too long");
if(!makedir(d->runtime) || !makedir(d->config) || !makedir(d->ibus) ||
!makedir(d->bus) || !makedir(d->home))
@@ -299,7 +296,7 @@ startdaemon(Daemon *d, char *program, char *mapdir)
strerror(errno));
_exit(126);
}
execl(program, program, mapdir, missing, (char*)0);
execl(program, program, mapdir, (char*)0);
dprintf(STDERR_FILENO, "exec %s: %s\n", program, strerror(errno));
_exit(127);
}

View File

@@ -174,7 +174,6 @@ waitsocket(Daemon *d, int notifyfd)
static int
startdaemon(Daemon *d, char *program, char *mapdir)
{
char missing[384];
int fd, notifyfd, watch, errpipe[2];
long maxfd;
pid_t pid;
@@ -196,9 +195,7 @@ startdaemon(Daemon *d, char *program, char *mapdir)
snprintf(d->home, sizeof d->home, "%s/home", d->root)
>= (int)sizeof d->home ||
snprintf(d->socket, sizeof d->socket, "%s/strans.sock", d->runtime)
>= (int)sizeof d->socket ||
snprintf(missing, sizeof missing, "%s/no-such-font.ttf", d->root)
>= (int)sizeof missing)
>= (int)sizeof d->socket)
return fail("temporary path is too long");
if(!makedir(d->runtime) || !makedir(d->config) || !makedir(d->ibus) ||
!makedir(d->bus) || !makedir(d->home))
@@ -246,7 +243,7 @@ startdaemon(Daemon *d, char *program, char *mapdir)
strerror(errno));
_exit(126);
}
execl(program, program, mapdir, missing, (char*)0);
execl(program, program, mapdir, (char*)0);
dprintf(STDERR_FILENO, "exec %s: %s\n", program, strerror(errno));
_exit(127);
}

5
win.c
View File

@@ -6,9 +6,6 @@ enum {
Asciitofull = 0xFEE0,
};
extern char **fontfiles;
extern int nfontfiles;
static xcb_connection_t *conn;
static xcb_screen_t *scr;
static xcb_window_t win;
@@ -141,7 +138,7 @@ wininit(void)
mask = XCB_CW_BACK_PIXMAP;
xcb_change_window_attributes(conn, win, mask, &pix);
img = emalloc(Imgw * Imgh * sizeof(img[0]));
if(!textinit(fontfiles, nfontfiles)){
if(!textinit()){
fprint(2, "strans: popup disabled: no usable fonts\n");
wincleanup();
return 0;