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) family_index: BTreeMap<String, alloc::vec::Vec<FontId>>,
1647 pub(crate) system_aliases: BTreeMap<String, Vec<String>>,
1656}
1657
1658impl FcFontCacheInner {
1659 pub(crate) fn index_pattern_family(&mut self, pattern: &FcPattern, id: FontId) {
1670 for key in [pattern.family.as_deref(), pattern.name.as_deref()]
1671 .into_iter()
1672 .flatten()
1673 .map(crate::utils::normalize_family_name)
1674 .filter(|k| !k.is_empty())
1675 {
1676 let slot = self.family_index.entry(key).or_default();
1677 if !slot.contains(&id) {
1678 slot.push(id);
1679 }
1680 }
1681 }
1682
1683 pub(crate) fn index_pattern_tokens(&mut self, _pattern: &FcPattern, _id: FontId) {
1684 }
1692}
1693
1694impl Clone for FcFontCache {
1695 fn clone(&self) -> Self {
1702 Self {
1703 shared: std::sync::Arc::clone(&self.shared),
1704 }
1705 }
1706}
1707
1708impl core::fmt::Debug for FcFontCache {
1709 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1710 let state = self.state_read();
1711 f.debug_struct("FcFontCache")
1712 .field("patterns_len", &state.patterns.len())
1713 .field("metadata_len", &state.metadata.len())
1714 .field("disk_fonts_len", &state.disk_fonts.len())
1715 .field("memory_fonts_len", &state.memory_fonts.len())
1716 .finish()
1717 }
1718}
1719
1720impl Default for FcFontCache {
1721 fn default() -> Self {
1722 Self {
1723 shared: std::sync::Arc::new(FcFontCacheShared {
1724 state: StLock::new(FcFontCacheInner::default()),
1725 chain_cache: StLock::new(std::collections::HashMap::new()),
1726 shared_bytes: StLock::new(std::collections::HashMap::new()),
1727 }),
1728 }
1729 }
1730}
1731
1732impl FcFontCache {
1733 pub fn system_alias_prefs(&self, family: &str) -> Vec<String> {
1737 let norm = crate::utils::normalize_family_name(family);
1738 self.state_read()
1739 .system_aliases
1740 .get(&norm)
1741 .cloned()
1742 .unwrap_or_default()
1743 }
1744
1745 pub fn expand_font_families_config_first(
1763 &self,
1764 families: &[String],
1765 os: OperatingSystem,
1766 unicode_ranges: &[UnicodeRange],
1767 ) -> Vec<String> {
1768 let mut expanded: Vec<String> = Vec::new();
1769 let mut push_unique = |v: &mut Vec<String>, f: String| {
1770 if !v.iter().any(|e| e.eq_ignore_ascii_case(&f)) {
1771 v.push(f);
1772 }
1773 };
1774 for family in families {
1775 let is_generic = matches!(
1776 family.to_ascii_lowercase().as_str(),
1777 "serif" | "sans-serif" | "monospace" | "cursive" | "fantasy" | "system-ui"
1778 );
1779 if !is_generic {
1780 push_unique(&mut expanded, family.clone());
1781 }
1782 let prefs = self.system_alias_prefs(family);
1783 if !prefs.is_empty() {
1784 for pref in prefs {
1785 push_unique(&mut expanded, pref);
1786 }
1787 } else if is_generic {
1788 for fallback in os.expand_generic_family(family, unicode_ranges) {
1789 push_unique(&mut expanded, fallback);
1790 }
1791 }
1792 }
1793 expanded
1794 }
1795
1796 #[inline]
1800 pub(crate) fn state_read(
1801 &self,
1802 ) -> StReadGuard<'_, FcFontCacheInner> {
1803 match self.shared.state.read() {
1805 Ok(g) => g,
1806 Err(e) => match e {},
1807 }
1808 }
1809
1810 #[inline]
1813 pub(crate) fn state_write(
1814 &self,
1815 ) -> StWriteGuard<'_, FcFontCacheInner> {
1816 match self.shared.state.write() {
1818 Ok(g) => g,
1819 Err(e) => match e {},
1820 }
1821 }
1822
1823 pub fn with_memory_fonts(&self, fonts: Vec<(FcPattern, FcFont)>) -> &Self {
1828 let fonts: Vec<(FcPattern, FcFont)> = fonts
1832 .into_iter()
1833 .map(|(pattern, font)| (Self::populate_memory_font_ranges(pattern, &font), font))
1834 .collect();
1835 let mut state = self.state_write();
1836 for (pattern, font) in fonts {
1837 let id = FontId::new();
1838 state.patterns.insert(pattern.clone(), id);
1839 state.metadata.insert(id, pattern.clone());
1840 state.memory_fonts.insert(id, font);
1841 state.index_pattern_tokens(&pattern, id);
1842 state.index_pattern_family(&pattern, id);
1843 }
1844 self
1845 }
1846
1847 pub fn with_memory_font_with_id(
1849 &self,
1850 id: FontId,
1851 pattern: FcPattern,
1852 font: FcFont,
1853 ) -> &Self {
1854 let pattern = Self::populate_memory_font_ranges(pattern, &font);
1855 let mut state = self.state_write();
1856 state.patterns.insert(pattern.clone(), id);
1857 state.metadata.insert(id, pattern.clone());
1858 state.memory_fonts.insert(id, font);
1859 state.index_pattern_tokens(&pattern, id);
1860 state.index_pattern_family(&pattern, id);
1861 self
1862 }
1863
1864 #[cfg(all(feature = "std", feature = "parsing"))]
1880 fn populate_memory_font_ranges(mut pattern: FcPattern, font: &FcFont) -> FcPattern {
1881 if !pattern.unicode_ranges.is_empty() {
1882 return pattern;
1883 }
1884 if let Some(faces) = FcParseFontBytes(&font.bytes, &font.id) {
1885 let ranges = faces
1889 .iter()
1890 .find(|(_, f)| f.font_index == font.font_index)
1891 .or_else(|| faces.first())
1892 .map(|(p, _)| p.unicode_ranges.clone())
1893 .unwrap_or_default();
1894 if !ranges.is_empty() {
1895 pattern.unicode_ranges = ranges;
1896 }
1897 }
1898 pattern
1899 }
1900
1901 #[cfg(not(all(feature = "std", feature = "parsing")))]
1904 fn populate_memory_font_ranges(pattern: FcPattern, _font: &FcFont) -> FcPattern {
1905 pattern
1906 }
1907
1908 pub fn insert_builder_font(&self, pattern: FcPattern, path: FcFontPath) {
1914 let id = FontId::new();
1915 {
1916 let mut state = self.state_write();
1917 state.index_pattern_tokens(&pattern, id);
1918 state.index_pattern_family(&pattern, id);
1919 state.patterns.insert(pattern.clone(), id);
1920 state.disk_fonts.insert(id, path);
1921 state.metadata.insert(id, pattern);
1922 }
1923 if let Ok(mut cc) = self.shared.chain_cache.lock() {
1927 cc.clear();
1928 }
1929 }
1930
1931 #[cfg(feature = "std")]
1932 #[doc(hidden)]
1933 pub fn chain_cache_len(&self) -> usize {
1934 self.shared.chain_cache.lock().map(|c| c.len()).unwrap_or(0)
1935 }
1936
1937 pub fn insert_fast_pattern(&self, pattern: FcPattern, path: FcFontPath) -> FontId {
1945 let id = FontId::new();
1946 let mut state = self.state_write();
1947 state.patterns.insert(pattern.clone(), id);
1948 state.disk_fonts.insert(id, path);
1949 state.metadata.insert(id, pattern);
1950 id
1951 }
1952
1953 pub fn lookup_paths_cached(&self, path: &str) -> Option<Vec<FontId>> {
1961 let state = self.state_read();
1962 let mut out = Vec::new();
1963 for (id, font_path) in &state.disk_fonts {
1964 if font_path.path == path {
1965 out.push(*id);
1966 }
1967 }
1968 if out.is_empty() { None } else { Some(out) }
1969 }
1970
1971 pub fn get_font_by_id(&self, id: &FontId) -> Option<OwnedFontSource> {
1978 let state = self.state_read();
1979 if let Some(font) = state.memory_fonts.get(id) {
1980 return Some(OwnedFontSource::Memory(font.clone()));
1981 }
1982 if let Some(path) = state.disk_fonts.get(id) {
1983 return Some(OwnedFontSource::Disk(path.clone()));
1984 }
1985 None
1986 }
1987
1988 pub fn get_metadata_by_id(&self, id: &FontId) -> Option<FcPattern> {
1992 self.state_read().metadata.get(id).cloned()
1993 }
1994
1995 #[cfg(feature = "std")]
2023 pub fn get_font_bytes(&self, id: &FontId) -> Option<std::sync::Arc<FontBytes>> {
2024 use std::sync::Arc;
2025 match self.get_font_by_id(id)? {
2026 OwnedFontSource::Memory(font) => Some(Arc::new(FontBytes::Owned(
2027 Arc::from(font.bytes.as_slice()),
2028 ))),
2029 OwnedFontSource::Disk(path) => {
2030 let hash = path.bytes_hash;
2031 if hash != 0 {
2032 if let Ok(guard) = self.shared.shared_bytes.lock() {
2033 if let Some(weak) = guard.get(&hash) {
2034 if let Some(arc) = weak.upgrade() {
2035 return Some(arc);
2036 }
2037 }
2038 }
2039 }
2040
2041 let arc = open_font_bytes_mmap(&path.path)?;
2042 if hash != 0 {
2043 if let Ok(mut guard) = self.shared.shared_bytes.lock() {
2044 guard.insert(hash, Arc::downgrade(&arc));
2046 }
2047 }
2048 Some(arc)
2049 }
2050 }
2051 }
2052
2053 #[cfg(not(feature = "std"))]
2055 pub fn build() -> Self { Self::default() }
2056
2057 #[cfg(all(feature = "std", not(feature = "parsing")))]
2059 pub fn build() -> Self { Self::build_from_filenames() }
2060
2061 #[cfg(all(feature = "std", feature = "parsing"))]
2063 pub fn build() -> Self { Self::build_inner(None) }
2064
2065 #[cfg(all(feature = "std", not(feature = "parsing")))]
2068 fn build_from_filenames() -> Self {
2069 let cache = Self::default();
2070 {
2071 let mut state = cache.state_write();
2072 for dir in crate::config::font_directories(OperatingSystem::current()) {
2073 for path in FcCollectFontFilesRecursive(dir) {
2074 let pattern = match pattern_from_filename(&path) {
2075 Some(p) => p,
2076 None => continue,
2077 };
2078 let id = FontId::new();
2079 state.disk_fonts.insert(id, FcFontPath {
2080 path: path.to_string_lossy().to_string(),
2081 font_index: 0,
2082 bytes_hash: 0,
2085 });
2086 state.index_pattern_tokens(&pattern, id);
2087 state.index_pattern_family(&pattern, id);
2088 state.metadata.insert(id, pattern.clone());
2089 state.patterns.insert(pattern, id);
2090 }
2091 }
2092 }
2093 cache
2094 }
2095
2096 #[cfg(all(feature = "std", feature = "parsing"))]
2121 pub fn build_with_families(families: &[impl AsRef<str>]) -> Self {
2122 let os = OperatingSystem::current();
2127 let mut target_families: Vec<String> = Vec::new();
2128
2129 for family in families {
2130 let family_str = family.as_ref();
2131 let expanded = os.expand_generic_family(family_str, &[]);
2132 if expanded.is_empty() || (expanded.len() == 1 && expanded[0] == family_str) {
2133 target_families.push(family_str.to_string());
2134 } else {
2135 target_families.extend(expanded);
2136 }
2137 }
2138
2139 Self::build_inner(Some(&target_families))
2140 }
2141
2142 #[cfg(all(feature = "std", feature = "parsing"))]
2148 fn build_inner(family_filter: Option<&[String]>) -> Self {
2149 let cache = FcFontCache::default();
2150
2151 let filter_normalized: Option<Vec<String>> = family_filter.map(|families| {
2153 families
2154 .iter()
2155 .map(|f| crate::utils::normalize_family_name(f))
2156 .collect()
2157 });
2158
2159 let matches_filter = |pattern: &FcPattern| -> bool {
2161 match &filter_normalized {
2162 None => true, Some(targets) => {
2164 pattern.name.as_ref().map_or(false, |name| {
2165 let name_norm = crate::utils::normalize_family_name(name);
2166 targets.iter().any(|target| name_norm.contains(target))
2167 }) || pattern.family.as_ref().map_or(false, |family| {
2168 let family_norm = crate::utils::normalize_family_name(family);
2169 targets.iter().any(|target| family_norm.contains(target))
2170 })
2171 }
2172 }
2173 };
2174
2175 let mut state = cache.state_write();
2176
2177 #[cfg(target_os = "linux")]
2178 {
2179 if let Some((font_entries, render_configs, system_aliases)) = FcScanDirectories() {
2180 state.system_aliases = system_aliases;
2181 for (mut pattern, path) in font_entries {
2182 if matches_filter(&pattern) {
2183 if let Some(family) = pattern.name.as_ref().or(pattern.family.as_ref()) {
2185 if let Some(rc) = render_configs.get(family) {
2186 pattern.render_config = rc.clone();
2187 }
2188 }
2189 let id = FontId::new();
2190 state.patterns.insert(pattern.clone(), id);
2191 state.metadata.insert(id, pattern.clone());
2192 state.disk_fonts.insert(id, path);
2193 state.index_pattern_tokens(&pattern, id);
2194 state.index_pattern_family(&pattern, id);
2195 }
2196 }
2197 }
2198 }
2199
2200 #[cfg(target_os = "windows")]
2201 {
2202 let system_root = std::env::var("SystemRoot")
2203 .or_else(|_| std::env::var("WINDIR"))
2204 .unwrap_or_else(|_| "C:\\Windows".to_string());
2205
2206 let user_profile = std::env::var("USERPROFILE")
2207 .unwrap_or_else(|_| "C:\\Users\\Default".to_string());
2208
2209 let font_dirs = vec![
2210 (None, format!("{}\\Fonts\\", system_root)),
2211 (None, format!("{}\\AppData\\Local\\Microsoft\\Windows\\Fonts\\", user_profile)),
2212 ];
2213
2214 let font_entries = FcScanDirectoriesInner(&font_dirs);
2215 for (pattern, path) in font_entries {
2216 if matches_filter(&pattern) {
2217 let id = FontId::new();
2218 state.patterns.insert(pattern.clone(), id);
2219 state.metadata.insert(id, pattern.clone());
2220 state.disk_fonts.insert(id, path);
2221 state.index_pattern_tokens(&pattern, id);
2222 state.index_pattern_family(&pattern, id);
2223 }
2224 }
2225 }
2226
2227 #[cfg(target_os = "macos")]
2228 {
2229 let font_dirs = vec![
2230 (None, "~/Library/Fonts".to_owned()),
2231 (None, "/System/Library/Fonts".to_owned()),
2232 (None, "/Library/Fonts".to_owned()),
2233 (None, "/System/Library/AssetsV2".to_owned()),
2234 ];
2235
2236 let font_entries = FcScanDirectoriesInner(&font_dirs);
2237 for (pattern, path) in font_entries {
2238 if matches_filter(&pattern) {
2239 let id = FontId::new();
2240 state.patterns.insert(pattern.clone(), id);
2241 state.metadata.insert(id, pattern.clone());
2242 state.disk_fonts.insert(id, path);
2243 state.index_pattern_tokens(&pattern, id);
2244 state.index_pattern_family(&pattern, id);
2245 }
2246 }
2247 }
2248
2249 #[cfg(target_os = "ios")]
2254 {
2255 let font_files = crate::mobile_ios::copy_available_font_urls();
2256 let font_entries = FcParseFontFiles(&font_files);
2257 for (pattern, path) in font_entries {
2258 if matches_filter(&pattern) {
2259 let id = FontId::new();
2260 state.patterns.insert(pattern.clone(), id);
2261 state.metadata.insert(id, pattern.clone());
2262 state.disk_fonts.insert(id, path);
2263 state.index_pattern_tokens(&pattern, id);
2264 state.index_pattern_family(&pattern, id);
2265 }
2266 }
2267 }
2268
2269 #[cfg(target_os = "android")]
2274 {
2275 let font_dirs = vec![
2276 (None, "/system/fonts".to_owned()),
2277 (None, "/product/fonts".to_owned()),
2278 (None, "/system_ext/fonts".to_owned()),
2279 (None, "/data/fonts".to_owned()),
2280 ];
2281
2282 let font_entries = FcScanDirectoriesInner(&font_dirs);
2283 for (pattern, path) in font_entries {
2284 if matches_filter(&pattern) {
2285 let id = FontId::new();
2286 state.patterns.insert(pattern.clone(), id);
2287 state.metadata.insert(id, pattern.clone());
2288 state.disk_fonts.insert(id, path);
2289 state.index_pattern_tokens(&pattern, id);
2290 state.index_pattern_family(&pattern, id);
2291 }
2292 }
2293 }
2294
2295 drop(state);
2296 cache
2297 }
2298
2299 pub fn is_memory_font(&self, id: &FontId) -> bool {
2301 self.state_read().memory_fonts.contains_key(id)
2302 }
2303
2304 pub fn list(&self) -> Vec<(FcPattern, FontId)> {
2311 self.state_read()
2312 .patterns
2313 .iter()
2314 .map(|(pattern, id)| (pattern.clone(), *id))
2315 .collect()
2316 }
2317
2318 pub fn for_each_pattern<F: FnMut(&FcPattern, &FontId)>(&self, mut f: F) {
2322 let state = self.state_read();
2323 for (pattern, id) in &state.patterns {
2324 f(pattern, id);
2325 }
2326 }
2327
2328 pub fn is_empty(&self) -> bool {
2330 self.state_read().patterns.is_empty()
2331 }
2332
2333 pub fn len(&self) -> usize {
2335 self.state_read().patterns.len()
2336 }
2337
2338 pub fn query_with_fallback(
2365 &self,
2366 pattern: &FcPattern,
2367 trace: &mut Vec<TraceMsg>,
2368 ) -> Option<FontMatch> {
2369 if let Some(m) = self.query(pattern, trace) {
2370 return Some(m);
2371 }
2372
2373 if pattern.name.is_some() || pattern.family.is_some() {
2375 let relaxed = FcPattern {
2376 name: None,
2377 family: None,
2378 ..pattern.clone()
2379 };
2380 if let Some(m) = self.query(&relaxed, trace) {
2381 return Some(m);
2382 }
2383 }
2384
2385 let bare = FcPattern {
2387 unicode_ranges: pattern.unicode_ranges.clone(),
2388 ..FcPattern::default()
2389 };
2390 self.query(&bare, trace)
2391 }
2392
2393 pub fn query(&self, pattern: &FcPattern, trace: &mut Vec<TraceMsg>) -> Option<FontMatch> {
2399 let state = self.state_read();
2400 let mut matches = Vec::new();
2401
2402 for (stored_pattern, id) in &state.patterns {
2403 if Self::query_matches_internal(stored_pattern, pattern, trace) {
2404 let metadata = state.metadata.get(id).unwrap_or(stored_pattern);
2405
2406 let unicode_compatibility = if pattern.unicode_ranges.is_empty() {
2408 Self::calculate_unicode_coverage(&metadata.unicode_ranges) as i32
2410 } else {
2411 Self::calculate_unicode_compatibility(&pattern.unicode_ranges, &metadata.unicode_ranges)
2413 };
2414
2415 let style_score = Self::calculate_style_score(pattern, metadata);
2416
2417 let is_memory = state.memory_fonts.contains_key(id);
2419
2420 matches.push((*id, unicode_compatibility, style_score, metadata.clone(), is_memory));
2421 }
2422 }
2423
2424 matches.sort_by(|a, b| {
2426 b.4.cmp(&a.4)
2428 .then_with(|| b.1.cmp(&a.1)) .then_with(|| a.2.cmp(&b.2)) });
2431
2432 matches.first().map(|(id, _, _, metadata, _)| {
2433 FontMatch {
2434 id: *id,
2435 unicode_ranges: metadata.unicode_ranges.clone(),
2436 fallbacks: Vec::new(), }
2438 })
2439 }
2440
2441 fn query_internal(&self, pattern: &FcPattern, trace: &mut Vec<TraceMsg>) -> Vec<FontMatch> {
2446 let state = self.state_read();
2447 self.query_internal_locked(&state, pattern, trace)
2448 }
2449
2450 fn query_internal_locked(
2453 &self,
2454 state: &FcFontCacheInner,
2455 pattern: &FcPattern,
2456 trace: &mut Vec<TraceMsg>,
2457 ) -> Vec<FontMatch> {
2458 let mut matches = Vec::new();
2459
2460 for (stored_pattern, id) in &state.patterns {
2461 if Self::query_matches_internal(stored_pattern, pattern, trace) {
2462 let metadata = state.metadata.get(id).unwrap_or(stored_pattern);
2463
2464 let unicode_compatibility = if pattern.unicode_ranges.is_empty() {
2466 Self::calculate_unicode_coverage(&metadata.unicode_ranges) as i32
2467 } else {
2468 Self::calculate_unicode_compatibility(&pattern.unicode_ranges, &metadata.unicode_ranges)
2469 };
2470
2471 let style_score = Self::calculate_style_score(pattern, metadata);
2472 matches.push((*id, unicode_compatibility, style_score, metadata.clone()));
2473 }
2474 }
2475
2476 matches.sort_by(|a, b| {
2480 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)) });
2485
2486 matches
2487 .into_iter()
2488 .map(|(id, _, _, metadata)| {
2489 FontMatch {
2490 id,
2491 unicode_ranges: metadata.unicode_ranges.clone(),
2492 fallbacks: Vec::new(), }
2494 })
2495 .collect()
2496 }
2497
2498 pub fn compute_fallbacks(
2502 &self,
2503 font_id: &FontId,
2504 trace: &mut Vec<TraceMsg>,
2505 ) -> Vec<FontMatchNoFallback> {
2506 let state = self.state_read();
2507 let pattern = match state.metadata.get(font_id) {
2508 Some(p) => p.clone(),
2509 None => return Vec::new(),
2510 };
2511 drop(state);
2512
2513 self.compute_fallbacks_for_pattern(&pattern, Some(font_id), trace)
2514 }
2515
2516 fn compute_fallbacks_for_pattern(
2517 &self,
2518 pattern: &FcPattern,
2519 exclude_id: Option<&FontId>,
2520 _trace: &mut Vec<TraceMsg>,
2521 ) -> Vec<FontMatchNoFallback> {
2522 let state = self.state_read();
2523 let mut candidates = Vec::new();
2524
2525 for (stored_pattern, id) in &state.patterns {
2527 if exclude_id.is_some() && exclude_id.unwrap() == id {
2529 continue;
2530 }
2531
2532 if !stored_pattern.unicode_ranges.is_empty() && !pattern.unicode_ranges.is_empty() {
2534 let unicode_compatibility = Self::calculate_unicode_compatibility(
2536 &pattern.unicode_ranges,
2537 &stored_pattern.unicode_ranges
2538 );
2539
2540 if unicode_compatibility > 0 {
2542 let style_score = Self::calculate_style_score(pattern, stored_pattern);
2543 candidates.push((
2544 FontMatchNoFallback {
2545 id: *id,
2546 unicode_ranges: stored_pattern.unicode_ranges.clone(),
2547 },
2548 unicode_compatibility,
2549 style_score,
2550 stored_pattern.clone(),
2551 ));
2552 }
2553 } else if pattern.unicode_ranges.is_empty() && !stored_pattern.unicode_ranges.is_empty() {
2554 let coverage = Self::calculate_unicode_coverage(&stored_pattern.unicode_ranges) as i32;
2556 let style_score = Self::calculate_style_score(pattern, stored_pattern);
2557 candidates.push((
2558 FontMatchNoFallback {
2559 id: *id,
2560 unicode_ranges: stored_pattern.unicode_ranges.clone(),
2561 },
2562 coverage,
2563 style_score,
2564 stored_pattern.clone(),
2565 ));
2566 }
2567 }
2568
2569 drop(state);
2570
2571 candidates.sort_by(|a, b| {
2573 b.1.cmp(&a.1)
2574 .then_with(|| a.2.cmp(&b.2))
2575 });
2576
2577 let mut seen_ranges = Vec::new();
2579 let mut deduplicated = Vec::new();
2580
2581 for (id, _, _, pattern) in candidates {
2582 let mut is_new_range = false;
2583
2584 for range in &pattern.unicode_ranges {
2585 if !seen_ranges.iter().any(|r: &UnicodeRange| r.overlaps(range)) {
2586 seen_ranges.push(*range);
2587 is_new_range = true;
2588 }
2589 }
2590
2591 if is_new_range {
2592 deduplicated.push(id);
2593 }
2594 }
2595
2596 deduplicated
2597 }
2598
2599 pub fn get_memory_font(&self, id: &FontId) -> Option<FcFont> {
2601 self.state_read().memory_fonts.get(id).cloned()
2602 }
2603
2604 fn trace_path(k: &FcPattern) -> String {
2606 k.name.as_ref().cloned().unwrap_or_else(|| "<unknown>".to_string())
2607 }
2608
2609 pub fn query_matches_internal(
2610 k: &FcPattern,
2611 pattern: &FcPattern,
2612 trace: &mut Vec<TraceMsg>,
2613 ) -> bool {
2614 if let Some(ref name) = pattern.name {
2616 if !k.name.as_ref().map_or(false, |kn| kn.contains(name)) {
2617 trace.push(TraceMsg {
2618 level: TraceLevel::Info,
2619 path: Self::trace_path(k),
2620 reason: MatchReason::NameMismatch {
2621 requested: pattern.name.clone(),
2622 found: k.name.clone(),
2623 },
2624 });
2625 return false;
2626 }
2627 }
2628
2629 if let Some(ref family) = pattern.family {
2631 if !k.family.as_ref().map_or(false, |kf| kf.contains(family)) {
2632 trace.push(TraceMsg {
2633 level: TraceLevel::Info,
2634 path: Self::trace_path(k),
2635 reason: MatchReason::FamilyMismatch {
2636 requested: pattern.family.clone(),
2637 found: k.family.clone(),
2638 },
2639 });
2640 return false;
2641 }
2642 }
2643
2644 let style_properties = [
2646 (
2647 "italic",
2648 pattern.italic.needs_to_match(),
2649 pattern.italic.matches(&k.italic),
2650 ),
2651 (
2652 "oblique",
2653 pattern.oblique.needs_to_match(),
2654 pattern.oblique.matches(&k.oblique),
2655 ),
2656 (
2657 "bold",
2658 pattern.bold.needs_to_match(),
2659 pattern.bold.matches(&k.bold),
2660 ),
2661 (
2662 "monospace",
2663 pattern.monospace.needs_to_match(),
2664 pattern.monospace.matches(&k.monospace),
2665 ),
2666 (
2667 "condensed",
2668 pattern.condensed.needs_to_match(),
2669 pattern.condensed.matches(&k.condensed),
2670 ),
2671 ];
2672
2673 for (property_name, needs_to_match, matches) in style_properties {
2674 if needs_to_match && !matches {
2675 let (requested, found) = match property_name {
2676 "italic" => (format!("{:?}", pattern.italic), format!("{:?}", k.italic)),
2677 "oblique" => (format!("{:?}", pattern.oblique), format!("{:?}", k.oblique)),
2678 "bold" => (format!("{:?}", pattern.bold), format!("{:?}", k.bold)),
2679 "monospace" => (
2680 format!("{:?}", pattern.monospace),
2681 format!("{:?}", k.monospace),
2682 ),
2683 "condensed" => (
2684 format!("{:?}", pattern.condensed),
2685 format!("{:?}", k.condensed),
2686 ),
2687 _ => (String::new(), String::new()),
2688 };
2689
2690 trace.push(TraceMsg {
2691 level: TraceLevel::Info,
2692 path: Self::trace_path(k),
2693 reason: MatchReason::StyleMismatch {
2694 property: property_name,
2695 requested,
2696 found,
2697 },
2698 });
2699 return false;
2700 }
2701 }
2702
2703 if pattern.weight != FcWeight::Normal && pattern.weight != k.weight {
2705 trace.push(TraceMsg {
2706 level: TraceLevel::Info,
2707 path: Self::trace_path(k),
2708 reason: MatchReason::WeightMismatch {
2709 requested: pattern.weight,
2710 found: k.weight,
2711 },
2712 });
2713 return false;
2714 }
2715
2716 if pattern.stretch != FcStretch::Normal && pattern.stretch != k.stretch {
2718 trace.push(TraceMsg {
2719 level: TraceLevel::Info,
2720 path: Self::trace_path(k),
2721 reason: MatchReason::StretchMismatch {
2722 requested: pattern.stretch,
2723 found: k.stretch,
2724 },
2725 });
2726 return false;
2727 }
2728
2729 if !pattern.unicode_ranges.is_empty() {
2731 let mut has_overlap = false;
2732
2733 for p_range in &pattern.unicode_ranges {
2734 for k_range in &k.unicode_ranges {
2735 if p_range.overlaps(k_range) {
2736 has_overlap = true;
2737 break;
2738 }
2739 }
2740 if has_overlap {
2741 break;
2742 }
2743 }
2744
2745 if !has_overlap {
2746 trace.push(TraceMsg {
2747 level: TraceLevel::Info,
2748 path: Self::trace_path(k),
2749 reason: MatchReason::UnicodeRangeMismatch {
2750 character: '\0', ranges: k.unicode_ranges.clone(),
2752 },
2753 });
2754 return false;
2755 }
2756 }
2757
2758 true
2759 }
2760
2761 #[cfg(feature = "std")]
2787 pub fn resolve_font_chain(
2788 &self,
2789 font_families: &[String],
2790 weight: FcWeight,
2791 italic: PatternMatch,
2792 oblique: PatternMatch,
2793 trace: &mut Vec<TraceMsg>,
2794 ) -> FontFallbackChain {
2795 self.resolve_font_chain_with_os(font_families, weight, italic, oblique, trace, OperatingSystem::current())
2796 }
2797
2798 #[cfg(feature = "std")]
2800 pub fn resolve_font_chain_with_os(
2801 &self,
2802 font_families: &[String],
2803 weight: FcWeight,
2804 italic: PatternMatch,
2805 oblique: PatternMatch,
2806 trace: &mut Vec<TraceMsg>,
2807 os: OperatingSystem,
2808 ) -> FontFallbackChain {
2809 self.resolve_font_chain_impl(font_families, weight, italic, oblique, None, trace, os)
2810 }
2811
2812 #[cfg(feature = "std")]
2827 pub fn resolve_font_chain_with_scripts(
2828 &self,
2829 font_families: &[String],
2830 weight: FcWeight,
2831 italic: PatternMatch,
2832 oblique: PatternMatch,
2833 scripts_hint: Option<&[UnicodeRange]>,
2834 trace: &mut Vec<TraceMsg>,
2835 ) -> FontFallbackChain {
2836 self.resolve_font_chain_impl(
2837 font_families, weight, italic, oblique, scripts_hint,
2838 trace, OperatingSystem::current(),
2839 )
2840 }
2841
2842 #[cfg(feature = "std")]
2846 fn resolve_font_chain_impl(
2847 &self,
2848 font_families: &[String],
2849 weight: FcWeight,
2850 italic: PatternMatch,
2851 oblique: PatternMatch,
2852 scripts_hint: Option<&[UnicodeRange]>,
2853 trace: &mut Vec<TraceMsg>,
2854 os: OperatingSystem,
2855 ) -> FontFallbackChain {
2856 let scripts_hint_hash = scripts_hint.map(hash_scripts_hint);
2860 let cache_key = FontChainCacheKey {
2861 font_families: font_families.to_vec(),
2862 weight,
2863 italic,
2864 oblique,
2865 scripts_hint_hash,
2866 };
2867
2868 if let Some(cached) = self
2869 .shared
2870 .chain_cache
2871 .lock()
2872 .ok()
2873 .and_then(|c| c.get(&cache_key).cloned())
2874 {
2875 return cached;
2876 }
2877
2878 let expanded_families = expand_font_families(font_families, os, &[]);
2880
2881 let generic_fallbacks: Vec<String> = font_families
2889 .iter()
2890 .filter(|f| config::is_generic_family(f))
2891 .cloned()
2892 .collect();
2893
2894 let chain = self.resolve_font_chain_uncached(
2896 &expanded_families,
2897 &generic_fallbacks,
2898 weight,
2899 italic,
2900 oblique,
2901 scripts_hint,
2902 trace,
2903 );
2904
2905 if let Ok(mut cache) = self.shared.chain_cache.lock() {
2907 cache.insert(cache_key, chain.clone());
2908 }
2909
2910 chain
2911 }
2912
2913 #[cfg(feature = "std")]
2921 fn resolve_font_chain_uncached(
2922 &self,
2923 font_families: &[String],
2924 generic_fallbacks: &[String],
2925 weight: FcWeight,
2926 italic: PatternMatch,
2927 oblique: PatternMatch,
2928 scripts_hint: Option<&[UnicodeRange]>,
2929 trace: &mut Vec<TraceMsg>,
2930 ) -> FontFallbackChain {
2931 let mut css_fallbacks = Vec::new();
2932
2933 for (_i, family) in font_families.iter().enumerate() {
2935 let (pattern, is_generic) = if config::is_generic_family(family) {
2937 let monospace = if family.eq_ignore_ascii_case("monospace") {
2938 PatternMatch::True
2939 } else {
2940 PatternMatch::False
2941 };
2942 let pattern = FcPattern {
2943 name: None,
2944 weight,
2945 italic,
2946 oblique,
2947 monospace,
2948 unicode_ranges: Vec::new(),
2949 ..Default::default()
2950 };
2951 (pattern, true)
2952 } else {
2953 let pattern = FcPattern {
2955 name: Some(family.clone()),
2956 weight,
2957 italic,
2958 oblique,
2959 unicode_ranges: Vec::new(),
2960 ..Default::default()
2961 };
2962 (pattern, false)
2963 };
2964
2965 let mut matches = if is_generic {
2968 self.query_internal(&pattern, trace)
2970 } else {
2971 let mut m = self.fuzzy_query_by_name(family, weight, italic, oblique, &[], trace);
2973 if m.is_empty() {
2986 m = self.query_by_family_normalized(family, weight, italic, oblique);
2987 }
2988 m
2989 };
2990
2991 if is_generic && matches.len() > 5 {
2993 matches.truncate(5);
2994 }
2995
2996 css_fallbacks.push(CssFallbackGroup {
2999 css_name: family.clone(),
3000 fonts: matches,
3001 });
3002 }
3003
3004 if !generic_fallbacks.is_empty()
3021 && css_fallbacks.iter().all(|g| g.fonts.is_empty())
3022 {
3023 for generic in generic_fallbacks {
3024 let monospace = if generic.eq_ignore_ascii_case("monospace") {
3025 PatternMatch::True
3026 } else {
3027 PatternMatch::False
3028 };
3029 let pattern = FcPattern {
3030 name: None,
3031 weight,
3032 italic,
3033 oblique,
3034 monospace,
3035 unicode_ranges: Vec::new(),
3036 ..Default::default()
3037 };
3038 let mut matches = self.query_internal(&pattern, trace);
3039 if matches.len() > 5 {
3040 matches.truncate(5);
3041 }
3042 if !matches.is_empty() {
3043 css_fallbacks.push(CssFallbackGroup {
3044 css_name: generic.clone(),
3045 fonts: matches,
3046 });
3047 }
3048 }
3049 }
3050
3051 let important_ranges: &[UnicodeRange] =
3064 scripts_hint.unwrap_or(DEFAULT_UNICODE_FALLBACK_SCRIPTS);
3065 let unicode_fallbacks = if important_ranges.is_empty() {
3066 Vec::new()
3067 } else {
3068 let all_uncovered = vec![false; important_ranges.len()];
3069 self.find_unicode_fallbacks(
3070 important_ranges,
3071 &all_uncovered,
3072 &css_fallbacks,
3073 weight,
3074 italic,
3075 oblique,
3076 trace,
3077 )
3078 };
3079
3080 let mut unicode_fallbacks = unicode_fallbacks;
3088 if css_fallbacks.is_empty() && unicode_fallbacks.is_empty() {
3089 let st = self.state_read();
3090 if let Some((pat, id)) = st.patterns.iter().next() {
3091 unicode_fallbacks.push(FontMatch {
3092 id: *id,
3093 unicode_ranges: pat.unicode_ranges.clone(),
3094 fallbacks: Vec::new(),
3095 });
3096 }
3097 }
3098
3099 FontFallbackChain {
3100 css_fallbacks,
3101 unicode_fallbacks,
3102 original_stack: font_families.to_vec(),
3103 }
3104 }
3105
3106 #[allow(dead_code)]
3108 fn extract_unicode_ranges(text: &str) -> Vec<UnicodeRange> {
3109 let mut chars: Vec<char> = text.chars().collect();
3110 chars.sort_unstable();
3111 chars.dedup();
3112
3113 if chars.is_empty() {
3114 return Vec::new();
3115 }
3116
3117 let mut ranges = Vec::new();
3118 let mut range_start = chars[0] as u32;
3119 let mut range_end = range_start;
3120
3121 for &c in &chars[1..] {
3122 let codepoint = c as u32;
3123 if codepoint == range_end + 1 {
3124 range_end = codepoint;
3125 } else {
3126 ranges.push(UnicodeRange { start: range_start, end: range_end });
3127 range_start = codepoint;
3128 range_end = codepoint;
3129 }
3130 }
3131
3132 ranges.push(UnicodeRange { start: range_start, end: range_end });
3133 ranges
3134 }
3135
3136 #[cfg(feature = "std")]
3143 fn fuzzy_query_by_name(
3144 &self,
3145 requested_name: &str,
3146 weight: FcWeight,
3147 italic: PatternMatch,
3148 oblique: PatternMatch,
3149 unicode_ranges: &[UnicodeRange],
3150 _trace: &mut Vec<TraceMsg>,
3151 ) -> Vec<FontMatch> {
3152 let tokens = Self::extract_font_name_tokens(requested_name);
3154
3155 if tokens.is_empty() {
3156 return Vec::new();
3157 }
3158
3159 let tokens_lower: Vec<String> = tokens.iter().map(|t| t.to_ascii_lowercase()).collect();
3161
3162 let state = self.state_read();
3168
3169 let first_token = &tokens_lower[0];
3171 let mut candidate_ids = match state.token_index.get(first_token) {
3172 Some(ids) if !ids.is_empty() => ids.clone(),
3173 _ => {
3174 return Vec::new();
3176 }
3177 };
3178
3179 for token in &tokens_lower[1..] {
3181 if let Some(token_ids) = state.token_index.get(token) {
3182 let intersection: alloc::collections::BTreeSet<FontId> =
3184 candidate_ids.intersection(token_ids).copied().collect();
3185
3186 if intersection.is_empty() {
3187 break;
3189 } else {
3190 candidate_ids = intersection;
3192 }
3193 } else {
3194 break;
3196 }
3197 }
3198
3199 let mut candidates = Vec::new();
3201
3202 for id in candidate_ids {
3203 let pattern = match state.metadata.get(&id) {
3204 Some(p) => p,
3205 None => continue,
3206 };
3207
3208 let font_tokens_lower = match state.font_tokens.get(&id) {
3210 Some(tokens) => tokens,
3211 None => continue,
3212 };
3213
3214 if font_tokens_lower.is_empty() {
3215 continue;
3216 }
3217
3218 let token_matches = tokens_lower.iter()
3221 .filter(|req_token| {
3222 font_tokens_lower.iter().any(|font_token| {
3223 font_token == *req_token
3225 })
3226 })
3227 .count();
3228
3229 if token_matches == 0 {
3231 continue;
3232 }
3233
3234 let token_similarity = (token_matches * 100 / tokens.len()) as i32;
3236
3237 let unicode_similarity = if !unicode_ranges.is_empty() && !pattern.unicode_ranges.is_empty() {
3239 Self::calculate_unicode_compatibility(unicode_ranges, &pattern.unicode_ranges)
3240 } else {
3241 0
3242 };
3243
3244 if !unicode_ranges.is_empty() && unicode_similarity == 0 {
3247 continue;
3248 }
3249
3250 let style_score = Self::calculate_style_score(&FcPattern {
3251 weight,
3252 italic,
3253 oblique,
3254 ..Default::default()
3255 }, pattern);
3256
3257 candidates.push((
3258 id,
3259 token_similarity,
3260 unicode_similarity,
3261 style_score,
3262 pattern.clone(),
3263 ));
3264 }
3265
3266 candidates.sort_by(|a, b| {
3272 if !unicode_ranges.is_empty() {
3273 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 {
3280 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)) }
3286 });
3287
3288 candidates.truncate(5);
3290
3291 candidates
3293 .into_iter()
3294 .map(|(id, _token_sim, _unicode_sim, _style, pattern)| {
3295 FontMatch {
3296 id,
3297 unicode_ranges: pattern.unicode_ranges.clone(),
3298 fallbacks: Vec::new(), }
3300 })
3301 .collect()
3302 }
3303
3304 fn query_by_family_normalized(
3321 &self,
3322 family: &str,
3323 weight: FcWeight,
3324 italic: PatternMatch,
3325 oblique: PatternMatch,
3326 ) -> Vec<FontMatch> {
3327 let target = crate::utils::normalize_family_name(family);
3328 if target.is_empty() {
3329 return Vec::new();
3330 }
3331 let query = FcPattern {
3332 weight,
3333 italic,
3334 oblique,
3335 ..Default::default()
3336 };
3337 let state = self.state_read();
3338 let Some(ids) = state.family_index.get(&target) else {
3348 return Vec::new();
3349 };
3350 let mut candidates: Vec<(FontId, i32, FcPattern)> = Vec::new();
3351 for id in ids {
3352 let Some(meta) = state.metadata.get(id).or_else(|| {
3353 state.patterns.iter().find(|(_, pid)| *pid == id).map(|(p, _)| p)
3354 }) else {
3355 continue;
3356 };
3357 let style_score = Self::calculate_style_score(&query, meta);
3358 candidates.push((*id, style_score, meta.clone()));
3359 }
3360 drop(state);
3361
3362 candidates.sort_by(|a, b| {
3364 a.1.cmp(&b.1)
3365 .then_with(|| a.2.italic.cmp(&b.2.italic))
3366 .then_with(|| a.2.name.cmp(&b.2.name))
3367 });
3368 candidates.truncate(5);
3369 candidates
3370 .into_iter()
3371 .map(|(id, _, pattern)| FontMatch {
3372 id,
3373 unicode_ranges: pattern.unicode_ranges.clone(),
3374 fallbacks: Vec::new(),
3375 })
3376 .collect()
3377 }
3378
3379 pub fn extract_font_name_tokens(name: &str) -> Vec<String> {
3383 let mut tokens = Vec::new();
3384 let mut current_token = String::new();
3385 let mut last_was_lower = false;
3386
3387 for c in name.chars() {
3388 if c.is_whitespace() || c == '-' || c == '_' {
3389 if !current_token.is_empty() {
3391 tokens.push(current_token.clone());
3392 current_token.clear();
3393 }
3394 last_was_lower = false;
3395 } else if c.is_uppercase() && last_was_lower && !current_token.is_empty() {
3396 tokens.push(current_token.clone());
3398 current_token.clear();
3399 current_token.push(c);
3400 last_was_lower = false;
3401 } else {
3402 current_token.push(c);
3403 last_was_lower = c.is_lowercase();
3404 }
3405 }
3406
3407 if !current_token.is_empty() {
3408 tokens.push(current_token);
3409 }
3410
3411 tokens
3412 }
3413
3414 fn find_unicode_fallbacks(
3418 &self,
3419 unicode_ranges: &[UnicodeRange],
3420 covered_chars: &[bool],
3421 existing_groups: &[CssFallbackGroup],
3422 _weight: FcWeight,
3423 _italic: PatternMatch,
3424 _oblique: PatternMatch,
3425 trace: &mut Vec<TraceMsg>,
3426 ) -> Vec<FontMatch> {
3427 let mut uncovered_ranges = Vec::new();
3429 for (i, &covered) in covered_chars.iter().enumerate() {
3430 if !covered && i < unicode_ranges.len() {
3431 uncovered_ranges.push(unicode_ranges[i].clone());
3432 }
3433 }
3434
3435 if uncovered_ranges.is_empty() {
3436 return Vec::new();
3437 }
3438
3439 let pattern = FcPattern {
3444 name: None,
3445 weight: FcWeight::Normal, italic: PatternMatch::DontCare,
3447 oblique: PatternMatch::DontCare,
3448 unicode_ranges: uncovered_ranges.clone(),
3449 ..Default::default()
3450 };
3451
3452 let mut candidates = self.query_internal(&pattern, trace);
3453
3454 let existing_prefixes: Vec<String> = existing_groups
3457 .iter()
3458 .flat_map(|group| {
3459 group.fonts.iter().filter_map(|font| {
3460 self.get_metadata_by_id(&font.id)
3461 .and_then(|meta| meta.family.clone())
3462 .and_then(|family| {
3463 family.split_whitespace()
3465 .take(2)
3466 .collect::<Vec<_>>()
3467 .join(" ")
3468 .into()
3469 })
3470 })
3471 })
3472 .collect();
3473
3474 candidates.sort_by(|a, b| {
3478 let a_meta = self.get_metadata_by_id(&a.id);
3479 let b_meta = self.get_metadata_by_id(&b.id);
3480
3481 let a_score = Self::calculate_font_similarity_score(a_meta.as_ref(), &existing_prefixes);
3482 let b_score = Self::calculate_font_similarity_score(b_meta.as_ref(), &existing_prefixes);
3483
3484 b_score.cmp(&a_score) .then_with(|| {
3486 let a_coverage = Self::calculate_unicode_compatibility(&uncovered_ranges, &a.unicode_ranges);
3487 let b_coverage = Self::calculate_unicode_compatibility(&uncovered_ranges, &b.unicode_ranges);
3488 b_coverage.cmp(&a_coverage)
3489 })
3490 });
3491
3492 let mut result = Vec::new();
3494 let mut remaining_uncovered: Vec<bool> = vec![true; uncovered_ranges.len()];
3495
3496 for candidate in candidates {
3497 let mut covers_new_range = false;
3499
3500 for (i, range) in uncovered_ranges.iter().enumerate() {
3501 if remaining_uncovered[i] {
3502 for font_range in &candidate.unicode_ranges {
3504 if font_range.overlaps(range) {
3505 remaining_uncovered[i] = false;
3506 covers_new_range = true;
3507 break;
3508 }
3509 }
3510 }
3511 }
3512
3513 if covers_new_range {
3515 result.push(candidate);
3516
3517 if remaining_uncovered.iter().all(|&uncovered| !uncovered) {
3519 break;
3520 }
3521 }
3522 }
3523
3524 result
3525 }
3526
3527 fn calculate_font_similarity_score(
3530 font_meta: Option<&FcPattern>,
3531 existing_prefixes: &[String],
3532 ) -> i32 {
3533 let Some(meta) = font_meta else { return 0; };
3534 let Some(family) = &meta.family else { return 0; };
3535
3536 for prefix in existing_prefixes {
3538 if family.starts_with(prefix) {
3539 return 100; }
3541 if family.contains(prefix) {
3542 return 50; }
3544 }
3545
3546 0 }
3548
3549 pub fn calculate_unicode_coverage(ranges: &[UnicodeRange]) -> u64 {
3552 ranges
3553 .iter()
3554 .map(|range| (range.end - range.start + 1) as u64)
3555 .sum()
3556 }
3557
3558 pub fn normalize_unicode_ranges(mut ranges: Vec<UnicodeRange>) -> Vec<UnicodeRange> {
3571 if ranges.len() < 2 {
3572 return ranges;
3573 }
3574
3575 ranges.sort_unstable();
3576
3577 let mut out: Vec<UnicodeRange> = Vec::with_capacity(ranges.len());
3578 for range in ranges {
3579 match out.last_mut() {
3580 Some(prev) if range.start <= prev.end.saturating_add(1) => {
3583 prev.end = prev.end.max(range.end);
3584 }
3585 _ => out.push(range),
3586 }
3587 }
3588 out
3589 }
3590
3591 pub fn calculate_unicode_compatibility(
3594 requested: &[UnicodeRange],
3595 available: &[UnicodeRange],
3596 ) -> i32 {
3597 if requested.is_empty() {
3598 return Self::calculate_unicode_coverage(available) as i32;
3600 }
3601
3602 let mut total_coverage = 0u32;
3603
3604 for req_range in requested {
3605 for avail_range in available {
3606 let overlap_start = req_range.start.max(avail_range.start);
3608 let overlap_end = req_range.end.min(avail_range.end);
3609
3610 if overlap_start <= overlap_end {
3611 let overlap_size = overlap_end - overlap_start + 1;
3613 total_coverage += overlap_size;
3614 }
3615 }
3616 }
3617
3618 total_coverage as i32
3619 }
3620
3621 pub fn calculate_style_score(original: &FcPattern, candidate: &FcPattern) -> i32 {
3622
3623 let mut score = 0_i32;
3624
3625 if (original.bold == PatternMatch::True && candidate.weight == FcWeight::Bold)
3627 || (original.bold == PatternMatch::False && candidate.weight != FcWeight::Bold)
3628 {
3629 } else {
3632 let weight_diff = (original.weight as i32 - candidate.weight as i32).abs();
3634 score += weight_diff as i32;
3635 }
3636
3637 if original.weight == candidate.weight {
3640 score -= 15;
3641 if original.weight == FcWeight::Normal {
3642 score -= 10; }
3644 }
3645
3646 if (original.condensed == PatternMatch::True && candidate.stretch.is_condensed())
3648 || (original.condensed == PatternMatch::False && !candidate.stretch.is_condensed())
3649 {
3650 } else {
3653 let stretch_diff = (original.stretch as i32 - candidate.stretch as i32).abs();
3655 score += (stretch_diff * 100) as i32;
3656 }
3657
3658 let style_props = [
3660 (original.italic, candidate.italic, 300, 150),
3661 (original.oblique, candidate.oblique, 200, 100),
3662 (original.bold, candidate.bold, 300, 150),
3663 (original.monospace, candidate.monospace, 100, 50),
3664 (original.condensed, candidate.condensed, 100, 50),
3665 ];
3666
3667 for (orig, cand, mismatch_penalty, dontcare_penalty) in style_props {
3668 if orig.needs_to_match() {
3669 if orig == PatternMatch::False && cand == PatternMatch::DontCare {
3670 score += dontcare_penalty / 2;
3673 } else if !orig.matches(&cand) {
3674 if cand == PatternMatch::DontCare {
3675 score += dontcare_penalty;
3676 } else {
3677 score += mismatch_penalty;
3678 }
3679 } else if orig == PatternMatch::True && cand == PatternMatch::True {
3680 score -= 20;
3682 } else if orig == PatternMatch::False && cand == PatternMatch::False {
3683 score -= 20;
3686 }
3687 } else {
3688 if cand == PatternMatch::True {
3693 score += dontcare_penalty / 3;
3694 }
3695 }
3696 }
3697
3698 if let (Some(name), Some(family)) = (&candidate.name, &candidate.family) {
3704 let name_lower = name.to_ascii_lowercase();
3705 let family_lower = family.to_ascii_lowercase();
3706
3707 let extra = if name_lower.starts_with(&family_lower) {
3709 name_lower[family_lower.len()..].to_string()
3710 } else {
3711 String::new()
3712 };
3713
3714 let stripped = extra
3716 .replace("regular", "")
3717 .replace("normal", "")
3718 .replace("book", "")
3719 .replace("roman", "");
3720 let stripped = stripped.trim();
3721
3722 if stripped.is_empty() {
3723 score -= 50;
3725 } else {
3726 let extra_words = stripped.split_whitespace().count();
3728 score += (extra_words as i32) * 25;
3729 }
3730 }
3731
3732 if let Some(ref subfamily) = candidate.metadata.font_subfamily {
3736 let sf_lower = subfamily.to_ascii_lowercase();
3737 if sf_lower == "regular" {
3738 score -= 30;
3739 }
3740 }
3741
3742 score
3743 }
3744}
3745
3746#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3747fn FcScanDirectories() -> Option<(
3748 Vec<(FcPattern, FcFontPath)>,
3749 BTreeMap<String, FcFontRenderConfig>,
3750 BTreeMap<String, Vec<String>>,
3751)> {
3752 use std::fs;
3753 use std::path::Path;
3754
3755 let base_path = std::env::var("FONTCONFIG_FILE")
3758 .ok()
3759 .filter(|p| !p.is_empty())
3760 .unwrap_or_else(|| "/etc/fonts/fonts.conf".to_string());
3761
3762 if !Path::new(&base_path).exists() {
3763 return None;
3764 }
3765
3766 let mut font_paths = Vec::with_capacity(32);
3767 let mut paths_to_visit = vec![(None, PathBuf::from(&base_path))];
3768 let mut render_configs: BTreeMap<String, FcFontRenderConfig> = BTreeMap::new();
3769 let mut system_aliases: BTreeMap<String, Vec<String>> = BTreeMap::new();
3770
3771 while let Some((prefix, path_to_visit)) = paths_to_visit.pop() {
3772 let path = match process_path(&prefix, path_to_visit, true) {
3773 Some(path) => path,
3774 None => continue,
3775 };
3776
3777 let metadata = match fs::metadata(&path) {
3778 Ok(metadata) => metadata,
3779 Err(_) => continue,
3780 };
3781
3782 if metadata.is_file() {
3783 let xml_utf8 = match fs::read_to_string(&path) {
3784 Ok(xml_utf8) => xml_utf8,
3785 Err(_) => continue,
3786 };
3787
3788 if ParseFontsConf(&xml_utf8, &mut paths_to_visit, &mut font_paths).is_none() {
3789 continue;
3790 }
3791
3792 ParseFontsConfRenderConfig(&xml_utf8, &mut render_configs);
3794
3795 ParseFontsConfAliases(&xml_utf8, &mut system_aliases);
3798 } else if metadata.is_dir() {
3799 let dir_entries = match fs::read_dir(&path) {
3800 Ok(dir_entries) => dir_entries,
3801 Err(_) => continue,
3802 };
3803
3804 for entry_result in dir_entries {
3805 let entry = match entry_result {
3806 Ok(entry) => entry,
3807 Err(_) => continue,
3808 };
3809
3810 let entry_path = entry.path();
3811
3812 let entry_metadata = match fs::metadata(&entry_path) {
3814 Ok(metadata) => metadata,
3815 Err(_) => continue,
3816 };
3817
3818 if !entry_metadata.is_file() {
3819 continue;
3820 }
3821
3822 let file_name = match entry_path.file_name() {
3823 Some(name) => name,
3824 None => continue,
3825 };
3826
3827 let file_name_str = file_name.to_string_lossy();
3828 if file_name_str.starts_with(|c: char| c.is_ascii_digit())
3829 && file_name_str.ends_with(".conf")
3830 {
3831 paths_to_visit.push((None, entry_path));
3832 }
3833 }
3834 }
3835 }
3836
3837 if font_paths.is_empty() {
3838 return None;
3839 }
3840
3841 Some((FcScanDirectoriesInner(&font_paths), render_configs, system_aliases))
3842}
3843
3844#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3851fn ParseFontsConfAliases(input: &str, aliases: &mut BTreeMap<String, Vec<String>>) {
3852 use xmlparser::Token::*;
3853 use xmlparser::Tokenizer;
3854
3855 #[derive(Clone, Copy, PartialEq)]
3856 enum State {
3857 Idle,
3858 InAlias,
3859 InAliasFamily,
3860 InPrefer,
3861 InPreferFamily,
3862 }
3863
3864 let mut state = State::Idle;
3865 let mut alias_key: Option<String> = None;
3866 let mut preferred: Vec<String> = Vec::new();
3867 let mut text_buf = String::new();
3868
3869 for token_result in Tokenizer::from(input) {
3870 let token = match token_result {
3871 Ok(token) => token,
3872 Err(_) => continue,
3873 };
3874 match token {
3875 ElementStart { local, .. } => match local.as_str() {
3876 "alias" => {
3877 state = State::InAlias;
3878 alias_key = None;
3879 preferred.clear();
3880 }
3881 "family" if state == State::InAlias => {
3882 state = State::InAliasFamily;
3883 text_buf.clear();
3884 }
3885 "prefer" if state == State::InAlias => {
3886 state = State::InPrefer;
3887 }
3888 "family" if state == State::InPrefer => {
3889 state = State::InPreferFamily;
3890 text_buf.clear();
3891 }
3892 _ => {}
3893 },
3894 Text { text } => {
3895 if state == State::InAliasFamily || state == State::InPreferFamily {
3896 text_buf.push_str(text.as_str());
3897 }
3898 }
3899 ElementEnd { end, .. } => {
3900 use xmlparser::ElementEnd;
3901 let closed = match end {
3902 ElementEnd::Close(_, local) => Some(local.as_str().to_owned()),
3903 _ => None,
3904 };
3905 let Some(closed) = closed else { continue };
3906 match closed.as_str() {
3907 "family" => match state {
3908 State::InAliasFamily => {
3909 let t = text_buf.trim();
3910 if !t.is_empty() && alias_key.is_none() {
3911 alias_key = Some(t.to_owned());
3912 }
3913 state = State::InAlias;
3914 }
3915 State::InPreferFamily => {
3916 let t = text_buf.trim();
3917 if !t.is_empty() {
3918 preferred.push(t.to_owned());
3919 }
3920 state = State::InPrefer;
3921 }
3922 _ => {}
3923 },
3924 "prefer" if state == State::InPrefer => {
3925 state = State::InAlias;
3926 }
3927 "alias" => {
3928 if let Some(key) = alias_key.take() {
3929 if !preferred.is_empty() {
3930 let norm = crate::utils::normalize_family_name(&key);
3931 let entry = aliases.entry(norm).or_default();
3932 for fam in preferred.drain(..) {
3933 if !entry.iter().any(|e| e == &fam) {
3934 entry.push(fam);
3935 }
3936 }
3937 }
3938 }
3939 state = State::Idle;
3940 }
3941 _ => {}
3942 }
3943 }
3944 _ => {}
3945 }
3946 }
3947}
3948
3949#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3951fn ParseFontsConf(
3952 input: &str,
3953 paths_to_visit: &mut Vec<(Option<String>, PathBuf)>,
3954 font_paths: &mut Vec<(Option<String>, String)>,
3955) -> Option<()> {
3956 use xmlparser::Token::*;
3957 use xmlparser::Tokenizer;
3958
3959 const TAG_INCLUDE: &str = "include";
3960 const TAG_DIR: &str = "dir";
3961 const ATTRIBUTE_PREFIX: &str = "prefix";
3962
3963 let mut current_prefix: Option<&str> = None;
3964 let mut current_path: Option<&str> = None;
3965 let mut is_in_include = false;
3966 let mut is_in_dir = false;
3967
3968 for token_result in Tokenizer::from(input) {
3969 let token = match token_result {
3970 Ok(token) => token,
3971 Err(_) => return None,
3972 };
3973
3974 match token {
3975 ElementStart { local, .. } => {
3976 if is_in_include || is_in_dir {
3977 return None; }
3979
3980 match local.as_str() {
3981 TAG_INCLUDE => {
3982 is_in_include = true;
3983 }
3984 TAG_DIR => {
3985 is_in_dir = true;
3986 }
3987 _ => continue,
3988 }
3989
3990 current_path = None;
3991 }
3992 Text { text, .. } => {
3993 let text = text.as_str().trim();
3994 if text.is_empty() {
3995 continue;
3996 }
3997 if is_in_include || is_in_dir {
3998 current_path = Some(text);
3999 }
4000 }
4001 Attribute { local, value, .. } => {
4002 if !is_in_include && !is_in_dir {
4003 continue;
4004 }
4005 if local.as_str() == ATTRIBUTE_PREFIX {
4007 current_prefix = Some(value.as_str());
4008 }
4009 }
4010 ElementEnd { end, .. } => {
4011 let end_tag = match end {
4012 xmlparser::ElementEnd::Close(_, a) => a,
4013 _ => continue,
4014 };
4015
4016 match end_tag.as_str() {
4017 TAG_INCLUDE => {
4018 if !is_in_include {
4019 continue;
4020 }
4021
4022 if let Some(current_path) = current_path.as_ref() {
4023 paths_to_visit.push((
4024 current_prefix.map(ToOwned::to_owned),
4025 PathBuf::from(*current_path),
4026 ));
4027 }
4028 }
4029 TAG_DIR => {
4030 if !is_in_dir {
4031 continue;
4032 }
4033
4034 if let Some(current_path) = current_path.as_ref() {
4035 font_paths.push((
4036 current_prefix.map(ToOwned::to_owned),
4037 (*current_path).to_owned(),
4038 ));
4039 }
4040 }
4041 _ => continue,
4042 }
4043
4044 is_in_include = false;
4045 is_in_dir = false;
4046 current_path = None;
4047 current_prefix = None;
4048 }
4049 _ => {}
4050 }
4051 }
4052
4053 Some(())
4054}
4055
4056#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
4068fn ParseFontsConfRenderConfig(
4069 input: &str,
4070 configs: &mut BTreeMap<String, FcFontRenderConfig>,
4071) {
4072 use xmlparser::Token::*;
4073 use xmlparser::Tokenizer;
4074
4075 #[derive(Clone, Copy, PartialEq)]
4077 enum State {
4078 Idle,
4080 InMatchFont,
4082 InTestFamily,
4084 InEdit,
4086 InValue,
4088 }
4089
4090 let mut state = State::Idle;
4091 let mut match_is_font_target = false;
4092 let mut current_family: Option<String> = None;
4093 let mut current_edit_name: Option<String> = None;
4094 let mut current_value: Option<String> = None;
4095 let mut value_tag: Option<String> = None;
4096 let mut config = FcFontRenderConfig::default();
4097 let mut in_test = false;
4098 let mut test_name: Option<String> = None;
4099
4100 for token_result in Tokenizer::from(input) {
4101 let token = match token_result {
4102 Ok(token) => token,
4103 Err(_) => continue,
4104 };
4105
4106 match token {
4107 ElementStart { local, .. } => {
4108 let tag = local.as_str();
4109 match tag {
4110 "match" => {
4111 match_is_font_target = false;
4113 current_family = None;
4114 config = FcFontRenderConfig::default();
4115 }
4116 "test" if state == State::InMatchFont => {
4117 in_test = true;
4118 test_name = None;
4119 }
4120 "edit" if state == State::InMatchFont => {
4121 current_edit_name = None;
4122 }
4123 "bool" | "double" | "const" | "string" | "int" => {
4124 if state == State::InTestFamily || state == State::InEdit {
4125 value_tag = Some(tag.to_owned());
4126 current_value = None;
4127 }
4128 }
4129 _ => {}
4130 }
4131 }
4132 Attribute { local, value, .. } => {
4133 let attr_name = local.as_str();
4134 let attr_value = value.as_str();
4135
4136 match attr_name {
4137 "target" => {
4138 if attr_value == "font" {
4139 match_is_font_target = true;
4140 }
4141 }
4142 "name" => {
4143 if in_test && state == State::InMatchFont {
4144 test_name = Some(attr_value.to_owned());
4145 } else if state == State::InMatchFont {
4146 current_edit_name = Some(attr_value.to_owned());
4147 }
4148 }
4149 _ => {}
4150 }
4151 }
4152 Text { text, .. } => {
4153 let text = text.as_str().trim();
4154 if !text.is_empty() && (state == State::InTestFamily || state == State::InEdit) {
4155 current_value = Some(text.to_owned());
4156 }
4157 }
4158 ElementEnd { end, .. } => {
4159 match end {
4160 xmlparser::ElementEnd::Open => {
4161 if match_is_font_target && state == State::Idle {
4163 state = State::InMatchFont;
4164 match_is_font_target = false;
4165 } else if in_test {
4166 if test_name.as_deref() == Some("family") {
4167 state = State::InTestFamily;
4168 }
4169 in_test = false;
4170 } else if current_edit_name.is_some() && state == State::InMatchFont {
4171 state = State::InEdit;
4172 }
4173 }
4174 xmlparser::ElementEnd::Close(_, local) => {
4175 let tag = local.as_str();
4176 match tag {
4177 "match" => {
4178 if let Some(family) = current_family.take() {
4180 let empty = FcFontRenderConfig::default();
4181 if config != empty {
4182 configs.insert(family, config.clone());
4183 }
4184 }
4185 state = State::Idle;
4186 config = FcFontRenderConfig::default();
4187 }
4188 "test" => {
4189 if state == State::InTestFamily {
4190 if let Some(ref val) = current_value {
4192 current_family = Some(val.clone());
4193 }
4194 state = State::InMatchFont;
4195 }
4196 current_value = None;
4197 value_tag = None;
4198 }
4199 "edit" => {
4200 if state == State::InEdit {
4201 if let (Some(ref name), Some(ref val)) = (¤t_edit_name, ¤t_value) {
4203 apply_edit_value(&mut config, name, val, value_tag.as_deref());
4204 }
4205 state = State::InMatchFont;
4206 }
4207 current_edit_name = None;
4208 current_value = None;
4209 value_tag = None;
4210 }
4211 "bool" | "double" | "const" | "string" | "int" => {
4212 }
4214 _ => {}
4215 }
4216 }
4217 xmlparser::ElementEnd::Empty => {
4218 }
4220 }
4221 }
4222 _ => {}
4223 }
4224 }
4225}
4226
4227#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
4229fn apply_edit_value(
4230 config: &mut FcFontRenderConfig,
4231 edit_name: &str,
4232 value: &str,
4233 value_tag: Option<&str>,
4234) {
4235 match edit_name {
4236 "antialias" => {
4237 config.antialias = parse_bool_value(value);
4238 }
4239 "hinting" => {
4240 config.hinting = parse_bool_value(value);
4241 }
4242 "autohint" => {
4243 config.autohint = parse_bool_value(value);
4244 }
4245 "embeddedbitmap" => {
4246 config.embeddedbitmap = parse_bool_value(value);
4247 }
4248 "embolden" => {
4249 config.embolden = parse_bool_value(value);
4250 }
4251 "minspace" => {
4252 config.minspace = parse_bool_value(value);
4253 }
4254 "hintstyle" => {
4255 config.hintstyle = parse_hintstyle_const(value);
4256 }
4257 "rgba" => {
4258 config.rgba = parse_rgba_const(value);
4259 }
4260 "lcdfilter" => {
4261 config.lcdfilter = parse_lcdfilter_const(value);
4262 }
4263 "dpi" => {
4264 if let Ok(v) = value.parse::<f64>() {
4265 config.dpi = Some(v);
4266 }
4267 }
4268 "scale" => {
4269 if let Ok(v) = value.parse::<f64>() {
4270 config.scale = Some(v);
4271 }
4272 }
4273 _ => {
4274 }
4276 }
4277}
4278
4279#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
4280fn parse_bool_value(value: &str) -> Option<bool> {
4281 match value {
4282 "true" => Some(true),
4283 "false" => Some(false),
4284 _ => None,
4285 }
4286}
4287
4288#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
4289fn parse_hintstyle_const(value: &str) -> Option<FcHintStyle> {
4290 match value {
4291 "hintnone" => Some(FcHintStyle::None),
4292 "hintslight" => Some(FcHintStyle::Slight),
4293 "hintmedium" => Some(FcHintStyle::Medium),
4294 "hintfull" => Some(FcHintStyle::Full),
4295 _ => None,
4296 }
4297}
4298
4299#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
4300fn parse_rgba_const(value: &str) -> Option<FcRgba> {
4301 match value {
4302 "unknown" => Some(FcRgba::Unknown),
4303 "rgb" => Some(FcRgba::Rgb),
4304 "bgr" => Some(FcRgba::Bgr),
4305 "vrgb" => Some(FcRgba::Vrgb),
4306 "vbgr" => Some(FcRgba::Vbgr),
4307 "none" => Some(FcRgba::None),
4308 _ => None,
4309 }
4310}
4311
4312#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
4313fn parse_lcdfilter_const(value: &str) -> Option<FcLcdFilter> {
4314 match value {
4315 "lcdnone" => Some(FcLcdFilter::None),
4316 "lcddefault" => Some(FcLcdFilter::Default),
4317 "lcdlight" => Some(FcLcdFilter::Light),
4318 "lcdlegacy" => Some(FcLcdFilter::Legacy),
4319 _ => None,
4320 }
4321}
4322
4323#[cfg(all(feature = "std", feature = "parsing"))]
4326const UNICODE_RANGE_MAPPINGS: &[(usize, u32, u32)] = &[
4327 (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), ];
4460
4461#[cfg(all(feature = "std", feature = "parsing"))]
4464struct ParsedFontFace {
4465 pattern: FcPattern,
4466 font_index: usize,
4467}
4468
4469#[cfg(all(feature = "std", feature = "parsing"))]
4475fn parse_font_faces(font_bytes: &[u8]) -> Option<Vec<ParsedFontFace>> {
4476 use allsorts::{
4477 binary::read::ReadScope,
4478 font_data::FontData,
4479 get_name::fontcode_get_name,
4480 post::PostTable,
4481 tables::{
4482 os2::Os2, HeadTable, NameTable,
4483 },
4484 tag,
4485 };
4486 use std::collections::BTreeSet;
4487
4488 const FONT_SPECIFIER_NAME_ID: u16 = 4;
4489 const FONT_SPECIFIER_FAMILY_ID: u16 = 1;
4490
4491 let max_fonts = if font_bytes.len() >= 12 && &font_bytes[0..4] == b"ttcf" {
4492 let num_fonts =
4494 u32::from_be_bytes([font_bytes[8], font_bytes[9], font_bytes[10], font_bytes[11]]);
4495 std::cmp::min(num_fonts as usize, 100)
4497 } else {
4498 1
4500 };
4501
4502 let scope = ReadScope::new(font_bytes);
4503 let font_file = scope.read::<FontData<'_>>().ok()?;
4504
4505 let mut results = Vec::new();
4507
4508 for font_index in 0..max_fonts {
4509 let provider = font_file.table_provider(font_index).ok()?;
4510 let head_data = provider.table_data(tag::HEAD).ok()??.into_owned();
4511 let head_table = ReadScope::new(&head_data).read::<HeadTable>().ok()?;
4512
4513 let is_bold = head_table.is_bold();
4514 let is_italic = head_table.is_italic();
4515 let mut detected_monospace = None;
4516
4517 let post_data = provider.table_data(tag::POST).ok()??;
4518 if let Ok(post_table) = ReadScope::new(&post_data).read::<PostTable>() {
4519 detected_monospace = Some(post_table.header.is_fixed_pitch != 0);
4521 }
4522
4523 let os2_data = provider.table_data(tag::OS_2).ok().flatten();
4535 let os2_table = os2_data
4536 .as_deref()
4537 .and_then(|data| ReadScope::new(data).read_dep::<Os2>(data.len()).ok());
4538
4539 let is_oblique = os2_table.as_ref().is_some_and(|os2| {
4541 os2.fs_selection
4542 .contains(allsorts::tables::os2::FsSelectionFlag::OBLIQUE)
4543 });
4544 let weight = os2_table.as_ref().map_or(
4547 if is_bold { FcWeight::Bold } else { FcWeight::Normal },
4548 |os2| FcWeight::from_u16(os2.us_weight_class),
4549 );
4550 let stretch = os2_table
4551 .as_ref()
4552 .map_or(FcStretch::Normal, |os2| FcStretch::from_u16(os2.us_width_class));
4553
4554 let mut unicode_ranges = Vec::new();
4558
4559 let os2_ranges = os2_table.as_ref().map_or([0u32; 4], |os2| {
4563 [
4564 os2.ul_unicode_range1,
4565 os2.ul_unicode_range2,
4566 os2.ul_unicode_range3,
4567 os2.ul_unicode_range4,
4568 ]
4569 });
4570
4571 for &(bit, start, end) in UNICODE_RANGE_MAPPINGS {
4572 let range_idx = bit / 32;
4573 let bit_pos = bit % 32;
4574 if range_idx < 4 && (os2_ranges[range_idx] & (1 << bit_pos)) != 0 {
4575 unicode_ranges.push(UnicodeRange { start, end });
4576 }
4577 }
4578
4579 unicode_ranges = verify_unicode_ranges_with_cmap(&provider, unicode_ranges);
4597
4598 if let Some(cmap_ranges) = analyze_cmap_coverage(&provider) {
4599 unicode_ranges.extend(cmap_ranges);
4600 }
4601
4602 unicode_ranges = FcFontCache::normalize_unicode_ranges(unicode_ranges);
4606
4607 let is_monospace = detect_monospace(&provider, os2_table.as_ref(), detected_monospace)
4609 .unwrap_or(false);
4610
4611 let name_data = provider.table_data(tag::NAME).ok()??.into_owned();
4612 let name_table = ReadScope::new(&name_data).read::<NameTable>().ok()?;
4613
4614 let mut metadata = FcFontMetadata::default();
4616
4617 const NAME_ID_COPYRIGHT: u16 = 0;
4618 const NAME_ID_FAMILY: u16 = 1;
4619 const NAME_ID_SUBFAMILY: u16 = 2;
4620 const NAME_ID_UNIQUE_ID: u16 = 3;
4621 const NAME_ID_FULL_NAME: u16 = 4;
4622 const NAME_ID_VERSION: u16 = 5;
4623 const NAME_ID_POSTSCRIPT_NAME: u16 = 6;
4624 const NAME_ID_TRADEMARK: u16 = 7;
4625 const NAME_ID_MANUFACTURER: u16 = 8;
4626 const NAME_ID_DESIGNER: u16 = 9;
4627 const NAME_ID_DESCRIPTION: u16 = 10;
4628 const NAME_ID_VENDOR_URL: u16 = 11;
4629 const NAME_ID_DESIGNER_URL: u16 = 12;
4630 const NAME_ID_LICENSE: u16 = 13;
4631 const NAME_ID_LICENSE_URL: u16 = 14;
4632 const NAME_ID_PREFERRED_FAMILY: u16 = 16;
4633 const NAME_ID_PREFERRED_SUBFAMILY: u16 = 17;
4634
4635 metadata.copyright = get_name_string(&name_data, NAME_ID_COPYRIGHT);
4636 metadata.font_family = get_name_string(&name_data, NAME_ID_FAMILY);
4637 metadata.font_subfamily = get_name_string(&name_data, NAME_ID_SUBFAMILY);
4638 metadata.full_name = get_name_string(&name_data, NAME_ID_FULL_NAME);
4639 metadata.unique_id = get_name_string(&name_data, NAME_ID_UNIQUE_ID);
4640 metadata.version = get_name_string(&name_data, NAME_ID_VERSION);
4641 metadata.postscript_name = get_name_string(&name_data, NAME_ID_POSTSCRIPT_NAME);
4642 metadata.trademark = get_name_string(&name_data, NAME_ID_TRADEMARK);
4643 metadata.manufacturer = get_name_string(&name_data, NAME_ID_MANUFACTURER);
4644 metadata.designer = get_name_string(&name_data, NAME_ID_DESIGNER);
4645 metadata.id_description = get_name_string(&name_data, NAME_ID_DESCRIPTION);
4646 metadata.designer_url = get_name_string(&name_data, NAME_ID_DESIGNER_URL);
4647 metadata.manufacturer_url = get_name_string(&name_data, NAME_ID_VENDOR_URL);
4648 metadata.license = get_name_string(&name_data, NAME_ID_LICENSE);
4649 metadata.license_url = get_name_string(&name_data, NAME_ID_LICENSE_URL);
4650 metadata.preferred_family = get_name_string(&name_data, NAME_ID_PREFERRED_FAMILY);
4651 metadata.preferred_subfamily = get_name_string(&name_data, NAME_ID_PREFERRED_SUBFAMILY);
4652
4653 let mut f_family = None;
4655
4656 let patterns = name_table
4657 .name_records
4658 .iter()
4659 .filter_map(|name_record| {
4660 let name_id = name_record.name_id;
4661 if name_id == FONT_SPECIFIER_FAMILY_ID {
4662 if let Ok(Some(family)) =
4663 fontcode_get_name(&name_data, FONT_SPECIFIER_FAMILY_ID)
4664 {
4665 f_family = Some(family);
4666 }
4667 None
4668 } else if name_id == FONT_SPECIFIER_NAME_ID {
4669 let family = f_family.as_ref()?;
4670 let name = fontcode_get_name(&name_data, FONT_SPECIFIER_NAME_ID).ok()??;
4671 if name.to_bytes().is_empty() {
4672 None
4673 } else {
4674 let mut name_str =
4675 String::from_utf8_lossy(name.to_bytes()).to_string();
4676 let mut family_str =
4677 String::from_utf8_lossy(family.as_bytes()).to_string();
4678 if name_str.starts_with('.') {
4679 name_str = name_str[1..].to_string();
4680 }
4681 if family_str.starts_with('.') {
4682 family_str = family_str[1..].to_string();
4683 }
4684 Some((
4685 FcPattern {
4686 name: Some(name_str),
4687 family: Some(family_str),
4688 bold: if is_bold {
4689 PatternMatch::True
4690 } else {
4691 PatternMatch::False
4692 },
4693 italic: if is_italic {
4694 PatternMatch::True
4695 } else {
4696 PatternMatch::False
4697 },
4698 oblique: if is_oblique {
4699 PatternMatch::True
4700 } else {
4701 PatternMatch::False
4702 },
4703 monospace: if is_monospace {
4704 PatternMatch::True
4705 } else {
4706 PatternMatch::False
4707 },
4708 condensed: if stretch <= FcStretch::Condensed {
4709 PatternMatch::True
4710 } else {
4711 PatternMatch::False
4712 },
4713 weight,
4714 stretch,
4715 unicode_ranges: unicode_ranges.clone(),
4716 metadata: metadata.clone(),
4717 render_config: FcFontRenderConfig::default(),
4718 },
4719 font_index,
4720 ))
4721 }
4722 } else {
4723 None
4724 }
4725 })
4726 .collect::<BTreeSet<_>>();
4727
4728 results.extend(patterns.into_iter().map(|(pat, idx)| ParsedFontFace {
4729 pattern: pat,
4730 font_index: idx,
4731 }));
4732 }
4733
4734 if results.is_empty() {
4735 None
4736 } else {
4737 Some(results)
4738 }
4739}
4740
4741#[cfg(all(feature = "std", feature = "parsing"))]
4743pub(crate) fn FcParseFont(filepath: &PathBuf) -> Option<Vec<(FcPattern, FcFontPath)>> {
4744 #[cfg(all(not(target_family = "wasm"), feature = "std"))]
4745 use mmapio::MmapOptions;
4746 use std::fs::File;
4747
4748 let file = File::open(filepath).ok()?;
4750
4751 #[cfg(all(not(target_family = "wasm"), feature = "std"))]
4752 let font_bytes = unsafe { MmapOptions::new().map(&file).ok()? };
4753
4754 #[cfg(not(all(not(target_family = "wasm"), feature = "std")))]
4755 let font_bytes = std::fs::read(filepath).ok()?;
4756
4757 let faces = parse_font_faces(&font_bytes[..])?;
4758 let path_str = filepath.to_string_lossy().to_string();
4759 let bytes_hash = crate::utils::content_dedup_hash_u64(&font_bytes[..]);
4764
4765 Some(
4766 faces
4767 .into_iter()
4768 .map(|face| {
4769 (
4770 face.pattern,
4771 FcFontPath {
4772 path: path_str.clone(),
4773 font_index: face.font_index,
4774 bytes_hash,
4775 },
4776 )
4777 })
4778 .collect(),
4779 )
4780}
4781
4782#[cfg(all(feature = "std", feature = "parsing"))]
4799#[derive(Debug, Clone)]
4800pub struct FastCoverage {
4801 pub pattern: FcPattern,
4807 pub covered: alloc::collections::BTreeSet<char>,
4812 pub is_bold: bool,
4814 pub is_italic: bool,
4816}
4817
4818#[cfg(all(feature = "std", feature = "parsing"))]
4835#[allow(non_snake_case)]
4836pub fn FcParseFontFaceFast(
4837 font_bytes: &[u8],
4838 font_index: usize,
4839 codepoints: &alloc::collections::BTreeSet<char>,
4840) -> Option<FastCoverage> {
4841 use allsorts::{
4842 binary::read::ReadScope,
4843 font_data::FontData,
4844 tables::{
4845 cmap::{Cmap, CmapSubtable},
4846 FontTableProvider, HeadTable,
4847 },
4848 tag,
4849 };
4850
4851 let scope = ReadScope::new(font_bytes);
4852 let font_file = scope.read::<FontData<'_>>().ok()?;
4853 let provider = font_file.table_provider(font_index).ok()?;
4854
4855 let head_data = provider.table_data(tag::HEAD).ok()??;
4857 let head_table = ReadScope::new(&head_data).read::<HeadTable>().ok()?;
4858 let is_bold = head_table.is_bold();
4859 let is_italic = head_table.is_italic();
4860
4861 let cmap_data = provider.table_data(tag::CMAP).ok()??;
4864 let cmap = ReadScope::new(&cmap_data).read::<Cmap<'_>>().ok()?;
4865 let encoding_record = find_best_cmap_subtable(&cmap)?;
4866 let cmap_subtable = ReadScope::new(&cmap_data)
4867 .offset(encoding_record.offset as usize)
4868 .read::<CmapSubtable<'_>>()
4869 .ok()?;
4870
4871 let mut covered: alloc::collections::BTreeSet<char> =
4872 alloc::collections::BTreeSet::new();
4873 let mut covered_ranges: Vec<UnicodeRange> = Vec::new();
4874 for ch in codepoints {
4875 let cp = *ch as u32;
4876 if let Ok(Some(gid)) = cmap_subtable.map_glyph(cp) {
4877 if gid != 0 {
4878 covered.insert(*ch);
4879 if let Some(last) = covered_ranges.last_mut() {
4883 if cp == last.end + 1 {
4884 last.end = cp;
4885 continue;
4886 }
4887 }
4888 covered_ranges.push(UnicodeRange { start: cp, end: cp });
4889 }
4890 }
4891 }
4892
4893 let weight = if is_bold {
4894 FcWeight::Bold
4895 } else {
4896 FcWeight::Normal
4897 };
4898 let italic_match = if is_italic {
4899 PatternMatch::True
4900 } else {
4901 PatternMatch::False
4902 };
4903
4904 let pattern = FcPattern {
4905 name: None,
4906 family: None,
4907 weight,
4908 italic: italic_match,
4909 oblique: PatternMatch::DontCare,
4910 monospace: PatternMatch::DontCare,
4911 unicode_ranges: covered_ranges,
4912 ..Default::default()
4913 };
4914
4915 Some(FastCoverage {
4916 pattern,
4917 covered,
4918 is_bold,
4919 is_italic,
4920 })
4921}
4922
4923#[cfg(all(feature = "std", feature = "parsing"))]
4928#[allow(non_snake_case)]
4929pub fn FcCountFontFaces(font_bytes: &[u8]) -> usize {
4930 if font_bytes.len() >= 12 && &font_bytes[0..4] == b"ttcf" {
4931 let num_fonts = u32::from_be_bytes([
4932 font_bytes[8], font_bytes[9], font_bytes[10], font_bytes[11],
4933 ]);
4934 std::cmp::min(num_fonts as usize, 100).max(1)
4936 } else {
4937 1
4938 }
4939}
4940
4941#[cfg(all(feature = "std", feature = "parsing"))]
4967#[allow(non_snake_case)]
4968pub fn FcParseFontBytes(font_bytes: &[u8], font_id: &str) -> Option<Vec<(FcPattern, FcFont)>> {
4969 FcParseFontBytesInner(font_bytes, font_id)
4970}
4971
4972#[cfg(all(feature = "std", feature = "parsing"))]
4975fn FcParseFontBytesInner(font_bytes: &[u8], font_id: &str) -> Option<Vec<(FcPattern, FcFont)>> {
4976 let faces = parse_font_faces(font_bytes)?;
4977 let id = font_id.to_string();
4978 let bytes = font_bytes.to_vec();
4979
4980 Some(
4981 faces
4982 .into_iter()
4983 .map(|face| {
4984 (
4985 face.pattern,
4986 FcFont {
4987 bytes: bytes.clone(),
4988 font_index: face.font_index,
4989 id: id.clone(),
4990 },
4991 )
4992 })
4993 .collect(),
4994 )
4995}
4996
4997#[cfg(all(feature = "std", feature = "parsing"))]
4998fn FcScanDirectoriesInner(paths: &[(Option<String>, String)]) -> Vec<(FcPattern, FcFontPath)> {
4999 #[cfg(all(feature = "multithreading", not(target_family = "wasm")))]
5000 {
5001 use rayon::prelude::*;
5002
5003 paths
5005 .par_iter()
5006 .filter_map(|(prefix, p)| {
5007 process_path(prefix, PathBuf::from(p), false).map(FcScanSingleDirectoryRecursive)
5008 })
5009 .flatten()
5010 .collect()
5011 }
5012 #[cfg(not(all(feature = "multithreading", not(target_family = "wasm"))))]
5015 {
5016 paths
5017 .iter()
5018 .filter_map(|(prefix, p)| {
5019 process_path(prefix, PathBuf::from(p), false).map(FcScanSingleDirectoryRecursive)
5020 })
5021 .flatten()
5022 .collect()
5023 }
5024}
5025
5026#[cfg(feature = "std")]
5028fn FcCollectFontFilesRecursive(dir: PathBuf) -> Vec<PathBuf> {
5029 let mut files = Vec::new();
5030 let mut dirs_to_parse = vec![dir];
5031
5032 loop {
5033 let mut new_dirs = Vec::new();
5034 for dir in &dirs_to_parse {
5035 let entries = match std::fs::read_dir(dir) {
5036 Ok(o) => o,
5037 Err(_) => continue,
5038 };
5039 for entry in entries.flatten() {
5040 let path = entry.path();
5041 if path.is_dir() {
5042 new_dirs.push(path);
5043 } else {
5044 files.push(path);
5045 }
5046 }
5047 }
5048 if new_dirs.is_empty() {
5049 break;
5050 }
5051 dirs_to_parse = new_dirs;
5052 }
5053
5054 files
5055}
5056
5057#[cfg(all(feature = "std", feature = "parsing"))]
5058fn FcScanSingleDirectoryRecursive(dir: PathBuf) -> Vec<(FcPattern, FcFontPath)> {
5059 let files = FcCollectFontFilesRecursive(dir);
5060 FcParseFontFiles(&files)
5061}
5062
5063#[cfg(all(feature = "std", feature = "parsing"))]
5064fn FcParseFontFiles(files_to_parse: &[PathBuf]) -> Vec<(FcPattern, FcFontPath)> {
5065 let result = {
5066 #[cfg(all(feature = "multithreading", not(target_family = "wasm")))]
5067 {
5068 use rayon::prelude::*;
5069
5070 files_to_parse
5071 .par_iter()
5072 .filter_map(|file| FcParseFont(file))
5073 .collect::<Vec<Vec<_>>>()
5074 }
5075 #[cfg(not(all(feature = "multithreading", not(target_family = "wasm"))))]
5076 {
5077 files_to_parse
5078 .iter()
5079 .filter_map(|file| FcParseFont(file))
5080 .collect::<Vec<Vec<_>>>()
5081 }
5082 };
5083
5084 result.into_iter().flat_map(|f| f.into_iter()).collect()
5085}
5086
5087#[cfg(all(feature = "std", feature = "parsing"))]
5088fn process_path(
5092 prefix: &Option<String>,
5093 mut path: PathBuf,
5094 is_include_path: bool,
5095) -> Option<PathBuf> {
5096 use std::env::var;
5097
5098 const HOME_SHORTCUT: &str = "~";
5099 const CWD_PATH: &str = ".";
5100
5101 const HOME_ENV_VAR: &str = "HOME";
5102 const XDG_CONFIG_HOME_ENV_VAR: &str = "XDG_CONFIG_HOME";
5103 const XDG_CONFIG_HOME_DEFAULT_PATH_SUFFIX: &str = ".config";
5104 const XDG_DATA_HOME_ENV_VAR: &str = "XDG_DATA_HOME";
5105 const XDG_DATA_HOME_DEFAULT_PATH_SUFFIX: &str = ".local/share";
5106
5107 const PREFIX_CWD: &str = "cwd";
5108 const PREFIX_DEFAULT: &str = "default";
5109 const PREFIX_XDG: &str = "xdg";
5110
5111 fn get_home_value() -> Option<PathBuf> {
5113 var(HOME_ENV_VAR).ok().map(PathBuf::from)
5114 }
5115 fn get_xdg_config_home_value() -> Option<PathBuf> {
5116 var(XDG_CONFIG_HOME_ENV_VAR)
5117 .ok()
5118 .map(PathBuf::from)
5119 .or_else(|| {
5120 get_home_value()
5121 .map(|home_path| home_path.join(XDG_CONFIG_HOME_DEFAULT_PATH_SUFFIX))
5122 })
5123 }
5124 fn get_xdg_data_home_value() -> Option<PathBuf> {
5125 var(XDG_DATA_HOME_ENV_VAR)
5126 .ok()
5127 .map(PathBuf::from)
5128 .or_else(|| {
5129 get_home_value().map(|home_path| home_path.join(XDG_DATA_HOME_DEFAULT_PATH_SUFFIX))
5130 })
5131 }
5132
5133 if path.starts_with(HOME_SHORTCUT) {
5135 if let Some(home_path) = get_home_value() {
5136 path = home_path.join(
5137 path.strip_prefix(HOME_SHORTCUT)
5138 .expect("already checked that it starts with the prefix"),
5139 );
5140 } else {
5141 return None;
5142 }
5143 }
5144
5145 match prefix {
5147 Some(prefix) => match prefix.as_str() {
5148 PREFIX_CWD | PREFIX_DEFAULT => {
5149 let mut new_path = PathBuf::from(CWD_PATH);
5150 new_path.push(path);
5151
5152 Some(new_path)
5153 }
5154 PREFIX_XDG => {
5155 if is_include_path {
5156 get_xdg_config_home_value()
5157 .map(|xdg_config_home_path| xdg_config_home_path.join(path))
5158 } else {
5159 get_xdg_data_home_value()
5160 .map(|xdg_data_home_path| xdg_data_home_path.join(path))
5161 }
5162 }
5163 _ => None, },
5165 None => Some(path),
5166 }
5167}
5168
5169#[cfg(all(feature = "std", feature = "parsing"))]
5171fn get_name_string(name_data: &[u8], name_id: u16) -> Option<String> {
5172 fontcode_get_name(name_data, name_id)
5173 .ok()
5174 .flatten()
5175 .map(|name| String::from_utf8_lossy(name.to_bytes()).to_string())
5176}
5177
5178#[cfg(all(feature = "std", feature = "parsing"))]
5182fn get_verification_codepoints(start: u32, end: u32) -> Vec<u32> {
5183 match start {
5184 0x0000 => vec!['A' as u32, 'M' as u32, 'Z' as u32, 'a' as u32, 'm' as u32, 'z' as u32],
5186 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], _ => {
5262 let range_size = end - start;
5263 if range_size > 20 {
5264 vec![
5265 start + range_size / 5,
5266 start + 2 * range_size / 5,
5267 start + 3 * range_size / 5,
5268 start + 4 * range_size / 5,
5269 ]
5270 } else {
5271 vec![start, start + range_size / 2]
5272 }
5273 }
5274 }
5275}
5276
5277#[cfg(all(feature = "std", feature = "parsing"))]
5280fn find_best_cmap_subtable<'a>(
5281 cmap: &allsorts::tables::cmap::Cmap<'a>,
5282) -> Option<allsorts::tables::cmap::EncodingRecord> {
5283 use allsorts::tables::cmap::{PlatformId, EncodingId};
5284
5285 cmap.find_subtable(PlatformId::UNICODE, EncodingId(3))
5286 .or_else(|| cmap.find_subtable(PlatformId::UNICODE, EncodingId(4)))
5287 .or_else(|| cmap.find_subtable(PlatformId::WINDOWS, EncodingId(1)))
5288 .or_else(|| cmap.find_subtable(PlatformId::WINDOWS, EncodingId(10)))
5289 .or_else(|| cmap.find_subtable(PlatformId::UNICODE, EncodingId(0)))
5290 .or_else(|| cmap.find_subtable(PlatformId::UNICODE, EncodingId(1)))
5291}
5292
5293#[cfg(all(feature = "std", feature = "parsing"))]
5296fn verify_unicode_ranges_with_cmap(
5297 provider: &impl FontTableProvider,
5298 os2_ranges: Vec<UnicodeRange>
5299) -> Vec<UnicodeRange> {
5300 use allsorts::tables::cmap::{Cmap, CmapSubtable};
5301
5302 if os2_ranges.is_empty() {
5303 return Vec::new();
5304 }
5305
5306 let cmap_data = match provider.table_data(tag::CMAP) {
5308 Ok(Some(data)) => data,
5309 _ => return os2_ranges, };
5311
5312 let cmap = match ReadScope::new(&cmap_data).read::<Cmap<'_>>() {
5313 Ok(c) => c,
5314 Err(_) => return os2_ranges,
5315 };
5316
5317 let encoding_record = match find_best_cmap_subtable(&cmap) {
5318 Some(r) => r,
5319 None => return os2_ranges, };
5321
5322 let cmap_subtable = match ReadScope::new(&cmap_data)
5323 .offset(encoding_record.offset as usize)
5324 .read::<CmapSubtable<'_>>()
5325 {
5326 Ok(st) => st,
5327 Err(_) => return os2_ranges,
5328 };
5329
5330 let mut verified_ranges = Vec::new();
5332
5333 for range in os2_ranges {
5334 let test_codepoints = get_verification_codepoints(range.start, range.end);
5335
5336 let required_hits = (test_codepoints.len() + 1) / 2; let mut hits = 0;
5340
5341 for cp in test_codepoints {
5342 if cp >= range.start && cp <= range.end {
5343 if let Ok(Some(gid)) = cmap_subtable.map_glyph(cp) {
5344 if gid != 0 {
5345 hits += 1;
5346 if hits >= required_hits {
5347 break;
5348 }
5349 }
5350 }
5351 }
5352 }
5353
5354 if hits >= required_hits {
5355 verified_ranges.push(range);
5356 }
5357 }
5358
5359 verified_ranges
5360}
5361
5362#[cfg(all(feature = "std", feature = "parsing"))]
5365fn analyze_cmap_coverage(provider: &impl FontTableProvider) -> Option<Vec<UnicodeRange>> {
5366 use allsorts::tables::cmap::{Cmap, CmapSubtable};
5367
5368 let cmap_data = provider.table_data(tag::CMAP).ok()??;
5369 let cmap = ReadScope::new(&cmap_data).read::<Cmap<'_>>().ok()?;
5370
5371 let encoding_record = find_best_cmap_subtable(&cmap)?;
5372
5373 let cmap_subtable = ReadScope::new(&cmap_data)
5374 .offset(encoding_record.offset as usize)
5375 .read::<CmapSubtable<'_>>()
5376 .ok()?;
5377
5378 let blocks_to_check: &[(u32, u32)] = &[
5380 (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), ];
5431
5432 let mut ranges = Vec::new();
5433
5434 for &(start, end) in blocks_to_check {
5435 let test_codepoints = get_verification_codepoints(start, end);
5436 let required_hits = (test_codepoints.len() + 1) / 2;
5437 let allowed_misses = test_codepoints.len() - required_hits;
5442 let mut hits = 0;
5443 let mut misses = 0;
5444
5445 for cp in test_codepoints {
5446 if matches!(cmap_subtable.map_glyph(cp), Ok(Some(gid)) if gid != 0) {
5447 hits += 1;
5448 if hits >= required_hits {
5449 break;
5450 }
5451 } else {
5452 misses += 1;
5453 if misses > allowed_misses {
5454 break;
5455 }
5456 }
5457 }
5458
5459 if hits >= required_hits {
5460 ranges.push(UnicodeRange { start, end });
5461 }
5462 }
5463
5464 if ranges.is_empty() {
5465 None
5466 } else {
5467 Some(ranges)
5468 }
5469}
5470
5471#[cfg(all(feature = "std", feature = "parsing"))]
5473#[allow(dead_code)]
5474fn extract_unicode_ranges(os2_table: &Os2) -> Vec<UnicodeRange> {
5475 let mut unicode_ranges = Vec::new();
5476
5477 let ranges = [
5478 os2_table.ul_unicode_range1,
5479 os2_table.ul_unicode_range2,
5480 os2_table.ul_unicode_range3,
5481 os2_table.ul_unicode_range4,
5482 ];
5483
5484 for &(bit, start, end) in UNICODE_RANGE_MAPPINGS {
5485 let range_idx = bit / 32;
5486 let bit_pos = bit % 32;
5487 if range_idx < 4 && (ranges[range_idx] & (1 << bit_pos)) != 0 {
5488 unicode_ranges.push(UnicodeRange { start, end });
5489 }
5490 }
5491
5492 unicode_ranges
5493}
5494
5495#[cfg(all(feature = "std", feature = "parsing"))]
5497fn detect_monospace(
5498 provider: &impl FontTableProvider,
5499 os2_table: Option<&Os2>,
5500 detected_monospace: Option<bool>,
5501) -> Option<bool> {
5502 if let Some(is_monospace) = detected_monospace {
5503 return Some(is_monospace);
5504 }
5505
5506 if let Some(os2_table) = os2_table {
5509 if os2_table.panose[0] == 2 {
5510 return Some(os2_table.panose[3] == 9); }
5513 }
5514
5515 let hhea_data = provider.table_data(tag::HHEA).ok()??;
5517 let hhea_table = ReadScope::new(&hhea_data).read::<HheaTable>().ok()?;
5518 let maxp_data = provider.table_data(tag::MAXP).ok()??;
5519 let maxp_table = ReadScope::new(&maxp_data).read::<MaxpTable>().ok()?;
5520 let hmtx_data = provider.table_data(tag::HMTX).ok()??;
5521 let hmtx_table = ReadScope::new(&hmtx_data)
5522 .read_dep::<HmtxTable<'_>>((
5523 usize::from(maxp_table.num_glyphs),
5524 usize::from(hhea_table.num_h_metrics),
5525 ))
5526 .ok()?;
5527
5528 let mut monospace = true;
5529 let mut last_advance = 0;
5530
5531 for i in 0..hhea_table.num_h_metrics as usize {
5533 let advance = hmtx_table.h_metrics.read_item(i).ok()?.advance_width;
5534 if i > 0 && advance != last_advance {
5535 monospace = false;
5536 break;
5537 }
5538 last_advance = advance;
5539 }
5540
5541 Some(monospace)
5542}
5543
5544#[cfg(all(feature = "std", not(feature = "parsing")))]
5553fn pattern_from_filename(path: &std::path::Path) -> Option<FcPattern> {
5554 let ext = path.extension()?.to_str()?.to_ascii_lowercase();
5555 match ext.as_str() {
5556 "ttf" | "otf" | "ttc" | "woff" | "woff2" => {}
5557 _ => return None,
5558 }
5559
5560 let stem = path.file_stem()?.to_str()?;
5561 let all_tokens = crate::config::tokenize_lowercase(stem);
5562
5563 let has_token = |kw: &str| all_tokens.iter().any(|t| t == kw);
5565 let is_bold = has_token("bold") || has_token("heavy");
5566 let is_italic = has_token("italic");
5567 let is_oblique = has_token("oblique");
5568 let is_mono = has_token("mono") || has_token("monospace");
5569 let is_condensed = has_token("condensed");
5570
5571 let family_tokens = crate::config::tokenize_font_stem(stem);
5573 if family_tokens.is_empty() { return None; }
5574 let family = family_tokens.join(" ");
5575
5576 Some(FcPattern {
5577 name: Some(stem.to_string()),
5578 family: Some(family),
5579 bold: if is_bold { PatternMatch::True } else { PatternMatch::False },
5580 italic: if is_italic { PatternMatch::True } else { PatternMatch::False },
5581 oblique: if is_oblique { PatternMatch::True } else { PatternMatch::DontCare },
5582 monospace: if is_mono { PatternMatch::True } else { PatternMatch::DontCare },
5583 condensed: if is_condensed { PatternMatch::True } else { PatternMatch::DontCare },
5584 weight: if is_bold { FcWeight::Bold } else { FcWeight::Normal },
5585 stretch: if is_condensed { FcStretch::Condensed } else { FcStretch::Normal },
5586 unicode_ranges: Vec::new(),
5587 metadata: FcFontMetadata::default(),
5588 render_config: FcFontRenderConfig::default(),
5589 })
5590}
5591
5592#[cfg(all(test, feature = "std", feature = "parsing", target_os = "linux"))]
5593mod system_alias_tests {
5594 use super::*;
5595
5596 const SAMPLE: &str = r#"<?xml version="1.0"?>
5597<fontconfig>
5598 <alias>
5599 <family>sans-serif</family>
5600 <prefer>
5601 <family>Noto Sans</family>
5602 <family>DejaVu Sans</family>
5603 </prefer>
5604 </alias>
5605 <alias>
5606 <family>Arial</family>
5607 <prefer><family>Liberation Sans</family></prefer>
5608 </alias>
5609 <alias binding="same">
5610 <family>monospace</family>
5611 <prefer><family>Noto Sans Mono</family></prefer>
5612 </alias>
5613</fontconfig>"#;
5614
5615 const SECOND_FILE: &str = r#"<fontconfig>
5616 <alias>
5617 <family>sans-serif</family>
5618 <prefer>
5619 <family>Ubuntu</family>
5620 <family>Noto Sans</family>
5621 </prefer>
5622 </alias>
5623</fontconfig>"#;
5624
5625 #[test]
5626 fn alias_blocks_parse_with_order_and_dedup_across_files() {
5627 let mut aliases = BTreeMap::new();
5628 ParseFontsConfAliases(SAMPLE, &mut aliases);
5629 ParseFontsConfAliases(SECOND_FILE, &mut aliases);
5630 let key = crate::utils::normalize_family_name("sans-serif");
5631 assert_eq!(
5632 aliases.get(&key).map(Vec::as_slice),
5633 Some(&["Noto Sans".to_string(), "DejaVu Sans".to_string(), "Ubuntu".to_string()][..]),
5634 "prefer entries append across files in include order, deduplicated"
5635 );
5636 assert_eq!(
5637 aliases.get("arial").map(Vec::as_slice),
5638 Some(&["Liberation Sans".to_string()][..]),
5639 "named-family aliases parse too (key normalized)"
5640 );
5641 assert_eq!(
5642 aliases.get("monospace").map(Vec::as_slice),
5643 Some(&["Noto Sans Mono".to_string()][..]),
5644 "alias attributes (binding=...) do not confuse the parser"
5645 );
5646 }
5647
5648 #[test]
5649 fn config_first_expansion_beats_the_builtin_lists() {
5650 let cache = FcFontCache::default();
5651 {
5652 let mut state = cache.state_write();
5653 let mut aliases = BTreeMap::new();
5654 ParseFontsConfAliases(SAMPLE, &mut aliases);
5655 state.system_aliases = aliases;
5656 }
5657 let out = cache.expand_font_families_config_first(
5658 &["Arial".to_string(), "sans-serif".to_string()],
5659 OperatingSystem::Linux,
5660 &[],
5661 );
5662 assert_eq!(
5663 out,
5664 vec![
5665 "Arial".to_string(), "Liberation Sans".to_string(), "Noto Sans".to_string(), "DejaVu Sans".to_string(),
5669 ],
5670 "configured preferences resolve the stack; no built-in list entries leak in"
5671 );
5672 }
5673
5674 #[test]
5675 fn generic_family_without_config_falls_back_to_builtin_lists() {
5676 let cache = FcFontCache::default();
5677 let out = cache.expand_font_families_config_first(
5678 &["sans-serif".to_string()],
5679 OperatingSystem::Linux,
5680 &[],
5681 );
5682 assert!(
5683 !out.is_empty() && out.iter().any(|f| f == "DejaVu Sans"),
5684 "no configuration parsed -> the built-in candidates are the last resort: {out:?}"
5685 );
5686 }
5687}