1#![allow(non_snake_case)]
68
69extern crate alloc;
77
78use alloc::collections::btree_map::BTreeMap;
79use alloc::string::{String, ToString};
80use alloc::vec::Vec;
81#[cfg(all(feature = "std", feature = "parsing"))]
82use allsorts::binary::read::ReadScope;
83#[cfg(all(feature = "std", feature = "parsing"))]
84use allsorts::get_name::fontcode_get_name;
85#[cfg(all(feature = "std", feature = "parsing"))]
86use allsorts::tables::os2::Os2;
87#[cfg(all(feature = "std", feature = "parsing"))]
88use allsorts::tables::{FontTableProvider, HheaTable, HmtxTable, MaxpTable};
89#[cfg(all(feature = "std", feature = "parsing"))]
90use allsorts::tag;
91#[cfg(feature = "std")]
92use std::path::PathBuf;
93
94pub mod utils;
95#[cfg(feature = "std")]
96pub mod config;
97
98#[cfg(feature = "ffi")]
99pub mod ffi;
100
101#[cfg(feature = "async-registry")]
102pub mod scoring;
103#[cfg(feature = "async-registry")]
104pub mod registry;
105#[cfg(feature = "async-registry")]
106pub mod multithread;
107#[cfg(feature = "cache")]
108pub mod disk_cache;
109
110#[cfg(all(target_os = "ios", feature = "std", feature = "parsing"))]
111mod mobile_ios;
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
115pub enum OperatingSystem {
116 Windows,
117 Linux,
118 MacOS,
119 IOS,
120 Android,
121 Wasm,
122}
123
124impl OperatingSystem {
125 pub fn current() -> Self {
127 #[cfg(target_os = "windows")]
128 return OperatingSystem::Windows;
129
130 #[cfg(target_os = "linux")]
131 return OperatingSystem::Linux;
132
133 #[cfg(target_os = "macos")]
134 return OperatingSystem::MacOS;
135
136 #[cfg(target_os = "ios")]
137 return OperatingSystem::IOS;
138
139 #[cfg(target_os = "android")]
140 return OperatingSystem::Android;
141
142 #[cfg(target_family = "wasm")]
143 return OperatingSystem::Wasm;
144
145 #[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos", target_os = "ios", target_os = "android", target_family = "wasm")))]
146 return OperatingSystem::Linux; }
148
149 pub fn get_serif_fonts(&self, unicode_ranges: &[UnicodeRange]) -> Vec<String> {
152 let has_cjk = has_cjk_ranges(unicode_ranges);
153 let has_arabic = has_arabic_ranges(unicode_ranges);
154 let _has_cyrillic = has_cyrillic_ranges(unicode_ranges);
155
156 match self {
157 OperatingSystem::Windows => {
158 let mut fonts = Vec::new();
159 if has_cjk {
160 fonts.extend_from_slice(&["MS Mincho", "SimSun", "MingLiU"]);
161 }
162 if has_arabic {
163 fonts.push("Traditional Arabic");
164 }
165 fonts.push("Times New Roman");
166 fonts.iter().map(|s| s.to_string()).collect()
167 }
168 OperatingSystem::Linux => {
169 let mut fonts = Vec::new();
170 if has_cjk {
171 fonts.extend_from_slice(&["Noto Serif CJK SC", "Noto Serif CJK JP", "Noto Serif CJK KR"]);
172 }
173 if has_arabic {
174 fonts.push("Noto Serif Arabic");
175 }
176 fonts.extend_from_slice(&[
177 "Times", "Times New Roman", "DejaVu Serif", "Free Serif",
178 "Noto Serif", "Bitstream Vera Serif", "Roman", "Regular"
179 ]);
180 fonts.iter().map(|s| s.to_string()).collect()
181 }
182 OperatingSystem::MacOS | OperatingSystem::IOS => {
183 let mut fonts = Vec::new();
184 if has_cjk {
185 fonts.extend_from_slice(&["Hiragino Mincho ProN", "STSong", "AppleMyungjo"]);
186 }
187 if has_arabic {
188 fonts.push("Geeza Pro");
189 }
190 fonts.extend_from_slice(&["Times New Roman", "Times", "New York", "Palatino"]);
191 fonts.iter().map(|s| s.to_string()).collect()
192 }
193 OperatingSystem::Android => {
194 let mut fonts = Vec::new();
195 if has_cjk {
196 fonts.extend_from_slice(&["Noto Serif CJK SC", "Noto Serif CJK JP", "Noto Serif CJK KR"]);
197 }
198 if has_arabic {
199 fonts.push("Noto Naskh Arabic");
200 }
201 fonts.extend_from_slice(&["Noto Serif", "Roboto Serif", "Droid Serif"]);
202 fonts.iter().map(|s| s.to_string()).collect()
203 }
204 OperatingSystem::Wasm => Vec::new(),
205 }
206 }
207
208 pub fn get_sans_serif_fonts(&self, unicode_ranges: &[UnicodeRange]) -> Vec<String> {
211 let has_cjk = has_cjk_ranges(unicode_ranges);
212 let has_arabic = has_arabic_ranges(unicode_ranges);
213 let _has_cyrillic = has_cyrillic_ranges(unicode_ranges);
214 let has_hebrew = has_hebrew_ranges(unicode_ranges);
215 let has_thai = has_thai_ranges(unicode_ranges);
216
217 match self {
218 OperatingSystem::Windows => {
219 let mut fonts = Vec::new();
220 if has_cjk {
221 fonts.extend_from_slice(&["Microsoft YaHei", "MS Gothic", "Malgun Gothic", "SimHei"]);
222 }
223 if has_arabic {
224 fonts.push("Segoe UI Arabic");
225 }
226 if has_hebrew {
227 fonts.push("Segoe UI Hebrew");
228 }
229 if has_thai {
230 fonts.push("Leelawadee UI");
231 }
232 fonts.extend_from_slice(&["Segoe UI", "Tahoma", "Microsoft Sans Serif", "MS Sans Serif", "Helv"]);
233 fonts.iter().map(|s| s.to_string()).collect()
234 }
235 OperatingSystem::Linux => {
236 let mut fonts = Vec::new();
237 if has_cjk {
238 fonts.extend_from_slice(&[
239 "Noto Sans CJK SC", "Noto Sans CJK JP", "Noto Sans CJK KR",
240 "WenQuanYi Micro Hei", "Droid Sans Fallback"
241 ]);
242 }
243 if has_arabic {
244 fonts.push("Noto Sans Arabic");
245 }
246 if has_hebrew {
247 fonts.push("Noto Sans Hebrew");
248 }
249 if has_thai {
250 fonts.push("Noto Sans Thai");
251 }
252 fonts.extend_from_slice(&["Ubuntu", "Arial", "DejaVu Sans", "Noto Sans", "Liberation Sans"]);
253 fonts.iter().map(|s| s.to_string()).collect()
254 }
255 OperatingSystem::MacOS | OperatingSystem::IOS => {
256 let mut fonts = Vec::new();
257 if has_cjk {
258 fonts.extend_from_slice(&[
259 "Hiragino Sans", "Hiragino Kaku Gothic ProN",
260 "PingFang SC", "PingFang TC", "Apple SD Gothic Neo"
261 ]);
262 }
263 if has_arabic {
264 fonts.push("Geeza Pro");
265 }
266 if has_hebrew {
267 fonts.push("Arial Hebrew");
268 }
269 if has_thai {
270 fonts.push("Thonburi");
271 }
272 fonts.extend_from_slice(&[
273 "San Francisco", ".AppleSystemUIFont", ".SFUIText", ".SFUI-Regular",
274 "Helvetica Neue", "Helvetica", "Lucida Grande",
275 ]);
276 fonts.iter().map(|s| s.to_string()).collect()
277 }
278 OperatingSystem::Android => {
279 let mut fonts = Vec::new();
280 if has_cjk {
281 fonts.extend_from_slice(&[
282 "Noto Sans CJK SC", "Noto Sans CJK JP", "Noto Sans CJK KR",
283 "Droid Sans Fallback",
284 ]);
285 }
286 if has_arabic {
287 fonts.push("Noto Sans Arabic");
288 }
289 if has_hebrew {
290 fonts.push("Noto Sans Hebrew");
291 }
292 if has_thai {
293 fonts.push("Noto Sans Thai");
294 }
295 fonts.extend_from_slice(&[
296 "Roboto", "Roboto-Regular", "Noto Sans", "Droid Sans",
297 ]);
298 fonts.iter().map(|s| s.to_string()).collect()
299 }
300 OperatingSystem::Wasm => Vec::new(),
301 }
302 }
303
304 pub fn get_monospace_fonts(&self, unicode_ranges: &[UnicodeRange]) -> Vec<String> {
307 let has_cjk = has_cjk_ranges(unicode_ranges);
308
309 match self {
310 OperatingSystem::Windows => {
311 let mut fonts = Vec::new();
312 if has_cjk {
313 fonts.extend_from_slice(&["MS Gothic", "SimHei"]);
314 }
315 fonts.extend_from_slice(&["Segoe UI Mono", "Courier New", "Cascadia Code", "Cascadia Mono", "Consolas"]);
316 fonts.iter().map(|s| s.to_string()).collect()
317 }
318 OperatingSystem::Linux => {
319 let mut fonts = Vec::new();
320 if has_cjk {
321 fonts.extend_from_slice(&["Noto Sans Mono CJK SC", "Noto Sans Mono CJK JP", "WenQuanYi Zen Hei Mono"]);
322 }
323 fonts.extend_from_slice(&[
324 "Source Code Pro", "Cantarell", "DejaVu Sans Mono",
325 "Roboto Mono", "Ubuntu Monospace", "Droid Sans Mono"
326 ]);
327 fonts.iter().map(|s| s.to_string()).collect()
328 }
329 OperatingSystem::MacOS | OperatingSystem::IOS => {
330 let mut fonts = Vec::new();
331 if has_cjk {
332 fonts.extend_from_slice(&["Hiragino Sans", "PingFang SC"]);
333 }
334 fonts.extend_from_slice(&["SF Mono", "Menlo", "Monaco", "Courier", "Oxygen Mono", "Source Code Pro", "Fira Mono"]);
335 fonts.iter().map(|s| s.to_string()).collect()
336 }
337 OperatingSystem::Android => {
338 let mut fonts = Vec::new();
339 if has_cjk {
340 fonts.extend_from_slice(&["Noto Sans Mono CJK SC", "Noto Sans Mono CJK JP"]);
341 }
342 fonts.extend_from_slice(&["Roboto Mono", "Droid Sans Mono", "Noto Sans Mono", "DejaVu Sans Mono"]);
343 fonts.iter().map(|s| s.to_string()).collect()
344 }
345 OperatingSystem::Wasm => Vec::new(),
346 }
347 }
348
349 pub fn expand_generic_family(&self, family: &str, unicode_ranges: &[UnicodeRange]) -> Vec<String> {
353 match family.to_ascii_lowercase().as_str() {
354 "serif" => self.get_serif_fonts(unicode_ranges),
355 "sans-serif" => self.get_sans_serif_fonts(unicode_ranges),
356 "monospace" => self.get_monospace_fonts(unicode_ranges),
357 "cursive" | "fantasy" | "system-ui" => {
358 self.get_sans_serif_fonts(unicode_ranges)
360 }
361 _ => vec![family.to_string()],
362 }
363 }
364}
365
366pub fn expand_font_families(families: &[String], os: OperatingSystem, unicode_ranges: &[UnicodeRange]) -> Vec<String> {
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 let registered = cache.list();
1169 if registered.len() == 1 {
1170 return Some((registered[0].1, "(web-last-resort)".to_string()));
1171 }
1172
1173 None
1174 }
1175
1176 pub fn resolve_text(&self, cache: &FcFontCache, text: &str) -> Vec<(char, Option<(FontId, String)>)> {
1179 text.chars()
1180 .map(|ch| (ch, self.resolve_char(cache, ch)))
1181 .collect()
1182 }
1183
1184 pub fn query_for_text(&self, cache: &FcFontCache, text: &str) -> Vec<ResolvedFontRun> {
1188 if text.is_empty() {
1189 return Vec::new();
1190 }
1191
1192 let mut runs: Vec<ResolvedFontRun> = Vec::new();
1193 let mut current_font: Option<FontId> = None;
1194 let mut current_css_source: Option<String> = None;
1195 let mut current_start_byte: usize = 0;
1196
1197 for (byte_idx, ch) in text.char_indices() {
1198 let resolved = self.resolve_char(cache, ch);
1199 let (font_id, css_source) = match &resolved {
1200 Some((id, source)) => (Some(*id), Some(source.clone())),
1201 None => (None, None),
1202 };
1203
1204 let font_changed = font_id != current_font;
1206
1207 if font_changed && byte_idx > 0 {
1208 let run_text = &text[current_start_byte..byte_idx];
1210 runs.push(ResolvedFontRun {
1211 text: run_text.to_string(),
1212 start_byte: current_start_byte,
1213 end_byte: byte_idx,
1214 font_id: current_font,
1215 css_source: current_css_source.clone().unwrap_or_default(),
1216 });
1217 current_start_byte = byte_idx;
1218 }
1219
1220 current_font = font_id;
1221 current_css_source = css_source;
1222 }
1223
1224 if current_start_byte < text.len() {
1226 let run_text = &text[current_start_byte..];
1227 runs.push(ResolvedFontRun {
1228 text: run_text.to_string(),
1229 start_byte: current_start_byte,
1230 end_byte: text.len(),
1231 font_id: current_font,
1232 css_source: current_css_source.unwrap_or_default(),
1233 });
1234 }
1235
1236 runs
1237 }
1238}
1239
1240#[derive(Debug, Clone, PartialEq, Eq)]
1242pub struct CssFallbackGroup {
1243 pub css_name: String,
1245
1246 pub fonts: Vec<FontMatch>,
1249}
1250
1251#[cfg(feature = "std")]
1264#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1265pub(crate) struct FontChainCacheKey {
1266 pub(crate) font_families: Vec<String>,
1268 pub(crate) weight: FcWeight,
1270 pub(crate) italic: PatternMatch,
1272 pub(crate) oblique: PatternMatch,
1273 pub(crate) scripts_hint_hash: Option<u64>,
1275}
1276
1277#[cfg(feature = "std")]
1282fn hash_scripts_hint(ranges: &[UnicodeRange]) -> u64 {
1283 let mut sorted: Vec<UnicodeRange> = ranges.to_vec();
1284 sorted.sort();
1285 let mut buf = Vec::with_capacity(sorted.len() * 8);
1286 for r in &sorted {
1287 buf.extend_from_slice(&r.start.to_le_bytes());
1288 buf.extend_from_slice(&r.end.to_le_bytes());
1289 }
1290 crate::utils::content_hash_u64(&buf)
1291}
1292
1293#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq)]
1305#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
1306#[repr(C)]
1307pub struct FcFontPath {
1308 pub path: String,
1309 pub font_index: usize,
1310 #[cfg_attr(feature = "cache", serde(default))]
1312 pub bytes_hash: u64,
1313}
1314
1315#[derive(Debug, Clone, PartialEq, Eq)]
1317#[repr(C)]
1318pub struct FcFont {
1319 pub bytes: Vec<u8>,
1320 pub font_index: usize,
1321 pub id: String, }
1323
1324#[derive(Debug, Clone)]
1336pub enum OwnedFontSource {
1337 Memory(FcFont),
1339 Disk(FcFontPath),
1341}
1342
1343#[cfg(feature = "std")]
1356pub enum FontBytes {
1357 Owned(std::sync::Arc<[u8]>),
1360 #[cfg(not(target_family = "wasm"))]
1364 Mmapped(mmapio::Mmap),
1365}
1366
1367#[cfg(feature = "std")]
1368impl FontBytes {
1369 #[inline]
1371 pub fn as_slice(&self) -> &[u8] {
1372 match self {
1373 FontBytes::Owned(arc) => arc,
1374 #[cfg(not(target_family = "wasm"))]
1375 FontBytes::Mmapped(m) => &m[..],
1376 }
1377 }
1378}
1379
1380#[cfg(feature = "std")]
1381impl core::ops::Deref for FontBytes {
1382 type Target = [u8];
1383 #[inline]
1384 fn deref(&self) -> &[u8] {
1385 self.as_slice()
1386 }
1387}
1388
1389#[cfg(feature = "std")]
1390impl AsRef<[u8]> for FontBytes {
1391 #[inline]
1392 fn as_ref(&self) -> &[u8] {
1393 self.as_slice()
1394 }
1395}
1396
1397#[cfg(feature = "std")]
1398impl core::fmt::Debug for FontBytes {
1399 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1400 let kind = match self {
1401 FontBytes::Owned(_) => "Owned",
1402 #[cfg(not(target_family = "wasm"))]
1403 FontBytes::Mmapped(_) => "Mmapped",
1404 };
1405 write!(f, "FontBytes::{}({} bytes)", kind, self.as_slice().len())
1406 }
1407}
1408
1409#[cfg(feature = "std")]
1413fn open_font_bytes_mmap(path: &str) -> Option<std::sync::Arc<FontBytes>> {
1414 use std::fs::File;
1415 use std::sync::Arc;
1416
1417 #[cfg(not(target_family = "wasm"))]
1418 {
1419 if let Ok(file) = File::open(path) {
1420 if let Ok(mmap) = unsafe { mmapio::MmapOptions::new().map(&file) } {
1425 return Some(Arc::new(FontBytes::Mmapped(mmap)));
1426 }
1427 }
1428 }
1429 let bytes = std::fs::read(path).ok()?;
1430 Some(Arc::new(FontBytes::Owned(Arc::from(bytes))))
1431}
1432
1433#[derive(Debug, Clone)]
1436pub struct NamedFont {
1437 pub name: String,
1439 pub bytes: Vec<u8>,
1441}
1442
1443impl NamedFont {
1444 pub fn new(name: impl Into<String>, bytes: Vec<u8>) -> Self {
1446 Self {
1447 name: name.into(),
1448 bytes,
1449 }
1450 }
1451}
1452
1453pub struct FcFontCache {
1471 pub(crate) shared: std::sync::Arc<FcFontCacheShared>,
1472}
1473
1474#[cfg(not(feature = "single-thread-unsafe-locks"))]
1489pub struct StLock<T> {
1490 lock: std::sync::RwLock<T>,
1491}
1492#[cfg(not(feature = "single-thread-unsafe-locks"))]
1493impl<T> core::fmt::Debug for StLock<T> {
1494 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1495 f.write_str("StLock(..)")
1496 }
1497}
1498#[cfg(not(feature = "single-thread-unsafe-locks"))]
1499impl<T> StLock<T> {
1500 pub fn new(v: T) -> Self {
1501 Self { lock: std::sync::RwLock::new(v) }
1502 }
1503 pub fn read(&self) -> Result<StReadGuard<'_, T>, core::convert::Infallible> {
1504 Ok(StReadGuard { g: self.lock.read().unwrap_or_else(|e| e.into_inner()) })
1505 }
1506 pub fn write(&self) -> Result<StWriteGuard<'_, T>, core::convert::Infallible> {
1507 Ok(StWriteGuard { g: self.lock.write().unwrap_or_else(|e| e.into_inner()) })
1508 }
1509 pub fn lock(&self) -> Result<StWriteGuard<'_, T>, core::convert::Infallible> {
1510 self.write()
1511 }
1512}
1513#[cfg(not(feature = "single-thread-unsafe-locks"))]
1514pub struct StReadGuard<'a, T> {
1515 g: std::sync::RwLockReadGuard<'a, T>,
1516}
1517#[cfg(not(feature = "single-thread-unsafe-locks"))]
1518impl<'a, T> core::ops::Deref for StReadGuard<'a, T> {
1519 type Target = T;
1520 fn deref(&self) -> &T { &self.g }
1521}
1522#[cfg(not(feature = "single-thread-unsafe-locks"))]
1523pub struct StWriteGuard<'a, T> {
1524 g: std::sync::RwLockWriteGuard<'a, T>,
1525}
1526#[cfg(not(feature = "single-thread-unsafe-locks"))]
1527impl<'a, T> core::ops::Deref for StWriteGuard<'a, T> {
1528 type Target = T;
1529 fn deref(&self) -> &T { &self.g }
1530}
1531#[cfg(not(feature = "single-thread-unsafe-locks"))]
1532impl<'a, T> core::ops::DerefMut for StWriteGuard<'a, T> {
1533 fn deref_mut(&mut self) -> &mut T { &mut self.g }
1534}
1535
1536#[cfg(feature = "single-thread-unsafe-locks")]
1537pub struct StLock<T> {
1538 cell: std::cell::UnsafeCell<T>,
1539}
1540#[cfg(feature = "single-thread-unsafe-locks")]
1541unsafe impl<T> Sync for StLock<T> {}
1542#[cfg(feature = "single-thread-unsafe-locks")]
1543unsafe impl<T> Send for StLock<T> {}
1544#[cfg(feature = "single-thread-unsafe-locks")]
1545impl<T> core::fmt::Debug for StLock<T> {
1546 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1547 f.write_str("StLock(..)")
1548 }
1549}
1550#[cfg(feature = "single-thread-unsafe-locks")]
1551impl<T> StLock<T> {
1552 pub fn new(v: T) -> Self {
1553 Self { cell: std::cell::UnsafeCell::new(v) }
1554 }
1555 pub fn read(&self) -> Result<StReadGuard<'_, T>, core::convert::Infallible> {
1556 Ok(StReadGuard { r: unsafe { &*self.cell.get() } })
1557 }
1558 pub fn write(&self) -> Result<StWriteGuard<'_, T>, core::convert::Infallible> {
1559 Ok(StWriteGuard { r: unsafe { &mut *self.cell.get() } })
1560 }
1561 pub fn lock(&self) -> Result<StWriteGuard<'_, T>, core::convert::Infallible> {
1562 Ok(StWriteGuard { r: unsafe { &mut *self.cell.get() } })
1563 }
1564}
1565#[cfg(feature = "single-thread-unsafe-locks")]
1566pub struct StReadGuard<'a, T> {
1567 r: &'a T,
1568}
1569#[cfg(feature = "single-thread-unsafe-locks")]
1570impl<'a, T> core::ops::Deref for StReadGuard<'a, T> {
1571 type Target = T;
1572 fn deref(&self) -> &T { self.r }
1573}
1574#[cfg(feature = "single-thread-unsafe-locks")]
1575pub struct StWriteGuard<'a, T> {
1576 r: &'a mut T,
1577}
1578#[cfg(feature = "single-thread-unsafe-locks")]
1579impl<'a, T> core::ops::Deref for StWriteGuard<'a, T> {
1580 type Target = T;
1581 fn deref(&self) -> &T { self.r }
1582}
1583#[cfg(feature = "single-thread-unsafe-locks")]
1584impl<'a, T> core::ops::DerefMut for StWriteGuard<'a, T> {
1585 fn deref_mut(&mut self) -> &mut T { self.r }
1586}
1587
1588pub(crate) struct FcFontCacheShared {
1589 pub(crate) state: StLock<FcFontCacheInner>,
1593 pub(crate) chain_cache: StLock<std::collections::HashMap<FontChainCacheKey, FontFallbackChain>>,
1597 pub(crate) shared_bytes: StLock<std::collections::HashMap<u64, std::sync::Weak<FontBytes>>>,
1606}
1607
1608#[derive(Default, Debug)]
1612pub(crate) struct FcFontCacheInner {
1613 pub(crate) patterns: BTreeMap<FcPattern, FontId>,
1615 pub(crate) disk_fonts: BTreeMap<FontId, FcFontPath>,
1617 pub(crate) memory_fonts: BTreeMap<FontId, FcFont>,
1619 pub(crate) metadata: BTreeMap<FontId, FcPattern>,
1621 pub(crate) token_index: BTreeMap<String, alloc::collections::BTreeSet<FontId>>,
1624 pub(crate) font_tokens: BTreeMap<FontId, Vec<String>>,
1627}
1628
1629impl FcFontCacheInner {
1630 pub(crate) fn index_pattern_tokens(&mut self, _pattern: &FcPattern, _id: FontId) {
1633 }
1641}
1642
1643impl Clone for FcFontCache {
1644 fn clone(&self) -> Self {
1651 Self {
1652 shared: std::sync::Arc::clone(&self.shared),
1653 }
1654 }
1655}
1656
1657impl core::fmt::Debug for FcFontCache {
1658 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1659 let state = self.state_read();
1660 f.debug_struct("FcFontCache")
1661 .field("patterns_len", &state.patterns.len())
1662 .field("metadata_len", &state.metadata.len())
1663 .field("disk_fonts_len", &state.disk_fonts.len())
1664 .field("memory_fonts_len", &state.memory_fonts.len())
1665 .finish()
1666 }
1667}
1668
1669impl Default for FcFontCache {
1670 fn default() -> Self {
1671 Self {
1672 shared: std::sync::Arc::new(FcFontCacheShared {
1673 state: StLock::new(FcFontCacheInner::default()),
1674 chain_cache: StLock::new(std::collections::HashMap::new()),
1675 shared_bytes: StLock::new(std::collections::HashMap::new()),
1676 }),
1677 }
1678 }
1679}
1680
1681impl FcFontCache {
1682 #[inline]
1686 pub(crate) fn state_read(
1687 &self,
1688 ) -> StReadGuard<'_, FcFontCacheInner> {
1689 match self.shared.state.read() {
1691 Ok(g) => g,
1692 Err(e) => match e {},
1693 }
1694 }
1695
1696 #[inline]
1699 pub(crate) fn state_write(
1700 &self,
1701 ) -> StWriteGuard<'_, FcFontCacheInner> {
1702 match self.shared.state.write() {
1704 Ok(g) => g,
1705 Err(e) => match e {},
1706 }
1707 }
1708
1709 pub fn with_memory_fonts(&self, fonts: Vec<(FcPattern, FcFont)>) -> &Self {
1714 let fonts: Vec<(FcPattern, FcFont)> = fonts
1718 .into_iter()
1719 .map(|(pattern, font)| (Self::populate_memory_font_ranges(pattern, &font), font))
1720 .collect();
1721 let mut state = self.state_write();
1722 for (pattern, font) in fonts {
1723 let id = FontId::new();
1724 state.patterns.insert(pattern.clone(), id);
1725 state.metadata.insert(id, pattern.clone());
1726 state.memory_fonts.insert(id, font);
1727 state.index_pattern_tokens(&pattern, id);
1728 }
1729 self
1730 }
1731
1732 pub fn with_memory_font_with_id(
1734 &self,
1735 id: FontId,
1736 pattern: FcPattern,
1737 font: FcFont,
1738 ) -> &Self {
1739 let pattern = Self::populate_memory_font_ranges(pattern, &font);
1740 let mut state = self.state_write();
1741 state.patterns.insert(pattern.clone(), id);
1742 state.metadata.insert(id, pattern.clone());
1743 state.memory_fonts.insert(id, font);
1744 state.index_pattern_tokens(&pattern, id);
1745 self
1746 }
1747
1748 #[cfg(all(feature = "std", feature = "parsing"))]
1764 fn populate_memory_font_ranges(mut pattern: FcPattern, font: &FcFont) -> FcPattern {
1765 if !pattern.unicode_ranges.is_empty() {
1766 return pattern;
1767 }
1768 if let Some(faces) = FcParseFontBytes(&font.bytes, &font.id) {
1769 let ranges = faces
1773 .iter()
1774 .find(|(_, f)| f.font_index == font.font_index)
1775 .or_else(|| faces.first())
1776 .map(|(p, _)| p.unicode_ranges.clone())
1777 .unwrap_or_default();
1778 if !ranges.is_empty() {
1779 pattern.unicode_ranges = ranges;
1780 }
1781 }
1782 pattern
1783 }
1784
1785 #[cfg(not(all(feature = "std", feature = "parsing")))]
1788 fn populate_memory_font_ranges(pattern: FcPattern, _font: &FcFont) -> FcPattern {
1789 pattern
1790 }
1791
1792 pub fn insert_builder_font(&self, pattern: FcPattern, path: FcFontPath) {
1798 let id = FontId::new();
1799 {
1800 let mut state = self.state_write();
1801 state.index_pattern_tokens(&pattern, id);
1802 state.patterns.insert(pattern.clone(), id);
1803 state.disk_fonts.insert(id, path);
1804 state.metadata.insert(id, pattern);
1805 }
1806 if let Ok(mut cc) = self.shared.chain_cache.lock() {
1810 cc.clear();
1811 }
1812 }
1813
1814 #[cfg(feature = "std")]
1815 #[doc(hidden)]
1816 pub fn chain_cache_len(&self) -> usize {
1817 self.shared.chain_cache.lock().map(|c| c.len()).unwrap_or(0)
1818 }
1819
1820 pub fn insert_fast_pattern(&self, pattern: FcPattern, path: FcFontPath) -> FontId {
1828 let id = FontId::new();
1829 let mut state = self.state_write();
1830 state.patterns.insert(pattern.clone(), id);
1831 state.disk_fonts.insert(id, path);
1832 state.metadata.insert(id, pattern);
1833 id
1834 }
1835
1836 pub fn lookup_paths_cached(&self, path: &str) -> Option<Vec<FontId>> {
1844 let state = self.state_read();
1845 let mut out = Vec::new();
1846 for (id, font_path) in &state.disk_fonts {
1847 if font_path.path == path {
1848 out.push(*id);
1849 }
1850 }
1851 if out.is_empty() { None } else { Some(out) }
1852 }
1853
1854 pub fn get_font_by_id(&self, id: &FontId) -> Option<OwnedFontSource> {
1861 let state = self.state_read();
1862 if let Some(font) = state.memory_fonts.get(id) {
1863 return Some(OwnedFontSource::Memory(font.clone()));
1864 }
1865 if let Some(path) = state.disk_fonts.get(id) {
1866 return Some(OwnedFontSource::Disk(path.clone()));
1867 }
1868 None
1869 }
1870
1871 pub fn get_metadata_by_id(&self, id: &FontId) -> Option<FcPattern> {
1875 self.state_read().metadata.get(id).cloned()
1876 }
1877
1878 #[cfg(feature = "std")]
1906 pub fn get_font_bytes(&self, id: &FontId) -> Option<std::sync::Arc<FontBytes>> {
1907 use std::sync::Arc;
1908 match self.get_font_by_id(id)? {
1909 OwnedFontSource::Memory(font) => Some(Arc::new(FontBytes::Owned(
1910 Arc::from(font.bytes.as_slice()),
1911 ))),
1912 OwnedFontSource::Disk(path) => {
1913 let hash = path.bytes_hash;
1914 if hash != 0 {
1915 if let Ok(guard) = self.shared.shared_bytes.lock() {
1916 if let Some(weak) = guard.get(&hash) {
1917 if let Some(arc) = weak.upgrade() {
1918 return Some(arc);
1919 }
1920 }
1921 }
1922 }
1923
1924 let arc = open_font_bytes_mmap(&path.path)?;
1925 if hash != 0 {
1926 if let Ok(mut guard) = self.shared.shared_bytes.lock() {
1927 guard.insert(hash, Arc::downgrade(&arc));
1929 }
1930 }
1931 Some(arc)
1932 }
1933 }
1934 }
1935
1936 #[cfg(not(feature = "std"))]
1938 pub fn build() -> Self { Self::default() }
1939
1940 #[cfg(all(feature = "std", not(feature = "parsing")))]
1942 pub fn build() -> Self { Self::build_from_filenames() }
1943
1944 #[cfg(all(feature = "std", feature = "parsing"))]
1946 pub fn build() -> Self { Self::build_inner(None) }
1947
1948 #[cfg(all(feature = "std", not(feature = "parsing")))]
1951 fn build_from_filenames() -> Self {
1952 let cache = Self::default();
1953 {
1954 let mut state = cache.state_write();
1955 for dir in crate::config::font_directories(OperatingSystem::current()) {
1956 for path in FcCollectFontFilesRecursive(dir) {
1957 let pattern = match pattern_from_filename(&path) {
1958 Some(p) => p,
1959 None => continue,
1960 };
1961 let id = FontId::new();
1962 state.disk_fonts.insert(id, FcFontPath {
1963 path: path.to_string_lossy().to_string(),
1964 font_index: 0,
1965 bytes_hash: 0,
1968 });
1969 state.index_pattern_tokens(&pattern, id);
1970 state.metadata.insert(id, pattern.clone());
1971 state.patterns.insert(pattern, id);
1972 }
1973 }
1974 }
1975 cache
1976 }
1977
1978 #[cfg(all(feature = "std", feature = "parsing"))]
2003 pub fn build_with_families(families: &[impl AsRef<str>]) -> Self {
2004 let os = OperatingSystem::current();
2006 let mut target_families: Vec<String> = Vec::new();
2007
2008 for family in families {
2009 let family_str = family.as_ref();
2010 let expanded = os.expand_generic_family(family_str, &[]);
2011 if expanded.is_empty() || (expanded.len() == 1 && expanded[0] == family_str) {
2012 target_families.push(family_str.to_string());
2013 } else {
2014 target_families.extend(expanded);
2015 }
2016 }
2017
2018 Self::build_inner(Some(&target_families))
2019 }
2020
2021 #[cfg(all(feature = "std", feature = "parsing"))]
2027 fn build_inner(family_filter: Option<&[String]>) -> Self {
2028 let cache = FcFontCache::default();
2029
2030 let filter_normalized: Option<Vec<String>> = family_filter.map(|families| {
2032 families
2033 .iter()
2034 .map(|f| crate::utils::normalize_family_name(f))
2035 .collect()
2036 });
2037
2038 let matches_filter = |pattern: &FcPattern| -> bool {
2040 match &filter_normalized {
2041 None => true, Some(targets) => {
2043 pattern.name.as_ref().map_or(false, |name| {
2044 let name_norm = crate::utils::normalize_family_name(name);
2045 targets.iter().any(|target| name_norm.contains(target))
2046 }) || pattern.family.as_ref().map_or(false, |family| {
2047 let family_norm = crate::utils::normalize_family_name(family);
2048 targets.iter().any(|target| family_norm.contains(target))
2049 })
2050 }
2051 }
2052 };
2053
2054 let mut state = cache.state_write();
2055
2056 #[cfg(target_os = "linux")]
2057 {
2058 if let Some((font_entries, render_configs)) = FcScanDirectories() {
2059 for (mut pattern, path) in font_entries {
2060 if matches_filter(&pattern) {
2061 if let Some(family) = pattern.name.as_ref().or(pattern.family.as_ref()) {
2063 if let Some(rc) = render_configs.get(family) {
2064 pattern.render_config = rc.clone();
2065 }
2066 }
2067 let id = FontId::new();
2068 state.patterns.insert(pattern.clone(), id);
2069 state.metadata.insert(id, pattern.clone());
2070 state.disk_fonts.insert(id, path);
2071 state.index_pattern_tokens(&pattern, id);
2072 }
2073 }
2074 }
2075 }
2076
2077 #[cfg(target_os = "windows")]
2078 {
2079 let system_root = std::env::var("SystemRoot")
2080 .or_else(|_| std::env::var("WINDIR"))
2081 .unwrap_or_else(|_| "C:\\Windows".to_string());
2082
2083 let user_profile = std::env::var("USERPROFILE")
2084 .unwrap_or_else(|_| "C:\\Users\\Default".to_string());
2085
2086 let font_dirs = vec![
2087 (None, format!("{}\\Fonts\\", system_root)),
2088 (None, format!("{}\\AppData\\Local\\Microsoft\\Windows\\Fonts\\", user_profile)),
2089 ];
2090
2091 let font_entries = FcScanDirectoriesInner(&font_dirs);
2092 for (pattern, path) in font_entries {
2093 if matches_filter(&pattern) {
2094 let id = FontId::new();
2095 state.patterns.insert(pattern.clone(), id);
2096 state.metadata.insert(id, pattern.clone());
2097 state.disk_fonts.insert(id, path);
2098 state.index_pattern_tokens(&pattern, id);
2099 }
2100 }
2101 }
2102
2103 #[cfg(target_os = "macos")]
2104 {
2105 let font_dirs = vec![
2106 (None, "~/Library/Fonts".to_owned()),
2107 (None, "/System/Library/Fonts".to_owned()),
2108 (None, "/Library/Fonts".to_owned()),
2109 (None, "/System/Library/AssetsV2".to_owned()),
2110 ];
2111
2112 let font_entries = FcScanDirectoriesInner(&font_dirs);
2113 for (pattern, path) in font_entries {
2114 if matches_filter(&pattern) {
2115 let id = FontId::new();
2116 state.patterns.insert(pattern.clone(), id);
2117 state.metadata.insert(id, pattern.clone());
2118 state.disk_fonts.insert(id, path);
2119 state.index_pattern_tokens(&pattern, id);
2120 }
2121 }
2122 }
2123
2124 #[cfg(target_os = "ios")]
2129 {
2130 let font_files = crate::mobile_ios::copy_available_font_urls();
2131 let font_entries = FcParseFontFiles(&font_files);
2132 for (pattern, path) in font_entries {
2133 if matches_filter(&pattern) {
2134 let id = FontId::new();
2135 state.patterns.insert(pattern.clone(), id);
2136 state.metadata.insert(id, pattern.clone());
2137 state.disk_fonts.insert(id, path);
2138 state.index_pattern_tokens(&pattern, id);
2139 }
2140 }
2141 }
2142
2143 #[cfg(target_os = "android")]
2148 {
2149 let font_dirs = vec![
2150 (None, "/system/fonts".to_owned()),
2151 (None, "/product/fonts".to_owned()),
2152 (None, "/system_ext/fonts".to_owned()),
2153 (None, "/data/fonts".to_owned()),
2154 ];
2155
2156 let font_entries = FcScanDirectoriesInner(&font_dirs);
2157 for (pattern, path) in font_entries {
2158 if matches_filter(&pattern) {
2159 let id = FontId::new();
2160 state.patterns.insert(pattern.clone(), id);
2161 state.metadata.insert(id, pattern.clone());
2162 state.disk_fonts.insert(id, path);
2163 state.index_pattern_tokens(&pattern, id);
2164 }
2165 }
2166 }
2167
2168 drop(state);
2169 cache
2170 }
2171
2172 pub fn is_memory_font(&self, id: &FontId) -> bool {
2174 self.state_read().memory_fonts.contains_key(id)
2175 }
2176
2177 pub fn list(&self) -> Vec<(FcPattern, FontId)> {
2184 self.state_read()
2185 .patterns
2186 .iter()
2187 .map(|(pattern, id)| (pattern.clone(), *id))
2188 .collect()
2189 }
2190
2191 pub fn for_each_pattern<F: FnMut(&FcPattern, &FontId)>(&self, mut f: F) {
2195 let state = self.state_read();
2196 for (pattern, id) in &state.patterns {
2197 f(pattern, id);
2198 }
2199 }
2200
2201 pub fn is_empty(&self) -> bool {
2203 self.state_read().patterns.is_empty()
2204 }
2205
2206 pub fn len(&self) -> usize {
2208 self.state_read().patterns.len()
2209 }
2210
2211 pub fn query_with_fallback(
2238 &self,
2239 pattern: &FcPattern,
2240 trace: &mut Vec<TraceMsg>,
2241 ) -> Option<FontMatch> {
2242 if let Some(m) = self.query(pattern, trace) {
2243 return Some(m);
2244 }
2245
2246 if pattern.name.is_some() || pattern.family.is_some() {
2248 let relaxed = FcPattern {
2249 name: None,
2250 family: None,
2251 ..pattern.clone()
2252 };
2253 if let Some(m) = self.query(&relaxed, trace) {
2254 return Some(m);
2255 }
2256 }
2257
2258 let bare = FcPattern {
2260 unicode_ranges: pattern.unicode_ranges.clone(),
2261 ..FcPattern::default()
2262 };
2263 self.query(&bare, trace)
2264 }
2265
2266 pub fn query(&self, pattern: &FcPattern, trace: &mut Vec<TraceMsg>) -> Option<FontMatch> {
2272 let state = self.state_read();
2273 let mut matches = Vec::new();
2274
2275 for (stored_pattern, id) in &state.patterns {
2276 if Self::query_matches_internal(stored_pattern, pattern, trace) {
2277 let metadata = state.metadata.get(id).unwrap_or(stored_pattern);
2278
2279 let unicode_compatibility = if pattern.unicode_ranges.is_empty() {
2281 Self::calculate_unicode_coverage(&metadata.unicode_ranges) as i32
2283 } else {
2284 Self::calculate_unicode_compatibility(&pattern.unicode_ranges, &metadata.unicode_ranges)
2286 };
2287
2288 let style_score = Self::calculate_style_score(pattern, metadata);
2289
2290 let is_memory = state.memory_fonts.contains_key(id);
2292
2293 matches.push((*id, unicode_compatibility, style_score, metadata.clone(), is_memory));
2294 }
2295 }
2296
2297 matches.sort_by(|a, b| {
2299 b.4.cmp(&a.4)
2301 .then_with(|| b.1.cmp(&a.1)) .then_with(|| a.2.cmp(&b.2)) });
2304
2305 matches.first().map(|(id, _, _, metadata, _)| {
2306 FontMatch {
2307 id: *id,
2308 unicode_ranges: metadata.unicode_ranges.clone(),
2309 fallbacks: Vec::new(), }
2311 })
2312 }
2313
2314 fn query_internal(&self, pattern: &FcPattern, trace: &mut Vec<TraceMsg>) -> Vec<FontMatch> {
2319 let state = self.state_read();
2320 self.query_internal_locked(&state, pattern, trace)
2321 }
2322
2323 fn query_internal_locked(
2326 &self,
2327 state: &FcFontCacheInner,
2328 pattern: &FcPattern,
2329 trace: &mut Vec<TraceMsg>,
2330 ) -> Vec<FontMatch> {
2331 let mut matches = Vec::new();
2332
2333 for (stored_pattern, id) in &state.patterns {
2334 if Self::query_matches_internal(stored_pattern, pattern, trace) {
2335 let metadata = state.metadata.get(id).unwrap_or(stored_pattern);
2336
2337 let unicode_compatibility = if pattern.unicode_ranges.is_empty() {
2339 Self::calculate_unicode_coverage(&metadata.unicode_ranges) as i32
2340 } else {
2341 Self::calculate_unicode_compatibility(&pattern.unicode_ranges, &metadata.unicode_ranges)
2342 };
2343
2344 let style_score = Self::calculate_style_score(pattern, metadata);
2345 matches.push((*id, unicode_compatibility, style_score, metadata.clone()));
2346 }
2347 }
2348
2349 matches.sort_by(|a, b| {
2353 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)) });
2358
2359 matches
2360 .into_iter()
2361 .map(|(id, _, _, metadata)| {
2362 FontMatch {
2363 id,
2364 unicode_ranges: metadata.unicode_ranges.clone(),
2365 fallbacks: Vec::new(), }
2367 })
2368 .collect()
2369 }
2370
2371 pub fn compute_fallbacks(
2375 &self,
2376 font_id: &FontId,
2377 trace: &mut Vec<TraceMsg>,
2378 ) -> Vec<FontMatchNoFallback> {
2379 let state = self.state_read();
2380 let pattern = match state.metadata.get(font_id) {
2381 Some(p) => p.clone(),
2382 None => return Vec::new(),
2383 };
2384 drop(state);
2385
2386 self.compute_fallbacks_for_pattern(&pattern, Some(font_id), trace)
2387 }
2388
2389 fn compute_fallbacks_for_pattern(
2390 &self,
2391 pattern: &FcPattern,
2392 exclude_id: Option<&FontId>,
2393 _trace: &mut Vec<TraceMsg>,
2394 ) -> Vec<FontMatchNoFallback> {
2395 let state = self.state_read();
2396 let mut candidates = Vec::new();
2397
2398 for (stored_pattern, id) in &state.patterns {
2400 if exclude_id.is_some() && exclude_id.unwrap() == id {
2402 continue;
2403 }
2404
2405 if !stored_pattern.unicode_ranges.is_empty() && !pattern.unicode_ranges.is_empty() {
2407 let unicode_compatibility = Self::calculate_unicode_compatibility(
2409 &pattern.unicode_ranges,
2410 &stored_pattern.unicode_ranges
2411 );
2412
2413 if unicode_compatibility > 0 {
2415 let style_score = Self::calculate_style_score(pattern, stored_pattern);
2416 candidates.push((
2417 FontMatchNoFallback {
2418 id: *id,
2419 unicode_ranges: stored_pattern.unicode_ranges.clone(),
2420 },
2421 unicode_compatibility,
2422 style_score,
2423 stored_pattern.clone(),
2424 ));
2425 }
2426 } else if pattern.unicode_ranges.is_empty() && !stored_pattern.unicode_ranges.is_empty() {
2427 let coverage = Self::calculate_unicode_coverage(&stored_pattern.unicode_ranges) as i32;
2429 let style_score = Self::calculate_style_score(pattern, stored_pattern);
2430 candidates.push((
2431 FontMatchNoFallback {
2432 id: *id,
2433 unicode_ranges: stored_pattern.unicode_ranges.clone(),
2434 },
2435 coverage,
2436 style_score,
2437 stored_pattern.clone(),
2438 ));
2439 }
2440 }
2441
2442 drop(state);
2443
2444 candidates.sort_by(|a, b| {
2446 b.1.cmp(&a.1)
2447 .then_with(|| a.2.cmp(&b.2))
2448 });
2449
2450 let mut seen_ranges = Vec::new();
2452 let mut deduplicated = Vec::new();
2453
2454 for (id, _, _, pattern) in candidates {
2455 let mut is_new_range = false;
2456
2457 for range in &pattern.unicode_ranges {
2458 if !seen_ranges.iter().any(|r: &UnicodeRange| r.overlaps(range)) {
2459 seen_ranges.push(*range);
2460 is_new_range = true;
2461 }
2462 }
2463
2464 if is_new_range {
2465 deduplicated.push(id);
2466 }
2467 }
2468
2469 deduplicated
2470 }
2471
2472 pub fn get_memory_font(&self, id: &FontId) -> Option<FcFont> {
2474 self.state_read().memory_fonts.get(id).cloned()
2475 }
2476
2477 fn trace_path(k: &FcPattern) -> String {
2479 k.name.as_ref().cloned().unwrap_or_else(|| "<unknown>".to_string())
2480 }
2481
2482 pub fn query_matches_internal(
2483 k: &FcPattern,
2484 pattern: &FcPattern,
2485 trace: &mut Vec<TraceMsg>,
2486 ) -> bool {
2487 if let Some(ref name) = pattern.name {
2489 if !k.name.as_ref().map_or(false, |kn| kn.contains(name)) {
2490 trace.push(TraceMsg {
2491 level: TraceLevel::Info,
2492 path: Self::trace_path(k),
2493 reason: MatchReason::NameMismatch {
2494 requested: pattern.name.clone(),
2495 found: k.name.clone(),
2496 },
2497 });
2498 return false;
2499 }
2500 }
2501
2502 if let Some(ref family) = pattern.family {
2504 if !k.family.as_ref().map_or(false, |kf| kf.contains(family)) {
2505 trace.push(TraceMsg {
2506 level: TraceLevel::Info,
2507 path: Self::trace_path(k),
2508 reason: MatchReason::FamilyMismatch {
2509 requested: pattern.family.clone(),
2510 found: k.family.clone(),
2511 },
2512 });
2513 return false;
2514 }
2515 }
2516
2517 let style_properties = [
2519 (
2520 "italic",
2521 pattern.italic.needs_to_match(),
2522 pattern.italic.matches(&k.italic),
2523 ),
2524 (
2525 "oblique",
2526 pattern.oblique.needs_to_match(),
2527 pattern.oblique.matches(&k.oblique),
2528 ),
2529 (
2530 "bold",
2531 pattern.bold.needs_to_match(),
2532 pattern.bold.matches(&k.bold),
2533 ),
2534 (
2535 "monospace",
2536 pattern.monospace.needs_to_match(),
2537 pattern.monospace.matches(&k.monospace),
2538 ),
2539 (
2540 "condensed",
2541 pattern.condensed.needs_to_match(),
2542 pattern.condensed.matches(&k.condensed),
2543 ),
2544 ];
2545
2546 for (property_name, needs_to_match, matches) in style_properties {
2547 if needs_to_match && !matches {
2548 let (requested, found) = match property_name {
2549 "italic" => (format!("{:?}", pattern.italic), format!("{:?}", k.italic)),
2550 "oblique" => (format!("{:?}", pattern.oblique), format!("{:?}", k.oblique)),
2551 "bold" => (format!("{:?}", pattern.bold), format!("{:?}", k.bold)),
2552 "monospace" => (
2553 format!("{:?}", pattern.monospace),
2554 format!("{:?}", k.monospace),
2555 ),
2556 "condensed" => (
2557 format!("{:?}", pattern.condensed),
2558 format!("{:?}", k.condensed),
2559 ),
2560 _ => (String::new(), String::new()),
2561 };
2562
2563 trace.push(TraceMsg {
2564 level: TraceLevel::Info,
2565 path: Self::trace_path(k),
2566 reason: MatchReason::StyleMismatch {
2567 property: property_name,
2568 requested,
2569 found,
2570 },
2571 });
2572 return false;
2573 }
2574 }
2575
2576 if pattern.weight != FcWeight::Normal && pattern.weight != k.weight {
2578 trace.push(TraceMsg {
2579 level: TraceLevel::Info,
2580 path: Self::trace_path(k),
2581 reason: MatchReason::WeightMismatch {
2582 requested: pattern.weight,
2583 found: k.weight,
2584 },
2585 });
2586 return false;
2587 }
2588
2589 if pattern.stretch != FcStretch::Normal && pattern.stretch != k.stretch {
2591 trace.push(TraceMsg {
2592 level: TraceLevel::Info,
2593 path: Self::trace_path(k),
2594 reason: MatchReason::StretchMismatch {
2595 requested: pattern.stretch,
2596 found: k.stretch,
2597 },
2598 });
2599 return false;
2600 }
2601
2602 if !pattern.unicode_ranges.is_empty() {
2604 let mut has_overlap = false;
2605
2606 for p_range in &pattern.unicode_ranges {
2607 for k_range in &k.unicode_ranges {
2608 if p_range.overlaps(k_range) {
2609 has_overlap = true;
2610 break;
2611 }
2612 }
2613 if has_overlap {
2614 break;
2615 }
2616 }
2617
2618 if !has_overlap {
2619 trace.push(TraceMsg {
2620 level: TraceLevel::Info,
2621 path: Self::trace_path(k),
2622 reason: MatchReason::UnicodeRangeMismatch {
2623 character: '\0', ranges: k.unicode_ranges.clone(),
2625 },
2626 });
2627 return false;
2628 }
2629 }
2630
2631 true
2632 }
2633
2634 #[cfg(feature = "std")]
2660 pub fn resolve_font_chain(
2661 &self,
2662 font_families: &[String],
2663 weight: FcWeight,
2664 italic: PatternMatch,
2665 oblique: PatternMatch,
2666 trace: &mut Vec<TraceMsg>,
2667 ) -> FontFallbackChain {
2668 self.resolve_font_chain_with_os(font_families, weight, italic, oblique, trace, OperatingSystem::current())
2669 }
2670
2671 #[cfg(feature = "std")]
2673 pub fn resolve_font_chain_with_os(
2674 &self,
2675 font_families: &[String],
2676 weight: FcWeight,
2677 italic: PatternMatch,
2678 oblique: PatternMatch,
2679 trace: &mut Vec<TraceMsg>,
2680 os: OperatingSystem,
2681 ) -> FontFallbackChain {
2682 self.resolve_font_chain_impl(font_families, weight, italic, oblique, None, trace, os)
2683 }
2684
2685 #[cfg(feature = "std")]
2700 pub fn resolve_font_chain_with_scripts(
2701 &self,
2702 font_families: &[String],
2703 weight: FcWeight,
2704 italic: PatternMatch,
2705 oblique: PatternMatch,
2706 scripts_hint: Option<&[UnicodeRange]>,
2707 trace: &mut Vec<TraceMsg>,
2708 ) -> FontFallbackChain {
2709 self.resolve_font_chain_impl(
2710 font_families, weight, italic, oblique, scripts_hint,
2711 trace, OperatingSystem::current(),
2712 )
2713 }
2714
2715 #[cfg(feature = "std")]
2719 fn resolve_font_chain_impl(
2720 &self,
2721 font_families: &[String],
2722 weight: FcWeight,
2723 italic: PatternMatch,
2724 oblique: PatternMatch,
2725 scripts_hint: Option<&[UnicodeRange]>,
2726 trace: &mut Vec<TraceMsg>,
2727 os: OperatingSystem,
2728 ) -> FontFallbackChain {
2729 let scripts_hint_hash = scripts_hint.map(hash_scripts_hint);
2733 let cache_key = FontChainCacheKey {
2734 font_families: font_families.to_vec(),
2735 weight,
2736 italic,
2737 oblique,
2738 scripts_hint_hash,
2739 };
2740
2741 if let Some(cached) = self
2742 .shared
2743 .chain_cache
2744 .lock()
2745 .ok()
2746 .and_then(|c| c.get(&cache_key).cloned())
2747 {
2748 return cached;
2749 }
2750
2751 let expanded_families = expand_font_families(font_families, os, &[]);
2753
2754 let generic_fallbacks: Vec<String> = font_families
2762 .iter()
2763 .filter(|f| config::is_generic_family(f))
2764 .cloned()
2765 .collect();
2766
2767 let chain = self.resolve_font_chain_uncached(
2769 &expanded_families,
2770 &generic_fallbacks,
2771 weight,
2772 italic,
2773 oblique,
2774 scripts_hint,
2775 trace,
2776 );
2777
2778 if let Ok(mut cache) = self.shared.chain_cache.lock() {
2780 cache.insert(cache_key, chain.clone());
2781 }
2782
2783 chain
2784 }
2785
2786 #[cfg(feature = "std")]
2794 fn resolve_font_chain_uncached(
2795 &self,
2796 font_families: &[String],
2797 generic_fallbacks: &[String],
2798 weight: FcWeight,
2799 italic: PatternMatch,
2800 oblique: PatternMatch,
2801 scripts_hint: Option<&[UnicodeRange]>,
2802 trace: &mut Vec<TraceMsg>,
2803 ) -> FontFallbackChain {
2804 let mut css_fallbacks = Vec::new();
2805
2806 for (_i, family) in font_families.iter().enumerate() {
2808 let (pattern, is_generic) = if config::is_generic_family(family) {
2810 let monospace = if family.eq_ignore_ascii_case("monospace") {
2811 PatternMatch::True
2812 } else {
2813 PatternMatch::False
2814 };
2815 let pattern = FcPattern {
2816 name: None,
2817 weight,
2818 italic,
2819 oblique,
2820 monospace,
2821 unicode_ranges: Vec::new(),
2822 ..Default::default()
2823 };
2824 (pattern, true)
2825 } else {
2826 let pattern = FcPattern {
2828 name: Some(family.clone()),
2829 weight,
2830 italic,
2831 oblique,
2832 unicode_ranges: Vec::new(),
2833 ..Default::default()
2834 };
2835 (pattern, false)
2836 };
2837
2838 let mut matches = if is_generic {
2841 self.query_internal(&pattern, trace)
2843 } else {
2844 let mut m = self.fuzzy_query_by_name(family, weight, italic, oblique, &[], trace);
2846 if m.is_empty() {
2859 m = self.query_by_family_normalized(family, weight, italic, oblique);
2860 }
2861 m
2862 };
2863
2864 if is_generic && matches.len() > 5 {
2866 matches.truncate(5);
2867 }
2868
2869 css_fallbacks.push(CssFallbackGroup {
2872 css_name: family.clone(),
2873 fonts: matches,
2874 });
2875 }
2876
2877 if !generic_fallbacks.is_empty()
2894 && css_fallbacks.iter().all(|g| g.fonts.is_empty())
2895 {
2896 for generic in generic_fallbacks {
2897 let monospace = if generic.eq_ignore_ascii_case("monospace") {
2898 PatternMatch::True
2899 } else {
2900 PatternMatch::False
2901 };
2902 let pattern = FcPattern {
2903 name: None,
2904 weight,
2905 italic,
2906 oblique,
2907 monospace,
2908 unicode_ranges: Vec::new(),
2909 ..Default::default()
2910 };
2911 let mut matches = self.query_internal(&pattern, trace);
2912 if matches.len() > 5 {
2913 matches.truncate(5);
2914 }
2915 if !matches.is_empty() {
2916 css_fallbacks.push(CssFallbackGroup {
2917 css_name: generic.clone(),
2918 fonts: matches,
2919 });
2920 }
2921 }
2922 }
2923
2924 let important_ranges: &[UnicodeRange] =
2937 scripts_hint.unwrap_or(DEFAULT_UNICODE_FALLBACK_SCRIPTS);
2938 let unicode_fallbacks = if important_ranges.is_empty() {
2939 Vec::new()
2940 } else {
2941 let all_uncovered = vec![false; important_ranges.len()];
2942 self.find_unicode_fallbacks(
2943 important_ranges,
2944 &all_uncovered,
2945 &css_fallbacks,
2946 weight,
2947 italic,
2948 oblique,
2949 trace,
2950 )
2951 };
2952
2953 let mut unicode_fallbacks = unicode_fallbacks;
2961 if css_fallbacks.is_empty() && unicode_fallbacks.is_empty() {
2962 let st = self.state_read();
2963 if let Some((pat, id)) = st.patterns.iter().next() {
2964 unicode_fallbacks.push(FontMatch {
2965 id: *id,
2966 unicode_ranges: pat.unicode_ranges.clone(),
2967 fallbacks: Vec::new(),
2968 });
2969 }
2970 }
2971
2972 FontFallbackChain {
2973 css_fallbacks,
2974 unicode_fallbacks,
2975 original_stack: font_families.to_vec(),
2976 }
2977 }
2978
2979 #[allow(dead_code)]
2981 fn extract_unicode_ranges(text: &str) -> Vec<UnicodeRange> {
2982 let mut chars: Vec<char> = text.chars().collect();
2983 chars.sort_unstable();
2984 chars.dedup();
2985
2986 if chars.is_empty() {
2987 return Vec::new();
2988 }
2989
2990 let mut ranges = Vec::new();
2991 let mut range_start = chars[0] as u32;
2992 let mut range_end = range_start;
2993
2994 for &c in &chars[1..] {
2995 let codepoint = c as u32;
2996 if codepoint == range_end + 1 {
2997 range_end = codepoint;
2998 } else {
2999 ranges.push(UnicodeRange { start: range_start, end: range_end });
3000 range_start = codepoint;
3001 range_end = codepoint;
3002 }
3003 }
3004
3005 ranges.push(UnicodeRange { start: range_start, end: range_end });
3006 ranges
3007 }
3008
3009 #[cfg(feature = "std")]
3016 fn fuzzy_query_by_name(
3017 &self,
3018 requested_name: &str,
3019 weight: FcWeight,
3020 italic: PatternMatch,
3021 oblique: PatternMatch,
3022 unicode_ranges: &[UnicodeRange],
3023 _trace: &mut Vec<TraceMsg>,
3024 ) -> Vec<FontMatch> {
3025 let tokens = Self::extract_font_name_tokens(requested_name);
3027
3028 if tokens.is_empty() {
3029 return Vec::new();
3030 }
3031
3032 let tokens_lower: Vec<String> = tokens.iter().map(|t| t.to_ascii_lowercase()).collect();
3034
3035 let state = self.state_read();
3041
3042 let first_token = &tokens_lower[0];
3044 let mut candidate_ids = match state.token_index.get(first_token) {
3045 Some(ids) if !ids.is_empty() => ids.clone(),
3046 _ => {
3047 return Vec::new();
3049 }
3050 };
3051
3052 for token in &tokens_lower[1..] {
3054 if let Some(token_ids) = state.token_index.get(token) {
3055 let intersection: alloc::collections::BTreeSet<FontId> =
3057 candidate_ids.intersection(token_ids).copied().collect();
3058
3059 if intersection.is_empty() {
3060 break;
3062 } else {
3063 candidate_ids = intersection;
3065 }
3066 } else {
3067 break;
3069 }
3070 }
3071
3072 let mut candidates = Vec::new();
3074
3075 for id in candidate_ids {
3076 let pattern = match state.metadata.get(&id) {
3077 Some(p) => p,
3078 None => continue,
3079 };
3080
3081 let font_tokens_lower = match state.font_tokens.get(&id) {
3083 Some(tokens) => tokens,
3084 None => continue,
3085 };
3086
3087 if font_tokens_lower.is_empty() {
3088 continue;
3089 }
3090
3091 let token_matches = tokens_lower.iter()
3094 .filter(|req_token| {
3095 font_tokens_lower.iter().any(|font_token| {
3096 font_token == *req_token
3098 })
3099 })
3100 .count();
3101
3102 if token_matches == 0 {
3104 continue;
3105 }
3106
3107 let token_similarity = (token_matches * 100 / tokens.len()) as i32;
3109
3110 let unicode_similarity = if !unicode_ranges.is_empty() && !pattern.unicode_ranges.is_empty() {
3112 Self::calculate_unicode_compatibility(unicode_ranges, &pattern.unicode_ranges)
3113 } else {
3114 0
3115 };
3116
3117 if !unicode_ranges.is_empty() && unicode_similarity == 0 {
3120 continue;
3121 }
3122
3123 let style_score = Self::calculate_style_score(&FcPattern {
3124 weight,
3125 italic,
3126 oblique,
3127 ..Default::default()
3128 }, pattern);
3129
3130 candidates.push((
3131 id,
3132 token_similarity,
3133 unicode_similarity,
3134 style_score,
3135 pattern.clone(),
3136 ));
3137 }
3138
3139 candidates.sort_by(|a, b| {
3145 if !unicode_ranges.is_empty() {
3146 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 {
3153 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)) }
3159 });
3160
3161 candidates.truncate(5);
3163
3164 candidates
3166 .into_iter()
3167 .map(|(id, _token_sim, _unicode_sim, _style, pattern)| {
3168 FontMatch {
3169 id,
3170 unicode_ranges: pattern.unicode_ranges.clone(),
3171 fallbacks: Vec::new(), }
3173 })
3174 .collect()
3175 }
3176
3177 fn query_by_family_normalized(
3194 &self,
3195 family: &str,
3196 weight: FcWeight,
3197 italic: PatternMatch,
3198 oblique: PatternMatch,
3199 ) -> Vec<FontMatch> {
3200 let target = crate::utils::normalize_family_name(family);
3201 if target.is_empty() {
3202 return Vec::new();
3203 }
3204 let query = FcPattern {
3205 weight,
3206 italic,
3207 oblique,
3208 ..Default::default()
3209 };
3210 let state = self.state_read();
3211 let mut candidates: Vec<(FontId, i32, FcPattern)> = Vec::new();
3212 for (stored_pattern, id) in &state.patterns {
3213 let meta = state.metadata.get(id).unwrap_or(stored_pattern);
3214 let fam_norm = meta
3215 .family
3216 .as_deref()
3217 .map(crate::utils::normalize_family_name)
3218 .unwrap_or_default();
3219 let matches_family = fam_norm == target
3220 || meta
3221 .name
3222 .as_deref()
3223 .map(crate::utils::normalize_family_name)
3224 .is_some_and(|n| n == target);
3225 if !matches_family {
3226 continue;
3227 }
3228 let style_score = Self::calculate_style_score(&query, meta);
3229 candidates.push((*id, style_score, meta.clone()));
3230 }
3231 drop(state);
3232
3233 candidates.sort_by(|a, b| {
3235 a.1.cmp(&b.1)
3236 .then_with(|| a.2.italic.cmp(&b.2.italic))
3237 .then_with(|| a.2.name.cmp(&b.2.name))
3238 });
3239 candidates.truncate(5);
3240 candidates
3241 .into_iter()
3242 .map(|(id, _, pattern)| FontMatch {
3243 id,
3244 unicode_ranges: pattern.unicode_ranges.clone(),
3245 fallbacks: Vec::new(),
3246 })
3247 .collect()
3248 }
3249
3250 pub fn extract_font_name_tokens(name: &str) -> Vec<String> {
3254 let mut tokens = Vec::new();
3255 let mut current_token = String::new();
3256 let mut last_was_lower = false;
3257
3258 for c in name.chars() {
3259 if c.is_whitespace() || c == '-' || c == '_' {
3260 if !current_token.is_empty() {
3262 tokens.push(current_token.clone());
3263 current_token.clear();
3264 }
3265 last_was_lower = false;
3266 } else if c.is_uppercase() && last_was_lower && !current_token.is_empty() {
3267 tokens.push(current_token.clone());
3269 current_token.clear();
3270 current_token.push(c);
3271 last_was_lower = false;
3272 } else {
3273 current_token.push(c);
3274 last_was_lower = c.is_lowercase();
3275 }
3276 }
3277
3278 if !current_token.is_empty() {
3279 tokens.push(current_token);
3280 }
3281
3282 tokens
3283 }
3284
3285 fn find_unicode_fallbacks(
3289 &self,
3290 unicode_ranges: &[UnicodeRange],
3291 covered_chars: &[bool],
3292 existing_groups: &[CssFallbackGroup],
3293 _weight: FcWeight,
3294 _italic: PatternMatch,
3295 _oblique: PatternMatch,
3296 trace: &mut Vec<TraceMsg>,
3297 ) -> Vec<FontMatch> {
3298 let mut uncovered_ranges = Vec::new();
3300 for (i, &covered) in covered_chars.iter().enumerate() {
3301 if !covered && i < unicode_ranges.len() {
3302 uncovered_ranges.push(unicode_ranges[i].clone());
3303 }
3304 }
3305
3306 if uncovered_ranges.is_empty() {
3307 return Vec::new();
3308 }
3309
3310 let pattern = FcPattern {
3315 name: None,
3316 weight: FcWeight::Normal, italic: PatternMatch::DontCare,
3318 oblique: PatternMatch::DontCare,
3319 unicode_ranges: uncovered_ranges.clone(),
3320 ..Default::default()
3321 };
3322
3323 let mut candidates = self.query_internal(&pattern, trace);
3324
3325 let existing_prefixes: Vec<String> = existing_groups
3328 .iter()
3329 .flat_map(|group| {
3330 group.fonts.iter().filter_map(|font| {
3331 self.get_metadata_by_id(&font.id)
3332 .and_then(|meta| meta.family.clone())
3333 .and_then(|family| {
3334 family.split_whitespace()
3336 .take(2)
3337 .collect::<Vec<_>>()
3338 .join(" ")
3339 .into()
3340 })
3341 })
3342 })
3343 .collect();
3344
3345 candidates.sort_by(|a, b| {
3349 let a_meta = self.get_metadata_by_id(&a.id);
3350 let b_meta = self.get_metadata_by_id(&b.id);
3351
3352 let a_score = Self::calculate_font_similarity_score(a_meta.as_ref(), &existing_prefixes);
3353 let b_score = Self::calculate_font_similarity_score(b_meta.as_ref(), &existing_prefixes);
3354
3355 b_score.cmp(&a_score) .then_with(|| {
3357 let a_coverage = Self::calculate_unicode_compatibility(&uncovered_ranges, &a.unicode_ranges);
3358 let b_coverage = Self::calculate_unicode_compatibility(&uncovered_ranges, &b.unicode_ranges);
3359 b_coverage.cmp(&a_coverage)
3360 })
3361 });
3362
3363 let mut result = Vec::new();
3365 let mut remaining_uncovered: Vec<bool> = vec![true; uncovered_ranges.len()];
3366
3367 for candidate in candidates {
3368 let mut covers_new_range = false;
3370
3371 for (i, range) in uncovered_ranges.iter().enumerate() {
3372 if remaining_uncovered[i] {
3373 for font_range in &candidate.unicode_ranges {
3375 if font_range.overlaps(range) {
3376 remaining_uncovered[i] = false;
3377 covers_new_range = true;
3378 break;
3379 }
3380 }
3381 }
3382 }
3383
3384 if covers_new_range {
3386 result.push(candidate);
3387
3388 if remaining_uncovered.iter().all(|&uncovered| !uncovered) {
3390 break;
3391 }
3392 }
3393 }
3394
3395 result
3396 }
3397
3398 fn calculate_font_similarity_score(
3401 font_meta: Option<&FcPattern>,
3402 existing_prefixes: &[String],
3403 ) -> i32 {
3404 let Some(meta) = font_meta else { return 0; };
3405 let Some(family) = &meta.family else { return 0; };
3406
3407 for prefix in existing_prefixes {
3409 if family.starts_with(prefix) {
3410 return 100; }
3412 if family.contains(prefix) {
3413 return 50; }
3415 }
3416
3417 0 }
3419
3420 pub fn calculate_unicode_coverage(ranges: &[UnicodeRange]) -> u64 {
3423 ranges
3424 .iter()
3425 .map(|range| (range.end - range.start + 1) as u64)
3426 .sum()
3427 }
3428
3429 pub fn normalize_unicode_ranges(mut ranges: Vec<UnicodeRange>) -> Vec<UnicodeRange> {
3442 if ranges.len() < 2 {
3443 return ranges;
3444 }
3445
3446 ranges.sort_unstable();
3447
3448 let mut out: Vec<UnicodeRange> = Vec::with_capacity(ranges.len());
3449 for range in ranges {
3450 match out.last_mut() {
3451 Some(prev) if range.start <= prev.end.saturating_add(1) => {
3454 prev.end = prev.end.max(range.end);
3455 }
3456 _ => out.push(range),
3457 }
3458 }
3459 out
3460 }
3461
3462 pub fn calculate_unicode_compatibility(
3465 requested: &[UnicodeRange],
3466 available: &[UnicodeRange],
3467 ) -> i32 {
3468 if requested.is_empty() {
3469 return Self::calculate_unicode_coverage(available) as i32;
3471 }
3472
3473 let mut total_coverage = 0u32;
3474
3475 for req_range in requested {
3476 for avail_range in available {
3477 let overlap_start = req_range.start.max(avail_range.start);
3479 let overlap_end = req_range.end.min(avail_range.end);
3480
3481 if overlap_start <= overlap_end {
3482 let overlap_size = overlap_end - overlap_start + 1;
3484 total_coverage += overlap_size;
3485 }
3486 }
3487 }
3488
3489 total_coverage as i32
3490 }
3491
3492 pub fn calculate_style_score(original: &FcPattern, candidate: &FcPattern) -> i32 {
3493
3494 let mut score = 0_i32;
3495
3496 if (original.bold == PatternMatch::True && candidate.weight == FcWeight::Bold)
3498 || (original.bold == PatternMatch::False && candidate.weight != FcWeight::Bold)
3499 {
3500 } else {
3503 let weight_diff = (original.weight as i32 - candidate.weight as i32).abs();
3505 score += weight_diff as i32;
3506 }
3507
3508 if original.weight == candidate.weight {
3511 score -= 15;
3512 if original.weight == FcWeight::Normal {
3513 score -= 10; }
3515 }
3516
3517 if (original.condensed == PatternMatch::True && candidate.stretch.is_condensed())
3519 || (original.condensed == PatternMatch::False && !candidate.stretch.is_condensed())
3520 {
3521 } else {
3524 let stretch_diff = (original.stretch as i32 - candidate.stretch as i32).abs();
3526 score += (stretch_diff * 100) as i32;
3527 }
3528
3529 let style_props = [
3531 (original.italic, candidate.italic, 300, 150),
3532 (original.oblique, candidate.oblique, 200, 100),
3533 (original.bold, candidate.bold, 300, 150),
3534 (original.monospace, candidate.monospace, 100, 50),
3535 (original.condensed, candidate.condensed, 100, 50),
3536 ];
3537
3538 for (orig, cand, mismatch_penalty, dontcare_penalty) in style_props {
3539 if orig.needs_to_match() {
3540 if orig == PatternMatch::False && cand == PatternMatch::DontCare {
3541 score += dontcare_penalty / 2;
3544 } else if !orig.matches(&cand) {
3545 if cand == PatternMatch::DontCare {
3546 score += dontcare_penalty;
3547 } else {
3548 score += mismatch_penalty;
3549 }
3550 } else if orig == PatternMatch::True && cand == PatternMatch::True {
3551 score -= 20;
3553 } else if orig == PatternMatch::False && cand == PatternMatch::False {
3554 score -= 20;
3557 }
3558 } else {
3559 if cand == PatternMatch::True {
3564 score += dontcare_penalty / 3;
3565 }
3566 }
3567 }
3568
3569 if let (Some(name), Some(family)) = (&candidate.name, &candidate.family) {
3575 let name_lower = name.to_ascii_lowercase();
3576 let family_lower = family.to_ascii_lowercase();
3577
3578 let extra = if name_lower.starts_with(&family_lower) {
3580 name_lower[family_lower.len()..].to_string()
3581 } else {
3582 String::new()
3583 };
3584
3585 let stripped = extra
3587 .replace("regular", "")
3588 .replace("normal", "")
3589 .replace("book", "")
3590 .replace("roman", "");
3591 let stripped = stripped.trim();
3592
3593 if stripped.is_empty() {
3594 score -= 50;
3596 } else {
3597 let extra_words = stripped.split_whitespace().count();
3599 score += (extra_words as i32) * 25;
3600 }
3601 }
3602
3603 if let Some(ref subfamily) = candidate.metadata.font_subfamily {
3607 let sf_lower = subfamily.to_ascii_lowercase();
3608 if sf_lower == "regular" {
3609 score -= 30;
3610 }
3611 }
3612
3613 score
3614 }
3615}
3616
3617#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3618fn FcScanDirectories() -> Option<(Vec<(FcPattern, FcFontPath)>, BTreeMap<String, FcFontRenderConfig>)> {
3619 use std::fs;
3620 use std::path::Path;
3621
3622 const BASE_FONTCONFIG_PATH: &str = "/etc/fonts/fonts.conf";
3623
3624 if !Path::new(BASE_FONTCONFIG_PATH).exists() {
3625 return None;
3626 }
3627
3628 let mut font_paths = Vec::with_capacity(32);
3629 let mut paths_to_visit = vec![(None, PathBuf::from(BASE_FONTCONFIG_PATH))];
3630 let mut render_configs: BTreeMap<String, FcFontRenderConfig> = BTreeMap::new();
3631
3632 while let Some((prefix, path_to_visit)) = paths_to_visit.pop() {
3633 let path = match process_path(&prefix, path_to_visit, true) {
3634 Some(path) => path,
3635 None => continue,
3636 };
3637
3638 let metadata = match fs::metadata(&path) {
3639 Ok(metadata) => metadata,
3640 Err(_) => continue,
3641 };
3642
3643 if metadata.is_file() {
3644 let xml_utf8 = match fs::read_to_string(&path) {
3645 Ok(xml_utf8) => xml_utf8,
3646 Err(_) => continue,
3647 };
3648
3649 if ParseFontsConf(&xml_utf8, &mut paths_to_visit, &mut font_paths).is_none() {
3650 continue;
3651 }
3652
3653 ParseFontsConfRenderConfig(&xml_utf8, &mut render_configs);
3655 } else if metadata.is_dir() {
3656 let dir_entries = match fs::read_dir(&path) {
3657 Ok(dir_entries) => dir_entries,
3658 Err(_) => continue,
3659 };
3660
3661 for entry_result in dir_entries {
3662 let entry = match entry_result {
3663 Ok(entry) => entry,
3664 Err(_) => continue,
3665 };
3666
3667 let entry_path = entry.path();
3668
3669 let entry_metadata = match fs::metadata(&entry_path) {
3671 Ok(metadata) => metadata,
3672 Err(_) => continue,
3673 };
3674
3675 if !entry_metadata.is_file() {
3676 continue;
3677 }
3678
3679 let file_name = match entry_path.file_name() {
3680 Some(name) => name,
3681 None => continue,
3682 };
3683
3684 let file_name_str = file_name.to_string_lossy();
3685 if file_name_str.starts_with(|c: char| c.is_ascii_digit())
3686 && file_name_str.ends_with(".conf")
3687 {
3688 paths_to_visit.push((None, entry_path));
3689 }
3690 }
3691 }
3692 }
3693
3694 if font_paths.is_empty() {
3695 return None;
3696 }
3697
3698 Some((FcScanDirectoriesInner(&font_paths), render_configs))
3699}
3700
3701#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3703fn ParseFontsConf(
3704 input: &str,
3705 paths_to_visit: &mut Vec<(Option<String>, PathBuf)>,
3706 font_paths: &mut Vec<(Option<String>, String)>,
3707) -> Option<()> {
3708 use xmlparser::Token::*;
3709 use xmlparser::Tokenizer;
3710
3711 const TAG_INCLUDE: &str = "include";
3712 const TAG_DIR: &str = "dir";
3713 const ATTRIBUTE_PREFIX: &str = "prefix";
3714
3715 let mut current_prefix: Option<&str> = None;
3716 let mut current_path: Option<&str> = None;
3717 let mut is_in_include = false;
3718 let mut is_in_dir = false;
3719
3720 for token_result in Tokenizer::from(input) {
3721 let token = match token_result {
3722 Ok(token) => token,
3723 Err(_) => return None,
3724 };
3725
3726 match token {
3727 ElementStart { local, .. } => {
3728 if is_in_include || is_in_dir {
3729 return None; }
3731
3732 match local.as_str() {
3733 TAG_INCLUDE => {
3734 is_in_include = true;
3735 }
3736 TAG_DIR => {
3737 is_in_dir = true;
3738 }
3739 _ => continue,
3740 }
3741
3742 current_path = None;
3743 }
3744 Text { text, .. } => {
3745 let text = text.as_str().trim();
3746 if text.is_empty() {
3747 continue;
3748 }
3749 if is_in_include || is_in_dir {
3750 current_path = Some(text);
3751 }
3752 }
3753 Attribute { local, value, .. } => {
3754 if !is_in_include && !is_in_dir {
3755 continue;
3756 }
3757 if local.as_str() == ATTRIBUTE_PREFIX {
3759 current_prefix = Some(value.as_str());
3760 }
3761 }
3762 ElementEnd { end, .. } => {
3763 let end_tag = match end {
3764 xmlparser::ElementEnd::Close(_, a) => a,
3765 _ => continue,
3766 };
3767
3768 match end_tag.as_str() {
3769 TAG_INCLUDE => {
3770 if !is_in_include {
3771 continue;
3772 }
3773
3774 if let Some(current_path) = current_path.as_ref() {
3775 paths_to_visit.push((
3776 current_prefix.map(ToOwned::to_owned),
3777 PathBuf::from(*current_path),
3778 ));
3779 }
3780 }
3781 TAG_DIR => {
3782 if !is_in_dir {
3783 continue;
3784 }
3785
3786 if let Some(current_path) = current_path.as_ref() {
3787 font_paths.push((
3788 current_prefix.map(ToOwned::to_owned),
3789 (*current_path).to_owned(),
3790 ));
3791 }
3792 }
3793 _ => continue,
3794 }
3795
3796 is_in_include = false;
3797 is_in_dir = false;
3798 current_path = None;
3799 current_prefix = None;
3800 }
3801 _ => {}
3802 }
3803 }
3804
3805 Some(())
3806}
3807
3808#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3820fn ParseFontsConfRenderConfig(
3821 input: &str,
3822 configs: &mut BTreeMap<String, FcFontRenderConfig>,
3823) {
3824 use xmlparser::Token::*;
3825 use xmlparser::Tokenizer;
3826
3827 #[derive(Clone, Copy, PartialEq)]
3829 enum State {
3830 Idle,
3832 InMatchFont,
3834 InTestFamily,
3836 InEdit,
3838 InValue,
3840 }
3841
3842 let mut state = State::Idle;
3843 let mut match_is_font_target = false;
3844 let mut current_family: Option<String> = None;
3845 let mut current_edit_name: Option<String> = None;
3846 let mut current_value: Option<String> = None;
3847 let mut value_tag: Option<String> = None;
3848 let mut config = FcFontRenderConfig::default();
3849 let mut in_test = false;
3850 let mut test_name: Option<String> = None;
3851
3852 for token_result in Tokenizer::from(input) {
3853 let token = match token_result {
3854 Ok(token) => token,
3855 Err(_) => continue,
3856 };
3857
3858 match token {
3859 ElementStart { local, .. } => {
3860 let tag = local.as_str();
3861 match tag {
3862 "match" => {
3863 match_is_font_target = false;
3865 current_family = None;
3866 config = FcFontRenderConfig::default();
3867 }
3868 "test" if state == State::InMatchFont => {
3869 in_test = true;
3870 test_name = None;
3871 }
3872 "edit" if state == State::InMatchFont => {
3873 current_edit_name = None;
3874 }
3875 "bool" | "double" | "const" | "string" | "int" => {
3876 if state == State::InTestFamily || state == State::InEdit {
3877 value_tag = Some(tag.to_owned());
3878 current_value = None;
3879 }
3880 }
3881 _ => {}
3882 }
3883 }
3884 Attribute { local, value, .. } => {
3885 let attr_name = local.as_str();
3886 let attr_value = value.as_str();
3887
3888 match attr_name {
3889 "target" => {
3890 if attr_value == "font" {
3891 match_is_font_target = true;
3892 }
3893 }
3894 "name" => {
3895 if in_test && state == State::InMatchFont {
3896 test_name = Some(attr_value.to_owned());
3897 } else if state == State::InMatchFont {
3898 current_edit_name = Some(attr_value.to_owned());
3899 }
3900 }
3901 _ => {}
3902 }
3903 }
3904 Text { text, .. } => {
3905 let text = text.as_str().trim();
3906 if !text.is_empty() && (state == State::InTestFamily || state == State::InEdit) {
3907 current_value = Some(text.to_owned());
3908 }
3909 }
3910 ElementEnd { end, .. } => {
3911 match end {
3912 xmlparser::ElementEnd::Open => {
3913 if match_is_font_target && state == State::Idle {
3915 state = State::InMatchFont;
3916 match_is_font_target = false;
3917 } else if in_test {
3918 if test_name.as_deref() == Some("family") {
3919 state = State::InTestFamily;
3920 }
3921 in_test = false;
3922 } else if current_edit_name.is_some() && state == State::InMatchFont {
3923 state = State::InEdit;
3924 }
3925 }
3926 xmlparser::ElementEnd::Close(_, local) => {
3927 let tag = local.as_str();
3928 match tag {
3929 "match" => {
3930 if let Some(family) = current_family.take() {
3932 let empty = FcFontRenderConfig::default();
3933 if config != empty {
3934 configs.insert(family, config.clone());
3935 }
3936 }
3937 state = State::Idle;
3938 config = FcFontRenderConfig::default();
3939 }
3940 "test" => {
3941 if state == State::InTestFamily {
3942 if let Some(ref val) = current_value {
3944 current_family = Some(val.clone());
3945 }
3946 state = State::InMatchFont;
3947 }
3948 current_value = None;
3949 value_tag = None;
3950 }
3951 "edit" => {
3952 if state == State::InEdit {
3953 if let (Some(ref name), Some(ref val)) = (¤t_edit_name, ¤t_value) {
3955 apply_edit_value(&mut config, name, val, value_tag.as_deref());
3956 }
3957 state = State::InMatchFont;
3958 }
3959 current_edit_name = None;
3960 current_value = None;
3961 value_tag = None;
3962 }
3963 "bool" | "double" | "const" | "string" | "int" => {
3964 }
3966 _ => {}
3967 }
3968 }
3969 xmlparser::ElementEnd::Empty => {
3970 }
3972 }
3973 }
3974 _ => {}
3975 }
3976 }
3977}
3978
3979#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3981fn apply_edit_value(
3982 config: &mut FcFontRenderConfig,
3983 edit_name: &str,
3984 value: &str,
3985 value_tag: Option<&str>,
3986) {
3987 match edit_name {
3988 "antialias" => {
3989 config.antialias = parse_bool_value(value);
3990 }
3991 "hinting" => {
3992 config.hinting = parse_bool_value(value);
3993 }
3994 "autohint" => {
3995 config.autohint = parse_bool_value(value);
3996 }
3997 "embeddedbitmap" => {
3998 config.embeddedbitmap = parse_bool_value(value);
3999 }
4000 "embolden" => {
4001 config.embolden = parse_bool_value(value);
4002 }
4003 "minspace" => {
4004 config.minspace = parse_bool_value(value);
4005 }
4006 "hintstyle" => {
4007 config.hintstyle = parse_hintstyle_const(value);
4008 }
4009 "rgba" => {
4010 config.rgba = parse_rgba_const(value);
4011 }
4012 "lcdfilter" => {
4013 config.lcdfilter = parse_lcdfilter_const(value);
4014 }
4015 "dpi" => {
4016 if let Ok(v) = value.parse::<f64>() {
4017 config.dpi = Some(v);
4018 }
4019 }
4020 "scale" => {
4021 if let Ok(v) = value.parse::<f64>() {
4022 config.scale = Some(v);
4023 }
4024 }
4025 _ => {
4026 }
4028 }
4029}
4030
4031#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
4032fn parse_bool_value(value: &str) -> Option<bool> {
4033 match value {
4034 "true" => Some(true),
4035 "false" => Some(false),
4036 _ => None,
4037 }
4038}
4039
4040#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
4041fn parse_hintstyle_const(value: &str) -> Option<FcHintStyle> {
4042 match value {
4043 "hintnone" => Some(FcHintStyle::None),
4044 "hintslight" => Some(FcHintStyle::Slight),
4045 "hintmedium" => Some(FcHintStyle::Medium),
4046 "hintfull" => Some(FcHintStyle::Full),
4047 _ => None,
4048 }
4049}
4050
4051#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
4052fn parse_rgba_const(value: &str) -> Option<FcRgba> {
4053 match value {
4054 "unknown" => Some(FcRgba::Unknown),
4055 "rgb" => Some(FcRgba::Rgb),
4056 "bgr" => Some(FcRgba::Bgr),
4057 "vrgb" => Some(FcRgba::Vrgb),
4058 "vbgr" => Some(FcRgba::Vbgr),
4059 "none" => Some(FcRgba::None),
4060 _ => None,
4061 }
4062}
4063
4064#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
4065fn parse_lcdfilter_const(value: &str) -> Option<FcLcdFilter> {
4066 match value {
4067 "lcdnone" => Some(FcLcdFilter::None),
4068 "lcddefault" => Some(FcLcdFilter::Default),
4069 "lcdlight" => Some(FcLcdFilter::Light),
4070 "lcdlegacy" => Some(FcLcdFilter::Legacy),
4071 _ => None,
4072 }
4073}
4074
4075#[cfg(all(feature = "std", feature = "parsing"))]
4078const UNICODE_RANGE_MAPPINGS: &[(usize, u32, u32)] = &[
4079 (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), ];
4212
4213#[cfg(all(feature = "std", feature = "parsing"))]
4216struct ParsedFontFace {
4217 pattern: FcPattern,
4218 font_index: usize,
4219}
4220
4221#[cfg(all(feature = "std", feature = "parsing"))]
4227fn parse_font_faces(font_bytes: &[u8]) -> Option<Vec<ParsedFontFace>> {
4228 use allsorts::{
4229 binary::read::ReadScope,
4230 font_data::FontData,
4231 get_name::fontcode_get_name,
4232 post::PostTable,
4233 tables::{
4234 os2::Os2, HeadTable, NameTable,
4235 },
4236 tag,
4237 };
4238 use std::collections::BTreeSet;
4239
4240 const FONT_SPECIFIER_NAME_ID: u16 = 4;
4241 const FONT_SPECIFIER_FAMILY_ID: u16 = 1;
4242
4243 let max_fonts = if font_bytes.len() >= 12 && &font_bytes[0..4] == b"ttcf" {
4244 let num_fonts =
4246 u32::from_be_bytes([font_bytes[8], font_bytes[9], font_bytes[10], font_bytes[11]]);
4247 std::cmp::min(num_fonts as usize, 100)
4249 } else {
4250 1
4252 };
4253
4254 let scope = ReadScope::new(font_bytes);
4255 let font_file = scope.read::<FontData<'_>>().ok()?;
4256
4257 let mut results = Vec::new();
4259
4260 for font_index in 0..max_fonts {
4261 let provider = font_file.table_provider(font_index).ok()?;
4262 let head_data = provider.table_data(tag::HEAD).ok()??.into_owned();
4263 let head_table = ReadScope::new(&head_data).read::<HeadTable>().ok()?;
4264
4265 let is_bold = head_table.is_bold();
4266 let is_italic = head_table.is_italic();
4267 let mut detected_monospace = None;
4268
4269 let post_data = provider.table_data(tag::POST).ok()??;
4270 if let Ok(post_table) = ReadScope::new(&post_data).read::<PostTable>() {
4271 detected_monospace = Some(post_table.header.is_fixed_pitch != 0);
4273 }
4274
4275 let os2_data = provider.table_data(tag::OS_2).ok().flatten();
4287 let os2_table = os2_data
4288 .as_deref()
4289 .and_then(|data| ReadScope::new(data).read_dep::<Os2>(data.len()).ok());
4290
4291 let is_oblique = os2_table.as_ref().is_some_and(|os2| {
4293 os2.fs_selection
4294 .contains(allsorts::tables::os2::FsSelectionFlag::OBLIQUE)
4295 });
4296 let weight = os2_table.as_ref().map_or(
4299 if is_bold { FcWeight::Bold } else { FcWeight::Normal },
4300 |os2| FcWeight::from_u16(os2.us_weight_class),
4301 );
4302 let stretch = os2_table
4303 .as_ref()
4304 .map_or(FcStretch::Normal, |os2| FcStretch::from_u16(os2.us_width_class));
4305
4306 let mut unicode_ranges = Vec::new();
4310
4311 let os2_ranges = os2_table.as_ref().map_or([0u32; 4], |os2| {
4315 [
4316 os2.ul_unicode_range1,
4317 os2.ul_unicode_range2,
4318 os2.ul_unicode_range3,
4319 os2.ul_unicode_range4,
4320 ]
4321 });
4322
4323 for &(bit, start, end) in UNICODE_RANGE_MAPPINGS {
4324 let range_idx = bit / 32;
4325 let bit_pos = bit % 32;
4326 if range_idx < 4 && (os2_ranges[range_idx] & (1 << bit_pos)) != 0 {
4327 unicode_ranges.push(UnicodeRange { start, end });
4328 }
4329 }
4330
4331 unicode_ranges = verify_unicode_ranges_with_cmap(&provider, unicode_ranges);
4349
4350 if let Some(cmap_ranges) = analyze_cmap_coverage(&provider) {
4351 unicode_ranges.extend(cmap_ranges);
4352 }
4353
4354 unicode_ranges = FcFontCache::normalize_unicode_ranges(unicode_ranges);
4358
4359 let is_monospace = detect_monospace(&provider, os2_table.as_ref(), detected_monospace)
4361 .unwrap_or(false);
4362
4363 let name_data = provider.table_data(tag::NAME).ok()??.into_owned();
4364 let name_table = ReadScope::new(&name_data).read::<NameTable>().ok()?;
4365
4366 let mut metadata = FcFontMetadata::default();
4368
4369 const NAME_ID_COPYRIGHT: u16 = 0;
4370 const NAME_ID_FAMILY: u16 = 1;
4371 const NAME_ID_SUBFAMILY: u16 = 2;
4372 const NAME_ID_UNIQUE_ID: u16 = 3;
4373 const NAME_ID_FULL_NAME: u16 = 4;
4374 const NAME_ID_VERSION: u16 = 5;
4375 const NAME_ID_POSTSCRIPT_NAME: u16 = 6;
4376 const NAME_ID_TRADEMARK: u16 = 7;
4377 const NAME_ID_MANUFACTURER: u16 = 8;
4378 const NAME_ID_DESIGNER: u16 = 9;
4379 const NAME_ID_DESCRIPTION: u16 = 10;
4380 const NAME_ID_VENDOR_URL: u16 = 11;
4381 const NAME_ID_DESIGNER_URL: u16 = 12;
4382 const NAME_ID_LICENSE: u16 = 13;
4383 const NAME_ID_LICENSE_URL: u16 = 14;
4384 const NAME_ID_PREFERRED_FAMILY: u16 = 16;
4385 const NAME_ID_PREFERRED_SUBFAMILY: u16 = 17;
4386
4387 metadata.copyright = get_name_string(&name_data, NAME_ID_COPYRIGHT);
4388 metadata.font_family = get_name_string(&name_data, NAME_ID_FAMILY);
4389 metadata.font_subfamily = get_name_string(&name_data, NAME_ID_SUBFAMILY);
4390 metadata.full_name = get_name_string(&name_data, NAME_ID_FULL_NAME);
4391 metadata.unique_id = get_name_string(&name_data, NAME_ID_UNIQUE_ID);
4392 metadata.version = get_name_string(&name_data, NAME_ID_VERSION);
4393 metadata.postscript_name = get_name_string(&name_data, NAME_ID_POSTSCRIPT_NAME);
4394 metadata.trademark = get_name_string(&name_data, NAME_ID_TRADEMARK);
4395 metadata.manufacturer = get_name_string(&name_data, NAME_ID_MANUFACTURER);
4396 metadata.designer = get_name_string(&name_data, NAME_ID_DESIGNER);
4397 metadata.id_description = get_name_string(&name_data, NAME_ID_DESCRIPTION);
4398 metadata.designer_url = get_name_string(&name_data, NAME_ID_DESIGNER_URL);
4399 metadata.manufacturer_url = get_name_string(&name_data, NAME_ID_VENDOR_URL);
4400 metadata.license = get_name_string(&name_data, NAME_ID_LICENSE);
4401 metadata.license_url = get_name_string(&name_data, NAME_ID_LICENSE_URL);
4402 metadata.preferred_family = get_name_string(&name_data, NAME_ID_PREFERRED_FAMILY);
4403 metadata.preferred_subfamily = get_name_string(&name_data, NAME_ID_PREFERRED_SUBFAMILY);
4404
4405 let mut f_family = None;
4407
4408 let patterns = name_table
4409 .name_records
4410 .iter()
4411 .filter_map(|name_record| {
4412 let name_id = name_record.name_id;
4413 if name_id == FONT_SPECIFIER_FAMILY_ID {
4414 if let Ok(Some(family)) =
4415 fontcode_get_name(&name_data, FONT_SPECIFIER_FAMILY_ID)
4416 {
4417 f_family = Some(family);
4418 }
4419 None
4420 } else if name_id == FONT_SPECIFIER_NAME_ID {
4421 let family = f_family.as_ref()?;
4422 let name = fontcode_get_name(&name_data, FONT_SPECIFIER_NAME_ID).ok()??;
4423 if name.to_bytes().is_empty() {
4424 None
4425 } else {
4426 let mut name_str =
4427 String::from_utf8_lossy(name.to_bytes()).to_string();
4428 let mut family_str =
4429 String::from_utf8_lossy(family.as_bytes()).to_string();
4430 if name_str.starts_with('.') {
4431 name_str = name_str[1..].to_string();
4432 }
4433 if family_str.starts_with('.') {
4434 family_str = family_str[1..].to_string();
4435 }
4436 Some((
4437 FcPattern {
4438 name: Some(name_str),
4439 family: Some(family_str),
4440 bold: if is_bold {
4441 PatternMatch::True
4442 } else {
4443 PatternMatch::False
4444 },
4445 italic: if is_italic {
4446 PatternMatch::True
4447 } else {
4448 PatternMatch::False
4449 },
4450 oblique: if is_oblique {
4451 PatternMatch::True
4452 } else {
4453 PatternMatch::False
4454 },
4455 monospace: if is_monospace {
4456 PatternMatch::True
4457 } else {
4458 PatternMatch::False
4459 },
4460 condensed: if stretch <= FcStretch::Condensed {
4461 PatternMatch::True
4462 } else {
4463 PatternMatch::False
4464 },
4465 weight,
4466 stretch,
4467 unicode_ranges: unicode_ranges.clone(),
4468 metadata: metadata.clone(),
4469 render_config: FcFontRenderConfig::default(),
4470 },
4471 font_index,
4472 ))
4473 }
4474 } else {
4475 None
4476 }
4477 })
4478 .collect::<BTreeSet<_>>();
4479
4480 results.extend(patterns.into_iter().map(|(pat, idx)| ParsedFontFace {
4481 pattern: pat,
4482 font_index: idx,
4483 }));
4484 }
4485
4486 if results.is_empty() {
4487 None
4488 } else {
4489 Some(results)
4490 }
4491}
4492
4493#[cfg(all(feature = "std", feature = "parsing"))]
4495pub(crate) fn FcParseFont(filepath: &PathBuf) -> Option<Vec<(FcPattern, FcFontPath)>> {
4496 #[cfg(all(not(target_family = "wasm"), feature = "std"))]
4497 use mmapio::MmapOptions;
4498 use std::fs::File;
4499
4500 let file = File::open(filepath).ok()?;
4502
4503 #[cfg(all(not(target_family = "wasm"), feature = "std"))]
4504 let font_bytes = unsafe { MmapOptions::new().map(&file).ok()? };
4505
4506 #[cfg(not(all(not(target_family = "wasm"), feature = "std")))]
4507 let font_bytes = std::fs::read(filepath).ok()?;
4508
4509 let faces = parse_font_faces(&font_bytes[..])?;
4510 let path_str = filepath.to_string_lossy().to_string();
4511 let bytes_hash = crate::utils::content_dedup_hash_u64(&font_bytes[..]);
4516
4517 Some(
4518 faces
4519 .into_iter()
4520 .map(|face| {
4521 (
4522 face.pattern,
4523 FcFontPath {
4524 path: path_str.clone(),
4525 font_index: face.font_index,
4526 bytes_hash,
4527 },
4528 )
4529 })
4530 .collect(),
4531 )
4532}
4533
4534#[cfg(all(feature = "std", feature = "parsing"))]
4551#[derive(Debug, Clone)]
4552pub struct FastCoverage {
4553 pub pattern: FcPattern,
4559 pub covered: alloc::collections::BTreeSet<char>,
4564 pub is_bold: bool,
4566 pub is_italic: bool,
4568}
4569
4570#[cfg(all(feature = "std", feature = "parsing"))]
4587#[allow(non_snake_case)]
4588pub fn FcParseFontFaceFast(
4589 font_bytes: &[u8],
4590 font_index: usize,
4591 codepoints: &alloc::collections::BTreeSet<char>,
4592) -> Option<FastCoverage> {
4593 use allsorts::{
4594 binary::read::ReadScope,
4595 font_data::FontData,
4596 tables::{
4597 cmap::{Cmap, CmapSubtable},
4598 FontTableProvider, HeadTable,
4599 },
4600 tag,
4601 };
4602
4603 let scope = ReadScope::new(font_bytes);
4604 let font_file = scope.read::<FontData<'_>>().ok()?;
4605 let provider = font_file.table_provider(font_index).ok()?;
4606
4607 let head_data = provider.table_data(tag::HEAD).ok()??;
4609 let head_table = ReadScope::new(&head_data).read::<HeadTable>().ok()?;
4610 let is_bold = head_table.is_bold();
4611 let is_italic = head_table.is_italic();
4612
4613 let cmap_data = provider.table_data(tag::CMAP).ok()??;
4616 let cmap = ReadScope::new(&cmap_data).read::<Cmap<'_>>().ok()?;
4617 let encoding_record = find_best_cmap_subtable(&cmap)?;
4618 let cmap_subtable = ReadScope::new(&cmap_data)
4619 .offset(encoding_record.offset as usize)
4620 .read::<CmapSubtable<'_>>()
4621 .ok()?;
4622
4623 let mut covered: alloc::collections::BTreeSet<char> =
4624 alloc::collections::BTreeSet::new();
4625 let mut covered_ranges: Vec<UnicodeRange> = Vec::new();
4626 for ch in codepoints {
4627 let cp = *ch as u32;
4628 if let Ok(Some(gid)) = cmap_subtable.map_glyph(cp) {
4629 if gid != 0 {
4630 covered.insert(*ch);
4631 if let Some(last) = covered_ranges.last_mut() {
4635 if cp == last.end + 1 {
4636 last.end = cp;
4637 continue;
4638 }
4639 }
4640 covered_ranges.push(UnicodeRange { start: cp, end: cp });
4641 }
4642 }
4643 }
4644
4645 let weight = if is_bold {
4646 FcWeight::Bold
4647 } else {
4648 FcWeight::Normal
4649 };
4650 let italic_match = if is_italic {
4651 PatternMatch::True
4652 } else {
4653 PatternMatch::False
4654 };
4655
4656 let pattern = FcPattern {
4657 name: None,
4658 family: None,
4659 weight,
4660 italic: italic_match,
4661 oblique: PatternMatch::DontCare,
4662 monospace: PatternMatch::DontCare,
4663 unicode_ranges: covered_ranges,
4664 ..Default::default()
4665 };
4666
4667 Some(FastCoverage {
4668 pattern,
4669 covered,
4670 is_bold,
4671 is_italic,
4672 })
4673}
4674
4675#[cfg(all(feature = "std", feature = "parsing"))]
4680#[allow(non_snake_case)]
4681pub fn FcCountFontFaces(font_bytes: &[u8]) -> usize {
4682 if font_bytes.len() >= 12 && &font_bytes[0..4] == b"ttcf" {
4683 let num_fonts = u32::from_be_bytes([
4684 font_bytes[8], font_bytes[9], font_bytes[10], font_bytes[11],
4685 ]);
4686 std::cmp::min(num_fonts as usize, 100).max(1)
4688 } else {
4689 1
4690 }
4691}
4692
4693#[cfg(all(feature = "std", feature = "parsing"))]
4719#[allow(non_snake_case)]
4720pub fn FcParseFontBytes(font_bytes: &[u8], font_id: &str) -> Option<Vec<(FcPattern, FcFont)>> {
4721 FcParseFontBytesInner(font_bytes, font_id)
4722}
4723
4724#[cfg(all(feature = "std", feature = "parsing"))]
4727fn FcParseFontBytesInner(font_bytes: &[u8], font_id: &str) -> Option<Vec<(FcPattern, FcFont)>> {
4728 let faces = parse_font_faces(font_bytes)?;
4729 let id = font_id.to_string();
4730 let bytes = font_bytes.to_vec();
4731
4732 Some(
4733 faces
4734 .into_iter()
4735 .map(|face| {
4736 (
4737 face.pattern,
4738 FcFont {
4739 bytes: bytes.clone(),
4740 font_index: face.font_index,
4741 id: id.clone(),
4742 },
4743 )
4744 })
4745 .collect(),
4746 )
4747}
4748
4749#[cfg(all(feature = "std", feature = "parsing"))]
4750fn FcScanDirectoriesInner(paths: &[(Option<String>, String)]) -> Vec<(FcPattern, FcFontPath)> {
4751 #[cfg(all(feature = "multithreading", not(target_family = "wasm")))]
4752 {
4753 use rayon::prelude::*;
4754
4755 paths
4757 .par_iter()
4758 .filter_map(|(prefix, p)| {
4759 process_path(prefix, PathBuf::from(p), false).map(FcScanSingleDirectoryRecursive)
4760 })
4761 .flatten()
4762 .collect()
4763 }
4764 #[cfg(not(all(feature = "multithreading", not(target_family = "wasm"))))]
4767 {
4768 paths
4769 .iter()
4770 .filter_map(|(prefix, p)| {
4771 process_path(prefix, PathBuf::from(p), false).map(FcScanSingleDirectoryRecursive)
4772 })
4773 .flatten()
4774 .collect()
4775 }
4776}
4777
4778#[cfg(feature = "std")]
4780fn FcCollectFontFilesRecursive(dir: PathBuf) -> Vec<PathBuf> {
4781 let mut files = Vec::new();
4782 let mut dirs_to_parse = vec![dir];
4783
4784 loop {
4785 let mut new_dirs = Vec::new();
4786 for dir in &dirs_to_parse {
4787 let entries = match std::fs::read_dir(dir) {
4788 Ok(o) => o,
4789 Err(_) => continue,
4790 };
4791 for entry in entries.flatten() {
4792 let path = entry.path();
4793 if path.is_dir() {
4794 new_dirs.push(path);
4795 } else {
4796 files.push(path);
4797 }
4798 }
4799 }
4800 if new_dirs.is_empty() {
4801 break;
4802 }
4803 dirs_to_parse = new_dirs;
4804 }
4805
4806 files
4807}
4808
4809#[cfg(all(feature = "std", feature = "parsing"))]
4810fn FcScanSingleDirectoryRecursive(dir: PathBuf) -> Vec<(FcPattern, FcFontPath)> {
4811 let files = FcCollectFontFilesRecursive(dir);
4812 FcParseFontFiles(&files)
4813}
4814
4815#[cfg(all(feature = "std", feature = "parsing"))]
4816fn FcParseFontFiles(files_to_parse: &[PathBuf]) -> Vec<(FcPattern, FcFontPath)> {
4817 let result = {
4818 #[cfg(all(feature = "multithreading", not(target_family = "wasm")))]
4819 {
4820 use rayon::prelude::*;
4821
4822 files_to_parse
4823 .par_iter()
4824 .filter_map(|file| FcParseFont(file))
4825 .collect::<Vec<Vec<_>>>()
4826 }
4827 #[cfg(not(all(feature = "multithreading", not(target_family = "wasm"))))]
4828 {
4829 files_to_parse
4830 .iter()
4831 .filter_map(|file| FcParseFont(file))
4832 .collect::<Vec<Vec<_>>>()
4833 }
4834 };
4835
4836 result.into_iter().flat_map(|f| f.into_iter()).collect()
4837}
4838
4839#[cfg(all(feature = "std", feature = "parsing"))]
4840fn process_path(
4844 prefix: &Option<String>,
4845 mut path: PathBuf,
4846 is_include_path: bool,
4847) -> Option<PathBuf> {
4848 use std::env::var;
4849
4850 const HOME_SHORTCUT: &str = "~";
4851 const CWD_PATH: &str = ".";
4852
4853 const HOME_ENV_VAR: &str = "HOME";
4854 const XDG_CONFIG_HOME_ENV_VAR: &str = "XDG_CONFIG_HOME";
4855 const XDG_CONFIG_HOME_DEFAULT_PATH_SUFFIX: &str = ".config";
4856 const XDG_DATA_HOME_ENV_VAR: &str = "XDG_DATA_HOME";
4857 const XDG_DATA_HOME_DEFAULT_PATH_SUFFIX: &str = ".local/share";
4858
4859 const PREFIX_CWD: &str = "cwd";
4860 const PREFIX_DEFAULT: &str = "default";
4861 const PREFIX_XDG: &str = "xdg";
4862
4863 fn get_home_value() -> Option<PathBuf> {
4865 var(HOME_ENV_VAR).ok().map(PathBuf::from)
4866 }
4867 fn get_xdg_config_home_value() -> Option<PathBuf> {
4868 var(XDG_CONFIG_HOME_ENV_VAR)
4869 .ok()
4870 .map(PathBuf::from)
4871 .or_else(|| {
4872 get_home_value()
4873 .map(|home_path| home_path.join(XDG_CONFIG_HOME_DEFAULT_PATH_SUFFIX))
4874 })
4875 }
4876 fn get_xdg_data_home_value() -> Option<PathBuf> {
4877 var(XDG_DATA_HOME_ENV_VAR)
4878 .ok()
4879 .map(PathBuf::from)
4880 .or_else(|| {
4881 get_home_value().map(|home_path| home_path.join(XDG_DATA_HOME_DEFAULT_PATH_SUFFIX))
4882 })
4883 }
4884
4885 if path.starts_with(HOME_SHORTCUT) {
4887 if let Some(home_path) = get_home_value() {
4888 path = home_path.join(
4889 path.strip_prefix(HOME_SHORTCUT)
4890 .expect("already checked that it starts with the prefix"),
4891 );
4892 } else {
4893 return None;
4894 }
4895 }
4896
4897 match prefix {
4899 Some(prefix) => match prefix.as_str() {
4900 PREFIX_CWD | PREFIX_DEFAULT => {
4901 let mut new_path = PathBuf::from(CWD_PATH);
4902 new_path.push(path);
4903
4904 Some(new_path)
4905 }
4906 PREFIX_XDG => {
4907 if is_include_path {
4908 get_xdg_config_home_value()
4909 .map(|xdg_config_home_path| xdg_config_home_path.join(path))
4910 } else {
4911 get_xdg_data_home_value()
4912 .map(|xdg_data_home_path| xdg_data_home_path.join(path))
4913 }
4914 }
4915 _ => None, },
4917 None => Some(path),
4918 }
4919}
4920
4921#[cfg(all(feature = "std", feature = "parsing"))]
4923fn get_name_string(name_data: &[u8], name_id: u16) -> Option<String> {
4924 fontcode_get_name(name_data, name_id)
4925 .ok()
4926 .flatten()
4927 .map(|name| String::from_utf8_lossy(name.to_bytes()).to_string())
4928}
4929
4930#[cfg(all(feature = "std", feature = "parsing"))]
4934fn get_verification_codepoints(start: u32, end: u32) -> Vec<u32> {
4935 match start {
4936 0x0000 => vec!['A' as u32, 'M' as u32, 'Z' as u32, 'a' as u32, 'm' as u32, 'z' as u32],
4938 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], _ => {
5014 let range_size = end - start;
5015 if range_size > 20 {
5016 vec![
5017 start + range_size / 5,
5018 start + 2 * range_size / 5,
5019 start + 3 * range_size / 5,
5020 start + 4 * range_size / 5,
5021 ]
5022 } else {
5023 vec![start, start + range_size / 2]
5024 }
5025 }
5026 }
5027}
5028
5029#[cfg(all(feature = "std", feature = "parsing"))]
5032fn find_best_cmap_subtable<'a>(
5033 cmap: &allsorts::tables::cmap::Cmap<'a>,
5034) -> Option<allsorts::tables::cmap::EncodingRecord> {
5035 use allsorts::tables::cmap::{PlatformId, EncodingId};
5036
5037 cmap.find_subtable(PlatformId::UNICODE, EncodingId(3))
5038 .or_else(|| cmap.find_subtable(PlatformId::UNICODE, EncodingId(4)))
5039 .or_else(|| cmap.find_subtable(PlatformId::WINDOWS, EncodingId(1)))
5040 .or_else(|| cmap.find_subtable(PlatformId::WINDOWS, EncodingId(10)))
5041 .or_else(|| cmap.find_subtable(PlatformId::UNICODE, EncodingId(0)))
5042 .or_else(|| cmap.find_subtable(PlatformId::UNICODE, EncodingId(1)))
5043}
5044
5045#[cfg(all(feature = "std", feature = "parsing"))]
5048fn verify_unicode_ranges_with_cmap(
5049 provider: &impl FontTableProvider,
5050 os2_ranges: Vec<UnicodeRange>
5051) -> Vec<UnicodeRange> {
5052 use allsorts::tables::cmap::{Cmap, CmapSubtable};
5053
5054 if os2_ranges.is_empty() {
5055 return Vec::new();
5056 }
5057
5058 let cmap_data = match provider.table_data(tag::CMAP) {
5060 Ok(Some(data)) => data,
5061 _ => return os2_ranges, };
5063
5064 let cmap = match ReadScope::new(&cmap_data).read::<Cmap<'_>>() {
5065 Ok(c) => c,
5066 Err(_) => return os2_ranges,
5067 };
5068
5069 let encoding_record = match find_best_cmap_subtable(&cmap) {
5070 Some(r) => r,
5071 None => return os2_ranges, };
5073
5074 let cmap_subtable = match ReadScope::new(&cmap_data)
5075 .offset(encoding_record.offset as usize)
5076 .read::<CmapSubtable<'_>>()
5077 {
5078 Ok(st) => st,
5079 Err(_) => return os2_ranges,
5080 };
5081
5082 let mut verified_ranges = Vec::new();
5084
5085 for range in os2_ranges {
5086 let test_codepoints = get_verification_codepoints(range.start, range.end);
5087
5088 let required_hits = (test_codepoints.len() + 1) / 2; let mut hits = 0;
5092
5093 for cp in test_codepoints {
5094 if cp >= range.start && cp <= range.end {
5095 if let Ok(Some(gid)) = cmap_subtable.map_glyph(cp) {
5096 if gid != 0 {
5097 hits += 1;
5098 if hits >= required_hits {
5099 break;
5100 }
5101 }
5102 }
5103 }
5104 }
5105
5106 if hits >= required_hits {
5107 verified_ranges.push(range);
5108 }
5109 }
5110
5111 verified_ranges
5112}
5113
5114#[cfg(all(feature = "std", feature = "parsing"))]
5117fn analyze_cmap_coverage(provider: &impl FontTableProvider) -> Option<Vec<UnicodeRange>> {
5118 use allsorts::tables::cmap::{Cmap, CmapSubtable};
5119
5120 let cmap_data = provider.table_data(tag::CMAP).ok()??;
5121 let cmap = ReadScope::new(&cmap_data).read::<Cmap<'_>>().ok()?;
5122
5123 let encoding_record = find_best_cmap_subtable(&cmap)?;
5124
5125 let cmap_subtable = ReadScope::new(&cmap_data)
5126 .offset(encoding_record.offset as usize)
5127 .read::<CmapSubtable<'_>>()
5128 .ok()?;
5129
5130 let blocks_to_check: &[(u32, u32)] = &[
5132 (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), ];
5183
5184 let mut ranges = Vec::new();
5185
5186 for &(start, end) in blocks_to_check {
5187 let test_codepoints = get_verification_codepoints(start, end);
5188 let required_hits = (test_codepoints.len() + 1) / 2;
5189 let allowed_misses = test_codepoints.len() - required_hits;
5194 let mut hits = 0;
5195 let mut misses = 0;
5196
5197 for cp in test_codepoints {
5198 if matches!(cmap_subtable.map_glyph(cp), Ok(Some(gid)) if gid != 0) {
5199 hits += 1;
5200 if hits >= required_hits {
5201 break;
5202 }
5203 } else {
5204 misses += 1;
5205 if misses > allowed_misses {
5206 break;
5207 }
5208 }
5209 }
5210
5211 if hits >= required_hits {
5212 ranges.push(UnicodeRange { start, end });
5213 }
5214 }
5215
5216 if ranges.is_empty() {
5217 None
5218 } else {
5219 Some(ranges)
5220 }
5221}
5222
5223#[cfg(all(feature = "std", feature = "parsing"))]
5225#[allow(dead_code)]
5226fn extract_unicode_ranges(os2_table: &Os2) -> Vec<UnicodeRange> {
5227 let mut unicode_ranges = Vec::new();
5228
5229 let ranges = [
5230 os2_table.ul_unicode_range1,
5231 os2_table.ul_unicode_range2,
5232 os2_table.ul_unicode_range3,
5233 os2_table.ul_unicode_range4,
5234 ];
5235
5236 for &(bit, start, end) in UNICODE_RANGE_MAPPINGS {
5237 let range_idx = bit / 32;
5238 let bit_pos = bit % 32;
5239 if range_idx < 4 && (ranges[range_idx] & (1 << bit_pos)) != 0 {
5240 unicode_ranges.push(UnicodeRange { start, end });
5241 }
5242 }
5243
5244 unicode_ranges
5245}
5246
5247#[cfg(all(feature = "std", feature = "parsing"))]
5249fn detect_monospace(
5250 provider: &impl FontTableProvider,
5251 os2_table: Option<&Os2>,
5252 detected_monospace: Option<bool>,
5253) -> Option<bool> {
5254 if let Some(is_monospace) = detected_monospace {
5255 return Some(is_monospace);
5256 }
5257
5258 if let Some(os2_table) = os2_table {
5261 if os2_table.panose[0] == 2 {
5262 return Some(os2_table.panose[3] == 9); }
5265 }
5266
5267 let hhea_data = provider.table_data(tag::HHEA).ok()??;
5269 let hhea_table = ReadScope::new(&hhea_data).read::<HheaTable>().ok()?;
5270 let maxp_data = provider.table_data(tag::MAXP).ok()??;
5271 let maxp_table = ReadScope::new(&maxp_data).read::<MaxpTable>().ok()?;
5272 let hmtx_data = provider.table_data(tag::HMTX).ok()??;
5273 let hmtx_table = ReadScope::new(&hmtx_data)
5274 .read_dep::<HmtxTable<'_>>((
5275 usize::from(maxp_table.num_glyphs),
5276 usize::from(hhea_table.num_h_metrics),
5277 ))
5278 .ok()?;
5279
5280 let mut monospace = true;
5281 let mut last_advance = 0;
5282
5283 for i in 0..hhea_table.num_h_metrics as usize {
5285 let advance = hmtx_table.h_metrics.read_item(i).ok()?.advance_width;
5286 if i > 0 && advance != last_advance {
5287 monospace = false;
5288 break;
5289 }
5290 last_advance = advance;
5291 }
5292
5293 Some(monospace)
5294}
5295
5296#[cfg(all(feature = "std", not(feature = "parsing")))]
5305fn pattern_from_filename(path: &std::path::Path) -> Option<FcPattern> {
5306 let ext = path.extension()?.to_str()?.to_ascii_lowercase();
5307 match ext.as_str() {
5308 "ttf" | "otf" | "ttc" | "woff" | "woff2" => {}
5309 _ => return None,
5310 }
5311
5312 let stem = path.file_stem()?.to_str()?;
5313 let all_tokens = crate::config::tokenize_lowercase(stem);
5314
5315 let has_token = |kw: &str| all_tokens.iter().any(|t| t == kw);
5317 let is_bold = has_token("bold") || has_token("heavy");
5318 let is_italic = has_token("italic");
5319 let is_oblique = has_token("oblique");
5320 let is_mono = has_token("mono") || has_token("monospace");
5321 let is_condensed = has_token("condensed");
5322
5323 let family_tokens = crate::config::tokenize_font_stem(stem);
5325 if family_tokens.is_empty() { return None; }
5326 let family = family_tokens.join(" ");
5327
5328 Some(FcPattern {
5329 name: Some(stem.to_string()),
5330 family: Some(family),
5331 bold: if is_bold { PatternMatch::True } else { PatternMatch::False },
5332 italic: if is_italic { PatternMatch::True } else { PatternMatch::False },
5333 oblique: if is_oblique { PatternMatch::True } else { PatternMatch::DontCare },
5334 monospace: if is_mono { PatternMatch::True } else { PatternMatch::DontCare },
5335 condensed: if is_condensed { PatternMatch::True } else { PatternMatch::DontCare },
5336 weight: if is_bold { FcWeight::Bold } else { FcWeight::Normal },
5337 stretch: if is_condensed { FcStretch::Condensed } else { FcStretch::Normal },
5338 unicode_ranges: Vec::new(),
5339 metadata: FcFontMetadata::default(),
5340 render_config: FcFontRenderConfig::default(),
5341 })
5342}