text_pango.c (11307B)
1 /* 2 * Copyright (c) 2021-2023 lumidify <nobody@lumidify.org> 3 * 4 * Permission to use, copy, modify, and/or distribute this software for any 5 * purpose with or without fee is hereby granted, provided that the above 6 * copyright notice and this permission notice appear in all copies. 7 * 8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 */ 16 17 #include <math.h> 18 #include <stdio.h> 19 #include <stdlib.h> 20 #include <stdint.h> 21 #include <stdarg.h> 22 23 #include <X11/Xos.h> 24 #include <X11/Xlib.h> 25 #include <X11/Xutil.h> 26 27 #include <pango/pangoxft.h> 28 #include <pango/pango-utils.h> /* for PANGO_VERSION_CHECK */ 29 30 #include "xlib_shared.h" 31 #include "memory.h" 32 #include "color.h" 33 #include "rect.h" 34 #include "widget.h" 35 #include "ltk.h" 36 #include "util.h" 37 #include "text.h" 38 39 struct ltk_text_line { 40 ltk_text_context *ctx; 41 char *text; 42 size_t len; 43 PangoLayout *layout; 44 uint16_t font_size; 45 PangoAttrList *attrs; 46 }; 47 48 struct ltk_text_context { 49 ltk_renderdata *data; 50 PangoFontMap *fontmap; 51 PangoContext *context; 52 char *default_font; 53 }; 54 55 ltk_text_context * 56 ltk_text_context_create(ltk_renderdata *data, char *default_font) { 57 ltk_text_context *ctx = ltk_malloc(sizeof(ltk_text_context)); 58 ctx->data = data; 59 ctx->fontmap = pango_xft_get_font_map(data->dpy, data->screen); 60 ctx->context = pango_font_map_create_context(ctx->fontmap); 61 ctx->default_font = ltk_strdup(default_font); 62 return ctx; 63 } 64 65 void 66 ltk_text_context_destroy(ltk_text_context *ctx) { 67 ltk_free(ctx->default_font); 68 /* FIXME: if both are unref'd, there is a segfault - what is 69 the normal thing to do here? */ 70 g_object_unref(ctx->fontmap); 71 /*g_object_unref(ctx->context);*/ 72 ltk_free(ctx); 73 } 74 75 void 76 ltk_text_line_set_width(ltk_text_line *tl, int width) { 77 if (width <= 0) 78 pango_layout_set_width(tl->layout, -1); 79 else 80 pango_layout_set_width(tl->layout, width * PANGO_SCALE); 81 } 82 83 void 84 ltk_text_line_set_text(ltk_text_line *tl, char *text, int take_over_text) { 85 if (tl->text) 86 free(tl->text); 87 if (take_over_text) 88 tl->text = text; 89 else 90 tl->text = ltk_strdup(text); 91 tl->len = strlen(tl->text); 92 pango_layout_set_text(tl->layout, tl->text, tl->len); 93 } 94 95 ltk_text_line * 96 ltk_text_line_create(ltk_text_context *ctx, uint16_t font_size, char *text, int take_over_text, int width) { 97 ltk_text_line *tl = ltk_malloc(sizeof(ltk_text_line)); 98 if (take_over_text) 99 tl->text = text; 100 else 101 tl->text = ltk_strdup(text); 102 tl->len = strlen(tl->text); 103 tl->font_size = font_size; 104 /* FIXME: does this ever return NULL? */ 105 tl->layout = pango_layout_new(ctx->context); 106 107 PangoFontDescription *desc = pango_font_description_from_string(ctx->default_font); 108 pango_font_description_set_size(desc, font_size * PANGO_SCALE); 109 pango_layout_set_font_description(tl->layout, desc); 110 pango_font_description_free(desc); 111 tl->ctx = ctx; 112 pango_layout_set_wrap(tl->layout, PANGO_WRAP_WORD_CHAR); 113 pango_layout_set_text(tl->layout, text, -1); 114 if (width > 0) 115 ltk_text_line_set_width(tl, width * PANGO_SCALE); 116 tl->attrs = NULL; 117 ltk_text_line_clear_attrs(tl); 118 119 return tl; 120 } 121 122 void 123 ltk_text_line_draw(ltk_text_line *tl, ltk_surface *s, ltk_color *color, int x, int y) { 124 XftDraw *d = ltk_surface_get_xft_draw(s); 125 pango_xft_render_layout(d, &color->xftcolor, tl->layout, x * PANGO_SCALE, y * PANGO_SCALE); 126 } 127 128 void 129 ltk_text_line_draw_clipped(ltk_text_line *tl, ltk_surface *s, ltk_color *color, int x, int y, ltk_rect clip) { 130 XftDraw *d = ltk_surface_get_xft_draw(s); 131 /* FIXME: check for integer overflow */ 132 XPoint points[] = { 133 {clip.x, clip.y}, 134 {clip.x + clip.w, clip.y}, 135 {clip.x + clip.w, clip.y + clip.h}, 136 {clip.x, clip.y + clip.h} 137 }; 138 Region r = XPolygonRegion(points, 4, EvenOddRule); 139 /* FIXME: error checking */ 140 XftDrawSetClip(d, r); 141 ltk_text_line_draw(tl, s, color, x, y); 142 XDestroyRegion(r); 143 XftDrawSetClip(d, NULL); 144 } 145 146 void 147 ltk_text_line_get_size(ltk_text_line *tl, int *w, int *h) { 148 pango_layout_get_pixel_size(tl->layout, w, h); 149 } 150 151 void 152 ltk_text_line_clear_attrs(ltk_text_line *tl) { 153 PangoAttrList *attrs = pango_attr_list_new(); 154 #if PANGO_VERSION_CHECK(1, 44, 0) 155 PangoAttribute *no_hyphens = pango_attr_insert_hyphens_new(FALSE); 156 pango_attr_list_insert(attrs, no_hyphens); 157 #endif 158 pango_layout_set_attributes(tl->layout, attrs); 159 if (tl->attrs) 160 pango_attr_list_unref(tl->attrs); 161 tl->attrs = attrs; 162 } 163 164 void 165 ltk_text_line_add_attr_bg(ltk_text_line *tl, size_t start, size_t end, ltk_color *color) { 166 XRenderColor c = color->xftcolor.color; 167 PangoAttribute *attr = pango_attr_background_new(c.red, c.green, c.blue); 168 attr->start_index = start; 169 attr->end_index = end; 170 /* FIXME: this is sketchy - if add_attr_bg/fg is called multiple times, 171 pango_layout_set_attributes will probably ref the same AttrList multiple times */ 172 pango_attr_list_insert(tl->attrs, attr); 173 pango_layout_set_attributes(tl->layout, tl->attrs); 174 } 175 176 void 177 ltk_text_line_add_attr_fg(ltk_text_line *tl, size_t start, size_t end, ltk_color *color) { 178 XRenderColor c = color->xftcolor.color; 179 PangoAttribute *attr = pango_attr_foreground_new(c.red, c.green, c.blue); 180 attr->start_index = start; 181 attr->end_index = end; 182 pango_attr_list_insert(tl->attrs, attr); 183 pango_layout_set_attributes(tl->layout, tl->attrs); 184 } 185 186 ltk_text_direction 187 ltk_text_line_get_softline_direction(ltk_text_line *tl, size_t line) { 188 int num_softlines = pango_layout_get_line_count(tl->layout); 189 if ((int)line >= num_softlines) 190 return LTK_TEXT_LTR; 191 PangoLayoutLine *sl = pango_layout_get_line_readonly(tl->layout, (int)line); 192 if (!sl) 193 return LTK_TEXT_LTR; 194 return sl->resolved_dir == PANGO_DIRECTION_RTL || sl->resolved_dir == PANGO_DIRECTION_WEAK_RTL ? LTK_TEXT_RTL : LTK_TEXT_LTR; 195 } 196 197 ltk_text_direction 198 ltk_text_line_get_byte_direction(ltk_text_line *tl, size_t byte) { 199 /* FIXME: check if index out of range first? */ 200 PangoDirection dir = pango_layout_get_direction(tl->layout, (int)byte); 201 return dir == PANGO_DIRECTION_RTL || dir == PANGO_DIRECTION_WEAK_RTL ? LTK_TEXT_RTL : LTK_TEXT_LTR; 202 } 203 204 size_t 205 ltk_text_line_get_num_softlines(ltk_text_line *tl) { 206 return (size_t)pango_layout_get_line_count(tl->layout); 207 } 208 209 size_t 210 ltk_text_line_xy_to_pos(ltk_text_line *tl, int x, int y, int snap_nearest) { 211 int index, trailing; 212 pango_layout_xy_to_index( 213 tl->layout, 214 x * PANGO_SCALE, y * PANGO_SCALE, 215 &index, & trailing 216 ); 217 if (snap_nearest) { 218 while (trailing > 0) { 219 trailing--; 220 /* FIXME: proper string type with length */ 221 // FIXME: next_utf8 should be size_t 222 index = next_utf8(tl->text, tl->len, index); 223 } 224 } 225 return (size_t)index; 226 } 227 228 /* FIXME: get_nearest_legal_pos */ 229 /* FIXME: factor out common text code from ltk and ledit */ 230 231 /* WARNING: width can be negative - https://docs.gtk.org/Pango/method.Layout.index_to_pos.html */ 232 void 233 ltk_text_line_pos_to_rect(ltk_text_line *tl, size_t pos, int *x_ret, int *y_ret, int *w_ret, int *h_ret) { 234 PangoRectangle rect; 235 pango_layout_index_to_pos(tl->layout, (int)pos, &rect); 236 *x_ret = rect.x / PANGO_SCALE; 237 *y_ret = rect.y / PANGO_SCALE; 238 *w_ret = rect.width / PANGO_SCALE; 239 *h_ret = rect.height / PANGO_SCALE; 240 } 241 242 /* FIXME: a lot more error checking, including integer overflows */ 243 size_t 244 ltk_text_line_x_softline_to_pos(ltk_text_line *tl, int x, size_t softline, int snap_nearest) { 245 int trailing = 0; 246 int x_relative = x * PANGO_SCALE; 247 PangoLayoutLine *pango_line = pango_layout_get_line_readonly(tl->layout, softline); 248 int tlw, tlh; 249 pango_layout_get_size(tl->layout, &tlw, &tlh); 250 /* x is absolute, so the margin at the left needs to be subtracted */ 251 if (pango_line->resolved_dir == PANGO_DIRECTION_RTL) { 252 PangoRectangle rect; 253 pango_layout_line_get_extents(pango_line, NULL, &rect); 254 x_relative -= (tlw - rect.width); 255 } 256 int tmp_pos; 257 pango_layout_line_x_to_index( 258 pango_line, x_relative, &tmp_pos, &trailing 259 ); 260 size_t pos = (size_t)tmp_pos; 261 /* snap to the nearest border between graphemes */ 262 if (snap_nearest) { 263 while (trailing > 0) { 264 trailing--; 265 pos = next_utf8(tl->text, tl->len, pos); 266 } 267 } 268 return pos; 269 } 270 271 void 272 ltk_text_line_pos_to_x_softline(ltk_text_line *tl, size_t pos, int middle, int *x_ret, size_t *softline_ret) { 273 /* 274 if (pos > INT_MAX) 275 err_overflow(); 276 */ 277 int sl_tmp; 278 pango_layout_index_to_line_x(tl->layout, (int)pos, 0, &sl_tmp, x_ret); 279 *softline_ret = sl_tmp; 280 PangoLayoutLine *pango_line = pango_layout_get_line_readonly(tl->layout, *softline_ret); 281 int tlw, tlh; 282 pango_layout_get_size(tl->layout, &tlw, &tlh); 283 /* FIXME: wouldn't it be easier to just use pango_layout_index_to_pos for everything here? */ 284 /* add left margin to x position if line is aligned right */ 285 if (pango_line->resolved_dir == PANGO_DIRECTION_RTL) { 286 PangoRectangle rect; 287 pango_layout_line_get_extents(pango_line, NULL, &rect); 288 *x_ret += (tlw - rect.width); 289 } 290 if (middle) { 291 PangoRectangle rect; 292 pango_layout_index_to_pos(tl->layout, (int)pos, &rect); 293 *x_ret += rect.width / 2; 294 } 295 *x_ret /= PANGO_SCALE; /* FIXME: PANGO_PIXELS, etc. */ 296 } 297 298 /* prev_index_ret is used instead of just calling get_legal_normal_pos 299 because weird things happen otherwise 300 -> in certain cases, this is still weird because prev_index_ret sometimes 301 is not at the end of the line, but this is the best I could come up 302 with for now */ 303 size_t 304 ltk_text_line_move_cursor_visually(ltk_text_line *tl, size_t pos, int movement, size_t *prev_index_ret) { 305 /* FIXME 306 if (pos > INT_MAX) 307 err_overflow(); 308 */ 309 /* FIXME: trailing */ 310 int trailing = 0; 311 int tmp_index; 312 int new_index = (int)pos, last_index = (int)pos; 313 int dir = 1; 314 int num = movement; 315 if (movement < 0) { 316 dir = -1; 317 num = -movement; 318 } 319 /* FIXME: This is stupid. Anything outside the range of int won't work 320 anyways because of pango (and because everything else would break 321 anyways with such long lines), so it's stupid to do all this weird 322 casting. */ 323 /* 324 if (cur_line->len > INT_MAX) 325 err_overflow(); 326 */ 327 while (num > 0) { 328 tmp_index = new_index; 329 pango_layout_move_cursor_visually( 330 tl->layout, TRUE, 331 new_index, trailing, dir, 332 &new_index, &trailing 333 ); 334 /* for some reason, this is necessary */ 335 if (new_index < 0) 336 new_index = 0; 337 else if (new_index > (int)tl->len) 338 new_index = (int)tl->len; 339 num--; 340 if (tmp_index != new_index) 341 last_index = tmp_index; 342 } 343 /* FIXME: Allow cursor to be at end of soft line */ 344 /* we don't currently support a difference between the cursor being at 345 the end of a soft line and the beginning of the next line */ 346 /* FIXME: spaces at end of softlines are weird in normal mode */ 347 while (trailing > 0) { 348 trailing--; 349 new_index = next_utf8(tl->text, tl->len, new_index); 350 } 351 if (new_index < 0) 352 new_index = 0; 353 if (prev_index_ret) 354 *prev_index_ret = (size_t)last_index; 355 return (size_t)new_index; 356 } 357 358 void 359 ltk_text_line_destroy(ltk_text_line *tl) { 360 if (tl->attrs) 361 pango_attr_list_unref(tl->attrs); 362 g_object_unref(tl->layout); 363 ltk_free(tl->text); 364 ltk_free(tl); 365 }