acadrust 0.6.0

A pure Rust library for reading and writing CAD files in DXF format (ASCII and Binary) and DWG format (Binary).
Documentation
//! Group code value types
//!
//! Determines how to interpret the value associated with a DXF group code.

use super::DxfCode;

/// Type of value associated with a group code
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum GroupCodeValueType {
    /// No value or unknown
    None,

    /// String value
    String,

    /// Boolean value (0 or 1)
    Bool,

    /// 8-bit integer
    Byte,

    /// 16-bit signed integer
    Int16,

    /// 32-bit signed integer
    Int32,

    /// 64-bit signed integer
    Int64,

    /// Double-precision floating-point
    Double,

    /// 3D point (three doubles: X, Y, Z)
    Point3D,

    /// Binary data (hex string)
    BinaryData,

    /// Object handle (hex string)
    Handle,
}

impl GroupCodeValueType {
    /// Determine the value type from a DXF group code enum.
    /// NOTE: This goes through the DxfCode enum, which may not cover all codes.
    /// For binary reading where correctness is critical, use `from_raw_code` instead.
    pub fn from_code(code: DxfCode) -> Self {
        // Delegate to from_raw_code using the raw i32 representation.
        // However, DxfCode::from_i32 can lose unmapped codes (they become Invalid = -9999).
        // So we use the DxfCode's i32 representation which preserves the original value
        // only if the DxfCode was constructed with a known variant.
        Self::from_raw_code(code.to_i32())
    }

    /// Determine the value type from a raw integer group code.
    /// This is the canonical mapping and should be preferred over `from_code`
    /// when the original integer code is available (e.g., binary DXF reading).
    pub fn from_raw_code(code_num: i32) -> Self {
        match code_num {
            // String values (0-9, 100-109, 300-309, 999)
            0..=9 | 100..=109 | 300..=309 | 999 => GroupCodeValueType::String,

            // Floating-point values (10-59, 110-149, 210-239, 460-469)
            10..=59 | 110..=149 | 210..=239 | 460..=469 => GroupCodeValueType::Double,

            // 16-bit integers (60-79, 170-179, 270-279, 370-389, 400-409)
            60..=79 | 170..=179 | 270..=279 | 370..=389 | 400..=409 => GroupCodeValueType::Int16,

            // 8-bit integers (280-289)
            280..=289 => GroupCodeValueType::Byte,

            // 32-bit integers (90-99, 420-429, 440-449)
            90..=99 | 420..=429 | 440..=449 => GroupCodeValueType::Int32,

            // 64-bit integers (160-169)
            160..=169 => GroupCodeValueType::Int64,

            // 32-bit integers (450-459) — DXF "Long" = 4-byte int
            450..=459 => GroupCodeValueType::Int32,

            // Boolean values (290-299)
            290..=299 => GroupCodeValueType::Bool,

            // Binary data (310-319)
            310..=319 => GroupCodeValueType::BinaryData,

            // Handle values (320-369, 390-399, 480-481)
            320..=369 | 390..=399 | 480..=481 => GroupCodeValueType::Handle,

            // String handles (410-419, 430-439, 470-479)
            410..=419 | 430..=439 | 470..=479 => GroupCodeValueType::String,

            // Extended data
            1004 => GroupCodeValueType::BinaryData,
            1005 => GroupCodeValueType::Handle,
            1000..=1009 => GroupCodeValueType::String,
            1040..=1042 => GroupCodeValueType::Double,
            1010..=1059 => GroupCodeValueType::Double,
            1060..=1070 => GroupCodeValueType::Int16,
            1071 => GroupCodeValueType::Int32,

            // Default to None for unknown codes
            _ => GroupCodeValueType::None,
        }
    }

    /// Check if this is a coordinate value (part of a 3D point)
    pub fn is_coordinate(code: DxfCode) -> bool {
        let code_num = code.to_i32();

        // X coordinates: 10, 11, 12, 13, 14, 15, 16, 17, 18, 110, 111, 112, 1010, 1011, 1012, 1013
        // Y coordinates: 20, 21, 22, 23, 24, 25, 26, 27, 28, 120, 121, 122, 1020, 1021, 1022, 1023
        // Z coordinates: 30, 31, 32, 33, 34, 35, 36, 37, 38, 130, 131, 132, 1030, 1031, 1032, 1033
        // Extrusion: 210, 220, 230

        matches!(
            code_num,
            10..=18 | 20..=28 | 30..=38 |
            110..=112 | 120..=122 | 130..=132 |
            210 | 220 | 230 |
            1010..=1013 | 1020..=1023 | 1030..=1033
        )
    }

