use bytes::Buf;
use clitype::*;
use std::fmt::Debug;
use std::mem::size_of;
pub mod util;
pub use util::*;
pub mod clitype {
use super::*;
pub trait CLIType
where
Self::Meta: Debug + Copy,
Self::Coord: Debug + Copy,
{
type Meta;
type Coord;
#[doc(hidden)]
const CMD_LAYER: u16;
#[doc(hidden)]
const CMD_PLINE: u16;
#[doc(hidden)]
const CMD_HATCH: u16;
#[doc(hidden)]
fn get_meta(buf: &mut &[u8], aligned: bool) -> Self::Meta;
#[doc(hidden)]
fn get_coord(buf: &mut &[u8], aligned: bool) -> Self::Coord;
#[doc(hidden)]
fn get_usize(buf: &mut &[u8], aligned: bool) -> usize;
}
#[derive(Debug)]
pub struct ShortCLI();
#[derive(Debug)]
pub struct LongCLI();
impl CLIType for ShortCLI {
type Meta = u16;
type Coord = u16;
const CMD_LAYER: u16 = 128;
const CMD_PLINE: u16 = 129;
const CMD_HATCH: u16 = 131;
fn get_meta(buf: &mut &[u8], aligned: bool) -> Self::Meta {
let t = buf.get_u16_le();
if aligned {
buf.advance(2)
};
return t;
}
fn get_coord(buf: &mut &[u8], aligned: bool) -> Self::Coord {
let t = buf.get_u16_le();
if aligned {
buf.advance(2)
};
return t;
}
fn get_usize(buf: &mut &[u8], aligned: bool) -> usize {
let t = buf.get_u16_le() as usize;
if aligned {
buf.advance(2)
};
return t;
}
}
impl CLIType for LongCLI {
type Meta = i32;
type Coord = f32;
const CMD_LAYER: u16 = 127;
const CMD_PLINE: u16 = 130;
const CMD_HATCH: u16 = 132;
fn get_meta(buf: &mut &[u8], _aligned: bool) -> Self::Meta {
buf.get_i32_le()
}
fn get_coord(buf: &mut &[u8], _aligned: bool) -> Self::Coord {
buf.get_f32_le()
}
fn get_usize(buf: &mut &[u8], _aligned: bool) -> usize {
buf.get_i32_le() as usize
}
}
}
pub trait Point<T: Copy> {
fn x(&self) -> T;
fn y(&self) -> T;
}
impl<T: Copy> Point<T> for [T; 2] {
#[inline]
fn x(&self) -> T {
self[0]
}
#[inline]
fn y(&self) -> T {
self[1]
}
}
pub trait Segment<T: Copy> {
fn start(&self) -> [T; 2];
fn end(&self) -> [T; 2];
}
impl<T: Copy> Segment<T> for [T; 4] {
#[inline]
fn start(&self) -> [T; 2] {
unsafe { *(&self[0..=1] as *const [T] as *const [T; 2]) }
}
#[inline]
fn end(&self) -> [T; 2] {
unsafe { *(&self[2..=3] as *const [T] as *const [T; 2]) }
}
}
#[derive(Debug, Clone)]
pub struct Loop<'a, T: CLIType> {
id: <T as CLIType>::Meta,
dir: <T as CLIType>::Meta,
points: &'a [<T as CLIType>::Coord],
}
impl<'a, T: CLIType> Loop<'a, T> {
pub fn iter(&'a self) -> ArrayChunksCopy<'a, <T as CLIType>::Coord, 2> {
ArrayChunksCopy::<'_, <T as CLIType>::Coord, 2>::new(self.points)
}
pub fn id(&self) -> <T as CLIType>::Meta {
self.id
}
pub fn dir(&self) -> <T as CLIType>::Meta {
self.dir
}
pub fn points(&'a self) -> &'a [<T as CLIType>::Coord] {
self.points
}
}
#[derive(Debug, Clone)]
pub struct Hatches<'a, T: CLIType> {
id: <T as CLIType>::Meta,
points: &'a [<T as CLIType>::Coord],
}
impl<'a, T: CLIType> Hatches<'a, T> {
pub fn iter(&'a self) -> ArrayChunks<'a, <T as CLIType>::Coord, 4> {
ArrayChunks::<'_, <T as CLIType>::Coord, 4>::new(self.points)
}
pub fn id(&self) -> <T as CLIType>::Meta {
self.id
}
pub fn points(&'a self) -> &'a [<T as CLIType>::Coord] {
self.points
}
}
#[derive(Debug, Clone)]
pub struct Layer<'a, T: CLIType> {
height: <T as CLIType>::Coord,
loops: Vec<Loop<'a, T>>,
hatches: Vec<Hatches<'a, T>>,
}
impl<'a, T: CLIType> Layer<'a, T> {
pub fn iter_loops(&'a self) -> std::slice::Iter<'a, Loop<'a, T>> {
self.loops.iter()
}
pub fn iter_hatches(&'a self) -> std::slice::Iter<'a, Hatches<'a, T>> {
self.hatches.iter()
}
pub fn height(&self) -> <T as CLIType>::Coord {
self.height
}
}
#[derive(Debug, Clone)]
pub struct Header {
pub binary: bool,
pub units: f64,
pub version: f32,
pub aligned: bool,
pub layers: Option<usize>,
}
#[derive(Debug)]
pub enum Error {
EmptyFile,
NoHeader,
HeaderInvalidUTF8,
UnsupportedGeometryFormat,
HeaderIncomplete(u8),
InvalidHeaderValue,
InvalidGeometryCommand(u16),
ElementOutsideLayer,
UnexpectedEOF,
TypeMismatch,
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
impl std::error::Error for Error {}
pub struct CLI<'a, T: CLIType> {
header: Header,
layers: Vec<Layer<'a, T>>,
}
impl<'a, T: CLIType> CLI<'a, T> {
pub fn new(raw: &'a [u8]) -> Result<Self, Error> {
let (mut gstart, header) = CLI::<T>::parse_header(&raw)?;
if !header.binary {
Err(Error::UnsupportedGeometryFormat)?;
}
if header.aligned {
gstart = 4 * ((gstart - 1) / 4) + 4;
}
let mut geom = &raw[gstart..];
let mut this = CLI {
header,
layers: Vec::new(),
};
let mut current_layer = None;
while this.next_element(&mut current_layer, &mut geom)? {}
Ok(this)
}
pub fn header(&self) -> &Header {
&self.header
}
#[inline]
fn parse_header(raw: &[u8]) -> Result<(usize, Header), Error> {
let pattern: &[u8] = b"$$HEADEREND";
if raw.len() <= pattern.len() {
Err(Error::EmptyFile)?;
}
let mut search_index = 0;
let mut pattern_index = 0;
while search_index < raw.len() && pattern_index < pattern.len() {
if raw[search_index] == pattern[pattern_index] {
pattern_index += 1;
} else {
pattern_index = 0;
}
search_index += 1;
}
if pattern_index < pattern.len() {
Err(Error::NoHeader)?;
}
let header =
std::str::from_utf8(&raw[0..search_index]).map_err(|_| Error::HeaderInvalidUTF8)?;
let mut items: [Option<&str>; 7] = [None, None, None, None, None, None, None];
for l in header.lines() {
let mut cleaned = l.trim();
if cleaned.starts_with("//") {
continue;
} if let Some(com) = cleaned.find("//") {
cleaned = &cleaned[0..com].trim();
}
let (command, _value) =
cleaned.split_at(cleaned.find("/").map(|x| x + 1).unwrap_or(cleaned.len()));
match command {
"$$BINARY" => items[0] = Some("0"),
"$$ASCII" => items[0] = Some("1"),
"$$UNITS/" => items[1] = Some(&cleaned["$$UNITS/".len()..]),
"$$VERSION/" => items[2] = Some(&cleaned["$$VERSION/".len()..]),
"$$LAYERS/" => items[5] = Some(&cleaned["$$LAYERS/".len()..]),
"$$ALIGN" => items[6] = Some(""),
_ => {}
}
}
for req in 0u8..=2 {
if items[req as usize].is_none() {
Err(Error::HeaderIncomplete(req))?;
}
}
Ok((
search_index,
Header {
binary: items[0].unwrap() == "0", units: items[1]
.unwrap()
.parse()
.map_err(|_| Error::InvalidHeaderValue)?,
version: items[2]
.unwrap()
.parse::<f32>()
.map(|x| x / 100.0)
.map_err(|_| Error::InvalidHeaderValue)?,
aligned: items[6].is_some(),
layers: if let Some(l) = items[5] {
Some(l.parse::<usize>().map_err(|_| Error::InvalidHeaderValue)?)
} else {
None
},
},
))
}
fn next_element(
&mut self,
current_layer: &mut Option<usize>,
buf: &mut &'a [u8],
) -> Result<bool, Error> {
let aligned = self.header.aligned;
let coord_size: usize = size_of::<<T as CLIType>::Coord>();
let meta_size: usize = size_of::<<T as CLIType>::Meta>();
let cmd = buf.get_u16_le();
if aligned {
buf.advance(2)
};
match cmd {
127 | 128 => {
if cmd != T::CMD_LAYER {
Err(Error::TypeMismatch)?;
}
CLI::<T>::expect_eof(buf, coord_size + aligned as usize * 2)?;
let l = Layer {
height: <T as CLIType>::get_coord(buf, aligned),
loops: vec![],
hatches: vec![],
};
self.layers.push(l);
if let Some(layer) = current_layer {
*current_layer = Some(*layer + 1);
} else {
*current_layer = Some(0);
}
}
129 | 130 => {
if cmd != T::CMD_PLINE {
Err(Error::TypeMismatch)?;
}
CLI::<T>::expect_eof(buf, 3 * (meta_size + aligned as usize * 2))?;
let id = T::get_meta(buf, aligned);
let dir = T::get_meta(buf, aligned);
let n_pts = T::get_usize(buf, aligned) * 2;
CLI::<T>::expect_eof(buf, coord_size * n_pts)?;
let points = CLI::<T>::cast_slice(n_pts, buf);
buf.advance(coord_size * n_pts);
if let Some(l) = current_layer {
self.layers[*l].loops.push(Loop { id, dir, points });
} else {
Err(Error::ElementOutsideLayer)?;
}
}
131 | 132 => {
if cmd != T::CMD_HATCH {
Err(Error::TypeMismatch)?;
}
CLI::<T>::expect_eof(buf, 2 * (meta_size + aligned as usize * 2))?;
let id = T::get_meta(buf, aligned);
let n_pts = T::get_usize(buf, aligned) * 4;
CLI::<T>::expect_eof(buf, coord_size * n_pts)?;
let points = CLI::<T>::cast_slice(n_pts, buf);
buf.advance(coord_size * n_pts);
if let Some(l) = current_layer {
self.layers[*l].hatches.push(Hatches { id, points });
} else {
Err(Error::ElementOutsideLayer)?;
}
}
_ => return Err(Error::InvalidGeometryCommand(cmd)),
}
return Ok(buf.len() > 0);
}
fn cast_slice<A>(count: usize, floats: &'a [u8]) -> &'a [A] {
unsafe { std::slice::from_raw_parts(floats.as_ptr() as *const _, count) }
}
fn expect_eof(buf: &[u8], req_bytes: usize) -> Result<(), Error> {
if buf.len() < req_bytes {
Err(Error::UnexpectedEOF)
} else {
Ok(())
}
}
pub fn iter(&'a self) -> std::slice::Iter<'a, Layer<'a, T>> {
self.layers.iter()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn header() -> Result<(), Error> {
let data = r#"
$$HEADERSTART
// This is a example for the use of the Layer Format //
$$ASCII
$$VERSION/105
$$UNITS/1 // all coordinates are given in mm //
// $$UNITS/0.01 all coordinates are given in units 0.01 mm //
$$DATE/070493 // 7. April 1993 //
$$LAYERS/100 // 100 layers //
$$HEADEREND
$$GEOMETRYSTART // start of GEOMETRY-section//
"#;
let (_, header) = CLI::<LongCLI>::parse_header(data.as_bytes())?;
assert_eq!(header.units, 1.0);
assert_eq!(header.version, 1.05);
Ok(())
}
#[test]
fn errors() {
assert_eq!("NoHeader", &format!("{}", Error::NoHeader));
assert_eq!("NoHeader", &format!("{:?}", Error::NoHeader));
}
}