ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
palette.c File Reference

🎨 Terminal color palette management with Unicode character width detection More...

Go to the source code of this file.

Functions

const palette_def_t * get_builtin_palette (palette_type_t type)
 
bool palette_requires_utf8_encoding (const char *chars, size_t len)
 
bool validate_palette_chars (const char *chars, size_t len)
 
bool detect_client_utf8_support (utf8_capabilities_t *caps)
 
palette_type_t select_compatible_palette (palette_type_t requested, bool client_utf8)
 
int apply_palette_config (palette_type_t type, const char *custom_chars)
 
int build_client_luminance_palette (const char *palette_chars, size_t palette_len, char luminance_mapping[256])
 
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])
 
utf8_palette_t * utf8_palette_create (const char *palette_string)
 
void utf8_palette_destroy (utf8_palette_t *palette)
 
const utf8_char_info_t * utf8_palette_get_char (const utf8_palette_t *palette, size_t index)
 
size_t utf8_palette_get_char_count (const utf8_palette_t *palette)
 
bool utf8_palette_contains_char (const utf8_palette_t *palette, const char *utf8_char, size_t char_bytes)
 
size_t utf8_palette_find_char_index (const utf8_palette_t *palette, const char *utf8_char, size_t char_bytes)
 
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)
 

Variables

const char DEFAULT_ASCII_PALETTE [] = PALETTE_CHARS_STANDARD
 
const size_t DEFAULT_ASCII_PALETTE_LEN = 23
 

Detailed Description

🎨 Terminal color palette management with Unicode character width detection

Definition in file palette.c.

Function Documentation

◆ apply_palette_config()

int apply_palette_config ( palette_type_t  type,
const char *  custom_chars 
)

Definition at line 250 of file palette.c.

250 {
251 // This function is now used only for client-side initialization
252 // Server uses initialize_client_palette() for per-client palettes
253
254 log_debug("Client palette config: type=%d, custom_chars=%s", type, custom_chars ? custom_chars : "(none)");
255
256 // Just validate the palette - no global state changes
257 if (type == PALETTE_CUSTOM) {
258 if (!custom_chars || strlen(custom_chars) == 0) {
259 SET_ERRNO(ERROR_INVALID_PARAM, "Custom palette requested but no characters provided");
260 return -1;
261 }
262
263 if (!validate_palette_chars(custom_chars, strlen(custom_chars))) {
264 SET_ERRNO(ERROR_INVALID_PARAM, "Custom palette validation failed");
265 return -1;
266 }
267 } else {
268 const palette_def_t *palette = get_builtin_palette(type);
269 if (!palette) {
270 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid palette type: %d", type);
271 return -1;
272 }
273 }
274
275 return 0; // Validation successful, no global state to update
276}
const palette_def_t * get_builtin_palette(palette_type_t type)
Definition palette.c:47
bool validate_palette_chars(const char *chars, size_t len)
Definition palette.c:71

References get_builtin_palette(), and validate_palette_chars().

Referenced by main().

◆ build_client_luminance_palette()

int build_client_luminance_palette ( const char *  palette_chars,
size_t  palette_len,
char  luminance_mapping[256] 
)

Definition at line 279 of file palette.c.

279 {
280 if (!palette_chars || palette_len == 0 || !luminance_mapping) {
281 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters for client luminance palette");
282 return -1;
283 }
284
285 // Map 256 luminance values to palette indices for this specific client
286 for (int i = 0; i < 256; i++) {
287 // Linear mapping with proper rounding
288 size_t palette_index = (i * (palette_len - 1) + 127) / 255;
289 if (palette_index >= palette_len) {
290 palette_index = palette_len - 1;
291 }
292 luminance_mapping[i] = palette_chars[palette_index];
293 }
294
295 return 0;
296}

Referenced by initialize_client_palette().

◆ detect_client_utf8_support()

bool detect_client_utf8_support ( utf8_capabilities_t *  caps)

