From fa2844580d82db19e9f376d7f557ebd38d88052d Mon Sep 17 00:00:00 2001 From: 7sDream Date: Sun, 31 May 2020 23:15:07 +0800 Subject: [PATCH] feat(render-trait): add traits to support different render library --- src/font/mod.rs | 1 + src/font/render/mod.rs | 31 ++++++++++ src/ft/bitmap.rs | 96 ++++++++++++++++++++++-------- src/ft/font_face.rs | 41 +++++++------ src/ft/library.rs | 22 ++++--- src/preview/terminal/render/mod.rs | 26 ++++---- src/preview/terminal/ui/mod.rs | 38 ++++++------ src/preview/terminal/ui/state.rs | 53 ++++++++++------- 8 files changed, 202 insertions(+), 106 deletions(-) create mode 100644 src/font/render/mod.rs diff --git a/src/font/mod.rs b/src/font/mod.rs index 1c02bac..e931045 100644 --- a/src/font/mod.rs +++ b/src/font/mod.rs @@ -16,6 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . pub mod matcher; +pub mod render; use { matcher::{FontMatcher, FontSet}, diff --git a/src/font/render/mod.rs b/src/font/render/mod.rs new file mode 100644 index 0000000..f72f1b4 --- /dev/null +++ b/src/font/render/mod.rs @@ -0,0 +1,31 @@ +use std::borrow::Cow; + +pub trait CharRenderResult: Sized { + type Render: CharRenderer; + + fn return_render(self) -> Self::Render; + fn get_height(&self) -> usize; + fn get_width(&self) -> usize; + fn get_buffer(&self) -> &[Cow<'_, [u8]>]; +} + +pub trait CharRenderer: Sized { + type Result: CharRenderResult; + type Error; + + fn set_cell_pixel(&mut self, height: usize, width: usize) -> Result<(), Self::Error>; + fn render_char(self, c: char, mono: bool) -> Result; +} + +pub enum LoaderInput<'a> { + FreeType(&'a str, usize), + #[allow(dead_code)] + CoreText(&'a str), +} + +pub trait CharRendererLoader<'i> { + type Render: CharRenderer; + type Error; + + fn load_render(&'i self, input: &LoaderInput<'_>) -> Result; +} diff --git a/src/ft/bitmap.rs b/src/ft/bitmap.rs index b3fde9c..c47b364 100644 --- a/src/ft/bitmap.rs +++ b/src/ft/bitmap.rs @@ -16,7 +16,12 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use {super::FontFace, freetype::freetype as ft, std::os::raw}; +use { + super::FontFace, + crate::font::render::CharRenderResult, + freetype::freetype as ft, + std::{borrow::Cow, iter::Iterator, os::raw}, +}; pub struct Metrics { pub left: ft::FT_Int, @@ -27,10 +32,35 @@ pub struct Metrics { pub struct Bitmap<'ft> { font_face: FontFace<'ft>, - pixel_mode: u8, - pitch: u32, metrics: Metrics, - bitmap: &'static [u8], + bitmap: Vec>, +} + +struct U8Bits { + index: u8, + value: u8, +} + +impl Iterator for U8Bits { + type Item = u8; + + fn next(&mut self) -> Option { + let index = self.index; + if index == 8 { + None + } else { + self.index += 1; + Some(if (self.value & (0b1000_0000 >> index)) == 0 { + u8::min_value() + } else { + u8::max_value() + }) + } + } +} + +const fn bits(value: u8) -> U8Bits { + U8Bits { index: 0, value } } impl<'ft> Bitmap<'ft> { @@ -42,35 +72,49 @@ impl<'ft> Bitmap<'ft> { let width = glyph.bitmap.width; let height = glyph.bitmap.rows; let pixel_mode = glyph.bitmap.pixel_mode; - let pitch = glyph.bitmap.pitch.abs() as u32; - let size = (pitch * height) as usize; + let pitch = glyph.bitmap.pitch.abs() as usize; + let size = pitch * height as usize; let bitmap = unsafe { std::slice::from_raw_parts(glyph.bitmap.buffer, size) }; - Self { font_face, pixel_mode, pitch, metrics: Metrics { left, top, height, width }, bitmap } - } - pub const fn return_font_face(self) -> FontFace<'ft> { - self.font_face + let bitmap = if u32::from(pixel_mode) == ft::FT_Pixel_Mode::FT_PIXEL_MODE_MONO as u32 { + bitmap + .chunks(pitch) + .map(|row| { + row.iter() + .flat_map(|value| bits(*value)) + .take(width as usize) + .collect::>() + }) + .map(Cow::Owned) + .collect::>() + } else { + bitmap.chunks(pitch).map(|row| Cow::from(&row[0..width as usize])).collect() + }; + + Self { font_face, metrics: Metrics { left, top, height, width }, bitmap } } pub const fn get_metrics(&self) -> &Metrics { &self.metrics } +} - pub fn get_pixel(&self, row: u32, col: u32) -> u8 { - if u32::from(self.pixel_mode) == ft::FT_Pixel_Mode::FT_PIXEL_MODE_MONO as u32 { - let index = (row * self.pitch + col / 8) as usize; - #[allow(clippy::cast_possible_truncation)] // because we mod with 8 so result is 0 - 7 - let bit_pos = (col % 8) as u8; - let gray = self.bitmap[index]; - let mask = 0b_1000_0000 >> (bit_pos); - if gray & mask == 0 { - u8::min_value() - } else { - u8::max_value() - } - } else { - let index = (row * self.pitch + col) as usize; - self.bitmap[index] - } +impl<'ft> CharRenderResult for Bitmap<'ft> { + type Render = FontFace<'ft>; + + fn return_render(self) -> Self::Render { + self.font_face + } + + fn get_height(&self) -> usize { + self.get_metrics().height as usize + } + + fn get_width(&self) -> usize { + self.get_metrics().width as usize + } + + fn get_buffer(&self) -> &[Cow<'_, [u8]>] { + &self.bitmap } } diff --git a/src/ft/font_face.rs b/src/ft/font_face.rs index 8004815..7f85039 100644 --- a/src/ft/font_face.rs +++ b/src/ft/font_face.rs @@ -17,8 +17,8 @@ // along with this program. If not, see . use { - super::{FreeTypeError, Library}, - crate::ft::bitmap::Bitmap, + super::{Bitmap, FreeTypeError, Library}, + crate::font::render::CharRenderer, freetype::freetype as ft, std::{ffi::CString, marker::PhantomData, path::Path, ptr}, }; @@ -49,22 +49,6 @@ impl<'ft> FontFace<'ft> { ret.as_result(Self { face, phantom: PhantomData }) } - pub fn set_cell_pixel( - &mut self, height: ft::FT_Long, width: ft::FT_Long, - ) -> Result<(), ft::FT_Error> { - let mut request = ft::FT_Size_RequestRec { - type_: ft::FT_Size_Request_Type::FT_SIZE_REQUEST_TYPE_CELL, - width: width << 6, // This FreeType API accept number in 26.6 fixed float format - height: height << 6, - horiResolution: 0, - vertResolution: 0, - }; - - let ret = unsafe { ft::FT_Request_Size(self.face, &mut request as *mut _) }; - - ret.as_result(()) - } - #[allow(dead_code)] pub fn set_height_pixel(&mut self, height: ft::FT_UInt) -> Result<(), ft::FT_Error> { let ret = unsafe { ft::FT_Set_Pixel_Sizes(self.face, 0, height) }; @@ -76,12 +60,31 @@ impl<'ft> FontFace<'ft> { let ret = unsafe { ft::FT_Set_Pixel_Sizes(self.face, width, 0) }; ret.as_result(()) } +} + +impl<'ft> CharRenderer for FontFace<'ft> { + type Result = Bitmap<'ft>; + type Error = ft::FT_Error; + + fn set_cell_pixel(&mut self, height: usize, width: usize) -> Result<(), ft::FT_Error> { + let mut request = ft::FT_Size_RequestRec { + type_: ft::FT_Size_Request_Type::FT_SIZE_REQUEST_TYPE_CELL, + width: (width << 6) as ft::FT_Long, // This FreeType API accept number in 26.6 fixed float format + height: (height << 6) as ft::FT_Long, + horiResolution: 0, + vertResolution: 0, + }; + + let ret = unsafe { ft::FT_Request_Size(self.face, &mut request as *mut _) }; + + ret.as_result(()) + } // FreeType's Load_Char API with render mode will change the glyph slot in `Face`, the result // `Bitmap` object can only be used before another call of load_char itself. So we consume self // and move it into the result `Bitmap`, which has an `return_face` method will consume itself // and return the `Face` to you. - pub fn load_char(self, c: char, mono: bool) -> Result, (Self, ft::FT_Error)> { + fn render_char(self, c: char, mono: bool) -> Result { let mut flag = ft::FT_LOAD_RENDER; if mono { flag |= ft::FT_LOAD_MONOCHROME; diff --git a/src/ft/library.rs b/src/ft/library.rs index a4f5dd6..5688171 100644 --- a/src/ft/library.rs +++ b/src/ft/library.rs @@ -18,8 +18,9 @@ use { super::{FontFace, FreeTypeError}, + crate::font::render::{CharRendererLoader, LoaderInput}, freetype::freetype as ft, - std::{path::Path, ptr}, + std::{hint::unreachable_unchecked, ptr}, }; pub struct Library { @@ -32,13 +33,20 @@ impl Library { let ret = unsafe { ft::FT_Init_FreeType(&mut library as *mut ft::FT_Library) }; ret.map_result(|| Self { library }) } +} + +impl<'i> CharRendererLoader<'i> for Library { + type Render = FontFace<'i>; + type Error = ft::FT_Error; - pub fn load_font

(&self, path: &P, index: usize) -> Result, ft::FT_Error> - where - P: AsRef, - { - let path = path.as_ref(); - FontFace::new(self, path, index as ft::FT_Long) + fn load_render(&'i self, input: &LoaderInput<'_>) -> Result { + match input { + LoaderInput::FreeType(path, index) => { + let path = path.as_ref(); + FontFace::new(self, path, *index as ft::FT_Long) + } + _ => unsafe { unreachable_unchecked() }, + } } } diff --git a/src/preview/terminal/render/mod.rs b/src/preview/terminal/render/mod.rs index 6813562..1e64c66 100644 --- a/src/preview/terminal/render/mod.rs +++ b/src/preview/terminal/render/mod.rs @@ -20,9 +20,9 @@ mod ascii; mod mono; mod moon; -use { - crate::ft::Bitmap, - std::fmt::{Display, Error, Formatter, Write}, +use std::{ + borrow::Cow, + fmt::{Display, Error, Formatter, Write}, }; pub use { @@ -60,20 +60,20 @@ pub trait CharBitmapRender { #[allow(clippy::too_many_arguments)] // need them..., fine, I will try make them a struct fn gray_to_char(&self, up: u8, left: u8, gray: u8, right: u8, down: u8) -> char; - fn render(&self, bm: &Bitmap<'_>) -> RenderResult { - let m = bm.get_metrics(); - + fn render(&self, bitmap: &[Cow<'_, [u8]>]) -> RenderResult { + let height = bitmap.len(); + let width = bitmap.get(0).map(|row| row.len()).unwrap_or_default(); RenderResult( - (0..m.height) + (0..height) .map(|row| { - (0..m.width) + (0..width) .map(move |col| { - let gray = bm.get_pixel(row, col); + let gray = bitmap[row][col]; - let l = if col > 0 { bm.get_pixel(row, col - 1) } else { 0 }; - let r = if col < m.width - 1 { bm.get_pixel(row, col + 1) } else { 0 }; - let u = if row > 0 { bm.get_pixel(row - 1, col) } else { 0 }; - let d = if row < m.height - 1 { bm.get_pixel(row + 1, col) } else { 0 }; + let l = if col > 0 { bitmap[row][col - 1] } else { 0 }; + let r = if col < width - 1 { bitmap[row][col] } else { 0 }; + let u = if row > 0 { bitmap[row - 1][col] } else { 0 }; + let d = if row < height - 1 { bitmap[row + 1][col] } else { 0 }; self.gray_to_char(u, l, gray, r, d) }) diff --git a/src/preview/terminal/ui/mod.rs b/src/preview/terminal/ui/mod.rs index 66d6932..375d9ea 100644 --- a/src/preview/terminal/ui/mod.rs +++ b/src/preview/terminal/ui/mod.rs @@ -21,7 +21,7 @@ mod event; mod state; use { - crate::{font::SortedFamilies, ft::Library as FtLibrary}, + crate::font::{render::CharRendererLoader, SortedFamilies}, canvas_render::CanvasRenderResult, crossterm::{ event::{KeyCode as CtKeyCode, KeyModifiers as CtKM}, @@ -52,13 +52,23 @@ enum OnEventResult { Exit, } -pub struct UI<'fc, 'ft> { +pub struct UI<'matcher, 'render, Library: CharRendererLoader<'render>> { idle_redraw: u8, - state: State<'fc, 'ft>, + state: State<'matcher, 'render, Library>, } -impl<'fc, 'ft> UI<'fc, 'ft> { - pub fn new(c: char, families: SortedFamilies<'fc>, ft: &'ft mut FtLibrary) -> Option { +fn generate_help_text(key: &'static str, help: &'static str) -> Vec> { + vec![ + Text::styled(key, Style::default().fg(Color::Cyan).modifier(Modifier::BOLD)), + Text::raw(": "), + Text::styled(help, Style::default().fg(Color::Blue).modifier(Modifier::BOLD)), + ] +} + +impl<'matcher, 'render, Library: CharRendererLoader<'render>> UI<'matcher, 'render, Library> { + pub fn new( + c: char, families: SortedFamilies<'matcher>, ft: &'render mut Library, + ) -> Option { if families.len() > 0 { Some(Self { state: State::new(c, families, ft), idle_redraw: 0 }) } else { @@ -123,14 +133,6 @@ impl<'fc, 'ft> UI<'fc, 'ft> { } } - fn generate_help_text<'a>(key: &'a str, help: &'a str) -> Vec> { - vec![ - Text::styled(key, Style::default().fg(Color::Cyan).modifier(Modifier::BOLD)), - Text::raw(": "), - Text::styled(help, Style::default().fg(Color::Blue).modifier(Modifier::BOLD)), - ] - } - fn draw_status_bar_info(&self, area: Rect, f: &mut Frame<'_, B>) where B: tui::backend::Backend, @@ -188,13 +190,13 @@ impl<'fc, 'ft> UI<'fc, 'ft> { ) .split(area); - let mut list_help = Self::generate_help_text("[Up]", "Prev Font "); - list_help.append(&mut Self::generate_help_text("[Down]", "Next Font")); + let mut list_help = generate_help_text("[Up]", "Prev Font "); + list_help.append(&mut generate_help_text("[Down]", "Next Font")); - let mut mode_help = Self::generate_help_text("[Left]", "Prev Mode "); - mode_help.append(&mut Self::generate_help_text("[Right]", "Next Mode")); + let mut mode_help = generate_help_text("[Left]", "Prev Mode "); + mode_help.append(&mut generate_help_text("[Right]", "Next Mode")); - let quit_help = Self::generate_help_text("[Q]", "Quit"); + let quit_help = generate_help_text("[Q]", "Quit"); f.render_widget( Paragraph::new(list_help.iter()) diff --git a/src/preview/terminal/ui/state.rs b/src/preview/terminal/ui/state.rs index de7b2bc..6351b99 100644 --- a/src/preview/terminal/ui/state.rs +++ b/src/preview/terminal/ui/state.rs @@ -16,10 +16,13 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +use crate::font::render::{CharRenderResult, CharRenderer}; use { crate::{ - font::{Font, SortedFamilies}, - ft::{FontFace as FtFontFace, Library as FtLibrary}, + font::{ + render::{CharRendererLoader, LoaderInput}, + Font, SortedFamilies, + }, preview::terminal::render::{ AsciiRender, AsciiRenders, CharBitmapRender, MonoRender, MoonRender, RenderResult, }, @@ -55,21 +58,23 @@ thread_local! { #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] struct CacheKey(usize, RenderType, u32, u32); -pub struct State<'fc, 'ft> { +pub struct State<'matcher, 'render, Library: CharRendererLoader<'render>> { c: char, - font_faces_info: Vec>, + font_faces_info: Vec>, name_width_max: usize, list_state: RefCell, height: Cell, width: Cell, rt: RenderType, cache: RefCell>>>, - font_faces: Vec>>>, - ft: &'ft FtLibrary, + font_faces: Vec>>, + render_library: &'render Library, } -impl<'fc, 'ft> State<'fc, 'ft> { - pub fn new(c: char, families: SortedFamilies<'fc>, ft: &'ft FtLibrary) -> Self { +impl<'matcher, 'render, Library: CharRendererLoader<'render>> State<'matcher, 'render, Library> { + pub fn new( + c: char, families: SortedFamilies<'matcher>, render_library: &'render Library, + ) -> Self { let font_faces_info: Vec<_> = families.into_iter().flat_map(|f| f.fonts.into_iter().map(|r| r.0)).collect(); let name_width_max = @@ -95,7 +100,7 @@ impl<'fc, 'ft> State<'fc, 'ft> { rt: RenderType::Mono, cache, font_faces, - ft, + render_library, } } @@ -108,33 +113,35 @@ impl<'fc, 'ft> State<'fc, 'ft> { self.cache.borrow_mut().entry(key).or_insert_with(|| Rc::new(self.real_render())).clone() } - fn get_font_face(&self) -> Result, &'static str> { + fn get_font_face(&self) -> Result { let font_face_slot = self.font_faces.get(self.index()).unwrap(); font_face_slot .take() .ok_or(()) .or_else(|_| { - let font_info = &self.font_faces_info[self.index()]; + let font_info = self.font_faces_info.get(self.index()).unwrap(); let path: &str = &font_info.path; - self.ft.load_font(&path, font_info.index).map_err(|_| "Can't load current font") + self.render_library + .load_render(&LoaderInput::FreeType(path, font_info.index)) + .map_err(|_| "Can't load current font") }) .and_then(|font_face| self.set_font_face_size(font_face)) } - fn return_font_face(&self, font: FtFontFace<'ft>) { + fn return_font_face(&self, font: Library::Render) { let font_face_slot = self.font_faces.get(self.index()).unwrap(); font_face_slot.set(Some(font)); } fn set_font_face_size( - &self, mut font_face: FtFontFace<'ft>, - ) -> Result, &'static str> { - let height = self.height.get(); - let width = self.width.get(); + &self, mut font_face: Library::Render, + ) -> Result { + let height = self.height.get() as usize; + let width = self.width.get() as usize; font_face - .set_cell_pixel(height.into(), width.into()) + .set_cell_pixel(height, width) .map(|_| font_face) .map_err(|_| "Current font don't support this size") } @@ -142,13 +149,13 @@ impl<'fc, 'ft> State<'fc, 'ft> { fn real_render(&self) -> Result { let font_face = self.get_font_face()?; - match font_face.load_char(self.c, self.rt == RenderType::Mono) { + match font_face.render_char(self.c, self.rt == RenderType::Mono) { Ok(bitmap) => { let result: RenderResult = RENDERS.with(|renders| { let render = renders.get(&self.rt).unwrap(); - render.render(&bitmap) + render.render(bitmap.get_buffer()) }); - self.return_font_face(bitmap.return_font_face()); + self.return_font_face(bitmap.return_render()); Ok(result) } Err((font, _)) => { @@ -162,7 +169,7 @@ impl<'fc, 'ft> State<'fc, 'ft> { &self.font_faces_info[self.index()].fullname } - pub const fn name_width_max(&self) -> usize { + pub fn name_width_max(&self) -> usize { self.name_width_max } @@ -191,7 +198,7 @@ impl<'fc, 'ft> State<'fc, 'ft> { self.list_state.borrow_mut().select(changed); } - pub const fn get_render_type(&self) -> &RenderType { + pub fn get_render_type(&self) -> &RenderType { &self.rt }