use std::{
cell::{Cell, RefCell, RefMut},
collections::hash_map::HashMap,
rc::Rc,
};
use ratatui::widgets::ListState;
use super::cache::{CacheKey, GlyphCache, GlyphCanvasShape, RenderType, CHAR_RENDERS, MONO_RENDER};
use crate::{
family::Family,
loader::{self, FaceInfo},
preview::terminal::{render::Render, ui::cache::GlyphParagraph},
rasterizer::{Bitmap, Rasterizer},
};
pub struct State<'a> {
font_faces_info: Vec<&'a FaceInfo>,
font_faces_name: Vec<&'a str>,
name_width_max: usize,
list_state: RefCell<ListState>,
height: Cell<u32>,
width: Cell<u32>,
rt: RenderType,
cache: RefCell<HashMap<CacheKey, Rc<Result<GlyphCache, &'static str>>>>,
}
impl<'a> State<'a> {
pub fn new(families: Vec<Family<'a>>) -> Self {
let font_faces_info: Vec<_> = families
.into_iter()
.flat_map(|f| f.faces.into_iter())
.collect();
let font_faces_name: Vec<_> = font_faces_info.iter().map(|f| f.name.as_ref()).collect();
let name_width_max = font_faces_name
.iter()
.map(|f| f.len())
.max()
.unwrap_or_default();
let cache = RefCell::default();
let mut list_state = ListState::default();
list_state.select(Some(0));
Self {
font_faces_info,
font_faces_name,
name_width_max,
list_state: RefCell::new(list_state),
height: Cell::new(0),
width: Cell::new(0),
rt: RenderType::Mono,
cache,
}
}
fn cache_key(&self, width: u32, height: u32) -> CacheKey {
CacheKey {
index: self.index(),
rt: self.rt,
width,
height,
}
}
pub fn render(&self) -> Rc<Result<GlyphCache, &'static str>> {
let (width, height) = match self.rt {
RenderType::Mono => self.get_canvas_size_by_pixel(),
_ => self.get_canvas_size_by_char(),
};
let key = self.cache_key(width, height);
self.cache
.borrow_mut()
.entry(key)
.or_insert_with(|| Rc::new(self.real_render(width, height)))
.clone()
}
fn rasterize(&self, _width: u32, height: u32) -> Result<Bitmap, &'static str> {
let info = self.font_faces_info[self.index()];
let scale = if matches!(self.rt, RenderType::AsciiLevel10 | RenderType::AsciiLevel70) {
Some(2.0)
} else {
None
};
loader::database()
.with_face_data(info.id, |data, index| -> Option<Bitmap> {
let mut r = Rasterizer::new(data, index).ok()?;
r.set_pixel_height(height);
if let Some(scale) = scale {
r.set_hscale(scale);
}
r.rasterize(info.gid)
})
.ok_or("Can't read this font file")?
.ok_or("Can't get glyph from this font")
}
fn real_render(&self, width: u32, height: u32) -> Result<GlyphCache, &'static str> {
let bitmap = self.rasterize(width, height)?;
let cache = match self.rt {
RenderType::Mono => GlyphCache::Canvas(GlyphCanvasShape::new(
MONO_RENDER.render(&bitmap),
width as f64,
height as f64,
)),
rt => GlyphCache::Paragraph(GlyphParagraph::new(
CHAR_RENDERS
.get(&rt)
.expect("all render must be exist")
.render(&bitmap),
)),
};
Ok(cache)
}
pub fn current_name(&self) -> &'a str {
self.font_faces_name[self.index()]
}
pub fn name_width_max(&self) -> usize {
self.name_width_max
}
pub fn family_names(&self) -> &Vec<&'a str> {
&self.font_faces_name
}
pub fn mut_list_state(&self) -> RefMut<'_, ListState> {
self.list_state.borrow_mut()
}
pub fn index(&self) -> usize {
self.list_state
.borrow()
.selected()
.expect("always has a selected item")
}
pub fn move_up(&mut self) {
let changed = self
.list_state
.borrow()
.selected()
.map(|index| index.saturating_sub(1));
self.list_state.borrow_mut().select(changed);
}
pub fn move_down(&mut self) {
let changed = self.list_state.borrow().selected().map(|index| {
index
.saturating_add(1)
.min(self.font_faces_name.len().saturating_sub(1))
});
self.list_state.borrow_mut().select(changed);
}
pub fn get_render_type(&self) -> &RenderType {
&self.rt
}
pub fn next_render_type(&mut self) {
self.rt = match self.rt {
RenderType::AsciiLevel10 => RenderType::AsciiLevel70,
RenderType::AsciiLevel70 => RenderType::Moon,
RenderType::Moon => RenderType::Mono,
RenderType::Mono => RenderType::AsciiLevel10,
}
}
pub fn prev_render_type(&mut self) {
self.rt = match self.rt {
RenderType::AsciiLevel10 => RenderType::Mono,
RenderType::AsciiLevel70 => RenderType::AsciiLevel10,
RenderType::Moon => RenderType::AsciiLevel70,
RenderType::Mono => RenderType::Moon,
}
}
pub fn update_canvas_size_by_char(&self, width: u32, height: u32) {
self.width.replace(width);
self.height.replace(height);
}
pub fn get_canvas_size_by_char(&self) -> (u32, u32) {
(self.width.get(), self.height.get())
}
pub fn get_canvas_size_by_pixel(&self) -> (u32, u32) {
(self.width.get() * 2, self.height.get() * 4)
}
}