commit 57d85ccfe4c9ac3de63dab6979e65dbe0696f4b7
parent 05e4c9d086c3db2ad726f789cee1b9f8d7ea07b9
Author: lumidify <nobody@lumidify.org>
Date:   Fri,  8 May 2020 20:36:38 +0200
Refactor gap buffer
Diffstat:
6 files changed, 364 insertions(+), 158 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -1 +1,3 @@
 test
+*.o
+test1
diff --git a/gap_buffer.h b/gap_buffer.h
@@ -0,0 +1,156 @@
+/*
+ * This file is part of the Lumidify ToolKit (LTK)
+ * 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
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef _LTK_GAP_BUFFER_H_
+#define _LTK_GAP_BUFFER_H_
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#define LTK_INIT_GAP_BUFFER_DECL(type)						\
+struct ltk_gap_buffer_##type## {						\
+	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,		\
+    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);
+
+#define LTK_INIT_GAP_BUFFER_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));				\
+	if (!gb)								\
+		goto error;							\
+	gb->buf = malloc(8 * sizeof(type));					\
+	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_##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));				\
+	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_##type##(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_##type## *new = malloc(new_size * sizeof(type));	\
+	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_##type##(struct ltk_gap_buffer_##type## *gb,		\
+    type *new, size_t start, size_t len) {					\
+	if (gb->gap_size < len)							\
+		ltk_gap_buffer_resize_gap_##type##(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_##type##(						\
+    struct ltk_gap_buffer_##type## *gb, type new) {				\
+	ltk_gap_buffer_insert_##type##(gb, &new, 0, 1);				\
+}										\
+										\
+void										\
+ltk_gap_buffer_move_gap_##type##(						\
+    struct ltk_gap_buffer_##type## *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_##type##(struct ltk_gap_buffer_##type## *gb) {		\
+	free(gb->buf);								\
+	free(gb);								\
+}
+
+#endif /* _LTK_GAP_BUFFER_H_ */
diff --git a/test1.c b/test1.c
@@ -34,8 +34,8 @@ int main(int argc, char *argv[])
 	LtkButton *button3 = ltk_create_button(window1, "I'm a button!", NULL);
 	ltk_grid_widget(button3, grid1, 1, 0, 1, 1, LTK_STICKY_TOP | LTK_STICKY_BOTTOM | LTK_STICKY_RIGHT);
 	//LtkButton *button4 = ltk_create_button(window1, "I'm a button!", NULL);
-	//LtkButton *button4 = ltk_create_button(window1, "ہمارے بارے میں blablabla", NULL);
-	LtkButton *button4 = ltk_create_button(window1, "پَیدایش", NULL);
+	LtkButton *button4 = ltk_create_button(window1, "ہمارے بارے میں blablabla", NULL);
+	//LtkButton *button4 = ltk_create_button(window1, "پَیدایش", NULL);
 	ltk_grid_widget(button4, grid1, 1, 1, 1, 1, LTK_STICKY_LEFT | LTK_STICKY_BOTTOM);
 	ltk_mainloop();
 }
diff --git a/text-hb.c b/text-hb.c
@@ -338,11 +338,17 @@ ltk_create_text_line(LtkTextManager *tm, char *text, uint16_t fontid, uint16_t s
 	}
         FriBidiCharType *pbase_dir = malloc(sizeof(FriBidiCharType) * ulen);
         for (int i = 0; i < ulen; i++) {
-                pbase_dir[i] = FRIBIDI_TYPE_ON;
+                pbase_dir[i] = i;
         }
+	FriBidiCharType pbase_dir1 = FRIBIDI_TYPE_ON;
 	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);
+	fribidi_log2vis(log_str, ulen, &pbase_dir1, vis_str, pbase_dir, NULL, NULL);
+	printf("%d\n", pbase_dir1);
+	for (int i = 0; i < ulen; i++) {
+		printf("%d ", pbase_dir[i]);
+	}
+	printf("\n");
 
 	hb_unicode_funcs_t *ufuncs = hb_unicode_funcs_get_default();
 	hb_script_t cur_script = hb_unicode_script(ufuncs, vis_str[0]);
