1#![doc(html_favicon_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo.png")]
3#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
9#![expect(clippy::type_complexity)]
11#![warn(unused_extern_crates)]
12#![warn(missing_docs)]
13
14use font_features::RFontVariations;
15use hashbrown::{HashMap, HashSet};
16use std::{borrow::Cow, fmt, ops, path::PathBuf, slice::SliceIndex, sync::Arc};
17
18#[macro_use]
19extern crate bitflags;
20
21pub mod font_features;
22
23mod query_util;
24
25mod emoji_util;
26pub use emoji_util::*;
27
28mod ligature_util;
29use ligature_util::*;
30
31mod unicode_bidi_util;
32
33mod segmenting;
34pub use segmenting::*;
35
36mod shaping;
37pub use shaping::*;
38use zng_clone_move::{async_clmv, clmv};
39
40mod hyphenation;
41pub use self::hyphenation::*;
42
43mod unit;
44pub use unit::*;
45
46use parking_lot::{Mutex, RwLock};
47use pastey::paste;
48use zng_app::{
49 AppExtension,
50 event::{event, event_args},
51 render::FontSynthesis,
52 update::{EventUpdate, UPDATES},
53 view_process::{
54 VIEW_PROCESS_INITED_EVENT, ViewRenderer,
55 raw_events::{RAW_FONT_AA_CHANGED_EVENT, RAW_FONT_CHANGED_EVENT},
56 },
57};
58use zng_app_context::app_local;
59use zng_ext_l10n::{Lang, LangMap, lang};
60use zng_layout::unit::{
61 EQ_GRANULARITY, EQ_GRANULARITY_100, Factor, FactorPercent, Px, PxPoint, PxRect, PxSize, TimeUnits as _, about_eq, about_eq_hash,
62 about_eq_ord, euclid,
63};
64use zng_task as task;
65use zng_txt::Txt;
66use zng_var::{
67 IntoVar, ResponderVar, ResponseVar, Var, animation::Transitionable, const_var, impl_from_and_into_var, response_done_var, response_var,
68 var,
69};
70use zng_view_api::config::FontAntiAliasing;
71
72#[derive(Clone)]
80pub struct FontName {
81 txt: Txt,
82 is_ascii: bool,
83}
84impl fmt::Debug for FontName {
85 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86 if f.alternate() {
87 f.debug_struct("FontName")
88 .field("txt", &self.txt)
89 .field("is_ascii", &self.is_ascii)
90 .finish()
91 } else {
92 write!(f, "{:?}", self.txt)
93 }
94 }
95}
96impl PartialEq for FontName {
97 fn eq(&self, other: &Self) -> bool {
98 self.unicase() == other.unicase()
99 }
100}
101impl Eq for FontName {}
102impl PartialEq<str> for FontName {
103 fn eq(&self, other: &str) -> bool {
104 self.unicase() == unicase::UniCase::<&str>::from(other)
105 }
106}
107impl std::hash::Hash for FontName {
108 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
109 std::hash::Hash::hash(&self.unicase(), state)
110 }
111}
112impl Ord for FontName {
113 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
114 if self == other {
115 return std::cmp::Ordering::Equal;
117 }
118 self.txt.cmp(&other.txt)
119 }
120}
121impl PartialOrd for FontName {
122 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
123 Some(self.cmp(other))
124 }
125}
126impl FontName {
127 fn unicase(&self) -> unicase::UniCase<&str> {
128 if self.is_ascii {
129 unicase::UniCase::ascii(self)
130 } else {
131 unicase::UniCase::unicode(self)
132 }
133 }
134
135 pub const fn from_static(name: &'static str) -> Self {
137 FontName {
138 txt: Txt::from_static(name),
139 is_ascii: {
140 let name_bytes = name.as_bytes();
142 let mut i = name_bytes.len();
143 let mut is_ascii = true;
144 while i > 0 {
145 i -= 1;
146 if !name_bytes[i].is_ascii() {
147 is_ascii = false;
148 break;
149 }
150 }
151 is_ascii
152 },
153 }
154 }
155
156 pub fn new(name: impl Into<Txt>) -> Self {
165 let txt = name.into();
166 FontName {
167 is_ascii: txt.is_ascii(),
168 txt,
169 }
170 }
171
172 pub fn serif() -> Self {
176 Self::new("serif")
177 }
178
179 pub fn sans_serif() -> Self {
184 Self::new("sans-serif")
185 }
186
187 pub fn monospace() -> Self {
191 Self::new("monospace")
192 }
193
194 pub fn cursive() -> Self {
199 Self::new("cursive")
200 }
201
202 pub fn fantasy() -> Self {
206 Self::new("fantasy")
207 }
208
209 pub fn name(&self) -> &str {
211 &self.txt
212 }
213
214 pub fn into_text(self) -> Txt {
218 self.txt
219 }
220}
221impl_from_and_into_var! {
222 fn from(s: &'static str) -> FontName {
223 FontName::new(s)
224 }
225 fn from(s: String) -> FontName {
226 FontName::new(s)
227 }
228 fn from(s: Cow<'static, str>) -> FontName {
229 FontName::new(s)
230 }
231 fn from(f: FontName) -> Txt {
232 f.into_text()
233 }
234}
235impl fmt::Display for FontName {
236 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
237 f.write_str(self.name())
238 }
239}
240impl std::ops::Deref for FontName {
241 type Target = str;
242
243 fn deref(&self) -> &Self::Target {
244 self.txt.deref()
245 }
246}
247impl AsRef<str> for FontName {
248 fn as_ref(&self) -> &str {
249 self.txt.as_ref()
250 }
251}
252impl serde::Serialize for FontName {
253 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
254 where
255 S: serde::Serializer,
256 {
257 self.txt.serialize(serializer)
258 }
259}
260impl<'de> serde::Deserialize<'de> for FontName {
261 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
262 where
263 D: serde::Deserializer<'de>,
264 {
265 Txt::deserialize(deserializer).map(FontName::new)
266 }
267}
268
269#[derive(Eq, PartialEq, Hash, Clone, serde::Serialize, serde::Deserialize)]
298#[serde(transparent)]
299pub struct FontNames(pub Vec<FontName>);
300impl FontNames {
301 pub fn empty() -> Self {
303 FontNames(vec![])
304 }
305
306 pub fn windows_ui(lang: &Lang) -> Self {
308 if lang!("zh-Hans").matches(lang, true, false) {
312 ["Segoe UI", "Microsoft YaHei", "Segoe Ui Emoji", "sans-serif"].into()
313 } else if lang!("zh-Hant").matches(lang, true, false) {
314 ["Segoe UI", "Microsoft Jhenghei", "Segoe Ui Emoji", "sans-serif"].into()
315 } else if lang!(ja).matches(lang, true, false) {
316 ["Segoe UI", "Yu Gothic UI", "Meiryo UI", "Segoe Ui Emoji", "sans-serif"].into()
317 } else if lang!(ko).matches(lang, true, false) {
318 ["Segoe UI", "Malgun Gothic", "Dotom", "Segoe Ui Emoji", "sans-serif"].into()
319 } else {
320 ["Segoe UI", "Segoe Ui Emoji", "sans-serif"].into()
321 }
322 }
323
324 pub fn mac_ui(lang: &Lang) -> Self {
326 if lang!("zh-Hans").matches(lang, true, false) {
329 ["PingFang SC", "Hiragino Sans GB", "Apple Color Emoji", "sans-serif"].into()
330 } else if lang!("zh-Hant").matches(lang, true, false) {
331 ["PingFang TC", "Apple Color Emoji", "sans-serif"].into()
332 } else if lang!(ja).matches(lang, true, false) {
333 ["Hiragino Kaku Gothic Pro", "Apple Color Emoji", "sans-serif"].into()
334 } else if lang!(ko).matches(lang, true, false) {
335 [
336 "Nanum Gothic",
337 "Apple SD Gothic Neo",
338 "AppleGothic",
339 "Apple Color Emoji",
340 "sans-serif",
341 ]
342 .into()
343 } else {
344 ["Neue Helvetica", "Lucida Grande", "Apple Color Emoji", "sans-serif"].into()
345 }
346 }
347
348 pub fn linux_ui(lang: &Lang) -> Self {
350 if lang!("zh-Hans").matches(lang, true, false) {
353 [
354 "Ubuntu",
355 "Droid Sans",
356 "Source Han Sans SC",
357 "Source Han Sans CN",
358 "Source Han Sans",
359 "Noto Color Emoji",
360 "sans-serif",
361 ]
362 .into()
363 } else if lang!("zh-Hant").matches(lang, true, false) {
364 [
365 "Ubuntu",
366 "Droid Sans",
367 "Source Han Sans TC",
368 "Source Han Sans TW",
369 "Source Han Sans",
370 "Noto Color Emoji",
371 "sans-serif",
372 ]
373 .into()
374 } else if lang!(ja).matches(lang, true, false) {
375 [
376 "system-ui",
377 "Ubuntu",
378 "Droid Sans",
379 "Source Han Sans J",
380 "Source Han Sans JP",
381 "Source Han Sans",
382 "Noto Color Emoji",
383 "sans-serif",
384 ]
385 .into()
386 } else if lang!(ko).matches(lang, true, false) {
387 [
388 "system-ui",
389 "Ubuntu",
390 "Droid Sans",
391 "Source Han Sans K",
392 "Source Han Sans JR",
393 "Source Han Sans",
394 "UnDotum",
395 "FBaekmuk Gulim",
396 "Noto Color Emoji",
397 "sans-serif",
398 ]
399 .into()
400 } else {
401 ["system-ui", "Ubuntu", "Droid Sans", "Noto Color Emoji", "sans-serif"].into()
402 }
403 }
404
405 pub fn system_ui(lang: &Lang) -> Self {
407 if cfg!(windows) {
408 Self::windows_ui(lang)
409 } else if cfg!(target_os = "linux") {
410 Self::linux_ui(lang)
411 } else if cfg!(target_os = "macos") {
412 Self::mac_ui(lang)
413 } else {
414 [FontName::sans_serif()].into()
415 }
416 }
417
418 pub fn push(&mut self, font_name: impl Into<FontName>) {
420 self.0.push(font_name.into())
421 }
422}
423impl Default for FontNames {
424 fn default() -> Self {
425 Self::system_ui(&Lang::default())
426 }
427}
428impl fmt::Debug for FontNames {
429 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
430 if f.alternate() {
431 f.debug_tuple("FontNames").field(&self.0).finish()
432 } else if self.0.is_empty() {
433 write!(f, "[]")
434 } else if self.0.len() == 1 {
435 write!(f, "{:?}", self.0[0])
436 } else {
437 write!(f, "[{:?}, ", self.0[0])?;
438 for name in &self.0[1..] {
439 write!(f, "{name:?}, ")?;
440 }
441 write!(f, "]")
442 }
443 }
444}
445impl fmt::Display for FontNames {
446 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
447 let mut iter = self.0.iter();
448
449 if let Some(name) = iter.next() {
450 write!(f, "{name}")?;
451 for name in iter {
452 write!(f, ", {name}")?;
453 }
454 }
455
456 Ok(())
457 }
458}
459impl_from_and_into_var! {
460 fn from(font_name: &'static str) -> FontNames {
461 FontNames(vec![FontName::new(font_name)])
462 }
463
464 fn from(font_name: String) -> FontNames {
465 FontNames(vec![FontName::new(font_name)])
466 }
467
468 fn from(font_name: Txt) -> FontNames {
469 FontNames(vec![FontName::new(font_name)])
470 }
471
472 fn from(font_names: Vec<FontName>) -> FontNames {
473 FontNames(font_names)
474 }
475
476 fn from(font_names: Vec<&'static str>) -> FontNames {
477 FontNames(font_names.into_iter().map(FontName::new).collect())
478 }
479
480 fn from(font_names: Vec<String>) -> FontNames {
481 FontNames(font_names.into_iter().map(FontName::new).collect())
482 }
483
484 fn from(font_name: FontName) -> FontNames {
485 FontNames(vec![font_name])
486 }
487}
488impl ops::Deref for FontNames {
489 type Target = Vec<FontName>;
490
491 fn deref(&self) -> &Self::Target {
492 &self.0
493 }
494}
495impl ops::DerefMut for FontNames {
496 fn deref_mut(&mut self) -> &mut Self::Target {
497 &mut self.0
498 }
499}
500impl std::iter::Extend<FontName> for FontNames {
501 fn extend<T: IntoIterator<Item = FontName>>(&mut self, iter: T) {
502 self.0.extend(iter)
503 }
504}
505impl IntoIterator for FontNames {
506 type Item = FontName;
507
508 type IntoIter = std::vec::IntoIter<FontName>;
509
510 fn into_iter(self) -> Self::IntoIter {
511 self.0.into_iter()
512 }
513}
514impl<const N: usize> From<[FontName; N]> for FontNames {
515 fn from(font_names: [FontName; N]) -> Self {
516 FontNames(font_names.into())
517 }
518}
519impl<const N: usize> IntoVar<FontNames> for [FontName; N] {
520 fn into_var(self) -> Var<FontNames> {
521 const_var(self.into())
522 }
523}
524impl<const N: usize> From<[&'static str; N]> for FontNames {
525 fn from(font_names: [&'static str; N]) -> Self {
526 FontNames(font_names.into_iter().map(FontName::new).collect())
527 }
528}
529impl<const N: usize> IntoVar<FontNames> for [&'static str; N] {
530 fn into_var(self) -> Var<FontNames> {
531 const_var(self.into())
532 }
533}
534impl<const N: usize> From<[String; N]> for FontNames {
535 fn from(font_names: [String; N]) -> Self {
536 FontNames(font_names.into_iter().map(FontName::new).collect())
537 }
538}
539impl<const N: usize> IntoVar<FontNames> for [String; N] {
540 fn into_var(self) -> Var<FontNames> {
541 const_var(self.into())
542 }
543}
544impl<const N: usize> From<[Txt; N]> for FontNames {
545 fn from(font_names: [Txt; N]) -> Self {
546 FontNames(font_names.into_iter().map(FontName::new).collect())
547 }
548}
549impl<const N: usize> IntoVar<FontNames> for [Txt; N] {
550 fn into_var(self) -> Var<FontNames> {
551 const_var(self.into())
552 }
553}
554
555event! {
556 pub static FONT_CHANGED_EVENT: FontChangedArgs;
567}
568
569event_args! {
570 pub struct FontChangedArgs {
572 pub change: FontChange,
574
575 ..
576
577 fn delivery_list(&self, list: &mut UpdateDeliveryList) {
579 list.search_all()
580 }
581 }
582}
583
584#[derive(Clone, Debug)]
586pub enum FontChange {
587 SystemFonts,
591
592 CustomFonts,
597
598 Refresh,
602
603 GenericFont(FontName, Lang),
609
610 Fallback(Lang),
612}
613
614#[derive(Default)]
625#[non_exhaustive]
626pub struct FontManager {}
627impl AppExtension for FontManager {
628 fn event_preview(&mut self, update: &mut EventUpdate) {
629 if RAW_FONT_CHANGED_EVENT.has(update) {
630 FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::SystemFonts));
631 } else if let Some(args) = RAW_FONT_AA_CHANGED_EVENT.on(update) {
632 FONTS_SV.read().font_aa.set(args.aa);
633 } else if FONT_CHANGED_EVENT.has(update) {
634 FONTS_SV.write().on_fonts_changed();
635 } else if let Some(args) = VIEW_PROCESS_INITED_EVENT.on(update)
636 && args.is_respawn
637 {
638 let mut fonts = FONTS_SV.write();
639 fonts.loader.on_view_process_respawn();
640 }
641 }
642
643 fn update(&mut self) {
644 let mut fonts = FONTS_SV.write();
645
646 {
647 let mut f = GENERIC_FONTS_SV.write();
648 for request in std::mem::take(&mut f.requests) {
649 request(&mut f);
650 }
651 }
652
653 let mut changed = false;
654 for (request, responder) in std::mem::take(&mut fonts.loader.unregister_requests) {
655 let r = if let Some(removed) = fonts.loader.custom_fonts.remove(&request) {
656 for removed in removed {
660 removed.on_refresh();
661 }
662
663 changed = true;
664
665 true
666 } else {
667 false
668 };
669 responder.respond(r);
670 }
671 if changed {
672 FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::CustomFonts));
673 }
674
675 if fonts.prune_requested {
676 fonts.on_prune();
677 }
678 }
679}
680
681app_local! {
682 static FONTS_SV: FontsService = FontsService {
683 loader: FontFaceLoader::new(),
684 prune_requested: false,
685 font_aa: var(FontAntiAliasing::Default),
686 };
687}
688
689struct FontsService {
690 loader: FontFaceLoader,
691 prune_requested: bool,
692 font_aa: Var<FontAntiAliasing>,
693}
694impl FontsService {
695 fn on_fonts_changed(&mut self) {
696 self.loader.on_refresh();
697 self.prune_requested = false;
698 }
699
700 fn on_prune(&mut self) {
701 self.loader.on_prune();
702 self.prune_requested = false;
703 }
704}
705
706pub struct FONTS;
708impl FONTS {
709 pub fn refresh(&self) {
713 FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::Refresh));
714 }
715
716 pub fn prune(&self) {
718 let mut ft = FONTS_SV.write();
719 if !ft.prune_requested {
720 ft.prune_requested = true;
721 UPDATES.update(None);
722 }
723 }
724
725 pub fn generics(&self) -> &'static GenericFonts {
727 &GenericFonts {}
728 }
729
730 pub fn register(&self, custom_font: CustomFont) -> ResponseVar<Result<FontFace, FontLoadingError>> {
739 FontFaceLoader::register(custom_font)
740 }
741
742 pub fn unregister(&self, custom_family: FontName) -> ResponseVar<bool> {
746 FONTS_SV.write().loader.unregister(custom_family)
747 }
748
749 pub fn list(
751 &self,
752 families: &[FontName],
753 style: FontStyle,
754 weight: FontWeight,
755 stretch: FontStretch,
756 lang: &Lang,
757 ) -> ResponseVar<FontFaceList> {
758 if let Some(cached) = FONTS_SV.read().loader.try_list(families, style, weight, stretch, lang) {
760 return cached;
761 }
762 FONTS_SV.write().loader.load_list(families, style, weight, stretch, lang)
764 }
765
766 pub fn find(
768 &self,
769 family: &FontName,
770 style: FontStyle,
771 weight: FontWeight,
772 stretch: FontStretch,
773 lang: &Lang,
774 ) -> ResponseVar<Option<FontFace>> {
775 if let Some(cached) = FONTS_SV.read().loader.try_cached(family, style, weight, stretch, lang) {
777 return cached;
778 }
779 FONTS_SV.write().loader.load(family, style, weight, stretch, lang)
781 }
782
783 pub fn normal(&self, family: &FontName, lang: &Lang) -> ResponseVar<Option<FontFace>> {
785 self.find(family, FontStyle::Normal, FontWeight::NORMAL, FontStretch::NORMAL, lang)
786 }
787
788 pub fn italic(&self, family: &FontName, lang: &Lang) -> ResponseVar<Option<FontFace>> {
790 self.find(family, FontStyle::Italic, FontWeight::NORMAL, FontStretch::NORMAL, lang)
791 }
792
793 pub fn bold(&self, family: &FontName, lang: &Lang) -> ResponseVar<Option<FontFace>> {
795 self.find(family, FontStyle::Normal, FontWeight::BOLD, FontStretch::NORMAL, lang)
796 }
797
798 pub fn custom_fonts(&self) -> Vec<FontName> {
800 FONTS_SV.read().loader.custom_fonts.keys().cloned().collect()
801 }
802
803 pub fn system_fonts(&self) -> ResponseVar<Vec<FontName>> {
807 query_util::system_all()
808 }
809
810 pub fn system_font_aa(&self) -> Var<FontAntiAliasing> {
814 FONTS_SV.read().font_aa.read_only()
815 }
816}
817
818impl<'a> From<ttf_parser::Face<'a>> for FontFaceMetrics {
819 fn from(f: ttf_parser::Face<'a>) -> Self {
820 let underline = f
821 .underline_metrics()
822 .unwrap_or(ttf_parser::LineMetrics { position: 0, thickness: 0 });
823 FontFaceMetrics {
824 units_per_em: f.units_per_em() as _,
825 ascent: f.ascender() as f32,
826 descent: f.descender() as f32,
827 line_gap: f.line_gap() as f32,
828 underline_position: underline.position as f32,
829 underline_thickness: underline.thickness as f32,
830 cap_height: f.capital_height().unwrap_or(0) as f32,
831 x_height: f.x_height().unwrap_or(0) as f32,
832 bounds: euclid::rect(
833 f.global_bounding_box().x_min as f32,
834 f.global_bounding_box().x_max as f32,
835 f.global_bounding_box().width() as f32,
836 f.global_bounding_box().height() as f32,
837 ),
838 }
839 }
840}
841
842#[derive(PartialEq, Eq, Hash)]
843struct FontInstanceKey(Px, Box<[(ttf_parser::Tag, i32)]>);
844impl FontInstanceKey {
845 pub fn new(size: Px, variations: &[rustybuzz::Variation]) -> Self {
847 let variations_key: Vec<_> = variations.iter().map(|p| (p.tag, (p.value * 1000.0) as i32)).collect();
848 FontInstanceKey(size, variations_key.into_boxed_slice())
849 }
850}
851
852#[derive(Clone)]
859pub struct FontFace(Arc<LoadedFontFace>);
860struct LoadedFontFace {
861 data: FontDataRef,
862 face_index: u32,
863 display_name: FontName,
864 family_name: FontName,
865 postscript_name: Option<Txt>,
866 style: FontStyle,
867 weight: FontWeight,
868 stretch: FontStretch,
869 metrics: FontFaceMetrics,
870 color_palettes: ColorPalettes,
871 color_glyphs: ColorGlyphs,
872 lig_carets: LigatureCaretList,
873 flags: FontFaceFlags,
874 m: Mutex<FontFaceMut>,
875}
876bitflags! {
877 #[derive(Debug, Clone, Copy)]
878 struct FontFaceFlags: u8 {
879 const IS_MONOSPACE = 0b0000_0001;
880 const HAS_LIGATURES = 0b0000_0010;
881 const HAS_RASTER_IMAGES = 0b0000_0100;
882 const HAS_SVG_IMAGES = 0b0000_1000;
883 }
884}
885struct FontFaceMut {
886 instances: HashMap<FontInstanceKey, Font>,
887 render_ids: Vec<RenderFontFace>,
888 unregistered: bool,
889}
890
891impl fmt::Debug for FontFace {
892 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
893 let m = self.0.m.lock();
894 f.debug_struct("FontFace")
895 .field("display_name", &self.0.display_name)
896 .field("family_name", &self.0.family_name)
897 .field("postscript_name", &self.0.postscript_name)
898 .field("flags", &self.0.flags)
899 .field("style", &self.0.style)
900 .field("weight", &self.0.weight)
901 .field("stretch", &self.0.stretch)
902 .field("metrics", &self.0.metrics)
903 .field("color_palettes.len()", &self.0.color_palettes.len())
904 .field("color_glyphs.len()", &self.0.color_glyphs.len())
905 .field("instances.len()", &m.instances.len())
906 .field("render_keys.len()", &m.render_ids.len())
907 .field("unregistered", &m.unregistered)
908 .finish_non_exhaustive()
909 }
910}
911impl PartialEq for FontFace {
912 fn eq(&self, other: &Self) -> bool {
913 Arc::ptr_eq(&self.0, &other.0)
914 }
915}
916impl Eq for FontFace {}
917impl FontFace {
918 pub fn empty() -> Self {
920 FontFace(Arc::new(LoadedFontFace {
921 data: FontDataRef::from_static(&[]),
922 face_index: 0,
923 display_name: FontName::from("<empty>"),
924 family_name: FontName::from("<empty>"),
925 postscript_name: None,
926 flags: FontFaceFlags::IS_MONOSPACE,
927 style: FontStyle::Normal,
928 weight: FontWeight::NORMAL,
929 stretch: FontStretch::NORMAL,
930 metrics: FontFaceMetrics {
932 units_per_em: 2048,
933 ascent: 1616.0,
934 descent: -432.0,
935 line_gap: 0.0,
936 underline_position: -205.0,
937 underline_thickness: 102.0,
938 cap_height: 1616.0,
939 x_height: 1616.0,
940 bounds: euclid::Box2D::new(euclid::point2(0.0, -432.0), euclid::point2(1291.0, 1616.0)).to_rect(),
942 },
943 color_palettes: ColorPalettes::empty(),
944 color_glyphs: ColorGlyphs::empty(),
945 lig_carets: LigatureCaretList::empty(),
946 m: Mutex::new(FontFaceMut {
947 instances: HashMap::default(),
948 render_ids: vec![],
949 unregistered: false,
950 }),
951 }))
952 }
953
954 pub fn is_empty(&self) -> bool {
956 self.0.data.is_empty()
957 }
958
959 async fn load_custom(custom_font: CustomFont) -> Result<Self, FontLoadingError> {
960 let bytes;
961 let mut face_index;
962
963 match custom_font.source {
964 FontSource::File(path, index) => {
965 bytes = FontDataRef(Arc::new(task::wait(|| std::fs::read(path)).await?));
966 face_index = index;
967 }
968 FontSource::Memory(arc, index) => {
969 bytes = arc;
970 face_index = index;
971 }
972 FontSource::Alias(other_font) => {
973 let result = FONTS_SV
974 .write()
975 .loader
976 .load_resolved(&other_font, custom_font.style, custom_font.weight, custom_font.stretch);
977 return match result.wait_rsp().await {
978 Some(other_font) => Ok(FontFace(Arc::new(LoadedFontFace {
979 data: other_font.0.data.clone(),
980 face_index: other_font.0.face_index,
981 display_name: custom_font.name.clone(),
982 family_name: custom_font.name,
983 postscript_name: None,
984 style: other_font.0.style,
985 weight: other_font.0.weight,
986 stretch: other_font.0.stretch,
987 metrics: other_font.0.metrics.clone(),
988 m: Mutex::new(FontFaceMut {
989 instances: Default::default(),
990 render_ids: Default::default(),
991 unregistered: Default::default(),
992 }),
993 color_palettes: other_font.0.color_palettes.clone(),
994 color_glyphs: other_font.0.color_glyphs.clone(),
995 lig_carets: other_font.0.lig_carets.clone(),
996 flags: other_font.0.flags,
997 }))),
998 None => Err(FontLoadingError::NoSuchFontInCollection),
999 };
1000 }
1001 }
1002
1003 let ttf_face = match ttf_parser::Face::parse(&bytes, face_index) {
1004 Ok(f) => f,
1005 Err(e) => {
1006 match e {
1007 ttf_parser::FaceParsingError::FaceIndexOutOfBounds => face_index = 0,
1009 e => return Err(FontLoadingError::Parse(e)),
1010 }
1011
1012 match ttf_parser::Face::parse(&bytes, face_index) {
1013 Ok(f) => f,
1014 Err(_) => return Err(FontLoadingError::Parse(e)),
1015 }
1016 }
1017 };
1018
1019 let color_palettes = ColorPalettes::load(ttf_face.raw_face())?;
1020 let color_glyphs = if color_palettes.is_empty() {
1021 ColorGlyphs::empty()
1022 } else {
1023 ColorGlyphs::load(ttf_face.raw_face())?
1024 };
1025 let has_ligatures = ttf_face.tables().gsub.is_some();
1026 let lig_carets = if has_ligatures {
1027 LigatureCaretList::empty()
1028 } else {
1029 LigatureCaretList::load(ttf_face.raw_face())?
1030 };
1031
1032 let has_raster_images = {
1034 let t = ttf_face.tables();
1035 t.sbix.is_some() || t.bdat.is_some() || t.ebdt.is_some() || t.cbdt.is_some()
1036 };
1037
1038 let mut flags = FontFaceFlags::empty();
1039 flags.set(FontFaceFlags::IS_MONOSPACE, ttf_face.is_monospaced());
1040 flags.set(FontFaceFlags::HAS_LIGATURES, has_ligatures);
1041 flags.set(FontFaceFlags::HAS_RASTER_IMAGES, has_raster_images);
1042 flags.set(FontFaceFlags::HAS_SVG_IMAGES, ttf_face.tables().svg.is_some());
1043
1044 Ok(FontFace(Arc::new(LoadedFontFace {
1045 face_index,
1046 display_name: custom_font.name.clone(),
1047 family_name: custom_font.name,
1048 postscript_name: None,
1049 style: custom_font.style,
1050 weight: custom_font.weight,
1051 stretch: custom_font.stretch,
1052 metrics: ttf_face.into(),
1053 color_palettes,
1054 color_glyphs,
1055 lig_carets,
1056 m: Mutex::new(FontFaceMut {
1057 instances: Default::default(),
1058 render_ids: Default::default(),
1059 unregistered: Default::default(),
1060 }),
1061 data: bytes,
1062 flags,
1063 })))
1064 }
1065
1066 fn load(bytes: FontDataRef, mut face_index: u32) -> Result<Self, FontLoadingError> {
1067 let _span = tracing::trace_span!("FontFace::load").entered();
1068
1069 let ttf_face = match ttf_parser::Face::parse(&bytes, face_index) {
1070 Ok(f) => f,
1071 Err(e) => {
1072 match e {
1073 ttf_parser::FaceParsingError::FaceIndexOutOfBounds => face_index = 0,
1075 e => return Err(FontLoadingError::Parse(e)),
1076 }
1077
1078 match ttf_parser::Face::parse(&bytes, face_index) {
1079 Ok(f) => f,
1080 Err(_) => return Err(FontLoadingError::Parse(e)),
1081 }
1082 }
1083 };
1084
1085 let color_palettes = ColorPalettes::load(ttf_face.raw_face())?;
1086 let color_glyphs = if color_palettes.is_empty() {
1087 ColorGlyphs::empty()
1088 } else {
1089 ColorGlyphs::load(ttf_face.raw_face())?
1090 };
1091
1092 let has_ligatures = ttf_face.tables().gsub.is_some();
1093 let lig_carets = if has_ligatures {
1094 LigatureCaretList::empty()
1095 } else {
1096 LigatureCaretList::load(ttf_face.raw_face())?
1097 };
1098
1099 let mut display_name = None;
1100 let mut family_name = None;
1101 let mut postscript_name = None;
1102 let mut any_name = None::<String>;
1103 for name in ttf_face.names() {
1104 if let Some(n) = name.to_string() {
1105 match name.name_id {
1106 ttf_parser::name_id::FULL_NAME => display_name = Some(n),
1107 ttf_parser::name_id::FAMILY => family_name = Some(n),
1108 ttf_parser::name_id::POST_SCRIPT_NAME => postscript_name = Some(n),
1109 _ => match &mut any_name {
1110 Some(s) => {
1111 if n.len() > s.len() {
1112 *s = n;
1113 }
1114 }
1115 None => any_name = Some(n),
1116 },
1117 }
1118 }
1119 }
1120 let display_name = FontName::new(Txt::from_str(
1121 display_name
1122 .as_ref()
1123 .or(family_name.as_ref())
1124 .or(postscript_name.as_ref())
1125 .or(any_name.as_ref())
1126 .unwrap(),
1127 ));
1128 let family_name = family_name.map(FontName::from).unwrap_or_else(|| display_name.clone());
1129 let postscript_name = postscript_name.map(Txt::from);
1130
1131 if ttf_face.units_per_em() == 0 {
1132 tracing::debug!("font {display_name:?} units_per_em 0");
1134 return Err(FontLoadingError::UnknownFormat);
1135 }
1136
1137 let has_raster_images = {
1139 let t = ttf_face.tables();
1140 t.sbix.is_some() || t.bdat.is_some() || t.ebdt.is_some() || t.cbdt.is_some()
1141 };
1142
1143 let mut flags = FontFaceFlags::empty();
1144 flags.set(FontFaceFlags::IS_MONOSPACE, ttf_face.is_monospaced());
1145 flags.set(FontFaceFlags::HAS_LIGATURES, has_ligatures);
1146 flags.set(FontFaceFlags::HAS_RASTER_IMAGES, has_raster_images);
1147 flags.set(FontFaceFlags::HAS_SVG_IMAGES, ttf_face.tables().svg.is_some());
1148
1149 Ok(FontFace(Arc::new(LoadedFontFace {
1150 face_index,
1151 family_name,
1152 display_name,
1153 postscript_name,
1154 style: ttf_face.style().into(),
1155 weight: ttf_face.weight().into(),
1156 stretch: ttf_face.width().into(),
1157 metrics: ttf_face.into(),
1158 color_palettes,
1159 color_glyphs,
1160 lig_carets,
1161 m: Mutex::new(FontFaceMut {
1162 instances: Default::default(),
1163 render_ids: Default::default(),
1164 unregistered: Default::default(),
1165 }),
1166 data: bytes,
1167 flags,
1168 })))
1169 }
1170
1171 fn on_refresh(&self) {
1172 let mut m = self.0.m.lock();
1173 m.instances.clear();
1174 m.unregistered = true;
1175 }
1176
1177 fn render_face(&self, renderer: &ViewRenderer) -> zng_view_api::font::FontFaceId {
1178 let mut m = self.0.m.lock();
1179 for r in m.render_ids.iter() {
1180 if &r.renderer == renderer {
1181 return r.face_id;
1182 }
1183 }
1184
1185 let key = match renderer.add_font_face((*self.0.data.0).clone(), self.0.face_index) {
1186 Ok(k) => k,
1187 Err(_) => {
1188 tracing::debug!("respawned calling `add_font`, will return dummy font key");
1189 return zng_view_api::font::FontFaceId::INVALID;
1190 }
1191 };
1192
1193 m.render_ids.push(RenderFontFace::new(renderer, key));
1194
1195 key
1196 }
1197
1198 pub fn harfbuzz(&self) -> Option<rustybuzz::Face<'_>> {
1207 if self.is_empty() {
1208 None
1209 } else {
1210 Some(rustybuzz::Face::from_slice(&self.0.data.0, self.0.face_index).unwrap())
1211 }
1212 }
1213
1214 pub fn ttf(&self) -> Option<ttf_parser::Face<'_>> {
1223 if self.is_empty() {
1224 None
1225 } else {
1226 Some(ttf_parser::Face::parse(&self.0.data.0, self.0.face_index).unwrap())
1227 }
1228 }
1229
1230 pub fn bytes(&self) -> &FontDataRef {
1232 &self.0.data
1233 }
1234 pub fn index(&self) -> u32 {
1236 self.0.face_index
1237 }
1238
1239 pub fn display_name(&self) -> &FontName {
1241 &self.0.display_name
1242 }
1243
1244 pub fn family_name(&self) -> &FontName {
1246 &self.0.family_name
1247 }
1248
1249 pub fn postscript_name(&self) -> Option<&str> {
1251 self.0.postscript_name.as_deref()
1252 }
1253
1254 pub fn style(&self) -> FontStyle {
1256 self.0.style
1257 }
1258
1259 pub fn weight(&self) -> FontWeight {
1261 self.0.weight
1262 }
1263
1264 pub fn stretch(&self) -> FontStretch {
1266 self.0.stretch
1267 }
1268
1269 pub fn is_monospace(&self) -> bool {
1271 self.0.flags.contains(FontFaceFlags::IS_MONOSPACE)
1272 }
1273
1274 pub fn metrics(&self) -> &FontFaceMetrics {
1276 &self.0.metrics
1277 }
1278
1279 pub fn sized(&self, font_size: Px, variations: RFontVariations) -> Font {
1288 let key = FontInstanceKey::new(font_size, &variations);
1289 let mut m = self.0.m.lock();
1290 if !m.unregistered {
1291 m.instances
1292 .entry(key)
1293 .or_insert_with(|| Font::new(self.clone(), font_size, variations))
1294 .clone()
1295 } else {
1296 tracing::debug!(target: "font_loading", "creating font from unregistered `{}`, will not cache", self.0.display_name);
1297 Font::new(self.clone(), font_size, variations)
1298 }
1299 }
1300
1301 pub fn synthesis_for(&self, style: FontStyle, weight: FontWeight) -> FontSynthesis {
1303 let mut synth = FontSynthesis::DISABLED;
1304
1305 if style != FontStyle::Normal && self.style() == FontStyle::Normal {
1306 synth |= FontSynthesis::OBLIQUE;
1308 }
1309 if weight > self.weight() {
1310 synth |= FontSynthesis::BOLD;
1313 }
1314
1315 synth
1316 }
1317
1318 pub fn is_cached(&self) -> bool {
1322 !self.0.m.lock().unregistered
1323 }
1324
1325 pub fn color_palettes(&self) -> &ColorPalettes {
1329 &self.0.color_palettes
1330 }
1331
1332 pub fn color_glyphs(&self) -> &ColorGlyphs {
1336 &self.0.color_glyphs
1337 }
1338
1339 pub fn has_ligatures(&self) -> bool {
1341 self.0.flags.contains(FontFaceFlags::HAS_LIGATURES)
1342 }
1343
1344 pub fn has_ligature_caret_offsets(&self) -> bool {
1349 !self.0.lig_carets.is_empty()
1350 }
1351
1352 pub fn has_raster_images(&self) -> bool {
1354 self.0.flags.contains(FontFaceFlags::HAS_RASTER_IMAGES)
1355 }
1356
1357 pub fn has_svg_images(&self) -> bool {
1359 self.0.flags.contains(FontFaceFlags::HAS_SVG_IMAGES)
1360 }
1361}
1362
1363#[derive(Clone)]
1369pub struct Font(Arc<LoadedFont>);
1370struct LoadedFont {
1371 face: FontFace,
1372 size: Px,
1373 variations: RFontVariations,
1374 metrics: FontMetrics,
1375 render_keys: Mutex<Vec<RenderFont>>,
1376 small_word_cache: RwLock<HashMap<WordCacheKey<[u8; Font::SMALL_WORD_LEN]>, ShapedSegmentData>>,
1377 word_cache: RwLock<HashMap<WordCacheKey<String>, ShapedSegmentData>>,
1378}
1379impl fmt::Debug for Font {
1380 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1381 f.debug_struct("Font")
1382 .field("face", &self.0.face)
1383 .field("size", &self.0.size)
1384 .field("metrics", &self.0.metrics)
1385 .field("render_keys.len()", &self.0.render_keys.lock().len())
1386 .field("small_word_cache.len()", &self.0.small_word_cache.read().len())
1387 .field("word_cache.len()", &self.0.word_cache.read().len())
1388 .finish()
1389 }
1390}
1391impl PartialEq for Font {
1392 fn eq(&self, other: &Self) -> bool {
1393 Arc::ptr_eq(&self.0, &other.0)
1394 }
1395}
1396impl Eq for Font {}
1397impl Font {
1398 const SMALL_WORD_LEN: usize = 8;
1399
1400 fn to_small_word(s: &str) -> Option<[u8; Self::SMALL_WORD_LEN]> {
1401 if s.len() <= Self::SMALL_WORD_LEN {
1402 let mut a = [b'\0'; Self::SMALL_WORD_LEN];
1403 a[..s.len()].copy_from_slice(s.as_bytes());
1404 Some(a)
1405 } else {
1406 None
1407 }
1408 }
1409
1410 fn new(face: FontFace, size: Px, variations: RFontVariations) -> Self {
1411 Font(Arc::new(LoadedFont {
1412 metrics: face.metrics().sized(size),
1413 face,
1414 size,
1415 variations,
1416 render_keys: Mutex::new(vec![]),
1417 small_word_cache: RwLock::default(),
1418 word_cache: RwLock::default(),
1419 }))
1420 }
1421
1422 fn render_font(&self, renderer: &ViewRenderer, synthesis: FontSynthesis) -> zng_view_api::font::FontId {
1423 let _span = tracing::trace_span!("Font::render_font").entered();
1424
1425 let mut render_keys = self.0.render_keys.lock();
1426 for r in render_keys.iter() {
1427 if &r.renderer == renderer && r.synthesis == synthesis {
1428 return r.font_id;
1429 }
1430 }
1431
1432 let font_key = self.0.face.render_face(renderer);
1433
1434 let mut opt = zng_view_api::font::FontOptions::default();
1435 opt.synthetic_oblique = synthesis.contains(FontSynthesis::OBLIQUE);
1436 opt.synthetic_bold = synthesis.contains(FontSynthesis::BOLD);
1437 let variations = self.0.variations.iter().map(|v| (v.tag.to_bytes(), v.value)).collect();
1438
1439 let key = match renderer.add_font(font_key, self.0.size, opt, variations) {
1440 Ok(k) => k,
1441 Err(_) => {
1442 tracing::debug!("respawned calling `add_font_instance`, will return dummy font key");
1443 return zng_view_api::font::FontId::INVALID;
1444 }
1445 };
1446
1447 render_keys.push(RenderFont::new(renderer, synthesis, key));
1448
1449 key
1450 }
1451
1452 pub fn face(&self) -> &FontFace {
1454 &self.0.face
1455 }
1456
1457 pub fn harfbuzz(&self) -> Option<rustybuzz::Face<'_>> {
1459 let ppem = self.0.size.0 as u16;
1460
1461 let mut font = self.0.face.harfbuzz()?;
1462
1463 font.set_pixels_per_em(Some((ppem, ppem)));
1464 font.set_variations(&self.0.variations);
1465
1466 Some(font)
1467 }
1468
1469 pub fn size(&self) -> Px {
1473 self.0.size
1474 }
1475
1476 pub fn variations(&self) -> &RFontVariations {
1478 &self.0.variations
1479 }
1480
1481 pub fn metrics(&self) -> &FontMetrics {
1483 &self.0.metrics
1484 }
1485
1486 pub fn ligature_caret_offsets(
1492 &self,
1493 lig: zng_view_api::font::GlyphIndex,
1494 ) -> impl ExactSizeIterator<Item = f32> + DoubleEndedIterator + '_ {
1495 let face = &self.0.face.0;
1496 face.lig_carets.carets(lig).iter().map(move |&o| match o {
1497 ligature_util::LigatureCaret::Coordinate(o) => {
1498 let size_scale = 1.0 / face.metrics.units_per_em as f32 * self.0.size.0 as f32;
1499 o as f32 * size_scale
1500 }
1501 ligature_util::LigatureCaret::GlyphContourPoint(i) => {
1502 if let Some(f) = self.harfbuzz() {
1503 struct Search {
1504 i: u16,
1505 s: u16,
1506 x: f32,
1507 }
1508 impl Search {
1509 fn check(&mut self, x: f32) {
1510 self.s = self.s.saturating_add(1);
1511 if self.s == self.i {
1512 self.x = x;
1513 }
1514 }
1515 }
1516 impl ttf_parser::OutlineBuilder for Search {
1517 fn move_to(&mut self, x: f32, _y: f32) {
1518 self.check(x);
1519 }
1520
1521 fn line_to(&mut self, x: f32, _y: f32) {
1522 self.check(x);
1523 }
1524
1525 fn quad_to(&mut self, _x1: f32, _y1: f32, x: f32, _y: f32) {
1526 self.check(x)
1527 }
1528
1529 fn curve_to(&mut self, _x1: f32, _y1: f32, _x2: f32, _y2: f32, x: f32, _y: f32) {
1530 self.check(x);
1531 }
1532
1533 fn close(&mut self) {}
1534 }
1535 let mut search = Search { i, s: 0, x: 0.0 };
1536 if f.outline_glyph(ttf_parser::GlyphId(lig as _), &mut search).is_some() && search.s >= search.i {
1537 return search.x * self.0.metrics.size_scale;
1538 }
1539 }
1540 0.0
1541 }
1542 })
1543 }
1544}
1545impl zng_app::render::Font for Font {
1546 fn is_empty_fallback(&self) -> bool {
1547 self.face().is_empty()
1548 }
1549
1550 fn renderer_id(&self, renderer: &ViewRenderer, synthesis: FontSynthesis) -> zng_view_api::font::FontId {
1551 self.render_font(renderer, synthesis)
1552 }
1553}
1554
1555#[derive(Debug, Clone)]
1559pub struct FontFaceList {
1560 fonts: Box<[FontFace]>,
1561 requested_style: FontStyle,
1562 requested_weight: FontWeight,
1563 requested_stretch: FontStretch,
1564}
1565impl FontFaceList {
1566 pub fn empty() -> Self {
1568 Self {
1569 fonts: Box::new([FontFace::empty()]),
1570 requested_style: FontStyle::Normal,
1571 requested_weight: FontWeight::NORMAL,
1572 requested_stretch: FontStretch::NORMAL,
1573 }
1574 }
1575
1576 pub fn requested_style(&self) -> FontStyle {
1578 self.requested_style
1579 }
1580
1581 pub fn requested_weight(&self) -> FontWeight {
1583 self.requested_weight
1584 }
1585
1586 pub fn requested_stretch(&self) -> FontStretch {
1588 self.requested_stretch
1589 }
1590
1591 pub fn best(&self) -> &FontFace {
1593 &self.fonts[0]
1594 }
1595
1596 pub fn face_synthesis(&self, face_index: usize) -> FontSynthesis {
1598 if let Some(face) = self.fonts.get(face_index) {
1599 face.synthesis_for(self.requested_style, self.requested_weight)
1600 } else {
1601 FontSynthesis::DISABLED
1602 }
1603 }
1604
1605 pub fn iter(&self) -> std::slice::Iter<'_, FontFace> {
1607 self.fonts.iter()
1608 }
1609
1610 pub fn len(&self) -> usize {
1614 self.fonts.len()
1615 }
1616
1617 pub fn is_empty(&self) -> bool {
1619 self.fonts[0].is_empty() && self.fonts.len() == 1
1620 }
1621
1622 pub fn sized(&self, font_size: Px, variations: RFontVariations) -> FontList {
1626 FontList {
1627 fonts: self.fonts.iter().map(|f| f.sized(font_size, variations.clone())).collect(),
1628 requested_style: self.requested_style,
1629 requested_weight: self.requested_weight,
1630 requested_stretch: self.requested_stretch,
1631 }
1632 }
1633}
1634impl PartialEq for FontFaceList {
1635 fn eq(&self, other: &Self) -> bool {
1637 self.requested_style == other.requested_style
1638 && self.requested_weight == other.requested_weight
1639 && self.requested_stretch == other.requested_stretch
1640 && self.fonts.len() == other.fonts.len()
1641 && self.fonts.iter().zip(other.fonts.iter()).all(|(a, b)| a == b)
1642 }
1643}
1644impl Eq for FontFaceList {}
1645impl std::ops::Deref for FontFaceList {
1646 type Target = [FontFace];
1647
1648 fn deref(&self) -> &Self::Target {
1649 &self.fonts
1650 }
1651}
1652impl<'a> std::iter::IntoIterator for &'a FontFaceList {
1653 type Item = &'a FontFace;
1654
1655 type IntoIter = std::slice::Iter<'a, FontFace>;
1656
1657 fn into_iter(self) -> Self::IntoIter {
1658 self.iter()
1659 }
1660}
1661impl std::ops::Index<usize> for FontFaceList {
1662 type Output = FontFace;
1663
1664 fn index(&self, index: usize) -> &Self::Output {
1665 &self.fonts[index]
1666 }
1667}
1668
1669#[derive(Debug, Clone)]
1671pub struct FontList {
1672 fonts: Box<[Font]>,
1673 requested_style: FontStyle,
1674 requested_weight: FontWeight,
1675 requested_stretch: FontStretch,
1676}
1677#[expect(clippy::len_without_is_empty)] impl FontList {
1679 pub fn best(&self) -> &Font {
1681 &self.fonts[0]
1682 }
1683
1684 pub fn requested_size(&self) -> Px {
1686 self.fonts[0].size()
1687 }
1688
1689 pub fn requested_style(&self) -> FontStyle {
1691 self.requested_style
1692 }
1693
1694 pub fn requested_weight(&self) -> FontWeight {
1696 self.requested_weight
1697 }
1698
1699 pub fn requested_stretch(&self) -> FontStretch {
1701 self.requested_stretch
1702 }
1703
1704 pub fn face_synthesis(&self, font_index: usize) -> FontSynthesis {
1706 if let Some(font) = self.fonts.get(font_index) {
1707 font.0.face.synthesis_for(self.requested_style, self.requested_weight)
1708 } else {
1709 FontSynthesis::DISABLED
1710 }
1711 }
1712
1713 pub fn iter(&self) -> std::slice::Iter<'_, Font> {
1715 self.fonts.iter()
1716 }
1717
1718 pub fn len(&self) -> usize {
1722 self.fonts.len()
1723 }
1724
1725 pub fn is_sized_from(&self, faces: &FontFaceList) -> bool {
1727 if self.len() != faces.len() {
1728 return false;
1729 }
1730
1731 for (font, face) in self.iter().zip(faces.iter()) {
1732 if font.face() != face {
1733 return false;
1734 }
1735 }
1736
1737 true
1738 }
1739}
1740impl PartialEq for FontList {
1741 fn eq(&self, other: &Self) -> bool {
1743 self.requested_style == other.requested_style
1744 && self.requested_weight == other.requested_weight
1745 && self.requested_stretch == other.requested_stretch
1746 && self.fonts.len() == other.fonts.len()
1747 && self.fonts.iter().zip(other.fonts.iter()).all(|(a, b)| a == b)
1748 }
1749}
1750impl Eq for FontList {}
1751impl std::ops::Deref for FontList {
1752 type Target = [Font];
1753
1754 fn deref(&self) -> &Self::Target {
1755 &self.fonts
1756 }
1757}
1758impl<'a> std::iter::IntoIterator for &'a FontList {
1759 type Item = &'a Font;
1760
1761 type IntoIter = std::slice::Iter<'a, Font>;
1762
1763 fn into_iter(self) -> Self::IntoIter {
1764 self.iter()
1765 }
1766}
1767impl<I: SliceIndex<[Font]>> std::ops::Index<I> for FontList {
1768 type Output = I::Output;
1769
1770 fn index(&self, index: I) -> &I::Output {
1771 &self.fonts[index]
1772 }
1773}
1774
1775struct FontFaceLoader {
1776 custom_fonts: HashMap<FontName, Vec<FontFace>>,
1777 unregister_requests: Vec<(FontName, ResponderVar<bool>)>,
1778
1779 system_fonts_cache: HashMap<FontName, Vec<SystemFontFace>>,
1780 list_cache: HashMap<Box<[FontName]>, Vec<FontFaceListQuery>>,
1781}
1782struct SystemFontFace {
1783 properties: (FontStyle, FontWeight, FontStretch),
1784 result: ResponseVar<Option<FontFace>>,
1785}
1786struct FontFaceListQuery {
1787 properties: (FontStyle, FontWeight, FontStretch),
1788 lang: Lang,
1789 result: ResponseVar<FontFaceList>,
1790}
1791impl FontFaceLoader {
1792 fn new() -> Self {
1793 FontFaceLoader {
1794 custom_fonts: HashMap::new(),
1795 unregister_requests: vec![],
1796 system_fonts_cache: HashMap::new(),
1797 list_cache: HashMap::new(),
1798 }
1799 }
1800
1801 fn on_view_process_respawn(&mut self) {
1802 let sys_fonts = self.system_fonts_cache.values().flatten().filter_map(|f| f.result.rsp().flatten());
1803 for face in self.custom_fonts.values().flatten().cloned().chain(sys_fonts) {
1804 let mut m = face.0.m.lock();
1805 m.render_ids.clear();
1806 for inst in m.instances.values() {
1807 inst.0.render_keys.lock().clear();
1808 }
1809 }
1810 }
1811
1812 fn on_refresh(&mut self) {
1813 for (_, sys_family) in self.system_fonts_cache.drain() {
1814 for sys_font in sys_family {
1815 sys_font.result.with(|r| {
1816 if let Some(Some(face)) = r.done() {
1817 face.on_refresh();
1818 }
1819 });
1820 }
1821 }
1822 }
1823 fn on_prune(&mut self) {
1824 self.system_fonts_cache.retain(|_, v| {
1825 v.retain(|sff| {
1826 if sff.result.strong_count() == 1 {
1827 sff.result.with(|r| {
1828 match r.done() {
1829 Some(Some(face)) => Arc::strong_count(&face.0) > 1, Some(None) => false, None => true, }
1833 })
1834 } else {
1835 true
1837 }
1838 });
1839 !v.is_empty()
1840 });
1841
1842 self.list_cache.clear();
1843 }
1844
1845 fn register(custom_font: CustomFont) -> ResponseVar<Result<FontFace, FontLoadingError>> {
1846 let resp = task::respond(FontFace::load_custom(custom_font));
1848
1849 resp.hook(|args| {
1851 if let Some(done) = args.value().done() {
1852 if let Ok(face) = done {
1853 let mut fonts = FONTS_SV.write();
1854 let family = fonts.loader.custom_fonts.entry(face.0.family_name.clone()).or_default();
1855 let existing = family
1856 .iter()
1857 .position(|f| f.0.weight == face.0.weight && f.0.style == face.0.style && f.0.stretch == face.0.stretch);
1858
1859 if let Some(i) = existing {
1860 family[i] = face.clone();
1861 } else {
1862 family.push(face.clone());
1863 }
1864
1865 FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::CustomFonts));
1866 }
1867 false
1868 } else {
1869 true
1870 }
1871 })
1872 .perm();
1873 resp
1874 }
1875
1876 fn unregister(&mut self, custom_family: FontName) -> ResponseVar<bool> {
1877 let (responder, response) = response_var();
1878
1879 if !self.unregister_requests.is_empty() {
1880 UPDATES.update(None);
1881 }
1882 self.unregister_requests.push((custom_family, responder));
1883
1884 response
1885 }
1886
1887 fn try_list(
1888 &self,
1889 families: &[FontName],
1890 style: FontStyle,
1891 weight: FontWeight,
1892 stretch: FontStretch,
1893 lang: &Lang,
1894 ) -> Option<ResponseVar<FontFaceList>> {
1895 if let Some(queries) = self.list_cache.get(families) {
1896 for q in queries {
1897 if q.properties == (style, weight, stretch) && &q.lang == lang {
1898 return Some(q.result.clone());
1899 }
1900 }
1901 }
1902 None
1903 }
1904
1905 fn load_list(
1906 &mut self,
1907 families: &[FontName],
1908 style: FontStyle,
1909 weight: FontWeight,
1910 stretch: FontStretch,
1911 lang: &Lang,
1912 ) -> ResponseVar<FontFaceList> {
1913 if let Some(r) = self.try_list(families, style, weight, stretch, lang) {
1914 return r;
1915 }
1916
1917 let mut list = Vec::with_capacity(families.len() + 1);
1918 let mut pending = vec![];
1919
1920 {
1921 let fallback = [GenericFonts {}.fallback(lang)];
1922 let mut used = HashSet::with_capacity(families.len());
1923 for name in families.iter().chain(&fallback) {
1924 if !used.insert(name) {
1925 continue;
1926 }
1927
1928 let face = self.load(name, style, weight, stretch, lang);
1929 if face.is_done() {
1930 if let Some(face) = face.rsp().unwrap() {
1931 list.push(face);
1932 }
1933 } else {
1934 pending.push((list.len(), face));
1935 }
1936 }
1937 }
1938
1939 let r = if pending.is_empty() {
1940 if list.is_empty() {
1941 tracing::error!(target: "font_loading", "failed to load fallback font");
1942 list.push(FontFace::empty());
1943 }
1944 response_done_var(FontFaceList {
1945 fonts: list.into_boxed_slice(),
1946 requested_style: style,
1947 requested_weight: weight,
1948 requested_stretch: stretch,
1949 })
1950 } else {
1951 task::respond(async move {
1952 for (i, pending) in pending.into_iter().rev() {
1953 if let Some(rsp) = pending.wait_rsp().await {
1954 list.insert(i, rsp);
1955 }
1956 }
1957
1958 if list.is_empty() {
1959 tracing::error!(target: "font_loading", "failed to load fallback font");
1960 list.push(FontFace::empty());
1961 }
1962
1963 FontFaceList {
1964 fonts: list.into_boxed_slice(),
1965 requested_style: style,
1966 requested_weight: weight,
1967 requested_stretch: stretch,
1968 }
1969 })
1970 };
1971
1972 self.list_cache
1973 .entry(families.iter().cloned().collect())
1974 .or_insert_with(|| Vec::with_capacity(1))
1975 .push(FontFaceListQuery {
1976 properties: (style, weight, stretch),
1977 lang: lang.clone(),
1978 result: r.clone(),
1979 });
1980
1981 r
1982 }
1983
1984 fn try_cached(
1985 &self,
1986 font_name: &FontName,
1987 style: FontStyle,
1988 weight: FontWeight,
1989 stretch: FontStretch,
1990 lang: &Lang,
1991 ) -> Option<ResponseVar<Option<FontFace>>> {
1992 let resolved = GenericFonts {}.resolve(font_name, lang);
1993 let font_name = resolved.as_ref().unwrap_or(font_name);
1994 self.try_resolved(font_name, style, weight, stretch)
1995 }
1996
1997 fn load(
1999 &mut self,
2000 font_name: &FontName,
2001 style: FontStyle,
2002 weight: FontWeight,
2003 stretch: FontStretch,
2004 lang: &Lang,
2005 ) -> ResponseVar<Option<FontFace>> {
2006 let resolved = GenericFonts {}.resolve(font_name, lang);
2007 let font_name = resolved.as_ref().unwrap_or(font_name);
2008 self.load_resolved(font_name, style, weight, stretch)
2009 }
2010
2011 fn try_resolved(
2013 &self,
2014 font_name: &FontName,
2015 style: FontStyle,
2016 weight: FontWeight,
2017 stretch: FontStretch,
2018 ) -> Option<ResponseVar<Option<FontFace>>> {
2019 if let Some(custom_family) = self.custom_fonts.get(font_name) {
2020 let custom = Self::match_custom(custom_family, style, weight, stretch);
2021 return Some(response_done_var(Some(custom)));
2022 }
2023
2024 if let Some(cached_sys_family) = self.system_fonts_cache.get(font_name) {
2025 for sys_face in cached_sys_family.iter() {
2026 if sys_face.properties == (style, weight, stretch) {
2027 return Some(sys_face.result.clone());
2028 }
2029 }
2030 }
2031
2032 None
2033 }
2034
2035 fn load_resolved(
2037 &mut self,
2038 font_name: &FontName,
2039 style: FontStyle,
2040 weight: FontWeight,
2041 stretch: FontStretch,
2042 ) -> ResponseVar<Option<FontFace>> {
2043 if let Some(cached) = self.try_resolved(font_name, style, weight, stretch) {
2044 return cached;
2045 }
2046
2047 let load = task::wait(clmv!(font_name, || {
2048 let (bytes, face_index) = match Self::get_system(&font_name, style, weight, stretch) {
2049 Some(h) => h,
2050 None => {
2051 #[cfg(debug_assertions)]
2052 static NOT_FOUND: Mutex<Option<HashSet<FontName>>> = Mutex::new(None);
2053
2054 #[cfg(debug_assertions)]
2055 if NOT_FOUND.lock().get_or_insert_with(HashSet::default).insert(font_name.clone()) {
2056 tracing::debug!(r#"font "{font_name}" not found"#);
2057 }
2058
2059 return None;
2060 }
2061 };
2062 match FontFace::load(bytes, face_index) {
2063 Ok(f) => Some(f),
2064 Err(FontLoadingError::UnknownFormat) => None,
2065 Err(e) => {
2066 tracing::error!(target: "font_loading", "failed to load system font, {e}\nquery: {:?}", (font_name, style, weight, stretch));
2067 None
2068 }
2069 }
2070 }));
2071 let result = task::respond(async_clmv!(font_name, {
2072 match task::with_deadline(load, 10.secs()).await {
2073 Ok(r) => r,
2074 Err(_) => {
2075 tracing::error!(target: "font_loading", "timeout loading {font_name:?}");
2076 None
2077 }
2078 }
2079 }));
2080
2081 self.system_fonts_cache
2082 .entry(font_name.clone())
2083 .or_insert_with(|| Vec::with_capacity(1))
2084 .push(SystemFontFace {
2085 properties: (style, weight, stretch),
2086 result: result.clone(),
2087 });
2088
2089 result
2090 }
2091
2092 fn get_system(font_name: &FontName, style: FontStyle, weight: FontWeight, stretch: FontStretch) -> Option<(FontDataRef, u32)> {
2093 let _span = tracing::trace_span!("FontFaceLoader::get_system").entered();
2094 match query_util::best(font_name, style, weight, stretch) {
2095 Ok(r) => r,
2096 Err(e) => {
2097 tracing::error!("cannot get `{font_name}` system font, {e}");
2098 None
2099 }
2100 }
2101 }
2102
2103 fn match_custom(faces: &[FontFace], style: FontStyle, weight: FontWeight, stretch: FontStretch) -> FontFace {
2104 if faces.len() == 1 {
2105 return faces[0].clone();
2107 }
2108
2109 let mut set = Vec::with_capacity(faces.len());
2110 let mut set_dist = 0.0f64; let wrong_side = if stretch <= FontStretch::NORMAL {
2117 |s| s > FontStretch::NORMAL
2118 } else {
2119 |s| s <= FontStretch::NORMAL
2120 };
2121 for face in faces {
2122 let mut dist = (face.stretch().0 - stretch.0).abs() as f64;
2123 if wrong_side(face.stretch()) {
2124 dist += f32::MAX as f64 + 1.0;
2125 }
2126
2127 if set.is_empty() {
2128 set.push(face);
2129 set_dist = dist;
2130 } else if dist < set_dist {
2131 set_dist = dist;
2133 set.clear();
2134 set.push(face);
2135 } else if (dist - set_dist).abs() < 0.0001 {
2136 set.push(face);
2138 }
2139 }
2140 if set.len() == 1 {
2141 return set[0].clone();
2142 }
2143
2144 let style_pref = match style {
2149 FontStyle::Normal => [FontStyle::Normal, FontStyle::Oblique, FontStyle::Italic],
2150 FontStyle::Italic => [FontStyle::Italic, FontStyle::Oblique, FontStyle::Normal],
2151 FontStyle::Oblique => [FontStyle::Oblique, FontStyle::Italic, FontStyle::Normal],
2152 };
2153 let mut best_style = style_pref.len();
2154 for face in &set {
2155 let i = style_pref.iter().position(|&s| s == face.style()).unwrap();
2156 if i < best_style {
2157 best_style = i;
2158 }
2159 }
2160 set.retain(|f| f.style() == style_pref[best_style]);
2161 if set.len() == 1 {
2162 return set[0].clone();
2163 }
2164
2165 let add_penalty = if weight.0 >= 400.0 && weight.0 <= 500.0 {
2173 |face: &FontFace, weight: FontWeight, dist: &mut f64| {
2175 if face.weight() < weight {
2177 *dist += 100.0;
2179 } else if face.weight().0 > 500.0 {
2180 *dist += 600.0;
2182 }
2183 }
2184 } else if weight.0 < 400.0 {
2185 |face: &FontFace, weight: FontWeight, dist: &mut f64| {
2187 if face.weight() > weight {
2188 *dist += weight.0 as f64;
2189 }
2190 }
2191 } else {
2192 debug_assert!(weight.0 > 500.0);
2193 |face: &FontFace, weight: FontWeight, dist: &mut f64| {
2195 if face.weight() < weight {
2196 *dist += f32::MAX as f64;
2197 }
2198 }
2199 };
2200
2201 let mut best = set[0];
2202 let mut best_dist = f64::MAX;
2203
2204 for face in &set {
2205 let mut dist = (face.weight().0 - weight.0).abs() as f64;
2206
2207 add_penalty(face, weight, &mut dist);
2208
2209 if dist < best_dist {
2210 best_dist = dist;
2211 best = face;
2212 }
2213 }
2214
2215 best.clone()
2216 }
2217}
2218
2219struct RenderFontFace {
2220 renderer: ViewRenderer,
2221 face_id: zng_view_api::font::FontFaceId,
2222}
2223impl RenderFontFace {
2224 fn new(renderer: &ViewRenderer, face_id: zng_view_api::font::FontFaceId) -> Self {
2225 RenderFontFace {
2226 renderer: renderer.clone(),
2227 face_id,
2228 }
2229 }
2230}
2231impl Drop for RenderFontFace {
2232 fn drop(&mut self) {
2233 let _ = self.renderer.delete_font_face(self.face_id);
2235 }
2236}
2237
2238struct RenderFont {
2239 renderer: ViewRenderer,
2240 synthesis: FontSynthesis,
2241 font_id: zng_view_api::font::FontId,
2242}
2243impl RenderFont {
2244 fn new(renderer: &ViewRenderer, synthesis: FontSynthesis, font_id: zng_view_api::font::FontId) -> RenderFont {
2245 RenderFont {
2246 renderer: renderer.clone(),
2247 synthesis,
2248 font_id,
2249 }
2250 }
2251}
2252impl Drop for RenderFont {
2253 fn drop(&mut self) {
2254 let _ = self.renderer.delete_font(self.font_id);
2256 }
2257}
2258
2259app_local! {
2260 static GENERIC_FONTS_SV: GenericFontsService = GenericFontsService::new();
2261}
2262
2263struct GenericFontsService {
2264 serif: LangMap<FontName>,
2265 sans_serif: LangMap<FontName>,
2266 monospace: LangMap<FontName>,
2267 cursive: LangMap<FontName>,
2268 fantasy: LangMap<FontName>,
2269 fallback: LangMap<FontName>,
2270
2271 requests: Vec<Box<dyn FnOnce(&mut GenericFontsService) + Send + Sync>>,
2272}
2273impl GenericFontsService {
2274 fn new() -> Self {
2275 fn default(name: impl Into<FontName>) -> LangMap<FontName> {
2276 let mut f = LangMap::with_capacity(1);
2277 f.insert(lang!(und), name.into());
2278 f
2279 }
2280
2281 let serif = "serif";
2282 let sans_serif = "sans-serif";
2283 let monospace = "monospace";
2284 let cursive = "cursive";
2285 let fantasy = "fantasy";
2286 let fallback = if cfg!(windows) {
2287 "Segoe UI Symbol"
2288 } else if cfg!(target_os = "linux") {
2289 "Standard Symbols PS"
2290 } else {
2291 "sans-serif"
2292 };
2293
2294 GenericFontsService {
2295 serif: default(serif),
2296 sans_serif: default(sans_serif),
2297 monospace: default(monospace),
2298 cursive: default(cursive),
2299 fantasy: default(fantasy),
2300
2301 fallback: default(fallback),
2302
2303 requests: vec![],
2304 }
2305 }
2306}
2307
2308#[non_exhaustive]
2323pub struct GenericFonts {}
2324macro_rules! impl_fallback_accessors {
2325 ($($name:ident=$name_str:tt),+ $(,)?) => {$($crate::paste! {
2326 #[doc = "Gets the fallback *"$name_str "* font for the given language."]
2327 #[doc = "Note that the returned name can still be the generic `\""$name_str "\"`, this delegates the resolution to the operating system."]
2331
2332 pub fn $name(&self, lang: &Lang) -> FontName {
2333 GENERIC_FONTS_SV.read().$name.get(lang).unwrap().clone()
2334 }
2335
2336 #[doc = "Sets the fallback *"$name_str "* font for the given language."]
2337 pub fn [<set_ $name>]<F: Into<FontName>>(&self, lang: Lang, font_name: F) {
2342 let mut g = GENERIC_FONTS_SV.write();
2343 let font_name = font_name.into();
2344 if g.requests.is_empty() {
2345 UPDATES.update(None);
2346 }
2347 g.requests.push(Box::new(move |g| {
2348 g.$name.insert(lang.clone(), font_name);
2349 FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::GenericFont(FontName::$name(), lang)));
2350 }));
2351 }
2352 })+};
2353}
2354impl GenericFonts {
2355 #[rustfmt::skip] impl_fallback_accessors! {
2357 serif="serif", sans_serif="sans-serif", monospace="monospace", cursive="cursive", fantasy="fantasy"
2358 }
2359
2360 pub fn fallback(&self, lang: &Lang) -> FontName {
2364 GENERIC_FONTS_SV.read().fallback.get(lang).unwrap().clone()
2365 }
2366
2367 pub fn set_fallback<F: Into<FontName>>(&self, lang: Lang, font_name: F) {
2373 let mut g = GENERIC_FONTS_SV.write();
2374 if g.requests.is_empty() {
2375 UPDATES.update(None);
2376 }
2377 let font_name = font_name.into();
2378 g.requests.push(Box::new(move |g| {
2379 FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::Fallback(lang.clone())));
2380 g.fallback.insert(lang, font_name);
2381 }));
2382 }
2383
2384 pub fn resolve(&self, name: &FontName, lang: &Lang) -> Option<FontName> {
2388 if name == &FontName::serif() {
2389 Some(self.serif(lang))
2390 } else if name == &FontName::sans_serif() {
2391 Some(self.sans_serif(lang))
2392 } else if name == &FontName::monospace() {
2393 Some(self.monospace(lang))
2394 } else if name == &FontName::cursive() {
2395 Some(self.cursive(lang))
2396 } else if name == &FontName::fantasy() {
2397 Some(self.fantasy(lang))
2398 } else {
2399 None
2400 }
2401 }
2402}
2403
2404#[derive(Clone)]
2406pub struct FontDataRef(pub Arc<Vec<u8>>);
2407impl FontDataRef {
2408 pub fn from_static(data: &'static [u8]) -> Self {
2410 FontDataRef(Arc::new(data.to_vec()))
2411 }
2412}
2413impl fmt::Debug for FontDataRef {
2414 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2415 write!(f, "FontDataRef(Arc<{} bytes>>)", self.0.len())
2416 }
2417}
2418impl std::ops::Deref for FontDataRef {
2419 type Target = [u8];
2420
2421 fn deref(&self) -> &Self::Target {
2422 self.0.deref()
2423 }
2424}
2425
2426#[derive(Debug, Clone)]
2427enum FontSource {
2428 File(PathBuf, u32),
2429 Memory(FontDataRef, u32),
2430 Alias(FontName),
2431}
2432
2433#[derive(Debug, Clone)]
2435pub struct CustomFont {
2436 name: FontName,
2437 source: FontSource,
2438 stretch: FontStretch,
2439 style: FontStyle,
2440 weight: FontWeight,
2441}
2442impl CustomFont {
2443 pub fn from_file<N: Into<FontName>, P: Into<PathBuf>>(name: N, path: P, font_index: u32) -> Self {
2451 CustomFont {
2452 name: name.into(),
2453 source: FontSource::File(path.into(), font_index),
2454 stretch: FontStretch::NORMAL,
2455 style: FontStyle::Normal,
2456 weight: FontWeight::NORMAL,
2457 }
2458 }
2459
2460 pub fn from_bytes<N: Into<FontName>>(name: N, data: FontDataRef, font_index: u32) -> Self {
2468 CustomFont {
2469 name: name.into(),
2470 source: FontSource::Memory(data, font_index),
2471 stretch: FontStretch::NORMAL,
2472 style: FontStyle::Normal,
2473 weight: FontWeight::NORMAL,
2474 }
2475 }
2476
2477 pub fn from_other<N: Into<FontName>, O: Into<FontName>>(name: N, other_font: O) -> Self {
2483 CustomFont {
2484 name: name.into(),
2485 source: FontSource::Alias(other_font.into()),
2486 stretch: FontStretch::NORMAL,
2487 style: FontStyle::Normal,
2488 weight: FontWeight::NORMAL,
2489 }
2490 }
2491
2492 pub fn stretch(mut self, stretch: FontStretch) -> Self {
2496 self.stretch = stretch;
2497 self
2498 }
2499
2500 pub fn style(mut self, style: FontStyle) -> Self {
2504 self.style = style;
2505 self
2506 }
2507
2508 pub fn weight(mut self, weight: FontWeight) -> Self {
2512 self.weight = weight;
2513 self
2514 }
2515}
2516
2517#[derive(Clone, Copy, serde::Serialize, serde::Deserialize, Transitionable)]
2521#[serde(transparent)]
2522pub struct FontStretch(pub f32);
2523impl fmt::Debug for FontStretch {
2524 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2525 let name = self.name();
2526 if name.is_empty() {
2527 f.debug_tuple("FontStretch").field(&self.0).finish()
2528 } else {
2529 if f.alternate() {
2530 write!(f, "FontStretch::")?;
2531 }
2532 write!(f, "{name}")
2533 }
2534 }
2535}
2536impl PartialOrd for FontStretch {
2537 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
2538 Some(self.cmp(other))
2539 }
2540}
2541impl Ord for FontStretch {
2542 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
2543 about_eq_ord(self.0, other.0, EQ_GRANULARITY)
2544 }
2545}
2546impl PartialEq for FontStretch {
2547 fn eq(&self, other: &Self) -> bool {
2548 about_eq(self.0, other.0, EQ_GRANULARITY)
2549 }
2550}
2551impl Eq for FontStretch {}
2552impl std::hash::Hash for FontStretch {
2553 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
2554 about_eq_hash(self.0, EQ_GRANULARITY, state)
2555 }
2556}
2557impl Default for FontStretch {
2558 fn default() -> FontStretch {
2559 FontStretch::NORMAL
2560 }
2561}
2562impl FontStretch {
2563 pub const ULTRA_CONDENSED: FontStretch = FontStretch(0.5);
2565 pub const EXTRA_CONDENSED: FontStretch = FontStretch(0.625);
2567 pub const CONDENSED: FontStretch = FontStretch(0.75);
2569 pub const SEMI_CONDENSED: FontStretch = FontStretch(0.875);
2571 pub const NORMAL: FontStretch = FontStretch(1.0);
2573 pub const SEMI_EXPANDED: FontStretch = FontStretch(1.125);
2575 pub const EXPANDED: FontStretch = FontStretch(1.25);
2577 pub const EXTRA_EXPANDED: FontStretch = FontStretch(1.5);
2579 pub const ULTRA_EXPANDED: FontStretch = FontStretch(2.0);
2581
2582 pub fn name(self) -> &'static str {
2584 macro_rules! name {
2585 ($($CONST:ident;)+) => {$(
2586 if self == Self::$CONST {
2587 return stringify!($CONST);
2588 }
2589 )+}
2590 }
2591 name! {
2592 ULTRA_CONDENSED;
2593 EXTRA_CONDENSED;
2594 CONDENSED;
2595 SEMI_CONDENSED;
2596 NORMAL;
2597 SEMI_EXPANDED;
2598 EXPANDED;
2599 EXTRA_EXPANDED;
2600 ULTRA_EXPANDED;
2601 }
2602 ""
2603 }
2604}
2605impl_from_and_into_var! {
2606 fn from(fct: Factor) -> FontStretch {
2607 FontStretch(fct.0)
2608 }
2609 fn from(pct: FactorPercent) -> FontStretch {
2610 FontStretch(pct.fct().0)
2611 }
2612 fn from(fct: f32) -> FontStretch {
2613 FontStretch(fct)
2614 }
2615}
2616impl From<ttf_parser::Width> for FontStretch {
2617 fn from(value: ttf_parser::Width) -> Self {
2618 use ttf_parser::Width::*;
2619 match value {
2620 UltraCondensed => FontStretch::ULTRA_CONDENSED,
2621 ExtraCondensed => FontStretch::EXTRA_CONDENSED,
2622 Condensed => FontStretch::CONDENSED,
2623 SemiCondensed => FontStretch::SEMI_CONDENSED,
2624 Normal => FontStretch::NORMAL,
2625 SemiExpanded => FontStretch::SEMI_EXPANDED,
2626 Expanded => FontStretch::EXPANDED,
2627 ExtraExpanded => FontStretch::EXTRA_EXPANDED,
2628 UltraExpanded => FontStretch::ULTRA_EXPANDED,
2629 }
2630 }
2631}
2632impl From<FontStretch> for ttf_parser::Width {
2633 fn from(value: FontStretch) -> Self {
2634 if value <= FontStretch::ULTRA_CONDENSED {
2635 ttf_parser::Width::UltraCondensed
2636 } else if value <= FontStretch::EXTRA_CONDENSED {
2637 ttf_parser::Width::ExtraCondensed
2638 } else if value <= FontStretch::CONDENSED {
2639 ttf_parser::Width::Condensed
2640 } else if value <= FontStretch::SEMI_CONDENSED {
2641 ttf_parser::Width::SemiCondensed
2642 } else if value <= FontStretch::NORMAL {
2643 ttf_parser::Width::Normal
2644 } else if value <= FontStretch::SEMI_EXPANDED {
2645 ttf_parser::Width::SemiExpanded
2646 } else if value <= FontStretch::EXPANDED {
2647 ttf_parser::Width::Expanded
2648 } else if value <= FontStretch::EXTRA_EXPANDED {
2649 ttf_parser::Width::ExtraExpanded
2650 } else {
2651 ttf_parser::Width::UltraExpanded
2652 }
2653 }
2654}
2655
2656#[derive(Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]
2658pub enum FontStyle {
2659 #[default]
2661 Normal,
2662 Italic,
2664 Oblique,
2666}
2667impl fmt::Debug for FontStyle {
2668 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2669 if f.alternate() {
2670 write!(f, "FontStyle::")?;
2671 }
2672 match self {
2673 Self::Normal => write!(f, "Normal"),
2674 Self::Italic => write!(f, "Italic"),
2675 Self::Oblique => write!(f, "Oblique"),
2676 }
2677 }
2678}
2679impl From<ttf_parser::Style> for FontStyle {
2680 fn from(value: ttf_parser::Style) -> Self {
2681 use ttf_parser::Style::*;
2682 match value {
2683 Normal => FontStyle::Normal,
2684 Italic => FontStyle::Italic,
2685 Oblique => FontStyle::Oblique,
2686 }
2687 }
2688}
2689
2690impl From<FontStyle> for ttf_parser::Style {
2691 fn from(value: FontStyle) -> Self {
2692 match value {
2693 FontStyle::Normal => Self::Normal,
2694 FontStyle::Italic => Self::Italic,
2695 FontStyle::Oblique => Self::Oblique,
2696 }
2697 }
2698}
2699
2700#[derive(Clone, Copy, Transitionable, serde::Serialize, serde::Deserialize)]
2703pub struct FontWeight(pub f32);
2704impl Default for FontWeight {
2705 fn default() -> FontWeight {
2706 FontWeight::NORMAL
2707 }
2708}
2709impl fmt::Debug for FontWeight {
2710 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2711 let name = self.name();
2712 if name.is_empty() {
2713 f.debug_tuple("FontWeight").field(&self.0).finish()
2714 } else {
2715 if f.alternate() {
2716 write!(f, "FontWeight::")?;
2717 }
2718 write!(f, "{name}")
2719 }
2720 }
2721}
2722impl PartialOrd for FontWeight {
2723 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
2724 Some(self.cmp(other))
2725 }
2726}
2727impl Ord for FontWeight {
2728 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
2729 about_eq_ord(self.0, other.0, EQ_GRANULARITY_100)
2730 }
2731}
2732impl PartialEq for FontWeight {
2733 fn eq(&self, other: &Self) -> bool {
2734 about_eq(self.0, other.0, EQ_GRANULARITY_100)
2735 }
2736}
2737impl Eq for FontWeight {}
2738impl std::hash::Hash for FontWeight {
2739 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
2740 about_eq_hash(self.0, EQ_GRANULARITY_100, state)
2741 }
2742}
2743impl FontWeight {
2744 pub const THIN: FontWeight = FontWeight(100.0);
2746 pub const EXTRA_LIGHT: FontWeight = FontWeight(200.0);
2748 pub const LIGHT: FontWeight = FontWeight(300.0);
2750 pub const NORMAL: FontWeight = FontWeight(400.0);
2752 pub const MEDIUM: FontWeight = FontWeight(500.0);
2754 pub const SEMIBOLD: FontWeight = FontWeight(600.0);
2756 pub const BOLD: FontWeight = FontWeight(700.0);
2758 pub const EXTRA_BOLD: FontWeight = FontWeight(800.0);
2760 pub const BLACK: FontWeight = FontWeight(900.0);
2762
2763 pub fn name(self) -> &'static str {
2765 macro_rules! name {
2766 ($($CONST:ident;)+) => {$(
2767 if self == Self::$CONST {
2768 return stringify!($CONST);
2769 }
2770 )+}
2771 }
2772 name! {
2773 THIN;
2774 EXTRA_LIGHT;
2775 LIGHT;
2776 NORMAL;
2777 MEDIUM;
2778 SEMIBOLD;
2779 BOLD;
2780 EXTRA_BOLD;
2781 BLACK;
2782 }
2783 ""
2784 }
2785}
2786impl_from_and_into_var! {
2787 fn from(weight: u32) -> FontWeight {
2788 FontWeight(weight as f32)
2789 }
2790 fn from(weight: f32) -> FontWeight {
2791 FontWeight(weight)
2792 }
2793}
2794impl From<ttf_parser::Weight> for FontWeight {
2795 fn from(value: ttf_parser::Weight) -> Self {
2796 use ttf_parser::Weight::*;
2797 match value {
2798 Thin => FontWeight::THIN,
2799 ExtraLight => FontWeight::EXTRA_LIGHT,
2800 Light => FontWeight::LIGHT,
2801 Normal => FontWeight::NORMAL,
2802 Medium => FontWeight::MEDIUM,
2803 SemiBold => FontWeight::SEMIBOLD,
2804 Bold => FontWeight::BOLD,
2805 ExtraBold => FontWeight::EXTRA_BOLD,
2806 Black => FontWeight::BLACK,
2807 Other(o) => FontWeight(o as f32),
2808 }
2809 }
2810}
2811impl From<FontWeight> for ttf_parser::Weight {
2812 fn from(value: FontWeight) -> Self {
2813 ttf_parser::Weight::from(value.0 as u16)
2814 }
2815}
2816
2817#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
2819pub enum LineBreak {
2820 Auto,
2822 Loose,
2824 Normal,
2826 Strict,
2828 Anywhere,
2830}
2831impl Default for LineBreak {
2832 fn default() -> Self {
2834 LineBreak::Auto
2835 }
2836}
2837impl fmt::Debug for LineBreak {
2838 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2839 if f.alternate() {
2840 write!(f, "LineBreak::")?;
2841 }
2842 match self {
2843 LineBreak::Auto => write!(f, "Auto"),
2844 LineBreak::Loose => write!(f, "Loose"),
2845 LineBreak::Normal => write!(f, "Normal"),
2846 LineBreak::Strict => write!(f, "Strict"),
2847 LineBreak::Anywhere => write!(f, "Anywhere"),
2848 }
2849 }
2850}
2851
2852#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
2854pub enum Hyphens {
2855 None,
2857 Manual,
2862 Auto,
2864}
2865impl Default for Hyphens {
2866 fn default() -> Self {
2868 Hyphens::Auto
2869 }
2870}
2871impl fmt::Debug for Hyphens {
2872 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2873 if f.alternate() {
2874 write!(f, "Hyphens::")?;
2875 }
2876 match self {
2877 Hyphens::None => write!(f, "None"),
2878 Hyphens::Manual => write!(f, "Manual"),
2879 Hyphens::Auto => write!(f, "Auto"),
2880 }
2881 }
2882}
2883
2884#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
2890pub enum WordBreak {
2891 Normal,
2893 BreakAll,
2895 KeepAll,
2897}
2898impl Default for WordBreak {
2899 fn default() -> Self {
2901 WordBreak::Normal
2902 }
2903}
2904impl fmt::Debug for WordBreak {
2905 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2906 if f.alternate() {
2907 write!(f, "WordBreak::")?;
2908 }
2909 match self {
2910 WordBreak::Normal => write!(f, "Normal"),
2911 WordBreak::BreakAll => write!(f, "BreakAll"),
2912 WordBreak::KeepAll => write!(f, "KeepAll"),
2913 }
2914 }
2915}
2916
2917#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
2919pub enum Justify {
2920 Auto,
2924 InterWord,
2926 InterLetter,
2928}
2929impl Default for Justify {
2930 fn default() -> Self {
2932 Justify::Auto
2933 }
2934}
2935impl Justify {
2936 pub fn resolve(self, lang: &Lang) -> Self {
2938 match self {
2939 Self::Auto => match lang.language.as_str() {
2940 "zh" | "ja" | "ko" => Self::InterLetter,
2941 _ => Self::InterWord,
2942 },
2943 m => m,
2944 }
2945 }
2946}
2947impl fmt::Debug for Justify {
2948 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2949 if f.alternate() {
2950 write!(f, "Justify::")?;
2951 }
2952 match self {
2953 Justify::Auto => write!(f, "Auto"),
2954 Justify::InterWord => write!(f, "InterWord"),
2955 Justify::InterLetter => write!(f, "InterLetter"),
2956 }
2957 }
2958}
2959
2960#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
2968#[non_exhaustive]
2969pub struct FontFaceMetrics {
2970 pub units_per_em: u32,
2974
2975 pub ascent: f32,
2977
2978 pub descent: f32,
2984
2985 pub line_gap: f32,
2987
2988 pub underline_position: f32,
2991
2992 pub underline_thickness: f32,
2994
2995 pub cap_height: f32,
2997
2998 pub x_height: f32,
3001
3002 pub bounds: euclid::Rect<f32, ()>,
3006}
3007impl FontFaceMetrics {
3008 pub fn sized(&self, font_size_px: Px) -> FontMetrics {
3010 let size_scale = 1.0 / self.units_per_em as f32 * font_size_px.0 as f32;
3011 let s = move |f: f32| Px((f * size_scale).round() as i32);
3012 FontMetrics {
3013 size_scale,
3014 ascent: s(self.ascent),
3015 descent: s(self.descent),
3016 line_gap: s(self.line_gap),
3017 underline_position: s(self.underline_position),
3018 underline_thickness: s(self.underline_thickness),
3019 cap_height: s(self.cap_height),
3020 x_height: (s(self.x_height)),
3021 bounds: {
3022 let b = self.bounds;
3023 PxRect::new(
3024 PxPoint::new(s(b.origin.x), s(b.origin.y)),
3025 PxSize::new(s(b.size.width), s(b.size.height)),
3026 )
3027 },
3028 }
3029 }
3030}
3031
3032#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
3036#[non_exhaustive]
3037pub struct FontMetrics {
3038 pub size_scale: f32,
3040
3041 pub ascent: Px,
3043
3044 pub descent: Px,
3050
3051 pub line_gap: Px,
3053
3054 pub underline_position: Px,
3057
3058 pub underline_thickness: Px,
3060
3061 pub cap_height: Px,
3063
3064 pub x_height: Px,
3066
3067 pub bounds: PxRect,
3071}
3072impl FontMetrics {
3073 pub fn line_height(&self) -> Px {
3075 self.ascent - self.descent + self.line_gap
3076 }
3077}
3078
3079#[derive(Clone)]
3081pub enum TextTransformFn {
3082 None,
3084 Uppercase,
3086 Lowercase,
3088 Custom(Arc<dyn Fn(&Txt) -> Cow<Txt> + Send + Sync>),
3090}
3091impl TextTransformFn {
3092 pub fn transform<'t>(&self, text: &'t Txt) -> Cow<'t, Txt> {
3096 match self {
3097 TextTransformFn::None => Cow::Borrowed(text),
3098 TextTransformFn::Uppercase => {
3099 if text.chars().any(|c| !c.is_uppercase()) {
3100 Cow::Owned(text.to_uppercase().into())
3101 } else {
3102 Cow::Borrowed(text)
3103 }
3104 }
3105 TextTransformFn::Lowercase => {
3106 if text.chars().any(|c| !c.is_lowercase()) {
3107 Cow::Owned(text.to_lowercase().into())
3108 } else {
3109 Cow::Borrowed(text)
3110 }
3111 }
3112 TextTransformFn::Custom(fn_) => fn_(text),
3113 }
3114 }
3115
3116 pub fn custom(fn_: impl Fn(&Txt) -> Cow<Txt> + Send + Sync + 'static) -> Self {
3118 TextTransformFn::Custom(Arc::new(fn_))
3119 }
3120}
3121impl fmt::Debug for TextTransformFn {
3122 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3123 if f.alternate() {
3124 write!(f, "TextTransformFn::")?;
3125 }
3126 match self {
3127 TextTransformFn::None => write!(f, "None"),
3128 TextTransformFn::Uppercase => write!(f, "Uppercase"),
3129 TextTransformFn::Lowercase => write!(f, "Lowercase"),
3130 TextTransformFn::Custom(_) => write!(f, "Custom"),
3131 }
3132 }
3133}
3134impl PartialEq for TextTransformFn {
3135 fn eq(&self, other: &Self) -> bool {
3136 match (self, other) {
3137 (Self::Custom(l0), Self::Custom(r0)) => Arc::ptr_eq(l0, r0),
3138 _ => core::mem::discriminant(self) == core::mem::discriminant(other),
3139 }
3140 }
3141}
3142
3143#[derive(Copy, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
3145pub enum WhiteSpace {
3146 Preserve,
3148 Merge,
3150 MergeAll,
3152}
3153impl Default for WhiteSpace {
3154 fn default() -> Self {
3156 WhiteSpace::Preserve
3157 }
3158}
3159impl WhiteSpace {
3160 pub fn transform(self, text: &Txt) -> Cow<'_, Txt> {
3164 match self {
3165 WhiteSpace::Preserve => Cow::Borrowed(text),
3166 WhiteSpace::Merge => {
3167 let is_white_space = |c: char| c.is_whitespace() && !"\n\r\u{85}".contains(c);
3168 let t = text.trim_matches(is_white_space);
3169
3170 let mut prev_space = false;
3171 for c in t.chars() {
3172 if is_white_space(c) {
3173 if prev_space || c != '\u{20}' {
3174 let mut r = String::new();
3177 let mut sep = "";
3178 for part in t.split(is_white_space).filter(|s| !s.is_empty()) {
3179 r.push_str(sep);
3180 r.push_str(part);
3181 sep = "\u{20}";
3182 }
3183 return Cow::Owned(Txt::from_str(&r));
3184 } else {
3185 prev_space = true;
3186 }
3187 } else {
3188 prev_space = false;
3189 }
3190 }
3191
3192 if t.len() != text.len() {
3193 Cow::Owned(Txt::from_str(t))
3194 } else {
3195 Cow::Borrowed(text)
3196 }
3197 }
3198 WhiteSpace::MergeAll => {
3199 let t = text.trim();
3200
3201 let mut prev_space = false;
3202 for c in t.chars() {
3203 if c.is_whitespace() {
3204 if prev_space || c != '\u{20}' {
3205 let mut r = String::new();
3208 let mut sep = "";
3209 for part in t.split_whitespace() {
3210 r.push_str(sep);
3211 r.push_str(part);
3212 sep = "\u{20}";
3213 }
3214 return Cow::Owned(Txt::from_str(&r));
3215 } else {
3216 prev_space = true;
3217 }
3218 } else {
3219 prev_space = false;
3220 }
3221 }
3222
3223 if t.len() != text.len() {
3224 Cow::Owned(Txt::from_str(t))
3225 } else {
3226 Cow::Borrowed(text)
3227 }
3228 }
3229 }
3230 }
3231}
3232impl fmt::Debug for WhiteSpace {
3233 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3234 if f.alternate() {
3235 write!(f, "WhiteSpace::")?;
3236 }
3237 match self {
3238 WhiteSpace::Preserve => write!(f, "Preserve"),
3239 WhiteSpace::Merge => write!(f, "Merge"),
3240 WhiteSpace::MergeAll => write!(f, "MergeAll"),
3241 }
3242 }
3243}
3244
3245#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
3247pub struct CaretIndex {
3248 pub index: usize,
3252 pub line: usize,
3262}
3263
3264impl PartialEq for CaretIndex {
3265 fn eq(&self, other: &Self) -> bool {
3266 self.index == other.index
3267 }
3268}
3269impl Eq for CaretIndex {}
3270impl CaretIndex {
3271 pub const ZERO: CaretIndex = CaretIndex { index: 0, line: 0 };
3273}
3274impl PartialOrd for CaretIndex {
3275 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
3276 Some(self.cmp(other))
3277 }
3278}
3279impl Ord for CaretIndex {
3280 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
3281 self.index.cmp(&other.index)
3282 }
3283}
3284
3285#[derive(Debug, Clone)]
3287#[non_exhaustive]
3288pub enum FontLoadingError {
3289 UnknownFormat,
3291 NoSuchFontInCollection,
3296 Parse(ttf_parser::FaceParsingError),
3298 NoFilesystem,
3301 Io(Arc<std::io::Error>),
3303}
3304impl PartialEq for FontLoadingError {
3305 fn eq(&self, other: &Self) -> bool {
3306 match (self, other) {
3307 (Self::Io(l0), Self::Io(r0)) => Arc::ptr_eq(l0, r0),
3308 _ => core::mem::discriminant(self) == core::mem::discriminant(other),
3309 }
3310 }
3311}
3312impl From<std::io::Error> for FontLoadingError {
3313 fn from(error: std::io::Error) -> FontLoadingError {
3314 Self::Io(Arc::new(error))
3315 }
3316}
3317impl fmt::Display for FontLoadingError {
3318 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3319 match self {
3320 Self::UnknownFormat => write!(f, "unknown format"),
3321 Self::NoSuchFontInCollection => write!(f, "no such font in the collection"),
3322 Self::NoFilesystem => write!(f, "no filesystem present"),
3323 Self::Parse(e) => fmt::Display::fmt(e, f),
3324 Self::Io(e) => fmt::Display::fmt(e, f),
3325 }
3326 }
3327}
3328impl std::error::Error for FontLoadingError {
3329 fn cause(&self) -> Option<&dyn std::error::Error> {
3330 match self {
3331 FontLoadingError::Parse(e) => Some(e),
3332 FontLoadingError::Io(e) => Some(e),
3333 _ => None,
3334 }
3335 }
3336}
3337
3338#[cfg(test)]
3339mod tests {
3340 use zng_app::APP;
3341
3342 use super::*;
3343
3344 #[test]
3345 fn generic_fonts_default() {
3346 let _app = APP.minimal().extend(FontManager::default()).run_headless(false);
3347
3348 assert_eq!(FontName::sans_serif(), GenericFonts {}.sans_serif(&lang!(und)))
3349 }
3350
3351 #[test]
3352 fn generic_fonts_fallback() {
3353 let _app = APP.minimal().extend(FontManager::default()).run_headless(false);
3354
3355 assert_eq!(FontName::sans_serif(), GenericFonts {}.sans_serif(&lang!(en_US)));
3356 assert_eq!(FontName::sans_serif(), GenericFonts {}.sans_serif(&lang!(es)));
3357 }
3358
3359 #[test]
3360 fn generic_fonts_get1() {
3361 let mut app = APP.minimal().extend(FontManager::default()).run_headless(false);
3362 GenericFonts {}.set_sans_serif(lang!(en_US), "Test Value");
3363 app.update(false).assert_wait();
3364
3365 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en-US")), "Test Value");
3366 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en")), "Test Value");
3367 }
3368
3369 #[test]
3370 fn generic_fonts_get2() {
3371 let mut app = APP.minimal().extend(FontManager::default()).run_headless(false);
3372 GenericFonts {}.set_sans_serif(lang!(en), "Test Value");
3373 app.update(false).assert_wait();
3374
3375 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en-US")), "Test Value");
3376 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en")), "Test Value");
3377 }
3378
3379 #[test]
3380 fn generic_fonts_get_best() {
3381 let mut app = APP.minimal().extend(FontManager::default()).run_headless(false);
3382 GenericFonts {}.set_sans_serif(lang!(en), "Test Value");
3383 GenericFonts {}.set_sans_serif(lang!(en_US), "Best");
3384 app.update(false).assert_wait();
3385
3386 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en-US")), "Best");
3387 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en")), "Test Value");
3388 assert_eq!(&GenericFonts {}.sans_serif(&lang!("und")), "sans-serif");
3389 }
3390
3391 #[test]
3392 fn generic_fonts_get_no_lang_match() {
3393 let mut app = APP.minimal().extend(FontManager::default()).run_headless(false);
3394 GenericFonts {}.set_sans_serif(lang!(es_US), "Test Value");
3395 app.update(false).assert_wait();
3396
3397 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en-US")), "sans-serif");
3398 assert_eq!(&GenericFonts {}.sans_serif(&lang!("es")), "Test Value");
3399 }
3400}