1use std::any::Any;
2use std::cell::RefCell;
3use std::cmp::Ordering;
4use std::collections::{BTreeMap, HashMap};
5use std::io::Read;
6use std::rc::Rc;
7
8use crate::media::fonts::font_family_loader::FontFamilyLoader;
9use crate::media::fonts::{FontCollectionKey, FontFallbackScriptHints, IFontCollection};
10use crate::media::text_formatting::unicode::{Codepoint, Script};
11use crate::media::{FontFamily, FontSimulations, FontStretch, FontStyle, FontWeight, GlyphTypeface, Typeface};
12use crate::platform::{IAssetLoader, IFontManagerImpl, ASSET_SCHEME};
13use crate::utilities::{CultureInfo, Uri};
14use crate::{FerroLocator, LocatorExtensions};
15
16pub type GlyphTypefaceMap = BTreeMap<FontCollectionKey, Option<Rc<GlyphTypeface>>>;
20
21fn to_upper_ordinal(c: char) -> char {
24 if c.is_ascii() {
25 return c.to_ascii_uppercase();
26 }
27
28 let mut upper = c.to_uppercase();
29
30 match (upper.next(), upper.next()) {
31 (Some(single), None) => single,
32 _ => c,
33 }
34}
35
36fn utf16_order_key(c: char) -> (u32, u32) {
38 let value = c as u32;
39
40 if value >= 0x10000 {
41 (0xD800 + ((value - 0x10000) >> 10), value)
42 } else {
43 (value, 0)
44 }
45}
46
47pub(crate) fn compare_ordinal_ignore_case(a: &str, b: &str) -> Ordering {
49 let mut a = a.chars();
50 let mut b = b.chars();
51
52 loop {
53 match (a.next(), b.next()) {
54 (None, None) => return Ordering::Equal,
55 (None, Some(_)) => return Ordering::Less,
56 (Some(_), None) => return Ordering::Greater,
57 (Some(x), Some(y)) => {
58 if x == y {
59 continue;
60 }
61
62 let (x, y) = (to_upper_ordinal(x), to_upper_ordinal(y));
63
64 if x != y {
65 return utf16_order_key(x).cmp(&utf16_order_key(y));
66 }
67 }
68 }
69 }
70}
71
72pub(crate) fn equals_ordinal_ignore_case(a: &str, b: &str) -> bool {
74 compare_ordinal_ignore_case(a, b) == Ordering::Equal
75}
76
77pub(crate) fn starts_with_ordinal_ignore_case(value: &str, prefix: &str) -> bool {
79 let mut value = value.chars();
80
81 for expected in prefix.chars() {
82 match value.next() {
83 Some(actual) if actual == expected || to_upper_ordinal(actual) == to_upper_ordinal(expected) => {}
84 _ => return false,
85 }
86 }
87
88 true
89}
90
91#[derive(Default)]
96pub struct GlyphTypefaceCache {
97 entries: RefCell<Vec<(String, Rc<RefCell<GlyphTypefaceMap>>)>>,
98}
99
100impl GlyphTypefaceCache {
101 fn search(entries: &[(String, Rc<RefCell<GlyphTypefaceMap>>)], family_name: &str) -> Result<usize, usize> {
102 entries.binary_search_by(|(name, _)| compare_ordinal_ignore_case(name, family_name))
103 }
104
105 pub fn try_get_value(&self, family_name: &str) -> Option<Rc<RefCell<GlyphTypefaceMap>>> {
107 let entries = self.entries.borrow();
108
109 Self::search(&entries, family_name).ok().map(|index| entries[index].1.clone())
110 }
111
112 fn get_or_add(&self, family_name: &str) -> (Rc<RefCell<GlyphTypefaceMap>>, bool) {
115 let mut entries = self.entries.borrow_mut();
116
117 match Self::search(&entries, family_name) {
118 Ok(index) => (entries[index].1.clone(), false),
119 Err(index) => {
120 let map = Rc::new(RefCell::new(GlyphTypefaceMap::new()));
121 entries.insert(index, (family_name.to_owned(), map.clone()));
122 (map, true)
123 }
124 }
125 }
126
127 pub fn contains_key(&self, family_name: &str) -> bool {
129 Self::search(&self.entries.borrow(), family_name).is_ok()
130 }
131
132 pub fn count(&self) -> usize {
134 self.entries.borrow().len()
135 }
136
137 pub fn keys(&self) -> Vec<String> {
139 self.entries.borrow().iter().map(|(name, _)| name.clone()).collect()
140 }
141
142 fn values(&self) -> Vec<Rc<RefCell<GlyphTypefaceMap>>> {
143 self.entries.borrow().iter().map(|(_, map)| map.clone()).collect()
144 }
145}
146
147type ScriptFallbackKey = (Script, Option<CultureInfo>);
149
150pub struct FontCollectionBase {
161 glyph_typeface_cache: GlyphTypefaceCache,
162
163 script_fallback_cache: RefCell<HashMap<ScriptFallbackKey, Option<String>>>,
168
169 font_families: RefCell<Vec<FontFamily>>,
170 font_manager_impl: Rc<dyn IFontManagerImpl>,
171 asset_loader: Option<Rc<dyn IAssetLoader>>,
172}
173
174pub trait FontCollectionBaseImpl: Sized + 'static {
181 fn base(this: &Self) -> &FontCollectionBase;
183
184 fn key(this: &Self) -> Uri;
186
187 fn try_match_character(
190 this: &Self,
191 codepoint: i32,
192 style: FontStyle,
193 weight: FontWeight,
194 stretch: FontStretch,
195 family_name: Option<&str>,
196 culture: Option<&CultureInfo>,
197 ) -> Option<Typeface> {
198 FontCollectionBase::try_match_character(this, codepoint, style, weight, stretch, family_name, culture)
199 }
200
201 fn try_match_character_from_platform(
206 _this: &Self,
207 _codepoint: i32,
208 _key: FontCollectionKey,
209 _family_name: Option<&str>,
210 _culture: Option<&CultureInfo>,
211 ) -> Option<Rc<GlyphTypeface>> {
212 None
213 }
214
215 fn try_create_synthetic_glyph_typeface(
217 this: &Self,
218 glyph_typeface: &Rc<GlyphTypeface>,
219 style: FontStyle,
220 weight: FontWeight,
221 stretch: FontStretch,
222 ) -> Option<Rc<GlyphTypeface>> {
223 FontCollectionBase::try_create_synthetic_glyph_typeface(this, glyph_typeface, style, weight, stretch)
224 }
225
226 fn try_get_glyph_typeface(
228 this: &Self,
229 family_name: &str,
230 style: FontStyle,
231 weight: FontWeight,
232 stretch: FontStretch,
233 ) -> Option<Rc<GlyphTypeface>> {
234 FontCollectionBase::try_get_glyph_typeface(this, family_name, style, weight, stretch)
235 }
236
237 fn try_get_family_typefaces(this: &Self, family_name: &str) -> Option<Vec<Typeface>> {
239 FontCollectionBase::try_get_family_typefaces(this, family_name)
240 }
241}
242
243pub trait IFontCollectionBase: IFontCollection {
247 fn base(&self) -> &FontCollectionBase;
249
250 fn try_match_character_from_platform(
253 &self,
254 codepoint: i32,
255 key: FontCollectionKey,
256 family_name: Option<&str>,
257 culture: Option<&CultureInfo>,
258 ) -> Option<Rc<GlyphTypeface>>;
259}
260
261impl<T: FontCollectionBaseImpl> IFontCollectionBase for T {
262 fn base(&self) -> &FontCollectionBase {
263 T::base(self)
264 }
265
266 fn try_match_character_from_platform(
267 &self,
268 codepoint: i32,
269 key: FontCollectionKey,
270 family_name: Option<&str>,
271 culture: Option<&CultureInfo>,
272 ) -> Option<Rc<GlyphTypeface>> {
273 T::try_match_character_from_platform(self, codepoint, key, family_name, culture)
274 }
275}
276
277impl<T: FontCollectionBaseImpl> IFontCollection for T {
278 fn key(&self) -> Uri {
279 T::key(self)
280 }
281
282 fn count(&self) -> usize {
283 T::base(self).count()
284 }
285
286 fn get(&self, index: usize) -> FontFamily {
287 T::base(self).get(index)
288 }
289
290 fn font_families(&self) -> Vec<FontFamily> {
291 T::base(self).font_families()
292 }
293
294 fn try_get_glyph_typeface(
295 &self,
296 family_name: &str,
297 style: FontStyle,
298 weight: FontWeight,
299 stretch: FontStretch,
300 ) -> Option<Rc<GlyphTypeface>> {
301 T::try_get_glyph_typeface(self, family_name, style, weight, stretch)
302 }
303
304 fn try_match_character(
305 &self,
306 codepoint: i32,
307 font_style: FontStyle,
308 font_weight: FontWeight,
309 font_stretch: FontStretch,
310 family_name: Option<&str>,
311 culture: Option<&CultureInfo>,
312 ) -> Option<Typeface> {
313 T::try_match_character(self, codepoint, font_style, font_weight, font_stretch, family_name, culture)
314 }
315
316 fn try_get_family_typefaces(&self, family_name: &str) -> Option<Vec<Typeface>> {
317 T::try_get_family_typefaces(self, family_name)
318 }
319
320 fn try_create_synthetic_glyph_typeface(
321 &self,
322 glyph_typeface: &Rc<GlyphTypeface>,
323 style: FontStyle,
324 weight: FontWeight,
325 stretch: FontStretch,
326 ) -> Option<Rc<GlyphTypeface>> {
327 T::try_create_synthetic_glyph_typeface(self, glyph_typeface, style, weight, stretch)
328 }
329
330 fn try_get_nearest_match(
331 &self,
332 family_name: &str,
333 style: FontStyle,
334 weight: FontWeight,
335 stretch: FontStretch,
336 ) -> Option<Rc<GlyphTypeface>> {
337 T::base(self).try_get_nearest_match(family_name, style, weight, stretch)
338 }
339
340 fn dispose(&self) {
341 T::base(self).dispose();
342 }
343
344 fn as_font_collection_base(&self) -> Option<&dyn IFontCollectionBase> {
345 Some(self)
346 }
347
348 fn as_any(&self) -> &dyn Any {
349 self
350 }
351}
352
353impl Default for FontCollectionBase {
354 fn default() -> Self {
355 Self::new()
356 }
357}
358
359impl FontCollectionBase {
360 pub fn new() -> Self {
365 Self {
366 glyph_typeface_cache: GlyphTypefaceCache::default(),
367 script_fallback_cache: RefCell::new(HashMap::new()),
368 font_families: RefCell::new(Vec::new()),
369 font_manager_impl: FerroLocator::current().get_required_service::<dyn IFontManagerImpl>(),
370 asset_loader: FerroLocator::current().get_service::<dyn IAssetLoader>(),
371 }
372 }
373
374 pub fn glyph_typeface_cache(&self) -> &GlyphTypefaceCache {
376 &self.glyph_typeface_cache
377 }
378
379 pub fn count(&self) -> usize {
381 self.font_families.borrow().len()
382 }
383
384 pub fn get(&self, index: usize) -> FontFamily {
388 self.font_families.borrow()[index].clone()
389 }
390
391 pub fn font_families(&self) -> Vec<FontFamily> {
393 self.font_families.borrow().clone()
394 }
395
396 pub fn try_match_character(
399 this: &dyn IFontCollectionBase,
400 codepoint: i32,
401 style: FontStyle,
402 weight: FontWeight,
403 stretch: FontStretch,
404 family_name: Option<&str>,
405 culture: Option<&CultureInfo>,
406 ) -> Option<Typeface> {
407 Self::try_match_character_with_script(
408 this,
409 codepoint,
410 style,
411 weight,
412 stretch,
413 family_name,
414 culture,
415 Script::Unknown,
416 )
417 }
418
419 #[allow(clippy::too_many_arguments)]
425 pub(crate) fn try_match_character_with_script(
426 this: &dyn IFontCollectionBase,
427 codepoint: i32,
428 style: FontStyle,
429 weight: FontWeight,
430 stretch: FontStretch,
431 family_name: Option<&str>,
432 culture: Option<&CultureInfo>,
433 shaping_script: Script,
434 ) -> Option<Typeface> {
435 let base = this.base();
436
437 let key = FontCollectionKey::new(style, weight, stretch);
438 let cp = Codepoint::new(codepoint as u32);
439 let script = cp.script();
440 let refined_script = FontFallbackScriptHints::refine_with_culture(cp, culture);
441 let script_key: ScriptFallbackKey = (refined_script, culture.cloned());
442
443 if let Some(family_name) = family_name {
445 if let Some(requested_family) = base.glyph_typeface_cache.try_get_value(family_name) {
446 if let Some(requested_glyph_typeface) = Self::try_get_covering_match_for_family(
447 this,
448 &requested_family,
449 key,
450 codepoint,
451 culture,
452 shaping_script,
453 ) {
454 if Self::is_culture_compatible(&requested_glyph_typeface, culture, script) {
455 return Some(Self::build_typeface_with_synthesis(this, &requested_glyph_typeface, key));
456 }
457 }
458 }
459 }
460
461 if FontFallbackScriptHints::is_locale_sensitive(refined_script) || culture.is_some() {
463 let cached_family = base.script_fallback_cache.borrow().get(&script_key).cloned().flatten();
464
465 if let Some(cached_family) = cached_family {
466 if !family_name.is_some_and(|family_name| equals_ordinal_ignore_case(&cached_family, family_name)) {
467 if let Some(cached_typefaces) = base.glyph_typeface_cache.try_get_value(&cached_family) {
468 if let Some(cached_glyph_typeface) = Self::try_get_covering_match_for_family(
469 this,
470 &cached_typefaces,
471 key,
472 codepoint,
473 culture,
474 shaping_script,
475 ) {
476 return Some(Self::build_typeface_with_synthesis(this, &cached_glyph_typeface, key));
477 }
478 }
479 }
480 }
481 }
482
483 if let Some((best_gt, best_family_name)) = base.try_match_in_cache(
485 codepoint,
486 key,
487 family_name,
488 culture,
489 script,
490 refined_script,
491 shaping_script,
492 false,
493 ) {
494 let best_gt = Self::prefer_exact_key(this, best_gt, key, codepoint, culture, shaping_script);
499
500 if FontFallbackScriptHints::is_locale_sensitive(refined_script) || culture.is_some() {
501 base.script_fallback_cache.borrow_mut().entry(script_key).or_insert(Some(best_family_name));
502 }
503
504 return Some(Self::build_typeface_with_synthesis(this, &best_gt, key));
505 }
506
507 if shaping_script == Script::Unknown {
518 let cache_entry = base.script_fallback_cache.borrow().get(&script_key).cloned();
519 let has_cache_entry = cache_entry.is_some();
520 let platform_already_attempted = matches!(cache_entry, Some(None));
521
522 if !platform_already_attempted {
523 if let Some(platform_gt) =
524 this.try_match_character_from_platform(codepoint, key, family_name, culture)
525 {
526 base.script_fallback_cache
530 .borrow_mut()
531 .entry(script_key)
532 .or_insert_with(|| Some(platform_gt.family_name().to_owned()));
533
534 return Some(Self::build_typeface_with_synthesis(this, &platform_gt, key));
535 }
536 }
537
538 if !has_cache_entry {
541 base.script_fallback_cache.borrow_mut().entry(script_key).or_insert(None);
542 }
543 }
544
545 if let Some((lr_gt, _)) = base.try_match_in_cache(
547 codepoint,
548 key,
549 family_name,
550 culture,
551 script,
552 refined_script,
553 shaping_script,
554 true,
555 ) {
556 return Some(Self::build_typeface_with_synthesis(this, &lr_gt, key));
557 }
558
559 None
560 }
561
562 #[allow(clippy::too_many_arguments)]
563 fn try_match_in_cache(
564 &self,
565 codepoint: i32,
566 key: FontCollectionKey,
567 skip_family_name: Option<&str>,
568 culture: Option<&CultureInfo>,
569 script: Script,
570 refined_script: Script,
571 shaping_script: Script,
572 is_last_resort: bool,
573 ) -> Option<(Rc<GlyphTypeface>, String)> {
574 let mut best: Option<(Rc<GlyphTypeface>, String)> = None;
575
576 let snapshot = self.font_families.borrow();
578 let mut best_score = i32::MIN;
579
580 for font_family in snapshot.iter() {
581 let family_name = font_family.name();
582
583 if skip_family_name.is_some_and(|skip| equals_ordinal_ignore_case(family_name, skip)) {
584 continue;
585 }
586
587 let Some(glyph_typefaces) = self.glyph_typeface_cache.try_get_value(family_name) else {
588 continue;
589 };
590
591 let Some(candidate) = Self::try_get_covering_match(
592 &glyph_typefaces.borrow(),
593 key,
594 codepoint,
595 is_last_resort,
596 shaping_script,
597 ) else {
598 continue;
599 };
600
601 let score = Self::score_candidate(&candidate, key, culture, script, refined_script);
602
603 if score > best_score {
604 best_score = score;
605 best = Some((candidate, family_name.to_owned()));
606 }
607 }
608
609 best
610 }
611
612 fn score_candidate(
613 candidate: &GlyphTypeface,
614 requested_key: FontCollectionKey,
615 culture: Option<&CultureInfo>,
616 script: Script,
617 refined_script: Script,
618 ) -> i32 {
619 let mut score = 0;
620
621 if let Some(culture) = culture {
622 if candidate.family_names().contains_key(culture) {
624 score += 8;
625 } else {
626 let parent = culture.parent();
627
628 if !parent.is_invariant() && candidate.family_names().contains_key(&parent) {
629 score += 4;
630 }
631 }
632
633 if FontFallbackScriptHints::is_font_compatible_with_culture(candidate, Some(culture)) {
635 score += 4;
636 }
637 }
638
639 if FontFallbackScriptHints::is_locale_sensitive(refined_script) {
641 if candidate.supports_script(refined_script) {
642 score += 2;
643 } else if refined_script != script && candidate.supports_script(script) {
644 score += 1;
645 }
646 }
647
648 if FontCollectionKey::from(candidate) == requested_key {
649 score += 1;
650 }
651
652 score
653 }
654
655 fn is_culture_compatible(candidate: &GlyphTypeface, culture: Option<&CultureInfo>, script: Script) -> bool {
656 let Some(culture) = culture else {
658 return true;
659 };
660
661 if !FontFallbackScriptHints::is_locale_sensitive(script) {
662 return true;
663 }
664
665 if FontFallbackScriptHints::is_font_compatible_with_culture(candidate, Some(culture)) {
668 return true;
669 }
670
671 if candidate.family_names().is_empty() {
673 return true;
674 }
675
676 if candidate.family_names().contains_key(culture) {
677 return true;
678 }
679
680 let parent = culture.parent();
681
682 if !parent.is_invariant() && candidate.family_names().contains_key(&parent) {
683 return true;
684 }
685
686 false
688 }
689
690 fn build_typeface_with_synthesis(
691 this: &dyn IFontCollectionBase,
692 glyph_typeface: &Rc<GlyphTypeface>,
693 requested_key: FontCollectionKey,
694 ) -> Typeface {
695 let matched_key = FontCollectionKey::from(&**glyph_typeface);
696
697 if matched_key != requested_key {
704 Self::try_add_glyph_typeface_by_name(
705 this,
706 glyph_typeface.family_name(),
707 requested_key,
708 Some(glyph_typeface.clone()),
709 );
710 }
711
712 Typeface::with_style(
713 FontFamily::with_base_uri(
714 None,
715 &format!("{}#{}", this.key().absolute_uri(), glyph_typeface.family_name()),
716 ),
717 requested_key.style,
718 requested_key.weight,
719 requested_key.stretch,
720 )
721 }
722
723 fn try_get_covering_match_for_family(
730 this: &dyn IFontCollectionBase,
731 glyph_typefaces: &Rc<RefCell<GlyphTypefaceMap>>,
732 key: FontCollectionKey,
733 codepoint: i32,
734 culture: Option<&CultureInfo>,
735 shaping_script: Script,
736 ) -> Option<Rc<GlyphTypeface>> {
737 let glyph_typeface =
739 Self::try_get_covering_match(&glyph_typefaces.borrow(), key, codepoint, false, shaping_script)?;
740
741 Some(Self::prefer_exact_key(this, glyph_typeface, key, codepoint, culture, shaping_script))
742 }
743
744 fn prefer_exact_key(
753 this: &dyn IFontCollectionBase,
754 glyph_typeface: Rc<GlyphTypeface>,
755 key: FontCollectionKey,
756 codepoint: i32,
757 culture: Option<&CultureInfo>,
758 shaping_script: Script,
759 ) -> Rc<GlyphTypeface> {
760 if FontCollectionKey::from(&*glyph_typeface) != key {
764 if let Some(exact) = this.try_match_character_from_platform(
765 codepoint,
766 key,
767 Some(glyph_typeface.family_name()),
768 culture,
769 ) {
770 if FontCollectionKey::from(&*exact) == key && Self::can_shape(&exact, shaping_script) {
771 return exact;
772 }
773 }
774 }
775
776 glyph_typeface
777 }
778
779 fn can_shape(glyph_typeface: &GlyphTypeface, shaping_script: Script) -> bool {
782 shaping_script == Script::Unknown || glyph_typeface.can_shape_script(shaping_script)
783 }
784
785 fn try_get_covering_match(
790 glyph_typefaces: &GlyphTypefaceMap,
791 key: FontCollectionKey,
792 codepoint: i32,
793 is_last_resort: bool,
794 shaping_script: Script,
795 ) -> Option<Rc<GlyphTypeface>> {
796 let covers = |glyph_typeface: &GlyphTypeface| {
797 glyph_typeface.is_last_resort() == is_last_resort
798 && glyph_typeface.character_to_glyph_map().try_get_glyph(codepoint).is_some()
799 && Self::can_shape(glyph_typeface, shaping_script)
800 };
801
802 if let Some(Some(glyph_typeface)) = glyph_typefaces.get(&key) {
804 if covers(glyph_typeface) {
805 return Some(glyph_typeface.clone());
806 }
807 }
808
809 let mut covering_nearest: Option<&Rc<GlyphTypeface>> = None;
810 let mut covering_distance = i32::MAX;
811
812 for (candidate_key, candidate) in glyph_typefaces {
814 let Some(candidate) = candidate else {
815 continue;
816 };
817
818 if !covers(candidate) {
819 continue;
820 }
821
822 let distance = Self::key_distance(*candidate_key, key);
823
824 if distance < covering_distance {
825 covering_distance = distance;
826 covering_nearest = Some(candidate);
827 }
828 }
829
830 covering_nearest.cloned()
831 }
832
833 fn key_distance(a: FontCollectionKey, b: FontCollectionKey) -> i32 {
834 let weight_delta = (a.weight.value() - b.weight.value()).abs();
835
836 let stretch_delta = (a.stretch as i32 - b.stretch as i32).abs();
837
838 let style_delta = if a.style == b.style { 0 } else { 1 };
839
840 weight_delta + stretch_delta * 100 + style_delta * 10_000
841 }
842
843 pub fn try_create_synthetic_glyph_typeface(
846 this: &dyn IFontCollectionBase,
847 glyph_typeface: &Rc<GlyphTypeface>,
848 style: FontStyle,
849 weight: FontWeight,
850 stretch: FontStretch,
851 ) -> Option<Rc<GlyphTypeface>> {
852 let base = this.base();
853
854 let glyph_typefaces = base.glyph_typeface_cache.try_get_value(glyph_typeface.family_name())?;
856
857 let key = FontCollectionKey::new(style, weight, stretch);
858
859 let current_key = FontCollectionKey::from(&**glyph_typeface);
860
861 if current_key == key {
862 return None;
863 }
864
865 let mut font_simulations = FontSimulations::None;
866
867 if style != FontStyle::Normal && glyph_typeface.style() != style {
868 font_simulations |= FontSimulations::Oblique;
869 }
870
871 if weight.value() >= 600 && glyph_typeface.weight() < weight {
872 font_simulations |= FontSimulations::Bold;
873 }
874
875 if font_simulations == FontSimulations::None {
876 return None;
877 }
878
879 if let Some(Some(cached_glyph_typeface)) = glyph_typefaces.borrow().get(&key) {
884 if cached_glyph_typeface.font_simulations() == font_simulations {
885 return Some(cached_glyph_typeface.clone());
886 }
887 }
888
889 let mut stream = glyph_typeface.platform_typeface().try_get_stream()?;
890
891 let platform_typeface =
892 base.font_manager_impl.try_create_glyph_typeface_from_stream(&mut *stream, font_simulations)?;
893
894 let synthetic_glyph_typeface = GlyphTypeface::try_create(platform_typeface, font_simulations)?;
895
896 if !glyph_typeface.typographic_family_name().is_empty() {
898 Self::try_add_glyph_typeface_by_name(
899 this,
900 glyph_typeface.typographic_family_name(),
901 key,
902 Some(synthetic_glyph_typeface.clone()),
903 );
904 }
905
906 for family_name in glyph_typeface.family_names().values() {
907 Self::try_add_glyph_typeface_by_name(this, family_name, key, Some(synthetic_glyph_typeface.clone()));
908 }
909
910 Some(synthetic_glyph_typeface)
911 }
912
913 pub fn try_get_glyph_typeface(
916 this: &dyn IFontCollectionBase,
917 family_name: &str,
918 style: FontStyle,
919 weight: FontWeight,
920 stretch: FontStretch,
921 ) -> Option<Rc<GlyphTypeface>> {
922 let (typeface, family_name) =
923 Typeface::from_name_with_style(family_name, style, weight, stretch).normalize();
924
925 let key = FontCollectionKey::from(&typeface);
926
927 Self::try_get_glyph_typeface_by_key(this, &family_name, key, true)
928 }
929
930 pub fn try_get_family_typefaces(this: &dyn IFontCollectionBase, family_name: &str) -> Option<Vec<Typeface>> {
933 let glyph_typefaces = this.base().glyph_typeface_cache.try_get_value(family_name)?;
934
935 let keys: Vec<FontCollectionKey> = glyph_typefaces.borrow().keys().copied().collect();
937
938 let collection_key = this.key();
939
940 Some(
941 keys.into_iter()
942 .map(|key| {
943 Typeface::with_style(
944 FontFamily::new(&format!("{collection_key}#{family_name}")),
945 key.style,
946 key.weight,
947 key.stretch,
948 )
949 })
950 .collect(),
951 )
952 }
953
954 pub fn try_get_nearest_match(
957 &self,
958 family_name: &str,
959 style: FontStyle,
960 weight: FontWeight,
961 stretch: FontStretch,
962 ) -> Option<Rc<GlyphTypeface>> {
963 let glyph_typefaces = self.glyph_typeface_cache.try_get_value(family_name)?;
964
965 let key = FontCollectionKey::new(style, weight, stretch);
966
967 let glyph_typefaces = glyph_typefaces.borrow();
968
969 Self::try_get_nearest_match_in(&glyph_typefaces, key)
970 }
971
972 pub fn try_add_glyph_typeface(this: &dyn IFontCollectionBase, glyph_typeface: &Rc<GlyphTypeface>) -> bool {
983 let key = FontCollectionKey::from(&**glyph_typeface);
984
985 Self::try_add_glyph_typeface_with_key(this, glyph_typeface, key)
986 }
987
988 pub fn try_add_glyph_typeface_with_key(
996 this: &dyn IFontCollectionBase,
997 glyph_typeface: &Rc<GlyphTypeface>,
998 key: FontCollectionKey,
999 ) -> bool {
1000 if glyph_typeface.family_name().is_empty() {
1001 return false;
1002 }
1003
1004 let mut result = false;
1005
1006 if !glyph_typeface.typographic_family_name().is_empty()
1008 && Self::try_add_glyph_typeface_by_name(
1009 this,
1010 glyph_typeface.typographic_family_name(),
1011 key,
1012 Some(glyph_typeface.clone()),
1013 )
1014 {
1015 result = true;
1016 }
1017
1018 for family_name in glyph_typeface.family_names().values() {
1019 if Self::try_add_glyph_typeface_by_name(this, family_name, key, Some(glyph_typeface.clone())) {
1020 result = true;
1021 }
1022 }
1023
1024 result
1025 }
1026
1027 pub fn try_add_glyph_typeface_from_stream(
1036 this: &dyn IFontCollectionBase,
1037 stream: &mut dyn Read,
1038 ) -> Option<Rc<GlyphTypeface>> {
1039 let platform_typeface =
1040 this.base().font_manager_impl.try_create_glyph_typeface_from_stream(stream, FontSimulations::None)?;
1041
1042 let glyph_typeface = GlyphTypeface::try_create(platform_typeface, FontSimulations::None)?;
1043
1044 Self::try_add_glyph_typeface(this, &glyph_typeface).then_some(glyph_typeface)
1045 }
1046
1047 pub fn try_add_font_source(this: &dyn IFontCollectionBase, source: &Uri) -> bool {
1059 if !source.is_absolute_uri() {
1060 return false;
1061 }
1062
1063 let base = this.base();
1064
1065 let mut result = false;
1066
1067 match source.scheme() {
1068 ASSET_SCHEME | "resm" => {
1069 let font_assets = FontFamilyLoader::load_font_assets(source);
1070
1071 if font_assets.is_empty() {
1072 return false;
1073 }
1074
1075 let asset_loader = base
1076 .asset_loader
1077 .clone()
1078 .unwrap_or_else(|| FerroLocator::current().get_required_service::<dyn IAssetLoader>());
1079
1080 for font_asset in &font_assets {
1081 let Ok(mut stream) = asset_loader.open(font_asset, None) else {
1082 continue;
1083 };
1084
1085 let Some(glyph_typeface) = base
1086 .font_manager_impl
1087 .try_create_glyph_typeface_from_stream(&mut *stream, FontSimulations::None)
1088 .and_then(|platform_typeface| {
1089 GlyphTypeface::try_create(platform_typeface, FontSimulations::None)
1090 })
1091 else {
1092 continue;
1093 };
1094
1095 let key = FontCollectionKey::from(&*glyph_typeface);
1096
1097 if !glyph_typeface.typographic_family_name().is_empty()
1099 && Self::try_add_glyph_typeface_by_name(
1100 this,
1101 glyph_typeface.typographic_family_name(),
1102 key,
1103 Some(glyph_typeface.clone()),
1104 )
1105 {
1106 result = true;
1107 }
1108
1109 if Self::try_add_glyph_typeface_by_name(
1110 this,
1111 glyph_typeface.family_name(),
1112 key,
1113 Some(glyph_typeface.clone()),
1114 ) {
1115 result = true;
1116 }
1117 }
1118 }
1119 "file" => {
1120 result = Self::try_add_file_font_source(this, source);
1121 }
1122 _ => {
1123 return false;
1125 }
1126 }
1127
1128 result
1129 }
1130
1131 #[cfg(not(target_family = "wasm"))]
1132 fn try_add_file_font_source(this: &dyn IFontCollectionBase, source: &Uri) -> bool {
1133 use std::fs::File;
1134 use std::path::{Path, PathBuf};
1135
1136 let add_file = |path: &Path| -> bool {
1137 let Ok(mut stream) = File::open(path) else {
1138 return false;
1139 };
1140
1141 this.base()
1142 .font_manager_impl
1143 .try_create_glyph_typeface_from_stream(&mut stream, FontSimulations::None)
1144 .and_then(|platform_typeface| GlyphTypeface::try_create(platform_typeface, FontSimulations::None))
1145 .is_some_and(|glyph_typeface| Self::try_add_glyph_typeface(this, &glyph_typeface))
1146 };
1147
1148 let local_path = PathBuf::from(local_path(source));
1149
1150 if FontFamilyLoader::is_font_source(source) {
1152 if !local_path.is_file() {
1153 return false;
1154 }
1155
1156 return add_file(&local_path);
1157 }
1158
1159 let Ok(entries) = std::fs::read_dir(&local_path) else {
1161 return false;
1162 };
1163
1164 let mut files: Vec<PathBuf> =
1167 entries.filter_map(Result::ok).map(|entry| entry.path()).filter(|path| path.is_file()).collect();
1168
1169 files.sort();
1170
1171 let mut result = false;
1172
1173 for file in &files {
1174 if file.to_str().is_some_and(FontFamilyLoader::is_font_file) && add_file(file) {
1175 result = true;
1176 }
1177 }
1178
1179 result
1180 }
1181
1182 #[cfg(target_family = "wasm")]
1184 fn try_add_file_font_source(_this: &dyn IFontCollectionBase, _source: &Uri) -> bool {
1185 false
1186 }
1187
1188 pub fn add_font_family(&self, font_family: FontFamily) {
1194 let mut font_families = self.font_families.borrow_mut();
1195
1196 let index = font_families
1197 .binary_search_by(|existing| compare_ordinal_ignore_case(existing.name(), font_family.name()));
1198
1199 if let Err(index) = index {
1201 font_families.insert(index, font_family);
1202 }
1203 }
1204
1205 pub fn try_get_glyph_typeface_by_key(
1216 this: &dyn IFontCollectionBase,
1217 family_name: &str,
1218 key: FontCollectionKey,
1219 allow_nearest_match: bool,
1220 ) -> Option<Rc<GlyphTypeface>> {
1221 let base = this.base();
1222
1223 if let Some(glyph_typefaces) = base.glyph_typeface_cache.try_get_value(family_name) {
1224 let matched = Self::try_get_match(&glyph_typefaces.borrow(), key, allow_nearest_match);
1226
1227 if let Some((mut glyph_typeface, is_nearest_match)) = matched {
1228 let matched_key = FontCollectionKey::from(&*glyph_typeface);
1229
1230 if is_nearest_match && matched_key != key {
1231 if let Some(synthetic_glyph_typeface) = this.try_create_synthetic_glyph_typeface(
1232 &glyph_typeface,
1233 key.style,
1234 key.weight,
1235 key.stretch,
1236 ) {
1237 glyph_typeface = synthetic_glyph_typeface;
1238 }
1239
1240 Self::try_add_glyph_typeface_by_name(this, family_name, key, Some(glyph_typeface.clone()));
1245 }
1246
1247 return Some(glyph_typeface);
1248 }
1249 }
1250
1251 let snapshot = base.font_families.borrow();
1253 let mut left = 0isize;
1254 let mut right = snapshot.len() as isize - 1;
1255 let mut first_match: Option<usize> = None;
1256
1257 while left <= right {
1258 let mid = ((left + right) / 2) as usize;
1259 let name = snapshot[mid].name();
1260
1261 match compare_ordinal_ignore_case(name, family_name) {
1262 Ordering::Less => left = mid as isize + 1,
1264 Ordering::Equal => {
1265 let exact_glyph_typefaces = base.glyph_typeface_cache.try_get_value(name)?;
1268 let exact_glyph_typefaces = exact_glyph_typefaces.borrow();
1269
1270 return Self::try_get_match(&exact_glyph_typefaces, key, allow_nearest_match)
1271 .map(|(glyph_typeface, _)| glyph_typeface);
1272 }
1273 Ordering::Greater => {
1274 if starts_with_ordinal_ignore_case(name, family_name) {
1278 first_match = Some(mid);
1279 }
1280
1281 right = mid as isize - 1;
1283 }
1284 }
1285 }
1286
1287 if let Some(first_match) = first_match {
1288 for font_family in &snapshot[first_match..] {
1290 if !starts_with_ordinal_ignore_case(font_family.name(), family_name) {
1291 break;
1292 }
1293
1294 if let Some(glyph_typefaces) = base.glyph_typeface_cache.try_get_value(font_family.name()) {
1295 if let Some((glyph_typeface, _)) =
1296 Self::try_get_match(&glyph_typefaces.borrow(), key, allow_nearest_match)
1297 {
1298 return Some(glyph_typeface);
1299 }
1300 }
1301 }
1302 }
1303
1304 None
1305 }
1306
1307 fn try_get_match(
1309 glyph_typefaces: &GlyphTypefaceMap,
1310 key: FontCollectionKey,
1311 allow_nearest_match: bool,
1312 ) -> Option<(Rc<GlyphTypeface>, bool)> {
1313 if let Some(Some(glyph_typeface)) = glyph_typefaces.get(&key) {
1314 return Some((glyph_typeface.clone(), false));
1315 }
1316
1317 if allow_nearest_match {
1318 if let Some(glyph_typeface) = Self::try_get_nearest_match_in(glyph_typefaces, key) {
1319 return Some((glyph_typeface, true));
1320 }
1321 }
1322
1323 None
1324 }
1325
1326 pub fn try_get_nearest_match_in(
1335 glyph_typefaces: &GlyphTypefaceMap,
1336 key: FontCollectionKey,
1337 ) -> Option<Rc<GlyphTypeface>> {
1338 Self::try_get_nearest_match_core(glyph_typefaces, key, false)
1339 .or_else(|| Self::try_get_nearest_match_core(glyph_typefaces, key, true))
1340 }
1341
1342 fn try_get_nearest_match_core(
1343 glyph_typefaces: &GlyphTypefaceMap,
1344 mut key: FontCollectionKey,
1345 is_last_resort: bool,
1346 ) -> Option<Rc<GlyphTypeface>> {
1347 if let Some(Some(glyph_typeface)) = glyph_typefaces.get(&key) {
1348 if glyph_typeface.is_last_resort() == is_last_resort {
1349 return Some(glyph_typeface.clone());
1350 }
1351 }
1352
1353 if key.style != FontStyle::Normal {
1354 key.style = FontStyle::Normal;
1355 }
1356
1357 if key.stretch != FontStretch::Normal {
1358 if let Some(glyph_typeface) = Self::try_find_stretch_fallback(glyph_typefaces, key, is_last_resort) {
1359 return Some(glyph_typeface);
1360 }
1361
1362 if key.weight != FontWeight::Normal {
1363 if let Some(glyph_typeface) = Self::try_find_stretch_fallback(
1364 glyph_typefaces,
1365 FontCollectionKey { weight: FontWeight::Normal, ..key },
1366 is_last_resort,
1367 ) {
1368 return Some(glyph_typeface);
1369 }
1370 }
1371
1372 key.stretch = FontStretch::Normal;
1373 }
1374
1375 if let Some(glyph_typeface) = Self::try_find_weight_fallback(glyph_typefaces, key, is_last_resort) {
1376 return Some(glyph_typeface);
1377 }
1378
1379 if let Some(glyph_typeface) = Self::try_find_stretch_fallback(glyph_typefaces, key, is_last_resort) {
1380 return Some(glyph_typeface);
1381 }
1382
1383 glyph_typefaces
1385 .values()
1386 .flatten()
1387 .find(|typeface| is_last_resort == typeface.is_last_resort())
1388 .cloned()
1389 }
1390
1391 pub fn try_add_glyph_typeface_by_name(
1406 this: &dyn IFontCollectionBase,
1407 family_name: &str,
1408 key: FontCollectionKey,
1409 glyph_typeface: Option<Rc<GlyphTypeface>>,
1410 ) -> bool {
1411 if family_name.is_empty() {
1412 return false;
1413 }
1414
1415 let base = this.base();
1416
1417 let (glyph_typefaces, _) = base.glyph_typeface_cache.get_or_add(family_name);
1418
1419 let mut glyph_typefaces = glyph_typefaces.borrow_mut();
1420
1421 if let Some(existing) = glyph_typefaces.get(&key) {
1423 return match (existing, &glyph_typeface) {
1424 (Some(existing), Some(glyph_typeface)) => Rc::ptr_eq(existing, glyph_typeface),
1425 (None, None) => true,
1426 _ => false,
1427 };
1428 }
1429
1430 let publish = glyph_typeface.is_some() && glyph_typefaces.values().all(Option::is_none);
1435
1436 glyph_typefaces.insert(key, glyph_typeface);
1437
1438 if publish {
1439 let font_family = FontFamily::new(&format!("{}#{}", this.key(), family_name));
1440
1441 base.add_font_family(font_family);
1443 }
1444
1445 true
1446 }
1447
1448 fn try_find_stretch_fallback(
1454 glyph_typefaces: &GlyphTypefaceMap,
1455 key: FontCollectionKey,
1456 is_last_resort: bool,
1457 ) -> Option<Rc<GlyphTypeface>> {
1458 let stretch = key.stretch as i32;
1459
1460 let try_get_with_stretch = |effective_stretch: i32| -> Option<Rc<GlyphTypeface>> {
1461 let effective_stretch = FontStretch::from_i32(effective_stretch)?;
1462
1463 match glyph_typefaces.get(&FontCollectionKey { stretch: effective_stretch, ..key }) {
1464 Some(Some(glyph_typeface)) if glyph_typeface.is_last_resort() == is_last_resort => {
1465 Some(glyph_typeface.clone())
1466 }
1467 _ => None,
1468 }
1469 };
1470
1471 if stretch < 5 {
1472 let mut i = 0;
1473
1474 while stretch + i < 9 {
1475 if let Some(glyph_typeface) = try_get_with_stretch(stretch + i) {
1476 return Some(glyph_typeface);
1477 }
1478
1479 i += 1;
1480 }
1481 } else {
1482 let mut i = 0;
1483
1484 while stretch - i > 1 {
1485 if let Some(glyph_typeface) = try_get_with_stretch(stretch - i) {
1486 return Some(glyph_typeface);
1487 }
1488
1489 i += 1;
1490 }
1491 }
1492
1493 None
1494 }
1495
1496 fn try_find_weight_fallback(
1504 glyph_typefaces: &GlyphTypefaceMap,
1505 key: FontCollectionKey,
1506 is_last_resort: bool,
1507 ) -> Option<Rc<GlyphTypeface>> {
1508 let weight = key.weight.value();
1509
1510 let try_get_with_weight = |effective_weight: i32| -> Option<Rc<GlyphTypeface>> {
1511 match glyph_typefaces.get(&FontCollectionKey { weight: FontWeight(effective_weight), ..key }) {
1512 Some(Some(glyph_typeface)) if glyph_typeface.is_last_resort() == is_last_resort => {
1513 Some(glyph_typeface.clone())
1514 }
1515 _ => None,
1516 }
1517 };
1518
1519 let ascending = |limit: i32| -> Option<Rc<GlyphTypeface>> {
1521 let mut i = 0;
1522
1523 while weight + i <= limit {
1524 if let Some(glyph_typeface) = try_get_with_weight(weight + i) {
1525 return Some(glyph_typeface);
1526 }
1527
1528 i += 50;
1529 }
1530
1531 None
1532 };
1533
1534 let descending = || -> Option<Rc<GlyphTypeface>> {
1536 let mut i = 0;
1537
1538 while weight - i >= 100 {
1539 if let Some(glyph_typeface) = try_get_with_weight(weight - i) {
1540 return Some(glyph_typeface);
1541 }
1542
1543 i += 50;
1544 }
1545
1546 None
1547 };
1548
1549 if (400..=500).contains(&weight) {
1551 if let Some(glyph_typeface) = ascending(500).or_else(descending).or_else(|| ascending(900)) {
1555 return Some(glyph_typeface);
1556 }
1557 }
1558
1559 if weight < 400 {
1561 if let Some(glyph_typeface) = descending().or_else(|| ascending(900)) {
1563 return Some(glyph_typeface);
1564 }
1565 }
1566
1567 if weight > 500 {
1569 if let Some(glyph_typeface) = ascending(900).or_else(descending) {
1571 return Some(glyph_typeface);
1572 }
1573 }
1574
1575 None
1576 }
1577
1578 pub fn dispose(&self) {
1580 for glyph_typefaces in self.glyph_typeface_cache.values() {
1581 let glyph_typefaces: Vec<Rc<GlyphTypeface>> =
1582 glyph_typefaces.borrow().values().flatten().cloned().collect();
1583
1584 for glyph_typeface in glyph_typefaces {
1585 glyph_typeface.dispose();
1586 }
1587 }
1588 }
1589}
1590
1591fn local_path(uri: &Uri) -> String {
1595 let path = crate::utilities::UriExtensions::get_unescape_absolute_path(uri);
1596
1597 if cfg!(windows) && uri.scheme() == "file" {
1598 let bytes = path.as_bytes();
1599
1600 if bytes.len() >= 3 && bytes[0] == b'/' && bytes[1].is_ascii_alphabetic() && bytes[2] == b':' {
1602 return path[1..].replace('/', "\\");
1603 }
1604 }
1605
1606 path
1607}