ltkx

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

text-hb.new.c (11742B)


      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 /* TODO: possibly "glyph manager" - only render glyph once and keep info in hash?
     37    -> would be difficult because of different sizes - would need to keep track of all that.
     38    -> reference counter - delete glyph from cache if not used anymore - good when there are many ligatures */
     39 
     40 /* Font manager: hash for font path -> font id
     41                  hash for font id -> ltk font struct */
     42 
     43 /* glyph id -> glyph info struct */
     44 KHASH_MAP_INIT_INT(glyphinfo, LtkGlyphInfo*)
     45 /* font path, size -> glyph cache hash
     46 KHASH_MAP_INIT_INT(glyphcache, khash_t(glyphinfo))
     47 /* font path -> font id */
     48 KHASH_MAP_INIT_STR(fontid, uint16_t)
     49 /* font id -> font struct */
     50 KHASH_MAP_INIT_INT(fontstruct, LtkFont*)
     51 
     52 typedef struct LtkTextManager_ {
     53 	khash_t(fontid) *font_paths;
     54 	khash_t(fontstruct) *font_cache;
     55 	khash_t(glyphcache) *glyph_cache;
     56 	uint16_t font_id_cur;
     57 } LtkTextManager;
     58 
     59 typedef struct {
     60 	stbtt_fontinfo info;
     61 	hb_font_t *hb;
     62 	uint16_t id;
     63 } LtkFont;
     64 
     65 LtkTextManager *
     66 ltk_init_text(void)
     67 {
     68 	LtkTextManager *m = malloc(sizeof LtkTextManager);
     69 	if (!m) ltk_fatal("Memory exhausted when trying to create text manager.");
     70 	m->font_paths = kh_init(fontid);
     71 	m->font_cache = kh_init(fontstruct);
     72 	m->glyph_cache = kh_init(glyphcache);
     73 	m->font_id_cur = 0;
     74 	return m;
     75 }
     76 
     77 /* Contains general info on glyphs that doesn't change regardless of the context */
     78 typedef struct _LtkGlyphInfo {
     79 	unsigned char *alphamap;
     80 	unsigned int w;
     81 	unsigned int h;
     82 	unsigned int xoff; /* x offset from origin to top left corner of glyph */
     83 	unsigned int yoff; /* y offset from origin to top left corner of glyph */
     84 	unsigned int refs;
     85 	/* FIXME: does refs need to be long? It could cause problems if a
     86 	program tries to cache/"keep alive" a lot of pages of text. */
     87 } LtkGlyphInfo;
     88 
     89 LtkGlyphInfo *
     90 ltk_create_glyph_info(LtkFont *font, unsigned int id, float scale)
     91 {
     92 	LtkGlyphInfo *glyph = malloc(sizeof(LtkGlyphInfo));
     93 	if (!glyph) {
     94 		printf("Out of memory!\n");
     95 		exit(1);
     96 	}
     97 	
     98 	glyph->alphamap = stbtt_GetGlyphBitmap(
     99 		&font->info, scale, scale, id, &glyph->w,
    100 		&glyph->h, &glyph->xoff, &glyph->yoff
    101 	);
    102 	return glyph;
    103 }
    104 
    105 LtkGlyphInfo *
    106 ltk_get_glyph_info(LtkFont *font, unsigned int id, float scale, khash_t(glyphinfo) *cache)
    107 {
    108 	int ret;
    109 	khint_t k;
    110 	LtkGlyphInfo *glyph;
    111 	k = kh_get(glyphinfo, cache, id);
    112 	if (k == kh_end(cache)) {
    113 		glyph = ltk_create_glyph_info(font, id, scale);
    114 		glyph->refs = 0;
    115 		/* FIXME: error checking with ret */
    116 		k = kh_put(glyphinfo, cache, glyph, &ret);
    117 		kh_value(cache, k) = glyph;
    118 	} else {
    119 		glyph = kh_value(cache, k);
    120 		glyph->refs++;
    121 	}
    122 
    123 	return glyph;
    124 }
    125 
    126 
    127 void
    128 ltk_create_glyph_cache(LtkTextManager *m, uint16_t font_id, uint16_t font_size)
    129 {
    130 	khash_t(glyphinfo) *cache = kh_init(glyphinfo);
    131 	int ret;
    132 	khint_t k;
    133 	/* I guess I can just ignore ret for now */
    134 	k = kh_put(glyphcache, m->glyph_cache, font_id << 16 + font_size, &ret);
    135 	kh_value(m->glyph_cache, k) = cache;
    136 }
    137 
    138 char *
    139 ltk_load_file(const char *path, unsigned long *len)
    140 {
    141 	FILE *f;
    142 	char *contents;
    143 	f = fopen(path, "rb");
    144 	fseek(f, 0, SEEK_END);
    145 	*len = ftell(f);
    146 	fseek(f, 0, SEEK_SET);
    147 	contents = malloc(*len + 1);
    148 	fread(contents, 1, *len, f);
    149 	contents[*len] = '\0';
    150 	fclose(f);
    151 	return contents;
    152 }
    153 
    154 unsigned long
    155 ltk_blend_pixel(Display *display, Colormap colormap, XColor fg, XColor bg, double a)
    156 {
    157         if (a >= 1.0) {
    158 		return fg.pixel;
    159         } else if (a == 0.0) {
    160 		return bg.pixel;
    161 	}
    162 
    163         XColor blended;
    164         blended.red = (int)((fg.red - bg.red) * a + bg.red);
    165         blended.green = (int)((fg.green - bg.green) * a + bg.green);
    166         blended.blue = (int)((fg.blue - bg.blue) * a + bg.blue);
    167         XAllocColor(display, colormap, &blended);
    168 
    169         return blended.pixel;
    170 }
    171 
    172 /* Contains glyph info specific to one run of text */
    173 typedef struct _LtkGlyph {
    174 	LtkGlyphInfo *glyph_info;
    175 	unsigned int x_offset; /* additional x offset given by harfbuzz */
    176 	unsigned int y_offset; /* additional y offset given by harfbuzz */
    177 	uint32_t cluster; /* index of char in original text - from harfbuzz */
    178 	struct _LtkGlyph *next;
    179 } LtkGlyph;
    180 
    181 typedef struct {
    182 	unsigned int width;
    183 	unsigned int height;
    184 	char *str;
    185 	LtkGlyph *start_glyph;
    186 } LtkTextSegment;
    187 
    188 LtkFont *
    189 ltk_create_font(char *path, unsigned int id)
    190 {
    191 	long len;
    192 	LtkFont *font = malloc(sizeof(LtkFont));
    193 	if (!font) {
    194 		fprintf(stderr, "Out of memory!\n");
    195 		exit(1);
    196 	}
    197 	char *contents = ltk_load_file(path, &len);
    198 	if (!stbtt_InitFont(&font->font_info, contents, 0))
    199 	{
    200 		fprintf(stderr, "Failed to load font %s\n", path);
    201 		exit(1);
    202 	}
    203 	hb_blob_t *blob = hb_blob_create(contents, len, HB_MEMORY_MODE_READONLY, NULL, NULL);
    204 	hb_face_t *face = hb_face_create(blob, 0);
    205 	hb_blob_destroy(blob);
    206 	font->hb = hb_font_create(face);
    207 	hb_face_destroy(face);
    208 	hb_ot_font_set_funcs(font->hb);
    209 	font->id = id;
    210 	return font;
    211 }
    212 
    213 unsigned char *
    214 ltk_render_text_bitmap(
    215 	uint8_t *text,
    216 	LtkFont *font,
    217 	int size,
    218 	int *width,
    219 	int *height)
    220 {
    221 	/* (x1*, y1*): top left corner (relative to origin and absolute)
    222 	   (x2*, y2*): bottom right corner (relative to origin and absolute) */
    223 	int x1, x2, y1, y2, x1_abs, x2_abs, y1_abs, y2_abs, x_abs = 0, y_abs = 0;
    224 	int x_min = INT_MAX, x_max = INT_MIN, y_min = INT_MAX, y_max = INT_MIN;
    225 	int char_w, char_h, x_off, y_off;
    226 
    227 	int byte_offset;
    228 	int alpha;
    229 	/* FIXME: Change to uint8_t? */
    230 	unsigned char *bitmap;
    231 	unsigned char *char_bitmap;
    232 	hb_buffer_t *buf;
    233 	hb_glyph_info_t *ginf, *gi;
    234 	hb_glyph_position_t *gpos, *gp;
    235 	unsigned int text_len = 0;
    236 	int text_bytes = strlen(text);
    237 	if (text_bytes < 1) {
    238 		printf("WARNING: ltk_render_text: length of text is less than 1.\n");
    239 		return "";
    240 	}
    241 
    242 	buf = hb_buffer_create();
    243 	hb_buffer_set_flags(buf, HB_BUFFER_FLAG_BOT | HB_BUFFER_FLAG_EOT);
    244 	hb_buffer_add_utf8(buf, text, text_bytes, 0, text_bytes);
    245 	hb_buffer_guess_segment_properties(buf);
    246 	hb_shape(font->font, buf, NULL, 0);
    247 	ginf = hb_buffer_get_glyph_infos(buf, &text_len);
    248 	gpos = hb_buffer_get_glyph_positions(buf, &text_len);
    249 	float scale = stbtt_ScaleForMappingEmToPixels(&font->font_info, size);
    250 
    251 	/* Calculate size of bitmap */
    252 	for (int i = 0; i < text_len; i++) {
    253 		gi = &ginf[i];
    254 		gp = &gpos[i];
    255 		stbtt_GetGlyphBitmapBox(&font->font_info, gi->codepoint, scale, scale, &x1, &y1, &x2, &y2);
    256 		x1_abs = (int)(x_abs + x1 + gp->x_offset * scale);
    257 		y1_abs = (int)(y_abs + y1 - gp->y_offset * scale);
    258 		x2_abs = x1_abs + (x2 - x1);
    259 		y2_abs = y1_abs + (y2 - y1);
    260 		if (x1_abs < x_min) x_min = x1_abs;
    261 		if (y1_abs < y_min) y_min = y1_abs;
    262 		if (x2_abs > x_max) x_max = x2_abs;
    263 		if (y2_abs > y_max) y_max = y2_abs;
    264 		x_abs += (gp->x_advance * scale);
    265 		y_abs -= (gp->y_advance * scale);
    266 	}
    267 	x_abs = -x_min;
    268 	y_abs = -y_min;
    269 	*width = x_max - x_min;
    270 	*height = y_max - y_min;
    271 	/* FIXME: calloc checks for integer overflow, right? */
    272 	/* FIXME: check if null returned */
    273 	bitmap = calloc(*width * *height, sizeof(char));
    274 	if (!bitmap) {
    275 		fprintf(stderr, "Can't allocate memory for bitmap!\n");
    276 		exit(1);
    277 	}
    278 	for (int i = 0; i < text_len; i++) {
    279 		gi = &ginf[i];
    280 		gp = &gpos[i];
    281 		stbtt_GetGlyphBitmapBox(&font->font_info, gi->codepoint, scale, scale, &x1, &y1, &x2, &y2);
    282 		char_bitmap = stbtt_GetGlyphBitmap(&font->font_info, scale, scale, gi->codepoint, &char_w, &char_h, &x_off, &y_off);
    283 
    284 		x1_abs = (int)(x_abs + x1 + gp->x_offset * scale);
    285 		y1_abs = (int)(y_abs + y1 - gp->y_offset * scale);
    286 		for (int k = 0; k < char_h; k++)
    287 		{
    288 			for (int j = 0; j < char_w; j++)
    289 			{
    290 				byte_offset = (y1_abs + k) * *width + x1_abs + j;
    291 				alpha = bitmap[byte_offset] + char_bitmap[k * char_w + j];
    292 				/* Cap at 255 so char doesn't overflow */
    293 				bitmap[byte_offset] = alpha > 255 ? 255 : alpha;
    294 			}
    295 		}
    296 		free(char_bitmap);
    297 
    298 		x_abs += gp->x_advance * scale;
    299 		y_abs -= gp->y_advance * scale;
    300 	}
    301 	return bitmap;
    302 }
    303 
    304 Pixmap
    305 ltk_render_text(
    306 	Display *dpy,
    307 	Window window,
    308 	GC gc,
    309 	XColor fg,
    310 	XColor bg,
    311 	Colormap colormap,
    312 	unsigned char *bitmap,
    313 	int width,
    314 	int height)
    315 {
    316 	XWindowAttributes attrs;
    317 	XGetWindowAttributes(dpy, window, &attrs);
    318 	int depth = attrs.depth;
    319 	Pixmap pix = XCreatePixmap(dpy, window, width, height, depth);
    320 	XSetForeground(dpy, gc, bg.pixel);
    321 	for (int i = 0; i < height; i++) {
    322 		for (int j = 0; j < width; j++) {
    323 			XSetForeground(dpy, gc, ltk_blend_pixel(dpy, colormap, fg, bg, bitmap[i * width + j] / 255.0));
    324 			XDrawPoint(dpy, pix, gc, j, i);
    325 		}
    326 	}
    327 	return pix;
    328 }
    329 
    330 int main(int argc, char *argv[])
    331 {
    332     Display *display;
    333     int screen;
    334     Window window;
    335     GC gc;
    336 
    337     unsigned long black, white;
    338     Colormap colormap;
    339     display = XOpenDisplay((char *)0);
    340     screen = DefaultScreen(display);
    341     colormap = DefaultColormap(display, screen);
    342     black = BlackPixel(display, screen);
    343     white = WhitePixel(display, screen);
    344     window = XCreateSimpleWindow(display, DefaultRootWindow(display), 0, 0, 1366, 512, 0, black, white);
    345     XSetStandardProperties(display, window, "Random Window", NULL, None, NULL, 0, NULL);
    346     XSelectInput(display, window, ExposureMask|ButtonPressMask|KeyPressMask);
    347     gc = XCreateGC(display, window, 0, 0);
    348     XSetBackground(display, gc, black);
    349     XSetForeground(display, gc, black);
    350     XClearWindow(display, window);
    351     XMapRaised(display, window);
    352     XColor c1, c2;
    353     XParseColor(display, colormap, "#FFFFFF", &c1);
    354     XParseColor(display, colormap, "#FF0000", &c2);
    355     XAllocColor(display, colormap, &c1);
    356     XAllocColor(display, colormap, &c2);
    357 
    358     LtkFont *font = ltk_load_font("NotoNastaliqUrdu-Regular.ttf");
    359     int w, h;
    360     unsigned char *bitmap = ltk_render_text_bitmap("ہمارے بارے میں", font, 256, &w, &h);
    361     Pixmap pix = ltk_render_text(display, window, gc, c1, c2, colormap, bitmap, w, h);
    362     XCopyArea(display, pix, window, gc, 0, 0, w, h, 0, 0);
    363 
    364     XEvent event;
    365     KeySym key;
    366     char text[255];
    367 
    368     while(1)
    369     {
    370         XNextEvent(display, &event);
    371         if (event.type == KeyPress && XLookupString(&event.xkey, text, 255, &key, 0) == 1)
    372         {
    373             XCopyArea(display, pix, window, gc, 0, 0, w, h, 0, 0);
    374             if (text[0] == 'q')
    375             {
    376                 XFreeGC(display, gc);
    377                 XFreeColormap(display, colormap);
    378                 XDestroyWindow(display, window);
    379                 XCloseDisplay(display);
    380                 exit(0);
    381             }
    382         }
    383     }
    384     
    385     return 0;
    386 }