use crate::error::Result;
use crate::io::dxf::{DxfCode, GroupCodeValueType};
use crate::types::Vector3;
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum CodePairValue {
None,
Int(i64),
Double(f64),
Bool(bool),
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DxfCodePair {
pub code: i32,
pub dxf_code: DxfCode,
pub value_string: String,
typed_value: CodePairValue,
}
#[derive(Debug, Clone, Default)]
pub struct DxfStreamContext {
pub source_offset: Option<u64>,
pub source_line: Option<usize>,
pub record_type: Option<String>,
pub record_handle: Option<u64>,
}
impl DxfCodePair {
pub fn new(code: i32, value_string: String) -> Self {
let dxf_code = DxfCode::from_i32(code);
let value_type = GroupCodeValueType::from_raw_code(code);
let typed_value = match value_type {
GroupCodeValueType::Int16
| GroupCodeValueType::Int32
| GroupCodeValueType::Int64
| GroupCodeValueType::Byte => match value_string.trim().parse::<i64>() {
Ok(v) => CodePairValue::Int(v),
Err(_) => CodePairValue::None,
},
GroupCodeValueType::Double => match value_string.trim().parse::<f64>() {
Ok(v) => CodePairValue::Double(v),
Err(_) => CodePairValue::None,
},
GroupCodeValueType::Bool => match value_string.trim().parse::<i32>() {
Ok(v) => CodePairValue::Bool(v != 0),
Err(_) => CodePairValue::None,
},
_ => CodePairValue::None,
};
Self {
code,
dxf_code,
value_string,
typed_value,
}
}
pub(crate) fn new_typed(code: i32, value_string: String, typed_value: CodePairValue) -> Self {
let dxf_code = DxfCode::from_i32(code);
Self {
code,
dxf_code,
value_string,
typed_value,
}
}
#[allow(dead_code)]
pub fn as_string(&self) -> &str {
&self.value_string
}
#[allow(dead_code)]
pub fn as_int(&self) -> Option<i64> {
match self.typed_value {
CodePairValue::Int(v) => Some(v),
_ => None,
}
}
pub fn as_i16(&self) -> Option<i16> {
match self.typed_value {
CodePairValue::Int(v) => i16::try_from(v).ok(),
_ => None,
}
}
#[allow(dead_code)]
pub fn as_i32(&self) -> Option<i32> {
match self.typed_value {
CodePairValue::Int(v) => i32::try_from(v).ok(),
_ => None,
}
}
pub fn as_i32_bits(&self) -> Option<i32> {
match self.typed_value {
CodePairValue::Int(v) => i32::try_from(v)
.ok()
.or_else(|| u32::try_from(v).ok().map(|bits| bits as i32)),
_ => None,
}
}
pub fn as_double(&self) -> Option<f64> {
match self.typed_value {
CodePairValue::Double(v) => Some(v),
_ => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
match self.typed_value {
CodePairValue::Bool(v) => Some(v),
_ => None,
}
}
#[allow(dead_code)]
pub fn as_handle(&self) -> Option<u64> {
u64::from_str_radix(self.value_string.trim(), 16).ok()
}
}
pub trait DxfStreamReader {
fn read_pair(&mut self) -> Result<Option<DxfCodePair>>;
#[allow(dead_code)]
fn peek_code(&mut self) -> Result<Option<i32>>;
fn push_back(&mut self, pair: DxfCodePair);
#[allow(dead_code)]
fn reset(&mut self) -> Result<()>;
fn set_encoding(&mut self, _encoding: &'static encoding_rs::Encoding) {
}
fn diagnostic_context(&self) -> DxfStreamContext {
DxfStreamContext::default()
}
}
pub struct PointReader {
x: Option<f64>,
y: Option<f64>,
z: Option<f64>,
group: Option<usize>,
}
impl PointReader {
pub fn new() -> Self {
Self {
x: None,
y: None,
z: None,
group: None,
}
}
pub fn add_coordinate(&mut self, pair: &DxfCodePair) -> bool {
if let Some(axis) = GroupCodeValueType::coordinate_axis(pair.dxf_code) {
let coord_group = GroupCodeValueType::coordinate_group(pair.dxf_code);
if self.group.is_some() && self.group != coord_group {
return false;
}
self.group = coord_group;
if let Some(value) = pair.as_double() {
match axis {
0 => self.x = Some(value),
1 => self.y = Some(value),
2 => self.z = Some(value),
_ => return false,
}
return true;
}
}
false
}
#[allow(dead_code)]
pub fn is_complete(&self) -> bool {
self.x.is_some() && self.y.is_some()
}
pub fn get_point(&self) -> Option<Vector3> {
if let (Some(x), Some(y)) = (self.x, self.y) {
Some(Vector3::new(x, y, self.z.unwrap_or(0.0)))
} else {
None
}
}
#[allow(dead_code)]
pub fn reset(&mut self) {
self.x = None;
self.y = None;
self.z = None;
self.group = None;
}
}
impl Default for PointReader {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn true_color_pair(text: &str) -> DxfCodePair {
DxfCodePair::new(420, text.to_string())
}
#[test]
fn as_i32_bits_keeps_what_as_i32_already_kept() {
for text in [
"0",
"16746496",
"-1023440896",
"-1",
"2147483647",
"-2147483648",
] {
let pair = true_color_pair(text);
assert_eq!(pair.as_i32_bits(), pair.as_i32(), "spelled {text}");
assert!(pair.as_i32_bits().is_some(), "spelled {text}");
}
}
#[test]
fn as_i32_bits_accepts_the_unsigned_half_of_the_range() {
for (unsigned, signed) in [
("2147483648", i32::MIN),
("3271526400", -1_023_440_896), ("4294967295", -1),
] {
let pair = true_color_pair(unsigned);
assert_eq!(pair.as_i32(), None, "as_i32 should still reject {unsigned}");
assert_eq!(pair.as_i32_bits(), Some(signed), "spelled {unsigned}");
}
}
#[test]
fn as_i32_bits_refuses_what_is_not_a_32_bit_pattern() {
assert_eq!(true_color_pair("4294967296").as_i32_bits(), None);
assert_eq!(true_color_pair("-2147483649").as_i32_bits(), None);
assert_eq!(true_color_pair("not a number").as_i32_bits(), None);
assert_eq!(DxfCodePair::new(40, "1.5".to_string()).as_i32_bits(), None);
}
}