diff --git a/st.c b/st.c index 4add638..c2da66b 100644 --- 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(); }