dxfscan 0.1.0

Binary DXF parser with typed entity data and lookup indices
Documentation
// SPDX-License-Identifier: ISC
mod arc;
mod attdef;
mod attrib;
mod circle;
mod dimension;
mod ellipse;
mod face3d;
pub(crate) mod hatch;
mod insert;
mod leader;
mod line;
mod lwpolyline;
mod mtext;
mod point;
mod polyline;
mod solid;
mod spline;
mod text;
mod trace;
mod wipeout;

pub use arc::Arc;
pub use attdef::AttDef;
pub use attrib::Attrib;
pub use circle::Circle;
pub use dimension::Dimension;
pub use ellipse::Ellipse;
pub use face3d::Face3d;
pub use hatch::{Hatch, HatchBoundaryPath, HatchEdge, HatchPatternDefLine, HatchPolylineBoundary};
pub use insert::Insert;
pub use leader::Leader;
pub use line::Line;
pub use lwpolyline::{LwPolyline, LwPolylineVertex};
pub use mtext::MText;
pub use point::DxfPoint;
pub use polyline::{Polyline, Vertex};
pub use solid::Solid;
pub use spline::Spline;
pub use text::Text;
pub use trace::Trace;
pub use wipeout::Wipeout;

use crate::point::Point3;
use crate::value::GroupValue;

/// Common properties shared by all DXF entities.
#[derive(Debug, Clone, Default)]
pub struct EntityCommon<'a> {
    /// Entity handle as a hex string.
    ///
    /// Handles are unique identifiers assigned by AutoCAD. They are
    /// persistent across save/reload and are used for cross-references
    /// between entities (e.g. DIMENSION → anonymous block).
    pub handle: &'a [u8],
    /// Layer name.
    pub layer: &'a [u8],
    /// Linetype name.
    ///
    /// Empty means not explicitly set. `b"BYLAYER"` inherits from the
    /// layer, `b"BYBLOCK"` inherits from the containing block reference.
    pub linetype_name: &'a [u8],
    /// Color number.
    ///
    /// | Value | Meaning         |
    /// |------:|-----------------|
    /// |     0 | BYBLOCK         |
    /// | 1–255 | ACI color index |
    /// |   256 | BYLAYER         |
    ///
    /// When `color_24_bit` is also set, the relationship between the
    /// two depends on the application. Some writers set both; consumers
    /// typically prefer `color` when it is in the 1–255 range.
    pub color: i16,
    /// Lineweight enum value.
    ///
    /// | Value | Meaning                             |
    /// |------:|-------------------------------------|
    /// |    -2 | BYLAYER                             |
    /// |    -1 | BYBLOCK                             |
    /// |    0+ | Explicit weight in hundredths of mm |
    ///
    /// A value of 0 does not mean invisible — lineweights are intended
    /// to be clamped to the application's minimum and maximum plotting
    /// widths (typically 1 pixel minimum on screen).
    pub lineweight: i16,
    /// Visibility flag.
    ///
    /// `true` when the entity is visible. Invisible entities are still
    /// part of the drawing but should not be rendered.
    pub is_visible: bool,
    /// 24-bit true color.
    ///
    /// RGB packed as `0x00RRGGBB`. When nonzero, this overrides `color`
    /// for rendering unless `color` is in the ACI 1–255 range.
    /// Zero means the true color is not set.
    pub color_24_bit: i32,
    /// Transparency value.
    ///
    /// Encoded as `0x020000TT` where `TT` is the transparency
    /// percentage (0 = fully opaque, 100 = fully transparent).
    /// Zero means transparency is not set (fully opaque).
    pub transparency: i32,
    /// Extrusion direction (OCS normal).
    ///
    /// Defines the object coordinate system (OCS) for this entity.
    /// Most entities use the default `(0, 0, 1)` which means their
    /// coordinates are in the world XY plane. Non-default values
    /// require an OCS-to-WCS transformation to render correctly.
    pub extrusion: Point3,
}

impl<'a> EntityCommon<'a> {
    /// Creates a new `EntityCommon` with default values.
    pub fn new() -> Self {
        Self {
            color: 256,     // BYLAYER
            lineweight: -2, // BYLAYER
            is_visible: true,
            extrusion: Point3 {
                x: 0.0,
                y: 0.0,
                z: 1.0,
            },
            ..Default::default()
        }
    }

