use super::TableEntry;
use crate::types::Handle;
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct LineTypeElement {
pub length: f64,
pub complex: Option<LineTypeComplexData>,
}
impl LineTypeElement {
pub fn dash(length: f64) -> Self {
LineTypeElement { length: length.abs(), complex: None }
}
pub fn space(length: f64) -> Self {
LineTypeElement { length: -length.abs(), complex: None }
}
pub fn dot() -> Self {
LineTypeElement { length: 0.0, complex: None }
}
pub fn is_dash(&self) -> bool {
self.length > 0.0
}
pub fn is_space(&self) -> bool {
self.length < 0.0
}
pub fn is_dot(&self) -> bool {
self.length == 0.0
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct LineType {
pub handle: Handle,
pub name: String,
pub description: String,
pub elements: Vec<LineTypeElement>,
pub pattern_length: f64,
pub alignment: char,
pub xref_dependent: bool,
}
impl LineType {
pub fn new(name: impl Into<String>) -> Self {
LineType {
handle: Handle::NULL,
name: name.into(),
description: String::new(),
elements: Vec::new(),
pattern_length: 0.0,
alignment: 'A',
xref_dependent: false,
}
}
pub fn continuous() -> Self {
LineType {
handle: Handle::NULL,
name: "Continuous".to_string(),
description: "Solid line".to_string(),
elements: Vec::new(),
pattern_length: 0.0,
alignment: 'A',
xref_dependent: false,
}
}
pub fn by_layer() -> Self {
LineType {
handle: Handle::NULL,
name: "ByLayer".to_string(),
description: String::new(),
elements: Vec::new(),
pattern_length: 0.0,
alignment: 'A',
xref_dependent: false,
}
}
pub fn by_block() -> Self {
LineType {
handle: Handle::NULL,
name: "ByBlock".to_string(),
description: String::new(),
elements: Vec::new(),
pattern_length: 0.0,
alignment: 'A',
xref_dependent: false,
}
}
pub fn dashed() -> Self {
let mut lt = LineType::new("Dashed");
lt.description = "__ __ __ __ __ __".to_string();
lt.add_element(LineTypeElement::dash(0.5));
lt.add_element(LineTypeElement::space(0.25));
lt.pattern_length = 0.75;
lt
}
pub fn dotted() -> Self {
let mut lt = LineType::new("Dotted");
lt.description = ". . . . . . . .".to_string();
lt.add_element(LineTypeElement::dot());
lt.add_element(LineTypeElement::space(0.25));
lt.pattern_length = 0.25;
lt
}
pub fn add_element(&mut self, element: LineTypeElement) {
self.elements.push(element);
}
pub fn element_count(&self) -> usize {
self.elements.len()
}
pub fn is_continuous(&self) -> bool {
self.elements.is_empty()
}
pub fn is_complex(&self) -> bool {
self.elements.iter().any(|e| e.complex.is_some())
}
}
impl TableEntry for LineType {
fn handle(&self) -> Handle {
self.handle
}
fn set_handle(&mut self, handle: Handle) {
self.handle = handle;
}
fn name(&self) -> &str {
&self.name
}
fn set_name(&mut self, name: String) {
self.name = name;
}
fn is_standard(&self) -> bool {
matches!(
self.name.as_str(),
"Continuous" | "ByLayer" | "ByBlock"
)
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum LineTypeComplexContent {
Shape { shape_number: i16 },
Text { text: String },
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct LineTypeComplexData {
pub content: LineTypeComplexContent,
pub style_handle: Handle,
pub scale: f64,
pub rotation: f64,
pub absolute_rotation: bool,
pub offset: [f64; 2],
}
impl Default for LineTypeComplexData {
fn default() -> Self {
Self {
content: LineTypeComplexContent::Shape { shape_number: 0 },
style_handle: Handle::NULL,
scale: 1.0,
rotation: 0.0,
absolute_rotation: false,
offset: [0.0, 0.0],
}
}
}
impl LineTypeComplexData {
#[inline]
pub fn is_shape(&self) -> bool {
matches!(self.content, LineTypeComplexContent::Shape { .. })
}
#[inline]
pub fn is_text(&self) -> bool {
matches!(self.content, LineTypeComplexContent::Text { .. })
}
#[inline]
pub fn is_absolute_rotation(&self) -> bool {
self.absolute_rotation
}
#[inline]
pub fn shape_number(&self) -> Option<i16> {
match self.content {
LineTypeComplexContent::Shape { shape_number } => Some(shape_number),
LineTypeComplexContent::Text { .. } => None,
}
}
#[inline]
pub fn text(&self) -> Option<&str> {
match &self.content {
LineTypeComplexContent::Text { text } => Some(text),
LineTypeComplexContent::Shape { .. } => None,
}
}
pub(crate) fn ensure_shape(&mut self) {
if !matches!(self.content, LineTypeComplexContent::Shape { .. }) {
self.content = LineTypeComplexContent::Shape { shape_number: 0 };
}
}
pub(crate) fn ensure_text(&mut self) {
if !matches!(self.content, LineTypeComplexContent::Text { .. }) {
self.content = LineTypeComplexContent::Text { text: String::new() };
}
}
pub(crate) fn set_shape_number(&mut self, n: i16) {
self.ensure_shape();
if let LineTypeComplexContent::Shape { shape_number } = &mut self.content {
*shape_number = n;
}
}
pub(crate) fn set_text(&mut self, text: String) {
self.ensure_text();
if let LineTypeComplexContent::Text { text: t } = &mut self.content {
*t = text;
}
}
pub(crate) fn apply_dxf_flags(&mut self, flags: i16) {
self.absolute_rotation = flags & 0x01 != 0;
if flags & 0x04 != 0 {
self.ensure_shape();
} else if flags & 0x02 != 0 {
self.ensure_text();
}
}
}
impl LineTypeElement {
pub fn complex_mut(&mut self) -> &mut LineTypeComplexData {
self.complex.get_or_insert_with(LineTypeComplexData::default)
}
pub fn is_shape(&self) -> bool {
self.complex.as_ref().map_or(false, |c| c.is_shape())
}
pub fn is_text(&self) -> bool {
self.complex.as_ref().map_or(false, |c| c.is_text())
}
pub fn is_absolute_rotation(&self) -> bool {
self.complex.as_ref().map_or(false, |c| c.is_absolute_rotation())
}
pub fn is_complex(&self) -> bool {
self.complex.is_some()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_complex_data_shape() {
let c = LineTypeComplexData {
content: LineTypeComplexContent::Shape { shape_number: 42 },
style_handle: Handle::new(100),
scale: 2.0,
rotation: 45.0,
absolute_rotation: false,
offset: [1.0, 2.0],
};
assert!(c.is_shape());
assert!(!c.is_text());
assert!(!c.is_absolute_rotation());
assert_eq!(c.shape_number(), Some(42));
assert_eq!(c.text(), None);
}
#[test]
fn test_complex_data_text() {
let c = LineTypeComplexData {
content: LineTypeComplexContent::Text { text: "X".to_string() },
style_handle: Handle::new(50),
scale: 1.0,
rotation: 0.0,
absolute_rotation: false,
offset: [0.0, 0.5],
};
assert!(!c.is_shape());
assert!(c.is_text());
assert!(!c.is_absolute_rotation());
assert_eq!(c.shape_number(), None);
assert_eq!(c.text(), Some("X"));
}
#[test]
fn test_complex_data_absolute_rotation() {
let c = LineTypeComplexData {
content: LineTypeComplexContent::Shape { shape_number: 1 },
style_handle: Handle::NULL,
scale: 1.0,
rotation: 90.0,
absolute_rotation: true,
offset: [0.0, 0.0],
};
assert!(c.is_shape());
assert!(c.is_absolute_rotation());
}
#[test]
fn test_element_complex_methods() {
let mut elem = LineTypeElement::dash(1.0);
assert!(!elem.is_complex());
assert!(!elem.is_shape());
assert!(!elem.is_text());
elem.complex = Some(LineTypeComplexData {
content: LineTypeComplexContent::Shape { shape_number: 5 },
..Default::default()
});
assert!(elem.is_complex());
assert!(elem.is_shape());
assert!(!elem.is_text());
}
#[test]
fn test_linetype_is_complex() {
let mut lt = LineType::continuous();
assert!(!lt.is_complex());
let mut elem = LineTypeElement::dot();
elem.complex = Some(LineTypeComplexData {
content: LineTypeComplexContent::Text { text: "A".to_string() },
..Default::default()
});
lt.elements.push(elem);
assert!(lt.is_complex());
}
#[test]
fn test_complex_default() {
let c = LineTypeComplexData::default();
assert!(c.is_shape());
assert_eq!(c.shape_number(), Some(0));
assert!(c.style_handle.is_null());
assert!(!c.is_text());
assert!(!c.is_absolute_rotation());
}
#[test]
fn test_dxf_flag_helpers() {
let mut c = LineTypeComplexData::default();
c.apply_dxf_flags(0x04);
assert!(c.is_shape());
assert!(!c.is_text());
c.apply_dxf_flags(0x02 | 0x01);
assert!(c.is_text());
assert!(c.is_absolute_rotation());
}
#[test]
fn test_ensure_shape_switches_content() {
let mut c = LineTypeComplexData {
content: LineTypeComplexContent::Text { text: "hi".to_string() },
..Default::default()
};
c.ensure_shape();
assert!(c.is_shape());
}
#[test]
fn test_ensure_text_switches_content() {
let mut c = LineTypeComplexData::default();
c.ensure_text();
assert!(c.is_text());
assert_eq!(c.text(), Some(""));
}
#[test]
fn test_element_constructors_no_complex() {
assert!(!LineTypeElement::dash(1.0).is_complex());
assert!(!LineTypeElement::space(0.5).is_complex());
assert!(!LineTypeElement::dot().is_complex());
}
}