URI: 
       tStart work on textedit - 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 05e4c9d086c3db2ad726f789cee1b9f8d7ea07b9
   DIR parent 93dfd2ced8dc537d2a667857f39b07bcc8307892
  HTML Author: lumidify <nobody@lumidify.org>
       Date:   Wed,  6 May 2020 20:59:25 +0200
       
       Start work on textedit
       
       Diffstat:
         M NOTES                               |       6 ++++++
         M test1.c                             |       3 ++-
         M text-hb.c                           |       8 +++++++-
         A textedit_wip.c                      |     798 ++++++++++++++++++++++++++++++
         A textedit_wip.h                      |     175 +++++++++++++++++++++++++++++++
       
       5 files changed, 988 insertions(+), 2 deletions(-)
       ---
   DIR diff --git a/NOTES b/NOTES
       t@@ -24,3 +24,9 @@ an abstract LtkTextLine that can be rendered without
        caring about such details. This would easily allow
        one file for shaping with harfbuzz, one for just
        using the regular kerning with stb_truetype, etc.
       +
       +Text editor tags (highlight, etc.) - just store in
       +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
   DIR diff --git a/test1.c b/test1.c
       t@@ -34,7 +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, "ہمارے بارے میں 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@@ -500,7 +500,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, 0, len);
       +        hb_buffer_add_codepoints(buf, ts->str, len, 1, len-1);
                /* 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@@ -511,11 +511,16 @@ ltk_create_text_segment(LtkTextManager *tm, uint32_t *text, unsigned int len, ui
                float scale = stbtt_ScaleForMappingEmToPixels(&font->info, size);
                LtkGlyph *last_glyph = NULL;
        
       +        /* FIXME: read https://harfbuzz.github.io/harfbuzz-hb-buffer.html#hb-glyph-position-t-struct */
       +
                int x_min = INT_MAX, x_max = INT_MIN, y_min = INT_MAX, y_max = INT_MIN;
                int x_abs = 0, y_abs = 0, x1_abs, y1_abs, x2_abs, y2_abs;
                /* 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));
       t@@ -572,6 +577,7 @@ ltk_create_text_segment(LtkTextManager *tm, uint32_t *text, unsigned int len, ui
                ts->y_max = y_max;
        
                font->refs++;
       +        /* FIXME: destroy hb_buffer */
        
                return ts;
        }
   DIR diff --git a/textedit_wip.c b/textedit_wip.c
       t@@ -0,0 +1,798 @@
       +/*
       + * This file is part of the Lumidify ToolKit (LTK)
       + * Copyright (c) 2017, 2018, 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.
       + */
       +
       +#include <stdio.h>
       +#include <stdlib.h>
       +#include <stdint.h>
       +#include <limits.h>
       +#include <X11/Xlib.h>
       +#include <X11/Xutil.h>
       +#include "stb_truetype.h" /* http://nothings.org/stb/stb_truetype.h */
       +#include <fontconfig/fontconfig.h>
       +#include "khash.h"
       +#include <fribidi.h>
       +#include <harfbuzz/hb.h>
       +#include <harfbuzz/hb-ot.h>
       +#include "text-hb.h"
       +#include "ltk.h"
       +
       +extern Ltk *ltk_global;
       +
       +/* These unicode routines are taken from
       + * https://github.com/JeffBezanson/cutef8 */
       +
       +/* is c the start of a utf8 sequence? */
       +#define isutf(c) (((c)&0xC0)!=0x80)
       +
       +static const uint32_t offsetsFromUTF8[6] = {
       +    0x00000000UL, 0x00003080UL, 0x000E2080UL,
       +    0x03C82080UL, 0xFA082080UL, 0x82082080UL
       +};
       +
       +/* next character without NUL character terminator */
       +uint32_t u8_nextmemchar(const char *s, size_t *i)
       +{
       +    uint32_t ch = 0;
       +    size_t sz = 0;
       +    do {
       +        ch <<= 6;
       +        ch += (unsigned char)s[(*i)++];
       +        sz++;
       +    } while (!isutf(s[*i]));
       +    ch -= offsetsFromUTF8[sz-1];
       +
       +    return ch;
       +}
       +
       +/* number of characters in NUL-terminated string */
       +size_t u8_strlen(const char *s)
       +{
       +    size_t count = 0;
       +    size_t i = 0, lasti;
       +
       +    while (1) {
       +        lasti = i;
       +        while (s[i] > 0)
       +            i++;
       +        count += (i-lasti);
       +        if (s[i++]==0) break;
       +        (void)(isutf(s[++i]) || isutf(s[++i]) || ++i);
       +        count++;
       +    }
       +    return count;
       +}
       +
       +size_t u8_wc_toutf8(char *dest, uint32_t ch)
       +{
       +    if (ch < 0x80) {
       +        dest[0] = (char)ch;
       +        return 1;
       +    }
       +    if (ch < 0x800) {
       +        dest[0] = (ch>>6) | 0xC0;
       +        dest[1] = (ch & 0x3F) | 0x80;
       +        return 2;
       +    }
       +    if (ch < 0x10000) {
       +        dest[0] = (ch>>12) | 0xE0;
       +        dest[1] = ((ch>>6) & 0x3F) | 0x80;
       +        dest[2] = (ch & 0x3F) | 0x80;
       +        return 3;
       +    }
       +    if (ch < 0x110000) {
       +        dest[0] = (ch>>18) | 0xF0;
       +        dest[1] = ((ch>>12) & 0x3F) | 0x80;
       +        dest[2] = ((ch>>6) & 0x3F) | 0x80;
       +        dest[3] = (ch & 0x3F) | 0x80;
       +        return 4;
       +    }
       +    return 0;
       +}
       +
       +LtkTextManager *
       +ltk_init_text(char *font_name)
       +{
       +        LtkTextManager *tm = malloc(sizeof(LtkTextManager));
       +        if (!tm) {
       +                (void)printf("Memory exhausted when trying to create text manager.");
       +                exit(1);
       +        }
       +        tm->font_paths = kh_init(fontid);
       +        tm->font_cache = kh_init(fontstruct);
       +        tm->glyph_cache = kh_init(glyphcache);
       +        tm->font_id_cur = 0;
       +        ltk_load_default_font(tm, font_name);
       +
       +        return tm;
       +}
       +
       +void
       +ltk_destroy_text_manager(LtkTextManager *tm)
       +{
       +        int k;
       +
       +        kh_destroy(fontid, tm->font_paths);
       +
       +        for (k = kh_begin(tm->font_cache); k != kh_end(tm->font_cache); k++) {
       +                if (kh_exist(tm->font_cache, k)) {
       +                        ltk_destroy_font(kh_value(tm->font_cache, k));
       +                }
       +        }
       +        kh_destroy(fontstruct, tm->font_cache);
       +
       +        for (k = kh_begin(tm->glyph_cache); k != kh_end(tm->glyph_cache); k++) {
       +                if (kh_exist(tm->glyph_cache, k)) {
       +                        ltk_destroy_glyph_cache(kh_value(tm->glyph_cache, k));
       +                }
       +        }
       +        kh_destroy(glyphcache, tm->glyph_cache);
       +
       +        free(tm);
       +}
       +
       +LtkGlyphInfo *
       +ltk_create_glyph_info(LtkFont *font, unsigned int id, float scale)
       +{
       +        LtkGlyphInfo *glyph = malloc(sizeof(LtkGlyphInfo));
       +        if (!glyph) {
       +                (void)printf("Out of memory!\n");
       +                exit(1);
       +        }
       +        
       +        glyph->id = id;
       +        glyph->refs = 0;
       +        glyph->alphamap = stbtt_GetGlyphBitmap(
       +                &font->info, scale, scale, id, &glyph->w,
       +                &glyph->h, &glyph->xoff, &glyph->yoff
       +        );
       +
       +        return glyph;
       +}
       +
       +void
       +ltk_destroy_glyph_info(LtkGlyphInfo *gi)
       +{
       +        free(gi->alphamap);
       +        free(gi);
       +}
       +
       +LtkGlyphInfo *
       +ltk_get_glyph_info(LtkFont *font, unsigned int id, float scale, khash_t(glyphinfo) *cache)
       +{
       +        int ret;
       +        khint_t k;
       +        LtkGlyphInfo *glyph;
       +        k = kh_get(glyphinfo, cache, id);
       +        if (k == kh_end(cache)) {
       +                glyph = ltk_create_glyph_info(font, id, scale);
       +                /* FIXME: error checking with ret */
       +                k = kh_put(glyphinfo, cache, id, &ret);
       +                kh_value(cache, k) = glyph;
       +        } else {
       +                glyph = kh_value(cache, k);
       +        }
       +
       +        return glyph;
       +}
       +
       +khint_t
       +ltk_create_glyph_cache(LtkTextManager *tm, uint16_t font_id, uint16_t font_size)
       +{
       +        khash_t(glyphinfo) *cache = kh_init(glyphinfo);
       +        int ret;
       +        khint_t k;
       +        /* I guess I can just ignore ret for now */
       +        k = kh_put(glyphcache, tm->glyph_cache, font_id << 16 + font_size, &ret);
       +        kh_value(tm->glyph_cache, k) = cache;
       +
       +        return k;
       +}
       +
       +void
       +ltk_destroy_glyph_cache(khash_t(glyphinfo) *cache)
       +{
       +        int k;
       +        for (k = kh_begin(cache); k != kh_end(cache); k++) {
       +                if (kh_exist(cache, k)) {
       +                        ltk_destroy_glyph_info(kh_value(cache, k));
       +                }
       +        }
       +        kh_destroy(glyphinfo, cache);
       +}
       +
       +void
       +ltk_load_default_font(LtkTextManager *tm, char *name)
       +{
       +        FcPattern *match;
       +        FcResult result;
       +        char *file;
       +        int index;
       +        uint16_t font;
       +
       +        tm->fcpattern = FcNameParse(name);
       +        FcPatternAddString(tm->fcpattern, FC_FONTFORMAT, "truetype");
       +        FcConfigSubstitute(NULL, tm->fcpattern, FcMatchPattern);
       +        FcDefaultSubstitute(tm->fcpattern);
       +        match = FcFontMatch(NULL, tm->fcpattern, &result);
       +
       +        FcPatternGetString (match, FC_FILE, 0, (FcChar8 **) &file);
       +        /* FIXME: Why is index never used? This is the index within the font file,
       +           so it might be important, although I'm not sure if stb_truetype even
       +           supports it */
       +        FcPatternGetInteger (match, FC_INDEX, 0, &index);
       +
       +        tm->default_font = ltk_get_font(tm, file);
       +
       +        FcPatternDestroy (match);
       +}
       +
       +LtkFont *
       +ltk_create_font(char *path, uint16_t id)
       +{
       +        long len;
       +        LtkFont *font = malloc(sizeof(LtkFont));
       +        if (!font) {
       +                (void)fprintf(stderr, "Out of memory!\n");
       +                exit(1);
       +        }
       +        char *contents = ltk_read_file(path, &len);
       +        if (!stbtt_InitFont(&font->info, contents, 0))
       +        {
       +                (void)fprintf(stderr, "Failed to load font %s\n", path);
       +                exit(1);
       +        }
       +        /* FIXME: make use of the destroy function (last argument to hb_blob_create - see hb-blob.cc in harfbuzz source) */
       +        hb_blob_t *blob = hb_blob_create(contents, len, HB_MEMORY_MODE_READONLY, NULL, NULL);
       +        hb_face_t *face = hb_face_create(blob, 0);
       +        /* FIXME: need to use destroy function in order for the original file data to be freed? */
       +        hb_blob_destroy(blob);
       +        font->hb = hb_font_create(face);
       +        hb_face_destroy(face);
       +        hb_ot_font_set_funcs(font->hb);
       +        font->id = id;
       +        font->refs = 0;
       +
       +        return font;
       +}
       +
       +void
       +ltk_destroy_font(LtkFont *font)
       +{
       +        free(font->info.data);
       +        hb_font_destroy(font->hb);
       +        free(font);
       +}
       +
       +uint16_t
       +ltk_load_font(LtkTextManager *tm, char *path)
       +{
       +        LtkFont *font = ltk_create_font(path, tm->font_id_cur++);
       +        int ret;
       +        khint_t k;
       +        /* FIXME: does kh_destroy also free these copied strings properly? */
       +        char *key = strdup(path);
       +        k = kh_put(fontid, tm->font_paths, key, &ret);
       +        kh_value(tm->font_paths, k) = font->id;
       +        k = kh_put(fontstruct, tm->font_cache, (khint_t) font->id, &ret);
       +        kh_value(tm->font_cache, k) = font;
       +        k = kh_get(fontid, tm->font_paths, path);
       +
       +        return font->id;
       +}
       +
       +uint16_t
       +ltk_get_font(LtkTextManager *tm, char *path)
       +{
       +        int ret;
       +        khint_t k;
       +        uint16_t id;
       +        k = kh_get(fontid, tm->font_paths, path);
       +        if (k == kh_end(tm->font_paths)) {
       +                id = ltk_load_font(tm, path);
       +        } else {
       +                id = kh_value(tm->font_paths, k);
       +        }
       +
       +        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. */
       +LtkTextSegment *
       +ltk_create_text_segment(LtkTextManager *tm, uint32_t *text, unsigned int len, uint16_t fontid, uint16_t size, hb_script_t script)
       +{
       +        /* (x1*, y1*): top left corner (relative to origin and absolute)
       +           (x2*, y2*): bottom right corner (relative to origin and absolute) */
       +        LtkFont *font;
       +        khash_t(glyphinfo) *glyph_cache;
       +        khint_t k;
       +
       +        k = kh_get(fontstruct, tm->font_cache, fontid);
       +        font = kh_value(tm->font_cache, k);
       +
       +        uint32_t attr = fontid << 16 + size;
       +        /* FIXME: turn this into ltk_get_glyph_cache */
       +        k = kh_get(glyphcache, tm->glyph_cache, attr);
       +        if (k == kh_end(tm->glyph_cache)) {
       +                k = ltk_create_glyph_cache(tm, fontid, size);
       +        }
       +        glyph_cache = kh_value(tm->glyph_cache, k);
       +
       +        LtkTextSegment *ts = malloc(sizeof(LtkTextSegment));
       +        if (!ts) {
       +                (void)fprintf(stderr, "Out of memory!\n");
       +                exit(1);
       +        }
       +        ts->str = malloc(sizeof(uint32_t) * (len + 1));
       +        memcpy(ts->str, text, len * sizeof(uint32_t));
       +        ts->str[len] = '\0';
       +        ts->font_id = fontid;
       +        ts->font_size = size;
       +
       +        hb_buffer_t *buf;
       +        hb_glyph_info_t *ginf, *gi;
       +        hb_glyph_position_t *gpos, *gp;
       +        unsigned int text_len = 0;
       +        if (len < 1) {
       +                (void)printf("WARNING: ltk_render_text_segment: length of text is less than 1.\n");
       +                return NULL;
       +        }
       +
       +        buf = hb_buffer_create();
       +        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, 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);
       +        hb_shape(font->hb, buf, NULL, 0);
       +        ts->dir = hb_buffer_get_direction(buf);
       +        ginf = hb_buffer_get_glyph_infos(buf, &text_len);
       +        gpos = hb_buffer_get_glyph_positions(buf, &text_len);
       +        float scale = stbtt_ScaleForMappingEmToPixels(&font->info, size);
       +        LtkGlyph *last_glyph = NULL;
       +
       +        int x_min = INT_MAX, x_max = INT_MIN, y_min = INT_MAX, y_max = INT_MIN;
       +        int x_abs = 0, y_abs = 0, x1_abs, y1_abs, x2_abs, y2_abs;
       +        /* magic, do not touch */
       +        LtkGlyph *glyph;
       +        for (int i = 0; i < text_len; i++) {
       +                gi = &ginf[i];
       +                gp = &gpos[i];
       +                glyph = malloc(sizeof(LtkGlyph));
       +                glyph->info = ltk_get_glyph_info(font, gi->codepoint, scale, glyph_cache);
       +                glyph->info->refs++;
       +                /* FIXME: round instead of just casting */
       +                glyph->x_offset = (int)(gp->x_offset * scale);
       +                glyph->y_offset = (int)(gp->y_offset * scale);
       +                glyph->x_advance = (int)(gp->x_advance * scale);
       +                glyph->y_advance = (int)(gp->y_advance * scale);
       +                glyph->next = NULL;
       +                if (i == 0) {
       +                        ts->start_glyph = glyph;
       +                } else {
       +                        last_glyph->next = glyph;
       +                }
       +                last_glyph = glyph;
       +
       +                /* Calculate position in order to determine full size of text segment */
       +                x1_abs = x_abs + glyph->info->xoff + glyph->x_offset;
       +                y1_abs = y_abs + glyph->info->yoff - glyph->y_offset;
       +                /* Okay, wait, so should I check if the script is horizontal, and then add
       +                   x_advance instead of glyph->info->w? It seems that the glyph width is
       +                   usually smaller than x_advance, and spaces etc. are completely lost
       +                   because their glyph width is 0. I have to distinguish between horizontal
       +                   and vertical scripts, though because to calculate the maximum y position
       +                   for horizontal scripts, I still need to use the glyph height since
       +                   y_advance doesn't really do much there. I dunno, at least *something*
       +                   works now... */
       +                /* FIXME: THIS PROBABLY DOESN'T REALLY WORK */
       +                if (HB_DIRECTION_IS_HORIZONTAL(dir)) {
       +                        x2_abs = x1_abs + glyph->x_advance;
       +                        y2_abs = y1_abs + glyph->info->h;
       +                } else {
       +                        x2_abs = x1_abs + glyph->info->w;
       +                        y2_abs = y1_abs - glyph->y_advance;
       +                }
       +                glyph->x_abs = x1_abs;
       +                glyph->y_abs = y1_abs;
       +                if (x1_abs < x_min) x_min = x1_abs;
       +                if (y1_abs < y_min) y_min = y1_abs;
       +                if (x2_abs > x_max) x_max = x2_abs;
       +                if (y2_abs > y_max) y_max = y2_abs;
       +                x_abs += glyph->x_advance;
       +                y_abs -= glyph->y_advance;
       +        }
       +        ts->start_x = -x_min;
       +        ts->start_y = -y_min;
       +        ts->w = x_max - x_min;
       +        ts->h = y_max - y_min;
       +        ts->x_min = x_min;
       +        ts->y_min = y_min;
       +        ts->x_max = x_max;
       +        ts->y_max = y_max;
       +
       +        font->refs++;
       +
       +        return ts;
       +}
       +
       +void
       +ltk_destroy_glyph(LtkGlyph *glyph, khash_t(glyphinfo) *cache)
       +{
       +        int k;
       +        if (--glyph->info->refs < 1) {
       +                k = kh_get(glyphinfo, cache, glyph->info->id);
       +                kh_del(glyphinfo, cache, k);
       +                ltk_destroy_glyph_info(glyph->info);
       +        }
       +        free(glyph);
       +}
       +
       +void
       +ltk_destroy_text_segment(LtkTextSegment *ts)
       +{
       +        LtkGlyph *glyph, *next_glyph;
       +        khash_t(glyphinfo) *gcache;
       +        LtkFont *font;
       +        int k;
       +        glyph = ts->start_glyph;
       +        k = kh_get(glyphinfo, ltk_global->tm->glyph_cache, ts->font_id << 16 + ts->font_size);
       +        gcache = kh_value(ltk_global->tm->glyph_cache, k);
       +        do {
       +                next_glyph = glyph->next;
       +                ltk_destroy_glyph(glyph, gcache);
       +        } while (glyph = next_glyph);
       +        k = kh_get(fontstruct, ltk_global->tm->font_cache, ts->font_id);
       +        font = kh_value(ltk_global->tm->font_cache, k);
       +        if (--font->refs < 1) {
       +                kh_del(fontstruct, ltk_global->tm->font_cache, k);
       +                ltk_destroy_font(font);
       +        }
       +        free(ts->str);
       +        free(ts);
       +}
       +
       +/* based on http://codemadness.org/git/dwm-font/file/drw.c.html#l315 */
       +XImage *
       +ltk_render_text_line(
       +        LtkTextLine *tl,
       +        Display *dpy,
       +        Window window,
       +        GC gc,
       +        Colormap colormap,
       +        XColor fg,
       +        XColor bg)
       +{
       +        XWindowAttributes attrs;
       +        XGetWindowAttributes(dpy, window, &attrs);
       +        int depth = attrs.depth;
       +        XImage *img = XCreateImage(dpy, CopyFromParent, depth, ZPixmap, 0, NULL, tl->w, tl->h, 32, 0);
       +        img->data = calloc(img->bytes_per_line, img->height);
       +        XInitImage(img);
       +        int b;
       +        for (int i = 0; i < tl->h; i++) {
       +                b = img->bytes_per_line * i;
       +                for (int j = 0; j < tl->w; j++) {
       +                        img->data[b++] = bg.blue / 257;
       +                        img->data[b++] = bg.green / 257;
       +                        img->data[b++] = bg.red / 257;
       +                        b++;
       +                }
       +        }
       +
       +        LtkTextSegment *ts = tl->start_segment;
       +        int x = 0;
       +        int y = 0;
       +        int is_hor = HB_DIRECTION_IS_HORIZONTAL(ts->dir);
       +        do {
       +                if (is_hor) {
       +                        y = tl->h - tl->y_max;
       +                        ltk_render_text_segment(ts, x + ts->start_x, y, img, fg);
       +                        x += ts->w;
       +                } else {
       +                        x = tl->w - tl->x_max;
       +                        ltk_render_text_segment(ts, x, y + ts->start_y, img, fg);
       +                        y += ts->h;
       +                }
       +        } while (ts = ts->next);
       +
       +        return img;
       +}
       +
       +void
       +ltk_render_text_segment(
       +        LtkTextSegment *ts,
       +        unsigned int start_x,
       +        unsigned int start_y,
       +        XImage *img,
       +        XColor fg)
       +{
       +        LtkGlyph *glyph = ts->start_glyph;
       +        int x_cur = start_x;
       +        int y_cur = start_y;
       +        int x, y;
       +        double a;
       +        int b;
       +        do {
       +                x = x_cur + glyph->info->xoff + glyph->x_offset;
       +                y = y_cur + glyph->info->yoff - glyph->y_offset;
       +                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;
       +                        }
       +                }
       +                x_cur += glyph->x_advance;
       +                y_cur -= glyph->y_advance;
       +        } while (glyph = glyph->next);
       +}
       +
       +/*
       +Notes: When inserting a character, check what the direction of the surrounding
       +script is - if it is the same (or a weak direction), then just insert the char
       +without re-doing the bidi algorithm. Only redo the whole line/paragraph is the
       +dir of the inserted character is different. Assume the text is LTR until a strong
       +character is inserted, i.e. the line should keep a flag if it already has a strong
       +dir set.
       +When reordering, the cursor needs to be kept in the right place:
       +a) The logical to visual mapping from FriBidi needs to be used to find the position
       +of the cursor in the text that gets passed to Harfbuzz.
       +b) The cluster values from Harfbuzz need to be used to figure out where the cursor
       +is on the screen.
       +Harfbuzz also should be given some context - maybe even completely reshape the
       +text until the last and next space? That would only be a problem with weird very
       +long words (mainly nonsense). At least maybe take up to five or so chars on each
       +side and pass even more with the optional context-passing feature for hb_shape.
       +Question: Does both the logical and visual text need to be stored? The logical text
       +is technically the master copy, but the visual text is what gets passed to harfbuzz.
       +-> Actually, since the visual text has to be split into script runs anyways, maybe
       +just append text to that (and also add it to the master buffer simultaneously). As
       +soon as a different script is inserted, everything has to be redone anyways. Also,
       +when reshaping with context, only the text in the current run has to be passed at all.
       +*/
       +
       +struct ltk_gap_buffer *
       +ltk_gap_buffer_create(void) {
       +        struct ltk_gap_buffer *gb = malloc(sizeof(struct ltk_gap_buffer));
       +        if (!gb)
       +                goto error;
       +        gb->buf = malloc(4 * 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);
       +}
       +
       +void
       +ltk_gap_buffer_resize_gap(struct ltk_gap_buffer *gb, int len) {
       +        /* FIXME: Should this use realloc? It's usually more efficient, but
       +           in this case, I would still need to copy the part after the gap
       +           manually, so it could potentially be copied twice, which really
       +           wouldn't be good. Maybe use realloc if only a small part is after
       +           the gap and just regular malloc otherwise? */
       +        int new_size = gb->buf_size - gb->gap-size + len;
       +        struct ltk_gap_buffer *new = malloc(new_size * sizeof(uint32_t));
       +        if (!new) {
       +                (void)fprintf(stderr, "Out of memory while trying to resize gap buffer\n");
       +                exit(1);
       +        }
       +        for (int i = 0; i < gb->gap_left; i++) {
       +                new[i] = gb->buf[i];
       +        }
       +        for (int i = gb->gap_left + gb->gap_size; i < gb->gap_end_left) {
       +                new[i - gb->gap_size + len] = gb->buf[i];
       +        }
       +        free(gb->buf);
       +        gb->buf = new;
       +}
       +
       +void
       +ltk_gap_buffer_insert(struct ltk_gap_buffer *gb, uint32_t *new, size_t start, size_t len) {
       +        if (gb->gap_size < len)
       +                ltk_gap_buffer_resize_gap(gb, len + 8);
       +        for (int i = 0; i < len; i++) {
       +                gb->buf[gb->gap_left + i] = new[start + i];
       +        }
       +        gb->gap_left = gb->gap_left + len;
       +        gb->gap_size -= len;
       +}
       +
       +void
       +ltk_gap_buffer_insert_single(struct ltk_gap_buffer *gb, uint32_t new) {
       +        ltk_gap_buffer_insert(gb, &new, 0, 1);
       +}
       +
       +void
       +ltk_gap_buffer_move_gap(struct ltk_gap_buffer *gb, size_t pos) {
       +        if (pos == gb->gap_left)
       +                return;
       +        if (pos < 0 || pos >= gb->gap_end_left - gb->gap_size) {
       +                (void)fprintf(stderr, "Index out of range while moving gap buffer gap\n");
       +                return;
       +        }
       +        if (pos >= gb->gap_left) {
       +                for (int i = gb->gap_left; i < pos) {
       +                        gb->buf[i] = gb->buf[i + gb->gap_size];
       +                }
       +        } else {
       +                for (int i = gb->gap_left - 1; i >= pos; i--) {
       +                        gb->buf[i + gb->gap_size] = gb->buf[i];
       +                }
       +        }
       +        gb->gap_left = pos;
       +}
       +
       +void
       +ltk_gap_buffer_destroy(struct ltk_gap_buffer *gb) {
       +        free(gb->buf);
       +        free(gb);
       +}
   DIR diff --git a/textedit_wip.h b/textedit_wip.h
       t@@ -0,0 +1,175 @@
       +/*
       + * This file is part of the Lumidify ToolKit (LTK)
       + * Copyright (c) 2017, 2018, 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 _TEXT_HB_H_
       +#define _TEXT_HB_H_
       +
       +/*
       +Requires the following includes:
       +<X11/Xlib.h>, <X11/Xutil.h>, "stb_truetype.h",
       +"khash.h", <harfbuzz/hb.h>, <fribidi.h>,
       +<fontconfig/fontconfig.h>
       +*/
       +
       +/* Contains glyph info specific to one run of text */
       +typedef struct ltk_glyph {
       +        LtkGlyphInfo *info;
       +        int x_offset; /* additional x offset given by harfbuzz */
       +        int y_offset; /* additional y offset given by harfbuzz */
       +        int x_advance;
       +        int y_advance;
       +        int x_abs;
       +        int y_abs;
       +        uint32_t cluster; /* index of char in original text - from harfbuzz */
       +};
       +
       +struct ltk_gap_buffer {
       +        uint32_t *buf;
       +        size_t buf_size;
       +        size_t gap_left;
       +        size_t gap_size;
       +        size_t gap_end_left;
       +};
       +
       +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;
       +};
       +
       +typedef struct {
       +        stbtt_fontinfo info;
       +        hb_font_t *hb;
       +        uint16_t id;
       +        unsigned int refs;
       +} LtkFont;
       +
       +/* Contains general info on glyphs that doesn't change regardless of the context */
       +typedef struct _LtkGlyphInfo {
       +        unsigned int id;
       +        unsigned char *alphamap;
       +        unsigned int w;
       +        unsigned int h;
       +        unsigned int xoff; /* x offset from origin to top left corner of glyph */
       +        unsigned int yoff; /* y offset from origin to top left corner of glyph */
       +        unsigned int refs;
       +        /* FIXME: does refs need to be long? It could cause problems if a
       +        program tries to cache/"keep alive" a lot of pages of text. */
       +} LtkGlyphInfo;
       +
       +/* Single segment of text with same font */
       +typedef struct LtkTextSegment {
       +        uint16_t font_id;
       +        uint16_t font_size;
       +        unsigned int w;
       +        unsigned int h;
       +        int start_x;
       +        int start_y;
       +        int x_min;
       +        int y_min;
       +        int x_max;
       +        int y_max;
       +        hb_direction_t dir;
       +        uint32_t *str;
       +        LtkGlyph *start_glyph;
       +        struct LtkTextSegment *next;
       +} LtkTextSegment;
       +
       +/* Single line of text */
       +typedef struct {
       +        unsigned int w;
       +        unsigned int h;
       +        int x_max;
       +        int x_min;
       +        int y_max;
       +        int y_min;
       +        FriBidiParType dir;
       +        LtkTextSegment *start_segment;
       +} LtkTextLine;
       +
       +/* Hash definitions */
       +/* glyph id -> glyph info struct */
       +KHASH_MAP_INIT_INT(glyphinfo, LtkGlyphInfo*)
       +/* font path, size -> glyph cache hash */
       +KHASH_MAP_INIT_INT(glyphcache, khash_t(glyphinfo)*)
       +/* font path -> font id */
       +KHASH_MAP_INIT_STR(fontid, uint16_t)
       +/* font id -> font struct */
       +KHASH_MAP_INIT_INT(fontstruct, LtkFont*)
       +
       +typedef struct LtkTextManager {
       +        khash_t(fontid) *font_paths;
       +        khash_t(fontstruct) *font_cache;
       +        khash_t(glyphcache) *glyph_cache;
       +        FcPattern *fcpattern;
       +        uint16_t default_font;
       +        uint16_t font_id_cur;
       +} LtkTextManager;
       +
       +LtkTextManager *ltk_init_text(char *font_name);
       +
       +void ltk_destroy_text_manager(LtkTextManager *tm);
       +
       +LtkGlyphInfo *ltk_create_glyph_info(LtkFont *font, unsigned int id, float scale);
       +
       +void ltk_destroy_glyph_info(LtkGlyphInfo *gi);
       +
       +LtkGlyphInfo *ltk_get_glyph_info(LtkFont *font, unsigned int id, float scale, khash_t(glyphinfo) *cache);
       +
       +khint_t ltk_create_glyph_cache(LtkTextManager *tm, uint16_t font_id, uint16_t font_size);
       +
       +void ltk_destroy_glyph_cache(khash_t(glyphinfo) *cache);
       +
       +void ltk_load_default_font(LtkTextManager *tm, char *name);
       +
       +LtkFont *ltk_create_font(char *path, uint16_t id);
       +
       +void ltk_destroy_font(LtkFont *font);
       +
       +/* FIXME: need to figure out how exactly the whole font system is going to work, especially with default fonts, etc. */
       +uint16_t ltk_load_font(LtkTextManager *tm, char *path);
       +
       +uint16_t ltk_get_font(LtkTextManager *tm, char *path);
       +
       +/* TODO: different sizes, colors, styles, etc. */
       +LtkTextLine *ltk_create_text_line(LtkTextManager *tm, char *text, uint16_t fontid, uint16_t size);
       +
       +/* FIXME: could use unsigned int for fontid and size as long as there is code to check neither of them become too large
       +   -> in case I want to get rid of uint_16_t, etc. */
       +LtkTextSegment *ltk_create_text_segment(LtkTextManager *tm, uint32_t *text, unsigned int text_len, uint16_t fontid, uint16_t size, hb_script_t script);
       +
       +void ltk_destroy_glyph(LtkGlyph *glyph, khash_t(glyphinfo) *cache);
       +
       +void ltk_destroy_text_segment(LtkTextSegment *ts);
       +
       +XImage *ltk_render_text_line(LtkTextLine *tl, Display *dpy, Window window, GC gc, Colormap colormap, XColor fg, XColor bg);
       +
       +void ltk_render_text_segment(LtkTextSegment *ts, unsigned int start_x, unsigned int start_y, XImage *img, XColor fg);
       +
       +
       +#endif