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_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> {
370 let mut expanded = Vec::new();
371
372 for family in families {
373 expanded.extend(os.expand_generic_family(family, unicode_ranges));
374 }
375
376 expanded
377}
378
379#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
381#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
382pub struct FontId(pub u128);
383
384impl core::fmt::Debug for FontId {
385 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
386 core::fmt::Display::fmt(self, f)
387 }
388}
389
390impl core::fmt::Display for FontId {
391 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
392 let id = self.0;
393 write!(
394 f,
395 "{:08x}-{:04x}-{:04x}-{:04x}-{:012x}",
396 (id >> 96) & 0xFFFFFFFF,
397 (id >> 80) & 0xFFFF,
398 (id >> 64) & 0xFFFF,
399 (id >> 48) & 0xFFFF,
400 id & 0xFFFFFFFFFFFF
401 )
402 }
403}
404
405impl FontId {
406 pub fn new() -> Self {
408 use core::sync::atomic::{AtomicU64, Ordering};
409 static COUNTER: AtomicU64 = AtomicU64::new(1);
410 let id = COUNTER.fetch_add(1, Ordering::Relaxed) as u128;
411 FontId(id)
412 }
413}
414
415#[derive(Debug, Default, Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
417#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
418#[repr(C)]
419pub enum PatternMatch {
420 #[default]
422 DontCare,
423 True,
425 False,
427}
428
429impl PatternMatch {
430 fn needs_to_match(&self) -> bool {
431 matches!(self, PatternMatch::True | PatternMatch::False)
432 }
433
434 fn matches(&self, other: &PatternMatch) -> bool {
435 match (self, other) {
436 (PatternMatch::DontCare, _) => true,
437 (_, PatternMatch::DontCare) => true,
438 (a, b) => a == b,
439 }
440 }
441}
442
443#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
445#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
446#[repr(C)]
447pub enum FcWeight {
448 Thin = 100,
449 ExtraLight = 200,
450 Light = 300,
451 Normal = 400,
452 Medium = 500,
453 SemiBold = 600,
454 Bold = 700,
455 ExtraBold = 800,
456 Black = 900,
457}
458
459impl FcWeight {
460 pub fn from_u16(weight: u16) -> Self {
461 match weight {
462 0..=149 => FcWeight::Thin,
463 150..=249 => FcWeight::ExtraLight,
464 250..=349 => FcWeight::Light,
465 350..=449 => FcWeight::Normal,
466 450..=549 => FcWeight::Medium,
467 550..=649 => FcWeight::SemiBold,
468 650..=749 => FcWeight::Bold,
469 750..=849 => FcWeight::ExtraBold,
470 _ => FcWeight::Black,
471 }
472 }
473
474 pub fn find_best_match(&self, available: &[FcWeight]) -> Option<FcWeight> {
475 if available.is_empty() {
476 return None;
477 }
478
479 if available.contains(self) {
481 return Some(*self);
482 }
483
484 let self_value = *self as u16;
486
487 match *self {
488 FcWeight::Normal => {
489 if available.contains(&FcWeight::Medium) {
491 return Some(FcWeight::Medium);
492 }
493 for weight in &[FcWeight::Light, FcWeight::ExtraLight, FcWeight::Thin] {
495 if available.contains(weight) {
496 return Some(*weight);
497 }
498 }
499 for weight in &[
501 FcWeight::SemiBold,
502 FcWeight::Bold,
503 FcWeight::ExtraBold,
504 FcWeight::Black,
505 ] {
506 if available.contains(weight) {
507 return Some(*weight);
508 }
509 }
510 }
511 FcWeight::Medium => {
512 if available.contains(&FcWeight::Normal) {
514 return Some(FcWeight::Normal);
515 }
516 for weight in &[FcWeight::Light, FcWeight::ExtraLight, FcWeight::Thin] {
518 if available.contains(weight) {
519 return Some(*weight);
520 }
521 }
522 for weight in &[
524 FcWeight::SemiBold,
525 FcWeight::Bold,
526 FcWeight::ExtraBold,
527 FcWeight::Black,
528 ] {
529 if available.contains(weight) {
530 return Some(*weight);
531 }
532 }
533 }
534 FcWeight::Thin | FcWeight::ExtraLight | FcWeight::Light => {
535 let mut best_match = None;
537 let mut smallest_diff = u16::MAX;
538
539 for weight in available {
541 let weight_value = *weight as u16;
542 if weight_value <= self_value {
544 let diff = self_value - weight_value;
545 if diff < smallest_diff {
546 smallest_diff = diff;
547 best_match = Some(*weight);
548 }
549 }
550 }
551
552 if best_match.is_some() {
553 return best_match;
554 }
555
556 best_match = None;
558 smallest_diff = u16::MAX;
559
560 for weight in available {
561 let weight_value = *weight as u16;
562 if weight_value > self_value {
563 let diff = weight_value - self_value;
564 if diff < smallest_diff {
565 smallest_diff = diff;
566 best_match = Some(*weight);
567 }
568 }
569 }
570
571 return best_match;
572 }
573 FcWeight::SemiBold | FcWeight::Bold | FcWeight::ExtraBold | FcWeight::Black => {
574 let mut best_match = None;
576 let mut smallest_diff = u16::MAX;
577
578 for weight in available {
580 let weight_value = *weight as u16;
581 if weight_value >= self_value {
583 let diff = weight_value - self_value;
584 if diff < smallest_diff {
585 smallest_diff = diff;
586 best_match = Some(*weight);
587 }
588 }
589 }
590
591 if best_match.is_some() {
592 return best_match;
593 }
594
595 best_match = None;
597 smallest_diff = u16::MAX;
598
599 for weight in available {
600 let weight_value = *weight as u16;
601 if weight_value < self_value {
602 let diff = self_value - weight_value;
603 if diff < smallest_diff {
604 smallest_diff = diff;
605 best_match = Some(*weight);
606 }
607 }
608 }
609
610 return best_match;
611 }
612 }
613
614 Some(available[0])
616 }
617}
618
619impl Default for FcWeight {
620 fn default() -> Self {
621 FcWeight::Normal
622 }
623}
624
625#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
627#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
628#[repr(C)]
629pub enum FcStretch {
630 UltraCondensed = 1,
631 ExtraCondensed = 2,
632 Condensed = 3,
633 SemiCondensed = 4,
634 Normal = 5,
635 SemiExpanded = 6,
636 Expanded = 7,
637 ExtraExpanded = 8,
638 UltraExpanded = 9,
639}
640
641impl FcStretch {
642 pub fn is_condensed(&self) -> bool {
643 use self::FcStretch::*;
644 match self {
645 UltraCondensed => true,
646 ExtraCondensed => true,
647 Condensed => true,
648 SemiCondensed => true,
649 Normal => false,
650 SemiExpanded => false,
651 Expanded => false,
652 ExtraExpanded => false,
653 UltraExpanded => false,
654 }
655 }
656 pub fn from_u16(width_class: u16) -> Self {
657 match width_class {
658 1 => FcStretch::UltraCondensed,
659 2 => FcStretch::ExtraCondensed,
660 3 => FcStretch::Condensed,
661 4 => FcStretch::SemiCondensed,
662 5 => FcStretch::Normal,
663 6 => FcStretch::SemiExpanded,
664 7 => FcStretch::Expanded,
665 8 => FcStretch::ExtraExpanded,
666 9 => FcStretch::UltraExpanded,
667 _ => FcStretch::Normal,
668 }
669 }
670
671 pub fn find_best_match(&self, available: &[FcStretch]) -> Option<FcStretch> {
673 if available.is_empty() {
674 return None;
675 }
676
677 if available.contains(self) {
678 return Some(*self);
679 }
680
681 if *self <= FcStretch::Normal {
683 let mut closest_narrower = None;
685 for stretch in available.iter() {
686 if *stretch < *self
687 && (closest_narrower.is_none() || *stretch > closest_narrower.unwrap())
688 {
689 closest_narrower = Some(*stretch);
690 }
691 }
692
693 if closest_narrower.is_some() {
694 return closest_narrower;
695 }
696
697 let mut closest_wider = None;
699 for stretch in available.iter() {
700 if *stretch > *self
701 && (closest_wider.is_none() || *stretch < closest_wider.unwrap())
702 {
703 closest_wider = Some(*stretch);
704 }
705 }
706
707 return closest_wider;
708 } else {
709 let mut closest_wider = None;
711 for stretch in available.iter() {
712 if *stretch > *self
713 && (closest_wider.is_none() || *stretch < closest_wider.unwrap())
714 {
715 closest_wider = Some(*stretch);
716 }
717 }
718
719 if closest_wider.is_some() {
720 return closest_wider;
721 }
722
723 let mut closest_narrower = None;
725 for stretch in available.iter() {
726 if *stretch < *self
727 && (closest_narrower.is_none() || *stretch > closest_narrower.unwrap())
728 {
729 closest_narrower = Some(*stretch);
730 }
731 }
732
733 return closest_narrower;
734 }
735 }
736}
737
738impl Default for FcStretch {
739 fn default() -> Self {
740 FcStretch::Normal
741 }
742}
743
744#[repr(C)]
746#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
747#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
748pub struct UnicodeRange {
749 pub start: u32,
750 pub end: u32,
751}
752
753pub const DEFAULT_UNICODE_FALLBACK_SCRIPTS: &[UnicodeRange] = &[
762 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 }, ];
770
771impl UnicodeRange {
772 pub fn contains(&self, c: char) -> bool {
773 let c = c as u32;
774 c >= self.start && c <= self.end
775 }
776
777 pub fn overlaps(&self, other: &UnicodeRange) -> bool {
778 self.start <= other.end && other.start <= self.end
779 }
780
781 pub fn is_subset_of(&self, other: &UnicodeRange) -> bool {
782 self.start >= other.start && self.end <= other.end
783 }
784}
785
786pub fn has_cjk_ranges(ranges: &[UnicodeRange]) -> bool {
788 ranges.iter().any(|r| {
789 (r.start >= 0x4E00 && r.start <= 0x9FFF) ||
790 (r.start >= 0x3040 && r.start <= 0x309F) ||
791 (r.start >= 0x30A0 && r.start <= 0x30FF) ||
792 (r.start >= 0xAC00 && r.start <= 0xD7AF)
793 })
794}
795
796pub fn has_arabic_ranges(ranges: &[UnicodeRange]) -> bool {
798 ranges.iter().any(|r| r.start >= 0x0600 && r.start <= 0x06FF)
799}
800
801pub fn has_cyrillic_ranges(ranges: &[UnicodeRange]) -> bool {
803 ranges.iter().any(|r| r.start >= 0x0400 && r.start <= 0x04FF)
804}
805
806pub fn has_hebrew_ranges(ranges: &[UnicodeRange]) -> bool {
808 ranges.iter().any(|r| r.start >= 0x0590 && r.start <= 0x05FF)
809}
810
811pub fn has_thai_ranges(ranges: &[UnicodeRange]) -> bool {
813 ranges.iter().any(|r| r.start >= 0x0E00 && r.start <= 0x0E7F)
814}
815
816#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
818pub enum TraceLevel {
819 Debug,
820 Info,
821 Warning,
822 Error,
823}
824
825#[derive(Debug, Clone, PartialEq, Eq, Hash)]
827pub enum MatchReason {
828 NameMismatch {
829 requested: Option<String>,
830 found: Option<String>,
831 },
832 FamilyMismatch {
833 requested: Option<String>,
834 found: Option<String>,
835 },
836 StyleMismatch {
837 property: &'static str,
838 requested: String,
839 found: String,
840 },
841 WeightMismatch {
842 requested: FcWeight,
843 found: FcWeight,
844 },
845 StretchMismatch {
846 requested: FcStretch,
847 found: FcStretch,
848 },
849 UnicodeRangeMismatch {
850 character: char,
851 ranges: Vec<UnicodeRange>,
852 },
853 Success,
854}
855
856#[derive(Debug, Clone, PartialEq, Eq)]
858pub struct TraceMsg {
859 pub level: TraceLevel,
860 pub path: String,
861 pub reason: MatchReason,
862}
863
864#[repr(C)]
866#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
867#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
868pub enum FcHintStyle {
869 #[default]
870 None = 0,
871 Slight = 1,
872 Medium = 2,
873 Full = 3,
874}
875
876#[repr(C)]
878#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
879#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
880pub enum FcRgba {
881 #[default]
882 Unknown = 0,
883 Rgb = 1,
884 Bgr = 2,
885 Vrgb = 3,
886 Vbgr = 4,
887 None = 5,
888}
889
890#[repr(C)]
892#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
893#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
894pub enum FcLcdFilter {
895 #[default]
896 None = 0,
897 Default = 1,
898 Light = 2,
899 Legacy = 3,
900}
901
902#[derive(Debug, Default, Clone, PartialEq, PartialOrd)]
907#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
908pub struct FcFontRenderConfig {
909 pub antialias: Option<bool>,
910 pub hinting: Option<bool>,
911 pub hintstyle: Option<FcHintStyle>,
912 pub autohint: Option<bool>,
913 pub rgba: Option<FcRgba>,
914 pub lcdfilter: Option<FcLcdFilter>,
915 pub embeddedbitmap: Option<bool>,
916 pub embolden: Option<bool>,
917 pub dpi: Option<f64>,
918 pub scale: Option<f64>,
919 pub minspace: Option<bool>,
920}
921
922impl Eq for FcFontRenderConfig {}
925
926impl Ord for FcFontRenderConfig {
927 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
928 let ord = self.antialias.cmp(&other.antialias)
930 .then_with(|| self.hinting.cmp(&other.hinting))
931 .then_with(|| self.hintstyle.cmp(&other.hintstyle))
932 .then_with(|| self.autohint.cmp(&other.autohint))
933 .then_with(|| self.rgba.cmp(&other.rgba))
934 .then_with(|| self.lcdfilter.cmp(&other.lcdfilter))
935 .then_with(|| self.embeddedbitmap.cmp(&other.embeddedbitmap))
936 .then_with(|| self.embolden.cmp(&other.embolden))
937 .then_with(|| self.minspace.cmp(&other.minspace));
938
939 let ord = ord.then_with(|| {
941 let a = self.dpi.map(|v| v.to_bits());
942 let b = other.dpi.map(|v| v.to_bits());
943 a.cmp(&b)
944 });
945 ord.then_with(|| {
946 let a = self.scale.map(|v| v.to_bits());
947 let b = other.scale.map(|v| v.to_bits());
948 a.cmp(&b)
949 })
950 }
951}
952
953#[derive(Default, Clone, PartialOrd, Ord, PartialEq, Eq)]
955#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
956#[repr(C)]
957pub struct FcPattern {
958 pub name: Option<String>,
960 pub family: Option<String>,
962 pub italic: PatternMatch,
964 pub oblique: PatternMatch,
966 pub bold: PatternMatch,
968 pub monospace: PatternMatch,
970 pub condensed: PatternMatch,
972 pub weight: FcWeight,
974 pub stretch: FcStretch,
976 pub unicode_ranges: Vec<UnicodeRange>,
978 pub metadata: FcFontMetadata,
980 pub render_config: FcFontRenderConfig,
982}
983
984impl core::fmt::Debug for FcPattern {
985 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
986 let mut d = f.debug_struct("FcPattern");
987
988 if let Some(name) = &self.name {
989 d.field("name", name);
990 }
991
992 if let Some(family) = &self.family {
993 d.field("family", family);
994 }
995
996 if self.italic != PatternMatch::DontCare {
997 d.field("italic", &self.italic);
998 }
999
1000 if self.oblique != PatternMatch::DontCare {
1001 d.field("oblique", &self.oblique);
1002 }
1003
1004 if self.bold != PatternMatch::DontCare {
1005 d.field("bold", &self.bold);
1006 }
1007
1008 if self.monospace != PatternMatch::DontCare {
1009 d.field("monospace", &self.monospace);
1010 }
1011
1012 if self.condensed != PatternMatch::DontCare {
1013 d.field("condensed", &self.condensed);
1014 }
1015
1016 if self.weight != FcWeight::Normal {
1017 d.field("weight", &self.weight);
1018 }
1019
1020 if self.stretch != FcStretch::Normal {
1021 d.field("stretch", &self.stretch);
1022 }
1023
1024 if !self.unicode_ranges.is_empty() {
1025 d.field("unicode_ranges", &self.unicode_ranges);
1026 }
1027
1028 let empty_metadata = FcFontMetadata::default();
1030 if self.metadata != empty_metadata {
1031 d.field("metadata", &self.metadata);
1032 }
1033
1034 let empty_render_config = FcFontRenderConfig::default();
1036 if self.render_config != empty_render_config {
1037 d.field("render_config", &self.render_config);
1038 }
1039
1040 d.finish()
1041 }
1042}
1043
1044#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]
1046#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
1047pub struct FcFontMetadata {
1048 pub copyright: Option<String>,
1049 pub designer: Option<String>,
1050 pub designer_url: Option<String>,
1051 pub font_family: Option<String>,
1052 pub font_subfamily: Option<String>,
1053 pub full_name: Option<String>,
1054 pub id_description: Option<String>,
1055 pub license: Option<String>,
1056 pub license_url: Option<String>,
1057 pub manufacturer: Option<String>,
1058 pub manufacturer_url: Option<String>,
1059 pub postscript_name: Option<String>,
1060 pub preferred_family: Option<String>,
1061 pub preferred_subfamily: Option<String>,
1062 pub trademark: Option<String>,
1063 pub unique_id: Option<String>,
1064 pub version: Option<String>,
1065}
1066
1067impl FcPattern {
1068 pub fn contains_char(&self, c: char) -> bool {
1070 if self.unicode_ranges.is_empty() {
1071 return true; }
1073
1074 for range in &self.unicode_ranges {
1075 if range.contains(c) {
1076 return true;
1077 }
1078 }
1079
1080 false
1081 }
1082}
1083
1084#[derive(Debug, Clone, PartialEq, Eq)]
1086pub struct FontMatch {
1087 pub id: FontId,
1088 pub unicode_ranges: Vec<UnicodeRange>,
1089 pub fallbacks: Vec<FontMatchNoFallback>,
1090}
1091
1092#[derive(Debug, Clone, PartialEq, Eq)]
1094pub struct FontMatchNoFallback {
1095 pub id: FontId,
1096 pub unicode_ranges: Vec<UnicodeRange>,
1097}
1098
1099#[derive(Debug, Clone, PartialEq, Eq)]
1102pub struct ResolvedFontRun {
1103 pub text: String,
1105 pub start_byte: usize,
1107 pub end_byte: usize,
1109 pub font_id: Option<FontId>,
1111 pub css_source: String,
1113}
1114
1115#[derive(Debug, Clone, PartialEq, Eq)]
1118pub struct FontFallbackChain {
1119 pub css_fallbacks: Vec<CssFallbackGroup>,
1122
1123 pub unicode_fallbacks: Vec<FontMatch>,
1126
1127 pub original_stack: Vec<String>,
1129}
1130
1131impl FontFallbackChain {
1132 pub fn resolve_char(&self, cache: &FcFontCache, ch: char) -> Option<(FontId, String)> {
1136 let codepoint = ch as u32;
1137
1138 for group in &self.css_fallbacks {
1140 for font in &group.fonts {
1141 let Some(meta) = cache.get_metadata_by_id(&font.id) else { continue };
1142 if meta.unicode_ranges.is_empty() {
1143 continue; }
1145 if meta.unicode_ranges.iter().any(|r| codepoint >= r.start && codepoint <= r.end) {
1146 return Some((font.id, group.css_name.clone()));
1147 }
1148 }
1149 }
1150
1151 for font in &self.unicode_fallbacks {
1153 let Some(meta) = cache.get_metadata_by_id(&font.id) else { continue };
1154 if meta.unicode_ranges.iter().any(|r| codepoint >= r.start && codepoint <= r.end) {
1155 return Some((font.id, "(unicode-fallback)".to_string()));
1156 }
1157 }
1158
1159 None
1160 }
1161
1162 pub fn resolve_text(&self, cache: &FcFontCache, text: &str) -> Vec<(char, Option<(FontId, String)>)> {
1165 text.chars()
1166 .map(|ch| (ch, self.resolve_char(cache, ch)))
1167 .collect()
1168 }
1169
1170 pub fn query_for_text(&self, cache: &FcFontCache, text: &str) -> Vec<ResolvedFontRun> {
1174 if text.is_empty() {
1175 return Vec::new();
1176 }
1177
1178 let mut runs: Vec<ResolvedFontRun> = Vec::new();
1179 let mut current_font: Option<FontId> = None;
1180 let mut current_css_source: Option<String> = None;
1181 let mut current_start_byte: usize = 0;
1182
1183 for (byte_idx, ch) in text.char_indices() {
1184 let resolved = self.resolve_char(cache, ch);
1185 let (font_id, css_source) = match &resolved {
1186 Some((id, source)) => (Some(*id), Some(source.clone())),
1187 None => (None, None),
1188 };
1189
1190 let font_changed = font_id != current_font;
1192
1193 if font_changed && byte_idx > 0 {
1194 let run_text = &text[current_start_byte..byte_idx];
1196 runs.push(ResolvedFontRun {
1197 text: run_text.to_string(),
1198 start_byte: current_start_byte,
1199 end_byte: byte_idx,
1200 font_id: current_font,
1201 css_source: current_css_source.clone().unwrap_or_default(),
1202 });
1203 current_start_byte = byte_idx;
1204 }
1205
1206 current_font = font_id;
1207 current_css_source = css_source;
1208 }
1209
1210 if current_start_byte < text.len() {
1212 let run_text = &text[current_start_byte..];
1213 runs.push(ResolvedFontRun {
1214 text: run_text.to_string(),
1215 start_byte: current_start_byte,
1216 end_byte: text.len(),
1217 font_id: current_font,
1218 css_source: current_css_source.unwrap_or_default(),
1219 });
1220 }
1221
1222 runs
1223 }
1224}
1225
1226#[derive(Debug, Clone, PartialEq, Eq)]
1228pub struct CssFallbackGroup {
1229 pub css_name: String,
1231
1232 pub fonts: Vec<FontMatch>,
1235}
1236
1237#[cfg(feature = "std")]
1250#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1251pub(crate) struct FontChainCacheKey {
1252 pub(crate) font_families: Vec<String>,
1254 pub(crate) weight: FcWeight,
1256 pub(crate) italic: PatternMatch,
1258 pub(crate) oblique: PatternMatch,
1259 pub(crate) scripts_hint_hash: Option<u64>,
1261}
1262
1263#[cfg(feature = "std")]
1268fn hash_scripts_hint(ranges: &[UnicodeRange]) -> u64 {
1269 let mut sorted: Vec<UnicodeRange> = ranges.to_vec();
1270 sorted.sort();
1271 let mut buf = Vec::with_capacity(sorted.len() * 8);
1272 for r in &sorted {
1273 buf.extend_from_slice(&r.start.to_le_bytes());
1274 buf.extend_from_slice(&r.end.to_le_bytes());
1275 }
1276 crate::utils::content_hash_u64(&buf)
1277}
1278
1279#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq)]
1291#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
1292#[repr(C)]
1293pub struct FcFontPath {
1294 pub path: String,
1295 pub font_index: usize,
1296 #[cfg_attr(feature = "cache", serde(default))]
1298 pub bytes_hash: u64,
1299}
1300
1301#[derive(Debug, Clone, PartialEq, Eq)]
1303#[repr(C)]
1304pub struct FcFont {
1305 pub bytes: Vec<u8>,
1306 pub font_index: usize,
1307 pub id: String, }
1309
1310#[derive(Debug, Clone)]
1322pub enum OwnedFontSource {
1323 Memory(FcFont),
1325 Disk(FcFontPath),
1327}
1328
1329#[cfg(feature = "std")]
1342pub enum FontBytes {
1343 Owned(std::sync::Arc<[u8]>),
1346 #[cfg(not(target_family = "wasm"))]
1350 Mmapped(mmapio::Mmap),
1351}
1352
1353#[cfg(feature = "std")]
1354impl FontBytes {
1355 #[inline]
1357 pub fn as_slice(&self) -> &[u8] {
1358 match self {
1359 FontBytes::Owned(arc) => arc,
1360 #[cfg(not(target_family = "wasm"))]
1361 FontBytes::Mmapped(m) => &m[..],
1362 }
1363 }
1364}
1365
1366#[cfg(feature = "std")]
1367impl core::ops::Deref for FontBytes {
1368 type Target = [u8];
1369 #[inline]
1370 fn deref(&self) -> &[u8] {
1371 self.as_slice()
1372 }
1373}
1374
1375#[cfg(feature = "std")]
1376impl AsRef<[u8]> for FontBytes {
1377 #[inline]
1378 fn as_ref(&self) -> &[u8] {
1379 self.as_slice()
1380 }
1381}
1382
1383#[cfg(feature = "std")]
1384impl core::fmt::Debug for FontBytes {
1385 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1386 let kind = match self {
1387 FontBytes::Owned(_) => "Owned",
1388 #[cfg(not(target_family = "wasm"))]
1389 FontBytes::Mmapped(_) => "Mmapped",
1390 };
1391 write!(f, "FontBytes::{}({} bytes)", kind, self.as_slice().len())
1392 }
1393}
1394
1395#[cfg(feature = "std")]
1399fn open_font_bytes_mmap(path: &str) -> Option<std::sync::Arc<FontBytes>> {
1400 use std::fs::File;
1401 use std::sync::Arc;
1402
1403 #[cfg(not(target_family = "wasm"))]
1404 {
1405 if let Ok(file) = File::open(path) {
1406 if let Ok(mmap) = unsafe { mmapio::MmapOptions::new().map(&file) } {
1411 return Some(Arc::new(FontBytes::Mmapped(mmap)));
1412 }
1413 }
1414 }
1415 let bytes = std::fs::read(path).ok()?;
1416 Some(Arc::new(FontBytes::Owned(Arc::from(bytes))))
1417}
1418
1419#[derive(Debug, Clone)]
1422pub struct NamedFont {
1423 pub name: String,
1425 pub bytes: Vec<u8>,
1427}
1428
1429impl NamedFont {
1430 pub fn new(name: impl Into<String>, bytes: Vec<u8>) -> Self {
1432 Self {
1433 name: name.into(),
1434 bytes,
1435 }
1436 }
1437}
1438
1439pub struct FcFontCache {
1457 pub(crate) shared: std::sync::Arc<FcFontCacheShared>,
1458}
1459
1460pub(crate) struct FcFontCacheShared {
1463 pub(crate) state: std::sync::RwLock<FcFontCacheInner>,
1467 pub(crate) chain_cache: std::sync::Mutex<std::collections::HashMap<FontChainCacheKey, FontFallbackChain>>,
1471 pub(crate) shared_bytes: std::sync::Mutex<std::collections::HashMap<u64, std::sync::Weak<FontBytes>>>,
1480}
1481
1482#[derive(Default, Debug)]
1486pub(crate) struct FcFontCacheInner {
1487 pub(crate) patterns: BTreeMap<FcPattern, FontId>,
1489 pub(crate) disk_fonts: BTreeMap<FontId, FcFontPath>,
1491 pub(crate) memory_fonts: BTreeMap<FontId, FcFont>,
1493 pub(crate) metadata: BTreeMap<FontId, FcPattern>,
1495 pub(crate) token_index: BTreeMap<String, alloc::collections::BTreeSet<FontId>>,
1498 pub(crate) font_tokens: BTreeMap<FontId, Vec<String>>,
1501}
1502
1503impl FcFontCacheInner {
1504 pub(crate) fn index_pattern_tokens(&mut self, pattern: &FcPattern, id: FontId) {
1507 let mut all_tokens = Vec::new();
1509
1510 if let Some(name) = &pattern.name {
1511 all_tokens.extend(FcFontCache::extract_font_name_tokens(name));
1512 }
1513
1514 if let Some(family) = &pattern.family {
1515 all_tokens.extend(FcFontCache::extract_font_name_tokens(family));
1516 }
1517
1518 let tokens_lower: Vec<String> =
1520 all_tokens.iter().map(|t| t.to_lowercase()).collect();
1521
1522 for token_lower in &tokens_lower {
1524 self.token_index
1525 .entry(token_lower.clone())
1526 .or_insert_with(alloc::collections::BTreeSet::new)
1527 .insert(id);
1528 }
1529
1530 self.font_tokens.insert(id, tokens_lower);
1532 }
1533}
1534
1535impl Clone for FcFontCache {
1536 fn clone(&self) -> Self {
1543 Self {
1544 shared: std::sync::Arc::clone(&self.shared),
1545 }
1546 }
1547}
1548
1549impl core::fmt::Debug for FcFontCache {
1550 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1551 let state = self.state_read();
1552 f.debug_struct("FcFontCache")
1553 .field("patterns_len", &state.patterns.len())
1554 .field("metadata_len", &state.metadata.len())
1555 .field("disk_fonts_len", &state.disk_fonts.len())
1556 .field("memory_fonts_len", &state.memory_fonts.len())
1557 .finish()
1558 }
1559}
1560
1561impl Default for FcFontCache {
1562 fn default() -> Self {
1563 Self {
1564 shared: std::sync::Arc::new(FcFontCacheShared {
1565 state: std::sync::RwLock::new(FcFontCacheInner::default()),
1566 chain_cache: std::sync::Mutex::new(std::collections::HashMap::new()),
1567 shared_bytes: std::sync::Mutex::new(std::collections::HashMap::new()),
1568 }),
1569 }
1570 }
1571}
1572
1573impl FcFontCache {
1574 #[inline]
1578 pub(crate) fn state_read(
1579 &self,
1580 ) -> std::sync::RwLockReadGuard<'_, FcFontCacheInner> {
1581 self.shared
1582 .state
1583 .read()
1584 .unwrap_or_else(|poisoned| poisoned.into_inner())
1585 }
1586
1587 #[inline]
1590 pub(crate) fn state_write(
1591 &self,
1592 ) -> std::sync::RwLockWriteGuard<'_, FcFontCacheInner> {
1593 self.shared
1594 .state
1595 .write()
1596 .unwrap_or_else(|poisoned| poisoned.into_inner())
1597 }
1598
1599 pub fn with_memory_fonts(&self, fonts: Vec<(FcPattern, FcFont)>) -> &Self {
1604 let mut state = self.state_write();
1605 for (pattern, font) in fonts {
1606 let id = FontId::new();
1607 state.patterns.insert(pattern.clone(), id);
1608 state.metadata.insert(id, pattern.clone());
1609 state.memory_fonts.insert(id, font);
1610 state.index_pattern_tokens(&pattern, id);
1611 }
1612 self
1613 }
1614
1615 pub fn with_memory_font_with_id(
1617 &self,
1618 id: FontId,
1619 pattern: FcPattern,
1620 font: FcFont,
1621 ) -> &Self {
1622 let mut state = self.state_write();
1623 state.patterns.insert(pattern.clone(), id);
1624 state.metadata.insert(id, pattern.clone());
1625 state.memory_fonts.insert(id, font);
1626 state.index_pattern_tokens(&pattern, id);
1627 self
1628 }
1629
1630 pub fn insert_builder_font(&self, pattern: FcPattern, path: FcFontPath) {
1636 let id = FontId::new();
1637 {
1638 let mut state = self.state_write();
1639 state.index_pattern_tokens(&pattern, id);
1640 state.patterns.insert(pattern.clone(), id);
1641 state.disk_fonts.insert(id, path);
1642 state.metadata.insert(id, pattern);
1643 }
1644 if let Ok(mut cc) = self.shared.chain_cache.lock() {
1648 cc.clear();
1649 }
1650 }
1651
1652 #[cfg(feature = "std")]
1653 #[doc(hidden)]
1654 pub fn chain_cache_len(&self) -> usize {
1655 self.shared.chain_cache.lock().map(|c| c.len()).unwrap_or(0)
1656 }
1657
1658 pub fn insert_fast_pattern(&self, pattern: FcPattern, path: FcFontPath) -> FontId {
1666 let id = FontId::new();
1667 let mut state = self.state_write();
1668 state.patterns.insert(pattern.clone(), id);
1669 state.disk_fonts.insert(id, path);
1670 state.metadata.insert(id, pattern);
1671 id
1672 }
1673
1674 pub fn lookup_paths_cached(&self, path: &str) -> Option<Vec<FontId>> {
1682 let state = self.state_read();
1683 let mut out = Vec::new();
1684 for (id, font_path) in &state.disk_fonts {
1685 if font_path.path == path {
1686 out.push(*id);
1687 }
1688 }
1689 if out.is_empty() { None } else { Some(out) }
1690 }
1691
1692 pub fn get_font_by_id(&self, id: &FontId) -> Option<OwnedFontSource> {
1699 let state = self.state_read();
1700 if let Some(font) = state.memory_fonts.get(id) {
1701 return Some(OwnedFontSource::Memory(font.clone()));
1702 }
1703 if let Some(path) = state.disk_fonts.get(id) {
1704 return Some(OwnedFontSource::Disk(path.clone()));
1705 }
1706 None
1707 }
1708
1709 pub fn get_metadata_by_id(&self, id: &FontId) -> Option<FcPattern> {
1713 self.state_read().metadata.get(id).cloned()
1714 }
1715
1716 #[cfg(feature = "std")]
1744 pub fn get_font_bytes(&self, id: &FontId) -> Option<std::sync::Arc<FontBytes>> {
1745 use std::sync::Arc;
1746 match self.get_font_by_id(id)? {
1747 OwnedFontSource::Memory(font) => Some(Arc::new(FontBytes::Owned(
1748 Arc::from(font.bytes.as_slice()),
1749 ))),
1750 OwnedFontSource::Disk(path) => {
1751 let hash = path.bytes_hash;
1752 if hash != 0 {
1753 if let Ok(guard) = self.shared.shared_bytes.lock() {
1754 if let Some(weak) = guard.get(&hash) {
1755 if let Some(arc) = weak.upgrade() {
1756 return Some(arc);
1757 }
1758 }
1759 }
1760 }
1761
1762 let arc = open_font_bytes_mmap(&path.path)?;
1763 if hash != 0 {
1764 if let Ok(mut guard) = self.shared.shared_bytes.lock() {
1765 guard.insert(hash, Arc::downgrade(&arc));
1767 }
1768 }
1769 Some(arc)
1770 }
1771 }
1772 }
1773
1774 #[cfg(not(feature = "std"))]
1776 pub fn build() -> Self { Self::default() }
1777
1778 #[cfg(all(feature = "std", not(feature = "parsing")))]
1780 pub fn build() -> Self { Self::build_from_filenames() }
1781
1782 #[cfg(all(feature = "std", feature = "parsing"))]
1784 pub fn build() -> Self { Self::build_inner(None) }
1785
1786 #[cfg(all(feature = "std", not(feature = "parsing")))]
1789 fn build_from_filenames() -> Self {
1790 let cache = Self::default();
1791 {
1792 let mut state = cache.state_write();
1793 for dir in crate::config::font_directories(OperatingSystem::current()) {
1794 for path in FcCollectFontFilesRecursive(dir) {
1795 let pattern = match pattern_from_filename(&path) {
1796 Some(p) => p,
1797 None => continue,
1798 };
1799 let id = FontId::new();
1800 state.disk_fonts.insert(id, FcFontPath {
1801 path: path.to_string_lossy().to_string(),
1802 font_index: 0,
1803 bytes_hash: 0,
1806 });
1807 state.index_pattern_tokens(&pattern, id);
1808 state.metadata.insert(id, pattern.clone());
1809 state.patterns.insert(pattern, id);
1810 }
1811 }
1812 }
1813 cache
1814 }
1815
1816 #[cfg(all(feature = "std", feature = "parsing"))]
1841 pub fn build_with_families(families: &[impl AsRef<str>]) -> Self {
1842 let os = OperatingSystem::current();
1844 let mut target_families: Vec<String> = Vec::new();
1845
1846 for family in families {
1847 let family_str = family.as_ref();
1848 let expanded = os.expand_generic_family(family_str, &[]);
1849 if expanded.is_empty() || (expanded.len() == 1 && expanded[0] == family_str) {
1850 target_families.push(family_str.to_string());
1851 } else {
1852 target_families.extend(expanded);
1853 }
1854 }
1855
1856 Self::build_inner(Some(&target_families))
1857 }
1858
1859 #[cfg(all(feature = "std", feature = "parsing"))]
1865 fn build_inner(family_filter: Option<&[String]>) -> Self {
1866 let cache = FcFontCache::default();
1867
1868 let filter_normalized: Option<Vec<String>> = family_filter.map(|families| {
1870 families
1871 .iter()
1872 .map(|f| crate::utils::normalize_family_name(f))
1873 .collect()
1874 });
1875
1876 let matches_filter = |pattern: &FcPattern| -> bool {
1878 match &filter_normalized {
1879 None => true, Some(targets) => {
1881 pattern.name.as_ref().map_or(false, |name| {
1882 let name_norm = crate::utils::normalize_family_name(name);
1883 targets.iter().any(|target| name_norm.contains(target))
1884 }) || pattern.family.as_ref().map_or(false, |family| {
1885 let family_norm = crate::utils::normalize_family_name(family);
1886 targets.iter().any(|target| family_norm.contains(target))
1887 })
1888 }
1889 }
1890 };
1891
1892 let mut state = cache.state_write();
1893
1894 #[cfg(target_os = "linux")]
1895 {
1896 if let Some((font_entries, render_configs)) = FcScanDirectories() {
1897 for (mut pattern, path) in font_entries {
1898 if matches_filter(&pattern) {
1899 if let Some(family) = pattern.name.as_ref().or(pattern.family.as_ref()) {
1901 if let Some(rc) = render_configs.get(family) {
1902 pattern.render_config = rc.clone();
1903 }
1904 }
1905 let id = FontId::new();
1906 state.patterns.insert(pattern.clone(), id);
1907 state.metadata.insert(id, pattern.clone());
1908 state.disk_fonts.insert(id, path);
1909 state.index_pattern_tokens(&pattern, id);
1910 }
1911 }
1912 }
1913 }
1914
1915 #[cfg(target_os = "windows")]
1916 {
1917 let system_root = std::env::var("SystemRoot")
1918 .or_else(|_| std::env::var("WINDIR"))
1919 .unwrap_or_else(|_| "C:\\Windows".to_string());
1920
1921 let user_profile = std::env::var("USERPROFILE")
1922 .unwrap_or_else(|_| "C:\\Users\\Default".to_string());
1923
1924 let font_dirs = vec![
1925 (None, format!("{}\\Fonts\\", system_root)),
1926 (None, format!("{}\\AppData\\Local\\Microsoft\\Windows\\Fonts\\", user_profile)),
1927 ];
1928
1929 let font_entries = FcScanDirectoriesInner(&font_dirs);
1930 for (pattern, path) in font_entries {
1931 if matches_filter(&pattern) {
1932 let id = FontId::new();
1933 state.patterns.insert(pattern.clone(), id);
1934 state.metadata.insert(id, pattern.clone());
1935 state.disk_fonts.insert(id, path);
1936 state.index_pattern_tokens(&pattern, id);
1937 }
1938 }
1939 }
1940
1941 #[cfg(target_os = "macos")]
1942 {
1943 let font_dirs = vec![
1944 (None, "~/Library/Fonts".to_owned()),
1945 (None, "/System/Library/Fonts".to_owned()),
1946 (None, "/Library/Fonts".to_owned()),
1947 (None, "/System/Library/AssetsV2".to_owned()),
1948 ];
1949
1950 let font_entries = FcScanDirectoriesInner(&font_dirs);
1951 for (pattern, path) in font_entries {
1952 if matches_filter(&pattern) {
1953 let id = FontId::new();
1954 state.patterns.insert(pattern.clone(), id);
1955 state.metadata.insert(id, pattern.clone());
1956 state.disk_fonts.insert(id, path);
1957 state.index_pattern_tokens(&pattern, id);
1958 }
1959 }
1960 }
1961
1962 #[cfg(target_os = "ios")]
1967 {
1968 let font_files = crate::mobile_ios::copy_available_font_urls();
1969 let font_entries = FcParseFontFiles(&font_files);
1970 for (pattern, path) in font_entries {
1971 if matches_filter(&pattern) {
1972 let id = FontId::new();
1973 state.patterns.insert(pattern.clone(), id);
1974 state.metadata.insert(id, pattern.clone());
1975 state.disk_fonts.insert(id, path);
1976 state.index_pattern_tokens(&pattern, id);
1977 }
1978 }
1979 }
1980
1981 #[cfg(target_os = "android")]
1986 {
1987 let font_dirs = vec![
1988 (None, "/system/fonts".to_owned()),
1989 (None, "/product/fonts".to_owned()),
1990 (None, "/system_ext/fonts".to_owned()),
1991 (None, "/data/fonts".to_owned()),
1992 ];
1993
1994 let font_entries = FcScanDirectoriesInner(&font_dirs);
1995 for (pattern, path) in font_entries {
1996 if matches_filter(&pattern) {
1997 let id = FontId::new();
1998 state.patterns.insert(pattern.clone(), id);
1999 state.metadata.insert(id, pattern.clone());
2000 state.disk_fonts.insert(id, path);
2001 state.index_pattern_tokens(&pattern, id);
2002 }
2003 }
2004 }
2005
2006 drop(state);
2007 cache
2008 }
2009
2010 pub fn is_memory_font(&self, id: &FontId) -> bool {
2012 self.state_read().memory_fonts.contains_key(id)
2013 }
2014
2015 pub fn list(&self) -> Vec<(FcPattern, FontId)> {
2022 self.state_read()
2023 .patterns
2024 .iter()
2025 .map(|(pattern, id)| (pattern.clone(), *id))
2026 .collect()
2027 }
2028
2029 pub fn for_each_pattern<F: FnMut(&FcPattern, &FontId)>(&self, mut f: F) {
2033 let state = self.state_read();
2034 for (pattern, id) in &state.patterns {
2035 f(pattern, id);
2036 }
2037 }
2038
2039 pub fn is_empty(&self) -> bool {
2041 self.state_read().patterns.is_empty()
2042 }
2043
2044 pub fn len(&self) -> usize {
2046 self.state_read().patterns.len()
2047 }
2048
2049 pub fn query(&self, pattern: &FcPattern, trace: &mut Vec<TraceMsg>) -> Option<FontMatch> {
2052 let state = self.state_read();
2053 let mut matches = Vec::new();
2054
2055 for (stored_pattern, id) in &state.patterns {
2056 if Self::query_matches_internal(stored_pattern, pattern, trace) {
2057 let metadata = state.metadata.get(id).unwrap_or(stored_pattern);
2058
2059 let unicode_compatibility = if pattern.unicode_ranges.is_empty() {
2061 Self::calculate_unicode_coverage(&metadata.unicode_ranges) as i32
2063 } else {
2064 Self::calculate_unicode_compatibility(&pattern.unicode_ranges, &metadata.unicode_ranges)
2066 };
2067
2068 let style_score = Self::calculate_style_score(pattern, metadata);
2069
2070 let is_memory = state.memory_fonts.contains_key(id);
2072
2073 matches.push((*id, unicode_compatibility, style_score, metadata.clone(), is_memory));
2074 }
2075 }
2076
2077 matches.sort_by(|a, b| {
2079 b.4.cmp(&a.4)
2081 .then_with(|| b.1.cmp(&a.1)) .then_with(|| a.2.cmp(&b.2)) });
2084
2085 matches.first().map(|(id, _, _, metadata, _)| {
2086 FontMatch {
2087 id: *id,
2088 unicode_ranges: metadata.unicode_ranges.clone(),
2089 fallbacks: Vec::new(), }
2091 })
2092 }
2093
2094 fn query_internal(&self, pattern: &FcPattern, trace: &mut Vec<TraceMsg>) -> Vec<FontMatch> {
2099 let state = self.state_read();
2100 self.query_internal_locked(&state, pattern, trace)
2101 }
2102
2103 fn query_internal_locked(
2106 &self,
2107 state: &FcFontCacheInner,
2108 pattern: &FcPattern,
2109 trace: &mut Vec<TraceMsg>,
2110 ) -> Vec<FontMatch> {
2111 let mut matches = Vec::new();
2112
2113 for (stored_pattern, id) in &state.patterns {
2114 if Self::query_matches_internal(stored_pattern, pattern, trace) {
2115 let metadata = state.metadata.get(id).unwrap_or(stored_pattern);
2116
2117 let unicode_compatibility = if pattern.unicode_ranges.is_empty() {
2119 Self::calculate_unicode_coverage(&metadata.unicode_ranges) as i32
2120 } else {
2121 Self::calculate_unicode_compatibility(&pattern.unicode_ranges, &metadata.unicode_ranges)
2122 };
2123
2124 let style_score = Self::calculate_style_score(pattern, metadata);
2125 matches.push((*id, unicode_compatibility, style_score, metadata.clone()));
2126 }
2127 }
2128
2129 matches.sort_by(|a, b| {
2133 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)) });
2138
2139 matches
2140 .into_iter()
2141 .map(|(id, _, _, metadata)| {
2142 FontMatch {
2143 id,
2144 unicode_ranges: metadata.unicode_ranges.clone(),
2145 fallbacks: Vec::new(), }
2147 })
2148 .collect()
2149 }
2150
2151 pub fn compute_fallbacks(
2155 &self,
2156 font_id: &FontId,
2157 trace: &mut Vec<TraceMsg>,
2158 ) -> Vec<FontMatchNoFallback> {
2159 let state = self.state_read();
2160 let pattern = match state.metadata.get(font_id) {
2161 Some(p) => p.clone(),
2162 None => return Vec::new(),
2163 };
2164 drop(state);
2165
2166 self.compute_fallbacks_for_pattern(&pattern, Some(font_id), trace)
2167 }
2168
2169 fn compute_fallbacks_for_pattern(
2170 &self,
2171 pattern: &FcPattern,
2172 exclude_id: Option<&FontId>,
2173 _trace: &mut Vec<TraceMsg>,
2174 ) -> Vec<FontMatchNoFallback> {
2175 let state = self.state_read();
2176 let mut candidates = Vec::new();
2177
2178 for (stored_pattern, id) in &state.patterns {
2180 if exclude_id.is_some() && exclude_id.unwrap() == id {
2182 continue;
2183 }
2184
2185 if !stored_pattern.unicode_ranges.is_empty() && !pattern.unicode_ranges.is_empty() {
2187 let unicode_compatibility = Self::calculate_unicode_compatibility(
2189 &pattern.unicode_ranges,
2190 &stored_pattern.unicode_ranges
2191 );
2192
2193 if unicode_compatibility > 0 {
2195 let style_score = Self::calculate_style_score(pattern, stored_pattern);
2196 candidates.push((
2197 FontMatchNoFallback {
2198 id: *id,
2199 unicode_ranges: stored_pattern.unicode_ranges.clone(),
2200 },
2201 unicode_compatibility,
2202 style_score,
2203 stored_pattern.clone(),
2204 ));
2205 }
2206 } else if pattern.unicode_ranges.is_empty() && !stored_pattern.unicode_ranges.is_empty() {
2207 let coverage = Self::calculate_unicode_coverage(&stored_pattern.unicode_ranges) as i32;
2209 let style_score = Self::calculate_style_score(pattern, stored_pattern);
2210 candidates.push((
2211 FontMatchNoFallback {
2212 id: *id,
2213 unicode_ranges: stored_pattern.unicode_ranges.clone(),
2214 },
2215 coverage,
2216 style_score,
2217 stored_pattern.clone(),
2218 ));
2219 }
2220 }
2221
2222 drop(state);
2223
2224 candidates.sort_by(|a, b| {
2226 b.1.cmp(&a.1)
2227 .then_with(|| a.2.cmp(&b.2))
2228 });
2229
2230 let mut seen_ranges = Vec::new();
2232 let mut deduplicated = Vec::new();
2233
2234 for (id, _, _, pattern) in candidates {
2235 let mut is_new_range = false;
2236
2237 for range in &pattern.unicode_ranges {
2238 if !seen_ranges.iter().any(|r: &UnicodeRange| r.overlaps(range)) {
2239 seen_ranges.push(*range);
2240 is_new_range = true;
2241 }
2242 }
2243
2244 if is_new_range {
2245 deduplicated.push(id);
2246 }
2247 }
2248
2249 deduplicated
2250 }
2251
2252 pub fn get_memory_font(&self, id: &FontId) -> Option<FcFont> {
2254 self.state_read().memory_fonts.get(id).cloned()
2255 }
2256
2257 fn trace_path(k: &FcPattern) -> String {
2259 k.name.as_ref().cloned().unwrap_or_else(|| "<unknown>".to_string())
2260 }
2261
2262 pub fn query_matches_internal(
2263 k: &FcPattern,
2264 pattern: &FcPattern,
2265 trace: &mut Vec<TraceMsg>,
2266 ) -> bool {
2267 if let Some(ref name) = pattern.name {
2269 if !k.name.as_ref().map_or(false, |kn| kn.contains(name)) {
2270 trace.push(TraceMsg {
2271 level: TraceLevel::Info,
2272 path: Self::trace_path(k),
2273 reason: MatchReason::NameMismatch {
2274 requested: pattern.name.clone(),
2275 found: k.name.clone(),
2276 },
2277 });
2278 return false;
2279 }
2280 }
2281
2282 if let Some(ref family) = pattern.family {
2284 if !k.family.as_ref().map_or(false, |kf| kf.contains(family)) {
2285 trace.push(TraceMsg {
2286 level: TraceLevel::Info,
2287 path: Self::trace_path(k),
2288 reason: MatchReason::FamilyMismatch {
2289 requested: pattern.family.clone(),
2290 found: k.family.clone(),
2291 },
2292 });
2293 return false;
2294 }
2295 }
2296
2297 let style_properties = [
2299 (
2300 "italic",
2301 pattern.italic.needs_to_match(),
2302 pattern.italic.matches(&k.italic),
2303 ),
2304 (
2305 "oblique",
2306 pattern.oblique.needs_to_match(),
2307 pattern.oblique.matches(&k.oblique),
2308 ),
2309 (
2310 "bold",
2311 pattern.bold.needs_to_match(),
2312 pattern.bold.matches(&k.bold),
2313 ),
2314 (
2315 "monospace",
2316 pattern.monospace.needs_to_match(),
2317 pattern.monospace.matches(&k.monospace),
2318 ),
2319 (
2320 "condensed",
2321 pattern.condensed.needs_to_match(),
2322 pattern.condensed.matches(&k.condensed),
2323 ),
2324 ];
2325
2326 for (property_name, needs_to_match, matches) in style_properties {
2327 if needs_to_match && !matches {
2328 let (requested, found) = match property_name {
2329 "italic" => (format!("{:?}", pattern.italic), format!("{:?}", k.italic)),
2330 "oblique" => (format!("{:?}", pattern.oblique), format!("{:?}", k.oblique)),
2331 "bold" => (format!("{:?}", pattern.bold), format!("{:?}", k.bold)),
2332 "monospace" => (
2333 format!("{:?}", pattern.monospace),
2334 format!("{:?}", k.monospace),
2335 ),
2336 "condensed" => (
2337 format!("{:?}", pattern.condensed),
2338 format!("{:?}", k.condensed),
2339 ),
2340 _ => (String::new(), String::new()),
2341 };
2342
2343 trace.push(TraceMsg {
2344 level: TraceLevel::Info,
2345 path: Self::trace_path(k),
2346 reason: MatchReason::StyleMismatch {
2347 property: property_name,
2348 requested,
2349 found,
2350 },
2351 });
2352 return false;
2353 }
2354 }
2355
2356 if pattern.weight != FcWeight::Normal && pattern.weight != k.weight {
2358 trace.push(TraceMsg {
2359 level: TraceLevel::Info,
2360 path: Self::trace_path(k),
2361 reason: MatchReason::WeightMismatch {
2362 requested: pattern.weight,
2363 found: k.weight,
2364 },
2365 });
2366 return false;
2367 }
2368
2369 if pattern.stretch != FcStretch::Normal && pattern.stretch != k.stretch {
2371 trace.push(TraceMsg {
2372 level: TraceLevel::Info,
2373 path: Self::trace_path(k),
2374 reason: MatchReason::StretchMismatch {
2375 requested: pattern.stretch,
2376 found: k.stretch,
2377 },
2378 });
2379 return false;
2380 }
2381
2382 if !pattern.unicode_ranges.is_empty() {
2384 let mut has_overlap = false;
2385
2386 for p_range in &pattern.unicode_ranges {
2387 for k_range in &k.unicode_ranges {
2388 if p_range.overlaps(k_range) {
2389 has_overlap = true;
2390 break;
2391 }
2392 }
2393 if has_overlap {
2394 break;
2395 }
2396 }
2397
2398 if !has_overlap {
2399 trace.push(TraceMsg {
2400 level: TraceLevel::Info,
2401 path: Self::trace_path(k),
2402 reason: MatchReason::UnicodeRangeMismatch {
2403 character: '\0', ranges: k.unicode_ranges.clone(),
2405 },
2406 });
2407 return false;
2408 }
2409 }
2410
2411 true
2412 }
2413
2414 #[cfg(feature = "std")]
2440 pub fn resolve_font_chain(
2441 &self,
2442 font_families: &[String],
2443 weight: FcWeight,
2444 italic: PatternMatch,
2445 oblique: PatternMatch,
2446 trace: &mut Vec<TraceMsg>,
2447 ) -> FontFallbackChain {
2448 self.resolve_font_chain_with_os(font_families, weight, italic, oblique, trace, OperatingSystem::current())
2449 }
2450
2451 #[cfg(feature = "std")]
2453 pub fn resolve_font_chain_with_os(
2454 &self,
2455 font_families: &[String],
2456 weight: FcWeight,
2457 italic: PatternMatch,
2458 oblique: PatternMatch,
2459 trace: &mut Vec<TraceMsg>,
2460 os: OperatingSystem,
2461 ) -> FontFallbackChain {
2462 self.resolve_font_chain_impl(font_families, weight, italic, oblique, None, trace, os)
2463 }
2464
2465 #[cfg(feature = "std")]
2480 pub fn resolve_font_chain_with_scripts(
2481 &self,
2482 font_families: &[String],
2483 weight: FcWeight,
2484 italic: PatternMatch,
2485 oblique: PatternMatch,
2486 scripts_hint: Option<&[UnicodeRange]>,
2487 trace: &mut Vec<TraceMsg>,
2488 ) -> FontFallbackChain {
2489 self.resolve_font_chain_impl(
2490 font_families, weight, italic, oblique, scripts_hint,
2491 trace, OperatingSystem::current(),
2492 )
2493 }
2494
2495 #[cfg(feature = "std")]
2499 fn resolve_font_chain_impl(
2500 &self,
2501 font_families: &[String],
2502 weight: FcWeight,
2503 italic: PatternMatch,
2504 oblique: PatternMatch,
2505 scripts_hint: Option<&[UnicodeRange]>,
2506 trace: &mut Vec<TraceMsg>,
2507 os: OperatingSystem,
2508 ) -> FontFallbackChain {
2509 let scripts_hint_hash = scripts_hint.map(hash_scripts_hint);
2513 let cache_key = FontChainCacheKey {
2514 font_families: font_families.to_vec(),
2515 weight,
2516 italic,
2517 oblique,
2518 scripts_hint_hash,
2519 };
2520
2521 if let Some(cached) = self
2522 .shared
2523 .chain_cache
2524 .lock()
2525 .ok()
2526 .and_then(|c| c.get(&cache_key).cloned())
2527 {
2528 return cached;
2529 }
2530
2531 let expanded_families = expand_font_families(font_families, os, &[]);
2533
2534 let chain = self.resolve_font_chain_uncached(
2536 &expanded_families,
2537 weight,
2538 italic,
2539 oblique,
2540 scripts_hint,
2541 trace,
2542 );
2543
2544 if let Ok(mut cache) = self.shared.chain_cache.lock() {
2546 cache.insert(cache_key, chain.clone());
2547 }
2548
2549 chain
2550 }
2551
2552 #[cfg(feature = "std")]
2560 fn resolve_font_chain_uncached(
2561 &self,
2562 font_families: &[String],
2563 weight: FcWeight,
2564 italic: PatternMatch,
2565 oblique: PatternMatch,
2566 scripts_hint: Option<&[UnicodeRange]>,
2567 trace: &mut Vec<TraceMsg>,
2568 ) -> FontFallbackChain {
2569 let mut css_fallbacks = Vec::new();
2570
2571 for (_i, family) in font_families.iter().enumerate() {
2573 let (pattern, is_generic) = if config::is_generic_family(family) {
2575 let monospace = if family.eq_ignore_ascii_case("monospace") {
2576 PatternMatch::True
2577 } else {
2578 PatternMatch::False
2579 };
2580 let pattern = FcPattern {
2581 name: None,
2582 weight,
2583 italic,
2584 oblique,
2585 monospace,
2586 unicode_ranges: Vec::new(),
2587 ..Default::default()
2588 };
2589 (pattern, true)
2590 } else {
2591 let pattern = FcPattern {
2593 name: Some(family.clone()),
2594 weight,
2595 italic,
2596 oblique,
2597 unicode_ranges: Vec::new(),
2598 ..Default::default()
2599 };
2600 (pattern, false)
2601 };
2602
2603 let mut matches = if is_generic {
2606 self.query_internal(&pattern, trace)
2608 } else {
2609 self.fuzzy_query_by_name(family, weight, italic, oblique, &[], trace)
2611 };
2612
2613 if is_generic && matches.len() > 5 {
2615 matches.truncate(5);
2616 }
2617
2618 css_fallbacks.push(CssFallbackGroup {
2621 css_name: family.clone(),
2622 fonts: matches,
2623 });
2624 }
2625
2626 let important_ranges: &[UnicodeRange] =
2639 scripts_hint.unwrap_or(DEFAULT_UNICODE_FALLBACK_SCRIPTS);
2640 let unicode_fallbacks = if important_ranges.is_empty() {
2641 Vec::new()
2642 } else {
2643 let all_uncovered = vec![false; important_ranges.len()];
2644 self.find_unicode_fallbacks(
2645 important_ranges,
2646 &all_uncovered,
2647 &css_fallbacks,
2648 weight,
2649 italic,
2650 oblique,
2651 trace,
2652 )
2653 };
2654
2655 FontFallbackChain {
2656 css_fallbacks,
2657 unicode_fallbacks,
2658 original_stack: font_families.to_vec(),
2659 }
2660 }
2661
2662 #[allow(dead_code)]
2664 fn extract_unicode_ranges(text: &str) -> Vec<UnicodeRange> {
2665 let mut chars: Vec<char> = text.chars().collect();
2666 chars.sort_unstable();
2667 chars.dedup();
2668
2669 if chars.is_empty() {
2670 return Vec::new();
2671 }
2672
2673 let mut ranges = Vec::new();
2674 let mut range_start = chars[0] as u32;
2675 let mut range_end = range_start;
2676
2677 for &c in &chars[1..] {
2678 let codepoint = c as u32;
2679 if codepoint == range_end + 1 {
2680 range_end = codepoint;
2681 } else {
2682 ranges.push(UnicodeRange { start: range_start, end: range_end });
2683 range_start = codepoint;
2684 range_end = codepoint;
2685 }
2686 }
2687
2688 ranges.push(UnicodeRange { start: range_start, end: range_end });
2689 ranges
2690 }
2691
2692 #[cfg(feature = "std")]
2699 fn fuzzy_query_by_name(
2700 &self,
2701 requested_name: &str,
2702 weight: FcWeight,
2703 italic: PatternMatch,
2704 oblique: PatternMatch,
2705 unicode_ranges: &[UnicodeRange],
2706 _trace: &mut Vec<TraceMsg>,
2707 ) -> Vec<FontMatch> {
2708 let tokens = Self::extract_font_name_tokens(requested_name);
2710
2711 if tokens.is_empty() {
2712 return Vec::new();
2713 }
2714
2715 let tokens_lower: Vec<String> = tokens.iter().map(|t| t.to_lowercase()).collect();
2717
2718 let state = self.state_read();
2724
2725 let first_token = &tokens_lower[0];
2727 let mut candidate_ids = match state.token_index.get(first_token) {
2728 Some(ids) if !ids.is_empty() => ids.clone(),
2729 _ => {
2730 return Vec::new();
2732 }
2733 };
2734
2735 for token in &tokens_lower[1..] {
2737 if let Some(token_ids) = state.token_index.get(token) {
2738 let intersection: alloc::collections::BTreeSet<FontId> =
2740 candidate_ids.intersection(token_ids).copied().collect();
2741
2742 if intersection.is_empty() {
2743 break;
2745 } else {
2746 candidate_ids = intersection;
2748 }
2749 } else {
2750 break;
2752 }
2753 }
2754
2755 let mut candidates = Vec::new();
2757
2758 for id in candidate_ids {
2759 let pattern = match state.metadata.get(&id) {
2760 Some(p) => p,
2761 None => continue,
2762 };
2763
2764 let font_tokens_lower = match state.font_tokens.get(&id) {
2766 Some(tokens) => tokens,
2767 None => continue,
2768 };
2769
2770 if font_tokens_lower.is_empty() {
2771 continue;
2772 }
2773
2774 let token_matches = tokens_lower.iter()
2777 .filter(|req_token| {
2778 font_tokens_lower.iter().any(|font_token| {
2779 font_token == *req_token
2781 })
2782 })
2783 .count();
2784
2785 if token_matches == 0 {
2787 continue;
2788 }
2789
2790 let token_similarity = (token_matches * 100 / tokens.len()) as i32;
2792
2793 let unicode_similarity = if !unicode_ranges.is_empty() && !pattern.unicode_ranges.is_empty() {
2795 Self::calculate_unicode_compatibility(unicode_ranges, &pattern.unicode_ranges)
2796 } else {
2797 0
2798 };
2799
2800 if !unicode_ranges.is_empty() && unicode_similarity == 0 {
2803 continue;
2804 }
2805
2806 let style_score = Self::calculate_style_score(&FcPattern {
2807 weight,
2808 italic,
2809 oblique,
2810 ..Default::default()
2811 }, pattern);
2812
2813 candidates.push((
2814 id,
2815 token_similarity,
2816 unicode_similarity,
2817 style_score,
2818 pattern.clone(),
2819 ));
2820 }
2821
2822 candidates.sort_by(|a, b| {
2828 if !unicode_ranges.is_empty() {
2829 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 {
2836 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)) }
2842 });
2843
2844 candidates.truncate(5);
2846
2847 candidates
2849 .into_iter()
2850 .map(|(id, _token_sim, _unicode_sim, _style, pattern)| {
2851 FontMatch {
2852 id,
2853 unicode_ranges: pattern.unicode_ranges.clone(),
2854 fallbacks: Vec::new(), }
2856 })
2857 .collect()
2858 }
2859
2860 pub fn extract_font_name_tokens(name: &str) -> Vec<String> {
2864 let mut tokens = Vec::new();
2865 let mut current_token = String::new();
2866 let mut last_was_lower = false;
2867
2868 for c in name.chars() {
2869 if c.is_whitespace() || c == '-' || c == '_' {
2870 if !current_token.is_empty() {
2872 tokens.push(current_token.clone());
2873 current_token.clear();
2874 }
2875 last_was_lower = false;
2876 } else if c.is_uppercase() && last_was_lower && !current_token.is_empty() {
2877 tokens.push(current_token.clone());
2879 current_token.clear();
2880 current_token.push(c);
2881 last_was_lower = false;
2882 } else {
2883 current_token.push(c);
2884 last_was_lower = c.is_lowercase();
2885 }
2886 }
2887
2888 if !current_token.is_empty() {
2889 tokens.push(current_token);
2890 }
2891
2892 tokens
2893 }
2894
2895 fn find_unicode_fallbacks(
2899 &self,
2900 unicode_ranges: &[UnicodeRange],
2901 covered_chars: &[bool],
2902 existing_groups: &[CssFallbackGroup],
2903 _weight: FcWeight,
2904 _italic: PatternMatch,
2905 _oblique: PatternMatch,
2906 trace: &mut Vec<TraceMsg>,
2907 ) -> Vec<FontMatch> {
2908 let mut uncovered_ranges = Vec::new();
2910 for (i, &covered) in covered_chars.iter().enumerate() {
2911 if !covered && i < unicode_ranges.len() {
2912 uncovered_ranges.push(unicode_ranges[i].clone());
2913 }
2914 }
2915
2916 if uncovered_ranges.is_empty() {
2917 return Vec::new();
2918 }
2919
2920 let pattern = FcPattern {
2925 name: None,
2926 weight: FcWeight::Normal, italic: PatternMatch::DontCare,
2928 oblique: PatternMatch::DontCare,
2929 unicode_ranges: uncovered_ranges.clone(),
2930 ..Default::default()
2931 };
2932
2933 let mut candidates = self.query_internal(&pattern, trace);
2934
2935 let existing_prefixes: Vec<String> = existing_groups
2938 .iter()
2939 .flat_map(|group| {
2940 group.fonts.iter().filter_map(|font| {
2941 self.get_metadata_by_id(&font.id)
2942 .and_then(|meta| meta.family.clone())
2943 .and_then(|family| {
2944 family.split_whitespace()
2946 .take(2)
2947 .collect::<Vec<_>>()
2948 .join(" ")
2949 .into()
2950 })
2951 })
2952 })
2953 .collect();
2954
2955 candidates.sort_by(|a, b| {
2959 let a_meta = self.get_metadata_by_id(&a.id);
2960 let b_meta = self.get_metadata_by_id(&b.id);
2961
2962 let a_score = Self::calculate_font_similarity_score(a_meta.as_ref(), &existing_prefixes);
2963 let b_score = Self::calculate_font_similarity_score(b_meta.as_ref(), &existing_prefixes);
2964
2965 b_score.cmp(&a_score) .then_with(|| {
2967 let a_coverage = Self::calculate_unicode_compatibility(&uncovered_ranges, &a.unicode_ranges);
2968 let b_coverage = Self::calculate_unicode_compatibility(&uncovered_ranges, &b.unicode_ranges);
2969 b_coverage.cmp(&a_coverage)
2970 })
2971 });
2972
2973 let mut result = Vec::new();
2975 let mut remaining_uncovered: Vec<bool> = vec![true; uncovered_ranges.len()];
2976
2977 for candidate in candidates {
2978 let mut covers_new_range = false;
2980
2981 for (i, range) in uncovered_ranges.iter().enumerate() {
2982 if remaining_uncovered[i] {
2983 for font_range in &candidate.unicode_ranges {
2985 if font_range.overlaps(range) {
2986 remaining_uncovered[i] = false;
2987 covers_new_range = true;
2988 break;
2989 }
2990 }
2991 }
2992 }
2993
2994 if covers_new_range {
2996 result.push(candidate);
2997
2998 if remaining_uncovered.iter().all(|&uncovered| !uncovered) {
3000 break;
3001 }
3002 }
3003 }
3004
3005 result
3006 }
3007
3008 fn calculate_font_similarity_score(
3011 font_meta: Option<&FcPattern>,
3012 existing_prefixes: &[String],
3013 ) -> i32 {
3014 let Some(meta) = font_meta else { return 0; };
3015 let Some(family) = &meta.family else { return 0; };
3016
3017 for prefix in existing_prefixes {
3019 if family.starts_with(prefix) {
3020 return 100; }
3022 if family.contains(prefix) {
3023 return 50; }
3025 }
3026
3027 0 }
3029
3030 pub fn calculate_unicode_coverage(ranges: &[UnicodeRange]) -> u64 {
3033 ranges
3034 .iter()
3035 .map(|range| (range.end - range.start + 1) as u64)
3036 .sum()
3037 }
3038
3039 pub fn calculate_unicode_compatibility(
3042 requested: &[UnicodeRange],
3043 available: &[UnicodeRange],
3044 ) -> i32 {
3045 if requested.is_empty() {
3046 return Self::calculate_unicode_coverage(available) as i32;
3048 }
3049
3050 let mut total_coverage = 0u32;
3051
3052 for req_range in requested {
3053 for avail_range in available {
3054 let overlap_start = req_range.start.max(avail_range.start);
3056 let overlap_end = req_range.end.min(avail_range.end);
3057
3058 if overlap_start <= overlap_end {
3059 let overlap_size = overlap_end - overlap_start + 1;
3061 total_coverage += overlap_size;
3062 }
3063 }
3064 }
3065
3066 total_coverage as i32
3067 }
3068
3069 pub fn calculate_style_score(original: &FcPattern, candidate: &FcPattern) -> i32 {
3070
3071 let mut score = 0_i32;
3072
3073 if (original.bold == PatternMatch::True && candidate.weight == FcWeight::Bold)
3075 || (original.bold == PatternMatch::False && candidate.weight != FcWeight::Bold)
3076 {
3077 } else {
3080 let weight_diff = (original.weight as i32 - candidate.weight as i32).abs();
3082 score += weight_diff as i32;
3083 }
3084
3085 if original.weight == candidate.weight {
3088 score -= 15;
3089 if original.weight == FcWeight::Normal {
3090 score -= 10; }
3092 }
3093
3094 if (original.condensed == PatternMatch::True && candidate.stretch.is_condensed())
3096 || (original.condensed == PatternMatch::False && !candidate.stretch.is_condensed())
3097 {
3098 } else {
3101 let stretch_diff = (original.stretch as i32 - candidate.stretch as i32).abs();
3103 score += (stretch_diff * 100) as i32;
3104 }
3105
3106 let style_props = [
3108 (original.italic, candidate.italic, 300, 150),
3109 (original.oblique, candidate.oblique, 200, 100),
3110 (original.bold, candidate.bold, 300, 150),
3111 (original.monospace, candidate.monospace, 100, 50),
3112 (original.condensed, candidate.condensed, 100, 50),
3113 ];
3114
3115 for (orig, cand, mismatch_penalty, dontcare_penalty) in style_props {
3116 if orig.needs_to_match() {
3117 if orig == PatternMatch::False && cand == PatternMatch::DontCare {
3118 score += dontcare_penalty / 2;
3121 } else if !orig.matches(&cand) {
3122 if cand == PatternMatch::DontCare {
3123 score += dontcare_penalty;
3124 } else {
3125 score += mismatch_penalty;
3126 }
3127 } else if orig == PatternMatch::True && cand == PatternMatch::True {
3128 score -= 20;
3130 } else if orig == PatternMatch::False && cand == PatternMatch::False {
3131 score -= 20;
3134 }
3135 } else {
3136 if cand == PatternMatch::True {
3141 score += dontcare_penalty / 3;
3142 }
3143 }
3144 }
3145
3146 if let (Some(name), Some(family)) = (&candidate.name, &candidate.family) {
3152 let name_lower = name.to_lowercase();
3153 let family_lower = family.to_lowercase();
3154
3155 let extra = if name_lower.starts_with(&family_lower) {
3157 name_lower[family_lower.len()..].to_string()
3158 } else {
3159 String::new()
3160 };
3161
3162 let stripped = extra
3164 .replace("regular", "")
3165 .replace("normal", "")
3166 .replace("book", "")
3167 .replace("roman", "");
3168 let stripped = stripped.trim();
3169
3170 if stripped.is_empty() {
3171 score -= 50;
3173 } else {
3174 let extra_words = stripped.split_whitespace().count();
3176 score += (extra_words as i32) * 25;
3177 }
3178 }
3179
3180 if let Some(ref subfamily) = candidate.metadata.font_subfamily {
3184 let sf_lower = subfamily.to_lowercase();
3185 if sf_lower == "regular" {
3186 score -= 30;
3187 }
3188 }
3189
3190 score
3191 }
3192}
3193
3194#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3195fn FcScanDirectories() -> Option<(Vec<(FcPattern, FcFontPath)>, BTreeMap<String, FcFontRenderConfig>)> {
3196 use std::fs;
3197 use std::path::Path;
3198
3199 const BASE_FONTCONFIG_PATH: &str = "/etc/fonts/fonts.conf";
3200
3201 if !Path::new(BASE_FONTCONFIG_PATH).exists() {
3202 return None;
3203 }
3204
3205 let mut font_paths = Vec::with_capacity(32);
3206 let mut paths_to_visit = vec![(None, PathBuf::from(BASE_FONTCONFIG_PATH))];
3207 let mut render_configs: BTreeMap<String, FcFontRenderConfig> = BTreeMap::new();
3208
3209 while let Some((prefix, path_to_visit)) = paths_to_visit.pop() {
3210 let path = match process_path(&prefix, path_to_visit, true) {
3211 Some(path) => path,
3212 None => continue,
3213 };
3214
3215 let metadata = match fs::metadata(&path) {
3216 Ok(metadata) => metadata,
3217 Err(_) => continue,
3218 };
3219
3220 if metadata.is_file() {
3221 let xml_utf8 = match fs::read_to_string(&path) {
3222 Ok(xml_utf8) => xml_utf8,
3223 Err(_) => continue,
3224 };
3225
3226 if ParseFontsConf(&xml_utf8, &mut paths_to_visit, &mut font_paths).is_none() {
3227 continue;
3228 }
3229
3230 ParseFontsConfRenderConfig(&xml_utf8, &mut render_configs);
3232 } else if metadata.is_dir() {
3233 let dir_entries = match fs::read_dir(&path) {
3234 Ok(dir_entries) => dir_entries,
3235 Err(_) => continue,
3236 };
3237
3238 for entry_result in dir_entries {
3239 let entry = match entry_result {
3240 Ok(entry) => entry,
3241 Err(_) => continue,
3242 };
3243
3244 let entry_path = entry.path();
3245
3246 let entry_metadata = match fs::metadata(&entry_path) {
3248 Ok(metadata) => metadata,
3249 Err(_) => continue,
3250 };
3251
3252 if !entry_metadata.is_file() {
3253 continue;
3254 }
3255
3256 let file_name = match entry_path.file_name() {
3257 Some(name) => name,
3258 None => continue,
3259 };
3260
3261 let file_name_str = file_name.to_string_lossy();
3262 if file_name_str.starts_with(|c: char| c.is_ascii_digit())
3263 && file_name_str.ends_with(".conf")
3264 {
3265 paths_to_visit.push((None, entry_path));
3266 }
3267 }
3268 }
3269 }
3270
3271 if font_paths.is_empty() {
3272 return None;
3273 }
3274
3275 Some((FcScanDirectoriesInner(&font_paths), render_configs))
3276}
3277
3278#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3280fn ParseFontsConf(
3281 input: &str,
3282 paths_to_visit: &mut Vec<(Option<String>, PathBuf)>,
3283 font_paths: &mut Vec<(Option<String>, String)>,
3284) -> Option<()> {
3285 use xmlparser::Token::*;
3286 use xmlparser::Tokenizer;
3287
3288 const TAG_INCLUDE: &str = "include";
3289 const TAG_DIR: &str = "dir";
3290 const ATTRIBUTE_PREFIX: &str = "prefix";
3291
3292 let mut current_prefix: Option<&str> = None;
3293 let mut current_path: Option<&str> = None;
3294 let mut is_in_include = false;
3295 let mut is_in_dir = false;
3296
3297 for token_result in Tokenizer::from(input) {
3298 let token = match token_result {
3299 Ok(token) => token,
3300 Err(_) => return None,
3301 };
3302
3303 match token {
3304 ElementStart { local, .. } => {
3305 if is_in_include || is_in_dir {
3306 return None; }
3308
3309 match local.as_str() {
3310 TAG_INCLUDE => {
3311 is_in_include = true;
3312 }
3313 TAG_DIR => {
3314 is_in_dir = true;
3315 }
3316 _ => continue,
3317 }
3318
3319 current_path = None;
3320 }
3321 Text { text, .. } => {
3322 let text = text.as_str().trim();
3323 if text.is_empty() {
3324 continue;
3325 }
3326 if is_in_include || is_in_dir {
3327 current_path = Some(text);
3328 }
3329 }
3330 Attribute { local, value, .. } => {
3331 if !is_in_include && !is_in_dir {
3332 continue;
3333 }
3334 if local.as_str() == ATTRIBUTE_PREFIX {
3336 current_prefix = Some(value.as_str());
3337 }
3338 }
3339 ElementEnd { end, .. } => {
3340 let end_tag = match end {
3341 xmlparser::ElementEnd::Close(_, a) => a,
3342 _ => continue,
3343 };
3344
3345 match end_tag.as_str() {
3346 TAG_INCLUDE => {
3347 if !is_in_include {
3348 continue;
3349 }
3350
3351 if let Some(current_path) = current_path.as_ref() {
3352 paths_to_visit.push((
3353 current_prefix.map(ToOwned::to_owned),
3354 PathBuf::from(*current_path),
3355 ));
3356 }
3357 }
3358 TAG_DIR => {
3359 if !is_in_dir {
3360 continue;
3361 }
3362
3363 if let Some(current_path) = current_path.as_ref() {
3364 font_paths.push((
3365 current_prefix.map(ToOwned::to_owned),
3366 (*current_path).to_owned(),
3367 ));
3368 }
3369 }
3370 _ => continue,
3371 }
3372
3373 is_in_include = false;
3374 is_in_dir = false;
3375 current_path = None;
3376 current_prefix = None;
3377 }
3378 _ => {}
3379 }
3380 }
3381
3382 Some(())
3383}
3384
3385#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3397fn ParseFontsConfRenderConfig(
3398 input: &str,
3399 configs: &mut BTreeMap<String, FcFontRenderConfig>,
3400) {
3401 use xmlparser::Token::*;
3402 use xmlparser::Tokenizer;
3403
3404 #[derive(Clone, Copy, PartialEq)]
3406 enum State {
3407 Idle,
3409 InMatchFont,
3411 InTestFamily,
3413 InEdit,
3415 InValue,
3417 }
3418
3419 let mut state = State::Idle;
3420 let mut match_is_font_target = false;
3421 let mut current_family: Option<String> = None;
3422 let mut current_edit_name: Option<String> = None;
3423 let mut current_value: Option<String> = None;
3424 let mut value_tag: Option<String> = None;
3425 let mut config = FcFontRenderConfig::default();
3426 let mut in_test = false;
3427 let mut test_name: Option<String> = None;
3428
3429 for token_result in Tokenizer::from(input) {
3430 let token = match token_result {
3431 Ok(token) => token,
3432 Err(_) => continue,
3433 };
3434
3435 match token {
3436 ElementStart { local, .. } => {
3437 let tag = local.as_str();
3438 match tag {
3439 "match" => {
3440 match_is_font_target = false;
3442 current_family = None;
3443 config = FcFontRenderConfig::default();
3444 }
3445 "test" if state == State::InMatchFont => {
3446 in_test = true;
3447 test_name = None;
3448 }
3449 "edit" if state == State::InMatchFont => {
3450 current_edit_name = None;
3451 }
3452 "bool" | "double" | "const" | "string" | "int" => {
3453 if state == State::InTestFamily || state == State::InEdit {
3454 value_tag = Some(tag.to_owned());
3455 current_value = None;
3456 }
3457 }
3458 _ => {}
3459 }
3460 }
3461 Attribute { local, value, .. } => {
3462 let attr_name = local.as_str();
3463 let attr_value = value.as_str();
3464
3465 match attr_name {
3466 "target" => {
3467 if attr_value == "font" {
3468 match_is_font_target = true;
3469 }
3470 }
3471 "name" => {
3472 if in_test && state == State::InMatchFont {
3473 test_name = Some(attr_value.to_owned());
3474 } else if state == State::InMatchFont {
3475 current_edit_name = Some(attr_value.to_owned());
3476 }
3477 }
3478 _ => {}
3479 }
3480 }
3481 Text { text, .. } => {
3482 let text = text.as_str().trim();
3483 if !text.is_empty() && (state == State::InTestFamily || state == State::InEdit) {
3484 current_value = Some(text.to_owned());
3485 }
3486 }
3487 ElementEnd { end, .. } => {
3488 match end {
3489 xmlparser::ElementEnd::Open => {
3490 if match_is_font_target && state == State::Idle {
3492 state = State::InMatchFont;
3493 match_is_font_target = false;
3494 } else if in_test {
3495 if test_name.as_deref() == Some("family") {
3496 state = State::InTestFamily;
3497 }
3498 in_test = false;
3499 } else if current_edit_name.is_some() && state == State::InMatchFont {
3500 state = State::InEdit;
3501 }
3502 }
3503 xmlparser::ElementEnd::Close(_, local) => {
3504 let tag = local.as_str();
3505 match tag {
3506 "match" => {
3507 if let Some(family) = current_family.take() {
3509 let empty = FcFontRenderConfig::default();
3510 if config != empty {
3511 configs.insert(family, config.clone());
3512 }
3513 }
3514 state = State::Idle;
3515 config = FcFontRenderConfig::default();
3516 }
3517 "test" => {
3518 if state == State::InTestFamily {
3519 if let Some(ref val) = current_value {
3521 current_family = Some(val.clone());
3522 }
3523 state = State::InMatchFont;
3524 }
3525 current_value = None;
3526 value_tag = None;
3527 }
3528 "edit" => {
3529 if state == State::InEdit {
3530 if let (Some(ref name), Some(ref val)) = (¤t_edit_name, ¤t_value) {
3532 apply_edit_value(&mut config, name, val, value_tag.as_deref());
3533 }
3534 state = State::InMatchFont;
3535 }
3536 current_edit_name = None;
3537 current_value = None;
3538 value_tag = None;
3539 }
3540 "bool" | "double" | "const" | "string" | "int" => {
3541 }
3543 _ => {}
3544 }
3545 }
3546 xmlparser::ElementEnd::Empty => {
3547 }
3549 }
3550 }
3551 _ => {}
3552 }
3553 }
3554}
3555
3556#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3558fn apply_edit_value(
3559 config: &mut FcFontRenderConfig,
3560 edit_name: &str,
3561 value: &str,
3562 value_tag: Option<&str>,
3563) {
3564 match edit_name {
3565 "antialias" => {
3566 config.antialias = parse_bool_value(value);
3567 }
3568 "hinting" => {
3569 config.hinting = parse_bool_value(value);
3570 }
3571 "autohint" => {
3572 config.autohint = parse_bool_value(value);
3573 }
3574 "embeddedbitmap" => {
3575 config.embeddedbitmap = parse_bool_value(value);
3576 }
3577 "embolden" => {
3578 config.embolden = parse_bool_value(value);
3579 }
3580 "minspace" => {
3581 config.minspace = parse_bool_value(value);
3582 }
3583 "hintstyle" => {
3584 config.hintstyle = parse_hintstyle_const(value);
3585 }
3586 "rgba" => {
3587 config.rgba = parse_rgba_const(value);
3588 }
3589 "lcdfilter" => {
3590 config.lcdfilter = parse_lcdfilter_const(value);
3591 }
3592 "dpi" => {
3593 if let Ok(v) = value.parse::<f64>() {
3594 config.dpi = Some(v);
3595 }
3596 }
3597 "scale" => {
3598 if let Ok(v) = value.parse::<f64>() {
3599 config.scale = Some(v);
3600 }
3601 }
3602 _ => {
3603 }
3605 }
3606}
3607
3608#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3609fn parse_bool_value(value: &str) -> Option<bool> {
3610 match value {
3611 "true" => Some(true),
3612 "false" => Some(false),
3613 _ => None,
3614 }
3615}
3616
3617#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3618fn parse_hintstyle_const(value: &str) -> Option<FcHintStyle> {
3619 match value {
3620 "hintnone" => Some(FcHintStyle::None),
3621 "hintslight" => Some(FcHintStyle::Slight),
3622 "hintmedium" => Some(FcHintStyle::Medium),
3623 "hintfull" => Some(FcHintStyle::Full),
3624 _ => None,
3625 }
3626}
3627
3628#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3629fn parse_rgba_const(value: &str) -> Option<FcRgba> {
3630 match value {
3631 "unknown" => Some(FcRgba::Unknown),
3632 "rgb" => Some(FcRgba::Rgb),
3633 "bgr" => Some(FcRgba::Bgr),
3634 "vrgb" => Some(FcRgba::Vrgb),
3635 "vbgr" => Some(FcRgba::Vbgr),
3636 "none" => Some(FcRgba::None),
3637 _ => None,
3638 }
3639}
3640
3641#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3642fn parse_lcdfilter_const(value: &str) -> Option<FcLcdFilter> {
3643 match value {
3644 "lcdnone" => Some(FcLcdFilter::None),
3645 "lcddefault" => Some(FcLcdFilter::Default),
3646 "lcdlight" => Some(FcLcdFilter::Light),
3647 "lcdlegacy" => Some(FcLcdFilter::Legacy),
3648 _ => None,
3649 }
3650}
3651
3652#[cfg(all(feature = "std", feature = "parsing"))]
3655const UNICODE_RANGE_MAPPINGS: &[(usize, u32, u32)] = &[
3656 (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), ];
3789
3790#[cfg(all(feature = "std", feature = "parsing"))]
3793struct ParsedFontFace {
3794 pattern: FcPattern,
3795 font_index: usize,
3796}
3797
3798#[cfg(all(feature = "std", feature = "parsing"))]
3804fn parse_font_faces(font_bytes: &[u8]) -> Option<Vec<ParsedFontFace>> {
3805 use allsorts::{
3806 binary::read::ReadScope,
3807 font_data::FontData,
3808 get_name::fontcode_get_name,
3809 post::PostTable,
3810 tables::{
3811 os2::Os2, HeadTable, NameTable,
3812 },
3813 tag,
3814 };
3815 use std::collections::BTreeSet;
3816
3817 const FONT_SPECIFIER_NAME_ID: u16 = 4;
3818 const FONT_SPECIFIER_FAMILY_ID: u16 = 1;
3819
3820 let max_fonts = if font_bytes.len() >= 12 && &font_bytes[0..4] == b"ttcf" {
3821 let num_fonts =
3823 u32::from_be_bytes([font_bytes[8], font_bytes[9], font_bytes[10], font_bytes[11]]);
3824 std::cmp::min(num_fonts as usize, 100)
3826 } else {
3827 1
3829 };
3830
3831 let scope = ReadScope::new(font_bytes);
3832 let font_file = scope.read::<FontData<'_>>().ok()?;
3833
3834 let mut results = Vec::new();
3836
3837 for font_index in 0..max_fonts {
3838 let provider = font_file.table_provider(font_index).ok()?;
3839 let head_data = provider.table_data(tag::HEAD).ok()??.into_owned();
3840 let head_table = ReadScope::new(&head_data).read::<HeadTable>().ok()?;
3841
3842 let is_bold = head_table.is_bold();
3843 let is_italic = head_table.is_italic();
3844 let mut detected_monospace = None;
3845
3846 let post_data = provider.table_data(tag::POST).ok()??;
3847 if let Ok(post_table) = ReadScope::new(&post_data).read::<PostTable>() {
3848 detected_monospace = Some(post_table.header.is_fixed_pitch != 0);
3850 }
3851
3852 let os2_data = provider.table_data(tag::OS_2).ok()??;
3854 let os2_table = ReadScope::new(&os2_data)
3855 .read_dep::<Os2>(os2_data.len())
3856 .ok()?;
3857
3858 let is_oblique = os2_table
3860 .fs_selection
3861 .contains(allsorts::tables::os2::FsSelection::OBLIQUE);
3862 let weight = FcWeight::from_u16(os2_table.us_weight_class);
3863 let stretch = FcStretch::from_u16(os2_table.us_width_class);
3864
3865 let mut unicode_ranges = Vec::new();
3869
3870 let os2_ranges = [
3872 os2_table.ul_unicode_range1,
3873 os2_table.ul_unicode_range2,
3874 os2_table.ul_unicode_range3,
3875 os2_table.ul_unicode_range4,
3876 ];
3877
3878 for &(bit, start, end) in UNICODE_RANGE_MAPPINGS {
3879 let range_idx = bit / 32;
3880 let bit_pos = bit % 32;
3881 if range_idx < 4 && (os2_ranges[range_idx] & (1 << bit_pos)) != 0 {
3882 unicode_ranges.push(UnicodeRange { start, end });
3883 }
3884 }
3885
3886 unicode_ranges = verify_unicode_ranges_with_cmap(&provider, unicode_ranges);
3890
3891 if unicode_ranges.is_empty() {
3893 if let Some(cmap_ranges) = analyze_cmap_coverage(&provider) {
3894 unicode_ranges = cmap_ranges;
3895 }
3896 }
3897
3898 let is_monospace = detect_monospace(&provider, &os2_table, detected_monospace)
3900 .unwrap_or(false);
3901
3902 let name_data = provider.table_data(tag::NAME).ok()??.into_owned();
3903 let name_table = ReadScope::new(&name_data).read::<NameTable>().ok()?;
3904
3905 let mut metadata = FcFontMetadata::default();
3907
3908 const NAME_ID_COPYRIGHT: u16 = 0;
3909 const NAME_ID_FAMILY: u16 = 1;
3910 const NAME_ID_SUBFAMILY: u16 = 2;
3911 const NAME_ID_UNIQUE_ID: u16 = 3;
3912 const NAME_ID_FULL_NAME: u16 = 4;
3913 const NAME_ID_VERSION: u16 = 5;
3914 const NAME_ID_POSTSCRIPT_NAME: u16 = 6;
3915 const NAME_ID_TRADEMARK: u16 = 7;
3916 const NAME_ID_MANUFACTURER: u16 = 8;
3917 const NAME_ID_DESIGNER: u16 = 9;
3918 const NAME_ID_DESCRIPTION: u16 = 10;
3919 const NAME_ID_VENDOR_URL: u16 = 11;
3920 const NAME_ID_DESIGNER_URL: u16 = 12;
3921 const NAME_ID_LICENSE: u16 = 13;
3922 const NAME_ID_LICENSE_URL: u16 = 14;
3923 const NAME_ID_PREFERRED_FAMILY: u16 = 16;
3924 const NAME_ID_PREFERRED_SUBFAMILY: u16 = 17;
3925
3926 metadata.copyright = get_name_string(&name_data, NAME_ID_COPYRIGHT);
3927 metadata.font_family = get_name_string(&name_data, NAME_ID_FAMILY);
3928 metadata.font_subfamily = get_name_string(&name_data, NAME_ID_SUBFAMILY);
3929 metadata.full_name = get_name_string(&name_data, NAME_ID_FULL_NAME);
3930 metadata.unique_id = get_name_string(&name_data, NAME_ID_UNIQUE_ID);
3931 metadata.version = get_name_string(&name_data, NAME_ID_VERSION);
3932 metadata.postscript_name = get_name_string(&name_data, NAME_ID_POSTSCRIPT_NAME);
3933 metadata.trademark = get_name_string(&name_data, NAME_ID_TRADEMARK);
3934 metadata.manufacturer = get_name_string(&name_data, NAME_ID_MANUFACTURER);
3935 metadata.designer = get_name_string(&name_data, NAME_ID_DESIGNER);
3936 metadata.id_description = get_name_string(&name_data, NAME_ID_DESCRIPTION);
3937 metadata.designer_url = get_name_string(&name_data, NAME_ID_DESIGNER_URL);
3938 metadata.manufacturer_url = get_name_string(&name_data, NAME_ID_VENDOR_URL);
3939 metadata.license = get_name_string(&name_data, NAME_ID_LICENSE);
3940 metadata.license_url = get_name_string(&name_data, NAME_ID_LICENSE_URL);
3941 metadata.preferred_family = get_name_string(&name_data, NAME_ID_PREFERRED_FAMILY);
3942 metadata.preferred_subfamily = get_name_string(&name_data, NAME_ID_PREFERRED_SUBFAMILY);
3943
3944 let mut f_family = None;
3946
3947 let patterns = name_table
3948 .name_records
3949 .iter()
3950 .filter_map(|name_record| {
3951 let name_id = name_record.name_id;
3952 if name_id == FONT_SPECIFIER_FAMILY_ID {
3953 if let Ok(Some(family)) =
3954 fontcode_get_name(&name_data, FONT_SPECIFIER_FAMILY_ID)
3955 {
3956 f_family = Some(family);
3957 }
3958 None
3959 } else if name_id == FONT_SPECIFIER_NAME_ID {
3960 let family = f_family.as_ref()?;
3961 let name = fontcode_get_name(&name_data, FONT_SPECIFIER_NAME_ID).ok()??;
3962 if name.to_bytes().is_empty() {
3963 None
3964 } else {
3965 let mut name_str =
3966 String::from_utf8_lossy(name.to_bytes()).to_string();
3967 let mut family_str =
3968 String::from_utf8_lossy(family.as_bytes()).to_string();
3969 if name_str.starts_with('.') {
3970 name_str = name_str[1..].to_string();
3971 }
3972 if family_str.starts_with('.') {
3973 family_str = family_str[1..].to_string();
3974 }
3975 Some((
3976 FcPattern {
3977 name: Some(name_str),
3978 family: Some(family_str),
3979 bold: if is_bold {
3980 PatternMatch::True
3981 } else {
3982 PatternMatch::False
3983 },
3984 italic: if is_italic {
3985 PatternMatch::True
3986 } else {
3987 PatternMatch::False
3988 },
3989 oblique: if is_oblique {
3990 PatternMatch::True
3991 } else {
3992 PatternMatch::False
3993 },
3994 monospace: if is_monospace {
3995 PatternMatch::True
3996 } else {
3997 PatternMatch::False
3998 },
3999 condensed: if stretch <= FcStretch::Condensed {
4000 PatternMatch::True
4001 } else {
4002 PatternMatch::False
4003 },
4004 weight,
4005 stretch,
4006 unicode_ranges: unicode_ranges.clone(),
4007 metadata: metadata.clone(),
4008 render_config: FcFontRenderConfig::default(),
4009 },
4010 font_index,
4011 ))
4012 }
4013 } else {
4014 None
4015 }
4016 })
4017 .collect::<BTreeSet<_>>();
4018
4019 results.extend(patterns.into_iter().map(|(pat, idx)| ParsedFontFace {
4020 pattern: pat,
4021 font_index: idx,
4022 }));
4023 }
4024
4025 if results.is_empty() {
4026 None
4027 } else {
4028 Some(results)
4029 }
4030}
4031
4032#[cfg(all(feature = "std", feature = "parsing"))]
4034pub(crate) fn FcParseFont(filepath: &PathBuf) -> Option<Vec<(FcPattern, FcFontPath)>> {
4035 #[cfg(all(not(target_family = "wasm"), feature = "std"))]
4036 use mmapio::MmapOptions;
4037 use std::fs::File;
4038
4039 let file = File::open(filepath).ok()?;
4041
4042 #[cfg(all(not(target_family = "wasm"), feature = "std"))]
4043 let font_bytes = unsafe { MmapOptions::new().map(&file).ok()? };
4044
4045 #[cfg(not(all(not(target_family = "wasm"), feature = "std")))]
4046 let font_bytes = std::fs::read(filepath).ok()?;
4047
4048 let faces = parse_font_faces(&font_bytes[..])?;
4049 let path_str = filepath.to_string_lossy().to_string();
4050 let bytes_hash = crate::utils::content_dedup_hash_u64(&font_bytes[..]);
4055
4056 Some(
4057 faces
4058 .into_iter()
4059 .map(|face| {
4060 (
4061 face.pattern,
4062 FcFontPath {
4063 path: path_str.clone(),
4064 font_index: face.font_index,
4065 bytes_hash,
4066 },
4067 )
4068 })
4069 .collect(),
4070 )
4071}
4072
4073#[cfg(all(feature = "std", feature = "parsing"))]
4090#[derive(Debug, Clone)]
4091pub struct FastCoverage {
4092 pub pattern: FcPattern,
4098 pub covered: alloc::collections::BTreeSet<char>,
4103 pub is_bold: bool,
4105 pub is_italic: bool,
4107}
4108
4109#[cfg(all(feature = "std", feature = "parsing"))]
4126#[allow(non_snake_case)]
4127pub fn FcParseFontFaceFast(
4128 font_bytes: &[u8],
4129 font_index: usize,
4130 codepoints: &alloc::collections::BTreeSet<char>,
4131) -> Option<FastCoverage> {
4132 use allsorts::{
4133 binary::read::ReadScope,
4134 font_data::FontData,
4135 tables::{
4136 cmap::{Cmap, CmapSubtable},
4137 FontTableProvider, HeadTable,
4138 },
4139 tag,
4140 };
4141
4142 let scope = ReadScope::new(font_bytes);
4143 let font_file = scope.read::<FontData<'_>>().ok()?;
4144 let provider = font_file.table_provider(font_index).ok()?;
4145
4146 let head_data = provider.table_data(tag::HEAD).ok()??;
4148 let head_table = ReadScope::new(&head_data).read::<HeadTable>().ok()?;
4149 let is_bold = head_table.is_bold();
4150 let is_italic = head_table.is_italic();
4151
4152 let cmap_data = provider.table_data(tag::CMAP).ok()??;
4155 let cmap = ReadScope::new(&cmap_data).read::<Cmap<'_>>().ok()?;
4156 let encoding_record = find_best_cmap_subtable(&cmap)?;
4157 let cmap_subtable = ReadScope::new(&cmap_data)
4158 .offset(encoding_record.offset as usize)
4159 .read::<CmapSubtable<'_>>()
4160 .ok()?;
4161
4162 let mut covered: alloc::collections::BTreeSet<char> =
4163 alloc::collections::BTreeSet::new();
4164 let mut covered_ranges: Vec<UnicodeRange> = Vec::new();
4165 for ch in codepoints {
4166 let cp = *ch as u32;
4167 if let Ok(Some(gid)) = cmap_subtable.map_glyph(cp) {
4168 if gid != 0 {
4169 covered.insert(*ch);
4170 if let Some(last) = covered_ranges.last_mut() {
4174 if cp == last.end + 1 {
4175 last.end = cp;
4176 continue;
4177 }
4178 }
4179 covered_ranges.push(UnicodeRange { start: cp, end: cp });
4180 }
4181 }
4182 }
4183
4184 let weight = if is_bold {
4185 FcWeight::Bold
4186 } else {
4187 FcWeight::Normal
4188 };
4189 let italic_match = if is_italic {
4190 PatternMatch::True
4191 } else {
4192 PatternMatch::False
4193 };
4194
4195 let pattern = FcPattern {
4196 name: None,
4197 family: None,
4198 weight,
4199 italic: italic_match,
4200 oblique: PatternMatch::DontCare,
4201 monospace: PatternMatch::DontCare,
4202 unicode_ranges: covered_ranges,
4203 ..Default::default()
4204 };
4205
4206 Some(FastCoverage {
4207 pattern,
4208 covered,
4209 is_bold,
4210 is_italic,
4211 })
4212}
4213
4214#[cfg(all(feature = "std", feature = "parsing"))]
4219#[allow(non_snake_case)]
4220pub fn FcCountFontFaces(font_bytes: &[u8]) -> usize {
4221 if font_bytes.len() >= 12 && &font_bytes[0..4] == b"ttcf" {
4222 let num_fonts = u32::from_be_bytes([
4223 font_bytes[8], font_bytes[9], font_bytes[10], font_bytes[11],
4224 ]);
4225 std::cmp::min(num_fonts as usize, 100).max(1)
4227 } else {
4228 1
4229 }
4230}
4231
4232#[cfg(all(feature = "std", feature = "parsing"))]
4258#[allow(non_snake_case)]
4259pub fn FcParseFontBytes(font_bytes: &[u8], font_id: &str) -> Option<Vec<(FcPattern, FcFont)>> {
4260 FcParseFontBytesInner(font_bytes, font_id)
4261}
4262
4263#[cfg(all(feature = "std", feature = "parsing"))]
4266fn FcParseFontBytesInner(font_bytes: &[u8], font_id: &str) -> Option<Vec<(FcPattern, FcFont)>> {
4267 let faces = parse_font_faces(font_bytes)?;
4268 let id = font_id.to_string();
4269 let bytes = font_bytes.to_vec();
4270
4271 Some(
4272 faces
4273 .into_iter()
4274 .map(|face| {
4275 (
4276 face.pattern,
4277 FcFont {
4278 bytes: bytes.clone(),
4279 font_index: face.font_index,
4280 id: id.clone(),
4281 },
4282 )
4283 })
4284 .collect(),
4285 )
4286}
4287
4288#[cfg(all(feature = "std", feature = "parsing"))]
4289fn FcScanDirectoriesInner(paths: &[(Option<String>, String)]) -> Vec<(FcPattern, FcFontPath)> {
4290 #[cfg(all(feature = "multithreading", not(target_family = "wasm")))]
4291 {
4292 use rayon::prelude::*;
4293
4294 paths
4296 .par_iter()
4297 .filter_map(|(prefix, p)| {
4298 process_path(prefix, PathBuf::from(p), false).map(FcScanSingleDirectoryRecursive)
4299 })
4300 .flatten()
4301 .collect()
4302 }
4303 #[cfg(not(all(feature = "multithreading", not(target_family = "wasm"))))]
4306 {
4307 paths
4308 .iter()
4309 .filter_map(|(prefix, p)| {
4310 process_path(prefix, PathBuf::from(p), false).map(FcScanSingleDirectoryRecursive)
4311 })
4312 .flatten()
4313 .collect()
4314 }
4315}
4316
4317#[cfg(feature = "std")]
4319fn FcCollectFontFilesRecursive(dir: PathBuf) -> Vec<PathBuf> {
4320 let mut files = Vec::new();
4321 let mut dirs_to_parse = vec![dir];
4322
4323 loop {
4324 let mut new_dirs = Vec::new();
4325 for dir in &dirs_to_parse {
4326 let entries = match std::fs::read_dir(dir) {
4327 Ok(o) => o,
4328 Err(_) => continue,
4329 };
4330 for entry in entries.flatten() {
4331 let path = entry.path();
4332 if path.is_dir() {
4333 new_dirs.push(path);
4334 } else {
4335 files.push(path);
4336 }
4337 }
4338 }
4339 if new_dirs.is_empty() {
4340 break;
4341 }
4342 dirs_to_parse = new_dirs;
4343 }
4344
4345 files
4346}
4347
4348#[cfg(all(feature = "std", feature = "parsing"))]
4349fn FcScanSingleDirectoryRecursive(dir: PathBuf) -> Vec<(FcPattern, FcFontPath)> {
4350 let files = FcCollectFontFilesRecursive(dir);
4351 FcParseFontFiles(&files)
4352}
4353
4354#[cfg(all(feature = "std", feature = "parsing"))]
4355fn FcParseFontFiles(files_to_parse: &[PathBuf]) -> Vec<(FcPattern, FcFontPath)> {
4356 let result = {
4357 #[cfg(all(feature = "multithreading", not(target_family = "wasm")))]
4358 {
4359 use rayon::prelude::*;
4360
4361 files_to_parse
4362 .par_iter()
4363 .filter_map(|file| FcParseFont(file))
4364 .collect::<Vec<Vec<_>>>()
4365 }
4366 #[cfg(not(all(feature = "multithreading", not(target_family = "wasm"))))]
4367 {
4368 files_to_parse
4369 .iter()
4370 .filter_map(|file| FcParseFont(file))
4371 .collect::<Vec<Vec<_>>>()
4372 }
4373 };
4374
4375 result.into_iter().flat_map(|f| f.into_iter()).collect()
4376}
4377
4378#[cfg(all(feature = "std", feature = "parsing"))]
4379fn process_path(
4383 prefix: &Option<String>,
4384 mut path: PathBuf,
4385 is_include_path: bool,
4386) -> Option<PathBuf> {
4387 use std::env::var;
4388
4389 const HOME_SHORTCUT: &str = "~";
4390 const CWD_PATH: &str = ".";
4391
4392 const HOME_ENV_VAR: &str = "HOME";
4393 const XDG_CONFIG_HOME_ENV_VAR: &str = "XDG_CONFIG_HOME";
4394 const XDG_CONFIG_HOME_DEFAULT_PATH_SUFFIX: &str = ".config";
4395 const XDG_DATA_HOME_ENV_VAR: &str = "XDG_DATA_HOME";
4396 const XDG_DATA_HOME_DEFAULT_PATH_SUFFIX: &str = ".local/share";
4397
4398 const PREFIX_CWD: &str = "cwd";
4399 const PREFIX_DEFAULT: &str = "default";
4400 const PREFIX_XDG: &str = "xdg";
4401
4402 fn get_home_value() -> Option<PathBuf> {
4404 var(HOME_ENV_VAR).ok().map(PathBuf::from)
4405 }
4406 fn get_xdg_config_home_value() -> Option<PathBuf> {
4407 var(XDG_CONFIG_HOME_ENV_VAR)
4408 .ok()
4409 .map(PathBuf::from)
4410 .or_else(|| {
4411 get_home_value()
4412 .map(|home_path| home_path.join(XDG_CONFIG_HOME_DEFAULT_PATH_SUFFIX))
4413 })
4414 }
4415 fn get_xdg_data_home_value() -> Option<PathBuf> {
4416 var(XDG_DATA_HOME_ENV_VAR)
4417 .ok()
4418 .map(PathBuf::from)
4419 .or_else(|| {
4420 get_home_value().map(|home_path| home_path.join(XDG_DATA_HOME_DEFAULT_PATH_SUFFIX))
4421 })
4422 }
4423
4424 if path.starts_with(HOME_SHORTCUT) {
4426 if let Some(home_path) = get_home_value() {
4427 path = home_path.join(
4428 path.strip_prefix(HOME_SHORTCUT)
4429 .expect("already checked that it starts with the prefix"),
4430 );
4431 } else {
4432 return None;
4433 }
4434 }
4435
4436 match prefix {
4438 Some(prefix) => match prefix.as_str() {
4439 PREFIX_CWD | PREFIX_DEFAULT => {
4440 let mut new_path = PathBuf::from(CWD_PATH);
4441 new_path.push(path);
4442
4443 Some(new_path)
4444 }
4445 PREFIX_XDG => {
4446 if is_include_path {
4447 get_xdg_config_home_value()
4448 .map(|xdg_config_home_path| xdg_config_home_path.join(path))
4449 } else {
4450 get_xdg_data_home_value()
4451 .map(|xdg_data_home_path| xdg_data_home_path.join(path))
4452 }
4453 }
4454 _ => None, },
4456 None => Some(path),
4457 }
4458}
4459
4460#[cfg(all(feature = "std", feature = "parsing"))]
4462fn get_name_string(name_data: &[u8], name_id: u16) -> Option<String> {
4463 fontcode_get_name(name_data, name_id)
4464 .ok()
4465 .flatten()
4466 .map(|name| String::from_utf8_lossy(name.to_bytes()).to_string())
4467}
4468
4469#[cfg(all(feature = "std", feature = "parsing"))]
4473fn get_verification_codepoints(start: u32, end: u32) -> Vec<u32> {
4474 match start {
4475 0x0000 => vec!['A' as u32, 'M' as u32, 'Z' as u32, 'a' as u32, 'm' as u32, 'z' as u32],
4477 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], _ => {
4553 let range_size = end - start;
4554 if range_size > 20 {
4555 vec![
4556 start + range_size / 5,
4557 start + 2 * range_size / 5,
4558 start + 3 * range_size / 5,
4559 start + 4 * range_size / 5,
4560 ]
4561 } else {
4562 vec![start, start + range_size / 2]
4563 }
4564 }
4565 }
4566}
4567
4568#[cfg(all(feature = "std", feature = "parsing"))]
4571fn find_best_cmap_subtable<'a>(
4572 cmap: &allsorts::tables::cmap::Cmap<'a>,
4573) -> Option<allsorts::tables::cmap::EncodingRecord> {
4574 use allsorts::tables::cmap::{PlatformId, EncodingId};
4575
4576 cmap.find_subtable(PlatformId::UNICODE, EncodingId(3))
4577 .or_else(|| cmap.find_subtable(PlatformId::UNICODE, EncodingId(4)))
4578 .or_else(|| cmap.find_subtable(PlatformId::WINDOWS, EncodingId(1)))
4579 .or_else(|| cmap.find_subtable(PlatformId::WINDOWS, EncodingId(10)))
4580 .or_else(|| cmap.find_subtable(PlatformId::UNICODE, EncodingId(0)))
4581 .or_else(|| cmap.find_subtable(PlatformId::UNICODE, EncodingId(1)))
4582}
4583
4584#[cfg(all(feature = "std", feature = "parsing"))]
4587fn verify_unicode_ranges_with_cmap(
4588 provider: &impl FontTableProvider,
4589 os2_ranges: Vec<UnicodeRange>
4590) -> Vec<UnicodeRange> {
4591 use allsorts::tables::cmap::{Cmap, CmapSubtable};
4592
4593 if os2_ranges.is_empty() {
4594 return Vec::new();
4595 }
4596
4597 let cmap_data = match provider.table_data(tag::CMAP) {
4599 Ok(Some(data)) => data,
4600 _ => return os2_ranges, };
4602
4603 let cmap = match ReadScope::new(&cmap_data).read::<Cmap<'_>>() {
4604 Ok(c) => c,
4605 Err(_) => return os2_ranges,
4606 };
4607
4608 let encoding_record = match find_best_cmap_subtable(&cmap) {
4609 Some(r) => r,
4610 None => return os2_ranges, };
4612
4613 let cmap_subtable = match ReadScope::new(&cmap_data)
4614 .offset(encoding_record.offset as usize)
4615 .read::<CmapSubtable<'_>>()
4616 {
4617 Ok(st) => st,
4618 Err(_) => return os2_ranges,
4619 };
4620
4621 let mut verified_ranges = Vec::new();
4623
4624 for range in os2_ranges {
4625 let test_codepoints = get_verification_codepoints(range.start, range.end);
4626
4627 let required_hits = (test_codepoints.len() + 1) / 2; let mut hits = 0;
4631
4632 for cp in test_codepoints {
4633 if cp >= range.start && cp <= range.end {
4634 if let Ok(Some(gid)) = cmap_subtable.map_glyph(cp) {
4635 if gid != 0 {
4636 hits += 1;
4637 if hits >= required_hits {
4638 break;
4639 }
4640 }
4641 }
4642 }
4643 }
4644
4645 if hits >= required_hits {
4646 verified_ranges.push(range);
4647 }
4648 }
4649
4650 verified_ranges
4651}
4652
4653#[cfg(all(feature = "std", feature = "parsing"))]
4656fn analyze_cmap_coverage(provider: &impl FontTableProvider) -> Option<Vec<UnicodeRange>> {
4657 use allsorts::tables::cmap::{Cmap, CmapSubtable};
4658
4659 let cmap_data = provider.table_data(tag::CMAP).ok()??;
4660 let cmap = ReadScope::new(&cmap_data).read::<Cmap<'_>>().ok()?;
4661
4662 let encoding_record = find_best_cmap_subtable(&cmap)?;
4663
4664 let cmap_subtable = ReadScope::new(&cmap_data)
4665 .offset(encoding_record.offset as usize)
4666 .read::<CmapSubtable<'_>>()
4667 .ok()?;
4668
4669 let blocks_to_check: &[(u32, u32)] = &[
4671 (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), ];
4722
4723 let mut ranges = Vec::new();
4724
4725 for &(start, end) in blocks_to_check {
4726 let test_codepoints = get_verification_codepoints(start, end);
4727 let required_hits = (test_codepoints.len() + 1) / 2;
4728 let mut hits = 0;
4729
4730 for cp in test_codepoints {
4731 if let Ok(Some(gid)) = cmap_subtable.map_glyph(cp) {
4732 if gid != 0 {
4733 hits += 1;
4734 if hits >= required_hits {
4735 break;
4736 }
4737 }
4738 }
4739 }
4740
4741 if hits >= required_hits {
4742 ranges.push(UnicodeRange { start, end });
4743 }
4744 }
4745
4746 if ranges.is_empty() {
4747 None
4748 } else {
4749 Some(ranges)
4750 }
4751}
4752
4753#[cfg(all(feature = "std", feature = "parsing"))]
4755#[allow(dead_code)]
4756fn extract_unicode_ranges(os2_table: &Os2) -> Vec<UnicodeRange> {
4757 let mut unicode_ranges = Vec::new();
4758
4759 let ranges = [
4760 os2_table.ul_unicode_range1,
4761 os2_table.ul_unicode_range2,
4762 os2_table.ul_unicode_range3,
4763 os2_table.ul_unicode_range4,
4764 ];
4765
4766 for &(bit, start, end) in UNICODE_RANGE_MAPPINGS {
4767 let range_idx = bit / 32;
4768 let bit_pos = bit % 32;
4769 if range_idx < 4 && (ranges[range_idx] & (1 << bit_pos)) != 0 {
4770 unicode_ranges.push(UnicodeRange { start, end });
4771 }
4772 }
4773
4774 unicode_ranges
4775}
4776
4777#[cfg(all(feature = "std", feature = "parsing"))]
4779fn detect_monospace(
4780 provider: &impl FontTableProvider,
4781 os2_table: &Os2,
4782 detected_monospace: Option<bool>,
4783) -> Option<bool> {
4784 if let Some(is_monospace) = detected_monospace {
4785 return Some(is_monospace);
4786 }
4787
4788 if os2_table.panose[0] == 2 {
4790 return Some(os2_table.panose[3] == 9); }
4793
4794 let hhea_data = provider.table_data(tag::HHEA).ok()??;
4796 let hhea_table = ReadScope::new(&hhea_data).read::<HheaTable>().ok()?;
4797 let maxp_data = provider.table_data(tag::MAXP).ok()??;
4798 let maxp_table = ReadScope::new(&maxp_data).read::<MaxpTable>().ok()?;
4799 let hmtx_data = provider.table_data(tag::HMTX).ok()??;
4800 let hmtx_table = ReadScope::new(&hmtx_data)
4801 .read_dep::<HmtxTable<'_>>((
4802 usize::from(maxp_table.num_glyphs),
4803 usize::from(hhea_table.num_h_metrics),
4804 ))
4805 .ok()?;
4806
4807 let mut monospace = true;
4808 let mut last_advance = 0;
4809
4810 for i in 0..hhea_table.num_h_metrics as usize {
4812 let advance = hmtx_table.h_metrics.read_item(i).ok()?.advance_width;
4813 if i > 0 && advance != last_advance {
4814 monospace = false;
4815 break;
4816 }
4817 last_advance = advance;
4818 }
4819
4820 Some(monospace)
4821}
4822
4823#[cfg(all(feature = "std", not(feature = "parsing")))]
4832fn pattern_from_filename(path: &std::path::Path) -> Option<FcPattern> {
4833 let ext = path.extension()?.to_str()?.to_lowercase();
4834 match ext.as_str() {
4835 "ttf" | "otf" | "ttc" | "woff" | "woff2" => {}
4836 _ => return None,
4837 }
4838
4839 let stem = path.file_stem()?.to_str()?;
4840 let all_tokens = crate::config::tokenize_lowercase(stem);
4841
4842 let has_token = |kw: &str| all_tokens.iter().any(|t| t == kw);
4844 let is_bold = has_token("bold") || has_token("heavy");
4845 let is_italic = has_token("italic");
4846 let is_oblique = has_token("oblique");
4847 let is_mono = has_token("mono") || has_token("monospace");
4848 let is_condensed = has_token("condensed");
4849
4850 let family_tokens = crate::config::tokenize_font_stem(stem);
4852 if family_tokens.is_empty() { return None; }
4853 let family = family_tokens.join(" ");
4854
4855 Some(FcPattern {
4856 name: Some(stem.to_string()),
4857 family: Some(family),
4858 bold: if is_bold { PatternMatch::True } else { PatternMatch::False },
4859 italic: if is_italic { PatternMatch::True } else { PatternMatch::False },
4860 oblique: if is_oblique { PatternMatch::True } else { PatternMatch::DontCare },
4861 monospace: if is_mono { PatternMatch::True } else { PatternMatch::DontCare },
4862 condensed: if is_condensed { PatternMatch::True } else { PatternMatch::DontCare },
4863 weight: if is_bold { FcWeight::Bold } else { FcWeight::Normal },
4864 stretch: if is_condensed { FcStretch::Condensed } else { FcStretch::Normal },
4865 unicode_ranges: Vec::new(),
4866 metadata: FcFontMetadata::default(),
4867 render_config: FcFontRenderConfig::default(),
4868 })
4869}