    /// Feeds a single group code/value pair. Returns `true` if consumed.
    pub(crate) fn feed(&mut self, code: u16, val: &GroupValue<'a>) -> bool {
        match code {
            5 => {
                if let Some(s) = val.as_str_bytes() {
                    self.handle = s;
                }
                true
            }
            6 => {
                if let Some(s) = val.as_str_bytes() {
                    self.linetype_name = s;
                }
                true
            }
            8 => {
                if let Some(s) = val.as_str_bytes() {
                    self.layer = s;
                }
                true
            }
            62 => {
                if let Some(v) = val.as_i16() {
                    self.color = v;
                }
                true
            }
            370 => {
                if let Some(v) = val.as_i16() {
                    self.lineweight = v;
                }
                true
            }
            60 => {
                if let Some(v) = val.as_i16() {
                    self.is_visible = v == 0;
                }
                true
            }
            420 => {
                if let Some(v) = val.as_i32() {
                    self.color_24_bit = v;
                }
                true
            }
            440 => {
                if let Some(v) = val.as_i32() {
                    self.transparency = v;
                }
                true
            }
            210 => {
                if let Some(v) = val.as_f64() {
                    self.extrusion.x = v;
                }
                true
            }
            220 => {
                if let Some(v) = val.as_f64() {
                    self.extrusion.y = v;
                }
                true
            }
            230 => {
                if let Some(v) = val.as_f64() {
                    self.extrusion.z = v;
                }
                true
            }
            // Common metadata codes that aren't entity fields. Consumed to
            // avoid passing to entity-specific feed. Note: group 100 (subclass
            // markers) is NOT consumed here — some entity types (e.g. Attrib)
            // need to track subclass boundaries.
            330 | 102 | 360 => true,
            _ => false,
        }
    }
}

/// A parsed DXF entity.
#[derive(Debug, Clone)]
pub enum Entity<'a> {
    /// A LINE entity.
    Line(EntityCommon<'a>, Line),
    /// A CIRCLE entity.
    Circle(EntityCommon<'a>, Circle),
    /// An ARC entity.
    Arc(EntityCommon<'a>, Arc),
    /// An ELLIPSE entity.
    Ellipse(EntityCommon<'a>, Ellipse),
    /// An LWPOLYLINE entity.
    LwPolyline(EntityCommon<'a>, LwPolyline),
    /// A POLYLINE entity.
    Polyline(EntityCommon<'a>, Polyline),
    /// A SPLINE entity.
    Spline(EntityCommon<'a>, Spline),
    /// A SOLID entity.
    Solid(EntityCommon<'a>, Solid),
    /// A TRACE entity.
    Trace(EntityCommon<'a>, Trace),
    /// A 3DFACE entity.
    Face3d(EntityCommon<'a>, Face3d),
    /// A POINT entity.
    Point(EntityCommon<'a>, DxfPoint),
    /// A TEXT entity.
    Text(EntityCommon<'a>, Text<'a>),
    /// An MTEXT entity.
    MText(EntityCommon<'a>, MText<'a>),
    /// An INSERT entity.
    Insert(EntityCommon<'a>, Insert<'a>),
    /// A DIMENSION entity.
    Dimension(EntityCommon<'a>, Dimension<'a>),
    /// A LEADER entity.
    Leader(EntityCommon<'a>, Leader<'a>),
    /// A HATCH entity.
    Hatch(EntityCommon<'a>, Hatch<'a>),
    /// A WIPEOUT entity.
    Wipeout(EntityCommon<'a>, Wipeout),
    /// An ATTRIB entity.
    Attrib(EntityCommon<'a>, Attrib<'a>),
    /// An ATTDEF entity.
    AttDef(EntityCommon<'a>, AttDef<'a>),
    /// An unrecognized entity type.
    ///
    /// The second field is the DXF type name.
    Unknown(EntityCommon<'a>, &'a [u8]),
}

impl<'a> Entity<'a> {
    /// Returns a reference to the common properties.
    pub fn common(&self) -> &EntityCommon<'a> {
        match self {
            Entity::Line(c, _)
            | Entity::Circle(c, _)
            | Entity::Arc(c, _)
            | Entity::Ellipse(c, _)
            | Entity::LwPolyline(c, _)
            | Entity::Polyline(c, _)
            | Entity::Spline(c, _)
            | Entity::Solid(c, _)
            | Entity::Trace(c, _)
            | Entity::Face3d(c, _)
            | Entity::Point(c, _)
            | Entity::Text(c, _)
            | Entity::MText(c, _)
            | Entity::Insert(c, _)
            | Entity::Dimension(c, _)
            | Entity::Leader(c, _)
            | Entity::Hatch(c, _)
            | Entity::Wipeout(c, _)
            | Entity::Attrib(c, _)
            | Entity::AttDef(c, _)
            | Entity::Unknown(c, _) => c,
        }
    }
}