Definition at line 148 of file palette.c.

148 {
149 if (!caps) {
150 return false;
151 }
152
153 // Initialize structure
154 SAFE_MEMSET(caps, sizeof(utf8_capabilities_t), 0, sizeof(utf8_capabilities_t));
155
156 // Check environment variables
157 const char *term = SAFE_GETENV("TERM");
158
159 // Store terminal type
160 if (term) {
161 SAFE_STRNCPY(caps->terminal_type, term, sizeof(caps->terminal_type));
162 }
163
164 // Use platform-specific UTF-8 detection from platform abstraction layer
165 caps->utf8_support = terminal_supports_utf8();
166
167 if (caps->utf8_support) {
168 SAFE_STRNCPY(caps->locale_encoding, "UTF-8", sizeof(caps->locale_encoding));
169 } else {
170 // Try to detect encoding via locale
171 char *current_locale = setlocale(LC_CTYPE, NULL);
172 char *old_locale = NULL;
173 if (current_locale) {
174 SAFE_STRDUP(old_locale, current_locale);
175 }
176 if (setlocale(LC_CTYPE, "")) {
177#ifndef _WIN32
178 const char *codeset = nl_langinfo(CODESET);
179 if (codeset) {
180 SAFE_STRNCPY(caps->locale_encoding, codeset, sizeof(caps->locale_encoding));
181 }
182#else
183 // Windows may not have locale set but still support UTF-8
184 SAFE_STRNCPY(caps->locale_encoding, "CP1252", sizeof(caps->locale_encoding));
185#endif
186 // Restore old locale
187 if (old_locale) {
188 (void)setlocale(LC_CTYPE, old_locale);
189 }
190 }
191 // Always free old_locale regardless of setlocale success
192 if (old_locale) {
193 SAFE_FREE(old_locale);
194 }
195 }
196
197 // Check for known UTF-8 supporting terminals
198 if (term) {
199 const char *utf8_terminals[] = {
200 "xterm-256color", "screen-256color", "tmux-256color", "alacritty", "kitty", "iterm",
201 "iterm2", "gnome-terminal", "konsole", "terminology", NULL};
202
203 for (int i = 0; utf8_terminals[i]; i++) {
204 if (strstr(term, utf8_terminals[i])) {
205 caps->utf8_support = true;
206 break;
207 }
208 }
209 }
210
211 log_debug("UTF-8 support detection: %s (term=%s, encoding=%s)", caps->utf8_support ? "supported" : "not supported",
212 caps->terminal_type[0] ? caps->terminal_type : "unknown",
213 caps->locale_encoding[0] ? caps->locale_encoding : "unknown");
214
215 return caps->utf8_support;
216}
bool terminal_supports_utf8(void)

References terminal_supports_utf8().

◆ get_builtin_palette()

const palette_def_t * get_builtin_palette ( palette_type_t  type)

Definition at line 47 of file palette.c.

47 {
48 if (type >= PALETTE_COUNT || type == PALETTE_CUSTOM || type == PALETTE_UNSET) {
49 return NULL;
50 }
51 return &builtin_palettes[type];
52}

Referenced by apply_palette_config(), initialize_client_palette(), and select_compatible_palette().

◆ initialize_client_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] 
)

Definition at line 299 of file palette.c.

