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 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()

View 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();

View 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};