use crate::config::{AsciiArtSource, Config};
use anyhow::{Context, Result};
use std::collections::HashMap;
use std::fs;
pub fn get_ascii_art(config: &Config) -> Result<String> {
match config.ascii_art.source {
AsciiArtSource::None => Ok(String::new()),
AsciiArtSource::Auto => {
if config.ascii_art.auto_detect {
get_auto_detected_art()
} else {
get_builtin_art("default")
}
}
AsciiArtSource::Builtin => {
let name = config.ascii_art.builtin.as_deref().unwrap_or("default");
get_builtin_art(name)
}
AsciiArtSource::File => {
if let Some(path) = &config.ascii_art.path {
load_ascii_art_file(path)
} else {
get_builtin_art("default")
}
}
AsciiArtSource::Image => {
if let Some(path) = &config.ascii_art.path {
render_image_as_terminal(path)
} else {
get_builtin_art("default")
}
}
}
}
fn get_auto_detected_art() -> Result<String> {
let os_name = sysinfo::System::name().unwrap_or_default().to_lowercase();
if os_name.contains("mac") || os_name.contains("darwin") {
get_builtin_art("macos")
} else if os_name.contains("ubuntu") {
get_builtin_art("ubuntu")
} else if os_name.contains("arch") {
get_builtin_art("arch")
} else if os_name.contains("debian") {
get_builtin_art("debian")
} else if os_name.contains("fedora") {
get_builtin_art("fedora")
} else if os_name.contains("linux") {
get_builtin_art("linux")
} else {
get_builtin_art("default")
}
}
fn get_builtin_art(name: &str) -> Result<String> {
let art_map = get_builtin_art_map();
art_map
.get(name)
.or_else(|| art_map.get("default"))
.map(|s| s.to_string())
.context("No ASCII art found")
}
fn load_ascii_art_file(path: &str) -> Result<String> {
fs::read_to_string(path).with_context(|| format!("Failed to read ASCII art file: {}", path))
}
fn get_builtin_art_map() -> HashMap<&'static str, &'static str> {
let mut map = HashMap::new();
map.insert("default", include_str!("../assets/ascii/default.txt"));
map.insert("macos", include_str!("../assets/ascii/macos.txt"));
map.insert("linux", include_str!("../assets/ascii/linux.txt"));
map.insert("ubuntu", include_str!("../assets/ascii/ubuntu.txt"));
map.insert("arch", include_str!("../assets/ascii/arch.txt"));
map.insert("debian", include_str!("../assets/ascii/debian.txt"));
map.insert("fedora", include_str!("../assets/ascii/fedora.txt"));
map
}
#[cfg(feature = "images")]
fn render_image_as_terminal(path: &str) -> Result<String> {
use std::env;
if let Ok(term_program) = env::var("TERM_PROGRAM") {
if term_program == "iTerm.app" {
return render_iterm2_image(path);
}
}
if let Ok(term) = env::var("TERM") {
if term.contains("kitty") {
return render_kitty_image(path);
}
}
render_image_as_ascii_blocks(path)
}
#[cfg(feature = "images")]
fn render_iterm2_image(path: &str) -> Result<String> {
use base64::{engine::general_purpose, Engine as _};
use std::fs;
let image_data =
fs::read(path).with_context(|| format!("Failed to read image file: {}", path))?;
let base64_data = general_purpose::STANDARD.encode(&image_data);
let width_cells = 30; let height_cells = 15;
let mut output = String::new();
if std::env::var("TMUX").is_ok() {
output.push_str(&format!(
"\x1bPtmux;\x1b\x1b]1337;File=width={};height={};inline=1;preserveAspectRatio=1:{}\x07\x1b\\",
width_cells, height_cells, base64_data
));
} else {
output.push_str(&format!(
"\x1b]1337;File=width={};height={};inline=1;preserveAspectRatio=1:{}\x07",
width_cells, height_cells, base64_data
));
}
Ok(output)
}
#[cfg(feature = "images")]
fn render_kitty_image(path: &str) -> Result<String> {
use base64::{engine::general_purpose, Engine as _};
use std::fs;
let image_data =
fs::read(path).with_context(|| format!("Failed to read image file: {}", path))?;
let base64_data = general_purpose::STANDARD.encode(&image_data);
let output = format!(
"\x1b_Ga=T,f=100,s={},v={};\x1b\\\n",
image_data.len(),
base64_data
);
Ok(output)
}
#[cfg(feature = "images")]
fn render_image_as_ascii_blocks(path: &str) -> Result<String> {
use image::GenericImageView;
let img = image::open(path).with_context(|| format!("Failed to load image: {}", path))?;
let terminal_width = 29;
let terminal_height = 15;
let img = img.resize(
terminal_width,
terminal_height,
image::imageops::FilterType::Lanczos3,
);
let (width, height) = img.dimensions();
let mut output = String::new();
for y in 0..height {
for x in 0..width {
let pixel = img.get_pixel(x, y);
let rgba = pixel.0;
let r = rgba[0];
let g = rgba[1];
let b = rgba[2];
output.push_str(&format!("\x1b[38;2;{};{};{}m▓\x1b[0m", r, g, b));
}
output.push('\n');
}
Ok(output)
}
#[cfg(not(feature = "images"))]
fn render_image_as_terminal(_path: &str) -> Result<String> {
anyhow::bail!("Image display not supported. Please compile with --features images")
}