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 FontFallbackChain {
2048 css_fallbacks,
2049 unicode_fallbacks: Vec::new(), original_stack: font_families.to_vec(),
2051 }
2052 }
2053
2054 #[allow(dead_code)]
2056 fn extract_unicode_ranges(text: &str) -> Vec<UnicodeRange> {
2057 let mut chars: Vec<char> = text.chars().collect();
2058 chars.sort_unstable();
2059 chars.dedup();
2060
2061 if chars.is_empty() {
2062 return Vec::new();
2063 }
2064
2065 let mut ranges = Vec::new();
2066 let mut range_start = chars[0] as u32;
2067 let mut range_end = range_start;
2068
2069 for &c in &chars[1..] {
2070 let codepoint = c as u32;
2071 if codepoint == range_end + 1 {
2072 range_end = codepoint;
2073 } else {
2074 ranges.push(UnicodeRange { start: range_start, end: range_end });
2075 range_start = codepoint;
2076 range_end = codepoint;
2077 }
2078 }
2079
2080 ranges.push(UnicodeRange { start: range_start, end: range_end });
2081 ranges
2082 }
2083
2084 #[cfg(feature = "std")]
2091 fn fuzzy_query_by_name(
2092 &self,
2093 requested_name: &str,
2094 weight: FcWeight,
2095 italic: PatternMatch,
2096 oblique: PatternMatch,
2097 unicode_ranges: &[UnicodeRange],
2098 _trace: &mut Vec<TraceMsg>,
2099 ) -> Vec<FontMatch> {
2100 let tokens = Self::extract_font_name_tokens(requested_name);
2102
2103 if tokens.is_empty() {
2104 return Vec::new();
2105 }
2106
2107 let tokens_lower: Vec<String> = tokens.iter().map(|t| t.to_lowercase()).collect();
2109
2110 let first_token = &tokens_lower[0];
2117 let mut candidate_ids = match self.token_index.get(first_token) {
2118 Some(ids) if !ids.is_empty() => ids.clone(),
2119 _ => {
2120 return Vec::new();
2122 }
2123 };
2124
2125 for token in &tokens_lower[1..] {
2127 if let Some(token_ids) = self.token_index.get(token) {
2128 let intersection: alloc::collections::BTreeSet<FontId> =
2130 candidate_ids.intersection(token_ids).copied().collect();
2131
2132 if intersection.is_empty() {
2133 break;
2135 } else {
2136 candidate_ids = intersection;
2138 }
2139 } else {
2140 break;
2142 }
2143 }
2144
2145 let mut candidates = Vec::new();
2147
2148 for id in candidate_ids {
2149 let pattern = match self.metadata.get(&id) {
2150 Some(p) => p,
2151 None => continue,
2152 };
2153
2154 let font_tokens_lower = match self.font_tokens.get(&id) {
2156 Some(tokens) => tokens,
2157 None => continue,
2158 };
2159
2160 if font_tokens_lower.is_empty() {
2161 continue;
2162 }
2163
2164 let token_matches = tokens_lower.iter()
2167 .filter(|req_token| {
2168 font_tokens_lower.iter().any(|font_token| {
2169 font_token == *req_token
2171 })
2172 })
2173 .count();
2174
2175 if token_matches == 0 {
2177 continue;
2178 }
2179
2180 let token_similarity = (token_matches * 100 / tokens.len()) as i32;
2182
2183 let unicode_similarity = if !unicode_ranges.is_empty() && !pattern.unicode_ranges.is_empty() {
2185 Self::calculate_unicode_compatibility(unicode_ranges, &pattern.unicode_ranges)
2186 } else {
2187 0
2188 };
2189
2190 if !unicode_ranges.is_empty() && unicode_similarity == 0 {
2193 continue;
2194 }
2195
2196 let style_score = Self::calculate_style_score(&FcPattern {
2197 weight,
2198 italic,
2199 oblique,
2200 ..Default::default()
2201 }, pattern);
2202
2203 candidates.push((
2204 id,
2205 token_similarity,
2206 unicode_similarity,
2207 style_score,
2208 pattern.clone(),
2209 ));
2210 }
2211
2212 candidates.sort_by(|a, b| {
2218 if !unicode_ranges.is_empty() {
2219 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 {
2226 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)) }
2232 });
2233
2234 candidates.truncate(5);
2236
2237 candidates
2239 .into_iter()
2240 .map(|(id, _token_sim, _unicode_sim, _style, pattern)| {
2241 FontMatch {
2242 id,
2243 unicode_ranges: pattern.unicode_ranges.clone(),
2244 fallbacks: Vec::new(), }
2246 })
2247 .collect()
2248 }
2249
2250 pub fn extract_font_name_tokens(name: &str) -> Vec<String> {
2254 let mut tokens = Vec::new();
2255 let mut current_token = String::new();
2256 let mut last_was_lower = false;
2257
2258 for c in name.chars() {
2259 if c.is_whitespace() || c == '-' || c == '_' {
2260 if !current_token.is_empty() {
2262 tokens.push(current_token.clone());
2263 current_token.clear();
2264 }
2265 last_was_lower = false;
2266 } else if c.is_uppercase() && last_was_lower && !current_token.is_empty() {
2267 tokens.push(current_token.clone());
2269 current_token.clear();
2270 current_token.push(c);
2271 last_was_lower = false;
2272 } else {
2273 current_token.push(c);
2274 last_was_lower = c.is_lowercase();
2275 }
2276 }
2277
2278 if !current_token.is_empty() {
2279 tokens.push(current_token);
2280 }
2281
2282 tokens
2283 }
2284
2285 #[allow(dead_code)]
2289 fn find_unicode_fallbacks(
2290 &self,
2291 unicode_ranges: &[UnicodeRange],
2292 covered_chars: &[bool],
2293 existing_groups: &[CssFallbackGroup],
2294 weight: FcWeight,
2295 italic: PatternMatch,
2296 oblique: PatternMatch,
2297 trace: &mut Vec<TraceMsg>,
2298 ) -> Vec<FontMatch> {
2299 let mut uncovered_ranges = Vec::new();
2301 for (i, &covered) in covered_chars.iter().enumerate() {
2302 if !covered && i < unicode_ranges.len() {
2303 uncovered_ranges.push(unicode_ranges[i].clone());
2304 }
2305 }
2306
2307 if uncovered_ranges.is_empty() {
2308 return Vec::new();
2309 }
2310
2311 let pattern = FcPattern {
2313 name: None, weight,
2315 italic,
2316 oblique,
2317 unicode_ranges: uncovered_ranges.clone(),
2318 ..Default::default()
2319 };
2320
2321 let mut candidates = self.query_internal(&pattern, trace);
2322
2323 let existing_prefixes: Vec<String> = existing_groups
2326 .iter()
2327 .flat_map(|group| {
2328 group.fonts.iter().filter_map(|font| {
2329 self.get_metadata_by_id(&font.id)
2330 .and_then(|meta| meta.family.clone())
2331 .and_then(|family| {
2332 family.split_whitespace()
2334 .take(2)
2335 .collect::<Vec<_>>()
2336 .join(" ")
2337 .into()
2338 })
2339 })
2340 })
2341 .collect();
2342
2343 candidates.sort_by(|a, b| {
2347 let a_meta = self.get_metadata_by_id(&a.id);
2348 let b_meta = self.get_metadata_by_id(&b.id);
2349
2350 let a_score = Self::calculate_font_similarity_score(a_meta, &existing_prefixes);
2351 let b_score = Self::calculate_font_similarity_score(b_meta, &existing_prefixes);
2352
2353 b_score.cmp(&a_score) .then_with(|| {
2355 let a_coverage = Self::calculate_unicode_compatibility(&uncovered_ranges, &a.unicode_ranges);
2356 let b_coverage = Self::calculate_unicode_compatibility(&uncovered_ranges, &b.unicode_ranges);
2357 b_coverage.cmp(&a_coverage)
2358 })
2359 });
2360
2361 let mut result = Vec::new();
2363 let mut remaining_uncovered: Vec<bool> = vec![true; uncovered_ranges.len()];
2364
2365 for candidate in candidates {
2366 let mut covers_new_range = false;
2368
2369 for (i, range) in uncovered_ranges.iter().enumerate() {
2370 if remaining_uncovered[i] {
2371 for font_range in &candidate.unicode_ranges {
2373 if font_range.overlaps(range) {
2374 remaining_uncovered[i] = false;
2375 covers_new_range = true;
2376 break;
2377 }
2378 }
2379 }
2380 }
2381
2382 if covers_new_range {
2384 result.push(candidate);
2385
2386 if remaining_uncovered.iter().all(|&uncovered| !uncovered) {
2388 break;
2389 }
2390 }
2391 }
2392
2393 result
2394 }
2395
2396 #[allow(dead_code)]
2399 fn calculate_font_similarity_score(
2400 font_meta: Option<&FcPattern>,
2401 existing_prefixes: &[String],
2402 ) -> i32 {
2403 let Some(meta) = font_meta else { return 0; };
2404 let Some(family) = &meta.family else { return 0; };
2405
2406 for prefix in existing_prefixes {
2408 if family.starts_with(prefix) {
2409 return 100; }
2411 if family.contains(prefix) {
2412 return 50; }
2414 }
2415
2416 0 }
2418
2419 pub fn calculate_unicode_coverage(ranges: &[UnicodeRange]) -> u64 {
2422 ranges
2423 .iter()
2424 .map(|range| (range.end - range.start + 1) as u64)
2425 .sum()
2426 }
2427
2428 pub fn calculate_unicode_compatibility(
2431 requested: &[UnicodeRange],
2432 available: &[UnicodeRange],
2433 ) -> i32 {
2434 if requested.is_empty() {
2435 return Self::calculate_unicode_coverage(available) as i32;
2437 }
2438
2439 let mut total_coverage = 0u32;
2440
2441 for req_range in requested {
2442 for avail_range in available {
2443 let overlap_start = req_range.start.max(avail_range.start);
2445 let overlap_end = req_range.end.min(avail_range.end);
2446
2447 if overlap_start <= overlap_end {
2448 let overlap_size = overlap_end - overlap_start + 1;
2450 total_coverage += overlap_size;
2451 }
2452 }
2453 }
2454
2455 total_coverage as i32
2456 }
2457
2458 pub fn calculate_style_score(original: &FcPattern, candidate: &FcPattern) -> i32 {
2459
2460 let mut score = 0_i32;
2461
2462 if (original.bold == PatternMatch::True && candidate.weight == FcWeight::Bold)
2464 || (original.bold == PatternMatch::False && candidate.weight != FcWeight::Bold)
2465 {
2466 } else {
2469 let weight_diff = (original.weight as i32 - candidate.weight as i32).abs();
2471 score += weight_diff as i32;
2472 }
2473
2474 if original.weight == candidate.weight {
2477 score -= 15;
2478 if original.weight == FcWeight::Normal {
2479 score -= 10; }
2481 }
2482
2483 if (original.condensed == PatternMatch::True && candidate.stretch.is_condensed())
2485 || (original.condensed == PatternMatch::False && !candidate.stretch.is_condensed())
2486 {
2487 } else {
2490 let stretch_diff = (original.stretch as i32 - candidate.stretch as i32).abs();
2492 score += (stretch_diff * 100) as i32;
2493 }
2494
2495 let style_props = [
2497 (original.italic, candidate.italic, 300, 150),
2498 (original.oblique, candidate.oblique, 200, 100),
2499 (original.bold, candidate.bold, 300, 150),
2500 (original.monospace, candidate.monospace, 100, 50),
2501 (original.condensed, candidate.condensed, 100, 50),
2502 ];
2503
2504 for (orig, cand, mismatch_penalty, dontcare_penalty) in style_props {
2505 if orig.needs_to_match() {
2506 if orig == PatternMatch::False && cand == PatternMatch::DontCare {
2507 score += dontcare_penalty / 2;
2510 } else if !orig.matches(&cand) {
2511 if cand == PatternMatch::DontCare {
2512 score += dontcare_penalty;
2513 } else {
2514 score += mismatch_penalty;
2515 }
2516 } else if orig == PatternMatch::True && cand == PatternMatch::True {
2517 score -= 20;
2519 } else if orig == PatternMatch::False && cand == PatternMatch::False {
2520 score -= 20;
2523 }
2524 } else {
2525 if cand == PatternMatch::True {
2530 score += dontcare_penalty / 3;
2531 }
2532 }
2533 }
2534
2535 if let (Some(name), Some(family)) = (&candidate.name, &candidate.family) {
2541 let name_lower = name.to_lowercase();
2542 let family_lower = family.to_lowercase();
2543
2544 let extra = if name_lower.starts_with(&family_lower) {
2546 name_lower[family_lower.len()..].to_string()
2547 } else {
2548 String::new()
2549 };
2550
2551 let stripped = extra
2553 .replace("regular", "")
2554 .replace("normal", "")
2555 .replace("book", "")
2556 .replace("roman", "");
2557 let stripped = stripped.trim();
2558
2559 if stripped.is_empty() {
2560 score -= 50;
2562 } else {
2563 let extra_words = stripped.split_whitespace().count();
2565 score += (extra_words as i32) * 25;
2566 }
2567 }
2568
2569 if let Some(ref subfamily) = candidate.metadata.font_subfamily {
2573 let sf_lower = subfamily.to_lowercase();
2574 if sf_lower == "regular" {
2575 score -= 30;
2576 }
2577 }
2578
2579 score
2580 }
2581}
2582
2583#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
2584fn FcScanDirectories() -> Option<(Vec<(FcPattern, FcFontPath)>, BTreeMap<String, FcFontRenderConfig>)> {
2585 use std::fs;
2586 use std::path::Path;
2587
2588 const BASE_FONTCONFIG_PATH: &str = "/etc/fonts/fonts.conf";
2589
2590 if !Path::new(BASE_FONTCONFIG_PATH).exists() {
2591 return None;
2592 }
2593
2594 let mut font_paths = Vec::with_capacity(32);
2595 let mut paths_to_visit = vec![(None, PathBuf::from(BASE_FONTCONFIG_PATH))];
2596 let mut render_configs: BTreeMap<String, FcFontRenderConfig> = BTreeMap::new();
2597
2598 while let Some((prefix, path_to_visit)) = paths_to_visit.pop() {
2599 let path = match process_path(&prefix, path_to_visit, true) {
2600 Some(path) => path,
2601 None => continue,
2602 };
2603
2604 let metadata = match fs::metadata(&path) {
2605 Ok(metadata) => metadata,
2606 Err(_) => continue,
2607 };
2608
2609 if metadata.is_file() {
2610 let xml_utf8 = match fs::read_to_string(&path) {
2611 Ok(xml_utf8) => xml_utf8,
2612 Err(_) => continue,
2613 };
2614
2615 if ParseFontsConf(&xml_utf8, &mut paths_to_visit, &mut font_paths).is_none() {
2616 continue;
2617 }
2618
2619 ParseFontsConfRenderConfig(&xml_utf8, &mut render_configs);
2621 } else if metadata.is_dir() {
2622 let dir_entries = match fs::read_dir(&path) {
2623 Ok(dir_entries) => dir_entries,
2624 Err(_) => continue,
2625 };
2626
2627 for entry_result in dir_entries {
2628 let entry = match entry_result {
2629 Ok(entry) => entry,
2630 Err(_) => continue,
2631 };
2632
2633 let entry_path = entry.path();
2634
2635 let entry_metadata = match fs::metadata(&entry_path) {
2637 Ok(metadata) => metadata,
2638 Err(_) => continue,
2639 };
2640
2641 if !entry_metadata.is_file() {
2642 continue;
2643 }
2644
2645 let file_name = match entry_path.file_name() {
2646 Some(name) => name,
2647 None => continue,
2648 };
2649
2650 let file_name_str = file_name.to_string_lossy();
2651 if file_name_str.starts_with(|c: char| c.is_ascii_digit())
2652 && file_name_str.ends_with(".conf")
2653 {
2654 paths_to_visit.push((None, entry_path));
2655 }
2656 }
2657 }
2658 }
2659
2660 if font_paths.is_empty() {
2661 return None;
2662 }
2663
2664 Some((FcScanDirectoriesInner(&font_paths), render_configs))
2665}
2666
2667#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
2669fn ParseFontsConf(
2670 input: &str,
2671 paths_to_visit: &mut Vec<(Option<String>, PathBuf)>,
2672 font_paths: &mut Vec<(Option<String>, String)>,
2673) -> Option<()> {
2674 use xmlparser::Token::*;
2675 use xmlparser::Tokenizer;
2676
2677 const TAG_INCLUDE: &str = "include";
2678 const TAG_DIR: &str = "dir";
2679 const ATTRIBUTE_PREFIX: &str = "prefix";
2680
2681 let mut current_prefix: Option<&str> = None;
2682 let mut current_path: Option<&str> = None;
2683 let mut is_in_include = false;
2684 let mut is_in_dir = false;
2685
2686 for token_result in Tokenizer::from(input) {
2687 let token = match token_result {
2688 Ok(token) => token,
2689 Err(_) => return None,
2690 };
2691
2692 match token {
2693 ElementStart { local, .. } => {
2694 if is_in_include || is_in_dir {
2695 return None; }
2697
2698 match local.as_str() {
2699 TAG_INCLUDE => {
2700 is_in_include = true;
2701 }
2702 TAG_DIR => {
2703 is_in_dir = true;
2704 }
2705 _ => continue,
2706 }
2707
2708 current_path = None;
2709 }
2710 Text { text, .. } => {
2711 let text = text.as_str().trim();
2712 if text.is_empty() {
2713 continue;
2714 }
2715 if is_in_include || is_in_dir {
2716 current_path = Some(text);
2717 }
2718 }
2719 Attribute { local, value, .. } => {
2720 if !is_in_include && !is_in_dir {
2721 continue;
2722 }
2723 if local.as_str() == ATTRIBUTE_PREFIX {
2725 current_prefix = Some(value.as_str());
2726 }
2727 }
2728 ElementEnd { end, .. } => {
2729 let end_tag = match end {
2730 xmlparser::ElementEnd::Close(_, a) => a,
2731 _ => continue,
2732 };
2733
2734 match end_tag.as_str() {
2735 TAG_INCLUDE => {
2736 if !is_in_include {
2737 continue;
2738 }
2739
2740 if let Some(current_path) = current_path.as_ref() {
2741 paths_to_visit.push((
2742 current_prefix.map(ToOwned::to_owned),
2743 PathBuf::from(*current_path),
2744 ));
2745 }
2746 }
2747 TAG_DIR => {
2748 if !is_in_dir {
2749 continue;
2750 }
2751
2752 if let Some(current_path) = current_path.as_ref() {
2753 font_paths.push((
2754 current_prefix.map(ToOwned::to_owned),
2755 (*current_path).to_owned(),
2756 ));
2757 }
2758 }
2759 _ => continue,
2760 }
2761
2762 is_in_include = false;
2763 is_in_dir = false;
2764 current_path = None;
2765 current_prefix = None;
2766 }
2767 _ => {}
2768 }
2769 }
2770
2771 Some(())
2772}
2773
2774#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
2786fn ParseFontsConfRenderConfig(
2787 input: &str,
2788 configs: &mut BTreeMap<String, FcFontRenderConfig>,
2789) {
2790 use xmlparser::Token::*;
2791 use xmlparser::Tokenizer;
2792
2793 #[derive(Clone, Copy, PartialEq)]
2795 enum State {
2796 Idle,
2798 InMatchFont,
2800 InTestFamily,
2802 InEdit,
2804 InValue,
2806 }
2807
2808 let mut state = State::Idle;
2809 let mut match_is_font_target = false;
2810 let mut current_family: Option<String> = None;
2811 let mut current_edit_name: Option<String> = None;
2812 let mut current_value: Option<String> = None;
2813 let mut value_tag: Option<String> = None;
2814 let mut config = FcFontRenderConfig::default();
2815 let mut in_test = false;
2816 let mut test_name: Option<String> = None;
2817
2818 for token_result in Tokenizer::from(input) {
2819 let token = match token_result {
2820 Ok(token) => token,
2821 Err(_) => continue,
2822 };
2823
2824 match token {
2825 ElementStart { local, .. } => {
2826 let tag = local.as_str();
2827 match tag {
2828 "match" => {
2829 match_is_font_target = false;
2831 current_family = None;
2832 config = FcFontRenderConfig::default();
2833 }
2834 "test" if state == State::InMatchFont => {
2835 in_test = true;
2836 test_name = None;
2837 }
2838 "edit" if state == State::InMatchFont => {
2839 current_edit_name = None;
2840 }
2841 "bool" | "double" | "const" | "string" | "int" => {
2842 if state == State::InTestFamily || state == State::InEdit {
2843 value_tag = Some(tag.to_owned());
2844 current_value = None;
2845 }
2846 }
2847 _ => {}
2848 }
2849 }
2850 Attribute { local, value, .. } => {
2851 let attr_name = local.as_str();
2852 let attr_value = value.as_str();
2853
2854 match attr_name {
2855 "target" => {
2856 if attr_value == "font" {
2857 match_is_font_target = true;
2858 }
2859 }
2860 "name" => {
2861 if in_test && state == State::InMatchFont {
2862 test_name = Some(attr_value.to_owned());
2863 } else if state == State::InMatchFont {
2864 current_edit_name = Some(attr_value.to_owned());
2865 }
2866 }
2867 _ => {}
2868 }
2869 }
2870 Text { text, .. } => {
2871 let text = text.as_str().trim();
2872 if !text.is_empty() && (state == State::InTestFamily || state == State::InEdit) {
2873 current_value = Some(text.to_owned());
2874 }
2875 }
2876 ElementEnd { end, .. } => {
2877 match end {
2878 xmlparser::ElementEnd::Open => {
2879 if match_is_font_target && state == State::Idle {
2881 state = State::InMatchFont;
2882 match_is_font_target = false;
2883 } else if in_test {
2884 if test_name.as_deref() == Some("family") {
2885 state = State::InTestFamily;
2886 }
2887 in_test = false;
2888 } else if current_edit_name.is_some() && state == State::InMatchFont {
2889 state = State::InEdit;
2890 }
2891 }
2892 xmlparser::ElementEnd::Close(_, local) => {
2893 let tag = local.as_str();
2894 match tag {
2895 "match" => {
2896 if let Some(family) = current_family.take() {
2898 let empty = FcFontRenderConfig::default();
2899 if config != empty {
2900 configs.insert(family, config.clone());
2901 }
2902 }
2903 state = State::Idle;
2904 config = FcFontRenderConfig::default();
2905 }
2906 "test" => {
2907 if state == State::InTestFamily {
2908 if let Some(ref val) = current_value {
2910 current_family = Some(val.clone());
2911 }
2912 state = State::InMatchFont;
2913 }
2914 current_value = None;
2915 value_tag = None;
2916 }
2917 "edit" => {
2918 if state == State::InEdit {
2919 if let (Some(ref name), Some(ref val)) = (¤t_edit_name, ¤t_value) {
2921 apply_edit_value(&mut config, name, val, value_tag.as_deref());
2922 }
2923 state = State::InMatchFont;
2924 }
2925 current_edit_name = None;
2926 current_value = None;
2927 value_tag = None;
2928 }
2929 "bool" | "double" | "const" | "string" | "int" => {
2930 }
2932 _ => {}
2933 }
2934 }
2935 xmlparser::ElementEnd::Empty => {
2936 }
2938 }
2939 }
2940 _ => {}
2941 }
2942 }
2943}
2944
2945#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
2947fn apply_edit_value(
2948 config: &mut FcFontRenderConfig,
2949 edit_name: &str,
2950 value: &str,
2951 value_tag: Option<&str>,
2952) {
2953 match edit_name {
2954 "antialias" => {
2955 config.antialias = parse_bool_value(value);
2956 }
2957 "hinting" => {
2958 config.hinting = parse_bool_value(value);
2959 }
2960 "autohint" => {
2961 config.autohint = parse_bool_value(value);
2962 }
2963 "embeddedbitmap" => {
2964 config.embeddedbitmap = parse_bool_value(value);
2965 }
2966 "embolden" => {
2967 config.embolden = parse_bool_value(value);
2968 }
2969 "minspace" => {
2970 config.minspace = parse_bool_value(value);
2971 }
2972 "hintstyle" => {
2973 config.hintstyle = parse_hintstyle_const(value);
2974 }
2975 "rgba" => {
2976 config.rgba = parse_rgba_const(value);
2977 }
2978 "lcdfilter" => {
2979 config.lcdfilter = parse_lcdfilter_const(value);
2980 }
2981 "dpi" => {
2982 if let Ok(v) = value.parse::<f64>() {
2983 config.dpi = Some(v);
2984 }
2985 }
2986 "scale" => {
2987 if let Ok(v) = value.parse::<f64>() {
2988 config.scale = Some(v);
2989 }
2990 }
2991 _ => {
2992 }
2994 }
2995}
2996
2997#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
2998fn parse_bool_value(value: &str) -> Option<bool> {
2999 match value {
3000 "true" => Some(true),
3001 "false" => Some(false),
3002 _ => None,
3003 }
3004}
3005
3006#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3007fn parse_hintstyle_const(value: &str) -> Option<FcHintStyle> {
3008 match value {
3009 "hintnone" => Some(FcHintStyle::None),
3010 "hintslight" => Some(FcHintStyle::Slight),
3011 "hintmedium" => Some(FcHintStyle::Medium),
3012 "hintfull" => Some(FcHintStyle::Full),
3013 _ => None,
3014 }
3015}
3016
3017#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3018fn parse_rgba_const(value: &str) -> Option<FcRgba> {
3019 match value {
3020 "unknown" => Some(FcRgba::Unknown),
3021 "rgb" => Some(FcRgba::Rgb),
3022 "bgr" => Some(FcRgba::Bgr),
3023 "vrgb" => Some(FcRgba::Vrgb),
3024 "vbgr" => Some(FcRgba::Vbgr),
3025 "none" => Some(FcRgba::None),
3026 _ => None,
3027 }
3028}
3029
3030#[cfg(all(feature = "std", feature = "parsing", target_os = "linux"))]
3031fn parse_lcdfilter_const(value: &str) -> Option<FcLcdFilter> {
3032 match value {
3033 "lcdnone" => Some(FcLcdFilter::None),
3034 "lcddefault" => Some(FcLcdFilter::Default),
3035 "lcdlight" => Some(FcLcdFilter::Light),
3036 "lcdlegacy" => Some(FcLcdFilter::Legacy),
3037 _ => None,
3038 }
3039}
3040
3041#[cfg(all(feature = "std", feature = "parsing"))]
3044const UNICODE_RANGE_MAPPINGS: &[(usize, u32, u32)] = &[
3045 (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), ];
3178
3179#[cfg(all(feature = "std", feature = "parsing"))]
3182struct ParsedFontFace {
3183 pattern: FcPattern,
3184 font_index: usize,
3185}
3186
3187#[cfg(all(feature = "std", feature = "parsing"))]
3193fn parse_font_faces(font_bytes: &[u8]) -> Option<Vec<ParsedFontFace>> {
3194 use allsorts::{
3195 binary::read::ReadScope,
3196 font_data::FontData,
3197 get_name::fontcode_get_name,
3198 post::PostTable,
3199 tables::{
3200 os2::Os2, HeadTable, NameTable,
3201 },
3202 tag,
3203 };
3204 use std::collections::BTreeSet;
3205
3206 const FONT_SPECIFIER_NAME_ID: u16 = 4;
3207 const FONT_SPECIFIER_FAMILY_ID: u16 = 1;
3208
3209 let max_fonts = if font_bytes.len() >= 12 && &font_bytes[0..4] == b"ttcf" {
3210 let num_fonts =
3212 u32::from_be_bytes([font_bytes[8], font_bytes[9], font_bytes[10], font_bytes[11]]);
3213 std::cmp::min(num_fonts as usize, 100)
3215 } else {
3216 1
3218 };
3219
3220 let scope = ReadScope::new(font_bytes);
3221 let font_file = scope.read::<FontData<'_>>().ok()?;
3222
3223 let mut results = Vec::new();
3225
3226 for font_index in 0..max_fonts {
3227 let provider = font_file.table_provider(font_index).ok()?;
3228 let head_data = provider.table_data(tag::HEAD).ok()??.into_owned();
3229 let head_table = ReadScope::new(&head_data).read::<HeadTable>().ok()?;
3230
3231 let is_bold = head_table.is_bold();
3232 let is_italic = head_table.is_italic();
3233 let mut detected_monospace = None;
3234
3235 let post_data = provider.table_data(tag::POST).ok()??;
3236 if let Ok(post_table) = ReadScope::new(&post_data).read::<PostTable>() {
3237 detected_monospace = Some(post_table.header.is_fixed_pitch != 0);
3239 }
3240
3241 let os2_data = provider.table_data(tag::OS_2).ok()??;
3243 let os2_table = ReadScope::new(&os2_data)
3244 .read_dep::<Os2>(os2_data.len())
3245 .ok()?;
3246
3247 let is_oblique = os2_table
3249 .fs_selection
3250 .contains(allsorts::tables::os2::FsSelection::OBLIQUE);
3251 let weight = FcWeight::from_u16(os2_table.us_weight_class);
3252 let stretch = FcStretch::from_u16(os2_table.us_width_class);
3253
3254 let mut unicode_ranges = Vec::new();
3258
3259 let os2_ranges = [
3261 os2_table.ul_unicode_range1,
3262 os2_table.ul_unicode_range2,
3263 os2_table.ul_unicode_range3,
3264 os2_table.ul_unicode_range4,
3265 ];
3266
3267 for &(bit, start, end) in UNICODE_RANGE_MAPPINGS {
3268 let range_idx = bit / 32;
3269 let bit_pos = bit % 32;
3270 if range_idx < 4 && (os2_ranges[range_idx] & (1 << bit_pos)) != 0 {
3271 unicode_ranges.push(UnicodeRange { start, end });
3272 }
3273 }
3274
3275 unicode_ranges = verify_unicode_ranges_with_cmap(&provider, unicode_ranges);
3279
3280 if unicode_ranges.is_empty() {
3282 if let Some(cmap_ranges) = analyze_cmap_coverage(&provider) {
3283 unicode_ranges = cmap_ranges;
3284 }
3285 }
3286
3287 let is_monospace = detect_monospace(&provider, &os2_table, detected_monospace)
3289 .unwrap_or(false);
3290
3291 let name_data = provider.table_data(tag::NAME).ok()??.into_owned();
3292 let name_table = ReadScope::new(&name_data).read::<NameTable>().ok()?;
3293
3294 let mut metadata = FcFontMetadata::default();
3296
3297 const NAME_ID_COPYRIGHT: u16 = 0;
3298 const NAME_ID_FAMILY: u16 = 1;
3299 const NAME_ID_SUBFAMILY: u16 = 2;
3300 const NAME_ID_UNIQUE_ID: u16 = 3;
3301 const NAME_ID_FULL_NAME: u16 = 4;
3302 const NAME_ID_VERSION: u16 = 5;
3303 const NAME_ID_POSTSCRIPT_NAME: u16 = 6;
3304 const NAME_ID_TRADEMARK: u16 = 7;
3305 const NAME_ID_MANUFACTURER: u16 = 8;
3306 const NAME_ID_DESIGNER: u16 = 9;
3307 const NAME_ID_DESCRIPTION: u16 = 10;
3308 const NAME_ID_VENDOR_URL: u16 = 11;
3309 const NAME_ID_DESIGNER_URL: u16 = 12;
3310 const NAME_ID_LICENSE: u16 = 13;
3311 const NAME_ID_LICENSE_URL: u16 = 14;
3312 const NAME_ID_PREFERRED_FAMILY: u16 = 16;
3313 const NAME_ID_PREFERRED_SUBFAMILY: u16 = 17;
3314
3315 metadata.copyright = get_name_string(&name_data, NAME_ID_COPYRIGHT);
3316 metadata.font_family = get_name_string(&name_data, NAME_ID_FAMILY);
3317 metadata.font_subfamily = get_name_string(&name_data, NAME_ID_SUBFAMILY);
3318 metadata.full_name = get_name_string(&name_data, NAME_ID_FULL_NAME);
3319 metadata.unique_id = get_name_string(&name_data, NAME_ID_UNIQUE_ID);
3320 metadata.version = get_name_string(&name_data, NAME_ID_VERSION);
3321 metadata.postscript_name = get_name_string(&name_data, NAME_ID_POSTSCRIPT_NAME);
3322 metadata.trademark = get_name_string(&name_data, NAME_ID_TRADEMARK);
3323 metadata.manufacturer = get_name_string(&name_data, NAME_ID_MANUFACTURER);
3324 metadata.designer = get_name_string(&name_data, NAME_ID_DESIGNER);
3325 metadata.id_description = get_name_string(&name_data, NAME_ID_DESCRIPTION);
3326 metadata.designer_url = get_name_string(&name_data, NAME_ID_DESIGNER_URL);
3327 metadata.manufacturer_url = get_name_string(&name_data, NAME_ID_VENDOR_URL);
3328 metadata.license = get_name_string(&name_data, NAME_ID_LICENSE);
3329 metadata.license_url = get_name_string(&name_data, NAME_ID_LICENSE_URL);
3330 metadata.preferred_family = get_name_string(&name_data, NAME_ID_PREFERRED_FAMILY);
3331 metadata.preferred_subfamily = get_name_string(&name_data, NAME_ID_PREFERRED_SUBFAMILY);
3332
3333 let mut f_family = None;
3335
3336 let patterns = name_table
3337 .name_records
3338 .iter()
3339 .filter_map(|name_record| {
3340 let name_id = name_record.name_id;
3341 if name_id == FONT_SPECIFIER_FAMILY_ID {
3342 if let Ok(Some(family)) =
3343 fontcode_get_name(&name_data, FONT_SPECIFIER_FAMILY_ID)
3344 {
3345 f_family = Some(family);
3346 }
3347 None
3348 } else if name_id == FONT_SPECIFIER_NAME_ID {
3349 let family = f_family.as_ref()?;
3350 let name = fontcode_get_name(&name_data, FONT_SPECIFIER_NAME_ID).ok()??;
3351 if name.to_bytes().is_empty() {
3352 None
3353 } else {
3354 let mut name_str =
3355 String::from_utf8_lossy(name.to_bytes()).to_string();
3356 let mut family_str =
3357 String::from_utf8_lossy(family.as_bytes()).to_string();
3358 if name_str.starts_with('.') {
3359 name_str = name_str[1..].to_string();
3360 }
3361 if family_str.starts_with('.') {
3362 family_str = family_str[1..].to_string();
3363 }
3364 Some((
3365 FcPattern {
3366 name: Some(name_str),
3367 family: Some(family_str),
3368 bold: if is_bold {
3369 PatternMatch::True
3370 } else {
3371 PatternMatch::False
3372 },
3373 italic: if is_italic {
3374 PatternMatch::True
3375 } else {
3376 PatternMatch::False
3377 },
3378 oblique: if is_oblique {
3379 PatternMatch::True
3380 } else {
3381 PatternMatch::False
3382 },
3383 monospace: if is_monospace {
3384 PatternMatch::True
3385 } else {
3386 PatternMatch::False
3387 },
3388 condensed: if stretch <= FcStretch::Condensed {
3389 PatternMatch::True
3390 } else {
3391 PatternMatch::False
3392 },
3393 weight,
3394 stretch,
3395 unicode_ranges: unicode_ranges.clone(),
3396 metadata: metadata.clone(),
3397 render_config: FcFontRenderConfig::default(),
3398 },
3399 font_index,
3400 ))
3401 }
3402 } else {
3403 None
3404 }
3405 })
3406 .collect::<BTreeSet<_>>();
3407
3408 results.extend(patterns.into_iter().map(|(pat, idx)| ParsedFontFace {
3409 pattern: pat,
3410 font_index: idx,
3411 }));
3412 }
3413
3414 if results.is_empty() {
3415 None
3416 } else {
3417 Some(results)
3418 }
3419}
3420
3421#[cfg(all(feature = "std", feature = "parsing"))]
3423pub(crate) fn FcParseFont(filepath: &PathBuf) -> Option<Vec<(FcPattern, FcFontPath)>> {
3424 #[cfg(all(not(target_family = "wasm"), feature = "std"))]
3425 use mmapio::MmapOptions;
3426 use std::fs::File;
3427
3428 let file = File::open(filepath).ok()?;
3430
3431 #[cfg(all(not(target_family = "wasm"), feature = "std"))]
3432 let font_bytes = unsafe { MmapOptions::new().map(&file).ok()? };
3433
3434 #[cfg(not(all(not(target_family = "wasm"), feature = "std")))]
3435 let font_bytes = std::fs::read(filepath).ok()?;
3436
3437 let faces = parse_font_faces(&font_bytes[..])?;
3438 let path_str = filepath.to_string_lossy().to_string();
3439
3440 Some(
3441 faces
3442 .into_iter()
3443 .map(|face| {
3444 (
3445 face.pattern,
3446 FcFontPath {
3447 path: path_str.clone(),
3448 font_index: face.font_index,
3449 },
3450 )
3451 })
3452 .collect(),
3453 )
3454}
3455
3456#[cfg(all(feature = "std", feature = "parsing"))]
3482#[allow(non_snake_case)]
3483pub fn FcParseFontBytes(font_bytes: &[u8], font_id: &str) -> Option<Vec<(FcPattern, FcFont)>> {
3484 FcParseFontBytesInner(font_bytes, font_id)
3485}
3486
3487#[cfg(all(feature = "std", feature = "parsing"))]
3490fn FcParseFontBytesInner(font_bytes: &[u8], font_id: &str) -> Option<Vec<(FcPattern, FcFont)>> {
3491 let faces = parse_font_faces(font_bytes)?;
3492 let id = font_id.to_string();
3493 let bytes = font_bytes.to_vec();
3494
3495 Some(
3496 faces
3497 .into_iter()
3498 .map(|face| {
3499 (
3500 face.pattern,
3501 FcFont {
3502 bytes: bytes.clone(),
3503 font_index: face.font_index,
3504 id: id.clone(),
3505 },
3506 )
3507 })
3508 .collect(),
3509 )
3510}
3511
3512#[cfg(all(feature = "std", feature = "parsing"))]
3513fn FcScanDirectoriesInner(paths: &[(Option<String>, String)]) -> Vec<(FcPattern, FcFontPath)> {
3514 #[cfg(feature = "multithreading")]
3515 {
3516 use rayon::prelude::*;
3517
3518 paths
3520 .par_iter()
3521 .filter_map(|(prefix, p)| {
3522 process_path(prefix, PathBuf::from(p), false).map(FcScanSingleDirectoryRecursive)
3523 })
3524 .flatten()
3525 .collect()
3526 }
3527 #[cfg(not(feature = "multithreading"))]
3528 {
3529 paths
3530 .iter()
3531 .filter_map(|(prefix, p)| {
3532 process_path(prefix, PathBuf::from(p), false).map(FcScanSingleDirectoryRecursive)
3533 })
3534 .flatten()
3535 .collect()
3536 }
3537}
3538
3539#[cfg(all(feature = "std", feature = "parsing"))]
3540fn FcScanSingleDirectoryRecursive(dir: PathBuf) -> Vec<(FcPattern, FcFontPath)> {
3541 let mut files_to_parse = Vec::new();
3542 let mut dirs_to_parse = vec![dir];
3543
3544 'outer: loop {
3545 let mut new_dirs_to_parse = Vec::new();
3546
3547 'inner: for dir in dirs_to_parse.clone() {
3548 let dir = match std::fs::read_dir(dir) {
3549 Ok(o) => o,
3550 Err(_) => continue 'inner,
3551 };
3552
3553 for (path, pathbuf) in dir.filter_map(|entry| {
3554 let entry = entry.ok()?;
3555 let path = entry.path();
3556 let pathbuf = path.to_path_buf();
3557 Some((path, pathbuf))
3558 }) {
3559 if path.is_dir() {
3560 new_dirs_to_parse.push(pathbuf);
3561 } else {
3562 files_to_parse.push(pathbuf);
3563 }
3564 }
3565 }
3566
3567 if new_dirs_to_parse.is_empty() {
3568 break 'outer;
3569 } else {
3570 dirs_to_parse = new_dirs_to_parse;
3571 }
3572 }
3573
3574 FcParseFontFiles(&files_to_parse)
3575}
3576
3577#[cfg(all(feature = "std", feature = "parsing"))]
3578fn FcParseFontFiles(files_to_parse: &[PathBuf]) -> Vec<(FcPattern, FcFontPath)> {
3579 let result = {
3580 #[cfg(feature = "multithreading")]
3581 {
3582 use rayon::prelude::*;
3583
3584 files_to_parse
3585 .par_iter()
3586 .filter_map(|file| FcParseFont(file))
3587 .collect::<Vec<Vec<_>>>()
3588 }
3589 #[cfg(not(feature = "multithreading"))]
3590 {
3591 files_to_parse
3592 .iter()
3593 .filter_map(|file| FcParseFont(file))
3594 .collect::<Vec<Vec<_>>>()
3595 }
3596 };
3597
3598 result.into_iter().flat_map(|f| f.into_iter()).collect()
3599}
3600
3601#[cfg(all(feature = "std", feature = "parsing"))]
3602fn process_path(
3606 prefix: &Option<String>,
3607 mut path: PathBuf,
3608 is_include_path: bool,
3609) -> Option<PathBuf> {
3610 use std::env::var;
3611
3612 const HOME_SHORTCUT: &str = "~";
3613 const CWD_PATH: &str = ".";
3614
3615 const HOME_ENV_VAR: &str = "HOME";
3616 const XDG_CONFIG_HOME_ENV_VAR: &str = "XDG_CONFIG_HOME";
3617 const XDG_CONFIG_HOME_DEFAULT_PATH_SUFFIX: &str = ".config";
3618 const XDG_DATA_HOME_ENV_VAR: &str = "XDG_DATA_HOME";
3619 const XDG_DATA_HOME_DEFAULT_PATH_SUFFIX: &str = ".local/share";
3620
3621 const PREFIX_CWD: &str = "cwd";
3622 const PREFIX_DEFAULT: &str = "default";
3623 const PREFIX_XDG: &str = "xdg";
3624
3625 fn get_home_value() -> Option<PathBuf> {
3627 var(HOME_ENV_VAR).ok().map(PathBuf::from)
3628 }
3629 fn get_xdg_config_home_value() -> Option<PathBuf> {
3630 var(XDG_CONFIG_HOME_ENV_VAR)
3631 .ok()
3632 .map(PathBuf::from)
3633 .or_else(|| {
3634 get_home_value()
3635 .map(|home_path| home_path.join(XDG_CONFIG_HOME_DEFAULT_PATH_SUFFIX))
3636 })
3637 }
3638 fn get_xdg_data_home_value() -> Option<PathBuf> {
3639 var(XDG_DATA_HOME_ENV_VAR)
3640 .ok()
3641 .map(PathBuf::from)
3642 .or_else(|| {
3643 get_home_value().map(|home_path| home_path.join(XDG_DATA_HOME_DEFAULT_PATH_SUFFIX))
3644 })
3645 }
3646
3647 if path.starts_with(HOME_SHORTCUT) {
3649 if let Some(home_path) = get_home_value() {
3650 path = home_path.join(
3651 path.strip_prefix(HOME_SHORTCUT)
3652 .expect("already checked that it starts with the prefix"),
3653 );
3654 } else {
3655 return None;
3656 }
3657 }
3658
3659 match prefix {
3661 Some(prefix) => match prefix.as_str() {
3662 PREFIX_CWD | PREFIX_DEFAULT => {
3663 let mut new_path = PathBuf::from(CWD_PATH);
3664 new_path.push(path);
3665
3666 Some(new_path)
3667 }
3668 PREFIX_XDG => {
3669 if is_include_path {
3670 get_xdg_config_home_value()
3671 .map(|xdg_config_home_path| xdg_config_home_path.join(path))
3672 } else {
3673 get_xdg_data_home_value()
3674 .map(|xdg_data_home_path| xdg_data_home_path.join(path))
3675 }
3676 }
3677 _ => None, },
3679 None => Some(path),
3680 }
3681}
3682
3683#[cfg(all(feature = "std", feature = "parsing"))]
3685fn get_name_string(name_data: &[u8], name_id: u16) -> Option<String> {
3686 fontcode_get_name(name_data, name_id)
3687 .ok()
3688 .flatten()
3689 .map(|name| String::from_utf8_lossy(name.to_bytes()).to_string())
3690}
3691
3692#[cfg(all(feature = "std", feature = "parsing"))]
3696fn get_verification_codepoints(start: u32, end: u32) -> Vec<u32> {
3697 match start {
3698 0x0000 => vec!['A' as u32, 'M' as u32, 'Z' as u32, 'a' as u32, 'm' as u32, 'z' as u32],
3700 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], _ => {
3776 let range_size = end - start;
3777 if range_size > 20 {
3778 vec![
3779 start + range_size / 5,
3780 start + 2 * range_size / 5,
3781 start + 3 * range_size / 5,
3782 start + 4 * range_size / 5,
3783 ]
3784 } else {
3785 vec![start, start + range_size / 2]
3786 }
3787 }
3788 }
3789}
3790
3791#[cfg(all(feature = "std", feature = "parsing"))]
3794fn find_best_cmap_subtable<'a>(
3795 cmap: &allsorts::tables::cmap::Cmap<'a>,
3796) -> Option<allsorts::tables::cmap::EncodingRecord> {
3797 use allsorts::tables::cmap::{PlatformId, EncodingId};
3798
3799 cmap.find_subtable(PlatformId::UNICODE, EncodingId(3))
3800 .or_else(|| cmap.find_subtable(PlatformId::UNICODE, EncodingId(4)))
3801 .or_else(|| cmap.find_subtable(PlatformId::WINDOWS, EncodingId(1)))
3802 .or_else(|| cmap.find_subtable(PlatformId::WINDOWS, EncodingId(10)))
3803 .or_else(|| cmap.find_subtable(PlatformId::UNICODE, EncodingId(0)))
3804 .or_else(|| cmap.find_subtable(PlatformId::UNICODE, EncodingId(1)))
3805}
3806
3807#[cfg(all(feature = "std", feature = "parsing"))]
3810fn verify_unicode_ranges_with_cmap(
3811 provider: &impl FontTableProvider,
3812 os2_ranges: Vec<UnicodeRange>
3813) -> Vec<UnicodeRange> {
3814 use allsorts::tables::cmap::{Cmap, CmapSubtable};
3815
3816 if os2_ranges.is_empty() {
3817 return Vec::new();
3818 }
3819
3820 let cmap_data = match provider.table_data(tag::CMAP) {
3822 Ok(Some(data)) => data,
3823 _ => return os2_ranges, };
3825
3826 let cmap = match ReadScope::new(&cmap_data).read::<Cmap<'_>>() {
3827 Ok(c) => c,
3828 Err(_) => return os2_ranges,
3829 };
3830
3831 let encoding_record = match find_best_cmap_subtable(&cmap) {
3832 Some(r) => r,
3833 None => return os2_ranges, };
3835
3836 let cmap_subtable = match ReadScope::new(&cmap_data)
3837 .offset(encoding_record.offset as usize)
3838 .read::<CmapSubtable<'_>>()
3839 {
3840 Ok(st) => st,
3841 Err(_) => return os2_ranges,
3842 };
3843
3844 let mut verified_ranges = Vec::new();
3846
3847 for range in os2_ranges {
3848 let test_codepoints = get_verification_codepoints(range.start, range.end);
3849
3850 let required_hits = (test_codepoints.len() + 1) / 2; let mut hits = 0;
3854
3855 for cp in test_codepoints {
3856 if cp >= range.start && cp <= range.end {
3857 if let Ok(Some(gid)) = cmap_subtable.map_glyph(cp) {
3858 if gid != 0 {
3859 hits += 1;
3860 if hits >= required_hits {
3861 break;
3862 }
3863 }
3864 }
3865 }
3866 }
3867
3868 if hits >= required_hits {
3869 verified_ranges.push(range);
3870 }
3871 }
3872
3873 verified_ranges
3874}
3875
3876#[cfg(all(feature = "std", feature = "parsing"))]
3879fn analyze_cmap_coverage(provider: &impl FontTableProvider) -> Option<Vec<UnicodeRange>> {
3880 use allsorts::tables::cmap::{Cmap, CmapSubtable};
3881
3882 let cmap_data = provider.table_data(tag::CMAP).ok()??;
3883 let cmap = ReadScope::new(&cmap_data).read::<Cmap<'_>>().ok()?;
3884
3885 let encoding_record = find_best_cmap_subtable(&cmap)?;
3886
3887 let cmap_subtable = ReadScope::new(&cmap_data)
3888 .offset(encoding_record.offset as usize)
3889 .read::<CmapSubtable<'_>>()
3890 .ok()?;
3891
3892 let blocks_to_check: &[(u32, u32)] = &[
3894 (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), ];
3945
3946 let mut ranges = Vec::new();
3947
3948 for &(start, end) in blocks_to_check {
3949 let test_codepoints = get_verification_codepoints(start, end);
3950 let required_hits = (test_codepoints.len() + 1) / 2;
3951 let mut hits = 0;
3952
3953 for cp in test_codepoints {
3954 if let Ok(Some(gid)) = cmap_subtable.map_glyph(cp) {
3955 if gid != 0 {
3956 hits += 1;
3957 if hits >= required_hits {
3958 break;
3959 }
3960 }
3961 }
3962 }
3963
3964 if hits >= required_hits {
3965 ranges.push(UnicodeRange { start, end });
3966 }
3967 }
3968
3969 if ranges.is_empty() {
3970 None
3971 } else {
3972 Some(ranges)
3973 }
3974}
3975
3976#[cfg(all(feature = "std", feature = "parsing"))]
3978#[allow(dead_code)]
3979fn extract_unicode_ranges(os2_table: &Os2) -> Vec<UnicodeRange> {
3980 let mut unicode_ranges = Vec::new();
3981
3982 let ranges = [
3983 os2_table.ul_unicode_range1,
3984 os2_table.ul_unicode_range2,
3985 os2_table.ul_unicode_range3,
3986 os2_table.ul_unicode_range4,
3987 ];
3988
3989 for &(bit, start, end) in UNICODE_RANGE_MAPPINGS {
3990 let range_idx = bit / 32;
3991 let bit_pos = bit % 32;
3992 if range_idx < 4 && (ranges[range_idx] & (1 << bit_pos)) != 0 {
3993 unicode_ranges.push(UnicodeRange { start, end });
3994 }
3995 }
3996
3997 unicode_ranges
3998}
3999
4000#[cfg(all(feature = "std", feature = "parsing"))]
4002fn detect_monospace(
4003 provider: &impl FontTableProvider,
4004 os2_table: &Os2,
4005 detected_monospace: Option<bool>,
4006) -> Option<bool> {
4007 if let Some(is_monospace) = detected_monospace {
4008 return Some(is_monospace);
4009 }
4010
4011 if os2_table.panose[0] == 2 {
4013 return Some(os2_table.panose[3] == 9); }
4016
4017 let hhea_data = provider.table_data(tag::HHEA).ok()??;
4019 let hhea_table = ReadScope::new(&hhea_data).read::<HheaTable>().ok()?;
4020 let maxp_data = provider.table_data(tag::MAXP).ok()??;
4021 let maxp_table = ReadScope::new(&maxp_data).read::<MaxpTable>().ok()?;
4022 let hmtx_data = provider.table_data(tag::HMTX).ok()??;
4023 let hmtx_table = ReadScope::new(&hmtx_data)
4024 .read_dep::<HmtxTable<'_>>((
4025 usize::from(maxp_table.num_glyphs),
4026 usize::from(hhea_table.num_h_metrics),
4027 ))
4028 .ok()?;
4029
4030 let mut monospace = true;
4031 let mut last_advance = 0;
4032
4033 for i in 0..hhea_table.num_h_metrics as usize {
4035 let advance = hmtx_table.h_metrics.read_item(i).ok()?.advance_width;
4036 if i > 0 && advance != last_advance {
4037 monospace = false;
4038 break;
4039 }
4040 last_advance = advance;
4041 }
4042
4043 Some(monospace)
4044}