zardzewialy-dekoder-cif/src/main.rs

236 lines
7.3 KiB
Rust

use std::{env, io, process};
use std::fs::File;
use std::io::{BufRead, BufReader, BufWriter, Lines};
use arrayvec::{ArrayString, ArrayVec};
use png::{BitDepth, ColorType, EncodingError};
use thiserror::Error;
mod nummap;
#[cfg(test)]
mod test;
#[derive(Error, Debug)]
#[allow(clippy::enum_variant_names)]
enum CifError {
#[error("The CIF number string was too long")]
CapacityError,
#[error("CIF Number is invalid: {0:?}")]
GrammarError(String),
#[error("An IO error has occurred: {0}")]
IoError(io::Error),
#[error("CIF header is invalid")]
InvalidHeaderError,
#[error("CIF size is invalid: {0:?}")]
InvalidSizeError(String),
#[error("CIF metadata corrupted: {0:?}")]
InvalidMetadataError(String),
#[error("Invalid CIF pixel: {0}")]
InvalidPixelError(String),
#[error("CIF Number is out of range: {0:?} a.k.a. {1}")]
OutOfRangeError(String, u16),
#[error("CIF image doesn't contain any pixels")]
NoDataError,
#[error("PNG error: {0}")]
PngError(EncodingError)
}
#[allow(clippy::enum_glob_use)]
use crate::CifError::*;
impl<T> From<arrayvec::CapacityError<T>> for CifError {
fn from(_: arrayvec::CapacityError<T>) -> Self {
CapacityError
}
}
impl From<io::Error> for CifError {
fn from(e: io::Error) -> Self {
IoError(e)
}
}
impl From<EncodingError> for CifError {
fn from(e: EncodingError) -> Self {
PngError(e)
}
}
type Result<T> = std::result::Result<T, CifError>;
#[inline]
fn join_to_arraystring<const C: usize>(elems: &[&str], str: &mut ArrayString<C>) -> Result<()> {
for (i, item) in elems.iter().enumerate() {
if i > 0 {
str.try_push(' ')?;
}
str.try_push_str(item)?;
}
Ok(())
}
fn lines_read_noempty<T: BufRead>(lines: &mut Lines<T>) -> Result<Option<String>> {
while let Some(s) = lines.next().transpose()? {
if !s.is_empty() {
return Ok(Some(s))
}
}
Ok(None)
}
fn unfuck_cif_number(number: &str) -> Result<u32> {
let mut buf = ArrayString::<2048>::new();
let split = number.split_whitespace().collect::<ArrayVec<_, 64>>();
let splitlen = split.len();
for (i, word) in split.iter().enumerate() {
if !word.starts_with("ty") {
continue;
}
let thousands = match *word {
"tysiąc" => {
if i == 0 {
&1_u16
} else {
return Err(GrammarError(number.to_string()));
}
}
"tysięcy" | "tysiące" => {
if i > 0 {
join_to_arraystring(&split[..i], &mut buf)?;
nummap::NUMMAP.get(&buf).ok_or_else(|| GrammarError(number.to_string()))?
} else {
return Err(GrammarError(number.to_string()));
}
}
_ => return Err(GrammarError(number.to_string()))
};
let remainder = if i + 1 < splitlen {
buf.clear();
join_to_arraystring(&split[i + 1..], &mut buf)?;
nummap::NUMMAP.get(&buf).ok_or_else(|| GrammarError(number.to_string()))?
} else {
&0_u16
};
return Ok(u32::from(*thousands) * 1000 + u32::from(*remainder));
}
Ok(u32::from(*nummap::NUMMAP.get(number).ok_or_else(|| GrammarError(number.to_string()))?))
}
#[inline]
fn unfuck_cif_number_fast(number: &str) -> Result<u8> {
let number = *(nummap::NUMMAP.get(number).ok_or_else(|| GrammarError(number.to_string()))?);
if number < 256 {
#[allow(clippy::cast_possible_truncation)]
Ok(number as u8)
} else {
Err(OutOfRangeError(number.to_string(), number))
}
}
fn decoder(inputfile: &str, outputfile: &str) -> Result<()> {
let infile = File::open(inputfile)?;
let outfile = File::create(outputfile)?;
let mut lines = BufReader::new(infile).lines();
let rawriter = BufWriter::new(outfile);
if !lines.next().transpose()?.ok_or(InvalidHeaderError)?.starts_with("CIF:") {
return Err(InvalidHeaderError)
}
if lines.next().transpose()?.ok_or(InvalidHeaderError)? != "WERSJA jeden" {
return Err(InvalidHeaderError)
}
let size_string = lines.next().transpose()?.ok_or(InvalidHeaderError)?;
if size_string.len() < 8 || !size_string.starts_with("ROZMIAR") {
return Err(InvalidSizeError(size_string))
}
let size_split = (&size_string[7..]).split(',').take(3).collect::<ArrayVec<_, 3>>();
if size_split.len() != 3 {
return Err(InvalidSizeError(size_string.to_string()))
}
let width = unfuck_cif_number(size_split[0].trim_start().strip_prefix("szerokość:")
.ok_or_else(|| InvalidSizeError(size_string.to_string()))?.trim_start())?;
let height = unfuck_cif_number(size_split[1].trim_start().strip_prefix("wysokość:")
.ok_or_else(|| InvalidSizeError(size_string.to_string()))?.trim_start())?;
let format = match unfuck_cif_number_fast(
size_split[2].trim_start().strip_prefix("bitów_na_piksel:")
.ok_or_else(|| InvalidSizeError(size_string.to_string()))?.trim_start()
)? {
24 => ColorType::Rgb,
32 => ColorType::Rgba,
_ => return Err(InvalidSizeError(size_string.to_string()))
};
if width == 0 || height == 0 {
return Err(NoDataError)
}
let mut outpng = png::Encoder::new(rawriter, width, height);
outpng.set_color(format);
outpng.set_depth(BitDepth::Eight);
let first = loop {
if let Some(line) = lines.next() {
let line = line?;
if line.starts_with("METADANE ") {
let md_split = line.splitn(3, ' ').collect::<ArrayVec<_, 3>>();
if md_split.len() != 3 {
return Err(InvalidMetadataError(line.to_string()))
}
outpng.add_itxt_chunk(md_split[1].to_string(), md_split[2].to_string())?;
continue
}
break line
}
return Err(NoDataError)
};
let mut outpng = outpng.write_header()?;
let bytes_per_pixel = if format == ColorType::Rgb {3} else {4};
let mut pixmap = Vec::with_capacity((bytes_per_pixel * width * height) as usize);
if !first.is_empty() {
let px_split = first.split(';').collect::<ArrayVec<_, 4>>();
if px_split.len() != bytes_per_pixel as usize {
return Err(InvalidPixelError(first.to_string()))
}
for px in px_split {
pixmap.push(unfuck_cif_number_fast(px.trim_start())?);
}
}
for line in lines {
let line = line?;
if line.is_empty() {
continue
}
let px_split = line.split(';').collect::<ArrayVec<_, 4>>();
if px_split.len() != bytes_per_pixel as usize {
return Err(InvalidPixelError(line.to_string()))
}
for px in px_split {
pixmap.push(unfuck_cif_number_fast(px.trim())?);
}
}
outpng.write_image_data(&pixmap)?;
Ok(())
}
fn main() {
let argv = env::args().skip(1).take(2).collect::<ArrayVec<_, 2>>();
if argv.len() != 2 {
eprintln!("Invalid usage;\nUsage: zardzewialy-dekoder-cif <input cif file> <output bmp file>");
process::exit(1);
}
if let Err(e) = decoder(&argv[0], &argv[1]) {
eprintln!("Error: {}", e);
process::exit(1);
}
}