renderer: use direct FreeType with explicit font files
This commit is contained in:
@@ -2,6 +2,7 @@ FROM archlinux:base@sha256:b0deabeb3d283da2c7f7dbf0eea051b7b2cd0554e0b737cc457fd
|
|||||||
|
|
||||||
RUN pacman -Syu --noconfirm --needed \
|
RUN pacman -Syu --noconfirm --needed \
|
||||||
diffutils \
|
diffutils \
|
||||||
|
freetype2 \
|
||||||
gcc \
|
gcc \
|
||||||
gtk3 \
|
gtk3 \
|
||||||
libxcb \
|
libxcb \
|
||||||
@@ -10,6 +11,8 @@ RUN pacman -Syu --noconfirm --needed \
|
|||||||
pkgconf \
|
pkgconf \
|
||||||
plan9port \
|
plan9port \
|
||||||
python \
|
python \
|
||||||
|
ttf-dejavu \
|
||||||
|
ttf-jigmo \
|
||||||
wayland \
|
wayland \
|
||||||
which \
|
which \
|
||||||
xcb-imdkit \
|
xcb-imdkit \
|
||||||
|
|||||||
8
Makefile
8
Makefile
@@ -2,6 +2,8 @@ CC = 9c
|
|||||||
LD = 9l
|
LD = 9l
|
||||||
PKG_CFLAGS = $(shell pkg-config --cflags dbus-1 wayland-client xkbcommon)
|
PKG_CFLAGS = $(shell pkg-config --cflags dbus-1 wayland-client xkbcommon)
|
||||||
PKG_LIBS = $(shell pkg-config --libs dbus-1 wayland-client xkbcommon)
|
PKG_LIBS = $(shell pkg-config --libs dbus-1 wayland-client xkbcommon)
|
||||||
|
FT_CFLAGS = $(shell pkg-config --cflags freetype2)
|
||||||
|
FT_LIBS = $(shell pkg-config --libs freetype2)
|
||||||
CFLAGS = -Wall -Wextra -O2 -g $(PKG_CFLAGS)
|
CFLAGS = -Wall -Wextra -O2 -g $(PKG_CFLAGS)
|
||||||
PROG = strans
|
PROG = strans
|
||||||
DOCKER_IMAGE = strans-build
|
DOCKER_IMAGE = strans-build
|
||||||
@@ -14,12 +16,10 @@ OBJS = $(SRCS:.c=.o)
|
|||||||
all: $(PROG) xim gtk
|
all: $(PROG) xim gtk
|
||||||
|
|
||||||
$(PROG): $(OBJS)
|
$(PROG): $(OBJS)
|
||||||
$(LD) -o $@ $(OBJS) -lthread -lbio -lxcb -lm $(PKG_LIBS)
|
$(LD) -o $@ $(OBJS) -lthread -lbio -lxcb $(PKG_LIBS) $(FT_LIBS)
|
||||||
|
|
||||||
$(OBJS): dat.h fn.h ipc.h
|
$(OBJS): dat.h fn.h ipc.h
|
||||||
# Avoid Arch's GLIBC_2.43 sqrtf optimization for older glibc runtimes.
|
font.o: CFLAGS += $(FT_CFLAGS)
|
||||||
font.o: CFLAGS += -fno-builtin-sqrt
|
|
||||||
font.o: stb_truetype.h
|
|
||||||
wayland.o: input-method-unstable-v2-client-protocol.h \
|
wayland.o: input-method-unstable-v2-client-protocol.h \
|
||||||
virtual-keyboard-unstable-v1-client-protocol.h
|
virtual-keyboard-unstable-v1-client-protocol.h
|
||||||
|
|
||||||
|
|||||||
19
README.md
19
README.md
@@ -99,12 +99,25 @@ tab-separated data consumable by the runtime dictionary reader.
|
|||||||
|
|
||||||
## Run and install
|
## Run and install
|
||||||
|
|
||||||
Start the daemon with the installed map and font directories:
|
Install one or more scalable outline fonts, then pass their files in fallback
|
||||||
|
order after the map directory. The daemon accepts at most four font files and
|
||||||
|
does not search the system font database. For example, the Docker-tested Arch
|
||||||
|
packages and paths are:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
./strans map font &
|
doas pacman -S ttf-dejavu ttf-jigmo
|
||||||
|
./strans map \
|
||||||
|
/usr/share/fonts/TTF/DejaVuSans.ttf \
|
||||||
|
/usr/share/fonts/TTF/Jigmo.ttf \
|
||||||
|
/usr/share/fonts/TTF/Jigmo2.ttf &
|
||||||
```
|
```
|
||||||
|
|
||||||
|
These fonts are test fixtures, not runtime requirements. Any suitable
|
||||||
|
scalable outline files may be supplied; their argument order is the glyph
|
||||||
|
fallback order. A collection file uses its first face. If any named file is
|
||||||
|
absent or invalid, the popup disables itself cleanly while input processing
|
||||||
|
continues. `run.sh` and `bench.sh` accept the same font-file arguments.
|
||||||
|
|
||||||
The IPC socket is placed at `$XDG_RUNTIME_DIR/strans.sock` when
|
The IPC socket is placed at `$XDG_RUNTIME_DIR/strans.sock` when
|
||||||
`XDG_RUNTIME_DIR` is an absolute path. Otherwise strans uses the per-user
|
`XDG_RUNTIME_DIR` is an absolute path. Otherwise strans uses the per-user
|
||||||
fallback `/tmp/strans.<uid>`. A second daemon will not replace a live
|
fallback `/tmp/strans.<uid>`. A second daemon will not replace a live
|
||||||
@@ -153,7 +166,7 @@ and disconnect affect the engine only when they come from its active owner;
|
|||||||
stale events cannot clear a newer owner's preedit.
|
stale events cannot clear a newer owner's preedit.
|
||||||
|
|
||||||
The popup is an optional X window, including when the active frontend is
|
The popup is an optional X window, including when the active frontend is
|
||||||
Wayland. It uses deterministic font fallback and fixed-width rune cells;
|
Wayland. It uses the explicit font-file order and fixed-width rune cells;
|
||||||
U+FE0E and U+FE0F variation selectors use zero cells. It does not perform
|
U+FE0E and U+FE0F variation selectors use zero cells. It does not perform
|
||||||
OpenType shaping, color-emoji rendering, or general grapheme clustering, and
|
OpenType shaping, color-emoji rendering, or general grapheme clustering, and
|
||||||
there is no native Wayland popup renderer. Multi-rune emoji are committed
|
there is no native Wayland popup renderer. Multi-rune emoji are committed
|
||||||
|
|||||||
6
bench.sh
6
bench.sh
@@ -22,14 +22,14 @@ cleanup()
|
|||||||
|
|
||||||
trap cleanup 0 1 2 15
|
trap cleanup 0 1 2 15
|
||||||
|
|
||||||
./strans map font &
|
./strans map "$@" &
|
||||||
strans_pid=$!
|
strans_pid=$!
|
||||||
sleep 1
|
sleep 1
|
||||||
kill -0 "$strans_pid"
|
kill -0 "$strans_pid"
|
||||||
|
|
||||||
# warm up glyph cache
|
# warm up the renderer
|
||||||
./bench/bench bench/bench.keys 100
|
./bench/bench bench/bench.keys 100
|
||||||
echo "cache warmed up"
|
echo "renderer warmed up"
|
||||||
|
|
||||||
perf record -g -o bench/perf.data -p "$strans_pid" &
|
perf record -g -o bench/perf.data -p "$strans_pid" &
|
||||||
perf_pid=$!
|
perf_pid=$!
|
||||||
|
|||||||
2
dat.h
2
dat.h
@@ -18,7 +18,7 @@ enum
|
|||||||
LangVI = 0x16,
|
LangVI = 0x16,
|
||||||
|
|
||||||
Fontsz = 32,
|
Fontsz = 32,
|
||||||
Nglyphs = 0x20000,
|
Maxfonts = 4,
|
||||||
Maxrunes = 64,
|
Maxrunes = 64,
|
||||||
Maxutf = Maxrunes * UTFmax + 1,
|
Maxutf = Maxrunes * UTFmax + 1,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ identifiers are:
|
|||||||
- input SHA-256: `dd44dcc856cf542b1022d0f39c2e9b9f8805fdcc5923be80f04849ed97ce0996`
|
- input SHA-256: `dd44dcc856cf542b1022d0f39c2e9b9f8805fdcc5923be80f04849ed97ce0996`
|
||||||
|
|
||||||
`map/libhangul2hanja` keeps only a one-syllable modern Hangul reading paired
|
`map/libhangul2hanja` keeps only a one-syllable modern Hangul reading paired
|
||||||
with one BMP Hanja rune supported by the popup. It writes one
|
with one BMP Hanja rune. It writes one
|
||||||
`Hanja<TAB>Hangul syllable` pair per row in upstream order; word entries are
|
`Hanja<TAB>Hangul syllable` pair per row in upstream order; word entries are
|
||||||
not part of this data. `map/mkhanja` groups those rows by reading for the
|
not part of this data. `map/mkhanja` groups those rows by reading for the
|
||||||
runtime dictionary without changing candidate order, retaining the first 32
|
runtime dictionary without changing candidate order, retaining the first 32
|
||||||
|
|||||||
2
fn.h
2
fn.h
@@ -55,5 +55,5 @@ int wlstaterepeat(Wlstate*, uvlong, u32int*);
|
|||||||
void* emalloc(ulong);
|
void* emalloc(ulong);
|
||||||
void* erealloc(void*, ulong);
|
void* erealloc(void*, ulong);
|
||||||
|
|
||||||
int fontinit(char*);
|
int fontinit(char**, int);
|
||||||
void putfont(u32int*, int, int, int, int, Rune);
|
void putfont(u32int*, int, int, int, int, Rune);
|
||||||
|
|||||||
209
font.c
209
font.c
@@ -1,29 +1,18 @@
|
|||||||
#define STB_TRUETYPE_IMPLEMENTATION
|
|
||||||
#include "stb_truetype.h"
|
|
||||||
#include "dat.h"
|
#include "dat.h"
|
||||||
|
#include <ft2build.h>
|
||||||
|
#include FT_FREETYPE_H
|
||||||
#include "fn.h"
|
#include "fn.h"
|
||||||
|
|
||||||
typedef struct Glyph Glyph;
|
|
||||||
typedef struct Font Font;
|
typedef struct Font Font;
|
||||||
struct Glyph
|
|
||||||
{
|
|
||||||
uchar *bmp;
|
|
||||||
int w, h, ox, oy, base;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Font
|
struct Font
|
||||||
{
|
{
|
||||||
stbtt_fontinfo info;
|
FT_Face face;
|
||||||
uchar *data;
|
|
||||||
float scale;
|
|
||||||
int base;
|
int base;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum { Maxfonts = 4 };
|
static FT_Library lib;
|
||||||
|
|
||||||
static Font fonts[Maxfonts];
|
static Font fonts[Maxfonts];
|
||||||
static int nfonts;
|
static int nfonts;
|
||||||
static Glyph cache[Nglyphs];
|
|
||||||
static u32int blendtab[2][256];
|
static u32int blendtab[2][256];
|
||||||
|
|
||||||
static u32int
|
static u32int
|
||||||
@@ -41,117 +30,139 @@ blend(u32int bg, u32int fg, int a)
|
|||||||
static int
|
static int
|
||||||
loadfont(char *path)
|
loadfont(char *path)
|
||||||
{
|
{
|
||||||
|
FT_Face face;
|
||||||
Font *f;
|
Font *f;
|
||||||
int ascent, descent, gap, fd;
|
vlong height, pixels;
|
||||||
long sz, n;
|
|
||||||
|
|
||||||
fd = open(path, OREAD);
|
if(nfonts >= nelem(fonts))
|
||||||
if(fd < 0){
|
return 0;
|
||||||
fprint(2, "strans: popup: can't open font: %s\n", path);
|
if(FT_New_Face(lib, path, 0, &face) != 0){
|
||||||
|
fprint(2, "strans: popup: can't load font: %s\n", path);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
sz = seek(fd, 0, 2);
|
height = (vlong)face->ascender - face->descender;
|
||||||
seek(fd, 0, 0);
|
if(height <= 0 || face->units_per_EM == 0){
|
||||||
if(sz <= 0){
|
fprint(2, "strans: popup: invalid font metrics: %s\n", path);
|
||||||
fprint(2, "strans: popup: empty font: %s\n", path);
|
FT_Done_Face(face);
|
||||||
close(fd);
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
f = &fonts[nfonts];
|
f = &fonts[nfonts];
|
||||||
f->data = emalloc(sz);
|
f->face = face;
|
||||||
n = readn(fd, f->data, sz);
|
f->base = (vlong)Fontsz * face->ascender / height;
|
||||||
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++;
|
nfonts++;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
static void
|
||||||
fontinit(char *dir)
|
clearfonts(void)
|
||||||
{
|
{
|
||||||
static char *names[] = {
|
int i;
|
||||||
"NotoSans-Regular.ttf",
|
|
||||||
"NotoSansMonoCJKjp-Regular.otf",
|
|
||||||
"NotoEmoji-Regular.ttf",
|
|
||||||
};
|
|
||||||
int i, a;
|
|
||||||
char path[256];
|
|
||||||
|
|
||||||
|
for(i = 0; i < nfonts; i++){
|
||||||
|
FT_Done_Face(fonts[i].face);
|
||||||
|
fonts[i].face = nil;
|
||||||
|
}
|
||||||
nfonts = 0;
|
nfonts = 0;
|
||||||
memset(cache, 0, sizeof cache);
|
}
|
||||||
for(i = 0; i < nelem(names); i++){
|
|
||||||
if(strlen(dir) + 1 + strlen(names[i]) + 1 > sizeof path){
|
int
|
||||||
fprint(2, "strans: popup: font path is too long\n");
|
fontinit(char **path, int npath)
|
||||||
continue;
|
{
|
||||||
|
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++){
|
for(a = 0; a < 256; a++){
|
||||||
blendtab[0][a] = blend(Colbg, Colfg, a);
|
blendtab[0][a] = blend(Colbg, Colfg, a);
|
||||||
blendtab[1][a] = blend(Colsel, 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
|
void
|
||||||
putfont(u32int *buf, int w, int h, int px, int py, Rune r)
|
putfont(u32int *buf, int w, int h, int px, int py, Rune r)
|
||||||
{
|
{
|
||||||
Glyph *g;
|
FT_UInt g;
|
||||||
int i, j, a, sel, f;
|
int f;
|
||||||
int y0, j0, j1, x0, i0, i1;
|
|
||||||
u32int *p;
|
|
||||||
|
|
||||||
if(r >= Nglyphs)
|
if(buf == nil || w <= 0 || h <= 0)
|
||||||
return;
|
return;
|
||||||
g = &cache[r];
|
for(f = 0; f < nfonts; f++){
|
||||||
if(g->bmp == nil){
|
g = FT_Get_Char_Index(fonts[f].face, r);
|
||||||
for(f = 0; f < nfonts; f++){
|
if(g == 0 || FT_Load_Glyph(fonts[f].face, g, FT_LOAD_DEFAULT) != 0 ||
|
||||||
if(stbtt_FindGlyphIndex(&fonts[f].info, r) == 0)
|
FT_Render_Glyph(fonts[f].face->glyph, FT_RENDER_MODE_NORMAL) != 0)
|
||||||
continue;
|
continue;
|
||||||
g->bmp = stbtt_GetCodepointBitmap(&fonts[f].info,
|
if(drawglyph(&fonts[f], buf, w, h, px, py))
|
||||||
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;
|
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];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
10
main.c
10
main.c
@@ -5,12 +5,13 @@ Channel *drawc;
|
|||||||
Channel *keyc;
|
Channel *keyc;
|
||||||
Channel *dictreqc;
|
Channel *dictreqc;
|
||||||
Channel *dictresc;
|
Channel *dictresc;
|
||||||
char *fontdir;
|
char **fontfiles;
|
||||||
|
int nfontfiles;
|
||||||
|
|
||||||
void
|
void
|
||||||
usage(void)
|
usage(void)
|
||||||
{
|
{
|
||||||
fprint(2, "usage: strans mapdir fontdir\n");
|
fprint(2, "usage: strans mapdir fontfile [fontfile ...]\n");
|
||||||
threadexitsall("usage");
|
threadexitsall("usage");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,10 +51,11 @@ erealloc(void *p, ulong n)
|
|||||||
void
|
void
|
||||||
threadmain(int argc, char **argv)
|
threadmain(int argc, char **argv)
|
||||||
{
|
{
|
||||||
if(argc != 3)
|
if(argc < 3 || argc > Maxfonts + 2)
|
||||||
usage();
|
usage();
|
||||||
|
|
||||||
fontdir = argv[2];
|
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);
|
||||||
|
|||||||
2
run.sh
2
run.sh
@@ -3,6 +3,6 @@
|
|||||||
cd "$(dirname "$0")"
|
cd "$(dirname "$0")"
|
||||||
pkill strans
|
pkill strans
|
||||||
sleep 1
|
sleep 1
|
||||||
./strans map font &
|
./strans map "$@" &
|
||||||
sleep 1
|
sleep 1
|
||||||
xim/strans-xim &
|
xim/strans-xim &
|
||||||
|
|||||||
5079
stb_truetype.h
5079
stb_truetype.h
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,17 @@
|
|||||||
CC = 9c
|
CC = 9c
|
||||||
LD = 9l
|
LD = 9l
|
||||||
CFLAGS = -std=c99 -Wall -Wextra -O2 -g -I.. -I../cutest
|
CFLAGS = -std=c99 -Wall -Wextra -O2 -g -I.. -I../cutest
|
||||||
LIBS = -lthread -lbio
|
FT_CFLAGS = $(shell pkg-config --cflags freetype2)
|
||||||
|
FT_LIBS = $(shell pkg-config --libs freetype2)
|
||||||
|
LIBS = -lthread -lbio $(FT_LIBS)
|
||||||
|
|
||||||
PROG = unit_test
|
PROG = unit_test
|
||||||
TESTSRC = unit_test.c test_util.c str_test.c hash_test.c trie_test.c \
|
TESTSRC = unit_test.c test_util.c str_test.c hash_test.c trie_test.c \
|
||||||
ko_test.c vi_test.c engine_test.c dict_test.c ipc_test.c wayland_test.c \
|
ko_test.c vi_test.c engine_test.c dict_test.c ipc_test.c wayland_test.c \
|
||||||
popup_test.c
|
popup_test.c font_test.c
|
||||||
TESTOBJ = $(TESTSRC:.c=.o)
|
TESTOBJ = $(TESTSRC:.c=.o)
|
||||||
PARENTSRC = str.c hash.c trie.c dict.c ko.c vi.c ipc.c wayland_state.c \
|
PARENTSRC = str.c hash.c trie.c dict.c ko.c vi.c ipc.c wayland_state.c \
|
||||||
popup_layout.c
|
popup_layout.c font.c
|
||||||
PARENTOBJ = $(PARENTSRC:%.c=unit_%.o)
|
PARENTOBJ = $(PARENTSRC:%.c=unit_%.o)
|
||||||
OBJS = $(TESTOBJ) $(PARENTOBJ)
|
OBJS = $(TESTOBJ) $(PARENTOBJ)
|
||||||
|
|
||||||
@@ -23,6 +25,7 @@ $(PROG): $(OBJS)
|
|||||||
|
|
||||||
$(TESTOBJ): test.h ../dat.h ../fn.h ../ipc.h ../cutest/cutest.h
|
$(TESTOBJ): test.h ../dat.h ../fn.h ../ipc.h ../cutest/cutest.h
|
||||||
engine_test.o: ../strans.c
|
engine_test.o: ../strans.c
|
||||||
|
unit_font.o: CFLAGS += $(FT_CFLAGS)
|
||||||
|
|
||||||
unit_%.o: ../%.c ../dat.h ../fn.h ../ipc.h
|
unit_%.o: ../%.c ../dat.h ../fn.h ../ipc.h
|
||||||
$(CC) $(CFLAGS) -c -o $@ $<
|
$(CC) $(CFLAGS) -c -o $@ $<
|
||||||
|
|||||||
157
tests/font_test.c
Normal file
157
tests/font_test.c
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
#include "test.h"
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
Guard = 8,
|
||||||
|
Testw = 3 * Fontsz,
|
||||||
|
Testh = 3 * Fontsz,
|
||||||
|
Testn = Testw * Testh,
|
||||||
|
Missingn = Fontsz * Fontsz,
|
||||||
|
};
|
||||||
|
|
||||||
|
static u32int guardcolor = 0x5a5a5a5a;
|
||||||
|
static char *testfonts[] = {
|
||||||
|
"/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)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for(i = 0; i < n; i++)
|
||||||
|
p[i] = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
hasink(u32int *p, int n)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for(i = 0; i < n; i++)
|
||||||
|
if(p[i] != Colbg)
|
||||||
|
return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
renders(Rune r)
|
||||||
|
{
|
||||||
|
u32int buf[Fontsz * Fontsz];
|
||||||
|
|
||||||
|
fillpixels(buf, nelem(buf), Colbg);
|
||||||
|
putfont(buf, Fontsz, Fontsz, 0, 0, r);
|
||||||
|
return hasink(buf, nelem(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
inkbounds(u32int *p, int *minx, int *miny, int *maxx, int *maxy)
|
||||||
|
{
|
||||||
|
int x, y;
|
||||||
|
|
||||||
|
*minx = Testw;
|
||||||
|
*miny = Testh;
|
||||||
|
*maxx = -1;
|
||||||
|
*maxy = -1;
|
||||||
|
for(y = 0; y < Testh; y++){
|
||||||
|
for(x = 0; x < Testw; x++){
|
||||||
|
if(p[y * Testw + x] == Colbg)
|
||||||
|
continue;
|
||||||
|
*minx = min(*minx, x);
|
||||||
|
*miny = min(*miny, y);
|
||||||
|
*maxx = max(*maxx, x);
|
||||||
|
*maxy = max(*maxy, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return *maxx >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
checkclip(struct ct *t, u32int *ref, int dx, int dy)
|
||||||
|
{
|
||||||
|
u32int *buf, *mem, *want;
|
||||||
|
int i, sx, sy, x, y;
|
||||||
|
|
||||||
|
mem = emalloc((Testn + 2 * Guard) * sizeof mem[0]);
|
||||||
|
want = emalloc(Testn * sizeof want[0]);
|
||||||
|
fillpixels(mem, Testn + 2 * Guard, guardcolor);
|
||||||
|
buf = mem + Guard;
|
||||||
|
fillpixels(buf, Testn, Colbg);
|
||||||
|
fillpixels(want, Testn, Colbg);
|
||||||
|
for(y = 0; y < Testh; y++){
|
||||||
|
for(x = 0; x < Testw; x++){
|
||||||
|
sx = x - dx;
|
||||||
|
sy = y - dy;
|
||||||
|
if(sx >= 0 && sx < Testw && sy >= 0 && sy < Testh)
|
||||||
|
want[y * Testw + x] = ref[sy * Testw + sx];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
putfont(buf, Testw, Testh, Fontsz + dx, Fontsz + dy, 'A');
|
||||||
|
CT_CHECK(t, hasink(buf, Testn));
|
||||||
|
CT_EQ_MEM(t, want, buf, Testn * sizeof buf[0]);
|
||||||
|
for(i = 0; i < Guard; i++){
|
||||||
|
CT_EQ_UINT(t, guardcolor, mem[i]);
|
||||||
|
CT_EQ_UINT(t, guardcolor, mem[Guard + Testn + i]);
|
||||||
|
}
|
||||||
|
free(want);
|
||||||
|
free(mem);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
font_render(struct ct *t)
|
||||||
|
{
|
||||||
|
u32int *buf, *missing, *ref;
|
||||||
|
int i, minx, miny, maxx, maxy;
|
||||||
|
|
||||||
|
missing = emalloc((Missingn + 2 * Guard) * sizeof missing[0]);
|
||||||
|
fillpixels(missing, Missingn + 2 * Guard, guardcolor);
|
||||||
|
buf = missing + Guard;
|
||||||
|
fillpixels(buf, Missingn, Colbg);
|
||||||
|
CT_CHECK(t, !fontinit(missingfonts, nelem(missingfonts)));
|
||||||
|
putfont(buf, Fontsz, Fontsz, 0, 0, 'A');
|
||||||
|
for(i = 0; i < Missingn; i++)
|
||||||
|
CT_EQ_UINT(t, Colbg, buf[i]);
|
||||||
|
for(i = 0; i < Guard; i++){
|
||||||
|
CT_EQ_UINT(t, guardcolor, missing[i]);
|
||||||
|
CT_EQ_UINT(t, guardcolor, missing[Guard + Missingn + i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!CT_CHECK(t, fontinit(testfonts, nelem(testfonts)))){
|
||||||
|
free(missing);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CT_CHECK(t, renders('A'));
|
||||||
|
CT_CHECK(t, renders(0xac00));
|
||||||
|
CT_CHECK(t, renders(0x4e00));
|
||||||
|
CT_CHECK(t, renders(0x1f600));
|
||||||
|
CT_CHECK(t, renders(0x2000b));
|
||||||
|
fillpixels(missing, Missingn + 2 * Guard, guardcolor);
|
||||||
|
buf = missing + Guard;
|
||||||
|
fillpixels(buf, Missingn, Colbg);
|
||||||
|
putfont(buf, Fontsz, Fontsz, 0, 0, 0x10ffff);
|
||||||
|
for(i = 0; i < Missingn; i++)
|
||||||
|
CT_EQ_UINT(t, Colbg, buf[i]);
|
||||||
|
for(i = 0; i < Guard; i++){
|
||||||
|
CT_EQ_UINT(t, guardcolor, missing[i]);
|
||||||
|
CT_EQ_UINT(t, guardcolor, missing[Guard + Missingn + i]);
|
||||||
|
}
|
||||||
|
free(missing);
|
||||||
|
|
||||||
|
ref = emalloc(Testn * sizeof ref[0]);
|
||||||
|
fillpixels(ref, Testn, Colbg);
|
||||||
|
putfont(ref, Testw, Testh, Fontsz, Fontsz, 'A');
|
||||||
|
if(!CT_CHECK(t, inkbounds(ref, &minx, &miny, &maxx, &maxy))){
|
||||||
|
free(ref);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
checkclip(t, ref, -minx - 1, 0);
|
||||||
|
checkclip(t, ref, Testw - maxx, 0);
|
||||||
|
checkclip(t, ref, 0, -miny - 1);
|
||||||
|
checkclip(t, ref, 0, Testh - maxy);
|
||||||
|
free(ref);
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ void wayland_press_release_state(struct ct*);
|
|||||||
void wayland_repeat_state(struct ct*);
|
void wayland_repeat_state(struct ct*);
|
||||||
void wayland_repeat_cancellation(struct ct*);
|
void wayland_repeat_cancellation(struct ct*);
|
||||||
void popup_layout(struct ct*);
|
void popup_layout(struct ct*);
|
||||||
|
void font_render(struct ct*);
|
||||||
void production_maps_load(struct ct*);
|
void production_maps_load(struct ct*);
|
||||||
void transmap_states(struct ct*);
|
void transmap_states(struct ct*);
|
||||||
void korean_sequences(struct ct*);
|
void korean_sequences(struct ct*);
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ static const struct ct_test tests[] = {
|
|||||||
{ "wayland/repeat", wayland_repeat_state },
|
{ "wayland/repeat", wayland_repeat_state },
|
||||||
{ "wayland/repeat-cancellation", wayland_repeat_cancellation },
|
{ "wayland/repeat-cancellation", wayland_repeat_cancellation },
|
||||||
{ "popup/layout", popup_layout },
|
{ "popup/layout", popup_layout },
|
||||||
|
{ "font/render", font_render },
|
||||||
{ "map/production-lifecycle", production_maps_load },
|
{ "map/production-lifecycle", production_maps_load },
|
||||||
{ "transmap/states", transmap_states },
|
{ "transmap/states", transmap_states },
|
||||||
{ "hangul/sequences", korean_sequences },
|
{ "hangul/sequences", korean_sequences },
|
||||||
|
|||||||
5
win.c
5
win.c
@@ -6,7 +6,8 @@ enum {
|
|||||||
Asciitofull = 0xFEE0,
|
Asciitofull = 0xFEE0,
|
||||||
};
|
};
|
||||||
|
|
||||||
extern char *fontdir;
|
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;
|
||||||
@@ -140,7 +141,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(!fontinit(fontdir)){
|
if(!fontinit(fontfiles, nfontfiles)){
|
||||||
fprint(2, "strans: popup disabled: no usable fonts\n");
|
fprint(2, "strans: popup disabled: no usable fonts\n");
|
||||||
wincleanup();
|
wincleanup();
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user