use std::io::Write;
use crate::error::Result;
use crate::types::Handle;
use super::stream_writer::DxfStreamWriter;
pub struct DxfTextWriter<W: Write> {
writer: W,
fmt_buf: [u8; 64],
}
impl<W: Write> DxfTextWriter<W> {
pub fn new(writer: W) -> Self {
Self {
writer,
fmt_buf: [0u8; 64],
}
}
#[inline]
fn write_code(&mut self, code: i32) -> Result<()> {
let mut ibuf = itoa::Buffer::new();
let s = ibuf.format(code);
let pad = if s.len() < 3 { 3 - s.len() } else { 0 };
let len = pad + s.len() + 2;
self.fmt_buf[..pad].fill(b' ');
self.fmt_buf[pad..pad + s.len()].copy_from_slice(s.as_bytes());
self.fmt_buf[pad + s.len()] = b'\r';
self.fmt_buf[pad + s.len() + 1] = b'\n';
self.writer.write_all(&self.fmt_buf[..len])?;
Ok(())
}
#[inline]
fn write_value_crlf(&mut self, value: &[u8]) -> Result<()> {
self.writer.write_all(value)?;
self.writer.write_all(b"\r\n")?;
Ok(())
}
#[inline]
fn format_right6(&mut self, value: i32) -> usize {
let mut ibuf = itoa::Buffer::new();
let s = ibuf.format(value);
let slen = s.len();
let pad = if slen < 6 { 6 - slen } else { 0 };
let total = pad + slen + 2; self.fmt_buf[..pad].fill(b' ');
self.fmt_buf[pad..pad + slen].copy_from_slice(s.as_bytes());
self.fmt_buf[pad + slen] = b'\r';
self.fmt_buf[pad + slen + 1] = b'\n';
total
}
#[inline]
fn format_double(&mut self, value: f64) -> usize {
use std::io::Cursor;
if !value.is_finite() {
self.fmt_buf[0] = b'0';
self.fmt_buf[1] = b'.';
self.fmt_buf[2] = b'0';
self.fmt_buf[3] = b'\r';
self.fmt_buf[4] = b'\n';
return 5;
}
let abs = value.abs();
if abs > 0.0 && abs < 1e-15 {
let s = format!("{:.16e}", value);
let bytes = s.as_bytes();
let total = bytes.len() + 2; if total <= self.fmt_buf.len() {
self.fmt_buf[..bytes.len()].copy_from_slice(bytes);
self.fmt_buf[bytes.len()] = b'\r';
self.fmt_buf[bytes.len() + 1] = b'\n';
return total;
}
}
if abs >= 1e15 && abs != 0.0 {
let s = format!("{:.6}", value);
let trimmed = s.trim_end_matches('0');
let trimmed = if trimmed.ends_with('.') {
&s[..trimmed.len() + 1] } else {
trimmed
};
let bytes = trimmed.as_bytes();
let total = bytes.len() + 2; if total <= self.fmt_buf.len() {
self.fmt_buf[..bytes.len()].copy_from_slice(bytes);
self.fmt_buf[bytes.len()] = b'\r';
self.fmt_buf[bytes.len() + 1] = b'\n';
return total;
}
let s2 = format!("{:.0}", value);
let b2 = s2.as_bytes();
let t2 = b2.len().min(self.fmt_buf.len() - 4);
self.fmt_buf[..t2].copy_from_slice(&b2[..t2]);
self.fmt_buf[t2] = b'.';
self.fmt_buf[t2 + 1] = b'0';
self.fmt_buf[t2 + 2] = b'\r';
self.fmt_buf[t2 + 3] = b'\n';
return t2 + 4;
}
let mut cursor = Cursor::new(&mut self.fmt_buf[..]);
let _ = write!(cursor, "{:.16}", value);
let mut len = cursor.position() as usize;
while len > 1 && self.fmt_buf[len - 1] == b'0' {
len -= 1;
}
if len > 0 && self.fmt_buf[len - 1] == b'.' {
self.fmt_buf[len] = b'0';
len += 1;
}
self.fmt_buf[len] = b'\r';
self.fmt_buf[len + 1] = b'\n';
len + 2
}
pub fn into_inner(self) -> W {
self.writer
}
}
impl<W: Write> DxfStreamWriter for DxfTextWriter<W> {
#[inline]
fn write_string(&mut self, code: i32, value: &str) -> Result<()> {
self.write_code(code)?;
if value.contains('\n') || value.contains('\r') {
let sanitized = value
.replace("\r\n", "\\P")
.replace('\r', "\\P")
.replace('\n', "\\P");
self.write_value_crlf(sanitized.as_bytes())?;
} else {
self.write_value_crlf(value.as_bytes())?;
}
Ok(())
}
#[inline]
fn write_byte(&mut self, code: i32, value: u8) -> Result<()> {
self.write_code(code)?;
let mut ibuf = itoa::Buffer::new();
let s = ibuf.format(value);
self.write_value_crlf(s.as_bytes())?;
Ok(())
}
#[inline]
fn write_i16(&mut self, code: i32, value: i16) -> Result<()> {
self.write_code(code)?;
let len = self.format_right6(value as i32);
self.writer.write_all(&self.fmt_buf[..len])?;
Ok(())
}
#[inline]
fn write_i32(&mut self, code: i32, value: i32) -> Result<()> {
self.write_code(code)?;
let len = self.format_right6(value);
self.writer.write_all(&self.fmt_buf[..len])?;
Ok(())
}
#[inline]
fn write_i64(&mut self, code: i32, value: i64) -> Result<()> {
self.write_code(code)?;
let mut ibuf = itoa::Buffer::new();
let s = ibuf.format(value);
self.write_value_crlf(s.as_bytes())?;
Ok(())
}
#[inline]
fn write_double(&mut self, code: i32, value: f64) -> Result<()> {
self.write_code(code)?;
let len = self.format_double(value);
self.writer.write_all(&self.fmt_buf[..len])?;
Ok(())
}
#[inline]
fn write_bool(&mut self, code: i32, value: bool) -> Result<()> {
self.write_code(code)?;
if value {
self.writer.write_all(b" 1\r\n")?;
} else {
self.writer.write_all(b" 0\r\n")?;
}
Ok(())
}
fn write_handle(&mut self, code: i32, handle: Handle) -> Result<()> {
self.write_code(code)?;
let val = handle.value();
if val == 0 {
self.write_value_crlf(b"0")?;
} else {
let mut pos = 16usize; let mut v = val;
while v > 0 {
pos -= 1;
let digit = (v & 0xF) as u8;
self.fmt_buf[pos] = if digit < 10 { b'0' + digit } else { b'A' + digit - 10 };
v >>= 4;
}
let hex_len = 16 - pos;
self.fmt_buf.copy_within(pos..16, 0);
self.fmt_buf[hex_len] = b'\r';
self.fmt_buf[hex_len + 1] = b'\n';
self.writer.write_all(&self.fmt_buf[..hex_len + 2])?;
}
Ok(())
}
fn write_binary(&mut self, code: i32, data: &[u8]) -> Result<()> {
self.write_code(code)?;
const HEX_CHARS: &[u8; 16] = b"0123456789ABCDEF";
let chunk_bytes = self.fmt_buf.len() / 2; for chunk in data.chunks(chunk_bytes) {
let mut pos = 0;
for &byte in chunk {
self.fmt_buf[pos] = HEX_CHARS[(byte >> 4) as usize];
self.fmt_buf[pos + 1] = HEX_CHARS[(byte & 0x0F) as usize];
pos += 2;
}
self.writer.write_all(&self.fmt_buf[..pos])?;
}
self.writer.write_all(b"\r\n")?;
Ok(())
}
fn flush(&mut self) -> Result<()> {
self.writer.flush()?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::Vector3;
use super::super::stream_writer::DxfStreamWriterExt;
#[test]
fn test_write_string() {
let mut buf = Vec::new();
{
let mut writer = DxfTextWriter::new(&mut buf);
writer.write_string(0, "LINE").unwrap();
}
let output = String::from_utf8(buf).unwrap();
assert_eq!(output, " 0\r\nLINE\r\n");
}
#[test]
fn test_write_code_formatting() {
let mut buf = Vec::new();
{
let mut writer = DxfTextWriter::new(&mut buf);
writer.write_i16(5, 100).unwrap();
writer.write_i16(62, 7).unwrap();
writer.write_i16(100, 1).unwrap();
}
let output = String::from_utf8(buf).unwrap();
assert!(output.starts_with(" 5\r\n"));
assert!(output.contains(" 62\r\n"));
assert!(output.contains("100\r\n"));
}
#[test]
fn test_write_point3d() {
let mut buf = Vec::new();
{
let mut writer = DxfTextWriter::new(&mut buf);
writer.write_point3d(10, Vector3::new(1.0, 2.0, 3.0)).unwrap();
}
let output = String::from_utf8(buf).unwrap();
assert!(output.contains(" 10\r\n"));
assert!(output.contains("1.0\r\n"));
assert!(output.contains(" 20\r\n"));
assert!(output.contains("2.0\r\n"));
assert!(output.contains(" 30\r\n"));
assert!(output.contains("3.0\r\n"));
}
#[test]
fn test_write_handle() {
let mut buf = Vec::new();
{
let mut writer = DxfTextWriter::new(&mut buf);
writer.write_handle(5, Handle::new(255)).unwrap();
}
let output = String::from_utf8(buf).unwrap();
assert!(output.contains("FF\r\n"));
}
}