URI:
       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 }