diff options
Diffstat (limited to 'font/src/ft/list_fonts.rs')
-rw-r--r-- | font/src/ft/list_fonts.rs | 396 |
1 files changed, 261 insertions, 135 deletions
diff --git a/font/src/ft/list_fonts.rs b/font/src/ft/list_fonts.rs index 0fbc7231..29912a8f 100644 --- a/font/src/ft/list_fonts.rs +++ b/font/src/ft/list_fonts.rs @@ -12,28 +12,31 @@ // See the License for the specific language governing permissions and // limitations under the License. // -use std::collections::HashMap; -use std::fmt; -use std::path::PathBuf; - -mod fc { +pub mod fc { use std::ptr; use std::ffi::{CStr, CString}; use std::str; use std::ops::Deref; + use std::path::PathBuf; - use ffi_util::ForeignTypeRef; + use ffi_util::{ForeignType, ForeignTypeRef}; use libc::{c_char, c_int}; use fontconfig::fontconfig as ffi; use self::ffi::{FcConfigGetCurrent, FcConfigGetFonts, FcSetSystem, FcSetApplication}; use self::ffi::{FcPatternGetString, FcPatternCreate, FcPatternAddString}; - use self::ffi::{FcPatternGetInteger}; + use self::ffi::{FcPatternGetInteger, FcPatternAddInteger}; use self::ffi::{FcObjectSetCreate, FcObjectSetAdd}; - use self::ffi::{FcResultMatch, FcFontSetList}; - use self::ffi::{FcChar8, FcConfig, FcPattern, FcFontSet, FcObjectSet}; + use self::ffi::{FcResultMatch, FcResultNoMatch, FcFontSetList}; + use self::ffi::{FcChar8, FcConfig, FcPattern, FcFontSet, FcObjectSet, FcCharSet}; use self::ffi::{FcFontSetDestroy, FcPatternDestroy, FcObjectSetDestroy, FcConfigDestroy}; + use self::ffi::{FcFontMatch, FcFontList, FcFontSort, FcConfigSubstitute, FcDefaultSubstitute}; + use self::ffi::{FcMatchFont, FcMatchPattern, FcMatchScan, FC_SLANT_ITALIC, FC_SLANT_ROMAN}; + use self::ffi::{FC_SLANT_OBLIQUE}; + use self::ffi::{FC_WEIGHT_THIN, FC_WEIGHT_EXTRALIGHT, FC_WEIGHT_LIGHT}; + use self::ffi::{FC_WEIGHT_BOOK, FC_WEIGHT_REGULAR, FC_WEIGHT_MEDIUM, FC_WEIGHT_SEMIBOLD}; + use self::ffi::{FC_WEIGHT_BOLD, FC_WEIGHT_EXTRABOLD, FC_WEIGHT_BLACK, FC_WEIGHT_EXTRABLACK}; /// Iterator over a font set pub struct FontSetIter<'a> { @@ -48,6 +51,7 @@ mod fc { ffi_type!(FontSet, FontSetRef, FcFontSet, FcFontSetDestroy); impl ObjectSet { + #[allow(dead_code)] pub fn new() -> ObjectSet { ObjectSet(unsafe { FcObjectSetCreate() @@ -55,6 +59,89 @@ mod fc { } } + /// Find the font closest matching the provided pattern. + #[allow(dead_code)] + pub fn font_match( + config: &ConfigRef, + pattern: &mut PatternRef, + ) -> Option<Pattern> { + pattern.config_subsitute(config, MatchKind::Pattern); + pattern.default_substitute(); + + unsafe { + // What is this result actually used for? Seems redundant with + // return type. + let mut result = FcResultNoMatch; + let ptr = FcFontMatch( + config.as_ptr(), + pattern.as_ptr(), + &mut result, + ); + + if ptr.is_null() { + None + } else { + Some(Pattern::from_ptr(ptr)) + } + } + } + + /// list fonts by closeness to the pattern + pub fn font_sort( + config: &ConfigRef, + pattern: &mut PatternRef, + ) -> Option<FontSet> { + pattern.config_subsitute(config, MatchKind::Pattern); + pattern.default_substitute(); + + unsafe { + // What is this result actually used for? Seems redundant with + // return type. + let mut result = FcResultNoMatch; + + let mut charsets: *mut FcCharSet = ptr::null_mut(); + + let ptr = FcFontSort( + config.as_ptr(), + pattern.as_ptr(), + 0, // false + &mut charsets, + &mut result, + ); + + if ptr.is_null() { + None + } else { + Some(FontSet::from_ptr(ptr)) + } + } + } + + /// List fonts matching pattern + #[allow(dead_code)] + pub fn font_list( + config: &ConfigRef, + pattern: &mut PatternRef, + objects: &ObjectSetRef, + ) -> Option<FontSet> { + pattern.config_subsitute(config, MatchKind::Pattern); + pattern.default_substitute(); + + unsafe { + let ptr = FcFontList( + config.as_ptr(), + pattern.as_ptr(), + objects.as_ptr(), + ); + + if ptr.is_null() { + None + } else { + Some(FontSet::from_ptr(ptr)) + } + } + } + impl ObjectSetRef { fn add(&mut self, property: &[u8]) { unsafe { @@ -79,13 +166,28 @@ mod fc { } macro_rules! pattern_add_string { - ($name:ident => $object:expr) => { - #[inline] - pub fn $name(&mut self, value: &str) -> bool { - unsafe { - self.add_string($object, value) + ($($name:ident => $object:expr),*) => { + $( + #[inline] + pub fn $name(&mut self, value: &str) -> bool { + unsafe { + self.add_string($object, value) + } } - } + )* + } + } + + macro_rules! pattern_add_int { + ($($name:ident => $object:expr),*) => { + $( + #[inline] + pub fn $name(&mut self, value: &str) -> bool { + unsafe { + self.add_string($object, value) + } + } + )* } } @@ -102,6 +204,14 @@ mod fc { Application = FcSetApplication as isize, } + /// When matching, how to match + #[derive(Debug, Copy, Clone)] + pub enum MatchKind { + Font = FcMatchFont as isize, + Pattern = FcMatchPattern as isize, + Scan = FcMatchScan as isize, + } + pub unsafe fn char8_to_string(fc_str: *mut FcChar8) -> String { str::from_utf8(CStr::from_ptr(fc_str as *const c_char).to_bytes()).unwrap().to_owned() } @@ -111,20 +221,24 @@ mod fc { $( pub fn $method(&self, id: isize) -> Option<String> { unsafe { - let mut format: *mut FcChar8 = ptr::null_mut(); + self.get_string($property, id) + } + } + )+ + }; + } - let result = FcPatternGetString( + macro_rules! pattern_add_integer { + ($($method:ident() => $property:expr),+) => { + $( + pub fn $method(&self, int: isize) -> bool { + unsafe { + FcPatternAddInteger( self.as_ptr(), $property.as_ptr() as *mut c_char, - id as c_int, - &mut format - ); - - if result == FcResultMatch { - Some(char8_to_string(format)) - } else { - None - } + int as c_int, + &mut index + ) == 1 } } )+ @@ -155,6 +269,28 @@ mod fc { }; } + #[derive(Debug, Copy, Clone)] + pub enum Slant { + Italic = FC_SLANT_ITALIC as isize, + Oblique = FC_SLANT_OBLIQUE as isize, + Roman = FC_SLANT_ROMAN as isize, + } + + #[derive(Debug, Copy, Clone)] + pub enum Weight { + Thin = FC_WEIGHT_THIN as isize, + Extralight = FC_WEIGHT_EXTRALIGHT as isize, + Light = FC_WEIGHT_LIGHT as isize, + Book = FC_WEIGHT_BOOK as isize, + Regular = FC_WEIGHT_REGULAR as isize, + Medium = FC_WEIGHT_MEDIUM as isize, + Semibold = FC_WEIGHT_SEMIBOLD as isize, + Bold = FC_WEIGHT_BOLD as isize, + Extrabold = FC_WEIGHT_EXTRABOLD as isize, + Black = FC_WEIGHT_BLACK as isize, + Extrablack = FC_WEIGHT_EXTRABLACK as isize, + } + impl PatternRef { /// Add a string value to the pattern /// @@ -175,20 +311,75 @@ mod fc { ) == 1 } + unsafe fn add_integer(&self, object: &[u8], int: isize) -> bool { + FcPatternAddInteger( + self.as_ptr(), + object.as_ptr() as *mut c_char, + int as c_int + ) == 1 + } + + unsafe fn get_string(&self, object: &[u8], index: isize) -> Option<String> { + let mut format: *mut FcChar8 = ptr::null_mut(); + + let result = FcPatternGetString( + self.as_ptr(), + object.as_ptr() as *mut c_char, + index as c_int, + &mut format + ); + + if result == FcResultMatch { + Some(char8_to_string(format)) + } else { + None + } + } + pattern_add_string! { - add_family => b"family\0" + add_family => b"family\0", + add_style => b"style\0" + } + + pub fn set_slant(&mut self, slant: Slant) -> bool { + unsafe { + self.add_integer(b"slant\0", slant as isize) + } + } + + pub fn set_weight(&mut self, weight: Weight) -> bool { + unsafe { + self.add_integer(b"weight\0", weight as isize) + } + } + + pub fn file(&self, index: isize) -> Option<PathBuf> { + unsafe { + self.get_string(b"file\0", index) + }.map(From::from) } pattern_get_string! { fontformat() => b"fontformat\0", family() => b"family\0", - file() => b"file\0", style() => b"style\0" } pattern_get_integer! { index() => b"index\0" } + + pub fn config_subsitute(&mut self, config: &ConfigRef, kind: MatchKind) { + unsafe { + FcConfigSubstitute(config.as_ptr(), self.as_ptr(), kind as u32); + } + } + + pub fn default_substitute(&mut self) { + unsafe { + FcDefaultSubstitute(self.as_ptr()); + } + } } impl<'a> IntoIterator for &'a FontSet { @@ -199,6 +390,8 @@ mod fc { (*self.as_ptr()).nfont as isize }; + println!("num fonts = {}", num_fonts); + FontSetIter { font_set: self.deref(), num_fonts: num_fonts as _, @@ -215,6 +408,8 @@ mod fc { (*self.as_ptr()).nfont as isize }; + println!("num fonts = {}", num_fonts); + FontSetIter { font_set: self, num_fonts: num_fonts as _, @@ -282,119 +477,50 @@ mod fc { } } -fn list_families() -> Vec<String> { - let mut families = Vec::new(); - - let config = fc::Config::get_current(); - let font_set = config.get_fonts(fc::SetName::System); - for font in font_set { - if let Some(format) = font.fontformat(0) { - if format == "TrueType" || format == "CFF" { - for id in 0.. { - match font.family(id) { - Some(family) => families.push(family), - None => break, - } - } - } - } - } - - families.sort(); - families.dedup(); - families -} - -#[derive(Debug)] -pub struct Variant { - style: String, - file: PathBuf, - index: isize, -} - -impl Variant { - #[inline] - pub fn path(&self) -> &::std::path::Path { - self.file.as_path() - } - - #[inline] - pub fn index(&self) -> isize { - self.index - } -} - -#[derive(Debug)] -pub struct Family { - name: String, - variants: HashMap<String, Variant>, -} +#[cfg(test)] +mod tests { + use super::fc; -impl fmt::Display for Family { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}: ", self.name)?; - for (k, _v) in &self.variants { - write!(f, "{}, ", k)?; + #[test] + fn font_match() { + let mut pattern = fc::Pattern::new(); + pattern.add_family("monospace"); + pattern.add_style("regular"); + + let config = fc::Config::get_current(); + let font = fc::font_match(config, &mut pattern).expect("match font monospace"); + + print!("family={:?}", font.family(0)); + for i in 0.. { + if let Some(style) = font.style(i) { + print!(", style={:?}, ", style); + } else { + break; + } } - - Ok(()) + println!(""); } -} - -impl Family { - #[inline] - pub fn variants(&self) -> &HashMap<String, Variant> { - &self.variants - } -} -#[allow(mutable_transmutes)] -pub fn get_family_info(family: String) -> Family { - let mut members = Vec::new(); - let config = fc::Config::get_current(); - let font_set = config.get_fonts(fc::SetName::System); - - let mut pattern = fc::Pattern::new(); - pattern.add_family(&family); - - let mut objects = fc::ObjectSet::new(); - objects.add_file(); - objects.add_index(); - objects.add_style(); - - let variants = fc::FontSet::list(&config, unsafe { ::std::mem::transmute(font_set) }, &pattern, &objects); - for variant in &variants { - if let Some(file) = variant.file(0) { - if let Some(style) = variant.style(0) { - if let Some(index) = variant.index(0) { - members.push(Variant { - style: style, - file: PathBuf::from(file), - index: index as isize, - }); + #[test] + fn font_sort() { + let mut pattern = fc::Pattern::new(); + pattern.add_family("monospace"); + pattern.set_slant(fc::Slant::Italic); + + let config = fc::Config::get_current(); + let fonts = fc::font_sort(config, &mut pattern) + .expect("sort font monospace"); + + for font in fonts.into_iter().take(10) { + print!("family={:?}", font.family(0)); + for i in 0.. { + if let Some(style) = font.style(i) { + print!(", style={:?}", style); + } else { + break; } } + println!(""); } } - - Family { - name: family, - variants: members.into_iter().map(|v| (v.style.clone(), v)).collect() - } -} - -pub fn get_font_families() -> HashMap<String, Family> { - list_families() - .into_iter() - .map(|family| (family.clone(), get_family_info(family))) - .collect() -} - -#[cfg(test)] -mod tests { - #[test] - fn get_font_families() { - let families = super::get_font_families(); - assert!(!families.is_empty()); - } } |