300 {
301 const palette_def_t *palette = NULL;
302 const char *chars_to_use = NULL;
303 size_t len_to_use = 0;
304
305 if (palette_type == PALETTE_CUSTOM) {
306 if (!custom_chars) {
307 SET_ERRNO(ERROR_INVALID_PARAM, "Client requested custom palette but custom_chars is NULL");
308 return -1;
309 }
310
311 len_to_use = strlen(custom_chars);
312 if (len_to_use == 0) {
313 SET_ERRNO(ERROR_INVALID_PARAM, "Client requested custom palette but custom_chars is empty");
314 return -1;
315 }
316 if (len_to_use >= 256) {
317 SET_ERRNO(ERROR_INVALID_PARAM, "Client custom palette too long: %zu chars", len_to_use);
318 return -1;
319 }
320
321 // Validate custom palette
322 if (!validate_palette_chars(custom_chars, len_to_use)) {
323 SET_ERRNO(ERROR_INVALID_PARAM, "Client custom palette validation failed");
324 return -1;
325 }
326
327 chars_to_use = custom_chars;
328 } else {
329 palette = get_builtin_palette(palette_type);
330 if (!palette) {
331 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid client palette type: %d", palette_type);
332 return -1;
333 }
334
335 chars_to_use = palette->chars;
336 len_to_use = strlen(palette->chars); // Use actual byte count for UTF-8, not character count
337
338 // Skip validation for built-in palettes since they're already validated
339 log_debug("Using built-in palette: %s, chars='%s', char_count=%zu, byte_len=%zu", palette->name, chars_to_use,
340 palette->length, len_to_use);
341 }
342
343 // Copy palette to client cache
344 SAFE_MEMCPY(client_palette_chars, len_to_use, chars_to_use, len_to_use);
345 client_palette_chars[len_to_use] = '\0';
346 *client_palette_len = len_to_use;
347
348 // Build client-specific luminance mapping
349 if (build_client_luminance_palette(chars_to_use, len_to_use, client_luminance_palette) != 0) {
350 SET_ERRNO(ERROR_INVALID_STATE, "Failed to build client luminance palette");
351 return -1;
352 }
353
354 log_debug("Initialized client palette: type=%d, %zu chars, first_char='%c', last_char='%c'", palette_type, len_to_use,
355 chars_to_use[0], chars_to_use[len_to_use - 1]);
356
357 return 0;
358}
int build_client_luminance_palette(const char *palette_chars, size_t palette_len, char luminance_mapping[256])
Definition palette.c:279

References build_client_luminance_palette(), get_builtin_palette(), and validate_palette_chars().

Referenced by handle_client_capabilities_packet(), and session_display_create().

◆ palette_requires_utf8_encoding()

bool palette_requires_utf8_encoding ( const char *  chars,
size_t  len 
)

Definition at line 55 of file palette.c.

55 {
56 // Handle NULL or empty string
57 if (!chars || len == 0) {
58 return false;
59 }
60
61 for (size_t i = 0; i < len; i++) {
62 // Any byte with high bit set indicates UTF-8
63 if ((unsigned char)chars[i] >= 128) {
64 return true;
65 }
66 }
67 return false;
68}

◆ select_compatible_palette()

palette_type_t select_compatible_palette ( palette_type_t  requested,
bool  client_utf8 
)

Definition at line 219 of file palette.c.

219 {
220 const palette_def_t *palette = get_builtin_palette(requested);
221
222 // Custom palettes are validated separately
223 if (requested == PALETTE_CUSTOM) {
224 return PALETTE_CUSTOM;
225 }
226
227 if (!palette) {
228 log_warn("Invalid palette type %d, falling back to standard", requested);
229 return PALETTE_STANDARD;
230 }
231
232 // If palette requires UTF-8 but client doesn't support it, find fallback
233 if (palette->requires_utf8 && !client_utf8) {
234 log_warn("Client doesn't support UTF-8, falling back from %s", palette->name);
235
236 // Fallback hierarchy
237 switch (requested) {
238 case PALETTE_BLOCKS:
239 case PALETTE_DIGITAL:
240 case PALETTE_COOL:
241 default:
242 return PALETTE_STANDARD; // ASCII equivalent
243 }
244 }
245
246 return requested; // Compatible, use as requested
247}

References get_builtin_palette().

◆ utf8_palette_contains_char()

bool utf8_palette_contains_char ( const utf8_palette_t *  palette,
const char *  utf8_char,
size_t  char_bytes 
)

Definition at line 510 of file palette.c.

