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(all(feature = "std", feature = "parsing"))]
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
695impl UnicodeRange {
696 pub fn contains(&self, c: char) -> bool {
697 let c = c as u32;
698 c >= self.start && c <= self.end
699 }
700
701 pub fn overlaps(&self, other: &UnicodeRange) -> bool {
702 self.start <= other.end && other.start <= self.end
703 }
704
705 pub fn is_subset_of(&self, other: &UnicodeRange) -> bool {
706 self.start >= other.start && self.end <= other.end
707 }
708}
709
710pub fn has_cjk_ranges(ranges: &[UnicodeRange]) -> bool {
712 ranges.iter().any(|r| {
713 (r.start >= 0x4E00 && r.start <= 0x9FFF) ||
714 (r.start >= 0x3040 && r.start <= 0x309F) ||
715 (r.start >= 0x30A0 && r.start <= 0x30FF) ||
716 (r.start >= 0xAC00 && r.start <= 0xD7AF)
717 })
718}
719
720pub fn has_arabic_ranges(ranges: &[UnicodeRange]) -> bool {
722 ranges.iter().any(|r| r.start >= 0x0600 && r.start <= 0x06FF)
723}
724
725pub fn has_cyrillic_ranges(ranges: &[UnicodeRange]) -> bool {
727 ranges.iter().any(|r| r.start >= 0x0400 && r.start <= 0x04FF)
728}
729
730pub fn has_hebrew_ranges(ranges: &[UnicodeRange]) -> bool {
732 ranges.iter().any(|r| r.start >= 0x0590 && r.start <= 0x05FF)
733}
734
735pub fn has_thai_ranges(ranges: &[UnicodeRange]) -> bool {
737 ranges.iter().any(|r| r.start >= 0x0E00 && r.start <= 0x0E7F)
738}
739
740#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
742pub enum TraceLevel {
743 Debug,
744 Info,
745 Warning,
746 Error,
747}
748
749#[derive(Debug, Clone, PartialEq, Eq, Hash)]
751pub enum MatchReason {
752 NameMismatch {
753 requested: Option<String>,
754 found: Option<String>,
755 },
756 FamilyMismatch {
757 requested: Option<String>,
758 found: Option<String>,
759 },
760 StyleMismatch {
761 property: &'static str,
762 requested: String,
763 found: String,
764 },
765 WeightMismatch {
766 requested: FcWeight,
767 found: FcWeight,
768 },
769 StretchMismatch {
770 requested: FcStretch,
771 found: FcStretch,
772 },
773 UnicodeRangeMismatch {
774 character: char,
775 ranges: Vec<UnicodeRange>,
776 },
777 Success,
778}
779
780#[derive(Debug, Clone, PartialEq, Eq)]
782pub struct TraceMsg {
783 pub level: TraceLevel,
784 pub path: String,
785 pub reason: MatchReason,
786}
787
788#[repr(C)]
790#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
791#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
792pub enum FcHintStyle {
793 #[default]
794 None = 0,
795 Slight = 1,
796 Medium = 2,
797 Full = 3,
798}
799
800#[repr(C)]
802#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
803#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
804pub enum FcRgba {
805 #[default]
806 Unknown = 0,
807 Rgb = 1,
808 Bgr = 2,
809 Vrgb = 3,
810 Vbgr = 4,
811 None = 5,
812}
813
814#[repr(C)]
816#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
817#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
818pub enum FcLcdFilter {
819 #[default]
820 None = 0,
821 Default = 1,
822 Light = 2,
823 Legacy = 3,
824}
825
826#[derive(Debug, Default, Clone, PartialEq, PartialOrd)]
831#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
832pub struct FcFontRenderConfig {
833 pub antialias: Option<bool>,
834 pub hinting: Option<bool>,
835 pub hintstyle: Option<FcHintStyle>,
836 pub autohint: Option<bool>,
837 pub rgba: Option<FcRgba>,
838 pub lcdfilter: Option<FcLcdFilter>,
839 pub embeddedbitmap: Option<bool>,
840 pub embolden: Option<bool>,
841 pub dpi: Option<f64>,
842 pub scale: Option<f64>,
843 pub minspace: Option<bool>,
844}
845
846impl Eq for FcFontRenderConfig {}
849
850impl Ord for FcFontRenderConfig {
851 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
852 let ord = self.antialias.cmp(&other.antialias)
854 .then_with(|| self.hinting.cmp(&other.hinting))
855 .then_with(|| self.hintstyle.cmp(&other.hintstyle))
856 .then_with(|| self.autohint.cmp(&other.autohint))
857 .then_with(|| self.rgba.cmp(&other.rgba))
858 .then_with(|| self.lcdfilter.cmp(&other.lcdfilter))
859 .then_with(|| self.embeddedbitmap.cmp(&other.embeddedbitmap))
860 .then_with(|| self.embolden.cmp(&other.embolden))
861 .then_with(|| self.minspace.cmp(&other.minspace));
862
863 let ord = ord.then_with(|| {
865 let a = self.dpi.map(|v| v.to_bits());
866 let b = other.dpi.map(|v| v.to_bits());
867 a.cmp(&b)
868 });
869 ord.then_with(|| {
870 let a = self.scale.map(|v| v.to_bits());
871 let b = other.scale.map(|v| v.to_bits());
872 a.cmp(&b)
873 })
874 }
875}
876
877#[derive(Default, Clone, PartialOrd, Ord, PartialEq, Eq)]
879#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
880#[repr(C)]
881pub struct FcPattern {
882 pub name: Option<String>,
884 pub family: Option<String>,
886 pub italic: PatternMatch,
888 pub oblique: PatternMatch,
890 pub bold: PatternMatch,
892 pub monospace: PatternMatch,
894 pub condensed: PatternMatch,
896 pub weight: FcWeight,
898 pub stretch: FcStretch,
900 pub unicode_ranges: Vec<UnicodeRange>,
902 pub metadata: FcFontMetadata,
904 pub render_config: FcFontRenderConfig,
906}
907
908impl core::fmt::Debug for FcPattern {
909 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
910 let mut d = f.debug_struct("FcPattern");
911
912 if let Some(name) = &self.name {
913 d.field("name", name);
914 }
915
916 if let Some(family) = &self.family {
917 d.field("family", family);
918 }
919
920 if self.italic != PatternMatch::DontCare {
921 d.field("italic", &self.italic);
922 }
923
924 if self.oblique != PatternMatch::DontCare {
925 d.field("oblique", &self.oblique);
926 }
927
928 if self.bold != PatternMatch::DontCare {
929 d.field("bold", &self.bold);
930 }
931
932 if self.monospace != PatternMatch::DontCare {
933 d.field("monospace", &self.monospace);
934 }
935
936 if self.condensed != PatternMatch::DontCare {
937 d.field("condensed", &self.condensed);
938 }
939
940 if self.weight != FcWeight::Normal {
941 d.field("weight", &self.weight);
942 }
943
944 if self.stretch != FcStretch::Normal {
945 d.field("stretch", &self.stretch);
946 }
947
948 if !self.unicode_ranges.is_empty() {
949 d.field("unicode_ranges", &self.unicode_ranges);
950 }
951
952 let empty_metadata = FcFontMetadata::default();
954 if self.metadata != empty_metadata {
955 d.field("metadata", &self.metadata);
956 }
957
958 let empty_render_config = FcFontRenderConfig::default();
960 if self.render_config != empty_render_config {
961 d.field("render_config", &self.render_config);
962 }
963
964 d.finish()
965 }
966}
967
968#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]
970#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
971pub struct FcFontMetadata {
972 pub copyright: Option<String>,
973 pub designer: Option<String>,
974 pub designer_url: Option<String>,
975 pub font_family: Option<String>,
976 pub font_subfamily: Option<String>,
977 pub full_name: Option<String>,
978 pub id_description: Option<String>,
979 pub license: Option<String>,
980 pub license_url: Option<String>,
981 pub manufacturer: Option<String>,
982 pub manufacturer_url: Option<String>,
983 pub postscript_name: Option<String>,
984 pub preferred_family: Option<String>,
985 pub preferred_subfamily: Option<String>,
986 pub trademark: Option<String>,
987 pub unique_id: Option<String>,
988 pub version: Option<String>,
989}
990
991impl FcPattern {
992 pub fn contains_char(&self, c: char) -> bool {
994 if self.unicode_ranges.is_empty() {
995 return true; }
997
998 for range in &self.unicode_ranges {
999 if range.contains(c) {
1000 return true;
1001 }
1002 }
1003
1004 false
1005 }
1006}
1007
1008#[derive(Debug, Clone, PartialEq, Eq)]
1010pub struct FontMatch {
1011 pub id: FontId,
1012 pub unicode_ranges: Vec<UnicodeRange>,
1013 pub fallbacks: Vec<FontMatchNoFallback>,
1014}
1015
1016#[derive(Debug, Clone, PartialEq, Eq)]
1018pub struct FontMatchNoFallback {
1019 pub id: FontId,
1020 pub unicode_ranges: Vec<UnicodeRange>,
1021}
1022
1023#[derive(Debug, Clone, PartialEq, Eq)]
1026pub struct ResolvedFontRun {
1027 pub text: String,
1029 pub start_byte: usize,
1031 pub end_byte: usize,
1033 pub font_id: Option<FontId>,
1035 pub css_source: String,
1037}
1038
1039#[derive(Debug, Clone, PartialEq, Eq)]
1042pub struct FontFallbackChain {
1043 pub css_fallbacks: Vec<CssFallbackGroup>,
1046
1047 pub unicode_fallbacks: Vec<FontMatch>,
1050
1051 pub original_stack: Vec<String>,
1053}
1054
1055impl FontFallbackChain {
1056 pub fn resolve_char(&self, cache: &FcFontCache, ch: char) -> Option<(FontId, String)> {
1060 let codepoint = ch as u32;
1061
1062 for group in &self.css_fallbacks {
1064 for font in &group.fonts {
1065 let Some(meta) = cache.get_metadata_by_id(&font.id) else { continue };
1066 if meta.unicode_ranges.is_empty() {
1067 continue; }
1069 if meta.unicode_ranges.iter().any(|r| codepoint >= r.start && codepoint <= r.end) {
1070 return Some((font.id, group.css_name.clone()));
1071 }
1072 }
1073 }
1074
1075 for font in &self.unicode_fallbacks {
1077 let Some(meta) = cache.get_metadata_by_id(&font.id) else { continue };
1078 if meta.unicode_ranges.iter().any(|r| codepoint >= r.start && codepoint <= r.end) {
1079 return Some((font.id, "(unicode-fallback)".to_string()));
1080 }
1081 }
1082
1083 None
1084 }
1085
1086 pub fn resolve_text(&self, cache: &FcFontCache, text: &str) -> Vec<(char, Option<(FontId, String)>)> {
1089 text.chars()
1090 .map(|ch| (ch, self.resolve_char(cache, ch)))
1091 .collect()
1092 }
1093
1094 pub fn query_for_text(&self, cache: &FcFontCache, text: &str) -> Vec<ResolvedFontRun> {
1098 if text.is_empty() {
1099 return Vec::new();
1100 }
1101
1102 let mut runs: Vec<ResolvedFontRun> = Vec::new();
1103 let mut current_font: Option<FontId> = None;
1104 let mut current_css_source: Option<String> = None;
1105 let mut current_start_byte: usize = 0;
1106
1107 for (byte_idx, ch) in text.char_indices() {
1108 let resolved = self.resolve_char(cache, ch);
1109 let (font_id, css_source) = match &resolved {
1110 Some((id, source)) => (Some(*id), Some(source.clone())),
1111 None => (None, None),
1112 };
1113
1114 let font_changed = font_id != current_font;
1116
1117 if font_changed && byte_idx > 0 {
1118 let run_text = &text[current_start_byte..byte_idx];
1120 runs.push(ResolvedFontRun {
1121 text: run_text.to_string(),
1122 start_byte: current_start_byte,
1123 end_byte: byte_idx,
1124 font_id: current_font,
1125 css_source: current_css_source.clone().unwrap_or_default(),
1126 });
1127 current_start_byte = byte_idx;
1128 }
1129
1130 current_font = font_id;
1131 current_css_source = css_source;
1132 }
1133
1134 if current_start_byte < text.len() {
1136 let run_text = &text[current_start_byte..];
1137 runs.push(ResolvedFontRun {
1138 text: run_text.to_string(),
1139 start_byte: current_start_byte,
1140 end_byte: text.len(),
1141 font_id: current_font,
1142 css_source: current_css_source.unwrap_or_default(),
1143 });
1144 }
1145
1146 runs
1147 }
1148}
1149
1150#[derive(Debug, Clone, PartialEq, Eq)]
1152pub struct CssFallbackGroup {
1153 pub css_name: String,
1155
1156 pub fonts: Vec<FontMatch>,
1159}
1160
1161#[cfg(feature = "std")]
1167#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1168pub(crate) struct FontChainCacheKey {
1169 pub(crate) font_families: Vec<String>,
1171 pub(crate) weight: FcWeight,
1173 pub(crate) italic: PatternMatch,
1175 pub(crate) oblique: PatternMatch,
1176}
1177
1178#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq)]
1180#[cfg_attr(feature = "cache", derive(serde::Serialize, serde::Deserialize))]
1181#[repr(C)]
1182pub struct FcFontPath {
1183 pub path: String,
1184 pub font_index: usize,
1185}
1186
1187#[derive(Debug, Clone, PartialEq, Eq)]
1189#[repr(C)]
1190pub struct FcFont {
1191 pub bytes: Vec<u8>,
1192 pub font_index: usize,
1193 pub id: String, }
1195
1196#[derive(Debug, Clone)]
1198pub enum FontSource<'a> {
1199 Memory(&'a FcFont),
1201 Disk(&'a FcFontPath),
1203}
1204
1205#[derive(Debug, Clone)]
1208pub struct NamedFont {
1209 pub name: String,
1211 pub bytes: Vec<u8>,
1213}
1214
1215impl NamedFont {
1216 pub fn new(name: impl Into<String>, bytes: Vec<u8>) -> Self {
1218 Self {
1219 name: name.into(),
1220 bytes,
1221 }
1222 }
1223}
1224
1225#[derive(Debug)]
1227pub struct FcFontCache {
1228 pub(crate) patterns: BTreeMap<FcPattern, FontId>,
1230 pub(crate) disk_fonts: BTreeMap<FontId, FcFontPath>,
1232 pub(crate) memory_fonts: BTreeMap<FontId, FcFont>,
1234 pub(crate) metadata: BTreeMap<FontId, FcPattern>,
1236 pub(crate) token_index: BTreeMap<String, alloc::collections::BTreeSet<FontId>>,
1239 pub(crate) font_tokens: BTreeMap<FontId, Vec<String>>,
1242 #[cfg(feature = "std")]
1244 pub(crate) chain_cache: std::sync::Mutex<std::collections::HashMap<FontChainCacheKey, FontFallbackChain>>,
1245}
1246
1247impl Clone for FcFontCache {
1248 fn clone(&self) -> Self {
1249 Self {
1250 patterns: self.patterns.clone(),
1251 disk_fonts: self.disk_fonts.clone(),
1252 memory_fonts: self.memory_fonts.clone(),
1253 metadata: self.metadata.clone(),
1254 token_index: self.token_index.clone(),
1255 font_tokens: self.font_tokens.clone(),
1256 #[cfg(feature = "std")]
1257 chain_cache: std::sync::Mutex::new(std::collections::HashMap::new()), }
1259 }
1260}
1261
1262impl Default for FcFontCache {
1263 fn default() -> Self {
1264 Self {
1265 patterns: BTreeMap::new(),
1266 disk_fonts: BTreeMap::new(),
1267 memory_fonts: BTreeMap::new(),
1268 metadata: BTreeMap::new(),
1269 token_index: BTreeMap::new(),
1270 font_tokens: BTreeMap::new(),
1271 #[cfg(feature = "std")]
1272 chain_cache: std::sync::Mutex::new(std::collections::HashMap::new()),
1273 }
1274 }
1275}
1276
1277impl FcFontCache {
1278 pub(crate) fn index_pattern_tokens(&mut self, pattern: &FcPattern, id: FontId) {
1280 let mut all_tokens = Vec::new();
1282
1283 if let Some(name) = &pattern.name {
1284 all_tokens.extend(Self::extract_font_name_tokens(name));
1285 }
1286
1287 if let Some(family) = &pattern.family {
1288 all_tokens.extend(Self::extract_font_name_tokens(family));
1289 }
1290
1291 let tokens_lower: Vec<String> = all_tokens.iter().map(|t| t.to_lowercase()).collect();
1293
1294 for token_lower in &tokens_lower {
1296 self.token_index
1297 .entry(token_lower.clone())
1298 .or_insert_with(alloc::collections::BTreeSet::new)
1299 .insert(id);
1300 }
1301
1302 self.font_tokens.insert(id, tokens_lower);
1304 }
1305
1306 pub fn with_memory_fonts(&mut self, fonts: Vec<(FcPattern, FcFont)>) -> &mut Self {
1308 for (pattern, font) in fonts {
1309 let id = FontId::new();
1310 self.patterns.insert(pattern.clone(), id);
1311 self.metadata.insert(id, pattern.clone());
1312 self.memory_fonts.insert(id, font);
1313 self.index_pattern_tokens(&pattern, id);
1314 }
1315 self
1316 }
1317
1318 pub fn with_memory_font_with_id(
1320 &mut self,
1321 id: FontId,
1322 pattern: FcPattern,
1323 font: FcFont,
1324 ) -> &mut Self {
1325 self.patterns.insert(pattern.clone(), id);
1326 self.metadata.insert(id, pattern.clone());
1327 self.memory_fonts.insert(id, font);
1328 self.index_pattern_tokens(&pattern, id);
1329 self
1330 }
1331
1332 pub fn get_font_by_id<'a>(&'a self, id: &FontId) -> Option<FontSource<'a>> {
1334 if let Some(font) = self.memory_fonts.get(id) {
1336 return Some(FontSource::Memory(font));
1337 }
1338 if let Some(path) = self.disk_fonts.get(id) {
1340 return Some(FontSource::Disk(path));
1341 }
1342 None
1343 }
1344
1345 pub fn get_metadata_by_id(&self, id: &FontId) -> Option<&FcPattern> {
1347 self.metadata.get(id)
1348 }
1349
1350 #[cfg(feature = "std")]
1352 pub fn get_font_bytes(&self, id: &FontId) -> Option<Vec<u8>> {
1353 match self.get_font_by_id(id)? {
1354 FontSource::Memory(font) => {
1355 Some(font.bytes.clone())
1356 }
1357 FontSource::Disk(path) => {
1358 std::fs::read(&path.path).ok()
1359 }
1360 }
1361 }
1362
1363 #[cfg(not(all(feature = "std", feature = "parsing")))]
1365 pub fn build() -> Self {
1366 Self::default()
1367 }
1368
1369 #[cfg(all(feature = "std", feature = "parsing"))]
1371 pub fn build() -> Self {
1372 Self::build_inner(None)
1373 }
1374
1375 #[cfg(all(feature = "std", feature = "parsing"))]
1400 pub fn build_with_families(families: &[impl AsRef<str>]) -> Self {
1401 let os = OperatingSystem::current();
1403 let mut target_families: Vec<String> = Vec::new();
1404
1405 for family in families {
1406 let family_str = family.as_ref();
1407 let expanded = os.expand_generic_family(family_str, &[]);
1408 if expanded.is_empty() || (expanded.len() == 1 && expanded[0] == family_str) {
1409 target_families.push(family_str.to_string());
1410 } else {
1411 target_families.extend(expanded);
1412 }
1413 }
1414
1415 Self::build_inner(Some(&target_families))
1416 }
1417
1418 #[cfg(all(feature = "std", feature = "parsing"))]
1424 fn build_inner(family_filter: Option<&[String]>) -> Self {
1425 let mut cache = FcFontCache::default();
1426
1427 let filter_normalized: Option<Vec<String>> = family_filter.map(|families| {
1429 families
1430 .iter()
1431 .map(|f| crate::utils::normalize_family_name(f))
1432 .collect()
1433 });
1434
1435 let matches_filter = |pattern: &FcPattern| -> bool {
1437 match &filter_normalized {
1438 None => true, Some(targets) => {
1440 pattern.name.as_ref().map_or(false, |name| {
1441 let name_norm = crate::utils::normalize_family_name(name);
1442 targets.iter().any(|target| name_norm.contains(target))
1443 }) || pattern.family.as_ref().map_or(false, |family| {
1444 let family_norm = crate::utils::normalize_family_name(family);
1445 targets.iter().any(|target| family_norm.contains(target))
1446 })
1447 }
1448 }
1449 };
1450
1451 #[cfg(target_os = "linux")]
1452 {
1453 if let Some((font_entries, render_configs)) = FcScanDirectories() {
1454 for (mut pattern, path) in font_entries {
1455 if matches_filter(&pattern) {
1456 if let Some(family) = pattern.name.as_ref().or(pattern.family.as_ref()) {
1458 if let Some(rc) = render_configs.get(family) {
1459 pattern.render_config = rc.clone();
1460 }
1461 }
1462 let id = FontId::new();
1463 cache.patterns.insert(pattern.clone(), id);
1464 cache.metadata.insert(id, pattern.clone());
1465 cache.disk_fonts.insert(id, path);
1466 cache.index_pattern_tokens(&pattern, id);
1467 }
1468 }
1469 }
1470 }
1471
1472 #[cfg(target_os = "windows")]
1473 {
1474 let system_root = std::env::var("SystemRoot")
1475 .or_else(|_| std::env::var("WINDIR"))
1476 .unwrap_or_else(|_| "C:\\Windows".to_string());
1477
1478 let user_profile = std::env::var("USERPROFILE")
1479 .unwrap_or_else(|_| "C:\\Users\\Default".to_string());
1480
1481 let font_dirs = vec![
1482 (None, format!("{}\\Fonts\\", system_root)),
1483 (None, format!("{}\\AppData\\Local\\Microsoft\\Windows\\Fonts\\", user_profile)),
1484 ];
1485
1486 let font_entries = FcScanDirectoriesInner(&font_dirs);
1487 for (pattern, path) in font_entries {
1488 if matches_filter(&pattern) {
1489 let id = FontId::new();
1490 cache.patterns.insert(pattern.clone(), id);
1491 cache.metadata.insert(id, pattern.clone());
1492 cache.disk_fonts.insert(id, path);
1493 cache.index_pattern_tokens(&pattern, id);
1494 }
1495 }
1496 }
1497
1498 #[cfg(target_os = "macos")]
1499 {
1500 let font_dirs = vec![
1501 (None, "~/Library/Fonts".to_owned()),
1502 (None, "/System/Library/Fonts".to_owned()),
1503 (None, "/Library/Fonts".to_owned()),
1504 (None, "/System/Library/AssetsV2".to_owned()),
1505 ];
1506
1507 let font_entries = FcScanDirectoriesInner(&font_dirs);
1508 for (pattern, path) in font_entries {
1509 if matches_filter(&pattern) {
1510 let id = FontId::new();
1511 cache.patterns.insert(pattern.clone(), id);
1512 cache.metadata.insert(id, pattern.clone());
1513 cache.disk_fonts.insert(id, path);
1514 cache.index_pattern_tokens(&pattern, id);
1515 }
1516 }
1517 }
1518
1519 cache
1520 }
1521
1522 pub fn is_memory_font(&self, id: &FontId) -> bool {
1524 self.memory_fonts.contains_key(id)
1525 }
1526
1527 pub fn list(&self) -> Vec<(&FcPattern, FontId)> {
1529 self.patterns
1530 .iter()
1531 .map(|(pattern, id)| (pattern, *id))
1532 .collect()
1533 }
1534
1535 pub fn is_empty(&self) -> bool {
1537 self.patterns.is_empty()
1538 }
1539
1540 pub fn len(&self) -> usize {
1542 self.patterns.len()
1543 }
1544
1545 pub fn query(&self, pattern: &FcPattern, trace: &mut Vec<TraceMsg>) -> Option<FontMatch> {
1548 let mut matches = Vec::new();
1549
1550 for (stored_pattern, id) in &self.patterns {
1551 if Self::query_matches_internal(stored_pattern, pattern, trace) {
1552 let metadata = self.metadata.get(id).unwrap_or(stored_pattern);
1553
1554 let unicode_compatibility = if pattern.unicode_ranges.is_empty() {
1556 Self::calculate_unicode_coverage(&metadata.unicode_ranges) as i32
1558 } else {
1559 Self::calculate_unicode_compatibility(&pattern.unicode_ranges, &metadata.unicode_ranges)
1561 };
1562
1563 let style_score = Self::calculate_style_score(pattern, metadata);
1564
1565 let is_memory = self.memory_fonts.contains_key(id);
1567
1568 matches.push((*id, unicode_compatibility, style_score, metadata.clone(), is_memory));
1569 }
1570 }
1571
1572 matches.sort_by(|a, b| {
1574 b.4.cmp(&a.4)
1576 .then_with(|| b.1.cmp(&a.1)) .then_with(|| a.2.cmp(&b.2)) });
1579
1580 matches.first().map(|(id, _, _, metadata, _)| {
1581 FontMatch {
1582 id: *id,
1583 unicode_ranges: metadata.unicode_ranges.clone(),
1584 fallbacks: Vec::new(), }
1586 })
1587 }
1588
1589 fn query_internal(&self, pattern: &FcPattern, trace: &mut Vec<TraceMsg>) -> Vec<FontMatch> {
1594 let mut matches = Vec::new();
1595
1596 for (stored_pattern, id) in &self.patterns {
1597 if Self::query_matches_internal(stored_pattern, pattern, trace) {
1598 let metadata = self.metadata.get(id).unwrap_or(stored_pattern);
1599
1600 let unicode_compatibility = if pattern.unicode_ranges.is_empty() {
1602 Self::calculate_unicode_coverage(&metadata.unicode_ranges) as i32
1603 } else {
1604 Self::calculate_unicode_compatibility(&pattern.unicode_ranges, &metadata.unicode_ranges)
1605 };
1606
1607 let style_score = Self::calculate_style_score(pattern, metadata);
1608 matches.push((*id, unicode_compatibility, style_score, metadata.clone()));
1609 }
1610 }
1611
1612 matches.sort_by(|a, b| {
1616 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)) });
1621
1622 matches
1623 .into_iter()
1624 .map(|(id, _, _, metadata)| {
1625 FontMatch {
1626 id,
1627 unicode_ranges: metadata.unicode_ranges.clone(),
1628 fallbacks: Vec::new(), }
1630 })
1631 .collect()
1632 }
1633
1634 pub fn compute_fallbacks(
1638 &self,
1639 font_id: &FontId,
1640 trace: &mut Vec<TraceMsg>,
1641 ) -> Vec<FontMatchNoFallback> {
1642 let pattern = match self.metadata.get(font_id) {
1644 Some(p) => p,
1645 None => return Vec::new(),
1646 };
1647
1648 self.compute_fallbacks_for_pattern(pattern, Some(font_id), trace)
1649 }
1650
1651 fn compute_fallbacks_for_pattern(
1652 &self,
1653 pattern: &FcPattern,
1654 exclude_id: Option<&FontId>,
1655 _trace: &mut Vec<TraceMsg>,
1656 ) -> Vec<FontMatchNoFallback> {
1657 let mut candidates = Vec::new();
1658
1659 for (stored_pattern, id) in &self.patterns {
1661 if exclude_id.is_some() && exclude_id.unwrap() == id {
1663 continue;
1664 }
1665
1666 if !stored_pattern.unicode_ranges.is_empty() && !pattern.unicode_ranges.is_empty() {
1668 let unicode_compatibility = Self::calculate_unicode_compatibility(
1670 &pattern.unicode_ranges,
1671 &stored_pattern.unicode_ranges
1672 );
1673
1674 if unicode_compatibility > 0 {
1676 let style_score = Self::calculate_style_score(pattern, stored_pattern);
1677 candidates.push((
1678 FontMatchNoFallback {
1679 id: *id,
1680 unicode_ranges: stored_pattern.unicode_ranges.clone(),
1681 },
1682 unicode_compatibility,
1683 style_score,
1684 stored_pattern.clone(),
1685 ));
1686 }
1687 } else if pattern.unicode_ranges.is_empty() && !stored_pattern.unicode_ranges.is_empty() {
1688 let coverage = Self::calculate_unicode_coverage(&stored_pattern.unicode_ranges) as i32;
1690 let style_score = Self::calculate_style_score(pattern, stored_pattern);
1691 candidates.push((
1692 FontMatchNoFallback {
1693 id: *id,
1694 unicode_ranges: stored_pattern.unicode_ranges.clone(),
1695 },
1696 coverage,
1697 style_score,
1698 stored_pattern.clone(),
1699 ));
1700 }
1701 }
1702
1703 candidates.sort_by(|a, b| {
1705 b.1.cmp(&a.1)
1706 .then_with(|| a.2.cmp(&b.2))
1707 });
1708
1709 let mut seen_ranges = Vec::new();
1711 let mut deduplicated = Vec::new();
1712
1713 for (id, _, _, pattern) in candidates {
1714 let mut is_new_range = false;
1715
1716 for range in &pattern.unicode_ranges {
1717 if !seen_ranges.iter().any(|r: &UnicodeRange| r.overlaps(range)) {
1718 seen_ranges.push(*range);
1719 is_new_range = true;
1720 }
1721 }
1722
1723 if is_new_range {
1724 deduplicated.push(id);
1725 }
1726 }
1727
1728 deduplicated
1729 }
1730
1731 pub fn get_memory_font(&self, id: &FontId) -> Option<&FcFont> {
1733 self.memory_fonts.get(id)
1734 }
1735
1736 fn trace_path(k: &FcPattern) -> String {
1738 k.name.as_ref().cloned().unwrap_or_else(|| "<unknown>".to_string())
1739 }
1740
1741 pub fn query_matches_internal(
1742 k: &FcPattern,
1743 pattern: &FcPattern,
1744 trace: &mut Vec<TraceMsg>,
1745 ) -> bool {
1746 if let Some(ref name) = pattern.name {
1748 if !k.name.as_ref().map_or(false, |kn| kn.contains(name)) {
1749 trace.push(TraceMsg {
1750 level: TraceLevel::Info,
1751 path: Self::trace_path(k),
1752 reason: MatchReason::NameMismatch {
1753 requested: pattern.name.clone(),
1754 found: k.name.clone(),
1755 },
1756 });
1757 return false;
1758 }
1759 }
1760
1761 if let Some(ref family) = pattern.family {
1763 if !k.family.as_ref().map_or(false, |kf| kf.contains(family)) {
1764 trace.push(TraceMsg {
1765 level: TraceLevel::Info,
1766 path: Self::trace_path(k),
1767 reason: MatchReason::FamilyMismatch {
1768 requested: pattern.family.clone(),
1769 found: k.family.clone(),
1770 },
1771 });
1772 return false;
1773 }
1774 }
1775
1776 let style_properties = [
1778 (
1779 "italic",
1780 pattern.italic.needs_to_match(),
1781 pattern.italic.matches(&k.italic),
1782 ),
1783 (
1784 "oblique",
1785 pattern.oblique.needs_to_match(),
1786 pattern.oblique.matches(&k.oblique),
1787 ),
1788 (
1789 "bold",
1790 pattern.bold.needs_to_match(),
1791 pattern.bold.matches(&k.bold),
1792 ),
1793 (
1794 "monospace",
1795 pattern.monospace.needs_to_match(),
1796 pattern.monospace.matches(&k.monospace),
1797 ),
1798 (
1799 "condensed",
1800 pattern.condensed.needs_to_match(),
1801 pattern.condensed.matches(&k.condensed),
1802 ),
1803 ];
1804
1805 for (property_name, needs_to_match, matches) in style_properties {
1806 if needs_to_match && !matches {
1807 let (requested, found) = match property_name {
1808 "italic" => (format!("{:?}", pattern.italic), format!("{:?}", k.italic)),
1809 "oblique" => (format!("{:?}", pattern.oblique), format!("{:?}", k.oblique)),
1810 "bold" => (format!("{:?}", pattern.bold), format!("{:?}", k.bold)),
1811 "monospace" => (
1812 format!("{:?}", pattern.monospace),
1813 format!("{:?}", k.monospace),
1814 ),
1815 "condensed" => (
1816 format!("{:?}", pattern.condensed),
1817 format!("{:?}", k.condensed),
1818 ),
1819 _ => (String::new(), String::new()),
1820 };
1821
1822 trace.push(TraceMsg {
1823 level: TraceLevel::Info,
1824 path: Self::trace_path(k),
1825 reason: MatchReason::StyleMismatch {
1826 property: property_name,
1827 requested,
1828 found,
1829 },
1830 });
1831 return false;
1832 }
1833 }
1834
1835 if pattern.weight != FcWeight::Normal && pattern.weight != k.weight {
1837 trace.push(TraceMsg {
1838 level: TraceLevel::Info,
1839 path: Self::trace_path(k),
1840 reason: MatchReason::WeightMismatch {
1841 requested: pattern.weight,
1842 found: k.weight,
1843 },
1844 });
1845 return false;
1846 }
1847
1848 if pattern.stretch != FcStretch::Normal && pattern.stretch != k.stretch {
1850 trace.push(TraceMsg {
1851 level: TraceLevel::Info,
1852 path: Self::trace_path(k),
1853 reason: MatchReason::StretchMismatch {
1854 requested: pattern.stretch,
1855 found: k.stretch,
1856 },
1857 });
1858 return false;
1859 }
1860
1861 if !pattern.unicode_ranges.is_empty() {
1863 let mut has_overlap = false;
1864
1865 for p_range in &pattern.unicode_ranges {
1866 for k_range in &k.unicode_ranges {
1867 if p_range.overlaps(k_range) {
1868 has_overlap = true;
1869 break;
1870 }
1871 }
1872 if has_overlap {
1873 break;
1874 }
1875 }
1876
1877 if !has_overlap {
1878 trace.push(TraceMsg {
1879 level: TraceLevel::Info,
1880 path: Self::trace_path(k),
1881 reason: MatchReason::UnicodeRangeMismatch {
1882 character: '\0', ranges: k.unicode_ranges.clone(),
1884 },
1885 });
1886 return false;
1887 }
1888 }
1889
1890 true
1891 }
1892
1893 #[cfg(feature = "std")]
1919 pub fn resolve_font_chain(
1920 &self,
1921 font_families: &[String],
1922 weight: FcWeight,
1923 italic: PatternMatch,
1924 oblique: PatternMatch,
1925 trace: &mut Vec<TraceMsg>,
1926 ) -> FontFallbackChain {
1927 self.resolve_font_chain_with_os(font_families, weight, italic, oblique, trace, OperatingSystem::current())
1928 }
1929
1930 #[cfg(feature = "std")]
1932 pub fn resolve_font_chain_with_os(
1933 &self,
1934 font_families: &[String],
1935 weight: FcWeight,
1936 italic: PatternMatch,
1937 oblique: PatternMatch,
1938 trace: &mut Vec<TraceMsg>,
1939 os: OperatingSystem,
1940 ) -> FontFallbackChain {
1941 let cache_key = FontChainCacheKey {
1944 font_families: font_families.to_vec(), weight,
1946 italic,
1947 oblique,
1948 };
1949
1950 if let Some(cached) = self.chain_cache.lock().ok().and_then(|c| c.get(&cache_key).cloned()) {
1951 return cached;
1952 }
1953
1954 let expanded_families = expand_font_families(font_families, os, &[]);
1956
1957 let chain = self.resolve_font_chain_uncached(
1959 &expanded_families,
1960 weight,
1961 italic,
1962 oblique,
1963 trace,
1964 );
1965
1966 if let Ok(mut cache) = self.chain_cache.lock() {
1968 cache.insert(cache_key, chain.clone());
1969 }
1970
1971 chain
1972 }
1973
1974 #[cfg(feature = "std")]
1980 fn resolve_font_chain_uncached(
1981 &self,
1982 font_families: &[String],
1983 weight: FcWeight,
1984 italic: PatternMatch,
1985 oblique: PatternMatch,
1986 trace: &mut Vec<TraceMsg>,
1987 ) -> FontFallbackChain {
1988 let mut css_fallbacks = Vec::new();
1989
1990 for (_i, family) in font_families.iter().enumerate() {
1992 let (pattern, is_generic) = if config::is_generic_family(family) {
1994 let monospace = if family.eq_ignore_ascii_case("monospace") {
1995 PatternMatch::True
1996 } else {
1997 PatternMatch::False
1998 };
1999 let pattern = FcPattern {
2000 name: None,
2001 weight,
2002 italic,
2003 oblique,
2004 monospace,
2005 unicode_ranges: Vec::new(),
2006 ..Default::default()
2007 };
2008 (pattern, true)
2009 } else {
2010 let pattern = FcPattern {
2012 name: Some(family.clone()),
2013 weight,
2014 italic,
2015 oblique,
2016 unicode_ranges: Vec::new(),
2017 ..Default::default()
2018 };
2019 (pattern, false)
2020 };
2021
2022 let mut matches = if is_generic {
2025 self.query_internal(&pattern, trace)
2027 } else {
2028 self.fuzzy_query_by_name(family, weight, italic, oblique, &[], trace)
2030 };
2031
2032 if is_generic && matches.len() > 5 {
2034 matches.truncate(5);
2035 }
2036
2037 css_fallbacks.push(CssFallbackGroup {
2040 css_name: family.clone(),
2041 fonts: matches,
2042 });
2043 }
2044
2045 let important_ranges = [
2050 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 }, ];
2058 let all_uncovered = vec![false; important_ranges.len()];
2059 let unicode_fallbacks = self.find_unicode_fallbacks(
2060 &important_ranges,
2061 &all_uncovered,
2062 &css_fallbacks,
2063 weight,
2064 italic,
2065 oblique,
2066 trace,
2067 );
2068
2069 FontFallbackChain {
2070 css_fallbacks,
2071 unicode_fallbacks,
2072 original_stack: font_families.to_vec(),
2073 }
2074 }
2075
2076 #[allow(dead_code)]
2078 fn extract_unicode_ranges(text: &str) -> Vec<UnicodeRange> {
2079 let mut chars: Vec<char> = text.chars().collect();
2080 chars.sort_unstable();
2081 chars.dedup();
2082
2083 if chars.is_empty() {
2084 return Vec::new();
2085 }
2086
2087 let mut ranges = Vec::new();
2088 let mut range_start = chars[0] as u32;
2089 let mut range_end = range_start;
2090
2091 for &c in &chars[1..] {
2092 let codepoint = c as u32;
2093 if codepoint == range_end + 1 {
2094 range_end = codepoint;
2095 } else {
2096 ranges.push(UnicodeRange { start: range_start, end: range_end });
2097 range_start = codepoint;
2098 range_end = codepoint;
2099 }
2100 }
2101
2102 ranges.push(UnicodeRange { start: range_start, end: range_end });
2103 ranges
2104 }
2105
2106 #[cfg(feature = "std")]
2113 fn fuzzy_query_by_name(
2114 &self,
2115 requested_name: &str,
2116 weight: FcWeight,
2117 italic: PatternMatch,
2118 oblique: PatternMatch,
2119 unicode_ranges: &[UnicodeRange],
2120 _trace: &mut Vec<TraceMsg>,
2121 ) -> Vec<FontMatch> {
2122 let tokens = Self::extract_font_name_tokens(requested_name);
2124
2125 if tokens.is_empty() {
2126 return Vec::new();
2127 }
2128
2129 let tokens_lower: Vec<String> = tokens.iter().map(|t| t.to_lowercase()).collect();
2131
2132 let first_token = &tokens_lower[0];
2139 let mut candidate_ids = match self.token_index.get(first_token) {
2140 Some(ids) if !ids.is_empty() => ids.clone(),
2141 _ => {
2142 return Vec::new();
2144 }
2145 };
2146
2147 for token in &tokens_lower[1..] {
2149 if let Some(token_ids) = self.token_index.get(token) {
2150 let intersection: alloc::collections::BTreeSet<FontId> =
2152 candidate_ids.intersection(token_ids).copied().collect();
2153
2154 if intersection.is_empty() {
2155 break;
2157 } else {
2158 candidate_ids = intersection;
2160 }
2161 } else {
2162 break;
2164 }
2165 }
2166
2167 let mut candidates = Vec::new();
2169
2170 for id in candidate_ids {
2171 let pattern = match self.metadata.get(&id) {
2172 Some(p) => p,
2173 None => continue,
2174 };
2175
2176 let font_tokens_lower = match self.font_tokens.get(&id) {
2178 Some(tokens) => tokens,
2179 None => continue,
2180 };
2181
2182 if font_tokens_lower.is_empty() {
2183 continue;
2184 }
2185
2186 let token_matches = tokens_lower.iter()
2189 .filter(|req_token| {
2190 font_tokens_lower.iter().any(|font_token| {
2191 font_token == *req_token
2193 })
2194 })
2195 .count();
2196
2197 if token_matches == 0 {
2199 continue;
2200 }
2201
2202 let token_similarity = (token_matches * 100 / tokens.len()) as i32;
2204
2205 let unicode_similarity = if !unicode_ranges.is_empty() && !pattern.unicode_ranges.is_empty() {
2207 Self::calculate_unicode_compatibility(unicode_ranges, &pattern.unicode_ranges)
2208 } else {
2209 0
2210 };
2211
2212 if !unicode_ranges.is_empty() && unicode_similarity == 0 {
2215 continue;
2216 }
2217
2218 let style_score = Self::calculate_style_score(&FcPattern {
2219 weight,
2220 italic,
2221 oblique,
2222 ..Default::default()
2223 }, pattern);
2224
2225 candidates.push((
2226 id,
2227 token_similarity,
2228 unicode_similarity,
2229 style_score,
2230 pattern.clone(),
2231 ));
2232 }
2233
2234 candidates.sort_by(|a, b| {
2240 if !unicode_ranges.is_empty() {
2241 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 {
2248 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)) }
2254 });
2255
2256 candidates.truncate(5);
2258
2259 candidates
2261 .into_iter()
2262 .map(|(id, _token_sim, _unicode_sim, _style, pattern)| {
2263 FontMatch {
2264 id,
2265 unicode_ranges: pattern.unicode_ranges.clone(),
2266 fallbacks: Vec::new(), }
2268 })
2269 .collect()
2270 }
2271
2272 pub fn extract_font_name_tokens(name: &str) -> Vec<String> {
2276 let mut tokens = Vec::new();
2277 let mut current_token = String::new();
2278 let mut last_was_lower = false;
2279
2280 for c in name.chars() {
2281 if c.is_whitespace() || c == '-' || c == '_' {
2282 if !current_token.is_empty() {
2284 tokens.push(current_token.clone());
2285 current_token.clear();
2286 }
2287 last_was_lower = false;
2288 } else if c.is_uppercase() && last_was_lower && !current_token.is_empty() {
2289 tokens.push(current_token.clone());
2291 current_token.clear();
2292 current_token.push(c);
2293 last_was_lower = false;
2294 } else {
2295 current_token.push(c);
2296 last_was_lower = c.is_lowercase();
2297 }
2298 }
2299
2300 if !current_token.is_empty() {
2301 tokens.push(current_token);
2302 }
2303
2304 tokens
2305 }
2306
2307 fn find_unicode_fallbacks(
2311 &self,
2312 unicode_ranges: &[UnicodeRange],
2313 covered_chars: &[bool],
2314 existing_groups: &[CssFallbackGroup],
2315 _weight: FcWeight,
2316 _italic: PatternMatch,
2317 _oblique: PatternMatch,
2318 trace: &mut Vec<TraceMsg>,
2319 ) -> Vec<FontMatch> {
2320 let mut uncovered_ranges = Vec::new();
2322 for (i, &covered) in covered_chars.iter().enumerate() {
2323 if !covered && i < unicode_ranges.len() {
2324 uncovered_ranges.push(unicode_ranges[i].clone());
2325 }
2326 }
2327
2328 if uncovered_ranges.is_empty() {
2329 return Vec::new();
2330 }
2331
2332 let pattern = FcPattern {
2337 name: None,
2338 weight: FcWeight::Normal, italic: PatternMatch::DontCare,
2340 oblique: PatternMatch::DontCare,
2341 unicode_ranges: uncovered_ranges.clone(),
2342 ..Default::default()
2343 };
2344
2345 let mut candidates = self.query_internal(&pattern, trace);
2346
2347 let existing_prefixes: Vec<String> = existing_groups
2350 .iter()
2351 .flat_map(|group| {
2352 group.fonts.iter().filter_map(|font| {
2353 self.get_metadata_by_id(&font.id)
2354 .and_then(|meta| meta.family.clone())
2355 .and_then(|family| {
2356 family.split_whitespace()
2358 .take(2)
2359 .collect::<Vec<_>>()
2360 .join(" ")
2361 .into()
2362 })
2363 })
2364 })
2365 .collect();
2366
2367 candidates.sort_by(|a, b| {
2371 let a_meta = self.get_metadata_by_id(&a.id);
2372 let b_meta = self.get_metadata_by_id(&b.id);
2373
2374 let a_score = Self::calculate_font_similarity_score(a_meta, &existing_prefixes);
2375 let b_score = Self::calculate_font_similarity_score(b_meta, &existing_prefixes);
2376
2377 b_score.cmp(&a_score) .then_with(|| {
2379 let a_coverage = Self::calculate_unicode_compatibility(&uncovered_ranges, &a.unicode_ranges);
2380 let b_coverage = Self::calculate_unicode_compatibility(&uncovered_ranges, &b.unicode_ranges);
2381 b_coverage.cmp(&a_coverage)
2382 })
2383 });
2384
2385 let mut result = Vec::new();
2387 let mut remaining_uncovered: Vec<bool> = vec![true; uncovered_ranges.len()];
2388
2389 for candidate in candidates {
2390 let mut covers_new_range = false;
2392
2393 for (i, range) in uncovered_ranges.iter().enumerate() {
2394 if remaining_uncovered[i] {
2395 for font_range in &candidate.unicode_ranges {
2397 if font_range.overlaps(range) {
2398 remaining_uncovered[i] = false;
2399 covers_new_range = true;
2400 break;
2401 }
2402 }
2403 }
2404 }
2405
2406 if covers_new_range {
2408 result.push(candidate);
2409
2410 if remaining_uncovered.iter().all(|&uncovered| !uncovered) {
2412 break;
2413 }
2414 }
2415 }
2416
2417 result
2418 }
2419
2420 fn calculate_font_similarity_score(
2423 font_meta: Option<&FcPattern>,
2424 existing_prefixes: &[String],
2425 ) -> i32 {
2426 let Some(meta) = font_meta else { return 0; };
2427 let Some(family) = &meta.family else { return 0; };
2428
2429 for prefix in existing_prefixes {
2431 if family.starts_with(prefix) {
2432 return 100; }
2434 if family.contains(prefix) {
2435 return 50; }
2437 }
2438
2439 0 }
2441
2442 pub fn calculate_unicode_coverage(ranges: &[UnicodeRange]) -> u64 {
2445 ranges
2446 .iter()
2447 .map(|range| (range.end - range.start + 1) as u64)
2448 .sum()
2449 }
2450
2451 pub fn calculate_unicode_compatibility(
2454 requested: &[UnicodeRange],
2455 available: &[UnicodeRange],
2456 ) -> i32 {
2457 if requested.is_empty() {
2458 return Self::calculate_unicode_coverage(available) as i32;
2460 }
2461
2462 let mut total_coverage = 0u32;
2463
2464 for req_range in requested {
2465 for avail_range in available {
2466 let overlap_start = req_range.start.max(avail_range.start);
2468 let overlap_end = req_range.end.min(avail_range.end);
2469
2470 if overlap_start <= overlap_end {
2471 let overlap_size = overlap_end - overlap_start + 1;
2473 total_coverage += overlap_size;
2474 }
2475 }
2476 }
2477
2478 total_coverage as i32
2479 }
2480
2481 pub fn calculate_style_score(original: &FcPattern, candidate: &FcPattern) -> i32 {
2482
2483 let mut score = 0_i32;
2484
2485 if (original.bold == PatternMatch::True && candidate.weight == FcWeight::Bold)
2487 || (original.bold == PatternMatch::False && candidate.weight != FcWeight::Bold)
2488 {
2489 } else {
2492 let weight_diff = (original.weight as i32 - candidate.weight as i32).abs();
2494 score += weight_diff as i32;
2495 }
2496
2497 if original.weight == candidate.weight {
2500 score -= 15;
2501 if original.weight == FcWeight::Normal {
2502 score -= 10; }
2504 }
2505
2506 if (original.condensed == PatternMatch::True && candidate.stretch.is_condensed())
2508 || (original.condensed == PatternMatch::False && !candidate.stretch.is_condensed())
2509 {
2510 } else {
2513 let stretch_diff = (original.stretch as i32 - candidate.stretch as i32).abs();
2515 score += (stretch_diff * 100) as i32;
2516 }
2517
2518 let style_props = [
2520 (original.italic, candidate.italic, 300, 150),
2521 (original.oblique, candidate.oblique, 200, 100),
2522 (original.bold, candidate.bold, 300, 150),
2523 (original.monospace, candidate.monospace, 100, 50),
2524 (original.condensed, candidate.condensed, 100, 50),
2525 ];
2526
2527 for (orig, cand, mismatch_penalty, dontcare_penalty) in style_props {
2528 if orig.needs_to_match() {
2529 if orig == PatternMatch::False && cand == PatternMatch::DontCare {
2530 score += dontcare_penalty / 2;
2533 } else if !orig.matches(&cand) {
2534 if cand == PatternMatch::DontCare {
2535 score += dontcare_penalty;
2536 } else {
2537 score += mismatch_penalty;
2538 }
2539 } else if orig == PatternMatch::True && cand == PatternMatch::True {
2540 score -= 20;
2542 } else if orig == PatternMatch::False && cand == PatternMatch::False {
2543 score -= 20;
2546 }
2547 } else {
2548 if cand == PatternMatch::True {
2553 score += dontcare_penalty / 3;
2554 }
2555 }
2556 }
2557
2558 if let (Some(name), Some(family)) = (&candidate.name, &candidate.family) {
2564 let name_lower = name.to_lowercase();
2565 let family_lower = family.to_lowercase();
2566
2567 let extra = if name_lower.starts_with(&family_lower) {
2569 name_lower[family_lower.len()..].to_string()
2570 } else {
2571 String::new()
2572 };
2573
2574 let stripped = extra
2576 .replace("regular", "")
2577 .replace("normal", "")
2578 .replace("book", "")
2579 .replace("roman", "");
2580 let stripped = stripped.trim();
2581
2582 if stripped.is_empty() {
2583 score -= 50;
2585 } else {
2586 let extra_words = stripped.split_whitespace().count();
2588 score += (extra_words as i32) * 25;
2589 }
2590 }
2591
2592 if let Some(ref subfamily) = candidate.metadata.font_subfamily {
2596 let sf_lower = subfamily.to_lowercase();
2597 if sf_lower == "regular" {
2598 score -= 30;
2599 }
2600 }
2601
2602 score
2603 }
2604}
2605
2606#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
2607fn FcScanDirectories() -> Option<(Vec<(FcPattern, FcFontPath)>, BTreeMap<String, FcFontRenderConfig>)> {
2608 use std::fs;
2609 use std::path::Path;
2610
2611 const BASE_FONTCONFIG_PATH: &str = "/etc/fonts/fonts.conf";
2612
2613 if !Path::new(BASE_FONTCONFIG_PATH).exists() {
2614 return None;
2615 }
2616
2617 let mut font_paths = Vec::with_capacity(32);
2618 let mut paths_to_visit = vec![(None, PathBuf::from(BASE_FONTCONFIG_PATH))];
2619 let mut render_configs: BTreeMap<String, FcFontRenderConfig> = BTreeMap::new();
2620
2621 while let Some((prefix, path_to_visit)) = paths_to_visit.pop() {
2622 let path = match process_path(&prefix, path_to_visit, true) {
2623 Some(path) => path,
2624 None => continue,
2625 };
2626
2627 let metadata = match fs::metadata(&path) {
2628 Ok(metadata) => metadata,
2629 Err(_) => continue,
2630 };
2631
2632 if metadata.is_file() {
2633 let xml_utf8 = match fs::read_to_string(&path) {
2634 Ok(xml_utf8) => xml_utf8,
2635 Err(_) => continue,
2636 };
2637
2638 if ParseFontsConf(&xml_utf8, &mut paths_to_visit, &mut font_paths).is_none() {
2639 continue;
2640 }
2641
2642 ParseFontsConfRenderConfig(&xml_utf8, &mut render_configs);
2644 } else if metadata.is_dir() {
2645 let dir_entries = match fs::read_dir(&path) {
2646 Ok(dir_entries) => dir_entries,
2647 Err(_) => continue,
2648 };
2649
2650 for entry_result in dir_entries {
2651 let entry = match entry_result {
2652 Ok(entry) => entry,
2653 Err(_) => continue,
2654 };
2655
2656 let entry_path = entry.path();
2657
2658 let entry_metadata = match fs::metadata(&entry_path) {
2660 Ok(metadata) => metadata,
2661 Err(_) => continue,
2662 };
2663
2664 if !entry_metadata.is_file() {
2665 continue;
2666 }
2667
2668 let file_name = match entry_path.file_name() {
2669 Some(name) => name,
2670 None => continue,
2671 };
2672
2673 let file_name_str = file_name.to_string_lossy();
2674 if file_name_str.starts_with(|c: char| c.is_ascii_digit())
2675 && file_name_str.ends_with(".conf")
2676 {
2677 paths_to_visit.push((None, entry_path));
2678 }
2679 }
2680 }
2681 }
2682
2683 if font_paths.is_empty() {
2684 return None;
2685 }
2686
2687 Some((FcScanDirectoriesInner(&font_paths), render_configs))
2688}
2689
2690#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
2692fn ParseFontsConf(
2693 input: &str,
2694 paths_to_visit: &mut Vec<(Option<String>, PathBuf)>,
2695 font_paths: &mut Vec<(Option<String>, String)>,
2696) -> Option<()> {
2697 use xmlparser::Token::*;
2698 use xmlparser::Tokenizer;
2699
2700 const TAG_INCLUDE: &str = "include";
2701 const TAG_DIR: &str = "dir";
2702 const ATTRIBUTE_PREFIX: &str = "prefix";
2703
2704 let mut current_prefix: Option<&str> = None;
2705 let mut current_path: Option<&str> = None;
2706 let mut is_in_include = false;
2707 let mut is_in_dir = false;
2708
2709 for token_result in Tokenizer::from(input) {
2710 let token = match token_result {
2711 Ok(token) => token,
2712 Err(_) => return None,
2713 };
2714
2715 match token {
2716 ElementStart { local, .. } => {
2717 if is_in_include || is_in_dir {
2718 return None; }
2720
2721 match local.as_str() {
2722 TAG_INCLUDE => {
2723 is_in_include = true;
2724 }
2725 TAG_DIR => {
2726 is_in_dir = true;
2727 }
2728 _ => continue,
2729 }
2730
2731 current_path = None;
2732 }
2733 Text { text, .. } => {
2734 let text = text.as_str().trim();
2735 if text.is_empty() {
2736 continue;
2737 }
2738 if is_in_include || is_in_dir {
2739 current_path = Some(text);
2740 }
2741 }
2742 Attribute { local, value, .. } => {
2743 if !is_in_include && !is_in_dir {
2744 continue;
2745 }
2746 if local.as_str() == ATTRIBUTE_PREFIX {
2748 current_prefix = Some(value.as_str());
2749 }
2750 }
2751 ElementEnd { end, .. } => {
2752 let end_tag = match end {
2753 xmlparser::ElementEnd::Close(_, a) => a,
2754 _ => continue,
2755 };
2756
2757 match end_tag.as_str() {
2758 TAG_INCLUDE => {
2759 if !is_in_include {
2760 continue;
2761 }
2762
2763 if let Some(current_path) = current_path.as_ref() {
2764 paths_to_visit.push((
2765 current_prefix.map(ToOwned::to_owned),
2766 PathBuf::from(*current_path),
2767 ));
2768 }
2769 }
2770 TAG_DIR => {
2771 if !is_in_dir {
2772 continue;
2773 }
2774
2775 if let Some(current_path) = current_path.as_ref() {
2776 font_paths.push((
2777 current_prefix.map(ToOwned::to_owned),
2778 (*current_path).to_owned(),
2779 ));
2780 }
2781 }
2782 _ => continue,
2783 }
2784
2785 is_in_include = false;
2786 is_in_dir = false;
2787 current_path = None;
2788 current_prefix = None;
2789 }
2790 _ => {}
2791 }
2792 }
2793
2794 Some(())
2795}
2796
2797#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
2809fn ParseFontsConfRenderConfig(
2810 input: &str,
2811 configs: &mut BTreeMap<String, FcFontRenderConfig>,
2812) {
2813 use xmlparser::Token::*;
2814 use xmlparser::Tokenizer;
2815
2816 #[derive(Clone, Copy, PartialEq)]
2818 enum State {
2819 Idle,
2821 InMatchFont,
2823 InTestFamily,
2825 InEdit,
2827 InValue,
2829 }
2830
2831 let mut state = State::Idle;
2832 let mut match_is_font_target = false;
2833 let mut current_family: Option<String> = None;
2834 let mut current_edit_name: Option<String> = None;
2835 let mut current_value: Option<String> = None;
2836 let mut value_tag: Option<String> = None;
2837 let mut config = FcFontRenderConfig::default();
2838 let mut in_test = false;
2839 let mut test_name: Option<String> = None;
2840
2841 for token_result in Tokenizer::from(input) {
2842 let token = match token_result {
2843 Ok(token) => token,
2844 Err(_) => continue,
2845 };
2846
2847 match token {
2848 ElementStart { local, .. } => {
2849 let tag = local.as_str();
2850 match tag {
2851 "match" => {
2852 match_is_font_target = false;
2854 current_family = None;
2855 config = FcFontRenderConfig::default();
2856 }
2857 "test" if state == State::InMatchFont => {
2858 in_test = true;
2859 test_name = None;
2860 }
2861 "edit" if state == State::InMatchFont => {
2862 current_edit_name = None;
2863 }
2864 "bool" | "double" | "const" | "string" | "int" => {
2865 if state == State::InTestFamily || state == State::InEdit {
2866 value_tag = Some(tag.to_owned());
2867 current_value = None;
2868 }
2869 }
2870 _ => {}
2871 }
2872 }
2873 Attribute { local, value, .. } => {
2874 let attr_name = local.as_str();
2875 let attr_value = value.as_str();
2876
2877 match attr_name {
2878 "target" => {
2879 if attr_value == "font" {
2880 match_is_font_target = true;
2881 }
2882 }
2883 "name" => {
2884 if in_test && state == State::InMatchFont {
2885 test_name = Some(attr_value.to_owned());
2886 } else if state == State::InMatchFont {
2887 current_edit_name = Some(attr_value.to_owned());
2888 }
2889 }
2890 _ => {}
2891 }
2892 }
2893 Text { text, .. } => {
2894 let text = text.as_str().trim();
2895 if !text.is_empty() && (state == State::InTestFamily || state == State::InEdit) {
2896 current_value = Some(text.to_owned());
2897 }
2898 }
2899 ElementEnd { end, .. } => {
2900 match end {
2901 xmlparser::ElementEnd::Open => {
2902 if match_is_font_target && state == State::Idle {
2904 state = State::InMatchFont;
2905 match_is_font_target = false;
2906 } else if in_test {
2907 if test_name.as_deref() == Some("family") {
2908 state = State::InTestFamily;
2909 }
2910 in_test = false;
2911 } else if current_edit_name.is_some() && state == State::InMatchFont {
2912 state = State::InEdit;
2913 }
2914 }
2915 xmlparser::ElementEnd::Close(_, local) => {
2916 let tag = local.as_str();
2917 match tag {
2918 "match" => {
2919 if let Some(family) = current_family.take() {
2921 let empty = FcFontRenderConfig::default();
2922 if config != empty {
2923 configs.insert(family, config.clone());
2924 }
2925 }
2926 state = State::Idle;
2927 config = FcFontRenderConfig::default();
2928 }
2929 "test" => {
2930 if state == State::InTestFamily {
2931 if let Some(ref val) = current_value {
2933 current_family = Some(val.clone());
2934 }
2935 state = State::InMatchFont;
2936 }
2937 current_value = None;
2938 value_tag = None;
2939 }
2940 "edit" => {
2941 if state == State::InEdit {
2942 if let (Some(ref name), Some(ref val)) = (¤t_edit_name, ¤t_value) {
2944 apply_edit_value(&mut config, name, val, value_tag.as_deref());
2945 }
2946 state = State::InMatchFont;
2947 }
2948 current_edit_name = None;
2949 current_value = None;
2950 value_tag = None;
2951 }
2952 "bool" | "double" | "const" | "string" | "int" => {
2953 }
2955 _ => {}
2956 }
2957 }
2958 xmlparser::ElementEnd::Empty => {
2959 }
2961 }
2962 }
2963 _ => {}
2964 }
2965 }
2966}
2967
2968#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
2970fn apply_edit_value(
2971 config: &mut FcFontRenderConfig,
2972 edit_name: &str,
2973 value: &str,
2974 value_tag: Option<&str>,
2975) {
2976 match edit_name {
2977 "antialias" => {
2978 config.antialias = parse_bool_value(value);
2979 }
2980 "hinting" => {
2981 config.hinting = parse_bool_value(value);
2982 }
2983 "autohint" => {
2984 config.autohint = parse_bool_value(value);
2985 }
2986 "embeddedbitmap" => {
2987 config.embeddedbitmap = parse_bool_value(value);
2988 }
2989 "embolden" => {
2990 config.embolden = parse_bool_value(value);
2991 }
2992 "minspace" => {
2993 config.minspace = parse_bool_value(value);
2994 }
2995 "hintstyle" => {
2996 config.hintstyle = parse_hintstyle_const(value);
2997 }
2998 "rgba" => {
2999 config.rgba = parse_rgba_const(value);
3000 }
3001 "lcdfilter" => {
3002 config.lcdfilter = parse_lcdfilter_const(value);
3003 }
3004 "dpi" => {
3005 if let Ok(v) = value.parse::<f64>() {
3006 config.dpi = Some(v);
3007 }
3008 }
3009 "scale" => {
3010 if let Ok(v) = value.parse::<f64>() {
3011 config.scale = Some(v);
3012 }
3013 }
3014 _ => {
3015 }
3017 }
3018}
3019
3020#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3021fn parse_bool_value(value: &str) -> Option<bool> {
3022 match value {
3023 "true" => Some(true),
3024 "false" => Some(false),
3025 _ => None,
3026 }
3027}
3028
3029#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3030fn parse_hintstyle_const(value: &str) -> Option<FcHintStyle> {
3031 match value {
3032 "hintnone" => Some(FcHintStyle::None),
3033 "hintslight" => Some(FcHintStyle::Slight),
3034 "hintmedium" => Some(FcHintStyle::Medium),
3035 "hintfull" => Some(FcHintStyle::Full),
3036 _ => None,
3037 }
3038}
3039
3040#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3041fn parse_rgba_const(value: &str) -> Option<FcRgba> {
3042 match value {
3043 "unknown" => Some(FcRgba::Unknown),
3044 "rgb" => Some(FcRgba::Rgb),
3045 "bgr" => Some(FcRgba::Bgr),
3046 "vrgb" => Some(FcRgba::Vrgb),
3047 "vbgr" => Some(FcRgba::Vbgr),
3048 "none" => Some(FcRgba::None),
3049 _ => None,
3050 }
3051}
3052
3053#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3054fn parse_lcdfilter_const(value: &str) -> Option<FcLcdFilter> {
3055 match value {
3056 "lcdnone" => Some(FcLcdFilter::None),
3057 "lcddefault" => Some(FcLcdFilter::Default),
3058 "lcdlight" => Some(FcLcdFilter::Light),
3059 "lcdlegacy" => Some(FcLcdFilter::Legacy),
3060 _ => None,
3061 }
3062}
3063
3064#[cfg(all(feature = "std", feature = "parsing"))]
3067const UNICODE_RANGE_MAPPINGS: &[(usize, u32, u32)] = &[
3068 (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), ];
3201
3202#[cfg(all(feature = "std", feature = "parsing"))]
3205struct ParsedFontFace {
3206 pattern: FcPattern,
3207 font_index: usize,
3208}
3209
3210#[cfg(all(feature = "std", feature = "parsing"))]
3216fn parse_font_faces(font_bytes: &[u8]) -> Option<Vec<ParsedFontFace>> {
3217 use allsorts::{
3218 binary::read::ReadScope,
3219 font_data::FontData,
3220 get_name::fontcode_get_name,
3221 post::PostTable,
3222 tables::{
3223 os2::Os2, HeadTable, NameTable,
3224 },
3225 tag,
3226 };
3227 use std::collections::BTreeSet;
3228
3229 const FONT_SPECIFIER_NAME_ID: u16 = 4;
3230 const FONT_SPECIFIER_FAMILY_ID: u16 = 1;
3231
3232 let max_fonts = if font_bytes.len() >= 12 && &font_bytes[0..4] == b"ttcf" {
3233 let num_fonts =
3235 u32::from_be_bytes([font_bytes[8], font_bytes[9], font_bytes[10], font_bytes[11]]);
3236 std::cmp::min(num_fonts as usize, 100)
3238 } else {
3239 1
3241 };
3242
3243 let scope = ReadScope::new(font_bytes);
3244 let font_file = scope.read::<FontData<'_>>().ok()?;
3245
3246 let mut results = Vec::new();
3248
3249 for font_index in 0..max_fonts {
3250 let provider = font_file.table_provider(font_index).ok()?;
3251 let head_data = provider.table_data(tag::HEAD).ok()??.into_owned();
3252 let head_table = ReadScope::new(&head_data).read::<HeadTable>().ok()?;
3253
3254 let is_bold = head_table.is_bold();
3255 let is_italic = head_table.is_italic();
3256 let mut detected_monospace = None;
3257
3258 let post_data = provider.table_data(tag::POST).ok()??;
3259 if let Ok(post_table) = ReadScope::new(&post_data).read::<PostTable>() {
3260 detected_monospace = Some(post_table.header.is_fixed_pitch != 0);
3262 }
3263
3264 let os2_data = provider.table_data(tag::OS_2).ok()??;
3266 let os2_table = ReadScope::new(&os2_data)
3267 .read_dep::<Os2>(os2_data.len())
3268 .ok()?;
3269
3270 let is_oblique = os2_table
3272 .fs_selection
3273 .contains(allsorts::tables::os2::FsSelection::OBLIQUE);
3274 let weight = FcWeight::from_u16(os2_table.us_weight_class);
3275 let stretch = FcStretch::from_u16(os2_table.us_width_class);
3276
3277 let mut unicode_ranges = Vec::new();
3281
3282 let os2_ranges = [
3284 os2_table.ul_unicode_range1,
3285 os2_table.ul_unicode_range2,
3286 os2_table.ul_unicode_range3,
3287 os2_table.ul_unicode_range4,
3288 ];
3289
3290 for &(bit, start, end) in UNICODE_RANGE_MAPPINGS {
3291 let range_idx = bit / 32;
3292 let bit_pos = bit % 32;
3293 if range_idx < 4 && (os2_ranges[range_idx] & (1 << bit_pos)) != 0 {
3294 unicode_ranges.push(UnicodeRange { start, end });
3295 }
3296 }
3297
3298 unicode_ranges = verify_unicode_ranges_with_cmap(&provider, unicode_ranges);
3302
3303 if unicode_ranges.is_empty() {
3305 if let Some(cmap_ranges) = analyze_cmap_coverage(&provider) {
3306 unicode_ranges = cmap_ranges;
3307 }
3308 }
3309
3310 let is_monospace = detect_monospace(&provider, &os2_table, detected_monospace)
3312 .unwrap_or(false);
3313
3314 let name_data = provider.table_data(tag::NAME).ok()??.into_owned();
3315 let name_table = ReadScope::new(&name_data).read::<NameTable>().ok()?;
3316
3317 let mut metadata = FcFontMetadata::default();
3319
3320 const NAME_ID_COPYRIGHT: u16 = 0;
3321 const NAME_ID_FAMILY: u16 = 1;
3322 const NAME_ID_SUBFAMILY: u16 = 2;
3323 const NAME_ID_UNIQUE_ID: u16 = 3;
3324 const NAME_ID_FULL_NAME: u16 = 4;
3325 const NAME_ID_VERSION: u16 = 5;
3326 const NAME_ID_POSTSCRIPT_NAME: u16 = 6;
3327 const NAME_ID_TRADEMARK: u16 = 7;
3328 const NAME_ID_MANUFACTURER: u16 = 8;
3329 const NAME_ID_DESIGNER: u16 = 9;
3330 const NAME_ID_DESCRIPTION: u16 = 10;
3331 const NAME_ID_VENDOR_URL: u16 = 11;
3332 const NAME_ID_DESIGNER_URL: u16 = 12;
3333 const NAME_ID_LICENSE: u16 = 13;
3334 const NAME_ID_LICENSE_URL: u16 = 14;
3335 const NAME_ID_PREFERRED_FAMILY: u16 = 16;
3336 const NAME_ID_PREFERRED_SUBFAMILY: u16 = 17;
3337
3338 metadata.copyright = get_name_string(&name_data, NAME_ID_COPYRIGHT);
3339 metadata.font_family = get_name_string(&name_data, NAME_ID_FAMILY);
3340 metadata.font_subfamily = get_name_string(&name_data, NAME_ID_SUBFAMILY);
3341 metadata.full_name = get_name_string(&name_data, NAME_ID_FULL_NAME);
3342 metadata.unique_id = get_name_string(&name_data, NAME_ID_UNIQUE_ID);
3343 metadata.version = get_name_string(&name_data, NAME_ID_VERSION);
3344 metadata.postscript_name = get_name_string(&name_data, NAME_ID_POSTSCRIPT_NAME);
3345 metadata.trademark = get_name_string(&name_data, NAME_ID_TRADEMARK);
3346 metadata.manufacturer = get_name_string(&name_data, NAME_ID_MANUFACTURER);
3347 metadata.designer = get_name_string(&name_data, NAME_ID_DESIGNER);
3348 metadata.id_description = get_name_string(&name_data, NAME_ID_DESCRIPTION);
3349 metadata.designer_url = get_name_string(&name_data, NAME_ID_DESIGNER_URL);
3350 metadata.manufacturer_url = get_name_string(&name_data, NAME_ID_VENDOR_URL);
3351 metadata.license = get_name_string(&name_data, NAME_ID_LICENSE);
3352 metadata.license_url = get_name_string(&name_data, NAME_ID_LICENSE_URL);
3353 metadata.preferred_family = get_name_string(&name_data, NAME_ID_PREFERRED_FAMILY);
3354 metadata.preferred_subfamily = get_name_string(&name_data, NAME_ID_PREFERRED_SUBFAMILY);
3355
3356 let mut f_family = None;
3358
3359 let patterns = name_table
3360 .name_records
3361 .iter()
3362 .filter_map(|name_record| {
3363 let name_id = name_record.name_id;
3364 if name_id == FONT_SPECIFIER_FAMILY_ID {
3365 if let Ok(Some(family)) =
3366 fontcode_get_name(&name_data, FONT_SPECIFIER_FAMILY_ID)
3367 {
3368 f_family = Some(family);
3369 }
3370 None
3371 } else if name_id == FONT_SPECIFIER_NAME_ID {
3372 let family = f_family.as_ref()?;
3373 let name = fontcode_get_name(&name_data, FONT_SPECIFIER_NAME_ID).ok()??;
3374 if name.to_bytes().is_empty() {
3375 None
3376 } else {
3377 let mut name_str =
3378 String::from_utf8_lossy(name.to_bytes()).to_string();
3379 let mut family_str =
3380 String::from_utf8_lossy(family.as_bytes()).to_string();
3381 if name_str.starts_with('.') {
3382 name_str = name_str[1..].to_string();
3383 }
3384 if family_str.starts_with('.') {
3385 family_str = family_str[1..].to_string();
3386 }
3387 Some((
3388 FcPattern {
3389 name: Some(name_str),
3390 family: Some(family_str),
3391 bold: if is_bold {
3392 PatternMatch::True
3393 } else {
3394 PatternMatch::False
3395 },
3396 italic: if is_italic {
3397 PatternMatch::True
3398 } else {
3399 PatternMatch::False
3400 },
3401 oblique: if is_oblique {
3402 PatternMatch::True
3403 } else {
3404 PatternMatch::False
3405 },
3406 monospace: if is_monospace {
3407 PatternMatch::True
3408 } else {
3409 PatternMatch::False
3410 },
3411 condensed: if stretch <= FcStretch::Condensed {
3412 PatternMatch::True
3413 } else {
3414 PatternMatch::False
3415 },
3416 weight,
3417 stretch,
3418 unicode_ranges: unicode_ranges.clone(),
3419 metadata: metadata.clone(),
3420 render_config: FcFontRenderConfig::default(),
3421 },
3422 font_index,
3423 ))
3424 }
3425 } else {
3426 None
3427 }
3428 })
3429 .collect::<BTreeSet<_>>();
3430
3431 results.extend(patterns.into_iter().map(|(pat, idx)| ParsedFontFace {
3432 pattern: pat,
3433 font_index: idx,
3434 }));
3435 }
3436
3437 if results.is_empty() {
3438 None
3439 } else {
3440 Some(results)
3441 }
3442}
3443
3444#[cfg(all(feature = "std", feature = "parsing"))]
3446pub(crate) fn FcParseFont(filepath: &PathBuf) -> Option<Vec<(FcPattern, FcFontPath)>> {
3447 #[cfg(all(not(target_family = "wasm"), feature = "std"))]
3448 use mmapio::MmapOptions;
3449 use std::fs::File;
3450
3451 let file = File::open(filepath).ok()?;
3453
3454 #[cfg(all(not(target_family = "wasm"), feature = "std"))]
3455 let font_bytes = unsafe { MmapOptions::new().map(&file).ok()? };
3456
3457 #[cfg(not(all(not(target_family = "wasm"), feature = "std")))]
3458 let font_bytes = std::fs::read(filepath).ok()?;
3459
3460 let faces = parse_font_faces(&font_bytes[..])?;
3461 let path_str = filepath.to_string_lossy().to_string();
3462
3463 Some(
3464 faces
3465 .into_iter()
3466 .map(|face| {
3467 (
3468 face.pattern,
3469 FcFontPath {
3470 path: path_str.clone(),
3471 font_index: face.font_index,
3472 },
3473 )
3474 })
3475 .collect(),
3476 )
3477}
3478
3479#[cfg(all(feature = "std", feature = "parsing"))]
3505#[allow(non_snake_case)]
3506pub fn FcParseFontBytes(font_bytes: &[u8], font_id: &str) -> Option<Vec<(FcPattern, FcFont)>> {
3507 FcParseFontBytesInner(font_bytes, font_id)
3508}
3509
3510#[cfg(all(feature = "std", feature = "parsing"))]
3513fn FcParseFontBytesInner(font_bytes: &[u8], font_id: &str) -> Option<Vec<(FcPattern, FcFont)>> {
3514 let faces = parse_font_faces(font_bytes)?;
3515 let id = font_id.to_string();
3516 let bytes = font_bytes.to_vec();
3517
3518 Some(
3519 faces
3520 .into_iter()
3521 .map(|face| {
3522 (
3523 face.pattern,
3524 FcFont {
3525 bytes: bytes.clone(),
3526 font_index: face.font_index,
3527 id: id.clone(),
3528 },
3529 )
3530 })
3531 .collect(),
3532 )
3533}
3534
3535#[cfg(all(feature = "std", feature = "parsing"))]
3536fn FcScanDirectoriesInner(paths: &[(Option<String>, String)]) -> Vec<(FcPattern, FcFontPath)> {
3537 #[cfg(feature = "multithreading")]
3538 {
3539 use rayon::prelude::*;
3540
3541 paths
3543 .par_iter()
3544 .filter_map(|(prefix, p)| {
3545 process_path(prefix, PathBuf::from(p), false).map(FcScanSingleDirectoryRecursive)
3546 })
3547 .flatten()
3548 .collect()
3549 }
3550 #[cfg(not(feature = "multithreading"))]
3551 {
3552 paths
3553 .iter()
3554 .filter_map(|(prefix, p)| {
3555 process_path(prefix, PathBuf::from(p), false).map(FcScanSingleDirectoryRecursive)
3556 })
3557 .flatten()
3558 .collect()
3559 }
3560}
3561
3562#[cfg(all(feature = "std", feature = "parsing"))]
3563fn FcScanSingleDirectoryRecursive(dir: PathBuf) -> Vec<(FcPattern, FcFontPath)> {
3564 let mut files_to_parse = Vec::new();
3565 let mut dirs_to_parse = vec![dir];
3566
3567 'outer: loop {
3568 let mut new_dirs_to_parse = Vec::new();
3569
3570 'inner: for dir in dirs_to_parse.clone() {
3571 let dir = match std::fs::read_dir(dir) {
3572 Ok(o) => o,
3573 Err(_) => continue 'inner,
3574 };
3575
3576 for (path, pathbuf) in dir.filter_map(|entry| {
3577 let entry = entry.ok()?;
3578 let path = entry.path();
3579 let pathbuf = path.to_path_buf();
3580 Some((path, pathbuf))
3581 }) {
3582 if path.is_dir() {
3583 new_dirs_to_parse.push(pathbuf);
3584 } else {
3585 files_to_parse.push(pathbuf);
3586 }
3587 }
3588 }
3589
3590 if new_dirs_to_parse.is_empty() {
3591 break 'outer;
3592 } else {
3593 dirs_to_parse = new_dirs_to_parse;
3594 }
3595 }
3596
3597 FcParseFontFiles(&files_to_parse)
3598}
3599
3600#[cfg(all(feature = "std", feature = "parsing"))]
3601fn FcParseFontFiles(files_to_parse: &[PathBuf]) -> Vec<(FcPattern, FcFontPath)> {
3602 let result = {
3603 #[cfg(feature = "multithreading")]
3604 {
3605 use rayon::prelude::*;
3606
3607 files_to_parse
3608 .par_iter()
3609 .filter_map(|file| FcParseFont(file))
3610 .collect::<Vec<Vec<_>>>()
3611 }
3612 #[cfg(not(feature = "multithreading"))]
3613 {
3614 files_to_parse
3615 .iter()
3616 .filter_map(|file| FcParseFont(file))
3617 .collect::<Vec<Vec<_>>>()
3618 }
3619 };
3620
3621 result.into_iter().flat_map(|f| f.into_iter()).collect()
3622}
3623
3624#[cfg(all(feature = "std", feature = "parsing"))]
3625fn process_path(
3629 prefix: &Option<String>,
3630 mut path: PathBuf,
3631 is_include_path: bool,
3632) -> Option<PathBuf> {
3633 use std::env::var;
3634
3635 const HOME_SHORTCUT: &str = "~";
3636 const CWD_PATH: &str = ".";
3637
3638 const HOME_ENV_VAR: &str = "HOME";
3639 const XDG_CONFIG_HOME_ENV_VAR: &str = "XDG_CONFIG_HOME";
3640 const XDG_CONFIG_HOME_DEFAULT_PATH_SUFFIX: &str = ".config";
3641 const XDG_DATA_HOME_ENV_VAR: &str = "XDG_DATA_HOME";
3642 const XDG_DATA_HOME_DEFAULT_PATH_SUFFIX: &str = ".local/share";
3643
3644 const PREFIX_CWD: &str = "cwd";
3645 const PREFIX_DEFAULT: &str = "default";
3646 const PREFIX_XDG: &str = "xdg";
3647
3648 fn get_home_value() -> Option<PathBuf> {
3650 var(HOME_ENV_VAR).ok().map(PathBuf::from)
3651 }
3652 fn get_xdg_config_home_value() -> Option<PathBuf> {
3653 var(XDG_CONFIG_HOME_ENV_VAR)
3654 .ok()
3655 .map(PathBuf::from)
3656 .or_else(|| {
3657 get_home_value()
3658 .map(|home_path| home_path.join(XDG_CONFIG_HOME_DEFAULT_PATH_SUFFIX))
3659 })
3660 }
3661 fn get_xdg_data_home_value() -> Option<PathBuf> {
3662 var(XDG_DATA_HOME_ENV_VAR)
3663 .ok()
3664 .map(PathBuf::from)
3665 .or_else(|| {
3666 get_home_value().map(|home_path| home_path.join(XDG_DATA_HOME_DEFAULT_PATH_SUFFIX))
3667 })
3668 }
3669
3670 if path.starts_with(HOME_SHORTCUT) {
3672 if let Some(home_path) = get_home_value() {
3673 path = home_path.join(
3674 path.strip_prefix(HOME_SHORTCUT)
3675 .expect("already checked that it starts with the prefix"),
3676 );
3677 } else {
3678 return None;
3679 }
3680 }
3681
3682 match prefix {
3684 Some(prefix) => match prefix.as_str() {
3685 PREFIX_CWD | PREFIX_DEFAULT => {
3686 let mut new_path = PathBuf::from(CWD_PATH);
3687 new_path.push(path);
3688
3689 Some(new_path)
3690 }
3691 PREFIX_XDG => {
3692 if is_include_path {
3693 get_xdg_config_home_value()
3694 .map(|xdg_config_home_path| xdg_config_home_path.join(path))
3695 } else {
3696 get_xdg_data_home_value()
3697 .map(|xdg_data_home_path| xdg_data_home_path.join(path))
3698 }
3699 }
3700 _ => None, },
3702 None => Some(path),
3703 }
3704}
3705
3706#[cfg(all(feature = "std", feature = "parsing"))]
3708fn get_name_string(name_data: &[u8], name_id: u16) -> Option<String> {
3709 fontcode_get_name(name_data, name_id)
3710 .ok()
3711 .flatten()
3712 .map(|name| String::from_utf8_lossy(name.to_bytes()).to_string())
3713}
3714
3715#[cfg(all(feature = "std", feature = "parsing"))]
3719fn get_verification_codepoints(start: u32, end: u32) -> Vec<u32> {
3720 match start {
3721 0x0000 => vec!['A' as u32, 'M' as u32, 'Z' as u32, 'a' as u32, 'm' as u32, 'z' as u32],
3723 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], _ => {
3799 let range_size = end - start;
3800 if range_size > 20 {
3801 vec![
3802 start + range_size / 5,
3803 start + 2 * range_size / 5,
3804 start + 3 * range_size / 5,
3805 start + 4 * range_size / 5,
3806 ]
3807 } else {
3808 vec![start, start + range_size / 2]
3809 }
3810 }
3811 }
3812}
3813
3814#[cfg(all(feature = "std", feature = "parsing"))]
3817fn find_best_cmap_subtable<'a>(
3818 cmap: &allsorts::tables::cmap::Cmap<'a>,
3819) -> Option<allsorts::tables::cmap::EncodingRecord> {
3820 use allsorts::tables::cmap::{PlatformId, EncodingId};
3821
3822 cmap.find_subtable(PlatformId::UNICODE, EncodingId(3))
3823 .or_else(|| cmap.find_subtable(PlatformId::UNICODE, EncodingId(4)))
3824 .or_else(|| cmap.find_subtable(PlatformId::WINDOWS, EncodingId(1)))
3825 .or_else(|| cmap.find_subtable(PlatformId::WINDOWS, EncodingId(10)))
3826 .or_else(|| cmap.find_subtable(PlatformId::UNICODE, EncodingId(0)))
3827 .or_else(|| cmap.find_subtable(PlatformId::UNICODE, EncodingId(1)))
3828}
3829
3830#[cfg(all(feature = "std", feature = "parsing"))]
3833fn verify_unicode_ranges_with_cmap(
3834 provider: &impl FontTableProvider,
3835 os2_ranges: Vec<UnicodeRange>
3836) -> Vec<UnicodeRange> {
3837 use allsorts::tables::cmap::{Cmap, CmapSubtable};
3838
3839 if os2_ranges.is_empty() {
3840 return Vec::new();
3841 }
3842
3843 let cmap_data = match provider.table_data(tag::CMAP) {
3845 Ok(Some(data)) => data,
3846 _ => return os2_ranges, };
3848
3849 let cmap = match ReadScope::new(&cmap_data).read::<Cmap<'_>>() {
3850 Ok(c) => c,
3851 Err(_) => return os2_ranges,
3852 };
3853
3854 let encoding_record = match find_best_cmap_subtable(&cmap) {
3855 Some(r) => r,
3856 None => return os2_ranges, };
3858
3859 let cmap_subtable = match ReadScope::new(&cmap_data)
3860 .offset(encoding_record.offset as usize)
3861 .read::<CmapSubtable<'_>>()
3862 {
3863 Ok(st) => st,
3864 Err(_) => return os2_ranges,
3865 };
3866
3867 let mut verified_ranges = Vec::new();
3869
3870 for range in os2_ranges {
3871 let test_codepoints = get_verification_codepoints(range.start, range.end);
3872
3873 let required_hits = (test_codepoints.len() + 1) / 2; let mut hits = 0;
3877
3878 for cp in test_codepoints {
3879 if cp >= range.start && cp <= range.end {
3880 if let Ok(Some(gid)) = cmap_subtable.map_glyph(cp) {
3881 if gid != 0 {
3882 hits += 1;
3883 if hits >= required_hits {
3884 break;
3885 }
3886 }
3887 }
3888 }
3889 }
3890
3891 if hits >= required_hits {
3892 verified_ranges.push(range);
3893 }
3894 }
3895
3896 verified_ranges
3897}
3898
3899#[cfg(all(feature = "std", feature = "parsing"))]
3902fn analyze_cmap_coverage(provider: &impl FontTableProvider) -> Option<Vec<UnicodeRange>> {
3903 use allsorts::tables::cmap::{Cmap, CmapSubtable};
3904
3905 let cmap_data = provider.table_data(tag::CMAP).ok()??;
3906 let cmap = ReadScope::new(&cmap_data).read::<Cmap<'_>>().ok()?;
3907
3908 let encoding_record = find_best_cmap_subtable(&cmap)?;
3909
3910 let cmap_subtable = ReadScope::new(&cmap_data)
3911 .offset(encoding_record.offset as usize)
3912 .read::<CmapSubtable<'_>>()
3913 .ok()?;
3914
3915 let blocks_to_check: &[(u32, u32)] = &[
3917 (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), ];
3968
3969 let mut ranges = Vec::new();
3970
3971 for &(start, end) in blocks_to_check {
3972 let test_codepoints = get_verification_codepoints(start, end);
3973 let required_hits = (test_codepoints.len() + 1) / 2;
3974 let mut hits = 0;
3975
3976 for cp in test_codepoints {
3977 if let Ok(Some(gid)) = cmap_subtable.map_glyph(cp) {
3978 if gid != 0 {
3979 hits += 1;
3980 if hits >= required_hits {
3981 break;
3982 }
3983 }
3984 }
3985 }
3986
3987 if hits >= required_hits {
3988 ranges.push(UnicodeRange { start, end });
3989 }
3990 }
3991
3992 if ranges.is_empty() {
3993 None
3994 } else {
3995 Some(ranges)
3996 }
3997}
3998
3999#[cfg(all(feature = "std", feature = "parsing"))]
4001#[allow(dead_code)]
4002fn extract_unicode_ranges(os2_table: &Os2) -> Vec<UnicodeRange> {
4003 let mut unicode_ranges = Vec::new();
4004
4005 let ranges = [
4006 os2_table.ul_unicode_range1,
4007 os2_table.ul_unicode_range2,
4008 os2_table.ul_unicode_range3,
4009 os2_table.ul_unicode_range4,
4010 ];
4011
4012 for &(bit, start, end) in UNICODE_RANGE_MAPPINGS {
4013 let range_idx = bit / 32;
4014 let bit_pos = bit % 32;
4015 if range_idx < 4 && (ranges[range_idx] & (1 << bit_pos)) != 0 {
4016 unicode_ranges.push(UnicodeRange { start, end });
4017 }
4018 }
4019
4020 unicode_ranges
4021}
4022
4023#[cfg(all(feature = "std", feature = "parsing"))]
4025fn detect_monospace(
4026 provider: &impl FontTableProvider,
4027 os2_table: &Os2,
4028 detected_monospace: Option<bool>,
4029) -> Option<bool> {
4030 if let Some(is_monospace) = detected_monospace {
4031 return Some(is_monospace);
4032 }
4033
4034 if os2_table.panose[0] == 2 {
4036 return Some(os2_table.panose[3] == 9); }
4039
4040 let hhea_data = provider.table_data(tag::HHEA).ok()??;
4042 let hhea_table = ReadScope::new(&hhea_data).read::<HheaTable>().ok()?;
4043 let maxp_data = provider.table_data(tag::MAXP).ok()??;
4044 let maxp_table = ReadScope::new(&maxp_data).read::<MaxpTable>().ok()?;
4045 let hmtx_data = provider.table_data(tag::HMTX).ok()??;
4046 let hmtx_table = ReadScope::new(&hmtx_data)
4047 .read_dep::<HmtxTable<'_>>((
4048 usize::from(maxp_table.num_glyphs),
4049 usize::from(hhea_table.num_h_metrics),
4050 ))
4051 .ok()?;
4052
4053 let mut monospace = true;
4054 let mut last_advance = 0;
4055
4056 for i in 0..hhea_table.num_h_metrics as usize {
4058 let advance = hmtx_table.h_metrics.read_item(i).ok()?.advance_width;
4059 if i > 0 && advance != last_advance {
4060 monospace = false;
4061 break;
4062 }
4063 last_advance = advance;
4064 }
4065
4066 Some(monospace)
4067}