ltkx

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

text-common.c (10432B)


      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 "ltk.h"
     38 
     39 extern Ltk *ltk_global;
     40 
     41 /* These unicode routines are taken from
     42  * https://github.com/JeffBezanson/cutef8 */
     43 
     44 /* is c the start of a utf8 sequence? */
     45 #define isutf(c) (((c)&0xC0)!=0x80)
     46 
     47 static const uint32_t offsetsFromUTF8[6] = {
     48     0x00000000UL, 0x00003080UL, 0x000E2080UL,
     49     0x03C82080UL, 0xFA082080UL, 0x82082080UL
     50 };
     51 
     52 /* next character without NUL character terminator */
     53 uint32_t u8_nextmemchar(const char *s, size_t *i)
     54 {
     55     uint32_t ch = 0;
     56     size_t sz = 0;
     57     do {
     58         ch <<= 6;
     59         ch += (unsigned char)s[(*i)++];
     60         sz++;
     61     } while (!isutf(s[*i]));
     62     ch -= offsetsFromUTF8[sz-1];
     63 
     64     return ch;
     65 }
     66 
     67 /* number of characters in NUL-terminated string */
     68 size_t u8_strlen(const char *s)
     69 {
     70     size_t count = 0;
     71     size_t i = 0, lasti;
     72 
     73     while (1) {
     74         lasti = i;
     75         while (s[i] > 0)
     76             i++;
     77         count += (i-lasti);
     78         if (s[i++]==0) break;
     79         (void)(isutf(s[++i]) || isutf(s[++i]) || ++i);
     80         count++;
     81     }
     82     return count;
     83 }
     84 
     85 size_t u8_wc_toutf8(char *dest, uint32_t ch)
     86 {
     87     if (ch < 0x80) {
     88         dest[0] = (char)ch;
     89         return 1;
     90     }
     91     if (ch < 0x800) {
     92         dest[0] = (ch>>6) | 0xC0;
     93         dest[1] = (ch & 0x3F) | 0x80;
     94         return 2;
     95     }
     96     if (ch < 0x10000) {
     97         dest[0] = (ch>>12) | 0xE0;
     98         dest[1] = ((ch>>6) & 0x3F) | 0x80;
     99         dest[2] = (ch & 0x3F) | 0x80;
    100         return 3;
    101     }
    102     if (ch < 0x110000) {
    103         dest[0] = (ch>>18) | 0xF0;
    104         dest[1] = ((ch>>12) & 0x3F) | 0x80;
    105         dest[2] = ((ch>>6) & 0x3F) | 0x80;
    106         dest[3] = (ch & 0x3F) | 0x80;
    107         return 4;
    108     }
    109     return 0;
    110 }
    111 
    112 LtkTextManager *
    113 ltk_init_text(char *font_name)
    114 {
    115 	LtkTextManager *tm = malloc(sizeof(LtkTextManager));
    116 	if (!tm) {
    117 		(void)printf("Memory exhausted when trying to create text manager.");
    118 		exit(1);
    119 	}
    120 	tm->font_paths = kh_init(fontid);
    121 	tm->font_cache = kh_init(fontstruct);
    122 	tm->glyph_cache = kh_init(glyphcache);
    123 	/* FIXME: THIS REALLY SHOULD NOT BE UINT16_T! IT GETS MESSY WITH BIT-SHIFTING */
    124 	tm->font_id_cur = 1;
    125 	ltk_load_default_font(tm, font_name);
    126 
    127 	return tm;
    128 }
    129 
    130 void
    131 ltk_destroy_text_manager(LtkTextManager *tm)
    132 {
    133 	int k;
    134 
    135 	kh_destroy(fontid, tm->font_paths);
    136 
    137 	for (k = kh_begin(tm->font_cache); k != kh_end(tm->font_cache); k++) {
    138 		if (kh_exist(tm->font_cache, k)) {
    139 			ltk_destroy_font(kh_value(tm->font_cache, k));
    140 		}
    141 	}
    142 	kh_destroy(fontstruct, tm->font_cache);
    143 
    144 	for (k = kh_begin(tm->glyph_cache); k != kh_end(tm->glyph_cache); k++) {
    145 		if (kh_exist(tm->glyph_cache, k)) {
    146 			ltk_destroy_glyph_cache(kh_value(tm->glyph_cache, k));
    147 		}
    148 	}
    149 	kh_destroy(glyphcache, tm->glyph_cache);
    150 
    151 	free(tm);
    152 }
    153 
    154 LtkGlyphInfo *
    155 ltk_create_glyph_info(LtkFont *font, unsigned int id, float scale)
    156 {
    157 	LtkGlyphInfo *glyph = malloc(sizeof(LtkGlyphInfo));
    158 	if (!glyph) {
    159 		(void)printf("Out of memory!\n");
    160 		exit(1);
    161 	}
    162 
    163 	glyph->id = id;
    164 	glyph->refs = 0;
    165 	glyph->alphamap = stbtt_GetGlyphBitmap(
    166 		&font->info, scale, scale, id, &glyph->w,
    167 		&glyph->h, &glyph->xoff, &glyph->yoff
    168 	);
    169 
    170 	return glyph;
    171 }
    172 
    173 void
    174 ltk_destroy_glyph_info(LtkGlyphInfo *gi)
    175 {
    176 	free(gi->alphamap);
    177 	free(gi);
    178 }
    179 
    180 LtkGlyphInfo *
    181 ltk_get_glyph_info(LtkFont *font, unsigned int id, float scale, khash_t(glyphinfo) *cache)
    182 {
    183 	int ret;
    184 	khint_t k;
    185 	LtkGlyphInfo *glyph;
    186 	k = kh_get(glyphinfo, cache, id);
    187 	if (k == kh_end(cache)) {
    188 		glyph = ltk_create_glyph_info(font, id, scale);
    189 		/* FIXME: error checking with ret */
    190 		k = kh_put(glyphinfo, cache, id, &ret);
    191 		kh_value(cache, k) = glyph;
    192 	} else {
    193 		glyph = kh_value(cache, k);
    194 	}
    195 
    196 	return glyph;
    197 }
    198 
    199 khint_t
    200 ltk_create_glyph_cache(LtkTextManager *tm, uint16_t font_id, uint16_t font_size)
    201 {
    202 	khash_t(glyphinfo) *cache = kh_init(glyphinfo);
    203 	int ret;
    204 	khint_t k;
    205 	/* I guess I can just ignore ret for now */
    206 	k = kh_put(glyphcache, tm->glyph_cache, font_id << 16 + font_size, &ret);
    207 	kh_value(tm->glyph_cache, k) = cache;
    208 
    209 	return k;
    210 }
    211 
    212 void
    213 ltk_destroy_glyph_cache(khash_t(glyphinfo) *cache)
    214 {
    215 	int k;
    216 	for (k = kh_begin(cache); k != kh_end(cache); k++) {
    217 		if (kh_exist(cache, k)) {
    218 			ltk_destroy_glyph_info(kh_value(cache, k));
    219 		}
    220 	}
    221 	kh_destroy(glyphinfo, cache);
    222 }
    223 
    224 void
    225 ltk_load_default_font(LtkTextManager *tm, char *name)
    226 {
    227 	FcPattern *match;
    228 	FcResult result;
    229 	char *file;
    230 	int index;
    231 	uint16_t font;
    232 
    233 	tm->fcpattern = FcNameParse(name);
    234 	FcPatternAddString(tm->fcpattern, FC_FONTFORMAT, "truetype");
    235 	FcConfigSubstitute(NULL, tm->fcpattern, FcMatchPattern);
    236 	FcDefaultSubstitute(tm->fcpattern);
    237 	match = FcFontMatch(NULL, tm->fcpattern, &result);
    238 
    239 	FcPatternGetString (match, FC_FILE, 0, (FcChar8 **) &file);
    240 	/* FIXME: Why is index never used? This is the index within the font file,
    241 	   so it might be important, although I'm not sure if stb_truetype even
    242 	   supports it */
    243 	FcPatternGetInteger (match, FC_INDEX, 0, &index);
    244 
    245 	tm->default_font = ltk_get_font(tm, file);
    246 
    247 	FcPatternDestroy (match);
    248 }
    249 
    250 LtkFont *
    251 ltk_create_font(char *path, uint16_t id)
    252 {
    253 	long len;
    254 	LtkFont *font = malloc(sizeof(LtkFont));
    255 	if (!font) {
    256 		(void)fprintf(stderr, "Out of memory!\n");
    257 		exit(1);
    258 	}
    259 	char *contents = ltk_read_file(path, &len);
    260 	if (!stbtt_InitFont(&font->info, contents, 0))
    261 	{
    262 		(void)fprintf(stderr, "Failed to load font %s\n", path);
    263 		exit(1);
    264 	}
    265 	/* FIXME: make use of the destroy function (last argument to hb_blob_create - see hb-blob.cc in harfbuzz source) */
    266 	hb_blob_t *blob = hb_blob_create(contents, len, HB_MEMORY_MODE_READONLY, NULL, NULL);
    267 	hb_face_t *face = hb_face_create(blob, 0);
    268 	/* FIXME: need to use destroy function in order for the original file data to be freed? */
    269 	hb_blob_destroy(blob);
    270 	font->hb = hb_font_create(face);
    271 	hb_face_destroy(face);
    272 	hb_ot_font_set_funcs(font->hb);
    273 	font->id = id;
    274 	font->refs = 0;
    275 
    276 	return font;
    277 }
    278 
    279 void
    280 ltk_destroy_font(LtkFont *font)
    281 {
    282 	free(font->info.data);
    283 	hb_font_destroy(font->hb);
    284 	free(font);
    285 }
    286 
    287 uint16_t
    288 ltk_load_font(LtkTextManager *tm, char *path)
    289 {
    290 	LtkFont *font = ltk_create_font(path, tm->font_id_cur++);
    291 	int ret;
    292 	khint_t k;
    293 	/* FIXME: does kh_destroy also free these copied strings properly? */
    294 	char *key = strdup(path);
    295 	k = kh_put(fontid, tm->font_paths, key, &ret);
    296 	kh_value(tm->font_paths, k) = font->id;
    297 	k = kh_put(fontstruct, tm->font_cache, (khint_t) font->id, &ret);
    298 	kh_value(tm->font_cache, k) = font;
    299 	k = kh_get(fontid, tm->font_paths, path);
    300 
    301 	return font->id;
    302 }
    303 
    304 uint16_t
    305 ltk_get_font(LtkTextManager *tm, char *path)
    306 {
    307 	int ret;
    308 	khint_t k;
    309 	uint16_t id;
    310 	k = kh_get(fontid, tm->font_paths, path);
    311 	if (k == kh_end(tm->font_paths)) {
    312 		id = ltk_load_font(tm, path);
    313 	} else {
    314 		id = kh_value(tm->font_paths, k);
    315 	}
    316 
    317 	return id;
    318 }
    319 
    320 void
    321 ltk_destroy_glyph(LtkGlyph *glyph, khash_t(glyphinfo) *cache)
    322 {
    323 	int k;
    324 	if (--glyph->info->refs < 1) {
    325 		k = kh_get(glyphinfo, cache, glyph->info->id);
    326 		kh_del(glyphinfo, cache, k);
    327 		ltk_destroy_glyph_info(glyph->info);
    328 	}
    329 	free(glyph);
    330 }
    331 
    332 #if 0
    333 /* based on http://codemadness.org/git/dwm-font/file/drw.c.html#l315 */
    334 XImage *
    335 ltk_render_text_line(
    336 	LtkTextLine *tl,
    337 	Display *dpy,
    338 	Window window,
    339 	GC gc,
    340 	Colormap colormap,
    341 	XColor fg,
    342 	XColor bg)
    343 {
    344 	XWindowAttributes attrs;
    345 	XGetWindowAttributes(dpy, window, &attrs);
    346 	int depth = attrs.depth;
    347 	XImage *img = XCreateImage(dpy, CopyFromParent, depth, ZPixmap, 0, NULL, tl->w, tl->h, 32, 0);
    348 	img->data = calloc(img->bytes_per_line, img->height);
    349 	XInitImage(img);
    350 	int b;
    351 	for (int i = 0; i < tl->h; i++) {
    352 		b = img->bytes_per_line * i;
    353 		for (int j = 0; j < tl->w; j++) {
    354 			img->data[b++] = bg.blue / 257;
    355 			img->data[b++] = bg.green / 257;
    356 			img->data[b++] = bg.red / 257;
    357 			b++;
    358 		}
    359 	}
    360 
    361 	LtkTextSegment *ts = tl->start_segment;
    362 	int x = 0;
    363 	int y = 0;
    364 	int is_hor = HB_DIRECTION_IS_HORIZONTAL(ts->dir);
    365 	do {
    366 		if (is_hor) {
    367 			y = tl->h - tl->y_max;
    368 			ltk_render_text_segment(ts, x + ts->start_x, y, img, fg);
    369 			x += ts->w;
    370 		} else {
    371 			x = tl->w - tl->x_max;
    372 			ltk_render_text_segment(ts, x, y + ts->start_y, img, fg);
    373 			y += ts->h;
    374 		}
    375 	} while (ts = ts->next);
    376 
    377 	return img;
    378 }
    379 
    380 void
    381 ltk_render_text_segment(
    382 	LtkTextSegment *ts,
    383 	unsigned int start_x,
    384 	unsigned int start_y,
    385 	XImage *img,
    386 	XColor fg)
    387 {
    388 	LtkGlyph *glyph = ts->start_glyph;
    389 	int x_cur = start_x;
    390 	int y_cur = start_y;
    391 	int x, y;
    392 	double a;
    393 	int b;
    394 	do {
    395 		x = x_cur + glyph->info->xoff + glyph->x_offset;
    396 		y = y_cur + glyph->info->yoff - glyph->y_offset;
    397 		for (int i = 0; i < glyph->info->h; i++) {
    398 			for (int j = 0; j < glyph->info->w; j++) {
    399 				b = (y + i) * img->bytes_per_line + (x + j) * 4;
    400 				a = glyph->info->alphamap[i * glyph->info->w + j] / 255.0;
    401 				img->data[b] = (fg.blue * a + (1 - a) * (uint16_t)img->data[b] * 257) / 257;
    402 				img->data[b + 1] = (fg.green * a + (1 - a) * (uint16_t)img->data[b + 1] * 257) / 257;
    403 				img->data[b + 2] = (fg.red * a + (1 - a) * (uint16_t)img->data[b + 2] * 257) / 257;
    404 			}
    405 		}
    406 		x_cur += glyph->x_advance;
    407 		y_cur -= glyph->y_advance;
    408 	} while (glyph = glyph->next);
    409 }
    410 #endif