tClean up xdraws and optimize glyph drawing with non-unit kerning values - st -… | |
git clone git://src.adamsgaard.dk/st | |
Log | |
Files | |
Refs | |
README | |
LICENSE | |
--- | |
commit ae1923d27533ff46400d93765e971558201ca1ee | |
parent 38af006b5e4a36c77f25affbb3f7e28899db75ed | |
Author: suigin <[email protected]> | |
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 | |
tthe 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(-) | |
--- | |
diff --git a/st.c b/st.c | |
t@@ -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) | |
t@@ -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 { | |
t@@ -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 */ | |
t@@ -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, in… | |
+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); | |
t@@ -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)); | |
t@@ -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… | |
+{ | |
+ 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 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; | |
} | |
t@@ -3314,22 +3436,9 @@ xdraws(char *s, Glyph base, int x, int y, int charlen, … | |
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]) { | |
t@@ -3363,7 +3472,7 @@ xdraws(char *s, Glyph base, int x, int y, int charlen, i… | |
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; | |
t@@ -3401,136 +3510,17 @@ xdraws(char *s, Glyph base, int x, int y, int charlen… | |
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, unicode… | |
- 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, unicod… | |
- /* 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); | |
} | |
t@@ -3540,11 +3530,10 @@ xdraws(char *s, Glyph base, int x, int y, int charlen,… | |
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 | |
t@@ -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)) | |
t@@ -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 - x… | |
+ | |
+ 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(); | |
} |