use core::str;
use crate::components::common::{
BinaryGcodeError, BlockKind, Checksum, CompressionAlgorithm, Encoding,
};
use crate::components::deserialiser::{DeserialisedResult, Deserialiser};
use crate::components::serialiser::{serialise_block, serialise_file_header};
use alloc::string::ToString;
use alloc::{borrow::ToOwned, boxed::Box, vec::Vec};
use base64::prelude::BASE64_STANDARD;
use base64::Engine;
use regex::Regex;
pub fn binary_to_ascii(
binary: &[u8],
with_block_comments: bool,
) -> Result<Box<str>, BinaryGcodeError> {
let mut out = Vec::new();
let mut deserialiser = Deserialiser::default();
deserialiser.digest(binary);
loop {
let r = deserialiser.deserialise()?;
match r {
DeserialisedResult::FileHeader(_) => {}
DeserialisedResult::Block(mut b) => {
b.to_ascii(&mut out, with_block_comments)?;
}
DeserialisedResult::MoreBytesRequired(_) => {
break;
}
}
}
let gcode = str::from_utf8(&out).unwrap().to_owned().into_boxed_str();
Ok(gcode)
}
pub fn ascii_to_binary(ascii: &str) -> Result<Box<[u8]>, BinaryGcodeError> {
let mut binary: Vec<u8> = Vec::new();
let header = serialise_file_header(1, Checksum::Crc32);
binary.extend(header);
if let Some(start) = ascii.find("; generated by") {
let needle = "\n\n";
if let Some(end) = ascii[start..].find(needle) {
let block_data = &ascii[start..start + end + needle.len()];
let block = serialise_block(
BlockKind::FileMetadata,
CompressionAlgorithm::None,
Encoding::Ini,
Checksum::Crc32,
&[],
block_data.as_bytes(),
)?;
binary.extend(block);
} else {
return Err(BinaryGcodeError::SerialiseError("file_metadata"));
}
}
if let Some(start) = ascii.find("; printer_model") {
let needle = "\n\n";
if let Some(end) = ascii[start..].find(needle) {
let block_data = &ascii[start..start + end + needle.len()];
let block = serialise_block(
BlockKind::PrinterMetadata,
CompressionAlgorithm::None,
Encoding::Ini,
Checksum::Crc32,
&[],
block_data.as_bytes(),
)?;
binary.extend(block);
} else {
return Err(BinaryGcodeError::SerialiseError("printer_metadata"));
}
}
let mut inner = ascii;
while let Some(start) = inner.find("thumbnail begin") {
let needle = "; thumbnail end";
if let Some(end) = inner[start..].find("; thumbnail end") {
let block =
thumbnail_block(&inner[start..start + end + needle.len()])?;
binary.extend(block);
inner = &inner[start + end + needle.len()..];
} else {
return Err(BinaryGcodeError::SerialiseError("thumbnail"));
}
}
if let Some(start) = ascii.find("M73 P0") {
let needle = "M73 P100 R0\n";
if let Some(end) = ascii[start..].find(needle) {
let gcode = &ascii[start..start + end + needle.len()];
let mut chunk: Vec<u8> = Vec::new();
for b in gcode.as_bytes() {
chunk.push(*b);
if u16::MAX - (chunk.len() as u16) < 100 && *b == 10 {
let block = serialise_block(
BlockKind::GCode,
CompressionAlgorithm::Heatshrink11_4,
Encoding::Ascii,
Checksum::Crc32,
&[],
&chunk,
)?;
binary.extend(block);
chunk.clear();
}
}
if !chunk.is_empty() {
let block = serialise_block(
BlockKind::GCode,
CompressionAlgorithm::Heatshrink11_4,
Encoding::Ascii,
Checksum::Crc32,
&[],
&chunk,
)?;
binary.extend(block);
chunk.clear();
}
}
}
if let Some(start) = ascii.find("; prusaslicer_config = begin") {
let needle = "; prusaslicer_config = end";
if let Some(end) = ascii[start..].find(needle) {
let block_data = &ascii[start..start + end + needle.len()];
let block = serialise_block(
BlockKind::SlicerMetadata,
CompressionAlgorithm::Deflate,
Encoding::Ini,
Checksum::Crc32,
&[],
block_data.as_bytes(),
)?;
binary.extend(block);
} else {
return Err(BinaryGcodeError::SerialiseError("slicer_config"));
}
}
Ok(binary.into_boxed_slice())
}
fn thumbnail_block(thumb: &str) -> Result<Box<[u8]>, BinaryGcodeError> {
let (left, right) = thumb.split_once(";").unwrap();
let mut encoding = Encoding::Png;
if left.contains("_QOI") {
encoding = Encoding::Qoi;
} else if left.contains("_JPG") {
encoding = Encoding::Jpg;
}
let re = Regex::new(r"\d+x\d+").unwrap();
let m = re.find(left);
if m.is_none() {
return Err(BinaryGcodeError::DevError(left.to_string()));
}
let m = m.unwrap().as_str();
let (w, h) = m.split_once("x").unwrap();
let w = w.parse::<u16>();
if w.is_err() {
return Err(BinaryGcodeError::DevError("width_error".to_string()));
}
let w = w.unwrap();
let h = h.parse::<u16>();
if h.is_err() {
return Err(BinaryGcodeError::DevError("height_error".to_string()));
}
let h = h.unwrap();
let mut parameters: Vec<u8> = Vec::new();
parameters.extend(w.to_le_bytes());
parameters.extend(h.to_le_bytes());
let mut right = right.to_string();
right = right.replace("\n; ", "");
right = right.replace("thumbnail end", "");
let right = right.trim();
let data = BASE64_STANDARD.decode(right);
if data.is_err() {
return Err(BinaryGcodeError::DevError(right.to_string()));
}
let data = data.unwrap();
serialise_block(
BlockKind::Thumbnail,
CompressionAlgorithm::None,
encoding,
Checksum::Crc32,
¶meters,
&data,
)
}
#[cfg(test)]
mod tests {
use super::thumbnail_block;
#[test]
fn convert_thumbnail_block() {
let thumb = "thumbnail begin 16x16 616
; iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABlElEQVR4AY2SP0/CUBTF76KLLg66Ob
; i5+CchMQYH458ogghIabEUWkoLGBQ1UQYXEzVxkLg56OrgLF/ARBeNLn6iI/eRV2kAYTjJ7c05v56+
; Pkomk5BSFMWn2+yckKHGfPv2DMllKpXylNXiaDjj+NknIZ7ddMTnkTmSC1VVha6zC16wXZ/lIdRz85
; 5P5ogfNE0TerKnuobb1XAmYKZ3hZ+zxENJj+KtNNI33N6mZqwKCOXzeVQTATwrhO/SYIDHKMHdmoFl
; WSDDMHCWDuJmnXAfIbzbvYOvFuFyhXC8SKjEAigUCiBd11GNBwSAVd8gvOx1tuGGHJQqhmfB2RYg8Q
; eQemjW/HAIX0XC3aY/zOJP+Bcg29SWOsM+QCaT6QlgdQtLAGcFoKCFcREaGxhwsDwGUwm3AFyDh5yu
; 4nRnui+gFJoG/znOeGfADwKSy+HEiuEqNNoBOAoO4zC9JjzS7wPwpahUKjBNE2azzXlk0gNw5Wxzx2
; 92XVd4PQAPjuOgXC571aT4cJ3tgG8nzqx5gWzbFv5fUBP7TVgxxNgAAAAASUVORK5CYII=
; thumbnail end";
let _ = thumbnail_block(thumb).expect("Error making thumbnail");
}
}