    /// Get the coordinate axis (0=X, 1=Y, 2=Z) for a coordinate code
    pub fn coordinate_axis(code: DxfCode) -> Option<usize> {
        let code_num = code.to_i32();

        // X coordinates (10-18, 110-112, 210, 1010-1013)
        if matches!(code_num, 10..=18 | 110..=112 | 210 | 1010..=1013) {
            return Some(0);
        }

        // Y coordinates (20-28, 120-122, 220, 1020-1023)
        if matches!(code_num, 20..=28 | 120..=122 | 220 | 1020..=1023) {
            return Some(1);
        }

        // Z coordinates (30-38, 130-132, 230, 1030-1033)
        if matches!(code_num, 30..=38 | 130..=132 | 230 | 1030..=1033) {
            return Some(2);
        }

        None
    }

    /// Get the coordinate group index (0=primary, 1=secondary, etc.)
    pub fn coordinate_group(code: DxfCode) -> Option<usize> {
        let code_num = code.to_i32();

        match code_num {
            10 | 20 | 30 => Some(0), // Primary point
            11 | 21 | 31 => Some(1), // Secondary point
            12 | 22 | 32 => Some(2), // Tertiary point
            13 | 23 | 33 => Some(3), // Quaternary point
            14 | 24 | 34 => Some(4),
            15 | 25 | 35 => Some(5),
            16 | 26 | 36 => Some(6),
            17 | 27 | 37 => Some(7),
            18 | 28 | 38 => Some(8),
            110 | 120 | 130 => Some(10),     // UCS origin
            111 | 121 | 131 => Some(11),     // UCS X-axis
            112 | 122 | 132 => Some(12),     // UCS Y-axis
            210 | 220 | 230 => Some(21),     // Extrusion direction
            1010 | 1020 | 1030 => Some(100), // XData point
            1011 | 1021 | 1031 => Some(101), // XData world position
            1012 | 1022 | 1032 => Some(102), // XData world displacement
            1013 | 1023 | 1033 => Some(103), // XData world direction
            _ => None,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_value_type_from_code() {
        assert_eq!(
            GroupCodeValueType::from_code(DxfCode::Start),
            GroupCodeValueType::String
        );
        assert_eq!(
            GroupCodeValueType::from_code(DxfCode::Text),
            GroupCodeValueType::String
        );
        assert_eq!(
            GroupCodeValueType::from_code(DxfCode::XCoordinate),
            GroupCodeValueType::Double
        );
        assert_eq!(
            GroupCodeValueType::from_code(DxfCode::Int70),
            GroupCodeValueType::Int16
        );
        assert_eq!(
            GroupCodeValueType::from_code(DxfCode::Int90),
            GroupCodeValueType::Int32
        );
        assert_eq!(
            GroupCodeValueType::from_code(DxfCode::Int160),
            GroupCodeValueType::Int64
        );
        assert_eq!(
            GroupCodeValueType::from_code(DxfCode::Bool290),
            GroupCodeValueType::Bool
        );
        assert_eq!(
            GroupCodeValueType::from_code(DxfCode::BinaryData310),
            GroupCodeValueType::BinaryData
        );
        assert_eq!(
            GroupCodeValueType::from_code(DxfCode::SoftPointerId330),
            GroupCodeValueType::Handle
        );
    }

    #[test]
    fn test_is_coordinate() {
        assert!(GroupCodeValueType::is_coordinate(DxfCode::XCoordinate));
        assert!(GroupCodeValueType::is_coordinate(DxfCode::YCoordinate));
        assert!(GroupCodeValueType::is_coordinate(DxfCode::ZCoordinate));
        assert!(GroupCodeValueType::is_coordinate(DxfCode::ExtrusionX));
        assert!(!GroupCodeValueType::is_coordinate(DxfCode::Real40));
        assert!(!GroupCodeValueType::is_coordinate(DxfCode::Int70));
    }

    #[test]
    fn test_coordinate_axis() {
        assert_eq!(
            GroupCodeValueType::coordinate_axis(DxfCode::XCoordinate),
            Some(0)
        );
        assert_eq!(
            GroupCodeValueType::coordinate_axis(DxfCode::YCoordinate),
            Some(1)
        );
        assert_eq!(
            GroupCodeValueType::coordinate_axis(DxfCode::ZCoordinate),
            Some(2)
        );
        assert_eq!(GroupCodeValueType::coordinate_axis(DxfCode::Real40), None);
    }

    #[test]
    fn test_coordinate_group() {
        assert_eq!(
            GroupCodeValueType::coordinate_group(DxfCode::XCoordinate),
            Some(0)
        );
        assert_eq!(
            GroupCodeValueType::coordinate_group(DxfCode::XCoordinate1),
            Some(1)
        );
        assert_eq!(
            GroupCodeValueType::coordinate_group(DxfCode::XCoordinate2),
            Some(2)
        );
        assert_eq!(
            GroupCodeValueType::coordinate_group(DxfCode::ExtrusionX),
            Some(21)
        );
    }
}