segment.h finds the boundaries of grapheme clusters, words and sentences (UAX #29) and the line break opportunities (UAX #14). All four use one iterator type, muniSegmentIterator, a plain struct the caller keeps on the stack that needs no cleanup. A copy of an initialized iterator is one too, and every call refuses an iterator no init call set up, such as a zeroed one, with muni_errorInvalid.
A user perceives a grapheme cluster as one character: an emoji family joined by zero width joiners, a flag of two regional indicators, a letter with its accents, a Hindi conjunct. Caret movement, selection and backspace must never split one. Word boundaries serve double-click selection and word-by-word movement; sentence boundaries serve sentence selection and text to speech.
An iterator reports each boundary after the start of the text, the last one being the end of the text; empty text has none. Here each cluster goes to the program's UseCluster:
muniSegmentIterator iterator;
if (muniInitGraphemeIterator(&iterator, text, length, false) == muni_success)
{
size_t start = 0;
size_t end = 0;
while (muniSegmentIterator_NextBreak(&iterator, &end) == muni_success)
{
UseCluster(text + start, end - start);
start = end;
}
}
muniInitWordIterator and muniInitSentenceIterator work the same way. After each boundary of a word iterator, muniSegmentIterator_GetWordKind tells what the segment before it holds: no word (spaces, punctuation, symbols), a number, letters, kana or ideographs, the highest among its code points. ICU's word rule statuses agree on a segment of letters or digits alone; on a mixed segment ICU reports the rule it matched last, so it calls "ab12" a number. Word movement and double-click selection skip the segments of kind muni_wordNone; letters with digits count as letters, and a superscript digit such as "²" is no number. muniFindGraphemeBreaks, muniFindWordBreaks and muniFindSentenceBreaks write every boundary of a whole text into an array instead, which is faster when the text is at hand.
muniMeasureUtf8 walks UTF-8 text by a unit, muni_unitCodePoint, muni_unitGraphemeCluster or muni_unitUtf16, and stops at the last boundary within both a byte limit and a unit limit, giving the prefix's length in bytes and in units. A limit that falls inside a code point or a cluster ends the prefix at the boundary before it. One call serves four uses:
SIZE_MAX).A line iterator reports each position where a line may end, and whether it must: after a line feed, a paragraph separator and the like, and at the end of the text.
muniSegmentIterator lines;
if (muniInitLineIterator(&lines, text, length, false) == muni_success)
{
size_t end = 0;
bool mandatory = false;
while (muniSegmentIterator_NextLineBreak(&lines, &end, &mandatory) == muni_success)
{
// a line may end at end; it must when mandatory is true
}
}
A layout engine measures the text between opportunities and ends each line at the last opportunity that fits, or at the first mandatory one. Spaces before an opportunity belong to the line before it and are not drawn at its end. muniFindLineBreaks fills arrays of offsets and mandatory flags.
A line iterator breaks at one of the strictness levels of CSS's line-break property, set before its first break. Normal, the default, follows UAX #14. Strict breaks less in Chinese and Japanese, and loose breaks more: before the small kana and the prolonged sound mark (Line_Break=CJ), before iteration marks, between characters such as two ellipses and before U+2010 and U+2013 after an ideograph, and in Chinese and Japanese also before centered punctuation, before a postfix and after a prefix that is wide, fullwidth or of ambiguous width, such as "%" or "€". Anywhere breaks between any two grapheme clusters, for text such as long identifiers or URLs that would otherwise overflow. Some of these rules apply only to Chinese and Japanese, so the iterator needs the text's writing system, which the caller knows from its language:
if (muniSegmentIterator_SetLineBreakStrictness(&lines, muni_lineBreakLoose) != muni_success ||
muniSegmentIterator_SetWritingSystem(&lines, muni_writingChineseJapanese) != muni_success)
{
// lines is not a line iterator, or has already started
}
The added breaks yield to what UAX #14 glues: a word joiner or no-break space, an opening bracket or quotation mark and what follows it, and the hard breaks, which stay mandatory at every level.
The levels follow CSS Text 3 as its Candidate Recommendation Draft of 14 August 2026 words them, so they differ from ICU's line breaking with the same @lb= keyword. ICU applies its Chinese and Japanese rules to Korean too; CSS Text 3 limits them to Chinese and Japanese, so Korean text, which takes muni_writingOther, breaks differently from ICU's Korean locale.
An editor asks about one position at a time: Backspace removes the cluster that ends at the caret, the arrow keys move to the cluster or word boundary before or after it, and a click selects the word under the pointer. muniSegmentIterator_FindBreaksAround answers that from an iterator that holds the whole text, without stepping from its start: it gives the last boundary at or before an offset and the first after it, the start of the text and its end standing in where there is none. Here Backspace removes the cluster before the caret, and the arrow keys move by cluster:
size_t before = 0;
size_t after = 0;
if (caret > 0 &&
muniSegmentIterator_FindBreaksAround(&clusters, caret - 1, &before, &after) == muni_success)
{
RemoveText(before, caret - before);
}
if (muniSegmentIterator_FindBreaksAround(&clusters, caret, &before, &after) == muni_success)
{
right = after;
}
if (caret > 0 &&
muniSegmentIterator_FindBreaksAround(&clusters, caret - 1, &before, &after) == muni_success)
{
left = before;
}
The call backs up from the offset to a boundary the rules make whatever came before, and reads on from there, so in ordinary text it reads a few code points: for grapheme clusters, words and lines, the space or letter before the offset almost always is one. Sentences restart after a terminator, a terminator and a space, or a paragraph separator, and text with no such boundary at all, such as a run of SA text with a word segmenter set or one base with many marks, is read back to its start. The answers are those the iterator reports stepping from the start, with the same kind and settings, class overrides and word segmenter included. The call leaves the iterator as it was, wherever its own iteration stands; it refuses one given its text in pieces with muni_errorState.
These scripts write words without spaces (Line_Break class SA), so breaking inside them needs a dictionary, which the library does not carry. By default, as rule LB1 of UAX #14 allows, a run of SA text has no line break inside it, and word segmentation gives one word per cluster.
A program with a word segmenter hands it to a line or word iterator. The segmenter receives a maximal run of SA text and an offset inside it, and answers with the offset of the first word break after it, or the run's length when there is none:
static size_t BreakThai(void* context, const char* run, size_t length, size_t from)
{
const ThaiDictionary* dictionary = context;
return FindWordBreak(dictionary, run, length, from);
}
It is set before the iterator is asked for its first break:
if (muniSegmentIterator_SetComplexBreaker(&lines, BreakThai, dictionary) != muni_success)
{
// lines is not a line or word iterator, or has already started
}
The iterator then never breaks before a combining mark, whatever the segmenter answers, and a word iterator reports whole words. The segmenter must give the same answer for the same run and offset each time. It may not use its own iterator: while it runs, the setters, muniSegmentIterator_Feed and the muniNext calls on that iterator return muni_errorBusy and change nothing, and it must not initialize the iterator again.
The classes Unicode assigns are the default. Some languages' conventions, as CLDR records them, give a few code points another class: Greek ends a sentence at the semicolon and at U+037E GREEK QUESTION MARK, and POSIX English keeps the full stop out of words while numbers keep it. A word or sentence iterator takes a table of such overrides before its first boundary:
static const muniBreakClassOverride greek[] = {
{0x003B, muni_sbSTerm},
{0x037E, muni_sbSTerm},
};
muniSegmentIterator sentences;
if (muniInitSentenceIterator(&sentences, text, length, false) == muni_success &&
muniSegmentIterator_SetBreakClassOverrides(&sentences, greek, 2) == muni_success)
{
// a sentence now ends after "; " as after "? "
}
The entries are in strictly increasing order of code point, and their values are Word_Break classes (muni_wb...) for a word iterator and Sentence_Break classes (muni_sb...) for a sentence iterator. The table is read in place, so it must stay unchanged while the iterator is used. Conventions that only remove boundaries, such as CLDR's abbreviations after which a sentence goes on, need no override: the caller skips those boundaries.
An iterator initialized with moreFollows set stops with muni_needMoreText when the next boundary depends on text it has not seen. muniSegmentIterator_Feed then hands it the next piece, which may start in the middle of a UTF-8 sequence. The iterator keeps no pointer to earlier pieces, so a stream can be segmented through a small buffer, and offsets stay relative to the start of the whole text. Feeding before the iterator asked, or after the last piece, returns muni_errorState, since the unread rest of the piece would be lost; so does a setting such as muniSegmentIterator_SetLineBreakStrictness once the iterator was asked for a break.
muni_needMoreText comes back before a boundary that looks decided.