popup: use system fonts exclusively
This commit is contained in:
24
README.md
24
README.md
@@ -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
1
dat.h
@@ -18,7 +18,6 @@ enum
|
||||
LangVI = 0x16,
|
||||
|
||||
Fontsz = 32,
|
||||
Maxfonts = 4,
|
||||
Maxrunes = 64,
|
||||
Maxutf = Maxrunes * UTFmax + 1,
|
||||
};
|
||||
|
||||
@@ -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
2
fn.h
@@ -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
25
font.c
@@ -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
8
main.c
@@ -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
21
run.sh
@@ -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 &
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
5
win.c
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user