/* meow_utf8.h — UTF-8 整序列有界拷贝/字符步进纯决策(宿主可单测;无 ui_width/mfont 依赖)。 * * 背景(问题2,M6.3 真机):主界面应用名「流媒体客户端」被截成 5 字 + 半个字。两处根因: * ① app_launcher.c `strncpy(a->name, name, 16)` 仍把显示名硬截 16 字节(UTF-8 下=5 汉字+ 半个 * 「端」)——M6.3 只扩了 name[49]+json_str 整序列,漏改此处; * ② main_menu.c `menu_name_fit` 按字节回退、边界判定错(do..while 读的是刚清零的字节,恒停续字节), * 停在半截多字节字符;再加 "..." 未为省略号留宽 → 溢出 maxw 被裁切。 * 本头把 UTF-8 整序列处理抽成纯函数(meow_utf8_char_len / meow_utf8_copy_bounded / 截断决策), * 两处复用并可宿主单测(test_utf8_name.c)。 */ #ifndef MEOW_UTF8_H #define MEOW_UTF8_H #include #include /* snprintf(有界拷贝用) */ #ifdef __cplusplus extern "C" { #endif /* 由首字节 c0 求整字符 UTF-8 字节长(1..4)。非法首字节(0x80..0xBF 续字节)返回 1(按异常字节跳过, * 绝不把孤立续字节与后字并成大字符)。纯按前导位,不看后续字节是否齐全(那由 copy_bounded 处理)。 */ static inline int meow_utf8_char_len(unsigned char c0) { if (c0 < 0x80) return 1; if (c0 < 0xC0) return 1; /* 孤立续字节:兜底 1 */ if (c0 < 0xE0) return 2; if (c0 < 0xF0) return 3; return 4; } /* 有界拷贝:把 src 拷入 dst(容量 cap,含 NUL),**绝不劈开多字节字符**。snprintf 截断若落在字符 * 中间(或 src 本身尾部是半截字符),回退到整字符边界:删去最末不完整字符。返回拷贝后字节长(不含 * NUL)。源可为 NULL。持久化/展示名均用——保证 UTF-8 整序列,杜绝「5 汉字+半个字」式残字。 */ static inline int meow_utf8_copy_bounded(char *dst, int cap, const char *src) { if (!dst || cap <= 0) return 0; if (cap == 1) { dst[0] = 0; return 0; } if (!src) src = ""; snprintf(dst, (size_t)cap, "%s", src); int n = (int)strlen(dst); if (n <= 0) return 0; /* 定位最末一个「起始字节」(非续字节):从尾部回退续字节。 */ int last_start = n - 1; while (last_start > 0 && ((unsigned char)dst[last_start] & 0xC0) == 0x80) last_start--; /* 末字符:起始字节=last_start,实际字节数=have;字符应需 need。have