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