dxfbin 0.1.0

Streaming text DXF to binary DXF converter
Documentation
// SPDX-License-Identifier: ISC
use alloc::vec::Vec;
use std::io::{self, BufRead, Read};

use crate::binary_sink::BinarySink;
use crate::convert::Converter;

/// A streaming text-DXF to binary-DXF converter that implements [`Read`].
///
/// Wraps a [`BufRead`] source of text DXF and produces binary DXF bytes
/// on read.
pub struct StreamConverter<R> {
    input: R,
    converter: Converter,
    out_buf: Vec<u8>,
    out_pos: usize,
    line_buf: Vec<u8>,
    done: bool,
}

impl<R: BufRead> StreamConverter<R> {
    /// Creates a new `StreamConverter` reading text DXF from `input`.
    pub fn new(input: R) -> Self {
        Self {
            input,
            converter: Converter::new(),
            out_buf: Vec::new(),
            out_pos: 0,
            line_buf: Vec::new(),
            done: false,
        }
    }
}

impl<R: BufRead> Read for StreamConverter<R> {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        loop {
            // Drain any buffered output.
            if self.out_pos < self.out_buf.len() {
                let available = &self.out_buf[self.out_pos..];
                let n = available.len().min(buf.len());
                buf[..n].copy_from_slice(&available[..n]);
                self.out_pos += n;
                if self.out_pos == self.out_buf.len() {
                    self.out_buf.clear();
                    self.out_pos = 0;
                }
                return Ok(n);
            }

            if self.done {
                return Ok(0);
            }

            // Read next line from input.
            self.line_buf.clear();
            let n = self.input.read_until(b'\n', &mut self.line_buf)?;
            if n == 0 {
                self.done = true;
                return Ok(0);
            }
            // At EOF, read_until may return data without a trailing \n.
            // Append one so feed() can find and process the line.
            if !self.line_buf.ends_with(b"\n") {
                self.line_buf.push(b'\n');
            }

            // Feed the line to the converter. Field-level borrows:
            // line_buf (shared), out_buf (mutable via sink), converter (mutable).
            let mut sink = BinarySink::new(&mut self.out_buf);
            self.converter
                .feed(&self.line_buf, &mut sink)
                .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, alloc::format!("{e}")))?;
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::convert::convert_all;
    use std::io::BufReader;

    #[test]
    fn stream_matches_direct() {
        let text_dxf = b"  0\nSECTION\n  2\nHEADER\n  0\nENDSEC\n  0\nEOF\n";

        let reader = BufReader::new(&text_dxf[..]);
        let mut stream = StreamConverter::new(reader);
        let mut stream_out = Vec::new();
        stream.read_to_end(&mut stream_out).unwrap();

        let mut direct_out = Vec::new();
        let mut sink = BinarySink::new(&mut direct_out);
        convert_all(text_dxf, &mut sink).unwrap();

        assert_eq!(stream_out, direct_out);
    }

    #[test]
    fn stream_with_numeric_types() {
        let text_dxf = b" 10\n1.5\n 70\n42\n290\n1\n";

        let reader = BufReader::new(&text_dxf[..]);
        let mut stream = StreamConverter::new(reader);
        let mut stream_out = Vec::new();
        stream.read_to_end(&mut stream_out).unwrap();

        let mut direct_out = Vec::new();
        let mut sink = BinarySink::new(&mut direct_out);
        convert_all(text_dxf, &mut sink).unwrap();

        assert_eq!(stream_out, direct_out);
    }

    #[test]
    fn stream_small_reads() {
        let text_dxf = b"  0\nSECTION\n  0\nEOF\n";

        let reader = BufReader::new(&text_dxf[..]);
        let mut stream = StreamConverter::new(reader);

        // Read one byte at a time
        let mut out = Vec::new();
        let mut byte = [0u8; 1];
        loop {
            match stream.read(&mut byte) {
                Ok(0) => break,
                Ok(_) => out.push(byte[0]),
                Err(e) => panic!("{e}"),
            }
        }

        let mut direct_out = Vec::new();
        let mut sink = BinarySink::new(&mut direct_out);
        convert_all(text_dxf, &mut sink).unwrap();

        assert_eq!(out, direct_out);
    }

    #[test]
    fn stream_no_trailing_newline() {
        let text_dxf = b"  0\nEOF";

        let reader = BufReader::new(&text_dxf[..]);
        let mut stream = StreamConverter::new(reader);
        let mut stream_out = Vec::new();
        stream.read_to_end(&mut stream_out).unwrap();

        let mut direct_out = Vec::new();
        let mut sink = BinarySink::new(&mut direct_out);
        convert_all(text_dxf, &mut sink).unwrap();

        assert_eq!(stream_out, direct_out);
    }
}