1#![allow(non_snake_case)]
84#![cfg_attr(not(feature = "std"), no_std)]
85
86extern crate alloc;
87
88use alloc::borrow::ToOwned;
89use alloc::collections::btree_map::BTreeMap;
90use alloc::string::{String, ToString};
91use alloc::vec::Vec;
92use alloc::{format, vec};
93use allsorts_subset_browser::binary::read::ReadScope;
94use allsorts_subset_browser::get_name::fontcode_get_name;
95use allsorts_subset_browser::tables::os2::Os2;
96use allsorts_subset_browser::tables::{FontTableProvider, HheaTable, HmtxTable, MaxpTable};
97use allsorts_subset_browser::tag;
98#[cfg(feature = "std")]
99use std::path::PathBuf;
100
101#[cfg(feature = "ffi")]
102pub mod ffi;
103
104#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
106pub struct FontId(pub u128);
107
108impl core::fmt::Debug for FontId {
109 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
110 core::fmt::Display::fmt(self, f)
111 }
112}
113
114impl core::fmt::Display for FontId {
115 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
116 let id = self.0;
117 write!(
118 f,
119 "{:08x}-{:04x}-{:04x}-{:04x}-{:012x}",
120 (id >> 96) & 0xFFFFFFFF,
121 (id >> 80) & 0xFFFF,
122 (id >> 64) & 0xFFFF,
123 (id >> 48) & 0xFFFF,
124 id & 0xFFFFFFFFFFFF
125 )
126 }
127}
128
129impl FontId {
130 pub fn new() -> Self {
132 #[cfg(feature = "std")]
133 {
134 use std::time::{SystemTime, UNIX_EPOCH};
135 let now = SystemTime::now()
136 .duration_since(UNIX_EPOCH)
137 .unwrap_or_default();
138
139 let time_part = now.as_nanos();
140 let random_part = {
141 let seed = now.as_secs() as u64;
143 let a = 6364136223846793005u64;
144 let c = 1442695040888963407u64;
145 let r = a.wrapping_mul(seed).wrapping_add(c);
146 r as u64
147 };
148
149 let id = (time_part & 0xFFFFFFFFFFFFFFFFu128) | ((random_part as u128) << 64);
151 FontId(id)
152 }
153
154 #[cfg(not(feature = "std"))]
155 {
156 static mut COUNTER: u128 = 0;
158 let id = unsafe {
159 COUNTER += 1;
160 COUNTER
161 };
162 FontId(id)
163 }
164 }
165}
166
167#[derive(Debug, Default, Copy, Clone, PartialOrd, Ord, PartialEq, Eq)]
169#[repr(C)]
170pub enum PatternMatch {
171 #[default]
173 DontCare,
174 True,
176 False,
178}
179
180impl PatternMatch {
181 fn needs_to_match(&self) -> bool {
182 matches!(self, PatternMatch::True | PatternMatch::False)
183 }
184
185 fn matches(&self, other: &PatternMatch) -> bool {
186 match (self, other) {
187 (PatternMatch::DontCare, _) => true,
188 (_, PatternMatch::DontCare) => true,
189 (a, b) => a == b,
190 }
191 }
192}
193
194#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
196#[repr(C)]
197pub enum FcWeight {
198 Thin = 100,
199 ExtraLight = 200,
200 Light = 300,
201 Normal = 400,
202 Medium = 500,
203 SemiBold = 600,
204 Bold = 700,
205 ExtraBold = 800,
206 Black = 900,
207}
208
209impl FcWeight {
210 pub fn from_u16(weight: u16) -> Self {
211 match weight {
212 0..=149 => FcWeight::Thin,
213 150..=249 => FcWeight::ExtraLight,
214 250..=349 => FcWeight::Light,
215 350..=449 => FcWeight::Normal,
216 450..=549 => FcWeight::Medium,
217 550..=649 => FcWeight::SemiBold,
218 650..=749 => FcWeight::Bold,
219 750..=849 => FcWeight::ExtraBold,
220 _ => FcWeight::Black,
221 }
222 }
223
224 pub fn find_best_match(&self, available: &[FcWeight]) -> Option<FcWeight> {
225 if available.is_empty() {
226 return None;
227 }
228
229 if available.contains(self) {
231 return Some(*self);
232 }
233
234 let self_value = *self as u16;
236
237 match *self {
238 FcWeight::Normal => {
239 if available.contains(&FcWeight::Medium) {
241 return Some(FcWeight::Medium);
242 }
243 for weight in &[FcWeight::Light, FcWeight::ExtraLight, FcWeight::Thin] {
245 if available.contains(weight) {
246 return Some(*weight);
247 }
248 }
249 for weight in &[
251 FcWeight::SemiBold,
252 FcWeight::Bold,
253 FcWeight::ExtraBold,
254 FcWeight::Black,
255 ] {
256 if available.contains(weight) {
257 return Some(*weight);
258 }
259 }
260 }
261 FcWeight::Medium => {
262 if available.contains(&FcWeight::Normal) {
264 return Some(FcWeight::Normal);
265 }
266 for weight in &[FcWeight::Light, FcWeight::ExtraLight, FcWeight::Thin] {
268 if available.contains(weight) {
269 return Some(*weight);
270 }
271 }
272 for weight in &[
274 FcWeight::SemiBold,
275 FcWeight::Bold,
276 FcWeight::ExtraBold,
277 FcWeight::Black,
278 ] {
279 if available.contains(weight) {
280 return Some(*weight);
281 }
282 }
283 }
284 FcWeight::Thin | FcWeight::ExtraLight | FcWeight::Light => {
285 let mut best_match = None;
287 let mut smallest_diff = u16::MAX;
288
289 for weight in available {
291 let weight_value = *weight as u16;
292 if weight_value <= self_value {
294 let diff = self_value - weight_value;
295 if diff < smallest_diff {
296 smallest_diff = diff;
297 best_match = Some(*weight);
298 }
299 }
300 }
301
302 if best_match.is_some() {
303 return best_match;
304 }
305
306 best_match = None;
308 smallest_diff = u16::MAX;
309
310 for weight in available {
311 let weight_value = *weight as u16;
312 if weight_value > self_value {
313 let diff = weight_value - self_value;
314 if diff < smallest_diff {
315 smallest_diff = diff;
316 best_match = Some(*weight);
317 }
318 }
319 }
320
321 return best_match;
322 }
323 FcWeight::SemiBold | FcWeight::Bold | FcWeight::ExtraBold | FcWeight::Black => {
324 let mut best_match = None;
326 let mut smallest_diff = u16::MAX;
327
328 for weight in available {
330 let weight_value = *weight as u16;
331 if weight_value >= self_value {
333 let diff = weight_value - self_value;
334 if diff < smallest_diff {
335 smallest_diff = diff;
336 best_match = Some(*weight);
337 }
338 }
339 }
340
341 if best_match.is_some() {
342 return best_match;
343 }
344
345 best_match = None;
347 smallest_diff = u16::MAX;
348
349 for weight in available {
350 let weight_value = *weight as u16;
351 if weight_value < self_value {
352 let diff = self_value - weight_value;
353 if diff < smallest_diff {
354 smallest_diff = diff;
355 best_match = Some(*weight);
356 }
357 }
358 }
359
360 return best_match;
361 }
362 }
363
364 Some(available[0])
366 }
367}
368
369impl Default for FcWeight {
370 fn default() -> Self {
371 FcWeight::Normal
372 }
373}
374
375#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
377#[repr(C)]
378pub enum FcStretch {
379 UltraCondensed = 1,
380 ExtraCondensed = 2,
381 Condensed = 3,
382 SemiCondensed = 4,
383 Normal = 5,
384 SemiExpanded = 6,
385 Expanded = 7,
386 ExtraExpanded = 8,
387 UltraExpanded = 9,
388}
389
390impl FcStretch {
391 pub fn is_condensed(&self) -> bool {
392 use self::FcStretch::*;
393 match self {
394 UltraCondensed => true,
395 ExtraCondensed => true,
396 Condensed => true,
397 SemiCondensed => true,
398 Normal => false,
399 SemiExpanded => false,
400 Expanded => false,
401 ExtraExpanded => false,
402 UltraExpanded => false,
403 }
404 }
405 pub fn from_u16(width_class: u16) -> Self {
406 match width_class {
407 1 => FcStretch::UltraCondensed,
408 2 => FcStretch::ExtraCondensed,
409 3 => FcStretch::Condensed,
410 4 => FcStretch::SemiCondensed,
411 5 => FcStretch::Normal,
412 6 => FcStretch::SemiExpanded,
413 7 => FcStretch::Expanded,
414 8 => FcStretch::ExtraExpanded,
415 9 => FcStretch::UltraExpanded,
416 _ => FcStretch::Normal,
417 }
418 }
419
420 pub fn find_best_match(&self, available: &[FcStretch]) -> Option<FcStretch> {
422 if available.is_empty() {
423 return None;
424 }
425
426 if available.contains(self) {
427 return Some(*self);
428 }
429
430 if *self <= FcStretch::Normal {
432 let mut closest_narrower = None;
434 for stretch in available.iter() {
435 if *stretch < *self
436 && (closest_narrower.is_none() || *stretch > closest_narrower.unwrap())
437 {
438 closest_narrower = Some(*stretch);
439 }
440 }
441
442 if closest_narrower.is_some() {
443 return closest_narrower;
444 }
445
446 let mut closest_wider = None;
448 for stretch in available.iter() {
449 if *stretch > *self
450 && (closest_wider.is_none() || *stretch < closest_wider.unwrap())
451 {
452 closest_wider = Some(*stretch);
453 }
454 }
455
456 return closest_wider;
457 } else {
458 let mut closest_wider = None;
460 for stretch in available.iter() {
461 if *stretch > *self
462 && (closest_wider.is_none() || *stretch < closest_wider.unwrap())
463 {
464 closest_wider = Some(*stretch);
465 }
466 }
467
468 if closest_wider.is_some() {
469 return closest_wider;
470 }
471
472 let mut closest_narrower = None;
474 for stretch in available.iter() {
475 if *stretch < *self
476 && (closest_narrower.is_none() || *stretch > closest_narrower.unwrap())
477 {
478 closest_narrower = Some(*stretch);
479 }
480 }
481
482 return closest_narrower;
483 }
484 }
485}
486
487impl Default for FcStretch {
488 fn default() -> Self {
489 FcStretch::Normal
490 }
491}
492
493#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
495pub struct UnicodeRange {
496 pub start: u32,
497 pub end: u32,
498}
499
500impl UnicodeRange {
501 pub fn contains(&self, c: char) -> bool {
502 let c = c as u32;
503 c >= self.start && c <= self.end
504 }
505
506 pub fn overlaps(&self, other: &UnicodeRange) -> bool {
507 self.start <= other.end && other.start <= self.end
508 }
509
510 pub fn is_subset_of(&self, other: &UnicodeRange) -> bool {
511 self.start >= other.start && self.end <= other.end
512 }
513}
514
515#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
517pub enum TraceLevel {
518 Debug,
519 Info,
520 Warning,
521 Error,
522}
523
524#[derive(Debug, Clone, PartialEq, Eq, Hash)]
526pub enum MatchReason {
527 NameMismatch {
528 requested: Option<String>,
529 found: Option<String>,
530 },
531 FamilyMismatch {
532 requested: Option<String>,
533 found: Option<String>,
534 },
535 StyleMismatch {
536 property: &'static str,
537 requested: String,
538 found: String,
539 },
540 WeightMismatch {
541 requested: FcWeight,
542 found: FcWeight,
543 },
544 StretchMismatch {
545 requested: FcStretch,
546 found: FcStretch,
547 },
548 UnicodeRangeMismatch {
549 character: char,
550 ranges: Vec<UnicodeRange>,
551 },
552 Success,
553}
554
555#[derive(Debug, Clone, PartialEq, Eq)]
557pub struct TraceMsg {
558 pub level: TraceLevel,
559 pub path: String,
560 pub reason: MatchReason,
561}
562
563#[derive(Default, Clone, PartialOrd, Ord, PartialEq, Eq)]
565#[repr(C)]
566pub struct FcPattern {
567 pub name: Option<String>,
569 pub family: Option<String>,
571 pub italic: PatternMatch,
573 pub oblique: PatternMatch,
575 pub bold: PatternMatch,
577 pub monospace: PatternMatch,
579 pub condensed: PatternMatch,
581 pub weight: FcWeight,
583 pub stretch: FcStretch,
585 pub unicode_ranges: Vec<UnicodeRange>,
587 pub metadata: FcFontMetadata,
589}
590
591impl core::fmt::Debug for FcPattern {
592 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
593 let mut d = f.debug_struct("FcPattern");
594
595 if let Some(name) = &self.name {
596 d.field("name", name);
597 }
598
599 if let Some(family) = &self.family {
600 d.field("family", family);
601 }
602
603 if self.italic != PatternMatch::DontCare {
604 d.field("italic", &self.italic);
605 }
606
607 if self.oblique != PatternMatch::DontCare {
608 d.field("oblique", &self.oblique);
609 }
610
611 if self.bold != PatternMatch::DontCare {
612 d.field("bold", &self.bold);
613 }
614
615 if self.monospace != PatternMatch::DontCare {
616 d.field("monospace", &self.monospace);
617 }
618
619 if self.condensed != PatternMatch::DontCare {
620 d.field("condensed", &self.condensed);
621 }
622
623 if self.weight != FcWeight::Normal {
624 d.field("weight", &self.weight);
625 }
626
627 if self.stretch != FcStretch::Normal {
628 d.field("stretch", &self.stretch);
629 }
630
631 if !self.unicode_ranges.is_empty() {
632 d.field("unicode_ranges", &self.unicode_ranges);
633 }
634
635 let empty_metadata = FcFontMetadata::default();
637 if self.metadata != empty_metadata {
638 d.field("metadata", &self.metadata);
639 }
640
641 d.finish()
642 }
643}
644
645#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]
647pub struct FcFontMetadata {
648 pub copyright: Option<String>,
649 pub designer: Option<String>,
650 pub designer_url: Option<String>,
651 pub font_family: Option<String>,
652 pub font_subfamily: Option<String>,
653 pub full_name: Option<String>,
654 pub id_description: Option<String>,
655 pub license: Option<String>,
656 pub license_url: Option<String>,
657 pub manufacturer: Option<String>,
658 pub manufacturer_url: Option<String>,
659 pub postscript_name: Option<String>,
660 pub preferred_family: Option<String>,
661 pub preferred_subfamily: Option<String>,
662 pub trademark: Option<String>,
663 pub unique_id: Option<String>,
664 pub version: Option<String>,
665}
666
667impl FcPattern {
668 pub fn contains_char(&self, c: char) -> bool {
670 if self.unicode_ranges.is_empty() {
671 return true; }
673
674 for range in &self.unicode_ranges {
675 if range.contains(c) {
676 return true;
677 }
678 }
679
680 false
681 }
682}
683
684#[derive(Debug, Clone, PartialEq, Eq)]
686pub struct FontMatch {
687 pub id: FontId,
688 pub unicode_ranges: Vec<UnicodeRange>,
689 pub fallbacks: Vec<FontMatchNoFallback>,
690}
691
692#[derive(Debug, Clone, PartialEq, Eq)]
694pub struct FontMatchNoFallback {
695 pub id: FontId,
696 pub unicode_ranges: Vec<UnicodeRange>,
697}
698
699#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq)]
701#[repr(C)]
702pub struct FcFontPath {
703 pub path: String,
704 pub font_index: usize,
705}
706
707#[derive(Debug, Clone, PartialEq, Eq)]
709#[repr(C)]
710pub struct FcFont {
711 pub bytes: Vec<u8>,
712 pub font_index: usize,
713 pub id: String, }
715
716#[derive(Debug, Clone)]
718pub enum FontSource<'a> {
719 Memory(&'a FcFont),
721 Disk(&'a FcFontPath),
723}
724
725#[derive(Debug, Default, Clone)]
727pub struct FcFontCache {
728 patterns: BTreeMap<FcPattern, FontId>,
730 disk_fonts: BTreeMap<FontId, FcFontPath>,
732 memory_fonts: BTreeMap<FontId, FcFont>,
734 metadata: BTreeMap<FontId, FcPattern>,
736}
737
738impl FcFontCache {
739 pub fn with_memory_fonts(&mut self, fonts: Vec<(FcPattern, FcFont)>) -> &mut Self {
741 for (pattern, font) in fonts {
742 let id = FontId::new();
743 self.patterns.insert(pattern.clone(), id);
744 self.metadata.insert(id, pattern);
745 self.memory_fonts.insert(id, font);
746 }
747 self
748 }
749
750 pub fn with_memory_font_with_id(
752 &mut self,
753 id: FontId,
754 pattern: FcPattern,
755 font: FcFont,
756 ) -> &mut Self {
757 self.patterns.insert(pattern.clone(), id);
758 self.metadata.insert(id, pattern);
759 self.memory_fonts.insert(id, font);
760 self
761 }
762
763 pub fn get_font_by_id(&self, id: &FontId) -> Option<FontSource> {
765 if let Some(font) = self.memory_fonts.get(id) {
767 return Some(FontSource::Memory(font));
768 }
769 if let Some(path) = self.disk_fonts.get(id) {
771 return Some(FontSource::Disk(path));
772 }
773 None
774 }
775
776 pub fn get_metadata_by_id(&self, id: &FontId) -> Option<&FcPattern> {
778 self.metadata.get(id)
779 }
780
781 #[cfg(feature = "std")]
783 pub fn get_font_bytes(&self, id: &FontId) -> Option<Vec<u8>> {
784 match self.get_font_by_id(id)? {
785 FontSource::Memory(font) => Some(font.bytes.clone()),
786 FontSource::Disk(path) => std::fs::read(&path.path).ok(),
787 }
788 }
789
790 #[cfg(not(all(feature = "std", feature = "parsing")))]
792 pub fn build() -> Self {
793 Self::default()
794 }
795
796 #[cfg(all(feature = "std", feature = "parsing"))]
798 pub fn build() -> Self {
799 let mut cache = FcFontCache::default();
800
801 #[cfg(target_os = "linux")]
802 {
803 if let Some(font_entries) = FcScanDirectories() {
804 for (pattern, path) in font_entries {
805 let id = FontId::new();
806 cache.patterns.insert(pattern.clone(), id);
807 cache.metadata.insert(id, pattern);
808 cache.disk_fonts.insert(id, path);
809 }
810 }
811 }
812
813 #[cfg(target_os = "windows")]
814 {
815 let font_dirs = vec![
817 (None, "C:\\Windows\\Fonts\\".to_owned()),
818 (
819 None,
820 "~\\AppData\\Local\\Microsoft\\Windows\\Fonts\\".to_owned(),
821 ),
822 ];
823
824 let font_entries = FcScanDirectoriesInner(&font_dirs);
825 for (pattern, path) in font_entries {
826 let id = FontId::new();
827 cache.patterns.insert(pattern.clone(), id);
828 cache.metadata.insert(id, pattern);
829 cache.disk_fonts.insert(id, path);
830 }
831 }
832
833 #[cfg(target_os = "macos")]
834 {
835 let font_dirs = vec![
836 (None, "~/Library/Fonts".to_owned()),
837 (None, "/System/Library/Fonts".to_owned()),
838 (None, "/Library/Fonts".to_owned()),
839 ];
840
841 let font_entries = FcScanDirectoriesInner(&font_dirs);
842 for (pattern, path) in font_entries {
843 let id = FontId::new();
844 cache.patterns.insert(pattern.clone(), id);
845 cache.metadata.insert(id, pattern);
846 cache.disk_fonts.insert(id, path);
847 }
848 }
849
850 cache
851 }
852
853 pub fn list(&self) -> Vec<(&FcPattern, FontId)> {
855 self.patterns
856 .iter()
857 .map(|(pattern, id)| (pattern, *id))
858 .collect()
859 }
860
861 pub fn query(&self, pattern: &FcPattern, trace: &mut Vec<TraceMsg>) -> Option<FontMatch> {
863 let mut matches = Vec::new();
864
865 for (stored_pattern, id) in &self.patterns {
866 if Self::query_matches_internal(stored_pattern, pattern, trace) {
867 let metadata = self.metadata.get(id).unwrap_or(stored_pattern);
868 let coverage = Self::calculate_unicode_coverage(&metadata.unicode_ranges);
869 let style_score = Self::calculate_style_score(pattern, metadata);
870 matches.push((*id, coverage, style_score, metadata.clone()));
871 }
872 }
873
874 matches.sort_by(|a, b| a.2.cmp(&b.2).then_with(|| b.1.cmp(&a.1)));
876
877 matches.first().map(|(id, _, _, metadata)| {
878 let fallbacks = self.find_fallbacks(metadata, trace);
880
881 FontMatch {
882 id: *id,
883 unicode_ranges: metadata.unicode_ranges.clone(),
884 fallbacks,
885 }
886 })
887 }
888
889 pub fn query_all(&self, pattern: &FcPattern, trace: &mut Vec<TraceMsg>) -> Vec<FontMatch> {
891 let mut matches = Vec::new();
892
893 for (stored_pattern, id) in &self.patterns {
894 if Self::query_matches_internal(stored_pattern, pattern, trace) {
895 let metadata = self.metadata.get(id).unwrap_or(stored_pattern);
896 let coverage = Self::calculate_unicode_coverage(&metadata.unicode_ranges);
897 let style_score = Self::calculate_style_score(pattern, metadata);
898 matches.push((*id, coverage, style_score, metadata.clone()));
899 }
900 }
901
902 matches.sort_by(|a, b| a.2.cmp(&b.2).then_with(|| b.1.cmp(&a.1)));
904
905 matches
906 .into_iter()
907 .map(|(id, _, _, metadata)| {
908 let fallbacks = self.find_fallbacks(&metadata, trace);
909
910 FontMatch {
911 id,
912 unicode_ranges: metadata.unicode_ranges.clone(),
913 fallbacks,
914 }
915 })
916 .collect()
917 }
918
919 fn find_fallbacks(
920 &self,
921 pattern: &FcPattern,
922 _trace: &mut Vec<TraceMsg>,
923 ) -> Vec<FontMatchNoFallback> {
924 let mut candidates = Vec::new();
925
926 let original_id = self.patterns.get(pattern);
928
929 for (stored_pattern, id) in &self.patterns {
930 if original_id.is_some() && original_id.unwrap() == id {
932 continue;
933 }
934
935 if !stored_pattern.unicode_ranges.is_empty() {
937 let supports_ranges = pattern.unicode_ranges.iter().any(|p_range| {
938 stored_pattern
939 .unicode_ranges
940 .iter()
941 .any(|k_range| p_range.overlaps(k_range))
942 });
943
944 if supports_ranges {
945 let coverage = Self::calculate_unicode_coverage(&stored_pattern.unicode_ranges);
946 let style_score = Self::calculate_style_score(pattern, stored_pattern);
947 candidates.push((
948 FontMatchNoFallback {
949 id: *id,
950 unicode_ranges: stored_pattern.unicode_ranges.clone(),
951 },
952 coverage,
953 style_score,
954 stored_pattern.clone(),
955 ));
956 }
957 }
958 }
959
960 candidates.sort_by(|a, b| a.2.cmp(&b.2).then_with(|| b.1.cmp(&a.1)));
962
963 let mut seen_ranges = Vec::new();
965 let mut deduplicated = Vec::new();
966
967 for (id, _, _, pattern) in candidates {
968 let mut is_new_range = false;
969
970 for range in &pattern.unicode_ranges {
971 if !seen_ranges.iter().any(|r: &UnicodeRange| r.overlaps(range)) {
972 seen_ranges.push(*range);
973 is_new_range = true;
974 }
975 }
976
977 if is_new_range {
978 deduplicated.push(id);
979 }
980 }
981
982 deduplicated
983 }
984
985 pub fn query_for_text(
987 &self,
988 pattern: &FcPattern,
989 text: &str,
990 trace: &mut Vec<TraceMsg>,
991 ) -> Vec<FontMatch> {
992 let base_matches = self.query_all(pattern, trace);
993
994 if base_matches.is_empty() || text.is_empty() {
996 return base_matches;
997 }
998
999 let chars: Vec<char> = text.chars().collect();
1000 let mut required_fonts = Vec::new();
1001 let mut covered_chars = vec![false; chars.len()];
1002
1003 for font_match in &base_matches {
1005 let metadata = match self.metadata.get(&font_match.id) {
1006 Some(metadata) => metadata,
1007 None => continue,
1008 };
1009
1010 for (i, &c) in chars.iter().enumerate() {
1011 if !covered_chars[i] && metadata.contains_char(c) {
1012 covered_chars[i] = true;
1013 }
1014 }
1015
1016 let covers_some = covered_chars.iter().any(|&covered| covered);
1018 if covers_some {
1019 required_fonts.push(font_match.clone());
1020 }
1021 }
1022
1023 let all_covered = covered_chars.iter().all(|&covered| covered);
1025 if !all_covered {
1026 let mut fallback_pattern = FcPattern::default();
1027
1028 for (i, &c) in chars.iter().enumerate() {
1030 if !covered_chars[i] {
1031 let c_value = c as u32;
1032 fallback_pattern.unicode_ranges.push(UnicodeRange {
1033 start: c_value,
1034 end: c_value,
1035 });
1036
1037 trace.push(TraceMsg {
1038 level: TraceLevel::Warning,
1039 path: "<fallback search>".to_string(),
1040 reason: MatchReason::UnicodeRangeMismatch {
1041 character: c,
1042 ranges: Vec::new(),
1043 },
1044 });
1045 }
1046 }
1047
1048 let fallback_matches = self.query_all(&fallback_pattern, trace);
1050 for font_match in fallback_matches {
1051 if !required_fonts.iter().any(|m| m.id == font_match.id) {
1052 required_fonts.push(font_match);
1053 }
1054 }
1055 }
1056
1057 required_fonts
1058 }
1059
1060 pub fn get_memory_font(&self, id: &FontId) -> Option<&FcFont> {
1062 self.memory_fonts.get(id)
1063 }
1064
1065 #[cfg(not(all(feature = "std", feature = "parsing")))]
1067 pub fn build() -> Self {
1068 Self::default()
1069 }
1070
1071 fn query_matches_internal(
1073 k: &FcPattern,
1074 pattern: &FcPattern,
1075 trace: &mut Vec<TraceMsg>,
1076 ) -> bool {
1077 if let Some(ref name) = pattern.name {
1079 let matches = k
1080 .name
1081 .as_ref()
1082 .map_or(false, |k_name| k_name.contains(name));
1083
1084 if !matches {
1085 trace.push(TraceMsg {
1086 level: TraceLevel::Info,
1087 path: k
1088 .name
1089 .as_ref()
1090 .map_or_else(|| "<unknown>".to_string(), Clone::clone),
1091 reason: MatchReason::NameMismatch {
1092 requested: pattern.name.clone(),
1093 found: k.name.clone(),
1094 },
1095 });
1096 return false;
1097 }
1098 }
1099
1100 if let Some(ref family) = pattern.family {
1102 let matches = k
1103 .family
1104 .as_ref()
1105 .map_or(false, |k_family| k_family.contains(family));
1106
1107 if !matches {
1108 trace.push(TraceMsg {
1109 level: TraceLevel::Info,
1110 path: k
1111 .name
1112 .as_ref()
1113 .map_or_else(|| "<unknown>".to_string(), Clone::clone),
1114 reason: MatchReason::FamilyMismatch {
1115 requested: pattern.family.clone(),
1116 found: k.family.clone(),
1117 },
1118 });
1119 return false;
1120 }
1121 }
1122
1123 let style_properties = [
1125 (
1126 "italic",
1127 pattern.italic.needs_to_match(),
1128 pattern.italic.matches(&k.italic),
1129 ),
1130 (
1131 "oblique",
1132 pattern.oblique.needs_to_match(),
1133 pattern.oblique.matches(&k.oblique),
1134 ),
1135 (
1136 "bold",
1137 pattern.bold.needs_to_match(),
1138 pattern.bold.matches(&k.bold),
1139 ),
1140 (
1141 "monospace",
1142 pattern.monospace.needs_to_match(),
1143 pattern.monospace.matches(&k.monospace),
1144 ),
1145 (
1146 "condensed",
1147 pattern.condensed.needs_to_match(),
1148 pattern.condensed.matches(&k.condensed),
1149 ),
1150 ];
1151
1152 for (property_name, needs_to_match, matches) in style_properties {
1153 if needs_to_match && !matches {
1154 let (requested, found) = match property_name {
1155 "italic" => (format!("{:?}", pattern.italic), format!("{:?}", k.italic)),
1156 "oblique" => (format!("{:?}", pattern.oblique), format!("{:?}", k.oblique)),
1157 "bold" => (format!("{:?}", pattern.bold), format!("{:?}", k.bold)),
1158 "monospace" => (
1159 format!("{:?}", pattern.monospace),
1160 format!("{:?}", k.monospace),
1161 ),
1162 "condensed" => (
1163 format!("{:?}", pattern.condensed),
1164 format!("{:?}", k.condensed),
1165 ),
1166 _ => (String::new(), String::new()),
1167 };
1168
1169 trace.push(TraceMsg {
1170 level: TraceLevel::Info,
1171 path: k
1172 .name
1173 .as_ref()
1174 .map_or_else(|| "<unknown>".to_string(), |s| s.clone()),
1175 reason: MatchReason::StyleMismatch {
1176 property: property_name,
1177 requested,
1178 found,
1179 },
1180 });
1181 return false;
1182 }
1183 }
1184
1185 if pattern.weight != FcWeight::Normal && pattern.weight != k.weight {
1187 trace.push(TraceMsg {
1188 level: TraceLevel::Info,
1189 path: k
1190 .name
1191 .as_ref()
1192 .map_or_else(|| "<unknown>".to_string(), |s| s.clone()),
1193 reason: MatchReason::WeightMismatch {
1194 requested: pattern.weight,
1195 found: k.weight,
1196 },
1197 });
1198 return false;
1199 }
1200
1201 if pattern.stretch != FcStretch::Normal && pattern.stretch != k.stretch {
1203 trace.push(TraceMsg {
1204 level: TraceLevel::Info,
1205 path: k
1206 .name
1207 .as_ref()
1208 .map_or_else(|| "<unknown>".to_string(), |s| s.clone()),
1209 reason: MatchReason::StretchMismatch {
1210 requested: pattern.stretch,
1211 found: k.stretch,
1212 },
1213 });
1214 return false;
1215 }
1216
1217 if !pattern.unicode_ranges.is_empty() {
1219 let mut has_overlap = false;
1220
1221 for p_range in &pattern.unicode_ranges {
1222 for k_range in &k.unicode_ranges {
1223 if p_range.overlaps(k_range) {
1224 has_overlap = true;
1225 break;
1226 }
1227 }
1228 if has_overlap {
1229 break;
1230 }
1231 }
1232
1233 if !has_overlap {
1234 trace.push(TraceMsg {
1235 level: TraceLevel::Info,
1236 path: k
1237 .name
1238 .as_ref()
1239 .map_or_else(|| "<unknown>".to_string(), |s| s.clone()),
1240 reason: MatchReason::UnicodeRangeMismatch {
1241 character: '\0', ranges: k.unicode_ranges.clone(),
1243 },
1244 });
1245 return false;
1246 }
1247 }
1248
1249 true
1250 }
1251 fn calculate_unicode_coverage(ranges: &[UnicodeRange]) -> u64 {
1254 ranges
1255 .iter()
1256 .map(|range| (range.end - range.start + 1) as u64)
1257 .sum()
1258 }
1259
1260 fn calculate_style_score(original: &FcPattern, candidate: &FcPattern) -> i32 {
1261
1262 let mut score = 0_i32;
1263
1264 if (original.bold == PatternMatch::True && candidate.weight == FcWeight::Bold)
1266 || (original.bold == PatternMatch::False && candidate.weight != FcWeight::Bold)
1267 {
1268 } else {
1271 let weight_diff = (original.weight as i32 - candidate.weight as i32).abs();
1273 score += weight_diff as i32;
1274 }
1275
1276 if (original.condensed == PatternMatch::True && candidate.stretch.is_condensed())
1278 || (original.condensed == PatternMatch::False && !candidate.stretch.is_condensed())
1279 {
1280 } else {
1283 let stretch_diff = (original.stretch as i32 - candidate.stretch as i32).abs();
1285 score += (stretch_diff * 100) as i32;
1286 }
1287
1288 let style_props = [
1290 (original.italic, candidate.italic, 300, 150),
1291 (original.oblique, candidate.oblique, 200, 100),
1292 (original.bold, candidate.bold, 300, 150),
1293 (original.monospace, candidate.monospace, 100, 50),
1294 (original.condensed, candidate.condensed, 100, 50),
1295 ];
1296
1297 for (orig, cand, mismatch_penalty, dontcare_penalty) in style_props {
1298 if orig.needs_to_match() {
1299 if !orig.matches(&cand) {
1300 if cand == PatternMatch::DontCare {
1301 score += dontcare_penalty;
1302 } else {
1303 score += mismatch_penalty;
1304 }
1305 } else if orig == PatternMatch::True && cand == PatternMatch::True {
1306 score -= 20;
1308 }
1309 }
1310 }
1311
1312 score
1313 }
1314}
1315
1316#[cfg(all(feature = "std", feature = "parsing"))]
1317fn FcScanDirectories() -> Option<Vec<(FcPattern, FcFontPath)>> {
1318 use std::fs;
1319 use std::path::Path;
1320
1321 const BASE_FONTCONFIG_PATH: &str = "/etc/fonts/fonts.conf";
1322
1323 if !Path::new(BASE_FONTCONFIG_PATH).exists() {
1324 return None;
1325 }
1326
1327 let mut font_paths = Vec::with_capacity(32);
1328 let mut paths_to_visit = vec![(None, PathBuf::from(BASE_FONTCONFIG_PATH))];
1329
1330 while let Some((prefix, path_to_visit)) = paths_to_visit.pop() {
1331 let path = match process_path(&prefix, path_to_visit, true) {
1332 Some(path) => path,
1333 None => continue,
1334 };
1335
1336 let metadata = match fs::metadata(&path) {
1337 Ok(metadata) => metadata,
1338 Err(_) => continue,
1339 };
1340
1341 if metadata.is_file() {
1342 let xml_utf8 = match fs::read_to_string(&path) {
1343 Ok(xml_utf8) => xml_utf8,
1344 Err(_) => continue,
1345 };
1346
1347 if ParseFontsConf(&xml_utf8, &mut paths_to_visit, &mut font_paths).is_none() {
1348 continue;
1349 }
1350 } else if metadata.is_dir() {
1351 let dir_entries = match fs::read_dir(&path) {
1352 Ok(dir_entries) => dir_entries,
1353 Err(_) => continue,
1354 };
1355
1356 for entry_result in dir_entries {
1357 let entry = match entry_result {
1358 Ok(entry) => entry,
1359 Err(_) => continue,
1360 };
1361
1362 let entry_path = entry.path();
1363
1364 let entry_metadata = match fs::metadata(&entry_path) {
1366 Ok(metadata) => metadata,
1367 Err(_) => continue,
1368 };
1369
1370 if !entry_metadata.is_file() {
1371 continue;
1372 }
1373
1374 let file_name = match entry_path.file_name() {
1375 Some(name) => name,
1376 None => continue,
1377 };
1378
1379 let file_name_str = file_name.to_string_lossy();
1380 if file_name_str.starts_with(|c: char| c.is_ascii_digit())
1381 && file_name_str.ends_with(".conf")
1382 {
1383 paths_to_visit.push((None, entry_path));
1384 }
1385 }
1386 }
1387 }
1388
1389 if font_paths.is_empty() {
1390 return None;
1391 }
1392
1393 Some(FcScanDirectoriesInner(&font_paths))
1394}
1395
1396#[cfg(all(feature = "std", feature = "parsing"))]
1398fn ParseFontsConf(
1399 input: &str,
1400 paths_to_visit: &mut Vec<(Option<String>, PathBuf)>,
1401 font_paths: &mut Vec<(Option<String>, String)>,
1402) -> Option<()> {
1403 use xmlparser::Token::*;
1404 use xmlparser::Tokenizer;
1405
1406 const TAG_INCLUDE: &str = "include";
1407 const TAG_DIR: &str = "dir";
1408 const ATTRIBUTE_PREFIX: &str = "prefix";
1409
1410 let mut current_prefix: Option<&str> = None;
1411 let mut current_path: Option<&str> = None;
1412 let mut is_in_include = false;
1413 let mut is_in_dir = false;
1414
1415 for token_result in Tokenizer::from(input) {
1416 let token = match token_result {
1417 Ok(token) => token,
1418 Err(_) => return None,
1419 };
1420
1421 match token {
1422 ElementStart { local, .. } => {
1423 if is_in_include || is_in_dir {
1424 return None; }
1426
1427 match local.as_str() {
1428 TAG_INCLUDE => {
1429 is_in_include = true;
1430 }
1431 TAG_DIR => {
1432 is_in_dir = true;
1433 }
1434 _ => continue,
1435 }
1436
1437 current_path = None;
1438 }
1439 Text { text, .. } => {
1440 let text = text.as_str().trim();
1441 if text.is_empty() {
1442 continue;
1443 }
1444 if is_in_include || is_in_dir {
1445 current_path = Some(text);
1446 }
1447 }
1448 Attribute { local, value, .. } => {
1449 if !is_in_include && !is_in_dir {
1450 continue;
1451 }
1452 if local.as_str() == ATTRIBUTE_PREFIX {
1454 current_prefix = Some(value.as_str());
1455 }
1456 }
1457 ElementEnd { end, .. } => {
1458 let end_tag = match end {
1459 xmlparser::ElementEnd::Close(_, a) => a,
1460 _ => continue,
1461 };
1462
1463 match end_tag.as_str() {
1464 TAG_INCLUDE => {
1465 if !is_in_include {
1466 continue;
1467 }
1468
1469 if let Some(current_path) = current_path.as_ref() {
1470 paths_to_visit.push((
1471 current_prefix.map(ToOwned::to_owned),
1472 PathBuf::from(*current_path),
1473 ));
1474 }
1475 }
1476 TAG_DIR => {
1477 if !is_in_dir {
1478 continue;
1479 }
1480
1481 if let Some(current_path) = current_path.as_ref() {
1482 font_paths.push((
1483 current_prefix.map(ToOwned::to_owned),
1484 (*current_path).to_owned(),
1485 ));
1486 }
1487 }
1488 _ => continue,
1489 }
1490
1491 is_in_include = false;
1492 is_in_dir = false;
1493 current_path = None;
1494 current_prefix = None;
1495 }
1496 _ => {}
1497 }
1498 }
1499
1500 Some(())
1501}
1502
1503#[cfg(all(feature = "std", feature = "parsing"))]
1505fn FcParseFont(filepath: &PathBuf) -> Option<Vec<(FcPattern, FcFontPath)>> {
1506 use allsorts_subset_browser::{
1507 binary::read::ReadScope,
1508 font_data::FontData,
1509 get_name::fontcode_get_name,
1510 post::PostTable,
1511 tables::{
1512 os2::Os2, FontTableProvider, HeadTable, HheaTable, HmtxTable, MaxpTable, NameTable,
1513 },
1514 tag,
1515 };
1516 #[cfg(all(not(target_family = "wasm"), feature = "std"))]
1517 use mmapio::MmapOptions;
1518 use std::collections::BTreeSet;
1519 use std::fs::File;
1520
1521 const FONT_SPECIFIER_NAME_ID: u16 = 4;
1522 const FONT_SPECIFIER_FAMILY_ID: u16 = 1;
1523
1524 let file = File::open(filepath).ok()?;
1526
1527 #[cfg(all(not(target_family = "wasm"), feature = "std"))]
1528 let font_bytes = unsafe { MmapOptions::new().map(&file).ok()? };
1529
1530 #[cfg(not(all(not(target_family = "wasm"), feature = "std")))]
1531 let font_bytes = std::fs::read(filepath).ok()?;
1532
1533 let max_fonts = if font_bytes.len() >= 12 && &font_bytes[0..4] == b"ttcf" {
1534 let num_fonts =
1536 u32::from_be_bytes([font_bytes[8], font_bytes[9], font_bytes[10], font_bytes[11]]);
1537 std::cmp::min(num_fonts as usize, 100)
1539 } else {
1540 1
1542 };
1543
1544 let scope = ReadScope::new(&font_bytes[..]);
1545 let font_file = scope.read::<FontData<'_>>().ok()?;
1546
1547 let mut results = Vec::new();
1549
1550 for font_index in 0..max_fonts {
1551 let provider = font_file.table_provider(font_index).ok()?;
1552 let head_data = provider.table_data(tag::HEAD).ok()??.into_owned();
1553 let head_table = ReadScope::new(&head_data).read::<HeadTable>().ok()?;
1554
1555 let is_bold = head_table.is_bold();
1556 let is_italic = head_table.is_italic();
1557 let mut detected_monospace = None;
1558
1559 let post_data = provider.table_data(tag::POST).ok()??;
1560 if let Ok(post_table) = ReadScope::new(&post_data).read::<PostTable>() {
1561 detected_monospace = Some(post_table.header.is_fixed_pitch != 0);
1563 }
1564
1565 let os2_data = provider.table_data(tag::OS_2).ok()??;
1567 let os2_table = ReadScope::new(&os2_data)
1568 .read_dep::<Os2>(os2_data.len())
1569 .ok()?;
1570
1571 let is_oblique = os2_table
1573 .fs_selection
1574 .contains(allsorts_subset_browser::tables::os2::FsSelection::OBLIQUE);
1575 let weight = FcWeight::from_u16(os2_table.us_weight_class);
1576 let stretch = FcStretch::from_u16(os2_table.us_width_class);
1577
1578 let mut unicode_ranges = Vec::new();
1580
1581 let ranges = [
1583 os2_table.ul_unicode_range1,
1584 os2_table.ul_unicode_range2,
1585 os2_table.ul_unicode_range3,
1586 os2_table.ul_unicode_range4,
1587 ];
1588
1589 let range_mappings = [
1592 (0, 0x0000, 0x007F), (1, 0x0080, 0x00FF), (2, 0x0100, 0x017F), (7, 0x0370, 0x03FF), (9, 0x0400, 0x04FF), (29, 0x2000, 0x206F), (57, 0x4E00, 0x9FFF), ];
1604
1605 for (range_idx, bit_pos, start, end) in range_mappings.iter().map(|&(bit, start, end)| {
1606 let range_idx = bit / 32;
1607 let bit_pos = bit % 32;
1608 (range_idx, bit_pos, start, end)
1609 }) {
1610 if range_idx < 4 && (ranges[range_idx] & (1 << bit_pos)) != 0 {
1611 unicode_ranges.push(UnicodeRange { start, end });
1612 }
1613 }
1614
1615 if detected_monospace.is_none() {
1617 if os2_table.panose[0] == 2 {
1619 detected_monospace = Some(os2_table.panose[3] == 9); } else {
1622 let hhea_data = provider.table_data(tag::HHEA).ok()??;
1623 let hhea_table = ReadScope::new(&hhea_data).read::<HheaTable>().ok()?;
1624 let maxp_data = provider.table_data(tag::MAXP).ok()??;
1625 let maxp_table = ReadScope::new(&maxp_data).read::<MaxpTable>().ok()?;
1626 let hmtx_data = provider.table_data(tag::HMTX).ok()??;
1627 let hmtx_table = ReadScope::new(&hmtx_data)
1628 .read_dep::<HmtxTable<'_>>((
1629 usize::from(maxp_table.num_glyphs),
1630 usize::from(hhea_table.num_h_metrics),
1631 ))
1632 .ok()?;
1633
1634 let mut monospace = true;
1635 let mut last_advance = 0;
1636 for i in 0..hhea_table.num_h_metrics as usize {
1637 let advance = hmtx_table.h_metrics.read_item(i).ok()?.advance_width;
1638 if i > 0 && advance != last_advance {
1639 monospace = false;
1640 break;
1641 }
1642 last_advance = advance;
1643 }
1644
1645 detected_monospace = Some(monospace);
1646 }
1647 }
1648
1649 let is_monospace = detected_monospace.unwrap_or(false);
1650
1651 let name_data = provider.table_data(tag::NAME).ok()??.into_owned();
1652 let name_table = ReadScope::new(&name_data).read::<NameTable>().ok()?;
1653
1654 let mut f_family = None;
1656
1657 let patterns = name_table
1658 .name_records
1659 .iter()
1660 .filter_map(|name_record| {
1661 let name_id = name_record.name_id;
1662 if name_id == FONT_SPECIFIER_FAMILY_ID {
1663 let family = fontcode_get_name(&name_data, FONT_SPECIFIER_FAMILY_ID).ok()??;
1664 f_family = Some(family);
1665 None
1666 } else if name_id == FONT_SPECIFIER_NAME_ID {
1667 let family = f_family.as_ref()?;
1668 let name = fontcode_get_name(&name_data, FONT_SPECIFIER_NAME_ID).ok()??;
1669 if name.to_bytes().is_empty() {
1670 None
1671 } else {
1672 let mut metadata = FcFontMetadata::default();
1674
1675 const NAME_ID_COPYRIGHT: u16 = 0;
1676 const NAME_ID_FAMILY: u16 = 1;
1677 const NAME_ID_SUBFAMILY: u16 = 2;
1678 const NAME_ID_UNIQUE_ID: u16 = 3;
1679 const NAME_ID_FULL_NAME: u16 = 4;
1680 const NAME_ID_VERSION: u16 = 5;
1681 const NAME_ID_POSTSCRIPT_NAME: u16 = 6;
1682 const NAME_ID_TRADEMARK: u16 = 7;
1683 const NAME_ID_MANUFACTURER: u16 = 8;
1684 const NAME_ID_DESIGNER: u16 = 9;
1685 const NAME_ID_DESCRIPTION: u16 = 10;
1686 const NAME_ID_VENDOR_URL: u16 = 11;
1687 const NAME_ID_DESIGNER_URL: u16 = 12;
1688 const NAME_ID_LICENSE: u16 = 13;
1689 const NAME_ID_LICENSE_URL: u16 = 14;
1690 const NAME_ID_PREFERRED_FAMILY: u16 = 16;
1691 const NAME_ID_PREFERRED_SUBFAMILY: u16 = 17;
1692
1693 metadata.copyright = get_name_string(&name_data, NAME_ID_COPYRIGHT);
1695 metadata.font_family = get_name_string(&name_data, NAME_ID_FAMILY);
1696 metadata.font_subfamily = get_name_string(&name_data, NAME_ID_SUBFAMILY);
1697 metadata.full_name = get_name_string(&name_data, NAME_ID_FULL_NAME);
1698 metadata.unique_id = get_name_string(&name_data, NAME_ID_UNIQUE_ID);
1699 metadata.version = get_name_string(&name_data, NAME_ID_VERSION);
1700 metadata.postscript_name =
1701 get_name_string(&name_data, NAME_ID_POSTSCRIPT_NAME);
1702 metadata.trademark = get_name_string(&name_data, NAME_ID_TRADEMARK);
1703 metadata.manufacturer = get_name_string(&name_data, NAME_ID_MANUFACTURER);
1704 metadata.designer = get_name_string(&name_data, NAME_ID_DESIGNER);
1705 metadata.id_description = get_name_string(&name_data, NAME_ID_DESCRIPTION);
1706 metadata.designer_url = get_name_string(&name_data, NAME_ID_DESIGNER_URL);
1707 metadata.manufacturer_url = get_name_string(&name_data, NAME_ID_VENDOR_URL);
1708 metadata.license = get_name_string(&name_data, NAME_ID_LICENSE);
1709 metadata.license_url = get_name_string(&name_data, NAME_ID_LICENSE_URL);
1710 metadata.preferred_family =
1711 get_name_string(&name_data, NAME_ID_PREFERRED_FAMILY);
1712 metadata.preferred_subfamily =
1713 get_name_string(&name_data, NAME_ID_PREFERRED_SUBFAMILY);
1714
1715 let mut name = String::from_utf8_lossy(name.to_bytes()).to_string();
1716 let mut family = String::from_utf8_lossy(family.as_bytes()).to_string();
1717 if name.starts_with(".") {
1718 name = name[1..].to_string();
1719 }
1720 if family.starts_with(".") {
1721 family = family[1..].to_string();
1722 }
1723 Some((
1724 FcPattern {
1725 name: Some(name),
1726 family: Some(family),
1727 bold: if is_bold {
1728 PatternMatch::True
1729 } else {
1730 PatternMatch::False
1731 },
1732 italic: if is_italic {
1733 PatternMatch::True
1734 } else {
1735 PatternMatch::False
1736 },
1737 oblique: if is_oblique {
1738 PatternMatch::True
1739 } else {
1740 PatternMatch::False
1741 },
1742 monospace: if is_monospace {
1743 PatternMatch::True
1744 } else {
1745 PatternMatch::False
1746 },
1747 condensed: if stretch <= FcStretch::Condensed {
1748 PatternMatch::True
1749 } else {
1750 PatternMatch::False
1751 },
1752 weight,
1753 stretch,
1754 unicode_ranges: unicode_ranges.clone(),
1755 metadata,
1756 },
1757 font_index,
1758 ))
1759 }
1760 } else {
1761 None
1762 }
1763 })
1764 .collect::<BTreeSet<_>>();
1765
1766 results.extend(patterns.into_iter().map(|(pat, index)| {
1767 (
1768 pat,
1769 FcFontPath {
1770 path: filepath.to_string_lossy().to_string(),
1771 font_index: index,
1772 },
1773 )
1774 }));
1775 }
1776
1777 if results.is_empty() {
1778 None
1779 } else {
1780 Some(results)
1781 }
1782}
1783
1784#[cfg(all(feature = "std", feature = "parsing"))]
1785fn FcScanDirectoriesInner(paths: &[(Option<String>, String)]) -> Vec<(FcPattern, FcFontPath)> {
1786 #[cfg(feature = "multithreading")]
1787 {
1788 use rayon::prelude::*;
1789
1790 paths
1792 .par_iter()
1793 .filter_map(|(prefix, p)| {
1794 if let Some(path) = process_path(prefix, PathBuf::from(p), false) {
1795 Some(FcScanSingleDirectoryRecursive(path))
1796 } else {
1797 None
1798 }
1799 })
1800 .flatten()
1801 .collect()
1802 }
1803 #[cfg(not(feature = "multithreading"))]
1804 {
1805 paths
1806 .iter()
1807 .filter_map(|(prefix, p)| {
1808 if let Some(path) = process_path(prefix, PathBuf::from(p), false) {
1809 Some(FcScanSingleDirectoryRecursive(path))
1810 } else {
1811 None
1812 }
1813 })
1814 .flatten()
1815 .collect()
1816 }
1817}
1818
1819#[cfg(all(feature = "std", feature = "parsing"))]
1820fn FcScanSingleDirectoryRecursive(dir: PathBuf) -> Vec<(FcPattern, FcFontPath)> {
1821 let mut files_to_parse = Vec::new();
1822 let mut dirs_to_parse = vec![dir];
1823
1824 'outer: loop {
1825 let mut new_dirs_to_parse = Vec::new();
1826
1827 'inner: for dir in dirs_to_parse.clone() {
1828 let dir = match std::fs::read_dir(dir) {
1829 Ok(o) => o,
1830 Err(_) => continue 'inner,
1831 };
1832
1833 for (path, pathbuf) in dir.filter_map(|entry| {
1834 let entry = entry.ok()?;
1835 let path = entry.path();
1836 let pathbuf = path.to_path_buf();
1837 Some((path, pathbuf))
1838 }) {
1839 if path.is_dir() {
1840 new_dirs_to_parse.push(pathbuf);
1841 } else {
1842 files_to_parse.push(pathbuf);
1843 }
1844 }
1845 }
1846
1847 if new_dirs_to_parse.is_empty() {
1848 break 'outer;
1849 } else {
1850 dirs_to_parse = new_dirs_to_parse;
1851 }
1852 }
1853
1854 FcParseFontFiles(&files_to_parse)
1855}
1856
1857#[cfg(all(feature = "std", feature = "parsing"))]
1858fn FcParseFontFiles(files_to_parse: &[PathBuf]) -> Vec<(FcPattern, FcFontPath)> {
1859 let result = {
1860 #[cfg(feature = "multithreading")]
1861 {
1862 use rayon::prelude::*;
1863
1864 files_to_parse
1865 .par_iter()
1866 .filter_map(|file| FcParseFont(file))
1867 .collect::<Vec<Vec<_>>>()
1868 }
1869 #[cfg(not(feature = "multithreading"))]
1870 {
1871 files_to_parse
1872 .iter()
1873 .filter_map(|file| FcParseFont(file))
1874 .collect::<Vec<Vec<_>>>()
1875 }
1876 };
1877
1878 result.into_iter().flat_map(|f| f.into_iter()).collect()
1879}
1880
1881#[cfg(feature = "std")]
1882fn process_path(
1886 prefix: &Option<String>,
1887 mut path: PathBuf,
1888 is_include_path: bool,
1889) -> Option<PathBuf> {
1890 use std::env::var;
1891
1892 const HOME_SHORTCUT: &str = "~";
1893 const CWD_PATH: &str = ".";
1894
1895 const HOME_ENV_VAR: &str = "HOME";
1896 const XDG_CONFIG_HOME_ENV_VAR: &str = "XDG_CONFIG_HOME";
1897 const XDG_CONFIG_HOME_DEFAULT_PATH_SUFFIX: &str = ".config";
1898 const XDG_DATA_HOME_ENV_VAR: &str = "XDG_DATA_HOME";
1899 const XDG_DATA_HOME_DEFAULT_PATH_SUFFIX: &str = ".local/share";
1900
1901 const PREFIX_CWD: &str = "cwd";
1902 const PREFIX_DEFAULT: &str = "default";
1903 const PREFIX_XDG: &str = "xdg";
1904
1905 fn get_home_value() -> Option<PathBuf> {
1907 var(HOME_ENV_VAR).ok().map(PathBuf::from)
1908 }
1909 fn get_xdg_config_home_value() -> Option<PathBuf> {
1910 var(XDG_CONFIG_HOME_ENV_VAR)
1911 .ok()
1912 .map(PathBuf::from)
1913 .or_else(|| {
1914 get_home_value()
1915 .map(|home_path| home_path.join(XDG_CONFIG_HOME_DEFAULT_PATH_SUFFIX))
1916 })
1917 }
1918 fn get_xdg_data_home_value() -> Option<PathBuf> {
1919 var(XDG_DATA_HOME_ENV_VAR)
1920 .ok()
1921 .map(PathBuf::from)
1922 .or_else(|| {
1923 get_home_value().map(|home_path| home_path.join(XDG_DATA_HOME_DEFAULT_PATH_SUFFIX))
1924 })
1925 }
1926
1927 if path.starts_with(HOME_SHORTCUT) {
1929 if let Some(home_path) = get_home_value() {
1930 path = home_path.join(
1931 path.strip_prefix(HOME_SHORTCUT)
1932 .expect("already checked that it starts with the prefix"),
1933 );
1934 } else {
1935 return None;
1936 }
1937 }
1938
1939 match prefix {
1941 Some(prefix) => match prefix.as_str() {
1942 PREFIX_CWD | PREFIX_DEFAULT => {
1943 let mut new_path = PathBuf::from(CWD_PATH);
1944 new_path.push(path);
1945
1946 Some(new_path)
1947 }
1948 PREFIX_XDG => {
1949 if is_include_path {
1950 get_xdg_config_home_value()
1951 .map(|xdg_config_home_path| xdg_config_home_path.join(path))
1952 } else {
1953 get_xdg_data_home_value()
1954 .map(|xdg_data_home_path| xdg_data_home_path.join(path))
1955 }
1956 }
1957 _ => None, },
1959 None => Some(path),
1960 }
1961}
1962
1963fn get_name_string(name_data: &[u8], name_id: u16) -> Option<String> {
1965 fontcode_get_name(name_data, name_id)
1966 .ok()
1967 .flatten()
1968 .map(|name| String::from_utf8_lossy(name.to_bytes()).to_string())
1969}
1970
1971fn extract_unicode_ranges(os2_table: &Os2) -> Vec<UnicodeRange> {
1973 let mut unicode_ranges = Vec::new();
1974
1975 let ranges = [
1977 os2_table.ul_unicode_range1,
1978 os2_table.ul_unicode_range2,
1979 os2_table.ul_unicode_range3,
1980 os2_table.ul_unicode_range4,
1981 ];
1982
1983 let range_mappings = [
1986 (0, 0x0000, 0x007F), (1, 0x0080, 0x00FF), (2, 0x0100, 0x017F), (7, 0x0370, 0x03FF), (9, 0x0400, 0x04FF), (29, 0x2000, 0x206F), (57, 0x4E00, 0x9FFF), ];
1995
1996 for (bit, start, end) in &range_mappings {
1997 let range_idx = bit / 32;
1998 let bit_pos = bit % 32;
1999
2000 if range_idx < 4 && (ranges[range_idx] & (1 << bit_pos)) != 0 {
2001 unicode_ranges.push(UnicodeRange {
2002 start: *start,
2003 end: *end,
2004 });
2005 }
2006 }
2007
2008 unicode_ranges
2009}
2010
2011fn detect_monospace(
2013 provider: &impl FontTableProvider,
2014 os2_table: &Os2,
2015 detected_monospace: Option<bool>,
2016) -> Option<bool> {
2017 if let Some(is_monospace) = detected_monospace {
2018 return Some(is_monospace);
2019 }
2020
2021 if os2_table.panose[0] == 2 {
2023 return Some(os2_table.panose[3] == 9); }
2026
2027 let hhea_data = provider.table_data(tag::HHEA).ok()??;
2029 let hhea_table = ReadScope::new(&hhea_data).read::<HheaTable>().ok()?;
2030 let maxp_data = provider.table_data(tag::MAXP).ok()??;
2031 let maxp_table = ReadScope::new(&maxp_data).read::<MaxpTable>().ok()?;
2032 let hmtx_data = provider.table_data(tag::HMTX).ok()??;
2033 let hmtx_table = ReadScope::new(&hmtx_data)
2034 .read_dep::<HmtxTable<'_>>((
2035 usize::from(maxp_table.num_glyphs),
2036 usize::from(hhea_table.num_h_metrics),
2037 ))
2038 .ok()?;
2039
2040 let mut monospace = true;
2041 let mut last_advance = 0;
2042
2043 for i in 0..hhea_table.num_h_metrics as usize {
2045 let advance = hmtx_table.h_metrics.read_item(i).ok()?.advance_width;
2046 if i > 0 && advance != last_advance {
2047 monospace = false;
2048 break;
2049 }
2050 last_advance = advance;
2051 }
2052
2053 Some(monospace)
2054}