1#![deny(missing_docs)]
2
3use fontconfig_sys as sys;
66use fontconfig_sys::ffi_dispatch;
67
68#[cfg(feature = "dlopen")]
69use sys::statics::{LIB, LIB_RESULT};
70#[cfg(not(feature = "dlopen"))]
71use sys::*;
72
73use std::ffi::{self, c_char, c_int, CStr, CString};
74use std::marker::PhantomData;
75use std::path::PathBuf;
76use std::str::{self, FromStr};
77use std::{fmt, ptr};
78
79pub use sys::constants::*;
80use sys::{FcBool, FcCharSet, FcPattern, FcResult};
81
82#[allow(non_upper_case_globals)]
83const FcTrue: FcBool = 1;
84#[allow(non_upper_case_globals, dead_code)]
85const FcFalse: FcBool = 0;
86
87pub struct Fontconfig {
89 _initialised: (),
90}
91
92#[derive(Debug)]
94pub enum FontconfigError {
95 Failed,
99 NulError,
101 Utf8Error,
103 NoMatch,
106 TypeMismatch,
108 NoId,
110 OutOfMemory,
112}
113
114#[derive(Debug)]
118pub struct UnknownFontFormat(pub String);
119
120#[derive(Eq, PartialEq, Debug)]
122#[allow(missing_docs)]
123pub enum FontFormat {
124 TrueType,
125 Type1,
126 BDF,
127 PCF,
128 Type42,
129 CIDType1,
130 CFF,
131 PFR,
132 WindowsFNT,
133}
134
135#[derive(Eq, PartialEq, Debug)]
138pub enum UnicodeCoverage {
139 Trim,
141 NoTrim,
143}
144
145trait ToResult {
146 fn to_result(self) -> Result<(), FontconfigError>;
147}
148
149impl Fontconfig {
150 #[doc(alias = "FcInit")]
162 pub fn new() -> Option<Self> {
163 #[cfg(feature = "dlopen")]
164 if LIB_RESULT.is_err() {
165 return None;
166 }
167 if unsafe { ffi_dispatch!(LIB, FcInit,) == FcTrue } {
168 Some(Fontconfig { _initialised: () })
169 } else {
170 None
171 }
172 }
173
174 pub fn find(&self, family: &str, style: Option<&str>) -> Result<Font, FontconfigError> {
178 Font::find(self, family, style)
179 }
180}
181
182pub struct Font {
204 pub name: String,
206 pub path: PathBuf,
208 pub index: Option<i32>,
210}
211
212impl Font {
213 fn find(fc: &Fontconfig, family: &str, style: Option<&str>) -> Result<Font, FontconfigError> {
214 let mut pat = Pattern::new(fc)?;
215 let family = CString::new(family)?;
216 pat.add_string(FC_FAMILY, &family)?;
217
218 if let Some(style) = style {
219 let style = CString::new(style)?;
220 pat.add_string(FC_STYLE, &style)?;
221 }
222
223 let font_match = pat.font_match()?;
224 let name = font_match.name()?;
225 let filename = font_match.filename()?;
226
227 Ok(Font {
228 name: name.to_owned(),
229 path: PathBuf::from(filename),
230 index: font_match.face_index().ok(),
231 })
232 }
233
234 #[cfg(test)]
235 fn print_debug(&self) {
236 println!(
237 "Name: {}\nPath: {}\nIndex: {:?}",
238 self.name,
239 self.path.display(),
240 self.index
241 );
242 }
243}
244
245#[repr(C)]
253#[doc(alias = "FcPattern")]
254pub struct Pattern<'fc> {
255 pat: *mut FcPattern,
257 fc: &'fc Fontconfig,
258}
259
260impl<'fc> Pattern<'fc> {
261 #[doc(alias = "FcPatternCreate")]
265 pub fn new(fc: &Fontconfig) -> Result<Pattern<'_>, FontconfigError> {
266 let pat = unsafe { ffi_dispatch!(LIB, FcPatternCreate,) };
267 is_non_null(pat)
268 .then_some(Pattern { pat, fc })
269 .ok_or(FontconfigError::Failed)
270 }
271
272 pub unsafe fn from_pattern(fc: &Fontconfig, pat: *mut FcPattern) -> Pattern<'_> {
278 assert!(is_non_null(pat));
279 ffi_dispatch!(LIB, FcPatternReference, pat);
280
281 Pattern { pat, fc }
282 }
283
284 #[doc(alias = "FcPatternAddString")]
290 pub fn add_string(&mut self, name: &CStr, val: &CStr) -> Result<(), FontconfigError> {
291 unsafe {
292 ffi_dispatch!(
293 LIB,
294 FcPatternAddString,
295 self.pat,
296 name.as_ptr(),
297 val.as_ptr() as *const u8
298 )
299 .to_result()
300 }
301 }
302
303 #[doc(alias = "FcPatternAddInteger")]
309 pub fn add_integer(&mut self, name: &CStr, val: c_int) -> Result<(), FontconfigError> {
310 unsafe { ffi_dispatch!(LIB, FcPatternAddInteger, self.pat, name.as_ptr(), val).to_result() }
311 }
312
313 #[doc(alias = "FcPatternAddCharSet")]
315 pub fn add_charset(&mut self, val: CharSet) -> Result<(), FontconfigError> {
316 unsafe {
317 ffi_dispatch!(
318 LIB,
319 FcPatternAddCharSet,
320 self.pat,
321 FC_CHARSET.as_ptr(),
322 val.char_set
323 )
324 .to_result()
325 }
326 }
327
328 #[doc(alias = "FcPatternGetString")]
330 pub fn get_string<'a>(&'a self, name: &'a CStr) -> Result<&'a str, FontconfigError> {
331 unsafe {
332 let mut ret: *mut sys::FcChar8 = ptr::null_mut();
333 ffi_dispatch!(
334 LIB,
335 FcPatternGetString,
336 self.pat,
337 name.as_ptr(),
338 0,
339 &mut ret as *mut _
340 )
341 .to_result()?;
342 let cstr = CStr::from_ptr(ret as *const c_char);
343 cstr.to_str().map_err(FontconfigError::from)
344 }
345 }
346
347 #[doc(alias = "FcPatternGetInteger")]
349 pub fn get_int(&self, name: &CStr) -> Result<i32, FontconfigError> {
350 unsafe {
351 let mut ret: i32 = 0;
352 ffi_dispatch!(
353 LIB,
354 FcPatternGetInteger,
355 self.pat,
356 name.as_ptr(),
357 0,
358 &mut ret as *mut i32
359 )
360 .to_result()?;
361 Ok(ret)
362 }
363 }
364
365 #[doc(alias = "FcPatternPrint")]
367 pub fn print(&self) {
368 unsafe {
369 ffi_dispatch!(LIB, FcPatternPrint, &*self.pat);
370 }
371 }
372
373 #[doc(alias = "FcDefaultSubstitute")]
385 pub fn default_substitute(&mut self) {
386 unsafe {
387 ffi_dispatch!(LIB, FcDefaultSubstitute, self.pat);
388 }
389 }
390
391 #[doc(alias = "FcConfigSubstitute")]
398 pub fn config_substitute(&mut self) -> Result<(), FontconfigError> {
399 unsafe {
400 ffi_dispatch!(
401 LIB,
402 FcConfigSubstitute,
403 ptr::null_mut(),
404 self.pat,
405 sys::FcMatchPattern
406 )
407 .to_result()
408 }
409 }
410
411 #[doc(alias = "FcFontMatch")]
416 pub fn font_match(&mut self) -> Result<Pattern<'_>, FontconfigError> {
417 self.config_substitute()?;
418 self.default_substitute();
419
420 unsafe {
421 let mut res = sys::FcResultNoMatch;
422 let pattern_ptr = ffi_dispatch!(LIB, FcFontMatch, ptr::null_mut(), self.pat, &mut res);
423 is_non_null(pattern_ptr)
424 .then(|| Pattern::from_pattern(self.fc, pattern_ptr))
425 .ok_or(FontconfigError::Failed)
426 }
427 }
428
429 #[doc(alias = "FcFontSort")]
438 pub fn sort_fonts(&mut self, trim: UnicodeCoverage) -> Result<FontSet<'fc>, FontconfigError> {
439 self.config_substitute()?;
442 self.default_substitute();
443
444 let mut res = sys::FcResultNoMatch;
446 let unicode_coverage = ptr::null_mut();
447 let config = ptr::null_mut();
448 unsafe {
449 let raw_set = ffi_dispatch!(
450 LIB,
451 FcFontSort,
452 config,
453 self.pat,
454 trim as FcBool,
455 unicode_coverage,
456 &mut res
457 );
458 Ok(FontSet::from_raw(self.fc, raw_set))
459 }
460 }
461
462 pub fn name(&self) -> Result<&str, FontconfigError> {
464 self.get_string(FC_FULLNAME)
465 }
466
467 pub fn filename(&self) -> Result<&str, FontconfigError> {
469 self.get_string(FC_FILE)
470 }
471
472 pub fn face_index(&self) -> Result<i32, FontconfigError> {
474 self.get_int(FC_INDEX)
475 }
476
477 pub fn slant(&self) -> Result<i32, FontconfigError> {
479 self.get_int(FC_SLANT)
480 }
481
482 pub fn weight(&self) -> Result<i32, FontconfigError> {
484 self.get_int(FC_WEIGHT)
485 }
486
487 pub fn width(&self) -> Result<i32, FontconfigError> {
489 self.get_int(FC_WIDTH)
490 }
491
492 pub fn format(&self) -> Result<FontFormat, UnknownFontFormat> {
497 self.get_string(FC_FONTFORMAT)
498 .map_err(|_| UnknownFontFormat(String::new()))
499 .and_then(|format| format.parse())
500 }
501
502 #[doc(alias = "FcPatternGetLangSet")]
504 pub fn lang_set(&self) -> Result<StrList<'_>, FontconfigError> {
505 unsafe {
506 let mut lang_set: *mut sys::FcLangSet = ptr::null_mut();
507 ffi_dispatch!(
508 LIB,
509 FcPatternGetLangSet,
510 self.pat,
511 FC_LANG.as_ptr(),
512 0,
513 &mut lang_set as *mut _
514 )
515 .to_result()?;
516 let ss: *mut sys::FcStrSet = ffi_dispatch!(LIB, FcLangSetGetLangs, lang_set);
517 if ss.is_null() {
518 return Err(FontconfigError::Failed);
519 }
520 let lang_strs: *mut sys::FcStrList = ffi_dispatch!(LIB, FcStrListCreate, ss);
521 if lang_strs.is_null() {
522 return Err(FontconfigError::Failed);
523 }
524 Ok(StrList::from_raw(self.fc, lang_strs))
525 }
526 }
527
528 pub fn as_ptr(&self) -> *const FcPattern {
530 self.pat
531 }
532
533 pub fn as_mut_ptr(&mut self) -> *mut FcPattern {
535 self.pat
536 }
537}
538
539impl<'fc> std::fmt::Debug for Pattern<'fc> {
540 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
541 let fcstr = unsafe { ffi_dispatch!(LIB, FcNameUnparse, self.pat) };
542 let fcstr = unsafe { CStr::from_ptr(fcstr as *const c_char) };
543 let result = write!(f, "{:?}", fcstr);
544 unsafe { ffi_dispatch!(LIB, FcStrFree, fcstr.as_ptr() as *mut u8) };
545 result
546 }
547}
548
549impl<'fc> Clone for Pattern<'fc> {
550 #[doc(alias = "FcPatternDuplicate")]
556 fn clone(&self) -> Self {
557 let clone = unsafe { ffi_dispatch!(LIB, FcPatternDuplicate, self.pat) };
558 if clone.is_null() {
559 panic!("Unable to clone. FcPatternDuplicate returned NULL")
560 }
561
562 Pattern {
563 pat: clone,
564 fc: self.fc,
565 }
566 }
567}
568
569impl<'fc> Drop for Pattern<'fc> {
570 fn drop(&mut self) {
571 unsafe {
572 ffi_dispatch!(LIB, FcPatternDestroy, self.pat);
573 }
574 }
575}
576
577#[doc(alias = "FcStrList")]
599pub struct StrList<'a> {
600 list: *mut sys::FcStrList,
601 _life: PhantomData<&'a sys::FcStrList>,
602}
603
604impl<'a> StrList<'a> {
605 unsafe fn from_raw(_: &Fontconfig, raw_list: *mut sys::FcStrSet) -> Self {
611 Self {
612 list: raw_list,
613 _life: PhantomData,
614 }
615 }
616}
617
618impl<'a> Drop for StrList<'a> {
619 fn drop(&mut self) {
620 unsafe { ffi_dispatch!(LIB, FcStrListDone, self.list) };
621 }
622}
623
624impl<'a> Iterator for StrList<'a> {
625 type Item = &'a str;
626
627 fn next(&mut self) -> Option<&'a str> {
628 let lang_str: *mut sys::FcChar8 = unsafe { ffi_dispatch!(LIB, FcStrListNext, self.list) };
629 if lang_str.is_null() {
630 None
631 } else {
632 match unsafe { CStr::from_ptr(lang_str as *const c_char) }.to_str() {
633 Ok(s) => Some(s),
634 _ => self.next(),
635 }
636 }
637 }
638}
639
640#[doc(alias = "FcFontSet")]
644pub struct FontSet<'fc> {
645 fcset: *mut sys::FcFontSet,
646 fc: &'fc Fontconfig,
647}
648
649impl<'fc> FontSet<'fc> {
650 pub fn new(fc: &Fontconfig) -> Result<FontSet<'_>, FontconfigError> {
652 let fcset = unsafe { ffi_dispatch!(LIB, FcFontSetCreate,) };
653 is_non_null(fcset)
654 .then_some(FontSet { fcset, fc })
655 .ok_or(FontconfigError::Failed)
656 }
657
658 pub unsafe fn from_raw(fc: &Fontconfig, raw_set: *mut sys::FcFontSet) -> FontSet<'_> {
664 FontSet { fcset: raw_set, fc }
665 }
666
667 #[doc(alias = "FcFontSetAdd")]
669 pub fn add_pattern(&mut self, pat: Pattern) -> Result<(), FontconfigError> {
670 unsafe { ffi_dispatch!(LIB, FcFontSetAdd, self.fcset, pat.pat).to_result() }
671 }
672
673 #[doc(alias = "FcFontSetPrint")]
675 pub fn print(&self) {
676 unsafe { ffi_dispatch!(LIB, FcFontSetPrint, self.fcset) };
677 }
678
679 pub fn iter(&self) -> impl Iterator<Item = Pattern<'_>> {
681 let patterns = unsafe {
682 let fontset = self.fcset;
683 if (*fontset).nfont > 0 {
686 std::slice::from_raw_parts((*fontset).fonts, (*fontset).nfont as usize)
687 } else {
688 &[]
689 }
690 };
691 patterns
692 .iter()
693 .map(move |&pat| unsafe { Pattern::from_pattern(self.fc, pat) })
694 }
695}
696
697impl<'fc> Drop for FontSet<'fc> {
698 fn drop(&mut self) {
699 unsafe { ffi_dispatch!(LIB, FcFontSetDestroy, self.fcset) }
700 }
701}
702
703#[doc(alias = "FcFontList")]
715pub fn list_fonts<'fc>(
716 pattern: &Pattern<'fc>,
717 objects: Option<&ObjectSet>,
718) -> Result<FontSet<'fc>, FontconfigError> {
719 let os = objects.map(|o| o.fcset).unwrap_or(ptr::null_mut());
720 unsafe {
721 let raw_set = ffi_dispatch!(LIB, FcFontList, ptr::null_mut(), pattern.pat, os);
722 is_non_null(raw_set)
723 .then(|| FontSet::from_raw(pattern.fc, raw_set))
724 .ok_or(FontconfigError::Failed)
725 }
726}
727
728#[doc(alias = "FcObjectSet")]
730pub struct ObjectSet {
731 fcset: *mut sys::FcObjectSet,
732}
733
734impl ObjectSet {
735 #[doc(alias = "FcObjectSetCreate")]
737 pub fn new(_: &Fontconfig) -> Result<ObjectSet, FontconfigError> {
738 let fcset = unsafe { ffi_dispatch!(LIB, FcObjectSetCreate,) };
739 is_non_null(fcset)
740 .then(|| ObjectSet { fcset })
741 .ok_or(FontconfigError::Failed)
742 }
743
744 pub unsafe fn from_raw(_: &Fontconfig, raw_set: *mut sys::FcObjectSet) -> ObjectSet {
750 assert!(!raw_set.is_null());
751 ObjectSet { fcset: raw_set }
752 }
753
754 #[doc(alias = "FcObjectSetAdd")]
756 pub fn add(&mut self, name: &CStr) -> Result<(), FontconfigError> {
757 unsafe { ffi_dispatch!(LIB, FcObjectSetAdd, self.fcset, name.as_ptr()).to_result() }
758 }
759}
760
761impl Drop for ObjectSet {
762 fn drop(&mut self) {
763 unsafe { ffi_dispatch!(LIB, FcObjectSetDestroy, self.fcset) }
764 }
765}
766
767impl FromStr for FontFormat {
768 type Err = UnknownFontFormat;
769
770 fn from_str(s: &str) -> Result<Self, Self::Err> {
771 match s {
772 "TrueType" => Ok(FontFormat::TrueType),
773 "Type 1" => Ok(FontFormat::Type1),
774 "BDF" => Ok(FontFormat::BDF),
775 "PCF" => Ok(FontFormat::PCF),
776 "Type 42" => Ok(FontFormat::Type42),
777 "CID Type 1" => Ok(FontFormat::CIDType1),
778 "CFF" => Ok(FontFormat::CFF),
779 "PFR" => Ok(FontFormat::PFR),
780 "Windows FNT" => Ok(FontFormat::WindowsFNT),
781 _ => Err(UnknownFontFormat(s.to_string())),
782 }
783 }
784}
785
786impl fmt::Display for FontFormat {
787 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
788 match self {
789 FontFormat::TrueType => write!(f, "TrueType"),
790 FontFormat::Type1 => write!(f, "Type 1"),
791 FontFormat::BDF => write!(f, "BDF"),
792 FontFormat::PCF => write!(f, "PCF"),
793 FontFormat::Type42 => write!(f, "Type 42"),
794 FontFormat::CIDType1 => write!(f, "CID Type 1"),
795 FontFormat::CFF => write!(f, "CFF"),
796 FontFormat::PFR => write!(f, "PFR"),
797 FontFormat::WindowsFNT => write!(f, "Windows FNT"),
798 }
799 }
800}
801
802#[repr(C)]
804pub struct CharSet {
805 char_set: *mut FcCharSet,
806}
807
808impl<'fc> CharSet {
809 #[doc(alias = "FcCharSetCreate")]
811 pub fn new(_: &'fc Fontconfig) -> Result<Self, FontconfigError> {
812 let char_set = unsafe { ffi_dispatch!(LIB, FcCharSetCreate,) };
813 is_non_null(char_set)
814 .then_some(CharSet { char_set })
815 .ok_or(FontconfigError::Failed)
816 }
817
818 #[doc(alias = "FcCharSetAddChar")]
820 pub fn add_char(&mut self, c: char) -> Result<(), FontconfigError> {
821 unsafe { ffi_dispatch!(LIB, FcCharSetAddChar, self.char_set, c as u32).to_result() }
822 }
823}
824
825impl Drop for CharSet {
826 fn drop(&mut self) {
827 unsafe {
828 ffi_dispatch!(LIB, FcCharSetDestroy, self.char_set);
829 }
830 }
831}
832
833impl fmt::Display for FontconfigError {
834 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
835 match self {
836 FontconfigError::Failed => f.write_str("operation failed"),
837 FontconfigError::NulError => f.write_str("string contained NUL byte"),
838 FontconfigError::Utf8Error => f.write_str("string contained invalid UTF-8"),
839 FontconfigError::NoMatch => f.write_str("no matches"),
840 FontconfigError::TypeMismatch => f.write_str("type mismatch"),
841 FontconfigError::NoId => f.write_str("no matching id"),
842 FontconfigError::OutOfMemory => f.write_str("out of memory"),
843 }
844 }
845}
846
847impl std::error::Error for FontconfigError {}
848
849impl From<str::Utf8Error> for FontconfigError {
850 fn from(_err: str::Utf8Error) -> Self {
851 FontconfigError::Utf8Error
852 }
853}
854
855impl From<ffi::NulError> for FontconfigError {
856 fn from(_err: ffi::NulError) -> Self {
857 FontconfigError::NulError
858 }
859}
860
861impl ToResult for FcBool {
862 fn to_result(self) -> Result<(), FontconfigError> {
863 if self == FcTrue {
864 Ok(())
865 } else {
866 Err(FontconfigError::Failed)
867 }
868 }
869}
870
871impl ToResult for FcResult {
872 fn to_result(self) -> Result<(), FontconfigError> {
873 match self {
874 sys::FcResultMatch => Ok(()),
875 sys::FcResultNoMatch => Err(FontconfigError::NoMatch),
876 sys::FcResultTypeMismatch => Err(FontconfigError::TypeMismatch),
877 sys::FcResultNoId => Err(FontconfigError::NoId),
878 sys::FcResultOutOfMemory => Err(FontconfigError::OutOfMemory),
879 _ => Err(FontconfigError::Failed),
880 }
881 }
882}
883
884fn is_non_null<T>(ptr: *mut T) -> bool {
885 !ptr.is_null()
886}
887
888#[cfg(test)]
889mod tests {
890 use super::*;
891
892 use std::sync::Mutex;
893
894 static FC: Mutex<Option<Fontconfig>> = Mutex::new(None);
897
898 #[test]
899 fn test_find_font() -> Result<(), FontconfigError> {
900 let mut lock = FC.lock().unwrap();
901 let fc = lock.get_or_insert_with(|| Fontconfig::new().expect("FcInit"));
902 fc.find("dejavu sans", None)?.print_debug();
903 fc.find("dejavu sans", Some("oblique"))?.print_debug();
904 Ok(())
905 }
906
907 #[test]
908 fn test_iter_and_print() -> Result<(), FontconfigError> {
909 let mut lock = FC.lock().unwrap();
910 let fc = lock.get_or_insert_with(|| Fontconfig::new().expect("FcInit"));
911
912 let fontset = list_fonts(&Pattern::new(&fc)?, None)?;
913 for pattern in fontset.iter() {
914 println!("{:?}", pattern.name());
915 }
916
917 assert!(fontset.iter().count() > 0);
919 Ok(())
920 }
921
922 #[test]
923 fn test_empty_font_set() -> Result<(), FontconfigError> {
924 let mut lock = FC.lock().unwrap();
925 let fc = lock.get_or_insert_with(|| Fontconfig::new().expect("FcInit"));
926
927 let mut pat = Pattern::new(&fc)?;
928 let family = CString::new("xxx yyy zzz does not exist")?;
929 pat.add_string(FC_FAMILY, &family)?;
930
931 let fontset = list_fonts(&pat, None)?;
932 assert_eq!(fontset.iter().count(), 0);
935 Ok(())
936 }
937
938 #[test]
939 fn iter_lang_set() -> Result<(), FontconfigError> {
940 let mut lock = FC.lock().unwrap();
941 let fc = lock.get_or_insert_with(|| Fontconfig::new().expect("FcInit"));
942
943 let mut pat = Pattern::new(&fc)?;
944 let family = CString::new("dejavu sans")?;
945 pat.add_string(FC_FAMILY, &family)?;
946 let pattern = pat.font_match()?;
947 for lang in pattern.lang_set()? {
948 println!("{:?}", lang);
949 }
950
951 assert!(pattern.lang_set()?.find(|&lang| lang == "za").is_some());
953
954 let langs = pattern.lang_set()?.collect::<Vec<_>>();
956 assert!(langs.iter().find(|&&l| l == "ie").is_some());
957 Ok(())
958 }
959
960 #[test]
961 fn test_sort_fonts() -> Result<(), FontconfigError> {
962 let mut lock = FC.lock().unwrap();
963 let fc = lock.get_or_insert_with(|| Fontconfig::new().expect("FcInit"));
964
965 let mut pat = Pattern::new(&fc)?;
966 let family = CString::new("dejavu sans")?;
967 pat.add_string(FC_FAMILY, &family)?;
968
969 let style = CString::new("oblique")?;
970 pat.add_string(FC_STYLE, &style)?;
971
972 let font_set = pat.sort_fonts(UnicodeCoverage::NoTrim)?;
973
974 for pattern in font_set.iter() {
975 println!("{:?}", pattern.name());
976 }
977
978 assert!(font_set.iter().count() > 0);
980 Ok(())
981 }
982
983 #[test]
984 fn finds_font_containing_charset() -> Result<(), FontconfigError> {
985 let mut lock = FC.lock().unwrap();
986 let fc = lock.get_or_insert_with(|| Fontconfig::new().expect("FcInit"));
987
988 let mut pat = Pattern::new(&fc)?;
989 let mut char_set = CharSet::new(&fc)?;
990 char_set.add_char('a')?;
991 pat.add_charset(char_set)?;
992 let font_set = list_fonts(&pat, None)?;
993
994 assert!(font_set.iter().count() > 0);
996 Ok(())
997 }
998
999 #[test]
1000 fn does_not_find_missing_charset() -> Result<(), FontconfigError> {
1001 let mut lock = FC.lock().unwrap();
1002 let fc = lock.get_or_insert_with(|| Fontconfig::new().expect("FcInit"));
1003
1004 let mut pat = Pattern::new(&fc)?;
1005 let mut char_set = CharSet::new(&fc)?;
1006 char_set.add_char('北')?;
1008 let family = CString::new("dejavu sans")?;
1009 pat.add_string(FC_FAMILY, &family)?;
1010 pat.add_charset(char_set)?;
1011 let font_set = list_fonts(&pat, None)?;
1012
1013 assert_eq!(font_set.iter().count(), 0);
1015 Ok(())
1016 }
1017}