st-ligatures-boxdraw-20221120-0.9.diff 15 KB

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