1#![allow(non_snake_case)]
68#![cfg_attr(not(feature = "std"), no_std)]
69
70extern crate alloc;
71
72#[cfg(all(feature = "std", feature = "parsing"))]
73use alloc::borrow::ToOwned;
74use alloc::collections::btree_map::BTreeMap;
75use alloc::string::{String, ToString};
76use alloc::vec::Vec;
77use alloc::{format, vec};
78#[cfg(all(feature = "std", feature = "parsing"))]
79use allsorts::binary::read::ReadScope;
80#[cfg(all(feature = "std", feature = "parsing"))]
81use allsorts::get_name::fontcode_get_name;
82#[cfg(all(feature = "std", feature = "parsing"))]
83use allsorts::tables::os2::Os2;
84#[cfg(all(feature = "std", feature = "parsing"))]
85use allsorts::tables::{FontTableProvider, HheaTable, HmtxTable, MaxpTable};
86#[cfg(all(feature = "std", feature = "parsing"))]
87use allsorts::tag;
88#[cfg(feature = "std")]
89use std::path::PathBuf;
90
91pub mod utils;
92#[cfg(feature = "std")]
93pub mod config;
94
95#[cfg(feature = "ffi")]
96pub mod ffi;
97
98#[cfg(feature = "async-registry")]
99pub mod scoring;
100#[cfg(feature = "async-registry")]
101pub mod registry;
102#[cfg(feature = "async-registry")]
103pub mod multithread;
104#[cfg(feature = "cache")]
105pub mod disk_cache;
106
107#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
109pub enum OperatingSystem {
110 Windows,
111 Linux,
112 MacOS,
113 Wasm,
114}
115
116impl OperatingSystem {
117 pub fn current() -> Self {
119 #[cfg(target_os = "windows")]
120 return OperatingSystem::Windows;
121
122 #[cfg(target_os = "linux")]
123 return OperatingSystem::Linux;
124
125 #[cfg(target_os = "macos")]
126 return OperatingSystem::MacOS;
127
128 #[cfg(target_family = "wasm")]
129 return OperatingSystem::Wasm;
130
131 #[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos", target_family = "wasm")))]
132 return OperatingSystem::Linux; }
134
135 pub fn get_serif_fonts(&self, unicode_ranges: &[UnicodeRange]) -> Vec<String> {
138 let has_cjk = has_cjk_ranges(unicode_ranges);
139 let has_arabic = has_arabic_ranges(unicode_ranges);
140 let _has_cyrillic = has_cyrillic_ranges(unicode_ranges);
141
142 match self {
143 OperatingSystem::Windows => {
144 let mut fonts = Vec::new();
145 if has_cjk {
146 fonts.extend_from_slice(&["MS Mincho", "SimSun", "MingLiU"]);
147 }
148 if has_arabic {
149 fonts.push("Traditional Arabic");
150 }
151 fonts.push("Times New Roman");
152 fonts.iter().map(|s| s.to_string()).collect()
153 }
154 OperatingSystem::Linux => {
155 let mut fonts = Vec::new();
156 if has_cjk {
157 fonts.extend_from_slice(&["Noto Serif CJK SC", "Noto Serif CJK JP", "Noto Serif CJK KR"]);
158 }
159 if has_arabic {
160 fonts.push("Noto Serif Arabic");
161 }
162 fonts.extend_from_slice(&[
163 "Times", "Times New Roman", "DejaVu Serif", "Free Serif",
164 "Noto Serif", "Bitstream Vera Serif", "Roman", "Regular"
165 ]);
166 fonts.iter().map(|s| s.to_string()).collect()
167 }
168 OperatingSystem::MacOS => {
169 let mut fonts = Vec::new();
170 if has_cjk {
171 fonts.extend_from_slice(&["Hiragino Mincho ProN", "STSong", "AppleMyungjo"]);
172 }
173 if has_arabic {
174 fonts.push("Geeza Pro");
175 }
176 fonts.extend_from_slice(&["Times", "New York", "Palatino"]);
177 fonts.iter().map(|s| s.to_string()).collect()
178 }
179 OperatingSystem::Wasm => Vec::new(),
180 }
181 }
182
183 pub fn get_sans_serif_fonts(&self, unicode_ranges: &[UnicodeRange]) -> Vec<String> {
186 let has_cjk = has_cjk_ranges(unicode_ranges);
187 let has_arabic = has_arabic_ranges(unicode_ranges);
188 let _has_cyrillic = has_cyrillic_ranges(unicode_ranges);
189 let has_hebrew = has_hebrew_ranges(unicode_ranges);
190 let has_thai = has_thai_ranges(unicode_ranges);
191
192 match self {
193 OperatingSystem::Windows => {
194 let mut fonts = Vec::new();
195 if has_cjk {
196 fonts.extend_from_slice(&["Microsoft YaHei", "MS Gothic", "Malgun Gothic", "SimHei"]);
197 }
198 if has_arabic {
199 fonts.push("Segoe UI Arabic");
200 }
201 if has_hebrew {
202 fonts.push("Segoe UI Hebrew");
203 }
204 if has_thai {
205 fonts.push("Leelawadee UI");
206 }
207 fonts.extend_from_slice(&["Segoe UI", "Tahoma", "Microsoft Sans Serif", "MS Sans Serif", "Helv"]);
208 fonts.iter().map(|s| s.to_string()).collect()
209 }
210 OperatingSystem::Linux => {
211 let mut fonts = Vec::new();
212 if has_cjk {
213 fonts.extend_from_slice(&[
214 "Noto Sans CJK SC", "Noto Sans CJK JP", "Noto Sans CJK KR",
215 "WenQuanYi Micro Hei", "Droid Sans Fallback"
216 ]);
217 }
218 if has_arabic {
219 fonts.push("Noto Sans Arabic");
220 }
221 if has_hebrew {
222 fonts.push("Noto Sans Hebrew");
223 }
224 if has_thai {
225 fonts.push("Noto Sans Thai");
226 }
227 fonts.extend_from_slice(&["Ubuntu", "Arial", "DejaVu Sans", "Noto Sans", "Liberation Sans"]);
228 fonts.iter().map(|s| s.to_string()).collect()
229 }
230 OperatingSystem::MacOS => {
231 let mut fonts = Vec::new();
232 if has_cjk {
233 fonts.extend_from_slice(&[
234 "Hiragino Sans", "Hiragino Kaku Gothic ProN",
235 "PingFang SC", "PingFang TC", "Apple SD Gothic Neo"
236 ]);
237 }
238 if has_arabic {
239 fonts.push("Geeza Pro");
240 }
241 if has_hebrew {
242 fonts.push("Arial Hebrew");
243 }
244 if has_thai {
245 fonts.push("Thonburi");
246 }
247 fonts.extend_from_slice(&["San Francisco", "Helvetica Neue", "Lucida Grande"]);
248 fonts.iter().map(|s| s.to_string()).collect()
249 }
250 OperatingSystem::Wasm => Vec::new(),
251 }
252 }
253
254 pub fn get_monospace_fonts(&self, unicode_ranges: &[UnicodeRange]) -> Vec<String> {
257 let has_cjk = has_cjk_ranges(unicode_ranges);
258
259 match self {
260 OperatingSystem::Windows => {
261 let mut fonts = Vec::new();
262 if has_cjk {
263 fonts.extend_from_slice(&["MS Gothic", "SimHei"]);
264 }
265 fonts.extend_from_slice(&["Segoe UI Mono", "Courier New", "Cascadia Code", "Cascadia Mono", "Consolas"]);
266 fonts.iter().map(|s| s.to_string()).collect()
267 }
268 OperatingSystem::Linux => {
269 let mut fonts = Vec::new();
270 if has_cjk {
271 fonts.extend_from_slice(&["Noto Sans Mono CJK SC", "Noto Sans Mono CJK JP", "WenQuanYi Zen Hei Mono"]);
272 }
273 fonts.extend_from_slice(&[
274 "Source Code Pro", "Cantarell", "DejaVu Sans Mono",
275 "Roboto Mono", "Ubuntu Monospace", "Droid Sans Mono"
276 ]);
277 fonts.iter().map(|s| s.to_string()).collect()
278 }
279 OperatingSystem::MacOS => {
280 let mut fonts = Vec::new();
281 if has_cjk {
282 fonts.extend_from_slice(&["Hiragino Sans", "PingFang SC"]);
283 }
284 fonts.extend_from_slice(&["SF Mono", "Menlo", "Monaco", "Courier", "Oxygen Mono", "Source Code Pro", "Fira Mono"]);
285 fonts.iter().map(|s| s.to_string()).collect()
286 }
287 OperatingSystem::Wasm => Vec::new(),
288 }
289 }
290
291 pub fn expand_generic_family(&self, family: &str, unicode_ranges: &[UnicodeRange]) -> Vec<String> {
295 match family.to_lowercase().as_str() {
296 "serif" => self.get_serif_fonts(unicode_ranges),
297 "sans-serif" => self.get_sans_serif_fonts(unicode_ranges),
298 "monospace" => self.get_monospace_fonts(unicode_ranges),
299 "cursive" | "fantasy" | "system-ui" => {
300 self.get_sans_serif_fonts(unicode_ranges)
302 }
303 _ => vec![family.to_string()],
304 }
305 }
306}
307
308pub fn expand_font_families(families: &[String], os: OperatingSystem, unicode_ranges: &[UnicodeRange]) -> Vec<String> {
312 let mut expanded = Vec::new();
313
314 for family in families {
315 expanded.extend(os.expand_generic_family(family, unicode_ranges));
316 }
317
318 expanded
319}
320
321#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
323#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
324pub struct FontId(pub u128);
325
326impl core::fmt::Debug for FontId {
327 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
328 core::fmt::Display::fmt(self, f)
329 }
330}
331
332impl core::fmt::Display for FontId {
333 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
334 let id = self.0;
335 write!(
336 f,
337 "{:08x}-{:04x}-{:04x}-{:04x}-{:012x}",
338 (id >> 96) & 0xFFFFFFFF,
339 (id >> 80) & 0xFFFF,
340 (id >> 64) & 0xFFFF,
341 (id >> 48) & 0xFFFF,
342 id & 0xFFFFFFFFFFFF
343 )
344 }
345}
346
347impl FontId {
348 pub fn new() -> Self {
350 use core::sync::atomic::{AtomicU64, Ordering};
351 static COUNTER: AtomicU64 = AtomicU64::new(1);
352 let id = COUNTER.fetch_add(1, Ordering::Relaxed) as u128;
353 FontId(id)
354 }
355}
356
357#[derive(Debug, Default, Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
359#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
360#[repr(C)]
361pub enum PatternMatch {
362 #[default]
364 DontCare,
365 True,
367 False,
369}
370
371impl PatternMatch {
372 fn needs_to_match(&self) -> bool {
373 matches!(self, PatternMatch::True | PatternMatch::False)
374 }
375
376 fn matches(&self, other: &PatternMatch) -> bool {
377 match (self, other) {
378 (PatternMatch::DontCare, _) => true,
379 (_, PatternMatch::DontCare) => true,
380 (a, b) => a == b,
381 }
382 }
383}
384
385#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
387#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
388#[repr(C)]
389pub enum FcWeight {
390 Thin = 100,
391 ExtraLight = 200,
392 Light = 300,
393 Normal = 400,
394 Medium = 500,
395 SemiBold = 600,
396 Bold = 700,
397 ExtraBold = 800,
398 Black = 900,
399}
400
401impl FcWeight {
402 pub fn from_u16(weight: u16) -> Self {
403 match weight {
404 0..=149 => FcWeight::Thin,
405 150..=249 => FcWeight::ExtraLight,
406 250..=349 => FcWeight::Light,
407 350..=449 => FcWeight::Normal,
408 450..=549 => FcWeight::Medium,
409 550..=649 => FcWeight::SemiBold,
410 650..=749 => FcWeight::Bold,
411 750..=849 => FcWeight::ExtraBold,
412 _ => FcWeight::Black,
413 }
414 }
415
416 pub fn find_best_match(&self, available: &[FcWeight]) -> Option<FcWeight> {
417 if available.is_empty() {
418 return None;
419 }
420
421 if available.contains(self) {
423 return Some(*self);
424 }
425
426 let self_value = *self as u16;
428
429 match *self {
430 FcWeight::Normal => {
431 if available.contains(&FcWeight::Medium) {
433 return Some(FcWeight::Medium);
434 }
435 for weight in &[FcWeight::Light, FcWeight::ExtraLight, FcWeight::Thin] {
437 if available.contains(weight) {
438 return Some(*weight);
439 }
440 }
441 for weight in &[
443 FcWeight::SemiBold,
444 FcWeight::Bold,
445 FcWeight::ExtraBold,
446 FcWeight::Black,
447 ] {
448 if available.contains(weight) {
449 return Some(*weight);
450 }
451 }
452 }
453 FcWeight::Medium => {
454 if available.contains(&FcWeight::Normal) {
456 return Some(FcWeight::Normal);
457 }
458 for weight in &[FcWeight::Light, FcWeight::ExtraLight, FcWeight::Thin] {
460 if available.contains(weight) {
461 return Some(*weight);
462 }
463 }
464 for weight in &[
466 FcWeight::SemiBold,
467 FcWeight::Bold,
468 FcWeight::ExtraBold,
469 FcWeight::Black,
470 ] {
471 if available.contains(weight) {
472 return Some(*weight);
473 }
474 }
475 }
476 FcWeight::Thin | FcWeight::ExtraLight | FcWeight::Light => {
477 let mut best_match = None;
479 let mut smallest_diff = u16::MAX;
480
481 for weight in available {
483 let weight_value = *weight as u16;
484 if weight_value <= self_value {
486 let diff = self_value - weight_value;
487 if diff < smallest_diff {
488 smallest_diff = diff;
489 best_match = Some(*weight);
490 }
491 }
492 }
493
494 if best_match.is_some() {
495 return best_match;
496 }
497
498 best_match = None;
500 smallest_diff = u16::MAX;
501
502 for weight in available {
503 let weight_value = *weight as u16;
504 if weight_value > self_value {
505 let diff = weight_value - self_value;
506 if diff < smallest_diff {
507 smallest_diff = diff;
508 best_match = Some(*weight);
509 }
510 }
511 }
512
513 return best_match;
514 }
515 FcWeight::SemiBold | FcWeight::Bold | FcWeight::ExtraBold | FcWeight::Black => {
516 let mut best_match = None;
518 let mut smallest_diff = u16::MAX;
519
520 for weight in available {
522 let weight_value = *weight as u16;
523 if weight_value >= self_value {
525 let diff = weight_value - self_value;
526 if diff < smallest_diff {
527 smallest_diff = diff;
528 best_match = Some(*weight);
529 }
530 }
531 }
532
533 if best_match.is_some() {
534 return best_match;
535 }
536
537 best_match = None;
539 smallest_diff = u16::MAX;
540
541 for weight in available {
542 let weight_value = *weight as u16;
543 if weight_value < self_value {
544 let diff = self_value - weight_value;
545 if diff < smallest_diff {
546 smallest_diff = diff;
547 best_match = Some(*weight);
548 }
549 }
550 }
551
552 return best_match;
553 }
554 }
555
556 Some(available[0])
558 }
559}
560
561impl Default for FcWeight {
562 fn default() -> Self {
563 FcWeight::Normal
564 }
565}
566
567#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
569#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
570#[repr(C)]
571pub enum FcStretch {
572 UltraCondensed = 1,
573 ExtraCondensed = 2,
574 Condensed = 3,
575 SemiCondensed = 4,
576 Normal = 5,
577 SemiExpanded = 6,
578 Expanded = 7,
579 ExtraExpanded = 8,
580 UltraExpanded = 9,
581}
582
583impl FcStretch {
584 pub fn is_condensed(&self) -> bool {
585 use self::FcStretch::*;
586 match self {
587 UltraCondensed => true,
588 ExtraCondensed => true,
589 Condensed => true,
590 SemiCondensed => true,
591 Normal => false,
592 SemiExpanded => false,
593 Expanded => false,
594 ExtraExpanded => false,
595 UltraExpanded => false,
596 }
597 }
598 pub fn from_u16(width_class: u16) -> Self {
599 match width_class {
600 1 => FcStretch::UltraCondensed,
601 2 => FcStretch::ExtraCondensed,
602 3 => FcStretch::Condensed,
603 4 => FcStretch::SemiCondensed,
604 5 => FcStretch::Normal,
605 6 => FcStretch::SemiExpanded,
606 7 => FcStretch::Expanded,
607 8 => FcStretch::ExtraExpanded,
608 9 => FcStretch::UltraExpanded,
609 _ => FcStretch::Normal,
610 }
611 }
612
613 pub fn find_best_match(&self, available: &[FcStretch]) -> Option<FcStretch> {
615 if available.is_empty() {
616 return None;
617 }
618
619 if available.contains(self) {
620 return Some(*self);
621 }
622
623 if *self <= FcStretch::Normal {
625 let mut closest_narrower = None;
627 for stretch in available.iter() {
628 if *stretch < *self
629 && (closest_narrower.is_none() || *stretch > closest_narrower.unwrap())
630 {
631 closest_narrower = Some(*stretch);
632 }
633 }
634
635 if closest_narrower.is_some() {
636 return closest_narrower;
637 }
638
639 let mut closest_wider = None;
641 for stretch in available.iter() {
642 if *stretch > *self
643 && (closest_wider.is_none() || *stretch < closest_wider.unwrap())
644 {
645 closest_wider = Some(*stretch);
646 }
647 }
648
649 return closest_wider;
650 } else {
651 let mut closest_wider = None;
653 for stretch in available.iter() {
654 if *stretch > *self
655 && (closest_wider.is_none() || *stretch < closest_wider.unwrap())
656 {
657 closest_wider = Some(*stretch);
658 }
659 }
660
661 if closest_wider.is_some() {
662 return closest_wider;
663 }
664
665 let mut closest_narrower = None;
667 for stretch in available.iter() {
668 if *stretch < *self
669 && (closest_narrower.is_none() || *stretch > closest_narrower.unwrap())
670 {
671 closest_narrower = Some(*stretch);
672 }
673 }
674
675 return closest_narrower;
676 }
677 }
678}
679
680impl Default for FcStretch {
681 fn default() -> Self {
682 FcStretch::Normal
683 }
684}
685
686#[repr(C)]
688#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
689#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
690pub struct UnicodeRange {
691 pub start: u32,
692 pub end: u32,
693}
694
695pub const DEFAULT_UNICODE_FALLBACK_SCRIPTS: &[UnicodeRange] = &[
704 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 }, ];
712
713impl UnicodeRange {
714 pub fn contains(&self, c: char) -> bool {
715 let c = c as u32;
716 c >= self.start && c <= self.end
717 }
718
719 pub fn overlaps(&self, other: &UnicodeRange) -> bool {
720 self.start <= other.end && other.start <= self.end
721 }
722
723 pub fn is_subset_of(&self, other: &UnicodeRange) -> bool {
724 self.start >= other.start && self.end <= other.end
725 }
726}
727
728pub fn has_cjk_ranges(ranges: &[UnicodeRange]) -> bool {
730 ranges.iter().any(|r| {
731 (r.start >= 0x4E00 && r.start <= 0x9FFF) ||
732 (r.start >= 0x3040 && r.start <= 0x309F) ||
733 (r.start >= 0x30A0 && r.start <= 0x30FF) ||
734 (r.start >= 0xAC00 && r.start <= 0xD7AF)
735 })
736}
737
738pub fn has_arabic_ranges(ranges: &[UnicodeRange]) -> bool {
740 ranges.iter().any(|r| r.start >= 0x0600 && r.start <= 0x06FF)
741}
742
743pub fn has_cyrillic_ranges(ranges: &[UnicodeRange]) -> bool {
745 ranges.iter().any(|r| r.start >= 0x0400 && r.start <= 0x04FF)
746}
747
748pub fn has_hebrew_ranges(ranges: &[UnicodeRange]) -> bool {
750 ranges.iter().any(|r| r.start >= 0x0590 && r.start <= 0x05FF)
751}
752
753pub fn has_thai_ranges(ranges: &[UnicodeRange]) -> bool {
755 ranges.iter().any(|r| r.start >= 0x0E00 && r.start <= 0x0E7F)
756}
757
758#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
760pub enum TraceLevel {
761 Debug,
762 Info,
763 Warning,
764 Error,
765}
766
767#[derive(Debug, Clone, PartialEq, Eq, Hash)]
769pub enum MatchReason {
770 NameMismatch {
771 requested: Option<String>,
772 found: Option<String>,
773 },
774 FamilyMismatch {
775 requested: Option<String>,
776 found: Option<String>,
777 },
778 StyleMismatch {
779 property: &'static str,
780 requested: String,
781 found: String,
782 },
783 WeightMismatch {
784 requested: FcWeight,
785 found: FcWeight,
786 },
787 StretchMismatch {
788 requested: FcStretch,
789 found: FcStretch,
790 },
791 UnicodeRangeMismatch {
792 character: char,
793 ranges: Vec<UnicodeRange>,
794 },
795 Success,
796}
797
798#[derive(Debug, Clone, PartialEq, Eq)]
800pub struct TraceMsg {
801 pub level: TraceLevel,
802 pub path: String,
803 pub reason: MatchReason,
804}
805
806#[repr(C)]
808#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
809#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
810pub enum FcHintStyle {
811 #[default]
812 None = 0,
813 Slight = 1,
814 Medium = 2,
815 Full = 3,
816}
817
818#[repr(C)]
820#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
821#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
822pub enum FcRgba {
823 #[default]
824 Unknown = 0,
825 Rgb = 1,
826 Bgr = 2,
827 Vrgb = 3,
828 Vbgr = 4,
829 None = 5,
830}
831
832#[repr(C)]
834#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
835#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
836pub enum FcLcdFilter {
837 #[default]
838 None = 0,
839 Default = 1,
840 Light = 2,
841 Legacy = 3,
842}
843
844#[derive(Debug, Default, Clone, PartialEq, PartialOrd)]
849#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
850pub struct FcFontRenderConfig {
851 pub antialias: Option<bool>,
852 pub hinting: Option<bool>,
853 pub hintstyle: Option<FcHintStyle>,
854 pub autohint: Option<bool>,
855 pub rgba: Option<FcRgba>,
856 pub lcdfilter: Option<FcLcdFilter>,
857 pub embeddedbitmap: Option<bool>,
858 pub embolden: Option<bool>,
859 pub dpi: Option<f64>,
860 pub scale: Option<f64>,
861 pub minspace: Option<bool>,
862}
863
864impl Eq for FcFontRenderConfig {}
867
868impl Ord for FcFontRenderConfig {
869 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
870 let ord = self.antialias.cmp(&other.antialias)
872 .then_with(|| self.hinting.cmp(&other.hinting))
873 .then_with(|| self.hintstyle.cmp(&other.hintstyle))
874 .then_with(|| self.autohint.cmp(&other.autohint))
875 .then_with(|| self.rgba.cmp(&other.rgba))
876 .then_with(|| self.lcdfilter.cmp(&other.lcdfilter))
877 .then_with(|| self.embeddedbitmap.cmp(&other.embeddedbitmap))
878 .then_with(|| self.embolden.cmp(&other.embolden))
879 .then_with(|| self.minspace.cmp(&other.minspace));
880
881 let ord = ord.then_with(|| {
883 let a = self.dpi.map(|v| v.to_bits());
884 let b = other.dpi.map(|v| v.to_bits());
885 a.cmp(&b)
886 });
887 ord.then_with(|| {
888 let a = self.scale.map(|v| v.to_bits());
889 let b = other.scale.map(|v| v.to_bits());
890 a.cmp(&b)
891 })
892 }
893}
894
895#[derive(Default, Clone, PartialOrd, Ord, PartialEq, Eq)]
897#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
898#[repr(C)]
899pub struct FcPattern {
900 pub name: Option<String>,
902 pub family: Option<String>,
904 pub italic: PatternMatch,
906 pub oblique: PatternMatch,
908 pub bold: PatternMatch,
910 pub monospace: PatternMatch,
912 pub condensed: PatternMatch,
914 pub weight: FcWeight,
916 pub stretch: FcStretch,
918 pub unicode_ranges: Vec<UnicodeRange>,
920 pub metadata: FcFontMetadata,
922 pub render_config: FcFontRenderConfig,
924}
925
926impl core::fmt::Debug for FcPattern {
927 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
928 let mut d = f.debug_struct("FcPattern");
929
930 if let Some(name) = &self.name {
931 d.field("name", name);
932 }
933
934 if let Some(family) = &self.family {
935 d.field("family", family);
936 }
937
938 if self.italic != PatternMatch::DontCare {
939 d.field("italic", &self.italic);
940 }
941
942 if self.oblique != PatternMatch::DontCare {
943 d.field("oblique", &self.oblique);
944 }
945
946 if self.bold != PatternMatch::DontCare {
947 d.field("bold", &self.bold);
948 }
949
950 if self.monospace != PatternMatch::DontCare {
951 d.field("monospace", &self.monospace);
952 }
953
954 if self.condensed != PatternMatch::DontCare {
955 d.field("condensed", &self.condensed);
956 }
957
958 if self.weight != FcWeight::Normal {
959 d.field("weight", &self.weight);
960 }
961
962 if self.stretch != FcStretch::Normal {
963 d.field("stretch", &self.stretch);
964 }
965
966 if !self.unicode_ranges.is_empty() {
967 d.field("unicode_ranges", &self.unicode_ranges);
968 }
969
970 let empty_metadata = FcFontMetadata::default();
972 if self.metadata != empty_metadata {
973 d.field("metadata", &self.metadata);
974 }
975
976 let empty_render_config = FcFontRenderConfig::default();
978 if self.render_config != empty_render_config {
979 d.field("render_config", &self.render_config);
980 }
981
982 d.finish()
983 }
984}
985
986#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]
988#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
989pub struct FcFontMetadata {
990 pub copyright: Option<String>,
991 pub designer: Option<String>,
992 pub designer_url: Option<String>,
993 pub font_family: Option<String>,
994 pub font_subfamily: Option<String>,
995 pub full_name: Option<String>,
996 pub id_description: Option<String>,
997 pub license: Option<String>,
998 pub license_url: Option<String>,
999 pub manufacturer: Option<String>,
1000 pub manufacturer_url: Option<String>,
1001 pub postscript_name: Option<String>,
1002 pub preferred_family: Option<String>,
1003 pub preferred_subfamily: Option<String>,
1004 pub trademark: Option<String>,
1005 pub unique_id: Option<String>,
1006 pub version: Option<String>,
1007}
1008
1009impl FcPattern {
1010 pub fn contains_char(&self, c: char) -> bool {
1012 if self.unicode_ranges.is_empty() {
1013 return true; }
1015
1016 for range in &self.unicode_ranges {
1017 if range.contains(c) {
1018 return true;
1019 }
1020 }
1021
1022 false
1023 }
1024}
1025
1026#[derive(Debug, Clone, PartialEq, Eq)]
1028pub struct FontMatch {
1029 pub id: FontId,
1030 pub unicode_ranges: Vec<UnicodeRange>,
1031 pub fallbacks: Vec<FontMatchNoFallback>,
1032}
1033
1034#[derive(Debug, Clone, PartialEq, Eq)]
1036pub struct FontMatchNoFallback {
1037 pub id: FontId,
1038 pub unicode_ranges: Vec<UnicodeRange>,
1039}
1040
1041#[derive(Debug, Clone, PartialEq, Eq)]
1044pub struct ResolvedFontRun {
1045 pub text: String,
1047 pub start_byte: usize,
1049 pub end_byte: usize,
1051 pub font_id: Option<FontId>,
1053 pub css_source: String,
1055}
1056
1057#[derive(Debug, Clone, PartialEq, Eq)]
1060pub struct FontFallbackChain {
1061 pub css_fallbacks: Vec<CssFallbackGroup>,
1064
1065 pub unicode_fallbacks: Vec<FontMatch>,
1068
1069 pub original_stack: Vec<String>,
1071}
1072
1073impl FontFallbackChain {
1074 pub fn resolve_char(&self, cache: &FcFontCache, ch: char) -> Option<(FontId, String)> {
1078 let codepoint = ch as u32;
1079
1080 for group in &self.css_fallbacks {
1082 for font in &group.fonts {
1083 let Some(meta) = cache.get_metadata_by_id(&font.id) else { continue };
1084 if meta.unicode_ranges.is_empty() {
1085 continue; }
1087 if meta.unicode_ranges.iter().any(|r| codepoint >= r.start && codepoint <= r.end) {
1088 return Some((font.id, group.css_name.clone()));
1089 }
1090 }
1091 }
1092
1093 for font in &self.unicode_fallbacks {
1095 let Some(meta) = cache.get_metadata_by_id(&font.id) else { continue };
1096 if meta.unicode_ranges.iter().any(|r| codepoint >= r.start && codepoint <= r.end) {
1097 return Some((font.id, "(unicode-fallback)".to_string()));
1098 }
1099 }
1100
1101 None
1102 }
1103
1104 pub fn resolve_text(&self, cache: &FcFontCache, text: &str) -> Vec<(char, Option<(FontId, String)>)> {
1107 text.chars()
1108 .map(|ch| (ch, self.resolve_char(cache, ch)))
1109 .collect()
1110 }
1111
1112 pub fn query_for_text(&self, cache: &FcFontCache, text: &str) -> Vec<ResolvedFontRun> {
1116 if text.is_empty() {
1117 return Vec::new();
1118 }
1119
1120 let mut runs: Vec<ResolvedFontRun> = Vec::new();
1121 let mut current_font: Option<FontId> = None;
1122 let mut current_css_source: Option<String> = None;
1123 let mut current_start_byte: usize = 0;
1124
1125 for (byte_idx, ch) in text.char_indices() {
1126 let resolved = self.resolve_char(cache, ch);
1127 let (font_id, css_source) = match &resolved {
1128 Some((id, source)) => (Some(*id), Some(source.clone())),
1129 None => (None, None),
1130 };
1131
1132 let font_changed = font_id != current_font;
1134
1135 if font_changed && byte_idx > 0 {
1136 let run_text = &text[current_start_byte..byte_idx];
1138 runs.push(ResolvedFontRun {
1139 text: run_text.to_string(),
1140 start_byte: current_start_byte,
1141 end_byte: byte_idx,
1142 font_id: current_font,
1143 css_source: current_css_source.clone().unwrap_or_default(),
1144 });
1145 current_start_byte = byte_idx;
1146 }
1147
1148 current_font = font_id;
1149 current_css_source = css_source;
1150 }
1151
1152 if current_start_byte < text.len() {
1154 let run_text = &text[current_start_byte..];
1155 runs.push(ResolvedFontRun {
1156 text: run_text.to_string(),
1157 start_byte: current_start_byte,
1158 end_byte: text.len(),
1159 font_id: current_font,
1160 css_source: current_css_source.unwrap_or_default(),
1161 });
1162 }
1163
1164 runs
1165 }
1166}
1167
1168#[derive(Debug, Clone, PartialEq, Eq)]
1170pub struct CssFallbackGroup {
1171 pub css_name: String,
1173
1174 pub fonts: Vec<FontMatch>,
1177}
1178
1179#[cfg(feature = "std")]
1192#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1193pub(crate) struct FontChainCacheKey {
1194 pub(crate) font_families: Vec<String>,
1196 pub(crate) weight: FcWeight,
1198 pub(crate) italic: PatternMatch,
1200 pub(crate) oblique: PatternMatch,
1201 pub(crate) scripts_hint_hash: Option<u64>,
1203}
1204
1205#[cfg(feature = "std")]
1210fn hash_scripts_hint(ranges: &[UnicodeRange]) -> u64 {
1211 let mut sorted: Vec<UnicodeRange> = ranges.to_vec();
1212 sorted.sort();
1213 let mut buf = Vec::with_capacity(sorted.len() * 8);
1214 for r in &sorted {
1215 buf.extend_from_slice(&r.start.to_le_bytes());
1216 buf.extend_from_slice(&r.end.to_le_bytes());
1217 }
1218 crate::utils::content_hash_u64(&buf)
1219}
1220
1221#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq)]
1233#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
1234#[repr(C)]
1235pub struct FcFontPath {
1236 pub path: String,
1237 pub font_index: usize,
1238 #[cfg_attr(feature = "cache", serde(default))]
1240 pub bytes_hash: u64,
1241}
1242
1243#[derive(Debug, Clone, PartialEq, Eq)]
1245#[repr(C)]
1246pub struct FcFont {
1247 pub bytes: Vec<u8>,
1248 pub font_index: usize,
1249 pub id: String, }
1251
1252#[derive(Debug, Clone)]
1254pub enum FontSource<'a> {
1255 Memory(&'a FcFont),
1257 Disk(&'a FcFontPath),
1259}
1260
1261#[derive(Debug, Clone)]
1264pub struct NamedFont {
1265 pub name: String,
1267 pub bytes: Vec<u8>,
1269}
1270
1271impl NamedFont {
1272 pub fn new(name: impl Into<String>, bytes: Vec<u8>) -> Self {
1274 Self {
1275 name: name.into(),
1276 bytes,
1277 }
1278 }
1279}
1280
1281#[derive(Debug)]
1283pub struct FcFontCache {
1284 pub(crate) patterns: BTreeMap<FcPattern, FontId>,
1286 pub(crate) disk_fonts: BTreeMap<FontId, FcFontPath>,
1288 pub(crate) memory_fonts: BTreeMap<FontId, FcFont>,
1290 pub(crate) metadata: BTreeMap<FontId, FcPattern>,
1292 pub(crate) token_index: BTreeMap<String, alloc::collections::BTreeSet<FontId>>,
1295 pub(crate) font_tokens: BTreeMap<FontId, Vec<String>>,
1298 #[cfg(feature = "std")]
1300 pub(crate) chain_cache: std::sync::Mutex<std::collections::HashMap<FontChainCacheKey, FontFallbackChain>>,
1301 #[cfg(feature = "std")]
1309 pub(crate) shared_bytes: std::sync::Mutex<std::collections::HashMap<u64, std::sync::Weak<[u8]>>>,
1310}
1311
1312impl Clone for FcFontCache {
1313 fn clone(&self) -> Self {
1314 Self {
1315 patterns: self.patterns.clone(),
1316 disk_fonts: self.disk_fonts.clone(),
1317 memory_fonts: self.memory_fonts.clone(),
1318 metadata: self.metadata.clone(),
1319 token_index: self.token_index.clone(),
1320 font_tokens: self.font_tokens.clone(),
1321 #[cfg(feature = "std")]
1322 chain_cache: std::sync::Mutex::new(std::collections::HashMap::new()), #[cfg(feature = "std")]
1324 shared_bytes: std::sync::Mutex::new(std::collections::HashMap::new()), }
1326 }
1327}
1328
1329impl Default for FcFontCache {
1330 fn default() -> Self {
1331 Self {
1332 patterns: BTreeMap::new(),
1333 disk_fonts: BTreeMap::new(),
1334 memory_fonts: BTreeMap::new(),
1335 metadata: BTreeMap::new(),
1336 token_index: BTreeMap::new(),
1337 font_tokens: BTreeMap::new(),
1338 #[cfg(feature = "std")]
1339 chain_cache: std::sync::Mutex::new(std::collections::HashMap::new()),
1340 #[cfg(feature = "std")]
1341 shared_bytes: std::sync::Mutex::new(std::collections::HashMap::new()),
1342 }
1343 }
1344}
1345
1346impl FcFontCache {
1347 pub(crate) fn index_pattern_tokens(&mut self, pattern: &FcPattern, id: FontId) {
1349 let mut all_tokens = Vec::new();
1351
1352 if let Some(name) = &pattern.name {
1353 all_tokens.extend(Self::extract_font_name_tokens(name));
1354 }
1355
1356 if let Some(family) = &pattern.family {
1357 all_tokens.extend(Self::extract_font_name_tokens(family));
1358 }
1359
1360 let tokens_lower: Vec<String> = all_tokens.iter().map(|t| t.to_lowercase()).collect();
1362
1363 for token_lower in &tokens_lower {
1365 self.token_index
1366 .entry(token_lower.clone())
1367 .or_insert_with(alloc::collections::BTreeSet::new)
1368 .insert(id);
1369 }
1370
1371 self.font_tokens.insert(id, tokens_lower);
1373 }
1374
1375 pub fn with_memory_fonts(&mut self, fonts: Vec<(FcPattern, FcFont)>) -> &mut Self {
1377 for (pattern, font) in fonts {
1378 let id = FontId::new();
1379 self.patterns.insert(pattern.clone(), id);
1380 self.metadata.insert(id, pattern.clone());
1381 self.memory_fonts.insert(id, font);
1382 self.index_pattern_tokens(&pattern, id);
1383 }
1384 self
1385 }
1386
1387 pub fn with_memory_font_with_id(
1389 &mut self,
1390 id: FontId,
1391 pattern: FcPattern,
1392 font: FcFont,
1393 ) -> &mut Self {
1394 self.patterns.insert(pattern.clone(), id);
1395 self.metadata.insert(id, pattern.clone());
1396 self.memory_fonts.insert(id, font);
1397 self.index_pattern_tokens(&pattern, id);
1398 self
1399 }
1400
1401 pub fn get_font_by_id<'a>(&'a self, id: &FontId) -> Option<FontSource<'a>> {
1403 if let Some(font) = self.memory_fonts.get(id) {
1405 return Some(FontSource::Memory(font));
1406 }
1407 if let Some(path) = self.disk_fonts.get(id) {
1409 return Some(FontSource::Disk(path));
1410 }
1411 None
1412 }
1413
1414 pub fn get_metadata_by_id(&self, id: &FontId) -> Option<&FcPattern> {
1416 self.metadata.get(id)
1417 }
1418
1419 #[cfg(feature = "std")]
1458 pub fn get_font_bytes_arc(&self, id: &FontId) -> Option<std::sync::Arc<[u8]>> {
1459 use std::sync::Arc;
1460 match self.get_font_by_id(id)? {
1461 FontSource::Memory(font) => Some(Arc::from(font.bytes.as_slice())),
1462 FontSource::Disk(path) => {
1463 let hash = path.bytes_hash;
1464 if hash != 0 {
1465 if let Ok(guard) = self.shared_bytes.lock() {
1466 if let Some(weak) = guard.get(&hash) {
1467 if let Some(arc) = weak.upgrade() {
1468 return Some(arc);
1469 }
1470 }
1471 }
1472 }
1473 let bytes = std::fs::read(&path.path).ok()?;
1474 let arc: Arc<[u8]> = Arc::from(bytes);
1475 if hash != 0 {
1476 if let Ok(mut guard) = self.shared_bytes.lock() {
1477 guard.insert(hash, Arc::downgrade(&arc));
1479 }
1480 }
1481 Some(arc)
1482 }
1483 }
1484 }
1485
1486 #[cfg(feature = "std")]
1493 pub fn get_font_bytes(&self, id: &FontId) -> Option<Vec<u8>> {
1494 self.get_font_bytes_arc(id).map(|a| a.to_vec())
1495 }
1496
1497 #[cfg(not(feature = "std"))]
1499 pub fn build() -> Self { Self::default() }
1500
1501 #[cfg(all(feature = "std", not(feature = "parsing")))]
1503 pub fn build() -> Self { Self::build_from_filenames() }
1504
1505 #[cfg(all(feature = "std", feature = "parsing"))]
1507 pub fn build() -> Self { Self::build_inner(None) }
1508
1509 #[cfg(all(feature = "std", not(feature = "parsing")))]
1512 fn build_from_filenames() -> Self {
1513 let mut cache = Self::default();
1514 for dir in crate::config::font_directories(OperatingSystem::current()) {
1515 for path in FcCollectFontFilesRecursive(dir) {
1516 let pattern = match pattern_from_filename(&path) {
1517 Some(p) => p,
1518 None => continue,
1519 };
1520 let id = FontId::new();
1521 cache.disk_fonts.insert(id, FcFontPath {
1522 path: path.to_string_lossy().to_string(),
1523 font_index: 0,
1524 bytes_hash: 0,
1527 });
1528 cache.index_pattern_tokens(&pattern, id);
1529 cache.metadata.insert(id, pattern.clone());
1530 cache.patterns.insert(pattern, id);
1531 }
1532 }
1533 cache
1534 }
1535
1536 #[cfg(all(feature = "std", feature = "parsing"))]
1561 pub fn build_with_families(families: &[impl AsRef<str>]) -> Self {
1562 let os = OperatingSystem::current();
1564 let mut target_families: Vec<String> = Vec::new();
1565
1566 for family in families {
1567 let family_str = family.as_ref();
1568 let expanded = os.expand_generic_family(family_str, &[]);
1569 if expanded.is_empty() || (expanded.len() == 1 && expanded[0] == family_str) {
1570 target_families.push(family_str.to_string());
1571 } else {
1572 target_families.extend(expanded);
1573 }
1574 }
1575
1576 Self::build_inner(Some(&target_families))
1577 }
1578
1579 #[cfg(all(feature = "std", feature = "parsing"))]
1585 fn build_inner(family_filter: Option<&[String]>) -> Self {
1586 let mut cache = FcFontCache::default();
1587
1588 let filter_normalized: Option<Vec<String>> = family_filter.map(|families| {
1590 families
1591 .iter()
1592 .map(|f| crate::utils::normalize_family_name(f))
1593 .collect()
1594 });
1595
1596 let matches_filter = |pattern: &FcPattern| -> bool {
1598 match &filter_normalized {
1599 None => true, Some(targets) => {
1601 pattern.name.as_ref().map_or(false, |name| {
1602 let name_norm = crate::utils::normalize_family_name(name);
1603 targets.iter().any(|target| name_norm.contains(target))
1604 }) || pattern.family.as_ref().map_or(false, |family| {
1605 let family_norm = crate::utils::normalize_family_name(family);
1606 targets.iter().any(|target| family_norm.contains(target))
1607 })
1608 }
1609 }
1610 };
1611
1612 #[cfg(target_os = "linux")]
1613 {
1614 if let Some((font_entries, render_configs)) = FcScanDirectories() {
1615 for (mut pattern, path) in font_entries {
1616 if matches_filter(&pattern) {
1617 if let Some(family) = pattern.name.as_ref().or(pattern.family.as_ref()) {
1619 if let Some(rc) = render_configs.get(family) {
1620 pattern.render_config = rc.clone();
1621 }
1622 }
1623 let id = FontId::new();
1624 cache.patterns.insert(pattern.clone(), id);
1625 cache.metadata.insert(id, pattern.clone());
1626 cache.disk_fonts.insert(id, path);
1627 cache.index_pattern_tokens(&pattern, id);
1628 }
1629 }
1630 }
1631 }
1632
1633 #[cfg(target_os = "windows")]
1634 {
1635 let system_root = std::env::var("SystemRoot")
1636 .or_else(|_| std::env::var("WINDIR"))
1637 .unwrap_or_else(|_| "C:\\Windows".to_string());
1638
1639 let user_profile = std::env::var("USERPROFILE")
1640 .unwrap_or_else(|_| "C:\\Users\\Default".to_string());
1641
1642 let font_dirs = vec![
1643 (None, format!("{}\\Fonts\\", system_root)),
1644 (None, format!("{}\\AppData\\Local\\Microsoft\\Windows\\Fonts\\", user_profile)),
1645 ];
1646
1647 let font_entries = FcScanDirectoriesInner(&font_dirs);
1648 for (pattern, path) in font_entries {
1649 if matches_filter(&pattern) {
1650 let id = FontId::new();
1651 cache.patterns.insert(pattern.clone(), id);
1652 cache.metadata.insert(id, pattern.clone());
1653 cache.disk_fonts.insert(id, path);
1654 cache.index_pattern_tokens(&pattern, id);
1655 }
1656 }
1657 }
1658
1659 #[cfg(target_os = "macos")]
1660 {
1661 let font_dirs = vec![
1662 (None, "~/Library/Fonts".to_owned()),
1663 (None, "/System/Library/Fonts".to_owned()),
1664 (None, "/Library/Fonts".to_owned()),
1665 (None, "/System/Library/AssetsV2".to_owned()),
1666 ];
1667
1668 let font_entries = FcScanDirectoriesInner(&font_dirs);
1669 for (pattern, path) in font_entries {
1670 if matches_filter(&pattern) {
1671 let id = FontId::new();
1672 cache.patterns.insert(pattern.clone(), id);
1673 cache.metadata.insert(id, pattern.clone());
1674 cache.disk_fonts.insert(id, path);
1675 cache.index_pattern_tokens(&pattern, id);
1676 }
1677 }
1678 }
1679
1680 cache
1681 }
1682
1683 pub fn is_memory_font(&self, id: &FontId) -> bool {
1685 self.memory_fonts.contains_key(id)
1686 }
1687
1688 pub fn list(&self) -> Vec<(&FcPattern, FontId)> {
1690 self.patterns
1691 .iter()
1692 .map(|(pattern, id)| (pattern, *id))
1693 .collect()
1694 }
1695
1696 pub fn is_empty(&self) -> bool {
1698 self.patterns.is_empty()
1699 }
1700
1701 pub fn len(&self) -> usize {
1703 self.patterns.len()
1704 }
1705
1706 pub fn query(&self, pattern: &FcPattern, trace: &mut Vec<TraceMsg>) -> Option<FontMatch> {
1709 let mut matches = Vec::new();
1710
1711 for (stored_pattern, id) in &self.patterns {
1712 if Self::query_matches_internal(stored_pattern, pattern, trace) {
1713 let metadata = self.metadata.get(id).unwrap_or(stored_pattern);
1714
1715 let unicode_compatibility = if pattern.unicode_ranges.is_empty() {
1717 Self::calculate_unicode_coverage(&metadata.unicode_ranges) as i32
1719 } else {
1720 Self::calculate_unicode_compatibility(&pattern.unicode_ranges, &metadata.unicode_ranges)
1722 };
1723
1724 let style_score = Self::calculate_style_score(pattern, metadata);
1725
1726 let is_memory = self.memory_fonts.contains_key(id);
1728
1729 matches.push((*id, unicode_compatibility, style_score, metadata.clone(), is_memory));
1730 }
1731 }
1732
1733 matches.sort_by(|a, b| {
1735 b.4.cmp(&a.4)
1737 .then_with(|| b.1.cmp(&a.1)) .then_with(|| a.2.cmp(&b.2)) });
1740
1741 matches.first().map(|(id, _, _, metadata, _)| {
1742 FontMatch {
1743 id: *id,
1744 unicode_ranges: metadata.unicode_ranges.clone(),
1745 fallbacks: Vec::new(), }
1747 })
1748 }
1749
1750 fn query_internal(&self, pattern: &FcPattern, trace: &mut Vec<TraceMsg>) -> Vec<FontMatch> {
1755 let mut matches = Vec::new();
1756
1757 for (stored_pattern, id) in &self.patterns {
1758 if Self::query_matches_internal(stored_pattern, pattern, trace) {
1759 let metadata = self.metadata.get(id).unwrap_or(stored_pattern);
1760
1761 let unicode_compatibility = if pattern.unicode_ranges.is_empty() {
1763 Self::calculate_unicode_coverage(&metadata.unicode_ranges) as i32
1764 } else {
1765 Self::calculate_unicode_compatibility(&pattern.unicode_ranges, &metadata.unicode_ranges)
1766 };
1767
1768 let style_score = Self::calculate_style_score(pattern, metadata);
1769 matches.push((*id, unicode_compatibility, style_score, metadata.clone()));
1770 }
1771 }
1772
1773 matches.sort_by(|a, b| {
1777 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)) });
1782
1783 matches
1784 .into_iter()
1785 .map(|(id, _, _, metadata)| {
1786 FontMatch {
1787 id,
1788 unicode_ranges: metadata.unicode_ranges.clone(),
1789 fallbacks: Vec::new(), }
1791 })
1792 .collect()
1793 }
1794
1795 pub fn compute_fallbacks(
1799 &self,
1800 font_id: &FontId,
1801 trace: &mut Vec<TraceMsg>,
1802 ) -> Vec<FontMatchNoFallback> {
1803 let pattern = match self.metadata.get(font_id) {
1805 Some(p) => p,
1806 None => return Vec::new(),
1807 };
1808
1809 self.compute_fallbacks_for_pattern(pattern, Some(font_id), trace)
1810 }
1811
1812 fn compute_fallbacks_for_pattern(
1813 &self,
1814 pattern: &FcPattern,
1815 exclude_id: Option<&FontId>,
1816 _trace: &mut Vec<TraceMsg>,
1817 ) -> Vec<FontMatchNoFallback> {
1818 let mut candidates = Vec::new();
1819
1820 for (stored_pattern, id) in &self.patterns {
1822 if exclude_id.is_some() && exclude_id.unwrap() == id {
1824 continue;
1825 }
1826
1827 if !stored_pattern.unicode_ranges.is_empty() && !pattern.unicode_ranges.is_empty() {
1829 let unicode_compatibility = Self::calculate_unicode_compatibility(
1831 &pattern.unicode_ranges,
1832 &stored_pattern.unicode_ranges
1833 );
1834
1835 if unicode_compatibility > 0 {
1837 let style_score = Self::calculate_style_score(pattern, stored_pattern);
1838 candidates.push((
1839 FontMatchNoFallback {
1840 id: *id,
1841 unicode_ranges: stored_pattern.unicode_ranges.clone(),
1842 },
1843 unicode_compatibility,
1844 style_score,
1845 stored_pattern.clone(),
1846 ));
1847 }
1848 } else if pattern.unicode_ranges.is_empty() && !stored_pattern.unicode_ranges.is_empty() {
1849 let coverage = Self::calculate_unicode_coverage(&stored_pattern.unicode_ranges) as i32;
1851 let style_score = Self::calculate_style_score(pattern, stored_pattern);
1852 candidates.push((
1853 FontMatchNoFallback {
1854 id: *id,
1855 unicode_ranges: stored_pattern.unicode_ranges.clone(),
1856 },
1857 coverage,
1858 style_score,
1859 stored_pattern.clone(),
1860 ));
1861 }
1862 }
1863
1864 candidates.sort_by(|a, b| {
1866 b.1.cmp(&a.1)
1867 .then_with(|| a.2.cmp(&b.2))
1868 });
1869
1870 let mut seen_ranges = Vec::new();
1872 let mut deduplicated = Vec::new();
1873
1874 for (id, _, _, pattern) in candidates {
1875 let mut is_new_range = false;
1876
1877 for range in &pattern.unicode_ranges {
1878 if !seen_ranges.iter().any(|r: &UnicodeRange| r.overlaps(range)) {
1879 seen_ranges.push(*range);
1880 is_new_range = true;
1881 }
1882 }
1883
1884 if is_new_range {
1885 deduplicated.push(id);
1886 }
1887 }
1888
1889 deduplicated
1890 }
1891
1892 pub fn get_memory_font(&self, id: &FontId) -> Option<&FcFont> {
1894 self.memory_fonts.get(id)
1895 }
1896
1897 fn trace_path(k: &FcPattern) -> String {
1899 k.name.as_ref().cloned().unwrap_or_else(|| "<unknown>".to_string())
1900 }
1901
1902 pub fn query_matches_internal(
1903 k: &FcPattern,
1904 pattern: &FcPattern,
1905 trace: &mut Vec<TraceMsg>,
1906 ) -> bool {
1907 if let Some(ref name) = pattern.name {
1909 if !k.name.as_ref().map_or(false, |kn| kn.contains(name)) {
1910 trace.push(TraceMsg {
1911 level: TraceLevel::Info,
1912 path: Self::trace_path(k),
1913 reason: MatchReason::NameMismatch {
1914 requested: pattern.name.clone(),
1915 found: k.name.clone(),
1916 },
1917 });
1918 return false;
1919 }
1920 }
1921
1922 if let Some(ref family) = pattern.family {
1924 if !k.family.as_ref().map_or(false, |kf| kf.contains(family)) {
1925 trace.push(TraceMsg {
1926 level: TraceLevel::Info,
1927 path: Self::trace_path(k),
1928 reason: MatchReason::FamilyMismatch {
1929 requested: pattern.family.clone(),
1930 found: k.family.clone(),
1931 },
1932 });
1933 return false;
1934 }
1935 }
1936
1937 let style_properties = [
1939 (
1940 "italic",
1941 pattern.italic.needs_to_match(),
1942 pattern.italic.matches(&k.italic),
1943 ),
1944 (
1945 "oblique",
1946 pattern.oblique.needs_to_match(),
1947 pattern.oblique.matches(&k.oblique),
1948 ),
1949 (
1950 "bold",
1951 pattern.bold.needs_to_match(),
1952 pattern.bold.matches(&k.bold),
1953 ),
1954 (
1955 "monospace",
1956 pattern.monospace.needs_to_match(),
1957 pattern.monospace.matches(&k.monospace),
1958 ),
1959 (
1960 "condensed",
1961 pattern.condensed.needs_to_match(),
1962 pattern.condensed.matches(&k.condensed),
1963 ),
1964 ];
1965
1966 for (property_name, needs_to_match, matches) in style_properties {
1967 if needs_to_match && !matches {
1968 let (requested, found) = match property_name {
1969 "italic" => (format!("{:?}", pattern.italic), format!("{:?}", k.italic)),
1970 "oblique" => (format!("{:?}", pattern.oblique), format!("{:?}", k.oblique)),
1971 "bold" => (format!("{:?}", pattern.bold), format!("{:?}", k.bold)),
1972 "monospace" => (
1973 format!("{:?}", pattern.monospace),
1974 format!("{:?}", k.monospace),
1975 ),
1976 "condensed" => (
1977 format!("{:?}", pattern.condensed),
1978 format!("{:?}", k.condensed),
1979 ),
1980 _ => (String::new(), String::new()),
1981 };
1982
1983 trace.push(TraceMsg {
1984 level: TraceLevel::Info,
1985 path: Self::trace_path(k),
1986 reason: MatchReason::StyleMismatch {
1987 property: property_name,
1988 requested,
1989 found,
1990 },
1991 });
1992 return false;
1993 }
1994 }
1995
1996 if pattern.weight != FcWeight::Normal && pattern.weight != k.weight {
1998 trace.push(TraceMsg {
1999 level: TraceLevel::Info,
2000 path: Self::trace_path(k),
2001 reason: MatchReason::WeightMismatch {
2002 requested: pattern.weight,
2003 found: k.weight,
2004 },
2005 });
2006 return false;
2007 }
2008
2009 if pattern.stretch != FcStretch::Normal && pattern.stretch != k.stretch {
2011 trace.push(TraceMsg {
2012 level: TraceLevel::Info,
2013 path: Self::trace_path(k),
2014 reason: MatchReason::StretchMismatch {
2015 requested: pattern.stretch,
2016 found: k.stretch,
2017 },
2018 });
2019 return false;
2020 }
2021
2022 if !pattern.unicode_ranges.is_empty() {
2024 let mut has_overlap = false;
2025
2026 for p_range in &pattern.unicode_ranges {
2027 for k_range in &k.unicode_ranges {
2028 if p_range.overlaps(k_range) {
2029 has_overlap = true;
2030 break;
2031 }
2032 }
2033 if has_overlap {
2034 break;
2035 }
2036 }
2037
2038 if !has_overlap {
2039 trace.push(TraceMsg {
2040 level: TraceLevel::Info,
2041 path: Self::trace_path(k),
2042 reason: MatchReason::UnicodeRangeMismatch {
2043 character: '\0', ranges: k.unicode_ranges.clone(),
2045 },
2046 });
2047 return false;
2048 }
2049 }
2050
2051 true
2052 }
2053
2054 #[cfg(feature = "std")]
2080 pub fn resolve_font_chain(
2081 &self,
2082 font_families: &[String],
2083 weight: FcWeight,
2084 italic: PatternMatch,
2085 oblique: PatternMatch,
2086 trace: &mut Vec<TraceMsg>,
2087 ) -> FontFallbackChain {
2088 self.resolve_font_chain_with_os(font_families, weight, italic, oblique, trace, OperatingSystem::current())
2089 }
2090
2091 #[cfg(feature = "std")]
2093 pub fn resolve_font_chain_with_os(
2094 &self,
2095 font_families: &[String],
2096 weight: FcWeight,
2097 italic: PatternMatch,
2098 oblique: PatternMatch,
2099 trace: &mut Vec<TraceMsg>,
2100 os: OperatingSystem,
2101 ) -> FontFallbackChain {
2102 self.resolve_font_chain_impl(font_families, weight, italic, oblique, None, trace, os)
2103 }
2104
2105 #[cfg(feature = "std")]
2120 pub fn resolve_font_chain_with_scripts(
2121 &self,
2122 font_families: &[String],
2123 weight: FcWeight,
2124 italic: PatternMatch,
2125 oblique: PatternMatch,
2126 scripts_hint: Option<&[UnicodeRange]>,
2127 trace: &mut Vec<TraceMsg>,
2128 ) -> FontFallbackChain {
2129 self.resolve_font_chain_impl(
2130 font_families, weight, italic, oblique, scripts_hint,
2131 trace, OperatingSystem::current(),
2132 )
2133 }
2134
2135 #[cfg(feature = "std")]
2139 fn resolve_font_chain_impl(
2140 &self,
2141 font_families: &[String],
2142 weight: FcWeight,
2143 italic: PatternMatch,
2144 oblique: PatternMatch,
2145 scripts_hint: Option<&[UnicodeRange]>,
2146 trace: &mut Vec<TraceMsg>,
2147 os: OperatingSystem,
2148 ) -> FontFallbackChain {
2149 let scripts_hint_hash = scripts_hint.map(hash_scripts_hint);
2153 let cache_key = FontChainCacheKey {
2154 font_families: font_families.to_vec(),
2155 weight,
2156 italic,
2157 oblique,
2158 scripts_hint_hash,
2159 };
2160
2161 if let Some(cached) = self.chain_cache.lock().ok().and_then(|c| c.get(&cache_key).cloned()) {
2162 return cached;
2163 }
2164
2165 let expanded_families = expand_font_families(font_families, os, &[]);
2167
2168 let chain = self.resolve_font_chain_uncached(
2170 &expanded_families,
2171 weight,
2172 italic,
2173 oblique,
2174 scripts_hint,
2175 trace,
2176 );
2177
2178 if let Ok(mut cache) = self.chain_cache.lock() {
2180 cache.insert(cache_key, chain.clone());
2181 }
2182
2183 chain
2184 }
2185
2186 #[cfg(feature = "std")]
2194 fn resolve_font_chain_uncached(
2195 &self,
2196 font_families: &[String],
2197 weight: FcWeight,
2198 italic: PatternMatch,
2199 oblique: PatternMatch,
2200 scripts_hint: Option<&[UnicodeRange]>,
2201 trace: &mut Vec<TraceMsg>,
2202 ) -> FontFallbackChain {
2203 let mut css_fallbacks = Vec::new();
2204
2205 for (_i, family) in font_families.iter().enumerate() {
2207 let (pattern, is_generic) = if config::is_generic_family(family) {
2209 let monospace = if family.eq_ignore_ascii_case("monospace") {
2210 PatternMatch::True
2211 } else {
2212 PatternMatch::False
2213 };
2214 let pattern = FcPattern {
2215 name: None,
2216 weight,
2217 italic,
2218 oblique,
2219 monospace,
2220 unicode_ranges: Vec::new(),
2221 ..Default::default()
2222 };
2223 (pattern, true)
2224 } else {
2225 let pattern = FcPattern {
2227 name: Some(family.clone()),
2228 weight,
2229 italic,
2230 oblique,
2231 unicode_ranges: Vec::new(),
2232 ..Default::default()
2233 };
2234 (pattern, false)
2235 };
2236
2237 let mut matches = if is_generic {
2240 self.query_internal(&pattern, trace)
2242 } else {
2243 self.fuzzy_query_by_name(family, weight, italic, oblique, &[], trace)
2245 };
2246
2247 if is_generic && matches.len() > 5 {
2249 matches.truncate(5);
2250 }
2251
2252 css_fallbacks.push(CssFallbackGroup {
2255 css_name: family.clone(),
2256 fonts: matches,
2257 });
2258 }
2259
2260 let important_ranges: &[UnicodeRange] =
2273 scripts_hint.unwrap_or(DEFAULT_UNICODE_FALLBACK_SCRIPTS);
2274 let unicode_fallbacks = if important_ranges.is_empty() {
2275 Vec::new()
2276 } else {
2277 let all_uncovered = vec![false; important_ranges.len()];
2278 self.find_unicode_fallbacks(
2279 important_ranges,
2280 &all_uncovered,
2281 &css_fallbacks,
2282 weight,
2283 italic,
2284 oblique,
2285 trace,
2286 )
2287 };
2288
2289 FontFallbackChain {
2290 css_fallbacks,
2291 unicode_fallbacks,
2292 original_stack: font_families.to_vec(),
2293 }
2294 }
2295
2296 #[allow(dead_code)]
2298 fn extract_unicode_ranges(text: &str) -> Vec<UnicodeRange> {
2299 let mut chars: Vec<char> = text.chars().collect();
2300 chars.sort_unstable();
2301 chars.dedup();
2302
2303 if chars.is_empty() {
2304 return Vec::new();
2305 }
2306
2307 let mut ranges = Vec::new();
2308 let mut range_start = chars[0] as u32;
2309 let mut range_end = range_start;
2310
2311 for &c in &chars[1..] {
2312 let codepoint = c as u32;
2313 if codepoint == range_end + 1 {
2314 range_end = codepoint;
2315 } else {
2316 ranges.push(UnicodeRange { start: range_start, end: range_end });
2317 range_start = codepoint;
2318 range_end = codepoint;
2319 }
2320 }
2321
2322 ranges.push(UnicodeRange { start: range_start, end: range_end });
2323 ranges
2324 }
2325
2326 #[cfg(feature = "std")]
2333 fn fuzzy_query_by_name(
2334 &self,
2335 requested_name: &str,
2336 weight: FcWeight,
2337 italic: PatternMatch,
2338 oblique: PatternMatch,
2339 unicode_ranges: &[UnicodeRange],
2340 _trace: &mut Vec<TraceMsg>,
2341 ) -> Vec<FontMatch> {
2342 let tokens = Self::extract_font_name_tokens(requested_name);
2344
2345 if tokens.is_empty() {
2346 return Vec::new();
2347 }
2348
2349 let tokens_lower: Vec<String> = tokens.iter().map(|t| t.to_lowercase()).collect();
2351
2352 let first_token = &tokens_lower[0];
2359 let mut candidate_ids = match self.token_index.get(first_token) {
2360 Some(ids) if !ids.is_empty() => ids.clone(),
2361 _ => {
2362 return Vec::new();
2364 }
2365 };
2366
2367 for token in &tokens_lower[1..] {
2369 if let Some(token_ids) = self.token_index.get(token) {
2370 let intersection: alloc::collections::BTreeSet<FontId> =
2372 candidate_ids.intersection(token_ids).copied().collect();
2373
2374 if intersection.is_empty() {
2375 break;
2377 } else {
2378 candidate_ids = intersection;
2380 }
2381 } else {
2382 break;
2384 }
2385 }
2386
2387 let mut candidates = Vec::new();
2389
2390 for id in candidate_ids {
2391 let pattern = match self.metadata.get(&id) {
2392 Some(p) => p,
2393 None => continue,
2394 };
2395
2396 let font_tokens_lower = match self.font_tokens.get(&id) {
2398 Some(tokens) => tokens,
2399 None => continue,
2400 };
2401
2402 if font_tokens_lower.is_empty() {
2403 continue;
2404 }
2405
2406 let token_matches = tokens_lower.iter()
2409 .filter(|req_token| {
2410 font_tokens_lower.iter().any(|font_token| {
2411 font_token == *req_token
2413 })
2414 })
2415 .count();
2416
2417 if token_matches == 0 {
2419 continue;
2420 }
2421
2422 let token_similarity = (token_matches * 100 / tokens.len()) as i32;
2424
2425 let unicode_similarity = if !unicode_ranges.is_empty() && !pattern.unicode_ranges.is_empty() {
2427 Self::calculate_unicode_compatibility(unicode_ranges, &pattern.unicode_ranges)
2428 } else {
2429 0
2430 };
2431
2432 if !unicode_ranges.is_empty() && unicode_similarity == 0 {
2435 continue;
2436 }
2437
2438 let style_score = Self::calculate_style_score(&FcPattern {
2439 weight,
2440 italic,
2441 oblique,
2442 ..Default::default()
2443 }, pattern);
2444
2445 candidates.push((
2446 id,
2447 token_similarity,
2448 unicode_similarity,
2449 style_score,
2450 pattern.clone(),
2451 ));
2452 }
2453
2454 candidates.sort_by(|a, b| {
2460 if !unicode_ranges.is_empty() {
2461 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 {
2468 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)) }
2474 });
2475
2476 candidates.truncate(5);
2478
2479 candidates
2481 .into_iter()
2482 .map(|(id, _token_sim, _unicode_sim, _style, pattern)| {
2483 FontMatch {
2484 id,
2485 unicode_ranges: pattern.unicode_ranges.clone(),
2486 fallbacks: Vec::new(), }
2488 })
2489 .collect()
2490 }
2491
2492 pub fn extract_font_name_tokens(name: &str) -> Vec<String> {
2496 let mut tokens = Vec::new();
2497 let mut current_token = String::new();
2498 let mut last_was_lower = false;
2499
2500 for c in name.chars() {
2501 if c.is_whitespace() || c == '-' || c == '_' {
2502 if !current_token.is_empty() {
2504 tokens.push(current_token.clone());
2505 current_token.clear();
2506 }
2507 last_was_lower = false;
2508 } else if c.is_uppercase() && last_was_lower && !current_token.is_empty() {
2509 tokens.push(current_token.clone());
2511 current_token.clear();
2512 current_token.push(c);
2513 last_was_lower = false;
2514 } else {
2515 current_token.push(c);
2516 last_was_lower = c.is_lowercase();
2517 }
2518 }
2519
2520 if !current_token.is_empty() {
2521 tokens.push(current_token);
2522 }
2523
2524 tokens
2525 }
2526
2527 fn find_unicode_fallbacks(
2531 &self,
2532 unicode_ranges: &[UnicodeRange],
2533 covered_chars: &[bool],
2534 existing_groups: &[CssFallbackGroup],
2535 _weight: FcWeight,
2536 _italic: PatternMatch,
2537 _oblique: PatternMatch,
2538 trace: &mut Vec<TraceMsg>,
2539 ) -> Vec<FontMatch> {
2540 let mut uncovered_ranges = Vec::new();
2542 for (i, &covered) in covered_chars.iter().enumerate() {
2543 if !covered && i < unicode_ranges.len() {
2544 uncovered_ranges.push(unicode_ranges[i].clone());
2545 }
2546 }
2547
2548 if uncovered_ranges.is_empty() {
2549 return Vec::new();
2550 }
2551
2552 let pattern = FcPattern {
2557 name: None,
2558 weight: FcWeight::Normal, italic: PatternMatch::DontCare,
2560 oblique: PatternMatch::DontCare,
2561 unicode_ranges: uncovered_ranges.clone(),
2562 ..Default::default()
2563 };
2564
2565 let mut candidates = self.query_internal(&pattern, trace);
2566
2567 let existing_prefixes: Vec<String> = existing_groups
2570 .iter()
2571 .flat_map(|group| {
2572 group.fonts.iter().filter_map(|font| {
2573 self.get_metadata_by_id(&font.id)
2574 .and_then(|meta| meta.family.clone())
2575 .and_then(|family| {
2576 family.split_whitespace()
2578 .take(2)
2579 .collect::<Vec<_>>()
2580 .join(" ")
2581 .into()
2582 })
2583 })
2584 })
2585 .collect();
2586
2587 candidates.sort_by(|a, b| {
2591 let a_meta = self.get_metadata_by_id(&a.id);
2592 let b_meta = self.get_metadata_by_id(&b.id);
2593
2594 let a_score = Self::calculate_font_similarity_score(a_meta, &existing_prefixes);
2595 let b_score = Self::calculate_font_similarity_score(b_meta, &existing_prefixes);
2596
2597 b_score.cmp(&a_score) .then_with(|| {
2599 let a_coverage = Self::calculate_unicode_compatibility(&uncovered_ranges, &a.unicode_ranges);
2600 let b_coverage = Self::calculate_unicode_compatibility(&uncovered_ranges, &b.unicode_ranges);
2601 b_coverage.cmp(&a_coverage)
2602 })
2603 });
2604
2605 let mut result = Vec::new();
2607 let mut remaining_uncovered: Vec<bool> = vec![true; uncovered_ranges.len()];
2608
2609 for candidate in candidates {
2610 let mut covers_new_range = false;
2612
2613 for (i, range) in uncovered_ranges.iter().enumerate() {
2614 if remaining_uncovered[i] {
2615 for font_range in &candidate.unicode_ranges {
2617 if font_range.overlaps(range) {
2618 remaining_uncovered[i] = false;
2619 covers_new_range = true;
2620 break;
2621 }
2622 }
2623 }
2624 }
2625
2626 if covers_new_range {
2628 result.push(candidate);
2629
2630 if remaining_uncovered.iter().all(|&uncovered| !uncovered) {
2632 break;
2633 }
2634 }
2635 }
2636
2637 result
2638 }
2639
2640 fn calculate_font_similarity_score(
2643 font_meta: Option<&FcPattern>,
2644 existing_prefixes: &[String],
2645 ) -> i32 {
2646 let Some(meta) = font_meta else { return 0; };
2647 let Some(family) = &meta.family else { return 0; };
2648
2649 for prefix in existing_prefixes {
2651 if family.starts_with(prefix) {
2652 return 100; }
2654 if family.contains(prefix) {
2655 return 50; }
2657 }
2658
2659 0 }
2661
2662 pub fn calculate_unicode_coverage(ranges: &[UnicodeRange]) -> u64 {
2665 ranges
2666 .iter()
2667 .map(|range| (range.end - range.start + 1) as u64)
2668 .sum()
2669 }
2670
2671 pub fn calculate_unicode_compatibility(
2674 requested: &[UnicodeRange],
2675 available: &[UnicodeRange],
2676 ) -> i32 {
2677 if requested.is_empty() {
2678 return Self::calculate_unicode_coverage(available) as i32;
2680 }
2681
2682 let mut total_coverage = 0u32;
2683
2684 for req_range in requested {
2685 for avail_range in available {
2686 let overlap_start = req_range.start.max(avail_range.start);
2688 let overlap_end = req_range.end.min(avail_range.end);
2689
2690 if overlap_start <= overlap_end {
2691 let overlap_size = overlap_end - overlap_start + 1;
2693 total_coverage += overlap_size;
2694 }
2695 }
2696 }
2697
2698 total_coverage as i32
2699 }
2700
2701 pub fn calculate_style_score(original: &FcPattern, candidate: &FcPattern) -> i32 {
2702
2703 let mut score = 0_i32;
2704
2705 if (original.bold == PatternMatch::True && candidate.weight == FcWeight::Bold)
2707 || (original.bold == PatternMatch::False && candidate.weight != FcWeight::Bold)
2708 {
2709 } else {
2712 let weight_diff = (original.weight as i32 - candidate.weight as i32).abs();
2714 score += weight_diff as i32;
2715 }
2716
2717 if original.weight == candidate.weight {
2720 score -= 15;
2721 if original.weight == FcWeight::Normal {
2722 score -= 10; }
2724 }
2725
2726 if (original.condensed == PatternMatch::True && candidate.stretch.is_condensed())
2728 || (original.condensed == PatternMatch::False && !candidate.stretch.is_condensed())
2729 {
2730 } else {
2733 let stretch_diff = (original.stretch as i32 - candidate.stretch as i32).abs();
2735 score += (stretch_diff * 100) as i32;
2736 }
2737
2738 let style_props = [
2740 (original.italic, candidate.italic, 300, 150),
2741 (original.oblique, candidate.oblique, 200, 100),
2742 (original.bold, candidate.bold, 300, 150),
2743 (original.monospace, candidate.monospace, 100, 50),
2744 (original.condensed, candidate.condensed, 100, 50),
2745 ];
2746
2747 for (orig, cand, mismatch_penalty, dontcare_penalty) in style_props {
2748 if orig.needs_to_match() {
2749 if orig == PatternMatch::False && cand == PatternMatch::DontCare {
2750 score += dontcare_penalty / 2;
2753 } else if !orig.matches(&cand) {
2754 if cand == PatternMatch::DontCare {
2755 score += dontcare_penalty;
2756 } else {
2757 score += mismatch_penalty;
2758 }
2759 } else if orig == PatternMatch::True && cand == PatternMatch::True {
2760 score -= 20;
2762 } else if orig == PatternMatch::False && cand == PatternMatch::False {
2763 score -= 20;
2766 }
2767 } else {
2768 if cand == PatternMatch::True {
2773 score += dontcare_penalty / 3;
2774 }
2775 }
2776 }
2777
2778 if let (Some(name), Some(family)) = (&candidate.name, &candidate.family) {
2784 let name_lower = name.to_lowercase();
2785 let family_lower = family.to_lowercase();
2786
2787 let extra = if name_lower.starts_with(&family_lower) {
2789 name_lower[family_lower.len()..].to_string()
2790 } else {
2791 String::new()
2792 };
2793
2794 let stripped = extra
2796 .replace("regular", "")
2797 .replace("normal", "")
2798 .replace("book", "")
2799 .replace("roman", "");
2800 let stripped = stripped.trim();
2801
2802 if stripped.is_empty() {
2803 score -= 50;
2805 } else {
2806 let extra_words = stripped.split_whitespace().count();
2808 score += (extra_words as i32) * 25;
2809 }
2810 }
2811
2812 if let Some(ref subfamily) = candidate.metadata.font_subfamily {
2816 let sf_lower = subfamily.to_lowercase();
2817 if sf_lower == "regular" {
2818 score -= 30;
2819 }
2820 }
2821
2822 score
2823 }
2824}
2825
2826#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
2827fn FcScanDirectories() -> Option<(Vec<(FcPattern, FcFontPath)>, BTreeMap<String, FcFontRenderConfig>)> {
2828 use std::fs;
2829 use std::path::Path;
2830
2831 const BASE_FONTCONFIG_PATH: &str = "/etc/fonts/fonts.conf";
2832
2833 if !Path::new(BASE_FONTCONFIG_PATH).exists() {
2834 return None;
2835 }
2836
2837 let mut font_paths = Vec::with_capacity(32);
2838 let mut paths_to_visit = vec![(None, PathBuf::from(BASE_FONTCONFIG_PATH))];
2839 let mut render_configs: BTreeMap<String, FcFontRenderConfig> = BTreeMap::new();
2840
2841 while let Some((prefix, path_to_visit)) = paths_to_visit.pop() {
2842 let path = match process_path(&prefix, path_to_visit, true) {
2843 Some(path) => path,
2844 None => continue,
2845 };
2846
2847 let metadata = match fs::metadata(&path) {
2848 Ok(metadata) => metadata,
2849 Err(_) => continue,
2850 };
2851
2852 if metadata.is_file() {
2853 let xml_utf8 = match fs::read_to_string(&path) {
2854 Ok(xml_utf8) => xml_utf8,
2855 Err(_) => continue,
2856 };
2857
2858 if ParseFontsConf(&xml_utf8, &mut paths_to_visit, &mut font_paths).is_none() {
2859 continue;
2860 }
2861
2862 ParseFontsConfRenderConfig(&xml_utf8, &mut render_configs);
2864 } else if metadata.is_dir() {
2865 let dir_entries = match fs::read_dir(&path) {
2866 Ok(dir_entries) => dir_entries,
2867 Err(_) => continue,
2868 };
2869
2870 for entry_result in dir_entries {
2871 let entry = match entry_result {
2872 Ok(entry) => entry,
2873 Err(_) => continue,
2874 };
2875
2876 let entry_path = entry.path();
2877
2878 let entry_metadata = match fs::metadata(&entry_path) {
2880 Ok(metadata) => metadata,
2881 Err(_) => continue,
2882 };
2883
2884 if !entry_metadata.is_file() {
2885 continue;
2886 }
2887
2888 let file_name = match entry_path.file_name() {
2889 Some(name) => name,
2890 None => continue,
2891 };
2892
2893 let file_name_str = file_name.to_string_lossy();
2894 if file_name_str.starts_with(|c: char| c.is_ascii_digit())
2895 && file_name_str.ends_with(".conf")
2896 {
2897 paths_to_visit.push((None, entry_path));
2898 }
2899 }
2900 }
2901 }
2902
2903 if font_paths.is_empty() {
2904 return None;
2905 }
2906
2907 Some((FcScanDirectoriesInner(&font_paths), render_configs))
2908}
2909
2910#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
2912fn ParseFontsConf(
2913 input: &str,
2914 paths_to_visit: &mut Vec<(Option<String>, PathBuf)>,
2915 font_paths: &mut Vec<(Option<String>, String)>,
2916) -> Option<()> {
2917 use xmlparser::Token::*;
2918 use xmlparser::Tokenizer;
2919
2920 const TAG_INCLUDE: &str = "include";
2921 const TAG_DIR: &str = "dir";
2922 const ATTRIBUTE_PREFIX: &str = "prefix";
2923
2924 let mut current_prefix: Option<&str> = None;
2925 let mut current_path: Option<&str> = None;
2926 let mut is_in_include = false;
2927 let mut is_in_dir = false;
2928
2929 for token_result in Tokenizer::from(input) {
2930 let token = match token_result {
2931 Ok(token) => token,
2932 Err(_) => return None,
2933 };
2934
2935 match token {
2936 ElementStart { local, .. } => {
2937 if is_in_include || is_in_dir {
2938 return None; }
2940
2941 match local.as_str() {
2942 TAG_INCLUDE => {
2943 is_in_include = true;
2944 }
2945 TAG_DIR => {
2946 is_in_dir = true;
2947 }
2948 _ => continue,
2949 }
2950
2951 current_path = None;
2952 }
2953 Text { text, .. } => {
2954 let text = text.as_str().trim();
2955 if text.is_empty() {
2956 continue;
2957 }
2958 if is_in_include || is_in_dir {
2959 current_path = Some(text);
2960 }
2961 }
2962 Attribute { local, value, .. } => {
2963 if !is_in_include && !is_in_dir {
2964 continue;
2965 }
2966 if local.as_str() == ATTRIBUTE_PREFIX {
2968 current_prefix = Some(value.as_str());
2969 }
2970 }
2971 ElementEnd { end, .. } => {
2972 let end_tag = match end {
2973 xmlparser::ElementEnd::Close(_, a) => a,
2974 _ => continue,
2975 };
2976
2977 match end_tag.as_str() {
2978 TAG_INCLUDE => {
2979 if !is_in_include {
2980 continue;
2981 }
2982
2983 if let Some(current_path) = current_path.as_ref() {
2984 paths_to_visit.push((
2985 current_prefix.map(ToOwned::to_owned),
2986 PathBuf::from(*current_path),
2987 ));
2988 }
2989 }
2990 TAG_DIR => {
2991 if !is_in_dir {
2992 continue;
2993 }
2994
2995 if let Some(current_path) = current_path.as_ref() {
2996 font_paths.push((
2997 current_prefix.map(ToOwned::to_owned),
2998 (*current_path).to_owned(),
2999 ));
3000 }
3001 }
3002 _ => continue,
3003 }
3004
3005 is_in_include = false;
3006 is_in_dir = false;
3007 current_path = None;
3008 current_prefix = None;
3009 }
3010 _ => {}
3011 }
3012 }
3013
3014 Some(())
3015}
3016
3017#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3029fn ParseFontsConfRenderConfig(
3030 input: &str,
3031 configs: &mut BTreeMap<String, FcFontRenderConfig>,
3032) {
3033 use xmlparser::Token::*;
3034 use xmlparser::Tokenizer;
3035
3036 #[derive(Clone, Copy, PartialEq)]
3038 enum State {
3039 Idle,
3041 InMatchFont,
3043 InTestFamily,
3045 InEdit,
3047 InValue,
3049 }
3050
3051 let mut state = State::Idle;
3052 let mut match_is_font_target = false;
3053 let mut current_family: Option<String> = None;
3054 let mut current_edit_name: Option<String> = None;
3055 let mut current_value: Option<String> = None;
3056 let mut value_tag: Option<String> = None;
3057 let mut config = FcFontRenderConfig::default();
3058 let mut in_test = false;
3059 let mut test_name: Option<String> = None;
3060
3061 for token_result in Tokenizer::from(input) {
3062 let token = match token_result {
3063 Ok(token) => token,
3064 Err(_) => continue,
3065 };
3066
3067 match token {
3068 ElementStart { local, .. } => {
3069 let tag = local.as_str();
3070 match tag {
3071 "match" => {
3072 match_is_font_target = false;
3074 current_family = None;
3075 config = FcFontRenderConfig::default();
3076 }
3077 "test" if state == State::InMatchFont => {
3078 in_test = true;
3079 test_name = None;
3080 }
3081 "edit" if state == State::InMatchFont => {
3082 current_edit_name = None;
3083 }
3084 "bool" | "double" | "const" | "string" | "int" => {
3085 if state == State::InTestFamily || state == State::InEdit {
3086 value_tag = Some(tag.to_owned());
3087 current_value = None;
3088 }
3089 }
3090 _ => {}
3091 }
3092 }
3093 Attribute { local, value, .. } => {
3094 let attr_name = local.as_str();
3095 let attr_value = value.as_str();
3096
3097 match attr_name {
3098 "target" => {
3099 if attr_value == "font" {
3100 match_is_font_target = true;
3101 }
3102 }
3103 "name" => {
3104 if in_test && state == State::InMatchFont {
3105 test_name = Some(attr_value.to_owned());
3106 } else if state == State::InMatchFont {
3107 current_edit_name = Some(attr_value.to_owned());
3108 }
3109 }
3110 _ => {}
3111 }
3112 }
3113 Text { text, .. } => {
3114 let text = text.as_str().trim();
3115 if !text.is_empty() && (state == State::InTestFamily || state == State::InEdit) {
3116 current_value = Some(text.to_owned());
3117 }
3118 }
3119 ElementEnd { end, .. } => {
3120 match end {
3121 xmlparser::ElementEnd::Open => {
3122 if match_is_font_target && state == State::Idle {
3124 state = State::InMatchFont;
3125 match_is_font_target = false;
3126 } else if in_test {
3127 if test_name.as_deref() == Some("family") {
3128 state = State::InTestFamily;
3129 }
3130 in_test = false;
3131 } else if current_edit_name.is_some() && state == State::InMatchFont {
3132 state = State::InEdit;
3133 }
3134 }
3135 xmlparser::ElementEnd::Close(_, local) => {
3136 let tag = local.as_str();
3137 match tag {
3138 "match" => {
3139 if let Some(family) = current_family.take() {
3141 let empty = FcFontRenderConfig::default();
3142 if config != empty {
3143 configs.insert(family, config.clone());
3144 }
3145 }
3146 state = State::Idle;
3147 config = FcFontRenderConfig::default();
3148 }
3149 "test" => {
3150 if state == State::InTestFamily {
3151 if let Some(ref val) = current_value {
3153 current_family = Some(val.clone());
3154 }
3155 state = State::InMatchFont;
3156 }
3157 current_value = None;
3158 value_tag = None;
3159 }
3160 "edit" => {
3161 if state == State::InEdit {
3162 if let (Some(ref name), Some(ref val)) = (¤t_edit_name, ¤t_value) {
3164 apply_edit_value(&mut config, name, val, value_tag.as_deref());
3165 }
3166 state = State::InMatchFont;
3167 }
3168 current_edit_name = None;
3169 current_value = None;
3170 value_tag = None;
3171 }
3172 "bool" | "double" | "const" | "string" | "int" => {
3173 }
3175 _ => {}
3176 }
3177 }
3178 xmlparser::ElementEnd::Empty => {
3179 }
3181 }
3182 }
3183 _ => {}
3184 }
3185 }
3186}
3187
3188#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3190fn apply_edit_value(
3191 config: &mut FcFontRenderConfig,
3192 edit_name: &str,
3193 value: &str,
3194 value_tag: Option<&str>,
3195) {
3196 match edit_name {
3197 "antialias" => {
3198 config.antialias = parse_bool_value(value);
3199 }
3200 "hinting" => {
3201 config.hinting = parse_bool_value(value);
3202 }
3203 "autohint" => {
3204 config.autohint = parse_bool_value(value);
3205 }
3206 "embeddedbitmap" => {
3207 config.embeddedbitmap = parse_bool_value(value);
3208 }
3209 "embolden" => {
3210 config.embolden = parse_bool_value(value);
3211 }
3212 "minspace" => {
3213 config.minspace = parse_bool_value(value);
3214 }
3215 "hintstyle" => {
3216 config.hintstyle = parse_hintstyle_const(value);
3217 }
3218 "rgba" => {
3219 config.rgba = parse_rgba_const(value);
3220 }
3221 "lcdfilter" => {
3222 config.lcdfilter = parse_lcdfilter_const(value);
3223 }
3224 "dpi" => {
3225 if let Ok(v) = value.parse::<f64>() {
3226 config.dpi = Some(v);
3227 }
3228 }
3229 "scale" => {
3230 if let Ok(v) = value.parse::<f64>() {
3231 config.scale = Some(v);
3232 }
3233 }
3234 _ => {
3235 }
3237 }
3238}
3239
3240#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3241fn parse_bool_value(value: &str) -> Option<bool> {
3242 match value {
3243 "true" => Some(true),
3244 "false" => Some(false),
3245 _ => None,
3246 }
3247}
3248
3249#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3250fn parse_hintstyle_const(value: &str) -> Option<FcHintStyle> {
3251 match value {
3252 "hintnone" => Some(FcHintStyle::None),
3253 "hintslight" => Some(FcHintStyle::Slight),
3254 "hintmedium" => Some(FcHintStyle::Medium),
3255 "hintfull" => Some(FcHintStyle::Full),
3256 _ => None,
3257 }
3258}
3259
3260#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3261fn parse_rgba_const(value: &str) -> Option<FcRgba> {
3262 match value {
3263 "unknown" => Some(FcRgba::Unknown),
3264 "rgb" => Some(FcRgba::Rgb),
3265 "bgr" => Some(FcRgba::Bgr),
3266 "vrgb" => Some(FcRgba::Vrgb),
3267 "vbgr" => Some(FcRgba::Vbgr),
3268 "none" => Some(FcRgba::None),
3269 _ => None,
3270 }
3271}
3272
3273#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3274fn parse_lcdfilter_const(value: &str) -> Option<FcLcdFilter> {
3275 match value {
3276 "lcdnone" => Some(FcLcdFilter::None),
3277 "lcddefault" => Some(FcLcdFilter::Default),
3278 "lcdlight" => Some(FcLcdFilter::Light),
3279 "lcdlegacy" => Some(FcLcdFilter::Legacy),
3280 _ => None,
3281 }
3282}
3283
3284#[cfg(all(feature = "std", feature = "parsing"))]
3287const UNICODE_RANGE_MAPPINGS: &[(usize, u32, u32)] = &[
3288 (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), ];
3421
3422#[cfg(all(feature = "std", feature = "parsing"))]
3425struct ParsedFontFace {
3426 pattern: FcPattern,
3427 font_index: usize,
3428}
3429
3430#[cfg(all(feature = "std", feature = "parsing"))]
3436fn parse_font_faces(font_bytes: &[u8]) -> Option<Vec<ParsedFontFace>> {
3437 use allsorts::{
3438 binary::read::ReadScope,
3439 font_data::FontData,
3440 get_name::fontcode_get_name,
3441 post::PostTable,
3442 tables::{
3443 os2::Os2, HeadTable, NameTable,
3444 },
3445 tag,
3446 };
3447 use std::collections::BTreeSet;
3448
3449 const FONT_SPECIFIER_NAME_ID: u16 = 4;
3450 const FONT_SPECIFIER_FAMILY_ID: u16 = 1;
3451
3452 let max_fonts = if font_bytes.len() >= 12 && &font_bytes[0..4] == b"ttcf" {
3453 let num_fonts =
3455 u32::from_be_bytes([font_bytes[8], font_bytes[9], font_bytes[10], font_bytes[11]]);
3456 std::cmp::min(num_fonts as usize, 100)
3458 } else {
3459 1
3461 };
3462
3463 let scope = ReadScope::new(font_bytes);
3464 let font_file = scope.read::<FontData<'_>>().ok()?;
3465
3466 let mut results = Vec::new();
3468
3469 for font_index in 0..max_fonts {
3470 let provider = font_file.table_provider(font_index).ok()?;
3471 let head_data = provider.table_data(tag::HEAD).ok()??.into_owned();
3472 let head_table = ReadScope::new(&head_data).read::<HeadTable>().ok()?;
3473
3474 let is_bold = head_table.is_bold();
3475 let is_italic = head_table.is_italic();
3476 let mut detected_monospace = None;
3477
3478 let post_data = provider.table_data(tag::POST).ok()??;
3479 if let Ok(post_table) = ReadScope::new(&post_data).read::<PostTable>() {
3480 detected_monospace = Some(post_table.header.is_fixed_pitch != 0);
3482 }
3483
3484 let os2_data = provider.table_data(tag::OS_2).ok()??;
3486 let os2_table = ReadScope::new(&os2_data)
3487 .read_dep::<Os2>(os2_data.len())
3488 .ok()?;
3489
3490 let is_oblique = os2_table
3492 .fs_selection
3493 .contains(allsorts::tables::os2::FsSelection::OBLIQUE);
3494 let weight = FcWeight::from_u16(os2_table.us_weight_class);
3495 let stretch = FcStretch::from_u16(os2_table.us_width_class);
3496
3497 let mut unicode_ranges = Vec::new();
3501
3502 let os2_ranges = [
3504 os2_table.ul_unicode_range1,
3505 os2_table.ul_unicode_range2,
3506 os2_table.ul_unicode_range3,
3507 os2_table.ul_unicode_range4,
3508 ];
3509
3510 for &(bit, start, end) in UNICODE_RANGE_MAPPINGS {
3511 let range_idx = bit / 32;
3512 let bit_pos = bit % 32;
3513 if range_idx < 4 && (os2_ranges[range_idx] & (1 << bit_pos)) != 0 {
3514 unicode_ranges.push(UnicodeRange { start, end });
3515 }
3516 }
3517
3518 unicode_ranges = verify_unicode_ranges_with_cmap(&provider, unicode_ranges);
3522
3523 if unicode_ranges.is_empty() {
3525 if let Some(cmap_ranges) = analyze_cmap_coverage(&provider) {
3526 unicode_ranges = cmap_ranges;
3527 }
3528 }
3529
3530 let is_monospace = detect_monospace(&provider, &os2_table, detected_monospace)
3532 .unwrap_or(false);
3533
3534 let name_data = provider.table_data(tag::NAME).ok()??.into_owned();
3535 let name_table = ReadScope::new(&name_data).read::<NameTable>().ok()?;
3536
3537 let mut metadata = FcFontMetadata::default();
3539
3540 const NAME_ID_COPYRIGHT: u16 = 0;
3541 const NAME_ID_FAMILY: u16 = 1;
3542 const NAME_ID_SUBFAMILY: u16 = 2;
3543 const NAME_ID_UNIQUE_ID: u16 = 3;
3544 const NAME_ID_FULL_NAME: u16 = 4;
3545 const NAME_ID_VERSION: u16 = 5;
3546 const NAME_ID_POSTSCRIPT_NAME: u16 = 6;
3547 const NAME_ID_TRADEMARK: u16 = 7;
3548 const NAME_ID_MANUFACTURER: u16 = 8;
3549 const NAME_ID_DESIGNER: u16 = 9;
3550 const NAME_ID_DESCRIPTION: u16 = 10;
3551 const NAME_ID_VENDOR_URL: u16 = 11;
3552 const NAME_ID_DESIGNER_URL: u16 = 12;
3553 const NAME_ID_LICENSE: u16 = 13;
3554 const NAME_ID_LICENSE_URL: u16 = 14;
3555 const NAME_ID_PREFERRED_FAMILY: u16 = 16;
3556 const NAME_ID_PREFERRED_SUBFAMILY: u16 = 17;
3557
3558 metadata.copyright = get_name_string(&name_data, NAME_ID_COPYRIGHT);
3559 metadata.font_family = get_name_string(&name_data, NAME_ID_FAMILY);
3560 metadata.font_subfamily = get_name_string(&name_data, NAME_ID_SUBFAMILY);
3561 metadata.full_name = get_name_string(&name_data, NAME_ID_FULL_NAME);
3562 metadata.unique_id = get_name_string(&name_data, NAME_ID_UNIQUE_ID);
3563 metadata.version = get_name_string(&name_data, NAME_ID_VERSION);
3564 metadata.postscript_name = get_name_string(&name_data, NAME_ID_POSTSCRIPT_NAME);
3565 metadata.trademark = get_name_string(&name_data, NAME_ID_TRADEMARK);
3566 metadata.manufacturer = get_name_string(&name_data, NAME_ID_MANUFACTURER);
3567 metadata.designer = get_name_string(&name_data, NAME_ID_DESIGNER);
3568 metadata.id_description = get_name_string(&name_data, NAME_ID_DESCRIPTION);
3569 metadata.designer_url = get_name_string(&name_data, NAME_ID_DESIGNER_URL);
3570 metadata.manufacturer_url = get_name_string(&name_data, NAME_ID_VENDOR_URL);
3571 metadata.license = get_name_string(&name_data, NAME_ID_LICENSE);
3572 metadata.license_url = get_name_string(&name_data, NAME_ID_LICENSE_URL);
3573 metadata.preferred_family = get_name_string(&name_data, NAME_ID_PREFERRED_FAMILY);
3574 metadata.preferred_subfamily = get_name_string(&name_data, NAME_ID_PREFERRED_SUBFAMILY);
3575
3576 let mut f_family = None;
3578
3579 let patterns = name_table
3580 .name_records
3581 .iter()
3582 .filter_map(|name_record| {
3583 let name_id = name_record.name_id;
3584 if name_id == FONT_SPECIFIER_FAMILY_ID {
3585 if let Ok(Some(family)) =
3586 fontcode_get_name(&name_data, FONT_SPECIFIER_FAMILY_ID)
3587 {
3588 f_family = Some(family);
3589 }
3590 None
3591 } else if name_id == FONT_SPECIFIER_NAME_ID {
3592 let family = f_family.as_ref()?;
3593 let name = fontcode_get_name(&name_data, FONT_SPECIFIER_NAME_ID).ok()??;
3594 if name.to_bytes().is_empty() {
3595 None
3596 } else {
3597 let mut name_str =
3598 String::from_utf8_lossy(name.to_bytes()).to_string();
3599 let mut family_str =
3600 String::from_utf8_lossy(family.as_bytes()).to_string();
3601 if name_str.starts_with('.') {
3602 name_str = name_str[1..].to_string();
3603 }
3604 if family_str.starts_with('.') {
3605 family_str = family_str[1..].to_string();
3606 }
3607 Some((
3608 FcPattern {
3609 name: Some(name_str),
3610 family: Some(family_str),
3611 bold: if is_bold {
3612 PatternMatch::True
3613 } else {
3614 PatternMatch::False
3615 },
3616 italic: if is_italic {
3617 PatternMatch::True
3618 } else {
3619 PatternMatch::False
3620 },
3621 oblique: if is_oblique {
3622 PatternMatch::True
3623 } else {
3624 PatternMatch::False
3625 },
3626 monospace: if is_monospace {
3627 PatternMatch::True
3628 } else {
3629 PatternMatch::False
3630 },
3631 condensed: if stretch <= FcStretch::Condensed {
3632 PatternMatch::True
3633 } else {
3634 PatternMatch::False
3635 },
3636 weight,
3637 stretch,
3638 unicode_ranges: unicode_ranges.clone(),
3639 metadata: metadata.clone(),
3640 render_config: FcFontRenderConfig::default(),
3641 },
3642 font_index,
3643 ))
3644 }
3645 } else {
3646 None
3647 }
3648 })
3649 .collect::<BTreeSet<_>>();
3650
3651 results.extend(patterns.into_iter().map(|(pat, idx)| ParsedFontFace {
3652 pattern: pat,
3653 font_index: idx,
3654 }));
3655 }
3656
3657 if results.is_empty() {
3658 None
3659 } else {
3660 Some(results)
3661 }
3662}
3663
3664#[cfg(all(feature = "std", feature = "parsing"))]
3666pub(crate) fn FcParseFont(filepath: &PathBuf) -> Option<Vec<(FcPattern, FcFontPath)>> {
3667 #[cfg(all(not(target_family = "wasm"), feature = "std"))]
3668 use mmapio::MmapOptions;
3669 use std::fs::File;
3670
3671 let file = File::open(filepath).ok()?;
3673
3674 #[cfg(all(not(target_family = "wasm"), feature = "std"))]
3675 let font_bytes = unsafe { MmapOptions::new().map(&file).ok()? };
3676
3677 #[cfg(not(all(not(target_family = "wasm"), feature = "std")))]
3678 let font_bytes = std::fs::read(filepath).ok()?;
3679
3680 let faces = parse_font_faces(&font_bytes[..])?;
3681 let path_str = filepath.to_string_lossy().to_string();
3682 let bytes_hash = crate::utils::content_dedup_hash_u64(&font_bytes[..]);
3687
3688 Some(
3689 faces
3690 .into_iter()
3691 .map(|face| {
3692 (
3693 face.pattern,
3694 FcFontPath {
3695 path: path_str.clone(),
3696 font_index: face.font_index,
3697 bytes_hash,
3698 },
3699 )
3700 })
3701 .collect(),
3702 )
3703}
3704
3705#[cfg(all(feature = "std", feature = "parsing"))]
3731#[allow(non_snake_case)]
3732pub fn FcParseFontBytes(font_bytes: &[u8], font_id: &str) -> Option<Vec<(FcPattern, FcFont)>> {
3733 FcParseFontBytesInner(font_bytes, font_id)
3734}
3735
3736#[cfg(all(feature = "std", feature = "parsing"))]
3739fn FcParseFontBytesInner(font_bytes: &[u8], font_id: &str) -> Option<Vec<(FcPattern, FcFont)>> {
3740 let faces = parse_font_faces(font_bytes)?;
3741 let id = font_id.to_string();
3742 let bytes = font_bytes.to_vec();
3743
3744 Some(
3745 faces
3746 .into_iter()
3747 .map(|face| {
3748 (
3749 face.pattern,
3750 FcFont {
3751 bytes: bytes.clone(),
3752 font_index: face.font_index,
3753 id: id.clone(),
3754 },
3755 )
3756 })
3757 .collect(),
3758 )
3759}
3760
3761#[cfg(all(feature = "std", feature = "parsing"))]
3762fn FcScanDirectoriesInner(paths: &[(Option<String>, String)]) -> Vec<(FcPattern, FcFontPath)> {
3763 #[cfg(feature = "multithreading")]
3764 {
3765 use rayon::prelude::*;
3766
3767 paths
3769 .par_iter()
3770 .filter_map(|(prefix, p)| {
3771 process_path(prefix, PathBuf::from(p), false).map(FcScanSingleDirectoryRecursive)
3772 })
3773 .flatten()
3774 .collect()
3775 }
3776 #[cfg(not(feature = "multithreading"))]
3777 {
3778 paths
3779 .iter()
3780 .filter_map(|(prefix, p)| {
3781 process_path(prefix, PathBuf::from(p), false).map(FcScanSingleDirectoryRecursive)
3782 })
3783 .flatten()
3784 .collect()
3785 }
3786}
3787
3788#[cfg(feature = "std")]
3790fn FcCollectFontFilesRecursive(dir: PathBuf) -> Vec<PathBuf> {
3791 let mut files = Vec::new();
3792 let mut dirs_to_parse = vec![dir];
3793
3794 loop {
3795 let mut new_dirs = Vec::new();
3796 for dir in &dirs_to_parse {
3797 let entries = match std::fs::read_dir(dir) {
3798 Ok(o) => o,
3799 Err(_) => continue,
3800 };
3801 for entry in entries.flatten() {
3802 let path = entry.path();
3803 if path.is_dir() {
3804 new_dirs.push(path);
3805 } else {
3806 files.push(path);
3807 }
3808 }
3809 }
3810 if new_dirs.is_empty() {
3811 break;
3812 }
3813 dirs_to_parse = new_dirs;
3814 }
3815
3816 files
3817}
3818
3819#[cfg(all(feature = "std", feature = "parsing"))]
3820fn FcScanSingleDirectoryRecursive(dir: PathBuf) -> Vec<(FcPattern, FcFontPath)> {
3821 let files = FcCollectFontFilesRecursive(dir);
3822 FcParseFontFiles(&files)
3823}
3824
3825#[cfg(all(feature = "std", feature = "parsing"))]
3826fn FcParseFontFiles(files_to_parse: &[PathBuf]) -> Vec<(FcPattern, FcFontPath)> {
3827 let result = {
3828 #[cfg(feature = "multithreading")]
3829 {
3830 use rayon::prelude::*;
3831
3832 files_to_parse
3833 .par_iter()
3834 .filter_map(|file| FcParseFont(file))
3835 .collect::<Vec<Vec<_>>>()
3836 }
3837 #[cfg(not(feature = "multithreading"))]
3838 {
3839 files_to_parse
3840 .iter()
3841 .filter_map(|file| FcParseFont(file))
3842 .collect::<Vec<Vec<_>>>()
3843 }
3844 };
3845
3846 result.into_iter().flat_map(|f| f.into_iter()).collect()
3847}
3848
3849#[cfg(all(feature = "std", feature = "parsing"))]
3850fn process_path(
3854 prefix: &Option<String>,
3855 mut path: PathBuf,
3856 is_include_path: bool,
3857) -> Option<PathBuf> {
3858 use std::env::var;
3859
3860 const HOME_SHORTCUT: &str = "~";
3861 const CWD_PATH: &str = ".";
3862
3863 const HOME_ENV_VAR: &str = "HOME";
3864 const XDG_CONFIG_HOME_ENV_VAR: &str = "XDG_CONFIG_HOME";
3865 const XDG_CONFIG_HOME_DEFAULT_PATH_SUFFIX: &str = ".config";
3866 const XDG_DATA_HOME_ENV_VAR: &str = "XDG_DATA_HOME";
3867 const XDG_DATA_HOME_DEFAULT_PATH_SUFFIX: &str = ".local/share";
3868
3869 const PREFIX_CWD: &str = "cwd";
3870 const PREFIX_DEFAULT: &str = "default";
3871 const PREFIX_XDG: &str = "xdg";
3872
3873 fn get_home_value() -> Option<PathBuf> {
3875 var(HOME_ENV_VAR).ok().map(PathBuf::from)
3876 }
3877 fn get_xdg_config_home_value() -> Option<PathBuf> {
3878 var(XDG_CONFIG_HOME_ENV_VAR)
3879 .ok()
3880 .map(PathBuf::from)
3881 .or_else(|| {
3882 get_home_value()
3883 .map(|home_path| home_path.join(XDG_CONFIG_HOME_DEFAULT_PATH_SUFFIX))
3884 })
3885 }
3886 fn get_xdg_data_home_value() -> Option<PathBuf> {
3887 var(XDG_DATA_HOME_ENV_VAR)
3888 .ok()
3889 .map(PathBuf::from)
3890 .or_else(|| {
3891 get_home_value().map(|home_path| home_path.join(XDG_DATA_HOME_DEFAULT_PATH_SUFFIX))
3892 })
3893 }
3894
3895 if path.starts_with(HOME_SHORTCUT) {
3897 if let Some(home_path) = get_home_value() {
3898 path = home_path.join(
3899 path.strip_prefix(HOME_SHORTCUT)
3900 .expect("already checked that it starts with the prefix"),
3901 );
3902 } else {
3903 return None;
3904 }
3905 }
3906
3907 match prefix {
3909 Some(prefix) => match prefix.as_str() {
3910 PREFIX_CWD | PREFIX_DEFAULT => {
3911 let mut new_path = PathBuf::from(CWD_PATH);
3912 new_path.push(path);
3913
3914 Some(new_path)
3915 }
3916 PREFIX_XDG => {
3917 if is_include_path {
3918 get_xdg_config_home_value()
3919 .map(|xdg_config_home_path| xdg_config_home_path.join(path))
3920 } else {
3921 get_xdg_data_home_value()
3922 .map(|xdg_data_home_path| xdg_data_home_path.join(path))
3923 }
3924 }
3925 _ => None, },
3927 None => Some(path),
3928 }
3929}
3930
3931#[cfg(all(feature = "std", feature = "parsing"))]
3933fn get_name_string(name_data: &[u8], name_id: u16) -> Option<String> {
3934 fontcode_get_name(name_data, name_id)
3935 .ok()
3936 .flatten()
3937 .map(|name| String::from_utf8_lossy(name.to_bytes()).to_string())
3938}
3939
3940#[cfg(all(feature = "std", feature = "parsing"))]
3944fn get_verification_codepoints(start: u32, end: u32) -> Vec<u32> {
3945 match start {
3946 0x0000 => vec!['A' as u32, 'M' as u32, 'Z' as u32, 'a' as u32, 'm' as u32, 'z' as u32],
3948 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], _ => {
4024 let range_size = end - start;
4025 if range_size > 20 {
4026 vec![
4027 start + range_size / 5,
4028 start + 2 * range_size / 5,
4029 start + 3 * range_size / 5,
4030 start + 4 * range_size / 5,
4031 ]
4032 } else {
4033 vec![start, start + range_size / 2]
4034 }
4035 }
4036 }
4037}
4038
4039#[cfg(all(feature = "std", feature = "parsing"))]
4042fn find_best_cmap_subtable<'a>(
4043 cmap: &allsorts::tables::cmap::Cmap<'a>,
4044) -> Option<allsorts::tables::cmap::EncodingRecord> {
4045 use allsorts::tables::cmap::{PlatformId, EncodingId};
4046
4047 cmap.find_subtable(PlatformId::UNICODE, EncodingId(3))
4048 .or_else(|| cmap.find_subtable(PlatformId::UNICODE, EncodingId(4)))
4049 .or_else(|| cmap.find_subtable(PlatformId::WINDOWS, EncodingId(1)))
4050 .or_else(|| cmap.find_subtable(PlatformId::WINDOWS, EncodingId(10)))
4051 .or_else(|| cmap.find_subtable(PlatformId::UNICODE, EncodingId(0)))
4052 .or_else(|| cmap.find_subtable(PlatformId::UNICODE, EncodingId(1)))
4053}
4054
4055#[cfg(all(feature = "std", feature = "parsing"))]
4058fn verify_unicode_ranges_with_cmap(
4059 provider: &impl FontTableProvider,
4060 os2_ranges: Vec<UnicodeRange>
4061) -> Vec<UnicodeRange> {
4062 use allsorts::tables::cmap::{Cmap, CmapSubtable};
4063
4064 if os2_ranges.is_empty() {
4065 return Vec::new();
4066 }
4067
4068 let cmap_data = match provider.table_data(tag::CMAP) {
4070 Ok(Some(data)) => data,
4071 _ => return os2_ranges, };
4073
4074 let cmap = match ReadScope::new(&cmap_data).read::<Cmap<'_>>() {
4075 Ok(c) => c,
4076 Err(_) => return os2_ranges,
4077 };
4078
4079 let encoding_record = match find_best_cmap_subtable(&cmap) {
4080 Some(r) => r,
4081 None => return os2_ranges, };
4083
4084 let cmap_subtable = match ReadScope::new(&cmap_data)
4085 .offset(encoding_record.offset as usize)
4086 .read::<CmapSubtable<'_>>()
4087 {
4088 Ok(st) => st,
4089 Err(_) => return os2_ranges,
4090 };
4091
4092 let mut verified_ranges = Vec::new();
4094
4095 for range in os2_ranges {
4096 let test_codepoints = get_verification_codepoints(range.start, range.end);
4097
4098 let required_hits = (test_codepoints.len() + 1) / 2; let mut hits = 0;
4102
4103 for cp in test_codepoints {
4104 if cp >= range.start && cp <= range.end {
4105 if let Ok(Some(gid)) = cmap_subtable.map_glyph(cp) {
4106 if gid != 0 {
4107 hits += 1;
4108 if hits >= required_hits {
4109 break;
4110 }
4111 }
4112 }
4113 }
4114 }
4115
4116 if hits >= required_hits {
4117 verified_ranges.push(range);
4118 }
4119 }
4120
4121 verified_ranges
4122}
4123
4124#[cfg(all(feature = "std", feature = "parsing"))]
4127fn analyze_cmap_coverage(provider: &impl FontTableProvider) -> Option<Vec<UnicodeRange>> {
4128 use allsorts::tables::cmap::{Cmap, CmapSubtable};
4129
4130 let cmap_data = provider.table_data(tag::CMAP).ok()??;
4131 let cmap = ReadScope::new(&cmap_data).read::<Cmap<'_>>().ok()?;
4132
4133 let encoding_record = find_best_cmap_subtable(&cmap)?;
4134
4135 let cmap_subtable = ReadScope::new(&cmap_data)
4136 .offset(encoding_record.offset as usize)
4137 .read::<CmapSubtable<'_>>()
4138 .ok()?;
4139
4140 let blocks_to_check: &[(u32, u32)] = &[
4142 (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), ];
4193
4194 let mut ranges = Vec::new();
4195
4196 for &(start, end) in blocks_to_check {
4197 let test_codepoints = get_verification_codepoints(start, end);
4198 let required_hits = (test_codepoints.len() + 1) / 2;
4199 let mut hits = 0;
4200
4201 for cp in test_codepoints {
4202 if let Ok(Some(gid)) = cmap_subtable.map_glyph(cp) {
4203 if gid != 0 {
4204 hits += 1;
4205 if hits >= required_hits {
4206 break;
4207 }
4208 }
4209 }
4210 }
4211
4212 if hits >= required_hits {
4213 ranges.push(UnicodeRange { start, end });
4214 }
4215 }
4216
4217 if ranges.is_empty() {
4218 None
4219 } else {
4220 Some(ranges)
4221 }
4222}
4223
4224#[cfg(all(feature = "std", feature = "parsing"))]
4226#[allow(dead_code)]
4227fn extract_unicode_ranges(os2_table: &Os2) -> Vec<UnicodeRange> {
4228 let mut unicode_ranges = Vec::new();
4229
4230 let ranges = [
4231 os2_table.ul_unicode_range1,
4232 os2_table.ul_unicode_range2,
4233 os2_table.ul_unicode_range3,
4234 os2_table.ul_unicode_range4,
4235 ];
4236
4237 for &(bit, start, end) in UNICODE_RANGE_MAPPINGS {
4238 let range_idx = bit / 32;
4239 let bit_pos = bit % 32;
4240 if range_idx < 4 && (ranges[range_idx] & (1 << bit_pos)) != 0 {
4241 unicode_ranges.push(UnicodeRange { start, end });
4242 }
4243 }
4244
4245 unicode_ranges
4246}
4247
4248#[cfg(all(feature = "std", feature = "parsing"))]
4250fn detect_monospace(
4251 provider: &impl FontTableProvider,
4252 os2_table: &Os2,
4253 detected_monospace: Option<bool>,
4254) -> Option<bool> {
4255 if let Some(is_monospace) = detected_monospace {
4256 return Some(is_monospace);
4257 }
4258
4259 if os2_table.panose[0] == 2 {
4261 return Some(os2_table.panose[3] == 9); }
4264
4265 let hhea_data = provider.table_data(tag::HHEA).ok()??;
4267 let hhea_table = ReadScope::new(&hhea_data).read::<HheaTable>().ok()?;
4268 let maxp_data = provider.table_data(tag::MAXP).ok()??;
4269 let maxp_table = ReadScope::new(&maxp_data).read::<MaxpTable>().ok()?;
4270 let hmtx_data = provider.table_data(tag::HMTX).ok()??;
4271 let hmtx_table = ReadScope::new(&hmtx_data)
4272 .read_dep::<HmtxTable<'_>>((
4273 usize::from(maxp_table.num_glyphs),
4274 usize::from(hhea_table.num_h_metrics),
4275 ))
4276 .ok()?;
4277
4278 let mut monospace = true;
4279 let mut last_advance = 0;
4280
4281 for i in 0..hhea_table.num_h_metrics as usize {
4283 let advance = hmtx_table.h_metrics.read_item(i).ok()?.advance_width;
4284 if i > 0 && advance != last_advance {
4285 monospace = false;
4286 break;
4287 }
4288 last_advance = advance;
4289 }
4290
4291 Some(monospace)
4292}
4293
4294#[cfg(feature = "std")]
4299fn pattern_from_filename(path: &std::path::Path) -> Option<FcPattern> {
4300 let ext = path.extension()?.to_str()?.to_lowercase();
4301 match ext.as_str() {
4302 "ttf" | "otf" | "ttc" | "woff" | "woff2" => {}
4303 _ => return None,
4304 }
4305
4306 let stem = path.file_stem()?.to_str()?;
4307 let all_tokens = crate::config::tokenize_lowercase(stem);
4308
4309 let has_token = |kw: &str| all_tokens.iter().any(|t| t == kw);
4311 let is_bold = has_token("bold") || has_token("heavy");
4312 let is_italic = has_token("italic");
4313 let is_oblique = has_token("oblique");
4314 let is_mono = has_token("mono") || has_token("monospace");
4315 let is_condensed = has_token("condensed");
4316
4317 let family_tokens = crate::config::tokenize_font_stem(stem);
4319 if family_tokens.is_empty() { return None; }
4320 let family = family_tokens.join(" ");
4321
4322 Some(FcPattern {
4323 name: Some(stem.to_string()),
4324 family: Some(family),
4325 bold: if is_bold { PatternMatch::True } else { PatternMatch::False },
4326 italic: if is_italic { PatternMatch::True } else { PatternMatch::False },
4327 oblique: if is_oblique { PatternMatch::True } else { PatternMatch::DontCare },
4328 monospace: if is_mono { PatternMatch::True } else { PatternMatch::DontCare },
4329 condensed: if is_condensed { PatternMatch::True } else { PatternMatch::DontCare },
4330 weight: if is_bold { FcWeight::Bold } else { FcWeight::Normal },
4331 stretch: if is_condensed { FcStretch::Condensed } else { FcStretch::Normal },
4332 unicode_ranges: Vec::new(),
4333 metadata: FcFontMetadata::default(),
4334 render_config: FcFontRenderConfig::default(),
4335 })
4336}