Skip to content

Commit

Permalink
feat(render-trait): add traits to support different render library
Browse files Browse the repository at this point in the history
  • Loading branch information
7sDream committed May 31, 2020
1 parent 2f85c9b commit a2a92bb
Show file tree
Hide file tree
Showing 8 changed files with 203 additions and 106 deletions.
1 change: 1 addition & 0 deletions src/font/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
pub mod matcher;
pub mod render;

use {
matcher::{FontMatcher, FontSet},
Expand Down
32 changes: 32 additions & 0 deletions src/font/render/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use std::borrow::Cow;

pub trait CharRenderResult: Sized {
type Render: CharRender<Result = Self>;

fn return_render(self) -> Self::Render;
fn get_height(&self) -> usize;
fn get_width(&self) -> usize;
fn get_buffer(&self) -> &[Cow<'_, [u8]>];
}

pub trait CharRender: Sized {
type Result: CharRenderResult<Render = Self>;
type Error;

fn set_cell_pixel(&mut self, height: usize, width: usize) -> Result<(), Self::Error>;
fn render_char(self, c: char, mono: bool) -> Result<Self::Result, (Self, Self::Error)>;
}

pub enum LoaderInput<'a> {
FreeType(&'a str, usize),
#[allow(dead_code)]
CoreText(&'a str),
}

pub trait CharRenderLoader<'i> {
type Render: CharRender;
type Error;

fn load_render(&'i self, input: &LoaderInput<'_>) -> Result<Self::Render, Self::Error>;
}
gi
96 changes: 70 additions & 26 deletions src/ft/bitmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

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,
Expand All @@ -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<Cow<'ft, [u8]>>,
}

struct U8Bits {
index: u8,
value: u8,
}

impl Iterator for U8Bits {
type Item = u8;

fn next(&mut self) -> Option<Self::Item> {
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()
})
}
}
}

fn bits(value: u8) -> U8Bits {
U8Bits { index: 0, value }
}

impl<'ft> Bitmap<'ft> {
Expand All @@ -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::<Vec<_>>()
})
.map(Cow::Owned)
.collect::<Vec<_>>()
} 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
}
}
41 changes: 22 additions & 19 deletions src/ft/font_face.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.

use {
super::{FreeTypeError, Library},
crate::ft::bitmap::Bitmap,
super::{Bitmap, FreeTypeError, Library},
crate::font::render::CharRender,
freetype::freetype as ft,
std::{ffi::CString, marker::PhantomData, path::Path, ptr},
};
Expand Down Expand Up @@ -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) };
Expand All @@ -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> CharRender 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<Bitmap<'ft>, (Self, ft::FT_Error)> {
fn render_char(self, c: char, mono: bool) -> Result<Self::Result, (Self, ft::FT_Error)> {
let mut flag = ft::FT_LOAD_RENDER;
if mono {
flag |= ft::FT_LOAD_MONOCHROME;
Expand Down
22 changes: 15 additions & 7 deletions src/ft/library.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@

use {
super::{FontFace, FreeTypeError},
crate::font::render::{CharRenderLoader, LoaderInput},
freetype::freetype as ft,
std::{path::Path, ptr},
std::{hint::unreachable_unchecked, ptr},
};

pub struct Library {
Expand All @@ -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> CharRenderLoader<'i> for Library {
type Render = FontFace<'i>;
type Error = ft::FT_Error;

pub fn load_font<P>(&self, path: &P, index: usize) -> Result<FontFace<'_>, ft::FT_Error>
where
P: AsRef<Path>,
{
let path = path.as_ref();
FontFace::new(self, path, index as ft::FT_Long)
fn load_render(&'i self, input: &LoaderInput<'_>) -> Result<Self::Render, Self::Error> {
match input {
&LoaderInput::FreeType(path, index) => {
let path = path.as_ref();
FontFace::new(self, path, index as ft::FT_Long)
}
_ => unsafe { unreachable_unchecked() },
}
}
}

Expand Down
26 changes: 13 additions & 13 deletions src/preview/terminal/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
})
Expand Down
38 changes: 20 additions & 18 deletions src/preview/terminal/ui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ mod event;
mod state;

use {
crate::{font::SortedFamilies, ft::Library as FtLibrary},
crate::font::{render::CharRenderLoader, SortedFamilies},
canvas_render::CanvasRenderResult,
crossterm::{
event::{KeyCode as CtKeyCode, KeyModifiers as CtKM},
Expand Down Expand Up @@ -52,13 +52,23 @@ enum OnEventResult {
Exit,
}

pub struct UI<'fc, 'ft> {
pub struct UI<'matcher, 'render, Library: CharRenderLoader<'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<Self> {
fn generate_help_text(key: &'static str, help: &'static str) -> Vec<Text<'static>> {
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: CharRenderLoader<'render>> UI<'matcher, 'render, Library> {
pub fn new(
c: char, families: SortedFamilies<'matcher>, ft: &'render mut Library,
) -> Option<Self> {
if families.len() > 0 {
Some(Self { state: State::new(c, families, ft), idle_redraw: 0 })
} else {
Expand Down Expand Up @@ -123,14 +133,6 @@ impl<'fc, 'ft> UI<'fc, 'ft> {
}
}

fn generate_help_text<'a>(key: &'a str, help: &'a str) -> Vec<Text<'a>> {
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<B>(&self, area: Rect, f: &mut Frame<'_, B>)
where
B: tui::backend::Backend,
Expand Down Expand Up @@ -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())
Expand Down
Loading

0 comments on commit a2a92bb

Please sign in to comment.