1use 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 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
178pub 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 _ 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}