sites

public wiki contents of suckless.org
git clone git://git.suckless.org/sites
Log | Files | Refs

commit 376bede2d966c67466772c547b7ad05f99402bc4
parent 4c8e7b5d6ed1f1a7f3bb91ad757d21aca372effd
Author: Alexander Rogachev <sorryforbadname@gmail.com>
Date:   Sat, 27 Apr 2024 03:51:42 +0400

[st][patch][ligatures] Update ligature patches for 0.9.2.

Diffstat:
Ast.suckless.org/patches/ligatures/0.9.2/st-ligatures-20240427-0.9.2.diff | 633+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ast.suckless.org/patches/ligatures/0.9.2/st-ligatures-alpha-20240427-0.9.2.diff | 635+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ast.suckless.org/patches/ligatures/0.9.2/st-ligatures-alpha-scrollback-20240427-0.9.2.diff | 635+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ast.suckless.org/patches/ligatures/0.9.2/st-ligatures-alpha-scrollback-ringbuffer-20240427-0.9.2.diff | 635+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ast.suckless.org/patches/ligatures/0.9.2/st-ligatures-boxdraw-20240427-0.9.2.diff | 647+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ast.suckless.org/patches/ligatures/0.9.2/st-ligatures-scrollback-20240427-0.9.2.diff | 634+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ast.suckless.org/patches/ligatures/0.9.2/st-ligatures-scrollback-ringbuffer-20240427-0.9.2.diff | 633+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mst.suckless.org/patches/ligatures/index.md | 9+++++++++
8 files changed, 4461 insertions(+), 0 deletions(-)

