diff options
| author | TheSiahxyz <164138827+TheSiahxyz@users.noreply.github.com> | 2025-02-09 10:19:46 +0900 |
|---|---|---|
| committer | TheSiahxyz <164138827+TheSiahxyz@users.noreply.github.com> | 2025-02-09 10:19:46 +0900 |
| commit | b9d337f3c0ca2501744a1bb12b9bd6d3929c692c (patch) | |
| tree | dd312fea6046764464b93cb3464b9b260bb3062f /dmenu/drw.c | |
| parent | 0bbc6ee96491c8f9619148018214427d8ca026b4 (diff) | |
updates
Diffstat (limited to 'dmenu/drw.c')
| -rw-r--r-- | dmenu/drw.c | 367 |
1 files changed, 292 insertions, 75 deletions
diff --git a/dmenu/drw.c b/dmenu/drw.c index ac1d696..0c5b84f 100644 --- a/dmenu/drw.c +++ b/dmenu/drw.c @@ -4,59 +4,57 @@ #include <string.h> #include <X11/Xlib.h> #include <X11/Xft/Xft.h> +#include <spng.h> #include "drw.h" #include "util.h" #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}; +struct image_item { + const char *path; + int width; + int height; + char *buf; + Pixmap pixmap; + struct image_item *next; +}; -static 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; -} - -static 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; -} +static struct image_item *images = NULL; -static size_t -utf8decode(const char *c, long *u, size_t clen) +static int +utf8decode(const char *s_in, long *u, int *err) { - size_t i, j, len, type; - long udecoded; - + static const unsigned char lens[] = { + /* 0XXXX */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + /* 10XXX */ 0, 0, 0, 0, 0, 0, 0, 0, /* invalid */ + /* 110XX */ 2, 2, 2, 2, + /* 1110X */ 3, 3, + /* 11110 */ 4, + /* 11111 */ 0, /* invalid */ + }; + static const unsigned char leading_mask[] = { 0x7F, 0x1F, 0x0F, 0x07 }; + static const unsigned int overlong[] = { 0x0, 0x80, 0x0800, 0x10000 }; + + const unsigned char *s = (const unsigned char *)s_in; + int len = lens[*s >> 3]; *u = UTF_INVALID; - if (!clen) - return 0; - udecoded = utf8decodebyte(c[0], &len); - if (!BETWEEN(len, 1, UTF_SIZ)) + *err = 1; + if (len == 0) return 1; - for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { - udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); - if (type) - return j; + + long cp = s[0] & leading_mask[len - 1]; + for (int i = 1; i < len; ++i) { + if (s[i] == '\0' || (s[i] & 0xC0) != 0x80) + return i; + cp = (cp << 6) | (s[i] & 0x3F); } - if (j < len) - return 0; - *u = udecoded; - utf8validate(u, len); + /* out of range, surrogate, overlong encoding */ + if (cp > 0x10FFFF || (cp >> 11) == 0x1B || cp < overlong[len - 1]) + return len; + *err = 0; + *u = cp; return len; } @@ -192,13 +190,14 @@ drw_clr_create(Drw *drw, Clr *dest, const char *clrname, unsigned int alpha) if (!XftColorAllocName(drw->dpy, drw->visual, drw->cmap, clrname, dest)) die("error, cannot allocate color '%s'", clrname); - dest->pixel = (dest->pixel & 0x00FFFFFFFU) | alpha << 24; + + dest->pixel = (dest->pixel & 0x00ffffffU) | (alpha << 24); } /* 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[], const unsigned int alphas[], size_t clrcount) +drw_scm_create(Drw *drw, char *clrnames[], const unsigned int alphas[], size_t clrcount) { size_t i; Clr *ret; @@ -241,47 +240,72 @@ drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int 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; + int ty, ellipsis_x = 0; + unsigned int tmpw, ew, ellipsis_w = 0, ellipsis_len, hash, h0, h1; XftDraw *d = NULL; Fnt *usedfont, *curfont, *nextfont; - size_t i, len; - int utf8strlen, utf8charlen, render = x || y || w || h; + int utf8strlen, utf8charlen, utf8err, render = x || y || w || h; long utf8codepoint = 0; const char *utf8str; FcCharSet *fccharset; FcPattern *fcpattern; FcPattern *match; XftResult result; - int charexists = 0; + int charexists = 0, overflow = 0; + /* keep track of a couple codepoints for which we have no match. */ + static unsigned int nomatches[128], ellipsis_width, invalid_width; + static const char invalid[] = "�"; - if (!drw || (render && !drw->scheme) || !text || !drw->fonts) + if (!drw || (render && (!drw->scheme || !w)) || !text || !drw->fonts) return 0; if (!render) { - w = ~w; + w = invert ? invert : ~invert; } else { XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel); XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); + if (w < lpad) + return x + w; d = XftDrawCreate(drw->dpy, drw->drawable, drw->visual, drw->cmap); x += lpad; w -= lpad; } usedfont = drw->fonts; + if (!ellipsis_width && render) + ellipsis_width = drw_fontset_getwidth(drw, "..."); + if (!invalid_width && render) + invalid_width = drw_fontset_getwidth(drw, invalid); while (1) { - utf8strlen = 0; + ew = ellipsis_len = utf8err = utf8charlen = utf8strlen = 0; utf8str = text; nextfont = NULL; while (*text) { - utf8charlen = utf8decode(text, &utf8codepoint, UTF_SIZ); + utf8charlen = utf8decode(text, &utf8codepoint, &utf8err); for (curfont = drw->fonts; curfont; curfont = curfont->next) { charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint); if (charexists) { - if (curfont == usedfont) { - utf8strlen += utf8charlen; + drw_font_getexts(curfont, text, utf8charlen, &tmpw, NULL); + if (ew + ellipsis_width <= w) { + /* keep track where the ellipsis still fits */ + ellipsis_x = x + ew; + ellipsis_w = w - ew; + ellipsis_len = utf8strlen; + } + + if (ew + tmpw > w) { + overflow = 1; + /* called from drw_fontset_getwidth_clamp(): + * it wants the width AFTER the overflow + */ + if (!render) + x += tmpw; + else + utf8strlen = ellipsis_len; + } else if (curfont == usedfont) { text += utf8charlen; + utf8strlen += utf8err ? 0 : utf8charlen; + ew += utf8err ? 0 : tmpw; } else { nextfont = curfont; } @@ -289,36 +313,31 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp } } - if (!charexists || nextfont) + if (overflow || !charexists || nextfont || utf8err) 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 (render) { + ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent; + XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg], + usedfont->xfont, x, ty, (XftChar8 *)utf8str, utf8strlen); } + x += ew; + w -= ew; } + if (utf8err && (!render || invalid_width < w)) { + if (render) + drw_text(drw, x, y, w, h, 0, invalid, invert); + x += invalid_width; + w -= invalid_width; + } + if (render && overflow) + drw_text(drw, ellipsis_x, y, ellipsis_w, h, 0, "...", invert); - if (!*text) { + if (!*text || overflow) { break; } else if (nextfont) { charexists = 0; @@ -328,6 +347,15 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp * character must be drawn. */ charexists = 1; + hash = (unsigned int)utf8codepoint; + hash = ((hash >> 16) ^ hash) * 0x21F0AAAD; + hash = ((hash >> 15) ^ hash) * 0xD35A2D97; + h0 = ((hash >> 15) ^ hash) % LENGTH(nomatches); + h1 = (hash >> 17) % LENGTH(nomatches); + /* avoid expensive XftFontMatch call when we know we won't find a match */ + if (nomatches[h0] == utf8codepoint || nomatches[h1] == utf8codepoint) + goto no_match; + fccharset = FcCharSetCreate(); FcCharSetAddChar(fccharset, utf8codepoint); @@ -355,6 +383,8 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp curfont->next = usedfont; } else { xfont_free(usedfont); + nomatches[nomatches[h0] ? h1 : h0] = utf8codepoint; +no_match: usedfont = drw->fonts; } } @@ -366,6 +396,163 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp return x + (render ? w : 0); } +static struct image_item * +load_image(Drw *drw, unsigned int maxw, unsigned int maxh, const char *path) +{ + FILE *png; + spng_ctx *ctx = NULL; + int ret = 0; + struct spng_ihdr ihdr; + struct spng_plte plte = {0}; + struct spng_row_info row_info = {0}; + char *spng_buf; + int fmt = SPNG_FMT_RGBA8; + int crop_width; + int crop_height; + + struct image_item *image = ecalloc(1, sizeof(struct image_item)); + image->path = path; + image->next = images; + images = image; + + png = fopen(path, "rb"); + if (png == NULL) { + fprintf(stderr, "error opening input file %s\n", path); + return NULL; + } + + /* Create a context */ + ctx = spng_ctx_new(0); + if (ctx == NULL) { + fprintf(stderr, "%s: spng_ctx_new() failed\n", path); + return NULL; + } + + /* Ignore and don't calculate chunk CRC's */ + spng_set_crc_action(ctx, SPNG_CRC_USE, SPNG_CRC_USE); + + /* Set memory usage limits for storing standard and unknown chunks, + this is important when reading untrusted files! */ + size_t limit = 1024 * 1024 * 64; + spng_set_chunk_limits(ctx, limit, limit); + + spng_set_png_file(ctx, png); + + ret = spng_get_ihdr(ctx, &ihdr); + if (ret) { + fprintf(stderr, "%s: spng_get_ihdr() error: %s\n", path, spng_strerror(ret)); + return NULL; + } + + ret = spng_get_plte(ctx, &plte); + if (ret && ret != SPNG_ECHUNKAVAIL) { + fprintf(stderr, "%s: spng_get_plte() error: %s\n", path, spng_strerror(ret)); + return NULL; + } + + size_t image_size, bytes_per_row; /* size in bytes, not in pixels */ + + ret = spng_decoded_image_size(ctx, fmt, &image_size); + if (ret) + return NULL; + + spng_buf = malloc(image_size); + if (!spng_buf) + return NULL; + + ret = spng_decode_image(ctx, NULL, 0, fmt, SPNG_DECODE_PROGRESSIVE); + if (ret) { + fprintf(stderr, "%s: progressive spng_decode_image() error: %s\n", + path, spng_strerror(ret)); + return NULL; + } + + /* ihdr.height will always be non-zero if spng_get_ihdr() succeeds */ + bytes_per_row = image_size / ihdr.height; + crop_width = MIN(ihdr.width, maxw); + crop_height = MIN(ihdr.height, maxh); + + do { + ret = spng_get_row_info(ctx, &row_info); + if (ret) + break; + ret = spng_decode_row(ctx, spng_buf + row_info.row_num * bytes_per_row, bytes_per_row); + } while (!ret && row_info.row_num < crop_height); + + if (ret != SPNG_EOI && row_info.row_num < crop_height) + fprintf(stderr, "%s: progressive decode error: %s\n", path, spng_strerror(ret)); + + image->buf = calloc(ihdr.width * crop_height * 4, sizeof(char)); + for (int i = 0; i < ihdr.width * crop_height; i++) { + /* RGBA to BGRA */ + image->buf[i*4+2] = spng_buf[i*4+0]; + image->buf[i*4+1] = spng_buf[i*4+1]; + image->buf[i*4+0] = spng_buf[i*4+2]; + image->buf[i*4+3] = spng_buf[i*4+3]; + } + image->width = crop_width; + image->height = crop_height; + + XImage *img = XCreateImage(drw->dpy, CopyFromParent, DefaultDepth(drw->dpy, drw->screen), + ZPixmap, 0, image->buf, ihdr.width, crop_height, 32, 0); + image->pixmap = XCreatePixmap(drw->dpy, drw->root, crop_width, crop_height, 24); + XPutImage(drw->dpy, image->pixmap, drw->gc, img, 0, 0, 0, 0, crop_width, crop_height); + spng_ctx_free(ctx); + fclose(png); + return image; +} + +void +drw_image(Drw *drw, int *x, int *y, unsigned int *w, unsigned int *h, + unsigned int lrpad, unsigned int tbpad, const char *path, int vertical) +{ + /* *x and *y refer to box position including padding, + * *w and *h are the maximum image width and height without padding */ + struct image_item *image = NULL; + int render = *x || *y; + int crop_width, crop_height; + + // find path in images + for (struct image_item *item = images; item != NULL; item = item->next) { + if (!strcmp(item->path, path)) { + image = item; + if (!image->buf) + goto file_error; + break; + } + } + + if (!image && !(image = load_image(drw, *w, *h, path))) + goto file_error; + + if (!render) { + *w = image->width; + *h = image->height; + return; + } + + crop_width = MIN(image->width, *w); + crop_height = MIN(image->height, *h); + if (vertical) + *h = crop_height; + else + *w = crop_width; + + XSetForeground(drw->dpy, drw->gc, drw->scheme[ColBg].pixel); + XFillRectangle(drw->dpy, drw->drawable, drw->gc, *x, *y, *w + lrpad, *h + tbpad); + XCopyArea(drw->dpy, image->pixmap, drw->drawable, drw->gc, 0, 0, + crop_width, crop_height, *x + lrpad/2, *y + tbpad/2); + + if (vertical) + *y += *h + tbpad; + else + *x += *w + lrpad; + return; + +file_error: + *w = *h = 0; +} + void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h) { @@ -384,6 +571,15 @@ drw_fontset_getwidth(Drw *drw, const char *text) return drw_text(drw, 0, 0, 0, 0, 0, text, 0); } +unsigned int +drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n) +{ + unsigned int tmp = 0; + if (drw && drw->fonts && text && n) + tmp = drw_text(drw, 0, 0, 0, 0, 0, text, n); + return MIN(n, tmp); +} + void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h) { @@ -399,6 +595,26 @@ drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, *h = font->h; } +unsigned int +drw_getimagewidth_clamp(Drw *drw, const char *path, unsigned int maxw, unsigned int maxh) +{ + int x = 0, y = 0; + unsigned int w = maxw, h = maxh; + if (drw && path && maxw && maxh) + drw_image(drw, &x, &y, &w, &h, 0, 0, path, 0); + return MIN(maxw, w); +} + +unsigned int +drw_getimageheight_clamp(Drw *drw, const char *path, unsigned int maxw, unsigned int maxh) +{ + int x = 0, y = 0; + unsigned int w = maxw, h = maxh; + if (drw && path && maxw && maxh) + drw_image(drw, &x, &y, &w, &h, 0, 0, path, 1); + return MIN(maxh, h); +} + Cur * drw_cur_create(Drw *drw, int shape) { @@ -421,3 +637,4 @@ drw_cur_free(Drw *drw, Cur *cursor) XFreeCursor(drw->dpy, cursor->cursor); free(cursor); } + |
