1// font-kit/src/sources/fontconfig.rs
2//
3// Copyright © 2018 The Pathfinder Project Developers.
4//
5// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8// option. This file may not be copied, modified, or distributed
9// except according to those terms.
10
11//! A source that contains the fonts installed on the system, as reported by the Fontconfig
12//! library.
13//!
14//! On macOS and Windows, the Cargo feature `source-fontconfig` can be used to opt into fontconfig
15//! support. To prefer it over the native font source (only if you know what you're doing), use the
16//! `source-fontconfig-default` feature.
17
18use 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/// A source that contains the fonts installed on the system, as reported by the Fontconfig
27/// library.
28///
29/// On macOS and Windows, the Cargo feature `source-fontconfig` can be used to opt into fontconfig
30/// support. To prefer it over the native font source (only if you know what you're doing), use the
31/// `source-fontconfig-default` feature.
32#[allow(missing_debug_implementations)]
33pub struct FontconfigSource {
34 config: fc::Config,
35}
36
37impl FontconfigSource {
38 /// Initializes Fontconfig and prepares it for queries.
39 pub fn new() -> FontconfigSource {
40 FontconfigSource {
41 config: fc::Config::new(),
42 }
43 }
44
45 /// Returns paths of all fonts installed on the system.
46 pub fn all_fonts(&self) -> Result<Vec<Handle>, SelectionError> {
47 let pattern = fc::Pattern::new();
48
49 // We want the family name.
50 let mut object_set = fc::ObjectSet::new();
51 object_set.push_string(fc::Object::File);
52 object_set.push_string(fc::Object::Index);
53
54 let patterns = pattern
55 .list(&self.config, object_set)
56 .map_err(|_| SelectionError::NotFound)?;
57
58 let mut handles = vec![];
59 for patt in patterns {
60 let path = match patt.get_string(fc::Object::File) {
61 Some(v) => v,
62 None => continue,
63 };
64
65 let index = match patt.get_integer(fc::Object::Index) {
66 Some(v) => v,
67 None => continue,
68 };
69
70 handles.push(Handle::Path {
71 path: path.into(),
72 font_index: index as u32,
73 });
74 }
75
76 if !handles.is_empty() {
77 Ok(handles)
78 } else {
79 Err(SelectionError::NotFound)
80 }
81 }
82
83 /// Returns the names of all families installed on the system.
84 pub fn all_families(&self) -> Result<Vec<String>, SelectionError> {
85 let pattern = fc::Pattern::new();
86
87 // We want the family name.
88 let mut object_set = fc::ObjectSet::new();
89 object_set.push_string(fc::Object::Family);
90
91 let patterns = pattern
92 .list(&self.config, object_set)
93 .map_err(|_| SelectionError::NotFound)?;
94
95 let mut result_families = vec![];
96 for patt in patterns {
97 if let Some(family) = patt.get_string(fc::Object::Family) {
98 result_families.push(family);
99 }
100 }
101
102 result_families.sort();
103 result_families.dedup();
104
105 if !result_families.is_empty() {
106 Ok(result_families)
107 } else {
108 Err(SelectionError::NotFound)
109 }
110 }
111
112 /// Looks up a font family by name and returns the handles of all the fonts in that family.
113 pub fn select_family_by_name(&self, family_name: &str) -> Result<FamilyHandle, SelectionError> {
114 use std::borrow::Cow;
115
116 let family_name = match family_name {
117 "serif" | "sans-serif" | "monospace" | "cursive" | "fantasy" => {
118 Cow::from(self.select_generic_font(family_name)?)
119 }
120 _ => Cow::from(family_name),
121 };
122
123 let pattern = fc::Pattern::from_name(family_name.as_ref());
124
125 let mut object_set = fc::ObjectSet::new();
126 object_set.push_string(fc::Object::File);
127 object_set.push_string(fc::Object::Index);
128
129 let patterns = pattern
130 .list(&self.config, object_set)
131 .map_err(|_| SelectionError::NotFound)?;
132
133 let mut handles = vec![];
134 for patt in patterns {
135 let font_path = patt.get_string(fc::Object::File).unwrap();
136 let font_index = patt.get_integer(fc::Object::Index).unwrap() as u32;
137 let handle = Handle::from_path(std::path::PathBuf::from(font_path), font_index);
138 handles.push(handle);
139 }
140
141 if !handles.is_empty() {
142 Ok(FamilyHandle::from_font_handles(handles.into_iter()))
143 } else {
144 Err(SelectionError::NotFound)
145 }
146 }
147
148 /// Selects a font by a generic name.
149 ///
150 /// Accepts: serif, sans-serif, monospace, cursive and fantasy.
151 fn select_generic_font(&self, name: &str) -> Result<String, SelectionError> {
152 let mut pattern = fc::Pattern::from_name(name);
153 pattern.config_substitute(fc::MatchKind::Pattern);
154 pattern.default_substitute();
155
156 let patterns = pattern
157 .sorted(&self.config)
158 .map_err(|_| SelectionError::NotFound)?;
159
160 if let Some(patt) = patterns.into_iter().next() {
161 if let Some(family) = patt.get_string(fc::Object::Family) {
162 return Ok(family);
163 }
164 }
165
166 Err(SelectionError::NotFound)
167 }
168
169 /// Selects a font by PostScript name, which should be a unique identifier.
170 ///
171 /// The default implementation, which is used by the DirectWrite and the filesystem backends,
172 /// does a brute-force search of installed fonts to find the one that matches.
173 pub fn select_by_postscript_name(
174 &self,
175 postscript_name: &str,
176 ) -> Result<Handle, SelectionError> {
177 let mut pattern = fc::Pattern::new();
178 pattern.push_string(fc::Object::PostScriptName, postscript_name.to_owned());
179
180 // We want the file path and the font index.
181 let mut object_set = fc::ObjectSet::new();
182 object_set.push_string(fc::Object::File);
183 object_set.push_string(fc::Object::Index);
184
185 let patterns = pattern
186 .list(&self.config, object_set)
187 .map_err(|_| SelectionError::NotFound)?;
188
189 if let Some(patt) = patterns.into_iter().next() {
190 let font_path = patt.get_string(fc::Object::File).unwrap();
191 let font_index = patt.get_integer(fc::Object::Index).unwrap() as u32;
192 let handle = Handle::from_path(std::path::PathBuf::from(font_path), font_index);
193 Ok(handle)
194 } else {
195 Err(SelectionError::NotFound)
196 }
197 }
198
199 /// Performs font matching according to the CSS Fonts Level 3 specification and returns the
200 /// handle.
201 #[inline]
202 pub fn select_best_match(
203 &self,
204 family_names: &[FamilyName],
205 properties: &Properties,
206 ) -> Result<Handle, SelectionError> {
207 <Self as Source>::select_best_match(self, family_names, properties)
208 }
209}
210
211impl Source for FontconfigSource {
212 #[inline]
213 fn all_fonts(&self) -> Result<Vec<Handle>, SelectionError> {
214 self.all_fonts()
215 }
216
217 #[inline]
218 fn all_families(&self) -> Result<Vec<String>, SelectionError> {
219 self.all_families()
220 }
221
222 #[inline]
223 fn select_family_by_name(&self, family_name: &str) -> Result<FamilyHandle, SelectionError> {
224 self.select_family_by_name(family_name)
225 }
226
227 #[inline]
228 fn select_by_postscript_name(&self, postscript_name: &str) -> Result<Handle, SelectionError> {
229 self.select_by_postscript_name(postscript_name)
230 }
231
232 #[inline]
233 fn as_any(&self) -> &dyn Any {
234 self
235 }
236
237 #[inline]
238 fn as_mut_any(&mut self) -> &mut dyn Any {
239 self
240 }
241}
242
243// A minimal fontconfig wrapper.
244mod fc {
245 #![allow(dead_code)]
246
247 use fontconfig_sys as ffi;
248 use fontconfig_sys::ffi_dispatch;
249
250 #[cfg(feature = "source-fontconfig-dlopen")]
251 use ffi::statics::LIB;
252 #[cfg(not(feature = "source-fontconfig-dlopen"))]
253 use ffi::*;
254
255 use std::ffi::{CStr, CString};
256 use std::os::raw::{c_char, c_uchar};
257 use std::ptr;
258
259 #[derive(Clone, Copy)]
260 pub enum Error {
261 NoMatch,
262 TypeMismatch,
263 NoId,
264 OutOfMemory,
265 }
266
267 #[derive(Clone, Copy)]
268 pub enum MatchKind {
269 Pattern,
270 Font,
271 Scan,
272 }
273
274 impl MatchKind {
275 fn to_u32(&self) -> u32 {
276 match self {
277 MatchKind::Pattern => ffi::FcMatchPattern,
278 MatchKind::Font => ffi::FcMatchFont,
279 MatchKind::Scan => ffi::FcMatchScan,
280 }
281 }
282 }
283
284 // https://www.freedesktop.org/software/fontconfig/fontconfig-devel/x19.html
285 #[derive(Clone, Copy)]
286 pub enum Object {
287 Family,
288 File,
289 Index,
290 PostScriptName,
291 }
292
293 impl Object {
294 fn as_bytes(&self) -> &[u8] {
295 match self {
296 Object::Family => b"family\0",
297 Object::File => b"file\0",
298 Object::Index => b"index\0",
299 Object::PostScriptName => b"postscriptname\0",
300 }
301 }
302
303 fn as_ptr(&self) -> *const libc::c_char {
304 self.as_bytes().as_ptr() as *const libc::c_char
305 }
306 }
307
308 pub struct Config {
309 d: *mut ffi::FcConfig,
310 }
311
312 impl Config {
313 // FcInitLoadConfigAndFonts
314 pub fn new() -> Self {
315 unsafe {
316 Config {
317 d: ffi_dispatch!(
318 feature = "source-fontconfig-dlopen",
319 LIB,
320 FcInitLoadConfigAndFonts,
321 ),
322 }
323 }
324 }
325 }
326
327 impl Drop for Config {
328 fn drop(&mut self) {
329 unsafe {
330 ffi_dispatch!(
331 feature = "source-fontconfig-dlopen",
332 LIB,
333 FcConfigDestroy,
334 self.d
335 );
336 }
337 }
338 }
339
340 pub struct Pattern {
341 d: *mut ffi::FcPattern,
342 c_strings: Vec<CString>,
343 }
344
345 impl Pattern {
346 fn from_ptr(d: *mut ffi::FcPattern) -> Self {
347 Pattern {
348 d,
349 c_strings: vec![],
350 }
351 }
352
353 // FcPatternCreate
354 pub fn new() -> Self {
355 unsafe {
356 Pattern::from_ptr(ffi_dispatch!(
357 feature = "source-fontconfig-dlopen",
358 LIB,
359 FcPatternCreate,
360 ))
361 }
362 }
363
364 // FcNameParse
365 pub fn from_name(name: &str) -> Self {
366 let c_name = CString::new(name).unwrap();
367 unsafe {
368 Pattern::from_ptr(ffi_dispatch!(
369 feature = "source-fontconfig-dlopen",
370 LIB,
371 FcNameParse,
372 c_name.as_ptr() as *mut c_uchar
373 ))
374 }
375 }
376
377 // FcPatternAddString
378 pub fn push_string(&mut self, object: Object, value: String) {
379 unsafe {
380 let c_string = CString::new(value).unwrap();
381 ffi_dispatch!(
382 feature = "source-fontconfig-dlopen",
383 LIB,
384 FcPatternAddString,
385 self.d,
386 object.as_ptr(),
387 c_string.as_ptr() as *const c_uchar
388 );
389
390 // We have to keep this string, because `FcPattern` has a pointer to it now.
391 self.c_strings.push(c_string)
392 }
393 }
394
395 // FcConfigSubstitute
396 pub fn config_substitute(&mut self, match_kind: MatchKind) {
397 unsafe {
398 ffi_dispatch!(
399 feature = "source-fontconfig-dlopen",
400 LIB,
401 FcConfigSubstitute,
402 ptr::null_mut(),
403 self.d,
404 match_kind.to_u32()
405 );
406 }
407 }
408
409 // FcDefaultSubstitute
410 pub fn default_substitute(&mut self) {
411 unsafe {
412 ffi_dispatch!(
413 feature = "source-fontconfig-dlopen",
414 LIB,
415 FcDefaultSubstitute,
416 self.d
417 );
418 }
419 }
420
421 // FcFontSort
422 pub fn sorted(&self, config: &Config) -> Result<FontSet, Error> {
423 let mut res = ffi::FcResultMatch;
424 let d = unsafe {
425 ffi_dispatch!(
426 feature = "source-fontconfig-dlopen",
427 LIB,
428 FcFontSort,
429 config.d,
430 self.d,
431 1,
432 ptr::null_mut(),
433 &mut res
434 )
435 };
436
437 match res {
438 ffi::FcResultMatch => Ok(FontSet { d, idx: 0 }),
439 ffi::FcResultTypeMismatch => Err(Error::TypeMismatch),
440 ffi::FcResultNoId => Err(Error::NoId),
441 ffi::FcResultOutOfMemory => Err(Error::OutOfMemory),
442 _ => Err(Error::NoMatch),
443 }
444 }
445
446 // FcFontList
447 pub fn list(&self, config: &Config, set: ObjectSet) -> Result<FontSet, Error> {
448 let d = unsafe {
449 ffi_dispatch!(
450 feature = "source-fontconfig-dlopen",
451 LIB,
452 FcFontList,
453 config.d,
454 self.d,
455 set.d
456 )
457 };
458 if !d.is_null() {
459 Ok(FontSet { d, idx: 0 })
460 } else {
461 Err(Error::NoMatch)
462 }
463 }
464 }
465
466 impl Drop for Pattern {
467 #[inline]
468 fn drop(&mut self) {
469 unsafe {
470 ffi_dispatch!(
471 feature = "source-fontconfig-dlopen",
472 LIB,
473 FcPatternDestroy,
474 self.d
475 )
476 }
477 }
478 }
479
480 // A read-only `FcPattern` without a destructor.
481 pub struct PatternRef {
482 d: *mut ffi::FcPattern,
483 }
484
485 impl PatternRef {
486 // FcPatternGetString
487 pub fn get_string(&self, object: Object) -> Option<String> {
488 unsafe {
489 let mut string = ptr::null_mut();
490 let res = ffi_dispatch!(
491 feature = "source-fontconfig-dlopen",
492 LIB,
493 FcPatternGetString,
494 self.d,
495 object.as_ptr(),
496 0,
497 &mut string
498 );
499 if res != ffi::FcResultMatch {
500 return None;
501 }
502
503 if string.is_null() {
504 return None;
505 }
506
507 CStr::from_ptr(string as *const c_char)
508 .to_str()
509 .ok()
510 .map(|string| string.to_owned())
511 }
512 }
513
514 // FcPatternGetInteger
515 pub fn get_integer(&self, object: Object) -> Option<i32> {
516 unsafe {
517 let mut integer = 0;
518 let res = ffi_dispatch!(
519 feature = "source-fontconfig-dlopen",
520 LIB,
521 FcPatternGetInteger,
522 self.d,
523 object.as_ptr(),
524 0,
525 &mut integer
526 );
527 if res != ffi::FcResultMatch {
528 return None;
529 }
530
531 Some(integer)
532 }
533 }
534 }
535
536 pub struct FontSet {
537 d: *mut ffi::FcFontSet,
538 idx: usize,
539 }
540
541 impl FontSet {
542 pub fn is_empty(&self) -> bool {
543 self.len() == 0
544 }
545
546 pub fn len(&self) -> usize {
547 unsafe { (*self.d).nfont as usize }
548 }
549 }
550
551 impl Iterator for FontSet {
552 type Item = PatternRef;
553
554 fn next(&mut self) -> Option<Self::Item> {
555 if self.idx == self.len() {
556 return None;
557 }
558
559 let idx = self.idx;
560 self.idx += 1;
561
562 let d = unsafe { *(*self.d).fonts.offset(idx as isize) };
563 Some(PatternRef { d })
564 }
565
566 fn size_hint(&self) -> (usize, Option<usize>) {
567 (0, Some(self.len()))
568 }
569 }
570
571 impl Drop for FontSet {
572 fn drop(&mut self) {
573 unsafe {
574 ffi_dispatch!(
575 feature = "source-fontconfig-dlopen",
576 LIB,
577 FcFontSetDestroy,
578 self.d
579 )
580 }
581 }
582 }
583
584 pub struct ObjectSet {
585 d: *mut ffi::FcObjectSet,
586 }
587
588 impl ObjectSet {
589 // FcObjectSetCreate
590 pub fn new() -> Self {
591 unsafe {
592 ObjectSet {
593 d: ffi_dispatch!(feature = "source-fontconfig-dlopen", LIB, FcObjectSetCreate,),
594 }
595 }
596 }
597
598 // FcObjectSetAdd
599 pub fn push_string(&mut self, object: Object) {
600 unsafe {
601 // Returns `false` if the property name cannot be inserted
602 // into the set (due to allocation failure).
603 assert_eq!(
604 ffi_dispatch!(
605 feature = "source-fontconfig-dlopen",
606 LIB,
607 FcObjectSetAdd,
608 self.d,
609 object.as_ptr()
610 ),
611 1
612 );
613 }
614 }
615 }
616
617 impl Drop for ObjectSet {
618 fn drop(&mut self) {
619 unsafe {
620 ffi_dispatch!(
621 feature = "source-fontconfig-dlopen",
622 LIB,
623 FcObjectSetDestroy,
624 self.d
625 )
626 }
627 }
628 }
629}
630