use std::convert::{From, Into};
use std::i64;
use std::io::Write;
use std::num::FpCategory;
use conv::TryFrom;
use num_rational::Ratio;
use crate::errors::{GerberError, GerberResult};
use crate::traits::PartialGerberCode;
macro_rules! impl_xy_partial_gerbercode {
($class:ty, $x:expr, $y: expr) => {
impl<W: Write> PartialGerberCode<W> for $class {
fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
if let Some(x) = self.x {
write!(writer, "{}{}", $x, x.gerber(&self.format)?)?;
}
if let Some(y) = self.y {
write!(writer, "{}{}", $y, y.gerber(&self.format)?)?;
}
Ok(())
}
}
};
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct CoordinateFormat {
pub integer: u8,
pub decimal: u8,
}
impl CoordinateFormat {
pub fn new(integer: u8, decimal: u8) -> Self {
CoordinateFormat { integer, decimal }
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct CoordinateNumber {
nano: i64,
}
impl CoordinateNumber {
pub fn new(nano: i64) -> Self {
CoordinateNumber { nano }
}
}
const DECIMAL_PLACES_CHARS: u8 = 6;
const DECIMAL_PLACES_FACTOR: i64 = 1_000_000;
impl TryFrom<f64> for CoordinateNumber {
type Err = GerberError;
fn try_from(val: f64) -> Result<Self, Self::Err> {
match val.classify() {
FpCategory::Nan => Err(GerberError::ConversionError("Value is NaN".into())),
FpCategory::Infinite => Err(GerberError::ConversionError("Value is infinite".into())),
FpCategory::Zero | FpCategory::Subnormal => Ok(CoordinateNumber { nano: 0 }),
FpCategory::Normal => {
let multiplied = val * DECIMAL_PLACES_FACTOR as f64;
if (multiplied > i64::MAX as f64) || (multiplied < i64::MIN as f64) {
Err(GerberError::ConversionError(
"Value is out of bounds".into(),
))
} else {
Ok(CoordinateNumber {
nano: multiplied as i64,
})
}
}
}
}
}
impl Into<f64> for CoordinateNumber {
fn into(self) -> f64 {
(self.nano as f64) / DECIMAL_PLACES_FACTOR as f64
}
}
macro_rules! impl_from_integer {
($class:ty) => {
impl From<$class> for CoordinateNumber {
fn from(val: $class) -> Self {
CoordinateNumber {
nano: val as i64 * DECIMAL_PLACES_FACTOR,
}
}
}
};
}
impl_from_integer!(i8);
impl_from_integer!(i16);
impl_from_integer!(i32);
impl_from_integer!(u8);
impl_from_integer!(u16);
impl CoordinateNumber {
pub fn gerber(&self, format: &CoordinateFormat) -> Result<String, GerberError> {
if format.decimal > DECIMAL_PLACES_CHARS {
return Err(GerberError::CoordinateFormatError(
"Invalid precision: Too high!".into(),
));
}
if self.nano.abs() >= 10_i64.pow((format.integer + DECIMAL_PLACES_CHARS) as u32) {
return Err(GerberError::CoordinateFormatError(
"Number is too large for chosen format!".into(),
));
}
let divisor: i64 = 10_i64.pow((DECIMAL_PLACES_CHARS - format.decimal) as u32);
let number: i64 = Ratio::new(self.nano, divisor).round().to_integer();
Ok(number.to_string())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Coordinates {
pub x: Option<CoordinateNumber>,
pub y: Option<CoordinateNumber>,
pub format: CoordinateFormat,
}
impl Coordinates {
pub fn new<T, U>(x: T, y: U, format: CoordinateFormat) -> Self
where
T: Into<CoordinateNumber>,
U: Into<CoordinateNumber>,
{
Coordinates {
x: Some(x.into()),
y: Some(y.into()),
format,
}
}
pub fn at_x<T>(x: T, format: CoordinateFormat) -> Self
where
T: Into<CoordinateNumber>,
{
Coordinates {
x: Some(x.into()),
y: None,
format,
}
}
pub fn at_y<T>(y: T, format: CoordinateFormat) -> Self
where
T: Into<CoordinateNumber>,
{
Coordinates {
x: None,
y: Some(y.into()),
format,
}
}
}
impl_xy_partial_gerbercode!(Coordinates, "X", "Y");
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CoordinateOffset {
pub x: Option<CoordinateNumber>,
pub y: Option<CoordinateNumber>,
pub format: CoordinateFormat,
}
impl CoordinateOffset {
pub fn new<T, U>(x: T, y: U, format: CoordinateFormat) -> Self
where
T: Into<CoordinateNumber>,
U: Into<CoordinateNumber>,
{
CoordinateOffset {
x: Some(x.into()),
y: Some(y.into()),
format,
}
}
pub fn at_x<T>(x: T, format: CoordinateFormat) -> Self
where
T: Into<CoordinateNumber>,
{
CoordinateOffset {
x: Some(x.into()),
y: None,
format,
}
}
pub fn at_y<T>(y: T, format: CoordinateFormat) -> Self
where
T: Into<CoordinateNumber>,
{
CoordinateOffset {
x: None,
y: Some(y.into()),
format,
}
}
}
impl_xy_partial_gerbercode!(CoordinateOffset, "I", "J");
#[cfg(test)]
mod test {
use super::*;
use std::f64;
use std::io::BufWriter;
use conv::TryFrom;
use crate::traits::PartialGerberCode;
#[test]
fn test_from_i8() {
let a = CoordinateNumber { nano: 13000000 };
let b = CoordinateNumber::from(13i8);
assert_eq!(a, b);
let c = CoordinateNumber { nano: -99000000 };
let d = CoordinateNumber::from(-99i8);
assert_eq!(c, d);
}
#[test]
fn test_from_i32() {
let a = CoordinateNumber { nano: 13000000 };
let b = CoordinateNumber::from(13);
assert_eq!(a, b);
let c = CoordinateNumber { nano: -998000000 };
let d = CoordinateNumber::from(-998);
assert_eq!(c, d);
}
#[test]
fn test_try_from_f64_success() {
let a = CoordinateNumber { nano: 1375000i64 };
let b = CoordinateNumber::try_from(1.375f64).unwrap();
assert_eq!(a, b);
let c = CoordinateNumber {
nano: 123456888888i64,
};
let d = CoordinateNumber::try_from(123456.888888f64).unwrap();
assert_eq!(c, d);
let e = CoordinateNumber { nano: 0i64 };
let f = CoordinateNumber::try_from(0f64).unwrap();
assert_eq!(e, f);
let g = CoordinateNumber { nano: -12345678 };
let h = CoordinateNumber::try_from(-12.345678).unwrap();
assert_eq!(g, h);
}
#[test]
fn test_try_from_f64_fail() {
let cn1 = CoordinateNumber::try_from(f64::NAN);
assert!(cn1.is_err());
let cn2 = CoordinateNumber::try_from(f64::INFINITY);
assert!(cn2.is_err());
let cn3 = CoordinateNumber::try_from(f64::MAX - 1.0);
assert!(cn3.is_err());
let cn4 = CoordinateNumber::try_from(f64::MIN + 1.0);
assert!(cn4.is_err());
}
#[test]
fn test_into_f64() {
let a: f64 = CoordinateNumber { nano: 1375000i64 }.into();
let b = 1.375f64;
assert_eq!(a, b);
let c: f64 = CoordinateNumber {
nano: 123456888888i64,
}
.into();
let d = 123456.888888f64;
assert_eq!(c, d);
let e: f64 = CoordinateNumber { nano: 0i64 }.into();
let f = 0f64;
assert_eq!(e, f);
}
#[test]
fn test_coordinate_number_new() {
let nano = 5;
let cn1 = CoordinateNumber::new(nano);
assert_eq!(cn1.nano, nano);
}
#[test]
fn test_formatted_zero() {
let cf1 = CoordinateFormat::new(6, 6);
let cf2 = CoordinateFormat::new(2, 4);
let a = CoordinateNumber { nano: 0 }.gerber(&cf1).unwrap();
let b = CoordinateNumber { nano: 0 }.gerber(&cf2).unwrap();
assert_eq!(a, "0".to_string());
assert_eq!(b, "0".to_string());
}
#[test]
fn test_formatted_decimal_zero() {
let cf1 = CoordinateFormat::new(6, 6);
let cf2 = CoordinateFormat::new(2, 4);
let a = CoordinateNumber { nano: 10000000 }.gerber(&cf1).unwrap();
let b = CoordinateNumber { nano: 20000000 }.gerber(&cf2).unwrap();
assert_eq!(a, "10000000".to_string());
assert_eq!(b, "200000".to_string());
}
#[test]
fn test_formatted_65() {
let cf = CoordinateFormat::new(6, 5);
let d = CoordinateNumber { nano: 123456789012 }.gerber(&cf).unwrap();
assert_eq!(d, "12345678901".to_string());
}
#[test]
fn test_formatted_54() {
let cf = CoordinateFormat::new(5, 4);
let d = CoordinateNumber { nano: 12345678901 }.gerber(&cf).unwrap();
assert_eq!(d, "123456789".to_string());
}
#[test]
fn test_formatted_number_too_large() {
let cf = CoordinateFormat::new(4, 5);
let d = CoordinateNumber { nano: 12345000000 }.gerber(&cf);
assert!(d.is_err());
}
#[test]
fn test_formatted_negative_number_too_large() {
let cf = CoordinateFormat::new(4, 5);
let d = CoordinateNumber { nano: -12345000000 }.gerber(&cf);
assert!(d.is_err());
}
#[test]
fn test_formatted_44_rounding() {
let cf = CoordinateFormat::new(4, 4);
let d = CoordinateNumber { nano: 1234432199 }.gerber(&cf).unwrap();
assert_eq!(d, "12344322".to_string());
}
#[test]
fn test_formatted_negative_rounding() {
let cf = CoordinateFormat::new(6, 4);
let d = CoordinateNumber {
nano: -123456789099,
}
.gerber(&cf)
.unwrap();
assert_eq!(d, "-1234567891".to_string());
}
#[test]
fn test_coordinates_into() {
let cf = CoordinateFormat::new(2, 4);
let c1 = Coordinates::new(CoordinateNumber::from(1), CoordinateNumber::from(2), cf);
let c2 = Coordinates::new(1, 2, cf);
assert_eq!(c1, c2);
}
#[test]
fn test_coordinates_into_mixed() {
let cf = CoordinateFormat::new(2, 4);
let c1 = Coordinates::new(CoordinateNumber::from(1), 2, cf);
let c2 = Coordinates::new(1, 2, cf);
assert_eq!(c1, c2);
}
#[test]
fn test_coordinates() {
macro_rules! assert_coords {
($coords:expr, $result:expr) => {{
assert_partial_code!($coords, $result);
}};
}
let cf44 = CoordinateFormat::new(4, 4);
let cf46 = CoordinateFormat::new(4, 6);
assert_coords!(Coordinates::new(10, 20, cf44), "X100000Y200000");
assert_coords!(
Coordinates {
x: None,
y: None,
format: cf44
},
""
);
assert_coords!(Coordinates::at_x(10, cf44), "X100000");
assert_coords!(Coordinates::at_y(20, cf46), "Y20000000");
assert_coords!(Coordinates::new(0, -400, cf44), "X0Y-4000000");
}
#[test]
fn test_offset() {
macro_rules! assert_coords {
($coords:expr, $result:expr) => {{
assert_partial_code!($coords, $result);
}};
}
let cf44 = CoordinateFormat::new(4, 4);
let cf55 = CoordinateFormat::new(5, 5);
let cf66 = CoordinateFormat::new(6, 6);
assert_coords!(CoordinateOffset::new(10, 20, cf44), "I100000J200000");
assert_coords!(
CoordinateOffset {
x: None,
y: None,
format: cf44
},
""
);
assert_coords!(CoordinateOffset::at_x(10, cf66), "I10000000");
assert_coords!(CoordinateOffset::at_y(20, cf55), "J2000000");
assert_coords!(CoordinateOffset::new(0, -400, cf44), "I0J-4000000");
}
}