1#![deny(missing_docs)]
2
3pub use fontconfig_sys as sys;
48use sys::ffi_dispatch;
49
50#[cfg(feature = "dlopen")]
51use sys::statics::{LIB, LIB_RESULT};
52#[cfg(not(feature = "dlopen"))]
53use sys::*;
54
55use std::path::PathBuf;
56use std::ptr::{self, NonNull};
57use std::str::FromStr;
58use std::sync::{Arc, Mutex};
59
60pub use sys::constants::*;
61use sys::FcBool;
62
63pub mod blanks;
64pub mod charset;
65pub mod fontset;
66pub mod langset;
67pub mod matrix;
68pub mod objectset;
69pub mod pattern;
70pub mod strings;
71pub mod stringset;
72
73#[allow(deprecated)]
74pub use blanks::Blanks;
75pub use charset::{CharSet, OwnedCharSet};
76pub use fontset::FontSet;
77pub use langset::{LangSet, LangSetCmp};
78pub use matrix::Matrix;
79pub use objectset::ObjectSet;
80pub use pattern::{
81 properties, properties::Property, properties::PropertyType, OwnedPattern, Pattern,
82};
83pub use strings::FcStr;
84pub use stringset::StringSet;
85
86#[allow(non_upper_case_globals)]
87const FcTrue: FcBool = 1;
88#[allow(non_upper_case_globals, dead_code)]
89const FcFalse: FcBool = 0;
90
91type Result<T> = std::result::Result<T, Error>;
92
93static INITIALIZED: once_cell::sync::Lazy<Arc<Mutex<usize>>> =
94 once_cell::sync::Lazy::new(|| Arc::new(Mutex::new(0)));
95
96#[derive(Debug, thiserror::Error)]
98pub enum Error {
99 #[error("Unknown format {0}")]
103 UnknownFontFormat(String),
104 #[error("malloc failed")]
106 OutOfMemory,
107 #[error("Object exists, but has fewer values than specified")]
109 NoId,
110 #[error("Object exists, but the type doesn't match")]
112 TypeMismatch,
113 #[error("Object doesn't exist at all")]
115 NoMatch,
116}
117
118trait ToResult {
119 fn ok(&self) -> Result<()>;
120
121 fn opt(&self) -> Option<()> {
122 self.ok().ok()
123 }
124}
125
126impl ToResult for sys::FcResult {
127 fn ok(&self) -> Result<()> {
128 match *self {
129 sys::FcResultMatch => Ok(()),
130 sys::FcResultNoMatch => Err(Error::NoMatch),
131 sys::FcResultTypeMismatch => Err(Error::TypeMismatch),
132 sys::FcResultNoId => Err(Error::NoId),
133 _ => unreachable!(),
134 }
135 }
136}
137
138pub enum MatchKind {
140 Pattern,
142 Font,
144 Scan,
146}
147
148#[doc(hidden)]
149impl From<sys::FcMatchKind> for MatchKind {
150 fn from(kind: sys::FcMatchKind) -> Self {
151 match kind {
152 sys::FcMatchPattern => MatchKind::Pattern,
153 sys::FcMatchFont => MatchKind::Font,
154 sys::FcMatchScan => MatchKind::Scan,
155 _ => unreachable!(),
156 }
157 }
158}
159
160#[doc(hidden)]
161impl From<MatchKind> for sys::FcMatchKind {
162 fn from(kind: MatchKind) -> sys::FcMatchKind {
163 match kind {
164 MatchKind::Pattern => sys::FcMatchPattern,
165 MatchKind::Font => sys::FcMatchFont,
166 MatchKind::Scan => sys::FcMatchScan,
167 }
168 }
169}
170
171#[derive(Eq, PartialEq)]
173#[allow(missing_docs)]
174pub enum FontFormat {
175 TrueType,
176 Type1,
177 BDF,
178 PCF,
179 Type42,
180 CIDType1,
181 CFF,
182 PFR,
183 WindowsFNT,
184}
185
186#[doc(alias = "FcConfig")]
188pub struct FontConfig {
189 cfg: Option<NonNull<sys::FcConfig>>,
190}
191
192impl FontConfig {
194 pub fn substitute(&mut self, pat: &mut Pattern, kind: MatchKind) {
222 let ret = unsafe {
223 ffi_dispatch!(
224 LIB,
225 FcConfigSubstitute,
226 self.as_mut_ptr(),
227 pat.as_mut_ptr(),
228 kind.into()
229 )
230 };
231 assert_eq!(ret, FcTrue);
232 }
233
234 #[doc(alias = "FcFontSetMatch")]
243 pub fn fontset_match(&mut self, sets: &mut [FontSet], pat: &mut Pattern) -> OwnedPattern {
244 let mut result = sys::FcResultNoMatch;
247 let pat = unsafe {
248 ffi_dispatch!(
249 LIB,
250 FcFontSetMatch,
251 self.as_mut_ptr(),
252 &mut sets.as_mut_ptr().cast(),
253 sets.len() as i32,
254 pat.as_mut_ptr(),
255 &mut result
256 )
257 };
258 result.ok().unwrap();
259 OwnedPattern {
260 pat: NonNull::new(pat).unwrap(),
261 }
262 }
263
264 fn as_mut_ptr(&mut self) -> *mut sys::FcConfig {
265 if let Some(ref mut cfg) = self.cfg {
266 cfg.as_ptr()
267 } else {
268 ptr::null_mut()
269 }
270 }
271
272 #[allow(dead_code)]
273 fn as_ptr(&self) -> *const sys::FcConfig {
274 if let Some(ref cfg) = self.cfg {
275 cfg.as_ptr()
276 } else {
277 ptr::null_mut()
278 }
279 }
280
281 pub fn find(&mut self, family: String, style: Option<String>) -> Option<Font> {
284 let mut pat = OwnedPattern::new();
285 pat.add(&properties::FC_FAMILY, family);
286
287 if let Some(style) = style {
288 let style = style;
289 pat.add(&properties::FC_STYLE, style);
290 }
291
292 let font_match = pat.font_match(self);
293
294 font_match.name().and_then(|name| {
295 font_match.filename().map(|filename| Font {
296 name: name.to_owned(),
297 path: PathBuf::from(filename),
298 })
299 })
300 }
301}
302
303impl Default for FontConfig {
304 fn default() -> Self {
308 let mut guard = INITIALIZED.lock().unwrap();
309 #[cfg(feature = "dlopen")]
310 if LIB_RESULT.is_err() {
311 panic!("Failed to load fontconfig library");
312 }
313 assert_eq!(FcTrue, unsafe { ffi_dispatch!(LIB, FcInit,) });
314 *guard += 1;
315 FontConfig { cfg: None }
316 }
317}
318
319impl Drop for FontConfig {
320 fn drop(&mut self) {
321 let guard = INITIALIZED.lock().unwrap();
322 if guard.checked_sub(1).unwrap_or_default() == 0 {
323 unsafe { ffi_dispatch!(LIB, FcFini,) };
324 }
325 }
326}
327
328pub struct Font {
339 pub name: String,
341 pub path: PathBuf,
343}
344
345impl Font {
346 #[allow(dead_code)]
347 fn print_debug(&self) {
348 println!("Name: {}\nPath: {}", self.name, self.path.display());
349 }
350}
351
352impl FromStr for FontFormat {
353 type Err = Error;
354
355 fn from_str(s: &str) -> Result<Self> {
356 match s {
357 "TrueType" => Ok(FontFormat::TrueType),
358 "Type 1" => Ok(FontFormat::Type1),
359 "BDF" => Ok(FontFormat::BDF),
360 "PCF" => Ok(FontFormat::PCF),
361 "Type 42" => Ok(FontFormat::Type42),
362 "CID Type 1" => Ok(FontFormat::CIDType1),
363 "CFF" => Ok(FontFormat::CFF),
364 "PFR" => Ok(FontFormat::PFR),
365 "Windows FNT" => Ok(FontFormat::WindowsFNT),
366 _ => Err(Error::UnknownFontFormat(s.to_string())),
367 }
368 }
369}
370
371#[derive(Debug, Clone, Copy, PartialEq, Eq)]
372pub struct Version {
374 pub major: u32,
376 pub minor: u32,
378 pub revision: u32,
380}
381
382impl std::fmt::Display for Version {
383 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
384 write!(f, "{}.{}.{}", self.major, self.minor, self.revision)
385 }
386}
387
388pub fn version() -> Version {
390 let version = unsafe { ffi_dispatch!(LIB, FcGetVersion,) as u32 };
391 let major = version / 10000;
392 let version = version % 10000;
393 let minor = version / 100;
394 let revision = version % 100;
395
396 Version {
397 major,
398 minor,
399 revision,
400 }
401}
402
403#[cfg(test)]
404mod tests {
405 use super::*;
406
407 #[test]
408 fn it_works() {
409 FontConfig::default();
410 }
411
412 #[test]
413 fn find_font() {
414 let mut config = FontConfig::default();
415 config
416 .find("dejavu sans".to_string(), None)
417 .unwrap()
418 .print_debug();
419 config
420 .find("dejavu sans".to_string(), Some("oblique".to_string()))
421 .unwrap()
422 .print_debug();
423 }
424
425 #[test]
426 fn iter_and_print() {
427 let mut config = FontConfig::default();
428 let pat = OwnedPattern::new();
429 let fontset = pat.font_list(&mut config, None);
430 for pattern in fontset.iter() {
431 println!("{:?}", pattern.name());
432 }
433
434 assert!(fontset.iter().count() > 0);
436 }
437
438 #[test]
439 fn iter_lang_set() {
440 let mut config = FontConfig::default();
441 let mut pat = OwnedPattern::new();
442 let family = "dejavu sans".to_string();
443 pat.add(&properties::FC_FAMILY, family);
444 let pattern = pat.font_match(&mut config);
445 for lang in pattern.lang_set().unwrap().langs().iter() {
446 println!("{:?}", lang);
447 }
448
449 assert!(pattern
451 .lang_set()
452 .unwrap()
453 .langs()
454 .iter()
455 .any(|lang| lang == "za"));
456
457 let langs = pattern
459 .lang_set()
460 .unwrap()
461 .langs()
462 .iter()
463 .map(ToString::to_string)
464 .collect::<Vec<_>>();
465 assert!(langs.iter().any(|l| l == "ie"));
466 }
467
468 #[test]
469 fn iter_font_sort() {
470 let mut config = FontConfig::default();
471 let mut pat = OwnedPattern::new();
472 let family = "dejavu sans".to_owned();
473 pat.add(&properties::FC_FAMILY, family);
474 pat.default_substitute();
475 config.substitute(&mut pat, MatchKind::Pattern);
476 let font_set = pat.font_sort(&mut config, false).unwrap();
477
478 for font in font_set.iter() {
479 println!("{:?}", font.name());
480 }
481 assert!(font_set.iter().count() > 1);
482 assert!(font_set.iter().next().unwrap().name().unwrap() == "DejaVu Sans");
483 }
484}