text-hb.c (13025B)
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 "text-hb.h" 38 #include "ltk.h" 39 40 extern Ltk *ltk_global; 41 42 /* FIXME: allow to either use fribidi for basic shaping and don't use harfbuzz then, 43 or just use harfbuzz (then fribidi doesn't need to do any shaping) */ 44 LtkTextLine * 45 ltk_create_text_line(LtkTextManager *tm, char *text, uint16_t fontid, uint16_t size) 46 { 47 /* NOTE: This doesn't actually take fontid into account right now - should it? */ 48 LtkTextLine *tl = malloc(sizeof(LtkTextLine)); 49 tl->start_segment = NULL; 50 LtkTextSegment *cur_ts = NULL; 51 LtkTextSegment *new_ts = NULL; 52 uint16_t cur_font_id = fontid; 53 int k; 54 LtkFont *font; 55 56 unsigned int ulen = u8_strlen(text); 57 FriBidiChar *log_str = malloc(sizeof(FriBidiChar) * ulen); 58 size_t inc = 0; 59 for (int i = 0; i < ulen; i++) { 60 log_str[i] = u8_nextmemchar(text, &inc); 61 } 62 FriBidiLevel *levels = malloc(ulen * sizeof(FriBidiLevel)); 63 int *l2v = malloc(ulen * sizeof(int)); 64 int *v2l = malloc(ulen * sizeof(int)); 65 FriBidiCharType pbase_dir = FRIBIDI_TYPE_ON; 66 FriBidiChar *vis_str = malloc(sizeof(FriBidiChar) * ulen); 67 ulen = fribidi_charset_to_unicode(FRIBIDI_CHAR_SET_UTF8, text, strlen(text), log_str); 68 fribidi_log2vis(log_str, ulen, &pbase_dir, vis_str, l2v, v2l, levels); 69 70 hb_unicode_funcs_t *ufuncs = hb_unicode_funcs_get_default(); 71 hb_script_t cur_script = hb_unicode_script(ufuncs, vis_str[0]); 72 hb_script_t last_script = cur_script; 73 size_t pos = 0; 74 size_t last_pos = 0; 75 size_t start_pos = 0; 76 uint32_t ch; 77 78 for (int p = 0; p <= ulen; p++) { 79 cur_script = hb_unicode_script(ufuncs, vis_str[p]); 80 if (p == ulen || 81 (last_script != cur_script && 82 cur_script != HB_SCRIPT_INHERITED && 83 cur_script != HB_SCRIPT_COMMON)) { 84 FcPattern *pat = FcPatternDuplicate(tm->fcpattern); 85 FcPattern *match; 86 FcResult result; 87 FcPatternAddBool(pat, FC_SCALABLE, 1); 88 FcConfigSubstitute(NULL, pat, FcMatchPattern); 89 FcDefaultSubstitute(pat); 90 FcCharSet *cs = FcCharSetCreate(); 91 for (int i = start_pos; i < p; i++) { 92 FcCharSetAddChar(cs, vis_str[i]); 93 } 94 FcPatternAddCharSet(pat, FC_CHARSET, cs); 95 match = FcFontMatch(NULL, pat, &result); 96 char *file; 97 FcPatternGetString(match, FC_FILE, 0, &file); 98 cur_font_id = ltk_get_font(tm, file); 99 k = kh_get(fontstruct, tm->font_cache, cur_font_id); 100 font = kh_value(tm->font_cache, k); 101 FcPatternDestroy(match); 102 FcPatternDestroy(pat); 103 // handle case that this is the last character 104 if (p == ulen) { 105 last_script = cur_script; 106 } 107 /* FIXME: There should be better handling for cases 108 where an error occurs while creating the segment */ 109 new_ts = ltk_create_text_segment( 110 tm, vis_str + start_pos, 111 p - start_pos, cur_font_id, 112 size, last_script 113 ); 114 if (!new_ts) continue; 115 new_ts->next = NULL; 116 if (!tl->start_segment) tl->start_segment = new_ts; 117 if (cur_ts) cur_ts->next = new_ts; 118 cur_ts = new_ts; 119 120 start_pos = p; 121 last_script = cur_script; 122 } 123 } 124 125 free(vis_str); 126 free(log_str); 127 free(levels); 128 free(l2v); 129 free(v2l); 130 131 /* calculate width of text line 132 NOTE: doesn't work with mixed horizontal and vertical text */ 133 LtkTextSegment *ts = tl->start_segment; 134 int is_hor = HB_DIRECTION_IS_HORIZONTAL(ts->dir); 135 tl->y_max = tl->x_max = INT_MIN; 136 tl->y_min = tl->x_min = INT_MAX; 137 tl->w = tl->h = 0; 138 while (ts) { 139 if (HB_DIRECTION_IS_HORIZONTAL(ts->dir) != is_hor) { 140 (void)fprintf(stderr, "WARNING: mixed horizontal/vertical text is not supported; ignoring\n"); 141 continue; 142 } 143 if (is_hor) { 144 if (tl->y_max < ts->y_max) { 145 tl->y_max = ts->y_max; 146 } 147 if (tl->y_min > ts->y_min) { 148 tl->y_min = ts->y_min; 149 } 150 tl->w += ts->w; 151 } else { 152 if (tl->x_max < ts->x_max) { 153 tl->x_max = ts->x_max; 154 } 155 if (tl->x_min > ts->x_min) { 156 tl->x_min = ts->x_min; 157 } 158 tl->h += ts->h; 159 } 160 ts = ts->next; 161 } 162 if (is_hor) { 163 tl->h = tl->y_max - tl->y_min; 164 } else { 165 tl->w = tl->x_max - tl->x_min; 166 } 167 168 return tl; 169 } 170 171 void 172 ltk_destroy_text_line(LtkTextLine *tl) { 173 LtkTextSegment *last_ts; 174 LtkTextSegment *cur_ts = tl->start_segment; 175 while (cur_ts) { 176 last_ts = cur_ts; 177 cur_ts = cur_ts->next; 178 ltk_destroy_text_segment(last_ts); 179 } 180 } 181 182 /* FIXME: could use unsigned int for fontid and size as long as there is code to check neither of them become too large 183 -> in case I want to get rid of uint_16_t, etc. */ 184 LtkTextSegment * 185 ltk_create_text_segment(LtkTextManager *tm, uint32_t *text, unsigned int len, uint16_t fontid, uint16_t size, hb_script_t script) 186 { 187 /* (x1*, y1*): top left corner (relative to origin and absolute) 188 (x2*, y2*): bottom right corner (relative to origin and absolute) */ 189 LtkFont *font; 190 khash_t(glyphinfo) *glyph_cache; 191 khint_t k; 192 193 k = kh_get(fontstruct, tm->font_cache, fontid); 194 font = kh_value(tm->font_cache, k); 195 196 uint32_t attr = fontid << 16 + size; 197 /* FIXME: turn this into ltk_get_glyph_cache */ 198 k = kh_get(glyphcache, tm->glyph_cache, attr); 199 if (k == kh_end(tm->glyph_cache)) { 200 k = ltk_create_glyph_cache(tm, fontid, size); 201 } 202 glyph_cache = kh_value(tm->glyph_cache, k); 203 204 LtkTextSegment *ts = malloc(sizeof(LtkTextSegment)); 205 if (!ts) { 206 (void)fprintf(stderr, "Out of memory!\n"); 207 exit(1); 208 } 209 ts->str = malloc(sizeof(uint32_t) * (len + 1)); 210 memcpy(ts->str, text, len * sizeof(uint32_t)); 211 ts->str[len] = '\0'; 212 ts->font_id = fontid; 213 ts->font_size = size; 214 215 hb_buffer_t *buf; 216 hb_glyph_info_t *ginf, *gi; 217 hb_glyph_position_t *gpos, *gp; 218 unsigned int text_len = 0; 219 if (len < 1) { 220 (void)printf("WARNING: ltk_render_text_segment: length of text is less than 1.\n"); 221 return NULL; 222 } 223 224 buf = hb_buffer_create(); 225 hb_direction_t dir = hb_script_get_horizontal_direction(script); 226 hb_buffer_set_direction(buf, dir); 227 hb_buffer_set_script(buf, script); 228 hb_buffer_add_utf32(buf, ts->str, len, 0, len); 229 /* According to https://harfbuzz.github.io/the-distinction-between-levels-0-and-1.html 230 * this should be level 1 clustering instead of level 0 */ 231 hb_buffer_set_cluster_level(buf, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS); 232 hb_shape(font->hb, buf, NULL, 0); 233 ts->dir = hb_buffer_get_direction(buf); 234 ginf = hb_buffer_get_glyph_infos(buf, &text_len); 235 gpos = hb_buffer_get_glyph_positions(buf, &text_len); 236 float scale = stbtt_ScaleForMappingEmToPixels(&font->info, size); 237 LtkGlyph *last_glyph = NULL; 238 239 /* FIXME: read https://harfbuzz.github.io/harfbuzz-hb-buffer.html#hb-glyph-position-t-struct */ 240 241 int x_min = INT_MAX, x_max = INT_MIN, y_min = INT_MAX, y_max = INT_MIN; 242 int x_abs = 0, y_abs = 0, x1_abs, y1_abs, x2_abs, y2_abs; 243 /* magic, do not touch */ 244 LtkGlyph *glyph; 245 for (int i = 0; i < text_len; i++) { 246 gi = &ginf[i]; 247 gp = &gpos[i]; 248 glyph = malloc(sizeof(LtkGlyph)); 249 glyph->info = ltk_get_glyph_info(font, gi->codepoint, scale, glyph_cache); 250 glyph->info->refs++; 251 /* FIXME: round instead of just casting */ 252 glyph->x_offset = (int)(gp->x_offset * scale); 253 glyph->y_offset = (int)(gp->y_offset * scale); 254 glyph->x_advance = (int)(gp->x_advance * scale); 255 glyph->y_advance = (int)(gp->y_advance * scale); 256 glyph->next = NULL; 257 if (i == 0) { 258 ts->start_glyph = glyph; 259 } else { 260 last_glyph->next = glyph; 261 } 262 last_glyph = glyph; 263 264 /* Calculate position in order to determine full size of text segment */ 265 x1_abs = x_abs + glyph->info->xoff + glyph->x_offset; 266 y1_abs = y_abs + glyph->info->yoff - glyph->y_offset; 267 /* Okay, wait, so should I check if the script is horizontal, and then add 268 x_advance instead of glyph->info->w? It seems that the glyph width is 269 usually smaller than x_advance, and spaces etc. are completely lost 270 because their glyph width is 0. I have to distinguish between horizontal 271 and vertical scripts, though because to calculate the maximum y position 272 for horizontal scripts, I still need to use the glyph height since 273 y_advance doesn't really do much there. I dunno, at least *something* 274 works now... */ 275 /* FIXME: THIS PROBABLY DOESN'T REALLY WORK */ 276 /* Wait, why do I calculate abs here and don't use it while rendering? */ 277 /* Oh, it can't be calculated after figuring out the max and min points 278 of the entire line */ 279 if (HB_DIRECTION_IS_HORIZONTAL(dir)) { 280 x2_abs = x1_abs + glyph->x_advance; 281 y2_abs = y1_abs + glyph->info->h; 282 } else { 283 x2_abs = x1_abs + glyph->info->w; 284 y2_abs = y1_abs - glyph->y_advance; 285 } 286 glyph->x_abs = x1_abs; 287 glyph->y_abs = y1_abs; 288 if (x1_abs < x_min) x_min = x1_abs; 289 if (y1_abs < y_min) y_min = y1_abs; 290 if (x2_abs > x_max) x_max = x2_abs; 291 if (y2_abs > y_max) y_max = y2_abs; 292 x_abs += glyph->x_advance; 293 y_abs -= glyph->y_advance; 294 } 295 ts->start_x = -x_min; 296 ts->start_y = -y_min; 297 ts->w = x_max - x_min; 298 ts->h = y_max - y_min; 299 ts->x_min = x_min; 300 ts->y_min = y_min; 301 ts->x_max = x_max; 302 ts->y_max = y_max; 303 304 font->refs++; 305 /* FIXME: destroy hb_buffer */ 306 307 return ts; 308 } 309 310 void 311 ltk_destroy_text_segment(LtkTextSegment *ts) 312 { 313 LtkGlyph *glyph, *next_glyph; 314 khash_t(glyphinfo) *gcache; 315 LtkFont *font; 316 int k; 317 glyph = ts->start_glyph; 318 k = kh_get(glyphinfo, ltk_global->tm->glyph_cache, ts->font_id << 16 + ts->font_size); 319 gcache = kh_value(ltk_global->tm->glyph_cache, k); 320 do { 321 next_glyph = glyph->next; 322 ltk_destroy_glyph(glyph, gcache); 323 } while (glyph = next_glyph); 324 k = kh_get(fontstruct, ltk_global->tm->font_cache, ts->font_id); 325 font = kh_value(ltk_global->tm->font_cache, k); 326 if (--font->refs < 1) { 327 kh_del(fontstruct, ltk_global->tm->font_cache, k); 328 ltk_destroy_font(font); 329 } 330 free(ts->str); 331 free(ts); 332 } 333 334 /* based on http://codemadness.org/git/dwm-font/file/drw.c.html#l315 */ 335 XImage * 336 ltk_render_text_line( 337 LtkTextLine *tl, 338 Display *dpy, 339 Window window, 340 GC gc, 341 Colormap colormap, 342 XColor fg, 343 XColor bg) 344 { 345 XWindowAttributes attrs; 346 XGetWindowAttributes(dpy, window, &attrs); 347 int depth = attrs.depth; 348 XImage *img = XCreateImage(dpy, CopyFromParent, depth, ZPixmap, 0, NULL, tl->w, tl->h, 32, 0); 349 img->data = calloc(img->bytes_per_line, img->height); 350 XInitImage(img); 351 int b; 352 for (int i = 0; i < tl->h; i++) { 353 b = img->bytes_per_line * i; 354 for (int j = 0; j < tl->w; j++) { 355 img->data[b++] = bg.blue / 257; 356 img->data[b++] = bg.green / 257; 357 img->data[b++] = bg.red / 257; 358 b++; 359 } 360 } 361 362 LtkTextSegment *ts = tl->start_segment; 363 int x = 0; 364 int y = 0; 365 int is_hor = HB_DIRECTION_IS_HORIZONTAL(ts->dir); 366 do { 367 if (is_hor) { 368 y = tl->h - tl->y_max; 369 ltk_render_text_segment(ts, x + ts->start_x, y, img, fg); 370 x += ts->w; 371 } else { 372 x = tl->w - tl->x_max; 373 ltk_render_text_segment(ts, x, y + ts->start_y, img, fg); 374 y += ts->h; 375 } 376 } while (ts = ts->next); 377 378 return img; 379 } 380 381 void 382 ltk_render_text_segment( 383 LtkTextSegment *ts, 384 unsigned int start_x, 385 unsigned int start_y, 386 XImage *img, 387 XColor fg) 388 { 389 LtkGlyph *glyph = ts->start_glyph; 390 int x_cur = start_x; 391 int y_cur = start_y; 392 int x, y; 393 double a; 394 int b; 395 do { 396 x = x_cur + glyph->info->xoff + glyph->x_offset; 397 y = y_cur + glyph->info->yoff - glyph->y_offset; 398 for (int i = 0; i < glyph->info->h; i++) { 399 for (int j = 0; j < glyph->info->w; j++) { 400 b = (y + i) * img->bytes_per_line + (x + j) * 4; 401 a = glyph->info->alphamap[i * glyph->info->w + j] / 255.0; 402 img->data[b] = (fg.blue * a + (1 - a) * (uint16_t)img->data[b] * 257) / 257; 403 img->data[b + 1] = (fg.green * a + (1 - a) * (uint16_t)img->data[b + 1] * 257) / 257; 404 img->data[b + 2] = (fg.red * a + (1 - a) * (uint16_t)img->data[b + 2] * 257) / 257; 405 } 406 } 407 x_cur += glyph->x_advance; 408 y_cur -= glyph->y_advance; 409 } while (glyph = glyph->next); 410 }