commit b05e1135200febcf01061df3e109943b6d828b8f
parent f344c3f28da7995f19ec0549d90f275d3fd1715a
Author: lumidify <nobody@lumidify.org>
Date:   Fri,  8 May 2020 21:13:42 +0200
Fix gap buffer and clean up textedit
Diffstat:
| M | gap_buffer.h |  |  | 64 | +++++++++++++++++++++++++++++++++------------------------------- | 
| M | textedit_wip.c |  |  | 619 | +++++++++++++++---------------------------------------------------------------- | 
| M | textedit_wip.h |  |  | 139 | +++++++------------------------------------------------------------------------ | 
3 files changed, 162 insertions(+), 660 deletions(-)
diff --git a/gap_buffer.h b/gap_buffer.h
@@ -27,31 +27,32 @@
 #include <stdio.h>
 #include <stdlib.h>
 
-#define LTK_GAP_BUFFER_INIT_DECL(type)						\
-struct ltk_gap_buffer_##type## {						\
+#define LTK_GAP_BUFFER_INIT_DECL(name, type)					\
+struct ltk_gap_buffer_##name## {						\
 	type *buf;								\
 	size_t buf_size;							\
 	size_t gap_left;							\
 	size_t gap_size;							\
 };										\
 										\
-struct ltk_gap_buffer_##type## * ltk_gap_buffer_create_##type##(void);		\
-struct ltk_gap_buffer_##type## *						\
-ltk_gap_buffer_create_from_data_##type##(type *data, size_t len);		\
-void ltk_gap_buffer_resize_gap_##type##(struct ltk_gap_buffer *gb, int len);	\
-void ltk_gap_buffer_insert_##type##(struct ltk_gap_buffer_##type## *gb,		\
+struct ltk_gap_buffer_##name## * ltk_gap_buffer_create_##name##(void);		\
+struct ltk_gap_buffer_##name## *						\
+ltk_gap_buffer_create_from_data_##name##(type *data, size_t len);		\
+void ltk_gap_buffer_resize_gap_##name##(					\
+    struct ltk_gap_buffer_##name## *gb, int len);				\
+void ltk_gap_buffer_insert_##name##(struct ltk_gap_buffer_##type## *gb,		\
     type *new, size_t start, size_t len);					\
-void ltk_gap_buffer_insert_single_##type##(					\
-    struct ltk_gap_buffer_##type## *gb, type new);				\
-void ltk_gap_buffer_move_gap_##type##(						\
-    struct ltk_gap_buffer_##type## *gb, size_t pos);				\
-void ltk_gap_buffer_destroy_##type##(struct ltk_gap_buffer_##type## *gb);
+void ltk_gap_buffer_insert_single_##name##(					\
+    struct ltk_gap_buffer_##name## *gb, type new);				\
+void ltk_gap_buffer_move_gap_##name##(						\
+    struct ltk_gap_buffer_##name## *gb, size_t pos);				\
+void ltk_gap_buffer_destroy_##name##(struct ltk_gap_buffer_##name## *gb);
 
-#define LTK_GAP_BUFFER_INIT_IMPL(type)						\
-struct ltk_gap_buffer_##type## *						\
-ltk_gap_buffer_create_##type##(void) {						\
-	struct ltk_gap_buffer_##type## *gb =					\
-	    malloc(sizeof(struct ltk_gap_buffer));				\
+#define LTK_GAP_BUFFER_INIT_IMPL(name, type)					\
+struct ltk_gap_buffer_##name## *						\
+ltk_gap_buffer_create_##name##(void) {						\
+	struct ltk_gap_buffer_##name## *gb =					\
+	    malloc(sizeof(struct ltk_gap_buffer_##name##));			\
 	if (!gb)								\
 		goto error;							\
 	gb->buf = malloc(8 * sizeof(type));					\
@@ -67,10 +68,10 @@ error:										\
 	exit(1);								\
 }										\
 										\
-struct ltk_gap_buffer_##type## *						\
-ltk_gap_buffer_create_from_data_##type##(type *data, size_t len) {		\
-	struct ltk_gap_buffer_##type## *gb =					\
-	    malloc(sizeof(struct ltk_gap_buffer));				\
+struct ltk_gap_buffer_##name## *						\
+ltk_gap_buffer_create_from_data_##name##(type *data, size_t len) {		\
+	struct ltk_gap_buffer_##name## *gb =					\
+	    malloc(sizeof(struct ltk_gap_buffer_##name##));			\
 	if (!gb) {								\
 		(void)fprintf(stderr, "Out of memory while trying to"		\
 		    "allocate gap buffer\n");					\
@@ -84,14 +85,15 @@ ltk_gap_buffer_create_from_data_##type##(type *data, size_t len) {		\
 }										\
 										\
 void										\
-ltk_gap_buffer_resize_gap_##type##(struct ltk_gap_buffer *gb, int len) {	\
+ltk_gap_buffer_resize_gap_##name##(						\
+    struct ltk_gap_buffer_##name## *gb, int len) {				\
 	/* FIXME: Should this use realloc? It's usually more efficient, but	\
 	   in this case, I would still need to copy the part after the gap	\
 	   manually, so it could potentially be copied twice, which really	\
 	   wouldn't be good. Maybe use realloc if only a small part is after	\
 	   the gap and just regular malloc otherwise? */			\
 	int new_size = gb->buf_size - gb->gap-size + len;			\
-	struct ltk_gap_buffer_##type## *new = malloc(new_size * sizeof(type));	\
+	struct ltk_gap_buffer_##name## *new = malloc(new_size * sizeof(type));	\
 	if (!new) {								\
 		(void)fprintf(stderr, "Out of memory while trying to"		\
 		    "resize gap buffer\n");					\
@@ -108,10 +110,10 @@ ltk_gap_buffer_resize_gap_##type##(struct ltk_gap_buffer *gb, int len) {	\
 }										\
 										\
 void										\
-ltk_gap_buffer_insert_##type##(struct ltk_gap_buffer_##type## *gb,		\
+ltk_gap_buffer_insert_##name##(struct ltk_gap_buffer_##name## *gb,		\
     type *new, size_t start, size_t len) {					\
 	if (gb->gap_size < len)							\
-		ltk_gap_buffer_resize_gap_##type##(gb, len + 8);		\
+		ltk_gap_buffer_resize_gap_##name##(gb, len + 8);		\
 	for (int i = 0; i < len; i++) {						\
 		gb->buf[gb->gap_left + i] = new[start + i];			\
 	}									\
@@ -120,14 +122,14 @@ ltk_gap_buffer_insert_##type##(struct ltk_gap_buffer_##type## *gb,		\
 }										\
 										\
 void										\
-ltk_gap_buffer_insert_single_##type##(						\
-    struct ltk_gap_buffer_##type## *gb, type new) {				\
-	ltk_gap_buffer_insert_##type##(gb, &new, 0, 1);				\
+ltk_gap_buffer_insert_single_##name##(						\
+    struct ltk_gap_buffer_##name## *gb, type new) {				\
+	ltk_gap_buffer_insert_##name##(gb, &new, 0, 1);				\
 }										\
 										\
 void										\
-ltk_gap_buffer_move_gap_##type##(						\
-    struct ltk_gap_buffer_##type## *gb, size_t pos) {				\
+ltk_gap_buffer_move_gap_##name##(						\
+    struct ltk_gap_buffer_##name## *gb, size_t pos) {				\
 	if (pos == gb->gap_left)						\
 		return;								\
 	if (pos < 0 || pos > gb->buf_size - gb->gap_size) {			\
@@ -148,7 +150,7 @@ ltk_gap_buffer_move_gap_##type##(						\
 }										\
 										\
 void										\
-ltk_gap_buffer_destroy_##type##(struct ltk_gap_buffer_##type## *gb) {		\
+ltk_gap_buffer_destroy_##name##(struct ltk_gap_buffer_##name## *gb) {		\
 	free(gb->buf);								\
 	free(gb);								\
 }
