URI: 
       tImprove bidi text rendering - ltkx - GUI toolkit for X11 (WIP)
  HTML git clone git://lumidify.org/ltkx.git
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
   DIR commit 528b74094b0aa1d585efe5ad28903758d467e8a6
   DIR parent 0e40d68a8b98ca331bfad96d3293d9c01c5875ca
  HTML Author: lumidify <nobody@lumidify.org>
       Date:   Sun, 17 May 2020 14:56:19 +0200
       
       Improve bidi text rendering
       
       Diffstat:
         M NOTES                               |       6 ++++++
         M array.h                             |      13 ++++++++++++-
         M test1.c                             |       4 ++--
         M text_buffer.c                       |     327 +++++++++++++++++++++----------
         M text_buffer.h                       |      14 ++++++++++----
         M text_edit.c                         |      24 +++++++++++++++++-------
         M text_edit.h                         |       2 +-
       
       7 files changed, 275 insertions(+), 115 deletions(-)
       ---
   DIR diff --git a/NOTES b/NOTES
       t@@ -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.
   DIR diff --git a/array.h b/array.h
       t@@ -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 *                                                                \
       t@@ -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_ */
   DIR diff --git a/test1.c b/test1.c
       t@@ -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();
        }
   DIR diff --git a/text_buffer.c b/text_buffer.c
       t@@ -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;
       t@@ -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 */
       t@@ -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;
       t@@ -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 */
   DIR diff --git a/text_buffer.h b/text_buffer.h
       t@@ -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;
       t@@ -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;
       t@@ -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);
   DIR diff --git a/text_edit.c b/text_edit.c
       t@@ -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 *
       t@@ -57,21 +65,23 @@ ltk_create_text_edit(LtkWindow *window, const char *text) {
                ltk_fill_widget_defaults(&te->widget, window, &ltk_draw_text_edit, &ltk_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, &ltk_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, &ltk_soft_line_destroy);
                free(te);
        }
   DIR diff --git a/text_edit.h b/text_edit.h
       t@@ -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> */