Impl fontconfig finding font with given char/str
Should work with non-ascii as well, provided that the system has the necessary fonts installed.
This commit is contained in:
parent
1d9342d8f5
commit
a57d287031
2 changed files with 338 additions and 2 deletions
3
build.rs
3
build.rs
|
@ -4,8 +4,7 @@ use std::path::PathBuf;
|
||||||
use bindgen;
|
use bindgen;
|
||||||
|
|
||||||
#[cfg(not(feature = "unicode_support"))]
|
#[cfg(not(feature = "unicode_support"))]
|
||||||
fn main() {
|
fn main() {}
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "unicode_support")]
|
#[cfg(feature = "unicode_support")]
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
|
use std::{path::PathBuf, str::FromStr};
|
||||||
|
|
||||||
mod ffi {
|
mod ffi {
|
||||||
|
use std::{ffi::CStr, os::raw::c_int};
|
||||||
|
|
||||||
mod bindgen {
|
mod bindgen {
|
||||||
#![allow(non_upper_case_globals)]
|
#![allow(non_upper_case_globals)]
|
||||||
#![allow(non_camel_case_types)]
|
#![allow(non_camel_case_types)]
|
||||||
|
@ -6,4 +10,337 @@ mod ffi {
|
||||||
|
|
||||||
include!(concat!(env!("OUT_DIR"), "/unicode_support_bindings.rs"));
|
include!(concat!(env!("OUT_DIR"), "/unicode_support_bindings.rs"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct FcConfigWr {
|
||||||
|
config: *mut bindgen::FcConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for FcConfigWr {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if !self.config.is_null() {
|
||||||
|
unsafe {
|
||||||
|
bindgen::FcConfigDestroy(self.config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FcConfigWr {
|
||||||
|
pub fn new() -> Result<Self, String> {
|
||||||
|
let config = unsafe { bindgen::FcInitLoadConfigAndFonts() };
|
||||||
|
if config.is_null() {
|
||||||
|
Err(String::from("Failed to create FcConfig"))
|
||||||
|
} else {
|
||||||
|
Ok(Self { config })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&mut self) -> *mut bindgen::FcConfig {
|
||||||
|
self.config
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply_pattern_to_config(&mut self, pattern: &mut FcPatternWr) -> bool {
|
||||||
|
unsafe {
|
||||||
|
bindgen::FcConfigSubstitute(
|
||||||
|
self.config,
|
||||||
|
pattern.get(),
|
||||||
|
bindgen::_FcMatchKind_FcMatchPattern,
|
||||||
|
) == bindgen::FcTrue as bindgen::FcBool
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn font_match(&mut self, pattern: &mut FcPatternWr) -> Result<FcPatternWr, String> {
|
||||||
|
unsafe {
|
||||||
|
let mut result: bindgen::FcResult = 0 as bindgen::FcResult;
|
||||||
|
let result_pattern = bindgen::FcFontMatch(
|
||||||
|
self.config,
|
||||||
|
pattern.get(),
|
||||||
|
&mut result as *mut bindgen::FcResult,
|
||||||
|
);
|
||||||
|
if result != bindgen::_FcResult_FcResultMatch {
|
||||||
|
if !result_pattern.is_null() {
|
||||||
|
bindgen::FcPatternDestroy(result_pattern);
|
||||||
|
return Err(String::from("Failed to FcFontMatch (FcResult is not FcResultMatch; result_pattern is not null)"));
|
||||||
|
} else {
|
||||||
|
return Err(format!(
|
||||||
|
"Failed to FcFontMatch (FcResult is not FcResultMatch; {:?})",
|
||||||
|
result
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else if result_pattern.is_null() {
|
||||||
|
return Err(String::from(
|
||||||
|
"Failed to FcFontMatch (result_pattern is null)",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(FcPatternWr {
|
||||||
|
pattern: result_pattern,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FcCharSetWr {
|
||||||
|
charset: *mut bindgen::FcCharSet,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for FcCharSetWr {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if !self.charset.is_null() {
|
||||||
|
unsafe {
|
||||||
|
bindgen::FcCharSetDestroy(self.charset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FcCharSetWr {
|
||||||
|
pub fn new_with_str(s: &str) -> Result<Self, String> {
|
||||||
|
let charset;
|
||||||
|
unsafe {
|
||||||
|
let charset_ptr = bindgen::FcCharSetCreate();
|
||||||
|
if charset_ptr.is_null() {
|
||||||
|
return Err(String::from("Failed to create FcCharSet with str"));
|
||||||
|
}
|
||||||
|
charset = FcCharSetWr {
|
||||||
|
charset: charset_ptr,
|
||||||
|
};
|
||||||
|
for c in s.chars() {
|
||||||
|
if bindgen::FcCharSetAddChar(charset.charset, c as u32)
|
||||||
|
== bindgen::FcFalse as bindgen::FcBool
|
||||||
|
{
|
||||||
|
return Err(String::from("Failed to add chars from str into FcCharSet"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(charset)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_with_char(c: char) -> Result<Self, String> {
|
||||||
|
let charset;
|
||||||
|
unsafe {
|
||||||
|
let charset_ptr = bindgen::FcCharSetCreate();
|
||||||
|
if charset_ptr.is_null() {
|
||||||
|
return Err(String::from("Failed to create FcCharSet with char"));
|
||||||
|
}
|
||||||
|
charset = FcCharSetWr {
|
||||||
|
charset: charset_ptr,
|
||||||
|
};
|
||||||
|
if bindgen::FcCharSetAddChar(charset.charset, c as u32)
|
||||||
|
== bindgen::FcFalse as bindgen::FcBool
|
||||||
|
{
|
||||||
|
return Err(String::from("Failed to add char to FcCharSet"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(charset)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&mut self) -> *mut bindgen::FcCharSet {
|
||||||
|
self.charset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FcPatternWr {
|
||||||
|
pattern: *mut bindgen::FcPattern,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for FcPatternWr {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if !self.pattern.is_null() {
|
||||||
|
unsafe {
|
||||||
|
bindgen::FcPatternDestroy(self.pattern);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FcPatternWr {
|
||||||
|
pub fn new_with_charset(c: &mut FcCharSetWr) -> Result<Self, String> {
|
||||||
|
let pattern;
|
||||||
|
unsafe {
|
||||||
|
let pattern_ptr = bindgen::FcPatternCreate();
|
||||||
|
if pattern_ptr.is_null() {
|
||||||
|
return Err(String::from("Failed to FcPatternCreate"));
|
||||||
|
}
|
||||||
|
pattern = Self {
|
||||||
|
pattern: pattern_ptr,
|
||||||
|
};
|
||||||
|
bindgen::FcDefaultSubstitute(pattern.pattern);
|
||||||
|
|
||||||
|
let value = bindgen::FcValue {
|
||||||
|
type_: bindgen::_FcType_FcTypeCharSet,
|
||||||
|
u: bindgen::_FcValue__bindgen_ty_1 { c: c.get() },
|
||||||
|
};
|
||||||
|
|
||||||
|
if bindgen::FcPatternAdd(
|
||||||
|
pattern.pattern,
|
||||||
|
bindgen::FC_CHARSET as *const _ as *const i8,
|
||||||
|
value,
|
||||||
|
bindgen::FcTrue as bindgen::FcBool,
|
||||||
|
) == bindgen::FcFalse as bindgen::FcBool
|
||||||
|
{
|
||||||
|
return Err(String::from("Failed to add FcCharSet to new Pattern"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&mut self) -> *mut bindgen::FcPattern {
|
||||||
|
self.pattern
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_count(&self) -> c_int {
|
||||||
|
unsafe { bindgen::FcPatternObjectCount(self.pattern) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn filter_to_filenames(&self) -> Result<Self, String> {
|
||||||
|
let pattern;
|
||||||
|
unsafe {
|
||||||
|
let mut file_object_set_filter = FcObjectSetWr::new_file_object_set()?;
|
||||||
|
let pattern_ptr =
|
||||||
|
bindgen::FcPatternFilter(self.pattern, file_object_set_filter.get());
|
||||||
|
if pattern_ptr.is_null() {
|
||||||
|
return Err(String::from("Failed to FcPatternFilter"));
|
||||||
|
}
|
||||||
|
pattern = Self {
|
||||||
|
pattern: pattern_ptr,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_filename_contents(&self) -> Result<Vec<String>, String> {
|
||||||
|
let mut vec: Vec<String> = Vec::new();
|
||||||
|
let count = self.get_count();
|
||||||
|
unsafe {
|
||||||
|
let mut value = bindgen::FcValue {
|
||||||
|
type_: 0,
|
||||||
|
u: bindgen::_FcValue__bindgen_ty_1 { i: 0 },
|
||||||
|
};
|
||||||
|
|
||||||
|
for i in 0..count {
|
||||||
|
if bindgen::FcPatternGet(
|
||||||
|
self.pattern,
|
||||||
|
bindgen::FC_FILE as *const _ as *const i8,
|
||||||
|
i,
|
||||||
|
&mut value as *mut bindgen::FcValue,
|
||||||
|
) == bindgen::_FcResult_FcResultMatch
|
||||||
|
{
|
||||||
|
if value.type_ == bindgen::_FcType_FcTypeString {
|
||||||
|
let cs = CStr::from_ptr(value.u.s as *const i8);
|
||||||
|
vec.push(
|
||||||
|
cs.to_str()
|
||||||
|
.map_err(|_| String::from("Failed to convert CStr to String"))?
|
||||||
|
.to_owned(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(vec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FcObjectSetWr {
|
||||||
|
object_set: *mut bindgen::FcObjectSet,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for FcObjectSetWr {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
if !self.object_set.is_null() {
|
||||||
|
bindgen::FcObjectSetDestroy(self.object_set);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FcObjectSetWr {
|
||||||
|
pub fn new_file_object_set() -> Result<Self, String> {
|
||||||
|
let object_set;
|
||||||
|
unsafe {
|
||||||
|
let object_set_ptr = bindgen::FcObjectSetCreate();
|
||||||
|
if object_set_ptr.is_null() {
|
||||||
|
return Err(String::from("Failed to FcObjectSetCreate"));
|
||||||
|
}
|
||||||
|
|
||||||
|
object_set = Self {
|
||||||
|
object_set: object_set_ptr,
|
||||||
|
};
|
||||||
|
|
||||||
|
if bindgen::FcObjectSetAdd(
|
||||||
|
object_set.object_set,
|
||||||
|
bindgen::FC_FILE as *const _ as *const i8,
|
||||||
|
) == bindgen::FcFalse as bindgen::FcBool
|
||||||
|
{
|
||||||
|
return Err(String::from(
|
||||||
|
"Failed to add \"FC_FILE\" with FcObjectSetAdd",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(object_set)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&mut self) -> *mut bindgen::FcObjectSet {
|
||||||
|
self.object_set
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_matching_font_from_str(s: &str) -> Result<PathBuf, String> {
|
||||||
|
let mut config = ffi::FcConfigWr::new()?;
|
||||||
|
let mut charset = ffi::FcCharSetWr::new_with_str(s)?;
|
||||||
|
let mut search_pattern = ffi::FcPatternWr::new_with_charset(&mut charset)?;
|
||||||
|
if !config.apply_pattern_to_config(&mut search_pattern) {
|
||||||
|
return Err(String::from("Failed to apply_pattern_to_config"));
|
||||||
|
}
|
||||||
|
let result_pattern = config.font_match(&mut search_pattern)?;
|
||||||
|
let filtered_pattern = result_pattern.filter_to_filenames()?;
|
||||||
|
let result_vec = filtered_pattern.get_filename_contents()?;
|
||||||
|
|
||||||
|
if result_vec.is_empty() {
|
||||||
|
Err(String::from(
|
||||||
|
"Empty result_vec for get_matching_font_from_str",
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
PathBuf::from_str(&result_vec[0]).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_matching_font_from_char(c: char) -> Result<PathBuf, String> {
|
||||||
|
let mut config = ffi::FcConfigWr::new()?;
|
||||||
|
let mut charset = ffi::FcCharSetWr::new_with_char(c)?;
|
||||||
|
let mut search_pattern = ffi::FcPatternWr::new_with_charset(&mut charset)?;
|
||||||
|
if !config.apply_pattern_to_config(&mut search_pattern) {
|
||||||
|
return Err(String::from("Failed to apply_pattern_to_config"));
|
||||||
|
}
|
||||||
|
let result_pattern = config.font_match(&mut search_pattern)?;
|
||||||
|
let filtered_pattern = result_pattern.filter_to_filenames()?;
|
||||||
|
let result_vec = filtered_pattern.get_filename_contents()?;
|
||||||
|
|
||||||
|
if result_vec.is_empty() {
|
||||||
|
Err(String::from(
|
||||||
|
"Empty result_vec for get_matching_font_from_char",
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
PathBuf::from_str(&result_vec[0]).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ascii_fetching() {
|
||||||
|
let fetched_path =
|
||||||
|
get_matching_font_from_char('a').expect("Should be able to find match for 'a'");
|
||||||
|
println!("{:?}", fetched_path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue