dxfbin 0.1.0

Streaming text DXF to binary DXF converter
Documentation
// SPDX-License-Identifier: ISC
use crate::sink::Sink;

const SENTINEL: &[u8] = b"AutoCAD Binary DXF\r\n\x1a\0";

/// A byte-oriented output target. Minimal `no_std` alternative to `std::io::Write`.
pub trait Output {
    /// The error type returned on write failure.
    type Error;
    /// Write the entire slice to the output.
    fn write_all(&mut self, bytes: &[u8]) -> Result<(), Self::Error>;
}

impl<T: Output> Output for &mut T {
    type Error = T::Error;

    fn write_all(&mut self, bytes: &[u8]) -> Result<(), Self::Error> {
        T::write_all(self, bytes)
    }
}

/// Error returned when an output slice is too small.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BufferOverflow;

impl Output for &mut [u8] {
    type Error = BufferOverflow;

    fn write_all(&mut self, bytes: &[u8]) -> Result<(), BufferOverflow> {
        if bytes.len() > self.len() {
            return Err(BufferOverflow);
        }
        let (head, tail) = core::mem::take(self).split_at_mut(bytes.len());
        head.copy_from_slice(bytes);
        *self = tail;
        Ok(())
    }
}

impl Output for alloc::vec::Vec<u8> {
    type Error = core::convert::Infallible;

    fn write_all(&mut self, bytes: &[u8]) -> Result<(), Self::Error> {
        self.extend_from_slice(bytes);
        Ok(())
    }
}

/// [`Output`] adapter for any [`std::io::Write`] implementor.
#[cfg(feature = "std")]
pub struct IoOutput<W>(pub W);

#[cfg(feature = "std")]
impl<W: std::io::Write> Output for IoOutput<W> {
    type Error = std::io::Error;

    fn write_all(&mut self, bytes: &[u8]) -> Result<(), std::io::Error> {
        self.0.write_all(bytes)
    }
}

/// A [`Sink`] that writes binary DXF encoding to an [`Output`].
pub struct BinarySink<W> {
    output: W,
}

impl<W> BinarySink<W> {
    /// Creates a new `BinarySink` writing to the given output.
    pub fn new(output: W) -> Self {
        Self { output }
    }

    /// Consumes the sink and returns the underlying output.
    pub fn into_inner(self) -> W {
        self.output
    }
}

impl<W: Output> Sink for BinarySink<W> {
    type Error = W::Error;

    fn sentinel(&mut self) -> Result<(), Self::Error> {
        self.output.write_all(SENTINEL)
    }

    fn group_code(&mut self, code: u16) -> Result<(), Self::Error> {
        self.output.write_all(&code.to_le_bytes())
    }

    fn string(&mut self, value: &[u8]) -> Result<(), Self::Error> {
        self.output.write_all(value)?;
        self.output.write_all(&[0])
    }

    fn boolean(&mut self, value: bool) -> Result<(), Self::Error> {
        self.output.write_all(&[value as u8])
    }

    fn int16(&mut self, value: i16) -> Result<(), Self::Error> {
        self.output.write_all(&value.to_le_bytes())
    }

    fn int32(&mut self, value: i32) -> Result<(), Self::Error> {
        self.output.write_all(&value.to_le_bytes())
    }

    fn int64(&mut self, value: i64) -> Result<(), Self::Error> {
        self.output.write_all(&value.to_le_bytes())
    }

    fn double(&mut self, value: f64) -> Result<(), Self::Error> {
        self.output.write_all(&value.to_le_bytes())
    }

    fn binary_chunk(&mut self, data: &[u8]) -> Result<(), Self::Error> {
        self.output.write_all(&[data.len() as u8])?;
        self.output.write_all(data)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::sink::Sink;
    use alloc::vec::Vec;

    #[test]
    fn sentinel_bytes() {
        let mut out = Vec::new();
        BinarySink::new(&mut out).sentinel().unwrap();
        assert_eq!(out, b"AutoCAD Binary DXF\r\n\x1a\0");
        assert_eq!(out.len(), 22);
    }

    #[test]
    fn group_code_encoding() {
        let mut out = Vec::new();
        BinarySink::new(&mut out).group_code(0).unwrap();
        assert_eq!(out, &[0x00, 0x00]);

        let mut out = Vec::new();
        BinarySink::new(&mut out).group_code(254).unwrap();
        assert_eq!(out, &[0xFE, 0x00]);

        let mut out = Vec::new();
        BinarySink::new(&mut out).group_code(310).unwrap();
        assert_eq!(out, &[0x36, 0x01]);

        let mut out = Vec::new();
        BinarySink::new(&mut out).group_code(1004).unwrap();
        assert_eq!(out, &[0xEC, 0x03]);
    }

    #[test]
    fn string_null_terminated() {
        let mut out = Vec::new();
        BinarySink::new(&mut out).string(b"SECTION").unwrap();
        assert_eq!(out, b"SECTION\0");
    }

    #[test]
    fn boolean_values() {
        let mut out = Vec::new();
        {
            let mut sink = BinarySink::new(&mut out);
            sink.boolean(false).unwrap();
            sink.boolean(true).unwrap();
        }
        assert_eq!(out, &[0x00, 0x01]);
    }

    #[test]
    fn integer_values() {
        let mut out = Vec::new();
        BinarySink::new(&mut out).int16(256).unwrap();
        assert_eq!(out, &[0x00, 0x01]);

        let mut out = Vec::new();
        BinarySink::new(&mut out).int32(0x01020304).unwrap();
        assert_eq!(out, &[0x04, 0x03, 0x02, 0x01]);

        let mut out = Vec::new();
        BinarySink::new(&mut out).int64(1).unwrap();
        assert_eq!(out, &[0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
    }

    #[test]
    fn double_value() {
        let mut out = Vec::new();
        BinarySink::new(&mut out).double(1.0).unwrap();
        assert_eq!(out, &1.0f64.to_le_bytes());
        assert_eq!(out.len(), 8);
    }

    #[test]
    fn binary_chunk_value() {
        let mut out = Vec::new();
        BinarySink::new(&mut out).binary_chunk(b"Hello").unwrap();
        assert_eq!(out, &[5, b'H', b'e', b'l', b'l', b'o']);
    }

    #[test]
    fn slice_output() {
        let mut buf = [0u8; 32];
        let mut slice = &mut buf[..];
        {
            let mut sink = BinarySink::new(&mut slice);
            sink.group_code(0).unwrap();
            sink.string(b"EOF").unwrap();
        }
        // 2-byte group code + 3 bytes "EOF" + 1 byte null = 6 bytes
        assert_eq!(&buf[..6], &[0x00, 0x00, b'E', b'O', b'F', 0x00]);
    }

    #[test]
    fn slice_output_overflow() {
        let mut buf = [0u8; 2];
        let mut slice = &mut buf[..];
        let mut sink = BinarySink::new(&mut slice);
        assert_eq!(sink.string(b"too long").unwrap_err(), BufferOverflow);
    }
}