Compare commits

...
Author SHA1 Message Date
Dylan M. Taylor 173cdf6e8a Use shared MTEXT escape decoding 2026-11-15 16:00:46 +00:00
3 changed files with 11 additions and 78 deletions
Generated
+8 -1
View File
@@ -1131,6 +1131,11 @@ dependencies = [
"libc",
]
[[package]]
name = "craft-codepage"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=6345459e1297ee85a7ca62b428a440691bd062c1#6345459e1297ee85a7ca62b428a440691bd062c1"
[[package]]
name = "craft-docpath"
version = "0.1.0"
@@ -1148,9 +1153,11 @@ dependencies = [
[[package]]
name = "craft-dxf"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=006e4df225fca57e95ead0fb1f91920900798a99#006e4df225fca57e95ead0fb1f91920900798a99"
source = "git+https://github.com/storytold/craft-libs?rev=6345459e1297ee85a7ca62b428a440691bd062c1#6345459e1297ee85a7ca62b428a440691bd062c1"
dependencies = [
"craft-codepage",
"encoding_rs",
"kurbo",
"log",
"thiserror 2.0.21",
]
+1 -1
View File
@@ -14,7 +14,7 @@ repository = "https://github.com/storytold/cadcraft"
# internal
cadcraft-geom = { path = "crates/geom" }
# DXF tags: the shared reader and writer (moved from crates/dxf; craft-libs#791).
craft-dxf = { git = "https://github.com/storytold/craft-libs", rev = "006e4df225fca57e95ead0fb1f91920900798a99" }
craft-dxf = { git = "https://github.com/storytold/craft-libs", rev = "6345459e1297ee85a7ca62b428a440691bd062c1" }
craft-dwg = { git = "https://github.com/storytold/craft-libs", rev = "7bb6c90076a0177d01cc4c482e7fd8dc9f31a8bf" }
cadcraft-color = { path = "crates/color" }
cadcraft-doc = { path = "crates/doc" }
+2 -76
View File
@@ -225,11 +225,8 @@ const MAX_ATOMS: usize = 200_000;
/// Scale of stacked text relative to the surrounding text.
const STACK_SCALE: f64 = 0.7;
/// Codes that take an argument terminated by `;` (besides `\S`, which has its own scan). Any
/// other code stands alone, so a missing `;` must not swallow the text after it.
pub(crate) fn takes_arg(code: char) -> bool {
matches!(code, 'f' | 'F' | 'H' | 'W' | 'Q' | 'T' | 'A' | 'C' | 'c' | 'p')
}
// Shared DXF escape handling and parameterized-command classification.
pub(crate) use craft_dxf::mtext::{takes_arg, unescape_codes};
/// The line-spacing factor to lay out with: AutoCAD's 0.25..=4 range, 1 for a missing or
/// non-finite value.
@@ -237,77 +234,6 @@ pub(crate) fn sane_line_spacing(v: f64) -> f64 {
if v.is_finite() && v > 0.0 { v.clamp(0.25, 4.0) } else { 1.0 }
}
/// Four hex digits at `chars[at..at + 4]`.
fn hex4(chars: &[char], at: usize) -> Option<u32> {
let digits = chars.get(at..at.checked_add(4)?)?;
digits.iter().try_fold(0u32, |acc, c| Some(acc * 16 + c.to_digit(16)?))
}
/// Push a decoded character so that the tokenizers read it as text, never as markup.
fn push_literal(out: &mut String, c: char) {
if matches!(c, '\\' | '{' | '}') {
out.push('\\');
}
out.push(c);
}
/// Decode `\U+hhhh` (one UTF-16 unit; a surrogate pair gives one character, a lone surrogate
/// U+FFFD) and `\M+nhhhh` (double-byte East Asian characters) to the characters they stand
/// for. Malformed escapes are left for the tokenizers, which drop the backslash and the code.
/// An escaped backslash (`\\U+0041`) is not an escape.
pub(crate) fn unescape_codes(s: &str) -> std::borrow::Cow<'_, str> {
if !s.contains("\\U+") && !s.contains("\\M+") {
return std::borrow::Cow::Borrowed(s);
}
let chars: Vec<char> = s.chars().collect();
let mut out = String::with_capacity(s.len());
let mut i = 0;
while let Some(&c) = chars.get(i) {
if c != '\\' {
out.push(c);
i += 1;
continue;
}
let (n, plus) = (chars.get(i + 1).copied(), chars.get(i + 2).copied());
if n == Some('U') && plus == Some('+') {
if let Some(unit) = hex4(&chars, i + 3) {
i += 7;
let low = |at: usize| {
(chars.get(at) == Some(&'\\') && chars.get(at + 1) == Some(&'U') && chars.get(at + 2) == Some(&'+'))
.then(|| hex4(&chars, at + 3))
.flatten()
.filter(|u| (0xDC00..0xE000).contains(u))
};
let ch = if (0xD800..0xDC00).contains(&unit) {
match low(i) {
Some(lo) => {
i += 7;
char::from_u32(0x10000 + ((unit - 0xD800) << 10) + (lo - 0xDC00))
}
None => None,
}
} else {
char::from_u32(unit)
};
push_literal(&mut out, ch.unwrap_or('\u{fffd}'));
continue;
}
} else if n == Some('M') && plus == Some('+') && chars.get(i + 3).is_some_and(char::is_ascii_digit) && hex4(&chars, i + 4).is_some() {
push_literal(&mut out, craft_dxf::mif_escape_at(&chars, i).unwrap_or('\u{fffd}'));
i += 8;
continue;
}
// Any other backslash pair stays as it is (and an escaped backslash stays one).
out.push('\\');
i += 1;
if let Some(&next) = chars.get(i) {
out.push(next);
i += 1;
}
}
std::borrow::Cow::Owned(out)
}
fn arg(chars: &[char], from: usize) -> (String, usize) {
let mut j = from;
let mut a = String::new();