1#![deny(missing_docs)]
2
3extern crate fontconfig_sys;
42
43use crate::fontconfig_sys::fontconfig as sys;
44
45use std::ffi::{CStr, CString};
46use std::mem;
47use std::path::PathBuf;
48use std::ptr;
49use std::str::FromStr;
50
51pub use sys::constants::*;
52use sys::{FcBool, FcPattern};
53
54#[allow(non_upper_case_globals)]
55const FcTrue: FcBool = 1;
56#[allow(non_upper_case_globals, dead_code)]
57const FcFalse: FcBool = 0;
58
59pub struct Fontconfig {
61 _initialised: (),
62}
63
64#[derive(Debug)]
68pub struct UnknownFontFormat(pub String);
69
70#[derive(Eq, PartialEq)]
72#[allow(missing_docs)]
73pub enum FontFormat {
74 TrueType,
75 Type1,
76 BDF,
77 PCF,
78 Type42,
79 CIDType1,
80 CFF,
81 PFR,
82 WindowsFNT,
83}
84
85impl Fontconfig {
86 pub fn new() -> Option<Self> {
90 if unsafe { sys::FcInit() == FcTrue } {
91 Some(Fontconfig { _initialised: () })
92 } else {
93 None
94 }
95 }
96
97 pub fn find(&self, family: &str, style: Option<&str>) -> Option<Font> {
100 Font::find(self, family, style)
101 }
102}
103
104pub struct Font {
115 pub name: String,
117 pub path: PathBuf,
119}
120
121impl Font {
122 fn find(fc: &Fontconfig, family: &str, style: Option<&str>) -> Option<Font> {
123 let mut pat = Pattern::new(fc);
124 let family = CString::new(family).ok()?;
125 pat.add_string(FC_FAMILY.as_cstr(), &family);
126
127 if let Some(style) = style {
128 let style = CString::new(style).ok()?;
129 pat.add_string(FC_STYLE.as_cstr(), &style);
130 }
131
132 let font_match = pat.font_match();
133
134 font_match.name().and_then(|name| {
135 font_match.filename().map(|filename| Font {
136 name: name.to_owned(),
137 path: PathBuf::from(filename),
138 })
139 })
140 }
141
142 #[allow(dead_code)]
143 fn print_debug(&self) {
144 println!("Name: {}\nPath: {}", self.name, self.path.display());
145 }
146}
147
148#[repr(C)]
150pub struct Pattern<'fc> {
151 pub pat: *mut FcPattern,
153 fc: &'fc Fontconfig,
154}
155
156impl<'fc> Pattern<'fc> {
157 pub fn new(fc: &Fontconfig) -> Pattern {
159 let pat = unsafe { sys::FcPatternCreate() };
160 assert!(!pat.is_null());
161
162 Pattern { pat, fc }
163 }
164
165 pub fn from_pattern(fc: &Fontconfig, pat: *mut FcPattern) -> Pattern {
167 unsafe {
168 sys::FcPatternReference(pat);
169 }
170
171 Pattern { pat, fc }
172 }
173
174 pub fn add_string(&mut self, name: &CStr, val: &CStr) {
180 unsafe {
181 sys::FcPatternAddString(self.pat, name.as_ptr(), val.as_ptr() as *const u8);
182 }
183 }
184
185 pub fn get_string<'a>(&'a self, name: &'a CStr) -> Option<&'a str> {
187 unsafe {
188 let mut ret: *mut sys::FcChar8 = ptr::null_mut();
189 if sys::FcPatternGetString(self.pat, name.as_ptr(), 0, &mut ret as *mut _)
190 == sys::FcResultMatch
191 {
192 let cstr = CStr::from_ptr(ret as *const i8);
193 Some(cstr.to_str().unwrap())
194 } else {
195 None
196 }
197 }
198 }
199
200 pub fn get_int(&self, name: &CStr) -> Option<i32> {
202 unsafe {
203 let mut ret: i32 = 0;
204 if sys::FcPatternGetInteger(self.pat, name.as_ptr(), 0, &mut ret as *mut i32)
205 == sys::FcResultMatch
206 {
207 Some(ret)
208 } else {
209 None
210 }
211 }
212 }
213
214 pub fn print(&self) {
216 unsafe {
217 sys::FcPatternPrint(&*self.pat);
218 }
219 }
220
221 fn default_substitute(&mut self) {
222 unsafe {
223 sys::FcDefaultSubstitute(self.pat);
224 }
225 }
226
227 fn config_substitute(&mut self) {
228 unsafe {
229 sys::FcConfigSubstitute(ptr::null_mut(), self.pat, sys::FcMatchPattern);
230 }
231 }
232
233 pub fn font_match(&mut self) -> Pattern {
235 self.default_substitute();
236 self.config_substitute();
237
238 unsafe {
239 let mut res = sys::FcResultNoMatch;
240 Pattern::from_pattern(
241 self.fc,
242 sys::FcFontMatch(ptr::null_mut(), self.pat, &mut res),
243 )
244 }
245 }
246
247 pub fn name(&self) -> Option<&str> {
249 self.get_string(FC_FULLNAME.as_cstr())
250 }
251
252 pub fn filename(&self) -> Option<&str> {
254 self.get_string(FC_FILE.as_cstr())
255 }
256
257 pub fn face_index(&self) -> Option<i32> {
259 self.get_int(FC_INDEX.as_cstr())
260 }
261
262 pub fn slant(&self) -> Option<i32> {
264 self.get_int(FC_SLANT.as_cstr())
265 }
266
267 pub fn weight(&self) -> Option<i32> {
269 self.get_int(FC_WEIGHT.as_cstr())
270 }
271
272 pub fn width(&self) -> Option<i32> {
274 self.get_int(FC_WIDTH.as_cstr())
275 }
276
277 pub fn format(&self) -> Result<FontFormat, UnknownFontFormat> {
279 self.get_string(FC_FONTFORMAT.as_cstr())
280 .ok_or_else(|| UnknownFontFormat(String::new()))
281 .and_then(|format| format.parse())
282 }
283}
284
285impl<'fc> std::fmt::Debug for Pattern<'fc> {
286 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
287 let fcstr = unsafe { sys::FcNameUnparse(self.pat) };
288 let fcstr = unsafe { CStr::from_ptr(fcstr as *const i8) };
289 let result = write!(f, "{:?}", fcstr);
290 unsafe { sys::FcStrFree(fcstr.as_ptr() as *mut u8) };
291 result
292 }
293}
294
295impl<'fc> Clone for Pattern<'fc> {
296 fn clone(&self) -> Self {
297 let clone = unsafe { sys::FcPatternDuplicate(self.pat) };
298 Pattern {
299 pat: clone,
300 fc: self.fc,
301 }
302 }
303}
304
305impl<'fc> Drop for Pattern<'fc> {
306 fn drop(&mut self) {
307 unsafe {
308 sys::FcPatternDestroy(self.pat);
309 }
310 }
311}
312
313pub struct FontSet<'fc> {
315 fcset: *mut sys::FcFontSet,
316 fc: &'fc Fontconfig,
317}
318
319impl<'fc> FontSet<'fc> {
320 pub fn new(fc: &Fontconfig) -> FontSet {
322 let fcset = unsafe { sys::FcFontSetCreate() };
323 FontSet { fcset, fc }
324 }
325
326 pub fn from_raw(fc: &Fontconfig, raw_set: *mut sys::FcFontSet) -> FontSet {
330 FontSet { fcset: raw_set, fc }
331 }
332
333 pub fn add_pattern(&mut self, pat: Pattern) {
335 unsafe {
336 sys::FcFontSetAdd(self.fcset, pat.pat);
337 mem::forget(pat);
338 }
339 }
340
341 pub fn print(&self) {
343 unsafe { sys::FcFontSetPrint(self.fcset) };
344 }
345
346 pub fn iter(&self) -> impl Iterator<Item = Pattern<'_>> {
348 let patterns = unsafe {
349 let fontset = self.fcset;
350 std::slice::from_raw_parts((*fontset).fonts, (*fontset).nfont as usize)
351 };
352 patterns
353 .iter()
354 .map(move |&pat| Pattern::from_pattern(self.fc, pat))
355 }
356}
357
358impl<'fc> Drop for FontSet<'fc> {
359 fn drop(&mut self) {
360 unsafe { sys::FcFontSetDestroy(self.fcset) }
361 }
362}
363
364pub fn list_fonts<'fc>(pattern: &Pattern<'fc>, objects: Option<&ObjectSet>) -> FontSet<'fc> {
366 let os = objects.map(|o| o.fcset).unwrap_or(ptr::null_mut());
367 let ptr = unsafe { sys::FcFontList(ptr::null_mut(), pattern.pat, os) };
368 FontSet::from_raw(pattern.fc, ptr)
369}
370
371pub struct ObjectSet {
373 fcset: *mut sys::FcObjectSet,
374}
375
376impl ObjectSet {
377 pub fn new(_: &Fontconfig) -> ObjectSet {
379 let fcset = unsafe { sys::FcObjectSetCreate() };
380 assert!(!fcset.is_null());
381
382 ObjectSet { fcset }
383 }
384
385 pub fn from_raw(_: &Fontconfig, raw_set: *mut sys::FcObjectSet) -> ObjectSet {
389 assert!(!raw_set.is_null());
390 ObjectSet { fcset: raw_set }
391 }
392
393 pub fn add(&mut self, name: &CStr) {
395 let res = unsafe { sys::FcObjectSetAdd(self.fcset, name.as_ptr()) };
396 assert_eq!(res, FcTrue);
397 }
398}
399
400impl Drop for ObjectSet {
401 fn drop(&mut self) {
402 unsafe { sys::FcObjectSetDestroy(self.fcset) }
403 }
404}
405
406impl FromStr for FontFormat {
407 type Err = UnknownFontFormat;
408
409 fn from_str(s: &str) -> Result<Self, Self::Err> {
410 match s {
411 "TrueType" => Ok(FontFormat::TrueType),
412 "Type 1" => Ok(FontFormat::Type1),
413 "BDF" => Ok(FontFormat::BDF),
414 "PCF" => Ok(FontFormat::PCF),
415 "Type 42" => Ok(FontFormat::Type42),
416 "CID Type 1" => Ok(FontFormat::CIDType1),
417 "CFF" => Ok(FontFormat::CFF),
418 "PFR" => Ok(FontFormat::PFR),
419 "Windows FNT" => Ok(FontFormat::WindowsFNT),
420 _ => Err(UnknownFontFormat(s.to_string())),
421 }
422 }
423}
424
425#[cfg(test)]
426mod tests {
427 use super::*;
428
429 #[test]
430 fn it_works() {
431 assert!(Fontconfig::new().is_some())
432 }
433
434 #[test]
435 fn test_find_font() {
436 let fc = Fontconfig::new().unwrap();
437 fc.find("dejavu sans", None).unwrap().print_debug();
438 fc.find("dejavu sans", Some("oblique"))
439 .unwrap()
440 .print_debug();
441 }
442
443 #[test]
444 fn test_iter_and_print() {
445 let fc = Fontconfig::new().unwrap();
446 let fontset = list_fonts(&Pattern::new(&fc), None);
447 for pattern in fontset.iter() {
448 println!("{:?}", pattern.name());
449 }
450
451 assert!(fontset.iter().count() > 0);
453 }
454}