Created
March 10, 2024 17:06
-
-
Save gboncoffee/bf28f7ba104f4a15803b1907707adb53 to your computer and use it in GitHub Desktop.
Header-only distribution of drw.c and drw.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef __DRW_INCLUDED | |
#define __DRW_INCLUDED | |
/* | |
* drw.h - Header-only library for easy X11 text-oriented graphics. | |
*/ | |
/* | |
* Copyright (C) 2023 Gabriel de Brito | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining a copy | |
* of this software and associated documentation files (the "Software"), to deal | |
* in the Software without restriction, including without limitation the rights | |
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
* copies of the Software, and to permit persons to whom the Software is | |
* furnished to do so, subject to the following conditions: | |
* The above copyright notice and this permission notice shall be included in | |
* all copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
* THE SOFTWARE. | |
*/ | |
/* | |
* This software a redistribution of the source code with little change of | |
* `drw.c` and `drw.h`, available bundled with the dmenu(1) source code | |
* (https://tools.suckless.org/dmenu/), under the following license: | |
* | |
* MIT/X Consortium License | |
* | |
* © 2006-2019 Anselm R Garbe <anselm@garbe.ca> | |
* © 2006-2008 Sander van Dijk <a.h.vandijk@gmail.com> | |
* © 2006-2007 Michał Janeczek <janeczek@gmail.com> | |
* © 2007 Kris Maglione <jg@suckless.org> | |
* © 2009 Gottox <gottox@s01.de> | |
* © 2009 Markus Schnalke <meillo@marmaro.de> | |
* © 2009 Evan Gates <evan.gates@gmail.com> | |
* © 2010-2012 Connor Lane Smith <cls@lubutu.com> | |
* © 2014-2019 Hiltjo Posthuma <hiltjo@codemadness.org> | |
* © 2015-2019 Quentin Rameau <quinq@fifth.space> | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining a | |
* copy of this software and associated documentation files (the "Software"), | |
* to deal in the Software without restriction, including without limitation | |
* the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
* and/or sell copies of the Software, and to permit persons to whom the | |
* Software is furnished to do so, subject to the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be included in | |
* all copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | |
* DEALINGS IN THE SOFTWARE. | |
*/ | |
#include <X11/Xlib.h> | |
#include <X11/Xft/Xft.h> | |
typedef struct { | |
Cursor cursor; | |
} Cur; | |
typedef struct Fnt { | |
Display *dpy; | |
unsigned int h; | |
XftFont *xfont; | |
FcPattern *pattern; | |
struct Fnt *next; | |
} Fnt; | |
enum { ColFg, ColBg }; /* Clr scheme index */ | |
typedef XftColor Clr; | |
typedef struct { | |
unsigned int w, h; | |
Display *dpy; | |
int screen; | |
Window root; | |
Drawable drawable; | |
GC gc; | |
Clr *scheme; | |
Fnt *fonts; | |
} Drw; | |
/* Drawable abstraction */ | |
Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w, unsigned int h); | |
void drw_resize(Drw *drw, unsigned int w, unsigned int h); | |
void drw_free(Drw *drw); | |
/* Fnt abstraction */ | |
Fnt *drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount); | |
void drw_fontset_free(Fnt* set); | |
unsigned int drw_fontset_getwidth(Drw *drw, const char *text); | |
void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h); | |
/* Colorscheme abstraction */ | |
int drw_clr_create(Drw *drw, Clr *dest, const char *clrname); | |
Clr *drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount); | |
/* Cursor abstraction */ | |
Cur *drw_cur_create(Drw *drw, int shape); | |
void drw_cur_free(Drw *drw, Cur *cursor); | |
/* Drawing context manipulation */ | |
void drw_setfontset(Drw *drw, Fnt *set); | |
void drw_setscheme(Drw *drw, Clr *scm); | |
/* Drawing functions */ | |
void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert); | |
int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert); | |
/* Map functions */ | |
void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h); | |
#ifdef DRW_IMPLEMENTATION | |
#undef DRW_IMPLEMENTATION | |
#define MAX(A, B) ((A) > (B) ? (A) : (B)) | |
#define MIN(A, B) ((A) < (B) ? (A) : (B)) | |
#define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B)) | |
#define UTF_INVALID 0xFFFD | |
#define UTF_SIZ 4 | |
static const unsigned char utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; | |
static const unsigned char utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; | |
static const long utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; | |
static const long utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; | |
long | |
utf8decodebyte(const char c, size_t *i) | |
{ | |
for (*i = 0; *i < (UTF_SIZ + 1); ++(*i)) | |
if (((unsigned char)c & utfmask[*i]) == utfbyte[*i]) | |
return (unsigned char)c & ~utfmask[*i]; | |
return 0; | |
} | |
size_t | |
utf8validate(long *u, size_t i) | |
{ | |
if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) | |
*u = UTF_INVALID; | |
for (i = 1; *u > utfmax[i]; ++i) | |
; | |
return i; | |
} | |
size_t | |
utf8decode(const char *c, long *u, size_t clen) | |
{ | |
size_t i, j, len, type; | |
long udecoded; | |
*u = UTF_INVALID; | |
if (!clen) | |
return 0; | |
udecoded = utf8decodebyte(c[0], &len); | |
if (!BETWEEN(len, 1, UTF_SIZ)) | |
return 1; | |
for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { | |
udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); | |
if (type) | |
return j; | |
} | |
if (j < len) | |
return 0; | |
*u = udecoded; | |
utf8validate(u, len); | |
return len; | |
} | |
Drw * | |
drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h) | |
{ | |
Drw *drw = malloc(sizeof(Drw)); | |
if (drw == NULL) | |
return NULL; | |
drw->dpy = dpy; | |
drw->screen = screen; | |
drw->root = root; | |
drw->w = w; | |
drw->h = h; | |
drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen)); | |
drw->gc = XCreateGC(dpy, root, 0, NULL); | |
XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter); | |
return drw; | |
} | |
void | |
drw_resize(Drw *drw, unsigned int w, unsigned int h) | |
{ | |
if (!drw) | |
return; | |
drw->w = w; | |
drw->h = h; | |
if (drw->drawable) | |
XFreePixmap(drw->dpy, drw->drawable); | |
drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, DefaultDepth(drw->dpy, drw->screen)); | |
} | |
void | |
drw_free(Drw *drw) | |
{ | |
XFreePixmap(drw->dpy, drw->drawable); | |
XFreeGC(drw->dpy, drw->gc); | |
free(drw); | |
} | |
/* This function is an implementation detail. Library users should use | |
* drw_fontset_create instead. | |
*/ | |
static Fnt * | |
xfont_create(Drw *drw, const char *fontname, FcPattern *fontpattern) | |
{ | |
Fnt *font; | |
XftFont *xfont = NULL; | |
FcPattern *pattern = NULL; | |
if (fontname) { | |
/* Using the pattern found at font->xfont->pattern does not yield the | |
* same substitution results as using the pattern returned by | |
* FcNameParse; using the latter results in the desired fallback | |
* behaviour whereas the former just results in missing-character | |
* rectangles being drawn, at least with some fonts. */ | |
if (!(xfont = XftFontOpenName(drw->dpy, drw->screen, fontname))) { | |
fprintf(stderr, "error, cannot load font from name: '%s'\n", fontname); | |
return NULL; | |
} | |
if (!(pattern = FcNameParse((FcChar8 *) fontname))) { | |
fprintf(stderr, "error, cannot parse font name to pattern: '%s'\n", fontname); | |
XftFontClose(drw->dpy, xfont); | |
return NULL; | |
} | |
} else if (fontpattern) { | |
if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))) { | |
fprintf(stderr, "error, cannot load font from pattern.\n"); | |
return NULL; | |
} | |
} else { | |
return NULL; | |
} | |
/* Do not allow using color fonts. This is a workaround for a BadLength | |
* error from Xft with color glyphs. Modelled on the Xterm workaround. See | |
* https://bugzilla.redhat.com/show_bug.cgi?id=1498269 | |
* https://lists.suckless.org/dev/1701/30932.html | |
* https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=916349 | |
* and lots more all over the internet. | |
*/ | |
FcBool iscol; | |
if(FcPatternGetBool(xfont->pattern, FC_COLOR, 0, &iscol) == FcResultMatch && iscol) { | |
XftFontClose(drw->dpy, xfont); | |
return NULL; | |
} | |
font = malloc(sizeof(Fnt)); | |
if (font == NULL) | |
return NULL; | |
font->xfont = xfont; | |
font->pattern = pattern; | |
font->h = xfont->ascent + xfont->descent; | |
font->dpy = drw->dpy; | |
return font; | |
} | |
static void | |
xfont_free(Fnt *font) | |
{ | |
if (!font) | |
return; | |
if (font->pattern) | |
FcPatternDestroy(font->pattern); | |
XftFontClose(font->dpy, font->xfont); | |
free(font); | |
} | |
Fnt * | |
drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount) | |
{ | |
Fnt *cur, *ret = NULL; | |
size_t i; | |
if (!drw || !fonts) | |
return NULL; | |
for (i = 1; i <= fontcount; i++) { | |
if ((cur = xfont_create(drw, fonts[fontcount - i], NULL))) { | |
cur->next = ret; | |
ret = cur; | |
} | |
} | |
return (drw->fonts = ret); | |
} | |
void | |
drw_fontset_free(Fnt *font) | |
{ | |
if (font) { | |
drw_fontset_free(font->next); | |
xfont_free(font); | |
} | |
} | |
int | |
drw_clr_create(Drw *drw, Clr *dest, const char *clrname) | |
{ | |
if (!drw || !dest || !clrname) | |
return 1; | |
return !XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen), | |
DefaultColormap(drw->dpy, drw->screen), | |
clrname, dest); | |
} | |
/* Wrapper to create color schemes. The caller has to call free(3) on the | |
* returned color scheme when done using it. */ | |
Clr * | |
drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount) | |
{ | |
size_t i; | |
Clr *ret; | |
/* need at least two colors for a scheme */ | |
if (!drw || !clrnames || clrcount < 2 || !(ret = malloc(clrcount * sizeof(XftColor)))) | |
return NULL; | |
for (i = 0; i < clrcount; i++) | |
drw_clr_create(drw, &ret[i], clrnames[i]); | |
return ret; | |
} | |
void | |
drw_setfontset(Drw *drw, Fnt *set) | |
{ | |
if (drw) | |
drw->fonts = set; | |
} | |
void | |
drw_setscheme(Drw *drw, Clr *scm) | |
{ | |
if (drw) | |
drw->scheme = scm; | |
} | |
void | |
drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert) | |
{ | |
if (!drw || !drw->scheme) | |
return; | |
XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme[ColBg].pixel : drw->scheme[ColFg].pixel); | |
if (filled) | |
XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); | |
else | |
XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w - 1, h - 1); | |
} | |
int | |
drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert) | |
{ | |
char buf[1024]; | |
int ty; | |
unsigned int ew; | |
XftDraw *d = NULL; | |
Fnt *usedfont, *curfont, *nextfont; | |
size_t i, len; | |
int utf8strlen, utf8charlen, render = x || y || w || h; | |
long utf8codepoint = 0; | |
const char *utf8str; | |
FcCharSet *fccharset; | |
FcPattern *fcpattern; | |
FcPattern *match; | |
XftResult result; | |
int charexists = 0; | |
if (!drw || (render && !drw->scheme) || !text || !drw->fonts) | |
return 0; | |
if (!render) { | |
w = ~w; | |
} else { | |
XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel); | |
XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); | |
d = XftDrawCreate(drw->dpy, drw->drawable, | |
DefaultVisual(drw->dpy, drw->screen), | |
DefaultColormap(drw->dpy, drw->screen)); | |
x += lpad; | |
w -= lpad; | |
} | |
usedfont = drw->fonts; | |
while (1) { | |
utf8strlen = 0; | |
utf8str = text; | |
nextfont = NULL; | |
while (*text) { | |
utf8charlen = utf8decode(text, &utf8codepoint, UTF_SIZ); | |
for (curfont = drw->fonts; curfont; curfont = curfont->next) { | |
charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint); | |
if (charexists) { | |
if (curfont == usedfont) { | |
utf8strlen += utf8charlen; | |
text += utf8charlen; | |
} else { | |
nextfont = curfont; | |
} | |
break; | |
} | |
} | |
if (!charexists || nextfont) | |
break; | |
else | |
charexists = 0; | |
} | |
if (utf8strlen) { | |
drw_font_getexts(usedfont, utf8str, utf8strlen, &ew, NULL); | |
/* shorten text if necessary */ | |
for (len = MIN(utf8strlen, sizeof(buf) - 1); len && ew > w; len--) | |
drw_font_getexts(usedfont, utf8str, len, &ew, NULL); | |
if (len) { | |
memcpy(buf, utf8str, len); | |
buf[len] = '\0'; | |
if (len < utf8strlen) | |
for (i = len; i && i > len - 3; buf[--i] = '.') | |
; /* NOP */ | |
if (render) { | |
ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent; | |
XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg], | |
usedfont->xfont, x, ty, (XftChar8 *)buf, len); | |
} | |
x += ew; | |
w -= ew; | |
} | |
} | |
if (!*text) { | |
break; | |
} else if (nextfont) { | |
charexists = 0; | |
usedfont = nextfont; | |
} else { | |
/* Regardless of whether or not a fallback font is found, the | |
* character must be drawn. */ | |
charexists = 1; | |
fccharset = FcCharSetCreate(); | |
FcCharSetAddChar(fccharset, utf8codepoint); | |
if (!drw->fonts->pattern) { | |
/* Refer to the comment in xfont_create for more information. */ | |
return 0; | |
} | |
fcpattern = FcPatternDuplicate(drw->fonts->pattern); | |
FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset); | |
FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue); | |
FcPatternAddBool(fcpattern, FC_COLOR, FcFalse); | |
FcConfigSubstitute(NULL, fcpattern, FcMatchPattern); | |
FcDefaultSubstitute(fcpattern); | |
match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result); | |
FcCharSetDestroy(fccharset); | |
FcPatternDestroy(fcpattern); | |
if (match) { | |
usedfont = xfont_create(drw, NULL, match); | |
if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) { | |
for (curfont = drw->fonts; curfont->next; curfont = curfont->next) | |
; /* NOP */ | |
curfont->next = usedfont; | |
} else { | |
xfont_free(usedfont); | |
usedfont = drw->fonts; | |
} | |
} | |
} | |
} | |
if (d) | |
XftDrawDestroy(d); | |
return x + (render ? w : 0); | |
} | |
void | |
drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h) | |
{ | |
if (!drw) | |
return; | |
XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y); | |
XSync(drw->dpy, False); | |
} | |
unsigned int | |
drw_fontset_getwidth(Drw *drw, const char *text) | |
{ | |
if (!drw || !drw->fonts || !text) | |
return 0; | |
return drw_text(drw, 0, 0, 0, 0, 0, text, 0); | |
} | |
void | |
drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h) | |
{ | |
XGlyphInfo ext; | |
if (!font || !text) | |
return; | |
XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext); | |
if (w) | |
*w = ext.xOff; | |
if (h) | |
*h = font->h; | |
} | |
Cur * | |
drw_cur_create(Drw *drw, int shape) | |
{ | |
Cur *cur; | |
if (!drw || !(cur = malloc(sizeof(Cur)))) | |
return NULL; | |
cur->cursor = XCreateFontCursor(drw->dpy, shape); | |
return cur; | |
} | |
void | |
drw_cur_free(Drw *drw, Cur *cursor) | |
{ | |
if (!cursor) | |
return; | |
XFreeCursor(drw->dpy, cursor->cursor); | |
free(cursor); | |
} | |
#endif /* DRW_IMPLEMENTATION */ | |
#endif /* __DRW_INCLUDED */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment