1#![allow(non_snake_case)]
68
69extern crate alloc;
77
78use alloc::collections::btree_map::BTreeMap;
79use alloc::string::{String, ToString};
80use alloc::vec::Vec;
81#[cfg(all(feature = "std", feature = "parsing"))]
82use allsorts::binary::read::ReadScope;
83#[cfg(all(feature = "std", feature = "parsing"))]
84use allsorts::get_name::fontcode_get_name;
85#[cfg(all(feature = "std", feature = "parsing"))]
86use allsorts::tables::os2::Os2;
87#[cfg(all(feature = "std", feature = "parsing"))]
88use allsorts::tables::{FontTableProvider, HheaTable, HmtxTable, MaxpTable};
89#[cfg(all(feature = "std", feature = "parsing"))]
90use allsorts::tag;
91#[cfg(feature = "std")]
92use std::path::PathBuf;
93
94pub mod utils;
95#[cfg(feature = "std")]
96pub mod config;
97
98#[cfg(feature = "ffi")]
99pub mod ffi;
100
101#[cfg(feature = "async-registry")]
102pub mod scoring;
103#[cfg(feature = "async-registry")]
104pub mod registry;
105#[cfg(feature = "async-registry")]
106pub mod multithread;
107#[cfg(feature = "cache")]
108pub mod disk_cache;
109
110#[cfg(all(target_os = "ios", feature = "std", feature = "parsing"))]
111mod mobile_ios;
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
115pub enum OperatingSystem {
116 Windows,
117 Linux,
118 MacOS,
119 IOS,
120 Android,
121 Wasm,
122}
123
124impl OperatingSystem {
125 pub fn current() -> Self {
127 #[cfg(target_os = "windows")]
128 return OperatingSystem::Windows;
129
130 #[cfg(target_os = "linux")]
131 return OperatingSystem::Linux;
132
133 #[cfg(target_os = "macos")]
134 return OperatingSystem::MacOS;
135
136 #[cfg(target_os = "ios")]
137 return OperatingSystem::IOS;
138
139 #[cfg(target_os = "android")]
140 return OperatingSystem::Android;
141
142 #[cfg(target_family = "wasm")]
143 return OperatingSystem::Wasm;
144
145 #[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos", target_os = "ios", target_os = "android", target_family = "wasm")))]
146 return OperatingSystem::Linux; }
148
149 pub fn get_serif_fonts(&self, unicode_ranges: &[UnicodeRange]) -> Vec<String> {
152 let has_cjk = has_cjk_ranges(unicode_ranges);
153 let has_arabic = has_arabic_ranges(unicode_ranges);
154 let _has_cyrillic = has_cyrillic_ranges(unicode_ranges);
155
156 match self {
157 OperatingSystem::Windows => {
158 let mut fonts = Vec::new();
159 if has_cjk {
160 fonts.extend_from_slice(&["MS Mincho", "SimSun", "MingLiU"]);
161 }
162 if has_arabic {
163 fonts.push("Traditional Arabic");
164 }
165 fonts.push("Times New Roman");
166 fonts.iter().map(|s| s.to_string()).collect()
167 }
168 OperatingSystem::Linux => {
169 let mut fonts = Vec::new();
170 if has_cjk {
171 fonts.extend_from_slice(&["Noto Serif CJK SC", "Noto Serif CJK JP", "Noto Serif CJK KR"]);
172 }
173 if has_arabic {
174 fonts.push("Noto Serif Arabic");
175 }
176 fonts.extend_from_slice(&[
177 "Times", "Times New Roman", "DejaVu Serif", "Free Serif",
178 "Noto Serif", "Bitstream Vera Serif", "Roman", "Regular"
179 ]);
180 fonts.iter().map(|s| s.to_string()).collect()
181 }
182 OperatingSystem::MacOS | OperatingSystem::IOS => {
183 let mut fonts = Vec::new();
184 if has_cjk {
185 fonts.extend_from_slice(&["Hiragino Mincho ProN", "STSong", "AppleMyungjo"]);
186 }
187 if has_arabic {
188 fonts.push("Geeza Pro");
189 }
190 fonts.extend_from_slice(&["Times New Roman", "Times", "New York", "Palatino"]);
191 fonts.iter().map(|s| s.to_string()).collect()
192 }
193 OperatingSystem::Android => {
194 let mut fonts = Vec::new();
195 if has_cjk {
196 fonts.extend_from_slice(&["Noto Serif CJK SC", "Noto Serif CJK JP", "Noto Serif CJK KR"]);
197 }
198 if has_arabic {
199 fonts.push("Noto Naskh Arabic");
200 }
201 fonts.extend_from_slice(&["Noto Serif", "Roboto Serif", "Droid Serif"]);
202 fonts.iter().map(|s| s.to_string()).collect()
203 }
204 OperatingSystem::Wasm => Vec::new(),
205 }
206 }
207
208 pub fn get_sans_serif_fonts(&self, unicode_ranges: &[UnicodeRange]) -> Vec<String> {
211 let has_cjk = has_cjk_ranges(unicode_ranges);
212 let has_arabic = has_arabic_ranges(unicode_ranges);
213 let _has_cyrillic = has_cyrillic_ranges(unicode_ranges);
214 let has_hebrew = has_hebrew_ranges(unicode_ranges);
215 let has_thai = has_thai_ranges(unicode_ranges);
216
217 match self {
218 OperatingSystem::Windows => {
219 let mut fonts = Vec::new();
220 if has_cjk {
221 fonts.extend_from_slice(&["Microsoft YaHei", "MS Gothic", "Malgun Gothic", "SimHei"]);
222 }
223 if has_arabic {
224 fonts.push("Segoe UI Arabic");
225 }
226 if has_hebrew {
227 fonts.push("Segoe UI Hebrew");
228 }
229 if has_thai {
230 fonts.push("Leelawadee UI");
231 }
232 fonts.extend_from_slice(&["Segoe UI", "Tahoma", "Microsoft Sans Serif", "MS Sans Serif", "Helv"]);
233 fonts.iter().map(|s| s.to_string()).collect()
234 }
235 OperatingSystem::Linux => {
236 let mut fonts = Vec::new();
237 if has_cjk {
238 fonts.extend_from_slice(&[
239 "Noto Sans CJK SC", "Noto Sans CJK JP", "Noto Sans CJK KR",
240 "WenQuanYi Micro Hei", "Droid Sans Fallback"
241 ]);
242 }
243 if has_arabic {
244 fonts.push("Noto Sans Arabic");
245 }
246 if has_hebrew {
247 fonts.push("Noto Sans Hebrew");
248 }
249 if has_thai {
250 fonts.push("Noto Sans Thai");
251 }
252 fonts.extend_from_slice(&["Ubuntu", "Arial", "DejaVu Sans", "Noto Sans", "Liberation Sans"]);
253 fonts.iter().map(|s| s.to_string()).collect()
254 }
255 OperatingSystem::MacOS | OperatingSystem::IOS => {
256 let mut fonts = Vec::new();
257 if has_cjk {
258 fonts.extend_from_slice(&[
259 "Hiragino Sans", "Hiragino Kaku Gothic ProN",
260 "PingFang SC", "PingFang TC", "Apple SD Gothic Neo"
261 ]);
262 }
263 if has_arabic {
264 fonts.push("Geeza Pro");
265 }
266 if has_hebrew {
267 fonts.push("Arial Hebrew");
268 }
269 if has_thai {
270 fonts.push("Thonburi");
271 }
272 fonts.extend_from_slice(&[
273 "San Francisco", ".AppleSystemUIFont", ".SFUIText", ".SFUI-Regular",
274 "Helvetica Neue", "Helvetica", "Lucida Grande",
275 ]);
276 fonts.iter().map(|s| s.to_string()).collect()
277 }
278 OperatingSystem::Android => {
279 let mut fonts = Vec::new();
280 if has_cjk {
281 fonts.extend_from_slice(&[
282 "Noto Sans CJK SC", "Noto Sans CJK JP", "Noto Sans CJK KR",
283 "Droid Sans Fallback",
284 ]);
285 }
286 if has_arabic {
287 fonts.push("Noto Sans Arabic");
288 }
289 if has_hebrew {
290 fonts.push("Noto Sans Hebrew");
291 }
292 if has_thai {
293 fonts.push("Noto Sans Thai");
294 }
295 fonts.extend_from_slice(&[
296 "Roboto", "Roboto-Regular", "Noto Sans", "Droid Sans",
297 ]);
298 fonts.iter().map(|s| s.to_string()).collect()
299 }
300 OperatingSystem::Wasm => Vec::new(),
301 }
302 }
303
304 pub fn get_monospace_fonts(&self, unicode_ranges: &[UnicodeRange]) -> Vec<String> {
307 let has_cjk = has_cjk_ranges(unicode_ranges);
308
309 match self {
310 OperatingSystem::Windows => {
311 let mut fonts = Vec::new();
312 if has_cjk {
313 fonts.extend_from_slice(&["MS Gothic", "SimHei"]);
314 }
315 fonts.extend_from_slice(&["Segoe UI Mono", "Courier New", "Cascadia Code", "Cascadia Mono", "Consolas"]);
316 fonts.iter().map(|s| s.to_string()).collect()
317 }
318 OperatingSystem::Linux => {
319 let mut fonts = Vec::new();
320 if has_cjk {
321 fonts.extend_from_slice(&["Noto Sans Mono CJK SC", "Noto Sans Mono CJK JP", "WenQuanYi Zen Hei Mono"]);
322 }
323 fonts.extend_from_slice(&[
324 "Source Code Pro", "Cantarell", "DejaVu Sans Mono",
325 "Roboto Mono", "Ubuntu Monospace", "Droid Sans Mono"
326 ]);
327 fonts.iter().map(|s| s.to_string()).collect()
328 }
329 OperatingSystem::MacOS | OperatingSystem::IOS => {
330 let mut fonts = Vec::new();
331 if has_cjk {
332 fonts.extend_from_slice(&["Hiragino Sans", "PingFang SC"]);
333 }
334 fonts.extend_from_slice(&["SF Mono", "Menlo", "Monaco", "Courier", "Oxygen Mono", "Source Code Pro", "Fira Mono"]);
335 fonts.iter().map(|s| s.to_string()).collect()
336 }
337 OperatingSystem::Android => {
338 let mut fonts = Vec::new();
339 if has_cjk {
340 fonts.extend_from_slice(&["Noto Sans Mono CJK SC", "Noto Sans Mono CJK JP"]);
341 }
342 fonts.extend_from_slice(&["Roboto Mono", "Droid Sans Mono", "Noto Sans Mono", "DejaVu Sans Mono"]);
343 fonts.iter().map(|s| s.to_string()).collect()
344 }
345 OperatingSystem::Wasm => Vec::new(),
346 }
347 }
348
349 pub fn expand_generic_family(&self, family: &str, unicode_ranges: &[UnicodeRange]) -> Vec<String> {
353 match family.to_ascii_lowercase().as_str() {
354 "serif" => self.get_serif_fonts(unicode_ranges),
355 "sans-serif" => self.get_sans_serif_fonts(unicode_ranges),
356 "monospace" => self.get_monospace_fonts(unicode_ranges),
357 "cursive" | "fantasy" | "system-ui" => {
358 self.get_sans_serif_fonts(unicode_ranges)
360 }
361 _ => vec![family.to_string()],
362 }
363 }
364}
365
366pub fn expand_font_families(families: &[String], os: OperatingSystem, unicode_ranges: &[UnicodeRange]) -> Vec<String> {
376 let mut expanded = Vec::new();
377
378 for family in families {
379 expanded.extend(os.expand_generic_family(family, unicode_ranges));
380 }
381
382 expanded
383}
384
385#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
387#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
388pub struct FontId(pub u128);
389
390impl core::fmt::Debug for FontId {
391 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
392 core::fmt::Display::fmt(self, f)
393 }
394}
395
396impl core::fmt::Display for FontId {
397 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
398 let id = self.0;
399 write!(
400 f,
401 "{:08x}-{:04x}-{:04x}-{:04x}-{:012x}",
402 (id >> 96) & 0xFFFFFFFF,
403 (id >> 80) & 0xFFFF,
404 (id >> 64) & 0xFFFF,
405 (id >> 48) & 0xFFFF,
406 id & 0xFFFFFFFFFFFF
407 )
408 }
409}
410
411impl FontId {
412 pub fn new() -> Self {
414 use core::sync::atomic::{AtomicU64, Ordering};
415 static COUNTER: AtomicU64 = AtomicU64::new(1);
416 let id = COUNTER.fetch_add(1, Ordering::Relaxed) as u128;
417 FontId(id)
418 }
419}
420
421#[derive(Debug, Default, Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
423#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
424#[repr(C)]
425pub enum PatternMatch {
426 #[default]
428 DontCare,
429 True,
431 False,
433}
434
435impl PatternMatch {
436 fn needs_to_match(&self) -> bool {
437 matches!(self, PatternMatch::True | PatternMatch::False)
438 }
439
440 fn matches(&self, other: &PatternMatch) -> bool {
441 match (self, other) {
442 (PatternMatch::DontCare, _) => true,
443 (_, PatternMatch::DontCare) => true,
444 (a, b) => a == b,
445 }
446 }
447}
448
449#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
451#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
452#[repr(C)]
453pub enum FcWeight {
454 Thin = 100,
455 ExtraLight = 200,
456 Light = 300,
457 Normal = 400,
458 Medium = 500,
459 SemiBold = 600,
460 Bold = 700,
461 ExtraBold = 800,
462 Black = 900,
463}
464
465impl FcWeight {
466 pub fn from_u16(weight: u16) -> Self {
467 match weight {
468 0..=149 => FcWeight::Thin,
469 150..=249 => FcWeight::ExtraLight,
470 250..=349 => FcWeight::Light,
471 350..=449 => FcWeight::Normal,
472 450..=549 => FcWeight::Medium,
473 550..=649 => FcWeight::SemiBold,
474 650..=749 => FcWeight::Bold,
475 750..=849 => FcWeight::ExtraBold,
476 _ => FcWeight::Black,
477 }
478 }
479
480 pub fn find_best_match(&self, available: &[FcWeight]) -> Option<FcWeight> {
481 if available.is_empty() {
482 return None;
483 }
484
485 if available.contains(self) {
487 return Some(*self);
488 }
489
490 let self_value = *self as u16;
492
493 match *self {
494 FcWeight::Normal => {
495 if available.contains(&FcWeight::Medium) {
497 return Some(FcWeight::Medium);
498 }
499 for weight in &[FcWeight::Light, FcWeight::ExtraLight, FcWeight::Thin] {
501 if available.contains(weight) {
502 return Some(*weight);
503 }
504 }
505 for weight in &[
507 FcWeight::SemiBold,
508 FcWeight::Bold,
509 FcWeight::ExtraBold,
510 FcWeight::Black,
511 ] {
512 if available.contains(weight) {
513 return Some(*weight);
514 }
515 }
516 }
517 FcWeight::Medium => {
518 if available.contains(&FcWeight::Normal) {
520 return Some(FcWeight::Normal);
521 }
522 for weight in &[FcWeight::Light, FcWeight::ExtraLight, FcWeight::Thin] {
524 if available.contains(weight) {
525 return Some(*weight);
526 }
527 }
528 for weight in &[
530 FcWeight::SemiBold,
531 FcWeight::Bold,
532 FcWeight::ExtraBold,
533 FcWeight::Black,
534 ] {
535 if available.contains(weight) {
536 return Some(*weight);
537 }
538 }
539 }
540 FcWeight::Thin | FcWeight::ExtraLight | FcWeight::Light => {
541 let mut best_match = None;
543 let mut smallest_diff = u16::MAX;
544
545 for weight in available {
547 let weight_value = *weight as u16;
548 if weight_value <= self_value {
550 let diff = self_value - weight_value;
551 if diff < smallest_diff {
552 smallest_diff = diff;
553 best_match = Some(*weight);
554 }
555 }
556 }
557
558 if best_match.is_some() {
559 return best_match;
560 }
561
562 best_match = None;
564 smallest_diff = u16::MAX;
565
566 for weight in available {
567 let weight_value = *weight as u16;
568 if weight_value > self_value {
569 let diff = weight_value - self_value;
570 if diff < smallest_diff {
571 smallest_diff = diff;
572 best_match = Some(*weight);
573 }
574 }
575 }
576
577 return best_match;
578 }
579 FcWeight::SemiBold | FcWeight::Bold | FcWeight::ExtraBold | FcWeight::Black => {
580 let mut best_match = None;
582 let mut smallest_diff = u16::MAX;
583
584 for weight in available {
586 let weight_value = *weight as u16;
587 if weight_value >= self_value {
589 let diff = weight_value - self_value;
590 if diff < smallest_diff {
591 smallest_diff = diff;
592 best_match = Some(*weight);
593 }
594 }
595 }
596
597 if best_match.is_some() {
598 return best_match;
599 }
600
601 best_match = None;
603 smallest_diff = u16::MAX;
604
605 for weight in available {
606 let weight_value = *weight as u16;
607 if weight_value < self_value {
608 let diff = self_value - weight_value;
609 if diff < smallest_diff {
610 smallest_diff = diff;
611 best_match = Some(*weight);
612 }
613 }
614 }
615
616 return best_match;
617 }
618 }
619
620 Some(available[0])
622 }
623}
624
625impl Default for FcWeight {
626 fn default() -> Self {
627 FcWeight::Normal
628 }
629}
630
631#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
633#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
634#[repr(C)]
635pub enum FcStretch {
636 UltraCondensed = 1,
637 ExtraCondensed = 2,
638 Condensed = 3,
639 SemiCondensed = 4,
640 Normal = 5,
641 SemiExpanded = 6,
642 Expanded = 7,
643 ExtraExpanded = 8,
644 UltraExpanded = 9,
645}
646
647impl FcStretch {
648 pub fn is_condensed(&self) -> bool {
649 use self::FcStretch::*;
650 match self {
651 UltraCondensed => true,
652 ExtraCondensed => true,
653 Condensed => true,
654 SemiCondensed => true,
655 Normal => false,
656 SemiExpanded => false,
657 Expanded => false,
658 ExtraExpanded => false,
659 UltraExpanded => false,
660 }
661 }
662 pub fn from_u16(width_class: u16) -> Self {
663 match width_class {
664 1 => FcStretch::UltraCondensed,
665 2 => FcStretch::ExtraCondensed,
666 3 => FcStretch::Condensed,
667 4 => FcStretch::SemiCondensed,
668 5 => FcStretch::Normal,
669 6 => FcStretch::SemiExpanded,
670 7 => FcStretch::Expanded,
671 8 => FcStretch::ExtraExpanded,
672 9 => FcStretch::UltraExpanded,
673 _ => FcStretch::Normal,
674 }
675 }
676
677 pub fn find_best_match(&self, available: &[FcStretch]) -> Option<FcStretch> {
679 if available.is_empty() {
680 return None;
681 }
682
683 if available.contains(self) {
684 return Some(*self);
685 }
686
687 if *self <= FcStretch::Normal {
689 let mut closest_narrower = None;
691 for stretch in available.iter() {
692 if *stretch < *self
693 && (closest_narrower.is_none() || *stretch > closest_narrower.unwrap())
694 {
695 closest_narrower = Some(*stretch);
696 }
697 }
698
699 if closest_narrower.is_some() {
700 return closest_narrower;
701 }
702
703 let mut closest_wider = None;
705 for stretch in available.iter() {
706 if *stretch > *self
707 && (closest_wider.is_none() || *stretch < closest_wider.unwrap())
708 {
709 closest_wider = Some(*stretch);
710 }
711 }
712
713 return closest_wider;
714 } else {
715 let mut closest_wider = None;
717 for stretch in available.iter() {
718 if *stretch > *self
719 && (closest_wider.is_none() || *stretch < closest_wider.unwrap())
720 {
721 closest_wider = Some(*stretch);
722 }
723 }
724
725 if closest_wider.is_some() {
726 return closest_wider;
727 }
728
729 let mut closest_narrower = None;
731 for stretch in available.iter() {
732 if *stretch < *self
733 && (closest_narrower.is_none() || *stretch > closest_narrower.unwrap())
734 {
735 closest_narrower = Some(*stretch);
736 }
737 }
738
739 return closest_narrower;
740 }
741 }
742}
743
744impl Default for FcStretch {
745 fn default() -> Self {
746 FcStretch::Normal
747 }
748}
749
750#[repr(C)]
752#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
753#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
754pub struct UnicodeRange {
755 pub start: u32,
756 pub end: u32,
757}
758
759pub const DEFAULT_UNICODE_FALLBACK_SCRIPTS: &[UnicodeRange] = &[
768 UnicodeRange { start: 0x0400, end: 0x04FF }, UnicodeRange { start: 0x0600, end: 0x06FF }, UnicodeRange { start: 0x0900, end: 0x097F }, UnicodeRange { start: 0x3040, end: 0x309F }, UnicodeRange { start: 0x30A0, end: 0x30FF }, UnicodeRange { start: 0x4E00, end: 0x9FFF }, UnicodeRange { start: 0xAC00, end: 0xD7A3 }, ];
776
777impl UnicodeRange {
778 pub fn contains(&self, c: char) -> bool {
779 let c = c as u32;
780 c >= self.start && c <= self.end
781 }
782
783 pub fn overlaps(&self, other: &UnicodeRange) -> bool {
784 self.start <= other.end && other.start <= self.end
785 }
786
787 pub fn is_subset_of(&self, other: &UnicodeRange) -> bool {
788 self.start >= other.start && self.end <= other.end
789 }
790}
791
792pub fn has_cjk_ranges(ranges: &[UnicodeRange]) -> bool {
794 ranges.iter().any(|r| {
795 (r.start >= 0x4E00 && r.start <= 0x9FFF) ||
796 (r.start >= 0x3040 && r.start <= 0x309F) ||
797 (r.start >= 0x30A0 && r.start <= 0x30FF) ||
798 (r.start >= 0xAC00 && r.start <= 0xD7AF)
799 })
800}
801
802pub fn has_arabic_ranges(ranges: &[UnicodeRange]) -> bool {
804 ranges.iter().any(|r| r.start >= 0x0600 && r.start <= 0x06FF)
805}
806
807pub fn has_cyrillic_ranges(ranges: &[UnicodeRange]) -> bool {
809 ranges.iter().any(|r| r.start >= 0x0400 && r.start <= 0x04FF)
810}
811
812pub fn has_hebrew_ranges(ranges: &[UnicodeRange]) -> bool {
814 ranges.iter().any(|r| r.start >= 0x0590 && r.start <= 0x05FF)
815}
816
817pub fn has_thai_ranges(ranges: &[UnicodeRange]) -> bool {
819 ranges.iter().any(|r| r.start >= 0x0E00 && r.start <= 0x0E7F)
820}
821
822#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
824pub enum TraceLevel {
825 Debug,
826 Info,
827 Warning,
828 Error,
829}
830
831#[derive(Debug, Clone, PartialEq, Eq, Hash)]
833pub enum MatchReason {
834 NameMismatch {
835 requested: Option<String>,
836 found: Option<String>,
837 },
838 FamilyMismatch {
839 requested: Option<String>,
840 found: Option<String>,
841 },
842 StyleMismatch {
843 property: &'static str,
844 requested: String,
845 found: String,
846 },
847 WeightMismatch {
848 requested: FcWeight,
849 found: FcWeight,
850 },
851 StretchMismatch {
852 requested: FcStretch,
853 found: FcStretch,
854 },
855 UnicodeRangeMismatch {
856 character: char,
857 ranges: Vec<UnicodeRange>,
858 },
859 Success,
860}
861
862#[derive(Debug, Clone, PartialEq, Eq)]
864pub struct TraceMsg {
865 pub level: TraceLevel,
866 pub path: String,
867 pub reason: MatchReason,
868}
869
870#[repr(C)]
872#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
873#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
874pub enum FcHintStyle {
875 #[default]
876 None = 0,
877 Slight = 1,
878 Medium = 2,
879 Full = 3,
880}
881
882#[repr(C)]
884#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
885#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
886pub enum FcRgba {
887 #[default]
888 Unknown = 0,
889 Rgb = 1,
890 Bgr = 2,
891 Vrgb = 3,
892 Vbgr = 4,
893 None = 5,
894}
895
896#[repr(C)]
898#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
899#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
900pub enum FcLcdFilter {
901 #[default]
902 None = 0,
903 Default = 1,
904 Light = 2,
905 Legacy = 3,
906}
907
908#[derive(Debug, Default, Clone, PartialEq, PartialOrd)]
913#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
914pub struct FcFontRenderConfig {
915 pub antialias: Option<bool>,
916 pub hinting: Option<bool>,
917 pub hintstyle: Option<FcHintStyle>,
918 pub autohint: Option<bool>,
919 pub rgba: Option<FcRgba>,
920 pub lcdfilter: Option<FcLcdFilter>,
921 pub embeddedbitmap: Option<bool>,
922 pub embolden: Option<bool>,
923 pub dpi: Option<f64>,
924 pub scale: Option<f64>,
925 pub minspace: Option<bool>,
926}
927
928impl Eq for FcFontRenderConfig {}
931
932impl Ord for FcFontRenderConfig {
933 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
934 let ord = self.antialias.cmp(&other.antialias)
936 .then_with(|| self.hinting.cmp(&other.hinting))
937 .then_with(|| self.hintstyle.cmp(&other.hintstyle))
938 .then_with(|| self.autohint.cmp(&other.autohint))
939 .then_with(|| self.rgba.cmp(&other.rgba))
940 .then_with(|| self.lcdfilter.cmp(&other.lcdfilter))
941 .then_with(|| self.embeddedbitmap.cmp(&other.embeddedbitmap))
942 .then_with(|| self.embolden.cmp(&other.embolden))
943 .then_with(|| self.minspace.cmp(&other.minspace));
944
945 let ord = ord.then_with(|| {
947 let a = self.dpi.map(|v| v.to_bits());
948 let b = other.dpi.map(|v| v.to_bits());
949 a.cmp(&b)
950 });
951 ord.then_with(|| {
952 let a = self.scale.map(|v| v.to_bits());
953 let b = other.scale.map(|v| v.to_bits());
954 a.cmp(&b)
955 })
956 }
957}
958
959#[derive(Default, Clone, PartialOrd, Ord, PartialEq, Eq)]
961#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
962#[repr(C)]
963pub struct FcPattern {
964 pub name: Option<String>,
966 pub family: Option<String>,
968 pub italic: PatternMatch,
970 pub oblique: PatternMatch,
972 pub bold: PatternMatch,
974 pub monospace: PatternMatch,
976 pub condensed: PatternMatch,
978 pub weight: FcWeight,
980 pub stretch: FcStretch,
982 pub unicode_ranges: Vec<UnicodeRange>,
984 pub metadata: FcFontMetadata,
986 pub render_config: FcFontRenderConfig,
988}
989
990impl core::fmt::Debug for FcPattern {
991 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
992 let mut d = f.debug_struct("FcPattern");
993
994 if let Some(name) = &self.name {
995 d.field("name", name);
996 }
997
998 if let Some(family) = &self.family {
999 d.field("family", family);
1000 }
1001
1002 if self.italic != PatternMatch::DontCare {
1003 d.field("italic", &self.italic);
1004 }
1005
1006 if self.oblique != PatternMatch::DontCare {
1007 d.field("oblique", &self.oblique);
1008 }
1009
1010 if self.bold != PatternMatch::DontCare {
1011 d.field("bold", &self.bold);
1012 }
1013
1014 if self.monospace != PatternMatch::DontCare {
1015 d.field("monospace", &self.monospace);
1016 }
1017
1018 if self.condensed != PatternMatch::DontCare {
1019 d.field("condensed", &self.condensed);
1020 }
1021
1022 if self.weight != FcWeight::Normal {
1023 d.field("weight", &self.weight);
1024 }
1025
1026 if self.stretch != FcStretch::Normal {
1027 d.field("stretch", &self.stretch);
1028 }
1029
1030 if !self.unicode_ranges.is_empty() {
1031 d.field("unicode_ranges", &self.unicode_ranges);
1032 }
1033
1034 let empty_metadata = FcFontMetadata::default();
1036 if self.metadata != empty_metadata {
1037 d.field("metadata", &self.metadata);
1038 }
1039
1040 let empty_render_config = FcFontRenderConfig::default();
1042 if self.render_config != empty_render_config {
1043 d.field("render_config", &self.render_config);
1044 }
1045
1046 d.finish()
1047 }
1048}
1049
1050#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]
1052#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
1053pub struct FcFontMetadata {
1054 pub copyright: Option<String>,
1055 pub designer: Option<String>,
1056 pub designer_url: Option<String>,
1057 pub font_family: Option<String>,
1058 pub font_subfamily: Option<String>,
1059 pub full_name: Option<String>,
1060 pub id_description: Option<String>,
1061 pub license: Option<String>,
1062 pub license_url: Option<String>,
1063 pub manufacturer: Option<String>,
1064 pub manufacturer_url: Option<String>,
1065 pub postscript_name: Option<String>,
1066 pub preferred_family: Option<String>,
1067 pub preferred_subfamily: Option<String>,
1068 pub trademark: Option<String>,
1069 pub unique_id: Option<String>,
1070 pub version: Option<String>,
1071}
1072
1073impl FcPattern {
1074 pub fn contains_char(&self, c: char) -> bool {
1076 if self.unicode_ranges.is_empty() {
1077 return true; }
1079
1080 for range in &self.unicode_ranges {
1081 if range.contains(c) {
1082 return true;
1083 }
1084 }
1085
1086 false
1087 }
1088}
1089
1090#[derive(Debug, Clone, PartialEq, Eq)]
1092pub struct FontMatch {
1093 pub id: FontId,
1094 pub unicode_ranges: Vec<UnicodeRange>,
1095 pub fallbacks: Vec<FontMatchNoFallback>,
1096}
1097
1098#[derive(Debug, Clone, PartialEq, Eq)]
1100pub struct FontMatchNoFallback {
1101 pub id: FontId,
1102 pub unicode_ranges: Vec<UnicodeRange>,
1103}
1104
1105#[derive(Debug, Clone, PartialEq, Eq)]
1108pub struct ResolvedFontRun {
1109 pub text: String,
1111 pub start_byte: usize,
1113 pub end_byte: usize,
1115 pub font_id: Option<FontId>,
1117 pub css_source: String,
1119}
1120
1121#[derive(Debug, Clone, PartialEq, Eq)]
1124pub struct FontFallbackChain {
1125 pub css_fallbacks: Vec<CssFallbackGroup>,
1128
1129 pub unicode_fallbacks: Vec<FontMatch>,
1132
1133 pub original_stack: Vec<String>,
1135}
1136
1137impl FontFallbackChain {
1138 pub fn resolve_char(&self, cache: &FcFontCache, ch: char) -> Option<(FontId, String)> {
1142 let codepoint = ch as u32;
1143
1144 for group in &self.css_fallbacks {
1146 for font in &group.fonts {
1147 let Some(meta) = cache.get_metadata_by_id(&font.id) else { continue };
1148 if meta.unicode_ranges.is_empty() {
1149 continue; }
1151 if meta.unicode_ranges.iter().any(|r| codepoint >= r.start && codepoint <= r.end) {
1152 return Some((font.id, group.css_name.clone()));
1153 }
1154 }
1155 }
1156
1157 for font in &self.unicode_fallbacks {
1159 let Some(meta) = cache.get_metadata_by_id(&font.id) else { continue };
1160 if meta.unicode_ranges.iter().any(|r| codepoint >= r.start && codepoint <= r.end) {
1161 return Some((font.id, "(unicode-fallback)".to_string()));
1162 }
1163 }
1164
1165 let registered = cache.list();
1175 if registered.len() == 1 {
1176 return Some((registered[0].1, "(web-last-resort)".to_string()));
1177 }
1178
1179 None
1180 }
1181
1182 pub fn resolve_text(&self, cache: &FcFontCache, text: &str) -> Vec<(char, Option<(FontId, String)>)> {
1185 text.chars()
1186 .map(|ch| (ch, self.resolve_char(cache, ch)))
1187 .collect()
1188 }
1189
1190 pub fn query_for_text(&self, cache: &FcFontCache, text: &str) -> Vec<ResolvedFontRun> {
1194 if text.is_empty() {
1195 return Vec::new();
1196 }
1197
1198 let mut runs: Vec<ResolvedFontRun> = Vec::new();
1199 let mut current_font: Option<FontId> = None;
1200 let mut current_css_source: Option<String> = None;
1201 let mut current_start_byte: usize = 0;
1202
1203 for (byte_idx, ch) in text.char_indices() {
1204 let resolved = self.resolve_char(cache, ch);
1205 let (font_id, css_source) = match &resolved {
1206 Some((id, source)) => (Some(*id), Some(source.clone())),
1207 None => (None, None),
1208 };
1209
1210 let font_changed = font_id != current_font;
1212
1213 if font_changed && byte_idx > 0 {
1214 let run_text = &text[current_start_byte..byte_idx];
1216 runs.push(ResolvedFontRun {
1217 text: run_text.to_string(),
1218 start_byte: current_start_byte,
1219 end_byte: byte_idx,
1220 font_id: current_font,
1221 css_source: current_css_source.clone().unwrap_or_default(),
1222 });
1223 current_start_byte = byte_idx;
1224 }
1225
1226 current_font = font_id;
1227 current_css_source = css_source;
1228 }
1229
1230 if current_start_byte < text.len() {
1232 let run_text = &text[current_start_byte..];
1233 runs.push(ResolvedFontRun {
1234 text: run_text.to_string(),
1235 start_byte: current_start_byte,
1236 end_byte: text.len(),
1237 font_id: current_font,
1238 css_source: current_css_source.unwrap_or_default(),
1239 });
1240 }
1241
1242 runs
1243 }
1244}
1245
1246#[derive(Debug, Clone, PartialEq, Eq)]
1248pub struct CssFallbackGroup {
1249 pub css_name: String,
1251
1252 pub fonts: Vec<FontMatch>,
1255}
1256
1257#[cfg(feature = "std")]
1270#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1271pub(crate) struct FontChainCacheKey {
1272 pub(crate) font_families: Vec<String>,
1274 pub(crate) weight: FcWeight,
1276 pub(crate) italic: PatternMatch,
1278 pub(crate) oblique: PatternMatch,
1279 pub(crate) scripts_hint_hash: Option<u64>,
1281}
1282
1283#[cfg(feature = "std")]
1288fn hash_scripts_hint(ranges: &[UnicodeRange]) -> u64 {
1289 let mut sorted: Vec<UnicodeRange> = ranges.to_vec();
1290 sorted.sort();
1291 let mut buf = Vec::with_capacity(sorted.len() * 8);
1292 for r in &sorted {
1293 buf.extend_from_slice(&r.start.to_le_bytes());
1294 buf.extend_from_slice(&r.end.to_le_bytes());
1295 }
1296 crate::utils::content_hash_u64(&buf)
1297}
1298
1299#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq)]
1311#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
1312#[repr(C)]
1313pub struct FcFontPath {
1314 pub path: String,
1315 pub font_index: usize,
1316 #[cfg_attr(feature = "cache", serde(default))]
1318 pub bytes_hash: u64,
1319}
1320
1321#[derive(Debug, Clone, PartialEq, Eq)]
1323#[repr(C)]
1324pub struct FcFont {
1325 pub bytes: Vec<u8>,
1326 pub font_index: usize,
1327 pub id: String, }
1329
1330#[derive(Debug, Clone)]
1342pub enum OwnedFontSource {
1343 Memory(FcFont),
1345 Disk(FcFontPath),
1347}
1348
1349#[cfg(feature = "std")]
1362pub enum FontBytes {
1363 Owned(std::sync::Arc<[u8]>),
1366 #[cfg(not(target_family = "wasm"))]
1370 Mmapped(mmapio::Mmap),
1371}
1372
1373#[cfg(feature = "std")]
1374impl FontBytes {
1375 #[inline]
1377 pub fn as_slice(&self) -> &[u8] {
1378 match self {
1379 FontBytes::Owned(arc) => arc,
1380 #[cfg(not(target_family = "wasm"))]
1381 FontBytes::Mmapped(m) => &m[..],
1382 }
1383 }
1384}
1385
1386#[cfg(feature = "std")]
1387impl core::ops::Deref for FontBytes {
1388 type Target = [u8];
1389 #[inline]
1390 fn deref(&self) -> &[u8] {
1391 self.as_slice()
1392 }
1393}
1394
1395#[cfg(feature = "std")]
1396impl AsRef<[u8]> for FontBytes {
1397 #[inline]
1398 fn as_ref(&self) -> &[u8] {
1399 self.as_slice()
1400 }
1401}
1402
1403#[cfg(feature = "std")]
1404impl core::fmt::Debug for FontBytes {
1405 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1406 let kind = match self {
1407 FontBytes::Owned(_) => "Owned",
1408 #[cfg(not(target_family = "wasm"))]
1409 FontBytes::Mmapped(_) => "Mmapped",
1410 };
1411 write!(f, "FontBytes::{}({} bytes)", kind, self.as_slice().len())
1412 }
1413}
1414
1415#[cfg(feature = "std")]
1419fn open_font_bytes_mmap(path: &str) -> Option<std::sync::Arc<FontBytes>> {
1420 use std::fs::File;
1421 use std::sync::Arc;
1422
1423 #[cfg(not(target_family = "wasm"))]
1424 {
1425 if let Ok(file) = File::open(path) {
1426 if let Ok(mmap) = unsafe { mmapio::MmapOptions::new().map(&file) } {
1431 return Some(Arc::new(FontBytes::Mmapped(mmap)));
1432 }
1433 }
1434 }
1435 let bytes = std::fs::read(path).ok()?;
1436 Some(Arc::new(FontBytes::Owned(Arc::from(bytes))))
1437}
1438
1439#[derive(Debug, Clone)]
1442pub struct NamedFont {
1443 pub name: String,
1445 pub bytes: Vec<u8>,
1447}
1448
1449impl NamedFont {
1450 pub fn new(name: impl Into<String>, bytes: Vec<u8>) -> Self {
1452 Self {
1453 name: name.into(),
1454 bytes,
1455 }
1456 }
1457}
1458
1459pub struct FcFontCache {
1477 pub(crate) shared: std::sync::Arc<FcFontCacheShared>,
1478}
1479
1480#[cfg(not(feature = "single-thread-unsafe-locks"))]
1495pub struct StLock<T> {
1496 lock: std::sync::RwLock<T>,
1497}
1498#[cfg(not(feature = "single-thread-unsafe-locks"))]
1499impl<T> core::fmt::Debug for StLock<T> {
1500 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1501 f.write_str("StLock(..)")
1502 }
1503}
1504#[cfg(not(feature = "single-thread-unsafe-locks"))]
1505impl<T> StLock<T> {
1506 pub fn new(v: T) -> Self {
1507 Self { lock: std::sync::RwLock::new(v) }
1508 }
1509 pub fn read(&self) -> Result<StReadGuard<'_, T>, core::convert::Infallible> {
1510 Ok(StReadGuard { g: self.lock.read().unwrap_or_else(|e| e.into_inner()) })
1511 }
1512 pub fn write(&self) -> Result<StWriteGuard<'_, T>, core::convert::Infallible> {
1513 Ok(StWriteGuard { g: self.lock.write().unwrap_or_else(|e| e.into_inner()) })
1514 }
1515 pub fn lock(&self) -> Result<StWriteGuard<'_, T>, core::convert::Infallible> {
1516 self.write()
1517 }
1518}
1519#[cfg(not(feature = "single-thread-unsafe-locks"))]
1520pub struct StReadGuard<'a, T> {
1521 g: std::sync::RwLockReadGuard<'a, T>,
1522}
1523#[cfg(not(feature = "single-thread-unsafe-locks"))]
1524impl<'a, T> core::ops::Deref for StReadGuard<'a, T> {
1525 type Target = T;
1526 fn deref(&self) -> &T { &self.g }
1527}
1528#[cfg(not(feature = "single-thread-unsafe-locks"))]
1529pub struct StWriteGuard<'a, T> {
1530 g: std::sync::RwLockWriteGuard<'a, T>,
1531}
1532#[cfg(not(feature = "single-thread-unsafe-locks"))]
1533impl<'a, T> core::ops::Deref for StWriteGuard<'a, T> {
1534 type Target = T;
1535 fn deref(&self) -> &T { &self.g }
1536}
1537#[cfg(not(feature = "single-thread-unsafe-locks"))]
1538impl<'a, T> core::ops::DerefMut for StWriteGuard<'a, T> {
1539 fn deref_mut(&mut self) -> &mut T { &mut self.g }
1540}
1541
1542#[cfg(feature = "single-thread-unsafe-locks")]
1543pub struct StLock<T> {
1544 cell: std::cell::UnsafeCell<T>,
1545}
1546#[cfg(feature = "single-thread-unsafe-locks")]
1547unsafe impl<T> Sync for StLock<T> {}
1548#[cfg(feature = "single-thread-unsafe-locks")]
1549unsafe impl<T> Send for StLock<T> {}
1550#[cfg(feature = "single-thread-unsafe-locks")]
1551impl<T> core::fmt::Debug for StLock<T> {
1552 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1553 f.write_str("StLock(..)")
1554 }
1555}
1556#[cfg(feature = "single-thread-unsafe-locks")]
1557impl<T> StLock<T> {
1558 pub fn new(v: T) -> Self {
1559 Self { cell: std::cell::UnsafeCell::new(v) }
1560 }
1561 pub fn read(&self) -> Result<StReadGuard<'_, T>, core::convert::Infallible> {
1562 Ok(StReadGuard { r: unsafe { &*self.cell.get() } })
1563 }
1564 pub fn write(&self) -> Result<StWriteGuard<'_, T>, core::convert::Infallible> {
1565 Ok(StWriteGuard { r: unsafe { &mut *self.cell.get() } })
1566 }
1567 pub fn lock(&self) -> Result<StWriteGuard<'_, T>, core::convert::Infallible> {
1568 Ok(StWriteGuard { r: unsafe { &mut *self.cell.get() } })
1569 }
1570}
1571#[cfg(feature = "single-thread-unsafe-locks")]
1572pub struct StReadGuard<'a, T> {
1573 r: &'a T,
1574}
1575#[cfg(feature = "single-thread-unsafe-locks")]
1576impl<'a, T> core::ops::Deref for StReadGuard<'a, T> {
1577 type Target = T;
1578 fn deref(&self) -> &T { self.r }
1579}
1580#[cfg(feature = "single-thread-unsafe-locks")]
1581pub struct StWriteGuard<'a, T> {
1582 r: &'a mut T,
1583}
1584#[cfg(feature = "single-thread-unsafe-locks")]
1585impl<'a, T> core::ops::Deref for StWriteGuard<'a, T> {
1586 type Target = T;
1587 fn deref(&self) -> &T { self.r }
1588}
1589#[cfg(feature = "single-thread-unsafe-locks")]
1590impl<'a, T> core::ops::DerefMut for StWriteGuard<'a, T> {
1591 fn deref_mut(&mut self) -> &mut T { self.r }
1592}
1593
1594pub(crate) struct FcFontCacheShared {
1595 pub(crate) state: StLock<FcFontCacheInner>,
1599 pub(crate) chain_cache: StLock<std::collections::HashMap<FontChainCacheKey, FontFallbackChain>>,
1603 pub(crate) shared_bytes: StLock<std::collections::HashMap<u64, std::sync::Weak<FontBytes>>>,
1612}
1613
1614#[derive(Default, Debug)]
1618pub(crate) struct FcFontCacheInner {
1619 pub(crate) patterns: BTreeMap<FcPattern, FontId>,
1621 pub(crate) disk_fonts: BTreeMap<FontId, FcFontPath>,
1623 pub(crate) memory_fonts: BTreeMap<FontId, FcFont>,
1625 pub(crate) metadata: BTreeMap<FontId, FcPattern>,
1627 pub(crate) token_index: BTreeMap<String, alloc::collections::BTreeSet<FontId>>,
1630 pub(crate) font_tokens: BTreeMap<FontId, Vec<String>>,
1633 pub(crate) system_aliases: BTreeMap<String, Vec<String>>,
1642}
1643
1644impl FcFontCacheInner {
1645 pub(crate) fn index_pattern_tokens(&mut self, _pattern: &FcPattern, _id: FontId) {
1648 }
1656}
1657
1658impl Clone for FcFontCache {
1659 fn clone(&self) -> Self {
1666 Self {
1667 shared: std::sync::Arc::clone(&self.shared),
1668 }
1669 }
1670}
1671
1672impl core::fmt::Debug for FcFontCache {
1673 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1674 let state = self.state_read();
1675 f.debug_struct("FcFontCache")
1676 .field("patterns_len", &state.patterns.len())
1677 .field("metadata_len", &state.metadata.len())
1678 .field("disk_fonts_len", &state.disk_fonts.len())
1679 .field("memory_fonts_len", &state.memory_fonts.len())
1680 .finish()
1681 }
1682}
1683
1684impl Default for FcFontCache {
1685 fn default() -> Self {
1686 Self {
1687 shared: std::sync::Arc::new(FcFontCacheShared {
1688 state: StLock::new(FcFontCacheInner::default()),
1689 chain_cache: StLock::new(std::collections::HashMap::new()),
1690 shared_bytes: StLock::new(std::collections::HashMap::new()),
1691 }),
1692 }
1693 }
1694}
1695
1696impl FcFontCache {
1697 pub fn system_alias_prefs(&self, family: &str) -> Vec<String> {
1701 let norm = crate::utils::normalize_family_name(family);
1702 self.state_read()
1703 .system_aliases
1704 .get(&norm)
1705 .cloned()
1706 .unwrap_or_default()
1707 }
1708
1709 pub fn expand_font_families_config_first(
1727 &self,
1728 families: &[String],
1729 os: OperatingSystem,
1730 unicode_ranges: &[UnicodeRange],
1731 ) -> Vec<String> {
1732 let mut expanded: Vec<String> = Vec::new();
1733 let mut push_unique = |v: &mut Vec<String>, f: String| {
1734 if !v.iter().any(|e| e.eq_ignore_ascii_case(&f)) {
1735 v.push(f);
1736 }
1737 };
1738 for family in families {
1739 let is_generic = matches!(
1740 family.to_ascii_lowercase().as_str(),
1741 "serif" | "sans-serif" | "monospace" | "cursive" | "fantasy" | "system-ui"
1742 );
1743 if !is_generic {
1744 push_unique(&mut expanded, family.clone());
1745 }
1746 let prefs = self.system_alias_prefs(family);
1747 if !prefs.is_empty() {
1748 for pref in prefs {
1749 push_unique(&mut expanded, pref);
1750 }
1751 } else if is_generic {
1752 for fallback in os.expand_generic_family(family, unicode_ranges) {
1753 push_unique(&mut expanded, fallback);
1754 }
1755 }
1756 }
1757 expanded
1758 }
1759
1760 #[inline]
1764 pub(crate) fn state_read(
1765 &self,
1766 ) -> StReadGuard<'_, FcFontCacheInner> {
1767 match self.shared.state.read() {
1769 Ok(g) => g,
1770 Err(e) => match e {},
1771 }
1772 }
1773
1774 #[inline]
1777 pub(crate) fn state_write(
1778 &self,
1779 ) -> StWriteGuard<'_, FcFontCacheInner> {
1780 match self.shared.state.write() {
1782 Ok(g) => g,
1783 Err(e) => match e {},
1784 }
1785 }
1786
1787 pub fn with_memory_fonts(&self, fonts: Vec<(FcPattern, FcFont)>) -> &Self {
1792 let fonts: Vec<(FcPattern, FcFont)> = fonts
1796 .into_iter()
1797 .map(|(pattern, font)| (Self::populate_memory_font_ranges(pattern, &font), font))
1798 .collect();
1799 let mut state = self.state_write();
1800 for (pattern, font) in fonts {
1801 let id = FontId::new();
1802 state.patterns.insert(pattern.clone(), id);
1803 state.metadata.insert(id, pattern.clone());
1804 state.memory_fonts.insert(id, font);
1805 state.index_pattern_tokens(&pattern, id);
1806 }
1807 self
1808 }
1809
1810 pub fn with_memory_font_with_id(
1812 &self,
1813 id: FontId,
1814 pattern: FcPattern,
1815 font: FcFont,
1816 ) -> &Self {
1817 let pattern = Self::populate_memory_font_ranges(pattern, &font);
1818 let mut state = self.state_write();
1819 state.patterns.insert(pattern.clone(), id);
1820 state.metadata.insert(id, pattern.clone());
1821 state.memory_fonts.insert(id, font);
1822 state.index_pattern_tokens(&pattern, id);
1823 self
1824 }
1825
1826 #[cfg(all(feature = "std", feature = "parsing"))]
1842 fn populate_memory_font_ranges(mut pattern: FcPattern, font: &FcFont) -> FcPattern {
1843 if !pattern.unicode_ranges.is_empty() {
1844 return pattern;
1845 }
1846 if let Some(faces) = FcParseFontBytes(&font.bytes, &font.id) {
1847 let ranges = faces
1851 .iter()
1852 .find(|(_, f)| f.font_index == font.font_index)
1853 .or_else(|| faces.first())
1854 .map(|(p, _)| p.unicode_ranges.clone())
1855 .unwrap_or_default();
1856 if !ranges.is_empty() {
1857 pattern.unicode_ranges = ranges;
1858 }
1859 }
1860 pattern
1861 }
1862
1863 #[cfg(not(all(feature = "std", feature = "parsing")))]
1866 fn populate_memory_font_ranges(pattern: FcPattern, _font: &FcFont) -> FcPattern {
1867 pattern
1868 }
1869
1870 pub fn insert_builder_font(&self, pattern: FcPattern, path: FcFontPath) {
1876 let id = FontId::new();
1877 {
1878 let mut state = self.state_write();
1879 state.index_pattern_tokens(&pattern, id);
1880 state.patterns.insert(pattern.clone(), id);
1881 state.disk_fonts.insert(id, path);
1882 state.metadata.insert(id, pattern);
1883 }
1884 if let Ok(mut cc) = self.shared.chain_cache.lock() {
1888 cc.clear();
1889 }
1890 }
1891
1892 #[cfg(feature = "std")]
1893 #[doc(hidden)]
1894 pub fn chain_cache_len(&self) -> usize {
1895 self.shared.chain_cache.lock().map(|c| c.len()).unwrap_or(0)
1896 }
1897
1898 pub fn insert_fast_pattern(&self, pattern: FcPattern, path: FcFontPath) -> FontId {
1906 let id = FontId::new();
1907 let mut state = self.state_write();
1908 state.patterns.insert(pattern.clone(), id);
1909 state.disk_fonts.insert(id, path);
1910 state.metadata.insert(id, pattern);
1911 id
1912 }
1913
1914 pub fn lookup_paths_cached(&self, path: &str) -> Option<Vec<FontId>> {
1922 let state = self.state_read();
1923 let mut out = Vec::new();
1924 for (id, font_path) in &state.disk_fonts {
1925 if font_path.path == path {
1926 out.push(*id);
1927 }
1928 }
1929 if out.is_empty() { None } else { Some(out) }
1930 }
1931
1932 pub fn get_font_by_id(&self, id: &FontId) -> Option<OwnedFontSource> {
1939 let state = self.state_read();
1940 if let Some(font) = state.memory_fonts.get(id) {
1941 return Some(OwnedFontSource::Memory(font.clone()));
1942 }
1943 if let Some(path) = state.disk_fonts.get(id) {
1944 return Some(OwnedFontSource::Disk(path.clone()));
1945 }
1946 None
1947 }
1948
1949 pub fn get_metadata_by_id(&self, id: &FontId) -> Option<FcPattern> {
1953 self.state_read().metadata.get(id).cloned()
1954 }
1955
1956 #[cfg(feature = "std")]
1984 pub fn get_font_bytes(&self, id: &FontId) -> Option<std::sync::Arc<FontBytes>> {
1985 use std::sync::Arc;
1986 match self.get_font_by_id(id)? {
1987 OwnedFontSource::Memory(font) => Some(Arc::new(FontBytes::Owned(
1988 Arc::from(font.bytes.as_slice()),
1989 ))),
1990 OwnedFontSource::Disk(path) => {
1991 let hash = path.bytes_hash;
1992 if hash != 0 {
1993 if let Ok(guard) = self.shared.shared_bytes.lock() {
1994 if let Some(weak) = guard.get(&hash) {
1995 if let Some(arc) = weak.upgrade() {
1996 return Some(arc);
1997 }
1998 }
1999 }
2000 }
2001
2002 let arc = open_font_bytes_mmap(&path.path)?;
2003 if hash != 0 {
2004 if let Ok(mut guard) = self.shared.shared_bytes.lock() {
2005 guard.insert(hash, Arc::downgrade(&arc));
2007 }
2008 }
2009 Some(arc)
2010 }
2011 }
2012 }
2013
2014 #[cfg(not(feature = "std"))]
2016 pub fn build() -> Self { Self::default() }
2017
2018 #[cfg(all(feature = "std", not(feature = "parsing")))]
2020 pub fn build() -> Self { Self::build_from_filenames() }
2021
2022 #[cfg(all(feature = "std", feature = "parsing"))]
2024 pub fn build() -> Self { Self::build_inner(None) }
2025
2026 #[cfg(all(feature = "std", not(feature = "parsing")))]
2029 fn build_from_filenames() -> Self {
2030 let cache = Self::default();
2031 {
2032 let mut state = cache.state_write();
2033 for dir in crate::config::font_directories(OperatingSystem::current()) {
2034 for path in FcCollectFontFilesRecursive(dir) {
2035 let pattern = match pattern_from_filename(&path) {
2036 Some(p) => p,
2037 None => continue,
2038 };
2039 let id = FontId::new();
2040 state.disk_fonts.insert(id, FcFontPath {
2041 path: path.to_string_lossy().to_string(),
2042 font_index: 0,
2043 bytes_hash: 0,
2046 });
2047 state.index_pattern_tokens(&pattern, id);
2048 state.metadata.insert(id, pattern.clone());
2049 state.patterns.insert(pattern, id);
2050 }
2051 }
2052 }
2053 cache
2054 }
2055
2056 #[cfg(all(feature = "std", feature = "parsing"))]
2081 pub fn build_with_families(families: &[impl AsRef<str>]) -> Self {
2082 let os = OperatingSystem::current();
2087 let mut target_families: Vec<String> = Vec::new();
2088
2089 for family in families {
2090 let family_str = family.as_ref();
2091 let expanded = os.expand_generic_family(family_str, &[]);
2092 if expanded.is_empty() || (expanded.len() == 1 && expanded[0] == family_str) {
2093 target_families.push(family_str.to_string());
2094 } else {
2095 target_families.extend(expanded);
2096 }
2097 }
2098
2099 Self::build_inner(Some(&target_families))
2100 }
2101
2102 #[cfg(all(feature = "std", feature = "parsing"))]
2108 fn build_inner(family_filter: Option<&[String]>) -> Self {
2109 let cache = FcFontCache::default();
2110
2111 let filter_normalized: Option<Vec<String>> = family_filter.map(|families| {
2113 families
2114 .iter()
2115 .map(|f| crate::utils::normalize_family_name(f))
2116 .collect()
2117 });
2118
2119 let matches_filter = |pattern: &FcPattern| -> bool {
2121 match &filter_normalized {
2122 None => true, Some(targets) => {
2124 pattern.name.as_ref().map_or(false, |name| {
2125 let name_norm = crate::utils::normalize_family_name(name);
2126 targets.iter().any(|target| name_norm.contains(target))
2127 }) || pattern.family.as_ref().map_or(false, |family| {
2128 let family_norm = crate::utils::normalize_family_name(family);
2129 targets.iter().any(|target| family_norm.contains(target))
2130 })
2131 }
2132 }
2133 };
2134
2135 let mut state = cache.state_write();
2136
2137 #[cfg(target_os = "linux")]
2138 {
2139 if let Some((font_entries, render_configs, system_aliases)) = FcScanDirectories() {
2140 state.system_aliases = system_aliases;
2141 for (mut pattern, path) in font_entries {
2142 if matches_filter(&pattern) {
2143 if let Some(family) = pattern.name.as_ref().or(pattern.family.as_ref()) {
2145 if let Some(rc) = render_configs.get(family) {
2146 pattern.render_config = rc.clone();
2147 }
2148 }
2149 let id = FontId::new();
2150 state.patterns.insert(pattern.clone(), id);
2151 state.metadata.insert(id, pattern.clone());
2152 state.disk_fonts.insert(id, path);
2153 state.index_pattern_tokens(&pattern, id);
2154 }
2155 }
2156 }
2157 }
2158
2159 #[cfg(target_os = "windows")]
2160 {
2161 let system_root = std::env::var("SystemRoot")
2162 .or_else(|_| std::env::var("WINDIR"))
2163 .unwrap_or_else(|_| "C:\\Windows".to_string());
2164
2165 let user_profile = std::env::var("USERPROFILE")
2166 .unwrap_or_else(|_| "C:\\Users\\Default".to_string());
2167
2168 let font_dirs = vec![
2169 (None, format!("{}\\Fonts\\", system_root)),
2170 (None, format!("{}\\AppData\\Local\\Microsoft\\Windows\\Fonts\\", user_profile)),
2171 ];
2172
2173 let font_entries = FcScanDirectoriesInner(&font_dirs);
2174 for (pattern, path) in font_entries {
2175 if matches_filter(&pattern) {
2176 let id = FontId::new();
2177 state.patterns.insert(pattern.clone(), id);
2178 state.metadata.insert(id, pattern.clone());
2179 state.disk_fonts.insert(id, path);
2180 state.index_pattern_tokens(&pattern, id);
2181 }
2182 }
2183 }
2184
2185 #[cfg(target_os = "macos")]
2186 {
2187 let font_dirs = vec![
2188 (None, "~/Library/Fonts".to_owned()),
2189 (None, "/System/Library/Fonts".to_owned()),
2190 (None, "/Library/Fonts".to_owned()),
2191 (None, "/System/Library/AssetsV2".to_owned()),
2192 ];
2193
2194 let font_entries = FcScanDirectoriesInner(&font_dirs);
2195 for (pattern, path) in font_entries {
2196 if matches_filter(&pattern) {
2197 let id = FontId::new();
2198 state.patterns.insert(pattern.clone(), id);
2199 state.metadata.insert(id, pattern.clone());
2200 state.disk_fonts.insert(id, path);
2201 state.index_pattern_tokens(&pattern, id);
2202 }
2203 }
2204 }
2205
2206 #[cfg(target_os = "ios")]
2211 {
2212 let font_files = crate::mobile_ios::copy_available_font_urls();
2213 let font_entries = FcParseFontFiles(&font_files);
2214 for (pattern, path) in font_entries {
2215 if matches_filter(&pattern) {
2216 let id = FontId::new();
2217 state.patterns.insert(pattern.clone(), id);
2218 state.metadata.insert(id, pattern.clone());
2219 state.disk_fonts.insert(id, path);
2220 state.index_pattern_tokens(&pattern, id);
2221 }
2222 }
2223 }
2224
2225 #[cfg(target_os = "android")]
2230 {
2231 let font_dirs = vec![
2232 (None, "/system/fonts".to_owned()),
2233 (None, "/product/fonts".to_owned()),
2234 (None, "/system_ext/fonts".to_owned()),
2235 (None, "/data/fonts".to_owned()),
2236 ];
2237
2238 let font_entries = FcScanDirectoriesInner(&font_dirs);
2239 for (pattern, path) in font_entries {
2240 if matches_filter(&pattern) {
2241 let id = FontId::new();
2242 state.patterns.insert(pattern.clone(), id);
2243 state.metadata.insert(id, pattern.clone());
2244 state.disk_fonts.insert(id, path);
2245 state.index_pattern_tokens(&pattern, id);
2246 }
2247 }
2248 }
2249
2250 drop(state);
2251 cache
2252 }
2253
2254 pub fn is_memory_font(&self, id: &FontId) -> bool {
2256 self.state_read().memory_fonts.contains_key(id)
2257 }
2258
2259 pub fn list(&self) -> Vec<(FcPattern, FontId)> {
2266 self.state_read()
2267 .patterns
2268 .iter()
2269 .map(|(pattern, id)| (pattern.clone(), *id))
2270 .collect()
2271 }
2272
2273 pub fn for_each_pattern<F: FnMut(&FcPattern, &FontId)>(&self, mut f: F) {
2277 let state = self.state_read();
2278 for (pattern, id) in &state.patterns {
2279 f(pattern, id);
2280 }
2281 }
2282
2283 pub fn is_empty(&self) -> bool {
2285 self.state_read().patterns.is_empty()
2286 }
2287
2288 pub fn len(&self) -> usize {
2290 self.state_read().patterns.len()
2291 }
2292
2293 pub fn query_with_fallback(
2320 &self,
2321 pattern: &FcPattern,
2322 trace: &mut Vec<TraceMsg>,
2323 ) -> Option<FontMatch> {
2324 if let Some(m) = self.query(pattern, trace) {
2325 return Some(m);
2326 }
2327
2328 if pattern.name.is_some() || pattern.family.is_some() {
2330 let relaxed = FcPattern {
2331 name: None,
2332 family: None,
2333 ..pattern.clone()
2334 };
2335 if let Some(m) = self.query(&relaxed, trace) {
2336 return Some(m);
2337 }
2338 }
2339
2340 let bare = FcPattern {
2342 unicode_ranges: pattern.unicode_ranges.clone(),
2343 ..FcPattern::default()
2344 };
2345 self.query(&bare, trace)
2346 }
2347
2348 pub fn query(&self, pattern: &FcPattern, trace: &mut Vec<TraceMsg>) -> Option<FontMatch> {
2354 let state = self.state_read();
2355 let mut matches = Vec::new();
2356
2357 for (stored_pattern, id) in &state.patterns {
2358 if Self::query_matches_internal(stored_pattern, pattern, trace) {
2359 let metadata = state.metadata.get(id).unwrap_or(stored_pattern);
2360
2361 let unicode_compatibility = if pattern.unicode_ranges.is_empty() {
2363 Self::calculate_unicode_coverage(&metadata.unicode_ranges) as i32
2365 } else {
2366 Self::calculate_unicode_compatibility(&pattern.unicode_ranges, &metadata.unicode_ranges)
2368 };
2369
2370 let style_score = Self::calculate_style_score(pattern, metadata);
2371
2372 let is_memory = state.memory_fonts.contains_key(id);
2374
2375 matches.push((*id, unicode_compatibility, style_score, metadata.clone(), is_memory));
2376 }
2377 }
2378
2379 matches.sort_by(|a, b| {
2381 b.4.cmp(&a.4)
2383 .then_with(|| b.1.cmp(&a.1)) .then_with(|| a.2.cmp(&b.2)) });
2386
2387 matches.first().map(|(id, _, _, metadata, _)| {
2388 FontMatch {
2389 id: *id,
2390 unicode_ranges: metadata.unicode_ranges.clone(),
2391 fallbacks: Vec::new(), }
2393 })
2394 }
2395
2396 fn query_internal(&self, pattern: &FcPattern, trace: &mut Vec<TraceMsg>) -> Vec<FontMatch> {
2401 let state = self.state_read();
2402 self.query_internal_locked(&state, pattern, trace)
2403 }
2404
2405 fn query_internal_locked(
2408 &self,
2409 state: &FcFontCacheInner,
2410 pattern: &FcPattern,
2411 trace: &mut Vec<TraceMsg>,
2412 ) -> Vec<FontMatch> {
2413 let mut matches = Vec::new();
2414
2415 for (stored_pattern, id) in &state.patterns {
2416 if Self::query_matches_internal(stored_pattern, pattern, trace) {
2417 let metadata = state.metadata.get(id).unwrap_or(stored_pattern);
2418
2419 let unicode_compatibility = if pattern.unicode_ranges.is_empty() {
2421 Self::calculate_unicode_coverage(&metadata.unicode_ranges) as i32
2422 } else {
2423 Self::calculate_unicode_compatibility(&pattern.unicode_ranges, &metadata.unicode_ranges)
2424 };
2425
2426 let style_score = Self::calculate_style_score(pattern, metadata);
2427 matches.push((*id, unicode_compatibility, style_score, metadata.clone()));
2428 }
2429 }
2430
2431 matches.sort_by(|a, b| {
2435 a.2.cmp(&b.2) .then_with(|| b.1.cmp(&a.1)) .then_with(|| a.3.italic.cmp(&b.3.italic)) .then_with(|| a.3.name.cmp(&b.3.name)) });
2440
2441 matches
2442 .into_iter()
2443 .map(|(id, _, _, metadata)| {
2444 FontMatch {
2445 id,
2446 unicode_ranges: metadata.unicode_ranges.clone(),
2447 fallbacks: Vec::new(), }
2449 })
2450 .collect()
2451 }
2452
2453 pub fn compute_fallbacks(
2457 &self,
2458 font_id: &FontId,
2459 trace: &mut Vec<TraceMsg>,
2460 ) -> Vec<FontMatchNoFallback> {
2461 let state = self.state_read();
2462 let pattern = match state.metadata.get(font_id) {
2463 Some(p) => p.clone(),
2464 None => return Vec::new(),
2465 };
2466 drop(state);
2467
2468 self.compute_fallbacks_for_pattern(&pattern, Some(font_id), trace)
2469 }
2470
2471 fn compute_fallbacks_for_pattern(
2472 &self,
2473 pattern: &FcPattern,
2474 exclude_id: Option<&FontId>,
2475 _trace: &mut Vec<TraceMsg>,
2476 ) -> Vec<FontMatchNoFallback> {
2477 let state = self.state_read();
2478 let mut candidates = Vec::new();
2479
2480 for (stored_pattern, id) in &state.patterns {
2482 if exclude_id.is_some() && exclude_id.unwrap() == id {
2484 continue;
2485 }
2486
2487 if !stored_pattern.unicode_ranges.is_empty() && !pattern.unicode_ranges.is_empty() {
2489 let unicode_compatibility = Self::calculate_unicode_compatibility(
2491 &pattern.unicode_ranges,
2492 &stored_pattern.unicode_ranges
2493 );
2494
2495 if unicode_compatibility > 0 {
2497 let style_score = Self::calculate_style_score(pattern, stored_pattern);
2498 candidates.push((
2499 FontMatchNoFallback {
2500 id: *id,
2501 unicode_ranges: stored_pattern.unicode_ranges.clone(),
2502 },
2503 unicode_compatibility,
2504 style_score,
2505 stored_pattern.clone(),
2506 ));
2507 }
2508 } else if pattern.unicode_ranges.is_empty() && !stored_pattern.unicode_ranges.is_empty() {
2509 let coverage = Self::calculate_unicode_coverage(&stored_pattern.unicode_ranges) as i32;
2511 let style_score = Self::calculate_style_score(pattern, stored_pattern);
2512 candidates.push((
2513 FontMatchNoFallback {
2514 id: *id,
2515 unicode_ranges: stored_pattern.unicode_ranges.clone(),
2516 },
2517 coverage,
2518 style_score,
2519 stored_pattern.clone(),
2520 ));
2521 }
2522 }
2523
2524 drop(state);
2525
2526 candidates.sort_by(|a, b| {
2528 b.1.cmp(&a.1)
2529 .then_with(|| a.2.cmp(&b.2))
2530 });
2531
2532 let mut seen_ranges = Vec::new();
2534 let mut deduplicated = Vec::new();
2535
2536 for (id, _, _, pattern) in candidates {
2537 let mut is_new_range = false;
2538
2539 for range in &pattern.unicode_ranges {
2540 if !seen_ranges.iter().any(|r: &UnicodeRange| r.overlaps(range)) {
2541 seen_ranges.push(*range);
2542 is_new_range = true;
2543 }
2544 }
2545
2546 if is_new_range {
2547 deduplicated.push(id);
2548 }
2549 }
2550
2551 deduplicated
2552 }
2553
2554 pub fn get_memory_font(&self, id: &FontId) -> Option<FcFont> {
2556 self.state_read().memory_fonts.get(id).cloned()
2557 }
2558
2559 fn trace_path(k: &FcPattern) -> String {
2561 k.name.as_ref().cloned().unwrap_or_else(|| "<unknown>".to_string())
2562 }
2563
2564 pub fn query_matches_internal(
2565 k: &FcPattern,
2566 pattern: &FcPattern,
2567 trace: &mut Vec<TraceMsg>,
2568 ) -> bool {
2569 if let Some(ref name) = pattern.name {
2571 if !k.name.as_ref().map_or(false, |kn| kn.contains(name)) {
2572 trace.push(TraceMsg {
2573 level: TraceLevel::Info,
2574 path: Self::trace_path(k),
2575 reason: MatchReason::NameMismatch {
2576 requested: pattern.name.clone(),
2577 found: k.name.clone(),
2578 },
2579 });
2580 return false;
2581 }
2582 }
2583
2584 if let Some(ref family) = pattern.family {
2586 if !k.family.as_ref().map_or(false, |kf| kf.contains(family)) {
2587 trace.push(TraceMsg {
2588 level: TraceLevel::Info,
2589 path: Self::trace_path(k),
2590 reason: MatchReason::FamilyMismatch {
2591 requested: pattern.family.clone(),
2592 found: k.family.clone(),
2593 },
2594 });
2595 return false;
2596 }
2597 }
2598
2599 let style_properties = [
2601 (
2602 "italic",
2603 pattern.italic.needs_to_match(),
2604 pattern.italic.matches(&k.italic),
2605 ),
2606 (
2607 "oblique",
2608 pattern.oblique.needs_to_match(),
2609 pattern.oblique.matches(&k.oblique),
2610 ),
2611 (
2612 "bold",
2613 pattern.bold.needs_to_match(),
2614 pattern.bold.matches(&k.bold),
2615 ),
2616 (
2617 "monospace",
2618 pattern.monospace.needs_to_match(),
2619 pattern.monospace.matches(&k.monospace),
2620 ),
2621 (
2622 "condensed",
2623 pattern.condensed.needs_to_match(),
2624 pattern.condensed.matches(&k.condensed),
2625 ),
2626 ];
2627
2628 for (property_name, needs_to_match, matches) in style_properties {
2629 if needs_to_match && !matches {
2630 let (requested, found) = match property_name {
2631 "italic" => (format!("{:?}", pattern.italic), format!("{:?}", k.italic)),
2632 "oblique" => (format!("{:?}", pattern.oblique), format!("{:?}", k.oblique)),
2633 "bold" => (format!("{:?}", pattern.bold), format!("{:?}", k.bold)),
2634 "monospace" => (
2635 format!("{:?}", pattern.monospace),
2636 format!("{:?}", k.monospace),
2637 ),
2638 "condensed" => (
2639 format!("{:?}", pattern.condensed),
2640 format!("{:?}", k.condensed),
2641 ),
2642 _ => (String::new(), String::new()),
2643 };
2644
2645 trace.push(TraceMsg {
2646 level: TraceLevel::Info,
2647 path: Self::trace_path(k),
2648 reason: MatchReason::StyleMismatch {
2649 property: property_name,
2650 requested,
2651 found,
2652 },
2653 });
2654 return false;
2655 }
2656 }
2657
2658 if pattern.weight != FcWeight::Normal && pattern.weight != k.weight {
2660 trace.push(TraceMsg {
2661 level: TraceLevel::Info,
2662 path: Self::trace_path(k),
2663 reason: MatchReason::WeightMismatch {
2664 requested: pattern.weight,
2665 found: k.weight,
2666 },
2667 });
2668 return false;
2669 }
2670
2671 if pattern.stretch != FcStretch::Normal && pattern.stretch != k.stretch {
2673 trace.push(TraceMsg {
2674 level: TraceLevel::Info,
2675 path: Self::trace_path(k),
2676 reason: MatchReason::StretchMismatch {
2677 requested: pattern.stretch,
2678 found: k.stretch,
2679 },
2680 });
2681 return false;
2682 }
2683
2684 if !pattern.unicode_ranges.is_empty() {
2686 let mut has_overlap = false;
2687
2688 for p_range in &pattern.unicode_ranges {
2689 for k_range in &k.unicode_ranges {
2690 if p_range.overlaps(k_range) {
2691 has_overlap = true;
2692 break;
2693 }
2694 }
2695 if has_overlap {
2696 break;
2697 }
2698 }
2699
2700 if !has_overlap {
2701 trace.push(TraceMsg {
2702 level: TraceLevel::Info,
2703 path: Self::trace_path(k),
2704 reason: MatchReason::UnicodeRangeMismatch {
2705 character: '\0', ranges: k.unicode_ranges.clone(),
2707 },
2708 });
2709 return false;
2710 }
2711 }
2712
2713 true
2714 }
2715
2716 #[cfg(feature = "std")]
2742 pub fn resolve_font_chain(
2743 &self,
2744 font_families: &[String],
2745 weight: FcWeight,
2746 italic: PatternMatch,
2747 oblique: PatternMatch,
2748 trace: &mut Vec<TraceMsg>,
2749 ) -> FontFallbackChain {
2750 self.resolve_font_chain_with_os(font_families, weight, italic, oblique, trace, OperatingSystem::current())
2751 }
2752
2753 #[cfg(feature = "std")]
2755 pub fn resolve_font_chain_with_os(
2756 &self,
2757 font_families: &[String],
2758 weight: FcWeight,
2759 italic: PatternMatch,
2760 oblique: PatternMatch,
2761 trace: &mut Vec<TraceMsg>,
2762 os: OperatingSystem,
2763 ) -> FontFallbackChain {
2764 self.resolve_font_chain_impl(font_families, weight, italic, oblique, None, trace, os)
2765 }
2766
2767 #[cfg(feature = "std")]
2782 pub fn resolve_font_chain_with_scripts(
2783 &self,
2784 font_families: &[String],
2785 weight: FcWeight,
2786 italic: PatternMatch,
2787 oblique: PatternMatch,
2788 scripts_hint: Option<&[UnicodeRange]>,
2789 trace: &mut Vec<TraceMsg>,
2790 ) -> FontFallbackChain {
2791 self.resolve_font_chain_impl(
2792 font_families, weight, italic, oblique, scripts_hint,
2793 trace, OperatingSystem::current(),
2794 )
2795 }
2796
2797 #[cfg(feature = "std")]
2801 fn resolve_font_chain_impl(
2802 &self,
2803 font_families: &[String],
2804 weight: FcWeight,
2805 italic: PatternMatch,
2806 oblique: PatternMatch,
2807 scripts_hint: Option<&[UnicodeRange]>,
2808 trace: &mut Vec<TraceMsg>,
2809 os: OperatingSystem,
2810 ) -> FontFallbackChain {
2811 let scripts_hint_hash = scripts_hint.map(hash_scripts_hint);
2815 let cache_key = FontChainCacheKey {
2816 font_families: font_families.to_vec(),
2817 weight,
2818 italic,
2819 oblique,
2820 scripts_hint_hash,
2821 };
2822
2823 if let Some(cached) = self
2824 .shared
2825 .chain_cache
2826 .lock()
2827 .ok()
2828 .and_then(|c| c.get(&cache_key).cloned())
2829 {
2830 return cached;
2831 }
2832
2833 let expanded_families = expand_font_families(font_families, os, &[]);
2835
2836 let generic_fallbacks: Vec<String> = font_families
2844 .iter()
2845 .filter(|f| config::is_generic_family(f))
2846 .cloned()
2847 .collect();
2848
2849 let chain = self.resolve_font_chain_uncached(
2851 &expanded_families,
2852 &generic_fallbacks,
2853 weight,
2854 italic,
2855 oblique,
2856 scripts_hint,
2857 trace,
2858 );
2859
2860 if let Ok(mut cache) = self.shared.chain_cache.lock() {
2862 cache.insert(cache_key, chain.clone());
2863 }
2864
2865 chain
2866 }
2867
2868 #[cfg(feature = "std")]
2876 fn resolve_font_chain_uncached(
2877 &self,
2878 font_families: &[String],
2879 generic_fallbacks: &[String],
2880 weight: FcWeight,
2881 italic: PatternMatch,
2882 oblique: PatternMatch,
2883 scripts_hint: Option<&[UnicodeRange]>,
2884 trace: &mut Vec<TraceMsg>,
2885 ) -> FontFallbackChain {
2886 let mut css_fallbacks = Vec::new();
2887
2888 for (_i, family) in font_families.iter().enumerate() {
2890 let (pattern, is_generic) = if config::is_generic_family(family) {
2892 let monospace = if family.eq_ignore_ascii_case("monospace") {
2893 PatternMatch::True
2894 } else {
2895 PatternMatch::False
2896 };
2897 let pattern = FcPattern {
2898 name: None,
2899 weight,
2900 italic,
2901 oblique,
2902 monospace,
2903 unicode_ranges: Vec::new(),
2904 ..Default::default()
2905 };
2906 (pattern, true)
2907 } else {
2908 let pattern = FcPattern {
2910 name: Some(family.clone()),
2911 weight,
2912 italic,
2913 oblique,
2914 unicode_ranges: Vec::new(),
2915 ..Default::default()
2916 };
2917 (pattern, false)
2918 };
2919
2920 let mut matches = if is_generic {
2923 self.query_internal(&pattern, trace)
2925 } else {
2926 let mut m = self.fuzzy_query_by_name(family, weight, italic, oblique, &[], trace);
2928 if m.is_empty() {
2941 m = self.query_by_family_normalized(family, weight, italic, oblique);
2942 }
2943 m
2944 };
2945
2946 if is_generic && matches.len() > 5 {
2948 matches.truncate(5);
2949 }
2950
2951 css_fallbacks.push(CssFallbackGroup {
2954 css_name: family.clone(),
2955 fonts: matches,
2956 });
2957 }
2958
2959 if !generic_fallbacks.is_empty()
2976 && css_fallbacks.iter().all(|g| g.fonts.is_empty())
2977 {
2978 for generic in generic_fallbacks {
2979 let monospace = if generic.eq_ignore_ascii_case("monospace") {
2980 PatternMatch::True
2981 } else {
2982 PatternMatch::False
2983 };
2984 let pattern = FcPattern {
2985 name: None,
2986 weight,
2987 italic,
2988 oblique,
2989 monospace,
2990 unicode_ranges: Vec::new(),
2991 ..Default::default()
2992 };
2993 let mut matches = self.query_internal(&pattern, trace);
2994 if matches.len() > 5 {
2995 matches.truncate(5);
2996 }
2997 if !matches.is_empty() {
2998 css_fallbacks.push(CssFallbackGroup {
2999 css_name: generic.clone(),
3000 fonts: matches,
3001 });
3002 }
3003 }
3004 }
3005
3006 let important_ranges: &[UnicodeRange] =
3019 scripts_hint.unwrap_or(DEFAULT_UNICODE_FALLBACK_SCRIPTS);
3020 let unicode_fallbacks = if important_ranges.is_empty() {
3021 Vec::new()
3022 } else {
3023 let all_uncovered = vec![false; important_ranges.len()];
3024 self.find_unicode_fallbacks(
3025 important_ranges,
3026 &all_uncovered,
3027 &css_fallbacks,
3028 weight,
3029 italic,
3030 oblique,
3031 trace,
3032 )
3033 };
3034
3035 let mut unicode_fallbacks = unicode_fallbacks;
3043 if css_fallbacks.is_empty() && unicode_fallbacks.is_empty() {
3044 let st = self.state_read();
3045 if let Some((pat, id)) = st.patterns.iter().next() {
3046 unicode_fallbacks.push(FontMatch {
3047 id: *id,
3048 unicode_ranges: pat.unicode_ranges.clone(),
3049 fallbacks: Vec::new(),
3050 });
3051 }
3052 }
3053
3054 FontFallbackChain {
3055 css_fallbacks,
3056 unicode_fallbacks,
3057 original_stack: font_families.to_vec(),
3058 }
3059 }
3060
3061 #[allow(dead_code)]
3063 fn extract_unicode_ranges(text: &str) -> Vec<UnicodeRange> {
3064 let mut chars: Vec<char> = text.chars().collect();
3065 chars.sort_unstable();
3066 chars.dedup();
3067
3068 if chars.is_empty() {
3069 return Vec::new();
3070 }
3071
3072 let mut ranges = Vec::new();
3073 let mut range_start = chars[0] as u32;
3074 let mut range_end = range_start;
3075
3076 for &c in &chars[1..] {
3077 let codepoint = c as u32;
3078 if codepoint == range_end + 1 {
3079 range_end = codepoint;
3080 } else {
3081 ranges.push(UnicodeRange { start: range_start, end: range_end });
3082 range_start = codepoint;
3083 range_end = codepoint;
3084 }
3085 }
3086
3087 ranges.push(UnicodeRange { start: range_start, end: range_end });
3088 ranges
3089 }
3090
3091 #[cfg(feature = "std")]
3098 fn fuzzy_query_by_name(
3099 &self,
3100 requested_name: &str,
3101 weight: FcWeight,
3102 italic: PatternMatch,
3103 oblique: PatternMatch,
3104 unicode_ranges: &[UnicodeRange],
3105 _trace: &mut Vec<TraceMsg>,
3106 ) -> Vec<FontMatch> {
3107 let tokens = Self::extract_font_name_tokens(requested_name);
3109
3110 if tokens.is_empty() {
3111 return Vec::new();
3112 }
3113
3114 let tokens_lower: Vec<String> = tokens.iter().map(|t| t.to_ascii_lowercase()).collect();
3116
3117 let state = self.state_read();
3123
3124 let first_token = &tokens_lower[0];
3126 let mut candidate_ids = match state.token_index.get(first_token) {
3127 Some(ids) if !ids.is_empty() => ids.clone(),
3128 _ => {
3129 return Vec::new();
3131 }
3132 };
3133
3134 for token in &tokens_lower[1..] {
3136 if let Some(token_ids) = state.token_index.get(token) {
3137 let intersection: alloc::collections::BTreeSet<FontId> =
3139 candidate_ids.intersection(token_ids).copied().collect();
3140
3141 if intersection.is_empty() {
3142 break;
3144 } else {
3145 candidate_ids = intersection;
3147 }
3148 } else {
3149 break;
3151 }
3152 }
3153
3154 let mut candidates = Vec::new();
3156
3157 for id in candidate_ids {
3158 let pattern = match state.metadata.get(&id) {
3159 Some(p) => p,
3160 None => continue,
3161 };
3162
3163 let font_tokens_lower = match state.font_tokens.get(&id) {
3165 Some(tokens) => tokens,
3166 None => continue,
3167 };
3168
3169 if font_tokens_lower.is_empty() {
3170 continue;
3171 }
3172
3173 let token_matches = tokens_lower.iter()
3176 .filter(|req_token| {
3177 font_tokens_lower.iter().any(|font_token| {
3178 font_token == *req_token
3180 })
3181 })
3182 .count();
3183
3184 if token_matches == 0 {
3186 continue;
3187 }
3188
3189 let token_similarity = (token_matches * 100 / tokens.len()) as i32;
3191
3192 let unicode_similarity = if !unicode_ranges.is_empty() && !pattern.unicode_ranges.is_empty() {
3194 Self::calculate_unicode_compatibility(unicode_ranges, &pattern.unicode_ranges)
3195 } else {
3196 0
3197 };
3198
3199 if !unicode_ranges.is_empty() && unicode_similarity == 0 {
3202 continue;
3203 }
3204
3205 let style_score = Self::calculate_style_score(&FcPattern {
3206 weight,
3207 italic,
3208 oblique,
3209 ..Default::default()
3210 }, pattern);
3211
3212 candidates.push((
3213 id,
3214 token_similarity,
3215 unicode_similarity,
3216 style_score,
3217 pattern.clone(),
3218 ));
3219 }
3220
3221 candidates.sort_by(|a, b| {
3227 if !unicode_ranges.is_empty() {
3228 b.1.cmp(&a.1) .then_with(|| b.2.cmp(&a.2)) .then_with(|| a.3.cmp(&b.3)) .then_with(|| a.4.italic.cmp(&b.4.italic)) .then_with(|| a.4.name.cmp(&b.4.name)) } else {
3235 b.1.cmp(&a.1) .then_with(|| a.3.cmp(&b.3)) .then_with(|| a.4.italic.cmp(&b.4.italic)) .then_with(|| a.4.name.cmp(&b.4.name)) }
3241 });
3242
3243 candidates.truncate(5);
3245
3246 candidates
3248 .into_iter()
3249 .map(|(id, _token_sim, _unicode_sim, _style, pattern)| {
3250 FontMatch {
3251 id,
3252 unicode_ranges: pattern.unicode_ranges.clone(),
3253 fallbacks: Vec::new(), }
3255 })
3256 .collect()
3257 }
3258
3259 fn query_by_family_normalized(
3276 &self,
3277 family: &str,
3278 weight: FcWeight,
3279 italic: PatternMatch,
3280 oblique: PatternMatch,
3281 ) -> Vec<FontMatch> {
3282 let target = crate::utils::normalize_family_name(family);
3283 if target.is_empty() {
3284 return Vec::new();
3285 }
3286 let query = FcPattern {
3287 weight,
3288 italic,
3289 oblique,
3290 ..Default::default()
3291 };
3292 let state = self.state_read();
3293 let mut candidates: Vec<(FontId, i32, FcPattern)> = Vec::new();
3294 for (stored_pattern, id) in &state.patterns {
3295 let meta = state.metadata.get(id).unwrap_or(stored_pattern);
3296 let fam_norm = meta
3297 .family
3298 .as_deref()
3299 .map(crate::utils::normalize_family_name)
3300 .unwrap_or_default();
3301 let matches_family = fam_norm == target
3302 || meta
3303 .name
3304 .as_deref()
3305 .map(crate::utils::normalize_family_name)
3306 .is_some_and(|n| n == target);
3307 if !matches_family {
3308 continue;
3309 }
3310 let style_score = Self::calculate_style_score(&query, meta);
3311 candidates.push((*id, style_score, meta.clone()));
3312 }
3313 drop(state);
3314
3315 candidates.sort_by(|a, b| {
3317 a.1.cmp(&b.1)
3318 .then_with(|| a.2.italic.cmp(&b.2.italic))
3319 .then_with(|| a.2.name.cmp(&b.2.name))
3320 });
3321 candidates.truncate(5);
3322 candidates
3323 .into_iter()
3324 .map(|(id, _, pattern)| FontMatch {
3325 id,
3326 unicode_ranges: pattern.unicode_ranges.clone(),
3327 fallbacks: Vec::new(),
3328 })
3329 .collect()
3330 }
3331
3332 pub fn extract_font_name_tokens(name: &str) -> Vec<String> {
3336 let mut tokens = Vec::new();
3337 let mut current_token = String::new();
3338 let mut last_was_lower = false;
3339
3340 for c in name.chars() {
3341 if c.is_whitespace() || c == '-' || c == '_' {
3342 if !current_token.is_empty() {
3344 tokens.push(current_token.clone());
3345 current_token.clear();
3346 }
3347 last_was_lower = false;
3348 } else if c.is_uppercase() && last_was_lower && !current_token.is_empty() {
3349 tokens.push(current_token.clone());
3351 current_token.clear();
3352 current_token.push(c);
3353 last_was_lower = false;
3354 } else {
3355 current_token.push(c);
3356 last_was_lower = c.is_lowercase();
3357 }
3358 }
3359
3360 if !current_token.is_empty() {
3361 tokens.push(current_token);
3362 }
3363
3364 tokens
3365 }
3366
3367 fn find_unicode_fallbacks(
3371 &self,
3372 unicode_ranges: &[UnicodeRange],
3373 covered_chars: &[bool],
3374 existing_groups: &[CssFallbackGroup],
3375 _weight: FcWeight,
3376 _italic: PatternMatch,
3377 _oblique: PatternMatch,
3378 trace: &mut Vec<TraceMsg>,
3379 ) -> Vec<FontMatch> {
3380 let mut uncovered_ranges = Vec::new();
3382 for (i, &covered) in covered_chars.iter().enumerate() {
3383 if !covered && i < unicode_ranges.len() {
3384 uncovered_ranges.push(unicode_ranges[i].clone());
3385 }
3386 }
3387
3388 if uncovered_ranges.is_empty() {
3389 return Vec::new();
3390 }
3391
3392 let pattern = FcPattern {
3397 name: None,
3398 weight: FcWeight::Normal, italic: PatternMatch::DontCare,
3400 oblique: PatternMatch::DontCare,
3401 unicode_ranges: uncovered_ranges.clone(),
3402 ..Default::default()
3403 };
3404
3405 let mut candidates = self.query_internal(&pattern, trace);
3406
3407 let existing_prefixes: Vec<String> = existing_groups
3410 .iter()
3411 .flat_map(|group| {
3412 group.fonts.iter().filter_map(|font| {
3413 self.get_metadata_by_id(&font.id)
3414 .and_then(|meta| meta.family.clone())
3415 .and_then(|family| {
3416 family.split_whitespace()
3418 .take(2)
3419 .collect::<Vec<_>>()
3420 .join(" ")
3421 .into()
3422 })
3423 })
3424 })
3425 .collect();
3426
3427 candidates.sort_by(|a, b| {
3431 let a_meta = self.get_metadata_by_id(&a.id);
3432 let b_meta = self.get_metadata_by_id(&b.id);
3433
3434 let a_score = Self::calculate_font_similarity_score(a_meta.as_ref(), &existing_prefixes);
3435 let b_score = Self::calculate_font_similarity_score(b_meta.as_ref(), &existing_prefixes);
3436
3437 b_score.cmp(&a_score) .then_with(|| {
3439 let a_coverage = Self::calculate_unicode_compatibility(&uncovered_ranges, &a.unicode_ranges);
3440 let b_coverage = Self::calculate_unicode_compatibility(&uncovered_ranges, &b.unicode_ranges);
3441 b_coverage.cmp(&a_coverage)
3442 })
3443 });
3444
3445 let mut result = Vec::new();
3447 let mut remaining_uncovered: Vec<bool> = vec![true; uncovered_ranges.len()];
3448
3449 for candidate in candidates {
3450 let mut covers_new_range = false;
3452
3453 for (i, range) in uncovered_ranges.iter().enumerate() {
3454 if remaining_uncovered[i] {
3455 for font_range in &candidate.unicode_ranges {
3457 if font_range.overlaps(range) {
3458 remaining_uncovered[i] = false;
3459 covers_new_range = true;
3460 break;
3461 }
3462 }
3463 }
3464 }
3465
3466 if covers_new_range {
3468 result.push(candidate);
3469
3470 if remaining_uncovered.iter().all(|&uncovered| !uncovered) {
3472 break;
3473 }
3474 }
3475 }
3476
3477 result
3478 }
3479
3480 fn calculate_font_similarity_score(
3483 font_meta: Option<&FcPattern>,
3484 existing_prefixes: &[String],
3485 ) -> i32 {
3486 let Some(meta) = font_meta else { return 0; };
3487 let Some(family) = &meta.family else { return 0; };
3488
3489 for prefix in existing_prefixes {
3491 if family.starts_with(prefix) {
3492 return 100; }
3494 if family.contains(prefix) {
3495 return 50; }
3497 }
3498
3499 0 }
3501
3502 pub fn calculate_unicode_coverage(ranges: &[UnicodeRange]) -> u64 {
3505 ranges
3506 .iter()
3507 .map(|range| (range.end - range.start + 1) as u64)
3508 .sum()
3509 }
3510
3511 pub fn normalize_unicode_ranges(mut ranges: Vec<UnicodeRange>) -> Vec<UnicodeRange> {
3524 if ranges.len() < 2 {
3525 return ranges;
3526 }
3527
3528 ranges.sort_unstable();
3529
3530 let mut out: Vec<UnicodeRange> = Vec::with_capacity(ranges.len());
3531 for range in ranges {
3532 match out.last_mut() {
3533 Some(prev) if range.start <= prev.end.saturating_add(1) => {
3536 prev.end = prev.end.max(range.end);
3537 }
3538 _ => out.push(range),
3539 }
3540 }
3541 out
3542 }
3543
3544 pub fn calculate_unicode_compatibility(
3547 requested: &[UnicodeRange],
3548 available: &[UnicodeRange],
3549 ) -> i32 {
3550 if requested.is_empty() {
3551 return Self::calculate_unicode_coverage(available) as i32;
3553 }
3554
3555 let mut total_coverage = 0u32;
3556
3557 for req_range in requested {
3558 for avail_range in available {
3559 let overlap_start = req_range.start.max(avail_range.start);
3561 let overlap_end = req_range.end.min(avail_range.end);
3562
3563 if overlap_start <= overlap_end {
3564 let overlap_size = overlap_end - overlap_start + 1;
3566 total_coverage += overlap_size;
3567 }
3568 }
3569 }
3570
3571 total_coverage as i32
3572 }
3573
3574 pub fn calculate_style_score(original: &FcPattern, candidate: &FcPattern) -> i32 {
3575
3576 let mut score = 0_i32;
3577
3578 if (original.bold == PatternMatch::True && candidate.weight == FcWeight::Bold)
3580 || (original.bold == PatternMatch::False && candidate.weight != FcWeight::Bold)
3581 {
3582 } else {
3585 let weight_diff = (original.weight as i32 - candidate.weight as i32).abs();
3587 score += weight_diff as i32;
3588 }
3589
3590 if original.weight == candidate.weight {
3593 score -= 15;
3594 if original.weight == FcWeight::Normal {
3595 score -= 10; }
3597 }
3598
3599 if (original.condensed == PatternMatch::True && candidate.stretch.is_condensed())
3601 || (original.condensed == PatternMatch::False && !candidate.stretch.is_condensed())
3602 {
3603 } else {
3606 let stretch_diff = (original.stretch as i32 - candidate.stretch as i32).abs();
3608 score += (stretch_diff * 100) as i32;
3609 }
3610
3611 let style_props = [
3613 (original.italic, candidate.italic, 300, 150),
3614 (original.oblique, candidate.oblique, 200, 100),
3615 (original.bold, candidate.bold, 300, 150),
3616 (original.monospace, candidate.monospace, 100, 50),
3617 (original.condensed, candidate.condensed, 100, 50),
3618 ];
3619
3620 for (orig, cand, mismatch_penalty, dontcare_penalty) in style_props {
3621 if orig.needs_to_match() {
3622 if orig == PatternMatch::False && cand == PatternMatch::DontCare {
3623 score += dontcare_penalty / 2;
3626 } else if !orig.matches(&cand) {
3627 if cand == PatternMatch::DontCare {
3628 score += dontcare_penalty;
3629 } else {
3630 score += mismatch_penalty;
3631 }
3632 } else if orig == PatternMatch::True && cand == PatternMatch::True {
3633 score -= 20;
3635 } else if orig == PatternMatch::False && cand == PatternMatch::False {
3636 score -= 20;
3639 }
3640 } else {
3641 if cand == PatternMatch::True {
3646 score += dontcare_penalty / 3;
3647 }
3648 }
3649 }
3650
3651 if let (Some(name), Some(family)) = (&candidate.name, &candidate.family) {
3657 let name_lower = name.to_ascii_lowercase();
3658 let family_lower = family.to_ascii_lowercase();
3659
3660 let extra = if name_lower.starts_with(&family_lower) {
3662 name_lower[family_lower.len()..].to_string()
3663 } else {
3664 String::new()
3665 };
3666
3667 let stripped = extra
3669 .replace("regular", "")
3670 .replace("normal", "")
3671 .replace("book", "")
3672 .replace("roman", "");
3673 let stripped = stripped.trim();
3674
3675 if stripped.is_empty() {
3676 score -= 50;
3678 } else {
3679 let extra_words = stripped.split_whitespace().count();
3681 score += (extra_words as i32) * 25;
3682 }
3683 }
3684
3685 if let Some(ref subfamily) = candidate.metadata.font_subfamily {
3689 let sf_lower = subfamily.to_ascii_lowercase();
3690 if sf_lower == "regular" {
3691 score -= 30;
3692 }
3693 }
3694
3695 score
3696 }
3697}
3698
3699#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3700fn FcScanDirectories() -> Option<(
3701 Vec<(FcPattern, FcFontPath)>,
3702 BTreeMap<String, FcFontRenderConfig>,
3703 BTreeMap<String, Vec<String>>,
3704)> {
3705 use std::fs;
3706 use std::path::Path;
3707
3708 let base_path = std::env::var("FONTCONFIG_FILE")
3711 .ok()
3712 .filter(|p| !p.is_empty())
3713 .unwrap_or_else(|| "/etc/fonts/fonts.conf".to_string());
3714
3715 if !Path::new(&base_path).exists() {
3716 return None;
3717 }
3718
3719 let mut font_paths = Vec::with_capacity(32);
3720 let mut paths_to_visit = vec![(None, PathBuf::from(&base_path))];
3721 let mut render_configs: BTreeMap<String, FcFontRenderConfig> = BTreeMap::new();
3722 let mut system_aliases: BTreeMap<String, Vec<String>> = BTreeMap::new();
3723
3724 while let Some((prefix, path_to_visit)) = paths_to_visit.pop() {
3725 let path = match process_path(&prefix, path_to_visit, true) {
3726 Some(path) => path,
3727 None => continue,
3728 };
3729
3730 let metadata = match fs::metadata(&path) {
3731 Ok(metadata) => metadata,
3732 Err(_) => continue,
3733 };
3734
3735 if metadata.is_file() {
3736 let xml_utf8 = match fs::read_to_string(&path) {
3737 Ok(xml_utf8) => xml_utf8,
3738 Err(_) => continue,
3739 };
3740
3741 if ParseFontsConf(&xml_utf8, &mut paths_to_visit, &mut font_paths).is_none() {
3742 continue;
3743 }
3744
3745 ParseFontsConfRenderConfig(&xml_utf8, &mut render_configs);
3747
3748 ParseFontsConfAliases(&xml_utf8, &mut system_aliases);
3751 } else if metadata.is_dir() {
3752 let dir_entries = match fs::read_dir(&path) {
3753 Ok(dir_entries) => dir_entries,
3754 Err(_) => continue,
3755 };
3756
3757 for entry_result in dir_entries {
3758 let entry = match entry_result {
3759 Ok(entry) => entry,
3760 Err(_) => continue,
3761 };
3762
3763 let entry_path = entry.path();
3764
3765 let entry_metadata = match fs::metadata(&entry_path) {
3767 Ok(metadata) => metadata,
3768 Err(_) => continue,
3769 };
3770
3771 if !entry_metadata.is_file() {
3772 continue;
3773 }
3774
3775 let file_name = match entry_path.file_name() {
3776 Some(name) => name,
3777 None => continue,
3778 };
3779
3780 let file_name_str = file_name.to_string_lossy();
3781 if file_name_str.starts_with(|c: char| c.is_ascii_digit())
3782 && file_name_str.ends_with(".conf")
3783 {
3784 paths_to_visit.push((None, entry_path));
3785 }
3786 }
3787 }
3788 }
3789
3790 if font_paths.is_empty() {
3791 return None;
3792 }
3793
3794 Some((FcScanDirectoriesInner(&font_paths), render_configs, system_aliases))
3795}
3796
3797#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3804fn ParseFontsConfAliases(input: &str, aliases: &mut BTreeMap<String, Vec<String>>) {
3805 use xmlparser::Token::*;
3806 use xmlparser::Tokenizer;
3807
3808 #[derive(Clone, Copy, PartialEq)]
3809 enum State {
3810 Idle,
3811 InAlias,
3812 InAliasFamily,
3813 InPrefer,
3814 InPreferFamily,
3815 }
3816
3817 let mut state = State::Idle;
3818 let mut alias_key: Option<String> = None;
3819 let mut preferred: Vec<String> = Vec::new();
3820 let mut text_buf = String::new();
3821
3822 for token_result in Tokenizer::from(input) {
3823 let token = match token_result {
3824 Ok(token) => token,
3825 Err(_) => continue,
3826 };
3827 match token {
3828 ElementStart { local, .. } => match local.as_str() {
3829 "alias" => {
3830 state = State::InAlias;
3831 alias_key = None;
3832 preferred.clear();
3833 }
3834 "family" if state == State::InAlias => {
3835 state = State::InAliasFamily;
3836 text_buf.clear();
3837 }
3838 "prefer" if state == State::InAlias => {
3839 state = State::InPrefer;
3840 }
3841 "family" if state == State::InPrefer => {
3842 state = State::InPreferFamily;
3843 text_buf.clear();
3844 }
3845 _ => {}
3846 },
3847 Text { text } => {
3848 if state == State::InAliasFamily || state == State::InPreferFamily {
3849 text_buf.push_str(text.as_str());
3850 }
3851 }
3852 ElementEnd { end, .. } => {
3853 use xmlparser::ElementEnd;
3854 let closed = match end {
3855 ElementEnd::Close(_, local) => Some(local.as_str().to_owned()),
3856 _ => None,
3857 };
3858 let Some(closed) = closed else { continue };
3859 match closed.as_str() {
3860 "family" => match state {
3861 State::InAliasFamily => {
3862 let t = text_buf.trim();
3863 if !t.is_empty() && alias_key.is_none() {
3864 alias_key = Some(t.to_owned());
3865 }
3866 state = State::InAlias;
3867 }
3868 State::InPreferFamily => {
3869 let t = text_buf.trim();
3870 if !t.is_empty() {
3871 preferred.push(t.to_owned());
3872 }
3873 state = State::InPrefer;
3874 }
3875 _ => {}
3876 },
3877 "prefer" if state == State::InPrefer => {
3878 state = State::InAlias;
3879 }
3880 "alias" => {
3881 if let Some(key) = alias_key.take() {
3882 if !preferred.is_empty() {
3883 let norm = crate::utils::normalize_family_name(&key);
3884 let entry = aliases.entry(norm).or_default();
3885 for fam in preferred.drain(..) {
3886 if !entry.iter().any(|e| e == &fam) {
3887 entry.push(fam);
3888 }
3889 }
3890 }
3891 }
3892 state = State::Idle;
3893 }
3894 _ => {}
3895 }
3896 }
3897 _ => {}
3898 }
3899 }
3900}
3901
3902#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3904fn ParseFontsConf(
3905 input: &str,
3906 paths_to_visit: &mut Vec<(Option<String>, PathBuf)>,
3907 font_paths: &mut Vec<(Option<String>, String)>,
3908) -> Option<()> {
3909 use xmlparser::Token::*;
3910 use xmlparser::Tokenizer;
3911
3912 const TAG_INCLUDE: &str = "include";
3913 const TAG_DIR: &str = "dir";
3914 const ATTRIBUTE_PREFIX: &str = "prefix";
3915
3916 let mut current_prefix: Option<&str> = None;
3917 let mut current_path: Option<&str> = None;
3918 let mut is_in_include = false;
3919 let mut is_in_dir = false;
3920
3921 for token_result in Tokenizer::from(input) {
3922 let token = match token_result {
3923 Ok(token) => token,
3924 Err(_) => return None,
3925 };
3926
3927 match token {
3928 ElementStart { local, .. } => {
3929 if is_in_include || is_in_dir {
3930 return None; }
3932
3933 match local.as_str() {
3934 TAG_INCLUDE => {
3935 is_in_include = true;
3936 }
3937 TAG_DIR => {
3938 is_in_dir = true;
3939 }
3940 _ => continue,
3941 }
3942
3943 current_path = None;
3944 }
3945 Text { text, .. } => {
3946 let text = text.as_str().trim();
3947 if text.is_empty() {
3948 continue;
3949 }
3950 if is_in_include || is_in_dir {
3951 current_path = Some(text);
3952 }
3953 }
3954 Attribute { local, value, .. } => {
3955 if !is_in_include && !is_in_dir {
3956 continue;
3957 }
3958 if local.as_str() == ATTRIBUTE_PREFIX {
3960 current_prefix = Some(value.as_str());
3961 }
3962 }
3963 ElementEnd { end, .. } => {
3964 let end_tag = match end {
3965 xmlparser::ElementEnd::Close(_, a) => a,
3966 _ => continue,
3967 };
3968
3969 match end_tag.as_str() {
3970 TAG_INCLUDE => {
3971 if !is_in_include {
3972 continue;
3973 }
3974
3975 if let Some(current_path) = current_path.as_ref() {
3976 paths_to_visit.push((
3977 current_prefix.map(ToOwned::to_owned),
3978 PathBuf::from(*current_path),
3979 ));
3980 }
3981 }
3982 TAG_DIR => {
3983 if !is_in_dir {
3984 continue;
3985 }
3986
3987 if let Some(current_path) = current_path.as_ref() {
3988 font_paths.push((
3989 current_prefix.map(ToOwned::to_owned),
3990 (*current_path).to_owned(),
3991 ));
3992 }
3993 }
3994 _ => continue,
3995 }
3996
3997 is_in_include = false;
3998 is_in_dir = false;
3999 current_path = None;
4000 current_prefix = None;
4001 }
4002 _ => {}
4003 }
4004 }
4005
4006 Some(())
4007}
4008
4009#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
4021fn ParseFontsConfRenderConfig(
4022 input: &str,
4023 configs: &mut BTreeMap<String, FcFontRenderConfig>,
4024) {
4025 use xmlparser::Token::*;
4026 use xmlparser::Tokenizer;
4027
4028 #[derive(Clone, Copy, PartialEq)]
4030 enum State {
4031 Idle,
4033 InMatchFont,
4035 InTestFamily,
4037 InEdit,
4039 InValue,
4041 }
4042
4043 let mut state = State::Idle;
4044 let mut match_is_font_target = false;
4045 let mut current_family: Option<String> = None;
4046 let mut current_edit_name: Option<String> = None;
4047 let mut current_value: Option<String> = None;
4048 let mut value_tag: Option<String> = None;
4049 let mut config = FcFontRenderConfig::default();
4050 let mut in_test = false;
4051 let mut test_name: Option<String> = None;
4052
4053 for token_result in Tokenizer::from(input) {
4054 let token = match token_result {
4055 Ok(token) => token,
4056 Err(_) => continue,
4057 };
4058
4059 match token {
4060 ElementStart { local, .. } => {
4061 let tag = local.as_str();
4062 match tag {
4063 "match" => {
4064 match_is_font_target = false;
4066 current_family = None;
4067 config = FcFontRenderConfig::default();
4068 }
4069 "test" if state == State::InMatchFont => {
4070 in_test = true;
4071 test_name = None;
4072 }
4073 "edit" if state == State::InMatchFont => {
4074 current_edit_name = None;
4075 }
4076 "bool" | "double" | "const" | "string" | "int" => {
4077 if state == State::InTestFamily || state == State::InEdit {
4078 value_tag = Some(tag.to_owned());
4079 current_value = None;
4080 }
4081 }
4082 _ => {}
4083 }
4084 }
4085 Attribute { local, value, .. } => {
4086 let attr_name = local.as_str();
4087 let attr_value = value.as_str();
4088
4089 match attr_name {
4090 "target" => {
4091 if attr_value == "font" {
4092 match_is_font_target = true;
4093 }
4094 }
4095 "name" => {
4096 if in_test && state == State::InMatchFont {
4097 test_name = Some(attr_value.to_owned());
4098 } else if state == State::InMatchFont {
4099 current_edit_name = Some(attr_value.to_owned());
4100 }
4101 }
4102 _ => {}
4103 }
4104 }
4105 Text { text, .. } => {
4106 let text = text.as_str().trim();
4107 if !text.is_empty() && (state == State::InTestFamily || state == State::InEdit) {
4108 current_value = Some(text.to_owned());
4109 }
4110 }
4111 ElementEnd { end, .. } => {
4112 match end {
4113 xmlparser::ElementEnd::Open => {
4114 if match_is_font_target && state == State::Idle {
4116 state = State::InMatchFont;
4117 match_is_font_target = false;
4118 } else if in_test {
4119 if test_name.as_deref() == Some("family") {
4120 state = State::InTestFamily;
4121 }
4122 in_test = false;
4123 } else if current_edit_name.is_some() && state == State::InMatchFont {
4124 state = State::InEdit;
4125 }
4126 }
4127 xmlparser::ElementEnd::Close(_, local) => {
4128 let tag = local.as_str();
4129 match tag {
4130 "match" => {
4131 if let Some(family) = current_family.take() {
4133 let empty = FcFontRenderConfig::default();
4134 if config != empty {
4135 configs.insert(family, config.clone());
4136 }
4137 }
4138 state = State::Idle;
4139 config = FcFontRenderConfig::default();
4140 }
4141 "test" => {
4142 if state == State::InTestFamily {
4143 if let Some(ref val) = current_value {
4145 current_family = Some(val.clone());
4146 }
4147 state = State::InMatchFont;
4148 }
4149 current_value = None;
4150 value_tag = None;
4151 }
4152 "edit" => {
4153 if state == State::InEdit {
4154 if let (Some(ref name), Some(ref val)) = (¤t_edit_name, ¤t_value) {
4156 apply_edit_value(&mut config, name, val, value_tag.as_deref());
4157 }
4158 state = State::InMatchFont;
4159 }
4160 current_edit_name = None;
4161 current_value = None;
4162 value_tag = None;
4163 }
4164 "bool" | "double" | "const" | "string" | "int" => {
4165 }
4167 _ => {}
4168 }
4169 }
4170 xmlparser::ElementEnd::Empty => {
4171 }
4173 }
4174 }
4175 _ => {}
4176 }
4177 }
4178}
4179
4180#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
4182fn apply_edit_value(
4183 config: &mut FcFontRenderConfig,
4184 edit_name: &str,
4185 value: &str,
4186 value_tag: Option<&str>,
4187) {
4188 match edit_name {
4189 "antialias" => {
4190 config.antialias = parse_bool_value(value);
4191 }
4192 "hinting" => {
4193 config.hinting = parse_bool_value(value);
4194 }
4195 "autohint" => {
4196 config.autohint = parse_bool_value(value);
4197 }
4198 "embeddedbitmap" => {
4199 config.embeddedbitmap = parse_bool_value(value);
4200 }
4201 "embolden" => {
4202 config.embolden = parse_bool_value(value);
4203 }
4204 "minspace" => {
4205 config.minspace = parse_bool_value(value);
4206 }
4207 "hintstyle" => {
4208 config.hintstyle = parse_hintstyle_const(value);
4209 }
4210 "rgba" => {
4211 config.rgba = parse_rgba_const(value);
4212 }
4213 "lcdfilter" => {
4214 config.lcdfilter = parse_lcdfilter_const(value);
4215 }
4216 "dpi" => {
4217 if let Ok(v) = value.parse::<f64>() {
4218 config.dpi = Some(v);
4219 }
4220 }
4221 "scale" => {
4222 if let Ok(v) = value.parse::<f64>() {
4223 config.scale = Some(v);
4224 }
4225 }
4226 _ => {
4227 }
4229 }
4230}
4231
4232#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
4233fn parse_bool_value(value: &str) -> Option<bool> {
4234 match value {
4235 "true" => Some(true),
4236 "false" => Some(false),
4237 _ => None,
4238 }
4239}
4240
4241#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
4242fn parse_hintstyle_const(value: &str) -> Option<FcHintStyle> {
4243 match value {
4244 "hintnone" => Some(FcHintStyle::None),
4245 "hintslight" => Some(FcHintStyle::Slight),
4246 "hintmedium" => Some(FcHintStyle::Medium),
4247 "hintfull" => Some(FcHintStyle::Full),
4248 _ => None,
4249 }
4250}
4251
4252#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
4253fn parse_rgba_const(value: &str) -> Option<FcRgba> {
4254 match value {
4255 "unknown" => Some(FcRgba::Unknown),
4256 "rgb" => Some(FcRgba::Rgb),
4257 "bgr" => Some(FcRgba::Bgr),
4258 "vrgb" => Some(FcRgba::Vrgb),
4259 "vbgr" => Some(FcRgba::Vbgr),
4260 "none" => Some(FcRgba::None),
4261 _ => None,
4262 }
4263}
4264
4265#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
4266fn parse_lcdfilter_const(value: &str) -> Option<FcLcdFilter> {
4267 match value {
4268 "lcdnone" => Some(FcLcdFilter::None),
4269 "lcddefault" => Some(FcLcdFilter::Default),
4270 "lcdlight" => Some(FcLcdFilter::Light),
4271 "lcdlegacy" => Some(FcLcdFilter::Legacy),
4272 _ => None,
4273 }
4274}
4275
4276#[cfg(all(feature = "std", feature = "parsing"))]
4279const UNICODE_RANGE_MAPPINGS: &[(usize, u32, u32)] = &[
4280 (0, 0x0000, 0x007F), (1, 0x0080, 0x00FF), (2, 0x0100, 0x017F), (3, 0x0180, 0x024F), (4, 0x0250, 0x02AF), (5, 0x02B0, 0x02FF), (6, 0x0300, 0x036F), (7, 0x0370, 0x03FF), (8, 0x2C80, 0x2CFF), (9, 0x0400, 0x04FF), (10, 0x0530, 0x058F), (11, 0x0590, 0x05FF), (12, 0x0600, 0x06FF), (13, 0x0700, 0x074F), (14, 0x0780, 0x07BF), (15, 0x0900, 0x097F), (16, 0x0980, 0x09FF), (17, 0x0A00, 0x0A7F), (18, 0x0A80, 0x0AFF), (19, 0x0B00, 0x0B7F), (20, 0x0B80, 0x0BFF), (21, 0x0C00, 0x0C7F), (22, 0x0C80, 0x0CFF), (23, 0x0D00, 0x0D7F), (24, 0x0E00, 0x0E7F), (25, 0x0E80, 0x0EFF), (26, 0x10A0, 0x10FF), (27, 0x1B00, 0x1B7F), (28, 0x1100, 0x11FF), (29, 0x1E00, 0x1EFF), (30, 0x1F00, 0x1FFF), (31, 0x2000, 0x206F), (32, 0x2070, 0x209F), (33, 0x20A0, 0x20CF), (34, 0x20D0, 0x20FF), (35, 0x2100, 0x214F), (36, 0x2150, 0x218F), (37, 0x2190, 0x21FF), (38, 0x2200, 0x22FF), (39, 0x2300, 0x23FF), (40, 0x2400, 0x243F), (41, 0x2440, 0x245F), (42, 0x2460, 0x24FF), (43, 0x2500, 0x257F), (44, 0x2580, 0x259F), (45, 0x25A0, 0x25FF), (46, 0x2600, 0x26FF), (47, 0x2700, 0x27BF), (48, 0x3000, 0x303F), (49, 0x3040, 0x309F), (50, 0x30A0, 0x30FF), (51, 0x3100, 0x312F), (52, 0x3130, 0x318F), (53, 0x3190, 0x319F), (54, 0x31A0, 0x31BF), (55, 0x31C0, 0x31EF), (56, 0x31F0, 0x31FF), (57, 0x3200, 0x32FF), (58, 0x3300, 0x33FF), (59, 0x4E00, 0x9FFF), (60, 0xA000, 0xA48F), (61, 0xA490, 0xA4CF), (62, 0xAC00, 0xD7AF), (63, 0xD800, 0xDFFF), (64, 0x10000, 0x10FFFF), (65, 0xF900, 0xFAFF), (66, 0xFB00, 0xFB4F), (67, 0xFB50, 0xFDFF), (68, 0xFE00, 0xFE0F), (69, 0xFE10, 0xFE1F), (70, 0xFE20, 0xFE2F), (71, 0xFE30, 0xFE4F), (72, 0xFE50, 0xFE6F), (73, 0xFE70, 0xFEFF), (74, 0xFF00, 0xFFEF), (75, 0xFFF0, 0xFFFF), (76, 0x0F00, 0x0FFF), (77, 0x0700, 0x074F), (78, 0x0780, 0x07BF), (79, 0x0D80, 0x0DFF), (80, 0x1000, 0x109F), (81, 0x1200, 0x137F), (82, 0x13A0, 0x13FF), (83, 0x1400, 0x167F), (84, 0x1680, 0x169F), (85, 0x16A0, 0x16FF), (86, 0x1780, 0x17FF), (87, 0x1800, 0x18AF), (88, 0x2800, 0x28FF), (89, 0xA000, 0xA48F), (90, 0x1680, 0x169F), (91, 0x16A0, 0x16FF), (92, 0x1700, 0x171F), (93, 0x1720, 0x173F), (94, 0x1740, 0x175F), (95, 0x1760, 0x177F), (96, 0x1900, 0x194F), (97, 0x1950, 0x197F), (98, 0x1980, 0x19DF), (99, 0x1A00, 0x1A1F), (100, 0x2C00, 0x2C5F), (101, 0x2D30, 0x2D7F), (102, 0x4DC0, 0x4DFF), (103, 0xA800, 0xA82F), (104, 0x10000, 0x1007F), (105, 0x10080, 0x100FF), (106, 0x10100, 0x1013F), (107, 0x10140, 0x1018F), (108, 0x10300, 0x1032F), (109, 0x10330, 0x1034F), (110, 0x10380, 0x1039F), (111, 0x103A0, 0x103DF), (112, 0x10400, 0x1044F), (113, 0x10450, 0x1047F), (114, 0x10480, 0x104AF), (115, 0x10800, 0x1083F), (116, 0x10A00, 0x10A5F), (117, 0x1D000, 0x1D0FF), (118, 0x1D100, 0x1D1FF), (119, 0x1D200, 0x1D24F), (120, 0x1D300, 0x1D35F), (121, 0x1D400, 0x1D7FF), (122, 0x1F000, 0x1F02F), (123, 0x1F030, 0x1F09F), (124, 0x1F300, 0x1F9FF), (125, 0x1F680, 0x1F6FF), (126, 0x1F700, 0x1F77F), (127, 0x1F900, 0x1F9FF), ];
4413
4414#[cfg(all(feature = "std", feature = "parsing"))]
4417struct ParsedFontFace {
4418 pattern: FcPattern,
4419 font_index: usize,
4420}
4421
4422#[cfg(all(feature = "std", feature = "parsing"))]
4428fn parse_font_faces(font_bytes: &[u8]) -> Option<Vec<ParsedFontFace>> {
4429 use allsorts::{
4430 binary::read::ReadScope,
4431 font_data::FontData,
4432 get_name::fontcode_get_name,
4433 post::PostTable,
4434 tables::{
4435 os2::Os2, HeadTable, NameTable,
4436 },
4437 tag,
4438 };
4439 use std::collections::BTreeSet;
4440
4441 const FONT_SPECIFIER_NAME_ID: u16 = 4;
4442 const FONT_SPECIFIER_FAMILY_ID: u16 = 1;
4443
4444 let max_fonts = if font_bytes.len() >= 12 && &font_bytes[0..4] == b"ttcf" {
4445 let num_fonts =
4447 u32::from_be_bytes([font_bytes[8], font_bytes[9], font_bytes[10], font_bytes[11]]);
4448 std::cmp::min(num_fonts as usize, 100)
4450 } else {
4451 1
4453 };
4454
4455 let scope = ReadScope::new(font_bytes);
4456 let font_file = scope.read::<FontData<'_>>().ok()?;
4457
4458 let mut results = Vec::new();
4460
4461 for font_index in 0..max_fonts {
4462 let provider = font_file.table_provider(font_index).ok()?;
4463 let head_data = provider.table_data(tag::HEAD).ok()??.into_owned();
4464 let head_table = ReadScope::new(&head_data).read::<HeadTable>().ok()?;
4465
4466 let is_bold = head_table.is_bold();
4467 let is_italic = head_table.is_italic();
4468 let mut detected_monospace = None;
4469
4470 let post_data = provider.table_data(tag::POST).ok()??;
4471 if let Ok(post_table) = ReadScope::new(&post_data).read::<PostTable>() {
4472 detected_monospace = Some(post_table.header.is_fixed_pitch != 0);
4474 }
4475
4476 let os2_data = provider.table_data(tag::OS_2).ok().flatten();
4488 let os2_table = os2_data
4489 .as_deref()
4490 .and_then(|data| ReadScope::new(data).read_dep::<Os2>(data.len()).ok());
4491
4492 let is_oblique = os2_table.as_ref().is_some_and(|os2| {
4494 os2.fs_selection
4495 .contains(allsorts::tables::os2::FsSelectionFlag::OBLIQUE)
4496 });
4497 let weight = os2_table.as_ref().map_or(
4500 if is_bold { FcWeight::Bold } else { FcWeight::Normal },
4501 |os2| FcWeight::from_u16(os2.us_weight_class),
4502 );
4503 let stretch = os2_table
4504 .as_ref()
4505 .map_or(FcStretch::Normal, |os2| FcStretch::from_u16(os2.us_width_class));
4506
4507 let mut unicode_ranges = Vec::new();
4511
4512 let os2_ranges = os2_table.as_ref().map_or([0u32; 4], |os2| {
4516 [
4517 os2.ul_unicode_range1,
4518 os2.ul_unicode_range2,
4519 os2.ul_unicode_range3,
4520 os2.ul_unicode_range4,
4521 ]
4522 });
4523
4524 for &(bit, start, end) in UNICODE_RANGE_MAPPINGS {
4525 let range_idx = bit / 32;
4526 let bit_pos = bit % 32;
4527 if range_idx < 4 && (os2_ranges[range_idx] & (1 << bit_pos)) != 0 {
4528 unicode_ranges.push(UnicodeRange { start, end });
4529 }
4530 }
4531
4532 unicode_ranges = verify_unicode_ranges_with_cmap(&provider, unicode_ranges);
4550
4551 if let Some(cmap_ranges) = analyze_cmap_coverage(&provider) {
4552 unicode_ranges.extend(cmap_ranges);
4553 }
4554
4555 unicode_ranges = FcFontCache::normalize_unicode_ranges(unicode_ranges);
4559
4560 let is_monospace = detect_monospace(&provider, os2_table.as_ref(), detected_monospace)
4562 .unwrap_or(false);
4563
4564 let name_data = provider.table_data(tag::NAME).ok()??.into_owned();
4565 let name_table = ReadScope::new(&name_data).read::<NameTable>().ok()?;
4566
4567 let mut metadata = FcFontMetadata::default();
4569
4570 const NAME_ID_COPYRIGHT: u16 = 0;
4571 const NAME_ID_FAMILY: u16 = 1;
4572 const NAME_ID_SUBFAMILY: u16 = 2;
4573 const NAME_ID_UNIQUE_ID: u16 = 3;
4574 const NAME_ID_FULL_NAME: u16 = 4;
4575 const NAME_ID_VERSION: u16 = 5;
4576 const NAME_ID_POSTSCRIPT_NAME: u16 = 6;
4577 const NAME_ID_TRADEMARK: u16 = 7;
4578 const NAME_ID_MANUFACTURER: u16 = 8;
4579 const NAME_ID_DESIGNER: u16 = 9;
4580 const NAME_ID_DESCRIPTION: u16 = 10;
4581 const NAME_ID_VENDOR_URL: u16 = 11;
4582 const NAME_ID_DESIGNER_URL: u16 = 12;
4583 const NAME_ID_LICENSE: u16 = 13;
4584 const NAME_ID_LICENSE_URL: u16 = 14;
4585 const NAME_ID_PREFERRED_FAMILY: u16 = 16;
4586 const NAME_ID_PREFERRED_SUBFAMILY: u16 = 17;
4587
4588 metadata.copyright = get_name_string(&name_data, NAME_ID_COPYRIGHT);
4589 metadata.font_family = get_name_string(&name_data, NAME_ID_FAMILY);
4590 metadata.font_subfamily = get_name_string(&name_data, NAME_ID_SUBFAMILY);
4591 metadata.full_name = get_name_string(&name_data, NAME_ID_FULL_NAME);
4592 metadata.unique_id = get_name_string(&name_data, NAME_ID_UNIQUE_ID);
4593 metadata.version = get_name_string(&name_data, NAME_ID_VERSION);
4594 metadata.postscript_name = get_name_string(&name_data, NAME_ID_POSTSCRIPT_NAME);
4595 metadata.trademark = get_name_string(&name_data, NAME_ID_TRADEMARK);
4596 metadata.manufacturer = get_name_string(&name_data, NAME_ID_MANUFACTURER);
4597 metadata.designer = get_name_string(&name_data, NAME_ID_DESIGNER);
4598 metadata.id_description = get_name_string(&name_data, NAME_ID_DESCRIPTION);
4599 metadata.designer_url = get_name_string(&name_data, NAME_ID_DESIGNER_URL);
4600 metadata.manufacturer_url = get_name_string(&name_data, NAME_ID_VENDOR_URL);
4601 metadata.license = get_name_string(&name_data, NAME_ID_LICENSE);
4602 metadata.license_url = get_name_string(&name_data, NAME_ID_LICENSE_URL);
4603 metadata.preferred_family = get_name_string(&name_data, NAME_ID_PREFERRED_FAMILY);
4604 metadata.preferred_subfamily = get_name_string(&name_data, NAME_ID_PREFERRED_SUBFAMILY);
4605
4606 let mut f_family = None;
4608
4609 let patterns = name_table
4610 .name_records
4611 .iter()
4612 .filter_map(|name_record| {
4613 let name_id = name_record.name_id;
4614 if name_id == FONT_SPECIFIER_FAMILY_ID {
4615 if let Ok(Some(family)) =
4616 fontcode_get_name(&name_data, FONT_SPECIFIER_FAMILY_ID)
4617 {
4618 f_family = Some(family);
4619 }
4620 None
4621 } else if name_id == FONT_SPECIFIER_NAME_ID {
4622 let family = f_family.as_ref()?;
4623 let name = fontcode_get_name(&name_data, FONT_SPECIFIER_NAME_ID).ok()??;
4624 if name.to_bytes().is_empty() {
4625 None
4626 } else {
4627 let mut name_str =
4628 String::from_utf8_lossy(name.to_bytes()).to_string();
4629 let mut family_str =
4630 String::from_utf8_lossy(family.as_bytes()).to_string();
4631 if name_str.starts_with('.') {
4632 name_str = name_str[1..].to_string();
4633 }
4634 if family_str.starts_with('.') {
4635 family_str = family_str[1..].to_string();
4636 }
4637 Some((
4638 FcPattern {
4639 name: Some(name_str),
4640 family: Some(family_str),
4641 bold: if is_bold {
4642 PatternMatch::True
4643 } else {
4644 PatternMatch::False
4645 },
4646 italic: if is_italic {
4647 PatternMatch::True
4648 } else {
4649 PatternMatch::False
4650 },
4651 oblique: if is_oblique {
4652 PatternMatch::True
4653 } else {
4654 PatternMatch::False
4655 },
4656 monospace: if is_monospace {
4657 PatternMatch::True
4658 } else {
4659 PatternMatch::False
4660 },
4661 condensed: if stretch <= FcStretch::Condensed {
4662 PatternMatch::True
4663 } else {
4664 PatternMatch::False
4665 },
4666 weight,
4667 stretch,
4668 unicode_ranges: unicode_ranges.clone(),
4669 metadata: metadata.clone(),
4670 render_config: FcFontRenderConfig::default(),
4671 },
4672 font_index,
4673 ))
4674 }
4675 } else {
4676 None
4677 }
4678 })
4679 .collect::<BTreeSet<_>>();
4680
4681 results.extend(patterns.into_iter().map(|(pat, idx)| ParsedFontFace {
4682 pattern: pat,
4683 font_index: idx,
4684 }));
4685 }
4686
4687 if results.is_empty() {
4688 None
4689 } else {
4690 Some(results)
4691 }
4692}
4693
4694#[cfg(all(feature = "std", feature = "parsing"))]
4696pub(crate) fn FcParseFont(filepath: &PathBuf) -> Option<Vec<(FcPattern, FcFontPath)>> {
4697 #[cfg(all(not(target_family = "wasm"), feature = "std"))]
4698 use mmapio::MmapOptions;
4699 use std::fs::File;
4700
4701 let file = File::open(filepath).ok()?;
4703
4704 #[cfg(all(not(target_family = "wasm"), feature = "std"))]
4705 let font_bytes = unsafe { MmapOptions::new().map(&file).ok()? };
4706
4707 #[cfg(not(all(not(target_family = "wasm"), feature = "std")))]
4708 let font_bytes = std::fs::read(filepath).ok()?;
4709
4710 let faces = parse_font_faces(&font_bytes[..])?;
4711 let path_str = filepath.to_string_lossy().to_string();
4712 let bytes_hash = crate::utils::content_dedup_hash_u64(&font_bytes[..]);
4717
4718 Some(
4719 faces
4720 .into_iter()
4721 .map(|face| {
4722 (
4723 face.pattern,
4724 FcFontPath {
4725 path: path_str.clone(),
4726 font_index: face.font_index,
4727 bytes_hash,
4728 },
4729 )
4730 })
4731 .collect(),
4732 )
4733}
4734
4735#[cfg(all(feature = "std", feature = "parsing"))]
4752#[derive(Debug, Clone)]
4753pub struct FastCoverage {
4754 pub pattern: FcPattern,
4760 pub covered: alloc::collections::BTreeSet<char>,
4765 pub is_bold: bool,
4767 pub is_italic: bool,
4769}
4770
4771#[cfg(all(feature = "std", feature = "parsing"))]
4788#[allow(non_snake_case)]
4789pub fn FcParseFontFaceFast(
4790 font_bytes: &[u8],
4791 font_index: usize,
4792 codepoints: &alloc::collections::BTreeSet<char>,
4793) -> Option<FastCoverage> {
4794 use allsorts::{
4795 binary::read::ReadScope,
4796 font_data::FontData,
4797 tables::{
4798 cmap::{Cmap, CmapSubtable},
4799 FontTableProvider, HeadTable,
4800 },
4801 tag,
4802 };
4803
4804 let scope = ReadScope::new(font_bytes);
4805 let font_file = scope.read::<FontData<'_>>().ok()?;
4806 let provider = font_file.table_provider(font_index).ok()?;
4807
4808 let head_data = provider.table_data(tag::HEAD).ok()??;
4810 let head_table = ReadScope::new(&head_data).read::<HeadTable>().ok()?;
4811 let is_bold = head_table.is_bold();
4812 let is_italic = head_table.is_italic();
4813
4814 let cmap_data = provider.table_data(tag::CMAP).ok()??;
4817 let cmap = ReadScope::new(&cmap_data).read::<Cmap<'_>>().ok()?;
4818 let encoding_record = find_best_cmap_subtable(&cmap)?;
4819 let cmap_subtable = ReadScope::new(&cmap_data)
4820 .offset(encoding_record.offset as usize)
4821 .read::<CmapSubtable<'_>>()
4822 .ok()?;
4823
4824 let mut covered: alloc::collections::BTreeSet<char> =
4825 alloc::collections::BTreeSet::new();
4826 let mut covered_ranges: Vec<UnicodeRange> = Vec::new();
4827 for ch in codepoints {
4828 let cp = *ch as u32;
4829 if let Ok(Some(gid)) = cmap_subtable.map_glyph(cp) {
4830 if gid != 0 {
4831 covered.insert(*ch);
4832 if let Some(last) = covered_ranges.last_mut() {
4836 if cp == last.end + 1 {
4837 last.end = cp;
4838 continue;
4839 }
4840 }
4841 covered_ranges.push(UnicodeRange { start: cp, end: cp });
4842 }
4843 }
4844 }
4845
4846 let weight = if is_bold {
4847 FcWeight::Bold
4848 } else {
4849 FcWeight::Normal
4850 };
4851 let italic_match = if is_italic {
4852 PatternMatch::True
4853 } else {
4854 PatternMatch::False
4855 };
4856
4857 let pattern = FcPattern {
4858 name: None,
4859 family: None,
4860 weight,
4861 italic: italic_match,
4862 oblique: PatternMatch::DontCare,
4863 monospace: PatternMatch::DontCare,
4864 unicode_ranges: covered_ranges,
4865 ..Default::default()
4866 };
4867
4868 Some(FastCoverage {
4869 pattern,
4870 covered,
4871 is_bold,
4872 is_italic,
4873 })
4874}
4875
4876#[cfg(all(feature = "std", feature = "parsing"))]
4881#[allow(non_snake_case)]
4882pub fn FcCountFontFaces(font_bytes: &[u8]) -> usize {
4883 if font_bytes.len() >= 12 && &font_bytes[0..4] == b"ttcf" {
4884 let num_fonts = u32::from_be_bytes([
4885 font_bytes[8], font_bytes[9], font_bytes[10], font_bytes[11],
4886 ]);
4887 std::cmp::min(num_fonts as usize, 100).max(1)
4889 } else {
4890 1
4891 }
4892}
4893
4894#[cfg(all(feature = "std", feature = "parsing"))]
4920#[allow(non_snake_case)]
4921pub fn FcParseFontBytes(font_bytes: &[u8], font_id: &str) -> Option<Vec<(FcPattern, FcFont)>> {
4922 FcParseFontBytesInner(font_bytes, font_id)
4923}
4924
4925#[cfg(all(feature = "std", feature = "parsing"))]
4928fn FcParseFontBytesInner(font_bytes: &[u8], font_id: &str) -> Option<Vec<(FcPattern, FcFont)>> {
4929 let faces = parse_font_faces(font_bytes)?;
4930 let id = font_id.to_string();
4931 let bytes = font_bytes.to_vec();
4932
4933 Some(
4934 faces
4935 .into_iter()
4936 .map(|face| {
4937 (
4938 face.pattern,
4939 FcFont {
4940 bytes: bytes.clone(),
4941 font_index: face.font_index,
4942 id: id.clone(),
4943 },
4944 )
4945 })
4946 .collect(),
4947 )
4948}
4949
4950#[cfg(all(feature = "std", feature = "parsing"))]
4951fn FcScanDirectoriesInner(paths: &[(Option<String>, String)]) -> Vec<(FcPattern, FcFontPath)> {
4952 #[cfg(all(feature = "multithreading", not(target_family = "wasm")))]
4953 {
4954 use rayon::prelude::*;
4955
4956 paths
4958 .par_iter()
4959 .filter_map(|(prefix, p)| {
4960 process_path(prefix, PathBuf::from(p), false).map(FcScanSingleDirectoryRecursive)
4961 })
4962 .flatten()
4963 .collect()
4964 }
4965 #[cfg(not(all(feature = "multithreading", not(target_family = "wasm"))))]
4968 {
4969 paths
4970 .iter()
4971 .filter_map(|(prefix, p)| {
4972 process_path(prefix, PathBuf::from(p), false).map(FcScanSingleDirectoryRecursive)
4973 })
4974 .flatten()
4975 .collect()
4976 }
4977}
4978
4979#[cfg(feature = "std")]
4981fn FcCollectFontFilesRecursive(dir: PathBuf) -> Vec<PathBuf> {
4982 let mut files = Vec::new();
4983 let mut dirs_to_parse = vec![dir];
4984
4985 loop {
4986 let mut new_dirs = Vec::new();
4987 for dir in &dirs_to_parse {
4988 let entries = match std::fs::read_dir(dir) {
4989 Ok(o) => o,
4990 Err(_) => continue,
4991 };
4992 for entry in entries.flatten() {
4993 let path = entry.path();
4994 if path.is_dir() {
4995 new_dirs.push(path);
4996 } else {
4997 files.push(path);
4998 }
4999 }
5000 }
5001 if new_dirs.is_empty() {
5002 break;
5003 }
5004 dirs_to_parse = new_dirs;
5005 }
5006
5007 files
5008}
5009
5010#[cfg(all(feature = "std", feature = "parsing"))]
5011fn FcScanSingleDirectoryRecursive(dir: PathBuf) -> Vec<(FcPattern, FcFontPath)> {
5012 let files = FcCollectFontFilesRecursive(dir);
5013 FcParseFontFiles(&files)
5014}
5015
5016#[cfg(all(feature = "std", feature = "parsing"))]
5017fn FcParseFontFiles(files_to_parse: &[PathBuf]) -> Vec<(FcPattern, FcFontPath)> {
5018 let result = {
5019 #[cfg(all(feature = "multithreading", not(target_family = "wasm")))]
5020 {
5021 use rayon::prelude::*;
5022
5023 files_to_parse
5024 .par_iter()
5025 .filter_map(|file| FcParseFont(file))
5026 .collect::<Vec<Vec<_>>>()
5027 }
5028 #[cfg(not(all(feature = "multithreading", not(target_family = "wasm"))))]
5029 {
5030 files_to_parse
5031 .iter()
5032 .filter_map(|file| FcParseFont(file))
5033 .collect::<Vec<Vec<_>>>()
5034 }
5035 };
5036
5037 result.into_iter().flat_map(|f| f.into_iter()).collect()
5038}
5039
5040#[cfg(all(feature = "std", feature = "parsing"))]
5041fn process_path(
5045 prefix: &Option<String>,
5046 mut path: PathBuf,
5047 is_include_path: bool,
5048) -> Option<PathBuf> {
5049 use std::env::var;
5050
5051 const HOME_SHORTCUT: &str = "~";
5052 const CWD_PATH: &str = ".";
5053
5054 const HOME_ENV_VAR: &str = "HOME";
5055 const XDG_CONFIG_HOME_ENV_VAR: &str = "XDG_CONFIG_HOME";
5056 const XDG_CONFIG_HOME_DEFAULT_PATH_SUFFIX: &str = ".config";
5057 const XDG_DATA_HOME_ENV_VAR: &str = "XDG_DATA_HOME";
5058 const XDG_DATA_HOME_DEFAULT_PATH_SUFFIX: &str = ".local/share";
5059
5060 const PREFIX_CWD: &str = "cwd";
5061 const PREFIX_DEFAULT: &str = "default";
5062 const PREFIX_XDG: &str = "xdg";
5063
5064 fn get_home_value() -> Option<PathBuf> {
5066 var(HOME_ENV_VAR).ok().map(PathBuf::from)
5067 }
5068 fn get_xdg_config_home_value() -> Option<PathBuf> {
5069 var(XDG_CONFIG_HOME_ENV_VAR)
5070 .ok()
5071 .map(PathBuf::from)
5072 .or_else(|| {
5073 get_home_value()
5074 .map(|home_path| home_path.join(XDG_CONFIG_HOME_DEFAULT_PATH_SUFFIX))
5075 })
5076 }
5077 fn get_xdg_data_home_value() -> Option<PathBuf> {
5078 var(XDG_DATA_HOME_ENV_VAR)
5079 .ok()
5080 .map(PathBuf::from)
5081 .or_else(|| {
5082 get_home_value().map(|home_path| home_path.join(XDG_DATA_HOME_DEFAULT_PATH_SUFFIX))
5083 })
5084 }
5085
5086 if path.starts_with(HOME_SHORTCUT) {
5088 if let Some(home_path) = get_home_value() {
5089 path = home_path.join(
5090 path.strip_prefix(HOME_SHORTCUT)
5091 .expect("already checked that it starts with the prefix"),
5092 );
5093 } else {
5094 return None;
5095 }
5096 }
5097
5098 match prefix {
5100 Some(prefix) => match prefix.as_str() {
5101 PREFIX_CWD | PREFIX_DEFAULT => {
5102 let mut new_path = PathBuf::from(CWD_PATH);
5103 new_path.push(path);
5104
5105 Some(new_path)
5106 }
5107 PREFIX_XDG => {
5108 if is_include_path {
5109 get_xdg_config_home_value()
5110 .map(|xdg_config_home_path| xdg_config_home_path.join(path))
5111 } else {
5112 get_xdg_data_home_value()
5113 .map(|xdg_data_home_path| xdg_data_home_path.join(path))
5114 }
5115 }
5116 _ => None, },
5118 None => Some(path),
5119 }
5120}
5121
5122#[cfg(all(feature = "std", feature = "parsing"))]
5124fn get_name_string(name_data: &[u8], name_id: u16) -> Option<String> {
5125 fontcode_get_name(name_data, name_id)
5126 .ok()
5127 .flatten()
5128 .map(|name| String::from_utf8_lossy(name.to_bytes()).to_string())
5129}
5130
5131#[cfg(all(feature = "std", feature = "parsing"))]
5135fn get_verification_codepoints(start: u32, end: u32) -> Vec<u32> {
5136 match start {
5137 0x0000 => vec!['A' as u32, 'M' as u32, 'Z' as u32, 'a' as u32, 'm' as u32, 'z' as u32],
5139 0x0080 => vec![0x00C0, 0x00C9, 0x00D1, 0x00E0, 0x00E9, 0x00F1], 0x0100 => vec![0x0100, 0x0110, 0x0141, 0x0152, 0x0160], 0x0180 => vec![0x0180, 0x01A0, 0x01B0, 0x01CD], 0x0250 => vec![0x0250, 0x0259, 0x026A, 0x0279], 0x0370 => vec![0x0391, 0x0392, 0x0393, 0x03B1, 0x03B2, 0x03C9], 0x0400 => vec![0x0410, 0x0411, 0x0412, 0x0430, 0x0431, 0x042F], 0x0530 => vec![0x0531, 0x0532, 0x0533, 0x0561, 0x0562], 0x0590 => vec![0x05D0, 0x05D1, 0x05D2, 0x05E9, 0x05EA], 0x0600 => vec![0x0627, 0x0628, 0x062A, 0x062C, 0x0645], 0x0700 => vec![0x0710, 0x0712, 0x0713, 0x0715], 0x0900 => vec![0x0905, 0x0906, 0x0915, 0x0916, 0x0939], 0x0980 => vec![0x0985, 0x0986, 0x0995, 0x0996], 0x0A00 => vec![0x0A05, 0x0A06, 0x0A15, 0x0A16], 0x0A80 => vec![0x0A85, 0x0A86, 0x0A95, 0x0A96], 0x0B00 => vec![0x0B05, 0x0B06, 0x0B15, 0x0B16], 0x0B80 => vec![0x0B85, 0x0B86, 0x0B95, 0x0BA4], 0x0C00 => vec![0x0C05, 0x0C06, 0x0C15, 0x0C16], 0x0C80 => vec![0x0C85, 0x0C86, 0x0C95, 0x0C96], 0x0D00 => vec![0x0D05, 0x0D06, 0x0D15, 0x0D16], 0x0E00 => vec![0x0E01, 0x0E02, 0x0E04, 0x0E07, 0x0E40], 0x0E80 => vec![0x0E81, 0x0E82, 0x0E84, 0x0E87], 0x1000 => vec![0x1000, 0x1001, 0x1002, 0x1010, 0x1019], 0x10A0 => vec![0x10D0, 0x10D1, 0x10D2, 0x10D3], 0x1100 => vec![0x1100, 0x1102, 0x1103, 0x1161, 0x1162], 0x1200 => vec![0x1200, 0x1208, 0x1210, 0x1218], 0x13A0 => vec![0x13A0, 0x13A1, 0x13A2, 0x13A3], 0x1780 => vec![0x1780, 0x1781, 0x1782, 0x1783], 0x1800 => vec![0x1820, 0x1821, 0x1822, 0x1823], 0x3040 => vec![0x3042, 0x3044, 0x3046, 0x304B, 0x304D, 0x3093], 0x30A0 => vec![0x30A2, 0x30A4, 0x30A6, 0x30AB, 0x30AD, 0x30F3], 0x3100 => vec![0x3105, 0x3106, 0x3107, 0x3108], 0x4E00 => vec![0x4E00, 0x4E2D, 0x4EBA, 0x5927, 0x65E5, 0x6708], 0xAC00 => vec![0xAC00, 0xAC01, 0xAC04, 0xB098, 0xB2E4], 0xF900 => vec![0xF900, 0xF901, 0xF902], 0xFB50 => vec![0xFB50, 0xFB51, 0xFB52, 0xFB56], 0xFE70 => vec![0xFE70, 0xFE72, 0xFE74, 0xFE76], 0xFF00 => vec![0xFF01, 0xFF21, 0xFF41, 0xFF61], _ => {
5215 let range_size = end - start;
5216 if range_size > 20 {
5217 vec![
5218 start + range_size / 5,
5219 start + 2 * range_size / 5,
5220 start + 3 * range_size / 5,
5221 start + 4 * range_size / 5,
5222 ]
5223 } else {
5224 vec![start, start + range_size / 2]
5225 }
5226 }
5227 }
5228}
5229
5230#[cfg(all(feature = "std", feature = "parsing"))]
5233fn find_best_cmap_subtable<'a>(
5234 cmap: &allsorts::tables::cmap::Cmap<'a>,
5235) -> Option<allsorts::tables::cmap::EncodingRecord> {
5236 use allsorts::tables::cmap::{PlatformId, EncodingId};
5237
5238 cmap.find_subtable(PlatformId::UNICODE, EncodingId(3))
5239 .or_else(|| cmap.find_subtable(PlatformId::UNICODE, EncodingId(4)))
5240 .or_else(|| cmap.find_subtable(PlatformId::WINDOWS, EncodingId(1)))
5241 .or_else(|| cmap.find_subtable(PlatformId::WINDOWS, EncodingId(10)))
5242 .or_else(|| cmap.find_subtable(PlatformId::UNICODE, EncodingId(0)))
5243 .or_else(|| cmap.find_subtable(PlatformId::UNICODE, EncodingId(1)))
5244}
5245
5246#[cfg(all(feature = "std", feature = "parsing"))]
5249fn verify_unicode_ranges_with_cmap(
5250 provider: &impl FontTableProvider,
5251 os2_ranges: Vec<UnicodeRange>
5252) -> Vec<UnicodeRange> {
5253 use allsorts::tables::cmap::{Cmap, CmapSubtable};
5254
5255 if os2_ranges.is_empty() {
5256 return Vec::new();
5257 }
5258
5259 let cmap_data = match provider.table_data(tag::CMAP) {
5261 Ok(Some(data)) => data,
5262 _ => return os2_ranges, };
5264
5265 let cmap = match ReadScope::new(&cmap_data).read::<Cmap<'_>>() {
5266 Ok(c) => c,
5267 Err(_) => return os2_ranges,
5268 };
5269
5270 let encoding_record = match find_best_cmap_subtable(&cmap) {
5271 Some(r) => r,
5272 None => return os2_ranges, };
5274
5275 let cmap_subtable = match ReadScope::new(&cmap_data)
5276 .offset(encoding_record.offset as usize)
5277 .read::<CmapSubtable<'_>>()
5278 {
5279 Ok(st) => st,
5280 Err(_) => return os2_ranges,
5281 };
5282
5283 let mut verified_ranges = Vec::new();
5285
5286 for range in os2_ranges {
5287 let test_codepoints = get_verification_codepoints(range.start, range.end);
5288
5289 let required_hits = (test_codepoints.len() + 1) / 2; let mut hits = 0;
5293
5294 for cp in test_codepoints {
5295 if cp >= range.start && cp <= range.end {
5296 if let Ok(Some(gid)) = cmap_subtable.map_glyph(cp) {
5297 if gid != 0 {
5298 hits += 1;
5299 if hits >= required_hits {
5300 break;
5301 }
5302 }
5303 }
5304 }
5305 }
5306
5307 if hits >= required_hits {
5308 verified_ranges.push(range);
5309 }
5310 }
5311
5312 verified_ranges
5313}
5314
5315#[cfg(all(feature = "std", feature = "parsing"))]
5318fn analyze_cmap_coverage(provider: &impl FontTableProvider) -> Option<Vec<UnicodeRange>> {
5319 use allsorts::tables::cmap::{Cmap, CmapSubtable};
5320
5321 let cmap_data = provider.table_data(tag::CMAP).ok()??;
5322 let cmap = ReadScope::new(&cmap_data).read::<Cmap<'_>>().ok()?;
5323
5324 let encoding_record = find_best_cmap_subtable(&cmap)?;
5325
5326 let cmap_subtable = ReadScope::new(&cmap_data)
5327 .offset(encoding_record.offset as usize)
5328 .read::<CmapSubtable<'_>>()
5329 .ok()?;
5330
5331 let blocks_to_check: &[(u32, u32)] = &[
5333 (0x0000, 0x007F), (0x0080, 0x00FF), (0x0100, 0x017F), (0x0180, 0x024F), (0x0250, 0x02AF), (0x0300, 0x036F), (0x0370, 0x03FF), (0x0400, 0x04FF), (0x0500, 0x052F), (0x0530, 0x058F), (0x0590, 0x05FF), (0x0600, 0x06FF), (0x0700, 0x074F), (0x0900, 0x097F), (0x0980, 0x09FF), (0x0A00, 0x0A7F), (0x0A80, 0x0AFF), (0x0B00, 0x0B7F), (0x0B80, 0x0BFF), (0x0C00, 0x0C7F), (0x0C80, 0x0CFF), (0x0D00, 0x0D7F), (0x0E00, 0x0E7F), (0x0E80, 0x0EFF), (0x1000, 0x109F), (0x10A0, 0x10FF), (0x1100, 0x11FF), (0x1200, 0x137F), (0x13A0, 0x13FF), (0x1780, 0x17FF), (0x1800, 0x18AF), (0x2000, 0x206F), (0x20A0, 0x20CF), (0x2100, 0x214F), (0x2190, 0x21FF), (0x2200, 0x22FF), (0x2500, 0x257F), (0x25A0, 0x25FF), (0x2600, 0x26FF), (0x3000, 0x303F), (0x3040, 0x309F), (0x30A0, 0x30FF), (0x3100, 0x312F), (0x3130, 0x318F), (0x4E00, 0x9FFF), (0xAC00, 0xD7AF), (0xF900, 0xFAFF), (0xFB50, 0xFDFF), (0xFE70, 0xFEFF), (0xFF00, 0xFFEF), ];
5384
5385 let mut ranges = Vec::new();
5386
5387 for &(start, end) in blocks_to_check {
5388 let test_codepoints = get_verification_codepoints(start, end);
5389 let required_hits = (test_codepoints.len() + 1) / 2;
5390 let allowed_misses = test_codepoints.len() - required_hits;
5395 let mut hits = 0;
5396 let mut misses = 0;
5397
5398 for cp in test_codepoints {
5399 if matches!(cmap_subtable.map_glyph(cp), Ok(Some(gid)) if gid != 0) {
5400 hits += 1;
5401 if hits >= required_hits {
5402 break;
5403 }
5404 } else {
5405 misses += 1;
5406 if misses > allowed_misses {
5407 break;
5408 }
5409 }
5410 }
5411
5412 if hits >= required_hits {
5413 ranges.push(UnicodeRange { start, end });
5414 }
5415 }
5416
5417 if ranges.is_empty() {
5418 None
5419 } else {
5420 Some(ranges)
5421 }
5422}
5423
5424#[cfg(all(feature = "std", feature = "parsing"))]
5426#[allow(dead_code)]
5427fn extract_unicode_ranges(os2_table: &Os2) -> Vec<UnicodeRange> {
5428 let mut unicode_ranges = Vec::new();
5429
5430 let ranges = [
5431 os2_table.ul_unicode_range1,
5432 os2_table.ul_unicode_range2,
5433 os2_table.ul_unicode_range3,
5434 os2_table.ul_unicode_range4,
5435 ];
5436
5437 for &(bit, start, end) in UNICODE_RANGE_MAPPINGS {
5438 let range_idx = bit / 32;
5439 let bit_pos = bit % 32;
5440 if range_idx < 4 && (ranges[range_idx] & (1 << bit_pos)) != 0 {
5441 unicode_ranges.push(UnicodeRange { start, end });
5442 }
5443 }
5444
5445 unicode_ranges
5446}
5447
5448#[cfg(all(feature = "std", feature = "parsing"))]
5450fn detect_monospace(
5451 provider: &impl FontTableProvider,
5452 os2_table: Option<&Os2>,
5453 detected_monospace: Option<bool>,
5454) -> Option<bool> {
5455 if let Some(is_monospace) = detected_monospace {
5456 return Some(is_monospace);
5457 }
5458
5459 if let Some(os2_table) = os2_table {
5462 if os2_table.panose[0] == 2 {
5463 return Some(os2_table.panose[3] == 9); }
5466 }
5467
5468 let hhea_data = provider.table_data(tag::HHEA).ok()??;
5470 let hhea_table = ReadScope::new(&hhea_data).read::<HheaTable>().ok()?;
5471 let maxp_data = provider.table_data(tag::MAXP).ok()??;
5472 let maxp_table = ReadScope::new(&maxp_data).read::<MaxpTable>().ok()?;
5473 let hmtx_data = provider.table_data(tag::HMTX).ok()??;
5474 let hmtx_table = ReadScope::new(&hmtx_data)
5475 .read_dep::<HmtxTable<'_>>((
5476 usize::from(maxp_table.num_glyphs),
5477 usize::from(hhea_table.num_h_metrics),
5478 ))
5479 .ok()?;
5480
5481 let mut monospace = true;
5482 let mut last_advance = 0;
5483
5484 for i in 0..hhea_table.num_h_metrics as usize {
5486 let advance = hmtx_table.h_metrics.read_item(i).ok()?.advance_width;
5487 if i > 0 && advance != last_advance {
5488 monospace = false;
5489 break;
5490 }
5491 last_advance = advance;
5492 }
5493
5494 Some(monospace)
5495}
5496
5497#[cfg(all(feature = "std", not(feature = "parsing")))]
5506fn pattern_from_filename(path: &std::path::Path) -> Option<FcPattern> {
5507 let ext = path.extension()?.to_str()?.to_ascii_lowercase();
5508 match ext.as_str() {
5509 "ttf" | "otf" | "ttc" | "woff" | "woff2" => {}
5510 _ => return None,
5511 }
5512
5513 let stem = path.file_stem()?.to_str()?;
5514 let all_tokens = crate::config::tokenize_lowercase(stem);
5515
5516 let has_token = |kw: &str| all_tokens.iter().any(|t| t == kw);
5518 let is_bold = has_token("bold") || has_token("heavy");
5519 let is_italic = has_token("italic");
5520 let is_oblique = has_token("oblique");
5521 let is_mono = has_token("mono") || has_token("monospace");
5522 let is_condensed = has_token("condensed");
5523
5524 let family_tokens = crate::config::tokenize_font_stem(stem);
5526 if family_tokens.is_empty() { return None; }
5527 let family = family_tokens.join(" ");
5528
5529 Some(FcPattern {
5530 name: Some(stem.to_string()),
5531 family: Some(family),
5532 bold: if is_bold { PatternMatch::True } else { PatternMatch::False },
5533 italic: if is_italic { PatternMatch::True } else { PatternMatch::False },
5534 oblique: if is_oblique { PatternMatch::True } else { PatternMatch::DontCare },
5535 monospace: if is_mono { PatternMatch::True } else { PatternMatch::DontCare },
5536 condensed: if is_condensed { PatternMatch::True } else { PatternMatch::DontCare },
5537 weight: if is_bold { FcWeight::Bold } else { FcWeight::Normal },
5538 stretch: if is_condensed { FcStretch::Condensed } else { FcStretch::Normal },
5539 unicode_ranges: Vec::new(),
5540 metadata: FcFontMetadata::default(),
5541 render_config: FcFontRenderConfig::default(),
5542 })
5543}
5544
5545#[cfg(all(test, feature = "std", feature = "parsing", target_os = "linux"))]
5546mod system_alias_tests {
5547 use super::*;
5548
5549 const SAMPLE: &str = r#"<?xml version="1.0"?>
5550<fontconfig>
5551 <alias>
5552 <family>sans-serif</family>
5553 <prefer>
5554 <family>Noto Sans</family>
5555 <family>DejaVu Sans</family>
5556 </prefer>
5557 </alias>
5558 <alias>
5559 <family>Arial</family>
5560 <prefer><family>Liberation Sans</family></prefer>
5561 </alias>
5562 <alias binding="same">
5563 <family>monospace</family>
5564 <prefer><family>Noto Sans Mono</family></prefer>
5565 </alias>
5566</fontconfig>"#;
5567
5568 const SECOND_FILE: &str = r#"<fontconfig>
5569 <alias>
5570 <family>sans-serif</family>
5571 <prefer>
5572 <family>Ubuntu</family>
5573 <family>Noto Sans</family>
5574 </prefer>
5575 </alias>
5576</fontconfig>"#;
5577
5578 #[test]
5579 fn alias_blocks_parse_with_order_and_dedup_across_files() {
5580 let mut aliases = BTreeMap::new();
5581 ParseFontsConfAliases(SAMPLE, &mut aliases);
5582 ParseFontsConfAliases(SECOND_FILE, &mut aliases);
5583 let key = crate::utils::normalize_family_name("sans-serif");
5584 assert_eq!(
5585 aliases.get(&key).map(Vec::as_slice),
5586 Some(&["Noto Sans".to_string(), "DejaVu Sans".to_string(), "Ubuntu".to_string()][..]),
5587 "prefer entries append across files in include order, deduplicated"
5588 );
5589 assert_eq!(
5590 aliases.get("arial").map(Vec::as_slice),
5591 Some(&["Liberation Sans".to_string()][..]),
5592 "named-family aliases parse too (key normalized)"
5593 );
5594 assert_eq!(
5595 aliases.get("monospace").map(Vec::as_slice),
5596 Some(&["Noto Sans Mono".to_string()][..]),
5597 "alias attributes (binding=...) do not confuse the parser"
5598 );
5599 }
5600
5601 #[test]
5602 fn config_first_expansion_beats_the_builtin_lists() {
5603 let cache = FcFontCache::default();
5604 {
5605 let mut state = cache.state_write();
5606 let mut aliases = BTreeMap::new();
5607 ParseFontsConfAliases(SAMPLE, &mut aliases);
5608 state.system_aliases = aliases;
5609 }
5610 let out = cache.expand_font_families_config_first(
5611 &["Arial".to_string(), "sans-serif".to_string()],
5612 OperatingSystem::Linux,
5613 &[],
5614 );
5615 assert_eq!(
5616 out,
5617 vec![
5618 "Arial".to_string(), "Liberation Sans".to_string(), "Noto Sans".to_string(), "DejaVu Sans".to_string(),
5622 ],
5623 "configured preferences resolve the stack; no built-in list entries leak in"
5624 );
5625 }
5626
5627 #[test]
5628 fn generic_family_without_config_falls_back_to_builtin_lists() {
5629 let cache = FcFontCache::default();
5630 let out = cache.expand_font_families_config_first(
5631 &["sans-serif".to_string()],
5632 OperatingSystem::Linux,
5633 &[],
5634 );
5635 assert!(
5636 !out.is_empty() && out.iter().any(|f| f == "DejaVu Sans"),
5637 "no configuration parsed -> the built-in candidates are the last resort: {out:?}"
5638 );
5639 }
5640}