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 ## Run
The popup needs one to four font files. No fonts are bundled. Each file is The popup uses the normal system fonts found by Pango and Fontconfig; no font
validated and registered with Pango as an application font. Pango shapes the file argument or system-wide strans installation is required. Run it directly
complete string and chooses glyph fallback from those fonts and the normal from the build tree:
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:
```sh ```sh
./run.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 Pango shapes the complete string and selects glyph fallback from the system
newer. font set.
The script starts the daemon and, when `DISPLAY` is set, the XIM frontend. It The popup renderer requires Pango/PangoCairo 1.56 or newer and Cairo 1.18 or
restarts any existing `strans` processes owned by the current user. 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: Configure clients as needed:

1
dat.h
View File

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

View File

@@ -46,14 +46,11 @@ new SKK revision.
## Fonts ## Fonts
No font binaries are bundled. The daemon receives one to four font file paths No font binaries are bundled. The daemon asks Pango and Fontconfig to select
on its command line, validates them, and registers them as Pango application from the user's normal system font set. Pango shapes each complete string and
fonts. Pango shapes each complete string and selects glyph fallback from the selects glyph fallback. `font/PROVENANCE` retains the exact checksums, source
registered fonts and the normal system font set. Exact command-line file order revisions, and license records for the three Noto faces that were formerly
is deliberately not promised: preserving it would require a separate stored in the repository.
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.
The Docker renderer tests obtain their fonts from signed Arch packages rather 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: 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* emalloc(ulong);
void* erealloc(void*, ulong); void* erealloc(void*, ulong);
int textinit(char**, int); int textinit(void);
int textwidth(Str*); int textwidth(Str*);
void textdraw(u32int*, int, int, int, int, Str*); void textdraw(u32int*, int, int, int, int, Str*);

25
font.c
View File

@@ -43,37 +43,14 @@ setfont(void)
} }
int int
textinit(char **path, int npath) textinit(void)
{ {
GError *err;
int i;
textclear(); 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(); fontmap = pango_cairo_font_map_new();
if(fontmap == nil){ if(fontmap == nil){
fprint(2, "strans: popup: can't initialize PangoCairo\n"); fprint(2, "strans: popup: can't initialize PangoCairo\n");
return 0; 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); pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(fontmap), 96);
context = pango_font_map_create_context(fontmap); context = pango_font_map_create_context(fontmap);
if(context != nil) if(context != nil)

8
main.c
View File

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

21
run.sh
View File

@@ -2,22 +2,9 @@
cd "$(dirname "$0")" || exit 1 cd "$(dirname "$0")" || exit 1
if test "$#" -eq 0; then if test "$#" -ne 0; then
set -- echo "usage: run.sh" >&2
for font in \ exit 1
/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
fi fi
if ! test -x ./strans; then 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-xim 2>/dev/null || :
pkill -KILL -u "$uid" -x strans 2>/dev/null || : pkill -KILL -u "$uid" -x strans 2>/dev/null || :
./strans map "$@" </dev/null & ./strans map </dev/null &
strans_pid=$! strans_pid=$!
if test -n "${DISPLAY-}"; then if test -n "${DISPLAY-}"; then
xim/strans-xim </dev/null & xim/strans-xim </dev/null &

View File

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

View File

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

View File

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

View File

@@ -10,15 +10,6 @@ enum
}; };
static u32int guardcolor = 0x5a5a5a5a; 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 static void
fillpixels(u32int *p, int n, u32int color) fillpixels(u32int *p, int n, u32int color)
@@ -195,14 +186,7 @@ font_render(struct ct *t)
buf = mem + Guard; buf = mem + Guard;
fillpixels(buf, Testn, Colbg); fillpixels(buf, Testn, Colbg);
a = mkstr("A"); a = mkstr("A");
CT_CHECK(t, !textinit(missingfonts, nelem(missingfonts))); if(!CT_CHECK(t, textinit())){
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)))){
free(mem); free(mem);
return; return;
} }

View File

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

View File

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

5
win.c
View File

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