Clean up xdraws and optimize glyph drawing with non-unit kerning values - st - Personal fork of st
 (HTM) git clone git://git.drkhsh.at/st.git
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit ae1923d27533ff46400d93765e971558201ca1ee
 (DIR) parent 38af006b5e4a36c77f25affbb3f7e28899db75ed
 (HTM) Author: suigin <suigin@national.shitposting.agency>
       Date:   Tue,  5 May 2015 20:24:00 -0700
       
       Clean up xdraws and optimize glyph drawing with non-unit kerning values
       
       I have another patch here for review that optimizes the performance of
       glyph drawing, primarily when using non-unit kerning values, and fixes a
       few other minor issues. It's dependent on the earlier patch from me that
       stores unicode codepoints in a Rune type, typedef'd to uint_least32_t.
       
       This patch is a pretty big change to xdraws so your scrutiny is
       appreciated.
       
       First, some performance numbers. I used Yu-Jie Lin termfps.sh shell
       script to benchmark before and after, and you can find it in the
       attachments. On my Kaveri A10 7850k machine, I get the following
       results:
       
       Before Patch
       ============
       
       1) Font: "Liberation Mono:pixelsize=12:antialias=false:autohint=false"
          cwscale: 1.0, chscale: 1.0
          For 273x83 100 frames.
          Elapsed time :     1.553
          Frames/second:    64.352
          Chars /second: 1,458,159
       
       2) Font: "Inconsolata:pixelsize=14:antialias=true:autohint=true"
          cwscale: 1.001, chscale: 1.001
          For 239x73 100 frames.
          Elapsed time :   159.286
          Frames/second:     0.627
          Chars /second:    10,953
       
       After Patch
       ===========
       
       3) Font: "Liberation Mono:pixelsize=12:antialias=false:autohint=false"
          cwscale: 1.0, chscale: 1.0
          For 273x83 100 frames.
          Elapsed time :     1.544
          Frames/second:    64.728
          Chars /second: 1,466,690
       
       4) Font: "Inconsolata:pixelsize=14:antialias=true:autohint=true"
          cwscale: 1.001, chscale: 1.001
          For 239x73 100 frames.
          Elapsed time :     1.955
          Frames/second:    51.146
          Chars /second:   892,361
       
       As you can see, while the improvements for fonts with unit-kerning is
       marginal, there's a huge ~81x performance increase with the patch when
       using kerning values other than 1.0.
       
       So what does the patch do?
       
       The `xdraws' function would render each glyph one at a time if non-unit
       kerning values were configured, and this was the primary cause of the
       slow down. Xft provides a handful of functions which allow you to render
       multiple characters or glyphs at time, each with a unique <x,y> position,
       so it was simply a matter of massaging the data into a format that would
       allow us to use one of these functions.
       
       I've split `xdraws' up into two functions. In the first pass with
       `xmakeglyphfontspecs' it will iterate over all of the glyphs in a given
       row and it will build up an array of corresponding XftGlyphFontSpec
       records. Much of the old logic for resolving fonts for glyphs using Xft
       and fontconfig went into this function.
       
       The second pass is done with `xrenderglyphfontspecs' which contains the
       old logic for determining colors, clearing the background, and finally
       rendering the array of XftGlyphFontSpec records.
       
       There's a couple of other things that have been improved by this patch.
       For instance, the UTF-32 codepoints in the Line's were being re-encoded
       back into UTF-8 strings to be passed to `xdraws' which in turn would then
       decode back to UTF-32 to verify that the Font contained a matching glyph
       for the code point. Next, the UTF-8 string was being passed to
       `XftDrawStringUtf8' which internally mallocs a scratch buffer and decodes
       back to UTF-32 and does the lookup of the glyphs all over again.
       
       This patch gets rid of all of this redundant round-trip encoding and
       decoding of characters to be rendered and only looks up the glyph index
       once (per font) during the font resolution phase. So this is probably
       what's responsible for the marginal improvements seen when kerning values
       are kept to 1.0.
       
       I imagine there are other performance improvements here too, not seen in
       the above benchmarks, if the user has lots of non-ASCII code plane characters
       on the screen, or several different fonts are being utilized during
       screen redraw.
       
       Anyway, if you see any problems, please let me know and I can fix them.
       
       Diffstat:
         M st.c                                |     383 +++++++++++++++----------------
       
       1 file changed, 187 insertions(+), 196 deletions(-)
       ---
 (DIR) diff --git a/st.c b/st.c
       @@ -58,7 +58,6 @@ char *argv0;
        #define ESC_ARG_SIZ   16
        #define STR_BUF_SIZ   ESC_BUF_SIZ
        #define STR_ARG_SIZ   ESC_ARG_SIZ
       -#define DRAW_BUF_SIZ  20*1024
        #define XK_ANY_MOD    UINT_MAX
        #define XK_NO_MOD     0
        #define XK_SWITCH_MOD (1<<13)
       @@ -87,18 +86,19 @@ char *argv0;
        
        
        enum glyph_attribute {
       -        ATTR_NULL      = 0,
       -        ATTR_BOLD      = 1 << 0,
       -        ATTR_FAINT     = 1 << 1,
       -        ATTR_ITALIC    = 1 << 2,
       -        ATTR_UNDERLINE = 1 << 3,
       -        ATTR_BLINK     = 1 << 4,
       -        ATTR_REVERSE   = 1 << 5,
       -        ATTR_INVISIBLE = 1 << 6,
       -        ATTR_STRUCK    = 1 << 7,
       -        ATTR_WRAP      = 1 << 8,
       -        ATTR_WIDE      = 1 << 9,
       -        ATTR_WDUMMY    = 1 << 10,
       +        ATTR_NULL       = 0,
       +        ATTR_BOLD       = 1 << 0,
       +        ATTR_FAINT      = 1 << 1,
       +        ATTR_ITALIC     = 1 << 2,
       +        ATTR_UNDERLINE  = 1 << 3,
       +        ATTR_BLINK      = 1 << 4,
       +        ATTR_REVERSE    = 1 << 5,
       +        ATTR_INVISIBLE  = 1 << 6,
       +        ATTR_STRUCK     = 1 << 7,
       +        ATTR_WRAP       = 1 << 8,
       +        ATTR_WIDE       = 1 << 9,
       +        ATTR_WDUMMY     = 1 << 10,
       +        ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT,
        };
        
        enum cursor_movement {
       @@ -232,6 +232,7 @@ typedef struct {
                Line *line;   /* screen */
                Line *alt;    /* alternate screen */
                bool *dirty;  /* dirtyness of lines */
       +        XftGlyphFontSpec *specbuf; /* font spec buffer used for rendering */
                TCursor c;    /* cursor */
                int top;      /* top    scroll limit */
                int bot;      /* bottom scroll limit */
       @@ -418,7 +419,8 @@ static void ttywrite(const char *, size_t);
        static void tstrsequence(uchar);
        
        static inline ushort sixd_to_16bit(int);
       -static void xdraws(char *, Glyph, int, int, int, int);
       +static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int);
       +static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int);
        static void xdrawglyph(Glyph, int, int);
        static void xhints(void);
        static void xclear(int, int, int, int);
       @@ -2819,6 +2821,9 @@ tresize(int col, int row) {
                        free(term.alt[i]);
                }
        
       +        /* resize to new width */
       +        term.specbuf = xrealloc(term.specbuf, col * sizeof(XftGlyphFontSpec));
       +
                /* resize to new height */
                term.line = xrealloc(term.line, row * sizeof(Line));
                term.alt  = xrealloc(term.alt,  row * sizeof(Line));
       @@ -3257,38 +3262,155 @@ xinit(void) {
                XSync(xw.dpy, False);
        }
        
       -void
       -xdraws(char *s, Glyph base, int x, int y, int charlen, int bytelen) {
       -        int winx = borderpx + x * xw.cw, winy = borderpx + y * xw.ch,
       -            width = charlen * xw.cw, xp, i;
       -        int frcflags, charexists;
       -        int u8fl, u8fblen, u8cblen, doesexist;
       -        char *u8c, *u8fs;
       -        Rune unicodep;
       +int
       +xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y)
       +{
       +        float winx = borderpx + x * xw.cw, winy = borderpx + y * xw.ch, xp, yp;
       +        ushort mode, prevmode = USHRT_MAX;
                Font *font = &dc.font;
       +        int frcflags = FRC_NORMAL;
       +        float runewidth = xw.cw;
       +        Rune rune;
       +        FT_UInt glyphidx;
                FcResult fcres;
                FcPattern *fcpattern, *fontpattern;
                FcFontSet *fcsets[] = { NULL };
                FcCharSet *fccharset;
       +        int i, f, numspecs = 0;
       +
       +        for(i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) {
       +                /* Fetch rune and mode for current glyph. */
       +                rune = glyphs[i].u;
       +                mode = glyphs[i].mode;
       +
       +                /* Skip dummy wide-character spacing. */
       +                if(mode == ATTR_WDUMMY)
       +                        continue;
       +
       +                /* Determine font for glyph if different from previous glyph. */
       +                if(prevmode != mode) {
       +                        prevmode = mode;
       +                        font = &dc.font;
       +                        frcflags = FRC_NORMAL;
       +                        runewidth = xw.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f);
       +                        if((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) {
       +                                font = &dc.ibfont;
       +                                frcflags = FRC_ITALICBOLD;
       +                        } else if(mode & ATTR_ITALIC) {
       +                                font = &dc.ifont;
       +                                frcflags = FRC_ITALIC;
       +                        } else if(mode & ATTR_BOLD) {
       +                                font = &dc.bfont;
       +                                frcflags = FRC_BOLD;
       +                        }
       +                        yp = winy + font->ascent;
       +                }
       +
       +                /* Lookup character index with default font. */
       +                glyphidx = XftCharIndex(xw.dpy, font->match, rune);
       +                if(glyphidx) {
       +                        specs[numspecs].font = font->match;
       +                        specs[numspecs].glyph = glyphidx;
       +                        specs[numspecs].x = (short)xp;
       +                        specs[numspecs].y = (short)yp;
       +                        xp += runewidth;
       +                        numspecs++;
       +                        continue;
       +                }
       +
       +                /* Fallback on font cache, search the font cache for match. */
       +                for(f = 0; f < frclen; f++) {
       +                        glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune);
       +                        /* Everything correct. */
       +                        if(glyphidx && frc[f].flags == frcflags)
       +                                break;
       +                        /* We got a default font for a not found glyph. */
       +                        if(!glyphidx && frc[f].flags == frcflags
       +                                        && frc[f].unicodep == rune) {
       +                                break;
       +                        }
       +                }
       +
       +                /* Nothing was found. Use fontconfig to find matching font. */
       +                if(f >= frclen) {
       +                        if(!font->set)
       +                                font->set = FcFontSort(0, font->pattern,
       +                                                       FcTrue, 0, &fcres);
       +                        fcsets[0] = font->set;
       +
       +                        /*
       +                         * Nothing was found in the cache. Now use
       +                         * some dozen of Fontconfig calls to get the
       +                         * font for one single character.
       +                         *
       +                         * Xft and fontconfig are design failures.
       +                         */
       +                        fcpattern = FcPatternDuplicate(font->pattern);
       +                        fccharset = FcCharSetCreate();
       +
       +                        FcCharSetAddChar(fccharset, rune);
       +                        FcPatternAddCharSet(fcpattern, FC_CHARSET,
       +                                        fccharset);
       +                        FcPatternAddBool(fcpattern, FC_SCALABLE,
       +                                        FcTrue);
       +
       +                        FcConfigSubstitute(0, fcpattern,
       +                                        FcMatchPattern);
       +                        FcDefaultSubstitute(fcpattern);
       +
       +                        fontpattern = FcFontSetMatch(0, fcsets, 1,
       +                                        fcpattern, &fcres);
       +
       +                        /*
       +                         * Overwrite or create the new cache entry.
       +                         */
       +                        if(frclen >= LEN(frc)) {
       +                                frclen = LEN(frc) - 1;
       +                                XftFontClose(xw.dpy, frc[frclen].font);
       +                                frc[frclen].unicodep = 0;
       +                        }
       +
       +                        frc[frclen].font = XftFontOpenPattern(xw.dpy,
       +                                        fontpattern);
       +                        frc[frclen].flags = frcflags;
       +                        frc[frclen].unicodep = rune;
       +
       +                        glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune);
       +
       +                        f = frclen;
       +                        frclen++;
       +
       +                        FcPatternDestroy(fcpattern);
       +                        FcCharSetDestroy(fccharset);
       +                }
       +
       +                specs[numspecs].font = frc[f].font;
       +                specs[numspecs].glyph = glyphidx;
       +                specs[numspecs].x = (short)xp;
       +                specs[numspecs].y = (short)(winy + frc[f].font->ascent);
       +                xp += runewidth;
       +                numspecs++;
       +        }
       +
       +        return numspecs;
       +}
       +
       +void
       +xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) {
       +        int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1);
       +        int winx = borderpx + x * xw.cw, winy = borderpx + y * xw.ch,
       +            width = charlen * xw.cw;
                Color *fg, *bg, *temp, revfg, revbg, truefg, truebg;
                XRenderColor colfg, colbg;
                XRectangle r;
       -        int oneatatime;
        
       -        frcflags = FRC_NORMAL;
       -
       -        if(base.mode & ATTR_ITALIC) {
       -                if(base.fg == defaultfg)
       +        /* Determine foreground and background colors based on mode. */
       +        if(base.fg == defaultfg) {
       +                if(base.mode & ATTR_ITALIC)
                                base.fg = defaultitalic;
       -                font = &dc.ifont;
       -                frcflags = FRC_ITALIC;
       -        } else if((base.mode & ATTR_ITALIC) && (base.mode & ATTR_BOLD)) {
       -                if(base.fg == defaultfg)
       +                else if((base.mode & ATTR_ITALIC) && (base.mode & ATTR_BOLD))
                                base.fg = defaultitalic;
       -                font = &dc.ibfont;
       -                frcflags = FRC_ITALICBOLD;
       -        } else if(base.mode & ATTR_UNDERLINE) {
       -                if(base.fg == defaultfg)
       +                else if(base.mode & ATTR_UNDERLINE)
                                base.fg = defaultunderline;
                }
        
       @@ -3314,22 +3436,9 @@ xdraws(char *s, Glyph base, int x, int y, int charlen, int bytelen) {
                        bg = &dc.col[base.bg];
                }
        
       -        if(base.mode & ATTR_BOLD) {
       -                /*
       -                 * change basic system colors [0-7]
       -                 * to bright system colors [8-15]
       -                 */
       -                if(BETWEEN(base.fg, 0, 7) && !(base.mode & ATTR_FAINT))
       -                        fg = &dc.col[base.fg + 8];
       -
       -                if(base.mode & ATTR_ITALIC) {
       -                        font = &dc.ibfont;
       -                        frcflags = FRC_ITALICBOLD;
       -                } else {
       -                        font = &dc.bfont;
       -                        frcflags = FRC_BOLD;
       -                }
       -        }
       +        /* Change basic system colors [0-7] to bright system colors [8-15] */
       +        if((base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7))
       +                fg = &dc.col[base.fg + 8];
        
                if(IS_SET(MODE_REVERSE)) {
                        if(fg == &dc.col[defaultfg]) {
       @@ -3363,7 +3472,7 @@ xdraws(char *s, Glyph base, int x, int y, int charlen, int bytelen) {
                        bg = temp;
                }
        
       -        if(base.mode & ATTR_FAINT && !(base.mode & ATTR_BOLD)) {
       +        if((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT) {
                        colfg.red = fg->color.red / 2;
                        colfg.green = fg->color.green / 2;
                        colfg.blue = fg->color.blue / 2;
       @@ -3401,136 +3510,17 @@ xdraws(char *s, Glyph base, int x, int y, int charlen, int bytelen) {
                r.width = width;
                XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1);
        
       -        for(xp = winx; bytelen > 0;) {
       -                /*
       -                 * Search for the range in the to be printed string of glyphs
       -                 * that are in the main font. Then print that range. If
       -                 * some glyph is found that is not in the font, do the
       -                 * fallback dance.
       -                 */
       -                u8fs = s;
       -                u8fblen = 0;
       -                u8fl = 0;
       -                oneatatime = font->width != xw.cw;
       -                for(;;) {
       -                        u8c = s;
       -                        u8cblen = utf8decode(s, &unicodep, UTF_SIZ);
       -                        s += u8cblen;
       -                        bytelen -= u8cblen;
       -
       -                        doesexist = XftCharExists(xw.dpy, font->match, unicodep);
       -                        if(doesexist) {
       -                                        u8fl++;
       -                                        u8fblen += u8cblen;
       -                                        if(!oneatatime && bytelen > 0)
       -                                                        continue;
       -                        }
       -
       -                        if(u8fl > 0) {
       -                                XftDrawStringUtf8(xw.draw, fg,
       -                                                font->match, xp,
       -                                                winy + font->ascent,
       -                                                (FcChar8 *)u8fs,
       -                                                u8fblen);
       -                                xp += xw.cw * u8fl;
       -                        }
       -                        break;
       -                }
       -                if(doesexist) {
       -                        if(oneatatime)
       -                                continue;
       -                        break;
       -                }
       -
       -                /* Search the font cache. */
       -                for(i = 0; i < frclen; i++) {
       -                        charexists = XftCharExists(xw.dpy, frc[i].font, unicodep);
       -                        /* Everything correct. */
       -                        if(charexists && frc[i].flags == frcflags)
       -                                break;
       -                        /* We got a default font for a not found glyph. */
       -                        if(!charexists && frc[i].flags == frcflags \
       -                                        && frc[i].unicodep == unicodep) {
       -                                break;
       -                        }
       -                }
       -
       -                /* Nothing was found. */
       -                if(i >= frclen) {
       -                        if(!font->set)
       -                                font->set = FcFontSort(0, font->pattern,
       -                                                       FcTrue, 0, &fcres);
       -                        fcsets[0] = font->set;
       -
       -                        /*
       -                         * Nothing was found in the cache. Now use
       -                         * some dozen of Fontconfig calls to get the
       -                         * font for one single character.
       -                         *
       -                         * Xft and fontconfig are design failures.
       -                         */
       -                        fcpattern = FcPatternDuplicate(font->pattern);
       -                        fccharset = FcCharSetCreate();
       -
       -                        FcCharSetAddChar(fccharset, unicodep);
       -                        FcPatternAddCharSet(fcpattern, FC_CHARSET,
       -                                        fccharset);
       -                        FcPatternAddBool(fcpattern, FC_SCALABLE,
       -                                        FcTrue);
       -
       -                        FcConfigSubstitute(0, fcpattern,
       -                                        FcMatchPattern);
       -                        FcDefaultSubstitute(fcpattern);
       -
       -                        fontpattern = FcFontSetMatch(0, fcsets, 1,
       -                                        fcpattern, &fcres);
       -
       -                        /*
       -                         * Overwrite or create the new cache entry.
       -                         */
       -                        if(frclen >= LEN(frc)) {
       -                                frclen = LEN(frc) - 1;
       -                                XftFontClose(xw.dpy, frc[frclen].font);
       -                                frc[frclen].unicodep = 0;
       -                        }
       -
       -                        frc[frclen].font = XftFontOpenPattern(xw.dpy,
       -                                        fontpattern);
       -                        frc[frclen].flags = frcflags;
       -                        frc[frclen].unicodep = unicodep;
       -
       -                        i = frclen;
       -                        frclen++;
       -
       -                        FcPatternDestroy(fcpattern);
       -                        FcCharSetDestroy(fccharset);
       -                }
       -
       -                XftDrawStringUtf8(xw.draw, fg, frc[i].font,
       -                                xp, winy + frc[i].font->ascent,
       -                                (FcChar8 *)u8c, u8cblen);
       -
       -                xp += xw.cw * wcwidth(unicodep);
       -        }
       -
       -        /*
       -         * This is how the loop above actually should be. Why does the
       -         * application have to care about font details?
       -         *
       -         * I have to repeat: Xft and Fontconfig are design failures.
       -         */
       -        /*
       -        XftDrawStringUtf8(xw.draw, fg, font->set, winx,
       -                        winy + font->ascent, (FcChar8 *)s, bytelen);
       -        */
       +        /* Render the glyphs. */
       +        XftDrawGlyphFontSpec(xw.draw, fg, specs, len);
        
       +        /* Render underline and strikethrough. */
                if(base.mode & ATTR_UNDERLINE) {
       -                XftDrawRect(xw.draw, fg, winx, winy + font->ascent + 1,
       +                XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent + 1,
                                        width, 1);
                }
        
                if(base.mode & ATTR_STRUCK) {
       -                XftDrawRect(xw.draw, fg, winx, winy + 2 * font->ascent / 3,
       +                XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent / 3,
                                        width, 1);
                }
        
       @@ -3540,11 +3530,10 @@ xdraws(char *s, Glyph base, int x, int y, int charlen, int bytelen) {
        
        void
        xdrawglyph(Glyph g, int x, int y) {
       -        static char buf[UTF_SIZ];
       -        size_t len = utf8encode(g.u, buf);
       -        int width = g.mode & ATTR_WIDE ? 2 : 1;
       -
       -        xdraws(buf, g, x, y, width, len);
       +        int numspecs;
       +        XftGlyphFontSpec spec;
       +        numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y);
       +        xdrawglyphfontspecs(&spec, g, numspecs, x, y);
        }
        
        void
       @@ -3658,9 +3647,9 @@ draw(void) {
        
        void
        drawregion(int x1, int y1, int x2, int y2) {
       -        int ic, ib, x, y, ox;
       +        int i, x, y, ox, numspecs;
                Glyph base, new;
       -        char buf[DRAW_BUF_SIZ];
       +        XftGlyphFontSpec* specs;
                bool ena_sel = sel.ob.x != -1 && sel.alt == IS_SET(MODE_ALTSCREEN);
        
                if(!(xw.state & WIN_VISIBLE))
       @@ -3672,29 +3661,31 @@ drawregion(int x1, int y1, int x2, int y2) {
        
                        xtermclear(0, y, term.col, y);
                        term.dirty[y] = 0;
       -                base = term.line[y][0];
       -                ic = ib = ox = 0;
       -                for(x = x1; x < x2; x++) {
       +
       +                specs = term.specbuf;
       +                numspecs = xmakeglyphfontspecs(specs, &term.line[y][0], x2 - x1, x1, y);
       +
       +                i = ox = 0;
       +                for(x = x1; x < x2 && i < numspecs; x++) {
                                new = term.line[y][x];
                                if(new.mode == ATTR_WDUMMY)
                                        continue;
                                if(ena_sel && selected(x, y))
                                        new.mode ^= ATTR_REVERSE;
       -                        if(ib > 0 && (ATTRCMP(base, new)
       -                                        || ib >= DRAW_BUF_SIZ-UTF_SIZ)) {
       -                                xdraws(buf, base, ox, y, ic, ib);
       -                                ic = ib = 0;
       +                        if(i > 0 && ATTRCMP(base, new)) {
       +                                xdrawglyphfontspecs(specs, base, i, ox, y);
       +                                specs += i;
       +                                numspecs -= i;
       +                                i = 0;
                                }
       -                        if(ib == 0) {
       +                        if(i == 0) {
                                        ox = x;
                                        base = new;
                                }
       -
       -                        ib += utf8encode(new.u, buf+ib);
       -                        ic += (new.mode & ATTR_WIDE)? 2 : 1;
       +                        i++;
                        }
       -                if(ib > 0)
       -                        xdraws(buf, base, ox, y, ic, ib);
       +                if(i > 0)
       +                        xdrawglyphfontspecs(specs, base, i, ox, y);
                }
                xdrawcursor();
        }