diff --git a/crates/io/src/dxf_read/document.rs b/crates/io/src/dxf_read/document.rs index 6f61e3415..d0fea46cc 100644 --- a/crates/io/src/dxf_read/document.rs +++ b/crates/io/src/dxf_read/document.rs @@ -314,7 +314,17 @@ pub(super) fn apply_sortents( paper_for: &HashMap, first_layout: &str, ) { - for (owner, map) in tables { + // Each sort rebuilds its whole space, so a space named by many tables is sorted once, by the + // last of them (which decided its order anyway); repeated tables made import quadratic. + let target = |owner: &String| { + let up = brs.get(owner)?.to_ascii_uppercase(); + Some(if up.starts_with("*PAPER_SPACE") { paper_for.get(&up).cloned().unwrap_or_else(|| first_layout.to_string()) + "\0paper" } else { up }) + }; + let last: HashMap = tables.iter().enumerate().filter_map(|(i, (owner, _))| Some((target(owner)?, i))).collect(); + for (i, (owner, map)) in tables.iter().enumerate() { + if target(owner).and_then(|t| last.get(&t)) != Some(&i) { + continue; + } let Some(name) = brs.get(owner) else { continue }; let up = name.to_ascii_uppercase(); // A sort handle of 0 draws last (ezdxf `reorder`, which notes AutoCAD defines it, undocumented). diff --git a/crates/io/src/tests.rs b/crates/io/src/tests.rs index 46536f24e..45c2d3470 100644 --- a/crates/io/src/tests.rs +++ b/crates/io/src/tests.rs @@ -337,6 +337,28 @@ fn entity_transparency_roundtrips_as_group_440() { assert_eq!(d.model.iter().map(|e| e.common.transparency).collect::>(), [Transparency::ByLayer, Transparency::Percent(90)]); } +/// Several SORTENTSTABLEs for one space sort it once, in the last table's order. +#[test] +fn repeated_sortents_tables_use_the_last() { + let table = + |a: u32, b: u32| format!("0\nSORTENTSTABLE\n5\n{:X}\n100\nAcDbSortentsTable\n330\n1F\n331\nA\n5\n{a:X}\n331\nB\n5\n{b:X}\n", 0x100 + a); + let mut t = String::from( + "0\nSECTION\n2\nTABLES\n0\nTABLE\n2\nBLOCK_RECORD\n0\nBLOCK_RECORD\n5\n1F\n2\n*Model_Space\n0\nENDTAB\n0\nENDSEC\n\ + 0\nSECTION\n2\nENTITIES\n0\nLINE\n5\nA\n330\n1F\n10\n0\n20\n0\n11\n1\n21\n1\n0\nLINE\n5\nB\n330\n1F\n10\n0\n20\n0\n11\n2\n21\n2\n0\nENDSEC\n\ + 0\nSECTION\n2\nOBJECTS\n0\nDICTIONARY\n5\nC\n", + ); + for _ in 0..1000 { + t.push_str(&table(2, 1)); + } + t.push_str(&table(1, 2)); + t.push_str("0\nENDSEC\n0\nEOF\n"); + let d = read(t.as_bytes(), "s.dxf").unwrap(); + assert_eq!(d.model.iter().map(|e| e.handle.0).collect::>(), [0xA, 0xB]); + let reversed = t.replacen(&table(1, 2), &table(2, 1), 1); + let d = read(reversed.as_bytes(), "s.dxf").unwrap(); + assert_eq!(d.model.iter().map(|e| e.handle.0).collect::>(), [0xB, 0xA]); +} + #[test] fn hostile_dxf_does_not_panic() { for t in [