Decode DXF surrogate pairs

Found while testing crates/cad/src/import/text.rs:80

Co-Authored-By: GPT-5.6-Luna <noreply@openai.com>
Co-Authored-By: GPT-6-Luna <noreply@openai.com>
This commit is contained in:
Max Mittelstädt
2026-11-02 14:36:14 +07:00
committed by bflatastic
co-authored by GPT-5.6-Luna
parent 743f8c526b
commit 9b93c609b9
+35 -4
View File
@@ -66,6 +66,31 @@ fn until_semicolon(chars: &mut std::iter::Peekable<std::str::Chars<'_>>) -> Stri
/// and caret escapes: `^ ` is a caret, `^I` a tab; other control characters are dropped. In
/// multiline text (`mtext`) `\\` is an escaped backslash and is kept as is, so that a `U+` after
/// it stays literal, and the `^` in a stacked fraction `\Snum^den;` is left for the caller.
fn unicode_escape(after: &str) -> Option<(char, usize)> {
let hex = after.strip_prefix("U+").or_else(|| after.strip_prefix("u+"))?.get(..4)?;
if !hex.bytes().all(|b| b.is_ascii_hexdigit()) {
return None;
}
let value = u32::from_str_radix(hex, 16).ok()?;
if (0xD800..=0xDBFF).contains(&value) {
let next = after.get(6..)?;
let low = next.strip_prefix("\\U+").or_else(|| next.strip_prefix("\\u+"))?.get(..4)?;
if !low.bytes().all(|b| b.is_ascii_hexdigit()) {
return None;
}
let low = u32::from_str_radix(low, 16).ok()?;
if !(0xDC00..=0xDFFF).contains(&low) {
return None;
}
let scalar = 0x1_0000 + ((value - 0xD800) << 10) + (low - 0xDC00);
return char::from_u32(scalar).map(|ch| (ch, 13));
}
if (0xDC00..=0xDFFF).contains(&value) {
return None;
}
char::from_u32(value).map(|ch| (ch, 6))
}
fn escapes(s: &str, mtext: bool) -> String {
let mut out = String::with_capacity(s.len());
let mut rest = s;
@@ -77,12 +102,10 @@ fn escapes(s: &str, mtext: bool) -> String {
continue;
}
if c == '\\'
&& let Some(hex) = after.strip_prefix("U+").or_else(|| after.strip_prefix("u+")).and_then(|h| h.get(..4))
&& hex.bytes().all(|b| b.is_ascii_hexdigit())
&& let Some(ch) = u32::from_str_radix(hex, 16).ok().and_then(char::from_u32)
&& let Some((ch, consumed)) = unicode_escape(after)
{
out.push(ch);
rest = after.get(6..).unwrap_or("");
rest = after.get(consumed..).unwrap_or("");
continue;
}
if mtext
@@ -161,6 +184,14 @@ mod tests {
assert_eq!(plain("\\U+00C9"), "É");
}
#[test]
fn unicode_escape_decodes_surrogate_pairs() {
assert_eq!(plain("\\U+D83D\\U+DE00"), "😀");
assert_eq!(plain("\\U+D83D"), "\\U+D83D");
assert_eq!(plain("\\U+DE00"), "\\U+DE00");
assert_eq!(plain("\\U+D83D\\U+0041"), "\\U+D83DA");
}
#[test]
fn control_codes_and_escapes() {
assert_eq!(plain("45%%d %%p0.1 %%c20 100%%% %%uunder%%u"), "45° ±0.1 ⌀20 100% under");