Skip to main content

org_gdocs/google/
utf16.rs

1//! UTF-16 code-unit measurement.
2//!
3//! The Google Docs API expresses every `Location.index` and range bound in
4//! UTF-16 code units (the JavaScript string convention), not bytes or Unicode
5//! scalar values. This module is the single authority for that arithmetic
6//! (domain invariant DI-7); other modules MUST route length calculations through
7//! it rather than using `str::len()` or `chars().count()`.
8
9/// Count the UTF-16 code units in `text`.
10///
11/// Astral-plane scalar values (e.g. most emoji) count as two code units because
12/// they encode as a surrogate pair; values in the Basic Multilingual Plane count
13/// as one.
14#[must_use]
15pub fn len_utf16(text: &str) -> usize {
16    text.chars().map(char::len_utf16).sum()
17}
18
19#[cfg(test)]
20mod tests {
21    use super::len_utf16;
22
23    #[test]
24    fn ascii_is_one_unit_each() {
25        assert_eq!(len_utf16("hello"), 5);
26        assert_eq!(len_utf16(""), 0);
27    }
28
29    #[test]
30    fn bmp_scalar_is_one_unit() {
31        // U+00E9 LATIN SMALL LETTER E WITH ACUTE: 2 UTF-8 bytes, 1 UTF-16 unit.
32        assert_eq!("Ć©".len(), 2);
33        assert_eq!(len_utf16("Ć©"), 1);
34    }
35
36    #[test]
37    fn astral_scalar_is_two_units() {
38        // U+1F600 GRINNING FACE: 4 UTF-8 bytes, surrogate pair = 2 UTF-16 units.
39        assert_eq!("šŸ˜€".len(), 4);
40        assert_eq!(len_utf16("šŸ˜€"), 2);
41    }
42
43    #[test]
44    fn mixed_string_sums_per_scalar() {
45        // 'a' (1) + 'Ć©' (1) + 'šŸ˜€' (2) + 'b' (1) = 5.
46        assert_eq!(len_utf16("aĆ©šŸ˜€b"), 5);
47    }
48}