Skip to content

telesvar/uwidth

Repository files navigation

uwidth

uwidth is a small C library for measuring Unicode text one grapheme cluster at a time.

Pass decoded Unicode scalar values to it as a stream. The library reports each completed cluster with a width of 0, 1, or 2. Controls are reported separately so the caller can choose how to handle them. The library does not decode UTF-8 or any other text encoding.

The generated tables use official Unicode 17.0.0 data. They cover combining characters, canonical decomposition, CJK width, emoji, flags, keycaps, and text presentation.

Using the library

Copy uwidth.h and uwidth.c into your project. The runtime does not allocate memory and has no dependencies. It uses C99.

Choose a width profile when you initialize the state:

  • uwidth_profile_narrow gives East Asian Ambiguous characters width 1
  • uwidth_profile_east_asian gives them width 2

Each input must be a Unicode scalar value, which means any code point other than a surrogate. Noncharacters are valid inputs.

uwidth_push consumes one scalar. It returns uwidth_event_none while a cluster is pending, uwidth_event_cluster with a width, or uwidth_event_control with a control scalar. A new scalar can complete the previous cluster. Call uwidth_finish after the last scalar.

This example ignores controls:

#include "uwidth.h"

static unsigned long measure(
    const Uwidth_Code_Point *text,
    unsigned int length
)
{
    Uwidth_State state;
    Uwidth_Event event;
    Uwidth_Event_Kind kind;
    unsigned int index;
    unsigned long width;

    width = 0UL;
    uwidth_init(&state, uwidth_profile_narrow);
    for (index = 0U; index < length; ++index) {
        kind = uwidth_push(&state, text[index], &event);
        if (kind == uwidth_event_cluster) {
            width += event.width;
        }
    }
    if (uwidth_finish(&state, &event) == uwidth_event_cluster) {
        width += event.width;
    }
    return width;
}

General Category Cc characters produce control events. CRLF is one control cluster whose reported scalar is CR. uwidth_reset discards pending input and keeps the selected profile. Copy the complete state when you need two independent continuations and do not inspect its words.

The UWIDTH_VERSION_* macros report the library version. The UWIDTH_UNICODE_VERSION_* macros report the Unicode data version.

Unicode model

The library follows UAX #29 for grapheme boundaries and UAX #15 for canonical decomposition. Ordinary width comes from East Asian Width. Emoji and text presentation change width only when the input matches an exact official sequence.

The result is deterministic. A particular font or terminal can still display the same text differently.

Generating and testing

The generator requires Python 3.9 or later.

python3 generate.py
python3 generate.py --check

Unicode files are downloaded from unicode.org and cached in .unicode-data/17.0.0. Their SHA-256 hashes are pinned in the generator. --check verifies the derived model, encoded tables, official conformance records, and generated source. Use --data-dir PATH for another cache or --fetch-only to download the files without generating source.

Build the compact test with:

cc -std=c99 -pedantic -Wall -Wextra -Werror -O2 \
    uwidth.c uwidth_test.c -o uwidth_test
./uwidth_test

Sources and licenses

The model follows UAX #11, UAX #15, UAX #29, and UTS #51.

  • Generated C code and the public header: 0BSD
  • Generator and runtime template: Apache-2.0 OR MIT
  • Unicode data: Unicode License

About

Unicode text width for terminals, in C

Resources

License

0BSD and 2 other licenses found

Licenses found

0BSD
LICENSE-0BSD
Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Stars

15 stars

Watchers

2 watching

Forks

Contributors