diff --git a/textedit_wip.c b/textedit_wip.c
@@ -1,6 +1,6 @@
 /*
  * This file is part of the Lumidify ToolKit (LTK)
- * Copyright (c) 2017, 2018, 2020 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2020 lumidify <nobody@lumidify.org>
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -33,290 +33,145 @@
 #include <fribidi.h>
 #include <harfbuzz/hb.h>
 #include <harfbuzz/hb-ot.h>
-#include "text-hb.h"
+#include "textedit_wip.h"
+#include "text-common.h"
 #include "ltk.h"
 
 extern Ltk *ltk_global;
 
-/* These unicode routines are taken from
- * https://github.com/JeffBezanson/cutef8 */
-
-/* is c the start of a utf8 sequence? */
-#define isutf(c) (((c)&0xC0)!=0x80)
-
-static const uint32_t offsetsFromUTF8[6] = {
-    0x00000000UL, 0x00003080UL, 0x000E2080UL,
-    0x03C82080UL, 0xFA082080UL, 0x82082080UL
-};
-
-/* next character without NUL character terminator */
-uint32_t u8_nextmemchar(const char *s, size_t *i)
-{
-    uint32_t ch = 0;
-    size_t sz = 0;
-    do {
-        ch <<= 6;
-        ch += (unsigned char)s[(*i)++];
-        sz++;
-    } while (!isutf(s[*i]));
-    ch -= offsetsFromUTF8[sz-1];
-
-    return ch;
-}
-
-/* number of characters in NUL-terminated string */
-size_t u8_strlen(const char *s)
-{
-    size_t count = 0;
-    size_t i = 0, lasti;
-
-    while (1) {
-        lasti = i;
-        while (s[i] > 0)
-            i++;
-        count += (i-lasti);
-        if (s[i++]==0) break;
-        (void)(isutf(s[++i]) || isutf(s[++i]) || ++i);
-        count++;
-    }
-    return count;
-}
-
-size_t u8_wc_toutf8(char *dest, uint32_t ch)
-{
-    if (ch < 0x80) {
-        dest[0] = (char)ch;
-        return 1;
-    }
-    if (ch < 0x800) {
-        dest[0] = (ch>>6) | 0xC0;
-        dest[1] = (ch & 0x3F) | 0x80;
-        return 2;
-    }
-    if (ch < 0x10000) {
-        dest[0] = (ch>>12) | 0xE0;
-        dest[1] = ((ch>>6) & 0x3F) | 0x80;
-        dest[2] = (ch & 0x3F) | 0x80;
-        return 3;
-    }
-    if (ch < 0x110000) {
-        dest[0] = (ch>>18) | 0xF0;
-        dest[1] = ((ch>>12) & 0x3F) | 0x80;
-        dest[2] = ((ch>>6) & 0x3F) | 0x80;
-        dest[3] = (ch & 0x3F) | 0x80;
-        return 4;
-    }
-    return 0;
-}
-
-LtkTextManager *
-ltk_init_text(char *font_name)
-{
-	LtkTextManager *tm = malloc(sizeof(LtkTextManager));
-	if (!tm) {
-		(void)printf("Memory exhausted when trying to create text manager.");
-		exit(1);
-	}
-	tm->font_paths = kh_init(fontid);
-	tm->font_cache = kh_init(fontstruct);
-	tm->glyph_cache = kh_init(glyphcache);
-	tm->font_id_cur = 0;
-	ltk_load_default_font(tm, font_name);
-
-	return tm;
-}
-
-void
-ltk_destroy_text_manager(LtkTextManager *tm)
+/* FIXME: allow to either use fribidi for basic shaping and don't use harfbuzz then,
+          or just use harfbuzz (then fribidi doesn't need to do any shaping) */
+LtkTextLine *
+ltk_create_text_line(LtkTextManager *tm, char *text, uint16_t fontid, uint16_t size)
 {
+	/* NOTE: This doesn't actually take fontid into account right now - should it? */
+	LtkTextLine *tl = malloc(sizeof(LtkTextLine));
+	tl->start_segment = NULL;
+	LtkTextSegment *cur_ts = NULL;
+	LtkTextSegment *new_ts = NULL;
+	uint16_t cur_font_id = fontid;
 	int k;
+	LtkFont *font;
 
-	kh_destroy(fontid, tm->font_paths);
-
-	for (k = kh_begin(tm->font_cache); k != kh_end(tm->font_cache); k++) {
-		if (kh_exist(tm->font_cache, k)) {
-			ltk_destroy_font(kh_value(tm->font_cache, k));
-		}
-	}
-	kh_destroy(fontstruct, tm->font_cache);
-
-	for (k = kh_begin(tm->glyph_cache); k != kh_end(tm->glyph_cache); k++) {
-		if (kh_exist(tm->glyph_cache, k)) {
-			ltk_destroy_glyph_cache(kh_value(tm->glyph_cache, k));
-		}
-	}
-	kh_destroy(glyphcache, tm->glyph_cache);
-
-	free(tm);
-}
-
-LtkGlyphInfo *
-ltk_create_glyph_info(LtkFont *font, unsigned int id, float scale)
-{
-	LtkGlyphInfo *glyph = malloc(sizeof(LtkGlyphInfo));
-	if (!glyph) {
-		(void)printf("Out of memory!\n");
-		exit(1);
-	}
-	
-	glyph->id = id;
-	glyph->refs = 0;
-	glyph->alphamap = stbtt_GetGlyphBitmap(
-		&font->info, scale, scale, id, &glyph->w,
-		&glyph->h, &glyph->xoff, &glyph->yoff
-	);
-
-	return glyph;
-}
-
-void
-ltk_destroy_glyph_info(LtkGlyphInfo *gi)
-{
-	free(gi->alphamap);
-	free(gi);
-}
-
-LtkGlyphInfo *
-ltk_get_glyph_info(LtkFont *font, unsigned int id, float scale, khash_t(glyphinfo) *cache)
-{
-	int ret;
-	khint_t k;
-	LtkGlyphInfo *glyph;
-	k = kh_get(glyphinfo, cache, id);
-	if (k == kh_end(cache)) {
-		glyph = ltk_create_glyph_info(font, id, scale);
-		/* FIXME: error checking with ret */
-		k = kh_put(glyphinfo, cache, id, &ret);
-		kh_value(cache, k) = glyph;
-	} else {
-		glyph = kh_value(cache, k);
+	unsigned int ulen = u8_strlen(text);
+	uint32_t *log_str = malloc(sizeof(uint32_t) * ulen);
+	size_t inc = 0;
+	for (int i = 0; i < ulen; i++) {
+		log_str[i] = u8_nextmemchar(text, &inc);
 	}
+	FriBidiChar *vis_str = malloc(sizeof(FriBidiChar) * ulen);
+	ulen = fribidi_charset_to_unicode(FRIBIDI_CHAR_SET_UTF8, text, strlen(text), log_str);
+	fribidi_log2vis(log_str, ulen, pbase_dir, vis_str, NULL, NULL, NULL);
 
-	return glyph;
-}
-
-khint_t
-ltk_create_glyph_cache(LtkTextManager *tm, uint16_t font_id, uint16_t font_size)
-{
-	khash_t(glyphinfo) *cache = kh_init(glyphinfo);
-	int ret;
-	khint_t k;
-	/* I guess I can just ignore ret for now */
-	k = kh_put(glyphcache, tm->glyph_cache, font_id << 16 + font_size, &ret);
-	kh_value(tm->glyph_cache, k) = cache;
+	hb_unicode_funcs_t *ufuncs = hb_unicode_funcs_get_default();
+	hb_script_t cur_script = hb_unicode_script(ufuncs, vis_str[0]);
+	hb_script_t last_script = cur_script;
+	size_t pos = 0;
+	size_t last_pos = 0;
+	size_t start_pos = 0;
+	uint32_t ch;
 
-	return k;
-}
+	for (int p = 0; p <= ulen; p++) {
+		cur_script = hb_unicode_script(ufuncs, vis_str[p]);
+		if (p == ulen ||
+			(last_script != cur_script &&
+			 cur_script != HB_SCRIPT_INHERITED &&
+			 cur_script != HB_SCRIPT_COMMON)) {
+			FcPattern *pat = FcPatternDuplicate(tm->fcpattern);
+			FcPattern *match;
+			FcResult result;
+			FcPatternAddBool(pat, FC_SCALABLE, 1);
+			FcConfigSubstitute(NULL, pat, FcMatchPattern);
+			FcDefaultSubstitute(pat);
+			FcCharSet *cs = FcCharSetCreate();
+			for (int i = start_pos; i < p; i++) {
+				FcCharSetAddChar(cs, vis_str[i]);
+			}
+			FcPatternAddCharSet(pat, FC_CHARSET, cs);
+			match = FcFontMatch(NULL, pat, &result);
+			char *file;
+			FcPatternGetString(match, FC_FILE, 0, &file);
+			cur_font_id = ltk_get_font(tm, file);
+			k = kh_get(fontstruct, tm->font_cache, cur_font_id);
+			font = kh_value(tm->font_cache, k);
+			FcPatternDestroy(match);
+			FcPatternDestroy(pat);
+			// handle case that this is the last character
+			if (p == ulen) {
+				last_script = cur_script;
+			}
+			/* FIXME: There should be better handling for cases
+			   where an error occurs while creating the segment */
+			new_ts = ltk_create_text_segment(
+				tm, vis_str + start_pos,
+				p - start_pos, cur_font_id,
+				size, last_script
+			);
+			if (!new_ts) continue;
+			new_ts->next = NULL;
+			if (!tl->start_segment) tl->start_segment = new_ts;
+			if (cur_ts) cur_ts->next = new_ts;
+			cur_ts = new_ts;
 
-void
-ltk_destroy_glyph_cache(khash_t(glyphinfo) *cache)
-{
-	int k;
-	for (k = kh_begin(cache); k != kh_end(cache); k++) {
-		if (kh_exist(cache, k)) {
-			ltk_destroy_glyph_info(kh_value(cache, k));
+			start_pos = p;
+			last_script = cur_script;
 		}
 	}
-	kh_destroy(glyphinfo, cache);
-}
-
-void
-ltk_load_default_font(LtkTextManager *tm, char *name)
-{
-	FcPattern *match;
-	FcResult result;
-	char *file;
-	int index;
-	uint16_t font;
 
-	tm->fcpattern = FcNameParse(name);
-	FcPatternAddString(tm->fcpattern, FC_FONTFORMAT, "truetype");
-	FcConfigSubstitute(NULL, tm->fcpattern, FcMatchPattern);
-	FcDefaultSubstitute(tm->fcpattern);
-	match = FcFontMatch(NULL, tm->fcpattern, &result);
-
-	FcPatternGetString (match, FC_FILE, 0, (FcChar8 **) &file);
-	/* FIXME: Why is index never used? This is the index within the font file,
-	   so it might be important, although I'm not sure if stb_truetype even
-	   supports it */
-	FcPatternGetInteger (match, FC_INDEX, 0, &index);
-
-	tm->default_font = ltk_get_font(tm, file);
-
-	FcPatternDestroy (match);
-}
+	free(vis_str);
+	free(log_str);
 
-LtkFont *
-ltk_create_font(char *path, uint16_t id)
-{
-	long len;
-	LtkFont *font = malloc(sizeof(LtkFont));
-	if (!font) {
-		(void)fprintf(stderr, "Out of memory!\n");
-		exit(1);
+	/* calculate width of text line
+	   NOTE: doesn't work with mixed horizontal and vertical text */
+	LtkTextSegment *ts = tl->start_segment;
+	int is_hor = HB_DIRECTION_IS_HORIZONTAL(ts->dir);
+	tl->y_max = tl->x_max = INT_MIN;
+	tl->y_min = tl->x_min = INT_MAX;
+	tl->w = tl->h = 0;
+	while (ts) {
+		if (HB_DIRECTION_IS_HORIZONTAL(ts->dir) != is_hor) {
+			(void)fprintf(stderr, "WARNING: mixed horizontal/vertical text is not supported; ignoring\n");
+			continue;
+		}
+		if (is_hor) {
+			if (tl->y_max < ts->y_max) {
+				tl->y_max = ts->y_max;
+			}
+			if (tl->y_min > ts->y_min) {
+				tl->y_min = ts->y_min;
+			}
+			tl->w += ts->w;
+		} else {
+			if (tl->x_max < ts->x_max) {
+				tl->x_max = ts->x_max;
+			}
+			if (tl->x_min > ts->x_min) {
+				tl->x_min = ts->x_min;
+			}
+			tl->h += ts->h;
+		}
+		ts = ts->next;
 	}
-	char *contents = ltk_read_file(path, &len);
-	if (!stbtt_InitFont(&font->info, contents, 0))
-	{
-		(void)fprintf(stderr, "Failed to load font %s\n", path);
-		exit(1);
+	if (is_hor) {
+		tl->h = tl->y_max - tl->y_min;
+	} else {
+		tl->w = tl->x_max - tl->x_min;
 	}
-	/* FIXME: make use of the destroy function (last argument to hb_blob_create - see hb-blob.cc in harfbuzz source) */
-	hb_blob_t *blob = hb_blob_create(contents, len, HB_MEMORY_MODE_READONLY, NULL, NULL);
-	hb_face_t *face = hb_face_create(blob, 0);
-	/* FIXME: need to use destroy function in order for the original file data to be freed? */
-	hb_blob_destroy(blob);
-	font->hb = hb_font_create(face);
-	hb_face_destroy(face);
-	hb_ot_font_set_funcs(font->hb);
-	font->id = id;
-	font->refs = 0;
 
-	return font;
+	return tl;
 }
 
 void
-ltk_destroy_font(LtkFont *font)
-{
-	free(font->info.data);
-	hb_font_destroy(font->hb);
-	free(font);
-}
-
-uint16_t
-ltk_load_font(LtkTextManager *tm, char *path)
-{
-	LtkFont *font = ltk_create_font(path, tm->font_id_cur++);
-	int ret;
-	khint_t k;
-	/* FIXME: does kh_destroy also free these copied strings properly? */
-	char *key = strdup(path);
-	k = kh_put(fontid, tm->font_paths, key, &ret);
-	kh_value(tm->font_paths, k) = font->id;
-	k = kh_put(fontstruct, tm->font_cache, (khint_t) font->id, &ret);
-	kh_value(tm->font_cache, k) = font;
-	k = kh_get(fontid, tm->font_paths, path);
-
-	return font->id;
-}
-
-uint16_t
-ltk_get_font(LtkTextManager *tm, char *path)
-{
-	int ret;
-	khint_t k;
-	uint16_t id;
-	k = kh_get(fontid, tm->font_paths, path);
-	if (k == kh_end(tm->font_paths)) {
-		id = ltk_load_font(tm, path);
-	} else {
-		id = kh_value(tm->font_paths, k);
+ltk_destroy_text_line(LtkTextLine *tl) {
+	LtkTextSegment *last_ts;
+	LtkTextSegment *cur_ts = tl->start_segment;
+	while (cur_ts) {
+		last_ts = cur_ts;
+		cur_ts = cur_ts->next;
+		ltk_destroy_text_segment(last_ts);
 	}
-
-	return id;
 }
 
-
 /* FIXME: could use unsigned int for fontid and size as long as there is code to check neither of them become too large
    -> in case I want to get rid of uint_16_t, etc. */
 LtkTextSegment *
@@ -440,18 +295,6 @@ ltk_create_text_segment(LtkTextManager *tm, uint32_t *text, unsigned int len, ui
 }
 
 void
-ltk_destroy_glyph(LtkGlyph *glyph, khash_t(glyphinfo) *cache)
-{
-	int k;
-	if (--glyph->info->refs < 1) {
-		k = kh_get(glyphinfo, cache, glyph->info->id);
-		kh_del(glyphinfo, cache, k);
-		ltk_destroy_glyph_info(glyph->info);
-	}
-	free(glyph);
-}
-
-void
 ltk_destroy_text_segment(LtkTextSegment *ts)
 {
 	LtkGlyph *glyph, *next_glyph;
@@ -554,6 +397,7 @@ ltk_render_text_segment(
 }
 
 /*
+NOTE: The following notes are outdated.
 Notes: When inserting a character, check what the direction of the surrounding
 script is - if it is the same (or a weak direction), then just insert the char
 without re-doing the bidi algorithm. Only redo the whole line/paragraph is the
@@ -577,102 +421,6 @@ soon as a different script is inserted, everything has to be redone anyways. Als
 when reshaping with context, only the text in the current run has to be passed at all.
 */
 
-struct ltk_gap_buffer *
-ltk_gap_buffer_create(void) {
-	struct ltk_gap_buffer *gb = malloc(sizeof(struct ltk_gap_buffer));
-	if (!gb)
-		goto error;
-	gb->buf = malloc(8 * sizeof(uint32_t));
-	if (!gb->buf)
-		goto error;
-	gb->buf_size = 8;
-	gb->gap_left = 0;
-	gb->gap_size = 8;
-	return gb;
-error:
-	(void)fprintf(stderr, "Out of memory while trying to allocate gap buffer\n");
-	exit(1);
-}
-
-struct ltk_gap_buffer *
-ltk_gap_buffer_create_from_data(uint32_t *data, size_t len) {
-	struct ltk_gap_buffer *gb = malloc(sizeof(struct ltk_gap_buffer));
-	if (!gb) {
-		(void)fprintf(stderr, "Out of memory while trying to allocate gap buffer\n");
-		exit(1);
-	}
-	gb->buf = data;
-	gb->buf_size = len;
-	gb->gap_left = 0;
-	gb->gap_size = 0;
-	return gb;
-}
-
-void
-ltk_gap_buffer_resize_gap(struct ltk_gap_buffer *gb, int len) {
-	/* FIXME: Should this use realloc? It's usually more efficient, but
-	   in this case, I would still need to copy the part after the gap
-	   manually, so it could potentially be copied twice, which really
-	   wouldn't be good. Maybe use realloc if only a small part is after
-	   the gap and just regular malloc otherwise? */
-	int new_size = gb->buf_size - gb->gap-size + len;
-	struct ltk_gap_buffer *new = malloc(new_size * sizeof(uint32_t));
-	if (!new) {
-		(void)fprintf(stderr, "Out of memory while trying to resize gap buffer\n");
-		exit(1);
-	}
-	for (int i = 0; i < gb->gap_left; i++) {
-		new[i] = gb->buf[i];
-	}
-	for (int i = gb->gap_left + gb->gap_size; i < gb->buf_size) {
-		new[i - gb->gap_size + len] = gb->buf[i];
-	}
-	free(gb->buf);
-	gb->buf = new;
-}
-
-void
-ltk_gap_buffer_insert(struct ltk_gap_buffer *gb, uint32_t *new, size_t start, size_t len) {
-	if (gb->gap_size < len)
-		ltk_gap_buffer_resize_gap(gb, len + 8);
-	for (int i = 0; i < len; i++) {
-		gb->buf[gb->gap_left + i] = new[start + i];
-	}
-	gb->gap_left = gb->gap_left + len;
-	gb->gap_size -= len;
-}
-
-void
-ltk_gap_buffer_insert_single(struct ltk_gap_buffer *gb, uint32_t new) {
-	ltk_gap_buffer_insert(gb, &new, 0, 1);
-}
-
-void
-ltk_gap_buffer_move_gap(struct ltk_gap_buffer *gb, size_t pos) {
-	if (pos == gb->gap_left)
-		return;
-	if (pos < 0 || pos > gb->buf_size - gb->gap_size) {
-		(void)fprintf(stderr, "Index out of range while moving gap buffer gap\n");
-		return;
-	}
-	if (pos >= gb->gap_left) {
-		for (int i = gb->gap_left; i < pos) {
-			gb->buf[i] = gb->buf[i + gb->gap_size];
-		}
-	} else {
-		for (int i = gb->gap_left - 1; i >= pos; i--) {
-			gb->buf[i + gb->gap_size] = gb->buf[i];
-		}
-	}
-	gb->gap_left = pos;
-}
-
-void
-ltk_gap_buffer_destroy(struct ltk_gap_buffer *gb) {
-	free(gb->buf);
-	free(gb);
-}
-
 void
 ltk_text_line_insert_text(struct ltk_text_line *tl, uint32_t *text, size_t len) {
 	/* check if any characters have a different script, only recalc then */
@@ -685,136 +433,3 @@ ltk_text_line_delete_text(struct ltk_text_line *tl, size_t len) {
 void
 ltk_text_line_delete_cur_cluster(struct ltk_text_line *tl) {
 }
-
-/* FIXME: allow to either use fribidi for basic shaping and don't use harfbuzz then,
-          or just use harfbuzz (then fribidi doesn't need to do any shaping) */
-LtkTextLine *
-ltk_create_text_line(LtkTextManager *tm, char *text, uint16_t fontid, uint16_t size)
-{
-	/* NOTE: This doesn't actually take fontid into account right now - should it? */
-	LtkTextLine *tl = malloc(sizeof(LtkTextLine));
-	tl->start_segment = NULL;
-	LtkTextSegment *cur_ts = NULL;
-	LtkTextSegment *new_ts = NULL;
-	uint16_t cur_font_id = fontid;
-	int k;
-	LtkFont *font;
-
-	unsigned int ulen = u8_strlen(text);
-	uint32_t *log_str = malloc(sizeof(uint32_t) * ulen);
-	size_t inc = 0;
-	for (int i = 0; i < ulen; i++) {
-		log_str[i] = u8_nextmemchar(text, &inc);
-	}
-	FriBidiChar *vis_str = malloc(sizeof(FriBidiChar) * ulen);
-	ulen = fribidi_charset_to_unicode(FRIBIDI_CHAR_SET_UTF8, text, strlen(text), log_str);
-	fribidi_log2vis(log_str, ulen, pbase_dir, vis_str, NULL, NULL, NULL);
-
-	hb_unicode_funcs_t *ufuncs = hb_unicode_funcs_get_default();
-	hb_script_t cur_script = hb_unicode_script(ufuncs, vis_str[0]);
-	hb_script_t last_script = cur_script;
-	size_t pos = 0;
-	size_t last_pos = 0;
-	size_t start_pos = 0;
-	uint32_t ch;
-
-	for (int p = 0; p <= ulen; p++) {
-		cur_script = hb_unicode_script(ufuncs, vis_str[p]);
-		if (p == ulen ||
-			(last_script != cur_script &&
-			 cur_script != HB_SCRIPT_INHERITED &&
-			 cur_script != HB_SCRIPT_COMMON)) {
-			FcPattern *pat = FcPatternDuplicate(tm->fcpattern);
-			FcPattern *match;
-			FcResult result;
-			FcPatternAddBool(pat, FC_SCALABLE, 1);
-			FcConfigSubstitute(NULL, pat, FcMatchPattern);
-			FcDefaultSubstitute(pat);
-			FcCharSet *cs = FcCharSetCreate();
-			for (int i = start_pos; i < p; i++) {
-				FcCharSetAddChar(cs, vis_str[i]);
-			}
-			FcPatternAddCharSet(pat, FC_CHARSET, cs);
-			match = FcFontMatch(NULL, pat, &result);
-			char *file;
-			FcPatternGetString(match, FC_FILE, 0, &file);
-			cur_font_id = ltk_get_font(tm, file);
-			k = kh_get(fontstruct, tm->font_cache, cur_font_id);
-			font = kh_value(tm->font_cache, k);
-			FcPatternDestroy(match);
-			FcPatternDestroy(pat);
-			// handle case that this is the last character
-			if (p == ulen) {
-				last_script = cur_script;
-			}
-			/* FIXME: There should be better handling for cases
-			   where an error occurs while creating the segment */
-			new_ts = ltk_create_text_segment(
-				tm, vis_str + start_pos,
-				p - start_pos, cur_font_id,
-				size, last_script
-			);
-			if (!new_ts) continue;
-			new_ts->next = NULL;
-			if (!tl->start_segment) tl->start_segment = new_ts;
-			if (cur_ts) cur_ts->next = new_ts;
-			cur_ts = new_ts;
-
-			start_pos = p;
-			last_script = cur_script;
-		}
-	}
-
-	free(vis_str);
-	free(log_str);
-
-	/* calculate width of text line
-	   NOTE: doesn't work with mixed horizontal and vertical text */
-	LtkTextSegment *ts = tl->start_segment;
-	int is_hor = HB_DIRECTION_IS_HORIZONTAL(ts->dir);
-	tl->y_max = tl->x_max = INT_MIN;
-	tl->y_min = tl->x_min = INT_MAX;
-	tl->w = tl->h = 0;
-	while (ts) {
-		if (HB_DIRECTION_IS_HORIZONTAL(ts->dir) != is_hor) {
-			(void)fprintf(stderr, "WARNING: mixed horizontal/vertical text is not supported; ignoring\n");
-			continue;
-		}
-		if (is_hor) {
-			if (tl->y_max < ts->y_max) {
-				tl->y_max = ts->y_max;
-			}
-			if (tl->y_min > ts->y_min) {
-				tl->y_min = ts->y_min;
-			}
-			tl->w += ts->w;
-		} else {
-			if (tl->x_max < ts->x_max) {
-				tl->x_max = ts->x_max;
-			}
-			if (tl->x_min > ts->x_min) {
-				tl->x_min = ts->x_min;
-			}
-			tl->h += ts->h;
-		}
-		ts = ts->next;
-	}
-	if (is_hor) {
-		tl->h = tl->y_max - tl->y_min;
-	} else {
-		tl->w = tl->x_max - tl->x_min;
-	}
-
-	return tl;
-}
-
-void
-ltk_destroy_text_line(LtkTextLine *tl) {
-	LtkTextSegment *last_ts;
-	LtkTextSegment *cur_ts = tl->start_segment;
-	while (cur_ts) {
-		last_ts = cur_ts;
-		cur_ts = cur_ts->next;
-		ltk_destroy_text_segment(last_ts);
-	}
-}
diff --git a/textedit_wip.h b/textedit_wip.h
@@ -1,6 +1,6 @@
 /*
  * This file is part of the Lumidify ToolKit (LTK)
- * Copyright (c) 2017, 2018, 2020 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2020 lumidify <nobody@lumidify.org>
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -31,8 +31,10 @@ Requires the following includes:
 <fontconfig/fontconfig.h>
 */
 
+#include "gap_buffer.h"
+
 /* Contains glyph info specific to one run of text */
-typedef struct ltk_glyph {
+struct ltk_glyph {
 	LtkGlyphInfo *info;
 	int x_offset; /* additional x offset given by harfbuzz */
 	int y_offset; /* additional y offset given by harfbuzz */
@@ -43,18 +45,12 @@ typedef struct ltk_glyph {
 	uint32_t cluster; /* index of char in original text - from harfbuzz */
 };
 
-struct ltk_gap_buffer {
-	uint32_t *buf;
-	size_t buf_size;
-	size_t gap_left;
-	size_t gap_size;
-};
+LTK_GAP_BUFFER_INIT_DECL(uint32, uint32_t)
+LTK_GAP_BUFFER_INIT_DECL(int, int)
+LTK_GAP_BUFFER_INIT_DECL(glyph, struct ltk_glyph)
 
-/* FIXME: macro version of gap buffer */
 struct ltk_text_run {
-	/* maybe make gap buffer of glyphs? */
-	struct ltk_glyph *head_glyph;
-	struct ltk_glyph *cur_glyph;
+	struct ltk_gap_buffer_glyph *glyphs;
 	struct ltk_text_run *next;
 	LtkFont *font;
 	unsigned int w;
@@ -69,9 +65,10 @@ struct ltk_text_run {
 }
 
 struct ltk_text_line {
-	struct ltk_gap_buffer *text_buf; /* buffer of the logical text */
-	struct ltk_gap_buffer *visual_text; /* buffer of visual text */
-	/* still need log2vis and vis2log */
+	struct ltk_gap_buffer_uint32 *log_buf; /* buffer of the logical text */
+	struct ltk_gap_buffer_uint32 *vis_buf; /* buffer of visual text */
+	struct ltk_gap_buffer_int *log2vis;
+	struct ltk_gap_buffer_int *vis2log;
 	struct ltk_text_run *runs; /* first node in the linked list of runs */
 	struct ltk_text_run *cur_run; /* current node in the linked list of runs */
 	struct ltk_text_line *next; /* next text line in the buffer */
@@ -84,115 +81,3 @@ struct ltk_text_buffer {
 	struct ltk_text_line *cur_line;
 	unsigned int line_gap;
 };
-
-typedef struct {
-	stbtt_fontinfo info;
-	hb_font_t *hb;
-	uint16_t id;
-	unsigned int refs;
-} LtkFont;
-
-/* Contains general info on glyphs that doesn't change regardless of the context */
-typedef struct _LtkGlyphInfo {
-	unsigned int id;
-	unsigned char *alphamap;
-	unsigned int w;
-	unsigned int h;
-	unsigned int xoff; /* x offset from origin to top left corner of glyph */
-	unsigned int yoff; /* y offset from origin to top left corner of glyph */
-	unsigned int refs;
-	/* FIXME: does refs need to be long? It could cause problems if a
-	program tries to cache/"keep alive" a lot of pages of text. */
-} LtkGlyphInfo;
-
-/* Single segment of text with same font */
-typedef struct LtkTextSegment {
-	uint16_t font_id;
-	uint16_t font_size;
-	unsigned int w;
-	unsigned int h;
-	int start_x;
-	int start_y;
-	int x_min;
-	int y_min;
-	int x_max;
-	int y_max;
-	hb_direction_t dir;
-	uint32_t *str;
-	LtkGlyph *start_glyph;
-	struct LtkTextSegment *next;
-} LtkTextSegment;
-
-/* Single line of text */
-typedef struct {
-	unsigned int w;
-	unsigned int h;
-	int x_max;
-	int x_min;
-	int y_max;
-	int y_min;
-	FriBidiParType dir;
-	LtkTextSegment *start_segment;
-} LtkTextLine;
-
-/* Hash definitions */
-/* glyph id -> glyph info struct */
-KHASH_MAP_INIT_INT(glyphinfo, LtkGlyphInfo*)
-/* font path, size -> glyph cache hash */
-KHASH_MAP_INIT_INT(glyphcache, khash_t(glyphinfo)*)
-/* font path -> font id */
-KHASH_MAP_INIT_STR(fontid, uint16_t)
-/* font id -> font struct */
-KHASH_MAP_INIT_INT(fontstruct, LtkFont*)
-
-typedef struct LtkTextManager {
-	khash_t(fontid) *font_paths;
-	khash_t(fontstruct) *font_cache;
-	khash_t(glyphcache) *glyph_cache;
-	FcPattern *fcpattern;
-	uint16_t default_font;
-	uint16_t font_id_cur;
-} LtkTextManager;
-
-LtkTextManager *ltk_init_text(char *font_name);
-
-void ltk_destroy_text_manager(LtkTextManager *tm);
-
-LtkGlyphInfo *ltk_create_glyph_info(LtkFont *font, unsigned int id, float scale);
-
-void ltk_destroy_glyph_info(LtkGlyphInfo *gi);
-
-LtkGlyphInfo *ltk_get_glyph_info(LtkFont *font, unsigned int id, float scale, khash_t(glyphinfo) *cache);
-
-khint_t ltk_create_glyph_cache(LtkTextManager *tm, uint16_t font_id, uint16_t font_size);
-
-void ltk_destroy_glyph_cache(khash_t(glyphinfo) *cache);
-
-void ltk_load_default_font(LtkTextManager *tm, char *name);
-
-LtkFont *ltk_create_font(char *path, uint16_t id);
-
-void ltk_destroy_font(LtkFont *font);
-
-/* FIXME: need to figure out how exactly the whole font system is going to work, especially with default fonts, etc. */
-uint16_t ltk_load_font(LtkTextManager *tm, char *path);
-
-uint16_t ltk_get_font(LtkTextManager *tm, char *path);
-
-/* TODO: different sizes, colors, styles, etc. */
-LtkTextLine *ltk_create_text_line(LtkTextManager *tm, char *text, uint16_t fontid, uint16_t size);
-
-/* FIXME: could use unsigned int for fontid and size as long as there is code to check neither of them become too large
-   -> in case I want to get rid of uint_16_t, etc. */
-LtkTextSegment *ltk_create_text_segment(LtkTextManager *tm, uint32_t *text, unsigned int text_len, uint16_t fontid, uint16_t size, hb_script_t script);
-
-void ltk_destroy_glyph(LtkGlyph *glyph, khash_t(glyphinfo) *cache);
-
-void ltk_destroy_text_segment(LtkTextSegment *ts);
-
-XImage *ltk_render_text_line(LtkTextLine *tl, Display *dpy, Window window, GC gc, Colormap colormap, XColor fg, XColor bg);
-
-void ltk_render_text_segment(LtkTextSegment *ts, unsigned int start_x, unsigned int start_y, XImage *img, XColor fg);
-
-
-#endif