]> git.seodisparate.com - mpd_info_screen/commitdiff
Impl unicode support via fontconfig and freetype
authorStephen Seo <seo.disparate@gmail.com>
Mon, 1 Aug 2022 03:04:38 +0000 (12:04 +0900)
committerStephen Seo <seo.disparate@gmail.com>
Mon, 1 Aug 2022 03:04:48 +0000 (12:04 +0900)
src/display.rs
src/main.rs
src/unicode_support.rs

index 5f2f4e209c8b466de9b9aec326a8e5051295b95e..cde0067194a1bfbb6fcc3c51dd54f8bf6576ba19 100644 (file)
@@ -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<usize> {
+            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<Image>,
     album_art_draw_transform: Option<Transform>,
     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()
index a5387e15bed46b2ffc1c90362c84fb8936374c74..aa4ce5ad2d60d75c5967f797aed027baf1961449 100644 (file)
@@ -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();
index 42cc9b3e6be7f30880687b0c69536ed514e263bb..eede2effeccc0e9b0ac6985465465f750619f1f5 100644 (file)
@@ -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};