popup: replace FreeType renderer with PangoCairo
This commit is contained in:
@@ -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 \
|
||||
|
||||
8
Makefile
8
Makefile
@@ -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)
|
||||
|
||||
15
README.md
15
README.md
@@ -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.
|
||||
|
||||
|
||||
@@ -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
6
fn.h
@@ -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
254
font.c
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
2
run.sh
@@ -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
|
||||
|
||||
@@ -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 $@ $<
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
32
win.c
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user