ltk

Socket-based GUI for X11 (WIP)
git clone git://lumidify.org/ltk.git (fast, but not encrypted)
git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
Log | Files | Refs | README | LICENSE

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 }