text_pango.c - ltk - GUI toolkit for X11 (WIP)
HTML git clone git://lumidify.org/ltk.git (fast, but not encrypted)
HTML git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
HTML git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ltk.git (over tor)
DIR Log
DIR Files
DIR Refs
DIR README
DIR 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 }