commit 528b74094b0aa1d585efe5ad28903758d467e8a6
parent 0e40d68a8b98ca331bfad96d3293d9c01c5875ca
Author: lumidify <nobody@lumidify.org>
Date:   Sun, 17 May 2020 14:56:19 +0200
Improve bidi text rendering
Diffstat:
7 files changed, 275 insertions(+), 115 deletions(-)
diff --git a/NOTES b/NOTES
@@ -30,3 +30,9 @@ linked list or something similar in sorted order;
 when looping over characters to draw them, keep a
 pointer to the current tag, then change it to the
 next one if the end of its bound comes
+
+Create a version of ltk_fatal that exits the actual program
+and creates a "failsafe" error window (just using basic
+XDrawString, etc.) to show errors (like index out of bounds,
+etc.). Still print the error, of course, in case creating
+a window doesn't work.
diff --git a/array.h b/array.h
@@ -43,7 +43,9 @@ void ltk_array_insert_##name(struct ltk_array_##name *ar, size_t index,			\
 void ltk_array_resize_##name(struct ltk_array_##name *ar, size_t size);			\
 void ltk_array_destroy_##name(struct ltk_array_##name *ar);				\
 void ltk_array_clear_##name(struct ltk_array_##name *ar);				\
-void ltk_array_append_##name(struct ltk_array_##name *ar, type elem);
+void ltk_array_append_##name(struct ltk_array_##name *ar, type elem);			\
+void ltk_array_destroy_deep_##name(struct ltk_array_##name *ar,				\
+    void (*destroy_func)(type));
 
 #define LTK_ARRAY_INIT_IMPL(name, type)							\
 struct ltk_array_##name *								\
@@ -137,6 +139,15 @@ ltk_array_destroy_##name(struct ltk_array_##name *ar) {					\
 	free(ar->buf);									\
 	ar->buf = NULL;									\
 	free(ar);									\
+}											\
+											\
+void											\
+ltk_array_destroy_deep_##name(struct ltk_array_##name *ar,				\
+    void (*destroy_func)(type)) {							\
+	for (int i = 0; i < ar->len; i++) {						\
+		destroy_func(ar->buf[i]);						\
+	}										\
+	ltk_array_destroy_##name(ar);							\
 }
 
 #endif /* _LTK_ARRAY_H_ */
diff --git a/test1.c b/test1.c
@@ -39,9 +39,9 @@ int main(int argc, char *argv[])
 	//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, "پَیدایش", NULL);
-	LtkTextEdit *edit = ltk_create_text_edit(window1, "ہمارے بارے میں blablabla");
+	LtkTextEdit *edit = ltk_create_text_edit(window1, "ہمارے بارے میں blabla bla");
 	LtkButton *button4 = ltk_create_button(window1, "ہمارے بارے میں blablabla", &bob3, edit);
 	ltk_grid_widget(button4, grid1, 1, 0, 1, 1, LTK_STICKY_TOP | LTK_STICKY_BOTTOM | LTK_STICKY_RIGHT);
-	ltk_grid_widget(edit, grid1, 1, 1, 1, 1, LTK_STICKY_LEFT | LTK_STICKY_BOTTOM | LTK_STICKY_TOP);
+	ltk_grid_widget(edit, grid1, 1, 1, 1, 1, LTK_STICKY_LEFT | LTK_STICKY_BOTTOM | LTK_STICKY_TOP | LTK_STICKY_RIGHT);
 	ltk_mainloop();
 }
diff --git a/text_buffer.c b/text_buffer.c
@@ -44,85 +44,151 @@ LTK_ARRAY_INIT_IMPL(uint32, uint32_t)
 LTK_ARRAY_INIT_IMPL(script, hb_script_t)
 LTK_ARRAY_INIT_IMPL(level, FriBidiLevel)
 LTK_ARRAY_INIT_IMPL(int, int)
+LTK_ARRAY_INIT_IMPL(line, struct ltk_soft_line *)
 
