use std::collections::HashMap;
use ratatui::text::Line;
use crate::ImageInfo;
pub use image as img_crate;
pub use ratatui_image::{protocol::Protocol, FontSize, Image, Resize};
pub use ratatui_image::picker::Picker;
pub struct ImagePlacement {
pub url: String,
pub line_start: usize,
pub cell_cols: u16,
pub cell_rows: u16,
}
pub fn fit_cell_size(
img: &img_crate::DynamicImage,
font_size: &FontSize,
max_cols: u16,
max_rows: u16,
) -> (u16, u16) {
if max_cols == 0 || max_rows == 0 || img.width() == 0 || img.height() == 0 {
return (max_cols.max(1), max_rows.max(1));
}
let cell_w = font_size.width as f64;
let cell_h = font_size.height as f64;
let img_cols = (img.width() as f64 / cell_w).ceil();
let img_rows = (img.height() as f64 / cell_h).ceil();
let scale_x = max_cols as f64 / img_cols;
let scale_y = max_rows as f64 / img_rows;
let scale = scale_x.min(scale_y).min(1.0);
let cols = (img_cols * scale).ceil().max(1.0) as u16;
let rows = (img_rows * scale).ceil().max(1.0) as u16;
(cols.min(max_cols), rows.min(max_rows))
}
pub fn make_protocol(
picker: &Picker,
img: &img_crate::DynamicImage,
cell_cols: u16,
cell_rows: u16,
) -> Option<Protocol> {
let size = ratatui::layout::Size::new(cell_cols, cell_rows);
picker
.new_protocol(img.clone(), size, Resize::Fit(None))
.ok()
}
pub fn halfblock_picker() -> Picker {
Picker::halfblocks()
}
#[allow(clippy::too_many_arguments)]
pub fn prepare_inline_images(
lines: &mut Vec<Line<'static>>,
images: &[ImageInfo],
cache: &HashMap<String, img_crate::DynamicImage>,
protocol_cache: &mut HashMap<String, Protocol>,
picker: &Picker,
font_size: &FontSize,
max_cols: u16,
max_rows: u16,
) -> Vec<ImagePlacement> {
let mut indexed: Vec<(usize, &ImageInfo)> = images.iter().enumerate().collect();
indexed.sort_by_key(|a| a.1.line_index);
for (_, img) in &indexed {
if cache.contains_key(&img.url) && !protocol_cache.contains_key(&img.url) {
if let Some(dyn_img) = cache.get(&img.url) {
let (cols, rows) = fit_cell_size(dyn_img, font_size, max_cols, max_rows);
if cols > 0 && rows > 0 {
if let Some(protocol) = make_protocol(picker, dyn_img, cols, rows) {
protocol_cache.insert(img.url.clone(), protocol);
}
}
}
}
}
let mut placements = Vec::new();
let mut offset: isize = 0;
let mut cursor: isize = 0;
for (_, img) in &indexed {
let adjusted_line = (img.line_index as isize + offset) as usize;
if !protocol_cache.contains_key(&img.url) {
continue;
}
let Some(dyn_img) = cache.get(&img.url) else {
continue;
};
let (cols, rows) = fit_cell_size(dyn_img, font_size, max_cols, max_rows);
if cols == 0 || rows == 0 {
continue;
}
let insert_at = (adjusted_line as isize).max(cursor) as usize;
if insert_at >= lines.len() {
continue;
}
let empty: Vec<Line<'static>> = (0..rows).map(|_| Line::from("")).collect();
lines.splice(insert_at..=insert_at, empty);
placements.push(ImagePlacement {
url: img.url.clone(),
line_start: insert_at,
cell_cols: cols,
cell_rows: rows,
});
offset += rows as isize - 1;
cursor = insert_at as isize + rows as isize;
}
placements
}