const MARKER: char = '\u{10FFFE}';
const SURROGATE_BASE: u32 = 0x10_0000;
const SURROGATES: std::ops::RangeInclusive<u32> = 0xD800..=0xDFFF;
const HIGH_SURROGATES: std::ops::RangeInclusive<u32> = 0xD800..=0xDBFF;
const LOW_SURROGATES: std::ops::RangeInclusive<u32> = 0xDC00..=0xDFFF;
pub(crate) fn push_lone_surrogate(out: &mut String, unit: u32) -> bool {
let Some(named) = SURROGATES
.contains(&unit)
.then(|| char::from_u32(SURROGATE_BASE + (unit - 0xD800)))
.flatten()
else {
return false;
};
if LOW_SURROGATES.contains(&unit)
&& let Some(high) = trailing_high_surrogate(out)
&& let Some(Ok(pair)) = char::decode_utf16([high, unit as u16]).next()
{
out.pop();
out.pop();
push_literal_char(out, pair);
return true;
}
out.push(MARKER);
out.push(named);
true
}
pub(crate) fn push_literal_text(out: &mut String, text: &str) {
let mut chars = text.chars();
if chars.next() == Some(MARKER)
&& let Some(low) = chars.next().and_then(named_surrogate)
&& LOW_SURROGATES.contains(&u32::from(low))
{
push_lone_surrogate(out, u32::from(low));
out.push_str(chars.as_str());
return;
}
out.push_str(text);
}
fn trailing_high_surrogate(out: &str) -> Option<u16> {
let mut tail = out.chars().rev();
let high = tail.next().and_then(named_surrogate)?;
if !HIGH_SURROGATES.contains(&u32::from(high)) {
return None;
}
let markers = tail.take_while(|c| *c == MARKER).count();
(markers % 2 == 1).then_some(high)
}
pub(crate) fn push_literal_char(out: &mut String, c: char) {
out.push(c);
if c == MARKER {
out.push(MARKER);
}
}
pub(crate) fn literal_units(text: &str) -> Vec<u16> {
let mut units = Vec::with_capacity(text.len());
let mut chars = text.chars();
while let Some(c) = chars.next() {
if c != MARKER {
units.extend_from_slice(c.encode_utf16(&mut [0; 2]));
continue;
}
let next = chars.next();
if let Some(unit) = next.and_then(named_surrogate) {
units.push(unit);
continue;
}
units.extend_from_slice(MARKER.encode_utf16(&mut [0; 2]));
if let Some(c) = next.filter(|c| *c != MARKER) {
units.extend_from_slice(c.encode_utf16(&mut [0; 2]));
}
}
units
}
pub(crate) fn literal_chars(text: &str) -> impl Iterator<Item = Result<char, u16>> {
char::decode_utf16(literal_units(text)).map(|c| c.map_err(|lone| lone.unpaired_surrogate()))
}
fn named_surrogate(named: char) -> Option<u16> {
let offset = u32::from(named).checked_sub(SURROGATE_BASE)?;
let unit = 0xD800 + offset;
SURROGATES.contains(&unit).then_some(unit as u16)
}
#[cfg(test)]
mod tests {
use super::*;
fn text_of(units: &[u32]) -> String {
let mut text = String::new();
for &unit in units {
if !push_lone_surrogate(&mut text, unit) {
push_literal_char(&mut text, char::from_u32(unit).unwrap());
}
}
text
}
#[test]
fn a_lone_surrogate_round_trips_to_one_unit() {
let text = text_of(&[0x61, 0xD800, 0xDFFF, 0x1F600]);
assert_eq!(
literal_units(&text),
vec![0x61, 0xD800, 0xDFFF, 0xD83D, 0xDE00]
);
}
#[test]
fn two_halves_concatenate_into_a_pair() {
let text = text_of(&[0xD83D]) + &text_of(&[0xDE00]);
assert_eq!(String::from_utf16(&literal_units(&text)).unwrap(), "😀");
}
#[test]
fn halves_written_separately_join_into_one_character() {
assert_eq!(text_of(&[0xD83D, 0xDE00]), "😀");
assert_eq!(
text_of(&[0xDE00, 0xD83D]),
text_of(&[0xDE00]) + &text_of(&[0xD83D])
);
}
#[test]
fn appended_text_joins_halves_across_the_seam() {
let mut text = text_of(&[0x61, 0xD83D]);
push_literal_text(&mut text, &text_of(&[0xDE00, 0x62]));
assert_eq!(text, "a😀b");
let mut doubled = text_of(&[0x10FFFE]);
push_literal_text(&mut doubled, &text_of(&[0x10FFFE]));
assert_eq!(doubled, text_of(&[0x10FFFE, 0x10FFFE]));
}
#[test]
fn literal_chars_report_lone_surrogates() {
let text = text_of(&[0x61, 0xD800, 0x10FFFE]);
let chars: Vec<_> = literal_chars(&text).collect();
assert_eq!(chars, vec![Ok('a'), Err(0xD800), Ok('\u{10FFFE}')]);
}
#[test]
fn an_escaped_marker_before_a_named_character_does_not_join() {
let text = text_of(&[0x10FFFE, 0x10_0000 + 0x3D, 0xDE00]);
let expected: Vec<u16> = "\u{10FFFE}\u{10003D}"
.encode_utf16()
.chain([0xDE00])
.collect();
assert_eq!(literal_units(&text), expected);
}
#[test]
fn the_marker_itself_round_trips() {
let text = text_of(&[0x10FFFE, 0xD800, 0x10FFFE, 0x10FFFF]);
let expected: Vec<u16> = "\u{10FFFE}"
.encode_utf16()
.chain([0xD800])
.chain("\u{10FFFE}\u{10FFFF}".encode_utf16())
.collect();
assert_eq!(literal_units(&text), expected);
}
#[test]
fn a_stray_marker_stands_for_itself() {
let text = "\u{10FFFE}a\u{10FFFE}";
assert_eq!(literal_units(text), text.encode_utf16().collect::<Vec<_>>());
}
#[test]
fn text_without_the_marker_is_plain_utf16() {
let text = "a\u{10FFFF}\u{100000}é";
assert_eq!(literal_units(text), text.encode_utf16().collect::<Vec<_>>());
}
}