-/* based on http://codemadness.org/git/dwm-font/file/drw.c.html#l315 */
-/* FIXME: rename this once everything is cleaned up (currently conflicts with the
-   old render function */
-XImage *
-ltk_render_text_line_new(
-	struct ltk_text_line *tl,
-	int max_width,
-	Display *dpy,
-	Window window,
-	GC gc,
-	Colormap colormap,
-	XColor fg,
-	XColor bg)
-{
-	XImage *img;
-	int par_is_rtl = FRIBIDI_IS_RTL(tl->dir);
-	int cur_y = 0;
-	int cur_x = par_is_rtl ? max_width : 0;
-	ltk_array_clear_int(tl->wrap_indeces);
-	ltk_array_append_uint32(tl->wrap_indeces, 0);
+void
+ltk_soft_line_destroy(struct ltk_soft_line *sl) {
+	if (sl->img) XDestroyImage(sl->img);
+	free(sl);
+}
+
+struct ltk_array_line *
+ltk_text_line_wrap(struct ltk_text_line *tl, int max_width) {
+	/* FIXME: if max_width == -1, don't wrap */
+	struct ltk_array_line *soft_lines = ltk_array_create_line(1);
+	int par_is_rtl = tl->dir == HB_DIRECTION_RTL;
 
-	/* FIXME: wrap bidi text properly */
-	/* FIXME: THIS IS UGLY */
 	struct ltk_text_run *cur = par_is_rtl ? tl->last_run : tl->first_run;
+	LtkGlyph *glyph;
+	struct ltk_soft_line *sl = malloc(sizeof(struct ltk_soft_line));
+	if (!sl) goto error;
+	sl->glyph_index = cur->dir == HB_DIRECTION_RTL ? cur->len - 1 : 0;
+	sl->run = cur;
+	sl->len = 0;
+	sl->w = 0;
+	ltk_array_append_line(soft_lines, sl);
+	int last_linebreak = par_is_rtl ? tl->w : 0;
+	int cur_start = 0;
+	/* FIXME: also calculate max height of each line */
 	while (cur) {
-		if (par_is_rtl) {
-			for (int i = cur->num_glyphs - 1; i >= 0; i--) {
-				cur_x -= cur->glyphs[i].x_advance;
-				if (cur_x < 0) {
-					int j = 0;
-					for (j = i; j < cur->num_glyphs; j++) {
-						if (cur->glyphs[j].cluster != cur->glyphs[i].cluster) {
-							/* must decrease one again so the actual
-							   last character is used */
-							j--;
+		printf("%d  %d\n", cur->len, cur->num_glyphs);
+		if (cur->len == 1) {
+			printf("%d\n", tl->log_buf->buf[cur->glyphs[0].cluster]);
+		}
+		if (sl->w + cur->w <= max_width) {
+			sl->w += cur->w;
+			sl->len += cur->len;
+			cur = par_is_rtl ? cur->last : cur->next;
+			continue;
+		}
+		if (cur->dir == HB_DIRECTION_RTL) {
+			cur_start = cur->glyphs[cur->len - 1].x_abs + cur->glyphs[cur->len - 1].info->w;
+			int i = cur->len - 1;
+			while (i >= 0) {
+				glyph = &cur->glyphs[i];
+				int cur_w = sl->w + cur_start - glyph->x_abs;
+				if (cur_w > max_width) {
+					/* FIXME: fix behavior when line isn't wide enough for single char */
+					for (int j = i; j < cur->len; j++) {
+						if (cur->glyphs[j].cluster != glyph->cluster || j == cur->len - 1) {
+							int new_index;
+							if (j == cur->len - 1 &&
+							    cur->glyphs[j].cluster == glyph->cluster) {
+								new_index = j;
+								i = j;
+								last_linebreak = cur_start;
+							} else {
+								new_index = j - 1;
+								i = j - 1;
+								last_linebreak = cur->glyphs[j].x_abs;
+								sl->len++;
+							}
+							sl->w += cur_start - last_linebreak;
+							cur_start = last_linebreak;
+							sl = malloc(sizeof(struct ltk_soft_line));
+							if (!sl) goto error;
+							sl->glyph_index = new_index;
+							sl->run = cur;
+							sl->len = 0;
+							sl->w = 0;
+							ltk_array_append_line(soft_lines, sl);
 							break;
+						} else {
+							sl->len--;
 						}
 					}
-					i = j;
-					/* FIXME: handle case that this is the same as the last index */
-					ltk_array_append_uint32(tl->wrap_indeces, cur->glyphs[i].cluster);
-					cur_x = max_width;
+				} else {
+					sl->len++;
+					i--;
 				}
 			}
+			if (sl->run == cur)
+				sl->w = last_linebreak - cur->glyphs[0].x_abs;
+			else
+				sl->w += cur->w;
 		} else {
-			for (int i = 0; i < cur->num_glyphs; i++) {
-				cur_x += cur->glyphs[i].x_advance;
-				if (cur_x > max_width) {
-					int j = 0;
-					for (j = i; j >= 0; j--) {
-						if (cur->glyphs[j].cluster != cur->glyphs[i].cluster) {
-							/* must increase one again so the actual
-							   next character is used */
-							j++;
+			cur_start = cur->glyphs[0].x_abs;
+			int i = 0;
+			while (i < cur->len) {
+				glyph = &cur->glyphs[i];
+				int cur_w = sl->w + glyph->x_abs + glyph->info->w - cur_start;
+				if (cur_w > max_width) {
+					for (int j = i; j >= 0; j--) {
+						if (cur->glyphs[j].cluster != glyph->cluster || j == 0) {
+							int new_index;
+							if (j == 0 && cur->glyphs[j].cluster == glyph->cluster) {
+								new_index = j;
+								i = j;
+								last_linebreak = cur_start;
+							} else {
+								new_index = j + 1;
+								i = j + 1;
+								last_linebreak = cur->glyphs[j + 1].x_abs;
+								sl->len++;
+							}
+							sl->w += last_linebreak - cur_start;
+							cur_start = last_linebreak;
+							sl = malloc(sizeof(struct ltk_soft_line));
+							if (!sl) goto error;
+							sl->glyph_index = new_index;
+							sl->run = cur;
+							sl->len = 0;
+							sl->w = 0;
+							ltk_array_append_line(soft_lines, sl);
 							break;
+						} else {
+							sl->len--;
 						}
 					}
-					i = j;
-					/* FIXME: handle case that this is the same as the last index */
-					ltk_array_append_uint32(tl->wrap_indeces, cur->glyphs[i].cluster);
-					cur_x = 0;
+				} else {
+					sl->len++;
+					i++;
 				}
+
 			}
+			if (sl->run == cur)
+				sl->w = cur->glyphs[cur->len - 1].x_abs + cur->glyphs[cur->len - 1].info->w - last_linebreak;
+			else
+				sl->w += cur->w;
 		}
 		cur = par_is_rtl ? cur->last : cur->next;
 	}
+	return soft_lines;
+error:
+	(void)fprintf(stderr, "Error allocating memory while wrapping text line.\n");
+	exit(1);
+}
 
-	XWindowAttributes attrs;
-	XGetWindowAttributes(dpy, window, &attrs);
-	int depth = attrs.depth;
-	img = XCreateImage(dpy, CopyFromParent, depth, ZPixmap, 0, NULL, max_width, (tl->h + tl->line_gap) * tl->wrap_indeces->len, 32, 0);
+XImage *
+ltk_create_ximage(Display *dpy, int w, int h, int depth, XColor bg) {
+	XImage *img = XCreateImage(dpy, CopyFromParent, depth, ZPixmap, 0, NULL, w, h, 32, 0);
 	img->data = calloc(img->bytes_per_line, img->height);
 	XInitImage(img);
 
 	int b;
-	for (int i = 0; i < tl->h * tl->wrap_indeces->len; i++) {
+	for (int i = 0; i < h; i++) {
 		b = img->bytes_per_line * i;
-		for (int j = 0; j < max_width; j++) {
+		for (int j = 0; j < w; j++) {
 			img->data[b++] = bg.blue / 257;
 			img->data[b++] = bg.green / 257;
 			img->data[b++] = bg.red / 257;
@@ -130,55 +196,112 @@ ltk_render_text_line_new(
 		}
 	}
 
-	cur = par_is_rtl ? tl->last_run : tl->first_run;
-	int x, y;
+	return img;
+}
+
+/* based on http://codemadness.org/git/dwm-font/file/drw.c.html#l315 */
+void
+ltk_draw_glyph(LtkGlyph *glyph, XImage *img, int x, int y, XColor fg) {
 	double a;
-	int cur_line_x = 0;
-	int cur_line = 0;
+	int b;
+	for (int i = 0; i < glyph->info->h; i++) {
+		for (int j = 0; j < glyph->info->w; j++) {
+			if (y + i >= img->height || x + j >= img->width)
+				continue;
+			b = (y + i) * img->bytes_per_line + (x + j) * 4;
+			a = glyph->info->alphamap[i * glyph->info->w + j] / 255.0;
+			img->data[b] = (fg.blue * a + (1 - a) * (uint16_t)img->data[b] * 257) / 257;
+			img->data[b + 1] = (fg.green * a + (1 - a) * (uint16_t)img->data[b + 1] * 257) / 257;
+			img->data[b + 2] = (fg.red * a + (1 - a) * (uint16_t)img->data[b + 2] * 257) / 257;
+		}
+	}
+}
+
+/* FIXME: rename this once everything is cleaned up (currently conflicts with the
+   old render function */
+struct ltk_array_line *
+ltk_render_text_line_new(
+	struct ltk_text_line *tl,
+	int max_width,
+	Display *dpy,
+	Window window,
+	GC gc,
+	Colormap colormap,
+	XColor fg,
+	XColor bg)
+{
 	LtkGlyph *glyph;
-	/* FIXME: ints are compared with size_t's in various places. Maybe I should fix that. */
-	/* FIXME: how should an empty line be handled? This doesn't use a do-for
-	   loop in case tl->first_run is NULL, but I should probably decide what
-	   to do in that case */
-	int index;
-	while (cur) {
-		for (int k = 0; k < cur->num_glyphs; k++) {
-			index = par_is_rtl ? cur->num_glyphs - k - 1 : k;
-			glyph = &cur->glyphs[index];
-			x = par_is_rtl ? max_width - ((tl->w - glyph->x_abs) - cur_line_x) : glyph->x_abs - cur_line_x;
-			/* FIXME: use the computed indeces from above */
-			if (par_is_rtl && x < 0) {
-				cur_line++;
-				cur_line_x = (tl->w - glyph->x_abs - glyph->info->w);
-				x = max_width - ((tl->w - glyph->x_abs) - cur_line_x);
-			} else if (!par_is_rtl && x + glyph->info->w > max_width) {
-				cur_line++;
-				cur_line_x = glyph->x_abs;
-				x = glyph->x_abs - cur_line_x;
-			}
-			y = glyph->y_abs + (tl->h + tl->line_gap) * cur_line;
-			/* FIXME: remove this when everything's fixed */
-			if (x < 0)
-				x = 0;
-			if (x > max_width - glyph->info->w)
-				x = max_width - glyph->info->w;
-			if (y < 0)
-				y = 0;
-			if (y > img->height - glyph->info->h)
-				y = img->height - glyph->info->h;
-			for (int i = 0; i < glyph->info->h; i++) {
-				for (int j = 0; j < glyph->info->w; j++) {
-					b = (y + i) * img->bytes_per_line + (x + j) * 4;
-					a = glyph->info->alphamap[i * glyph->info->w + j] / 255.0;
-					img->data[b] = (fg.blue * a + (1 - a) * (uint16_t)img->data[b] * 257) / 257;
-					img->data[b + 1] = (fg.green * a + (1 - a) * (uint16_t)img->data[b + 1] * 257) / 257;
-					img->data[b + 2] = (fg.red * a + (1 - a) * (uint16_t)img->data[b + 2] * 257) / 257;
+	int par_is_rtl = tl->dir == HB_DIRECTION_RTL;
+	/* FIXME: can't soft_lines just be a normal array instead of pointer array? */
+	struct ltk_array_line *soft_lines = ltk_text_line_wrap(tl, max_width);
+
+	XWindowAttributes attrs;
+	XGetWindowAttributes(dpy, window, &attrs);
+	int depth = attrs.depth;
+
+	for (int i = 0; i < soft_lines->len; i++) {
+		struct ltk_soft_line *sl = soft_lines->buf[i];
+		sl->img = ltk_create_ximage(dpy, sl->w, tl->h, depth, bg);
+		struct ltk_text_run *cur = sl->run;
+		size_t cur_len = 0;
+		int cur_border = par_is_rtl ? sl->w : 0;
+		while (cur && cur_len < sl->len) {
+			int start_index;
+			/* FIXME: the borders here aren't correct since they are
+			   x_abs + glyph->info->w, which may not be the same thing
+			   as the actual x_abs of the bordering glyph */
+			if (cur->dir == HB_DIRECTION_RTL) {
+				start_index = cur == sl->run ? sl->glyph_index : cur->len - 1;
+				int local_border = cur->glyphs[start_index].x_abs + cur->glyphs[start_index].info->w;
+				int end_index;
+				if (start_index + 1 < sl->len - cur_len) {
+					end_index = 0;
+				} else {
+					end_index = start_index - (sl->len - cur_len) + 1;
+				}
+				for (int i = start_index; i >= 0 && cur_len < sl->len; i--) {
+					cur_len++;
+					int x;
+					if (par_is_rtl) {
+						x = cur_border - (local_border - cur->glyphs[i].x_abs);
+					} else {
+						x = cur_border + (cur->glyphs[i].x_abs - cur->glyphs[end_index].x_abs);
+					}
+					ltk_draw_glyph(&cur->glyphs[i], sl->img, x, cur->glyphs[i].y_abs, fg);
+				}
+				if (par_is_rtl)
+					cur_border -= local_border - cur->glyphs[0].x_abs;
+				else
+					cur_border += local_border - cur->glyphs[0].x_abs;
+			} else {
+				start_index = cur == sl->run ? sl->glyph_index : 0;
+				int local_border = cur->glyphs[start_index].x_abs;
+				int end_index;
+				if (cur->len - start_index < sl->len - cur_len) {
+					end_index = cur->len - 1;
+				} else {
+					end_index = start_index + sl->len - cur_len - 1;
+				}
+				for (int i = start_index; i < cur->len && cur_len < sl->len; i++) {
+					cur_len++;
+					int x;
+					if (par_is_rtl) {
+						x = cur_border - (cur->glyphs[end_index].x_abs + cur->glyphs[end_index].info->w - cur->glyphs[i].x_abs);
+					} else {
+						x = cur_border + (cur->glyphs[i].x_abs - local_border);
+					}
+					ltk_draw_glyph(&cur->glyphs[i], sl->img, x, cur->glyphs[i].y_abs, fg);
 				}
+				if (par_is_rtl)
+					cur_border -= cur->glyphs[cur->len - 1].x_abs + cur->glyphs[cur->len - 1].info->w - local_border;
+				else
+					cur_border += cur->glyphs[cur->len - 1].x_abs + cur->glyphs[cur->len - 1].info->w - local_border;
 			}
+			cur = par_is_rtl ? cur->last : cur->next;
 		}
-		cur = par_is_rtl ? cur->last : cur->next;
 	}
-	return img;
+
+	return soft_lines;
 }
 
 /* Begin stuff stolen from raqm */
@@ -566,10 +689,15 @@ ltk_text_line_destroy_runs(struct ltk_text_run *runs) {
 
 static void
 ltk_text_line_recalculate(LtkTextManager *tm, struct ltk_text_line *tl) {
+	FriBidiCharType par_dir = FRIBIDI_TYPE_ON;
 	fribidi_log2vis(
 	    tl->log_buf->buf, tl->log_buf->len,
-	    &tl->dir, tl->vis_buf->buf, tl->log2vis->buf, tl->vis2log->buf, tl->bidi_levels->buf
+	    &par_dir, tl->vis_buf->buf, tl->log2vis->buf, tl->vis2log->buf, tl->bidi_levels->buf
 	);
+	if (FRIBIDI_IS_RTL(par_dir))
+		tl->dir = HB_DIRECTION_RTL;
+	else
+		tl->dir = HB_DIRECTION_LTR;
 	struct ltk_text_run *old_runs = tl->first_run;
 	ltk_text_line_itemize(tl);
 	struct ltk_text_run *cur = tl->first_run;
@@ -630,7 +758,6 @@ ltk_text_line_create(void) {
 	line->cur_run = NULL;
 	line->next = NULL;
 	line->height = 0;
-	line->dir = FRIBIDI_TYPE_ON;
 	line->len = 0;
 	line->cursor_pos = 0;
 	/* FIXME */
diff --git a/text_buffer.h b/text_buffer.h
@@ -55,11 +55,16 @@ struct ltk_text_run {
 	hb_direction_t dir;
 };
 
-struct ltk_line_break {
-	size_t index;
+struct ltk_soft_line {
+	size_t glyph_index;
+	size_t len;
+	int w;
 	struct ltk_text_run *run;
+	XImage *img;
 };
 
+LTK_ARRAY_INIT_DECL(line, struct ltk_soft_line *)
+
 struct ltk_text_line {
 	struct ltk_array_uint32 *log_buf; /* buffer of the logical text */
 	struct ltk_array_script *scripts;
@@ -73,7 +78,7 @@ struct ltk_text_line {
 	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 */
+	hb_direction_t dir; /* overall paragraph direction */
 	size_t len;
 	uint16_t font_size;
 	int y_max;
@@ -90,7 +95,8 @@ struct ltk_text_buffer {
 	unsigned int line_gap;
 };
 
-XImage *ltk_render_text_line_new(struct ltk_text_line *tl, int max_width,
+void ltk_soft_line_destroy(struct ltk_soft_line *sl);
+struct ltk_array_line *ltk_render_text_line_new(struct ltk_text_line *tl, int max_width,
     Display *dpy, Window window, GC gc, Colormap colormap, XColor fg, XColor bg);
 void ltk_text_line_insert_text(struct ltk_text_line *tl, uint32_t *text, size_t len);
 struct ltk_text_line *ltk_text_line_create(void);
diff --git a/text_edit.c b/text_edit.c
@@ -44,9 +44,17 @@ ltk_draw_text_edit(LtkTextEdit *te) {
 	XColor bg = ltk_global->theme->window->bg;
 	LtkRect rect = te->widget.rect;
 	LtkWindow *window = te->widget.window;
-	if (!te->img)
-		te->img = ltk_render_text_line_new(te->tl, rect.w, ltk_global->display, window->xwindow, window->gc, ltk_global->colormap, fg, bg);
-	XPutImage(ltk_global->display, window->xwindow, window->gc, te->img, 0, 0, rect.x, rect.y, te->img->width, te->img->height);
+	if (!te->soft_lines)
+		te->soft_lines = ltk_render_text_line_new(te->tl, rect.w, ltk_global->display, window->xwindow, window->gc, ltk_global->colormap, fg, bg);
+	XSetForeground(ltk_global->display, window->gc, fg.pixel);
+	XFillRectangle(ltk_global->display, window->xwindow, window->gc, rect.x, rect.y, rect.w, rect.h);
+	for (int i = 0; i < te->soft_lines->len; i++) {
+		XImage *img = te->soft_lines->buf[i]->img;
+		int x = rect.x;
+		if (te->tl->dir == HB_DIRECTION_RTL)
+			x += rect.w - img->width;
+		XPutImage(ltk_global->display, window->xwindow, window->gc, img, 0, 0, x, rect.y + te->tl->h * i, img->width, img->height);
+	}
 }
 
 LtkTextEdit *
@@ -57,21 +65,23 @@ ltk_create_text_edit(LtkWindow *window, const char *text) {
 	ltk_fill_widget_defaults(&te->widget, window, <k_draw_text_edit, <k_destroy_text_edit, 1);
 	te->tl = ltk_text_line_create();
 	ltk_text_line_insert_utf8(te->tl, text);
-	te->img = NULL;
+	te->soft_lines = NULL;
 	return te;
 }
 
 void
 ltk_text_edit_insert_text(LtkTextEdit *te, const char *text) {
 	ltk_text_line_insert_utf8(te->tl, text);
-	if (te->img) XDestroyImage(te->img);
-	te->img = NULL;
+	if (te->soft_lines)
+		ltk_array_destroy_deep_line(te->soft_lines, <k_soft_line_destroy);
+	te->soft_lines = NULL;
 	/* FIXME: Need to "queue redraw" for whole window */
 	ltk_draw_text_edit(te);
 }
 
 void ltk_destroy_text_edit(LtkTextEdit *te) {
 	ltk_text_line_destroy(te->tl);
-	if (te->img) XDestroyImage(te->img);
+	if (te->soft_lines)
+		ltk_array_destroy_deep_line(te->soft_lines, <k_soft_line_destroy);
 	free(te);
 }
diff --git a/text_edit.h b/text_edit.h
@@ -26,8 +26,8 @@
 
 typedef struct {
 	LtkWidget widget;
-	XImage *img;
 	struct ltk_text_line *tl;
+	struct ltk_array_line *soft_lines;
 } LtkTextEdit;
 
 /* FIXME: standardize ltk_<widget>_destroy, etc. instead of ltk_destroy_<widget> */