Keep the denominator of DXF MTEXT stacked fractions written with ^

Found while testing crates/cad/src/import/text.rs:14-27 (mtext calls escapes at :14, which treats `^2` as a caret escape and drops it; the `\S` branch at :25 expects the `^`)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
NetterSilver
2026-10-24 20:45:46 +07:00
committed by bflatastic
co-authored by Claude Haiku 5.5
parent aaf336ba31
commit 41c26c6997
+18 -5
View File
@@ -4,13 +4,13 @@
/// A single-line text value (TEXT, ATTRIB) as plain text.
pub(crate) fn plain(s: &str) -> String {
percent_codes(&escapes(s))
percent_codes(&escapes(s, false))
}
/// A multiline text value (MTEXT) as plain text with `\n` between paragraphs, and the font family
/// its first font change names.
pub(crate) fn mtext(s: &str) -> (String, Option<String>) {
let s = escapes(s);
let s = escapes(s, true);
let mut out = String::with_capacity(s.len());
let mut family = None;
let mut chars = s.chars().peekable();
@@ -25,7 +25,7 @@ pub(crate) fn mtext(s: &str) -> (String, Option<String>) {
Some('S') => {
// A stacked fraction `\Snum^den;` (or / or #): written as num/den.
let arg = until_semicolon(&mut chars);
out.push_str(&arg.replace(['^', '#'], "/"));
out.push_str(&arg.replace("^ ", "^").replace(['^', '#'], "/"));
}
Some(f @ ('f' | 'F')) => {
let arg = until_semicolon(&mut chars);
@@ -63,8 +63,9 @@ fn until_semicolon(chars: &mut std::iter::Peekable<std::str::Chars<'_>>) -> Stri
}
/// `\U+XXXX` Unicode escapes (how files before DXF 2007 hold characters outside their code page)
/// and caret escapes: `^ ` is a caret, `^I` a tab; other control characters are dropped.
fn escapes(s: &str) -> String {
/// and caret escapes: `^ ` is a caret, `^I` a tab; other control characters are dropped. With
/// `keep_stacks`, the `^` in an MTEXT stacked fraction `\Snum^den;` is left for the caller.
fn escapes(s: &str, keep_stacks: bool) -> String {
let mut out = String::with_capacity(s.len());
let mut rest = s;
while let Some(c) = rest.chars().next() {
@@ -78,6 +79,16 @@ fn escapes(s: &str) -> String {
rest = after.get(6..).unwrap_or("");
continue;
}
if keep_stacks
&& c == '\\'
&& let Some(tail) = after.strip_prefix('S')
{
let end = tail.find(';').map_or(tail.len(), |i| i + 1);
out.push_str("\\S");
out.push_str(tail.get(..end).unwrap_or(""));
rest = tail.get(end..).unwrap_or("");
continue;
}
if c == '^'
&& let Some(next) = after.chars().next()
{
@@ -161,6 +172,8 @@ mod tests {
// Carets are written "^ " (a caret before another character is a control character).
assert_eq!(mtext("x\\S3^ 4;").0, "x3/4");
assert_eq!(mtext("x\\S3#4;").0, "x3/4");
assert_eq!(mtext("\\S1^2;").0, "1/2");
assert_eq!(mtext("\\S+1^-2;^I").0, "+1/-2\t");
assert_eq!(mtext("trailing\\").0, "trailing\\");
}
}