ltkx

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

text-hb.uberubernew.c (14858B)


      1 /*
      2  * This file is part of the Lumidify ToolKit (LTK)
      3  * Copyright (c) 2017, 2018 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 <harfbuzz/hb.h>
     31 #include <harfbuzz/hb-ot.h>
     32 #define STB_TRUETYPE_IMPLEMENTATION
     33 #include "stb_truetype.h" /* http://nothings.org/stb/stb_truetype.h */
     34 #include "khash.h"
     35 
     36 typedef struct {
     37 	stbtt_fontinfo info;
     38 	hb_font_t *hb;
     39 	uint16_t id;
     40 	unsigned int refs;
     41 } LtkFont;
     42 
     43 /* Contains general info on glyphs that doesn't change regardless of the context */
     44 typedef struct _LtkGlyphInfo {
     45 	unsigned char *alphamap;
     46 	unsigned int w;
     47 	unsigned int h;
     48 	unsigned int xoff; /* x offset from origin to top left corner of glyph */
     49 	unsigned int yoff; /* y offset from origin to top left corner of glyph */
     50 	unsigned int refs;
     51 	/* FIXME: does refs need to be long? It could cause problems if a
     52 	program tries to cache/"keep alive" a lot of pages of text. */
     53 } LtkGlyphInfo;
     54 
     55 /* Contains glyph info specific to one run of text */
     56 typedef struct _LtkGlyph {
     57 	LtkGlyphInfo *info;
     58 	int x_offset; /* additional x offset given by harfbuzz */
     59 	int y_offset; /* additional y offset given by harfbuzz */
     60 	int x_advance;
     61 	int y_advance;
     62 	uint32_t cluster; /* index of char in original text - from harfbuzz */
     63 	struct _LtkGlyph *next;
     64 } LtkGlyph;
     65 
     66 typedef struct {
     67 	unsigned int w;
     68 	unsigned int h;
     69 	int start_x;
     70 	int start_y;
     71 	char *str;
     72 	LtkGlyph *start_glyph;
     73 } LtkTextSegment;
     74 
     75 /* Hash definitions */
     76 /* glyph id -> glyph info struct */
     77 KHASH_MAP_INIT_INT(glyphinfo, LtkGlyphInfo*)
     78 /* font path, size -> glyph cache hash */
     79 KHASH_MAP_INIT_INT(glyphcache, khash_t(glyphinfo)*)
     80 /* font path -> font id */
     81 KHASH_MAP_INIT_STR(fontid, uint16_t)
     82 /* font id -> font struct */
     83 KHASH_MAP_INIT_INT(fontstruct, LtkFont*)
     84 
     85 typedef struct LtkTextManager_ {
     86 	khash_t(fontid) *font_paths;
     87 	khash_t(fontstruct) *font_cache;
     88 	khash_t(glyphcache) *glyph_cache;
     89 	uint16_t font_id_cur;
     90 } LtkTextManager;
     91 
     92 LtkTextManager *
     93 ltk_init_text(void)
     94 {
     95 	LtkTextManager *tm = malloc(sizeof(LtkTextManager));
     96 	if (!tm) {
     97 		printf("Memory exhausted when trying to create text manager.");
     98 		exit(1);
     99 	}
    100 	tm->font_paths = kh_init(fontid);
    101 	tm->font_cache = kh_init(fontstruct);
    102 	tm->glyph_cache = kh_init(glyphcache);
    103 	tm->font_id_cur = 0;
    104 
    105 	return tm;
    106 }
    107 
    108 LtkGlyphInfo *
    109 ltk_create_glyph_info(LtkFont *font, unsigned int id, float scale)
    110 {
    111 	LtkGlyphInfo *glyph = malloc(sizeof(LtkGlyphInfo));
    112 	if (!glyph) {
    113 		printf("Out of memory!\n");
    114 		exit(1);
    115 	}
    116 	
    117 	glyph->alphamap = stbtt_GetGlyphBitmap(
    118 		&font->info, scale, scale, id, &glyph->w,
    119 		&glyph->h, &glyph->xoff, &glyph->yoff
    120 	);
    121 
    122 	return glyph;
    123 }
    124 
    125 LtkGlyphInfo *
    126 ltk_get_glyph_info(LtkFont *font, unsigned int id, float scale, khash_t(glyphinfo) *cache)
    127 {
    128 	int ret;
    129 	khint_t k;
    130 	LtkGlyphInfo *glyph;
    131 	k = kh_get(glyphinfo, cache, id);
    132 	if (k == kh_end(cache)) {
    133 		glyph = ltk_create_glyph_info(font, id, scale);
    134 		glyph->refs = 0;
    135 		/* FIXME: error checking with ret */
    136 		k = kh_put(glyphinfo, cache, id, &ret);
    137 		kh_value(cache, k) = glyph;
    138 	} else {
    139 		glyph = kh_value(cache, k);
    140 		glyph->refs++;
    141 	}
    142 
    143 	return glyph;
    144 }
    145 
    146 
    147 khint_t
    148 ltk_create_glyph_cache(LtkTextManager *tm, uint16_t font_id, uint16_t font_size)
    149 {
    150 	khash_t(glyphinfo) *cache = kh_init(glyphinfo);
    151 	int ret;
    152 	khint_t k;
    153 	/* I guess I can just ignore ret for now */
    154 	k = kh_put(glyphcache, tm->glyph_cache, font_id << 16 + font_size, &ret);
    155 	kh_value(tm->glyph_cache, k) = cache;
    156 
    157 	return k;
    158 }
    159 
    160 char *
    161 ltk_load_file(const char *path, unsigned long *len)
    162 {
    163 	FILE *f;
    164 	char *contents;
    165 	f = fopen(path, "rb");
    166 	fseek(f, 0, SEEK_END);
    167 	*len = ftell(f);
    168 	fseek(f, 0, SEEK_SET);
    169 	contents = malloc(*len + 1);
    170 	fread(contents, 1, *len, f);
    171 	contents[*len] = '\0';
    172 	fclose(f);
    173 	return contents;
    174 }
    175 
    176 unsigned long
    177 ltk_blend_pixel(Display *display, Colormap colormap, XColor fg, XColor bg, double a)
    178 {
    179         if (a >= 1.0) {
    180 		return fg.pixel;
    181         } else if (a == 0.0) {
    182 		return bg.pixel;
    183 	}
    184 
    185         XColor blended;
    186         blended.red = (int)((fg.red - bg.red) * a + bg.red);
    187         blended.green = (int)((fg.green - bg.green) * a + bg.green);
    188         blended.blue = (int)((fg.blue - bg.blue) * a + bg.blue);
    189         XAllocColor(display, colormap, &blended);
    190 
    191         return blended.pixel;
    192 }
    193 
    194 LtkFont *
    195 ltk_create_font(char *path, uint16_t id)
    196 {
    197 	long len;
    198 	LtkFont *font = malloc(sizeof(LtkFont));
    199 	if (!font) {
    200 		fprintf(stderr, "Out of memory!\n");
    201 		exit(1);
    202 	}
    203 	char *contents = ltk_load_file(path, &len);
    204 	if (!stbtt_InitFont(&font->info, contents, 0))
    205 	{
    206 		fprintf(stderr, "Failed to load font %s\n", path);
    207 		exit(1);
    208 	}
    209 	/* FIXME: make use of the destroy function (last argument to hb_blob_create - see hb-blob.cc in harfbuzz source) */
    210 	hb_blob_t *blob = hb_blob_create(contents, len, HB_MEMORY_MODE_READONLY, NULL, NULL);
    211 	hb_face_t *face = hb_face_create(blob, 0);
    212 	/* FIXME: need to use destroy function in order for the original file data to be freed? */
    213 	hb_blob_destroy(blob);
    214 	font->hb = hb_font_create(face);
    215 	hb_face_destroy(face);
    216 	hb_ot_font_set_funcs(font->hb);
    217 	font->id = id;
    218 	return font;
    219 }
    220 
    221 /* FIXME: need to figure out how exactly the whole font system is going to work, especially with default fonts, etc. */
    222 uint16_t
    223 ltk_load_font(LtkTextManager *tm, char *path)
    224 {
    225 	LtkFont *font = ltk_create_font(path, tm->font_id_cur++);
    226 	int ret;
    227 	khint_t k;
    228 	k = kh_put(fontid, tm->font_paths, path, &ret);
    229 	kh_value(tm->font_paths, k) = font->id;
    230 	k = kh_put(fontstruct, tm->font_cache, (khint_t) font->id, &ret);
    231 	kh_value(tm->font_cache, k) = font;
    232 
    233 	return font->id;
    234 }
    235 
    236 uint16_t
    237 ltk_get_font(LtkTextManager *tm, char *path)
    238 {
    239 	int ret;
    240 	khint_t k;
    241 	uint16_t id;
    242 	k = kh_get(fontid, tm->font_paths, path);
    243 	if (k == kh_end(tm->font_paths)) {
    244 		id = ltk_load_font(tm, path);
    245 	} else {
    246 		id = kh_value(tm->font_paths, k);
    247 	}
    248 
    249 	return id;
    250 }
    251 
    252 /* FIXME: could use unsigned int for fontid and size as long as there is code to check neither of them become too large
    253    -> in case I want to get rid of uint_16_t, etc. */
    254 LtkTextSegment *
    255 ltk_create_text_segment(LtkTextManager *tm, char *text, uint16_t fontid, uint16_t size)
    256 {
    257 	/* (x1*, y1*): top left corner (relative to origin and absolute)
    258 	   (x2*, y2*): bottom right corner (relative to origin and absolute) */
    259 	LtkFont *font;
    260 	khash_t(glyphinfo) *glyph_cache;
    261 	khint_t k;
    262 
    263 	k = kh_get(fontstruct, tm->font_cache, fontid);
    264 	font = kh_value(tm->font_cache, k);
    265 	/* FIXME: when should refs be increased? maybe only at the end in case
    266 	   it has to return for some other reason (out of memory, etc.) */
    267 	font->refs++;
    268 
    269 	uint32_t attr = fontid << 16 + size;
    270 	/* FIXME: turn this int ltk_get_glyph_cache */
    271 	k = kh_get(glyphcache, tm->glyph_cache, attr);
    272 	if (k == kh_end(tm->glyph_cache)) {
    273 		k = ltk_create_glyph_cache(tm, fontid, size);
    274 	}
    275 	glyph_cache = kh_value(tm->glyph_cache, k);
    276 
    277 	LtkTextSegment *ts = malloc(sizeof(LtkTextSegment));
    278 	if (!ts) {
    279 		fprintf(stderr, "Out of memory!\n");
    280 		exit(1);
    281 	}
    282 	ts->str = text;
    283 
    284 	hb_buffer_t *buf;
    285 	hb_glyph_info_t *ginf, *gi;
    286 	hb_glyph_position_t *gpos, *gp;
    287 	unsigned int text_len = 0;
    288 	int text_bytes = strlen(text);
    289 	if (text_bytes < 1) {
    290 		printf("WARNING: ltk_render_text_segment: length of text is less than 1.\n");
    291 	}
    292 
    293 	buf = hb_buffer_create();
    294 	hb_buffer_set_flags(buf, HB_BUFFER_FLAG_BOT | HB_BUFFER_FLAG_EOT);
    295 	hb_buffer_add_utf8(buf, text, text_bytes, 0, text_bytes);
    296 	hb_buffer_guess_segment_properties(buf);
    297 	hb_shape(font->hb, buf, NULL, 0);
    298 	ginf = hb_buffer_get_glyph_infos(buf, &text_len);
    299 	gpos = hb_buffer_get_glyph_positions(buf, &text_len);
    300 	float scale = stbtt_ScaleForMappingEmToPixels(&font->info, size);
    301 	LtkGlyph *last_glyph = NULL;
    302 
    303 	int x_min = INT_MAX, x_max = INT_MIN, y_min = INT_MAX, y_max = INT_MIN;
    304 	int x_abs = 0, y_abs = 0, x1_abs, y1_abs, x2_abs, y2_abs;
    305 	for (int i = 0; i < text_len; i++) {
    306 		gi = &ginf[i];
    307 		gp = &gpos[i];
    308 		LtkGlyph *glyph = malloc(sizeof(LtkGlyph));
    309 		glyph->info = ltk_get_glyph_info(font, gi->codepoint, scale, glyph_cache);
    310 		/* FIXME: round instead of just casting */
    311 		glyph->x_offset = (int)(gp->x_offset * scale);
    312 		glyph->y_offset = (int)(gp->y_offset * scale);
    313 		glyph->x_advance = (int)(gp->x_advance * scale);
    314 		glyph->y_advance = (int)(gp->y_advance * scale);
    315 		glyph->next = NULL;
    316 		if (i == 0) {
    317 			ts->start_glyph = glyph;
    318 		} else {
    319 			last_glyph->next = glyph;
    320 		}
    321 		last_glyph = glyph;
    322 
    323 		/* Calculate position in order to determine full size of text segment */
    324 		x1_abs = x_abs + glyph->info->xoff + glyph->x_offset;
    325 		y1_abs = y_abs + glyph->info->yoff - glyph->y_offset;
    326 		x2_abs = x1_abs + glyph->info->w;
    327 		y2_abs = y1_abs + glyph->info->h;
    328 		if (x1_abs < x_min) x_min = x1_abs;
    329 		if (y1_abs < y_min) y_min = y1_abs;
    330 		if (x2_abs > x_max) x_max = x2_abs;
    331 		if (y2_abs > y_max) y_max = y2_abs;
    332 		x_abs += glyph->x_advance;
    333 		y_abs -= glyph->y_advance;
    334 	}
    335 	/* FIXME: what was this supposed to do?
    336 	   I think it was supposed to be the start drawing position, but why would this not be 0?
    337 	   I'm guessing it had something to do with the fact that I need to calculate where the
    338 	   actual top left corner of the glyph is since harfbuzz gives me the origin - maybe I
    339 	   should just store that position directly in LtkGlyph? Is there any need to advance, etc.
    340 	   later on after I've positioned the glyphs? Well, I guess I'll figure that out eventually... */
    341         ts->start_x = -x_min;
    342         ts->start_y = -y_min;
    343 	ts->w = x_max - x_min;
    344 	ts->h = y_max - y_min;
    345 	return ts;
    346 }
    347 
    348 XImage *
    349 ltk_render_text_segment(
    350 	LtkTextSegment *ts,
    351 	Display *dpy,
    352 	Window window,
    353 	GC gc,
    354 	Colormap colormap,
    355 	XColor fg,
    356 	XColor bg)
    357 {
    358 	XWindowAttributes attrs;
    359 	XGetWindowAttributes(dpy, window, &attrs);
    360 	int depth = attrs.depth;
    361 	XImage *img = XCreateImage(dpy, CopyFromParent, depth, ZPixmap, 0, NULL, ts->w, ts->h, 32, 0);
    362 	img->data = calloc(img->bytes_per_line, img->height);
    363 	XInitImage(img);
    364 	int b;
    365 	for (int i = 0; i < ts->h; i++) {
    366 		b = img->bytes_per_line * i;
    367 		for (int j = 0; j < ts->w; j++) {
    368 			img->data[b++] = bg.blue / 257;
    369 			img->data[b++] = bg.green / 257;
    370 			img->data[b++] = bg.red / 257;
    371 			b++;
    372 		}
    373 	}
    374 
    375 	LtkGlyph *glyph = ts->start_glyph;
    376 	int x_cur = ts->start_x;
    377 	int y_cur = ts->start_y;
    378 	int x, y;
    379 	double a;
    380 	unsigned int out_r, out_g, out_b;
    381 	do {
    382 		x = x_cur + glyph->info->xoff + glyph->x_offset;
    383 		y = y_cur + glyph->info->yoff - glyph->y_offset;
    384 		for (int i = 0; i < glyph->info->h; i++) {
    385 			for (int j = 0; j < glyph->info->w; j++) {
    386 				b = (y + i) * img->bytes_per_line + (x + j) * 4;
    387 				a = glyph->info->alphamap[i * glyph->info->w + j] / 255.0;
    388 				img->data[b] = (fg.blue * a + (1 - a) * (uint16_t)img->data[b] * 257) / 257;
    389 				img->data[b + 1] = (fg.green * a + (1 - a) * (uint16_t)img->data[b + 1] * 257) / 257;
    390 				img->data[b + 2] = (fg.red * a + (1 - a) * (uint16_t)img->data[b + 2] * 257) / 257;
    391 
    392 				/*
    393 				out_r = (fg.red / 255) * a1 + (uint8_t)img->data[b + 2] * (255 - a1);
    394 				out_g = (fg.green / 255) * a1 + (uint8_t)img->data[b + 1] * (255 - a1);
    395 				out_b = (fg.blue / 255) * a1 + (uint8_t)img->data[b] * (255 - a1);
    396 				out_r = (out_r + 1 + (out_r >> 8)) >> 8;
    397 				out_g = (out_g + 1 + (out_g >> 8)) >> 8;
    398 				out_b = (out_b + 1 + (out_b >> 8)) >> 8;
    399 				*/
    400 			}
    401 		}
    402 		x_cur += glyph->x_advance;
    403 		y_cur -= glyph->y_advance;
    404 	} while (glyph = glyph->next);
    405 
    406 	return img;
    407 }
    408 
    409 int main(int argc, char *argv[])
    410 {
    411     Display *display;
    412     int screen;
    413     Window window;
    414     GC gc;
    415 
    416     unsigned long black, white;
    417     Colormap colormap;
    418     display = XOpenDisplay((char *)0);
    419     screen = DefaultScreen(display);
    420     colormap = DefaultColormap(display, screen);
    421     black = BlackPixel(display, screen);
    422     white = WhitePixel(display, screen);
    423     XSetWindowAttributes wattr;
    424     wattr.background_pixel = white;
    425     wattr.border_pixel = black;
    426     wattr.colormap = colormap;
    427     window = XCreateWindow(display, DefaultRootWindow(display), 0, 0, 1366, 512, 0, 24, CopyFromParent, CopyFromParent, CWBackPixel | CWBorderPixel, &wattr);
    428     XSetStandardProperties(display, window, "Random Window", NULL, None, NULL, 0, NULL);
    429     XSelectInput(display, window, ExposureMask|ButtonPressMask|KeyPressMask);
    430     gc = XCreateGC(display, window, 0, 0);
    431     XSetBackground(display, gc, black);
    432     XSetForeground(display, gc, black);
    433     XClearWindow(display, window);
    434     XMapRaised(display, window);
    435     XColor c1, c2;
    436     XParseColor(display, colormap, "#FFFFFF", &c1);
    437     XParseColor(display, colormap, "#FF0000", &c2);
    438     XAllocColor(display, colormap, &c1);
    439     XAllocColor(display, colormap, &c2);
    440 
    441     LtkTextManager *tm = ltk_init_text();
    442     uint16_t font_id = ltk_get_font(tm, "NotoNastaliqUrdu-Regular.ttf");
    443     uint16_t font_size = 100;
    444     LtkTextSegment *text = ltk_create_text_segment(tm, "ہمارے بارے میں", font_id, font_size);
    445     //Pixmap pix = ltk_render_text_segment(text, display, window, gc, colormap, c1, c2);
    446     XImage *img = ltk_render_text_segment(text, display, window, gc, colormap, c1, c2);
    447     //XCopyArea(display, pix, window, gc, 0, 0, text->w, text->h, 0, 0);
    448     XPutImage(display, window, gc, img, 0, 0, 0, 0, text->w, text->h);
    449 
    450     XEvent event;
    451     KeySym key;
    452     char txt[255];
    453 
    454     while(1)
    455     {
    456         XNextEvent(display, &event);
    457         if (event.type == KeyPress && XLookupString(&event.xkey, txt, 255, &key, 0) == 1)
    458         {
    459             //XCopyArea(display, pix, window, gc, 0, 0, text->w, text->h, 0, 0);
    460 	    XPutImage(display, window, gc, img, 0, 0, 0, 0, text->w, text->h);
    461             if (txt[0] == 'q')
    462             {
    463                 XFreeGC(display, gc);
    464                 XFreeColormap(display, colormap);
    465                 XDestroyWindow(display, window);
    466                 XCloseDisplay(display);
    467                 exit(0);
    468             }
    469         }
    470     }
    471     
    472     return 0;
    473 }