#include "base.h" #include "util.h" #include "unicode.h" #if _WIN32 #include #include #include #elif __unix__ #include #else #error "Unrecognized operating system." #endif #include #include // on 16-bit systems, this is 16383. on 32/64-bit systems, this is 1073741823 // it is unusual to have a string that long. #define STRLEN_SAFE_MAX (UINT_MAX >> 2) bool is32_word(char32_t c) { return c > WCHAR_MAX || c == '_' || iswalnum((wint_t)c); } bool is32_space(char32_t c) { return c <= WINT_MAX && iswspace((wint_t)c); } bool is32_alpha(char32_t c) { return c <= WINT_MAX && iswalpha((wint_t)c); } bool is32_alnum(char32_t c) { return c <= WINT_MAX && iswalnum((wint_t)c); } bool is32_digit(char32_t c) { return c <= WINT_MAX && iswdigit((wint_t)c); } bool is32_graph(char32_t c) { return c <= WINT_MAX && iswgraph((wint_t)c); } bool is_a_tty(FILE *out) { #if _WIN32 int fd = _fileno(out); return fd >= 0 && _isatty(fd); #else int fd = fileno(out); return fd >= 0 && isatty(fd); #endif } const char *term_italics(FILE *out) { return is_a_tty(out) ? "\x1b[3m" : ""; } const char *term_bold(FILE *out) { return is_a_tty(out) ? "\x1b[1m" : ""; } const char *term_yellow(FILE *out) { return is_a_tty(out) ? "\x1b[93m" : ""; } const char *term_clear(FILE *out) { return is_a_tty(out) ? "\x1b[0m" : ""; } u8 util_popcount(u64 x) { #ifdef __GNUC__ return (u8)__builtin_popcountll(x); #else u8 count = 0; while (x) { x &= x-1; ++count; } return count; #endif } u8 util_count_leading_zeroes32(u32 x) { if (x == 0) return 32; // GCC's __builtin_clz is undefined for x = 0 #if __GNUC__ && UINT_MAX == 4294967295 return (u8)__builtin_clz(x); #elif _WIN32 && UINT_MAX == 4294967295 return (u8)__lzcnt(x); #else u8 count = 0; for (int i = 31; i >= 0; --i) { if (x & ((u32)1< len) n = len; char *ret = malloc(n + 1); if (ret) { memcpy(ret, src, n); ret[n] = 0; } return ret; } // duplicates a null-terminated string. the returned string should be passed to free() char *str_dup(const char *src) { return strn_dup(src, SIZE_MAX); } // safer version of strncat. dst_sz includes a null terminator. void strn_cat(char *dst, size_t dst_sz, const char *src, size_t src_len) { size_t dst_len = strlen(dst); // make sure dst_len + src_len + 1 doesn't overflow if (dst_len > STRLEN_SAFE_MAX || src_len > STRLEN_SAFE_MAX) { assert(0); return; } if (dst_len >= dst_sz) { // dst doesn't actually contain a null-terminated string! assert(0); return; } if (dst_len + src_len + 1 > dst_sz) { // number of bytes left in dst, not including null terminator size_t n = dst_sz - dst_len - 1; memcpy(dst + dst_len, src, n); dst[dst_sz - 1] = 0; // dst_len + n == dst_sz - 1 } else { memcpy(dst + dst_len, src, src_len); dst[dst_len + src_len] = 0; } } // safer version of strcat. dst_sz includes a null terminator. void str_cat(char *dst, size_t dst_sz, const char *src) { strn_cat(dst, dst_sz, src, strlen(src)); } // safer version of strncpy. dst_sz includes a null terminator. void strn_cpy(char *dst, size_t dst_sz, const char *src, size_t src_len) { size_t n = src_len; // number of bytes to copy for (size_t i = 0; i < n; ++i) { if (src[i] == '\0') { n = i; break; } } if (dst_sz == 0) { assert(0); return; } if (dst_sz-1 < n) n = dst_sz-1; memcpy(dst, src, n); dst[n] = 0; } void str_cpy(char *dst, size_t dst_sz, const char *src) { strn_cpy(dst, dst_sz, src, SIZE_MAX); } void str_trim_start(char *str) { size_t n = strspn(str, "\r\v\t\n\f "); size_t len = strlen(str); memmove(str, str + n, len - n); str[len - n] = '\0'; } void str_trim_end(char *str) { size_t i = strlen(str); while (i > 0 && isspace(str[i - 1])) { str[i - 1] = '\0'; --i; } } void str_trim(char *str) { str_trim_end(str); str_trim_start(str); } char *a_sprintf(PRINTF_FORMAT_STRING const char *fmt, ...) ATTRIBUTE_PRINTF(1, 2); char *a_sprintf(const char *fmt, ...) { // idk if you can always just pass NULL to vsnprintf va_list args; char fakebuf[2] = {0}; va_start(args, fmt); int ret = vsnprintf(fakebuf, 1, fmt, args); va_end(args); if (ret < 0) return NULL; // bad format or something u32 n = (u32)ret; char *str = calloc(1, n + 1); va_start(args, fmt); vsnprintf(str, n + 1, fmt, args); va_end(args); return str; } void str_binary_number(char s[65], u64 n) { if (n == 0) { strcpy(s, "0"); return; } u64 digits = 0; u64 m = n; while (m) { m >>= 1; digits += 1; } m = n; s[digits] = '\0'; char *p = s + digits - 1; while (m) { *p-- = (m & 1) + '0'; m >>= 1; } } // advances str to the start of the next UTF8 character static void utf8_next_char_const(const char **str) { if (**str) { do { ++*str; } while (((u8)(**str) & 0xC0) == 0x80); // while we are on a continuation byte } } char *strstr_case_insensitive(const char *haystack, const char *needle) { size_t needle_bytes = strlen(needle), haystack_bytes = strlen(haystack); if (needle_bytes > haystack_bytes) return NULL; const char *haystack_end = haystack + haystack_bytes; const char *needle_end = needle + needle_bytes; for (const char *haystack_start = haystack; haystack_start + needle_bytes <= haystack_end; utf8_next_char_const(&haystack_start)) { const char *p = haystack_start, *q = needle; bool match = true; // check if p matches q while (q < needle_end) { char32_t pchar = 0, qchar = 0; size_t bytes_p = unicode_utf8_to_utf32(&pchar, p, (size_t)(haystack_end - p)); size_t bytes_q = unicode_utf8_to_utf32(&qchar, q, (size_t)(needle_end - q)); if (bytes_p >= (size_t)-2 || bytes_q >= (size_t)-2) return NULL; // invalid UTF-8 bool same = pchar == qchar; if (pchar < WINT_MAX && qchar < WINT_MAX) // on Windows, there is no way of finding the lower-case version of a codepoint outside the BMP. ): same = towlower((wint_t)pchar) == towlower((wint_t)qchar); if (!same) match = false; p += bytes_p; q += bytes_q; } if (match) return (char *)haystack_start; } return NULL; } void print_bytes(const u8 *bytes, size_t n) { const u8 *b, *end; for (b = bytes, end = bytes + n; b != end; ++b) printf("%02x ", *b); printf("\n"); } int strcmp_case_insensitive(const char *a, const char *b) { #if _WIN32 return _stricmp(a, b); #else return strcasecmp(a, b); #endif } bool streq_case_insensitive(const char *a, const char *b) { return strcmp_case_insensitive(a, b) == 0; } int str_qsort_case_insensitive_cmp(const void *av, const void *bv) { const char *const *a = av, *const *b = bv; return strcmp_case_insensitive(*a, *b); } #if _WIN32 void qsort_with_context(void *base, size_t nmemb, size_t size, int (*compar)(void *, const void *, const void *), void *arg) { qsort_s(base, nmemb, size, compar, arg); } #else typedef struct { int (*compar)(void *, const void *, const void *); void *context; } QSortWithContext; int qsort_with_context_cmp(const void *a, const void *b, void *context) { QSortWithContext *c = context; return c->compar(c->context, a, b); } void qsort_with_context(void *base, size_t nmemb, size_t size, int (*compar)(void *, const void *, const void *), void *arg) { QSortWithContext ctx = { .compar = compar, .context = arg }; qsort_r(base, nmemb, size, qsort_with_context_cmp, &ctx); } #endif const char *path_filename(const char *path) { for (int i = (int)strlen(path) - 1; i >= 0; --i) { if (strchr(ALL_PATH_SEPARATORS, path[i])) return &path[i+1]; } // (a relative path with no path separators) return path; } bool path_is_absolute(const char *path) { return strchr(ALL_PATH_SEPARATORS, path[0]) != NULL #if _WIN32 || path[1] == ':' #endif ; } void path_full(const char *dir, const char *relpath, char *abspath, size_t abspath_size) { assert(abspath_size); assert(dir[0]); abspath[0] = '\0'; if (path_is_absolute(relpath)) { if (strchr(ALL_PATH_SEPARATORS, relpath[0])) { // make sure that on windows, if dir's drive is C: the absolute path of \a is c:\a strn_cat(abspath, abspath_size, dir, strcspn(dir, ALL_PATH_SEPARATORS)); } else { // copy drive component (e.g. set abspath to "C:") size_t drive_len = strcspn(relpath, ALL_PATH_SEPARATORS); strn_cat(abspath, abspath_size, relpath, drive_len); relpath += drive_len; if (*relpath) ++relpath; // move past separator } } else { str_cpy(abspath, abspath_size, dir); } while (*relpath) { size_t component_len = strcspn(relpath, ALL_PATH_SEPARATORS); const char *component_end = relpath + component_len; size_t len = strlen(abspath); if (component_len == 1 && relpath[0] == '.') { // ., do nothing } else if (component_len == 2 && relpath[0] == '.' && relpath[1] == '.') { // .. char *lastsep = strrchr(abspath, PATH_SEPARATOR); if (lastsep) { if (lastsep == abspath) lastsep[1] = '\0'; // e.g. /abc else lastsep[0] = '\0'; } else { // e.g. if abspath is currently C: // (do nothing) } } else { if (len == 0 || abspath[len - 1] != PATH_SEPARATOR) str_cat(abspath, abspath_size, PATH_SEPARATOR_STR); strn_cat(abspath, abspath_size, relpath, component_len); } if (*component_end == 0) break; else relpath = component_end + 1; } } bool paths_eq(const char *path1, const char *path2) { #if __unix__ return streq(path1, path2); #else char fixed_path1[8192]; char fixed_path2[8192]; strbuf_cpy(fixed_path1, path1); strbuf_cpy(fixed_path2, path2); for (size_t i = 0; fixed_path1[i]; ++i) if (fixed_path1[i] == '/') fixed_path1[i] = '\\'; for (size_t i = 0; fixed_path2[i]; ++i) if (fixed_path2[i] == '/') fixed_path2[i] = '\\'; return _stricmp(fixed_path1, fixed_path2) == 0; #endif } void change_directory(const char *path) { #if _WIN32 _chdir(path); #else chdir(path); #endif } bool copy_file(const char *src, const char *dst) { bool success = false; FILE *src_file = fopen(src, "rb"); if (src_file) { FILE *dst_file = fopen(dst, "wb"); if (dst_file) { char buf[1024]; while (1) { size_t count = fread(buf, 1, sizeof buf, src_file); fwrite(buf, 1, count, dst_file); if (count < sizeof buf) break; } success = !ferror(src_file) && !ferror(dst_file); fclose(dst_file); } fclose(src_file); } return success; } #ifdef MATH_GL #undef MATH_GL #define MATH_GL 1 #endif float degrees(float r) { return r * (180.0f / PIf); } float radians(float r) { return r * (PIf / 180.f); } // map x from the interval [0, 1] to the interval [a, b]. does NOT clamp. float lerpf(float x, float a, float b) { return x * (b-a) + a; } // opposite of lerp; map x from the interval [a, b] to the interval [0, 1]. does NOT clamp. float normf(float x, float a, float b) { return (x-a) / (b-a); } float clampf(float x, float a, float b) { if (x < a) return a; if (x > b) return b; return x; } double clampd(double x, double a, double b) { if (x < a) return a; if (x > b) return b; return x; } int clampi(int x, int a, int b) { if (x < a) return a; if (x > b) return b; return x; } i16 clamp_i16(i16 x, i16 a, i16 b) { if (x < a) return a; if (x > b) return b; return x; } u16 clamp_u16(u16 x, u16 a, u16 b) { if (x < a) return a; if (x > b) return b; return x; } i32 clamp_i32(i32 x, i32 a, i32 b) { if (x < a) return a; if (x > b) return b; return x; } u32 clamp_u32(u32 x, u32 a, u32 b) { if (x < a) return a; if (x > b) return b; return x; } u8 ndigits_u64(u64 x) { u8 ndigits = 1; while (x > 9) { x /= 10; ++ndigits; } return ndigits; } // remap x from the interval [from_a, from_b] to the interval [to_a, to_b], NOT clamping if x is outside the "from" interval. float remapf(float x, float from_a, float from_b, float to_a, float to_b) { float pos = (x - from_a) / (from_b - from_a); return lerpf(pos, to_a, to_b); } float minf(float a, float b) { return a < b ? a : b; } float maxf(float a, float b) { return a > b ? a : b; } double maxd(double a, double b) { return a > b ? a : b; } double mind(double a, double b) { return a < b ? a : b; } u32 min_u32(u32 a, u32 b) { return a < b ? a : b; } u32 max_u32(u32 a, u32 b) { return a > b ? a : b; } // set *a to the minimum of *a and *b, and *b to the maximum void sort2_u32(u32 *a, u32 *b) { u32 x = *a, y = *b; if (x > y) { *a = y; *b = x; } } i32 min_i32(i32 a, i32 b) { return a < b ? a : b; } i32 max_i32(i32 a, i32 b) { return a > b ? a : b; } u64 min_u64(u64 a, u64 b) { return a < b ? a : b; } u64 max_u64(u64 a, u64 b) { return a > b ? a : b; } i64 min_i64(i64 a, i64 b) { return a < b ? a : b; } i64 max_i64(i64 a, i64 b) { return a > b ? a : b; } i64 mod_i64(i64 a, i64 b) { i64 ret = a % b; if (ret < 0) ret += b; return ret; } i32 mod_i32(i32 a, i32 b) { i32 ret = a % b; if (ret < 0) ret += b; return ret; } i64 abs_i64(i64 x) { return x < 0 ? -x : +x; } i64 sgn_i64(i64 x) { if (x < 0) return -1; if (x > 0) return +1; return 0; } float sgnf(float x) { if (x < 0) return -1; if (x > 0) return +1; return 0; } float smoothstepf(float x) { if (x <= 0) return 0; if (x >= 1) return 1; return x * x * (3 - 2 * x); } float randf(void) { return (float)rand() / (float)((ulong)RAND_MAX + 1); } u32 rand_u32(void) { return ((u32)rand() & 0xfff) | ((u32)rand() & 0xfff) << 12 | ((u32)rand() & 0xff) << 24; } float rand_uniform(float from, float to) { return lerpf(randf(), from, to); } float sigmoidf(float x) { return 1.0f / (1.0f + expf(-x)); } // returns ⌈x/y⌉ (x/y rounded up) i32 ceildivi32(i32 x, i32 y) { if (y < 0) { // negating both operands doesn't change the answer x = -x; y = -y; } if (x < 0) { // truncation is the same as ceiling for negative numbers return x / y; } else { return (x + (y-1)) / y; } } vec2 Vec2(float x, float y) { vec2 v; v.x = x; v.y = y; return v; } vec2 vec2_add(vec2 a, vec2 b) { return Vec2(a.x + b.x, a.y + b.y); } vec2 vec2_add_const(vec2 a, float c) { return Vec2(a.x + c, a.y + c); } vec2 vec2_sub(vec2 a, vec2 b) { return Vec2(a.x - b.x, a.y - b.y); } vec2 vec2_scale(vec2 v, float s) { return Vec2(v.x * s, v.y * s); } vec2 vec2_mul(vec2 a, vec2 b) { return Vec2(a.x * b.x, a.y * b.y); } vec2 vec2_clamp(vec2 x, vec2 a, vec2 b) { return Vec2(clampf(x.x, a.x, b.x), clampf(x.y, a.y, b.y)); } float vec2_dot(vec2 a, vec2 b) { return a.x * b.x + a.y * b.y; } float vec2_len(vec2 v) { return sqrtf(vec2_dot(v, v)); } vec2 vec2_lerp(float x, vec2 a, vec2 b) { return Vec2(lerpf(x, a.x, b.x), lerpf(x, a.y, b.y)); } // rotate v theta radians counterclockwise vec2 vec2_rotate(vec2 v, float theta) { float c = cosf(theta), s = sinf(theta); return Vec2( c * v.x - s * v.y, s * v.x + c * v.y ); } vec2 vec2_normalize(vec2 v) { float len = vec2_len(v); float mul = len == 0.0f ? 1.0f : 1.0f/len; return vec2_scale(v, mul); } float vec2_dist(vec2 a, vec2 b) { return vec2_len(vec2_sub(a, b)); } float vec2_dist_squared(vec2 a, vec2 b) { vec2 diff = vec2_sub(a, b); return vec2_dot(diff, diff); } void vec2_print(vec2 v) { printf("(%f, %f)\n", v.x, v.y); } vec2 vec2_rand_unit(void) { float theta = rand_uniform(0, TAUf); return Vec2(cosf(theta), sinf(theta)); } vec2 vec2_polar(float r, float theta) { return Vec2(r * cosf(theta), r * sinf(theta)); } vec4 Vec4(float x, float y, float z, float w) { vec4 v; v.x = x; v.y = y; v.z = z; v.w = w; return v; } vec2d Vec2d(double x, double y) { return (vec2d) { .x = x, .y = y }; } void rgba_u32_to_floats(u32 rgba, float floats[4]) { floats[0] = (float)((rgba >> 24) & 0xFF) / 255.f; floats[1] = (float)((rgba >> 16) & 0xFF) / 255.f; floats[2] = (float)((rgba >> 8) & 0xFF) / 255.f; floats[3] = (float)((rgba >> 0) & 0xFF) / 255.f; } vec4 rgba_u32_to_vec4(u32 rgba) { float c[4]; rgba_u32_to_floats(rgba, c); return Vec4(c[0], c[1], c[2], c[3]); } u32 rgba_vec4_to_u32(vec4 color) { return (u32)(color.x * 255) << 24 | (u32)(color.y * 255) << 16 | (u32)(color.z * 255) << 8 | (u32)(color.w * 255); } // returns average of red green and blue components of color float rgba_brightness(u32 color) { u8 r = (u8)(color >> 24), g = (u8)(color >> 16), b = (u8)(color >> 8); return ((float)r+(float)g+(float)b) * (1.0f / 3); } bool rect_contains_point_v2(vec2 pos, vec2 size, vec2 point) { float x1 = pos.x, y1 = pos.y, x2 = pos.x + size.x, y2 = pos.y + size.y, x = point.x, y = point.y; return x >= x1 && x < x2 && y >= y1 && y < y2; } bool centered_rect_contains_point(vec2 center, vec2 size, vec2 point) { return rect_contains_point_v2(vec2_sub(center, vec2_scale(size, 0.5f)), size, point); } Rect rect(vec2 pos, vec2 size) { Rect r; r.pos = pos; r.size = size; return r; } Rect rect_endpoints(vec2 e1, vec2 e2) { Rect r; r.pos = e1; r.size = vec2_sub(e2, e1); return r; } Rect rect4(float x1, float y1, float x2, float y2) { assert(x2 >= x1); assert(y2 >= y1); return rect(Vec2(x1,y1), Vec2(x2-x1, y2-y1)); } Rect rect_xywh(float x, float y, float w, float h) { assert(w >= 0); assert(h >= 0); return rect(Vec2(x, y), Vec2(w, h)); } Rect rect_centered(vec2 center, vec2 size) { Rect r; r.pos = vec2_sub(center, vec2_scale(size, 0.5f)); r.size = size; return r; } vec2 rect_center(Rect r) { return vec2_add(r.pos, vec2_scale(r.size, 0.5f)); } bool rect_contains_point(Rect r, vec2 point) { return rect_contains_point_v2(r.pos, r.size, point); } Rect rect_translate(Rect r, vec2 by) { return rect(vec2_add(r.pos, by), r.size); } float rect_x1(Rect r) { return r.pos.x; } float rect_y1(Rect r) { return r.pos.y; } float rect_x2(Rect r) { return r.pos.x + r.size.x; } float rect_y2(Rect r) { return r.pos.y + r.size.y; } float rect_xmid(Rect r) { return r.pos.x + r.size.x * 0.5f; } float rect_ymid(Rect r) { return r.pos.y + r.size.y * 0.5f; } void rect_coords(Rect r, float *x1, float *y1, float *x2, float *y2) { *x1 = r.pos.x; *y1 = r.pos.y; *x2 = r.pos.x + r.size.x; *y2 = r.pos.y + r.size.y; } void rect_print(Rect r) { printf("Position: (%f, %f), Size: (%f, %f)\n", r.pos.x, r.pos.y, r.size.x, r.size.y); } float rects_intersect(Rect r1, Rect r2) { if (r1.pos.x >= r2.pos.x + r2.size.x) return false; // r1 is to the right of r2 if (r2.pos.x >= r1.pos.x + r1.size.x) return false; // r2 is to the right of r1 if (r1.pos.y >= r2.pos.y + r2.size.y) return false; // r1 is above r2 if (r2.pos.y >= r1.pos.y + r1.size.y) return false; // r2 is above r1 return true; } // returns whether or not there is any of the clipped rectangle left bool rect_clip_to_rect(Rect *clipped, Rect clipper) { vec2 start_pos = clipped->pos; clipped->pos.x = maxf(clipped->pos.x, clipper.pos.x); clipped->pos.y = maxf(clipped->pos.y, clipper.pos.y); clipped->size = vec2_add(clipped->size, vec2_sub(start_pos, clipped->pos)); clipped->size.x = clampf(clipped->size.x, 0, clipper.pos.x + clipper.size.x - clipped->pos.x); clipped->size.y = clampf(clipped->size.y, 0, clipper.pos.y + clipper.size.y - clipped->pos.y); return clipped->size.x > 0 && clipped->size.y > 0; } // removes `amount` from all sides of r Rect rect_shrink(Rect r, float amount) { r.pos.x += amount; r.pos.y += amount; r.size.x -= 2 * amount; r.size.y -= 2 * amount; r.size.x = maxf(r.size.x, 0); r.size.y = maxf(r.size.y, 0); return r; } // adds `amount` to all sides of r Rect rect_grow(Rect r, float amount) { r.pos.x -= amount; r.pos.y -= amount; r.size.x += 2 * amount; r.size.y += 2 * amount; return r; } vec4 color_rgba_to_hsva(vec4 rgba) { float R = rgba.x, G = rgba.y, B = rgba.z, A = rgba.w; float M = maxf(R, maxf(G, B)); float m = minf(R, minf(G, B)); float C = M - m; float H = 0; if (C == 0) H = 0; else if (M == R) H = fmodf((G - B) / C, 6); else if (M == G) H = (B - R) / C + 2; else if (M == B) H = (R - G) / C + 4; H *= 60; float V = M; float S = V == 0 ? 0 : C / V; return Vec4(H, S, V, A); } vec4 color_hsva_to_rgba(vec4 hsva) { float H = hsva.x, S = hsva.y, V = hsva.z, A = hsva.w; H /= 60; float C = S * V; float X = C * (1 - fabsf(fmodf(H, 2) - 1)); float R, G, B; if (H <= 1) R=C, G=X, B=0; else if (H <= 2) R=X, G=C, B=0; else if (H <= 3) R=0, G=C, B=X; else if (H <= 4) R=0, G=X, B=C; else if (H <= 5) R=X, G=0, B=C; else R=C, G=0, B=X; float m = V-C; R += m; G += m; B += m; return Vec4(R, G, B, A); } u32 color_interpolate(float x, u32 color1, u32 color2) { x = x * x * (3 - 2*x); // hermite interpolation vec4 c1 = rgba_u32_to_vec4(color1), c2 = rgba_u32_to_vec4(color2); // to make it interpolate more nicely, convert to hsv, interpolate in that space, then convert back c1 = color_rgba_to_hsva(c1); c2 = color_rgba_to_hsva(c2); // v_1/2 named differently to avoid shadowing float h1 = c1.x, s1 = c1.y, v_1 = c1.z, a1 = c1.w; float h2 = c2.x, s2 = c2.y, v_2 = c2.z, a2 = c2.w; float s_out = lerpf(x, s1, s2); float v_out = lerpf(x, v_1, v_2); float a_out = lerpf(x, a1, a2); float h_out; // because hue is on a circle, we need to make sure we take the shorter route around the circle if (fabsf(h1 - h2) < 180) { h_out = lerpf(x, h1, h2); } else if (h1 > h2) { h_out = lerpf(x, h1, h2 + 360); } else { h_out = lerpf(x, h1 + 360, h2); } h_out = fmodf(h_out, 360); vec4 c_out = Vec4(h_out, s_out, v_out, a_out); c_out = color_hsva_to_rgba(c_out); return rgba_vec4_to_u32(c_out); } int timespec_cmp(struct timespec a, struct timespec b) { if (a.tv_sec > b.tv_sec) return 1; if (a.tv_sec < b.tv_sec) return -1; if (a.tv_nsec > b.tv_nsec) return 1; if (a.tv_nsec < b.tv_nsec) return -1; return 0; } bool timespec_eq(struct timespec a, struct timespec b) { return timespec_cmp(a, b) == 0; } struct timespec timespec_max(struct timespec a, struct timespec b) { return timespec_cmp(a, b) < 0 ? b : a; } double timespec_to_seconds(struct timespec ts) { return (double)ts.tv_sec + (double)ts.tv_nsec * 1e-9; } String32 str32(char32_t *str, size_t len) { String32 s = {str, len}; return s; } String32 str32_substr(String32 s, size_t from, size_t len) { return str32(s.str + from, len); } // frees string and sets it to "" void str32_free(String32 *s) { free(s->str); s->str = NULL; s->len = 0; } // the string returned should be str32_free'd. // this will return an empty string if the allocation failed or the string is invalid UTF-8 String32 str32_from_utf8(const char *utf8) { String32 string = {NULL, 0}; size_t len = strlen(utf8); if (len) { // the wide string uses at most as many "characters" (elements?) as the UTF-8 string char32_t *widestr = calloc(len, sizeof *widestr); if (widestr) { char32_t *wide_p = widestr; const char *utf8_p = utf8; const char *utf8_end = utf8_p + len; while (utf8_p < utf8_end) { char32_t c = 0; size_t n = unicode_utf8_to_utf32(&c, utf8_p, (size_t)(utf8_end - utf8_p)); if (n == 0 // null character. this shouldn't happen. || n >= (size_t)(-2) // invalid UTF-8 ) { free(widestr); widestr = wide_p = NULL; break; } else { // n bytes consumed *wide_p++ = c; utf8_p += n; } } string.str = widestr; string.len = (size_t)(wide_p - widestr); } } return string; } // returns a null-terminated UTF-8 string // the string returned should be free'd // this will return NULL on failure char *str32_to_utf8_cstr(String32 s) { char *utf8 = calloc(4 * s.len + 1, 1); // each codepoint takes up at most 4 bytes in UTF-8, + we need a terminating null byte if (utf8) { char *p = utf8; for (size_t i = 0; i < s.len; ++i) { size_t bytes = unicode_utf32_to_utf8(p, s.str[i]); if (bytes == (size_t)-1) { // invalid UTF-32 code point free(utf8); return NULL; } else { p += bytes; } } *p = '\0'; } return utf8; } // compare s to the ASCII string `ascii` int str32_cmp_ascii(String32 s, const char *ascii) { for (size_t i = 0; i < s.len; ++i) { assert((char32_t)ascii[i] < 128); if ((char32_t)ascii[i] == '\0') return -1; // ascii is a prefix of s if (s.str[i] > (char32_t)ascii[i]) return +1; if (s.str[i] < (char32_t)ascii[i]) return -1; } if (ascii[s.len]) { // s is a prefix of ascii return +1; } return 0; } // check if s starts with the ASCII string `ascii` bool str32_has_ascii_prefix(String32 s, const char *ascii) { for (size_t i = 0; i < s.len; ++i) { assert((char32_t)ascii[i] < 128); if ((char32_t)ascii[i] == '\0') return true; // ascii is a prefix of s if (s.str[i] > (char32_t)ascii[i]) return false; if (s.str[i] < (char32_t)ascii[i]) return false; } if (ascii[s.len]) { // s is a prefix of ascii return false; } // s is the same as ascii return true; } // returns the index of the given character in the string, or the length of the string if it's not found. size_t str32chr(String32 s, char32_t c) { for (size_t i = 0; i < s.len; ++i) { if (s.str[i] == c) return i; } return s.len; } // returns number of instances of c in s size_t str32_count_char(String32 s, char32_t c) { size_t total = 0; for (size_t i = 0; i < s.len; ++i) { total += s.str[i] == c; } return total; } // returns number of characters deleted from s size_t str32_remove_all_instances_of_char(String32 *s, char32_t c) { char32_t *str = s->str; size_t ndeleted = 0; size_t len = s->len; size_t out = 0; for (size_t in = 0; in < len; ++in) { if (str[in] == c) { ++ndeleted; } else { str[out++] = str[in]; } } s->len = out; return ndeleted; } // returns the length of the longest prefix of `s` containing only // ASCII characters in the C-string `charset`. size_t str32_ascii_spn(String32 s, const char *charset) { for (u32 i = 0; i < s.len; ++i) { if (s.str[i] >= 128) return i; // non-ASCII character in s, so that can't be in charset. bool found = false; for (const char *p = charset; *p; ++p) { assert((char32_t)*p < 128); if ((char32_t)*p == s.str[i]) { found = true; break; } } if (!found) return i; } return s.len; }