acadrust 0.6.7

A pure Rust library for reading and writing CAD files in DXF format (ASCII and Binary) and DWG format (Binary).
Documentation
use super::super::entities::{
    decode_entities, decode_positioned_entities, encode_entities, EntityTables,
};
use super::*;
use crate::io::dwg::crc::crc16;

const ENTITY_BASE: u32 = 0x1000;
const BLOCK_BASE: u32 = 0x2000;
const EXTRA_BASE: u32 = 0x3000;

fn tables() -> EntityTables {
    EntityTables {
        layers: vec!["0".into()],
        styles: vec!["STANDARD".into()],
        blocks: vec!["B".into()],
        ..EntityTables::default()
    }
}

fn frame(kind: u8, flag: u8, body: &[u8], padding: usize) -> Vec<u8> {
    let size = (4 + body.len() + 2 + padding) as u16;
    let mut raw = vec![kind, flag];
    raw.extend(size.to_le_bytes());
    raw.extend(body);
    raw.extend(crc16(0xc0c1, &raw).to_le_bytes());
    raw.resize(size as usize, 0xee);
    raw
}

fn common(kind: u8, flag: u8, geometry: &[u8]) -> Vec<u8> {
    let mut body = vec![0; 4];
    body.extend(geometry);
    frame(kind, flag, &body, 0)
}

fn jump(address: u32, padding: usize) -> Vec<u8> {
    frame(18, 0, &address.to_le_bytes(), padding)
}

fn vertex(x: f64) -> Vec<u8> {
    let mut geometry = Vec::from(x.to_le_bytes());
    geometry.extend(2_f64.to_le_bytes());
    common(20, 0, &geometry)
}

fn point(x: f64) -> Vec<u8> {
    let mut geometry = Vec::from(x.to_le_bytes());
    geometry.extend(2_f64.to_le_bytes());
    geometry.extend(0_f64.to_le_bytes());
    common(2, 0, &geometry)
}

fn seqend(owner: u32) -> Vec<u8> {
    common(17, 0, &owner.to_le_bytes())
}

fn insert() -> Vec<u8> {
    let mut geometry = vec![0; 2];
    geometry.extend(0_f64.to_le_bytes());
    geometry.extend(0_f64.to_le_bytes());
    common(14, 128, &geometry)
}

fn attrib() -> Vec<u8> {
    let mut geometry = vec![0; 16];
    geometry.extend(1_f64.to_le_bytes());
    geometry.extend([1, 0, b'V', 1, 0, b'T', 0]);
    common(16, 0, &geometry)
}

fn section(base: u32, raw: Vec<u8>) -> SectionRecords {
    SectionRecords {
        base_address: base,
        size: raw.len() as u32,
        records: decode_positioned_entities(&raw, &tables(), base, "ANSI_1252").unwrap(),
    }
}

fn assemble(
    main: Vec<u8>,
    blocks: Vec<u8>,
    extras: Vec<u8>,
) -> Result<(Vec<EntityRecord>, Vec<EntityRecord>)> {
    reassemble_sections(
        section(ENTITY_BASE, main),
        section(BLOCK_BASE, blocks),
        section(EXTRA_BASE, extras),
    )
}

fn kinds(records: &[EntityRecord]) -> Vec<&str> {
    records.iter().map(|record| record.kind.as_str()).collect()
}

#[test]
fn padded_jump_checks_crc_before_padding_and_preserves_unsigned_target() {
    let raw = jump(0x8000_1234, 19);
    let decoded = decode_positioned_entities(&raw, &tables(), 0, "ANSI_1252").unwrap();
    assert_eq!(decoded[0].jump_address, Some(0x8000_1234));
    assert_eq!(decoded[0].size, 29);
    let mut corrupted = raw.clone();
    corrupted[8] ^= 1;
    assert!(decode_positioned_entities(&corrupted, &tables(), 0, "ANSI_1252").is_err());
    let mut altered_padding = raw;
    altered_padding[10] ^= 1;
    assert!(decode_positioned_entities(&altered_padding, &tables(), 0, "ANSI_1252").is_ok());
}

#[test]
fn unsigned_jump_address_survives_low_level_codec() {
    let raw = jump(0x8000_1234, 0);
    let decoded = decode_entities(&raw, &tables(), 0, "ANSI_1252").unwrap();
    assert_eq!(
        encode_entities(&decoded, &tables(), 0, "ANSI_1252").unwrap(),
        raw
    );
}

#[test]
fn polyline_continuation_follows_extra_and_return_jump() {
    let mut main = common(19, 128, &[]);
    main.extend(jump(0x8000_0000, 14));
    let resume = main.len() as u32;
    main.extend(point(9.));
    let mut extras = vertex(3.);
    extras.extend(vertex(4.));
    extras.extend(seqend(ENTITY_BASE));
    extras.extend(jump(resume, 7));
    let (entities, blocks) = assemble(main, Vec::new(), extras).unwrap();
    assert!(blocks.is_empty());
    assert_eq!(
        kinds(&entities),
        ["POLYLINE", "VERTEX", "VERTEX", "SEQEND", "POINT"]
    );
    assert_eq!(entities[1].number(10, 0.), 3.);
    assert_eq!(entities[2].number(10, 0.), 4.);
    assert_eq!(entities[4].number(10, 0.), 9.);
}

#[test]
fn insert_attributes_reassemble_before_following_entities() {
    let mut main = insert();
    main.extend(jump(0x8000_0000, 0));
    let resume = main.len() as u32;
    main.extend(point(8.));
    let mut extras = attrib();
    extras.extend(seqend(ENTITY_BASE));
    extras.extend(jump(resume, 0));
    let (entities, _) = assemble(main, Vec::new(), extras).unwrap();
    assert_eq!(kinds(&entities), ["INSERT", "ATTRIB", "SEQEND", "POINT"]);
    assert_eq!(entities[1].string(1), "V");
    assert_eq!(entities[1].string(2), "T");
}

#[test]
fn block_owned_extra_vertices_stay_with_block() {
    let mut blocks = common(12, 0, &[0; 16]);
    let owner = BLOCK_BASE + blocks.len() as u32;
    blocks.extend(common(19, 128, &[]));
    blocks.extend(jump(0x8000_0000, 0));
    let resume = blocks.len() as u32;
    blocks.extend(common(13, 0, &[]));
    let mut extras = vertex(6.);
    extras.extend(seqend(owner));
    extras.extend(jump(0x4000_0000 | resume, 0));
    let (entities, blocks) = assemble(point(1.), blocks, extras).unwrap();
    assert_eq!(kinds(&entities), ["POINT"]);
    assert_eq!(
        kinds(&blocks),
        ["BLOCK", "POLYLINE", "VERTEX", "SEQEND", "ENDBLK"]
    );
    assert_eq!(blocks[2].number(10, 0.), 6.);
}

#[test]
fn block_seqend_accepts_section_tagged_owner_address() {
    let mut blocks = common(12, 0, &[0; 16]);
    let owner = BLOCK_BASE + blocks.len() as u32;
    blocks.extend(common(19, 128, &[]));
    blocks.extend(jump(0x8000_0000, 0));
    let resume = blocks.len() as u32;
    blocks.extend(common(13, 0, &[]));
    let mut extras = vertex(6.);
    // Native BLOCK section addresses are tagged with 0x40000000 and remain
    // section-relative when a sequence terminates in the extra stream.
    extras.extend(seqend(0x4000_0000 | (owner - BLOCK_BASE)));
    extras.extend(jump(0x4000_0000 | resume, 0));
    let (entities, blocks) = assemble(point(1.), blocks, extras).unwrap();
    assert_eq!(kinds(&entities), ["POINT"]);
    assert_eq!(
        kinds(&blocks),
        ["BLOCK", "POLYLINE", "VERTEX", "SEQEND", "ENDBLK"]
    );
}

#[test]
fn deleted_bit_jump_is_still_followed() {
    let target = jump(0, 0).len() as u32;
    let mut marked = jump(target, 0);
    marked[0] |= 128;
    let checksum = crc16(0xc0c1, &marked[..8]);
    marked[8..10].copy_from_slice(&checksum.to_le_bytes());
    let mut main = marked;
    main.extend(point(4.));
    let (entities, blocks) = assemble(main, Vec::new(), Vec::new()).unwrap();
    assert!(blocks.is_empty());
    assert_eq!(kinds(&entities), ["POINT"]);
    assert_eq!(entities[0].number(10, 0.), 4.);
}

#[test]
fn backward_jump_is_valid_when_no_record_is_revisited() {
    let early_point = point(1.);
    let late_start = 10 + early_point.len() as u32 + 10;
    let final_offset = late_start + point(2.).len() as u32 + 10;
    let mut main = jump(late_start, 0);
    main.extend(early_point);
    main.extend(jump(final_offset, 0));
    main.extend(point(2.));
    main.extend(jump(10, 0));
    let (entities, _) = assemble(main, Vec::new(), Vec::new()).unwrap();
    assert_eq!(
        entities
            .iter()
            .map(|record| record.number(10, 0.))
            .collect::<Vec<_>>(),
        [2., 1.]
    );
}

#[test]
fn malformed_jump_boundaries_and_flags_are_rejected() {
    for target in [1, 11, 0x2000_0000, 0x4000_0001, 0x8000_0001] {
        assert!(
            assemble(jump(target, 0), Vec::new(), Vec::new()).is_err(),
            "target {target:#x}"
        );
    }
}

#[test]
fn jump_cycles_and_duplicate_ownership_are_rejected() {
    assert!(assemble(jump(0, 0), Vec::new(), Vec::new()).is_err());
    let main = jump(0x8000_0000, 0);
    let extras = jump(0, 0);
    assert!(assemble(main, Vec::new(), extras).is_err());

    let mut main = common(19, 128, &[]);
    main.extend(jump(0x8000_0000, 0));
    let second = main.len() as u32;
    main.extend(common(19, 128, &[]));
    main.extend(jump(0x8000_0000, 0));
    let mut extras = vertex(3.);
    extras.extend(seqend(ENTITY_BASE));
    extras.extend(jump(second, 0));
    assert!(assemble(main, Vec::new(), extras).is_err());
}

#[test]
fn orphan_children_missing_seqend_and_wrong_owner_are_rejected() {
    for child in [vertex(3.), attrib(), seqend(ENTITY_BASE)] {
        assert!(assemble(point(1.), Vec::new(), child).is_err());
    }
    assert!(assemble(common(19, 128, &[]), Vec::new(), Vec::new()).is_err());
    let mut main = common(19, 128, &[]);
    main.extend(vertex(3.));
    main.extend(seqend(ENTITY_BASE + 1));
    assert!(assemble(main, Vec::new(), Vec::new()).is_err());
}

#[test]
fn native_viewport_is_unsigned_and_follows_handle_before_geometry() {
    let mut body = vec![0; 4];
    body.extend([4, 2, 0xab, 0xcd]);
    body.extend(u16::MAX.to_le_bytes());
    body.extend(1_f64.to_le_bytes());
    body.extend(2_f64.to_le_bytes());
    body.extend(3_f64.to_le_bytes());
    let raw = frame(2, 64 | 32, &body, 0);
    let records = decode_positioned_entities(&raw, &tables(), 0, "ANSI_1252").unwrap();
    let record = records[0].record.as_ref().unwrap();
    assert_eq!(record.native_viewport, Some(u16::MAX));
    assert_eq!(record.string(5), "ABCD");
    assert_eq!(record.number(10, 0.), 1.);
    assert_eq!(record.number(30, 0.), 3.);
    assert!(!record.pairs.iter().any(|(code, _)| *code == 69));
    let encoded = encode_entities(&[record.clone()], &tables(), 0, "ANSI_1252").unwrap();
    assert_eq!(encoded, raw);
}

#[test]
fn seqend_address_metadata_is_not_exported_through_dxf_pairs() {
    let raw = seqend(0x1234_5678);
    let records = decode_positioned_entities(&raw, &tables(), EXTRA_BASE, "ANSI_1252").unwrap();
    assert_eq!(records[0].sequence_owner, Some(0x1234_5678));
    assert_eq!(records[0].record.as_ref().unwrap().pairs, [(8, "0".into())]);
}