Show HN: Mojibake – 一个用C语言编写的底层Unicode库

Hacker News Top 工具

摘要

Mojibake是一个为C11/C++17设计的自包含Unicode 17库,提供归一化、大小写转换和字符数据库功能,零依赖。

我编写Mojibake是因为我不喜欢其他用于Unicode支持的Unicode库。<p>它仅由两个合并文件组成:mojibake.h和mojibake.c。我添加了所有最重要的Unicode算法,例如归一化、大小写转换、分词、双向文本、排序、易混淆字符等。<p>我定期在以下操作系统中测试它:Linux、macOS、FreeBSD、OpenBSD、NetBSD和Windows 11。<p>你可以在该网站上找到一个包含所有公共API函数和文档的WASM演示。如果你想参与,请随意。欢迎任何形式的帮助。请查看GitHub仓库中的CONTRIBUTING.md和API.md文件以获取操作说明。
查看原文
查看缓存全文

缓存时间: 2026/07/16 22:54

# Mojibake — Unicode 17 for C 来源:https://mojibake.zaerl.com/ Unicode 17 - C11 - 零依赖 ## Unicode 文本处理,*无累赘* Mojibake 是一个小巧、快速、自包含的 Unicode 库,支持 C11 和 C++17。无需运行时或依赖树即可提供符合标准的文本处理能力。 | 标准 | 运行时 | 许可证 | 从这里开始 | |------|--------|--------|------------| | Unicode 17.0 | 无 | MIT | | ## 实用的 Unicode 工具包 **Mojibake** 是一个底层 Unicode 17 文本处理库,使用 C11 编写,兼容 C++17,并以 MIT 许可证发布。 ## 使用方法 您无需安装任何内容。只需将两个文件(`mojibake.c`、`mojibake.h`)添加到您的 C/C++ 项目中即可。在此处下载: [mojibake-amalgamation-027.zip](https://github.com/zaerl/mojibake/releases/download/v0.2.7/mojibake-amalgamation-027.zip) 归一化、字符计数和 NFKC 大小写折叠的示例。 ```c #include <stdio.h> #include <string.h> #include "mojibake.h" void print_string(const char *input, size_t length); int main(int argc, char *const argv[]) { const char *input = "Cafe\xCC\x81"; size_t length = strlen(input); mjb_result result; // 归一化示例:在 NFC 中,e + ◌́ -> é (U+00E9) if(mjb_normalize(input, length, MJB_ENC_UTF_8, MJB_NORMALIZATION_NFC, MJB_ENC_UTF_8, &result) != MJB_STATUS_OK) { return 1; } // Cafe + ◌́ (U+0301, 组合锐音符) -> Café print_string(input, length); // Caf + é (U+00E9, 带锐音符的拉丁小写字母 e) -> Café print_string(result.output, result.output_size); const char *mojibake = "文字化け"; length = strlen(mojibake); // 字符串长度示例:mjb_string_length 计算字符串中的字符数,而不是字节数。 printf("\"%s\" encoded in UTF-8 is %zu bytes long, and %zu characters long\n", mojibake, length, mjb_string_length(mojibake, length, MJB_ENC_UTF_8)); mjb_result_free(&result); const char *case_input = "Straße"; // NFKC 大小写折叠示例:在 NFKC 大小写折叠中,ß -> ss if(mjb_nfkc_casefold(case_input, strlen(case_input), MJB_ENC_UTF_8, MJB_ENC_UTF_8, &result) != MJB_STATUS_OK) { return 1; } printf("%s -> %.*s\n", case_input, (int)result.output_size, result.output); mjb_result_free(&result); return 0; } void print_string(const char *input, size_t length) { for(size_t i = 0; i < length; ++i) { unsigned char byte = (unsigned char)input[i]; if(byte >= 0x21 && byte <= 0x7E) { printf("%c", byte); } else { printf("<%02X>", byte); } } printf("\n"); } ``` 输出: ``` Cafe<81> Caf<C3><A9> "Café" "文字化け" encoded in UTF-8 is 12 bytes long, and 4 characters long Straße -> strasse ``` Mojibake 的目标是: 1. 小巧 2. 易于使用 3. 快速 4. 自包含 Mojibake 具备以下特点: 1. 在所有现代操作系统上运行(Linux、macOS、FreeBSD、OpenBSD、NetBSD、Windows 10/11) 2. 通过所支持算法的官方 Unicode 测试套件 3. 实现所有 Unicode 标准算法 4. 满足所有 [Unicode 一致性要求](https://github.com/zaerl/mojibake/blob/main/CONFORMANCE_REQUIREMENTS.md) ## 功能亮点 所有 C 文件,连同 Unicode 数据表,都被合并成一个大的单一文件和头文件:`mojibake.c` 和 `mojibake.h`。零依赖。 **文本转换** - **归一化**:NFC/NFD/NFKC/NFKD(`mjb_normalize`)、面向标识符的 NFKC 大小写折叠(`mjb_nfkc_casefold`),以及快速预检查(`mjb_string_is_normalized`)([UAX #15, Unicode 17.0.0](https://www.unicode.org/reports/tr15/tr15-57.html)) - **大小写转换**:大写、小写、首字母大写以及带有完全特殊大小写和条件映射的大小写折叠(`mjb_case`) - **过滤**:在归一化时去除控制字符、空格或数字字符(`mjb_string_filter`) **文本分析** - **字符数据库**:每个 Unicode 字符数据库属性:类别、文字和 Script_Extensions、区块、平面、数值、名称(`mjb_codepoint_character`、`mjb_codepoint_script_extensions`) - **分割**:字素簇、单词、句子和断行机会([UAX #29, Unicode 17.0.0](https://www.unicode.org/reports/tr29/tr29-47.html)、[UAX #14, Unicode 17.0.0](https://www.unicode.org/reports/tr14/tr14-55.html)) - **双向文本**:完整的 Unicode 双向算法:段落解析、行重排序、运行([UAX #9, Unicode 17.0.0](https://www.unicode.org/reports/tr9/tr9-51.html)) - **Emoji**:码点属性、序列分析、RGI emoji 检测 - **显示宽度**:东亚宽度和终端显示宽度,支持宽度感知截断(`mjb_display_width`、`mjb_truncate_width`) **排序与比较** - **排序**:Unicode 排序算法的字符串比较和排序键,支持 shifted 和 non-ignorable 模式(`mjb_string_compare`、`mjb_collation_key`、[UTS #10, Unicode 17.0.0](https://www.unicode.org/reports/tr10/tr10-53.html)) **安全** - **易混淆检测**:生成可重用的骨架并检查字符串在视觉上是否易混淆(`mjb_confusable_skeleton`、`mjb_string_is_confusable`、[UTS #39, Unicode 17.0.0](https://www.unicode.org/reports/tr39/tr39-32.html)) - **标识符验证**:为解析器和编译器作者提供 XID/ID 检查(`mjb_string_is_identifier`、[UAX #31, Unicode 17.0.0](https://www.unicode.org/reports/tr31/tr31-43.html)) **集成** - **编码**:API 接受并输出 UTF-8、UTF-16LE、UTF-16BE、UTF-32LE、UTF-32BE 字符串,支持编码检测和转换(`mjb_string_encoding`、`mjb_string_convert_encoding`) - **解析和字符串函数**:逐个字符迭代(`mjb_next_character`)和标准 C `string.h` 样式的辅助函数(`mjb_string_length` 等) - **区域设置**:严格的 BCP 47 语言标签解析(`mjb_locale_parse`) - **可嵌入**:自定义分配器(`mjb_set_memory_functions`)、编译时功能标志以缩减表大小、C++17 封装(`src/cpp/mojibake.hpp`)、CLI 工具(`src/shell`)以及 WASM + TypeScript API(`src/api`) - **测试**:Mojibake 使用 [Attractor](https://github.com/zaerl/attractor/) 作为测试套件,运行超过 [150 万个断言](https://github.com/zaerl/mojibake/blob/main/TESTS.md),包括所支持算法的官方 Unicode 一致性套件 - **模糊测试**:Mojibake 使用 [libFuzzer](https://llvm.org/docs/LibFuzzer.html) 对不可信的字节输入进行模糊测试,且 `AddressSanitizer` 和 `UBSan` 报告干净 ### 编译时功能 Mojibake 可以编译掉可选功能表,以减小二进制大小。功能宏默认启用。 - `#define MJB_FEATURE_CHARACTER_NAMES` 控制由 `mjb_codepoint_character(...)` 用来填充 `mjb_character.name` 的 Unicode 字符名称表。禁用时,这些表不会被编译,且 `mjb_character.name` 报告为 `Codepoint U+XXXX`。这将使输出减少约 **30%**。 使用 CMake: ``` cmake -S . -B build-no-name -DMJB_FEATURE_CHARACTER_NAMES=OFF cmake --build build-no-name ``` 使用提供的 Makefile: ``` make build BUILD_DIR=build-no-name FEATURE_CHARACTER_NAMES=OFF make test-no-names ``` ### API 文档 详细文档请参阅 [API.md](https://github.com/zaerl/mojibake/blob/main/API.md) 或网站。 ### CLI `src/shell` 目录构建了用于测试库的 `mojibake` CLI。用法示例: ``` # 输出 "NFC: Café",e + ◌́ -> é mojibake nfc $'Cafe\u0301' # 输出 emoji 序列 [1] Basic, [2] Fully-qualified,两个字符 U+263A U+FE0F mojibake emoji "☺️" ``` ## 从源码构建与贡献 请参阅 [CONTRIBUTING.md](https://github.com/zaerl/mojibake/blob/main/CONTRIBUTING.md) 获取说明。 ## 许可证 Mojibake 以 MIT 许可证发布(参见 [LICENSE](https://github.com/zaerl/mojibake/blob/main/LICENSE))。 ## 法律声明 此处提供了让该库符合 Unicode 标准所需的非常详细且枯燥的信息,至少是我所掌握的内容,详见 [CONFORMANCE_REQUIREMENTS.md](https://github.com/zaerl/mojibake/blob/main/CONFORMANCE_REQUIREMENTS.md)。 ## 致谢 Mojibake 建立在杰出个人和团队的工作之上。 1. Unicode 字符数据库 – 版权所有 © 1991-2026 Unicode, Inc.(参见 [license.txt](https://www.unicode.org/license.txt)) 2. Unicode CLDR 项目 – 版权所有 © 2004-2026 Unicode, Inc.(参见 [LICENSE](https://raw.githubusercontent.com/unicode-org/cldr/refs/heads/main/LICENSE)) 无需安装 ## 在浏览器中试用每个函数 下面的每个 API 参考都包含一个由 WASM 构建支持的实时表单。展开一个函数,输入参数,立即查看结果。 下载 [mojibake-wasm-027.zip](https://github.com/zaerl/mojibake/releases/download/v0.2.7/mojibake-wasm-027.zip) ## 参考 ## C API 函数按元数据部分组织。点击加号按钮可查看详细行为、示例、规范和实时 WASM 表单。 ### mjb_normalize ```c mjb_status mjb_normalize( const char *buffer, size_t byte_length, mjb_encoding encoding, mjb_normalization form, mjb_encoding output_encoding, mjb_result *result ); ``` 将字符串归一化为请求的 Unicode 归一化形式。如果输入已经归一化且无需编码转换,则直接返回输入缓冲区(`result->output`),并将 `result->transformed` 设置为 false,不进行分配。 #### 返回值 - `MJB_STATUS_OK` — 字符串已归一化(或已处于归一化状态) - `MJB_STATUS_INVALID_ARGUMENT` — `result` 为 NULL,或 `buffer` 为 NULL 且 size 非零 - `MJB_STATUS_INVALID_FORM` — `form` 不是 NFC、NFD、NFKC 或 NFKD - `MJB_STATUS_OVERFLOW` — 输出大小会溢出 - `MJB_STATUS_NO_MEMORY` — 分配失败 #### 示例 ```c const char *input = "Cafe\xCC\x81"; // "Cafe" + U+0301 组合锐音符 mjb_result result; if(mjb_normalize(input, strlen(input), MJB_ENC_UTF_8, MJB_NORMALIZATION_NFC, MJB_ENC_UTF_8, &result) != MJB_STATUS_OK) { return 1; } // NFC: Café printf("NFC: %.*s", (int)result.output_size, result.output); if(result.transformed) { mjb_free(result.output); } ``` #### 相关函数 - [mjb_string_is_normalized](https://mojibake.zaerl.com/#mjb_string_is_normalized) - [mjb_string_filter](https://mojibake.zaerl.com/#mjb_string_filter) #### 规范 - [UAX #15: Unicode 归一化形式, Unicode 17.0.0](https://www.unicode.org/reports/tr15/tr15-57.html) --- ### mjb_string_filter ```c mjb_status mjb_string_filter( const char *buffer, size_t byte_length, mjb_encoding encoding, mjb_filter filters, mjb_encoding output_encoding, mjb_result *result ); ``` `MJB_FILTER_LIMIT_COMBINING` 会在发出的运行中,在连续第 `MJB_FILTER_MAX_COMBINING_MARKS` 个组合标记之后移除后续组合标记。这有助于减少 Zalgo 样式的文本,同时保留普通重音和堆叠标记。 #### 示例 ```c const char *mixed_whitespace = "Hello\t\t\n\nworld"; mjb_result result; if(mjb_string_filter(mixed_whitespace, strlen(mixed_whitespace), MJB_ENC_UTF_8, MJB_FILTER_COLLAPSE_SPACES, MJB_ENC_UTF_8, &result) != MJB_STATUS_OK) { return 1; } // Filtered: Hello world printf("Filtered: %.*s", (int)result.output_size, result.output); if(result.transformed) { mjb_free(result.output); } const char *controls = "\x1\x2\t\n\v\f\r\x1f"; if(mjb_string_filter(controls, strlen(controls), MJB_ENC_UTF_8, MJB_FILTER_CONTROLS, MJB_ENC_UTF_8, &result) != MJB_STATUS_OK) { return 1; } // Filtered: \t\n\v\f\r printf("Filtered: %.*s", (int)result.output_size, result.output); if(result.transformed) { mjb_free(result.output); } ``` #### 相关函数 - [mjb_normalize](https://mojibake.zaerl.com/#mjb_normalize) --- ### mjb_nfkc_casefold ```c mjb_status mjb_nfkc_casefold( const char *buffer, size_t byte_length, mjb_encoding encoding, mjb_encoding output_encoding, mjb_result *result ); ``` 应用标准的 `NFKC_Casefold` 映射并将结果归一化为 NFC。此转换执行兼容性折叠、完全默认大小写折叠以及移除默认可忽略码点。它用于标识符比较,且不依赖于区域设置。 #### 返回值 - `MJB_STATUS_OK` — 返回转换后的字符串 - `MJB_STATUS_INVALID_ARGUMENT` — `result` 为 NULL,或 `buffer` 为 NULL 且 size 非零 - `MJB_STATUS_OVERFLOW` — 输出大小会溢出 - `MJB_STATUS_NO_MEMORY` — 分配失败 #### 示例 ```c const char *input = "Stra\xC3\x9F" "e\xC2\xAD"; mjb_result result; if(mjb_nfkc_casefold(input, strlen(input), MJB_ENC_UTF_8, MJB_ENC_UTF_8, &result) != MJB_STATUS_OK) { return 1; } // strasse printf("%.*s", (int)result.output_size, result.output); mjb_result_free(&result); ``` #### 相关函数 - [mjb_normalize](https://mojibake.zaerl.com/#mjb_normalize) - [mjb_case](https://mojibake.zaerl.com/#mjb_case) - [mjb_string_is_identifier](https://mojibake.zaerl.com/#mjb_string_is_identifier) #### 规范 - [Unicode 标准第 17.0.0 版第 3.13 节:默认大小写算法](https://www.unicode.org/versions/Unicode17.0.0/core-spec/chapter-3/#G33992) - [UAX #31: Unicode 标识符和语法, Unicode 17.0.0](https://www.unicode.org/reports/tr31/tr31-43.html) - [UAX #44: Unicode 字符数据库, Unicode 17.0.0](https://www.unicode.org/reports/tr44/tr44-36.html) --- ### mjb_codepoint_encode ```c unsigned int mjb_codepoint_encode( mjb_codepoint codepoint, char *buffer, size_t byte_length, mjb_encoding encoding ); ``` #### 示例 ```c char encoded[4]; unsigned int size = mjb_codepoint_encode(0x20AC, encoded, sizeof(encoded), MJB_ENC_UTF_8); // € 符号占 3 个 UTF-8 字节 printf("%.*s sign uses %u UTF-8 bytes", (int)size, encoded, size); ``` --- ### mjb_string_convert_encoding ```c mjb_status mjb_string_convert_encoding( const char *buffer, size_t byte_length, mjb_encoding encoding, mjb_encoding output_encoding, mjb_result *result ); ``` 在支持的编码(UTF-8、UTF-16LE/BE、UTF-32LE/BE)之间转换字符串。通用 UTF-16/UTF-32 输入会将开头的 BOM 作为编码方案签名,并用于决定字节序。显式字节序输入将保留开头的 U+FEFF 作为文本。不带 BOM 的通用 UTF-16/UTF-32 输入以及通用 UTF-16/UTF-32 输出会被拒绝,因为字节序未指定。 #### 返回值 - `MJB_STATUS_OK` — 字符串已转换 - `MJB_STATUS_INVALID_ARGUMENT` — `result` 为 NULL,或 `buffer` 为 NULL 且 size 非零,或输入在源编码中无效 - `MJB_STATUS_INVALID_ENCODING` — 通用 UTF-16/UTF-32 编码未提供足够的字节序信息 - `MJB_STATUS_UNSUPPORTED` — 请求的编码转换不受支持 - `MJB_STATUS_OVERFLOW` — 输出大小会溢出 - `MJB_STATUS_NO_MEMORY` — 分配失败 #### 示例 ```c const char *input = "caf\xC3\xA9"; mjb_result result; if(mjb_string_convert_encoding(input, strlen(input), MJB_ENC_UTF_8, MJB_ENC_UTF_16LE, &result) != MJB_STATUS_OK) { return 1; } // UTF-16LE 字节数:8 printf("UTF-16LE bytes: %zu", result.output_size); mjb_result_free(&result); ``` #### 相关函数 - [mjb_string_encoding](https://mojibake.zaerl.com/#mjb_string_encoding) - [mjb_codepoint_encode](https://mojibake.zaerl.com/#mjb_codepoint_encode) --- ### mjb_case ```c mjb_status mjb_case( const char *buffer, size_t byte_length, mjb_encoding encoding, mjb_case_type type, mjb_encoding output_encoding, mjb_result *result ); ``` 将字符串转换为大写、小写、首字母大写或大小写折叠形式。应用完全大小写映射,包括特殊大小写和条件映射,因此输出长度可能与输入不同。首字母大写使用 UAX #29 的单词边界:每个单词段中的第一个字母字符被大写,该段中的后续字符被小写。大小写转换由使用 `mjb_locale_set` 设置的进程全局区域设置定制。

相似文章

Show HN: Nibble

Hacker News Top

Nibble 是一种类 C 的系统编程语言,用 3000 行 C 代码实现,无需外部依赖或堆分配即可生成 LLVM IR。它支持 defer、递归、多种类型、结构体、指针,并包含图形演示。

Show HN:一个ASCII 3D渲染引擎

Hacker News Top

GlyphCSS是一个JavaScript库,它使用ASCII字符在DOM中渲染带纹理的3D网格,支持多种3D格式,并与原生JS、React和Vue集成。