Show HN: Mojibake – 一个用 C 语言编写的底层 Unicode 库
Show HN: Mojibake – A low-level Unicode library written in C

原始链接: https://mojibake.zaerl.com/

Mojibake 是一个轻量级、高性能的 Unicode 处理库,采用 C11 编写并兼容 C++17。该库以 MIT 协议开源,旨在提供易用性与可移植性,仅由两个文件(`mojibake.c` 和 `mojibake.h`)组成,且没有任何外部依赖。 该库实现了完整的 Unicode 标准算法,包括规范化(NFC/NFD/NFKC/NFKD)、大小写转换、分段(字位、词、句、断行)、双向文本(Bidi)以及排序规则。它还具备用于文本分析、Emoji 序列检测、显示宽度计算以及侧重于安全性的标识符/混淆字符验证的强大工具。 Mojibake 支持多种编码(UTF-8/16/32),提供可自定义的内存分配,并包含构建时功能标志以最小化二进制体积。它通过了官方 Unicode 一致性测试套件的严格测试,并使用 libFuzzer 进行了模糊测试,确保了可靠性与安全性。凭借跨平台兼容性(包括 WASM 和 TypeScript 支持),Mojibake 为需要在 C 或 C++ 项目中进行可靠 Unicode 处理的开发者提供了一个全面、自包含的解决方案。

“Mojibake”是一个新型轻量级 C 语言 Unicode 库,旨在作为现有解决方案的替代方案。该项目仅包含两个文件——`mojibake.h` 和 `mojibake.c`,实现了必要的 Unicode 算法,包括规范化、大小写转换、分段、双向文本、整理和易混淆字符处理。 该库支持跨平台,已在 Linux、macOS、Windows 11 和 BSD 系列系统上通过验证测试。用户可以通过基于 WASM 的在线演示进行探索,其中展示了公共 API 和文档。开发者欢迎社区通过项目的 GitHub 仓库进行贡献和参与。
相关文章

原文

Mojibake is a low-level Unicode 17 text-processing library written in C11 and compatible with C++17. It is released under the MIT License.

Usage

You don't need to install anything. There are two files (mojibake.c, mojibake.h) to add to your C/C++ project. Download it here mojibake-amalgamation-027.zip

Examples of normalization, characters count and NFKC casefold.

#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;

    
    if(mjb_normalize(input, length, MJB_ENC_UTF_8, MJB_NORMALIZATION_NFC, MJB_ENC_UTF_8,
        &result) != MJB_STATUS_OK) {
        return 1;
    }

    
    print_string(input, length);

    
    print_string(result.output, result.output_size);

    const char *mojibake = "文字化け";
    length = strlen(mojibake);

    
    
    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";

    
    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");
}

This output:

Cafe<CC><81>
Caf<C3><A9>
"文字化け" encoded in UTF-8 is 12 bytes long, and 4 characters long
Straße -> strasse

Mojibake aims to be:

  1. Small
  2. Easy to use
  3. Fast
  4. Self-contained

Mojibake do:

  1. Run in all modern OSes (Linux, macOS, FreeBSD, OpenBSD, NetBSD, Windows 10/11)
  2. Pass the official Unicode test suites for supported algorithms
  3. Implement all Unicode standard algorithms
  4. Satisfy all Unicode Conformance Requirements

Feature highlights

All the C files, together with the Unicode data tables, are concatenated into a single large file and header: mojibake.c and mojibake.h. Zero dependencies.

