Impl unicode support via fontconfig and freetype
This commit is contained in:
parent
2bcc19d7de
commit
9cf94f7377
3 changed files with 156 additions and 10 deletions
158
src/display.rs
158
src/display.rs
|
@ -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()
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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};
|
||||||
|
|
Loading…
Reference in a new issue