URI: 
       ttext_pango.c - ltk - Socket-based GUI 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)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
       ttext_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 }