use ratatui::{
prelude::Stylize,
style::{Color, Style},
text::{Line, Span},
};
#[cfg(feature = "image")]
pub use image;
#[cfg(feature = "image")]
pub struct CropRect {
pub col_offset: u16,
pub row_offset: u16,
pub visible_width: u16,
pub visible_height: u16,
}
#[cfg(feature = "image")]
pub struct ImagePlacement {
pub row: usize,
pub col: usize,
pub width_cells: u16,
pub height_cells: u16,
pub image: image::DynamicImage,
pub crop: Option<CropRect>,
}
#[cfg(feature = "image")]
pub struct ResolvedImage {
pub path: String,
pub image: image::DynamicImage,
}
#[cfg(feature = "image")]
pub struct MarkdownRenderOutput {
pub lines: Vec<Line<'static>>,
pub images: Vec<ImagePlacement>,
}
#[cfg(feature = "image")]
const DEFAULT_PIXELS_PER_CELL_W: u32 = 9;
#[cfg(feature = "image")]
const DEFAULT_PIXELS_PER_CELL_H: u32 = 18;
#[cfg(feature = "image")]
pub trait ImageResolver {
fn resolve(&mut self, path: &str) -> Option<image::DynamicImage>;
fn cell_dimensions(
&mut self,
img: &image::DynamicImage,
max_width: u16,
_max_height: u16,
) -> (u16, u16) {
let pw = img.width();
let ph = img.height();
if pw == 0 || ph == 0 || max_width == 0 {
return (0, 0);
}
let w_cells = pw.div_ceil(DEFAULT_PIXELS_PER_CELL_W) as u16;
let w = w_cells.min(max_width);
let ratio = ph * w as u32 / (pw.max(1));
let h = ratio.div_ceil(DEFAULT_PIXELS_PER_CELL_H) as u16;
(w.max(1), h.max(1))
}
fn fallback(&self, path: &str, alt: &str) -> Span<'static> {
let label = if alt.is_empty() {
path.to_string()
} else {
alt.to_string()
};
Span::styled(
format!("[image: {label}]"),
Style::default().italic().fg(Color::Gray),
)
}
}
#[cfg(feature = "image")]
pub struct NoopImageResolver;
#[cfg(feature = "image")]
impl ImageResolver for NoopImageResolver {
fn resolve(&mut self, _path: &str) -> Option<image::DynamicImage> {
None
}
}
#[cfg(feature = "image")]
impl MarkdownRenderOutput {
pub fn new() -> Self {
Self {
lines: Vec::new(),
images: Vec::new(),
}
}
}
#[cfg(feature = "image")]
impl Default for MarkdownRenderOutput {
fn default() -> Self {
Self::new()
}
}