19static int wcwidth(
wchar_t wc) {
24 if (wc >= 0x1100 && wc <= 0x115F)
26 if (wc >= 0x2E80 && wc <= 0x9FFF)
28 if (wc >= 0xAC00 && wc <= 0xD7AF)
30 if (wc >= 0xF900 && wc <= 0xFAFF)
32 if (wc >= 0xFE10 && wc <= 0xFE19)
34 if (wc >= 0xFE30 && wc <= 0xFE6F)
36 if (wc >= 0xFF00 && wc <= 0xFF60)
38 if (wc >= 0xFFE0 && wc <= 0xFFE6)
56 .requires_utf8 =
false,
57 .is_validated =
true},
59 {.name =
"blocks", .chars =
PALETTE_CHARS_BLOCKS, .length = 11, .requires_utf8 =
true, .is_validated =
true},
61 {.name =
"digital", .chars =
PALETTE_CHARS_DIGITAL, .length = 10, .requires_utf8 =
true, .is_validated =
true},
63 {.name =
"minimal", .chars =
PALETTE_CHARS_MINIMAL, .length = 8, .requires_utf8 =
false, .is_validated =
true},
65 {.name =
"cool", .chars =
PALETTE_CHARS_COOL, .length = 11, .requires_utf8 =
true, .is_validated =
true},
74 return &builtin_palettes[type];
80 if (!chars || len == 0) {
84 for (
size_t i = 0; i < len; i++) {
86 if ((
unsigned char)chars[i] >= 128) {
95 if (!chars || len == 0) {
106 char *current_locale = setlocale(LC_CTYPE, NULL);
107 char *old_locale = NULL;
108 if (current_locale) {
112 if (!setlocale(LC_CTYPE,
"")) {
113 log_warn(
"Failed to set locale for UTF-8 validation, continuing anyway");
116 const char *p = chars;
117 size_t char_count = 0;
118 size_t byte_count = 0;
120 while (byte_count < len && *p) {
122 int bytes = mbtowc(&wc, p, len - byte_count);
128 (void)setlocale(LC_CTYPE, old_locale);
135 int width = wcwidth(wc);
136 if (width < 0 || width > 2) {
138 "Palette validation failed: character at position %zu has invalid width %d (must be 1 or 2)",
142 (void)setlocale(LC_CTYPE, old_locale);
149 if (wc < 32 && wc !=
'\t') {
153 (void)setlocale(LC_CTYPE, old_locale);
166 (void)setlocale(LC_CTYPE, old_locale);
170 log_debug(
"Palette validation successful: %zu characters validated", char_count);
198 char *current_locale = setlocale(LC_CTYPE, NULL);
199 char *old_locale = NULL;
200 if (current_locale) {
203 if (setlocale(LC_CTYPE,
"")) {
205 const char *codeset = nl_langinfo(CODESET);
215 (void)setlocale(LC_CTYPE, old_locale);
226 const char *utf8_terminals[] = {
227 "xterm-256color",
"screen-256color",
"tmux-256color",
"alacritty",
"kitty",
"iterm",
228 "iterm2",
"gnome-terminal",
"konsole",
"terminology", NULL};
230 for (
int i = 0; utf8_terminals[i]; i++) {
231 if (strstr(term, utf8_terminals[i])) {
238 log_debug(
"UTF-8 support detection: %s (term=%s, encoding=%s)", caps->
utf8_support ?
"supported" :
"not supported",
255 log_warn(
"Invalid palette type %d, falling back to standard", requested);
261 log_warn(
"Client doesn't support UTF-8, falling back from %s", palette->
name);
281 log_info(
"Client palette config: type=%d, custom_chars=%s", type, custom_chars ? custom_chars :
"(none)");
285 if (!custom_chars || strlen(custom_chars) == 0) {
307 if (!palette_chars || palette_len == 0 || !luminance_mapping) {
313 for (
int i = 0; i < 256; i++) {
315 size_t palette_index = (i * (palette_len - 1) + 127) / 255;
316 if (palette_index >= palette_len) {
317 palette_index = palette_len - 1;
319 luminance_mapping[i] = palette_chars[palette_index];
327 size_t *client_palette_len,
char client_luminance_palette[256]) {
329 const char *chars_to_use = NULL;
330 size_t len_to_use = 0;
338 len_to_use = strlen(custom_chars);
339 if (len_to_use == 0) {
343 if (len_to_use >= 256) {
354 chars_to_use = custom_chars;
362 chars_to_use = palette->
chars;
363 len_to_use = strlen(palette->
chars);
366 log_debug(
"Using built-in palette: %s, chars='%s', char_count=%zu, byte_len=%zu", palette->
name, chars_to_use,
367 palette->
length, len_to_use);
371 SAFE_MEMCPY(client_palette_chars, len_to_use, chars_to_use, len_to_use);
372 client_palette_chars[len_to_use] =
'\0';
373 *client_palette_len = len_to_use;
381 log_info(
"Initialized client palette: type=%d, %zu chars, first_char='%c', last_char='%c'", palette_type, len_to_use,
382 chars_to_use[0], chars_to_use[len_to_use - 1]);
391 if (!palette_string || *palette_string ==
'\0') {
399 size_t char_count = 0;
400 const char *p = palette_string;
401 size_t total_bytes = strlen(palette_string);
404 size_t bytes_processed = 0;
405 while (bytes_processed < total_bytes) {
407 unsigned char c = (
unsigned char)p[0];
409 if ((c & 0x80) == 0) {
411 }
else if ((c & 0xE0) == 0xC0) {
413 }
else if ((c & 0xF0) == 0xE0) {
415 }
else if ((c & 0xF8) == 0xF0) {
420 if (bytes_processed + bytes > total_bytes) {
425 bytes_processed += bytes;
430 if (char_count == 0) {
458 char old_locale[256] = {0};
459 char *current_locale = setlocale(LC_CTYPE, NULL);
460 if (current_locale) {
461 SAFE_STRNCPY(old_locale, current_locale,
sizeof(old_locale));
463 (void)setlocale(LC_CTYPE,
"");
465 while (char_idx < char_count && bytes_processed < total_bytes) {
469 unsigned char c = (
unsigned char)*p;
472 if ((c & 0x80) == 0) {
474 }
else if ((c & 0xE0) == 0xC0) {
476 }
else if ((c & 0xF0) == 0xE0) {
478 }
else if ((c & 0xF8) == 0xF0) {
483 if (bytes_processed + bytes > total_bytes) {
496 if (mbtowc(&wc, p, bytes) > 0) {
497 int width = wcwidth(wc);
498 char_info->
display_width = (width > 0 && width <= 2) ? width : 1;
504 bytes_processed += bytes;
509 if (old_locale[0] !=
'\0') {
510 (void)setlocale(LC_CTYPE, old_locale);
527 if (!palette || index >= palette->
char_count) {
530 return &palette->
chars[index];
543 if (!palette || !utf8_char || char_bytes == 0 || char_bytes > 4) {
547 for (
size_t i = 0; i < palette->
char_count; i++) {
549 if (char_info->
byte_len == char_bytes && memcmp(char_info->
bytes, utf8_char, char_bytes) == 0) {
559 if (!palette || !utf8_char || char_bytes == 0 || char_bytes > 4) {
563 for (
size_t i = 0; i < palette->
char_count; i++) {
565 if (char_info->
byte_len == char_bytes && memcmp(char_info->
bytes, utf8_char, char_bytes) == 0) {
576 size_t *indices,
size_t max_indices) {
577 if (!palette || !utf8_char || char_bytes == 0 || char_bytes > 4 || !indices || max_indices == 0) {
581 size_t found_count = 0;
583 for (
size_t i = 0; i < palette->
char_count && found_count < max_indices; i++) {
585 if (char_info->
byte_len == char_bytes && memcmp(char_info->
bytes, utf8_char, char_bytes) == 0) {
586 indices[found_count++] = i;
#define SAFE_STRNCPY(dst, src, size)
#define SAFE_MEMSET(dest, dest_size, ch, count)
#define SAFE_MALLOC(size, cast)
#define SAFE_GETENV(name)
#define SAFE_MEMCPY(dest, dest_size, src, count)
#define SAFE_STRDUP(dst, src)
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
#define log_warn(...)
Log a WARN message.
#define log_info(...)
Log an INFO message.
#define log_debug(...)
Log a DEBUG message.
uint8_t byte_len
Number of bytes in UTF-8 encoding (1-4)
void utf8_palette_destroy(utf8_palette_t *palette)
Destroy a UTF-8 palette and free resources.
char bytes[4]
UTF-8 byte sequence (max 4 bytes per character)
size_t utf8_palette_find_all_char_indices(const utf8_palette_t *palette, const char *utf8_char, size_t char_bytes, size_t *indices, size_t max_indices)
Find all indices of UTF-8 character in palette.
int initialize_client_palette(palette_type_t palette_type, const char *custom_chars, char client_palette_chars[256], size_t *client_palette_len, char client_luminance_palette[256])
Initialize client palette with full configuration.
size_t char_count
Number of characters (not bytes!)
bool palette_requires_utf8_encoding(const char *chars, size_t len)
Check if palette requires UTF-8 encoding.
size_t utf8_palette_get_char_count(const utf8_palette_t *palette)
Get number of characters in UTF-8 palette.
#define PALETTE_CHARS_MINIMAL
Minimal ASCII palette character string.
const char * name
Human-readable palette name.
bool detect_client_utf8_support(utf8_capabilities_t *caps)
Detect client UTF-8 support capabilities.
palette_type_t select_compatible_palette(palette_type_t requested, bool client_utf8)
Select compatible palette based on client capabilities.
bool utf8_support
True if terminal supports UTF-8 (detected automatically)
const size_t DEFAULT_ASCII_PALETTE_LEN
Length of default ASCII palette.
char terminal_type[32]
$TERM environment variable value (for detection)
utf8_char_info_t * chars
Array of UTF-8 character information.
const char DEFAULT_ASCII_PALETTE[]
Default ASCII palette for legacy functions.
uint8_t display_width
Terminal display width in character cells (1-2)
const palette_def_t * get_builtin_palette(palette_type_t type)
Get built-in palette definition.
#define PALETTE_CHARS_DIGITAL
Digital/glitch palette character string.
palette_type_t
Built-in palette type enumeration.
#define PALETTE_CHARS_STANDARD
Standard ASCII palette character string.
int apply_palette_config(palette_type_t type, const char *custom_chars)
Apply palette configuration (set global palette)
bool requires_utf8
True if palette contains UTF-8 multi-byte characters.
int build_client_luminance_palette(const char *palette_chars, size_t palette_len, char luminance_mapping[256])
Build luminance mapping table from palette characters.
size_t total_bytes
Total byte length of palette string.
size_t utf8_palette_find_char_index(const utf8_palette_t *palette, const char *utf8_char, size_t char_bytes)
Find index of UTF-8 character in palette.
bool validate_palette_chars(const char *chars, size_t len)
Validate palette character sequence.
const char * chars
Character sequence (ordered from dark to light)
const utf8_char_info_t * utf8_palette_get_char(const utf8_palette_t *palette, size_t index)
Get UTF-8 character information at index.
char * raw_string
Original palette string (for reference/debugging)
char locale_encoding[16]
Current locale encoding (e.g., "UTF-8")
size_t length
Number of characters in sequence.
#define PALETTE_CHARS_BLOCKS
Unicode block palette character string.
bool utf8_palette_contains_char(const utf8_palette_t *palette, const char *utf8_char, size_t char_bytes)
Check if UTF-8 palette contains a specific character.
#define PALETTE_CHARS_COOL
Cool ascending blocks palette character string.
utf8_palette_t * utf8_palette_create(const char *palette_string)
Create a UTF-8 palette from string.
@ PALETTE_COUNT
Number of palette types (not a valid palette)
@ PALETTE_BLOCKS
Unicode block characters: " ░░▒▒▓▓██".
@ PALETTE_CUSTOM
User-defined via –palette-chars.
@ PALETTE_COOL
Ascending blocks: " ▁▂▃▄▅▆▇█".
@ PALETTE_STANDARD
Standard ASCII palette: " ...',;:clodxkO0KXNWM".
@ PALETTE_DIGITAL
Digital/glitch aesthetic: " -=≡≣▰▱◼".
@ PALETTE_MINIMAL
Simple ASCII: " .-+*#".
ASCII Palette Management for Video-to-ASCII Conversion.
Palette definition structure.
UTF-8 capability detection structure.
UTF-8 character information structure.
🖥️ Cross-platform terminal interface for ascii-chat