commit 70432622424f20d7132f5965b89127a720d90a1a
parent f9cf6541ad9b8951f5aa22b311114f12807ffcad
Author: Alexander Rogachev <sorryforbadname@gmail.com>
Date:   Sat,  6 Jan 2024 19:46:05 +0400
[st][patch][ligatures] A couple of fixes:
- Fixed buffer overflow on single cell redraw in case of glyph expansion using some fonts.
- Set cluster level to 1 in HarfBuzz for prevent merging of composite emojis.
Diffstat:
15 files changed, 4459 insertions(+), 4297 deletions(-)
diff --git a/st.suckless.org/patches/ligatures/0.9/st-ligatures-20230105-0.9.diff b/st.suckless.org/patches/ligatures/0.9/st-ligatures-20230105-0.9.diff
@@ -1,609 +0,0 @@
-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: options 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..528c040
---- /dev/null
-+++ b/hb.c
-@@ -0,0 +1,124 @@
-+#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);
-+
-+	/* 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..88de9bd
---- /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..9b97075 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,6 +142,7 @@ 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 xdrawglyph(Glyph, int, 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,119 +1274,148 @@ 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;
- 
--			/*
--			 * 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);
-+			/* Cleanup and get ready for next segment. */
-+			hbcleanup(&shaped);
-+			start = i;
- 
--			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;
-@@ -1517,14 +1567,17 @@ xdrawglyph(Glyph g, int x, int y)
- }
- 
- 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);
- 			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);
-+	}
- }
- 
- void
diff --git a/st.suckless.org/patches/ligatures/0.9/st-ligatures-20240105-0.9.diff b/st.suckless.org/patches/ligatures/0.9/st-ligatures-20240105-0.9.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: options 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/st-ligatures-alpha-20230105-0.9.diff b/st.suckless.org/patches/ligatures/0.9/st-ligatures-alpha-20230105-0.9.diff
@@ -1,613 +0,0 @@
-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: options 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..528c040
---- /dev/null
-+++ b/hb.c
-@@ -0,0 +1,124 @@
-+#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);
-+
-+	/* 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..88de9bd
---- /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..9d84793 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,6 +143,7 @@ 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 xdrawglyph(Glyph, int, 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,121 +1291,151 @@ 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;
- 
--			/*
--			 * 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);
-+			/* Cleanup and get ready for next segment. */
-+			hbcleanup(&shaped);
-+			start = i;
- 
--			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;
- }
- 
-@@ -1534,14 +1585,17 @@ xdrawglyph(Glyph g, int x, int y)
- }
- 
- 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 +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);
- 			i = 0;
- 		}
- 		if (i == 0) {
-@@ -1689,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);
-+	}
- }
- 
- void
diff --git a/st.suckless.org/patches/ligatures/0.9/st-ligatures-alpha-20240105-0.9.diff b/st.suckless.org/patches/ligatures/0.9/st-ligatures-alpha-20240105-0.9.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: options 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/st-ligatures-alpha-scrollback-20230105-0.9.diff b/st.suckless.org/patches/ligatures/0.9/st-ligatures-alpha-scrollback-20230105-0.9.diff
@@ -1,611 +0,0 @@
-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: options 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..528c040
---- /dev/null
-+++ b/hb.c
-@@ -0,0 +1,124 @@
-+#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);
-+
-+	/* 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..88de9bd
---- /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..5d19ed7 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,6 +143,7 @@ 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 xdrawglyph(Glyph, int, 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,119 +1291,148 @@ 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;
- 
--			/*
--			 * 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);
-+			/* Cleanup and get ready for next segment. */
-+			hbcleanup(&shaped);
-+			start = i;
- 
--			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;
-@@ -1534,14 +1584,17 @@ xdrawglyph(Glyph g, int x, int y)
- }
- 
- 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);
- 			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);
-+	}
- }
- 
- void
diff --git a/st.suckless.org/patches/ligatures/0.9/st-ligatures-alpha-scrollback-20240105-0.9.diff b/st.suckless.org/patches/ligatures/0.9/st-ligatures-alpha-scrollback-20240105-0.9.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: options 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/st-ligatures-alpha-scrollback-ringbuffer-20230105-0.9.diff b/st.suckless.org/patches/ligatures/0.9/st-ligatures-alpha-scrollback-ringbuffer-20230105-0.9.diff
@@ -1,611 +0,0 @@
-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: options 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..528c040
---- /dev/null
-+++ b/hb.c
-@@ -0,0 +1,124 @@
-+#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);
-+
-+	/* 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..88de9bd
---- /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 (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 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..5d19ed7 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,6 +143,7 @@ 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 xdrawglyph(Glyph, int, 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,119 +1291,148 @@ 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;
- 
--			/*
--			 * 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);
-+			/* Cleanup and get ready for next segment. */
-+			hbcleanup(&shaped);
-+			start = i;
- 
--			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;
-@@ -1534,14 +1584,17 @@ xdrawglyph(Glyph g, int x, int y)
- }
- 
- 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);
- 			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);
-+	}
- }
- 
- void
diff --git a/st.suckless.org/patches/ligatures/0.9/st-ligatures-alpha-scrollback-ringbuffer-20240105-0.9.diff b/st.suckless.org/patches/ligatures/0.9/st-ligatures-alpha-scrollback-ringbuffer-20240105-0.9.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: options 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/st-ligatures-boxdraw-20230105-0.9.diff b/st.suckless.org/patches/ligatures/0.9/st-ligatures-boxdraw-20230105-0.9.diff
@@ -1,625 +0,0 @@
-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: options 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..528c040
---- /dev/null
-+++ b/hb.c
-@@ -0,0 +1,124 @@
-+#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);
-+
-+	/* 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..88de9bd
---- /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..929a59a 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,6 +142,7 @@ 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 xdrawglyph(Glyph, int, 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,126 +1276,158 @@ 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;
- }
- 
-@@ -1528,14 +1581,17 @@ xdrawglyph(Glyph g, int x, int y)
- }
- 
- 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 +1719,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);
- 			i = 0;
- 		}
- 		if (i == 0) {
-@@ -1683,8 +1737,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);
-+	}
- }
- 
- void
diff --git a/st.suckless.org/patches/ligatures/0.9/st-ligatures-boxdraw-20240105-0.9.diff b/st.suckless.org/patches/ligatures/0.9/st-ligatures-boxdraw-20240105-0.9.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: options 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/st-ligatures-scrollback-20230105-0.9.diff b/st.suckless.org/patches/ligatures/0.9/st-ligatures-scrollback-20230105-0.9.diff
@@ -1,612 +0,0 @@
-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: options 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..528c040
---- /dev/null
-+++ b/hb.c
-@@ -0,0 +1,124 @@
-+#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);
-+
-+	/* 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..88de9bd
---- /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..e66cf0c 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,6 +142,7 @@ 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 xdrawglyph(Glyph, int, 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,121 +1274,151 @@ 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;
- 
--			/*
--			 * 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);
-+			/* Cleanup and get ready for next segment. */
-+			hbcleanup(&shaped);
-+			start = i;
- 
--			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;
- }
- 
-@@ -1517,14 +1568,17 @@ xdrawglyph(Glyph g, int x, int y)
- }
- 
- 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 +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);
- 			i = 0;
- 		}
- 		if (i == 0) {
-@@ -1672,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);
-+	}
- }
- 
- void
diff --git a/st.suckless.org/patches/ligatures/0.9/st-ligatures-scrollback-20240105-0.9.diff b/st.suckless.org/patches/ligatures/0.9/st-ligatures-scrollback-20240105-0.9.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: options 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/st-ligatures-scrollback-ringbuffer-20230105-0.9.diff b/st.suckless.org/patches/ligatures/0.9/st-ligatures-scrollback-ringbuffer-20230105-0.9.diff
@@ -1,609 +0,0 @@
-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: options 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..528c040
---- /dev/null
-+++ b/hb.c
-@@ -0,0 +1,124 @@
-+#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);
-+
-+	/* 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..88de9bd
---- /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..ec3567a 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,6 +144,7 @@ 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 xdrawglyph(Glyph, int, 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,119 +1276,148 @@ 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;
- 
--			/*
--			 * 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);
-+			/* Cleanup and get ready for next segment. */
-+			hbcleanup(&shaped);
-+			start = i;
- 
--			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;
-@@ -1519,14 +1569,17 @@ xdrawglyph(Glyph g, int x, int y)
- }
- 
- 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 +1707,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);
- 			i = 0;
- 		}
- 		if (i == 0) {
-@@ -1674,8 +1725,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);
-+	}
- }
- 
- void
diff --git a/st.suckless.org/patches/ligatures/0.9/st-ligatures-scrollback-ringbuffer-20240105-0.9.diff b/st.suckless.org/patches/ligatures/0.9/st-ligatures-scrollback-ringbuffer-20240105-0.9.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: options 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
@@ -28,13 +28,13 @@ Boxdraw
 Download
 --------
 **0.9**:
