ltk

GUI toolkit for X11 (WIP)
git clone git://lumidify.org/ltk.git (fast, but not encrypted)
git clone https://lumidify.org/ltk.git (encrypted, but very slow)
git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ltk.git (over tor)
Log | Files | Refs | README | LICENSE

text_pango.c (12781B)


      1 /*
      2  * Copyright (c) 2021-2024 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 <stdint.h>
     18 #include <stdio.h>
     19 #include <stdlib.h>
     20 #include <string.h>
     21 
     22 #include <X11/X.h>
     23 #include <X11/Xlib.h>
     24 #include <X11/Xutil.h>
     25 #include <X11/extensions/Xrender.h>
     26 
     27 #include <pango/pangoxft.h>
     28 
     29 #include "color.h"
     30 #include "graphics.h"
     31 #include "graphics_xlib.h"
     32 #include "memory.h"
     33 #include "rect.h"
     34 #include "text.h"
     35 #include "util.h"
     36 
     37 struct ltk_text_line {
     38 	ltk_text_context *ctx;
     39 	char *text;
     40 	size_t len;
     41 	PangoLayout *layout;
     42 	PangoFontDescription *font_desc;
     43 	int font_size;
     44 	PangoAttrList *attrs;
     45 };
     46 
     47 struct ltk_text_context {
     48 	ltk_renderdata *data;
     49 	PangoFontMap *fontmap;
     50 	PangoContext *context;
     51 };
     52 
     53 ltk_text_context *
     54 ltk_text_context_create(ltk_renderdata *data) {
     55 	ltk_text_context *ctx = ltk_malloc(sizeof(ltk_text_context));
     56 	ctx->data = data;
     57 	ctx->fontmap = NULL;
     58 	ctx->context = NULL;
     59 	return ctx;
     60 }
     61 
     62 /* NOTE: It is crucial that the text context is initialized lazily instead of
     63    directly in the beginning. Pango does some initialization work in a separate
     64    thread, which causes problems when ltkd daemonizes itself, so the Pango
     65    initialization is only allowed to take place after daemonization. */
     66 static void
     67 ltk_text_context_init(ltk_text_context *ctx) {
     68 	if (!ctx->fontmap) {
     69 		ctx->fontmap = pango_xft_get_font_map(ctx->data->dpy, ctx->data->screen);
     70 		ctx->context = pango_font_map_create_context(ctx->fontmap);
     71 	}
     72 }
     73 
     74 void
     75 ltk_text_context_destroy(ltk_text_context *ctx) {
     76 	/* FIXME: if both are unref'd, there is a segfault - what is
     77 	   the normal thing to do here? */
     78 	if (ctx->fontmap)
     79 		g_object_unref(ctx->fontmap);
     80 	/*g_object_unref(ctx->context);*/
     81 	ltk_free(ctx);
     82 }
     83 
     84 void
     85 ltk_text_line_set_width(ltk_text_line *tl, int width) {
     86 	if (width <= 0)
     87 		pango_layout_set_width(tl->layout, -1);
     88 	else
     89 		pango_layout_set_width(tl->layout, width * PANGO_SCALE);
     90 }
     91 
     92 void
     93 ltk_text_line_set_text(ltk_text_line *tl, char *text, int take_over_text) {
     94 	if (tl->text)
     95 		free(tl->text);
     96 	if (take_over_text)
     97 		tl->text = text;
     98 	else
     99 		tl->text = ltk_strdup(text);
    100 	tl->len = strlen(tl->text);
    101 	pango_layout_set_text(tl->layout, tl->text, tl->len);
    102 }
    103 
    104 void
    105 ltk_text_line_set_const_text(ltk_text_line *tl, const char *text) {
    106 	ltk_text_line_set_text(tl, ltk_strdup(text), 1);
    107 }
    108 
    109 const char *
    110 ltk_text_line_get_text(ltk_text_line *tl) {
    111 	return tl->text;
    112 }
    113 
    114 void
    115 ltk_text_line_set_font_size(ltk_text_line *tl, int font_size) {
    116 	if (font_size == tl->font_size)
    117 		return;
    118 	pango_font_description_set_absolute_size(tl->font_desc, font_size * PANGO_SCALE);
    119 	pango_layout_set_font_description(tl->layout, tl->font_desc);
    120 	tl->font_size = font_size;
    121 }
    122 
    123 void
    124 ltk_text_line_set_font(ltk_text_line *tl, const char *font, int font_size) {
    125 	/* it doesn't seem as if there's a way to parse a font string without
    126 	   createing a new font description (but I might have missed something) */
    127 	PangoFontDescription *desc = pango_font_description_from_string(font);
    128 	pango_font_description_set_absolute_size(desc, font_size * PANGO_SCALE);
    129 	pango_layout_set_font_description(tl->layout, desc);
    130 	if (tl->font_desc)
    131 		pango_font_description_free(tl->font_desc);
    132 	tl->font_desc = desc;
    133 	tl->font_size = font_size;
    134 }
    135 
    136 ltk_text_line *
    137 ltk_text_line_create(ltk_text_context *ctx, const char *font, int font_size, char *text, int take_over_text, int width) {
    138 	ltk_text_context_init(ctx);
    139 	ltk_text_line *tl = ltk_malloc(sizeof(ltk_text_line));
    140 	if (take_over_text)
    141 		tl->text = text;
    142 	else
    143 		tl->text = ltk_strdup(text);
    144 	tl->len = strlen(tl->text);
    145 	tl->font_size = font_size;
    146 	/* FIXME: does this ever return NULL? */
    147 	tl->layout = pango_layout_new(ctx->context);
    148 
    149 	tl->ctx = ctx;
    150 	pango_layout_set_wrap(tl->layout, PANGO_WRAP_WORD_CHAR);
    151 	pango_layout_set_text(tl->layout, text, -1);
    152 	if (width > 0)
    153 		ltk_text_line_set_width(tl, width * PANGO_SCALE);
    154 	tl->attrs = NULL;
    155 	ltk_text_line_clear_attrs(tl);
    156 	tl->font_desc = NULL;
    157 	ltk_text_line_set_font(tl, font, font_size);
    158 
    159 	return tl;
    160 }
    161 
    162 ltk_text_line *
    163 ltk_text_line_create_const_text(ltk_text_context *ctx, const char *font, int font_size, const char *text, int width) {
    164 	return ltk_text_line_create(ctx, font, font_size, ltk_strdup(text), 1, width);
    165 }
    166 
    167 void
    168 ltk_text_line_draw(ltk_text_line *tl, ltk_surface *s, ltk_color *color, int x, int y) {
    169 	pango_xft_render_layout(s->xftdraw, &color->xftcolor, tl->layout, x * PANGO_SCALE, y * PANGO_SCALE);
    170 }
    171 
    172 void
    173 ltk_text_line_draw_clipped(ltk_text_line *tl, ltk_surface *s, ltk_color *color, int x, int y, ltk_rect clip) {
    174 	XftDraw *d = s->xftdraw;
    175 	/* FIXME: check for integer overflow */
    176 	XPoint points[] = {
    177 	    {clip.x, clip.y},
    178 	    {clip.x + clip.w, clip.y},
    179 	    {clip.x + clip.w, clip.y + clip.h},
    180 	    {clip.x, clip.y + clip.h}
    181 	};
    182 	/* FIXME: reuse region? */
    183 	Region r = XPolygonRegion(points, 4, EvenOddRule);
    184 	/* FIXME: error checking */
    185 	XftDrawSetClip(d, r);
    186 	ltk_text_line_draw(tl, s, color, x, y);
    187 	XDestroyRegion(r);
    188 	XftDrawSetClip(d, NULL);
    189 }
    190 
    191 void
    192 ltk_text_line_get_size(ltk_text_line *tl, int *w, int *h) {
    193 	pango_layout_get_pixel_size(tl->layout, w, h);
    194 }
    195 
    196 void
    197 ltk_text_line_clear_attrs(ltk_text_line *tl) {
    198 	PangoAttrList *attrs = pango_attr_list_new();
    199 	#if PANGO_VERSION_CHECK(1, 44, 0)
    200 	PangoAttribute *no_hyphens = pango_attr_insert_hyphens_new(FALSE);
    201 	pango_attr_list_insert(attrs, no_hyphens);
    202 	#endif
    203 	pango_layout_set_attributes(tl->layout, attrs);
    204 	if (tl->attrs)
    205 		pango_attr_list_unref(tl->attrs);
    206 	tl->attrs = attrs;
    207 }
    208 
    209 void
    210 ltk_text_line_add_attr_bg(ltk_text_line *tl, size_t start, size_t end, ltk_color *color) {
    211 	XRenderColor c = color->xftcolor.color;
    212 	PangoAttribute *attr = pango_attr_background_new(c.red, c.green, c.blue);
    213 	attr->start_index = start;
    214 	attr->end_index = end;
    215 	/* FIXME: this is sketchy - if add_attr_bg/fg is called multiple times,
    216 	   pango_layout_set_attributes will probably ref the same AttrList multiple times */
    217 	pango_attr_list_insert(tl->attrs, attr);
    218 	pango_layout_set_attributes(tl->layout, tl->attrs);
    219 }
    220 
    221 void
    222 ltk_text_line_add_attr_fg(ltk_text_line *tl, size_t start, size_t end, ltk_color *color) {
    223 	XRenderColor c = color->xftcolor.color;
    224 	PangoAttribute *attr = pango_attr_foreground_new(c.red, c.green, c.blue);
    225 	attr->start_index = start;
    226 	attr->end_index = end;
    227 	pango_attr_list_insert(tl->attrs, attr);
    228 	pango_layout_set_attributes(tl->layout, tl->attrs);
    229 }
    230 
    231 ltk_text_direction
    232 ltk_text_line_get_softline_direction(ltk_text_line *tl, size_t line) {
    233 	int num_softlines = pango_layout_get_line_count(tl->layout);
    234 	if ((int)line >= num_softlines)
    235 		return LTK_TEXT_LTR;
    236 	PangoLayoutLine *sl = pango_layout_get_line_readonly(tl->layout, (int)line);
    237 	if (!sl)
    238 		return LTK_TEXT_LTR;
    239 	return sl->resolved_dir == PANGO_DIRECTION_RTL || sl->resolved_dir == PANGO_DIRECTION_WEAK_RTL ? LTK_TEXT_RTL : LTK_TEXT_LTR;
    240 }
    241 
    242 ltk_text_direction
    243 ltk_text_line_get_byte_direction(ltk_text_line *tl, size_t byte) {
    244 	/* FIXME: check if index out of range first? */
    245 	PangoDirection dir = pango_layout_get_direction(tl->layout, (int)byte);
    246 	return dir == PANGO_DIRECTION_RTL || dir == PANGO_DIRECTION_WEAK_RTL ? LTK_TEXT_RTL : LTK_TEXT_LTR;
    247 }
    248 
    249 size_t
    250 ltk_text_line_get_num_softlines(ltk_text_line *tl) {
    251 	return (size_t)pango_layout_get_line_count(tl->layout);
    252 }
    253 
    254 size_t
    255 ltk_text_line_xy_to_pos(ltk_text_line *tl, int x, int y, int snap_nearest) {
    256 	int index, trailing;
    257 	pango_layout_xy_to_index(
    258 	    tl->layout,
    259 	    x * PANGO_SCALE, y * PANGO_SCALE,
    260 	    &index, & trailing
    261 	);
    262 	if (snap_nearest) {
    263 		while (trailing > 0) {
    264 			trailing--;
    265 			/* FIXME: proper string type with length */
    266 			// FIXME: next_utf8 should be size_t
    267 			index = next_utf8(tl->text, tl->len, index);
    268 		}
    269 	}
    270 	return (size_t)index;
    271 }
    272 
    273 /* FIXME: get_nearest_legal_pos */
    274 /* FIXME: factor out common text code from ltk and ledit */
    275 
    276 /* WARNING: width can be negative - https://docs.gtk.org/Pango/method.Layout.index_to_pos.html */
    277 void
    278 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) {
    279 	PangoRectangle rect;
    280 	pango_layout_index_to_pos(tl->layout, (int)pos, &rect);
    281 	*x_ret = rect.x / PANGO_SCALE;
    282 	*y_ret = rect.y / PANGO_SCALE;
    283 	*w_ret = rect.width / PANGO_SCALE;
    284 	*h_ret = rect.height / PANGO_SCALE;
    285 }
    286 
    287 /* FIXME: a lot more error checking, including integer overflows */
    288 size_t
    289 ltk_text_line_x_softline_to_pos(ltk_text_line *tl, int x, size_t softline, int snap_nearest) {
    290 	int trailing = 0;
    291 	int x_relative = x * PANGO_SCALE;
    292 	PangoLayoutLine *pango_line = pango_layout_get_line_readonly(tl->layout, softline);
    293 	int tlw, tlh;
    294 	pango_layout_get_size(tl->layout, &tlw, &tlh);
    295 	/* x is absolute, so the margin at the left needs to be subtracted */
    296 	if (pango_line->resolved_dir == PANGO_DIRECTION_RTL) {
    297 		PangoRectangle rect;
    298 		pango_layout_line_get_extents(pango_line, NULL, &rect);
    299 		x_relative -= (tlw - rect.width);
    300 	}
    301 	int tmp_pos;
    302 	pango_layout_line_x_to_index(
    303 	    pango_line, x_relative, &tmp_pos, &trailing
    304 	);
    305 	size_t pos = (size_t)tmp_pos;
    306 	/* snap to the nearest border between graphemes */
    307 	if (snap_nearest) {
    308 		while (trailing > 0) {
    309 			trailing--;
    310 			pos = next_utf8(tl->text, tl->len, pos);
    311 		}
    312 	}
    313 	return pos;
    314 }
    315 
    316 void
    317 ltk_text_line_pos_to_x_softline(ltk_text_line *tl, size_t pos, int middle, int *x_ret, size_t *softline_ret) {
    318 	/*
    319 	if (pos > INT_MAX)
    320 		err_overflow();
    321 	*/
    322 	int sl_tmp;
    323 	pango_layout_index_to_line_x(tl->layout, (int)pos, 0, &sl_tmp, x_ret);
    324 	*softline_ret = sl_tmp;
    325 	PangoLayoutLine *pango_line = pango_layout_get_line_readonly(tl->layout, *softline_ret);
    326 	int tlw, tlh;
    327 	pango_layout_get_size(tl->layout, &tlw, &tlh);
    328 	/* FIXME: wouldn't it be easier to just use pango_layout_index_to_pos for everything here? */
    329 	/* add left margin to x position if line is aligned right */
    330 	if (pango_line->resolved_dir == PANGO_DIRECTION_RTL) {
    331 		PangoRectangle rect;
    332 		pango_layout_line_get_extents(pango_line, NULL, &rect);
    333 		*x_ret += (tlw - rect.width);
    334 	}
    335 	if (middle) {
    336 		PangoRectangle rect;
    337 		pango_layout_index_to_pos(tl->layout, (int)pos, &rect);
    338 		*x_ret += rect.width / 2;
    339 	}
    340 	*x_ret /= PANGO_SCALE; /* FIXME: PANGO_PIXELS, etc. */
    341 }
    342 
    343 /* prev_index_ret is used instead of just calling get_legal_normal_pos
    344    because weird things happen otherwise
    345    -> in certain cases, this is still weird because prev_index_ret sometimes
    346       is not at the end of the line, but this is the best I could come up
    347       with for now */
    348 size_t
    349 ltk_text_line_move_cursor_visually(ltk_text_line *tl, size_t pos, int movement, size_t *prev_index_ret) {
    350 	/* FIXME
    351 	if (pos > INT_MAX)
    352 		err_overflow();
    353 	*/
    354 	/* FIXME: trailing */
    355 	int trailing = 0;
    356 	int tmp_index;
    357 	int new_index = (int)pos, last_index = (int)pos;
    358 	int dir = 1;
    359 	int num = movement;
    360 	if (movement < 0) {
    361 		dir = -1;
    362 		num = -movement;
    363 	}
    364 	/* FIXME: This is stupid. Anything outside the range of int won't work
    365 	   anyways because of pango (and because everything else would break
    366 	   anyways with such long lines), so it's stupid to do all this weird
    367 	   casting. */
    368 	/*
    369 	if (cur_line->len > INT_MAX)
    370 		err_overflow();
    371 	*/
    372 	while (num > 0) {
    373 		tmp_index = new_index;
    374 		pango_layout_move_cursor_visually(
    375 		    tl->layout, TRUE,
    376 		    new_index, trailing, dir,
    377 		    &new_index, &trailing
    378 		);
    379 		/* for some reason, this is necessary */
    380 		if (new_index < 0)
    381 			new_index = 0;
    382 		else if (new_index > (int)tl->len)
    383 			new_index = (int)tl->len;
    384 		num--;
    385 		if (tmp_index != new_index)
    386 			last_index = tmp_index;
    387 	}
    388 	/* FIXME: Allow cursor to be at end of soft line */
    389 	/* we don't currently support a difference between the cursor being at
    390 	   the end of a soft line and the beginning of the next line */
    391 	/* FIXME: spaces at end of softlines are weird in normal mode */
    392 	while (trailing > 0) {
    393 		trailing--;
    394 		new_index = next_utf8(tl->text, tl->len, new_index);
    395 	}
    396 	if (new_index < 0)
    397 		new_index = 0;
    398 	if (prev_index_ret)
    399 		*prev_index_ret = (size_t)last_index;
    400 	return (size_t)new_index;
    401 }
    402 
    403 void
    404 ltk_text_line_destroy(ltk_text_line *tl) {
    405 	pango_font_description_free(tl->font_desc);
    406 	if (tl->attrs)
    407 		pango_attr_list_unref(tl->attrs);
    408 	g_object_unref(tl->layout);
    409 	ltk_free(tl->text);
    410 	ltk_free(tl);
    411 }