@@ -500,7 +506,7 @@ ltk_create_text_segment(LtkTextManager *tm, uint32_t *text, unsigned int len, ui
 	hb_direction_t dir = hb_script_get_horizontal_direction(script);
 	hb_buffer_set_direction(buf, dir);
 	hb_buffer_set_script(buf, script);
-	hb_buffer_add_codepoints(buf, ts->str, len, 1, len-1);
+	hb_buffer_add_codepoints(buf, ts->str, len, 0, len);
 	/* According to https://harfbuzz.github.io/the-distinction-between-levels-0-and-1.html
 	 * this should be level 1 clustering instead of level 0 */
 	hb_buffer_set_cluster_level(buf, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS);
@@ -518,9 +524,6 @@ ltk_create_text_segment(LtkTextManager *tm, uint32_t *text, unsigned int len, ui
 	/* magic, do not touch */
 	LtkGlyph *glyph;
 	for (int i = 0; i < text_len; i++) {
-		if (len == 7) {
-			printf("%d\n", ginf[i].cluster);
-		}
 		gi = &ginf[i];
 		gp = &gpos[i];
 		glyph = malloc(sizeof(LtkGlyph));
diff --git a/textedit_wip.c b/textedit_wip.c
@@ -316,143 +316,6 @@ ltk_get_font(LtkTextManager *tm, char *path)
 	return id;
 }
 
-/* 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);
-	FriBidiChar *log_str = malloc(sizeof(FriBidiChar) * ulen);
-	size_t inc = 0;
-	for (int i = 0; i < ulen; i++) {
-		log_str[i] = u8_nextmemchar(text, &inc);
-	}
-        FriBidiCharType *pbase_dir = malloc(sizeof(FriBidiCharType) * ulen);
-        for (int i = 0; i < ulen; i++) {
-                pbase_dir[i] = FRIBIDI_TYPE_ON;
-        }
-	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);
-	free(pbase_dir);
-
-	/* 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);
-	}
-}
 
 /* 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. */
@@ -719,19 +582,32 @@ ltk_gap_buffer_create(void) {
 	struct ltk_gap_buffer *gb = malloc(sizeof(struct ltk_gap_buffer));
 	if (!gb)
 		goto error;
-	gb->buf = malloc(4 * sizeof(uint32_t));
+	gb->buf = malloc(8 * sizeof(uint32_t));
 	if (!gb->buf)
 		goto error;
 	gb->buf_size = 8;
 	gb->gap_left = 0;
 	gb->gap_size = 8;
-	gb->gap_end_left = 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
@@ -748,7 +624,7 @@ ltk_gap_buffer_resize_gap(struct ltk_gap_buffer *gb, int len) {
 	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->gap_end_left) {
+	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);
@@ -775,7 +651,7 @@ 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->gap_end_left - gb->gap_size) {
+	if (pos < 0 || pos > gb->buf_size - gb->gap_size) {
 		(void)fprintf(stderr, "Index out of range while moving gap buffer gap\n");
 		return;
 	}
@@ -796,3 +672,149 @@ 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 */
+}
+
+void
+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
@@ -48,18 +48,41 @@ struct ltk_gap_buffer {
 	size_t buf_size;
 	size_t gap_left;
 	size_t gap_size;
-	size_t gap_end_left;
+};
+
+/* 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_text_run *next;
+	LtkFont *font;
+	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_script_t script;
+}
+
+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_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 */
+	unsigned int height; /* height of the line (including wrapping) */
+	FribidiCharType dir; /* overall paragraph direction */
 };
 
 struct ltk_text_buffer {
-	uint32_t *buffer;
-	struct ltk_glyph *glyphs;
-	size_t buf_size;
-	size_t buf_left;
-	size_t buf_gap_size;
-	size_t glyphs_size;
-	size_t glyphs_left;
-	size_t glyphs_gap_size;
+	struct ltk_text_line *head;
+	struct ltk_text_line *cur_line;
+	unsigned int line_gap;
 };
 
 typedef struct {