diff --git a/st.c b/st.c index 3dd5caf..82952ce 100644 --- a/st.c +++ b/st.c @@ -98,6 +98,7 @@ enum glyph_attribute { ATTR_WRAP = 1 << 8, ATTR_WIDE = 1 << 9, ATTR_WDUMMY = 1 << 10, + ATTR_DECOMPPTR = 1 << 11, ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, }; @@ -190,6 +191,10 @@ typedef XftColor Color; typedef struct { Rune u; /* character code */ + union { + Rune c[2]; + Rune *pc; + } dc; ushort mode; /* attribute flags */ uint32_t fg; /* foreground */ uint32_t bg; /* background */ @@ -233,6 +238,7 @@ typedef struct { Line *alt; /* alternate screen */ bool *dirty; /* dirtyness of lines */ XftGlyphFontSpec *specbuf; /* font spec buffer used for rendering */ + int specbuflen; TCursor c; /* cursor */ int top; /* top scroll limit */ int bot; /* bottom scroll limit */ @@ -399,6 +405,7 @@ static void tscrollup(int, int); static void tscrolldown(int, int); static void tsetattr(int *, int); static void tsetchar(Rune, Glyph *, int, int); +static void taddcchar(Rune, int, int); static void tsetscroll(int, int); static void tswapscreen(void); static void tsetdirt(int, int); @@ -419,8 +426,9 @@ static void ttywrite(const char *, size_t); static void tstrsequence(uchar); static inline ushort sixd_to_16bit(int); -static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); -static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); +static int xmakeglyphfontspecs(XftGlyphFontSpec *, int, const Glyph *, int, int, int); +static int xmakeglyphfontspecsintermbuf(const Glyph *, int, int, int); +static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int, int); static void xdrawglyph(Glyph, int, int); static void xhints(void); static void xclear(int, int, int, int); @@ -957,11 +965,12 @@ getsel(void) { char *str, *ptr; int y, bufsize, lastx, linelen; Glyph *gp, *last; + Rune *pc; if(sel.ob.x == -1) return NULL; - bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ; + bufsize = (term.specbuflen+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ; ptr = str = xmalloc(bufsize); /* append every set & selected glyph to the selection */ @@ -984,6 +993,16 @@ getsel(void) { continue; ptr += utf8encode(gp->u, ptr); + if(gp->mode & ATTR_DECOMPPTR) { + pc = gp->dc.pc; + for(pc = gp->dc.pc; *pc; pc++) + ptr += utf8encode(*pc, ptr); + } else { + if(gp->dc.c[0]) + ptr += utf8encode(gp->dc.c[0], ptr); + if(gp->dc.c[1]) + ptr += utf8encode(gp->dc.c[1], ptr); + } } /* @@ -1681,6 +1700,58 @@ tsetchar(Rune u, Glyph *attr, int x, int y) { } void +taddcchar(Rune u, int x, int y) { + Rune *p; + int sz; + + if(!u) + return; + + /* don't add combining chars to wide dummies, add them to the real char */ + if(term.line[y][x].mode == ATTR_WDUMMY) { + if(x == 0) { + if(y > 0) { + y--; + x = term.col-1; + } else { + /* this shouldn't happen, but if it does... */ + return; + } + } else { + x--; + } + } + /* can this happen? */ + if(term.line[y][x].mode == ATTR_WDUMMY) + return; + + term.dirty[y] = 1; + if(term.line[y][x].mode & ATTR_DECOMPPTR) { + p = term.line[y][x].dc.pc; + while(*p) + p++; + sz = (p - term.line[y][x].dc.pc) + 2; + term.line[y][x].dc.pc = xrealloc(term.line[y][x].dc.pc, sz * sizeof(Rune)); + term.line[y][x].dc.pc[sz-2] = u; + term.line[y][x].dc.pc[sz-1] = 0; + } else { + if(!term.line[y][x].dc.c[0]) { + term.line[y][x].dc.c[0] = u; + } else if(!term.line[y][x].dc.c[1]) { + term.line[y][x].dc.c[1] = u; + } else { + p = xmalloc(4 * sizeof(Rune)); + p[0] = term.line[y][x].dc.c[0]; + p[1] = term.line[y][x].dc.c[1]; + p[2] = u; + p[3] = 0; + term.line[y][x].dc.pc = p; + term.line[y][x].mode |= ATTR_DECOMPPTR; + } + } +} + +void tclearregion(int x1, int y1, int x2, int y2) { int x, y, temp; Glyph *gp; @@ -1703,7 +1774,11 @@ tclearregion(int x1, int y1, int x2, int y2) { selclear(NULL); gp->fg = term.c.attr.fg; gp->bg = term.c.attr.bg; + if(gp->mode & ATTR_DECOMPPTR) + free(gp->dc.pc); gp->mode = 0; + gp->dc.c[0] = 0; + gp->dc.c[1] = 0; gp->u = ' '; } } @@ -1711,7 +1786,7 @@ tclearregion(int x1, int y1, int x2, int y2) { void tdeletechar(int n) { - int dst, src, size; + int dst, src, size, i; Glyph *line; LIMIT(n, 0, term.col - term.c.x); @@ -1721,13 +1796,17 @@ tdeletechar(int n) { size = term.col - src; line = term.line[term.c.y]; + for(i = dst; i < src; i++) { + if(line[i].mode & ATTR_DECOMPPTR) + line[i].dc.pc = NULL; + } memmove(&line[dst], &line[src], size * sizeof(Glyph)); tclearregion(term.col-n, term.c.y, term.col-1, term.c.y); } void tinsertblank(int n) { - int dst, src, size; + int dst, src, size, i; Glyph *line; LIMIT(n, 0, term.col - term.c.x); @@ -1737,6 +1816,10 @@ tinsertblank(int n) { size = term.col - dst; line = term.line[term.c.y]; + for(i = src; i < dst; i++) { + if(line[i].mode & ATTR_DECOMPPTR) + line[i].dc.pc = NULL; + } memmove(&line[dst], &line[src], size * sizeof(Glyph)); tclearregion(src, term.c.y, dst - 1, term.c.y); } @@ -2774,6 +2857,19 @@ tputc(Rune u) { if(sel.ob.x != -1 && BETWEEN(term.c.y, sel.ob.y, sel.oe.y)) selclear(NULL); + /* combining chars are added to the previous cell */ + if(width == 0) { + if(term.c.x == 0) { + if(term.c.y == 0) + taddcchar(u, 0, 0); + else + taddcchar(u, term.col-1, term.c.y-1); + } else { + taddcchar(u, term.c.x-1, term.c.y); + } + return; + } + gp = &term.line[term.c.y][term.c.x]; if(IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) { gp->mode |= ATTR_WRAP; @@ -2807,7 +2903,7 @@ tputc(Rune u) { void tresize(int col, int row) { - int i; + int i, j; int minrow = MIN(row, term.row); int mincol = MIN(col, term.col); bool *bp; @@ -2825,6 +2921,12 @@ tresize(int col, int row) { * memmove because we're freeing the earlier lines */ for(i = 0; i <= term.c.y - row; i++) { + for(j = 0; j < term.col; j++) { + if(term.line[i][j].mode & ATTR_DECOMPPTR) + free(term.line[i][j].dc.pc); + if(term.alt[i][j].mode & ATTR_DECOMPPTR) + free(term.alt[i][j].dc.pc); + } free(term.line[i]); free(term.alt[i]); } @@ -2834,12 +2936,19 @@ tresize(int col, int row) { memmove(term.alt, term.alt + i, row * sizeof(Line)); } for(i += row; i < term.row; i++) { + for(j = 0; j < term.col; j++) { + if(term.line[i][j].mode & ATTR_DECOMPPTR) + free(term.line[i][j].dc.pc); + if(term.alt[i][j].mode & ATTR_DECOMPPTR) + free(term.alt[i][j].dc.pc); + } free(term.line[i]); free(term.alt[i]); } /* resize to new width */ - term.specbuf = xrealloc(term.specbuf, col * sizeof(XftGlyphFontSpec)); + term.specbuflen = col * 2; + term.specbuf = xrealloc(term.specbuf, term.specbuflen * sizeof(XftGlyphFontSpec)); /* resize to new height */ term.line = xrealloc(term.line, row * sizeof(Line)); @@ -2849,14 +2958,26 @@ tresize(int col, int row) { /* resize each row to new width, zero-pad if needed */ for(i = 0; i < minrow; i++) { + for(j = col; j < term.col; j++) { + if(term.line[i][j].mode & ATTR_DECOMPPTR) + free(term.line[i][j].dc.pc); + if(term.alt[i][j].mode & ATTR_DECOMPPTR) + free(term.alt[i][j].dc.pc); + } term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph)); + if(col > term.col) { + memset(&term.line[i][term.col], 0, (col - term.col) * sizeof(Glyph)); + memset(&term.alt[i][term.col], 0, (col - term.col) * sizeof(Glyph)); + } } /* allocate any new rows */ for(/* i == minrow */; i < row; i++) { term.line[i] = xmalloc(col * sizeof(Glyph)); term.alt[i] = xmalloc(col * sizeof(Glyph)); + memset(term.line[i], 0, col * sizeof(Glyph)); + memset(term.alt[i], 0, col * sizeof(Glyph)); } if(col > term.col) { bp = term.tabs + term.col; @@ -3280,12 +3401,13 @@ xinit(void) { } int -xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) +xmakeglyphfontspecs(XftGlyphFontSpec *specs, int nspecs, const Glyph *glyphs, int len, int x, int y) { - float winx = borderpx + x * xw.cw, winy = borderpx + y * xw.ch, xp, yp; + float winx = borderpx + x * xw.cw, winy = borderpx + y * xw.ch; ushort mode, prevmode = USHRT_MAX; Font *font = &dc.font; int frcflags = FRC_NORMAL; + float xp = winx, yp = winy + font->ascent; float runewidth = xw.cw; Rune rune; FT_UInt glyphidx; @@ -3293,23 +3415,39 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x FcPattern *fcpattern, *fontpattern; FcFontSet *fcsets[] = { NULL }; FcCharSet *fccharset; - int i, f, numspecs = 0; + int i = 0, ci = -1, j, f, numspecs = 0; - for(i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { + while(i < len) { /* Fetch rune and mode for current glyph. */ rune = glyphs[i].u; mode = glyphs[i].mode; /* Skip dummy wide-character spacing. */ - if(mode == ATTR_WDUMMY) + if(mode == ATTR_WDUMMY) { + i++; continue; + } + + if((j = ci++) >= 0) { + if(mode & ATTR_DECOMPPTR) + rune = glyphs[i].dc.pc[j]; + else + rune = (j < 2) ? glyphs[i].dc.c[j] : 0; + + if(!rune) { + i++; + ci = -1; + xp += runewidth; + 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); + runewidth = xw.cw * ((mode & ATTR_WIDE) ? 2.0 : 1.0); if((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { font = &dc.ibfont; frcflags = FRC_ITALICBOLD; @@ -3326,11 +3464,12 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x /* 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; + if(numspecs < nspecs) { + specs[numspecs].font = font->match; + specs[numspecs].glyph = glyphidx; + specs[numspecs].x = (short)xp; + specs[numspecs].y = (short)yp; + } numspecs++; continue; } @@ -3401,20 +3540,43 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x 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; + if(numspecs < nspecs) { + specs[numspecs].font = frc[f].font; + specs[numspecs].glyph = glyphidx; + specs[numspecs].x = (short)xp; + specs[numspecs].y = (short)(winy + frc[f].font->ascent); + } numspecs++; } return numspecs; } +int +xmakeglyphfontspecsintermbuf(const Glyph *glyphs, int len, int x, int y) +{ + int numspecs; + + while(1) { + numspecs = xmakeglyphfontspecs(term.specbuf, term.specbuflen, glyphs, len, x, y); + if(numspecs <= term.specbuflen) + break; + if(term.specbuflen == INT_MAX) + abort(); + + /* combining chars require a larger spec buffer */ + if(term.specbuflen > INT_MAX/2) + term.specbuflen = INT_MAX; + else + term.specbuflen *= 2; + term.specbuf = xrealloc(term.specbuf, term.specbuflen * sizeof(XftGlyphFontSpec)); + } + + return numspecs; +} + void -xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) { - int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); +xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int charlen, int x, int y) { int winx = borderpx + x * xw.cw, winy = borderpx + y * xw.ch, width = charlen * xw.cw; Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; @@ -3548,16 +3710,15 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i void xdrawglyph(Glyph g, int x, int y) { int numspecs; - XftGlyphFontSpec spec; - numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); - xdrawglyphfontspecs(&spec, g, numspecs, x, y); + numspecs = xmakeglyphfontspecsintermbuf(&g, 1, x, y); + xdrawglyphfontspecs(term.specbuf, g, numspecs, ((g.mode & ATTR_WIDE) ? 2 : 1), x, y); } void xdrawcursor(void) { static int oldx = 0, oldy = 0; int curx; - Glyph g = {' ', ATTR_NULL, defaultbg, defaultcs}; + Glyph g = {' ', {{0, 0}}, ATTR_NULL, defaultbg, defaultcs}; LIMIT(oldx, 0, term.col-1); LIMIT(oldy, 0, term.row-1); @@ -3570,7 +3731,9 @@ xdrawcursor(void) { if(term.line[term.c.y][curx].mode & ATTR_WDUMMY) curx--; - g.u = term.line[term.c.y][term.c.x].u; + g.u = term.line[term.c.y][curx].u; + g.dc = term.line[term.c.y][curx].dc; + g.mode |= term.line[term.c.y][curx].mode & ATTR_DECOMPPTR; /* remove the old cursor */ xdrawglyph(term.line[oldy][oldx], oldx, oldy); @@ -3664,8 +3827,9 @@ draw(void) { void drawregion(int x1, int y1, int x2, int y2) { - int i, x, y, ox, numspecs; + int i, w, x, y, ox, numspecs; Glyph base, new; + Rune *pc; XftGlyphFontSpec* specs; bool ena_sel = sel.ob.x != -1 && sel.alt == IS_SET(MODE_ALTSCREEN); @@ -3679,30 +3843,43 @@ drawregion(int x1, int y1, int x2, int y2) { xtermclear(0, y, term.col, y); term.dirty[y] = 0; + numspecs = xmakeglyphfontspecsintermbuf(&term.line[y][x1], x2 - x1, x1, y); specs = term.specbuf; - numspecs = xmakeglyphfontspecs(specs, &term.line[y][x1], x2 - x1, x1, y); - i = ox = 0; - for(x = x1; x < x2 && i < numspecs; x++) { + i = w = 0; + for(ox = 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(i > 0 && ATTRCMP(base, new)) { - xdrawglyphfontspecs(specs, base, i, ox, y); + xdrawglyphfontspecs(specs, base, i, w, ox, y); specs += i; numspecs -= i; i = 0; } if(i == 0) { - ox = x; + ox += w; base = new; + w = 0; + } + if(new.mode & ATTR_DECOMPPTR) { + pc = new.dc.pc; + while(*pc) + pc++; + i += pc - new.dc.pc; + } else { + if(new.dc.c[0]) + i++; + if(new.dc.c[1]) + i++; } i++; + w += (new.mode & ATTR_WIDE) ? 2 : 1; } if(i > 0) - xdrawglyphfontspecs(specs, base, i, ox, y); + xdrawglyphfontspecs(specs, base, i, w, ox, y); } xdrawcursor(); }