Packages

encodings/utf16

Measure, index, order and case-map strings the way JavaScript does, in UTF-16 code units with 1-based positions.

import "std:encodings/utf16"

OSL strings count characters: "😀".len is 1. JavaScript, Java, C# and the Windows API count UTF-16 code units, where that emoji takes two. When you port code from those languages or must match offsets and orderings produced by them, this package gives you the same answers.

Use it when you read positions from JavaScript (an editor, a language server, a browser), must match JavaScript's <, or need its exact casing. Otherwise the built-in string methods are the right tool.

osl
import "std:encodings/utf16"

The import name is utf16.

Length in code units#

Characters in the basic plane take one code unit. Characters outside it, such as emoji, take two (a surrogate pair).

main.osl
import "std:encodings/utf16"

string s = "a😀b"
log s.len
log utf16.length(s)
log utf16.length("") utf16.length("héllo")
Click the code to edit it

Try it: measure "héllo" and "日本語". Which are one unit per character?

Positions#

Positions are counted in code units and start at 1, like the rest of OSL. index finds where a part first occurs and returns 0 when it doesn't. at returns the code unit at a position, and trim returns the code units from one position to another, both ends included.

main.osl
import "std:encodings/utf16"

string s = "a😀b"
log utf16.index(s, "b") utf16.index(s, "z")
log utf16.at(s, 1) utf16.at(s, 4)
log utf16.trim(s, 2, 3)
log utf16.trim(s, 1, 1)
Click the code to edit it

"b" is at position 4, not 3, because the emoji before it takes two units. A position that falls in the middle of a pair gives half a character:

main.osl
import "std:encodings/utf16"

string half = utf16.at("😀", 1)
log utf16.length(half)
log utf16.join(half, utf16.at("😀", 2)) == "😀"
Click the code to edit it

join glues strings together like JavaScript's +, so two halves of a pair become the whole character again.

Ordering#

compare(a, b) returns -1, 0 or 1 by comparing code units, as JavaScript's < does. This differs from OSL's own ordering for characters beyond the basic plane: a high surrogate (the first half of an emoji) sorts before characters in the range U+E000 to U+FFFF.

main.osl
import "std:encodings/utf16"

log utf16.compare("apple", "banana") utf16.compare("b", "a") utf16.compare("a", "a")

string wide = "\u{FF5E}"
string emoji = "\u{1F600}"
log "OSL:" (wide < emoji)
log "UTF-16:" utf16.compare(wide, emoji)
Click the code to edit it

Case#

upper and lower apply full Unicode case mapping, like toUpperCase and toLowerCase. Note that ß becomes SS, which changes the length.

main.osl
import "std:encodings/utf16"

log utf16.upper("straße") utf16.lower("ÀLVARO")
log utf16.length("straße") utf16.length(utf16.upper("straße"))
Click the code to edit it

Case-insensitive checks#

Three helpers compare as if both sides were lower-cased: compareLower, containsLower and isSameLower.

main.osl
import "std:encodings/utf16"

log utf16.containsLower("Hello World", "WORLD")
log utf16.compareLower("Apple", "banana")
log utf16.isSameLower("HeLLo", "hello")
Click the code to edit it

isSameLower(text, lowered) expects the second argument to be lower-case already, so you can lower-case a search term once and test many strings against it.

Quick check

What is utf16.length("😀")?

Your turn

Port a JavaScript offset

An editor reports position 4 in "😀 hi". Print the letter there, then the OSL length and the UTF-16 length.

main.osl
Ctrl Enter runs
Expected output
h
4 5

API reference#

12 members, generated from the std:encodings/utf16 source of OSL 9.3.0.

Functions

#utf16.at(string text, int index) string

at returns the UTF-16 code unit at a 1-based index as a string, like text[index] for code units: half of a surrogate pair comes back as a lone surrogate. Returns "" when index is outside the string, as text[index] does.

#utf16.compare(string a, string b) int

compare orders a and b by UTF-16 code units, as JavaScript's < does, and returns -1 when a sorts first, 0 when they are equal and 1 when b sorts first.

#utf16.compareLower(string a, string b) int

compareLower orders a and b as compare(lower(a), lower(b)) does, which is JavaScript's a.toLowerCase() < b.toLowerCase(), without building either lowered copy when both are ASCII.

#utf16.containsLower(string text, string part) boolean

containsLower reports whether lower(text) contains lower(part), as JavaScript's text.toLowerCase().includes(part.toLowerCase()) does, without building either lowered copy when both are ASCII.

#utf16.index(string text, string part) int

index returns the 1-based code unit position where part first occurs in text, or 0 when it doesn't occur, as text.index does. An empty part is found at 1.

#utf16.isSameLower(string text, string lowered) boolean

isSameLower reports whether utf16.lower(text) equals lowered, which is JavaScript's text.toLowerCase() === lowered, without building the lowered copy for ASCII text.

#utf16.join(string a, string b) string

join concatenates a and b like JavaScript's +: a lone high surrogate at the end of a and a lone low surrogate at the start of b become one character again.

#utf16.length(string text) int

length returns the length of text in UTF-16 code units, which is JavaScript's string.length: an emoji outside the Basic Multilingual Plane counts as 2.

#utf16.lower(string text) string

lower returns text in lower case like JavaScript's toLowerCase, with full Unicode case mapping (final sigma included). Lone surrogates are kept.

#utf16.sameLower(string s, string lowered) boolean

Deprecated. Deprecated: use utf16.isSameLower(text, lowered) instead.

sameLower reports whether utf16.lower(s) equals lowered.

#utf16.trim(string text, int start, int end) string

trim returns the code units of text from start to end, both 1-based and inclusive, like string.trim(start, end) counted in UTF-16 code units. A negative position counts back from the end (-1 is the last code unit), positions past either end are clamped, and reversed positions are swapped, so trim never fails.

#utf16.upper(string text) string

upper returns text in upper case like JavaScript's toUpperCase, with full Unicode case mapping ("ß" becomes "SS"). Lone surrogates are kept.