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_EPSILON, EQ_EPSILON_100, Factor, FactorPercent, Px, PxPoint, PxRect, PxSize, TimeUnits as _, about_eq, about_eq_hash, about_eq_ord,
62 euclid,
63};
64use zng_task as task;
65use zng_txt::Txt;
66use zng_var::{
67 AnyVar, ArcVar, IntoVar, LocalVar, ResponderVar, ResponseVar, Var, animation::Transitionable, impl_from_and_into_var,
68 response_done_var, response_var, var,
69};
70use zng_view_api::{ViewProcessOffline, 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 type Var = LocalVar<FontNames>;
521
522 fn into_var(self) -> Self::Var {
523 LocalVar(self.into())
524 }
525}
526impl<const N: usize> From<[&'static str; N]> for FontNames {
527 fn from(font_names: [&'static str; N]) -> Self {
528 FontNames(font_names.into_iter().map(FontName::new).collect())
529 }
530}
531impl<const N: usize> IntoVar<FontNames> for [&'static str; N] {
532 type Var = LocalVar<FontNames>;
533
534 fn into_var(self) -> Self::Var {
535 LocalVar(self.into())
536 }
537}
538impl<const N: usize> From<[String; N]> for FontNames {
539 fn from(font_names: [String; N]) -> Self {
540 FontNames(font_names.into_iter().map(FontName::new).collect())
541 }
542}
543impl<const N: usize> IntoVar<FontNames> for [String; N] {
544 type Var = LocalVar<FontNames>;
545
546 fn into_var(self) -> Self::Var {
547 LocalVar(self.into())
548 }
549}
550impl<const N: usize> From<[Txt; N]> for FontNames {
551 fn from(font_names: [Txt; N]) -> Self {
552 FontNames(font_names.into_iter().map(FontName::new).collect())
553 }
554}
555impl<const N: usize> IntoVar<FontNames> for [Txt; N] {
556 type Var = LocalVar<FontNames>;
557
558 fn into_var(self) -> Self::Var {
559 LocalVar(self.into())
560 }
561}
562
563event! {
564 pub static FONT_CHANGED_EVENT: FontChangedArgs;
575}
576
577event_args! {
578 pub struct FontChangedArgs {
580 pub change: FontChange,
582
583 ..
584
585 fn delivery_list(&self, list: &mut UpdateDeliveryList) {
587 list.search_all()
588 }
589 }
590}
591
592#[derive(Clone, Debug)]
594pub enum FontChange {
595 SystemFonts,
599
600 CustomFonts,
605
606 Refresh,
610
611 GenericFont(FontName, Lang),
617
618 Fallback(Lang),
620}
621
622#[derive(Default)]
633pub struct FontManager {}
634impl AppExtension for FontManager {
635 fn event_preview(&mut self, update: &mut EventUpdate) {
636 if RAW_FONT_CHANGED_EVENT.has(update) {
637 FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::SystemFonts));
638 } else if let Some(args) = RAW_FONT_AA_CHANGED_EVENT.on(update) {
639 FONTS_SV.read().font_aa.set(args.aa);
640 } else if FONT_CHANGED_EVENT.has(update) {
641 FONTS_SV.write().on_fonts_changed();
642 } else if let Some(args) = VIEW_PROCESS_INITED_EVENT.on(update) {
643 let mut fonts = FONTS_SV.write();
644 fonts.font_aa.set(args.font_aa);
645 if args.is_respawn {
646 fonts.loader.on_view_process_respawn();
647 }
648 }
649 }
650
651 fn update(&mut self) {
652 let mut fonts = FONTS_SV.write();
653
654 {
655 let mut f = GENERIC_FONTS_SV.write();
656 for request in std::mem::take(&mut f.requests) {
657 request(&mut f);
658 }
659 }
660
661 let mut changed = false;
662 for (request, responder) in std::mem::take(&mut fonts.loader.unregister_requests) {
663 let r = if let Some(removed) = fonts.loader.custom_fonts.remove(&request) {
664 for removed in removed {
668 removed.on_refresh();
669 }
670
671 changed = true;
672
673 true
674 } else {
675 false
676 };
677 responder.respond(r);
678 }
679 if changed {
680 FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::CustomFonts));
681 }
682
683 if fonts.prune_requested {
684 fonts.on_prune();
685 }
686 }
687}
688
689app_local! {
690 static FONTS_SV: FontsService = FontsService {
691 loader: FontFaceLoader::new(),
692 prune_requested: false,
693 font_aa: var(FontAntiAliasing::Default),
694 };
695}
696
697struct FontsService {
698 loader: FontFaceLoader,
699 prune_requested: bool,
700 font_aa: ArcVar<FontAntiAliasing>,
701}
702impl FontsService {
703 fn on_fonts_changed(&mut self) {
704 self.loader.on_refresh();
705 self.prune_requested = false;
706 }
707
708 fn on_prune(&mut self) {
709 self.loader.on_prune();
710 self.prune_requested = false;
711 }
712}
713
714pub struct FONTS;
716impl FONTS {
717 pub fn refresh(&self) {
721 FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::Refresh));
722 }
723
724 pub fn prune(&self) {
726 let mut ft = FONTS_SV.write();
727 if !ft.prune_requested {
728 ft.prune_requested = true;
729 UPDATES.update(None);
730 }
731 }
732
733 pub fn generics(&self) -> &'static GenericFonts {
735 &GenericFonts {}
736 }
737
738 pub fn register(&self, custom_font: CustomFont) -> ResponseVar<Result<FontFace, FontLoadingError>> {
747 FontFaceLoader::register(custom_font)
748 }
749
750 pub fn unregister(&self, custom_family: FontName) -> ResponseVar<bool> {
754 FONTS_SV.write().loader.unregister(custom_family)
755 }
756
757 pub fn list(
759 &self,
760 families: &[FontName],
761 style: FontStyle,
762 weight: FontWeight,
763 stretch: FontStretch,
764 lang: &Lang,
765 ) -> ResponseVar<FontFaceList> {
766 if let Some(cached) = FONTS_SV.read().loader.try_list(families, style, weight, stretch, lang) {
768 return cached;
769 }
770 FONTS_SV.write().loader.load_list(families, style, weight, stretch, lang)
772 }
773
774 pub fn find(
776 &self,
777 family: &FontName,
778 style: FontStyle,
779 weight: FontWeight,
780 stretch: FontStretch,
781 lang: &Lang,
782 ) -> ResponseVar<Option<FontFace>> {
783 if let Some(cached) = FONTS_SV.read().loader.try_cached(family, style, weight, stretch, lang) {
785 return cached;
786 }
787 FONTS_SV.write().loader.load(family, style, weight, stretch, lang)
789 }
790
791 pub fn normal(&self, family: &FontName, lang: &Lang) -> ResponseVar<Option<FontFace>> {
793 self.find(family, FontStyle::Normal, FontWeight::NORMAL, FontStretch::NORMAL, lang)
794 }
795
796 pub fn italic(&self, family: &FontName, lang: &Lang) -> ResponseVar<Option<FontFace>> {
798 self.find(family, FontStyle::Italic, FontWeight::NORMAL, FontStretch::NORMAL, lang)
799 }
800
801 pub fn bold(&self, family: &FontName, lang: &Lang) -> ResponseVar<Option<FontFace>> {
803 self.find(family, FontStyle::Normal, FontWeight::BOLD, FontStretch::NORMAL, lang)
804 }
805
806 pub fn custom_fonts(&self) -> Vec<FontName> {
808 FONTS_SV.read().loader.custom_fonts.keys().cloned().collect()
809 }
810
811 pub fn system_fonts(&self) -> ResponseVar<Vec<FontName>> {
815 query_util::system_all()
816 }
817
818 pub fn system_font_aa(&self) -> impl Var<FontAntiAliasing> {
822 FONTS_SV.read().font_aa.read_only()
823 }
824}
825
826impl<'a> From<ttf_parser::Face<'a>> for FontFaceMetrics {
827 fn from(f: ttf_parser::Face<'a>) -> Self {
828 let underline = f
829 .underline_metrics()
830 .unwrap_or(ttf_parser::LineMetrics { position: 0, thickness: 0 });
831 FontFaceMetrics {
832 units_per_em: f.units_per_em() as _,
833 ascent: f.ascender() as f32,
834 descent: f.descender() as f32,
835 line_gap: f.line_gap() as f32,
836 underline_position: underline.position as f32,
837 underline_thickness: underline.thickness as f32,
838 cap_height: f.capital_height().unwrap_or(0) as f32,
839 x_height: f.x_height().unwrap_or(0) as f32,
840 bounds: euclid::rect(
841 f.global_bounding_box().x_min as f32,
842 f.global_bounding_box().x_max as f32,
843 f.global_bounding_box().width() as f32,
844 f.global_bounding_box().height() as f32,
845 ),
846 }
847 }
848}
849
850#[derive(PartialEq, Eq, Hash)]
851struct FontInstanceKey(Px, Box<[(ttf_parser::Tag, i32)]>);
852impl FontInstanceKey {
853 pub fn new(size: Px, variations: &[rustybuzz::Variation]) -> Self {
855 let variations_key: Vec<_> = variations.iter().map(|p| (p.tag, (p.value * 1000.0) as i32)).collect();
856 FontInstanceKey(size, variations_key.into_boxed_slice())
857 }
858}
859
860#[derive(Clone)]
867pub struct FontFace(Arc<LoadedFontFace>);
868struct LoadedFontFace {
869 data: FontDataRef,
870 face_index: u32,
871 display_name: FontName,
872 family_name: FontName,
873 postscript_name: Option<Txt>,
874 style: FontStyle,
875 weight: FontWeight,
876 stretch: FontStretch,
877 metrics: FontFaceMetrics,
878 color_palettes: ColorPalettes,
879 color_glyphs: ColorGlyphs,
880 lig_carets: LigatureCaretList,
881 flags: FontFaceFlags,
882 m: Mutex<FontFaceMut>,
883}
884bitflags! {
885 #[derive(Debug, Clone, Copy)]
886 struct FontFaceFlags: u8 {
887 const IS_MONOSPACE = 0b0000_0001;
888 const HAS_LIGATURES = 0b0000_0010;
889 const HAS_RASTER_IMAGES = 0b0000_0100;
890 const HAS_SVG_IMAGES = 0b0000_1000;
891 }
892}
893struct FontFaceMut {
894 instances: HashMap<FontInstanceKey, Font>,
895 render_ids: Vec<RenderFontFace>,
896 unregistered: bool,
897}
898
899impl fmt::Debug for FontFace {
900 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
901 let m = self.0.m.lock();
902 f.debug_struct("FontFace")
903 .field("display_name", &self.0.display_name)
904 .field("family_name", &self.0.family_name)
905 .field("postscript_name", &self.0.postscript_name)
906 .field("flags", &self.0.flags)
907 .field("style", &self.0.style)
908 .field("weight", &self.0.weight)
909 .field("stretch", &self.0.stretch)
910 .field("metrics", &self.0.metrics)
911 .field("color_palettes.len()", &self.0.color_palettes.len())
912 .field("color_glyphs.len()", &self.0.color_glyphs.len())
913 .field("instances.len()", &m.instances.len())
914 .field("render_keys.len()", &m.render_ids.len())
915 .field("unregistered", &m.unregistered)
916 .finish_non_exhaustive()
917 }
918}
919impl PartialEq for FontFace {
920 fn eq(&self, other: &Self) -> bool {
921 Arc::ptr_eq(&self.0, &other.0)
922 }
923}
924impl Eq for FontFace {}
925impl FontFace {
926 pub fn empty() -> Self {
928 FontFace(Arc::new(LoadedFontFace {
929 data: FontDataRef::from_static(&[]),
930 face_index: 0,
931 display_name: FontName::from("<empty>"),
932 family_name: FontName::from("<empty>"),
933 postscript_name: None,
934 flags: FontFaceFlags::IS_MONOSPACE,
935 style: FontStyle::Normal,
936 weight: FontWeight::NORMAL,
937 stretch: FontStretch::NORMAL,
938 metrics: FontFaceMetrics {
940 units_per_em: 2048,
941 ascent: 1616.0,
942 descent: -432.0,
943 line_gap: 0.0,
944 underline_position: -205.0,
945 underline_thickness: 102.0,
946 cap_height: 1616.0,
947 x_height: 1616.0,
948 bounds: euclid::Box2D::new(euclid::point2(0.0, -432.0), euclid::point2(1291.0, 1616.0)).to_rect(),
950 },
951 color_palettes: ColorPalettes::empty(),
952 color_glyphs: ColorGlyphs::empty(),
953 lig_carets: LigatureCaretList::empty(),
954 m: Mutex::new(FontFaceMut {
955 instances: HashMap::default(),
956 render_ids: vec![],
957 unregistered: false,
958 }),
959 }))
960 }
961
962 pub fn is_empty(&self) -> bool {
964 self.0.data.is_empty()
965 }
966
967 async fn load_custom(custom_font: CustomFont) -> Result<Self, FontLoadingError> {
968 let bytes;
969 let mut face_index;
970
971 match custom_font.source {
972 FontSource::File(path, index) => {
973 bytes = FontDataRef(Arc::new(task::wait(|| std::fs::read(path)).await?));
974 face_index = index;
975 }
976 FontSource::Memory(arc, index) => {
977 bytes = arc;
978 face_index = index;
979 }
980 FontSource::Alias(other_font) => {
981 let result = FONTS_SV
982 .write()
983 .loader
984 .load_resolved(&other_font, custom_font.style, custom_font.weight, custom_font.stretch);
985 return match result.wait_into_rsp().await {
986 Some(other_font) => Ok(FontFace(Arc::new(LoadedFontFace {
987 data: other_font.0.data.clone(),
988 face_index: other_font.0.face_index,
989 display_name: custom_font.name.clone(),
990 family_name: custom_font.name,
991 postscript_name: None,
992 style: other_font.0.style,
993 weight: other_font.0.weight,
994 stretch: other_font.0.stretch,
995 metrics: other_font.0.metrics.clone(),
996 m: Mutex::new(FontFaceMut {
997 instances: Default::default(),
998 render_ids: Default::default(),
999 unregistered: Default::default(),
1000 }),
1001 color_palettes: other_font.0.color_palettes.clone(),
1002 color_glyphs: other_font.0.color_glyphs.clone(),
1003 lig_carets: other_font.0.lig_carets.clone(),
1004 flags: other_font.0.flags,
1005 }))),
1006 None => Err(FontLoadingError::NoSuchFontInCollection),
1007 };
1008 }
1009 }
1010
1011 let ttf_face = match ttf_parser::Face::parse(&bytes, face_index) {
1012 Ok(f) => f,
1013 Err(e) => {
1014 match e {
1015 ttf_parser::FaceParsingError::FaceIndexOutOfBounds => face_index = 0,
1017 e => return Err(FontLoadingError::Parse(e)),
1018 }
1019
1020 match ttf_parser::Face::parse(&bytes, face_index) {
1021 Ok(f) => f,
1022 Err(_) => return Err(FontLoadingError::Parse(e)),
1023 }
1024 }
1025 };
1026
1027 let color_palettes = ColorPalettes::load(ttf_face.raw_face())?;
1028 let color_glyphs = if color_palettes.is_empty() {
1029 ColorGlyphs::empty()
1030 } else {
1031 ColorGlyphs::load(ttf_face.raw_face())?
1032 };
1033 let has_ligatures = ttf_face.tables().gsub.is_some();
1034 let lig_carets = if has_ligatures {
1035 LigatureCaretList::empty()
1036 } else {
1037 LigatureCaretList::load(ttf_face.raw_face())?
1038 };
1039
1040 let has_raster_images = {
1042 let t = ttf_face.tables();
1043 t.sbix.is_some() || t.bdat.is_some() || t.ebdt.is_some() || t.cbdt.is_some()
1044 };
1045
1046 let mut flags = FontFaceFlags::empty();
1047 flags.set(FontFaceFlags::IS_MONOSPACE, ttf_face.is_monospaced());
1048 flags.set(FontFaceFlags::HAS_LIGATURES, has_ligatures);
1049 flags.set(FontFaceFlags::HAS_RASTER_IMAGES, has_raster_images);
1050 flags.set(FontFaceFlags::HAS_SVG_IMAGES, ttf_face.tables().svg.is_some());
1051
1052 Ok(FontFace(Arc::new(LoadedFontFace {
1053 face_index,
1054 display_name: custom_font.name.clone(),
1055 family_name: custom_font.name,
1056 postscript_name: None,
1057 style: custom_font.style,
1058 weight: custom_font.weight,
1059 stretch: custom_font.stretch,
1060 metrics: ttf_face.into(),
1061 color_palettes,
1062 color_glyphs,
1063 lig_carets,
1064 m: Mutex::new(FontFaceMut {
1065 instances: Default::default(),
1066 render_ids: Default::default(),
1067 unregistered: Default::default(),
1068 }),
1069 data: bytes,
1070 flags,
1071 })))
1072 }
1073
1074 fn load(bytes: FontDataRef, mut face_index: u32) -> Result<Self, FontLoadingError> {
1075 let _span = tracing::trace_span!("FontFace::load").entered();
1076
1077 let ttf_face = match ttf_parser::Face::parse(&bytes, face_index) {
1078 Ok(f) => f,
1079 Err(e) => {
1080 match e {
1081 ttf_parser::FaceParsingError::FaceIndexOutOfBounds => face_index = 0,
1083 e => return Err(FontLoadingError::Parse(e)),
1084 }
1085
1086 match ttf_parser::Face::parse(&bytes, face_index) {
1087 Ok(f) => f,
1088 Err(_) => return Err(FontLoadingError::Parse(e)),
1089 }
1090 }
1091 };
1092
1093 let color_palettes = ColorPalettes::load(ttf_face.raw_face())?;
1094 let color_glyphs = if color_palettes.is_empty() {
1095 ColorGlyphs::empty()
1096 } else {
1097 ColorGlyphs::load(ttf_face.raw_face())?
1098 };
1099
1100 let has_ligatures = ttf_face.tables().gsub.is_some();
1101 let lig_carets = if has_ligatures {
1102 LigatureCaretList::empty()
1103 } else {
1104 LigatureCaretList::load(ttf_face.raw_face())?
1105 };
1106
1107 let mut display_name = None;
1108 let mut family_name = None;
1109 let mut postscript_name = None;
1110 let mut any_name = None::<String>;
1111 for name in ttf_face.names() {
1112 if let Some(n) = name.to_string() {
1113 match name.name_id {
1114 ttf_parser::name_id::FULL_NAME => display_name = Some(n),
1115 ttf_parser::name_id::FAMILY => family_name = Some(n),
1116 ttf_parser::name_id::POST_SCRIPT_NAME => postscript_name = Some(n),
1117 _ => match &mut any_name {
1118 Some(s) => {
1119 if n.len() > s.len() {
1120 *s = n;
1121 }
1122 }
1123 None => any_name = Some(n),
1124 },
1125 }
1126 }
1127 }
1128 let display_name = FontName::new(Txt::from_str(
1129 display_name
1130 .as_ref()
1131 .or(family_name.as_ref())
1132 .or(postscript_name.as_ref())
1133 .or(any_name.as_ref())
1134 .unwrap(),
1135 ));
1136 let family_name = family_name.map(FontName::from).unwrap_or_else(|| display_name.clone());
1137 let postscript_name = postscript_name.map(Txt::from);
1138
1139 if ttf_face.units_per_em() == 0 {
1140 tracing::debug!("font {display_name:?} units_per_em 0");
1142 return Err(FontLoadingError::UnknownFormat);
1143 }
1144
1145 let has_raster_images = {
1147 let t = ttf_face.tables();
1148 t.sbix.is_some() || t.bdat.is_some() || t.ebdt.is_some() || t.cbdt.is_some()
1149 };
1150
1151 let mut flags = FontFaceFlags::empty();
1152 flags.set(FontFaceFlags::IS_MONOSPACE, ttf_face.is_monospaced());
1153 flags.set(FontFaceFlags::HAS_LIGATURES, has_ligatures);
1154 flags.set(FontFaceFlags::HAS_RASTER_IMAGES, has_raster_images);
1155 flags.set(FontFaceFlags::HAS_SVG_IMAGES, ttf_face.tables().svg.is_some());
1156
1157 Ok(FontFace(Arc::new(LoadedFontFace {
1158 face_index,
1159 family_name,
1160 display_name,
1161 postscript_name,
1162 style: ttf_face.style().into(),
1163 weight: ttf_face.weight().into(),
1164 stretch: ttf_face.width().into(),
1165 metrics: ttf_face.into(),
1166 color_palettes,
1167 color_glyphs,
1168 lig_carets,
1169 m: Mutex::new(FontFaceMut {
1170 instances: Default::default(),
1171 render_ids: Default::default(),
1172 unregistered: Default::default(),
1173 }),
1174 data: bytes,
1175 flags,
1176 })))
1177 }
1178
1179 fn on_refresh(&self) {
1180 let mut m = self.0.m.lock();
1181 m.instances.clear();
1182 m.unregistered = true;
1183 }
1184
1185 fn render_face(&self, renderer: &ViewRenderer) -> zng_view_api::font::FontFaceId {
1186 let mut m = self.0.m.lock();
1187 for r in m.render_ids.iter() {
1188 if &r.renderer == renderer {
1189 return r.face_id;
1190 }
1191 }
1192
1193 let key = match renderer.add_font_face((*self.0.data.0).clone(), self.0.face_index) {
1194 Ok(k) => k,
1195 Err(ViewProcessOffline) => {
1196 tracing::debug!("respawned calling `add_font`, will return dummy font key");
1197 return zng_view_api::font::FontFaceId::INVALID;
1198 }
1199 };
1200
1201 m.render_ids.push(RenderFontFace::new(renderer, key));
1202
1203 key
1204 }
1205
1206 pub fn harfbuzz(&self) -> Option<rustybuzz::Face> {
1215 if self.is_empty() {
1216 None
1217 } else {
1218 Some(rustybuzz::Face::from_slice(&self.0.data.0, self.0.face_index).unwrap())
1219 }
1220 }
1221
1222 pub fn ttf(&self) -> Option<ttf_parser::Face> {
1231 if self.is_empty() {
1232 None
1233 } else {
1234 Some(ttf_parser::Face::parse(&self.0.data.0, self.0.face_index).unwrap())
1235 }
1236 }
1237
1238 pub fn bytes(&self) -> &FontDataRef {
1240 &self.0.data
1241 }
1242 pub fn index(&self) -> u32 {
1244 self.0.face_index
1245 }
1246
1247 pub fn display_name(&self) -> &FontName {
1249 &self.0.display_name
1250 }
1251
1252 pub fn family_name(&self) -> &FontName {
1254 &self.0.family_name
1255 }
1256
1257 pub fn postscript_name(&self) -> Option<&str> {
1259 self.0.postscript_name.as_deref()
1260 }
1261
1262 pub fn style(&self) -> FontStyle {
1264 self.0.style
1265 }
1266
1267 pub fn weight(&self) -> FontWeight {
1269 self.0.weight
1270 }
1271
1272 pub fn stretch(&self) -> FontStretch {
1274 self.0.stretch
1275 }
1276
1277 pub fn is_monospace(&self) -> bool {
1279 self.0.flags.contains(FontFaceFlags::IS_MONOSPACE)
1280 }
1281
1282 pub fn metrics(&self) -> &FontFaceMetrics {
1284 &self.0.metrics
1285 }
1286
1287 pub fn sized(&self, font_size: Px, variations: RFontVariations) -> Font {
1296 let key = FontInstanceKey::new(font_size, &variations);
1297 let mut m = self.0.m.lock();
1298 if !m.unregistered {
1299 m.instances
1300 .entry(key)
1301 .or_insert_with(|| Font::new(self.clone(), font_size, variations))
1302 .clone()
1303 } else {
1304 tracing::debug!(target: "font_loading", "creating font from unregistered `{}`, will not cache", self.0.display_name);
1305 Font::new(self.clone(), font_size, variations)
1306 }
1307 }
1308
1309 pub fn synthesis_for(&self, style: FontStyle, weight: FontWeight) -> FontSynthesis {
1311 let mut synth = FontSynthesis::DISABLED;
1312
1313 if style != FontStyle::Normal && self.style() == FontStyle::Normal {
1314 synth |= FontSynthesis::OBLIQUE;
1316 }
1317 if weight > self.weight() {
1318 synth |= FontSynthesis::BOLD;
1321 }
1322
1323 synth
1324 }
1325
1326 pub fn is_cached(&self) -> bool {
1330 !self.0.m.lock().unregistered
1331 }
1332
1333 pub fn color_palettes(&self) -> &ColorPalettes {
1337 &self.0.color_palettes
1338 }
1339
1340 pub fn color_glyphs(&self) -> &ColorGlyphs {
1344 &self.0.color_glyphs
1345 }
1346
1347 pub fn has_ligatures(&self) -> bool {
1349 self.0.flags.contains(FontFaceFlags::HAS_LIGATURES)
1350 }
1351
1352 pub fn has_ligature_caret_offsets(&self) -> bool {
1357 !self.0.lig_carets.is_empty()
1358 }
1359
1360 pub fn has_raster_images(&self) -> bool {
1362 self.0.flags.contains(FontFaceFlags::HAS_RASTER_IMAGES)
1363 }
1364
1365 pub fn has_svg_images(&self) -> bool {
1367 self.0.flags.contains(FontFaceFlags::HAS_SVG_IMAGES)
1368 }
1369}
1370
1371#[derive(Clone)]
1377pub struct Font(Arc<LoadedFont>);
1378struct LoadedFont {
1379 face: FontFace,
1380 size: Px,
1381 variations: RFontVariations,
1382 metrics: FontMetrics,
1383 render_keys: Mutex<Vec<RenderFont>>,
1384 small_word_cache: RwLock<HashMap<WordCacheKey<[u8; Font::SMALL_WORD_LEN]>, ShapedSegmentData>>,
1385 word_cache: RwLock<HashMap<WordCacheKey<String>, ShapedSegmentData>>,
1386}
1387impl fmt::Debug for Font {
1388 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1389 f.debug_struct("Font")
1390 .field("face", &self.0.face)
1391 .field("size", &self.0.size)
1392 .field("metrics", &self.0.metrics)
1393 .field("render_keys.len()", &self.0.render_keys.lock().len())
1394 .field("small_word_cache.len()", &self.0.small_word_cache.read().len())
1395 .field("word_cache.len()", &self.0.word_cache.read().len())
1396 .finish()
1397 }
1398}
1399impl PartialEq for Font {
1400 fn eq(&self, other: &Self) -> bool {
1401 Arc::ptr_eq(&self.0, &other.0)
1402 }
1403}
1404impl Eq for Font {}
1405impl Font {
1406 const SMALL_WORD_LEN: usize = 8;
1407
1408 fn to_small_word(s: &str) -> Option<[u8; Self::SMALL_WORD_LEN]> {
1409 if s.len() <= Self::SMALL_WORD_LEN {
1410 let mut a = [b'\0'; Self::SMALL_WORD_LEN];
1411 a[..s.len()].copy_from_slice(s.as_bytes());
1412 Some(a)
1413 } else {
1414 None
1415 }
1416 }
1417
1418 fn new(face: FontFace, size: Px, variations: RFontVariations) -> Self {
1419 Font(Arc::new(LoadedFont {
1420 metrics: face.metrics().sized(size),
1421 face,
1422 size,
1423 variations,
1424 render_keys: Mutex::new(vec![]),
1425 small_word_cache: RwLock::default(),
1426 word_cache: RwLock::default(),
1427 }))
1428 }
1429
1430 fn render_font(&self, renderer: &ViewRenderer, synthesis: FontSynthesis) -> zng_view_api::font::FontId {
1431 let _span = tracing::trace_span!("Font::render_font").entered();
1432
1433 let mut render_keys = self.0.render_keys.lock();
1434 for r in render_keys.iter() {
1435 if &r.renderer == renderer && r.synthesis == synthesis {
1436 return r.font_id;
1437 }
1438 }
1439
1440 let font_key = self.0.face.render_face(renderer);
1441
1442 let opt = zng_view_api::font::FontOptions {
1443 synthetic_oblique: synthesis.contains(FontSynthesis::OBLIQUE),
1444 synthetic_bold: synthesis.contains(FontSynthesis::BOLD),
1445 ..Default::default()
1446 };
1447 let variations = self.0.variations.iter().map(|v| (v.tag.to_bytes(), v.value)).collect();
1448
1449 let key = match renderer.add_font(font_key, self.0.size, opt, variations) {
1450 Ok(k) => k,
1451 Err(ViewProcessOffline) => {
1452 tracing::debug!("respawned calling `add_font_instance`, will return dummy font key");
1453 return zng_view_api::font::FontId::INVALID;
1454 }
1455 };
1456
1457 render_keys.push(RenderFont::new(renderer, synthesis, key));
1458
1459 key
1460 }
1461
1462 pub fn face(&self) -> &FontFace {
1464 &self.0.face
1465 }
1466
1467 pub fn harfbuzz(&self) -> Option<rustybuzz::Face> {
1469 let ppem = self.0.size.0 as u16;
1470
1471 let mut font = self.0.face.harfbuzz()?;
1472
1473 font.set_pixels_per_em(Some((ppem, ppem)));
1474 font.set_variations(&self.0.variations);
1475
1476 Some(font)
1477 }
1478
1479 pub fn size(&self) -> Px {
1483 self.0.size
1484 }
1485
1486 pub fn variations(&self) -> &RFontVariations {
1488 &self.0.variations
1489 }
1490
1491 pub fn metrics(&self) -> &FontMetrics {
1493 &self.0.metrics
1494 }
1495
1496 pub fn ligature_caret_offsets(
1502 &self,
1503 lig: zng_view_api::font::GlyphIndex,
1504 ) -> impl ExactSizeIterator<Item = f32> + DoubleEndedIterator + '_ {
1505 let face = &self.0.face.0;
1506 face.lig_carets.carets(lig).iter().map(move |&o| match o {
1507 ligature_util::LigatureCaret::Coordinate(o) => {
1508 let size_scale = 1.0 / face.metrics.units_per_em as f32 * self.0.size.0 as f32;
1509 o as f32 * size_scale
1510 }
1511 ligature_util::LigatureCaret::GlyphContourPoint(i) => {
1512 if let Some(f) = self.harfbuzz() {
1513 struct Search {
1514 i: u16,
1515 s: u16,
1516 x: f32,
1517 }
1518 impl Search {
1519 fn check(&mut self, x: f32) {
1520 self.s = self.s.saturating_add(1);
1521 if self.s == self.i {
1522 self.x = x;
1523 }
1524 }
1525 }
1526 impl ttf_parser::OutlineBuilder for Search {
1527 fn move_to(&mut self, x: f32, _y: f32) {
1528 self.check(x);
1529 }
1530
1531 fn line_to(&mut self, x: f32, _y: f32) {
1532 self.check(x);
1533 }
1534
1535 fn quad_to(&mut self, _x1: f32, _y1: f32, x: f32, _y: f32) {
1536 self.check(x)
1537 }
1538
1539 fn curve_to(&mut self, _x1: f32, _y1: f32, _x2: f32, _y2: f32, x: f32, _y: f32) {
1540 self.check(x);
1541 }
1542
1543 fn close(&mut self) {}
1544 }
1545 let mut search = Search { i, s: 0, x: 0.0 };
1546 if f.outline_glyph(ttf_parser::GlyphId(lig as _), &mut search).is_some() && search.s >= search.i {
1547 return search.x * self.0.metrics.size_scale;
1548 }
1549 }
1550 0.0
1551 }
1552 })
1553 }
1554}
1555impl zng_app::render::Font for Font {
1556 fn is_empty_fallback(&self) -> bool {
1557 self.face().is_empty()
1558 }
1559
1560 fn renderer_id(&self, renderer: &ViewRenderer, synthesis: FontSynthesis) -> zng_view_api::font::FontId {
1561 self.render_font(renderer, synthesis)
1562 }
1563}
1564
1565#[derive(Debug, Clone)]
1569pub struct FontFaceList {
1570 fonts: Box<[FontFace]>,
1571 requested_style: FontStyle,
1572 requested_weight: FontWeight,
1573 requested_stretch: FontStretch,
1574}
1575impl FontFaceList {
1576 pub fn empty() -> Self {
1578 Self {
1579 fonts: Box::new([FontFace::empty()]),
1580 requested_style: FontStyle::Normal,
1581 requested_weight: FontWeight::NORMAL,
1582 requested_stretch: FontStretch::NORMAL,
1583 }
1584 }
1585
1586 pub fn requested_style(&self) -> FontStyle {
1588 self.requested_style
1589 }
1590
1591 pub fn requested_weight(&self) -> FontWeight {
1593 self.requested_weight
1594 }
1595
1596 pub fn requested_stretch(&self) -> FontStretch {
1598 self.requested_stretch
1599 }
1600
1601 pub fn best(&self) -> &FontFace {
1603 &self.fonts[0]
1604 }
1605
1606 pub fn face_synthesis(&self, face_index: usize) -> FontSynthesis {
1608 if let Some(face) = self.fonts.get(face_index) {
1609 face.synthesis_for(self.requested_style, self.requested_weight)
1610 } else {
1611 FontSynthesis::DISABLED
1612 }
1613 }
1614
1615 pub fn iter(&self) -> std::slice::Iter<FontFace> {
1617 self.fonts.iter()
1618 }
1619
1620 pub fn len(&self) -> usize {
1624 self.fonts.len()
1625 }
1626
1627 pub fn is_empty(&self) -> bool {
1629 self.fonts[0].is_empty() && self.fonts.len() == 1
1630 }
1631
1632 pub fn sized(&self, font_size: Px, variations: RFontVariations) -> FontList {
1636 FontList {
1637 fonts: self.fonts.iter().map(|f| f.sized(font_size, variations.clone())).collect(),
1638 requested_style: self.requested_style,
1639 requested_weight: self.requested_weight,
1640 requested_stretch: self.requested_stretch,
1641 }
1642 }
1643}
1644impl PartialEq for FontFaceList {
1645 fn eq(&self, other: &Self) -> bool {
1647 self.requested_style == other.requested_style
1648 && self.requested_weight == other.requested_weight
1649 && self.requested_stretch == other.requested_stretch
1650 && self.fonts.len() == other.fonts.len()
1651 && self.fonts.iter().zip(other.fonts.iter()).all(|(a, b)| a == b)
1652 }
1653}
1654impl Eq for FontFaceList {}
1655impl std::ops::Deref for FontFaceList {
1656 type Target = [FontFace];
1657
1658 fn deref(&self) -> &Self::Target {
1659 &self.fonts
1660 }
1661}
1662impl<'a> std::iter::IntoIterator for &'a FontFaceList {
1663 type Item = &'a FontFace;
1664
1665 type IntoIter = std::slice::Iter<'a, FontFace>;
1666
1667 fn into_iter(self) -> Self::IntoIter {
1668 self.iter()
1669 }
1670}
1671impl std::ops::Index<usize> for FontFaceList {
1672 type Output = FontFace;
1673
1674 fn index(&self, index: usize) -> &Self::Output {
1675 &self.fonts[index]
1676 }
1677}
1678
1679#[derive(Debug, Clone)]
1681pub struct FontList {
1682 fonts: Box<[Font]>,
1683 requested_style: FontStyle,
1684 requested_weight: FontWeight,
1685 requested_stretch: FontStretch,
1686}
1687#[expect(clippy::len_without_is_empty)] impl FontList {
1689 pub fn best(&self) -> &Font {
1691 &self.fonts[0]
1692 }
1693
1694 pub fn requested_size(&self) -> Px {
1696 self.fonts[0].size()
1697 }
1698
1699 pub fn requested_style(&self) -> FontStyle {
1701 self.requested_style
1702 }
1703
1704 pub fn requested_weight(&self) -> FontWeight {
1706 self.requested_weight
1707 }
1708
1709 pub fn requested_stretch(&self) -> FontStretch {
1711 self.requested_stretch
1712 }
1713
1714 pub fn face_synthesis(&self, font_index: usize) -> FontSynthesis {
1716 if let Some(font) = self.fonts.get(font_index) {
1717 font.0.face.synthesis_for(self.requested_style, self.requested_weight)
1718 } else {
1719 FontSynthesis::DISABLED
1720 }
1721 }
1722
1723 pub fn iter(&self) -> std::slice::Iter<Font> {
1725 self.fonts.iter()
1726 }
1727
1728 pub fn len(&self) -> usize {
1732 self.fonts.len()
1733 }
1734
1735 pub fn is_sized_from(&self, faces: &FontFaceList) -> bool {
1737 if self.len() != faces.len() {
1738 return false;
1739 }
1740
1741 for (font, face) in self.iter().zip(faces.iter()) {
1742 if font.face() != face {
1743 return false;
1744 }
1745 }
1746
1747 true
1748 }
1749}
1750impl PartialEq for FontList {
1751 fn eq(&self, other: &Self) -> bool {
1753 self.requested_style == other.requested_style
1754 && self.requested_weight == other.requested_weight
1755 && self.requested_stretch == other.requested_stretch
1756 && self.fonts.len() == other.fonts.len()
1757 && self.fonts.iter().zip(other.fonts.iter()).all(|(a, b)| a == b)
1758 }
1759}
1760impl Eq for FontList {}
1761impl std::ops::Deref for FontList {
1762 type Target = [Font];
1763
1764 fn deref(&self) -> &Self::Target {
1765 &self.fonts
1766 }
1767}
1768impl<'a> std::iter::IntoIterator for &'a FontList {
1769 type Item = &'a Font;
1770
1771 type IntoIter = std::slice::Iter<'a, Font>;
1772
1773 fn into_iter(self) -> Self::IntoIter {
1774 self.iter()
1775 }
1776}
1777impl<I: SliceIndex<[Font]>> std::ops::Index<I> for FontList {
1778 type Output = I::Output;
1779
1780 fn index(&self, index: I) -> &I::Output {
1781 &self.fonts[index]
1782 }
1783}
1784
1785struct FontFaceLoader {
1786 custom_fonts: HashMap<FontName, Vec<FontFace>>,
1787 unregister_requests: Vec<(FontName, ResponderVar<bool>)>,
1788
1789 system_fonts_cache: HashMap<FontName, Vec<SystemFontFace>>,
1790 list_cache: HashMap<Box<[FontName]>, Vec<FontFaceListQuery>>,
1791}
1792struct SystemFontFace {
1793 properties: (FontStyle, FontWeight, FontStretch),
1794 result: ResponseVar<Option<FontFace>>,
1795}
1796struct FontFaceListQuery {
1797 properties: (FontStyle, FontWeight, FontStretch),
1798 lang: Lang,
1799 result: ResponseVar<FontFaceList>,
1800}
1801impl FontFaceLoader {
1802 fn new() -> Self {
1803 FontFaceLoader {
1804 custom_fonts: HashMap::new(),
1805 unregister_requests: vec![],
1806 system_fonts_cache: HashMap::new(),
1807 list_cache: HashMap::new(),
1808 }
1809 }
1810
1811 fn on_view_process_respawn(&mut self) {
1812 let sys_fonts = self.system_fonts_cache.values().flatten().filter_map(|f| f.result.rsp().flatten());
1813 for face in self.custom_fonts.values().flatten().cloned().chain(sys_fonts) {
1814 let mut m = face.0.m.lock();
1815 m.render_ids.clear();
1816 for inst in m.instances.values() {
1817 inst.0.render_keys.lock().clear();
1818 }
1819 }
1820 }
1821
1822 fn on_refresh(&mut self) {
1823 for (_, sys_family) in self.system_fonts_cache.drain() {
1824 for sys_font in sys_family {
1825 sys_font.result.with(|r| {
1826 if let Some(Some(face)) = r.done() {
1827 face.on_refresh();
1828 }
1829 });
1830 }
1831 }
1832 }
1833 fn on_prune(&mut self) {
1834 self.system_fonts_cache.retain(|_, v| {
1835 v.retain(|sff| {
1836 if sff.result.strong_count() == 1 {
1837 sff.result.with(|r| {
1838 match r.done() {
1839 Some(Some(face)) => Arc::strong_count(&face.0) > 1, Some(None) => false, None => true, }
1843 })
1844 } else {
1845 true
1847 }
1848 });
1849 !v.is_empty()
1850 });
1851
1852 self.list_cache.clear();
1853 }
1854
1855 fn register(custom_font: CustomFont) -> ResponseVar<Result<FontFace, FontLoadingError>> {
1856 let resp = task::respond(FontFace::load_custom(custom_font));
1858
1859 resp.hook(|args| {
1861 if let Some(done) = args.value().done() {
1862 if let Ok(face) = done {
1863 let mut fonts = FONTS_SV.write();
1864 let family = fonts.loader.custom_fonts.entry(face.0.family_name.clone()).or_default();
1865 let existing = family
1866 .iter()
1867 .position(|f| f.0.weight == face.0.weight && f.0.style == face.0.style && f.0.stretch == face.0.stretch);
1868
1869 if let Some(i) = existing {
1870 family[i] = face.clone();
1871 } else {
1872 family.push(face.clone());
1873 }
1874
1875 FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::CustomFonts));
1876 }
1877 false
1878 } else {
1879 true
1880 }
1881 })
1882 .perm();
1883 resp
1884 }
1885
1886 fn unregister(&mut self, custom_family: FontName) -> ResponseVar<bool> {
1887 let (responder, response) = response_var();
1888
1889 if !self.unregister_requests.is_empty() {
1890 UPDATES.update(None);
1891 }
1892 self.unregister_requests.push((custom_family, responder));
1893
1894 response
1895 }
1896
1897 fn try_list(
1898 &self,
1899 families: &[FontName],
1900 style: FontStyle,
1901 weight: FontWeight,
1902 stretch: FontStretch,
1903 lang: &Lang,
1904 ) -> Option<ResponseVar<FontFaceList>> {
1905 if let Some(queries) = self.list_cache.get(families) {
1906 for q in queries {
1907 if q.properties == (style, weight, stretch) && &q.lang == lang {
1908 return Some(q.result.clone());
1909 }
1910 }
1911 }
1912 None
1913 }
1914
1915 fn load_list(
1916 &mut self,
1917 families: &[FontName],
1918 style: FontStyle,
1919 weight: FontWeight,
1920 stretch: FontStretch,
1921 lang: &Lang,
1922 ) -> ResponseVar<FontFaceList> {
1923 if let Some(r) = self.try_list(families, style, weight, stretch, lang) {
1924 return r;
1925 }
1926
1927 let mut list = Vec::with_capacity(families.len() + 1);
1928 let mut pending = vec![];
1929
1930 {
1931 let fallback = [GenericFonts {}.fallback(lang)];
1932 let mut used = HashSet::with_capacity(families.len());
1933 for name in families.iter().chain(&fallback) {
1934 if !used.insert(name) {
1935 continue;
1936 }
1937
1938 let face = self.load(name, style, weight, stretch, lang);
1939 if face.is_done() {
1940 if let Some(face) = face.into_rsp().unwrap() {
1941 list.push(face);
1942 }
1943 } else {
1944 pending.push((list.len(), face));
1945 }
1946 }
1947 }
1948
1949 let r = if pending.is_empty() {
1950 if list.is_empty() {
1951 tracing::error!(target: "font_loading", "failed to load fallback font");
1952 list.push(FontFace::empty());
1953 }
1954 response_done_var(FontFaceList {
1955 fonts: list.into_boxed_slice(),
1956 requested_style: style,
1957 requested_weight: weight,
1958 requested_stretch: stretch,
1959 })
1960 } else {
1961 task::respond(async move {
1962 for (i, pending) in pending.into_iter().rev() {
1963 if let Some(rsp) = pending.wait_into_rsp().await {
1964 list.insert(i, rsp);
1965 }
1966 }
1967
1968 if list.is_empty() {
1969 tracing::error!(target: "font_loading", "failed to load fallback font");
1970 list.push(FontFace::empty());
1971 }
1972
1973 FontFaceList {
1974 fonts: list.into_boxed_slice(),
1975 requested_style: style,
1976 requested_weight: weight,
1977 requested_stretch: stretch,
1978 }
1979 })
1980 };
1981
1982 self.list_cache
1983 .entry(families.iter().cloned().collect())
1984 .or_insert_with(|| Vec::with_capacity(1))
1985 .push(FontFaceListQuery {
1986 properties: (style, weight, stretch),
1987 lang: lang.clone(),
1988 result: r.clone(),
1989 });
1990
1991 r
1992 }
1993
1994 fn try_cached(
1995 &self,
1996 font_name: &FontName,
1997 style: FontStyle,
1998 weight: FontWeight,
1999 stretch: FontStretch,
2000 lang: &Lang,
2001 ) -> Option<ResponseVar<Option<FontFace>>> {
2002 let resolved = GenericFonts {}.resolve(font_name, lang);
2003 let font_name = resolved.as_ref().unwrap_or(font_name);
2004 self.try_resolved(font_name, style, weight, stretch)
2005 }
2006
2007 fn load(
2009 &mut self,
2010 font_name: &FontName,
2011 style: FontStyle,
2012 weight: FontWeight,
2013 stretch: FontStretch,
2014 lang: &Lang,
2015 ) -> ResponseVar<Option<FontFace>> {
2016 let resolved = GenericFonts {}.resolve(font_name, lang);
2017 let font_name = resolved.as_ref().unwrap_or(font_name);
2018 self.load_resolved(font_name, style, weight, stretch)
2019 }
2020
2021 fn try_resolved(
2023 &self,
2024 font_name: &FontName,
2025 style: FontStyle,
2026 weight: FontWeight,
2027 stretch: FontStretch,
2028 ) -> Option<ResponseVar<Option<FontFace>>> {
2029 if let Some(custom_family) = self.custom_fonts.get(font_name) {
2030 let custom = Self::match_custom(custom_family, style, weight, stretch);
2031 return Some(response_done_var(Some(custom)));
2032 }
2033
2034 if let Some(cached_sys_family) = self.system_fonts_cache.get(font_name) {
2035 for sys_face in cached_sys_family.iter() {
2036 if sys_face.properties == (style, weight, stretch) {
2037 return Some(sys_face.result.clone());
2038 }
2039 }
2040 }
2041
2042 None
2043 }
2044
2045 fn load_resolved(
2047 &mut self,
2048 font_name: &FontName,
2049 style: FontStyle,
2050 weight: FontWeight,
2051 stretch: FontStretch,
2052 ) -> ResponseVar<Option<FontFace>> {
2053 if let Some(cached) = self.try_resolved(font_name, style, weight, stretch) {
2054 return cached;
2055 }
2056
2057 let load = task::wait(clmv!(font_name, || {
2058 let (bytes, face_index) = match Self::get_system(&font_name, style, weight, stretch) {
2059 Some(h) => h,
2060 None => {
2061 #[cfg(debug_assertions)]
2062 static NOT_FOUND: Mutex<Option<HashSet<FontName>>> = Mutex::new(None);
2063
2064 #[cfg(debug_assertions)]
2065 if NOT_FOUND.lock().get_or_insert_with(HashSet::default).insert(font_name.clone()) {
2066 tracing::debug!(r#"font "{font_name}" not found"#);
2067 }
2068
2069 return None;
2070 }
2071 };
2072 match FontFace::load(bytes, face_index) {
2073 Ok(f) => Some(f),
2074 Err(FontLoadingError::UnknownFormat) => None,
2075 Err(e) => {
2076 tracing::error!(target: "font_loading", "failed to load system font, {e}\nquery: {:?}", (font_name, style, weight, stretch));
2077 None
2078 }
2079 }
2080 }));
2081 let result = task::respond(async_clmv!(font_name, {
2082 match task::with_deadline(load, 10.secs()).await {
2083 Ok(r) => r,
2084 Err(_) => {
2085 tracing::error!(target: "font_loading", "timeout loading {font_name:?}");
2086 None
2087 }
2088 }
2089 }));
2090
2091 self.system_fonts_cache
2092 .entry(font_name.clone())
2093 .or_insert_with(|| Vec::with_capacity(1))
2094 .push(SystemFontFace {
2095 properties: (style, weight, stretch),
2096 result: result.clone(),
2097 });
2098
2099 result
2100 }
2101
2102 fn get_system(font_name: &FontName, style: FontStyle, weight: FontWeight, stretch: FontStretch) -> Option<(FontDataRef, u32)> {
2103 let _span = tracing::trace_span!("FontFaceLoader::get_system").entered();
2104 match query_util::best(font_name, style, weight, stretch) {
2105 Ok(r) => r,
2106 Err(e) => {
2107 tracing::error!("cannot get `{font_name}` system font, {e}");
2108 None
2109 }
2110 }
2111 }
2112
2113 fn match_custom(faces: &[FontFace], style: FontStyle, weight: FontWeight, stretch: FontStretch) -> FontFace {
2114 if faces.len() == 1 {
2115 return faces[0].clone();
2117 }
2118
2119 let mut set = Vec::with_capacity(faces.len());
2120 let mut set_dist = 0.0f64; let wrong_side = if stretch <= FontStretch::NORMAL {
2127 |s| s > FontStretch::NORMAL
2128 } else {
2129 |s| s <= FontStretch::NORMAL
2130 };
2131 for face in faces {
2132 let mut dist = (face.stretch().0 - stretch.0).abs() as f64;
2133 if wrong_side(face.stretch()) {
2134 dist += f32::MAX as f64 + 1.0;
2135 }
2136
2137 if set.is_empty() {
2138 set.push(face);
2139 set_dist = dist;
2140 } else if dist < set_dist {
2141 set_dist = dist;
2143 set.clear();
2144 set.push(face);
2145 } else if (dist - set_dist).abs() < 0.0001 {
2146 set.push(face);
2148 }
2149 }
2150 if set.len() == 1 {
2151 return set[0].clone();
2152 }
2153
2154 let style_pref = match style {
2159 FontStyle::Normal => [FontStyle::Normal, FontStyle::Oblique, FontStyle::Italic],
2160 FontStyle::Italic => [FontStyle::Italic, FontStyle::Oblique, FontStyle::Normal],
2161 FontStyle::Oblique => [FontStyle::Oblique, FontStyle::Italic, FontStyle::Normal],
2162 };
2163 let mut best_style = style_pref.len();
2164 for face in &set {
2165 let i = style_pref.iter().position(|&s| s == face.style()).unwrap();
2166 if i < best_style {
2167 best_style = i;
2168 }
2169 }
2170 set.retain(|f| f.style() == style_pref[best_style]);
2171 if set.len() == 1 {
2172 return set[0].clone();
2173 }
2174
2175 let add_penalty = if weight.0 >= 400.0 && weight.0 <= 500.0 {
2183 |face: &FontFace, weight: FontWeight, dist: &mut f64| {
2185 if face.weight() < weight {
2187 *dist += 100.0;
2189 } else if face.weight().0 > 500.0 {
2190 *dist += 600.0;
2192 }
2193 }
2194 } else if weight.0 < 400.0 {
2195 |face: &FontFace, weight: FontWeight, dist: &mut f64| {
2197 if face.weight() > weight {
2198 *dist += weight.0 as f64;
2199 }
2200 }
2201 } else {
2202 debug_assert!(weight.0 > 500.0);
2203 |face: &FontFace, weight: FontWeight, dist: &mut f64| {
2205 if face.weight() < weight {
2206 *dist += f32::MAX as f64;
2207 }
2208 }
2209 };
2210
2211 let mut best = set[0];
2212 let mut best_dist = f64::MAX;
2213
2214 for face in &set {
2215 let mut dist = (face.weight().0 - weight.0).abs() as f64;
2216
2217 add_penalty(face, weight, &mut dist);
2218
2219 if dist < best_dist {
2220 best_dist = dist;
2221 best = face;
2222 }
2223 }
2224
2225 best.clone()
2226 }
2227}
2228
2229struct RenderFontFace {
2230 renderer: ViewRenderer,
2231 face_id: zng_view_api::font::FontFaceId,
2232}
2233impl RenderFontFace {
2234 fn new(renderer: &ViewRenderer, face_id: zng_view_api::font::FontFaceId) -> Self {
2235 RenderFontFace {
2236 renderer: renderer.clone(),
2237 face_id,
2238 }
2239 }
2240}
2241impl Drop for RenderFontFace {
2242 fn drop(&mut self) {
2243 let _ = self.renderer.delete_font_face(self.face_id);
2245 }
2246}
2247
2248struct RenderFont {
2249 renderer: ViewRenderer,
2250 synthesis: FontSynthesis,
2251 font_id: zng_view_api::font::FontId,
2252}
2253impl RenderFont {
2254 fn new(renderer: &ViewRenderer, synthesis: FontSynthesis, font_id: zng_view_api::font::FontId) -> RenderFont {
2255 RenderFont {
2256 renderer: renderer.clone(),
2257 synthesis,
2258 font_id,
2259 }
2260 }
2261}
2262impl Drop for RenderFont {
2263 fn drop(&mut self) {
2264 let _ = self.renderer.delete_font(self.font_id);
2266 }
2267}
2268
2269app_local! {
2270 static GENERIC_FONTS_SV: GenericFontsService = GenericFontsService::new();
2271}
2272
2273struct GenericFontsService {
2274 serif: LangMap<FontName>,
2275 sans_serif: LangMap<FontName>,
2276 monospace: LangMap<FontName>,
2277 cursive: LangMap<FontName>,
2278 fantasy: LangMap<FontName>,
2279 fallback: LangMap<FontName>,
2280
2281 requests: Vec<Box<dyn FnOnce(&mut GenericFontsService) + Send + Sync>>,
2282}
2283impl GenericFontsService {
2284 fn new() -> Self {
2285 fn default(name: impl Into<FontName>) -> LangMap<FontName> {
2286 let mut f = LangMap::with_capacity(1);
2287 f.insert(lang!(und), name.into());
2288 f
2289 }
2290
2291 let serif = "serif";
2292 let sans_serif = "sans-serif";
2293 let monospace = "monospace";
2294 let cursive = "cursive";
2295 let fantasy = "fantasy";
2296 let fallback = if cfg!(windows) {
2297 "Segoe UI Symbol"
2298 } else if cfg!(target_os = "linux") {
2299 "Standard Symbols PS"
2300 } else {
2301 "sans-serif"
2302 };
2303
2304 GenericFontsService {
2305 serif: default(serif),
2306 sans_serif: default(sans_serif),
2307 monospace: default(monospace),
2308 cursive: default(cursive),
2309 fantasy: default(fantasy),
2310
2311 fallback: default(fallback),
2312
2313 requests: vec![],
2314 }
2315 }
2316}
2317
2318pub struct GenericFonts {}
2333impl GenericFonts {}
2334macro_rules! impl_fallback_accessors {
2335 ($($name:ident=$name_str:tt),+ $(,)?) => {$($crate::paste! {
2336 #[doc = "Gets the fallback *"$name_str "* font for the given language."]
2337 #[doc = "Note that the returned name can still be the generic `\""$name_str "\"`, this delegates the resolution to the operating system."]
2341
2342 pub fn $name(&self, lang: &Lang) -> FontName {
2343 GENERIC_FONTS_SV.read().$name.get(lang).unwrap().clone()
2344 }
2345
2346 #[doc = "Sets the fallback *"$name_str "* font for the given language."]
2347 pub fn [<set_ $name>]<F: Into<FontName>>(&self, lang: Lang, font_name: F) {
2352 let mut g = GENERIC_FONTS_SV.write();
2353 let font_name = font_name.into();
2354 if g.requests.is_empty() {
2355 UPDATES.update(None);
2356 }
2357 g.requests.push(Box::new(move |g| {
2358 g.$name.insert(lang.clone(), font_name);
2359 FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::GenericFont(FontName::$name(), lang)));
2360 }));
2361 }
2362 })+};
2363}
2364impl GenericFonts {
2365 impl_fallback_accessors! {
2366 serif="serif", sans_serif="sans-serif", monospace="monospace", cursive="cursive", fantasy="fantasy"
2367 }
2368
2369 pub fn fallback(&self, lang: &Lang) -> FontName {
2373 GENERIC_FONTS_SV.read().fallback.get(lang).unwrap().clone()
2374 }
2375
2376 pub fn set_fallback<F: Into<FontName>>(&self, lang: Lang, font_name: F) {
2382 let mut g = GENERIC_FONTS_SV.write();
2383 if g.requests.is_empty() {
2384 UPDATES.update(None);
2385 }
2386 let font_name = font_name.into();
2387 g.requests.push(Box::new(move |g| {
2388 FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::Fallback(lang.clone())));
2389 g.fallback.insert(lang, font_name);
2390 }));
2391 }
2392
2393 pub fn resolve(&self, name: &FontName, lang: &Lang) -> Option<FontName> {
2397 if name == &FontName::serif() {
2398 Some(self.serif(lang))
2399 } else if name == &FontName::sans_serif() {
2400 Some(self.sans_serif(lang))
2401 } else if name == &FontName::monospace() {
2402 Some(self.monospace(lang))
2403 } else if name == &FontName::cursive() {
2404 Some(self.cursive(lang))
2405 } else if name == &FontName::fantasy() {
2406 Some(self.fantasy(lang))
2407 } else {
2408 None
2409 }
2410 }
2411}
2412
2413#[derive(Clone)]
2415pub struct FontDataRef(pub Arc<Vec<u8>>);
2416impl FontDataRef {
2417 pub fn from_static(data: &'static [u8]) -> Self {
2419 FontDataRef(Arc::new(data.to_vec()))
2420 }
2421}
2422impl fmt::Debug for FontDataRef {
2423 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2424 write!(f, "FontDataRef(Arc<{} bytes>>)", self.0.len())
2425 }
2426}
2427impl std::ops::Deref for FontDataRef {
2428 type Target = [u8];
2429
2430 fn deref(&self) -> &Self::Target {
2431 self.0.deref()
2432 }
2433}
2434
2435#[derive(Debug, Clone)]
2436enum FontSource {
2437 File(PathBuf, u32),
2438 Memory(FontDataRef, u32),
2439 Alias(FontName),
2440}
2441
2442#[derive(Debug, Clone)]
2444pub struct CustomFont {
2445 name: FontName,
2446 source: FontSource,
2447 stretch: FontStretch,
2448 style: FontStyle,
2449 weight: FontWeight,
2450}
2451impl CustomFont {
2452 pub fn from_file<N: Into<FontName>, P: Into<PathBuf>>(name: N, path: P, font_index: u32) -> Self {
2460 CustomFont {
2461 name: name.into(),
2462 source: FontSource::File(path.into(), font_index),
2463 stretch: FontStretch::NORMAL,
2464 style: FontStyle::Normal,
2465 weight: FontWeight::NORMAL,
2466 }
2467 }
2468
2469 pub fn from_bytes<N: Into<FontName>>(name: N, data: FontDataRef, font_index: u32) -> Self {
2477 CustomFont {
2478 name: name.into(),
2479 source: FontSource::Memory(data, font_index),
2480 stretch: FontStretch::NORMAL,
2481 style: FontStyle::Normal,
2482 weight: FontWeight::NORMAL,
2483 }
2484 }
2485
2486 pub fn from_other<N: Into<FontName>, O: Into<FontName>>(name: N, other_font: O) -> Self {
2492 CustomFont {
2493 name: name.into(),
2494 source: FontSource::Alias(other_font.into()),
2495 stretch: FontStretch::NORMAL,
2496 style: FontStyle::Normal,
2497 weight: FontWeight::NORMAL,
2498 }
2499 }
2500
2501 pub fn stretch(mut self, stretch: FontStretch) -> Self {
2505 self.stretch = stretch;
2506 self
2507 }
2508
2509 pub fn style(mut self, style: FontStyle) -> Self {
2513 self.style = style;
2514 self
2515 }
2516
2517 pub fn weight(mut self, weight: FontWeight) -> Self {
2521 self.weight = weight;
2522 self
2523 }
2524}
2525
2526#[derive(Clone, Copy, serde::Serialize, serde::Deserialize, Transitionable)]
2530#[serde(transparent)]
2531pub struct FontStretch(pub f32);
2532impl fmt::Debug for FontStretch {
2533 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2534 let name = self.name();
2535 if name.is_empty() {
2536 f.debug_tuple("FontStretch").field(&self.0).finish()
2537 } else {
2538 if f.alternate() {
2539 write!(f, "FontStretch::")?;
2540 }
2541 write!(f, "{name}")
2542 }
2543 }
2544}
2545impl PartialOrd for FontStretch {
2546 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
2547 Some(about_eq_ord(self.0, other.0, EQ_EPSILON))
2548 }
2549}
2550impl PartialEq for FontStretch {
2551 fn eq(&self, other: &Self) -> bool {
2552 about_eq(self.0, other.0, EQ_EPSILON)
2553 }
2554}
2555impl std::hash::Hash for FontStretch {
2556 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
2557 about_eq_hash(self.0, EQ_EPSILON, state)
2558 }
2559}
2560impl Default for FontStretch {
2561 fn default() -> FontStretch {
2562 FontStretch::NORMAL
2563 }
2564}
2565impl FontStretch {
2566 pub const ULTRA_CONDENSED: FontStretch = FontStretch(0.5);
2568 pub const EXTRA_CONDENSED: FontStretch = FontStretch(0.625);
2570 pub const CONDENSED: FontStretch = FontStretch(0.75);
2572 pub const SEMI_CONDENSED: FontStretch = FontStretch(0.875);
2574 pub const NORMAL: FontStretch = FontStretch(1.0);
2576 pub const SEMI_EXPANDED: FontStretch = FontStretch(1.125);
2578 pub const EXPANDED: FontStretch = FontStretch(1.25);
2580 pub const EXTRA_EXPANDED: FontStretch = FontStretch(1.5);
2582 pub const ULTRA_EXPANDED: FontStretch = FontStretch(2.0);
2584
2585 pub fn name(self) -> &'static str {
2587 macro_rules! name {
2588 ($($CONST:ident;)+) => {$(
2589 if self == Self::$CONST {
2590 return stringify!($CONST);
2591 }
2592 )+}
2593 }
2594 name! {
2595 ULTRA_CONDENSED;
2596 EXTRA_CONDENSED;
2597 CONDENSED;
2598 SEMI_CONDENSED;
2599 NORMAL;
2600 SEMI_EXPANDED;
2601 EXPANDED;
2602 EXTRA_EXPANDED;
2603 ULTRA_EXPANDED;
2604 }
2605 ""
2606 }
2607}
2608impl_from_and_into_var! {
2609 fn from(fct: Factor) -> FontStretch {
2610 FontStretch(fct.0)
2611 }
2612 fn from(pct: FactorPercent) -> FontStretch {
2613 FontStretch(pct.fct().0)
2614 }
2615 fn from(fct: f32) -> FontStretch {
2616 FontStretch(fct)
2617 }
2618}
2619impl From<ttf_parser::Width> for FontStretch {
2620 fn from(value: ttf_parser::Width) -> Self {
2621 use ttf_parser::Width::*;
2622 match value {
2623 UltraCondensed => FontStretch::ULTRA_CONDENSED,
2624 ExtraCondensed => FontStretch::EXTRA_CONDENSED,
2625 Condensed => FontStretch::CONDENSED,
2626 SemiCondensed => FontStretch::SEMI_CONDENSED,
2627 Normal => FontStretch::NORMAL,
2628 SemiExpanded => FontStretch::SEMI_EXPANDED,
2629 Expanded => FontStretch::EXPANDED,
2630 ExtraExpanded => FontStretch::EXTRA_EXPANDED,
2631 UltraExpanded => FontStretch::ULTRA_EXPANDED,
2632 }
2633 }
2634}
2635impl From<FontStretch> for ttf_parser::Width {
2636 fn from(value: FontStretch) -> Self {
2637 if value <= FontStretch::ULTRA_CONDENSED {
2638 ttf_parser::Width::UltraCondensed
2639 } else if value <= FontStretch::EXTRA_CONDENSED {
2640 ttf_parser::Width::ExtraCondensed
2641 } else if value <= FontStretch::CONDENSED {
2642 ttf_parser::Width::Condensed
2643 } else if value <= FontStretch::SEMI_CONDENSED {
2644 ttf_parser::Width::SemiCondensed
2645 } else if value <= FontStretch::NORMAL {
2646 ttf_parser::Width::Normal
2647 } else if value <= FontStretch::SEMI_EXPANDED {
2648 ttf_parser::Width::SemiExpanded
2649 } else if value <= FontStretch::EXPANDED {
2650 ttf_parser::Width::Expanded
2651 } else if value <= FontStretch::EXTRA_EXPANDED {
2652 ttf_parser::Width::ExtraExpanded
2653 } else {
2654 ttf_parser::Width::UltraExpanded
2655 }
2656 }
2657}
2658
2659#[derive(Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]
2661pub enum FontStyle {
2662 #[default]
2664 Normal,
2665 Italic,
2667 Oblique,
2669}
2670impl fmt::Debug for FontStyle {
2671 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2672 if f.alternate() {
2673 write!(f, "FontStyle::")?;
2674 }
2675 match self {
2676 Self::Normal => write!(f, "Normal"),
2677 Self::Italic => write!(f, "Italic"),
2678 Self::Oblique => write!(f, "Oblique"),
2679 }
2680 }
2681}
2682impl From<ttf_parser::Style> for FontStyle {
2683 fn from(value: ttf_parser::Style) -> Self {
2684 use ttf_parser::Style::*;
2685 match value {
2686 Normal => FontStyle::Normal,
2687 Italic => FontStyle::Italic,
2688 Oblique => FontStyle::Oblique,
2689 }
2690 }
2691}
2692
2693impl From<FontStyle> for ttf_parser::Style {
2694 fn from(value: FontStyle) -> Self {
2695 match value {
2696 FontStyle::Normal => Self::Normal,
2697 FontStyle::Italic => Self::Italic,
2698 FontStyle::Oblique => Self::Oblique,
2699 }
2700 }
2701}
2702
2703#[derive(Clone, Copy, Transitionable, serde::Serialize, serde::Deserialize)]
2706pub struct FontWeight(pub f32);
2707impl Default for FontWeight {
2708 fn default() -> FontWeight {
2709 FontWeight::NORMAL
2710 }
2711}
2712impl fmt::Debug for FontWeight {
2713 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2714 let name = self.name();
2715 if name.is_empty() {
2716 f.debug_tuple("FontWeight").field(&self.0).finish()
2717 } else {
2718 if f.alternate() {
2719 write!(f, "FontWeight::")?;
2720 }
2721 write!(f, "{name}")
2722 }
2723 }
2724}
2725impl PartialOrd for FontWeight {
2726 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
2727 Some(about_eq_ord(self.0, other.0, EQ_EPSILON_100))
2728 }
2729}
2730impl PartialEq for FontWeight {
2731 fn eq(&self, other: &Self) -> bool {
2732 about_eq(self.0, other.0, EQ_EPSILON_100)
2733 }
2734}
2735impl std::hash::Hash for FontWeight {
2736 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
2737 about_eq_hash(self.0, EQ_EPSILON_100, state)
2738 }
2739}
2740impl FontWeight {
2741 pub const THIN: FontWeight = FontWeight(100.0);
2743 pub const EXTRA_LIGHT: FontWeight = FontWeight(200.0);
2745 pub const LIGHT: FontWeight = FontWeight(300.0);
2747 pub const NORMAL: FontWeight = FontWeight(400.0);
2749 pub const MEDIUM: FontWeight = FontWeight(500.0);
2751 pub const SEMIBOLD: FontWeight = FontWeight(600.0);
2753 pub const BOLD: FontWeight = FontWeight(700.0);
2755 pub const EXTRA_BOLD: FontWeight = FontWeight(800.0);
2757 pub const BLACK: FontWeight = FontWeight(900.0);
2759
2760 pub fn name(self) -> &'static str {
2762 macro_rules! name {
2763 ($($CONST:ident;)+) => {$(
2764 if self == Self::$CONST {
2765 return stringify!($CONST);
2766 }
2767 )+}
2768 }
2769 name! {
2770 THIN;
2771 EXTRA_LIGHT;
2772 LIGHT;
2773 NORMAL;
2774 MEDIUM;
2775 SEMIBOLD;
2776 BOLD;
2777 EXTRA_BOLD;
2778 BLACK;
2779 }
2780 ""
2781 }
2782}
2783impl_from_and_into_var! {
2784 fn from(weight: u32) -> FontWeight {
2785 FontWeight(weight as f32)
2786 }
2787 fn from(weight: f32) -> FontWeight {
2788 FontWeight(weight)
2789 }
2790}
2791impl From<ttf_parser::Weight> for FontWeight {
2792 fn from(value: ttf_parser::Weight) -> Self {
2793 use ttf_parser::Weight::*;
2794 match value {
2795 Thin => FontWeight::THIN,
2796 ExtraLight => FontWeight::EXTRA_LIGHT,
2797 Light => FontWeight::LIGHT,
2798 Normal => FontWeight::NORMAL,
2799 Medium => FontWeight::MEDIUM,
2800 SemiBold => FontWeight::SEMIBOLD,
2801 Bold => FontWeight::BOLD,
2802 ExtraBold => FontWeight::EXTRA_BOLD,
2803 Black => FontWeight::BLACK,
2804 Other(o) => FontWeight(o as f32),
2805 }
2806 }
2807}
2808impl From<FontWeight> for ttf_parser::Weight {
2809 fn from(value: FontWeight) -> Self {
2810 ttf_parser::Weight::from(value.0 as u16)
2811 }
2812}
2813
2814#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
2816pub enum LineBreak {
2817 Auto,
2819 Loose,
2821 Normal,
2823 Strict,
2825 Anywhere,
2827}
2828impl Default for LineBreak {
2829 fn default() -> Self {
2831 LineBreak::Auto
2832 }
2833}
2834impl fmt::Debug for LineBreak {
2835 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2836 if f.alternate() {
2837 write!(f, "LineBreak::")?;
2838 }
2839 match self {
2840 LineBreak::Auto => write!(f, "Auto"),
2841 LineBreak::Loose => write!(f, "Loose"),
2842 LineBreak::Normal => write!(f, "Normal"),
2843 LineBreak::Strict => write!(f, "Strict"),
2844 LineBreak::Anywhere => write!(f, "Anywhere"),
2845 }
2846 }
2847}
2848
2849#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
2851pub enum Hyphens {
2852 None,
2854 Manual,
2859 Auto,
2861}
2862impl Default for Hyphens {
2863 fn default() -> Self {
2865 Hyphens::Auto
2866 }
2867}
2868impl fmt::Debug for Hyphens {
2869 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2870 if f.alternate() {
2871 write!(f, "Hyphens::")?;
2872 }
2873 match self {
2874 Hyphens::None => write!(f, "None"),
2875 Hyphens::Manual => write!(f, "Manual"),
2876 Hyphens::Auto => write!(f, "Auto"),
2877 }
2878 }
2879}
2880
2881#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
2887pub enum WordBreak {
2888 Normal,
2890 BreakAll,
2892 KeepAll,
2894}
2895impl Default for WordBreak {
2896 fn default() -> Self {
2898 WordBreak::Normal
2899 }
2900}
2901impl fmt::Debug for WordBreak {
2902 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2903 if f.alternate() {
2904 write!(f, "WordBreak::")?;
2905 }
2906 match self {
2907 WordBreak::Normal => write!(f, "Normal"),
2908 WordBreak::BreakAll => write!(f, "BreakAll"),
2909 WordBreak::KeepAll => write!(f, "KeepAll"),
2910 }
2911 }
2912}
2913
2914#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
2916pub enum Justify {
2917 Auto,
2921 InterWord,
2923 InterLetter,
2925}
2926impl Default for Justify {
2927 fn default() -> Self {
2929 Justify::Auto
2930 }
2931}
2932impl Justify {
2933 pub fn resolve(self, lang: &Lang) -> Self {
2935 match self {
2936 Self::Auto => match lang.language.as_str() {
2937 "zh" | "ja" | "ko" => Self::InterLetter,
2938 _ => Self::InterWord,
2939 },
2940 m => m,
2941 }
2942 }
2943}
2944impl fmt::Debug for Justify {
2945 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2946 if f.alternate() {
2947 write!(f, "Justify::")?;
2948 }
2949 match self {
2950 Justify::Auto => write!(f, "Auto"),
2951 Justify::InterWord => write!(f, "InterWord"),
2952 Justify::InterLetter => write!(f, "InterLetter"),
2953 }
2954 }
2955}
2956
2957#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
2965pub struct FontFaceMetrics {
2966 pub units_per_em: u32,
2970
2971 pub ascent: f32,
2973
2974 pub descent: f32,
2980
2981 pub line_gap: f32,
2983
2984 pub underline_position: f32,
2987
2988 pub underline_thickness: f32,
2990
2991 pub cap_height: f32,
2993
2994 pub x_height: f32,
2997
2998 pub bounds: euclid::Rect<f32, ()>,
3002}
3003impl FontFaceMetrics {
3004 pub fn sized(&self, font_size_px: Px) -> FontMetrics {
3006 let size_scale = 1.0 / self.units_per_em as f32 * font_size_px.0 as f32;
3007 let s = move |f: f32| Px((f * size_scale).round() as i32);
3008 FontMetrics {
3009 size_scale,
3010 ascent: s(self.ascent),
3011 descent: s(self.descent),
3012 line_gap: s(self.line_gap),
3013 underline_position: s(self.underline_position),
3014 underline_thickness: s(self.underline_thickness),
3015 cap_height: s(self.cap_height),
3016 x_height: (s(self.x_height)),
3017 bounds: {
3018 let b = self.bounds;
3019 PxRect::new(
3020 PxPoint::new(s(b.origin.x), s(b.origin.y)),
3021 PxSize::new(s(b.size.width), s(b.size.height)),
3022 )
3023 },
3024 }
3025 }
3026}
3027
3028#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
3032pub struct FontMetrics {
3033 pub size_scale: f32,
3035
3036 pub ascent: Px,
3038
3039 pub descent: Px,
3045
3046 pub line_gap: Px,
3048
3049 pub underline_position: Px,
3052
3053 pub underline_thickness: Px,
3055
3056 pub cap_height: Px,
3058
3059 pub x_height: Px,
3061
3062 pub bounds: PxRect,
3066}
3067impl FontMetrics {
3068 pub fn line_height(&self) -> Px {
3070 self.ascent - self.descent + self.line_gap
3071 }
3072}
3073
3074#[derive(Clone)]
3076pub enum TextTransformFn {
3077 None,
3079 Uppercase,
3081 Lowercase,
3083 Custom(Arc<dyn Fn(&Txt) -> Cow<Txt> + Send + Sync>),
3085}
3086impl TextTransformFn {
3087 pub fn transform<'t>(&self, text: &'t Txt) -> Cow<'t, Txt> {
3091 match self {
3092 TextTransformFn::None => Cow::Borrowed(text),
3093 TextTransformFn::Uppercase => {
3094 if text.chars().any(|c| !c.is_uppercase()) {
3095 Cow::Owned(text.to_uppercase().into())
3096 } else {
3097 Cow::Borrowed(text)
3098 }
3099 }
3100 TextTransformFn::Lowercase => {
3101 if text.chars().any(|c| !c.is_lowercase()) {
3102 Cow::Owned(text.to_lowercase().into())
3103 } else {
3104 Cow::Borrowed(text)
3105 }
3106 }
3107 TextTransformFn::Custom(fn_) => fn_(text),
3108 }
3109 }
3110
3111 pub fn custom(fn_: impl Fn(&Txt) -> Cow<Txt> + Send + Sync + 'static) -> Self {
3113 TextTransformFn::Custom(Arc::new(fn_))
3114 }
3115}
3116impl fmt::Debug for TextTransformFn {
3117 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3118 if f.alternate() {
3119 write!(f, "TextTransformFn::")?;
3120 }
3121 match self {
3122 TextTransformFn::None => write!(f, "None"),
3123 TextTransformFn::Uppercase => write!(f, "Uppercase"),
3124 TextTransformFn::Lowercase => write!(f, "Lowercase"),
3125 TextTransformFn::Custom(_) => write!(f, "Custom"),
3126 }
3127 }
3128}
3129impl PartialEq for TextTransformFn {
3130 fn eq(&self, other: &Self) -> bool {
3131 match (self, other) {
3132 (Self::Custom(l0), Self::Custom(r0)) => Arc::ptr_eq(l0, r0),
3133 _ => core::mem::discriminant(self) == core::mem::discriminant(other),
3134 }
3135 }
3136}
3137
3138#[derive(Copy, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
3140pub enum WhiteSpace {
3141 Preserve,
3143 Merge,
3145 MergeAll,
3147}
3148impl Default for WhiteSpace {
3149 fn default() -> Self {
3151 WhiteSpace::Preserve
3152 }
3153}
3154impl WhiteSpace {
3155 pub fn transform(self, text: &Txt) -> Cow<Txt> {
3159 match self {
3160 WhiteSpace::Preserve => Cow::Borrowed(text),
3161 WhiteSpace::Merge => {
3162 let is_white_space = |c: char| c.is_whitespace() && !"\n\r\u{85}".contains(c);
3163 let t = text.trim_matches(is_white_space);
3164
3165 let mut prev_space = false;
3166 for c in t.chars() {
3167 if is_white_space(c) {
3168 if prev_space || c != '\u{20}' {
3169 let mut r = String::new();
3172 let mut sep = "";
3173 for part in t.split(is_white_space).filter(|s| !s.is_empty()) {
3174 r.push_str(sep);
3175 r.push_str(part);
3176 sep = "\u{20}";
3177 }
3178 return Cow::Owned(Txt::from_str(&r));
3179 } else {
3180 prev_space = true;
3181 }
3182 } else {
3183 prev_space = false;
3184 }
3185 }
3186
3187 if t.len() != text.len() {
3188 Cow::Owned(Txt::from_str(t))
3189 } else {
3190 Cow::Borrowed(text)
3191 }
3192 }
3193 WhiteSpace::MergeAll => {
3194 let t = text.trim();
3195
3196 let mut prev_space = false;
3197 for c in t.chars() {
3198 if c.is_whitespace() {
3199 if prev_space || c != '\u{20}' {
3200 let mut r = String::new();
3203 let mut sep = "";
3204 for part in t.split_whitespace() {
3205 r.push_str(sep);
3206 r.push_str(part);
3207 sep = "\u{20}";
3208 }
3209 return Cow::Owned(Txt::from_str(&r));
3210 } else {
3211 prev_space = true;
3212 }
3213 } else {
3214 prev_space = false;
3215 }
3216 }
3217
3218 if t.len() != text.len() {
3219 Cow::Owned(Txt::from_str(t))
3220 } else {
3221 Cow::Borrowed(text)
3222 }
3223 }
3224 }
3225 }
3226}
3227impl fmt::Debug for WhiteSpace {
3228 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3229 if f.alternate() {
3230 write!(f, "WhiteSpace::")?;
3231 }
3232 match self {
3233 WhiteSpace::Preserve => write!(f, "Preserve"),
3234 WhiteSpace::Merge => write!(f, "Merge"),
3235 WhiteSpace::MergeAll => write!(f, "MergeAll"),
3236 }
3237 }
3238}
3239
3240#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
3242pub struct CaretIndex {
3243 pub index: usize,
3247 pub line: usize,
3257}
3258
3259impl PartialEq for CaretIndex {
3260 fn eq(&self, other: &Self) -> bool {
3261 self.index == other.index
3262 }
3263}
3264impl Eq for CaretIndex {}
3265impl CaretIndex {
3266 pub const ZERO: CaretIndex = CaretIndex { index: 0, line: 0 };
3268}
3269impl PartialOrd for CaretIndex {
3270 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
3271 Some(self.cmp(other))
3272 }
3273}
3274impl Ord for CaretIndex {
3275 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
3276 self.index.cmp(&other.index)
3277 }
3278}
3279
3280#[derive(Debug, Clone)]
3282pub enum FontLoadingError {
3283 UnknownFormat,
3285 NoSuchFontInCollection,
3290 Parse(ttf_parser::FaceParsingError),
3292 NoFilesystem,
3295 Io(Arc<std::io::Error>),
3297}
3298impl PartialEq for FontLoadingError {
3299 fn eq(&self, other: &Self) -> bool {
3300 match (self, other) {
3301 (Self::Io(l0), Self::Io(r0)) => Arc::ptr_eq(l0, r0),
3302 _ => core::mem::discriminant(self) == core::mem::discriminant(other),
3303 }
3304 }
3305}
3306impl From<std::io::Error> for FontLoadingError {
3307 fn from(error: std::io::Error) -> FontLoadingError {
3308 Self::Io(Arc::new(error))
3309 }
3310}
3311impl fmt::Display for FontLoadingError {
3312 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3313 match self {
3314 Self::UnknownFormat => write!(f, "unknown format"),
3315 Self::NoSuchFontInCollection => write!(f, "no such font in the collection"),
3316 Self::NoFilesystem => write!(f, "no filesystem present"),
3317 Self::Parse(e) => fmt::Display::fmt(e, f),
3318 Self::Io(e) => fmt::Display::fmt(e, f),
3319 }
3320 }
3321}
3322impl std::error::Error for FontLoadingError {
3323 fn cause(&self) -> Option<&dyn std::error::Error> {
3324 match self {
3325 FontLoadingError::Parse(e) => Some(e),
3326 FontLoadingError::Io(e) => Some(e),
3327 _ => None,
3328 }
3329 }
3330}
3331
3332#[cfg(test)]
3333mod tests {
3334 use zng_app::APP;
3335
3336 use super::*;
3337
3338 #[test]
3339 fn generic_fonts_default() {
3340 let _app = APP.minimal().extend(FontManager::default()).run_headless(false);
3341
3342 assert_eq!(FontName::sans_serif(), GenericFonts {}.sans_serif(&lang!(und)))
3343 }
3344
3345 #[test]
3346 fn generic_fonts_fallback() {
3347 let _app = APP.minimal().extend(FontManager::default()).run_headless(false);
3348
3349 assert_eq!(FontName::sans_serif(), GenericFonts {}.sans_serif(&lang!(en_US)));
3350 assert_eq!(FontName::sans_serif(), GenericFonts {}.sans_serif(&lang!(es)));
3351 }
3352
3353 #[test]
3354 fn generic_fonts_get1() {
3355 let mut app = APP.minimal().extend(FontManager::default()).run_headless(false);
3356 GenericFonts {}.set_sans_serif(lang!(en_US), "Test Value");
3357 app.update(false).assert_wait();
3358
3359 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en-US")), "Test Value");
3360 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en")), "Test Value");
3361 }
3362
3363 #[test]
3364 fn generic_fonts_get2() {
3365 let mut app = APP.minimal().extend(FontManager::default()).run_headless(false);
3366 GenericFonts {}.set_sans_serif(lang!(en), "Test Value");
3367 app.update(false).assert_wait();
3368
3369 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en-US")), "Test Value");
3370 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en")), "Test Value");
3371 }
3372
3373 #[test]
3374 fn generic_fonts_get_best() {
3375 let mut app = APP.minimal().extend(FontManager::default()).run_headless(false);
3376 GenericFonts {}.set_sans_serif(lang!(en), "Test Value");
3377 GenericFonts {}.set_sans_serif(lang!(en_US), "Best");
3378 app.update(false).assert_wait();
3379
3380 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en-US")), "Best");
3381 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en")), "Test Value");
3382 assert_eq!(&GenericFonts {}.sans_serif(&lang!("und")), "sans-serif");
3383 }
3384
3385 #[test]
3386 fn generic_fonts_get_no_lang_match() {
3387 let mut app = APP.minimal().extend(FontManager::default()).run_headless(false);
3388 GenericFonts {}.set_sans_serif(lang!(es_US), "Test Value");
3389 app.update(false).assert_wait();
3390
3391 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en-US")), "sans-serif");
3392 assert_eq!(&GenericFonts {}.sans_serif(&lang!("es")), "Test Value");
3393 }
3394}