URI: 
       ttext-hb.c - ltkx - GUI toolkit for X11 (WIP)
  HTML git clone git://lumidify.org/ltkx.git
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
       ttext-hb.c (13025B)
       ---
            1 /*
            2  * This file is part of the Lumidify ToolKit (LTK)
            3  * Copyright (c) 2017, 2018, 2020 lumidify <nobody@lumidify.org>
            4  *
            5  * Permission is hereby granted, free of charge, to any person obtaining a copy
            6  * of this software and associated documentation files (the "Software"), to deal
            7  * in the Software without restriction, including without limitation the rights
            8  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
            9  * copies of the Software, and to permit persons to whom the Software is
           10  * furnished to do so, subject to the following conditions:
           11  *
           12  * The above copyright notice and this permission notice shall be included in all
           13  * copies or substantial portions of the Software.
           14  *
           15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
           16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
           17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
           18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
           19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
           20  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
           21  * SOFTWARE.
           22  */
           23 
           24 #include <stdio.h>
           25 #include <stdlib.h>
           26 #include <stdint.h>
           27 #include <limits.h>
           28 #include <X11/Xlib.h>
           29 #include <X11/Xutil.h>
           30 #include "stb_truetype.h" /* http://nothings.org/stb/stb_truetype.h */
           31 #include <fontconfig/fontconfig.h>
           32 #include "khash.h"
           33 #include <fribidi.h>
           34 #include <harfbuzz/hb.h>
           35 #include <harfbuzz/hb-ot.h>
           36 #include "text-common.h"
           37 #include "text-hb.h"
           38 #include "ltk.h"
           39 
           40 extern Ltk *ltk_global;
           41 
           42 /* FIXME: allow to either use fribidi for basic shaping and don't use harfbuzz then,
           43           or just use harfbuzz (then fribidi doesn't need to do any shaping) */
           44 LtkTextLine *
           45 ltk_create_text_line(LtkTextManager *tm, char *text, uint16_t fontid, uint16_t size)
           46 {
           47         /* NOTE: This doesn't actually take fontid into account right now - should it? */
           48         LtkTextLine *tl = malloc(sizeof(LtkTextLine));
           49         tl->start_segment = NULL;
           50         LtkTextSegment *cur_ts = NULL;
           51         LtkTextSegment *new_ts = NULL;
           52         uint16_t cur_font_id = fontid;
           53         int k;
           54         LtkFont *font;
           55 
           56         unsigned int ulen = u8_strlen(text);
           57         FriBidiChar *log_str = malloc(sizeof(FriBidiChar) * ulen);
           58         size_t inc = 0;
           59         for (int i = 0; i < ulen; i++) {
           60                 log_str[i] = u8_nextmemchar(text, &inc);
           61         }
           62         FriBidiLevel *levels = malloc(ulen * sizeof(FriBidiLevel));
           63         int *l2v = malloc(ulen * sizeof(int));
           64         int *v2l = malloc(ulen * sizeof(int));
           65         FriBidiCharType pbase_dir = FRIBIDI_TYPE_ON;
           66         FriBidiChar *vis_str = malloc(sizeof(FriBidiChar) * ulen);
           67         ulen = fribidi_charset_to_unicode(FRIBIDI_CHAR_SET_UTF8, text, strlen(text), log_str);
           68         fribidi_log2vis(log_str, ulen, &pbase_dir, vis_str, l2v, v2l, levels);
           69 
           70         hb_unicode_funcs_t *ufuncs = hb_unicode_funcs_get_default();
           71         hb_script_t cur_script = hb_unicode_script(ufuncs, vis_str[0]);
           72         hb_script_t last_script = cur_script;
           73         size_t pos = 0;
           74         size_t last_pos = 0;
           75         size_t start_pos = 0;
           76         uint32_t ch;
           77 
           78         for (int p = 0; p <= ulen; p++) {
           79                 cur_script = hb_unicode_script(ufuncs, vis_str[p]);
           80                 if (p == ulen ||
           81                         (last_script != cur_script &&
           82                          cur_script != HB_SCRIPT_INHERITED &&
           83                          cur_script != HB_SCRIPT_COMMON)) {
           84                         FcPattern *pat = FcPatternDuplicate(tm->fcpattern);
           85                         FcPattern *match;
           86                         FcResult result;
           87                         FcPatternAddBool(pat, FC_SCALABLE, 1);
           88                         FcConfigSubstitute(NULL, pat, FcMatchPattern);
           89                         FcDefaultSubstitute(pat);
           90                         FcCharSet *cs = FcCharSetCreate();
           91                         for (int i = start_pos; i < p; i++) {
           92                                 FcCharSetAddChar(cs, vis_str[i]);
           93                         }
           94                         FcPatternAddCharSet(pat, FC_CHARSET, cs);
           95                         match = FcFontMatch(NULL, pat, &result);
           96                         char *file;
           97                         FcPatternGetString(match, FC_FILE, 0, &file);
           98                         cur_font_id = ltk_get_font(tm, file);
           99                         k = kh_get(fontstruct, tm->font_cache, cur_font_id);
          100                         font = kh_value(tm->font_cache, k);
          101                         FcPatternDestroy(match);
          102                         FcPatternDestroy(pat);
          103                         // handle case that this is the last character
          104                         if (p == ulen) {
          105                                 last_script = cur_script;
          106                         }
          107                         /* FIXME: There should be better handling for cases
          108                            where an error occurs while creating the segment */
          109                         new_ts = ltk_create_text_segment(
          110                                 tm, vis_str + start_pos,
          111                                 p - start_pos, cur_font_id,
          112                                 size, last_script
          113                         );
          114                         if (!new_ts) continue;
          115                         new_ts->next = NULL;
          116                         if (!tl->start_segment) tl->start_segment = new_ts;
          117                         if (cur_ts) cur_ts->next = new_ts;
          118                         cur_ts = new_ts;
          119 
          120                         start_pos = p;
          121                         last_script = cur_script;
          122                 }
          123         }
          124 
          125         free(vis_str);
          126         free(log_str);
          127         free(levels);
          128         free(l2v);
          129         free(v2l);
          130 
          131         /* calculate width of text line
          132            NOTE: doesn't work with mixed horizontal and vertical text */
          133         LtkTextSegment *ts = tl->start_segment;
          134         int is_hor = HB_DIRECTION_IS_HORIZONTAL(ts->dir);
          135         tl->y_max = tl->x_max = INT_MIN;
          136         tl->y_min = tl->x_min = INT_MAX;
          137         tl->w = tl->h = 0;
          138         while (ts) {
          139                 if (HB_DIRECTION_IS_HORIZONTAL(ts->dir) != is_hor) {
          140                         (void)fprintf(stderr, "WARNING: mixed horizontal/vertical text is not supported; ignoring\n");
          141                         continue;
          142                 }
          143                 if (is_hor) {
          144                         if (tl->y_max < ts->y_max) {
          145                                 tl->y_max = ts->y_max;
          146                         }
          147                         if (tl->y_min > ts->y_min) {
          148                                 tl->y_min = ts->y_min;
          149                         }
          150                         tl->w += ts->w;
          151                 } else {
          152                         if (tl->x_max < ts->x_max) {
          153                                 tl->x_max = ts->x_max;
          154                         }
          155                         if (tl->x_min > ts->x_min) {
          156                                 tl->x_min = ts->x_min;
          157                         }
          158                         tl->h += ts->h;
          159                 }
          160                 ts = ts->next;
          161         }
          162         if (is_hor) {
          163                 tl->h = tl->y_max - tl->y_min;
          164         } else {
          165                 tl->w = tl->x_max - tl->x_min;
          166         }
          167 
          168         return tl;
          169 }
          170 
          171 void
          172 ltk_destroy_text_line(LtkTextLine *tl) {
          173         LtkTextSegment *last_ts;
          174         LtkTextSegment *cur_ts = tl->start_segment;
          175         while (cur_ts) {
          176                 last_ts = cur_ts;
          177                 cur_ts = cur_ts->next;
          178                 ltk_destroy_text_segment(last_ts);
          179         }
          180 }
          181 
          182 /* FIXME: could use unsigned int for fontid and size as long as there is code to check neither of them become too large
          183    -> in case I want to get rid of uint_16_t, etc. */
          184 LtkTextSegment *
          185 ltk_create_text_segment(LtkTextManager *tm, uint32_t *text, unsigned int len, uint16_t fontid, uint16_t size, hb_script_t script)
          186 {
          187         /* (x1*, y1*): top left corner (relative to origin and absolute)
          188            (x2*, y2*): bottom right corner (relative to origin and absolute) */
          189         LtkFont *font;
          190         khash_t(glyphinfo) *glyph_cache;
          191         khint_t k;
          192 
          193         k = kh_get(fontstruct, tm->font_cache, fontid);
          194         font = kh_value(tm->font_cache, k);
          195 
          196         uint32_t attr = fontid << 16 + size;
          197         /* FIXME: turn this into ltk_get_glyph_cache */
          198         k = kh_get(glyphcache, tm->glyph_cache, attr);
          199         if (k == kh_end(tm->glyph_cache)) {
          200                 k = ltk_create_glyph_cache(tm, fontid, size);
          201         }
          202         glyph_cache = kh_value(tm->glyph_cache, k);
          203 
          204         LtkTextSegment *ts = malloc(sizeof(LtkTextSegment));
          205         if (!ts) {
          206                 (void)fprintf(stderr, "Out of memory!\n");
          207                 exit(1);
          208         }
          209         ts->str = malloc(sizeof(uint32_t) * (len + 1));
          210         memcpy(ts->str, text, len * sizeof(uint32_t));
          211         ts->str[len] = '\0';
          212         ts->font_id = fontid;
          213         ts->font_size = size;
          214 
          215         hb_buffer_t *buf;
          216         hb_glyph_info_t *ginf, *gi;
          217         hb_glyph_position_t *gpos, *gp;
          218         unsigned int text_len = 0;
          219         if (len < 1) {
          220                 (void)printf("WARNING: ltk_render_text_segment: length of text is less than 1.\n");
          221                 return NULL;
          222         }
          223 
          224         buf = hb_buffer_create();
          225         hb_direction_t dir = hb_script_get_horizontal_direction(script);
          226         hb_buffer_set_direction(buf, dir);
          227         hb_buffer_set_script(buf, script);
          228         hb_buffer_add_utf32(buf, ts->str, len, 0, len);
          229         /* According to https://harfbuzz.github.io/the-distinction-between-levels-0-and-1.html
          230          * this should be level 1 clustering instead of level 0 */
          231         hb_buffer_set_cluster_level(buf, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS);
          232         hb_shape(font->hb, buf, NULL, 0);
          233         ts->dir = hb_buffer_get_direction(buf);
          234         ginf = hb_buffer_get_glyph_infos(buf, &text_len);
          235         gpos = hb_buffer_get_glyph_positions(buf, &text_len);
          236         float scale = stbtt_ScaleForMappingEmToPixels(&font->info, size);
          237         LtkGlyph *last_glyph = NULL;
          238 
          239         /* FIXME: read https://harfbuzz.github.io/harfbuzz-hb-buffer.html#hb-glyph-position-t-struct */
          240 
          241         int x_min = INT_MAX, x_max = INT_MIN, y_min = INT_MAX, y_max = INT_MIN;
          242         int x_abs = 0, y_abs = 0, x1_abs, y1_abs, x2_abs, y2_abs;
          243         /* magic, do not touch */
          244         LtkGlyph *glyph;
          245         for (int i = 0; i < text_len; i++) {
          246                 gi = &ginf[i];
          247                 gp = &gpos[i];
          248                 glyph = malloc(sizeof(LtkGlyph));
          249                 glyph->info = ltk_get_glyph_info(font, gi->codepoint, scale, glyph_cache);
          250                 glyph->info->refs++;
          251                 /* FIXME: round instead of just casting */
          252                 glyph->x_offset = (int)(gp->x_offset * scale);
          253                 glyph->y_offset = (int)(gp->y_offset * scale);
          254                 glyph->x_advance = (int)(gp->x_advance * scale);
          255                 glyph->y_advance = (int)(gp->y_advance * scale);
          256                 glyph->next = NULL;
          257                 if (i == 0) {
          258                         ts->start_glyph = glyph;
          259                 } else {
          260                         last_glyph->next = glyph;
          261                 }
          262                 last_glyph = glyph;
          263 
          264                 /* Calculate position in order to determine full size of text segment */
          265                 x1_abs = x_abs + glyph->info->xoff + glyph->x_offset;
          266                 y1_abs = y_abs + glyph->info->yoff - glyph->y_offset;
          267                 /* Okay, wait, so should I check if the script is horizontal, and then add
          268                    x_advance instead of glyph->info->w? It seems that the glyph width is
          269                    usually smaller than x_advance, and spaces etc. are completely lost
          270                    because their glyph width is 0. I have to distinguish between horizontal
          271                    and vertical scripts, though because to calculate the maximum y position
          272                    for horizontal scripts, I still need to use the glyph height since
          273                    y_advance doesn't really do much there. I dunno, at least *something*
          274                    works now... */
          275                 /* FIXME: THIS PROBABLY DOESN'T REALLY WORK */
          276                 /* Wait, why do I calculate abs here and don't use it while rendering? */
          277                 /* Oh, it can't be calculated after figuring out the max and min points
          278                    of the entire line */
          279                 if (HB_DIRECTION_IS_HORIZONTAL(dir)) {
          280                         x2_abs = x1_abs + glyph->x_advance;
          281                         y2_abs = y1_abs + glyph->info->h;
          282                 } else {
          283                         x2_abs = x1_abs + glyph->info->w;
          284                         y2_abs = y1_abs - glyph->y_advance;
          285                 }
          286                 glyph->x_abs = x1_abs;
          287                 glyph->y_abs = y1_abs;
          288                 if (x1_abs < x_min) x_min = x1_abs;
          289                 if (y1_abs < y_min) y_min = y1_abs;
          290                 if (x2_abs > x_max) x_max = x2_abs;
          291                 if (y2_abs > y_max) y_max = y2_abs;
          292                 x_abs += glyph->x_advance;
          293                 y_abs -= glyph->y_advance;
          294         }
          295         ts->start_x = -x_min;
          296         ts->start_y = -y_min;
          297         ts->w = x_max - x_min;
          298         ts->h = y_max - y_min;
          299         ts->x_min = x_min;
          300         ts->y_min = y_min;
          301         ts->x_max = x_max;
          302         ts->y_max = y_max;
          303 
          304         font->refs++;
          305         /* FIXME: destroy hb_buffer */
          306 
          307         return ts;
          308 }
          309 
          310 void
          311 ltk_destroy_text_segment(LtkTextSegment *ts)
          312 {
          313         LtkGlyph *glyph, *next_glyph;
          314         khash_t(glyphinfo) *gcache;
          315         LtkFont *font;
          316         int k;
          317         glyph = ts->start_glyph;
          318         k = kh_get(glyphinfo, ltk_global->tm->glyph_cache, ts->font_id << 16 + ts->font_size);
          319         gcache = kh_value(ltk_global->tm->glyph_cache, k);
          320         do {
          321                 next_glyph = glyph->next;
          322                 ltk_destroy_glyph(glyph, gcache);
          323         } while (glyph = next_glyph);
          324         k = kh_get(fontstruct, ltk_global->tm->font_cache, ts->font_id);
          325         font = kh_value(ltk_global->tm->font_cache, k);
          326         if (--font->refs < 1) {
          327                 kh_del(fontstruct, ltk_global->tm->font_cache, k);
          328                 ltk_destroy_font(font);
          329         }
          330         free(ts->str);
          331         free(ts);
          332 }
          333 
          334 /* based on http://codemadness.org/git/dwm-font/file/drw.c.html#l315 */
          335 XImage *
          336 ltk_render_text_line(
          337         LtkTextLine *tl,
          338         Display *dpy,
          339         Window window,
          340         GC gc,
          341         Colormap colormap,
          342         XColor fg,
          343         XColor bg)
          344 {
          345         XWindowAttributes attrs;
          346         XGetWindowAttributes(dpy, window, &attrs);
          347         int depth = attrs.depth;
          348         XImage *img = XCreateImage(dpy, CopyFromParent, depth, ZPixmap, 0, NULL, tl->w, tl->h, 32, 0);
          349         img->data = calloc(img->bytes_per_line, img->height);
          350         XInitImage(img);
          351         int b;
          352         for (int i = 0; i < tl->h; i++) {
          353                 b = img->bytes_per_line * i;
          354                 for (int j = 0; j < tl->w; j++) {
          355                         img->data[b++] = bg.blue / 257;
          356                         img->data[b++] = bg.green / 257;
          357                         img->data[b++] = bg.red / 257;
          358                         b++;
          359                 }
          360         }
          361 
          362         LtkTextSegment *ts = tl->start_segment;
          363         int x = 0;
          364         int y = 0;
          365         int is_hor = HB_DIRECTION_IS_HORIZONTAL(ts->dir);
          366         do {
          367                 if (is_hor) {
          368                         y = tl->h - tl->y_max;
          369                         ltk_render_text_segment(ts, x + ts->start_x, y, img, fg);
          370                         x += ts->w;
          371                 } else {
          372                         x = tl->w - tl->x_max;
          373                         ltk_render_text_segment(ts, x, y + ts->start_y, img, fg);
          374                         y += ts->h;
          375                 }
          376         } while (ts = ts->next);
          377 
          378         return img;
          379 }
          380 
          381 void
          382 ltk_render_text_segment(
          383         LtkTextSegment *ts,
          384         unsigned int start_x,
          385         unsigned int start_y,
          386         XImage *img,
          387         XColor fg)
          388 {
          389         LtkGlyph *glyph = ts->start_glyph;
          390         int x_cur = start_x;
          391         int y_cur = start_y;
          392         int x, y;
          393         double a;
          394         int b;
          395         do {
          396                 x = x_cur + glyph->info->xoff + glyph->x_offset;
          397                 y = y_cur + glyph->info->yoff - glyph->y_offset;
          398                 for (int i = 0; i < glyph->info->h; i++) {
          399                         for (int j = 0; j < glyph->info->w; j++) {
          400                                 b = (y + i) * img->bytes_per_line + (x + j) * 4;
          401                                 a = glyph->info->alphamap[i * glyph->info->w + j] / 255.0;
          402                                 img->data[b] = (fg.blue * a + (1 - a) * (uint16_t)img->data[b] * 257) / 257;
          403                                 img->data[b + 1] = (fg.green * a + (1 - a) * (uint16_t)img->data[b + 1] * 257) / 257;
          404                                 img->data[b + 2] = (fg.red * a + (1 - a) * (uint16_t)img->data[b + 2] * 257) / 257;
          405                         }
          406                 }
          407                 x_cur += glyph->x_advance;
          408                 y_cur -= glyph->y_advance;
          409         } while (glyph = glyph->next);
          410 }