1use crate::error::SelectionError;
19use crate::family_handle::FamilyHandle;
20use crate::family_name::FamilyName;
21use crate::handle::Handle;
22use crate::properties::Properties;
23use crate::source::Source;
24use std::any::Any;
25
26#[allow(missing_debug_implementations)]
33pub struct FontconfigSource {
34 config: fc::Config,
35}
36
37impl Default for FontconfigSource {
38 fn default() -> Self {
39 Self::new()
40 }
41}
42
43impl FontconfigSource {
44 pub fn new() -> FontconfigSource {
46 FontconfigSource {
47 config: fc::Config::new(),
48 }
49 }
50
51 pub fn all_fonts(&self) -> Result<Vec<Handle>, SelectionError> {
53 let pattern = fc::Pattern::new();
54
55 let mut object_set = fc::ObjectSet::new();
57 object_set.push_string(fc::Object::File);
58 object_set.push_string(fc::Object::Index);
59
60 let patterns = pattern
61 .list(&self.config, object_set)
62 .map_err(|_| SelectionError::NotFound)?;
63
64 let mut handles = vec![];
65 for patt in patterns {
66 let path = match patt.get_string(fc::Object::File) {
67 Some(v) => v,
68 None => continue,
69 };
70
71 let index = match patt.get_integer(fc::Object::Index) {
72 Some(v) => v,
73 None => continue,
74 };
75
76 handles.push(Handle::Path {
77 path: path.into(),
78 font_index: index as u32,
79 });
80 }
81
82 if !handles.is_empty() {
83 Ok(handles)
84 } else {
85 Err(SelectionError::NotFound)
86 }
87 }
88
89 pub fn all_families(&self) -> Result<Vec<String>, SelectionError> {
91 let pattern = fc::Pattern::new();
92
93 let mut object_set = fc::ObjectSet::new();
95 object_set.push_string(fc::Object::Family);
96
97 let patterns = pattern
98 .list(&self.config, object_set)
99 .map_err(|_| SelectionError::NotFound)?;
100
101 let mut result_families = vec![];
102 for patt in patterns {
103 if let Some(family) = patt.get_string(fc::Object::Family) {
104 result_families.push(family);
105 }
106 }
107
108 result_families.sort();
109 result_families.dedup();
110
111 if !result_families.is_empty() {
112 Ok(result_families)
113 } else {
114 Err(SelectionError::NotFound)
115 }
116 }
117
118 pub fn select_family_by_name(&self, family_name: &str) -> Result<FamilyHandle, SelectionError> {
120 use std::borrow::Cow;
121
122 let family_name = match family_name {
123 "serif" | "sans-serif" | "monospace" | "cursive" | "fantasy" => {
124 Cow::from(self.select_generic_font(family_name)?)
125 }
126 _ => Cow::from(family_name),
127 };
128
129 let pattern = fc::Pattern::from_name(family_name.as_ref());
130
131 let mut object_set = fc::ObjectSet::new();
132 object_set.push_string(fc::Object::File);
133 object_set.push_string(fc::Object::Index);
134
135 let patterns = pattern
136 .list(&self.config, object_set)
137 .map_err(|_| SelectionError::NotFound)?;
138
139 let mut handles = vec![];
140 for patt in patterns {
141 let font_path = patt.get_string(fc::Object::File).unwrap();
142 let font_index = patt.get_integer(fc::Object::Index).unwrap() as u32;
143 let handle = Handle::from_path(std::path::PathBuf::from(font_path), font_index);
144 handles.push(handle);
145 }
146
147 if !handles.is_empty() {
148 Ok(FamilyHandle::from_font_handles(handles.into_iter()))
149 } else {
150 Err(SelectionError::NotFound)
151 }
152 }
153
154 fn select_generic_font(&self, name: &str) -> Result<String, SelectionError> {
158 let mut pattern = fc::Pattern::from_name(name);
159 pattern.config_substitute(fc::MatchKind::Pattern);
160 pattern.default_substitute();
161
162 let patterns = pattern
163 .sorted(&self.config)
164 .map_err(|_| SelectionError::NotFound)?;
165
166 if let Some(patt) = patterns.into_iter().next() {
167 if let Some(family) = patt.get_string(fc::Object::Family) {
168 return Ok(family);
169 }
170 }
171
172 Err(SelectionError::NotFound)
173 }
174
175 pub fn select_by_postscript_name(
180 &self,
181 postscript_name: &str,
182 ) -> Result<Handle, SelectionError> {
183 let mut pattern = fc::Pattern::new();
184 pattern.push_string(fc::Object::PostScriptName, postscript_name.to_owned());
185
186 let mut object_set = fc::ObjectSet::new();
188 object_set.push_string(fc::Object::File);
189 object_set.push_string(fc::Object::Index);
190
191 let patterns = pattern
192 .list(&self.config, object_set)
193 .map_err(|_| SelectionError::NotFound)?;
194
195 if let Some(patt) = patterns.into_iter().next() {
196 let font_path = patt.get_string(fc::Object::File).unwrap();
197 let font_index = patt.get_integer(fc::Object::Index).unwrap() as u32;
198 let handle = Handle::from_path(std::path::PathBuf::from(font_path), font_index);
199 Ok(handle)
200 } else {
201 Err(SelectionError::NotFound)
202 }
203 }
204
205 #[inline]
208 pub fn select_best_match(
209 &self,
210 family_names: &[FamilyName],
211 properties: &Properties,
212 ) -> Result<Handle, SelectionError> {
213 <Self as Source>::select_best_match(self, family_names, properties)
214 }
215}
216
217impl Source for FontconfigSource {
218 #[inline]
219 fn all_fonts(&self) -> Result<Vec<Handle>, SelectionError> {
220 self.all_fonts()
221 }
222
223 #[inline]
224 fn all_families(&self) -> Result<Vec<String>, SelectionError> {
225 self.all_families()
226 }
227
228 #[inline]
229 fn select_family_by_name(&self, family_name: &str) -> Result<FamilyHandle, SelectionError> {
230 self.select_family_by_name(family_name)
231 }
232
233 #[inline]
234 fn select_by_postscript_name(&self, postscript_name: &str) -> Result<Handle, SelectionError> {
235 self.select_by_postscript_name(postscript_name)
236 }
237
238 #[inline]
239 fn as_any(&self) -> &dyn Any {
240 self
241 }
242
243 #[inline]
244 fn as_mut_any(&mut self) -> &mut dyn Any {
245 self
246 }
247}
248
249mod fc {
251 #![allow(dead_code)]
252
253 use fontconfig_sys as ffi;
254 use fontconfig_sys::ffi_dispatch;
255
256 #[cfg(feature = "source-fontconfig-dlopen")]
257 use ffi::statics::LIB;
258 #[cfg(not(feature = "source-fontconfig-dlopen"))]
259 use ffi::*;
260
261 use std::ffi::{CStr, CString};
262 use std::os::raw::{c_char, c_uchar};
263 use std::ptr;
264
265 #[derive(Clone, Copy)]
266 pub enum Error {
267 NoMatch,
268 TypeMismatch,
269 NoId,
270 OutOfMemory,
271 }
272
273 #[derive(Clone, Copy)]
274 pub enum MatchKind {
275 Pattern,
276 Font,
277 Scan,
278 }
279
280 impl MatchKind {
281 fn to_u32(self) -> u32 {
282 match self {
283 MatchKind::Pattern => ffi::FcMatchPattern,
284 MatchKind::Font => ffi::FcMatchFont,
285 MatchKind::Scan => ffi::FcMatchScan,
286 }
287 }
288 }
289
290 #[derive(Clone, Copy)]
292 pub enum Object {
293 Family,
294 File,
295 Index,
296 PostScriptName,
297 }
298
299 impl Object {
300 fn as_bytes(&self) -> &[u8] {
301 match self {
302 Object::Family => b"family\0",
303 Object::File => b"file\0",
304 Object::Index => b"index\0",
305 Object::PostScriptName => b"postscriptname\0",
306 }
307 }
308
309 fn as_ptr(&self) -> *const libc::c_char {
310 self.as_bytes().as_ptr() as *const libc::c_char
311 }
312 }
313
314 pub struct Config {
315 d: *mut ffi::FcConfig,
316 }
317
318 impl Config {
319 pub fn new() -> Self {
321 unsafe {
322 Config {
323 d: ffi_dispatch!(
324 feature = "source-fontconfig-dlopen",
325 LIB,
326 FcInitLoadConfigAndFonts,
327 ),
328 }
329 }
330 }
331 }
332
333 impl Drop for Config {
334 fn drop(&mut self) {
335 unsafe {
336 ffi_dispatch!(
337 feature = "source-fontconfig-dlopen",
338 LIB,
339 FcConfigDestroy,
340 self.d
341 );
342 }
343 }
344 }
345
346 pub struct Pattern {
347 d: *mut ffi::FcPattern,
348 c_strings: Vec<CString>,
349 }
350
351 impl Pattern {
352 fn from_ptr(d: *mut ffi::FcPattern) -> Self {
353 Pattern {
354 d,
355 c_strings: vec![],
356 }
357 }
358
359 pub fn new() -> Self {
361 unsafe {
362 Pattern::from_ptr(ffi_dispatch!(
363 feature = "source-fontconfig-dlopen",
364 LIB,
365 FcPatternCreate,
366 ))
367 }
368 }
369
370 pub fn from_name(name: &str) -> Self {
372 let c_name = CString::new(name).unwrap();
373 unsafe {
374 Pattern::from_ptr(ffi_dispatch!(
375 feature = "source-fontconfig-dlopen",
376 LIB,
377 FcNameParse,
378 c_name.as_ptr() as *mut c_uchar
379 ))
380 }
381 }
382
383 pub fn push_string(&mut self, object: Object, value: String) {
385 unsafe {
386 let c_string = CString::new(value).unwrap();
387 ffi_dispatch!(
388 feature = "source-fontconfig-dlopen",
389 LIB,
390 FcPatternAddString,
391 self.d,
392 object.as_ptr(),
393 c_string.as_ptr() as *const c_uchar
394 );
395
396 self.c_strings.push(c_string)
398 }
399 }
400
401 pub fn config_substitute(&mut self, match_kind: MatchKind) {
403 unsafe {
404 ffi_dispatch!(
405 feature = "source-fontconfig-dlopen",
406 LIB,
407 FcConfigSubstitute,
408 ptr::null_mut(),
409 self.d,
410 match_kind.to_u32()
411 );
412 }
413 }
414
415 pub fn default_substitute(&mut self) {
417 unsafe {
418 ffi_dispatch!(
419 feature = "source-fontconfig-dlopen",
420 LIB,
421 FcDefaultSubstitute,
422 self.d
423 );
424 }
425 }
426
427 pub fn sorted(&self, config: &Config) -> Result<FontSet, Error> {
429 let mut res = ffi::FcResultMatch;
430 let d = unsafe {
431 ffi_dispatch!(
432 feature = "source-fontconfig-dlopen",
433 LIB,
434 FcFontSort,
435 config.d,
436 self.d,
437 1,
438 ptr::null_mut(),
439 &mut res
440 )
441 };
442
443 match res {
444 ffi::FcResultMatch => Ok(FontSet { d, idx: 0 }),
445 ffi::FcResultTypeMismatch => Err(Error::TypeMismatch),
446 ffi::FcResultNoId => Err(Error::NoId),
447 ffi::FcResultOutOfMemory => Err(Error::OutOfMemory),
448 _ => Err(Error::NoMatch),
449 }
450 }
451
452 pub fn list(&self, config: &Config, set: ObjectSet) -> Result<FontSet, Error> {
454 let d = unsafe {
455 ffi_dispatch!(
456 feature = "source-fontconfig-dlopen",
457 LIB,
458 FcFontList,
459 config.d,
460 self.d,
461 set.d
462 )
463 };
464 if !d.is_null() {
465 Ok(FontSet { d, idx: 0 })
466 } else {
467 Err(Error::NoMatch)
468 }
469 }
470 }
471
472 impl Drop for Pattern {
473 #[inline]
474 fn drop(&mut self) {
475 unsafe {
476 ffi_dispatch!(
477 feature = "source-fontconfig-dlopen",
478 LIB,
479 FcPatternDestroy,
480 self.d
481 )
482 }
483 }
484 }
485
486 pub struct PatternRef {
488 d: *mut ffi::FcPattern,
489 }
490
491 impl PatternRef {
492 pub fn get_string(&self, object: Object) -> Option<String> {
494 unsafe {
495 let mut string = ptr::null_mut();
496 let res = ffi_dispatch!(
497 feature = "source-fontconfig-dlopen",
498 LIB,
499 FcPatternGetString,
500 self.d,
501 object.as_ptr(),
502 0,
503 &mut string
504 );
505 if res != ffi::FcResultMatch {
506 return None;
507 }
508
509 if string.is_null() {
510 return None;
511 }
512
513 CStr::from_ptr(string as *const c_char)
514 .to_str()
515 .ok()
516 .map(|string| string.to_owned())
517 }
518 }
519
520 pub fn get_integer(&self, object: Object) -> Option<i32> {
522 unsafe {
523 let mut integer = 0;
524 let res = ffi_dispatch!(
525 feature = "source-fontconfig-dlopen",
526 LIB,
527 FcPatternGetInteger,
528 self.d,
529 object.as_ptr(),
530 0,
531 &mut integer
532 );
533 if res != ffi::FcResultMatch {
534 return None;
535 }
536
537 Some(integer)
538 }
539 }
540 }
541
542 pub struct FontSet {
543 d: *mut ffi::FcFontSet,
544 idx: usize,
545 }
546
547 impl FontSet {
548 pub fn is_empty(&self) -> bool {
549 self.len() == 0
550 }
551
552 pub fn len(&self) -> usize {
553 unsafe { (*self.d).nfont as usize }
554 }
555 }
556
557 impl Iterator for FontSet {
558 type Item = PatternRef;
559
560 fn next(&mut self) -> Option<Self::Item> {
561 if self.idx == self.len() {
562 return None;
563 }
564
565 let idx = self.idx;
566 self.idx += 1;
567
568 let d = unsafe { *(*self.d).fonts.add(idx) };
569 Some(PatternRef { d })
570 }
571
572 fn size_hint(&self) -> (usize, Option<usize>) {
573 (0, Some(self.len()))
574 }
575 }
576
577 impl Drop for FontSet {
578 fn drop(&mut self) {
579 unsafe {
580 ffi_dispatch!(
581 feature = "source-fontconfig-dlopen",
582 LIB,
583 FcFontSetDestroy,
584 self.d
585 )
586 }
587 }
588 }
589
590 pub struct ObjectSet {
591 d: *mut ffi::FcObjectSet,
592 }
593
594 impl ObjectSet {
595 pub fn new() -> Self {
597 unsafe {
598 ObjectSet {
599 d: ffi_dispatch!(feature = "source-fontconfig-dlopen", LIB, FcObjectSetCreate,),
600 }
601 }
602 }
603
604 pub fn push_string(&mut self, object: Object) {
606 unsafe {
607 assert_eq!(
610 ffi_dispatch!(
611 feature = "source-fontconfig-dlopen",
612 LIB,
613 FcObjectSetAdd,
614 self.d,
615 object.as_ptr()
616 ),
617 1
618 );
619 }
620 }
621 }
622
623 impl Drop for ObjectSet {
624 fn drop(&mut self) {
625 unsafe {
626 ffi_dispatch!(
627 feature = "source-fontconfig-dlopen",
628 LIB,
629 FcObjectSetDestroy,
630 self.d
631 )
632 }
633 }
634 }
635}