ltkx

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

commit 323ceb5cbc54b4d70002a4f4e6e5ae9d308793c4
parent a06777c1797439f43e6b8a1026a53c5e6ac9298e
Author: lumidify <nobody@lumidify.org>
Date:   Sun, 12 Aug 2018 19:49:58 +0200

Improve text rendering; combine with actual widgets; add cleanup functions

Yeah, it's probably probably still pretty bad. Oh well...

Diffstat:
MMakefile | 6+++---
ANOTES | 6++++++
MREADME.md | 2+-
Mbutton.c | 84+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Mbutton.h | 19+++++++++----------
Mgrid.c | 2+-
Mgrid.h | 2+-
Akhash.h | 627+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mltk.c | 40++++++++++++++++++++++++----------------
Mltk.h | 16++++++++++------
Mtest1.c | 28+++++++++-------------------
Atext-hb.c | 399+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atext-hb.h | 126+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtext.c | 2+-
Mtext/Makefile | 2+-
Mtext/text-hb.ubernew.c | 2++
Atext/text-hb.uberubernew.c | 473+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Duthash.h | 1074-------------------------------------------------------------------------------
18 files changed, 1741 insertions(+), 1169 deletions(-)

diff --git a/Makefile b/Makefile @@ -1,7 +1,7 @@ -LIBS = -lX11 -lm -L/usr/X11R6/lib +LIBS = -lm `pkg-config --libs x11 harfbuzz` STD = -std=c99 -FLAGS = -g -w -fcommon -Wall -Werror -Wextra -I/usr/X11R6/include #-pedantic -CFILES = text.c ltk.c ini.c grid.c button.c test1.c +FLAGS = -g -w -fcommon -Wall -Werror -Wextra `pkg-config --cflags x11 harfbuzz`#-pedantic +CFILES = text-hb.c ltk.c ini.c grid.c button.c test1.c all: test1.c gcc $(STD) $(FLAGS) $(LIBS) $(CFILES) -o test diff --git a/NOTES b/NOTES @@ -0,0 +1,6 @@ +Maybe use XCheckWindowEvent? - this would eliminate need for hash (could just store window ids in array) +-> Is it okay to block until event found? Loading bars, etc. should still work since control is in another + function (or even thread?) which has to call ltk_update_progress_bar or something similar. +HarfBuzz - need to add option to LTK functions to allow user to choose language system manually +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 diff --git a/README.md b/README.md @@ -4,7 +4,7 @@ This is work in progress. Please do not attempt to actually use any of the code. ## Licenses of Other Libraries Used -[uthash](https://troydhanson.github.io/uthash/) by Troy D. Hanson: [BSD Revised](https://troydhanson.github.io/uthash/license.html) +[khash](https://github.com/attractivechaos/klib) by Attractive Chaos: [MIT](https://github.com/attractivechaos/klib/blob/31cb0301482a762af0adbfc85dff1632cecc2bb4/khash.h#L3) [inih](https://github.com/benhoyt/inih) by Ben Hoyt: [New BSD](https://github.com/benhoyt/inih/blob/master/LICENSE.txt) diff --git a/button.c b/button.c @@ -1,6 +1,6 @@ /* * This file is part of the Lumidify ToolKit (LTK) - * Copyright (c) 2016, 2017 Lumidify Productions <lumidify@openmailbox.org> + * Copyright (c) 2016, 2017, 2018 lumidify <nobody@lumidify.org> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,6 +22,8 @@ */ #include "ltk.h" +#include "text-hb.h" +#include "button.h" void ltk_button_ini_handler(LtkTheme *theme, const char *prop, const char *value) { @@ -64,22 +66,6 @@ void ltk_button_ini_handler(LtkTheme *theme, const char *prop, const char *value } } -/* FIXME: this is probably really slow and there is probably a really simple alternative */ -unsigned long ltk_blend_pixel(XColor fg, XColor bg, double a) -{ - XColor blended; - if (a == 1.0) - return fg.pixel; - else if (a == 0.0) - return bg.pixel; - blended.red = (int)((fg.red - bg.red) * a + bg.red); - blended.green = (int)((fg.green - bg.green) * a + bg.green); - blended.blue = (int)((fg.blue - bg.blue) * a + bg.blue); - XAllocColor(ltk_global->display, ltk_global->colormap, &blended); - - return blended.pixel; -} - void ltk_draw_button(LtkButton *button) { LtkButtonTheme *theme = ltk_global->theme->button; @@ -87,52 +73,67 @@ void ltk_draw_button(LtkButton *button) LtkRect rect = button->widget.rect; XColor border; XColor fill; + XImage *img; switch (button->widget.state) { case LTK_NORMAL: border = theme->border; fill = theme->fill; + img = button->text; break; case LTK_HOVERACTIVE: case LTK_HOVER: border = theme->border_hover; fill = theme->fill_hover; + img = button->text_hover; break; case LTK_PRESSED: border = theme->border_pressed; fill = theme->fill_pressed; + img = button->text_pressed; break; case LTK_ACTIVE: border = theme->border_active; fill = theme->fill_active; + img = button->text_active; break; case LTK_DISABLED: border = theme->border_disabled; fill = theme->fill_disabled; + img = button->text_disabled; break; default: ltk_fatal("No style found for button!\n"); } XSetForeground(ltk_global->display, window->gc, fill.pixel); - XFillRectangle(ltk_global->display, window->xwindow, window->gc, - rect.x, rect.y, rect.w, rect.h); + XFillRectangle(ltk_global->display, window->xwindow, window->gc, rect.x, rect.y, rect.w, rect.h); + /* FIXME: Why did I do this? */ if (theme->border_width < 1) return; XSetForeground(ltk_global->display, window->gc, border.pixel); - XSetLineAttributes(ltk_global->display, window->gc, theme->border_width, - LineSolid, CapButt, JoinMiter); - XDrawRectangle(ltk_global->display, window->xwindow, window->gc, - rect.x, rect.y, rect.w, rect.h); - int height = theme->font_size; - int width = button->text_width; - int i, j; - for (i = 0; i < height; i++) - { - for (j = 0; j < width; j++) - { -// XSetForeground(ltk_global->display, window->gc, (button->text_bitmap[i * width + j] / 255.0) * theme->text_color.pixel + ((255 - button->text_bitmap[i * width + j]) / 255.0) * fill.pixel); - XSetForeground(ltk_global->display, window->gc, ltk_blend_pixel(theme->text_color, fill, (button->text_bitmap[i * width + j] / 255.0))); - XDrawPoint(ltk_global->display, window->xwindow, window->gc, rect.x + j, rect.y + i); + XSetLineAttributes(ltk_global->display, window->gc, theme->border_width, LineSolid, CapButt, JoinMiter); + XDrawRectangle(ltk_global->display, window->xwindow, window->gc, rect.x, rect.y, rect.w, rect.h); + if (!img) { + img = ltk_render_text_segment(button->ts, 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: + button->text = img; + break; + case LTK_HOVERACTIVE: + case LTK_HOVER: + button->text_hover = img; + break; + case LTK_PRESSED: + button->text_pressed = img; + break; + case LTK_ACTIVE: + button->text_active = img; + break; + case LTK_DISABLED: + button->text_disabled = img; + break; } } + XPutImage(ltk_global->display, window->xwindow, window->gc, img, 0, 0, rect.x + theme->border_width, rect.y + theme->border_width, button->ts->w, button->ts->h); } LtkButton *ltk_create_button(LtkWindow *window, const char *text, @@ -149,8 +150,14 @@ LtkButton *ltk_create_button(LtkWindow *window, const char *text, button->callback = callback; LtkTheme *theme = ltk_global->theme; - button->text_width = ltk_text_width(text, theme->window->font, theme->button->font_size); - button->text_bitmap = ltk_render_text(text, theme->window->font, theme->button->font_size, button->text_width); + 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 * 2; + button->widget.rect.h = button->ts->h + theme->button->border_width * 2; + button->text = NULL; + button->text_pressed = NULL; + button->text_hover = NULL; + button->text_disabled = NULL; + button->text_active = NULL; return button; } @@ -161,7 +168,12 @@ void ltk_destroy_button(void *widget) if (!button) { printf("WARNING: Tried to destroy NULL button.\n"); } - free(button->text_bitmap); + if (button->text) XDestroyImage(button->text); + if (button->text_hover) XDestroyImage(button->text_hover); + 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); free(button); } diff --git a/button.h b/button.h @@ -1,6 +1,6 @@ /* * This file is part of the Lumidify ToolKit (LTK) - * Copyright (c) 2016, 2017 Lumidify Productions <lumidify@openmailbox.org> + * Copyright (c) 2016, 2017, 2018 lumidify <nobody@lumidify.org> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -24,18 +24,17 @@ #ifndef _LTK_BUTTON_H_ #define _LTK_BUTTON_H_ +#include "text-hb.h" + typedef struct { LtkWidget widget; void (*callback) (void); - - int text_width; - char *text_raw; - unsigned char *text_bitmap; - Pixmap text; - Pixmap text_hover; - Pixmap text_pressed; - Pixmap text_active; - Pixmap text_disabled; + LtkTextSegment *ts; + XImage *text; + XImage *text_hover; + XImage *text_pressed; + XImage *text_active; + XImage *text_disabled; } LtkButton; typedef struct LtkButtonTheme { diff --git a/grid.c b/grid.c @@ -1,6 +1,6 @@ /* * This file is part of the Lumidify ToolKit (LTK) - * Copyright (c) 2016, 2017 Lumidify Productions <lumidify@openmailbox.org> + * Copyright (c) 2016, 2017, 2018 lumidify <nobody@lumidify.org> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/grid.h b/grid.h @@ -1,6 +1,6 @@ /* * This file is part of the Lumidify ToolKit (LTK) - * Copyright (c) 2016, 2017 Lumidify Productions <lumidify@openmailbox.org> + * Copyright (c) 2016, 2017, 2018 lumidify <nobody@lumidify.org> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/khash.h b/khash.h @@ -0,0 +1,627 @@ +/* The MIT License + + Copyright (c) 2008, 2009, 2011 by Attractive Chaos <attractor@live.co.uk> + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +/* + An example: + +#include "khash.h" +KHASH_MAP_INIT_INT(32, char) +int main() { + int ret, is_missing; + khiter_t k; + khash_t(32) *h = kh_init(32); + k = kh_put(32, h, 5, &ret); + kh_value(h, k) = 10; + k = kh_get(32, h, 10); + is_missing = (k == kh_end(h)); + k = kh_get(32, h, 5); + kh_del(32, h, k); + for (k = kh_begin(h); k != kh_end(h); ++k) + if (kh_exist(h, k)) kh_value(h, k) = 1; + kh_destroy(32, h); + return 0; +} +*/ + +/* + 2013-05-02 (0.2.8): + + * Use quadratic probing. When the capacity is power of 2, stepping function + i*(i+1)/2 guarantees to traverse each bucket. It is better than double + hashing on cache performance and is more robust than linear probing. + + In theory, double hashing should be more robust than quadratic probing. + However, my implementation is probably not for large hash tables, because + the second hash function is closely tied to the first hash function, + which reduce the effectiveness of double hashing. + + Reference: http://research.cs.vt.edu/AVresearch/hashing/quadratic.php + + 2011-12-29 (0.2.7): + + * Minor code clean up; no actual effect. + + 2011-09-16 (0.2.6): + + * The capacity is a power of 2. This seems to dramatically improve the + speed for simple keys. Thank Zilong Tan for the suggestion. Reference: + + - http://code.google.com/p/ulib/ + - http://nothings.org/computer/judy/ + + * Allow to optionally use linear probing which usually has better + performance for random input. Double hashing is still the default as it + is more robust to certain non-random input. + + * Added Wang's integer hash function (not used by default). This hash + function is more robust to certain non-random input. + + 2011-02-14 (0.2.5): + + * Allow to declare global functions. + + 2009-09-26 (0.2.4): + + * Improve portability + + 2008-09-19 (0.2.3): + + * Corrected the example + * Improved interfaces + + 2008-09-11 (0.2.2): + + * Improved speed a little in kh_put() + + 2008-09-10 (0.2.1): + + * Added kh_clear() + * Fixed a compiling error + + 2008-09-02 (0.2.0): + + * Changed to token concatenation which increases flexibility. + + 2008-08-31 (0.1.2): + + * Fixed a bug in kh_get(), which has not been tested previously. + + 2008-08-31 (0.1.1): + + * Added destructor +*/ + + +#ifndef __AC_KHASH_H +#define __AC_KHASH_H + +/*! + @header + + Generic hash table library. + */ + +#define AC_VERSION_KHASH_H "0.2.8" + +#include <stdlib.h> +#include <string.h> +#include <limits.h> + +/* compiler specific configuration */ + +#if UINT_MAX == 0xffffffffu +typedef unsigned int khint32_t; +#elif ULONG_MAX == 0xffffffffu +typedef unsigned long khint32_t; +#endif + +#if ULONG_MAX == ULLONG_MAX +typedef unsigned long khint64_t; +#else +typedef unsigned long long khint64_t; +#endif + +#ifndef kh_inline +#ifdef _MSC_VER +#define kh_inline __inline +#else +#define kh_inline inline +#endif +#endif /* kh_inline */ + +#ifndef klib_unused +#if (defined __clang__ && __clang_major__ >= 3) || (defined __GNUC__ && __GNUC__ >= 3) +#define klib_unused __attribute__ ((__unused__)) +#else +#define klib_unused +#endif +#endif /* klib_unused */ + +typedef khint32_t khint_t; +typedef khint_t khiter_t; + +#define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2) +#define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1) +#define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3) +#define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1))) +#define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1))) +#define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1))) +#define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1)) + +#define __ac_fsize(m) ((m) < 16? 1 : (m)>>4) + +#ifndef kroundup32 +#define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x)) +#endif + +#ifndef kcalloc +#define kcalloc(N,Z) calloc(N,Z) +#endif +#ifndef kmalloc +#define kmalloc(Z) malloc(Z) +#endif +#ifndef krealloc +#define krealloc(P,Z) realloc(P,Z) +#endif +#ifndef kfree +#define kfree(P) free(P) +#endif + +static const double __ac_HASH_UPPER = 0.77; + +#define __KHASH_TYPE(name, khkey_t, khval_t) \ + typedef struct kh_##name##_s { \ + khint_t n_buckets, size, n_occupied, upper_bound; \ + khint32_t *flags; \ + khkey_t *keys; \ + khval_t *vals; \ + } kh_##name##_t; + +#define __KHASH_PROTOTYPES(name, khkey_t, khval_t) \ + extern kh_##name##_t *kh_init_##name(void); \ + extern void kh_destroy_##name(kh_##name##_t *h); \ + extern void kh_clear_##name(kh_##name##_t *h); \ + extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \ + extern int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \ + extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \ + extern void kh_del_##name(kh_##name##_t *h, khint_t x); + +#define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ + SCOPE kh_##name##_t *kh_init_##name(void) { \ + return (kh_##name##_t*)kcalloc(1, sizeof(kh_##name##_t)); \ + } \ + SCOPE void kh_destroy_##name(kh_##name##_t *h) \ + { \ + if (h) { \ + kfree((void *)h->keys); kfree(h->flags); \ + kfree((void *)h->vals); \ + kfree(h); \ + } \ + } \ + SCOPE void kh_clear_##name(kh_##name##_t *h) \ + { \ + if (h && h->flags) { \ + memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \ + h->size = h->n_occupied = 0; \ + } \ + } \ + SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \ + { \ + if (h->n_buckets) { \ + khint_t k, i, last, mask, step = 0; \ + mask = h->n_buckets - 1; \ + k = __hash_func(key); i = k & mask; \ + last = i; \ + while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ + i = (i + (++step)) & mask; \ + if (i == last) return h->n_buckets; \ + } \ + return __ac_iseither(h->flags, i)? h->n_buckets : i; \ + } else return 0; \ + } \ + SCOPE int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \ + { /* This function uses 0.25*n_buckets bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \ + khint32_t *new_flags = 0; \ + khint_t j = 1; \ + { \ + kroundup32(new_n_buckets); \ + if (new_n_buckets < 4) new_n_buckets = 4; \ + if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0; /* requested size is too small */ \ + else { /* hash table size to be changed (shrink or expand); rehash */ \ + new_flags = (khint32_t*)kmalloc(__ac_fsize(new_n_buckets) * sizeof(khint32_t)); \ + if (!new_flags) return -1; \ + memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \ + if (h->n_buckets < new_n_buckets) { /* expand */ \ + khkey_t *new_keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \ + if (!new_keys) { kfree(new_flags); return -1; } \ + h->keys = new_keys; \ + if (kh_is_map) { \ + khval_t *new_vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \ + if (!new_vals) { kfree(new_flags); return -1; } \ + h->vals = new_vals; \ + } \ + } /* otherwise shrink */ \ + } \ + } \ + if (j) { /* rehashing is needed */ \ + for (j = 0; j != h->n_buckets; ++j) { \ + if (__ac_iseither(h->flags, j) == 0) { \ + khkey_t key = h->keys[j]; \ + khval_t val; \ + khint_t new_mask; \ + new_mask = new_n_buckets - 1; \ + if (kh_is_map) val = h->vals[j]; \ + __ac_set_isdel_true(h->flags, j); \ + while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \ + khint_t k, i, step = 0; \ + k = __hash_func(key); \ + i = k & new_mask; \ + while (!__ac_isempty(new_flags, i)) i = (i + (++step)) & new_mask; \ + __ac_set_isempty_false(new_flags, i); \ + if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { /* kick out the existing element */ \ + { khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \ + if (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \ + __ac_set_isdel_true(h->flags, i); /* mark it as deleted in the old hash table */ \ + } else { /* write the element and jump out of the loop */ \ + h->keys[i] = key; \ + if (kh_is_map) h->vals[i] = val; \ + break; \ + } \ + } \ + } \ + } \ + if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \ + h->keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \ + if (kh_is_map) h->vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \ + } \ + kfree(h->flags); /* free the working space */ \ + h->flags = new_flags; \ + h->n_buckets = new_n_buckets; \ + h->n_occupied = h->size; \ + h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \ + } \ + return 0; \ + } \ + SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \ + { \ + khint_t x; \ + if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \ + if (h->n_buckets > (h->size<<1)) { \ + if (kh_resize_##name(h, h->n_buckets - 1) < 0) { /* clear "deleted" elements */ \ + *ret = -1; return h->n_buckets; \ + } \ + } else if (kh_resize_##name(h, h->n_buckets + 1) < 0) { /* expand the hash table */ \ + *ret = -1; return h->n_buckets; \ + } \ + } /* TODO: to implement automatically shrinking; resize() already support shrinking */ \ + { \ + khint_t k, i, site, last, mask = h->n_buckets - 1, step = 0; \ + x = site = h->n_buckets; k = __hash_func(key); i = k & mask; \ + if (__ac_isempty(h->flags, i)) x = i; /* for speed up */ \ + else { \ + last = i; \ + while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ + if (__ac_isdel(h->flags, i)) site = i; \ + i = (i + (++step)) & mask; \ + if (i == last) { x = site; break; } \ + } \ + if (x == h->n_buckets) { \ + if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \ + else x = i; \ + } \ + } \ + } \ + if (__ac_isempty(h->flags, x)) { /* not present at all */ \ + h->keys[x] = key; \ + __ac_set_isboth_false(h->flags, x); \ + ++h->size; ++h->n_occupied; \ + *ret = 1; \ + } else if (__ac_isdel(h->flags, x)) { /* deleted */ \ + h->keys[x] = key; \ + __ac_set_isboth_false(h->flags, x); \ + ++h->size; \ + *ret = 2; \ + } else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \ + return x; \ + } \ + SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \ + { \ + if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \ + __ac_set_isdel_true(h->flags, x); \ + --h->size; \ + } \ + } + +#define KHASH_DECLARE(name, khkey_t, khval_t) \ + __KHASH_TYPE(name, khkey_t, khval_t) \ + __KHASH_PROTOTYPES(name, khkey_t, khval_t) + +#define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ + __KHASH_TYPE(name, khkey_t, khval_t) \ + __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) + +#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ + KHASH_INIT2(name, static kh_inline klib_unused, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) + +/* --- BEGIN OF HASH FUNCTIONS --- */ + +/*! @function + @abstract Integer hash function + @param key The integer [khint32_t] + @return The hash value [khint_t] + */ +#define kh_int_hash_func(key) (khint32_t)(key) +/*! @function + @abstract Integer comparison function + */ +#define kh_int_hash_equal(a, b) ((a) == (b)) +/*! @function + @abstract 64-bit integer hash function + @param key The integer [khint64_t] + @return The hash value [khint_t] + */ +#define kh_int64_hash_func(key) (khint32_t)((key)>>33^(key)^(key)<<11) +/*! @function + @abstract 64-bit integer comparison function + */ +#define kh_int64_hash_equal(a, b) ((a) == (b)) +/*! @function + @abstract const char* hash function + @param s Pointer to a null terminated string + @return The hash value + */ +static kh_inline khint_t __ac_X31_hash_string(const char *s) +{ + khint_t h = (khint_t)*s; + if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s; + return h; +} +/*! @function + @abstract Another interface to const char* hash function + @param key Pointer to a null terminated string [const char*] + @return The hash value [khint_t] + */ +#define kh_str_hash_func(key) __ac_X31_hash_string(key) +/*! @function + @abstract Const char* comparison function + */ +#define kh_str_hash_equal(a, b) (strcmp(a, b) == 0) + +static kh_inline khint_t __ac_Wang_hash(khint_t key) +{ + key += ~(key << 15); + key ^= (key >> 10); + key += (key << 3); + key ^= (key >> 6); + key += ~(key << 11); + key ^= (key >> 16); + return key; +} +#define kh_int_hash_func2(key) __ac_Wang_hash((khint_t)key) + +/* --- END OF HASH FUNCTIONS --- */ + +/* Other convenient macros... */ + +/*! + @abstract Type of the hash table. + @param name Name of the hash table [symbol] + */ +#define khash_t(name) kh_##name##_t + +/*! @function + @abstract Initiate a hash table. + @param name Name of the hash table [symbol] + @return Pointer to the hash table [khash_t(name)*] + */ +#define kh_init(name) kh_init_##name() + +/*! @function + @abstract Destroy a hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + */ +#define kh_destroy(name, h) kh_destroy_##name(h) + +/*! @function + @abstract Reset a hash table without deallocating memory. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + */ +#define kh_clear(name, h) kh_clear_##name(h) + +/*! @function + @abstract Resize a hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param s New size [khint_t] + */ +#define kh_resize(name, h, s) kh_resize_##name(h, s) + +/*! @function + @abstract Insert a key to the hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param k Key [type of keys] + @param r Extra return code: -1 if the operation failed; + 0 if the key is present in the hash table; + 1 if the bucket is empty (never used); 2 if the element in + the bucket has been deleted [int*] + @return Iterator to the inserted element [khint_t] + */ +#define kh_put(name, h, k, r) kh_put_##name(h, k, r) + +/*! @function + @abstract Retrieve a key from the hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param k Key [type of keys] + @return Iterator to the found element, or kh_end(h) if the element is absent [khint_t] + */ +#define kh_get(name, h, k) kh_get_##name(h, k) + +/*! @function + @abstract Remove a key from the hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param k Iterator to the element to be deleted [khint_t] + */ +#define kh_del(name, h, k) kh_del_##name(h, k) + +/*! @function + @abstract Test whether a bucket contains data. + @param h Pointer to the hash table [khash_t(name)*] + @param x Iterator to the bucket [khint_t] + @return 1 if containing data; 0 otherwise [int] + */ +#define kh_exist(h, x) (!__ac_iseither((h)->flags, (x))) + +/*! @function + @abstract Get key given an iterator + @param h Pointer to the hash table [khash_t(name)*] + @param x Iterator to the bucket [khint_t] + @return Key [type of keys] + */ +#define kh_key(h, x) ((h)->keys[x]) + +/*! @function + @abstract Get value given an iterator + @param h Pointer to the hash table [khash_t(name)*] + @param x Iterator to the bucket [khint_t] + @return Value [type of values] + @discussion For hash sets, calling this results in segfault. + */ +#define kh_val(h, x) ((h)->vals[x]) + +/*! @function + @abstract Alias of kh_val() + */ +#define kh_value(h, x) ((h)->vals[x]) + +/*! @function + @abstract Get the start iterator + @param h Pointer to the hash table [khash_t(name)*] + @return The start iterator [khint_t] + */ +#define kh_begin(h) (khint_t)(0) + +/*! @function + @abstract Get the end iterator + @param h Pointer to the hash table [khash_t(name)*] + @return The end iterator [khint_t] + */ +#define kh_end(h) ((h)->n_buckets) + +/*! @function + @abstract Get the number of elements in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @return Number of elements in the hash table [khint_t] + */ +#define kh_size(h) ((h)->size) + +/*! @function + @abstract Get the number of buckets in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @return Number of buckets in the hash table [khint_t] + */ +#define kh_n_buckets(h) ((h)->n_buckets) + +/*! @function + @abstract Iterate over the entries in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @param kvar Variable to which key will be assigned + @param vvar Variable to which value will be assigned + @param code Block of code to execute + */ +#define kh_foreach(h, kvar, vvar, code) { khint_t __i; \ + for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ + if (!kh_exist(h,__i)) continue; \ + (kvar) = kh_key(h,__i); \ + (vvar) = kh_val(h,__i); \ + code; \ + } } + +/*! @function + @abstract Iterate over the values in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @param vvar Variable to which value will be assigned + @param code Block of code to execute + */ +#define kh_foreach_value(h, vvar, code) { khint_t __i; \ + for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ + if (!kh_exist(h,__i)) continue; \ + (vvar) = kh_val(h,__i); \ + code; \ + } } + +/* More conenient interfaces */ + +/*! @function + @abstract Instantiate a hash set containing integer keys + @param name Name of the hash table [symbol] + */ +#define KHASH_SET_INIT_INT(name) \ + KHASH_INIT(name, khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing integer keys + @param name Name of the hash table [symbol] + @param khval_t Type of values [type] + */ +#define KHASH_MAP_INIT_INT(name, khval_t) \ + KHASH_INIT(name, khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing 64-bit integer keys + @param name Name of the hash table [symbol] + */ +#define KHASH_SET_INIT_INT64(name) \ + KHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing 64-bit integer keys + @param name Name of the hash table [symbol] + @param khval_t Type of values [type] + */ +#define KHASH_MAP_INIT_INT64(name, khval_t) \ + KHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal) + +typedef const char *kh_cstr_t; +/*! @function + @abstract Instantiate a hash map containing const char* keys + @param name Name of the hash table [symbol] + */ +#define KHASH_SET_INIT_STR(name) \ + KHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing const char* keys + @param name Name of the hash table [symbol] + @param khval_t Type of values [type] + */ +#define KHASH_MAP_INIT_STR(name, khval_t) \ + KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal) + +#endif /* __AC_KHASH_H */ diff --git a/ltk.c b/ltk.c @@ -1,6 +1,6 @@ /* * This file is part of the Lumidify ToolKit (LTK) - * Copyright (c) 2016, 2017 Lumidify Productions <lumidify@openmailbox.org> + * Copyright (c) 2016, 2017, 2018 lumidify <nobody@lumidify.org> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,8 +21,8 @@ * SOFTWARE. */ +#include "text-hb.h" #include "ltk.h" -#include "text.h" void ltk_init(const char *theme_path) { @@ -32,19 +32,24 @@ void ltk_init(const char *theme_path) ltk->screen = DefaultScreen(ltk->display); ltk->colormap = DefaultColormap(ltk->display, ltk->screen); ltk->theme = ltk_load_theme(theme_path); - ltk->window_hash = NULL; + 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); } void ltk_clean_up(void) { LtkWindow *window; - for (window = ltk_global->window_hash; window != NULL; - window = window->hh.next) { - ltk_destroy_window(window); + for (int k = kh_begin(ltk_global->window_hash); k != kh_end(ltk_global->window_hash); k++) { + if (kh_exist(ltk_global->window_hash, k)) { + ltk_destroy_window(kh_value(ltk_global->window_hash, k)); + } } + kh_destroy(winhash, ltk_global->window_hash); XCloseDisplay(ltk_global->display); ltk_destroy_theme(ltk_global->theme); + ltk_destroy_text_manager(ltk_global->tm); free(ltk_global); } @@ -141,7 +146,9 @@ LtkWindow *ltk_create_window(const char *title, int x, int y, ButtonPressMask | ButtonReleaseMask | StructureNotifyMask | PointerMotionMask); - HASH_ADD_INT(ltk_global->window_hash, xwindow, window); + int ret; + int k = kh_put(winhash, ltk_global->window_hash, window->xwindow, &ret); + kh_value(ltk_global->window_hash, k) = window; return window; } @@ -155,7 +162,8 @@ void ltk_remove_window(LtkWindow * window) void ltk_destroy_window(LtkWindow * window) { - HASH_DEL(ltk_global->window_hash, window); + int k = kh_get(winhash, ltk_global->window_hash, window->xwindow); + kh_del(winhash, ltk_global->window_hash, k); LtkWidget *ptr = window->root_widget; if (ptr) ptr->destroy(ptr); @@ -199,7 +207,7 @@ void ltk_window_ini_handler(LtkTheme *theme, const char *prop, const char *value } else if (strcmp(prop, "fg") == 0) { theme->window->fg = ltk_create_xcolor(value); } else if (strcmp(prop, "font") == 0) { - theme->window->font = ltk_load_font(value); + theme->window->font = strdup(value); } } @@ -258,18 +266,17 @@ void ltk_destroy_theme(LtkTheme * theme) free(theme); } -char *ltk_read_file(const char *path) +char *ltk_read_file(const char *path, unsigned long *len) { FILE *f; - long len; char *file_contents; f = fopen(path, "rb"); fseek(f, 0, SEEK_END); - len = ftell(f); + *len = ftell(f); fseek(f, 0, SEEK_SET); - file_contents = malloc(len + 1); - fread(file_contents, 1, len, f); - file_contents[len] = '\0'; + file_contents = malloc(*len + 1); + fread(file_contents, 1, *len, f); + file_contents[*len] = '\0'; fclose(f); return file_contents; @@ -424,7 +431,8 @@ void ltk_handle_event(XEvent event) { LtkWindow *window; LtkWidget *root_widget; - HASH_FIND_INT(ltk_global->window_hash, &event.xany.window, window); + int k = kh_get(winhash, ltk_global->window_hash, event.xany.window); + window = kh_value(ltk_global->window_hash, k); if (!window) return; root_widget = window->root_widget; diff --git a/ltk.h b/ltk.h @@ -1,6 +1,6 @@ /* * This file is part of the Lumidify ToolKit (LTK) - * Copyright (c) 2016, 2017 Lumidify Productions <lumidify@openmailbox.org> + * Copyright (c) 2016, 2017, 2018 lumidify <nobody@lumidify.org> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -29,7 +29,7 @@ #include <X11/Xlib.h> #include <X11/Xutil.h> #include "ini.h" -#include "uthash.h" +#include "khash.h" #include "stb_truetype.h" typedef struct { @@ -85,18 +85,20 @@ typedef struct LtkWidget { unsigned short sticky; } LtkWidget; +/* Window hash */ +KHASH_MAP_INIT_INT(winhash, LtkWindow*) + typedef struct LtkWindow { Window xwindow; GC gc; void *root_widget; void (*other_event) (void *, XEvent event); LtkRect rect; - UT_hash_handle hh; } LtkWindow; typedef struct LtkWindowTheme { int border_width; - stbtt_fontinfo font; + char *font; XColor fg; XColor bg; } LtkWindowTheme; @@ -113,10 +115,12 @@ LtkTheme *ltk_load_theme(const char *path); typedef struct { LtkTheme *theme; + LtkTextManager *tm; + uint16_t default_font; Display *display; int screen; Colormap colormap; - LtkWindow *window_hash; + khash_t(winhash) *window_hash; Atom wm_delete_msg; } Ltk; @@ -145,7 +149,7 @@ void ltk_destroy_theme(LtkTheme * theme); int ltk_collide_rect(LtkRect rect, int x, int y); -char *ltk_read_file(const char *path); +char *ltk_read_file(const char *path, unsigned long *len); void ltk_change_active_widget_state(void *widget, LtkWidgetState state); diff --git a/test1.c b/test1.c @@ -16,8 +16,7 @@ void bob2(void *widget, XEvent event) int main(int argc, char *argv[]) { ltk_init("themes/default.ini"); - LtkWindow *window1 = - ltk_create_window("Cool Window!", 0, 0, 500, 500); + LtkWindow *window1 = ltk_create_window("Cool Window!", 0, 0, 500, 500); /* LtkWindow *window2 = ltk_create_window("Cool Window!", 0, 0, 500, 500);*/ LtkGrid *grid1 = ltk_create_grid(window1, 2, 2); window1->root_widget = grid1; @@ -26,24 +25,15 @@ int main(int argc, char *argv[]) ltk_set_column_weight(grid1, 0, 1); ltk_set_column_weight(grid1, 1, 1); /* Test callback functions */ - LtkButton *button1 = - ltk_create_button(window1, "I'm a button!", &bob1); - ltk_grid_widget(button1, grid1, 0, 0, 1, 1, - LTK_STICKY_LEFT | LTK_STICKY_RIGHT); + LtkButton *button1 = ltk_create_button(window1, "I'm a button!", &bob1); + ltk_grid_widget(button1, grid1, 0, 0, 1, 1, LTK_STICKY_LEFT | LTK_STICKY_RIGHT); /* Test manual callback functions */ - LtkButton *button2 = - ltk_create_button(window1, "I'm a button!", NULL); + LtkButton *button2 = ltk_create_button(window1, "I'm a button!", NULL); button2->widget.mouse_release = &bob2; - 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); - ltk_grid_widget(button4, grid1, 1, 1, 1, 1, - LTK_STICKY_LEFT | LTK_STICKY_BOTTOM); + 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); + 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 @@ -0,0 +1,399 @@ +/* + * This file is part of the Lumidify ToolKit (LTK) + * Copyright (c) 2017, 2018 lumidify <nobody@lumidify.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <limits.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include "ltk.h" +#include "khash.h" +#include "text-hb.h" + +LtkTextManager * +ltk_init_text(void) +{ + LtkTextManager *tm = malloc(sizeof(LtkTextManager)); + if (!tm) { + printf("Memory exhausted when trying to create text manager."); + exit(1); + } + tm->font_paths = kh_init(fontid); + tm->font_cache = kh_init(fontstruct); + tm->glyph_cache = kh_init(glyphcache); + tm->font_id_cur = 0; + + return tm; +} + +void +ltk_destroy_text_manager(LtkTextManager *tm) +{ + int k; + + kh_destroy(fontid, tm->font_paths); + + for (k = kh_begin(tm->font_cache); k != kh_end(tm->font_cache); k++) { + if (kh_exist(tm->font_cache, k)) { + ltk_destroy_font(kh_value(tm->font_cache, k)); + } + } + kh_destroy(fontstruct, tm->font_cache); + + for (k = kh_begin(tm->glyph_cache); k != kh_end(tm->glyph_cache); k++) { + if (kh_exist(tm->glyph_cache, k)) { + ltk_destroy_glyph_cache(kh_value(tm->glyph_cache, k)); + } + } + kh_destroy(glyphcache, tm->glyph_cache); + + free(tm); +} + +LtkGlyphInfo * +ltk_create_glyph_info(LtkFont *font, unsigned int id, float scale) +{ + LtkGlyphInfo *glyph = malloc(sizeof(LtkGlyphInfo)); + if (!glyph) { + printf("Out of memory!\n"); + exit(1); + } + + glyph->id = id; + glyph->alphamap = stbtt_GetGlyphBitmap( + &font->info, scale, scale, id, &glyph->w, + &glyph->h, &glyph->xoff, &glyph->yoff + ); + + return glyph; +} + +void +ltk_destroy_glyph_info(LtkGlyphInfo *gi) +{ + free(gi->alphamap); + free(gi); +} + +LtkGlyphInfo * +ltk_get_glyph_info(LtkFont *font, unsigned int id, float scale, khash_t(glyphinfo) *cache) +{ + int ret; + khint_t k; + LtkGlyphInfo *glyph; + k = kh_get(glyphinfo, cache, id); + if (k == kh_end(cache)) { + glyph = ltk_create_glyph_info(font, id, scale); + glyph->refs = 0; + /* FIXME: error checking with ret */ + k = kh_put(glyphinfo, cache, id, &ret); + kh_value(cache, k) = glyph; + } else { + glyph = kh_value(cache, k); + glyph->refs++; + } + + return glyph; +} + +khint_t +ltk_create_glyph_cache(LtkTextManager *tm, uint16_t font_id, uint16_t font_size) +{ + khash_t(glyphinfo) *cache = kh_init(glyphinfo); + int ret; + khint_t k; + /* I guess I can just ignore ret for now */ + k = kh_put(glyphcache, tm->glyph_cache, font_id << 16 + font_size, &ret); + kh_value(tm->glyph_cache, k) = cache; + + return k; +} + +void +ltk_destroy_glyph_cache(khash_t(glyphinfo) *cache) +{ + int k; + for (k = kh_begin(cache); k != kh_end(cache); k++) { + if (kh_exist(cache, k)) { + ltk_destroy_glyph_info(kh_value(cache, k)); + } + } + kh_destroy(glyphinfo, cache); +} + +LtkFont * +ltk_create_font(char *path, uint16_t id) +{ + long len; + LtkFont *font = malloc(sizeof(LtkFont)); + if (!font) { + fprintf(stderr, "Out of memory!\n"); + exit(1); + } + char *contents = ltk_read_file(path, &len); + if (!stbtt_InitFont(&font->info, contents, 0)) + { + fprintf(stderr, "Failed to load font %s\n", path); + exit(1); + } + /* FIXME: make use of the destroy function (last argument to hb_blob_create - see hb-blob.cc in harfbuzz source) */ + hb_blob_t *blob = hb_blob_create(contents, len, HB_MEMORY_MODE_READONLY, NULL, NULL); + hb_face_t *face = hb_face_create(blob, 0); + /* FIXME: need to use destroy function in order for the original file data to be freed? */ + hb_blob_destroy(blob); + font->hb = hb_font_create(face); + hb_face_destroy(face); + hb_ot_font_set_funcs(font->hb); + font->id = id; + font->refs = 0; + + return font; +} + +void +ltk_destroy_font(LtkFont *font) +{ + free(font->info.data); + hb_font_destroy(font->hb); + 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); + 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; + + return font->id; +} + +uint16_t +ltk_get_font(LtkTextManager *tm, char *path) +{ + int ret; + khint_t k; + uint16_t id; + k = kh_get(fontid, tm->font_paths, path); + if (k == kh_end(tm->font_paths)) { + id = ltk_load_font(tm, path); + } else { + id = kh_value(tm->font_paths, k); + } + + return id; +} + +/* 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) +{ + /* (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; + + k = kh_get(fontstruct, tm->font_cache, fontid); + font = kh_value(tm->font_cache, k); + /* FIXME: when should refs be increased? maybe only at the end in case + it has to return for some other reason (out of memory, etc.) */ + font->refs++; + + uint32_t attr = fontid << 16 + size; + /* FIXME: turn this int 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); + } + glyph_cache = kh_value(tm->glyph_cache, k); + + LtkTextSegment *ts = malloc(sizeof(LtkTextSegment)); + if (!ts) { + fprintf(stderr, "Out of memory!\n"); + exit(1); + } + int tlen = strlen(text); + ts->str = strdup(text); + ts->font_id = fontid; + ts->font_size = size; + + hb_buffer_t *buf; + 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) { + printf("WARNING: ltk_render_text_segment: length of text is less than 1.\n"); + } + + 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_shape(font->hb, buf, NULL, 0); + 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); + LtkGlyph *last_glyph = NULL; + + 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; + for (int i = 0; i < text_len; i++) { + gi = &ginf[i]; + gp = &gpos[i]; + LtkGlyph *glyph = malloc(sizeof(LtkGlyph)); + glyph->info = ltk_get_glyph_info(font, gi->codepoint, scale, glyph_cache); + /* FIXME: round instead of just casting */ + glyph->x_offset = (int)(gp->x_offset * scale); + glyph->y_offset = (int)(gp->y_offset * scale); + glyph->x_advance = (int)(gp->x_advance * scale); + glyph->y_advance = (int)(gp->y_advance * scale); + glyph->next = NULL; + if (i == 0) { + ts->start_glyph = glyph; + } else { + last_glyph->next = glyph; + } + last_glyph = glyph; + + /* Calculate position in order to determine full size of text segment */ + x1_abs = x_abs + glyph->info->xoff + glyph->x_offset; + y1_abs = y_abs + glyph->info->yoff - glyph->y_offset; + x2_abs = x1_abs + glyph->info->w; + y2_abs = y1_abs + glyph->info->h; + 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; + if (y2_abs > y_max) y_max = y2_abs; + x_abs += glyph->x_advance; + y_abs -= glyph->y_advance; + } + /* FIXME: what was this supposed to do? + I think it was supposed to be the start drawing position, but why would this not be 0? + I'm guessing it had something to do with the fact that I need to calculate where the + actual top left corner of the glyph is since harfbuzz gives me the origin - maybe I + should just store that position directly in LtkGlyph? Is there any need to advance, etc. + later on after I've positioned the glyphs? Well, I guess I'll figure that out eventually... */ + ts->start_x = -x_min; + ts->start_y = -y_min; + ts->w = x_max - x_min; + ts->h = y_max - y_min; + return ts; +} + +void +ltk_destroy_glyph(LtkGlyph *glyph, khash_t(glyphinfo) *cache) +{ + int k; + if (--glyph->info->refs < 1) { + k = kh_get(glyphinfo, cache, glyph->info->id); + kh_del(glyphinfo, cache, k); + ltk_destroy_glyph_info(glyph->info); + } + free(glyph); +} + +void +ltk_destroy_text_segment(LtkTextSegment *ts) +{ + LtkGlyph *glyph, *next_glyph; + khash_t(glyphinfo) *gcache; + LtkFont *font; + int k; + glyph = ts->start_glyph; + k = kh_get(glyphinfo, ltk_global->tm->glyph_cache, ts->font_id << 16 + ts->font_size); + gcache = kh_value(ltk_global->tm->glyph_cache, k); + do { + next_glyph = glyph->next; + ltk_destroy_glyph(glyph, gcache); + } while (glyph = next_glyph); + k = kh_get(fontstruct, ltk_global->tm->font_cache, ts->font_id); + font = kh_value(ltk_global->tm->font_cache, k); + if (--font->refs < 1) { + kh_del(fontstruct, ltk_global->tm->font_cache, k); + ltk_destroy_font(font); + } + free(ts->str); + free(ts); +} + +XImage * +ltk_render_text_segment( + LtkTextSegment *ts, + Display *dpy, + Window window, + GC gc, + Colormap colormap, + 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); + img->data = calloc(img->bytes_per_line, img->height); + XInitImage(img); + int b; + for (int i = 0; i < ts->h; i++) { + b = img->bytes_per_line * i; + for (int j = 0; j < ts->w; j++) { + img->data[b++] = bg.blue / 257; + img->data[b++] = bg.green / 257; + img->data[b++] = bg.red / 257; + b++; + } + } + + LtkGlyph *glyph = ts->start_glyph; + int x_cur = ts->start_x; + int y_cur = ts->start_y; + int x, y; + double a; + unsigned int out_r, out_g, out_b; + do { + x = x_cur + glyph->info->xoff + glyph->x_offset; + y = y_cur + glyph->info->yoff - glyph->y_offset; + for (int i = 0; i < glyph->info->h; i++) { + for (int j = 0; j < glyph->info->w; j++) { + b = (y + i) * img->bytes_per_line + (x + j) * 4; + a = glyph->info->alphamap[i * glyph->info->w + j] / 255.0; + img->data[b] = (fg.blue * a + (1 - a) * (uint16_t)img->data[b] * 257) / 257; + img->data[b + 1] = (fg.green * a + (1 - a) * (uint16_t)img->data[b + 1] * 257) / 257; + img->data[b + 2] = (fg.red * a + (1 - a) * (uint16_t)img->data[b + 2] * 257) / 257; + } + } + 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 @@ -0,0 +1,126 @@ +/* + * This file is part of the Lumidify ToolKit (LTK) + * Copyright (c) 2017, 2018 lumidify <nobody@lumidify.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef TEXT_HB_H +#define TEXT_HB_H + +#include <harfbuzz/hb.h> +#include <harfbuzz/hb-ot.h> +#define STB_TRUETYPE_IMPLEMENTATION +#include "stb_truetype.h" /* http://nothings.org/stb/stb_truetype.h */ +#include "khash.h" +#include <X11/Xlib.h> + +typedef struct { + stbtt_fontinfo info; + hb_font_t *hb; + uint16_t id; + unsigned int refs; +} LtkFont; + +/* Contains general info on glyphs that doesn't change regardless of the context */ +typedef struct _LtkGlyphInfo { + unsigned int id; + unsigned char *alphamap; + unsigned int w; + unsigned int h; + unsigned int xoff; /* x offset from origin to top left corner of glyph */ + unsigned int yoff; /* y offset from origin to top left corner of glyph */ + unsigned int refs; + /* FIXME: does refs need to be long? It could cause problems if a + program tries to cache/"keep alive" a lot of pages of text. */ +} LtkGlyphInfo; + +/* Contains glyph info specific to one run of text */ +typedef struct _LtkGlyph { + LtkGlyphInfo *info; + int x_offset; /* additional x offset given by harfbuzz */ + int y_offset; /* additional y offset given by harfbuzz */ + int x_advance; + int y_advance; + uint32_t cluster; /* index of char in original text - from harfbuzz */ + struct _LtkGlyph *next; +} LtkGlyph; + +typedef struct { + uint16_t font_id; + uint16_t font_size; + unsigned int w; + unsigned int h; + int start_x; + int start_y; + char *str; + LtkGlyph *start_glyph; +} LtkTextSegment; + +/* Hash definitions */ +/* glyph id -> glyph info struct */ +KHASH_MAP_INIT_INT(glyphinfo, LtkGlyphInfo*) +/* font path, size -> glyph cache hash */ +KHASH_MAP_INIT_INT(glyphcache, khash_t(glyphinfo)*) +/* font path -> font id */ +KHASH_MAP_INIT_STR(fontid, uint16_t) +/* font id -> font struct */ +KHASH_MAP_INIT_INT(fontstruct, LtkFont*) + +typedef struct LtkTextManager_ { + khash_t(fontid) *font_paths; + khash_t(fontstruct) *font_cache; + khash_t(glyphcache) *glyph_cache; + uint16_t font_id_cur; +} LtkTextManager; + +LtkTextManager *ltk_init_text(void); + +void ltk_destroy_text_manager(LtkTextManager *tm); + +LtkGlyphInfo *ltk_create_glyph_info(LtkFont *font, unsigned int id, float scale); + +void ltk_destroy_glyph_info(LtkGlyphInfo *gi); + +LtkGlyphInfo *ltk_get_glyph_info(LtkFont *font, unsigned int id, float scale, khash_t(glyphinfo) *cache); + +khint_t ltk_create_glyph_cache(LtkTextManager *tm, uint16_t font_id, uint16_t font_size); + +void ltk_destroy_glyph_cache(khash_t(glyphinfo) *cache); + +LtkFont *ltk_create_font(char *path, uint16_t id); + +void ltk_destroy_font(LtkFont *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); + +uint16_t ltk_get_font(LtkTextManager *tm, char *path); + +/* 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); + +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); + +#endif diff --git a/text.c b/text.c @@ -92,7 +92,7 @@ int ltk_text_width(uint8_t *text, stbtt_fontinfo fontinfo, int height) unsigned char *ltk_render_text(uint8_t *text, stbtt_fontinfo fontinfo, int height, int width) { /* int width = ltk_text_width(text, fontinfo, height);*/ - unsigned char *bitmap = calloc(sizeof(char), width * height); + unsigned char *bitmap = calloc(width * height, sizeof(char)); float scale = stbtt_ScaleForPixelHeight(&fontinfo, height); int ascent, descent, line_gap; diff --git a/text/Makefile b/text/Makefile @@ -1,2 +1,2 @@ all: text-hb.ubernew.c - gcc text-hb.ubernew.c -g -std=c99 -o test -I /usr/X11R6/include -L /usr/X11R6/lib -I /usr/local/include -L /usr/local/lib -lm -lharfbuzz -lX11 -lXrender + gcc text-hb.uberubernew.c -g -std=c99 -o test `pkg-config --cflags --libs x11 harfbuzz` -lm diff --git a/text/text-hb.ubernew.c b/text/text-hb.ubernew.c @@ -206,8 +206,10 @@ ltk_create_font(char *path, uint16_t id) fprintf(stderr, "Failed to load font %s\n", path); exit(1); } + /* FIXME: make use of the destroy function (last argument to hb_blob_create - see hb-blob.cc in harfbuzz source) */ hb_blob_t *blob = hb_blob_create(contents, len, HB_MEMORY_MODE_READONLY, NULL, NULL); hb_face_t *face = hb_face_create(blob, 0); + /* FIXME: need to use destroy function in order for the original file data to be freed? */ hb_blob_destroy(blob); font->hb = hb_font_create(face); hb_face_destroy(face); diff --git a/text/text-hb.uberubernew.c b/text/text-hb.uberubernew.c @@ -0,0 +1,473 @@ +/* + * This file is part of the Lumidify ToolKit (LTK) + * Copyright (c) 2017, 2018 lumidify <nobody@lumidify.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <limits.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <harfbuzz/hb.h> +#include <harfbuzz/hb-ot.h> +#define STB_TRUETYPE_IMPLEMENTATION +#include "stb_truetype.h" /* http://nothings.org/stb/stb_truetype.h */ +#include "khash.h" + +typedef struct { + stbtt_fontinfo info; + hb_font_t *hb; + uint16_t id; + unsigned int refs; +} LtkFont; + +/* Contains general info on glyphs that doesn't change regardless of the context */ +typedef struct _LtkGlyphInfo { + unsigned char *alphamap; + unsigned int w; + unsigned int h; + unsigned int xoff; /* x offset from origin to top left corner of glyph */ + unsigned int yoff; /* y offset from origin to top left corner of glyph */ + unsigned int refs; + /* FIXME: does refs need to be long? It could cause problems if a + program tries to cache/"keep alive" a lot of pages of text. */ +} LtkGlyphInfo; + +/* Contains glyph info specific to one run of text */ +typedef struct _LtkGlyph { + LtkGlyphInfo *info; + int x_offset; /* additional x offset given by harfbuzz */ + int y_offset; /* additional y offset given by harfbuzz */ + int x_advance; + int y_advance; + uint32_t cluster; /* index of char in original text - from harfbuzz */ + struct _LtkGlyph *next; +} LtkGlyph; + +typedef struct { + unsigned int w; + unsigned int h; + int start_x; + int start_y; + char *str; + LtkGlyph *start_glyph; +} LtkTextSegment; + +/* Hash definitions */ +/* glyph id -> glyph info struct */ +KHASH_MAP_INIT_INT(glyphinfo, LtkGlyphInfo*) +/* font path, size -> glyph cache hash */ +KHASH_MAP_INIT_INT(glyphcache, khash_t(glyphinfo)*) +/* font path -> font id */ +KHASH_MAP_INIT_STR(fontid, uint16_t) +/* font id -> font struct */ +KHASH_MAP_INIT_INT(fontstruct, LtkFont*) + +typedef struct LtkTextManager_ { + khash_t(fontid) *font_paths; + khash_t(fontstruct) *font_cache; + khash_t(glyphcache) *glyph_cache; + uint16_t font_id_cur; +} LtkTextManager; + +LtkTextManager * +ltk_init_text(void) +{ + LtkTextManager *tm = malloc(sizeof(LtkTextManager)); + if (!tm) { + printf("Memory exhausted when trying to create text manager."); + exit(1); + } + tm->font_paths = kh_init(fontid); + tm->font_cache = kh_init(fontstruct); + tm->glyph_cache = kh_init(glyphcache); + tm->font_id_cur = 0; + + return tm; +} + +LtkGlyphInfo * +ltk_create_glyph_info(LtkFont *font, unsigned int id, float scale) +{ + LtkGlyphInfo *glyph = malloc(sizeof(LtkGlyphInfo)); + if (!glyph) { + printf("Out of memory!\n"); + exit(1); + } + + glyph->alphamap = stbtt_GetGlyphBitmap( + &font->info, scale, scale, id, &glyph->w, + &glyph->h, &glyph->xoff, &glyph->yoff + ); + + return glyph; +} + +LtkGlyphInfo * +ltk_get_glyph_info(LtkFont *font, unsigned int id, float scale, khash_t(glyphinfo) *cache) +{ + int ret; + khint_t k; + LtkGlyphInfo *glyph; + k = kh_get(glyphinfo, cache, id); + if (k == kh_end(cache)) { + glyph = ltk_create_glyph_info(font, id, scale); + glyph->refs = 0; + /* FIXME: error checking with ret */ + k = kh_put(glyphinfo, cache, id, &ret); + kh_value(cache, k) = glyph; + } else { + glyph = kh_value(cache, k); + glyph->refs++; + } + + return glyph; +} + + +khint_t +ltk_create_glyph_cache(LtkTextManager *tm, uint16_t font_id, uint16_t font_size) +{ + khash_t(glyphinfo) *cache = kh_init(glyphinfo); + int ret; + khint_t k; + /* I guess I can just ignore ret for now */ + k = kh_put(glyphcache, tm->glyph_cache, font_id << 16 + font_size, &ret); + kh_value(tm->glyph_cache, k) = cache; + + return k; +} + +char * +ltk_load_file(const char *path, unsigned long *len) +{ + FILE *f; + char *contents; + f = fopen(path, "rb"); + fseek(f, 0, SEEK_END); + *len = ftell(f); + fseek(f, 0, SEEK_SET); + contents = malloc(*len + 1); + fread(contents, 1, *len, f); + contents[*len] = '\0'; + fclose(f); + return contents; +} + +unsigned long +ltk_blend_pixel(Display *display, Colormap colormap, XColor fg, XColor bg, double a) +{ + if (a >= 1.0) { + return fg.pixel; + } else if (a == 0.0) { + return bg.pixel; + } + + XColor blended; + blended.red = (int)((fg.red - bg.red) * a + bg.red); + blended.green = (int)((fg.green - bg.green) * a + bg.green); + blended.blue = (int)((fg.blue - bg.blue) * a + bg.blue); + XAllocColor(display, colormap, &blended); + + return blended.pixel; +} + +LtkFont * +ltk_create_font(char *path, uint16_t id) +{ + long len; + LtkFont *font = malloc(sizeof(LtkFont)); + if (!font) { + fprintf(stderr, "Out of memory!\n"); + exit(1); + } + char *contents = ltk_load_file(path, &len); + if (!stbtt_InitFont(&font->info, contents, 0)) + { + fprintf(stderr, "Failed to load font %s\n", path); + exit(1); + } + /* FIXME: make use of the destroy function (last argument to hb_blob_create - see hb-blob.cc in harfbuzz source) */ + hb_blob_t *blob = hb_blob_create(contents, len, HB_MEMORY_MODE_READONLY, NULL, NULL); + hb_face_t *face = hb_face_create(blob, 0); + /* FIXME: need to use destroy function in order for the original file data to be freed? */ + hb_blob_destroy(blob); + font->hb = hb_font_create(face); + hb_face_destroy(face); + hb_ot_font_set_funcs(font->hb); + font->id = id; + return 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); + 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; + + return font->id; +} + +uint16_t +ltk_get_font(LtkTextManager *tm, char *path) +{ + int ret; + khint_t k; + uint16_t id; + k = kh_get(fontid, tm->font_paths, path); + if (k == kh_end(tm->font_paths)) { + id = ltk_load_font(tm, path); + } else { + id = kh_value(tm->font_paths, k); + } + + return id; +} + +/* 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) +{ + /* (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; + + k = kh_get(fontstruct, tm->font_cache, fontid); + font = kh_value(tm->font_cache, k); + /* FIXME: when should refs be increased? maybe only at the end in case + it has to return for some other reason (out of memory, etc.) */ + font->refs++; + + uint32_t attr = fontid << 16 + size; + /* FIXME: turn this int 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); + } + glyph_cache = kh_value(tm->glyph_cache, k); + + LtkTextSegment *ts = malloc(sizeof(LtkTextSegment)); + if (!ts) { + fprintf(stderr, "Out of memory!\n"); + exit(1); + } + ts->str = text; + + hb_buffer_t *buf; + 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) { + printf("WARNING: ltk_render_text_segment: length of text is less than 1.\n"); + } + + 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_shape(font->hb, buf, NULL, 0); + 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); + LtkGlyph *last_glyph = NULL; + + 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; + for (int i = 0; i < text_len; i++) { + gi = &ginf[i]; + gp = &gpos[i]; + LtkGlyph *glyph = malloc(sizeof(LtkGlyph)); + glyph->info = ltk_get_glyph_info(font, gi->codepoint, scale, glyph_cache); + /* FIXME: round instead of just casting */ + glyph->x_offset = (int)(gp->x_offset * scale); + glyph->y_offset = (int)(gp->y_offset * scale); + glyph->x_advance = (int)(gp->x_advance * scale); + glyph->y_advance = (int)(gp->y_advance * scale); + glyph->next = NULL; + if (i == 0) { + ts->start_glyph = glyph; + } else { + last_glyph->next = glyph; + } + last_glyph = glyph; + + /* Calculate position in order to determine full size of text segment */ + x1_abs = x_abs + glyph->info->xoff + glyph->x_offset; + y1_abs = y_abs + glyph->info->yoff - glyph->y_offset; + x2_abs = x1_abs + glyph->info->w; + y2_abs = y1_abs + glyph->info->h; + 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; + if (y2_abs > y_max) y_max = y2_abs; + x_abs += glyph->x_advance; + y_abs -= glyph->y_advance; + } + /* FIXME: what was this supposed to do? + I think it was supposed to be the start drawing position, but why would this not be 0? + I'm guessing it had something to do with the fact that I need to calculate where the + actual top left corner of the glyph is since harfbuzz gives me the origin - maybe I + should just store that position directly in LtkGlyph? Is there any need to advance, etc. + later on after I've positioned the glyphs? Well, I guess I'll figure that out eventually... */ + ts->start_x = -x_min; + ts->start_y = -y_min; + ts->w = x_max - x_min; + ts->h = y_max - y_min; + return ts; +} + +XImage * +ltk_render_text_segment( + LtkTextSegment *ts, + Display *dpy, + Window window, + GC gc, + Colormap colormap, + XColor fg, + XColor bg) +{ + 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); + img->data = calloc(img->bytes_per_line, img->height); + XInitImage(img); + int b; + for (int i = 0; i < ts->h; i++) { + b = img->bytes_per_line * i; + for (int j = 0; j < ts->w; j++) { + img->data[b++] = bg.blue / 257; + img->data[b++] = bg.green / 257; + img->data[b++] = bg.red / 257; + b++; + } + } + + LtkGlyph *glyph = ts->start_glyph; + int x_cur = ts->start_x; + int y_cur = ts->start_y; + int x, y; + double a; + unsigned int out_r, out_g, out_b; + do { + x = x_cur + glyph->info->xoff + glyph->x_offset; + y = y_cur + glyph->info->yoff - glyph->y_offset; + for (int i = 0; i < glyph->info->h; i++) { + for (int j = 0; j < glyph->info->w; j++) { + b = (y + i) * img->bytes_per_line + (x + j) * 4; + a = glyph->info->alphamap[i * glyph->info->w + j] / 255.0; + img->data[b] = (fg.blue * a + (1 - a) * (uint16_t)img->data[b] * 257) / 257; + img->data[b + 1] = (fg.green * a + (1 - a) * (uint16_t)img->data[b + 1] * 257) / 257; + img->data[b + 2] = (fg.red * a + (1 - a) * (uint16_t)img->data[b + 2] * 257) / 257; + + /* + out_r = (fg.red / 255) * a1 + (uint8_t)img->data[b + 2] * (255 - a1); + out_g = (fg.green / 255) * a1 + (uint8_t)img->data[b + 1] * (255 - a1); + out_b = (fg.blue / 255) * a1 + (uint8_t)img->data[b] * (255 - a1); + out_r = (out_r + 1 + (out_r >> 8)) >> 8; + out_g = (out_g + 1 + (out_g >> 8)) >> 8; + out_b = (out_b + 1 + (out_b >> 8)) >> 8; + */ + } + } + x_cur += glyph->x_advance; + y_cur -= glyph->y_advance; + } while (glyph = glyph->next); + + return img; +} + +int main(int argc, char *argv[]) +{ + Display *display; + int screen; + Window window; + GC gc; + + unsigned long black, white; + Colormap colormap; + display = XOpenDisplay((char *)0); + screen = DefaultScreen(display); + colormap = DefaultColormap(display, screen); + black = BlackPixel(display, screen); + white = WhitePixel(display, screen); + XSetWindowAttributes wattr; + wattr.background_pixel = white; + wattr.border_pixel = black; + wattr.colormap = colormap; + window = XCreateWindow(display, DefaultRootWindow(display), 0, 0, 1366, 512, 0, 24, CopyFromParent, CopyFromParent, CWBackPixel | CWBorderPixel, &wattr); + XSetStandardProperties(display, window, "Random Window", NULL, None, NULL, 0, NULL); + XSelectInput(display, window, ExposureMask|ButtonPressMask|KeyPressMask); + gc = XCreateGC(display, window, 0, 0); + XSetBackground(display, gc, black); + XSetForeground(display, gc, black); + XClearWindow(display, window); + XMapRaised(display, window); + XColor c1, c2; + XParseColor(display, colormap, "#FFFFFF", &c1); + XParseColor(display, colormap, "#FF0000", &c2); + XAllocColor(display, colormap, &c1); + XAllocColor(display, colormap, &c2); + + LtkTextManager *tm = ltk_init_text(); + uint16_t font_id = ltk_get_font(tm, "NotoNastaliqUrdu-Regular.ttf"); + uint16_t font_size = 100; + LtkTextSegment *text = ltk_create_text_segment(tm, "ہمارے بارے میں", font_id, font_size); + //Pixmap pix = ltk_render_text_segment(text, display, window, gc, colormap, c1, c2); + XImage *img = ltk_render_text_segment(text, display, window, gc, colormap, c1, c2); + //XCopyArea(display, pix, window, gc, 0, 0, text->w, text->h, 0, 0); + XPutImage(display, window, gc, img, 0, 0, 0, 0, text->w, text->h); + + XEvent event; + KeySym key; + char txt[255]; + + while(1) + { + XNextEvent(display, &event); + if (event.type == KeyPress && XLookupString(&event.xkey, txt, 255, &key, 0) == 1) + { + //XCopyArea(display, pix, window, gc, 0, 0, text->w, text->h, 0, 0); + XPutImage(display, window, gc, img, 0, 0, 0, 0, text->w, text->h); + if (txt[0] == 'q') + { + XFreeGC(display, gc); + XFreeColormap(display, colormap); + XDestroyWindow(display, window); + XCloseDisplay(display); + exit(0); + } + } + } + + return 0; +} diff --git a/uthash.h b/uthash.h @@ -1,1074 +0,0 @@ -/* -Copyright (c) 2003-2016, Troy D. Hanson http://troydhanson.github.com/uthash/ -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS -IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED -TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A -PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER -OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#ifndef UTHASH_H -#define UTHASH_H - -#define UTHASH_VERSION 2.0.1 - -#include <string.h> /* memcmp,strlen */ -#include <stddef.h> /* ptrdiff_t */ -#include <stdlib.h> /* exit() */ - -/* These macros use decltype or the earlier __typeof GNU extension. - As decltype is only available in newer compilers (VS2010 or gcc 4.3+ - when compiling c++ source) this code uses whatever method is needed - or, for VS2008 where neither is available, uses casting workarounds. */ -#if defined(_MSC_VER) /* MS compiler */ -#if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */ -#define DECLTYPE(x) (decltype(x)) -#else /* VS2008 or older (or VS2010 in C mode) */ -#define NO_DECLTYPE -#define DECLTYPE(x) -#endif -#elif defined(__BORLANDC__) || defined(__LCC__) || defined(__WATCOMC__) -#define NO_DECLTYPE -#define DECLTYPE(x) -#else /* GNU, Sun and other compilers */ -#define DECLTYPE(x) (__typeof(x)) -#endif - -#ifdef NO_DECLTYPE -#define DECLTYPE_ASSIGN(dst,src) \ -do { \ - char **_da_dst = (char**)(&(dst)); \ - *_da_dst = (char*)(src); \ -} while (0) -#else -#define DECLTYPE_ASSIGN(dst,src) \ -do { \ - (dst) = DECLTYPE(dst)(src); \ -} while (0) -#endif - -/* a number of the hash function use uint32_t which isn't defined on Pre VS2010 */ -#if defined(_WIN32) -#if defined(_MSC_VER) && _MSC_VER >= 1600 -#include <stdint.h> -#elif defined(__WATCOMC__) || defined(__MINGW32__) || defined(__CYGWIN__) -#include <stdint.h> -#else -typedef unsigned int uint32_t; -typedef unsigned char uint8_t; -#endif -#elif defined(__GNUC__) && !defined(__VXWORKS__) -#include <stdint.h> -#else -typedef unsigned int uint32_t; -typedef unsigned char uint8_t; -#endif - -#ifndef uthash_fatal -#define uthash_fatal(msg) exit(-1) /* fatal error (out of memory,etc) */ -#endif -#ifndef uthash_malloc -#define uthash_malloc(sz) malloc(sz) /* malloc fcn */ -#endif -#ifndef uthash_free -#define uthash_free(ptr,sz) free(ptr) /* free fcn */ -#endif -#ifndef uthash_strlen -#define uthash_strlen(s) strlen(s) -#endif -#ifndef uthash_memcmp -#define uthash_memcmp(a,b,n) memcmp(a,b,n) -#endif - -#ifndef uthash_noexpand_fyi -#define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */ -#endif -#ifndef uthash_expand_fyi -#define uthash_expand_fyi(tbl) /* can be defined to log expands */ -#endif - -/* initial number of buckets */ -#define HASH_INITIAL_NUM_BUCKETS 32U /* initial number of buckets */ -#define HASH_INITIAL_NUM_BUCKETS_LOG2 5U /* lg2 of initial number of buckets */ -#define HASH_BKT_CAPACITY_THRESH 10U /* expand when bucket count reaches */ - -/* calculate the element whose hash handle address is hhp */ -#define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho))) -/* calculate the hash handle from element address elp */ -#define HH_FROM_ELMT(tbl,elp) ((UT_hash_handle *)(((char*)(elp)) + ((tbl)->hho))) - -#define HASH_VALUE(keyptr,keylen,hashv) \ -do { \ - HASH_FCN(keyptr, keylen, hashv); \ -} while (0) - -#define HASH_FIND_BYHASHVALUE(hh,head,keyptr,keylen,hashval,out) \ -do { \ - (out) = NULL; \ - if (head) { \ - unsigned _hf_bkt; \ - HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _hf_bkt); \ - if (HASH_BLOOM_TEST((head)->hh.tbl, hashval) != 0) { \ - HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], keyptr, keylen, hashval, out); \ - } \ - } \ -} while (0) - -#define HASH_FIND(hh,head,keyptr,keylen,out) \ -do { \ - unsigned _hf_hashv; \ - HASH_VALUE(keyptr, keylen, _hf_hashv); \ - HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out); \ -} while (0) - -#ifdef HASH_BLOOM -#define HASH_BLOOM_BITLEN (1UL << HASH_BLOOM) -#define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8UL) + (((HASH_BLOOM_BITLEN%8UL)!=0UL) ? 1UL : 0UL) -#define HASH_BLOOM_MAKE(tbl) \ -do { \ - (tbl)->bloom_nbits = HASH_BLOOM; \ - (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN); \ - if (!((tbl)->bloom_bv)) { uthash_fatal( "out of memory"); } \ - memset((tbl)->bloom_bv, 0, HASH_BLOOM_BYTELEN); \ - (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \ -} while (0) - -#define HASH_BLOOM_FREE(tbl) \ -do { \ - uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ -} while (0) - -#define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8U] |= (1U << ((idx)%8U))) -#define HASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8U] & (1U << ((idx)%8U))) - -#define HASH_BLOOM_ADD(tbl,hashv) \ - HASH_BLOOM_BITSET((tbl)->bloom_bv, (hashv & (uint32_t)((1ULL << (tbl)->bloom_nbits) - 1U))) - -#define HASH_BLOOM_TEST(tbl,hashv) \ - HASH_BLOOM_BITTEST((tbl)->bloom_bv, (hashv & (uint32_t)((1ULL << (tbl)->bloom_nbits) - 1U))) - -#else -#define HASH_BLOOM_MAKE(tbl) -#define HASH_BLOOM_FREE(tbl) -#define HASH_BLOOM_ADD(tbl,hashv) -#define HASH_BLOOM_TEST(tbl,hashv) (1) -#define HASH_BLOOM_BYTELEN 0U -#endif - -#define HASH_MAKE_TABLE(hh,head) \ -do { \ - (head)->hh.tbl = (UT_hash_table*)uthash_malloc( \ - sizeof(UT_hash_table)); \ - if (!((head)->hh.tbl)) { uthash_fatal( "out of memory"); } \ - memset((head)->hh.tbl, 0, sizeof(UT_hash_table)); \ - (head)->hh.tbl->tail = &((head)->hh); \ - (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \ - (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \ - (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head); \ - (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc( \ - HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ - if (! (head)->hh.tbl->buckets) { uthash_fatal( "out of memory"); } \ - memset((head)->hh.tbl->buckets, 0, \ - HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ - HASH_BLOOM_MAKE((head)->hh.tbl); \ - (head)->hh.tbl->signature = HASH_SIGNATURE; \ -} while (0) - -#define HASH_REPLACE_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,replaced,cmpfcn) \ -do { \ - (replaced) = NULL; \ - HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ - if (replaced) { \ - HASH_DELETE(hh, head, replaced); \ - } \ - HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn); \ -} while (0) - -#define HASH_REPLACE_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add,replaced) \ -do { \ - (replaced) = NULL; \ - HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ - if (replaced) { \ - HASH_DELETE(hh, head, replaced); \ - } \ - HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add); \ -} while (0) - -#define HASH_REPLACE(hh,head,fieldname,keylen_in,add,replaced) \ -do { \ - unsigned _hr_hashv; \ - HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ - HASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced); \ -} while (0) - -#define HASH_REPLACE_INORDER(hh,head,fieldname,keylen_in,add,replaced,cmpfcn) \ -do { \ - unsigned _hr_hashv; \ - HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ - HASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced, cmpfcn); \ -} while (0) - -#define HASH_APPEND_LIST(hh, head, add) \ -do { \ - (add)->hh.next = NULL; \ - (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \ - (head)->hh.tbl->tail->next = (add); \ - (head)->hh.tbl->tail = &((add)->hh); \ -} while (0) - -#define HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh,head,keyptr,keylen_in,hashval,add,cmpfcn) \ -do { \ - unsigned _ha_bkt; \ - (add)->hh.hashv = (hashval); \ - (add)->hh.key = (char*) (keyptr); \ - (add)->hh.keylen = (unsigned) (keylen_in); \ - if (!(head)) { \ - (add)->hh.next = NULL; \ - (add)->hh.prev = NULL; \ - (head) = (add); \ - HASH_MAKE_TABLE(hh, head); \ - } else { \ - struct UT_hash_handle *_hs_iter = &(head)->hh; \ - (add)->hh.tbl = (head)->hh.tbl; \ - do { \ - if (cmpfcn(DECLTYPE(head) ELMT_FROM_HH((head)->hh.tbl, _hs_iter), add) > 0) \ - break; \ - } while ((_hs_iter = _hs_iter->next)); \ - if (_hs_iter) { \ - (add)->hh.next = _hs_iter; \ - if (((add)->hh.prev = _hs_iter->prev)) { \ - HH_FROM_ELMT((head)->hh.tbl, _hs_iter->prev)->next = (add); \ - } else { \ - (head) = (add); \ - } \ - _hs_iter->prev = (add); \ - } else { \ - HASH_APPEND_LIST(hh, head, add); \ - } \ - } \ - (head)->hh.tbl->num_items++; \ - HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ - HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], &(add)->hh); \ - HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ - HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ - HASH_FSCK(hh, head); \ -} while (0) - -#define HASH_ADD_KEYPTR_INORDER(hh,head,keyptr,keylen_in,add,cmpfcn) \ -do { \ - unsigned _hs_hashv; \ - HASH_VALUE(keyptr, keylen_in, _hs_hashv); \ - HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, _hs_hashv, add, cmpfcn); \ -} while (0) - -#define HASH_ADD_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,cmpfcn) \ - HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn) - -#define HASH_ADD_INORDER(hh,head,fieldname,keylen_in,add,cmpfcn) \ - HASH_ADD_KEYPTR_INORDER(hh, head, &((add)->fieldname), keylen_in, add, cmpfcn) - -#define HASH_ADD_KEYPTR_BYHASHVALUE(hh,head,keyptr,keylen_in,hashval,add) \ -do { \ - unsigned _ha_bkt; \ - (add)->hh.hashv = (hashval); \ - (add)->hh.key = (char*) (keyptr); \ - (add)->hh.keylen = (unsigned) (keylen_in); \ - if (!(head)) { \ - (add)->hh.next = NULL; \ - (add)->hh.prev = NULL; \ - (head) = (add); \ - HASH_MAKE_TABLE(hh, head); \ - } else { \ - (add)->hh.tbl = (head)->hh.tbl; \ - HASH_APPEND_LIST(hh, head, add); \ - } \ - (head)->hh.tbl->num_items++; \ - HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ - HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], &(add)->hh); \ - HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ - HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ - HASH_FSCK(hh, head); \ -} while (0) - -#define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add) \ -do { \ - unsigned _ha_hashv; \ - HASH_VALUE(keyptr, keylen_in, _ha_hashv); \ - HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, _ha_hashv, add); \ -} while (0) - -#define HASH_ADD_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add) \ - HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add) - -#define HASH_ADD(hh,head,fieldname,keylen_in,add) \ - HASH_ADD_KEYPTR(hh, head, &((add)->fieldname), keylen_in, add) - -#define HASH_TO_BKT(hashv,num_bkts,bkt) \ -do { \ - bkt = ((hashv) & ((num_bkts) - 1U)); \ -} while (0) - -/* delete "delptr" from the hash table. - * "the usual" patch-up process for the app-order doubly-linked-list. - * The use of _hd_hh_del below deserves special explanation. - * These used to be expressed using (delptr) but that led to a bug - * if someone used the same symbol for the head and deletee, like - * HASH_DELETE(hh,users,users); - * We want that to work, but by changing the head (users) below - * we were forfeiting our ability to further refer to the deletee (users) - * in the patch-up process. Solution: use scratch space to - * copy the deletee pointer, then the latter references are via that - * scratch pointer rather than through the repointed (users) symbol. - */ -#define HASH_DELETE(hh,head,delptr) \ -do { \ - struct UT_hash_handle *_hd_hh_del; \ - if ( ((delptr)->hh.prev == NULL) && ((delptr)->hh.next == NULL) ) { \ - uthash_free((head)->hh.tbl->buckets, \ - (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket) ); \ - HASH_BLOOM_FREE((head)->hh.tbl); \ - uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ - head = NULL; \ - } else { \ - unsigned _hd_bkt; \ - _hd_hh_del = &((delptr)->hh); \ - if ((delptr) == ELMT_FROM_HH((head)->hh.tbl,(head)->hh.tbl->tail)) { \ - (head)->hh.tbl->tail = \ - (UT_hash_handle*)((ptrdiff_t)((delptr)->hh.prev) + \ - (head)->hh.tbl->hho); \ - } \ - if ((delptr)->hh.prev != NULL) { \ - ((UT_hash_handle*)((ptrdiff_t)((delptr)->hh.prev) + \ - (head)->hh.tbl->hho))->next = (delptr)->hh.next; \ - } else { \ - DECLTYPE_ASSIGN(head,(delptr)->hh.next); \ - } \ - if (_hd_hh_del->next != NULL) { \ - ((UT_hash_handle*)((ptrdiff_t)_hd_hh_del->next + \ - (head)->hh.tbl->hho))->prev = \ - _hd_hh_del->prev; \ - } \ - HASH_TO_BKT( _hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ - HASH_DEL_IN_BKT(hh,(head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \ - (head)->hh.tbl->num_items--; \ - } \ - HASH_FSCK(hh,head); \ -} while (0) - - -/* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */ -#define HASH_FIND_STR(head,findstr,out) \ - HASH_FIND(hh,head,findstr,(unsigned)uthash_strlen(findstr),out) -#define HASH_ADD_STR(head,strfield,add) \ - HASH_ADD(hh,head,strfield[0],(unsigned)uthash_strlen(add->strfield),add) -#define HASH_REPLACE_STR(head,strfield,add,replaced) \ - HASH_REPLACE(hh,head,strfield[0],(unsigned)uthash_strlen(add->strfield),add,replaced) -#define HASH_FIND_INT(head,findint,out) \ - HASH_FIND(hh,head,findint,sizeof(int),out) -#define HASH_ADD_INT(head,intfield,add) \ - HASH_ADD(hh,head,intfield,sizeof(int),add) -#define HASH_REPLACE_INT(head,intfield,add,replaced) \ - HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced) -#define HASH_FIND_PTR(head,findptr,out) \ - HASH_FIND(hh,head,findptr,sizeof(void *),out) -#define HASH_ADD_PTR(head,ptrfield,add) \ - HASH_ADD(hh,head,ptrfield,sizeof(void *),add) -#define HASH_REPLACE_PTR(head,ptrfield,add,replaced) \ - HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced) -#define HASH_DEL(head,delptr) \ - HASH_DELETE(hh,head,delptr) - -/* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined. - * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined. - */ -#ifdef HASH_DEBUG -#define HASH_OOPS(...) do { fprintf(stderr,__VA_ARGS__); exit(-1); } while (0) -#define HASH_FSCK(hh,head) \ -do { \ - struct UT_hash_handle *_thh; \ - if (head) { \ - unsigned _bkt_i; \ - unsigned _count; \ - char *_prev; \ - _count = 0; \ - for( _bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; _bkt_i++) { \ - unsigned _bkt_count = 0; \ - _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \ - _prev = NULL; \ - while (_thh) { \ - if (_prev != (char*)(_thh->hh_prev)) { \ - HASH_OOPS("invalid hh_prev %p, actual %p\n", \ - _thh->hh_prev, _prev ); \ - } \ - _bkt_count++; \ - _prev = (char*)(_thh); \ - _thh = _thh->hh_next; \ - } \ - _count += _bkt_count; \ - if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) { \ - HASH_OOPS("invalid bucket count %u, actual %u\n", \ - (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \ - } \ - } \ - if (_count != (head)->hh.tbl->num_items) { \ - HASH_OOPS("invalid hh item count %u, actual %u\n", \ - (head)->hh.tbl->num_items, _count ); \ - } \ - /* traverse hh in app order; check next/prev integrity, count */ \ - _count = 0; \ - _prev = NULL; \ - _thh = &(head)->hh; \ - while (_thh) { \ - _count++; \ - if (_prev !=(char*)(_thh->prev)) { \ - HASH_OOPS("invalid prev %p, actual %p\n", \ - _thh->prev, _prev ); \ - } \ - _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh); \ - _thh = ( _thh->next ? (UT_hash_handle*)((char*)(_thh->next) + \ - (head)->hh.tbl->hho) : NULL ); \ - } \ - if (_count != (head)->hh.tbl->num_items) { \ - HASH_OOPS("invalid app item count %u, actual %u\n", \ - (head)->hh.tbl->num_items, _count ); \ - } \ - } \ -} while (0) -#else -#define HASH_FSCK(hh,head) -#endif - -/* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to - * the descriptor to which this macro is defined for tuning the hash function. - * The app can #include <unistd.h> to get the prototype for write(2). */ -#ifdef HASH_EMIT_KEYS -#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) \ -do { \ - unsigned _klen = fieldlen; \ - write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \ - write(HASH_EMIT_KEYS, keyptr, (unsigned long)fieldlen); \ -} while (0) -#else -#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) -#endif - -/* default to Jenkin's hash unless overridden e.g. DHASH_FUNCTION=HASH_SAX */ -#ifdef HASH_FUNCTION -#define HASH_FCN HASH_FUNCTION -#else -#define HASH_FCN HASH_JEN -#endif - -/* The Bernstein hash function, used in Perl prior to v5.6. Note (x<<5+x)=x*33. */ -#define HASH_BER(key,keylen,hashv) \ -do { \ - unsigned _hb_keylen=(unsigned)keylen; \ - const unsigned char *_hb_key=(const unsigned char*)(key); \ - (hashv) = 0; \ - while (_hb_keylen-- != 0U) { \ - (hashv) = (((hashv) << 5) + (hashv)) + *_hb_key++; \ - } \ -} while (0) - - -/* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at - * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx */ -#define HASH_SAX(key,keylen,hashv) \ -do { \ - unsigned _sx_i; \ - const unsigned char *_hs_key=(const unsigned char*)(key); \ - hashv = 0; \ - for(_sx_i=0; _sx_i < keylen; _sx_i++) { \ - hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i]; \ - } \ -} while (0) -/* FNV-1a variation */ -#define HASH_FNV(key,keylen,hashv) \ -do { \ - unsigned _fn_i; \ - const unsigned char *_hf_key=(const unsigned char*)(key); \ - hashv = 2166136261U; \ - for(_fn_i=0; _fn_i < keylen; _fn_i++) { \ - hashv = hashv ^ _hf_key[_fn_i]; \ - hashv = hashv * 16777619U; \ - } \ -} while (0) - -#define HASH_OAT(key,keylen,hashv) \ -do { \ - unsigned _ho_i; \ - const unsigned char *_ho_key=(const unsigned char*)(key); \ - hashv = 0; \ - for(_ho_i=0; _ho_i < keylen; _ho_i++) { \ - hashv += _ho_key[_ho_i]; \ - hashv += (hashv << 10); \ - hashv ^= (hashv >> 6); \ - } \ - hashv += (hashv << 3); \ - hashv ^= (hashv >> 11); \ - hashv += (hashv << 15); \ -} while (0) - -#define HASH_JEN_MIX(a,b,c) \ -do { \ - a -= b; a -= c; a ^= ( c >> 13 ); \ - b -= c; b -= a; b ^= ( a << 8 ); \ - c -= a; c -= b; c ^= ( b >> 13 ); \ - a -= b; a -= c; a ^= ( c >> 12 ); \ - b -= c; b -= a; b ^= ( a << 16 ); \ - c -= a; c -= b; c ^= ( b >> 5 ); \ - a -= b; a -= c; a ^= ( c >> 3 ); \ - b -= c; b -= a; b ^= ( a << 10 ); \ - c -= a; c -= b; c ^= ( b >> 15 ); \ -} while (0) - -#define HASH_JEN(key,keylen,hashv) \ -do { \ - unsigned _hj_i,_hj_j,_hj_k; \ - unsigned const char *_hj_key=(unsigned const char*)(key); \ - hashv = 0xfeedbeefu; \ - _hj_i = _hj_j = 0x9e3779b9u; \ - _hj_k = (unsigned)(keylen); \ - while (_hj_k >= 12U) { \ - _hj_i += (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 ) \ - + ( (unsigned)_hj_key[2] << 16 ) \ - + ( (unsigned)_hj_key[3] << 24 ) ); \ - _hj_j += (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 ) \ - + ( (unsigned)_hj_key[6] << 16 ) \ - + ( (unsigned)_hj_key[7] << 24 ) ); \ - hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 ) \ - + ( (unsigned)_hj_key[10] << 16 ) \ - + ( (unsigned)_hj_key[11] << 24 ) ); \ - \ - HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ - \ - _hj_key += 12; \ - _hj_k -= 12U; \ - } \ - hashv += (unsigned)(keylen); \ - switch ( _hj_k ) { \ - case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); /* FALLTHROUGH */ \ - case 10: hashv += ( (unsigned)_hj_key[9] << 16 ); /* FALLTHROUGH */ \ - case 9: hashv += ( (unsigned)_hj_key[8] << 8 ); /* FALLTHROUGH */ \ - case 8: _hj_j += ( (unsigned)_hj_key[7] << 24 ); /* FALLTHROUGH */ \ - case 7: _hj_j += ( (unsigned)_hj_key[6] << 16 ); /* FALLTHROUGH */ \ - case 6: _hj_j += ( (unsigned)_hj_key[5] << 8 ); /* FALLTHROUGH */ \ - case 5: _hj_j += _hj_key[4]; /* FALLTHROUGH */ \ - case 4: _hj_i += ( (unsigned)_hj_key[3] << 24 ); /* FALLTHROUGH */ \ - case 3: _hj_i += ( (unsigned)_hj_key[2] << 16 ); /* FALLTHROUGH */ \ - case 2: _hj_i += ( (unsigned)_hj_key[1] << 8 ); /* FALLTHROUGH */ \ - case 1: _hj_i += _hj_key[0]; \ - } \ - HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ -} while (0) - -/* The Paul Hsieh hash function */ -#undef get16bits -#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \ - || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__) -#define get16bits(d) (*((const uint16_t *) (d))) -#endif - -#if !defined (get16bits) -#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) \ - +(uint32_t)(((const uint8_t *)(d))[0]) ) -#endif -#define HASH_SFH(key,keylen,hashv) \ -do { \ - unsigned const char *_sfh_key=(unsigned const char*)(key); \ - uint32_t _sfh_tmp, _sfh_len = (uint32_t)keylen; \ - \ - unsigned _sfh_rem = _sfh_len & 3U; \ - _sfh_len >>= 2; \ - hashv = 0xcafebabeu; \ - \ - /* Main loop */ \ - for (;_sfh_len > 0U; _sfh_len--) { \ - hashv += get16bits (_sfh_key); \ - _sfh_tmp = ((uint32_t)(get16bits (_sfh_key+2)) << 11) ^ hashv; \ - hashv = (hashv << 16) ^ _sfh_tmp; \ - _sfh_key += 2U*sizeof (uint16_t); \ - hashv += hashv >> 11; \ - } \ - \ - /* Handle end cases */ \ - switch (_sfh_rem) { \ - case 3: hashv += get16bits (_sfh_key); \ - hashv ^= hashv << 16; \ - hashv ^= (uint32_t)(_sfh_key[sizeof (uint16_t)]) << 18; \ - hashv += hashv >> 11; \ - break; \ - case 2: hashv += get16bits (_sfh_key); \ - hashv ^= hashv << 11; \ - hashv += hashv >> 17; \ - break; \ - case 1: hashv += *_sfh_key; \ - hashv ^= hashv << 10; \ - hashv += hashv >> 1; \ - } \ - \ - /* Force "avalanching" of final 127 bits */ \ - hashv ^= hashv << 3; \ - hashv += hashv >> 5; \ - hashv ^= hashv << 4; \ - hashv += hashv >> 17; \ - hashv ^= hashv << 25; \ - hashv += hashv >> 6; \ -} while (0) - -#ifdef HASH_USING_NO_STRICT_ALIASING -/* The MurmurHash exploits some CPU's (x86,x86_64) tolerance for unaligned reads. - * For other types of CPU's (e.g. Sparc) an unaligned read causes a bus error. - * MurmurHash uses the faster approach only on CPU's where we know it's safe. - * - * Note the preprocessor built-in defines can be emitted using: - * - * gcc -m64 -dM -E - < /dev/null (on gcc) - * cc -## a.c (where a.c is a simple test file) (Sun Studio) - */ -#if (defined(__i386__) || defined(__x86_64__) || defined(_M_IX86)) -#define MUR_GETBLOCK(p,i) p[i] -#else /* non intel */ -#define MUR_PLUS0_ALIGNED(p) (((unsigned long)p & 3UL) == 0UL) -#define MUR_PLUS1_ALIGNED(p) (((unsigned long)p & 3UL) == 1UL) -#define MUR_PLUS2_ALIGNED(p) (((unsigned long)p & 3UL) == 2UL) -#define MUR_PLUS3_ALIGNED(p) (((unsigned long)p & 3UL) == 3UL) -#define WP(p) ((uint32_t*)((unsigned long)(p) & ~3UL)) -#if (defined(__BIG_ENDIAN__) || defined(SPARC) || defined(__ppc__) || defined(__ppc64__)) -#define MUR_THREE_ONE(p) ((((*WP(p))&0x00ffffff) << 8) | (((*(WP(p)+1))&0xff000000) >> 24)) -#define MUR_TWO_TWO(p) ((((*WP(p))&0x0000ffff) <<16) | (((*(WP(p)+1))&0xffff0000) >> 16)) -#define MUR_ONE_THREE(p) ((((*WP(p))&0x000000ff) <<24) | (((*(WP(p)+1))&0xffffff00) >> 8)) -#else /* assume little endian non-intel */ -#define MUR_THREE_ONE(p) ((((*WP(p))&0xffffff00) >> 8) | (((*(WP(p)+1))&0x000000ff) << 24)) -#define MUR_TWO_TWO(p) ((((*WP(p))&0xffff0000) >>16) | (((*(WP(p)+1))&0x0000ffff) << 16)) -#define MUR_ONE_THREE(p) ((((*WP(p))&0xff000000) >>24) | (((*(WP(p)+1))&0x00ffffff) << 8)) -#endif -#define MUR_GETBLOCK(p,i) (MUR_PLUS0_ALIGNED(p) ? ((p)[i]) : \ - (MUR_PLUS1_ALIGNED(p) ? MUR_THREE_ONE(p) : \ - (MUR_PLUS2_ALIGNED(p) ? MUR_TWO_TWO(p) : \ - MUR_ONE_THREE(p)))) -#endif -#define MUR_ROTL32(x,r) (((x) << (r)) | ((x) >> (32 - (r)))) -#define MUR_FMIX(_h) \ -do { \ - _h ^= _h >> 16; \ - _h *= 0x85ebca6bu; \ - _h ^= _h >> 13; \ - _h *= 0xc2b2ae35u; \ - _h ^= _h >> 16; \ -} while (0) - -#define HASH_MUR(key,keylen,hashv) \ -do { \ - const uint8_t *_mur_data = (const uint8_t*)(key); \ - const int _mur_nblocks = (int)(keylen) / 4; \ - uint32_t _mur_h1 = 0xf88D5353u; \ - uint32_t _mur_c1 = 0xcc9e2d51u; \ - uint32_t _mur_c2 = 0x1b873593u; \ - uint32_t _mur_k1 = 0; \ - const uint8_t *_mur_tail; \ - const uint32_t *_mur_blocks = (const uint32_t*)(_mur_data+(_mur_nblocks*4)); \ - int _mur_i; \ - for(_mur_i = -_mur_nblocks; _mur_i!=0; _mur_i++) { \ - _mur_k1 = MUR_GETBLOCK(_mur_blocks,_mur_i); \ - _mur_k1 *= _mur_c1; \ - _mur_k1 = MUR_ROTL32(_mur_k1,15); \ - _mur_k1 *= _mur_c2; \ - \ - _mur_h1 ^= _mur_k1; \ - _mur_h1 = MUR_ROTL32(_mur_h1,13); \ - _mur_h1 = (_mur_h1*5U) + 0xe6546b64u; \ - } \ - _mur_tail = (const uint8_t*)(_mur_data + (_mur_nblocks*4)); \ - _mur_k1=0; \ - switch((keylen) & 3U) { \ - case 3: _mur_k1 ^= (uint32_t)_mur_tail[2] << 16; /* FALLTHROUGH */ \ - case 2: _mur_k1 ^= (uint32_t)_mur_tail[1] << 8; /* FALLTHROUGH */ \ - case 1: _mur_k1 ^= (uint32_t)_mur_tail[0]; \ - _mur_k1 *= _mur_c1; \ - _mur_k1 = MUR_ROTL32(_mur_k1,15); \ - _mur_k1 *= _mur_c2; \ - _mur_h1 ^= _mur_k1; \ - } \ - _mur_h1 ^= (uint32_t)(keylen); \ - MUR_FMIX(_mur_h1); \ - hashv = _mur_h1; \ -} while (0) -#endif /* HASH_USING_NO_STRICT_ALIASING */ - -/* iterate over items in a known bucket to find desired item */ -#define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,hashval,out) \ -do { \ - if ((head).hh_head != NULL) { \ - DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (head).hh_head)); \ - } else { \ - (out) = NULL; \ - } \ - while ((out) != NULL) { \ - if ((out)->hh.hashv == (hashval) && (out)->hh.keylen == (keylen_in)) { \ - if (uthash_memcmp((out)->hh.key, keyptr, keylen_in) == 0) { \ - break; \ - } \ - } \ - if ((out)->hh.hh_next != NULL) { \ - DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (out)->hh.hh_next)); \ - } else { \ - (out) = NULL; \ - } \ - } \ -} while (0) - -/* add an item to a bucket */ -#define HASH_ADD_TO_BKT(head,addhh) \ -do { \ - head.count++; \ - (addhh)->hh_next = head.hh_head; \ - (addhh)->hh_prev = NULL; \ - if (head.hh_head != NULL) { (head).hh_head->hh_prev = (addhh); } \ - (head).hh_head=addhh; \ - if ((head.count >= ((head.expand_mult+1U) * HASH_BKT_CAPACITY_THRESH)) \ - && ((addhh)->tbl->noexpand != 1U)) { \ - HASH_EXPAND_BUCKETS((addhh)->tbl); \ - } \ -} while (0) - -/* remove an item from a given bucket */ -#define HASH_DEL_IN_BKT(hh,head,hh_del) \ - (head).count--; \ - if ((head).hh_head == hh_del) { \ - (head).hh_head = hh_del->hh_next; \ - } \ - if (hh_del->hh_prev) { \ - hh_del->hh_prev->hh_next = hh_del->hh_next; \ - } \ - if (hh_del->hh_next) { \ - hh_del->hh_next->hh_prev = hh_del->hh_prev; \ - } - -/* Bucket expansion has the effect of doubling the number of buckets - * and redistributing the items into the new buckets. Ideally the - * items will distribute more or less evenly into the new buckets - * (the extent to which this is true is a measure of the quality of - * the hash function as it applies to the key domain). - * - * With the items distributed into more buckets, the chain length - * (item count) in each bucket is reduced. Thus by expanding buckets - * the hash keeps a bound on the chain length. This bounded chain - * length is the essence of how a hash provides constant time lookup. - * - * The calculation of tbl->ideal_chain_maxlen below deserves some - * explanation. First, keep in mind that we're calculating the ideal - * maximum chain length based on the *new* (doubled) bucket count. - * In fractions this is just n/b (n=number of items,b=new num buckets). - * Since the ideal chain length is an integer, we want to calculate - * ceil(n/b). We don't depend on floating point arithmetic in this - * hash, so to calculate ceil(n/b) with integers we could write - * - * ceil(n/b) = (n/b) + ((n%b)?1:0) - * - * and in fact a previous version of this hash did just that. - * But now we have improved things a bit by recognizing that b is - * always a power of two. We keep its base 2 log handy (call it lb), - * so now we can write this with a bit shift and logical AND: - * - * ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0) - * - */ -#define HASH_EXPAND_BUCKETS(tbl) \ -do { \ - unsigned _he_bkt; \ - unsigned _he_bkt_i; \ - struct UT_hash_handle *_he_thh, *_he_hh_nxt; \ - UT_hash_bucket *_he_new_buckets, *_he_newbkt; \ - _he_new_buckets = (UT_hash_bucket*)uthash_malloc( \ - 2UL * tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ - if (!_he_new_buckets) { uthash_fatal( "out of memory"); } \ - memset(_he_new_buckets, 0, \ - 2UL * tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ - tbl->ideal_chain_maxlen = \ - (tbl->num_items >> (tbl->log2_num_buckets+1U)) + \ - (((tbl->num_items & ((tbl->num_buckets*2U)-1U)) != 0U) ? 1U : 0U); \ - tbl->nonideal_items = 0; \ - for(_he_bkt_i = 0; _he_bkt_i < tbl->num_buckets; _he_bkt_i++) \ - { \ - _he_thh = tbl->buckets[ _he_bkt_i ].hh_head; \ - while (_he_thh != NULL) { \ - _he_hh_nxt = _he_thh->hh_next; \ - HASH_TO_BKT( _he_thh->hashv, tbl->num_buckets*2U, _he_bkt); \ - _he_newbkt = &(_he_new_buckets[ _he_bkt ]); \ - if (++(_he_newbkt->count) > tbl->ideal_chain_maxlen) { \ - tbl->nonideal_items++; \ - _he_newbkt->expand_mult = _he_newbkt->count / \ - tbl->ideal_chain_maxlen; \ - } \ - _he_thh->hh_prev = NULL; \ - _he_thh->hh_next = _he_newbkt->hh_head; \ - if (_he_newbkt->hh_head != NULL) { _he_newbkt->hh_head->hh_prev = \ - _he_thh; } \ - _he_newbkt->hh_head = _he_thh; \ - _he_thh = _he_hh_nxt; \ - } \ - } \ - uthash_free( tbl->buckets, tbl->num_buckets*sizeof(struct UT_hash_bucket) ); \ - tbl->num_buckets *= 2U; \ - tbl->log2_num_buckets++; \ - tbl->buckets = _he_new_buckets; \ - tbl->ineff_expands = (tbl->nonideal_items > (tbl->num_items >> 1)) ? \ - (tbl->ineff_expands+1U) : 0U; \ - if (tbl->ineff_expands > 1U) { \ - tbl->noexpand=1; \ - uthash_noexpand_fyi(tbl); \ - } \ - uthash_expand_fyi(tbl); \ -} while (0) - - -/* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */ -/* Note that HASH_SORT assumes the hash handle name to be hh. - * HASH_SRT was added to allow the hash handle name to be passed in. */ -#define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn) -#define HASH_SRT(hh,head,cmpfcn) \ -do { \ - unsigned _hs_i; \ - unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize; \ - struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \ - if (head != NULL) { \ - _hs_insize = 1; \ - _hs_looping = 1; \ - _hs_list = &((head)->hh); \ - while (_hs_looping != 0U) { \ - _hs_p = _hs_list; \ - _hs_list = NULL; \ - _hs_tail = NULL; \ - _hs_nmerges = 0; \ - while (_hs_p != NULL) { \ - _hs_nmerges++; \ - _hs_q = _hs_p; \ - _hs_psize = 0; \ - for ( _hs_i = 0; _hs_i < _hs_insize; _hs_i++ ) { \ - _hs_psize++; \ - _hs_q = (UT_hash_handle*)((_hs_q->next != NULL) ? \ - ((void*)((char*)(_hs_q->next) + \ - (head)->hh.tbl->hho)) : NULL); \ - if (! (_hs_q) ) { break; } \ - } \ - _hs_qsize = _hs_insize; \ - while ((_hs_psize > 0U) || ((_hs_qsize > 0U) && (_hs_q != NULL))) {\ - if (_hs_psize == 0U) { \ - _hs_e = _hs_q; \ - _hs_q = (UT_hash_handle*)((_hs_q->next != NULL) ? \ - ((void*)((char*)(_hs_q->next) + \ - (head)->hh.tbl->hho)) : NULL); \ - _hs_qsize--; \ - } else if ( (_hs_qsize == 0U) || (_hs_q == NULL) ) { \ - _hs_e = _hs_p; \ - if (_hs_p != NULL){ \ - _hs_p = (UT_hash_handle*)((_hs_p->next != NULL) ? \ - ((void*)((char*)(_hs_p->next) + \ - (head)->hh.tbl->hho)) : NULL); \ - } \ - _hs_psize--; \ - } else if (( \ - cmpfcn(DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl,_hs_p)), \ - DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl,_hs_q))) \ - ) <= 0) { \ - _hs_e = _hs_p; \ - if (_hs_p != NULL){ \ - _hs_p = (UT_hash_handle*)((_hs_p->next != NULL) ? \ - ((void*)((char*)(_hs_p->next) + \ - (head)->hh.tbl->hho)) : NULL); \ - } \ - _hs_psize--; \ - } else { \ - _hs_e = _hs_q; \ - _hs_q = (UT_hash_handle*)((_hs_q->next != NULL) ? \ - ((void*)((char*)(_hs_q->next) + \ - (head)->hh.tbl->hho)) : NULL); \ - _hs_qsize--; \ - } \ - if ( _hs_tail != NULL ) { \ - _hs_tail->next = ((_hs_e != NULL) ? \ - ELMT_FROM_HH((head)->hh.tbl,_hs_e) : NULL); \ - } else { \ - _hs_list = _hs_e; \ - } \ - if (_hs_e != NULL) { \ - _hs_e->prev = ((_hs_tail != NULL) ? \ - ELMT_FROM_HH((head)->hh.tbl,_hs_tail) : NULL); \ - } \ - _hs_tail = _hs_e; \ - } \ - _hs_p = _hs_q; \ - } \ - if (_hs_tail != NULL){ \ - _hs_tail->next = NULL; \ - } \ - if ( _hs_nmerges <= 1U ) { \ - _hs_looping=0; \ - (head)->hh.tbl->tail = _hs_tail; \ - DECLTYPE_ASSIGN(head,ELMT_FROM_HH((head)->hh.tbl, _hs_list)); \ - } \ - _hs_insize *= 2U; \ - } \ - HASH_FSCK(hh,head); \ - } \ -} while (0) - -/* This function selects items from one hash into another hash. - * The end result is that the selected items have dual presence - * in both hashes. There is no copy of the items made; rather - * they are added into the new hash through a secondary hash - * hash handle that must be present in the structure. */ -#define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \ -do { \ - unsigned _src_bkt, _dst_bkt; \ - void *_last_elt=NULL, *_elt; \ - UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL; \ - ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst)); \ - if (src != NULL) { \ - for(_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) { \ - for(_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \ - _src_hh != NULL; \ - _src_hh = _src_hh->hh_next) { \ - _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \ - if (cond(_elt)) { \ - _dst_hh = (UT_hash_handle*)(((char*)_elt) + _dst_hho); \ - _dst_hh->key = _src_hh->key; \ - _dst_hh->keylen = _src_hh->keylen; \ - _dst_hh->hashv = _src_hh->hashv; \ - _dst_hh->prev = _last_elt; \ - _dst_hh->next = NULL; \ - if (_last_elt_hh != NULL) { _last_elt_hh->next = _elt; } \ - if (dst == NULL) { \ - DECLTYPE_ASSIGN(dst,_elt); \ - HASH_MAKE_TABLE(hh_dst,dst); \ - } else { \ - _dst_hh->tbl = (dst)->hh_dst.tbl; \ - } \ - HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt); \ - HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt],_dst_hh); \ - (dst)->hh_dst.tbl->num_items++; \ - _last_elt = _elt; \ - _last_elt_hh = _dst_hh; \ - } \ - } \ - } \ - } \ - HASH_FSCK(hh_dst,dst); \ -} while (0) - -#define HASH_CLEAR(hh,head) \ -do { \ - if (head != NULL) { \ - uthash_free((head)->hh.tbl->buckets, \ - (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket)); \ - HASH_BLOOM_FREE((head)->hh.tbl); \ - uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ - (head)=NULL; \ - } \ -} while (0) - -#define HASH_OVERHEAD(hh,head) \ - ((head != NULL) ? ( \ - (size_t)(((head)->hh.tbl->num_items * sizeof(UT_hash_handle)) + \ - ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket)) + \ - sizeof(UT_hash_table) + \ - (HASH_BLOOM_BYTELEN))) : 0U) - -#ifdef NO_DECLTYPE -#define HASH_ITER(hh,head,el,tmp) \ -for(((el)=(head)), ((*(char**)(&(tmp)))=(char*)((head!=NULL)?(head)->hh.next:NULL)); \ - (el) != NULL; ((el)=(tmp)), ((*(char**)(&(tmp)))=(char*)((tmp!=NULL)?(tmp)->hh.next:NULL))) -#else -#define HASH_ITER(hh,head,el,tmp) \ -for(((el)=(head)), ((tmp)=DECLTYPE(el)((head!=NULL)?(head)->hh.next:NULL)); \ - (el) != NULL; ((el)=(tmp)), ((tmp)=DECLTYPE(el)((tmp!=NULL)?(tmp)->hh.next:NULL))) -#endif - -/* obtain a count of items in the hash */ -#define HASH_COUNT(head) HASH_CNT(hh,head) -#define HASH_CNT(hh,head) ((head != NULL)?((head)->hh.tbl->num_items):0U) - -typedef struct UT_hash_bucket { - struct UT_hash_handle *hh_head; - unsigned count; - - /* expand_mult is normally set to 0. In this situation, the max chain length - * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If - * the bucket's chain exceeds this length, bucket expansion is triggered). - * However, setting expand_mult to a non-zero value delays bucket expansion - * (that would be triggered by additions to this particular bucket) - * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH. - * (The multiplier is simply expand_mult+1). The whole idea of this - * multiplier is to reduce bucket expansions, since they are expensive, in - * situations where we know that a particular bucket tends to be overused. - * It is better to let its chain length grow to a longer yet-still-bounded - * value, than to do an O(n) bucket expansion too often. - */ - unsigned expand_mult; - -} UT_hash_bucket; - -/* random signature used only to find hash tables in external analysis */ -#define HASH_SIGNATURE 0xa0111fe1u -#define HASH_BLOOM_SIGNATURE 0xb12220f2u - -typedef struct UT_hash_table { - UT_hash_bucket *buckets; - unsigned num_buckets, log2_num_buckets; - unsigned num_items; - struct UT_hash_handle *tail; /* tail hh in app order, for fast append */ - ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */ - - /* in an ideal situation (all buckets used equally), no bucket would have - * more than ceil(#items/#buckets) items. that's the ideal chain length. */ - unsigned ideal_chain_maxlen; - - /* nonideal_items is the number of items in the hash whose chain position - * exceeds the ideal chain maxlen. these items pay the penalty for an uneven - * hash distribution; reaching them in a chain traversal takes >ideal steps */ - unsigned nonideal_items; - - /* ineffective expands occur when a bucket doubling was performed, but - * afterward, more than half the items in the hash had nonideal chain - * positions. If this happens on two consecutive expansions we inhibit any - * further expansion, as it's not helping; this happens when the hash - * function isn't a good fit for the key domain. When expansion is inhibited - * the hash will still work, albeit no longer in constant time. */ - unsigned ineff_expands, noexpand; - - uint32_t signature; /* used only to find hash tables in external analysis */ -#ifdef HASH_BLOOM - uint32_t bloom_sig; /* used only to test bloom exists in external analysis */ - uint8_t *bloom_bv; - uint8_t bloom_nbits; -#endif - -} UT_hash_table; - -typedef struct UT_hash_handle { - struct UT_hash_table *tbl; - void *prev; /* prev element in app order */ - void *next; /* next element in app order */ - struct UT_hash_handle *hh_prev; /* previous hh in bucket order */ - struct UT_hash_handle *hh_next; /* next hh in bucket order */ - void *key; /* ptr to enclosing struct's key */ - unsigned keylen; /* enclosing struct's key len */ - unsigned hashv; /* result of hash-fcn(key) */ -} UT_hash_handle; - -#endif /* UTHASH_H */