510 {
511 if (!palette || !utf8_char || char_bytes == 0 || char_bytes > 4) {
512 return false;
513 }
514
515 for (size_t i = 0; i < palette->char_count; i++) {
516 const utf8_char_info_t *char_info = &palette->chars[i];
517 if (char_info->byte_len == char_bytes && memcmp(char_info->bytes, utf8_char, char_bytes) == 0) {
518 return true;
519 }
520 }
521
522 return false;
523}

◆ utf8_palette_create()

utf8_palette_t * utf8_palette_create ( const char *  palette_string)

Definition at line 363 of file palette.c.

363 {
364 if (!palette_string || *palette_string == '\0') {
365 return NULL;
366 }
367
368 utf8_palette_t *palette;
369 palette = SAFE_MALLOC(sizeof(utf8_palette_t), utf8_palette_t *);
370
371 // Count UTF-8 characters (not bytes)
372 size_t char_count = 0;
373 const char *p = palette_string;
374 size_t total_bytes = strlen(palette_string);
375
376 // First pass: count characters
377 size_t bytes_processed = 0;
378 while (bytes_processed < total_bytes) {
379 int bytes = 1;
380 unsigned char c = (unsigned char)p[0];
381
382 if ((c & 0x80) == 0) {
383 bytes = 1; // ASCII
384 } else if ((c & 0xE0) == 0xC0) {
385 bytes = 2; // 2-byte UTF-8
386 } else if ((c & 0xF0) == 0xE0) {
387 bytes = 3; // 3-byte UTF-8
388 } else if ((c & 0xF8) == 0xF0) {
389 bytes = 4; // 4-byte UTF-8
390 }
391
392 // Verify we have enough bytes left
393 if (bytes_processed + bytes > total_bytes) {
394 break;
395 }
396
397 p += bytes;
398 bytes_processed += bytes;
399 char_count++;
400 }
401
402 // Validate we got at least one character
403 if (char_count == 0) {
404 SET_ERRNO(ERROR_INVALID_PARAM, "Palette string contains no valid UTF-8 characters");
405 SAFE_FREE(palette);
406 return NULL;
407 }
408
409 // Allocate character array
410 palette->chars = SAFE_MALLOC(char_count * sizeof(utf8_char_info_t), utf8_char_info_t *);
411 palette->raw_string = SAFE_MALLOC(total_bytes + 1, char *);
412 // Explicit NULL checks to satisfy static analyzer (SAFE_MALLOC calls FATAL on failure)
413 if (palette->chars == NULL || palette->raw_string == NULL) {
414 SAFE_FREE(palette->chars);
415 SAFE_FREE(palette->raw_string);
416 SAFE_FREE(palette);
417 SET_ERRNO(ERROR_MEMORY, "Failed to allocate palette character array");
418 return NULL;
419 }
420
421 SAFE_MEMCPY(palette->raw_string, total_bytes + 1, palette_string, total_bytes + 1);
422 palette->char_count = char_count;
423 palette->total_bytes = total_bytes; // Use strlen() value
424
425 // Second pass: parse characters
426 p = palette_string;
427 size_t char_idx = 0;
428 bytes_processed = 0;
429
430 // Set locale for wcwidth - save a copy of the old locale
431 char old_locale[256] = {0};
432 char *current_locale = setlocale(LC_CTYPE, NULL);
433 if (current_locale) {
434 SAFE_STRNCPY(old_locale, current_locale, sizeof(old_locale));
435 }
436 (void)setlocale(LC_CTYPE, "");
437
438 while (char_idx < char_count && bytes_processed < total_bytes) {
439 utf8_char_info_t *char_info = &palette->chars[char_idx];
440
441 // Determine UTF-8 byte length
442 unsigned char c = (unsigned char)*p;
443 int bytes = 1;
444
445 if ((c & 0x80) == 0) {
446 bytes = 1;
447 } else if ((c & 0xE0) == 0xC0) {
448 bytes = 2;
449 } else if ((c & 0xF0) == 0xE0) {
450 bytes = 3;
451 } else if ((c & 0xF8) == 0xF0) {
452 bytes = 4;
453 }
454
455 // Verify we have enough bytes left
456 if (bytes_processed + bytes > total_bytes) {
457 break;
458 }
459
460 // Copy bytes and null-terminate
461 SAFE_MEMCPY(char_info->bytes, bytes, p, bytes);
462 if (bytes < 4) {
463 SAFE_MEMSET(char_info->bytes + bytes, 4 - bytes, 0, 4 - bytes);
464 }
465 char_info->byte_len = bytes;
466
467 // Get display width using utf8_display_width
468 int width = utf8_display_width_n(p, bytes);
469 char_info->display_width = (width > 0 && width <= 2) ? width : 1;
470
471 p += bytes;
472 bytes_processed += bytes;
473 char_idx++;
474 }
475
476 // Restore locale
477 if (old_locale[0] != '\0') {
478 (void)setlocale(LC_CTYPE, old_locale);
479 }
480
481 return palette;
482}
int utf8_display_width_n(const char *str, size_t max_bytes)
Definition utf8.c:92

