text_buffer.c (30537B)
1 /* 2 * This file is part of the Lumidify ToolKit (LTK) 3 * Copyright (c) 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 "array.h" 38 #include "text_buffer.h" 39 #include "ltk.h" 40 41 LTK_ARRAY_INIT_IMPL(uint32, uint32_t) 42 LTK_ARRAY_INIT_IMPL(script, hb_script_t) 43 LTK_ARRAY_INIT_IMPL(level, FriBidiLevel) 44 LTK_ARRAY_INIT_IMPL(int, int) 45 LTK_ARRAY_INIT_IMPL(line, struct ltk_soft_line *) 46 47 void 48 ltk_soft_line_destroy(struct ltk_soft_line *sl) { 49 if (sl->glyph_pos) 50 ltk_array_destroy_int(sl->glyph_pos); 51 if (sl->glyph_clusters) 52 ltk_array_destroy_uint32(sl->glyph_clusters); 53 free(sl); 54 } 55 56 struct ltk_soft_line * 57 ltk_soft_line_create(void) { 58 struct ltk_soft_line *sl = malloc(sizeof(struct ltk_soft_line)); 59 if (!sl) { 60 (void)fprintf(stderr, "Error creating soft line\n"); 61 exit(1); 62 } 63 sl->glyph_pos = NULL; 64 sl->glyph_clusters = NULL; 65 return sl; 66 } 67 68 static void 69 ltk_text_line_cleanup_soft_lines(struct ltk_array_line *soft_lines, int old_len) { 70 /* remove old soft lines that aren't needed anymore */ 71 for (int i = soft_lines->len; i < old_len; i++) { 72 ltk_soft_line_destroy(soft_lines->buf[i]); 73 } 74 ltk_array_resize_line(soft_lines, soft_lines->len); 75 } 76 77 void 78 ltk_text_line_wrap(struct ltk_text_line *tl, int max_width) { 79 tl->w_wrapped = max_width; 80 int old_len = tl->soft_lines->len; 81 tl->soft_lines->len = 0; 82 int par_is_rtl = tl->dir == HB_DIRECTION_RTL; 83 84 struct ltk_text_run *cur = par_is_rtl ? tl->last_run : tl->first_run; 85 LtkGlyph *glyph; 86 int cur_index = 0; 87 struct ltk_soft_line *sl = old_len > cur_index ? tl->soft_lines->buf[cur_index] : ltk_soft_line_create(); 88 sl->glyph_index = cur->dir == HB_DIRECTION_RTL ? cur->num_glyphs - 1 : 0; 89 sl->run = cur; 90 sl->len = 0; 91 sl->w = 0; 92 ltk_array_append_line(tl->soft_lines, sl); 93 if (max_width == -1) { 94 cur = tl->first_run; 95 while (cur) { 96 sl->len += cur->num_glyphs; 97 sl->w += cur->w; 98 cur = cur->next; 99 } 100 ltk_text_line_cleanup_soft_lines(tl->soft_lines, old_len); 101 tl->w_wrapped = tl->w; 102 tl->h_wrapped = tl->h; 103 return; 104 } 105 int last_linebreak = par_is_rtl ? tl->w : 0; 106 int cur_start = 0; 107 /* FIXME: also calculate max height of each line */ 108 /* Note: 0x20 is space */ 109 /* Note: No, this doesn't do proper Unicode linebreaking */ 110 /* Note: This is probably buggy */ 111 while (cur) { 112 if (sl->w + cur->w <= max_width) { 113 sl->w += cur->w; 114 sl->len += cur->num_glyphs; 115 cur = par_is_rtl ? cur->last : cur->next; 116 continue; 117 } 118 if (cur->dir == HB_DIRECTION_RTL) { 119 cur_start = cur->glyphs[cur->num_glyphs - 1].x_abs + cur->glyphs[cur->num_glyphs - 1].x_advance; 120 int i = cur->num_glyphs - 1; 121 /* This is needed to properly break a run over multiple lines. 122 We can't just reuse sl->glyph_index because that might be 123 located in another run */ 124 int cur_start_index = cur->num_glyphs - 1; 125 while (i >= 0) { 126 glyph = &cur->glyphs[i]; 127 int cur_w = sl->w + cur_start - glyph->x_abs; 128 if (cur_w > max_width) { 129 int char_break = -1; 130 for (int j = i; j < cur->num_glyphs; j++) { 131 if (char_break == -1 && cur->glyphs[j].cluster != glyph->cluster) 132 char_break = j; 133 if ((j != i && tl->log_buf->buf[cur->glyphs[j].cluster] == 0x20) || 134 j == cur_start_index || sl->len == 0) { 135 if (j == cur_start_index && 136 tl->log_buf->buf[cur->glyphs[j].cluster] != 0x20) { 137 if (sl->len == 0) { 138 char_break = char_break == -1 ? j : char_break; 139 i = char_break - 1; 140 last_linebreak = cur->glyphs[char_break].x_abs; 141 sl->len += j - char_break + 1; 142 } else { 143 i = j; 144 last_linebreak = cur_start; 145 } 146 } else { 147 i = j - 1; 148 last_linebreak = cur->glyphs[j].x_abs; 149 sl->len++; 150 } 151 sl->w += cur_start - last_linebreak; 152 cur_start = last_linebreak; 153 cur_index++; 154 sl = old_len > cur_index ? 155 tl->soft_lines->buf[cur_index] : 156 ltk_soft_line_create(); 157 sl->glyph_index = i; 158 cur_start_index = i; 159 sl->run = cur; 160 sl->len = 0; 161 sl->w = 0; 162 ltk_array_append_line(tl->soft_lines, sl); 163 break; 164 } else { 165 sl->len--; 166 } 167 } 168 } else { 169 sl->len++; 170 i--; 171 } 172 } 173 if (sl->run == cur) 174 sl->w = last_linebreak - cur->glyphs[0].x_abs; 175 else 176 sl->w += cur->w; 177 } else { 178 cur_start = cur->glyphs[0].x_abs; 179 int i = 0; 180 int cur_start_index = 0; 181 while (i < cur->num_glyphs) { 182 glyph = &cur->glyphs[i]; 183 /* FIXME: This uses x_advance instead of glyph width so it works correctly 184 together with the current shaping, but should it maybe take the largest 185 of the two? What if the glyph width is actually larger than x_advance? */ 186 int cur_w = sl->w + glyph->x_abs + glyph->x_advance - cur_start; 187 if (cur_w > max_width) { 188 int char_break = -1; 189 for (int j = i; j >= 0; j--) { 190 if (char_break == -1 && cur->glyphs[j].cluster != glyph->cluster) 191 char_break = j; 192 if ((j != i && tl->log_buf->buf[cur->glyphs[j].cluster] == 0x20) || 193 j == cur_start_index || sl->len == 0) { 194 if (j == cur_start_index && 195 tl->log_buf->buf[cur->glyphs[j].cluster] != 0x20) { 196 if (sl->len == 0) { 197 char_break = char_break == -1 ? j : char_break; 198 i = char_break + 1; 199 last_linebreak = cur->glyphs[char_break + 1].x_abs; 200 sl->len += char_break - j + 1; 201 } else { 202 i = j; 203 last_linebreak = cur_start; 204 } 205 } else { 206 i = j + 1; 207 last_linebreak = cur->glyphs[j + 1].x_abs; 208 sl->len++; 209 } 210 sl->w += last_linebreak - cur_start; 211 cur_start = last_linebreak; 212 cur_index++; 213 sl = old_len > cur_index ? 214 tl->soft_lines->buf[cur_index] : 215 ltk_soft_line_create(); 216 sl->glyph_index = i; 217 cur_start_index = i; 218 sl->run = cur; 219 sl->len = 0; 220 sl->w = 0; 221 ltk_array_append_line(tl->soft_lines, sl); 222 break; 223 } else { 224 sl->len--; 225 } 226 } 227 } else { 228 sl->len++; 229 i++; 230 } 231 232 } 233 if (sl->run == cur) 234 sl->w = cur->glyphs[cur->num_glyphs - 1].x_abs + cur->glyphs[cur->num_glyphs - 1].x_advance - last_linebreak; 235 else 236 sl->w += cur->w; 237 } 238 cur = par_is_rtl ? cur->last : cur->next; 239 } 240 ltk_text_line_cleanup_soft_lines(tl->soft_lines, old_len); 241 /* if it fits on one line, don't waste all the space to the end of 242 the line, just use the actual width of the text */ 243 if (tl->soft_lines->len == 1) 244 tl->w_wrapped = tl->soft_lines->buf[0]->w; 245 else 246 tl->w_wrapped = max_width; 247 tl->h_wrapped = tl->soft_lines->len * tl->h; 248 } 249 250 XImage * 251 ltk_create_ximage(Display *dpy, int w, int h, int depth, XColor bg) { 252 XImage *img = XCreateImage(dpy, CopyFromParent, depth, ZPixmap, 0, NULL, w, h, 32, 0); 253 img->data = calloc(img->bytes_per_line, img->height); 254 XInitImage(img); 255 256 int b; 257 for (int i = 0; i < h; i++) { 258 b = img->bytes_per_line * i; 259 for (int j = 0; j < w; j++) { 260 img->data[b++] = bg.blue / 257; 261 img->data[b++] = bg.green / 257; 262 img->data[b++] = bg.red / 257; 263 b++; 264 } 265 } 266 267 return img; 268 } 269 270 /* based on http://codemadness.org/git/dwm-font/file/drw.c.html#l315 */ 271 void 272 ltk_soft_line_draw_glyph(LtkGlyph *glyph, XImage *img, struct ltk_soft_line *sl, int x, int y, XColor fg) { 273 ltk_array_append_int(sl->glyph_pos, x); 274 ltk_array_append_uint32(sl->glyph_clusters, glyph->cluster); 275 double a; 276 int b; 277 for (int i = 0; i < glyph->info->h; i++) { 278 for (int j = 0; j < glyph->info->w; j++) { 279 if (y + i >= img->height || x + j >= img->width || y + i < 0 || x + i < 0) 280 continue; 281 b = (y + i) * img->bytes_per_line + (x + j) * 4; 282 a = glyph->info->alphamap[i * glyph->info->w + j] / 255.0; 283 img->data[b] = (fg.blue * a + (1 - a) * (uint16_t)img->data[b] * 257) / 257; 284 img->data[b + 1] = (fg.green * a + (1 - a) * (uint16_t)img->data[b + 1] * 257) / 257; 285 img->data[b + 2] = (fg.red * a + (1 - a) * (uint16_t)img->data[b + 2] * 257) / 257; 286 } 287 } 288 } 289 290 /* FIXME: the glyph drawing function sometimes receives negative x positions */ 291 /* FIXME: Fix memory leaks... */ 292 XImage * 293 ltk_text_line_render( 294 struct ltk_text_line *tl, 295 Display *dpy, 296 Window window, 297 GC gc, 298 Colormap colormap, 299 XColor fg, 300 XColor bg) 301 { 302 LtkGlyph *glyph; 303 int par_is_rtl = tl->dir == HB_DIRECTION_RTL; 304 305 XWindowAttributes attrs; 306 XGetWindowAttributes(dpy, window, &attrs); 307 int depth = attrs.depth; 308 /* FIXME: pass old image; if it has same dimensions, just clear it */ 309 XImage *img = ltk_create_ximage(dpy, tl->w_wrapped, tl->h_wrapped, depth, bg); 310 311 for (int i = 0; i < tl->soft_lines->len; i++) { 312 struct ltk_soft_line *sl = tl->soft_lines->buf[i]; 313 /* FIXME: allow to disable this if selection isn't needed */ 314 if (!sl->glyph_pos) 315 sl->glyph_pos = ltk_array_create_int(tl->len); 316 else 317 sl->glyph_pos->len = 0; 318 if (!sl->glyph_clusters) 319 sl->glyph_clusters = ltk_array_create_uint32(tl->len); 320 else 321 sl->glyph_clusters->len = 0; 322 struct ltk_text_run *cur = sl->run; 323 size_t cur_len = 0; 324 int cur_border = par_is_rtl ? sl->w : 0; 325 while (cur && cur_len < sl->len) { 326 int start_index; 327 if (cur->dir == HB_DIRECTION_RTL) { 328 start_index = cur == sl->run ? sl->glyph_index : cur->num_glyphs - 1; 329 int local_border = cur->glyphs[start_index].x_abs + cur->glyphs[start_index].x_advance; 330 int end_index; 331 if (start_index + 1 < sl->len - cur_len) 332 end_index = 0; 333 else 334 end_index = start_index - (sl->len - cur_len) + 1; 335 if (par_is_rtl) { 336 for (int j = start_index; j >= 0 && cur_len < sl->len; j--) { 337 cur_len++; 338 int x = cur_border - (local_border - cur->glyphs[j].x_abs) + img->width - sl->w; 339 ltk_soft_line_draw_glyph(&cur->glyphs[j], img, sl, x, cur->glyphs[j].y_abs + tl->h * i, fg); 340 } 341 cur_border -= local_border - cur->glyphs[end_index].x_abs; 342 } else { 343 for (int j = end_index; j <= start_index && cur_len < sl->len; j++) { 344 cur_len++; 345 int x = cur_border + (cur->glyphs[j].x_abs - cur->glyphs[end_index].x_abs); 346 ltk_soft_line_draw_glyph(&cur->glyphs[j], img, sl, x, cur->glyphs[j].y_abs + tl->h * i, fg); 347 } 348 cur_border += local_border - cur->glyphs[end_index].x_abs; 349 } 350 } else { 351 start_index = cur == sl->run ? sl->glyph_index : 0; 352 int local_border = cur->glyphs[start_index].x_abs; 353 int end_index; 354 if (cur->num_glyphs - start_index < sl->len - cur_len) 355 end_index = cur->num_glyphs - 1; 356 else 357 end_index = start_index + sl->len - cur_len - 1; 358 if (par_is_rtl) { 359 for (int j = end_index; j >= start_index && cur_len < sl->len; j--) { 360 cur_len++; 361 int x = cur_border - (cur->glyphs[end_index].x_abs + cur->glyphs[end_index].x_advance - cur->glyphs[j].x_abs) + img->width - sl->w; 362 ltk_soft_line_draw_glyph(&cur->glyphs[j], img, sl, x, cur->glyphs[j].y_abs + tl->h * i, fg); 363 } 364 cur_border -= cur->glyphs[cur->num_glyphs - 1].x_abs + cur->glyphs[cur->num_glyphs - 1].x_advance - local_border; 365 } else { 366 for (int j = start_index; j < cur->num_glyphs && cur_len < sl->len; j++) { 367 cur_len++; 368 int x = cur_border + (cur->glyphs[j].x_abs - local_border); 369 ltk_soft_line_draw_glyph(&cur->glyphs[j], img, sl, x, cur->glyphs[j].y_abs + tl->h * i, fg); 370 } 371 cur_border += cur->glyphs[cur->num_glyphs - 1].x_abs + cur->glyphs[cur->num_glyphs - 1].x_advance - local_border; 372 } 373 } 374 cur = par_is_rtl ? cur->last : cur->next; 375 } 376 ltk_array_resize_int(sl->glyph_pos, sl->glyph_pos->len); 377 ltk_array_resize_uint32(sl->glyph_clusters, sl->glyph_clusters->len); 378 } 379 return img; 380 } 381 382 uint32_t 383 ltk_soft_line_get_index_from_pos(int x, struct ltk_text_line *tl, struct ltk_soft_line *sl, int *found_pos) { 384 /* FIXME: need par dir! for better guess! */ 385 /* also need it to determine if insert should be before or after char */ 386 /* FIXME: should I be messing around with casting between uint32_t and size_t? */ 387 /* FIXME: handle negative x */ 388 int guess = (int)(((double)x / sl->w) * (sl->len - 1)); 389 guess = guess >= sl->len ? sl->len - 1 : guess; 390 int delta = 0; 391 int i = 0; 392 int last_dist; 393 /* FIXME: add more safety - sl->len and sl->glyph_pos->len *should* be the 394 same, but who knows? */ 395 if (sl->glyph_pos->len - 1 > guess && 396 abs(sl->glyph_pos->buf[guess + 1] - x) < abs(sl->glyph_pos->buf[guess] - x)) { 397 delta = 1; 398 i = guess + 1; 399 last_dist = abs(sl->glyph_pos->buf[guess + 1] - x); 400 } else if (guess > 0 && 401 abs(sl->glyph_pos->buf[guess - 1] - x) < abs(sl->glyph_pos->buf[guess] - x)) { 402 delta = -1; 403 i = guess - 1; 404 last_dist = abs(sl->glyph_pos->buf[guess - 1] - x); 405 } 406 int final_index; 407 if (delta == 0) { 408 final_index = guess; 409 } else { 410 int new_dist; 411 for (; i >= 0 && i < sl->len; i += delta) { 412 new_dist = abs(sl->glyph_pos->buf[i] - x); 413 if (last_dist < new_dist) 414 break; 415 last_dist = new_dist; 416 } 417 final_index = i - delta; 418 } 419 if (found_pos) 420 *found_pos = sl->glyph_pos->buf[final_index]; 421 uint32_t cluster = sl->glyph_clusters->buf[final_index]; 422 if (FRIBIDI_LEVEL_IS_RTL(tl->bidi_levels->buf[cluster])) 423 return cluster >= tl->log_buf->len - 1 ? tl->log_buf->len - 1 : cluster + 1; 424 else 425 return cluster; 426 } 427 428 /* Begin stuff stolen from raqm */ 429 430 /* Special paired characters for script detection */ 431 static size_t paired_len = 34; 432 static const FriBidiChar paired_chars[] = { 433 0x0028, 0x0029, /* ascii paired punctuation */ 434 0x003c, 0x003e, 435 0x005b, 0x005d, 436 0x007b, 0x007d, 437 0x00ab, 0x00bb, /* guillemets */ 438 0x2018, 0x2019, /* general punctuation */ 439 0x201c, 0x201d, 440 0x2039, 0x203a, 441 0x3008, 0x3009, /* chinese paired punctuation */ 442 0x300a, 0x300b, 443 0x300c, 0x300d, 444 0x300e, 0x300f, 445 0x3010, 0x3011, 446 0x3014, 0x3015, 447 0x3016, 0x3017, 448 0x3018, 0x3019, 449 0x301a, 0x301b 450 }; 451 452 static int 453 get_pair_index (const FriBidiChar ch) { 454 int lower = 0; 455 int upper = paired_len - 1; 456 457 while (lower <= upper) { 458 int mid = (lower + upper) / 2; 459 if (ch < paired_chars[mid]) 460 upper = mid - 1; 461 else if (ch > paired_chars[mid]) 462 lower = mid + 1; 463 else 464 return mid; 465 } 466 467 return -1; 468 } 469 470 #define STACK_IS_EMPTY(stack) ((stack)->len <= 0) 471 #define IS_OPEN(pair_index) (((pair_index) & 1) == 0) 472 473 /* Resolve the script for each character in the input string, if the character 474 * script is common or inherited it takes the script of the character before it 475 * except paired characters which we try to make them use the same script. We 476 * then split the BiDi runs, if necessary, on script boundaries. 477 */ 478 static int 479 ltk_resolve_scripts(struct ltk_text_line *tl) { 480 int last_script_index = -1; 481 int last_set_index = -1; 482 hb_script_t last_script = HB_SCRIPT_INVALID; 483 hb_script_t cur_script; 484 struct ltk_array_script *script_stack = NULL; 485 struct ltk_array_int *pair_indeces = NULL; 486 hb_unicode_funcs_t* unicode_funcs = hb_unicode_funcs_get_default(); 487 488 script_stack = ltk_array_create_script(4); 489 pair_indeces = ltk_array_create_int(4); 490 491 for (int i = 0; i < (int) tl->len; i++) { 492 cur_script = tl->scripts->buf[i]; 493 if (cur_script == HB_SCRIPT_COMMON && last_script_index != -1) { 494 int pair_index = get_pair_index(tl->log_buf->buf[i]); 495 if (pair_index >= 0) { 496 if (IS_OPEN (pair_index)) { 497 /* is a paired character */ 498 tl->scripts->buf[i] = last_script; 499 last_set_index = i; 500 ltk_array_append_script(script_stack, cur_script); 501 ltk_array_append_int(pair_indeces, pair_index); 502 } else { 503 /* is a close paired character */ 504 /* find matching opening (by getting the last 505 * even index for current odd index) */ 506 while (!STACK_IS_EMPTY(pair_indeces) && 507 pair_indeces->buf[pair_indeces->len-1] != (pair_index & ~1)) { 508 ltk_array_pop_script(script_stack); 509 ltk_array_pop_int(pair_indeces); 510 } 511 if (!STACK_IS_EMPTY(script_stack)) { 512 tl->scripts->buf[i] = script_stack->buf[script_stack->len-1]; 513 last_script = cur_script; 514 last_set_index = i; 515 } else { 516 tl->scripts->buf[i] = last_script; 517 last_set_index = i; 518 } 519 } 520 } else { 521 tl->scripts->buf[i] = last_script; 522 last_set_index = i; 523 } 524 } else if (cur_script == HB_SCRIPT_INHERITED && last_script_index != -1) { 525 tl->scripts->buf[i] = last_script; 526 last_set_index = i; 527 } else { 528 for (int j = last_set_index + 1; j < i; ++j) 529 tl->scripts->buf[j] = cur_script; 530 last_script = cur_script; 531 last_script_index = i; 532 last_set_index = i; 533 } 534 } 535 536 /* Loop backwards and change any remaining Common or Inherit characters to 537 * take the script of the next character. 538 * https://github.com/HOST-Oman/libraqm/issues/95 539 */ 540 hb_script_t scr; 541 for (int i = tl->len - 2; i >= 0; --i) { 542 scr = tl->scripts->buf[i]; 543 if (scr == HB_SCRIPT_INHERITED || scr == HB_SCRIPT_COMMON) { 544 tl->scripts->buf[i] = tl->scripts->buf[i + 1]; 545 } 546 } 547 548 ltk_array_destroy_script(script_stack); 549 ltk_array_destroy_int(pair_indeces); 550 551 return 1; 552 } 553 554 /* End stuff stolen from raqm */ 555 /* Update: That's a lie; much more is stolen from raqm. */ 556 557 static struct ltk_text_run * 558 ltk_text_run_create(size_t start_index, size_t len, hb_script_t script, hb_direction_t dir) { 559 struct ltk_text_run *run = malloc(sizeof(struct ltk_text_run)); 560 if (!run) { 561 (void)fprintf(stderr, "Cannot allocate memory for text run.\n"); 562 exit(1); 563 } 564 run->start_index = start_index; 565 run->len = len; 566 run->script = script; 567 run->dir = dir; 568 run->next = NULL; 569 run->last = NULL; 570 return run; 571 } 572 573 static void 574 ltk_text_line_itemize(struct ltk_text_line *tl) { 575 ltk_resolve_scripts(tl); 576 struct ltk_text_run *first_run = NULL; 577 struct ltk_text_run *cur_run = NULL; 578 struct ltk_text_run *new = NULL; 579 FriBidiLevel last_level; 580 FriBidiLevel cur_level; 581 hb_script_t last_script; 582 hb_script_t cur_script; 583 size_t start_index = 0; 584 size_t end_index; 585 hb_direction_t dir; 586 int par_is_rtl = FRIBIDI_IS_RTL(tl->dir); 587 while (start_index < tl->len) { 588 end_index = start_index; 589 cur_level = last_level = tl->bidi_levels->buf[tl->vis2log->buf[start_index]]; 590 cur_script = last_script = tl->scripts->buf[tl->vis2log->buf[start_index]]; 591 while (end_index < tl->len && 592 cur_level == last_level && cur_script == last_script) { 593 end_index++; 594 /* I should probably make this nicer at some point */ 595 if (end_index < tl->len) { 596 cur_level = tl->bidi_levels->buf[tl->vis2log->buf[end_index]]; 597 cur_script = tl->scripts->buf[tl->vis2log->buf[end_index]]; 598 } 599 } 600 dir = HB_DIRECTION_LTR; 601 if (FRIBIDI_LEVEL_IS_RTL(last_level)) 602 dir = HB_DIRECTION_RTL; 603 size_t start_log = tl->vis2log->buf[start_index]; 604 size_t end_log = tl->vis2log->buf[end_index - 1]; 605 if (start_log > end_log) 606 start_log = end_log; 607 new = ltk_text_run_create( 608 start_log, end_index - start_index, last_script, dir); 609 if (!first_run) { 610 first_run = new; 611 } else { 612 cur_run->next = new; 613 new->last = cur_run; 614 } 615 cur_run = new; 616 start_index = end_index; 617 } 618 tl->first_run = first_run; 619 tl->last_run = cur_run; 620 cur_run = first_run; 621 } 622 623 static void 624 ltk_text_run_shape(struct ltk_text_run *tr, 625 struct ltk_text_line *tl, uint16_t font_size, uint16_t font_id, int *ret_y_max) { 626 LtkTextManager *tm = ltk_get_text_manager(); 627 khash_t(glyphinfo) *glyph_cache; 628 khint_t k; 629 630 tr->font_id = font_id; 631 tr->font_size = font_size; 632 uint32_t attr = (uint32_t)font_id << 16 + font_size; 633 /* FIXME: turn this into ltk_get_glyph_cache */ 634 k = kh_get(glyphcache, tm->glyph_cache, attr); 635 if (k == kh_end(tm->glyph_cache)) { 636 k = ltk_create_glyph_cache(tm, font_id, font_size); 637 } 638 glyph_cache = kh_value(tm->glyph_cache, k); 639 640 hb_buffer_t *buf; 641 hb_glyph_info_t *ginf, *gi; 642 hb_glyph_position_t *gpos, *gp; 643 tr->num_glyphs = 0; 644 645 buf = hb_buffer_create(); 646 hb_buffer_add_utf32(buf, tl->log_buf->buf, tl->len, tr->start_index, tr->len); 647 hb_buffer_set_direction(buf, tr->dir); 648 hb_buffer_set_script(buf, tr->script); 649 /* According to https://harfbuzz.github.io/the-distinction-between-levels-0-and-1.html 650 * this should be level 1 clustering instead of level 0 */ 651 hb_buffer_set_cluster_level(buf, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS); 652 hb_shape(tr->font->hb, buf, NULL, 0); 653 ginf = hb_buffer_get_glyph_infos(buf, &tr->num_glyphs); 654 gpos = hb_buffer_get_glyph_positions(buf, &tr->num_glyphs); 655 float scale = stbtt_ScaleForMappingEmToPixels(&tr->font->info, font_size); 656 657 int x_min = INT_MAX, x_max = INT_MIN, y_min = INT_MAX, y_max = INT_MIN; 658 int x_abs = 0, y_abs = 0, x1_abs, y1_abs, x2_abs, y2_abs; 659 tr->glyphs = malloc(sizeof(LtkGlyph) * tr->num_glyphs); 660 if (!tr->glyphs) { 661 (void)fprintf(stderr, "Cannot allocate space for glyphs.\n"); 662 exit(1); 663 } 664 /* FIXME: should x_max be calculated using glyph->info->w? 665 The advance might be different and not represent where pixels 666 will actually have to be drawn. */ 667 /* magic, do not touch */ 668 LtkGlyph *glyph; 669 for (int i = 0; i < tr->num_glyphs; i++) { 670 gi = &ginf[i]; 671 gp = &gpos[i]; 672 glyph = &tr->glyphs[i]; 673 glyph->cluster = gi->cluster; 674 glyph->info = ltk_get_glyph_info(tr->font, gi->codepoint, scale, glyph_cache); 675 glyph->info->refs++; 676 /* FIXME: round instead of just casting */ 677 glyph->x_offset = (int)(gp->x_offset * scale); 678 glyph->y_offset = (int)(gp->y_offset * scale); 679 glyph->x_advance = (int)(gp->x_advance * scale); 680 glyph->y_advance = (int)(gp->y_advance * scale); 681 682 /* Calculate position in order to determine full size of text segment */ 683 x1_abs = x_abs + glyph->info->xoff + glyph->x_offset; 684 y1_abs = y_abs + glyph->info->yoff - glyph->y_offset; 685 /* FIXME: THIS PROBABLY DOESN'T REALLY WORK */ 686 if (HB_DIRECTION_IS_HORIZONTAL(tr->dir)) { 687 x2_abs = x1_abs + glyph->x_advance; 688 y2_abs = y1_abs + glyph->info->h; 689 } else { 690 x2_abs = x1_abs + glyph->info->w; 691 y2_abs = y1_abs - glyph->y_advance; 692 } 693 if (x1_abs < x_min) x_min = x1_abs; 694 if (y1_abs < y_min) y_min = y1_abs; 695 if (x2_abs > x_max) x_max = x2_abs; 696 if (y2_abs > y_max) y_max = y2_abs; 697 x_abs += glyph->x_advance; 698 y_abs -= glyph->y_advance; 699 } 700 tr->start_x = -x_min; 701 tr->start_y = -y_min; 702 tr->w = x_max - x_min; 703 *ret_y_max = y_max; 704 705 tr->font->refs++; 706 } 707 708 static void 709 ltk_text_line_shape(struct ltk_text_line *tl) { 710 LtkTextManager *tm = ltk_get_text_manager(); 711 struct ltk_text_run *run = tl->first_run; 712 tl->w = tl->h = 0; 713 int y_max; 714 int y_max_overall = INT_MIN; 715 int y_min_overall = INT_MAX; 716 while (run) { 717 /* Question: Why does this not work with FcPatternDuplicate? */ 718 FcPattern *pat = FcPatternCreate(); 719 FcPattern *match; 720 FcResult result; 721 FcPatternAddBool(pat, FC_SCALABLE, 1); 722 FcConfigSubstitute(NULL, pat, FcMatchPattern); 723 FcDefaultSubstitute(pat); 724 /* FIXME: make this more efficient */ 725 FcCharSet *cs = FcCharSetCreate(); 726 for (size_t i = run->start_index; i < run->start_index + run->len; i++) { 727 FcCharSetAddChar(cs, tl->log_buf->buf[i]); 728 } 729 FcPatternAddCharSet(pat, FC_CHARSET, cs); 730 match = FcFontMatch(NULL, pat, &result); 731 char *file; 732 FcPatternGetString(match, FC_FILE, 0, &file); 733 uint16_t font_id = ltk_get_font(tm, file); 734 khint_t k = kh_get(fontstruct, tm->font_cache, font_id); 735 run->font = kh_value(tm->font_cache, k); 736 FcPatternDestroy(match); 737 FcPatternDestroy(pat); 738 ltk_text_run_shape(run, tl, tl->font_size, font_id, &y_max); 739 if (y_max_overall < y_max) 740 y_max_overall = y_max; 741 /* tr->start_y is -y_min */ 742 if (y_min_overall > -run->start_y) 743 y_min_overall = -run->start_y; 744 tl->w += run->w; 745 run = run->next; 746 }; 747 tl->h = y_max_overall - y_min_overall; 748 749 /* calculate the actual position of the characters */ 750 run = tl->first_run; 751 int x = 0; 752 LtkGlyph *glyph; 753 while (run) { 754 int cur_x = x + run->start_x; 755 int cur_y = tl->h - y_max_overall; /* baseline (I think?) */ 756 for (int i = 0; i < run->len; i++) { 757 glyph = &run->glyphs[i]; 758 glyph->x_abs = cur_x + glyph->info->xoff + glyph->x_offset; 759 glyph->y_abs = cur_y + glyph->info->yoff - glyph->y_offset; 760 cur_x += glyph->x_advance; 761 cur_y -= glyph->y_advance; 762 } 763 x += run->w; 764 run = run->next; 765 } 766 } 767 768 void 769 ltk_text_run_destroy(struct ltk_text_run *tr) { 770 khash_t(glyphinfo) *gcache; 771 LtkFont *font; 772 LtkGlyph *glyph; 773 khint_t k; 774 LtkTextManager *tm = ltk_get_text_manager(); 775 k = kh_get(glyphinfo, tm->glyph_cache, tr->font_id << 16 + tr->font_size); 776 gcache = kh_value(tm->glyph_cache, k); 777 for (int i = 0; i < tr->num_glyphs; i++) { 778 glyph = &tr->glyphs[i]; 779 if (--glyph->info->refs < 1) { 780 k = kh_get(glyphinfo, gcache, glyph->info->id); 781 kh_del(glyphinfo, gcache, k); 782 ltk_destroy_glyph_info(glyph->info); 783 } 784 } 785 k = kh_get(fontstruct, tm->font_cache, tr->font_id); 786 font = kh_value(tm->font_cache, k); 787 if (--font->refs < 1) { 788 kh_del(fontstruct, tm->font_cache, k); 789 ltk_destroy_font(font); 790 } 791 free(tr->glyphs); 792 free(tr); 793 } 794 795 void 796 ltk_text_line_destroy_runs(struct ltk_text_run *runs) { 797 struct ltk_text_run *cur, *next; 798 cur = runs; 799 while (cur) { 800 next = cur->next; 801 ltk_text_run_destroy(cur); 802 cur = next; 803 } 804 } 805 806 static void 807 ltk_text_line_recalculate(struct ltk_text_line *tl) { 808 FriBidiCharType par_dir = FRIBIDI_TYPE_ON; 809 fribidi_log2vis( 810 tl->log_buf->buf, tl->log_buf->len, 811 &par_dir, tl->vis_buf->buf, tl->log2vis->buf, tl->vis2log->buf, tl->bidi_levels->buf 812 ); 813 if (FRIBIDI_IS_RTL(par_dir)) 814 tl->dir = HB_DIRECTION_RTL; 815 else 816 tl->dir = HB_DIRECTION_LTR; 817 struct ltk_text_run *old_runs = tl->first_run; 818 ltk_text_line_itemize(tl); 819 struct ltk_text_run *cur = tl->first_run; 820 ltk_text_line_shape(tl); 821 /* this needs to be done after shaping so the fonts, etc. aren't 822 removed if their reference counts drop and then loaded again 823 right afterwards */ 824 if (old_runs) 825 ltk_text_line_destroy_runs(old_runs); 826 } 827 828 829 void 830 ltk_text_line_insert_utf32(struct ltk_text_line *tl, size_t index, uint32_t *text, size_t len) { 831 ltk_array_insert_uint32(tl->log_buf, index, text, len); 832 ltk_array_prepare_gap_script(tl->scripts, index, len); 833 hb_unicode_funcs_t *ufuncs = hb_unicode_funcs_get_default(); 834 for (int i = 0; i < len; i++) 835 tl->scripts->buf[index + i] = hb_unicode_script(ufuncs, text[i]); 836 ltk_array_resize_uint32(tl->vis_buf, tl->len + len); 837 ltk_array_resize_int(tl->log2vis, tl->len + len); 838 ltk_array_resize_int(tl->vis2log, tl->len + len); 839 ltk_array_resize_level(tl->bidi_levels, tl->len + len); 840 tl->len += len; 841 ltk_text_line_recalculate(tl); 842 } 843 844 /* must be NULL-terminated */ 845 void 846 ltk_text_line_insert_utf8(struct ltk_text_line *tl, size_t index, char *text) { 847 size_t len = u8_strlen(text); 848 uint32_t *new = malloc(sizeof(uint32_t) * len); 849 if (!new) { 850 (void)fprintf(stderr, "Error allocating memory for string\n"); 851 exit(1); 852 } 853 size_t inc = 0; 854 for (int i = 0; i < len; i++) 855 new[i] = u8_nextmemchar(text, &inc); 856 ltk_text_line_insert_utf32(tl, index, new, len); 857 free(new); 858 } 859 860 struct ltk_text_line * 861 ltk_text_line_create(uint16_t font_size) { 862 struct ltk_text_line *line = malloc(sizeof(struct ltk_text_line)); 863 if (!line) goto error; 864 line->log_buf = ltk_array_create_uint32(4); 865 line->vis_buf = ltk_array_create_uint32(4); 866 line->log2vis = ltk_array_create_int(4); 867 line->vis2log = ltk_array_create_int(4); 868 line->scripts = ltk_array_create_script(4); 869 line->bidi_levels = ltk_array_create_level(4); 870 line->soft_lines = ltk_array_create_line(1); 871 line->first_run = NULL; 872 line->next = NULL; 873 line->len = 0; 874 line->w_wrapped = line->h_wrapped = 0; 875 line->font_size = font_size; 876 return line; 877 error: 878 (void)fprintf(stderr, "No memory left while creating text line\n"); 879 exit(1); 880 } 881 882 void 883 ltk_text_buffer_scroll(struct ltk_text_buffer *tb, int offset_y) { 884 } 885 886 void 887 ltk_text_buffer_render(struct ltk_text_buffer *tb, int offset_y, int offset_x, int w, int h) { 888 struct ltk_text_line *cur = tb->head; 889 int cur_y = 0; 890 while (cur) { 891 if (cur_y > offset_y + h) 892 break; 893 if (cur_y > offset_y || cur_y + cur->h_wrapped > offset_y) { 894 } 895 cur_y += cur->h_wrapped; 896 cur = cur->next; 897 } 898 } 899 900 void 901 ltk_text_buffer_wrap(struct ltk_text_buffer *tb, int max_width) { 902 struct ltk_text_line *cur = tb->head; 903 while (cur) { 904 ltk_text_line_wrap(cur, max_width); 905 cur = cur->next; 906 } 907 } 908 909 void 910 ltk_text_buffer_insert_utf8_at_cursor(struct ltk_text_buffer *tb, char *text) { 911 size_t len = u8_strlen(text); 912 uint32_t *new = malloc(sizeof(uint32_t) * len); 913 if (!new) { 914 (void)fprintf(stderr, "Error allocating memory for string\n"); 915 exit(1); 916 } 917 size_t inc = 0; 918 uint32_t c; 919 size_t actual_len = 0; 920 for (int i = 0; i < len; i++) { 921 c = u8_nextmemchar(text, &inc); 922 if (c == 0x000A) { 923 printf("newline\n"); 924 } else { 925 actual_len++; 926 new[i] = c; 927 } 928 } 929 //ltk_text_line_insert_utf32(tb->cur_line, tb->cursor_pos, new, actual_len); 930 free(new); 931 } 932 933 struct ltk_text_buffer * 934 ltk_text_buffer_create(void) { 935 struct ltk_text_buffer *buf = malloc(sizeof(struct ltk_text_buffer)); 936 if (!buf) { 937 (void)fprintf(stderr, "No memory while creating text buffer\n"); 938 exit(1); 939 } 940 buf->head = NULL; 941 buf->cursor_pos = 0; 942 } 943 944 void 945 ltk_text_line_destroy(struct ltk_text_line *tl) { 946 ltk_array_destroy_uint32(tl->log_buf); 947 ltk_array_destroy_uint32(tl->vis_buf); 948 ltk_array_destroy_script(tl->scripts); 949 ltk_array_destroy_int(tl->log2vis); 950 ltk_array_destroy_int(tl->vis2log); 951 ltk_array_destroy_level(tl->bidi_levels); 952 ltk_array_destroy_deep_line(tl->soft_lines, <k_soft_line_destroy); 953 ltk_text_line_destroy_runs(tl->first_run); 954 free(tl); 955 }