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