References utf8_display_width_n().

Referenced by build_ramp64().

◆ utf8_palette_destroy()

void utf8_palette_destroy ( utf8_palette_t *  palette)

Definition at line 485 of file palette.c.

485 {
486 if (palette) {
487 SAFE_FREE(palette->chars);
488 SAFE_FREE(palette->raw_string);
489 SAFE_FREE(palette);
490 }
491}

Referenced by build_ramp64().

◆ utf8_palette_find_all_char_indices()

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 
)

Definition at line 543 of file palette.c.

544 {
545 if (!palette || !utf8_char || char_bytes == 0 || char_bytes > 4 || !indices || max_indices == 0) {
546 return 0;
547 }
548
549 size_t found_count = 0;
550
551 for (size_t i = 0; i < palette->char_count && found_count < max_indices; i++) {
552 const utf8_char_info_t *char_info = &palette->chars[i];
553 if (char_info->byte_len == char_bytes && memcmp(char_info->bytes, utf8_char, char_bytes) == 0) {
554 indices[found_count++] = i;
555 }
556 }
557
558 return found_count;
559}

◆ utf8_palette_find_char_index()

size_t utf8_palette_find_char_index ( const utf8_palette_t *  palette,
const char *  utf8_char,
size_t  char_bytes 
)

Definition at line 526 of file palette.c.

526 {
527 if (!palette || !utf8_char || char_bytes == 0 || char_bytes > 4) {
528 return (size_t)-1;
529 }
530
531 for (size_t i = 0; i < palette->char_count; i++) {
532 const utf8_char_info_t *char_info = &palette->chars[i];
533 if (char_info->byte_len == char_bytes && memcmp(char_info->bytes, utf8_char, char_bytes) == 0) {
534 return i;
535 }
536 }
537
538 return (size_t)-1;
539}

◆ utf8_palette_get_char()

const utf8_char_info_t * utf8_palette_get_char ( const utf8_palette_t *  palette,
size_t  index 
)

Definition at line 494 of file palette.c.

494 {
495 if (!palette || index >= palette->char_count) {
496 return NULL;
497 }
498 return &palette->chars[index];
499}

Referenced by build_ramp64().

◆ utf8_palette_get_char_count()

size_t utf8_palette_get_char_count ( const utf8_palette_t *  palette)

Definition at line 502 of file palette.c.

502 {
503 if (!palette) {
504 return 0;
505 }
506 return palette->char_count;
507}

Referenced by build_ramp64().

◆ validate_palette_chars()

bool validate_palette_chars ( const char *  chars,
size_t  len 
)

Definition at line 71 of file palette.c.

