Getting started

Maul Unicode is a C library. It builds with CMake, installs as a CMake package and a pkg-config module, and needs nothing but a C compiler and the C standard library.

Requirements

The public headers are plain C17 and C++17, so programs that include them need neither a C23 compiler nor C at all.

Build and install

cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
ctest --test-dir build
cmake --install build --prefix /path/to/prefix

Then, from CMake:

find_package(maul-unicode REQUIRED CONFIG)
target_link_libraries(app PRIVATE maul-unicode::maul-unicode)

or through pkg-config, pkg-config --cflags --libs maul-unicode. A project can also add the source tree with add_subdirectory; tests, samples, benchmarks and the generator are then off by default.

samples/minimal/ is a complete project that uses only the installed package.

Build options

Option Default Effect
MAUL_UNICODE_BUILD_SHARED OFF A shared library instead of a static one.
MAUL_UNICODE_NORMALIZATION ON Normalization, with about 35 KB of tables.
MAUL_UNICODE_CASE ON Case mapping and folding, with about 10 KB of tables.
MAUL_UNICODE_SECURITY ON Confusable skeletons and restriction levels, with about 36 KB of tables. Needs normalization.
MAUL_UNICODE_HARFBUZZ OFF The separate library maul-unicode-harfbuzz, see Script runs and shaping.

A component left out keeps its header, so a call into it fails to link instead of failing at run time. muniGetNfkcCasefold needs both normalization and case.

The model

Every answer comes from one Unicode version, 18.0.0. muniGetUnicodeVersion tells a program which version it linked. Stored results (skeletons, normalized and case-folded keys) depend on more than the Unicode version, since a release may also fix a rule: store muniGetResultsVersion beside them, and recompute them when it changes. It moves up with every change to what normalization, NFKC_Casefold, case conversion or skeletons give, or to the verdicts of muniCheckIdentifier, muniGetRestrictionLevel, muniCheckMixedNumbers and muniGetResolvedScripts, and with nothing else.

Results and buffers

A fallible function returns a muniResult: muni_success (0), a positive outcome such as muni_done or muni_needMoreText, or a negative error. Functions over text return a muniTextResult: the status and, on an error, the byte offset where the offending input starts. muniGetResultName names any result for logs.

Functions that write text take an output buffer and its capacity, write what fits, and report through neededOut how many units the whole result needs. A result that does not fit returns muni_errorCapacity; one whose size does not fit in a size_t, which can happen on 32-bit targets, reports SIZE_MAX as its size and returns muni_errorCapacity too. A NULL buffer with capacity 0 measures:

size_t needed = 0;
muniTextResult result =
    muniNormalize(text, length, muni_nfc, muni_convertStrict, nullptr, 0, &needed);
// result.status is muni_errorCapacity (or muni_success for empty output);
// allocate needed bytes and call again.

A call that fails leaves its outputs as they were, unless its header says what the failure leaves in them, such as the size an output that is too small needs, or the text converted before an error. muni_errorInvalid leaves them as they were too, with one exception: muniInvertBidiMap checks the map in its output, so when it refuses a map that is not a permutation (an entry out of range or one that repeats an earlier one), it sets every entry of inverseOut to 0. An output must not overlap an input. The calls whose output has the type of an input refuse an overlap with muni_errorInvalid, so text cannot be normalized, case-mapped or folded in place: write the result to another buffer. They are muniNormalize, muniConvertCase, muniGetNfkcCasefold, muniGetSkeleton and muniInvertBidiMap.

Each status a function can return is listed in its header and in the API reference.

Samples

The samples build with the library (-DMAUL_UNICODE_BUILD_SAMPLES=OFF turns them off) and print something to check: