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