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_EPSILON, EQ_EPSILON_100, Factor, FactorPercent, Px, PxPoint, PxRect, PxSize, TimeUnits as _, about_eq, about_eq_hash, about_eq_ord,
62    euclid,
63};
64use zng_task as task;
65use zng_txt::Txt;
66use zng_var::{
67    AnyVar, ArcVar, IntoVar, LocalVar, ResponderVar, ResponseVar, Var, animation::Transitionable, impl_from_and_into_var,
68    response_done_var, response_var, var,
69};
70use zng_view_api::{ViewProcessOffline, config::FontAntiAliasing};
71
72/// 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    type Var = LocalVar<FontNames>;
521
522    fn into_var(self) -> Self::Var {
523        LocalVar(self.into())
524    }
525}
526impl<const N: usize> From<[&'static str; N]> for FontNames {
527    fn from(font_names: [&'static str; N]) -> Self {
528        FontNames(font_names.into_iter().map(FontName::new).collect())
529    }
530}
531impl<const N: usize> IntoVar<FontNames> for [&'static str; N] {
532    type Var = LocalVar<FontNames>;
533
534    fn into_var(self) -> Self::Var {
535        LocalVar(self.into())
536    }
537}
538impl<const N: usize> From<[String; N]> for FontNames {
539    fn from(font_names: [String; N]) -> Self {
540        FontNames(font_names.into_iter().map(FontName::new).collect())
541    }
542}
543impl<const N: usize> IntoVar<FontNames> for [String; N] {
544    type Var = LocalVar<FontNames>;
545
546    fn into_var(self) -> Self::Var {
547        LocalVar(self.into())
548    }
549}
550impl<const N: usize> From<[Txt; N]> for FontNames {
551    fn from(font_names: [Txt; N]) -> Self {
552        FontNames(font_names.into_iter().map(FontName::new).collect())
553    }
554}
555impl<const N: usize> IntoVar<FontNames> for [Txt; N] {
556    type Var = LocalVar<FontNames>;
557
558    fn into_var(self) -> Self::Var {
559        LocalVar(self.into())
560    }
561}
562
563event! {
564    /// Change in [`FONTS`] that may cause a font query to now give
565    /// a different result.
566    ///
567    /// # Cache
568    ///
569    /// Every time this event updates the font cache is cleared. Meaning that even
570    /// if the query returns the same font it will be a new reference.
571    ///
572    /// Fonts only unload when all references to then are dropped, so you can still continue using
573    /// old references if you don't want to monitor this event.
574    pub static FONT_CHANGED_EVENT: FontChangedArgs;
575}
576
577event_args! {
578    /// [`FONT_CHANGED_EVENT`] arguments.
579    pub struct FontChangedArgs {
580        /// The change that happened.
581        pub change: FontChange,
582
583        ..
584
585        /// Broadcast to all widgets.
586        fn delivery_list(&self, list: &mut UpdateDeliveryList) {
587            list.search_all()
588        }
589    }
590}
591
592/// Possible changes in a [`FontChangedArgs`].
593#[derive(Clone, Debug)]
594pub enum FontChange {
595    /// OS fonts change.
596    ///
597    /// Currently this is only supported in Microsoft Windows.
598    SystemFonts,
599
600    /// Custom fonts change caused by call to [`FONTS.register`] or [`FONTS.unregister`].
601    ///
602    /// [`FONTS.register`]: FONTS::register
603    /// [`FONTS.unregister`]: FONTS::unregister
604    CustomFonts,
605
606    /// Custom request caused by call to [`FONTS.refresh`].
607    ///
608    /// [`FONTS.refresh`]: FONTS::refresh
609    Refresh,
610
611    /// One of the [`GenericFonts`] was set for the language.
612    ///
613    /// The font name is one of [`FontName`] generic names.
614    ///
615    /// [`GenericFonts`]: struct@GenericFonts
616    GenericFont(FontName, Lang),
617
618    /// A new [fallback](GenericFonts::fallback) font was set for the language.
619    Fallback(Lang),
620}
621
622/// Application extension that manages text fonts.
623///
624/// Services this extension provides:
625///
626/// * [`FONTS`] - Service that finds and loads fonts.
627/// * [`HYPHENATION`] - Service that loads and applies hyphenation dictionaries.
628///
629/// Events this extension provides:
630///
631/// * [`FONT_CHANGED_EVENT`] - Font config or system fonts changed.
632#[derive(Default)]
633pub struct FontManager {}
634impl AppExtension for FontManager {
635    fn event_preview(&mut self, update: &mut EventUpdate) {
636        if RAW_FONT_CHANGED_EVENT.has(update) {
637            FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::SystemFonts));
638        } else if let Some(args) = RAW_FONT_AA_CHANGED_EVENT.on(update) {
639            FONTS_SV.read().font_aa.set(args.aa);
640        } else if FONT_CHANGED_EVENT.has(update) {
641            FONTS_SV.write().on_fonts_changed();
642        } else if let Some(args) = VIEW_PROCESS_INITED_EVENT.on(update) {
643            let mut fonts = FONTS_SV.write();
644            fonts.font_aa.set(args.font_aa);
645            if args.is_respawn {
646                fonts.loader.on_view_process_respawn();
647            }
648        }
649    }
650
651    fn update(&mut self) {
652        let mut fonts = FONTS_SV.write();
653
654        {
655            let mut f = GENERIC_FONTS_SV.write();
656            for request in std::mem::take(&mut f.requests) {
657                request(&mut f);
658            }
659        }
660
661        let mut changed = false;
662        for (request, responder) in std::mem::take(&mut fonts.loader.unregister_requests) {
663            let r = if let Some(removed) = fonts.loader.custom_fonts.remove(&request) {
664                // cut circular reference so that when the last font ref gets dropped
665                // this font face also gets dropped. Also tag the font as unregistered
666                // so it does not create further circular references.
667                for removed in removed {
668                    removed.on_refresh();
669                }
670
671                changed = true;
672
673                true
674            } else {
675                false
676            };
677            responder.respond(r);
678        }
679        if changed {
680            FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::CustomFonts));
681        }
682
683        if fonts.prune_requested {
684            fonts.on_prune();
685        }
686    }
687}
688
689app_local! {
690    static FONTS_SV: FontsService = FontsService {
691        loader: FontFaceLoader::new(),
692        prune_requested: false,
693        font_aa: var(FontAntiAliasing::Default),
694    };
695}
696
697struct FontsService {
698    loader: FontFaceLoader,
699    prune_requested: bool,
700    font_aa: ArcVar<FontAntiAliasing>,
701}
702impl FontsService {
703    fn on_fonts_changed(&mut self) {
704        self.loader.on_refresh();
705        self.prune_requested = false;
706    }
707
708    fn on_prune(&mut self) {
709        self.loader.on_prune();
710        self.prune_requested = false;
711    }
712}
713
714/// Font loading, custom fonts and app font configuration.
715pub struct FONTS;
716impl FONTS {
717    /// Clear cache and notify `Refresh` in [`FONT_CHANGED_EVENT`].
718    ///
719    /// See the event documentation for more information.
720    pub fn refresh(&self) {
721        FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::Refresh));
722    }
723
724    /// Remove all unused fonts from cache.
725    pub fn prune(&self) {
726        let mut ft = FONTS_SV.write();
727        if !ft.prune_requested {
728            ft.prune_requested = true;
729            UPDATES.update(None);
730        }
731    }
732
733    /// Actual name of generic fonts.
734    pub fn generics(&self) -> &'static GenericFonts {
735        &GenericFonts {}
736    }
737
738    /// Load and register a custom font.
739    ///
740    /// If the font loads correctly a [`FONT_CHANGED_EVENT`] notification is scheduled.
741    /// Fonts sourced from a file are not monitored for changes, you can *reload* the font
742    /// by calling `register` again with the same font name.
743    ///
744    /// The returned response will update once when the font finishes loading with the new font.
745    /// At minimum the new font will be available on the next update.
746    pub fn register(&self, custom_font: CustomFont) -> ResponseVar<Result<FontFace, FontLoadingError>> {
747        FontFaceLoader::register(custom_font)
748    }
749
750    /// Removes a custom font family. If the font faces are not in use it is also unloaded.
751    ///
752    /// Returns a response var that updates once with a value that indicates if any custom font was removed.
753    pub fn unregister(&self, custom_family: FontName) -> ResponseVar<bool> {
754        FONTS_SV.write().loader.unregister(custom_family)
755    }
756
757    /// Gets a font list that best matches the query.
758    pub fn list(
759        &self,
760        families: &[FontName],
761        style: FontStyle,
762        weight: FontWeight,
763        stretch: FontStretch,
764        lang: &Lang,
765    ) -> ResponseVar<FontFaceList> {
766        // try with shared lock
767        if let Some(cached) = FONTS_SV.read().loader.try_list(families, style, weight, stretch, lang) {
768            return cached;
769        }
770        // begin load with exclusive lock (cache is tried again in `load`)
771        FONTS_SV.write().loader.load_list(families, style, weight, stretch, lang)
772    }
773
774    /// Find a single font face that best matches the query.
775    pub fn find(
776        &self,
777        family: &FontName,
778        style: FontStyle,
779        weight: FontWeight,
780        stretch: FontStretch,
781        lang: &Lang,
782    ) -> ResponseVar<Option<FontFace>> {
783        // try with shared lock
784        if let Some(cached) = FONTS_SV.read().loader.try_cached(family, style, weight, stretch, lang) {
785            return cached;
786        }
787        // begin load with exclusive lock (cache is tried again in `load`)
788        FONTS_SV.write().loader.load(family, style, weight, stretch, lang)
789    }
790
791    /// Find a single font face with all normal properties.
792    pub fn normal(&self, family: &FontName, lang: &Lang) -> ResponseVar<Option<FontFace>> {
793        self.find(family, FontStyle::Normal, FontWeight::NORMAL, FontStretch::NORMAL, lang)
794    }
795
796    /// Find a single font face with italic style, normal weight and stretch.
797    pub fn italic(&self, family: &FontName, lang: &Lang) -> ResponseVar<Option<FontFace>> {
798        self.find(family, FontStyle::Italic, FontWeight::NORMAL, FontStretch::NORMAL, lang)
799    }
800
801    /// Find a single font face with bold weight, normal style and stretch.
802    pub fn bold(&self, family: &FontName, lang: &Lang) -> ResponseVar<Option<FontFace>> {
803        self.find(family, FontStyle::Normal, FontWeight::BOLD, FontStretch::NORMAL, lang)
804    }
805
806    /// Gets all [registered](Self::register) font families.
807    pub fn custom_fonts(&self) -> Vec<FontName> {
808        FONTS_SV.read().loader.custom_fonts.keys().cloned().collect()
809    }
810
811    /// Query all font families available in the system.
812    ///
813    /// Note that the variable will only update once with the query result, this is not a live view.
814    pub fn system_fonts(&self) -> ResponseVar<Vec<FontName>> {
815        query_util::system_all()
816    }
817
818    /// Gets the system font anti-aliasing config as a read-only var.
819    ///
820    /// The variable updates when the system config changes.
821    pub fn system_font_aa(&self) -> impl Var<FontAntiAliasing> {
822        FONTS_SV.read().font_aa.read_only()
823    }
824}
825
826impl<'a> From<ttf_parser::Face<'a>> for FontFaceMetrics {
827    fn from(f: ttf_parser::Face<'a>) -> Self {
828        let underline = f
829            .underline_metrics()
830            .unwrap_or(ttf_parser::LineMetrics { position: 0, thickness: 0 });
831        FontFaceMetrics {
832            units_per_em: f.units_per_em() as _,
833            ascent: f.ascender() as f32,
834            descent: f.descender() as f32,
835            line_gap: f.line_gap() as f32,
836            underline_position: underline.position as f32,
837            underline_thickness: underline.thickness as f32,
838            cap_height: f.capital_height().unwrap_or(0) as f32,
839            x_height: f.x_height().unwrap_or(0) as f32,
840            bounds: euclid::rect(
841                f.global_bounding_box().x_min as f32,
842                f.global_bounding_box().x_max as f32,
843                f.global_bounding_box().width() as f32,
844                f.global_bounding_box().height() as f32,
845            ),
846        }
847    }
848}
849
850#[derive(PartialEq, Eq, Hash)]
851struct FontInstanceKey(Px, Box<[(ttf_parser::Tag, i32)]>);
852impl FontInstanceKey {
853    /// Returns the key.
854    pub fn new(size: Px, variations: &[rustybuzz::Variation]) -> Self {
855        let variations_key: Vec<_> = variations.iter().map(|p| (p.tag, (p.value * 1000.0) as i32)).collect();
856        FontInstanceKey(size, variations_key.into_boxed_slice())
857    }
858}
859
860/// A font face selected from a font family.
861///
862/// Usually this is part of a [`FontList`] that can be requested from
863/// the [`FONTS`] service.
864///
865/// This type is a shared reference to the font data, cloning it is cheap.
866#[derive(Clone)]
867pub struct FontFace(Arc<LoadedFontFace>);
868struct LoadedFontFace {
869    data: FontDataRef,
870    face_index: u32,
871    display_name: FontName,
872    family_name: FontName,
873    postscript_name: Option<Txt>,
874    style: FontStyle,
875    weight: FontWeight,
876    stretch: FontStretch,
877    metrics: FontFaceMetrics,
878    color_palettes: ColorPalettes,
879    color_glyphs: ColorGlyphs,
880    lig_carets: LigatureCaretList,
881    flags: FontFaceFlags,
882    m: Mutex<FontFaceMut>,
883}
884bitflags! {
885    #[derive(Debug, Clone, Copy)]
886    struct FontFaceFlags: u8 {
887        const IS_MONOSPACE =      0b0000_0001;
888        const HAS_LIGATURES =     0b0000_0010;
889        const HAS_RASTER_IMAGES = 0b0000_0100;
890        const HAS_SVG_IMAGES =    0b0000_1000;
891    }
892}
893struct FontFaceMut {
894    instances: HashMap<FontInstanceKey, Font>,
895    render_ids: Vec<RenderFontFace>,
896    unregistered: bool,
897}
898
899impl fmt::Debug for FontFace {
900    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
901        let m = self.0.m.lock();
902        f.debug_struct("FontFace")
903            .field("display_name", &self.0.display_name)
904            .field("family_name", &self.0.family_name)
905            .field("postscript_name", &self.0.postscript_name)
906            .field("flags", &self.0.flags)
907            .field("style", &self.0.style)
908            .field("weight", &self.0.weight)
909            .field("stretch", &self.0.stretch)
910            .field("metrics", &self.0.metrics)
911            .field("color_palettes.len()", &self.0.color_palettes.len())
912            .field("color_glyphs.len()", &self.0.color_glyphs.len())
913            .field("instances.len()", &m.instances.len())
914            .field("render_keys.len()", &m.render_ids.len())
915            .field("unregistered", &m.unregistered)
916            .finish_non_exhaustive()
917    }
918}
919impl PartialEq for FontFace {
920    fn eq(&self, other: &Self) -> bool {
921        Arc::ptr_eq(&self.0, &other.0)
922    }
923}
924impl Eq for FontFace {}
925impl FontFace {
926    /// New empty font face.
927    pub fn empty() -> Self {
928        FontFace(Arc::new(LoadedFontFace {
929            data: FontDataRef::from_static(&[]),
930            face_index: 0,
931            display_name: FontName::from("<empty>"),
932            family_name: FontName::from("<empty>"),
933            postscript_name: None,
934            flags: FontFaceFlags::IS_MONOSPACE,
935            style: FontStyle::Normal,
936            weight: FontWeight::NORMAL,
937            stretch: FontStretch::NORMAL,
938            // values copied from a monospace font
939            metrics: FontFaceMetrics {
940                units_per_em: 2048,
941                ascent: 1616.0,
942                descent: -432.0,
943                line_gap: 0.0,
944                underline_position: -205.0,
945                underline_thickness: 102.0,
946                cap_height: 1616.0,
947                x_height: 1616.0,
948                // `xMin`/`xMax`/`yMin`/`yMax`
949                bounds: euclid::Box2D::new(euclid::point2(0.0, -432.0), euclid::point2(1291.0, 1616.0)).to_rect(),
950            },
951            color_palettes: ColorPalettes::empty(),
952            color_glyphs: ColorGlyphs::empty(),
953            lig_carets: LigatureCaretList::empty(),
954            m: Mutex::new(FontFaceMut {
955                instances: HashMap::default(),
956                render_ids: vec![],
957                unregistered: false,
958            }),
959        }))
960    }
961
962    /// Is empty font face.
963    pub fn is_empty(&self) -> bool {
964        self.0.data.is_empty()
965    }
966
967    async fn load_custom(custom_font: CustomFont) -> Result<Self, FontLoadingError> {
968        let bytes;
969        let mut face_index;
970
971        match custom_font.source {
972            FontSource::File(path, index) => {
973                bytes = FontDataRef(Arc::new(task::wait(|| std::fs::read(path)).await?));
974                face_index = index;
975            }
976            FontSource::Memory(arc, index) => {
977                bytes = arc;
978                face_index = index;
979            }
980            FontSource::Alias(other_font) => {
981                let result = FONTS_SV
982                    .write()
983                    .loader
984                    .load_resolved(&other_font, custom_font.style, custom_font.weight, custom_font.stretch);
985                return match result.wait_into_rsp().await {
986                    Some(other_font) => Ok(FontFace(Arc::new(LoadedFontFace {
987                        data: other_font.0.data.clone(),
988                        face_index: other_font.0.face_index,
989                        display_name: custom_font.name.clone(),
990                        family_name: custom_font.name,
991                        postscript_name: None,
992                        style: other_font.0.style,
993                        weight: other_font.0.weight,
994                        stretch: other_font.0.stretch,
995                        metrics: other_font.0.metrics.clone(),
996                        m: Mutex::new(FontFaceMut {
997                            instances: Default::default(),
998                            render_ids: Default::default(),
999                            unregistered: Default::default(),
1000                        }),
1001                        color_palettes: other_font.0.color_palettes.clone(),
1002                        color_glyphs: other_font.0.color_glyphs.clone(),
1003                        lig_carets: other_font.0.lig_carets.clone(),
1004                        flags: other_font.0.flags,
1005                    }))),
1006                    None => Err(FontLoadingError::NoSuchFontInCollection),
1007                };
1008            }
1009        }
1010
1011        let ttf_face = match ttf_parser::Face::parse(&bytes, face_index) {
1012            Ok(f) => f,
1013            Err(e) => {
1014                match e {
1015                    // try again with font 0 (font-kit selects a high index for Ubuntu Font)
1016                    ttf_parser::FaceParsingError::FaceIndexOutOfBounds => face_index = 0,
1017                    e => return Err(FontLoadingError::Parse(e)),
1018                }
1019
1020                match ttf_parser::Face::parse(&bytes, face_index) {
1021                    Ok(f) => f,
1022                    Err(_) => return Err(FontLoadingError::Parse(e)),
1023                }
1024            }
1025        };
1026
1027        let color_palettes = ColorPalettes::load(ttf_face.raw_face())?;
1028        let color_glyphs = if color_palettes.is_empty() {
1029            ColorGlyphs::empty()
1030        } else {
1031            ColorGlyphs::load(ttf_face.raw_face())?
1032        };
1033        let has_ligatures = ttf_face.tables().gsub.is_some();
1034        let lig_carets = if has_ligatures {
1035            LigatureCaretList::empty()
1036        } else {
1037            LigatureCaretList::load(ttf_face.raw_face())?
1038        };
1039
1040        // all tables used by `ttf_parser::Face::glyph_raster_image`
1041        let has_raster_images = {
1042            let t = ttf_face.tables();
1043            t.sbix.is_some() || t.bdat.is_some() || t.ebdt.is_some() || t.cbdt.is_some()
1044        };
1045
1046        let mut flags = FontFaceFlags::empty();
1047        flags.set(FontFaceFlags::IS_MONOSPACE, ttf_face.is_monospaced());
1048        flags.set(FontFaceFlags::HAS_LIGATURES, has_ligatures);
1049        flags.set(FontFaceFlags::HAS_RASTER_IMAGES, has_raster_images);
1050        flags.set(FontFaceFlags::HAS_SVG_IMAGES, ttf_face.tables().svg.is_some());
1051
1052        Ok(FontFace(Arc::new(LoadedFontFace {
1053            face_index,
1054            display_name: custom_font.name.clone(),
1055            family_name: custom_font.name,
1056            postscript_name: None,
1057            style: custom_font.style,
1058            weight: custom_font.weight,
1059            stretch: custom_font.stretch,
1060            metrics: ttf_face.into(),
1061            color_palettes,
1062            color_glyphs,
1063            lig_carets,
1064            m: Mutex::new(FontFaceMut {
1065                instances: Default::default(),
1066                render_ids: Default::default(),
1067                unregistered: Default::default(),
1068            }),
1069            data: bytes,
1070            flags,
1071        })))
1072    }
1073
1074    fn load(bytes: FontDataRef, mut face_index: u32) -> Result<Self, FontLoadingError> {
1075        let _span = tracing::trace_span!("FontFace::load").entered();
1076
1077        let ttf_face = match ttf_parser::Face::parse(&bytes, face_index) {
1078            Ok(f) => f,
1079            Err(e) => {
1080                match e {
1081                    // try again with font 0 (font-kit selects a high index for Ubuntu Font)
1082                    ttf_parser::FaceParsingError::FaceIndexOutOfBounds => face_index = 0,
1083                    e => return Err(FontLoadingError::Parse(e)),
1084                }
1085
1086                match ttf_parser::Face::parse(&bytes, face_index) {
1087                    Ok(f) => f,
1088                    Err(_) => return Err(FontLoadingError::Parse(e)),
1089                }
1090            }
1091        };
1092
1093        let color_palettes = ColorPalettes::load(ttf_face.raw_face())?;
1094        let color_glyphs = if color_palettes.is_empty() {
1095            ColorGlyphs::empty()
1096        } else {
1097            ColorGlyphs::load(ttf_face.raw_face())?
1098        };
1099
1100        let has_ligatures = ttf_face.tables().gsub.is_some();
1101        let lig_carets = if has_ligatures {
1102            LigatureCaretList::empty()
1103        } else {
1104            LigatureCaretList::load(ttf_face.raw_face())?
1105        };
1106
1107        let mut display_name = None;
1108        let mut family_name = None;
1109        let mut postscript_name = None;
1110        let mut any_name = None::<String>;
1111        for name in ttf_face.names() {
1112            if let Some(n) = name.to_string() {
1113                match name.name_id {
1114                    ttf_parser::name_id::FULL_NAME => display_name = Some(n),
1115                    ttf_parser::name_id::FAMILY => family_name = Some(n),
1116                    ttf_parser::name_id::POST_SCRIPT_NAME => postscript_name = Some(n),
1117                    _ => match &mut any_name {
1118                        Some(s) => {
1119                            if n.len() > s.len() {
1120                                *s = n;
1121                            }
1122                        }
1123                        None => any_name = Some(n),
1124                    },
1125                }
1126            }
1127        }
1128        let display_name = FontName::new(Txt::from_str(
1129            display_name
1130                .as_ref()
1131                .or(family_name.as_ref())
1132                .or(postscript_name.as_ref())
1133                .or(any_name.as_ref())
1134                .unwrap(),
1135        ));
1136        let family_name = family_name.map(FontName::from).unwrap_or_else(|| display_name.clone());
1137        let postscript_name = postscript_name.map(Txt::from);
1138
1139        if ttf_face.units_per_em() == 0 {
1140            // observed this in Noto Color Emoji (with font_kit)
1141            tracing::debug!("font {display_name:?} units_per_em 0");
1142            return Err(FontLoadingError::UnknownFormat);
1143        }
1144
1145        // all tables used by `ttf_parser::Face::glyph_raster_image`
1146        let has_raster_images = {
1147            let t = ttf_face.tables();
1148            t.sbix.is_some() || t.bdat.is_some() || t.ebdt.is_some() || t.cbdt.is_some()
1149        };
1150
1151        let mut flags = FontFaceFlags::empty();
1152        flags.set(FontFaceFlags::IS_MONOSPACE, ttf_face.is_monospaced());
1153        flags.set(FontFaceFlags::HAS_LIGATURES, has_ligatures);
1154        flags.set(FontFaceFlags::HAS_RASTER_IMAGES, has_raster_images);
1155        flags.set(FontFaceFlags::HAS_SVG_IMAGES, ttf_face.tables().svg.is_some());
1156
1157        Ok(FontFace(Arc::new(LoadedFontFace {
1158            face_index,
1159            family_name,
1160            display_name,
1161            postscript_name,
1162            style: ttf_face.style().into(),
1163            weight: ttf_face.weight().into(),
1164            stretch: ttf_face.width().into(),
1165            metrics: ttf_face.into(),
1166            color_palettes,
1167            color_glyphs,
1168            lig_carets,
1169            m: Mutex::new(FontFaceMut {
1170                instances: Default::default(),
1171                render_ids: Default::default(),
1172                unregistered: Default::default(),
1173            }),
1174            data: bytes,
1175            flags,
1176        })))
1177    }
1178
1179    fn on_refresh(&self) {
1180        let mut m = self.0.m.lock();
1181        m.instances.clear();
1182        m.unregistered = true;
1183    }
1184
1185    fn render_face(&self, renderer: &ViewRenderer) -> zng_view_api::font::FontFaceId {
1186        let mut m = self.0.m.lock();
1187        for r in m.render_ids.iter() {
1188            if &r.renderer == renderer {
1189                return r.face_id;
1190            }
1191        }
1192
1193        let key = match renderer.add_font_face((*self.0.data.0).clone(), self.0.face_index) {
1194            Ok(k) => k,
1195            Err(ViewProcessOffline) => {
1196                tracing::debug!("respawned calling `add_font`, will return dummy font key");
1197                return zng_view_api::font::FontFaceId::INVALID;
1198            }
1199        };
1200
1201        m.render_ids.push(RenderFontFace::new(renderer, key));
1202
1203        key
1204    }
1205
1206    /// Loads the harfbuzz face.
1207    ///
1208    /// Loads from in memory [`bytes`].
1209    ///
1210    /// Returns `None` if [`is_empty`].
1211    ///
1212    /// [`is_empty`]: Self::is_empty
1213    /// [`bytes`]: Self::bytes
1214    pub fn harfbuzz(&self) -> Option<rustybuzz::Face> {
1215        if self.is_empty() {
1216            None
1217        } else {
1218            Some(rustybuzz::Face::from_slice(&self.0.data.0, self.0.face_index).unwrap())
1219        }
1220    }
1221
1222    /// Loads the full TTF face.
1223    ///
1224    /// Loads from in memory [`bytes`].
1225    ///
1226    /// Returns `None` if [`is_empty`].
1227    ///
1228    /// [`is_empty`]: Self::is_empty
1229    /// [`bytes`]: Self::bytes
1230    pub fn ttf(&self) -> Option<ttf_parser::Face> {
1231        if self.is_empty() {
1232            None
1233        } else {
1234            Some(ttf_parser::Face::parse(&self.0.data.0, self.0.face_index).unwrap())
1235        }
1236    }
1237
1238    /// Reference the font file bytes.
1239    pub fn bytes(&self) -> &FontDataRef {
1240        &self.0.data
1241    }
1242    /// Index of the font face in the [font file](Self::bytes).
1243    pub fn index(&self) -> u32 {
1244        self.0.face_index
1245    }
1246
1247    /// Font full name.
1248    pub fn display_name(&self) -> &FontName {
1249        &self.0.display_name
1250    }
1251
1252    /// Font family name.
1253    pub fn family_name(&self) -> &FontName {
1254        &self.0.family_name
1255    }
1256
1257    /// Font globally unique name.
1258    pub fn postscript_name(&self) -> Option<&str> {
1259        self.0.postscript_name.as_deref()
1260    }
1261
1262    /// Font style.
1263    pub fn style(&self) -> FontStyle {
1264        self.0.style
1265    }
1266
1267    /// Font weight.
1268    pub fn weight(&self) -> FontWeight {
1269        self.0.weight
1270    }
1271
1272    /// Font stretch.
1273    pub fn stretch(&self) -> FontStretch {
1274        self.0.stretch
1275    }
1276
1277    /// Font is monospace (fixed-width).
1278    pub fn is_monospace(&self) -> bool {
1279        self.0.flags.contains(FontFaceFlags::IS_MONOSPACE)
1280    }
1281
1282    /// Font metrics in font units.
1283    pub fn metrics(&self) -> &FontFaceMetrics {
1284        &self.0.metrics
1285    }
1286
1287    /// Gets a cached sized [`Font`].
1288    ///
1289    /// The `font_size` is the size of `1 font EM` in pixels.
1290    ///
1291    /// The `variations` are custom [font variations] that will be used
1292    /// during shaping and rendering.
1293    ///
1294    /// [font variations]: crate::font_features::FontVariations::finalize
1295    pub fn sized(&self, font_size: Px, variations: RFontVariations) -> Font {
1296        let key = FontInstanceKey::new(font_size, &variations);
1297        let mut m = self.0.m.lock();
1298        if !m.unregistered {
1299            m.instances
1300                .entry(key)
1301                .or_insert_with(|| Font::new(self.clone(), font_size, variations))
1302                .clone()
1303        } else {
1304            tracing::debug!(target: "font_loading", "creating font from unregistered `{}`, will not cache", self.0.display_name);
1305            Font::new(self.clone(), font_size, variations)
1306        }
1307    }
1308
1309    /// Gets what font synthesis to use to better render this font face given the style and weight.
1310    pub fn synthesis_for(&self, style: FontStyle, weight: FontWeight) -> FontSynthesis {
1311        let mut synth = FontSynthesis::DISABLED;
1312
1313        if style != FontStyle::Normal && self.style() == FontStyle::Normal {
1314            // if requested oblique or italic and the face is neither.
1315            synth |= FontSynthesis::OBLIQUE;
1316        }
1317        if weight > self.weight() {
1318            // if requested a weight larger then the face weight the renderer can
1319            // add extra stroke outlines to compensate.
1320            synth |= FontSynthesis::BOLD;
1321        }
1322
1323        synth
1324    }
1325
1326    /// If this font face is cached. All font faces are cached by default, a font face can be detached from
1327    /// cache when a [`FONT_CHANGED_EVENT`] event happens, in this case the font can still be used normally, but
1328    /// a request for the same font name will return a different reference.
1329    pub fn is_cached(&self) -> bool {
1330        !self.0.m.lock().unregistered
1331    }
1332
1333    /// CPAL table.
1334    ///
1335    /// Is empty if not provided by the font.
1336    pub fn color_palettes(&self) -> &ColorPalettes {
1337        &self.0.color_palettes
1338    }
1339
1340    /// COLR table.
1341    ///
1342    /// Is empty if not provided by the font.
1343    pub fn color_glyphs(&self) -> &ColorGlyphs {
1344        &self.0.color_glyphs
1345    }
1346
1347    /// If the font provides glyph substitutions.
1348    pub fn has_ligatures(&self) -> bool {
1349        self.0.flags.contains(FontFaceFlags::HAS_LIGATURES)
1350    }
1351
1352    /// If this font provides custom positioned carets for some or all ligature glyphs.
1353    ///
1354    /// If `true` the [`Font::ligature_caret_offsets`] method can be used to get the caret offsets, otherwise
1355    /// it always returns empty.
1356    pub fn has_ligature_caret_offsets(&self) -> bool {
1357        !self.0.lig_carets.is_empty()
1358    }
1359
1360    /// If this font has bitmap images associated with some glyphs.
1361    pub fn has_raster_images(&self) -> bool {
1362        self.0.flags.contains(FontFaceFlags::HAS_RASTER_IMAGES)
1363    }
1364
1365    /// If this font has SVG images associated with some glyphs.
1366    pub fn has_svg_images(&self) -> bool {
1367        self.0.flags.contains(FontFaceFlags::HAS_SVG_IMAGES)
1368    }
1369}
1370
1371/// A sized font face.
1372///
1373/// A sized font can be requested from a [`FontFace`].
1374///
1375/// This type is a shared reference to the loaded font data, cloning it is cheap.
1376#[derive(Clone)]
1377pub struct Font(Arc<LoadedFont>);
1378struct LoadedFont {
1379    face: FontFace,
1380    size: Px,
1381    variations: RFontVariations,
1382    metrics: FontMetrics,
1383    render_keys: Mutex<Vec<RenderFont>>,
1384    small_word_cache: RwLock<HashMap<WordCacheKey<[u8; Font::SMALL_WORD_LEN]>, ShapedSegmentData>>,
1385    word_cache: RwLock<HashMap<WordCacheKey<String>, ShapedSegmentData>>,
1386}
1387impl fmt::Debug for Font {
1388    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1389        f.debug_struct("Font")
1390            .field("face", &self.0.face)
1391            .field("size", &self.0.size)
1392            .field("metrics", &self.0.metrics)
1393            .field("render_keys.len()", &self.0.render_keys.lock().len())
1394            .field("small_word_cache.len()", &self.0.small_word_cache.read().len())
1395            .field("word_cache.len()", &self.0.word_cache.read().len())
1396            .finish()
1397    }
1398}
1399impl PartialEq for Font {
1400    fn eq(&self, other: &Self) -> bool {
1401        Arc::ptr_eq(&self.0, &other.0)
1402    }
1403}
1404impl Eq for Font {}
1405impl Font {
1406    const SMALL_WORD_LEN: usize = 8;
1407
1408    fn to_small_word(s: &str) -> Option<[u8; Self::SMALL_WORD_LEN]> {
1409        if s.len() <= Self::SMALL_WORD_LEN {
1410            let mut a = [b'\0'; Self::SMALL_WORD_LEN];
1411            a[..s.len()].copy_from_slice(s.as_bytes());
1412            Some(a)
1413        } else {
1414            None
1415        }
1416    }
1417
1418    fn new(face: FontFace, size: Px, variations: RFontVariations) -> Self {
1419        Font(Arc::new(LoadedFont {
1420            metrics: face.metrics().sized(size),
1421            face,
1422            size,
1423            variations,
1424            render_keys: Mutex::new(vec![]),
1425            small_word_cache: RwLock::default(),
1426            word_cache: RwLock::default(),
1427        }))
1428    }
1429
1430    fn render_font(&self, renderer: &ViewRenderer, synthesis: FontSynthesis) -> zng_view_api::font::FontId {
1431        let _span = tracing::trace_span!("Font::render_font").entered();
1432
1433        let mut render_keys = self.0.render_keys.lock();
1434        for r in render_keys.iter() {
1435            if &r.renderer == renderer && r.synthesis == synthesis {
1436                return r.font_id;
1437            }
1438        }
1439
1440        let font_key = self.0.face.render_face(renderer);
1441
1442        let opt = zng_view_api::font::FontOptions {
1443            synthetic_oblique: synthesis.contains(FontSynthesis::OBLIQUE),
1444            synthetic_bold: synthesis.contains(FontSynthesis::BOLD),
1445            ..Default::default()
1446        };
1447        let variations = self.0.variations.iter().map(|v| (v.tag.to_bytes(), v.value)).collect();
1448
1449        let key = match renderer.add_font(font_key, self.0.size, opt, variations) {
1450            Ok(k) => k,
1451            Err(ViewProcessOffline) => {
1452                tracing::debug!("respawned calling `add_font_instance`, will return dummy font key");
1453                return zng_view_api::font::FontId::INVALID;
1454            }
1455        };
1456
1457        render_keys.push(RenderFont::new(renderer, synthesis, key));
1458
1459        key
1460    }
1461
1462    /// Reference the font face source of this font.
1463    pub fn face(&self) -> &FontFace {
1464        &self.0.face
1465    }
1466
1467    /// Gets the sized harfbuzz font.
1468    pub fn harfbuzz(&self) -> Option<rustybuzz::Face> {
1469        let ppem = self.0.size.0 as u16;
1470
1471        let mut font = self.0.face.harfbuzz()?;
1472
1473        font.set_pixels_per_em(Some((ppem, ppem)));
1474        font.set_variations(&self.0.variations);
1475
1476        Some(font)
1477    }
1478
1479    /// Font size.
1480    ///
1481    /// This is also the *pixels-per-em* value.
1482    pub fn size(&self) -> Px {
1483        self.0.size
1484    }
1485
1486    /// Custom font variations.
1487    pub fn variations(&self) -> &RFontVariations {
1488        &self.0.variations
1489    }
1490
1491    /// Sized font metrics.
1492    pub fn metrics(&self) -> &FontMetrics {
1493        &self.0.metrics
1494    }
1495
1496    /// Iterate over pixel offsets relative to `lig` glyph start that represents the
1497    /// caret offset for each cluster that is covered by the ligature, after the first.
1498    ///
1499    /// The caret offset for the first cluster is the glyph offset and is not yielded in the iterator. The
1500    /// yielded offsets are relative to the glyph position.
1501    pub fn ligature_caret_offsets(
1502        &self,
1503        lig: zng_view_api::font::GlyphIndex,
1504    ) -> impl ExactSizeIterator<Item = f32> + DoubleEndedIterator + '_ {
1505        let face = &self.0.face.0;
1506        face.lig_carets.carets(lig).iter().map(move |&o| match o {
1507            ligature_util::LigatureCaret::Coordinate(o) => {
1508                let size_scale = 1.0 / face.metrics.units_per_em as f32 * self.0.size.0 as f32;
1509                o as f32 * size_scale
1510            }
1511            ligature_util::LigatureCaret::GlyphContourPoint(i) => {
1512                if let Some(f) = self.harfbuzz() {
1513                    struct Search {
1514                        i: u16,
1515                        s: u16,
1516                        x: f32,
1517                    }
1518                    impl Search {
1519                        fn check(&mut self, x: f32) {
1520                            self.s = self.s.saturating_add(1);
1521                            if self.s == self.i {
1522                                self.x = x;
1523                            }
1524                        }
1525                    }
1526                    impl ttf_parser::OutlineBuilder for Search {
1527                        fn move_to(&mut self, x: f32, _y: f32) {
1528                            self.check(x);
1529                        }
1530
1531                        fn line_to(&mut self, x: f32, _y: f32) {
1532                            self.check(x);
1533                        }
1534
1535                        fn quad_to(&mut self, _x1: f32, _y1: f32, x: f32, _y: f32) {
1536                            self.check(x)
1537                        }
1538
1539                        fn curve_to(&mut self, _x1: f32, _y1: f32, _x2: f32, _y2: f32, x: f32, _y: f32) {
1540                            self.check(x);
1541                        }
1542
1543                        fn close(&mut self) {}
1544                    }
1545                    let mut search = Search { i, s: 0, x: 0.0 };
1546                    if f.outline_glyph(ttf_parser::GlyphId(lig as _), &mut search).is_some() && search.s >= search.i {
1547                        return search.x * self.0.metrics.size_scale;
1548                    }
1549                }
1550                0.0
1551            }
1552        })
1553    }
1554}
1555impl zng_app::render::Font for Font {
1556    fn is_empty_fallback(&self) -> bool {
1557        self.face().is_empty()
1558    }
1559
1560    fn renderer_id(&self, renderer: &ViewRenderer, synthesis: FontSynthesis) -> zng_view_api::font::FontId {
1561        self.render_font(renderer, synthesis)
1562    }
1563}
1564
1565/// A list of [`FontFace`] resolved from a [`FontName`] list, plus the [fallback](GenericFonts::fallback) font.
1566///
1567/// Glyphs that are not resolved by the first font fallback to the second font and so on.
1568#[derive(Debug, Clone)]
1569pub struct FontFaceList {
1570    fonts: Box<[FontFace]>,
1571    requested_style: FontStyle,
1572    requested_weight: FontWeight,
1573    requested_stretch: FontStretch,
1574}
1575impl FontFaceList {
1576    /// New list with only the [`FontFace::empty`].
1577    pub fn empty() -> Self {
1578        Self {
1579            fonts: Box::new([FontFace::empty()]),
1580            requested_style: FontStyle::Normal,
1581            requested_weight: FontWeight::NORMAL,
1582            requested_stretch: FontStretch::NORMAL,
1583        }
1584    }
1585
1586    /// Style requested in the query that generated this font face list.
1587    pub fn requested_style(&self) -> FontStyle {
1588        self.requested_style
1589    }
1590
1591    /// Weight requested in the query that generated this font face list.
1592    pub fn requested_weight(&self) -> FontWeight {
1593        self.requested_weight
1594    }
1595
1596    /// Stretch requested in the query that generated this font face list.
1597    pub fn requested_stretch(&self) -> FontStretch {
1598        self.requested_stretch
1599    }
1600
1601    /// The font face that best matches the requested properties.
1602    pub fn best(&self) -> &FontFace {
1603        &self.fonts[0]
1604    }
1605
1606    /// Gets the font synthesis to use to better render the given font face on the list.
1607    pub fn face_synthesis(&self, face_index: usize) -> FontSynthesis {
1608        if let Some(face) = self.fonts.get(face_index) {
1609            face.synthesis_for(self.requested_style, self.requested_weight)
1610        } else {
1611            FontSynthesis::DISABLED
1612        }
1613    }
1614
1615    /// Iterate over font faces, more specific first.
1616    pub fn iter(&self) -> std::slice::Iter<FontFace> {
1617        self.fonts.iter()
1618    }
1619
1620    /// Number of font faces in the list.
1621    ///
1622    /// This is at least `1`, but can be the empty face.
1623    pub fn len(&self) -> usize {
1624        self.fonts.len()
1625    }
1626
1627    /// Is length `1` and only contains the empty face.
1628    pub fn is_empty(&self) -> bool {
1629        self.fonts[0].is_empty() && self.fonts.len() == 1
1630    }
1631
1632    /// Gets a sized font list.
1633    ///
1634    /// This calls [`FontFace::sized`] for each font in the list.
1635    pub fn sized(&self, font_size: Px, variations: RFontVariations) -> FontList {
1636        FontList {
1637            fonts: self.fonts.iter().map(|f| f.sized(font_size, variations.clone())).collect(),
1638            requested_style: self.requested_style,
1639            requested_weight: self.requested_weight,
1640            requested_stretch: self.requested_stretch,
1641        }
1642    }
1643}
1644impl PartialEq for FontFaceList {
1645    /// Both are equal if each point to the same fonts in the same order and have the same requested properties.
1646    fn eq(&self, other: &Self) -> bool {
1647        self.requested_style == other.requested_style
1648            && self.requested_weight == other.requested_weight
1649            && self.requested_stretch == other.requested_stretch
1650            && self.fonts.len() == other.fonts.len()
1651            && self.fonts.iter().zip(other.fonts.iter()).all(|(a, b)| a == b)
1652    }
1653}
1654impl Eq for FontFaceList {}
1655impl std::ops::Deref for FontFaceList {
1656    type Target = [FontFace];
1657
1658    fn deref(&self) -> &Self::Target {
1659        &self.fonts
1660    }
1661}
1662impl<'a> std::iter::IntoIterator for &'a FontFaceList {
1663    type Item = &'a FontFace;
1664
1665    type IntoIter = std::slice::Iter<'a, FontFace>;
1666
1667    fn into_iter(self) -> Self::IntoIter {
1668        self.iter()
1669    }
1670}
1671impl std::ops::Index<usize> for FontFaceList {
1672    type Output = FontFace;
1673
1674    fn index(&self, index: usize) -> &Self::Output {
1675        &self.fonts[index]
1676    }
1677}
1678
1679/// A list of [`Font`] created from a [`FontFaceList`].
1680#[derive(Debug, Clone)]
1681pub struct FontList {
1682    fonts: Box<[Font]>,
1683    requested_style: FontStyle,
1684    requested_weight: FontWeight,
1685    requested_stretch: FontStretch,
1686}
1687#[expect(clippy::len_without_is_empty)] // cannot be empty.
1688impl FontList {
1689    /// The font that best matches the requested properties.
1690    pub fn best(&self) -> &Font {
1691        &self.fonts[0]
1692    }
1693
1694    /// Font size requested in the query that generated this font list.
1695    pub fn requested_size(&self) -> Px {
1696        self.fonts[0].size()
1697    }
1698
1699    /// Style requested in the query that generated this font list.
1700    pub fn requested_style(&self) -> FontStyle {
1701        self.requested_style
1702    }
1703
1704    /// Weight requested in the query that generated this font list.
1705    pub fn requested_weight(&self) -> FontWeight {
1706        self.requested_weight
1707    }
1708
1709    /// Stretch requested in the query that generated this font list.
1710    pub fn requested_stretch(&self) -> FontStretch {
1711        self.requested_stretch
1712    }
1713
1714    /// Gets the font synthesis to use to better render the given font on the list.
1715    pub fn face_synthesis(&self, font_index: usize) -> FontSynthesis {
1716        if let Some(font) = self.fonts.get(font_index) {
1717            font.0.face.synthesis_for(self.requested_style, self.requested_weight)
1718        } else {
1719            FontSynthesis::DISABLED
1720        }
1721    }
1722
1723    /// Iterate over font faces, more specific first.
1724    pub fn iter(&self) -> std::slice::Iter<Font> {
1725        self.fonts.iter()
1726    }
1727
1728    /// Number of font faces in the list.
1729    ///
1730    /// This is at least `1`.
1731    pub fn len(&self) -> usize {
1732        self.fonts.len()
1733    }
1734
1735    /// Returns `true` is `self` is sized from the `faces` list.
1736    pub fn is_sized_from(&self, faces: &FontFaceList) -> bool {
1737        if self.len() != faces.len() {
1738            return false;
1739        }
1740
1741        for (font, face) in self.iter().zip(faces.iter()) {
1742            if font.face() != face {
1743                return false;
1744            }
1745        }
1746
1747        true
1748    }
1749}
1750impl PartialEq for FontList {
1751    /// Both are equal if each point to the same fonts in the same order and have the same requested properties.
1752    fn eq(&self, other: &Self) -> bool {
1753        self.requested_style == other.requested_style
1754            && self.requested_weight == other.requested_weight
1755            && self.requested_stretch == other.requested_stretch
1756            && self.fonts.len() == other.fonts.len()
1757            && self.fonts.iter().zip(other.fonts.iter()).all(|(a, b)| a == b)
1758    }
1759}
1760impl Eq for FontList {}
1761impl std::ops::Deref for FontList {
1762    type Target = [Font];
1763
1764    fn deref(&self) -> &Self::Target {
1765        &self.fonts
1766    }
1767}
1768impl<'a> std::iter::IntoIterator for &'a FontList {
1769    type Item = &'a Font;
1770
1771    type IntoIter = std::slice::Iter<'a, Font>;
1772
1773    fn into_iter(self) -> Self::IntoIter {
1774        self.iter()
1775    }
1776}
1777impl<I: SliceIndex<[Font]>> std::ops::Index<I> for FontList {
1778    type Output = I::Output;
1779
1780    fn index(&self, index: I) -> &I::Output {
1781        &self.fonts[index]
1782    }
1783}
1784
1785struct FontFaceLoader {
1786    custom_fonts: HashMap<FontName, Vec<FontFace>>,
1787    unregister_requests: Vec<(FontName, ResponderVar<bool>)>,
1788
1789    system_fonts_cache: HashMap<FontName, Vec<SystemFontFace>>,
1790    list_cache: HashMap<Box<[FontName]>, Vec<FontFaceListQuery>>,
1791}
1792struct SystemFontFace {
1793    properties: (FontStyle, FontWeight, FontStretch),
1794    result: ResponseVar<Option<FontFace>>,
1795}
1796struct FontFaceListQuery {
1797    properties: (FontStyle, FontWeight, FontStretch),
1798    lang: Lang,
1799    result: ResponseVar<FontFaceList>,
1800}
1801impl FontFaceLoader {
1802    fn new() -> Self {
1803        FontFaceLoader {
1804            custom_fonts: HashMap::new(),
1805            unregister_requests: vec![],
1806            system_fonts_cache: HashMap::new(),
1807            list_cache: HashMap::new(),
1808        }
1809    }
1810
1811    fn on_view_process_respawn(&mut self) {
1812        let sys_fonts = self.system_fonts_cache.values().flatten().filter_map(|f| f.result.rsp().flatten());
1813        for face in self.custom_fonts.values().flatten().cloned().chain(sys_fonts) {
1814            let mut m = face.0.m.lock();
1815            m.render_ids.clear();
1816            for inst in m.instances.values() {
1817                inst.0.render_keys.lock().clear();
1818            }
1819        }
1820    }
1821
1822    fn on_refresh(&mut self) {
1823        for (_, sys_family) in self.system_fonts_cache.drain() {
1824            for sys_font in sys_family {
1825                sys_font.result.with(|r| {
1826                    if let Some(Some(face)) = r.done() {
1827                        face.on_refresh();
1828                    }
1829                });
1830            }
1831        }
1832    }
1833    fn on_prune(&mut self) {
1834        self.system_fonts_cache.retain(|_, v| {
1835            v.retain(|sff| {
1836                if sff.result.strong_count() == 1 {
1837                    sff.result.with(|r| {
1838                        match r.done() {
1839                            Some(Some(face)) => Arc::strong_count(&face.0) > 1, // face shared
1840                            Some(None) => false,                                // loading for no one
1841                            None => true,                                       // retain not found
1842                        }
1843                    })
1844                } else {
1845                    // response var shared
1846                    true
1847                }
1848            });
1849            !v.is_empty()
1850        });
1851
1852        self.list_cache.clear();
1853    }
1854
1855    fn register(custom_font: CustomFont) -> ResponseVar<Result<FontFace, FontLoadingError>> {
1856        // start loading
1857        let resp = task::respond(FontFace::load_custom(custom_font));
1858
1859        // modify loader.custom_fonts at the end of whatever update is happening when finishes loading.
1860        resp.hook(|args| {
1861            if let Some(done) = args.value().done() {
1862                if let Ok(face) = done {
1863                    let mut fonts = FONTS_SV.write();
1864                    let family = fonts.loader.custom_fonts.entry(face.0.family_name.clone()).or_default();
1865                    let existing = family
1866                        .iter()
1867                        .position(|f| f.0.weight == face.0.weight && f.0.style == face.0.style && f.0.stretch == face.0.stretch);
1868
1869                    if let Some(i) = existing {
1870                        family[i] = face.clone();
1871                    } else {
1872                        family.push(face.clone());
1873                    }
1874
1875                    FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::CustomFonts));
1876                }
1877                false
1878            } else {
1879                true
1880            }
1881        })
1882        .perm();
1883        resp
1884    }
1885
1886    fn unregister(&mut self, custom_family: FontName) -> ResponseVar<bool> {
1887        let (responder, response) = response_var();
1888
1889        if !self.unregister_requests.is_empty() {
1890            UPDATES.update(None);
1891        }
1892        self.unregister_requests.push((custom_family, responder));
1893
1894        response
1895    }
1896
1897    fn try_list(
1898        &self,
1899        families: &[FontName],
1900        style: FontStyle,
1901        weight: FontWeight,
1902        stretch: FontStretch,
1903        lang: &Lang,
1904    ) -> Option<ResponseVar<FontFaceList>> {
1905        if let Some(queries) = self.list_cache.get(families) {
1906            for q in queries {
1907                if q.properties == (style, weight, stretch) && &q.lang == lang {
1908                    return Some(q.result.clone());
1909                }
1910            }
1911        }
1912        None
1913    }
1914
1915    fn load_list(
1916        &mut self,
1917        families: &[FontName],
1918        style: FontStyle,
1919        weight: FontWeight,
1920        stretch: FontStretch,
1921        lang: &Lang,
1922    ) -> ResponseVar<FontFaceList> {
1923        if let Some(r) = self.try_list(families, style, weight, stretch, lang) {
1924            return r;
1925        }
1926
1927        let mut list = Vec::with_capacity(families.len() + 1);
1928        let mut pending = vec![];
1929
1930        {
1931            let fallback = [GenericFonts {}.fallback(lang)];
1932            let mut used = HashSet::with_capacity(families.len());
1933            for name in families.iter().chain(&fallback) {
1934                if !used.insert(name) {
1935                    continue;
1936                }
1937
1938                let face = self.load(name, style, weight, stretch, lang);
1939                if face.is_done() {
1940                    if let Some(face) = face.into_rsp().unwrap() {
1941                        list.push(face);
1942                    }
1943                } else {
1944                    pending.push((list.len(), face));
1945                }
1946            }
1947        }
1948
1949        let r = if pending.is_empty() {
1950            if list.is_empty() {
1951                tracing::error!(target: "font_loading", "failed to load fallback font");
1952                list.push(FontFace::empty());
1953            }
1954            response_done_var(FontFaceList {
1955                fonts: list.into_boxed_slice(),
1956                requested_style: style,
1957                requested_weight: weight,
1958                requested_stretch: stretch,
1959            })
1960        } else {
1961            task::respond(async move {
1962                for (i, pending) in pending.into_iter().rev() {
1963                    if let Some(rsp) = pending.wait_into_rsp().await {
1964                        list.insert(i, rsp);
1965                    }
1966                }
1967
1968                if list.is_empty() {
1969                    tracing::error!(target: "font_loading", "failed to load fallback font");
1970                    list.push(FontFace::empty());
1971                }
1972
1973                FontFaceList {
1974                    fonts: list.into_boxed_slice(),
1975                    requested_style: style,
1976                    requested_weight: weight,
1977                    requested_stretch: stretch,
1978                }
1979            })
1980        };
1981
1982        self.list_cache
1983            .entry(families.iter().cloned().collect())
1984            .or_insert_with(|| Vec::with_capacity(1))
1985            .push(FontFaceListQuery {
1986                properties: (style, weight, stretch),
1987                lang: lang.clone(),
1988                result: r.clone(),
1989            });
1990
1991        r
1992    }
1993
1994    fn try_cached(
1995        &self,
1996        font_name: &FontName,
1997        style: FontStyle,
1998        weight: FontWeight,
1999        stretch: FontStretch,
2000        lang: &Lang,
2001    ) -> Option<ResponseVar<Option<FontFace>>> {
2002        let resolved = GenericFonts {}.resolve(font_name, lang);
2003        let font_name = resolved.as_ref().unwrap_or(font_name);
2004        self.try_resolved(font_name, style, weight, stretch)
2005    }
2006
2007    /// Try cached again, otherwise begins loading and inserts the response in the cache.
2008    fn load(
2009        &mut self,
2010        font_name: &FontName,
2011        style: FontStyle,
2012        weight: FontWeight,
2013        stretch: FontStretch,
2014        lang: &Lang,
2015    ) -> ResponseVar<Option<FontFace>> {
2016        let resolved = GenericFonts {}.resolve(font_name, lang);
2017        let font_name = resolved.as_ref().unwrap_or(font_name);
2018        self.load_resolved(font_name, style, weight, stretch)
2019    }
2020
2021    /// Get a `font_name` that already resolved generic names if it is already in cache.
2022    fn try_resolved(
2023        &self,
2024        font_name: &FontName,
2025        style: FontStyle,
2026        weight: FontWeight,
2027        stretch: FontStretch,
2028    ) -> Option<ResponseVar<Option<FontFace>>> {
2029        if let Some(custom_family) = self.custom_fonts.get(font_name) {
2030            let custom = Self::match_custom(custom_family, style, weight, stretch);
2031            return Some(response_done_var(Some(custom)));
2032        }
2033
2034        if let Some(cached_sys_family) = self.system_fonts_cache.get(font_name) {
2035            for sys_face in cached_sys_family.iter() {
2036                if sys_face.properties == (style, weight, stretch) {
2037                    return Some(sys_face.result.clone());
2038                }
2039            }
2040        }
2041
2042        None
2043    }
2044
2045    /// Load a `font_name` that already resolved generic names.
2046    fn load_resolved(
2047        &mut self,
2048        font_name: &FontName,
2049        style: FontStyle,
2050        weight: FontWeight,
2051        stretch: FontStretch,
2052    ) -> ResponseVar<Option<FontFace>> {
2053        if let Some(cached) = self.try_resolved(font_name, style, weight, stretch) {
2054            return cached;
2055        }
2056
2057        let load = task::wait(clmv!(font_name, || {
2058            let (bytes, face_index) = match Self::get_system(&font_name, style, weight, stretch) {
2059                Some(h) => h,
2060                None => {
2061                    #[cfg(debug_assertions)]
2062                    static NOT_FOUND: Mutex<Option<HashSet<FontName>>> = Mutex::new(None);
2063
2064                    #[cfg(debug_assertions)]
2065                    if NOT_FOUND.lock().get_or_insert_with(HashSet::default).insert(font_name.clone()) {
2066                        tracing::debug!(r#"font "{font_name}" not found"#);
2067                    }
2068
2069                    return None;
2070                }
2071            };
2072            match FontFace::load(bytes, face_index) {
2073                Ok(f) => Some(f),
2074                Err(FontLoadingError::UnknownFormat) => None,
2075                Err(e) => {
2076                    tracing::error!(target: "font_loading", "failed to load system font, {e}\nquery: {:?}", (font_name, style, weight, stretch));
2077                    None
2078                }
2079            }
2080        }));
2081        let result = task::respond(async_clmv!(font_name, {
2082            match task::with_deadline(load, 10.secs()).await {
2083                Ok(r) => r,
2084                Err(_) => {
2085                    tracing::error!(target: "font_loading", "timeout loading {font_name:?}");
2086                    None
2087                }
2088            }
2089        }));
2090
2091        self.system_fonts_cache
2092            .entry(font_name.clone())
2093            .or_insert_with(|| Vec::with_capacity(1))
2094            .push(SystemFontFace {
2095                properties: (style, weight, stretch),
2096                result: result.clone(),
2097            });
2098
2099        result
2100    }
2101
2102    fn get_system(font_name: &FontName, style: FontStyle, weight: FontWeight, stretch: FontStretch) -> Option<(FontDataRef, u32)> {
2103        let _span = tracing::trace_span!("FontFaceLoader::get_system").entered();
2104        match query_util::best(font_name, style, weight, stretch) {
2105            Ok(r) => r,
2106            Err(e) => {
2107                tracing::error!("cannot get `{font_name}` system font, {e}");
2108                None
2109            }
2110        }
2111    }
2112
2113    fn match_custom(faces: &[FontFace], style: FontStyle, weight: FontWeight, stretch: FontStretch) -> FontFace {
2114        if faces.len() == 1 {
2115            // it is common for custom font names to only have one face.
2116            return faces[0].clone();
2117        }
2118
2119        let mut set = Vec::with_capacity(faces.len());
2120        let mut set_dist = 0.0f64; // stretch distance of current set if it is not empty.
2121
2122        // # Filter Stretch
2123        //
2124        // Closest to query stretch, if the query is narrow, closest narrow then
2125        // closest wide, if the query is wide the reverse.
2126        let wrong_side = if stretch <= FontStretch::NORMAL {
2127            |s| s > FontStretch::NORMAL
2128        } else {
2129            |s| s <= FontStretch::NORMAL
2130        };
2131        for face in faces {
2132            let mut dist = (face.stretch().0 - stretch.0).abs() as f64;
2133            if wrong_side(face.stretch()) {
2134                dist += f32::MAX as f64 + 1.0;
2135            }
2136
2137            if set.is_empty() {
2138                set.push(face);
2139                set_dist = dist;
2140            } else if dist < set_dist {
2141                // better candidate found, restart closest set.
2142                set_dist = dist;
2143                set.clear();
2144                set.push(face);
2145            } else if (dist - set_dist).abs() < 0.0001 {
2146                // another candidate, same distance.
2147                set.push(face);
2148            }
2149        }
2150        if set.len() == 1 {
2151            return set[0].clone();
2152        }
2153
2154        // # Filter Style
2155        //
2156        // Each query style has a fallback preference, we retain the faces that have the best
2157        // style given the query preference.
2158        let style_pref = match style {
2159            FontStyle::Normal => [FontStyle::Normal, FontStyle::Oblique, FontStyle::Italic],
2160            FontStyle::Italic => [FontStyle::Italic, FontStyle::Oblique, FontStyle::Normal],
2161            FontStyle::Oblique => [FontStyle::Oblique, FontStyle::Italic, FontStyle::Normal],
2162        };
2163        let mut best_style = style_pref.len();
2164        for face in &set {
2165            let i = style_pref.iter().position(|&s| s == face.style()).unwrap();
2166            if i < best_style {
2167                best_style = i;
2168            }
2169        }
2170        set.retain(|f| f.style() == style_pref[best_style]);
2171        if set.len() == 1 {
2172            return set[0].clone();
2173        }
2174
2175        // # Filter Weight
2176        //
2177        // a: under 400 query matches query then descending under query then ascending over query.
2178        // b: over 500 query matches query then ascending over query then descending under query.
2179        //
2180        // c: in 400..=500 query matches query then ascending to 500 then descending under query
2181        //     then ascending over 500.
2182        let add_penalty = if weight.0 >= 400.0 && weight.0 <= 500.0 {
2183            // c:
2184            |face: &FontFace, weight: FontWeight, dist: &mut f64| {
2185                // Add penalty for:
2186                if face.weight() < weight {
2187                    // Not being in search up to 500
2188                    *dist += 100.0;
2189                } else if face.weight().0 > 500.0 {
2190                    // Not being in search down to 0
2191                    *dist += 600.0;
2192                }
2193            }
2194        } else if weight.0 < 400.0 {
2195            // a:
2196            |face: &FontFace, weight: FontWeight, dist: &mut f64| {
2197                if face.weight() > weight {
2198                    *dist += weight.0 as f64;
2199                }
2200            }
2201        } else {
2202            debug_assert!(weight.0 > 500.0);
2203            // b:
2204            |face: &FontFace, weight: FontWeight, dist: &mut f64| {
2205                if face.weight() < weight {
2206                    *dist += f32::MAX as f64;
2207                }
2208            }
2209        };
2210
2211        let mut best = set[0];
2212        let mut best_dist = f64::MAX;
2213
2214        for face in &set {
2215            let mut dist = (face.weight().0 - weight.0).abs() as f64;
2216
2217            add_penalty(face, weight, &mut dist);
2218
2219            if dist < best_dist {
2220                best_dist = dist;
2221                best = face;
2222            }
2223        }
2224
2225        best.clone()
2226    }
2227}
2228
2229struct RenderFontFace {
2230    renderer: ViewRenderer,
2231    face_id: zng_view_api::font::FontFaceId,
2232}
2233impl RenderFontFace {
2234    fn new(renderer: &ViewRenderer, face_id: zng_view_api::font::FontFaceId) -> Self {
2235        RenderFontFace {
2236            renderer: renderer.clone(),
2237            face_id,
2238        }
2239    }
2240}
2241impl Drop for RenderFontFace {
2242    fn drop(&mut self) {
2243        // error here means the entire renderer was already dropped.
2244        let _ = self.renderer.delete_font_face(self.face_id);
2245    }
2246}
2247
2248struct RenderFont {
2249    renderer: ViewRenderer,
2250    synthesis: FontSynthesis,
2251    font_id: zng_view_api::font::FontId,
2252}
2253impl RenderFont {
2254    fn new(renderer: &ViewRenderer, synthesis: FontSynthesis, font_id: zng_view_api::font::FontId) -> RenderFont {
2255        RenderFont {
2256            renderer: renderer.clone(),
2257            synthesis,
2258            font_id,
2259        }
2260    }
2261}
2262impl Drop for RenderFont {
2263    fn drop(&mut self) {
2264        // error here means the entire renderer was already dropped.
2265        let _ = self.renderer.delete_font(self.font_id);
2266    }
2267}
2268
2269app_local! {
2270    static GENERIC_FONTS_SV: GenericFontsService = GenericFontsService::new();
2271}
2272
2273struct GenericFontsService {
2274    serif: LangMap<FontName>,
2275    sans_serif: LangMap<FontName>,
2276    monospace: LangMap<FontName>,
2277    cursive: LangMap<FontName>,
2278    fantasy: LangMap<FontName>,
2279    fallback: LangMap<FontName>,
2280
2281    requests: Vec<Box<dyn FnOnce(&mut GenericFontsService) + Send + Sync>>,
2282}
2283impl GenericFontsService {
2284    fn new() -> Self {
2285        fn default(name: impl Into<FontName>) -> LangMap<FontName> {
2286            let mut f = LangMap::with_capacity(1);
2287            f.insert(lang!(und), name.into());
2288            f
2289        }
2290
2291        let serif = "serif";
2292        let sans_serif = "sans-serif";
2293        let monospace = "monospace";
2294        let cursive = "cursive";
2295        let fantasy = "fantasy";
2296        let fallback = if cfg!(windows) {
2297            "Segoe UI Symbol"
2298        } else if cfg!(target_os = "linux") {
2299            "Standard Symbols PS"
2300        } else {
2301            "sans-serif"
2302        };
2303
2304        GenericFontsService {
2305            serif: default(serif),
2306            sans_serif: default(sans_serif),
2307            monospace: default(monospace),
2308            cursive: default(cursive),
2309            fantasy: default(fantasy),
2310
2311            fallback: default(fallback),
2312
2313            requests: vec![],
2314        }
2315    }
2316}
2317
2318/// Generic fonts configuration for the app.
2319///
2320/// This type can be accessed from the [`FONTS`] service.
2321///
2322/// # Defaults
2323///
2324/// By default the `serif`, `sans_serif`, `monospace`, `cursive` and `fantasy` are set to their own generic name,
2325/// this delegates the resolution to the operating system.
2326///
2327/// The default `fallback` font is "Segoe UI Symbol" for Windows, "Standard Symbols PS" for Linux and "sans-serif" for others.
2328///
2329/// See also [`FontNames::system_ui`] for the default font selection for UIs.
2330///
2331/// [`FontNames::system_ui`]: crate::FontNames::system_ui
2332pub struct GenericFonts {}
2333impl GenericFonts {}
2334macro_rules! impl_fallback_accessors {
2335    ($($name:ident=$name_str:tt),+ $(,)?) => {$($crate::paste! {
2336    #[doc = "Gets the fallback *"$name_str "* font for the given language."]
2337    ///
2338    /// Returns a font name for the best `lang` match.
2339    ///
2340    #[doc = "Note that the returned name can still be the generic `\""$name_str "\"`, this delegates the resolution to the operating system."]
2341
2342    pub fn $name(&self, lang: &Lang) -> FontName {
2343        GENERIC_FONTS_SV.read().$name.get(lang).unwrap().clone()
2344    }
2345
2346    #[doc = "Sets the fallback *"$name_str "* font for the given language."]
2347    ///
2348    /// The change applied for the next update.
2349    ///
2350    /// Use `lang!(und)` to set name used when no language matches.
2351    pub fn [<set_ $name>]<F: Into<FontName>>(&self, lang: Lang, font_name: F) {
2352        let mut g = GENERIC_FONTS_SV.write();
2353        let font_name = font_name.into();
2354        if g.requests.is_empty() {
2355            UPDATES.update(None);
2356        }
2357        g.requests.push(Box::new(move |g| {
2358            g.$name.insert(lang.clone(), font_name);
2359            FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::GenericFont(FontName::$name(), lang)));
2360        }));
2361    }
2362    })+};
2363}
2364impl GenericFonts {
2365    impl_fallback_accessors! {
2366        serif="serif", sans_serif="sans-serif", monospace="monospace", cursive="cursive", fantasy="fantasy"
2367    }
2368
2369    /// Gets the ultimate fallback font used when none of the other fonts support a glyph.
2370    ///
2371    /// Returns a font name.
2372    pub fn fallback(&self, lang: &Lang) -> FontName {
2373        GENERIC_FONTS_SV.read().fallback.get(lang).unwrap().clone()
2374    }
2375
2376    /// Sets the ultimate fallback font used when none of other fonts support a glyph.
2377    ///
2378    /// The change applies for the next update.
2379    ///
2380    /// Use `lang!(und)` to set name used when no language matches.
2381    pub fn set_fallback<F: Into<FontName>>(&self, lang: Lang, font_name: F) {
2382        let mut g = GENERIC_FONTS_SV.write();
2383        if g.requests.is_empty() {
2384            UPDATES.update(None);
2385        }
2386        let font_name = font_name.into();
2387        g.requests.push(Box::new(move |g| {
2388            FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::Fallback(lang.clone())));
2389            g.fallback.insert(lang, font_name);
2390        }));
2391    }
2392
2393    /// Returns the font name registered for the generic `name` and `lang`.
2394    ///
2395    /// Returns `None` if `name` if not one of the generic font names.
2396    pub fn resolve(&self, name: &FontName, lang: &Lang) -> Option<FontName> {
2397        if name == &FontName::serif() {
2398            Some(self.serif(lang))
2399        } else if name == &FontName::sans_serif() {
2400            Some(self.sans_serif(lang))
2401        } else if name == &FontName::monospace() {
2402            Some(self.monospace(lang))
2403        } else if name == &FontName::cursive() {
2404            Some(self.cursive(lang))
2405        } else if name == &FontName::fantasy() {
2406            Some(self.fantasy(lang))
2407        } else {
2408            None
2409        }
2410    }
2411}
2412
2413/// Reference to in memory font data.
2414#[derive(Clone)]
2415pub struct FontDataRef(pub Arc<Vec<u8>>);
2416impl FontDataRef {
2417    /// Copy bytes from embedded font.
2418    pub fn from_static(data: &'static [u8]) -> Self {
2419        FontDataRef(Arc::new(data.to_vec()))
2420    }
2421}
2422impl fmt::Debug for FontDataRef {
2423    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2424        write!(f, "FontDataRef(Arc<{} bytes>>)", self.0.len())
2425    }
2426}
2427impl std::ops::Deref for FontDataRef {
2428    type Target = [u8];
2429
2430    fn deref(&self) -> &Self::Target {
2431        self.0.deref()
2432    }
2433}
2434
2435#[derive(Debug, Clone)]
2436enum FontSource {
2437    File(PathBuf, u32),
2438    Memory(FontDataRef, u32),
2439    Alias(FontName),
2440}
2441
2442/// Custom font builder.
2443#[derive(Debug, Clone)]
2444pub struct CustomFont {
2445    name: FontName,
2446    source: FontSource,
2447    stretch: FontStretch,
2448    style: FontStyle,
2449    weight: FontWeight,
2450}
2451impl CustomFont {
2452    /// A custom font loaded from a file.
2453    ///
2454    /// If the file is a collection of fonts, `font_index` determines which, otherwise just pass `0`.
2455    ///
2456    /// The font is loaded in [`FONTS.register`].
2457    ///
2458    /// [`FONTS.register`]: FONTS::register
2459    pub fn from_file<N: Into<FontName>, P: Into<PathBuf>>(name: N, path: P, font_index: u32) -> Self {
2460        CustomFont {
2461            name: name.into(),
2462            source: FontSource::File(path.into(), font_index),
2463            stretch: FontStretch::NORMAL,
2464            style: FontStyle::Normal,
2465            weight: FontWeight::NORMAL,
2466        }
2467    }
2468
2469    /// A custom font loaded from a shared byte slice.
2470    ///
2471    /// If the font data is a collection of fonts, `font_index` determines which, otherwise just pass `0`.
2472    ///
2473    /// The font is loaded in [`FONTS.register`].
2474    ///
2475    /// [`FONTS.register`]: FONTS::register
2476    pub fn from_bytes<N: Into<FontName>>(name: N, data: FontDataRef, font_index: u32) -> Self {
2477        CustomFont {
2478            name: name.into(),
2479            source: FontSource::Memory(data, font_index),
2480            stretch: FontStretch::NORMAL,
2481            style: FontStyle::Normal,
2482            weight: FontWeight::NORMAL,
2483        }
2484    }
2485
2486    /// A custom font that maps to another font.
2487    ///
2488    /// The font is loaded in [`FONTS.register`].
2489    ///
2490    /// [`FONTS.register`]: FONTS::register
2491    pub fn from_other<N: Into<FontName>, O: Into<FontName>>(name: N, other_font: O) -> Self {
2492        CustomFont {
2493            name: name.into(),
2494            source: FontSource::Alias(other_font.into()),
2495            stretch: FontStretch::NORMAL,
2496            style: FontStyle::Normal,
2497            weight: FontWeight::NORMAL,
2498        }
2499    }
2500
2501    /// Set the [`FontStretch`].
2502    ///
2503    /// Default is [`FontStretch::NORMAL`].
2504    pub fn stretch(mut self, stretch: FontStretch) -> Self {
2505        self.stretch = stretch;
2506        self
2507    }
2508
2509    /// Set the [`FontStyle`].
2510    ///
2511    /// Default is [`FontStyle::Normal`].
2512    pub fn style(mut self, style: FontStyle) -> Self {
2513        self.style = style;
2514        self
2515    }
2516
2517    /// Set the [`FontWeight`].
2518    ///
2519    /// Default is [`FontWeight::NORMAL`].
2520    pub fn weight(mut self, weight: FontWeight) -> Self {
2521        self.weight = weight;
2522        self
2523    }
2524}
2525
2526/// The width of a font as an approximate fraction of the normal width.
2527///
2528/// Widths range from 0.5 to 2.0 inclusive, with 1.0 as the normal width.
2529#[derive(Clone, Copy, serde::Serialize, serde::Deserialize, Transitionable)]
2530#[serde(transparent)]
2531pub struct FontStretch(pub f32);
2532impl fmt::Debug for FontStretch {
2533    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2534        let name = self.name();
2535        if name.is_empty() {
2536            f.debug_tuple("FontStretch").field(&self.0).finish()
2537        } else {
2538            if f.alternate() {
2539                write!(f, "FontStretch::")?;
2540            }
2541            write!(f, "{name}")
2542        }
2543    }
2544}
2545impl PartialOrd for FontStretch {
2546    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
2547        Some(about_eq_ord(self.0, other.0, EQ_EPSILON))
2548    }
2549}
2550impl PartialEq for FontStretch {
2551    fn eq(&self, other: &Self) -> bool {
2552        about_eq(self.0, other.0, EQ_EPSILON)
2553    }
2554}
2555impl std::hash::Hash for FontStretch {
2556    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
2557        about_eq_hash(self.0, EQ_EPSILON, state)
2558    }
2559}
2560impl Default for FontStretch {
2561    fn default() -> FontStretch {
2562        FontStretch::NORMAL
2563    }
2564}
2565impl FontStretch {
2566    /// Ultra-condensed width (50%), the narrowest possible.
2567    pub const ULTRA_CONDENSED: FontStretch = FontStretch(0.5);
2568    /// Extra-condensed width (62.5%).
2569    pub const EXTRA_CONDENSED: FontStretch = FontStretch(0.625);
2570    /// Condensed width (75%).
2571    pub const CONDENSED: FontStretch = FontStretch(0.75);
2572    /// Semi-condensed width (87.5%).
2573    pub const SEMI_CONDENSED: FontStretch = FontStretch(0.875);
2574    /// Normal width (100%).
2575    pub const NORMAL: FontStretch = FontStretch(1.0);
2576    /// Semi-expanded width (112.5%).
2577    pub const SEMI_EXPANDED: FontStretch = FontStretch(1.125);
2578    /// Expanded width (125%).
2579    pub const EXPANDED: FontStretch = FontStretch(1.25);
2580    /// Extra-expanded width (150%).
2581    pub const EXTRA_EXPANDED: FontStretch = FontStretch(1.5);
2582    /// Ultra-expanded width (200%), the widest possible.
2583    pub const ULTRA_EXPANDED: FontStretch = FontStretch(2.0);
2584
2585    /// Gets the const name, if this value is one of the constants.
2586    pub fn name(self) -> &'static str {
2587        macro_rules! name {
2588            ($($CONST:ident;)+) => {$(
2589                if self == Self::$CONST {
2590                    return stringify!($CONST);
2591                }
2592            )+}
2593        }
2594        name! {
2595            ULTRA_CONDENSED;
2596            EXTRA_CONDENSED;
2597            CONDENSED;
2598            SEMI_CONDENSED;
2599            NORMAL;
2600            SEMI_EXPANDED;
2601            EXPANDED;
2602            EXTRA_EXPANDED;
2603            ULTRA_EXPANDED;
2604        }
2605        ""
2606    }
2607}
2608impl_from_and_into_var! {
2609    fn from(fct: Factor) -> FontStretch {
2610        FontStretch(fct.0)
2611    }
2612    fn from(pct: FactorPercent) -> FontStretch {
2613        FontStretch(pct.fct().0)
2614    }
2615    fn from(fct: f32) -> FontStretch {
2616        FontStretch(fct)
2617    }
2618}
2619impl From<ttf_parser::Width> for FontStretch {
2620    fn from(value: ttf_parser::Width) -> Self {
2621        use ttf_parser::Width::*;
2622        match value {
2623            UltraCondensed => FontStretch::ULTRA_CONDENSED,
2624            ExtraCondensed => FontStretch::EXTRA_CONDENSED,
2625            Condensed => FontStretch::CONDENSED,
2626            SemiCondensed => FontStretch::SEMI_CONDENSED,
2627            Normal => FontStretch::NORMAL,
2628            SemiExpanded => FontStretch::SEMI_EXPANDED,
2629            Expanded => FontStretch::EXPANDED,
2630            ExtraExpanded => FontStretch::EXTRA_EXPANDED,
2631            UltraExpanded => FontStretch::ULTRA_EXPANDED,
2632        }
2633    }
2634}
2635impl From<FontStretch> for ttf_parser::Width {
2636    fn from(value: FontStretch) -> Self {
2637        if value <= FontStretch::ULTRA_CONDENSED {
2638            ttf_parser::Width::UltraCondensed
2639        } else if value <= FontStretch::EXTRA_CONDENSED {
2640            ttf_parser::Width::ExtraCondensed
2641        } else if value <= FontStretch::CONDENSED {
2642            ttf_parser::Width::Condensed
2643        } else if value <= FontStretch::SEMI_CONDENSED {
2644            ttf_parser::Width::SemiCondensed
2645        } else if value <= FontStretch::NORMAL {
2646            ttf_parser::Width::Normal
2647        } else if value <= FontStretch::SEMI_EXPANDED {
2648            ttf_parser::Width::SemiExpanded
2649        } else if value <= FontStretch::EXPANDED {
2650            ttf_parser::Width::Expanded
2651        } else if value <= FontStretch::EXTRA_EXPANDED {
2652            ttf_parser::Width::ExtraExpanded
2653        } else {
2654            ttf_parser::Width::UltraExpanded
2655        }
2656    }
2657}
2658
2659/// The italic or oblique form of a font.
2660#[derive(Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]
2661pub enum FontStyle {
2662    /// The regular form.
2663    #[default]
2664    Normal,
2665    /// A form that is generally cursive in nature.
2666    Italic,
2667    /// A skewed version of the regular form.
2668    Oblique,
2669}
2670impl fmt::Debug for FontStyle {
2671    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2672        if f.alternate() {
2673            write!(f, "FontStyle::")?;
2674        }
2675        match self {
2676            Self::Normal => write!(f, "Normal"),
2677            Self::Italic => write!(f, "Italic"),
2678            Self::Oblique => write!(f, "Oblique"),
2679        }
2680    }
2681}
2682impl From<ttf_parser::Style> for FontStyle {
2683    fn from(value: ttf_parser::Style) -> Self {
2684        use ttf_parser::Style::*;
2685        match value {
2686            Normal => FontStyle::Normal,
2687            Italic => FontStyle::Italic,
2688            Oblique => FontStyle::Oblique,
2689        }
2690    }
2691}
2692
2693impl From<FontStyle> for ttf_parser::Style {
2694    fn from(value: FontStyle) -> Self {
2695        match value {
2696            FontStyle::Normal => Self::Normal,
2697            FontStyle::Italic => Self::Italic,
2698            FontStyle::Oblique => Self::Oblique,
2699        }
2700    }
2701}
2702
2703/// The degree of stroke thickness of a font. This value ranges from 100.0 to 900.0,
2704/// with 400.0 as normal.
2705#[derive(Clone, Copy, Transitionable, serde::Serialize, serde::Deserialize)]
2706pub struct FontWeight(pub f32);
2707impl Default for FontWeight {
2708    fn default() -> FontWeight {
2709        FontWeight::NORMAL
2710    }
2711}
2712impl fmt::Debug for FontWeight {
2713    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2714        let name = self.name();
2715        if name.is_empty() {
2716            f.debug_tuple("FontWeight").field(&self.0).finish()
2717        } else {
2718            if f.alternate() {
2719                write!(f, "FontWeight::")?;
2720            }
2721            write!(f, "{name}")
2722        }
2723    }
2724}
2725impl PartialOrd for FontWeight {
2726    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
2727        Some(about_eq_ord(self.0, other.0, EQ_EPSILON_100))
2728    }
2729}
2730impl PartialEq for FontWeight {
2731    fn eq(&self, other: &Self) -> bool {
2732        about_eq(self.0, other.0, EQ_EPSILON_100)
2733    }
2734}
2735impl std::hash::Hash for FontWeight {
2736    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
2737        about_eq_hash(self.0, EQ_EPSILON_100, state)
2738    }
2739}
2740impl FontWeight {
2741    /// Thin weight (100), the thinnest value.
2742    pub const THIN: FontWeight = FontWeight(100.0);
2743    /// Extra light weight (200).
2744    pub const EXTRA_LIGHT: FontWeight = FontWeight(200.0);
2745    /// Light weight (300).
2746    pub const LIGHT: FontWeight = FontWeight(300.0);
2747    /// Normal (400).
2748    pub const NORMAL: FontWeight = FontWeight(400.0);
2749    /// Medium weight (500, higher than normal).
2750    pub const MEDIUM: FontWeight = FontWeight(500.0);
2751    /// Semi-bold weight (600).
2752    pub const SEMIBOLD: FontWeight = FontWeight(600.0);
2753    /// Bold weight (700).
2754    pub const BOLD: FontWeight = FontWeight(700.0);
2755    /// Extra-bold weight (800).
2756    pub const EXTRA_BOLD: FontWeight = FontWeight(800.0);
2757    /// Black weight (900), the thickest value.
2758    pub const BLACK: FontWeight = FontWeight(900.0);
2759
2760    /// Gets the const name, if this value is one of the constants.
2761    pub fn name(self) -> &'static str {
2762        macro_rules! name {
2763                ($($CONST:ident;)+) => {$(
2764                    if self == Self::$CONST {
2765                        return stringify!($CONST);
2766                    }
2767                )+}
2768            }
2769        name! {
2770            THIN;
2771            EXTRA_LIGHT;
2772            LIGHT;
2773            NORMAL;
2774            MEDIUM;
2775            SEMIBOLD;
2776            BOLD;
2777            EXTRA_BOLD;
2778            BLACK;
2779        }
2780        ""
2781    }
2782}
2783impl_from_and_into_var! {
2784    fn from(weight: u32) -> FontWeight {
2785        FontWeight(weight as f32)
2786    }
2787    fn from(weight: f32) -> FontWeight {
2788        FontWeight(weight)
2789    }
2790}
2791impl From<ttf_parser::Weight> for FontWeight {
2792    fn from(value: ttf_parser::Weight) -> Self {
2793        use ttf_parser::Weight::*;
2794        match value {
2795            Thin => FontWeight::THIN,
2796            ExtraLight => FontWeight::EXTRA_LIGHT,
2797            Light => FontWeight::LIGHT,
2798            Normal => FontWeight::NORMAL,
2799            Medium => FontWeight::MEDIUM,
2800            SemiBold => FontWeight::SEMIBOLD,
2801            Bold => FontWeight::BOLD,
2802            ExtraBold => FontWeight::EXTRA_BOLD,
2803            Black => FontWeight::BLACK,
2804            Other(o) => FontWeight(o as f32),
2805        }
2806    }
2807}
2808impl From<FontWeight> for ttf_parser::Weight {
2809    fn from(value: FontWeight) -> Self {
2810        ttf_parser::Weight::from(value.0 as u16)
2811    }
2812}
2813
2814/// Configuration of text wrapping for Chinese, Japanese, or Korean text.
2815#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
2816pub enum LineBreak {
2817    /// The same rule used by other languages.
2818    Auto,
2819    /// The least restrictive rule, good for short lines.
2820    Loose,
2821    /// The most common rule.
2822    Normal,
2823    /// The most stringent rule.
2824    Strict,
2825    /// Allow line breaks in between any character including punctuation.
2826    Anywhere,
2827}
2828impl Default for LineBreak {
2829    /// [`LineBreak::Auto`]
2830    fn default() -> Self {
2831        LineBreak::Auto
2832    }
2833}
2834impl fmt::Debug for LineBreak {
2835    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2836        if f.alternate() {
2837            write!(f, "LineBreak::")?;
2838        }
2839        match self {
2840            LineBreak::Auto => write!(f, "Auto"),
2841            LineBreak::Loose => write!(f, "Loose"),
2842            LineBreak::Normal => write!(f, "Normal"),
2843            LineBreak::Strict => write!(f, "Strict"),
2844            LineBreak::Anywhere => write!(f, "Anywhere"),
2845        }
2846    }
2847}
2848
2849/// Hyphenation mode.
2850#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
2851pub enum Hyphens {
2852    /// Hyphens are never inserted in word breaks.
2853    None,
2854    /// Word breaks only happen in specially marked break characters: `-` and `\u{00AD} SHY`.
2855    ///
2856    /// * `U+2010` - The visible hyphen character.
2857    /// * `U+00AD` - The invisible hyphen character, is made visible in a word break.
2858    Manual,
2859    /// Hyphens are inserted like `Manual` and also using language specific hyphenation rules.
2860    Auto,
2861}
2862impl Default for Hyphens {
2863    /// [`Hyphens::Auto`]
2864    fn default() -> Self {
2865        Hyphens::Auto
2866    }
2867}
2868impl fmt::Debug for Hyphens {
2869    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2870        if f.alternate() {
2871            write!(f, "Hyphens::")?;
2872        }
2873        match self {
2874            Hyphens::None => write!(f, "None"),
2875            Hyphens::Manual => write!(f, "Manual"),
2876            Hyphens::Auto => write!(f, "Auto"),
2877        }
2878    }
2879}
2880
2881/// Configure line breaks inside words during text wrap.
2882///
2883/// This value is only considered if it is impossible to fit a full word to a line.
2884///
2885/// Hyphens can be inserted in word breaks using the [`Hyphens`] configuration.
2886#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
2887pub enum WordBreak {
2888    /// Line breaks can be inserted in between letters of Chinese/Japanese/Korean text only.
2889    Normal,
2890    /// Line breaks can be inserted between any letter.
2891    BreakAll,
2892    /// Line breaks are not inserted between any letter.
2893    KeepAll,
2894}
2895impl Default for WordBreak {
2896    /// [`WordBreak::Normal`]
2897    fn default() -> Self {
2898        WordBreak::Normal
2899    }
2900}
2901impl fmt::Debug for WordBreak {
2902    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2903        if f.alternate() {
2904            write!(f, "WordBreak::")?;
2905        }
2906        match self {
2907            WordBreak::Normal => write!(f, "Normal"),
2908            WordBreak::BreakAll => write!(f, "BreakAll"),
2909            WordBreak::KeepAll => write!(f, "KeepAll"),
2910        }
2911    }
2912}
2913
2914/// Text alignment justification mode.
2915#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
2916pub enum Justify {
2917    /// Selects the justification mode based on the language.
2918    ///
2919    /// For Chinese/Japanese/Korean uses `InterLetter` for the others uses `InterWord`.
2920    Auto,
2921    /// The text is justified by adding space between words.
2922    InterWord,
2923    /// The text is justified by adding space between letters.
2924    InterLetter,
2925}
2926impl Default for Justify {
2927    /// [`Justify::Auto`]
2928    fn default() -> Self {
2929        Justify::Auto
2930    }
2931}
2932impl Justify {
2933    /// Resolve `Auto` for the given language.
2934    pub fn resolve(self, lang: &Lang) -> Self {
2935        match self {
2936            Self::Auto => match lang.language.as_str() {
2937                "zh" | "ja" | "ko" => Self::InterLetter,
2938                _ => Self::InterWord,
2939            },
2940            m => m,
2941        }
2942    }
2943}
2944impl fmt::Debug for Justify {
2945    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2946        if f.alternate() {
2947            write!(f, "Justify::")?;
2948        }
2949        match self {
2950            Justify::Auto => write!(f, "Auto"),
2951            Justify::InterWord => write!(f, "InterWord"),
2952            Justify::InterLetter => write!(f, "InterLetter"),
2953        }
2954    }
2955}
2956
2957/// Various metrics that apply to the entire [`FontFace`].
2958///
2959/// For OpenType fonts, these mostly come from the `OS/2` table.
2960///
2961/// See the [`FreeType Glyph Metrics`] documentation for an explanation of the various metrics.
2962///
2963/// [`FreeType Glyph Metrics`]: https://freetype.org/freetype2/docs/glyphs/glyphs-3.html
2964#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
2965pub struct FontFaceMetrics {
2966    /// The number of font units per em.
2967    ///
2968    /// Font sizes are usually expressed in pixels per em; e.g. `12px` means 12 pixels per em.
2969    pub units_per_em: u32,
2970
2971    /// The maximum amount the font rises above the baseline, in font units.
2972    pub ascent: f32,
2973
2974    /// The maximum amount the font descends below the baseline, in font units.
2975    ///
2976    /// This is typically a negative value to match the definition of `sTypoDescender` in the
2977    /// `OS/2` table in the OpenType specification. If you are used to using Windows or Mac APIs,
2978    /// beware, as the sign is reversed from what those APIs return.
2979    pub descent: f32,
2980
2981    /// Distance between baselines, in font units.
2982    pub line_gap: f32,
2983
2984    /// The suggested distance of the top of the underline from the baseline (negative values
2985    /// indicate below baseline), in font units.
2986    pub underline_position: f32,
2987
2988    /// A suggested value for the underline thickness, in font units.
2989    pub underline_thickness: f32,
2990
2991    /// The approximate amount that uppercase letters rise above the baseline, in font units.
2992    pub cap_height: f32,
2993
2994    /// The approximate amount that non-ascending lowercase letters rise above the baseline, in
2995    /// font units.
2996    pub x_height: f32,
2997
2998    /// A rectangle that surrounds all bounding boxes of all glyphs, in font units.
2999    ///
3000    /// This corresponds to the `xMin`/`xMax`/`yMin`/`yMax` values in the OpenType `head` table.
3001    pub bounds: euclid::Rect<f32, ()>,
3002}
3003impl FontFaceMetrics {
3004    /// Compute [`FontMetrics`] given a font size in pixels.
3005    pub fn sized(&self, font_size_px: Px) -> FontMetrics {
3006        let size_scale = 1.0 / self.units_per_em as f32 * font_size_px.0 as f32;
3007        let s = move |f: f32| Px((f * size_scale).round() as i32);
3008        FontMetrics {
3009            size_scale,
3010            ascent: s(self.ascent),
3011            descent: s(self.descent),
3012            line_gap: s(self.line_gap),
3013            underline_position: s(self.underline_position),
3014            underline_thickness: s(self.underline_thickness),
3015            cap_height: s(self.cap_height),
3016            x_height: (s(self.x_height)),
3017            bounds: {
3018                let b = self.bounds;
3019                PxRect::new(
3020                    PxPoint::new(s(b.origin.x), s(b.origin.y)),
3021                    PxSize::new(s(b.size.width), s(b.size.height)),
3022                )
3023            },
3024        }
3025    }
3026}
3027
3028/// Various metrics about a [`Font`].
3029///
3030/// You can compute these metrics from a [`FontFaceMetrics`]
3031#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
3032pub struct FontMetrics {
3033    /// Multiply this to a font EM value to get the size in pixels.
3034    pub size_scale: f32,
3035
3036    /// The maximum amount the font rises above the baseline, in pixels.
3037    pub ascent: Px,
3038
3039    /// The maximum amount the font descends below the baseline, in pixels.
3040    ///
3041    /// This is typically a negative value to match the definition of `sTypoDescender` in the
3042    /// `OS/2` table in the OpenType specification. If you are used to using Windows or Mac APIs,
3043    /// beware, as the sign is reversed from what those APIs return.
3044    pub descent: Px,
3045
3046    /// Distance between baselines, in pixels.
3047    pub line_gap: Px,
3048
3049    /// The suggested distance of the top of the underline from the baseline (negative values
3050    /// indicate below baseline), in pixels.
3051    pub underline_position: Px,
3052
3053    /// A suggested value for the underline thickness, in pixels.
3054    pub underline_thickness: Px,
3055
3056    /// The approximate amount that uppercase letters rise above the baseline, in pixels.
3057    pub cap_height: Px,
3058
3059    /// The approximate amount that non-ascending lowercase letters rise above the baseline, in pixels.
3060    pub x_height: Px,
3061
3062    /// A rectangle that surrounds all bounding boxes of all glyphs, in pixels.
3063    ///
3064    /// This corresponds to the `xMin`/`xMax`/`yMin`/`yMax` values in the OpenType `head` table.
3065    pub bounds: PxRect,
3066}
3067impl FontMetrics {
3068    /// The font line height.
3069    pub fn line_height(&self) -> Px {
3070        self.ascent - self.descent + self.line_gap
3071    }
3072}
3073
3074/// Text transform function.
3075#[derive(Clone)]
3076pub enum TextTransformFn {
3077    /// No transform.
3078    None,
3079    /// To UPPERCASE.
3080    Uppercase,
3081    /// to lowercase.
3082    Lowercase,
3083    /// Custom transform function.
3084    Custom(Arc<dyn Fn(&Txt) -> Cow<Txt> + Send + Sync>),
3085}
3086impl TextTransformFn {
3087    /// Apply the text transform.
3088    ///
3089    /// Returns [`Cow::Owned`] if the text was changed.
3090    pub fn transform<'t>(&self, text: &'t Txt) -> Cow<'t, Txt> {
3091        match self {
3092            TextTransformFn::None => Cow::Borrowed(text),
3093            TextTransformFn::Uppercase => {
3094                if text.chars().any(|c| !c.is_uppercase()) {
3095                    Cow::Owned(text.to_uppercase().into())
3096                } else {
3097                    Cow::Borrowed(text)
3098                }
3099            }
3100            TextTransformFn::Lowercase => {
3101                if text.chars().any(|c| !c.is_lowercase()) {
3102                    Cow::Owned(text.to_lowercase().into())
3103                } else {
3104                    Cow::Borrowed(text)
3105                }
3106            }
3107            TextTransformFn::Custom(fn_) => fn_(text),
3108        }
3109    }
3110
3111    /// New [`Custom`](Self::Custom).
3112    pub fn custom(fn_: impl Fn(&Txt) -> Cow<Txt> + Send + Sync + 'static) -> Self {
3113        TextTransformFn::Custom(Arc::new(fn_))
3114    }
3115}
3116impl fmt::Debug for TextTransformFn {
3117    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3118        if f.alternate() {
3119            write!(f, "TextTransformFn::")?;
3120        }
3121        match self {
3122            TextTransformFn::None => write!(f, "None"),
3123            TextTransformFn::Uppercase => write!(f, "Uppercase"),
3124            TextTransformFn::Lowercase => write!(f, "Lowercase"),
3125            TextTransformFn::Custom(_) => write!(f, "Custom"),
3126        }
3127    }
3128}
3129impl PartialEq for TextTransformFn {
3130    fn eq(&self, other: &Self) -> bool {
3131        match (self, other) {
3132            (Self::Custom(l0), Self::Custom(r0)) => Arc::ptr_eq(l0, r0),
3133            _ => core::mem::discriminant(self) == core::mem::discriminant(other),
3134        }
3135    }
3136}
3137
3138/// Text white space transform.
3139#[derive(Copy, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
3140pub enum WhiteSpace {
3141    /// Text is not changed, all white spaces and line breaks are preserved.
3142    Preserve,
3143    /// Replace white spaces with a single `U+0020 SPACE` and trim lines. Line breaks are preserved.
3144    Merge,
3145    /// Replace white spaces and line breaks with `U+0020 SPACE` and trim the text.
3146    MergeAll,
3147}
3148impl Default for WhiteSpace {
3149    /// [`WhiteSpace::Preserve`].
3150    fn default() -> Self {
3151        WhiteSpace::Preserve
3152    }
3153}
3154impl WhiteSpace {
3155    /// Transform the white space of the text.
3156    ///
3157    /// Returns [`Cow::Owned`] if the text was changed.
3158    pub fn transform(self, text: &Txt) -> Cow<Txt> {
3159        match self {
3160            WhiteSpace::Preserve => Cow::Borrowed(text),
3161            WhiteSpace::Merge => {
3162                let is_white_space = |c: char| c.is_whitespace() && !"\n\r\u{85}".contains(c);
3163                let t = text.trim_matches(is_white_space);
3164
3165                let mut prev_space = false;
3166                for c in t.chars() {
3167                    if is_white_space(c) {
3168                        if prev_space || c != '\u{20}' {
3169                            // collapse spaces or replace non ' ' white space with ' '.
3170
3171                            let mut r = String::new();
3172                            let mut sep = "";
3173                            for part in t.split(is_white_space).filter(|s| !s.is_empty()) {
3174                                r.push_str(sep);
3175                                r.push_str(part);
3176                                sep = "\u{20}";
3177                            }
3178                            return Cow::Owned(Txt::from_str(&r));
3179                        } else {
3180                            prev_space = true;
3181                        }
3182                    } else {
3183                        prev_space = false;
3184                    }
3185                }
3186
3187                if t.len() != text.len() {
3188                    Cow::Owned(Txt::from_str(t))
3189                } else {
3190                    Cow::Borrowed(text)
3191                }
3192            }
3193            WhiteSpace::MergeAll => {
3194                let t = text.trim();
3195
3196                let mut prev_space = false;
3197                for c in t.chars() {
3198                    if c.is_whitespace() {
3199                        if prev_space || c != '\u{20}' {
3200                            // collapse spaces or replace non ' ' white space with ' '.
3201
3202                            let mut r = String::new();
3203                            let mut sep = "";
3204                            for part in t.split_whitespace() {
3205                                r.push_str(sep);
3206                                r.push_str(part);
3207                                sep = "\u{20}";
3208                            }
3209                            return Cow::Owned(Txt::from_str(&r));
3210                        } else {
3211                            prev_space = true;
3212                        }
3213                    } else {
3214                        prev_space = false;
3215                    }
3216                }
3217
3218                if t.len() != text.len() {
3219                    Cow::Owned(Txt::from_str(t))
3220                } else {
3221                    Cow::Borrowed(text)
3222                }
3223            }
3224        }
3225    }
3226}
3227impl fmt::Debug for WhiteSpace {
3228    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3229        if f.alternate() {
3230            write!(f, "WhiteSpace::")?;
3231        }
3232        match self {
3233            WhiteSpace::Preserve => write!(f, "Preserve"),
3234            WhiteSpace::Merge => write!(f, "Merge"),
3235            WhiteSpace::MergeAll => write!(f, "MergeAll"),
3236        }
3237    }
3238}
3239
3240/// Defines an insert offset in a shaped text.
3241#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
3242pub struct CaretIndex {
3243    /// Char byte offset in the full text.
3244    ///
3245    /// This index can be computed using the [`SegmentedText`].
3246    pub index: usize,
3247    /// Line index in the shaped text.
3248    ///
3249    /// This value is only used to disambiguate between the *end* of a wrap and
3250    /// the *start* of the next, the text itself does not have any line
3251    /// break but visually the user interacts with two lines. Note that this
3252    /// counts wrap lines, and that this value is not required to define a valid
3253    /// CaretIndex.
3254    ///
3255    /// This index can be computed using the [`ShapedText::snap_caret_line`].
3256    pub line: usize,
3257}
3258
3259impl PartialEq for CaretIndex {
3260    fn eq(&self, other: &Self) -> bool {
3261        self.index == other.index
3262    }
3263}
3264impl Eq for CaretIndex {}
3265impl CaretIndex {
3266    /// First position.
3267    pub const ZERO: CaretIndex = CaretIndex { index: 0, line: 0 };
3268}
3269impl PartialOrd for CaretIndex {
3270    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
3271        Some(self.cmp(other))
3272    }
3273}
3274impl Ord for CaretIndex {
3275    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
3276        self.index.cmp(&other.index)
3277    }
3278}
3279
3280/// Reasons why a loader might fail to load a font.
3281#[derive(Debug, Clone)]
3282pub enum FontLoadingError {
3283    /// The data was of a format the loader didn't recognize.
3284    UnknownFormat,
3285    /// Attempted to load an invalid index in a TrueType or OpenType font collection.
3286    ///
3287    /// For example, if a `.ttc` file has 2 fonts in it, and you ask for the 5th one, you'll get
3288    /// this error.
3289    NoSuchFontInCollection,
3290    /// Attempted to load a malformed or corrupted font.
3291    Parse(ttf_parser::FaceParsingError),
3292    /// Attempted to load a font from the filesystem, but there is no filesystem (e.g. in
3293    /// WebAssembly).
3294    NoFilesystem,
3295    /// A disk or similar I/O error occurred while attempting to load the font.
3296    Io(Arc<std::io::Error>),
3297}
3298impl PartialEq for FontLoadingError {
3299    fn eq(&self, other: &Self) -> bool {
3300        match (self, other) {
3301            (Self::Io(l0), Self::Io(r0)) => Arc::ptr_eq(l0, r0),
3302            _ => core::mem::discriminant(self) == core::mem::discriminant(other),
3303        }
3304    }
3305}
3306impl From<std::io::Error> for FontLoadingError {
3307    fn from(error: std::io::Error) -> FontLoadingError {
3308        Self::Io(Arc::new(error))
3309    }
3310}
3311impl fmt::Display for FontLoadingError {
3312    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3313        match self {
3314            Self::UnknownFormat => write!(f, "unknown format"),
3315            Self::NoSuchFontInCollection => write!(f, "no such font in the collection"),
3316            Self::NoFilesystem => write!(f, "no filesystem present"),
3317            Self::Parse(e) => fmt::Display::fmt(e, f),
3318            Self::Io(e) => fmt::Display::fmt(e, f),
3319        }
3320    }
3321}
3322impl std::error::Error for FontLoadingError {
3323    fn cause(&self) -> Option<&dyn std::error::Error> {
3324        match self {
3325            FontLoadingError::Parse(e) => Some(e),
3326            FontLoadingError::Io(e) => Some(e),
3327            _ => None,
3328        }
3329    }
3330}
3331
3332#[cfg(test)]
3333mod tests {
3334    use zng_app::APP;
3335
3336    use super::*;
3337
3338    #[test]
3339    fn generic_fonts_default() {
3340        let _app = APP.minimal().extend(FontManager::default()).run_headless(false);
3341
3342        assert_eq!(FontName::sans_serif(), GenericFonts {}.sans_serif(&lang!(und)))
3343    }
3344
3345    #[test]
3346    fn generic_fonts_fallback() {
3347        let _app = APP.minimal().extend(FontManager::default()).run_headless(false);
3348
3349        assert_eq!(FontName::sans_serif(), GenericFonts {}.sans_serif(&lang!(en_US)));
3350        assert_eq!(FontName::sans_serif(), GenericFonts {}.sans_serif(&lang!(es)));
3351    }
3352
3353    #[test]
3354    fn generic_fonts_get1() {
3355        let mut app = APP.minimal().extend(FontManager::default()).run_headless(false);
3356        GenericFonts {}.set_sans_serif(lang!(en_US), "Test Value");
3357        app.update(false).assert_wait();
3358
3359        assert_eq!(&GenericFonts {}.sans_serif(&lang!("en-US")), "Test Value");
3360        assert_eq!(&GenericFonts {}.sans_serif(&lang!("en")), "Test Value");
3361    }
3362
3363    #[test]
3364    fn generic_fonts_get2() {
3365        let mut app = APP.minimal().extend(FontManager::default()).run_headless(false);
3366        GenericFonts {}.set_sans_serif(lang!(en), "Test Value");
3367        app.update(false).assert_wait();
3368
3369        assert_eq!(&GenericFonts {}.sans_serif(&lang!("en-US")), "Test Value");
3370        assert_eq!(&GenericFonts {}.sans_serif(&lang!("en")), "Test Value");
3371    }
3372
3373    #[test]
3374    fn generic_fonts_get_best() {
3375        let mut app = APP.minimal().extend(FontManager::default()).run_headless(false);
3376        GenericFonts {}.set_sans_serif(lang!(en), "Test Value");
3377        GenericFonts {}.set_sans_serif(lang!(en_US), "Best");
3378        app.update(false).assert_wait();
3379
3380        assert_eq!(&GenericFonts {}.sans_serif(&lang!("en-US")), "Best");
3381        assert_eq!(&GenericFonts {}.sans_serif(&lang!("en")), "Test Value");
3382        assert_eq!(&GenericFonts {}.sans_serif(&lang!("und")), "sans-serif");
3383    }
3384
3385    #[test]
3386    fn generic_fonts_get_no_lang_match() {
3387        let mut app = APP.minimal().extend(FontManager::default()).run_headless(false);
3388        GenericFonts {}.set_sans_serif(lang!(es_US), "Test Value");
3389        app.update(false).assert_wait();
3390
3391        assert_eq!(&GenericFonts {}.sans_serif(&lang!("en-US")), "sans-serif");
3392        assert_eq!(&GenericFonts {}.sans_serif(&lang!("es")), "Test Value");
3393    }
3394}