Text transformation

  • Normalization: NFC/NFD/NFKC/NFKD (mjb_normalize), identifier-oriented NFKC case folding (mjb_nfkc_casefold), plus a fast quick-check (mjb_string_is_normalized) (UAX #15, Unicode 17.0.0)
  • Case conversion: uppercase, lowercase, titlecase, and case folding with full special-casing and conditional mappings (mjb_case)
  • Filtering: strip controls, spaces, or numeric characters while normalizing (mjb_string_filter)

Text analysis

  • Character database: every Unicode Character Database property: category, script and Script_Extensions, block, plane, numeric value, name (mjb_codepoint_character, mjb_codepoint_script_extensions)
  • Segmentation: grapheme clusters, words, sentences, and line-break opportunities (UAX #29, Unicode 17.0.0, UAX #14, Unicode 17.0.0)
  • Bidirectional text: full Unicode Bidirectional Algorithm: paragraph resolution, line reordering, runs (UAX #9, Unicode 17.0.0)
  • Emoji: codepoint properties, sequence analysis, RGI emoji detection
  • Display width: East Asian width and terminal display width, with width-aware truncation (mjb_display_width, mjb_truncate_width)

Sorting and comparison

  • Collation: Unicode Collation Algorithm string comparison and sort keys, in shifted and non-ignorable modes (mjb_string_compare, mjb_collation_key, UTS #10, Unicode 17.0.0)

Security

  • Confusable detection: generate reusable skeletons and check if strings are visually confusable (mjb_confusable_skeleton, mjb_string_is_confusable, UTS #39, Unicode 17.0.0)
  • Identifier validation: XID/ID checks for parser and compiler authors (mjb_string_is_identifier, UAX #31, Unicode 17.0.0)

Integration

  • Encodings: the API accepts and outputs UTF-8, UTF-16LE, UTF-16BE, UTF-32LE, UTF-32BE strings, with encoding detection and conversion (mjb_string_encoding, mjb_string_convert_encoding)
  • Parsing and string functions: character-by-character iteration (mjb_next_character) and standard C string.h-style helpers (mjb_string_length, and others)
  • Locales: strict BCP 47 language tag parsing (mjb_locale_parse)
  • Embeddable: custom allocators (mjb_set_memory_functions), build-time feature flags to trim table size, a C++17 wrapper (src/cpp/mojibake.hpp), a CLI tool (src/shell), and a WASM + TypeScript API (src/api)
  • Tested: Mojibake uses Attractor as test suite and run 1.5M+ assertions including the official Unicode conformance suites for supported algorithms
  • Fuzz Mojibake is fuzzed with libFuzzer over untrusted byte input
  • AddressSanitizer and UBSan clean

Build-time features

Mojibake can compile out optional feature tables to reduce binary size. Feature macros default to enabled.

  • #define MJB_FEATURE_CHARACTER_NAMES controls the Unicode character-name tables used by mjb_codepoint_character(...) to fill mjb_character.name. When disabled, the tables are not compiled and mjb_character.name is reported as Codepoint U+XXXX. This will redude the output of ~30%.

With CMake:

cmake -S . -B build-no-name -DMJB_FEATURE_CHARACTER_NAMES=OFF
cmake --build build-no-name

With the provided Makefile:

make build BUILD_DIR=build-no-name FEATURE_CHARACTER_NAMES=OFF
make test-no-names

API documentation

See API.md or the site for the detailed documentation.

CLI

The src/shell directory builds the mojibake CLI used to test the library. Example usage:

# This outputs "NFC: Café", e + ◌́ -> é
mojibake nfc $'Cafe\u0301'

# The output an emoji sequence [1] Basic, [2] Fully-qualified of two characters U+263A U+FE0F
mojibake emoji "☺️"

Building from source and contributing

See CONTRIBUTING.md for instructions.

Licenses

Mojibake is released under the MIT License (see LICENSE).

Legalese

Here you can find the very detailed and boring informations needed to have this library conformant to the Unicode standard, or at least what I got, at CONFORMANCE_REQUIREMENTS.md

Thanks

Mojibake is built using the work of extraordinary individuals and teams.

  1. Unicode Character Database - Copyright © 1991-2026 Unicode, Inc. (see license.txt)
  2. Unicode CLDR Project - Copyright © 2004-2026 Unicode, Inc. (see LICENSE)
联系我们 contact @ memedata.com