ltkx

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

commit f28ba51069614bc481cc7e396fd12961e16c40f3
parent b1250321e3fc95848914e726871bea1a04f1454e
Author: lumidify <nobody@lumidify.org>
Date:   Sun, 10 May 2020 20:44:27 +0200

Implement rendering for textedit (no, nothing actually works yet)

Diffstat:
MREADME.md | 4++++
Marray.h | 33+++++++++++++++++++++------------
Mgap_buffer.h | 87+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Astack.h | 107+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtext-hb.c | 20++++++++------------
Mtextedit_wip.c | 598+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Mtextedit_wip.h | 31+++++++++++++++----------------
7 files changed, 584 insertions(+), 296 deletions(-)

diff --git a/README.md b/README.md @@ -14,4 +14,8 @@ Note that stb_truetype.h was split into a .c and .h file to make it fit in bette [cutef8](https://github.com/JeffBezanson/cutef8/) by Jeff Bezanson: [Public Domain](https://github.com/JeffBezanson/cutef8/blob/ce8607864ef59ceef39fc20c9653265f6b91d4bc/utf8.c#L4) +Bits and pieces stolen from this: + +[libraqm](https://github.com/HOST-Oman/libraqm) by Information Technology Authority (ITA) and Khaled Hosny: [MIT](https://raw.githubusercontent.com/HOST-Oman/libraqm/master/COPYING) + Note: LTK is in no way affiliated with any of the projects listed above. diff --git a/array.h b/array.h @@ -28,23 +28,23 @@ #include <stdlib.h> #define LTK_ARRAY_INIT_DECL(name, type) \ -struct ltk_array_##name## { \ +struct ltk_array_##name { \ type *buf; \ size_t buf_size; \ size_t len; \ } \ -struct ltk_array_##name## *ltk_array_create_##name##(size_t initial_len); \ -void ltk_array_resize_##name##(struct ltk_array_##name## *ar, size_t size); \ -void ltk_array_destroy_##name##(struct ltk_array_##name## *ar); +struct ltk_array_##name *ltk_array_create_##name(size_t initial_len); \ +void ltk_array_resize_##name(struct ltk_array_##name *ar, size_t size); \ +void ltk_array_destroy_##name(struct ltk_array_##name *ar); #define LTK_ARRAY_INIT_IMPL(name, type) \ -struct ltk_array_##name## * \ -ltk_array_create_##name##(size_t initial_len) { \ +struct ltk_array_##name * \ +ltk_array_create_##name(size_t initial_len) { \ if (initial_len == 0) { \ (void)fprintf(stderr, "Array length is zero\n"); \ exit(1); \ } \ - struct ltk_gap_buffer_##name## *ar = malloc(sizeof(struct ltk_array_##name##)); \ + struct ltk_gap_buffer_##name *ar = malloc(sizeof(struct ltk_array_##name)); \ if (!ar) goto error; \ ar->buf = malloc(initial_len * sizeof(type)); \ if (!ar->buf) goto error; \ @@ -56,19 +56,28 @@ error: \ } \ \ void \ -ltk_array_resize_##name##(struct ltk_array_##name## *ar, size_t size) { \ - type *new = realloc(ar->buf, size); \ +ltk_array_resize_##name(struct ltk_array_##name *ar, size_t len) { \ + size_t new_size; \ + if (4 * len <= ar->buf_size) { \ + new_size = 2 * len; \ + } \ + } else if (len > ar->len) { \ + new_size = 2 * len; \ + } else { \ + return; \ + } \ + type *new = realloc(ar->buf, new_size); \ if (!new) { \ (void)fprintf(stderr, "Cannot realloc array\n"); \ exit(1); \ } \ ar->buf = new; \ - ar->buf_size = size; \ - ar->len = ar->len < size ? ar->len : size; \ + ar->buf_size = new_size; \ + ar->len = ar->len < new_size ? ar->len : new_size; \ } \ \ void \ -ltk_array_destroy_##name##(struct ltk_array_##name## *ar) { \ +ltk_array_destroy_##name(struct ltk_array_##name *ar) { \ free(ar->buf); \ free(ar); \ } diff --git a/gap_buffer.h b/gap_buffer.h @@ -28,32 +28,32 @@ #include <stdlib.h> #define LTK_GAP_BUFFER_INIT_DECL(name, type) \ -struct ltk_gap_buffer_##name## { \ +struct ltk_gap_buffer_##name { \ type *buf; \ size_t buf_size; \ size_t gap_left; \ size_t gap_size; \ }; \ \ -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, \ +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_##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_clear_##name##(struct ltk_gap_buffer_##name## *gb); \ -void ltk_gap_buffer_destroy_##name##(struct ltk_gap_buffer_##name## *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_clear_##name(struct ltk_gap_buffer_##name *gb); \ +void ltk_gap_buffer_destroy_##name(struct ltk_gap_buffer_##name *gb); #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##)); \ +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)); \ @@ -69,10 +69,10 @@ error: \ exit(1); \ } \ \ -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##)); \ +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"); \ @@ -85,16 +85,37 @@ ltk_gap_buffer_create_from_data_##name##(type *data, size_t len) { \ return gb; \ } \ \ +type \ +ltk_gap_buffer_get_##name(struct ltk_gap_buffer_##name *gb, size_t index) { \ + if (index < gb->gap_left) \ + return gb->buf[index]; \ + else if (index < gb->buf_size - gb->gap_size) \ + return gb->buf[index - gb->gap_size]; \ + (void)fprintf("Gap buffer index out of bounds\n"); \ + exit(1); \ +} \ + \ +void \ +ltk_gap_buffer_get_##name( \ + struct ltk_gap_buffer_##name *gb, size_t index, type data) { \ + if (index < gb->gap_left) \ + gb->buf[index] = data; \ + else if (index < gb->buf_size - gb->gap_size) \ + gb->buf[index - gb->gap_size] = data; \ + (void)fprintf("Gap buffer index out of bounds\n"); \ + exit(1); \ +} \ + \ void \ -ltk_gap_buffer_resize_gap_##name##( \ - struct ltk_gap_buffer_##name## *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_##name## *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"); \ @@ -111,10 +132,10 @@ ltk_gap_buffer_resize_gap_##name##( \ } \ \ void \ -ltk_gap_buffer_insert_##name##(struct ltk_gap_buffer_##name## *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_##name##(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]; \ } \ @@ -123,14 +144,14 @@ ltk_gap_buffer_insert_##name##(struct ltk_gap_buffer_##name## *gb, \ } \ \ void \ -ltk_gap_buffer_insert_single_##name##( \ - struct ltk_gap_buffer_##name## *gb, type new) { \ - ltk_gap_buffer_insert_##name##(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_##name##( \ - struct ltk_gap_buffer_##name## *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) { \ @@ -150,13 +171,13 @@ ltk_gap_buffer_move_gap_##name##( \ gb->gap_left = pos; \ } \ \ -void ltk_gap_buffer_clear_##name##(struct ltk_gap_buffer_##name## *gb) { \ +void ltk_gap_buffer_clear_##name(struct ltk_gap_buffer_##name *gb) { \ gb->gap_left = 0; \ gb->gap_size = gb->buf_size; \ } \ \ void \ -ltk_gap_buffer_destroy_##name##(struct ltk_gap_buffer_##name## *gb) { \ +ltk_gap_buffer_destroy_##name(struct ltk_gap_buffer_##name *gb) { \ free(gb->buf); \ free(gb); \ } diff --git a/stack.h b/stack.h @@ -0,0 +1,107 @@ +/* + * This file is part of the Lumidify ToolKit (LTK) + * Copyright (c) 2020 lumidify <nobody@lumidify.org> + * Based on the stack from libraqm. + * + * 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. + */ + +/* This is a "macro'd" and modified version of raqm's stack */ + +#define LTK_INIT_STACK_DECL(name, type1, type2, data1, data2) \ +struct ltk_stack_##name { \ + size_t capacity; \ + size_t size; \ + type1 *data1; \ + type2 *data2; \ +}; \ + \ +static int ltk_stack_pop_##name(ltk_stack_##name *stack); \ +static data1 ltk_stack_top1_##name(ltk_stack##name *stack); \ +static void ltk_stack_destroy_##name(ltk_stack_##name *stack); \ +static int ltk_stack_push_##name( \ + ltk_stack_##name *stack, type1 data1, type2 data2); \ +static data2 ltk_stack_top2_##name(ltk_stack##name *stack); \ +static ltk_stack_##name *ltk_stack_create_##name (size_t max); + +#define LTK_INIT_STACK_IMPL(name, type1, type2, data1, data2) \ +static ltk_stack_##name * \ +ltk_stack_create_##name (size_t max) { \ + ltk_stack_##name *stack; \ + stack = malloc(sizeof(ltk_stack_##name)); \ + if (!stack) goto error; \ + \ + stack->data1 = malloc(sizeof(data1) * max); \ + if (!stack->data1) goto error; \ + stack->data2 = malloc(sizeof(data2) * max); \ + if (!stack->data2) goto error; \ + \ + stack->size = 0; \ + stack->capacity = max; \ + \ + return stack; \ +error: \ + (void)fprintf("Cannot allocate memory for stack\n"); \ + exit(1); \ +} \ + \ +static int \ +ltk_stack_pop_##name(ltk_stack_##name *stack) { \ + if (!stack->size) \ + return 0; \ + \ + stack->size--; \ + \ + return 1; \ +} \ + \ +static data1 \ +ltk_stack_top1_##name(ltk_stack##name *stack, type1 default) { \ + if (!stack->size) \ + return default; \ + \ + return stack->data1[stack->size]; \ +} \ + \ +static data2 \ +ltk_stack_top2_##name(ltk_stack##name *stack, type2 default) { \ + if (!stack->size) \ + return default; \ + \ + return stack->data2[stack->size]; \ +} \ + \ +static int \ +ltk_stack_push_##name(ltk_stack_##name *stack, type1 data1, type2 data2) { \ + if (stack->size == stack->capacity) \ + return 0; \ + \ + stack->size++; \ + stack->data1[stack->size] = data1; \ + stack->data2[stack->size] = data2; \ + \ + return 1; \ +} \ + \ +static void \ +ltk_stack_destroy_##name(ltk_stack_##name *stack) { \ + free(stack->data1); \ + free(stack->data2); \ + free(stack); \ +} diff --git a/text-hb.c b/text-hb.c @@ -59,19 +59,13 @@ ltk_create_text_line(LtkTextManager *tm, char *text, uint16_t fontid, uint16_t s 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] = i; - } - FriBidiCharType pbase_dir1 = FRIBIDI_TYPE_ON; + FriBidiLevel *levels = malloc(ulen * sizeof(FriBidiLevel)); + int *l2v = malloc(ulen * sizeof(int)); + int *v2l = malloc(ulen * sizeof(int)); + FriBidiCharType pbase_dir = 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_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"); + fribidi_log2vis(log_str, ulen, &pbase_dir, vis_str, l2v, v2l, levels); hb_unicode_funcs_t *ufuncs = hb_unicode_funcs_get_default(); hb_script_t cur_script = hb_unicode_script(ufuncs, vis_str[0]); @@ -130,7 +124,9 @@ ltk_create_text_line(LtkTextManager *tm, char *text, uint16_t fontid, uint16_t s free(vis_str); free(log_str); - free(pbase_dir); + free(levels); + free(l2v); + free(v2l); /* calculate width of text line NOTE: doesn't work with mixed horizontal and vertical text */ diff --git a/textedit_wip.c b/textedit_wip.c @@ -42,96 +42,12 @@ extern Ltk *ltk_global; LTK_GAP_BUFFER_INIT_IMPL(uint32, uint32_t) +LTK_GAP_BUFFER_INIT_IMPL(script, hb_script_t) LTK_GAP_BUFFER_INIT_IMPL(int, int) -LTK_GAP_BUFFER_INIT_IMPL(glyph, struct ltk_glyph) LTK_ARRAY_INIT_IMPL(char_type, FriBidiCharType) LTK_ARRAY_INIT_IMPL(level, FriBidiLevel) +LTK_STACK_INIT_IMPL(script, int, hb_script_t, pair_index, script); -/* 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; @@ -170,139 +86,6 @@ ltk_create_text_line(LtkTextManager *tm, char *text, uint16_t fontid, uint16_t s } 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_text_segment(LtkTextSegment *ts) { LtkGlyph *glyph, *next_glyph; @@ -429,11 +212,371 @@ 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. */ -void -ltk_text_line_recalculate(struct ltk_text_line *tl) { +/* Begin stuff stolen from raqm */ + +/* Special paired characters for script detection */ +static size_t paired_len = 34; +static const FriBidiChar paired_chars[] = { + 0x0028, 0x0029, /* ascii paired punctuation */ + 0x003c, 0x003e, + 0x005b, 0x005d, + 0x007b, 0x007d, + 0x00ab, 0x00bb, /* guillemets */ + 0x2018, 0x2019, /* general punctuation */ + 0x201c, 0x201d, + 0x2039, 0x203a, + 0x3008, 0x3009, /* chinese paired punctuation */ + 0x300a, 0x300b, + 0x300c, 0x300d, + 0x300e, 0x300f, + 0x3010, 0x3011, + 0x3014, 0x3015, + 0x3016, 0x3017, + 0x3018, 0x3019, + 0x301a, 0x301b +}; + +static int +get_pair_index (const FriBidiChar ch) { + int lower = 0; + int upper = paired_len - 1; + + while (lower <= upper) + { + int mid = (lower + upper) / 2; + if (ch < paired_chars[mid]) + upper = mid - 1; + else if (ch > paired_chars[mid]) + lower = mid + 1; + else + return mid; + } + + return -1; +} + +#define STACK_IS_EMPTY(stack) ((stack)->size <= 0) +#define IS_OPEN(pair_index) (((pair_index) & 1) == 0) + +/* Resolve the script for each character in the input string, if the character + * script is common or inherited it takes the script of the character before it + * except paired characters which we try to make them use the same script. We + * then split the BiDi runs, if necessary, on script boundaries. + */ +static int +ltk_resolve_scripts(struct ltk_text_line *tl) { + int last_script_index = -1; + int last_set_index = -1; + hb_script_t last_script = HB_SCRIPT_INVALID; + hb_script_t cur_script; + ltk_stack_script *stack = NULL; + hb_unicode_funcs_t* unicode_funcs = hb_unicode_funcs_get_default(); + + stack = ltk_stack_create_script(tl->len); + + for (int i = 0; i < (int) tl->len; i++) { + cur_script = ltk_gap_buffer_get_script(tl->scripts, i); + if (cur_script == HB_SCRIPT_COMMON && last_script_index != -1) { + int pair_index = get_pair_index(ltk_gap_buffer_get_uint32(tl->log_buf, i)); + if (pair_index >= 0) { + if (IS_OPEN (pair_index)) { + /* is a paired character */ + ltk_gap_buffer_set_script(tl->scripts, i, last_script); + last_set_index = i; + ltk_stack_push_script(stack, cur_script, pair_index); + } else { + /* is a close paired character */ + /* find matching opening (by getting the last + * even index for current odd index) */ + while (!STACK_IS_EMPTY(stack) && + stack->pair_index[stack->size] != (pair_index & ~1)) { + ltk_stack_pop_script(stack); + } + if (!STACK_IS_EMPTY(stack)) { + ltk_gap_buffer_set_script( + tl->scripts, i, + ltk_stack_top2_script(stack, HB_SCRIPT_INVALID)); + last_script = cur_script; + last_set_index = i; + } else { + ltk_gap_buffer_set_script(tl->scripts, i, last_script); + last_set_index = i; + } + } + } else { + ltk_gap_buffer_set_script(tl->scripts, i, last_script); + last_set_index = i; + } + } else if (cur_script == HB_SCRIPT_INHERITED && last_script_index != -1) { + ltk_gap_buffer_set_script(tl->scripts, i, last_script); + last_set_index = i; + } else { + for (int j = last_set_index + 1; j < i; ++j) + ltk_gap_buffer_set_script(tl->scripts, j, cur_script); + last_script = cur_script; + last_script_index = i; + last_set_index = i; + } + } + + /* Loop backwards and change any remaining Common or Inherit characters to + * take the script if the next character. + * https://github.com/HOST-Oman/libraqm/issues/95 + */ + hb_script_t scr; + for (int i = tl->len - 2; i >= 0; --i) { + scr = ltk_gap_buffer_get_script(tl->scripts, i); + if (scr == HB_SCRIPT_INHERITED || scr == HB_SCRIPT_COMMON) { + ltk_gap_buffer_set_script(tl->scripts, + ltk_gap_buffer_get_script(tl->scripts, i + 1)); + } + } + + ltk_stack_destroy_script(stack); + + return 1; +} + +/* End stuff stolen from raqm */ +/* Update: That's a lie; much more is stolen from raqm. */ + +static struct +ltk_text_run_create(size_t start_index, size_t len, hb_script_t script, hb_direction_t dir) { + struct ltk_text_run *run = malloc(sizeof(struct ltk_text_run)); + if (!run) { + (void)fprintf(stderr, "Cannot allocate memory for text run.\n"); + exit(1); + } + run->start_index = start_index; + run->len = len; + run->script = script; + run->dir = dir; + run->next = NULL; +} + +static void +ltk_text_line_itemize(struct ltk_text_line *tl) { + ltk_resolve_scripts(tl); + struct ltk_text_run *first_run = NULL; + struct ltk_text_run *cur_run = NULL; + FriBidilevel last_level; + FriBidilevel cur_level; + hb_script_t last_script; + hb_script_t cur_script; + size_t start_index = 0; + size_t end_index; + hb_direction_t dir; + while (start_index < tl->len) { + end_index = start_index; + cur_level = last_level = ltk_gap_buffer_get_level(tl->bidi_levels, start_index); + cur_script = last_script = ltk_gap_buffer_get_script(tl->scripts, start_index); + while (end_index < tl->len && + cur_level == last_level && cur_script == last_script) { + end_index++; + cur_level = ltk_gap_buffer_get_level(tl->bidi_levels, end_index); + cur_script = ltk_gap_buffer_get_script(tl->scripts, end_index); + } + dir = HB_DIRECTION_LTR; + if (FRIBIDI_LEVEL_IS_RTL(last_level)) + dir = HB_DIRECTION_RTL; + struct ltk_text_run *new = ltk_text_run_create( + start_index, end_index - start_index, last_script, dir); + if (!first_run) { + first_run = cur_run = new; + } else { + cur->next = new; + cur = new; + } + start_index = end_index; + } + tl->runs = tl->cur_run = first_run; +} + +static void +ltk_text_run_shape(LtkTextManager *tm, struct ltk_text_run *tr, + struct ltk_text_line *tl, uint16_t font_size, uint16_t font_id) { + khash_t(glyphinfo) *glyph_cache; + khint_t k; + + uint32_t attr = font_id << 16 + font_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, font_id, font_size); + } + glyph_cache = kh_value(tm->glyph_cache, k); + + hb_buffer_t *buf; + hb_glyph_info_t *ginf, *gi; + hb_glyph_position_t *gpos, *gp; + unsigned int num_glyphs = 0; + + buf = hb_buffer_create(); + hb_buffer_set_direction(buf, run->dir); + hb_buffer_set_script(buf, run->script); + /* WARNING: vis_buf has to be normalized (without gap) for this! */ + hb_buffer_add_codepoints(buf, tl->vis-buf, tl->len, tr->start_index, tr->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); + ginf = hb_buffer_get_glyph_infos(buf, &num_glyphs); + gpos = hb_buffer_get_glyph_positions(buf, &num_glyphs); + float scale = stbtt_ScaleForMappingEmToPixels(&tr->font->info, font_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 */ + /* FIXME: array instead of linked list */ + LtkGlyph *glyph; + for (int i = 0; i < num_glyphs; i++) { + gi = &ginf[i]; + gp = &gpos[i]; + glyph = malloc(sizeof(LtkGlyph)); + if (!glyph) { + (void)fprintf(stderr, "Cannot allocate glyph.\n"); + exit(1); + } + glyph->cluster = gi->cluster; + glyph->info = ltk_get_glyph_info(tr->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) { + tr->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(tr->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; + } + tr->start_x = -x_min; + tr->start_y = -y_min; + tr->w = x_max - x_min; + tr->h = y_max - y_min; + tr->x_min = x_min; + tr->y_min = y_min; + tr->x_max = x_max; + tr->y_max = y_max; + + tr->font->refs++; +} + +static void +ltk_text_line_shape(LtkTextManager *tm, struct ltk_text_line *tl) { + struct ltk_text_run *run = tl->runs; + while (run) { + 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 = run->start_index; i < run->start_index + run->len; i++) { + FcCharSetAddChar(cs, ltk_gap_buffer_get_uint32(tl->vis_buf, i)); + } + FcPatternAddCharSet(pat, FC_CHARSET, cs); + match = FcFontMatch(NULL, pat, &result); + char *file; + FcPatternGetString(match, FC_FILE, 0, &file); + uint16_t font_id = ltk_get_font(tm, file); + khint_t k = kh_get(fontstruct, tm->font_cache, font_id); + run->font = kh_value(tm->font_cache, k); + FcPatternDestroy(match); + FcPatternDestroy(pat); + ltk_text_run_shape(tm, run, tl->font_size, font_id); + run = run->next; + } + + /* calculate width of text line + NOTE: doesn't work with mixed horizontal and vertical text */ + /* Another note: none of this works at all with vertical text anyways */ + struct ltk_text_run *tr = tl->runs; + int is_hor = HB_DIRECTION_IS_HORIZONTAL(tr->dir); + tl->y_max = tl->x_max = INT_MIN; + tl->y_min = tl->x_min = INT_MAX; + tl->w = tl->h = 0; + while (tr) { + if (HB_DIRECTION_IS_HORIZONTAL(tr->dir) != is_hor) { + (void)fprintf(stderr, "WARNING: mixed horizontal/vertical" + "text is not supported; ignoring\n"); + continue; + } + if (is_hor) { + if (tl->y_max < tr->y_max) { + tl->y_max = tr->y_max; + } + if (tl->y_min > tr->y_min) { + tl->y_min = tr->y_min; + } + tl->w += tr->w; + } else { + if (tl->x_max < tr->x_max) { + tl->x_max = tr->x_max; + } + if (tl->x_min > tr->x_min) { + tl->x_min = tr->x_min; + } + tl->h += tr->h; + } + tr = tr->next; + } + if (is_hor) { + tl->h = tl->y_max - tl->y_min; + } else { + tl->w = tl->x_max - tl->x_min; + } +} + +static void +ltk_text_line_recalculate(LtkTextManager *tm, struct ltk_text_line *tl) { ltk_gap_buffer_clear_uint32(tl->vis_buf); ltk_gap_buffer_clear_int(tl->log2vis); ltk_gap_buffer_clear_int(tl->vis2log); + size_t gap_pos = tl->log_buf->gap_left; + size_t gap_end = tl->log_buf->buf_size - tl->log_buf->gap_size; + ltk_gap_buffer_move_gap_uint32(tl->log_buf, gap_end); + fribidi_log2vis( + tl->log_buf, tl->log_buf->buf_size - tl->log_buf->gap_size, + &tl->dir, tl->vis_buf, tl->log2vis, tl->vis2log, tl->bidi_levels + ); + ltk_gap_buffer_move_gap_uint32(tl->log_buf, gap_pos); + ltk_text_line_destroy_runs(tl); /* FIXME: IMPLEMENT */ + ltk_text_line_itemize(tl); + ltk_text_line_shape(tm, tl); } void @@ -454,16 +597,22 @@ ltk_text_line_insert_text(struct ltk_text_line *tl, uint32_t *text, size_t len) } */ ltk_gap_buffer_insert_uint32(tl->log_buf, text, 0, len); + if (len > tl->scripts->gap-size) + ltk_gap_buffer_resize_gap_script(tl->scripts, len + 8); + hb_unicode_funcs_t *ufuncs = hb_unicode_funcs_get_default(); + for (int i = 0; i < len; i++) { + ltk_gap_buffer_insert_single_script( + tl->scripts, hb_unicode_script(ufuncs, text[i])); + } if (len > tl->vis_buf->gap_size) ltk_gap_buffer_resize_gap_uint32(tl->vis_buf, len + 8); if (len > tl->log2vis->gap_size) ltk_gap_buffer_resize_gap_int(tl->log2vis, len + 8); if (len > tl->vis2log->gap_size) ltk_gap_buffer_resize_gap_int(tl->vis2log, len + 8); - if (len + tl->bidi_types->len > tl->bidi_types->buf_size) - ltk_array_resize_char_type(tl->bidi_types, tl->bidi_types->len + len + 8); if (len + tl->bidi_levels->len > tl->bidi_levels->buf_size) ltk_array_resize_levels(tl->bidi_levels, tl->bidi_levels->len + len + 8); + tl->len += len; ltk_text_line_recalculate(tl); } @@ -472,14 +621,17 @@ ltk_text_line_create(void) { struct ltk_text_line *line = malloc(sizeof(struct ltk_text_line)); if (!line) goto error; line->log_buf = ltk_gap_buffer_create_uint32(); + line->scripts = ltk_gap_buffer_create_script(); line->vis_buf = ltk_gap_buffer_create_uint32(); line->log2vis = ltk_gap_buffer_create_int(); line->vis2log = ltk_gap_buffer_create_int(); + line->bidi_levels = ltk_array_create_levels(8); line->runs = NULL; line->cur_run = NULL; line->next = NULL; line->height = 0; line->dir = FRIBIDI_TYPE_ON; + line->len = 0; error: (void)fprintf(stderr, "No memory left while creating text line\n"); exit(1); diff --git a/textedit_wip.h b/textedit_wip.h @@ -33,28 +33,19 @@ Requires the following includes: #include "gap_buffer.h" #include "array.h" - -/* Contains glyph info specific to one run of text */ -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 */ -}; +#include "stack.h" LTK_GAP_BUFFER_INIT_DECL(uint32, uint32_t) +LTK_GAP_BUFFER_INIT_DECL(script, hb_script_t) LTK_GAP_BUFFER_INIT_DECL(int, int) -LTK_GAP_BUFFER_INIT_DECL(glyph, struct ltk_glyph) -LTK_ARRAY_INIT_DECL(char_type, FriBidiCharType) LTK_ARRAY_INIT_DECL(level, FriBidiLevel) +LTK_STACK_INIT_DECL(script, int, hb_script_t, pair_index, script); struct ltk_text_run { - struct ltk_gap_buffer_glyph *glyphs; + struct ltk_glyph *start_glyph; struct ltk_text_run *next; + size_t start_index; + size_t len; LtkFont *font; unsigned int w; unsigned int h; @@ -70,16 +61,24 @@ struct ltk_text_run { struct ltk_text_line { struct ltk_gap_buffer_uint32 *log_buf; /* buffer of the logical text */ + struct ltk_gap_buffer_script *scripts; 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_array_char_type *bidi_types; struct ltk_array_level *bidi_levels; 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 */ + size_t len; + uint16_t font_size; + int x_max; + int x_min; + int y_max; + int y_min; + int w; + int h; }; struct ltk_text_buffer {