std/unicode

Unicode beyond the bytes of a str: code points and grapheme clusters, the properties of characters, case conversion and case folding, normalization, word and line boundaries, the width text takes in a terminal, and offsets in UTF-16, as the language server protocol counts them. A str stays bytes, indexed by bytes; these functions read it as UTF-8, with U+FFFD for a byte that does not start a valid character, and the offsets they take and give are byte offsets unless the name says otherwise.

import "std/unicode"

print(graphemes("e\u{301}👍🏽!"))            // three: "é", "👍🏽", "!"
print(upper_case("straße"))                 // STRASSE
print(normalize("e\u{301}", "NFC") == "é")  // true
print(display_width("日本"))                // 4
Function Result
code_points(text), from_code_points(codes) the code points of text, as an int[], and back
code_point_at(text, offset) the code point at a byte offset and how many bytes it takes
code_point_text(code), code_point_count(text) the text of one code point, and how many a text has
graphemes(text), grapheme_count(text) the grapheme clusters: what a reader takes as one character, as a letter with its accents or an emoji with its modifiers
grapheme_boundaries(text), next_grapheme(text, offset), previous_grapheme(text, offset) where the clusters start, and the next and previous one, as a cursor moves
words(text) the words: the pieces between word boundaries that hold a letter or a digit
word_boundaries(text), next_word_boundary(text, offset), previous_word_boundary(text, offset) the word boundaries, as an editor moves by words
line_breaks(text) where lines may break, as LineBreak items with an offset and whether the break is mandatory
general_category(code), script(code), combining_class(code) "Lu", "Latin", and the canonical combining class
is_letter, is_mark, is_number, is_digit, is_punctuation, is_symbol, is_upper, is_lower, is_whitespace, is_pictographic properties of a code point
upper_case(text), lower_case(text), title_case(text) text in another case, with the mappings of more than one character, as ß to SS
fold_case(text), equal_ignoring_case(a, b) case folding, for comparing text whatever its case
normalize(text, form), is_normalized(text, form) the forms "NFC", "NFD", "NFKC", and "NFKD"
code_point_width(code), display_width(text) the columns a terminal gives a character and a text
utf16_length(text), utf16_offset(text, offset), byte_offset_from_utf16(text, offset) UTF-16 lengths and offsets
code_point_offset(text, offset), byte_offset_from_code_point(text, index) code point offsets

The boundaries follow UAX 29 for grapheme clusters and words and UAX 14 for lines, without tailoring; normalization follows UAX 15. A capital sigma at the end of a word is lowercase ς; other mappings that depend on the language, as Turkish dotted and dotless i, are not made. display_width counts a grapheme cluster at a time: wide characters, such as ideographs and most emoji, take 2 columns, marks and characters that are not shown take none, an emoji asked for with U+FE0F takes 2, and one asked for as text with U+FE0E takes 1.

The tables come from the Unicode Character Database of the version UNICODE_VERSION gives, 17.0.0, through tools/unicode.cz, and are read the first time a function needs them.