ltkx

GUI toolkit for X11 (old)
git clone git://lumidify.org/ltkx.git (fast, but not encrypted)
git clone https://lumidify.org/ltkx.git (encrypted, but very slow)
git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ltkx.git (over tor)
Log | Files | Refs | README | LICENSE

text-hb.c (13025B)


      1 /*
      2  * This file is part of the Lumidify ToolKit (LTK)
      3  * Copyright (c) 2017, 2018, 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 #include <stdio.h>
     25 #include <stdlib.h>
     26 #include <stdint.h>
     27 #include <limits.h>
     28 #include <X11/Xlib.h>
     29 #include <X11/Xutil.h>
     30 #include "stb_truetype.h" /* http://nothings.org/stb/stb_truetype.h */
     31 #include <fontconfig/fontconfig.h>
     32 #include "khash.h"
     33 #include <fribidi.h>
     34 #include <harfbuzz/hb.h>
     35 #include <harfbuzz/hb-ot.h>
     36 #include "text-common.h"
     37 #include "text-hb.h"
     38 #include "ltk.h"
     39 
     40 extern Ltk *ltk_global;
     41 
     42 /* FIXME: allow to either use fribidi for basic shaping and don't use harfbuzz then,
     43           or just use harfbuzz (then fribidi doesn't need to do any shaping) */
     44 LtkTextLine *
     45 ltk_create_text_line(LtkTextManager *tm, char *text, uint16_t fontid, uint16_t size)
     46 {
     47 	/* NOTE: This doesn't actually take fontid into account right now - should it? */
     48 	LtkTextLine *tl = malloc(sizeof(LtkTextLine));
     49 	tl->start_segment = NULL;
     50 	LtkTextSegment *cur_ts = NULL;
     51 	LtkTextSegment *new_ts = NULL;
     52 	uint16_t cur_font_id = fontid;
     53 	int k;
     54 	LtkFont *font;
     55 
     56 	unsigned int ulen = u8_strlen(text);
     57 	FriBidiChar *log_str = malloc(sizeof(FriBidiChar) * ulen);
     58 	size_t inc = 0;
     59 	for (int i = 0; i < ulen; i++) {
     60 		log_str[i] = u8_nextmemchar(text, &inc);
     61 	}
     62         FriBidiLevel *levels = malloc(ulen * sizeof(FriBidiLevel));
     63 	int *l2v = malloc(ulen * sizeof(int));
     64 	int *v2l = malloc(ulen * sizeof(int));
     65 	FriBidiCharType pbase_dir = FRIBIDI_TYPE_ON;
     66 	FriBidiChar *vis_str = malloc(sizeof(FriBidiChar) * ulen);
     67 	ulen = fribidi_charset_to_unicode(FRIBIDI_CHAR_SET_UTF8, text, strlen(text), log_str);
     68 	fribidi_log2vis(log_str, ulen, &pbase_dir, vis_str, l2v, v2l, levels);
     69 
     70 	hb_unicode_funcs_t *ufuncs = hb_unicode_funcs_get_default();
     71 	hb_script_t cur_script = hb_unicode_script(ufuncs, vis_str[0]);
     72 	hb_script_t last_script = cur_script;
     73 	size_t pos = 0;
     74 	size_t last_pos = 0;
     75 	size_t start_pos = 0;
     76 	uint32_t ch;
     77 
     78 	for (int p = 0; p <= ulen; p++) {
     79 		cur_script = hb_unicode_script(ufuncs, vis_str[p]);
     80 		if (p == ulen ||
     81 			(last_script != cur_script &&
     82 			 cur_script != HB_SCRIPT_INHERITED &&
     83 			 cur_script != HB_SCRIPT_COMMON)) {
     84 			FcPattern *pat = FcPatternDuplicate(tm->fcpattern);
     85 			FcPattern *match;
     86 			FcResult result;
     87 			FcPatternAddBool(pat, FC_SCALABLE, 1);
     88 			FcConfigSubstitute(NULL, pat, FcMatchPattern);
     89 			FcDefaultSubstitute(pat);
     90 			FcCharSet *cs = FcCharSetCreate();
     91 			for (int i = start_pos; i < p; i++) {
     92 				FcCharSetAddChar(cs, vis_str[i]);
     93 			}
     94 			FcPatternAddCharSet(pat, FC_CHARSET, cs);
     95 			match = FcFontMatch(NULL, pat, &result);
     96 			char *file;
     97 			FcPatternGetString(match, FC_FILE, 0, &file);
     98 			cur_font_id = ltk_get_font(tm, file);
     99 			k = kh_get(fontstruct, tm->font_cache, cur_font_id);
    100 			font = kh_value(tm->font_cache, k);
    101 			FcPatternDestroy(match);
    102 			FcPatternDestroy(pat);
    103 			// handle case that this is the last character
    104 			if (p == ulen) {
    105 				last_script = cur_script;
    106 			}
    107 			/* FIXME: There should be better handling for cases
    108 			   where an error occurs while creating the segment */
    109 			new_ts = ltk_create_text_segment(
    110 				tm, vis_str + start_pos,
    111 				p - start_pos, cur_font_id,
    112 				size, last_script
    113 			);
    114 			if (!new_ts) continue;
    115 			new_ts->next = NULL;
    116 			if (!tl->start_segment) tl->start_segment = new_ts;
    117 			if (cur_ts) cur_ts->next = new_ts;
    118 			cur_ts = new_ts;
    119 
    120 			start_pos = p;
    121 			last_script = cur_script;
    122 		}
    123 	}
    124 
    125 	free(vis_str);
    126 	free(log_str);
    127 	free(levels);
    128 	free(l2v);
    129 	free(v2l);
    130 
    131 	/* calculate width of text line
    132 	   NOTE: doesn't work with mixed horizontal and vertical text */
    133 	LtkTextSegment *ts = tl->start_segment;
    134 	int is_hor = HB_DIRECTION_IS_HORIZONTAL(ts->dir);
    135 	tl->y_max = tl->x_max = INT_MIN;
    136 	tl->y_min = tl->x_min = INT_MAX;
    137 	tl->w = tl->h = 0;
    138 	while (ts) {
    139 		if (HB_DIRECTION_IS_HORIZONTAL(ts->dir) != is_hor) {
    140 			(void)fprintf(stderr, "WARNING: mixed horizontal/vertical text is not supported; ignoring\n");
    141 			continue;
    142 		}
    143 		if (is_hor) {
    144 			if (tl->y_max < ts->y_max) {
    145 				tl->y_max = ts->y_max;
    146 			}
    147 			if (tl->y_min > ts->y_min) {
    148 				tl->y_min = ts->y_min;
    149 			}
    150 			tl->w += ts->w;
    151 		} else {
    152 			if (tl->x_max < ts->x_max) {
    153 				tl->x_max = ts->x_max;
    154 			}
    155 			if (tl->x_min > ts->x_min) {
    156 				tl->x_min = ts->x_min;
    157 			}
    158 			tl->h += ts->h;
    159 		}
    160 		ts = ts->next;
    161 	}
    162 	if (is_hor) {
    163 		tl->h = tl->y_max - tl->y_min;
    164 	} else {
    165 		tl->w = tl->x_max - tl->x_min;
    166 	}
    167 
    168 	return tl;
    169 }
    170 
    171 void
    172 ltk_destroy_text_line(LtkTextLine *tl) {
    173 	LtkTextSegment *last_ts;
    174 	LtkTextSegment *cur_ts = tl->start_segment;
    175 	while (cur_ts) {
    176 		last_ts = cur_ts;
    177 		cur_ts = cur_ts->next;
    178 		ltk_destroy_text_segment(last_ts);
    179 	}
    180 }
    181 
    182 /* FIXME: could use unsigned int for fontid and size as long as there is code to check neither of them become too large
    183    -> in case I want to get rid of uint_16_t, etc. */
    184 LtkTextSegment *
    185 ltk_create_text_segment(LtkTextManager *tm, uint32_t *text, unsigned int len, uint16_t fontid, uint16_t size, hb_script_t script)
    186 {
    187 	/* (x1*, y1*): top left corner (relative to origin and absolute)
    188 	   (x2*, y2*): bottom right corner (relative to origin and absolute) */
    189 	LtkFont *font;
    190 	khash_t(glyphinfo) *glyph_cache;
    191 	khint_t k;
    192 
    193 	k = kh_get(fontstruct, tm->font_cache, fontid);
    194 	font = kh_value(tm->font_cache, k);
    195 
    196 	uint32_t attr = fontid << 16 + size;
    197 	/* FIXME: turn this into ltk_get_glyph_cache */
    198 	k = kh_get(glyphcache, tm->glyph_cache, attr);
    199 	if (k == kh_end(tm->glyph_cache)) {
    200 		k = ltk_create_glyph_cache(tm, fontid, size);
    201 	}
    202 	glyph_cache = kh_value(tm->glyph_cache, k);
    203 
    204 	LtkTextSegment *ts = malloc(sizeof(LtkTextSegment));
    205 	if (!ts) {
    206 		(void)fprintf(stderr, "Out of memory!\n");
    207 		exit(1);
    208 	}
    209 	ts->str = malloc(sizeof(uint32_t) * (len + 1));
    210 	memcpy(ts->str, text, len * sizeof(uint32_t));
    211 	ts->str[len] = '\0';
    212 	ts->font_id = fontid;
    213 	ts->font_size = size;
    214 
    215 	hb_buffer_t *buf;
    216 	hb_glyph_info_t *ginf, *gi;
    217 	hb_glyph_position_t *gpos, *gp;
    218 	unsigned int text_len = 0;
    219 	if (len < 1) {
    220 		(void)printf("WARNING: ltk_render_text_segment: length of text is less than 1.\n");
    221 		return NULL;
    222 	}
    223 
    224 	buf = hb_buffer_create();
    225 	hb_direction_t dir = hb_script_get_horizontal_direction(script);
    226 	hb_buffer_set_direction(buf, dir);
    227 	hb_buffer_set_script(buf, script);
    228 	hb_buffer_add_utf32(buf, ts->str, len, 0, len);
    229 	/* According to https://harfbuzz.github.io/the-distinction-between-levels-0-and-1.html
    230 	 * this should be level 1 clustering instead of level 0 */
    231 	hb_buffer_set_cluster_level(buf, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS);
    232 	hb_shape(font->hb, buf, NULL, 0);
    233 	ts->dir = hb_buffer_get_direction(buf);
    234 	ginf = hb_buffer_get_glyph_infos(buf, &text_len);
    235 	gpos = hb_buffer_get_glyph_positions(buf, &text_len);
    236 	float scale = stbtt_ScaleForMappingEmToPixels(&font->info, size);
    237 	LtkGlyph *last_glyph = NULL;
    238 
    239 	/* FIXME: read https://harfbuzz.github.io/harfbuzz-hb-buffer.html#hb-glyph-position-t-struct */
    240 
    241 	int x_min = INT_MAX, x_max = INT_MIN, y_min = INT_MAX, y_max = INT_MIN;
    242 	int x_abs = 0, y_abs = 0, x1_abs, y1_abs, x2_abs, y2_abs;
    243 	/* magic, do not touch */
    244 	LtkGlyph *glyph;
    245 	for (int i = 0; i < text_len; i++) {
    246 		gi = &ginf[i];
    247 		gp = &gpos[i];
    248 		glyph = malloc(sizeof(LtkGlyph));
    249 		glyph->info = ltk_get_glyph_info(font, gi->codepoint, scale, glyph_cache);
    250 		glyph->info->refs++;
    251 		/* FIXME: round instead of just casting */
    252 		glyph->x_offset = (int)(gp->x_offset * scale);
    253 		glyph->y_offset = (int)(gp->y_offset * scale);
    254 		glyph->x_advance = (int)(gp->x_advance * scale);
    255 		glyph->y_advance = (int)(gp->y_advance * scale);
    256 		glyph->next = NULL;
    257 		if (i == 0) {
    258 			ts->start_glyph = glyph;
    259 		} else {
    260 			last_glyph->next = glyph;
    261 		}
    262 		last_glyph = glyph;
    263 
    264 		/* Calculate position in order to determine full size of text segment */
    265 		x1_abs = x_abs + glyph->info->xoff + glyph->x_offset;
    266 		y1_abs = y_abs + glyph->info->yoff - glyph->y_offset;
    267 		/* Okay, wait, so should I check if the script is horizontal, and then add
    268 		   x_advance instead of glyph->info->w? It seems that the glyph width is
    269 		   usually smaller than x_advance, and spaces etc. are completely lost
    270 		   because their glyph width is 0. I have to distinguish between horizontal
    271 		   and vertical scripts, though because to calculate the maximum y position
    272 		   for horizontal scripts, I still need to use the glyph height since
    273 		   y_advance doesn't really do much there. I dunno, at least *something*
    274 		   works now... */
    275 		/* FIXME: THIS PROBABLY DOESN'T REALLY WORK */
    276 		/* Wait, why do I calculate abs here and don't use it while rendering? */
    277 		/* Oh, it can't be calculated after figuring out the max and min points
    278 		   of the entire line */
    279 		if (HB_DIRECTION_IS_HORIZONTAL(dir)) {
    280 			x2_abs = x1_abs + glyph->x_advance;
    281 			y2_abs = y1_abs + glyph->info->h;
    282 		} else {
    283 			x2_abs = x1_abs + glyph->info->w;
    284 			y2_abs = y1_abs - glyph->y_advance;
    285 		}
    286 		glyph->x_abs = x1_abs;
    287 		glyph->y_abs = y1_abs;
    288 		if (x1_abs < x_min) x_min = x1_abs;
    289 		if (y1_abs < y_min) y_min = y1_abs;
    290 		if (x2_abs > x_max) x_max = x2_abs;
    291 		if (y2_abs > y_max) y_max = y2_abs;
    292 		x_abs += glyph->x_advance;
    293 		y_abs -= glyph->y_advance;
    294 	}
    295         ts->start_x = -x_min;
    296         ts->start_y = -y_min;
    297 	ts->w = x_max - x_min;
    298 	ts->h = y_max - y_min;
    299 	ts->x_min = x_min;
    300 	ts->y_min = y_min;
    301 	ts->x_max = x_max;
    302 	ts->y_max = y_max;
    303 
    304 	font->refs++;
    305 	/* FIXME: destroy hb_buffer */
    306 
    307 	return ts;
    308 }
    309 
    310 void
    311 ltk_destroy_text_segment(LtkTextSegment *ts)
    312 {
    313 	LtkGlyph *glyph, *next_glyph;
    314 	khash_t(glyphinfo) *gcache;
    315 	LtkFont *font;
    316 	int k;
    317 	glyph = ts->start_glyph;
    318 	k = kh_get(glyphinfo, ltk_global->tm->glyph_cache, ts->font_id << 16 + ts->font_size);
    319 	gcache = kh_value(ltk_global->tm->glyph_cache, k);
    320 	do {
    321 		next_glyph = glyph->next;
    322 		ltk_destroy_glyph(glyph, gcache);
    323 	} while (glyph = next_glyph);
    324 	k = kh_get(fontstruct, ltk_global->tm->font_cache, ts->font_id);
    325 	font = kh_value(ltk_global->tm->font_cache, k);
    326 	if (--font->refs < 1) {
    327 		kh_del(fontstruct, ltk_global->tm->font_cache, k);
    328 		ltk_destroy_font(font);
    329 	}
    330 	free(ts->str);
    331 	free(ts);
    332 }
    333 
    334 /* based on http://codemadness.org/git/dwm-font/file/drw.c.html#l315 */
    335 XImage *
    336 ltk_render_text_line(
    337 	LtkTextLine *tl,
    338 	Display *dpy,
    339 	Window window,
    340 	GC gc,
    341 	Colormap colormap,
    342 	XColor fg,
    343 	XColor bg)
    344 {
    345 	XWindowAttributes attrs;
    346 	XGetWindowAttributes(dpy, window, &attrs);
    347 	int depth = attrs.depth;
    348 	XImage *img = XCreateImage(dpy, CopyFromParent, depth, ZPixmap, 0, NULL, tl->w, tl->h, 32, 0);
    349 	img->data = calloc(img->bytes_per_line, img->height);
    350 	XInitImage(img);
    351 	int b;
    352 	for (int i = 0; i < tl->h; i++) {
    353 		b = img->bytes_per_line * i;
    354 		for (int j = 0; j < tl->w; j++) {
    355 			img->data[b++] = bg.blue / 257;
    356 			img->data[b++] = bg.green / 257;
    357 			img->data[b++] = bg.red / 257;
    358 			b++;
    359 		}
    360 	}
    361 
    362 	LtkTextSegment *ts = tl->start_segment;
    363 	int x = 0;
    364 	int y = 0;
    365 	int is_hor = HB_DIRECTION_IS_HORIZONTAL(ts->dir);
    366 	do {
    367 		if (is_hor) {
    368 			y = tl->h - tl->y_max;
    369 			ltk_render_text_segment(ts, x + ts->start_x, y, img, fg);
    370 			x += ts->w;
    371 		} else {
    372 			x = tl->w - tl->x_max;
    373 			ltk_render_text_segment(ts, x, y + ts->start_y, img, fg);
    374 			y += ts->h;
    375 		}
    376 	} while (ts = ts->next);
    377 
    378 	return img;
    379 }
    380 
    381 void
    382 ltk_render_text_segment(
    383 	LtkTextSegment *ts,
    384 	unsigned int start_x,
    385 	unsigned int start_y,
    386 	XImage *img,
    387 	XColor fg)
    388 {
    389 	LtkGlyph *glyph = ts->start_glyph;
    390 	int x_cur = start_x;
    391 	int y_cur = start_y;
    392 	int x, y;
    393 	double a;
    394 	int b;
    395 	do {
    396 		x = x_cur + glyph->info->xoff + glyph->x_offset;
    397 		y = y_cur + glyph->info->yoff - glyph->y_offset;
    398 		for (int i = 0; i < glyph->info->h; i++) {
    399 			for (int j = 0; j < glyph->info->w; j++) {
    400 				b = (y + i) * img->bytes_per_line + (x + j) * 4;
    401 				a = glyph->info->alphamap[i * glyph->info->w + j] / 255.0;
    402 				img->data[b] = (fg.blue * a + (1 - a) * (uint16_t)img->data[b] * 257) / 257;
    403 				img->data[b + 1] = (fg.green * a + (1 - a) * (uint16_t)img->data[b + 1] * 257) / 257;
    404 				img->data[b + 2] = (fg.red * a + (1 - a) * (uint16_t)img->data[b + 2] * 257) / 257;
    405 			}
    406 		}
    407 		x_cur += glyph->x_advance;
    408 		y_cur -= glyph->y_advance;
    409 	} while (glyph = glyph->next);
    410 }