popup: replace FreeType renderer with PangoCairo

This commit is contained in:
2026-08-14 14:37:30 +09:00
parent 279b9230a4
commit 54355b8190
12 changed files with 349 additions and 266 deletions

View File

@@ -1,13 +1,17 @@
FROM archlinux:base@sha256:b0deabeb3d283da2c7f7dbf0eea051b7b2cd0554e0b737cc457fd21683bdcdd1
RUN pacman -Syu --noconfirm --needed \
cairo \
fontconfig \
diffutils \
freetype2 \
gcc \
gtk3 \
harfbuzz \
libxcb \
libxkbcommon \
make \
noto-fonts-emoji \
pango \
pkgconf \
plan9port \
python \

View File

@@ -2,8 +2,8 @@ CC = 9c
LD = 9l
PKG_CFLAGS = $(shell pkg-config --cflags dbus-1 xkbcommon)
PKG_LIBS = $(shell pkg-config --libs dbus-1 xkbcommon)
FT_CFLAGS = $(shell pkg-config --cflags freetype2)
FT_LIBS = $(shell pkg-config --libs freetype2)
TEXT_CFLAGS = $(shell pkg-config --cflags 'pangocairo >= 1.56' 'cairo >= 1.18')
TEXT_LIBS = $(shell pkg-config --libs 'pangocairo >= 1.56' 'cairo >= 1.18')
CFLAGS = -Wall -Wextra -O2 -g $(PKG_CFLAGS)
PROG = strans
DOCKER_IMAGE = strans-build
@@ -17,10 +17,10 @@ OBJS = $(SRCS:.c=.o)
all: $(PROG) xim gtk
$(PROG): $(OBJS)
$(LD) -o $@ $(OBJS) -lthread -lbio -lxcb $(PKG_LIBS) $(FT_LIBS)
$(LD) -o $@ $(OBJS) -lthread -lbio -lxcb $(PKG_LIBS) $(TEXT_LIBS)
$(OBJS): dat.h fn.h ipc.h
font.o: CFLAGS += $(FT_CFLAGS)
font.o: CFLAGS += $(TEXT_CFLAGS)
clean:
rm -f $(OBJS) $(PROG)

View File

@@ -51,15 +51,24 @@ dependencies listed in [`Dockerfile`](Dockerfile) are installed.
## Run
The popup needs one to four scalable font files. No fonts are bundled.
`run.sh` uses installed DejaVu, Jigmo, or Noto CJK fonts when available, or
accepts explicit files in fallback order:
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:
```sh
./run.sh
./run.sh /path/to/primary.ttf /path/to/fallback.ttc
```
The popup renderer requires Pango/PangoCairo 1.56 or newer and Cairo 1.18 or
newer.
The script starts the daemon and, when `DISPLAY` is set, the XIM frontend. It
restarts any existing `strans` processes owned by the current user.

View File

@@ -47,14 +47,20 @@ new SKK revision.
## Fonts
No font binaries are bundled. The daemon receives one to four font file paths
on its command line and tries them in that order; it does not discover fonts by
family name or directory contents. `font/PROVENANCE` retains the exact
checksums, source revisions, and license records for the three Noto faces that
were formerly stored in the repository.
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.
The Docker renderer tests obtain their fonts from signed Arch packages rather
than the source checkout. On August 13, 2026 the tested package contract was:
than the source checkout. On August 14, 2026 the tested package contract was:
- `noto-fonts-emoji` `1:2.051-1`, providing
`/usr/share/fonts/noto/NotoColorEmoji.ttf` (SHA-256
`72a635cb3d2f3524c51620cdde406b217204e8a6a06c6a096ff8ed4b5fd6e27b`);
- `ttf-dejavu` `2.37+18+g9b5d1b2f-8`, providing
`/usr/share/fonts/TTF/DejaVuSans.ttf` (SHA-256
`6038a160b491e121c1f12c7bccb4a9c8730296e3adc1086a059404ed84b7451c`);
@@ -64,13 +70,14 @@ than the source checkout. On August 13, 2026 the tested package contract was:
and `/usr/share/fonts/TTF/Jigmo2.ttf` (SHA-256
`5da3582efe77e22073b86b3b86b556d7111148a76b957cfb53318a91da2efff0`).
Pacman validates the package checksums and repository signatures. DejaVu Sans
supplies ASCII and the monochrome U+1F600 outline in the tests; Jigmo supplies
Hangul and BMP CJK, and Jigmo2 supplies U+2000B. Their installed license files
are `/usr/share/licenses/ttf-dejavu/LICENSE` and
`/usr/share/licenses/ttf-jigmo/LICENSE.txt`; the packages identify their terms
as the DejaVu custom license and CC0 1.0, respectively. These packages are
test fixtures, not names or paths hard-coded into the daemon.
Pacman validates the package checksums and repository signatures. Noto Color
Emoji supplies the color and shaped-sequence coverage; DejaVu Sans supplies
ASCII, Jigmo supplies Hangul and BMP CJK, and Jigmo2 supplies U+2000B. Their
installed license files are `/usr/share/licenses/noto-fonts-emoji/LICENSE`,
`/usr/share/licenses/ttf-dejavu/LICENSE`, and
`/usr/share/licenses/ttf-jigmo/LICENSE.txt`. The packages identify their terms
as OFL-1.1-no-RFN, the DejaVu custom license, and CC0 1.0, respectively. These
packages are test fixtures, not names or paths hard-coded into the daemon.
## Git submodules

