ltkx

GUI toolkit for X11 (WIP)
git clone git://lumidify.org/ltkx.git
Log | Files | Refs | README | LICENSE

commit 17f7c08af649a3bfe395f77a63980d7d1defbce1
parent 9ad6cdddb14e3bc4594142edb39360a6588c9660
Author: lumidify <nobody@lumidify.org>
Date:   Wed, 18 Mar 2020 19:51:03 +0100

Make some stuff work and other stuff not work

Hey, at least it's displaying some of the text right-to-left.
Wait, that's actually the left-to-right text it's displaying
right-to-left. Crap.
Can't we all just use ASCII?

Diffstat:
MMakefile | 4++--
MNOTES | 3+++
Mbutton.c | 19+++++++++----------
Mbutton.h | 2+-
Mltk.c | 12++++++------
Mltk.h | 2+-
Mtest1.c | 3++-
Mtext-hb.c | 449+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mtext-hb.h | 39++++++++++++++++++++++++++++++++++-----
Mthemes/default.ini | 2+-
Atmpfb.txt | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atmpnofb.txt | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
12 files changed, 615 insertions(+), 50 deletions(-)

diff --git a/Makefile b/Makefile @@ -1,6 +1,6 @@ -LIBS = -lm `pkg-config --libs x11 harfbuzz` +LIBS = -lm `pkg-config --libs x11 harfbuzz fontconfig fribidi` STD = -std=c99 -FLAGS = -g -w -fcommon -Wall -Werror -Wextra `pkg-config --cflags x11 harfbuzz`#-pedantic +FLAGS = -g -w -fcommon -Wall -Werror -Wextra `pkg-config --cflags x11 harfbuzz fontconfig fribidi`#-pedantic CFILES = text-hb.c ltk.c ini.c grid.c button.c test1.c all: test1.c diff --git a/NOTES b/NOTES @@ -5,3 +5,6 @@ HarfBuzz - need to add option to LTK functions to allow user to choose language LtkTextSegment - just use array instead of linked list - just need to get actual number of glyphs in advance from hb Add void* to LtkWidget to hold specific widget instead of doing weird casts to simulate OOP When the screen size is set differently by the window manager on startup, the original drawing stays on screen - any way to fix this? +Change list of windows to simple array or binary search tree - hash overkill since there usually won't be too many windows + -> maybe optimize for common case of one window so no need to search at all +Fribidi - needs to be run on entire paragraph first; line breaking later - needs to take paragraph dir into account diff --git a/button.c b/button.c @@ -108,7 +108,7 @@ void ltk_draw_button(LtkButton *button) XSetLineAttributes(ltk_global->display, window->gc, bw, LineSolid, CapButt, JoinMiter); XDrawRectangle(ltk_global->display, window->xwindow, window->gc, rect.x + bw / 2, rect.y + bw / 2, rect.w - bw, rect.h - bw); if (!img) { - img = ltk_render_text_segment(button->ts, ltk_global->display, window->xwindow, window->gc, ltk_global->colormap, theme->text_color, fill); + img = ltk_render_text_line(button->tl, ltk_global->display, window->xwindow, window->gc, ltk_global->colormap, theme->text_color, fill); /* FIXME: any nicer way to do this? */ switch (button->widget.state) { case LTK_NORMAL: @@ -129,13 +129,12 @@ void ltk_draw_button(LtkButton *button) break; } } - text_x = rect.x + (rect.w - button->ts->w) / 2; - text_y = rect.y + (rect.h - button->ts->h) / 2; - XPutImage(ltk_global->display, window->xwindow, window->gc, img, 0, 0, text_x, text_y, button->ts->w, button->ts->h); + text_x = rect.x + (rect.w - button->tl->w) / 2; + text_y = rect.y + (rect.h - button->tl->h) / 2; + XPutImage(ltk_global->display, window->xwindow, window->gc, img, 0, 0, text_x, text_y, button->tl->w, button->tl->h); } -LtkButton *ltk_create_button(LtkWindow *window, const char *text, - void (*callback) (void)) +LtkButton *ltk_create_button(LtkWindow *window, const char *text, void (*callback) (void)) { LtkButton *button = malloc(sizeof(LtkButton)); @@ -148,9 +147,9 @@ LtkButton *ltk_create_button(LtkWindow *window, const char *text, button->callback = callback; LtkTheme *theme = ltk_global->theme; - button->ts = ltk_create_text_segment(ltk_global->tm, text, ltk_global->default_font, theme->button->font_size); - button->widget.rect.w = button->ts->w + (theme->button->border_width + theme->button->pad) * 2; - button->widget.rect.h = button->ts->h + (theme->button->border_width + theme->button->pad) * 2; + button->tl = ltk_create_text_line(ltk_global->tm, text, ltk_global->tm->default_font, theme->button->font_size); + button->widget.rect.w = button->tl->w + (theme->button->border_width + theme->button->pad) * 2; + button->widget.rect.h = button->tl->h + (theme->button->border_width + theme->button->pad) * 2; button->text = NULL; button->text_pressed = NULL; button->text_hover = NULL; @@ -171,7 +170,7 @@ void ltk_destroy_button(void *widget) if (button->text_pressed) XDestroyImage(button->text_pressed); if (button->text_active) XDestroyImage(button->text_active); if (button->text_disabled) XDestroyImage(button->text_disabled); - ltk_destroy_text_segment(button->ts); + //ltk_destroy_text_segment(button->ts); free(button); } diff --git a/button.h b/button.h @@ -29,7 +29,7 @@ typedef struct { LtkWidget widget; void (*callback) (void); - LtkTextSegment *ts; + LtkTextLine *tl; XImage *text; XImage *text_hover; XImage *text_pressed; diff --git a/ltk.c b/ltk.c @@ -34,8 +34,7 @@ void ltk_init(const char *theme_path) ltk->theme = ltk_load_theme(theme_path); ltk->window_hash = kh_init(winhash); ltk->wm_delete_msg = XInternAtom(ltk->display, "WM_DELETE_WINDOW", False); - ltk->tm = ltk_init_text(); - ltk->default_font = ltk_get_font(ltk->tm, ltk->theme->window->font); + ltk->tm = ltk_init_text(ltk->theme->window->font); } void ltk_clean_up(void) @@ -149,14 +148,15 @@ LtkWindow *ltk_create_window(const char *title, int x, int y, int ret; int k = kh_put(winhash, ltk_global->window_hash, window->xwindow, &ret); kh_value(ltk_global->window_hash, k) = window; + ltk_global->window_num++; return window; } -void ltk_remove_window(LtkWindow * window) +void ltk_remove_window(LtkWindow *window) { ltk_destroy_window(window); - if (!ltk_global->window_hash) + if (ltk_global->window_num == 0) ltk_quit(); } @@ -169,6 +169,7 @@ void ltk_destroy_window(LtkWindow * window) ptr->destroy(ptr); XDestroyWindow(ltk_global->display, window->xwindow); free(window); + ltk_global->window_num--; } void ltk_window_other_event(void *widget, XEvent event) @@ -433,8 +434,7 @@ void ltk_handle_event(XEvent event) LtkWidget *root_widget; int k = kh_get(winhash, ltk_global->window_hash, event.xany.window); window = kh_value(ltk_global->window_hash, k); - if (!window) - return; + if (!window) return; root_widget = window->root_widget; switch (event.type) { case KeyPress: diff --git a/ltk.h b/ltk.h @@ -116,11 +116,11 @@ LtkTheme *ltk_load_theme(const char *path); typedef struct { LtkTheme *theme; LtkTextManager *tm; - uint16_t default_font; Display *display; int screen; Colormap colormap; khash_t(winhash) *window_hash; + int window_num; Atom wm_delete_msg; } Ltk; diff --git a/test1.c b/test1.c @@ -33,7 +33,8 @@ int main(int argc, char *argv[]) ltk_grid_widget(button2, grid1, 0, 1, 1, 1, LTK_STICKY_TOP | LTK_STICKY_BOTTOM); LtkButton *button3 = ltk_create_button(window1, "I'm a button!", NULL); ltk_grid_widget(button3, grid1, 1, 0, 1, 1, LTK_STICKY_TOP | LTK_STICKY_BOTTOM | LTK_STICKY_RIGHT); - LtkButton *button4 = ltk_create_button(window1, "I'm a button!", NULL); + //LtkButton *button4 = ltk_create_button(window1, "I'm a button!", NULL); + LtkButton *button4 = ltk_create_button(window1, "ہمارے بارے میں blablabla", NULL); ltk_grid_widget(button4, grid1, 1, 1, 1, 1, LTK_STICKY_LEFT | LTK_STICKY_BOTTOM); ltk_mainloop(); } diff --git a/text-hb.c b/text-hb.c @@ -27,12 +27,85 @@ #include <limits.h> #include <X11/Xlib.h> #include <X11/Xutil.h> +#include <fontconfig/fontconfig.h> #include "ltk.h" #include "khash.h" #include "text-hb.h" +#include <fribidi.h> + +/* These unicode routines are taken from + * https://github.com/JeffBezanson/cutef8 */ + +/* is c the start of a utf8 sequence? */ +#define isutf(c) (((c)&0xC0)!=0x80) + +static const uint32_t offsetsFromUTF8[6] = { + 0x00000000UL, 0x00003080UL, 0x000E2080UL, + 0x03C82080UL, 0xFA082080UL, 0x82082080UL +}; + +/* next character without NUL character terminator */ +uint32_t u8_nextmemchar(const char *s, size_t *i) +{ + uint32_t ch = 0; + size_t sz = 0; + do { + ch <<= 6; + ch += (unsigned char)s[(*i)++]; + sz++; + } while (!isutf(s[*i])); + ch -= offsetsFromUTF8[sz-1]; + + return ch; +} + +/* number of characters in NUL-terminated string */ +size_t u8_strlen(const char *s) +{ + size_t count = 0; + size_t i = 0, lasti; + + while (1) { + lasti = i; + while (s[i] > 0) + i++; + count += (i-lasti); + if (s[i++]==0) break; + (void)(isutf(s[++i]) || isutf(s[++i]) || ++i); + count++; + } + return count; +} + +size_t u8_wc_toutf8(char *dest, uint32_t ch) +{ + if (ch < 0x80) { + dest[0] = (char)ch; + return 1; + } + if (ch < 0x800) { + dest[0] = (ch>>6) | 0xC0; + dest[1] = (ch & 0x3F) | 0x80; + return 2; + } + if (ch < 0x10000) { + dest[0] = (ch>>12) | 0xE0; + dest[1] = ((ch>>6) & 0x3F) | 0x80; + dest[2] = (ch & 0x3F) | 0x80; + return 3; + } + if (ch < 0x110000) { + dest[0] = (ch>>18) | 0xF0; + dest[1] = ((ch>>12) & 0x3F) | 0x80; + dest[2] = ((ch>>6) & 0x3F) | 0x80; + dest[3] = (ch & 0x3F) | 0x80; + return 4; + } + return 0; +} LtkTextManager * -ltk_init_text(void) +ltk_init_text(char *font_name) { LtkTextManager *tm = malloc(sizeof(LtkTextManager)); if (!tm) { @@ -43,6 +116,7 @@ ltk_init_text(void) tm->font_cache = kh_init(fontstruct); tm->glyph_cache = kh_init(glyphcache); tm->font_id_cur = 0; + ltk_load_default_font(tm, font_name); return tm; } @@ -142,6 +216,32 @@ ltk_destroy_glyph_cache(khash_t(glyphinfo) *cache) kh_destroy(glyphinfo, cache); } +void +ltk_load_default_font(LtkTextManager *tm, char *name) +{ + FcPattern *match; + FcResult result; + char *file; + int index; + uint16_t font; + + tm->fcpattern = FcNameParse(name); + FcPatternAddString(tm->fcpattern, FC_FONTFORMAT, "truetype"); + FcConfigSubstitute(NULL, tm->fcpattern, FcMatchPattern); + FcDefaultSubstitute(tm->fcpattern); + match = FcFontMatch(NULL, tm->fcpattern, &result); + + FcPatternGetString (match, FC_FILE, 0, (FcChar8 **) &file); + /* FIXME: Why is index never used? This is the index within the font file, + so it might be important, although I'm not sure if stb_truetype even + supports it */ + FcPatternGetInteger (match, FC_INDEX, 0, &index); + + tm->default_font = ltk_get_font(tm, file); + + FcPatternDestroy (match); +} + LtkFont * ltk_create_font(char *path, uint16_t id) { @@ -179,17 +279,19 @@ ltk_destroy_font(LtkFont *font) free(font); } -/* FIXME: need to figure out how exactly the whole font system is going to work, especially with default fonts, etc. */ uint16_t ltk_load_font(LtkTextManager *tm, char *path) { LtkFont *font = ltk_create_font(path, tm->font_id_cur++); int ret; khint_t k; - k = kh_put(fontid, tm->font_paths, path, &ret); + /* FIXME: does kh_destroy also free these copied strings properly? */ + char *key = strdup(path); + k = kh_put(fontid, tm->font_paths, key, &ret); kh_value(tm->font_paths, k) = font->id; k = kh_put(fontstruct, tm->font_cache, (khint_t) font->id, &ret); kh_value(tm->font_cache, k) = font; + k = kh_get(fontid, tm->font_paths, path); return font->id; } @@ -210,16 +312,279 @@ ltk_get_font(LtkTextManager *tm, char *path) return id; } +/* FIXME: allow to either use fribidi for basic shaping and don't use harfbuzz then, + or just use harfbuzz (then fribidi doesn't need to do any shaping) */ +/* FIXME: take baseline into account; save script in LtkTextSegment */ +LtkTextLine * +ltk_create_text_line(LtkTextManager *tm, char *text, uint16_t fontid, uint16_t size) +{ + LtkFont *font; + LtkFont *default_font; + LtkTextLine *tl = malloc(sizeof(LtkTextLine)); + tl->w = 0; + tl->h = 0; + tl->start_segment = NULL; + LtkTextSegment *cur_ts = NULL; + LtkTextSegment *new_ts = NULL; + uint16_t default_font_id = fontid; + uint16_t last_font_id = fontid; + uint16_t cur_font_id = fontid; + int k = kh_get(fontstruct, tm->font_cache, fontid); + font = default_font = kh_value(tm->font_cache, k); + + unsigned int ulen = u8_strlen(text); + FriBidiChar *log_str = malloc(sizeof(FriBidiChar) * ulen); + size_t inc = 0; + for (int i = 0; i < ulen; i++) { + log_str[i] = u8_nextmemchar(text, &inc); + } + FriBidiCharType *pbase_dir = malloc(sizeof(FriBidiCharType) * ulen); + for (int i = 0; i < ulen; i++) { + pbase_dir[i] = FRIBIDI_TYPE_ON; + } + FriBidiChar *vis_str = malloc(sizeof(FriBidiChar) * ulen); + ulen = fribidi_charset_to_unicode(FRIBIDI_CHAR_SET_UTF8, text, strlen(text), log_str); + fribidi_log2vis(log_str, ulen, pbase_dir, vis_str, NULL, NULL, NULL); + free(log_str); + free(pbase_dir); + + hb_unicode_funcs_t *ufuncs = hb_unicode_funcs_get_default(); + hb_script_t cur_script = hb_unicode_script(ufuncs, vis_str[0]); + hb_script_t last_script = cur_script; + size_t pos = 0; + size_t last_pos = 0; + size_t start_pos = 0; + uint32_t ch; + uint32_t gid; + for (int p = 0; p < ulen; p++) { + gid = stbtt_FindGlyphIndex(&font->info, vis_str[p]); + cur_script = hb_unicode_script(ufuncs, vis_str[p]); + if (!gid || (last_script != cur_script && cur_script != HB_SCRIPT_INHERITED && cur_script != HB_SCRIPT_COMMON)) { + /* This is extremely efficient... */ + FcPattern *pat = FcPatternDuplicate(tm->fcpattern); + FcPattern *match; + FcResult result; + FcPatternAddBool(pat, FC_SCALABLE, 1); + FcConfigSubstitute(NULL, pat, FcMatchPattern); + FcDefaultSubstitute(pat); + FcCharSet *cs = FcCharSetCreate(); + FcCharSetAddChar(cs, vis_str[p]); + FcPatternAddCharSet(pat, FC_CHARSET, cs); + match = FcFontMatch(NULL, pat, &result); + char *file; + FcPatternGetString(match, FC_FILE, 0, &file); + last_font_id = cur_font_id; + cur_font_id = ltk_get_font(tm, file); + k = kh_get(fontstruct, tm->font_cache, cur_font_id); + font = kh_value(tm->font_cache, k); + FcPatternDestroy(match); + FcPatternDestroy(pat); + + new_ts = ltk_create_text_segment(tm, vis_str + start_pos, p - start_pos, last_font_id, size, cur_script); + // FIXME: error + if (!new_ts) continue; + new_ts->next = NULL; + if (!tl->start_segment) tl->start_segment = new_ts; + if (cur_ts) cur_ts->next = new_ts; + cur_ts = new_ts; + start_pos = p; + + last_script = cur_script; + } + } + new_ts = ltk_create_text_segment(tm, vis_str + start_pos, ulen - start_pos, cur_font_id, size, cur_script); + // FIXME: error if new_ts null + new_ts->next = NULL; + if (!tl->start_segment) tl->start_segment = new_ts; + if (cur_ts) cur_ts->next = new_ts; + cur_ts = new_ts; + + free(vis_str); + + /* calculate width of text line + NOTE: doesn't work with mixed horizontal and vertical text */ + LtkTextSegment *ts = tl->start_segment; + int is_hor = HB_DIRECTION_IS_HORIZONTAL(ts->dir); + while (ts) { + if (is_hor) { + if (tl->h < ts->h) { + tl->h = ts->h; + } + tl->w += ts->w; + } else { + if (tl->w < ts->w) { + tl->w = ts->w; + } + tl->h += ts->h; + } + ts = ts->next; + } + + return tl; +} + +#if 0 +LtkTextLine * +ltk_create_text_line(LtkTextManager *tm, char *text, uint16_t fontid, uint16_t size) +{ + size_t pos = 0; + size_t last_pos = 0; + size_t start_pos = 0; + unsigned int len = strlen(text); + uint32_t ch; + uint32_t gid; + LtkFont *font; + LtkFont *default_font; + LtkTextLine *tl = malloc(sizeof(LtkTextLine)); + tl->w = 0; + tl->h = 0; + tl->start_segment = NULL; + LtkTextSegment *cur_ts = NULL; + LtkTextSegment *new_ts = NULL; + uint16_t default_font_id = fontid; + uint16_t last_font_id = fontid; + uint16_t cur_font_id = fontid; + int k = kh_get(fontstruct, tm->font_cache, fontid); + font = default_font = kh_value(tm->font_cache, k); + int utlen = u8_strlen(text); + + FriBidiChar *us = malloc(sizeof(FriBidiChar) * utlen); + int ulen = fribidi_charset_to_unicode(FRIBIDI_CHAR_SET_UTF8, text, len, us); + + uint32_t* tmp_vis = malloc(sizeof(uint32_t) * utlen); + FriBidiStrIndex *tmp_pos_log2vis = malloc(sizeof(FriBidiStrIndex) * utlen); + FriBidiCharType *tmp_bidi_types = malloc(sizeof(FriBidiCharType) * utlen); + FriBidiLevel *tmp_embed_lvls = malloc(sizeof(FriBidiLevel) * utlen); + FriBidiJoiningType *tmp_join_types = malloc(sizeof(FriBidiJoiningType) * utlen); + FriBidiArabicProp *tmp_ar_props = malloc(sizeof(FriBidiArabicProp) * utlen); + + fribidi_get_bidi_types(us, ulen, tmp_bidi_types); + FriBidiParType basedir = FRIBIDI_PAR_LTR; + FriBidiLevel resolve_par_dir = fribidi_get_par_embedding_levels(tmp_bidi_types, ulen, &basedir, tmp_embed_lvls); + //for (int i = 0; i < utlen; i++) {printf("%d ", tmp_bidi_types[i]);}; printf("\n"); + fribidi_get_joining_types(us, ulen, tmp_join_types); + memcpy(tmp_ar_props, tmp_join_types, ulen * sizeof(FriBidiJoiningType)); + fribidi_join_arabic(tmp_bidi_types, ulen, tmp_embed_lvls, tmp_ar_props); + + //fribidi_shape(FRIBIDI_FLAG_SHAPE_MIRRORING | FRIBIDI_FLAG_SHAPE_ARAB_PRES | FRIBIDI_FLAG_SHAPE_ARAB_LIGA, tmp_embed_lvls, ulen, tmp_ar_props, us); + fribidi_shape(FRIBIDI_FLAG_SHAPE_MIRRORING, tmp_embed_lvls, ulen, tmp_ar_props, us); + for (int i = 0; i < utlen; i++) { + printf("%d ", tmp_embed_lvls[i]); + } + printf("\n"); + + memcpy(tmp_vis, us, sizeof(uint32_t) * ulen); + for (int i = 0; i < ulen; i++) { + tmp_pos_log2vis[i] = i; + } + + FriBidiLevel levels = fribidi_reorder_line(FRIBIDI_FLAGS_ARABIC, tmp_bidi_types, ulen, 0, basedir, tmp_embed_lvls, tmp_vis, tmp_pos_log2vis); + + uint32_t *tmp_log = malloc(sizeof(uint32_t) * ulen); + size_t inc = 0; + for (int i = 0; i < ulen; i++) { + tmp_log[i] = u8_nextmemchar(text, &inc); + } + FriBidiChar *vis = malloc(sizeof(FriBidiChar) * ulen); + for (int i = 0; i < ulen; i++) { + vis[i] = tmp_log[tmp_pos_log2vis[i]]; + //printf("%x %x %x\n", vis[i], tmp_vis[i], tmp_log[i]); + } + /* + FriBidiCharType *pbase_dir = malloc(sizeof(FriBidiCharType) * (ulen + 1)); + for (int i = 0; i < ulen; i++) { + pbase_dir[i] = FRIBIDI_TYPE_ON; + } + fribidi_log2vis(us, ulen, pbase_dir, vis, NULL, NULL, NULL); + */ + char *tmp_i_have_no_clue = malloc(len + 1); + fribidi_charset_to_unicode(FRIBIDI_CHAR_SET_UTF8, text, len, us); + fribidi_unicode_to_charset(FRIBIDI_CHAR_SET_UTF8, tmp_vis, ulen, tmp_i_have_no_clue); + tmp_i_have_no_clue[len] = '\0'; + //printf("%s\n", tmp_i_have_no_clue); + /* NEED TO CREATE NEW SEGMENT WHEN CHANGING DIR!!! + Never mind - hb is forced into LTR anyways since fribidi already did all that stuff + Need to apply this: https://github.com/simoncozens/sile/issues/76#issuecomment-98496101 + Maybe split into runs based on dir or script and start with orig font for each run? + */ + for (int p = 0; p < ulen; p++) { + /* no idea why I get so many null characters... */ + //if (!tmp_vis[p]) continue; + /* just pass gid to hb? */ + gid = stbtt_FindGlyphIndex(&font->info, tmp_vis[p]); + if (!gid || (p > 0 && tmp_embed_lvls[p] != tmp_embed_lvls[p-1])) {//(p > 0 && (tmp_bidi_types[p] & FRIBIDI_MASK_RTL) != (tmp_bidi_types[p-1] & FRIBIDI_MASK_RTL))) { + printf("%d\n", tmp_bidi_types[p] & FRIBIDI_MASK_RTL); + /* This is extremely efficient... */ + FcPattern *pat = FcPatternDuplicate(tm->fcpattern); + FcPattern *match; + FcResult result; + //FcFontSet *sets[] = {NULL}; + FcPatternAddBool(pat, FC_SCALABLE, 1); + FcConfigSubstitute(NULL, pat, FcMatchPattern); + FcDefaultSubstitute(pat); + //sets[0] = FcFontSort(NULL, pat, FcTrue, NULL, &result); + FcCharSet *cs = FcCharSetCreate(); + FcCharSetAddChar(cs, tmp_vis[p]); + FcPatternAddCharSet(pat, FC_CHARSET, cs); + match = FcFontMatch(NULL, pat, &result); + //match = FcFontSetMatch(NULL, sets, FcTrue, pat, &result); + char *file;// = "/usr/local/share/fonts/AwamiNastaliq-1.101/AwamiNastaliq-Regular.ttf"; + FcPatternGetString(match, FC_FILE, 0, &file); + printf("%s\n", file); + last_font_id = cur_font_id; + cur_font_id = ltk_get_font(tm, file); + k = kh_get(fontstruct, tm->font_cache, cur_font_id); + font = kh_value(tm->font_cache, k); + FcPatternDestroy(match); + FcPatternDestroy(pat); + if (p - 1 != start_pos) { + new_ts = ltk_create_text_segment(tm, tmp_vis + start_pos, p - start_pos, last_font_id, size); + if (!new_ts) continue; + new_ts->next = NULL; + if (!tl->start_segment) tl->start_segment = new_ts; + if (cur_ts) cur_ts->next = new_ts; + cur_ts = new_ts; + start_pos = p; + tl->w += new_ts->w; + tl->h += new_ts->h; + } + } + if (p == ulen - 1 && p != start_pos) { + printf("%d\n", tmp_bidi_types[p] & FRIBIDI_MASK_RTL); + new_ts = ltk_create_text_segment(tm, tmp_vis + start_pos, p - start_pos + 1, cur_font_id, size); + if (!new_ts) continue; + new_ts->next = NULL; + if (!tl->start_segment) tl->start_segment = new_ts; + if (cur_ts) cur_ts->next = new_ts; + cur_ts = new_ts; + tl->w += new_ts->w; + tl->h += new_ts->h; + } + } + + return tl; +} +#endif + /* FIXME: could use unsigned int for fontid and size as long as there is code to check neither of them become too large -> in case I want to get rid of uint_16_t, etc. */ LtkTextSegment * -ltk_create_text_segment(LtkTextManager *tm, char *text, uint16_t fontid, uint16_t size) +ltk_create_text_segment(LtkTextManager *tm, uint32_t *text, unsigned int len, uint16_t fontid, uint16_t size, hb_script_t script) { /* (x1*, y1*): top left corner (relative to origin and absolute) (x2*, y2*): bottom right corner (relative to origin and absolute) */ LtkFont *font; khash_t(glyphinfo) *glyph_cache; khint_t k; + char bob[4]; + for (int i = 0; i < len; i++) { + for (int j = 0; j < 3; j++) { + bob[j] = '\0'; + } + u8_wc_toutf8(bob, text[i]); + printf("%s", bob); + } + printf("END\n"); k = kh_get(fontstruct, tm->font_cache, fontid); font = kh_value(tm->font_cache, k); @@ -228,7 +593,7 @@ ltk_create_text_segment(LtkTextManager *tm, char *text, uint16_t fontid, uint16_ font->refs++; uint32_t attr = fontid << 16 + size; - /* FIXME: turn this int ltk_get_glyph_cache */ + /* FIXME: turn this into ltk_get_glyph_cache */ k = kh_get(glyphcache, tm->glyph_cache, attr); if (k == kh_end(tm->glyph_cache)) { k = ltk_create_glyph_cache(tm, fontid, size); @@ -240,8 +605,9 @@ ltk_create_text_segment(LtkTextManager *tm, char *text, uint16_t fontid, uint16_ fprintf(stderr, "Out of memory!\n"); exit(1); } - int tlen = strlen(text); - ts->str = strdup(text); + ts->str = malloc(sizeof(uint32_t) * (len + 1)); + memcpy(ts->str, text, len * sizeof(uint32_t)); + ts->str[len] = '\0'; ts->font_id = fontid; ts->font_size = size; @@ -249,16 +615,21 @@ ltk_create_text_segment(LtkTextManager *tm, char *text, uint16_t fontid, uint16_ hb_glyph_info_t *ginf, *gi; hb_glyph_position_t *gpos, *gp; unsigned int text_len = 0; - int text_bytes = strlen(text); - if (text_bytes < 1) { + if (len < 1) { printf("WARNING: ltk_render_text_segment: length of text is less than 1.\n"); + return NULL; } buf = hb_buffer_create(); - hb_buffer_set_flags(buf, HB_BUFFER_FLAG_BOT | HB_BUFFER_FLAG_EOT); - hb_buffer_add_utf8(buf, text, text_bytes, 0, text_bytes); - hb_buffer_guess_segment_properties(buf); + hb_direction_t dir = hb_script_get_horizontal_direction(script); + hb_buffer_set_direction(buf, dir); + hb_buffer_set_script(buf, script); + //hb_buffer_set_direction(buf, HB_DIRECTION_LTR); + //hb_buffer_set_flags(buf, HB_BUFFER_FLAG_BOT | HB_BUFFER_FLAG_EOT); + hb_buffer_add_codepoints(buf, ts->str, len, 0, len); + //hb_buffer_guess_segment_properties(buf); hb_shape(font->hb, buf, NULL, 0); + ts->dir = hb_buffer_get_direction(buf); ginf = hb_buffer_get_glyph_infos(buf, &text_len); gpos = hb_buffer_get_glyph_positions(buf, &text_len); float scale = stbtt_ScaleForMappingEmToPixels(&font->info, size); @@ -266,6 +637,7 @@ ltk_create_text_segment(LtkTextManager *tm, char *text, uint16_t fontid, uint16_ int x_min = INT_MAX, x_max = INT_MIN, y_min = INT_MAX, y_max = INT_MIN; int x_abs = 0, y_abs = 0, x1_abs, y1_abs, x2_abs, y2_abs; + /* magic, do not touch */ for (int i = 0; i < text_len; i++) { gi = &ginf[i]; gp = &gpos[i]; @@ -289,6 +661,8 @@ ltk_create_text_segment(LtkTextManager *tm, char *text, uint16_t fontid, uint16_ y1_abs = y_abs + glyph->info->yoff - glyph->y_offset; x2_abs = x1_abs + glyph->info->w; y2_abs = y1_abs + glyph->info->h; + glyph->x_abs = x1_abs; + glyph->y_abs = y1_abs; if (x1_abs < x_min) x_min = x1_abs; if (y1_abs < y_min) y_min = y1_abs; if (x2_abs > x_max) x_max = x2_abs; @@ -306,6 +680,10 @@ ltk_create_text_segment(LtkTextManager *tm, char *text, uint16_t fontid, uint16_ ts->start_y = -y_min; ts->w = x_max - x_min; ts->h = y_max - y_min; + ts->x_min = x_min; + ts->y_min = y_min; + ts->x_max = x_max; + ts->y_max = y_max; return ts; } @@ -345,9 +723,10 @@ ltk_destroy_text_segment(LtkTextSegment *ts) free(ts); } +/* based on http://codemadness.org/git/dwm-font/file/drw.c.html#l315 */ XImage * -ltk_render_text_segment( - LtkTextSegment *ts, +ltk_render_text_line( + LtkTextLine *tl, Display *dpy, Window window, GC gc, @@ -355,17 +734,16 @@ ltk_render_text_segment( XColor fg, XColor bg) { - /* based on http://codemadness.org/git/dwm-font/file/drw.c.html#l315 */ XWindowAttributes attrs; XGetWindowAttributes(dpy, window, &attrs); int depth = attrs.depth; - XImage *img = XCreateImage(dpy, CopyFromParent, depth, ZPixmap, 0, NULL, ts->w, ts->h, 32, 0); + XImage *img = XCreateImage(dpy, CopyFromParent, depth, ZPixmap, 0, NULL, tl->w, tl->h, 32, 0); img->data = calloc(img->bytes_per_line, img->height); XInitImage(img); int b; - for (int i = 0; i < ts->h; i++) { + for (int i = 0; i < tl->h; i++) { b = img->bytes_per_line * i; - for (int j = 0; j < ts->w; j++) { + for (int j = 0; j < tl->w; j++) { img->data[b++] = bg.blue / 257; img->data[b++] = bg.green / 257; img->data[b++] = bg.red / 257; @@ -373,12 +751,39 @@ ltk_render_text_segment( } } + LtkTextSegment *ts = tl->start_segment; + int x = 0; + int y = 0; + int is_hor = HB_DIRECTION_IS_HORIZONTAL(ts->dir); + do { + if (is_hor) { + y = tl->h - ts->y_max; + ltk_render_text_segment(ts, x + ts->start_x, y, img, fg); + x += ts->w; + } else { + x = tl->w - ts->x_max; + ltk_render_text_segment(ts, x, y + ts->start_y, img, fg); + y += ts->h; + } + } while (ts = ts->next); + + return img; +} + +void +ltk_render_text_segment( + LtkTextSegment *ts, + unsigned int start_x, + unsigned int start_y, + XImage *img, + XColor fg) +{ LtkGlyph *glyph = ts->start_glyph; - int x_cur = ts->start_x; - int y_cur = ts->start_y; + int x_cur = start_x; + int y_cur = start_y; int x, y; double a; - unsigned int out_r, out_g, out_b; + int b; do { x = x_cur + glyph->info->xoff + glyph->x_offset; y = y_cur + glyph->info->yoff - glyph->y_offset; @@ -394,6 +799,4 @@ ltk_render_text_segment( x_cur += glyph->x_advance; y_cur -= glyph->y_advance; } while (glyph = glyph->next); - - return img; } diff --git a/text-hb.h b/text-hb.h @@ -30,6 +30,8 @@ #include "stb_truetype.h" /* http://nothings.org/stb/stb_truetype.h */ #include "khash.h" #include <X11/Xlib.h> +#include <fontconfig/fontconfig.h> +#include <fribidi.h> typedef struct { stbtt_fontinfo info; @@ -58,21 +60,38 @@ typedef struct _LtkGlyph { int y_offset; /* additional y offset given by harfbuzz */ int x_advance; int y_advance; + int x_abs; + int y_abs; uint32_t cluster; /* index of char in original text - from harfbuzz */ struct _LtkGlyph *next; } LtkGlyph; -typedef struct { +/* Single segment of text with same font */ +typedef struct LtkTextSegment { uint16_t font_id; uint16_t font_size; unsigned int w; unsigned int h; int start_x; int start_y; - char *str; + int x_min; + int y_min; + int x_max; + int y_max; + hb_direction_t dir; + uint32_t *str; LtkGlyph *start_glyph; + struct LtkTextSegment *next; } LtkTextSegment; +/* Single line of text */ +typedef struct { + unsigned int w; + unsigned int h; + FriBidiParType dir; + LtkTextSegment *start_segment; +} LtkTextLine; + /* Hash definitions */ /* glyph id -> glyph info struct */ KHASH_MAP_INIT_INT(glyphinfo, LtkGlyphInfo*) @@ -87,10 +106,12 @@ typedef struct LtkTextManager_ { khash_t(fontid) *font_paths; khash_t(fontstruct) *font_cache; khash_t(glyphcache) *glyph_cache; + FcPattern *fcpattern; + uint16_t default_font; uint16_t font_id_cur; } LtkTextManager; -LtkTextManager *ltk_init_text(void); +LtkTextManager *ltk_init_text(char *font_name); void ltk_destroy_text_manager(LtkTextManager *tm); @@ -104,6 +125,8 @@ khint_t ltk_create_glyph_cache(LtkTextManager *tm, uint16_t font_id, uint16_t fo void ltk_destroy_glyph_cache(khash_t(glyphinfo) *cache); +void ltk_load_default_font(LtkTextManager *tm, char *name); + LtkFont *ltk_create_font(char *path, uint16_t id); void ltk_destroy_font(LtkFont *font); @@ -113,14 +136,20 @@ uint16_t ltk_load_font(LtkTextManager *tm, char *path); uint16_t ltk_get_font(LtkTextManager *tm, char *path); +/* TODO: different sizes, colors, styles, etc. */ +LtkTextLine *ltk_create_text_line(LtkTextManager *tm, char *text, uint16_t fontid, uint16_t size); + /* FIXME: could use unsigned int for fontid and size as long as there is code to check neither of them become too large -> in case I want to get rid of uint_16_t, etc. */ -LtkTextSegment *ltk_create_text_segment(LtkTextManager *tm, char *text, uint16_t fontid, uint16_t size); +LtkTextSegment *ltk_create_text_segment(LtkTextManager *tm, uint32_t *text, unsigned int text_len, uint16_t fontid, uint16_t size, hb_script_t script); void ltk_destroy_glyph(LtkGlyph *glyph, khash_t(glyphinfo) *cache); void ltk_destroy_text_segment(LtkTextSegment *ts); -XImage *ltk_render_text_segment(LtkTextSegment *ts, Display *dpy, Window window, GC gc, Colormap colormap, XColor fg, XColor bg); +XImage *ltk_render_text_line(LtkTextLine *tl, Display *dpy, Window window, GC gc, Colormap colormap, XColor fg, XColor bg); + +void ltk_render_text_segment(LtkTextSegment *ts, unsigned int start_x, unsigned int start_y, XImage *img, XColor fg); + #endif diff --git a/themes/default.ini b/themes/default.ini @@ -2,7 +2,7 @@ border_width = 0 bg = #000000 fg = #FFFFFF -font = Lumidify_Casual.ttf +font = Awami Nastaliq [button] border_width = 2 diff --git a/tmpfb.txt b/tmpfb.txt @@ -0,0 +1,67 @@ +49 +27 +6d +20 +61 +20 +62 +75 +74 +74 +6f +6e +21 +0 +49 +27 +6d +20 +61 +20 +62 +75 +74 +74 +6f +6e +21 +0 +49 +27 +6d +20 +61 +20 +62 +75 +74 +74 +6f +6e +21 +0 +6c1 +645 +627 +631 +6d2 +20 +628 +627 +631 +6d2 +20 +645 +6cc +6ba +20 +62 +6c +61 +62 +6c +61 +62 +6c +61 +0 diff --git a/tmpnofb.txt b/tmpnofb.txt @@ -0,0 +1,63 @@ +49 +27 +6d +20 +61 +20 +62 +75 +74 +74 +6f +6e +21 +0 +49 +27 +6d +20 +61 +20 +62 +75 +74 +74 +6f +6e +21 +0 +49 +27 +6d +20 +61 +20 +62 +75 +74 +74 +6f +6e +21 +0 +62 +6c +61 +62 +6c +61 +62 +6c +61 +20 +fb9f +fbff +fee3 +20 +fbae +fead +fe8e +fe91 +20 +fbae +fead