Impl unicode support via fontconfig and freetype

This commit is contained in:
Stephen Seo 2022-08-01 12:04:38 +09:00
parent 2bcc19d7de
commit 9cf94f7377
3 changed files with 156 additions and 10 deletions

View file

@ -9,6 +9,7 @@ use ggez::graphics::{
use ggez::{timer, Context, GameError, GameResult}; use ggez::{timer, Context, GameError, GameResult};
use image::io::Reader as ImageReader; use image::io::Reader as ImageReader;
use std::io::Cursor; use std::io::Cursor;
use std::path::PathBuf;
use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicBool;
use std::sync::{atomic::Ordering, Arc, RwLockReadGuard}; use std::sync::{atomic::Ordering, Arc, RwLockReadGuard};
use std::thread; use std::thread;
@ -50,14 +51,122 @@ fn seconds_to_time(seconds: f64) -> String {
} }
#[cfg(not(feature = "unicode_support"))] #[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)) Text::new(TextFragment::from(string))
} }
#[cfg(feature = "unicode_support")] #[cfg(feature = "unicode_support")]
fn string_to_text(string: String) -> Text { fn string_to_text(
// TODO impl string: String,
Text::new(TextFragment::from(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 { pub struct MPDDisplay {
@ -74,10 +183,13 @@ pub struct MPDDisplay {
album_art: Option<Image>, album_art: Option<Image>,
album_art_draw_transform: Option<Transform>, album_art_draw_transform: Option<Transform>,
filename_text: Text, filename_text: Text,
filename_string_cache: String,
filename_transform: Transform, filename_transform: Transform,
artist_text: Text, artist_text: Text,
artist_string_cache: String,
artist_transform: Transform, artist_transform: Transform,
title_text: Text, title_text: Text,
title_string_cache: String,
title_transform: Transform, title_transform: Transform,
timer_text: Text, timer_text: Text,
timer_transform: Transform, timer_transform: Transform,
@ -89,6 +201,7 @@ pub struct MPDDisplay {
hide_text: bool, hide_text: bool,
tried_album_art_in_dir: bool, tried_album_art_in_dir: bool,
mpd_play_state: MPDPlayState, mpd_play_state: MPDPlayState,
loaded_fonts: Vec<(PathBuf, Font)>,
} }
impl MPDDisplay { impl MPDDisplay {
@ -122,6 +235,10 @@ impl MPDDisplay {
hide_text: false, hide_text: false,
tried_album_art_in_dir: false, tried_album_art_in_dir: false,
mpd_play_state: MPDPlayState::Playing, 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 { } else {
self.mpd_play_state = MPDPlayState::Playing; self.mpd_play_state = MPDPlayState::Playing;
if !shared.title.is_empty() { 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 { } else {
self.dirty_flag self.dirty_flag
.as_ref() .as_ref()
@ -617,7 +741,14 @@ impl EventHandler for MPDDisplay {
.store(true, Ordering::Relaxed); .store(true, Ordering::Relaxed);
} }
if !shared.artist.is_empty() { 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 { } else {
self.dirty_flag self.dirty_flag
.as_ref() .as_ref()
@ -625,11 +756,18 @@ impl EventHandler for MPDDisplay {
.store(true, Ordering::Relaxed); .store(true, Ordering::Relaxed);
} }
if !shared.filename.is_empty() { if !shared.filename.is_empty() {
if self.filename_text.contents() != shared.filename { if shared.filename != self.filename_string_cache {
self.album_art = None; self.filename_string_cache = shared.filename.clone();
self.tried_album_art_in_dir = false; 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 { } else {
self.dirty_flag self.dirty_flag
.as_ref() .as_ref()

View file

@ -7,9 +7,11 @@ mod unicode_support;
use ggez::conf::{WindowMode, WindowSetup}; use ggez::conf::{WindowMode, WindowSetup};
use ggez::event::winit_event::{ElementState, KeyboardInput, ModifiersState}; use ggez::event::winit_event::{ElementState, KeyboardInput, ModifiersState};
use ggez::event::{self, ControlFlow, EventHandler}; use ggez::event::{self, ControlFlow, EventHandler};
use ggez::filesystem::mount;
use ggez::graphics::{self, Rect}; use ggez::graphics::{self, Rect};
use ggez::{ContextBuilder, GameError}; use ggez::{ContextBuilder, GameError};
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
use std::path::PathBuf;
use std::thread; use std::thread;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use structopt::StructOpt; use structopt::StructOpt;
@ -63,6 +65,9 @@ fn main() -> Result<(), String> {
.build() .build()
.expect("Failed to create ggez context"); .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 display = display::MPDDisplay::new(&mut ctx, opt.clone());
let mut modifiers_state: ModifiersState = ModifiersState::default(); let mut modifiers_state: ModifiersState = ModifiersState::default();

View file

@ -1,2 +1,5 @@
mod fontconfig; mod fontconfig;
mod freetype; mod freetype;
pub use self::freetype::font_has_char;
pub use fontconfig::{get_matching_font_from_char, get_matching_font_from_str};