From 9cf94f7377e5f4951dfc4a55691de39bd54d8af9 Mon Sep 17 00:00:00 2001 From: Stephen Seo Date: Mon, 1 Aug 2022 12:04:38 +0900 Subject: [PATCH] Impl unicode support via fontconfig and freetype --- src/display.rs | 158 ++++++++++++++++++++++++++++++++++++++--- src/main.rs | 5 ++ src/unicode_support.rs | 3 + 3 files changed, 156 insertions(+), 10 deletions(-) diff --git a/src/display.rs b/src/display.rs index 5f2f4e2..cde0067 100644 --- a/src/display.rs +++ b/src/display.rs @@ -9,6 +9,7 @@ use ggez::graphics::{ use ggez::{timer, Context, GameError, GameResult}; use image::io::Reader as ImageReader; use std::io::Cursor; +use std::path::PathBuf; use std::sync::atomic::AtomicBool; use std::sync::{atomic::Ordering, Arc, RwLockReadGuard}; use std::thread; @@ -50,14 +51,122 @@ fn seconds_to_time(seconds: f64) -> String { } #[cfg(not(feature = "unicode_support"))] -fn string_to_text(string: String) -> Text { +fn string_to_text( + string: String, + loaded_fonts: &mut Vec<(PathBuf, Font)>, + ctx: &mut Context, +) -> Text { Text::new(TextFragment::from(string)) } #[cfg(feature = "unicode_support")] -fn string_to_text(string: String) -> Text { - // TODO impl - Text::new(TextFragment::from(string)) +fn string_to_text( + string: String, + loaded_fonts: &mut Vec<(PathBuf, Font)>, + ctx: &mut Context, +) -> Text { + use super::unicode_support; + + let mut text = Text::default(); + let mut current_fragment = TextFragment::default(); + + if string.is_ascii() { + current_fragment.text = string; + text.add(current_fragment); + return text; + } + + let find_font = + |c: char, loaded_fonts: &mut Vec<(PathBuf, Font)>, ctx: &mut Context| -> Option { + for (idx, (path, _)) in loaded_fonts.iter().enumerate() { + let result = unicode_support::font_has_char(c, path); + if result.is_ok() && result.unwrap() { + return Some(idx); + } + } + + let find_result = unicode_support::get_matching_font_from_char(c); + if let Ok(path) = find_result { + let new_font = Font::new(ctx, &path); + if let Ok(font) = new_font { + loaded_fonts.push((path, font)); + return Some(loaded_fonts.len() - 1); + } else { + println!("Failed to load {:?}: {:?}", &path, new_font); + } + } else { + println!("Failed to find font for {}", c); + } + + None + }; + + let mut prev_is_ascii = true; + for c in string.chars() { + if c.is_ascii() { + if prev_is_ascii { + current_fragment.text.push(c); + } else { + if !current_fragment.text.is_empty() { + text.add(current_fragment); + current_fragment = Default::default(); + } + current_fragment.text.push(c); + } + prev_is_ascii = true; + } else { + let idx_opt = find_font(c, loaded_fonts, ctx); + if prev_is_ascii { + if let Some(idx) = idx_opt { + if !current_fragment.text.is_empty() { + text.add(current_fragment); + current_fragment = Default::default(); + } + let (_, font) = loaded_fonts[idx]; + current_fragment.font = Some(font); + } + current_fragment.text.push(c); + } else { + if let Some(idx) = idx_opt { + let font = loaded_fonts[idx].1; + if let Some(current_font) = current_fragment.font { + if current_font == font { + current_fragment.text.push(c); + } else { + if !current_fragment.text.is_empty() { + text.add(current_fragment); + current_fragment = Default::default(); + } + current_fragment.text.push(c); + current_fragment.font = Some(font); + } + } else if current_fragment.text.is_empty() { + current_fragment.text.push(c); + current_fragment.font = Some(font); + } else { + text.add(current_fragment); + current_fragment = Default::default(); + + current_fragment.text.push(c); + current_fragment.font = Some(font); + } + } else { + if !current_fragment.text.is_empty() && current_fragment.font.is_some() { + text.add(current_fragment); + current_fragment = Default::default(); + } + current_fragment.text.push(c); + } + } + prev_is_ascii = false; + } + } + + if !current_fragment.text.is_empty() { + text.add(current_fragment); + } + + text } pub struct MPDDisplay { @@ -74,10 +183,13 @@ pub struct MPDDisplay { album_art: Option, album_art_draw_transform: Option, filename_text: Text, + filename_string_cache: String, filename_transform: Transform, artist_text: Text, + artist_string_cache: String, artist_transform: Transform, title_text: Text, + title_string_cache: String, title_transform: Transform, timer_text: Text, timer_transform: Transform, @@ -89,6 +201,7 @@ pub struct MPDDisplay { hide_text: bool, tried_album_art_in_dir: bool, mpd_play_state: MPDPlayState, + loaded_fonts: Vec<(PathBuf, Font)>, } impl MPDDisplay { @@ -122,6 +235,10 @@ impl MPDDisplay { hide_text: false, tried_album_art_in_dir: false, mpd_play_state: MPDPlayState::Playing, + loaded_fonts: Vec::new(), + filename_string_cache: String::new(), + artist_string_cache: String::new(), + title_string_cache: String::new(), } } @@ -609,7 +726,14 @@ impl EventHandler for MPDDisplay { } else { self.mpd_play_state = MPDPlayState::Playing; if !shared.title.is_empty() { - self.title_text = string_to_text(shared.title.clone()); + if shared.title != self.title_string_cache { + self.title_string_cache = shared.title.clone(); + self.title_text = string_to_text( + shared.title.clone(), + &mut self.loaded_fonts, + ctx, + ); + } } else { self.dirty_flag .as_ref() @@ -617,7 +741,14 @@ impl EventHandler for MPDDisplay { .store(true, Ordering::Relaxed); } if !shared.artist.is_empty() { - self.artist_text = string_to_text(shared.artist.clone()); + if shared.artist != self.artist_string_cache { + self.artist_string_cache = shared.artist.clone(); + self.artist_text = string_to_text( + shared.artist.clone(), + &mut self.loaded_fonts, + ctx, + ); + } } else { self.dirty_flag .as_ref() @@ -625,11 +756,18 @@ impl EventHandler for MPDDisplay { .store(true, Ordering::Relaxed); } if !shared.filename.is_empty() { - if self.filename_text.contents() != shared.filename { - self.album_art = None; - self.tried_album_art_in_dir = false; + if shared.filename != self.filename_string_cache { + self.filename_string_cache = shared.filename.clone(); + if self.filename_text.contents() != shared.filename { + self.album_art = None; + self.tried_album_art_in_dir = false; + } + self.filename_text = string_to_text( + shared.filename.clone(), + &mut self.loaded_fonts, + ctx, + ); } - self.filename_text = string_to_text(shared.filename.clone()); } else { self.dirty_flag .as_ref() diff --git a/src/main.rs b/src/main.rs index a5387e1..aa4ce5a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,9 +7,11 @@ mod unicode_support; use ggez::conf::{WindowMode, WindowSetup}; use ggez::event::winit_event::{ElementState, KeyboardInput, ModifiersState}; use ggez::event::{self, ControlFlow, EventHandler}; +use ggez::filesystem::mount; use ggez::graphics::{self, Rect}; use ggez::{ContextBuilder, GameError}; use std::net::Ipv4Addr; +use std::path::PathBuf; use std::thread; use std::time::{Duration, Instant}; use structopt::StructOpt; @@ -63,6 +65,9 @@ fn main() -> Result<(), String> { .build() .expect("Failed to create ggez context"); + // mount "/" read-only so that fonts can be loaded via absolute paths + mount(&mut ctx, &PathBuf::from("/"), true); + let mut display = display::MPDDisplay::new(&mut ctx, opt.clone()); let mut modifiers_state: ModifiersState = ModifiersState::default(); diff --git a/src/unicode_support.rs b/src/unicode_support.rs index 42cc9b3..eede2ef 100644 --- a/src/unicode_support.rs +++ b/src/unicode_support.rs @@ -1,2 +1,5 @@ mod fontconfig; mod freetype; + +pub use self::freetype::font_has_char; +pub use fontconfig::{get_matching_font_from_char, get_matching_font_from_str};