-* [st-ligatures-0.9](0.9/st-ligatures-20230105-0.9.diff)
-* [st-ligatures-scrollback-0.9](0.9/st-ligatures-scrollback-20230105-0.9.diff)
-* [st-ligatures-scrollback-ringbuffer-0.9](0.9/st-ligatures-scrollback-ringbuffer-20230105-0.9.diff)
-* [st-ligatures-alpha-0.9](0.9/st-ligatures-alpha-20230105-0.9.diff)
-* [st-ligatures-alpha-scrollback-0.9](0.9/st-ligatures-alpha-scrollback-20230105-0.9.diff)
-* [st-ligatures-alpha-scrollback-ringbuffer-0.9](0.9/st-ligatures-alpha-scrollback-ringbuffer-20230105-0.9.diff)
-* [st-ligatures-boxdraw-0.9](0.9/st-ligatures-boxdraw-20230105-0.9.diff)
+* [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)
+* [st-ligatures-scrollback-ringbuffer-0.9](0.9/st-ligatures-scrollback-ringbuffer-20240105-0.9.diff)
+* [st-ligatures-alpha-0.9](0.9/st-ligatures-alpha-20240105-0.9.diff)
+* [st-ligatures-alpha-scrollback-0.9](0.9/st-ligatures-alpha-scrollback-20240105-0.9.diff)
+* [st-ligatures-alpha-scrollback-ringbuffer-0.9](0.9/st-ligatures-alpha-scrollback-ringbuffer-20240105-0.9.diff)
+* [st-ligatures-boxdraw-0.9](0.9/st-ligatures-boxdraw-20240105-0.9.diff)
 
 **0.8.4**:
 * [st-ligatures-0.8.4](0.8.4/st-ligatures-20210824-0.8.4.diff)