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 }