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 mut state = self.state_write();
1715 for (pattern, font) in fonts {
1716 let id = FontId::new();
1717 state.patterns.insert(pattern.clone(), id);
1718 state.metadata.insert(id, pattern.clone());
1719 state.memory_fonts.insert(id, font);
1720 state.index_pattern_tokens(&pattern, id);
1721 }
1722 self
1723 }
1724
1725 pub fn with_memory_font_with_id(
1727 &self,
1728 id: FontId,
1729 pattern: FcPattern,
1730 font: FcFont,
1731 ) -> &Self {
1732 let mut state = self.state_write();
1733 state.patterns.insert(pattern.clone(), id);
1734 state.metadata.insert(id, pattern.clone());
1735 state.memory_fonts.insert(id, font);
1736 state.index_pattern_tokens(&pattern, id);
1737 self
1738 }
1739
1740 pub fn insert_builder_font(&self, pattern: FcPattern, path: FcFontPath) {
1746 let id = FontId::new();
1747 {
1748 let mut state = self.state_write();
1749 state.index_pattern_tokens(&pattern, id);
1750 state.patterns.insert(pattern.clone(), id);
1751 state.disk_fonts.insert(id, path);
1752 state.metadata.insert(id, pattern);
1753 }
1754 if let Ok(mut cc) = self.shared.chain_cache.lock() {
1758 cc.clear();
1759 }
1760 }
1761
1762 #[cfg(feature = "std")]
1763 #[doc(hidden)]
1764 pub fn chain_cache_len(&self) -> usize {
1765 self.shared.chain_cache.lock().map(|c| c.len()).unwrap_or(0)
1766 }
1767
1768 pub fn insert_fast_pattern(&self, pattern: FcPattern, path: FcFontPath) -> FontId {
1776 let id = FontId::new();
1777 let mut state = self.state_write();
1778 state.patterns.insert(pattern.clone(), id);
1779 state.disk_fonts.insert(id, path);
1780 state.metadata.insert(id, pattern);
1781 id
1782 }
1783
1784 pub fn lookup_paths_cached(&self, path: &str) -> Option<Vec<FontId>> {
1792 let state = self.state_read();
1793 let mut out = Vec::new();
1794 for (id, font_path) in &state.disk_fonts {
1795 if font_path.path == path {
1796 out.push(*id);
1797 }
1798 }
1799 if out.is_empty() { None } else { Some(out) }
1800 }
1801
1802 pub fn get_font_by_id(&self, id: &FontId) -> Option<OwnedFontSource> {
1809 let state = self.state_read();
1810 if let Some(font) = state.memory_fonts.get(id) {
1811 return Some(OwnedFontSource::Memory(font.clone()));
1812 }
1813 if let Some(path) = state.disk_fonts.get(id) {
1814 return Some(OwnedFontSource::Disk(path.clone()));
1815 }
1816 None
1817 }
1818
1819 pub fn get_metadata_by_id(&self, id: &FontId) -> Option<FcPattern> {
1823 self.state_read().metadata.get(id).cloned()
1824 }
1825
1826 #[cfg(feature = "std")]
1854 pub fn get_font_bytes(&self, id: &FontId) -> Option<std::sync::Arc<FontBytes>> {
1855 use std::sync::Arc;
1856 match self.get_font_by_id(id)? {
1857 OwnedFontSource::Memory(font) => Some(Arc::new(FontBytes::Owned(
1858 Arc::from(font.bytes.as_slice()),
1859 ))),
1860 OwnedFontSource::Disk(path) => {
1861 let hash = path.bytes_hash;
1862 if hash != 0 {
1863 if let Ok(guard) = self.shared.shared_bytes.lock() {
1864 if let Some(weak) = guard.get(&hash) {
1865 if let Some(arc) = weak.upgrade() {
1866 return Some(arc);
1867 }
1868 }
1869 }
1870 }
1871
1872 let arc = open_font_bytes_mmap(&path.path)?;
1873 if hash != 0 {
1874 if let Ok(mut guard) = self.shared.shared_bytes.lock() {
1875 guard.insert(hash, Arc::downgrade(&arc));
1877 }
1878 }
1879 Some(arc)
1880 }
1881 }
1882 }
1883
1884 #[cfg(not(feature = "std"))]
1886 pub fn build() -> Self { Self::default() }
1887
1888 #[cfg(all(feature = "std", not(feature = "parsing")))]
1890 pub fn build() -> Self { Self::build_from_filenames() }
1891
1892 #[cfg(all(feature = "std", feature = "parsing"))]
1894 pub fn build() -> Self { Self::build_inner(None) }
1895
1896 #[cfg(all(feature = "std", not(feature = "parsing")))]
1899 fn build_from_filenames() -> Self {
1900 let cache = Self::default();
1901 {
1902 let mut state = cache.state_write();
1903 for dir in crate::config::font_directories(OperatingSystem::current()) {
1904 for path in FcCollectFontFilesRecursive(dir) {
1905 let pattern = match pattern_from_filename(&path) {
1906 Some(p) => p,
1907 None => continue,
1908 };
1909 let id = FontId::new();
1910 state.disk_fonts.insert(id, FcFontPath {
1911 path: path.to_string_lossy().to_string(),
1912 font_index: 0,
1913 bytes_hash: 0,
1916 });
1917 state.index_pattern_tokens(&pattern, id);
1918 state.metadata.insert(id, pattern.clone());
1919 state.patterns.insert(pattern, id);
1920 }
1921 }
1922 }
1923 cache
1924 }
1925
1926 #[cfg(all(feature = "std", feature = "parsing"))]
1951 pub fn build_with_families(families: &[impl AsRef<str>]) -> Self {
1952 let os = OperatingSystem::current();
1954 let mut target_families: Vec<String> = Vec::new();
1955
1956 for family in families {
1957 let family_str = family.as_ref();
1958 let expanded = os.expand_generic_family(family_str, &[]);
1959 if expanded.is_empty() || (expanded.len() == 1 && expanded[0] == family_str) {
1960 target_families.push(family_str.to_string());
1961 } else {
1962 target_families.extend(expanded);
1963 }
1964 }
1965
1966 Self::build_inner(Some(&target_families))
1967 }
1968
1969 #[cfg(all(feature = "std", feature = "parsing"))]
1975 fn build_inner(family_filter: Option<&[String]>) -> Self {
1976 let cache = FcFontCache::default();
1977
1978 let filter_normalized: Option<Vec<String>> = family_filter.map(|families| {
1980 families
1981 .iter()
1982 .map(|f| crate::utils::normalize_family_name(f))
1983 .collect()
1984 });
1985
1986 let matches_filter = |pattern: &FcPattern| -> bool {
1988 match &filter_normalized {
1989 None => true, Some(targets) => {
1991 pattern.name.as_ref().map_or(false, |name| {
1992 let name_norm = crate::utils::normalize_family_name(name);
1993 targets.iter().any(|target| name_norm.contains(target))
1994 }) || pattern.family.as_ref().map_or(false, |family| {
1995 let family_norm = crate::utils::normalize_family_name(family);
1996 targets.iter().any(|target| family_norm.contains(target))
1997 })
1998 }
1999 }
2000 };
2001
2002 let mut state = cache.state_write();
2003
2004 #[cfg(target_os = "linux")]
2005 {
2006 if let Some((font_entries, render_configs)) = FcScanDirectories() {
2007 for (mut pattern, path) in font_entries {
2008 if matches_filter(&pattern) {
2009 if let Some(family) = pattern.name.as_ref().or(pattern.family.as_ref()) {
2011 if let Some(rc) = render_configs.get(family) {
2012 pattern.render_config = rc.clone();
2013 }
2014 }
2015 let id = FontId::new();
2016 state.patterns.insert(pattern.clone(), id);
2017 state.metadata.insert(id, pattern.clone());
2018 state.disk_fonts.insert(id, path);
2019 state.index_pattern_tokens(&pattern, id);
2020 }
2021 }
2022 }
2023 }
2024
2025 #[cfg(target_os = "windows")]
2026 {
2027 let system_root = std::env::var("SystemRoot")
2028 .or_else(|_| std::env::var("WINDIR"))
2029 .unwrap_or_else(|_| "C:\\Windows".to_string());
2030
2031 let user_profile = std::env::var("USERPROFILE")
2032 .unwrap_or_else(|_| "C:\\Users\\Default".to_string());
2033
2034 let font_dirs = vec![
2035 (None, format!("{}\\Fonts\\", system_root)),
2036 (None, format!("{}\\AppData\\Local\\Microsoft\\Windows\\Fonts\\", user_profile)),
2037 ];
2038
2039 let font_entries = FcScanDirectoriesInner(&font_dirs);
2040 for (pattern, path) in font_entries {
2041 if matches_filter(&pattern) {
2042 let id = FontId::new();
2043 state.patterns.insert(pattern.clone(), id);
2044 state.metadata.insert(id, pattern.clone());
2045 state.disk_fonts.insert(id, path);
2046 state.index_pattern_tokens(&pattern, id);
2047 }
2048 }
2049 }
2050
2051 #[cfg(target_os = "macos")]
2052 {
2053 let font_dirs = vec![
2054 (None, "~/Library/Fonts".to_owned()),
2055 (None, "/System/Library/Fonts".to_owned()),
2056 (None, "/Library/Fonts".to_owned()),
2057 (None, "/System/Library/AssetsV2".to_owned()),
2058 ];
2059
2060 let font_entries = FcScanDirectoriesInner(&font_dirs);
2061 for (pattern, path) in font_entries {
2062 if matches_filter(&pattern) {
2063 let id = FontId::new();
2064 state.patterns.insert(pattern.clone(), id);
2065 state.metadata.insert(id, pattern.clone());
2066 state.disk_fonts.insert(id, path);
2067 state.index_pattern_tokens(&pattern, id);
2068 }
2069 }
2070 }
2071
2072 #[cfg(target_os = "ios")]
2077 {
2078 let font_files = crate::mobile_ios::copy_available_font_urls();
2079 let font_entries = FcParseFontFiles(&font_files);
2080 for (pattern, path) in font_entries {
2081 if matches_filter(&pattern) {
2082 let id = FontId::new();
2083 state.patterns.insert(pattern.clone(), id);
2084 state.metadata.insert(id, pattern.clone());
2085 state.disk_fonts.insert(id, path);
2086 state.index_pattern_tokens(&pattern, id);
2087 }
2088 }
2089 }
2090
2091 #[cfg(target_os = "android")]
2096 {
2097 let font_dirs = vec![
2098 (None, "/system/fonts".to_owned()),
2099 (None, "/product/fonts".to_owned()),
2100 (None, "/system_ext/fonts".to_owned()),
2101 (None, "/data/fonts".to_owned()),
2102 ];
2103
2104 let font_entries = FcScanDirectoriesInner(&font_dirs);
2105 for (pattern, path) in font_entries {
2106 if matches_filter(&pattern) {
2107 let id = FontId::new();
2108 state.patterns.insert(pattern.clone(), id);
2109 state.metadata.insert(id, pattern.clone());
2110 state.disk_fonts.insert(id, path);
2111 state.index_pattern_tokens(&pattern, id);
2112 }
2113 }
2114 }
2115
2116 drop(state);
2117 cache
2118 }
2119
2120 pub fn is_memory_font(&self, id: &FontId) -> bool {
2122 self.state_read().memory_fonts.contains_key(id)
2123 }
2124
2125 pub fn list(&self) -> Vec<(FcPattern, FontId)> {
2132 self.state_read()
2133 .patterns
2134 .iter()
2135 .map(|(pattern, id)| (pattern.clone(), *id))
2136 .collect()
2137 }
2138
2139 pub fn for_each_pattern<F: FnMut(&FcPattern, &FontId)>(&self, mut f: F) {
2143 let state = self.state_read();
2144 for (pattern, id) in &state.patterns {
2145 f(pattern, id);
2146 }
2147 }
2148
2149 pub fn is_empty(&self) -> bool {
2151 self.state_read().patterns.is_empty()
2152 }
2153
2154 pub fn len(&self) -> usize {
2156 self.state_read().patterns.len()
2157 }
2158
2159 pub fn query(&self, pattern: &FcPattern, trace: &mut Vec<TraceMsg>) -> Option<FontMatch> {
2162 let state = self.state_read();
2163 let mut matches = Vec::new();
2164
2165 for (stored_pattern, id) in &state.patterns {
2166 if Self::query_matches_internal(stored_pattern, pattern, trace) {
2167 let metadata = state.metadata.get(id).unwrap_or(stored_pattern);
2168
2169 let unicode_compatibility = if pattern.unicode_ranges.is_empty() {
2171 Self::calculate_unicode_coverage(&metadata.unicode_ranges) as i32
2173 } else {
2174 Self::calculate_unicode_compatibility(&pattern.unicode_ranges, &metadata.unicode_ranges)
2176 };
2177
2178 let style_score = Self::calculate_style_score(pattern, metadata);
2179
2180 let is_memory = state.memory_fonts.contains_key(id);
2182
2183 matches.push((*id, unicode_compatibility, style_score, metadata.clone(), is_memory));
2184 }
2185 }
2186
2187 matches.sort_by(|a, b| {
2189 b.4.cmp(&a.4)
2191 .then_with(|| b.1.cmp(&a.1)) .then_with(|| a.2.cmp(&b.2)) });
2194
2195 matches.first().map(|(id, _, _, metadata, _)| {
2196 FontMatch {
2197 id: *id,
2198 unicode_ranges: metadata.unicode_ranges.clone(),
2199 fallbacks: Vec::new(), }
2201 })
2202 }
2203
2204 fn query_internal(&self, pattern: &FcPattern, trace: &mut Vec<TraceMsg>) -> Vec<FontMatch> {
2209 let state = self.state_read();
2210 self.query_internal_locked(&state, pattern, trace)
2211 }
2212
2213 fn query_internal_locked(
2216 &self,
2217 state: &FcFontCacheInner,
2218 pattern: &FcPattern,
2219 trace: &mut Vec<TraceMsg>,
2220 ) -> Vec<FontMatch> {
2221 let mut matches = Vec::new();
2222
2223 for (stored_pattern, id) in &state.patterns {
2224 if Self::query_matches_internal(stored_pattern, pattern, trace) {
2225 let metadata = state.metadata.get(id).unwrap_or(stored_pattern);
2226
2227 let unicode_compatibility = if pattern.unicode_ranges.is_empty() {
2229 Self::calculate_unicode_coverage(&metadata.unicode_ranges) as i32
2230 } else {
2231 Self::calculate_unicode_compatibility(&pattern.unicode_ranges, &metadata.unicode_ranges)
2232 };
2233
2234 let style_score = Self::calculate_style_score(pattern, metadata);
2235 matches.push((*id, unicode_compatibility, style_score, metadata.clone()));
2236 }
2237 }
2238
2239 matches.sort_by(|a, b| {
2243 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)) });
2248
2249 matches
2250 .into_iter()
2251 .map(|(id, _, _, metadata)| {
2252 FontMatch {
2253 id,
2254 unicode_ranges: metadata.unicode_ranges.clone(),
2255 fallbacks: Vec::new(), }
2257 })
2258 .collect()
2259 }
2260
2261 pub fn compute_fallbacks(
2265 &self,
2266 font_id: &FontId,
2267 trace: &mut Vec<TraceMsg>,
2268 ) -> Vec<FontMatchNoFallback> {
2269 let state = self.state_read();
2270 let pattern = match state.metadata.get(font_id) {
2271 Some(p) => p.clone(),
2272 None => return Vec::new(),
2273 };
2274 drop(state);
2275
2276 self.compute_fallbacks_for_pattern(&pattern, Some(font_id), trace)
2277 }
2278
2279 fn compute_fallbacks_for_pattern(
2280 &self,
2281 pattern: &FcPattern,
2282 exclude_id: Option<&FontId>,
2283 _trace: &mut Vec<TraceMsg>,
2284 ) -> Vec<FontMatchNoFallback> {
2285 let state = self.state_read();
2286 let mut candidates = Vec::new();
2287
2288 for (stored_pattern, id) in &state.patterns {
2290 if exclude_id.is_some() && exclude_id.unwrap() == id {
2292 continue;
2293 }
2294
2295 if !stored_pattern.unicode_ranges.is_empty() && !pattern.unicode_ranges.is_empty() {
2297 let unicode_compatibility = Self::calculate_unicode_compatibility(
2299 &pattern.unicode_ranges,
2300 &stored_pattern.unicode_ranges
2301 );
2302
2303 if unicode_compatibility > 0 {
2305 let style_score = Self::calculate_style_score(pattern, stored_pattern);
2306 candidates.push((
2307 FontMatchNoFallback {
2308 id: *id,
2309 unicode_ranges: stored_pattern.unicode_ranges.clone(),
2310 },
2311 unicode_compatibility,
2312 style_score,
2313 stored_pattern.clone(),
2314 ));
2315 }
2316 } else if pattern.unicode_ranges.is_empty() && !stored_pattern.unicode_ranges.is_empty() {
2317 let coverage = Self::calculate_unicode_coverage(&stored_pattern.unicode_ranges) as i32;
2319 let style_score = Self::calculate_style_score(pattern, stored_pattern);
2320 candidates.push((
2321 FontMatchNoFallback {
2322 id: *id,
2323 unicode_ranges: stored_pattern.unicode_ranges.clone(),
2324 },
2325 coverage,
2326 style_score,
2327 stored_pattern.clone(),
2328 ));
2329 }
2330 }
2331
2332 drop(state);
2333
2334 candidates.sort_by(|a, b| {
2336 b.1.cmp(&a.1)
2337 .then_with(|| a.2.cmp(&b.2))
2338 });
2339
2340 let mut seen_ranges = Vec::new();
2342 let mut deduplicated = Vec::new();
2343
2344 for (id, _, _, pattern) in candidates {
2345 let mut is_new_range = false;
2346
2347 for range in &pattern.unicode_ranges {
2348 if !seen_ranges.iter().any(|r: &UnicodeRange| r.overlaps(range)) {
2349 seen_ranges.push(*range);
2350 is_new_range = true;
2351 }
2352 }
2353
2354 if is_new_range {
2355 deduplicated.push(id);
2356 }
2357 }
2358
2359 deduplicated
2360 }
2361
2362 pub fn get_memory_font(&self, id: &FontId) -> Option<FcFont> {
2364 self.state_read().memory_fonts.get(id).cloned()
2365 }
2366
2367 fn trace_path(k: &FcPattern) -> String {
2369 k.name.as_ref().cloned().unwrap_or_else(|| "<unknown>".to_string())
2370 }
2371
2372 pub fn query_matches_internal(
2373 k: &FcPattern,
2374 pattern: &FcPattern,
2375 trace: &mut Vec<TraceMsg>,
2376 ) -> bool {
2377 if let Some(ref name) = pattern.name {
2379 if !k.name.as_ref().map_or(false, |kn| kn.contains(name)) {
2380 trace.push(TraceMsg {
2381 level: TraceLevel::Info,
2382 path: Self::trace_path(k),
2383 reason: MatchReason::NameMismatch {
2384 requested: pattern.name.clone(),
2385 found: k.name.clone(),
2386 },
2387 });
2388 return false;
2389 }
2390 }
2391
2392 if let Some(ref family) = pattern.family {
2394 if !k.family.as_ref().map_or(false, |kf| kf.contains(family)) {
2395 trace.push(TraceMsg {
2396 level: TraceLevel::Info,
2397 path: Self::trace_path(k),
2398 reason: MatchReason::FamilyMismatch {
2399 requested: pattern.family.clone(),
2400 found: k.family.clone(),
2401 },
2402 });
2403 return false;
2404 }
2405 }
2406
2407 let style_properties = [
2409 (
2410 "italic",
2411 pattern.italic.needs_to_match(),
2412 pattern.italic.matches(&k.italic),
2413 ),
2414 (
2415 "oblique",
2416 pattern.oblique.needs_to_match(),
2417 pattern.oblique.matches(&k.oblique),
2418 ),
2419 (
2420 "bold",
2421 pattern.bold.needs_to_match(),
2422 pattern.bold.matches(&k.bold),
2423 ),
2424 (
2425 "monospace",
2426 pattern.monospace.needs_to_match(),
2427 pattern.monospace.matches(&k.monospace),
2428 ),
2429 (
2430 "condensed",
2431 pattern.condensed.needs_to_match(),
2432 pattern.condensed.matches(&k.condensed),
2433 ),
2434 ];
2435
2436 for (property_name, needs_to_match, matches) in style_properties {
2437 if needs_to_match && !matches {
2438 let (requested, found) = match property_name {
2439 "italic" => (format!("{:?}", pattern.italic), format!("{:?}", k.italic)),
2440 "oblique" => (format!("{:?}", pattern.oblique), format!("{:?}", k.oblique)),
2441 "bold" => (format!("{:?}", pattern.bold), format!("{:?}", k.bold)),
2442 "monospace" => (
2443 format!("{:?}", pattern.monospace),
2444 format!("{:?}", k.monospace),
2445 ),
2446 "condensed" => (
2447 format!("{:?}", pattern.condensed),
2448 format!("{:?}", k.condensed),
2449 ),
2450 _ => (String::new(), String::new()),
2451 };
2452
2453 trace.push(TraceMsg {
2454 level: TraceLevel::Info,
2455 path: Self::trace_path(k),
2456 reason: MatchReason::StyleMismatch {
2457 property: property_name,
2458 requested,
2459 found,
2460 },
2461 });
2462 return false;
2463 }
2464 }
2465
2466 if pattern.weight != FcWeight::Normal && pattern.weight != k.weight {
2468 trace.push(TraceMsg {
2469 level: TraceLevel::Info,
2470 path: Self::trace_path(k),
2471 reason: MatchReason::WeightMismatch {
2472 requested: pattern.weight,
2473 found: k.weight,
2474 },
2475 });
2476 return false;
2477 }
2478
2479 if pattern.stretch != FcStretch::Normal && pattern.stretch != k.stretch {
2481 trace.push(TraceMsg {
2482 level: TraceLevel::Info,
2483 path: Self::trace_path(k),
2484 reason: MatchReason::StretchMismatch {
2485 requested: pattern.stretch,
2486 found: k.stretch,
2487 },
2488 });
2489 return false;
2490 }
2491
2492 if !pattern.unicode_ranges.is_empty() {
2494 let mut has_overlap = false;
2495
2496 for p_range in &pattern.unicode_ranges {
2497 for k_range in &k.unicode_ranges {
2498 if p_range.overlaps(k_range) {
2499 has_overlap = true;
2500 break;
2501 }
2502 }
2503 if has_overlap {
2504 break;
2505 }
2506 }
2507
2508 if !has_overlap {
2509 trace.push(TraceMsg {
2510 level: TraceLevel::Info,
2511 path: Self::trace_path(k),
2512 reason: MatchReason::UnicodeRangeMismatch {
2513 character: '\0', ranges: k.unicode_ranges.clone(),
2515 },
2516 });
2517 return false;
2518 }
2519 }
2520
2521 true
2522 }
2523
2524 #[cfg(feature = "std")]
2550 pub fn resolve_font_chain(
2551 &self,
2552 font_families: &[String],
2553 weight: FcWeight,
2554 italic: PatternMatch,
2555 oblique: PatternMatch,
2556 trace: &mut Vec<TraceMsg>,
2557 ) -> FontFallbackChain {
2558 self.resolve_font_chain_with_os(font_families, weight, italic, oblique, trace, OperatingSystem::current())
2559 }
2560
2561 #[cfg(feature = "std")]
2563 pub fn resolve_font_chain_with_os(
2564 &self,
2565 font_families: &[String],
2566 weight: FcWeight,
2567 italic: PatternMatch,
2568 oblique: PatternMatch,
2569 trace: &mut Vec<TraceMsg>,
2570 os: OperatingSystem,
2571 ) -> FontFallbackChain {
2572 self.resolve_font_chain_impl(font_families, weight, italic, oblique, None, trace, os)
2573 }
2574
2575 #[cfg(feature = "std")]
2590 pub fn resolve_font_chain_with_scripts(
2591 &self,
2592 font_families: &[String],
2593 weight: FcWeight,
2594 italic: PatternMatch,
2595 oblique: PatternMatch,
2596 scripts_hint: Option<&[UnicodeRange]>,
2597 trace: &mut Vec<TraceMsg>,
2598 ) -> FontFallbackChain {
2599 self.resolve_font_chain_impl(
2600 font_families, weight, italic, oblique, scripts_hint,
2601 trace, OperatingSystem::current(),
2602 )
2603 }
2604
2605 #[cfg(feature = "std")]
2609 fn resolve_font_chain_impl(
2610 &self,
2611 font_families: &[String],
2612 weight: FcWeight,
2613 italic: PatternMatch,
2614 oblique: PatternMatch,
2615 scripts_hint: Option<&[UnicodeRange]>,
2616 trace: &mut Vec<TraceMsg>,
2617 os: OperatingSystem,
2618 ) -> FontFallbackChain {
2619 let scripts_hint_hash = scripts_hint.map(hash_scripts_hint);
2623 let cache_key = FontChainCacheKey {
2624 font_families: font_families.to_vec(),
2625 weight,
2626 italic,
2627 oblique,
2628 scripts_hint_hash,
2629 };
2630
2631 if let Some(cached) = self
2632 .shared
2633 .chain_cache
2634 .lock()
2635 .ok()
2636 .and_then(|c| c.get(&cache_key).cloned())
2637 {
2638 return cached;
2639 }
2640
2641 let expanded_families = expand_font_families(font_families, os, &[]);
2643
2644 let chain = self.resolve_font_chain_uncached(
2646 &expanded_families,
2647 weight,
2648 italic,
2649 oblique,
2650 scripts_hint,
2651 trace,
2652 );
2653
2654 if let Ok(mut cache) = self.shared.chain_cache.lock() {
2656 cache.insert(cache_key, chain.clone());
2657 }
2658
2659 chain
2660 }
2661
2662 #[cfg(feature = "std")]
2670 fn resolve_font_chain_uncached(
2671 &self,
2672 font_families: &[String],
2673 weight: FcWeight,
2674 italic: PatternMatch,
2675 oblique: PatternMatch,
2676 scripts_hint: Option<&[UnicodeRange]>,
2677 trace: &mut Vec<TraceMsg>,
2678 ) -> FontFallbackChain {
2679 let mut css_fallbacks = Vec::new();
2680
2681 for (_i, family) in font_families.iter().enumerate() {
2683 let (pattern, is_generic) = if config::is_generic_family(family) {
2685 let monospace = if family.eq_ignore_ascii_case("monospace") {
2686 PatternMatch::True
2687 } else {
2688 PatternMatch::False
2689 };
2690 let pattern = FcPattern {
2691 name: None,
2692 weight,
2693 italic,
2694 oblique,
2695 monospace,
2696 unicode_ranges: Vec::new(),
2697 ..Default::default()
2698 };
2699 (pattern, true)
2700 } else {
2701 let pattern = FcPattern {
2703 name: Some(family.clone()),
2704 weight,
2705 italic,
2706 oblique,
2707 unicode_ranges: Vec::new(),
2708 ..Default::default()
2709 };
2710 (pattern, false)
2711 };
2712
2713 let mut matches = if is_generic {
2716 self.query_internal(&pattern, trace)
2718 } else {
2719 self.fuzzy_query_by_name(family, weight, italic, oblique, &[], trace)
2721 };
2722
2723 if is_generic && matches.len() > 5 {
2725 matches.truncate(5);
2726 }
2727
2728 css_fallbacks.push(CssFallbackGroup {
2731 css_name: family.clone(),
2732 fonts: matches,
2733 });
2734 }
2735
2736 let important_ranges: &[UnicodeRange] =
2749 scripts_hint.unwrap_or(DEFAULT_UNICODE_FALLBACK_SCRIPTS);
2750 let unicode_fallbacks = if important_ranges.is_empty() {
2751 Vec::new()
2752 } else {
2753 let all_uncovered = vec![false; important_ranges.len()];
2754 self.find_unicode_fallbacks(
2755 important_ranges,
2756 &all_uncovered,
2757 &css_fallbacks,
2758 weight,
2759 italic,
2760 oblique,
2761 trace,
2762 )
2763 };
2764
2765 let mut unicode_fallbacks = unicode_fallbacks;
2773 if css_fallbacks.is_empty() && unicode_fallbacks.is_empty() {
2774 let st = self.state_read();
2775 if let Some((pat, id)) = st.patterns.iter().next() {
2776 unicode_fallbacks.push(FontMatch {
2777 id: *id,
2778 unicode_ranges: pat.unicode_ranges.clone(),
2779 fallbacks: Vec::new(),
2780 });
2781 }
2782 }
2783
2784 FontFallbackChain {
2785 css_fallbacks,
2786 unicode_fallbacks,
2787 original_stack: font_families.to_vec(),
2788 }
2789 }
2790
2791 #[allow(dead_code)]
2793 fn extract_unicode_ranges(text: &str) -> Vec<UnicodeRange> {
2794 let mut chars: Vec<char> = text.chars().collect();
2795 chars.sort_unstable();
2796 chars.dedup();
2797
2798 if chars.is_empty() {
2799 return Vec::new();
2800 }
2801
2802 let mut ranges = Vec::new();
2803 let mut range_start = chars[0] as u32;
2804 let mut range_end = range_start;
2805
2806 for &c in &chars[1..] {
2807 let codepoint = c as u32;
2808 if codepoint == range_end + 1 {
2809 range_end = codepoint;
2810 } else {
2811 ranges.push(UnicodeRange { start: range_start, end: range_end });
2812 range_start = codepoint;
2813 range_end = codepoint;
2814 }
2815 }
2816
2817 ranges.push(UnicodeRange { start: range_start, end: range_end });
2818 ranges
2819 }
2820
2821 #[cfg(feature = "std")]
2828 fn fuzzy_query_by_name(
2829 &self,
2830 requested_name: &str,
2831 weight: FcWeight,
2832 italic: PatternMatch,
2833 oblique: PatternMatch,
2834 unicode_ranges: &[UnicodeRange],
2835 _trace: &mut Vec<TraceMsg>,
2836 ) -> Vec<FontMatch> {
2837 let tokens = Self::extract_font_name_tokens(requested_name);
2839
2840 if tokens.is_empty() {
2841 return Vec::new();
2842 }
2843
2844 let tokens_lower: Vec<String> = tokens.iter().map(|t| t.to_ascii_lowercase()).collect();
2846
2847 let state = self.state_read();
2853
2854 let first_token = &tokens_lower[0];
2856 let mut candidate_ids = match state.token_index.get(first_token) {
2857 Some(ids) if !ids.is_empty() => ids.clone(),
2858 _ => {
2859 return Vec::new();
2861 }
2862 };
2863
2864 for token in &tokens_lower[1..] {
2866 if let Some(token_ids) = state.token_index.get(token) {
2867 let intersection: alloc::collections::BTreeSet<FontId> =
2869 candidate_ids.intersection(token_ids).copied().collect();
2870
2871 if intersection.is_empty() {
2872 break;
2874 } else {
2875 candidate_ids = intersection;
2877 }
2878 } else {
2879 break;
2881 }
2882 }
2883
2884 let mut candidates = Vec::new();
2886
2887 for id in candidate_ids {
2888 let pattern = match state.metadata.get(&id) {
2889 Some(p) => p,
2890 None => continue,
2891 };
2892
2893 let font_tokens_lower = match state.font_tokens.get(&id) {
2895 Some(tokens) => tokens,
2896 None => continue,
2897 };
2898
2899 if font_tokens_lower.is_empty() {
2900 continue;
2901 }
2902
2903 let token_matches = tokens_lower.iter()
2906 .filter(|req_token| {
2907 font_tokens_lower.iter().any(|font_token| {
2908 font_token == *req_token
2910 })
2911 })
2912 .count();
2913
2914 if token_matches == 0 {
2916 continue;
2917 }
2918
2919 let token_similarity = (token_matches * 100 / tokens.len()) as i32;
2921
2922 let unicode_similarity = if !unicode_ranges.is_empty() && !pattern.unicode_ranges.is_empty() {
2924 Self::calculate_unicode_compatibility(unicode_ranges, &pattern.unicode_ranges)
2925 } else {
2926 0
2927 };
2928
2929 if !unicode_ranges.is_empty() && unicode_similarity == 0 {
2932 continue;
2933 }
2934
2935 let style_score = Self::calculate_style_score(&FcPattern {
2936 weight,
2937 italic,
2938 oblique,
2939 ..Default::default()
2940 }, pattern);
2941
2942 candidates.push((
2943 id,
2944 token_similarity,
2945 unicode_similarity,
2946 style_score,
2947 pattern.clone(),
2948 ));
2949 }
2950
2951 candidates.sort_by(|a, b| {
2957 if !unicode_ranges.is_empty() {
2958 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 {
2965 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)) }
2971 });
2972
2973 candidates.truncate(5);
2975
2976 candidates
2978 .into_iter()
2979 .map(|(id, _token_sim, _unicode_sim, _style, pattern)| {
2980 FontMatch {
2981 id,
2982 unicode_ranges: pattern.unicode_ranges.clone(),
2983 fallbacks: Vec::new(), }
2985 })
2986 .collect()
2987 }
2988
2989 pub fn extract_font_name_tokens(name: &str) -> Vec<String> {
2993 let mut tokens = Vec::new();
2994 let mut current_token = String::new();
2995 let mut last_was_lower = false;
2996
2997 for c in name.chars() {
2998 if c.is_whitespace() || c == '-' || c == '_' {
2999 if !current_token.is_empty() {
3001 tokens.push(current_token.clone());
3002 current_token.clear();
3003 }
3004 last_was_lower = false;
3005 } else if c.is_uppercase() && last_was_lower && !current_token.is_empty() {
3006 tokens.push(current_token.clone());
3008 current_token.clear();
3009 current_token.push(c);
3010 last_was_lower = false;
3011 } else {
3012 current_token.push(c);
3013 last_was_lower = c.is_lowercase();
3014 }
3015 }
3016
3017 if !current_token.is_empty() {
3018 tokens.push(current_token);
3019 }
3020
3021 tokens
3022 }
3023
3024 fn find_unicode_fallbacks(
3028 &self,
3029 unicode_ranges: &[UnicodeRange],
3030 covered_chars: &[bool],
3031 existing_groups: &[CssFallbackGroup],
3032 _weight: FcWeight,
3033 _italic: PatternMatch,
3034 _oblique: PatternMatch,
3035 trace: &mut Vec<TraceMsg>,
3036 ) -> Vec<FontMatch> {
3037 let mut uncovered_ranges = Vec::new();
3039 for (i, &covered) in covered_chars.iter().enumerate() {
3040 if !covered && i < unicode_ranges.len() {
3041 uncovered_ranges.push(unicode_ranges[i].clone());
3042 }
3043 }
3044
3045 if uncovered_ranges.is_empty() {
3046 return Vec::new();
3047 }
3048
3049 let pattern = FcPattern {
3054 name: None,
3055 weight: FcWeight::Normal, italic: PatternMatch::DontCare,
3057 oblique: PatternMatch::DontCare,
3058 unicode_ranges: uncovered_ranges.clone(),
3059 ..Default::default()
3060 };
3061
3062 let mut candidates = self.query_internal(&pattern, trace);
3063
3064 let existing_prefixes: Vec<String> = existing_groups
3067 .iter()
3068 .flat_map(|group| {
3069 group.fonts.iter().filter_map(|font| {
3070 self.get_metadata_by_id(&font.id)
3071 .and_then(|meta| meta.family.clone())
3072 .and_then(|family| {
3073 family.split_whitespace()
3075 .take(2)
3076 .collect::<Vec<_>>()
3077 .join(" ")
3078 .into()
3079 })
3080 })
3081 })
3082 .collect();
3083
3084 candidates.sort_by(|a, b| {
3088 let a_meta = self.get_metadata_by_id(&a.id);
3089 let b_meta = self.get_metadata_by_id(&b.id);
3090
3091 let a_score = Self::calculate_font_similarity_score(a_meta.as_ref(), &existing_prefixes);
3092 let b_score = Self::calculate_font_similarity_score(b_meta.as_ref(), &existing_prefixes);
3093
3094 b_score.cmp(&a_score) .then_with(|| {
3096 let a_coverage = Self::calculate_unicode_compatibility(&uncovered_ranges, &a.unicode_ranges);
3097 let b_coverage = Self::calculate_unicode_compatibility(&uncovered_ranges, &b.unicode_ranges);
3098 b_coverage.cmp(&a_coverage)
3099 })
3100 });
3101
3102 let mut result = Vec::new();
3104 let mut remaining_uncovered: Vec<bool> = vec![true; uncovered_ranges.len()];
3105
3106 for candidate in candidates {
3107 let mut covers_new_range = false;
3109
3110 for (i, range) in uncovered_ranges.iter().enumerate() {
3111 if remaining_uncovered[i] {
3112 for font_range in &candidate.unicode_ranges {
3114 if font_range.overlaps(range) {
3115 remaining_uncovered[i] = false;
3116 covers_new_range = true;
3117 break;
3118 }
3119 }
3120 }
3121 }
3122
3123 if covers_new_range {
3125 result.push(candidate);
3126
3127 if remaining_uncovered.iter().all(|&uncovered| !uncovered) {
3129 break;
3130 }
3131 }
3132 }
3133
3134 result
3135 }
3136
3137 fn calculate_font_similarity_score(
3140 font_meta: Option<&FcPattern>,
3141 existing_prefixes: &[String],
3142 ) -> i32 {
3143 let Some(meta) = font_meta else { return 0; };
3144 let Some(family) = &meta.family else { return 0; };
3145
3146 for prefix in existing_prefixes {
3148 if family.starts_with(prefix) {
3149 return 100; }
3151 if family.contains(prefix) {
3152 return 50; }
3154 }
3155
3156 0 }
3158
3159 pub fn calculate_unicode_coverage(ranges: &[UnicodeRange]) -> u64 {
3162 ranges
3163 .iter()
3164 .map(|range| (range.end - range.start + 1) as u64)
3165 .sum()
3166 }
3167
3168 pub fn calculate_unicode_compatibility(
3171 requested: &[UnicodeRange],
3172 available: &[UnicodeRange],
3173 ) -> i32 {
3174 if requested.is_empty() {
3175 return Self::calculate_unicode_coverage(available) as i32;
3177 }
3178
3179 let mut total_coverage = 0u32;
3180
3181 for req_range in requested {
3182 for avail_range in available {
3183 let overlap_start = req_range.start.max(avail_range.start);
3185 let overlap_end = req_range.end.min(avail_range.end);
3186
3187 if overlap_start <= overlap_end {
3188 let overlap_size = overlap_end - overlap_start + 1;
3190 total_coverage += overlap_size;
3191 }
3192 }
3193 }
3194
3195 total_coverage as i32
3196 }
3197
3198 pub fn calculate_style_score(original: &FcPattern, candidate: &FcPattern) -> i32 {
3199
3200 let mut score = 0_i32;
3201
3202 if (original.bold == PatternMatch::True && candidate.weight == FcWeight::Bold)
3204 || (original.bold == PatternMatch::False && candidate.weight != FcWeight::Bold)
3205 {
3206 } else {
3209 let weight_diff = (original.weight as i32 - candidate.weight as i32).abs();
3211 score += weight_diff as i32;
3212 }
3213
3214 if original.weight == candidate.weight {
3217 score -= 15;
3218 if original.weight == FcWeight::Normal {
3219 score -= 10; }
3221 }
3222
3223 if (original.condensed == PatternMatch::True && candidate.stretch.is_condensed())
3225 || (original.condensed == PatternMatch::False && !candidate.stretch.is_condensed())
3226 {
3227 } else {
3230 let stretch_diff = (original.stretch as i32 - candidate.stretch as i32).abs();
3232 score += (stretch_diff * 100) as i32;
3233 }
3234
3235 let style_props = [
3237 (original.italic, candidate.italic, 300, 150),
3238 (original.oblique, candidate.oblique, 200, 100),
3239 (original.bold, candidate.bold, 300, 150),
3240 (original.monospace, candidate.monospace, 100, 50),
3241 (original.condensed, candidate.condensed, 100, 50),
3242 ];
3243
3244 for (orig, cand, mismatch_penalty, dontcare_penalty) in style_props {
3245 if orig.needs_to_match() {
3246 if orig == PatternMatch::False && cand == PatternMatch::DontCare {
3247 score += dontcare_penalty / 2;
3250 } else if !orig.matches(&cand) {
3251 if cand == PatternMatch::DontCare {
3252 score += dontcare_penalty;
3253 } else {
3254 score += mismatch_penalty;
3255 }
3256 } else if orig == PatternMatch::True && cand == PatternMatch::True {
3257 score -= 20;
3259 } else if orig == PatternMatch::False && cand == PatternMatch::False {
3260 score -= 20;
3263 }
3264 } else {
3265 if cand == PatternMatch::True {
3270 score += dontcare_penalty / 3;
3271 }
3272 }
3273 }
3274
3275 if let (Some(name), Some(family)) = (&candidate.name, &candidate.family) {
3281 let name_lower = name.to_ascii_lowercase();
3282 let family_lower = family.to_ascii_lowercase();
3283
3284 let extra = if name_lower.starts_with(&family_lower) {
3286 name_lower[family_lower.len()..].to_string()
3287 } else {
3288 String::new()
3289 };
3290
3291 let stripped = extra
3293 .replace("regular", "")
3294 .replace("normal", "")
3295 .replace("book", "")
3296 .replace("roman", "");
3297 let stripped = stripped.trim();
3298
3299 if stripped.is_empty() {
3300 score -= 50;
3302 } else {
3303 let extra_words = stripped.split_whitespace().count();
3305 score += (extra_words as i32) * 25;
3306 }
3307 }
3308
3309 if let Some(ref subfamily) = candidate.metadata.font_subfamily {
3313 let sf_lower = subfamily.to_ascii_lowercase();
3314 if sf_lower == "regular" {
3315 score -= 30;
3316 }
3317 }
3318
3319 score
3320 }
3321}
3322
3323#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3324fn FcScanDirectories() -> Option<(Vec<(FcPattern, FcFontPath)>, BTreeMap<String, FcFontRenderConfig>)> {
3325 use std::fs;
3326 use std::path::Path;
3327
3328 const BASE_FONTCONFIG_PATH: &str = "/etc/fonts/fonts.conf";
3329
3330 if !Path::new(BASE_FONTCONFIG_PATH).exists() {
3331 return None;
3332 }
3333
3334 let mut font_paths = Vec::with_capacity(32);
3335 let mut paths_to_visit = vec![(None, PathBuf::from(BASE_FONTCONFIG_PATH))];
3336 let mut render_configs: BTreeMap<String, FcFontRenderConfig> = BTreeMap::new();
3337
3338 while let Some((prefix, path_to_visit)) = paths_to_visit.pop() {
3339 let path = match process_path(&prefix, path_to_visit, true) {
3340 Some(path) => path,
3341 None => continue,
3342 };
3343
3344 let metadata = match fs::metadata(&path) {
3345 Ok(metadata) => metadata,
3346 Err(_) => continue,
3347 };
3348
3349 if metadata.is_file() {
3350 let xml_utf8 = match fs::read_to_string(&path) {
3351 Ok(xml_utf8) => xml_utf8,
3352 Err(_) => continue,
3353 };
3354
3355 if ParseFontsConf(&xml_utf8, &mut paths_to_visit, &mut font_paths).is_none() {
3356 continue;
3357 }
3358
3359 ParseFontsConfRenderConfig(&xml_utf8, &mut render_configs);
3361 } else if metadata.is_dir() {
3362 let dir_entries = match fs::read_dir(&path) {
3363 Ok(dir_entries) => dir_entries,
3364 Err(_) => continue,
3365 };
3366
3367 for entry_result in dir_entries {
3368 let entry = match entry_result {
3369 Ok(entry) => entry,
3370 Err(_) => continue,
3371 };
3372
3373 let entry_path = entry.path();
3374
3375 let entry_metadata = match fs::metadata(&entry_path) {
3377 Ok(metadata) => metadata,
3378 Err(_) => continue,
3379 };
3380
3381 if !entry_metadata.is_file() {
3382 continue;
3383 }
3384
3385 let file_name = match entry_path.file_name() {
3386 Some(name) => name,
3387 None => continue,
3388 };
3389
3390 let file_name_str = file_name.to_string_lossy();
3391 if file_name_str.starts_with(|c: char| c.is_ascii_digit())
3392 && file_name_str.ends_with(".conf")
3393 {
3394 paths_to_visit.push((None, entry_path));
3395 }
3396 }
3397 }
3398 }
3399
3400 if font_paths.is_empty() {
3401 return None;
3402 }
3403
3404 Some((FcScanDirectoriesInner(&font_paths), render_configs))
3405}
3406
3407#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3409fn ParseFontsConf(
3410 input: &str,
3411 paths_to_visit: &mut Vec<(Option<String>, PathBuf)>,
3412 font_paths: &mut Vec<(Option<String>, String)>,
3413) -> Option<()> {
3414 use xmlparser::Token::*;
3415 use xmlparser::Tokenizer;
3416
3417 const TAG_INCLUDE: &str = "include";
3418 const TAG_DIR: &str = "dir";
3419 const ATTRIBUTE_PREFIX: &str = "prefix";
3420
3421 let mut current_prefix: Option<&str> = None;
3422 let mut current_path: Option<&str> = None;
3423 let mut is_in_include = false;
3424 let mut is_in_dir = false;
3425
3426 for token_result in Tokenizer::from(input) {
3427 let token = match token_result {
3428 Ok(token) => token,
3429 Err(_) => return None,
3430 };
3431
3432 match token {
3433 ElementStart { local, .. } => {
3434 if is_in_include || is_in_dir {
3435 return None; }
3437
3438 match local.as_str() {
3439 TAG_INCLUDE => {
3440 is_in_include = true;
3441 }
3442 TAG_DIR => {
3443 is_in_dir = true;
3444 }
3445 _ => continue,
3446 }
3447
3448 current_path = None;
3449 }
3450 Text { text, .. } => {
3451 let text = text.as_str().trim();
3452 if text.is_empty() {
3453 continue;
3454 }
3455 if is_in_include || is_in_dir {
3456 current_path = Some(text);
3457 }
3458 }
3459 Attribute { local, value, .. } => {
3460 if !is_in_include && !is_in_dir {
3461 continue;
3462 }
3463 if local.as_str() == ATTRIBUTE_PREFIX {
3465 current_prefix = Some(value.as_str());
3466 }
3467 }
3468 ElementEnd { end, .. } => {
3469 let end_tag = match end {
3470 xmlparser::ElementEnd::Close(_, a) => a,
3471 _ => continue,
3472 };
3473
3474 match end_tag.as_str() {
3475 TAG_INCLUDE => {
3476 if !is_in_include {
3477 continue;
3478 }
3479
3480 if let Some(current_path) = current_path.as_ref() {
3481 paths_to_visit.push((
3482 current_prefix.map(ToOwned::to_owned),
3483 PathBuf::from(*current_path),
3484 ));
3485 }
3486 }
3487 TAG_DIR => {
3488 if !is_in_dir {
3489 continue;
3490 }
3491
3492 if let Some(current_path) = current_path.as_ref() {
3493 font_paths.push((
3494 current_prefix.map(ToOwned::to_owned),
3495 (*current_path).to_owned(),
3496 ));
3497 }
3498 }
3499 _ => continue,
3500 }
3501
3502 is_in_include = false;
3503 is_in_dir = false;
3504 current_path = None;
3505 current_prefix = None;
3506 }
3507 _ => {}
3508 }
3509 }
3510
3511 Some(())
3512}
3513
3514#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3526fn ParseFontsConfRenderConfig(
3527 input: &str,
3528 configs: &mut BTreeMap<String, FcFontRenderConfig>,
3529) {
3530 use xmlparser::Token::*;
3531 use xmlparser::Tokenizer;
3532
3533 #[derive(Clone, Copy, PartialEq)]
3535 enum State {
3536 Idle,
3538 InMatchFont,
3540 InTestFamily,
3542 InEdit,
3544 InValue,
3546 }
3547
3548 let mut state = State::Idle;
3549 let mut match_is_font_target = false;
3550 let mut current_family: Option<String> = None;
3551 let mut current_edit_name: Option<String> = None;
3552 let mut current_value: Option<String> = None;
3553 let mut value_tag: Option<String> = None;
3554 let mut config = FcFontRenderConfig::default();
3555 let mut in_test = false;
3556 let mut test_name: Option<String> = None;
3557
3558 for token_result in Tokenizer::from(input) {
3559 let token = match token_result {
3560 Ok(token) => token,
3561 Err(_) => continue,
3562 };
3563
3564 match token {
3565 ElementStart { local, .. } => {
3566 let tag = local.as_str();
3567 match tag {
3568 "match" => {
3569 match_is_font_target = false;
3571 current_family = None;
3572 config = FcFontRenderConfig::default();
3573 }
3574 "test" if state == State::InMatchFont => {
3575 in_test = true;
3576 test_name = None;
3577 }
3578 "edit" if state == State::InMatchFont => {
3579 current_edit_name = None;
3580 }
3581 "bool" | "double" | "const" | "string" | "int" => {
3582 if state == State::InTestFamily || state == State::InEdit {
3583 value_tag = Some(tag.to_owned());
3584 current_value = None;
3585 }
3586 }
3587 _ => {}
3588 }
3589 }
3590 Attribute { local, value, .. } => {
3591 let attr_name = local.as_str();
3592 let attr_value = value.as_str();
3593
3594 match attr_name {
3595 "target" => {
3596 if attr_value == "font" {
3597 match_is_font_target = true;
3598 }
3599 }
3600 "name" => {
3601 if in_test && state == State::InMatchFont {
3602 test_name = Some(attr_value.to_owned());
3603 } else if state == State::InMatchFont {
3604 current_edit_name = Some(attr_value.to_owned());
3605 }
3606 }
3607 _ => {}
3608 }
3609 }
3610 Text { text, .. } => {
3611 let text = text.as_str().trim();
3612 if !text.is_empty() && (state == State::InTestFamily || state == State::InEdit) {
3613 current_value = Some(text.to_owned());
3614 }
3615 }
3616 ElementEnd { end, .. } => {
3617 match end {
3618 xmlparser::ElementEnd::Open => {
3619 if match_is_font_target && state == State::Idle {
3621 state = State::InMatchFont;
3622 match_is_font_target = false;
3623 } else if in_test {
3624 if test_name.as_deref() == Some("family") {
3625 state = State::InTestFamily;
3626 }
3627 in_test = false;
3628 } else if current_edit_name.is_some() && state == State::InMatchFont {
3629 state = State::InEdit;
3630 }
3631 }
3632 xmlparser::ElementEnd::Close(_, local) => {
3633 let tag = local.as_str();
3634 match tag {
3635 "match" => {
3636 if let Some(family) = current_family.take() {
3638 let empty = FcFontRenderConfig::default();
3639 if config != empty {
3640 configs.insert(family, config.clone());
3641 }
3642 }
3643 state = State::Idle;
3644 config = FcFontRenderConfig::default();
3645 }
3646 "test" => {
3647 if state == State::InTestFamily {
3648 if let Some(ref val) = current_value {
3650 current_family = Some(val.clone());
3651 }
3652 state = State::InMatchFont;
3653 }
3654 current_value = None;
3655 value_tag = None;
3656 }
3657 "edit" => {
3658 if state == State::InEdit {
3659 if let (Some(ref name), Some(ref val)) = (¤t_edit_name, ¤t_value) {
3661 apply_edit_value(&mut config, name, val, value_tag.as_deref());
3662 }
3663 state = State::InMatchFont;
3664 }
3665 current_edit_name = None;
3666 current_value = None;
3667 value_tag = None;
3668 }
3669 "bool" | "double" | "const" | "string" | "int" => {
3670 }
3672 _ => {}
3673 }
3674 }
3675 xmlparser::ElementEnd::Empty => {
3676 }
3678 }
3679 }
3680 _ => {}
3681 }
3682 }
3683}
3684
3685#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3687fn apply_edit_value(
3688 config: &mut FcFontRenderConfig,
3689 edit_name: &str,
3690 value: &str,
3691 value_tag: Option<&str>,
3692) {
3693 match edit_name {
3694 "antialias" => {
3695 config.antialias = parse_bool_value(value);
3696 }
3697 "hinting" => {
3698 config.hinting = parse_bool_value(value);
3699 }
3700 "autohint" => {
3701 config.autohint = parse_bool_value(value);
3702 }
3703 "embeddedbitmap" => {
3704 config.embeddedbitmap = parse_bool_value(value);
3705 }
3706 "embolden" => {
3707 config.embolden = parse_bool_value(value);
3708 }
3709 "minspace" => {
3710 config.minspace = parse_bool_value(value);
3711 }
3712 "hintstyle" => {
3713 config.hintstyle = parse_hintstyle_const(value);
3714 }
3715 "rgba" => {
3716 config.rgba = parse_rgba_const(value);
3717 }
3718 "lcdfilter" => {
3719 config.lcdfilter = parse_lcdfilter_const(value);
3720 }
3721 "dpi" => {
3722 if let Ok(v) = value.parse::<f64>() {
3723 config.dpi = Some(v);
3724 }
3725 }
3726 "scale" => {
3727 if let Ok(v) = value.parse::<f64>() {
3728 config.scale = Some(v);
3729 }
3730 }
3731 _ => {
3732 }
3734 }
3735}
3736
3737#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3738fn parse_bool_value(value: &str) -> Option<bool> {
3739 match value {
3740 "true" => Some(true),
3741 "false" => Some(false),
3742 _ => None,
3743 }
3744}
3745
3746#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3747fn parse_hintstyle_const(value: &str) -> Option<FcHintStyle> {
3748 match value {
3749 "hintnone" => Some(FcHintStyle::None),
3750 "hintslight" => Some(FcHintStyle::Slight),
3751 "hintmedium" => Some(FcHintStyle::Medium),
3752 "hintfull" => Some(FcHintStyle::Full),
3753 _ => None,
3754 }
3755}
3756
3757#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3758fn parse_rgba_const(value: &str) -> Option<FcRgba> {
3759 match value {
3760 "unknown" => Some(FcRgba::Unknown),
3761 "rgb" => Some(FcRgba::Rgb),
3762 "bgr" => Some(FcRgba::Bgr),
3763 "vrgb" => Some(FcRgba::Vrgb),
3764 "vbgr" => Some(FcRgba::Vbgr),
3765 "none" => Some(FcRgba::None),
3766 _ => None,
3767 }
3768}
3769
3770#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3771fn parse_lcdfilter_const(value: &str) -> Option<FcLcdFilter> {
3772 match value {
3773 "lcdnone" => Some(FcLcdFilter::None),
3774 "lcddefault" => Some(FcLcdFilter::Default),
3775 "lcdlight" => Some(FcLcdFilter::Light),
3776 "lcdlegacy" => Some(FcLcdFilter::Legacy),
3777 _ => None,
3778 }
3779}
3780
3781#[cfg(all(feature = "std", feature = "parsing"))]
3784const UNICODE_RANGE_MAPPINGS: &[(usize, u32, u32)] = &[
3785 (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), ];
3918
3919#[cfg(all(feature = "std", feature = "parsing"))]
3922struct ParsedFontFace {
3923 pattern: FcPattern,
3924 font_index: usize,
3925}
3926
3927#[cfg(all(feature = "std", feature = "parsing"))]
3933fn parse_font_faces(font_bytes: &[u8]) -> Option<Vec<ParsedFontFace>> {
3934 use allsorts::{
3935 binary::read::ReadScope,
3936 font_data::FontData,
3937 get_name::fontcode_get_name,
3938 post::PostTable,
3939 tables::{
3940 os2::Os2, HeadTable, NameTable,
3941 },
3942 tag,
3943 };
3944 use std::collections::BTreeSet;
3945
3946 const FONT_SPECIFIER_NAME_ID: u16 = 4;
3947 const FONT_SPECIFIER_FAMILY_ID: u16 = 1;
3948
3949 let max_fonts = if font_bytes.len() >= 12 && &font_bytes[0..4] == b"ttcf" {
3950 let num_fonts =
3952 u32::from_be_bytes([font_bytes[8], font_bytes[9], font_bytes[10], font_bytes[11]]);
3953 std::cmp::min(num_fonts as usize, 100)
3955 } else {
3956 1
3958 };
3959
3960 let scope = ReadScope::new(font_bytes);
3961 let font_file = scope.read::<FontData<'_>>().ok()?;
3962
3963 let mut results = Vec::new();
3965
3966 for font_index in 0..max_fonts {
3967 let provider = font_file.table_provider(font_index).ok()?;
3968 let head_data = provider.table_data(tag::HEAD).ok()??.into_owned();
3969 let head_table = ReadScope::new(&head_data).read::<HeadTable>().ok()?;
3970
3971 let is_bold = head_table.is_bold();
3972 let is_italic = head_table.is_italic();
3973 let mut detected_monospace = None;
3974
3975 let post_data = provider.table_data(tag::POST).ok()??;
3976 if let Ok(post_table) = ReadScope::new(&post_data).read::<PostTable>() {
3977 detected_monospace = Some(post_table.header.is_fixed_pitch != 0);
3979 }
3980
3981 let os2_data = provider.table_data(tag::OS_2).ok()??;
3983 let os2_table = ReadScope::new(&os2_data)
3984 .read_dep::<Os2>(os2_data.len())
3985 .ok()?;
3986
3987 let is_oblique = os2_table
3989 .fs_selection
3990 .contains(allsorts::tables::os2::FsSelection::OBLIQUE);
3991 let weight = FcWeight::from_u16(os2_table.us_weight_class);
3992 let stretch = FcStretch::from_u16(os2_table.us_width_class);
3993
3994 let mut unicode_ranges = Vec::new();
3998
3999 let os2_ranges = [
4001 os2_table.ul_unicode_range1,
4002 os2_table.ul_unicode_range2,
4003 os2_table.ul_unicode_range3,
4004 os2_table.ul_unicode_range4,
4005 ];
4006
4007 for &(bit, start, end) in UNICODE_RANGE_MAPPINGS {
4008 let range_idx = bit / 32;
4009 let bit_pos = bit % 32;
4010 if range_idx < 4 && (os2_ranges[range_idx] & (1 << bit_pos)) != 0 {
4011 unicode_ranges.push(UnicodeRange { start, end });
4012 }
4013 }
4014
4015 unicode_ranges = verify_unicode_ranges_with_cmap(&provider, unicode_ranges);
4019
4020 if unicode_ranges.is_empty() {
4022 if let Some(cmap_ranges) = analyze_cmap_coverage(&provider) {
4023 unicode_ranges = cmap_ranges;
4024 }
4025 }
4026
4027 let is_monospace = detect_monospace(&provider, &os2_table, detected_monospace)
4029 .unwrap_or(false);
4030
4031 let name_data = provider.table_data(tag::NAME).ok()??.into_owned();
4032 let name_table = ReadScope::new(&name_data).read::<NameTable>().ok()?;
4033
4034 let mut metadata = FcFontMetadata::default();
4036
4037 const NAME_ID_COPYRIGHT: u16 = 0;
4038 const NAME_ID_FAMILY: u16 = 1;
4039 const NAME_ID_SUBFAMILY: u16 = 2;
4040 const NAME_ID_UNIQUE_ID: u16 = 3;
4041 const NAME_ID_FULL_NAME: u16 = 4;
4042 const NAME_ID_VERSION: u16 = 5;
4043 const NAME_ID_POSTSCRIPT_NAME: u16 = 6;
4044 const NAME_ID_TRADEMARK: u16 = 7;
4045 const NAME_ID_MANUFACTURER: u16 = 8;
4046 const NAME_ID_DESIGNER: u16 = 9;
4047 const NAME_ID_DESCRIPTION: u16 = 10;
4048 const NAME_ID_VENDOR_URL: u16 = 11;
4049 const NAME_ID_DESIGNER_URL: u16 = 12;
4050 const NAME_ID_LICENSE: u16 = 13;
4051 const NAME_ID_LICENSE_URL: u16 = 14;
4052 const NAME_ID_PREFERRED_FAMILY: u16 = 16;
4053 const NAME_ID_PREFERRED_SUBFAMILY: u16 = 17;
4054
4055 metadata.copyright = get_name_string(&name_data, NAME_ID_COPYRIGHT);
4056 metadata.font_family = get_name_string(&name_data, NAME_ID_FAMILY);
4057 metadata.font_subfamily = get_name_string(&name_data, NAME_ID_SUBFAMILY);
4058 metadata.full_name = get_name_string(&name_data, NAME_ID_FULL_NAME);
4059 metadata.unique_id = get_name_string(&name_data, NAME_ID_UNIQUE_ID);
4060 metadata.version = get_name_string(&name_data, NAME_ID_VERSION);
4061 metadata.postscript_name = get_name_string(&name_data, NAME_ID_POSTSCRIPT_NAME);
4062 metadata.trademark = get_name_string(&name_data, NAME_ID_TRADEMARK);
4063 metadata.manufacturer = get_name_string(&name_data, NAME_ID_MANUFACTURER);
4064 metadata.designer = get_name_string(&name_data, NAME_ID_DESIGNER);
4065 metadata.id_description = get_name_string(&name_data, NAME_ID_DESCRIPTION);
4066 metadata.designer_url = get_name_string(&name_data, NAME_ID_DESIGNER_URL);
4067 metadata.manufacturer_url = get_name_string(&name_data, NAME_ID_VENDOR_URL);
4068 metadata.license = get_name_string(&name_data, NAME_ID_LICENSE);
4069 metadata.license_url = get_name_string(&name_data, NAME_ID_LICENSE_URL);
4070 metadata.preferred_family = get_name_string(&name_data, NAME_ID_PREFERRED_FAMILY);
4071 metadata.preferred_subfamily = get_name_string(&name_data, NAME_ID_PREFERRED_SUBFAMILY);
4072
4073 let mut f_family = None;
4075
4076 let patterns = name_table
4077 .name_records
4078 .iter()
4079 .filter_map(|name_record| {
4080 let name_id = name_record.name_id;
4081 if name_id == FONT_SPECIFIER_FAMILY_ID {
4082 if let Ok(Some(family)) =
4083 fontcode_get_name(&name_data, FONT_SPECIFIER_FAMILY_ID)
4084 {
4085 f_family = Some(family);
4086 }
4087 None
4088 } else if name_id == FONT_SPECIFIER_NAME_ID {
4089 let family = f_family.as_ref()?;
4090 let name = fontcode_get_name(&name_data, FONT_SPECIFIER_NAME_ID).ok()??;
4091 if name.to_bytes().is_empty() {
4092 None
4093 } else {
4094 let mut name_str =
4095 String::from_utf8_lossy(name.to_bytes()).to_string();
4096 let mut family_str =
4097 String::from_utf8_lossy(family.as_bytes()).to_string();
4098 if name_str.starts_with('.') {
4099 name_str = name_str[1..].to_string();
4100 }
4101 if family_str.starts_with('.') {
4102 family_str = family_str[1..].to_string();
4103 }
4104 Some((
4105 FcPattern {
4106 name: Some(name_str),
4107 family: Some(family_str),
4108 bold: if is_bold {
4109 PatternMatch::True
4110 } else {
4111 PatternMatch::False
4112 },
4113 italic: if is_italic {
4114 PatternMatch::True
4115 } else {
4116 PatternMatch::False
4117 },
4118 oblique: if is_oblique {
4119 PatternMatch::True
4120 } else {
4121 PatternMatch::False
4122 },
4123 monospace: if is_monospace {
4124 PatternMatch::True
4125 } else {
4126 PatternMatch::False
4127 },
4128 condensed: if stretch <= FcStretch::Condensed {
4129 PatternMatch::True
4130 } else {
4131 PatternMatch::False
4132 },
4133 weight,
4134 stretch,
4135 unicode_ranges: unicode_ranges.clone(),
4136 metadata: metadata.clone(),
4137 render_config: FcFontRenderConfig::default(),
4138 },
4139 font_index,
4140 ))
4141 }
4142 } else {
4143 None
4144 }
4145 })
4146 .collect::<BTreeSet<_>>();
4147
4148 results.extend(patterns.into_iter().map(|(pat, idx)| ParsedFontFace {
4149 pattern: pat,
4150 font_index: idx,
4151 }));
4152 }
4153
4154 if results.is_empty() {
4155 None
4156 } else {
4157 Some(results)
4158 }
4159}
4160
4161#[cfg(all(feature = "std", feature = "parsing"))]
4163pub(crate) fn FcParseFont(filepath: &PathBuf) -> Option<Vec<(FcPattern, FcFontPath)>> {
4164 #[cfg(all(not(target_family = "wasm"), feature = "std"))]
4165 use mmapio::MmapOptions;
4166 use std::fs::File;
4167
4168 let file = File::open(filepath).ok()?;
4170
4171 #[cfg(all(not(target_family = "wasm"), feature = "std"))]
4172 let font_bytes = unsafe { MmapOptions::new().map(&file).ok()? };
4173
4174 #[cfg(not(all(not(target_family = "wasm"), feature = "std")))]
4175 let font_bytes = std::fs::read(filepath).ok()?;
4176
4177 let faces = parse_font_faces(&font_bytes[..])?;
4178 let path_str = filepath.to_string_lossy().to_string();
4179 let bytes_hash = crate::utils::content_dedup_hash_u64(&font_bytes[..]);
4184
4185 Some(
4186 faces
4187 .into_iter()
4188 .map(|face| {
4189 (
4190 face.pattern,
4191 FcFontPath {
4192 path: path_str.clone(),
4193 font_index: face.font_index,
4194 bytes_hash,
4195 },
4196 )
4197 })
4198 .collect(),
4199 )
4200}
4201
4202#[cfg(all(feature = "std", feature = "parsing"))]
4219#[derive(Debug, Clone)]
4220pub struct FastCoverage {
4221 pub pattern: FcPattern,
4227 pub covered: alloc::collections::BTreeSet<char>,
4232 pub is_bold: bool,
4234 pub is_italic: bool,
4236}
4237
4238#[cfg(all(feature = "std", feature = "parsing"))]
4255#[allow(non_snake_case)]
4256pub fn FcParseFontFaceFast(
4257 font_bytes: &[u8],
4258 font_index: usize,
4259 codepoints: &alloc::collections::BTreeSet<char>,
4260) -> Option<FastCoverage> {
4261 use allsorts::{
4262 binary::read::ReadScope,
4263 font_data::FontData,
4264 tables::{
4265 cmap::{Cmap, CmapSubtable},
4266 FontTableProvider, HeadTable,
4267 },
4268 tag,
4269 };
4270
4271 let scope = ReadScope::new(font_bytes);
4272 let font_file = scope.read::<FontData<'_>>().ok()?;
4273 let provider = font_file.table_provider(font_index).ok()?;
4274
4275 let head_data = provider.table_data(tag::HEAD).ok()??;
4277 let head_table = ReadScope::new(&head_data).read::<HeadTable>().ok()?;
4278 let is_bold = head_table.is_bold();
4279 let is_italic = head_table.is_italic();
4280
4281 let cmap_data = provider.table_data(tag::CMAP).ok()??;
4284 let cmap = ReadScope::new(&cmap_data).read::<Cmap<'_>>().ok()?;
4285 let encoding_record = find_best_cmap_subtable(&cmap)?;
4286 let cmap_subtable = ReadScope::new(&cmap_data)
4287 .offset(encoding_record.offset as usize)
4288 .read::<CmapSubtable<'_>>()
4289 .ok()?;
4290
4291 let mut covered: alloc::collections::BTreeSet<char> =
4292 alloc::collections::BTreeSet::new();
4293 let mut covered_ranges: Vec<UnicodeRange> = Vec::new();
4294 for ch in codepoints {
4295 let cp = *ch as u32;
4296 if let Ok(Some(gid)) = cmap_subtable.map_glyph(cp) {
4297 if gid != 0 {
4298 covered.insert(*ch);
4299 if let Some(last) = covered_ranges.last_mut() {
4303 if cp == last.end + 1 {
4304 last.end = cp;
4305 continue;
4306 }
4307 }
4308 covered_ranges.push(UnicodeRange { start: cp, end: cp });
4309 }
4310 }
4311 }
4312
4313 let weight = if is_bold {
4314 FcWeight::Bold
4315 } else {
4316 FcWeight::Normal
4317 };
4318 let italic_match = if is_italic {
4319 PatternMatch::True
4320 } else {
4321 PatternMatch::False
4322 };
4323
4324 let pattern = FcPattern {
4325 name: None,
4326 family: None,
4327 weight,
4328 italic: italic_match,
4329 oblique: PatternMatch::DontCare,
4330 monospace: PatternMatch::DontCare,
4331 unicode_ranges: covered_ranges,
4332 ..Default::default()
4333 };
4334
4335 Some(FastCoverage {
4336 pattern,
4337 covered,
4338 is_bold,
4339 is_italic,
4340 })
4341}
4342
4343#[cfg(all(feature = "std", feature = "parsing"))]
4348#[allow(non_snake_case)]
4349pub fn FcCountFontFaces(font_bytes: &[u8]) -> usize {
4350 if font_bytes.len() >= 12 && &font_bytes[0..4] == b"ttcf" {
4351 let num_fonts = u32::from_be_bytes([
4352 font_bytes[8], font_bytes[9], font_bytes[10], font_bytes[11],
4353 ]);
4354 std::cmp::min(num_fonts as usize, 100).max(1)
4356 } else {
4357 1
4358 }
4359}
4360
4361#[cfg(all(feature = "std", feature = "parsing"))]
4387#[allow(non_snake_case)]
4388pub fn FcParseFontBytes(font_bytes: &[u8], font_id: &str) -> Option<Vec<(FcPattern, FcFont)>> {
4389 FcParseFontBytesInner(font_bytes, font_id)
4390}
4391
4392#[cfg(all(feature = "std", feature = "parsing"))]
4395fn FcParseFontBytesInner(font_bytes: &[u8], font_id: &str) -> Option<Vec<(FcPattern, FcFont)>> {
4396 let faces = parse_font_faces(font_bytes)?;
4397 let id = font_id.to_string();
4398 let bytes = font_bytes.to_vec();
4399
4400 Some(
4401 faces
4402 .into_iter()
4403 .map(|face| {
4404 (
4405 face.pattern,
4406 FcFont {
4407 bytes: bytes.clone(),
4408 font_index: face.font_index,
4409 id: id.clone(),
4410 },
4411 )
4412 })
4413 .collect(),
4414 )
4415}
4416
4417#[cfg(all(feature = "std", feature = "parsing"))]
4418fn FcScanDirectoriesInner(paths: &[(Option<String>, String)]) -> Vec<(FcPattern, FcFontPath)> {
4419 #[cfg(all(feature = "multithreading", not(target_family = "wasm")))]
4420 {
4421 use rayon::prelude::*;
4422
4423 paths
4425 .par_iter()
4426 .filter_map(|(prefix, p)| {
4427 process_path(prefix, PathBuf::from(p), false).map(FcScanSingleDirectoryRecursive)
4428 })
4429 .flatten()
4430 .collect()
4431 }
4432 #[cfg(not(all(feature = "multithreading", not(target_family = "wasm"))))]
4435 {
4436 paths
4437 .iter()
4438 .filter_map(|(prefix, p)| {
4439 process_path(prefix, PathBuf::from(p), false).map(FcScanSingleDirectoryRecursive)
4440 })
4441 .flatten()
4442 .collect()
4443 }
4444}
4445
4446#[cfg(feature = "std")]
4448fn FcCollectFontFilesRecursive(dir: PathBuf) -> Vec<PathBuf> {
4449 let mut files = Vec::new();
4450 let mut dirs_to_parse = vec![dir];
4451
4452 loop {
4453 let mut new_dirs = Vec::new();
4454 for dir in &dirs_to_parse {
4455 let entries = match std::fs::read_dir(dir) {
4456 Ok(o) => o,
4457 Err(_) => continue,
4458 };
4459 for entry in entries.flatten() {
4460 let path = entry.path();
4461 if path.is_dir() {
4462 new_dirs.push(path);
4463 } else {
4464 files.push(path);
4465 }
4466 }
4467 }
4468 if new_dirs.is_empty() {
4469 break;
4470 }
4471 dirs_to_parse = new_dirs;
4472 }
4473
4474 files
4475}
4476
4477#[cfg(all(feature = "std", feature = "parsing"))]
4478fn FcScanSingleDirectoryRecursive(dir: PathBuf) -> Vec<(FcPattern, FcFontPath)> {
4479 let files = FcCollectFontFilesRecursive(dir);
4480 FcParseFontFiles(&files)
4481}
4482
4483#[cfg(all(feature = "std", feature = "parsing"))]
4484fn FcParseFontFiles(files_to_parse: &[PathBuf]) -> Vec<(FcPattern, FcFontPath)> {
4485 let result = {
4486 #[cfg(all(feature = "multithreading", not(target_family = "wasm")))]
4487 {
4488 use rayon::prelude::*;
4489
4490 files_to_parse
4491 .par_iter()
4492 .filter_map(|file| FcParseFont(file))
4493 .collect::<Vec<Vec<_>>>()
4494 }
4495 #[cfg(not(all(feature = "multithreading", not(target_family = "wasm"))))]
4496 {
4497 files_to_parse
4498 .iter()
4499 .filter_map(|file| FcParseFont(file))
4500 .collect::<Vec<Vec<_>>>()
4501 }
4502 };
4503
4504 result.into_iter().flat_map(|f| f.into_iter()).collect()
4505}
4506
4507#[cfg(all(feature = "std", feature = "parsing"))]
4508fn process_path(
4512 prefix: &Option<String>,
4513 mut path: PathBuf,
4514 is_include_path: bool,
4515) -> Option<PathBuf> {
4516 use std::env::var;
4517
4518 const HOME_SHORTCUT: &str = "~";
4519 const CWD_PATH: &str = ".";
4520
4521 const HOME_ENV_VAR: &str = "HOME";
4522 const XDG_CONFIG_HOME_ENV_VAR: &str = "XDG_CONFIG_HOME";
4523 const XDG_CONFIG_HOME_DEFAULT_PATH_SUFFIX: &str = ".config";
4524 const XDG_DATA_HOME_ENV_VAR: &str = "XDG_DATA_HOME";
4525 const XDG_DATA_HOME_DEFAULT_PATH_SUFFIX: &str = ".local/share";
4526
4527 const PREFIX_CWD: &str = "cwd";
4528 const PREFIX_DEFAULT: &str = "default";
4529 const PREFIX_XDG: &str = "xdg";
4530
4531 fn get_home_value() -> Option<PathBuf> {
4533 var(HOME_ENV_VAR).ok().map(PathBuf::from)
4534 }
4535 fn get_xdg_config_home_value() -> Option<PathBuf> {
4536 var(XDG_CONFIG_HOME_ENV_VAR)
4537 .ok()
4538 .map(PathBuf::from)
4539 .or_else(|| {
4540 get_home_value()
4541 .map(|home_path| home_path.join(XDG_CONFIG_HOME_DEFAULT_PATH_SUFFIX))
4542 })
4543 }
4544 fn get_xdg_data_home_value() -> Option<PathBuf> {
4545 var(XDG_DATA_HOME_ENV_VAR)
4546 .ok()
4547 .map(PathBuf::from)
4548 .or_else(|| {
4549 get_home_value().map(|home_path| home_path.join(XDG_DATA_HOME_DEFAULT_PATH_SUFFIX))
4550 })
4551 }
4552
4553 if path.starts_with(HOME_SHORTCUT) {
4555 if let Some(home_path) = get_home_value() {
4556 path = home_path.join(
4557 path.strip_prefix(HOME_SHORTCUT)
4558 .expect("already checked that it starts with the prefix"),
4559 );
4560 } else {
4561 return None;
4562 }
4563 }
4564
4565 match prefix {
4567 Some(prefix) => match prefix.as_str() {
4568 PREFIX_CWD | PREFIX_DEFAULT => {
4569 let mut new_path = PathBuf::from(CWD_PATH);
4570 new_path.push(path);
4571
4572 Some(new_path)
4573 }
4574 PREFIX_XDG => {
4575 if is_include_path {
4576 get_xdg_config_home_value()
4577 .map(|xdg_config_home_path| xdg_config_home_path.join(path))
4578 } else {
4579 get_xdg_data_home_value()
4580 .map(|xdg_data_home_path| xdg_data_home_path.join(path))
4581 }
4582 }
4583 _ => None, },
4585 None => Some(path),
4586 }
4587}
4588
4589#[cfg(all(feature = "std", feature = "parsing"))]
4591fn get_name_string(name_data: &[u8], name_id: u16) -> Option<String> {
4592 fontcode_get_name(name_data, name_id)
4593 .ok()
4594 .flatten()
4595 .map(|name| String::from_utf8_lossy(name.to_bytes()).to_string())
4596}
4597
4598#[cfg(all(feature = "std", feature = "parsing"))]
4602fn get_verification_codepoints(start: u32, end: u32) -> Vec<u32> {
4603 match start {
4604 0x0000 => vec!['A' as u32, 'M' as u32, 'Z' as u32, 'a' as u32, 'm' as u32, 'z' as u32],
4606 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], _ => {
4682 let range_size = end - start;
4683 if range_size > 20 {
4684 vec![
4685 start + range_size / 5,
4686 start + 2 * range_size / 5,
4687 start + 3 * range_size / 5,
4688 start + 4 * range_size / 5,
4689 ]
4690 } else {
4691 vec![start, start + range_size / 2]
4692 }
4693 }
4694 }
4695}
4696
4697#[cfg(all(feature = "std", feature = "parsing"))]
4700fn find_best_cmap_subtable<'a>(
4701 cmap: &allsorts::tables::cmap::Cmap<'a>,
4702) -> Option<allsorts::tables::cmap::EncodingRecord> {
4703 use allsorts::tables::cmap::{PlatformId, EncodingId};
4704
4705 cmap.find_subtable(PlatformId::UNICODE, EncodingId(3))
4706 .or_else(|| cmap.find_subtable(PlatformId::UNICODE, EncodingId(4)))
4707 .or_else(|| cmap.find_subtable(PlatformId::WINDOWS, EncodingId(1)))
4708 .or_else(|| cmap.find_subtable(PlatformId::WINDOWS, EncodingId(10)))
4709 .or_else(|| cmap.find_subtable(PlatformId::UNICODE, EncodingId(0)))
4710 .or_else(|| cmap.find_subtable(PlatformId::UNICODE, EncodingId(1)))
4711}
4712
4713#[cfg(all(feature = "std", feature = "parsing"))]
4716fn verify_unicode_ranges_with_cmap(
4717 provider: &impl FontTableProvider,
4718 os2_ranges: Vec<UnicodeRange>
4719) -> Vec<UnicodeRange> {
4720 use allsorts::tables::cmap::{Cmap, CmapSubtable};
4721
4722 if os2_ranges.is_empty() {
4723 return Vec::new();
4724 }
4725
4726 let cmap_data = match provider.table_data(tag::CMAP) {
4728 Ok(Some(data)) => data,
4729 _ => return os2_ranges, };
4731
4732 let cmap = match ReadScope::new(&cmap_data).read::<Cmap<'_>>() {
4733 Ok(c) => c,
4734 Err(_) => return os2_ranges,
4735 };
4736
4737 let encoding_record = match find_best_cmap_subtable(&cmap) {
4738 Some(r) => r,
4739 None => return os2_ranges, };
4741
4742 let cmap_subtable = match ReadScope::new(&cmap_data)
4743 .offset(encoding_record.offset as usize)
4744 .read::<CmapSubtable<'_>>()
4745 {
4746 Ok(st) => st,
4747 Err(_) => return os2_ranges,
4748 };
4749
4750 let mut verified_ranges = Vec::new();
4752
4753 for range in os2_ranges {
4754 let test_codepoints = get_verification_codepoints(range.start, range.end);
4755
4756 let required_hits = (test_codepoints.len() + 1) / 2; let mut hits = 0;
4760
4761 for cp in test_codepoints {
4762 if cp >= range.start && cp <= range.end {
4763 if let Ok(Some(gid)) = cmap_subtable.map_glyph(cp) {
4764 if gid != 0 {
4765 hits += 1;
4766 if hits >= required_hits {
4767 break;
4768 }
4769 }
4770 }
4771 }
4772 }
4773
4774 if hits >= required_hits {
4775 verified_ranges.push(range);
4776 }
4777 }
4778
4779 verified_ranges
4780}
4781
4782#[cfg(all(feature = "std", feature = "parsing"))]
4785fn analyze_cmap_coverage(provider: &impl FontTableProvider) -> Option<Vec<UnicodeRange>> {
4786 use allsorts::tables::cmap::{Cmap, CmapSubtable};
4787
4788 let cmap_data = provider.table_data(tag::CMAP).ok()??;
4789 let cmap = ReadScope::new(&cmap_data).read::<Cmap<'_>>().ok()?;
4790
4791 let encoding_record = find_best_cmap_subtable(&cmap)?;
4792
4793 let cmap_subtable = ReadScope::new(&cmap_data)
4794 .offset(encoding_record.offset as usize)
4795 .read::<CmapSubtable<'_>>()
4796 .ok()?;
4797
4798 let blocks_to_check: &[(u32, u32)] = &[
4800 (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), ];
4851
4852 let mut ranges = Vec::new();
4853
4854 for &(start, end) in blocks_to_check {
4855 let test_codepoints = get_verification_codepoints(start, end);
4856 let required_hits = (test_codepoints.len() + 1) / 2;
4857 let mut hits = 0;
4858
4859 for cp in test_codepoints {
4860 if let Ok(Some(gid)) = cmap_subtable.map_glyph(cp) {
4861 if gid != 0 {
4862 hits += 1;
4863 if hits >= required_hits {
4864 break;
4865 }
4866 }
4867 }
4868 }
4869
4870 if hits >= required_hits {
4871 ranges.push(UnicodeRange { start, end });
4872 }
4873 }
4874
4875 if ranges.is_empty() {
4876 None
4877 } else {
4878 Some(ranges)
4879 }
4880}
4881
4882#[cfg(all(feature = "std", feature = "parsing"))]
4884#[allow(dead_code)]
4885fn extract_unicode_ranges(os2_table: &Os2) -> Vec<UnicodeRange> {
4886 let mut unicode_ranges = Vec::new();
4887
4888 let ranges = [
4889 os2_table.ul_unicode_range1,
4890 os2_table.ul_unicode_range2,
4891 os2_table.ul_unicode_range3,
4892 os2_table.ul_unicode_range4,
4893 ];
4894
4895 for &(bit, start, end) in UNICODE_RANGE_MAPPINGS {
4896 let range_idx = bit / 32;
4897 let bit_pos = bit % 32;
4898 if range_idx < 4 && (ranges[range_idx] & (1 << bit_pos)) != 0 {
4899 unicode_ranges.push(UnicodeRange { start, end });
4900 }
4901 }
4902
4903 unicode_ranges
4904}
4905
4906#[cfg(all(feature = "std", feature = "parsing"))]
4908fn detect_monospace(
4909 provider: &impl FontTableProvider,
4910 os2_table: &Os2,
4911 detected_monospace: Option<bool>,
4912) -> Option<bool> {
4913 if let Some(is_monospace) = detected_monospace {
4914 return Some(is_monospace);
4915 }
4916
4917 if os2_table.panose[0] == 2 {
4919 return Some(os2_table.panose[3] == 9); }
4922
4923 let hhea_data = provider.table_data(tag::HHEA).ok()??;
4925 let hhea_table = ReadScope::new(&hhea_data).read::<HheaTable>().ok()?;
4926 let maxp_data = provider.table_data(tag::MAXP).ok()??;
4927 let maxp_table = ReadScope::new(&maxp_data).read::<MaxpTable>().ok()?;
4928 let hmtx_data = provider.table_data(tag::HMTX).ok()??;
4929 let hmtx_table = ReadScope::new(&hmtx_data)
4930 .read_dep::<HmtxTable<'_>>((
4931 usize::from(maxp_table.num_glyphs),
4932 usize::from(hhea_table.num_h_metrics),
4933 ))
4934 .ok()?;
4935
4936 let mut monospace = true;
4937 let mut last_advance = 0;
4938
4939 for i in 0..hhea_table.num_h_metrics as usize {
4941 let advance = hmtx_table.h_metrics.read_item(i).ok()?.advance_width;
4942 if i > 0 && advance != last_advance {
4943 monospace = false;
4944 break;
4945 }
4946 last_advance = advance;
4947 }
4948
4949 Some(monospace)
4950}
4951
4952#[cfg(all(feature = "std", not(feature = "parsing")))]
4961fn pattern_from_filename(path: &std::path::Path) -> Option<FcPattern> {
4962 let ext = path.extension()?.to_str()?.to_ascii_lowercase();
4963 match ext.as_str() {
4964 "ttf" | "otf" | "ttc" | "woff" | "woff2" => {}
4965 _ => return None,
4966 }
4967
4968 let stem = path.file_stem()?.to_str()?;
4969 let all_tokens = crate::config::tokenize_lowercase(stem);
4970
4971 let has_token = |kw: &str| all_tokens.iter().any(|t| t == kw);
4973 let is_bold = has_token("bold") || has_token("heavy");
4974 let is_italic = has_token("italic");
4975 let is_oblique = has_token("oblique");
4976 let is_mono = has_token("mono") || has_token("monospace");
4977 let is_condensed = has_token("condensed");
4978
4979 let family_tokens = crate::config::tokenize_font_stem(stem);
4981 if family_tokens.is_empty() { return None; }
4982 let family = family_tokens.join(" ");
4983
4984 Some(FcPattern {
4985 name: Some(stem.to_string()),
4986 family: Some(family),
4987 bold: if is_bold { PatternMatch::True } else { PatternMatch::False },
4988 italic: if is_italic { PatternMatch::True } else { PatternMatch::False },
4989 oblique: if is_oblique { PatternMatch::True } else { PatternMatch::DontCare },
4990 monospace: if is_mono { PatternMatch::True } else { PatternMatch::DontCare },
4991 condensed: if is_condensed { PatternMatch::True } else { PatternMatch::DontCare },
4992 weight: if is_bold { FcWeight::Bold } else { FcWeight::Normal },
4993 stretch: if is_condensed { FcStretch::Condensed } else { FcStretch::Normal },
4994 unicode_ranges: Vec::new(),
4995 metadata: FcFontMetadata::default(),
4996 render_config: FcFontRenderConfig::default(),
4997 })
4998}