enginerenderer 0.0.1

A zero-dependency offline rendering engine in pure Rust — CPU path tracing, BVH acceleration, 16-band spectral rendering, PBR materials, animation & video export.
Documentation
use std::io::{self, IsTerminal};

pub fn terminal_columns() -> usize {
    std::env::var("COLUMNS")
        .ok()
        .and_then(|v| v.parse::<usize>().ok())
        .filter(|v| *v >= 40)
        .unwrap_or(100)
}

pub fn print_framed_panel(title: &str, lines: &[String], width: usize) {
    let inner_width = width + 2;
    let title_block = format!(" {} ", title);
    let title_w = display_width(&title_block);
    let dash_total = inner_width.saturating_sub(title_w);
    let left = "".repeat(dash_total / 2);
    let right = "".repeat(dash_total - (dash_total / 2));
    eprintln!("{}{}{}", left, title_block, right);
    for line in lines {
        print_console_row(&fit_to_width(line, width), width);
    }
    eprintln!("{}", "".repeat(inner_width));
}

pub fn fit_to_width(text: &str, width: usize) -> String {
    if display_width(text) <= width.saturating_sub(2) {
        return text.to_string();
    }
    let mut out = String::new();
    let mut used = 0usize;
    let limit = width.saturating_sub(5);
    for c in text.chars() {
        let w = char_display_width(c);
        if used + w > limit {
            break;
        }
        out.push(c);
        used += w;
    }
    out.push_str("...");
    out
}

pub fn print_console_block(title: &str, lines: &[String]) {
    let width = lines
        .iter()
        .map(|l| display_width(l))
        .max()
        .unwrap_or(0)
        .max(display_width(title) + 8)
        .max(28);

    let top = "+".to_string() + &"-".repeat(width + 2) + "+";
    let sep = "+".to_string() + &"-".repeat(width + 2) + "+";
    let bottom = "+".to_string() + &"-".repeat(width + 2) + "+";

    eprintln!("{}", top);
    print_console_row(title, width);
    eprintln!("{}", sep);
    for line in lines {
        print_console_row(line, width);
    }
    eprintln!("{}", bottom);
}

pub fn print_console_row(content: &str, width: usize) {
    if io::stderr().is_terminal() {
        eprint!("| {}", content);
        eprint!("\x1B[{}G", width + 4);
        eprintln!("|");
    } else {
        eprintln!("| {} |", pad_display(content, width));
    }
}

pub fn pad_display(text: &str, width: usize) -> String {
    let current = display_width(text);
    if current >= width {
        text.to_string()
    } else {
        format!("{}{}", text, " ".repeat(width - current))
    }
}

pub fn display_width(text: &str) -> usize {
    text.chars().map(char_display_width).sum()
}

pub fn char_display_width(c: char) -> usize {
    if c.is_ascii() {
        1
    } else if is_zero_width_char(c) {
        0
    } else {
        2
    }
}

pub fn is_zero_width_char(c: char) -> bool {
    matches!(
        c,
        '\u{200D}'
            | '\u{FE0E}'
            | '\u{FE0F}'
            | '\u{FE20}'..='\u{FE2F}'
            | '\u{0300}'..='\u{036F}'
            | '\u{1AB0}'..='\u{1AFF}'
            | '\u{1DC0}'..='\u{1DFF}'
            | '\u{20D0}'..='\u{20FF}'
            | '\u{1F3FB}'..='\u{1F3FF}'
    )
}