swim.git

draw.c

espurr
/* swim - window manager
 * Copyright (C) 2022-2023 ArcNyxx
 * see LICENCE file for licensing information */

#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>

#include <ft2build.h>
#include FT_ADVANCES_H
#include FT_FREETYPE_H
#include <X11/Xlib.h>
#include <X11/extensions/Xrender.h>

#include <xcb/xcb.h>
#include <xcb/render.h>
#include <xcb/xcb_renderutil.h>

#include "draw.h"
#include "swim.h"
#include "util.h"

#define RGB XRenderFindStandardFormat(dpy, PictStandardARGB32)

typedef struct ch {
	wchar_t ch;
	uint16_t adv; /* only supports left-to-right fonts */
} char_t;

typedef struct row {
	char_t *data;
	size_t len, alc;
} row_t;

static const struct { const char *name; uint16_t size; } fonts[1] =
		{ { "/usr/share/fonts/gnu-free/FreeMono.otf", 16 } };
static const struct { uint8_t r, g, b; } clrs[CLR_LST] = { { 0x44, 0x44,
		0x44 }, { 0x8C, 0x24, 0xC9 }, { 0xEE, 0xEE, 0xEE } };

static FT_Face faces[LENGTH(fonts)];
static GlyphSet glyphs;
static wchar_t array[256];
static int tlen;
static uint32_t fil;

XColor xclrs[CLR_LST];

static char_t *loadchar(wchar_t ch);
static char utf8decodebyte(const char ch, int *ext);
static int utf8decode(const char *restrict str, wchar_t *restrict val);

static char_t *
loadchar(wchar_t ch)
{
	static row_t hash[128];
	row_t *row = &hash[ch % LENGTH(hash)];
	for (size_t i = 0; i < row->len; ++i)
		if (row->data[i].ch == ch)
			return &row->data[i];

	FT_GlyphSlot slot = NULL;
	for (size_t i = 0; i < LENGTH(fonts); ++i) {
		uint32_t code;
		if ((code = FT_Get_Char_Index(faces[i], ch)) == 0 ||
				FT_Load_Glyph(faces[i], code, FT_LOAD_RENDER))
			continue;
		slot = faces[i]->glyph;
		break;
	}
	if (slot == NULL) {
		char_t *data;
		if ((data = loadchar(L' ')) != NULL)
			return data;
		die("swim: unable to load char\n");
	}

	XGlyphInfo gi = {
		.x = -slot->bitmap_left, .y = slot->bitmap_top,
		.width = slot->bitmap.width, .height = slot->bitmap.rows,
		.xOff = slot->advance.x >> 6, .yOff = slot->advance.y >> 6
	};
	char *img = srealloc(NULL, 4 * gi.width * gi.height);
	for (size_t y = 0; y < gi.height; ++y)
		for (size_t x = 0; x < gi.width; ++x)
			for (size_t i = 0; i < 4; ++i)
				img[4 * (y * gi.width + x) + i] = slot->bitmap.
						buffer[y * gi.width + x];
	XRenderAddGlyphs(dpy, glyphs, (void *)&ch, &gi, 1, img,
			4 * gi.width * gi.height);
	free(img);

	if (row->alc == row->len)
		row->data = srealloc(row->data, (row->alc = 2 * row->alc + 1) *
				sizeof(char_t));
	row->data[row->len] = (char_t){ ch, slot->advance.x >> 6 };
	return &row->data[row->len++];
}

static char
utf8decodebyte(const char ch, int *ext)
{
	static const unsigned char byte[5] = { 0x80, 0, 0xC0, 0xE0, 0xF0 };
	static const unsigned char mask[5] = { 0xC0, 0x80, 0xE0, 0xF0, 0xF8 };

	for (*ext = 0; *ext < 5; ++(*ext))
		if ((ch & mask[*ext]) == byte[*ext])
			return ch & ~mask[*ext];
	*ext = -1;
	return 0;
}

