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 }