From e29c2f80a495bacaac0ac32f4737e73826aed20e Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Tue, 3 Nov 2026 11:45:50 +0000 Subject: [PATCH] fix(io): sort each space once when a DXF has several SORTENTSTABLEs for it Every SORTENTSTABLE rebuilt its whole entity store, so many small tables naming one large space made import quadratic. Each space is now sorted once, by the last table naming it, which already decided its order. --- crates/io/src/dxf_read/document.rs | 12 +++++++++++- crates/io/src/tests.rs | 22 ++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) 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 [