Files
cKit/Foundation/c_utf8.c
T
2026-08-30 03:59:45 +08:00

818 lines
28 KiB
C

#include <c_utf8.h>
c_size_t c_utf8_strlen(const char* str) {
if (!str) return 0;
c_size_t char_count = 0;
c_size_t i = 0;
while (str[i] != '\0') {
i += c_utf8_char_len(str[i]); // 跳过当前字符占用的全部字节
char_count++;
}
return char_count;
}
const char* c_utf8_strchr(const char* str, const char* utf8_char) {
if (!str || !utf8_char || utf8_char[0] == '\0') return NULL;
c_size_t target_bytes = c_utf8_char_len(utf8_char[0]);
c_size_t i = 0;
while (str[i] != '\0') {
c_size_t curr_bytes = c_utf8_char_len(str[i]);
// 当且仅当两个字符占用的字节数相同,且多字节内容完全一致时匹配成功
if (curr_bytes == target_bytes) {
if (memcmp(&str[i], utf8_char, target_bytes) == 0) {
return &str[i];
}
}
i += curr_bytes; // 移动到下一个 UTF-8 字符
}
return NULL;
}
char* c_utf8_strncpy(char* dest, const char* src, c_size_t char_num) {
if (!dest || !src || char_num == 0) return dest;
c_size_t src_idx = 0;
c_size_t dest_idx = 0;
c_size_t copied_chars = 0;
while (src[src_idx] != '\0' && copied_chars < char_num) {
c_size_t char_bytes = c_utf8_char_len(src[src_idx]);
// 批量精确拷贝当前完整字符的 N 个字节
memcpy(&dest[dest_idx], &src[src_idx], char_bytes);
src_idx += char_bytes;
dest_idx += char_bytes;
copied_chars++;
}
// 兼容 strncpy 标准:如果源串长度小于 char_num,则用 '\0' 填充剩余的空隙
// 注意:这里的剩余空隙在实际工程中通常按字节填充更安全
dest[dest_idx] = '\0';
return dest;
}
int c_utf8_strncmp(const char* str1, const char* str2, c_size_t char_num) {
if (!str1 || !str2 || char_num == 0) return 0;
c_size_t idx1 = 0;
c_size_t idx2 = 0;
c_size_t compared_chars = 0;
while (compared_chars < char_num) {
// 任意一端到达末尾
if (str1[idx1] == '\0' || str2[idx2] == '\0') {
return (int)((unsigned char)str1[idx1] - (unsigned char)str2[idx2]);
}
c_size_t len1 = c_utf8_char_len(str1[idx1]);
c_size_t len2 = c_utf8_char_len(str2[idx2]);
// 如果单字长字节不相等,直接根据当前字符进行排序比较
if (len1 != len2) {
return (int)((unsigned char)str1[idx1] - (unsigned char)str2[idx2]);
}
// 长度相同时,直接比较当前单个 UTF-8 字符的内容
int res = memcmp(&str1[idx1], &str2[idx2], len1);
if (res != 0) {
return res;
}
idx1 += len1;
idx2 += len2;
compared_chars++;
}
return 0;
}
/* ------------------------------------------------------------------------------------------------------------------ */
/* */
char* c_utf8_tolower(char* str) {
if (!str) return NULL;
c_size_t i = 0;
while (str[i] != '\0') {
unsigned char b1 = (unsigned char)str[i];
c_size_t len = c_utf8_char_len(str[i]);
// Case A: Standard Single-byte ASCII Case Folding
if (len == 1) {
if (b1 >= 'A' && b1 <= 'Z') {
str[i] = (char)(b1 + 32);
}
}
// Case B: Double-byte UTF-8 Case Folding (e.g., Cyrillic / Greek scripts)
else if (len == 2) {
unsigned char b2 = (unsigned char)str[i + 1];
// Cyrillic script transformations (Capital letters: 0xD0 0x80 to 0xD0 0xBF)
if (b1 == 0xD0) {
if (b2 >= 0x90 && b2 <= 0xAF) {
// Shift to lowercase variant range located inside 0xD0 / 0xD1 blocks
str[i + 1] = (char)(b2 + 0x20);
} else if (b2 >= 0xB0 && b2 <= 0xBF) {
str[i] = (char)0xD1;
str[i + 1] = (char)(b2 - 0x20);
}
}
// Greek script transformations (Capital letters: 0xCE 0x91 to 0xCE 0xAB)
else if (b1 == 0xCE) {
if (b2 >= 0x91 && b2 <= 0xAB && b2 != 0xA2) { // 0xA2 is a special variant
// Shift down to lowercase variant range block inside 0xCE / 0xCF
if (b2 <= 0x9F) {
str[i + 1] = (char)(b2 + 0x20);
} else {
str[i] = (char)0xCF;
str[i + 1] = (char)(b2 - 0x20);
}
}
}
}
// Multi-byte Chinese ideographs (3 bytes) and Emojis (4 bytes) lack casing concepts; step past them
i += len;
}
return str;
}
char* c_utf8_toupper(char* str) {
if (!str) return NULL;
c_size_t i = 0;
while (str[i] != '\0') {
unsigned char b1 = (unsigned char)str[i];
c_size_t len = c_utf8_char_len(str[i]);
// Case A: Standard Single-byte ASCII Case Folding
if (len == 1) {
if (b1 >= 'a' && b1 <= 'z') {
str[i] = (char)(b1 - 32);
}
}
// Case B: Double-byte UTF-8 Case Folding (e.g., Cyrillic / Greek scripts)
else if (len == 2) {
unsigned char b2 = (unsigned char)str[i + 1];
// Cyrillic script transformations (Lowercase letters: 0xD0 0xB0 to 0xD0 0xBF, and 0xD1 0x80 to 0xD1 0x8F)
if (b1 == 0xD0) {
if (b2 >= 0xB0 && b2 <= 0xBF) {
// Shift up to uppercase variant range located inside the 0xD0 block
str[i + 1] = (char)(b2 - 0x20);
}
} else if (b1 == 0xD1) {
if (b2 >= 0x80 && b2 <= 0x8F) {
// Convert leading byte from 0xD1 back to 0xD0 and realign low byte
str[i] = (char)0xD0;
str[i + 1] = (char)(b2 + 0x20);
}
}
// Greek script transformations (Lowercase letters: 0xCE 0xB1 to 0xCE 0xBF, and 0xCF 0x80 to 0xCF 0x8B)
else if (b1 == 0xCE) {
if (b2 >= 0xB1 && b2 <= 0xBF) {
// Shift down to uppercase variant range block inside 0xCE
str[i + 1] = (char)(b2 - 0x20);
}
} else if (b1 == 0xCF) {
if (b2 >= 0x80 && b2 <= 0x8B) {
// Convert leading byte from 0xCF back to 0xCE and realign low byte
str[i] = (char)0xCE;
str[i + 1] = (char)(b2 + 0x20);
}
}
}
// 3-byte characters (Chinese Ideographs) and 4-byte characters (Emojis) lack casing concepts; jump past them safely
i += len;
}
return str;
}
char* c_utf8_strcat(char* dest, const char* src) {
if (!dest || !src) return dest;
// Locate the termination boundary point of the original destination array
c_size_t dest_idx = 0;
while (dest[dest_idx] != '\0') {
dest_idx++;
}
// Continuously append source bytes until hitting the terminator character
c_size_t src_idx = 0;
while (src[src_idx] != '\0') {
dest[dest_idx++] = src[src_idx++];
}
// Force secure terminal character sealing
dest[dest_idx] = '\0';
return dest;
}
/* ------------------------------------------------------------------------------------------------------------------ */
/* */
const char* c_utf8_strstr(const char* haystack, const char* needle) {
if (!haystack || !needle) return NULL;
// An empty needle matches the beginning of the haystack per standard strstr specification
if (needle[0] == '\0') {
return haystack;
}
c_size_t h_idx = 0;
while (haystack[h_idx] != '\0') {
c_size_t n_idx = 0;
c_size_t current_match_idx = h_idx;
// Perform byte-by-byte substring evaluation from the current boundary anchor
while (haystack[current_match_idx] != '\0' && needle[n_idx] != '\0' &&
haystack[current_match_idx] == needle[n_idx]) {
current_match_idx++;
n_idx++;
}
// If we successfully traversed the entire needle string, a match is found
if (needle[n_idx] == '\0') {
return &haystack[h_idx];
}
// Advance to the next valid UTF-8 character point in the haystack
h_idx += c_utf8_char_len(haystack[h_idx]);
}
return NULL;
}
/* ------------------------------------------------------------------------------------------------------------------ */
/* */
#include <string.h>
const char* c_utf8_strrchr(const char* str, const char* utf8_char) {
if (!str || !utf8_char || utf8_char[0] == '\0') return NULL;
c_size_t target_bytes = c_utf8_char_len(utf8_char[0]);
c_size_t i = 0;
const char* last_match = NULL;
while (str[i] != '\0') {
c_size_t curr_bytes = c_utf8_char_len(str[i]);
// Continuous linear check, updating our tracker to keep the furthest matched offset
if (curr_bytes == target_bytes) {
if (memcmp(&str[i], utf8_char, target_bytes) == 0) {
last_match = &str[i];
}
}
i += curr_bytes; // Jump forward explicitly across whole multi-byte characters
}
return last_match;
}
/**
* @brief Internal helper to verify if a specific UTF-8 character pointer matches any delimiter in the list.
*/
C_STATIC_FORCE_INLINE
c_bool_t c_utf8_is_delim(const char* current_char, const char* delims, c_size_t* delim_len) {
c_size_t d_idx = 0;
c_size_t c_len = c_utf8_char_len(*current_char);
while (delims[d_idx] != '\0') {
c_size_t d_len = c_utf8_char_len(delims[d_idx]);
if (c_len == d_len && memcmp(current_char, &delims[d_idx], c_len) == 0) {
*delim_len = d_len;
return C_TRUE;
}
d_idx += d_len;
}
return C_FALSE;
}
char* c_utf8_strtok(char* str, const char* delims, char** saveptr) {
if (!delims || !saveptr) return NULL;
// Use our saved pointer context if str is passed as NULL
char* token_cursor = (str != NULL) ? str : *saveptr;
if (!token_cursor || *token_cursor == '\0') {
return NULL;
}
// Step 1: Skip over any leading delimiter sequences to locate the token start
c_size_t skip_len = 0;
while (*token_cursor != '\0' && c_utf8_is_delim(token_cursor, delims, &skip_len)) {
token_cursor += skip_len;
}
// If we hit the absolute end of the input string while skipping delims, no tokens exist
if (*token_cursor == '\0') {
*saveptr = token_cursor;
return NULL;
}
char* token_start = token_cursor;
// Step 2: Track forward to locate the terminal delimiter boundary of this token
while (*token_cursor != '\0') {
c_size_t next_char_len = c_utf8_char_len(*token_cursor);
c_size_t match_delim_len = 0;
if (c_utf8_is_delim(token_cursor, delims, &match_delim_len)) {
// Found a boundary delimiter! Overwrite its leading byte with a null terminator
*token_cursor = '\0';
// Save state context tracking pointing immediately past the clipped delimiter
*saveptr = token_cursor + match_delim_len;
return token_start;
}
token_cursor += next_char_len;
}
// If we reached the end of the text string naturally, ensure the saveptr updates to string termination
*saveptr = token_cursor;
return token_start;
}
/**
* @brief Internal helper to return the uppercase variant value of a single ASCII or 2-byte UTF-8 character.
* Returns the original trailing byte structure if no case mapping applies.
*/
C_STATIC_FORCE_INLINE
void c_utf8_fold_char(const char* src, size_t len, unsigned char* out_b1, unsigned char* out_b2) {
*out_b1 = (unsigned char)src[0];
*out_b2 = (len > 1) ? (unsigned char)src[1] : 0;
// Single-byte ASCII case folding
if (len == 1) {
if (*out_b1 >= 'a' && *out_b1 <= 'z') {
*out_b1 -= 32;
}
}
// Double-byte UTF-8 case folding (Cyrillic & Greek scripts)
else if (len == 2) {
// Cyrillic script: 0xD0 0xB0...0xBF to 0xD0 0x90...0x9F; 0xD1 0x80...0x8F to 0xD0 0xA0...0xAF
if (*out_b1 == 0xD0) {
if (*out_b2 >= 0xB0 && *out_b2 <= 0xBF) {
*out_b2 -= 0x20;
}
} else if (*out_b1 == 0xD1) {
if (*out_b2 >= 0x80 && *out_b2 <= 0x8F) {
*out_b1 = 0xD0;
*out_b2 += 0x20;
}
}
// Greek script: 0xCE 0xB1...0xBF to 0xCE 0x91...0x9F; 0xCF 0x80...0x8B to 0xCE 0xA0...0xAB
else if (*out_b1 == 0xCE) {
if (*out_b2 >= 0xB1 && *out_b2 <= 0xBF) {
*out_b2 -= 0x20;
}
} else if (*out_b1 == 0xCF) {
if (*out_b2 >= 0x80 && *out_b2 <= 0x8B) {
*out_b1 = 0xCE;
*out_b2 += 0x20;
}
}
}
}
int c_utf8_strncasecmp(const char* str1, const char* str2, c_size_t char_num) {
if (!str1 || !str2 || char_num == 0) return 0;
c_size_t idx1 = 0;
c_size_t idx2 = 0;
c_size_t compared_chars = 0;
while (compared_chars < char_num) {
// Handle termination boundaries gracefully
if (str1[idx1] == '\0' || str2[idx2] == '\0') {
return (int)((unsigned char)str1[idx1] - (unsigned char)str2[idx2]);
}
c_size_t len1 = c_utf8_char_len(str1[idx1]);
c_size_t len2 = c_utf8_char_len(str2[idx2]);
// Fold characters to uppercase form for comparison
unsigned char f1_b1, f1_b2;
unsigned char f2_b1, f2_b2;
c_utf8_fold_char(&str1[idx1], len1, &f1_b1, &f1_b2);
c_utf8_fold_char(&str2[idx2], len2, &f2_b1, &f2_b2);
// Compare first bytes or script widths
if (f1_b1 != f2_b1) {
return (int)f1_b1 - (int)f2_b1;
}
// Compare second bytes (relevant for 2-byte sequences)
if (f1_b2 != f2_b2) {
return (int)f1_b2 - (int)f2_b2;
}
// For 3-byte (Chinese) or 4-byte characters, fallback to raw memory comparison if lead bytes matched
if (len1 > 2) {
if (len1 != len2) {
return (int)len1 - (int)len2;
}
int raw_res = memcmp(&str1[idx1], &str2[idx2], len1);
if (raw_res != 0) {
return raw_res;
}
}
// Advance iteration offsets
idx1 += len1;
idx2 += len2;
compared_chars++;
}
return 0;
}
/* ------------------------------------------------------------------------------------------------------------------ */
/* */
c_err_t c_utf8_to_unicode(const char* str, c_ucs4_t* out_codepoint, c_size_t* out_bytes_consumed) {
if (!str || *str == '\0' || !out_codepoint || !out_bytes_consumed) {
return C_ERR_PARAM;
}
unsigned char b1 = (unsigned char)str[0];
c_size_t len = c_utf8_char_len(str[0]);
c_ucs4_t cp = 0;
// Case 1: 1-Byte ASCII (0xxxxxxx)
if (len == 1) {
if (b1 >= 0x80) return C_ERR_PARAM; // Guard against malformed lead bytes
cp = b1;
}
// Case 2: 2-Byte Sequence (110xxxxx 10xxxxxx)
else if (len == 2) {
unsigned char b2 = (unsigned char)str[1];
if ((b2 & 0xC0) != 0x80) return C_ERR_PARAM; // Validate continuation byte
cp = ((b1 & 0x1F) << 6) | (b2 & 0x3F);
if (cp < 0x80) return C_ERR_PARAM; // Overlong encoding defense
}
// Case 3: 3-Byte Sequence (1110xxxx 10xxxxxx 10xxxxxx)
else if (len == 3) {
unsigned char b2 = (unsigned char)str[1];
unsigned char b3 = (unsigned char)str[2];
if ((b2 & 0xC0) != 0x80 || (b3 & 0xC0) != 0x80) return C_ERR_PARAM;
cp = ((b1 & 0x0F) << 12) | ((b2 & 0x3F) << 6) | (b3 & 0x3F);
if (cp < 0x0800) return C_ERR_PARAM; // Overlong encoding defense
if (cp >= 0xD800 && cp <= 0xDFFF) return C_ERR_PARAM; // Surrogate pairs rejection
}
// Case 4: 4-Byte Sequence (11110xxx 10xxxxxx 10xxxxxx 10xxxxxx)
else if (len == 4) {
unsigned char b2 = (unsigned char)str[1];
unsigned char b3 = (unsigned char)str[2];
unsigned char b4 = (unsigned char)str[3];
if ((b2 & 0xC0) != 0x80 || (b3 & 0xC0) != 0x80 || (b4 & 0xC0) != 0x80) return C_ERR_PARAM;
cp = ((b1 & 0x07) << 18) | ((b2 & 0x3F) << 12) | ((b3 & 0x3F) << 6) | (b4 & 0x3F);
if (cp < 0x010000) return C_ERR_PARAM; // Overlong encoding defense
}
else {
return C_ERR_PARAM;
}
// Limit validation (Unicode max standard is U+10FFFF)
if (cp > 0x10FFFF) {
return C_ERR_PARAM;
}
*out_codepoint = cp;
*out_bytes_consumed = len;
return C_ERR_OK;
}
c_err_t c_utf8_from_unicode(c_ucs4_t codepoint, char* dest_buffer, c_size_t* out_bytes_written) {
if (!dest_buffer || !out_bytes_written) {
return C_ERR_PARAM;
}
// Reject out-of-range codepoints or UTF-16 surrogate pairs (reserved for UTF-16 only)
if (codepoint > 0x10FFFF || (codepoint >= 0xD800 && codepoint <= 0xDFFF)) {
return C_ERR_PARAM;
}
// Case 1: Standard ASCII range (U+0000 to U+007F) -> Requires 1 byte
if (codepoint <= 0x7F) {
dest_buffer[0] = (char)codepoint;
*out_bytes_written = 1;
}
// Case 2: U+0080 to U+07FF -> Requires 2 bytes
else if (codepoint <= 0x7FF) {
dest_buffer[0] = (char)(0xC0 | ((codepoint >> 6) & 0x1F));
dest_buffer[1] = (char)(0x80 | (codepoint & 0x3F));
*out_bytes_written = 2;
}
// Case 3: U+0800 to U+FFFF -> Requires 3 bytes (Handles most Chinese characters)
else if (codepoint <= 0xFFFF) {
dest_buffer[0] = (char)(0xE0 | ((codepoint >> 12) & 0x0F));
dest_buffer[1] = (char)(0x80 | ((codepoint >> 6) & 0x3F));
dest_buffer[2] = (char)(0x80 | (codepoint & 0x3F));
*out_bytes_written = 3;
}
// Case 4: U+10000 to U+10FFFF -> Requires 4 bytes (Handles Emojis and ancient scripts)
else {
dest_buffer[0] = (char)(0xF0 | ((codepoint >> 18) & 0x07));
dest_buffer[1] = (char)(0x80 | ((codepoint >> 12) & 0x3F));
dest_buffer[2] = (char)(0x80 | ((codepoint >> 6) & 0x3F));
dest_buffer[3] = (char)(0x80 | (codepoint & 0x3F));
*out_bytes_written = 4;
}
// Securely seal the local buffer layout array with a trailing null terminator
dest_buffer[*out_bytes_written] = '\0';
return C_ERR_OK;
}
c_err_t c_utf8_to_unicode_array(const char* str, c_ucs4_t* dest_array, c_size_t array_capacity, c_size_t* out_chars_written) {
if (!str || !dest_array || !out_chars_written) {
return C_ERR_PARAM;
}
c_size_t src_idx = 0;
c_size_t chars_count = 0;
while (str[src_idx] != '\0') {
// Enforce array capacity threshold constraints
if (chars_count >= array_capacity) {
*out_chars_written = chars_count;
return C_ERR_PARAM; // Destination array is too small to fit the remaining string
}
c_ucs4_t cp = 0;
c_size_t bytes_consumed = 0;
// Decode the single character point via your core decoding function
c_err_t err = c_utf8_to_unicode(&str[src_idx], &cp, &bytes_consumed);
if (err != C_ERR_OK) {
*out_chars_written = chars_count;
return err; // Propagate the malformed stream error up
}
dest_array[chars_count++] = cp;
src_idx += bytes_consumed;
}
*out_chars_written = chars_count;
return C_ERR_OK;
}
c_err_t c_utf8_from_unicode_array(const c_ucs4_t* src_array, c_size_t src_array_len, char* dest_buffer, c_size_t dest_capacity, c_size_t* out_bytes_written) {
if (!src_array || !dest_buffer || !out_bytes_written) {
return C_ERR_PARAM;
}
c_size_t dest_idx = 0;
for (c_size_t i = 0; i < src_array_len; i++) {
char temp_char_buf[5]; // Temporary standalone slot buffer
c_size_t bytes_written = 0;
// Encode single code point state back to byte wrappers
c_err_t err = c_utf8_from_unicode(src_array[i], temp_char_buf, &bytes_written);
if (err != C_ERR_OK) {
*out_bytes_written = dest_idx;
return err;
}
// Verify if destination capacity bounds can hold the new character block (+1 for terminal null)
if (dest_idx + bytes_written + 1 > dest_capacity) {
*out_bytes_written = dest_idx;
dest_buffer[dest_idx] = '\0'; // Gracefully terminate the current chunk before failing
return C_ERR_PARAM;
}
// Copy raw encoded data bytes into our tracking stream layout
memcpy(dest_buffer + dest_idx, temp_char_buf, bytes_written);
dest_idx += bytes_written;
}
// Force strict trailing character termination closure
dest_buffer[dest_idx] = '\0';
*out_bytes_written = dest_idx;
return C_ERR_OK;
}
/* ------------------------------------------------------------------------------------------------------------------ */
/* */
c_err_t c_utf8_to_utf16(const char* str, c_uint16_t* dest_array, c_size_t array_capacity, c_size_t* out_units_written) {
if (!str || !dest_array || !out_units_written) {
return C_ERR_PARAM;
}
c_size_t src_idx = 0;
c_size_t units_count = 0;
while (str[src_idx] != '\0') {
c_ucs4_t cp = 0;
c_size_t bytes_consumed = 0;
// 1. Decode the UTF-8 sequence into a Unicode codepoint
c_err_t err = c_utf8_to_unicode(&str[src_idx], &cp, &bytes_consumed);
if (err != C_ERR_OK) {
*out_units_written = units_count;
return err;
}
// 2. Encode the codepoint into UTF-16
if (cp <= 0xFFFF) {
// BMP Range: Requires exactly one 16-bit code unit
if (units_count + 1 >= array_capacity) {
*out_units_written = units_count;
return C_ERR_PARAM; // Out of bounds
}
dest_array[units_count++] = (c_uint16_t)cp;
} else {
// Supplementary Planes (Astral): Requires a surrogate pair (two 16-bit units)
if (units_count + 2 >= array_capacity) {
*out_units_written = units_count;
return C_ERR_PARAM; // Out of bounds
}
cp -= 0x10000;
dest_array[units_count++] = (c_uint16_t)(0xD800 | ((cp >> 10) & 0x3FF)); // High Surrogate
dest_array[units_count++] = (c_uint16_t)(0xDC00 | (cp & 0x3FF)); // Low Surrogate
}
src_idx += bytes_consumed;
}
// Append the trailing null terminator to make it a valid UTF-16 string
if (units_count < array_capacity) {
dest_array[units_count] = 0;
} else {
*out_units_written = units_count;
return C_ERR_PARAM;
}
*out_units_written = units_count;
return C_ERR_OK;
}
c_err_t c_utf8_from_utf16(const c_uint16_t* src_array, c_size_t src_array_len, char* dest_buffer, c_size_t dest_capacity, c_size_t* out_bytes_written) {
if (!src_array || !dest_buffer || !out_bytes_written) {
return C_ERR_PARAM;
}
c_size_t src_idx = 0;
c_size_t dest_idx = 0;
while (src_idx < src_array_len) {
c_ucs4_t cp = 0;
c_uint16_t u1 = src_array[src_idx++];
// 1. Decode UTF-16 to Unicode codepoint
if (u1 >= 0xD800 && u1 <= 0xDBFF) {
// High surrogate detected, look ahead for the matching low surrogate
if (src_idx >= src_array_len) {
*out_bytes_written = dest_idx;
return C_ERR_PARAM; // Truncated/Malformed surrogate pair
}
c_uint16_t u2 = src_array[src_idx++];
if (u2 < 0xDC00 || u2 > 0xDFFF) {
*out_bytes_written = dest_idx;
return C_ERR_PARAM; // Missing or invalid low surrogate
}
cp = (((u1 & 0x3FF) << 10) | (u2 & 0x3FF)) + 0x10000;
} else if (u1 >= 0xDC00 && u1 <= 0xDFFF) {
// Isolated low surrogate is invalid in a lead position
*out_bytes_written = dest_idx;
return C_ERR_PARAM;
} else {
// Normal BMP character
cp = u1;
}
// 2. Encode the Unicode codepoint back into the destination UTF-8 buffer
char temp_buf[5];
c_size_t bytes_written = 0;
c_err_t err = c_utf8_from_unicode(cp, temp_buf, &bytes_written);
if (err != C_ERR_OK) {
*out_bytes_written = dest_idx;
return err;
}
// Verify if destination capacity bounds can hold the new block (+1 for terminal null)
if (dest_idx + bytes_written + 1 > dest_capacity) {
*out_bytes_written = dest_idx;
dest_buffer[dest_idx] = '\0';
return C_ERR_PARAM; // Overflow protection
}
memcpy(dest_buffer + dest_idx, temp_buf, bytes_written);
dest_idx += bytes_written;
}
// Force strict trailing character termination closure
dest_buffer[dest_idx] = '\0';
*out_bytes_written = dest_idx;
return C_ERR_OK;
}
/* ------------------------------------------------------------------------------------------------------------------ */
/* */
c_err_t c_utf16_swap_endian(c_uint16_t* utf16_array, c_size_t length) {
if (!utf16_array) {
return C_ERR_PARAM;
}
for (c_size_t i = 0; i < length; i++) {
c_uint16_t value = utf16_array[i];
// Bitwise swap: (value >> 8) extracts the high byte, (value << 8) extracts the low byte
utf16_array[i] = (c_uint16_t)(((value & 0x00FF) << 8) | ((value & 0xFF00) >> 8));
}
return C_ERR_OK;
}
c_err_t c_utf16_to_unicode(const c_uint16_t* src_units, c_size_t src_capacity, c_ucs4_t* out_codepoint, c_size_t* out_units_read) {
if (!src_units || src_capacity == 0 || !out_codepoint || !out_units_read) {
return C_ERR_PARAM;
}
c_uint16_t u1 = src_units[0];
// Case 1: High Surrogate Point Detection (0xD800 to 0xDBFF)
if (u1 >= 0xD800 && u1 <= 0xDBFF) {
if (src_capacity < 2) {
return C_ERR_PARAM; // Truncated sequence: expected a matching low surrogate
}
c_uint16_t u2 = src_units[1];
if (u2 < 0xDC00 || u2 > 0xDFFF) {
return C_ERR_PARAM; // Malformed sequence: missing a valid trailing low surrogate
}
// Reconstruct Astral Plane Codepoint via formula: ((High - 0xD800) << 10) + (Low - 0xDC00) + 0x10000
*out_codepoint = (c_ucs4_t)((((u1 & 0x3FF) << 10) | (u2 & 0x3FF)) + 0x10000);
*out_units_read = 2;
}
// Case 2: Isolated Low Surrogate Error Check (0xDC00 to 0xDFFF)
else if (u1 >= 0xDC00 && u1 <= 0xDFFF) {
return C_ERR_PARAM; // Isolated low surrogate is mathematically invalid in a lead position
}
// Case 3: Standard BMP Range Character
else {
*out_codepoint = (c_ucs4_t)u1;
*out_units_read = 1;
}
return C_ERR_OK;
}
c_err_t c_utf16_from_unicode(c_ucs4_t codepoint, c_uint16_t* dest_units, c_size_t dest_capacity, c_size_t* out_units_written) {
if (!dest_units || dest_capacity == 0 || !out_units_written) {
return C_ERR_PARAM;
}
// Limit Validation: Reject invalid Astral values or illegal UTF-16 surrogate codepoint blocks
if (codepoint > 0x10FFFF || (codepoint >= 0xD800 && codepoint <= 0xDFFF)) {
return C_ERR_PARAM;
}
// Case 1: BMP Range (U+0000 to U+FFFF) -> Requires 1 code unit
if (codepoint <= 0xFFFF) {
dest_units[0] = (c_uint16_t)codepoint;
*out_units_written = 1;
}
// Case 2: Supplementary Planes (U+10000 to U+10FFFF) -> Requires 2 code units (Surrogate Pair)
else {
if (dest_capacity < 2) {
return C_ERR_PARAM; // Insufficient buffer capacity
}
c_ucs4_t adjusted = codepoint - 0x10000;
dest_units[0] = (c_uint16_t)(0xD800 | ((adjusted >> 10) & 0x3FF)); // High Surrogate
dest_units[1] = (c_uint16_t)(0xDC00 | (adjusted & 0x3FF)); // Low Surrogate
*out_units_written = 2;
}
return C_ERR_OK;
}