st-ligatures-scrollback-20210824-0.8.4.diff 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. diff --git a/Makefile b/Makefile
  2. index 470ac86..38240da 100644
  3. --- a/Makefile
  4. +++ b/Makefile
  5. @@ -4,7 +4,7 @@
  6. include config.mk
  7. -SRC = st.c x.c
  8. +SRC = st.c x.c hb.c
  9. OBJ = $(SRC:.c=.o)
  10. all: options st
  11. @@ -22,7 +22,8 @@ config.h:
  12. $(CC) $(STCFLAGS) -c $<
  13. st.o: config.h st.h win.h
  14. -x.o: arg.h config.h st.h win.h
  15. +x.o: arg.h config.h st.h win.h hb.h
  16. +hb.o: st.h
  17. $(OBJ): config.h config.mk
  18. diff --git a/config.mk b/config.mk
  19. index c070a4a..3d236f0 100644
  20. --- a/config.mk
  21. +++ b/config.mk
  22. @@ -15,10 +15,12 @@ PKG_CONFIG = pkg-config
  23. # includes and libs
  24. INCS = -I$(X11INC) \
  25. `$(PKG_CONFIG) --cflags fontconfig` \
  26. - `$(PKG_CONFIG) --cflags freetype2`
  27. + `$(PKG_CONFIG) --cflags freetype2` \
  28. + `$(PKG_CONFIG) --cflags harfbuzz`
  29. LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft \
  30. `$(PKG_CONFIG) --libs fontconfig` \
  31. - `$(PKG_CONFIG) --libs freetype2`
  32. + `$(PKG_CONFIG) --libs freetype2` \
  33. + `$(PKG_CONFIG) --libs harfbuzz`
  34. # flags
  35. STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600
  36. diff --git a/hb.c b/hb.c
  37. new file mode 100644
  38. index 0000000..f9c4f76
  39. --- /dev/null
  40. +++ b/hb.c
  41. @@ -0,0 +1,145 @@
  42. +#include <stdlib.h>
  43. +#include <stdio.h>
  44. +#include <math.h>
  45. +#include <X11/Xft/Xft.h>
  46. +#include <hb.h>
  47. +#include <hb-ft.h>
  48. +
  49. +#include "st.h"
  50. +
  51. +#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 }
  52. +
  53. +void hbtransformsegment(XftFont *xfont, const Glyph *string, hb_codepoint_t *codepoints, int start, int length);
  54. +hb_font_t *hbfindfont(XftFont *match);
  55. +
  56. +typedef struct {
  57. + XftFont *match;
  58. + hb_font_t *font;
  59. +} HbFontMatch;
  60. +
  61. +static int hbfontslen = 0;
  62. +static HbFontMatch *hbfontcache = NULL;
  63. +
  64. +/*
  65. + * Poplulate the array with a list of font features, wrapped in FEATURE macro,
  66. + * e. g.
  67. + * FEATURE('c', 'a', 'l', 't'), FEATURE('d', 'l', 'i', 'g')
  68. + */
  69. +hb_feature_t features[] = { };
  70. +
  71. +void
  72. +hbunloadfonts()
  73. +{
  74. + for (int i = 0; i < hbfontslen; i++) {
  75. + hb_font_destroy(hbfontcache[i].font);
  76. + XftUnlockFace(hbfontcache[i].match);
  77. + }
  78. +
  79. + if (hbfontcache != NULL) {
  80. + free(hbfontcache);
  81. + hbfontcache = NULL;
  82. + }
  83. + hbfontslen = 0;
  84. +}
  85. +
  86. +hb_font_t *
  87. +hbfindfont(XftFont *match)
  88. +{
  89. + for (int i = 0; i < hbfontslen; i++) {
  90. + if (hbfontcache[i].match == match)
  91. + return hbfontcache[i].font;
  92. + }
  93. +
  94. + /* Font not found in cache, caching it now. */
  95. + hbfontcache = realloc(hbfontcache, sizeof(HbFontMatch) * (hbfontslen + 1));
  96. + FT_Face face = XftLockFace(match);
  97. + hb_font_t *font = hb_ft_font_create(face, NULL);
  98. + if (font == NULL)
  99. + die("Failed to load Harfbuzz font.");
  100. +
  101. + hbfontcache[hbfontslen].match = match;
  102. + hbfontcache[hbfontslen].font = font;
  103. + hbfontslen += 1;
  104. +
  105. + return font;
  106. +}
  107. +
  108. +void
  109. +hbtransform(XftGlyphFontSpec *specs, const Glyph *glyphs, size_t len, int x, int y)
  110. +{
  111. + int start = 0, length = 1, gstart = 0;
  112. + hb_codepoint_t *codepoints = calloc((unsigned int)len, sizeof(hb_codepoint_t));
  113. +
  114. + for (int idx = 1, specidx = 1; idx < len; idx++) {
  115. + if (glyphs[idx].mode & ATTR_WDUMMY) {
  116. + length += 1;
  117. + continue;
  118. + }
  119. +
  120. + if (specs[specidx].font != specs[start].font || ATTRCMP(glyphs[gstart], glyphs[idx]) || selected(x + idx, y) != selected(x + gstart, y)) {
  121. + hbtransformsegment(specs[start].font, glyphs, codepoints, gstart, length);
  122. +
  123. + /* Reset the sequence. */
  124. + length = 1;
  125. + start = specidx;
  126. + gstart = idx;
  127. + } else {
  128. + length += 1;
  129. + }
  130. +
  131. + specidx++;
  132. + }
  133. +
  134. + /* EOL. */
  135. + hbtransformsegment(specs[start].font, glyphs, codepoints, gstart, length);
  136. +
  137. + /* Apply the transformation to glyph specs. */
  138. + for (int i = 0, specidx = 0; i < len; i++) {
  139. + if (glyphs[i].mode & ATTR_WDUMMY)
  140. + continue;
  141. +
  142. + if (codepoints[i] != specs[specidx].glyph)
  143. + ((Glyph *)glyphs)[i].mode |= ATTR_LIGA;
  144. +
  145. + specs[specidx++].glyph = codepoints[i];
  146. + }
  147. +
  148. + free(codepoints);
  149. +}
  150. +
  151. +void
  152. +hbtransformsegment(XftFont *xfont, const Glyph *string, hb_codepoint_t *codepoints, int start, int length)
  153. +{
  154. + hb_font_t *font = hbfindfont(xfont);
  155. + if (font == NULL)
  156. + return;
  157. +
  158. + Rune rune;
  159. + ushort mode = USHRT_MAX;
  160. + hb_buffer_t *buffer = hb_buffer_create();
  161. + hb_buffer_set_direction(buffer, HB_DIRECTION_LTR);
  162. +
  163. + /* Fill buffer with codepoints. */
  164. + for (int i = start; i < (start+length); i++) {
  165. + rune = string[i].u;
  166. + mode = string[i].mode;
  167. + if (mode & ATTR_WDUMMY)
  168. + rune = 0x0020;
  169. + hb_buffer_add_codepoints(buffer, &rune, 1, 0, 1);
  170. + }
  171. +
  172. + /* Shape the segment. */
  173. + hb_shape(font, buffer, features, sizeof(features)/sizeof(hb_feature_t));
  174. +
  175. + /* Get new glyph info. */
  176. + hb_glyph_info_t *info = hb_buffer_get_glyph_infos(buffer, NULL);
  177. +
  178. + /* Write new codepoints. */
  179. + for (int i = 0; i < length; i++) {
  180. + hb_codepoint_t gid = info[i].codepoint;
  181. + codepoints[start+i] = gid;
  182. + }
  183. +
  184. + /* Cleanup. */
  185. + hb_buffer_destroy(buffer);
  186. +}
  187. diff --git a/hb.h b/hb.h
  188. new file mode 100644
  189. index 0000000..07888df
  190. --- /dev/null
  191. +++ b/hb.h
  192. @@ -0,0 +1,6 @@
  193. +#include <X11/Xft/Xft.h>
  194. +#include <hb.h>
  195. +#include <hb-ft.h>
  196. +
  197. +void hbunloadfonts();
  198. +void hbtransform(XftGlyphFontSpec *, const Glyph *, size_t, int, int);
  199. diff --git a/st.c b/st.c
  200. index edec064..ea13c13 100644
  201. --- a/st.c
  202. +++ b/st.c
  203. @@ -2652,7 +2652,8 @@ draw(void)
  204. drawregion(0, 0, term.col, term.row);
  205. if (term.scr == 0)
  206. xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
  207. - term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
  208. + term.ocx, term.ocy, term.line[term.ocy][term.ocx],
  209. + term.line[term.ocy], term.col);
  210. term.ocx = cx;
  211. term.ocy = term.c.y;
  212. xfinishdraw();
  213. diff --git a/st.h b/st.h
  214. index f44e1d3..00c796c 100644
  215. --- a/st.h
  216. +++ b/st.h
  217. @@ -11,7 +11,8 @@
  218. #define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d))
  219. #define DEFAULT(a, b) (a) = (a) ? (a) : (b)
  220. #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)
  221. -#define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \
  222. +#define ATTRCMP(a, b) (((a).mode & (~ATTR_WRAP) & (~ATTR_LIGA)) != ((b).mode & (~ATTR_WRAP) & (~ATTR_LIGA)) || \
  223. + (a).fg != (b).fg || \
  224. (a).bg != (b).bg)
  225. #define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \
  226. (t1.tv_nsec-t2.tv_nsec)/1E6)
  227. @@ -33,6 +34,7 @@ enum glyph_attribute {
  228. ATTR_WRAP = 1 << 8,
  229. ATTR_WIDE = 1 << 9,
  230. ATTR_WDUMMY = 1 << 10,
  231. + ATTR_LIGA = 1 << 11,
  232. ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT,
  233. };
  234. diff --git a/win.h b/win.h
  235. index a6ef1b9..bc0d180 100644
  236. --- a/win.h
  237. +++ b/win.h
  238. @@ -25,7 +25,7 @@ enum win_mode {
  239. void xbell(void);
  240. void xclipcopy(void);
  241. -void xdrawcursor(int, int, Glyph, int, int, Glyph);
  242. +void xdrawcursor(int, int, Glyph, int, int, Glyph, Line, int);
  243. void xdrawline(Line, int, int, int);
  244. void xfinishdraw(void);
  245. void xloadcols(void);
  246. diff --git a/x.c b/x.c
  247. index 210f184..f6d67ef 100644
  248. --- a/x.c
  249. +++ b/x.c
  250. @@ -19,6 +19,7 @@ char *argv0;
  251. #include "arg.h"
  252. #include "st.h"
  253. #include "win.h"
  254. +#include "hb.h"
  255. /* types used in config.h */
  256. typedef struct {
  257. @@ -1031,6 +1032,9 @@ xunloadfont(Font *f)
  258. void
  259. xunloadfonts(void)
  260. {
  261. + /* Clear Harfbuzz font cache. */
  262. + hbunloadfonts();
  263. +
  264. /* Free the loaded fonts in the font cache. */
  265. while (frclen > 0)
  266. XftFontClose(xw.dpy, frc[--frclen].font);
  267. @@ -1229,7 +1233,7 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
  268. mode = glyphs[i].mode;
  269. /* Skip dummy wide-character spacing. */
  270. - if (mode == ATTR_WDUMMY)
  271. + if (mode & ATTR_WDUMMY)
  272. continue;
  273. /* Determine font for glyph if different from previous glyph. */
  274. @@ -1336,6 +1340,9 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
  275. numspecs++;
  276. }
  277. + /* Harfbuzz transformation for ligatures. */
  278. + hbtransform(specs, glyphs, len, x, y);
  279. +
  280. return numspecs;
  281. }
  282. @@ -1485,14 +1492,17 @@ xdrawglyph(Glyph g, int x, int y)
  283. }
  284. void
  285. -xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
  286. +xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og, Line line, int len)
  287. {
  288. Color drawcol;
  289. /* remove the old cursor */
  290. if (selected(ox, oy))
  291. og.mode ^= ATTR_REVERSE;
  292. - xdrawglyph(og, ox, oy);
  293. +
  294. + /* Redraw the line where cursor was previously.
  295. + * It will restore the ligatures broken by the cursor. */
  296. + xdrawline(line, 0, oy, len);
  297. if (IS_SET(MODE_HIDE))
  298. return;