insa 1.0.0

Insa renders imaxes to text - it's a terminal bitmap graphics library
Documentation
use std::{env, fs, io, process::exit};

#[cfg(not(feature = "fontdue"))]
fn main () {
    panic!("Rasterization requires feature fontdue")
}

#[cfg(feature = "fontdue")]

fn main() {
    let mut args = env::args();
    let myname = args.next().unwrap_or("insa".to_owned());
    let myversion = env!["CARGO_PKG_VERSION"];
    let mut options_done = false;
    let mut font = None;

    let usage = move |error| {
        let output = format!(
            "Insa rasterize {myversion}\nUsage: {myname} font <utf-8-brushes\n - it works well with unifont 15\n - use it to make a brush map for insa"
        );
        if let Some(error) = &error {
            eprintln!("{error}\n\n{output}");
            exit(1);
        } else {
            println!("{output}");
            exit(0);
        }
    };

    while let Some(arg) = args.next() {
        match arg.as_str() {
            "--help" => {
                usage(None);
            }
            "--version" => {
                println!("Insa {myversion}");
                return;
            }
            "--" => options_done = true,
            _ if !options_done && arg.starts_with("--") => {
                usage(Some(format!("Unknown option {arg}")));
            }
            _ => {
                if font.is_some() {
                    usage(Some(format!("Give us only one font")))
                } else {
                    font = Some(arg)
                }
            }
        }
    }

    let font = if let Some(font) = font {
        font
    } else {
        usage(Some("Give Insa some font to play with".into()));
        return;
    };

    let font = fs::read(font).expect("reading font data");
    let font = fontdue::Font::from_bytes(font, fontdue::FontSettings::default())
        .expect("loading font data");

    let mut input = Chars::new(io::stdin());
    while let Some(c) = input.next() {
        match c {
            Err(e) => panic!("Error reading brushes: {e:?}"),
            Ok(c) => {
                let bitmap = insa::rasterize_u128(&font, c);
                eprint!(
                    "{}",
                    insa::dump_bitmap_u128(bitmap)
                        .replace("0b", "//")
                        .replace("0", " ")
                        .replace("1", "")
                        .replace("\n", "|\n")
                );
                println!("(0b{bitmap:0128b},'{c}'),")
            }
        }
    }
}

struct Chars<R> {
    reader: R,
    pos: usize,
    char: [u8; 4],
}
impl<R> Chars<R> {
    fn new(reader: R) -> Self {
        Self {
            reader,
            pos: 0,
            char: Default::default(),
        }
    }
}

impl<R: io::Read> Iterator for Chars<R> {
    type Item = Result<char, io::Result<Vec<u8>>>;

    fn next(&mut self) -> Option<Self::Item> {
        let bytes = |me: &mut Self| {
            let bs = me.char.iter().cloned().take(me.pos).collect();
            me.pos = 0;
            me.char.fill(0);
            Some(Err(Ok(bs)))
        };

        loop {
            break match self.reader.read(&mut self.char[self.pos..self.pos + 1]) {
                Err(e) => Some(Err(Err(e))),
                Ok(0) => {
                    if self.pos == 0 {
                        None
                    } else {
                        bytes(self)
                    }
                }
                Ok(_) => {
                    if let Ok(str) = std::str::from_utf8(&self.char[..self.pos + 1]) {
                        let c = str.chars().next().expect("first char");
                        self.char.fill(0);
                        self.pos = 0;
                        if c == '\n' {
                            continue;
                        } else {
                            Some(Ok(c))
                        }
                    } else if self.pos < 4 {
                        self.pos += 1;
                        continue;
                    } else {
                        bytes(self)
                    }
                }
            };
        }
    }
}