6
fn.h
View File

@@ -28,7 +28,6 @@ void dictthread(void*);
void dictlookup(Dictreq*, Dictres*);
void drawthread(void*);
int popupcells(Rune*, int);
void popupposition(Caret*, int, int, int, int, int, int, int*, int*);
void imthread(void*);
Emit transmap(Im*, Rune);
@@ -44,5 +43,6 @@ void ibusthread(void*);
void* emalloc(ulong);
void* erealloc(void*, ulong);
int fontinit(char**, int);
void putfont(u32int*, int, int, int, int, Rune);
int textinit(char**, int);
int textwidth(Str*);
void textdraw(u32int*, int, int, int, int, Str*);

254
font.c
View File

@@ -1,168 +1,166 @@
#include "dat.h"
#include <ft2build.h>
#include FT_FREETYPE_H
#include <pango/pangocairo.h>
#include "fn.h"
typedef struct Font Font;
struct Font
static PangoFontMap *fontmap;
static PangoContext *context;
static PangoLayout *layout;
static void
textclear(void)
{
FT_Face face;
int base;
};
static FT_Library lib;
static Font fonts[Maxfonts];
static int nfonts;
static u32int blendtab[2][256];
static u32int
blend(u32int bg, u32int fg, int a)
{
int r, g, b, inv;
inv = 255 - a;
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 int
loadfont(char *path)
{
FT_Face face;
Font *f;
vlong height, pixels;
if(nfonts >= nelem(fonts))
return 0;
if(FT_New_Face(lib, path, 0, &face) != 0){
fprint(2, "strans: popup: can't load font: %s\n", path);
return 0;
}
height = (vlong)face->ascender - face->descender;
if(height <= 0 || face->units_per_EM == 0){
fprint(2, "strans: popup: invalid font metrics: %s\n", path);
FT_Done_Face(face);
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;
}
f = &fonts[nfonts];
f->face = face;
f->base = (vlong)Fontsz * face->ascender / height;
nfonts++;
return 1;
if(layout != nil)
g_object_unref(layout);
if(context != nil)
g_object_unref(context);
if(fontmap != nil)
g_object_unref(fontmap);
layout = nil;
context = nil;
fontmap = nil;
}
static void
clearfonts(void)
setfont(void)
{
int i;
PangoFontDescription *font;
PangoRectangle r;
int size;
for(i = 0; i < nfonts; i++){
FT_Done_Face(fonts[i].face);
fonts[i].face = nil;
font = pango_font_description_new();
pango_font_description_set_family(font, "sans");
pango_font_description_set_absolute_size(font, Fontsz * PANGO_SCALE);
pango_layout_set_font_description(layout, font);
pango_layout_set_text(layout, "Mg", -1);
pango_layout_get_pixel_extents(layout, nil, &r);
if(r.height > 0){
/* Fontsz is the popup row height, not a point size. */
size = Fontsz * PANGO_SCALE * Fontsz / r.height;
pango_font_description_set_absolute_size(font, max(size, PANGO_SCALE));
pango_layout_set_font_description(layout, font);
}
nfonts = 0;
pango_font_description_free(font);
}
int
fontinit(char **path, int npath)
textinit(char **path, int npath)
{
int i, a;
GError *err;
int i;
if(path == nil || npath < 1 || npath > nelem(fonts)){
fprint(2, "strans: popup: need 1-%d font files\n", nelem(fonts));
clearfonts();
textclear();
if(path == nil || npath < 1 || npath > Maxfonts){
fprint(2, "strans: popup: need 1-%d font files\n", Maxfonts);
return 0;
}
if(lib == nil && FT_Init_FreeType(&lib) != 0){
fprint(2, "strans: popup: can't initialize FreeType\n");
fontmap = pango_cairo_font_map_new();
if(fontmap == nil){
fprint(2, "strans: popup: can't initialize PangoCairo\n");
return 0;
}
clearfonts();
for(i = 0; i < npath; i++){
if(path[i] == nil || !loadfont(path[i])){
clearfonts();
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;
}
}
for(a = 0; a < 256; a++){
blendtab[0][a] = blend(Colbg, Colfg, a);
blendtab[1][a] = blend(Colsel, Colfg, a);
pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(fontmap), 96);
context = pango_font_map_create_context(fontmap);
if(context != nil)
layout = pango_layout_new(context);
if(context == nil || layout == nil){
fprint(2, "strans: popup: can't create text layout\n");
textclear();
return 0;
}
pango_layout_set_single_paragraph_mode(layout, TRUE);
setfont();
return 1;
}
static int
drawglyph(Font *f, u32int *buf, int w, int h, int px, int py)
settext(Str *s)
{
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;
char utf[Maxutf];
int n;
g = f->face->glyph;
b = &g->bitmap;
if(b->pixel_mode != FT_PIXEL_MODE_GRAY || b->num_grays != 256)
if(layout == nil || s == nil || s->n <= 0)
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)
n = stoutf(s, utf, sizeof utf);
pango_layout_set_text(layout, utf, n);
return n > 0;
}
static void
textextents(PangoRectangle *r)
{
PangoRectangle ink, logical;
int x0, y0, x1, y1;
pango_layout_get_pixel_extents(layout, &ink, &logical);
x0 = min(ink.x, logical.x);
y0 = min(ink.y, logical.y);
x1 = max(ink.x + ink.width, logical.x + logical.width);
y1 = max(ink.y + ink.height, logical.y + logical.height);
r->x = x0;
r->y = y0;
r->width = max(x1 - x0, 0);
r->height = max(y1 - y0, 0);
}
int
textwidth(Str *s)
{
PangoRectangle r;
if(!settext(s))
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;
textextents(&r);
return r.width;
}
void
putfont(u32int *buf, int w, int h, int px, int py, Rune r)
textdraw(u32int *buf, int w, int h, int x, int y, Str *s)
{
FT_UInt g;
int f;
PangoRectangle r;
cairo_surface_t *surface;
cairo_t *cr;
int stride;
if(buf == nil || w <= 0 || h <= 0)
if(buf == nil || w <= 0 || h <= 0 || !settext(s))
return;
stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, w);
if(stride < 0 || (vlong)stride != (vlong)w * sizeof buf[0])
return;
surface = cairo_image_surface_create_for_data((uchar*)buf,
CAIRO_FORMAT_RGB24, w, h, stride);
if(cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS){
cairo_surface_destroy(surface);
return;
for(f = 0; f < nfonts; f++){
g = FT_Get_Char_Index(fonts[f].face, r);
if(g == 0 || FT_Load_Glyph(fonts[f].face, g, FT_LOAD_DEFAULT) != 0 ||
FT_Render_Glyph(fonts[f].face->glyph, FT_RENDER_MODE_NORMAL) != 0)
continue;
if(drawglyph(&fonts[f], buf, w, h, px, py))
return;
}
cairo_surface_mark_dirty(surface);
cr = cairo_create(surface);
if(cairo_status(cr) == CAIRO_STATUS_SUCCESS){
pango_cairo_update_layout(cr, layout);
textextents(&r);
/* A fallback face must not paint into another popup row. */
cairo_rectangle(cr, 0, y, w, Fontsz);
cairo_clip(cr);
cairo_move_to(cr, x - r.x, y + (Fontsz - r.height) / 2 - r.y);
cairo_set_source_rgb(cr, 0, 0, 0);
pango_cairo_show_layout(cr, layout);
}
cairo_destroy(cr);
cairo_surface_flush(surface);
cairo_surface_destroy(surface);
}

View File

@@ -1,18 +1,6 @@
#include "dat.h"
#include "fn.h"
int
popupcells(Rune *r, int n)
{
int i, w;
w = 0;
for(i = 0; i < n; i++)
if(r[i] != 0xfe0e && r[i] != 0xfe0f)
w++;
return w;
}
void
popupposition(Caret *caret, int pointerx, int pointery, int screenw,
int screenh, int w, int h, int *x, int *y)

2
run.sh
View File

@@ -5,11 +5,13 @@ 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

View File

@@ -2,13 +2,13 @@ CC = 9c
LD = 9l
HOSTCC = cc
CFLAGS = -std=c99 -Wall -Wextra -O2 -g -I.. -I../cutest
FT_CFLAGS = $(shell pkg-config --cflags freetype2)
FT_LIBS = $(shell pkg-config --libs freetype2)
TEXT_CFLAGS = $(shell pkg-config --cflags 'pangocairo >= 1.56' 'cairo >= 1.18')
TEXT_LIBS = $(shell pkg-config --libs 'pangocairo >= 1.56' 'cairo >= 1.18')
DBUS_CFLAGS = $(shell pkg-config --cflags dbus-1)
DBUS_LIBS = $(shell pkg-config --libs dbus-1)
IBUS_CFLAGS = $(shell pkg-config --cflags dbus-1 xkbcommon)
IBUS_LIBS = $(shell pkg-config --libs dbus-1 xkbcommon)
LIBS = -lthread -lbio $(FT_LIBS) $(IBUS_LIBS)
LIBS = -lthread -lbio $(TEXT_LIBS) $(IBUS_LIBS)
PROG = unit_test
LIVE = ibus_live_test ipc_live_test daemon_collision_test daemon_failure_test \
@@ -57,7 +57,7 @@ engine_test.o: ../strans.c
ibus_test.o: CFLAGS += $(IBUS_CFLAGS)
ibus_test.o: ../ibus.c
server_test.o: ../srv.c
unit_font.o: CFLAGS += $(FT_CFLAGS)
unit_font.o: CFLAGS += $(TEXT_CFLAGS)
unit_%.o: ../%.c ../dat.h ../fn.h ../ipc.h
$(CC) $(CFLAGS) -c -o $@ $<

View File

@@ -3,14 +3,15 @@
enum
{
Guard = 8,
Testw = 3 * Fontsz,
Testw = 6 * Fontsz,
Testh = 3 * Fontsz,
Testn = Testw * Testh,
Missingn = Fontsz * Fontsz,
Rgbmask = 0xffffff,
};
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",
@@ -29,38 +30,84 @@ fillpixels(u32int *p, int n, u32int color)
}
static int
hasink(u32int *p, int n)
hasink(u32int *p, int n, u32int bg)
{
int i;
bg &= Rgbmask;
for(i = 0; i < n; i++)
if(p[i] != Colbg)
if((p[i] & Rgbmask) != bg)
return 1;
return 0;
}
static int
renders(Rune r)
hascolors(u32int *p, int n, u32int bg)
{
u32int buf[Fontsz * Fontsz];
u32int c, first;
int b, found, g, i, r;
fillpixels(buf, nelem(buf), Colbg);
putfont(buf, Fontsz, Fontsz, 0, 0, r);
return hasink(buf, nelem(buf));
bg &= Rgbmask;
first = 0;
found = 0;
for(i = 0; i < n; i++){
c = p[i] & Rgbmask;
if(c == bg)
continue;
r = c >> 16 & 0xff;
g = c >> 8 & 0xff;
b = c & 0xff;
if(r == g && g == b)
continue;
if(!found){
first = c;
found = 1;
}else if(c != first)
return 1;
}
return 0;
}
static void
checkguards(struct ct *t, u32int *mem)
{
int i;
for(i = 0; i < Guard; i++){
CT_EQ_UINT(t, guardcolor, mem[i]);
CT_EQ_UINT(t, guardcolor, mem[Guard + Testn + i]);
}
}
static void
checkoutside(struct ct *t, u32int *buf, u32int bg, int row)
{
int x, y, y0, y1;
bg &= Rgbmask;
y0 = max(row, 0);
y1 = min(row + Fontsz, Testh);
for(y = 0; y < Testh; y++){
if(y >= y0 && y < y1)
continue;
for(x = 0; x < Testw; x++)
CT_EQ_UINT(t, bg, buf[y * Testw + x] & Rgbmask);
}
}
static int
inkbounds(u32int *p, int *minx, int *miny, int *maxx, int *maxy)
inkbounds(u32int *p, u32int bg, int *minx, int *miny, int *maxx, int *maxy)
{
int x, y;
bg &= Rgbmask;
*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)
if((p[y * Testw + x] & Rgbmask) == bg)
continue;
*minx = min(*minx, x);
*miny = min(*miny, y);
@@ -72,86 +119,124 @@ inkbounds(u32int *p, int *minx, int *miny, int *maxx, int *maxy)
}
static void
checkclip(struct ct *t, u32int *ref, int dx, int dy)
checktext(struct ct *t, u32int *buf, char *utf)
{
u32int *buf, *mem, *want;
int i, sx, sy, x, y;
Str s;
int maxx, maxy, minx, miny, w;
s = mkstr(utf);
w = textwidth(&s);
CT_CHECK(t, w > 0);
CT_CHECK(t, w < Testw - Fontsz);
fillpixels(buf, Testn, Colbg);
textdraw(buf, Testw, Testh, Fontsz, Fontsz, &s);
if(!CT_CHECK(t, inkbounds(buf, Colbg, &minx, &miny, &maxx, &maxy)))
return;
CT_CHECK(t, minx >= 0 && miny >= 0);
CT_CHECK(t, maxx < Testw && maxy < Testh);
CT_CHECK(t, maxx - minx + 1 <= w);
}
static void
checkcolor(struct ct *t, u32int *buf, char *utf)
{
Str s;
int w;
s = mkstr(utf);
w = textwidth(&s);
CT_CHECK(t, w > 0);
if(s.n > 1)
CT_CHECK(t, w < s.n * Fontsz);
fillpixels(buf, Testn, Colbg);
textdraw(buf, Testw, Testh, Fontsz, Fontsz, &s);
CT_CHECK(t, hasink(buf, Testn, Colbg));
CT_CHECK(t, hascolors(buf, Testn, Colbg));
}
static void
checkshape(struct ct *t, u32int *buf, char *utf)
{
Str s;
int w;
s = mkstr(utf);
w = textwidth(&s);
CT_CHECK(t, w > 0);
CT_CHECK(t, w < s.n * Fontsz);
fillpixels(buf, Testn, Colbg);
textdraw(buf, Testw, Testh, Fontsz, Fontsz, &s);
CT_CHECK(t, hasink(buf, Testn, Colbg));
}
static void
checkclip(struct ct *t, u32int *mem, int x, int y, Str *s)
{
u32int *buf;
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);
textdraw(buf, Testw, Testh, x, y, s);
CT_CHECK(t, hasink(buf, Testn, Colbg));
checkoutside(t, buf, Colbg, y);
checkguards(t, mem);
}
void
font_render(struct ct *t)
{
u32int *buf, *missing, *ref;
int i, minx, miny, maxx, maxy;
static char *plain[] = { "A", "", "", "", "", "𠀋" };
u32int *buf, *mem;
Str a, heart, missing;
int i, w;
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++)
mem = emalloc((Testn + 2 * Guard) * sizeof mem[0]);
fillpixels(mem, Testn + 2 * Guard, guardcolor);
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]);
for(i = 0; i < Guard; i++){
CT_EQ_UINT(t, guardcolor, missing[i]);
CT_EQ_UINT(t, guardcolor, missing[Guard + Missingn + i]);
}
checkguards(t, mem);
if(!CT_CHECK(t, fontinit(testfonts, nelem(testfonts)))){
free(missing);
if(!CT_CHECK(t, textinit(testfonts, nelem(testfonts)))){
free(mem);
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);
for(i = 0; i < nelem(plain); i++)
checktext(t, buf, plain[i]);
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;
checkcolor(t, buf, "😀");
checkcolor(t, buf, "❤️");
checkcolor(t, buf, "☕️");
checkshape(t, buf, "👩‍💻");
checkshape(t, buf, "👍🏽");
checkshape(t, buf, "🇰🇷");
checkshape(t, buf, "☕︎");
heart = mkstr("❤️");
fillpixels(buf, Testn, Colsel);
textdraw(buf, Testw, Testh, Fontsz, Fontsz, &heart);
CT_CHECK(t, hascolors(buf, Testn, Colsel));
CT_EQ_UINT(t, Colsel,
buf[(Fontsz + Fontsz/2) * Testw + Testw - 1] & Rgbmask);
checkoutside(t, buf, Colsel, Fontsz);
checkguards(t, mem);
w = textwidth(&a);
checkclip(t, mem, -w / 2, Fontsz, &a);
checkclip(t, mem, Testw - w / 2, Fontsz, &a);
checkclip(t, mem, Fontsz, -Fontsz / 2, &a);
checkclip(t, mem, Fontsz, Testh - Fontsz / 2, &a);
missing = mkstr("\xF4\x8F\xBF\xBF");
for(i = 0; i < 32; i++){
textdraw(buf, Testw, Testh, Fontsz, Fontsz, &heart);
textdraw(buf, Testw, Testh, 0, 0, &missing);
}
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);
checkguards(t, mem);
free(mem);
}

