st-ligatures-scrollback-20221120-0.9.diff (14780B)
1 diff --git a/Makefile b/Makefile 2 index 470ac86..38240da 100644 3 --- a/Makefile 4 +++ b/Makefile 5 @@ -4,7 +4,7 @@ 6 7 include config.mk 8 9 -SRC = st.c x.c 10 +SRC = st.c x.c hb.c 11 OBJ = $(SRC:.c=.o) 12 13 all: options st 14 @@ -22,7 +22,8 @@ config.h: 15 $(CC) $(STCFLAGS) -c $< 16 17 st.o: config.h st.h win.h 18 -x.o: arg.h config.h st.h win.h 19 +x.o: arg.h config.h st.h win.h hb.h 20 +hb.o: st.h 21 22 $(OBJ): config.h config.mk 23 24 diff --git a/config.mk b/config.mk 25 index 1e306f8..3e13e53 100644 26 --- a/config.mk 27 +++ b/config.mk 28 @@ -15,10 +15,12 @@ PKG_CONFIG = pkg-config 29 # includes and libs 30 INCS = -I$(X11INC) \ 31 `$(PKG_CONFIG) --cflags fontconfig` \ 32 - `$(PKG_CONFIG) --cflags freetype2` 33 + `$(PKG_CONFIG) --cflags freetype2` \ 34 + `$(PKG_CONFIG) --cflags harfbuzz` 35 LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft \ 36 `$(PKG_CONFIG) --libs fontconfig` \ 37 - `$(PKG_CONFIG) --libs freetype2` 38 + `$(PKG_CONFIG) --libs freetype2` \ 39 + `$(PKG_CONFIG) --libs harfbuzz` 40 41 # flags 42 STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 43 diff --git a/hb.c b/hb.c 44 new file mode 100644 45 index 0000000..59b9200 46 --- /dev/null 47 +++ b/hb.c 48 @@ -0,0 +1,107 @@ 49 +#include <stdlib.h> 50 +#include <stdio.h> 51 +#include <math.h> 52 +#include <X11/Xft/Xft.h> 53 +#include <X11/cursorfont.h> 54 +#include <hb.h> 55 +#include <hb-ft.h> 56 + 57 +#include "st.h" 58 +#include "hb.h" 59 + 60 +#define FEATURE(c1,c2,c3,c4) { .tag = HB_TAG(c1,c2,c3,c4), .value = 1, .start = HB_FEATURE_GLOBAL_START, .end = HB_FEATURE_GLOBAL_END } 61 + 62 +hb_font_t *hbfindfont(XftFont *match); 63 + 64 +typedef struct { 65 + XftFont *match; 66 + hb_font_t *font; 67 +} HbFontMatch; 68 + 69 +static int hbfontslen = 0; 70 +static HbFontMatch *hbfontcache = NULL; 71 + 72 +/* 73 + * Poplulate the array with a list of font features, wrapped in FEATURE macro, 74 + * e. g. 75 + * FEATURE('c', 'a', 'l', 't'), FEATURE('d', 'l', 'i', 'g') 76 + */ 77 +hb_feature_t features[] = { }; 78 + 79 +void 80 +hbunloadfonts() 81 +{ 82 + for (int i = 0; i < hbfontslen; i++) { 83 + hb_font_destroy(hbfontcache[i].font); 84 + XftUnlockFace(hbfontcache[i].match); 85 + } 86 + 87 + if (hbfontcache != NULL) { 88 + free(hbfontcache); 89 + hbfontcache = NULL; 90 + } 91 + hbfontslen = 0; 92 +} 93 + 94 +hb_font_t * 95 +hbfindfont(XftFont *match) 96 +{ 97 + for (int i = 0; i < hbfontslen; i++) { 98 + if (hbfontcache[i].match == match) 99 + return hbfontcache[i].font; 100 + } 101 + 102 + /* Font not found in cache, caching it now. */ 103 + hbfontcache = realloc(hbfontcache, sizeof(HbFontMatch) * (hbfontslen + 1)); 104 + FT_Face face = XftLockFace(match); 105 + hb_font_t *font = hb_ft_font_create(face, NULL); 106 + if (font == NULL) 107 + die("Failed to load Harfbuzz font."); 108 + 109 + hbfontcache[hbfontslen].match = match; 110 + hbfontcache[hbfontslen].font = font; 111 + hbfontslen += 1; 112 + 113 + return font; 114 +} 115 + 116 +void hbtransform(HbTransformData *data, XftFont *xfont, const Glyph *glyphs, int start, int length) { 117 + Rune rune; 118 + ushort mode = USHRT_MAX; 119 + unsigned int glyph_count; 120 + int i, end = start + length; 121 + 122 + hb_font_t *font = hbfindfont(xfont); 123 + if (font == NULL) 124 + return; 125 + 126 + hb_buffer_t *buffer = hb_buffer_create(); 127 + hb_buffer_set_direction(buffer, HB_DIRECTION_LTR); 128 + 129 + /* Fill buffer with codepoints. */ 130 + for (i = start; i < end; i++) { 131 + rune = glyphs[i].u; 132 + mode = glyphs[i].mode; 133 + if (mode & ATTR_WDUMMY) 134 + rune = 0x0020; 135 + hb_buffer_add_codepoints(buffer, &rune, 1, 0, 1); 136 + } 137 + 138 + /* Shape the segment. */ 139 + hb_shape(font, buffer, features, sizeof(features)/sizeof(hb_feature_t)); 140 + 141 + /* Get new glyph info. */ 142 + hb_glyph_info_t *info = hb_buffer_get_glyph_infos(buffer, &glyph_count); 143 + hb_glyph_position_t *pos = hb_buffer_get_glyph_positions(buffer, &glyph_count); 144 + 145 + /** Fill the output. */ 146 + data->buffer = buffer; 147 + data->glyphs = info; 148 + data->positions = pos; 149 + data->count = glyph_count; 150 +} 151 + 152 +void hbcleanup(HbTransformData *data) { 153 + hb_buffer_destroy(data->buffer); 154 + memset(data, 0, sizeof(HbTransformData)); 155 +} 156 diff --git a/hb.h b/hb.h 157 new file mode 100644 158 index 0000000..88de9bd 159 --- /dev/null 160 +++ b/hb.h 161 @@ -0,0 +1,14 @@ 162 +#include <X11/Xft/Xft.h> 163 +#include <hb.h> 164 +#include <hb-ft.h> 165 + 166 +typedef struct { 167 + hb_buffer_t *buffer; 168 + hb_glyph_info_t *glyphs; 169 + hb_glyph_position_t *positions; 170 + unsigned int count; 171 +} HbTransformData; 172 + 173 +void hbunloadfonts(); 174 +void hbtransform(HbTransformData *, XftFont *, const Glyph *, int, int); 175 +void hbcleanup(HbTransformData *); 176 diff --git a/st.c b/st.c 177 index 79ee9ba..7675db6 100644 178 --- a/st.c 179 +++ b/st.c 180 @@ -2711,7 +2711,8 @@ draw(void) 181 drawregion(0, 0, term.col, term.row); 182 if (term.scr == 0) 183 xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], 184 - term.ocx, term.ocy, term.line[term.ocy][term.ocx]); 185 + term.ocx, term.ocy, term.line[term.ocy][term.ocx], 186 + term.line[term.ocy], term.col); 187 term.ocx = cx; 188 term.ocy = term.c.y; 189 xfinishdraw(); 190 diff --git a/st.h b/st.h 191 index 818a6f8..4e584b6 100644 192 --- a/st.h 193 +++ b/st.h 194 @@ -11,7 +11,8 @@ 195 #define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d)) 196 #define DEFAULT(a, b) (a) = (a) ? (a) : (b) 197 #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) 198 -#define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \ 199 +#define ATTRCMP(a, b) (((a).mode & (~ATTR_WRAP)) != ((b).mode & (~ATTR_WRAP)) || \ 200 + (a).fg != (b).fg || \ 201 (a).bg != (b).bg) 202 #define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \ 203 (t1.tv_nsec-t2.tv_nsec)/1E6) 204 diff --git a/win.h b/win.h 205 index 6de960d..94679e4 100644 206 --- a/win.h 207 +++ b/win.h 208 @@ -25,7 +25,7 @@ enum win_mode { 209 210 void xbell(void); 211 void xclipcopy(void); 212 -void xdrawcursor(int, int, Glyph, int, int, Glyph); 213 +void xdrawcursor(int, int, Glyph, int, int, Glyph, Line, int); 214 void xdrawline(Line, int, int, int); 215 void xfinishdraw(void); 216 void xloadcols(void); 217 diff --git a/x.c b/x.c 218 index 2a3bd38..5feac09 100644 219 --- a/x.c 220 +++ b/x.c 221 @@ -19,6 +19,7 @@ char *argv0; 222 #include "arg.h" 223 #include "st.h" 224 #include "win.h" 225 +#include "hb.h" 226 227 /* types used in config.h */ 228 typedef struct { 229 @@ -141,6 +142,7 @@ typedef struct { 230 } DC; 231 232 static inline ushort sixd_to_16bit(int); 233 +static void xresetfontsettings(ushort mode, Font **font, int *frcflags); 234 static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); 235 static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); 236 static void xdrawglyph(Glyph, int, int); 237 @@ -1062,6 +1064,9 @@ xunloadfont(Font *f) 238 void 239 xunloadfonts(void) 240 { 241 + /* Clear Harfbuzz font cache. */ 242 + hbunloadfonts(); 243 + 244 /* Free the loaded fonts in the font cache. */ 245 while (frclen > 0) 246 XftFontClose(xw.dpy, frc[--frclen].font); 247 @@ -1239,6 +1244,22 @@ xinit(int cols, int rows) 248 xsel.xtarget = XA_STRING; 249 } 250 251 +void 252 +xresetfontsettings(ushort mode, Font **font, int *frcflags) 253 +{ 254 + *font = &dc.font; 255 + if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { 256 + *font = &dc.ibfont; 257 + *frcflags = FRC_ITALICBOLD; 258 + } else if (mode & ATTR_ITALIC) { 259 + *font = &dc.ifont; 260 + *frcflags = FRC_ITALIC; 261 + } else if (mode & ATTR_BOLD) { 262 + *font = &dc.bfont; 263 + *frcflags = FRC_BOLD; 264 + } 265 +} 266 + 267 int 268 xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) 269 { 270 @@ -1253,119 +1274,137 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x 271 FcPattern *fcpattern, *fontpattern; 272 FcFontSet *fcsets[] = { NULL }; 273 FcCharSet *fccharset; 274 - int i, f, numspecs = 0; 275 + int i, f, length = 0, start = 0, numspecs = 0; 276 + HbTransformData shaped = { 0 }; 277 + 278 + /* Initial values. */ 279 + mode = prevmode = glyphs[0].mode; 280 + xresetfontsettings(mode, &font, &frcflags); 281 282 for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { 283 - /* Fetch rune and mode for current glyph. */ 284 - rune = glyphs[i].u; 285 mode = glyphs[i].mode; 286 287 /* Skip dummy wide-character spacing. */ 288 - if (mode == ATTR_WDUMMY) 289 + if (mode & ATTR_WDUMMY) 290 continue; 291 292 - /* Determine font for glyph if different from previous glyph. */ 293 - if (prevmode != mode) { 294 - prevmode = mode; 295 - font = &dc.font; 296 - frcflags = FRC_NORMAL; 297 - runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f); 298 - if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { 299 - font = &dc.ibfont; 300 - frcflags = FRC_ITALICBOLD; 301 - } else if (mode & ATTR_ITALIC) { 302 - font = &dc.ifont; 303 - frcflags = FRC_ITALIC; 304 - } else if (mode & ATTR_BOLD) { 305 - font = &dc.bfont; 306 - frcflags = FRC_BOLD; 307 + if ( 308 + prevmode != mode 309 + || ATTRCMP(glyphs[start], glyphs[i]) 310 + || selected(x + i, y) != selected(x + start, y) 311 + || i == (len - 1) 312 + ) { 313 + /* Handle 1-character wide segments and end of line */ 314 + length = i - start; 315 + if (i == start) { 316 + length = 1; 317 + } else if (i == (len - 1)) { 318 + length = (i - start + 1); 319 } 320 - yp = winy + font->ascent; 321 - } 322 - 323 - /* Lookup character index with default font. */ 324 - glyphidx = XftCharIndex(xw.dpy, font->match, rune); 325 - if (glyphidx) { 326 - specs[numspecs].font = font->match; 327 - specs[numspecs].glyph = glyphidx; 328 - specs[numspecs].x = (short)xp; 329 - specs[numspecs].y = (short)yp; 330 - xp += runewidth; 331 - numspecs++; 332 - continue; 333 - } 334 335 - /* Fallback on font cache, search the font cache for match. */ 336 - for (f = 0; f < frclen; f++) { 337 - glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); 338 - /* Everything correct. */ 339 - if (glyphidx && frc[f].flags == frcflags) 340 - break; 341 - /* We got a default font for a not found glyph. */ 342 - if (!glyphidx && frc[f].flags == frcflags 343 - && frc[f].unicodep == rune) { 344 - break; 345 + /* Shape the segment. */ 346 + hbtransform(&shaped, font->match, glyphs, start, length); 347 + for (int code_idx = 0; code_idx < shaped.count; code_idx++) { 348 + rune = glyphs[start + code_idx].u; 349 + runewidth = win.cw * ((glyphs[start + code_idx].mode & ATTR_WIDE) ? 2.0f : 1.0f); 350 + 351 + if (glyphs[start + code_idx].mode & ATTR_WDUMMY) 352 + continue; 353 + 354 + if (shaped.glyphs[code_idx].codepoint != 0) { 355 + /* If symbol is found, put it into the specs. */ 356 + specs[numspecs].font = font->match; 357 + specs[numspecs].glyph = shaped.glyphs[code_idx].codepoint; 358 + specs[numspecs].x = xp + (short)shaped.positions[code_idx].x_offset; 359 + specs[numspecs].y = yp + (short)shaped.positions[code_idx].y_offset; 360 + xp += runewidth; 361 + numspecs++; 362 + } else { 363 + /* If it's not found, try to fetch it through the font cache. */ 364 + for (f = 0; f < frclen; f++) { 365 + glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); 366 + /* Everything correct. */ 367 + if (glyphidx && frc[f].flags == frcflags) 368 + break; 369 + /* We got a default font for a not found glyph. */ 370 + if (!glyphidx && frc[f].flags == frcflags 371 + && frc[f].unicodep == rune) { 372 + break; 373 + } 374 + } 375 + 376 + /* Nothing was found. Use fontconfig to find matching font. */ 377 + if (f >= frclen) { 378 + if (!font->set) 379 + font->set = FcFontSort(0, font->pattern, 380 + 1, 0, &fcres); 381 + fcsets[0] = font->set; 382 + 383 + /* 384 + * Nothing was found in the cache. Now use 385 + * some dozen of Fontconfig calls to get the 386 + * font for one single character. 387 + * 388 + * Xft and fontconfig are design failures. 389 + */ 390 + fcpattern = FcPatternDuplicate(font->pattern); 391 + fccharset = FcCharSetCreate(); 392 + 393 + FcCharSetAddChar(fccharset, rune); 394 + FcPatternAddCharSet(fcpattern, FC_CHARSET, 395 + fccharset); 396 + FcPatternAddBool(fcpattern, FC_SCALABLE, 1); 397 + 398 + FcConfigSubstitute(0, fcpattern, 399 + FcMatchPattern); 400 + FcDefaultSubstitute(fcpattern); 401 + 402 + fontpattern = FcFontSetMatch(0, fcsets, 1, 403 + fcpattern, &fcres); 404 + 405 + /* Allocate memory for the new cache entry. */ 406 + if (frclen >= frccap) { 407 + frccap += 16; 408 + frc = xrealloc(frc, frccap * sizeof(Fontcache)); 409 + } 410 + 411 + frc[frclen].font = XftFontOpenPattern(xw.dpy, 412 + fontpattern); 413 + if (!frc[frclen].font) 414 + die("XftFontOpenPattern failed seeking fallback font: %s\n", 415 + strerror(errno)); 416 + frc[frclen].flags = frcflags; 417 + frc[frclen].unicodep = rune; 418 + 419 + glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); 420 + 421 + f = frclen; 422 + frclen++; 423 + 424 + FcPatternDestroy(fcpattern); 425 + FcCharSetDestroy(fccharset); 426 + } 427 + 428 + specs[numspecs].font = frc[f].font; 429 + specs[numspecs].glyph = glyphidx; 430 + specs[numspecs].x = (short)xp; 431 + specs[numspecs].y = (short)yp; 432 + xp += runewidth; 433 + numspecs++; 434 + } 435 } 436 - } 437 438 - /* Nothing was found. Use fontconfig to find matching font. */ 439 - if (f >= frclen) { 440 - if (!font->set) 441 - font->set = FcFontSort(0, font->pattern, 442 - 1, 0, &fcres); 443 - fcsets[0] = font->set; 444 + /* Cleanup and get ready for next segment. */ 445 + hbcleanup(&shaped); 446 + start = i; 447 448 - /* 449 - * Nothing was found in the cache. Now use 450 - * some dozen of Fontconfig calls to get the 451 - * font for one single character. 452 - * 453 - * Xft and fontconfig are design failures. 454 - */ 455 - fcpattern = FcPatternDuplicate(font->pattern); 456 - fccharset = FcCharSetCreate(); 457 - 458 - FcCharSetAddChar(fccharset, rune); 459 - FcPatternAddCharSet(fcpattern, FC_CHARSET, 460 - fccharset); 461 - FcPatternAddBool(fcpattern, FC_SCALABLE, 1); 462 - 463 - FcConfigSubstitute(0, fcpattern, 464 - FcMatchPattern); 465 - FcDefaultSubstitute(fcpattern); 466 - 467 - fontpattern = FcFontSetMatch(0, fcsets, 1, 468 - fcpattern, &fcres); 469 - 470 - /* Allocate memory for the new cache entry. */ 471 - if (frclen >= frccap) { 472 - frccap += 16; 473 - frc = xrealloc(frc, frccap * sizeof(Fontcache)); 474 + /* Determine font for glyph if different from previous glyph. */ 475 + if (prevmode != mode) { 476 + prevmode = mode; 477 + xresetfontsettings(mode, &font, &frcflags); 478 + yp = winy + font->ascent; 479 } 480 - 481 - frc[frclen].font = XftFontOpenPattern(xw.dpy, 482 - fontpattern); 483 - if (!frc[frclen].font) 484 - die("XftFontOpenPattern failed seeking fallback font: %s\n", 485 - strerror(errno)); 486 - frc[frclen].flags = frcflags; 487 - frc[frclen].unicodep = rune; 488 - 489 - glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); 490 - 491 - f = frclen; 492 - frclen++; 493 - 494 - FcPatternDestroy(fcpattern); 495 - FcCharSetDestroy(fccharset); 496 } 497 - 498 - specs[numspecs].font = frc[f].font; 499 - specs[numspecs].glyph = glyphidx; 500 - specs[numspecs].x = (short)xp; 501 - specs[numspecs].y = (short)yp; 502 - xp += runewidth; 503 - numspecs++; 504 } 505 506 return numspecs; 507 @@ -1517,14 +1556,17 @@ xdrawglyph(Glyph g, int x, int y) 508 } 509 510 void 511 -xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) 512 +xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og, Line line, int len) 513 { 514 Color drawcol; 515 516 /* remove the old cursor */ 517 if (selected(ox, oy)) 518 og.mode ^= ATTR_REVERSE; 519 - xdrawglyph(og, ox, oy); 520 + 521 + /* Redraw the line where cursor was previously. 522 + * It will restore the ligatures broken by the cursor. */ 523 + xdrawline(line, 0, oy, len); 524 525 if (IS_SET(MODE_HIDE)) 526 return;