diff --git a/st.suckless.org/patches/ligatures/0.9.2/st-ligatures-20240427-0.9.2.diff b/st.suckless.org/patches/ligatures/0.9.2/st-ligatures-20240427-0.9.2.diff @@ -0,0 +1,633 @@ +diff --git a/Makefile b/Makefile +index 470ac86..38240da 100644 +--- a/Makefile ++++ b/Makefile +@@ -4,7 +4,7 @@ + + include config.mk + +-SRC = st.c x.c ++SRC = st.c x.c hb.c + OBJ = $(SRC:.c=.o) + + all: st +@@ -22,7 +22,8 @@ config.h: + $(CC) $(STCFLAGS) -c $< + + st.o: config.h st.h win.h +-x.o: arg.h config.h st.h win.h ++x.o: arg.h config.h st.h win.h hb.h ++hb.o: st.h + + $(OBJ): config.h config.mk + +diff --git a/config.mk b/config.mk +index 1e306f8..3e13e53 100644 +--- a/config.mk ++++ b/config.mk +@@ -15,10 +15,12 @@ PKG_CONFIG = pkg-config + # includes and libs + INCS = -I$(X11INC) \ + `$(PKG_CONFIG) --cflags fontconfig` \ +- `$(PKG_CONFIG) --cflags freetype2` ++ `$(PKG_CONFIG) --cflags freetype2` \ ++ `$(PKG_CONFIG) --cflags harfbuzz` + LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft \ + `$(PKG_CONFIG) --libs fontconfig` \ +- `$(PKG_CONFIG) --libs freetype2` ++ `$(PKG_CONFIG) --libs freetype2` \ ++ `$(PKG_CONFIG) --libs harfbuzz` + + # flags + STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 +diff --git a/hb.c b/hb.c +new file mode 100644 +index 0000000..99412c8 +--- /dev/null ++++ b/hb.c +@@ -0,0 +1,125 @@ ++#include <stdlib.h> ++#include <stdio.h> ++#include <math.h> ++#include <X11/Xft/Xft.h> ++#include <X11/cursorfont.h> ++#include <hb.h> ++#include <hb-ft.h> ++ ++#include "st.h" ++#include "hb.h" ++ ++#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 } ++#define BUFFER_STEP 256 ++ ++hb_font_t *hbfindfont(XftFont *match); ++ ++typedef struct { ++ XftFont *match; ++ hb_font_t *font; ++} HbFontMatch; ++ ++typedef struct { ++ size_t capacity; ++ HbFontMatch *fonts; ++} HbFontCache; ++ ++static HbFontCache hbfontcache = { 0, NULL }; ++ ++typedef struct { ++ size_t capacity; ++ Rune *runes; ++} RuneBuffer; ++ ++static RuneBuffer hbrunebuffer = { 0, NULL }; ++ ++/* ++ * Poplulate the array with a list of font features, wrapped in FEATURE macro, ++ * e. g. ++ * FEATURE('c', 'a', 'l', 't'), FEATURE('d', 'l', 'i', 'g') ++ */ ++hb_feature_t features[] = { }; ++ ++void ++hbunloadfonts() ++{ ++ for (int i = 0; i < hbfontcache.capacity; i++) { ++ hb_font_destroy(hbfontcache.fonts[i].font); ++ XftUnlockFace(hbfontcache.fonts[i].match); ++ } ++ ++ if (hbfontcache.fonts != NULL) { ++ free(hbfontcache.fonts); ++ hbfontcache.fonts = NULL; ++ } ++ hbfontcache.capacity = 0; ++} ++ ++hb_font_t * ++hbfindfont(XftFont *match) ++{ ++ for (int i = 0; i < hbfontcache.capacity; i++) { ++ if (hbfontcache.fonts[i].match == match) ++ return hbfontcache.fonts[i].font; ++ } ++ ++ /* Font not found in cache, caching it now. */ ++ hbfontcache.fonts = realloc(hbfontcache.fonts, sizeof(HbFontMatch) * (hbfontcache.capacity + 1)); ++ FT_Face face = XftLockFace(match); ++ hb_font_t *font = hb_ft_font_create(face, NULL); ++ if (font == NULL) ++ die("Failed to load Harfbuzz font."); ++ ++ hbfontcache.fonts[hbfontcache.capacity].match = match; ++ hbfontcache.fonts[hbfontcache.capacity].font = font; ++ hbfontcache.capacity += 1; ++ ++ return font; ++} ++ ++void hbtransform(HbTransformData *data, XftFont *xfont, const Glyph *glyphs, int start, int length) { ++ ushort mode = USHRT_MAX; ++ unsigned int glyph_count; ++ int rune_idx, glyph_idx, end = start + length; ++ ++ hb_font_t *font = hbfindfont(xfont); ++ if (font == NULL) ++ return; ++ ++ hb_buffer_t *buffer = hb_buffer_create(); ++ hb_buffer_set_direction(buffer, HB_DIRECTION_LTR); ++ hb_buffer_set_cluster_level(buffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS); ++ ++ /* Resize the buffer if required length is larger. */ ++ if (hbrunebuffer.capacity < length) { ++ hbrunebuffer.capacity = (length / BUFFER_STEP + 1) * BUFFER_STEP; ++ hbrunebuffer.runes = realloc(hbrunebuffer.runes, hbrunebuffer.capacity * sizeof(Rune)); ++ } ++ ++ /* Fill buffer with codepoints. */ ++ for (rune_idx = 0, glyph_idx = start; glyph_idx < end; glyph_idx++, rune_idx++) { ++ hbrunebuffer.runes[rune_idx] = glyphs[glyph_idx].u; ++ mode = glyphs[glyph_idx].mode; ++ if (mode & ATTR_WDUMMY) ++ hbrunebuffer.runes[rune_idx] = 0x0020; ++ } ++ hb_buffer_add_codepoints(buffer, hbrunebuffer.runes, length, 0, length); ++ ++ /* Shape the segment. */ ++ hb_shape(font, buffer, features, sizeof(features)/sizeof(hb_feature_t)); ++ ++ /* Get new glyph info. */ ++ hb_glyph_info_t *info = hb_buffer_get_glyph_infos(buffer, &glyph_count); ++ hb_glyph_position_t *pos = hb_buffer_get_glyph_positions(buffer, &glyph_count); ++ ++ /* Fill the output. */ ++ data->buffer = buffer; ++ data->glyphs = info; ++ data->positions = pos; ++ data->count = glyph_count; ++} ++ ++void hbcleanup(HbTransformData *data) { ++ hb_buffer_destroy(data->buffer); ++ memset(data, 0, sizeof(HbTransformData)); ++} +diff --git a/hb.h b/hb.h +new file mode 100644 +index 0000000..3b0ef44 +--- /dev/null ++++ b/hb.h +@@ -0,0 +1,14 @@ ++#include <X11/Xft/Xft.h> ++#include <hb.h> ++#include <hb-ft.h> ++ ++typedef struct { ++ hb_buffer_t *buffer; ++ hb_glyph_info_t *glyphs; ++ hb_glyph_position_t *positions; ++ unsigned int count; ++} HbTransformData; ++ ++void hbunloadfonts(); ++void hbtransform(HbTransformData *, XftFont *, const Glyph *, int, int); ++void hbcleanup(HbTransformData *); +diff --git a/st.c b/st.c +index 62def59..041c6d8 100644 +--- a/st.c ++++ b/st.c +@@ -2640,7 +2640,8 @@ draw(void) + + drawregion(0, 0, term.col, term.row); + xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], +- term.ocx, term.ocy, term.line[term.ocy][term.ocx]); ++ term.ocx, term.ocy, term.line[term.ocy][term.ocx], ++ term.line[term.ocy], term.col); + term.ocx = cx; + term.ocy = term.c.y; + xfinishdraw(); +diff --git a/st.h b/st.h +index fd3b0d8..142fdfe 100644 +--- a/st.h ++++ b/st.h +@@ -11,7 +11,8 @@ + #define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d)) + #define DEFAULT(a, b) (a) = (a) ? (a) : (b) + #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) +-#define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \ ++#define ATTRCMP(a, b) (((a).mode & (~ATTR_WRAP)) != ((b).mode & (~ATTR_WRAP)) || \ ++ (a).fg != (b).fg || \ + (a).bg != (b).bg) + #define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \ + (t1.tv_nsec-t2.tv_nsec)/1E6) +diff --git a/win.h b/win.h +index 6de960d..94679e4 100644 +--- a/win.h ++++ b/win.h +@@ -25,7 +25,7 @@ enum win_mode { + + void xbell(void); + void xclipcopy(void); +-void xdrawcursor(int, int, Glyph, int, int, Glyph); ++void xdrawcursor(int, int, Glyph, int, int, Glyph, Line, int); + void xdrawline(Line, int, int, int); + void xfinishdraw(void); + void xloadcols(void); +diff --git a/x.c b/x.c +index 2a3bd38..66605ae 100644 +--- a/x.c ++++ b/x.c +@@ -19,6 +19,7 @@ char *argv0; + #include "arg.h" + #include "st.h" + #include "win.h" ++#include "hb.h" + + /* types used in config.h */ + typedef struct { +@@ -141,8 +142,9 @@ typedef struct { + } DC; + + static inline ushort sixd_to_16bit(int); ++static void xresetfontsettings(ushort mode, Font **font, int *frcflags); + static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); +-static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); ++static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int, int); + static void xdrawglyph(Glyph, int, int); + static void xclear(int, int, int, int); + static int xgeommasktogravity(int); +@@ -757,7 +759,7 @@ xresize(int col, int row) + xclear(0, 0, win.w, win.h); + + /* resize to new width */ +- xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec)); ++ xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec) * 4); + } + + ushort +@@ -1062,6 +1064,9 @@ xunloadfont(Font *f) + void + xunloadfonts(void) + { ++ /* Clear Harfbuzz font cache. */ ++ hbunloadfonts(); ++ + /* Free the loaded fonts in the font cache. */ + while (frclen > 0) + XftFontClose(xw.dpy, frc[--frclen].font); +@@ -1185,7 +1190,7 @@ xinit(int cols, int rows) + XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); + + /* font spec buffer */ +- xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec)); ++ xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec) * 4); + + /* Xft rendering context */ + xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); +@@ -1239,6 +1244,22 @@ xinit(int cols, int rows) + xsel.xtarget = XA_STRING; + } + ++void ++xresetfontsettings(ushort mode, Font **font, int *frcflags) ++{ ++ *font = &dc.font; ++ if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { ++ *font = &dc.ibfont; ++ *frcflags = FRC_ITALICBOLD; ++ } else if (mode & ATTR_ITALIC) { ++ *font = &dc.ifont; ++ *frcflags = FRC_ITALIC; ++ } else if (mode & ATTR_BOLD) { ++ *font = &dc.bfont; ++ *frcflags = FRC_BOLD; ++ } ++} ++ + int + xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) + { +@@ -1253,128 +1274,156 @@ 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, f, length = 0, start = 0, numspecs = 0; ++ float cluster_xp = xp, cluster_yp = yp; ++ HbTransformData shaped = { 0 }; ++ ++ /* Initial values. */ ++ mode = prevmode = glyphs[0].mode & ~ATTR_WRAP; ++ xresetfontsettings(mode, &font, &frcflags); + + for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { +- /* Fetch rune and mode for current glyph. */ +- rune = glyphs[i].u; +- mode = glyphs[i].mode; ++ mode = glyphs[i].mode & ~ATTR_WRAP; + + /* Skip dummy wide-character spacing. */ +- if (mode == ATTR_WDUMMY) ++ if (mode & ATTR_WDUMMY && i < (len - 1)) + continue; + +- /* Determine font for glyph if different from previous glyph. */ +- if (prevmode != mode) { +- prevmode = mode; +- font = &dc.font; +- frcflags = FRC_NORMAL; +- runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f); +- if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { +- font = &dc.ibfont; +- frcflags = FRC_ITALICBOLD; +- } else if (mode & ATTR_ITALIC) { +- font = &dc.ifont; +- frcflags = FRC_ITALIC; +- } else if (mode & ATTR_BOLD) { +- font = &dc.bfont; +- frcflags = FRC_BOLD; ++ if ( ++ prevmode != mode ++ || ATTRCMP(glyphs[start], glyphs[i]) ++ || selected(x + i, y) != selected(x + start, y) ++ || i == (len - 1) ++ ) { ++ /* Handle 1-character wide segments and end of line */ ++ length = i - start; ++ if (i == start) { ++ length = 1; ++ } else if (i == (len - 1)) { ++ length = (i - start + 1); + } +- yp = winy + font->ascent; +- } + +- /* 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; +- numspecs++; +- continue; +- } +- +- /* Fallback on font cache, search the font cache for match. */ +- for (f = 0; f < frclen; f++) { +- glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); +- /* Everything correct. */ +- if (glyphidx && frc[f].flags == frcflags) +- break; +- /* We got a default font for a not found glyph. */ +- if (!glyphidx && frc[f].flags == frcflags +- && frc[f].unicodep == rune) { +- break; ++ /* Shape the segment. */ ++ hbtransform(&shaped, font->match, glyphs, start, length); ++ runewidth = win.cw * ((glyphs[start].mode & ATTR_WIDE) ? 2.0f : 1.0f); ++ cluster_xp = xp; cluster_yp = yp; ++ for (int code_idx = 0; code_idx < shaped.count; code_idx++) { ++ int idx = shaped.glyphs[code_idx].cluster; ++ ++ if (glyphs[start + idx].mode & ATTR_WDUMMY) ++ continue; ++ ++ /* Advance the drawing cursor if we've moved to a new cluster */ ++ if (code_idx > 0 && idx != shaped.glyphs[code_idx - 1].cluster) { ++ xp += runewidth; ++ cluster_xp = xp; ++ cluster_yp = yp; ++ runewidth = win.cw * ((glyphs[start + idx].mode & ATTR_WIDE) ? 2.0f : 1.0f); ++ } ++ ++ if (shaped.glyphs[code_idx].codepoint != 0) { ++ /* If symbol is found, put it into the specs. */ ++ specs[numspecs].font = font->match; ++ specs[numspecs].glyph = shaped.glyphs[code_idx].codepoint; ++ specs[numspecs].x = cluster_xp + (short)(shaped.positions[code_idx].x_offset / 64.); ++ specs[numspecs].y = cluster_yp - (short)(shaped.positions[code_idx].y_offset / 64.); ++ cluster_xp += shaped.positions[code_idx].x_advance / 64.; ++ cluster_yp += shaped.positions[code_idx].y_advance / 64.; ++ numspecs++; ++ } else { ++ /* If it's not found, try to fetch it through the font cache. */ ++ rune = glyphs[start + idx].u; ++ for (f = 0; f < frclen; f++) { ++ glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); ++ /* Everything correct. */ ++ if (glyphidx && frc[f].flags == frcflags) ++ break; ++ /* We got a default font for a not found glyph. */ ++ if (!glyphidx && frc[f].flags == frcflags ++ && frc[f].unicodep == rune) { ++ break; ++ } ++ } ++ ++ /* Nothing was found. Use fontconfig to find matching font. */ ++ if (f >= frclen) { ++ if (!font->set) ++ font->set = FcFontSort(0, font->pattern, ++ 1, 0, &fcres); ++ fcsets[0] = font->set; ++ ++ /* ++ * Nothing was found in the cache. Now use ++ * some dozen of Fontconfig calls to get the ++ * font for one single character. ++ * ++ * Xft and fontconfig are design failures. ++ */ ++ fcpattern = FcPatternDuplicate(font->pattern); ++ fccharset = FcCharSetCreate(); ++ ++ FcCharSetAddChar(fccharset, rune); ++ FcPatternAddCharSet(fcpattern, FC_CHARSET, ++ fccharset); ++ FcPatternAddBool(fcpattern, FC_SCALABLE, 1); ++ ++ FcConfigSubstitute(0, fcpattern, ++ FcMatchPattern); ++ FcDefaultSubstitute(fcpattern); ++ ++ fontpattern = FcFontSetMatch(0, fcsets, 1, ++ fcpattern, &fcres); ++ ++ /* Allocate memory for the new cache entry. */ ++ if (frclen >= frccap) { ++ frccap += 16; ++ frc = xrealloc(frc, frccap * sizeof(Fontcache)); ++ } ++ ++ frc[frclen].font = XftFontOpenPattern(xw.dpy, ++ fontpattern); ++ if (!frc[frclen].font) ++ die("XftFontOpenPattern failed seeking fallback font: %s\n", ++ strerror(errno)); ++ frc[frclen].flags = frcflags; ++ frc[frclen].unicodep = rune; ++ ++ glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); ++ ++ f = frclen; ++ frclen++; ++ ++ FcPatternDestroy(fcpattern); ++ FcCharSetDestroy(fccharset); ++ } ++ ++ specs[numspecs].font = frc[f].font; ++ specs[numspecs].glyph = glyphidx; ++ specs[numspecs].x = (short)xp; ++ specs[numspecs].y = (short)yp; ++ numspecs++; ++ } + } +- } + +- /* Nothing was found. Use fontconfig to find matching font. */ +- if (f >= frclen) { +- if (!font->set) +- font->set = FcFontSort(0, font->pattern, +- 1, 0, &fcres); +- fcsets[0] = font->set; ++ /* Cleanup and get ready for next segment. */ ++ hbcleanup(&shaped); ++ start = i; + +- /* +- * Nothing was found in the cache. Now use +- * some dozen of Fontconfig calls to get the +- * font for one single character. +- * +- * Xft and fontconfig are design failures. +- */ +- fcpattern = FcPatternDuplicate(font->pattern); +- fccharset = FcCharSetCreate(); +- +- FcCharSetAddChar(fccharset, rune); +- FcPatternAddCharSet(fcpattern, FC_CHARSET, +- fccharset); +- FcPatternAddBool(fcpattern, FC_SCALABLE, 1); +- +- FcConfigSubstitute(0, fcpattern, +- FcMatchPattern); +- FcDefaultSubstitute(fcpattern); +- +- fontpattern = FcFontSetMatch(0, fcsets, 1, +- fcpattern, &fcres); +- +- /* Allocate memory for the new cache entry. */ +- if (frclen >= frccap) { +- frccap += 16; +- frc = xrealloc(frc, frccap * sizeof(Fontcache)); ++ /* Determine font for glyph if different from previous glyph. */ ++ if (prevmode != mode) { ++ prevmode = mode; ++ xresetfontsettings(mode, &font, &frcflags); ++ yp = winy + font->ascent; + } +- +- frc[frclen].font = XftFontOpenPattern(xw.dpy, +- fontpattern); +- if (!frc[frclen].font) +- die("XftFontOpenPattern failed seeking fallback font: %s\n", +- strerror(errno)); +- frc[frclen].flags = frcflags; +- frc[frclen].unicodep = rune; +- +- glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); +- +- f = frclen; +- frclen++; +- +- FcPatternDestroy(fcpattern); +- FcCharSetDestroy(fccharset); + } +- +- specs[numspecs].font = frc[f].font; +- specs[numspecs].glyph = glyphidx; +- specs[numspecs].x = (short)xp; +- specs[numspecs].y = (short)yp; +- xp += runewidth; +- numspecs++; + } + + return numspecs; + } + + void +-xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) ++xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y, int charlen) + { +- int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); + int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, + width = charlen * win.cw; + Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; +@@ -1510,21 +1559,24 @@ void + xdrawglyph(Glyph g, int x, int y) + { + int numspecs; +- XftGlyphFontSpec spec; ++ XftGlyphFontSpec *specs = xw.specbuf; + +- numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); +- xdrawglyphfontspecs(&spec, g, numspecs, x, y); ++ numspecs = xmakeglyphfontspecs(specs, &g, 1, x, y); ++ xdrawglyphfontspecs(specs, g, numspecs, x, y, (g.mode & ATTR_WIDE) ? 2 : 1); + } + + void +-xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) ++xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og, Line line, int len) + { + Color drawcol; + + /* remove the old cursor */ + if (selected(ox, oy)) + og.mode ^= ATTR_REVERSE; +- xdrawglyph(og, ox, oy); ++ ++ /* Redraw the line where cursor was previously. ++ * It will restore the ligatures broken by the cursor. */ ++ xdrawline(line, 0, oy, len); + + if (IS_SET(MODE_HIDE)) + return; +@@ -1652,18 +1704,16 @@ xdrawline(Line line, int x1, int y1, int x2) + Glyph base, new; + XftGlyphFontSpec *specs = xw.specbuf; + +- numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1); + i = ox = 0; +- for (x = x1; x < x2 && i < numspecs; x++) { ++ for (x = x1; x < x2; x++) { + new = line[x]; + if (new.mode == ATTR_WDUMMY) + continue; + if (selected(x, y1)) + new.mode ^= ATTR_REVERSE; +- if (i > 0 && ATTRCMP(base, new)) { +- xdrawglyphfontspecs(specs, base, i, ox, y1); +- specs += i; +- numspecs -= i; ++ if ((i > 0) && ATTRCMP(base, new)) { ++ numspecs = xmakeglyphfontspecs(specs, &line[ox], x - ox, ox, y1); ++ xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x - ox); + i = 0; + } + if (i == 0) { +@@ -1672,8 +1722,10 @@ xdrawline(Line line, int x1, int y1, int x2) + } + i++; + } +- if (i > 0) +- xdrawglyphfontspecs(specs, base, i, ox, y1); ++ if (i > 0) { ++ numspecs = xmakeglyphfontspecs(specs, &line[ox], x2 - ox, ox, y1); ++ xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x2 - ox); ++ } + } + + void diff --git a/st.suckless.org/patches/ligatures/0.9.2/st-ligatures-alpha-20240427-0.9.2.diff b/st.suckless.org/patches/ligatures/0.9.2/st-ligatures-alpha-20240427-0.9.2.diff @@ -0,0 +1,635 @@ +diff --git a/Makefile b/Makefile +index 470ac86..38240da 100644 +--- a/Makefile ++++ b/Makefile +@@ -4,7 +4,7 @@ + + include config.mk + +-SRC = st.c x.c ++SRC = st.c x.c hb.c + OBJ = $(SRC:.c=.o) + + all: st +@@ -22,7 +22,8 @@ config.h: + $(CC) $(STCFLAGS) -c $< + + st.o: config.h st.h win.h +-x.o: arg.h config.h st.h win.h ++x.o: arg.h config.h st.h win.h hb.h ++hb.o: st.h + + $(OBJ): config.h config.mk + +diff --git a/config.mk b/config.mk +index 47c615e..d7439a3 100644 +--- a/config.mk ++++ b/config.mk +@@ -15,10 +15,12 @@ PKG_CONFIG = pkg-config + # includes and libs + INCS = -I$(X11INC) \ + `$(PKG_CONFIG) --cflags fontconfig` \ +- `$(PKG_CONFIG) --cflags freetype2` +-LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft -lXrender\ ++ `$(PKG_CONFIG) --cflags freetype2` \ ++ `$(PKG_CONFIG) --cflags harfbuzz` ++LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft -lXrender \ + `$(PKG_CONFIG) --libs fontconfig` \ +- `$(PKG_CONFIG) --libs freetype2` ++ `$(PKG_CONFIG) --libs freetype2` \ ++ `$(PKG_CONFIG) --libs harfbuzz` + + # flags + STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 +diff --git a/hb.c b/hb.c +new file mode 100644 +index 0000000..58d534b +--- /dev/null ++++ b/hb.c +@@ -0,0 +1,125 @@ ++#include <stdlib.h> ++#include <stdio.h> ++#include <math.h> ++#include <X11/Xft/Xft.h> ++#include <X11/cursorfont.h> ++#include <hb.h> ++#include <hb-ft.h> ++ ++#include "st.h" ++#include "hb.h" ++ ++#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 } ++#define BUFFER_STEP 256 ++ ++hb_font_t *hbfindfont(XftFont *match); ++ ++typedef struct { ++ XftFont *match; ++ hb_font_t *font; ++} HbFontMatch; ++ ++typedef struct { ++ size_t capacity; ++ HbFontMatch *fonts; ++} HbFontCache; ++ ++static HbFontCache hbfontcache = { 0, NULL }; ++ ++typedef struct { ++ size_t capacity; ++ Rune *runes; ++} RuneBuffer; ++ ++static RuneBuffer hbrunebuffer = { 0, NULL }; ++ ++/* ++ * Poplulate the array with a list of font features, wrapped in FEATURE macro, ++ * e. g. ++ * FEATURE('c', 'a', 'l', 't'), FEATURE('d', 'l', 'i', 'g') ++ */ ++hb_feature_t features[] = { }; ++ ++void ++hbunloadfonts() ++{ ++ for (int i = 0; i < hbfontcache.capacity; i++) { ++ hb_font_destroy(hbfontcache.fonts[i].font); ++ XftUnlockFace(hbfontcache.fonts[i].match); ++ } ++ ++ if (hbfontcache.fonts != NULL) { ++ free(hbfontcache.fonts); ++ hbfontcache.fonts = NULL; ++ } ++ hbfontcache.capacity = 0; ++} ++ ++hb_font_t * ++hbfindfont(XftFont *match) ++{ ++ for (int i = 0; i < hbfontcache.capacity; i++) { ++ if (hbfontcache.fonts[i].match == match) ++ return hbfontcache.fonts[i].font; ++ } ++ ++ /* Font not found in cache, caching it now. */ ++ hbfontcache.fonts = realloc(hbfontcache.fonts, sizeof(HbFontMatch) * (hbfontcache.capacity + 1)); ++ FT_Face face = XftLockFace(match); ++ hb_font_t *font = hb_ft_font_create(face, NULL); ++ if (font == NULL) ++ die("Failed to load Harfbuzz font."); ++ ++ hbfontcache.fonts[hbfontcache.capacity].match = match; ++ hbfontcache.fonts[hbfontcache.capacity].font = font; ++ hbfontcache.capacity += 1; ++ ++ return font; ++} ++ ++void hbtransform(HbTransformData *data, XftFont *xfont, const Glyph *glyphs, int start, int length) { ++ ushort mode = USHRT_MAX; ++ unsigned int glyph_count; ++ int rune_idx, glyph_idx, end = start + length; ++ ++ hb_font_t *font = hbfindfont(xfont); ++ if (font == NULL) ++ return; ++ ++ hb_buffer_t *buffer = hb_buffer_create(); ++ hb_buffer_set_direction(buffer, HB_DIRECTION_LTR); ++ hb_buffer_set_cluster_level(buffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS); ++ ++ /* Resize the buffer if required length is larger. */ ++ if (hbrunebuffer.capacity < length) { ++ hbrunebuffer.capacity = (length / BUFFER_STEP + 1) * BUFFER_STEP; ++ hbrunebuffer.runes = realloc(hbrunebuffer.runes, hbrunebuffer.capacity * sizeof(Rune)); ++ } ++ ++ /* Fill buffer with codepoints. */ ++ for (rune_idx = 0, glyph_idx = start; glyph_idx < end; glyph_idx++, rune_idx++) { ++ hbrunebuffer.runes[rune_idx] = glyphs[glyph_idx].u; ++ mode = glyphs[glyph_idx].mode; ++ if (mode & ATTR_WDUMMY) ++ hbrunebuffer.runes[rune_idx] = 0x0020; ++ } ++ hb_buffer_add_codepoints(buffer, hbrunebuffer.runes, length, 0, length); ++ ++ /* Shape the segment. */ ++ hb_shape(font, buffer, features, sizeof(features)/sizeof(hb_feature_t)); ++ ++ /* Get new glyph info. */ ++ hb_glyph_info_t *info = hb_buffer_get_glyph_infos(buffer, &glyph_count); ++ hb_glyph_position_t *pos = hb_buffer_get_glyph_positions(buffer, &glyph_count); ++ ++ /* Fill the output. */ ++ data->buffer = buffer; ++ data->glyphs = info; ++ data->positions = pos; ++ data->count = glyph_count; ++} ++ ++void hbcleanup(HbTransformData *data) { ++ hb_buffer_destroy(data->buffer); ++ memset(data, 0, sizeof(HbTransformData)); ++} +diff --git a/hb.h b/hb.h +new file mode 100644 +index 0000000..3b0ef44 +--- /dev/null ++++ b/hb.h +@@ -0,0 +1,14 @@ ++#include <X11/Xft/Xft.h> ++#include <hb.h> ++#include <hb-ft.h> ++ ++typedef struct { ++ hb_buffer_t *buffer; ++ hb_glyph_info_t *glyphs; ++ hb_glyph_position_t *positions; ++ unsigned int count; ++} HbTransformData; ++ ++void hbunloadfonts(); ++void hbtransform(HbTransformData *, XftFont *, const Glyph *, int, int); ++void hbcleanup(HbTransformData *); +diff --git a/st.c b/st.c +index 62def59..041c6d8 100644 +--- a/st.c ++++ b/st.c +@@ -2640,7 +2640,8 @@ draw(void) + + drawregion(0, 0, term.col, term.row); + xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], +- term.ocx, term.ocy, term.line[term.ocy][term.ocx]); ++ term.ocx, term.ocy, term.line[term.ocy][term.ocx], ++ term.line[term.ocy], term.col); + term.ocx = cx; + term.ocy = term.c.y; + xfinishdraw(); +diff --git a/st.h b/st.h +index 9f91e2a..b1a6256 100644 +--- a/st.h ++++ b/st.h +@@ -11,7 +11,8 @@ + #define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d)) + #define DEFAULT(a, b) (a) = (a) ? (a) : (b) + #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) +-#define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \ ++#define ATTRCMP(a, b) (((a).mode & (~ATTR_WRAP)) != ((b).mode & (~ATTR_WRAP)) || \ ++ (a).fg != (b).fg || \ + (a).bg != (b).bg) + #define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \ + (t1.tv_nsec-t2.tv_nsec)/1E6) +diff --git a/win.h b/win.h +index 6de960d..94679e4 100644 +--- a/win.h ++++ b/win.h +@@ -25,7 +25,7 @@ enum win_mode { + + void xbell(void); + void xclipcopy(void); +-void xdrawcursor(int, int, Glyph, int, int, Glyph); ++void xdrawcursor(int, int, Glyph, int, int, Glyph, Line, int); + void xdrawline(Line, int, int, int); + void xfinishdraw(void); + void xloadcols(void); +diff --git a/x.c b/x.c +index 27e81d1..0632636 100644 +--- a/x.c ++++ b/x.c +@@ -19,6 +19,7 @@ char *argv0; + #include "arg.h" + #include "st.h" + #include "win.h" ++#include "hb.h" + + /* types used in config.h */ + typedef struct { +@@ -142,8 +143,9 @@ typedef struct { + } DC; + + static inline ushort sixd_to_16bit(int); ++static void xresetfontsettings(ushort mode, Font **font, int *frcflags); + static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); +-static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); ++static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int, int); + static void xdrawglyph(Glyph, int, int); + static void xclear(int, int, int, int); + static int xgeommasktogravity(int); +@@ -759,7 +761,7 @@ xresize(int col, int row) + xclear(0, 0, win.w, win.h); + + /* resize to new width */ +- xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec)); ++ xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec) * 4); + } + + ushort +@@ -1071,6 +1073,9 @@ xunloadfont(Font *f) + void + xunloadfonts(void) + { ++ /* Clear Harfbuzz font cache. */ ++ hbunloadfonts(); ++ + /* Free the loaded fonts in the font cache. */ + while (frclen > 0) + XftFontClose(xw.dpy, frc[--frclen].font); +@@ -1202,7 +1207,7 @@ xinit(int cols, int rows) + XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); + + /* font spec buffer */ +- xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec)); ++ xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec) * 4); + + /* Xft rendering context */ + xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); +@@ -1256,6 +1261,22 @@ xinit(int cols, int rows) + xsel.xtarget = XA_STRING; + } + ++void ++xresetfontsettings(ushort mode, Font **font, int *frcflags) ++{ ++ *font = &dc.font; ++ if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { ++ *font = &dc.ibfont; ++ *frcflags = FRC_ITALICBOLD; ++ } else if (mode & ATTR_ITALIC) { ++ *font = &dc.ifont; ++ *frcflags = FRC_ITALIC; ++ } else if (mode & ATTR_BOLD) { ++ *font = &dc.bfont; ++ *frcflags = FRC_BOLD; ++ } ++} ++ + int + xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) + { +@@ -1270,128 +1291,157 @@ 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, f, length = 0, start = 0, numspecs = 0; ++ float cluster_xp = xp, cluster_yp = yp; ++ HbTransformData shaped = { 0 }; ++ ++ /* Initial values. */ ++ mode = prevmode = glyphs[0].mode & ~ATTR_WRAP; ++ xresetfontsettings(mode, &font, &frcflags); + + for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { +- /* Fetch rune and mode for current glyph. */ +- rune = glyphs[i].u; +- mode = glyphs[i].mode; ++ mode = glyphs[i].mode & ~ATTR_WRAP; + + /* Skip dummy wide-character spacing. */ +- if (mode == ATTR_WDUMMY) ++ if (mode & ATTR_WDUMMY && i < (len - 1)) + continue; + +- /* Determine font for glyph if different from previous glyph. */ +- if (prevmode != mode) { +- prevmode = mode; +- font = &dc.font; +- frcflags = FRC_NORMAL; +- runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f); +- if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { +- font = &dc.ibfont; +- frcflags = FRC_ITALICBOLD; +- } else if (mode & ATTR_ITALIC) { +- font = &dc.ifont; +- frcflags = FRC_ITALIC; +- } else if (mode & ATTR_BOLD) { +- font = &dc.bfont; +- frcflags = FRC_BOLD; ++ if ( ++ prevmode != mode ++ || ATTRCMP(glyphs[start], glyphs[i]) ++ || selected(x + i, y) != selected(x + start, y) ++ || i == (len - 1) ++ ) { ++ /* Handle 1-character wide segments and end of line */ ++ length = i - start; ++ if (i == start) { ++ length = 1; ++ } else if (i == (len - 1)) { ++ length = (i - start + 1); + } +- yp = winy + font->ascent; +- } + +- /* 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; +- numspecs++; +- continue; +- } +- +- /* Fallback on font cache, search the font cache for match. */ +- for (f = 0; f < frclen; f++) { +- glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); +- /* Everything correct. */ +- if (glyphidx && frc[f].flags == frcflags) +- break; +- /* We got a default font for a not found glyph. */ +- if (!glyphidx && frc[f].flags == frcflags +- && frc[f].unicodep == rune) { +- break; ++ /* Shape the segment. */ ++ hbtransform(&shaped, font->match, glyphs, start, length); ++ runewidth = win.cw * ((glyphs[start].mode & ATTR_WIDE) ? 2.0f : 1.0f); ++ cluster_xp = xp; cluster_yp = yp; ++ for (int code_idx = 0; code_idx < shaped.count; code_idx++) { ++ int idx = shaped.glyphs[code_idx].cluster; ++ ++ if (glyphs[start + idx].mode & ATTR_WDUMMY) ++ continue; ++ ++ /* Advance the drawing cursor if we've moved to a new cluster */ ++ if (code_idx > 0 && idx != shaped.glyphs[code_idx - 1].cluster) { ++ xp += runewidth; ++ cluster_xp = xp; ++ cluster_yp = yp; ++ runewidth = win.cw * ((glyphs[start + idx].mode & ATTR_WIDE) ? 2.0f : 1.0f); ++ } ++ ++ if (shaped.glyphs[code_idx].codepoint != 0) { ++ /* If symbol is found, put it into the specs. */ ++ specs[numspecs].font = font->match; ++ specs[numspecs].glyph = shaped.glyphs[code_idx].codepoint; ++ specs[numspecs].x = cluster_xp + (short)(shaped.positions[code_idx].x_offset / 64.); ++ specs[numspecs].y = cluster_yp - (short)(shaped.positions[code_idx].y_offset / 64.); ++ cluster_xp += shaped.positions[code_idx].x_advance / 64.; ++ cluster_yp += shaped.positions[code_idx].y_advance / 64.; ++ numspecs++; ++ } else { ++ /* If it's not found, try to fetch it through the font cache. */ ++ rune = glyphs[start + idx].u; ++ for (f = 0; f < frclen; f++) { ++ glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); ++ /* Everything correct. */ ++ if (glyphidx && frc[f].flags == frcflags) ++ break; ++ /* We got a default font for a not found glyph. */ ++ if (!glyphidx && frc[f].flags == frcflags ++ && frc[f].unicodep == rune) { ++ break; ++ } ++ } ++ ++ /* Nothing was found. Use fontconfig to find matching font. */ ++ if (f >= frclen) { ++ if (!font->set) ++ font->set = FcFontSort(0, font->pattern, ++ 1, 0, &fcres); ++ fcsets[0] = font->set; ++ ++ /* ++ * Nothing was found in the cache. Now use ++ * some dozen of Fontconfig calls to get the ++ * font for one single character. ++ * ++ * Xft and fontconfig are design failures. ++ */ ++ fcpattern = FcPatternDuplicate(font->pattern); ++ fccharset = FcCharSetCreate(); ++ ++ FcCharSetAddChar(fccharset, rune); ++ FcPatternAddCharSet(fcpattern, FC_CHARSET, ++ fccharset); ++ FcPatternAddBool(fcpattern, FC_SCALABLE, 1); ++ ++ FcConfigSubstitute(0, fcpattern, ++ FcMatchPattern); ++ FcDefaultSubstitute(fcpattern); ++ ++ fontpattern = FcFontSetMatch(0, fcsets, 1, ++ fcpattern, &fcres); ++ ++ /* Allocate memory for the new cache entry. */ ++ if (frclen >= frccap) { ++ frccap += 16; ++ frc = xrealloc(frc, frccap * sizeof(Fontcache)); ++ } ++ ++ frc[frclen].font = XftFontOpenPattern(xw.dpy, ++ fontpattern); ++ if (!frc[frclen].font) ++ die("XftFontOpenPattern failed seeking fallback font: %s\n", ++ strerror(errno)); ++ frc[frclen].flags = frcflags; ++ frc[frclen].unicodep = rune; ++ ++ glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); ++ ++ f = frclen; ++ frclen++; ++ ++ FcPatternDestroy(fcpattern); ++ FcCharSetDestroy(fccharset); ++ } ++ ++ specs[numspecs].font = frc[f].font; ++ specs[numspecs].glyph = glyphidx; ++ specs[numspecs].x = (short)xp; ++ specs[numspecs].y = (short)yp; ++ numspecs++; ++ } + } +- } + +- /* Nothing was found. Use fontconfig to find matching font. */ +- if (f >= frclen) { +- if (!font->set) +- font->set = FcFontSort(0, font->pattern, +- 1, 0, &fcres); +- fcsets[0] = font->set; ++ /* Cleanup and get ready for next segment. */ ++ hbcleanup(&shaped); ++ start = i; + +- /* +- * Nothing was found in the cache. Now use +- * some dozen of Fontconfig calls to get the +- * font for one single character. +- * +- * Xft and fontconfig are design failures. +- */ +- fcpattern = FcPatternDuplicate(font->pattern); +- fccharset = FcCharSetCreate(); +- +- FcCharSetAddChar(fccharset, rune); +- FcPatternAddCharSet(fcpattern, FC_CHARSET, +- fccharset); +- FcPatternAddBool(fcpattern, FC_SCALABLE, 1); +- +- FcConfigSubstitute(0, fcpattern, +- FcMatchPattern); +- FcDefaultSubstitute(fcpattern); +- +- fontpattern = FcFontSetMatch(0, fcsets, 1, +- fcpattern, &fcres); +- +- /* Allocate memory for the new cache entry. */ +- if (frclen >= frccap) { +- frccap += 16; +- frc = xrealloc(frc, frccap * sizeof(Fontcache)); ++ /* Determine font for glyph if different from previous glyph. */ ++ if (prevmode != mode) { ++ prevmode = mode; ++ xresetfontsettings(mode, &font, &frcflags); ++ yp = winy + font->ascent; + } +- +- frc[frclen].font = XftFontOpenPattern(xw.dpy, +- fontpattern); +- if (!frc[frclen].font) +- die("XftFontOpenPattern failed seeking fallback font: %s\n", +- strerror(errno)); +- frc[frclen].flags = frcflags; +- frc[frclen].unicodep = rune; +- +- glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); +- +- f = frclen; +- frclen++; +- +- FcPatternDestroy(fcpattern); +- FcCharSetDestroy(fccharset); + } +- +- specs[numspecs].font = frc[f].font; +- specs[numspecs].glyph = glyphidx; +- specs[numspecs].x = (short)xp; +- specs[numspecs].y = (short)yp; +- xp += runewidth; +- numspecs++; + } + ++ hbcleanup(&shaped); + return numspecs; + } + + void +-xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) ++xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y, int charlen) + { +- int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); + int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, + width = charlen * win.cw; + Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; +@@ -1527,21 +1577,24 @@ void + xdrawglyph(Glyph g, int x, int y) + { + int numspecs; +- XftGlyphFontSpec spec; ++ XftGlyphFontSpec *specs = xw.specbuf; + +- numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); +- xdrawglyphfontspecs(&spec, g, numspecs, x, y); ++ numspecs = xmakeglyphfontspecs(specs, &g, 1, x, y); ++ xdrawglyphfontspecs(specs, g, numspecs, x, y, (g.mode & ATTR_WIDE) ? 2 : 1); + } + + void +-xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) ++xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og, Line line, int len) + { + Color drawcol; + + /* remove the old cursor */ + if (selected(ox, oy)) + og.mode ^= ATTR_REVERSE; +- xdrawglyph(og, ox, oy); ++ ++ /* Redraw the line where cursor was previously. ++ * It will restore the ligatures broken by the cursor. */ ++ xdrawline(line, 0, oy, len); + + if (IS_SET(MODE_HIDE)) + return; +@@ -1669,18 +1722,16 @@ xdrawline(Line line, int x1, int y1, int x2) + Glyph base, new; + XftGlyphFontSpec *specs = xw.specbuf; + +- numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1); + i = ox = 0; +- for (x = x1; x < x2 && i < numspecs; x++) { ++ for (x = x1; x < x2; x++) { + new = line[x]; + if (new.mode == ATTR_WDUMMY) + continue; + if (selected(x, y1)) + new.mode ^= ATTR_REVERSE; +- if (i > 0 && ATTRCMP(base, new)) { +- xdrawglyphfontspecs(specs, base, i, ox, y1); +- specs += i; +- numspecs -= i; ++ if ((i > 0) && ATTRCMP(base, new)) { ++ numspecs = xmakeglyphfontspecs(specs, &line[ox], x - ox, ox, y1); ++ xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x - ox); + i = 0; + } + if (i == 0) { +@@ -1689,8 +1740,10 @@ xdrawline(Line line, int x1, int y1, int x2) + } + i++; + } +- if (i > 0) +- xdrawglyphfontspecs(specs, base, i, ox, y1); ++ if (i > 0) { ++ numspecs = xmakeglyphfontspecs(specs, &line[ox], x2 - ox, ox, y1); ++ xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x2 - ox); ++ } + } + + void diff --git a/st.suckless.org/patches/ligatures/0.9.2/st-ligatures-alpha-scrollback-20240427-0.9.2.diff b/st.suckless.org/patches/ligatures/0.9.2/st-ligatures-alpha-scrollback-20240427-0.9.2.diff @@ -0,0 +1,635 @@ +diff --git a/Makefile b/Makefile +index 470ac86..38240da 100644 +--- a/Makefile ++++ b/Makefile +@@ -4,7 +4,7 @@ + + include config.mk + +-SRC = st.c x.c ++SRC = st.c x.c hb.c + OBJ = $(SRC:.c=.o) + + all: st +@@ -22,7 +22,8 @@ config.h: + $(CC) $(STCFLAGS) -c $< + + st.o: config.h st.h win.h +-x.o: arg.h config.h st.h win.h ++x.o: arg.h config.h st.h win.h hb.h ++hb.o: st.h + + $(OBJ): config.h config.mk + +diff --git a/config.mk b/config.mk +index 47c615e..d7439a3 100644 +--- a/config.mk ++++ b/config.mk +@@ -15,10 +15,12 @@ PKG_CONFIG = pkg-config + # includes and libs + INCS = -I$(X11INC) \ + `$(PKG_CONFIG) --cflags fontconfig` \ +- `$(PKG_CONFIG) --cflags freetype2` +-LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft -lXrender\ ++ `$(PKG_CONFIG) --cflags freetype2` \ ++ `$(PKG_CONFIG) --cflags harfbuzz` ++LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft -lXrender \ + `$(PKG_CONFIG) --libs fontconfig` \ +- `$(PKG_CONFIG) --libs freetype2` ++ `$(PKG_CONFIG) --libs freetype2` \ ++ `$(PKG_CONFIG) --libs harfbuzz` + + # flags + STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 +diff --git a/hb.c b/hb.c +new file mode 100644 +index 0000000..99412c8 +--- /dev/null ++++ b/hb.c +@@ -0,0 +1,125 @@ ++#include <stdlib.h> ++#include <stdio.h> ++#include <math.h> ++#include <X11/Xft/Xft.h> ++#include <X11/cursorfont.h> ++#include <hb.h> ++#include <hb-ft.h> ++ ++#include "st.h" ++#include "hb.h" ++ ++#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 } ++#define BUFFER_STEP 256 ++ ++hb_font_t *hbfindfont(XftFont *match); ++ ++typedef struct { ++ XftFont *match; ++ hb_font_t *font; ++} HbFontMatch; ++ ++typedef struct { ++ size_t capacity; ++ HbFontMatch *fonts; ++} HbFontCache; ++ ++static HbFontCache hbfontcache = { 0, NULL }; ++ ++typedef struct { ++ size_t capacity; ++ Rune *runes; ++} RuneBuffer; ++ ++static RuneBuffer hbrunebuffer = { 0, NULL }; ++ ++/* ++ * Poplulate the array with a list of font features, wrapped in FEATURE macro, ++ * e. g. ++ * FEATURE('c', 'a', 'l', 't'), FEATURE('d', 'l', 'i', 'g') ++ */ ++hb_feature_t features[] = { }; ++ ++void ++hbunloadfonts() ++{ ++ for (int i = 0; i < hbfontcache.capacity; i++) { ++ hb_font_destroy(hbfontcache.fonts[i].font); ++ XftUnlockFace(hbfontcache.fonts[i].match); ++ } ++ ++ if (hbfontcache.fonts != NULL) { ++ free(hbfontcache.fonts); ++ hbfontcache.fonts = NULL; ++ } ++ hbfontcache.capacity = 0; ++} ++ ++hb_font_t * ++hbfindfont(XftFont *match) ++{ ++ for (int i = 0; i < hbfontcache.capacity; i++) { ++ if (hbfontcache.fonts[i].match == match) ++ return hbfontcache.fonts[i].font; ++ } ++ ++ /* Font not found in cache, caching it now. */ ++ hbfontcache.fonts = realloc(hbfontcache.fonts, sizeof(HbFontMatch) * (hbfontcache.capacity + 1)); ++ FT_Face face = XftLockFace(match); ++ hb_font_t *font = hb_ft_font_create(face, NULL); ++ if (font == NULL) ++ die("Failed to load Harfbuzz font."); ++ ++ hbfontcache.fonts[hbfontcache.capacity].match = match; ++ hbfontcache.fonts[hbfontcache.capacity].font = font; ++ hbfontcache.capacity += 1; ++ ++ return font; ++} ++ ++void hbtransform(HbTransformData *data, XftFont *xfont, const Glyph *glyphs, int start, int length) { ++ ushort mode = USHRT_MAX; ++ unsigned int glyph_count; ++ int rune_idx, glyph_idx, end = start + length; ++ ++ hb_font_t *font = hbfindfont(xfont); ++ if (font == NULL) ++ return; ++ ++ hb_buffer_t *buffer = hb_buffer_create(); ++ hb_buffer_set_direction(buffer, HB_DIRECTION_LTR); ++ hb_buffer_set_cluster_level(buffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS); ++ ++ /* Resize the buffer if required length is larger. */ ++ if (hbrunebuffer.capacity < length) { ++ hbrunebuffer.capacity = (length / BUFFER_STEP + 1) * BUFFER_STEP; ++ hbrunebuffer.runes = realloc(hbrunebuffer.runes, hbrunebuffer.capacity * sizeof(Rune)); ++ } ++ ++ /* Fill buffer with codepoints. */ ++ for (rune_idx = 0, glyph_idx = start; glyph_idx < end; glyph_idx++, rune_idx++) { ++ hbrunebuffer.runes[rune_idx] = glyphs[glyph_idx].u; ++ mode = glyphs[glyph_idx].mode; ++ if (mode & ATTR_WDUMMY) ++ hbrunebuffer.runes[rune_idx] = 0x0020; ++ } ++ hb_buffer_add_codepoints(buffer, hbrunebuffer.runes, length, 0, length); ++ ++ /* Shape the segment. */ ++ hb_shape(font, buffer, features, sizeof(features)/sizeof(hb_feature_t)); ++ ++ /* Get new glyph info. */ ++ hb_glyph_info_t *info = hb_buffer_get_glyph_infos(buffer, &glyph_count); ++ hb_glyph_position_t *pos = hb_buffer_get_glyph_positions(buffer, &glyph_count); ++ ++ /* Fill the output. */ ++ data->buffer = buffer; ++ data->glyphs = info; ++ data->positions = pos; ++ data->count = glyph_count; ++} ++ ++void hbcleanup(HbTransformData *data) { ++ hb_buffer_destroy(data->buffer); ++ memset(data, 0, sizeof(HbTransformData)); ++} +diff --git a/hb.h b/hb.h +new file mode 100644 +index 0000000..3b0ef44 +--- /dev/null ++++ b/hb.h +@@ -0,0 +1,14 @@ ++#include <X11/Xft/Xft.h> ++#include <hb.h> ++#include <hb-ft.h> ++ ++typedef struct { ++ hb_buffer_t *buffer; ++ hb_glyph_info_t *glyphs; ++ hb_glyph_position_t *positions; ++ unsigned int count; ++} HbTransformData; ++ ++void hbunloadfonts(); ++void hbtransform(HbTransformData *, XftFont *, const Glyph *, int, int); ++void hbcleanup(HbTransformData *); +diff --git a/st.c b/st.c +index 79ee9ba..454771d 100644 +--- a/st.c ++++ b/st.c +@@ -2711,7 +2711,9 @@ draw(void) + drawregion(0, 0, term.col, term.row); + if (term.scr == 0) + xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], +- term.ocx, term.ocy, term.line[term.ocy][term.ocx]); ++ term.ocx, term.ocy, term.line[term.ocy][term.ocx], ++ term.line[term.ocy], term.col); ++ + term.ocx = cx; + term.ocy = term.c.y; + xfinishdraw(); +diff --git a/st.h b/st.h +index 78762a2..01eea49 100644 +--- a/st.h ++++ b/st.h +@@ -11,7 +11,8 @@ + #define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d)) + #define DEFAULT(a, b) (a) = (a) ? (a) : (b) + #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) +-#define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \ ++#define ATTRCMP(a, b) (((a).mode & (~ATTR_WRAP)) != ((b).mode & (~ATTR_WRAP)) || \ ++ (a).fg != (b).fg || \ + (a).bg != (b).bg) + #define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \ + (t1.tv_nsec-t2.tv_nsec)/1E6) +diff --git a/win.h b/win.h +index 6de960d..94679e4 100644 +--- a/win.h ++++ b/win.h +@@ -25,7 +25,7 @@ enum win_mode { + + void xbell(void); + void xclipcopy(void); +-void xdrawcursor(int, int, Glyph, int, int, Glyph); ++void xdrawcursor(int, int, Glyph, int, int, Glyph, Line, int); + void xdrawline(Line, int, int, int); + void xfinishdraw(void); + void xloadcols(void); +diff --git a/x.c b/x.c +index 27e81d1..5e11c1f 100644 +--- a/x.c ++++ b/x.c +@@ -19,6 +19,7 @@ char *argv0; + #include "arg.h" + #include "st.h" + #include "win.h" ++#include "hb.h" + + /* types used in config.h */ + typedef struct { +@@ -142,8 +143,9 @@ typedef struct { + } DC; + + static inline ushort sixd_to_16bit(int); ++static void xresetfontsettings(ushort mode, Font **font, int *frcflags); + static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); +-static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); ++static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int, int); + static void xdrawglyph(Glyph, int, int); + static void xclear(int, int, int, int); + static int xgeommasktogravity(int); +@@ -759,7 +761,7 @@ xresize(int col, int row) + xclear(0, 0, win.w, win.h); + + /* resize to new width */ +- xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec)); ++ xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec) * 4); + } + + ushort +@@ -1071,6 +1073,9 @@ xunloadfont(Font *f) + void + xunloadfonts(void) + { ++ /* Clear Harfbuzz font cache. */ ++ hbunloadfonts(); ++ + /* Free the loaded fonts in the font cache. */ + while (frclen > 0) + XftFontClose(xw.dpy, frc[--frclen].font); +@@ -1202,7 +1207,7 @@ xinit(int cols, int rows) + XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); + + /* font spec buffer */ +- xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec)); ++ xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec) * 4); + + /* Xft rendering context */ + xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); +@@ -1256,6 +1261,22 @@ xinit(int cols, int rows) + xsel.xtarget = XA_STRING; + } + ++void ++xresetfontsettings(ushort mode, Font **font, int *frcflags) ++{ ++ *font = &dc.font; ++ if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { ++ *font = &dc.ibfont; ++ *frcflags = FRC_ITALICBOLD; ++ } else if (mode & ATTR_ITALIC) { ++ *font = &dc.ifont; ++ *frcflags = FRC_ITALIC; ++ } else if (mode & ATTR_BOLD) { ++ *font = &dc.bfont; ++ *frcflags = FRC_BOLD; ++ } ++} ++ + int + xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) + { +@@ -1270,128 +1291,156 @@ 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, f, length = 0, start = 0, numspecs = 0; ++ float cluster_xp = xp, cluster_yp = yp; ++ HbTransformData shaped = { 0 }; ++ ++ /* Initial values. */ ++ mode = prevmode = glyphs[0].mode & ~ATTR_WRAP; ++ xresetfontsettings(mode, &font, &frcflags); + + for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { +- /* Fetch rune and mode for current glyph. */ +- rune = glyphs[i].u; +- mode = glyphs[i].mode; ++ mode = glyphs[i].mode & ~ATTR_WRAP; + + /* Skip dummy wide-character spacing. */ +- if (mode == ATTR_WDUMMY) ++ if (mode & ATTR_WDUMMY && i < (len - 1)) + continue; + +- /* Determine font for glyph if different from previous glyph. */ +- if (prevmode != mode) { +- prevmode = mode; +- font = &dc.font; +- frcflags = FRC_NORMAL; +- runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f); +- if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { +- font = &dc.ibfont; +- frcflags = FRC_ITALICBOLD; +- } else if (mode & ATTR_ITALIC) { +- font = &dc.ifont; +- frcflags = FRC_ITALIC; +- } else if (mode & ATTR_BOLD) { +- font = &dc.bfont; +- frcflags = FRC_BOLD; ++ if ( ++ prevmode != mode ++ || ATTRCMP(glyphs[start], glyphs[i]) ++ || selected(x + i, y) != selected(x + start, y) ++ || i == (len - 1) ++ ) { ++ /* Handle 1-character wide segments and end of line */ ++ length = i - start; ++ if (i == start) { ++ length = 1; ++ } else if (i == (len - 1)) { ++ length = (i - start + 1); + } +- yp = winy + font->ascent; +- } + +- /* 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; +- numspecs++; +- continue; +- } +- +- /* Fallback on font cache, search the font cache for match. */ +- for (f = 0; f < frclen; f++) { +- glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); +- /* Everything correct. */ +- if (glyphidx && frc[f].flags == frcflags) +- break; +- /* We got a default font for a not found glyph. */ +- if (!glyphidx && frc[f].flags == frcflags +- && frc[f].unicodep == rune) { +- break; ++ /* Shape the segment. */ ++ hbtransform(&shaped, font->match, glyphs, start, length); ++ runewidth = win.cw * ((glyphs[start].mode & ATTR_WIDE) ? 2.0f : 1.0f); ++ cluster_xp = xp; cluster_yp = yp; ++ for (int code_idx = 0; code_idx < shaped.count; code_idx++) { ++ int idx = shaped.glyphs[code_idx].cluster; ++ ++ if (glyphs[start + idx].mode & ATTR_WDUMMY) ++ continue; ++ ++ /* Advance the drawing cursor if we've moved to a new cluster */ ++ if (code_idx > 0 && idx != shaped.glyphs[code_idx - 1].cluster) { ++ xp += runewidth; ++ cluster_xp = xp; ++ cluster_yp = yp; ++ runewidth = win.cw * ((glyphs[start + idx].mode & ATTR_WIDE) ? 2.0f : 1.0f); ++ } ++ ++ if (shaped.glyphs[code_idx].codepoint != 0) { ++ /* If symbol is found, put it into the specs. */ ++ specs[numspecs].font = font->match; ++ specs[numspecs].glyph = shaped.glyphs[code_idx].codepoint; ++ specs[numspecs].x = cluster_xp + (short)(shaped.positions[code_idx].x_offset / 64.); ++ specs[numspecs].y = cluster_yp - (short)(shaped.positions[code_idx].y_offset / 64.); ++ cluster_xp += shaped.positions[code_idx].x_advance / 64.; ++ cluster_yp += shaped.positions[code_idx].y_advance / 64.; ++ numspecs++; ++ } else { ++ /* If it's not found, try to fetch it through the font cache. */ ++ rune = glyphs[start + idx].u; ++ for (f = 0; f < frclen; f++) { ++ glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); ++ /* Everything correct. */ ++ if (glyphidx && frc[f].flags == frcflags) ++ break; ++ /* We got a default font for a not found glyph. */ ++ if (!glyphidx && frc[f].flags == frcflags ++ && frc[f].unicodep == rune) { ++ break; ++ } ++ } ++ ++ /* Nothing was found. Use fontconfig to find matching font. */ ++ if (f >= frclen) { ++ if (!font->set) ++ font->set = FcFontSort(0, font->pattern, ++ 1, 0, &fcres); ++ fcsets[0] = font->set; ++ ++ /* ++ * Nothing was found in the cache. Now use ++ * some dozen of Fontconfig calls to get the ++ * font for one single character. ++ * ++ * Xft and fontconfig are design failures. ++ */ ++ fcpattern = FcPatternDuplicate(font->pattern); ++ fccharset = FcCharSetCreate(); ++ ++ FcCharSetAddChar(fccharset, rune); ++ FcPatternAddCharSet(fcpattern, FC_CHARSET, ++ fccharset); ++ FcPatternAddBool(fcpattern, FC_SCALABLE, 1); ++ ++ FcConfigSubstitute(0, fcpattern, ++ FcMatchPattern); ++ FcDefaultSubstitute(fcpattern); ++ ++ fontpattern = FcFontSetMatch(0, fcsets, 1, ++ fcpattern, &fcres); ++ ++ /* Allocate memory for the new cache entry. */ ++ if (frclen >= frccap) { ++ frccap += 16; ++ frc = xrealloc(frc, frccap * sizeof(Fontcache)); ++ } ++ ++ frc[frclen].font = XftFontOpenPattern(xw.dpy, ++ fontpattern); ++ if (!frc[frclen].font) ++ die("XftFontOpenPattern failed seeking fallback font: %s\n", ++ strerror(errno)); ++ frc[frclen].flags = frcflags; ++ frc[frclen].unicodep = rune; ++ ++ glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); ++ ++ f = frclen; ++ frclen++; ++ ++ FcPatternDestroy(fcpattern); ++ FcCharSetDestroy(fccharset); ++ } ++ ++ specs[numspecs].font = frc[f].font; ++ specs[numspecs].glyph = glyphidx; ++ specs[numspecs].x = (short)xp; ++ specs[numspecs].y = (short)yp; ++ numspecs++; ++ } + } +- } + +- /* Nothing was found. Use fontconfig to find matching font. */ +- if (f >= frclen) { +- if (!font->set) +- font->set = FcFontSort(0, font->pattern, +- 1, 0, &fcres); +- fcsets[0] = font->set; ++ /* Cleanup and get ready for next segment. */ ++ hbcleanup(&shaped); ++ start = i; + +- /* +- * Nothing was found in the cache. Now use +- * some dozen of Fontconfig calls to get the +- * font for one single character. +- * +- * Xft and fontconfig are design failures. +- */ +- fcpattern = FcPatternDuplicate(font->pattern); +- fccharset = FcCharSetCreate(); +- +- FcCharSetAddChar(fccharset, rune); +- FcPatternAddCharSet(fcpattern, FC_CHARSET, +- fccharset); +- FcPatternAddBool(fcpattern, FC_SCALABLE, 1); +- +- FcConfigSubstitute(0, fcpattern, +- FcMatchPattern); +- FcDefaultSubstitute(fcpattern); +- +- fontpattern = FcFontSetMatch(0, fcsets, 1, +- fcpattern, &fcres); +- +- /* Allocate memory for the new cache entry. */ +- if (frclen >= frccap) { +- frccap += 16; +- frc = xrealloc(frc, frccap * sizeof(Fontcache)); ++ /* Determine font for glyph if different from previous glyph. */ ++ if (prevmode != mode) { ++ prevmode = mode; ++ xresetfontsettings(mode, &font, &frcflags); ++ yp = winy + font->ascent; + } +- +- frc[frclen].font = XftFontOpenPattern(xw.dpy, +- fontpattern); +- if (!frc[frclen].font) +- die("XftFontOpenPattern failed seeking fallback font: %s\n", +- strerror(errno)); +- frc[frclen].flags = frcflags; +- frc[frclen].unicodep = rune; +- +- glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); +- +- f = frclen; +- frclen++; +- +- FcPatternDestroy(fcpattern); +- FcCharSetDestroy(fccharset); + } +- +- specs[numspecs].font = frc[f].font; +- specs[numspecs].glyph = glyphidx; +- specs[numspecs].x = (short)xp; +- specs[numspecs].y = (short)yp; +- xp += runewidth; +- numspecs++; + } + + return numspecs; + } + + void +-xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) ++xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y, int charlen) + { +- int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); + int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, + width = charlen * win.cw; + Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; +@@ -1527,21 +1576,24 @@ void + xdrawglyph(Glyph g, int x, int y) + { + int numspecs; +- XftGlyphFontSpec spec; ++ XftGlyphFontSpec *specs = xw.specbuf; + +- numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); +- xdrawglyphfontspecs(&spec, g, numspecs, x, y); ++ numspecs = xmakeglyphfontspecs(specs, &g, 1, x, y); ++ xdrawglyphfontspecs(specs, g, numspecs, x, y, (g.mode & ATTR_WIDE) ? 2 : 1); + } + + void +-xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) ++xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og, Line line, int len) + { + Color drawcol; + + /* remove the old cursor */ + if (selected(ox, oy)) + og.mode ^= ATTR_REVERSE; +- xdrawglyph(og, ox, oy); ++ ++ /* Redraw the line where cursor was previously. ++ * It will restore the ligatures broken by the cursor. */ ++ xdrawline(line, 0, oy, len); + + if (IS_SET(MODE_HIDE)) + return; +@@ -1669,18 +1721,16 @@ xdrawline(Line line, int x1, int y1, int x2) + Glyph base, new; + XftGlyphFontSpec *specs = xw.specbuf; + +- numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1); + i = ox = 0; +- for (x = x1; x < x2 && i < numspecs; x++) { ++ for (x = x1; x < x2; x++) { + new = line[x]; + if (new.mode == ATTR_WDUMMY) + continue; + if (selected(x, y1)) + new.mode ^= ATTR_REVERSE; +- if (i > 0 && ATTRCMP(base, new)) { +- xdrawglyphfontspecs(specs, base, i, ox, y1); +- specs += i; +- numspecs -= i; ++ if ((i > 0) && ATTRCMP(base, new)) { ++ numspecs = xmakeglyphfontspecs(specs, &line[ox], x - ox, ox, y1); ++ xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x - ox); + i = 0; + } + if (i == 0) { +@@ -1689,8 +1739,10 @@ xdrawline(Line line, int x1, int y1, int x2) + } + i++; + } +- if (i > 0) +- xdrawglyphfontspecs(specs, base, i, ox, y1); ++ if (i > 0) { ++ numspecs = xmakeglyphfontspecs(specs, &line[ox], x2 - ox, ox, y1); ++ xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x2 - ox); ++ } + } + + void diff --git a/st.suckless.org/patches/ligatures/0.9.2/st-ligatures-alpha-scrollback-ringbuffer-20240427-0.9.2.diff b/st.suckless.org/patches/ligatures/0.9.2/st-ligatures-alpha-scrollback-ringbuffer-20240427-0.9.2.diff @@ -0,0 +1,635 @@ +diff --git a/Makefile b/Makefile +index 470ac86..38240da 100644 +--- a/Makefile ++++ b/Makefile +@@ -4,7 +4,7 @@ + + include config.mk + +-SRC = st.c x.c ++SRC = st.c x.c hb.c + OBJ = $(SRC:.c=.o) + + all: st +@@ -22,7 +22,8 @@ config.h: + $(CC) $(STCFLAGS) -c $< + + st.o: config.h st.h win.h +-x.o: arg.h config.h st.h win.h ++x.o: arg.h config.h st.h win.h hb.h ++hb.o: st.h + + $(OBJ): config.h config.mk + +diff --git a/config.mk b/config.mk +index 47c615e..d7439a3 100644 +--- a/config.mk ++++ b/config.mk +@@ -15,10 +15,12 @@ PKG_CONFIG = pkg-config + # includes and libs + INCS = -I$(X11INC) \ + `$(PKG_CONFIG) --cflags fontconfig` \ +- `$(PKG_CONFIG) --cflags freetype2` +-LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft -lXrender\ ++ `$(PKG_CONFIG) --cflags freetype2` \ ++ `$(PKG_CONFIG) --cflags harfbuzz` ++LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft -lXrender \ + `$(PKG_CONFIG) --libs fontconfig` \ +- `$(PKG_CONFIG) --libs freetype2` ++ `$(PKG_CONFIG) --libs freetype2` \ ++ `$(PKG_CONFIG) --libs harfbuzz` + + # flags + STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 +diff --git a/hb.c b/hb.c +new file mode 100644 +index 0000000..99412c8 +--- /dev/null ++++ b/hb.c +@@ -0,0 +1,125 @@ ++#include <stdlib.h> ++#include <stdio.h> ++#include <math.h> ++#include <X11/Xft/Xft.h> ++#include <X11/cursorfont.h> ++#include <hb.h> ++#include <hb-ft.h> ++ ++#include "st.h" ++#include "hb.h" ++ ++#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 } ++#define BUFFER_STEP 256 ++ ++hb_font_t *hbfindfont(XftFont *match); ++ ++typedef struct { ++ XftFont *match; ++ hb_font_t *font; ++} HbFontMatch; ++ ++typedef struct { ++ size_t capacity; ++ HbFontMatch *fonts; ++} HbFontCache; ++ ++static HbFontCache hbfontcache = { 0, NULL }; ++ ++typedef struct { ++ size_t capacity; ++ Rune *runes; ++} RuneBuffer; ++ ++static RuneBuffer hbrunebuffer = { 0, NULL }; ++ ++/* ++ * Poplulate the array with a list of font features, wrapped in FEATURE macro, ++ * e. g. ++ * FEATURE('c', 'a', 'l', 't'), FEATURE('d', 'l', 'i', 'g') ++ */ ++hb_feature_t features[] = { }; ++ ++void ++hbunloadfonts() ++{ ++ for (int i = 0; i < hbfontcache.capacity; i++) { ++ hb_font_destroy(hbfontcache.fonts[i].font); ++ XftUnlockFace(hbfontcache.fonts[i].match); ++ } ++ ++ if (hbfontcache.fonts != NULL) { ++ free(hbfontcache.fonts); ++ hbfontcache.fonts = NULL; ++ } ++ hbfontcache.capacity = 0; ++} ++ ++hb_font_t * ++hbfindfont(XftFont *match) ++{ ++ for (int i = 0; i < hbfontcache.capacity; i++) { ++ if (hbfontcache.fonts[i].match == match) ++ return hbfontcache.fonts[i].font; ++ } ++ ++ /* Font not found in cache, caching it now. */ ++ hbfontcache.fonts = realloc(hbfontcache.fonts, sizeof(HbFontMatch) * (hbfontcache.capacity + 1)); ++ FT_Face face = XftLockFace(match); ++ hb_font_t *font = hb_ft_font_create(face, NULL); ++ if (font == NULL) ++ die("Failed to load Harfbuzz font."); ++ ++ hbfontcache.fonts[hbfontcache.capacity].match = match; ++ hbfontcache.fonts[hbfontcache.capacity].font = font; ++ hbfontcache.capacity += 1; ++ ++ return font; ++} ++ ++void hbtransform(HbTransformData *data, XftFont *xfont, const Glyph *glyphs, int start, int length) { ++ ushort mode = USHRT_MAX; ++ unsigned int glyph_count; ++ int rune_idx, glyph_idx, end = start + length; ++ ++ hb_font_t *font = hbfindfont(xfont); ++ if (font == NULL) ++ return; ++ ++ hb_buffer_t *buffer = hb_buffer_create(); ++ hb_buffer_set_direction(buffer, HB_DIRECTION_LTR); ++ hb_buffer_set_cluster_level(buffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS); ++ ++ /* Resize the buffer if required length is larger. */ ++ if (hbrunebuffer.capacity < length) { ++ hbrunebuffer.capacity = (length / BUFFER_STEP + 1) * BUFFER_STEP; ++ hbrunebuffer.runes = realloc(hbrunebuffer.runes, hbrunebuffer.capacity * sizeof(Rune)); ++ } ++ ++ /* Fill buffer with codepoints. */ ++ for (rune_idx = 0, glyph_idx = start; glyph_idx < end; glyph_idx++, rune_idx++) { ++ hbrunebuffer.runes[rune_idx] = glyphs[glyph_idx].u; ++ mode = glyphs[glyph_idx].mode; ++ if (mode & ATTR_WDUMMY) ++ hbrunebuffer.runes[rune_idx] = 0x0020; ++ } ++ hb_buffer_add_codepoints(buffer, hbrunebuffer.runes, length, 0, length); ++ ++ /* Shape the segment. */ ++ hb_shape(font, buffer, features, sizeof(features)/sizeof(hb_feature_t)); ++ ++ /* Get new glyph info. */ ++ hb_glyph_info_t *info = hb_buffer_get_glyph_infos(buffer, &glyph_count); ++ hb_glyph_position_t *pos = hb_buffer_get_glyph_positions(buffer, &glyph_count); ++ ++ /* Fill the output. */ ++ data->buffer = buffer; ++ data->glyphs = info; ++ data->positions = pos; ++ data->count = glyph_count; ++} ++ ++void hbcleanup(HbTransformData *data) { ++ hb_buffer_destroy(data->buffer); ++ memset(data, 0, sizeof(HbTransformData)); ++} +diff --git a/hb.h b/hb.h +new file mode 100644 +index 0000000..3b0ef44 +--- /dev/null ++++ b/hb.h +@@ -0,0 +1,14 @@ ++#include <X11/Xft/Xft.h> ++#include <hb.h> ++#include <hb-ft.h> ++ ++typedef struct { ++ hb_buffer_t *buffer; ++ hb_glyph_info_t *glyphs; ++ hb_glyph_position_t *positions; ++ unsigned int count; ++} HbTransformData; ++ ++void hbunloadfonts(); ++void hbtransform(HbTransformData *, XftFont *, const Glyph *, int, int); ++void hbcleanup(HbTransformData *); +diff --git a/st.c b/st.c +index c44797b..18aa1bf 100644 +--- a/st.c ++++ b/st.c +@@ -2759,7 +2759,9 @@ draw(void) + drawregion(0, 0, term.col, term.row); + if (TSCREEN.off == 0) + xdrawcursor(cx, term.c.y, TLINE(term.c.y)[cx], +- term.ocx, term.ocy, TLINE(term.ocy)[term.ocx]); ++ term.ocx, term.ocy, TLINE(term.ocy)[term.ocx], ++ TLINE(term.ocy), term.col); ++ + term.ocx = cx; + term.ocy = term.c.y; + xfinishdraw(); +diff --git a/st.h b/st.h +index 073851a..d0b071d 100644 +--- a/st.h ++++ b/st.h +@@ -11,7 +11,8 @@ + #define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d)) + #define DEFAULT(a, b) (a) = (a) ? (a) : (b) + #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) +-#define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \ ++#define ATTRCMP(a, b) (((a).mode & (~ATTR_WRAP)) != ((b).mode & (~ATTR_WRAP)) || \ ++ (a).fg != (b).fg || \ + (a).bg != (b).bg) + #define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \ + (t1.tv_nsec-t2.tv_nsec)/1E6) +diff --git a/win.h b/win.h +index 6de960d..94679e4 100644 +--- a/win.h ++++ b/win.h +@@ -25,7 +25,7 @@ enum win_mode { + + void xbell(void); + void xclipcopy(void); +-void xdrawcursor(int, int, Glyph, int, int, Glyph); ++void xdrawcursor(int, int, Glyph, int, int, Glyph, Line, int); + void xdrawline(Line, int, int, int); + void xfinishdraw(void); + void xloadcols(void); +diff --git a/x.c b/x.c +index b81f5be..c1611bb 100644 +--- a/x.c ++++ b/x.c +@@ -19,6 +19,7 @@ char *argv0; + #include "arg.h" + #include "st.h" + #include "win.h" ++#include "hb.h" + + /* types used in config.h */ + typedef struct { +@@ -144,8 +145,9 @@ typedef struct { + } DC; + + static inline ushort sixd_to_16bit(int); ++static void xresetfontsettings(ushort mode, Font **font, int *frcflags); + static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); +-static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); ++static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int, int); + static void xdrawglyph(Glyph, int, int); + static void xclear(int, int, int, int); + static int xgeommasktogravity(int); +@@ -761,7 +763,7 @@ xresize(int col, int row) + xclear(0, 0, win.w, win.h); + + /* resize to new width */ +- xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec)); ++ xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec) * 4); + } + + ushort +@@ -1073,6 +1075,9 @@ xunloadfont(Font *f) + void + xunloadfonts(void) + { ++ /* Clear Harfbuzz font cache. */ ++ hbunloadfonts(); ++ + /* Free the loaded fonts in the font cache. */ + while (frclen > 0) + XftFontClose(xw.dpy, frc[--frclen].font); +@@ -1204,7 +1209,7 @@ xinit(int cols, int rows) + XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); + + /* font spec buffer */ +- xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec)); ++ xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec) * 4); + + /* Xft rendering context */ + xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); +@@ -1258,6 +1263,22 @@ xinit(int cols, int rows) + xsel.xtarget = XA_STRING; + } + ++void ++xresetfontsettings(ushort mode, Font **font, int *frcflags) ++{ ++ *font = &dc.font; ++ if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { ++ *font = &dc.ibfont; ++ *frcflags = FRC_ITALICBOLD; ++ } else if (mode & ATTR_ITALIC) { ++ *font = &dc.ifont; ++ *frcflags = FRC_ITALIC; ++ } else if (mode & ATTR_BOLD) { ++ *font = &dc.bfont; ++ *frcflags = FRC_BOLD; ++ } ++} ++ + int + xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) + { +@@ -1272,128 +1293,156 @@ 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, f, length = 0, start = 0, numspecs = 0; ++ float cluster_xp = xp, cluster_yp = yp; ++ HbTransformData shaped = { 0 }; ++ ++ /* Initial values. */ ++ mode = prevmode = glyphs[0].mode & ~ATTR_WRAP; ++ xresetfontsettings(mode, &font, &frcflags); + + for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { +- /* Fetch rune and mode for current glyph. */ +- rune = glyphs[i].u; +- mode = glyphs[i].mode; ++ mode = glyphs[i].mode & ~ATTR_WRAP; + + /* Skip dummy wide-character spacing. */ +- if (mode == ATTR_WDUMMY) ++ if (mode & ATTR_WDUMMY && i < (len - 1)) + continue; + +- /* Determine font for glyph if different from previous glyph. */ +- if (prevmode != mode) { +- prevmode = mode; +- font = &dc.font; +- frcflags = FRC_NORMAL; +- runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f); +- if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { +- font = &dc.ibfont; +- frcflags = FRC_ITALICBOLD; +- } else if (mode & ATTR_ITALIC) { +- font = &dc.ifont; +- frcflags = FRC_ITALIC; +- } else if (mode & ATTR_BOLD) { +- font = &dc.bfont; +- frcflags = FRC_BOLD; ++ if ( ++ prevmode != mode ++ || ATTRCMP(glyphs[start], glyphs[i]) ++ || selected(x + i, y) != selected(x + start, y) ++ || i == (len - 1) ++ ) { ++ /* Handle 1-character wide segments and end of line */ ++ length = i - start; ++ if (i == start) { ++ length = 1; ++ } else if (i == (len - 1)) { ++ length = (i - start + 1); + } +- yp = winy + font->ascent; +- } + +- /* 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; +- numspecs++; +- continue; +- } +- +- /* Fallback on font cache, search the font cache for match. */ +- for (f = 0; f < frclen; f++) { +- glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); +- /* Everything correct. */ +- if (glyphidx && frc[f].flags == frcflags) +- break; +- /* We got a default font for a not found glyph. */ +- if (!glyphidx && frc[f].flags == frcflags +- && frc[f].unicodep == rune) { +- break; ++ /* Shape the segment. */ ++ hbtransform(&shaped, font->match, glyphs, start, length); ++ runewidth = win.cw * ((glyphs[start].mode & ATTR_WIDE) ? 2.0f : 1.0f); ++ cluster_xp = xp; cluster_yp = yp; ++ for (int code_idx = 0; code_idx < shaped.count; code_idx++) { ++ int idx = shaped.glyphs[code_idx].cluster; ++ ++ if (glyphs[start + idx].mode & ATTR_WDUMMY) ++ continue; ++ ++ /* Advance the drawing cursor if we've moved to a new cluster */ ++ if (code_idx > 0 && idx != shaped.glyphs[code_idx - 1].cluster) { ++ xp += runewidth; ++ cluster_xp = xp; ++ cluster_yp = yp; ++ runewidth = win.cw * ((glyphs[start + idx].mode & ATTR_WIDE) ? 2.0f : 1.0f); ++ } ++ ++ if (shaped.glyphs[code_idx].codepoint != 0) { ++ /* If symbol is found, put it into the specs. */ ++ specs[numspecs].font = font->match; ++ specs[numspecs].glyph = shaped.glyphs[code_idx].codepoint; ++ specs[numspecs].x = cluster_xp + (short)(shaped.positions[code_idx].x_offset / 64.); ++ specs[numspecs].y = cluster_yp - (short)(shaped.positions[code_idx].y_offset / 64.); ++ cluster_xp += shaped.positions[code_idx].x_advance / 64.; ++ cluster_yp += shaped.positions[code_idx].y_advance / 64.; ++ numspecs++; ++ } else { ++ /* If it's not found, try to fetch it through the font cache. */ ++ rune = glyphs[start + idx].u; ++ for (f = 0; f < frclen; f++) { ++ glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); ++ /* Everything correct. */ ++ if (glyphidx && frc[f].flags == frcflags) ++ break; ++ /* We got a default font for a not found glyph. */ ++ if (!glyphidx && frc[f].flags == frcflags ++ && frc[f].unicodep == rune) { ++ break; ++ } ++ } ++ ++ /* Nothing was found. Use fontconfig to find matching font. */ ++ if (f >= frclen) { ++ if (!font->set) ++ font->set = FcFontSort(0, font->pattern, ++ 1, 0, &fcres); ++ fcsets[0] = font->set; ++ ++ /* ++ * Nothing was found in the cache. Now use ++ * some dozen of Fontconfig calls to get the ++ * font for one single character. ++ * ++ * Xft and fontconfig are design failures. ++ */ ++ fcpattern = FcPatternDuplicate(font->pattern); ++ fccharset = FcCharSetCreate(); ++ ++ FcCharSetAddChar(fccharset, rune); ++ FcPatternAddCharSet(fcpattern, FC_CHARSET, ++ fccharset); ++ FcPatternAddBool(fcpattern, FC_SCALABLE, 1); ++ ++ FcConfigSubstitute(0, fcpattern, ++ FcMatchPattern); ++ FcDefaultSubstitute(fcpattern); ++ ++ fontpattern = FcFontSetMatch(0, fcsets, 1, ++ fcpattern, &fcres); ++ ++ /* Allocate memory for the new cache entry. */ ++ if (frclen >= frccap) { ++ frccap += 16; ++ frc = xrealloc(frc, frccap * sizeof(Fontcache)); ++ } ++ ++ frc[frclen].font = XftFontOpenPattern(xw.dpy, ++ fontpattern); ++ if (!frc[frclen].font) ++ die("XftFontOpenPattern failed seeking fallback font: %s\n", ++ strerror(errno)); ++ frc[frclen].flags = frcflags; ++ frc[frclen].unicodep = rune; ++ ++ glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); ++ ++ f = frclen; ++ frclen++; ++ ++ FcPatternDestroy(fcpattern); ++ FcCharSetDestroy(fccharset); ++ } ++ ++ specs[numspecs].font = frc[f].font; ++ specs[numspecs].glyph = glyphidx; ++ specs[numspecs].x = (short)xp; ++ specs[numspecs].y = (short)yp; ++ numspecs++; ++ } + } +- } + +- /* Nothing was found. Use fontconfig to find matching font. */ +- if (f >= frclen) { +- if (!font->set) +- font->set = FcFontSort(0, font->pattern, +- 1, 0, &fcres); +- fcsets[0] = font->set; ++ /* Cleanup and get ready for next segment. */ ++ hbcleanup(&shaped); ++ start = i; + +- /* +- * Nothing was found in the cache. Now use +- * some dozen of Fontconfig calls to get the +- * font for one single character. +- * +- * Xft and fontconfig are design failures. +- */ +- fcpattern = FcPatternDuplicate(font->pattern); +- fccharset = FcCharSetCreate(); +- +- FcCharSetAddChar(fccharset, rune); +- FcPatternAddCharSet(fcpattern, FC_CHARSET, +- fccharset); +- FcPatternAddBool(fcpattern, FC_SCALABLE, 1); +- +- FcConfigSubstitute(0, fcpattern, +- FcMatchPattern); +- FcDefaultSubstitute(fcpattern); +- +- fontpattern = FcFontSetMatch(0, fcsets, 1, +- fcpattern, &fcres); +- +- /* Allocate memory for the new cache entry. */ +- if (frclen >= frccap) { +- frccap += 16; +- frc = xrealloc(frc, frccap * sizeof(Fontcache)); ++ /* Determine font for glyph if different from previous glyph. */ ++ if (prevmode != mode) { ++ prevmode = mode; ++ xresetfontsettings(mode, &font, &frcflags); ++ yp = winy + font->ascent; + } +- +- frc[frclen].font = XftFontOpenPattern(xw.dpy, +- fontpattern); +- if (!frc[frclen].font) +- die("XftFontOpenPattern failed seeking fallback font: %s\n", +- strerror(errno)); +- frc[frclen].flags = frcflags; +- frc[frclen].unicodep = rune; +- +- glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); +- +- f = frclen; +- frclen++; +- +- FcPatternDestroy(fcpattern); +- FcCharSetDestroy(fccharset); + } +- +- specs[numspecs].font = frc[f].font; +- specs[numspecs].glyph = glyphidx; +- specs[numspecs].x = (short)xp; +- specs[numspecs].y = (short)yp; +- xp += runewidth; +- numspecs++; + } + + return numspecs; + } + + void +-xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) ++xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y, int charlen) + { +- int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); + int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, + width = charlen * win.cw; + Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; +@@ -1529,21 +1578,24 @@ void + xdrawglyph(Glyph g, int x, int y) + { + int numspecs; +- XftGlyphFontSpec spec; ++ XftGlyphFontSpec *specs = xw.specbuf; + +- numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); +- xdrawglyphfontspecs(&spec, g, numspecs, x, y); ++ numspecs = xmakeglyphfontspecs(specs, &g, 1, x, y); ++ xdrawglyphfontspecs(specs, g, numspecs, x, y, (g.mode & ATTR_WIDE) ? 2 : 1); + } + + void +-xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) ++xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og, Line line, int len) + { + Color drawcol; + + /* remove the old cursor */ + if (selected(ox, oy)) + og.mode ^= ATTR_REVERSE; +- xdrawglyph(og, ox, oy); ++ ++ /* Redraw the line where cursor was previously. ++ * It will restore the ligatures broken by the cursor. */ ++ xdrawline(line, 0, oy, len); + + if (IS_SET(MODE_HIDE)) + return; +@@ -1671,18 +1723,16 @@ xdrawline(Line line, int x1, int y1, int x2) + Glyph base, new; + XftGlyphFontSpec *specs = xw.specbuf; + +- numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1); + i = ox = 0; +- for (x = x1; x < x2 && i < numspecs; x++) { ++ for (x = x1; x < x2; x++) { + new = line[x]; + if (new.mode == ATTR_WDUMMY) + continue; + if (selected(x, y1)) + new.mode ^= ATTR_REVERSE; +- if (i > 0 && ATTRCMP(base, new)) { +- xdrawglyphfontspecs(specs, base, i, ox, y1); +- specs += i; +- numspecs -= i; ++ if ((i > 0) && ATTRCMP(base, new)) { ++ numspecs = xmakeglyphfontspecs(specs, &line[ox], x - ox, ox, y1); ++ xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x - ox); + i = 0; + } + if (i == 0) { +@@ -1691,8 +1741,10 @@ xdrawline(Line line, int x1, int y1, int x2) + } + i++; + } +- if (i > 0) +- xdrawglyphfontspecs(specs, base, i, ox, y1); ++ if (i > 0) { ++ numspecs = xmakeglyphfontspecs(specs, &line[ox], x2 - ox, ox, y1); ++ xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x2 - ox); ++ } + } + + void diff --git a/st.suckless.org/patches/ligatures/0.9.2/st-ligatures-boxdraw-20240427-0.9.2.diff b/st.suckless.org/patches/ligatures/0.9.2/st-ligatures-boxdraw-20240427-0.9.2.diff @@ -0,0 +1,647 @@ +diff --git a/Makefile b/Makefile +index 6dfa212..adfa07a 100644 +--- a/Makefile ++++ b/Makefile +@@ -4,7 +4,7 @@ + + include config.mk + +-SRC = st.c x.c boxdraw.c ++SRC = st.c x.c boxdraw.c hb.c + OBJ = $(SRC:.c=.o) + + all: st +@@ -22,8 +22,9 @@ config.h: + $(CC) $(STCFLAGS) -c $< + + st.o: config.h st.h win.h +-x.o: arg.h config.h st.h win.h ++x.o: arg.h config.h st.h win.h hb.h + boxdraw.o: config.h st.h boxdraw_data.h ++hb.o: st.h + + $(OBJ): config.h config.mk + +diff --git a/config.mk b/config.mk +index 1e306f8..3e13e53 100644 +--- a/config.mk ++++ b/config.mk +@@ -15,10 +15,12 @@ PKG_CONFIG = pkg-config + # includes and libs + INCS = -I$(X11INC) \ + `$(PKG_CONFIG) --cflags fontconfig` \ +- `$(PKG_CONFIG) --cflags freetype2` ++ `$(PKG_CONFIG) --cflags freetype2` \ ++ `$(PKG_CONFIG) --cflags harfbuzz` + LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft \ + `$(PKG_CONFIG) --libs fontconfig` \ +- `$(PKG_CONFIG) --libs freetype2` ++ `$(PKG_CONFIG) --libs freetype2` \ ++ `$(PKG_CONFIG) --libs harfbuzz` + + # flags + STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 +diff --git a/hb.c b/hb.c +new file mode 100644 +index 0000000..99412c8 +--- /dev/null ++++ b/hb.c +@@ -0,0 +1,125 @@ ++#include <stdlib.h> ++#include <stdio.h> ++#include <math.h> ++#include <X11/Xft/Xft.h> ++#include <X11/cursorfont.h> ++#include <hb.h> ++#include <hb-ft.h> ++ ++#include "st.h" ++#include "hb.h" ++ ++#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 } ++#define BUFFER_STEP 256 ++ ++hb_font_t *hbfindfont(XftFont *match); ++ ++typedef struct { ++ XftFont *match; ++ hb_font_t *font; ++} HbFontMatch; ++ ++typedef struct { ++ size_t capacity; ++ HbFontMatch *fonts; ++} HbFontCache; ++ ++static HbFontCache hbfontcache = { 0, NULL }; ++ ++typedef struct { ++ size_t capacity; ++ Rune *runes; ++} RuneBuffer; ++ ++static RuneBuffer hbrunebuffer = { 0, NULL }; ++ ++/* ++ * Poplulate the array with a list of font features, wrapped in FEATURE macro, ++ * e. g. ++ * FEATURE('c', 'a', 'l', 't'), FEATURE('d', 'l', 'i', 'g') ++ */ ++hb_feature_t features[] = { }; ++ ++void ++hbunloadfonts() ++{ ++ for (int i = 0; i < hbfontcache.capacity; i++) { ++ hb_font_destroy(hbfontcache.fonts[i].font); ++ XftUnlockFace(hbfontcache.fonts[i].match); ++ } ++ ++ if (hbfontcache.fonts != NULL) { ++ free(hbfontcache.fonts); ++ hbfontcache.fonts = NULL; ++ } ++ hbfontcache.capacity = 0; ++} ++ ++hb_font_t * ++hbfindfont(XftFont *match) ++{ ++ for (int i = 0; i < hbfontcache.capacity; i++) { ++ if (hbfontcache.fonts[i].match == match) ++ return hbfontcache.fonts[i].font; ++ } ++ ++ /* Font not found in cache, caching it now. */ ++ hbfontcache.fonts = realloc(hbfontcache.fonts, sizeof(HbFontMatch) * (hbfontcache.capacity + 1)); ++ FT_Face face = XftLockFace(match); ++ hb_font_t *font = hb_ft_font_create(face, NULL); ++ if (font == NULL) ++ die("Failed to load Harfbuzz font."); ++ ++ hbfontcache.fonts[hbfontcache.capacity].match = match; ++ hbfontcache.fonts[hbfontcache.capacity].font = font; ++ hbfontcache.capacity += 1; ++ ++ return font; ++} ++ ++void hbtransform(HbTransformData *data, XftFont *xfont, const Glyph *glyphs, int start, int length) { ++ ushort mode = USHRT_MAX; ++ unsigned int glyph_count; ++ int rune_idx, glyph_idx, end = start + length; ++ ++ hb_font_t *font = hbfindfont(xfont); ++ if (font == NULL) ++ return; ++ ++ hb_buffer_t *buffer = hb_buffer_create(); ++ hb_buffer_set_direction(buffer, HB_DIRECTION_LTR); ++ hb_buffer_set_cluster_level(buffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS); ++ ++ /* Resize the buffer if required length is larger. */ ++ if (hbrunebuffer.capacity < length) { ++ hbrunebuffer.capacity = (length / BUFFER_STEP + 1) * BUFFER_STEP; ++ hbrunebuffer.runes = realloc(hbrunebuffer.runes, hbrunebuffer.capacity * sizeof(Rune)); ++ } ++ ++ /* Fill buffer with codepoints. */ ++ for (rune_idx = 0, glyph_idx = start; glyph_idx < end; glyph_idx++, rune_idx++) { ++ hbrunebuffer.runes[rune_idx] = glyphs[glyph_idx].u; ++ mode = glyphs[glyph_idx].mode; ++ if (mode & ATTR_WDUMMY) ++ hbrunebuffer.runes[rune_idx] = 0x0020; ++ } ++ hb_buffer_add_codepoints(buffer, hbrunebuffer.runes, length, 0, length); ++ ++ /* Shape the segment. */ ++ hb_shape(font, buffer, features, sizeof(features)/sizeof(hb_feature_t)); ++ ++ /* Get new glyph info. */ ++ hb_glyph_info_t *info = hb_buffer_get_glyph_infos(buffer, &glyph_count); ++ hb_glyph_position_t *pos = hb_buffer_get_glyph_positions(buffer, &glyph_count); ++ ++ /* Fill the output. */ ++ data->buffer = buffer; ++ data->glyphs = info; ++ data->positions = pos; ++ data->count = glyph_count; ++} ++ ++void hbcleanup(HbTransformData *data) { ++ hb_buffer_destroy(data->buffer); ++ memset(data, 0, sizeof(HbTransformData)); ++} +diff --git a/hb.h b/hb.h +new file mode 100644 +index 0000000..3b0ef44 +--- /dev/null ++++ b/hb.h +@@ -0,0 +1,14 @@ ++#include <X11/Xft/Xft.h> ++#include <hb.h> ++#include <hb-ft.h> ++ ++typedef struct { ++ hb_buffer_t *buffer; ++ hb_glyph_info_t *glyphs; ++ hb_glyph_position_t *positions; ++ unsigned int count; ++} HbTransformData; ++ ++void hbunloadfonts(); ++void hbtransform(HbTransformData *, XftFont *, const Glyph *, int, int); ++void hbcleanup(HbTransformData *); +diff --git a/st.c b/st.c +index 41d5ace..1c2edd6 100644 +--- a/st.c ++++ b/st.c +@@ -2643,7 +2643,8 @@ draw(void) + + drawregion(0, 0, term.col, term.row); + xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], +- term.ocx, term.ocy, term.line[term.ocy][term.ocx]); ++ term.ocx, term.ocy, term.line[term.ocy][term.ocx], ++ term.line[term.ocy], term.col); + term.ocx = cx; + term.ocy = term.c.y; + xfinishdraw(); +diff --git a/st.h b/st.h +index 808f5f7..ae41368 100644 +--- a/st.h ++++ b/st.h +@@ -11,7 +11,8 @@ + #define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d)) + #define DEFAULT(a, b) (a) = (a) ? (a) : (b) + #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) +-#define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \ ++#define ATTRCMP(a, b) (((a).mode & (~ATTR_WRAP)) != ((b).mode & (~ATTR_WRAP)) || \ ++ (a).fg != (b).fg || \ + (a).bg != (b).bg) + #define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \ + (t1.tv_nsec-t2.tv_nsec)/1E6) +diff --git a/win.h b/win.h +index 6de960d..94679e4 100644 +--- a/win.h ++++ b/win.h +@@ -25,7 +25,7 @@ enum win_mode { + + void xbell(void); + void xclipcopy(void); +-void xdrawcursor(int, int, Glyph, int, int, Glyph); ++void xdrawcursor(int, int, Glyph, int, int, Glyph, Line, int); + void xdrawline(Line, int, int, int); + void xfinishdraw(void); + void xloadcols(void); +diff --git a/x.c b/x.c +index bf6bbf9..96b117f 100644 +--- a/x.c ++++ b/x.c +@@ -19,6 +19,7 @@ char *argv0; + #include "arg.h" + #include "st.h" + #include "win.h" ++#include "hb.h" + + /* types used in config.h */ + typedef struct { +@@ -141,8 +142,9 @@ typedef struct { + } DC; + + static inline ushort sixd_to_16bit(int); ++static void xresetfontsettings(ushort mode, Font **font, int *frcflags); + static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); +-static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); ++static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int, int); + static void xdrawglyph(Glyph, int, int); + static void xclear(int, int, int, int); + static int xgeommasktogravity(int); +@@ -757,7 +759,7 @@ xresize(int col, int row) + xclear(0, 0, win.w, win.h); + + /* resize to new width */ +- xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec)); ++ xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec) * 4); + } + + ushort +@@ -1062,6 +1064,9 @@ xunloadfont(Font *f) + void + xunloadfonts(void) + { ++ /* Clear Harfbuzz font cache. */ ++ hbunloadfonts(); ++ + /* Free the loaded fonts in the font cache. */ + while (frclen > 0) + XftFontClose(xw.dpy, frc[--frclen].font); +@@ -1185,7 +1190,7 @@ xinit(int cols, int rows) + XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); + + /* font spec buffer */ +- xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec)); ++ xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec) * 4); + + /* Xft rendering context */ + xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); +@@ -1241,6 +1246,22 @@ xinit(int cols, int rows) + boxdraw_xinit(xw.dpy, xw.cmap, xw.draw, xw.vis); + } + ++void ++xresetfontsettings(ushort mode, Font **font, int *frcflags) ++{ ++ *font = &dc.font; ++ if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { ++ *font = &dc.ibfont; ++ *frcflags = FRC_ITALICBOLD; ++ } else if (mode & ATTR_ITALIC) { ++ *font = &dc.ifont; ++ *frcflags = FRC_ITALIC; ++ } else if (mode & ATTR_BOLD) { ++ *font = &dc.bfont; ++ *frcflags = FRC_BOLD; ++ } ++} ++ + int + xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) + { +@@ -1255,133 +1276,164 @@ 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, f, length = 0, start = 0, numspecs = 0; ++ float cluster_xp = xp, cluster_yp = yp; ++ HbTransformData shaped = { 0 }; ++ ++ /* Initial values. */ ++ mode = prevmode = glyphs[0].mode & ~ATTR_WRAP; ++ xresetfontsettings(mode, &font, &frcflags); + + for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { +- /* Fetch rune and mode for current glyph. */ +- rune = glyphs[i].u; +- mode = glyphs[i].mode; ++ mode = glyphs[i].mode & ~ATTR_WRAP; + + /* Skip dummy wide-character spacing. */ +- if (mode == ATTR_WDUMMY) ++ if (mode & ATTR_WDUMMY && i < (len - 1)) + continue; + +- /* Determine font for glyph if different from previous glyph. */ +- if (prevmode != mode) { +- prevmode = mode; +- font = &dc.font; +- frcflags = FRC_NORMAL; +- runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f); +- if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { +- font = &dc.ibfont; +- frcflags = FRC_ITALICBOLD; +- } else if (mode & ATTR_ITALIC) { +- font = &dc.ifont; +- frcflags = FRC_ITALIC; +- } else if (mode & ATTR_BOLD) { +- font = &dc.bfont; +- frcflags = FRC_BOLD; ++ if ( ++ prevmode != mode ++ || ATTRCMP(glyphs[start], glyphs[i]) ++ || selected(x + i, y) != selected(x + start, y) ++ || i == (len - 1) ++ ) { ++ /* Handle 1-character wide segments and end of line */ ++ length = i - start; ++ if (i == start) { ++ length = 1; ++ } else if (i == (len - 1)) { ++ length = (i - start + 1); + } +- yp = winy + font->ascent; +- } + +- if (mode & ATTR_BOXDRAW) { +- /* minor shoehorning: boxdraw uses only this ushort */ +- glyphidx = boxdrawindex(&glyphs[i]); +- } else { +- /* 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; +- numspecs++; +- continue; +- } +- +- /* Fallback on font cache, search the font cache for match. */ +- for (f = 0; f < frclen; f++) { +- glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); +- /* Everything correct. */ +- if (glyphidx && frc[f].flags == frcflags) +- break; +- /* We got a default font for a not found glyph. */ +- if (!glyphidx && frc[f].flags == frcflags +- && frc[f].unicodep == rune) { +- break; ++ /* Shape the segment. */ ++ hbtransform(&shaped, font->match, glyphs, start, length); ++ runewidth = win.cw * ((glyphs[start].mode & ATTR_WIDE) ? 2.0f : 1.0f); ++ cluster_xp = xp; cluster_yp = yp; ++ for (int code_idx = 0; code_idx < shaped.count; code_idx++) { ++ int idx = shaped.glyphs[code_idx].cluster; ++ ++ if (glyphs[start + idx].mode & ATTR_WDUMMY) ++ continue; ++ ++ /* Advance the drawing cursor if we've moved to a new cluster */ ++ if (code_idx > 0 && idx != shaped.glyphs[code_idx - 1].cluster) { ++ xp += runewidth; ++ cluster_xp = xp; ++ cluster_yp = yp; ++ runewidth = win.cw * ((glyphs[start + idx].mode & ATTR_WIDE) ? 2.0f : 1.0f); ++ } ++ ++ if (glyphs[start + idx].mode & ATTR_BOXDRAW) { ++ /* minor shoehorning: boxdraw uses only this ushort */ ++ specs[numspecs].font = font->match; ++ specs[numspecs].glyph = boxdrawindex(&glyphs[start + idx]); ++ specs[numspecs].x = xp; ++ specs[numspecs].y = yp; ++ numspecs++; ++ } else if (shaped.glyphs[code_idx].codepoint != 0) { ++ /* If symbol is found, put it into the specs. */ ++ specs[numspecs].font = font->match; ++ specs[numspecs].glyph = shaped.glyphs[code_idx].codepoint; ++ specs[numspecs].x = cluster_xp + (short)(shaped.positions[code_idx].x_offset / 64.); ++ specs[numspecs].y = cluster_yp - (short)(shaped.positions[code_idx].y_offset / 64.); ++ cluster_xp += shaped.positions[code_idx].x_advance / 64.; ++ cluster_yp += shaped.positions[code_idx].y_advance / 64.; ++ numspecs++; ++ } else { ++ /* If it's not found, try to fetch it through the font cache. */ ++ rune = glyphs[start + idx].u; ++ for (f = 0; f < frclen; f++) { ++ glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); ++ /* Everything correct. */ ++ if (glyphidx && frc[f].flags == frcflags) ++ break; ++ /* We got a default font for a not found glyph. */ ++ if (!glyphidx && frc[f].flags == frcflags ++ && frc[f].unicodep == rune) { ++ break; ++ } ++ } ++ ++ /* Nothing was found. Use fontconfig to find matching font. */ ++ if (f >= frclen) { ++ if (!font->set) ++ font->set = FcFontSort(0, font->pattern, ++ 1, 0, &fcres); ++ fcsets[0] = font->set; ++ ++ /* ++ * Nothing was found in the cache. Now use ++ * some dozen of Fontconfig calls to get the ++ * font for one single character. ++ * ++ * Xft and fontconfig are design failures. ++ */ ++ fcpattern = FcPatternDuplicate(font->pattern); ++ fccharset = FcCharSetCreate(); ++ ++ FcCharSetAddChar(fccharset, rune); ++ FcPatternAddCharSet(fcpattern, FC_CHARSET, ++ fccharset); ++ FcPatternAddBool(fcpattern, FC_SCALABLE, 1); ++ ++ FcConfigSubstitute(0, fcpattern, ++ FcMatchPattern); ++ FcDefaultSubstitute(fcpattern); ++ ++ fontpattern = FcFontSetMatch(0, fcsets, 1, ++ fcpattern, &fcres); ++ ++ /* Allocate memory for the new cache entry. */ ++ if (frclen >= frccap) { ++ frccap += 16; ++ frc = xrealloc(frc, frccap * sizeof(Fontcache)); ++ } ++ ++ frc[frclen].font = XftFontOpenPattern(xw.dpy, ++ fontpattern); ++ if (!frc[frclen].font) ++ die("XftFontOpenPattern failed seeking fallback font: %s\n", ++ strerror(errno)); ++ frc[frclen].flags = frcflags; ++ frc[frclen].unicodep = rune; ++ ++ glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); ++ ++ f = frclen; ++ frclen++; ++ ++ FcPatternDestroy(fcpattern); ++ FcCharSetDestroy(fccharset); ++ } ++ ++ specs[numspecs].font = frc[f].font; ++ specs[numspecs].glyph = glyphidx; ++ specs[numspecs].x = (short)xp; ++ specs[numspecs].y = (short)yp; ++ numspecs++; ++ } + } +- } + +- /* Nothing was found. Use fontconfig to find matching font. */ +- if (f >= frclen) { +- if (!font->set) +- font->set = FcFontSort(0, font->pattern, +- 1, 0, &fcres); +- fcsets[0] = font->set; ++ /* Cleanup and get ready for next segment. */ ++ hbcleanup(&shaped); ++ start = i; + +- /* +- * Nothing was found in the cache. Now use +- * some dozen of Fontconfig calls to get the +- * font for one single character. +- * +- * Xft and fontconfig are design failures. +- */ +- fcpattern = FcPatternDuplicate(font->pattern); +- fccharset = FcCharSetCreate(); +- +- FcCharSetAddChar(fccharset, rune); +- FcPatternAddCharSet(fcpattern, FC_CHARSET, +- fccharset); +- FcPatternAddBool(fcpattern, FC_SCALABLE, 1); +- +- FcConfigSubstitute(0, fcpattern, +- FcMatchPattern); +- FcDefaultSubstitute(fcpattern); +- +- fontpattern = FcFontSetMatch(0, fcsets, 1, +- fcpattern, &fcres); +- +- /* Allocate memory for the new cache entry. */ +- if (frclen >= frccap) { +- frccap += 16; +- frc = xrealloc(frc, frccap * sizeof(Fontcache)); ++ /* Determine font for glyph if different from previous glyph. */ ++ if (prevmode != mode) { ++ prevmode = mode; ++ xresetfontsettings(mode, &font, &frcflags); ++ yp = winy + font->ascent; + } +- +- frc[frclen].font = XftFontOpenPattern(xw.dpy, +- fontpattern); +- if (!frc[frclen].font) +- die("XftFontOpenPattern failed seeking fallback font: %s\n", +- strerror(errno)); +- frc[frclen].flags = frcflags; +- frc[frclen].unicodep = rune; +- +- glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); +- +- f = frclen; +- frclen++; +- +- FcPatternDestroy(fcpattern); +- FcCharSetDestroy(fccharset); + } +- +- specs[numspecs].font = frc[f].font; +- specs[numspecs].glyph = glyphidx; +- specs[numspecs].x = (short)xp; +- specs[numspecs].y = (short)yp; +- xp += runewidth; +- numspecs++; + } + ++ hbcleanup(&shaped); + return numspecs; + } + + void +-xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) ++xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y, int charlen) + { +- int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); + int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, + width = charlen * win.cw; + Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; +@@ -1521,21 +1573,24 @@ void + xdrawglyph(Glyph g, int x, int y) + { + int numspecs; +- XftGlyphFontSpec spec; ++ XftGlyphFontSpec *specs = xw.specbuf; + +- numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); +- xdrawglyphfontspecs(&spec, g, numspecs, x, y); ++ numspecs = xmakeglyphfontspecs(specs, &g, 1, x, y); ++ xdrawglyphfontspecs(specs, g, numspecs, x, y, (g.mode & ATTR_WIDE) ? 2 : 1); + } + + void +-xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) ++xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og, Line line, int len) + { + Color drawcol; + + /* remove the old cursor */ + if (selected(ox, oy)) + og.mode ^= ATTR_REVERSE; +- xdrawglyph(og, ox, oy); ++ ++ /* Redraw the line where cursor was previously. ++ * It will restore the ligatures broken by the cursor. */ ++ xdrawline(line, 0, oy, len); + + if (IS_SET(MODE_HIDE)) + return; +@@ -1663,18 +1718,16 @@ xdrawline(Line line, int x1, int y1, int x2) + Glyph base, new; + XftGlyphFontSpec *specs = xw.specbuf; + +- numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1); + i = ox = 0; +- for (x = x1; x < x2 && i < numspecs; x++) { ++ for (x = x1; x < x2; x++) { + new = line[x]; + if (new.mode == ATTR_WDUMMY) + continue; + if (selected(x, y1)) + new.mode ^= ATTR_REVERSE; +- if (i > 0 && ATTRCMP(base, new)) { +- xdrawglyphfontspecs(specs, base, i, ox, y1); +- specs += i; +- numspecs -= i; ++ if ((i > 0) && ATTRCMP(base, new)) { ++ numspecs = xmakeglyphfontspecs(specs, &line[ox], x - ox, ox, y1); ++ xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x - ox); + i = 0; + } + if (i == 0) { +@@ -1683,8 +1736,10 @@ xdrawline(Line line, int x1, int y1, int x2) + } + i++; + } +- if (i > 0) +- xdrawglyphfontspecs(specs, base, i, ox, y1); ++ if (i > 0) { ++ numspecs = xmakeglyphfontspecs(specs, &line[ox], x2 - ox, ox, y1); ++ xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x2 - ox); ++ } + } + + void diff --git a/st.suckless.org/patches/ligatures/0.9.2/st-ligatures-scrollback-20240427-0.9.2.diff b/st.suckless.org/patches/ligatures/0.9.2/st-ligatures-scrollback-20240427-0.9.2.diff @@ -0,0 +1,634 @@ +diff --git a/Makefile b/Makefile +index 470ac86..38240da 100644 +--- a/Makefile ++++ b/Makefile +@@ -4,7 +4,7 @@ + + include config.mk + +-SRC = st.c x.c ++SRC = st.c x.c hb.c + OBJ = $(SRC:.c=.o) + + all: st +@@ -22,7 +22,8 @@ config.h: + $(CC) $(STCFLAGS) -c $< + + st.o: config.h st.h win.h +-x.o: arg.h config.h st.h win.h ++x.o: arg.h config.h st.h win.h hb.h ++hb.o: st.h + + $(OBJ): config.h config.mk + +diff --git a/config.mk b/config.mk +index 1e306f8..3e13e53 100644 +--- a/config.mk ++++ b/config.mk +@@ -15,10 +15,12 @@ PKG_CONFIG = pkg-config + # includes and libs + INCS = -I$(X11INC) \ + `$(PKG_CONFIG) --cflags fontconfig` \ +- `$(PKG_CONFIG) --cflags freetype2` ++ `$(PKG_CONFIG) --cflags freetype2` \ ++ `$(PKG_CONFIG) --cflags harfbuzz` + LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft \ + `$(PKG_CONFIG) --libs fontconfig` \ +- `$(PKG_CONFIG) --libs freetype2` ++ `$(PKG_CONFIG) --libs freetype2` \ ++ `$(PKG_CONFIG) --libs harfbuzz` + + # flags + STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 +diff --git a/hb.c b/hb.c +new file mode 100644 +index 0000000..99412c8 +--- /dev/null ++++ b/hb.c +@@ -0,0 +1,125 @@ ++#include <stdlib.h> ++#include <stdio.h> ++#include <math.h> ++#include <X11/Xft/Xft.h> ++#include <X11/cursorfont.h> ++#include <hb.h> ++#include <hb-ft.h> ++ ++#include "st.h" ++#include "hb.h" ++ ++#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 } ++#define BUFFER_STEP 256 ++ ++hb_font_t *hbfindfont(XftFont *match); ++ ++typedef struct { ++ XftFont *match; ++ hb_font_t *font; ++} HbFontMatch; ++ ++typedef struct { ++ size_t capacity; ++ HbFontMatch *fonts; ++} HbFontCache; ++ ++static HbFontCache hbfontcache = { 0, NULL }; ++ ++typedef struct { ++ size_t capacity; ++ Rune *runes; ++} RuneBuffer; ++ ++static RuneBuffer hbrunebuffer = { 0, NULL }; ++ ++/* ++ * Poplulate the array with a list of font features, wrapped in FEATURE macro, ++ * e. g. ++ * FEATURE('c', 'a', 'l', 't'), FEATURE('d', 'l', 'i', 'g') ++ */ ++hb_feature_t features[] = { }; ++ ++void ++hbunloadfonts() ++{ ++ for (int i = 0; i < hbfontcache.capacity; i++) { ++ hb_font_destroy(hbfontcache.fonts[i].font); ++ XftUnlockFace(hbfontcache.fonts[i].match); ++ } ++ ++ if (hbfontcache.fonts != NULL) { ++ free(hbfontcache.fonts); ++ hbfontcache.fonts = NULL; ++ } ++ hbfontcache.capacity = 0; ++} ++ ++hb_font_t * ++hbfindfont(XftFont *match) ++{ ++ for (int i = 0; i < hbfontcache.capacity; i++) { ++ if (hbfontcache.fonts[i].match == match) ++ return hbfontcache.fonts[i].font; ++ } ++ ++ /* Font not found in cache, caching it now. */ ++ hbfontcache.fonts = realloc(hbfontcache.fonts, sizeof(HbFontMatch) * (hbfontcache.capacity + 1)); ++ FT_Face face = XftLockFace(match); ++ hb_font_t *font = hb_ft_font_create(face, NULL); ++ if (font == NULL) ++ die("Failed to load Harfbuzz font."); ++ ++ hbfontcache.fonts[hbfontcache.capacity].match = match; ++ hbfontcache.fonts[hbfontcache.capacity].font = font; ++ hbfontcache.capacity += 1; ++ ++ return font; ++} ++ ++void hbtransform(HbTransformData *data, XftFont *xfont, const Glyph *glyphs, int start, int length) { ++ ushort mode = USHRT_MAX; ++ unsigned int glyph_count; ++ int rune_idx, glyph_idx, end = start + length; ++ ++ hb_font_t *font = hbfindfont(xfont); ++ if (font == NULL) ++ return; ++ ++ hb_buffer_t *buffer = hb_buffer_create(); ++ hb_buffer_set_direction(buffer, HB_DIRECTION_LTR); ++ hb_buffer_set_cluster_level(buffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS); ++ ++ /* Resize the buffer if required length is larger. */ ++ if (hbrunebuffer.capacity < length) { ++ hbrunebuffer.capacity = (length / BUFFER_STEP + 1) * BUFFER_STEP; ++ hbrunebuffer.runes = realloc(hbrunebuffer.runes, hbrunebuffer.capacity * sizeof(Rune)); ++ } ++ ++ /* Fill buffer with codepoints. */ ++ for (rune_idx = 0, glyph_idx = start; glyph_idx < end; glyph_idx++, rune_idx++) { ++ hbrunebuffer.runes[rune_idx] = glyphs[glyph_idx].u; ++ mode = glyphs[glyph_idx].mode; ++ if (mode & ATTR_WDUMMY) ++ hbrunebuffer.runes[rune_idx] = 0x0020; ++ } ++ hb_buffer_add_codepoints(buffer, hbrunebuffer.runes, length, 0, length); ++ ++ /* Shape the segment. */ ++ hb_shape(font, buffer, features, sizeof(features)/sizeof(hb_feature_t)); ++ ++ /* Get new glyph info. */ ++ hb_glyph_info_t *info = hb_buffer_get_glyph_infos(buffer, &glyph_count); ++ hb_glyph_position_t *pos = hb_buffer_get_glyph_positions(buffer, &glyph_count); ++ ++ /* Fill the output. */ ++ data->buffer = buffer; ++ data->glyphs = info; ++ data->positions = pos; ++ data->count = glyph_count; ++} ++ ++void hbcleanup(HbTransformData *data) { ++ hb_buffer_destroy(data->buffer); ++ memset(data, 0, sizeof(HbTransformData)); ++} +diff --git a/hb.h b/hb.h +new file mode 100644 +index 0000000..3b0ef44 +--- /dev/null ++++ b/hb.h +@@ -0,0 +1,14 @@ ++#include <X11/Xft/Xft.h> ++#include <hb.h> ++#include <hb-ft.h> ++ ++typedef struct { ++ hb_buffer_t *buffer; ++ hb_glyph_info_t *glyphs; ++ hb_glyph_position_t *positions; ++ unsigned int count; ++} HbTransformData; ++ ++void hbunloadfonts(); ++void hbtransform(HbTransformData *, XftFont *, const Glyph *, int, int); ++void hbcleanup(HbTransformData *); +diff --git a/st.c b/st.c +index 79ee9ba..7675db6 100644 +--- a/st.c ++++ b/st.c +@@ -2711,7 +2711,8 @@ draw(void) + drawregion(0, 0, term.col, term.row); + if (term.scr == 0) + xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], +- term.ocx, term.ocy, term.line[term.ocy][term.ocx]); ++ term.ocx, term.ocy, term.line[term.ocy][term.ocx], ++ term.line[term.ocy], term.col); + term.ocx = cx; + term.ocy = term.c.y; + xfinishdraw(); +diff --git a/st.h b/st.h +index 818a6f8..4e584b6 100644 +--- a/st.h ++++ b/st.h +@@ -11,7 +11,8 @@ + #define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d)) + #define DEFAULT(a, b) (a) = (a) ? (a) : (b) + #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) +-#define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \ ++#define ATTRCMP(a, b) (((a).mode & (~ATTR_WRAP)) != ((b).mode & (~ATTR_WRAP)) || \ ++ (a).fg != (b).fg || \ + (a).bg != (b).bg) + #define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \ + (t1.tv_nsec-t2.tv_nsec)/1E6) +diff --git a/win.h b/win.h +index 6de960d..94679e4 100644 +--- a/win.h ++++ b/win.h +@@ -25,7 +25,7 @@ enum win_mode { + + void xbell(void); + void xclipcopy(void); +-void xdrawcursor(int, int, Glyph, int, int, Glyph); ++void xdrawcursor(int, int, Glyph, int, int, Glyph, Line, int); + void xdrawline(Line, int, int, int); + void xfinishdraw(void); + void xloadcols(void); +diff --git a/x.c b/x.c +index 2a3bd38..0bb51ff 100644 +--- a/x.c ++++ b/x.c +@@ -19,6 +19,7 @@ char *argv0; + #include "arg.h" + #include "st.h" + #include "win.h" ++#include "hb.h" + + /* types used in config.h */ + typedef struct { +@@ -141,8 +142,9 @@ typedef struct { + } DC; + + static inline ushort sixd_to_16bit(int); ++static void xresetfontsettings(ushort mode, Font **font, int *frcflags); + static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); +-static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); ++static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int, int); + static void xdrawglyph(Glyph, int, int); + static void xclear(int, int, int, int); + static int xgeommasktogravity(int); +@@ -757,7 +759,7 @@ xresize(int col, int row) + xclear(0, 0, win.w, win.h); + + /* resize to new width */ +- xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec)); ++ xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec) * 4); + } + + ushort +@@ -1062,6 +1064,9 @@ xunloadfont(Font *f) + void + xunloadfonts(void) + { ++ /* Clear Harfbuzz font cache. */ ++ hbunloadfonts(); ++ + /* Free the loaded fonts in the font cache. */ + while (frclen > 0) + XftFontClose(xw.dpy, frc[--frclen].font); +@@ -1185,7 +1190,7 @@ xinit(int cols, int rows) + XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); + + /* font spec buffer */ +- xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec)); ++ xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec) * 4); + + /* Xft rendering context */ + xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); +@@ -1239,6 +1244,22 @@ xinit(int cols, int rows) + xsel.xtarget = XA_STRING; + } + ++void ++xresetfontsettings(ushort mode, Font **font, int *frcflags) ++{ ++ *font = &dc.font; ++ if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { ++ *font = &dc.ibfont; ++ *frcflags = FRC_ITALICBOLD; ++ } else if (mode & ATTR_ITALIC) { ++ *font = &dc.ifont; ++ *frcflags = FRC_ITALIC; ++ } else if (mode & ATTR_BOLD) { ++ *font = &dc.bfont; ++ *frcflags = FRC_BOLD; ++ } ++} ++ + int + xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) + { +@@ -1253,128 +1274,157 @@ 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, f, length = 0, start = 0, numspecs = 0; ++ float cluster_xp = xp, cluster_yp = yp; ++ HbTransformData shaped = { 0 }; ++ ++ /* Initial values. */ ++ mode = prevmode = glyphs[0].mode & ~ATTR_WRAP; ++ xresetfontsettings(mode, &font, &frcflags); + + for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { +- /* Fetch rune and mode for current glyph. */ +- rune = glyphs[i].u; +- mode = glyphs[i].mode; ++ mode = glyphs[i].mode & ~ATTR_WRAP; + + /* Skip dummy wide-character spacing. */ +- if (mode == ATTR_WDUMMY) ++ if (mode & ATTR_WDUMMY && i < (len - 1)) + continue; + +- /* Determine font for glyph if different from previous glyph. */ +- if (prevmode != mode) { +- prevmode = mode; +- font = &dc.font; +- frcflags = FRC_NORMAL; +- runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f); +- if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { +- font = &dc.ibfont; +- frcflags = FRC_ITALICBOLD; +- } else if (mode & ATTR_ITALIC) { +- font = &dc.ifont; +- frcflags = FRC_ITALIC; +- } else if (mode & ATTR_BOLD) { +- font = &dc.bfont; +- frcflags = FRC_BOLD; ++ if ( ++ prevmode != mode ++ || ATTRCMP(glyphs[start], glyphs[i]) ++ || selected(x + i, y) != selected(x + start, y) ++ || i == (len - 1) ++ ) { ++ /* Handle 1-character wide segments and end of line */ ++ length = i - start; ++ if (i == start) { ++ length = 1; ++ } else if (i == (len - 1)) { ++ length = (i - start + 1); + } +- yp = winy + font->ascent; +- } + +- /* 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; +- numspecs++; +- continue; +- } +- +- /* Fallback on font cache, search the font cache for match. */ +- for (f = 0; f < frclen; f++) { +- glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); +- /* Everything correct. */ +- if (glyphidx && frc[f].flags == frcflags) +- break; +- /* We got a default font for a not found glyph. */ +- if (!glyphidx && frc[f].flags == frcflags +- && frc[f].unicodep == rune) { +- break; ++ /* Shape the segment. */ ++ hbtransform(&shaped, font->match, glyphs, start, length); ++ runewidth = win.cw * ((glyphs[start].mode & ATTR_WIDE) ? 2.0f : 1.0f); ++ cluster_xp = xp; cluster_yp = yp; ++ for (int code_idx = 0; code_idx < shaped.count; code_idx++) { ++ int idx = shaped.glyphs[code_idx].cluster; ++ ++ if (glyphs[start + idx].mode & ATTR_WDUMMY) ++ continue; ++ ++ /* Advance the drawing cursor if we've moved to a new cluster */ ++ if (code_idx > 0 && idx != shaped.glyphs[code_idx - 1].cluster) { ++ xp += runewidth; ++ cluster_xp = xp; ++ cluster_yp = yp; ++ runewidth = win.cw * ((glyphs[start + idx].mode & ATTR_WIDE) ? 2.0f : 1.0f); ++ } ++ ++ if (shaped.glyphs[code_idx].codepoint != 0) { ++ /* If symbol is found, put it into the specs. */ ++ specs[numspecs].font = font->match; ++ specs[numspecs].glyph = shaped.glyphs[code_idx].codepoint; ++ specs[numspecs].x = cluster_xp + (short)(shaped.positions[code_idx].x_offset / 64.); ++ specs[numspecs].y = cluster_yp - (short)(shaped.positions[code_idx].y_offset / 64.); ++ cluster_xp += shaped.positions[code_idx].x_advance / 64.; ++ cluster_yp += shaped.positions[code_idx].y_advance / 64.; ++ numspecs++; ++ } else { ++ /* If it's not found, try to fetch it through the font cache. */ ++ rune = glyphs[start + idx].u; ++ for (f = 0; f < frclen; f++) { ++ glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); ++ /* Everything correct. */ ++ if (glyphidx && frc[f].flags == frcflags) ++ break; ++ /* We got a default font for a not found glyph. */ ++ if (!glyphidx && frc[f].flags == frcflags ++ && frc[f].unicodep == rune) { ++ break; ++ } ++ } ++ ++ /* Nothing was found. Use fontconfig to find matching font. */ ++ if (f >= frclen) { ++ if (!font->set) ++ font->set = FcFontSort(0, font->pattern, ++ 1, 0, &fcres); ++ fcsets[0] = font->set; ++ ++ /* ++ * Nothing was found in the cache. Now use ++ * some dozen of Fontconfig calls to get the ++ * font for one single character. ++ * ++ * Xft and fontconfig are design failures. ++ */ ++ fcpattern = FcPatternDuplicate(font->pattern); ++ fccharset = FcCharSetCreate(); ++ ++ FcCharSetAddChar(fccharset, rune); ++ FcPatternAddCharSet(fcpattern, FC_CHARSET, ++ fccharset); ++ FcPatternAddBool(fcpattern, FC_SCALABLE, 1); ++ ++ FcConfigSubstitute(0, fcpattern, ++ FcMatchPattern); ++ FcDefaultSubstitute(fcpattern); ++ ++ fontpattern = FcFontSetMatch(0, fcsets, 1, ++ fcpattern, &fcres); ++ ++ /* Allocate memory for the new cache entry. */ ++ if (frclen >= frccap) { ++ frccap += 16; ++ frc = xrealloc(frc, frccap * sizeof(Fontcache)); ++ } ++ ++ frc[frclen].font = XftFontOpenPattern(xw.dpy, ++ fontpattern); ++ if (!frc[frclen].font) ++ die("XftFontOpenPattern failed seeking fallback font: %s\n", ++ strerror(errno)); ++ frc[frclen].flags = frcflags; ++ frc[frclen].unicodep = rune; ++ ++ glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); ++ ++ f = frclen; ++ frclen++; ++ ++ FcPatternDestroy(fcpattern); ++ FcCharSetDestroy(fccharset); ++ } ++ ++ specs[numspecs].font = frc[f].font; ++ specs[numspecs].glyph = glyphidx; ++ specs[numspecs].x = (short)xp; ++ specs[numspecs].y = (short)yp; ++ numspecs++; ++ } + } +- } + +- /* Nothing was found. Use fontconfig to find matching font. */ +- if (f >= frclen) { +- if (!font->set) +- font->set = FcFontSort(0, font->pattern, +- 1, 0, &fcres); +- fcsets[0] = font->set; ++ /* Cleanup and get ready for next segment. */ ++ hbcleanup(&shaped); ++ start = i; + +- /* +- * Nothing was found in the cache. Now use +- * some dozen of Fontconfig calls to get the +- * font for one single character. +- * +- * Xft and fontconfig are design failures. +- */ +- fcpattern = FcPatternDuplicate(font->pattern); +- fccharset = FcCharSetCreate(); +- +- FcCharSetAddChar(fccharset, rune); +- FcPatternAddCharSet(fcpattern, FC_CHARSET, +- fccharset); +- FcPatternAddBool(fcpattern, FC_SCALABLE, 1); +- +- FcConfigSubstitute(0, fcpattern, +- FcMatchPattern); +- FcDefaultSubstitute(fcpattern); +- +- fontpattern = FcFontSetMatch(0, fcsets, 1, +- fcpattern, &fcres); +- +- /* Allocate memory for the new cache entry. */ +- if (frclen >= frccap) { +- frccap += 16; +- frc = xrealloc(frc, frccap * sizeof(Fontcache)); ++ /* Determine font for glyph if different from previous glyph. */ ++ if (prevmode != mode) { ++ prevmode = mode; ++ xresetfontsettings(mode, &font, &frcflags); ++ yp = winy + font->ascent; + } +- +- frc[frclen].font = XftFontOpenPattern(xw.dpy, +- fontpattern); +- if (!frc[frclen].font) +- die("XftFontOpenPattern failed seeking fallback font: %s\n", +- strerror(errno)); +- frc[frclen].flags = frcflags; +- frc[frclen].unicodep = rune; +- +- glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); +- +- f = frclen; +- frclen++; +- +- FcPatternDestroy(fcpattern); +- FcCharSetDestroy(fccharset); + } +- +- specs[numspecs].font = frc[f].font; +- specs[numspecs].glyph = glyphidx; +- specs[numspecs].x = (short)xp; +- specs[numspecs].y = (short)yp; +- xp += runewidth; +- numspecs++; + } + ++ hbcleanup(&shaped); + return numspecs; + } + + void +-xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) ++xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y, int charlen) + { +- int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); + int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, + width = charlen * win.cw; + Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; +@@ -1510,21 +1560,24 @@ void + xdrawglyph(Glyph g, int x, int y) + { + int numspecs; +- XftGlyphFontSpec spec; ++ XftGlyphFontSpec *specs = xw.specbuf; + +- numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); +- xdrawglyphfontspecs(&spec, g, numspecs, x, y); ++ numspecs = xmakeglyphfontspecs(specs, &g, 1, x, y); ++ xdrawglyphfontspecs(specs, g, numspecs, x, y, (g.mode & ATTR_WIDE) ? 2 : 1); + } + + void +-xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) ++xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og, Line line, int len) + { + Color drawcol; + + /* remove the old cursor */ + if (selected(ox, oy)) + og.mode ^= ATTR_REVERSE; +- xdrawglyph(og, ox, oy); ++ ++ /* Redraw the line where cursor was previously. ++ * It will restore the ligatures broken by the cursor. */ ++ xdrawline(line, 0, oy, len); + + if (IS_SET(MODE_HIDE)) + return; +@@ -1652,18 +1705,16 @@ xdrawline(Line line, int x1, int y1, int x2) + Glyph base, new; + XftGlyphFontSpec *specs = xw.specbuf; + +- numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1); + i = ox = 0; +- for (x = x1; x < x2 && i < numspecs; x++) { ++ for (x = x1; x < x2; x++) { + new = line[x]; + if (new.mode == ATTR_WDUMMY) + continue; + if (selected(x, y1)) + new.mode ^= ATTR_REVERSE; +- if (i > 0 && ATTRCMP(base, new)) { +- xdrawglyphfontspecs(specs, base, i, ox, y1); +- specs += i; +- numspecs -= i; ++ if ((i > 0) && ATTRCMP(base, new)) { ++ numspecs = xmakeglyphfontspecs(specs, &line[ox], x - ox, ox, y1); ++ xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x - ox); + i = 0; + } + if (i == 0) { +@@ -1672,8 +1723,10 @@ xdrawline(Line line, int x1, int y1, int x2) + } + i++; + } +- if (i > 0) +- xdrawglyphfontspecs(specs, base, i, ox, y1); ++ if (i > 0) { ++ numspecs = xmakeglyphfontspecs(specs, &line[ox], x2 - ox, ox, y1); ++ xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x2 - ox); ++ } + } + + void diff --git a/st.suckless.org/patches/ligatures/0.9.2/st-ligatures-scrollback-ringbuffer-20240427-0.9.2.diff b/st.suckless.org/patches/ligatures/0.9.2/st-ligatures-scrollback-ringbuffer-20240427-0.9.2.diff @@ -0,0 +1,633 @@ +diff --git a/Makefile b/Makefile +index 470ac86..38240da 100644 +--- a/Makefile ++++ b/Makefile +@@ -4,7 +4,7 @@ + + include config.mk + +-SRC = st.c x.c ++SRC = st.c x.c hb.c + OBJ = $(SRC:.c=.o) + + all: st +@@ -22,7 +22,8 @@ config.h: + $(CC) $(STCFLAGS) -c $< + + st.o: config.h st.h win.h +-x.o: arg.h config.h st.h win.h ++x.o: arg.h config.h st.h win.h hb.h ++hb.o: st.h + + $(OBJ): config.h config.mk + +diff --git a/config.mk b/config.mk +index 1e306f8..3e13e53 100644 +--- a/config.mk ++++ b/config.mk +@@ -15,10 +15,12 @@ PKG_CONFIG = pkg-config + # includes and libs + INCS = -I$(X11INC) \ + `$(PKG_CONFIG) --cflags fontconfig` \ +- `$(PKG_CONFIG) --cflags freetype2` ++ `$(PKG_CONFIG) --cflags freetype2` \ ++ `$(PKG_CONFIG) --cflags harfbuzz` + LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft \ + `$(PKG_CONFIG) --libs fontconfig` \ +- `$(PKG_CONFIG) --libs freetype2` ++ `$(PKG_CONFIG) --libs freetype2` \ ++ `$(PKG_CONFIG) --libs harfbuzz` + + # flags + STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 +diff --git a/hb.c b/hb.c +new file mode 100644 +index 0000000..99412c8 +--- /dev/null ++++ b/hb.c +@@ -0,0 +1,125 @@ ++#include <stdlib.h> ++#include <stdio.h> ++#include <math.h> ++#include <X11/Xft/Xft.h> ++#include <X11/cursorfont.h> ++#include <hb.h> ++#include <hb-ft.h> ++ ++#include "st.h" ++#include "hb.h" ++ ++#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 } ++#define BUFFER_STEP 256 ++ ++hb_font_t *hbfindfont(XftFont *match); ++ ++typedef struct { ++ XftFont *match; ++ hb_font_t *font; ++} HbFontMatch; ++ ++typedef struct { ++ size_t capacity; ++ HbFontMatch *fonts; ++} HbFontCache; ++ ++static HbFontCache hbfontcache = { 0, NULL }; ++ ++typedef struct { ++ size_t capacity; ++ Rune *runes; ++} RuneBuffer; ++ ++static RuneBuffer hbrunebuffer = { 0, NULL }; ++ ++/* ++ * Poplulate the array with a list of font features, wrapped in FEATURE macro, ++ * e. g. ++ * FEATURE('c', 'a', 'l', 't'), FEATURE('d', 'l', 'i', 'g') ++ */ ++hb_feature_t features[] = { }; ++ ++void ++hbunloadfonts() ++{ ++ for (int i = 0; i < hbfontcache.capacity; i++) { ++ hb_font_destroy(hbfontcache.fonts[i].font); ++ XftUnlockFace(hbfontcache.fonts[i].match); ++ } ++ ++ if (hbfontcache.fonts != NULL) { ++ free(hbfontcache.fonts); ++ hbfontcache.fonts = NULL; ++ } ++ hbfontcache.capacity = 0; ++} ++ ++hb_font_t * ++hbfindfont(XftFont *match) ++{ ++ for (int i = 0; i < hbfontcache.capacity; i++) { ++ if (hbfontcache.fonts[i].match == match) ++ return hbfontcache.fonts[i].font; ++ } ++ ++ /* Font not found in cache, caching it now. */ ++ hbfontcache.fonts = realloc(hbfontcache.fonts, sizeof(HbFontMatch) * (hbfontcache.capacity + 1)); ++ FT_Face face = XftLockFace(match); ++ hb_font_t *font = hb_ft_font_create(face, NULL); ++ if (font == NULL) ++ die("Failed to load Harfbuzz font."); ++ ++ hbfontcache.fonts[hbfontcache.capacity].match = match; ++ hbfontcache.fonts[hbfontcache.capacity].font = font; ++ hbfontcache.capacity += 1; ++ ++ return font; ++} ++ ++void hbtransform(HbTransformData *data, XftFont *xfont, const Glyph *glyphs, int start, int length) { ++ ushort mode = USHRT_MAX; ++ unsigned int glyph_count; ++ int rune_idx, glyph_idx, end = start + length; ++ ++ hb_font_t *font = hbfindfont(xfont); ++ if (font == NULL) ++ return; ++ ++ hb_buffer_t *buffer = hb_buffer_create(); ++ hb_buffer_set_direction(buffer, HB_DIRECTION_LTR); ++ hb_buffer_set_cluster_level(buffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS); ++ ++ /* Resize the buffer if required length is larger. */ ++ if (hbrunebuffer.capacity < length) { ++ hbrunebuffer.capacity = (length / BUFFER_STEP + 1) * BUFFER_STEP; ++ hbrunebuffer.runes = realloc(hbrunebuffer.runes, hbrunebuffer.capacity * sizeof(Rune)); ++ } ++ ++ /* Fill buffer with codepoints. */ ++ for (rune_idx = 0, glyph_idx = start; glyph_idx < end; glyph_idx++, rune_idx++) { ++ hbrunebuffer.runes[rune_idx] = glyphs[glyph_idx].u; ++ mode = glyphs[glyph_idx].mode; ++ if (mode & ATTR_WDUMMY) ++ hbrunebuffer.runes[rune_idx] = 0x0020; ++ } ++ hb_buffer_add_codepoints(buffer, hbrunebuffer.runes, length, 0, length); ++ ++ /* Shape the segment. */ ++ hb_shape(font, buffer, features, sizeof(features)/sizeof(hb_feature_t)); ++ ++ /* Get new glyph info. */ ++ hb_glyph_info_t *info = hb_buffer_get_glyph_infos(buffer, &glyph_count); ++ hb_glyph_position_t *pos = hb_buffer_get_glyph_positions(buffer, &glyph_count); ++ ++ /* Fill the output. */ ++ data->buffer = buffer; ++ data->glyphs = info; ++ data->positions = pos; ++ data->count = glyph_count; ++} ++ ++void hbcleanup(HbTransformData *data) { ++ hb_buffer_destroy(data->buffer); ++ memset(data, 0, sizeof(HbTransformData)); ++} +diff --git a/hb.h b/hb.h +new file mode 100644 +index 0000000..3b0ef44 +--- /dev/null ++++ b/hb.h +@@ -0,0 +1,14 @@ ++#include <X11/Xft/Xft.h> ++#include <hb.h> ++#include <hb-ft.h> ++ ++typedef struct { ++ hb_buffer_t *buffer; ++ hb_glyph_info_t *glyphs; ++ hb_glyph_position_t *positions; ++ unsigned int count; ++} HbTransformData; ++ ++void hbunloadfonts(); ++void hbtransform(HbTransformData *, XftFont *, const Glyph *, int, int); ++void hbcleanup(HbTransformData *); +diff --git a/st.c b/st.c +index c44797b..91f54dc 100644 +--- a/st.c ++++ b/st.c +@@ -2759,7 +2759,8 @@ draw(void) + drawregion(0, 0, term.col, term.row); + if (TSCREEN.off == 0) + xdrawcursor(cx, term.c.y, TLINE(term.c.y)[cx], +- term.ocx, term.ocy, TLINE(term.ocy)[term.ocx]); ++ term.ocx, term.ocy, TLINE(term.ocy)[term.ocx], ++ TLINE(term.ocy), term.col); + term.ocx = cx; + term.ocy = term.c.y; + xfinishdraw(); +diff --git a/st.h b/st.h +index 3cea73b..709a369 100644 +--- a/st.h ++++ b/st.h +@@ -11,7 +11,8 @@ + #define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d)) + #define DEFAULT(a, b) (a) = (a) ? (a) : (b) + #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) +-#define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \ ++#define ATTRCMP(a, b) (((a).mode & (~ATTR_WRAP)) != ((b).mode & (~ATTR_WRAP)) || \ ++ (a).fg != (b).fg || \ + (a).bg != (b).bg) + #define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \ + (t1.tv_nsec-t2.tv_nsec)/1E6) +diff --git a/win.h b/win.h +index 6de960d..94679e4 100644 +--- a/win.h ++++ b/win.h +@@ -25,7 +25,7 @@ enum win_mode { + + void xbell(void); + void xclipcopy(void); +-void xdrawcursor(int, int, Glyph, int, int, Glyph); ++void xdrawcursor(int, int, Glyph, int, int, Glyph, Line, int); + void xdrawline(Line, int, int, int); + void xfinishdraw(void); + void xloadcols(void); +diff --git a/x.c b/x.c +index 9891e91..7d42790 100644 +--- a/x.c ++++ b/x.c +@@ -19,6 +19,7 @@ char *argv0; + #include "arg.h" + #include "st.h" + #include "win.h" ++#include "hb.h" + + /* types used in config.h */ + typedef struct { +@@ -143,8 +144,9 @@ typedef struct { + } DC; + + static inline ushort sixd_to_16bit(int); ++static void xresetfontsettings(ushort mode, Font **font, int *frcflags); + static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); +-static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); ++static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int, int); + static void xdrawglyph(Glyph, int, int); + static void xclear(int, int, int, int); + static int xgeommasktogravity(int); +@@ -759,7 +761,7 @@ xresize(int col, int row) + xclear(0, 0, win.w, win.h); + + /* resize to new width */ +- xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec)); ++ xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec) * 4); + } + + ushort +@@ -1064,6 +1066,9 @@ xunloadfont(Font *f) + void + xunloadfonts(void) + { ++ /* Clear Harfbuzz font cache. */ ++ hbunloadfonts(); ++ + /* Free the loaded fonts in the font cache. */ + while (frclen > 0) + XftFontClose(xw.dpy, frc[--frclen].font); +@@ -1187,7 +1192,7 @@ xinit(int cols, int rows) + XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); + + /* font spec buffer */ +- xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec)); ++ xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec) * 4); + + /* Xft rendering context */ + xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); +@@ -1241,6 +1246,22 @@ xinit(int cols, int rows) + xsel.xtarget = XA_STRING; + } + ++void ++xresetfontsettings(ushort mode, Font **font, int *frcflags) ++{ ++ *font = &dc.font; ++ if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { ++ *font = &dc.ibfont; ++ *frcflags = FRC_ITALICBOLD; ++ } else if (mode & ATTR_ITALIC) { ++ *font = &dc.ifont; ++ *frcflags = FRC_ITALIC; ++ } else if (mode & ATTR_BOLD) { ++ *font = &dc.bfont; ++ *frcflags = FRC_BOLD; ++ } ++} ++ + int + xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) + { +@@ -1255,128 +1276,156 @@ 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, f, length = 0, start = 0, numspecs = 0; ++ float cluster_xp = xp, cluster_yp = yp; ++ HbTransformData shaped = { 0 }; ++ ++ /* Initial values. */ ++ mode = prevmode = glyphs[0].mode & ~ATTR_WRAP; ++ xresetfontsettings(mode, &font, &frcflags); + + for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { +- /* Fetch rune and mode for current glyph. */ +- rune = glyphs[i].u; +- mode = glyphs[i].mode; ++ mode = glyphs[i].mode & ~ATTR_WRAP; + + /* Skip dummy wide-character spacing. */ +- if (mode == ATTR_WDUMMY) ++ if (mode & ATTR_WDUMMY && i < (len - 1)) + continue; + +- /* Determine font for glyph if different from previous glyph. */ +- if (prevmode != mode) { +- prevmode = mode; +- font = &dc.font; +- frcflags = FRC_NORMAL; +- runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f); +- if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { +- font = &dc.ibfont; +- frcflags = FRC_ITALICBOLD; +- } else if (mode & ATTR_ITALIC) { +- font = &dc.ifont; +- frcflags = FRC_ITALIC; +- } else if (mode & ATTR_BOLD) { +- font = &dc.bfont; +- frcflags = FRC_BOLD; ++ if ( ++ prevmode != mode ++ || ATTRCMP(glyphs[start], glyphs[i]) ++ || selected(x + i, y) != selected(x + start, y) ++ || i == (len - 1) ++ ) { ++ /* Handle 1-character wide segments and end of line */ ++ length = i - start; ++ if (i == start) { ++ length = 1; ++ } else if (i == (len - 1)) { ++ length = (i - start + 1); + } +- yp = winy + font->ascent; +- } + +- /* 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; +- numspecs++; +- continue; +- } +- +- /* Fallback on font cache, search the font cache for match. */ +- for (f = 0; f < frclen; f++) { +- glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); +- /* Everything correct. */ +- if (glyphidx && frc[f].flags == frcflags) +- break; +- /* We got a default font for a not found glyph. */ +- if (!glyphidx && frc[f].flags == frcflags +- && frc[f].unicodep == rune) { +- break; ++ /* Shape the segment. */ ++ hbtransform(&shaped, font->match, glyphs, start, length); ++ runewidth = win.cw * ((glyphs[start].mode & ATTR_WIDE) ? 2.0f : 1.0f); ++ cluster_xp = xp; cluster_yp = yp; ++ for (int code_idx = 0; code_idx < shaped.count; code_idx++) { ++ int idx = shaped.glyphs[code_idx].cluster; ++ ++ if (glyphs[start + idx].mode & ATTR_WDUMMY) ++ continue; ++ ++ /* Advance the drawing cursor if we've moved to a new cluster */ ++ if (code_idx > 0 && idx != shaped.glyphs[code_idx - 1].cluster) { ++ xp += runewidth; ++ cluster_xp = xp; ++ cluster_yp = yp; ++ runewidth = win.cw * ((glyphs[start + idx].mode & ATTR_WIDE) ? 2.0f : 1.0f); ++ } ++ ++ if (shaped.glyphs[code_idx].codepoint != 0) { ++ /* If symbol is found, put it into the specs. */ ++ specs[numspecs].font = font->match; ++ specs[numspecs].glyph = shaped.glyphs[code_idx].codepoint; ++ specs[numspecs].x = cluster_xp + (short)(shaped.positions[code_idx].x_offset / 64.); ++ specs[numspecs].y = cluster_yp - (short)(shaped.positions[code_idx].y_offset / 64.); ++ cluster_xp += shaped.positions[code_idx].x_advance / 64.; ++ cluster_yp += shaped.positions[code_idx].y_advance / 64.; ++ numspecs++; ++ } else { ++ /* If it's not found, try to fetch it through the font cache. */ ++ rune = glyphs[start + idx].u; ++ for (f = 0; f < frclen; f++) { ++ glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); ++ /* Everything correct. */ ++ if (glyphidx && frc[f].flags == frcflags) ++ break; ++ /* We got a default font for a not found glyph. */ ++ if (!glyphidx && frc[f].flags == frcflags ++ && frc[f].unicodep == rune) { ++ break; ++ } ++ } ++ ++ /* Nothing was found. Use fontconfig to find matching font. */ ++ if (f >= frclen) { ++ if (!font->set) ++ font->set = FcFontSort(0, font->pattern, ++ 1, 0, &fcres); ++ fcsets[0] = font->set; ++ ++ /* ++ * Nothing was found in the cache. Now use ++ * some dozen of Fontconfig calls to get the ++ * font for one single character. ++ * ++ * Xft and fontconfig are design failures. ++ */ ++ fcpattern = FcPatternDuplicate(font->pattern); ++ fccharset = FcCharSetCreate(); ++ ++ FcCharSetAddChar(fccharset, rune); ++ FcPatternAddCharSet(fcpattern, FC_CHARSET, ++ fccharset); ++ FcPatternAddBool(fcpattern, FC_SCALABLE, 1); ++ ++ FcConfigSubstitute(0, fcpattern, ++ FcMatchPattern); ++ FcDefaultSubstitute(fcpattern); ++ ++ fontpattern = FcFontSetMatch(0, fcsets, 1, ++ fcpattern, &fcres); ++ ++ /* Allocate memory for the new cache entry. */ ++ if (frclen >= frccap) { ++ frccap += 16; ++ frc = xrealloc(frc, frccap * sizeof(Fontcache)); ++ } ++ ++ frc[frclen].font = XftFontOpenPattern(xw.dpy, ++ fontpattern); ++ if (!frc[frclen].font) ++ die("XftFontOpenPattern failed seeking fallback font: %s\n", ++ strerror(errno)); ++ frc[frclen].flags = frcflags; ++ frc[frclen].unicodep = rune; ++ ++ glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); ++ ++ f = frclen; ++ frclen++; ++ ++ FcPatternDestroy(fcpattern); ++ FcCharSetDestroy(fccharset); ++ } ++ ++ specs[numspecs].font = frc[f].font; ++ specs[numspecs].glyph = glyphidx; ++ specs[numspecs].x = (short)xp; ++ specs[numspecs].y = (short)yp; ++ numspecs++; ++ } + } +- } + +- /* Nothing was found. Use fontconfig to find matching font. */ +- if (f >= frclen) { +- if (!font->set) +- font->set = FcFontSort(0, font->pattern, +- 1, 0, &fcres); +- fcsets[0] = font->set; ++ /* Cleanup and get ready for next segment. */ ++ hbcleanup(&shaped); ++ start = i; + +- /* +- * Nothing was found in the cache. Now use +- * some dozen of Fontconfig calls to get the +- * font for one single character. +- * +- * Xft and fontconfig are design failures. +- */ +- fcpattern = FcPatternDuplicate(font->pattern); +- fccharset = FcCharSetCreate(); +- +- FcCharSetAddChar(fccharset, rune); +- FcPatternAddCharSet(fcpattern, FC_CHARSET, +- fccharset); +- FcPatternAddBool(fcpattern, FC_SCALABLE, 1); +- +- FcConfigSubstitute(0, fcpattern, +- FcMatchPattern); +- FcDefaultSubstitute(fcpattern); +- +- fontpattern = FcFontSetMatch(0, fcsets, 1, +- fcpattern, &fcres); +- +- /* Allocate memory for the new cache entry. */ +- if (frclen >= frccap) { +- frccap += 16; +- frc = xrealloc(frc, frccap * sizeof(Fontcache)); ++ /* Determine font for glyph if different from previous glyph. */ ++ if (prevmode != mode) { ++ prevmode = mode; ++ xresetfontsettings(mode, &font, &frcflags); ++ yp = winy + font->ascent; + } +- +- frc[frclen].font = XftFontOpenPattern(xw.dpy, +- fontpattern); +- if (!frc[frclen].font) +- die("XftFontOpenPattern failed seeking fallback font: %s\n", +- strerror(errno)); +- frc[frclen].flags = frcflags; +- frc[frclen].unicodep = rune; +- +- glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); +- +- f = frclen; +- frclen++; +- +- FcPatternDestroy(fcpattern); +- FcCharSetDestroy(fccharset); + } +- +- specs[numspecs].font = frc[f].font; +- specs[numspecs].glyph = glyphidx; +- specs[numspecs].x = (short)xp; +- specs[numspecs].y = (short)yp; +- xp += runewidth; +- numspecs++; + } + + return numspecs; + } + + void +-xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) ++xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y, int charlen) + { +- int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); + int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, + width = charlen * win.cw; + Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; +@@ -1512,21 +1561,24 @@ void + xdrawglyph(Glyph g, int x, int y) + { + int numspecs; +- XftGlyphFontSpec spec; ++ XftGlyphFontSpec *specs = xw.specbuf; + +- numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); +- xdrawglyphfontspecs(&spec, g, numspecs, x, y); ++ numspecs = xmakeglyphfontspecs(specs, &g, 1, x, y); ++ xdrawglyphfontspecs(specs, g, numspecs, x, y, (g.mode & ATTR_WIDE) ? 2 : 1); + } + + void +-xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) ++xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og, Line line, int len) + { + Color drawcol; + + /* remove the old cursor */ + if (selected(ox, oy)) + og.mode ^= ATTR_REVERSE; +- xdrawglyph(og, ox, oy); ++ ++ /* Redraw the line where cursor was previously. ++ * It will restore the ligatures broken by the cursor. */ ++ xdrawline(line, 0, oy, len); + + if (IS_SET(MODE_HIDE)) + return; +@@ -1654,18 +1706,16 @@ xdrawline(Line line, int x1, int y1, int x2) + Glyph base, new; + XftGlyphFontSpec *specs = xw.specbuf; + +- numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1); + i = ox = 0; +- for (x = x1; x < x2 && i < numspecs; x++) { ++ for (x = x1; x < x2; x++) { + new = line[x]; + if (new.mode == ATTR_WDUMMY) + continue; + if (selected(x, y1)) + new.mode ^= ATTR_REVERSE; +- if (i > 0 && ATTRCMP(base, new)) { +- xdrawglyphfontspecs(specs, base, i, ox, y1); +- specs += i; +- numspecs -= i; ++ if ((i > 0) && ATTRCMP(base, new)) { ++ numspecs = xmakeglyphfontspecs(specs, &line[ox], x - ox, ox, y1); ++ xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x - ox); + i = 0; + } + if (i == 0) { +@@ -1674,8 +1724,10 @@ xdrawline(Line line, int x1, int y1, int x2) + } + i++; + } +- if (i > 0) +- xdrawglyphfontspecs(specs, base, i, ox, y1); ++ if (i > 0) { ++ numspecs = xmakeglyphfontspecs(specs, &line[ox], x2 - ox, ox, y1); ++ xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x2 - ox); ++ } + } + + void diff --git a/st.suckless.org/patches/ligatures/index.md b/st.suckless.org/patches/ligatures/index.md @@ -27,6 +27,15 @@ Boxdraw Download -------- +**0.9.2**: +* [st-ligatures-0.9.2](0.9.2/st-ligatures-20240427-0.9.2.diff) +* [st-ligatures-scrollback-0.9.2](0.9.2/st-ligatures-scrollback-20240427-0.9.2.diff) +* [st-ligatures-scrollback-ringbuffer-0.9.2](0.9.2/st-ligatures-scrollback-ringbuffer-20240427-0.9.2.diff) +* [st-ligatures-alpha-0.9.2](0.9.2/st-ligatures-alpha-20240427-0.9.2.diff) +* [st-ligatures-alpha-scrollback-0.9.2](0.9.2/st-ligatures-alpha-scrollback-20240427-0.9.2.diff) +* [st-ligatures-alpha-scrollback-ringbuffer-0.9.2](0.9.2/st-ligatures-alpha-scrollback-ringbuffer-20240427-0.9.2.diff) +* [st-ligatures-boxdraw-0.9.2](0.9.2/st-ligatures-boxdraw-20240427-0.9.2.diff) + **0.9**: * [st-ligatures-0.9](0.9/st-ligatures-20240105-0.9.diff) * [st-ligatures-scrollback-0.9](0.9/st-ligatures-scrollback-20240105-0.9.diff)