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(&self, pattern: &FcPattern, trace: &mut Vec<TraceMsg>) -> Option<FontMatch> {
2214 let state = self.state_read();
2215 let mut matches = Vec::new();
2216
2217 for (stored_pattern, id) in &state.patterns {
2218 if Self::query_matches_internal(stored_pattern, pattern, trace) {
2219 let metadata = state.metadata.get(id).unwrap_or(stored_pattern);
2220
2221 let unicode_compatibility = if pattern.unicode_ranges.is_empty() {
2223 Self::calculate_unicode_coverage(&metadata.unicode_ranges) as i32
2225 } else {
2226 Self::calculate_unicode_compatibility(&pattern.unicode_ranges, &metadata.unicode_ranges)
2228 };
2229
2230 let style_score = Self::calculate_style_score(pattern, metadata);
2231
2232 let is_memory = state.memory_fonts.contains_key(id);
2234
2235 matches.push((*id, unicode_compatibility, style_score, metadata.clone(), is_memory));
2236 }
2237 }
2238
2239 matches.sort_by(|a, b| {
2241 b.4.cmp(&a.4)
2243 .then_with(|| b.1.cmp(&a.1)) .then_with(|| a.2.cmp(&b.2)) });
2246
2247 matches.first().map(|(id, _, _, metadata, _)| {
2248 FontMatch {
2249 id: *id,
2250 unicode_ranges: metadata.unicode_ranges.clone(),
2251 fallbacks: Vec::new(), }
2253 })
2254 }
2255
2256 fn query_internal(&self, pattern: &FcPattern, trace: &mut Vec<TraceMsg>) -> Vec<FontMatch> {
2261 let state = self.state_read();
2262 self.query_internal_locked(&state, pattern, trace)
2263 }
2264
2265 fn query_internal_locked(
2268 &self,
2269 state: &FcFontCacheInner,
2270 pattern: &FcPattern,
2271 trace: &mut Vec<TraceMsg>,
2272 ) -> Vec<FontMatch> {
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
2282 } else {
2283 Self::calculate_unicode_compatibility(&pattern.unicode_ranges, &metadata.unicode_ranges)
2284 };
2285
2286 let style_score = Self::calculate_style_score(pattern, metadata);
2287 matches.push((*id, unicode_compatibility, style_score, metadata.clone()));
2288 }
2289 }
2290
2291 matches.sort_by(|a, b| {
2295 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)) });
2300
2301 matches
2302 .into_iter()
2303 .map(|(id, _, _, metadata)| {
2304 FontMatch {
2305 id,
2306 unicode_ranges: metadata.unicode_ranges.clone(),
2307 fallbacks: Vec::new(), }
2309 })
2310 .collect()
2311 }
2312
2313 pub fn compute_fallbacks(
2317 &self,
2318 font_id: &FontId,
2319 trace: &mut Vec<TraceMsg>,
2320 ) -> Vec<FontMatchNoFallback> {
2321 let state = self.state_read();
2322 let pattern = match state.metadata.get(font_id) {
2323 Some(p) => p.clone(),
2324 None => return Vec::new(),
2325 };
2326 drop(state);
2327
2328 self.compute_fallbacks_for_pattern(&pattern, Some(font_id), trace)
2329 }
2330
2331 fn compute_fallbacks_for_pattern(
2332 &self,
2333 pattern: &FcPattern,
2334 exclude_id: Option<&FontId>,
2335 _trace: &mut Vec<TraceMsg>,
2336 ) -> Vec<FontMatchNoFallback> {
2337 let state = self.state_read();
2338 let mut candidates = Vec::new();
2339
2340 for (stored_pattern, id) in &state.patterns {
2342 if exclude_id.is_some() && exclude_id.unwrap() == id {
2344 continue;
2345 }
2346
2347 if !stored_pattern.unicode_ranges.is_empty() && !pattern.unicode_ranges.is_empty() {
2349 let unicode_compatibility = Self::calculate_unicode_compatibility(
2351 &pattern.unicode_ranges,
2352 &stored_pattern.unicode_ranges
2353 );
2354
2355 if unicode_compatibility > 0 {
2357 let style_score = Self::calculate_style_score(pattern, stored_pattern);
2358 candidates.push((
2359 FontMatchNoFallback {
2360 id: *id,
2361 unicode_ranges: stored_pattern.unicode_ranges.clone(),
2362 },
2363 unicode_compatibility,
2364 style_score,
2365 stored_pattern.clone(),
2366 ));
2367 }
2368 } else if pattern.unicode_ranges.is_empty() && !stored_pattern.unicode_ranges.is_empty() {
2369 let coverage = Self::calculate_unicode_coverage(&stored_pattern.unicode_ranges) as i32;
2371 let style_score = Self::calculate_style_score(pattern, stored_pattern);
2372 candidates.push((
2373 FontMatchNoFallback {
2374 id: *id,
2375 unicode_ranges: stored_pattern.unicode_ranges.clone(),
2376 },
2377 coverage,
2378 style_score,
2379 stored_pattern.clone(),
2380 ));
2381 }
2382 }
2383
2384 drop(state);
2385
2386 candidates.sort_by(|a, b| {
2388 b.1.cmp(&a.1)
2389 .then_with(|| a.2.cmp(&b.2))
2390 });
2391
2392 let mut seen_ranges = Vec::new();
2394 let mut deduplicated = Vec::new();
2395
2396 for (id, _, _, pattern) in candidates {
2397 let mut is_new_range = false;
2398
2399 for range in &pattern.unicode_ranges {
2400 if !seen_ranges.iter().any(|r: &UnicodeRange| r.overlaps(range)) {
2401 seen_ranges.push(*range);
2402 is_new_range = true;
2403 }
2404 }
2405
2406 if is_new_range {
2407 deduplicated.push(id);
2408 }
2409 }
2410
2411 deduplicated
2412 }
2413
2414 pub fn get_memory_font(&self, id: &FontId) -> Option<FcFont> {
2416 self.state_read().memory_fonts.get(id).cloned()
2417 }
2418
2419 fn trace_path(k: &FcPattern) -> String {
2421 k.name.as_ref().cloned().unwrap_or_else(|| "<unknown>".to_string())
2422 }
2423
2424 pub fn query_matches_internal(
2425 k: &FcPattern,
2426 pattern: &FcPattern,
2427 trace: &mut Vec<TraceMsg>,
2428 ) -> bool {
2429 if let Some(ref name) = pattern.name {
2431 if !k.name.as_ref().map_or(false, |kn| kn.contains(name)) {
2432 trace.push(TraceMsg {
2433 level: TraceLevel::Info,
2434 path: Self::trace_path(k),
2435 reason: MatchReason::NameMismatch {
2436 requested: pattern.name.clone(),
2437 found: k.name.clone(),
2438 },
2439 });
2440 return false;
2441 }
2442 }
2443
2444 if let Some(ref family) = pattern.family {
2446 if !k.family.as_ref().map_or(false, |kf| kf.contains(family)) {
2447 trace.push(TraceMsg {
2448 level: TraceLevel::Info,
2449 path: Self::trace_path(k),
2450 reason: MatchReason::FamilyMismatch {
2451 requested: pattern.family.clone(),
2452 found: k.family.clone(),
2453 },
2454 });
2455 return false;
2456 }
2457 }
2458
2459 let style_properties = [
2461 (
2462 "italic",
2463 pattern.italic.needs_to_match(),
2464 pattern.italic.matches(&k.italic),
2465 ),
2466 (
2467 "oblique",
2468 pattern.oblique.needs_to_match(),
2469 pattern.oblique.matches(&k.oblique),
2470 ),
2471 (
2472 "bold",
2473 pattern.bold.needs_to_match(),
2474 pattern.bold.matches(&k.bold),
2475 ),
2476 (
2477 "monospace",
2478 pattern.monospace.needs_to_match(),
2479 pattern.monospace.matches(&k.monospace),
2480 ),
2481 (
2482 "condensed",
2483 pattern.condensed.needs_to_match(),
2484 pattern.condensed.matches(&k.condensed),
2485 ),
2486 ];
2487
2488 for (property_name, needs_to_match, matches) in style_properties {
2489 if needs_to_match && !matches {
2490 let (requested, found) = match property_name {
2491 "italic" => (format!("{:?}", pattern.italic), format!("{:?}", k.italic)),
2492 "oblique" => (format!("{:?}", pattern.oblique), format!("{:?}", k.oblique)),
2493 "bold" => (format!("{:?}", pattern.bold), format!("{:?}", k.bold)),
2494 "monospace" => (
2495 format!("{:?}", pattern.monospace),
2496 format!("{:?}", k.monospace),
2497 ),
2498 "condensed" => (
2499 format!("{:?}", pattern.condensed),
2500 format!("{:?}", k.condensed),
2501 ),
2502 _ => (String::new(), String::new()),
2503 };
2504
2505 trace.push(TraceMsg {
2506 level: TraceLevel::Info,
2507 path: Self::trace_path(k),
2508 reason: MatchReason::StyleMismatch {
2509 property: property_name,
2510 requested,
2511 found,
2512 },
2513 });
2514 return false;
2515 }
2516 }
2517
2518 if pattern.weight != FcWeight::Normal && pattern.weight != k.weight {
2520 trace.push(TraceMsg {
2521 level: TraceLevel::Info,
2522 path: Self::trace_path(k),
2523 reason: MatchReason::WeightMismatch {
2524 requested: pattern.weight,
2525 found: k.weight,
2526 },
2527 });
2528 return false;
2529 }
2530
2531 if pattern.stretch != FcStretch::Normal && pattern.stretch != k.stretch {
2533 trace.push(TraceMsg {
2534 level: TraceLevel::Info,
2535 path: Self::trace_path(k),
2536 reason: MatchReason::StretchMismatch {
2537 requested: pattern.stretch,
2538 found: k.stretch,
2539 },
2540 });
2541 return false;
2542 }
2543
2544 if !pattern.unicode_ranges.is_empty() {
2546 let mut has_overlap = false;
2547
2548 for p_range in &pattern.unicode_ranges {
2549 for k_range in &k.unicode_ranges {
2550 if p_range.overlaps(k_range) {
2551 has_overlap = true;
2552 break;
2553 }
2554 }
2555 if has_overlap {
2556 break;
2557 }
2558 }
2559
2560 if !has_overlap {
2561 trace.push(TraceMsg {
2562 level: TraceLevel::Info,
2563 path: Self::trace_path(k),
2564 reason: MatchReason::UnicodeRangeMismatch {
2565 character: '\0', ranges: k.unicode_ranges.clone(),
2567 },
2568 });
2569 return false;
2570 }
2571 }
2572
2573 true
2574 }
2575
2576 #[cfg(feature = "std")]
2602 pub fn resolve_font_chain(
2603 &self,
2604 font_families: &[String],
2605 weight: FcWeight,
2606 italic: PatternMatch,
2607 oblique: PatternMatch,
2608 trace: &mut Vec<TraceMsg>,
2609 ) -> FontFallbackChain {
2610 self.resolve_font_chain_with_os(font_families, weight, italic, oblique, trace, OperatingSystem::current())
2611 }
2612
2613 #[cfg(feature = "std")]
2615 pub fn resolve_font_chain_with_os(
2616 &self,
2617 font_families: &[String],
2618 weight: FcWeight,
2619 italic: PatternMatch,
2620 oblique: PatternMatch,
2621 trace: &mut Vec<TraceMsg>,
2622 os: OperatingSystem,
2623 ) -> FontFallbackChain {
2624 self.resolve_font_chain_impl(font_families, weight, italic, oblique, None, trace, os)
2625 }
2626
2627 #[cfg(feature = "std")]
2642 pub fn resolve_font_chain_with_scripts(
2643 &self,
2644 font_families: &[String],
2645 weight: FcWeight,
2646 italic: PatternMatch,
2647 oblique: PatternMatch,
2648 scripts_hint: Option<&[UnicodeRange]>,
2649 trace: &mut Vec<TraceMsg>,
2650 ) -> FontFallbackChain {
2651 self.resolve_font_chain_impl(
2652 font_families, weight, italic, oblique, scripts_hint,
2653 trace, OperatingSystem::current(),
2654 )
2655 }
2656
2657 #[cfg(feature = "std")]
2661 fn resolve_font_chain_impl(
2662 &self,
2663 font_families: &[String],
2664 weight: FcWeight,
2665 italic: PatternMatch,
2666 oblique: PatternMatch,
2667 scripts_hint: Option<&[UnicodeRange]>,
2668 trace: &mut Vec<TraceMsg>,
2669 os: OperatingSystem,
2670 ) -> FontFallbackChain {
2671 let scripts_hint_hash = scripts_hint.map(hash_scripts_hint);
2675 let cache_key = FontChainCacheKey {
2676 font_families: font_families.to_vec(),
2677 weight,
2678 italic,
2679 oblique,
2680 scripts_hint_hash,
2681 };
2682
2683 if let Some(cached) = self
2684 .shared
2685 .chain_cache
2686 .lock()
2687 .ok()
2688 .and_then(|c| c.get(&cache_key).cloned())
2689 {
2690 return cached;
2691 }
2692
2693 let expanded_families = expand_font_families(font_families, os, &[]);
2695
2696 let generic_fallbacks: Vec<String> = font_families
2704 .iter()
2705 .filter(|f| config::is_generic_family(f))
2706 .cloned()
2707 .collect();
2708
2709 let chain = self.resolve_font_chain_uncached(
2711 &expanded_families,
2712 &generic_fallbacks,
2713 weight,
2714 italic,
2715 oblique,
2716 scripts_hint,
2717 trace,
2718 );
2719
2720 if let Ok(mut cache) = self.shared.chain_cache.lock() {
2722 cache.insert(cache_key, chain.clone());
2723 }
2724
2725 chain
2726 }
2727
2728 #[cfg(feature = "std")]
2736 fn resolve_font_chain_uncached(
2737 &self,
2738 font_families: &[String],
2739 generic_fallbacks: &[String],
2740 weight: FcWeight,
2741 italic: PatternMatch,
2742 oblique: PatternMatch,
2743 scripts_hint: Option<&[UnicodeRange]>,
2744 trace: &mut Vec<TraceMsg>,
2745 ) -> FontFallbackChain {
2746 let mut css_fallbacks = Vec::new();
2747
2748 for (_i, family) in font_families.iter().enumerate() {
2750 let (pattern, is_generic) = if config::is_generic_family(family) {
2752 let monospace = if family.eq_ignore_ascii_case("monospace") {
2753 PatternMatch::True
2754 } else {
2755 PatternMatch::False
2756 };
2757 let pattern = FcPattern {
2758 name: None,
2759 weight,
2760 italic,
2761 oblique,
2762 monospace,
2763 unicode_ranges: Vec::new(),
2764 ..Default::default()
2765 };
2766 (pattern, true)
2767 } else {
2768 let pattern = FcPattern {
2770 name: Some(family.clone()),
2771 weight,
2772 italic,
2773 oblique,
2774 unicode_ranges: Vec::new(),
2775 ..Default::default()
2776 };
2777 (pattern, false)
2778 };
2779
2780 let mut matches = if is_generic {
2783 self.query_internal(&pattern, trace)
2785 } else {
2786 let mut m = self.fuzzy_query_by_name(family, weight, italic, oblique, &[], trace);
2788 if m.is_empty() {
2801 m = self.query_by_family_normalized(family, weight, italic, oblique);
2802 }
2803 m
2804 };
2805
2806 if is_generic && matches.len() > 5 {
2808 matches.truncate(5);
2809 }
2810
2811 css_fallbacks.push(CssFallbackGroup {
2814 css_name: family.clone(),
2815 fonts: matches,
2816 });
2817 }
2818
2819 if !generic_fallbacks.is_empty()
2836 && css_fallbacks.iter().all(|g| g.fonts.is_empty())
2837 {
2838 for generic in generic_fallbacks {
2839 let monospace = if generic.eq_ignore_ascii_case("monospace") {
2840 PatternMatch::True
2841 } else {
2842 PatternMatch::False
2843 };
2844 let pattern = FcPattern {
2845 name: None,
2846 weight,
2847 italic,
2848 oblique,
2849 monospace,
2850 unicode_ranges: Vec::new(),
2851 ..Default::default()
2852 };
2853 let mut matches = self.query_internal(&pattern, trace);
2854 if matches.len() > 5 {
2855 matches.truncate(5);
2856 }
2857 if !matches.is_empty() {
2858 css_fallbacks.push(CssFallbackGroup {
2859 css_name: generic.clone(),
2860 fonts: matches,
2861 });
2862 }
2863 }
2864 }
2865
2866 let important_ranges: &[UnicodeRange] =
2879 scripts_hint.unwrap_or(DEFAULT_UNICODE_FALLBACK_SCRIPTS);
2880 let unicode_fallbacks = if important_ranges.is_empty() {
2881 Vec::new()
2882 } else {
2883 let all_uncovered = vec![false; important_ranges.len()];
2884 self.find_unicode_fallbacks(
2885 important_ranges,
2886 &all_uncovered,
2887 &css_fallbacks,
2888 weight,
2889 italic,
2890 oblique,
2891 trace,
2892 )
2893 };
2894
2895 let mut unicode_fallbacks = unicode_fallbacks;
2903 if css_fallbacks.is_empty() && unicode_fallbacks.is_empty() {
2904 let st = self.state_read();
2905 if let Some((pat, id)) = st.patterns.iter().next() {
2906 unicode_fallbacks.push(FontMatch {
2907 id: *id,
2908 unicode_ranges: pat.unicode_ranges.clone(),
2909 fallbacks: Vec::new(),
2910 });
2911 }
2912 }
2913
2914 FontFallbackChain {
2915 css_fallbacks,
2916 unicode_fallbacks,
2917 original_stack: font_families.to_vec(),
2918 }
2919 }
2920
2921 #[allow(dead_code)]
2923 fn extract_unicode_ranges(text: &str) -> Vec<UnicodeRange> {
2924 let mut chars: Vec<char> = text.chars().collect();
2925 chars.sort_unstable();
2926 chars.dedup();
2927
2928 if chars.is_empty() {
2929 return Vec::new();
2930 }
2931
2932 let mut ranges = Vec::new();
2933 let mut range_start = chars[0] as u32;
2934 let mut range_end = range_start;
2935
2936 for &c in &chars[1..] {
2937 let codepoint = c as u32;
2938 if codepoint == range_end + 1 {
2939 range_end = codepoint;
2940 } else {
2941 ranges.push(UnicodeRange { start: range_start, end: range_end });
2942 range_start = codepoint;
2943 range_end = codepoint;
2944 }
2945 }
2946
2947 ranges.push(UnicodeRange { start: range_start, end: range_end });
2948 ranges
2949 }
2950
2951 #[cfg(feature = "std")]
2958 fn fuzzy_query_by_name(
2959 &self,
2960 requested_name: &str,
2961 weight: FcWeight,
2962 italic: PatternMatch,
2963 oblique: PatternMatch,
2964 unicode_ranges: &[UnicodeRange],
2965 _trace: &mut Vec<TraceMsg>,
2966 ) -> Vec<FontMatch> {
2967 let tokens = Self::extract_font_name_tokens(requested_name);
2969
2970 if tokens.is_empty() {
2971 return Vec::new();
2972 }
2973
2974 let tokens_lower: Vec<String> = tokens.iter().map(|t| t.to_ascii_lowercase()).collect();
2976
2977 let state = self.state_read();
2983
2984 let first_token = &tokens_lower[0];
2986 let mut candidate_ids = match state.token_index.get(first_token) {
2987 Some(ids) if !ids.is_empty() => ids.clone(),
2988 _ => {
2989 return Vec::new();
2991 }
2992 };
2993
2994 for token in &tokens_lower[1..] {
2996 if let Some(token_ids) = state.token_index.get(token) {
2997 let intersection: alloc::collections::BTreeSet<FontId> =
2999 candidate_ids.intersection(token_ids).copied().collect();
3000
3001 if intersection.is_empty() {
3002 break;
3004 } else {
3005 candidate_ids = intersection;
3007 }
3008 } else {
3009 break;
3011 }
3012 }
3013
3014 let mut candidates = Vec::new();
3016
3017 for id in candidate_ids {
3018 let pattern = match state.metadata.get(&id) {
3019 Some(p) => p,
3020 None => continue,
3021 };
3022
3023 let font_tokens_lower = match state.font_tokens.get(&id) {
3025 Some(tokens) => tokens,
3026 None => continue,
3027 };
3028
3029 if font_tokens_lower.is_empty() {
3030 continue;
3031 }
3032
3033 let token_matches = tokens_lower.iter()
3036 .filter(|req_token| {
3037 font_tokens_lower.iter().any(|font_token| {
3038 font_token == *req_token
3040 })
3041 })
3042 .count();
3043
3044 if token_matches == 0 {
3046 continue;
3047 }
3048
3049 let token_similarity = (token_matches * 100 / tokens.len()) as i32;
3051
3052 let unicode_similarity = if !unicode_ranges.is_empty() && !pattern.unicode_ranges.is_empty() {
3054 Self::calculate_unicode_compatibility(unicode_ranges, &pattern.unicode_ranges)
3055 } else {
3056 0
3057 };
3058
3059 if !unicode_ranges.is_empty() && unicode_similarity == 0 {
3062 continue;
3063 }
3064
3065 let style_score = Self::calculate_style_score(&FcPattern {
3066 weight,
3067 italic,
3068 oblique,
3069 ..Default::default()
3070 }, pattern);
3071
3072 candidates.push((
3073 id,
3074 token_similarity,
3075 unicode_similarity,
3076 style_score,
3077 pattern.clone(),
3078 ));
3079 }
3080
3081 candidates.sort_by(|a, b| {
3087 if !unicode_ranges.is_empty() {
3088 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 {
3095 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)) }
3101 });
3102
3103 candidates.truncate(5);
3105
3106 candidates
3108 .into_iter()
3109 .map(|(id, _token_sim, _unicode_sim, _style, pattern)| {
3110 FontMatch {
3111 id,
3112 unicode_ranges: pattern.unicode_ranges.clone(),
3113 fallbacks: Vec::new(), }
3115 })
3116 .collect()
3117 }
3118
3119 fn query_by_family_normalized(
3136 &self,
3137 family: &str,
3138 weight: FcWeight,
3139 italic: PatternMatch,
3140 oblique: PatternMatch,
3141 ) -> Vec<FontMatch> {
3142 let target = crate::utils::normalize_family_name(family);
3143 if target.is_empty() {
3144 return Vec::new();
3145 }
3146 let query = FcPattern {
3147 weight,
3148 italic,
3149 oblique,
3150 ..Default::default()
3151 };
3152 let state = self.state_read();
3153 let mut candidates: Vec<(FontId, i32, FcPattern)> = Vec::new();
3154 for (stored_pattern, id) in &state.patterns {
3155 let meta = state.metadata.get(id).unwrap_or(stored_pattern);
3156 let fam_norm = meta
3157 .family
3158 .as_deref()
3159 .map(crate::utils::normalize_family_name)
3160 .unwrap_or_default();
3161 let matches_family = fam_norm == target
3162 || meta
3163 .name
3164 .as_deref()
3165 .map(crate::utils::normalize_family_name)
3166 .is_some_and(|n| n == target);
3167 if !matches_family {
3168 continue;
3169 }
3170 let style_score = Self::calculate_style_score(&query, meta);
3171 candidates.push((*id, style_score, meta.clone()));
3172 }
3173 drop(state);
3174
3175 candidates.sort_by(|a, b| {
3177 a.1.cmp(&b.1)
3178 .then_with(|| a.2.italic.cmp(&b.2.italic))
3179 .then_with(|| a.2.name.cmp(&b.2.name))
3180 });
3181 candidates.truncate(5);
3182 candidates
3183 .into_iter()
3184 .map(|(id, _, pattern)| FontMatch {
3185 id,
3186 unicode_ranges: pattern.unicode_ranges.clone(),
3187 fallbacks: Vec::new(),
3188 })
3189 .collect()
3190 }
3191
3192 pub fn extract_font_name_tokens(name: &str) -> Vec<String> {
3196 let mut tokens = Vec::new();
3197 let mut current_token = String::new();
3198 let mut last_was_lower = false;
3199
3200 for c in name.chars() {
3201 if c.is_whitespace() || c == '-' || c == '_' {
3202 if !current_token.is_empty() {
3204 tokens.push(current_token.clone());
3205 current_token.clear();
3206 }
3207 last_was_lower = false;
3208 } else if c.is_uppercase() && last_was_lower && !current_token.is_empty() {
3209 tokens.push(current_token.clone());
3211 current_token.clear();
3212 current_token.push(c);
3213 last_was_lower = false;
3214 } else {
3215 current_token.push(c);
3216 last_was_lower = c.is_lowercase();
3217 }
3218 }
3219
3220 if !current_token.is_empty() {
3221 tokens.push(current_token);
3222 }
3223
3224 tokens
3225 }
3226
3227 fn find_unicode_fallbacks(
3231 &self,
3232 unicode_ranges: &[UnicodeRange],
3233 covered_chars: &[bool],
3234 existing_groups: &[CssFallbackGroup],
3235 _weight: FcWeight,
3236 _italic: PatternMatch,
3237 _oblique: PatternMatch,
3238 trace: &mut Vec<TraceMsg>,
3239 ) -> Vec<FontMatch> {
3240 let mut uncovered_ranges = Vec::new();
3242 for (i, &covered) in covered_chars.iter().enumerate() {
3243 if !covered && i < unicode_ranges.len() {
3244 uncovered_ranges.push(unicode_ranges[i].clone());
3245 }
3246 }
3247
3248 if uncovered_ranges.is_empty() {
3249 return Vec::new();
3250 }
3251
3252 let pattern = FcPattern {
3257 name: None,
3258 weight: FcWeight::Normal, italic: PatternMatch::DontCare,
3260 oblique: PatternMatch::DontCare,
3261 unicode_ranges: uncovered_ranges.clone(),
3262 ..Default::default()
3263 };
3264
3265 let mut candidates = self.query_internal(&pattern, trace);
3266
3267 let existing_prefixes: Vec<String> = existing_groups
3270 .iter()
3271 .flat_map(|group| {
3272 group.fonts.iter().filter_map(|font| {
3273 self.get_metadata_by_id(&font.id)
3274 .and_then(|meta| meta.family.clone())
3275 .and_then(|family| {
3276 family.split_whitespace()
3278 .take(2)
3279 .collect::<Vec<_>>()
3280 .join(" ")
3281 .into()
3282 })
3283 })
3284 })
3285 .collect();
3286
3287 candidates.sort_by(|a, b| {
3291 let a_meta = self.get_metadata_by_id(&a.id);
3292 let b_meta = self.get_metadata_by_id(&b.id);
3293
3294 let a_score = Self::calculate_font_similarity_score(a_meta.as_ref(), &existing_prefixes);
3295 let b_score = Self::calculate_font_similarity_score(b_meta.as_ref(), &existing_prefixes);
3296
3297 b_score.cmp(&a_score) .then_with(|| {
3299 let a_coverage = Self::calculate_unicode_compatibility(&uncovered_ranges, &a.unicode_ranges);
3300 let b_coverage = Self::calculate_unicode_compatibility(&uncovered_ranges, &b.unicode_ranges);
3301 b_coverage.cmp(&a_coverage)
3302 })
3303 });
3304
3305 let mut result = Vec::new();
3307 let mut remaining_uncovered: Vec<bool> = vec![true; uncovered_ranges.len()];
3308
3309 for candidate in candidates {
3310 let mut covers_new_range = false;
3312
3313 for (i, range) in uncovered_ranges.iter().enumerate() {
3314 if remaining_uncovered[i] {
3315 for font_range in &candidate.unicode_ranges {
3317 if font_range.overlaps(range) {
3318 remaining_uncovered[i] = false;
3319 covers_new_range = true;
3320 break;
3321 }
3322 }
3323 }
3324 }
3325
3326 if covers_new_range {
3328 result.push(candidate);
3329
3330 if remaining_uncovered.iter().all(|&uncovered| !uncovered) {
3332 break;
3333 }
3334 }
3335 }
3336
3337 result
3338 }
3339
3340 fn calculate_font_similarity_score(
3343 font_meta: Option<&FcPattern>,
3344 existing_prefixes: &[String],
3345 ) -> i32 {
3346 let Some(meta) = font_meta else { return 0; };
3347 let Some(family) = &meta.family else { return 0; };
3348
3349 for prefix in existing_prefixes {
3351 if family.starts_with(prefix) {
3352 return 100; }
3354 if family.contains(prefix) {
3355 return 50; }
3357 }
3358
3359 0 }
3361
3362 pub fn calculate_unicode_coverage(ranges: &[UnicodeRange]) -> u64 {
3365 ranges
3366 .iter()
3367 .map(|range| (range.end - range.start + 1) as u64)
3368 .sum()
3369 }
3370
3371 pub fn calculate_unicode_compatibility(
3374 requested: &[UnicodeRange],
3375 available: &[UnicodeRange],
3376 ) -> i32 {
3377 if requested.is_empty() {
3378 return Self::calculate_unicode_coverage(available) as i32;
3380 }
3381
3382 let mut total_coverage = 0u32;
3383
3384 for req_range in requested {
3385 for avail_range in available {
3386 let overlap_start = req_range.start.max(avail_range.start);
3388 let overlap_end = req_range.end.min(avail_range.end);
3389
3390 if overlap_start <= overlap_end {
3391 let overlap_size = overlap_end - overlap_start + 1;
3393 total_coverage += overlap_size;
3394 }
3395 }
3396 }
3397
3398 total_coverage as i32
3399 }
3400
3401 pub fn calculate_style_score(original: &FcPattern, candidate: &FcPattern) -> i32 {
3402
3403 let mut score = 0_i32;
3404
3405 if (original.bold == PatternMatch::True && candidate.weight == FcWeight::Bold)
3407 || (original.bold == PatternMatch::False && candidate.weight != FcWeight::Bold)
3408 {
3409 } else {
3412 let weight_diff = (original.weight as i32 - candidate.weight as i32).abs();
3414 score += weight_diff as i32;
3415 }
3416
3417 if original.weight == candidate.weight {
3420 score -= 15;
3421 if original.weight == FcWeight::Normal {
3422 score -= 10; }
3424 }
3425
3426 if (original.condensed == PatternMatch::True && candidate.stretch.is_condensed())
3428 || (original.condensed == PatternMatch::False && !candidate.stretch.is_condensed())
3429 {
3430 } else {
3433 let stretch_diff = (original.stretch as i32 - candidate.stretch as i32).abs();
3435 score += (stretch_diff * 100) as i32;
3436 }
3437
3438 let style_props = [
3440 (original.italic, candidate.italic, 300, 150),
3441 (original.oblique, candidate.oblique, 200, 100),
3442 (original.bold, candidate.bold, 300, 150),
3443 (original.monospace, candidate.monospace, 100, 50),
3444 (original.condensed, candidate.condensed, 100, 50),
3445 ];
3446
3447 for (orig, cand, mismatch_penalty, dontcare_penalty) in style_props {
3448 if orig.needs_to_match() {
3449 if orig == PatternMatch::False && cand == PatternMatch::DontCare {
3450 score += dontcare_penalty / 2;
3453 } else if !orig.matches(&cand) {
3454 if cand == PatternMatch::DontCare {
3455 score += dontcare_penalty;
3456 } else {
3457 score += mismatch_penalty;
3458 }
3459 } else if orig == PatternMatch::True && cand == PatternMatch::True {
3460 score -= 20;
3462 } else if orig == PatternMatch::False && cand == PatternMatch::False {
3463 score -= 20;
3466 }
3467 } else {
3468 if cand == PatternMatch::True {
3473 score += dontcare_penalty / 3;
3474 }
3475 }
3476 }
3477
3478 if let (Some(name), Some(family)) = (&candidate.name, &candidate.family) {
3484 let name_lower = name.to_ascii_lowercase();
3485 let family_lower = family.to_ascii_lowercase();
3486
3487 let extra = if name_lower.starts_with(&family_lower) {
3489 name_lower[family_lower.len()..].to_string()
3490 } else {
3491 String::new()
3492 };
3493
3494 let stripped = extra
3496 .replace("regular", "")
3497 .replace("normal", "")
3498 .replace("book", "")
3499 .replace("roman", "");
3500 let stripped = stripped.trim();
3501
3502 if stripped.is_empty() {
3503 score -= 50;
3505 } else {
3506 let extra_words = stripped.split_whitespace().count();
3508 score += (extra_words as i32) * 25;
3509 }
3510 }
3511
3512 if let Some(ref subfamily) = candidate.metadata.font_subfamily {
3516 let sf_lower = subfamily.to_ascii_lowercase();
3517 if sf_lower == "regular" {
3518 score -= 30;
3519 }
3520 }
3521
3522 score
3523 }
3524}
3525
3526#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3527fn FcScanDirectories() -> Option<(Vec<(FcPattern, FcFontPath)>, BTreeMap<String, FcFontRenderConfig>)> {
3528 use std::fs;
3529 use std::path::Path;
3530
3531 const BASE_FONTCONFIG_PATH: &str = "/etc/fonts/fonts.conf";
3532
3533 if !Path::new(BASE_FONTCONFIG_PATH).exists() {
3534 return None;
3535 }
3536
3537 let mut font_paths = Vec::with_capacity(32);
3538 let mut paths_to_visit = vec![(None, PathBuf::from(BASE_FONTCONFIG_PATH))];
3539 let mut render_configs: BTreeMap<String, FcFontRenderConfig> = BTreeMap::new();
3540
3541 while let Some((prefix, path_to_visit)) = paths_to_visit.pop() {
3542 let path = match process_path(&prefix, path_to_visit, true) {
3543 Some(path) => path,
3544 None => continue,
3545 };
3546
3547 let metadata = match fs::metadata(&path) {
3548 Ok(metadata) => metadata,
3549 Err(_) => continue,
3550 };
3551
3552 if metadata.is_file() {
3553 let xml_utf8 = match fs::read_to_string(&path) {
3554 Ok(xml_utf8) => xml_utf8,
3555 Err(_) => continue,
3556 };
3557
3558 if ParseFontsConf(&xml_utf8, &mut paths_to_visit, &mut font_paths).is_none() {
3559 continue;
3560 }
3561
3562 ParseFontsConfRenderConfig(&xml_utf8, &mut render_configs);
3564 } else if metadata.is_dir() {
3565 let dir_entries = match fs::read_dir(&path) {
3566 Ok(dir_entries) => dir_entries,
3567 Err(_) => continue,
3568 };
3569
3570 for entry_result in dir_entries {
3571 let entry = match entry_result {
3572 Ok(entry) => entry,
3573 Err(_) => continue,
3574 };
3575
3576 let entry_path = entry.path();
3577
3578 let entry_metadata = match fs::metadata(&entry_path) {
3580 Ok(metadata) => metadata,
3581 Err(_) => continue,
3582 };
3583
3584 if !entry_metadata.is_file() {
3585 continue;
3586 }
3587
3588 let file_name = match entry_path.file_name() {
3589 Some(name) => name,
3590 None => continue,
3591 };
3592
3593 let file_name_str = file_name.to_string_lossy();
3594 if file_name_str.starts_with(|c: char| c.is_ascii_digit())
3595 && file_name_str.ends_with(".conf")
3596 {
3597 paths_to_visit.push((None, entry_path));
3598 }
3599 }
3600 }
3601 }
3602
3603 if font_paths.is_empty() {
3604 return None;
3605 }
3606
3607 Some((FcScanDirectoriesInner(&font_paths), render_configs))
3608}
3609
3610#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3612fn ParseFontsConf(
3613 input: &str,
3614 paths_to_visit: &mut Vec<(Option<String>, PathBuf)>,
3615 font_paths: &mut Vec<(Option<String>, String)>,
3616) -> Option<()> {
3617 use xmlparser::Token::*;
3618 use xmlparser::Tokenizer;
3619
3620 const TAG_INCLUDE: &str = "include";
3621 const TAG_DIR: &str = "dir";
3622 const ATTRIBUTE_PREFIX: &str = "prefix";
3623
3624 let mut current_prefix: Option<&str> = None;
3625 let mut current_path: Option<&str> = None;
3626 let mut is_in_include = false;
3627 let mut is_in_dir = false;
3628
3629 for token_result in Tokenizer::from(input) {
3630 let token = match token_result {
3631 Ok(token) => token,
3632 Err(_) => return None,
3633 };
3634
3635 match token {
3636 ElementStart { local, .. } => {
3637 if is_in_include || is_in_dir {
3638 return None; }
3640
3641 match local.as_str() {
3642 TAG_INCLUDE => {
3643 is_in_include = true;
3644 }
3645 TAG_DIR => {
3646 is_in_dir = true;
3647 }
3648 _ => continue,
3649 }
3650
3651 current_path = None;
3652 }
3653 Text { text, .. } => {
3654 let text = text.as_str().trim();
3655 if text.is_empty() {
3656 continue;
3657 }
3658 if is_in_include || is_in_dir {
3659 current_path = Some(text);
3660 }
3661 }
3662 Attribute { local, value, .. } => {
3663 if !is_in_include && !is_in_dir {
3664 continue;
3665 }
3666 if local.as_str() == ATTRIBUTE_PREFIX {
3668 current_prefix = Some(value.as_str());
3669 }
3670 }
3671 ElementEnd { end, .. } => {
3672 let end_tag = match end {
3673 xmlparser::ElementEnd::Close(_, a) => a,
3674 _ => continue,
3675 };
3676
3677 match end_tag.as_str() {
3678 TAG_INCLUDE => {
3679 if !is_in_include {
3680 continue;
3681 }
3682
3683 if let Some(current_path) = current_path.as_ref() {
3684 paths_to_visit.push((
3685 current_prefix.map(ToOwned::to_owned),
3686 PathBuf::from(*current_path),
3687 ));
3688 }
3689 }
3690 TAG_DIR => {
3691 if !is_in_dir {
3692 continue;
3693 }
3694
3695 if let Some(current_path) = current_path.as_ref() {
3696 font_paths.push((
3697 current_prefix.map(ToOwned::to_owned),
3698 (*current_path).to_owned(),
3699 ));
3700 }
3701 }
3702 _ => continue,
3703 }
3704
3705 is_in_include = false;
3706 is_in_dir = false;
3707 current_path = None;
3708 current_prefix = None;
3709 }
3710 _ => {}
3711 }
3712 }
3713
3714 Some(())
3715}
3716
3717#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3729fn ParseFontsConfRenderConfig(
3730 input: &str,
3731 configs: &mut BTreeMap<String, FcFontRenderConfig>,
3732) {
3733 use xmlparser::Token::*;
3734 use xmlparser::Tokenizer;
3735
3736 #[derive(Clone, Copy, PartialEq)]
3738 enum State {
3739 Idle,
3741 InMatchFont,
3743 InTestFamily,
3745 InEdit,
3747 InValue,
3749 }
3750
3751 let mut state = State::Idle;
3752 let mut match_is_font_target = false;
3753 let mut current_family: Option<String> = None;
3754 let mut current_edit_name: Option<String> = None;
3755 let mut current_value: Option<String> = None;
3756 let mut value_tag: Option<String> = None;
3757 let mut config = FcFontRenderConfig::default();
3758 let mut in_test = false;
3759 let mut test_name: Option<String> = None;
3760
3761 for token_result in Tokenizer::from(input) {
3762 let token = match token_result {
3763 Ok(token) => token,
3764 Err(_) => continue,
3765 };
3766
3767 match token {
3768 ElementStart { local, .. } => {
3769 let tag = local.as_str();
3770 match tag {
3771 "match" => {
3772 match_is_font_target = false;
3774 current_family = None;
3775 config = FcFontRenderConfig::default();
3776 }
3777 "test" if state == State::InMatchFont => {
3778 in_test = true;
3779 test_name = None;
3780 }
3781 "edit" if state == State::InMatchFont => {
3782 current_edit_name = None;
3783 }
3784 "bool" | "double" | "const" | "string" | "int" => {
3785 if state == State::InTestFamily || state == State::InEdit {
3786 value_tag = Some(tag.to_owned());
3787 current_value = None;
3788 }
3789 }
3790 _ => {}
3791 }
3792 }
3793 Attribute { local, value, .. } => {
3794 let attr_name = local.as_str();
3795 let attr_value = value.as_str();
3796
3797 match attr_name {
3798 "target" => {
3799 if attr_value == "font" {
3800 match_is_font_target = true;
3801 }
3802 }
3803 "name" => {
3804 if in_test && state == State::InMatchFont {
3805 test_name = Some(attr_value.to_owned());
3806 } else if state == State::InMatchFont {
3807 current_edit_name = Some(attr_value.to_owned());
3808 }
3809 }
3810 _ => {}
3811 }
3812 }
3813 Text { text, .. } => {
3814 let text = text.as_str().trim();
3815 if !text.is_empty() && (state == State::InTestFamily || state == State::InEdit) {
3816 current_value = Some(text.to_owned());
3817 }
3818 }
3819 ElementEnd { end, .. } => {
3820 match end {
3821 xmlparser::ElementEnd::Open => {
3822 if match_is_font_target && state == State::Idle {
3824 state = State::InMatchFont;
3825 match_is_font_target = false;
3826 } else if in_test {
3827 if test_name.as_deref() == Some("family") {
3828 state = State::InTestFamily;
3829 }
3830 in_test = false;
3831 } else if current_edit_name.is_some() && state == State::InMatchFont {
3832 state = State::InEdit;
3833 }
3834 }
3835 xmlparser::ElementEnd::Close(_, local) => {
3836 let tag = local.as_str();
3837 match tag {
3838 "match" => {
3839 if let Some(family) = current_family.take() {
3841 let empty = FcFontRenderConfig::default();
3842 if config != empty {
3843 configs.insert(family, config.clone());
3844 }
3845 }
3846 state = State::Idle;
3847 config = FcFontRenderConfig::default();
3848 }
3849 "test" => {
3850 if state == State::InTestFamily {
3851 if let Some(ref val) = current_value {
3853 current_family = Some(val.clone());
3854 }
3855 state = State::InMatchFont;
3856 }
3857 current_value = None;
3858 value_tag = None;
3859 }
3860 "edit" => {
3861 if state == State::InEdit {
3862 if let (Some(ref name), Some(ref val)) = (¤t_edit_name, ¤t_value) {
3864 apply_edit_value(&mut config, name, val, value_tag.as_deref());
3865 }
3866 state = State::InMatchFont;
3867 }
3868 current_edit_name = None;
3869 current_value = None;
3870 value_tag = None;
3871 }
3872 "bool" | "double" | "const" | "string" | "int" => {
3873 }
3875 _ => {}
3876 }
3877 }
3878 xmlparser::ElementEnd::Empty => {
3879 }
3881 }
3882 }
3883 _ => {}
3884 }
3885 }
3886}
3887
3888#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3890fn apply_edit_value(
3891 config: &mut FcFontRenderConfig,
3892 edit_name: &str,
3893 value: &str,
3894 value_tag: Option<&str>,
3895) {
3896 match edit_name {
3897 "antialias" => {
3898 config.antialias = parse_bool_value(value);
3899 }
3900 "hinting" => {
3901 config.hinting = parse_bool_value(value);
3902 }
3903 "autohint" => {
3904 config.autohint = parse_bool_value(value);
3905 }
3906 "embeddedbitmap" => {
3907 config.embeddedbitmap = parse_bool_value(value);
3908 }
3909 "embolden" => {
3910 config.embolden = parse_bool_value(value);
3911 }
3912 "minspace" => {
3913 config.minspace = parse_bool_value(value);
3914 }
3915 "hintstyle" => {
3916 config.hintstyle = parse_hintstyle_const(value);
3917 }
3918 "rgba" => {
3919 config.rgba = parse_rgba_const(value);
3920 }
3921 "lcdfilter" => {
3922 config.lcdfilter = parse_lcdfilter_const(value);
3923 }
3924 "dpi" => {
3925 if let Ok(v) = value.parse::<f64>() {
3926 config.dpi = Some(v);
3927 }
3928 }
3929 "scale" => {
3930 if let Ok(v) = value.parse::<f64>() {
3931 config.scale = Some(v);
3932 }
3933 }
3934 _ => {
3935 }
3937 }
3938}
3939
3940#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3941fn parse_bool_value(value: &str) -> Option<bool> {
3942 match value {
3943 "true" => Some(true),
3944 "false" => Some(false),
3945 _ => None,
3946 }
3947}
3948
3949#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3950fn parse_hintstyle_const(value: &str) -> Option<FcHintStyle> {
3951 match value {
3952 "hintnone" => Some(FcHintStyle::None),
3953 "hintslight" => Some(FcHintStyle::Slight),
3954 "hintmedium" => Some(FcHintStyle::Medium),
3955 "hintfull" => Some(FcHintStyle::Full),
3956 _ => None,
3957 }
3958}
3959
3960#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3961fn parse_rgba_const(value: &str) -> Option<FcRgba> {
3962 match value {
3963 "unknown" => Some(FcRgba::Unknown),
3964 "rgb" => Some(FcRgba::Rgb),
3965 "bgr" => Some(FcRgba::Bgr),
3966 "vrgb" => Some(FcRgba::Vrgb),
3967 "vbgr" => Some(FcRgba::Vbgr),
3968 "none" => Some(FcRgba::None),
3969 _ => None,
3970 }
3971}
3972
3973#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3974fn parse_lcdfilter_const(value: &str) -> Option<FcLcdFilter> {
3975 match value {
3976 "lcdnone" => Some(FcLcdFilter::None),
3977 "lcddefault" => Some(FcLcdFilter::Default),
3978 "lcdlight" => Some(FcLcdFilter::Light),
3979 "lcdlegacy" => Some(FcLcdFilter::Legacy),
3980 _ => None,
3981 }
3982}
3983
3984#[cfg(all(feature = "std", feature = "parsing"))]
3987const UNICODE_RANGE_MAPPINGS: &[(usize, u32, u32)] = &[
3988 (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), ];
4121
4122#[cfg(all(feature = "std", feature = "parsing"))]
4125struct ParsedFontFace {
4126 pattern: FcPattern,
4127 font_index: usize,
4128}
4129
4130#[cfg(all(feature = "std", feature = "parsing"))]
4136fn parse_font_faces(font_bytes: &[u8]) -> Option<Vec<ParsedFontFace>> {
4137 use allsorts::{
4138 binary::read::ReadScope,
4139 font_data::FontData,
4140 get_name::fontcode_get_name,
4141 post::PostTable,
4142 tables::{
4143 os2::Os2, HeadTable, NameTable,
4144 },
4145 tag,
4146 };
4147 use std::collections::BTreeSet;
4148
4149 const FONT_SPECIFIER_NAME_ID: u16 = 4;
4150 const FONT_SPECIFIER_FAMILY_ID: u16 = 1;
4151
4152 let max_fonts = if font_bytes.len() >= 12 && &font_bytes[0..4] == b"ttcf" {
4153 let num_fonts =
4155 u32::from_be_bytes([font_bytes[8], font_bytes[9], font_bytes[10], font_bytes[11]]);
4156 std::cmp::min(num_fonts as usize, 100)
4158 } else {
4159 1
4161 };
4162
4163 let scope = ReadScope::new(font_bytes);
4164 let font_file = scope.read::<FontData<'_>>().ok()?;
4165
4166 let mut results = Vec::new();
4168
4169 for font_index in 0..max_fonts {
4170 let provider = font_file.table_provider(font_index).ok()?;
4171 let head_data = provider.table_data(tag::HEAD).ok()??.into_owned();
4172 let head_table = ReadScope::new(&head_data).read::<HeadTable>().ok()?;
4173
4174 let is_bold = head_table.is_bold();
4175 let is_italic = head_table.is_italic();
4176 let mut detected_monospace = None;
4177
4178 let post_data = provider.table_data(tag::POST).ok()??;
4179 if let Ok(post_table) = ReadScope::new(&post_data).read::<PostTable>() {
4180 detected_monospace = Some(post_table.header.is_fixed_pitch != 0);
4182 }
4183
4184 let os2_data = provider.table_data(tag::OS_2).ok()??;
4186 let os2_table = ReadScope::new(&os2_data)
4187 .read_dep::<Os2>(os2_data.len())
4188 .ok()?;
4189
4190 let is_oblique = os2_table
4192 .fs_selection
4193 .contains(allsorts::tables::os2::FsSelectionFlag::OBLIQUE);
4194 let weight = FcWeight::from_u16(os2_table.us_weight_class);
4195 let stretch = FcStretch::from_u16(os2_table.us_width_class);
4196
4197 let mut unicode_ranges = Vec::new();
4201
4202 let os2_ranges = [
4204 os2_table.ul_unicode_range1,
4205 os2_table.ul_unicode_range2,
4206 os2_table.ul_unicode_range3,
4207 os2_table.ul_unicode_range4,
4208 ];
4209
4210 for &(bit, start, end) in UNICODE_RANGE_MAPPINGS {
4211 let range_idx = bit / 32;
4212 let bit_pos = bit % 32;
4213 if range_idx < 4 && (os2_ranges[range_idx] & (1 << bit_pos)) != 0 {
4214 unicode_ranges.push(UnicodeRange { start, end });
4215 }
4216 }
4217
4218 unicode_ranges = verify_unicode_ranges_with_cmap(&provider, unicode_ranges);
4222
4223 if unicode_ranges.is_empty() {
4225 if let Some(cmap_ranges) = analyze_cmap_coverage(&provider) {
4226 unicode_ranges = cmap_ranges;
4227 }
4228 }
4229
4230 let is_monospace = detect_monospace(&provider, &os2_table, detected_monospace)
4232 .unwrap_or(false);
4233
4234 let name_data = provider.table_data(tag::NAME).ok()??.into_owned();
4235 let name_table = ReadScope::new(&name_data).read::<NameTable>().ok()?;
4236
4237 let mut metadata = FcFontMetadata::default();
4239
4240 const NAME_ID_COPYRIGHT: u16 = 0;
4241 const NAME_ID_FAMILY: u16 = 1;
4242 const NAME_ID_SUBFAMILY: u16 = 2;
4243 const NAME_ID_UNIQUE_ID: u16 = 3;
4244 const NAME_ID_FULL_NAME: u16 = 4;
4245 const NAME_ID_VERSION: u16 = 5;
4246 const NAME_ID_POSTSCRIPT_NAME: u16 = 6;
4247 const NAME_ID_TRADEMARK: u16 = 7;
4248 const NAME_ID_MANUFACTURER: u16 = 8;
4249 const NAME_ID_DESIGNER: u16 = 9;
4250 const NAME_ID_DESCRIPTION: u16 = 10;
4251 const NAME_ID_VENDOR_URL: u16 = 11;
4252 const NAME_ID_DESIGNER_URL: u16 = 12;
4253 const NAME_ID_LICENSE: u16 = 13;
4254 const NAME_ID_LICENSE_URL: u16 = 14;
4255 const NAME_ID_PREFERRED_FAMILY: u16 = 16;
4256 const NAME_ID_PREFERRED_SUBFAMILY: u16 = 17;
4257
4258 metadata.copyright = get_name_string(&name_data, NAME_ID_COPYRIGHT);
4259 metadata.font_family = get_name_string(&name_data, NAME_ID_FAMILY);
4260 metadata.font_subfamily = get_name_string(&name_data, NAME_ID_SUBFAMILY);
4261 metadata.full_name = get_name_string(&name_data, NAME_ID_FULL_NAME);
4262 metadata.unique_id = get_name_string(&name_data, NAME_ID_UNIQUE_ID);
4263 metadata.version = get_name_string(&name_data, NAME_ID_VERSION);
4264 metadata.postscript_name = get_name_string(&name_data, NAME_ID_POSTSCRIPT_NAME);
4265 metadata.trademark = get_name_string(&name_data, NAME_ID_TRADEMARK);
4266 metadata.manufacturer = get_name_string(&name_data, NAME_ID_MANUFACTURER);
4267 metadata.designer = get_name_string(&name_data, NAME_ID_DESIGNER);
4268 metadata.id_description = get_name_string(&name_data, NAME_ID_DESCRIPTION);
4269 metadata.designer_url = get_name_string(&name_data, NAME_ID_DESIGNER_URL);
4270 metadata.manufacturer_url = get_name_string(&name_data, NAME_ID_VENDOR_URL);
4271 metadata.license = get_name_string(&name_data, NAME_ID_LICENSE);
4272 metadata.license_url = get_name_string(&name_data, NAME_ID_LICENSE_URL);
4273 metadata.preferred_family = get_name_string(&name_data, NAME_ID_PREFERRED_FAMILY);
4274 metadata.preferred_subfamily = get_name_string(&name_data, NAME_ID_PREFERRED_SUBFAMILY);
4275
4276 let mut f_family = None;
4278
4279 let patterns = name_table
4280 .name_records
4281 .iter()
4282 .filter_map(|name_record| {
4283 let name_id = name_record.name_id;
4284 if name_id == FONT_SPECIFIER_FAMILY_ID {
4285 if let Ok(Some(family)) =
4286 fontcode_get_name(&name_data, FONT_SPECIFIER_FAMILY_ID)
4287 {
4288 f_family = Some(family);
4289 }
4290 None
4291 } else if name_id == FONT_SPECIFIER_NAME_ID {
4292 let family = f_family.as_ref()?;
4293 let name = fontcode_get_name(&name_data, FONT_SPECIFIER_NAME_ID).ok()??;
4294 if name.to_bytes().is_empty() {
4295 None
4296 } else {
4297 let mut name_str =
4298 String::from_utf8_lossy(name.to_bytes()).to_string();
4299 let mut family_str =
4300 String::from_utf8_lossy(family.as_bytes()).to_string();
4301 if name_str.starts_with('.') {
4302 name_str = name_str[1..].to_string();
4303 }
4304 if family_str.starts_with('.') {
4305 family_str = family_str[1..].to_string();
4306 }
4307 Some((
4308 FcPattern {
4309 name: Some(name_str),
4310 family: Some(family_str),
4311 bold: if is_bold {
4312 PatternMatch::True
4313 } else {
4314 PatternMatch::False
4315 },
4316 italic: if is_italic {
4317 PatternMatch::True
4318 } else {
4319 PatternMatch::False
4320 },
4321 oblique: if is_oblique {
4322 PatternMatch::True
4323 } else {
4324 PatternMatch::False
4325 },
4326 monospace: if is_monospace {
4327 PatternMatch::True
4328 } else {
4329 PatternMatch::False
4330 },
4331 condensed: if stretch <= FcStretch::Condensed {
4332 PatternMatch::True
4333 } else {
4334 PatternMatch::False
4335 },
4336 weight,
4337 stretch,
4338 unicode_ranges: unicode_ranges.clone(),
4339 metadata: metadata.clone(),
4340 render_config: FcFontRenderConfig::default(),
4341 },
4342 font_index,
4343 ))
4344 }
4345 } else {
4346 None
4347 }
4348 })
4349 .collect::<BTreeSet<_>>();
4350
4351 results.extend(patterns.into_iter().map(|(pat, idx)| ParsedFontFace {
4352 pattern: pat,
4353 font_index: idx,
4354 }));
4355 }
4356
4357 if results.is_empty() {
4358 None
4359 } else {
4360 Some(results)
4361 }
4362}
4363
4364#[cfg(all(feature = "std", feature = "parsing"))]
4366pub(crate) fn FcParseFont(filepath: &PathBuf) -> Option<Vec<(FcPattern, FcFontPath)>> {
4367 #[cfg(all(not(target_family = "wasm"), feature = "std"))]
4368 use mmapio::MmapOptions;
4369 use std::fs::File;
4370
4371 let file = File::open(filepath).ok()?;
4373
4374 #[cfg(all(not(target_family = "wasm"), feature = "std"))]
4375 let font_bytes = unsafe { MmapOptions::new().map(&file).ok()? };
4376
4377 #[cfg(not(all(not(target_family = "wasm"), feature = "std")))]
4378 let font_bytes = std::fs::read(filepath).ok()?;
4379
4380 let faces = parse_font_faces(&font_bytes[..])?;
4381 let path_str = filepath.to_string_lossy().to_string();
4382 let bytes_hash = crate::utils::content_dedup_hash_u64(&font_bytes[..]);
4387
4388 Some(
4389 faces
4390 .into_iter()
4391 .map(|face| {
4392 (
4393 face.pattern,
4394 FcFontPath {
4395 path: path_str.clone(),
4396 font_index: face.font_index,
4397 bytes_hash,
4398 },
4399 )
4400 })
4401 .collect(),
4402 )
4403}
4404
4405#[cfg(all(feature = "std", feature = "parsing"))]
4422#[derive(Debug, Clone)]
4423pub struct FastCoverage {
4424 pub pattern: FcPattern,
4430 pub covered: alloc::collections::BTreeSet<char>,
4435 pub is_bold: bool,
4437 pub is_italic: bool,
4439}
4440
4441#[cfg(all(feature = "std", feature = "parsing"))]
4458#[allow(non_snake_case)]
4459pub fn FcParseFontFaceFast(
4460 font_bytes: &[u8],
4461 font_index: usize,
4462 codepoints: &alloc::collections::BTreeSet<char>,
4463) -> Option<FastCoverage> {
4464 use allsorts::{
4465 binary::read::ReadScope,
4466 font_data::FontData,
4467 tables::{
4468 cmap::{Cmap, CmapSubtable},
4469 FontTableProvider, HeadTable,
4470 },
4471 tag,
4472 };
4473
4474 let scope = ReadScope::new(font_bytes);
4475 let font_file = scope.read::<FontData<'_>>().ok()?;
4476 let provider = font_file.table_provider(font_index).ok()?;
4477
4478 let head_data = provider.table_data(tag::HEAD).ok()??;
4480 let head_table = ReadScope::new(&head_data).read::<HeadTable>().ok()?;
4481 let is_bold = head_table.is_bold();
4482 let is_italic = head_table.is_italic();
4483
4484 let cmap_data = provider.table_data(tag::CMAP).ok()??;
4487 let cmap = ReadScope::new(&cmap_data).read::<Cmap<'_>>().ok()?;
4488 let encoding_record = find_best_cmap_subtable(&cmap)?;
4489 let cmap_subtable = ReadScope::new(&cmap_data)
4490 .offset(encoding_record.offset as usize)
4491 .read::<CmapSubtable<'_>>()
4492 .ok()?;
4493
4494 let mut covered: alloc::collections::BTreeSet<char> =
4495 alloc::collections::BTreeSet::new();
4496 let mut covered_ranges: Vec<UnicodeRange> = Vec::new();
4497 for ch in codepoints {
4498 let cp = *ch as u32;
4499 if let Ok(Some(gid)) = cmap_subtable.map_glyph(cp) {
4500 if gid != 0 {
4501 covered.insert(*ch);
4502 if let Some(last) = covered_ranges.last_mut() {
4506 if cp == last.end + 1 {
4507 last.end = cp;
4508 continue;
4509 }
4510 }
4511 covered_ranges.push(UnicodeRange { start: cp, end: cp });
4512 }
4513 }
4514 }
4515
4516 let weight = if is_bold {
4517 FcWeight::Bold
4518 } else {
4519 FcWeight::Normal
4520 };
4521 let italic_match = if is_italic {
4522 PatternMatch::True
4523 } else {
4524 PatternMatch::False
4525 };
4526
4527 let pattern = FcPattern {
4528 name: None,
4529 family: None,
4530 weight,
4531 italic: italic_match,
4532 oblique: PatternMatch::DontCare,
4533 monospace: PatternMatch::DontCare,
4534 unicode_ranges: covered_ranges,
4535 ..Default::default()
4536 };
4537
4538 Some(FastCoverage {
4539 pattern,
4540 covered,
4541 is_bold,
4542 is_italic,
4543 })
4544}
4545
4546#[cfg(all(feature = "std", feature = "parsing"))]
4551#[allow(non_snake_case)]
4552pub fn FcCountFontFaces(font_bytes: &[u8]) -> usize {
4553 if font_bytes.len() >= 12 && &font_bytes[0..4] == b"ttcf" {
4554 let num_fonts = u32::from_be_bytes([
4555 font_bytes[8], font_bytes[9], font_bytes[10], font_bytes[11],
4556 ]);
4557 std::cmp::min(num_fonts as usize, 100).max(1)
4559 } else {
4560 1
4561 }
4562}
4563
4564#[cfg(all(feature = "std", feature = "parsing"))]
4590#[allow(non_snake_case)]
4591pub fn FcParseFontBytes(font_bytes: &[u8], font_id: &str) -> Option<Vec<(FcPattern, FcFont)>> {
4592 FcParseFontBytesInner(font_bytes, font_id)
4593}
4594
4595#[cfg(all(feature = "std", feature = "parsing"))]
4598fn FcParseFontBytesInner(font_bytes: &[u8], font_id: &str) -> Option<Vec<(FcPattern, FcFont)>> {
4599 let faces = parse_font_faces(font_bytes)?;
4600 let id = font_id.to_string();
4601 let bytes = font_bytes.to_vec();
4602
4603 Some(
4604 faces
4605 .into_iter()
4606 .map(|face| {
4607 (
4608 face.pattern,
4609 FcFont {
4610 bytes: bytes.clone(),
4611 font_index: face.font_index,
4612 id: id.clone(),
4613 },
4614 )
4615 })
4616 .collect(),
4617 )
4618}
4619
4620#[cfg(all(feature = "std", feature = "parsing"))]
4621fn FcScanDirectoriesInner(paths: &[(Option<String>, String)]) -> Vec<(FcPattern, FcFontPath)> {
4622 #[cfg(all(feature = "multithreading", not(target_family = "wasm")))]
4623 {
4624 use rayon::prelude::*;
4625
4626 paths
4628 .par_iter()
4629 .filter_map(|(prefix, p)| {
4630 process_path(prefix, PathBuf::from(p), false).map(FcScanSingleDirectoryRecursive)
4631 })
4632 .flatten()
4633 .collect()
4634 }
4635 #[cfg(not(all(feature = "multithreading", not(target_family = "wasm"))))]
4638 {
4639 paths
4640 .iter()
4641 .filter_map(|(prefix, p)| {
4642 process_path(prefix, PathBuf::from(p), false).map(FcScanSingleDirectoryRecursive)
4643 })
4644 .flatten()
4645 .collect()
4646 }
4647}
4648
4649#[cfg(feature = "std")]
4651fn FcCollectFontFilesRecursive(dir: PathBuf) -> Vec<PathBuf> {
4652 let mut files = Vec::new();
4653 let mut dirs_to_parse = vec![dir];
4654
4655 loop {
4656 let mut new_dirs = Vec::new();
4657 for dir in &dirs_to_parse {
4658 let entries = match std::fs::read_dir(dir) {
4659 Ok(o) => o,
4660 Err(_) => continue,
4661 };
4662 for entry in entries.flatten() {
4663 let path = entry.path();
4664 if path.is_dir() {
4665 new_dirs.push(path);
4666 } else {
4667 files.push(path);
4668 }
4669 }
4670 }
4671 if new_dirs.is_empty() {
4672 break;
4673 }
4674 dirs_to_parse = new_dirs;
4675 }
4676
4677 files
4678}
4679
4680#[cfg(all(feature = "std", feature = "parsing"))]
4681fn FcScanSingleDirectoryRecursive(dir: PathBuf) -> Vec<(FcPattern, FcFontPath)> {
4682 let files = FcCollectFontFilesRecursive(dir);
4683 FcParseFontFiles(&files)
4684}
4685
4686#[cfg(all(feature = "std", feature = "parsing"))]
4687fn FcParseFontFiles(files_to_parse: &[PathBuf]) -> Vec<(FcPattern, FcFontPath)> {
4688 let result = {
4689 #[cfg(all(feature = "multithreading", not(target_family = "wasm")))]
4690 {
4691 use rayon::prelude::*;
4692
4693 files_to_parse
4694 .par_iter()
4695 .filter_map(|file| FcParseFont(file))
4696 .collect::<Vec<Vec<_>>>()
4697 }
4698 #[cfg(not(all(feature = "multithreading", not(target_family = "wasm"))))]
4699 {
4700 files_to_parse
4701 .iter()
4702 .filter_map(|file| FcParseFont(file))
4703 .collect::<Vec<Vec<_>>>()
4704 }
4705 };
4706
4707 result.into_iter().flat_map(|f| f.into_iter()).collect()
4708}
4709
4710#[cfg(all(feature = "std", feature = "parsing"))]
4711fn process_path(
4715 prefix: &Option<String>,
4716 mut path: PathBuf,
4717 is_include_path: bool,
4718) -> Option<PathBuf> {
4719 use std::env::var;
4720
4721 const HOME_SHORTCUT: &str = "~";
4722 const CWD_PATH: &str = ".";
4723
4724 const HOME_ENV_VAR: &str = "HOME";
4725 const XDG_CONFIG_HOME_ENV_VAR: &str = "XDG_CONFIG_HOME";
4726 const XDG_CONFIG_HOME_DEFAULT_PATH_SUFFIX: &str = ".config";
4727 const XDG_DATA_HOME_ENV_VAR: &str = "XDG_DATA_HOME";
4728 const XDG_DATA_HOME_DEFAULT_PATH_SUFFIX: &str = ".local/share";
4729
4730 const PREFIX_CWD: &str = "cwd";
4731 const PREFIX_DEFAULT: &str = "default";
4732 const PREFIX_XDG: &str = "xdg";
4733
4734 fn get_home_value() -> Option<PathBuf> {
4736 var(HOME_ENV_VAR).ok().map(PathBuf::from)
4737 }
4738 fn get_xdg_config_home_value() -> Option<PathBuf> {
4739 var(XDG_CONFIG_HOME_ENV_VAR)
4740 .ok()
4741 .map(PathBuf::from)
4742 .or_else(|| {
4743 get_home_value()
4744 .map(|home_path| home_path.join(XDG_CONFIG_HOME_DEFAULT_PATH_SUFFIX))
4745 })
4746 }
4747 fn get_xdg_data_home_value() -> Option<PathBuf> {
4748 var(XDG_DATA_HOME_ENV_VAR)
4749 .ok()
4750 .map(PathBuf::from)
4751 .or_else(|| {
4752 get_home_value().map(|home_path| home_path.join(XDG_DATA_HOME_DEFAULT_PATH_SUFFIX))
4753 })
4754 }
4755
4756 if path.starts_with(HOME_SHORTCUT) {
4758 if let Some(home_path) = get_home_value() {
4759 path = home_path.join(
4760 path.strip_prefix(HOME_SHORTCUT)
4761 .expect("already checked that it starts with the prefix"),
4762 );
4763 } else {
4764 return None;
4765 }
4766 }
4767
4768 match prefix {
4770 Some(prefix) => match prefix.as_str() {
4771 PREFIX_CWD | PREFIX_DEFAULT => {
4772 let mut new_path = PathBuf::from(CWD_PATH);
4773 new_path.push(path);
4774
4775 Some(new_path)
4776 }
4777 PREFIX_XDG => {
4778 if is_include_path {
4779 get_xdg_config_home_value()
4780 .map(|xdg_config_home_path| xdg_config_home_path.join(path))
4781 } else {
4782 get_xdg_data_home_value()
4783 .map(|xdg_data_home_path| xdg_data_home_path.join(path))
4784 }
4785 }
4786 _ => None, },
4788 None => Some(path),
4789 }
4790}
4791
4792#[cfg(all(feature = "std", feature = "parsing"))]
4794fn get_name_string(name_data: &[u8], name_id: u16) -> Option<String> {
4795 fontcode_get_name(name_data, name_id)
4796 .ok()
4797 .flatten()
4798 .map(|name| String::from_utf8_lossy(name.to_bytes()).to_string())
4799}
4800
4801#[cfg(all(feature = "std", feature = "parsing"))]
4805fn get_verification_codepoints(start: u32, end: u32) -> Vec<u32> {
4806 match start {
4807 0x0000 => vec!['A' as u32, 'M' as u32, 'Z' as u32, 'a' as u32, 'm' as u32, 'z' as u32],
4809 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], _ => {
4885 let range_size = end - start;
4886 if range_size > 20 {
4887 vec![
4888 start + range_size / 5,
4889 start + 2 * range_size / 5,
4890 start + 3 * range_size / 5,
4891 start + 4 * range_size / 5,
4892 ]
4893 } else {
4894 vec![start, start + range_size / 2]
4895 }
4896 }
4897 }
4898}
4899
4900#[cfg(all(feature = "std", feature = "parsing"))]
4903fn find_best_cmap_subtable<'a>(
4904 cmap: &allsorts::tables::cmap::Cmap<'a>,
4905) -> Option<allsorts::tables::cmap::EncodingRecord> {
4906 use allsorts::tables::cmap::{PlatformId, EncodingId};
4907
4908 cmap.find_subtable(PlatformId::UNICODE, EncodingId(3))
4909 .or_else(|| cmap.find_subtable(PlatformId::UNICODE, EncodingId(4)))
4910 .or_else(|| cmap.find_subtable(PlatformId::WINDOWS, EncodingId(1)))
4911 .or_else(|| cmap.find_subtable(PlatformId::WINDOWS, EncodingId(10)))
4912 .or_else(|| cmap.find_subtable(PlatformId::UNICODE, EncodingId(0)))
4913 .or_else(|| cmap.find_subtable(PlatformId::UNICODE, EncodingId(1)))
4914}
4915
4916#[cfg(all(feature = "std", feature = "parsing"))]
4919fn verify_unicode_ranges_with_cmap(
4920 provider: &impl FontTableProvider,
4921 os2_ranges: Vec<UnicodeRange>
4922) -> Vec<UnicodeRange> {
4923 use allsorts::tables::cmap::{Cmap, CmapSubtable};
4924
4925 if os2_ranges.is_empty() {
4926 return Vec::new();
4927 }
4928
4929 let cmap_data = match provider.table_data(tag::CMAP) {
4931 Ok(Some(data)) => data,
4932 _ => return os2_ranges, };
4934
4935 let cmap = match ReadScope::new(&cmap_data).read::<Cmap<'_>>() {
4936 Ok(c) => c,
4937 Err(_) => return os2_ranges,
4938 };
4939
4940 let encoding_record = match find_best_cmap_subtable(&cmap) {
4941 Some(r) => r,
4942 None => return os2_ranges, };
4944
4945 let cmap_subtable = match ReadScope::new(&cmap_data)
4946 .offset(encoding_record.offset as usize)
4947 .read::<CmapSubtable<'_>>()
4948 {
4949 Ok(st) => st,
4950 Err(_) => return os2_ranges,
4951 };
4952
4953 let mut verified_ranges = Vec::new();
4955
4956 for range in os2_ranges {
4957 let test_codepoints = get_verification_codepoints(range.start, range.end);
4958
4959 let required_hits = (test_codepoints.len() + 1) / 2; let mut hits = 0;
4963
4964 for cp in test_codepoints {
4965 if cp >= range.start && cp <= range.end {
4966 if let Ok(Some(gid)) = cmap_subtable.map_glyph(cp) {
4967 if gid != 0 {
4968 hits += 1;
4969 if hits >= required_hits {
4970 break;
4971 }
4972 }
4973 }
4974 }
4975 }
4976
4977 if hits >= required_hits {
4978 verified_ranges.push(range);
4979 }
4980 }
4981
4982 verified_ranges
4983}
4984
4985#[cfg(all(feature = "std", feature = "parsing"))]
4988fn analyze_cmap_coverage(provider: &impl FontTableProvider) -> Option<Vec<UnicodeRange>> {
4989 use allsorts::tables::cmap::{Cmap, CmapSubtable};
4990
4991 let cmap_data = provider.table_data(tag::CMAP).ok()??;
4992 let cmap = ReadScope::new(&cmap_data).read::<Cmap<'_>>().ok()?;
4993
4994 let encoding_record = find_best_cmap_subtable(&cmap)?;
4995
4996 let cmap_subtable = ReadScope::new(&cmap_data)
4997 .offset(encoding_record.offset as usize)
4998 .read::<CmapSubtable<'_>>()
4999 .ok()?;
5000
5001 let blocks_to_check: &[(u32, u32)] = &[
5003 (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), ];
5054
5055 let mut ranges = Vec::new();
5056
5057 for &(start, end) in blocks_to_check {
5058 let test_codepoints = get_verification_codepoints(start, end);
5059 let required_hits = (test_codepoints.len() + 1) / 2;
5060 let mut hits = 0;
5061
5062 for cp in test_codepoints {
5063 if let Ok(Some(gid)) = cmap_subtable.map_glyph(cp) {
5064 if gid != 0 {
5065 hits += 1;
5066 if hits >= required_hits {
5067 break;
5068 }
5069 }
5070 }
5071 }
5072
5073 if hits >= required_hits {
5074 ranges.push(UnicodeRange { start, end });
5075 }
5076 }
5077
5078 if ranges.is_empty() {
5079 None
5080 } else {
5081 Some(ranges)
5082 }
5083}
5084
5085#[cfg(all(feature = "std", feature = "parsing"))]
5087#[allow(dead_code)]
5088fn extract_unicode_ranges(os2_table: &Os2) -> Vec<UnicodeRange> {
5089 let mut unicode_ranges = Vec::new();
5090
5091 let ranges = [
5092 os2_table.ul_unicode_range1,
5093 os2_table.ul_unicode_range2,
5094 os2_table.ul_unicode_range3,
5095 os2_table.ul_unicode_range4,
5096 ];
5097
5098 for &(bit, start, end) in UNICODE_RANGE_MAPPINGS {
5099 let range_idx = bit / 32;
5100 let bit_pos = bit % 32;
5101 if range_idx < 4 && (ranges[range_idx] & (1 << bit_pos)) != 0 {
5102 unicode_ranges.push(UnicodeRange { start, end });
5103 }
5104 }
5105
5106 unicode_ranges
5107}
5108
5109#[cfg(all(feature = "std", feature = "parsing"))]
5111fn detect_monospace(
5112 provider: &impl FontTableProvider,
5113 os2_table: &Os2,
5114 detected_monospace: Option<bool>,
5115) -> Option<bool> {
5116 if let Some(is_monospace) = detected_monospace {
5117 return Some(is_monospace);
5118 }
5119
5120 if os2_table.panose[0] == 2 {
5122 return Some(os2_table.panose[3] == 9); }
5125
5126 let hhea_data = provider.table_data(tag::HHEA).ok()??;
5128 let hhea_table = ReadScope::new(&hhea_data).read::<HheaTable>().ok()?;
5129 let maxp_data = provider.table_data(tag::MAXP).ok()??;
5130 let maxp_table = ReadScope::new(&maxp_data).read::<MaxpTable>().ok()?;
5131 let hmtx_data = provider.table_data(tag::HMTX).ok()??;
5132 let hmtx_table = ReadScope::new(&hmtx_data)
5133 .read_dep::<HmtxTable<'_>>((
5134 usize::from(maxp_table.num_glyphs),
5135 usize::from(hhea_table.num_h_metrics),
5136 ))
5137 .ok()?;
5138
5139 let mut monospace = true;
5140 let mut last_advance = 0;
5141
5142 for i in 0..hhea_table.num_h_metrics as usize {
5144 let advance = hmtx_table.h_metrics.read_item(i).ok()?.advance_width;
5145 if i > 0 && advance != last_advance {
5146 monospace = false;
5147 break;
5148 }
5149 last_advance = advance;
5150 }
5151
5152 Some(monospace)
5153}
5154
5155#[cfg(all(feature = "std", not(feature = "parsing")))]
5164fn pattern_from_filename(path: &std::path::Path) -> Option<FcPattern> {
5165 let ext = path.extension()?.to_str()?.to_ascii_lowercase();
5166 match ext.as_str() {
5167 "ttf" | "otf" | "ttc" | "woff" | "woff2" => {}
5168 _ => return None,
5169 }
5170
5171 let stem = path.file_stem()?.to_str()?;
5172 let all_tokens = crate::config::tokenize_lowercase(stem);
5173
5174 let has_token = |kw: &str| all_tokens.iter().any(|t| t == kw);
5176 let is_bold = has_token("bold") || has_token("heavy");
5177 let is_italic = has_token("italic");
5178 let is_oblique = has_token("oblique");
5179 let is_mono = has_token("mono") || has_token("monospace");
5180 let is_condensed = has_token("condensed");
5181
5182 let family_tokens = crate::config::tokenize_font_stem(stem);
5184 if family_tokens.is_empty() { return None; }
5185 let family = family_tokens.join(" ");
5186
5187 Some(FcPattern {
5188 name: Some(stem.to_string()),
5189 family: Some(family),
5190 bold: if is_bold { PatternMatch::True } else { PatternMatch::False },
5191 italic: if is_italic { PatternMatch::True } else { PatternMatch::False },
5192 oblique: if is_oblique { PatternMatch::True } else { PatternMatch::DontCare },
5193 monospace: if is_mono { PatternMatch::True } else { PatternMatch::DontCare },
5194 condensed: if is_condensed { PatternMatch::True } else { PatternMatch::DontCare },
5195 weight: if is_bold { FcWeight::Bold } else { FcWeight::Normal },
5196 stretch: if is_condensed { FcStretch::Condensed } else { FcStretch::Normal },
5197 unicode_ranges: Vec::new(),
5198 metadata: FcFontMetadata::default(),
5199 render_config: FcFontRenderConfig::default(),
5200 })
5201}