Show HN: Mojibake – 一个用C语言编写的底层Unicode库
摘要
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: Mochi.js:专为 Bun 原生开发的高保真浏览器自动化库
Mochi.js 是一个新的开源浏览器自动化库,专为 Bun 运行时原生构建,旨在通过关系一致性、原生 Chromium 获取和行为合成来绕过检测机制。
Show HN: Nibble
Nibble 是一种类 C 的系统编程语言,用 3000 行 C 代码实现,无需外部依赖或堆分配即可生成 LLVM IR。它支持 defer、递归、多种类型、结构体、指针,并包含图形演示。
H2JVM - 用于编写JVM字节码的Haskell库
H2JVM 是一个Haskell库,允许开发者直接在Haskell中编写JVM字节码,支持底层JVM操作。
Show HN:一个ASCII 3D渲染引擎
GlyphCSS是一个JavaScript库,它使用ASCII字符在DOM中渲染带纹理的3D网格,支持多种3D格式,并与原生JS、React和Vue集成。
Hax – 一个用 C 编写的极简、终端原生编码代理
Hax 是一个用 C 编写的极简、终端原生编码代理,设计轻量、内存高效,对本地 LLM 使用友好,并支持多种提供商。