Skip to main content

fonts/platform/freetype/
font_list.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::ffi::{CStr, CString};
6use std::ptr;
7
8use fontconfig_sys::constants::{
9    FC_FAMILY, FC_FILE, FC_FONTFORMAT, FC_INDEX, FC_SLANT, FC_SLANT_ITALIC, FC_SLANT_OBLIQUE,
10    FC_WEIGHT, FC_WEIGHT_BOLD, FC_WEIGHT_EXTRABLACK, FC_WEIGHT_REGULAR, FC_WIDTH,
11    FC_WIDTH_CONDENSED, FC_WIDTH_EXPANDED, FC_WIDTH_EXTRACONDENSED, FC_WIDTH_EXTRAEXPANDED,
12    FC_WIDTH_NORMAL, FC_WIDTH_SEMICONDENSED, FC_WIDTH_SEMIEXPANDED, FC_WIDTH_ULTRACONDENSED,
13    FC_WIDTH_ULTRAEXPANDED,
14};
15use fontconfig_sys::{
16    FcChar8, FcConfigGetCurrent, FcConfigGetFonts, FcConfigSubstitute, FcDefaultSubstitute,
17    FcFontMatch, FcFontSetDestroy, FcFontSetList, FcMatchPattern, FcNameParse, FcObjectSetAdd,
18    FcObjectSetCreate, FcObjectSetDestroy, FcPattern, FcPatternAddString, FcPatternCreate,
19    FcPatternDestroy, FcPatternGetInteger, FcPatternGetString, FcResultMatch, FcSetSystem,
20};
21use fonts_traits::{FontTemplate, FontTemplateDescriptor, LocalFontIdentifier};
22use icu_locid::subtags::language;
23use libc::{c_char, c_int};
24use log::debug;
25use servo_base::text::{UnicodeBlock, UnicodeBlockMethod};
26use style::Atom;
27use style::values::computed::font::GenericFontFamily;
28use style::values::computed::{FontStretch, FontStyle, FontWeight};
29use unicode_script::Script;
30
31use crate::font::map_platform_values_to_style_values;
32use crate::platform::add_noto_fallback_families;
33use crate::{
34    EmojiPresentationPreference, FallbackFontSelectionOptions, FontIdentifier,
35    LowercaseFontFamilyName,
36};
37
38pub(crate) fn for_each_available_family<F>(mut callback: F)
39where
40    F: FnMut(String),
41{
42    unsafe {
43        let config = FcConfigGetCurrent();
44        let font_set = FcConfigGetFonts(config, FcSetSystem);
45        for i in 0..((*font_set).nfont as isize) {
46            let font = (*font_set).fonts.offset(i);
47            let mut family: *mut FcChar8 = ptr::null_mut();
48            let mut format: *mut FcChar8 = ptr::null_mut();
49            let mut v: c_int = 0;
50            if FcPatternGetString(*font, FC_FONTFORMAT.as_ptr() as *mut c_char, v, &mut format) !=
51                FcResultMatch
52            {
53                continue;
54            }
55
56            // Skip bitmap fonts. They aren't supported by FreeType.
57            let fontformat = CStr::from_ptr(format as *const c_char);
58            if !matches!(fontformat.to_bytes(), b"TrueType" | b"CFF" | b"Type 1") {
59                continue;
60            }
61
62            while FcPatternGetString(*font, FC_FAMILY.as_ptr() as *mut c_char, v, &mut family) ==
63                FcResultMatch
64            {
65                let family_name = match CStr::from_ptr(family as *const c_char).to_str() {
66                    Ok(family_name) => family_name,
67                    Err(error) => {
68                        log::error!(
69                            "Ignoring font family because its name contains invalid UTF-8: {error:?}"
70                        );
71                        continue;
72                    },
73                };
74                callback(family_name.to_owned());
75                v += 1;
76            }
77        }
78    }
79}
80
81pub(crate) fn for_each_variation<F>(family_name: &str, mut callback: F)
82where
83    F: FnMut(FontTemplate),
84{
85    unsafe {
86        let config = FcConfigGetCurrent();
87        let mut font_set = FcConfigGetFonts(config, FcSetSystem);
88        let font_set_array_ptr = &mut font_set;
89        let pattern = FcPatternCreate();
90        debug_assert!(!pattern.is_null());
91        if pattern.is_null() {
92            log::error!("Failed to create FcPattern");
93            return;
94        }
95
96        let Ok(family_name_cstr) = CString::new(family_name) else {
97            return;
98        };
99        let ok = FcPatternAddString(
100            pattern,
101            FC_FAMILY.as_ptr() as *mut c_char,
102            family_name_cstr.as_ptr() as *const FcChar8,
103        );
104        debug_assert_ne!(ok, 0);
105        if ok == 0 {
106            log::error!("Failed to create FcPattern");
107            return;
108        }
109
110        let object_set = FcObjectSetCreate();
111        debug_assert!(!object_set.is_null());
112        if object_set.is_null() {
113            log::error!("Failed to create FcObjectSet");
114            return;
115        }
116
117        FcObjectSetAdd(object_set, FC_FILE.as_ptr() as *mut c_char);
118        FcObjectSetAdd(object_set, FC_INDEX.as_ptr() as *mut c_char);
119        FcObjectSetAdd(object_set, FC_WEIGHT.as_ptr() as *mut c_char);
120        FcObjectSetAdd(object_set, FC_SLANT.as_ptr() as *mut c_char);
121        FcObjectSetAdd(object_set, FC_WIDTH.as_ptr() as *mut c_char);
122
123        let matches = FcFontSetList(config, font_set_array_ptr, 1, pattern, object_set);
124        debug!("Found {} variations for {}", (*matches).nfont, family_name);
125
126        for variation_index in 0..((*matches).nfont as isize) {
127            let font = (*matches).fonts.offset(variation_index);
128
129            let mut path: *mut FcChar8 = ptr::null_mut();
130            let result = FcPatternGetString(*font, FC_FILE.as_ptr() as *mut c_char, 0, &mut path);
131            assert_eq!(result, FcResultMatch);
132
133            let mut index: libc::c_int = 0;
134            let result =
135                FcPatternGetInteger(*font, FC_INDEX.as_ptr() as *mut c_char, 0, &mut index);
136            assert_eq!(result, FcResultMatch);
137
138            let Some(weight) = font_weight_from_fontconfig_pattern(*font) else {
139                continue;
140            };
141            let Some(stretch) = font_stretch_from_fontconfig_pattern(*font) else {
142                continue;
143            };
144            let Some(style) = font_style_from_fontconfig_pattern(*font) else {
145                continue;
146            };
147
148            let path = match CStr::from_ptr(path as *const c_char).to_str() {
149                Ok(path) => path,
150                Err(error) => {
151                    log::error!(
152                        "Ignoring font variation from the {family_name:?} family because file path contains invalid UTF-8: {error:?}"
153                    );
154                    continue;
155                },
156            };
157            let local_font_identifier = LocalFontIdentifier {
158                path: Atom::from(path),
159                face_index: (index & 0xFFFF) as u16,
160                named_instance_index: (index >> 16) as u16,
161            };
162            let descriptor = FontTemplateDescriptor::new(weight, stretch, style);
163
164            callback(FontTemplate::new(
165                FontIdentifier::Local(local_font_identifier),
166                descriptor,
167                None,
168            ))
169        }
170
171        FcFontSetDestroy(matches);
172        FcPatternDestroy(pattern);
173        FcObjectSetDestroy(object_set);
174    }
175}
176
177// Based on gfxPlatformGtk::GetCommonFallbackFonts() in Gecko
178pub fn fallback_font_families(options: FallbackFontSelectionOptions) -> Vec<&'static str> {
179    let mut families = Vec::new();
180    if options.presentation_preference == EmojiPresentationPreference::Emoji {
181        families.push("Noto Color Emoji");
182    }
183
184    add_noto_fallback_families(options.clone(), &mut families);
185
186    if let Some(block) = options.character.block() {
187        match block {
188            // In Japanese typography, it is not common to use different fonts
189            // for Kanji(Han), Hiragana, and Katakana within the same document.
190            // We uniformly fallback to Japanese fonts when the document language is Japanese.
191            _ if options.language == language!("ja") => {
192                families.push("TakaoPGothic");
193            },
194            _ if matches!(
195                Script::from(options.character),
196                Script::Bopomofo | Script::Han
197            ) && options.language != language!("ja") =>
198            {
199                families.push("WenQuanYi Micro Hei");
200            },
201            UnicodeBlock::HalfwidthandFullwidthForms |
202            UnicodeBlock::EnclosedIdeographicSupplement => families.push("WenQuanYi Micro Hei"),
203            UnicodeBlock::Hiragana |
204            UnicodeBlock::Katakana |
205            UnicodeBlock::KatakanaPhoneticExtensions => {
206                families.push("TakaoPGothic");
207            },
208            _ => {},
209        }
210    }
211
212    families.push("DejaVu Serif");
213    families.push("FreeSerif");
214    families.push("DejaVu Sans");
215    families.push("DejaVu Sans Mono");
216    families.push("FreeSans");
217    families.push("Symbola");
218    families.push("Droid Sans Fallback");
219
220    families
221}
222
223pub(crate) fn default_system_generic_font_family(
224    generic: GenericFontFamily,
225) -> LowercaseFontFamilyName {
226    let generic_string = match generic {
227        GenericFontFamily::None | GenericFontFamily::Serif => c"serif",
228        GenericFontFamily::SansSerif => c"sans-serif",
229        GenericFontFamily::Monospace => c"monospace",
230        GenericFontFamily::Cursive => c"cursive",
231        GenericFontFamily::Fantasy => c"fantasy",
232        GenericFontFamily::SystemUi => c"sans-serif",
233    };
234
235    let generic_name_ptr = generic_string.as_ptr();
236    unsafe {
237        let pattern = FcNameParse(generic_name_ptr as *mut FcChar8);
238        FcConfigSubstitute(ptr::null_mut(), pattern, FcMatchPattern);
239        FcDefaultSubstitute(pattern);
240
241        let mut result = 0;
242        let family_match = FcFontMatch(ptr::null_mut(), pattern, &mut result);
243
244        if result == FcResultMatch {
245            let mut match_string: *mut FcChar8 = ptr::null_mut();
246            FcPatternGetString(
247                family_match,
248                FC_FAMILY.as_ptr() as *mut c_char,
249                0,
250                &mut match_string,
251            );
252            let family_name = CStr::from_ptr(match_string as *const c_char)
253                .to_str()
254                .expect("Font family name contains invalid UTF-8")
255                .to_owned();
256
257            FcPatternDestroy(family_match);
258            FcPatternDestroy(pattern);
259
260            return family_name.into();
261        }
262
263        FcPatternDestroy(family_match);
264        FcPatternDestroy(pattern);
265    }
266
267    match generic {
268        GenericFontFamily::None | GenericFontFamily::Serif => "Noto Serif",
269        GenericFontFamily::SansSerif => "Noto Sans",
270        GenericFontFamily::Monospace => "Deja Vu Sans Mono",
271        GenericFontFamily::Cursive => "Comic Sans MS",
272        GenericFontFamily::Fantasy => "Impact",
273        GenericFontFamily::SystemUi => "Noto Sans",
274    }
275    .into()
276}
277
278fn font_style_from_fontconfig_pattern(pattern: *mut FcPattern) -> Option<FontStyle> {
279    let mut slant: c_int = 0;
280    unsafe {
281        if FcResultMatch != FcPatternGetInteger(pattern, FC_SLANT.as_ptr(), 0, &mut slant) {
282            return None;
283        }
284    }
285    Some(match slant {
286        FC_SLANT_ITALIC => FontStyle::ITALIC,
287        FC_SLANT_OBLIQUE => FontStyle::OBLIQUE,
288        _ => FontStyle::NORMAL,
289    })
290}
291
292fn font_stretch_from_fontconfig_pattern(pattern: *mut FcPattern) -> Option<FontStretch> {
293    let mut width: c_int = 0;
294    unsafe {
295        if FcResultMatch != FcPatternGetInteger(pattern, FC_WIDTH.as_ptr(), 0, &mut width) {
296            return None;
297        }
298    }
299    let mapping = [
300        (FC_WIDTH_ULTRACONDENSED as f64, 0.5),
301        (FC_WIDTH_EXTRACONDENSED as f64, 0.625),
302        (FC_WIDTH_CONDENSED as f64, 0.75),
303        (FC_WIDTH_SEMICONDENSED as f64, 0.875),
304        (FC_WIDTH_NORMAL as f64, 1.0),
305        (FC_WIDTH_SEMIEXPANDED as f64, 1.125),
306        (FC_WIDTH_EXPANDED as f64, 1.25),
307        (FC_WIDTH_EXTRAEXPANDED as f64, 1.50),
308        (FC_WIDTH_ULTRAEXPANDED as f64, 2.00),
309    ];
310
311    let mapped_width = map_platform_values_to_style_values(&mapping, width as f64);
312    Some(FontStretch::from_percentage(mapped_width as f32))
313}
314
315fn font_weight_from_fontconfig_pattern(pattern: *mut FcPattern) -> Option<FontWeight> {
316    let mut weight: c_int = 0;
317    unsafe {
318        let result = FcPatternGetInteger(pattern, FC_WEIGHT.as_ptr(), 0, &mut weight);
319        if result != FcResultMatch {
320            return None;
321        }
322    }
323
324    let mapping = [
325        (0., 0.),
326        (FC_WEIGHT_REGULAR as f64, 400_f64),
327        (FC_WEIGHT_BOLD as f64, 700_f64),
328        (FC_WEIGHT_EXTRABLACK as f64, 1000_f64),
329    ];
330
331    let mapped_weight = map_platform_values_to_style_values(&mapping, weight as f64);
332    Some(FontWeight::from_float(mapped_weight as f32))
333}