URI: 
       tRefactor gap buffer - 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 57d85ccfe4c9ac3de63dab6979e65dbe0696f4b7
   DIR parent 05e4c9d086c3db2ad726f789cee1b9f8d7ea07b9
  HTML Author: lumidify <nobody@lumidify.org>
       Date:   Fri,  8 May 2020 20:36:38 +0200
       
       Refactor gap buffer
       
       Diffstat:
         M .gitignore                          |       2 ++
         A gap_buffer.h                        |     156 +++++++++++++++++++++++++++++++
         M test1.c                             |       4 ++--
         M text-hb.c                           |      15 +++++++++------
         M textedit_wip.c                      |     304 +++++++++++++++++--------------
         M textedit_wip.h                      |      41 ++++++++++++++++++++++++-------
       
       6 files changed, 364 insertions(+), 158 deletions(-)
       ---
   DIR diff --git a/.gitignore b/.gitignore
       t@@ -1 +1,3 @@
        test
       +*.o
       +test1
   DIR diff --git a/gap_buffer.h b/gap_buffer.h
       t@@ -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_ */
   DIR diff --git a/test1.c b/test1.c
       t@@ -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();
        }
   DIR diff --git a/text-hb.c b/text-hb.c
       t@@ -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]);
       t@@ -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);
       t@@ -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));
   DIR diff --git a/textedit_wip.c b/textedit_wip.c
       t@@ -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. */
       t@@ -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
       t@@ -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);
       t@@ -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;
                }
       t@@ -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);
       +        }
       +}
   DIR diff --git a/textedit_wip.h b/textedit_wip.h
       t@@ -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 {