use std::io::Cursor;
use acadrust::entities::{EntityType, Polyline2D, PolylineFlags, Vertex2D};
use acadrust::objects::{FieldList, ObjectType};
use acadrust::types::{DxfVersion, Handle, Vector3};
use acadrust::{CadDocument, DxfReader, DxfWriter};
fn write_text(doc: &CadDocument) -> String {
String::from_utf8(DxfWriter::new(doc).write_to_vec().expect("DXF write failed")).unwrap()
}
fn read_bytes(bytes: Vec<u8>) -> CadDocument {
DxfReader::from_reader(Cursor::new(bytes))
.expect("DXF reader init failed")
.read()
.expect("DXF read failed")
}
fn has_record(text: &str, record_type: &str) -> bool {
text.contains(&format!(" 0\r\n{}\r\n", record_type))
}
fn dictionary_entry(text: &str, key: &str) -> (u16, u64) {
let lines: Vec<&str> = text.split("\r\n").collect();
for i in 1..lines.len() {
if lines[i] == key && lines[i - 1].trim() == "3" {
let code: u16 = lines[i + 1].trim().parse().expect("group code");
let handle = u64::from_str_radix(lines[i + 2].trim(), 16).expect("handle");
return (code, handle);
}
}
panic!("dictionary entry {:?} not found", key);
}
#[test]
fn acad_field_nod_entry_uses_hard_owner_pointer() {
let mut doc = CadDocument::with_version(DxfVersion::AC1032);
let nod_handle = doc.header.named_objects_dict_handle;
assert!(!nod_handle.is_null(), "default document must have a root NOD");
let field_list_handle = Handle::new(0x114D);
let field_list = FieldList {
handle: field_list_handle,
owner: nod_handle,
..Default::default()
};
doc.objects
.insert(field_list_handle, ObjectType::FieldList(field_list));
if let Some(ObjectType::Dictionary(nod)) = doc.objects.get_mut(&nod_handle) {
nod.add_entry("ACAD_FIELD", field_list_handle);
} else {
panic!("root NOD missing from objects");
}
let output = write_text(&doc);
let (code, target) = dictionary_entry(&output, "ACAD_FIELD");
assert_eq!(code, 360, "ACAD_FIELD must use hard-owner code 360");
assert_eq!(target, 0x114D, "ACAD_FIELD must point at the FIELDLIST");
assert!(has_record(&output, "FIELDLIST"), "FIELDLIST object must be written");
}
#[test]
fn acad_layout_and_plotstylename_nod_entries_match_bricscad() {
let doc = CadDocument::with_version(DxfVersion::AC1032);
let output = write_text(&doc);
for key in ["ACAD_LAYOUT", "ACAD_PLOTSTYLENAME"] {
let (code, _) = dictionary_entry(&output, key);
assert_eq!(code, 350, "{} must use soft-pointer code 350", key);
}
let (group_code, _) = dictionary_entry(&output, "ACAD_GROUP");
assert_eq!(group_code, 350, "ACAD_GROUP stays a soft pointer");
}
fn plain_polyline() -> Polyline2D {
let mut pl = Polyline2D::new();
pl.start_width = 0.5;
pl.elevation = 2.0;
pl.close();
let mut v0 = Vertex2D::new(Vector3::new(0.0, 0.0, 2.0));
v0.bulge = 0.5;
pl.add_vertex(v0);
let mut v1 = Vertex2D::new(Vector3::new(10.0, 0.0, 2.0));
v1.start_width = 1.0;
v1.end_width = 0.25;
pl.add_vertex(v1);
pl.add_vertex(Vertex2D::new(Vector3::new(10.0, 10.0, 2.0)));
pl
}
#[test]
fn plain_polyline2d_downsaves_to_lwpolyline_r2000_plus() {
let mut doc = CadDocument::with_version(DxfVersion::AC1032);
doc.add_entity(EntityType::Polyline2D(plain_polyline())).unwrap();
let output = write_text(&doc);
assert!(has_record(&output, "LWPOLYLINE"), "expected LWPOLYLINE output");
assert!(!has_record(&output, "POLYLINE"), "legacy POLYLINE must not be written");
assert!(!has_record(&output, "VERTEX"), "VERTEX records must not be written");
assert!(!has_record(&output, "SEQEND"), "SEQEND records must not be written");
let rt = read_bytes(DxfWriter::new(&doc).write_to_vec().unwrap());
assert_eq!(rt.entity_count(), 1);
for entity in rt.entities() {
match entity {
EntityType::LwPolyline(lw) => {
assert!(lw.is_closed);
assert_eq!(lw.vertices.len(), 3);
assert_eq!(lw.elevation, 2.0);
assert_eq!(lw.vertices[0].location.x, 0.0);
assert_eq!(lw.vertices[0].location.y, 0.0);
assert_eq!(lw.vertices[0].bulge, 0.5);
assert_eq!(lw.vertices[0].start_width, 0.5, "default width baked in");
assert_eq!(lw.vertices[1].start_width, 1.0, "per-vertex width preserved");
assert_eq!(lw.vertices[1].end_width, 0.25);
assert_eq!(lw.vertices[2].start_width, 0.5, "default width baked in");
assert_eq!(lw.vertices[2].end_width, 0.0);
}
other => panic!("expected LwPolyline after down-save, got {:?}", other),
}
}
}
#[test]
fn spline_fit_polyline2d_keeps_legacy_form() {
let mut doc = CadDocument::with_version(DxfVersion::AC1032);
let mut pl = plain_polyline();
pl.flags = pl.flags | PolylineFlags::SPLINE_FIT;
doc.add_entity(EntityType::Polyline2D(pl)).unwrap();
let output = write_text(&doc);
assert!(has_record(&output, "POLYLINE"), "fitted polylines keep the legacy form");
assert!(has_record(&output, "VERTEX"));
assert!(has_record(&output, "SEQEND"));
assert!(!has_record(&output, "LWPOLYLINE"));
}
#[test]
fn polyface_mesh_flags_keep_legacy_form() {
let mut doc = CadDocument::with_version(DxfVersion::AC1032);
let mut pl = plain_polyline();
pl.flags = pl.flags | PolylineFlags::POLYFACE_MESH;
doc.add_entity(EntityType::Polyline2D(pl)).unwrap();
let output = write_text(&doc);
assert!(has_record(&output, "POLYLINE"));
assert!(!has_record(&output, "LWPOLYLINE"));
}
#[test]
fn r14_output_keeps_legacy_polyline_form() {
let mut doc = CadDocument::with_version(DxfVersion::AC1014);
doc.add_entity(EntityType::Polyline2D(plain_polyline())).unwrap();
let output = write_text(&doc);
assert!(has_record(&output, "POLYLINE"), "pre-R2000 output keeps legacy POLYLINE");
assert!(has_record(&output, "VERTEX"));
assert!(has_record(&output, "SEQEND"));
assert!(!has_record(&output, "LWPOLYLINE"));
}
#[test]
fn empty_polyline2d_still_downsaves_without_children() {
let mut doc = CadDocument::with_version(DxfVersion::AC1032);
doc.add_entity(EntityType::Polyline2D(Polyline2D::new())).unwrap();
let output = write_text(&doc);
assert!(has_record(&output, "LWPOLYLINE"));
assert!(!has_record(&output, "SEQEND"));
}