diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cebbb9..115825c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Remove dependency of fontconfig and freetype lib - Add `-vv` option to show font file location and face index - ASCII mode render result now not narrow (Issue [#61](https://github.com/7sDream/fontfor/issues/61), fixed by PR [#63](https://github.com/7sDream/fontfor/pull/63)) +- Support custom font paths (Issue [#62](https://github.com/7sDream/fontfor/issues/62)) - Now release contains ia32/x64/arm64 binary for Windows - Now release contains x64/arm64 binary for macOS - Now release contains x64/arm64/armhf binary Linux diff --git a/src/args.rs b/src/args.rs index ac8d48c..573ab89 100644 --- a/src/args.rs +++ b/src/args.rs @@ -16,6 +16,8 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +use std::path::PathBuf; + use clap::Parser; use super::one_char::OneChar; @@ -36,6 +38,11 @@ pub struct Args { #[arg(short, long)] pub tui: bool, + /// Also load fonts in a custom path. + /// This arg can be provided multiple times. + #[arg(short = 'I', long = "include", name = "PATH", action = clap::ArgAction::Append)] + pub custom_font_paths: Vec, + /// The character #[arg(name = "CHAR")] pub char: OneChar, diff --git a/src/loader/face_info.rs b/src/loader/face_info.rs index a3219d4..f5fd363 100644 --- a/src/loader/face_info.rs +++ b/src/loader/face_info.rs @@ -18,16 +18,18 @@ use std::{borrow::Cow, path::Path}; +use fontdb::Source; use ttf_parser::{ name::{name_id, Table as NameTable}, - GlyphId, Language, RawFace, + Language, RawFace, }; use super::{ cmap::CMapTable, error::{BROKEN_NAME_TABLE, MISSING_NAME_TABLE, NAME_TAG}, - Error, Result, DATABASE, + Error, Result, }; +use crate::loader::database; /// FaceInfo contains basic font face info like family and name, /// and pre-located glyph id for target character. @@ -40,12 +42,12 @@ pub struct FaceInfo { pub path: &'static Path, pub index: u32, - pub gid: GlyphId, + pub gid: u16, } impl FaceInfo { pub fn parse_if_contains(face: &'static fontdb::FaceInfo, c: char) -> Result> { - let Some((gid, name)) = DATABASE + let Some((gid, name)) = database() .with_face_data(face.id, |data, index| -> Result<_> { let rf = RawFace::parse(data, index)?; let Some(gid) = CMapTable::parse(rf)?.glyph_index(c) else { @@ -53,7 +55,7 @@ impl FaceInfo { }; let name = Self::parse_full_name(rf)?; - Ok(Some((gid, name))) + Ok(Some((gid.0, name))) }) .expect("we only load font from database so it must not None")? else { @@ -71,7 +73,7 @@ impl FaceInfo { .unwrap_or_else(|| face.post_script_name.as_str().into()); let path = match face.source { - fontdb::Source::File(ref path) => path, + Source::File(ref path) => path, _ => unreachable!("we only load font file, so source must be File variant"), }; diff --git a/src/loader/mod.rs b/src/loader/mod.rs index 0dbdc4f..5039253 100644 --- a/src/loader/mod.rs +++ b/src/loader/mod.rs @@ -20,19 +20,40 @@ mod face_info; mod cmap; mod error; -use once_cell::sync::Lazy; +use std::path::Path; + +use fontdb::Database; +use once_cell::sync::OnceCell; pub use self::{error::Error, face_info::FaceInfo}; pub type Result = std::result::Result; -pub static DATABASE: Lazy = Lazy::new(|| { - let mut db = fontdb::Database::default(); +static DATABASE: OnceCell = OnceCell::new(); + +pub fn init(paths: I) +where + I: IntoIterator, + P: AsRef, +{ + let mut db = Database::default(); + db.load_system_fonts(); - db -}); -pub fn faces_contains(c: char) -> Vec { - DATABASE + for path in paths.into_iter() { + db.load_fonts_dir(path) + } + + if DATABASE.set(db).is_err() { + panic!("call init more then once") + } +} + +pub fn database() -> &'static Database { + DATABASE.get().expect("initialized") +} + +pub fn query(c: char) -> Vec { + database() .faces() .filter_map(|info| { let face = FaceInfo::parse_if_contains(info, c); diff --git a/src/main.rs b/src/main.rs index 265908c..d9b34bf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -38,8 +38,9 @@ use preview::{browser::ServerBuilder as PreviewServerBuilder, terminal::ui::UI}; fn main() { let argument = args::get(); - let font_set = loader::faces_contains(argument.char.0); + loader::init(&argument.custom_font_paths); + let font_set = loader::query(argument.char.0); let families = family::group_by_family_sort_by_name(&font_set); if families.is_empty() { diff --git a/src/preview/terminal/ui/state.rs b/src/preview/terminal/ui/state.rs index 53edf34..313762d 100644 --- a/src/preview/terminal/ui/state.rs +++ b/src/preview/terminal/ui/state.rs @@ -27,7 +27,7 @@ use tui::widgets::ListState; use super::cache::{CacheKey, GlyphCache, GlyphCanvasShape, RenderType, CHAR_RENDERS, MONO_RENDER}; use crate::{ family::Family, - loader::{FaceInfo, DATABASE}, + loader::{self, FaceInfo}, preview::terminal::{render::Render, ui::cache::GlyphParagraph}, rasterizer::{Bitmap, Rasterizer}, }; @@ -105,14 +105,14 @@ impl<'a> State<'a> { None }; - DATABASE + loader::database() .with_face_data(info.id, |data, index| -> Option { 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) + r.rasterize(info.gid) }) .ok_or("Can't read this font file")? .ok_or("Can't get glyph from this font")