text-hb.c - ltkx - GUI toolkit for X11 (old)
HTML git clone git://lumidify.org/ltkx.git (fast, but not encrypted)
HTML git clone https://lumidify.org/ltkx.git (encrypted, but very slow)
HTML git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ltkx.git (over tor)
DIR Log
DIR Files
DIR Refs
DIR README
DIR LICENSE
---
text-hb.c (11344B)
---
1 /*
2 * This file is part of the Lumidify ToolKit (LTK)
3 * Copyright (c) 2017, 2018 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 <harfbuzz/hb.h>
31 #include <harfbuzz/hb-ot.h>
32 #define STB_TRUETYPE_IMPLEMENTATION
33 #include "stb_truetype.h" /* http://nothings.org/stb/stb_truetype.h */
34 #include "khash.h"
35
36 /* TODO: possibly "glyph manager" - only render glyph once and keep info in hash?
37 -> would be difficult because of different sizes - would need to keep track of all that.
38 -> reference counter - delete glyph from cache if not used anymore - good when there are many ligatures */
39
40 /* Font manager: hash for font path -> font id
41 hash for font id -> ltk font struct */
42
43 /* FIXME: this needs to be uint32_t, NOT just int! */
44 /* font path, size -> glyph cache hash */
45 KHASH_MAP_INIT_INT(ihash, khash_t(iglyph))
46 /* glyph id -> glyph info struct */
47 KHASH_MAP_INIT_INT(iglyph, LtkGlyphInfo*)
48 /* font path -> font struct */
49 KHASH_MAP_INIT_STR(cfont, LtkFont*)
50
51 typedef struct LtkTextManager_ {
52 khash_t(cfont) *font_cache;
53 khash_t(ihash) *glyph_cache;
54 unsigned int font_id_cur;
55 } LtkTextManager;
56
57 /* Make LtkFont union of LtkFontHB, LtkFontGR, and LtkFontLT to handle harfbuzz, graphite, and normal latin? */
58 typedef struct {
59 stbtt_fontinfo info;
60 hb_font_t *hb;
61 unsigned int id;
62 } LtkFont;
63
64 void
65 ltk_render_glyph(LtkFont *font, unsigned int id, float scale, khash_t(iglyph) *cache)
66 {
67 int x1, y1, x_off, y_off;
68 int ret;
69 khiter t;
70
71 LtkGlyphInfo *info = malloc(sizeof(LtkGlyphInfo));
72 info->alphamap = stbtt_GetGlyphBitmap(&font->font_info, scale, scale, id, info->w, info->h, info->x_topleft, info->y_topleft);
73 k = kh_put(iglyph, cache, info, &ret);
74 /* x/y_advance? */
75 }
76
77 LtkTextManager *
78 ltk_init_text(void)
79 {
80 LtkTextManager *m = malloc(sizeof LtkTextManager);
81 if (!m) ltk_fatal("Memory exhausted when trying to create text manager.");
82 m->font_cache = kh_init(cfont);
83 m->glyph_cache = kh_init(ihash);
84 m->font_id_cur = 0;
85 return m;
86 }
87
88 void
89 ltk_create_glyph_cache(LtkTextManager *m, unsigned int font_id, unsigned int font_size)
90 {
91 khash_t(iglyph) *cache = kh_init(iglyph);
92 int ret;
93 khiter_t k;
94 /* I guess I can just ignore ret for now */
95 k = kh_put(iglyph, m->glyph_cache, cache, &ret);
96 }
97
98
99
100 char *
101 ltk_load_file(const char *path, unsigned long *len)
102 {
103 FILE *f;
104 char *contents;
105 f = fopen(path, "rb");
106 fseek(f, 0, SEEK_END);
107 *len = ftell(f);
108 fseek(f, 0, SEEK_SET);
109 contents = malloc(*len + 1);
110 fread(contents, 1, *len, f);
111 contents[*len] = '\0';
112 fclose(f);
113 return contents;
114 }
115
116 unsigned long
117 ltk_blend_pixel(Display *display, Colormap colormap, XColor fg, XColor bg, double a)
118 {
119 if (a >= 1.0) {
120 return fg.pixel;
121 } else if (a == 0.0) {
122 return bg.pixel;
123 }
124
125 XColor blended;
126 blended.red = (int)((fg.red - bg.red) * a + bg.red);
127 blended.green = (int)((fg.green - bg.green) * a + bg.green);
128 blended.blue = (int)((fg.blue - bg.blue) * a + bg.blue);
129 XAllocColor(display, colormap, &blended);
130
131 return blended.pixel;
132 }
133
134 /* Contains general info on glyphs that doesn't change regardless of the context */
135 typedef struct _LtkGlyphInfo {
136 unsigned char *alphamap;
137 unsigned int w;
138 unsigned int h;
139 unsigned int x_topleft; /* x offset from origin to top left corner of glyph */
140 unsigned int y_topleft; /* y offset from origin to top left corner of glyph */
141 } LtkGlyphInfo;
142
143 /* Contains glyph info specific to one run of text */
144 typedef struct _LtkGlyph {
145 LtkGlyphInfo *glyph_info;
146 /* Does all this need to be stored or are just the top left coordinates needed? */
147 unsigned int x; /* top left x coordinate */
148 unsigned int y; /* top left y coordinate */
149 unsigned int x_offset; /* additional x offset given by harfbuzz */
150 unsigned int y_offset; /* additional y offset given by harfbuzz */
151 unsigned int x_advance;
152 unsigned int y_advance;
153 uint32_t cluster; /* index of char in original text - from harfbuzz */
154 struct _LtkGlyph *next;
155 } LtkGlyph;
156
157 typedef struct {
158 unsigned int width;
159 unsigned int height;
160 char *str;
161 LtkGlyph *start_glyph;
162 } LtkTextSegment;
163
164 LtkFont *
165 ltk_load_font(char *path, unsigned int id)
166 {
167 long len;
168 LtkFont *font = malloc(sizeof(LtkFont));
169 if (!font) {
170 fprintf(stderr, "Out of memory!\n");
171 exit(1);
172 }
173 char *contents = ltk_load_file(path, &len);
174 if (!stbtt_InitFont(&font->font_info, contents, 0))
175 {
176 fprintf(stderr, "Failed to load font %s\n", path);
177 exit(1);
178 }
179 hb_blob_t *blob = hb_blob_create(contents, len, HB_MEMORY_MODE_READONLY, NULL, NULL);
180 hb_face_t *face = hb_face_create(blob, 0);
181 hb_blob_destroy(blob);
182 font->hb = hb_font_create(face);
183 hb_face_destroy(face);
184 hb_ot_font_set_funcs(font->hb);
185 font->id = id;
186 return font;
187 }
188
189 unsigned char *
190 ltk_render_text_bitmap(
191 uint8_t *text,
192 LtkFont *font,
193 int size,
194 int *width,
195 int *height)
196 {
197 /* (x1*, y1*): top left corner (relative to origin and absolute)
198 (x2*, y2*): bottom right corner (relative to origin and absolute) */
199 int x1, x2, y1, y2, x1_abs, x2_abs, y1_abs, y2_abs, x_abs = 0, y_abs = 0;
200 int x_min = INT_MAX, x_max = INT_MIN, y_min = INT_MAX, y_max = INT_MIN;
201 int char_w, char_h, x_off, y_off;
202
203 int byte_offset;
204 int alpha;
205 /* FIXME: Change to uint8_t? */
206 unsigned char *bitmap;
207 unsigned char *char_bitmap;
208 hb_buffer_t *buf;
209 hb_glyph_info_t *ginf, *gi;
210 hb_glyph_position_t *gpos, *gp;
211 unsigned int text_len = 0;
212 int text_bytes = strlen(text);
213 if (text_bytes < 1) {
214 printf("WARNING: ltk_render_text: length of text is less than 1.\n");
215 return "";
216 }
217
218 buf = hb_buffer_create();
219 hb_buffer_set_flags(buf, HB_BUFFER_FLAG_BOT | HB_BUFFER_FLAG_EOT);
220 hb_buffer_add_utf8(buf, text, text_bytes, 0, text_bytes);
221 hb_buffer_guess_segment_properties(buf);
222 hb_shape(font->font, buf, NULL, 0);
223 ginf = hb_buffer_get_glyph_infos(buf, &text_len);
224 gpos = hb_buffer_get_glyph_positions(buf, &text_len);
225 float scale = stbtt_ScaleForMappingEmToPixels(&font->font_info, size);
226
227 /* Calculate size of bitmap */
228 for (int i = 0; i < text_len; i++) {
229 gi = &ginf[i];
230 gp = &gpos[i];
231 stbtt_GetGlyphBitmapBox(&font->font_info, gi->codepoint, scale, scale, &x1, &y1, &x2, &y2);
232 x1_abs = (int)(x_abs + x1 + gp->x_offset * scale);
233 y1_abs = (int)(y_abs + y1 - gp->y_offset * scale);
234 x2_abs = x1_abs + (x2 - x1);
235 y2_abs = y1_abs + (y2 - y1);
236 if (x1_abs < x_min) x_min = x1_abs;
237 if (y1_abs < y_min) y_min = y1_abs;
238 if (x2_abs > x_max) x_max = x2_abs;
239 if (y2_abs > y_max) y_max = y2_abs;
240 x_abs += (gp->x_advance * scale);
241 y_abs -= (gp->y_advance * scale);
242 }
243 x_abs = -x_min;
244 y_abs = -y_min;
245 *width = x_max - x_min;
246 *height = y_max - y_min;
247 /* FIXME: calloc checks for integer overflow, right? */
248 /* FIXME: check if null returned */
249 bitmap = calloc(*width * *height, sizeof(char));
250 if (!bitmap) {
251 fprintf(stderr, "Can't allocate memory for bitmap!\n");
252 exit(1);
253 }
254 for (int i = 0; i < text_len; i++) {
255 gi = &ginf[i];
256 gp = &gpos[i];
257 stbtt_GetGlyphBitmapBox(&font->font_info, gi->codepoint, scale, scale, &x1, &y1, &x2, &y2);
258 char_bitmap = stbtt_GetGlyphBitmap(&font->font_info, scale, scale, gi->codepoint, &char_w, &char_h, &x_off, &y_off);
259
260 x1_abs = (int)(x_abs + x1 + gp->x_offset * scale);
261 y1_abs = (int)(y_abs + y1 - gp->y_offset * scale);
262 for (int k = 0; k < char_h; k++)
263 {
264 for (int j = 0; j < char_w; j++)
265 {
266 byte_offset = (y1_abs + k) * *width + x1_abs + j;
267 alpha = bitmap[byte_offset] + char_bitmap[k * char_w + j];
268 /* Cap at 255 so char doesn't overflow */
269 bitmap[byte_offset] = alpha > 255 ? 255 : alpha;
270 }
271 }
272 free(char_bitmap);
273
274 x_abs += gp->x_advance * scale;
275 y_abs -= gp->y_advance * scale;
276 }
277 return bitmap;
278 }
279
280 Pixmap
281 ltk_render_text(
282 Display *dpy,
283 Window window,
284 GC gc,
285 XColor fg,
286 XColor bg,
287 Colormap colormap,
288 unsigned char *bitmap,
289 int width,
290 int height)
291 {
292 XWindowAttributes attrs;
293 XGetWindowAttributes(dpy, window, &attrs);
294 int depth = attrs.depth;
295 Pixmap pix = XCreatePixmap(dpy, window, width, height, depth);
296 XSetForeground(dpy, gc, bg.pixel);
297 for (int i = 0; i < height; i++) {
298 for (int j = 0; j < width; j++) {
299 XSetForeground(dpy, gc, ltk_blend_pixel(dpy, colormap, fg, bg, bitmap[i * width + j] / 255.0));
300 XDrawPoint(dpy, pix, gc, j, i);
301 }
302 }
303 return pix;
304 }
305
306 int main(int argc, char *argv[])
307 {
308 Display *display;
309 int screen;
310 Window window;
311 GC gc;
312
313 unsigned long black, white;
314 Colormap colormap;
315 display = XOpenDisplay((char *)0);
316 screen = DefaultScreen(display);
317 colormap = DefaultColormap(display, screen);
318 black = BlackPixel(display, screen);
319 white = WhitePixel(display, screen);
320 window = XCreateSimpleWindow(display, DefaultRootWindow(display), 0, 0, 1366, 512, 0, black, white);
321 XSetStandardProperties(display, window, "Random Window", NULL, None, NULL, 0, NULL);
322 XSelectInput(display, window, ExposureMask|ButtonPressMask|KeyPressMask);
323 gc = XCreateGC(display, window, 0, 0);
324 XSetBackground(display, gc, black);
325 XSetForeground(display, gc, black);
326 XClearWindow(display, window);
327 XMapRaised(display, window);
328 XColor c1, c2;
329 XParseColor(display, colormap, "#FFFFFF", &c1);
330 XParseColor(display, colormap, "#FF0000", &c2);
331 XAllocColor(display, colormap, &c1);
332 XAllocColor(display, colormap, &c2);
333
334 LtkFont *font = ltk_load_font("NotoNastaliqUrdu-Regular.ttf");
335 int w, h;
336 unsigned char *bitmap = ltk_render_text_bitmap("ہمارے بارے میں", font, 256, &w, &h);
337 Pixmap pix = ltk_render_text(display, window, gc, c1, c2, colormap, bitmap, w, h);
338 XCopyArea(display, pix, window, gc, 0, 0, w, h, 0, 0);
339
340 XEvent event;
341 KeySym key;
342 char text[255];
343
344 while(1)
345 {
346 XNextEvent(display, &event);
347 if (event.type == KeyPress && XLookupString(&event.xkey, text, 255, &key, 0) == 1)
348 {
349 XCopyArea(display, pix, window, gc, 0, 0, w, h, 0, 0);
350 if (text[0] == 'q')
351 {
352 XFreeGC(display, gc);
353 XFreeColormap(display, colormap);
354 XDestroyWindow(display, window);
355 XCloseDisplay(display);
356 exit(0);
357 }
358 }
359 }
360
361 return 0;
362 }