From 9ec9e763887a2fe35127e276bd7c3a11c6e9839f Mon Sep 17 00:00:00 2001 From: 7sDream <7822577+7sDream@users.noreply.github.com> Date: Tue, 14 Nov 2023 23:19:15 +0800 Subject: [PATCH] fix(tui): ascii too narrow (#63), fixes #61 --- src/preview/terminal/render/mono.rs | 2 +- src/preview/terminal/ui/cache.rs | 4 +- src/preview/terminal/ui/mod.rs | 25 +++++------- src/preview/terminal/ui/state.rs | 63 +++++++++++++++++------------ src/rasterizer/bitmap.rs | 20 +-------- src/rasterizer/font_face.rs | 44 -------------------- src/rasterizer/mod.rs | 35 ++++++++++++++-- 7 files changed, 86 insertions(+), 107 deletions(-) delete mode 100644 src/rasterizer/font_face.rs diff --git a/src/preview/terminal/render/mono.rs b/src/preview/terminal/render/mono.rs index 6288e4a..ad9c992 100644 --- a/src/preview/terminal/render/mono.rs +++ b/src/preview/terminal/render/mono.rs @@ -25,6 +25,6 @@ impl Render for MonoRender { type Pixel = bool; fn render_pixel(&self, _up: u8, _left: u8, gray: u8, _right: u8, _down: u8) -> Self::Pixel { - gray == u8::MAX + gray >= 128 } } diff --git a/src/preview/terminal/ui/cache.rs b/src/preview/terminal/ui/cache.rs index cf4d6bd..76dfc9b 100644 --- a/src/preview/terminal/ui/cache.rs +++ b/src/preview/terminal/ui/cache.rs @@ -45,14 +45,14 @@ pub static CHAR_RENDERS: Lazy>> = Lazy::ne renders }); -pub static MONO_RENDER: Lazy> = Lazy::new(|| Box::::default()); +pub static MONO_RENDER: Lazy = Lazy::new(MonoRender::default); #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] pub struct CacheKey { pub index: usize, pub rt: RenderType, - pub height: u32, pub width: u32, + pub height: u32, } pub enum GlyphCache { diff --git a/src/preview/terminal/ui/mod.rs b/src/preview/terminal/ui/mod.rs index cea1c3e..afd6e83 100644 --- a/src/preview/terminal/ui/mod.rs +++ b/src/preview/terminal/ui/mod.rs @@ -40,7 +40,7 @@ use tui::{ }; use self::{ - cache::{GlyphCache, GlyphCanvasShape, RenderType}, + cache::{GlyphCache, GlyphCanvasShape}, event::{TerminalEvent, TerminalEventStream}, state::State, }; @@ -80,7 +80,7 @@ impl<'a: 'a> UI<'a> { } fn draw_preview_canvas(&self, area: Rect, f: &mut Frame<'_>, shape: &GlyphCanvasShape) { - let (canvas_width, canvas_height) = self.state.get_char_pixel_cell(); + let (canvas_width, canvas_height) = self.state.get_canvas_size_by_pixel(); let canvas_width = f64::from(canvas_width); let canvas_height = f64::from(canvas_height); let canvas = Canvas::default() @@ -98,8 +98,13 @@ impl<'a: 'a> UI<'a> { I: IntoIterator, I::IntoIter: ExactSizeIterator, { + let (_, height) = self.state.get_canvas_size_by_char(); + let iter = paragraph.into_iter(); - let padding = (area.height as usize).saturating_sub(2).saturating_sub(iter.len()); + + // saturating_sub here makes padding zero instead of overflow to a huge number + // if render result taller then preview area + let padding = (height as usize).saturating_sub(iter.len()); let mut lines = vec![Line::from(""); padding / 2]; for line in iter { @@ -249,17 +254,9 @@ impl<'a: 'a> UI<'a> { let list = main[0]; let canvas = main[1]; - let mut width = u32::from(canvas.width.saturating_sub(2)); - let mut height = u32::from(canvas.height.saturating_sub(2)); - let rt = self.state.get_render_type(); - if rt == &RenderType::Moon { - width /= 2; - } else if rt == &RenderType::Mono { - width = width.saturating_mul(2); - height = height.saturating_mul(4); - } - - self.state.update_char_pixel_cell(width, height); + let width = u32::from(canvas.width.saturating_sub(2)); + let height = u32::from(canvas.height.saturating_sub(2)); + self.state.update_canvas_size_by_char(width, height); self.draw_list(list, f); self.draw_preview(canvas, f); diff --git a/src/preview/terminal/ui/state.rs b/src/preview/terminal/ui/state.rs index 67d0443..22772bc 100644 --- a/src/preview/terminal/ui/state.rs +++ b/src/preview/terminal/ui/state.rs @@ -28,8 +28,8 @@ use super::cache::{CacheKey, GlyphCache, GlyphCanvasShape, RenderType, CHAR_REND use crate::{ family::Family, loader::{FaceInfo, DATABASE}, - preview::terminal::ui::cache::GlyphParagraph, - rasterizer::{Bitmap, FontFace, PixelFormat}, + preview::terminal::{render::Render, ui::cache::GlyphParagraph}, + rasterizer::{Bitmap, Rasterizer}, }; pub struct State<'a> { @@ -67,44 +67,53 @@ impl<'a> State<'a> { } } - fn cache_key(&self) -> CacheKey { - CacheKey { - index: self.index(), - rt: self.rt, - height: self.height.get(), - width: self.width.get(), - } + fn cache_key(&self, width: u32, height: u32) -> CacheKey { + CacheKey { index: self.index(), rt: self.rt, width, height } } pub fn render(&self) -> Rc> { - let key = self.cache_key(); - self.cache.borrow_mut().entry(key).or_insert_with(|| Rc::new(self.real_render())).clone() + 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) -> Result { + fn rasterize(&self, _width: u32, height: u32) -> Result { let info = self.font_faces_info[self.index()]; + let scale = if matches!(self.rt, RenderType::AsciiLevel10 | RenderType::AsciiLevel70) { + Some(2.0) + } else { + None + }; + DATABASE .with_face_data(info.id, |data, index| -> Option { - let mut face = FontFace::new(data, index).ok()?; - face.set_size(self.height.get(), self.width.get()); - face.load_glyph(info.gid.0, match self.rt { - RenderType::Mono => PixelFormat::Monochrome, - _ => PixelFormat::Gray, - }) + 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.0) }) .ok_or("Can't read this font file")? .ok_or("Can't get glyph from this font") } - fn real_render(&self) -> Result { - let bitmap = self.rasterize()?; - + fn real_render(&self, width: u32, height: u32) -> Result { + let bitmap = self.rasterize(width, height)?; let cache = match self.rt { RenderType::Mono => GlyphCache::Canvas(GlyphCanvasShape::new( MONO_RENDER.render(&bitmap), - self.width.get() as f64, - self.height.get() as f64, + width as f64, + height as f64, )), rt => GlyphCache::Paragraph(GlyphParagraph::new( CHAR_RENDERS.get(&rt).expect("all render must be exist").render(&bitmap), @@ -169,12 +178,16 @@ impl<'a> State<'a> { } } - pub fn update_char_pixel_cell(&self, width: u32, height: u32) { + pub fn update_canvas_size_by_char(&self, width: u32, height: u32) { self.width.replace(width); self.height.replace(height); } - pub fn get_char_pixel_cell(&self) -> (u32, u32) { + 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) + } } diff --git a/src/rasterizer/bitmap.rs b/src/rasterizer/bitmap.rs index 833a52e..24192dc 100644 --- a/src/rasterizer/bitmap.rs +++ b/src/rasterizer/bitmap.rs @@ -26,19 +26,13 @@ pub struct Metrics { pub width: usize, } -#[derive(Copy, Clone)] -pub enum PixelFormat { - Gray, - Monochrome, -} - pub struct Bitmap { metrics: Metrics, bitmap: Grid, } impl Bitmap { - pub fn new(curves: &OutlinedGlyph, format: PixelFormat) -> Self { + pub fn new(curves: &OutlinedGlyph) -> Self { let bound = curves.px_bounds(); let metrics = Metrics { @@ -51,17 +45,7 @@ impl Bitmap { let mut bitmap = Grid::new(metrics.height, metrics.width); curves.draw(|x, y, c| { - let value = match format { - PixelFormat::Gray => (c * 255.0).round() as u8, - PixelFormat::Monochrome => { - if c <= 0.5 { - u8::MIN - } else { - u8::MAX - } - } - }; - + let value = (c * 255.0).round() as u8; bitmap[y as usize][x as usize] = value }); diff --git a/src/rasterizer/font_face.rs b/src/rasterizer/font_face.rs deleted file mode 100644 index 40c3bd0..0000000 --- a/src/rasterizer/font_face.rs +++ /dev/null @@ -1,44 +0,0 @@ -// FontFor: find fonts which can show a specified character -// Copyright (C) 2019 - 2023 7sDream and contributors -// -// This file is part of FontFor. -// -// FontFor is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use ab_glyph::{Font, FontRef, GlyphId, InvalidFont}; - -use super::{Bitmap, PixelFormat}; - -pub struct FontFace<'a> { - face: FontRef<'a>, - height: u32, - width: u32, -} - -impl<'a> FontFace<'a> { - pub fn new(data: &'a [u8], index: u32) -> Result { - let face = FontRef::try_from_slice_and_index(data, index)?; - Ok(Self { face, height: 0, width: 0 }) - } - - pub fn set_size(&mut self, height: u32, width: u32) { - self.height = height; - self.width = width; - } - - pub fn load_glyph(self, gid: u16, format: PixelFormat) -> Option { - let curves = self.face.outline_glyph(GlyphId(gid).with_scale(self.height as f32))?; - Some(Bitmap::new(&curves, format)) - } -} diff --git a/src/rasterizer/mod.rs b/src/rasterizer/mod.rs index ac98fba..6b22d61 100644 --- a/src/rasterizer/mod.rs +++ b/src/rasterizer/mod.rs @@ -17,7 +17,36 @@ // along with this program. If not, see . mod bitmap; -mod font_face; -pub use bitmap::{Bitmap, Metrics, PixelFormat}; -pub use font_face::FontFace; +use ab_glyph::{Font, FontRef, GlyphId, InvalidFont, PxScale}; + +pub use self::bitmap::{Bitmap, Metrics}; + +pub struct Rasterizer<'a> { + face: FontRef<'a>, + height: u32, + hscale: f32, +} + +impl<'a> Rasterizer<'a> { + pub fn new(data: &'a [u8], index: u32) -> Result { + let face = FontRef::try_from_slice_and_index(data, index)?; + Ok(Self { face, height: 0, hscale: 1.0 }) + } + + pub fn set_pixel_height(&mut self, height: u32) { + self.height = height; + } + + pub fn set_hscale(&mut self, scale: f32) { + self.hscale = scale + } + + pub fn rasterize(self, gid: u16) -> Option { + let glyph_id = GlyphId(gid); + let glyph = glyph_id + .with_scale(PxScale { x: self.height as f32 * self.hscale, y: self.height as f32 }); + let curve = self.face.outline_glyph(glyph)?; + Some(Bitmap::new(&curve)) + } +}