DXF: write kept application data groups as read, not as handle lists (#681)

Squash of PR #681 by @CaseyKleinman.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
CaseyKleinman
2026-10-12 03:11:09 -04:00
committed by GitHub
co-authored by Claude Opus 5.5
parent b6f2e86f78
commit f8cb50eaa3
2 changed files with 40 additions and 0 deletions
+13
View File
@@ -598,6 +598,19 @@ impl Map<'_> {
while let Some(r) = o.tags.get(i) {
i += 1;
if r.code == 102 && r.value.trim().starts_with('{') {
// A `{NAME` group is a handle list (`{ACAD_REACTORS 330 … }`) only when every
// group inside it is a reference. Application data groups also use 102 brackets
// with mixed values and their own closing form — the AcDs thumbnail schema's
// `{ATTRRECORD 341 h 2 name 280 1 291 1 ATTRRECORD}` — and collapsing those into
// a handle list rewrote the handle under the LAST code read (291, a bool) and
// dropped the rest, which strict DXF readers reject. Those are written as read,
// references remapped, by the plain path below.
let end = (i..o.tags.len()).find(|&k| o.tags[k].code == 102).unwrap_or(o.tags.len());
let inner = &o.tags[i..end];
if !inner.is_empty() && !inner.iter().all(|n| is_pointer(n.code)) {
out.push(tag(r));
continue;
}
let name = r.value.trim().trim_start_matches('{').to_string();
let mut hs = Vec::new();
let mut code = 330;
+27
View File
@@ -1985,3 +1985,30 @@ fn svg_no_geometry_yields_an_empty_drawing() {
let d = read(svg.as_bytes(), "empty.svg").unwrap();
assert!(kinds(&d).is_empty());
}
/// An application data group inside a kept XRECORD — the AcDs thumbnail schema's
/// `{ATTRRECORD 341 h 2 name 280 1 291 1 ATTRRECORD}` — is written back as read. It used to be
/// collapsed into a handle list: the handle came out under group 291 (a bool) and the name and
/// flags were dropped, which strict DXF readers (ezdxf) reject outright.
#[test]
fn kept_xrecord_application_group_roundtrips_as_read() {
let dxf = "0\nSECTION\n2\nOBJECTS\n\
0\nDICTIONARY\n5\nC\n330\n0\n100\nAcDbDictionary\n3\nTD_SCHEMA\n350\n20\n\
0\nXRECORD\n5\n20\n330\nC\n100\nAcDbXrecord\n280\n1\n1\nAcDb_Thumbnail_Schema\n\
102\n{ATTRRECORD\n341\n21\n2\nAcDbDs::TreatedAsObjectData\n280\n1\n291\n1\n102\nATTRRECORD}\n\
0\nXRECORD\n5\n21\n330\n20\n100\nAcDbXrecord\n280\n1\n1\nTarget\n\
0\nENDSEC\n0\nEOF\n";
let d = read_dxf(dxf.as_bytes()).expect("read");
let out = write_dxf(&d);
let lines: Vec<&str> = out.lines().map(str::trim).collect();
let at = lines.iter().position(|l| *l == "{ATTRRECORD").expect("group kept");
// The group keeps its own shape: a reference under 341, then the name, flag and bool.
assert_eq!(lines[at + 1], "341", "reference group code: {:?}", &lines[at..at + 12]);
assert!(lines[at + 2] != "0" && !lines[at + 2].is_empty());
assert_eq!(&lines[at + 3..at + 5], &["2", "AcDbDs::TreatedAsObjectData"]);
assert_eq!(&lines[at + 5..at + 7], &["280", "1"]);
assert_eq!(&lines[at + 7..at + 9], &["291", "1"]);
assert_eq!(&lines[at + 9..at + 11], &["102", "ATTRRECORD}"]);
// And no handle is written under a bool code anywhere.
assert!(!out.contains("291\n2"), "no handle may be written under a bool code");
}