ltkx

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

textedit_wip.c (21936B)


      1 /*
      2  * This file is part of the Lumidify ToolKit (LTK)
      3  * Copyright (c) 2020 lumidify <nobody@lumidify.org>
      4  *
      5  * Permission is hereby granted, free of charge, to any person obtaining a copy
      6  * of this software and associated documentation files (the "Software"), to deal
      7  * in the Software without restriction, including without limitation the rights
      8  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
      9  * copies of the Software, and to permit persons to whom the Software is
     10  * furnished to do so, subject to the following conditions:
     11  *
     12  * The above copyright notice and this permission notice shall be included in all
     13  * copies or substantial portions of the Software.
     14  *
     15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
     18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     20  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
     21  * SOFTWARE.
     22  */
     23 
     24 /* NOTE: THIS DOESN'T HAVE ANY USABLE CODE YET! */
     25 
     26 #include <stdio.h>
     27 #include <stdlib.h>
     28 #include <stdint.h>
     29 #include <limits.h>
     30 #include <X11/Xlib.h>
     31 #include <X11/Xutil.h>
     32 #include "stb_truetype.h" /* http://nothings.org/stb/stb_truetype.h */
     33 #include <fontconfig/fontconfig.h>
     34 #include "khash.h"
     35 #include <fribidi.h>
     36 #include <harfbuzz/hb.h>
     37 #include <harfbuzz/hb-ot.h>
     38 #include "textedit_wip.h"
     39 #include "text-common.h"
     40 #include "ltk.h"
     41 
     42 extern Ltk *ltk_global;
     43 
     44 LTK_GAP_BUFFER_INIT_IMPL(uint32, uint32_t)
     45 LTK_GAP_BUFFER_INIT_IMPL(script, hb_script_t)
     46 LTK_GAP_BUFFER_INIT_IMPL(int, int)
     47 LTK_ARRAY_INIT_IMPL(level, FriBidiLevel)
     48 LTK_ARRAY_INIT_IMPL(int, int)
     49 LTK_STACK_INIT_IMPL(script, int, hb_script_t, pair_index, script);
     50 
     51 /* based on http://codemadness.org/git/dwm-font/file/drw.c.html#l315 */
     52 void
     53 ltk_render_text_line(
     54 	struct ltk_text_line *tl,
     55 	int max_width,
     56 	Display *dpy,
     57 	Window window,
     58 	GC gc,
     59 	Colormap colormap,
     60 	XColor fg,
     61 	XColor bg)
     62 {
     63 	int cur_x = 0, cur_y = 0;
     64 	int par_is_rtl = FRIBIDI_IS_RTL(tl->dir);
     65 	ltk_array_clear_int(tl->wrap_indeces);
     66 	ltk_array_append_int(tl->wrap_indeces, 0);
     67 
     68 	/* FIXME: wrap bidi text properly */
     69 	struct ltk_text_run *cur = tl->first_run;
     70 	do {
     71 		for (int i = 0; i < cur->num_glyphs; i++) {
     72 			cur_x += cur->glyphs[i]->x_advance;
     73 			if (cur_x > max_width) {
     74 				int j = 0;
     75 				for (j = i; j >= 0; j--) {
     76 					if (cur->glyphs[j]->cluster != cur->glyphs[i]->cluster) {
     77 						/* must increase one again so the actual
     78 						   next character is used */
     79 						j++;
     80 						break;
     81 					}
     82 				}
     83 				i = j;
     84 				/* FIXME: handle case that this is the same as the last index */
     85 				ltk_array_append_int(tl->wrap_indeces, cur->glyphs[i].cluster);
     86 				cur_x = 0;
     87 			}
     88 		}
     89 	} while (cur = cur->next);
     90 
     91 	if (tl->img) XDestroyImage(tl->img);
     92 	XWindowAttributes attrs;
     93 	XGetWindowAttributes(dpy, window, &attrs);
     94 	int depth = attrs.depth;
     95 	tl->img = XCreateImage(dpy, CopyFromParent, depth, ZPixmap, 0, NULL, max_width, tl->h * tl->wrap_indeces->len, 32, 0);
     96 	tl->img->data = calloc(img->bytes_per_line, img->height);
     97 	XInitImage(tl->img);
     98 
     99 	int b;
    100 	for (int i = 0; i < tl->h * tl->wrap_indeces->len; i++) {
    101 		b = tl->img->bytes_per_line * i;
    102 		for (int j = 0; j < max_width; j++) {
    103 			tl->img->data[b++] = bg.blue / 257;
    104 			tl->img->data[b++] = bg.green / 257;
    105 			tl->img->data[b++] = bg.red / 257;
    106 			b++;
    107 		}
    108 	}
    109 
    110 	cur = tl->first_run;
    111 	int x, y;
    112 	int cur_line_x = 0;
    113 	int cur_line = 0;
    114 	LtkGlyph *glyph;
    115 	/* FIXME: Ints are compared with size_t's in various places. Maybe I should fix that. */
    116 	/* FIXME: how should an empty line be handled? This doesn't use a do-for
    117 	   loop in case tl->first_run is NULL, but I should probably decide what
    118 	   to do in that case */
    119 	while (cur) {
    120 		for (int k = 0; k < cur->len; k++) {
    121 			glyph = cur->glyphs[k];
    122 			if (cur_line < tl->wrap_indeces->len - 1 &&
    123 			    glyph->cluster >= tl->wrap_indeces->buf[cur_line + 1]) {
    124 				cur_line++;
    125 				cur_line_x += glyph->x_abs - cur_line_x;
    126 			}
    127 			x = glyph->x_abs - cur_line_x;
    128 			y = glyph->y_abs + tl->h * cur_line;
    129 			for (int i = 0; i < glyph->info->h; i++) {
    130 				for (int j = 0; j < glyph->info->w; j++) {
    131 					b = (y + i) * tl->img->bytes_per_line + (x + j) * 4;
    132 					a = glyph->info->alphamap[i * glyph->info->w + j] / 255.0;
    133 					tl->img->data[b] = (fg.blue * a + (1 - a) * (uint16_t)tl->img->data[b] * 257) / 257;
    134 					tl->img->data[b + 1] = (fg.green * a + (1 - a) * (uint16_t)tl->img->data[b + 1] * 257) / 257;
    135 					tl->img->data[b + 2] = (fg.red * a + (1 - a) * (uint16_t)tl->img->data[b + 2] * 257) / 257;
    136 				}
    137 			}
    138 		}
    139 		cur = cur->next;
    140 	}
    141 }
    142 
    143 /*
    144 NOTE: The following notes are outdated.
    145 Notes: When inserting a character, check what the direction of the surrounding
    146 script is - if it is the same (or a weak direction), then just insert the char
    147 without re-doing the bidi algorithm. Only redo the whole line/paragraph is the
    148 dir of the inserted character is different. Assume the text is LTR until a strong
    149 character is inserted, i.e. the line should keep a flag if it already has a strong
    150 dir set.
    151 When reordering, the cursor needs to be kept in the right place:
    152 a) The logical to visual mapping from FriBidi needs to be used to find the position
    153 of the cursor in the text that gets passed to Harfbuzz.
    154 b) The cluster values from Harfbuzz need to be used to figure out where the cursor
    155 is on the screen.
    156 Harfbuzz also should be given some context - maybe even completely reshape the
    157 text until the last and next space? That would only be a problem with weird very
    158 long words (mainly nonsense). At least maybe take up to five or so chars on each
    159 side and pass even more with the optional context-passing feature for hb_shape.
    160 Question: Does both the logical and visual text need to be stored? The logical text
    161 is technically the master copy, but the visual text is what gets passed to harfbuzz.
    162 -> Actually, since the visual text has to be split into script runs anyways, maybe
    163 just append text to that (and also add it to the master buffer simultaneously). As
    164 soon as a different script is inserted, everything has to be redone anyways. Also,
    165 when reshaping with context, only the text in the current run has to be passed at all.
    166 */
    167 
    168 /* Begin stuff stolen from raqm */
    169 
    170 /* Special paired characters for script detection */
    171 static size_t paired_len = 34;
    172 static const FriBidiChar paired_chars[] = {
    173 	0x0028, 0x0029, /* ascii paired punctuation */
    174 	0x003c, 0x003e,
    175 	0x005b, 0x005d,
    176 	0x007b, 0x007d,
    177 	0x00ab, 0x00bb, /* guillemets */
    178 	0x2018, 0x2019, /* general punctuation */
    179 	0x201c, 0x201d,
    180 	0x2039, 0x203a,
    181 	0x3008, 0x3009, /* chinese paired punctuation */
    182 	0x300a, 0x300b,
    183 	0x300c, 0x300d,
    184 	0x300e, 0x300f,
    185 	0x3010, 0x3011,
    186 	0x3014, 0x3015,
    187 	0x3016, 0x3017,
    188 	0x3018, 0x3019,
    189 	0x301a, 0x301b
    190 };
    191 
    192 static int
    193 get_pair_index (const FriBidiChar ch) {
    194 	int lower = 0;
    195 	int upper = paired_len - 1;
    196 
    197 	while (lower <= upper) {
    198 	int mid = (lower + upper) / 2;
    199 	if (ch < paired_chars[mid])
    200 		upper = mid - 1;
    201 	else if (ch > paired_chars[mid])
    202 		lower = mid + 1;
    203 	else
    204 		return mid;
    205 	}
    206 
    207 	return -1;
    208 }
    209 
    210 #define STACK_IS_EMPTY(stack) ((stack)->size <= 0)
    211 #define IS_OPEN(pair_index) (((pair_index) & 1) == 0)
    212 
    213 /* Resolve the script for each character in the input string, if the character
    214  * script is common or inherited it takes the script of the character before it
    215  * except paired characters which we try to make them use the same script. We
    216  * then split the BiDi runs, if necessary, on script boundaries.
    217  */
    218 static int
    219 ltk_resolve_scripts(struct ltk_text_line *tl) {
    220 	int last_script_index = -1;
    221 	int last_set_index = -1;
    222 	hb_script_t last_script = HB_SCRIPT_INVALID;
    223 	hb_script_t cur_script;
    224 	ltk_stack_script *stack = NULL;
    225 	hb_unicode_funcs_t* unicode_funcs = hb_unicode_funcs_get_default();
    226 
    227 	stack = ltk_stack_create_script(tl->len);
    228 
    229 	for (int i = 0; i < (int) tl->len; i++) {
    230 		cur_script = ltk_gap_buffer_get_script(tl->scripts, i);
    231 		if (cur_script == HB_SCRIPT_COMMON && last_script_index != -1) {
    232 			int pair_index = get_pair_index(ltk_gap_buffer_get_uint32(tl->log_buf, i));
    233 			if (pair_index >= 0) {
    234 				if (IS_OPEN (pair_index)) {
    235 					/* is a paired character */
    236 					ltk_gap_buffer_set_script(tl->scripts, i, last_script);
    237 					last_set_index = i;
    238 					ltk_stack_push_script(stack, cur_script, pair_index);
    239 				} else {
    240 					/* is a close paired character */
    241 					/* find matching opening (by getting the last
    242 					 * even index for current odd index) */
    243 					while (!STACK_IS_EMPTY(stack) &&
    244 					    stack->pair_index[stack->size] != (pair_index & ~1)) {
    245 						ltk_stack_pop_script(stack);
    246 					}
    247 					if (!STACK_IS_EMPTY(stack)) {
    248 						ltk_gap_buffer_set_script(
    249 						    tl->scripts, i,
    250 						    ltk_stack_top2_script(stack, HB_SCRIPT_INVALID));
    251 						last_script = cur_script;
    252 						last_set_index = i;
    253 					} else {
    254 						ltk_gap_buffer_set_script(tl->scripts, i, last_script);
    255 						last_set_index = i;
    256 					}
    257 				}
    258 			} else {
    259 				ltk_gap_buffer_set_script(tl->scripts, i, last_script);
    260 				last_set_index = i;
    261 			}
    262 		} else if (cur_script == HB_SCRIPT_INHERITED && last_script_index != -1) {
    263 			ltk_gap_buffer_set_script(tl->scripts, i, last_script);
    264 			last_set_index = i;
    265 		} else {
    266 			for (int j = last_set_index + 1; j < i; ++j)
    267 				ltk_gap_buffer_set_script(tl->scripts, j, cur_script);
    268 			last_script = cur_script;
    269 			last_script_index = i;
    270 			last_set_index = i;
    271 		}
    272 	}
    273 
    274 	/* Loop backwards and change any remaining Common or Inherit characters to
    275 	* take the script if the next character.
    276 	* https://github.com/HOST-Oman/libraqm/issues/95
    277 	*/
    278 	hb_script_t scr;
    279 	for (int i = tl->len - 2; i >= 0;  --i) {
    280 		scr = ltk_gap_buffer_get_script(tl->scripts, i);
    281 		if (scr == HB_SCRIPT_INHERITED || scr == HB_SCRIPT_COMMON) {
    282 			ltk_gap_buffer_set_script(tl->scripts,
    283 			    ltk_gap_buffer_get_script(tl->scripts, i + 1));
    284 		}
    285 	}
    286 
    287 	ltk_stack_destroy_script(stack);
    288 
    289 	return 1;
    290 }
    291 
    292 /* End stuff stolen from raqm */
    293 /* Update: That's a lie; much more is stolen from raqm. */
    294 
    295 static struct
    296 ltk_text_run_create(size_t start_index, size_t len, hb_script_t script, hb_direction_t dir) {
    297 	struct ltk_text_run *run = malloc(sizeof(struct ltk_text_run));
    298 	if (!run) {
    299 		(void)fprintf(stderr, "Cannot allocate memory for text run.\n");
    300 		exit(1);
    301 	}
    302 	run->start_index = start_index;
    303 	run->len = len;
    304 	run->script = script;
    305 	run->dir = dir;
    306 	run->last = NULL;
    307 	run->next = NULL;
    308 }
    309 
    310 static void
    311 ltk_text_line_itemize(struct ltk_text_line *tl) {
    312 	ltk_resolve_scripts(tl);
    313 	struct ltk_text_run *first_run = NULL;
    314 	struct ltk_text_run *cur_run = NULL;
    315 	FriBidilevel last_level;
    316 	FriBidilevel cur_level;
    317 	hb_script_t last_script;
    318 	hb_script_t cur_script;
    319 	size_t start_index = 0;
    320 	size_t end_index;
    321 	hb_direction_t dir;
    322 	int par_is_rtl = FRIBIDI_IS_RTL(tl->dir);
    323 	while (start_index < tl->len) {
    324 		end_index = start_index;
    325 		cur_level = last_level = ltk_gap_buffer_get_level(tl->bidi_levels, start_index);
    326 		cur_script = last_script = ltk_gap_buffer_get_script(tl->scripts, start_index);
    327 		while (end_index < tl->len &&
    328 		    cur_level == last_level && cur_script == last_script) {
    329 			end_index++;
    330 			cur_level = ltk_gap_buffer_get_level(tl->bidi_levels, end_index);
    331 			cur_script = ltk_gap_buffer_get_script(tl->scripts, end_index);
    332 		}
    333 		dir = HB_DIRECTION_LTR;
    334 		if (FRIBIDI_LEVEL_IS_RTL(last_level))
    335 			dir = HB_DIRECTION_RTL;
    336 		struct ltk_text_run *new = ltk_text_run_create(
    337 		    start_index, end_index - start_index, last_script, dir);
    338 		if (!first_run) {
    339 			first_run = cur_run = new;
    340 		} else {
    341 			if (par_is_rtl) {
    342 				new->next = cur_run;
    343 				cur_run->last = new;
    344 			} else {
    345 				cur_run->next = new;
    346 				new->last = cur_run;
    347 			}
    348 			cur_run = new;
    349 		}
    350 		start_index = end_index;
    351 	}
    352 	tl->first_run = tl->cur_run = first_run;
    353 	tl->last_run = cur_run;
    354 }
    355 
    356 /* FIXME: return start_x, etc. instead of saving in struct text run */
    357 static void
    358 ltk_text_run_shape(LtkTextManager *tm, struct ltk_text_run *tr,
    359     struct ltk_text_line *tl, uint16_t font_size, uint16_t font_id, int *ret_y_max) {
    360 	khash_t(glyphinfo) *glyph_cache;
    361 	khint_t k;
    362 
    363 	tr->font_id = font_id;
    364 	uint32_t attr = font_id << 16 + font_size;
    365 	/* FIXME: turn this into ltk_get_glyph_cache */
    366 	k = kh_get(glyphcache, tm->glyph_cache, attr);
    367 	if (k == kh_end(tm->glyph_cache)) {
    368 		k = ltk_create_glyph_cache(tm, font_id, font_size);
    369 	}
    370 	glyph_cache = kh_value(tm->glyph_cache, k);
    371 
    372 	hb_buffer_t *buf;
    373 	hb_glyph_info_t *ginf, *gi;
    374 	hb_glyph_position_t *gpos, *gp;
    375 	tr->num_glyphs = 0;
    376 
    377 	buf = hb_buffer_create();
    378 	hb_buffer_set_direction(buf, run->dir);
    379 	hb_buffer_set_script(buf, run->script);
    380 	/* WARNING: vis_buf has to be normalized (without gap) for this! */
    381 	hb_buffer_add_codepoints(buf, tl->vis-buf, tl->len, tr->start_index, tr->len);
    382 	/* According to https://harfbuzz.github.io/the-distinction-between-levels-0-and-1.html
    383 	 * this should be level 1 clustering instead of level 0 */
    384 	hb_buffer_set_cluster_level(buf, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS);
    385 	hb_shape(font->hb, buf, NULL, 0);
    386 	ginf = hb_buffer_get_glyph_infos(buf, &tr->num_glyphs);
    387 	gpos = hb_buffer_get_glyph_positions(buf, &tr->num_glyphs);
    388 	float scale = stbtt_ScaleForMappingEmToPixels(&tr->font->info, font_size);
    389 
    390 	int x_min = INT_MAX, x_max = INT_MIN, y_min = INT_MAX, y_max = INT_MIN;
    391 	int x_abs = 0, y_abs = 0, x1_abs, y1_abs, x2_abs, y2_abs;
    392 	/* magic, do not touch */
    393 	tr->glyphs = malloc(sizeof(LtkGlyph) * num_glyph);
    394 	if (!tr->glyphs) {
    395 		(void)fprintf("Cannot allocate space for glyphs.\n");
    396 		exit(1);
    397 	}
    398 	/* FIXME: should x_max be calculated using glyph->info->w?
    399 	   The advance might be different and not represent where pixels
    400 	   will actually have to be drawn. */
    401 	LtkGlyph *glyph;
    402 	for (int i = 0; i < tr->num_glyphs; i++) {
    403 		gi = &ginf[i];
    404 		gp = &gpos[i];
    405 		glyph = tr->glyphs[i];
    406 		glyph->cluster = gi->cluster;
    407 		glyph->info = ltk_get_glyph_info(tr->font, gi->codepoint, scale, glyph_cache);
    408 		glyph->info->refs++;
    409 		/* FIXME: round instead of just casting */
    410 		glyph->x_offset = (int)(gp->x_offset * scale);
    411 		glyph->y_offset = (int)(gp->y_offset * scale);
    412 		glyph->x_advance = (int)(gp->x_advance * scale);
    413 		glyph->y_advance = (int)(gp->y_advance * scale);
    414 
    415 		/* Calculate position in order to determine full size of text segment */
    416 		x1_abs = x_abs + glyph->info->xoff + glyph->x_offset;
    417 		y1_abs = y_abs + glyph->info->yoff - glyph->y_offset;
    418 		/* Okay, wait, so should I check if the script is horizontal, and then add
    419 		   x_advance instead of glyph->info->w? It seems that the glyph width is
    420 		   usually smaller than x_advance, and spaces etc. are completely lost
    421 		   because their glyph width is 0. I have to distinguish between horizontal
    422 		   and vertical scripts, though because to calculate the maximum y position
    423 		   for horizontal scripts, I still need to use the glyph height since
    424 		   y_advance doesn't really do much there. I dunno, at least *something*
    425 		   works now... */
    426 		/* FIXME: THIS PROBABLY DOESN'T REALLY WORK */
    427 		if (HB_DIRECTION_IS_HORIZONTAL(tr->dir)) {
    428 			x2_abs = x1_abs + glyph->x_advance;
    429 			y2_abs = y1_abs + glyph->info->h;
    430 		} else {
    431 			x2_abs = x1_abs + glyph->info->w;
    432 			y2_abs = y1_abs - glyph->y_advance;
    433 		}
    434 		if (x1_abs < x_min) x_min = x1_abs;
    435 		if (y1_abs < y_min) y_min = y1_abs;
    436 		if (x2_abs > x_max) x_max = x2_abs;
    437 		if (y2_abs > y_max) y_max = y2_abs;
    438 		x_abs += glyph->x_advance;
    439 		y_abs -= glyph->y_advance;
    440 	}
    441         tr->start_x = -x_min;
    442         tr->start_y = -y_min;
    443 	tr->w = x_max - x_min;
    444 	*ret_y_max = y_max;
    445 
    446 	tr->font->refs++;
    447 }
    448 
    449 static void
    450 ltk_text_line_shape(LtkTextManager *tm, struct ltk_text_line *tl) {
    451 	struct ltk_text_run *run = tl->runs;
    452 	tl->y_max = INT_MIN;
    453 	tl->y_min = INT_MAX;
    454 	tl->w = tl->h = 0;
    455 	int x_max, y_max;
    456 	while (run) {
    457 		FcPattern *pat = FcPatternDuplicate(tm->fcpattern);
    458 		FcPattern *match;
    459 		FcResult result;
    460 		FcPatternAddBool(pat, FC_SCALABLE, 1);
    461 		FcConfigSubstitute(NULL, pat, FcMatchPattern);
    462 		FcDefaultSubstitute(pat);
    463 		FcCharSet *cs = FcCharSetCreate();
    464 		for (int i = run->start_index; i < run->start_index + run->len; i++) {
    465 			FcCharSetAddChar(cs, ltk_gap_buffer_get_uint32(tl->vis_buf, i));
    466 		}
    467 		FcPatternAddCharSet(pat, FC_CHARSET, cs);
    468 		match = FcFontMatch(NULL, pat, &result);
    469 		char *file;
    470 		FcPatternGetString(match, FC_FILE, 0, &file);
    471 		uint16_t font_id = ltk_get_font(tm, file);
    472 		khint_t k = kh_get(fontstruct, tm->font_cache, font_id);
    473 		run->font = kh_value(tm->font_cache, k);
    474 		FcPatternDestroy(match);
    475 		FcPatternDestroy(pat);
    476 		ltk_text_run_shape(tm, run, tl->font_size, font_id, &y_max);
    477 		if (tl->y_max < y_max)
    478 			tl->y_max = y_max;
    479 		/* tr->start_y is -y_min */
    480 		if (tl->y_min > -tr->start_y)
    481 			tl->y_min = -tr->start_y;
    482 		tl->w += tr->w;
    483 		run = run->next;
    484 	};
    485 	tl->h = tl->y_max - tl->y_min;
    486 
    487 	/* calculate the actual position of the characters */
    488 	run = tl->runs;
    489 	int x = 0;
    490 	LtkGlyph *glyph;
    491 	while (run) {
    492 		int cur_x = x + run->start_x;
    493 		int cur_y = tl->h - tl->y_max; /* baseline (I think?) */
    494 		for (int i = 0; i < run->len; i++) {
    495 			glyph = run->glyphs[i];
    496 			glyph->x_abs = cur_x + glyph->info->xoff + glyph->x_offset;
    497 			glyph->y_abs = cur_y - glyph->info->yoff - glyph->y_offset;
    498 			cur_x += glyph->x_advance;
    499 			cur_y -= glyph->y_advance;
    500 		}
    501 		x += run->w;
    502 		run = run->next;
    503 	}
    504 }
    505 
    506 /* FIXME: Don't destroy fonts, etc. every time the line is recalculated */
    507 /* Maybe decrease font refs but don't destroy until after the runs have been re-shaped */
    508 void
    509 ltk_text_run_destroy(struct ltk_text_line *tl, struct ltk_text_run *tr) {
    510 	khash_t(glyphinfo) *gcache;
    511 	LtkFont *font;
    512 	LtkGlyph *glyph;
    513 	khint_t k;
    514 	k = kh_get(glyphinfo, ltk_global->tm->glyph_cache, tr->font_id << 16 + tl->font_size);
    515 	gcache = kh_value(ltk_global->tm->glyph_cache, k);
    516 	for (int i = 0; i < tr->len; i++) {
    517 		glyph = tr->glyphs[i];
    518 		if (--glyph->info->refs < 1) {
    519 			k = kh_get(glyphinfo, cache, glyph->info->id);
    520 			kh_del(glyphinfo, cache, k);
    521 			ltk_destroy_glyph_info(glyph->info);
    522 		}
    523 	}
    524 	k = kh_get(fontstruct, ltk_global->tm->font_cache, tr->font_id);
    525 	font = kh_value(ltk_global->tm->font_cache, k);
    526 	if (--font->refs < 1) {
    527 		kh_del(fontstruct, ltk_global->tm->font_cache, k);
    528 		ltk_destroy_font(font);
    529 	}
    530 	free(tr->glyphs);
    531 	free(tr);
    532 }
    533 
    534 void
    535 ltk_text_line_destroy_runs(struct ltk_text_line *tl) {
    536 	struct ltk_text_run *cur, *last;
    537 	cur = tl->first_run;
    538 	while (cur) {
    539 		last = cur;
    540 		ltk_text_run_destroy(cur);
    541 		cur = last->next;
    542 	}
    543 }
    544 
    545 static void
    546 ltk_text_line_recalculate(LtkTextManager *tm, struct ltk_text_line *tl) {
    547 	ltk_gap_buffer_clear_uint32(tl->vis_buf);
    548 	ltk_gap_buffer_clear_int(tl->log2vis);
    549 	ltk_gap_buffer_clear_int(tl->vis2log);
    550 	size_t gap_pos = tl->log_buf->gap_left;
    551 	size_t gap_end = tl->log_buf->buf_size - tl->log_buf->gap_size;
    552 	ltk_gap_buffer_move_gap_uint32(tl->log_buf, gap_end);
    553 	fribidi_log2vis(
    554 	    tl->log_buf, tl->log_buf->buf_size - tl->log_buf->gap_size,
    555 	    &tl->dir, tl->vis_buf, tl->log2vis, tl->vis2log, tl->bidi_levels
    556 	);
    557 	ltk_gap_buffer_move_gap_uint32(tl->log_buf, gap_pos);
    558 	ltk_text_line_destroy_runs(tl); /* FIXME: IMPLEMENT */
    559 	ltk_text_line_itemize(tl);
    560 	ltk_text_line_shape(tm, tl);
    561 }
    562 
    563 void
    564 ltk_text_line_insert_text(struct ltk_text_line *tl, uint32_t *text, size_t len) {
    565 	/* check if any characters have a different script, only recalc then */
    566 	/*
    567 	hb_unicode_funcs_t *uf= hb_unicode_funcs_get_default();
    568 	struct ltk_text_run *run = tl->cur_run;
    569 	int recalc = 0;
    570 	hb_script_t script;
    571 	for (int i = 0; i < len; i++) {
    572 		scr = hb_unicode_script(uf, text[i]);
    573 		if (script != run->script &&
    574 		    script != HB_SCRIPT_INHERITED &&
    575 		    script != HB_SCRIPT_COMMON) {
    576 			recalc = 1;
    577 		}
    578 	}
    579 	*/
    580 	ltk_gap_buffer_insert_uint32(tl->log_buf, text, 0, len);
    581 	if (len > tl->scripts->gap-size)
    582 		ltk_gap_buffer_resize_gap_script(tl->scripts, len + 8);
    583 	hb_unicode_funcs_t *ufuncs = hb_unicode_funcs_get_default();
    584 	for (int i = 0; i < len; i++) {
    585 		ltk_gap_buffer_insert_single_script(
    586 		    tl->scripts, hb_unicode_script(ufuncs, text[i]));
    587 	}
    588 	if (len > tl->vis_buf->gap_size)
    589 		ltk_gap_buffer_resize_gap_uint32(tl->vis_buf, len + 8);
    590 	if (len > tl->log2vis->gap_size)
    591 		ltk_gap_buffer_resize_gap_int(tl->log2vis, len + 8);
    592 	if (len > tl->vis2log->gap_size)
    593 		ltk_gap_buffer_resize_gap_int(tl->vis2log, len + 8);
    594 	if (len + tl->bidi_levels->len > tl->bidi_levels->buf_size)
    595 		ltk_array_resize_levels(tl->bidi_levels, tl->bidi_levels->len + len + 8);
    596 	tl->len += len;
    597 	ltk_text_line_recalculate(tl);
    598 }
    599 
    600 struct ltk_text_line *
    601 ltk_text_line_create(void) {
    602 	struct ltk_text_line *line = malloc(sizeof(struct ltk_text_line));
    603 	if (!line) goto error;
    604 	line->log_buf = ltk_gap_buffer_create_uint32();
    605 	line->scripts = ltk_gap_buffer_create_script();
    606 	line->vis_buf = ltk_gap_buffer_create_uint32();
    607 	line->log2vis = ltk_gap_buffer_create_int();
    608 	line->vis2log = ltk_gap_buffer_create_int();
    609 	line->bidi_levels = ltk_array_create_levels(8);
    610 	line->wrap_indeces = ltk_array_create_int(1);
    611 	line->runs = NULL;
    612 	line->cur_run = NULL;
    613 	line->next = NULL;
    614 	line->height = 0;
    615 	line->dir = FRIBIDI_TYPE_ON;
    616 	line->len = 0;
    617 error:
    618 	(void)fprintf(stderr, "No memory left while creating text line\n");
    619 	exit(1);
    620 }
    621 
    622 struct ltk_text_buffer *
    623 ltk_text_buffer_create(void) {
    624 	struct ltk_text_buffer *buf = malloc(sizeof(struct ltk_text_buffer));
    625 	if (!buf) {
    626 		(void)fprintf(stderr, "No memory while creating text buffer\n");
    627 		exit(1);
    628 	}
    629 	buf->head = ltk_text_line_create();
    630 	buf->cur_line = buf->head;
    631 	buf->line_gap = 0;
    632 }
    633 
    634 void
    635 ltk_text_line_destroy(struct ltk_text_line *tl) {
    636 	ltk_gap_buffer_destroy_uint32(tl->log_buf);
    637 	ltk_gap_buffer_destroy_uint32(tl->vis_buf);
    638 	ltk_gap_buffer_destroy_script(tl->scripts);
    639 	ltk_gap_buffer_destroy_int(tl->log2vis);
    640 	ltk_gap_buffer_destroy_int(tl->vis2log);
    641 	ltk_array_destroy_level(tl->bidi_levels);
    642 	ltk_array_destroy_int(tl->wrap_indeces);
    643 	ltk_text_line_destroy_runs(tl);
    644 	if (tl->img) XDestroyImage(tl->img);
    645 	free(tl);
    646 }