View File

@@ -4,11 +4,9 @@
void
popup_layout(struct ct *t)
{
Rune heart[] = { 0x2764, 0xfe0f };
Caret caret;
int x, y;
CT_EQ_INT(t, 1, popupcells(heart, nelem(heart)));
memset(&caret, 0, sizeof caret);
popupposition(&caret, 40, 50, 800, 600, 100, 60, &x, &y);
CT_EQ_INT(t, 50, x);

32
win.c
View File

@@ -141,7 +141,7 @@ wininit(void)
mask = XCB_CW_BACK_PIXMAP;
xcb_change_window_attributes(conn, win, mask, &pix);
img = emalloc(Imgw * Imgh * sizeof(img[0]));
if(!fontinit(fontfiles, nfontfiles)){
if(!textinit(fontfiles, nfontfiles)){
fprint(2, "strans: popup disabled: no usable fonts\n");
wincleanup();
return 0;
@@ -149,18 +149,6 @@ wininit(void)
return 1;
}
static void
drawstr(u32int *buf, int x, int y, Rune *r, int n, int maxw, int maxh)
{
while(n-- > 0){
if(*r != 0xfe0e && *r != 0xfe0f){
putfont(buf, maxw, maxh, x, y, *r);
x += Fontsz;
}
r++;
}
}
static void
fill(u32int *buf, int n, u32int color)
{
@@ -174,20 +162,22 @@ static void
drawkouho(Drawcmd *dc, int n, int w, int h)
{
int npre, sely, y, i;
Str *s;
Str num, *s;
fill(img, w * h, Colbg);
npre = dc->pre.n != 0;
if(npre)
drawstr(img, 0, 0, dc->pre.r, dc->pre.n, w, h);
textdraw(img, w, h, 0, 0, &dc->pre);
if(dc->sel >= 0 && dc->sel < n){
sely = (npre + dc->sel) * Fontsz;
fill(img + sely * w, Fontsz * w, Colsel);
}
for(i = 0, y = npre * Fontsz; i < n; i++, y += Fontsz){
s = &dc->kouho[i];
putfont(img, w, h, 0, y, '1' + i + Asciitofull);
drawstr(img, 2*Fontsz, y, s->r, s->n, w, h);
sclear(&num);
sputr(&num, '1' + i + Asciitofull);
textdraw(img, w, h, 0, y, &num);
textdraw(img, w, h, 2*Fontsz, y, s);
}
}
@@ -210,6 +200,7 @@ winhide(void)
static int
winshow(Drawcmd *dc)
{
vlong width;
int npre, px, py, w, h, i, n, maxw;
u32int vals[4];
xcb_query_pointer_reply_t *ptr;
@@ -220,11 +211,12 @@ winshow(Drawcmd *dc)
if(n == 0 && npre == 0){
return winhide();
}
maxw = popupcells(dc->pre.r, dc->pre.n);
maxw = textwidth(&dc->pre);
for(i = 0; i < n; i++)
maxw = max(maxw, popupcells(dc->kouho[i].r, dc->kouho[i].n));
maxw = max(maxw, textwidth(&dc->kouho[i]));
vals[3] = h = (n + npre) * Fontsz;
vals[2] = w = (maxw + 3) * Fontsz;
width = (vlong)maxw + 3*Fontsz;
vals[2] = w = min(max(width, 1), Imgw);
px = py = 0;
if(!dc->caret.valid){
cookie = xcb_query_pointer(conn, scr->root);