mirror of
https://github.com/storytold/cadcraft.git
synced 2026-10-24 02:46:51 -06:00
The *D block and EXPLODE's MTEXT took the dimension's decoded text, so %%c arrived as U+2300, which no SHX font has: other programs plotted a diameter dimension in an SHX style without its sign. They now write DimGeometry::mtext_contents(): the sign as %%c, which every font draws (and CADCraft decodes), a % that would run into a code as %%%. Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
@@ -1403,7 +1403,7 @@ pub(crate) fn explode_kind(d: &cadcraft_doc::Drawing, e: &Entity) -> Option<Vec<
|
||||
attach: 5,
|
||||
rotation: g.text_angle,
|
||||
style: style.text_style.clone(),
|
||||
contents: g.value.clone(),
|
||||
contents: g.mtext_contents(),
|
||||
line_spacing: 1.0,
|
||||
line_spacing_exact: false,
|
||||
columns: None,
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
//! #1645: a diameter dimension's text in its `*D` block, and in the MTEXT EXPLODE leaves, holds
|
||||
//! the diameter sign as `%%c`, as CADCraft's own TEXT and MTEXT keep it. The decoded ⌀ (U+2300)
|
||||
//! is in no SHX font, so other programs plotted the dimension in an SHX style ("romans.shx")
|
||||
//! without its sign. CADCraft still draws and reads the sign.
|
||||
|
||||
use cadcraft_engine::Session;
|
||||
use cadcraft_engine::doc::{EntityKind, Handle};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
fn h(v: &Value) -> Handle {
|
||||
v["handle"].as_str().and_then(Handle::parse_hex).unwrap()
|
||||
}
|
||||
|
||||
/// The issue's drawing: an SHX text style, a dimension style using it, a diameter dimension
|
||||
/// of a circle of radius 30.
|
||||
fn session() -> (Session, Handle) {
|
||||
let mut s = Session::new();
|
||||
s.execute("units", &json!({"measurement": "metric"})).ok();
|
||||
s.execute("style", &json!({"name": "R", "font": "romans.shx", "current": true})).unwrap();
|
||||
s.execute("dimstyle", &json!({"name": "D", "current": true, "DIMTXSTY": "R", "DIMTXT": 5, "DIMASZ": 5, "DIMDEC": 0})).unwrap();
|
||||
let c = h(&s.execute("circle", &json!({"center": [0, 0], "radius": 30})).unwrap());
|
||||
let dim = h(&s.execute("dimdiameter", &json!({"handle": c.hex(), "at": [40, 40]})).unwrap());
|
||||
(s, dim)
|
||||
}
|
||||
|
||||
/// The MTEXT contents (group 1) inside each `*D…` block of a DXF.
|
||||
fn dblock_texts(dxf: &str) -> Vec<String> {
|
||||
let lines: Vec<&str> = dxf.lines().map(str::trim).collect();
|
||||
let mut out = Vec::new();
|
||||
let (mut in_d, mut in_mtext) = (false, false);
|
||||
for pair in lines.chunks(2) {
|
||||
let [code, value] = pair else { continue };
|
||||
match (*code, *value) {
|
||||
("0", "BLOCK") => in_d = false,
|
||||
("2", name) if !in_mtext && name.starts_with("*D") => in_d = true,
|
||||
("0", "MTEXT") => in_mtext = in_d,
|
||||
("0", _) => in_mtext = false,
|
||||
("1", text) if in_mtext => out.push(text.to_string()),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_dimension_block_writes_the_sign_as_a_code() {
|
||||
let (s, dim) = session();
|
||||
let d = s.doc().unwrap();
|
||||
let EntityKind::Dimension(dm) = &d.entity(dim).unwrap().kind else { panic!("not a dimension") };
|
||||
assert!(matches!(dm.kind, cadcraft_engine::doc::DimKind::Diameter));
|
||||
let texts = dblock_texts(&cadcraft_io::write_dxf(d));
|
||||
assert_eq!(texts, vec!["%%c60".to_string()], "the *D block's MTEXT");
|
||||
// Read back, CADCraft still shows the sign.
|
||||
let read = cadcraft_io::read_dxf(cadcraft_io::write_dxf(d).as_bytes()).unwrap();
|
||||
let dm = read.model.iter().find_map(|e| if let EntityKind::Dimension(dm) = &e.kind { Some(dm.clone()) } else { None }).unwrap();
|
||||
let st = read.dim_styles.iter().find(|x| x.name.eq_ignore_ascii_case(&dm.style)).cloned().unwrap_or_default();
|
||||
assert_eq!(cadcraft_render::dimension_geometry(&dm, &st, 1.0).value, "⌀60");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exploding_keeps_the_sign_as_a_code() {
|
||||
let (mut s, dim) = session();
|
||||
s.execute("explode", &json!({"handles": [dim.hex()]})).unwrap();
|
||||
let d = s.doc().unwrap();
|
||||
let texts: Vec<String> =
|
||||
d.model.iter().filter_map(|e| if let EntityKind::MText(m) = &e.kind { Some(m.contents.clone()) } else { None }).collect();
|
||||
assert_eq!(texts, vec!["%%c60".to_string()]);
|
||||
let saved = cadcraft_io::write_dxf(d);
|
||||
assert!(!saved.contains('⌀'), "no U+2300 is saved");
|
||||
}
|
||||
@@ -1424,7 +1424,7 @@ fn dim_block_entities(d: &Drawing, dm: &Dimension, layer: &str) -> Vec<Entity> {
|
||||
attach: 5,
|
||||
rotation: g.text_angle,
|
||||
style: style.text_style.clone(),
|
||||
contents: g.value.clone(),
|
||||
contents: g.mtext_contents(),
|
||||
line_spacing: 1.0,
|
||||
line_spacing_exact: false,
|
||||
columns: None,
|
||||
|
||||
@@ -53,6 +53,22 @@ pub struct DimGeometry {
|
||||
}
|
||||
|
||||
impl DimGeometry {
|
||||
/// `value` as the contents of a plain MTEXT (the dimension's `*D` block, its EXPLODE
|
||||
/// pieces): the diameter sign as `%%c`, not the decoded ⌀ (U+2300), which no SHX font has,
|
||||
/// so other programs plot nothing for it, while every font draws `%%c` (#1645). A `%`
|
||||
/// that would run into a code is written `%%%`.
|
||||
pub fn mtext_contents(&self) -> String {
|
||||
let mut out = String::with_capacity(self.value.len() + 2);
|
||||
let mut chars = self.value.chars().peekable();
|
||||
while let Some(c) = chars.next() {
|
||||
match c {
|
||||
'⌀' => out.push_str("%%c"),
|
||||
'%' if matches!(chars.peek(), Some('%' | '⌀')) => out.push_str("%%%"),
|
||||
c => out.push(c),
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
fn dim(&mut self, l: Vec<Vec2>) {
|
||||
if l.len() >= 2 {
|
||||
self.lines.push(l);
|
||||
@@ -1381,6 +1397,7 @@ mod tests {
|
||||
d.defpt = Vec3::new(-3.0, 0.0, 0.0);
|
||||
let g = dimension_geometry(&d, &st_with(|s| s.decimals = 1), 1.0);
|
||||
assert_eq!(g.value, "⌀6.0");
|
||||
assert_eq!(g.mtext_contents(), "%%c6.0", "the *D block's text (#1645)");
|
||||
let mut a = lin((10.0, 0.0), (0.0, 10.0), (3.0, 3.0), 0.0);
|
||||
a.kind = DimKind::Angular3P;
|
||||
a.p15 = Vec3::ZERO;
|
||||
@@ -1430,6 +1447,28 @@ mod tests {
|
||||
let g = dimension_geometry(&d, &DimStyle::default(), 1.0);
|
||||
assert!(g.lines.is_empty() && g.fills.is_empty(), "{:?}", g.lines);
|
||||
}
|
||||
|
||||
/// #1645: the MTEXT contents give back the dimension's text when decoded, with the diameter
|
||||
/// sign as `%%c` (no SHX font has U+2300) and a `%` that would start a code escaped.
|
||||
#[test]
|
||||
fn mtext_contents_write_the_diameter_sign_as_a_code() {
|
||||
let with = |v: &str| DimGeometry { value: v.into(), ..DimGeometry::default() };
|
||||
for (value, contents) in [
|
||||
("⌀60", "%%c60"),
|
||||
("2x ⌀8.5 THRU", "2x %%c8.5 THRU"),
|
||||
("R3.0", "R3.0"),
|
||||
("36.9° ±0.1", "36.9° ±0.1"),
|
||||
("50%", "50%"),
|
||||
("5%⌀6", "5%%%%%c6"),
|
||||
("%%", "%%%%"),
|
||||
("", ""),
|
||||
] {
|
||||
let g = with(value);
|
||||
assert_eq!(g.mtext_contents(), contents, "{value:?}");
|
||||
assert_eq!(decode_value(&g.mtext_contents()), value, "{value:?} reads back");
|
||||
assert!(!g.mtext_contents().contains('⌀'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Reference in New Issue
Block a user