+/* get next utf8 char from s return its codepoint and set next_ret to pointer to next character */
+static FcChar32
+getnextutf8char(const char *s, const char **next_ret)
+{
+ /* */
+ static const unsigned char utfbyte[] = {0x80, 0x00, 0xC0, 0xE0, 0xF0};
+ /* */
+ static const unsigned char utfmask[] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
+ /* 0xFFFD is the replacement character, used to represent unknown characters */
+ static const FcChar32 utfmin[] = {0, 0x00, 0x80, 0x800, 0x10000};
+ static const FcChar32 utfmax[] = {0, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
+ static const FcChar32 unknown = 0xFFFD;
+ FcChar32 ucode; /* FcChar32 type holds 32 bits */
+ size_t usize = 0; /* n' of bytes of the utf8 character */
+ size_t i;
+
+ *next_ret = s+1;
+
+ /* get code of first byte of utf8 character */
+ for (i = 0; i < sizeof utfmask; i++) {
+ if (((unsigned char)*s & utfmask[i]) == utfbyte[i]) {
+ usize = i;
+ ucode = (unsigned char)*s & ~utfmask[i];
+ break;
+ }
+ }
+
+ /* if first byte is a continuation byte or is not allowed, return unknown */
+ if (i == sizeof utfmask || usize == 0)
+ return unknown;
+
+ /* check the other usize-1 bytes */
+ s++;
+ for (i = 1; i < usize; i++) {
+ *next_ret = s+1;
+ /* if byte is EOS or is not a continuation byte, return unknown */
+ if (*s == '\0' || ((unsigned char)*s & utfmask[0]) != utfbyte[0])
+ return unknown;
+ /* 6 is the number of relevant bits in the continuation byte */
+ ucode = (ucode << 6) | ((unsigned char)*s & ~utfmask[0]);
+ s++;
+ }
+
+ /* check if ucode is invalid or in utf-16 surrogate halves */
+ if (!BETWEEN(ucode, utfmin[usize], utfmax[usize])
+ || BETWEEN (ucode, 0xD800, 0xDFFF))
+ return unknown;
+
+ return ucode;
+}
+
+/* draw text into XftDraw */
+static int
+drawtext(XftDraw *draw, XftColor *color, int x, int y, unsigned h, const char *text)
+{
+ const char *s, *nexts;
+ FcChar32 ucode;
+ XftFont *currfont;
+ int textlen = 0;
+
+ s = text;
+ while (*s) {
+ XGlyphInfo ext;
+ int charexists;
+ size_t len;
+ size_t i;
+
+ charexists = 0;
+ ucode = getnextutf8char(s, &nexts);
+ for (i = 0; i < dc.nfonts; i++) {
+ charexists = XftCharExists(dpy, dc.fonts[i], ucode);
+ if (charexists)
+ break;
+ }
+ if (charexists)
+ currfont = dc.fonts[i];
+
+ len = nexts - s;
+
+ XftTextExtentsUtf8(dpy, currfont, (XftChar8 *)s,
+ len, &ext);
+ textlen += ext.xOff;
+
+ if (draw) {
+ int texty;
+
+ texty = y + (h + currfont->ascent) / 2;
+ XftDrawStringUtf8(draw, color, currfont, x, texty,
+ (XftChar8 *)s, len);
+ x += ext.xOff;
+ }
+
+ s = nexts;
+ }
+
+ return textlen;
+}
+
+/* draw pixmap for the selected and unselected version of each item on menu */
+static void
+drawitems(struct Menu *menu)
+{
+ struct Item *item;
+
+ for (item = menu->list; item != NULL; item = item->next) {
+ XftDraw *dsel, *dunsel;
+ int x, y;
+
+ item->unsel = XCreatePixmap(dpy, menu->win, menu->w, item->h,
+ DefaultDepth(dpy, screen));
+
+ XSetForeground(dpy, dc.gc, dc.normal[ColorBG].pixel);
+ XFillRectangle(dpy, item->unsel, dc.gc, 0, 0, menu->w, item->h);
+
+ if (item->label == NULL) { /* item is separator */
+ y = item->h/2;
+ XSetForeground(dpy, dc.gc, dc.separator.pixel);
+ XDrawLine(dpy, item->unsel, dc.gc, config.horzpadding, y,
+ menu->w - config.horzpadding, y);
+
+ item->sel = item->unsel;
+ } else {
+
+ item->sel = XCreatePixmap(dpy, menu->win, menu->w, item->h,
+ DefaultDepth(dpy, screen));
+ XSetForeground(dpy, dc.gc, dc.selected[ColorBG].pixel);
+ XFillRectangle(dpy, item->sel, dc.gc, 0, 0, menu->w, item->h);
+
+ /* draw text */
+ x = config.horzpadding;
+ x += (iflag) ? 0 : config.horzpadding + config.iconsize;
+ dsel = XftDrawCreate(dpy, item->sel, visual, colormap);
+ dunsel = XftDrawCreate(dpy, item->unsel, visual, colormap);
+ XSetForeground(dpy, dc.gc, dc.selected[ColorFG].pixel);
+ drawtext(dsel, &dc.selected[ColorFG], x, 0, item->h, item->label);
+ XSetForeground(dpy, dc.gc, dc.normal[ColorFG].pixel);
+ drawtext(dunsel, &dc.normal[ColorFG], x, 0, item->h, item->label);
+ XftDrawDestroy(dsel);
+ XftDrawDestroy(dunsel);
+
+ /* draw triangle */
+ if (item->submenu != NULL) {
+ x = menu->w - config.triangle_width - config.horzpadding;
+ y = (item->h - config.triangle_height + 1) / 2;
+
+ XPoint triangle[] = {
+ {x, y},
+ {x + config.triangle_width, y + config.triangle_height/2},
+ {x, y + config.triangle_height},
+ {x, y}
+ };
+
+ XSetForeground(dpy, dc.gc, dc.selected[ColorFG].pixel);
+ XFillPolygon(dpy, item->sel, dc.gc, triangle, LEN(triangle),
+ Convex, CoordModeOrigin);
+ XSetForeground(dpy, dc.gc, dc.normal[ColorFG].pixel);
+ XFillPolygon(dpy, item->unsel, dc.gc, triangle, LEN(triangle),
+ Convex, CoordModeOrigin);
+ }
+
+ /* draw icon */
+ if (item->file != NULL && !iflag) {
+ item->icon = loadicon(item->file);
+
+ imlib_context_set_drawable(item->sel);
+ imlib_context_set_image(item->icon);
+ imlib_render_image_on_drawable(config.horzpadding, config.iconpadding);
+
+ imlib_context_set_drawable(item->unsel);
+ imlib_context_set_image(item->icon);
+ imlib_render_image_on_drawable(config.horzpadding, config.iconpadding);
+ }
+ }
+ }
+}
+
+/* setup the height, width and icon of the items of a menu */