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                None,
169            ))
170        }
171
172        FcFontSetDestroy(matches);
173        FcPatternDestroy(pattern);
174        FcObjectSetDestroy(object_set);
175    }
176}
177
178// Based on gfxPlatformGtk::GetCommonFallbackFonts() in Gecko
179pub fn fallback_font_families(options: FallbackFontSelectionOptions) -> Vec<&'static str> {
180    let mut families = Vec::new();
181    if options.presentation_preference == EmojiPresentationPreference::Emoji {
182        families.push("Noto Color Emoji");
183    }
184
185    add_noto_fallback_families(options.clone(), &mut families);
186
187    if let Some(block) = options.character.block() {
188        match block {
189            // In Japanese typography, it is not common to use different fonts
190            // for Kanji(Han), Hiragana, and Katakana within the same document.
191            // We uniformly fallback to Japanese fonts when the document language is Japanese.
192            _ if options.language == language!("ja") => {
193                families.push("TakaoPGothic");
194            },
195            _ if matches!(
196                Script::from(options.character),
197                Script::Bopomofo | Script::Han
198            ) && options.language != language!("ja") =>
199            {
200                families.push("WenQuanYi Micro Hei");
201            },
202            UnicodeBlock::HalfwidthandFullwidthForms |
203            UnicodeBlock::EnclosedIdeographicSupplement => families.push("WenQuanYi Micro Hei"),
204            UnicodeBlock::Hiragana |
205            UnicodeBlock::Katakana |
206            UnicodeBlock::KatakanaPhoneticExtensions => {
207                families.push("TakaoPGothic");
208            },
209            _ => {},
210        }
211    }
212
213    families.push("DejaVu Serif");
214    families.push("FreeSerif");
215    families.push("DejaVu Sans");
216    families.push("DejaVu Sans Mono");
217    families.push("FreeSans");
218    families.push("Symbola");
219    families.push("Droid Sans Fallback");
220
221    families
222}
223
224pub(crate) fn default_system_generic_font_family(
225    generic: GenericFontFamily,
226) -> LowercaseFontFamilyName {
227    let generic_string = match generic {
228        GenericFontFamily::None | GenericFontFamily::Serif => c"serif",
229        GenericFontFamily::SansSerif => c"sans-serif",
230        GenericFontFamily::Monospace => c"monospace",
231        GenericFontFamily::Cursive => c"cursive",
232        GenericFontFamily::Fantasy => c"fantasy",
233        GenericFontFamily::SystemUi => c"sans-serif",
234    };
235
236    let generic_name_ptr = generic_string.as_ptr();
237    unsafe {
238        let pattern = FcNameParse(generic_name_ptr as *mut FcChar8);
239        FcConfigSubstitute(ptr::null_mut(), pattern, FcMatchPattern);
240        FcDefaultSubstitute(pattern);
241
242        let mut result = 0;
243        let family_match = FcFontMatch(ptr::null_mut(), pattern, &mut result);
244
245        if result == FcResultMatch {
246            let mut match_string: *mut FcChar8 = ptr::null_mut();
247            FcPatternGetString(
248                family_match,
249                FC_FAMILY.as_ptr() as *mut c_char,
250                0,
251                &mut match_string,
252            );
253            let family_name = CStr::from_ptr(match_string as *const c_char)
254                .to_str()
255                .expect("Font family name contains invalid UTF-8")
256                .to_owned();
257
258            FcPatternDestroy(family_match);
259            FcPatternDestroy(pattern);
260
261            return family_name.into();
262        }
263
264        FcPatternDestroy(family_match);
265        FcPatternDestroy(pattern);
266    }
267
268    match generic {
269        GenericFontFamily::None | GenericFontFamily::Serif => "Noto Serif",
270        GenericFontFamily::SansSerif => "Noto Sans",
271        GenericFontFamily::Monospace => "Deja Vu Sans Mono",
272        GenericFontFamily::Cursive => "Comic Sans MS",
273        GenericFontFamily::Fantasy => "Impact",
274        GenericFontFamily::SystemUi => "Noto Sans",
275    }
276    .into()
277}
278
279fn font_style_from_fontconfig_pattern(pattern: *mut FcPattern) -> Option<FontStyle> {
280    let mut slant: c_int = 0;
281    unsafe {
282        if FcResultMatch != FcPatternGetInteger(pattern, FC_SLANT.as_ptr(), 0, &mut slant) {
283            return None;
284        }
285    }
286    Some(match slant {
287        FC_SLANT_ITALIC => FontStyle::ITALIC,
288        FC_SLANT_OBLIQUE => FontStyle::OBLIQUE,
289        _ => FontStyle::NORMAL,
290    })
291}
292
293fn font_stretch_from_fontconfig_pattern(pattern: *mut FcPattern) -> Option<FontStretch> {
294    let mut width: c_int = 0;
295    unsafe {
296        if FcResultMatch != FcPatternGetInteger(pattern, FC_WIDTH.as_ptr(), 0, &mut width) {
297            return None;
298        }
299    }
300    let mapping = [
301        (FC_WIDTH_ULTRACONDENSED as f64, 0.5),
302        (FC_WIDTH_EXTRACONDENSED as f64, 0.625),
303        (FC_WIDTH_CONDENSED as f64, 0.75),
304        (FC_WIDTH_SEMICONDENSED as f64, 0.875),
305        (FC_WIDTH_NORMAL as f64, 1.0),
306        (FC_WIDTH_SEMIEXPANDED as f64, 1.125),
307        (FC_WIDTH_EXPANDED as f64, 1.25),
308        (FC_WIDTH_EXTRAEXPANDED as f64, 1.50),
309        (FC_WIDTH_ULTRAEXPANDED as f64, 2.00),
310    ];
311
312    let mapped_width = map_platform_values_to_style_values(&mapping, width as f64);
313    Some(FontStretch::from_percentage(mapped_width as f32))
314}
315
316fn font_weight_from_fontconfig_pattern(pattern: *mut FcPattern) -> Option<FontWeight> {
317    let mut weight: c_int = 0;
318    unsafe {
319        let result = FcPatternGetInteger(pattern, FC_WEIGHT.as_ptr(), 0, &mut weight);
320        if result != FcResultMatch {
321            return None;
322        }
323    }
324
325    let mapping = [
326        (0., 0.),
327        (FC_WEIGHT_REGULAR as f64, 400_f64),
328        (FC_WEIGHT_BOLD as f64, 700_f64),
329        (FC_WEIGHT_EXTRABLACK as f64, 1000_f64),
330    ];
331
332    let mapped_weight = map_platform_values_to_style_values(&mapping, weight as f64);
333    Some(FontWeight::from_float(mapped_weight as f32))
334}