diff --git a/Cargo.lock b/Cargo.lock index 9349c19..0ef749d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,6 +43,18 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "bytemuck" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" + [[package]] name = "clap" version = "2.34.0" @@ -51,13 +63,22 @@ checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ "ansi_term", "atty", - "bitflags", + "bitflags 1.3.2", "strsim", "textwrap", "unicode-width", "vec_map", ] +[[package]] +name = "core_maths" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b02505ccb8c50b0aa21ace0fc08c3e53adebd4e58caa18a36152803c7709a3" +dependencies = [ + "libm", +] + [[package]] name = "hermit-abi" version = "0.1.19" @@ -79,6 +100,18 @@ version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + [[package]] name = "memchr" version = "2.5.0" @@ -97,7 +130,7 @@ version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -126,6 +159,30 @@ version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +[[package]] +name = "rustybuzz" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c85d1ccd519e61834798eb52c4e886e8c2d7d698dd3d6ce0b1b47eb8557f1181" +dependencies = [ + "bitflags 2.6.0", + "bytemuck", + "core_maths", + "log", + "smallvec", + "ttf-parser", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-properties", + "unicode-script", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + [[package]] name = "strsim" version = "0.8.0" @@ -161,10 +218,44 @@ dependencies = [ "clap", "lazy_static", "regex", + "rustybuzz", "termion", "unicode-width", ] +[[package]] +name = "ttf-parser" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be21190ff5d38e8b4a2d3b6a3ae57f612cc39c96e83cedeaf7abc338a8bac4a" +dependencies = [ + "core_maths", +] + +[[package]] +name = "unicode-bidi-mirroring" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64af057ad7466495ca113126be61838d8af947f41d93a949980b2389a118082f" + +[[package]] +name = "unicode-ccc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "260bc6647b3893a9a90668360803a15f96b85a5257b1c3a0c3daf6ae2496de42" + +[[package]] +name = "unicode-properties" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524" + +[[package]] +name = "unicode-script" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8d71f5726e5f285a935e9fe8edfd53f0491eb6e9a5774097fdabee7cd8c9cd" + [[package]] name = "unicode-width" version = "0.1.10" diff --git a/Cargo.toml b/Cargo.toml index 582943a..1d84814 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ clap = "2.34.0" base64 = "0.13.1" unicode-width = "0.1.10" lazy_static = "1.4.0" +rustybuzz = "0.18.0" [[bin]] name = "thumbs" diff --git a/MononokiNerdFontPropo-Regular.ttf b/MononokiNerdFontPropo-Regular.ttf new file mode 100644 index 0000000..57e5b40 Binary files /dev/null and b/MononokiNerdFontPropo-Regular.ttf differ diff --git a/src/main.rs b/src/main.rs index 49ded04..d7fef12 100644 --- a/src/main.rs +++ b/src/main.rs @@ -136,6 +136,12 @@ fn app_args<'a>() -> clap::ArgMatches<'a> { .short("t") .takes_value(true), ) + .arg( + Arg::with_name("font_file") + .help("Font file to use to calculate ligatures; will skip ligature counting if not provided") + .long("font-file") + .default_value(""), + ) .get_matches() } @@ -163,6 +169,7 @@ fn main() { let select_background_color = colors::get_color(args.value_of("select_background_color").unwrap()); let multi_foreground_color = colors::get_color(args.value_of("multi_foreground_color").unwrap()); let multi_background_color = colors::get_color(args.value_of("multi_background_color").unwrap()); + let font_file = args.value_of("font_file").unwrap(); let stdin = io::stdin(); let mut handle = stdin.lock(); @@ -190,6 +197,7 @@ fn main() { background_color, hint_foreground_color, hint_background_color, + font_file, ); viewbox.present() diff --git a/src/swapper.rs b/src/swapper.rs index c901f48..d575b4b 100644 --- a/src/swapper.rs +++ b/src/swapper.rs @@ -181,6 +181,7 @@ impl<'a> Swapper<'a> { "select-bg-color", "multi-fg-color", "multi-bg-color", + "font-file", ]; if string_params.iter().any(|&x| x == name) { diff --git a/src/view.rs b/src/view.rs index e92cf95..2899f45 100644 --- a/src/view.rs +++ b/src/view.rs @@ -1,5 +1,6 @@ use super::*; use std::char; +use std::fs; use std::io::{stdout, Read, Write}; use termion::async_stdin; use termion::event::Key; @@ -8,6 +9,7 @@ use termion::raw::IntoRawMode; use termion::screen::AlternateScreen; use termion::{color, cursor}; +use rustybuzz::{Face, UnicodeBuffer}; use unicode_width::UnicodeWidthStr; pub struct View<'a> { @@ -25,6 +27,7 @@ pub struct View<'a> { background_color: Box, hint_background_color: Box, hint_foreground_color: Box, + font_file: &'a str, chosen: Vec<(String, bool)>, } @@ -49,6 +52,7 @@ impl<'a> View<'a> { background_color: Box, hint_foreground_color: Box, hint_background_color: Box, + font_file: &'a str, ) -> View<'a> { let matches = state.matches(reverse, unique); let skip = if reverse { matches.len() - 1 } else { 0 }; @@ -68,6 +72,7 @@ impl<'a> View<'a> { background_color, hint_foreground_color, hint_background_color, + font_file, chosen: vec![], } } @@ -92,6 +97,27 @@ impl<'a> View<'a> { } } + fn get_ligature_offset(&self, text: &str) -> u32 { + let font_data = fs::read(self.font_file).expect("Font file not found"); + let face = Face::from_slice(&font_data, 0).expect("Failed to load font face"); + + let mut buffer = UnicodeBuffer::new(); + buffer.push_str(text); + let glyph_buffer = rustybuzz::shape(&face, &[], buffer); + + let mut ligature_offset = 0; + let mut prev_cluster_index = 0; + for glyph_info in glyph_buffer.glyph_infos() { + let cluster_size = glyph_info.cluster - prev_cluster_index; + if cluster_size > 1 { + ligature_offset += cluster_size - 1; + } + prev_cluster_index = glyph_info.cluster; + } + + ligature_offset + } + fn render(&self, stdout: &mut dyn Write, typed_hint: &str) -> () { write!(stdout, "{}", cursor::Hide).unwrap(); @@ -126,14 +152,15 @@ impl<'a> View<'a> { // Find long utf sequences and extract it from mat.x let line = &self.state.lines[mat.y as usize]; let prefix = &line[0..mat.x as usize]; + let ligature_offset = if self.font_file.is_empty() {0} else {self.get_ligature_offset(prefix)}; let extra = prefix.width_cjk() - prefix.chars().count(); - let offset = (mat.x as u16) - (extra as u16); + let offset = (mat.x as u16) - (extra as u16) - (ligature_offset as u16); let text = self.make_hint_text(mat.text); print!( - "{goto}{background}{foregroud}{text}{resetf}{resetb}", + "{goto}{background}{foreground}{text}{resetf}{resetb}", goto = cursor::Goto(offset + 1, mat.y as u16 + 1), - foregroud = color::Fg(&**selected_color), + foreground = color::Fg(&**selected_color), background = color::Bg(&**selected_background_color), resetf = color::Fg(color::Reset), resetb = color::Bg(color::Reset), @@ -152,9 +179,9 @@ impl<'a> View<'a> { let final_position = std::cmp::max(offset as i16 + extra_position as i16, 0); print!( - "{goto}{background}{foregroud}{text}{resetf}{resetb}", + "{goto}{background}{foreground}{text}{resetf}{resetb}", goto = cursor::Goto(final_position as u16 + 1, mat.y as u16 + 1), - foregroud = color::Fg(&*self.hint_foreground_color), + foreground = color::Fg(&*self.hint_foreground_color), background = color::Bg(&*self.hint_background_color), resetf = color::Fg(color::Reset), resetb = color::Bg(color::Reset), @@ -163,9 +190,9 @@ impl<'a> View<'a> { if hint.starts_with(typed_hint) { print!( - "{goto}{background}{foregroud}{text}{resetf}{resetb}", + "{goto}{background}{foreground}{text}{resetf}{resetb}", goto = cursor::Goto(final_position as u16 + 1, mat.y as u16 + 1), - foregroud = color::Fg(&*self.multi_foreground_color), + foreground = color::Fg(&*self.multi_foreground_color), background = color::Bg(&*self.multi_background_color), resetf = color::Fg(color::Reset), resetb = color::Bg(color::Reset), @@ -337,6 +364,7 @@ mod tests { hint_background_color: colors::get_color("default"), hint_foreground_color: colors::get_color("default"), chosen: vec![], + font_file: "MononokiNerdFontPropo-Regular.ttf", }; let result = view.make_hint_text("a"); @@ -345,5 +373,8 @@ mod tests { view.contrast = true; let result = view.make_hint_text("a"); assert_eq!(result, "[a]".to_string()); + + let result = view.get_ligature_offset(&"↳ 20240913-ligatures-test".to_string()); + assert_eq!(result, 2) } }