commit 77456c4698f33511b0652c9dfd6f2904b4797584
parent b034a2ce4801188b3af190036eb66a13e24cbffc
Author: Alexander Rogachev <sorryforbadname@gmail.com>
Date: Fri, 10 Jan 2025 01:55:48 +0400
[st][patches][ligatures]
Some code cleanup and refactoring.
Diffstat:
15 files changed, 4569 insertions(+), 4459 deletions(-)
diff --git a/st.suckless.org/patches/ligatures/0.9.2/st-ligatures-20240427-0.9.2.diff b/st.suckless.org/patches/ligatures/0.9.2/st-ligatures-20240427-0.9.2.diff
@@ -1,633 +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: st
-@@ -22,7 +22,8 @@ config.h:
- $(CC) $(STCFLAGS) -c $<
-
- st.o: config.h st.h win.h
--x.o: arg.h config.h st.h win.h
-+x.o: arg.h config.h st.h win.h hb.h
-+hb.o: st.h
-
- $(OBJ): config.h config.mk
-
-diff --git a/config.mk b/config.mk
-index 1e306f8..3e13e53 100644
---- a/config.mk
-+++ b/config.mk
-@@ -15,10 +15,12 @@ PKG_CONFIG = pkg-config
- # includes and libs
- INCS = -I$(X11INC) \
- `$(PKG_CONFIG) --cflags fontconfig` \
-- `$(PKG_CONFIG) --cflags freetype2`
-+ `$(PKG_CONFIG) --cflags freetype2` \
-+ `$(PKG_CONFIG) --cflags harfbuzz`
- LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft \
- `$(PKG_CONFIG) --libs fontconfig` \
-- `$(PKG_CONFIG) --libs freetype2`
-+ `$(PKG_CONFIG) --libs freetype2` \
-+ `$(PKG_CONFIG) --libs harfbuzz`
-
- # flags
- STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600
-diff --git a/hb.c b/hb.c
-new file mode 100644
-index 0000000..99412c8
---- /dev/null
-+++ b/hb.c
-@@ -0,0 +1,125 @@
-+#include <stdlib.h>
-+#include <stdio.h>
-+#include <math.h>
-+#include <X11/Xft/Xft.h>
-+#include <X11/cursorfont.h>
-+#include <hb.h>
-+#include <hb-ft.h>
-+
-+#include "st.h"
-+#include "hb.h"
-+
-+#define FEATURE(c1,c2,c3,c4) { .tag = HB_TAG(c1,c2,c3,c4), .value = 1, .start = HB_FEATURE_GLOBAL_START, .end = HB_FEATURE_GLOBAL_END }
-+#define BUFFER_STEP 256
-+
-+hb_font_t *hbfindfont(XftFont *match);
-+
-+typedef struct {
-+ XftFont *match;
-+ hb_font_t *font;
-+} HbFontMatch;
-+
-+typedef struct {
-+ size_t capacity;
-+ HbFontMatch *fonts;
-+} HbFontCache;
-+
-+static HbFontCache hbfontcache = { 0, NULL };
-+
-+typedef struct {
-+ size_t capacity;
-+ Rune *runes;
-+} RuneBuffer;
-+
-+static RuneBuffer hbrunebuffer = { 0, NULL };
-+
-+/*
-+ * Poplulate the array with a list of font features, wrapped in FEATURE macro,
-+ * e. g.
-+ * FEATURE('c', 'a', 'l', 't'), FEATURE('d', 'l', 'i', 'g')
-+ */
-+hb_feature_t features[] = { };
-+
-+void
-+hbunloadfonts()
-+{
-+ for (int i = 0; i < hbfontcache.capacity; i++) {
-+ hb_font_destroy(hbfontcache.fonts[i].font);
-+ XftUnlockFace(hbfontcache.fonts[i].match);
-+ }
-+
-+ if (hbfontcache.fonts != NULL) {
-+ free(hbfontcache.fonts);
-+ hbfontcache.fonts = NULL;
-+ }
-+ hbfontcache.capacity = 0;
-+}
-+
-+hb_font_t *
-+hbfindfont(XftFont *match)
-+{
-+ for (int i = 0; i < hbfontcache.capacity; i++) {
-+ if (hbfontcache.fonts[i].match == match)
-+ return hbfontcache.fonts[i].font;
-+ }
-+
-+ /* Font not found in cache, caching it now. */
-+ hbfontcache.fonts = realloc(hbfontcache.fonts, sizeof(HbFontMatch) * (hbfontcache.capacity + 1));
-+ FT_Face face = XftLockFace(match);
-+ hb_font_t *font = hb_ft_font_create(face, NULL);
-+ if (font == NULL)
-+ die("Failed to load Harfbuzz font.");
-+
-+ hbfontcache.fonts[hbfontcache.capacity].match = match;
-+ hbfontcache.fonts[hbfontcache.capacity].font = font;
-+ hbfontcache.capacity += 1;
-+
-+ return font;
-+}
-+
-+void hbtransform(HbTransformData *data, XftFont *xfont, const Glyph *glyphs, int start, int length) {
-+ ushort mode = USHRT_MAX;
-+ unsigned int glyph_count;
-+ int rune_idx, glyph_idx, end = start + length;
-+
-+ hb_font_t *font = hbfindfont(xfont);
-+ if (font == NULL)
-+ return;
-+
-+ hb_buffer_t *buffer = hb_buffer_create();
-+ hb_buffer_set_direction(buffer, HB_DIRECTION_LTR);
-+ hb_buffer_set_cluster_level(buffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS);
-+
-+ /* Resize the buffer if required length is larger. */
-+ if (hbrunebuffer.capacity < length) {
-+ hbrunebuffer.capacity = (length / BUFFER_STEP + 1) * BUFFER_STEP;
-+ hbrunebuffer.runes = realloc(hbrunebuffer.runes, hbrunebuffer.capacity * sizeof(Rune));
-+ }
-+
-+ /* Fill buffer with codepoints. */
-+ for (rune_idx = 0, glyph_idx = start; glyph_idx < end; glyph_idx++, rune_idx++) {
-+ hbrunebuffer.runes[rune_idx] = glyphs[glyph_idx].u;
-+ mode = glyphs[glyph_idx].mode;
-+ if (mode & ATTR_WDUMMY)
-+ hbrunebuffer.runes[rune_idx] = 0x0020;
-+ }
-+ hb_buffer_add_codepoints(buffer, hbrunebuffer.runes, length, 0, length);
-+
-+ /* Shape the segment. */
-+ hb_shape(font, buffer, features, sizeof(features)/sizeof(hb_feature_t));
-+
-+ /* Get new glyph info. */
-+ hb_glyph_info_t *info = hb_buffer_get_glyph_infos(buffer, &glyph_count);
-+ hb_glyph_position_t *pos = hb_buffer_get_glyph_positions(buffer, &glyph_count);
-+
-+ /* Fill the output. */
-+ data->buffer = buffer;
-+ data->glyphs = info;
-+ data->positions = pos;
-+ data->count = glyph_count;
-+}
-+
-+void hbcleanup(HbTransformData *data) {
-+ hb_buffer_destroy(data->buffer);
-+ memset(data, 0, sizeof(HbTransformData));
-+}
-diff --git a/hb.h b/hb.h
-new file mode 100644
-index 0000000..3b0ef44
---- /dev/null
-+++ b/hb.h
-@@ -0,0 +1,14 @@
-+#include <X11/Xft/Xft.h>
-+#include <hb.h>
-+#include <hb-ft.h>
-+
-+typedef struct {
-+ hb_buffer_t *buffer;
-+ hb_glyph_info_t *glyphs;
-+ hb_glyph_position_t *positions;
-+ unsigned int count;
-+} HbTransformData;
-+
-+void hbunloadfonts();
-+void hbtransform(HbTransformData *, XftFont *, const Glyph *, int, int);
-+void hbcleanup(HbTransformData *);
-diff --git a/st.c b/st.c
-index 62def59..041c6d8 100644
---- a/st.c
-+++ b/st.c
-@@ -2640,7 +2640,8 @@ draw(void)
-
- drawregion(0, 0, term.col, term.row);
- xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
-- term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
-+ term.ocx, term.ocy, term.line[term.ocy][term.ocx],
-+ term.line[term.ocy], term.col);
- term.ocx = cx;
- term.ocy = term.c.y;
- xfinishdraw();
-diff --git a/st.h b/st.h
-index fd3b0d8..142fdfe 100644
---- a/st.h
-+++ b/st.h
-@@ -11,7 +11,8 @@
- #define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d))
- #define DEFAULT(a, b) (a) = (a) ? (a) : (b)
- #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)
--#define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \
-+#define ATTRCMP(a, b) (((a).mode & (~ATTR_WRAP)) != ((b).mode & (~ATTR_WRAP)) || \
-+ (a).fg != (b).fg || \
- (a).bg != (b).bg)
- #define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \
- (t1.tv_nsec-t2.tv_nsec)/1E6)
-diff --git a/win.h b/win.h
-index 6de960d..94679e4 100644
---- a/win.h
-+++ b/win.h
-@@ -25,7 +25,7 @@ enum win_mode {
-
- void xbell(void);
- void xclipcopy(void);
--void xdrawcursor(int, int, Glyph, int, int, Glyph);
-+void xdrawcursor(int, int, Glyph, int, int, Glyph, Line, int);
- void xdrawline(Line, int, int, int);
- void xfinishdraw(void);
- void xloadcols(void);
-diff --git a/x.c b/x.c
-index 2a3bd38..66605ae 100644
---- a/x.c
-+++ b/x.c
-@@ -19,6 +19,7 @@ char *argv0;
- #include "arg.h"
- #include "st.h"
- #include "win.h"
-+#include "hb.h"
-
- /* types used in config.h */
- typedef struct {
-@@ -141,8 +142,9 @@ typedef struct {
- } DC;
-
- static inline ushort sixd_to_16bit(int);
-+static void xresetfontsettings(ushort mode, Font **font, int *frcflags);
- static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int);
--static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int);
-+static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int, int);
- static void xdrawglyph(Glyph, int, int);
- static void xclear(int, int, int, int);
- static int xgeommasktogravity(int);
-@@ -757,7 +759,7 @@ xresize(int col, int row)
- xclear(0, 0, win.w, win.h);
-
- /* resize to new width */
-- xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec));
-+ xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec) * 4);
- }
-
- ushort
-@@ -1062,6 +1064,9 @@ xunloadfont(Font *f)
- void
- xunloadfonts(void)
- {
-+ /* Clear Harfbuzz font cache. */
-+ hbunloadfonts();
-+
- /* Free the loaded fonts in the font cache. */
- while (frclen > 0)
- XftFontClose(xw.dpy, frc[--frclen].font);
-@@ -1185,7 +1190,7 @@ xinit(int cols, int rows)
- XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h);
-
- /* font spec buffer */
-- xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec));
-+ xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec) * 4);
-
- /* Xft rendering context */
- xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap);
-@@ -1239,6 +1244,22 @@ xinit(int cols, int rows)
- xsel.xtarget = XA_STRING;
- }
-
-+void
-+xresetfontsettings(ushort mode, Font **font, int *frcflags)
-+{
-+ *font = &dc.font;
-+ if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) {
-+ *font = &dc.ibfont;
-+ *frcflags = FRC_ITALICBOLD;
-+ } else if (mode & ATTR_ITALIC) {
-+ *font = &dc.ifont;
-+ *frcflags = FRC_ITALIC;
-+ } else if (mode & ATTR_BOLD) {
-+ *font = &dc.bfont;
-+ *frcflags = FRC_BOLD;
-+ }
-+}
-+
- int
- xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y)
- {
-@@ -1253,128 +1274,156 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
- FcPattern *fcpattern, *fontpattern;
- FcFontSet *fcsets[] = { NULL };
- FcCharSet *fccharset;
-- int i, f, numspecs = 0;
-+ int i, f, length = 0, start = 0, numspecs = 0;
-+ float cluster_xp = xp, cluster_yp = yp;
-+ HbTransformData shaped = { 0 };
-+
-+ /* Initial values. */
-+ mode = prevmode = glyphs[0].mode & ~ATTR_WRAP;
-+ xresetfontsettings(mode, &font, &frcflags);
-
- for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) {
-- /* Fetch rune and mode for current glyph. */
-- rune = glyphs[i].u;
-- mode = glyphs[i].mode;
-+ mode = glyphs[i].mode & ~ATTR_WRAP;
-
- /* Skip dummy wide-character spacing. */
-- if (mode == ATTR_WDUMMY)
-+ if (mode & ATTR_WDUMMY && i < (len - 1))
- continue;
-
-- /* Determine font for glyph if different from previous glyph. */
-- if (prevmode != mode) {
-- prevmode = mode;
-- font = &dc.font;
-- frcflags = FRC_NORMAL;
-- runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f);
-- if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) {
-- font = &dc.ibfont;
-- frcflags = FRC_ITALICBOLD;
-- } else if (mode & ATTR_ITALIC) {
-- font = &dc.ifont;
-- frcflags = FRC_ITALIC;
-- } else if (mode & ATTR_BOLD) {
-- font = &dc.bfont;
-- frcflags = FRC_BOLD;
-+ if (
-+ prevmode != mode
-+ || ATTRCMP(glyphs[start], glyphs[i])
-+ || selected(x + i, y) != selected(x + start, y)
-+ || i == (len - 1)
-+ ) {
-+ /* Handle 1-character wide segments and end of line */
-+ length = i - start;
-+ if (i == start) {
-+ length = 1;
-+ } else if (i == (len - 1)) {
-+ length = (i - start + 1);
- }
-- yp = winy + font->ascent;
-- }
-
-- /* Lookup character index with default font. */
-- glyphidx = XftCharIndex(xw.dpy, font->match, rune);
-- if (glyphidx) {
-- specs[numspecs].font = font->match;
-- specs[numspecs].glyph = glyphidx;
-- specs[numspecs].x = (short)xp;
-- specs[numspecs].y = (short)yp;
-- xp += runewidth;
-- numspecs++;
-- continue;
-- }
--
-- /* Fallback on font cache, search the font cache for match. */
-- for (f = 0; f < frclen; f++) {
-- glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune);
-- /* Everything correct. */
-- if (glyphidx && frc[f].flags == frcflags)
-- break;
-- /* We got a default font for a not found glyph. */
-- if (!glyphidx && frc[f].flags == frcflags
-- && frc[f].unicodep == rune) {
-- break;
-+ /* Shape the segment. */
-+ hbtransform(&shaped, font->match, glyphs, start, length);
-+ runewidth = win.cw * ((glyphs[start].mode & ATTR_WIDE) ? 2.0f : 1.0f);
-+ cluster_xp = xp; cluster_yp = yp;
-+ for (int code_idx = 0; code_idx < shaped.count; code_idx++) {
-+ int idx = shaped.glyphs[code_idx].cluster;
-+
-+ if (glyphs[start + idx].mode & ATTR_WDUMMY)
-+ continue;
-+
-+ /* Advance the drawing cursor if we've moved to a new cluster */
-+ if (code_idx > 0 && idx != shaped.glyphs[code_idx - 1].cluster) {
-+ xp += runewidth;
-+ cluster_xp = xp;
-+ cluster_yp = yp;
-+ runewidth = win.cw * ((glyphs[start + idx].mode & ATTR_WIDE) ? 2.0f : 1.0f);
-+ }
-+
-+ if (shaped.glyphs[code_idx].codepoint != 0) {
-+ /* If symbol is found, put it into the specs. */
-+ specs[numspecs].font = font->match;
-+ specs[numspecs].glyph = shaped.glyphs[code_idx].codepoint;
-+ specs[numspecs].x = cluster_xp + (short)(shaped.positions[code_idx].x_offset / 64.);
-+ specs[numspecs].y = cluster_yp - (short)(shaped.positions[code_idx].y_offset / 64.);
-+ cluster_xp += shaped.positions[code_idx].x_advance / 64.;
-+ cluster_yp += shaped.positions[code_idx].y_advance / 64.;
-+ numspecs++;
-+ } else {
-+ /* If it's not found, try to fetch it through the font cache. */
-+ rune = glyphs[start + idx].u;
-+ for (f = 0; f < frclen; f++) {
-+ glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune);
-+ /* Everything correct. */
-+ if (glyphidx && frc[f].flags == frcflags)
-+ break;
-+ /* We got a default font for a not found glyph. */
-+ if (!glyphidx && frc[f].flags == frcflags
-+ && frc[f].unicodep == rune) {
-+ break;
-+ }
-+ }
-+
-+ /* Nothing was found. Use fontconfig to find matching font. */
-+ if (f >= frclen) {
-+ if (!font->set)
-+ font->set = FcFontSort(0, font->pattern,
-+ 1, 0, &fcres);
-+ fcsets[0] = font->set;
-+
-+ /*
-+ * Nothing was found in the cache. Now use
-+ * some dozen of Fontconfig calls to get the
-+ * font for one single character.
-+ *
-+ * Xft and fontconfig are design failures.
-+ */
-+ fcpattern = FcPatternDuplicate(font->pattern);
-+ fccharset = FcCharSetCreate();
-+
-+ FcCharSetAddChar(fccharset, rune);
-+ FcPatternAddCharSet(fcpattern, FC_CHARSET,
-+ fccharset);
-+ FcPatternAddBool(fcpattern, FC_SCALABLE, 1);
-+
-+ FcConfigSubstitute(0, fcpattern,
-+ FcMatchPattern);
-+ FcDefaultSubstitute(fcpattern);
-+
-+ fontpattern = FcFontSetMatch(0, fcsets, 1,
-+ fcpattern, &fcres);
-+
-+ /* Allocate memory for the new cache entry. */
-+ if (frclen >= frccap) {
-+ frccap += 16;
-+ frc = xrealloc(frc, frccap * sizeof(Fontcache));
-+ }
-+
-+ frc[frclen].font = XftFontOpenPattern(xw.dpy,
-+ fontpattern);
-+ if (!frc[frclen].font)
-+ die("XftFontOpenPattern failed seeking fallback font: %s\n",
-+ strerror(errno));
-+ frc[frclen].flags = frcflags;
-+ frc[frclen].unicodep = rune;
-+
-+ glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune);
-+
-+ f = frclen;
-+ frclen++;
-+
-+ FcPatternDestroy(fcpattern);
-+ FcCharSetDestroy(fccharset);
-+ }
-+
-+ specs[numspecs].font = frc[f].font;
-+ specs[numspecs].glyph = glyphidx;
-+ specs[numspecs].x = (short)xp;
-+ specs[numspecs].y = (short)yp;
-+ numspecs++;
-+ }
- }
-- }
-
-- /* Nothing was found. Use fontconfig to find matching font. */
-- if (f >= frclen) {
-- if (!font->set)
-- font->set = FcFontSort(0, font->pattern,
-- 1, 0, &fcres);
-- fcsets[0] = font->set;
-+ /* Cleanup and get ready for next segment. */
-+ hbcleanup(&shaped);
-+ start = i;
-
-- /*
-- * Nothing was found in the cache. Now use
-- * some dozen of Fontconfig calls to get the
-- * font for one single character.
-- *
-- * Xft and fontconfig are design failures.
-- */
-- fcpattern = FcPatternDuplicate(font->pattern);
-- fccharset = FcCharSetCreate();
--
-- FcCharSetAddChar(fccharset, rune);
-- FcPatternAddCharSet(fcpattern, FC_CHARSET,
-- fccharset);
-- FcPatternAddBool(fcpattern, FC_SCALABLE, 1);
--
-- FcConfigSubstitute(0, fcpattern,
-- FcMatchPattern);
-- FcDefaultSubstitute(fcpattern);
--
-- fontpattern = FcFontSetMatch(0, fcsets, 1,
-- fcpattern, &fcres);
--
-- /* Allocate memory for the new cache entry. */
-- if (frclen >= frccap) {
-- frccap += 16;
-- frc = xrealloc(frc, frccap * sizeof(Fontcache));
-+ /* Determine font for glyph if different from previous glyph. */
-+ if (prevmode != mode) {
-+ prevmode = mode;
-+ xresetfontsettings(mode, &font, &frcflags);
-+ yp = winy + font->ascent;
- }
--
-- frc[frclen].font = XftFontOpenPattern(xw.dpy,
-- fontpattern);
-- if (!frc[frclen].font)
-- die("XftFontOpenPattern failed seeking fallback font: %s\n",
-- strerror(errno));
-- frc[frclen].flags = frcflags;
-- frc[frclen].unicodep = rune;
--
-- glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune);
--
-- f = frclen;
-- frclen++;
--
-- FcPatternDestroy(fcpattern);
-- FcCharSetDestroy(fccharset);
- }
--
-- specs[numspecs].font = frc[f].font;
-- specs[numspecs].glyph = glyphidx;
-- specs[numspecs].x = (short)xp;
-- specs[numspecs].y = (short)yp;
-- xp += runewidth;
-- numspecs++;
- }
-
- return numspecs;
- }
-
- void
--xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y)
-+xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y, int charlen)
- {
-- int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1);
- int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch,
- width = charlen * win.cw;
- Color *fg, *bg, *temp, revfg, revbg, truefg, truebg;
-@@ -1510,21 +1559,24 @@ void
- xdrawglyph(Glyph g, int x, int y)
- {
- int numspecs;
-- XftGlyphFontSpec spec;
-+ XftGlyphFontSpec *specs = xw.specbuf;
-
-- numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y);
-- xdrawglyphfontspecs(&spec, g, numspecs, x, y);
-+ numspecs = xmakeglyphfontspecs(specs, &g, 1, x, y);
-+ xdrawglyphfontspecs(specs, g, numspecs, x, y, (g.mode & ATTR_WIDE) ? 2 : 1);
- }
-
- void
--xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
-+xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og, Line line, int len)
- {
- Color drawcol;
-
- /* remove the old cursor */
- if (selected(ox, oy))
- og.mode ^= ATTR_REVERSE;
-- xdrawglyph(og, ox, oy);
-+
-+ /* Redraw the line where cursor was previously.
-+ * It will restore the ligatures broken by the cursor. */
-+ xdrawline(line, 0, oy, len);
-
- if (IS_SET(MODE_HIDE))
- return;
-@@ -1652,18 +1704,16 @@ xdrawline(Line line, int x1, int y1, int x2)
- Glyph base, new;
- XftGlyphFontSpec *specs = xw.specbuf;
-
-- numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1);
- i = ox = 0;
-- for (x = x1; x < x2 && i < numspecs; x++) {
-+ for (x = x1; x < x2; x++) {
- new = line[x];
- if (new.mode == ATTR_WDUMMY)
- continue;
- if (selected(x, y1))
- new.mode ^= ATTR_REVERSE;
-- if (i > 0 && ATTRCMP(base, new)) {
-- xdrawglyphfontspecs(specs, base, i, ox, y1);
-- specs += i;
-- numspecs -= i;
-+ if ((i > 0) && ATTRCMP(base, new)) {
-+ numspecs = xmakeglyphfontspecs(specs, &line[ox], x - ox, ox, y1);
-+ xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x - ox);
- i = 0;
- }
- if (i == 0) {
-@@ -1672,8 +1722,10 @@ xdrawline(Line line, int x1, int y1, int x2)
- }
- i++;
- }
-- if (i > 0)
-- xdrawglyphfontspecs(specs, base, i, ox, y1);
-+ if (i > 0) {
-+ numspecs = xmakeglyphfontspecs(specs, &line[ox], x2 - ox, ox, y1);
-+ xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x2 - ox);
-+ }
- }
-
- void
diff --git a/st.suckless.org/patches/ligatures/0.9.2/st-ligatures-20241226-0.9.2.diff b/st.suckless.org/patches/ligatures/0.9.2/st-ligatures-20241226-0.9.2.diff
@@ -0,0 +1,650 @@
+diff --git a/Makefile b/Makefile
+index 15db421..dfcea0f 100644
+--- a/Makefile
++++ b/Makefile
+@@ -3,9 +3,9 @@
+ .POSIX:
+
+ include config.mk
+
+-SRC = st.c x.c
++SRC = st.c x.c hb.c
+ OBJ = $(SRC:.c=.o)
+
+ all: st
+
+@@ -15,9 +15,10 @@ config.h:
+ .c.o:
+ $(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
+
+ st: $(OBJ)
+diff --git a/config.mk b/config.mk
+index fdc29a7..6833b3b 100644
+--- a/config.mk
++++ b/config.mk
+@@ -14,12 +14,14 @@ 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
+ STCFLAGS = $(INCS) $(STCPPFLAGS) $(CPPFLAGS) $(CFLAGS)
+@@ -28,9 +30,10 @@ STLDFLAGS = $(LIBS) $(LDFLAGS)
+ # OpenBSD:
+ #CPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 -D_BSD_SOURCE
+ #LIBS = -L$(X11LIB) -lm -lX11 -lutil -lXft \
+ # `$(PKG_CONFIG) --libs fontconfig` \
+-# `$(PKG_CONFIG) --libs freetype2`
++# `$(PKG_CONFIG) --libs freetype2` \
++# `$(PKG_CONFIG) --libs harfbuzz`
+ #MANPREFIX = ${PREFIX}/man
+
+ # compiler and linker
+ # CC = c99
+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 b9f66e7..da33a85 100644
+--- a/st.c
++++ b/st.c
+@@ -2658,9 +2658,10 @@ draw(void)
+ cx--;
+
+ 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();
+ if (ocx != term.ocx || ocy != term.ocy)
+diff --git a/st.h b/st.h
+index fd3b0d8..142fdfe 100644
+--- a/st.h
++++ b/st.h
+@@ -10,9 +10,10 @@
+ #define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b))
+ #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)
+ #define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit)))
+diff --git a/win.h b/win.h
+index 6de960d..94679e4 100644
+--- a/win.h
++++ b/win.h
+@@ -24,9 +24,9 @@ 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);
+ int xsetcolorname(int, const char *);
+diff --git a/x.c b/x.c
+index bd23686..2bf3b72 100644
+--- a/x.c
++++ b/x.c
+@@ -18,8 +18,9 @@
+ char *argv0;
+ #include "arg.h"
+ #include "st.h"
+ #include "win.h"
++#include "hb.h"
+
+ /* types used in config.h */
+ typedef struct {
+ uint mod;
+@@ -140,10 +141,11 @@ typedef struct {
+ GC gc;
+ } 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);
+ static int ximopen(Display *);
+@@ -756,9 +758,9 @@ xresize(int col, int row)
+ XftDrawChange(xw.draw, xw.buf);
+ 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
+ sixd_to_16bit(int x)
+@@ -1061,8 +1063,11 @@ 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);
+
+@@ -1184,9 +1189,9 @@ xinit(int cols, int rows)
+ XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel);
+ 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);
+
+@@ -1238,144 +1243,155 @@ xinit(int cols, int rows)
+ if (xsel.xtarget == None)
+ 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)
+ {
+ float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp;
+- ushort mode, prevmode = USHRT_MAX;
++ ushort mode = glyphs[0].mode & ~ATTR_WRAP;
+ Font *font = &dc.font;
+ int frcflags = FRC_NORMAL;
+- float runewidth = win.cw;
++ float runewidth = win.cw * ((glyphs[0].mode & ATTR_WIDE) ? 2.0f : 1.0f);
+ Rune rune;
+ FT_UInt glyphidx;
+ FcResult fcres;
+ FcPattern *fcpattern, *fontpattern;
+ FcFontSet *fcsets[] = { NULL };
+ FcCharSet *fccharset;
+- int i, f, numspecs = 0;
++ int f, code_idx, numspecs = 0;
++ float cluster_xp = xp, cluster_yp = yp;
++ HbTransformData shaped = { 0 };
+
+- 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;
++ /* Initial values. */
++ xresetfontsettings(mode, &font, &frcflags);
+
+- /* Skip dummy wide-character spacing. */
+- if (mode == ATTR_WDUMMY)
++ /* Shape the segment. */
++ hbtransform(&shaped, font->match, glyphs, 0, len);
++ xp = winx; yp = winy + font->ascent;
++ cluster_xp = xp; cluster_yp = yp;
++
++ for (code_idx = 0; code_idx < shaped.count; code_idx++) {
++ int idx = shaped.glyphs[code_idx].cluster;
++
++ if (glyphs[idx].mode & ATTR_WDUMMY)
+ 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;
+- }
+- yp = winy + font->ascent;
++ /* 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;
+ }
+
+- /* Lookup character index with default font. */
+- glyphidx = XftCharIndex(xw.dpy, font->match, rune);
+- if (glyphidx) {
++ if (shaped.glyphs[code_idx].codepoint != 0) {
++ /* If symbol is found, put it into the specs. */
+ specs[numspecs].font = font->match;
+- specs[numspecs].glyph = glyphidx;
+- specs[numspecs].x = (short)xp;
+- specs[numspecs].y = (short)yp;
+- xp += runewidth;
++ 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++;
+- 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;
++ } else {
++ /* If it's not found, try to fetch it through the font cache. */
++ rune = glyphs[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));
++ /* 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);
+ }
+
+- 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++;
+ }
+-
+- specs[numspecs].font = frc[f].font;
+- specs[numspecs].glyph = glyphidx;
+- specs[numspecs].x = (short)xp;
+- specs[numspecs].y = (short)yp;
+- xp += runewidth;
+- numspecs++;
+ }
+
++ /* Cleanup and get ready for next segment. */
++ 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;
+ XRenderColor colfg, colbg;
+@@ -1509,23 +1525,26 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
+ 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;
+
+@@ -1657,30 +1676,30 @@ xdrawline(Line line, int x1, int y1, int x2)
+ int i, x, ox, numspecs;
+ 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) {
+ ox = x;
+ base = new;
+ }
+ 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
+ xfinishdraw(void)
diff --git a/st.suckless.org/patches/ligatures/0.9.2/st-ligatures-alpha-20240427-0.9.2.diff b/st.suckless.org/patches/ligatures/0.9.2/st-ligatures-alpha-20240427-0.9.2.diff
@@ -1,635 +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: st
-@@ -22,7 +22,8 @@ config.h:
- $(CC) $(STCFLAGS) -c $<
-
- st.o: config.h st.h win.h
--x.o: arg.h config.h st.h win.h
-+x.o: arg.h config.h st.h win.h hb.h
-+hb.o: st.h
-
- $(OBJ): config.h config.mk
-
-diff --git a/config.mk b/config.mk
-index 47c615e..d7439a3 100644
---- a/config.mk
-+++ b/config.mk
-@@ -15,10 +15,12 @@ PKG_CONFIG = pkg-config
- # includes and libs
- INCS = -I$(X11INC) \
- `$(PKG_CONFIG) --cflags fontconfig` \
-- `$(PKG_CONFIG) --cflags freetype2`
--LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft -lXrender\
-+ `$(PKG_CONFIG) --cflags freetype2` \
-+ `$(PKG_CONFIG) --cflags harfbuzz`
-+LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft -lXrender \
- `$(PKG_CONFIG) --libs fontconfig` \
-- `$(PKG_CONFIG) --libs freetype2`
-+ `$(PKG_CONFIG) --libs freetype2` \
-+ `$(PKG_CONFIG) --libs harfbuzz`
-
- # flags
- STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600
-diff --git a/hb.c b/hb.c
-new file mode 100644
-index 0000000..58d534b
---- /dev/null
-+++ b/hb.c
-@@ -0,0 +1,125 @@
-+#include <stdlib.h>
-+#include <stdio.h>
-+#include <math.h>
-+#include <X11/Xft/Xft.h>
-+#include <X11/cursorfont.h>
-+#include <hb.h>
-+#include <hb-ft.h>
-+
-+#include "st.h"
-+#include "hb.h"
-+
-+#define FEATURE(c1,c2,c3,c4) { .tag = HB_TAG(c1,c2,c3,c4), .value = 1, .start = HB_FEATURE_GLOBAL_START, .end = HB_FEATURE_GLOBAL_END }
-+#define BUFFER_STEP 256
-+
-+hb_font_t *hbfindfont(XftFont *match);
-+
-+typedef struct {
-+ XftFont *match;
-+ hb_font_t *font;
-+} HbFontMatch;
-+
-+typedef struct {
-+ size_t capacity;
-+ HbFontMatch *fonts;
-+} HbFontCache;
-+
-+static HbFontCache hbfontcache = { 0, NULL };
-+
-+typedef struct {
-+ size_t capacity;
-+ Rune *runes;
-+} RuneBuffer;
-+
-+static RuneBuffer hbrunebuffer = { 0, NULL };
-+
-+/*
-+ * Poplulate the array with a list of font features, wrapped in FEATURE macro,
-+ * e. g.
-+ * FEATURE('c', 'a', 'l', 't'), FEATURE('d', 'l', 'i', 'g')
-+ */
-+hb_feature_t features[] = { };
-+
-+void
-+hbunloadfonts()
-+{
-+ for (int i = 0; i < hbfontcache.capacity; i++) {
-+ hb_font_destroy(hbfontcache.fonts[i].font);
-+ XftUnlockFace(hbfontcache.fonts[i].match);
-+ }
-+
-+ if (hbfontcache.fonts != NULL) {
-+ free(hbfontcache.fonts);
-+ hbfontcache.fonts = NULL;
-+ }
-+ hbfontcache.capacity = 0;
-+}
-+
-+hb_font_t *
-+hbfindfont(XftFont *match)
-+{
-+ for (int i = 0; i < hbfontcache.capacity; i++) {
-+ if (hbfontcache.fonts[i].match == match)
-+ return hbfontcache.fonts[i].font;
-+ }
-+
-+ /* Font not found in cache, caching it now. */
-+ hbfontcache.fonts = realloc(hbfontcache.fonts, sizeof(HbFontMatch) * (hbfontcache.capacity + 1));
-+ FT_Face face = XftLockFace(match);
-+ hb_font_t *font = hb_ft_font_create(face, NULL);
-+ if (font == NULL)
-+ die("Failed to load Harfbuzz font.");
-+
-+ hbfontcache.fonts[hbfontcache.capacity].match = match;
-+ hbfontcache.fonts[hbfontcache.capacity].font = font;
-+ hbfontcache.capacity += 1;
-+
-+ return font;
-+}
-+
-+void hbtransform(HbTransformData *data, XftFont *xfont, const Glyph *glyphs, int start, int length) {
-+ ushort mode = USHRT_MAX;
-+ unsigned int glyph_count;
-+ int rune_idx, glyph_idx, end = start + length;
-+
-+ hb_font_t *font = hbfindfont(xfont);
-+ if (font == NULL)
-+ return;
-+
-+ hb_buffer_t *buffer = hb_buffer_create();
-+ hb_buffer_set_direction(buffer, HB_DIRECTION_LTR);
-+ hb_buffer_set_cluster_level(buffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS);
-+
-+ /* Resize the buffer if required length is larger. */
-+ if (hbrunebuffer.capacity < length) {
-+ hbrunebuffer.capacity = (length / BUFFER_STEP + 1) * BUFFER_STEP;
-+ hbrunebuffer.runes = realloc(hbrunebuffer.runes, hbrunebuffer.capacity * sizeof(Rune));
-+ }
-+
-+ /* Fill buffer with codepoints. */
-+ for (rune_idx = 0, glyph_idx = start; glyph_idx < end; glyph_idx++, rune_idx++) {
-+ hbrunebuffer.runes[rune_idx] = glyphs[glyph_idx].u;
-+ mode = glyphs[glyph_idx].mode;
-+ if (mode & ATTR_WDUMMY)
-+ hbrunebuffer.runes[rune_idx] = 0x0020;
-+ }
-+ hb_buffer_add_codepoints(buffer, hbrunebuffer.runes, length, 0, length);
-+
-+ /* Shape the segment. */
-+ hb_shape(font, buffer, features, sizeof(features)/sizeof(hb_feature_t));
-+
-+ /* Get new glyph info. */
-+ hb_glyph_info_t *info = hb_buffer_get_glyph_infos(buffer, &glyph_count);
-+ hb_glyph_position_t *pos = hb_buffer_get_glyph_positions(buffer, &glyph_count);
-+
-+ /* Fill the output. */
-+ data->buffer = buffer;
-+ data->glyphs = info;
-+ data->positions = pos;
-+ data->count = glyph_count;
-+}
-+
-+void hbcleanup(HbTransformData *data) {
-+ hb_buffer_destroy(data->buffer);
-+ memset(data, 0, sizeof(HbTransformData));
-+}
-diff --git a/hb.h b/hb.h
-new file mode 100644
-index 0000000..3b0ef44
---- /dev/null
-+++ b/hb.h
-@@ -0,0 +1,14 @@
-+#include <X11/Xft/Xft.h>
-+#include <hb.h>
-+#include <hb-ft.h>
-+
-+typedef struct {
-+ hb_buffer_t *buffer;
-+ hb_glyph_info_t *glyphs;
-+ hb_glyph_position_t *positions;
-+ unsigned int count;
-+} HbTransformData;
-+
-+void hbunloadfonts();
-+void hbtransform(HbTransformData *, XftFont *, const Glyph *, int, int);
-+void hbcleanup(HbTransformData *);
-diff --git a/st.c b/st.c
-index 62def59..041c6d8 100644
---- a/st.c
-+++ b/st.c
-@@ -2640,7 +2640,8 @@ draw(void)
-
- drawregion(0, 0, term.col, term.row);
- xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
-- term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
-+ term.ocx, term.ocy, term.line[term.ocy][term.ocx],
-+ term.line[term.ocy], term.col);
- term.ocx = cx;
- term.ocy = term.c.y;
- xfinishdraw();
-diff --git a/st.h b/st.h
-index 9f91e2a..b1a6256 100644
---- a/st.h
-+++ b/st.h
-@@ -11,7 +11,8 @@
- #define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d))
- #define DEFAULT(a, b) (a) = (a) ? (a) : (b)
- #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)
--#define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \
-+#define ATTRCMP(a, b) (((a).mode & (~ATTR_WRAP)) != ((b).mode & (~ATTR_WRAP)) || \
-+ (a).fg != (b).fg || \
- (a).bg != (b).bg)
- #define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \
- (t1.tv_nsec-t2.tv_nsec)/1E6)
-diff --git a/win.h b/win.h
-index 6de960d..94679e4 100644
---- a/win.h
-+++ b/win.h
-@@ -25,7 +25,7 @@ enum win_mode {
-
- void xbell(void);
- void xclipcopy(void);
--void xdrawcursor(int, int, Glyph, int, int, Glyph);
-+void xdrawcursor(int, int, Glyph, int, int, Glyph, Line, int);
- void xdrawline(Line, int, int, int);
- void xfinishdraw(void);
- void xloadcols(void);
-diff --git a/x.c b/x.c
-index 27e81d1..0632636 100644
---- a/x.c
-+++ b/x.c
-@@ -19,6 +19,7 @@ char *argv0;
- #include "arg.h"
- #include "st.h"
- #include "win.h"
-+#include "hb.h"
-
- /* types used in config.h */
- typedef struct {
-@@ -142,8 +143,9 @@ typedef struct {
- } DC;
-
- static inline ushort sixd_to_16bit(int);
-+static void xresetfontsettings(ushort mode, Font **font, int *frcflags);
- static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int);
--static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int);
-+static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int, int);
- static void xdrawglyph(Glyph, int, int);
- static void xclear(int, int, int, int);
- static int xgeommasktogravity(int);
-@@ -759,7 +761,7 @@ xresize(int col, int row)
- xclear(0, 0, win.w, win.h);
-
- /* resize to new width */
-- xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec));
-+ xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec) * 4);
- }
-
- ushort
-@@ -1071,6 +1073,9 @@ xunloadfont(Font *f)
- void
- xunloadfonts(void)
- {
-+ /* Clear Harfbuzz font cache. */
-+ hbunloadfonts();
-+
- /* Free the loaded fonts in the font cache. */
- while (frclen > 0)
- XftFontClose(xw.dpy, frc[--frclen].font);
-@@ -1202,7 +1207,7 @@ xinit(int cols, int rows)
- XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h);
-
- /* font spec buffer */
-- xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec));
-+ xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec) * 4);
-
- /* Xft rendering context */
- xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap);
-@@ -1256,6 +1261,22 @@ xinit(int cols, int rows)
- xsel.xtarget = XA_STRING;
- }
-
-+void
-+xresetfontsettings(ushort mode, Font **font, int *frcflags)
-+{
-+ *font = &dc.font;
-+ if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) {
-+ *font = &dc.ibfont;
-+ *frcflags = FRC_ITALICBOLD;
-+ } else if (mode & ATTR_ITALIC) {
-+ *font = &dc.ifont;
-+ *frcflags = FRC_ITALIC;
-+ } else if (mode & ATTR_BOLD) {
-+ *font = &dc.bfont;
-+ *frcflags = FRC_BOLD;
-+ }
-+}
-+
- int
- xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y)
- {
-@@ -1270,128 +1291,157 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
- FcPattern *fcpattern, *fontpattern;
- FcFontSet *fcsets[] = { NULL };
- FcCharSet *fccharset;
-- int i, f, numspecs = 0;
-+ int i, f, length = 0, start = 0, numspecs = 0;
-+ float cluster_xp = xp, cluster_yp = yp;
-+ HbTransformData shaped = { 0 };
-+
-+ /* Initial values. */
-+ mode = prevmode = glyphs[0].mode & ~ATTR_WRAP;
-+ xresetfontsettings(mode, &font, &frcflags);
-
- for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) {
-- /* Fetch rune and mode for current glyph. */
-- rune = glyphs[i].u;
-- mode = glyphs[i].mode;
-+ mode = glyphs[i].mode & ~ATTR_WRAP;
-
- /* Skip dummy wide-character spacing. */
-- if (mode == ATTR_WDUMMY)
-+ if (mode & ATTR_WDUMMY && i < (len - 1))
- continue;
-
-- /* Determine font for glyph if different from previous glyph. */
-- if (prevmode != mode) {
-- prevmode = mode;
-- font = &dc.font;
-- frcflags = FRC_NORMAL;
-- runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f);
-- if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) {
-- font = &dc.ibfont;
-- frcflags = FRC_ITALICBOLD;
-- } else if (mode & ATTR_ITALIC) {
-- font = &dc.ifont;
-- frcflags = FRC_ITALIC;
-- } else if (mode & ATTR_BOLD) {
-- font = &dc.bfont;
-- frcflags = FRC_BOLD;
-+ if (
-+ prevmode != mode
-+ || ATTRCMP(glyphs[start], glyphs[i])
-+ || selected(x + i, y) != selected(x + start, y)
-+ || i == (len - 1)
-+ ) {
-+ /* Handle 1-character wide segments and end of line */
-+ length = i - start;
-+ if (i == start) {
-+ length = 1;
-+ } else if (i == (len - 1)) {
-+ length = (i - start + 1);
- }
-- yp = winy + font->ascent;
-- }
-
-- /* Lookup character index with default font. */
-- glyphidx = XftCharIndex(xw.dpy, font->match, rune);
-- if (glyphidx) {
-- specs[numspecs].font = font->match;
-- specs[numspecs].glyph = glyphidx;
-- specs[numspecs].x = (short)xp;
-- specs[numspecs].y = (short)yp;
-- xp += runewidth;
-- numspecs++;
-- continue;
-- }
--
-- /* Fallback on font cache, search the font cache for match. */
-- for (f = 0; f < frclen; f++) {
-- glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune);
-- /* Everything correct. */
-- if (glyphidx && frc[f].flags == frcflags)
-- break;
-- /* We got a default font for a not found glyph. */
-- if (!glyphidx && frc[f].flags == frcflags
-- && frc[f].unicodep == rune) {
-- break;
-+ /* Shape the segment. */
-+ hbtransform(&shaped, font->match, glyphs, start, length);
-+ runewidth = win.cw * ((glyphs[start].mode & ATTR_WIDE) ? 2.0f : 1.0f);
-+ cluster_xp = xp; cluster_yp = yp;
-+ for (int code_idx = 0; code_idx < shaped.count; code_idx++) {
-+ int idx = shaped.glyphs[code_idx].cluster;
-+
-+ if (glyphs[start + idx].mode & ATTR_WDUMMY)
-+ continue;
-+
-+ /* Advance the drawing cursor if we've moved to a new cluster */
-+ if (code_idx > 0 && idx != shaped.glyphs[code_idx - 1].cluster) {
-+ xp += runewidth;
-+ cluster_xp = xp;
-+ cluster_yp = yp;
-+ runewidth = win.cw * ((glyphs[start + idx].mode & ATTR_WIDE) ? 2.0f : 1.0f);
-+ }
-+
-+ if (shaped.glyphs[code_idx].codepoint != 0) {
-+ /* If symbol is found, put it into the specs. */
-+ specs[numspecs].font = font->match;
-+ specs[numspecs].glyph = shaped.glyphs[code_idx].codepoint;
-+ specs[numspecs].x = cluster_xp + (short)(shaped.positions[code_idx].x_offset / 64.);
-+ specs[numspecs].y = cluster_yp - (short)(shaped.positions[code_idx].y_offset / 64.);
-+ cluster_xp += shaped.positions[code_idx].x_advance / 64.;
-+ cluster_yp += shaped.positions[code_idx].y_advance / 64.;
-+ numspecs++;
-+ } else {
-+ /* If it's not found, try to fetch it through the font cache. */
-+ rune = glyphs[start + idx].u;
-+ for (f = 0; f < frclen; f++) {
-+ glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune);
-+ /* Everything correct. */
-+ if (glyphidx && frc[f].flags == frcflags)
-+ break;
-+ /* We got a default font for a not found glyph. */
-+ if (!glyphidx && frc[f].flags == frcflags
-+ && frc[f].unicodep == rune) {
-+ break;
-+ }
-+ }
-+
-+ /* Nothing was found. Use fontconfig to find matching font. */
-+ if (f >= frclen) {
-+ if (!font->set)
-+ font->set = FcFontSort(0, font->pattern,
-+ 1, 0, &fcres);
-+ fcsets[0] = font->set;
-+
-+ /*
-+ * Nothing was found in the cache. Now use
-+ * some dozen of Fontconfig calls to get the
-+ * font for one single character.
-+ *
-+ * Xft and fontconfig are design failures.
-+ */
-+ fcpattern = FcPatternDuplicate(font->pattern);
-+ fccharset = FcCharSetCreate();
-+
-+ FcCharSetAddChar(fccharset, rune);
-+ FcPatternAddCharSet(fcpattern, FC_CHARSET,
-+ fccharset);
-+ FcPatternAddBool(fcpattern, FC_SCALABLE, 1);
-+
-+ FcConfigSubstitute(0, fcpattern,
-+ FcMatchPattern);
-+ FcDefaultSubstitute(fcpattern);
-+
-+ fontpattern = FcFontSetMatch(0, fcsets, 1,
-+ fcpattern, &fcres);
-+
-+ /* Allocate memory for the new cache entry. */
-+ if (frclen >= frccap) {
-+ frccap += 16;
-+ frc = xrealloc(frc, frccap * sizeof(Fontcache));
-+ }
-+
-+ frc[frclen].font = XftFontOpenPattern(xw.dpy,
-+ fontpattern);
-+ if (!frc[frclen].font)
-+ die("XftFontOpenPattern failed seeking fallback font: %s\n",
-+ strerror(errno));
-+ frc[frclen].flags = frcflags;
-+ frc[frclen].unicodep = rune;
-+
-+ glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune);
-+
-+ f = frclen;
-+ frclen++;
-+
-+ FcPatternDestroy(fcpattern);
-+ FcCharSetDestroy(fccharset);
-+ }
-+
-+ specs[numspecs].font = frc[f].font;
-+ specs[numspecs].glyph = glyphidx;
-+ specs[numspecs].x = (short)xp;
-+ specs[numspecs].y = (short)yp;
-+ numspecs++;
-+ }
- }
-- }
-
-- /* Nothing was found. Use fontconfig to find matching font. */
-- if (f >= frclen) {
-- if (!font->set)
-- font->set = FcFontSort(0, font->pattern,
-- 1, 0, &fcres);
-- fcsets[0] = font->set;
-+ /* Cleanup and get ready for next segment. */
-+ hbcleanup(&shaped);
-+ start = i;
-
-- /*
-- * Nothing was found in the cache. Now use
-- * some dozen of Fontconfig calls to get the
-- * font for one single character.
-- *
-- * Xft and fontconfig are design failures.
-- */
-- fcpattern = FcPatternDuplicate(font->pattern);
-- fccharset = FcCharSetCreate();
--
-- FcCharSetAddChar(fccharset, rune);
-- FcPatternAddCharSet(fcpattern, FC_CHARSET,
-- fccharset);
-- FcPatternAddBool(fcpattern, FC_SCALABLE, 1);
--
-- FcConfigSubstitute(0, fcpattern,
-- FcMatchPattern);
-- FcDefaultSubstitute(fcpattern);
--
-- fontpattern = FcFontSetMatch(0, fcsets, 1,
-- fcpattern, &fcres);
--
-- /* Allocate memory for the new cache entry. */
-- if (frclen >= frccap) {
-- frccap += 16;
-- frc = xrealloc(frc, frccap * sizeof(Fontcache));
-+ /* Determine font for glyph if different from previous glyph. */
-+ if (prevmode != mode) {
-+ prevmode = mode;
-+ xresetfontsettings(mode, &font, &frcflags);
-+ yp = winy + font->ascent;
- }
--
-- frc[frclen].font = XftFontOpenPattern(xw.dpy,
-- fontpattern);
-- if (!frc[frclen].font)
-- die("XftFontOpenPattern failed seeking fallback font: %s\n",
-- strerror(errno));
-- frc[frclen].flags = frcflags;
-- frc[frclen].unicodep = rune;
--
-- glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune);
--
-- f = frclen;
-- frclen++;
--
-- FcPatternDestroy(fcpattern);
-- FcCharSetDestroy(fccharset);
- }
--
-- specs[numspecs].font = frc[f].font;
-- specs[numspecs].glyph = glyphidx;
-- specs[numspecs].x = (short)xp;
-- specs[numspecs].y = (short)yp;
-- xp += runewidth;
-- numspecs++;
- }
-
-+ hbcleanup(&shaped);
- return numspecs;
- }
-
- void
--xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y)
-+xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y, int charlen)
- {
-- int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1);
- int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch,
- width = charlen * win.cw;
- Color *fg, *bg, *temp, revfg, revbg, truefg, truebg;
-@@ -1527,21 +1577,24 @@ void
- xdrawglyph(Glyph g, int x, int y)
- {
- int numspecs;
-- XftGlyphFontSpec spec;
-+ XftGlyphFontSpec *specs = xw.specbuf;
-
-- numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y);
-- xdrawglyphfontspecs(&spec, g, numspecs, x, y);
-+ numspecs = xmakeglyphfontspecs(specs, &g, 1, x, y);
-+ xdrawglyphfontspecs(specs, g, numspecs, x, y, (g.mode & ATTR_WIDE) ? 2 : 1);
- }
-
- void
--xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
-+xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og, Line line, int len)
- {
- Color drawcol;
-
- /* remove the old cursor */
- if (selected(ox, oy))
- og.mode ^= ATTR_REVERSE;
-- xdrawglyph(og, ox, oy);
-+
-+ /* Redraw the line where cursor was previously.
-+ * It will restore the ligatures broken by the cursor. */
-+ xdrawline(line, 0, oy, len);
-
- if (IS_SET(MODE_HIDE))
- return;
-@@ -1669,18 +1722,16 @@ xdrawline(Line line, int x1, int y1, int x2)
- Glyph base, new;
- XftGlyphFontSpec *specs = xw.specbuf;
-
-- numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1);
- i = ox = 0;
-- for (x = x1; x < x2 && i < numspecs; x++) {
-+ for (x = x1; x < x2; x++) {
- new = line[x];
- if (new.mode == ATTR_WDUMMY)
- continue;
- if (selected(x, y1))
- new.mode ^= ATTR_REVERSE;
-- if (i > 0 && ATTRCMP(base, new)) {
-- xdrawglyphfontspecs(specs, base, i, ox, y1);
-- specs += i;
-- numspecs -= i;
-+ if ((i > 0) && ATTRCMP(base, new)) {
-+ numspecs = xmakeglyphfontspecs(specs, &line[ox], x - ox, ox, y1);
-+ xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x - ox);
- i = 0;
- }
- if (i == 0) {
-@@ -1689,8 +1740,10 @@ xdrawline(Line line, int x1, int y1, int x2)
- }
- i++;
- }
-- if (i > 0)
-- xdrawglyphfontspecs(specs, base, i, ox, y1);
-+ if (i > 0) {
-+ numspecs = xmakeglyphfontspecs(specs, &line[ox], x2 - ox, ox, y1);
-+ xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x2 - ox);
-+ }
- }
-
- void
diff --git a/st.suckless.org/patches/ligatures/0.9.2/st-ligatures-alpha-20241226-0.9.2.diff b/st.suckless.org/patches/ligatures/0.9.2/st-ligatures-alpha-20241226-0.9.2.diff
@@ -0,0 +1,650 @@
+diff --git a/Makefile b/Makefile
+index 15db421..dfcea0f 100644
+--- a/Makefile
++++ b/Makefile
+@@ -3,9 +3,9 @@
+ .POSIX:
+
+ include config.mk
+
+-SRC = st.c x.c
++SRC = st.c x.c hb.c
+ OBJ = $(SRC:.c=.o)
+
+ all: st
+
+@@ -15,9 +15,10 @@ config.h:
+ .c.o:
+ $(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
+
+ st: $(OBJ)
+diff --git a/config.mk b/config.mk
+index 069a6c2..977b7c7 100644
+--- a/config.mk
++++ b/config.mk
+@@ -14,12 +14,14 @@ 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 -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
+ STCFLAGS = $(INCS) $(STCPPFLAGS) $(CPPFLAGS) $(CFLAGS)
+@@ -28,9 +30,10 @@ STLDFLAGS = $(LIBS) $(LDFLAGS)
+ # OpenBSD:
+ #CPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 -D_BSD_SOURCE
+ #LIBS = -L$(X11LIB) -lm -lX11 -lutil -lXft \
+ # `$(PKG_CONFIG) --libs fontconfig` \
+-# `$(PKG_CONFIG) --libs freetype2`
++# `$(PKG_CONFIG) --libs freetype2` \
++# `$(PKG_CONFIG) --libs harfbuzz`
+ #MANPREFIX = ${PREFIX}/man
+
+ # compiler and linker
+ # CC = c99
+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 b9f66e7..da33a85 100644
+--- a/st.c
++++ b/st.c
+@@ -2658,9 +2658,10 @@ draw(void)
+ cx--;
+
+ 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();
+ if (ocx != term.ocx || ocy != term.ocy)
+diff --git a/st.h b/st.h
+index 9f91e2a..b1a6256 100644
+--- a/st.h
++++ b/st.h
+@@ -10,9 +10,10 @@
+ #define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b))
+ #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)
+ #define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit)))
+diff --git a/win.h b/win.h
+index 6de960d..94679e4 100644
+--- a/win.h
++++ b/win.h
+@@ -24,9 +24,9 @@ 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);
+ int xsetcolorname(int, const char *);
+diff --git a/x.c b/x.c
+index 1e12ac8..b0819ac 100644
+--- a/x.c
++++ b/x.c
+@@ -18,8 +18,9 @@
+ char *argv0;
+ #include "arg.h"
+ #include "st.h"
+ #include "win.h"
++#include "hb.h"
+
+ /* types used in config.h */
+ typedef struct {
+ uint mod;
+@@ -141,10 +142,11 @@ typedef struct {
+ GC gc;
+ } 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);
+ static int ximopen(Display *);
+@@ -758,9 +760,9 @@ xresize(int col, int row)
+ XftDrawChange(xw.draw, xw.buf);
+ 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
+ sixd_to_16bit(int x)
+@@ -1070,8 +1072,11 @@ 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);
+
+@@ -1201,9 +1206,9 @@ xinit(int cols, int rows)
+ XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel);
+ 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);
+
+@@ -1255,144 +1260,155 @@ xinit(int cols, int rows)
+ if (xsel.xtarget == None)
+ 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)
+ {
+ float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp;
+- ushort mode, prevmode = USHRT_MAX;
++ ushort mode = glyphs[0].mode & ~ATTR_WRAP;
+ Font *font = &dc.font;
+ int frcflags = FRC_NORMAL;
+- float runewidth = win.cw;
++ float runewidth = win.cw * ((glyphs[0].mode & ATTR_WIDE) ? 2.0f : 1.0f);
+ Rune rune;
+ FT_UInt glyphidx;
+ FcResult fcres;
+ FcPattern *fcpattern, *fontpattern;
+ FcFontSet *fcsets[] = { NULL };
+ FcCharSet *fccharset;
+- int i, f, numspecs = 0;
++ int f, code_idx, numspecs = 0;
++ float cluster_xp = xp, cluster_yp = yp;
++ HbTransformData shaped = { 0 };
+
+- 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;
++ /* Initial values. */
++ xresetfontsettings(mode, &font, &frcflags);
+
+- /* Skip dummy wide-character spacing. */
+- if (mode == ATTR_WDUMMY)
++ /* Shape the segment. */
++ hbtransform(&shaped, font->match, glyphs, 0, len);
++ xp = winx; yp = winy + font->ascent;
++ cluster_xp = xp; cluster_yp = yp;
++
++ for (code_idx = 0; code_idx < shaped.count; code_idx++) {
++ int idx = shaped.glyphs[code_idx].cluster;
++
++ if (glyphs[idx].mode & ATTR_WDUMMY)
+ 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;
+- }
+- yp = winy + font->ascent;
++ /* 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;
+ }
+
+- /* Lookup character index with default font. */
+- glyphidx = XftCharIndex(xw.dpy, font->match, rune);
+- if (glyphidx) {
++ if (shaped.glyphs[code_idx].codepoint != 0) {
++ /* If symbol is found, put it into the specs. */
+ specs[numspecs].font = font->match;
+- specs[numspecs].glyph = glyphidx;
+- specs[numspecs].x = (short)xp;
+- specs[numspecs].y = (short)yp;
+- xp += runewidth;
++ 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++;
+- 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;
++ } else {
++ /* If it's not found, try to fetch it through the font cache. */
++ rune = glyphs[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));
++ /* 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);
+ }
+
+- 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++;
+ }
+-
+- specs[numspecs].font = frc[f].font;
+- specs[numspecs].glyph = glyphidx;
+- specs[numspecs].x = (short)xp;
+- specs[numspecs].y = (short)yp;
+- xp += runewidth;
+- numspecs++;
+ }
+
++ /* Cleanup and get ready for next segment. */
++ 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;
+ XRenderColor colfg, colbg;
+@@ -1526,23 +1542,26 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
+ 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;
+
+@@ -1674,30 +1693,30 @@ xdrawline(Line line, int x1, int y1, int x2)
+ int i, x, ox, numspecs;
+ 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) {
+ ox = x;
+ base = new;
+ }
+ 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
+ xfinishdraw(void)
diff --git a/st.suckless.org/patches/ligatures/0.9.2/st-ligatures-alpha-scrollback-20240427-0.9.2.diff b/st.suckless.org/patches/ligatures/0.9.2/st-ligatures-alpha-scrollback-20240427-0.9.2.diff
@@ -1,635 +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: st
-@@ -22,7 +22,8 @@ config.h:
- $(CC) $(STCFLAGS) -c $<
-
- st.o: config.h st.h win.h
--x.o: arg.h config.h st.h win.h
-+x.o: arg.h config.h st.h win.h hb.h
-+hb.o: st.h
-
- $(OBJ): config.h config.mk
-
-diff --git a/config.mk b/config.mk
-index 47c615e..d7439a3 100644
---- a/config.mk
-+++ b/config.mk
-@@ -15,10 +15,12 @@ PKG_CONFIG = pkg-config
- # includes and libs
- INCS = -I$(X11INC) \
- `$(PKG_CONFIG) --cflags fontconfig` \
-- `$(PKG_CONFIG) --cflags freetype2`
--LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft -lXrender\
-+ `$(PKG_CONFIG) --cflags freetype2` \
-+ `$(PKG_CONFIG) --cflags harfbuzz`
-+LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft -lXrender \
- `$(PKG_CONFIG) --libs fontconfig` \
-- `$(PKG_CONFIG) --libs freetype2`
-+ `$(PKG_CONFIG) --libs freetype2` \
-+ `$(PKG_CONFIG) --libs harfbuzz`
-
- # flags
- STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600
-diff --git a/hb.c b/hb.c
-new file mode 100644
-index 0000000..99412c8
---- /dev/null
-+++ b/hb.c
-@@ -0,0 +1,125 @@
-+#include <stdlib.h>
-+#include <stdio.h>
-+#include <math.h>
-+#include <X11/Xft/Xft.h>
-+#include <X11/cursorfont.h>
-+#include <hb.h>
-+#include <hb-ft.h>
-+
-+#include "st.h"
-+#include "hb.h"
-+
-+#define FEATURE(c1,c2,c3,c4) { .tag = HB_TAG(c1,c2,c3,c4), .value = 1, .start = HB_FEATURE_GLOBAL_START, .end = HB_FEATURE_GLOBAL_END }
-+#define BUFFER_STEP 256
-+
-+hb_font_t *hbfindfont(XftFont *match);
-+
-+typedef struct {
-+ XftFont *match;
-+ hb_font_t *font;
-+} HbFontMatch;
-+
-+typedef struct {
-+ size_t capacity;
-+ HbFontMatch *fonts;
-+} HbFontCache;
-+
-+static HbFontCache hbfontcache = { 0, NULL };
-+
-+typedef struct {
-+ size_t capacity;
-+ Rune *runes;
-+} RuneBuffer;
-+
-+static RuneBuffer hbrunebuffer = { 0, NULL };
-+
-+/*
-+ * Poplulate the array with a list of font features, wrapped in FEATURE macro,
-+ * e. g.
-+ * FEATURE('c', 'a', 'l', 't'), FEATURE('d', 'l', 'i', 'g')
-+ */
-+hb_feature_t features[] = { };
-+
-+void
-+hbunloadfonts()
-+{
-+ for (int i = 0; i < hbfontcache.capacity; i++) {
-+ hb_font_destroy(hbfontcache.fonts[i].font);
-+ XftUnlockFace(hbfontcache.fonts[i].match);
-+ }
-+
-+ if (hbfontcache.fonts != NULL) {
-+ free(hbfontcache.fonts);
-+ hbfontcache.fonts = NULL;
-+ }
-+ hbfontcache.capacity = 0;
-+}
-+
-+hb_font_t *
-+hbfindfont(XftFont *match)
-+{
-+ for (int i = 0; i < hbfontcache.capacity; i++) {
-+ if (hbfontcache.fonts[i].match == match)
-+ return hbfontcache.fonts[i].font;
-+ }
-+
-+ /* Font not found in cache, caching it now. */
-+ hbfontcache.fonts = realloc(hbfontcache.fonts, sizeof(HbFontMatch) * (hbfontcache.capacity + 1));
-+ FT_Face face = XftLockFace(match);
-+ hb_font_t *font = hb_ft_font_create(face, NULL);
-+ if (font == NULL)
-+ die("Failed to load Harfbuzz font.");
-+
-+ hbfontcache.fonts[hbfontcache.capacity].match = match;
-+ hbfontcache.fonts[hbfontcache.capacity].font = font;
-+ hbfontcache.capacity += 1;
-+
-+ return font;
-+}
-+
-+void hbtransform(HbTransformData *data, XftFont *xfont, const Glyph *glyphs, int start, int length) {
-+ ushort mode = USHRT_MAX;
-+ unsigned int glyph_count;
-+ int rune_idx, glyph_idx, end = start + length;
-+
-+ hb_font_t *font = hbfindfont(xfont);
-+ if (font == NULL)
-+ return;
-+
-+ hb_buffer_t *buffer = hb_buffer_create();
-+ hb_buffer_set_direction(buffer, HB_DIRECTION_LTR);
-+ hb_buffer_set_cluster_level(buffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS);
-+
-+ /* Resize the buffer if required length is larger. */
-+ if (hbrunebuffer.capacity < length) {
-+ hbrunebuffer.capacity = (length / BUFFER_STEP + 1) * BUFFER_STEP;
-+ hbrunebuffer.runes = realloc(hbrunebuffer.runes, hbrunebuffer.capacity * sizeof(Rune));
-+ }
-+
-+ /* Fill buffer with codepoints. */
-+ for (rune_idx = 0, glyph_idx = start; glyph_idx < end; glyph_idx++, rune_idx++) {
-+ hbrunebuffer.runes[rune_idx] = glyphs[glyph_idx].u;
-+ mode = glyphs[glyph_idx].mode;
-+ if (mode & ATTR_WDUMMY)
-+ hbrunebuffer.runes[rune_idx] = 0x0020;
-+ }
-+ hb_buffer_add_codepoints(buffer, hbrunebuffer.runes, length, 0, length);
-+
-+ /* Shape the segment. */
-+ hb_shape(font, buffer, features, sizeof(features)/sizeof(hb_feature_t));
-+
-+ /* Get new glyph info. */
-+ hb_glyph_info_t *info = hb_buffer_get_glyph_infos(buffer, &glyph_count);
-+ hb_glyph_position_t *pos = hb_buffer_get_glyph_positions(buffer, &glyph_count);
-+
-+ /* Fill the output. */
-+ data->buffer = buffer;
-+ data->glyphs = info;
-+ data->positions = pos;
-+ data->count = glyph_count;
-+}
-+
-+void hbcleanup(HbTransformData *data) {
-+ hb_buffer_destroy(data->buffer);
-+ memset(data, 0, sizeof(HbTransformData));
-+}
-diff --git a/hb.h b/hb.h
-new file mode 100644
-index 0000000..3b0ef44
---- /dev/null
-+++ b/hb.h
-@@ -0,0 +1,14 @@
-+#include <X11/Xft/Xft.h>
-+#include <hb.h>
-+#include <hb-ft.h>
-+
-+typedef struct {
-+ hb_buffer_t *buffer;
-+ hb_glyph_info_t *glyphs;
-+ hb_glyph_position_t *positions;
-+ unsigned int count;
-+} HbTransformData;
-+
-+void hbunloadfonts();
-+void hbtransform(HbTransformData *, XftFont *, const Glyph *, int, int);
-+void hbcleanup(HbTransformData *);
-diff --git a/st.c b/st.c
-index 79ee9ba..454771d 100644
---- a/st.c
-+++ b/st.c
-@@ -2711,7 +2711,9 @@ draw(void)
- drawregion(0, 0, term.col, term.row);
- if (term.scr == 0)
- xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
-- term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
-+ term.ocx, term.ocy, term.line[term.ocy][term.ocx],
-+ term.line[term.ocy], term.col);
-+
- term.ocx = cx;
- term.ocy = term.c.y;
- xfinishdraw();
-diff --git a/st.h b/st.h
-index 78762a2..01eea49 100644
---- a/st.h
-+++ b/st.h
-@@ -11,7 +11,8 @@
- #define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d))
- #define DEFAULT(a, b) (a) = (a) ? (a) : (b)
- #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)
--#define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \
-+#define ATTRCMP(a, b) (((a).mode & (~ATTR_WRAP)) != ((b).mode & (~ATTR_WRAP)) || \
-+ (a).fg != (b).fg || \
- (a).bg != (b).bg)
- #define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \
- (t1.tv_nsec-t2.tv_nsec)/1E6)
-diff --git a/win.h b/win.h
-index 6de960d..94679e4 100644
---- a/win.h
-+++ b/win.h
-@@ -25,7 +25,7 @@ enum win_mode {
-
- void xbell(void);
- void xclipcopy(void);
--void xdrawcursor(int, int, Glyph, int, int, Glyph);
-+void xdrawcursor(int, int, Glyph, int, int, Glyph, Line, int);
- void xdrawline(Line, int, int, int);
- void xfinishdraw(void);
- void xloadcols(void);
-diff --git a/x.c b/x.c
-index 27e81d1..5e11c1f 100644
---- a/x.c
-+++ b/x.c
-@@ -19,6 +19,7 @@ char *argv0;
- #include "arg.h"
- #include "st.h"
- #include "win.h"
-+#include "hb.h"
-
- /* types used in config.h */
- typedef struct {
-@@ -142,8 +143,9 @@ typedef struct {
- } DC;
-
- static inline ushort sixd_to_16bit(int);
-+static void xresetfontsettings(ushort mode, Font **font, int *frcflags);
- static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int);
--static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int);
-+static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int, int);
- static void xdrawglyph(Glyph, int, int);
- static void xclear(int, int, int, int);
- static int xgeommasktogravity(int);
-@@ -759,7 +761,7 @@ xresize(int col, int row)
- xclear(0, 0, win.w, win.h);
-
- /* resize to new width */
-- xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec));
-+ xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec) * 4);
- }
-
- ushort
-@@ -1071,6 +1073,9 @@ xunloadfont(Font *f)
- void
- xunloadfonts(void)
- {
-+ /* Clear Harfbuzz font cache. */
-+ hbunloadfonts();
-+
- /* Free the loaded fonts in the font cache. */
- while (frclen > 0)
- XftFontClose(xw.dpy, frc[--frclen].font);
-@@ -1202,7 +1207,7 @@ xinit(int cols, int rows)
- XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h);
-
- /* font spec buffer */
-- xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec));
-+ xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec) * 4);
-
- /* Xft rendering context */
- xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap);
-@@ -1256,6 +1261,22 @@ xinit(int cols, int rows)
- xsel.xtarget = XA_STRING;
- }
-
-+void
-+xresetfontsettings(ushort mode, Font **font, int *frcflags)
-+{
-+ *font = &dc.font;
-+ if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) {
-+ *font = &dc.ibfont;
-+ *frcflags = FRC_ITALICBOLD;
-+ } else if (mode & ATTR_ITALIC) {
-+ *font = &dc.ifont;
-+ *frcflags = FRC_ITALIC;
-+ } else if (mode & ATTR_BOLD) {
-+ *font = &dc.bfont;
-+ *frcflags = FRC_BOLD;
-+ }
-+}
-+
- int
- xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y)
- {
-@@ -1270,128 +1291,156 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
- FcPattern *fcpattern, *fontpattern;
- FcFontSet *fcsets[] = { NULL };
- FcCharSet *fccharset;
-- int i, f, numspecs = 0;
-+ int i, f, length = 0, start = 0, numspecs = 0;
-+ float cluster_xp = xp, cluster_yp = yp;
-+ HbTransformData shaped = { 0 };
-+
-+ /* Initial values. */
-+ mode = prevmode = glyphs[0].mode & ~ATTR_WRAP;
-+ xresetfontsettings(mode, &font, &frcflags);
-
- for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) {
-- /* Fetch rune and mode for current glyph. */
-- rune = glyphs[i].u;
-- mode = glyphs[i].mode;
-+ mode = glyphs[i].mode & ~ATTR_WRAP;
-
- /* Skip dummy wide-character spacing. */
-- if (mode == ATTR_WDUMMY)
-+ if (mode & ATTR_WDUMMY && i < (len - 1))
- continue;
-
-- /* Determine font for glyph if different from previous glyph. */
-- if (prevmode != mode) {
-- prevmode = mode;
-- font = &dc.font;
-- frcflags = FRC_NORMAL;
-- runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f);
-- if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) {
-- font = &dc.ibfont;
-- frcflags = FRC_ITALICBOLD;
-- } else if (mode & ATTR_ITALIC) {
-- font = &dc.ifont;
-- frcflags = FRC_ITALIC;
-- } else if (mode & ATTR_BOLD) {
-- font = &dc.bfont;
-- frcflags = FRC_BOLD;
-+ if (
-+ prevmode != mode
-+ || ATTRCMP(glyphs[start], glyphs[i])
-+ || selected(x + i, y) != selected(x + start, y)
-+ || i == (len - 1)
-+ ) {
-+ /* Handle 1-character wide segments and end of line */
-+ length = i - start;
-+ if (i == start) {
-+ length = 1;
-+ } else if (i == (len - 1)) {
-+ length = (i - start + 1);
- }
-- yp = winy + font->ascent;
-- }
-
-- /* Lookup character index with default font. */
-- glyphidx = XftCharIndex(xw.dpy, font->match, rune);
-- if (glyphidx) {
-- specs[numspecs].font = font->match;
-- specs[numspecs].glyph = glyphidx;
-- specs[numspecs].x = (short)xp;
-- specs[numspecs].y = (short)yp;
-- xp += runewidth;
-- numspecs++;
-- continue;
-- }
--
-- /* Fallback on font cache, search the font cache for match. */
-- for (f = 0; f < frclen; f++) {
-- glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune);
-- /* Everything correct. */
-- if (glyphidx && frc[f].flags == frcflags)
-- break;
-- /* We got a default font for a not found glyph. */
-- if (!glyphidx && frc[f].flags == frcflags
-- && frc[f].unicodep == rune) {
-- break;
-+ /* Shape the segment. */
-+ hbtransform(&shaped, font->match, glyphs, start, length);
-+ runewidth = win.cw * ((glyphs[start].mode & ATTR_WIDE) ? 2.0f : 1.0f);
-+ cluster_xp = xp; cluster_yp = yp;
-+ for (int code_idx = 0; code_idx < shaped.count; code_idx++) {
-+ int idx = shaped.glyphs[code_idx].cluster;
-+
-+ if (glyphs[start + idx].mode & ATTR_WDUMMY)
-+ continue;
-+
-+ /* Advance the drawing cursor if we've moved to a new cluster */
-+ if (code_idx > 0 && idx != shaped.glyphs[code_idx - 1].cluster) {
-+ xp += runewidth;
-+ cluster_xp = xp;
-+ cluster_yp = yp;
-+ runewidth = win.cw * ((glyphs[start + idx].mode & ATTR_WIDE) ? 2.0f : 1.0f);
-+ }
-+
-+ if (shaped.glyphs[code_idx].codepoint != 0) {
-+ /* If symbol is found, put it into the specs. */
-+ specs[numspecs].font = font->match;
-+ specs[numspecs].glyph = shaped.glyphs[code_idx].codepoint;
-+ specs[numspecs].x = cluster_xp + (short)(shaped.positions[code_idx].x_offset / 64.);
-+ specs[numspecs].y = cluster_yp - (short)(shaped.positions[code_idx].y_offset / 64.);
-+ cluster_xp += shaped.positions[code_idx].x_advance / 64.;
-+ cluster_yp += shaped.positions[code_idx].y_advance / 64.;
-+ numspecs++;
-+ } else {
-+ /* If it's not found, try to fetch it through the font cache. */
-+ rune = glyphs[start + idx].u;
-+ for (f = 0; f < frclen; f++) {
-+ glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune);
-+ /* Everything correct. */
-+ if (glyphidx && frc[f].flags == frcflags)
-+ break;
-+ /* We got a default font for a not found glyph. */
-+ if (!glyphidx && frc[f].flags == frcflags
-+ && frc[f].unicodep == rune) {
-+ break;
-+ }
-+ }
-+
-+ /* Nothing was found. Use fontconfig to find matching font. */
-+ if (f >= frclen) {
-+ if (!font->set)
-+ font->set = FcFontSort(0, font->pattern,
-+ 1, 0, &fcres);
-+ fcsets[0] = font->set;
-+
-+ /*
-+ * Nothing was found in the cache. Now use
-+ * some dozen of Fontconfig calls to get the
-+ * font for one single character.
-+ *
-+ * Xft and fontconfig are design failures.
-+ */
-+ fcpattern = FcPatternDuplicate(font->pattern);
-+ fccharset = FcCharSetCreate();
-+
-+ FcCharSetAddChar(fccharset, rune);
-+ FcPatternAddCharSet(fcpattern, FC_CHARSET,
-+ fccharset);
-+ FcPatternAddBool(fcpattern, FC_SCALABLE, 1);
-+
-+ FcConfigSubstitute(0, fcpattern,
-+ FcMatchPattern);
-+ FcDefaultSubstitute(fcpattern);
-+
-+ fontpattern = FcFontSetMatch(0, fcsets, 1,
-+ fcpattern, &fcres);
-+
-+ /* Allocate memory for the new cache entry. */
-+ if (frclen >= frccap) {
-+ frccap += 16;
-+ frc = xrealloc(frc, frccap * sizeof(Fontcache));
-+ }
-+
-+ frc[frclen].font = XftFontOpenPattern(xw.dpy,
-+ fontpattern);
-+ if (!frc[frclen].font)
-+ die("XftFontOpenPattern failed seeking fallback font: %s\n",
-+ strerror(errno));
-+ frc[frclen].flags = frcflags;
-+ frc[frclen].unicodep = rune;
-+
-+ glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune);
-+
-+ f = frclen;
-+ frclen++;
-+
-+ FcPatternDestroy(fcpattern);
-+ FcCharSetDestroy(fccharset);
-+ }
-+
-+ specs[numspecs].font = frc[f].font;
-+ specs[numspecs].glyph = glyphidx;
-+ specs[numspecs].x = (short)xp;
-+ specs[numspecs].y = (short)yp;
-+ numspecs++;
-+ }
- }
-- }
-
-- /* Nothing was found. Use fontconfig to find matching font. */
-- if (f >= frclen) {
-- if (!font->set)
-- font->set = FcFontSort(0, font->pattern,
-- 1, 0, &fcres);
-- fcsets[0] = font->set;
-+ /* Cleanup and get ready for next segment. */
-+ hbcleanup(&shaped);
-+ start = i;
-
-- /*
-- * Nothing was found in the cache. Now use
-- * some dozen of Fontconfig calls to get the
-- * font for one single character.
-- *
-- * Xft and fontconfig are design failures.
-- */
-- fcpattern = FcPatternDuplicate(font->pattern);
-- fccharset = FcCharSetCreate();
--
-- FcCharSetAddChar(fccharset, rune);
-- FcPatternAddCharSet(fcpattern, FC_CHARSET,
-- fccharset);
-- FcPatternAddBool(fcpattern, FC_SCALABLE, 1);
--
-- FcConfigSubstitute(0, fcpattern,
-- FcMatchPattern);
-- FcDefaultSubstitute(fcpattern);
--
-- fontpattern = FcFontSetMatch(0, fcsets, 1,
-- fcpattern, &fcres);
--
-- /* Allocate memory for the new cache entry. */
-- if (frclen >= frccap) {
-- frccap += 16;
-- frc = xrealloc(frc, frccap * sizeof(Fontcache));
-+ /* Determine font for glyph if different from previous glyph. */
-+ if (prevmode != mode) {
-+ prevmode = mode;
-+ xresetfontsettings(mode, &font, &frcflags);
-+ yp = winy + font->ascent;
- }
--
-- frc[frclen].font = XftFontOpenPattern(xw.dpy,
-- fontpattern);
-- if (!frc[frclen].font)
-- die("XftFontOpenPattern failed seeking fallback font: %s\n",
-- strerror(errno));
-- frc[frclen].flags = frcflags;
-- frc[frclen].unicodep = rune;
--
-- glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune);
--
-- f = frclen;
-- frclen++;
--
-- FcPatternDestroy(fcpattern);
-- FcCharSetDestroy(fccharset);
- }
--
-- specs[numspecs].font = frc[f].font;
-- specs[numspecs].glyph = glyphidx;
-- specs[numspecs].x = (short)xp;
-- specs[numspecs].y = (short)yp;
-- xp += runewidth;
-- numspecs++;
- }
-
- return numspecs;
- }
-
- void
--xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y)
-+xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y, int charlen)
- {
-- int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1);
- int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch,
- width = charlen * win.cw;
- Color *fg, *bg, *temp, revfg, revbg, truefg, truebg;
-@@ -1527,21 +1576,24 @@ void
- xdrawglyph(Glyph g, int x, int y)
- {
- int numspecs;
-- XftGlyphFontSpec spec;
-+ XftGlyphFontSpec *specs = xw.specbuf;
-
-- numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y);
-- xdrawglyphfontspecs(&spec, g, numspecs, x, y);
-+ numspecs = xmakeglyphfontspecs(specs, &g, 1, x, y);
-+ xdrawglyphfontspecs(specs, g, numspecs, x, y, (g.mode & ATTR_WIDE) ? 2 : 1);
- }
-
- void
--xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
-+xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og, Line line, int len)
- {
- Color drawcol;
-
- /* remove the old cursor */
- if (selected(ox, oy))
- og.mode ^= ATTR_REVERSE;
-- xdrawglyph(og, ox, oy);
-+
-+ /* Redraw the line where cursor was previously.
-+ * It will restore the ligatures broken by the cursor. */
-+ xdrawline(line, 0, oy, len);
-
- if (IS_SET(MODE_HIDE))
- return;
-@@ -1669,18 +1721,16 @@ xdrawline(Line line, int x1, int y1, int x2)
- Glyph base, new;
- XftGlyphFontSpec *specs = xw.specbuf;
-
-- numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1);
- i = ox = 0;
-- for (x = x1; x < x2 && i < numspecs; x++) {
-+ for (x = x1; x < x2; x++) {
- new = line[x];
- if (new.mode == ATTR_WDUMMY)
- continue;
- if (selected(x, y1))
- new.mode ^= ATTR_REVERSE;
-- if (i > 0 && ATTRCMP(base, new)) {
-- xdrawglyphfontspecs(specs, base, i, ox, y1);
-- specs += i;
-- numspecs -= i;
-+ if ((i > 0) && ATTRCMP(base, new)) {
-+ numspecs = xmakeglyphfontspecs(specs, &line[ox], x - ox, ox, y1);
-+ xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x - ox);
- i = 0;
- }
- if (i == 0) {
-@@ -1689,8 +1739,10 @@ xdrawline(Line line, int x1, int y1, int x2)
- }
- i++;
- }
-- if (i > 0)
-- xdrawglyphfontspecs(specs, base, i, ox, y1);
-+ if (i > 0) {
-+ numspecs = xmakeglyphfontspecs(specs, &line[ox], x2 - ox, ox, y1);
-+ xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x2 - ox);
-+ }
- }
-
- void
diff --git a/st.suckless.org/patches/ligatures/0.9.2/st-ligatures-alpha-scrollback-20241226-0.9.2.diff b/st.suckless.org/patches/ligatures/0.9.2/st-ligatures-alpha-scrollback-20241226-0.9.2.diff
@@ -0,0 +1,650 @@
+diff --git a/Makefile b/Makefile
+index 15db421..dfcea0f 100644
+--- a/Makefile
++++ b/Makefile
+@@ -3,9 +3,9 @@
+ .POSIX:
+
+ include config.mk
+
+-SRC = st.c x.c
++SRC = st.c x.c hb.c
+ OBJ = $(SRC:.c=.o)
+
+ all: st
+
+@@ -15,9 +15,10 @@ config.h:
+ .c.o:
+ $(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
+
+ st: $(OBJ)
+diff --git a/config.mk b/config.mk
+index 069a6c2..977b7c7 100644
+--- a/config.mk
++++ b/config.mk
+@@ -14,12 +14,14 @@ 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 -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
+ STCFLAGS = $(INCS) $(STCPPFLAGS) $(CPPFLAGS) $(CFLAGS)
+@@ -28,9 +30,10 @@ STLDFLAGS = $(LIBS) $(LDFLAGS)
+ # OpenBSD:
+ #CPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 -D_BSD_SOURCE
+ #LIBS = -L$(X11LIB) -lm -lX11 -lutil -lXft \
+ # `$(PKG_CONFIG) --libs fontconfig` \
+-# `$(PKG_CONFIG) --libs freetype2`
++# `$(PKG_CONFIG) --libs freetype2` \
++# `$(PKG_CONFIG) --libs harfbuzz`
+ #MANPREFIX = ${PREFIX}/man
+
+ # compiler and linker
+ # CC = c99
+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 2478942..bba90d3 100644
+--- a/st.c
++++ b/st.c
+@@ -2729,9 +2729,10 @@ 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();
+ if (ocx != term.ocx || ocy != term.ocy)
+diff --git a/st.h b/st.h
+index 78762a2..01eea49 100644
+--- a/st.h
++++ b/st.h
+@@ -10,9 +10,10 @@
+ #define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b))
+ #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)
+ #define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit)))
+diff --git a/win.h b/win.h
+index 6de960d..94679e4 100644
+--- a/win.h
++++ b/win.h
+@@ -24,9 +24,9 @@ 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);
+ int xsetcolorname(int, const char *);
+diff --git a/x.c b/x.c
+index 1e12ac8..b0819ac 100644
+--- a/x.c
++++ b/x.c
+@@ -18,8 +18,9 @@
+ char *argv0;
+ #include "arg.h"
+ #include "st.h"
+ #include "win.h"
++#include "hb.h"
+
+ /* types used in config.h */
+ typedef struct {
+ uint mod;
+@@ -141,10 +142,11 @@ typedef struct {
+ GC gc;
+ } 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);
+ static int ximopen(Display *);
+@@ -758,9 +760,9 @@ xresize(int col, int row)
+ XftDrawChange(xw.draw, xw.buf);
+ 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
+ sixd_to_16bit(int x)
+@@ -1070,8 +1072,11 @@ 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);
+
+@@ -1201,9 +1206,9 @@ xinit(int cols, int rows)
+ XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel);
+ 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);
+
+@@ -1255,144 +1260,155 @@ xinit(int cols, int rows)
+ if (xsel.xtarget == None)
+ 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)
+ {
+ float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp;
+- ushort mode, prevmode = USHRT_MAX;
++ ushort mode = glyphs[0].mode & ~ATTR_WRAP;
+ Font *font = &dc.font;
+ int frcflags = FRC_NORMAL;
+- float runewidth = win.cw;
++ float runewidth = win.cw * ((glyphs[0].mode & ATTR_WIDE) ? 2.0f : 1.0f);
+ Rune rune;
+ FT_UInt glyphidx;
+ FcResult fcres;
+ FcPattern *fcpattern, *fontpattern;
+ FcFontSet *fcsets[] = { NULL };
+ FcCharSet *fccharset;
+- int i, f, numspecs = 0;
++ int f, code_idx, numspecs = 0;
++ float cluster_xp = xp, cluster_yp = yp;
++ HbTransformData shaped = { 0 };
+
+- 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;
++ /* Initial values. */
++ xresetfontsettings(mode, &font, &frcflags);
+
+- /* Skip dummy wide-character spacing. */
+- if (mode == ATTR_WDUMMY)
++ /* Shape the segment. */
++ hbtransform(&shaped, font->match, glyphs, 0, len);
++ xp = winx; yp = winy + font->ascent;
++ cluster_xp = xp; cluster_yp = yp;
++
++ for (code_idx = 0; code_idx < shaped.count; code_idx++) {
++ int idx = shaped.glyphs[code_idx].cluster;
++
++ if (glyphs[idx].mode & ATTR_WDUMMY)
+ 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;
+- }
+- yp = winy + font->ascent;
++ /* 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;
+ }
+
+- /* Lookup character index with default font. */
+- glyphidx = XftCharIndex(xw.dpy, font->match, rune);
+- if (glyphidx) {
++ if (shaped.glyphs[code_idx].codepoint != 0) {
++ /* If symbol is found, put it into the specs. */
+ specs[numspecs].font = font->match;
+- specs[numspecs].glyph = glyphidx;
+- specs[numspecs].x = (short)xp;
+- specs[numspecs].y = (short)yp;
+- xp += runewidth;
++ 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++;
+- 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;
++ } else {
++ /* If it's not found, try to fetch it through the font cache. */
++ rune = glyphs[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));
++ /* 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);
+ }
+
+- 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++;
+ }
+-
+- specs[numspecs].font = frc[f].font;
+- specs[numspecs].glyph = glyphidx;
+- specs[numspecs].x = (short)xp;
+- specs[numspecs].y = (short)yp;
+- xp += runewidth;
+- numspecs++;
+ }
+
++ /* Cleanup and get ready for next segment. */
++ 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;
+ XRenderColor colfg, colbg;
+@@ -1526,23 +1542,26 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
+ 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;
+
+@@ -1674,30 +1693,30 @@ xdrawline(Line line, int x1, int y1, int x2)
+ int i, x, ox, numspecs;
+ 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) {
+ ox = x;
+ base = new;
+ }
+ 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
+ xfinishdraw(void)
diff --git a/st.suckless.org/patches/ligatures/0.9.2/st-ligatures-alpha-scrollback-ringbuffer-20240427-0.9.2.diff b/st.suckless.org/patches/ligatures/0.9.2/st-ligatures-alpha-scrollback-ringbuffer-20240427-0.9.2.diff
@@ -1,635 +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: st
-@@ -22,7 +22,8 @@ config.h:
- $(CC) $(STCFLAGS) -c $<
-
- st.o: config.h st.h win.h
--x.o: arg.h config.h st.h win.h
-+x.o: arg.h config.h st.h win.h hb.h
-+hb.o: st.h
-
- $(OBJ): config.h config.mk
-
-diff --git a/config.mk b/config.mk
-index 47c615e..d7439a3 100644
---- a/config.mk
-+++ b/config.mk
-@@ -15,10 +15,12 @@ PKG_CONFIG = pkg-config
- # includes and libs
- INCS = -I$(X11INC) \
- `$(PKG_CONFIG) --cflags fontconfig` \
-- `$(PKG_CONFIG) --cflags freetype2`
--LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft -lXrender\
-+ `$(PKG_CONFIG) --cflags freetype2` \
-+ `$(PKG_CONFIG) --cflags harfbuzz`
-+LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft -lXrender \
- `$(PKG_CONFIG) --libs fontconfig` \
-- `$(PKG_CONFIG) --libs freetype2`
-+ `$(PKG_CONFIG) --libs freetype2` \
-+ `$(PKG_CONFIG) --libs harfbuzz`
-
- # flags
- STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600
-diff --git a/hb.c b/hb.c
-new file mode 100644
-index 0000000..99412c8
---- /dev/null
-+++ b/hb.c
-@@ -0,0 +1,125 @@
-+#include <stdlib.h>
-+#include <stdio.h>
-+#include <math.h>
-+#include <X11/Xft/Xft.h>
-+#include <X11/cursorfont.h>
-+#include <hb.h>
-+#include <hb-ft.h>
-+
-+#include "st.h"
-+#include "hb.h"
-+
-+#define FEATURE(c1,c2,c3,c4) { .tag = HB_TAG(c1,c2,c3,c4), .value = 1, .start = HB_FEATURE_GLOBAL_START, .end = HB_FEATURE_GLOBAL_END }
-+#define BUFFER_STEP 256
-+
-+hb_font_t *hbfindfont(XftFont *match);
-+
-+typedef struct {
-+ XftFont *match;
-+ hb_font_t *font;
-+} HbFontMatch;
-+
-+typedef struct {
-+ size_t capacity;
-+ HbFontMatch *fonts;
-+} HbFontCache;
-+
-+static HbFontCache hbfontcache = { 0, NULL };
-+
-+typedef struct {
-+ size_t capacity;
-+ Rune *runes;
-+} RuneBuffer;
-+
-+static RuneBuffer hbrunebuffer = { 0, NULL };
-+
-+/*
-+ * Poplulate the array with a list of font features, wrapped in FEATURE macro,
-+ * e. g.
-+ * FEATURE('c', 'a', 'l', 't'), FEATURE('d', 'l', 'i', 'g')
-+ */
-+hb_feature_t features[] = { };
-+
-+void
-+hbunloadfonts()
-+{
-+ for (int i = 0; i < hbfontcache.capacity; i++) {
-+ hb_font_destroy(hbfontcache.fonts[i].font);
-+ XftUnlockFace(hbfontcache.fonts[i].match);
-+ }
-+
-+ if (hbfontcache.fonts != NULL) {
-+ free(hbfontcache.fonts);
-+ hbfontcache.fonts = NULL;
-+ }
-+ hbfontcache.capacity = 0;
-+}
-+
-+hb_font_t *
-+hbfindfont(XftFont *match)
-+{
-+ for (int i = 0; i < hbfontcache.capacity; i++) {
-+ if (hbfontcache.fonts[i].match == match)
-+ return hbfontcache.fonts[i].font;
-+ }
-+
-+ /* Font not found in cache, caching it now. */
-+ hbfontcache.fonts = realloc(hbfontcache.fonts, sizeof(HbFontMatch) * (hbfontcache.capacity + 1));
-+ FT_Face face = XftLockFace(match);
-+ hb_font_t *font = hb_ft_font_create(face, NULL);
-+ if (font == NULL)
-+ die("Failed to load Harfbuzz font.");
-+
-+ hbfontcache.fonts[hbfontcache.capacity].match = match;
-+ hbfontcache.fonts[hbfontcache.capacity].font = font;
-+ hbfontcache.capacity += 1;
-+
-+ return font;
-+}
-+
-+void hbtransform(HbTransformData *data, XftFont *xfont, const Glyph *glyphs, int start, int length) {
-+ ushort mode = USHRT_MAX;
-+ unsigned int glyph_count;
-+ int rune_idx, glyph_idx, end = start + length;
-+
-+ hb_font_t *font = hbfindfont(xfont);
-+ if (font == NULL)
-+ return;
-+
-+ hb_buffer_t *buffer = hb_buffer_create();
-+ hb_buffer_set_direction(buffer, HB_DIRECTION_LTR);
-+ hb_buffer_set_cluster_level(buffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS);
-+
-+ /* Resize the buffer if required length is larger. */
-+ if (hbrunebuffer.capacity < length) {
-+ hbrunebuffer.capacity = (length / BUFFER_STEP + 1) * BUFFER_STEP;
-+ hbrunebuffer.runes = realloc(hbrunebuffer.runes, hbrunebuffer.capacity * sizeof(Rune));
-+ }
-+
-+ /* Fill buffer with codepoints. */
-+ for (rune_idx = 0, glyph_idx = start; glyph_idx < end; glyph_idx++, rune_idx++) {
-+ hbrunebuffer.runes[rune_idx] = glyphs[glyph_idx].u;
-+ mode = glyphs[glyph_idx].mode;
-+ if (mode & ATTR_WDUMMY)
-+ hbrunebuffer.runes[rune_idx] = 0x0020;
-+ }
-+ hb_buffer_add_codepoints(buffer, hbrunebuffer.runes, length, 0, length);
-+
-+ /* Shape the segment. */
-+ hb_shape(font, buffer, features, sizeof(features)/sizeof(hb_feature_t));
-+
-+ /* Get new glyph info. */
-+ hb_glyph_info_t *info = hb_buffer_get_glyph_infos(buffer, &glyph_count);
-+ hb_glyph_position_t *pos = hb_buffer_get_glyph_positions(buffer, &glyph_count);
-+
-+ /* Fill the output. */
-+ data->buffer = buffer;
-+ data->glyphs = info;
-+ data->positions = pos;
-+ data->count = glyph_count;
-+}
-+
-+void hbcleanup(HbTransformData *data) {
-+ hb_buffer_destroy(data->buffer);
-+ memset(data, 0, sizeof(HbTransformData));
-+}
-diff --git a/hb.h b/hb.h
-new file mode 100644
-index 0000000..3b0ef44
---- /dev/null
-+++ b/hb.h
-@@ -0,0 +1,14 @@
-+#include <X11/Xft/Xft.h>
-+#include <hb.h>
-+#include <hb-ft.h>
-+
-+typedef struct {
-+ hb_buffer_t *buffer;
-+ hb_glyph_info_t *glyphs;
-+ hb_glyph_position_t *positions;
-+ unsigned int count;
-+} HbTransformData;
-+
-+void hbunloadfonts();
-+void hbtransform(HbTransformData *, XftFont *, const Glyph *, int, int);
-+void hbcleanup(HbTransformData *);
-diff --git a/st.c b/st.c
-index c44797b..18aa1bf 100644
---- a/st.c
-+++ b/st.c
-@@ -2759,7 +2759,9 @@ draw(void)
- drawregion(0, 0, term.col, term.row);
- if (TSCREEN.off == 0)
- xdrawcursor(cx, term.c.y, TLINE(term.c.y)[cx],
-- term.ocx, term.ocy, TLINE(term.ocy)[term.ocx]);
-+ term.ocx, term.ocy, TLINE(term.ocy)[term.ocx],
-+ TLINE(term.ocy), term.col);
-+
- term.ocx = cx;
- term.ocy = term.c.y;
- xfinishdraw();
-diff --git a/st.h b/st.h
-index 073851a..d0b071d 100644
---- a/st.h
-+++ b/st.h
-@@ -11,7 +11,8 @@
- #define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d))
- #define DEFAULT(a, b) (a) = (a) ? (a) : (b)
- #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)
--#define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \
-+#define ATTRCMP(a, b) (((a).mode & (~ATTR_WRAP)) != ((b).mode & (~ATTR_WRAP)) || \
-+ (a).fg != (b).fg || \
- (a).bg != (b).bg)
- #define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \
- (t1.tv_nsec-t2.tv_nsec)/1E6)
-diff --git a/win.h b/win.h
-index 6de960d..94679e4 100644
---- a/win.h
-+++ b/win.h
-@@ -25,7 +25,7 @@ enum win_mode {
-
- void xbell(void);
- void xclipcopy(void);
--void xdrawcursor(int, int, Glyph, int, int, Glyph);
-+void xdrawcursor(int, int, Glyph, int, int, Glyph, Line, int);
- void xdrawline(Line, int, int, int);
- void xfinishdraw(void);
- void xloadcols(void);
-diff --git a/x.c b/x.c
-index b81f5be..c1611bb 100644
---- a/x.c
-+++ b/x.c
-@@ -19,6 +19,7 @@ char *argv0;
- #include "arg.h"
- #include "st.h"
- #include "win.h"
-+#include "hb.h"
-
- /* types used in config.h */
- typedef struct {
-@@ -144,8 +145,9 @@ typedef struct {
- } DC;
-
- static inline ushort sixd_to_16bit(int);
-+static void xresetfontsettings(ushort mode, Font **font, int *frcflags);
- static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int);
--static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int);
-+static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int, int);
- static void xdrawglyph(Glyph, int, int);
- static void xclear(int, int, int, int);
- static int xgeommasktogravity(int);
-@@ -761,7 +763,7 @@ xresize(int col, int row)
- xclear(0, 0, win.w, win.h);
-
- /* resize to new width */
-- xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec));
-+ xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec) * 4);
- }
-
- ushort
-@@ -1073,6 +1075,9 @@ xunloadfont(Font *f)
- void
- xunloadfonts(void)
- {
-+ /* Clear Harfbuzz font cache. */
-+ hbunloadfonts();
-+
- /* Free the loaded fonts in the font cache. */
- while (frclen > 0)
- XftFontClose(xw.dpy, frc[--frclen].font);
-@@ -1204,7 +1209,7 @@ xinit(int cols, int rows)
- XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h);
-
- /* font spec buffer */
-- xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec));
-+ xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec) * 4);
-
- /* Xft rendering context */
- xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap);
-@@ -1258,6 +1263,22 @@ xinit(int cols, int rows)
- xsel.xtarget = XA_STRING;
- }
-
-+void
-+xresetfontsettings(ushort mode, Font **font, int *frcflags)
-+{
-+ *font = &dc.font;
-+ if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) {
-+ *font = &dc.ibfont;
-+ *frcflags = FRC_ITALICBOLD;
-+ } else if (mode & ATTR_ITALIC) {
-+ *font = &dc.ifont;
-+ *frcflags = FRC_ITALIC;
-+ } else if (mode & ATTR_BOLD) {
-+ *font = &dc.bfont;
-+ *frcflags = FRC_BOLD;
-+ }
-+}
-+
- int
- xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y)
- {
-@@ -1272,128 +1293,156 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
- FcPattern *fcpattern, *fontpattern;
- FcFontSet *fcsets[] = { NULL };
- FcCharSet *fccharset;
-- int i, f, numspecs = 0;
-+ int i, f, length = 0, start = 0, numspecs = 0;
-+ float cluster_xp = xp, cluster_yp = yp;
-+ HbTransformData shaped = { 0 };
-+
-+ /* Initial values. */
-+ mode = prevmode = glyphs[0].mode & ~ATTR_WRAP;
-+ xresetfontsettings(mode, &font, &frcflags);
-
- for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) {
-- /* Fetch rune and mode for current glyph. */
-- rune = glyphs[i].u;
-- mode = glyphs[i].mode;
-+ mode = glyphs[i].mode & ~ATTR_WRAP;
-
- /* Skip dummy wide-character spacing. */
-- if (mode == ATTR_WDUMMY)
-+ if (mode & ATTR_WDUMMY && i < (len - 1))
- continue;
-
-- /* Determine font for glyph if different from previous glyph. */
-- if (prevmode != mode) {
-- prevmode = mode;
-- font = &dc.font;
-- frcflags = FRC_NORMAL;
-- runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f);
-- if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) {
-- font = &dc.ibfont;
-- frcflags = FRC_ITALICBOLD;
-- } else if (mode & ATTR_ITALIC) {
-- font = &dc.ifont;
-- frcflags = FRC_ITALIC;
-- } else if (mode & ATTR_BOLD) {
-- font = &dc.bfont;
-- frcflags = FRC_BOLD;
-+ if (
-+ prevmode != mode
-+ || ATTRCMP(glyphs[start], glyphs[i])
-+ || selected(x + i, y) != selected(x + start, y)
-+ || i == (len - 1)
-+ ) {
-+ /* Handle 1-character wide segments and end of line */
-+ length = i - start;
-+ if (i == start) {
-+ length = 1;
-+ } else if (i == (len - 1)) {
-+ length = (i - start + 1);
- }
-- yp = winy + font->ascent;
-- }
-
-- /* Lookup character index with default font. */
-- glyphidx = XftCharIndex(xw.dpy, font->match, rune);
-- if (glyphidx) {
-- specs[numspecs].font = font->match;
-- specs[numspecs].glyph = glyphidx;
-- specs[numspecs].x = (short)xp;
-- specs[numspecs].y = (short)yp;
-- xp += runewidth;
-- numspecs++;
-- continue;
-- }
--
-- /* Fallback on font cache, search the font cache for match. */
-- for (f = 0; f < frclen; f++) {
-- glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune);
-- /* Everything correct. */
-- if (glyphidx && frc[f].flags == frcflags)
-- break;
-- /* We got a default font for a not found glyph. */
-- if (!glyphidx && frc[f].flags == frcflags
-- && frc[f].unicodep == rune) {
-- break;
-+ /* Shape the segment. */
-+ hbtransform(&shaped, font->match, glyphs, start, length);
-+ runewidth = win.cw * ((glyphs[start].mode & ATTR_WIDE) ? 2.0f : 1.0f);
-+ cluster_xp = xp; cluster_yp = yp;
-+ for (int code_idx = 0; code_idx < shaped.count; code_idx++) {
-+ int idx = shaped.glyphs[code_idx].cluster;
-+
-+ if (glyphs[start + idx].mode & ATTR_WDUMMY)
-+ continue;
-+
-+ /* Advance the drawing cursor if we've moved to a new cluster */
-+ if (code_idx > 0 && idx != shaped.glyphs[code_idx - 1].cluster) {
-+ xp += runewidth;
-+ cluster_xp = xp;
-+ cluster_yp = yp;
-+ runewidth = win.cw * ((glyphs[start + idx].mode & ATTR_WIDE) ? 2.0f : 1.0f);
-+ }
-+
-+ if (shaped.glyphs[code_idx].codepoint != 0) {
-+ /* If symbol is found, put it into the specs. */
-+ specs[numspecs].font = font->match;
-+ specs[numspecs].glyph = shaped.glyphs[code_idx].codepoint;
-+ specs[numspecs].x = cluster_xp + (short)(shaped.positions[code_idx].x_offset / 64.);
-+ specs[numspecs].y = cluster_yp - (short)(shaped.positions[code_idx].y_offset / 64.);
-+ cluster_xp += shaped.positions[code_idx].x_advance / 64.;
-+ cluster_yp += shaped.positions[code_idx].y_advance / 64.;
-+ numspecs++;
-+ } else {
-+ /* If it's not found, try to fetch it through the font cache. */
-+ rune = glyphs[start + idx].u;
-+ for (f = 0; f < frclen; f++) {
-+ glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune);
-+ /* Everything correct. */
-+ if (glyphidx && frc[f].flags == frcflags)
-+ break;
-+ /* We got a default font for a not found glyph. */
-+ if (!glyphidx && frc[f].flags == frcflags
-+ && frc[f].unicodep == rune) {
-+ break;
-+ }
-+ }
-+
-+ /* Nothing was found. Use fontconfig to find matching font. */
-+ if (f >= frclen) {
-+ if (!font->set)
-+ font->set = FcFontSort(0, font->pattern,
-+ 1, 0, &fcres);
-+ fcsets[0] = font->set;
-+
-+ /*
-+ * Nothing was found in the cache. Now use
-+ * some dozen of Fontconfig calls to get the
-+ * font for one single character.
-+ *
-+ * Xft and fontconfig are design failures.
-+ */
-+ fcpattern = FcPatternDuplicate(font->pattern);
-+ fccharset = FcCharSetCreate();
-+
-+ FcCharSetAddChar(fccharset, rune);
-+ FcPatternAddCharSet(fcpattern, FC_CHARSET,
-+ fccharset);
-+ FcPatternAddBool(fcpattern, FC_SCALABLE, 1);
-+
-+ FcConfigSubstitute(0, fcpattern,
-+ FcMatchPattern);
-+ FcDefaultSubstitute(fcpattern);
-+
-+ fontpattern = FcFontSetMatch(0, fcsets, 1,
-+ fcpattern, &fcres);
-+
-+ /* Allocate memory for the new cache entry. */
-+ if (frclen >= frccap) {
-+ frccap += 16;
-+ frc = xrealloc(frc, frccap * sizeof(Fontcache));
-+ }
-+
-+ frc[frclen].font = XftFontOpenPattern(xw.dpy,
-+ fontpattern);
-+ if (!frc[frclen].font)
-+ die("XftFontOpenPattern failed seeking fallback font: %s\n",
-+ strerror(errno));
-+ frc[frclen].flags = frcflags;
-+ frc[frclen].unicodep = rune;
-+
-+ glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune);
-+
-+ f = frclen;
-+ frclen++;
-+
-+ FcPatternDestroy(fcpattern);
-+ FcCharSetDestroy(fccharset);
-+ }
-+
-+ specs[numspecs].font = frc[f].font;
-+ specs[numspecs].glyph = glyphidx;
-+ specs[numspecs].x = (short)xp;
-+ specs[numspecs].y = (short)yp;
-+ numspecs++;
-+ }
- }
-- }
-
-- /* Nothing was found. Use fontconfig to find matching font. */
-- if (f >= frclen) {
-- if (!font->set)
-- font->set = FcFontSort(0, font->pattern,
-- 1, 0, &fcres);
-- fcsets[0] = font->set;
-+ /* Cleanup and get ready for next segment. */
-+ hbcleanup(&shaped);
-+ start = i;
-
-- /*
-- * Nothing was found in the cache. Now use
-- * some dozen of Fontconfig calls to get the
-- * font for one single character.
-- *
-- * Xft and fontconfig are design failures.
-- */
-- fcpattern = FcPatternDuplicate(font->pattern);
-- fccharset = FcCharSetCreate();
--
-- FcCharSetAddChar(fccharset, rune);
-- FcPatternAddCharSet(fcpattern, FC_CHARSET,
-- fccharset);
-- FcPatternAddBool(fcpattern, FC_SCALABLE, 1);
--
-- FcConfigSubstitute(0, fcpattern,
-- FcMatchPattern);
-- FcDefaultSubstitute(fcpattern);
--
-- fontpattern = FcFontSetMatch(0, fcsets, 1,
-- fcpattern, &fcres);
--
-- /* Allocate memory for the new cache entry. */
-- if (frclen >= frccap) {
-- frccap += 16;
-- frc = xrealloc(frc, frccap * sizeof(Fontcache));
-+ /* Determine font for glyph if different from previous glyph. */
-+ if (prevmode != mode) {
-+ prevmode = mode;
-+ xresetfontsettings(mode, &font, &frcflags);
-+ yp = winy + font->ascent;
- }
--
-- frc[frclen].font = XftFontOpenPattern(xw.dpy,
-- fontpattern);
-- if (!frc[frclen].font)
-- die("XftFontOpenPattern failed seeking fallback font: %s\n",
-- strerror(errno));
-- frc[frclen].flags = frcflags;
-- frc[frclen].unicodep = rune;
--
-- glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune);
--
-- f = frclen;
-- frclen++;
--
-- FcPatternDestroy(fcpattern);
-- FcCharSetDestroy(fccharset);
- }
--
-- specs[numspecs].font = frc[f].font;
-- specs[numspecs].glyph = glyphidx;
-- specs[numspecs].x = (short)xp;
-- specs[numspecs].y = (short)yp;
-- xp += runewidth;
-- numspecs++;
- }
-
- return numspecs;
- }
-
- void
--xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y)
-+xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y, int charlen)
- {
-- int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1);
- int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch,
- width = charlen * win.cw;
- Color *fg, *bg, *temp, revfg, revbg, truefg, truebg;
-@@ -1529,21 +1578,24 @@ void
- xdrawglyph(Glyph g, int x, int y)
- {
- int numspecs;
-- XftGlyphFontSpec spec;
-+ XftGlyphFontSpec *specs = xw.specbuf;
-
-- numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y);
-- xdrawglyphfontspecs(&spec, g, numspecs, x, y);
-+ numspecs = xmakeglyphfontspecs(specs, &g, 1, x, y);
-+ xdrawglyphfontspecs(specs, g, numspecs, x, y, (g.mode & ATTR_WIDE) ? 2 : 1);
- }
-
- void
--xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
-+xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og, Line line, int len)
- {
- Color drawcol;
-
- /* remove the old cursor */
- if (selected(ox, oy))
- og.mode ^= ATTR_REVERSE;
-- xdrawglyph(og, ox, oy);
-+
-+ /* Redraw the line where cursor was previously.
-+ * It will restore the ligatures broken by the cursor. */
-+ xdrawline(line, 0, oy, len);
-
- if (IS_SET(MODE_HIDE))
- return;
-@@ -1671,18 +1723,16 @@ xdrawline(Line line, int x1, int y1, int x2)
- Glyph base, new;
- XftGlyphFontSpec *specs = xw.specbuf;
-
-- numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1);
- i = ox = 0;
-- for (x = x1; x < x2 && i < numspecs; x++) {
-+ for (x = x1; x < x2; x++) {
- new = line[x];
- if (new.mode == ATTR_WDUMMY)
- continue;
- if (selected(x, y1))
- new.mode ^= ATTR_REVERSE;
-- if (i > 0 && ATTRCMP(base, new)) {
-- xdrawglyphfontspecs(specs, base, i, ox, y1);
-- specs += i;
-- numspecs -= i;
-+ if ((i > 0) && ATTRCMP(base, new)) {
-+ numspecs = xmakeglyphfontspecs(specs, &line[ox], x - ox, ox, y1);
-+ xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x - ox);
- i = 0;
- }
- if (i == 0) {
-@@ -1691,8 +1741,10 @@ xdrawline(Line line, int x1, int y1, int x2)
- }
- i++;
- }
-- if (i > 0)
-- xdrawglyphfontspecs(specs, base, i, ox, y1);
-+ if (i > 0) {
-+ numspecs = xmakeglyphfontspecs(specs, &line[ox], x2 - ox, ox, y1);
-+ xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x2 - ox);
-+ }
- }
-
- void
diff --git a/st.suckless.org/patches/ligatures/0.9.2/st-ligatures-alpha-scrollback-ringbuffer-20241226-0.9.2.diff b/st.suckless.org/patches/ligatures/0.9.2/st-ligatures-alpha-scrollback-ringbuffer-20241226-0.9.2.diff
@@ -0,0 +1,650 @@
+diff --git a/Makefile b/Makefile
+index 15db421..dfcea0f 100644
+--- a/Makefile
++++ b/Makefile
+@@ -3,9 +3,9 @@
+ .POSIX:
+
+ include config.mk
+
+-SRC = st.c x.c
++SRC = st.c x.c hb.c
+ OBJ = $(SRC:.c=.o)
+
+ all: st
+
+@@ -15,9 +15,10 @@ config.h:
+ .c.o:
+ $(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
+
+ st: $(OBJ)
+diff --git a/config.mk b/config.mk
+index 069a6c2..977b7c7 100644
+--- a/config.mk
++++ b/config.mk
+@@ -14,12 +14,14 @@ 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 -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
+ STCFLAGS = $(INCS) $(STCPPFLAGS) $(CPPFLAGS) $(CFLAGS)
+@@ -28,9 +30,10 @@ STLDFLAGS = $(LIBS) $(LDFLAGS)
+ # OpenBSD:
+ #CPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 -D_BSD_SOURCE
+ #LIBS = -L$(X11LIB) -lm -lX11 -lutil -lXft \
+ # `$(PKG_CONFIG) --libs fontconfig` \
+-# `$(PKG_CONFIG) --libs freetype2`
++# `$(PKG_CONFIG) --libs freetype2` \
++# `$(PKG_CONFIG) --libs harfbuzz`
+ #MANPREFIX = ${PREFIX}/man
+
+ # compiler and linker
+ # CC = c99
+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 d9b163e..fbca4ba 100644
+--- a/st.c
++++ b/st.c
+@@ -2777,9 +2777,10 @@ 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();
+ if (ocx != term.ocx || ocy != term.ocy)
+diff --git a/st.h b/st.h
+index 073851a..d0b071d 100644
+--- a/st.h
++++ b/st.h
+@@ -10,9 +10,10 @@
+ #define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b))
+ #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)
+ #define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit)))
+diff --git a/win.h b/win.h
+index 6de960d..94679e4 100644
+--- a/win.h
++++ b/win.h
+@@ -24,9 +24,9 @@ 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);
+ int xsetcolorname(int, const char *);
+diff --git a/x.c b/x.c
+index c497e53..a213e52 100644
+--- a/x.c
++++ b/x.c
+@@ -18,8 +18,9 @@
+ char *argv0;
+ #include "arg.h"
+ #include "st.h"
+ #include "win.h"
++#include "hb.h"
+
+ /* types used in config.h */
+ typedef struct {
+ uint mod;
+@@ -143,10 +144,11 @@ typedef struct {
+ GC gc;
+ } 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);
+ static int ximopen(Display *);
+@@ -760,9 +762,9 @@ xresize(int col, int row)
+ XftDrawChange(xw.draw, xw.buf);
+ 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
+ sixd_to_16bit(int x)
+@@ -1072,8 +1074,11 @@ 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);
+
+@@ -1203,9 +1208,9 @@ xinit(int cols, int rows)
+ XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel);
+ 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);
+
+@@ -1257,144 +1262,155 @@ xinit(int cols, int rows)
+ if (xsel.xtarget == None)
+ 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)
+ {
+ float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp;
+- ushort mode, prevmode = USHRT_MAX;
++ ushort mode = glyphs[0].mode & ~ATTR_WRAP;
+ Font *font = &dc.font;
+ int frcflags = FRC_NORMAL;
+- float runewidth = win.cw;
++ float runewidth = win.cw * ((glyphs[0].mode & ATTR_WIDE) ? 2.0f : 1.0f);
+ Rune rune;
+ FT_UInt glyphidx;
+ FcResult fcres;
+ FcPattern *fcpattern, *fontpattern;
+ FcFontSet *fcsets[] = { NULL };
+ FcCharSet *fccharset;
+- int i, f, numspecs = 0;
++ int f, code_idx, numspecs = 0;
++ float cluster_xp = xp, cluster_yp = yp;
++ HbTransformData shaped = { 0 };
+
+- 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;
++ /* Initial values. */
++ xresetfontsettings(mode, &font, &frcflags);
+
+- /* Skip dummy wide-character spacing. */
+- if (mode == ATTR_WDUMMY)
++ /* Shape the segment. */
++ hbtransform(&shaped, font->match, glyphs, 0, len);
++ xp = winx; yp = winy + font->ascent;
++ cluster_xp = xp; cluster_yp = yp;
++
++ for (code_idx = 0; code_idx < shaped.count; code_idx++) {
++ int idx = shaped.glyphs[code_idx].cluster;
++
++ if (glyphs[idx].mode & ATTR_WDUMMY)
+ 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;
+- }
+- yp = winy + font->ascent;
++ /* 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;
+ }
+
+- /* Lookup character index with default font. */
+- glyphidx = XftCharIndex(xw.dpy, font->match, rune);
+- if (glyphidx) {
++ if (shaped.glyphs[code_idx].codepoint != 0) {
++ /* If symbol is found, put it into the specs. */
+ specs[numspecs].font = font->match;
+- specs[numspecs].glyph = glyphidx;
+- specs[numspecs].x = (short)xp;
+- specs[numspecs].y = (short)yp;
+- xp += runewidth;
++ 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++;
+- 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;
++ } else {
++ /* If it's not found, try to fetch it through the font cache. */
++ rune = glyphs[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));
++ /* 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);
+ }
+
+- 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++;
+ }
+-
+- specs[numspecs].font = frc[f].font;
+- specs[numspecs].glyph = glyphidx;
+- specs[numspecs].x = (short)xp;
+- specs[numspecs].y = (short)yp;
+- xp += runewidth;
+- numspecs++;
+ }
+
++ /* Cleanup and get ready for next segment. */
++ 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;
+ XRenderColor colfg, colbg;
+@@ -1528,23 +1544,26 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
+ 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;
+
+@@ -1676,30 +1695,30 @@ xdrawline(Line line, int x1, int y1, int x2)
+ int i, x, ox, numspecs;
+ 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) {
+ ox = x;
+ base = new;
+ }
+ 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
+ xfinishdraw(void)
diff --git a/st.suckless.org/patches/ligatures/0.9.2/st-ligatures-boxdraw-20240427-0.9.2.diff b/st.suckless.org/patches/ligatures/0.9.2/st-ligatures-boxdraw-20240427-0.9.2.diff
@@ -1,647 +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: st
-@@ -22,8 +22,9 @@ config.h:
- $(CC) $(STCFLAGS) -c $<
-
- st.o: config.h st.h win.h
--x.o: arg.h config.h st.h win.h
-+x.o: arg.h config.h st.h win.h hb.h
- boxdraw.o: config.h st.h boxdraw_data.h
-+hb.o: st.h
-
- $(OBJ): config.h config.mk
-
-diff --git a/config.mk b/config.mk
-index 1e306f8..3e13e53 100644
---- a/config.mk
-+++ b/config.mk
-@@ -15,10 +15,12 @@ PKG_CONFIG = pkg-config
- # includes and libs
- INCS = -I$(X11INC) \
- `$(PKG_CONFIG) --cflags fontconfig` \
-- `$(PKG_CONFIG) --cflags freetype2`
-+ `$(PKG_CONFIG) --cflags freetype2` \
-+ `$(PKG_CONFIG) --cflags harfbuzz`
- LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft \
- `$(PKG_CONFIG) --libs fontconfig` \
-- `$(PKG_CONFIG) --libs freetype2`
-+ `$(PKG_CONFIG) --libs freetype2` \
-+ `$(PKG_CONFIG) --libs harfbuzz`
-
- # flags
- STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600
-diff --git a/hb.c b/hb.c
-new file mode 100644
-index 0000000..99412c8
---- /dev/null
-+++ b/hb.c
-@@ -0,0 +1,125 @@
-+#include <stdlib.h>
-+#include <stdio.h>
-+#include <math.h>
-+#include <X11/Xft/Xft.h>
-+#include <X11/cursorfont.h>
-+#include <hb.h>
-+#include <hb-ft.h>
-+
-+#include "st.h"
-+#include "hb.h"
-+
-+#define FEATURE(c1,c2,c3,c4) { .tag = HB_TAG(c1,c2,c3,c4), .value = 1, .start = HB_FEATURE_GLOBAL_START, .end = HB_FEATURE_GLOBAL_END }
-+#define BUFFER_STEP 256
-+
-+hb_font_t *hbfindfont(XftFont *match);
-+
-+typedef struct {
-+ XftFont *match;
-+ hb_font_t *font;
-+} HbFontMatch;
-+
-+typedef struct {
-+ size_t capacity;
-+ HbFontMatch *fonts;
-+} HbFontCache;
-+
-+static HbFontCache hbfontcache = { 0, NULL };
-+
-+typedef struct {
-+ size_t capacity;
-+ Rune *runes;
-+} RuneBuffer;
-+
-+static RuneBuffer hbrunebuffer = { 0, NULL };
-+
-+/*
-+ * Poplulate the array with a list of font features, wrapped in FEATURE macro,
-+ * e. g.
-+ * FEATURE('c', 'a', 'l', 't'), FEATURE('d', 'l', 'i', 'g')
-+ */
-+hb_feature_t features[] = { };
-+
-+void
-+hbunloadfonts()
-+{
-+ for (int i = 0; i < hbfontcache.capacity; i++) {
-+ hb_font_destroy(hbfontcache.fonts[i].font);
-+ XftUnlockFace(hbfontcache.fonts[i].match);
-+ }
-+
-+ if (hbfontcache.fonts != NULL) {
-+ free(hbfontcache.fonts);
-+ hbfontcache.fonts = NULL;
-+ }
-+ hbfontcache.capacity = 0;
-+}
-+
-+hb_font_t *
-+hbfindfont(XftFont *match)
-+{
-+ for (int i = 0; i < hbfontcache.capacity; i++) {
-+ if (hbfontcache.fonts[i].match == match)
-+ return hbfontcache.fonts[i].font;
-+ }
-+
-+ /* Font not found in cache, caching it now. */
-+ hbfontcache.fonts = realloc(hbfontcache.fonts, sizeof(HbFontMatch) * (hbfontcache.capacity + 1));
-+ FT_Face face = XftLockFace(match);
-+ hb_font_t *font = hb_ft_font_create(face, NULL);
-+ if (font == NULL)
-+ die("Failed to load Harfbuzz font.");
-+
-+ hbfontcache.fonts[hbfontcache.capacity].match = match;
-+ hbfontcache.fonts[hbfontcache.capacity].font = font;
-+ hbfontcache.capacity += 1;
-+
-+ return font;
-+}
-+
-+void hbtransform(HbTransformData *data, XftFont *xfont, const Glyph *glyphs, int start, int length) {
-+ ushort mode = USHRT_MAX;
-+ unsigned int glyph_count;
-+ int rune_idx, glyph_idx, end = start + length;
-+
-+ hb_font_t *font = hbfindfont(xfont);
-+ if (font == NULL)
-+ return;
-+
-+ hb_buffer_t *buffer = hb_buffer_create();
-+ hb_buffer_set_direction(buffer, HB_DIRECTION_LTR);
-+ hb_buffer_set_cluster_level(buffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS);
-+
-+ /* Resize the buffer if required length is larger. */
-+ if (hbrunebuffer.capacity < length) {
-+ hbrunebuffer.capacity = (length / BUFFER_STEP + 1) * BUFFER_STEP;
-+ hbrunebuffer.runes = realloc(hbrunebuffer.runes, hbrunebuffer.capacity * sizeof(Rune));
-+ }
-+
-+ /* Fill buffer with codepoints. */
-+ for (rune_idx = 0, glyph_idx = start; glyph_idx < end; glyph_idx++, rune_idx++) {
-+ hbrunebuffer.runes[rune_idx] = glyphs[glyph_idx].u;
-+ mode = glyphs[glyph_idx].mode;
-+ if (mode & ATTR_WDUMMY)
-+ hbrunebuffer.runes[rune_idx] = 0x0020;
-+ }
-+ hb_buffer_add_codepoints(buffer, hbrunebuffer.runes, length, 0, length);
-+
-+ /* Shape the segment. */
-+ hb_shape(font, buffer, features, sizeof(features)/sizeof(hb_feature_t));
-+
-+ /* Get new glyph info. */
-+ hb_glyph_info_t *info = hb_buffer_get_glyph_infos(buffer, &glyph_count);
-+ hb_glyph_position_t *pos = hb_buffer_get_glyph_positions(buffer, &glyph_count);
-+
-+ /* Fill the output. */
-+ data->buffer = buffer;
-+ data->glyphs = info;
-+ data->positions = pos;
-+ data->count = glyph_count;
-+}
-+
-+void hbcleanup(HbTransformData *data) {
-+ hb_buffer_destroy(data->buffer);
-+ memset(data, 0, sizeof(HbTransformData));
-+}
-diff --git a/hb.h b/hb.h
-new file mode 100644
-index 0000000..3b0ef44
---- /dev/null
-+++ b/hb.h
-@@ -0,0 +1,14 @@
-+#include <X11/Xft/Xft.h>
-+#include <hb.h>
-+#include <hb-ft.h>
-+
-+typedef struct {
-+ hb_buffer_t *buffer;
-+ hb_glyph_info_t *glyphs;
-+ hb_glyph_position_t *positions;
-+ unsigned int count;
-+} HbTransformData;
-+
-+void hbunloadfonts();
-+void hbtransform(HbTransformData *, XftFont *, const Glyph *, int, int);
-+void hbcleanup(HbTransformData *);
-diff --git a/st.c b/st.c
-index 41d5ace..1c2edd6 100644
---- a/st.c
-+++ b/st.c
-@@ -2643,7 +2643,8 @@ draw(void)
-
- drawregion(0, 0, term.col, term.row);
- xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
-- term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
-+ term.ocx, term.ocy, term.line[term.ocy][term.ocx],
-+ term.line[term.ocy], term.col);
- term.ocx = cx;
- term.ocy = term.c.y;
- xfinishdraw();
-diff --git a/st.h b/st.h
-index 808f5f7..ae41368 100644
---- a/st.h
-+++ b/st.h
-@@ -11,7 +11,8 @@
- #define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d))
- #define DEFAULT(a, b) (a) = (a) ? (a) : (b)
- #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)
--#define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \
-+#define ATTRCMP(a, b) (((a).mode & (~ATTR_WRAP)) != ((b).mode & (~ATTR_WRAP)) || \
-+ (a).fg != (b).fg || \
- (a).bg != (b).bg)
- #define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \
- (t1.tv_nsec-t2.tv_nsec)/1E6)
-diff --git a/win.h b/win.h
-index 6de960d..94679e4 100644
---- a/win.h
-+++ b/win.h
-@@ -25,7 +25,7 @@ enum win_mode {
-
- void xbell(void);
- void xclipcopy(void);
--void xdrawcursor(int, int, Glyph, int, int, Glyph);
-+void xdrawcursor(int, int, Glyph, int, int, Glyph, Line, int);
- void xdrawline(Line, int, int, int);
- void xfinishdraw(void);
- void xloadcols(void);
-diff --git a/x.c b/x.c
-index bf6bbf9..96b117f 100644
---- a/x.c
-+++ b/x.c
-@@ -19,6 +19,7 @@ char *argv0;
- #include "arg.h"
- #include "st.h"
- #include "win.h"
-+#include "hb.h"
-
- /* types used in config.h */
- typedef struct {
-@@ -141,8 +142,9 @@ typedef struct {
- } DC;
-
- static inline ushort sixd_to_16bit(int);
-+static void xresetfontsettings(ushort mode, Font **font, int *frcflags);
- static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int);
--static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int);
-+static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int, int);
- static void xdrawglyph(Glyph, int, int);
- static void xclear(int, int, int, int);
- static int xgeommasktogravity(int);
-@@ -757,7 +759,7 @@ xresize(int col, int row)
- xclear(0, 0, win.w, win.h);
-
- /* resize to new width */
-- xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec));
-+ xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec) * 4);
- }
-
- ushort
-@@ -1062,6 +1064,9 @@ xunloadfont(Font *f)
- void
- xunloadfonts(void)
- {
-+ /* Clear Harfbuzz font cache. */
-+ hbunloadfonts();
-+
- /* Free the loaded fonts in the font cache. */
- while (frclen > 0)
- XftFontClose(xw.dpy, frc[--frclen].font);
-@@ -1185,7 +1190,7 @@ xinit(int cols, int rows)
- XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h);
-
- /* font spec buffer */
-- xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec));
-+ xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec) * 4);
-
- /* Xft rendering context */
- xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap);
-@@ -1241,6 +1246,22 @@ xinit(int cols, int rows)
- boxdraw_xinit(xw.dpy, xw.cmap, xw.draw, xw.vis);
- }
-
-+void
-+xresetfontsettings(ushort mode, Font **font, int *frcflags)
-+{
-+ *font = &dc.font;
-+ if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) {
-+ *font = &dc.ibfont;
-+ *frcflags = FRC_ITALICBOLD;
-+ } else if (mode & ATTR_ITALIC) {
-+ *font = &dc.ifont;
-+ *frcflags = FRC_ITALIC;
-+ } else if (mode & ATTR_BOLD) {
-+ *font = &dc.bfont;
-+ *frcflags = FRC_BOLD;
-+ }
-+}
-+
- int
- xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y)
- {
-@@ -1255,133 +1276,164 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
- FcPattern *fcpattern, *fontpattern;
- FcFontSet *fcsets[] = { NULL };
- FcCharSet *fccharset;
-- int i, f, numspecs = 0;
-+ int i, f, length = 0, start = 0, numspecs = 0;
-+ float cluster_xp = xp, cluster_yp = yp;
-+ HbTransformData shaped = { 0 };
-+
-+ /* Initial values. */
-+ mode = prevmode = glyphs[0].mode & ~ATTR_WRAP;
-+ xresetfontsettings(mode, &font, &frcflags);
-
- for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) {
-- /* Fetch rune and mode for current glyph. */
-- rune = glyphs[i].u;
-- mode = glyphs[i].mode;
-+ mode = glyphs[i].mode & ~ATTR_WRAP;
-
- /* Skip dummy wide-character spacing. */
-- if (mode == ATTR_WDUMMY)
-+ if (mode & ATTR_WDUMMY && i < (len - 1))
- continue;
-
-- /* Determine font for glyph if different from previous glyph. */
-- if (prevmode != mode) {
-- prevmode = mode;
-- font = &dc.font;
-- frcflags = FRC_NORMAL;
-- runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f);
-- if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) {
-- font = &dc.ibfont;
-- frcflags = FRC_ITALICBOLD;
-- } else if (mode & ATTR_ITALIC) {
-- font = &dc.ifont;
-- frcflags = FRC_ITALIC;
-- } else if (mode & ATTR_BOLD) {
-- font = &dc.bfont;
-- frcflags = FRC_BOLD;
-+ if (
-+ prevmode != mode
-+ || ATTRCMP(glyphs[start], glyphs[i])
-+ || selected(x + i, y) != selected(x + start, y)
-+ || i == (len - 1)
-+ ) {
-+ /* Handle 1-character wide segments and end of line */
-+ length = i - start;
-+ if (i == start) {
-+ length = 1;
-+ } else if (i == (len - 1)) {
-+ length = (i - start + 1);
- }
-- yp = winy + font->ascent;
-- }
-
-- if (mode & ATTR_BOXDRAW) {
-- /* minor shoehorning: boxdraw uses only this ushort */
-- glyphidx = boxdrawindex(&glyphs[i]);
-- } else {
-- /* Lookup character index with default font. */
-- glyphidx = XftCharIndex(xw.dpy, font->match, rune);
-- }
-- if (glyphidx) {
-- specs[numspecs].font = font->match;
-- specs[numspecs].glyph = glyphidx;
-- specs[numspecs].x = (short)xp;
-- specs[numspecs].y = (short)yp;
-- xp += runewidth;
-- numspecs++;
-- continue;
-- }
--
-- /* Fallback on font cache, search the font cache for match. */
-- for (f = 0; f < frclen; f++) {
-- glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune);
-- /* Everything correct. */
-- if (glyphidx && frc[f].flags == frcflags)
-- break;
-- /* We got a default font for a not found glyph. */
-- if (!glyphidx && frc[f].flags == frcflags
-- && frc[f].unicodep == rune) {
-- break;
-+ /* Shape the segment. */
-+ hbtransform(&shaped, font->match, glyphs, start, length);
-+ runewidth = win.cw * ((glyphs[start].mode & ATTR_WIDE) ? 2.0f : 1.0f);
-+ cluster_xp = xp; cluster_yp = yp;
-+ for (int code_idx = 0; code_idx < shaped.count; code_idx++) {
-+ int idx = shaped.glyphs[code_idx].cluster;
-+
-+ if (glyphs[start + idx].mode & ATTR_WDUMMY)
-+ continue;
-+
-+ /* Advance the drawing cursor if we've moved to a new cluster */
-+ if (code_idx > 0 && idx != shaped.glyphs[code_idx - 1].cluster) {
-+ xp += runewidth;
-+ cluster_xp = xp;
-+ cluster_yp = yp;
-+ runewidth = win.cw * ((glyphs[start + idx].mode & ATTR_WIDE) ? 2.0f : 1.0f);
-+ }
-+
-+ if (glyphs[start + idx].mode & ATTR_BOXDRAW) {
-+ /* minor shoehorning: boxdraw uses only this ushort */
-+ specs[numspecs].font = font->match;
-+ specs[numspecs].glyph = boxdrawindex(&glyphs[start + idx]);
-+ specs[numspecs].x = xp;
-+ specs[numspecs].y = yp;
-+ numspecs++;
-+ } else if (shaped.glyphs[code_idx].codepoint != 0) {
-+ /* If symbol is found, put it into the specs. */
-+ specs[numspecs].font = font->match;
-+ specs[numspecs].glyph = shaped.glyphs[code_idx].codepoint;
-+ specs[numspecs].x = cluster_xp + (short)(shaped.positions[code_idx].x_offset / 64.);
-+ specs[numspecs].y = cluster_yp - (short)(shaped.positions[code_idx].y_offset / 64.);
-+ cluster_xp += shaped.positions[code_idx].x_advance / 64.;
-+ cluster_yp += shaped.positions[code_idx].y_advance / 64.;
-+ numspecs++;
-+ } else {
-+ /* If it's not found, try to fetch it through the font cache. */
-+ rune = glyphs[start + idx].u;
-+ for (f = 0; f < frclen; f++) {
-+ glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune);
-+ /* Everything correct. */
-+ if (glyphidx && frc[f].flags == frcflags)
-+ break;
-+ /* We got a default font for a not found glyph. */
-+ if (!glyphidx && frc[f].flags == frcflags
-+ && frc[f].unicodep == rune) {
-+ break;
-+ }
-+ }
-+
-+ /* Nothing was found. Use fontconfig to find matching font. */
-+ if (f >= frclen) {
-+ if (!font->set)
-+ font->set = FcFontSort(0, font->pattern,
-+ 1, 0, &fcres);
-+ fcsets[0] = font->set;
-+
-+ /*
-+ * Nothing was found in the cache. Now use
-+ * some dozen of Fontconfig calls to get the
-+ * font for one single character.
-+ *
-+ * Xft and fontconfig are design failures.
-+ */
-+ fcpattern = FcPatternDuplicate(font->pattern);
-+ fccharset = FcCharSetCreate();
-+
-+ FcCharSetAddChar(fccharset, rune);
-+ FcPatternAddCharSet(fcpattern, FC_CHARSET,
-+ fccharset);
-+ FcPatternAddBool(fcpattern, FC_SCALABLE, 1);
-+
-+ FcConfigSubstitute(0, fcpattern,
-+ FcMatchPattern);
-+ FcDefaultSubstitute(fcpattern);
-+
-+ fontpattern = FcFontSetMatch(0, fcsets, 1,
-+ fcpattern, &fcres);
-+
-+ /* Allocate memory for the new cache entry. */
-+ if (frclen >= frccap) {
-+ frccap += 16;
-+ frc = xrealloc(frc, frccap * sizeof(Fontcache));
-+ }
-+
-+ frc[frclen].font = XftFontOpenPattern(xw.dpy,
-+ fontpattern);
-+ if (!frc[frclen].font)
-+ die("XftFontOpenPattern failed seeking fallback font: %s\n",
-+ strerror(errno));
-+ frc[frclen].flags = frcflags;
-+ frc[frclen].unicodep = rune;
-+
-+ glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune);
-+
-+ f = frclen;
-+ frclen++;
-+
-+ FcPatternDestroy(fcpattern);
-+ FcCharSetDestroy(fccharset);
-+ }
-+
-+ specs[numspecs].font = frc[f].font;
-+ specs[numspecs].glyph = glyphidx;
-+ specs[numspecs].x = (short)xp;
-+ specs[numspecs].y = (short)yp;
-+ numspecs++;
-+ }
- }
-- }
-
-- /* Nothing was found. Use fontconfig to find matching font. */
-- if (f >= frclen) {
-- if (!font->set)
-- font->set = FcFontSort(0, font->pattern,
-- 1, 0, &fcres);
-- fcsets[0] = font->set;
-+ /* Cleanup and get ready for next segment. */
-+ hbcleanup(&shaped);
-+ start = i;
-
-- /*
-- * Nothing was found in the cache. Now use
-- * some dozen of Fontconfig calls to get the
-- * font for one single character.
-- *
-- * Xft and fontconfig are design failures.
-- */
-- fcpattern = FcPatternDuplicate(font->pattern);
-- fccharset = FcCharSetCreate();
--
-- FcCharSetAddChar(fccharset, rune);
-- FcPatternAddCharSet(fcpattern, FC_CHARSET,
-- fccharset);
-- FcPatternAddBool(fcpattern, FC_SCALABLE, 1);
--
-- FcConfigSubstitute(0, fcpattern,
-- FcMatchPattern);
-- FcDefaultSubstitute(fcpattern);
--
-- fontpattern = FcFontSetMatch(0, fcsets, 1,
-- fcpattern, &fcres);
--
-- /* Allocate memory for the new cache entry. */
-- if (frclen >= frccap) {
-- frccap += 16;
-- frc = xrealloc(frc, frccap * sizeof(Fontcache));
-+ /* Determine font for glyph if different from previous glyph. */
-+ if (prevmode != mode) {
-+ prevmode = mode;
-+ xresetfontsettings(mode, &font, &frcflags);
-+ yp = winy + font->ascent;
- }
--
-- frc[frclen].font = XftFontOpenPattern(xw.dpy,
-- fontpattern);
-- if (!frc[frclen].font)
-- die("XftFontOpenPattern failed seeking fallback font: %s\n",
-- strerror(errno));
-- frc[frclen].flags = frcflags;
-- frc[frclen].unicodep = rune;
--
-- glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune);
--
-- f = frclen;
-- frclen++;
--
-- FcPatternDestroy(fcpattern);
-- FcCharSetDestroy(fccharset);
- }
--
-- specs[numspecs].font = frc[f].font;
-- specs[numspecs].glyph = glyphidx;
-- specs[numspecs].x = (short)xp;
-- specs[numspecs].y = (short)yp;
-- xp += runewidth;
-- numspecs++;
- }
-
-+ hbcleanup(&shaped);
- return numspecs;
- }
-
- void
--xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y)
-+xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y, int charlen)
- {
-- int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1);
- int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch,
- width = charlen * win.cw;
- Color *fg, *bg, *temp, revfg, revbg, truefg, truebg;
-@@ -1521,21 +1573,24 @@ void
- xdrawglyph(Glyph g, int x, int y)
- {
- int numspecs;
-- XftGlyphFontSpec spec;
-+ XftGlyphFontSpec *specs = xw.specbuf;
-
-- numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y);
-- xdrawglyphfontspecs(&spec, g, numspecs, x, y);
-+ numspecs = xmakeglyphfontspecs(specs, &g, 1, x, y);
-+ xdrawglyphfontspecs(specs, g, numspecs, x, y, (g.mode & ATTR_WIDE) ? 2 : 1);
- }
-
- void
--xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
-+xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og, Line line, int len)
- {
- Color drawcol;
-
- /* remove the old cursor */
- if (selected(ox, oy))
- og.mode ^= ATTR_REVERSE;
-- xdrawglyph(og, ox, oy);
-+
-+ /* Redraw the line where cursor was previously.
-+ * It will restore the ligatures broken by the cursor. */
-+ xdrawline(line, 0, oy, len);
-
- if (IS_SET(MODE_HIDE))
- return;
-@@ -1663,18 +1718,16 @@ xdrawline(Line line, int x1, int y1, int x2)
- Glyph base, new;
- XftGlyphFontSpec *specs = xw.specbuf;
-
-- numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1);
- i = ox = 0;
-- for (x = x1; x < x2 && i < numspecs; x++) {
-+ for (x = x1; x < x2; x++) {
- new = line[x];
- if (new.mode == ATTR_WDUMMY)
- continue;
- if (selected(x, y1))
- new.mode ^= ATTR_REVERSE;
-- if (i > 0 && ATTRCMP(base, new)) {
-- xdrawglyphfontspecs(specs, base, i, ox, y1);
-- specs += i;
-- numspecs -= i;
-+ if ((i > 0) && ATTRCMP(base, new)) {
-+ numspecs = xmakeglyphfontspecs(specs, &line[ox], x - ox, ox, y1);
-+ xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x - ox);
- i = 0;
- }
- if (i == 0) {
-@@ -1683,8 +1736,10 @@ xdrawline(Line line, int x1, int y1, int x2)
- }
- i++;
- }
-- if (i > 0)
-- xdrawglyphfontspecs(specs, base, i, ox, y1);
-+ if (i > 0) {
-+ numspecs = xmakeglyphfontspecs(specs, &line[ox], x2 - ox, ox, y1);
-+ xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x2 - ox);
-+ }
- }
-
- void
diff --git a/st.suckless.org/patches/ligatures/0.9.2/st-ligatures-boxdraw-20241226-0.9.2.diff b/st.suckless.org/patches/ligatures/0.9.2/st-ligatures-boxdraw-20241226-0.9.2.diff
@@ -0,0 +1,662 @@
+diff --git a/Makefile b/Makefile
+index a64b4c2..05124bf 100644
+--- a/Makefile
++++ b/Makefile
+@@ -3,9 +3,9 @@
+ .POSIX:
+
+ include config.mk
+
+-SRC = st.c x.c boxdraw.c
++SRC = st.c x.c boxdraw.c hb.c
+ OBJ = $(SRC:.c=.o)
+
+ all: st
+
+@@ -15,10 +15,11 @@ config.h:
+ .c.o:
+ $(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
+
+ st: $(OBJ)
+diff --git a/config.mk b/config.mk
+index fdc29a7..6833b3b 100644
+--- a/config.mk
++++ b/config.mk
+@@ -14,12 +14,14 @@ 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
+ STCFLAGS = $(INCS) $(STCPPFLAGS) $(CPPFLAGS) $(CFLAGS)
+@@ -28,9 +30,10 @@ STLDFLAGS = $(LIBS) $(LDFLAGS)
+ # OpenBSD:
+ #CPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 -D_BSD_SOURCE
+ #LIBS = -L$(X11LIB) -lm -lX11 -lutil -lXft \
+ # `$(PKG_CONFIG) --libs fontconfig` \
+-# `$(PKG_CONFIG) --libs freetype2`
++# `$(PKG_CONFIG) --libs freetype2` \
++# `$(PKG_CONFIG) --libs harfbuzz`
+ #MANPREFIX = ${PREFIX}/man
+
+ # compiler and linker
+ # CC = c99
+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 ec6fbf3..1385f77 100644
+--- a/st.c
++++ b/st.c
+@@ -2661,9 +2661,10 @@ draw(void)
+ cx--;
+
+ 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();
+ if (ocx != term.ocx || ocy != term.ocy)
+diff --git a/st.h b/st.h
+index 808f5f7..ae41368 100644
+--- a/st.h
++++ b/st.h
+@@ -10,9 +10,10 @@
+ #define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b))
+ #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)
+ #define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit)))
+diff --git a/win.h b/win.h
+index 6de960d..94679e4 100644
+--- a/win.h
++++ b/win.h
+@@ -24,9 +24,9 @@ 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);
+ int xsetcolorname(int, const char *);
+diff --git a/x.c b/x.c
+index 978a8fc..c2d9993 100644
+--- a/x.c
++++ b/x.c
+@@ -18,8 +18,9 @@
+ char *argv0;
+ #include "arg.h"
+ #include "st.h"
+ #include "win.h"
++#include "hb.h"
+
+ /* types used in config.h */
+ typedef struct {
+ uint mod;
+@@ -140,10 +141,11 @@ typedef struct {
+ GC gc;
+ } 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);
+ static int ximopen(Display *);
+@@ -756,9 +758,9 @@ xresize(int col, int row)
+ XftDrawChange(xw.draw, xw.buf);
+ 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
+ sixd_to_16bit(int x)
+@@ -1061,8 +1063,11 @@ 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);
+
+@@ -1184,9 +1189,9 @@ xinit(int cols, int rows)
+ XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel);
+ 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);
+
+@@ -1240,149 +1245,162 @@ 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)
+ {
+ float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp;
+- ushort mode, prevmode = USHRT_MAX;
++ ushort mode = glyphs[0].mode & ~ATTR_WRAP;
+ Font *font = &dc.font;
+ int frcflags = FRC_NORMAL;
+- float runewidth = win.cw;
++ float runewidth = win.cw * ((glyphs[0].mode & ATTR_WIDE) ? 2.0f : 1.0f);
+ Rune rune;
+ FT_UInt glyphidx;
+ FcResult fcres;
+ FcPattern *fcpattern, *fontpattern;
+ FcFontSet *fcsets[] = { NULL };
+ FcCharSet *fccharset;
+- int i, f, numspecs = 0;
++ int f, code_idx, numspecs = 0;
++ float cluster_xp = xp, cluster_yp = yp;
++ HbTransformData shaped = { 0 };
+
+- 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;
++ /* Initial values. */
++ xresetfontsettings(mode, &font, &frcflags);
+
+- /* Skip dummy wide-character spacing. */
+- if (mode == ATTR_WDUMMY)
++ /* Shape the segment. */
++ hbtransform(&shaped, font->match, glyphs, 0, len);
++ xp = winx; yp = winy + font->ascent;
++ cluster_xp = xp; cluster_yp = yp;
++
++ for (code_idx = 0; code_idx < shaped.count; code_idx++) {
++ int idx = shaped.glyphs[code_idx].cluster;
++
++ if (glyphs[idx].mode & ATTR_WDUMMY)
+ 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;
+- }
+- yp = winy + font->ascent;
++ /* 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;
+ }
+
+- if (mode & ATTR_BOXDRAW) {
++ if (glyphs[idx].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;
++ specs[numspecs].glyph = boxdrawindex(&glyphs[idx]);
++ specs[numspecs].x = xp;
++ specs[numspecs].y = yp;
+ 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;
++ } 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[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));
++ /* 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);
+ }
+
+- 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++;
+ }
+-
+- specs[numspecs].font = frc[f].font;
+- specs[numspecs].glyph = glyphidx;
+- specs[numspecs].x = (short)xp;
+- specs[numspecs].y = (short)yp;
+- xp += runewidth;
+- numspecs++;
+ }
+
++ /* Cleanup and get ready for next segment. */
++ 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;
+ XRenderColor colfg, colbg;
+@@ -1520,23 +1538,26 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
+ 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;
+
+@@ -1668,30 +1689,30 @@ xdrawline(Line line, int x1, int y1, int x2)
+ int i, x, ox, numspecs;
+ 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) {
+ ox = x;
+ base = new;
+ }
+ 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
+ xfinishdraw(void)
diff --git a/st.suckless.org/patches/ligatures/0.9.2/st-ligatures-scrollback-20240427-0.9.2.diff b/st.suckless.org/patches/ligatures/0.9.2/st-ligatures-scrollback-20240427-0.9.2.diff
@@ -1,634 +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: st
-@@ -22,7 +22,8 @@ config.h:
- $(CC) $(STCFLAGS) -c $<
-
- st.o: config.h st.h win.h
--x.o: arg.h config.h st.h win.h
-+x.o: arg.h config.h st.h win.h hb.h
-+hb.o: st.h
-
- $(OBJ): config.h config.mk
-
-diff --git a/config.mk b/config.mk
-index 1e306f8..3e13e53 100644
---- a/config.mk
-+++ b/config.mk
-@@ -15,10 +15,12 @@ PKG_CONFIG = pkg-config
- # includes and libs
- INCS = -I$(X11INC) \
- `$(PKG_CONFIG) --cflags fontconfig` \
-- `$(PKG_CONFIG) --cflags freetype2`
-+ `$(PKG_CONFIG) --cflags freetype2` \
-+ `$(PKG_CONFIG) --cflags harfbuzz`
- LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft \
- `$(PKG_CONFIG) --libs fontconfig` \
-- `$(PKG_CONFIG) --libs freetype2`
-+ `$(PKG_CONFIG) --libs freetype2` \
-+ `$(PKG_CONFIG) --libs harfbuzz`
-
- # flags
- STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600
-diff --git a/hb.c b/hb.c
-new file mode 100644
-index 0000000..99412c8
---- /dev/null
-+++ b/hb.c
-@@ -0,0 +1,125 @@
-+#include <stdlib.h>
-+#include <stdio.h>
-+#include <math.h>
-+#include <X11/Xft/Xft.h>
-+#include <X11/cursorfont.h>
-+#include <hb.h>
-+#include <hb-ft.h>
-+
-+#include "st.h"
-+#include "hb.h"
-+
-+#define FEATURE(c1,c2,c3,c4) { .tag = HB_TAG(c1,c2,c3,c4), .value = 1, .start = HB_FEATURE_GLOBAL_START, .end = HB_FEATURE_GLOBAL_END }
-+#define BUFFER_STEP 256
-+
-+hb_font_t *hbfindfont(XftFont *match);
-+
-+typedef struct {
-+ XftFont *match;
-+ hb_font_t *font;
-+} HbFontMatch;
-+
-+typedef struct {
-+ size_t capacity;
-+ HbFontMatch *fonts;
-+} HbFontCache;
-+
-+static HbFontCache hbfontcache = { 0, NULL };
-+
-+typedef struct {
-+ size_t capacity;
-+ Rune *runes;
-+} RuneBuffer;
-+
-+static RuneBuffer hbrunebuffer = { 0, NULL };
-+
-+/*
-+ * Poplulate the array with a list of font features, wrapped in FEATURE macro,
-+ * e. g.
-+ * FEATURE('c', 'a', 'l', 't'), FEATURE('d', 'l', 'i', 'g')
-+ */
-+hb_feature_t features[] = { };
-+
-+void
-+hbunloadfonts()
-+{
-+ for (int i = 0; i < hbfontcache.capacity; i++) {
-+ hb_font_destroy(hbfontcache.fonts[i].font);
-+ XftUnlockFace(hbfontcache.fonts[i].match);
-+ }
-+
-+ if (hbfontcache.fonts != NULL) {
-+ free(hbfontcache.fonts);
-+ hbfontcache.fonts = NULL;
-+ }
-+ hbfontcache.capacity = 0;
-+}
-+
-+hb_font_t *
-+hbfindfont(XftFont *match)
-+{
-+ for (int i = 0; i < hbfontcache.capacity; i++) {
-+ if (hbfontcache.fonts[i].match == match)
-+ return hbfontcache.fonts[i].font;
-+ }
-+
-+ /* Font not found in cache, caching it now. */
-+ hbfontcache.fonts = realloc(hbfontcache.fonts, sizeof(HbFontMatch) * (hbfontcache.capacity + 1));
-+ FT_Face face = XftLockFace(match);
-+ hb_font_t *font = hb_ft_font_create(face, NULL);
-+ if (font == NULL)
-+ die("Failed to load Harfbuzz font.");
-+
-+ hbfontcache.fonts[hbfontcache.capacity].match = match;
-+ hbfontcache.fonts[hbfontcache.capacity].font = font;
-+ hbfontcache.capacity += 1;
-+
-+ return font;
-+}
-+
-+void hbtransform(HbTransformData *data, XftFont *xfont, const Glyph *glyphs, int start, int length) {
-+ ushort mode = USHRT_MAX;
-+ unsigned int glyph_count;
-+ int rune_idx, glyph_idx, end = start + length;
-+
-+ hb_font_t *font = hbfindfont(xfont);
-+ if (font == NULL)
-+ return;
-+
-+ hb_buffer_t *buffer = hb_buffer_create();
-+ hb_buffer_set_direction(buffer, HB_DIRECTION_LTR);
-+ hb_buffer_set_cluster_level(buffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS);
-+
-+ /* Resize the buffer if required length is larger. */
-+ if (hbrunebuffer.capacity < length) {
-+ hbrunebuffer.capacity = (length / BUFFER_STEP + 1) * BUFFER_STEP;
-+ hbrunebuffer.runes = realloc(hbrunebuffer.runes, hbrunebuffer.capacity * sizeof(Rune));
-+ }
-+
-+ /* Fill buffer with codepoints. */
-+ for (rune_idx = 0, glyph_idx = start; glyph_idx < end; glyph_idx++, rune_idx++) {
-+ hbrunebuffer.runes[rune_idx] = glyphs[glyph_idx].u;
-+ mode = glyphs[glyph_idx].mode;
-+ if (mode & ATTR_WDUMMY)
-+ hbrunebuffer.runes[rune_idx] = 0x0020;
-+ }
-+ hb_buffer_add_codepoints(buffer, hbrunebuffer.runes, length, 0, length);
-+
-+ /* Shape the segment. */
-+ hb_shape(font, buffer, features, sizeof(features)/sizeof(hb_feature_t));
-+
-+ /* Get new glyph info. */
-+ hb_glyph_info_t *info = hb_buffer_get_glyph_infos(buffer, &glyph_count);
-+ hb_glyph_position_t *pos = hb_buffer_get_glyph_positions(buffer, &glyph_count);
-+
-+ /* Fill the output. */
-+ data->buffer = buffer;
-+ data->glyphs = info;
-+ data->positions = pos;
-+ data->count = glyph_count;
-+}
-+
-+void hbcleanup(HbTransformData *data) {
-+ hb_buffer_destroy(data->buffer);
-+ memset(data, 0, sizeof(HbTransformData));
-+}
-diff --git a/hb.h b/hb.h
-new file mode 100644
-index 0000000..3b0ef44
---- /dev/null
-+++ b/hb.h
-@@ -0,0 +1,14 @@
-+#include <X11/Xft/Xft.h>
-+#include <hb.h>
-+#include <hb-ft.h>
-+
-+typedef struct {
-+ hb_buffer_t *buffer;
-+ hb_glyph_info_t *glyphs;
-+ hb_glyph_position_t *positions;
-+ unsigned int count;
-+} HbTransformData;
-+
-+void hbunloadfonts();
-+void hbtransform(HbTransformData *, XftFont *, const Glyph *, int, int);
-+void hbcleanup(HbTransformData *);
-diff --git a/st.c b/st.c
-index 79ee9ba..7675db6 100644
---- a/st.c
-+++ b/st.c
-@@ -2711,7 +2711,8 @@ draw(void)
- drawregion(0, 0, term.col, term.row);
- if (term.scr == 0)
- xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
-- term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
-+ term.ocx, term.ocy, term.line[term.ocy][term.ocx],
-+ term.line[term.ocy], term.col);
- term.ocx = cx;
- term.ocy = term.c.y;
- xfinishdraw();
-diff --git a/st.h b/st.h
-index 818a6f8..4e584b6 100644
---- a/st.h
-+++ b/st.h
-@@ -11,7 +11,8 @@
- #define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d))
- #define DEFAULT(a, b) (a) = (a) ? (a) : (b)
- #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)
--#define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \
-+#define ATTRCMP(a, b) (((a).mode & (~ATTR_WRAP)) != ((b).mode & (~ATTR_WRAP)) || \
-+ (a).fg != (b).fg || \
- (a).bg != (b).bg)
- #define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \
- (t1.tv_nsec-t2.tv_nsec)/1E6)
-diff --git a/win.h b/win.h
-index 6de960d..94679e4 100644
---- a/win.h
-+++ b/win.h
-@@ -25,7 +25,7 @@ enum win_mode {
-
- void xbell(void);
- void xclipcopy(void);
--void xdrawcursor(int, int, Glyph, int, int, Glyph);
-+void xdrawcursor(int, int, Glyph, int, int, Glyph, Line, int);
- void xdrawline(Line, int, int, int);
- void xfinishdraw(void);
- void xloadcols(void);
-diff --git a/x.c b/x.c
-index 2a3bd38..0bb51ff 100644
---- a/x.c
-+++ b/x.c
-@@ -19,6 +19,7 @@ char *argv0;
- #include "arg.h"
- #include "st.h"
- #include "win.h"
-+#include "hb.h"
-
- /* types used in config.h */
- typedef struct {
-@@ -141,8 +142,9 @@ typedef struct {
- } DC;
-
- static inline ushort sixd_to_16bit(int);
-+static void xresetfontsettings(ushort mode, Font **font, int *frcflags);
- static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int);
--static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int);
-+static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int, int);
- static void xdrawglyph(Glyph, int, int);
- static void xclear(int, int, int, int);
- static int xgeommasktogravity(int);
-@@ -757,7 +759,7 @@ xresize(int col, int row)
- xclear(0, 0, win.w, win.h);
-
- /* resize to new width */
-- xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec));
-+ xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec) * 4);
- }
-
- ushort
-@@ -1062,6 +1064,9 @@ xunloadfont(Font *f)
- void
- xunloadfonts(void)
- {
-+ /* Clear Harfbuzz font cache. */
-+ hbunloadfonts();
-+
- /* Free the loaded fonts in the font cache. */
- while (frclen > 0)
- XftFontClose(xw.dpy, frc[--frclen].font);
-@@ -1185,7 +1190,7 @@ xinit(int cols, int rows)
- XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h);
-
- /* font spec buffer */
-- xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec));
-+ xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec) * 4);
-
- /* Xft rendering context */
- xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap);
-@@ -1239,6 +1244,22 @@ xinit(int cols, int rows)
- xsel.xtarget = XA_STRING;
- }
-
-+void
-+xresetfontsettings(ushort mode, Font **font, int *frcflags)
-+{
-+ *font = &dc.font;
-+ if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) {
-+ *font = &dc.ibfont;
-+ *frcflags = FRC_ITALICBOLD;
-+ } else if (mode & ATTR_ITALIC) {
-+ *font = &dc.ifont;
-+ *frcflags = FRC_ITALIC;
-+ } else if (mode & ATTR_BOLD) {
-+ *font = &dc.bfont;
-+ *frcflags = FRC_BOLD;
-+ }
-+}
-+
- int
- xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y)
- {
-@@ -1253,128 +1274,157 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
- FcPattern *fcpattern, *fontpattern;
- FcFontSet *fcsets[] = { NULL };
- FcCharSet *fccharset;
-- int i, f, numspecs = 0;
-+ int i, f, length = 0, start = 0, numspecs = 0;
-+ float cluster_xp = xp, cluster_yp = yp;
-+ HbTransformData shaped = { 0 };
-+
-+ /* Initial values. */
-+ mode = prevmode = glyphs[0].mode & ~ATTR_WRAP;
-+ xresetfontsettings(mode, &font, &frcflags);
-
- for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) {
-- /* Fetch rune and mode for current glyph. */
-- rune = glyphs[i].u;
-- mode = glyphs[i].mode;
-+ mode = glyphs[i].mode & ~ATTR_WRAP;
-
- /* Skip dummy wide-character spacing. */
-- if (mode == ATTR_WDUMMY)
-+ if (mode & ATTR_WDUMMY && i < (len - 1))
- continue;
-
-- /* Determine font for glyph if different from previous glyph. */
-- if (prevmode != mode) {
-- prevmode = mode;
-- font = &dc.font;
-- frcflags = FRC_NORMAL;
-- runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f);
-- if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) {
-- font = &dc.ibfont;
-- frcflags = FRC_ITALICBOLD;
-- } else if (mode & ATTR_ITALIC) {
-- font = &dc.ifont;
-- frcflags = FRC_ITALIC;
-- } else if (mode & ATTR_BOLD) {
-- font = &dc.bfont;
-- frcflags = FRC_BOLD;
-+ if (
-+ prevmode != mode
-+ || ATTRCMP(glyphs[start], glyphs[i])
-+ || selected(x + i, y) != selected(x + start, y)
-+ || i == (len - 1)
-+ ) {
-+ /* Handle 1-character wide segments and end of line */
-+ length = i - start;
-+ if (i == start) {
-+ length = 1;
-+ } else if (i == (len - 1)) {
-+ length = (i - start + 1);
- }
-- yp = winy + font->ascent;
-- }
-
-- /* Lookup character index with default font. */
-- glyphidx = XftCharIndex(xw.dpy, font->match, rune);
-- if (glyphidx) {
-- specs[numspecs].font = font->match;
-- specs[numspecs].glyph = glyphidx;
-- specs[numspecs].x = (short)xp;
-- specs[numspecs].y = (short)yp;
-- xp += runewidth;
-- numspecs++;
-- continue;
-- }
--
-- /* Fallback on font cache, search the font cache for match. */
-- for (f = 0; f < frclen; f++) {
-- glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune);
-- /* Everything correct. */
-- if (glyphidx && frc[f].flags == frcflags)
-- break;
-- /* We got a default font for a not found glyph. */
-- if (!glyphidx && frc[f].flags == frcflags
-- && frc[f].unicodep == rune) {
-- break;
-+ /* Shape the segment. */
-+ hbtransform(&shaped, font->match, glyphs, start, length);
-+ runewidth = win.cw * ((glyphs[start].mode & ATTR_WIDE) ? 2.0f : 1.0f);
-+ cluster_xp = xp; cluster_yp = yp;
-+ for (int code_idx = 0; code_idx < shaped.count; code_idx++) {
-+ int idx = shaped.glyphs[code_idx].cluster;
-+
-+ if (glyphs[start + idx].mode & ATTR_WDUMMY)
-+ continue;
-+
-+ /* Advance the drawing cursor if we've moved to a new cluster */
-+ if (code_idx > 0 && idx != shaped.glyphs[code_idx - 1].cluster) {
-+ xp += runewidth;
-+ cluster_xp = xp;
-+ cluster_yp = yp;
-+ runewidth = win.cw * ((glyphs[start + idx].mode & ATTR_WIDE) ? 2.0f : 1.0f);
-+ }
-+
-+ if (shaped.glyphs[code_idx].codepoint != 0) {
-+ /* If symbol is found, put it into the specs. */
-+ specs[numspecs].font = font->match;
-+ specs[numspecs].glyph = shaped.glyphs[code_idx].codepoint;
-+ specs[numspecs].x = cluster_xp + (short)(shaped.positions[code_idx].x_offset / 64.);
-+ specs[numspecs].y = cluster_yp - (short)(shaped.positions[code_idx].y_offset / 64.);
-+ cluster_xp += shaped.positions[code_idx].x_advance / 64.;
-+ cluster_yp += shaped.positions[code_idx].y_advance / 64.;
-+ numspecs++;
-+ } else {
-+ /* If it's not found, try to fetch it through the font cache. */
-+ rune = glyphs[start + idx].u;
-+ for (f = 0; f < frclen; f++) {
-+ glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune);
-+ /* Everything correct. */
-+ if (glyphidx && frc[f].flags == frcflags)
-+ break;
-+ /* We got a default font for a not found glyph. */
-+ if (!glyphidx && frc[f].flags == frcflags
-+ && frc[f].unicodep == rune) {
-+ break;
-+ }
-+ }
-+
-+ /* Nothing was found. Use fontconfig to find matching font. */
-+ if (f >= frclen) {
-+ if (!font->set)
-+ font->set = FcFontSort(0, font->pattern,
-+ 1, 0, &fcres);
-+ fcsets[0] = font->set;
-+
-+ /*
-+ * Nothing was found in the cache. Now use
-+ * some dozen of Fontconfig calls to get the
-+ * font for one single character.
-+ *
-+ * Xft and fontconfig are design failures.
-+ */
-+ fcpattern = FcPatternDuplicate(font->pattern);
-+ fccharset = FcCharSetCreate();
-+
-+ FcCharSetAddChar(fccharset, rune);
-+ FcPatternAddCharSet(fcpattern, FC_CHARSET,
-+ fccharset);
-+ FcPatternAddBool(fcpattern, FC_SCALABLE, 1);
-+
-+ FcConfigSubstitute(0, fcpattern,
-+ FcMatchPattern);
-+ FcDefaultSubstitute(fcpattern);
-+
-+ fontpattern = FcFontSetMatch(0, fcsets, 1,
-+ fcpattern, &fcres);
-+
-+ /* Allocate memory for the new cache entry. */
-+ if (frclen >= frccap) {
-+ frccap += 16;
-+ frc = xrealloc(frc, frccap * sizeof(Fontcache));
-+ }
-+
-+ frc[frclen].font = XftFontOpenPattern(xw.dpy,
-+ fontpattern);
-+ if (!frc[frclen].font)
-+ die("XftFontOpenPattern failed seeking fallback font: %s\n",
-+ strerror(errno));
-+ frc[frclen].flags = frcflags;
-+ frc[frclen].unicodep = rune;
-+
-+ glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune);
-+
-+ f = frclen;
-+ frclen++;
-+
-+ FcPatternDestroy(fcpattern);
-+ FcCharSetDestroy(fccharset);
-+ }
-+
-+ specs[numspecs].font = frc[f].font;
-+ specs[numspecs].glyph = glyphidx;
-+ specs[numspecs].x = (short)xp;
-+ specs[numspecs].y = (short)yp;
-+ numspecs++;
-+ }
- }
-- }
-
-- /* Nothing was found. Use fontconfig to find matching font. */
-- if (f >= frclen) {
-- if (!font->set)
-- font->set = FcFontSort(0, font->pattern,
-- 1, 0, &fcres);
-- fcsets[0] = font->set;
-+ /* Cleanup and get ready for next segment. */
-+ hbcleanup(&shaped);
-+ start = i;
-
-- /*
-- * Nothing was found in the cache. Now use
-- * some dozen of Fontconfig calls to get the
-- * font for one single character.
-- *
-- * Xft and fontconfig are design failures.
-- */
-- fcpattern = FcPatternDuplicate(font->pattern);
-- fccharset = FcCharSetCreate();
--
-- FcCharSetAddChar(fccharset, rune);
-- FcPatternAddCharSet(fcpattern, FC_CHARSET,
-- fccharset);
-- FcPatternAddBool(fcpattern, FC_SCALABLE, 1);
--
-- FcConfigSubstitute(0, fcpattern,
-- FcMatchPattern);
-- FcDefaultSubstitute(fcpattern);
--
-- fontpattern = FcFontSetMatch(0, fcsets, 1,
-- fcpattern, &fcres);
--
-- /* Allocate memory for the new cache entry. */
-- if (frclen >= frccap) {
-- frccap += 16;
-- frc = xrealloc(frc, frccap * sizeof(Fontcache));
-+ /* Determine font for glyph if different from previous glyph. */
-+ if (prevmode != mode) {
-+ prevmode = mode;
-+ xresetfontsettings(mode, &font, &frcflags);
-+ yp = winy + font->ascent;
- }
--
-- frc[frclen].font = XftFontOpenPattern(xw.dpy,
-- fontpattern);
-- if (!frc[frclen].font)
-- die("XftFontOpenPattern failed seeking fallback font: %s\n",
-- strerror(errno));
-- frc[frclen].flags = frcflags;
-- frc[frclen].unicodep = rune;
--
-- glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune);
--
-- f = frclen;
-- frclen++;
--
-- FcPatternDestroy(fcpattern);
-- FcCharSetDestroy(fccharset);
- }
--
-- specs[numspecs].font = frc[f].font;
-- specs[numspecs].glyph = glyphidx;
-- specs[numspecs].x = (short)xp;
-- specs[numspecs].y = (short)yp;
-- xp += runewidth;
-- numspecs++;
- }
-
-+ hbcleanup(&shaped);
- return numspecs;
- }
-
- void
--xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y)
-+xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y, int charlen)
- {
-- int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1);
- int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch,
- width = charlen * win.cw;
- Color *fg, *bg, *temp, revfg, revbg, truefg, truebg;
-@@ -1510,21 +1560,24 @@ void
- xdrawglyph(Glyph g, int x, int y)
- {
- int numspecs;
-- XftGlyphFontSpec spec;
-+ XftGlyphFontSpec *specs = xw.specbuf;
-
-- numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y);
-- xdrawglyphfontspecs(&spec, g, numspecs, x, y);
-+ numspecs = xmakeglyphfontspecs(specs, &g, 1, x, y);
-+ xdrawglyphfontspecs(specs, g, numspecs, x, y, (g.mode & ATTR_WIDE) ? 2 : 1);
- }
-
- void
--xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
-+xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og, Line line, int len)
- {
- Color drawcol;
-
- /* remove the old cursor */
- if (selected(ox, oy))
- og.mode ^= ATTR_REVERSE;
-- xdrawglyph(og, ox, oy);
-+
-+ /* Redraw the line where cursor was previously.
-+ * It will restore the ligatures broken by the cursor. */
-+ xdrawline(line, 0, oy, len);
-
- if (IS_SET(MODE_HIDE))
- return;
-@@ -1652,18 +1705,16 @@ xdrawline(Line line, int x1, int y1, int x2)
- Glyph base, new;
- XftGlyphFontSpec *specs = xw.specbuf;
-
-- numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1);
- i = ox = 0;
-- for (x = x1; x < x2 && i < numspecs; x++) {
-+ for (x = x1; x < x2; x++) {
- new = line[x];
- if (new.mode == ATTR_WDUMMY)
- continue;
- if (selected(x, y1))
- new.mode ^= ATTR_REVERSE;
-- if (i > 0 && ATTRCMP(base, new)) {
-- xdrawglyphfontspecs(specs, base, i, ox, y1);
-- specs += i;
-- numspecs -= i;
-+ if ((i > 0) && ATTRCMP(base, new)) {
-+ numspecs = xmakeglyphfontspecs(specs, &line[ox], x - ox, ox, y1);
-+ xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x - ox);
- i = 0;
- }
- if (i == 0) {
-@@ -1672,8 +1723,10 @@ xdrawline(Line line, int x1, int y1, int x2)
- }
- i++;
- }
-- if (i > 0)
-- xdrawglyphfontspecs(specs, base, i, ox, y1);
-+ if (i > 0) {
-+ numspecs = xmakeglyphfontspecs(specs, &line[ox], x2 - ox, ox, y1);
-+ xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x2 - ox);
-+ }
- }
-
- void
diff --git a/st.suckless.org/patches/ligatures/0.9.2/st-ligatures-scrollback-20241226-0.9.2.diff b/st.suckless.org/patches/ligatures/0.9.2/st-ligatures-scrollback-20241226-0.9.2.diff
@@ -0,0 +1,650 @@
+diff --git a/Makefile b/Makefile
+index 15db421..dfcea0f 100644
+--- a/Makefile
++++ b/Makefile
+@@ -3,9 +3,9 @@
+ .POSIX:
+
+ include config.mk
+
+-SRC = st.c x.c
++SRC = st.c x.c hb.c
+ OBJ = $(SRC:.c=.o)
+
+ all: st
+
+@@ -15,9 +15,10 @@ config.h:
+ .c.o:
+ $(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
+
+ st: $(OBJ)
+diff --git a/config.mk b/config.mk
+index fdc29a7..6833b3b 100644
+--- a/config.mk
++++ b/config.mk
+@@ -14,12 +14,14 @@ 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
+ STCFLAGS = $(INCS) $(STCPPFLAGS) $(CPPFLAGS) $(CFLAGS)
+@@ -28,9 +30,10 @@ STLDFLAGS = $(LIBS) $(LDFLAGS)
+ # OpenBSD:
+ #CPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 -D_BSD_SOURCE
+ #LIBS = -L$(X11LIB) -lm -lX11 -lutil -lXft \
+ # `$(PKG_CONFIG) --libs fontconfig` \
+-# `$(PKG_CONFIG) --libs freetype2`
++# `$(PKG_CONFIG) --libs freetype2` \
++# `$(PKG_CONFIG) --libs harfbuzz`
+ #MANPREFIX = ${PREFIX}/man
+
+ # compiler and linker
+ # CC = c99
+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 2478942..bba90d3 100644
+--- a/st.c
++++ b/st.c
+@@ -2729,9 +2729,10 @@ 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();
+ if (ocx != term.ocx || ocy != term.ocy)
+diff --git a/st.h b/st.h
+index 818a6f8..4e584b6 100644
+--- a/st.h
++++ b/st.h
+@@ -10,9 +10,10 @@
+ #define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b))
+ #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)
+ #define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit)))
+diff --git a/win.h b/win.h
+index 6de960d..94679e4 100644
+--- a/win.h
++++ b/win.h
+@@ -24,9 +24,9 @@ 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);
+ int xsetcolorname(int, const char *);
+diff --git a/x.c b/x.c
+index bd23686..2bf3b72 100644
+--- a/x.c
++++ b/x.c
+@@ -18,8 +18,9 @@
+ char *argv0;
+ #include "arg.h"
+ #include "st.h"
+ #include "win.h"
++#include "hb.h"
+
+ /* types used in config.h */
+ typedef struct {
+ uint mod;
+@@ -140,10 +141,11 @@ typedef struct {
+ GC gc;
+ } 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);
+ static int ximopen(Display *);
+@@ -756,9 +758,9 @@ xresize(int col, int row)
+ XftDrawChange(xw.draw, xw.buf);
+ 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
+ sixd_to_16bit(int x)
+@@ -1061,8 +1063,11 @@ 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);
+
+@@ -1184,9 +1189,9 @@ xinit(int cols, int rows)
+ XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel);
+ 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);
+
+@@ -1238,144 +1243,155 @@ xinit(int cols, int rows)
+ if (xsel.xtarget == None)
+ 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)
+ {
+ float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp;
+- ushort mode, prevmode = USHRT_MAX;
++ ushort mode = glyphs[0].mode & ~ATTR_WRAP;
+ Font *font = &dc.font;
+ int frcflags = FRC_NORMAL;
+- float runewidth = win.cw;
++ float runewidth = win.cw * ((glyphs[0].mode & ATTR_WIDE) ? 2.0f : 1.0f);
+ Rune rune;
+ FT_UInt glyphidx;
+ FcResult fcres;
+ FcPattern *fcpattern, *fontpattern;
+ FcFontSet *fcsets[] = { NULL };
+ FcCharSet *fccharset;
+- int i, f, numspecs = 0;
++ int f, code_idx, numspecs = 0;
++ float cluster_xp = xp, cluster_yp = yp;
++ HbTransformData shaped = { 0 };
+
+- 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;
++ /* Initial values. */
++ xresetfontsettings(mode, &font, &frcflags);
+
+- /* Skip dummy wide-character spacing. */
+- if (mode == ATTR_WDUMMY)
++ /* Shape the segment. */
++ hbtransform(&shaped, font->match, glyphs, 0, len);
++ xp = winx; yp = winy + font->ascent;
++ cluster_xp = xp; cluster_yp = yp;
++
++ for (code_idx = 0; code_idx < shaped.count; code_idx++) {
++ int idx = shaped.glyphs[code_idx].cluster;
++
++ if (glyphs[idx].mode & ATTR_WDUMMY)
+ 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;
+- }
+- yp = winy + font->ascent;
++ /* 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;
+ }
+
+- /* Lookup character index with default font. */
+- glyphidx = XftCharIndex(xw.dpy, font->match, rune);
+- if (glyphidx) {
++ if (shaped.glyphs[code_idx].codepoint != 0) {
++ /* If symbol is found, put it into the specs. */
+ specs[numspecs].font = font->match;
+- specs[numspecs].glyph = glyphidx;
+- specs[numspecs].x = (short)xp;
+- specs[numspecs].y = (short)yp;
+- xp += runewidth;
++ 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++;
+- 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;
++ } else {
++ /* If it's not found, try to fetch it through the font cache. */
++ rune = glyphs[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));
++ /* 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);
+ }
+
+- 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++;
+ }
+-
+- specs[numspecs].font = frc[f].font;
+- specs[numspecs].glyph = glyphidx;
+- specs[numspecs].x = (short)xp;
+- specs[numspecs].y = (short)yp;
+- xp += runewidth;
+- numspecs++;
+ }
+
++ /* Cleanup and get ready for next segment. */
++ 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;
+ XRenderColor colfg, colbg;
+@@ -1509,23 +1525,26 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
+ 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;
+
+@@ -1657,30 +1676,30 @@ xdrawline(Line line, int x1, int y1, int x2)
+ int i, x, ox, numspecs;
+ 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) {
+ ox = x;
+ base = new;
+ }
+ 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
+ xfinishdraw(void)
diff --git a/st.suckless.org/patches/ligatures/0.9.2/st-ligatures-scrollback-ringbuffer-20240427-0.9.2.diff b/st.suckless.org/patches/ligatures/0.9.2/st-ligatures-scrollback-ringbuffer-20240427-0.9.2.diff
@@ -1,633 +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: 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/0.9.2/st-ligatures-scrollback-ringbuffer-20241226-0.9.2.diff b/st.suckless.org/patches/ligatures/0.9.2/st-ligatures-scrollback-ringbuffer-20241226-0.9.2.diff
@@ -0,0 +1,650 @@
+diff --git a/Makefile b/Makefile
+index 15db421..dfcea0f 100644
+--- a/Makefile
++++ b/Makefile
+@@ -3,9 +3,9 @@
+ .POSIX:
+
+ include config.mk
+
+-SRC = st.c x.c
++SRC = st.c x.c hb.c
+ OBJ = $(SRC:.c=.o)
+
+ all: st
+
+@@ -15,9 +15,10 @@ config.h:
+ .c.o:
+ $(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
+
+ st: $(OBJ)
+diff --git a/config.mk b/config.mk
+index fdc29a7..6833b3b 100644
+--- a/config.mk
++++ b/config.mk
+@@ -14,12 +14,14 @@ 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
+ STCFLAGS = $(INCS) $(STCPPFLAGS) $(CPPFLAGS) $(CFLAGS)
+@@ -28,9 +30,10 @@ STLDFLAGS = $(LIBS) $(LDFLAGS)
+ # OpenBSD:
+ #CPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 -D_BSD_SOURCE
+ #LIBS = -L$(X11LIB) -lm -lX11 -lutil -lXft \
+ # `$(PKG_CONFIG) --libs fontconfig` \
+-# `$(PKG_CONFIG) --libs freetype2`
++# `$(PKG_CONFIG) --libs freetype2` \
++# `$(PKG_CONFIG) --libs harfbuzz`
+ #MANPREFIX = ${PREFIX}/man
+
+ # compiler and linker
+ # CC = c99
+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 d9b163e..fbca4ba 100644
+--- a/st.c
++++ b/st.c
+@@ -2777,9 +2777,10 @@ 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();
+ if (ocx != term.ocx || ocy != term.ocy)
+diff --git a/st.h b/st.h
+index 3cea73b..709a369 100644
+--- a/st.h
++++ b/st.h
+@@ -10,9 +10,10 @@
+ #define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b))
+ #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)
+ #define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit)))
+diff --git a/win.h b/win.h
+index 6de960d..94679e4 100644
+--- a/win.h
++++ b/win.h
+@@ -24,9 +24,9 @@ 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);
+ int xsetcolorname(int, const char *);
+diff --git a/x.c b/x.c
+index 25785a6..16d7a3a 100644
+--- a/x.c
++++ b/x.c
+@@ -18,8 +18,9 @@
+ char *argv0;
+ #include "arg.h"
+ #include "st.h"
+ #include "win.h"
++#include "hb.h"
+
+ /* types used in config.h */
+ typedef struct {
+ uint mod;
+@@ -142,10 +143,11 @@ typedef struct {
+ GC gc;
+ } 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);
+ static int ximopen(Display *);
+@@ -758,9 +760,9 @@ xresize(int col, int row)
+ XftDrawChange(xw.draw, xw.buf);
+ 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
+ sixd_to_16bit(int x)
+@@ -1063,8 +1065,11 @@ 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);
+
+@@ -1186,9 +1191,9 @@ xinit(int cols, int rows)
+ XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel);
+ 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);
+
+@@ -1240,144 +1245,155 @@ xinit(int cols, int rows)
+ if (xsel.xtarget == None)
+ 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)
+ {
+ float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp;
+- ushort mode, prevmode = USHRT_MAX;
++ ushort mode = glyphs[0].mode & ~ATTR_WRAP;
+ Font *font = &dc.font;
+ int frcflags = FRC_NORMAL;
+- float runewidth = win.cw;
++ float runewidth = win.cw * ((glyphs[0].mode & ATTR_WIDE) ? 2.0f : 1.0f);
+ Rune rune;
+ FT_UInt glyphidx;
+ FcResult fcres;
+ FcPattern *fcpattern, *fontpattern;
+ FcFontSet *fcsets[] = { NULL };
+ FcCharSet *fccharset;
+- int i, f, numspecs = 0;
++ int f, code_idx, numspecs = 0;
++ float cluster_xp = xp, cluster_yp = yp;
++ HbTransformData shaped = { 0 };
+
+- 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;
++ /* Initial values. */
++ xresetfontsettings(mode, &font, &frcflags);
+
+- /* Skip dummy wide-character spacing. */
+- if (mode == ATTR_WDUMMY)
++ /* Shape the segment. */
++ hbtransform(&shaped, font->match, glyphs, 0, len);
++ xp = winx; yp = winy + font->ascent;
++ cluster_xp = xp; cluster_yp = yp;
++
++ for (code_idx = 0; code_idx < shaped.count; code_idx++) {
++ int idx = shaped.glyphs[code_idx].cluster;
++
++ if (glyphs[idx].mode & ATTR_WDUMMY)
+ 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;
+- }
+- yp = winy + font->ascent;
++ /* 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;
+ }
+
+- /* Lookup character index with default font. */
+- glyphidx = XftCharIndex(xw.dpy, font->match, rune);
+- if (glyphidx) {
++ if (shaped.glyphs[code_idx].codepoint != 0) {
++ /* If symbol is found, put it into the specs. */
+ specs[numspecs].font = font->match;
+- specs[numspecs].glyph = glyphidx;
+- specs[numspecs].x = (short)xp;
+- specs[numspecs].y = (short)yp;
+- xp += runewidth;
++ 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++;
+- 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;
++ } else {
++ /* If it's not found, try to fetch it through the font cache. */
++ rune = glyphs[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));
++ /* 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);
+ }
+
+- 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++;
+ }
+-
+- specs[numspecs].font = frc[f].font;
+- specs[numspecs].glyph = glyphidx;
+- specs[numspecs].x = (short)xp;
+- specs[numspecs].y = (short)yp;
+- xp += runewidth;
+- numspecs++;
+ }
+
++ /* Cleanup and get ready for next segment. */
++ 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;
+ XRenderColor colfg, colbg;
+@@ -1511,23 +1527,26 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
+ 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;
+
+@@ -1659,30 +1678,30 @@ xdrawline(Line line, int x1, int y1, int x2)
+ int i, x, ox, numspecs;
+ 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) {
+ ox = x;
+ base = new;
+ }
+ 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
+ xfinishdraw(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.2**:
-* [st-ligatures-0.9.2](0.9.2/st-ligatures-20240427-0.9.2.diff)
-* [st-ligatures-scrollback-0.9.2](0.9.2/st-ligatures-scrollback-20240427-0.9.2.diff)
-* [st-ligatures-scrollback-ringbuffer-0.9.2](0.9.2/st-ligatures-scrollback-ringbuffer-20240427-0.9.2.diff)
-* [st-ligatures-alpha-0.9.2](0.9.2/st-ligatures-alpha-20240427-0.9.2.diff)
-* [st-ligatures-alpha-scrollback-0.9.2](0.9.2/st-ligatures-alpha-scrollback-20240427-0.9.2.diff)
-* [st-ligatures-alpha-scrollback-ringbuffer-0.9.2](0.9.2/st-ligatures-alpha-scrollback-ringbuffer-20240427-0.9.2.diff)
-* [st-ligatures-boxdraw-0.9.2](0.9.2/st-ligatures-boxdraw-20240427-0.9.2.diff)
+* [st-ligatures-0.9.2](0.9.2/st-ligatures-20241226-0.9.2.diff)
+* [st-ligatures-scrollback-0.9.2](0.9.2/st-ligatures-scrollback-20241226-0.9.2.diff)
+* [st-ligatures-scrollback-ringbuffer-0.9.2](0.9.2/st-ligatures-scrollback-ringbuffer-20241226-0.9.2.diff)
+* [st-ligatures-alpha-0.9.2](0.9.2/st-ligatures-alpha-20241226-0.9.2.diff)
+* [st-ligatures-alpha-scrollback-0.9.2](0.9.2/st-ligatures-alpha-scrollback-20241226-0.9.2.diff)
+* [st-ligatures-alpha-scrollback-ringbuffer-0.9.2](0.9.2/st-ligatures-alpha-scrollback-ringbuffer-20241226-0.9.2.diff)
+* [st-ligatures-boxdraw-0.9.2](0.9.2/st-ligatures-boxdraw-20241226-0.9.2.diff)
**0.9**:
* [st-ligatures-0.9](0.9/st-ligatures-20240105-0.9.diff)