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
//! Text entity

use super::{Entity, EntityCommon};
use crate::types::{BoundingBox3D, Vector3};

pub(crate) fn estimated_width_in_heights(s: &str) -> f64 {
    s.chars()
        .map(|c| match c as u32 {
            0x1100..=0x115F
            | 0x2E80..=0xA4CF
            | 0xAC00..=0xD7A3
            | 0xF900..=0xFAFF
            | 0xFE30..=0xFE4F
            | 0xFF00..=0xFF60
            | 0xFFE0..=0xFFE6 => 1.0,
            _ => 0.6,
        })
        .sum()
}

/// Text horizontal alignment
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum TextHorizontalAlignment {
    /// Left aligned
    Left,
    /// Center aligned
    Center,
    /// Right aligned
    Right,
    /// Aligned (fit between two points)
    Aligned,
    /// Middle (centered horizontally and vertically)
    Middle,
    /// Fit (fit between two points, adjust height)
    Fit,
}

/// Text vertical alignment
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum TextVerticalAlignment {
    /// Baseline
    Baseline,
    /// Bottom
    Bottom,
    /// Middle
    Middle,
    /// Top
    Top,
}

/// A single-line text entity
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Text {
    /// Common entity data
    pub common: EntityCommon,
    /// Text content
    pub value: String,
    /// Insertion point (first alignment point)
    pub insertion_point: Vector3,
    /// Second alignment point (for aligned/fit text)
    pub alignment_point: Option<Vector3>,
    /// Text height
    pub height: f64,
    /// Rotation angle in radians
    pub rotation: f64,
    /// Width scale factor
    pub width_factor: f64,
    /// Oblique angle in radians
    pub oblique_angle: f64,
    /// Text style name
    pub style: String,
    /// Horizontal alignment
    pub horizontal_alignment: TextHorizontalAlignment,
    /// Vertical alignment
    pub vertical_alignment: TextVerticalAlignment,
    /// Normal vector
    pub normal: Vector3,
    /// Thickness / extrusion depth (DXF 39).
    pub thickness: f64,
    /// Text generation flags (DXF 71): bit 2 = backward (mirrored in X),
    /// bit 4 = upside-down (mirrored in Y).
    pub generation_flags: i16,
}

impl Text {
    /// Create a new text entity
    pub fn new() -> Self {
        Text {
            common: EntityCommon::new(),
            value: String::new(),
            insertion_point: Vector3::ZERO,
            alignment_point: None,
            height: 1.0,
            rotation: 0.0,
            width_factor: 1.0,
            oblique_angle: 0.0,
            style: "Standard".to_string(),
            horizontal_alignment: TextHorizontalAlignment::Left,
            vertical_alignment: TextVerticalAlignment::Baseline,
            normal: Vector3::UNIT_Z,
            thickness: 0.0,
            generation_flags: 0,
        }
    }

    /// Create a new text with value and position
    pub fn with_value(value: impl Into<String>, position: Vector3) -> Self {
        Text {
            value: value.into(),
            insertion_point: position,
            ..Self::new()
        }
    }

    /// Set the text height
    pub fn with_height(mut self, height: f64) -> Self {
        self.height = height;
        self
    }

    /// Set the rotation angle
    pub fn with_rotation(mut self, rotation: f64) -> Self {
        self.rotation = rotation;
        self
    }
}

impl Default for Text {
    fn default() -> Self {
        Self::new()
    }
}

impl Entity for Text {
    fn common(&self) -> &super::EntityCommon {
        &self.common
    }

    fn common_mut(&mut self) -> &mut super::EntityCommon {
        &mut self.common
    }

    fn bounding_box(&self) -> BoundingBox3D {
        // Simplified bounding box based on insertion point and height
        let width = estimated_width_in_heights(&self.value) * self.height * self.width_factor;
        BoundingBox3D::new(
            self.insertion_point,
            Vector3::new(
                self.insertion_point.x + width,
                self.insertion_point.y + self.height,
                self.insertion_point.z,
            ),
        )
        .ocs_to_wcs(self.normal)
    }

    fn translate(&mut self, offset: Vector3) {
        super::translate::translate_text(self, offset);
    }

    fn entity_type(&self) -> &'static str {
        "TEXT"
    }

    fn apply_transform(&mut self, transform: &crate::types::Transform) {
        super::transform::transform_text(self, transform);
    }

    fn apply_mirror(&mut self, transform: &crate::types::Transform) {
        super::mirror::mirror_text(self, transform);
    }

    fn apply_mirror_with_options(
        &mut self,
        transform: &crate::types::Transform,
        options: super::MirrorOptions,
    ) {
        super::mirror::mirror_text_with_options(self, transform, options);
    }
}