1#![allow(non_snake_case)]
68
69extern crate alloc;
77
78use alloc::collections::btree_map::BTreeMap;
79use alloc::string::{String, ToString};
80use alloc::vec::Vec;
81#[cfg(all(feature = "std", feature = "parsing"))]
82use allsorts::binary::read::ReadScope;
83#[cfg(all(feature = "std", feature = "parsing"))]
84use allsorts::get_name::fontcode_get_name;
85#[cfg(all(feature = "std", feature = "parsing"))]
86use allsorts::tables::os2::Os2;
87#[cfg(all(feature = "std", feature = "parsing"))]
88use allsorts::tables::{FontTableProvider, HheaTable, HmtxTable, MaxpTable};
89#[cfg(all(feature = "std", feature = "parsing"))]
90use allsorts::tag;
91#[cfg(feature = "std")]
92use std::path::PathBuf;
93
94pub mod utils;
95#[cfg(feature = "std")]
96pub mod config;
97
98#[cfg(feature = "ffi")]
99pub mod ffi;
100
101#[cfg(feature = "async-registry")]
102pub mod scoring;
103#[cfg(feature = "async-registry")]
104pub mod registry;
105#[cfg(feature = "async-registry")]
106pub mod multithread;
107#[cfg(feature = "cache")]
108pub mod disk_cache;
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
112pub enum OperatingSystem {
113 Windows,
114 Linux,
115 MacOS,
116 Wasm,
117}
118
119impl OperatingSystem {
120 pub fn current() -> Self {
122 #[cfg(target_os = "windows")]
123 return OperatingSystem::Windows;
124
125 #[cfg(target_os = "linux")]
126 return OperatingSystem::Linux;
127
128 #[cfg(target_os = "macos")]
129 return OperatingSystem::MacOS;
130
131 #[cfg(target_family = "wasm")]
132 return OperatingSystem::Wasm;
133
134 #[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos", target_family = "wasm")))]
135 return OperatingSystem::Linux; }
137
138 pub fn get_serif_fonts(&self, unicode_ranges: &[UnicodeRange]) -> Vec<String> {
141 let has_cjk = has_cjk_ranges(unicode_ranges);
142 let has_arabic = has_arabic_ranges(unicode_ranges);
143 let _has_cyrillic = has_cyrillic_ranges(unicode_ranges);
144
145 match self {
146 OperatingSystem::Windows => {
147 let mut fonts = Vec::new();
148 if has_cjk {
149 fonts.extend_from_slice(&["MS Mincho", "SimSun", "MingLiU"]);
150 }
151 if has_arabic {
152 fonts.push("Traditional Arabic");
153 }
154 fonts.push("Times New Roman");
155 fonts.iter().map(|s| s.to_string()).collect()
156 }
157 OperatingSystem::Linux => {
158 let mut fonts = Vec::new();
159 if has_cjk {
160 fonts.extend_from_slice(&["Noto Serif CJK SC", "Noto Serif CJK JP", "Noto Serif CJK KR"]);
161 }
162 if has_arabic {
163 fonts.push("Noto Serif Arabic");
164 }
165 fonts.extend_from_slice(&[
166 "Times", "Times New Roman", "DejaVu Serif", "Free Serif",
167 "Noto Serif", "Bitstream Vera Serif", "Roman", "Regular"
168 ]);
169 fonts.iter().map(|s| s.to_string()).collect()
170 }
171 OperatingSystem::MacOS => {
172 let mut fonts = Vec::new();
173 if has_cjk {
174 fonts.extend_from_slice(&["Hiragino Mincho ProN", "STSong", "AppleMyungjo"]);
175 }
176 if has_arabic {
177 fonts.push("Geeza Pro");
178 }
179 fonts.extend_from_slice(&["Times", "New York", "Palatino"]);
180 fonts.iter().map(|s| s.to_string()).collect()
181 }
182 OperatingSystem::Wasm => Vec::new(),
183 }
184 }
185
186 pub fn get_sans_serif_fonts(&self, unicode_ranges: &[UnicodeRange]) -> Vec<String> {
189 let has_cjk = has_cjk_ranges(unicode_ranges);
190 let has_arabic = has_arabic_ranges(unicode_ranges);
191 let _has_cyrillic = has_cyrillic_ranges(unicode_ranges);
192 let has_hebrew = has_hebrew_ranges(unicode_ranges);
193 let has_thai = has_thai_ranges(unicode_ranges);
194
195 match self {
196 OperatingSystem::Windows => {
197 let mut fonts = Vec::new();
198 if has_cjk {
199 fonts.extend_from_slice(&["Microsoft YaHei", "MS Gothic", "Malgun Gothic", "SimHei"]);
200 }
201 if has_arabic {
202 fonts.push("Segoe UI Arabic");
203 }
204 if has_hebrew {
205 fonts.push("Segoe UI Hebrew");
206 }
207 if has_thai {
208 fonts.push("Leelawadee UI");
209 }
210 fonts.extend_from_slice(&["Segoe UI", "Tahoma", "Microsoft Sans Serif", "MS Sans Serif", "Helv"]);
211 fonts.iter().map(|s| s.to_string()).collect()
212 }
213 OperatingSystem::Linux => {
214 let mut fonts = Vec::new();
215 if has_cjk {
216 fonts.extend_from_slice(&[
217 "Noto Sans CJK SC", "Noto Sans CJK JP", "Noto Sans CJK KR",
218 "WenQuanYi Micro Hei", "Droid Sans Fallback"
219 ]);
220 }
221 if has_arabic {
222 fonts.push("Noto Sans Arabic");
223 }
224 if has_hebrew {
225 fonts.push("Noto Sans Hebrew");
226 }
227 if has_thai {
228 fonts.push("Noto Sans Thai");
229 }
230 fonts.extend_from_slice(&["Ubuntu", "Arial", "DejaVu Sans", "Noto Sans", "Liberation Sans"]);
231 fonts.iter().map(|s| s.to_string()).collect()
232 }
233 OperatingSystem::MacOS => {
234 let mut fonts = Vec::new();
235 if has_cjk {
236 fonts.extend_from_slice(&[
237 "Hiragino Sans", "Hiragino Kaku Gothic ProN",
238 "PingFang SC", "PingFang TC", "Apple SD Gothic Neo"
239 ]);
240 }
241 if has_arabic {
242 fonts.push("Geeza Pro");
243 }
244 if has_hebrew {
245 fonts.push("Arial Hebrew");
246 }
247 if has_thai {
248 fonts.push("Thonburi");
249 }
250 fonts.extend_from_slice(&["San Francisco", "Helvetica Neue", "Lucida Grande"]);
251 fonts.iter().map(|s| s.to_string()).collect()
252 }
253 OperatingSystem::Wasm => Vec::new(),
254 }
255 }
256
257 pub fn get_monospace_fonts(&self, unicode_ranges: &[UnicodeRange]) -> Vec<String> {
260 let has_cjk = has_cjk_ranges(unicode_ranges);
261
262 match self {
263 OperatingSystem::Windows => {
264 let mut fonts = Vec::new();
265 if has_cjk {
266 fonts.extend_from_slice(&["MS Gothic", "SimHei"]);
267 }
268 fonts.extend_from_slice(&["Segoe UI Mono", "Courier New", "Cascadia Code", "Cascadia Mono", "Consolas"]);
269 fonts.iter().map(|s| s.to_string()).collect()
270 }
271 OperatingSystem::Linux => {
272 let mut fonts = Vec::new();
273 if has_cjk {
274 fonts.extend_from_slice(&["Noto Sans Mono CJK SC", "Noto Sans Mono CJK JP", "WenQuanYi Zen Hei Mono"]);
275 }
276 fonts.extend_from_slice(&[
277 "Source Code Pro", "Cantarell", "DejaVu Sans Mono",
278 "Roboto Mono", "Ubuntu Monospace", "Droid Sans Mono"
279 ]);
280 fonts.iter().map(|s| s.to_string()).collect()
281 }
282 OperatingSystem::MacOS => {
283 let mut fonts = Vec::new();
284 if has_cjk {
285 fonts.extend_from_slice(&["Hiragino Sans", "PingFang SC"]);
286 }
287 fonts.extend_from_slice(&["SF Mono", "Menlo", "Monaco", "Courier", "Oxygen Mono", "Source Code Pro", "Fira Mono"]);
288 fonts.iter().map(|s| s.to_string()).collect()
289 }
290 OperatingSystem::Wasm => Vec::new(),
291 }
292 }
293
294 pub fn expand_generic_family(&self, family: &str, unicode_ranges: &[UnicodeRange]) -> Vec<String> {
298 match family.to_lowercase().as_str() {
299 "serif" => self.get_serif_fonts(unicode_ranges),
300 "sans-serif" => self.get_sans_serif_fonts(unicode_ranges),
301 "monospace" => self.get_monospace_fonts(unicode_ranges),
302 "cursive" | "fantasy" | "system-ui" => {
303 self.get_sans_serif_fonts(unicode_ranges)
305 }
306 _ => vec![family.to_string()],
307 }
308 }
309}
310
311pub fn expand_font_families(families: &[String], os: OperatingSystem, unicode_ranges: &[UnicodeRange]) -> Vec<String> {
315 let mut expanded = Vec::new();
316
317 for family in families {
318 expanded.extend(os.expand_generic_family(family, unicode_ranges));
319 }
320
321 expanded
322}
323
324#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
326#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
327pub struct FontId(pub u128);
328
329impl core::fmt::Debug for FontId {
330 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
331 core::fmt::Display::fmt(self, f)
332 }
333}
334
335impl core::fmt::Display for FontId {
336 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
337 let id = self.0;
338 write!(
339 f,
340 "{:08x}-{:04x}-{:04x}-{:04x}-{:012x}",
341 (id >> 96) & 0xFFFFFFFF,
342 (id >> 80) & 0xFFFF,
343 (id >> 64) & 0xFFFF,
344 (id >> 48) & 0xFFFF,
345 id & 0xFFFFFFFFFFFF
346 )
347 }
348}
349
350impl FontId {
351 pub fn new() -> Self {
353 use core::sync::atomic::{AtomicU64, Ordering};
354 static COUNTER: AtomicU64 = AtomicU64::new(1);
355 let id = COUNTER.fetch_add(1, Ordering::Relaxed) as u128;
356 FontId(id)
357 }
358}
359
360#[derive(Debug, Default, Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
362#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
363#[repr(C)]
364pub enum PatternMatch {
365 #[default]
367 DontCare,
368 True,
370 False,
372}
373
374impl PatternMatch {
375 fn needs_to_match(&self) -> bool {
376 matches!(self, PatternMatch::True | PatternMatch::False)
377 }
378
379 fn matches(&self, other: &PatternMatch) -> bool {
380 match (self, other) {
381 (PatternMatch::DontCare, _) => true,
382 (_, PatternMatch::DontCare) => true,
383 (a, b) => a == b,
384 }
385 }
386}
387
388#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
390#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
391#[repr(C)]
392pub enum FcWeight {
393 Thin = 100,
394 ExtraLight = 200,
395 Light = 300,
396 Normal = 400,
397 Medium = 500,
398 SemiBold = 600,
399 Bold = 700,
400 ExtraBold = 800,
401 Black = 900,
402}
403
404impl FcWeight {
405 pub fn from_u16(weight: u16) -> Self {
406 match weight {
407 0..=149 => FcWeight::Thin,
408 150..=249 => FcWeight::ExtraLight,
409 250..=349 => FcWeight::Light,
410 350..=449 => FcWeight::Normal,
411 450..=549 => FcWeight::Medium,
412 550..=649 => FcWeight::SemiBold,
413 650..=749 => FcWeight::Bold,
414 750..=849 => FcWeight::ExtraBold,
415 _ => FcWeight::Black,
416 }
417 }
418
419 pub fn find_best_match(&self, available: &[FcWeight]) -> Option<FcWeight> {
420 if available.is_empty() {
421 return None;
422 }
423
424 if available.contains(self) {
426 return Some(*self);
427 }
428
429 let self_value = *self as u16;
431
432 match *self {
433 FcWeight::Normal => {
434 if available.contains(&FcWeight::Medium) {
436 return Some(FcWeight::Medium);
437 }
438 for weight in &[FcWeight::Light, FcWeight::ExtraLight, FcWeight::Thin] {
440 if available.contains(weight) {
441 return Some(*weight);
442 }
443 }
444 for weight in &[
446 FcWeight::SemiBold,
447 FcWeight::Bold,
448 FcWeight::ExtraBold,
449 FcWeight::Black,
450 ] {
451 if available.contains(weight) {
452 return Some(*weight);
453 }
454 }
455 }
456 FcWeight::Medium => {
457 if available.contains(&FcWeight::Normal) {
459 return Some(FcWeight::Normal);
460 }
461 for weight in &[FcWeight::Light, FcWeight::ExtraLight, FcWeight::Thin] {
463 if available.contains(weight) {
464 return Some(*weight);
465 }
466 }
467 for weight in &[
469 FcWeight::SemiBold,
470 FcWeight::Bold,
471 FcWeight::ExtraBold,
472 FcWeight::Black,
473 ] {
474 if available.contains(weight) {
475 return Some(*weight);
476 }
477 }
478 }
479 FcWeight::Thin | FcWeight::ExtraLight | FcWeight::Light => {
480 let mut best_match = None;
482 let mut smallest_diff = u16::MAX;
483
484 for weight in available {
486 let weight_value = *weight as u16;
487 if weight_value <= self_value {
489 let diff = self_value - weight_value;
490 if diff < smallest_diff {
491 smallest_diff = diff;
492 best_match = Some(*weight);
493 }
494 }
495 }
496
497 if best_match.is_some() {
498 return best_match;
499 }
500
501 best_match = None;
503 smallest_diff = u16::MAX;
504
505 for weight in available {
506 let weight_value = *weight as u16;
507 if weight_value > self_value {
508 let diff = weight_value - self_value;
509 if diff < smallest_diff {
510 smallest_diff = diff;
511 best_match = Some(*weight);
512 }
513 }
514 }
515
516 return best_match;
517 }
518 FcWeight::SemiBold | FcWeight::Bold | FcWeight::ExtraBold | FcWeight::Black => {
519 let mut best_match = None;
521 let mut smallest_diff = u16::MAX;
522
523 for weight in available {
525 let weight_value = *weight as u16;
526 if weight_value >= self_value {
528 let diff = weight_value - self_value;
529 if diff < smallest_diff {
530 smallest_diff = diff;
531 best_match = Some(*weight);
532 }
533 }
534 }
535
536 if best_match.is_some() {
537 return best_match;
538 }
539
540 best_match = None;
542 smallest_diff = u16::MAX;
543
544 for weight in available {
545 let weight_value = *weight as u16;
546 if weight_value < self_value {
547 let diff = self_value - weight_value;
548 if diff < smallest_diff {
549 smallest_diff = diff;
550 best_match = Some(*weight);
551 }
552 }
553 }
554
555 return best_match;
556 }
557 }
558
559 Some(available[0])
561 }
562}
563
564impl Default for FcWeight {
565 fn default() -> Self {
566 FcWeight::Normal
567 }
568}
569
570#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
572#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
573#[repr(C)]
574pub enum FcStretch {
575 UltraCondensed = 1,
576 ExtraCondensed = 2,
577 Condensed = 3,
578 SemiCondensed = 4,
579 Normal = 5,
580 SemiExpanded = 6,
581 Expanded = 7,
582 ExtraExpanded = 8,
583 UltraExpanded = 9,
584}
585
586impl FcStretch {
587 pub fn is_condensed(&self) -> bool {
588 use self::FcStretch::*;
589 match self {
590 UltraCondensed => true,
591 ExtraCondensed => true,
592 Condensed => true,
593 SemiCondensed => true,
594 Normal => false,
595 SemiExpanded => false,
596 Expanded => false,
597 ExtraExpanded => false,
598 UltraExpanded => false,
599 }
600 }
601 pub fn from_u16(width_class: u16) -> Self {
602 match width_class {
603 1 => FcStretch::UltraCondensed,
604 2 => FcStretch::ExtraCondensed,
605 3 => FcStretch::Condensed,
606 4 => FcStretch::SemiCondensed,
607 5 => FcStretch::Normal,
608 6 => FcStretch::SemiExpanded,
609 7 => FcStretch::Expanded,
610 8 => FcStretch::ExtraExpanded,
611 9 => FcStretch::UltraExpanded,
612 _ => FcStretch::Normal,
613 }
614 }
615
616 pub fn find_best_match(&self, available: &[FcStretch]) -> Option<FcStretch> {
618 if available.is_empty() {
619 return None;
620 }
621
622 if available.contains(self) {
623 return Some(*self);
624 }
625
626 if *self <= FcStretch::Normal {
628 let mut closest_narrower = None;
630 for stretch in available.iter() {
631 if *stretch < *self
632 && (closest_narrower.is_none() || *stretch > closest_narrower.unwrap())
633 {
634 closest_narrower = Some(*stretch);
635 }
636 }
637
638 if closest_narrower.is_some() {
639 return closest_narrower;
640 }
641
642 let mut closest_wider = None;
644 for stretch in available.iter() {
645 if *stretch > *self
646 && (closest_wider.is_none() || *stretch < closest_wider.unwrap())
647 {
648 closest_wider = Some(*stretch);
649 }
650 }
651
652 return closest_wider;
653 } else {
654 let mut closest_wider = None;
656 for stretch in available.iter() {
657 if *stretch > *self
658 && (closest_wider.is_none() || *stretch < closest_wider.unwrap())
659 {
660 closest_wider = Some(*stretch);
661 }
662 }
663
664 if closest_wider.is_some() {
665 return closest_wider;
666 }
667
668 let mut closest_narrower = None;
670 for stretch in available.iter() {
671 if *stretch < *self
672 && (closest_narrower.is_none() || *stretch > closest_narrower.unwrap())
673 {
674 closest_narrower = Some(*stretch);
675 }
676 }
677
678 return closest_narrower;
679 }
680 }
681}
682
683impl Default for FcStretch {
684 fn default() -> Self {
685 FcStretch::Normal
686 }
687}
688
689#[repr(C)]
691#[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
698pub const DEFAULT_UNICODE_FALLBACK_SCRIPTS: &[UnicodeRange] = &[
707 UnicodeRange { start: 0x0400, end: 0x04FF }, UnicodeRange { start: 0x0600, end: 0x06FF }, UnicodeRange { start: 0x0900, end: 0x097F }, UnicodeRange { start: 0x3040, end: 0x309F }, UnicodeRange { start: 0x30A0, end: 0x30FF }, UnicodeRange { start: 0x4E00, end: 0x9FFF }, UnicodeRange { start: 0xAC00, end: 0xD7A3 }, ];
715
716impl UnicodeRange {
717 pub fn contains(&self, c: char) -> bool {
718 let c = c as u32;
719 c >= self.start && c <= self.end
720 }
721
722 pub fn overlaps(&self, other: &UnicodeRange) -> bool {
723 self.start <= other.end && other.start <= self.end
724 }
725
726 pub fn is_subset_of(&self, other: &UnicodeRange) -> bool {
727 self.start >= other.start && self.end <= other.end
728 }
729}
730
731pub fn has_cjk_ranges(ranges: &[UnicodeRange]) -> bool {
733 ranges.iter().any(|r| {
734 (r.start >= 0x4E00 && r.start <= 0x9FFF) ||
735 (r.start >= 0x3040 && r.start <= 0x309F) ||
736 (r.start >= 0x30A0 && r.start <= 0x30FF) ||
737 (r.start >= 0xAC00 && r.start <= 0xD7AF)
738 })
739}
740
741pub fn has_arabic_ranges(ranges: &[UnicodeRange]) -> bool {
743 ranges.iter().any(|r| r.start >= 0x0600 && r.start <= 0x06FF)
744}
745
746pub fn has_cyrillic_ranges(ranges: &[UnicodeRange]) -> bool {
748 ranges.iter().any(|r| r.start >= 0x0400 && r.start <= 0x04FF)
749}
750
751pub fn has_hebrew_ranges(ranges: &[UnicodeRange]) -> bool {
753 ranges.iter().any(|r| r.start >= 0x0590 && r.start <= 0x05FF)
754}
755
756pub fn has_thai_ranges(ranges: &[UnicodeRange]) -> bool {
758 ranges.iter().any(|r| r.start >= 0x0E00 && r.start <= 0x0E7F)
759}
760
761#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
763pub enum TraceLevel {
764 Debug,
765 Info,
766 Warning,
767 Error,
768}
769
770#[derive(Debug, Clone, PartialEq, Eq, Hash)]
772pub enum MatchReason {
773 NameMismatch {
774 requested: Option<String>,
775 found: Option<String>,
776 },
777 FamilyMismatch {
778 requested: Option<String>,
779 found: Option<String>,
780 },
781 StyleMismatch {
782 property: &'static str,
783 requested: String,
784 found: String,
785 },
786 WeightMismatch {
787 requested: FcWeight,
788 found: FcWeight,
789 },
790 StretchMismatch {
791 requested: FcStretch,
792 found: FcStretch,
793 },
794 UnicodeRangeMismatch {
795 character: char,
796 ranges: Vec<UnicodeRange>,
797 },
798 Success,
799}
800
801#[derive(Debug, Clone, PartialEq, Eq)]
803pub struct TraceMsg {
804 pub level: TraceLevel,
805 pub path: String,
806 pub reason: MatchReason,
807}
808
809#[repr(C)]
811#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
812#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
813pub enum FcHintStyle {
814 #[default]
815 None = 0,
816 Slight = 1,
817 Medium = 2,
818 Full = 3,
819}
820
821#[repr(C)]
823#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
824#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
825pub enum FcRgba {
826 #[default]
827 Unknown = 0,
828 Rgb = 1,
829 Bgr = 2,
830 Vrgb = 3,
831 Vbgr = 4,
832 None = 5,
833}
834
835#[repr(C)]
837#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
838#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
839pub enum FcLcdFilter {
840 #[default]
841 None = 0,
842 Default = 1,
843 Light = 2,
844 Legacy = 3,
845}
846
847#[derive(Debug, Default, Clone, PartialEq, PartialOrd)]
852#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
853pub struct FcFontRenderConfig {
854 pub antialias: Option<bool>,
855 pub hinting: Option<bool>,
856 pub hintstyle: Option<FcHintStyle>,
857 pub autohint: Option<bool>,
858 pub rgba: Option<FcRgba>,
859 pub lcdfilter: Option<FcLcdFilter>,
860 pub embeddedbitmap: Option<bool>,
861 pub embolden: Option<bool>,
862 pub dpi: Option<f64>,
863 pub scale: Option<f64>,
864 pub minspace: Option<bool>,
865}
866
867impl Eq for FcFontRenderConfig {}
870
871impl Ord for FcFontRenderConfig {
872 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
873 let ord = self.antialias.cmp(&other.antialias)
875 .then_with(|| self.hinting.cmp(&other.hinting))
876 .then_with(|| self.hintstyle.cmp(&other.hintstyle))
877 .then_with(|| self.autohint.cmp(&other.autohint))
878 .then_with(|| self.rgba.cmp(&other.rgba))
879 .then_with(|| self.lcdfilter.cmp(&other.lcdfilter))
880 .then_with(|| self.embeddedbitmap.cmp(&other.embeddedbitmap))
881 .then_with(|| self.embolden.cmp(&other.embolden))
882 .then_with(|| self.minspace.cmp(&other.minspace));
883
884 let ord = ord.then_with(|| {
886 let a = self.dpi.map(|v| v.to_bits());
887 let b = other.dpi.map(|v| v.to_bits());
888 a.cmp(&b)
889 });
890 ord.then_with(|| {
891 let a = self.scale.map(|v| v.to_bits());
892 let b = other.scale.map(|v| v.to_bits());
893 a.cmp(&b)
894 })
895 }
896}
897
898#[derive(Default, Clone, PartialOrd, Ord, PartialEq, Eq)]
900#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
901#[repr(C)]
902pub struct FcPattern {
903 pub name: Option<String>,
905 pub family: Option<String>,
907 pub italic: PatternMatch,
909 pub oblique: PatternMatch,
911 pub bold: PatternMatch,
913 pub monospace: PatternMatch,
915 pub condensed: PatternMatch,
917 pub weight: FcWeight,
919 pub stretch: FcStretch,
921 pub unicode_ranges: Vec<UnicodeRange>,
923 pub metadata: FcFontMetadata,
925 pub render_config: FcFontRenderConfig,
927}
928
929impl core::fmt::Debug for FcPattern {
930 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
931 let mut d = f.debug_struct("FcPattern");
932
933 if let Some(name) = &self.name {
934 d.field("name", name);
935 }
936
937 if let Some(family) = &self.family {
938 d.field("family", family);
939 }
940
941 if self.italic != PatternMatch::DontCare {
942 d.field("italic", &self.italic);
943 }
944
945 if self.oblique != PatternMatch::DontCare {
946 d.field("oblique", &self.oblique);
947 }
948
949 if self.bold != PatternMatch::DontCare {
950 d.field("bold", &self.bold);
951 }
952
953 if self.monospace != PatternMatch::DontCare {
954 d.field("monospace", &self.monospace);
955 }
956
957 if self.condensed != PatternMatch::DontCare {
958 d.field("condensed", &self.condensed);
959 }
960
961 if self.weight != FcWeight::Normal {
962 d.field("weight", &self.weight);
963 }
964
965 if self.stretch != FcStretch::Normal {
966 d.field("stretch", &self.stretch);
967 }
968
969 if !self.unicode_ranges.is_empty() {
970 d.field("unicode_ranges", &self.unicode_ranges);
971 }
972
973 let empty_metadata = FcFontMetadata::default();
975 if self.metadata != empty_metadata {
976 d.field("metadata", &self.metadata);
977 }
978
979 let empty_render_config = FcFontRenderConfig::default();
981 if self.render_config != empty_render_config {
982 d.field("render_config", &self.render_config);
983 }
984
985 d.finish()
986 }
987}
988
989#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]
991#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
992pub struct FcFontMetadata {
993 pub copyright: Option<String>,
994 pub designer: Option<String>,
995 pub designer_url: Option<String>,
996 pub font_family: Option<String>,
997 pub font_subfamily: Option<String>,
998 pub full_name: Option<String>,
999 pub id_description: Option<String>,
1000 pub license: Option<String>,
1001 pub license_url: Option<String>,
1002 pub manufacturer: Option<String>,
1003 pub manufacturer_url: Option<String>,
1004 pub postscript_name: Option<String>,
1005 pub preferred_family: Option<String>,
1006 pub preferred_subfamily: Option<String>,
1007 pub trademark: Option<String>,
1008 pub unique_id: Option<String>,
1009 pub version: Option<String>,
1010}
1011
1012impl FcPattern {
1013 pub fn contains_char(&self, c: char) -> bool {
1015 if self.unicode_ranges.is_empty() {
1016 return true; }
1018
1019 for range in &self.unicode_ranges {
1020 if range.contains(c) {
1021 return true;
1022 }
1023 }
1024
1025 false
1026 }
1027}
1028
1029#[derive(Debug, Clone, PartialEq, Eq)]
1031pub struct FontMatch {
1032 pub id: FontId,
1033 pub unicode_ranges: Vec<UnicodeRange>,
1034 pub fallbacks: Vec<FontMatchNoFallback>,
1035}
1036
1037#[derive(Debug, Clone, PartialEq, Eq)]
1039pub struct FontMatchNoFallback {
1040 pub id: FontId,
1041 pub unicode_ranges: Vec<UnicodeRange>,
1042}
1043
1044#[derive(Debug, Clone, PartialEq, Eq)]
1047pub struct ResolvedFontRun {
1048 pub text: String,
1050 pub start_byte: usize,
1052 pub end_byte: usize,
1054 pub font_id: Option<FontId>,
1056 pub css_source: String,
1058}
1059
1060#[derive(Debug, Clone, PartialEq, Eq)]
1063pub struct FontFallbackChain {
1064 pub css_fallbacks: Vec<CssFallbackGroup>,
1067
1068 pub unicode_fallbacks: Vec<FontMatch>,
1071
1072 pub original_stack: Vec<String>,
1074}
1075
1076impl FontFallbackChain {
1077 pub fn resolve_char(&self, cache: &FcFontCache, ch: char) -> Option<(FontId, String)> {
1081 let codepoint = ch as u32;
1082
1083 for group in &self.css_fallbacks {
1085 for font in &group.fonts {
1086 let Some(meta) = cache.get_metadata_by_id(&font.id) else { continue };
1087 if meta.unicode_ranges.is_empty() {
1088 continue; }
1090 if meta.unicode_ranges.iter().any(|r| codepoint >= r.start && codepoint <= r.end) {
1091 return Some((font.id, group.css_name.clone()));
1092 }
1093 }
1094 }
1095
1096 for font in &self.unicode_fallbacks {
1098 let Some(meta) = cache.get_metadata_by_id(&font.id) else { continue };
1099 if meta.unicode_ranges.iter().any(|r| codepoint >= r.start && codepoint <= r.end) {
1100 return Some((font.id, "(unicode-fallback)".to_string()));
1101 }
1102 }
1103
1104 None
1105 }
1106
1107 pub fn resolve_text(&self, cache: &FcFontCache, text: &str) -> Vec<(char, Option<(FontId, String)>)> {
1110 text.chars()
1111 .map(|ch| (ch, self.resolve_char(cache, ch)))
1112 .collect()
1113 }
1114
1115 pub fn query_for_text(&self, cache: &FcFontCache, text: &str) -> Vec<ResolvedFontRun> {
1119 if text.is_empty() {
1120 return Vec::new();
1121 }
1122
1123 let mut runs: Vec<ResolvedFontRun> = Vec::new();
1124 let mut current_font: Option<FontId> = None;
1125 let mut current_css_source: Option<String> = None;
1126 let mut current_start_byte: usize = 0;
1127
1128 for (byte_idx, ch) in text.char_indices() {
1129 let resolved = self.resolve_char(cache, ch);
1130 let (font_id, css_source) = match &resolved {
1131 Some((id, source)) => (Some(*id), Some(source.clone())),
1132 None => (None, None),
1133 };
1134
1135 let font_changed = font_id != current_font;
1137
1138 if font_changed && byte_idx > 0 {
1139 let run_text = &text[current_start_byte..byte_idx];
1141 runs.push(ResolvedFontRun {
1142 text: run_text.to_string(),
1143 start_byte: current_start_byte,
1144 end_byte: byte_idx,
1145 font_id: current_font,
1146 css_source: current_css_source.clone().unwrap_or_default(),
1147 });
1148 current_start_byte = byte_idx;
1149 }
1150
1151 current_font = font_id;
1152 current_css_source = css_source;
1153 }
1154
1155 if current_start_byte < text.len() {
1157 let run_text = &text[current_start_byte..];
1158 runs.push(ResolvedFontRun {
1159 text: run_text.to_string(),
1160 start_byte: current_start_byte,
1161 end_byte: text.len(),
1162 font_id: current_font,
1163 css_source: current_css_source.unwrap_or_default(),
1164 });
1165 }
1166
1167 runs
1168 }
1169}
1170
1171#[derive(Debug, Clone, PartialEq, Eq)]
1173pub struct CssFallbackGroup {
1174 pub css_name: String,
1176
1177 pub fonts: Vec<FontMatch>,
1180}
1181
1182#[cfg(feature = "std")]
1195#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1196pub(crate) struct FontChainCacheKey {
1197 pub(crate) font_families: Vec<String>,
1199 pub(crate) weight: FcWeight,
1201 pub(crate) italic: PatternMatch,
1203 pub(crate) oblique: PatternMatch,
1204 pub(crate) scripts_hint_hash: Option<u64>,
1206}
1207
1208#[cfg(feature = "std")]
1213fn hash_scripts_hint(ranges: &[UnicodeRange]) -> u64 {
1214 let mut sorted: Vec<UnicodeRange> = ranges.to_vec();
1215 sorted.sort();
1216 let mut buf = Vec::with_capacity(sorted.len() * 8);
1217 for r in &sorted {
1218 buf.extend_from_slice(&r.start.to_le_bytes());
1219 buf.extend_from_slice(&r.end.to_le_bytes());
1220 }
1221 crate::utils::content_hash_u64(&buf)
1222}
1223
1224#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq)]
1236#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
1237#[repr(C)]
1238pub struct FcFontPath {
1239 pub path: String,
1240 pub font_index: usize,
1241 #[cfg_attr(feature = "cache", serde(default))]
1243 pub bytes_hash: u64,
1244}
1245
1246#[derive(Debug, Clone, PartialEq, Eq)]
1248#[repr(C)]
1249pub struct FcFont {
1250 pub bytes: Vec<u8>,
1251 pub font_index: usize,
1252 pub id: String, }
1254
1255#[derive(Debug, Clone)]
1267pub enum OwnedFontSource {
1268 Memory(FcFont),
1270 Disk(FcFontPath),
1272}
1273
1274#[cfg(feature = "std")]
1287pub enum FontBytes {
1288 Owned(std::sync::Arc<[u8]>),
1291 Mmapped(mmapio::Mmap),
1294}
1295
1296#[cfg(feature = "std")]
1297impl FontBytes {
1298 #[inline]
1300 pub fn as_slice(&self) -> &[u8] {
1301 match self {
1302 FontBytes::Owned(arc) => arc,
1303 FontBytes::Mmapped(m) => &m[..],
1304 }
1305 }
1306}
1307
1308#[cfg(feature = "std")]
1309impl core::ops::Deref for FontBytes {
1310 type Target = [u8];
1311 #[inline]
1312 fn deref(&self) -> &[u8] {
1313 self.as_slice()
1314 }
1315}
1316
1317#[cfg(feature = "std")]
1318impl AsRef<[u8]> for FontBytes {
1319 #[inline]
1320 fn as_ref(&self) -> &[u8] {
1321 self.as_slice()
1322 }
1323}
1324
1325#[cfg(feature = "std")]
1326impl core::fmt::Debug for FontBytes {
1327 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1328 let kind = match self {
1329 FontBytes::Owned(_) => "Owned",
1330 FontBytes::Mmapped(_) => "Mmapped",
1331 };
1332 write!(f, "FontBytes::{}({} bytes)", kind, self.as_slice().len())
1333 }
1334}
1335
1336#[cfg(feature = "std")]
1340fn open_font_bytes_mmap(path: &str) -> Option<std::sync::Arc<FontBytes>> {
1341 use std::fs::File;
1342 use std::sync::Arc;
1343
1344 #[cfg(not(target_family = "wasm"))]
1345 {
1346 if let Ok(file) = File::open(path) {
1347 if let Ok(mmap) = unsafe { mmapio::MmapOptions::new().map(&file) } {
1352 return Some(Arc::new(FontBytes::Mmapped(mmap)));
1353 }
1354 }
1355 }
1356 let bytes = std::fs::read(path).ok()?;
1357 Some(Arc::new(FontBytes::Owned(Arc::from(bytes))))
1358}
1359
1360#[derive(Debug, Clone)]
1363pub struct NamedFont {
1364 pub name: String,
1366 pub bytes: Vec<u8>,
1368}
1369
1370impl NamedFont {
1371 pub fn new(name: impl Into<String>, bytes: Vec<u8>) -> Self {
1373 Self {
1374 name: name.into(),
1375 bytes,
1376 }
1377 }
1378}
1379
1380pub struct FcFontCache {
1398 pub(crate) shared: std::sync::Arc<FcFontCacheShared>,
1399}
1400
1401pub(crate) struct FcFontCacheShared {
1404 pub(crate) state: std::sync::RwLock<FcFontCacheInner>,
1408 pub(crate) chain_cache: std::sync::Mutex<std::collections::HashMap<FontChainCacheKey, FontFallbackChain>>,
1412 pub(crate) shared_bytes: std::sync::Mutex<std::collections::HashMap<u64, std::sync::Weak<FontBytes>>>,
1421}
1422
1423#[derive(Default, Debug)]
1427pub(crate) struct FcFontCacheInner {
1428 pub(crate) patterns: BTreeMap<FcPattern, FontId>,
1430 pub(crate) disk_fonts: BTreeMap<FontId, FcFontPath>,
1432 pub(crate) memory_fonts: BTreeMap<FontId, FcFont>,
1434 pub(crate) metadata: BTreeMap<FontId, FcPattern>,
1436 pub(crate) token_index: BTreeMap<String, alloc::collections::BTreeSet<FontId>>,
1439 pub(crate) font_tokens: BTreeMap<FontId, Vec<String>>,
1442}
1443
1444impl FcFontCacheInner {
1445 pub(crate) fn index_pattern_tokens(&mut self, pattern: &FcPattern, id: FontId) {
1448 let mut all_tokens = Vec::new();
1450
1451 if let Some(name) = &pattern.name {
1452 all_tokens.extend(FcFontCache::extract_font_name_tokens(name));
1453 }
1454
1455 if let Some(family) = &pattern.family {
1456 all_tokens.extend(FcFontCache::extract_font_name_tokens(family));
1457 }
1458
1459 let tokens_lower: Vec<String> =
1461 all_tokens.iter().map(|t| t.to_lowercase()).collect();
1462
1463 for token_lower in &tokens_lower {
1465 self.token_index
1466 .entry(token_lower.clone())
1467 .or_insert_with(alloc::collections::BTreeSet::new)
1468 .insert(id);
1469 }
1470
1471 self.font_tokens.insert(id, tokens_lower);
1473 }
1474}
1475
1476impl Clone for FcFontCache {
1477 fn clone(&self) -> Self {
1484 Self {
1485 shared: std::sync::Arc::clone(&self.shared),
1486 }
1487 }
1488}
1489
1490impl core::fmt::Debug for FcFontCache {
1491 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1492 let state = self.state_read();
1493 f.debug_struct("FcFontCache")
1494 .field("patterns_len", &state.patterns.len())
1495 .field("metadata_len", &state.metadata.len())
1496 .field("disk_fonts_len", &state.disk_fonts.len())
1497 .field("memory_fonts_len", &state.memory_fonts.len())
1498 .finish()
1499 }
1500}
1501
1502impl Default for FcFontCache {
1503 fn default() -> Self {
1504 Self {
1505 shared: std::sync::Arc::new(FcFontCacheShared {
1506 state: std::sync::RwLock::new(FcFontCacheInner::default()),
1507 chain_cache: std::sync::Mutex::new(std::collections::HashMap::new()),
1508 shared_bytes: std::sync::Mutex::new(std::collections::HashMap::new()),
1509 }),
1510 }
1511 }
1512}
1513
1514impl FcFontCache {
1515 #[inline]
1519 pub(crate) fn state_read(
1520 &self,
1521 ) -> std::sync::RwLockReadGuard<'_, FcFontCacheInner> {
1522 self.shared
1523 .state
1524 .read()
1525 .unwrap_or_else(|poisoned| poisoned.into_inner())
1526 }
1527
1528 #[inline]
1531 pub(crate) fn state_write(
1532 &self,
1533 ) -> std::sync::RwLockWriteGuard<'_, FcFontCacheInner> {
1534 self.shared
1535 .state
1536 .write()
1537 .unwrap_or_else(|poisoned| poisoned.into_inner())
1538 }
1539
1540 pub fn with_memory_fonts(&self, fonts: Vec<(FcPattern, FcFont)>) -> &Self {
1545 let mut state = self.state_write();
1546 for (pattern, font) in fonts {
1547 let id = FontId::new();
1548 state.patterns.insert(pattern.clone(), id);
1549 state.metadata.insert(id, pattern.clone());
1550 state.memory_fonts.insert(id, font);
1551 state.index_pattern_tokens(&pattern, id);
1552 }
1553 self
1554 }
1555
1556 pub fn with_memory_font_with_id(
1558 &self,
1559 id: FontId,
1560 pattern: FcPattern,
1561 font: FcFont,
1562 ) -> &Self {
1563 let mut state = self.state_write();
1564 state.patterns.insert(pattern.clone(), id);
1565 state.metadata.insert(id, pattern.clone());
1566 state.memory_fonts.insert(id, font);
1567 state.index_pattern_tokens(&pattern, id);
1568 self
1569 }
1570
1571 pub fn insert_builder_font(&self, pattern: FcPattern, path: FcFontPath) {
1577 let id = FontId::new();
1578 {
1579 let mut state = self.state_write();
1580 state.index_pattern_tokens(&pattern, id);
1581 state.patterns.insert(pattern.clone(), id);
1582 state.disk_fonts.insert(id, path);
1583 state.metadata.insert(id, pattern);
1584 }
1585 if let Ok(mut cc) = self.shared.chain_cache.lock() {
1589 cc.clear();
1590 }
1591 }
1592
1593 pub fn get_font_by_id(&self, id: &FontId) -> Option<OwnedFontSource> {
1600 let state = self.state_read();
1601 if let Some(font) = state.memory_fonts.get(id) {
1602 return Some(OwnedFontSource::Memory(font.clone()));
1603 }
1604 if let Some(path) = state.disk_fonts.get(id) {
1605 return Some(OwnedFontSource::Disk(path.clone()));
1606 }
1607 None
1608 }
1609
1610 pub fn get_metadata_by_id(&self, id: &FontId) -> Option<FcPattern> {
1614 self.state_read().metadata.get(id).cloned()
1615 }
1616
1617 #[cfg(feature = "std")]
1645 pub fn get_font_bytes(&self, id: &FontId) -> Option<std::sync::Arc<FontBytes>> {
1646 use std::sync::Arc;
1647 match self.get_font_by_id(id)? {
1648 OwnedFontSource::Memory(font) => Some(Arc::new(FontBytes::Owned(
1649 Arc::from(font.bytes.as_slice()),
1650 ))),
1651 OwnedFontSource::Disk(path) => {
1652 let hash = path.bytes_hash;
1653 if hash != 0 {
1654 if let Ok(guard) = self.shared.shared_bytes.lock() {
1655 if let Some(weak) = guard.get(&hash) {
1656 if let Some(arc) = weak.upgrade() {
1657 return Some(arc);
1658 }
1659 }
1660 }
1661 }
1662
1663 let arc = open_font_bytes_mmap(&path.path)?;
1664 if hash != 0 {
1665 if let Ok(mut guard) = self.shared.shared_bytes.lock() {
1666 guard.insert(hash, Arc::downgrade(&arc));
1668 }
1669 }
1670 Some(arc)
1671 }
1672 }
1673 }
1674
1675 #[cfg(not(feature = "std"))]
1677 pub fn build() -> Self { Self::default() }
1678
1679 #[cfg(all(feature = "std", not(feature = "parsing")))]
1681 pub fn build() -> Self { Self::build_from_filenames() }
1682
1683 #[cfg(all(feature = "std", feature = "parsing"))]
1685 pub fn build() -> Self { Self::build_inner(None) }
1686
1687 #[cfg(all(feature = "std", not(feature = "parsing")))]
1690 fn build_from_filenames() -> Self {
1691 let cache = Self::default();
1692 {
1693 let mut state = cache.state_write();
1694 for dir in crate::config::font_directories(OperatingSystem::current()) {
1695 for path in FcCollectFontFilesRecursive(dir) {
1696 let pattern = match pattern_from_filename(&path) {
1697 Some(p) => p,
1698 None => continue,
1699 };
1700 let id = FontId::new();
1701 state.disk_fonts.insert(id, FcFontPath {
1702 path: path.to_string_lossy().to_string(),
1703 font_index: 0,
1704 bytes_hash: 0,
1707 });
1708 state.index_pattern_tokens(&pattern, id);
1709 state.metadata.insert(id, pattern.clone());
1710 state.patterns.insert(pattern, id);
1711 }
1712 }
1713 }
1714 cache
1715 }
1716
1717 #[cfg(all(feature = "std", feature = "parsing"))]
1742 pub fn build_with_families(families: &[impl AsRef<str>]) -> Self {
1743 let os = OperatingSystem::current();
1745 let mut target_families: Vec<String> = Vec::new();
1746
1747 for family in families {
1748 let family_str = family.as_ref();
1749 let expanded = os.expand_generic_family(family_str, &[]);
1750 if expanded.is_empty() || (expanded.len() == 1 && expanded[0] == family_str) {
1751 target_families.push(family_str.to_string());
1752 } else {
1753 target_families.extend(expanded);
1754 }
1755 }
1756
1757 Self::build_inner(Some(&target_families))
1758 }
1759
1760 #[cfg(all(feature = "std", feature = "parsing"))]
1766 fn build_inner(family_filter: Option<&[String]>) -> Self {
1767 let cache = FcFontCache::default();
1768
1769 let filter_normalized: Option<Vec<String>> = family_filter.map(|families| {
1771 families
1772 .iter()
1773 .map(|f| crate::utils::normalize_family_name(f))
1774 .collect()
1775 });
1776
1777 let matches_filter = |pattern: &FcPattern| -> bool {
1779 match &filter_normalized {
1780 None => true, Some(targets) => {
1782 pattern.name.as_ref().map_or(false, |name| {
1783 let name_norm = crate::utils::normalize_family_name(name);
1784 targets.iter().any(|target| name_norm.contains(target))
1785 }) || pattern.family.as_ref().map_or(false, |family| {
1786 let family_norm = crate::utils::normalize_family_name(family);
1787 targets.iter().any(|target| family_norm.contains(target))
1788 })
1789 }
1790 }
1791 };
1792
1793 let mut state = cache.state_write();
1794
1795 #[cfg(target_os = "linux")]
1796 {
1797 if let Some((font_entries, render_configs)) = FcScanDirectories() {
1798 for (mut pattern, path) in font_entries {
1799 if matches_filter(&pattern) {
1800 if let Some(family) = pattern.name.as_ref().or(pattern.family.as_ref()) {
1802 if let Some(rc) = render_configs.get(family) {
1803 pattern.render_config = rc.clone();
1804 }
1805 }
1806 let id = FontId::new();
1807 state.patterns.insert(pattern.clone(), id);
1808 state.metadata.insert(id, pattern.clone());
1809 state.disk_fonts.insert(id, path);
1810 state.index_pattern_tokens(&pattern, id);
1811 }
1812 }
1813 }
1814 }
1815
1816 #[cfg(target_os = "windows")]
1817 {
1818 let system_root = std::env::var("SystemRoot")
1819 .or_else(|_| std::env::var("WINDIR"))
1820 .unwrap_or_else(|_| "C:\\Windows".to_string());
1821
1822 let user_profile = std::env::var("USERPROFILE")
1823 .unwrap_or_else(|_| "C:\\Users\\Default".to_string());
1824
1825 let font_dirs = vec![
1826 (None, format!("{}\\Fonts\\", system_root)),
1827 (None, format!("{}\\AppData\\Local\\Microsoft\\Windows\\Fonts\\", user_profile)),
1828 ];
1829
1830 let font_entries = FcScanDirectoriesInner(&font_dirs);
1831 for (pattern, path) in font_entries {
1832 if matches_filter(&pattern) {
1833 let id = FontId::new();
1834 state.patterns.insert(pattern.clone(), id);
1835 state.metadata.insert(id, pattern.clone());
1836 state.disk_fonts.insert(id, path);
1837 state.index_pattern_tokens(&pattern, id);
1838 }
1839 }
1840 }
1841
1842 #[cfg(target_os = "macos")]
1843 {
1844 let font_dirs = vec![
1845 (None, "~/Library/Fonts".to_owned()),
1846 (None, "/System/Library/Fonts".to_owned()),
1847 (None, "/Library/Fonts".to_owned()),
1848 (None, "/System/Library/AssetsV2".to_owned()),
1849 ];
1850
1851 let font_entries = FcScanDirectoriesInner(&font_dirs);
1852 for (pattern, path) in font_entries {
1853 if matches_filter(&pattern) {
1854 let id = FontId::new();
1855 state.patterns.insert(pattern.clone(), id);
1856 state.metadata.insert(id, pattern.clone());
1857 state.disk_fonts.insert(id, path);
1858 state.index_pattern_tokens(&pattern, id);
1859 }
1860 }
1861 }
1862
1863 drop(state);
1864 cache
1865 }
1866
1867 pub fn is_memory_font(&self, id: &FontId) -> bool {
1869 self.state_read().memory_fonts.contains_key(id)
1870 }
1871
1872 pub fn list(&self) -> Vec<(FcPattern, FontId)> {
1879 self.state_read()
1880 .patterns
1881 .iter()
1882 .map(|(pattern, id)| (pattern.clone(), *id))
1883 .collect()
1884 }
1885
1886 pub fn for_each_pattern<F: FnMut(&FcPattern, &FontId)>(&self, mut f: F) {
1890 let state = self.state_read();
1891 for (pattern, id) in &state.patterns {
1892 f(pattern, id);
1893 }
1894 }
1895
1896 pub fn is_empty(&self) -> bool {
1898 self.state_read().patterns.is_empty()
1899 }
1900
1901 pub fn len(&self) -> usize {
1903 self.state_read().patterns.len()
1904 }
1905
1906 pub fn query(&self, pattern: &FcPattern, trace: &mut Vec<TraceMsg>) -> Option<FontMatch> {
1909 let state = self.state_read();
1910 let mut matches = Vec::new();
1911
1912 for (stored_pattern, id) in &state.patterns {
1913 if Self::query_matches_internal(stored_pattern, pattern, trace) {
1914 let metadata = state.metadata.get(id).unwrap_or(stored_pattern);
1915
1916 let unicode_compatibility = if pattern.unicode_ranges.is_empty() {
1918 Self::calculate_unicode_coverage(&metadata.unicode_ranges) as i32
1920 } else {
1921 Self::calculate_unicode_compatibility(&pattern.unicode_ranges, &metadata.unicode_ranges)
1923 };
1924
1925 let style_score = Self::calculate_style_score(pattern, metadata);
1926
1927 let is_memory = state.memory_fonts.contains_key(id);
1929
1930 matches.push((*id, unicode_compatibility, style_score, metadata.clone(), is_memory));
1931 }
1932 }
1933
1934 matches.sort_by(|a, b| {
1936 b.4.cmp(&a.4)
1938 .then_with(|| b.1.cmp(&a.1)) .then_with(|| a.2.cmp(&b.2)) });
1941
1942 matches.first().map(|(id, _, _, metadata, _)| {
1943 FontMatch {
1944 id: *id,
1945 unicode_ranges: metadata.unicode_ranges.clone(),
1946 fallbacks: Vec::new(), }
1948 })
1949 }
1950
1951 fn query_internal(&self, pattern: &FcPattern, trace: &mut Vec<TraceMsg>) -> Vec<FontMatch> {
1956 let state = self.state_read();
1957 self.query_internal_locked(&state, pattern, trace)
1958 }
1959
1960 fn query_internal_locked(
1963 &self,
1964 state: &FcFontCacheInner,
1965 pattern: &FcPattern,
1966 trace: &mut Vec<TraceMsg>,
1967 ) -> Vec<FontMatch> {
1968 let mut matches = Vec::new();
1969
1970 for (stored_pattern, id) in &state.patterns {
1971 if Self::query_matches_internal(stored_pattern, pattern, trace) {
1972 let metadata = state.metadata.get(id).unwrap_or(stored_pattern);
1973
1974 let unicode_compatibility = if pattern.unicode_ranges.is_empty() {
1976 Self::calculate_unicode_coverage(&metadata.unicode_ranges) as i32
1977 } else {
1978 Self::calculate_unicode_compatibility(&pattern.unicode_ranges, &metadata.unicode_ranges)
1979 };
1980
1981 let style_score = Self::calculate_style_score(pattern, metadata);
1982 matches.push((*id, unicode_compatibility, style_score, metadata.clone()));
1983 }
1984 }
1985
1986 matches.sort_by(|a, b| {
1990 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)) });
1995
1996 matches
1997 .into_iter()
1998 .map(|(id, _, _, metadata)| {
1999 FontMatch {
2000 id,
2001 unicode_ranges: metadata.unicode_ranges.clone(),
2002 fallbacks: Vec::new(), }
2004 })
2005 .collect()
2006 }
2007
2008 pub fn compute_fallbacks(
2012 &self,
2013 font_id: &FontId,
2014 trace: &mut Vec<TraceMsg>,
2015 ) -> Vec<FontMatchNoFallback> {
2016 let state = self.state_read();
2017 let pattern = match state.metadata.get(font_id) {
2018 Some(p) => p.clone(),
2019 None => return Vec::new(),
2020 };
2021 drop(state);
2022
2023 self.compute_fallbacks_for_pattern(&pattern, Some(font_id), trace)
2024 }
2025
2026 fn compute_fallbacks_for_pattern(
2027 &self,
2028 pattern: &FcPattern,
2029 exclude_id: Option<&FontId>,
2030 _trace: &mut Vec<TraceMsg>,
2031 ) -> Vec<FontMatchNoFallback> {
2032 let state = self.state_read();
2033 let mut candidates = Vec::new();
2034
2035 for (stored_pattern, id) in &state.patterns {
2037 if exclude_id.is_some() && exclude_id.unwrap() == id {
2039 continue;
2040 }
2041
2042 if !stored_pattern.unicode_ranges.is_empty() && !pattern.unicode_ranges.is_empty() {
2044 let unicode_compatibility = Self::calculate_unicode_compatibility(
2046 &pattern.unicode_ranges,
2047 &stored_pattern.unicode_ranges
2048 );
2049
2050 if unicode_compatibility > 0 {
2052 let style_score = Self::calculate_style_score(pattern, stored_pattern);
2053 candidates.push((
2054 FontMatchNoFallback {
2055 id: *id,
2056 unicode_ranges: stored_pattern.unicode_ranges.clone(),
2057 },
2058 unicode_compatibility,
2059 style_score,
2060 stored_pattern.clone(),
2061 ));
2062 }
2063 } else if pattern.unicode_ranges.is_empty() && !stored_pattern.unicode_ranges.is_empty() {
2064 let coverage = Self::calculate_unicode_coverage(&stored_pattern.unicode_ranges) as i32;
2066 let style_score = Self::calculate_style_score(pattern, stored_pattern);
2067 candidates.push((
2068 FontMatchNoFallback {
2069 id: *id,
2070 unicode_ranges: stored_pattern.unicode_ranges.clone(),
2071 },
2072 coverage,
2073 style_score,
2074 stored_pattern.clone(),
2075 ));
2076 }
2077 }
2078
2079 drop(state);
2080
2081 candidates.sort_by(|a, b| {
2083 b.1.cmp(&a.1)
2084 .then_with(|| a.2.cmp(&b.2))
2085 });
2086
2087 let mut seen_ranges = Vec::new();
2089 let mut deduplicated = Vec::new();
2090
2091 for (id, _, _, pattern) in candidates {
2092 let mut is_new_range = false;
2093
2094 for range in &pattern.unicode_ranges {
2095 if !seen_ranges.iter().any(|r: &UnicodeRange| r.overlaps(range)) {
2096 seen_ranges.push(*range);
2097 is_new_range = true;
2098 }
2099 }
2100
2101 if is_new_range {
2102 deduplicated.push(id);
2103 }
2104 }
2105
2106 deduplicated
2107 }
2108
2109 pub fn get_memory_font(&self, id: &FontId) -> Option<FcFont> {
2111 self.state_read().memory_fonts.get(id).cloned()
2112 }
2113
2114 fn trace_path(k: &FcPattern) -> String {
2116 k.name.as_ref().cloned().unwrap_or_else(|| "<unknown>".to_string())
2117 }
2118
2119 pub fn query_matches_internal(
2120 k: &FcPattern,
2121 pattern: &FcPattern,
2122 trace: &mut Vec<TraceMsg>,
2123 ) -> bool {
2124 if let Some(ref name) = pattern.name {
2126 if !k.name.as_ref().map_or(false, |kn| kn.contains(name)) {
2127 trace.push(TraceMsg {
2128 level: TraceLevel::Info,
2129 path: Self::trace_path(k),
2130 reason: MatchReason::NameMismatch {
2131 requested: pattern.name.clone(),
2132 found: k.name.clone(),
2133 },
2134 });
2135 return false;
2136 }
2137 }
2138
2139 if let Some(ref family) = pattern.family {
2141 if !k.family.as_ref().map_or(false, |kf| kf.contains(family)) {
2142 trace.push(TraceMsg {
2143 level: TraceLevel::Info,
2144 path: Self::trace_path(k),
2145 reason: MatchReason::FamilyMismatch {
2146 requested: pattern.family.clone(),
2147 found: k.family.clone(),
2148 },
2149 });
2150 return false;
2151 }
2152 }
2153
2154 let style_properties = [
2156 (
2157 "italic",
2158 pattern.italic.needs_to_match(),
2159 pattern.italic.matches(&k.italic),
2160 ),
2161 (
2162 "oblique",
2163 pattern.oblique.needs_to_match(),
2164 pattern.oblique.matches(&k.oblique),
2165 ),
2166 (
2167 "bold",
2168 pattern.bold.needs_to_match(),
2169 pattern.bold.matches(&k.bold),
2170 ),
2171 (
2172 "monospace",
2173 pattern.monospace.needs_to_match(),
2174 pattern.monospace.matches(&k.monospace),
2175 ),
2176 (
2177 "condensed",
2178 pattern.condensed.needs_to_match(),
2179 pattern.condensed.matches(&k.condensed),
2180 ),
2181 ];
2182
2183 for (property_name, needs_to_match, matches) in style_properties {
2184 if needs_to_match && !matches {
2185 let (requested, found) = match property_name {
2186 "italic" => (format!("{:?}", pattern.italic), format!("{:?}", k.italic)),
2187 "oblique" => (format!("{:?}", pattern.oblique), format!("{:?}", k.oblique)),
2188 "bold" => (format!("{:?}", pattern.bold), format!("{:?}", k.bold)),
2189 "monospace" => (
2190 format!("{:?}", pattern.monospace),
2191 format!("{:?}", k.monospace),
2192 ),
2193 "condensed" => (
2194 format!("{:?}", pattern.condensed),
2195 format!("{:?}", k.condensed),
2196 ),
2197 _ => (String::new(), String::new()),
2198 };
2199
2200 trace.push(TraceMsg {
2201 level: TraceLevel::Info,
2202 path: Self::trace_path(k),
2203 reason: MatchReason::StyleMismatch {
2204 property: property_name,
2205 requested,
2206 found,
2207 },
2208 });
2209 return false;
2210 }
2211 }
2212
2213 if pattern.weight != FcWeight::Normal && pattern.weight != k.weight {
2215 trace.push(TraceMsg {
2216 level: TraceLevel::Info,
2217 path: Self::trace_path(k),
2218 reason: MatchReason::WeightMismatch {
2219 requested: pattern.weight,
2220 found: k.weight,
2221 },
2222 });
2223 return false;
2224 }
2225
2226 if pattern.stretch != FcStretch::Normal && pattern.stretch != k.stretch {
2228 trace.push(TraceMsg {
2229 level: TraceLevel::Info,
2230 path: Self::trace_path(k),
2231 reason: MatchReason::StretchMismatch {
2232 requested: pattern.stretch,
2233 found: k.stretch,
2234 },
2235 });
2236 return false;
2237 }
2238
2239 if !pattern.unicode_ranges.is_empty() {
2241 let mut has_overlap = false;
2242
2243 for p_range in &pattern.unicode_ranges {
2244 for k_range in &k.unicode_ranges {
2245 if p_range.overlaps(k_range) {
2246 has_overlap = true;
2247 break;
2248 }
2249 }
2250 if has_overlap {
2251 break;
2252 }
2253 }
2254
2255 if !has_overlap {
2256 trace.push(TraceMsg {
2257 level: TraceLevel::Info,
2258 path: Self::trace_path(k),
2259 reason: MatchReason::UnicodeRangeMismatch {
2260 character: '\0', ranges: k.unicode_ranges.clone(),
2262 },
2263 });
2264 return false;
2265 }
2266 }
2267
2268 true
2269 }
2270
2271 #[cfg(feature = "std")]
2297 pub fn resolve_font_chain(
2298 &self,
2299 font_families: &[String],
2300 weight: FcWeight,
2301 italic: PatternMatch,
2302 oblique: PatternMatch,
2303 trace: &mut Vec<TraceMsg>,
2304 ) -> FontFallbackChain {
2305 self.resolve_font_chain_with_os(font_families, weight, italic, oblique, trace, OperatingSystem::current())
2306 }
2307
2308 #[cfg(feature = "std")]
2310 pub fn resolve_font_chain_with_os(
2311 &self,
2312 font_families: &[String],
2313 weight: FcWeight,
2314 italic: PatternMatch,
2315 oblique: PatternMatch,
2316 trace: &mut Vec<TraceMsg>,
2317 os: OperatingSystem,
2318 ) -> FontFallbackChain {
2319 self.resolve_font_chain_impl(font_families, weight, italic, oblique, None, trace, os)
2320 }
2321
2322 #[cfg(feature = "std")]
2337 pub fn resolve_font_chain_with_scripts(
2338 &self,
2339 font_families: &[String],
2340 weight: FcWeight,
2341 italic: PatternMatch,
2342 oblique: PatternMatch,
2343 scripts_hint: Option<&[UnicodeRange]>,
2344 trace: &mut Vec<TraceMsg>,
2345 ) -> FontFallbackChain {
2346 self.resolve_font_chain_impl(
2347 font_families, weight, italic, oblique, scripts_hint,
2348 trace, OperatingSystem::current(),
2349 )
2350 }
2351
2352 #[cfg(feature = "std")]
2356 fn resolve_font_chain_impl(
2357 &self,
2358 font_families: &[String],
2359 weight: FcWeight,
2360 italic: PatternMatch,
2361 oblique: PatternMatch,
2362 scripts_hint: Option<&[UnicodeRange]>,
2363 trace: &mut Vec<TraceMsg>,
2364 os: OperatingSystem,
2365 ) -> FontFallbackChain {
2366 let scripts_hint_hash = scripts_hint.map(hash_scripts_hint);
2370 let cache_key = FontChainCacheKey {
2371 font_families: font_families.to_vec(),
2372 weight,
2373 italic,
2374 oblique,
2375 scripts_hint_hash,
2376 };
2377
2378 if let Some(cached) = self
2379 .shared
2380 .chain_cache
2381 .lock()
2382 .ok()
2383 .and_then(|c| c.get(&cache_key).cloned())
2384 {
2385 return cached;
2386 }
2387
2388 let expanded_families = expand_font_families(font_families, os, &[]);
2390
2391 let chain = self.resolve_font_chain_uncached(
2393 &expanded_families,
2394 weight,
2395 italic,
2396 oblique,
2397 scripts_hint,
2398 trace,
2399 );
2400
2401 if let Ok(mut cache) = self.shared.chain_cache.lock() {
2403 cache.insert(cache_key, chain.clone());
2404 }
2405
2406 chain
2407 }
2408
2409 #[cfg(feature = "std")]
2417 fn resolve_font_chain_uncached(
2418 &self,
2419 font_families: &[String],
2420 weight: FcWeight,
2421 italic: PatternMatch,
2422 oblique: PatternMatch,
2423 scripts_hint: Option<&[UnicodeRange]>,
2424 trace: &mut Vec<TraceMsg>,
2425 ) -> FontFallbackChain {
2426 let mut css_fallbacks = Vec::new();
2427
2428 for (_i, family) in font_families.iter().enumerate() {
2430 let (pattern, is_generic) = if config::is_generic_family(family) {
2432 let monospace = if family.eq_ignore_ascii_case("monospace") {
2433 PatternMatch::True
2434 } else {
2435 PatternMatch::False
2436 };
2437 let pattern = FcPattern {
2438 name: None,
2439 weight,
2440 italic,
2441 oblique,
2442 monospace,
2443 unicode_ranges: Vec::new(),
2444 ..Default::default()
2445 };
2446 (pattern, true)
2447 } else {
2448 let pattern = FcPattern {
2450 name: Some(family.clone()),
2451 weight,
2452 italic,
2453 oblique,
2454 unicode_ranges: Vec::new(),
2455 ..Default::default()
2456 };
2457 (pattern, false)
2458 };
2459
2460 let mut matches = if is_generic {
2463 self.query_internal(&pattern, trace)
2465 } else {
2466 self.fuzzy_query_by_name(family, weight, italic, oblique, &[], trace)
2468 };
2469
2470 if is_generic && matches.len() > 5 {
2472 matches.truncate(5);
2473 }
2474
2475 css_fallbacks.push(CssFallbackGroup {
2478 css_name: family.clone(),
2479 fonts: matches,
2480 });
2481 }
2482
2483 let important_ranges: &[UnicodeRange] =
2496 scripts_hint.unwrap_or(DEFAULT_UNICODE_FALLBACK_SCRIPTS);
2497 let unicode_fallbacks = if important_ranges.is_empty() {
2498 Vec::new()
2499 } else {
2500 let all_uncovered = vec![false; important_ranges.len()];
2501 self.find_unicode_fallbacks(
2502 important_ranges,
2503 &all_uncovered,
2504 &css_fallbacks,
2505 weight,
2506 italic,
2507 oblique,
2508 trace,
2509 )
2510 };
2511
2512 FontFallbackChain {
2513 css_fallbacks,
2514 unicode_fallbacks,
2515 original_stack: font_families.to_vec(),
2516 }
2517 }
2518
2519 #[allow(dead_code)]
2521 fn extract_unicode_ranges(text: &str) -> Vec<UnicodeRange> {
2522 let mut chars: Vec<char> = text.chars().collect();
2523 chars.sort_unstable();
2524 chars.dedup();
2525
2526 if chars.is_empty() {
2527 return Vec::new();
2528 }
2529
2530 let mut ranges = Vec::new();
2531 let mut range_start = chars[0] as u32;
2532 let mut range_end = range_start;
2533
2534 for &c in &chars[1..] {
2535 let codepoint = c as u32;
2536 if codepoint == range_end + 1 {
2537 range_end = codepoint;
2538 } else {
2539 ranges.push(UnicodeRange { start: range_start, end: range_end });
2540 range_start = codepoint;
2541 range_end = codepoint;
2542 }
2543 }
2544
2545 ranges.push(UnicodeRange { start: range_start, end: range_end });
2546 ranges
2547 }
2548
2549 #[cfg(feature = "std")]
2556 fn fuzzy_query_by_name(
2557 &self,
2558 requested_name: &str,
2559 weight: FcWeight,
2560 italic: PatternMatch,
2561 oblique: PatternMatch,
2562 unicode_ranges: &[UnicodeRange],
2563 _trace: &mut Vec<TraceMsg>,
2564 ) -> Vec<FontMatch> {
2565 let tokens = Self::extract_font_name_tokens(requested_name);
2567
2568 if tokens.is_empty() {
2569 return Vec::new();
2570 }
2571
2572 let tokens_lower: Vec<String> = tokens.iter().map(|t| t.to_lowercase()).collect();
2574
2575 let state = self.state_read();
2581
2582 let first_token = &tokens_lower[0];
2584 let mut candidate_ids = match state.token_index.get(first_token) {
2585 Some(ids) if !ids.is_empty() => ids.clone(),
2586 _ => {
2587 return Vec::new();
2589 }
2590 };
2591
2592 for token in &tokens_lower[1..] {
2594 if let Some(token_ids) = state.token_index.get(token) {
2595 let intersection: alloc::collections::BTreeSet<FontId> =
2597 candidate_ids.intersection(token_ids).copied().collect();
2598
2599 if intersection.is_empty() {
2600 break;
2602 } else {
2603 candidate_ids = intersection;
2605 }
2606 } else {
2607 break;
2609 }
2610 }
2611
2612 let mut candidates = Vec::new();
2614
2615 for id in candidate_ids {
2616 let pattern = match state.metadata.get(&id) {
2617 Some(p) => p,
2618 None => continue,
2619 };
2620
2621 let font_tokens_lower = match state.font_tokens.get(&id) {
2623 Some(tokens) => tokens,
2624 None => continue,
2625 };
2626
2627 if font_tokens_lower.is_empty() {
2628 continue;
2629 }
2630
2631 let token_matches = tokens_lower.iter()
2634 .filter(|req_token| {
2635 font_tokens_lower.iter().any(|font_token| {
2636 font_token == *req_token
2638 })
2639 })
2640 .count();
2641
2642 if token_matches == 0 {
2644 continue;
2645 }
2646
2647 let token_similarity = (token_matches * 100 / tokens.len()) as i32;
2649
2650 let unicode_similarity = if !unicode_ranges.is_empty() && !pattern.unicode_ranges.is_empty() {
2652 Self::calculate_unicode_compatibility(unicode_ranges, &pattern.unicode_ranges)
2653 } else {
2654 0
2655 };
2656
2657 if !unicode_ranges.is_empty() && unicode_similarity == 0 {
2660 continue;
2661 }
2662
2663 let style_score = Self::calculate_style_score(&FcPattern {
2664 weight,
2665 italic,
2666 oblique,
2667 ..Default::default()
2668 }, pattern);
2669
2670 candidates.push((
2671 id,
2672 token_similarity,
2673 unicode_similarity,
2674 style_score,
2675 pattern.clone(),
2676 ));
2677 }
2678
2679 candidates.sort_by(|a, b| {
2685 if !unicode_ranges.is_empty() {
2686 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 {
2693 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)) }
2699 });
2700
2701 candidates.truncate(5);
2703
2704 candidates
2706 .into_iter()
2707 .map(|(id, _token_sim, _unicode_sim, _style, pattern)| {
2708 FontMatch {
2709 id,
2710 unicode_ranges: pattern.unicode_ranges.clone(),
2711 fallbacks: Vec::new(), }
2713 })
2714 .collect()
2715 }
2716
2717 pub fn extract_font_name_tokens(name: &str) -> Vec<String> {
2721 let mut tokens = Vec::new();
2722 let mut current_token = String::new();
2723 let mut last_was_lower = false;
2724
2725 for c in name.chars() {
2726 if c.is_whitespace() || c == '-' || c == '_' {
2727 if !current_token.is_empty() {
2729 tokens.push(current_token.clone());
2730 current_token.clear();
2731 }
2732 last_was_lower = false;
2733 } else if c.is_uppercase() && last_was_lower && !current_token.is_empty() {
2734 tokens.push(current_token.clone());
2736 current_token.clear();
2737 current_token.push(c);
2738 last_was_lower = false;
2739 } else {
2740 current_token.push(c);
2741 last_was_lower = c.is_lowercase();
2742 }
2743 }
2744
2745 if !current_token.is_empty() {
2746 tokens.push(current_token);
2747 }
2748
2749 tokens
2750 }
2751
2752 fn find_unicode_fallbacks(
2756 &self,
2757 unicode_ranges: &[UnicodeRange],
2758 covered_chars: &[bool],
2759 existing_groups: &[CssFallbackGroup],
2760 _weight: FcWeight,
2761 _italic: PatternMatch,
2762 _oblique: PatternMatch,
2763 trace: &mut Vec<TraceMsg>,
2764 ) -> Vec<FontMatch> {
2765 let mut uncovered_ranges = Vec::new();
2767 for (i, &covered) in covered_chars.iter().enumerate() {
2768 if !covered && i < unicode_ranges.len() {
2769 uncovered_ranges.push(unicode_ranges[i].clone());
2770 }
2771 }
2772
2773 if uncovered_ranges.is_empty() {
2774 return Vec::new();
2775 }
2776
2777 let pattern = FcPattern {
2782 name: None,
2783 weight: FcWeight::Normal, italic: PatternMatch::DontCare,
2785 oblique: PatternMatch::DontCare,
2786 unicode_ranges: uncovered_ranges.clone(),
2787 ..Default::default()
2788 };
2789
2790 let mut candidates = self.query_internal(&pattern, trace);
2791
2792 let existing_prefixes: Vec<String> = existing_groups
2795 .iter()
2796 .flat_map(|group| {
2797 group.fonts.iter().filter_map(|font| {
2798 self.get_metadata_by_id(&font.id)
2799 .and_then(|meta| meta.family.clone())
2800 .and_then(|family| {
2801 family.split_whitespace()
2803 .take(2)
2804 .collect::<Vec<_>>()
2805 .join(" ")
2806 .into()
2807 })
2808 })
2809 })
2810 .collect();
2811
2812 candidates.sort_by(|a, b| {
2816 let a_meta = self.get_metadata_by_id(&a.id);
2817 let b_meta = self.get_metadata_by_id(&b.id);
2818
2819 let a_score = Self::calculate_font_similarity_score(a_meta.as_ref(), &existing_prefixes);
2820 let b_score = Self::calculate_font_similarity_score(b_meta.as_ref(), &existing_prefixes);
2821
2822 b_score.cmp(&a_score) .then_with(|| {
2824 let a_coverage = Self::calculate_unicode_compatibility(&uncovered_ranges, &a.unicode_ranges);
2825 let b_coverage = Self::calculate_unicode_compatibility(&uncovered_ranges, &b.unicode_ranges);
2826 b_coverage.cmp(&a_coverage)
2827 })
2828 });
2829
2830 let mut result = Vec::new();
2832 let mut remaining_uncovered: Vec<bool> = vec![true; uncovered_ranges.len()];
2833
2834 for candidate in candidates {
2835 let mut covers_new_range = false;
2837
2838 for (i, range) in uncovered_ranges.iter().enumerate() {
2839 if remaining_uncovered[i] {
2840 for font_range in &candidate.unicode_ranges {
2842 if font_range.overlaps(range) {
2843 remaining_uncovered[i] = false;
2844 covers_new_range = true;
2845 break;
2846 }
2847 }
2848 }
2849 }
2850
2851 if covers_new_range {
2853 result.push(candidate);
2854
2855 if remaining_uncovered.iter().all(|&uncovered| !uncovered) {
2857 break;
2858 }
2859 }
2860 }
2861
2862 result
2863 }
2864
2865 fn calculate_font_similarity_score(
2868 font_meta: Option<&FcPattern>,
2869 existing_prefixes: &[String],
2870 ) -> i32 {
2871 let Some(meta) = font_meta else { return 0; };
2872 let Some(family) = &meta.family else { return 0; };
2873
2874 for prefix in existing_prefixes {
2876 if family.starts_with(prefix) {
2877 return 100; }
2879 if family.contains(prefix) {
2880 return 50; }
2882 }
2883
2884 0 }
2886
2887 pub fn calculate_unicode_coverage(ranges: &[UnicodeRange]) -> u64 {
2890 ranges
2891 .iter()
2892 .map(|range| (range.end - range.start + 1) as u64)
2893 .sum()
2894 }
2895
2896 pub fn calculate_unicode_compatibility(
2899 requested: &[UnicodeRange],
2900 available: &[UnicodeRange],
2901 ) -> i32 {
2902 if requested.is_empty() {
2903 return Self::calculate_unicode_coverage(available) as i32;
2905 }
2906
2907 let mut total_coverage = 0u32;
2908
2909 for req_range in requested {
2910 for avail_range in available {
2911 let overlap_start = req_range.start.max(avail_range.start);
2913 let overlap_end = req_range.end.min(avail_range.end);
2914
2915 if overlap_start <= overlap_end {
2916 let overlap_size = overlap_end - overlap_start + 1;
2918 total_coverage += overlap_size;
2919 }
2920 }
2921 }
2922
2923 total_coverage as i32
2924 }
2925
2926 pub fn calculate_style_score(original: &FcPattern, candidate: &FcPattern) -> i32 {
2927
2928 let mut score = 0_i32;
2929
2930 if (original.bold == PatternMatch::True && candidate.weight == FcWeight::Bold)
2932 || (original.bold == PatternMatch::False && candidate.weight != FcWeight::Bold)
2933 {
2934 } else {
2937 let weight_diff = (original.weight as i32 - candidate.weight as i32).abs();
2939 score += weight_diff as i32;
2940 }
2941
2942 if original.weight == candidate.weight {
2945 score -= 15;
2946 if original.weight == FcWeight::Normal {
2947 score -= 10; }
2949 }
2950
2951 if (original.condensed == PatternMatch::True && candidate.stretch.is_condensed())
2953 || (original.condensed == PatternMatch::False && !candidate.stretch.is_condensed())
2954 {
2955 } else {
2958 let stretch_diff = (original.stretch as i32 - candidate.stretch as i32).abs();
2960 score += (stretch_diff * 100) as i32;
2961 }
2962
2963 let style_props = [
2965 (original.italic, candidate.italic, 300, 150),
2966 (original.oblique, candidate.oblique, 200, 100),
2967 (original.bold, candidate.bold, 300, 150),
2968 (original.monospace, candidate.monospace, 100, 50),
2969 (original.condensed, candidate.condensed, 100, 50),
2970 ];
2971
2972 for (orig, cand, mismatch_penalty, dontcare_penalty) in style_props {
2973 if orig.needs_to_match() {
2974 if orig == PatternMatch::False && cand == PatternMatch::DontCare {
2975 score += dontcare_penalty / 2;
2978 } else if !orig.matches(&cand) {
2979 if cand == PatternMatch::DontCare {
2980 score += dontcare_penalty;
2981 } else {
2982 score += mismatch_penalty;
2983 }
2984 } else if orig == PatternMatch::True && cand == PatternMatch::True {
2985 score -= 20;
2987 } else if orig == PatternMatch::False && cand == PatternMatch::False {
2988 score -= 20;
2991 }
2992 } else {
2993 if cand == PatternMatch::True {
2998 score += dontcare_penalty / 3;
2999 }
3000 }
3001 }
3002
3003 if let (Some(name), Some(family)) = (&candidate.name, &candidate.family) {
3009 let name_lower = name.to_lowercase();
3010 let family_lower = family.to_lowercase();
3011
3012 let extra = if name_lower.starts_with(&family_lower) {
3014 name_lower[family_lower.len()..].to_string()
3015 } else {
3016 String::new()
3017 };
3018
3019 let stripped = extra
3021 .replace("regular", "")
3022 .replace("normal", "")
3023 .replace("book", "")
3024 .replace("roman", "");
3025 let stripped = stripped.trim();
3026
3027 if stripped.is_empty() {
3028 score -= 50;
3030 } else {
3031 let extra_words = stripped.split_whitespace().count();
3033 score += (extra_words as i32) * 25;
3034 }
3035 }
3036
3037 if let Some(ref subfamily) = candidate.metadata.font_subfamily {
3041 let sf_lower = subfamily.to_lowercase();
3042 if sf_lower == "regular" {
3043 score -= 30;
3044 }
3045 }
3046
3047 score
3048 }
3049}
3050
3051#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3052fn FcScanDirectories() -> Option<(Vec<(FcPattern, FcFontPath)>, BTreeMap<String, FcFontRenderConfig>)> {
3053 use std::fs;
3054 use std::path::Path;
3055
3056 const BASE_FONTCONFIG_PATH: &str = "/etc/fonts/fonts.conf";
3057
3058 if !Path::new(BASE_FONTCONFIG_PATH).exists() {
3059 return None;
3060 }
3061
3062 let mut font_paths = Vec::with_capacity(32);
3063 let mut paths_to_visit = vec![(None, PathBuf::from(BASE_FONTCONFIG_PATH))];
3064 let mut render_configs: BTreeMap<String, FcFontRenderConfig> = BTreeMap::new();
3065
3066 while let Some((prefix, path_to_visit)) = paths_to_visit.pop() {
3067 let path = match process_path(&prefix, path_to_visit, true) {
3068 Some(path) => path,
3069 None => continue,
3070 };
3071
3072 let metadata = match fs::metadata(&path) {
3073 Ok(metadata) => metadata,
3074 Err(_) => continue,
3075 };
3076
3077 if metadata.is_file() {
3078 let xml_utf8 = match fs::read_to_string(&path) {
3079 Ok(xml_utf8) => xml_utf8,
3080 Err(_) => continue,
3081 };
3082
3083 if ParseFontsConf(&xml_utf8, &mut paths_to_visit, &mut font_paths).is_none() {
3084 continue;
3085 }
3086
3087 ParseFontsConfRenderConfig(&xml_utf8, &mut render_configs);
3089 } else if metadata.is_dir() {
3090 let dir_entries = match fs::read_dir(&path) {
3091 Ok(dir_entries) => dir_entries,
3092 Err(_) => continue,
3093 };
3094
3095 for entry_result in dir_entries {
3096 let entry = match entry_result {
3097 Ok(entry) => entry,
3098 Err(_) => continue,
3099 };
3100
3101 let entry_path = entry.path();
3102
3103 let entry_metadata = match fs::metadata(&entry_path) {
3105 Ok(metadata) => metadata,
3106 Err(_) => continue,
3107 };
3108
3109 if !entry_metadata.is_file() {
3110 continue;
3111 }
3112
3113 let file_name = match entry_path.file_name() {
3114 Some(name) => name,
3115 None => continue,
3116 };
3117
3118 let file_name_str = file_name.to_string_lossy();
3119 if file_name_str.starts_with(|c: char| c.is_ascii_digit())
3120 && file_name_str.ends_with(".conf")
3121 {
3122 paths_to_visit.push((None, entry_path));
3123 }
3124 }
3125 }
3126 }
3127
3128 if font_paths.is_empty() {
3129 return None;
3130 }
3131
3132 Some((FcScanDirectoriesInner(&font_paths), render_configs))
3133}
3134
3135#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3137fn ParseFontsConf(
3138 input: &str,
3139 paths_to_visit: &mut Vec<(Option<String>, PathBuf)>,
3140 font_paths: &mut Vec<(Option<String>, String)>,
3141) -> Option<()> {
3142 use xmlparser::Token::*;
3143 use xmlparser::Tokenizer;
3144
3145 const TAG_INCLUDE: &str = "include";
3146 const TAG_DIR: &str = "dir";
3147 const ATTRIBUTE_PREFIX: &str = "prefix";
3148
3149 let mut current_prefix: Option<&str> = None;
3150 let mut current_path: Option<&str> = None;
3151 let mut is_in_include = false;
3152 let mut is_in_dir = false;
3153
3154 for token_result in Tokenizer::from(input) {
3155 let token = match token_result {
3156 Ok(token) => token,
3157 Err(_) => return None,
3158 };
3159
3160 match token {
3161 ElementStart { local, .. } => {
3162 if is_in_include || is_in_dir {
3163 return None; }
3165
3166 match local.as_str() {
3167 TAG_INCLUDE => {
3168 is_in_include = true;
3169 }
3170 TAG_DIR => {
3171 is_in_dir = true;
3172 }
3173 _ => continue,
3174 }
3175
3176 current_path = None;
3177 }
3178 Text { text, .. } => {
3179 let text = text.as_str().trim();
3180 if text.is_empty() {
3181 continue;
3182 }
3183 if is_in_include || is_in_dir {
3184 current_path = Some(text);
3185 }
3186 }
3187 Attribute { local, value, .. } => {
3188 if !is_in_include && !is_in_dir {
3189 continue;
3190 }
3191 if local.as_str() == ATTRIBUTE_PREFIX {
3193 current_prefix = Some(value.as_str());
3194 }
3195 }
3196 ElementEnd { end, .. } => {
3197 let end_tag = match end {
3198 xmlparser::ElementEnd::Close(_, a) => a,
3199 _ => continue,
3200 };
3201
3202 match end_tag.as_str() {
3203 TAG_INCLUDE => {
3204 if !is_in_include {
3205 continue;
3206 }
3207
3208 if let Some(current_path) = current_path.as_ref() {
3209 paths_to_visit.push((
3210 current_prefix.map(ToOwned::to_owned),
3211 PathBuf::from(*current_path),
3212 ));
3213 }
3214 }
3215 TAG_DIR => {
3216 if !is_in_dir {
3217 continue;
3218 }
3219
3220 if let Some(current_path) = current_path.as_ref() {
3221 font_paths.push((
3222 current_prefix.map(ToOwned::to_owned),
3223 (*current_path).to_owned(),
3224 ));
3225 }
3226 }
3227 _ => continue,
3228 }
3229
3230 is_in_include = false;
3231 is_in_dir = false;
3232 current_path = None;
3233 current_prefix = None;
3234 }
3235 _ => {}
3236 }
3237 }
3238
3239 Some(())
3240}
3241
3242#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3254fn ParseFontsConfRenderConfig(
3255 input: &str,
3256 configs: &mut BTreeMap<String, FcFontRenderConfig>,
3257) {
3258 use xmlparser::Token::*;
3259 use xmlparser::Tokenizer;
3260
3261 #[derive(Clone, Copy, PartialEq)]
3263 enum State {
3264 Idle,
3266 InMatchFont,
3268 InTestFamily,
3270 InEdit,
3272 InValue,
3274 }
3275
3276 let mut state = State::Idle;
3277 let mut match_is_font_target = false;
3278 let mut current_family: Option<String> = None;
3279 let mut current_edit_name: Option<String> = None;
3280 let mut current_value: Option<String> = None;
3281 let mut value_tag: Option<String> = None;
3282 let mut config = FcFontRenderConfig::default();
3283 let mut in_test = false;
3284 let mut test_name: Option<String> = None;
3285
3286 for token_result in Tokenizer::from(input) {
3287 let token = match token_result {
3288 Ok(token) => token,
3289 Err(_) => continue,
3290 };
3291
3292 match token {
3293 ElementStart { local, .. } => {
3294 let tag = local.as_str();
3295 match tag {
3296 "match" => {
3297 match_is_font_target = false;
3299 current_family = None;
3300 config = FcFontRenderConfig::default();
3301 }
3302 "test" if state == State::InMatchFont => {
3303 in_test = true;
3304 test_name = None;
3305 }
3306 "edit" if state == State::InMatchFont => {
3307 current_edit_name = None;
3308 }
3309 "bool" | "double" | "const" | "string" | "int" => {
3310 if state == State::InTestFamily || state == State::InEdit {
3311 value_tag = Some(tag.to_owned());
3312 current_value = None;
3313 }
3314 }
3315 _ => {}
3316 }
3317 }
3318 Attribute { local, value, .. } => {
3319 let attr_name = local.as_str();
3320 let attr_value = value.as_str();
3321
3322 match attr_name {
3323 "target" => {
3324 if attr_value == "font" {
3325 match_is_font_target = true;
3326 }
3327 }
3328 "name" => {
3329 if in_test && state == State::InMatchFont {
3330 test_name = Some(attr_value.to_owned());
3331 } else if state == State::InMatchFont {
3332 current_edit_name = Some(attr_value.to_owned());
3333 }
3334 }
3335 _ => {}
3336 }
3337 }
3338 Text { text, .. } => {
3339 let text = text.as_str().trim();
3340 if !text.is_empty() && (state == State::InTestFamily || state == State::InEdit) {
3341 current_value = Some(text.to_owned());
3342 }
3343 }
3344 ElementEnd { end, .. } => {
3345 match end {
3346 xmlparser::ElementEnd::Open => {
3347 if match_is_font_target && state == State::Idle {
3349 state = State::InMatchFont;
3350 match_is_font_target = false;
3351 } else if in_test {
3352 if test_name.as_deref() == Some("family") {
3353 state = State::InTestFamily;
3354 }
3355 in_test = false;
3356 } else if current_edit_name.is_some() && state == State::InMatchFont {
3357 state = State::InEdit;
3358 }
3359 }
3360 xmlparser::ElementEnd::Close(_, local) => {
3361 let tag = local.as_str();
3362 match tag {
3363 "match" => {
3364 if let Some(family) = current_family.take() {
3366 let empty = FcFontRenderConfig::default();
3367 if config != empty {
3368 configs.insert(family, config.clone());
3369 }
3370 }
3371 state = State::Idle;
3372 config = FcFontRenderConfig::default();
3373 }
3374 "test" => {
3375 if state == State::InTestFamily {
3376 if let Some(ref val) = current_value {
3378 current_family = Some(val.clone());
3379 }
3380 state = State::InMatchFont;
3381 }
3382 current_value = None;
3383 value_tag = None;
3384 }
3385 "edit" => {
3386 if state == State::InEdit {
3387 if let (Some(ref name), Some(ref val)) = (¤t_edit_name, ¤t_value) {
3389 apply_edit_value(&mut config, name, val, value_tag.as_deref());
3390 }
3391 state = State::InMatchFont;
3392 }
3393 current_edit_name = None;
3394 current_value = None;
3395 value_tag = None;
3396 }
3397 "bool" | "double" | "const" | "string" | "int" => {
3398 }
3400 _ => {}
3401 }
3402 }
3403 xmlparser::ElementEnd::Empty => {
3404 }
3406 }
3407 }
3408 _ => {}
3409 }
3410 }
3411}
3412
3413#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3415fn apply_edit_value(
3416 config: &mut FcFontRenderConfig,
3417 edit_name: &str,
3418 value: &str,
3419 value_tag: Option<&str>,
3420) {
3421 match edit_name {
3422 "antialias" => {
3423 config.antialias = parse_bool_value(value);
3424 }
3425 "hinting" => {
3426 config.hinting = parse_bool_value(value);
3427 }
3428 "autohint" => {
3429 config.autohint = parse_bool_value(value);
3430 }
3431 "embeddedbitmap" => {
3432 config.embeddedbitmap = parse_bool_value(value);
3433 }
3434 "embolden" => {
3435 config.embolden = parse_bool_value(value);
3436 }
3437 "minspace" => {
3438 config.minspace = parse_bool_value(value);
3439 }
3440 "hintstyle" => {
3441 config.hintstyle = parse_hintstyle_const(value);
3442 }
3443 "rgba" => {
3444 config.rgba = parse_rgba_const(value);
3445 }
3446 "lcdfilter" => {
3447 config.lcdfilter = parse_lcdfilter_const(value);
3448 }
3449 "dpi" => {
3450 if let Ok(v) = value.parse::<f64>() {
3451 config.dpi = Some(v);
3452 }
3453 }
3454 "scale" => {
3455 if let Ok(v) = value.parse::<f64>() {
3456 config.scale = Some(v);
3457 }
3458 }
3459 _ => {
3460 }
3462 }
3463}
3464
3465#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3466fn parse_bool_value(value: &str) -> Option<bool> {
3467 match value {
3468 "true" => Some(true),
3469 "false" => Some(false),
3470 _ => None,
3471 }
3472}
3473
3474#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3475fn parse_hintstyle_const(value: &str) -> Option<FcHintStyle> {
3476 match value {
3477 "hintnone" => Some(FcHintStyle::None),
3478 "hintslight" => Some(FcHintStyle::Slight),
3479 "hintmedium" => Some(FcHintStyle::Medium),
3480 "hintfull" => Some(FcHintStyle::Full),
3481 _ => None,
3482 }
3483}
3484
3485#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3486fn parse_rgba_const(value: &str) -> Option<FcRgba> {
3487 match value {
3488 "unknown" => Some(FcRgba::Unknown),
3489 "rgb" => Some(FcRgba::Rgb),
3490 "bgr" => Some(FcRgba::Bgr),
3491 "vrgb" => Some(FcRgba::Vrgb),
3492 "vbgr" => Some(FcRgba::Vbgr),
3493 "none" => Some(FcRgba::None),
3494 _ => None,
3495 }
3496}
3497
3498#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3499fn parse_lcdfilter_const(value: &str) -> Option<FcLcdFilter> {
3500 match value {
3501 "lcdnone" => Some(FcLcdFilter::None),
3502 "lcddefault" => Some(FcLcdFilter::Default),
3503 "lcdlight" => Some(FcLcdFilter::Light),
3504 "lcdlegacy" => Some(FcLcdFilter::Legacy),
3505 _ => None,
3506 }
3507}
3508
3509#[cfg(all(feature = "std", feature = "parsing"))]
3512const UNICODE_RANGE_MAPPINGS: &[(usize, u32, u32)] = &[
3513 (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), ];
3646
3647#[cfg(all(feature = "std", feature = "parsing"))]
3650struct ParsedFontFace {
3651 pattern: FcPattern,
3652 font_index: usize,
3653}
3654
3655#[cfg(all(feature = "std", feature = "parsing"))]
3661fn parse_font_faces(font_bytes: &[u8]) -> Option<Vec<ParsedFontFace>> {
3662 use allsorts::{
3663 binary::read::ReadScope,
3664 font_data::FontData,
3665 get_name::fontcode_get_name,
3666 post::PostTable,
3667 tables::{
3668 os2::Os2, HeadTable, NameTable,
3669 },
3670 tag,
3671 };
3672 use std::collections::BTreeSet;
3673
3674 const FONT_SPECIFIER_NAME_ID: u16 = 4;
3675 const FONT_SPECIFIER_FAMILY_ID: u16 = 1;
3676
3677 let max_fonts = if font_bytes.len() >= 12 && &font_bytes[0..4] == b"ttcf" {
3678 let num_fonts =
3680 u32::from_be_bytes([font_bytes[8], font_bytes[9], font_bytes[10], font_bytes[11]]);
3681 std::cmp::min(num_fonts as usize, 100)
3683 } else {
3684 1
3686 };
3687
3688 let scope = ReadScope::new(font_bytes);
3689 let font_file = scope.read::<FontData<'_>>().ok()?;
3690
3691 let mut results = Vec::new();
3693
3694 for font_index in 0..max_fonts {
3695 let provider = font_file.table_provider(font_index).ok()?;
3696 let head_data = provider.table_data(tag::HEAD).ok()??.into_owned();
3697 let head_table = ReadScope::new(&head_data).read::<HeadTable>().ok()?;
3698
3699 let is_bold = head_table.is_bold();
3700 let is_italic = head_table.is_italic();
3701 let mut detected_monospace = None;
3702
3703 let post_data = provider.table_data(tag::POST).ok()??;
3704 if let Ok(post_table) = ReadScope::new(&post_data).read::<PostTable>() {
3705 detected_monospace = Some(post_table.header.is_fixed_pitch != 0);
3707 }
3708
3709 let os2_data = provider.table_data(tag::OS_2).ok()??;
3711 let os2_table = ReadScope::new(&os2_data)
3712 .read_dep::<Os2>(os2_data.len())
3713 .ok()?;
3714
3715 let is_oblique = os2_table
3717 .fs_selection
3718 .contains(allsorts::tables::os2::FsSelection::OBLIQUE);
3719 let weight = FcWeight::from_u16(os2_table.us_weight_class);
3720 let stretch = FcStretch::from_u16(os2_table.us_width_class);
3721
3722 let mut unicode_ranges = Vec::new();
3726
3727 let os2_ranges = [
3729 os2_table.ul_unicode_range1,
3730 os2_table.ul_unicode_range2,
3731 os2_table.ul_unicode_range3,
3732 os2_table.ul_unicode_range4,
3733 ];
3734
3735 for &(bit, start, end) in UNICODE_RANGE_MAPPINGS {
3736 let range_idx = bit / 32;
3737 let bit_pos = bit % 32;
3738 if range_idx < 4 && (os2_ranges[range_idx] & (1 << bit_pos)) != 0 {
3739 unicode_ranges.push(UnicodeRange { start, end });
3740 }
3741 }
3742
3743 unicode_ranges = verify_unicode_ranges_with_cmap(&provider, unicode_ranges);
3747
3748 if unicode_ranges.is_empty() {
3750 if let Some(cmap_ranges) = analyze_cmap_coverage(&provider) {
3751 unicode_ranges = cmap_ranges;
3752 }
3753 }
3754
3755 let is_monospace = detect_monospace(&provider, &os2_table, detected_monospace)
3757 .unwrap_or(false);
3758
3759 let name_data = provider.table_data(tag::NAME).ok()??.into_owned();
3760 let name_table = ReadScope::new(&name_data).read::<NameTable>().ok()?;
3761
3762 let mut metadata = FcFontMetadata::default();
3764
3765 const NAME_ID_COPYRIGHT: u16 = 0;
3766 const NAME_ID_FAMILY: u16 = 1;
3767 const NAME_ID_SUBFAMILY: u16 = 2;
3768 const NAME_ID_UNIQUE_ID: u16 = 3;
3769 const NAME_ID_FULL_NAME: u16 = 4;
3770 const NAME_ID_VERSION: u16 = 5;
3771 const NAME_ID_POSTSCRIPT_NAME: u16 = 6;
3772 const NAME_ID_TRADEMARK: u16 = 7;
3773 const NAME_ID_MANUFACTURER: u16 = 8;
3774 const NAME_ID_DESIGNER: u16 = 9;
3775 const NAME_ID_DESCRIPTION: u16 = 10;
3776 const NAME_ID_VENDOR_URL: u16 = 11;
3777 const NAME_ID_DESIGNER_URL: u16 = 12;
3778 const NAME_ID_LICENSE: u16 = 13;
3779 const NAME_ID_LICENSE_URL: u16 = 14;
3780 const NAME_ID_PREFERRED_FAMILY: u16 = 16;
3781 const NAME_ID_PREFERRED_SUBFAMILY: u16 = 17;
3782
3783 metadata.copyright = get_name_string(&name_data, NAME_ID_COPYRIGHT);
3784 metadata.font_family = get_name_string(&name_data, NAME_ID_FAMILY);
3785 metadata.font_subfamily = get_name_string(&name_data, NAME_ID_SUBFAMILY);
3786 metadata.full_name = get_name_string(&name_data, NAME_ID_FULL_NAME);
3787 metadata.unique_id = get_name_string(&name_data, NAME_ID_UNIQUE_ID);
3788 metadata.version = get_name_string(&name_data, NAME_ID_VERSION);
3789 metadata.postscript_name = get_name_string(&name_data, NAME_ID_POSTSCRIPT_NAME);
3790 metadata.trademark = get_name_string(&name_data, NAME_ID_TRADEMARK);
3791 metadata.manufacturer = get_name_string(&name_data, NAME_ID_MANUFACTURER);
3792 metadata.designer = get_name_string(&name_data, NAME_ID_DESIGNER);
3793 metadata.id_description = get_name_string(&name_data, NAME_ID_DESCRIPTION);
3794 metadata.designer_url = get_name_string(&name_data, NAME_ID_DESIGNER_URL);
3795 metadata.manufacturer_url = get_name_string(&name_data, NAME_ID_VENDOR_URL);
3796 metadata.license = get_name_string(&name_data, NAME_ID_LICENSE);
3797 metadata.license_url = get_name_string(&name_data, NAME_ID_LICENSE_URL);
3798 metadata.preferred_family = get_name_string(&name_data, NAME_ID_PREFERRED_FAMILY);
3799 metadata.preferred_subfamily = get_name_string(&name_data, NAME_ID_PREFERRED_SUBFAMILY);
3800
3801 let mut f_family = None;
3803
3804 let patterns = name_table
3805 .name_records
3806 .iter()
3807 .filter_map(|name_record| {
3808 let name_id = name_record.name_id;
3809 if name_id == FONT_SPECIFIER_FAMILY_ID {
3810 if let Ok(Some(family)) =
3811 fontcode_get_name(&name_data, FONT_SPECIFIER_FAMILY_ID)
3812 {
3813 f_family = Some(family);
3814 }
3815 None
3816 } else if name_id == FONT_SPECIFIER_NAME_ID {
3817 let family = f_family.as_ref()?;
3818 let name = fontcode_get_name(&name_data, FONT_SPECIFIER_NAME_ID).ok()??;
3819 if name.to_bytes().is_empty() {
3820 None
3821 } else {
3822 let mut name_str =
3823 String::from_utf8_lossy(name.to_bytes()).to_string();
3824 let mut family_str =
3825 String::from_utf8_lossy(family.as_bytes()).to_string();
3826 if name_str.starts_with('.') {
3827 name_str = name_str[1..].to_string();
3828 }
3829 if family_str.starts_with('.') {
3830 family_str = family_str[1..].to_string();
3831 }
3832 Some((
3833 FcPattern {
3834 name: Some(name_str),
3835 family: Some(family_str),
3836 bold: if is_bold {
3837 PatternMatch::True
3838 } else {
3839 PatternMatch::False
3840 },
3841 italic: if is_italic {
3842 PatternMatch::True
3843 } else {
3844 PatternMatch::False
3845 },
3846 oblique: if is_oblique {
3847 PatternMatch::True
3848 } else {
3849 PatternMatch::False
3850 },
3851 monospace: if is_monospace {
3852 PatternMatch::True
3853 } else {
3854 PatternMatch::False
3855 },
3856 condensed: if stretch <= FcStretch::Condensed {
3857 PatternMatch::True
3858 } else {
3859 PatternMatch::False
3860 },
3861 weight,
3862 stretch,
3863 unicode_ranges: unicode_ranges.clone(),
3864 metadata: metadata.clone(),
3865 render_config: FcFontRenderConfig::default(),
3866 },
3867 font_index,
3868 ))
3869 }
3870 } else {
3871 None
3872 }
3873 })
3874 .collect::<BTreeSet<_>>();
3875
3876 results.extend(patterns.into_iter().map(|(pat, idx)| ParsedFontFace {
3877 pattern: pat,
3878 font_index: idx,
3879 }));
3880 }
3881
3882 if results.is_empty() {
3883 None
3884 } else {
3885 Some(results)
3886 }
3887}
3888
3889#[cfg(all(feature = "std", feature = "parsing"))]
3891pub(crate) fn FcParseFont(filepath: &PathBuf) -> Option<Vec<(FcPattern, FcFontPath)>> {
3892 #[cfg(all(not(target_family = "wasm"), feature = "std"))]
3893 use mmapio::MmapOptions;
3894 use std::fs::File;
3895
3896 let file = File::open(filepath).ok()?;
3898
3899 #[cfg(all(not(target_family = "wasm"), feature = "std"))]
3900 let font_bytes = unsafe { MmapOptions::new().map(&file).ok()? };
3901
3902 #[cfg(not(all(not(target_family = "wasm"), feature = "std")))]
3903 let font_bytes = std::fs::read(filepath).ok()?;
3904
3905 let faces = parse_font_faces(&font_bytes[..])?;
3906 let path_str = filepath.to_string_lossy().to_string();
3907 let bytes_hash = crate::utils::content_dedup_hash_u64(&font_bytes[..]);
3912
3913 Some(
3914 faces
3915 .into_iter()
3916 .map(|face| {
3917 (
3918 face.pattern,
3919 FcFontPath {
3920 path: path_str.clone(),
3921 font_index: face.font_index,
3922 bytes_hash,
3923 },
3924 )
3925 })
3926 .collect(),
3927 )
3928}
3929
3930#[cfg(all(feature = "std", feature = "parsing"))]
3956#[allow(non_snake_case)]
3957pub fn FcParseFontBytes(font_bytes: &[u8], font_id: &str) -> Option<Vec<(FcPattern, FcFont)>> {
3958 FcParseFontBytesInner(font_bytes, font_id)
3959}
3960
3961#[cfg(all(feature = "std", feature = "parsing"))]
3964fn FcParseFontBytesInner(font_bytes: &[u8], font_id: &str) -> Option<Vec<(FcPattern, FcFont)>> {
3965 let faces = parse_font_faces(font_bytes)?;
3966 let id = font_id.to_string();
3967 let bytes = font_bytes.to_vec();
3968
3969 Some(
3970 faces
3971 .into_iter()
3972 .map(|face| {
3973 (
3974 face.pattern,
3975 FcFont {
3976 bytes: bytes.clone(),
3977 font_index: face.font_index,
3978 id: id.clone(),
3979 },
3980 )
3981 })
3982 .collect(),
3983 )
3984}
3985
3986#[cfg(all(feature = "std", feature = "parsing"))]
3987fn FcScanDirectoriesInner(paths: &[(Option<String>, String)]) -> Vec<(FcPattern, FcFontPath)> {
3988 #[cfg(feature = "multithreading")]
3989 {
3990 use rayon::prelude::*;
3991
3992 paths
3994 .par_iter()
3995 .filter_map(|(prefix, p)| {
3996 process_path(prefix, PathBuf::from(p), false).map(FcScanSingleDirectoryRecursive)
3997 })
3998 .flatten()
3999 .collect()
4000 }
4001 #[cfg(not(feature = "multithreading"))]
4002 {
4003 paths
4004 .iter()
4005 .filter_map(|(prefix, p)| {
4006 process_path(prefix, PathBuf::from(p), false).map(FcScanSingleDirectoryRecursive)
4007 })
4008 .flatten()
4009 .collect()
4010 }
4011}
4012
4013#[cfg(feature = "std")]
4015fn FcCollectFontFilesRecursive(dir: PathBuf) -> Vec<PathBuf> {
4016 let mut files = Vec::new();
4017 let mut dirs_to_parse = vec![dir];
4018
4019 loop {
4020 let mut new_dirs = Vec::new();
4021 for dir in &dirs_to_parse {
4022 let entries = match std::fs::read_dir(dir) {
4023 Ok(o) => o,
4024 Err(_) => continue,
4025 };
4026 for entry in entries.flatten() {
4027 let path = entry.path();
4028 if path.is_dir() {
4029 new_dirs.push(path);
4030 } else {
4031 files.push(path);
4032 }
4033 }
4034 }
4035 if new_dirs.is_empty() {
4036 break;
4037 }
4038 dirs_to_parse = new_dirs;
4039 }
4040
4041 files
4042}
4043
4044#[cfg(all(feature = "std", feature = "parsing"))]
4045fn FcScanSingleDirectoryRecursive(dir: PathBuf) -> Vec<(FcPattern, FcFontPath)> {
4046 let files = FcCollectFontFilesRecursive(dir);
4047 FcParseFontFiles(&files)
4048}
4049
4050#[cfg(all(feature = "std", feature = "parsing"))]
4051fn FcParseFontFiles(files_to_parse: &[PathBuf]) -> Vec<(FcPattern, FcFontPath)> {
4052 let result = {
4053 #[cfg(feature = "multithreading")]
4054 {
4055 use rayon::prelude::*;
4056
4057 files_to_parse
4058 .par_iter()
4059 .filter_map(|file| FcParseFont(file))
4060 .collect::<Vec<Vec<_>>>()
4061 }
4062 #[cfg(not(feature = "multithreading"))]
4063 {
4064 files_to_parse
4065 .iter()
4066 .filter_map(|file| FcParseFont(file))
4067 .collect::<Vec<Vec<_>>>()
4068 }
4069 };
4070
4071 result.into_iter().flat_map(|f| f.into_iter()).collect()
4072}
4073
4074#[cfg(all(feature = "std", feature = "parsing"))]
4075fn process_path(
4079 prefix: &Option<String>,
4080 mut path: PathBuf,
4081 is_include_path: bool,
4082) -> Option<PathBuf> {
4083 use std::env::var;
4084
4085 const HOME_SHORTCUT: &str = "~";
4086 const CWD_PATH: &str = ".";
4087
4088 const HOME_ENV_VAR: &str = "HOME";
4089 const XDG_CONFIG_HOME_ENV_VAR: &str = "XDG_CONFIG_HOME";
4090 const XDG_CONFIG_HOME_DEFAULT_PATH_SUFFIX: &str = ".config";
4091 const XDG_DATA_HOME_ENV_VAR: &str = "XDG_DATA_HOME";
4092 const XDG_DATA_HOME_DEFAULT_PATH_SUFFIX: &str = ".local/share";
4093
4094 const PREFIX_CWD: &str = "cwd";
4095 const PREFIX_DEFAULT: &str = "default";
4096 const PREFIX_XDG: &str = "xdg";
4097
4098 fn get_home_value() -> Option<PathBuf> {
4100 var(HOME_ENV_VAR).ok().map(PathBuf::from)
4101 }
4102 fn get_xdg_config_home_value() -> Option<PathBuf> {
4103 var(XDG_CONFIG_HOME_ENV_VAR)
4104 .ok()
4105 .map(PathBuf::from)
4106 .or_else(|| {
4107 get_home_value()
4108 .map(|home_path| home_path.join(XDG_CONFIG_HOME_DEFAULT_PATH_SUFFIX))
4109 })
4110 }
4111 fn get_xdg_data_home_value() -> Option<PathBuf> {
4112 var(XDG_DATA_HOME_ENV_VAR)
4113 .ok()
4114 .map(PathBuf::from)
4115 .or_else(|| {
4116 get_home_value().map(|home_path| home_path.join(XDG_DATA_HOME_DEFAULT_PATH_SUFFIX))
4117 })
4118 }
4119
4120 if path.starts_with(HOME_SHORTCUT) {
4122 if let Some(home_path) = get_home_value() {
4123 path = home_path.join(
4124 path.strip_prefix(HOME_SHORTCUT)
4125 .expect("already checked that it starts with the prefix"),
4126 );
4127 } else {
4128 return None;
4129 }
4130 }
4131
4132 match prefix {
4134 Some(prefix) => match prefix.as_str() {
4135 PREFIX_CWD | PREFIX_DEFAULT => {
4136 let mut new_path = PathBuf::from(CWD_PATH);
4137 new_path.push(path);
4138
4139 Some(new_path)
4140 }
4141 PREFIX_XDG => {
4142 if is_include_path {
4143 get_xdg_config_home_value()
4144 .map(|xdg_config_home_path| xdg_config_home_path.join(path))
4145 } else {
4146 get_xdg_data_home_value()
4147 .map(|xdg_data_home_path| xdg_data_home_path.join(path))
4148 }
4149 }
4150 _ => None, },
4152 None => Some(path),
4153 }
4154}
4155
4156#[cfg(all(feature = "std", feature = "parsing"))]
4158fn get_name_string(name_data: &[u8], name_id: u16) -> Option<String> {
4159 fontcode_get_name(name_data, name_id)
4160 .ok()
4161 .flatten()
4162 .map(|name| String::from_utf8_lossy(name.to_bytes()).to_string())
4163}
4164
4165#[cfg(all(feature = "std", feature = "parsing"))]
4169fn get_verification_codepoints(start: u32, end: u32) -> Vec<u32> {
4170 match start {
4171 0x0000 => vec!['A' as u32, 'M' as u32, 'Z' as u32, 'a' as u32, 'm' as u32, 'z' as u32],
4173 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], _ => {
4249 let range_size = end - start;
4250 if range_size > 20 {
4251 vec![
4252 start + range_size / 5,
4253 start + 2 * range_size / 5,
4254 start + 3 * range_size / 5,
4255 start + 4 * range_size / 5,
4256 ]
4257 } else {
4258 vec![start, start + range_size / 2]
4259 }
4260 }
4261 }
4262}
4263
4264#[cfg(all(feature = "std", feature = "parsing"))]
4267fn find_best_cmap_subtable<'a>(
4268 cmap: &allsorts::tables::cmap::Cmap<'a>,
4269) -> Option<allsorts::tables::cmap::EncodingRecord> {
4270 use allsorts::tables::cmap::{PlatformId, EncodingId};
4271
4272 cmap.find_subtable(PlatformId::UNICODE, EncodingId(3))
4273 .or_else(|| cmap.find_subtable(PlatformId::UNICODE, EncodingId(4)))
4274 .or_else(|| cmap.find_subtable(PlatformId::WINDOWS, EncodingId(1)))
4275 .or_else(|| cmap.find_subtable(PlatformId::WINDOWS, EncodingId(10)))
4276 .or_else(|| cmap.find_subtable(PlatformId::UNICODE, EncodingId(0)))
4277 .or_else(|| cmap.find_subtable(PlatformId::UNICODE, EncodingId(1)))
4278}
4279
4280#[cfg(all(feature = "std", feature = "parsing"))]
4283fn verify_unicode_ranges_with_cmap(
4284 provider: &impl FontTableProvider,
4285 os2_ranges: Vec<UnicodeRange>
4286) -> Vec<UnicodeRange> {
4287 use allsorts::tables::cmap::{Cmap, CmapSubtable};
4288
4289 if os2_ranges.is_empty() {
4290 return Vec::new();
4291 }
4292
4293 let cmap_data = match provider.table_data(tag::CMAP) {
4295 Ok(Some(data)) => data,
4296 _ => return os2_ranges, };
4298
4299 let cmap = match ReadScope::new(&cmap_data).read::<Cmap<'_>>() {
4300 Ok(c) => c,
4301 Err(_) => return os2_ranges,
4302 };
4303
4304 let encoding_record = match find_best_cmap_subtable(&cmap) {
4305 Some(r) => r,
4306 None => return os2_ranges, };
4308
4309 let cmap_subtable = match ReadScope::new(&cmap_data)
4310 .offset(encoding_record.offset as usize)
4311 .read::<CmapSubtable<'_>>()
4312 {
4313 Ok(st) => st,
4314 Err(_) => return os2_ranges,
4315 };
4316
4317 let mut verified_ranges = Vec::new();
4319
4320 for range in os2_ranges {
4321 let test_codepoints = get_verification_codepoints(range.start, range.end);
4322
4323 let required_hits = (test_codepoints.len() + 1) / 2; let mut hits = 0;
4327
4328 for cp in test_codepoints {
4329 if cp >= range.start && cp <= range.end {
4330 if let Ok(Some(gid)) = cmap_subtable.map_glyph(cp) {
4331 if gid != 0 {
4332 hits += 1;
4333 if hits >= required_hits {
4334 break;
4335 }
4336 }
4337 }
4338 }
4339 }
4340
4341 if hits >= required_hits {
4342 verified_ranges.push(range);
4343 }
4344 }
4345
4346 verified_ranges
4347}
4348
4349#[cfg(all(feature = "std", feature = "parsing"))]
4352fn analyze_cmap_coverage(provider: &impl FontTableProvider) -> Option<Vec<UnicodeRange>> {
4353 use allsorts::tables::cmap::{Cmap, CmapSubtable};
4354
4355 let cmap_data = provider.table_data(tag::CMAP).ok()??;
4356 let cmap = ReadScope::new(&cmap_data).read::<Cmap<'_>>().ok()?;
4357
4358 let encoding_record = find_best_cmap_subtable(&cmap)?;
4359
4360 let cmap_subtable = ReadScope::new(&cmap_data)
4361 .offset(encoding_record.offset as usize)
4362 .read::<CmapSubtable<'_>>()
4363 .ok()?;
4364
4365 let blocks_to_check: &[(u32, u32)] = &[
4367 (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), ];
4418
4419 let mut ranges = Vec::new();
4420
4421 for &(start, end) in blocks_to_check {
4422 let test_codepoints = get_verification_codepoints(start, end);
4423 let required_hits = (test_codepoints.len() + 1) / 2;
4424 let mut hits = 0;
4425
4426 for cp in test_codepoints {
4427 if let Ok(Some(gid)) = cmap_subtable.map_glyph(cp) {
4428 if gid != 0 {
4429 hits += 1;
4430 if hits >= required_hits {
4431 break;
4432 }
4433 }
4434 }
4435 }
4436
4437 if hits >= required_hits {
4438 ranges.push(UnicodeRange { start, end });
4439 }
4440 }
4441
4442 if ranges.is_empty() {
4443 None
4444 } else {
4445 Some(ranges)
4446 }
4447}
4448
4449#[cfg(all(feature = "std", feature = "parsing"))]
4451#[allow(dead_code)]
4452fn extract_unicode_ranges(os2_table: &Os2) -> Vec<UnicodeRange> {
4453 let mut unicode_ranges = Vec::new();
4454
4455 let ranges = [
4456 os2_table.ul_unicode_range1,
4457 os2_table.ul_unicode_range2,
4458 os2_table.ul_unicode_range3,
4459 os2_table.ul_unicode_range4,
4460 ];
4461
4462 for &(bit, start, end) in UNICODE_RANGE_MAPPINGS {
4463 let range_idx = bit / 32;
4464 let bit_pos = bit % 32;
4465 if range_idx < 4 && (ranges[range_idx] & (1 << bit_pos)) != 0 {
4466 unicode_ranges.push(UnicodeRange { start, end });
4467 }
4468 }
4469
4470 unicode_ranges
4471}
4472
4473#[cfg(all(feature = "std", feature = "parsing"))]
4475fn detect_monospace(
4476 provider: &impl FontTableProvider,
4477 os2_table: &Os2,
4478 detected_monospace: Option<bool>,
4479) -> Option<bool> {
4480 if let Some(is_monospace) = detected_monospace {
4481 return Some(is_monospace);
4482 }
4483
4484 if os2_table.panose[0] == 2 {
4486 return Some(os2_table.panose[3] == 9); }
4489
4490 let hhea_data = provider.table_data(tag::HHEA).ok()??;
4492 let hhea_table = ReadScope::new(&hhea_data).read::<HheaTable>().ok()?;
4493 let maxp_data = provider.table_data(tag::MAXP).ok()??;
4494 let maxp_table = ReadScope::new(&maxp_data).read::<MaxpTable>().ok()?;
4495 let hmtx_data = provider.table_data(tag::HMTX).ok()??;
4496 let hmtx_table = ReadScope::new(&hmtx_data)
4497 .read_dep::<HmtxTable<'_>>((
4498 usize::from(maxp_table.num_glyphs),
4499 usize::from(hhea_table.num_h_metrics),
4500 ))
4501 .ok()?;
4502
4503 let mut monospace = true;
4504 let mut last_advance = 0;
4505
4506 for i in 0..hhea_table.num_h_metrics as usize {
4508 let advance = hmtx_table.h_metrics.read_item(i).ok()?.advance_width;
4509 if i > 0 && advance != last_advance {
4510 monospace = false;
4511 break;
4512 }
4513 last_advance = advance;
4514 }
4515
4516 Some(monospace)
4517}
4518
4519#[cfg(feature = "std")]
4524fn pattern_from_filename(path: &std::path::Path) -> Option<FcPattern> {
4525 let ext = path.extension()?.to_str()?.to_lowercase();
4526 match ext.as_str() {
4527 "ttf" | "otf" | "ttc" | "woff" | "woff2" => {}
4528 _ => return None,
4529 }
4530
4531 let stem = path.file_stem()?.to_str()?;
4532 let all_tokens = crate::config::tokenize_lowercase(stem);
4533
4534 let has_token = |kw: &str| all_tokens.iter().any(|t| t == kw);
4536 let is_bold = has_token("bold") || has_token("heavy");
4537 let is_italic = has_token("italic");
4538 let is_oblique = has_token("oblique");
4539 let is_mono = has_token("mono") || has_token("monospace");
4540 let is_condensed = has_token("condensed");
4541
4542 let family_tokens = crate::config::tokenize_font_stem(stem);
4544 if family_tokens.is_empty() { return None; }
4545 let family = family_tokens.join(" ");
4546
4547 Some(FcPattern {
4548 name: Some(stem.to_string()),
4549 family: Some(family),
4550 bold: if is_bold { PatternMatch::True } else { PatternMatch::False },
4551 italic: if is_italic { PatternMatch::True } else { PatternMatch::False },
4552 oblique: if is_oblique { PatternMatch::True } else { PatternMatch::DontCare },
4553 monospace: if is_mono { PatternMatch::True } else { PatternMatch::DontCare },
4554 condensed: if is_condensed { PatternMatch::True } else { PatternMatch::DontCare },
4555 weight: if is_bold { FcWeight::Bold } else { FcWeight::Normal },
4556 stretch: if is_condensed { FcStretch::Condensed } else { FcStretch::Normal },
4557 unicode_ranges: Vec::new(),
4558 metadata: FcFontMetadata::default(),
4559 render_config: FcFontRenderConfig::default(),
4560 })
4561}