71 {
72 if (!chars || len == 0) {
73 SET_ERRNO(ERROR_INVALID_PARAM, "Palette validation failed: empty or NULL palette");
74 return false;
75 }
76
77 if (len > 256) {
78 SET_ERRNO(ERROR_INVALID_PARAM, "Palette validation failed: palette too long (%zu chars, max 256)", len);
79 return false;
80 }
81
82 // Decode UTF-8 string into codepoints
83 uint32_t codepoints[256];
84 size_t codepoint_count = utf8_to_codepoints(chars, codepoints, 256);
85
86 if (codepoint_count == SIZE_MAX) {
87 SET_ERRNO(ERROR_INVALID_PARAM, "Palette validation failed: invalid UTF-8 sequence");
88 return false;
89 }
90
91 // Validate each codepoint
92 for (size_t i = 0; i < codepoint_count; i++) {
93 uint32_t cp = codepoints[i];
94
95 // Check for control characters (except tab)
96 if (cp < 32 && cp != '\t') {
97 SET_ERRNO(ERROR_INVALID_PARAM, "Palette validation failed: control character at position %zu", i);
98 return false;
99 }
100
101 // Convert codepoint to UTF-8 and check display width
102 // For ASCII characters, this is straightforward
103 // For multi-byte UTF-8, we need to check the display width
104 if (cp < 128) {
105 // ASCII character - always width 1
106 continue;
107 } else {
108 // Non-ASCII: use utf8_display_width to check character width
109 // Create a temporary buffer with the UTF-8 encoded character
110 uint8_t utf8_buf[5] = {0};
111
112 // Encode codepoint to UTF-8 (simple for demo, should use proper encoder)
113 if (cp <= 0x7F) {
114 utf8_buf[0] = (uint8_t)cp;
115 } else if (cp <= 0x7FF) {
116 utf8_buf[0] = (uint8_t)(0xC0 | (cp >> 6));
117 utf8_buf[1] = (uint8_t)(0x80 | (cp & 0x3F));
118 } else if (cp <= 0xFFFF) {
119 utf8_buf[0] = (uint8_t)(0xE0 | (cp >> 12));
120 utf8_buf[1] = (uint8_t)(0x80 | ((cp >> 6) & 0x3F));
121 utf8_buf[2] = (uint8_t)(0x80 | (cp & 0x3F));
122 } else if (cp <= 0x10FFFF) {
123 utf8_buf[0] = (uint8_t)(0xF0 | (cp >> 18));
124 utf8_buf[1] = (uint8_t)(0x80 | ((cp >> 12) & 0x3F));
125 utf8_buf[2] = (uint8_t)(0x80 | ((cp >> 6) & 0x3F));
126 utf8_buf[3] = (uint8_t)(0x80 | (cp & 0x3F));
127 } else {
128 SET_ERRNO(ERROR_INVALID_PARAM, "Palette validation failed: invalid codepoint at position %zu", i);
129 return false;
130 }
131
132 // Check character width - allow width 1 and 2 (for emoji and wide characters)
133 int width = utf8_display_width((const char *)utf8_buf);
134 if (width < 0 || width > 2) {
135 SET_ERRNO(ERROR_INVALID_PARAM,
136 "Palette validation failed: character at position %zu has invalid width %d (must be 1 or 2)", i,
137 width);
138 return false;
139 }
140 }
141 }
142
143 log_debug("Palette validation successful: %zu characters validated", codepoint_count);
144 return true;
145}
size_t utf8_to_codepoints(const char *str, uint32_t *out_codepoints, size_t max_codepoints)
Definition utf8.c:187
int utf8_display_width(const char *str)
Definition utf8.c:46

References utf8_display_width(), and utf8_to_codepoints().

Referenced by apply_palette_config(), and initialize_client_palette().

Variable Documentation

◆ DEFAULT_ASCII_PALETTE

◆ DEFAULT_ASCII_PALETTE_LEN

const size_t DEFAULT_ASCII_PALETTE_LEN = 23

Definition at line 26 of file palette.c.

Referenced by init_default_luminance_palette().