1#![allow(non_snake_case)]
65#![cfg_attr(not(feature = "std"), no_std)]
66
67extern crate alloc;
68
69#[cfg(all(feature = "std", feature = "parsing"))]
70use alloc::borrow::ToOwned;
71use alloc::collections::btree_map::BTreeMap;
72use alloc::string::{String, ToString};
73use alloc::vec::Vec;
74use alloc::{format, vec};
75#[cfg(feature = "parsing")]
76use allsorts::binary::read::ReadScope;
77#[cfg(all(feature = "std", feature = "parsing"))]
78use allsorts::get_name::fontcode_get_name;
79#[cfg(feature = "parsing")]
80use allsorts::tables::os2::Os2;
81#[cfg(feature = "parsing")]
82use allsorts::tables::{FontTableProvider, HheaTable, HmtxTable, MaxpTable};
83#[cfg(feature = "parsing")]
84use allsorts::tag;
85#[cfg(all(feature = "std", feature = "parsing"))]
86use std::path::PathBuf;
87
88#[cfg(feature = "ffi")]
89pub mod ffi;
90
91#[cfg(feature = "async-registry")]
92pub mod registry;
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
96pub enum OperatingSystem {
97 Windows,
98 Linux,
99 MacOS,
100 Wasm,
101}
102
103impl OperatingSystem {
104 pub fn current() -> Self {
106 #[cfg(target_os = "windows")]
107 return OperatingSystem::Windows;
108
109 #[cfg(target_os = "linux")]
110 return OperatingSystem::Linux;
111
112 #[cfg(target_os = "macos")]
113 return OperatingSystem::MacOS;
114
115 #[cfg(target_family = "wasm")]
116 return OperatingSystem::Wasm;
117
118 #[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos", target_family = "wasm")))]
119 return OperatingSystem::Linux; }
121
122 pub fn get_serif_fonts(&self, unicode_ranges: &[UnicodeRange]) -> Vec<String> {
125 let has_cjk = unicode_ranges.iter().any(|r| {
126 (r.start >= 0x4E00 && r.start <= 0x9FFF) || (r.start >= 0x3040 && r.start <= 0x309F) || (r.start >= 0x30A0 && r.start <= 0x30FF) || (r.start >= 0xAC00 && r.start <= 0xD7AF) });
131
132 let has_arabic = unicode_ranges.iter().any(|r| r.start >= 0x0600 && r.start <= 0x06FF);
133 let _has_cyrillic = unicode_ranges.iter().any(|r| r.start >= 0x0400 && r.start <= 0x04FF);
134
135 match self {
136 OperatingSystem::Windows => {
137 let mut fonts = Vec::new();
138 if has_cjk {
139 fonts.extend_from_slice(&["MS Mincho", "SimSun", "MingLiU"]);
140 }
141 if has_arabic {
142 fonts.push("Traditional Arabic");
143 }
144 fonts.push("Times New Roman");
145 fonts.iter().map(|s| s.to_string()).collect()
146 }
147 OperatingSystem::Linux => {
148 let mut fonts = Vec::new();
149 if has_cjk {
150 fonts.extend_from_slice(&["Noto Serif CJK SC", "Noto Serif CJK JP", "Noto Serif CJK KR"]);
151 }
152 if has_arabic {
153 fonts.push("Noto Serif Arabic");
154 }
155 fonts.extend_from_slice(&[
156 "Times", "Times New Roman", "DejaVu Serif", "Free Serif",
157 "Noto Serif", "Bitstream Vera Serif", "Roman", "Regular"
158 ]);
159 fonts.iter().map(|s| s.to_string()).collect()
160 }
161 OperatingSystem::MacOS => {
162 let mut fonts = Vec::new();
163 if has_cjk {
164 fonts.extend_from_slice(&["Hiragino Mincho ProN", "STSong", "AppleMyungjo"]);
165 }
166 if has_arabic {
167 fonts.push("Geeza Pro");
168 }
169 fonts.extend_from_slice(&["Times", "New York", "Palatino"]);
170 fonts.iter().map(|s| s.to_string()).collect()
171 }
172 OperatingSystem::Wasm => Vec::new(),
173 }
174 }
175
176 pub fn get_sans_serif_fonts(&self, unicode_ranges: &[UnicodeRange]) -> Vec<String> {
179 let has_cjk = unicode_ranges.iter().any(|r| {
180 (r.start >= 0x4E00 && r.start <= 0x9FFF) || (r.start >= 0x3040 && r.start <= 0x309F) || (r.start >= 0x30A0 && r.start <= 0x30FF) || (r.start >= 0xAC00 && r.start <= 0xD7AF) });
185
186 let has_arabic = unicode_ranges.iter().any(|r| r.start >= 0x0600 && r.start <= 0x06FF);
187 let _has_cyrillic = unicode_ranges.iter().any(|r| r.start >= 0x0400 && r.start <= 0x04FF);
188 let has_hebrew = unicode_ranges.iter().any(|r| r.start >= 0x0590 && r.start <= 0x05FF);
189 let has_thai = unicode_ranges.iter().any(|r| r.start >= 0x0E00 && r.start <= 0x0E7F);
190
191 match self {
192 OperatingSystem::Windows => {
193 let mut fonts = Vec::new();
194 if has_cjk {
195 fonts.extend_from_slice(&["Microsoft YaHei", "MS Gothic", "Malgun Gothic", "SimHei"]);
196 }
197 if has_arabic {
198 fonts.push("Segoe UI Arabic");
199 }
200 if has_hebrew {
201 fonts.push("Segoe UI Hebrew");
202 }
203 if has_thai {
204 fonts.push("Leelawadee UI");
205 }
206 fonts.extend_from_slice(&["Segoe UI", "Tahoma", "Microsoft Sans Serif", "MS Sans Serif", "Helv"]);
207 fonts.iter().map(|s| s.to_string()).collect()
208 }
209 OperatingSystem::Linux => {
210 let mut fonts = Vec::new();
211 if has_cjk {
212 fonts.extend_from_slice(&[
213 "Noto Sans CJK SC", "Noto Sans CJK JP", "Noto Sans CJK KR",
214 "WenQuanYi Micro Hei", "Droid Sans Fallback"
215 ]);
216 }
217 if has_arabic {
218 fonts.push("Noto Sans Arabic");
219 }
220 if has_hebrew {
221 fonts.push("Noto Sans Hebrew");
222 }
223 if has_thai {
224 fonts.push("Noto Sans Thai");
225 }
226 fonts.extend_from_slice(&["Ubuntu", "Arial", "DejaVu Sans", "Noto Sans", "Liberation Sans"]);
227 fonts.iter().map(|s| s.to_string()).collect()
228 }
229 OperatingSystem::MacOS => {
230 let mut fonts = Vec::new();
231 if has_cjk {
232 fonts.extend_from_slice(&[
233 "Hiragino Sans", "Hiragino Kaku Gothic ProN",
234 "PingFang SC", "PingFang TC", "Apple SD Gothic Neo"
235 ]);
236 }
237 if has_arabic {
238 fonts.push("Geeza Pro");
239 }
240 if has_hebrew {
241 fonts.push("Arial Hebrew");
242 }
243 if has_thai {
244 fonts.push("Thonburi");
245 }
246 fonts.extend_from_slice(&["San Francisco", "Helvetica Neue", "Lucida Grande"]);
247 fonts.iter().map(|s| s.to_string()).collect()
248 }
249 OperatingSystem::Wasm => Vec::new(),
250 }
251 }
252
253 pub fn get_monospace_fonts(&self, unicode_ranges: &[UnicodeRange]) -> Vec<String> {
256 let has_cjk = unicode_ranges.iter().any(|r| {
257 (r.start >= 0x4E00 && r.start <= 0x9FFF) || (r.start >= 0x3040 && r.start <= 0x309F) || (r.start >= 0x30A0 && r.start <= 0x30FF) || (r.start >= 0xAC00 && r.start <= 0xD7AF) });
262
263 match self {
264 OperatingSystem::Windows => {
265 let mut fonts = Vec::new();
266 if has_cjk {
267 fonts.extend_from_slice(&["MS Gothic", "SimHei"]);
268 }
269 fonts.extend_from_slice(&["Segoe UI Mono", "Courier New", "Cascadia Code", "Cascadia Mono", "Consolas"]);
270 fonts.iter().map(|s| s.to_string()).collect()
271 }
272 OperatingSystem::Linux => {
273 let mut fonts = Vec::new();
274 if has_cjk {
275 fonts.extend_from_slice(&["Noto Sans Mono CJK SC", "Noto Sans Mono CJK JP", "WenQuanYi Zen Hei Mono"]);
276 }
277 fonts.extend_from_slice(&[
278 "Source Code Pro", "Cantarell", "DejaVu Sans Mono",
279 "Roboto Mono", "Ubuntu Monospace", "Droid Sans Mono"
280 ]);
281 fonts.iter().map(|s| s.to_string()).collect()
282 }
283 OperatingSystem::MacOS => {
284 let mut fonts = Vec::new();
285 if has_cjk {
286 fonts.extend_from_slice(&["Hiragino Sans", "PingFang SC"]);
287 }
288 fonts.extend_from_slice(&["SF Mono", "Menlo", "Monaco", "Courier", "Oxygen Mono", "Source Code Pro", "Fira Mono"]);
289 fonts.iter().map(|s| s.to_string()).collect()
290 }
291 OperatingSystem::Wasm => Vec::new(),
292 }
293 }
294
295 pub fn expand_generic_family(&self, family: &str, unicode_ranges: &[UnicodeRange]) -> Vec<String> {
299 match family.to_lowercase().as_str() {
300 "serif" => self.get_serif_fonts(unicode_ranges),
301 "sans-serif" => self.get_sans_serif_fonts(unicode_ranges),
302 "monospace" => self.get_monospace_fonts(unicode_ranges),
303 "cursive" | "fantasy" | "system-ui" => {
304 self.get_sans_serif_fonts(unicode_ranges)
306 }
307 _ => vec![family.to_string()],
308 }
309 }
310}
311
312pub fn expand_font_families(families: &[String], os: OperatingSystem, unicode_ranges: &[UnicodeRange]) -> Vec<String> {
316 let mut expanded = Vec::new();
317
318 for family in families {
319 expanded.extend(os.expand_generic_family(family, unicode_ranges));
320 }
321
322 expanded
323}
324
325#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
327#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
328pub struct FontId(pub u128);
329
330impl core::fmt::Debug for FontId {
331 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
332 core::fmt::Display::fmt(self, f)
333 }
334}
335
336impl core::fmt::Display for FontId {
337 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
338 let id = self.0;
339 write!(
340 f,
341 "{:08x}-{:04x}-{:04x}-{:04x}-{:012x}",
342 (id >> 96) & 0xFFFFFFFF,
343 (id >> 80) & 0xFFFF,
344 (id >> 64) & 0xFFFF,
345 (id >> 48) & 0xFFFF,
346 id & 0xFFFFFFFFFFFF
347 )
348 }
349}
350
351impl FontId {
352 pub fn new() -> Self {
354 use core::sync::atomic::{AtomicU64, Ordering};
355 static COUNTER: AtomicU64 = AtomicU64::new(1);
356 let id = COUNTER.fetch_add(1, Ordering::Relaxed) as u128;
357 FontId(id)
358 }
359}
360
361#[derive(Debug, Default, Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
363#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
364#[repr(C)]
365pub enum PatternMatch {
366 #[default]
368 DontCare,
369 True,
371 False,
373}
374
375impl PatternMatch {
376 fn needs_to_match(&self) -> bool {
377 matches!(self, PatternMatch::True | PatternMatch::False)
378 }
379
380 fn matches(&self, other: &PatternMatch) -> bool {
381 match (self, other) {
382 (PatternMatch::DontCare, _) => true,
383 (_, PatternMatch::DontCare) => true,
384 (a, b) => a == b,
385 }
386 }
387}
388
389#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
391#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
392#[repr(C)]
393pub enum FcWeight {
394 Thin = 100,
395 ExtraLight = 200,
396 Light = 300,
397 Normal = 400,
398 Medium = 500,
399 SemiBold = 600,
400 Bold = 700,
401 ExtraBold = 800,
402 Black = 900,
403}
404
405impl FcWeight {
406 pub fn from_u16(weight: u16) -> Self {
407 match weight {
408 0..=149 => FcWeight::Thin,
409 150..=249 => FcWeight::ExtraLight,
410 250..=349 => FcWeight::Light,
411 350..=449 => FcWeight::Normal,
412 450..=549 => FcWeight::Medium,
413 550..=649 => FcWeight::SemiBold,
414 650..=749 => FcWeight::Bold,
415 750..=849 => FcWeight::ExtraBold,
416 _ => FcWeight::Black,
417 }
418 }
419
420 pub fn find_best_match(&self, available: &[FcWeight]) -> Option<FcWeight> {
421 if available.is_empty() {
422 return None;
423 }
424
425 if available.contains(self) {
427 return Some(*self);
428 }
429
430 let self_value = *self as u16;
432
433 match *self {
434 FcWeight::Normal => {
435 if available.contains(&FcWeight::Medium) {
437 return Some(FcWeight::Medium);
438 }
439 for weight in &[FcWeight::Light, FcWeight::ExtraLight, FcWeight::Thin] {
441 if available.contains(weight) {
442 return Some(*weight);
443 }
444 }
445 for weight in &[
447 FcWeight::SemiBold,
448 FcWeight::Bold,
449 FcWeight::ExtraBold,
450 FcWeight::Black,
451 ] {
452 if available.contains(weight) {
453 return Some(*weight);
454 }
455 }
456 }
457 FcWeight::Medium => {
458 if available.contains(&FcWeight::Normal) {
460 return Some(FcWeight::Normal);
461 }
462 for weight in &[FcWeight::Light, FcWeight::ExtraLight, FcWeight::Thin] {
464 if available.contains(weight) {
465 return Some(*weight);
466 }
467 }
468 for weight in &[
470 FcWeight::SemiBold,
471 FcWeight::Bold,
472 FcWeight::ExtraBold,
473 FcWeight::Black,
474 ] {
475 if available.contains(weight) {
476 return Some(*weight);
477 }
478 }
479 }
480 FcWeight::Thin | FcWeight::ExtraLight | FcWeight::Light => {
481 let mut best_match = None;
483 let mut smallest_diff = u16::MAX;
484
485 for weight in available {
487 let weight_value = *weight as u16;
488 if weight_value <= self_value {
490 let diff = self_value - weight_value;
491 if diff < smallest_diff {
492 smallest_diff = diff;
493 best_match = Some(*weight);
494 }
495 }
496 }
497
498 if best_match.is_some() {
499 return best_match;
500 }
501
502 best_match = None;
504 smallest_diff = u16::MAX;
505
506 for weight in available {
507 let weight_value = *weight as u16;
508 if weight_value > self_value {
509 let diff = weight_value - self_value;
510 if diff < smallest_diff {
511 smallest_diff = diff;
512 best_match = Some(*weight);
513 }
514 }
515 }
516
517 return best_match;
518 }
519 FcWeight::SemiBold | FcWeight::Bold | FcWeight::ExtraBold | FcWeight::Black => {
520 let mut best_match = None;
522 let mut smallest_diff = u16::MAX;
523
524 for weight in available {
526 let weight_value = *weight as u16;
527 if weight_value >= self_value {
529 let diff = weight_value - self_value;
530 if diff < smallest_diff {
531 smallest_diff = diff;
532 best_match = Some(*weight);
533 }
534 }
535 }
536
537 if best_match.is_some() {
538 return best_match;
539 }
540
541 best_match = None;
543 smallest_diff = u16::MAX;
544
545 for weight in available {
546 let weight_value = *weight as u16;
547 if weight_value < self_value {
548 let diff = self_value - weight_value;
549 if diff < smallest_diff {
550 smallest_diff = diff;
551 best_match = Some(*weight);
552 }
553 }
554 }
555
556 return best_match;
557 }
558 }
559
560 Some(available[0])
562 }
563}
564
565impl Default for FcWeight {
566 fn default() -> Self {
567 FcWeight::Normal
568 }
569}
570
571#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
573#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
574#[repr(C)]
575pub enum FcStretch {
576 UltraCondensed = 1,
577 ExtraCondensed = 2,
578 Condensed = 3,
579 SemiCondensed = 4,
580 Normal = 5,
581 SemiExpanded = 6,
582 Expanded = 7,
583 ExtraExpanded = 8,
584 UltraExpanded = 9,
585}
586
587impl FcStretch {
588 pub fn is_condensed(&self) -> bool {
589 use self::FcStretch::*;
590 match self {
591 UltraCondensed => true,
592 ExtraCondensed => true,
593 Condensed => true,
594 SemiCondensed => true,
595 Normal => false,
596 SemiExpanded => false,
597 Expanded => false,
598 ExtraExpanded => false,
599 UltraExpanded => false,
600 }
601 }
602 pub fn from_u16(width_class: u16) -> Self {
603 match width_class {
604 1 => FcStretch::UltraCondensed,
605 2 => FcStretch::ExtraCondensed,
606 3 => FcStretch::Condensed,
607 4 => FcStretch::SemiCondensed,
608 5 => FcStretch::Normal,
609 6 => FcStretch::SemiExpanded,
610 7 => FcStretch::Expanded,
611 8 => FcStretch::ExtraExpanded,
612 9 => FcStretch::UltraExpanded,
613 _ => FcStretch::Normal,
614 }
615 }
616
617 pub fn find_best_match(&self, available: &[FcStretch]) -> Option<FcStretch> {
619 if available.is_empty() {
620 return None;
621 }
622
623 if available.contains(self) {
624 return Some(*self);
625 }
626
627 if *self <= FcStretch::Normal {
629 let mut closest_narrower = None;
631 for stretch in available.iter() {
632 if *stretch < *self
633 && (closest_narrower.is_none() || *stretch > closest_narrower.unwrap())
634 {
635 closest_narrower = Some(*stretch);
636 }
637 }
638
639 if closest_narrower.is_some() {
640 return closest_narrower;
641 }
642
643 let mut closest_wider = None;
645 for stretch in available.iter() {
646 if *stretch > *self
647 && (closest_wider.is_none() || *stretch < closest_wider.unwrap())
648 {
649 closest_wider = Some(*stretch);
650 }
651 }
652
653 return closest_wider;
654 } else {
655 let mut closest_wider = None;
657 for stretch in available.iter() {
658 if *stretch > *self
659 && (closest_wider.is_none() || *stretch < closest_wider.unwrap())
660 {
661 closest_wider = Some(*stretch);
662 }
663 }
664
665 if closest_wider.is_some() {
666 return closest_wider;
667 }
668
669 let mut closest_narrower = None;
671 for stretch in available.iter() {
672 if *stretch < *self
673 && (closest_narrower.is_none() || *stretch > closest_narrower.unwrap())
674 {
675 closest_narrower = Some(*stretch);
676 }
677 }
678
679 return closest_narrower;
680 }
681 }
682}
683
684impl Default for FcStretch {
685 fn default() -> Self {
686 FcStretch::Normal
687 }
688}
689
690#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
692#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
693pub struct UnicodeRange {
694 pub start: u32,
695 pub end: u32,
696}
697
698impl UnicodeRange {
699 pub fn contains(&self, c: char) -> bool {
700 let c = c as u32;
701 c >= self.start && c <= self.end
702 }
703
704 pub fn overlaps(&self, other: &UnicodeRange) -> bool {
705 self.start <= other.end && other.start <= self.end
706 }
707
708 pub fn is_subset_of(&self, other: &UnicodeRange) -> bool {
709 self.start >= other.start && self.end <= other.end
710 }
711}
712
713#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
715pub enum TraceLevel {
716 Debug,
717 Info,
718 Warning,
719 Error,
720}
721
722#[derive(Debug, Clone, PartialEq, Eq, Hash)]
724pub enum MatchReason {
725 NameMismatch {
726 requested: Option<String>,
727 found: Option<String>,
728 },
729 FamilyMismatch {
730 requested: Option<String>,
731 found: Option<String>,
732 },
733 StyleMismatch {
734 property: &'static str,
735 requested: String,
736 found: String,
737 },
738 WeightMismatch {
739 requested: FcWeight,
740 found: FcWeight,
741 },
742 StretchMismatch {
743 requested: FcStretch,
744 found: FcStretch,
745 },
746 UnicodeRangeMismatch {
747 character: char,
748 ranges: Vec<UnicodeRange>,
749 },
750 Success,
751}
752
753#[derive(Debug, Clone, PartialEq, Eq)]
755pub struct TraceMsg {
756 pub level: TraceLevel,
757 pub path: String,
758 pub reason: MatchReason,
759}
760
761#[derive(Default, Clone, PartialOrd, Ord, PartialEq, Eq)]
763#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
764#[repr(C)]
765pub struct FcPattern {
766 pub name: Option<String>,
768 pub family: Option<String>,
770 pub italic: PatternMatch,
772 pub oblique: PatternMatch,
774 pub bold: PatternMatch,
776 pub monospace: PatternMatch,
778 pub condensed: PatternMatch,
780 pub weight: FcWeight,
782 pub stretch: FcStretch,
784 pub unicode_ranges: Vec<UnicodeRange>,
786 pub metadata: FcFontMetadata,
788}
789
790impl core::fmt::Debug for FcPattern {
791 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
792 let mut d = f.debug_struct("FcPattern");
793
794 if let Some(name) = &self.name {
795 d.field("name", name);
796 }
797
798 if let Some(family) = &self.family {
799 d.field("family", family);
800 }
801
802 if self.italic != PatternMatch::DontCare {
803 d.field("italic", &self.italic);
804 }
805
806 if self.oblique != PatternMatch::DontCare {
807 d.field("oblique", &self.oblique);
808 }
809
810 if self.bold != PatternMatch::DontCare {
811 d.field("bold", &self.bold);
812 }
813
814 if self.monospace != PatternMatch::DontCare {
815 d.field("monospace", &self.monospace);
816 }
817
818 if self.condensed != PatternMatch::DontCare {
819 d.field("condensed", &self.condensed);
820 }
821
822 if self.weight != FcWeight::Normal {
823 d.field("weight", &self.weight);
824 }
825
826 if self.stretch != FcStretch::Normal {
827 d.field("stretch", &self.stretch);
828 }
829
830 if !self.unicode_ranges.is_empty() {
831 d.field("unicode_ranges", &self.unicode_ranges);
832 }
833
834 let empty_metadata = FcFontMetadata::default();
836 if self.metadata != empty_metadata {
837 d.field("metadata", &self.metadata);
838 }
839
840 d.finish()
841 }
842}
843
844#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]
846#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
847pub struct FcFontMetadata {
848 pub copyright: Option<String>,
849 pub designer: Option<String>,
850 pub designer_url: Option<String>,
851 pub font_family: Option<String>,
852 pub font_subfamily: Option<String>,
853 pub full_name: Option<String>,
854 pub id_description: Option<String>,
855 pub license: Option<String>,
856 pub license_url: Option<String>,
857 pub manufacturer: Option<String>,
858 pub manufacturer_url: Option<String>,
859 pub postscript_name: Option<String>,
860 pub preferred_family: Option<String>,
861 pub preferred_subfamily: Option<String>,
862 pub trademark: Option<String>,
863 pub unique_id: Option<String>,
864 pub version: Option<String>,
865}
866
867impl FcPattern {
868 pub fn contains_char(&self, c: char) -> bool {
870 if self.unicode_ranges.is_empty() {
871 return true; }
873
874 for range in &self.unicode_ranges {
875 if range.contains(c) {
876 return true;
877 }
878 }
879
880 false
881 }
882}
883
884#[derive(Debug, Clone, PartialEq, Eq)]
886pub struct FontMatch {
887 pub id: FontId,
888 pub unicode_ranges: Vec<UnicodeRange>,
889 pub fallbacks: Vec<FontMatchNoFallback>,
890}
891
892#[derive(Debug, Clone, PartialEq, Eq)]
894pub struct FontMatchNoFallback {
895 pub id: FontId,
896 pub unicode_ranges: Vec<UnicodeRange>,
897}
898
899#[derive(Debug, Clone, PartialEq, Eq)]
902pub struct ResolvedFontRun {
903 pub text: String,
905 pub start_byte: usize,
907 pub end_byte: usize,
909 pub font_id: Option<FontId>,
911 pub css_source: String,
913}
914
915#[derive(Debug, Clone, PartialEq, Eq)]
918pub struct FontFallbackChain {
919 pub css_fallbacks: Vec<CssFallbackGroup>,
922
923 pub unicode_fallbacks: Vec<FontMatch>,
926
927 pub original_stack: Vec<String>,
929}
930
931impl FontFallbackChain {
932 pub fn resolve_char(&self, cache: &FcFontCache, ch: char) -> Option<(FontId, String)> {
936 let codepoint = ch as u32;
937
938 for group in &self.css_fallbacks {
940 for font in &group.fonts {
941 if let Some(meta) = cache.get_metadata_by_id(&font.id) {
942 if meta.unicode_ranges.is_empty() {
944 continue;
948 } else {
949 for range in &meta.unicode_ranges {
951 if codepoint >= range.start && codepoint <= range.end {
952 return Some((font.id, group.css_name.clone()));
953 }
954 }
955 }
957 }
958 }
959 }
960
961 for font in &self.unicode_fallbacks {
963 if let Some(meta) = cache.get_metadata_by_id(&font.id) {
964 for range in &meta.unicode_ranges {
966 if codepoint >= range.start && codepoint <= range.end {
967 return Some((font.id, "(unicode-fallback)".to_string()));
968 }
969 }
970 }
971 }
972
973 None
974 }
975
976 pub fn resolve_text(&self, cache: &FcFontCache, text: &str) -> Vec<(char, Option<(FontId, String)>)> {
979 text.chars()
980 .map(|ch| (ch, self.resolve_char(cache, ch)))
981 .collect()
982 }
983
984 pub fn query_for_text(&self, cache: &FcFontCache, text: &str) -> Vec<ResolvedFontRun> {
988 if text.is_empty() {
989 return Vec::new();
990 }
991
992 let mut runs: Vec<ResolvedFontRun> = Vec::new();
993 let mut current_font: Option<FontId> = None;
994 let mut current_css_source: Option<String> = None;
995 let mut current_start_byte: usize = 0;
996
997 for (byte_idx, ch) in text.char_indices() {
998 let resolved = self.resolve_char(cache, ch);
999 let (font_id, css_source) = match &resolved {
1000 Some((id, source)) => (Some(*id), Some(source.clone())),
1001 None => (None, None),
1002 };
1003
1004 let font_changed = font_id != current_font;
1006
1007 if font_changed && byte_idx > 0 {
1008 let run_text = &text[current_start_byte..byte_idx];
1010 runs.push(ResolvedFontRun {
1011 text: run_text.to_string(),
1012 start_byte: current_start_byte,
1013 end_byte: byte_idx,
1014 font_id: current_font,
1015 css_source: current_css_source.clone().unwrap_or_default(),
1016 });
1017 current_start_byte = byte_idx;
1018 }
1019
1020 current_font = font_id;
1021 current_css_source = css_source;
1022 }
1023
1024 if current_start_byte < text.len() {
1026 let run_text = &text[current_start_byte..];
1027 runs.push(ResolvedFontRun {
1028 text: run_text.to_string(),
1029 start_byte: current_start_byte,
1030 end_byte: text.len(),
1031 font_id: current_font,
1032 css_source: current_css_source.unwrap_or_default(),
1033 });
1034 }
1035
1036 runs
1037 }
1038}
1039
1040#[derive(Debug, Clone, PartialEq, Eq)]
1042pub struct CssFallbackGroup {
1043 pub css_name: String,
1045
1046 pub fonts: Vec<FontMatch>,
1049}
1050
1051#[cfg(feature = "std")]
1057#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1058pub(crate) struct FontChainCacheKey {
1059 pub(crate) font_families: Vec<String>,
1061 pub(crate) weight: FcWeight,
1063 pub(crate) italic: PatternMatch,
1065 pub(crate) oblique: PatternMatch,
1066}
1067
1068#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq)]
1070#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
1071#[repr(C)]
1072pub struct FcFontPath {
1073 pub path: String,
1074 pub font_index: usize,
1075}
1076
1077#[derive(Debug, Clone, PartialEq, Eq)]
1079#[repr(C)]
1080pub struct FcFont {
1081 pub bytes: Vec<u8>,
1082 pub font_index: usize,
1083 pub id: String, }
1085
1086#[derive(Debug, Clone)]
1088pub enum FontSource<'a> {
1089 Memory(&'a FcFont),
1091 Disk(&'a FcFontPath),
1093}
1094
1095#[derive(Debug, Clone)]
1098pub struct NamedFont {
1099 pub name: String,
1101 pub bytes: Vec<u8>,
1103}
1104
1105impl NamedFont {
1106 pub fn new(name: impl Into<String>, bytes: Vec<u8>) -> Self {
1108 Self {
1109 name: name.into(),
1110 bytes,
1111 }
1112 }
1113}
1114
1115#[derive(Debug)]
1117pub struct FcFontCache {
1118 pub(crate) patterns: BTreeMap<FcPattern, FontId>,
1120 pub(crate) disk_fonts: BTreeMap<FontId, FcFontPath>,
1122 pub(crate) memory_fonts: BTreeMap<FontId, FcFont>,
1124 pub(crate) metadata: BTreeMap<FontId, FcPattern>,
1126 pub(crate) token_index: BTreeMap<String, alloc::collections::BTreeSet<FontId>>,
1129 pub(crate) font_tokens: BTreeMap<FontId, Vec<String>>,
1132 #[cfg(feature = "std")]
1134 chain_cache: std::sync::Mutex<std::collections::HashMap<FontChainCacheKey, FontFallbackChain>>,
1135}
1136
1137impl Clone for FcFontCache {
1138 fn clone(&self) -> Self {
1139 Self {
1140 patterns: self.patterns.clone(),
1141 disk_fonts: self.disk_fonts.clone(),
1142 memory_fonts: self.memory_fonts.clone(),
1143 metadata: self.metadata.clone(),
1144 token_index: self.token_index.clone(),
1145 font_tokens: self.font_tokens.clone(),
1146 #[cfg(feature = "std")]
1147 chain_cache: std::sync::Mutex::new(std::collections::HashMap::new()), }
1149 }
1150}
1151
1152impl Default for FcFontCache {
1153 fn default() -> Self {
1154 Self {
1155 patterns: BTreeMap::new(),
1156 disk_fonts: BTreeMap::new(),
1157 memory_fonts: BTreeMap::new(),
1158 metadata: BTreeMap::new(),
1159 token_index: BTreeMap::new(),
1160 font_tokens: BTreeMap::new(),
1161 #[cfg(feature = "std")]
1162 chain_cache: std::sync::Mutex::new(std::collections::HashMap::new()),
1163 }
1164 }
1165}
1166
1167impl FcFontCache {
1168 fn index_pattern_tokens(&mut self, pattern: &FcPattern, id: FontId) {
1170 let mut all_tokens = Vec::new();
1172
1173 if let Some(name) = &pattern.name {
1174 all_tokens.extend(Self::extract_font_name_tokens(name));
1175 }
1176
1177 if let Some(family) = &pattern.family {
1178 all_tokens.extend(Self::extract_font_name_tokens(family));
1179 }
1180
1181 let tokens_lower: Vec<String> = all_tokens.iter().map(|t| t.to_lowercase()).collect();
1183
1184 for token_lower in &tokens_lower {
1186 self.token_index
1187 .entry(token_lower.clone())
1188 .or_insert_with(alloc::collections::BTreeSet::new)
1189 .insert(id);
1190 }
1191
1192 self.font_tokens.insert(id, tokens_lower);
1194 }
1195
1196 pub fn with_memory_fonts(&mut self, fonts: Vec<(FcPattern, FcFont)>) -> &mut Self {
1198 for (pattern, font) in fonts {
1199 let id = FontId::new();
1200 self.patterns.insert(pattern.clone(), id);
1201 self.metadata.insert(id, pattern.clone());
1202 self.memory_fonts.insert(id, font);
1203 self.index_pattern_tokens(&pattern, id);
1204 }
1205 self
1206 }
1207
1208 pub fn with_memory_font_with_id(
1210 &mut self,
1211 id: FontId,
1212 pattern: FcPattern,
1213 font: FcFont,
1214 ) -> &mut Self {
1215 self.patterns.insert(pattern.clone(), id);
1216 self.metadata.insert(id, pattern.clone());
1217 self.memory_fonts.insert(id, font);
1218 self.index_pattern_tokens(&pattern, id);
1219 self
1220 }
1221
1222 pub fn get_font_by_id<'a>(&'a self, id: &FontId) -> Option<FontSource<'a>> {
1224 if let Some(font) = self.memory_fonts.get(id) {
1226 return Some(FontSource::Memory(font));
1227 }
1228 if let Some(path) = self.disk_fonts.get(id) {
1230 return Some(FontSource::Disk(path));
1231 }
1232 None
1233 }
1234
1235 pub fn get_metadata_by_id(&self, id: &FontId) -> Option<&FcPattern> {
1237 self.metadata.get(id)
1238 }
1239
1240 #[cfg(feature = "std")]
1242 pub fn get_font_bytes(&self, id: &FontId) -> Option<Vec<u8>> {
1243 match self.get_font_by_id(id)? {
1244 FontSource::Memory(font) => {
1245 Some(font.bytes.clone())
1246 }
1247 FontSource::Disk(path) => {
1248 std::fs::read(&path.path).ok()
1249 }
1250 }
1251 }
1252
1253 #[cfg(not(all(feature = "std", feature = "parsing")))]
1255 pub fn build() -> Self {
1256 Self::default()
1257 }
1258
1259 #[cfg(all(feature = "std", feature = "parsing"))]
1261 pub fn build() -> Self {
1262 Self::build_inner(None)
1263 }
1264
1265 #[cfg(all(feature = "std", feature = "parsing"))]
1290 pub fn build_with_families(families: &[impl AsRef<str>]) -> Self {
1291 let os = OperatingSystem::current();
1293 let mut target_families: Vec<String> = Vec::new();
1294
1295 for family in families {
1296 let family_str = family.as_ref();
1297 let expanded = os.expand_generic_family(family_str, &[]);
1298 if expanded.is_empty() || (expanded.len() == 1 && expanded[0] == family_str) {
1299 target_families.push(family_str.to_string());
1300 } else {
1301 target_families.extend(expanded);
1302 }
1303 }
1304
1305 Self::build_inner(Some(&target_families))
1306 }
1307
1308 #[cfg(all(feature = "std", feature = "parsing"))]
1314 fn build_inner(family_filter: Option<&[String]>) -> Self {
1315 let mut cache = FcFontCache::default();
1316
1317 let filter_normalized: Option<Vec<String>> = family_filter.map(|families| {
1319 families
1320 .iter()
1321 .map(|f| f.to_lowercase().replace(' ', "").replace('-', ""))
1322 .collect()
1323 });
1324
1325 let matches_filter = |pattern: &FcPattern| -> bool {
1327 match &filter_normalized {
1328 None => true, Some(targets) => {
1330 pattern.name.as_ref().map_or(false, |name| {
1331 let name_norm = name.to_lowercase().replace(' ', "").replace('-', "");
1332 targets.iter().any(|target| name_norm.contains(target))
1333 }) || pattern.family.as_ref().map_or(false, |family| {
1334 let family_norm = family.to_lowercase().replace(' ', "").replace('-', "");
1335 targets.iter().any(|target| family_norm.contains(target))
1336 })
1337 }
1338 }
1339 };
1340
1341 #[cfg(target_os = "linux")]
1342 {
1343 if let Some(font_entries) = FcScanDirectories() {
1344 for (pattern, path) in font_entries {
1345 if matches_filter(&pattern) {
1346 let id = FontId::new();
1347 cache.patterns.insert(pattern.clone(), id);
1348 cache.metadata.insert(id, pattern.clone());
1349 cache.disk_fonts.insert(id, path);
1350 cache.index_pattern_tokens(&pattern, id);
1351 }
1352 }
1353 }
1354 }
1355
1356 #[cfg(target_os = "windows")]
1357 {
1358 let system_root = std::env::var("SystemRoot")
1359 .or_else(|_| std::env::var("WINDIR"))
1360 .unwrap_or_else(|_| "C:\\Windows".to_string());
1361
1362 let user_profile = std::env::var("USERPROFILE")
1363 .unwrap_or_else(|_| "C:\\Users\\Default".to_string());
1364
1365 let font_dirs = vec![
1366 (None, format!("{}\\Fonts\\", system_root)),
1367 (None, format!("{}\\AppData\\Local\\Microsoft\\Windows\\Fonts\\", user_profile)),
1368 ];
1369
1370 let font_entries = FcScanDirectoriesInner(&font_dirs);
1371 for (pattern, path) in font_entries {
1372 if matches_filter(&pattern) {
1373 let id = FontId::new();
1374 cache.patterns.insert(pattern.clone(), id);
1375 cache.metadata.insert(id, pattern.clone());
1376 cache.disk_fonts.insert(id, path);
1377 cache.index_pattern_tokens(&pattern, id);
1378 }
1379 }
1380 }
1381
1382 #[cfg(target_os = "macos")]
1383 {
1384 let font_dirs = vec![
1385 (None, "~/Library/Fonts".to_owned()),
1386 (None, "/System/Library/Fonts".to_owned()),
1387 (None, "/Library/Fonts".to_owned()),
1388 (None, "/System/Library/AssetsV2".to_owned()),
1389 ];
1390
1391 let font_entries = FcScanDirectoriesInner(&font_dirs);
1392 for (pattern, path) in font_entries {
1393 if matches_filter(&pattern) {
1394 let id = FontId::new();
1395 cache.patterns.insert(pattern.clone(), id);
1396 cache.metadata.insert(id, pattern.clone());
1397 cache.disk_fonts.insert(id, path);
1398 cache.index_pattern_tokens(&pattern, id);
1399 }
1400 }
1401 }
1402
1403 cache
1404 }
1405
1406 pub fn is_memory_font(&self, id: &FontId) -> bool {
1408 self.memory_fonts.contains_key(id)
1409 }
1410
1411 pub fn list(&self) -> Vec<(&FcPattern, FontId)> {
1413 self.patterns
1414 .iter()
1415 .map(|(pattern, id)| (pattern, *id))
1416 .collect()
1417 }
1418
1419 pub fn is_empty(&self) -> bool {
1421 self.patterns.is_empty()
1422 }
1423
1424 pub fn len(&self) -> usize {
1426 self.patterns.len()
1427 }
1428
1429 pub fn query(&self, pattern: &FcPattern, trace: &mut Vec<TraceMsg>) -> Option<FontMatch> {
1432 let mut matches = Vec::new();
1433
1434 for (stored_pattern, id) in &self.patterns {
1435 if Self::query_matches_internal(stored_pattern, pattern, trace) {
1436 let metadata = self.metadata.get(id).unwrap_or(stored_pattern);
1437
1438 let unicode_compatibility = if pattern.unicode_ranges.is_empty() {
1440 Self::calculate_unicode_coverage(&metadata.unicode_ranges) as i32
1442 } else {
1443 Self::calculate_unicode_compatibility(&pattern.unicode_ranges, &metadata.unicode_ranges)
1445 };
1446
1447 let style_score = Self::calculate_style_score(pattern, metadata);
1448
1449 let is_memory = self.memory_fonts.contains_key(id);
1451
1452 matches.push((*id, unicode_compatibility, style_score, metadata.clone(), is_memory));
1453 }
1454 }
1455
1456 matches.sort_by(|a, b| {
1458 b.4.cmp(&a.4)
1460 .then_with(|| b.1.cmp(&a.1)) .then_with(|| a.2.cmp(&b.2)) });
1463
1464 matches.first().map(|(id, _, _, metadata, _)| {
1465 FontMatch {
1466 id: *id,
1467 unicode_ranges: metadata.unicode_ranges.clone(),
1468 fallbacks: Vec::new(), }
1470 })
1471 }
1472
1473 fn query_internal(&self, pattern: &FcPattern, trace: &mut Vec<TraceMsg>) -> Vec<FontMatch> {
1478 let mut matches = Vec::new();
1479
1480 for (stored_pattern, id) in &self.patterns {
1481 if Self::query_matches_internal(stored_pattern, pattern, trace) {
1482 let metadata = self.metadata.get(id).unwrap_or(stored_pattern);
1483
1484 let unicode_compatibility = if pattern.unicode_ranges.is_empty() {
1486 Self::calculate_unicode_coverage(&metadata.unicode_ranges) as i32
1487 } else {
1488 Self::calculate_unicode_compatibility(&pattern.unicode_ranges, &metadata.unicode_ranges)
1489 };
1490
1491 let style_score = Self::calculate_style_score(pattern, metadata);
1492 matches.push((*id, unicode_compatibility, style_score, metadata.clone()));
1493 }
1494 }
1495
1496 matches.sort_by(|a, b| {
1500 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)) });
1505
1506 matches
1507 .into_iter()
1508 .map(|(id, _, _, metadata)| {
1509 FontMatch {
1510 id,
1511 unicode_ranges: metadata.unicode_ranges.clone(),
1512 fallbacks: Vec::new(), }
1514 })
1515 .collect()
1516 }
1517
1518 pub fn compute_fallbacks(
1522 &self,
1523 font_id: &FontId,
1524 trace: &mut Vec<TraceMsg>,
1525 ) -> Vec<FontMatchNoFallback> {
1526 let pattern = match self.metadata.get(font_id) {
1528 Some(p) => p,
1529 None => return Vec::new(),
1530 };
1531
1532 self.compute_fallbacks_for_pattern(pattern, Some(font_id), trace)
1533 }
1534
1535 fn compute_fallbacks_for_pattern(
1536 &self,
1537 pattern: &FcPattern,
1538 exclude_id: Option<&FontId>,
1539 _trace: &mut Vec<TraceMsg>,
1540 ) -> Vec<FontMatchNoFallback> {
1541 let mut candidates = Vec::new();
1542
1543 for (stored_pattern, id) in &self.patterns {
1545 if exclude_id.is_some() && exclude_id.unwrap() == id {
1547 continue;
1548 }
1549
1550 if !stored_pattern.unicode_ranges.is_empty() && !pattern.unicode_ranges.is_empty() {
1552 let unicode_compatibility = Self::calculate_unicode_compatibility(
1554 &pattern.unicode_ranges,
1555 &stored_pattern.unicode_ranges
1556 );
1557
1558 if unicode_compatibility > 0 {
1560 let style_score = Self::calculate_style_score(pattern, stored_pattern);
1561 candidates.push((
1562 FontMatchNoFallback {
1563 id: *id,
1564 unicode_ranges: stored_pattern.unicode_ranges.clone(),
1565 },
1566 unicode_compatibility,
1567 style_score,
1568 stored_pattern.clone(),
1569 ));
1570 }
1571 } else if pattern.unicode_ranges.is_empty() && !stored_pattern.unicode_ranges.is_empty() {
1572 let coverage = Self::calculate_unicode_coverage(&stored_pattern.unicode_ranges) as i32;
1574 let style_score = Self::calculate_style_score(pattern, stored_pattern);
1575 candidates.push((
1576 FontMatchNoFallback {
1577 id: *id,
1578 unicode_ranges: stored_pattern.unicode_ranges.clone(),
1579 },
1580 coverage,
1581 style_score,
1582 stored_pattern.clone(),
1583 ));
1584 }
1585 }
1586
1587 candidates.sort_by(|a, b| {
1589 b.1.cmp(&a.1)
1590 .then_with(|| a.2.cmp(&b.2))
1591 });
1592
1593 let mut seen_ranges = Vec::new();
1595 let mut deduplicated = Vec::new();
1596
1597 for (id, _, _, pattern) in candidates {
1598 let mut is_new_range = false;
1599
1600 for range in &pattern.unicode_ranges {
1601 if !seen_ranges.iter().any(|r: &UnicodeRange| r.overlaps(range)) {
1602 seen_ranges.push(*range);
1603 is_new_range = true;
1604 }
1605 }
1606
1607 if is_new_range {
1608 deduplicated.push(id);
1609 }
1610 }
1611
1612 deduplicated
1613 }
1614
1615 pub fn get_memory_font(&self, id: &FontId) -> Option<&FcFont> {
1617 self.memory_fonts.get(id)
1618 }
1619
1620 pub fn query_matches_internal(
1622 k: &FcPattern,
1623 pattern: &FcPattern,
1624 trace: &mut Vec<TraceMsg>,
1625 ) -> bool {
1626 if let Some(ref name) = pattern.name {
1628 let matches = k
1629 .name
1630 .as_ref()
1631 .map_or(false, |k_name| k_name.contains(name));
1632
1633 if !matches {
1634 trace.push(TraceMsg {
1635 level: TraceLevel::Info,
1636 path: k
1637 .name
1638 .as_ref()
1639 .map_or_else(|| "<unknown>".to_string(), Clone::clone),
1640 reason: MatchReason::NameMismatch {
1641 requested: pattern.name.clone(),
1642 found: k.name.clone(),
1643 },
1644 });
1645 return false;
1646 }
1647 }
1648
1649 if let Some(ref family) = pattern.family {
1651 let matches = k
1652 .family
1653 .as_ref()
1654 .map_or(false, |k_family| k_family.contains(family));
1655
1656 if !matches {
1657 trace.push(TraceMsg {
1658 level: TraceLevel::Info,
1659 path: k
1660 .name
1661 .as_ref()
1662 .map_or_else(|| "<unknown>".to_string(), Clone::clone),
1663 reason: MatchReason::FamilyMismatch {
1664 requested: pattern.family.clone(),
1665 found: k.family.clone(),
1666 },
1667 });
1668 return false;
1669 }
1670 }
1671
1672 let style_properties = [
1674 (
1675 "italic",
1676 pattern.italic.needs_to_match(),
1677 pattern.italic.matches(&k.italic),
1678 ),
1679 (
1680 "oblique",
1681 pattern.oblique.needs_to_match(),
1682 pattern.oblique.matches(&k.oblique),
1683 ),
1684 (
1685 "bold",
1686 pattern.bold.needs_to_match(),
1687 pattern.bold.matches(&k.bold),
1688 ),
1689 (
1690 "monospace",
1691 pattern.monospace.needs_to_match(),
1692 pattern.monospace.matches(&k.monospace),
1693 ),
1694 (
1695 "condensed",
1696 pattern.condensed.needs_to_match(),
1697 pattern.condensed.matches(&k.condensed),
1698 ),
1699 ];
1700
1701 for (property_name, needs_to_match, matches) in style_properties {
1702 if needs_to_match && !matches {
1703 let (requested, found) = match property_name {
1704 "italic" => (format!("{:?}", pattern.italic), format!("{:?}", k.italic)),
1705 "oblique" => (format!("{:?}", pattern.oblique), format!("{:?}", k.oblique)),
1706 "bold" => (format!("{:?}", pattern.bold), format!("{:?}", k.bold)),
1707 "monospace" => (
1708 format!("{:?}", pattern.monospace),
1709 format!("{:?}", k.monospace),
1710 ),
1711 "condensed" => (
1712 format!("{:?}", pattern.condensed),
1713 format!("{:?}", k.condensed),
1714 ),
1715 _ => (String::new(), String::new()),
1716 };
1717
1718 trace.push(TraceMsg {
1719 level: TraceLevel::Info,
1720 path: k
1721 .name
1722 .as_ref()
1723 .map_or_else(|| "<unknown>".to_string(), |s| s.clone()),
1724 reason: MatchReason::StyleMismatch {
1725 property: property_name,
1726 requested,
1727 found,
1728 },
1729 });
1730 return false;
1731 }
1732 }
1733
1734 if pattern.weight != FcWeight::Normal && pattern.weight != k.weight {
1736 trace.push(TraceMsg {
1737 level: TraceLevel::Info,
1738 path: k
1739 .name
1740 .as_ref()
1741 .map_or_else(|| "<unknown>".to_string(), |s| s.clone()),
1742 reason: MatchReason::WeightMismatch {
1743 requested: pattern.weight,
1744 found: k.weight,
1745 },
1746 });
1747 return false;
1748 }
1749
1750 if pattern.stretch != FcStretch::Normal && pattern.stretch != k.stretch {
1752 trace.push(TraceMsg {
1753 level: TraceLevel::Info,
1754 path: k
1755 .name
1756 .as_ref()
1757 .map_or_else(|| "<unknown>".to_string(), |s| s.clone()),
1758 reason: MatchReason::StretchMismatch {
1759 requested: pattern.stretch,
1760 found: k.stretch,
1761 },
1762 });
1763 return false;
1764 }
1765
1766 if !pattern.unicode_ranges.is_empty() {
1768 let mut has_overlap = false;
1769
1770 for p_range in &pattern.unicode_ranges {
1771 for k_range in &k.unicode_ranges {
1772 if p_range.overlaps(k_range) {
1773 has_overlap = true;
1774 break;
1775 }
1776 }
1777 if has_overlap {
1778 break;
1779 }
1780 }
1781
1782 if !has_overlap {
1783 trace.push(TraceMsg {
1784 level: TraceLevel::Info,
1785 path: k
1786 .name
1787 .as_ref()
1788 .map_or_else(|| "<unknown>".to_string(), |s| s.clone()),
1789 reason: MatchReason::UnicodeRangeMismatch {
1790 character: '\0', ranges: k.unicode_ranges.clone(),
1792 },
1793 });
1794 return false;
1795 }
1796 }
1797
1798 true
1799 }
1800
1801 #[cfg(feature = "std")]
1827 pub fn resolve_font_chain(
1828 &self,
1829 font_families: &[String],
1830 weight: FcWeight,
1831 italic: PatternMatch,
1832 oblique: PatternMatch,
1833 trace: &mut Vec<TraceMsg>,
1834 ) -> FontFallbackChain {
1835 self.resolve_font_chain_with_os(font_families, weight, italic, oblique, trace, OperatingSystem::current())
1836 }
1837
1838 #[cfg(feature = "std")]
1840 pub fn resolve_font_chain_with_os(
1841 &self,
1842 font_families: &[String],
1843 weight: FcWeight,
1844 italic: PatternMatch,
1845 oblique: PatternMatch,
1846 trace: &mut Vec<TraceMsg>,
1847 os: OperatingSystem,
1848 ) -> FontFallbackChain {
1849 let cache_key = FontChainCacheKey {
1852 font_families: font_families.to_vec(), weight,
1854 italic,
1855 oblique,
1856 };
1857
1858 if let Ok(cache) = self.chain_cache.lock() {
1859 if let Some(cached) = cache.get(&cache_key) {
1860 return cached.clone();
1861 }
1862 }
1863
1864 let expanded_families = expand_font_families(font_families, os, &[]);
1866
1867 let chain = self.resolve_font_chain_uncached(
1869 &expanded_families,
1870 weight,
1871 italic,
1872 oblique,
1873 trace,
1874 );
1875
1876 if let Ok(mut cache) = self.chain_cache.lock() {
1878 cache.insert(cache_key, chain.clone());
1879 }
1880
1881 chain
1882 }
1883
1884 #[cfg(feature = "std")]
1890 fn resolve_font_chain_uncached(
1891 &self,
1892 font_families: &[String],
1893 weight: FcWeight,
1894 italic: PatternMatch,
1895 oblique: PatternMatch,
1896 trace: &mut Vec<TraceMsg>,
1897 ) -> FontFallbackChain {
1898 let mut css_fallbacks = Vec::new();
1899
1900 for (_i, family) in font_families.iter().enumerate() {
1902 let (pattern, is_generic) = if Self::is_generic_family(family) {
1904 let pattern = match family.as_str() {
1906 "sans-serif" => FcPattern {
1907 name: None,
1908 weight,
1909 italic,
1910 oblique,
1911 monospace: PatternMatch::False,
1912 unicode_ranges: Vec::new(),
1913 ..Default::default()
1914 },
1915 "serif" => FcPattern {
1916 name: None,
1917 weight,
1918 italic,
1919 oblique,
1920 monospace: PatternMatch::False,
1921 unicode_ranges: Vec::new(),
1922 ..Default::default()
1923 },
1924 "monospace" => FcPattern {
1925 name: None,
1926 weight,
1927 italic,
1928 oblique,
1929 monospace: PatternMatch::True,
1930 unicode_ranges: Vec::new(),
1931 ..Default::default()
1932 },
1933 _ => FcPattern {
1934 name: None,
1935 weight,
1936 italic,
1937 oblique,
1938 unicode_ranges: Vec::new(),
1939 ..Default::default()
1940 },
1941 };
1942 (pattern, true)
1943 } else {
1944 let pattern = FcPattern {
1946 name: Some(family.clone()),
1947 weight,
1948 italic,
1949 oblique,
1950 unicode_ranges: Vec::new(),
1951 ..Default::default()
1952 };
1953 (pattern, false)
1954 };
1955
1956 let mut matches = if is_generic {
1959 self.query_internal(&pattern, trace)
1961 } else {
1962 self.fuzzy_query_by_name(family, weight, italic, oblique, &[], trace)
1964 };
1965
1966 if is_generic && matches.len() > 5 {
1968 matches.truncate(5);
1969 }
1970
1971 css_fallbacks.push(CssFallbackGroup {
1974 css_name: family.clone(),
1975 fonts: matches,
1976 });
1977 }
1978
1979 FontFallbackChain {
1982 css_fallbacks,
1983 unicode_fallbacks: Vec::new(), original_stack: font_families.to_vec(),
1985 }
1986 }
1987
1988 #[allow(dead_code)]
1990 fn extract_unicode_ranges(text: &str) -> Vec<UnicodeRange> {
1991 let mut chars: Vec<char> = text.chars().collect();
1992 chars.sort_unstable();
1993 chars.dedup();
1994
1995 if chars.is_empty() {
1996 return Vec::new();
1997 }
1998
1999 let mut ranges = Vec::new();
2000 let mut range_start = chars[0] as u32;
2001 let mut range_end = range_start;
2002
2003 for &c in &chars[1..] {
2004 let codepoint = c as u32;
2005 if codepoint == range_end + 1 {
2006 range_end = codepoint;
2007 } else {
2008 ranges.push(UnicodeRange { start: range_start, end: range_end });
2009 range_start = codepoint;
2010 range_end = codepoint;
2011 }
2012 }
2013
2014 ranges.push(UnicodeRange { start: range_start, end: range_end });
2015 ranges
2016 }
2017
2018 #[cfg(feature = "std")]
2020 fn is_generic_family(family: &str) -> bool {
2021 matches!(
2022 family.to_lowercase().as_str(),
2023 "serif" | "sans-serif" | "monospace" | "cursive" | "fantasy" | "system-ui"
2024 )
2025 }
2026
2027 #[cfg(feature = "std")]
2034 fn fuzzy_query_by_name(
2035 &self,
2036 requested_name: &str,
2037 weight: FcWeight,
2038 italic: PatternMatch,
2039 oblique: PatternMatch,
2040 unicode_ranges: &[UnicodeRange],
2041 _trace: &mut Vec<TraceMsg>,
2042 ) -> Vec<FontMatch> {
2043 let tokens = Self::extract_font_name_tokens(requested_name);
2045
2046 if tokens.is_empty() {
2047 return Vec::new();
2048 }
2049
2050 let tokens_lower: Vec<String> = tokens.iter().map(|t| t.to_lowercase()).collect();
2052
2053 let first_token = &tokens_lower[0];
2060 let mut candidate_ids = match self.token_index.get(first_token) {
2061 Some(ids) if !ids.is_empty() => ids.clone(),
2062 _ => {
2063 return Vec::new();
2065 }
2066 };
2067
2068 for token in &tokens_lower[1..] {
2070 if let Some(token_ids) = self.token_index.get(token) {
2071 let intersection: alloc::collections::BTreeSet<FontId> =
2073 candidate_ids.intersection(token_ids).copied().collect();
2074
2075 if intersection.is_empty() {
2076 break;
2078 } else {
2079 candidate_ids = intersection;
2081 }
2082 } else {
2083 break;
2085 }
2086 }
2087
2088 let mut candidates = Vec::new();
2090
2091 for id in candidate_ids {
2092 let pattern = match self.metadata.get(&id) {
2093 Some(p) => p,
2094 None => continue,
2095 };
2096
2097 let font_tokens_lower = match self.font_tokens.get(&id) {
2099 Some(tokens) => tokens,
2100 None => continue,
2101 };
2102
2103 if font_tokens_lower.is_empty() {
2104 continue;
2105 }
2106
2107 let token_matches = tokens_lower.iter()
2110 .filter(|req_token| {
2111 font_tokens_lower.iter().any(|font_token| {
2112 font_token.contains(req_token.as_str())
2114 })
2115 })
2116 .count();
2117
2118 if token_matches == 0 {
2120 continue;
2121 }
2122
2123 let token_similarity = (token_matches * 100 / tokens.len()) as i32;
2125
2126 let unicode_similarity = if !unicode_ranges.is_empty() && !pattern.unicode_ranges.is_empty() {
2128 Self::calculate_unicode_compatibility(unicode_ranges, &pattern.unicode_ranges)
2129 } else {
2130 0
2131 };
2132
2133 if !unicode_ranges.is_empty() && unicode_similarity == 0 {
2136 continue;
2137 }
2138
2139 let style_score = Self::calculate_style_score(&FcPattern {
2140 weight,
2141 italic,
2142 oblique,
2143 ..Default::default()
2144 }, pattern);
2145
2146 candidates.push((
2147 id,
2148 token_similarity,
2149 unicode_similarity,
2150 style_score,
2151 pattern.clone(),
2152 ));
2153 }
2154
2155 candidates.sort_by(|a, b| {
2161 if !unicode_ranges.is_empty() {
2162 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 {
2169 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)) }
2175 });
2176
2177 candidates.truncate(5);
2179
2180 candidates
2182 .into_iter()
2183 .map(|(id, _token_sim, _unicode_sim, _style, pattern)| {
2184 FontMatch {
2185 id,
2186 unicode_ranges: pattern.unicode_ranges.clone(),
2187 fallbacks: Vec::new(), }
2189 })
2190 .collect()
2191 }
2192
2193 pub fn extract_font_name_tokens(name: &str) -> Vec<String> {
2197 let mut tokens = Vec::new();
2198 let mut current_token = String::new();
2199 let mut last_was_lower = false;
2200
2201 for c in name.chars() {
2202 if c.is_whitespace() || c == '-' || c == '_' {
2203 if !current_token.is_empty() {
2205 tokens.push(current_token.clone());
2206 current_token.clear();
2207 }
2208 last_was_lower = false;
2209 } else if c.is_uppercase() && last_was_lower && !current_token.is_empty() {
2210 tokens.push(current_token.clone());
2212 current_token.clear();
2213 current_token.push(c);
2214 last_was_lower = false;
2215 } else {
2216 current_token.push(c);
2217 last_was_lower = c.is_lowercase();
2218 }
2219 }
2220
2221 if !current_token.is_empty() {
2222 tokens.push(current_token);
2223 }
2224
2225 tokens
2226 }
2227
2228 #[allow(dead_code)]
2231 fn normalize_font_name(name: &str) -> String {
2232 name.chars()
2233 .filter(|c| c.is_ascii_alphanumeric())
2234 .map(|c| c.to_ascii_lowercase())
2235 .collect()
2236 }
2237
2238 #[allow(dead_code)]
2240 fn levenshtein_distance(s1: &str, s2: &str) -> usize {
2241 let len1 = s1.chars().count();
2242 let len2 = s2.chars().count();
2243
2244 if len1 == 0 {
2245 return len2;
2246 }
2247 if len2 == 0 {
2248 return len1;
2249 }
2250
2251 let mut prev_row: Vec<usize> = (0..=len2).collect();
2252 let mut curr_row = vec![0; len2 + 1];
2253
2254 for (i, c1) in s1.chars().enumerate() {
2255 curr_row[0] = i + 1;
2256
2257 for (j, c2) in s2.chars().enumerate() {
2258 let cost = if c1 == c2 { 0 } else { 1 };
2259 curr_row[j + 1] = (curr_row[j] + 1)
2260 .min(prev_row[j + 1] + 1)
2261 .min(prev_row[j] + cost);
2262 }
2263
2264 core::mem::swap(&mut prev_row, &mut curr_row);
2265 }
2266
2267 prev_row[len2]
2268 }
2269
2270 #[allow(dead_code)]
2274 fn find_unicode_fallbacks(
2275 &self,
2276 unicode_ranges: &[UnicodeRange],
2277 covered_chars: &[bool],
2278 existing_groups: &[CssFallbackGroup],
2279 weight: FcWeight,
2280 italic: PatternMatch,
2281 oblique: PatternMatch,
2282 trace: &mut Vec<TraceMsg>,
2283 ) -> Vec<FontMatch> {
2284 let mut uncovered_ranges = Vec::new();
2286 for (i, &covered) in covered_chars.iter().enumerate() {
2287 if !covered && i < unicode_ranges.len() {
2288 uncovered_ranges.push(unicode_ranges[i].clone());
2289 }
2290 }
2291
2292 if uncovered_ranges.is_empty() {
2293 return Vec::new();
2294 }
2295
2296 let pattern = FcPattern {
2298 name: None, weight,
2300 italic,
2301 oblique,
2302 unicode_ranges: uncovered_ranges.clone(),
2303 ..Default::default()
2304 };
2305
2306 let mut candidates = self.query_internal(&pattern, trace);
2307
2308 let existing_prefixes: Vec<String> = existing_groups
2311 .iter()
2312 .flat_map(|group| {
2313 group.fonts.iter().filter_map(|font| {
2314 self.get_metadata_by_id(&font.id)
2315 .and_then(|meta| meta.family.clone())
2316 .and_then(|family| {
2317 family.split_whitespace()
2319 .take(2)
2320 .collect::<Vec<_>>()
2321 .join(" ")
2322 .into()
2323 })
2324 })
2325 })
2326 .collect();
2327
2328 candidates.sort_by(|a, b| {
2332 let a_meta = self.get_metadata_by_id(&a.id);
2333 let b_meta = self.get_metadata_by_id(&b.id);
2334
2335 let a_score = Self::calculate_font_similarity_score(a_meta, &existing_prefixes);
2336 let b_score = Self::calculate_font_similarity_score(b_meta, &existing_prefixes);
2337
2338 b_score.cmp(&a_score) .then_with(|| {
2340 let a_coverage = Self::calculate_unicode_compatibility(&uncovered_ranges, &a.unicode_ranges);
2341 let b_coverage = Self::calculate_unicode_compatibility(&uncovered_ranges, &b.unicode_ranges);
2342 b_coverage.cmp(&a_coverage)
2343 })
2344 });
2345
2346 let mut result = Vec::new();
2348 let mut remaining_uncovered: Vec<bool> = vec![true; uncovered_ranges.len()];
2349
2350 for candidate in candidates {
2351 let mut covers_new_range = false;
2353
2354 for (i, range) in uncovered_ranges.iter().enumerate() {
2355 if remaining_uncovered[i] {
2356 for font_range in &candidate.unicode_ranges {
2358 if font_range.overlaps(range) {
2359 remaining_uncovered[i] = false;
2360 covers_new_range = true;
2361 break;
2362 }
2363 }
2364 }
2365 }
2366
2367 if covers_new_range {
2369 result.push(candidate);
2370
2371 if remaining_uncovered.iter().all(|&uncovered| !uncovered) {
2373 break;
2374 }
2375 }
2376 }
2377
2378 result
2379 }
2380
2381 #[allow(dead_code)]
2384 fn calculate_font_similarity_score(
2385 font_meta: Option<&FcPattern>,
2386 existing_prefixes: &[String],
2387 ) -> i32 {
2388 let Some(meta) = font_meta else { return 0; };
2389 let Some(family) = &meta.family else { return 0; };
2390
2391 for prefix in existing_prefixes {
2393 if family.starts_with(prefix) {
2394 return 100; }
2396 if family.contains(prefix) {
2397 return 50; }
2399 }
2400
2401 0 }
2403
2404 pub fn calculate_unicode_coverage(ranges: &[UnicodeRange]) -> u64 {
2407 ranges
2408 .iter()
2409 .map(|range| (range.end - range.start + 1) as u64)
2410 .sum()
2411 }
2412
2413 pub fn calculate_unicode_compatibility(
2416 requested: &[UnicodeRange],
2417 available: &[UnicodeRange],
2418 ) -> i32 {
2419 if requested.is_empty() {
2420 return Self::calculate_unicode_coverage(available) as i32;
2422 }
2423
2424 let mut total_coverage = 0u32;
2425
2426 for req_range in requested {
2427 for avail_range in available {
2428 let overlap_start = req_range.start.max(avail_range.start);
2430 let overlap_end = req_range.end.min(avail_range.end);
2431
2432 if overlap_start <= overlap_end {
2433 let overlap_size = overlap_end - overlap_start + 1;
2435 total_coverage += overlap_size;
2436 }
2437 }
2438 }
2439
2440 total_coverage as i32
2441 }
2442
2443 pub fn calculate_style_score(original: &FcPattern, candidate: &FcPattern) -> i32 {
2444
2445 let mut score = 0_i32;
2446
2447 if (original.bold == PatternMatch::True && candidate.weight == FcWeight::Bold)
2449 || (original.bold == PatternMatch::False && candidate.weight != FcWeight::Bold)
2450 {
2451 } else {
2454 let weight_diff = (original.weight as i32 - candidate.weight as i32).abs();
2456 score += weight_diff as i32;
2457 }
2458
2459 if original.weight == candidate.weight {
2462 score -= 15;
2463 if original.weight == FcWeight::Normal {
2464 score -= 10; }
2466 }
2467
2468 if (original.condensed == PatternMatch::True && candidate.stretch.is_condensed())
2470 || (original.condensed == PatternMatch::False && !candidate.stretch.is_condensed())
2471 {
2472 } else {
2475 let stretch_diff = (original.stretch as i32 - candidate.stretch as i32).abs();
2477 score += (stretch_diff * 100) as i32;
2478 }
2479
2480 let style_props = [
2482 (original.italic, candidate.italic, 300, 150),
2483 (original.oblique, candidate.oblique, 200, 100),
2484 (original.bold, candidate.bold, 300, 150),
2485 (original.monospace, candidate.monospace, 100, 50),
2486 (original.condensed, candidate.condensed, 100, 50),
2487 ];
2488
2489 for (orig, cand, mismatch_penalty, dontcare_penalty) in style_props {
2490 if orig.needs_to_match() {
2491 if orig == PatternMatch::False && cand == PatternMatch::DontCare {
2492 score += dontcare_penalty / 2;
2495 } else if !orig.matches(&cand) {
2496 if cand == PatternMatch::DontCare {
2497 score += dontcare_penalty;
2498 } else {
2499 score += mismatch_penalty;
2500 }
2501 } else if orig == PatternMatch::True && cand == PatternMatch::True {
2502 score -= 20;
2504 } else if orig == PatternMatch::False && cand == PatternMatch::False {
2505 score -= 20;
2508 }
2509 } else {
2510 if cand == PatternMatch::True {
2515 score += dontcare_penalty / 3;
2516 }
2517 }
2518 }
2519
2520 if let (Some(name), Some(family)) = (&candidate.name, &candidate.family) {
2526 let name_lower = name.to_lowercase();
2527 let family_lower = family.to_lowercase();
2528
2529 let extra = if name_lower.starts_with(&family_lower) {
2531 name_lower[family_lower.len()..].to_string()
2532 } else {
2533 String::new()
2534 };
2535
2536 let stripped = extra
2538 .replace("regular", "")
2539 .replace("normal", "")
2540 .replace("book", "")
2541 .replace("roman", "");
2542 let stripped = stripped.trim();
2543
2544 if stripped.is_empty() {
2545 score -= 50;
2547 } else {
2548 let extra_words = stripped.split_whitespace().count();
2550 score += (extra_words as i32) * 25;
2551 }
2552 }
2553
2554 if let Some(ref subfamily) = candidate.metadata.font_subfamily {
2558 let sf_lower = subfamily.to_lowercase();
2559 if sf_lower == "regular" {
2560 score -= 30;
2561 }
2562 }
2563
2564 score
2565 }
2566}
2567
2568#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
2569fn FcScanDirectories() -> Option<Vec<(FcPattern, FcFontPath)>> {
2570 use std::fs;
2571 use std::path::Path;
2572
2573 const BASE_FONTCONFIG_PATH: &str = "/etc/fonts/fonts.conf";
2574
2575 if !Path::new(BASE_FONTCONFIG_PATH).exists() {
2576 return None;
2577 }
2578
2579 let mut font_paths = Vec::with_capacity(32);
2580 let mut paths_to_visit = vec![(None, PathBuf::from(BASE_FONTCONFIG_PATH))];
2581
2582 while let Some((prefix, path_to_visit)) = paths_to_visit.pop() {
2583 let path = match process_path(&prefix, path_to_visit, true) {
2584 Some(path) => path,
2585 None => continue,
2586 };
2587
2588 let metadata = match fs::metadata(&path) {
2589 Ok(metadata) => metadata,
2590 Err(_) => continue,
2591 };
2592
2593 if metadata.is_file() {
2594 let xml_utf8 = match fs::read_to_string(&path) {
2595 Ok(xml_utf8) => xml_utf8,
2596 Err(_) => continue,
2597 };
2598
2599 if ParseFontsConf(&xml_utf8, &mut paths_to_visit, &mut font_paths).is_none() {
2600 continue;
2601 }
2602 } else if metadata.is_dir() {
2603 let dir_entries = match fs::read_dir(&path) {
2604 Ok(dir_entries) => dir_entries,
2605 Err(_) => continue,
2606 };
2607
2608 for entry_result in dir_entries {
2609 let entry = match entry_result {
2610 Ok(entry) => entry,
2611 Err(_) => continue,
2612 };
2613
2614 let entry_path = entry.path();
2615
2616 let entry_metadata = match fs::metadata(&entry_path) {
2618 Ok(metadata) => metadata,
2619 Err(_) => continue,
2620 };
2621
2622 if !entry_metadata.is_file() {
2623 continue;
2624 }
2625
2626 let file_name = match entry_path.file_name() {
2627 Some(name) => name,
2628 None => continue,
2629 };
2630
2631 let file_name_str = file_name.to_string_lossy();
2632 if file_name_str.starts_with(|c: char| c.is_ascii_digit())
2633 && file_name_str.ends_with(".conf")
2634 {
2635 paths_to_visit.push((None, entry_path));
2636 }
2637 }
2638 }
2639 }
2640
2641 if font_paths.is_empty() {
2642 return None;
2643 }
2644
2645 Some(FcScanDirectoriesInner(&font_paths))
2646}
2647
2648#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
2650fn ParseFontsConf(
2651 input: &str,
2652 paths_to_visit: &mut Vec<(Option<String>, PathBuf)>,
2653 font_paths: &mut Vec<(Option<String>, String)>,
2654) -> Option<()> {
2655 use xmlparser::Token::*;
2656 use xmlparser::Tokenizer;
2657
2658 const TAG_INCLUDE: &str = "include";
2659 const TAG_DIR: &str = "dir";
2660 const ATTRIBUTE_PREFIX: &str = "prefix";
2661
2662 let mut current_prefix: Option<&str> = None;
2663 let mut current_path: Option<&str> = None;
2664 let mut is_in_include = false;
2665 let mut is_in_dir = false;
2666
2667 for token_result in Tokenizer::from(input) {
2668 let token = match token_result {
2669 Ok(token) => token,
2670 Err(_) => return None,
2671 };
2672
2673 match token {
2674 ElementStart { local, .. } => {
2675 if is_in_include || is_in_dir {
2676 return None; }
2678
2679 match local.as_str() {
2680 TAG_INCLUDE => {
2681 is_in_include = true;
2682 }
2683 TAG_DIR => {
2684 is_in_dir = true;
2685 }
2686 _ => continue,
2687 }
2688
2689 current_path = None;
2690 }
2691 Text { text, .. } => {
2692 let text = text.as_str().trim();
2693 if text.is_empty() {
2694 continue;
2695 }
2696 if is_in_include || is_in_dir {
2697 current_path = Some(text);
2698 }
2699 }
2700 Attribute { local, value, .. } => {
2701 if !is_in_include && !is_in_dir {
2702 continue;
2703 }
2704 if local.as_str() == ATTRIBUTE_PREFIX {
2706 current_prefix = Some(value.as_str());
2707 }
2708 }
2709 ElementEnd { end, .. } => {
2710 let end_tag = match end {
2711 xmlparser::ElementEnd::Close(_, a) => a,
2712 _ => continue,
2713 };
2714
2715 match end_tag.as_str() {
2716 TAG_INCLUDE => {
2717 if !is_in_include {
2718 continue;
2719 }
2720
2721 if let Some(current_path) = current_path.as_ref() {
2722 paths_to_visit.push((
2723 current_prefix.map(ToOwned::to_owned),
2724 PathBuf::from(*current_path),
2725 ));
2726 }
2727 }
2728 TAG_DIR => {
2729 if !is_in_dir {
2730 continue;
2731 }
2732
2733 if let Some(current_path) = current_path.as_ref() {
2734 font_paths.push((
2735 current_prefix.map(ToOwned::to_owned),
2736 (*current_path).to_owned(),
2737 ));
2738 }
2739 }
2740 _ => continue,
2741 }
2742
2743 is_in_include = false;
2744 is_in_dir = false;
2745 current_path = None;
2746 current_prefix = None;
2747 }
2748 _ => {}
2749 }
2750 }
2751
2752 Some(())
2753}
2754
2755#[cfg(all(feature = "std", feature = "parsing"))]
2757pub(crate) fn FcParseFont(filepath: &PathBuf) -> Option<Vec<(FcPattern, FcFontPath)>> {
2758 use allsorts::{
2759 binary::read::ReadScope,
2760 font_data::FontData,
2761 get_name::fontcode_get_name,
2762 post::PostTable,
2763 tables::{
2764 os2::Os2, FontTableProvider, HeadTable, HheaTable, HmtxTable, MaxpTable, NameTable,
2765 },
2766 tag,
2767 };
2768 #[cfg(all(not(target_family = "wasm"), feature = "std"))]
2769 use mmapio::MmapOptions;
2770 use std::collections::BTreeSet;
2771 use std::fs::File;
2772
2773 const FONT_SPECIFIER_NAME_ID: u16 = 4;
2774 const FONT_SPECIFIER_FAMILY_ID: u16 = 1;
2775
2776 let file = File::open(filepath).ok()?;
2778
2779 #[cfg(all(not(target_family = "wasm"), feature = "std"))]
2780 let font_bytes = unsafe { MmapOptions::new().map(&file).ok()? };
2781
2782 #[cfg(not(all(not(target_family = "wasm"), feature = "std")))]
2783 let font_bytes = std::fs::read(filepath).ok()?;
2784
2785 let max_fonts = if font_bytes.len() >= 12 && &font_bytes[0..4] == b"ttcf" {
2786 let num_fonts =
2788 u32::from_be_bytes([font_bytes[8], font_bytes[9], font_bytes[10], font_bytes[11]]);
2789 std::cmp::min(num_fonts as usize, 100)
2791 } else {
2792 1
2794 };
2795
2796 let scope = ReadScope::new(&font_bytes[..]);
2797 let font_file = scope.read::<FontData<'_>>().ok()?;
2798
2799 let mut results = Vec::new();
2801
2802 for font_index in 0..max_fonts {
2803 let provider = font_file.table_provider(font_index).ok()?;
2804 let head_data = provider.table_data(tag::HEAD).ok()??.into_owned();
2805 let head_table = ReadScope::new(&head_data).read::<HeadTable>().ok()?;
2806
2807 let is_bold = head_table.is_bold();
2808 let is_italic = head_table.is_italic();
2809 let mut detected_monospace = None;
2810
2811 let post_data = provider.table_data(tag::POST).ok()??;
2812 if let Ok(post_table) = ReadScope::new(&post_data).read::<PostTable>() {
2813 detected_monospace = Some(post_table.header.is_fixed_pitch != 0);
2815 }
2816
2817 let os2_data = provider.table_data(tag::OS_2).ok()??;
2819 let os2_table = ReadScope::new(&os2_data)
2820 .read_dep::<Os2>(os2_data.len())
2821 .ok()?;
2822
2823 let is_oblique = os2_table
2825 .fs_selection
2826 .contains(allsorts::tables::os2::FsSelection::OBLIQUE);
2827 let weight = FcWeight::from_u16(os2_table.us_weight_class);
2828 let stretch = FcStretch::from_u16(os2_table.us_width_class);
2829
2830 let mut unicode_ranges = Vec::new();
2834
2835 let ranges = [
2837 os2_table.ul_unicode_range1,
2838 os2_table.ul_unicode_range2,
2839 os2_table.ul_unicode_range3,
2840 os2_table.ul_unicode_range4,
2841 ];
2842
2843 let range_mappings = [
2846 (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), ];
2982
2983 for (range_idx, bit_pos, start, end) in range_mappings.iter().map(|&(bit, start, end)| {
2984 let range_idx = bit / 32;
2985 let bit_pos = bit % 32;
2986 (range_idx, bit_pos, start, end)
2987 }) {
2988 if range_idx < 4 && (ranges[range_idx] & (1 << bit_pos)) != 0 {
2989 unicode_ranges.push(UnicodeRange { start, end });
2990 }
2991 }
2992
2993 unicode_ranges = verify_unicode_ranges_with_cmap(&provider, unicode_ranges);
2997
2998 if unicode_ranges.is_empty() {
3000 if let Some(cmap_ranges) = analyze_cmap_coverage(&provider) {
3001 unicode_ranges = cmap_ranges;
3002 }
3003 }
3004
3005 if detected_monospace.is_none() {
3007 if os2_table.panose[0] == 2 {
3009 detected_monospace = Some(os2_table.panose[3] == 9); } else {
3012 let hhea_data = provider.table_data(tag::HHEA).ok()??;
3013 let hhea_table = ReadScope::new(&hhea_data).read::<HheaTable>().ok()?;
3014 let maxp_data = provider.table_data(tag::MAXP).ok()??;
3015 let maxp_table = ReadScope::new(&maxp_data).read::<MaxpTable>().ok()?;
3016 let hmtx_data = provider.table_data(tag::HMTX).ok()??;
3017 let hmtx_table = ReadScope::new(&hmtx_data)
3018 .read_dep::<HmtxTable<'_>>((
3019 usize::from(maxp_table.num_glyphs),
3020 usize::from(hhea_table.num_h_metrics),
3021 ))
3022 .ok()?;
3023
3024 let mut monospace = true;
3025 let mut last_advance = 0;
3026 for i in 0..hhea_table.num_h_metrics as usize {
3027 let advance = hmtx_table.h_metrics.read_item(i).ok()?.advance_width;
3028 if i > 0 && advance != last_advance {
3029 monospace = false;
3030 break;
3031 }
3032 last_advance = advance;
3033 }
3034
3035 detected_monospace = Some(monospace);
3036 }
3037 }
3038
3039 let is_monospace = detected_monospace.unwrap_or(false);
3040
3041 let name_data = provider.table_data(tag::NAME).ok()??.into_owned();
3042 let name_table = ReadScope::new(&name_data).read::<NameTable>().ok()?;
3043
3044 let mut f_family = None;
3046
3047 let patterns = name_table
3048 .name_records
3049 .iter()
3050 .filter_map(|name_record| {
3051 let name_id = name_record.name_id;
3052 if name_id == FONT_SPECIFIER_FAMILY_ID {
3053 let family = fontcode_get_name(&name_data, FONT_SPECIFIER_FAMILY_ID).ok()??;
3054 f_family = Some(family);
3055 None
3056 } else if name_id == FONT_SPECIFIER_NAME_ID {
3057 let family = f_family.as_ref()?;
3058 let name = fontcode_get_name(&name_data, FONT_SPECIFIER_NAME_ID).ok()??;
3059 if name.to_bytes().is_empty() {
3060 None
3061 } else {
3062 let mut metadata = FcFontMetadata::default();
3064
3065 const NAME_ID_COPYRIGHT: u16 = 0;
3066 const NAME_ID_FAMILY: u16 = 1;
3067 const NAME_ID_SUBFAMILY: u16 = 2;
3068 const NAME_ID_UNIQUE_ID: u16 = 3;
3069 const NAME_ID_FULL_NAME: u16 = 4;
3070 const NAME_ID_VERSION: u16 = 5;
3071 const NAME_ID_POSTSCRIPT_NAME: u16 = 6;
3072 const NAME_ID_TRADEMARK: u16 = 7;
3073 const NAME_ID_MANUFACTURER: u16 = 8;
3074 const NAME_ID_DESIGNER: u16 = 9;
3075 const NAME_ID_DESCRIPTION: u16 = 10;
3076 const NAME_ID_VENDOR_URL: u16 = 11;
3077 const NAME_ID_DESIGNER_URL: u16 = 12;
3078 const NAME_ID_LICENSE: u16 = 13;
3079 const NAME_ID_LICENSE_URL: u16 = 14;
3080 const NAME_ID_PREFERRED_FAMILY: u16 = 16;
3081 const NAME_ID_PREFERRED_SUBFAMILY: u16 = 17;
3082
3083 metadata.copyright = get_name_string(&name_data, NAME_ID_COPYRIGHT);
3085 metadata.font_family = get_name_string(&name_data, NAME_ID_FAMILY);
3086 metadata.font_subfamily = get_name_string(&name_data, NAME_ID_SUBFAMILY);
3087 metadata.full_name = get_name_string(&name_data, NAME_ID_FULL_NAME);
3088 metadata.unique_id = get_name_string(&name_data, NAME_ID_UNIQUE_ID);
3089 metadata.version = get_name_string(&name_data, NAME_ID_VERSION);
3090 metadata.postscript_name =
3091 get_name_string(&name_data, NAME_ID_POSTSCRIPT_NAME);
3092 metadata.trademark = get_name_string(&name_data, NAME_ID_TRADEMARK);
3093 metadata.manufacturer = get_name_string(&name_data, NAME_ID_MANUFACTURER);
3094 metadata.designer = get_name_string(&name_data, NAME_ID_DESIGNER);
3095 metadata.id_description = get_name_string(&name_data, NAME_ID_DESCRIPTION);
3096 metadata.designer_url = get_name_string(&name_data, NAME_ID_DESIGNER_URL);
3097 metadata.manufacturer_url = get_name_string(&name_data, NAME_ID_VENDOR_URL);
3098 metadata.license = get_name_string(&name_data, NAME_ID_LICENSE);
3099 metadata.license_url = get_name_string(&name_data, NAME_ID_LICENSE_URL);
3100 metadata.preferred_family =
3101 get_name_string(&name_data, NAME_ID_PREFERRED_FAMILY);
3102 metadata.preferred_subfamily =
3103 get_name_string(&name_data, NAME_ID_PREFERRED_SUBFAMILY);
3104
3105 let mut name = String::from_utf8_lossy(name.to_bytes()).to_string();
3106 let mut family = String::from_utf8_lossy(family.as_bytes()).to_string();
3107 if name.starts_with(".") {
3108 name = name[1..].to_string();
3109 }
3110 if family.starts_with(".") {
3111 family = family[1..].to_string();
3112 }
3113 Some((
3114 FcPattern {
3115 name: Some(name),
3116 family: Some(family),
3117 bold: if is_bold {
3118 PatternMatch::True
3119 } else {
3120 PatternMatch::False
3121 },
3122 italic: if is_italic {
3123 PatternMatch::True
3124 } else {
3125 PatternMatch::False
3126 },
3127 oblique: if is_oblique {
3128 PatternMatch::True
3129 } else {
3130 PatternMatch::False
3131 },
3132 monospace: if is_monospace {
3133 PatternMatch::True
3134 } else {
3135 PatternMatch::False
3136 },
3137 condensed: if stretch <= FcStretch::Condensed {
3138 PatternMatch::True
3139 } else {
3140 PatternMatch::False
3141 },
3142 weight,
3143 stretch,
3144 unicode_ranges: unicode_ranges.clone(),
3145 metadata,
3146 },
3147 font_index,
3148 ))
3149 }
3150 } else {
3151 None
3152 }
3153 })
3154 .collect::<BTreeSet<_>>();
3155
3156 results.extend(patterns.into_iter().map(|(pat, index)| {
3157 (
3158 pat,
3159 FcFontPath {
3160 path: filepath.to_string_lossy().to_string(),
3161 font_index: index,
3162 },
3163 )
3164 }));
3165 }
3166
3167 if results.is_empty() {
3168 None
3169 } else {
3170 Some(results)
3171 }
3172}
3173
3174#[cfg(all(feature = "std", feature = "parsing"))]
3200#[allow(non_snake_case)]
3201pub fn FcParseFontBytes(font_bytes: &[u8], font_id: &str) -> Option<Vec<(FcPattern, FcFont)>> {
3202 FcParseFontBytesInner(font_bytes, font_id)
3203}
3204
3205#[cfg(all(feature = "std", feature = "parsing"))]
3208fn FcParseFontBytesInner(font_bytes: &[u8], font_id: &str) -> Option<Vec<(FcPattern, FcFont)>> {
3209 use allsorts::{
3210 binary::read::ReadScope,
3211 font_data::FontData,
3212 get_name::fontcode_get_name,
3213 post::PostTable,
3214 tables::{
3215 os2::Os2, FontTableProvider, HeadTable, HheaTable, HmtxTable, MaxpTable, NameTable,
3216 },
3217 tag,
3218 };
3219 use std::collections::BTreeSet;
3220
3221 const FONT_SPECIFIER_NAME_ID: u16 = 4;
3222 const FONT_SPECIFIER_FAMILY_ID: u16 = 1;
3223
3224 let max_fonts = if font_bytes.len() >= 12 && &font_bytes[0..4] == b"ttcf" {
3225 let num_fonts =
3226 u32::from_be_bytes([font_bytes[8], font_bytes[9], font_bytes[10], font_bytes[11]]);
3227 std::cmp::min(num_fonts as usize, 100)
3228 } else {
3229 1
3230 };
3231
3232 let scope = ReadScope::new(font_bytes);
3233 let font_file = scope.read::<FontData<'_>>().ok()?;
3234
3235 let mut results = Vec::new();
3236
3237 for font_index in 0..max_fonts {
3238 let provider = font_file.table_provider(font_index).ok()?;
3239 let head_data = provider.table_data(tag::HEAD).ok()??.into_owned();
3240 let head_table = ReadScope::new(&head_data).read::<HeadTable>().ok()?;
3241
3242 let is_bold = head_table.is_bold();
3243 let is_italic = head_table.is_italic();
3244 let mut detected_monospace = None;
3245
3246 let post_data = provider.table_data(tag::POST).ok()??;
3247 if let Ok(post_table) = ReadScope::new(&post_data).read::<PostTable>() {
3248 detected_monospace = Some(post_table.header.is_fixed_pitch != 0);
3249 }
3250
3251 let os2_data = provider.table_data(tag::OS_2).ok()??;
3252 let os2_table = ReadScope::new(&os2_data)
3253 .read_dep::<Os2>(os2_data.len())
3254 .ok()?;
3255
3256 let is_oblique = os2_table
3257 .fs_selection
3258 .contains(allsorts::tables::os2::FsSelection::OBLIQUE);
3259 let weight = FcWeight::from_u16(os2_table.us_weight_class);
3260 let stretch = FcStretch::from_u16(os2_table.us_width_class);
3261
3262 let mut unicode_ranges = Vec::new();
3263 let ranges = [
3264 os2_table.ul_unicode_range1,
3265 os2_table.ul_unicode_range2,
3266 os2_table.ul_unicode_range3,
3267 os2_table.ul_unicode_range4,
3268 ];
3269
3270 let range_mappings = [
3272 (0, 0x0000u32, 0x007Fu32),
3273 (1, 0x0080, 0x00FF),
3274 (2, 0x0100, 0x017F),
3275 (3, 0x0180, 0x024F),
3276 (4, 0x0250, 0x02AF),
3277 (5, 0x02B0, 0x02FF),
3278 (6, 0x0300, 0x036F),
3279 (7, 0x0370, 0x03FF),
3280 (8, 0x2C80, 0x2CFF),
3281 (9, 0x0400, 0x04FF),
3282 (10, 0x0530, 0x058F),
3283 (11, 0x0590, 0x05FF),
3284 (12, 0x0600, 0x06FF),
3285 (31, 0x2000, 0x206F),
3286 (48, 0x3000, 0x303F),
3287 (49, 0x3040, 0x309F),
3288 (50, 0x30A0, 0x30FF),
3289 (59, 0x4E00, 0x9FFF),
3290 (62, 0xAC00, 0xD7AF),
3291 ];
3292
3293 for &(bit, start, end) in &range_mappings {
3294 let range_idx = (bit / 32) as usize;
3295 let bit_pos = bit % 32;
3296 if range_idx < 4 && (ranges[range_idx] & (1 << bit_pos)) != 0 {
3297 unicode_ranges.push(UnicodeRange { start, end });
3298 }
3299 }
3300
3301 unicode_ranges = verify_unicode_ranges_with_cmap(&provider, unicode_ranges);
3302
3303 if unicode_ranges.is_empty() {
3304 if let Some(cmap_ranges) = analyze_cmap_coverage(&provider) {
3305 unicode_ranges = cmap_ranges;
3306 }
3307 }
3308
3309 if detected_monospace.is_none() {
3310 if os2_table.panose[0] == 2 {
3311 detected_monospace = Some(os2_table.panose[3] == 9);
3312 } else if let (Ok(Some(hhea_data)), Ok(Some(maxp_data)), Ok(Some(hmtx_data))) = (
3313 provider.table_data(tag::HHEA),
3314 provider.table_data(tag::MAXP),
3315 provider.table_data(tag::HMTX),
3316 ) {
3317 if let (Ok(hhea_table), Ok(maxp_table)) = (
3318 ReadScope::new(&hhea_data).read::<HheaTable>(),
3319 ReadScope::new(&maxp_data).read::<MaxpTable>(),
3320 ) {
3321 if let Ok(hmtx_table) = ReadScope::new(&hmtx_data).read_dep::<HmtxTable<'_>>((
3322 usize::from(maxp_table.num_glyphs),
3323 usize::from(hhea_table.num_h_metrics),
3324 )) {
3325 let mut monospace = true;
3326 let mut last_advance = 0;
3327 for i in 0..hhea_table.num_h_metrics as usize {
3328 if let Ok(metric) = hmtx_table.h_metrics.read_item(i) {
3329 if i > 0 && metric.advance_width != last_advance {
3330 monospace = false;
3331 break;
3332 }
3333 last_advance = metric.advance_width;
3334 }
3335 }
3336 detected_monospace = Some(monospace);
3337 }
3338 }
3339 }
3340 }
3341
3342 let is_monospace = detected_monospace.unwrap_or(false);
3343
3344 let name_data = provider.table_data(tag::NAME).ok()??.into_owned();
3345 let name_table = ReadScope::new(&name_data).read::<NameTable>().ok()?;
3346
3347 let mut f_family = None;
3348
3349 let patterns: BTreeSet<_> = name_table
3350 .name_records
3351 .iter()
3352 .filter_map(|name_record| {
3353 let name_id = name_record.name_id;
3354 if name_id == FONT_SPECIFIER_FAMILY_ID {
3355 if let Ok(Some(family)) = fontcode_get_name(&name_data, FONT_SPECIFIER_FAMILY_ID) {
3356 f_family = Some(family);
3357 }
3358 None
3359 } else if name_id == FONT_SPECIFIER_NAME_ID {
3360 let family = f_family.as_ref()?;
3361 let name = fontcode_get_name(&name_data, FONT_SPECIFIER_NAME_ID).ok()??;
3362 if name.to_bytes().is_empty() {
3363 None
3364 } else {
3365 let mut name_str = String::from_utf8_lossy(name.to_bytes()).to_string();
3366 let mut family_str = String::from_utf8_lossy(family.as_bytes()).to_string();
3367 if name_str.starts_with('.') {
3368 name_str = name_str[1..].to_string();
3369 }
3370 if family_str.starts_with('.') {
3371 family_str = family_str[1..].to_string();
3372 }
3373
3374 Some((
3375 FcPattern {
3376 name: Some(name_str),
3377 family: Some(family_str),
3378 bold: if is_bold { PatternMatch::True } else { PatternMatch::False },
3379 italic: if is_italic { PatternMatch::True } else { PatternMatch::False },
3380 oblique: if is_oblique { PatternMatch::True } else { PatternMatch::False },
3381 monospace: if is_monospace { PatternMatch::True } else { PatternMatch::False },
3382 condensed: if stretch <= FcStretch::Condensed { PatternMatch::True } else { PatternMatch::False },
3383 weight,
3384 stretch,
3385 unicode_ranges: unicode_ranges.clone(),
3386 metadata: FcFontMetadata::default(),
3387 },
3388 font_index,
3389 ))
3390 }
3391 } else {
3392 None
3393 }
3394 })
3395 .collect();
3396
3397 results.extend(patterns.into_iter().map(|(pat, idx)| {
3398 (
3399 pat,
3400 FcFont {
3401 bytes: font_bytes.to_vec(),
3402 font_index: idx,
3403 id: font_id.to_string(),
3404 },
3405 )
3406 }));
3407 }
3408
3409 if results.is_empty() {
3410 None
3411 } else {
3412 Some(results)
3413 }
3414}
3415
3416#[cfg(all(feature = "std", feature = "parsing"))]
3417fn FcScanDirectoriesInner(paths: &[(Option<String>, String)]) -> Vec<(FcPattern, FcFontPath)> {
3418 #[cfg(feature = "multithreading")]
3419 {
3420 use rayon::prelude::*;
3421
3422 paths
3424 .par_iter()
3425 .filter_map(|(prefix, p)| {
3426 if let Some(path) = process_path(prefix, PathBuf::from(p), false) {
3427 Some(FcScanSingleDirectoryRecursive(path))
3428 } else {
3429 None
3430 }
3431 })
3432 .flatten()
3433 .collect()
3434 }
3435 #[cfg(not(feature = "multithreading"))]
3436 {
3437 paths
3438 .iter()
3439 .filter_map(|(prefix, p)| {
3440 if let Some(path) = process_path(prefix, PathBuf::from(p), false) {
3441 Some(FcScanSingleDirectoryRecursive(path))
3442 } else {
3443 None
3444 }
3445 })
3446 .flatten()
3447 .collect()
3448 }
3449}
3450
3451#[cfg(all(feature = "std", feature = "parsing"))]
3452fn FcScanSingleDirectoryRecursive(dir: PathBuf) -> Vec<(FcPattern, FcFontPath)> {
3453 let mut files_to_parse = Vec::new();
3454 let mut dirs_to_parse = vec![dir];
3455
3456 'outer: loop {
3457 let mut new_dirs_to_parse = Vec::new();
3458
3459 'inner: for dir in dirs_to_parse.clone() {
3460 let dir = match std::fs::read_dir(dir) {
3461 Ok(o) => o,
3462 Err(_) => continue 'inner,
3463 };
3464
3465 for (path, pathbuf) in dir.filter_map(|entry| {
3466 let entry = entry.ok()?;
3467 let path = entry.path();
3468 let pathbuf = path.to_path_buf();
3469 Some((path, pathbuf))
3470 }) {
3471 if path.is_dir() {
3472 new_dirs_to_parse.push(pathbuf);
3473 } else {
3474 files_to_parse.push(pathbuf);
3475 }
3476 }
3477 }
3478
3479 if new_dirs_to_parse.is_empty() {
3480 break 'outer;
3481 } else {
3482 dirs_to_parse = new_dirs_to_parse;
3483 }
3484 }
3485
3486 FcParseFontFiles(&files_to_parse)
3487}
3488
3489#[cfg(all(feature = "std", feature = "parsing"))]
3490fn FcParseFontFiles(files_to_parse: &[PathBuf]) -> Vec<(FcPattern, FcFontPath)> {
3491 let result = {
3492 #[cfg(feature = "multithreading")]
3493 {
3494 use rayon::prelude::*;
3495
3496 files_to_parse
3497 .par_iter()
3498 .filter_map(|file| FcParseFont(file))
3499 .collect::<Vec<Vec<_>>>()
3500 }
3501 #[cfg(not(feature = "multithreading"))]
3502 {
3503 files_to_parse
3504 .iter()
3505 .filter_map(|file| FcParseFont(file))
3506 .collect::<Vec<Vec<_>>>()
3507 }
3508 };
3509
3510 result.into_iter().flat_map(|f| f.into_iter()).collect()
3511}
3512
3513#[cfg(all(feature = "std", feature = "parsing"))]
3514fn process_path(
3518 prefix: &Option<String>,
3519 mut path: PathBuf,
3520 is_include_path: bool,
3521) -> Option<PathBuf> {
3522 use std::env::var;
3523
3524 const HOME_SHORTCUT: &str = "~";
3525 const CWD_PATH: &str = ".";
3526
3527 const HOME_ENV_VAR: &str = "HOME";
3528 const XDG_CONFIG_HOME_ENV_VAR: &str = "XDG_CONFIG_HOME";
3529 const XDG_CONFIG_HOME_DEFAULT_PATH_SUFFIX: &str = ".config";
3530 const XDG_DATA_HOME_ENV_VAR: &str = "XDG_DATA_HOME";
3531 const XDG_DATA_HOME_DEFAULT_PATH_SUFFIX: &str = ".local/share";
3532
3533 const PREFIX_CWD: &str = "cwd";
3534 const PREFIX_DEFAULT: &str = "default";
3535 const PREFIX_XDG: &str = "xdg";
3536
3537 fn get_home_value() -> Option<PathBuf> {
3539 var(HOME_ENV_VAR).ok().map(PathBuf::from)
3540 }
3541 fn get_xdg_config_home_value() -> Option<PathBuf> {
3542 var(XDG_CONFIG_HOME_ENV_VAR)
3543 .ok()
3544 .map(PathBuf::from)
3545 .or_else(|| {
3546 get_home_value()
3547 .map(|home_path| home_path.join(XDG_CONFIG_HOME_DEFAULT_PATH_SUFFIX))
3548 })
3549 }
3550 fn get_xdg_data_home_value() -> Option<PathBuf> {
3551 var(XDG_DATA_HOME_ENV_VAR)
3552 .ok()
3553 .map(PathBuf::from)
3554 .or_else(|| {
3555 get_home_value().map(|home_path| home_path.join(XDG_DATA_HOME_DEFAULT_PATH_SUFFIX))
3556 })
3557 }
3558
3559 if path.starts_with(HOME_SHORTCUT) {
3561 if let Some(home_path) = get_home_value() {
3562 path = home_path.join(
3563 path.strip_prefix(HOME_SHORTCUT)
3564 .expect("already checked that it starts with the prefix"),
3565 );
3566 } else {
3567 return None;
3568 }
3569 }
3570
3571 match prefix {
3573 Some(prefix) => match prefix.as_str() {
3574 PREFIX_CWD | PREFIX_DEFAULT => {
3575 let mut new_path = PathBuf::from(CWD_PATH);
3576 new_path.push(path);
3577
3578 Some(new_path)
3579 }
3580 PREFIX_XDG => {
3581 if is_include_path {
3582 get_xdg_config_home_value()
3583 .map(|xdg_config_home_path| xdg_config_home_path.join(path))
3584 } else {
3585 get_xdg_data_home_value()
3586 .map(|xdg_data_home_path| xdg_data_home_path.join(path))
3587 }
3588 }
3589 _ => None, },
3591 None => Some(path),
3592 }
3593}
3594
3595#[cfg(all(feature = "std", feature = "parsing"))]
3597fn get_name_string(name_data: &[u8], name_id: u16) -> Option<String> {
3598 fontcode_get_name(name_data, name_id)
3599 .ok()
3600 .flatten()
3601 .map(|name| String::from_utf8_lossy(name.to_bytes()).to_string())
3602}
3603
3604#[cfg(all(feature = "std", feature = "parsing"))]
3608fn get_verification_codepoints(start: u32, end: u32) -> Vec<u32> {
3609 match start {
3610 0x0000 => vec!['A' as u32, 'M' as u32, 'Z' as u32, 'a' as u32, 'm' as u32, 'z' as u32],
3612 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], _ => {
3688 let range_size = end - start;
3689 if range_size > 20 {
3690 vec![
3691 start + range_size / 5,
3692 start + 2 * range_size / 5,
3693 start + 3 * range_size / 5,
3694 start + 4 * range_size / 5,
3695 ]
3696 } else {
3697 vec![start, start + range_size / 2]
3698 }
3699 }
3700 }
3701}
3702
3703#[cfg(all(feature = "std", feature = "parsing"))]
3706fn verify_unicode_ranges_with_cmap(
3707 provider: &impl FontTableProvider,
3708 os2_ranges: Vec<UnicodeRange>
3709) -> Vec<UnicodeRange> {
3710 use allsorts::tables::cmap::{Cmap, CmapSubtable, PlatformId, EncodingId};
3711
3712 if os2_ranges.is_empty() {
3713 return Vec::new();
3714 }
3715
3716 let cmap_data = match provider.table_data(tag::CMAP) {
3718 Ok(Some(data)) => data,
3719 _ => return os2_ranges, };
3721
3722 let cmap = match ReadScope::new(&cmap_data).read::<Cmap<'_>>() {
3723 Ok(c) => c,
3724 Err(_) => return os2_ranges,
3725 };
3726
3727 let encoding_record = cmap.find_subtable(PlatformId::UNICODE, EncodingId(3))
3729 .or_else(|| cmap.find_subtable(PlatformId::UNICODE, EncodingId(4)))
3730 .or_else(|| cmap.find_subtable(PlatformId::WINDOWS, EncodingId(1)))
3731 .or_else(|| cmap.find_subtable(PlatformId::WINDOWS, EncodingId(10)))
3732 .or_else(|| cmap.find_subtable(PlatformId::UNICODE, EncodingId(0)))
3733 .or_else(|| cmap.find_subtable(PlatformId::UNICODE, EncodingId(1)));
3734
3735 let encoding_record = match encoding_record {
3736 Some(r) => r,
3737 None => return os2_ranges, };
3739
3740 let cmap_subtable = match ReadScope::new(&cmap_data)
3741 .offset(encoding_record.offset as usize)
3742 .read::<CmapSubtable<'_>>()
3743 {
3744 Ok(st) => st,
3745 Err(_) => return os2_ranges,
3746 };
3747
3748 let mut verified_ranges = Vec::new();
3750
3751 for range in os2_ranges {
3752 let test_codepoints = get_verification_codepoints(range.start, range.end);
3753
3754 let required_hits = (test_codepoints.len() + 1) / 2; let mut hits = 0;
3758
3759 for cp in test_codepoints {
3760 if cp >= range.start && cp <= range.end {
3761 if let Ok(Some(gid)) = cmap_subtable.map_glyph(cp) {
3762 if gid != 0 {
3763 hits += 1;
3764 if hits >= required_hits {
3765 break;
3766 }
3767 }
3768 }
3769 }
3770 }
3771
3772 if hits >= required_hits {
3773 verified_ranges.push(range);
3774 }
3775 }
3776
3777 verified_ranges
3778}
3779
3780#[cfg(all(feature = "std", feature = "parsing"))]
3783fn analyze_cmap_coverage(provider: &impl FontTableProvider) -> Option<Vec<UnicodeRange>> {
3784 use allsorts::tables::cmap::{Cmap, CmapSubtable, PlatformId, EncodingId};
3785
3786 let cmap_data = provider.table_data(tag::CMAP).ok()??;
3787 let cmap = ReadScope::new(&cmap_data).read::<Cmap<'_>>().ok()?;
3788
3789 let encoding_record = cmap.find_subtable(PlatformId::UNICODE, EncodingId(3))
3790 .or_else(|| cmap.find_subtable(PlatformId::UNICODE, EncodingId(4)))
3791 .or_else(|| cmap.find_subtable(PlatformId::WINDOWS, EncodingId(1)))
3792 .or_else(|| cmap.find_subtable(PlatformId::WINDOWS, EncodingId(10)))
3793 .or_else(|| cmap.find_subtable(PlatformId::UNICODE, EncodingId(0)))
3794 .or_else(|| cmap.find_subtable(PlatformId::UNICODE, EncodingId(1)))?;
3795
3796 let cmap_subtable = ReadScope::new(&cmap_data)
3797 .offset(encoding_record.offset as usize)
3798 .read::<CmapSubtable<'_>>()
3799 .ok()?;
3800
3801 let blocks_to_check: &[(u32, u32)] = &[
3803 (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), ];
3854
3855 let mut ranges = Vec::new();
3856
3857 for &(start, end) in blocks_to_check {
3858 let test_codepoints = get_verification_codepoints(start, end);
3859 let required_hits = (test_codepoints.len() + 1) / 2;
3860 let mut hits = 0;
3861
3862 for cp in test_codepoints {
3863 if let Ok(Some(gid)) = cmap_subtable.map_glyph(cp) {
3864 if gid != 0 {
3865 hits += 1;
3866 if hits >= required_hits {
3867 break;
3868 }
3869 }
3870 }
3871 }
3872
3873 if hits >= required_hits {
3874 ranges.push(UnicodeRange { start, end });
3875 }
3876 }
3877
3878 if ranges.is_empty() {
3879 None
3880 } else {
3881 Some(ranges)
3882 }
3883}
3884
3885#[cfg(feature = "parsing")]
3887#[allow(dead_code)]
3888fn extract_unicode_ranges(os2_table: &Os2) -> Vec<UnicodeRange> {
3889 let mut unicode_ranges = Vec::new();
3890
3891 let ranges = [
3893 os2_table.ul_unicode_range1,
3894 os2_table.ul_unicode_range2,
3895 os2_table.ul_unicode_range3,
3896 os2_table.ul_unicode_range4,
3897 ];
3898
3899 let range_mappings = [
3902 (0, 0x0000, 0x007F), (1, 0x0080, 0x00FF), (2, 0x0100, 0x017F), (7, 0x0370, 0x03FF), (9, 0x0400, 0x04FF), (29, 0x2000, 0x206F), (57, 0x4E00, 0x9FFF), ];
3911
3912 for (bit, start, end) in &range_mappings {
3913 let range_idx = bit / 32;
3914 let bit_pos = bit % 32;
3915
3916 if range_idx < 4 && (ranges[range_idx] & (1 << bit_pos)) != 0 {
3917 unicode_ranges.push(UnicodeRange {
3918 start: *start,
3919 end: *end,
3920 });
3921 }
3922 }
3923
3924 unicode_ranges
3925}
3926
3927#[cfg(feature = "parsing")]
3929#[allow(dead_code)]
3930fn detect_monospace(
3931 provider: &impl FontTableProvider,
3932 os2_table: &Os2,
3933 detected_monospace: Option<bool>,
3934) -> Option<bool> {
3935 if let Some(is_monospace) = detected_monospace {
3936 return Some(is_monospace);
3937 }
3938
3939 if os2_table.panose[0] == 2 {
3941 return Some(os2_table.panose[3] == 9); }
3944
3945 let hhea_data = provider.table_data(tag::HHEA).ok()??;
3947 let hhea_table = ReadScope::new(&hhea_data).read::<HheaTable>().ok()?;
3948 let maxp_data = provider.table_data(tag::MAXP).ok()??;
3949 let maxp_table = ReadScope::new(&maxp_data).read::<MaxpTable>().ok()?;
3950 let hmtx_data = provider.table_data(tag::HMTX).ok()??;
3951 let hmtx_table = ReadScope::new(&hmtx_data)
3952 .read_dep::<HmtxTable<'_>>((
3953 usize::from(maxp_table.num_glyphs),
3954 usize::from(hhea_table.num_h_metrics),
3955 ))
3956 .ok()?;
3957
3958 let mut monospace = true;
3959 let mut last_advance = 0;
3960
3961 for i in 0..hhea_table.num_h_metrics as usize {
3963 let advance = hmtx_table.h_metrics.read_item(i).ok()?.advance_width;
3964 if i > 0 && advance != last_advance {
3965 monospace = false;
3966 break;
3967 }
3968 last_advance = advance;
3969 }
3970
3971 Some(monospace)
3972}