ltkx

GUI toolkit for X11 (WIP)
git clone git://lumidify.org/ltkx.git
Log | Files | Refs | README | LICENSE

commit b05e1135200febcf01061df3e109943b6d828b8f
parent f344c3f28da7995f19ec0549d90f275d3fd1715a
Author: lumidify <nobody@lumidify.org>
Date:   Fri,  8 May 2020 21:13:42 +0200

Fix gap buffer and clean up textedit

Diffstat:
Mgap_buffer.h | 64+++++++++++++++++++++++++++++++++-------------------------------
Mtextedit_wip.c | 619+++++++++++++++----------------------------------------------------------------
Mtextedit_wip.h | 139+++++++------------------------------------------------------------------------
3 files changed, 162 insertions(+), 660 deletions(-)

diff --git a/gap_buffer.h b/gap_buffer.h @@ -27,31 +27,32 @@ #include <stdio.h> #include <stdlib.h> -#define LTK_GAP_BUFFER_INIT_DECL(type) \ -struct ltk_gap_buffer_##type## { \ +#define LTK_GAP_BUFFER_INIT_DECL(name, type) \ +struct ltk_gap_buffer_##name## { \ 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, \ +struct ltk_gap_buffer_##name## * ltk_gap_buffer_create_##name##(void); \ +struct ltk_gap_buffer_##name## * \ +ltk_gap_buffer_create_from_data_##name##(type *data, size_t len); \ +void ltk_gap_buffer_resize_gap_##name##( \ + struct ltk_gap_buffer_##name## *gb, int len); \ +void ltk_gap_buffer_insert_##name##(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); +void ltk_gap_buffer_insert_single_##name##( \ + struct ltk_gap_buffer_##name## *gb, type new); \ +void ltk_gap_buffer_move_gap_##name##( \ + struct ltk_gap_buffer_##name## *gb, size_t pos); \ +void ltk_gap_buffer_destroy_##name##(struct ltk_gap_buffer_##name## *gb); -#define LTK_GAP_BUFFER_INIT_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)); \ +#define LTK_GAP_BUFFER_INIT_IMPL(name, type) \ +struct ltk_gap_buffer_##name## * \ +ltk_gap_buffer_create_##name##(void) { \ + struct ltk_gap_buffer_##name## *gb = \ + malloc(sizeof(struct ltk_gap_buffer_##name##)); \ if (!gb) \ goto error; \ gb->buf = malloc(8 * sizeof(type)); \ @@ -67,10 +68,10 @@ error: \ 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)); \ +struct ltk_gap_buffer_##name## * \ +ltk_gap_buffer_create_from_data_##name##(type *data, size_t len) { \ + struct ltk_gap_buffer_##name## *gb = \ + malloc(sizeof(struct ltk_gap_buffer_##name##)); \ if (!gb) { \ (void)fprintf(stderr, "Out of memory while trying to" \ "allocate gap buffer\n"); \ @@ -84,14 +85,15 @@ 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) { \ +ltk_gap_buffer_resize_gap_##name##( \ + struct ltk_gap_buffer_##name## *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)); \ + struct ltk_gap_buffer_##name## *new = malloc(new_size * sizeof(type)); \ if (!new) { \ (void)fprintf(stderr, "Out of memory while trying to" \ "resize gap buffer\n"); \ @@ -108,10 +110,10 @@ 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, \ +ltk_gap_buffer_insert_##name##(struct ltk_gap_buffer_##name## *gb, \ type *new, size_t start, size_t len) { \ if (gb->gap_size < len) \ - ltk_gap_buffer_resize_gap_##type##(gb, len + 8); \ + ltk_gap_buffer_resize_gap_##name##(gb, len + 8); \ for (int i = 0; i < len; i++) { \ gb->buf[gb->gap_left + i] = new[start + i]; \ } \ @@ -120,14 +122,14 @@ ltk_gap_buffer_insert_##type##(struct ltk_gap_buffer_##type## *gb, \ } \ \ void \ -ltk_gap_buffer_insert_single_##type##( \ - struct ltk_gap_buffer_##type## *gb, type new) { \ - ltk_gap_buffer_insert_##type##(gb, &new, 0, 1); \ +ltk_gap_buffer_insert_single_##name##( \ + struct ltk_gap_buffer_##name## *gb, type new) { \ + ltk_gap_buffer_insert_##name##(gb, &new, 0, 1); \ } \ \ void \ -ltk_gap_buffer_move_gap_##type##( \ - struct ltk_gap_buffer_##type## *gb, size_t pos) { \ +ltk_gap_buffer_move_gap_##name##( \ + struct ltk_gap_buffer_##name## *gb, size_t pos) { \ if (pos == gb->gap_left) \ return; \ if (pos < 0 || pos > gb->buf_size - gb->gap_size) { \ @@ -148,7 +150,7 @@ ltk_gap_buffer_move_gap_##type##( \ } \ \ void \ -ltk_gap_buffer_destroy_##type##(struct ltk_gap_buffer_##type## *gb) { \ +ltk_gap_buffer_destroy_##name##(struct ltk_gap_buffer_##name## *gb) { \ free(gb->buf); \ free(gb); \ } diff --git a/textedit_wip.c b/textedit_wip.c @@ -1,6 +1,6 @@ /* * This file is part of the Lumidify ToolKit (LTK) - * Copyright (c) 2017, 2018, 2020 lumidify <nobody@lumidify.org> + * 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 @@ -33,290 +33,145 @@ #include <fribidi.h> #include <harfbuzz/hb.h> #include <harfbuzz/hb-ot.h> -#include "text-hb.h" +#include "textedit_wip.h" +#include "text-common.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) +/* 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; - 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); + 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); - 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; + 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; - return k; -} + 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; -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)); + start_pos = p; + last_script = cur_script; } } - 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); -} + free(vis_str); + free(log_str); -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); + /* 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; } - 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); + if (is_hor) { + tl->h = tl->y_max - tl->y_min; + } else { + tl->w = tl->x_max - tl->x_min; } - /* 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; + return tl; } 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); +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); } - - return id; } - /* 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 * @@ -440,18 +295,6 @@ ltk_create_text_segment(LtkTextManager *tm, uint32_t *text, unsigned int len, ui } 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; @@ -554,6 +397,7 @@ ltk_render_text_segment( } /* +NOTE: The following notes are outdated. 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 @@ -577,102 +421,6 @@ soon as a different script is inserted, everything has to be redone anyways. Als 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(8 * sizeof(uint32_t)); - 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 * -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 - 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->buf_size) { - 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->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(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 */ @@ -685,136 +433,3 @@ ltk_text_line_delete_text(struct ltk_text_line *tl, size_t len) { void ltk_text_line_delete_cur_cluster(struct ltk_text_line *tl) { } - -/* FIXME: allow to either use fribidi for basic shaping and don't use harfbuzz then, - or just use harfbuzz (then fribidi doesn't need to do any shaping) */ -LtkTextLine * -ltk_create_text_line(LtkTextManager *tm, char *text, uint16_t fontid, uint16_t size) -{ - /* NOTE: This doesn't actually take fontid into account right now - should it? */ - LtkTextLine *tl = malloc(sizeof(LtkTextLine)); - tl->start_segment = NULL; - LtkTextSegment *cur_ts = NULL; - LtkTextSegment *new_ts = NULL; - uint16_t cur_font_id = fontid; - int k; - LtkFont *font; - - unsigned int ulen = u8_strlen(text); - uint32_t *log_str = malloc(sizeof(uint32_t) * ulen); - size_t inc = 0; - for (int i = 0; i < ulen; i++) { - log_str[i] = u8_nextmemchar(text, &inc); - } - FriBidiChar *vis_str = malloc(sizeof(FriBidiChar) * ulen); - ulen = fribidi_charset_to_unicode(FRIBIDI_CHAR_SET_UTF8, text, strlen(text), log_str); - fribidi_log2vis(log_str, ulen, pbase_dir, vis_str, NULL, NULL, NULL); - - hb_unicode_funcs_t *ufuncs = hb_unicode_funcs_get_default(); - hb_script_t cur_script = hb_unicode_script(ufuncs, vis_str[0]); - hb_script_t last_script = cur_script; - size_t pos = 0; - size_t last_pos = 0; - size_t start_pos = 0; - uint32_t ch; - - for (int p = 0; p <= ulen; p++) { - cur_script = hb_unicode_script(ufuncs, vis_str[p]); - if (p == ulen || - (last_script != cur_script && - cur_script != HB_SCRIPT_INHERITED && - cur_script != HB_SCRIPT_COMMON)) { - FcPattern *pat = FcPatternDuplicate(tm->fcpattern); - FcPattern *match; - FcResult result; - FcPatternAddBool(pat, FC_SCALABLE, 1); - FcConfigSubstitute(NULL, pat, FcMatchPattern); - FcDefaultSubstitute(pat); - FcCharSet *cs = FcCharSetCreate(); - for (int i = start_pos; i < p; i++) { - FcCharSetAddChar(cs, vis_str[i]); - } - FcPatternAddCharSet(pat, FC_CHARSET, cs); - match = FcFontMatch(NULL, pat, &result); - char *file; - FcPatternGetString(match, FC_FILE, 0, &file); - cur_font_id = ltk_get_font(tm, file); - k = kh_get(fontstruct, tm->font_cache, cur_font_id); - font = kh_value(tm->font_cache, k); - FcPatternDestroy(match); - FcPatternDestroy(pat); - // handle case that this is the last character - if (p == ulen) { - last_script = cur_script; - } - /* FIXME: There should be better handling for cases - where an error occurs while creating the segment */ - new_ts = ltk_create_text_segment( - tm, vis_str + start_pos, - p - start_pos, cur_font_id, - size, last_script - ); - if (!new_ts) continue; - new_ts->next = NULL; - if (!tl->start_segment) tl->start_segment = new_ts; - if (cur_ts) cur_ts->next = new_ts; - cur_ts = new_ts; - - start_pos = p; - last_script = cur_script; - } - } - - free(vis_str); - free(log_str); - - /* calculate width of text line - NOTE: doesn't work with mixed horizontal and vertical text */ - LtkTextSegment *ts = tl->start_segment; - int is_hor = HB_DIRECTION_IS_HORIZONTAL(ts->dir); - tl->y_max = tl->x_max = INT_MIN; - tl->y_min = tl->x_min = INT_MAX; - tl->w = tl->h = 0; - while (ts) { - if (HB_DIRECTION_IS_HORIZONTAL(ts->dir) != is_hor) { - (void)fprintf(stderr, "WARNING: mixed horizontal/vertical text is not supported; ignoring\n"); - continue; - } - if (is_hor) { - if (tl->y_max < ts->y_max) { - tl->y_max = ts->y_max; - } - if (tl->y_min > ts->y_min) { - tl->y_min = ts->y_min; - } - tl->w += ts->w; - } else { - if (tl->x_max < ts->x_max) { - tl->x_max = ts->x_max; - } - if (tl->x_min > ts->x_min) { - tl->x_min = ts->x_min; - } - tl->h += ts->h; - } - ts = ts->next; - } - if (is_hor) { - tl->h = tl->y_max - tl->y_min; - } else { - tl->w = tl->x_max - tl->x_min; - } - - return tl; -} - -void -ltk_destroy_text_line(LtkTextLine *tl) { - LtkTextSegment *last_ts; - LtkTextSegment *cur_ts = tl->start_segment; - while (cur_ts) { - last_ts = cur_ts; - cur_ts = cur_ts->next; - ltk_destroy_text_segment(last_ts); - } -} diff --git a/textedit_wip.h b/textedit_wip.h @@ -1,6 +1,6 @@ /* * This file is part of the Lumidify ToolKit (LTK) - * Copyright (c) 2017, 2018, 2020 lumidify <nobody@lumidify.org> + * 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 @@ -31,8 +31,10 @@ Requires the following includes: <fontconfig/fontconfig.h> */ +#include "gap_buffer.h" + /* Contains glyph info specific to one run of text */ -typedef struct ltk_glyph { +struct ltk_glyph { LtkGlyphInfo *info; int x_offset; /* additional x offset given by harfbuzz */ int y_offset; /* additional y offset given by harfbuzz */ @@ -43,18 +45,12 @@ typedef struct ltk_glyph { 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; -}; +LTK_GAP_BUFFER_INIT_DECL(uint32, uint32_t) +LTK_GAP_BUFFER_INIT_DECL(int, int) +LTK_GAP_BUFFER_INIT_DECL(glyph, struct ltk_glyph) -/* 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_gap_buffer_glyph *glyphs; struct ltk_text_run *next; LtkFont *font; unsigned int w; @@ -69,9 +65,10 @@ struct ltk_text_run { } 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_gap_buffer_uint32 *log_buf; /* buffer of the logical text */ + struct ltk_gap_buffer_uint32 *vis_buf; /* buffer of visual text */ + struct ltk_gap_buffer_int *log2vis; + struct ltk_gap_buffer_int *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 */ @@ -84,115 +81,3 @@ struct ltk_text_buffer { struct ltk_text_line *cur_line; unsigned int line_gap; }; - -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