Hi NRK, I think you've gotten it backwards, this patch should *faster* if the > string is too long, since we're incrementing up to w instead of > decerementing down it it.
Yes you are right of course, I was mixing up the two approaches. I have attached the changes I was experimenting with. I still don't like the amount of effort required for drawing an ellipsis though so I attached another patch file that doesn't try to draw the ellipsis for comparison. -Stein On Sat, Mar 19, 2022 at 5:18 PM Silvan Jegen <s.je...@gmail.com> wrote: > Hi > > On Sat, Mar 19, 2022 at 12:59 PM Stein Gunnar Bakkeby <bakk...@gmail.com> > wrote: > > > > Hi NRK, > > > > I was tinkering with this for a few hours earlier this week and there > are several issues with this area of code and not specifically related to > this modification of the patch. > > > > The handling of adding the ellipsis (...) when the text is too long is > rather hacky and can cause several issues. > > > > The general idea is that: > > - we work out how many consecutive characters we can print for the > same font > > - then we check if the length of the string exceeds the remaining > drawable space > > - if it is too long then we check the length of the string for every > character until we find that it no longer fits > > - then we replace the last three bytes with periods (.) > > > > These are the problematic areas I have found: > > > > 1. In the original code the text is allowed to bleed into the right > padding as long as it fits (I presume this is intentional). If you add one > more character then the text is too long and it will crop of four > characters to add the ellipsis. This has to do with that the for loop > reduces len once more than necessary. > > > > 2. The adding of the ellipsis naively assumes that the last characters > in the string are single-byte UTF-8 characters. As such when it replaces > the last bytes of the string then you can end up with split multi-byte > UTF-8 characters which can have various effects. > > I *think* I somewhat improved this issue in `vis-menu` (which is based > on `dmenu`, IIRC): > > > https://github.com/Shugyousha/vis/commit/d59b98d934815e54320ad000eebfdaaf8fee344d > > Note that this is not well tested and still has issues though. > > > Cheers, > > Silvan > > -- Stein Gunnar Bakkeby OpenBet Developer bakk...@gmail.com
From a69c9a63ae08b2201ee3ef615a356ace129b7f48 Mon Sep 17 00:00:00 2001 From: Bakkeby <bakk...@gmail.com> Date: Sun, 20 Mar 2022 13:59:23 +0100 Subject: [PATCH] Fixing split multi-byte issue when drawing ellipsis --- dmenu.c | 9 +++--- drw.c | 85 ++++++++++++++++++++++++++------------------------------- drw.h | 3 +- 3 files changed, 46 insertions(+), 51 deletions(-) diff --git a/dmenu.c b/dmenu.c index eca67ac..5e1edd6 100644 --- a/dmenu.c +++ b/dmenu.c @@ -541,7 +541,7 @@ readstdin(void) { char buf[sizeof text], *p; size_t i, imax = 0, size = 0; - unsigned int tmpmax = 0; + XGlyphInfo ext; /* read each line from stdin and add it to the item list */ for (i = 0; fgets(buf, sizeof buf, stdin); i++) { @@ -553,9 +553,9 @@ readstdin(void) if (!(items[i].text = strdup(buf))) die("cannot strdup %u bytes:", strlen(buf) + 1); items[i].out = 0; - drw_font_getexts(drw->fonts, buf, strlen(buf), &tmpmax, NULL); - if (tmpmax > inputw) { - inputw = tmpmax; + XftTextExtentsUtf8(drw->fonts->dpy, drw->fonts->xfont, (XftChar8 *)buf, strlen(buf), &ext); + if (ext.xOff > inputw) { + inputw = ext.xOff; imax = i; } } @@ -768,6 +768,7 @@ main(int argc, char *argv[]) drw = drw_create(dpy, screen, root, wa.width, wa.height); if (!drw_fontset_create(drw, fonts, LENGTH(fonts))) die("no fonts could be loaded."); + ellipsis_width = TEXTW("...") - lrpad; lrpad = drw->fonts->h; #ifdef __OpenBSD__ diff --git a/drw.c b/drw.c index 4cdbcbe..b82dab4 100644 --- a/drw.c +++ b/drw.c @@ -15,6 +15,7 @@ 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}; +unsigned int ellipsis_width = 0; static long utf8decodebyte(const char c, size_t *i) @@ -252,18 +253,18 @@ 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, stop = 0; + unsigned int ew = 0, ellipsis_ew = 0; XftDraw *d = NULL; Fnt *usedfont, *curfont, *nextfont; - size_t i, len; - int utf8strlen, utf8charlen, render = x || y || w || h; + size_t i, b = 0, ellipsis_b = 0; + int utf8charlen, render = x || y || w || h; long utf8codepoint = 0; - const char *utf8str; FcCharSet *fccharset; FcPattern *fcpattern; FcPattern *match; XftResult result; + XGlyphInfo ext; int charexists = 0; if (!drw || (render && !drw->scheme) || !text || !drw->fonts) @@ -283,8 +284,8 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp usedfont = drw->fonts; while (1) { - utf8strlen = 0; - utf8str = text; + ellipsis_ew = ew = 0; + ellipsis_b = b = 0; nextfont = NULL; while (*text) { utf8charlen = utf8decode(text, &utf8codepoint, UTF_SIZ); @@ -292,8 +293,27 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint); if (charexists) { if (curfont == usedfont) { - utf8strlen += utf8charlen; - text += utf8charlen; + XftTextExtentsUtf8(curfont->dpy, curfont->xfont, (XftChar8 *)text, utf8charlen, &ext); + if (ew + ext.xOff + lpad > w || b + utf8charlen > sizeof(buf) - 1) { + /* Only draw ellipsis if we have not recently started another font */ + if (render && ellipsis_b > 3) { + ew = ellipsis_ew; + b = ellipsis_b; + for (i = 0; i < 3; i++) + buf[b++] = '.'; + } + stop = 1; + break; + } + + /* Record the last buffer index where the ellipsis would still fit */ + if (ew + ellipsis_width + lpad <= w) { + ellipsis_ew = ew; + ellipsis_b = b; + } + for (i = 0; i < utf8charlen; i++) + buf[b++] = *text++; + ew += ext.xOff; } else { nextfont = curfont; } @@ -301,36 +321,24 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp } } - if (!charexists || nextfont) + if (stop || !charexists || nextfont) break; else charexists = 0; } + buf[b] = '\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 (b) { + if (render) { + ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent; + XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg], + usedfont->xfont, x, ty, (XftChar8 *)buf, b); } + x += ew; + w -= ew; } - if (!*text) { + if (stop || !*text) { break; } else if (nextfont) { charexists = 0; @@ -397,21 +405,6 @@ drw_fontset_getwidth(Drw *drw, const char *text) 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) { diff --git a/drw.h b/drw.h index 4c67419..7c43b3d 100644 --- a/drw.h +++ b/drw.h @@ -26,6 +26,8 @@ typedef struct { Fnt *fonts; } Drw; +extern unsigned int ellipsis_width; + /* 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); @@ -35,7 +37,6 @@ void drw_free(Drw *drw); 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 */ void drw_clr_create(Drw *drw, Clr *dest, const char *clrname); -- 2.35.1
From 26effbb589dbb175bc35020910cd09a45501ae38 Mon Sep 17 00:00:00 2001 From: Bakkeby <bakk...@gmail.com> Date: Sun, 20 Mar 2022 13:59:23 +0100 Subject: [PATCH] Fixing split multi-byte issue and removing ellipsis drawing --- dmenu.c | 8 +++---- drw.c | 71 ++++++++++++++++++++------------------------------------- drw.h | 1 - 3 files changed, 29 insertions(+), 51 deletions(-) diff --git a/dmenu.c b/dmenu.c index eca67ac..22eecae 100644 --- a/dmenu.c +++ b/dmenu.c @@ -541,7 +541,7 @@ readstdin(void) { char buf[sizeof text], *p; size_t i, imax = 0, size = 0; - unsigned int tmpmax = 0; + XGlyphInfo ext; /* read each line from stdin and add it to the item list */ for (i = 0; fgets(buf, sizeof buf, stdin); i++) { @@ -553,9 +553,9 @@ readstdin(void) if (!(items[i].text = strdup(buf))) die("cannot strdup %u bytes:", strlen(buf) + 1); items[i].out = 0; - drw_font_getexts(drw->fonts, buf, strlen(buf), &tmpmax, NULL); - if (tmpmax > inputw) { - inputw = tmpmax; + XftTextExtentsUtf8(drw->fonts->dpy, drw->fonts->xfont, (XftChar8 *)buf, strlen(buf), &ext); + if (ext.xOff > inputw) { + inputw = ext.xOff; imax = i; } } diff --git a/drw.c b/drw.c index 4cdbcbe..556b79a 100644 --- a/drw.c +++ b/drw.c @@ -252,18 +252,18 @@ 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, stop = 0; + unsigned int ew = 0; XftDraw *d = NULL; Fnt *usedfont, *curfont, *nextfont; - size_t i, len; - int utf8strlen, utf8charlen, render = x || y || w || h; + size_t i, b = 0; + int utf8charlen, render = x || y || w || h; long utf8codepoint = 0; - const char *utf8str; FcCharSet *fccharset; FcPattern *fcpattern; FcPattern *match; XftResult result; + XGlyphInfo ext; int charexists = 0; if (!drw || (render && !drw->scheme) || !text || !drw->fonts) @@ -283,8 +283,8 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp usedfont = drw->fonts; while (1) { - utf8strlen = 0; - utf8str = text; + ew = 0; + b = 0; nextfont = NULL; while (*text) { utf8charlen = utf8decode(text, &utf8codepoint, UTF_SIZ); @@ -292,8 +292,14 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint); if (charexists) { if (curfont == usedfont) { - utf8strlen += utf8charlen; - text += utf8charlen; + XftTextExtentsUtf8(curfont->dpy, curfont->xfont, (XftChar8 *)text, utf8charlen, &ext); + if (ew + ext.xOff + lpad > w || b + utf8charlen > sizeof(buf) - 1) { + stop = 1; + break; + } + for (i = 0; i < utf8charlen; i++) + buf[b++] = *text++; + ew += ext.xOff; } else { nextfont = curfont; } @@ -301,36 +307,24 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp } } - if (!charexists || nextfont) + if (stop || !charexists || nextfont) break; else charexists = 0; } + buf[b] = '\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 (b) { + if (render) { + ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent; + XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg], + usedfont->xfont, x, ty, (XftChar8 *)buf, b); } + x += ew; + w -= ew; } - if (!*text) { + if (stop || !*text) { break; } else if (nextfont) { charexists = 0; @@ -397,21 +391,6 @@ drw_fontset_getwidth(Drw *drw, const char *text) 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) { diff --git a/drw.h b/drw.h index 4c67419..1e910a0 100644 --- a/drw.h +++ b/drw.h @@ -35,7 +35,6 @@ void drw_free(Drw *drw); 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 */ void drw_clr_create(Drw *drw, Clr *dest, const char *clrname); -- 2.35.1