zng_ext_font/
lib.rs

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//!
4//! Font loading, text segmenting and shaping.
5//!
6//! # Crate
7//!
8#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
9// suppress nag about very simple boxed closure signatures.
10#![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/// Font family name.
73///
74/// A possible value for the `font_family` property.
75///
76/// # Case Insensitive
77///
78/// Font family names are case-insensitive. `"Arial"` and `"ARIAL"` are equal and have the same hash.
79#[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            // case insensitive eq
116            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    /// New font name from `&'static str`.
136    pub const fn from_static(name: &'static str) -> Self {
137        FontName {
138            txt: Txt::from_static(name),
139            is_ascii: {
140                // str::is_ascii is not const
141                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    /// New font name.
157    ///
158    /// Note that the inner name value is a [`Txt`] so you can define a font name using `&'static str` or `String`.
159    ///
160    /// Font names are case insensitive but the input casing is preserved, this casing shows during display and in
161    /// the value of [`name`](Self::name).
162    ///
163    /// [`Txt`]: zng_txt::Txt
164    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    /// New "serif" font name.
173    ///
174    /// Serif fonts represent the formal text style for a script.
175    pub fn serif() -> Self {
176        Self::new("serif")
177    }
178
179    /// New "sans-serif" font name.
180    ///
181    /// Glyphs in sans-serif fonts, are generally low contrast (vertical and horizontal stems have close to the same thickness)
182    /// and have stroke endings that are plain — without any flaring, cross stroke, or other ornamentation.
183    pub fn sans_serif() -> Self {
184        Self::new("sans-serif")
185    }
186
187    /// New "monospace" font name.
188    ///
189    /// The sole criterion of a monospace font is that all glyphs have the same fixed width.
190    pub fn monospace() -> Self {
191        Self::new("monospace")
192    }
193
194    /// New "cursive" font name.
195    ///
196    /// Glyphs in cursive fonts generally use a more informal script style, and the result looks more
197    /// like handwritten pen or brush writing than printed letter-work.
198    pub fn cursive() -> Self {
199        Self::new("cursive")
200    }
201
202    /// New "fantasy" font name.
203    ///
204    /// Fantasy fonts are primarily decorative or expressive fonts that contain decorative or expressive representations of characters.
205    pub fn fantasy() -> Self {
206        Self::new("fantasy")
207    }
208
209    /// Reference the font name string.
210    pub fn name(&self) -> &str {
211        &self.txt
212    }
213
214    /// Unwraps into a [`Txt`].
215    ///
216    /// [`Txt`]: zng_txt::Txt
217    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/// A list of [font names](FontName) in priority order.
270///
271/// # Examples
272///
273/// This type is usually initialized using conversion:
274///
275/// ```
276/// # use zng_ext_font::*;
277/// fn foo(font_names: impl Into<FontNames>) {}
278///
279/// foo(["Arial", "sans-serif", "monospace"]);
280/// ```
281///
282/// You can also use the specialized [`push`](Self::push) that converts:
283///
284/// ```
285/// # use zng_ext_font::*;
286/// let user_preference = "Comic Sans".to_owned();
287///
288/// let mut names = FontNames::empty();
289/// names.push(user_preference);
290/// names.push("Arial");
291/// names.extend(FontNames::default());
292/// ```
293///
294/// # Default
295///
296/// The default value is the [`system_ui`](FontNames::system_ui) for the undefined language (`und`).
297#[derive(Eq, PartialEq, Hash, Clone, serde::Serialize, serde::Deserialize)]
298#[serde(transparent)]
299pub struct FontNames(pub Vec<FontName>);
300impl FontNames {
301    /// Empty list.
302    pub fn empty() -> Self {
303        FontNames(vec![])
304    }
305
306    /// Returns the default UI font names for Windows.
307    pub fn windows_ui(lang: &Lang) -> Self {
308        // source: VSCode
309        // https://github.com/microsoft/vscode/blob/6825c886700ac11d07f7646d8d8119c9cdd9d288/src/vs/code/electron-sandbox/processExplorer/media/processExplorer.css
310
311        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    /// Returns the default UI font names for MacOS/iOS.
325    pub fn mac_ui(lang: &Lang) -> Self {
326        // source: VSCode
327
328        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    /// Returns the default UI font names for Linux.
349    pub fn linux_ui(lang: &Lang) -> Self {
350        // source: VSCode
351
352        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    /// Returns the default UI font names for the current operating system.
406    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    /// Push a font name from any type that converts to [`FontName`].
419    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    /// Change in [`FONTS`] that may cause a font query to now give
557    /// a different result.
558    ///
559    /// # Cache
560    ///
561    /// Every time this event updates the font cache is cleared. Meaning that even
562    /// if the query returns the same font it will be a new reference.
563    ///
564    /// Fonts only unload when all references to then are dropped, so you can still continue using
565    /// old references if you don't want to monitor this event.
566    pub static FONT_CHANGED_EVENT: FontChangedArgs;
567}
568
569event_args! {
570    /// [`FONT_CHANGED_EVENT`] arguments.
571    pub struct FontChangedArgs {
572        /// The change that happened.
573        pub change: FontChange,
574
575        ..
576
577        /// Broadcast to all widgets.
578        fn delivery_list(&self, list: &mut UpdateDeliveryList) {
579            list.search_all()
580        }
581    }
582}
583
584/// Possible changes in a [`FontChangedArgs`].
585#[derive(Clone, Debug)]
586pub enum FontChange {
587    /// OS fonts change.
588    ///
589    /// Currently this is only supported in Microsoft Windows.
590    SystemFonts,
591
592    /// Custom fonts change caused by call to [`FONTS.register`] or [`FONTS.unregister`].
593    ///
594    /// [`FONTS.register`]: FONTS::register
595    /// [`FONTS.unregister`]: FONTS::unregister
596    CustomFonts,
597
598    /// Custom request caused by call to [`FONTS.refresh`].
599    ///
600    /// [`FONTS.refresh`]: FONTS::refresh
601    Refresh,
602
603    /// One of the [`GenericFonts`] was set for the language.
604    ///
605    /// The font name is one of [`FontName`] generic names.
606    ///
607    /// [`GenericFonts`]: struct@GenericFonts
608    GenericFont(FontName, Lang),
609
610    /// A new [fallback](GenericFonts::fallback) font was set for the language.
611    Fallback(Lang),
612}
613
614/// Application extension that manages text fonts.
615///
616/// Services this extension provides:
617///
618/// * [`FONTS`] - Service that finds and loads fonts.
619/// * [`HYPHENATION`] - Service that loads and applies hyphenation dictionaries.
620///
621/// Events this extension provides:
622///
623/// * [`FONT_CHANGED_EVENT`] - Font config or system fonts changed.
624#[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                // cut circular reference so that when the last font ref gets dropped
657                // this font face also gets dropped. Also tag the font as unregistered
658                // so it does not create further circular references.
659                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
706/// Font loading, custom fonts and app font configuration.
707pub struct FONTS;
708impl FONTS {
709    /// Clear cache and notify `Refresh` in [`FONT_CHANGED_EVENT`].
710    ///
711    /// See the event documentation for more information.
712    pub fn refresh(&self) {
713        FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::Refresh));
714    }
715
716    /// Remove all unused fonts from cache.
717    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    /// Actual name of generic fonts.
726    pub fn generics(&self) -> &'static GenericFonts {
727        &GenericFonts {}
728    }
729
730    /// Load and register a custom font.
731    ///
732    /// If the font loads correctly a [`FONT_CHANGED_EVENT`] notification is scheduled.
733    /// Fonts sourced from a file are not monitored for changes, you can *reload* the font
734    /// by calling `register` again with the same font name.
735    ///
736    /// The returned response will update once when the font finishes loading with the new font.
737    /// At minimum the new font will be available on the next update.
738    pub fn register(&self, custom_font: CustomFont) -> ResponseVar<Result<FontFace, FontLoadingError>> {
739        FontFaceLoader::register(custom_font)
740    }
741
742    /// Removes a custom font family. If the font faces are not in use it is also unloaded.
743    ///
744    /// Returns a response var that updates once with a value that indicates if any custom font was removed.
745    pub fn unregister(&self, custom_family: FontName) -> ResponseVar<bool> {
746        FONTS_SV.write().loader.unregister(custom_family)
747    }
748
749    /// Gets a font list that best matches the query.
750    pub fn list(
751        &self,
752        families: &[FontName],
753        style: FontStyle,
754        weight: FontWeight,
755        stretch: FontStretch,
756        lang: &Lang,
757    ) -> ResponseVar<FontFaceList> {
758        // try with shared lock
759        if let Some(cached) = FONTS_SV.read().loader.try_list(families, style, weight, stretch, lang) {
760            return cached;
761        }
762        // begin load with exclusive lock (cache is tried again in `load`)
763        FONTS_SV.write().loader.load_list(families, style, weight, stretch, lang)
764    }
765
766    /// Find a single font face that best matches the query.
767    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        // try with shared lock
776        if let Some(cached) = FONTS_SV.read().loader.try_cached(family, style, weight, stretch, lang) {
777            return cached;
778        }
779        // begin load with exclusive lock (cache is tried again in `load`)
780        FONTS_SV.write().loader.load(family, style, weight, stretch, lang)
781    }
782
783    /// Find a single font face with all normal properties.
784    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    /// Find a single font face with italic style, normal weight and stretch.
789    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    /// Find a single font face with bold weight, normal style and stretch.
794    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    /// Gets all [registered](Self::register) font families.
799    pub fn custom_fonts(&self) -> Vec<FontName> {
800        FONTS_SV.read().loader.custom_fonts.keys().cloned().collect()
801    }
802
803    /// Query all font families available in the system.
804    ///
805    /// Note that the variable will only update once with the query result, this is not a live view.
806    pub fn system_fonts(&self) -> ResponseVar<Vec<FontName>> {
807        query_util::system_all()
808    }
809
810    /// Gets the system font anti-aliasing config as a read-only var.
811    ///
812    /// The variable updates when the system config changes.
813    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    /// Returns the key.
846    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/// A font face selected from a font family.
853///
854/// Usually this is part of a [`FontList`] that can be requested from
855/// the [`FONTS`] service.
856///
857/// This type is a shared reference to the font data, cloning it is cheap.
858#[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    /// New empty font face.
919    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            // values copied from a monospace font
931            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                // `xMin`/`xMax`/`yMin`/`yMax`
941                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    /// Is empty font face.
955    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                    // try again with font 0 (font-kit selects a high index for Ubuntu Font)
1008                    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        // all tables used by `ttf_parser::Face::glyph_raster_image`
1033        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                    // try again with font 0 (font-kit selects a high index for Ubuntu Font)
1074                    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            // observed this in Noto Color Emoji (with font_kit)
1133            tracing::debug!("font {display_name:?} units_per_em 0");
1134            return Err(FontLoadingError::UnknownFormat);
1135        }
1136
1137        // all tables used by `ttf_parser::Face::glyph_raster_image`
1138        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    /// Loads the harfbuzz face.
1199    ///
1200    /// Loads from in memory [`bytes`].
1201    ///
1202    /// Returns `None` if [`is_empty`].
1203    ///
1204    /// [`is_empty`]: Self::is_empty
1205    /// [`bytes`]: Self::bytes
1206    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    /// Loads the full TTF face.
1215    ///
1216    /// Loads from in memory [`bytes`].
1217    ///
1218    /// Returns `None` if [`is_empty`].
1219    ///
1220    /// [`is_empty`]: Self::is_empty
1221    /// [`bytes`]: Self::bytes
1222    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    /// Reference the font file bytes.
1231    pub fn bytes(&self) -> &FontDataRef {
1232        &self.0.data
1233    }
1234    /// Index of the font face in the [font file](Self::bytes).
1235    pub fn index(&self) -> u32 {
1236        self.0.face_index
1237    }
1238
1239    /// Font full name.
1240    pub fn display_name(&self) -> &FontName {
1241        &self.0.display_name
1242    }
1243
1244    /// Font family name.
1245    pub fn family_name(&self) -> &FontName {
1246        &self.0.family_name
1247    }
1248
1249    /// Font globally unique name.
1250    pub fn postscript_name(&self) -> Option<&str> {
1251        self.0.postscript_name.as_deref()
1252    }
1253
1254    /// Font style.
1255    pub fn style(&self) -> FontStyle {
1256        self.0.style
1257    }
1258
1259    /// Font weight.
1260    pub fn weight(&self) -> FontWeight {
1261        self.0.weight
1262    }
1263
1264    /// Font stretch.
1265    pub fn stretch(&self) -> FontStretch {
1266        self.0.stretch
1267    }
1268
1269    /// Font is monospace (fixed-width).
1270    pub fn is_monospace(&self) -> bool {
1271        self.0.flags.contains(FontFaceFlags::IS_MONOSPACE)
1272    }
1273
1274    /// Font metrics in font units.
1275    pub fn metrics(&self) -> &FontFaceMetrics {
1276        &self.0.metrics
1277    }
1278
1279    /// Gets a cached sized [`Font`].
1280    ///
1281    /// The `font_size` is the size of `1 font EM` in pixels.
1282    ///
1283    /// The `variations` are custom [font variations] that will be used
1284    /// during shaping and rendering.
1285    ///
1286    /// [font variations]: crate::font_features::FontVariations::finalize
1287    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    /// Gets what font synthesis to use to better render this font face given the style and weight.
1302    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            // if requested oblique or italic and the face is neither.
1307            synth |= FontSynthesis::OBLIQUE;
1308        }
1309        if weight > self.weight() {
1310            // if requested a weight larger then the face weight the renderer can
1311            // add extra stroke outlines to compensate.
1312            synth |= FontSynthesis::BOLD;
1313        }
1314
1315        synth
1316    }
1317
1318    /// If this font face is cached. All font faces are cached by default, a font face can be detached from
1319    /// cache when a [`FONT_CHANGED_EVENT`] event happens, in this case the font can still be used normally, but
1320    /// a request for the same font name will return a different reference.
1321    pub fn is_cached(&self) -> bool {
1322        !self.0.m.lock().unregistered
1323    }
1324
1325    /// CPAL table.
1326    ///
1327    /// Is empty if not provided by the font.
1328    pub fn color_palettes(&self) -> &ColorPalettes {
1329        &self.0.color_palettes
1330    }
1331
1332    /// COLR table.
1333    ///
1334    /// Is empty if not provided by the font.
1335    pub fn color_glyphs(&self) -> &ColorGlyphs {
1336        &self.0.color_glyphs
1337    }
1338
1339    /// If the font provides glyph substitutions.
1340    pub fn has_ligatures(&self) -> bool {
1341        self.0.flags.contains(FontFaceFlags::HAS_LIGATURES)
1342    }
1343
1344    /// If this font provides custom positioned carets for some or all ligature glyphs.
1345    ///
1346    /// If `true` the [`Font::ligature_caret_offsets`] method can be used to get the caret offsets, otherwise
1347    /// it always returns empty.
1348    pub fn has_ligature_caret_offsets(&self) -> bool {
1349        !self.0.lig_carets.is_empty()
1350    }
1351
1352    /// If this font has bitmap images associated with some glyphs.
1353    pub fn has_raster_images(&self) -> bool {
1354        self.0.flags.contains(FontFaceFlags::HAS_RASTER_IMAGES)
1355    }
1356
1357    /// If this font has SVG images associated with some glyphs.
1358    pub fn has_svg_images(&self) -> bool {
1359        self.0.flags.contains(FontFaceFlags::HAS_SVG_IMAGES)
1360    }
1361}
1362
1363/// A sized font face.
1364///
1365/// A sized font can be requested from a [`FontFace`].
1366///
1367/// This type is a shared reference to the loaded font data, cloning it is cheap.
1368#[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    /// Reference the font face source of this font.
1453    pub fn face(&self) -> &FontFace {
1454        &self.0.face
1455    }
1456
1457    /// Gets the sized harfbuzz font.
1458    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    /// Font size.
1470    ///
1471    /// This is also the *pixels-per-em* value.
1472    pub fn size(&self) -> Px {
1473        self.0.size
1474    }
1475
1476    /// Custom font variations.
1477    pub fn variations(&self) -> &RFontVariations {
1478        &self.0.variations
1479    }
1480
1481    /// Sized font metrics.
1482    pub fn metrics(&self) -> &FontMetrics {
1483        &self.0.metrics
1484    }
1485
1486    /// Iterate over pixel offsets relative to `lig` glyph start that represents the
1487    /// caret offset for each cluster that is covered by the ligature, after the first.
1488    ///
1489    /// The caret offset for the first cluster is the glyph offset and is not yielded in the iterator. The
1490    /// yielded offsets are relative to the glyph position.
1491    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/// A list of [`FontFace`] resolved from a [`FontName`] list, plus the [fallback](GenericFonts::fallback) font.
1556///
1557/// Glyphs that are not resolved by the first font fallback to the second font and so on.
1558#[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    /// New list with only the [`FontFace::empty`].
1567    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    /// Style requested in the query that generated this font face list.
1577    pub fn requested_style(&self) -> FontStyle {
1578        self.requested_style
1579    }
1580
1581    /// Weight requested in the query that generated this font face list.
1582    pub fn requested_weight(&self) -> FontWeight {
1583        self.requested_weight
1584    }
1585
1586    /// Stretch requested in the query that generated this font face list.
1587    pub fn requested_stretch(&self) -> FontStretch {
1588        self.requested_stretch
1589    }
1590
1591    /// The font face that best matches the requested properties.
1592    pub fn best(&self) -> &FontFace {
1593        &self.fonts[0]
1594    }
1595
1596    /// Gets the font synthesis to use to better render the given font face on the list.
1597    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    /// Iterate over font faces, more specific first.
1606    pub fn iter(&self) -> std::slice::Iter<'_, FontFace> {
1607        self.fonts.iter()
1608    }
1609
1610    /// Number of font faces in the list.
1611    ///
1612    /// This is at least `1`, but can be the empty face.
1613    pub fn len(&self) -> usize {
1614        self.fonts.len()
1615    }
1616
1617    /// Is length `1` and only contains the empty face.
1618    pub fn is_empty(&self) -> bool {
1619        self.fonts[0].is_empty() && self.fonts.len() == 1
1620    }
1621
1622    /// Gets a sized font list.
1623    ///
1624    /// This calls [`FontFace::sized`] for each font in the list.
1625    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    /// Both are equal if each point to the same fonts in the same order and have the same requested properties.
1636    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/// A list of [`Font`] created from a [`FontFaceList`].
1670#[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)] // cannot be empty.
1678impl FontList {
1679    /// The font that best matches the requested properties.
1680    pub fn best(&self) -> &Font {
1681        &self.fonts[0]
1682    }
1683
1684    /// Font size requested in the query that generated this font list.
1685    pub fn requested_size(&self) -> Px {
1686        self.fonts[0].size()
1687    }
1688
1689    /// Style requested in the query that generated this font list.
1690    pub fn requested_style(&self) -> FontStyle {
1691        self.requested_style
1692    }
1693
1694    /// Weight requested in the query that generated this font list.
1695    pub fn requested_weight(&self) -> FontWeight {
1696        self.requested_weight
1697    }
1698
1699    /// Stretch requested in the query that generated this font list.
1700    pub fn requested_stretch(&self) -> FontStretch {
1701        self.requested_stretch
1702    }
1703
1704    /// Gets the font synthesis to use to better render the given font on the list.
1705    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    /// Iterate over font faces, more specific first.
1714    pub fn iter(&self) -> std::slice::Iter<'_, Font> {
1715        self.fonts.iter()
1716    }
1717
1718    /// Number of font faces in the list.
1719    ///
1720    /// This is at least `1`.
1721    pub fn len(&self) -> usize {
1722        self.fonts.len()
1723    }
1724
1725    /// Returns `true` is `self` is sized from the `faces` list.
1726    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    /// Both are equal if each point to the same fonts in the same order and have the same requested properties.
1742    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, // face shared
1830                            Some(None) => false,                                // loading for no one
1831                            None => true,                                       // retain not found
1832                        }
1833                    })
1834                } else {
1835                    // response var shared
1836                    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        // start loading
1847        let resp = task::respond(FontFace::load_custom(custom_font));
1848
1849        // modify loader.custom_fonts at the end of whatever update is happening when finishes loading.
1850        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    /// Try cached again, otherwise begins loading and inserts the response in the cache.
1998    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    /// Get a `font_name` that already resolved generic names if it is already in cache.
2012    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    /// Load a `font_name` that already resolved generic names.
2036    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            // it is common for custom font names to only have one face.
2106            return faces[0].clone();
2107        }
2108
2109        let mut set = Vec::with_capacity(faces.len());
2110        let mut set_dist = 0.0f64; // stretch distance of current set if it is not empty.
2111
2112        // # Filter Stretch
2113        //
2114        // Closest to query stretch, if the query is narrow, closest narrow then
2115        // closest wide, if the query is wide the reverse.
2116        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                // better candidate found, restart closest set.
2132                set_dist = dist;
2133                set.clear();
2134                set.push(face);
2135            } else if (dist - set_dist).abs() < 0.0001 {
2136                // another candidate, same distance.
2137                set.push(face);
2138            }
2139        }
2140        if set.len() == 1 {
2141            return set[0].clone();
2142        }
2143
2144        // # Filter Style
2145        //
2146        // Each query style has a fallback preference, we retain the faces that have the best
2147        // style given the query preference.
2148        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        // # Filter Weight
2166        //
2167        // a: under 400 query matches query then descending under query then ascending over query.
2168        // b: over 500 query matches query then ascending over query then descending under query.
2169        //
2170        // c: in 400..=500 query matches query then ascending to 500 then descending under query
2171        //     then ascending over 500.
2172        let add_penalty = if weight.0 >= 400.0 && weight.0 <= 500.0 {
2173            // c:
2174            |face: &FontFace, weight: FontWeight, dist: &mut f64| {
2175                // Add penalty for:
2176                if face.weight() < weight {
2177                    // Not being in search up to 500
2178                    *dist += 100.0;
2179                } else if face.weight().0 > 500.0 {
2180                    // Not being in search down to 0
2181                    *dist += 600.0;
2182                }
2183            }
2184        } else if weight.0 < 400.0 {
2185            // a:
2186            |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            // b:
2194            |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        // error here means the entire renderer was already dropped.
2234        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        // error here means the entire renderer was already dropped.
2255        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/// Generic fonts configuration for the app.
2309///
2310/// This type can be accessed from the [`FONTS`] service.
2311///
2312/// # Defaults
2313///
2314/// By default the `serif`, `sans_serif`, `monospace`, `cursive` and `fantasy` are set to their own generic name,
2315/// this delegates the resolution to the operating system.
2316///
2317/// The default `fallback` font is "Segoe UI Symbol" for Windows, "Standard Symbols PS" for Linux and "sans-serif" for others.
2318///
2319/// See also [`FontNames::system_ui`] for the default font selection for UIs.
2320///
2321/// [`FontNames::system_ui`]: crate::FontNames::system_ui
2322#[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    ///
2328    /// Returns a font name for the best `lang` match.
2329    ///
2330    #[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    ///
2338    /// The change applied for the next update.
2339    ///
2340    /// Use `lang!(und)` to set name used when no language matches.
2341    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] // for zng fmt
2356    impl_fallback_accessors! {
2357        serif="serif", sans_serif="sans-serif", monospace="monospace", cursive="cursive", fantasy="fantasy"
2358    }
2359
2360    /// Gets the ultimate fallback font used when none of the other fonts support a glyph.
2361    ///
2362    /// Returns a font name.
2363    pub fn fallback(&self, lang: &Lang) -> FontName {
2364        GENERIC_FONTS_SV.read().fallback.get(lang).unwrap().clone()
2365    }
2366
2367    /// Sets the ultimate fallback font used when none of other fonts support a glyph.
2368    ///
2369    /// The change applies for the next update.
2370    ///
2371    /// Use `lang!(und)` to set name used when no language matches.
2372    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    /// Returns the font name registered for the generic `name` and `lang`.
2385    ///
2386    /// Returns `None` if `name` if not one of the generic font names.
2387    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/// Reference to in memory font data.
2405#[derive(Clone)]
2406pub struct FontDataRef(pub Arc<Vec<u8>>);
2407impl FontDataRef {
2408    /// Copy bytes from embedded font.
2409    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/// Custom font builder.
2434#[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    /// A custom font loaded from a file.
2444    ///
2445    /// If the file is a collection of fonts, `font_index` determines which, otherwise just pass `0`.
2446    ///
2447    /// The font is loaded in [`FONTS.register`].
2448    ///
2449    /// [`FONTS.register`]: FONTS::register
2450    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    /// A custom font loaded from a shared byte slice.
2461    ///
2462    /// If the font data is a collection of fonts, `font_index` determines which, otherwise just pass `0`.
2463    ///
2464    /// The font is loaded in [`FONTS.register`].
2465    ///
2466    /// [`FONTS.register`]: FONTS::register
2467    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    /// A custom font that maps to another font.
2478    ///
2479    /// The font is loaded in [`FONTS.register`].
2480    ///
2481    /// [`FONTS.register`]: FONTS::register
2482    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    /// Set the [`FontStretch`].
2493    ///
2494    /// Default is [`FontStretch::NORMAL`].
2495    pub fn stretch(mut self, stretch: FontStretch) -> Self {
2496        self.stretch = stretch;
2497        self
2498    }
2499
2500    /// Set the [`FontStyle`].
2501    ///
2502    /// Default is [`FontStyle::Normal`].
2503    pub fn style(mut self, style: FontStyle) -> Self {
2504        self.style = style;
2505        self
2506    }
2507
2508    /// Set the [`FontWeight`].
2509    ///
2510    /// Default is [`FontWeight::NORMAL`].
2511    pub fn weight(mut self, weight: FontWeight) -> Self {
2512        self.weight = weight;
2513        self
2514    }
2515}
2516
2517/// The width of a font as an approximate fraction of the normal width.
2518///
2519/// Widths range from 0.5 to 2.0 inclusive, with 1.0 as the normal width.
2520#[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    /// Ultra-condensed width (50%), the narrowest possible.
2564    pub const ULTRA_CONDENSED: FontStretch = FontStretch(0.5);
2565    /// Extra-condensed width (62.5%).
2566    pub const EXTRA_CONDENSED: FontStretch = FontStretch(0.625);
2567    /// Condensed width (75%).
2568    pub const CONDENSED: FontStretch = FontStretch(0.75);
2569    /// Semi-condensed width (87.5%).
2570    pub const SEMI_CONDENSED: FontStretch = FontStretch(0.875);
2571    /// Normal width (100%).
2572    pub const NORMAL: FontStretch = FontStretch(1.0);
2573    /// Semi-expanded width (112.5%).
2574    pub const SEMI_EXPANDED: FontStretch = FontStretch(1.125);
2575    /// Expanded width (125%).
2576    pub const EXPANDED: FontStretch = FontStretch(1.25);
2577    /// Extra-expanded width (150%).
2578    pub const EXTRA_EXPANDED: FontStretch = FontStretch(1.5);
2579    /// Ultra-expanded width (200%), the widest possible.
2580    pub const ULTRA_EXPANDED: FontStretch = FontStretch(2.0);
2581
2582    /// Gets the const name, if this value is one of the constants.
2583    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/// The italic or oblique form of a font.
2657#[derive(Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]
2658pub enum FontStyle {
2659    /// The regular form.
2660    #[default]
2661    Normal,
2662    /// A form that is generally cursive in nature.
2663    Italic,
2664    /// A skewed version of the regular form.
2665    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/// The degree of stroke thickness of a font. This value ranges from 100.0 to 900.0,
2701/// with 400.0 as normal.
2702#[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    /// Thin weight (100), the thinnest value.
2745    pub const THIN: FontWeight = FontWeight(100.0);
2746    /// Extra light weight (200).
2747    pub const EXTRA_LIGHT: FontWeight = FontWeight(200.0);
2748    /// Light weight (300).
2749    pub const LIGHT: FontWeight = FontWeight(300.0);
2750    /// Normal (400).
2751    pub const NORMAL: FontWeight = FontWeight(400.0);
2752    /// Medium weight (500, higher than normal).
2753    pub const MEDIUM: FontWeight = FontWeight(500.0);
2754    /// Semi-bold weight (600).
2755    pub const SEMIBOLD: FontWeight = FontWeight(600.0);
2756    /// Bold weight (700).
2757    pub const BOLD: FontWeight = FontWeight(700.0);
2758    /// Extra-bold weight (800).
2759    pub const EXTRA_BOLD: FontWeight = FontWeight(800.0);
2760    /// Black weight (900), the thickest value.
2761    pub const BLACK: FontWeight = FontWeight(900.0);
2762
2763    /// Gets the const name, if this value is one of the constants.
2764    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/// Configuration of text wrapping for Chinese, Japanese, or Korean text.
2818#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
2819pub enum LineBreak {
2820    /// The same rule used by other languages.
2821    Auto,
2822    /// The least restrictive rule, good for short lines.
2823    Loose,
2824    /// The most common rule.
2825    Normal,
2826    /// The most stringent rule.
2827    Strict,
2828    /// Allow line breaks in between any character including punctuation.
2829    Anywhere,
2830}
2831impl Default for LineBreak {
2832    /// [`LineBreak::Auto`]
2833    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/// Hyphenation mode.
2853#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
2854pub enum Hyphens {
2855    /// Hyphens are never inserted in word breaks.
2856    None,
2857    /// Word breaks only happen in specially marked break characters: `-` and `\u{00AD} SHY`.
2858    ///
2859    /// * `U+2010` - The visible hyphen character.
2860    /// * `U+00AD` - The invisible hyphen character, is made visible in a word break.
2861    Manual,
2862    /// Hyphens are inserted like `Manual` and also using language specific hyphenation rules.
2863    Auto,
2864}
2865impl Default for Hyphens {
2866    /// [`Hyphens::Auto`]
2867    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/// Configure line breaks inside words during text wrap.
2885///
2886/// This value is only considered if it is impossible to fit a full word to a line.
2887///
2888/// Hyphens can be inserted in word breaks using the [`Hyphens`] configuration.
2889#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
2890pub enum WordBreak {
2891    /// Line breaks can be inserted in between letters of Chinese/Japanese/Korean text only.
2892    Normal,
2893    /// Line breaks can be inserted between any letter.
2894    BreakAll,
2895    /// Line breaks are not inserted between any letter.
2896    KeepAll,
2897}
2898impl Default for WordBreak {
2899    /// [`WordBreak::Normal`]
2900    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/// Text alignment justification mode.
2918#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
2919pub enum Justify {
2920    /// Selects the justification mode based on the language.
2921    ///
2922    /// For Chinese/Japanese/Korean uses `InterLetter` for the others uses `InterWord`.
2923    Auto,
2924    /// The text is justified by adding space between words.
2925    InterWord,
2926    /// The text is justified by adding space between letters.
2927    InterLetter,
2928}
2929impl Default for Justify {
2930    /// [`Justify::Auto`]
2931    fn default() -> Self {
2932        Justify::Auto
2933    }
2934}
2935impl Justify {
2936    /// Resolve `Auto` for the given language.
2937    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/// Various metrics that apply to the entire [`FontFace`].
2961///
2962/// For OpenType fonts, these mostly come from the `OS/2` table.
2963///
2964/// See the [`FreeType Glyph Metrics`] documentation for an explanation of the various metrics.
2965///
2966/// [`FreeType Glyph Metrics`]: https://freetype.org/freetype2/docs/glyphs/glyphs-3.html
2967#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
2968#[non_exhaustive]
2969pub struct FontFaceMetrics {
2970    /// The number of font units per em.
2971    ///
2972    /// Font sizes are usually expressed in pixels per em; e.g. `12px` means 12 pixels per em.
2973    pub units_per_em: u32,
2974
2975    /// The maximum amount the font rises above the baseline, in font units.
2976    pub ascent: f32,
2977
2978    /// The maximum amount the font descends below the baseline, in font units.
2979    ///
2980    /// This is typically a negative value to match the definition of `sTypoDescender` in the
2981    /// `OS/2` table in the OpenType specification. If you are used to using Windows or Mac APIs,
2982    /// beware, as the sign is reversed from what those APIs return.
2983    pub descent: f32,
2984
2985    /// Distance between baselines, in font units.
2986    pub line_gap: f32,
2987
2988    /// The suggested distance of the top of the underline from the baseline (negative values
2989    /// indicate below baseline), in font units.
2990    pub underline_position: f32,
2991
2992    /// A suggested value for the underline thickness, in font units.
2993    pub underline_thickness: f32,
2994
2995    /// The approximate amount that uppercase letters rise above the baseline, in font units.
2996    pub cap_height: f32,
2997
2998    /// The approximate amount that non-ascending lowercase letters rise above the baseline, in
2999    /// font units.
3000    pub x_height: f32,
3001
3002    /// A rectangle that surrounds all bounding boxes of all glyphs, in font units.
3003    ///
3004    /// This corresponds to the `xMin`/`xMax`/`yMin`/`yMax` values in the OpenType `head` table.
3005    pub bounds: euclid::Rect<f32, ()>,
3006}
3007impl FontFaceMetrics {
3008    /// Compute [`FontMetrics`] given a font size in pixels.
3009    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/// Various metrics about a [`Font`].
3033///
3034/// You can compute these metrics from a [`FontFaceMetrics`]
3035#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
3036#[non_exhaustive]
3037pub struct FontMetrics {
3038    /// Multiply this to a font EM value to get the size in pixels.
3039    pub size_scale: f32,
3040
3041    /// The maximum amount the font rises above the baseline, in pixels.
3042    pub ascent: Px,
3043
3044    /// The maximum amount the font descends below the baseline, in pixels.
3045    ///
3046    /// This is typically a negative value to match the definition of `sTypoDescender` in the
3047    /// `OS/2` table in the OpenType specification. If you are used to using Windows or Mac APIs,
3048    /// beware, as the sign is reversed from what those APIs return.
3049    pub descent: Px,
3050
3051    /// Distance between baselines, in pixels.
3052    pub line_gap: Px,
3053
3054    /// The suggested distance of the top of the underline from the baseline (negative values
3055    /// indicate below baseline), in pixels.
3056    pub underline_position: Px,
3057
3058    /// A suggested value for the underline thickness, in pixels.
3059    pub underline_thickness: Px,
3060
3061    /// The approximate amount that uppercase letters rise above the baseline, in pixels.
3062    pub cap_height: Px,
3063
3064    /// The approximate amount that non-ascending lowercase letters rise above the baseline, in pixels.
3065    pub x_height: Px,
3066
3067    /// A rectangle that surrounds all bounding boxes of all glyphs, in pixels.
3068    ///
3069    /// This corresponds to the `xMin`/`xMax`/`yMin`/`yMax` values in the OpenType `head` table.
3070    pub bounds: PxRect,
3071}
3072impl FontMetrics {
3073    /// The font line height.
3074    pub fn line_height(&self) -> Px {
3075        self.ascent - self.descent + self.line_gap
3076    }
3077}
3078
3079/// Text transform function.
3080#[derive(Clone)]
3081pub enum TextTransformFn {
3082    /// No transform.
3083    None,
3084    /// To UPPERCASE.
3085    Uppercase,
3086    /// to lowercase.
3087    Lowercase,
3088    /// Custom transform function.
3089    Custom(Arc<dyn Fn(&Txt) -> Cow<Txt> + Send + Sync>),
3090}
3091impl TextTransformFn {
3092    /// Apply the text transform.
3093    ///
3094    /// Returns [`Cow::Owned`] if the text was changed.
3095    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    /// New [`Custom`](Self::Custom).
3117    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/// Text white space transform.
3144#[derive(Copy, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
3145pub enum WhiteSpace {
3146    /// Text is not changed, all white spaces and line breaks are preserved.
3147    Preserve,
3148    /// Replace white spaces with a single `U+0020 SPACE` and trim lines. Line breaks are preserved.
3149    Merge,
3150    /// Replace white spaces and line breaks with `U+0020 SPACE` and trim the text.
3151    MergeAll,
3152}
3153impl Default for WhiteSpace {
3154    /// [`WhiteSpace::Preserve`].
3155    fn default() -> Self {
3156        WhiteSpace::Preserve
3157    }
3158}
3159impl WhiteSpace {
3160    /// Transform the white space of the text.
3161    ///
3162    /// Returns [`Cow::Owned`] if the text was changed.
3163    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                            // collapse spaces or replace non ' ' white space with ' '.
3175
3176                            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                            // collapse spaces or replace non ' ' white space with ' '.
3206
3207                            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/// Defines an insert offset in a shaped text.
3246#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
3247pub struct CaretIndex {
3248    /// Char byte offset in the full text.
3249    ///
3250    /// This index can be computed using the [`SegmentedText`].
3251    pub index: usize,
3252    /// Line index in the shaped text.
3253    ///
3254    /// This value is only used to disambiguate between the *end* of a wrap and
3255    /// the *start* of the next, the text itself does not have any line
3256    /// break but visually the user interacts with two lines. Note that this
3257    /// counts wrap lines, and that this value is not required to define a valid
3258    /// CaretIndex.
3259    ///
3260    /// This index can be computed using the [`ShapedText::snap_caret_line`].
3261    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    /// First position.
3272    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/// Reasons why a loader might fail to load a font.
3286#[derive(Debug, Clone)]
3287#[non_exhaustive]
3288pub enum FontLoadingError {
3289    /// The data was of a format the loader didn't recognize.
3290    UnknownFormat,
3291    /// Attempted to load an invalid index in a TrueType or OpenType font collection.
3292    ///
3293    /// For example, if a `.ttc` file has 2 fonts in it, and you ask for the 5th one, you'll get
3294    /// this error.
3295    NoSuchFontInCollection,
3296    /// Attempted to load a malformed or corrupted font.
3297    Parse(ttf_parser::FaceParsingError),
3298    /// Attempted to load a font from the filesystem, but there is no filesystem (e.g. in
3299    /// WebAssembly).
3300    NoFilesystem,
3301    /// A disk or similar I/O error occurred while attempting to load the font.
3302    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}