use super::stream_reader::{DxfCodePair, DxfStreamReader};
use crate::error::{DxfError, Result};
use encoding_rs::Encoding;
use std::io::{BufRead, BufReader, Read, Seek, SeekFrom};
pub struct DxfTextReader<R: Read + Seek> {
reader: BufReader<R>,
line_number: usize,
peeked_pair: Option<DxfCodePair>,
encoding: Option<&'static Encoding>,
line_buf: Vec<u8>,
}
impl<R: Read + Seek> DxfTextReader<R> {
pub fn new(reader: BufReader<R>) -> Result<Self> {
Ok(Self {
reader,
line_number: 0,
peeked_pair: None,
encoding: None,
line_buf: Vec::with_capacity(256),
})
}
fn read_line(&mut self) -> Result<Option<String>> {
let mut buf = std::mem::take(&mut self.line_buf);
buf.clear();
let bytes_read = self.reader.read_until(b'\n', &mut buf)?;
if bytes_read == 0 {
self.line_buf = buf; return Ok(None);
}
self.line_number += 1;
if buf.last() == Some(&b'\n') { buf.pop(); }
if buf.last() == Some(&b'\r') { buf.pop(); }
let line = match String::from_utf8(buf) {
Ok(s) => {
self.line_buf = Vec::with_capacity(256);
s
}
Err(e) => {
let bytes = e.into_bytes();
let result = if let Some(enc) = self.encoding {
let (decoded, _, _) = enc.decode(&bytes);
decoded.into_owned()
} else {
bytes.iter().map(|&b| b as char).collect()
};
self.line_buf = bytes; result
}
};
Ok(Some(line))
}
fn read_line_raw(&mut self) -> Result<bool> {
self.line_buf.clear();
let bytes_read = self.reader.read_until(b'\n', &mut self.line_buf)?;
if bytes_read == 0 {
return Ok(false);
}
self.line_number += 1;
if self.line_buf.last() == Some(&b'\n') { self.line_buf.pop(); }
if self.line_buf.last() == Some(&b'\r') { self.line_buf.pop(); }
Ok(true)
}
fn read_pair_internal(&mut self) -> Result<Option<DxfCodePair>> {
if !self.read_line_raw()? {
return Ok(None);
}
let code_str = std::str::from_utf8(&self.line_buf)
.map_err(|_| DxfError::Parse(format!("Invalid UTF-8 in code at line {}", self.line_number)))?;
let code = code_str.trim().parse::<i32>()
.map_err(|_| DxfError::Parse(format!("Invalid DXF code at line {}: '{}'", self.line_number, code_str)))?;
let value_line = match self.read_line()? {
Some(line) => line,
None => return Err(DxfError::Parse(format!("Unexpected EOF after code {} at line {}", code, self.line_number))),
};
let value = self.process_string_value(&value_line);
Ok(Some(DxfCodePair::new(code, value)))
}
fn process_string_value(&self, value: &str) -> String {
if !value.contains('^') {
return value.to_string();
}
value
.replace("^J", "\n")
.replace("^M", "\r")
.replace("^I", "\t")
.replace("^ ", "^")
}
}
impl<R: Read + Seek> DxfStreamReader for DxfTextReader<R> {
fn read_pair(&mut self) -> Result<Option<DxfCodePair>> {
if let Some(pair) = self.peeked_pair.take() {
return Ok(Some(pair));
}
self.read_pair_internal()
}
fn peek_code(&mut self) -> Result<Option<i32>> {
if let Some(ref pair) = self.peeked_pair {
return Ok(Some(pair.code));
}
if let Some(pair) = self.read_pair_internal()? {
let code = pair.code;
self.peeked_pair = Some(pair);
Ok(Some(code))
} else {
Ok(None)
}
}
fn push_back(&mut self, pair: DxfCodePair) {
self.peeked_pair = Some(pair);
}
fn reset(&mut self) -> Result<()> {
self.reader.seek(SeekFrom::Start(0))?;
self.line_number = 0;
self.peeked_pair = None;
Ok(())
}
fn set_encoding(&mut self, encoding: &'static Encoding) {
self.encoding = Some(encoding);
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn test_read_simple_pair() {
let data = "0\nSECTION\n";
let cursor = Cursor::new(data.as_bytes());
let buf_reader = BufReader::new(cursor);
let mut reader = DxfTextReader::new(buf_reader).unwrap();
let pair = reader.read_pair().unwrap().unwrap();
assert_eq!(pair.code, 0);
assert_eq!(pair.value_string, "SECTION");
}
#[test]
fn test_read_integer_pair() {
let data = "70\n42\n";
let cursor = Cursor::new(data.as_bytes());
let buf_reader = BufReader::new(cursor);
let mut reader = DxfTextReader::new(buf_reader).unwrap();
let pair = reader.read_pair().unwrap().unwrap();
assert_eq!(pair.code, 70);
assert_eq!(pair.as_int(), Some(42));
}
#[test]
fn test_read_double_pair() {
let data = "10\n123.456\n";
let cursor = Cursor::new(data.as_bytes());
let buf_reader = BufReader::new(cursor);
let mut reader = DxfTextReader::new(buf_reader).unwrap();
let pair = reader.read_pair().unwrap().unwrap();
assert_eq!(pair.code, 10);
assert_eq!(pair.as_double(), Some(123.456));
}
#[test]
fn test_peek_code() {
let data = "0\nSECTION\n2\nHEADER\n";
let cursor = Cursor::new(data.as_bytes());
let buf_reader = BufReader::new(cursor);
let mut reader = DxfTextReader::new(buf_reader).unwrap();
assert_eq!(reader.peek_code().unwrap(), Some(0));
let pair = reader.read_pair().unwrap().unwrap();
assert_eq!(pair.code, 0);
assert_eq!(reader.peek_code().unwrap(), Some(2));
}
#[test]
fn test_special_characters() {
let data = "1\nLine1^JLine2^MLine3\n";
let cursor = Cursor::new(data.as_bytes());
let buf_reader = BufReader::new(cursor);
let mut reader = DxfTextReader::new(buf_reader).unwrap();
let pair = reader.read_pair().unwrap().unwrap();
assert_eq!(pair.value_string, "Line1\nLine2\rLine3");
}
}