static int
utf8decode(const char *restrict str, wchar_t *restrict val)
{
	static const int min[5] = { 0, 0, 0x80, 0x800, 0x10000 };
	static const int max[5] = { 0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF };

	int len; *val = 0xFFFD;
	wchar_t decoded = utf8decodebyte(str[0], &len);
	for (int i = 1, type; i < len; ++i) {
		decoded = (decoded << 6) + utf8decodebyte(str[i], &type);
		if (type != 0)
			return i;
	}
	if (len >= 1 && decoded <= max[len] && decoded >= min[len] &&
			(decoded < 0xD800 || decoded > 0xDFFF))
		*val = decoded;
	return MAX(1, len);
}

extern xcb_connection_t *c;
extern xcb_screen_t *screen;

xcb_render_query_pict_formats_reply_t *pfr;
uint32_t visual_fmt, idt;

void
drawinit(void)
{
	static FT_Library lib;
	if (FT_Init_FreeType(&lib))
		die("swim: unable to init freetype\n");
	for (size_t i = 0; i < LENGTH(fonts); ++i)
		if (FT_New_Face(lib, fonts[i].name, 0, &faces[i]) ||
				FT_Set_Char_Size(faces[i],
				fonts[i].size << 6, 0, 0, 0))
			die("swim: unable to load face: %s\n", fonts[i].name);

        pfr = xcb_render_query_pict_formats_reply(c,
                        xcb_render_query_pict_formats_unchecked(c), NULL);
        xcb_pixmap_t pix = ID(c); fil = ID(c);
        xcb_create_pixmap(c, 32, pix, screen->root, 1, 1);
	xcb_render_pictvisual_t *vft = xcb_render_util_find_visual_format(pfr,
			screen->root_visual);
	visual_fmt = vft->format;
        xcb_render_pictforminfo_t *std = xcb_render_util_find_standard_format(
                        pfr, XCB_PICT_STANDARD_ARGB_32);
        xcb_render_create_picture(c, fil, pix, std->id, 0, NULL);
	idt = std->id;
        xcb_free_pixmap(c, pix); /* storage freed when references destroyed */

	glyphs = XRenderCreateGlyphSet(dpy, RGB);

	for (size_t i = 0; i < LENGTH(clrs); ++i) {
		xclrs[i] = (XColor){
			.red   = (clrs[i].r << 8) + clrs[i].r,
			.green = (clrs[i].g << 8) + clrs[i].g,
			.blue  = (clrs[i].b << 8) + clrs[i].b,
			.flags = DoRed | DoGreen | DoBlue
		};
		XAllocColor(dpy, DEF(Colormap), &xclrs[i]);
	}
}

void
drawwide(const char *restrict str, uint16_t max, uint16_t *restrict width)
{
	tlen = 0;
	for (int i = 0; i < 256 && str[i] != '\0'; ++tlen) {
		i += utf8decode(&str[i], &array[tlen]);
		char_t *data = loadchar(array[tlen]);

		if ((*width += data->adv) > max) {
			*width = max; break;
		}
	}
}

void
drawrect(const mon_t *mon, clr_t col, bool fill,
		uint16_t x, uint16_t y, uint16_t w, uint16_t h)
{
	XSetForeground(dpy, DEF(GC), xclrs[col].pixel);
	(fill ? XFillRectangle : XDrawRectangle)(dpy, mon->win, DEF(GC), x, y,
			w - !fill, h - !fill);
}

void
drawtext(const mon_t *mon, clr_t bg, clr_t fg, uint16_t x, uint16_t w)
{
	drawrect(mon, bg, true, x, 0, w, H);
	XRenderFillRectangle(dpy, PictOpSrc, fil, &(XRenderColor){
		(clrs[fg].r << 8) + clrs[fg].r, (clrs[fg].g << 8) + clrs[fg].g,
		(clrs[fg].b << 8) + clrs[fg].b, 0xFFFF
	}, 0, 0, 1, 1);
	XRenderCompositeString32(dpy, PictOpOver, fil, mon->pic, RGB,
			glyphs, 0, 0, x + PW / 2, H - 4, (void *)array, tlen);
}