Skip to main content

zng_ext_font/
lib.rs

1#![doc(html_favicon_url = "https://zng-ui.github.io/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://zng-ui.github.io/res/zng-logo.png")]
3//!
4//! Font loading, text segmenting and shaping.
5//!
6//! # Services
7//!
8//! Services this extension provides:
9//!
10//! * [`FONTS`] - Service that finds and loads fonts.
11//! * [`HYPHENATION`] - Service that loads and applies hyphenation dictionaries.
12//!
13//! # Events
14//!
15//! Events this extension provides:
16//!
17//! * [`FONT_CHANGED_EVENT`] - Font config or system fonts changed.
18//!
19//! # Crate
20//!
21#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
22// suppress nag about very simple boxed closure signatures.
23#![expect(clippy::type_complexity)]
24#![warn(unused_extern_crates)]
25#![warn(missing_docs)]
26#![cfg_attr(not(ipc), allow(unused))]
27
28use font_features::RFontVariations;
29use hashbrown::{HashMap, HashSet};
30use std::{borrow::Cow, fmt, io, ops, path::PathBuf, slice::SliceIndex, sync::Arc};
31#[cfg(not(any(target_arch = "wasm32", target_os = "android")))]
32use zng_task::channel::WeakIpcBytes;
33
34#[macro_use]
35extern crate bitflags;
36
37pub mod font_features;
38
39mod query_util;
40
41mod emoji_util;
42pub use emoji_util::*;
43
44mod ligature_util;
45use ligature_util::*;
46
47mod unicode_bidi_util;
48
49mod segmenting;
50pub use segmenting::*;
51
52mod shaping;
53pub use shaping::*;
54use zng_clone_move::{async_clmv, clmv};
55
56mod hyphenation;
57pub use self::hyphenation::*;
58
59mod unit;
60pub use unit::*;
61
62use parking_lot::{Mutex, RwLock};
63use pastey::paste;
64use zng_app::{
65    event::{event, event_args},
66    render::FontSynthesis,
67    update::UPDATES,
68    view_process::{
69        VIEW_PROCESS_INITED_EVENT, ViewRenderer,
70        raw_events::{RAW_FONT_AA_CHANGED_EVENT, RAW_FONT_CHANGED_EVENT},
71    },
72};
73use zng_app_context::app_local;
74use zng_ext_l10n::{Lang, LangMap, lang};
75use zng_layout::unit::{
76    ByteUnits as _, EQ_GRANULARITY, EQ_GRANULARITY_100, Factor, FactorPercent, Px, PxPoint, PxRect, PxSize, TimeUnits as _, about_eq,
77    about_eq_hash, about_eq_ord, euclid,
78};
79use zng_task::{self as task, channel::IpcBytes};
80use zng_txt::Txt;
81use zng_var::{IntoVar, ResponseVar, Var, animation::Transitionable, const_var, impl_from_and_into_var, response_done_var, response_var};
82use zng_view_api::{config::FontAntiAliasing, font::IpcFontBytes};
83
84/// Font family name.
85///
86/// A possible value for the `font_family` property.
87///
88/// # Case Insensitive
89///
90/// Font family names are case-insensitive. `"Arial"` and `"ARIAL"` are equal and have the same hash.
91#[derive(Clone)]
92pub struct FontName {
93    txt: Txt,
94    is_ascii: bool,
95}
96impl fmt::Debug for FontName {
97    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98        if f.alternate() {
99            f.debug_struct("FontName")
100                .field("txt", &self.txt)
101                .field("is_ascii", &self.is_ascii)
102                .finish()
103        } else {
104            write!(f, "{:?}", self.txt)
105        }
106    }
107}
108impl PartialEq for FontName {
109    fn eq(&self, other: &Self) -> bool {
110        self.unicase() == other.unicase()
111    }
112}
113impl Eq for FontName {}
114impl PartialEq<str> for FontName {
115    fn eq(&self, other: &str) -> bool {
116        self.unicase() == unicase::UniCase::<&str>::from(other)
117    }
118}
119impl std::hash::Hash for FontName {
120    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
121        std::hash::Hash::hash(&self.unicase(), state)
122    }
123}
124impl Ord for FontName {
125    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
126        if self == other {
127            // case insensitive eq
128            return std::cmp::Ordering::Equal;
129        }
130        self.txt.cmp(&other.txt)
131    }
132}
133impl PartialOrd for FontName {
134    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
135        Some(self.cmp(other))
136    }
137}
138impl FontName {
139    fn unicase(&self) -> unicase::UniCase<&str> {
140        if self.is_ascii {
141            unicase::UniCase::ascii(self)
142        } else {
143            unicase::UniCase::unicode(self)
144        }
145    }
146
147    /// New font name from `&'static str`.
148    pub const fn from_static(name: &'static str) -> Self {
149        FontName {
150            txt: Txt::from_static(name),
151            is_ascii: {
152                // str::is_ascii is not const
153                let name_bytes = name.as_bytes();
154                let mut i = name_bytes.len();
155                let mut is_ascii = true;
156                while i > 0 {
157                    i -= 1;
158                    if !name_bytes[i].is_ascii() {
159                        is_ascii = false;
160                        break;
161                    }
162                }
163                is_ascii
164            },
165        }
166    }
167
168    /// New font name.
169    ///
170    /// Note that the inner name value is a [`Txt`] so you can define a font name using `&'static str` or `String`.
171    ///
172    /// Font names are case insensitive but the input casing is preserved, this casing shows during display and in
173    /// the value of [`name`](Self::name).
174    ///
175    /// [`Txt`]: zng_txt::Txt
176    pub fn new(name: impl Into<Txt>) -> Self {
177        let txt = name.into();
178        FontName {
179            is_ascii: txt.is_ascii(),
180            txt,
181        }
182    }
183
184    /// New "serif" font name.
185    ///
186    /// Serif fonts represent the formal text style for a script.
187    ///
188    /// The font is resolved to the [`GenericFonts::serif`] value.
189    pub fn serif() -> Self {
190        Self::new("serif")
191    }
192
193    /// New "sans-serif" font name.
194    ///
195    /// Glyphs in sans-serif fonts, are generally low contrast (vertical and horizontal stems have close to the same thickness)
196    /// and have stroke endings that are plain — without any flaring, cross stroke, or other ornamentation.
197    ///
198    /// The font is resolved to the [`GenericFonts::sans_serif`] value.
199    pub fn sans_serif() -> Self {
200        Self::new("sans-serif")
201    }
202
203    /// New "monospace" font name.
204    ///
205    /// The sole criterion of a monospace font is that all glyphs have the same fixed width.
206    ///
207    /// The font is resolved to the [`GenericFonts::monospace`] value.
208    pub fn monospace() -> Self {
209        Self::new("monospace")
210    }
211
212    /// New "cursive" font name.
213    ///
214    /// Glyphs in cursive fonts generally use a more informal script style, and the result looks more
215    /// like handwritten pen or brush writing than printed letter-work.
216    ///    
217    /// The font is resolved to the [`GenericFonts::cursive`] value.
218    pub fn cursive() -> Self {
219        Self::new("cursive")
220    }
221
222    /// New "fantasy" font name.
223    ///
224    /// Fantasy fonts are primarily decorative or expressive fonts that contain decorative or expressive representations of characters.
225    ///
226    /// The font is resolved to the [`GenericFonts::fantasy`] value.
227    pub fn fantasy() -> Self {
228        Self::new("fantasy")
229    }
230
231    /// New "system-ui" font name.
232    ///
233    /// This represents the default UI font defined by for the given operating system and language.
234    ///
235    /// The font is resolved to the [`GenericFonts::system_ui`] value.
236    pub fn system_ui() -> Self {
237        Self::new("system-ui")
238    }
239
240    /// Reference the font name string.
241    pub fn name(&self) -> &str {
242        &self.txt
243    }
244
245    /// Unwraps into a [`Txt`].
246    ///
247    /// [`Txt`]: zng_txt::Txt
248    pub fn into_text(self) -> Txt {
249        self.txt
250    }
251}
252impl_from_and_into_var! {
253    fn from(s: &'static str) -> FontName {
254        FontName::new(s)
255    }
256    fn from(s: String) -> FontName {
257        FontName::new(s)
258    }
259    fn from(s: Cow<'static, str>) -> FontName {
260        FontName::new(s)
261    }
262    fn from(f: FontName) -> Txt {
263        f.into_text()
264    }
265}
266impl fmt::Display for FontName {
267    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
268        f.write_str(self.name())
269    }
270}
271impl std::ops::Deref for FontName {
272    type Target = str;
273
274    fn deref(&self) -> &Self::Target {
275        self.txt.deref()
276    }
277}
278impl AsRef<str> for FontName {
279    fn as_ref(&self) -> &str {
280        self.txt.as_ref()
281    }
282}
283impl serde::Serialize for FontName {
284    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
285    where
286        S: serde::Serializer,
287    {
288        self.txt.serialize(serializer)
289    }
290}
291impl<'de> serde::Deserialize<'de> for FontName {
292    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
293    where
294        D: serde::Deserializer<'de>,
295    {
296        Txt::deserialize(deserializer).map(FontName::new)
297    }
298}
299
300/// A list of [font names](FontName) in priority order.
301///
302/// # Examples
303///
304/// This type is usually initialized using conversion:
305///
306/// ```
307/// # use zng_ext_font::*;
308/// fn foo(font_names: impl Into<FontNames>) {}
309///
310/// foo(["Arial", "sans-serif", "monospace"]);
311/// ```
312///
313/// You can also use the specialized [`push`](Self::push) that converts:
314///
315/// ```
316/// # use zng_ext_font::*;
317/// let user_preference = "Comic Sans".to_owned();
318///
319/// let mut names = FontNames::empty();
320/// names.push(user_preference);
321/// names.push("Arial");
322/// names.extend(FontNames::default());
323/// ```
324///
325/// # Default
326///
327/// The default value is the [`system_ui`](FontName::system_ui).
328#[derive(Eq, PartialEq, Hash, Clone, serde::Serialize, serde::Deserialize)]
329#[serde(transparent)]
330pub struct FontNames(pub Vec<FontName>);
331impl FontNames {
332    /// Empty list.
333    pub fn empty() -> Self {
334        FontNames(vec![])
335    }
336
337    /// Returns the default UI font names for Windows.
338    #[deprecated = "use `FONTS.generics().system_ui`"]
339    pub fn windows_ui(lang: &Lang) -> Self {
340        FONTS.generics().system_ui(lang)
341    }
342
343    /// Returns the default UI font names for MacOS/iOS.
344    #[deprecated = "use `FONTS.generics().system_ui`"]
345    pub fn mac_ui(lang: &Lang) -> Self {
346        FONTS.generics().system_ui(lang)
347    }
348
349    /// Returns the default UI font names for Linux.
350    #[deprecated = "use `FONTS.generics().system_ui`"]
351    pub fn linux_ui(lang: &Lang) -> Self {
352        FONTS.generics().system_ui(lang)
353    }
354
355    /// Returns the default UI font names for the current operating system.
356    #[deprecated = "use `FONTS.generics().system_ui`"]
357    pub fn system_ui(lang: &Lang) -> Self {
358        FONTS.generics().system_ui(lang)
359    }
360
361    /// Push a font name from any type that converts to [`FontName`].
362    pub fn push(&mut self, font_name: impl Into<FontName>) {
363        self.0.push(font_name.into())
364    }
365}
366impl Default for FontNames {
367    fn default() -> Self {
368        FontName::system_ui().into()
369    }
370}
371impl fmt::Debug for FontNames {
372    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
373        if f.alternate() {
374            f.debug_tuple("FontNames").field(&self.0).finish()
375        } else if self.0.is_empty() {
376            write!(f, "[]")
377        } else if self.0.len() == 1 {
378            write!(f, "{:?}", self.0[0])
379        } else {
380            write!(f, "[{:?}, ", self.0[0])?;
381            for name in &self.0[1..] {
382                write!(f, "{name:?}, ")?;
383            }
384            write!(f, "]")
385        }
386    }
387}
388impl fmt::Display for FontNames {
389    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
390        let mut iter = self.0.iter();
391
392        if let Some(name) = iter.next() {
393            write!(f, "{name}")?;
394            for name in iter {
395                write!(f, ", {name}")?;
396            }
397        }
398
399        Ok(())
400    }
401}
402impl_from_and_into_var! {
403    fn from(font_name: &'static str) -> FontNames {
404        FontNames(vec![FontName::new(font_name)])
405    }
406
407    fn from(font_name: String) -> FontNames {
408        FontNames(vec![FontName::new(font_name)])
409    }
410
411    fn from(font_name: Txt) -> FontNames {
412        FontNames(vec![FontName::new(font_name)])
413    }
414
415    fn from(font_names: Vec<FontName>) -> FontNames {
416        FontNames(font_names)
417    }
418
419    fn from(font_names: Vec<&'static str>) -> FontNames {
420        FontNames(font_names.into_iter().map(FontName::new).collect())
421    }
422
423    fn from(font_names: Vec<String>) -> FontNames {
424        FontNames(font_names.into_iter().map(FontName::new).collect())
425    }
426
427    fn from(font_name: FontName) -> FontNames {
428        FontNames(vec![font_name])
429    }
430}
431impl ops::Deref for FontNames {
432    type Target = Vec<FontName>;
433
434    fn deref(&self) -> &Self::Target {
435        &self.0
436    }
437}
438impl ops::DerefMut for FontNames {
439    fn deref_mut(&mut self) -> &mut Self::Target {
440        &mut self.0
441    }
442}
443impl std::iter::Extend<FontName> for FontNames {
444    fn extend<T: IntoIterator<Item = FontName>>(&mut self, iter: T) {
445        self.0.extend(iter)
446    }
447}
448impl IntoIterator for FontNames {
449    type Item = FontName;
450
451    type IntoIter = std::vec::IntoIter<FontName>;
452
453    fn into_iter(self) -> Self::IntoIter {
454        self.0.into_iter()
455    }
456}
457impl<const N: usize> From<[FontName; N]> for FontNames {
458    fn from(font_names: [FontName; N]) -> Self {
459        FontNames(font_names.into())
460    }
461}
462impl<const N: usize> IntoVar<FontNames> for [FontName; N] {
463    fn into_var(self) -> Var<FontNames> {
464        const_var(self.into())
465    }
466}
467impl<const N: usize> From<[&'static str; N]> for FontNames {
468    fn from(font_names: [&'static str; N]) -> Self {
469        FontNames(font_names.into_iter().map(FontName::new).collect())
470    }
471}
472impl<const N: usize> IntoVar<FontNames> for [&'static str; N] {
473    fn into_var(self) -> Var<FontNames> {
474        const_var(self.into())
475    }
476}
477impl<const N: usize> From<[String; N]> for FontNames {
478    fn from(font_names: [String; N]) -> Self {
479        FontNames(font_names.into_iter().map(FontName::new).collect())
480    }
481}
482impl<const N: usize> IntoVar<FontNames> for [String; N] {
483    fn into_var(self) -> Var<FontNames> {
484        const_var(self.into())
485    }
486}
487impl<const N: usize> From<[Txt; N]> for FontNames {
488    fn from(font_names: [Txt; N]) -> Self {
489        FontNames(font_names.into_iter().map(FontName::new).collect())
490    }
491}
492impl<const N: usize> IntoVar<FontNames> for [Txt; N] {
493    fn into_var(self) -> Var<FontNames> {
494        const_var(self.into())
495    }
496}
497
498event! {
499    /// Change in [`FONTS`] that may cause a font query to now give
500    /// a different result.
501    ///
502    /// # Cache
503    ///
504    /// Every time this event updates the font cache is cleared. Meaning that even
505    /// if the query returns the same font it will be a new reference.
506    ///
507    /// Fonts only unload when all references to then are dropped, so you can still continue using
508    /// old references if you don't want to monitor this event.
509    pub static FONT_CHANGED_EVENT: FontChangedArgs;
510}
511
512event_args! {
513    /// [`FONT_CHANGED_EVENT`] arguments.
514    pub struct FontChangedArgs {
515        /// The change that happened.
516        pub change: FontChange,
517
518        ..
519
520        /// Broadcast to all widgets.
521        fn is_in_target(&self, id: WidgetId) -> bool {
522            true
523        }
524    }
525}
526
527/// Possible changes in a [`FontChangedArgs`].
528#[derive(Clone, Debug, PartialEq)]
529pub enum FontChange {
530    /// OS fonts change.
531    ///
532    /// Currently this is only supported in Microsoft Windows.
533    SystemFonts,
534
535    /// Custom fonts change caused by call to [`FONTS.register`] or [`FONTS.unregister`].
536    ///
537    /// [`FONTS.register`]: FONTS::register
538    /// [`FONTS.unregister`]: FONTS::unregister
539    CustomFonts,
540
541    /// Custom request caused by call to [`FONTS.refresh`].
542    ///
543    /// [`FONTS.refresh`]: FONTS::refresh
544    Refresh,
545
546    /// One of the [`GenericFonts`] was set for the language.
547    ///
548    /// The font name is one of [`FontName`] generic names.
549    ///
550    /// [`GenericFonts`]: struct@GenericFonts
551    GenericFont(FontName, Lang),
552
553    /// A new [fallback](GenericFonts::fallback) font was set for the language.
554    Fallback(Lang),
555}
556
557app_local! {
558    static FONTS_SV: FontsService = FontsService::new();
559}
560
561struct FontsService {
562    loader: FontFaceLoader,
563}
564impl FontsService {
565    fn new() -> Self {
566        let s = FontsService {
567            loader: FontFaceLoader::new(),
568        };
569
570        // propagate view-process notification
571        RAW_FONT_CHANGED_EVENT
572            .hook(|args| {
573                FONT_CHANGED_EVENT.notify(FontChangedArgs::new(
574                    args.timestamp,
575                    args.propagation.clone(),
576                    FontChange::SystemFonts,
577                ));
578                true
579            })
580            .perm();
581
582        // FONTS service can also fire this event.
583        FONT_CHANGED_EVENT
584            .hook(|_| {
585                let mut s = FONTS_SV.write();
586                s.loader.on_refresh();
587                true
588            })
589            .perm();
590
591        // handle respawn
592        VIEW_PROCESS_INITED_EVENT
593            .hook(|args| {
594                if args.is_respawn {
595                    FONTS_SV.write().loader.on_view_process_respawn();
596                }
597                true
598            })
599            .perm();
600
601        s
602    }
603}
604
605/// Font loading, custom fonts and app font configuration.
606pub struct FONTS;
607impl FONTS {
608    /// Clear cache and notify `Refresh` in [`FONT_CHANGED_EVENT`].
609    ///
610    /// See the event documentation for more information.
611    pub fn refresh(&self) {
612        FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::Refresh));
613    }
614
615    /// Remove all unused fonts from cache.
616    pub fn prune(&self) {
617        UPDATES.once_update("FONTS.prune", move || {
618            FONTS_SV.write().loader.on_prune();
619        });
620    }
621
622    /// Actual name of generic fonts.
623    pub fn generics(&self) -> &'static GenericFonts {
624        &GenericFonts {}
625    }
626
627    /// Load and register a custom font.
628    ///
629    /// If the font loads correctly a [`FONT_CHANGED_EVENT`] notification is scheduled.
630    /// Fonts sourced from a file are not monitored for changes, you can *reload* the font
631    /// by calling `register` again with the same font name.
632    ///
633    /// The returned response will update once when the font finishes loading with the new font.
634    /// At minimum the new font will be available on the next update.
635    pub fn register(&self, custom_font: CustomFont) -> ResponseVar<Result<FontFace, FontLoadingError>> {
636        // start loading
637        let resp = task::respond(FontFace::load_custom(custom_font));
638
639        // modify loader.custom_fonts at the end of whatever update is happening when finishes loading.
640        resp.hook(|args| {
641            if let Some(done) = args.value().done() {
642                if let Ok(face) = done {
643                    let mut fonts = FONTS_SV.write();
644                    let family = fonts.loader.custom_fonts.entry(face.0.family_name.clone()).or_default();
645                    let existing = family
646                        .iter()
647                        .position(|f| f.0.weight == face.0.weight && f.0.style == face.0.style && f.0.stretch == face.0.stretch);
648
649                    if let Some(i) = existing {
650                        family[i] = face.clone();
651                    } else {
652                        family.push(face.clone());
653                    }
654
655                    FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::CustomFonts));
656                }
657                false
658            } else {
659                true
660            }
661        })
662        .perm();
663
664        resp
665    }
666
667    /// Removes a custom font family. If the font faces are not in use it is also unloaded.
668    ///
669    /// Returns a response var that updates once with a value that indicates if any custom font was removed.
670    pub fn unregister(&self, custom_family: FontName) -> ResponseVar<bool> {
671        let (responder, response) = response_var();
672
673        UPDATES.once_update("FONTS.unregister", move || {
674            let mut fonts = FONTS_SV.write();
675            let r = if let Some(removed) = fonts.loader.custom_fonts.remove(&custom_family) {
676                // cut circular reference so that when the last font ref gets dropped
677                // this font face also gets dropped. Also tag the font as unregistered
678                // so it does not create further circular references.
679                for removed in removed {
680                    removed.on_refresh();
681                }
682
683                true
684            } else {
685                false
686            };
687            responder.respond(r);
688
689            if r {
690                FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::CustomFonts));
691            }
692        });
693
694        response
695    }
696
697    /// Gets a font list that best matches the query.
698    pub fn list(
699        &self,
700        families: &[FontName],
701        style: FontStyle,
702        weight: FontWeight,
703        stretch: FontStretch,
704        lang: &Lang,
705    ) -> ResponseVar<FontFaceList> {
706        // try with shared lock
707        if let Some(cached) = FONTS_SV.read().loader.try_list(families, style, weight, stretch, lang) {
708            tracing::trace!("font list ({families:?} {style:?} {weight:?} {stretch:?} {lang:?}) found cached");
709            return cached;
710        }
711        tracing::trace!("font list ({families:?} {style:?} {weight:?} {stretch:?} {lang:?}) not cached, load");
712        // begin load with exclusive lock (cache is tried again in `load`)
713        FONTS_SV.write().loader.load_list(families, style, weight, stretch, lang)
714    }
715
716    /// Find a single font face that best matches the query.
717    pub fn find(
718        &self,
719        family: &FontName,
720        style: FontStyle,
721        weight: FontWeight,
722        stretch: FontStretch,
723        lang: &Lang,
724    ) -> ResponseVar<Option<FontFace>> {
725        let resolved = GenericFonts {}.resolve(family, lang);
726        let family = resolved.as_ref().unwrap_or(family);
727
728        // try with shared lock
729        if let Some(cached) = FONTS_SV.read().loader.try_resolved(family, style, weight, stretch) {
730            return cached;
731        }
732        // begin load with exclusive lock (cache is tried again in `load`)
733        FONTS_SV.write().loader.load_resolved(family, style, weight, stretch)
734    }
735
736    /// Find a single font face with all normal properties.
737    pub fn normal(&self, family: &FontName, lang: &Lang) -> ResponseVar<Option<FontFace>> {
738        self.find(family, FontStyle::Normal, FontWeight::NORMAL, FontStretch::NORMAL, lang)
739    }
740
741    /// Find a single font face with italic style, normal weight and stretch.
742    pub fn italic(&self, family: &FontName, lang: &Lang) -> ResponseVar<Option<FontFace>> {
743        self.find(family, FontStyle::Italic, FontWeight::NORMAL, FontStretch::NORMAL, lang)
744    }
745
746    /// Find a single font face with bold weight, normal style and stretch.
747    pub fn bold(&self, family: &FontName, lang: &Lang) -> ResponseVar<Option<FontFace>> {
748        self.find(family, FontStyle::Normal, FontWeight::BOLD, FontStretch::NORMAL, lang)
749    }
750
751    /// Gets all [registered](Self::register) font families.
752    pub fn custom_fonts(&self) -> Vec<FontName> {
753        FONTS_SV.read().loader.custom_fonts.keys().cloned().collect()
754    }
755
756    /// Query all font families available in the system.
757    ///
758    /// Note that the variable will only update once with the query result, this is not a live view.
759    pub fn system_fonts(&self) -> ResponseVar<Vec<FontName>> {
760        query_util::system_all()
761    }
762
763    /// Gets the system font anti-aliasing config as a read-only var.
764    ///
765    /// The variable updates when the system config changes.
766    pub fn system_font_aa(&self) -> Var<FontAntiAliasing> {
767        RAW_FONT_AA_CHANGED_EVENT.var_map(|a| Some(a.aa), || FontAntiAliasing::Default)
768    }
769}
770
771impl<'a> From<ttf_parser::Face<'a>> for FontFaceMetrics {
772    fn from(f: ttf_parser::Face<'a>) -> Self {
773        let underline = f
774            .underline_metrics()
775            .unwrap_or(ttf_parser::LineMetrics { position: 0, thickness: 0 });
776        FontFaceMetrics {
777            units_per_em: f.units_per_em() as _,
778            ascent: f.ascender() as f32,
779            descent: f.descender() as f32,
780            line_gap: f.line_gap() as f32,
781            underline_position: underline.position as f32,
782            underline_thickness: underline.thickness as f32,
783            cap_height: f.capital_height().unwrap_or(0) as f32,
784            x_height: f.x_height().unwrap_or(0) as f32,
785            bounds: euclid::rect(
786                f.global_bounding_box().x_min as f32,
787                f.global_bounding_box().x_max as f32,
788                f.global_bounding_box().width() as f32,
789                f.global_bounding_box().height() as f32,
790            ),
791        }
792    }
793}
794
795#[derive(PartialEq, Eq, Hash)]
796struct FontInstanceKey(Px, Box<[(ttf_parser::Tag, i32)]>);
797impl FontInstanceKey {
798    /// Returns the key.
799    pub fn new(size: Px, variations: &[rustybuzz::Variation]) -> Self {
800        let variations_key: Vec<_> = variations.iter().map(|p| (p.tag, (p.value * 1000.0) as i32)).collect();
801        FontInstanceKey(size, variations_key.into_boxed_slice())
802    }
803}
804
805/// A font face selected from a font family.
806///
807/// Usually this is part of a [`FontList`] that can be requested from
808/// the [`FONTS`] service.
809///
810/// This type is a shared reference to the font data, cloning it is cheap.
811#[derive(Clone)]
812pub struct FontFace(Arc<LoadedFontFace>);
813struct LoadedFontFace {
814    data: FontBytes,
815    face_index: u32,
816    display_name: FontName,
817    family_name: FontName,
818    postscript_name: Option<Txt>,
819    style: FontStyle,
820    weight: FontWeight,
821    stretch: FontStretch,
822    metrics: FontFaceMetrics,
823    lig_carets: LigatureCaretList,
824    flags: FontFaceFlags,
825    m: Mutex<FontFaceMut>,
826}
827bitflags! {
828    #[derive(Debug, Clone, Copy)]
829    struct FontFaceFlags: u8 {
830        const IS_MONOSPACE = 0b0000_0001;
831        const HAS_LIGATURES = 0b0000_0010;
832        const HAS_RASTER_IMAGES = 0b0000_0100;
833        const HAS_SVG_IMAGES = 0b0000_1000;
834    }
835}
836struct FontFaceMut {
837    instances: HashMap<FontInstanceKey, Font>,
838    render_ids: Vec<RenderFontFace>,
839    unregistered: bool,
840}
841
842impl fmt::Debug for FontFace {
843    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
844        let m = self.0.m.lock();
845        f.debug_struct("FontFace")
846            .field("display_name", &self.0.display_name)
847            .field("family_name", &self.0.family_name)
848            .field("postscript_name", &self.0.postscript_name)
849            .field("flags", &self.0.flags)
850            .field("style", &self.0.style)
851            .field("weight", &self.0.weight)
852            .field("stretch", &self.0.stretch)
853            .field("metrics", &self.0.metrics)
854            .field("instances.len()", &m.instances.len())
855            .field("render_keys.len()", &m.render_ids.len())
856            .field("unregistered", &m.unregistered)
857            .finish_non_exhaustive()
858    }
859}
860impl PartialEq for FontFace {
861    fn eq(&self, other: &Self) -> bool {
862        Arc::ptr_eq(&self.0, &other.0)
863    }
864}
865impl Eq for FontFace {}
866impl FontFace {
867    /// New empty font face.
868    pub fn empty() -> Self {
869        FontFace(Arc::new(LoadedFontFace {
870            data: FontBytes::from_static(&[]),
871            face_index: 0,
872            display_name: FontName::from("<empty>"),
873            family_name: FontName::from("<empty>"),
874            postscript_name: None,
875            flags: FontFaceFlags::IS_MONOSPACE,
876            style: FontStyle::Normal,
877            weight: FontWeight::NORMAL,
878            stretch: FontStretch::NORMAL,
879            // values copied from a monospace font
880            metrics: FontFaceMetrics {
881                units_per_em: 2048,
882                ascent: 1616.0,
883                descent: -432.0,
884                line_gap: 0.0,
885                underline_position: -205.0,
886                underline_thickness: 102.0,
887                cap_height: 1616.0,
888                x_height: 1616.0,
889                // `xMin`/`xMax`/`yMin`/`yMax`
890                bounds: euclid::Box2D::new(euclid::point2(0.0, -432.0), euclid::point2(1291.0, 1616.0)).to_rect(),
891            },
892            lig_carets: LigatureCaretList::empty(),
893            m: Mutex::new(FontFaceMut {
894                instances: HashMap::default(),
895                render_ids: vec![],
896                unregistered: false,
897            }),
898        }))
899    }
900
901    /// Is empty font face.
902    pub fn is_empty(&self) -> bool {
903        self.0.data.is_empty()
904    }
905
906    async fn load_custom(custom_font: CustomFont) -> Result<Self, FontLoadingError> {
907        let bytes;
908        let mut face_index;
909
910        match custom_font.source {
911            FontSource::File(path, index) => {
912                bytes = task::wait(|| FontBytes::from_file(path)).await?;
913                face_index = index;
914            }
915            FontSource::Memory(arc, index) => {
916                bytes = arc;
917                face_index = index;
918            }
919            FontSource::Alias(other_font) => {
920                let result = FONTS_SV
921                    .write()
922                    .loader
923                    .load_resolved(&other_font, custom_font.style, custom_font.weight, custom_font.stretch);
924                return match result.wait_rsp().await {
925                    Some(other_font) => Ok(FontFace(Arc::new(LoadedFontFace {
926                        data: other_font.0.data.clone(),
927                        face_index: other_font.0.face_index,
928                        display_name: custom_font.name.clone(),
929                        family_name: custom_font.name,
930                        postscript_name: None,
931                        style: other_font.0.style,
932                        weight: other_font.0.weight,
933                        stretch: other_font.0.stretch,
934                        metrics: other_font.0.metrics.clone(),
935                        m: Mutex::new(FontFaceMut {
936                            instances: Default::default(),
937                            render_ids: Default::default(),
938                            unregistered: Default::default(),
939                        }),
940                        lig_carets: other_font.0.lig_carets.clone(),
941                        flags: other_font.0.flags,
942                    }))),
943                    None => Err(FontLoadingError::NoSuchFontInCollection),
944                };
945            }
946        }
947
948        let ttf_face = match ttf_parser::Face::parse(&bytes, face_index) {
949            Ok(f) => f,
950            Err(e) => {
951                match e {
952                    // try again with font 0 (font-kit selects a high index for Ubuntu Font)
953                    ttf_parser::FaceParsingError::FaceIndexOutOfBounds => face_index = 0,
954                    e => return Err(FontLoadingError::Parse(e)),
955                }
956
957                match ttf_parser::Face::parse(&bytes, face_index) {
958                    Ok(f) => f,
959                    Err(_) => return Err(FontLoadingError::Parse(e)),
960                }
961            }
962        };
963
964        let has_ligatures = ttf_face.tables().gsub.is_some();
965        let lig_carets = if has_ligatures {
966            LigatureCaretList::empty()
967        } else {
968            LigatureCaretList::load(ttf_face.raw_face())?
969        };
970
971        // all tables used by `ttf_parser::Face::glyph_raster_image`
972        let has_raster_images = {
973            let t = ttf_face.tables();
974            t.sbix.is_some() || t.bdat.is_some() || t.ebdt.is_some() || t.cbdt.is_some()
975        };
976
977        let mut flags = FontFaceFlags::empty();
978        flags.set(FontFaceFlags::IS_MONOSPACE, ttf_face.is_monospaced());
979        flags.set(FontFaceFlags::HAS_LIGATURES, has_ligatures);
980        flags.set(FontFaceFlags::HAS_RASTER_IMAGES, has_raster_images);
981        flags.set(FontFaceFlags::HAS_SVG_IMAGES, ttf_face.tables().svg.is_some());
982
983        Ok(FontFace(Arc::new(LoadedFontFace {
984            face_index,
985            display_name: custom_font.name.clone(),
986            family_name: custom_font.name,
987            postscript_name: None,
988            style: custom_font.style,
989            weight: custom_font.weight,
990            stretch: custom_font.stretch,
991            metrics: ttf_face.into(),
992            lig_carets,
993            m: Mutex::new(FontFaceMut {
994                instances: Default::default(),
995                render_ids: Default::default(),
996                unregistered: Default::default(),
997            }),
998            data: bytes,
999            flags,
1000        })))
1001    }
1002
1003    fn load(bytes: FontBytes, mut face_index: u32) -> Result<Self, FontLoadingError> {
1004        let _span = tracing::trace_span!("FontFace::load").entered();
1005
1006        let ttf_face = match ttf_parser::Face::parse(&bytes, face_index) {
1007            Ok(f) => f,
1008            Err(e) => {
1009                match e {
1010                    // try again with font 0 (font-kit selects a high index for Ubuntu Font)
1011                    ttf_parser::FaceParsingError::FaceIndexOutOfBounds => face_index = 0,
1012                    e => return Err(FontLoadingError::Parse(e)),
1013                }
1014
1015                match ttf_parser::Face::parse(&bytes, face_index) {
1016                    Ok(f) => f,
1017                    Err(_) => return Err(FontLoadingError::Parse(e)),
1018                }
1019            }
1020        };
1021
1022        let has_ligatures = ttf_face.tables().gsub.is_some();
1023        let lig_carets = if has_ligatures {
1024            LigatureCaretList::empty()
1025        } else {
1026            LigatureCaretList::load(ttf_face.raw_face())?
1027        };
1028
1029        let mut display_name = None;
1030        let mut family_name = None;
1031        let mut postscript_name = None;
1032        let mut any_name = None::<String>;
1033        for name in ttf_face.names() {
1034            if let Some(n) = name.to_string() {
1035                match name.name_id {
1036                    ttf_parser::name_id::FULL_NAME => display_name = Some(n),
1037                    ttf_parser::name_id::FAMILY => family_name = Some(n),
1038                    ttf_parser::name_id::POST_SCRIPT_NAME => postscript_name = Some(n),
1039                    _ => match &mut any_name {
1040                        Some(s) => {
1041                            if n.len() > s.len() {
1042                                *s = n;
1043                            }
1044                        }
1045                        None => any_name = Some(n),
1046                    },
1047                }
1048            }
1049        }
1050        let display_name = FontName::new(Txt::from_str(
1051            display_name
1052                .as_ref()
1053                .or(family_name.as_ref())
1054                .or(postscript_name.as_ref())
1055                .or(any_name.as_ref())
1056                .unwrap(),
1057        ));
1058        let family_name = family_name.map(FontName::from).unwrap_or_else(|| display_name.clone());
1059        let postscript_name = postscript_name.map(Txt::from);
1060
1061        if ttf_face.units_per_em() == 0 {
1062            // observed this in Noto Color Emoji (with font_kit)
1063            tracing::debug!("font {display_name:?} units_per_em 0");
1064            return Err(FontLoadingError::UnknownFormat);
1065        }
1066
1067        // all tables used by `ttf_parser::Face::glyph_raster_image`
1068        let has_raster_images = {
1069            let t = ttf_face.tables();
1070            t.sbix.is_some() || t.bdat.is_some() || t.ebdt.is_some() || t.cbdt.is_some()
1071        };
1072
1073        let mut flags = FontFaceFlags::empty();
1074        flags.set(FontFaceFlags::IS_MONOSPACE, ttf_face.is_monospaced());
1075        flags.set(FontFaceFlags::HAS_LIGATURES, has_ligatures);
1076        flags.set(FontFaceFlags::HAS_RASTER_IMAGES, has_raster_images);
1077        flags.set(FontFaceFlags::HAS_SVG_IMAGES, ttf_face.tables().svg.is_some());
1078
1079        Ok(FontFace(Arc::new(LoadedFontFace {
1080            face_index,
1081            family_name,
1082            display_name,
1083            postscript_name,
1084            style: ttf_face.style().into(),
1085            weight: ttf_face.weight().into(),
1086            stretch: ttf_face.width().into(),
1087            metrics: ttf_face.into(),
1088            lig_carets,
1089            m: Mutex::new(FontFaceMut {
1090                instances: Default::default(),
1091                render_ids: Default::default(),
1092                unregistered: Default::default(),
1093            }),
1094            data: bytes,
1095            flags,
1096        })))
1097    }
1098
1099    fn on_refresh(&self) {
1100        let mut m = self.0.m.lock();
1101        m.instances.clear();
1102        m.unregistered = true;
1103    }
1104
1105    fn render_face(&self, renderer: &ViewRenderer) -> zng_view_api::font::FontFaceId {
1106        let mut m = self.0.m.lock();
1107        for r in m.render_ids.iter() {
1108            if &r.renderer == renderer {
1109                return r.face_id;
1110            }
1111        }
1112
1113        let data = match self.0.data.to_ipc() {
1114            Ok(d) => d,
1115            Err(e) => {
1116                tracing::error!("cannot allocate ipc font data, {e}");
1117                return zng_view_api::font::FontFaceId::INVALID;
1118            }
1119        };
1120
1121        let key = match renderer.add_font_face(data, self.0.face_index) {
1122            Ok(k) => k,
1123            Err(_) => {
1124                tracing::debug!("respawned calling `add_font`, will return dummy font key");
1125                return zng_view_api::font::FontFaceId::INVALID;
1126            }
1127        };
1128
1129        m.render_ids.push(RenderFontFace::new(renderer, key));
1130
1131        key
1132    }
1133
1134    /// Loads the harfbuzz face.
1135    ///
1136    /// Loads from in memory [`bytes`].
1137    ///
1138    /// Returns `None` if [`is_empty`].
1139    ///
1140    /// [`is_empty`]: Self::is_empty
1141    /// [`bytes`]: Self::bytes
1142    pub fn harfbuzz(&self) -> Option<rustybuzz::Face<'_>> {
1143        if self.is_empty() {
1144            None
1145        } else {
1146            Some(rustybuzz::Face::from_slice(&self.0.data, self.0.face_index).unwrap())
1147        }
1148    }
1149
1150    /// Loads the full TTF face.
1151    ///
1152    /// Loads from in memory [`bytes`].
1153    ///
1154    /// Returns `None` if [`is_empty`].
1155    ///
1156    /// [`is_empty`]: Self::is_empty
1157    /// [`bytes`]: Self::bytes
1158    pub fn ttf(&self) -> Option<ttf_parser::Face<'_>> {
1159        if self.is_empty() {
1160            None
1161        } else {
1162            Some(ttf_parser::Face::parse(&self.0.data, self.0.face_index).unwrap())
1163        }
1164    }
1165
1166    /// Reference the font file bytes.
1167    pub fn bytes(&self) -> &FontBytes {
1168        &self.0.data
1169    }
1170    /// Index of the font face in the [font file](Self::bytes).
1171    pub fn index(&self) -> u32 {
1172        self.0.face_index
1173    }
1174
1175    /// Font full name.
1176    pub fn display_name(&self) -> &FontName {
1177        &self.0.display_name
1178    }
1179
1180    /// Font family name.
1181    pub fn family_name(&self) -> &FontName {
1182        &self.0.family_name
1183    }
1184
1185    /// Font globally unique name.
1186    pub fn postscript_name(&self) -> Option<&str> {
1187        self.0.postscript_name.as_deref()
1188    }
1189
1190    /// Font style.
1191    pub fn style(&self) -> FontStyle {
1192        self.0.style
1193    }
1194
1195    /// Font weight.
1196    pub fn weight(&self) -> FontWeight {
1197        self.0.weight
1198    }
1199
1200    /// Font stretch.
1201    pub fn stretch(&self) -> FontStretch {
1202        self.0.stretch
1203    }
1204
1205    /// Font is monospace (fixed-width).
1206    pub fn is_monospace(&self) -> bool {
1207        self.0.flags.contains(FontFaceFlags::IS_MONOSPACE)
1208    }
1209
1210    /// Font metrics in font units.
1211    pub fn metrics(&self) -> &FontFaceMetrics {
1212        &self.0.metrics
1213    }
1214
1215    /// Gets a cached sized [`Font`].
1216    ///
1217    /// The `font_size` is the size of `1 font EM` in pixels.
1218    ///
1219    /// The `variations` are custom [font variations] that will be used
1220    /// during shaping and rendering.
1221    ///
1222    /// [font variations]: crate::font_features::FontVariations::finalize
1223    pub fn sized(&self, font_size: Px, variations: RFontVariations) -> Font {
1224        let key = FontInstanceKey::new(font_size, &variations);
1225        let mut m = self.0.m.lock();
1226        if !m.unregistered {
1227            m.instances
1228                .entry(key)
1229                .or_insert_with(|| Font::new(self.clone(), font_size, variations))
1230                .clone()
1231        } else {
1232            tracing::debug!(target: "font_loading", "creating font from unregistered `{}`, will not cache", self.0.display_name);
1233            Font::new(self.clone(), font_size, variations)
1234        }
1235    }
1236
1237    /// Gets what font synthesis to use to better render this font face given the style and weight.
1238    pub fn synthesis_for(&self, style: FontStyle, weight: FontWeight) -> FontSynthesis {
1239        let mut synth = FontSynthesis::DISABLED;
1240
1241        if style != FontStyle::Normal && self.style() == FontStyle::Normal {
1242            // if requested oblique or italic and the face is neither.
1243            synth |= FontSynthesis::OBLIQUE;
1244        }
1245        if weight > self.weight() {
1246            // if requested a weight larger then the face weight the renderer can
1247            // add extra stroke outlines to compensate.
1248            synth |= FontSynthesis::BOLD;
1249        }
1250
1251        synth
1252    }
1253
1254    /// If this font face is cached. All font faces are cached by default, a font face can be detached from
1255    /// cache when a [`FONT_CHANGED_EVENT`] event happens, in this case the font can still be used normally, but
1256    /// a request for the same font name will return a different reference.
1257    pub fn is_cached(&self) -> bool {
1258        !self.0.m.lock().unregistered
1259    }
1260
1261    /// CPAL table.
1262    ///
1263    /// Is empty if not provided by the font.
1264    pub fn color_palettes(&self) -> ColorPalettes<'_> {
1265        match self.ttf() {
1266            Some(ttf) => ColorPalettes::new(*ttf.raw_face()),
1267            None => ColorPalettes::empty(),
1268        }
1269    }
1270
1271    /// COLR table.
1272    ///
1273    /// Is empty if not provided by the font.
1274    pub fn color_glyphs(&self) -> ColorGlyphs<'_> {
1275        match self.ttf() {
1276            Some(ttf) => ColorGlyphs::new(*ttf.raw_face()),
1277            None => ColorGlyphs::empty(),
1278        }
1279    }
1280
1281    /// If the font provides glyph substitutions.
1282    pub fn has_ligatures(&self) -> bool {
1283        self.0.flags.contains(FontFaceFlags::HAS_LIGATURES)
1284    }
1285
1286    /// If this font provides custom positioned carets for some or all ligature glyphs.
1287    ///
1288    /// If `true` the [`Font::ligature_caret_offsets`] method can be used to get the caret offsets, otherwise
1289    /// it always returns empty.
1290    pub fn has_ligature_caret_offsets(&self) -> bool {
1291        !self.0.lig_carets.is_empty()
1292    }
1293
1294    /// If this font has bitmap images associated with some glyphs.
1295    pub fn has_raster_images(&self) -> bool {
1296        self.0.flags.contains(FontFaceFlags::HAS_RASTER_IMAGES)
1297    }
1298
1299    /// If this font has SVG images associated with some glyphs.
1300    pub fn has_svg_images(&self) -> bool {
1301        self.0.flags.contains(FontFaceFlags::HAS_SVG_IMAGES)
1302    }
1303}
1304
1305/// A sized font face.
1306///
1307/// A sized font can be requested from a [`FontFace`].
1308///
1309/// This type is a shared reference to the loaded font data, cloning it is cheap.
1310#[derive(Clone)]
1311pub struct Font(Arc<LoadedFont>);
1312struct LoadedFont {
1313    face: FontFace,
1314    size: Px,
1315    variations: RFontVariations,
1316    metrics: FontMetrics,
1317    render_keys: Mutex<Vec<RenderFont>>,
1318    small_word_cache: RwLock<HashMap<WordCacheKey<[u8; Font::SMALL_WORD_LEN]>, ShapedSegmentData>>,
1319    word_cache: RwLock<HashMap<WordCacheKey<String>, ShapedSegmentData>>,
1320}
1321impl fmt::Debug for Font {
1322    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1323        f.debug_struct("Font")
1324            .field("face", &self.0.face)
1325            .field("size", &self.0.size)
1326            .field("metrics", &self.0.metrics)
1327            .field("render_keys.len()", &self.0.render_keys.lock().len())
1328            .field("small_word_cache.len()", &self.0.small_word_cache.read().len())
1329            .field("word_cache.len()", &self.0.word_cache.read().len())
1330            .finish()
1331    }
1332}
1333impl PartialEq for Font {
1334    fn eq(&self, other: &Self) -> bool {
1335        Arc::ptr_eq(&self.0, &other.0)
1336    }
1337}
1338impl Eq for Font {}
1339impl Font {
1340    const SMALL_WORD_LEN: usize = 8;
1341
1342    fn to_small_word(s: &str) -> Option<[u8; Self::SMALL_WORD_LEN]> {
1343        if s.len() <= Self::SMALL_WORD_LEN {
1344            let mut a = [b'\0'; Self::SMALL_WORD_LEN];
1345            a[..s.len()].copy_from_slice(s.as_bytes());
1346            Some(a)
1347        } else {
1348            None
1349        }
1350    }
1351
1352    fn new(face: FontFace, size: Px, variations: RFontVariations) -> Self {
1353        Font(Arc::new(LoadedFont {
1354            metrics: face.metrics().sized(size),
1355            face,
1356            size,
1357            variations,
1358            render_keys: Mutex::new(vec![]),
1359            small_word_cache: RwLock::default(),
1360            word_cache: RwLock::default(),
1361        }))
1362    }
1363
1364    fn render_font(&self, renderer: &ViewRenderer, synthesis: FontSynthesis) -> zng_view_api::font::FontId {
1365        let _span = tracing::trace_span!("Font::render_font").entered();
1366
1367        let mut render_keys = self.0.render_keys.lock();
1368        for r in render_keys.iter() {
1369            if &r.renderer == renderer && r.synthesis == synthesis {
1370                return r.font_id;
1371            }
1372        }
1373
1374        let font_key = self.0.face.render_face(renderer);
1375
1376        let mut opt = zng_view_api::font::FontOptions::default();
1377        opt.synthetic_oblique = synthesis.contains(FontSynthesis::OBLIQUE);
1378        opt.synthetic_bold = synthesis.contains(FontSynthesis::BOLD);
1379        let variations = self.0.variations.iter().map(|v| (v.tag.to_bytes(), v.value)).collect();
1380
1381        let key = match renderer.add_font(font_key, self.0.size, opt, variations) {
1382            Ok(k) => k,
1383            Err(_) => {
1384                tracing::debug!("respawned calling `add_font_instance`, will return dummy font key");
1385                return zng_view_api::font::FontId::INVALID;
1386            }
1387        };
1388
1389        render_keys.push(RenderFont::new(renderer, synthesis, key));
1390
1391        key
1392    }
1393
1394    /// Reference the font face source of this font.
1395    pub fn face(&self) -> &FontFace {
1396        &self.0.face
1397    }
1398
1399    /// Gets the sized harfbuzz font.
1400    pub fn harfbuzz(&self) -> Option<rustybuzz::Face<'_>> {
1401        let ppem = self.0.size.0 as u16;
1402
1403        let mut font = self.0.face.harfbuzz()?;
1404
1405        font.set_pixels_per_em(Some((ppem, ppem)));
1406        font.set_variations(&self.0.variations);
1407
1408        Some(font)
1409    }
1410
1411    /// Font size.
1412    ///
1413    /// This is also the *pixels-per-em* value.
1414    pub fn size(&self) -> Px {
1415        self.0.size
1416    }
1417
1418    /// Custom font variations.
1419    pub fn variations(&self) -> &RFontVariations {
1420        &self.0.variations
1421    }
1422
1423    /// Sized font metrics.
1424    pub fn metrics(&self) -> &FontMetrics {
1425        &self.0.metrics
1426    }
1427
1428    /// Iterate over pixel offsets relative to `lig` glyph start that represents the
1429    /// caret offset for each cluster that is covered by the ligature, after the first.
1430    ///
1431    /// The caret offset for the first cluster is the glyph offset and is not yielded in the iterator. The
1432    /// yielded offsets are relative to the glyph position.
1433    pub fn ligature_caret_offsets(
1434        &self,
1435        lig: zng_view_api::font::GlyphIndex,
1436    ) -> impl ExactSizeIterator<Item = f32> + DoubleEndedIterator + '_ {
1437        let face = &self.0.face.0;
1438        face.lig_carets.carets(lig).iter().map(move |&o| match o {
1439            ligature_util::LigatureCaret::Coordinate(o) => {
1440                let size_scale = 1.0 / face.metrics.units_per_em as f32 * self.0.size.0 as f32;
1441                o as f32 * size_scale
1442            }
1443            ligature_util::LigatureCaret::GlyphContourPoint(i) => {
1444                if let Some(f) = self.harfbuzz() {
1445                    struct Search {
1446                        i: u16,
1447                        s: u16,
1448                        x: f32,
1449                    }
1450                    impl Search {
1451                        fn check(&mut self, x: f32) {
1452                            self.s = self.s.saturating_add(1);
1453                            if self.s == self.i {
1454                                self.x = x;
1455                            }
1456                        }
1457                    }
1458                    impl ttf_parser::OutlineBuilder for Search {
1459                        fn move_to(&mut self, x: f32, _y: f32) {
1460                            self.check(x);
1461                        }
1462
1463                        fn line_to(&mut self, x: f32, _y: f32) {
1464                            self.check(x);
1465                        }
1466
1467                        fn quad_to(&mut self, _x1: f32, _y1: f32, x: f32, _y: f32) {
1468                            self.check(x)
1469                        }
1470
1471                        fn curve_to(&mut self, _x1: f32, _y1: f32, _x2: f32, _y2: f32, x: f32, _y: f32) {
1472                            self.check(x);
1473                        }
1474
1475                        fn close(&mut self) {}
1476                    }
1477                    let mut search = Search { i, s: 0, x: 0.0 };
1478                    if f.outline_glyph(ttf_parser::GlyphId(lig as _), &mut search).is_some() && search.s >= search.i {
1479                        return search.x * self.0.metrics.size_scale;
1480                    }
1481                }
1482                0.0
1483            }
1484        })
1485    }
1486}
1487impl zng_app::render::Font for Font {
1488    fn is_empty_fallback(&self) -> bool {
1489        self.face().is_empty()
1490    }
1491
1492    fn renderer_id(&self, renderer: &ViewRenderer, synthesis: FontSynthesis) -> zng_view_api::font::FontId {
1493        self.render_font(renderer, synthesis)
1494    }
1495}
1496
1497/// A list of [`FontFace`] resolved from a [`FontName`] list, plus the [fallback](GenericFonts::fallback) font.
1498///
1499/// Glyphs that are not resolved by the first font fallback to the second font and so on.
1500#[derive(Debug, Clone)]
1501pub struct FontFaceList {
1502    fonts: Box<[FontFace]>,
1503    requested_style: FontStyle,
1504    requested_weight: FontWeight,
1505    requested_stretch: FontStretch,
1506}
1507impl FontFaceList {
1508    /// New list with only the [`FontFace::empty`].
1509    pub fn empty() -> Self {
1510        Self {
1511            fonts: Box::new([FontFace::empty()]),
1512            requested_style: FontStyle::Normal,
1513            requested_weight: FontWeight::NORMAL,
1514            requested_stretch: FontStretch::NORMAL,
1515        }
1516    }
1517
1518    /// Style requested in the query that generated this font face list.
1519    pub fn requested_style(&self) -> FontStyle {
1520        self.requested_style
1521    }
1522
1523    /// Weight requested in the query that generated this font face list.
1524    pub fn requested_weight(&self) -> FontWeight {
1525        self.requested_weight
1526    }
1527
1528    /// Stretch requested in the query that generated this font face list.
1529    pub fn requested_stretch(&self) -> FontStretch {
1530        self.requested_stretch
1531    }
1532
1533    /// The font face that best matches the requested properties.
1534    pub fn best(&self) -> &FontFace {
1535        &self.fonts[0]
1536    }
1537
1538    /// Gets the font synthesis to use to better render the given font face on the list.
1539    pub fn face_synthesis(&self, face_index: usize) -> FontSynthesis {
1540        if let Some(face) = self.fonts.get(face_index) {
1541            face.synthesis_for(self.requested_style, self.requested_weight)
1542        } else {
1543            FontSynthesis::DISABLED
1544        }
1545    }
1546
1547    /// Iterate over font faces, more specific first.
1548    pub fn iter(&self) -> std::slice::Iter<'_, FontFace> {
1549        self.fonts.iter()
1550    }
1551
1552    /// Number of font faces in the list.
1553    ///
1554    /// This is at least `1`, but can be the empty face.
1555    pub fn len(&self) -> usize {
1556        self.fonts.len()
1557    }
1558
1559    /// Is length `1` and only contains the empty face.
1560    pub fn is_empty(&self) -> bool {
1561        self.fonts[0].is_empty() && self.fonts.len() == 1
1562    }
1563
1564    /// Gets a sized font list.
1565    ///
1566    /// This calls [`FontFace::sized`] for each font in the list.
1567    pub fn sized(&self, font_size: Px, variations: RFontVariations) -> FontList {
1568        FontList {
1569            fonts: self.fonts.iter().map(|f| f.sized(font_size, variations.clone())).collect(),
1570            requested_style: self.requested_style,
1571            requested_weight: self.requested_weight,
1572            requested_stretch: self.requested_stretch,
1573        }
1574    }
1575}
1576impl PartialEq for FontFaceList {
1577    /// Both are equal if each point to the same fonts in the same order and have the same requested properties.
1578    fn eq(&self, other: &Self) -> bool {
1579        self.requested_style == other.requested_style
1580            && self.requested_weight == other.requested_weight
1581            && self.requested_stretch == other.requested_stretch
1582            && self.fonts.len() == other.fonts.len()
1583            && self.fonts.iter().zip(other.fonts.iter()).all(|(a, b)| a == b)
1584    }
1585}
1586impl Eq for FontFaceList {}
1587impl std::ops::Deref for FontFaceList {
1588    type Target = [FontFace];
1589
1590    fn deref(&self) -> &Self::Target {
1591        &self.fonts
1592    }
1593}
1594impl<'a> std::iter::IntoIterator for &'a FontFaceList {
1595    type Item = &'a FontFace;
1596
1597    type IntoIter = std::slice::Iter<'a, FontFace>;
1598
1599    fn into_iter(self) -> Self::IntoIter {
1600        self.iter()
1601    }
1602}
1603impl std::ops::Index<usize> for FontFaceList {
1604    type Output = FontFace;
1605
1606    fn index(&self, index: usize) -> &Self::Output {
1607        &self.fonts[index]
1608    }
1609}
1610
1611/// A list of [`Font`] created from a [`FontFaceList`].
1612#[derive(Debug, Clone)]
1613pub struct FontList {
1614    fonts: Box<[Font]>,
1615    requested_style: FontStyle,
1616    requested_weight: FontWeight,
1617    requested_stretch: FontStretch,
1618}
1619#[expect(clippy::len_without_is_empty)] // cannot be empty.
1620impl FontList {
1621    /// The font that best matches the requested properties.
1622    pub fn best(&self) -> &Font {
1623        &self.fonts[0]
1624    }
1625
1626    /// Font size requested in the query that generated this font list.
1627    pub fn requested_size(&self) -> Px {
1628        self.fonts[0].size()
1629    }
1630
1631    /// Style requested in the query that generated this font list.
1632    pub fn requested_style(&self) -> FontStyle {
1633        self.requested_style
1634    }
1635
1636    /// Weight requested in the query that generated this font list.
1637    pub fn requested_weight(&self) -> FontWeight {
1638        self.requested_weight
1639    }
1640
1641    /// Stretch requested in the query that generated this font list.
1642    pub fn requested_stretch(&self) -> FontStretch {
1643        self.requested_stretch
1644    }
1645
1646    /// Gets the font synthesis to use to better render the given font on the list.
1647    pub fn face_synthesis(&self, font_index: usize) -> FontSynthesis {
1648        if let Some(font) = self.fonts.get(font_index) {
1649            font.0.face.synthesis_for(self.requested_style, self.requested_weight)
1650        } else {
1651            FontSynthesis::DISABLED
1652        }
1653    }
1654
1655    /// Iterate over font faces, more specific first.
1656    pub fn iter(&self) -> std::slice::Iter<'_, Font> {
1657        self.fonts.iter()
1658    }
1659
1660    /// Number of font faces in the list.
1661    ///
1662    /// This is at least `1`.
1663    pub fn len(&self) -> usize {
1664        self.fonts.len()
1665    }
1666
1667    /// Returns `true` is `self` is sized from the `faces` list.
1668    pub fn is_sized_from(&self, faces: &FontFaceList) -> bool {
1669        if self.len() != faces.len() {
1670            return false;
1671        }
1672
1673        for (font, face) in self.iter().zip(faces.iter()) {
1674            if font.face() != face {
1675                return false;
1676            }
1677        }
1678
1679        true
1680    }
1681}
1682impl PartialEq for FontList {
1683    /// Both are equal if each point to the same fonts in the same order and have the same requested properties.
1684    fn eq(&self, other: &Self) -> bool {
1685        self.requested_style == other.requested_style
1686            && self.requested_weight == other.requested_weight
1687            && self.requested_stretch == other.requested_stretch
1688            && self.fonts.len() == other.fonts.len()
1689            && self.fonts.iter().zip(other.fonts.iter()).all(|(a, b)| a == b)
1690    }
1691}
1692impl Eq for FontList {}
1693impl std::ops::Deref for FontList {
1694    type Target = [Font];
1695
1696    fn deref(&self) -> &Self::Target {
1697        &self.fonts
1698    }
1699}
1700impl<'a> std::iter::IntoIterator for &'a FontList {
1701    type Item = &'a Font;
1702
1703    type IntoIter = std::slice::Iter<'a, Font>;
1704
1705    fn into_iter(self) -> Self::IntoIter {
1706        self.iter()
1707    }
1708}
1709impl<I: SliceIndex<[Font]>> std::ops::Index<I> for FontList {
1710    type Output = I::Output;
1711
1712    fn index(&self, index: I) -> &I::Output {
1713        &self.fonts[index]
1714    }
1715}
1716
1717struct FontFaceLoader {
1718    custom_fonts: HashMap<FontName, Vec<FontFace>>,
1719
1720    system_fonts_cache: HashMap<FontName, Vec<SystemFontFace>>,
1721    list_cache: HashMap<Box<[FontName]>, Vec<FontFaceListQuery>>,
1722}
1723struct SystemFontFace {
1724    properties: (FontStyle, FontWeight, FontStretch),
1725    result: ResponseVar<Option<FontFace>>,
1726}
1727struct FontFaceListQuery {
1728    properties: (FontStyle, FontWeight, FontStretch),
1729    lang: Lang,
1730    result: ResponseVar<FontFaceList>,
1731}
1732impl FontFaceLoader {
1733    fn new() -> Self {
1734        FontFaceLoader {
1735            custom_fonts: HashMap::new(),
1736            system_fonts_cache: HashMap::new(),
1737            list_cache: HashMap::new(),
1738        }
1739    }
1740
1741    fn on_view_process_respawn(&mut self) {
1742        let sys_fonts = self.system_fonts_cache.values().flatten().filter_map(|f| f.result.rsp().flatten());
1743        for face in self.custom_fonts.values().flatten().cloned().chain(sys_fonts) {
1744            let mut m = face.0.m.lock();
1745            m.render_ids.clear();
1746            for inst in m.instances.values() {
1747                inst.0.render_keys.lock().clear();
1748            }
1749        }
1750    }
1751
1752    fn on_refresh(&mut self) {
1753        for (_, sys_family) in self.system_fonts_cache.drain() {
1754            for sys_font in sys_family {
1755                sys_font.result.with(|r| {
1756                    if let Some(Some(face)) = r.done() {
1757                        face.on_refresh();
1758                    }
1759                });
1760            }
1761        }
1762    }
1763    fn on_prune(&mut self) {
1764        self.system_fonts_cache.retain(|_, v| {
1765            v.retain(|sff| {
1766                if sff.result.strong_count() == 1 {
1767                    sff.result.with(|r| {
1768                        match r.done() {
1769                            Some(Some(face)) => Arc::strong_count(&face.0) > 1, // face shared
1770                            Some(None) => false,                                // loading for no one
1771                            None => true,                                       // retain not found
1772                        }
1773                    })
1774                } else {
1775                    // response var shared
1776                    true
1777                }
1778            });
1779            !v.is_empty()
1780        });
1781
1782        self.list_cache.clear();
1783    }
1784
1785    fn try_list(
1786        &self,
1787        families: &[FontName],
1788        style: FontStyle,
1789        weight: FontWeight,
1790        stretch: FontStretch,
1791        lang: &Lang,
1792    ) -> Option<ResponseVar<FontFaceList>> {
1793        if let Some(queries) = self.list_cache.get(families) {
1794            for q in queries {
1795                if q.properties == (style, weight, stretch) && &q.lang == lang {
1796                    return Some(q.result.clone());
1797                }
1798            }
1799        }
1800        None
1801    }
1802
1803    fn load_list(
1804        &mut self,
1805        families: &[FontName],
1806        style: FontStyle,
1807        weight: FontWeight,
1808        stretch: FontStretch,
1809        lang: &Lang,
1810    ) -> ResponseVar<FontFaceList> {
1811        if let Some(r) = self.try_list(families, style, weight, stretch, lang) {
1812            return r;
1813        }
1814
1815        let resolved = GenericFonts {}.resolve_list(families, lang);
1816        let families = resolved.as_ref().map(|n| &***n).unwrap_or(families);
1817        let mut list = Vec::with_capacity(families.len() + 1);
1818        let mut pending = vec![];
1819
1820        {
1821            let fallback = [GenericFonts {}.fallback(lang)];
1822            let mut used = HashSet::with_capacity(families.len());
1823            for name in families.iter().chain(&fallback) {
1824                if !used.insert(name) {
1825                    continue;
1826                }
1827
1828                let face = self.load_resolved(name, style, weight, stretch);
1829                if face.is_done() {
1830                    if let Some(face) = face.rsp().unwrap() {
1831                        list.push(face);
1832                    }
1833                } else {
1834                    pending.push((list.len(), face));
1835                }
1836            }
1837        }
1838
1839        let r = if pending.is_empty() {
1840            if list.is_empty() {
1841                tracing::error!(target: "font_loading", "failed to load fallback font");
1842                list.push(FontFace::empty());
1843            }
1844            response_done_var(FontFaceList {
1845                fonts: list.into_boxed_slice(),
1846                requested_style: style,
1847                requested_weight: weight,
1848                requested_stretch: stretch,
1849            })
1850        } else {
1851            task::respond(async move {
1852                for (i, pending) in pending.into_iter().rev() {
1853                    if let Some(rsp) = pending.wait_rsp().await {
1854                        list.insert(i, rsp);
1855                    }
1856                }
1857
1858                if list.is_empty() {
1859                    tracing::error!(target: "font_loading", "failed to load fallback font");
1860                    list.push(FontFace::empty());
1861                }
1862
1863                FontFaceList {
1864                    fonts: list.into_boxed_slice(),
1865                    requested_style: style,
1866                    requested_weight: weight,
1867                    requested_stretch: stretch,
1868                }
1869            })
1870        };
1871
1872        self.list_cache
1873            .entry(families.iter().cloned().collect())
1874            .or_insert_with(|| Vec::with_capacity(1))
1875            .push(FontFaceListQuery {
1876                properties: (style, weight, stretch),
1877                lang: lang.clone(),
1878                result: r.clone(),
1879            });
1880
1881        r
1882    }
1883
1884    /// Get a `font_name` that already resolved generic names if it is already in cache.
1885    fn try_resolved(
1886        &self,
1887        font_name: &FontName,
1888        style: FontStyle,
1889        weight: FontWeight,
1890        stretch: FontStretch,
1891    ) -> Option<ResponseVar<Option<FontFace>>> {
1892        if let Some(custom_family) = self.custom_fonts.get(font_name) {
1893            let custom = Self::match_custom(custom_family, style, weight, stretch);
1894            return Some(response_done_var(Some(custom)));
1895        }
1896
1897        if let Some(cached_sys_family) = self.system_fonts_cache.get(font_name) {
1898            for sys_face in cached_sys_family.iter() {
1899                if sys_face.properties == (style, weight, stretch) {
1900                    return Some(sys_face.result.clone());
1901                }
1902            }
1903        }
1904
1905        None
1906    }
1907
1908    /// Load a `font_name` that already resolved generic names.
1909    fn load_resolved(
1910        &mut self,
1911        font_name: &FontName,
1912        style: FontStyle,
1913        weight: FontWeight,
1914        stretch: FontStretch,
1915    ) -> ResponseVar<Option<FontFace>> {
1916        if let Some(cached) = self.try_resolved(font_name, style, weight, stretch) {
1917            return cached;
1918        }
1919
1920        let load = task::wait(clmv!(font_name, || {
1921            let (bytes, face_index) = match Self::get_system(&font_name, style, weight, stretch) {
1922                Some(h) => h,
1923                None => {
1924                    #[cfg(debug_assertions)]
1925                    static NOT_FOUND: Mutex<Option<HashSet<FontName>>> = Mutex::new(None);
1926
1927                    #[cfg(debug_assertions)]
1928                    if NOT_FOUND.lock().get_or_insert_with(HashSet::default).insert(font_name.clone()) {
1929                        tracing::debug!(r#"font "{font_name}" not found"#);
1930                    }
1931
1932                    return None;
1933                }
1934            };
1935            match FontFace::load(bytes, face_index) {
1936                Ok(f) => Some(f),
1937                Err(FontLoadingError::UnknownFormat) => None,
1938                Err(e) => {
1939                    tracing::error!(target: "font_loading", "failed to load system font, {e}\nquery: {:?}", (font_name, style, weight, stretch));
1940                    None
1941                }
1942            }
1943        }));
1944        let result = task::respond(async_clmv!(font_name, {
1945            match task::with_deadline(load, 10.secs()).await {
1946                Ok(r) => r,
1947                Err(_) => {
1948                    tracing::error!(target: "font_loading", "timeout loading {font_name:?}");
1949                    None
1950                }
1951            }
1952        }));
1953
1954        self.system_fonts_cache
1955            .entry(font_name.clone())
1956            .or_insert_with(|| Vec::with_capacity(1))
1957            .push(SystemFontFace {
1958                properties: (style, weight, stretch),
1959                result: result.clone(),
1960            });
1961
1962        result
1963    }
1964
1965    fn get_system(font_name: &FontName, style: FontStyle, weight: FontWeight, stretch: FontStretch) -> Option<(FontBytes, u32)> {
1966        let _span = tracing::trace_span!("FontFaceLoader::get_system").entered();
1967        match query_util::best(font_name, style, weight, stretch) {
1968            Ok(r) => r,
1969            Err(e) => {
1970                tracing::error!("cannot get `{font_name}` system font, {e}");
1971                None
1972            }
1973        }
1974    }
1975
1976    fn match_custom(faces: &[FontFace], style: FontStyle, weight: FontWeight, stretch: FontStretch) -> FontFace {
1977        if faces.len() == 1 {
1978            // it is common for custom font names to only have one face.
1979            return faces[0].clone();
1980        }
1981
1982        let mut set = Vec::with_capacity(faces.len());
1983        let mut set_dist = 0.0f64; // stretch distance of current set if it is not empty.
1984
1985        // # Filter Stretch
1986        //
1987        // Closest to query stretch, if the query is narrow, closest narrow then
1988        // closest wide, if the query is wide the reverse.
1989        let wrong_side = if stretch <= FontStretch::NORMAL {
1990            |s| s > FontStretch::NORMAL
1991        } else {
1992            |s| s <= FontStretch::NORMAL
1993        };
1994        for face in faces {
1995            let mut dist = (face.stretch().0 - stretch.0).abs() as f64;
1996            if wrong_side(face.stretch()) {
1997                dist += f32::MAX as f64 + 1.0;
1998            }
1999
2000            if set.is_empty() {
2001                set.push(face);
2002                set_dist = dist;
2003            } else if dist < set_dist {
2004                // better candidate found, restart closest set.
2005                set_dist = dist;
2006                set.clear();
2007                set.push(face);
2008            } else if (dist - set_dist).abs() < 0.0001 {
2009                // another candidate, same distance.
2010                set.push(face);
2011            }
2012        }
2013        if set.len() == 1 {
2014            return set[0].clone();
2015        }
2016
2017        // # Filter Style
2018        //
2019        // Each query style has a fallback preference, we retain the faces that have the best
2020        // style given the query preference.
2021        let style_pref = match style {
2022            FontStyle::Normal => [FontStyle::Normal, FontStyle::Oblique, FontStyle::Italic],
2023            FontStyle::Italic => [FontStyle::Italic, FontStyle::Oblique, FontStyle::Normal],
2024            FontStyle::Oblique => [FontStyle::Oblique, FontStyle::Italic, FontStyle::Normal],
2025        };
2026        let mut best_style = style_pref.len();
2027        for face in &set {
2028            let i = style_pref.iter().position(|&s| s == face.style()).unwrap();
2029            if i < best_style {
2030                best_style = i;
2031            }
2032        }
2033        set.retain(|f| f.style() == style_pref[best_style]);
2034        if set.len() == 1 {
2035            return set[0].clone();
2036        }
2037
2038        // # Filter Weight
2039        //
2040        // a: under 400 query matches query then descending under query then ascending over query.
2041        // b: over 500 query matches query then ascending over query then descending under query.
2042        //
2043        // c: in 400..=500 query matches query then ascending to 500 then descending under query
2044        //     then ascending over 500.
2045        let add_penalty = if weight.0 >= 400.0 && weight.0 <= 500.0 {
2046            // c:
2047            |face: &FontFace, weight: FontWeight, dist: &mut f64| {
2048                // Add penalty for:
2049                if face.weight() < weight {
2050                    // Not being in search up to 500
2051                    *dist += 100.0;
2052                } else if face.weight().0 > 500.0 {
2053                    // Not being in search down to 0
2054                    *dist += 600.0;
2055                }
2056            }
2057        } else if weight.0 < 400.0 {
2058            // a:
2059            |face: &FontFace, weight: FontWeight, dist: &mut f64| {
2060                if face.weight() > weight {
2061                    *dist += weight.0 as f64;
2062                }
2063            }
2064        } else {
2065            debug_assert!(weight.0 > 500.0);
2066            // b:
2067            |face: &FontFace, weight: FontWeight, dist: &mut f64| {
2068                if face.weight() < weight {
2069                    *dist += f32::MAX as f64;
2070                }
2071            }
2072        };
2073
2074        let mut best = set[0];
2075        let mut best_dist = f64::MAX;
2076
2077        for face in &set {
2078            let mut dist = (face.weight().0 - weight.0).abs() as f64;
2079
2080            add_penalty(face, weight, &mut dist);
2081
2082            if dist < best_dist {
2083                best_dist = dist;
2084                best = face;
2085            }
2086        }
2087
2088        best.clone()
2089    }
2090}
2091
2092struct RenderFontFace {
2093    renderer: ViewRenderer,
2094    face_id: zng_view_api::font::FontFaceId,
2095}
2096impl RenderFontFace {
2097    fn new(renderer: &ViewRenderer, face_id: zng_view_api::font::FontFaceId) -> Self {
2098        RenderFontFace {
2099            renderer: renderer.clone(),
2100            face_id,
2101        }
2102    }
2103}
2104impl Drop for RenderFontFace {
2105    fn drop(&mut self) {
2106        // error here means the entire renderer was already dropped.
2107        let _ = self.renderer.delete_font_face(self.face_id);
2108    }
2109}
2110
2111struct RenderFont {
2112    renderer: ViewRenderer,
2113    synthesis: FontSynthesis,
2114    font_id: zng_view_api::font::FontId,
2115}
2116impl RenderFont {
2117    fn new(renderer: &ViewRenderer, synthesis: FontSynthesis, font_id: zng_view_api::font::FontId) -> RenderFont {
2118        RenderFont {
2119            renderer: renderer.clone(),
2120            synthesis,
2121            font_id,
2122        }
2123    }
2124}
2125impl Drop for RenderFont {
2126    fn drop(&mut self) {
2127        // error here means the entire renderer was already dropped.
2128        let _ = self.renderer.delete_font(self.font_id);
2129    }
2130}
2131
2132app_local! {
2133    static GENERIC_FONTS_SV: GenericFontsService = GenericFontsService::new();
2134}
2135
2136struct GenericFontsService {
2137    serif: LangMap<FontName>,
2138    sans_serif: LangMap<FontName>,
2139    monospace: LangMap<FontName>,
2140    cursive: LangMap<FontName>,
2141    fantasy: LangMap<FontName>,
2142    fallback: LangMap<FontName>,
2143    system_ui: LangMap<FontNames>,
2144}
2145impl GenericFontsService {
2146    fn new() -> Self {
2147        fn default(name: impl Into<FontName>) -> LangMap<FontName> {
2148            let mut f = LangMap::with_capacity(1);
2149            f.insert(lang!(und), name.into());
2150            f
2151        }
2152
2153        let serif = "serif";
2154        let sans_serif = "sans-serif";
2155        let monospace = "monospace";
2156        let cursive = "cursive";
2157        let fantasy = "fantasy";
2158        let fallback = if cfg!(windows) {
2159            "Segoe UI Symbol"
2160        } else if cfg!(target_os = "linux") {
2161            "Standard Symbols PS"
2162        } else {
2163            "sans-serif"
2164        };
2165
2166        let mut system_ui = LangMap::with_capacity(5);
2167
2168        // source: VSCode
2169        // https://github.com/microsoft/vscode/blob/6825c886700ac11d07f7646d8d8119c9cdd9d288/src/vs/code/electron-sandbox/processExplorer/media/processExplorer.css
2170
2171        if cfg!(windows) {
2172            system_ui.insert(
2173                lang!("zh-Hans"),
2174                ["Segoe UI", "Microsoft YaHei", "Segoe Ui Emoji", "sans-serif"].into(),
2175            );
2176            system_ui.insert(
2177                lang!("zh-Hant"),
2178                ["Segoe UI", "Microsoft Jhenghei", "Segoe Ui Emoji", "sans-serif"].into(),
2179            );
2180            system_ui.insert(
2181                lang!("ja"),
2182                ["Segoe UI", "Yu Gothic UI", "Meiryo UI", "Segoe Ui Emoji", "sans-serif"].into(),
2183            );
2184            system_ui.insert(
2185                lang!("ko"),
2186                ["Segoe UI", "Malgun Gothic", "Dotom", "Segoe Ui Emoji", "sans-serif"].into(),
2187            );
2188            system_ui.insert(lang!(und), ["Segoe UI", "Segoe Ui Emoji", "sans-serif"].into());
2189        } else if cfg!(target_os = "macos") {
2190            system_ui.insert(
2191                lang!("zh-Hans"),
2192                ["PingFang SC", "Hiragino Sans GB", "Apple Color Emoji", "sans-serif"].into(),
2193            );
2194            system_ui.insert(lang!("zh-Hant"), ["PingFang TC", "Apple Color Emoji", "sans-serif"].into());
2195            system_ui.insert(lang!("ja"), ["Hiragino Kaku Gothic Pro", "Apple Color Emoji", "sans-serif"].into());
2196            system_ui.insert(
2197                lang!("ko"),
2198                [
2199                    "Nanum Gothic",
2200                    "Apple SD Gothic Neo",
2201                    "AppleGothic",
2202                    "Apple Color Emoji",
2203                    "sans-serif",
2204                ]
2205                .into(),
2206            );
2207            system_ui.insert(
2208                lang!(und),
2209                ["Neue Helvetica", "Lucida Grande", "Apple Color Emoji", "sans-serif"].into(),
2210            );
2211        } else if cfg!(target_os = "linux") {
2212            system_ui.insert(
2213                lang!("zh-Hans"),
2214                [
2215                    "Ubuntu",
2216                    "Droid Sans",
2217                    "Source Han Sans SC",
2218                    "Source Han Sans CN",
2219                    "Source Han Sans",
2220                    "Noto Color Emoji",
2221                    "sans-serif",
2222                ]
2223                .into(),
2224            );
2225            system_ui.insert(
2226                lang!("zh-Hant"),
2227                [
2228                    "Ubuntu",
2229                    "Droid Sans",
2230                    "Source Han Sans TC",
2231                    "Source Han Sans TW",
2232                    "Source Han Sans",
2233                    "Noto Color Emoji",
2234                    "sans-serif",
2235                ]
2236                .into(),
2237            );
2238            system_ui.insert(
2239                lang!("ja"),
2240                [
2241                    "system-ui",
2242                    "Ubuntu",
2243                    "Droid Sans",
2244                    "Source Han Sans J",
2245                    "Source Han Sans JP",
2246                    "Source Han Sans",
2247                    "Noto Color Emoji",
2248                    "sans-serif",
2249                ]
2250                .into(),
2251            );
2252            system_ui.insert(
2253                lang!("ko"),
2254                [
2255                    "system-ui",
2256                    "Ubuntu",
2257                    "Droid Sans",
2258                    "Source Han Sans K",
2259                    "Source Han Sans JR",
2260                    "Source Han Sans",
2261                    "UnDotum",
2262                    "FBaekmuk Gulim",
2263                    "Noto Color Emoji",
2264                    "sans-serif",
2265                ]
2266                .into(),
2267            );
2268            system_ui.insert(
2269                lang!(und),
2270                ["system-ui", "Ubuntu", "Droid Sans", "Noto Color Emoji", "sans-serif"].into(),
2271            );
2272        } else {
2273            system_ui.insert(lang!(und), ["system-ui"].into());
2274        }
2275
2276        GenericFontsService {
2277            serif: default(serif),
2278            sans_serif: default(sans_serif),
2279            monospace: default(monospace),
2280            cursive: default(cursive),
2281            fantasy: default(fantasy),
2282
2283            system_ui,
2284
2285            fallback: default(fallback),
2286        }
2287    }
2288}
2289
2290/// Generic fonts configuration for the app.
2291///
2292/// This type can be accessed from the [`FONTS`] service.
2293///
2294/// # Defaults
2295///
2296/// By default the `serif`, `sans_serif`, `monospace`, `cursive` and `fantasy` are set to their own generic name,
2297/// this delegates the resolution to the operating system. The `set_*` methods can be used to override the default.
2298///
2299/// The default `fallback` font is "Segoe UI Symbol" for Windows, "Standard Symbols PS" for Linux and "sans-serif" for others.
2300///
2301/// See also [`FontNames::system_ui`] for the default font selection for `system-ui`.
2302///
2303/// [`FontNames::system_ui`]: crate::FontNames::system_ui
2304#[non_exhaustive]
2305pub struct GenericFonts {}
2306macro_rules! impl_fallback_accessors {
2307    ($($name:ident=$name_str:tt),+ $(,)?) => {$($crate::paste! {
2308    #[doc = "Gets the *"$name_str "* font for the given language."]
2309    ///
2310    /// Returns a font name for the best `lang` match.
2311    ///
2312    #[doc = "Note that the returned name can still be the generic `\""$name_str "\"`, this delegates the resolution to the operating system."]
2313
2314    pub fn $name(&self, lang: &Lang) -> FontName {
2315        GENERIC_FONTS_SV.read().$name.get(lang).unwrap().clone()
2316    }
2317
2318    #[doc = "Sets the *"$name_str "* font for the given language."]
2319    ///
2320    /// The change is applied for the next update.
2321    ///
2322    /// Use `lang!(und)` to set name used when no language matches.
2323    pub fn [<set_ $name>]<F: Into<FontName>>(&self, lang: Lang, font_name: F) {
2324        self.[<set_ $name _impl>](lang, font_name.into());
2325    }
2326    fn [<set_ $name _impl>](&self, lang: Lang, font_name: FontName) {
2327        UPDATES.once_update("GenericFonts.set", move || {
2328            GENERIC_FONTS_SV.write().$name.insert(lang.clone(), font_name);
2329            FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::GenericFont(FontName::$name(), lang)));
2330        });
2331    }
2332    })+};
2333}
2334impl GenericFonts {
2335    #[rustfmt::skip] // for zng fmt
2336    impl_fallback_accessors! {
2337        serif="serif", sans_serif="sans-serif", monospace="monospace", cursive="cursive", fantasy="fantasy"
2338    }
2339
2340    /// Gets the *"system-ui"* font for the given language.
2341    ///
2342    /// Returns a font name list for the best `lang` match.
2343    ///
2344    /// Note that the returned names can still contain the generic `"system-ui"`, this delegates the resolution to the operating system.
2345    pub fn system_ui(&self, lang: &Lang) -> FontNames {
2346        GENERIC_FONTS_SV.read().system_ui.get(lang).unwrap().clone()
2347    }
2348
2349    /// Sets the *"system-ui"* fonts for a the given language.
2350    ///
2351    /// The change is applied for the next update.
2352    ///
2353    /// Use `lang!(und)` to set fonts used when no language matches.
2354    pub fn set_system_ui(&self, lang: Lang, font_names: impl Into<FontNames>) {
2355        self.set_system_ui_impl(lang, font_names.into())
2356    }
2357    fn set_system_ui_impl(&self, lang: Lang, font_names: FontNames) {
2358        UPDATES.once_update("GenericFonts.set_system_ui", move || {
2359            GENERIC_FONTS_SV.write().system_ui.insert(lang.clone(), font_names);
2360            FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::GenericFont(FontName::system_ui(), lang)));
2361        });
2362    }
2363
2364    /// Gets the ultimate fallback font used when none of the other fonts support a glyph.
2365    ///
2366    /// Returns a font name.
2367    pub fn fallback(&self, lang: &Lang) -> FontName {
2368        GENERIC_FONTS_SV.read().fallback.get(lang).unwrap().clone()
2369    }
2370
2371    /// Sets the ultimate fallback font used when none of other fonts support a glyph.
2372    ///
2373    /// The change applies for the next update.
2374    ///
2375    /// Use `lang!(und)` to set name used when no language matches.
2376    pub fn set_fallback<F: Into<FontName>>(&self, lang: Lang, font_name: F) {
2377        self.set_fallback_impl(lang, font_name.into());
2378    }
2379    fn set_fallback_impl(&self, lang: Lang, font_name: FontName) {
2380        UPDATES.once_update("GenericFonts.set", move || {
2381            GENERIC_FONTS_SV.write().fallback.insert(lang.clone(), font_name);
2382            FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::Fallback(lang)));
2383        });
2384    }
2385
2386    /// Returns the font name registered for the generic `name` and `lang`.
2387    ///
2388    /// Returns `None` if `name` if not one of the generic font names.
2389    ///
2390    /// Note that this does not resolve `"system-ui"`, use [`resolve_list`] for that.
2391    ///
2392    /// [`resolve_list`]: GenericFonts::resolve_list
2393    pub fn resolve(&self, name: &FontName, lang: &Lang) -> Option<FontName> {
2394        match &**name {
2395            "serif" => Some(self.serif(lang)),
2396            "sans-serif" => Some(self.sans_serif(lang)),
2397            "monospace" => Some(self.monospace(lang)),
2398            "cursive" => Some(self.cursive(lang)),
2399            "fantasy" => Some(self.fantasy(lang)),
2400            _ => None,
2401        }
2402    }
2403
2404    /// Returns a new list if any name in `names` can [`resolve`].
2405    ///
2406    /// [`resolve`]: GenericFonts::resolve
2407    pub fn resolve_list(&self, names: &[FontName], lang: &Lang) -> Option<FontNames> {
2408        if names
2409            .iter()
2410            .any(|n| ["system-ui", "serif", "sans-serif", "monospace", "cursive", "fantasy"].contains(&&**n))
2411        {
2412            let mut r = FontNames(Vec::with_capacity(names.len()));
2413            for name in names {
2414                match self.resolve(name, lang) {
2415                    Some(n) => r.push(n),
2416                    None => {
2417                        if name == "system-ui" {
2418                            r.extend(self.system_ui(lang));
2419                        } else {
2420                            r.push(name.clone())
2421                        }
2422                    }
2423                }
2424            }
2425            Some(r)
2426        } else {
2427            None
2428        }
2429    }
2430}
2431
2432#[cfg(not(any(target_arch = "wasm32", target_os = "android")))]
2433pub(crate) enum WeakFontBytes {
2434    Ipc(WeakIpcBytes),
2435    Arc(std::sync::Weak<Vec<u8>>),
2436    Static(&'static [u8]),
2437    Mmap(std::sync::Weak<SystemFontBytes>),
2438}
2439#[cfg(not(any(target_arch = "wasm32", target_os = "android")))]
2440impl WeakFontBytes {
2441    pub(crate) fn upgrade(&self) -> Option<FontBytes> {
2442        match self {
2443            WeakFontBytes::Ipc(weak) => Some(FontBytes(FontBytesImpl::Ipc(weak.upgrade()?))),
2444            WeakFontBytes::Arc(weak) => Some(FontBytes(FontBytesImpl::Arc(weak.upgrade()?))),
2445            WeakFontBytes::Static(b) => Some(FontBytes(FontBytesImpl::Static(b))),
2446            WeakFontBytes::Mmap(weak) => Some(FontBytes(FontBytesImpl::System(weak.upgrade()?))),
2447        }
2448    }
2449
2450    pub(crate) fn strong_count(&self) -> usize {
2451        match self {
2452            WeakFontBytes::Ipc(weak) => weak.strong_count(),
2453            WeakFontBytes::Arc(weak) => weak.strong_count(),
2454            WeakFontBytes::Static(_) => 1,
2455            WeakFontBytes::Mmap(weak) => weak.strong_count(),
2456        }
2457    }
2458}
2459
2460struct SystemFontBytes {
2461    path: std::path::PathBuf,
2462    mmap: IpcBytes,
2463}
2464
2465#[derive(Clone)]
2466enum FontBytesImpl {
2467    /// IpcBytes already clones references, but we need the weak_count for caching
2468    Ipc(IpcBytes),
2469    Arc(Arc<Vec<u8>>),
2470    Static(&'static [u8]),
2471    System(Arc<SystemFontBytes>),
2472}
2473/// Reference to in memory font data.
2474#[derive(Clone)]
2475pub struct FontBytes(FontBytesImpl);
2476impl FontBytes {
2477    /// From shared memory that can be efficiently referenced in the view-process for rendering.
2478    pub fn from_ipc(bytes: IpcBytes) -> Self {
2479        Self(FontBytesImpl::Ipc(bytes))
2480    }
2481
2482    /// Moves data to an [`IpcBytes`] shared reference.
2483    pub fn from_vec(bytes: Vec<u8>) -> io::Result<Self> {
2484        Ok(Self(FontBytesImpl::Ipc(IpcBytes::from_vec_blocking(bytes)?)))
2485    }
2486
2487    /// Uses the reference in the app-process. In case the font needs to be send to view-process turns into [`IpcBytes`].
2488    pub fn from_static(bytes: &'static [u8]) -> Self {
2489        Self(FontBytesImpl::Static(bytes))
2490    }
2491
2492    /// Uses the reference in the app-process. In case the font needs to be send to view-process turns into [`IpcBytes`].
2493    ///
2494    /// Prefer `from_ipc` if you can control the data creation.
2495    pub fn from_arc(bytes: Arc<Vec<u8>>) -> Self {
2496        Self(FontBytesImpl::Arc(bytes))
2497    }
2498
2499    /// If the `path` is in the restricted system fonts directory memory maps it. Otherwise reads into [`IpcBytes`].
2500    pub fn from_file(path: PathBuf) -> io::Result<Self> {
2501        let path = dunce::canonicalize(path)?;
2502
2503        #[cfg(windows)]
2504        {
2505            use windows::Win32::{Foundation::MAX_PATH, System::SystemInformation::GetSystemWindowsDirectoryW};
2506            let mut buffer = [0u16; MAX_PATH as usize];
2507            // SAFETY: Buffer allocated to max possible
2508            let len = unsafe { GetSystemWindowsDirectoryW(Some(&mut buffer)) };
2509            let fonts_dir = String::from_utf16_lossy(&buffer[..len as usize]);
2510            // usually this is: r"C:\Windows\Fonts"
2511            if path.starts_with(fonts_dir) {
2512                // SAFETY: Windows restricts write access to files in this directory.
2513                return unsafe { load_from_system(path) };
2514            }
2515        }
2516        #[cfg(target_os = "macos")]
2517        if path.starts_with("/System/Library/Fonts/") || path.starts_with("/Library/Fonts/") {
2518            // SAFETY: macOS restricts write access to files in this directory.
2519            return unsafe { load_from_system(path) };
2520        }
2521        #[cfg(target_os = "android")]
2522        if path.starts_with("/system/fonts/") || path.starts_with("/system/font/") || path.starts_with("/system/product/fonts/") {
2523            // SAFETY: Android restricts write access to files in this directory.
2524            return unsafe { load_from_system(path) };
2525        }
2526        #[cfg(unix)]
2527        if path.starts_with("/usr/share/fonts/") {
2528            // SAFETY: OS restricts write access to files in this directory.
2529            return unsafe { load_from_system(path) };
2530        }
2531
2532        #[cfg(ipc)]
2533        unsafe fn load_from_system(path: PathBuf) -> io::Result<FontBytes> {
2534            // SAFETY: up to the caller
2535            let mmap = unsafe { IpcBytes::open_memmap_blocking(path.clone(), None) }?;
2536            Ok(FontBytes(FontBytesImpl::System(Arc::new(SystemFontBytes { path, mmap }))))
2537        }
2538
2539        #[cfg(all(not(ipc), not(target_arch = "wasm32")))]
2540        unsafe fn load_from_system(path: PathBuf) -> io::Result<FontBytes> {
2541            let mmap = IpcBytes::from_path_blocking(&path)?;
2542            Ok(FontBytes(FontBytesImpl::System(Arc::new(SystemFontBytes { path, mmap }))))
2543        }
2544
2545        Ok(Self(FontBytesImpl::Ipc(IpcBytes::from_path_blocking(&path)?)))
2546    }
2547
2548    /// Read lock the `path` and memory maps it.
2549    ///
2550    /// # Safety
2551    ///
2552    /// You must ensure the file content does not change. If the file has the same access restrictions as the
2553    /// current executable file you can say it is safe.
2554    #[cfg(ipc)]
2555    pub unsafe fn from_file_mmap(path: PathBuf) -> std::io::Result<Self> {
2556        // SAFETY: up to the caller
2557        let ipc = unsafe { IpcBytes::open_memmap_blocking(path, None) }?;
2558        Ok(Self(FontBytesImpl::Ipc(ipc)))
2559    }
2560
2561    /// File path, if the bytes are memory mapped.
2562    ///
2563    /// Note that the path is read-locked until all clones of `FontBytes` are dropped.
2564    #[cfg(ipc)]
2565    pub fn mmap_path(&self) -> Option<&std::path::Path> {
2566        if let FontBytesImpl::System(m) = &self.0 {
2567            Some(&m.path)
2568        } else {
2569            None
2570        }
2571    }
2572
2573    /// Clone [`IpcBytes`] reference or clone data into a new one.
2574    pub fn to_ipc(&self) -> io::Result<IpcFontBytes> {
2575        Ok(if let FontBytesImpl::System(m) = &self.0 {
2576            IpcFontBytes::System(m.path.clone())
2577        } else {
2578            IpcFontBytes::Bytes(self.to_ipc_bytes()?)
2579        })
2580    }
2581
2582    /// Clone [`IpcBytes`] reference or clone data into a new one.
2583    pub fn to_ipc_bytes(&self) -> io::Result<IpcBytes> {
2584        match &self.0 {
2585            FontBytesImpl::Ipc(b) => Ok(b.clone()),
2586            FontBytesImpl::Arc(b) => IpcBytes::from_slice_blocking(b),
2587            FontBytesImpl::Static(b) => IpcBytes::from_slice_blocking(b),
2588            FontBytesImpl::System(m) => IpcBytes::from_slice_blocking(&m.mmap[..]),
2589        }
2590    }
2591
2592    #[cfg(not(any(target_arch = "wasm32", target_os = "android")))]
2593    pub(crate) fn downgrade(&self) -> WeakFontBytes {
2594        match &self.0 {
2595            FontBytesImpl::Ipc(ipc) => WeakFontBytes::Ipc(ipc.downgrade()),
2596            FontBytesImpl::Arc(arc) => WeakFontBytes::Arc(Arc::downgrade(arc)),
2597            FontBytesImpl::Static(b) => WeakFontBytes::Static(b),
2598            FontBytesImpl::System(arc) => WeakFontBytes::Mmap(Arc::downgrade(arc)),
2599        }
2600    }
2601}
2602impl std::ops::Deref for FontBytes {
2603    type Target = [u8];
2604
2605    fn deref(&self) -> &Self::Target {
2606        match &self.0 {
2607            FontBytesImpl::Ipc(b) => &b[..],
2608            FontBytesImpl::Arc(b) => &b[..],
2609            FontBytesImpl::Static(b) => b,
2610            FontBytesImpl::System(m) => &m.mmap[..],
2611        }
2612    }
2613}
2614impl fmt::Debug for FontBytes {
2615    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2616        let mut b = f.debug_struct("FontBytes");
2617        b.field(
2618            ".kind",
2619            &match &self.0 {
2620                FontBytesImpl::Ipc(_) => "IpcBytes",
2621                FontBytesImpl::Arc(_) => "Arc",
2622                FontBytesImpl::Static(_) => "Static",
2623                FontBytesImpl::System(_) => "Mmap",
2624            },
2625        );
2626        b.field(".len", &self.len().bytes());
2627        if let FontBytesImpl::System(m) = &self.0 {
2628            b.field(".path", &m.path);
2629        }
2630
2631        b.finish()
2632    }
2633}
2634
2635#[derive(Debug, Clone)]
2636enum FontSource {
2637    File(PathBuf, u32),
2638    Memory(FontBytes, u32),
2639    Alias(FontName),
2640}
2641
2642/// Custom font builder.
2643#[derive(Debug, Clone)]
2644pub struct CustomFont {
2645    name: FontName,
2646    source: FontSource,
2647    stretch: FontStretch,
2648    style: FontStyle,
2649    weight: FontWeight,
2650}
2651impl CustomFont {
2652    /// A custom font loaded from a file.
2653    ///
2654    /// If the file is a collection of fonts, `font_index` determines which, otherwise just pass `0`.
2655    ///
2656    /// The font is loaded in [`FONTS.register`].
2657    ///
2658    /// [`FONTS.register`]: FONTS::register
2659    pub fn from_file<N: Into<FontName>, P: Into<PathBuf>>(name: N, path: P, font_index: u32) -> Self {
2660        CustomFont {
2661            name: name.into(),
2662            source: FontSource::File(path.into(), font_index),
2663            stretch: FontStretch::NORMAL,
2664            style: FontStyle::Normal,
2665            weight: FontWeight::NORMAL,
2666        }
2667    }
2668
2669    /// A custom font loaded from a shared byte slice.
2670    ///
2671    /// If the font data is a collection of fonts, `font_index` determines which, otherwise just pass `0`.
2672    ///
2673    /// The font is loaded in [`FONTS.register`].
2674    ///
2675    /// [`FONTS.register`]: FONTS::register
2676    pub fn from_bytes<N: Into<FontName>>(name: N, data: FontBytes, font_index: u32) -> Self {
2677        CustomFont {
2678            name: name.into(),
2679            source: FontSource::Memory(data, font_index),
2680            stretch: FontStretch::NORMAL,
2681            style: FontStyle::Normal,
2682            weight: FontWeight::NORMAL,
2683        }
2684    }
2685
2686    /// A custom font that maps to another font.
2687    ///
2688    /// The font is loaded in [`FONTS.register`].
2689    ///
2690    /// [`FONTS.register`]: FONTS::register
2691    pub fn from_other<N: Into<FontName>, O: Into<FontName>>(name: N, other_font: O) -> Self {
2692        CustomFont {
2693            name: name.into(),
2694            source: FontSource::Alias(other_font.into()),
2695            stretch: FontStretch::NORMAL,
2696            style: FontStyle::Normal,
2697            weight: FontWeight::NORMAL,
2698        }
2699    }
2700
2701    /// Set the [`FontStretch`].
2702    ///
2703    /// Default is [`FontStretch::NORMAL`].
2704    pub fn stretch(mut self, stretch: FontStretch) -> Self {
2705        self.stretch = stretch;
2706        self
2707    }
2708
2709    /// Set the [`FontStyle`].
2710    ///
2711    /// Default is [`FontStyle::Normal`].
2712    pub fn style(mut self, style: FontStyle) -> Self {
2713        self.style = style;
2714        self
2715    }
2716
2717    /// Set the [`FontWeight`].
2718    ///
2719    /// Default is [`FontWeight::NORMAL`].
2720    pub fn weight(mut self, weight: FontWeight) -> Self {
2721        self.weight = weight;
2722        self
2723    }
2724}
2725
2726/// The width of a font as an approximate fraction of the normal width.
2727///
2728/// Widths range from 0.5 to 2.0 inclusive, with 1.0 as the normal width.
2729#[derive(Clone, Copy, serde::Serialize, serde::Deserialize, Transitionable)]
2730#[serde(transparent)]
2731pub struct FontStretch(pub f32);
2732impl fmt::Debug for FontStretch {
2733    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2734        let name = self.name();
2735        if name.is_empty() {
2736            f.debug_tuple("FontStretch").field(&self.0).finish()
2737        } else {
2738            if f.alternate() {
2739                write!(f, "FontStretch::")?;
2740            }
2741            write!(f, "{name}")
2742        }
2743    }
2744}
2745impl PartialOrd for FontStretch {
2746    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
2747        Some(self.cmp(other))
2748    }
2749}
2750impl Ord for FontStretch {
2751    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
2752        about_eq_ord(self.0, other.0, EQ_GRANULARITY)
2753    }
2754}
2755impl PartialEq for FontStretch {
2756    fn eq(&self, other: &Self) -> bool {
2757        about_eq(self.0, other.0, EQ_GRANULARITY)
2758    }
2759}
2760impl Eq for FontStretch {}
2761impl std::hash::Hash for FontStretch {
2762    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
2763        about_eq_hash(self.0, EQ_GRANULARITY, state)
2764    }
2765}
2766impl Default for FontStretch {
2767    fn default() -> FontStretch {
2768        FontStretch::NORMAL
2769    }
2770}
2771impl FontStretch {
2772    /// Ultra-condensed width (50%), the narrowest possible.
2773    pub const ULTRA_CONDENSED: FontStretch = FontStretch(0.5);
2774    /// Extra-condensed width (62.5%).
2775    pub const EXTRA_CONDENSED: FontStretch = FontStretch(0.625);
2776    /// Condensed width (75%).
2777    pub const CONDENSED: FontStretch = FontStretch(0.75);
2778    /// Semi-condensed width (87.5%).
2779    pub const SEMI_CONDENSED: FontStretch = FontStretch(0.875);
2780    /// Normal width (100%).
2781    pub const NORMAL: FontStretch = FontStretch(1.0);
2782    /// Semi-expanded width (112.5%).
2783    pub const SEMI_EXPANDED: FontStretch = FontStretch(1.125);
2784    /// Expanded width (125%).
2785    pub const EXPANDED: FontStretch = FontStretch(1.25);
2786    /// Extra-expanded width (150%).
2787    pub const EXTRA_EXPANDED: FontStretch = FontStretch(1.5);
2788    /// Ultra-expanded width (200%), the widest possible.
2789    pub const ULTRA_EXPANDED: FontStretch = FontStretch(2.0);
2790
2791    /// Gets the const name, if this value is one of the constants.
2792    pub fn name(self) -> &'static str {
2793        macro_rules! name {
2794            ($($CONST:ident;)+) => {$(
2795                if self == Self::$CONST {
2796                    return stringify!($CONST);
2797                }
2798            )+}
2799        }
2800        name! {
2801            ULTRA_CONDENSED;
2802            EXTRA_CONDENSED;
2803            CONDENSED;
2804            SEMI_CONDENSED;
2805            NORMAL;
2806            SEMI_EXPANDED;
2807            EXPANDED;
2808            EXTRA_EXPANDED;
2809            ULTRA_EXPANDED;
2810        }
2811        ""
2812    }
2813}
2814impl_from_and_into_var! {
2815    fn from(fct: Factor) -> FontStretch {
2816        FontStretch(fct.0)
2817    }
2818    fn from(pct: FactorPercent) -> FontStretch {
2819        FontStretch(pct.fct().0)
2820    }
2821    fn from(fct: f32) -> FontStretch {
2822        FontStretch(fct)
2823    }
2824}
2825impl From<ttf_parser::Width> for FontStretch {
2826    fn from(value: ttf_parser::Width) -> Self {
2827        use ttf_parser::Width::*;
2828        match value {
2829            UltraCondensed => FontStretch::ULTRA_CONDENSED,
2830            ExtraCondensed => FontStretch::EXTRA_CONDENSED,
2831            Condensed => FontStretch::CONDENSED,
2832            SemiCondensed => FontStretch::SEMI_CONDENSED,
2833            Normal => FontStretch::NORMAL,
2834            SemiExpanded => FontStretch::SEMI_EXPANDED,
2835            Expanded => FontStretch::EXPANDED,
2836            ExtraExpanded => FontStretch::EXTRA_EXPANDED,
2837            UltraExpanded => FontStretch::ULTRA_EXPANDED,
2838        }
2839    }
2840}
2841impl From<FontStretch> for ttf_parser::Width {
2842    fn from(value: FontStretch) -> Self {
2843        if value <= FontStretch::ULTRA_CONDENSED {
2844            ttf_parser::Width::UltraCondensed
2845        } else if value <= FontStretch::EXTRA_CONDENSED {
2846            ttf_parser::Width::ExtraCondensed
2847        } else if value <= FontStretch::CONDENSED {
2848            ttf_parser::Width::Condensed
2849        } else if value <= FontStretch::SEMI_CONDENSED {
2850            ttf_parser::Width::SemiCondensed
2851        } else if value <= FontStretch::NORMAL {
2852            ttf_parser::Width::Normal
2853        } else if value <= FontStretch::SEMI_EXPANDED {
2854            ttf_parser::Width::SemiExpanded
2855        } else if value <= FontStretch::EXPANDED {
2856            ttf_parser::Width::Expanded
2857        } else if value <= FontStretch::EXTRA_EXPANDED {
2858            ttf_parser::Width::ExtraExpanded
2859        } else {
2860            ttf_parser::Width::UltraExpanded
2861        }
2862    }
2863}
2864
2865/// The italic or oblique form of a font.
2866#[derive(Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]
2867pub enum FontStyle {
2868    /// The regular form.
2869    #[default]
2870    Normal,
2871    /// A form that is generally cursive in nature.
2872    Italic,
2873    /// A skewed version of the regular form.
2874    Oblique,
2875}
2876impl fmt::Debug for FontStyle {
2877    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2878        if f.alternate() {
2879            write!(f, "FontStyle::")?;
2880        }
2881        match self {
2882            Self::Normal => write!(f, "Normal"),
2883            Self::Italic => write!(f, "Italic"),
2884            Self::Oblique => write!(f, "Oblique"),
2885        }
2886    }
2887}
2888impl From<ttf_parser::Style> for FontStyle {
2889    fn from(value: ttf_parser::Style) -> Self {
2890        use ttf_parser::Style::*;
2891        match value {
2892            Normal => FontStyle::Normal,
2893            Italic => FontStyle::Italic,
2894            Oblique => FontStyle::Oblique,
2895        }
2896    }
2897}
2898
2899impl From<FontStyle> for ttf_parser::Style {
2900    fn from(value: FontStyle) -> Self {
2901        match value {
2902            FontStyle::Normal => Self::Normal,
2903            FontStyle::Italic => Self::Italic,
2904            FontStyle::Oblique => Self::Oblique,
2905        }
2906    }
2907}
2908
2909/// The degree of stroke thickness of a font. This value ranges from 100.0 to 900.0,
2910/// with 400.0 as normal.
2911#[derive(Clone, Copy, Transitionable, serde::Serialize, serde::Deserialize)]
2912pub struct FontWeight(pub f32);
2913impl Default for FontWeight {
2914    fn default() -> FontWeight {
2915        FontWeight::NORMAL
2916    }
2917}
2918impl fmt::Debug for FontWeight {
2919    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2920        let name = self.name();
2921        if name.is_empty() {
2922            f.debug_tuple("FontWeight").field(&self.0).finish()
2923        } else {
2924            if f.alternate() {
2925                write!(f, "FontWeight::")?;
2926            }
2927            write!(f, "{name}")
2928        }
2929    }
2930}
2931impl PartialOrd for FontWeight {
2932    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
2933        Some(self.cmp(other))
2934    }
2935}
2936impl Ord for FontWeight {
2937    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
2938        about_eq_ord(self.0, other.0, EQ_GRANULARITY_100)
2939    }
2940}
2941impl PartialEq for FontWeight {
2942    fn eq(&self, other: &Self) -> bool {
2943        about_eq(self.0, other.0, EQ_GRANULARITY_100)
2944    }
2945}
2946impl Eq for FontWeight {}
2947impl std::hash::Hash for FontWeight {
2948    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
2949        about_eq_hash(self.0, EQ_GRANULARITY_100, state)
2950    }
2951}
2952impl FontWeight {
2953    /// Thin weight (100), the thinnest value.
2954    pub const THIN: FontWeight = FontWeight(100.0);
2955    /// Extra light weight (200).
2956    pub const EXTRA_LIGHT: FontWeight = FontWeight(200.0);
2957    /// Light weight (300).
2958    pub const LIGHT: FontWeight = FontWeight(300.0);
2959    /// Normal (400).
2960    pub const NORMAL: FontWeight = FontWeight(400.0);
2961    /// Medium weight (500, higher than normal).
2962    pub const MEDIUM: FontWeight = FontWeight(500.0);
2963    /// Semi-bold weight (600).
2964    pub const SEMIBOLD: FontWeight = FontWeight(600.0);
2965    /// Bold weight (700).
2966    pub const BOLD: FontWeight = FontWeight(700.0);
2967    /// Extra-bold weight (800).
2968    pub const EXTRA_BOLD: FontWeight = FontWeight(800.0);
2969    /// Black weight (900), the thickest value.
2970    pub const BLACK: FontWeight = FontWeight(900.0);
2971
2972    /// Gets the const name, if this value is one of the constants.
2973    pub fn name(self) -> &'static str {
2974        macro_rules! name {
2975                ($($CONST:ident;)+) => {$(
2976                    if self == Self::$CONST {
2977                        return stringify!($CONST);
2978                    }
2979                )+}
2980            }
2981        name! {
2982            THIN;
2983            EXTRA_LIGHT;
2984            LIGHT;
2985            NORMAL;
2986            MEDIUM;
2987            SEMIBOLD;
2988            BOLD;
2989            EXTRA_BOLD;
2990            BLACK;
2991        }
2992        ""
2993    }
2994}
2995impl_from_and_into_var! {
2996    fn from(weight: u32) -> FontWeight {
2997        FontWeight(weight as f32)
2998    }
2999    fn from(weight: f32) -> FontWeight {
3000        FontWeight(weight)
3001    }
3002}
3003impl From<ttf_parser::Weight> for FontWeight {
3004    fn from(value: ttf_parser::Weight) -> Self {
3005        use ttf_parser::Weight::*;
3006        match value {
3007            Thin => FontWeight::THIN,
3008            ExtraLight => FontWeight::EXTRA_LIGHT,
3009            Light => FontWeight::LIGHT,
3010            Normal => FontWeight::NORMAL,
3011            Medium => FontWeight::MEDIUM,
3012            SemiBold => FontWeight::SEMIBOLD,
3013            Bold => FontWeight::BOLD,
3014            ExtraBold => FontWeight::EXTRA_BOLD,
3015            Black => FontWeight::BLACK,
3016            Other(o) => FontWeight(o as f32),
3017        }
3018    }
3019}
3020impl From<FontWeight> for ttf_parser::Weight {
3021    fn from(value: FontWeight) -> Self {
3022        ttf_parser::Weight::from(value.0 as u16)
3023    }
3024}
3025
3026/// Configuration of text wrapping for Chinese, Japanese, or Korean text.
3027#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
3028pub enum LineBreak {
3029    /// The same rule used by other languages.
3030    Auto,
3031    /// The least restrictive rule, good for short lines.
3032    Loose,
3033    /// The most common rule.
3034    Normal,
3035    /// The most stringent rule.
3036    Strict,
3037    /// Allow line breaks in between any character including punctuation.
3038    Anywhere,
3039}
3040impl Default for LineBreak {
3041    /// [`LineBreak::Auto`]
3042    fn default() -> Self {
3043        LineBreak::Auto
3044    }
3045}
3046impl fmt::Debug for LineBreak {
3047    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3048        if f.alternate() {
3049            write!(f, "LineBreak::")?;
3050        }
3051        match self {
3052            LineBreak::Auto => write!(f, "Auto"),
3053            LineBreak::Loose => write!(f, "Loose"),
3054            LineBreak::Normal => write!(f, "Normal"),
3055            LineBreak::Strict => write!(f, "Strict"),
3056            LineBreak::Anywhere => write!(f, "Anywhere"),
3057        }
3058    }
3059}
3060
3061/// Definition of how text is split into paragraphs.
3062///
3063/// In the core text shaping this affects paragraph spacing and indent. Rich text widgets
3064/// may also use this when defining their own paragraph segmentation.
3065#[derive(Default, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
3066#[non_exhaustive]
3067pub enum ParagraphBreak {
3068    /// The entire text is a single paragraph.
3069    #[default]
3070    None,
3071    /// Each actual line is a paragraph. That is `\n` is the paragraph break.
3072    Line,
3073}
3074impl fmt::Debug for ParagraphBreak {
3075    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3076        if f.alternate() {
3077            write!(f, "ParagraphBreak::")?;
3078        }
3079        match self {
3080            ParagraphBreak::None => write!(f, "None"),
3081            ParagraphBreak::Line => write!(f, "Line"),
3082        }
3083    }
3084}
3085
3086/// Hyphenation mode.
3087#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
3088pub enum Hyphens {
3089    /// Hyphens are never inserted in word breaks.
3090    None,
3091    /// Word breaks only happen in specially marked break characters: `-` and `\u{00AD} SHY`.
3092    ///
3093    /// * `U+2010` - The visible hyphen character.
3094    /// * `U+00AD` - The invisible hyphen character, is made visible in a word break.
3095    Manual,
3096    /// Hyphens are inserted like `Manual` and also using language specific hyphenation rules.
3097    Auto,
3098}
3099impl Default for Hyphens {
3100    /// [`Hyphens::Auto`]
3101    fn default() -> Self {
3102        Hyphens::Auto
3103    }
3104}
3105impl fmt::Debug for Hyphens {
3106    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3107        if f.alternate() {
3108            write!(f, "Hyphens::")?;
3109        }
3110        match self {
3111            Hyphens::None => write!(f, "None"),
3112            Hyphens::Manual => write!(f, "Manual"),
3113            Hyphens::Auto => write!(f, "Auto"),
3114        }
3115    }
3116}
3117
3118/// Configure line breaks inside words during text wrap.
3119///
3120/// This value is only considered if it is impossible to fit a full word to a line.
3121///
3122/// Hyphens can be inserted in word breaks using the [`Hyphens`] configuration.
3123#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
3124pub enum WordBreak {
3125    /// Line breaks can be inserted in between letters of Chinese/Japanese/Korean text only.
3126    Normal,
3127    /// Line breaks can be inserted between any letter.
3128    BreakAll,
3129    /// Line breaks are not inserted between any letter.
3130    KeepAll,
3131}
3132impl Default for WordBreak {
3133    /// [`WordBreak::Normal`]
3134    fn default() -> Self {
3135        WordBreak::Normal
3136    }
3137}
3138impl fmt::Debug for WordBreak {
3139    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3140        if f.alternate() {
3141            write!(f, "WordBreak::")?;
3142        }
3143        match self {
3144            WordBreak::Normal => write!(f, "Normal"),
3145            WordBreak::BreakAll => write!(f, "BreakAll"),
3146            WordBreak::KeepAll => write!(f, "KeepAll"),
3147        }
3148    }
3149}
3150
3151/// Text alignment justification mode.
3152#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
3153pub enum Justify {
3154    /// Selects the justification mode based on the language.
3155    ///
3156    /// For Chinese/Japanese/Korean uses `InterLetter` for the others uses `InterWord`.
3157    Auto,
3158    /// The text is justified by adding space between words.
3159    InterWord,
3160    /// The text is justified by adding space between letters.
3161    InterLetter,
3162}
3163impl Default for Justify {
3164    /// [`Justify::Auto`]
3165    fn default() -> Self {
3166        Justify::Auto
3167    }
3168}
3169impl Justify {
3170    /// Resolve `Auto` for the given language.
3171    pub fn resolve(self, lang: &Lang) -> Self {
3172        match self {
3173            Self::Auto => match lang.language.as_str() {
3174                "zh" | "ja" | "ko" => Self::InterLetter,
3175                _ => Self::InterWord,
3176            },
3177            m => m,
3178        }
3179    }
3180}
3181impl fmt::Debug for Justify {
3182    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3183        if f.alternate() {
3184            write!(f, "Justify::")?;
3185        }
3186        match self {
3187            Justify::Auto => write!(f, "Auto"),
3188            Justify::InterWord => write!(f, "InterWord"),
3189            Justify::InterLetter => write!(f, "InterLetter"),
3190        }
3191    }
3192}
3193
3194/// Various metrics that apply to the entire [`FontFace`].
3195///
3196/// For OpenType fonts, these mostly come from the `OS/2` table.
3197///
3198/// See the [`FreeType Glyph Metrics`] documentation for an explanation of the various metrics.
3199///
3200/// [`FreeType Glyph Metrics`]: https://freetype.org/freetype2/docs/glyphs/glyphs-3.html
3201#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
3202#[non_exhaustive]
3203pub struct FontFaceMetrics {
3204    /// The number of font units per em.
3205    ///
3206    /// Font sizes are usually expressed in pixels per em; e.g. `12px` means 12 pixels per em.
3207    pub units_per_em: u32,
3208
3209    /// The maximum amount the font rises above the baseline, in font units.
3210    pub ascent: f32,
3211
3212    /// The maximum amount the font descends below the baseline, in font units.
3213    ///
3214    /// This is typically a negative value to match the definition of `sTypoDescender` in the
3215    /// `OS/2` table in the OpenType specification. If you are used to using Windows or Mac APIs,
3216    /// beware, as the sign is reversed from what those APIs return.
3217    pub descent: f32,
3218
3219    /// Distance between baselines, in font units.
3220    pub line_gap: f32,
3221
3222    /// The suggested distance of the top of the underline from the baseline (negative values
3223    /// indicate below baseline), in font units.
3224    pub underline_position: f32,
3225
3226    /// A suggested value for the underline thickness, in font units.
3227    pub underline_thickness: f32,
3228
3229    /// The approximate amount that uppercase letters rise above the baseline, in font units.
3230    pub cap_height: f32,
3231
3232    /// The approximate amount that non-ascending lowercase letters rise above the baseline, in
3233    /// font units.
3234    pub x_height: f32,
3235
3236    /// A rectangle that surrounds all bounding boxes of all glyphs, in font units.
3237    ///
3238    /// This corresponds to the `xMin`/`xMax`/`yMin`/`yMax` values in the OpenType `head` table.
3239    pub bounds: euclid::Rect<f32, ()>,
3240}
3241impl FontFaceMetrics {
3242    /// Compute [`FontMetrics`] given a font size in pixels.
3243    pub fn sized(&self, font_size_px: Px) -> FontMetrics {
3244        let size_scale = 1.0 / self.units_per_em as f32 * font_size_px.0 as f32;
3245        let s = move |f: f32| Px((f * size_scale).round() as i32);
3246        FontMetrics {
3247            size_scale,
3248            ascent: s(self.ascent),
3249            descent: s(self.descent),
3250            line_gap: s(self.line_gap),
3251            underline_position: s(self.underline_position),
3252            underline_thickness: s(self.underline_thickness),
3253            cap_height: s(self.cap_height),
3254            x_height: (s(self.x_height)),
3255            bounds: {
3256                let b = self.bounds;
3257                PxRect::new(
3258                    PxPoint::new(s(b.origin.x), s(b.origin.y)),
3259                    PxSize::new(s(b.size.width), s(b.size.height)),
3260                )
3261            },
3262        }
3263    }
3264}
3265
3266/// Various metrics about a [`Font`].
3267///
3268/// You can compute these metrics from a [`FontFaceMetrics`]
3269#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
3270#[non_exhaustive]
3271pub struct FontMetrics {
3272    /// Multiply this to a font EM value to get the size in pixels.
3273    pub size_scale: f32,
3274
3275    /// The maximum amount the font rises above the baseline, in pixels.
3276    pub ascent: Px,
3277
3278    /// The maximum amount the font descends below the baseline, in pixels.
3279    ///
3280    /// This is typically a negative value to match the definition of `sTypoDescender` in the
3281    /// `OS/2` table in the OpenType specification. If you are used to using Windows or Mac APIs,
3282    /// beware, as the sign is reversed from what those APIs return.
3283    pub descent: Px,
3284
3285    /// Distance between baselines, in pixels.
3286    pub line_gap: Px,
3287
3288    /// The suggested distance of the top of the underline from the baseline (negative values
3289    /// indicate below baseline), in pixels.
3290    pub underline_position: Px,
3291
3292    /// A suggested value for the underline thickness, in pixels.
3293    pub underline_thickness: Px,
3294
3295    /// The approximate amount that uppercase letters rise above the baseline, in pixels.
3296    pub cap_height: Px,
3297
3298    /// The approximate amount that non-ascending lowercase letters rise above the baseline, in pixels.
3299    pub x_height: Px,
3300
3301    /// A rectangle that surrounds all bounding boxes of all glyphs, in pixels.
3302    ///
3303    /// This corresponds to the `xMin`/`xMax`/`yMin`/`yMax` values in the OpenType `head` table.
3304    pub bounds: PxRect,
3305}
3306impl FontMetrics {
3307    /// The font line height.
3308    pub fn line_height(&self) -> Px {
3309        self.ascent - self.descent + self.line_gap
3310    }
3311}
3312
3313/// Text transform function.
3314#[derive(Clone)]
3315pub enum TextTransformFn {
3316    /// No transform.
3317    None,
3318    /// To UPPERCASE.
3319    Uppercase,
3320    /// to lowercase.
3321    Lowercase,
3322    /// Custom transform function.
3323    Custom(Arc<dyn Fn(&Txt) -> Cow<Txt> + Send + Sync>),
3324}
3325impl TextTransformFn {
3326    /// Apply the text transform.
3327    ///
3328    /// Returns [`Cow::Owned`] if the text was changed.
3329    pub fn transform<'t>(&self, text: &'t Txt) -> Cow<'t, Txt> {
3330        match self {
3331            TextTransformFn::None => Cow::Borrowed(text),
3332            TextTransformFn::Uppercase => {
3333                if text.chars().any(|c| !c.is_uppercase()) {
3334                    Cow::Owned(text.to_uppercase().into())
3335                } else {
3336                    Cow::Borrowed(text)
3337                }
3338            }
3339            TextTransformFn::Lowercase => {
3340                if text.chars().any(|c| !c.is_lowercase()) {
3341                    Cow::Owned(text.to_lowercase().into())
3342                } else {
3343                    Cow::Borrowed(text)
3344                }
3345            }
3346            TextTransformFn::Custom(fn_) => fn_(text),
3347        }
3348    }
3349
3350    /// New [`Custom`](Self::Custom).
3351    pub fn custom(fn_: impl Fn(&Txt) -> Cow<Txt> + Send + Sync + 'static) -> Self {
3352        TextTransformFn::Custom(Arc::new(fn_))
3353    }
3354}
3355impl fmt::Debug for TextTransformFn {
3356    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3357        if f.alternate() {
3358            write!(f, "TextTransformFn::")?;
3359        }
3360        match self {
3361            TextTransformFn::None => write!(f, "None"),
3362            TextTransformFn::Uppercase => write!(f, "Uppercase"),
3363            TextTransformFn::Lowercase => write!(f, "Lowercase"),
3364            TextTransformFn::Custom(_) => write!(f, "Custom"),
3365        }
3366    }
3367}
3368impl PartialEq for TextTransformFn {
3369    fn eq(&self, other: &Self) -> bool {
3370        match (self, other) {
3371            (Self::Custom(l0), Self::Custom(r0)) => Arc::ptr_eq(l0, r0),
3372            _ => core::mem::discriminant(self) == core::mem::discriminant(other),
3373        }
3374    }
3375}
3376
3377/// Text white space transform.
3378#[derive(Default, Copy, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
3379pub enum WhiteSpace {
3380    /// Text is not changed, all white spaces and line breaks are preserved.
3381    #[default]
3382    Preserve,
3383    /// Replace white space sequences with a single `' '` and trim lines.
3384    /// Replace line break sequences with a single `'\n'` and trim text.
3385    Merge,
3386    /// Replace white space sequences with a single `' '` and trim lines.
3387    /// Remove single line breaks. Replace line break sequences (>1) with a single `'\n'` and trim text.
3388    MergeParagraph,
3389    /// Replace white spaces and line breaks sequences with a single `' '` and trim the text.
3390    MergeAll,
3391}
3392impl WhiteSpace {
3393    /// Transform the white space of the text.
3394    ///
3395    /// Returns [`Cow::Owned`] if the text was changed.
3396    pub fn transform(self, text: &Txt) -> Cow<'_, Txt> {
3397        match self {
3398            WhiteSpace::Preserve => Cow::Borrowed(text),
3399            WhiteSpace::Merge => {
3400                // search first repeat
3401                let mut prev_i = 0;
3402                for line in text.split_inclusive('\n') {
3403                    // try trim
3404                    let line_exclusive = line.trim_end_matches('\n').trim_end_matches('\r');
3405                    let line_trim = line_exclusive.trim();
3406                    let mut merge = line_trim.len() != line_exclusive.len() || line_trim.is_empty();
3407
3408                    // try sequence of spaces
3409                    if !merge {
3410                        let mut prev_is_space = true; // start true to trim
3411                        for c in line.chars() {
3412                            let is_space = c.is_whitespace();
3413                            if prev_is_space && is_space {
3414                                merge = true;
3415                                break;
3416                            }
3417                            prev_is_space = is_space;
3418                        }
3419                    }
3420
3421                    if !merge {
3422                        prev_i += line.len();
3423                        continue;
3424                    }
3425
3426                    // found repeat, enter merge mode
3427                    let mut out = String::with_capacity(text.len() - 1);
3428                    out.push_str(&text[..prev_i]);
3429
3430                    let mut chars = text[prev_i..].chars();
3431                    let mut prev_is_space = true;
3432                    let mut prev_is_break = true;
3433                    while let Some(c) = chars.next() {
3434                        if c == '\r'
3435                            && let Some(nc) = chars.next()
3436                        {
3437                            if nc == '\n' {
3438                                if !prev_is_break && !out.is_empty() {
3439                                    out.push('\n');
3440                                }
3441                                prev_is_break = true;
3442                                prev_is_space = true;
3443                            } else {
3444                                out.push(c);
3445                                out.push(nc);
3446                                prev_is_break = false;
3447                                prev_is_space = nc.is_whitespace();
3448                            }
3449                        } else if c == '\n' {
3450                            if !prev_is_break && !out.is_empty() {
3451                                out.push('\n');
3452                            }
3453                            prev_is_break = true;
3454                            prev_is_space = true;
3455                        } else if c.is_whitespace() {
3456                            if prev_is_space {
3457                                continue;
3458                            }
3459                            out.push(' ');
3460                            prev_is_space = true;
3461                        } else {
3462                            out.push(c);
3463                            prev_is_space = false;
3464                            prev_is_break = false;
3465                        }
3466                    }
3467
3468                    // trim end
3469                    if let Some((i, c)) = out.char_indices().rev().find(|(_, c)| !c.is_whitespace()) {
3470                        out.truncate(i + c.len_utf8());
3471                    }
3472
3473                    return Cow::Owned(out.into());
3474                }
3475                Cow::Borrowed(text)
3476            }
3477            WhiteSpace::MergeParagraph => {
3478                // needs to merge if contains '\n' because it is either removed or merged
3479                // also needs to merge it needs to trim.
3480                let mut merge = text.contains('\n') || text.chars().last().unwrap_or('\0').is_whitespace();
3481                if !merge {
3482                    let mut prev_is_space = true;
3483                    for c in text.chars() {
3484                        let is_space = c.is_whitespace();
3485                        if prev_is_space && is_space {
3486                            merge = true;
3487                            break;
3488                        }
3489                        prev_is_space = is_space;
3490                    }
3491                }
3492
3493                if merge {
3494                    let mut out = String::with_capacity(text.len());
3495                    let mut prev_is_break = false;
3496                    for line in text.lines() {
3497                        let line = line.trim();
3498                        let is_break = line.is_empty();
3499                        if !prev_is_break && is_break && !out.is_empty() {
3500                            out.push('\n');
3501                        }
3502                        if !prev_is_break && !is_break && !out.is_empty() {
3503                            out.push(' ');
3504                        }
3505                        prev_is_break = is_break;
3506
3507                        let mut prev_is_space = false;
3508                        for c in line.chars() {
3509                            let is_space = c.is_whitespace();
3510                            if is_space {
3511                                if !prev_is_space {
3512                                    out.push(' ');
3513                                }
3514                            } else {
3515                                out.push(c);
3516                            }
3517                            prev_is_space = is_space;
3518                        }
3519                    }
3520
3521                    // trim end
3522                    if let Some((i, c)) = out.char_indices().rev().find(|(_, c)| !c.is_whitespace()) {
3523                        out.truncate(i + c.len_utf8());
3524                    }
3525
3526                    return Cow::Owned(out.into());
3527                }
3528                Cow::Borrowed(text)
3529            }
3530            WhiteSpace::MergeAll => {
3531                // search first repeat
3532                let mut prev_i = 0;
3533                let mut prev_is_space = true; // starts true to trim
3534                for (i, c) in text.char_indices() {
3535                    let is_space = c.is_whitespace();
3536                    if prev_is_space && is_space || c == '\n' {
3537                        if !prev_is_space {
3538                            debug_assert_eq!(c, '\n');
3539                            prev_i += c.len_utf8();
3540                            prev_is_space = true;
3541                        }
3542                        // found repeat, enter merge mode
3543                        let mut out = String::with_capacity(text.len() - 1);
3544                        // push ok start or trim start
3545                        out.push_str(&text[..prev_i]);
3546                        if !out.is_empty() {
3547                            out.push(' ');
3548                        }
3549                        // collapse other whitespace sequences
3550                        for c in text[(i + c.len_utf8())..].chars() {
3551                            let is_space = c.is_whitespace();
3552                            if prev_is_space && is_space {
3553                                continue;
3554                            }
3555                            out.push(if is_space { ' ' } else { c });
3556                            prev_is_space = is_space;
3557                        }
3558
3559                        // trim end
3560                        if let Some((i, c)) = out.char_indices().rev().find(|(_, c)| !c.is_whitespace()) {
3561                            out.truncate(i + c.len_utf8());
3562                        }
3563
3564                        return Cow::Owned(out.into());
3565                    }
3566                    prev_i = i;
3567                    prev_is_space = is_space;
3568                }
3569
3570                // search did not trim start nor collapse whitespace sequences
3571
3572                // try trim end
3573                let out = text.trim_end();
3574                if out.len() != text.len() {
3575                    return Cow::Owned(Txt::from_str(out));
3576                }
3577
3578                Cow::Borrowed(text)
3579            }
3580        }
3581    }
3582}
3583impl fmt::Debug for WhiteSpace {
3584    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3585        if f.alternate() {
3586            write!(f, "WhiteSpace::")?;
3587        }
3588        match self {
3589            WhiteSpace::Preserve => write!(f, "Preserve"),
3590            WhiteSpace::Merge => write!(f, "Merge"),
3591            WhiteSpace::MergeAll => write!(f, "MergeAll"),
3592            WhiteSpace::MergeParagraph => write!(f, "MergeParagraph"),
3593        }
3594    }
3595}
3596
3597/// Defines an insert offset in a shaped text.
3598#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
3599pub struct CaretIndex {
3600    /// Char byte offset in the full text.
3601    ///
3602    /// This index can be computed using the [`SegmentedText`].
3603    pub index: usize,
3604    /// Line index in the shaped text.
3605    ///
3606    /// This value is only used to disambiguate between the *end* of a wrap and
3607    /// the *start* of the next, the text itself does not have any line
3608    /// break but visually the user interacts with two lines. Note that this
3609    /// counts wrap lines, and that this value is not required to define a valid
3610    /// CaretIndex.
3611    ///
3612    /// This index can be computed using the [`ShapedText::snap_caret_line`].
3613    pub line: usize,
3614}
3615
3616impl PartialEq for CaretIndex {
3617    fn eq(&self, other: &Self) -> bool {
3618        self.index == other.index
3619    }
3620}
3621impl Eq for CaretIndex {}
3622impl CaretIndex {
3623    /// First position.
3624    pub const ZERO: CaretIndex = CaretIndex { index: 0, line: 0 };
3625}
3626impl PartialOrd for CaretIndex {
3627    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
3628        Some(self.cmp(other))
3629    }
3630}
3631impl Ord for CaretIndex {
3632    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
3633        self.index.cmp(&other.index)
3634    }
3635}
3636
3637/// Reasons why a loader might fail to load a font.
3638#[derive(Debug, Clone)]
3639#[non_exhaustive]
3640pub enum FontLoadingError {
3641    /// The data was of a format the loader didn't recognize.
3642    UnknownFormat,
3643    /// Attempted to load an invalid index in a TrueType or OpenType font collection.
3644    ///
3645    /// For example, if a `.ttc` file has 2 fonts in it, and you ask for the 5th one, you'll get
3646    /// this error.
3647    NoSuchFontInCollection,
3648    /// Attempted to load a malformed or corrupted font.
3649    Parse(ttf_parser::FaceParsingError),
3650    /// Attempted to load a font from the filesystem, but there is no filesystem (e.g. in
3651    /// WebAssembly).
3652    NoFilesystem,
3653    /// A disk or similar I/O error occurred while attempting to load the font.
3654    Io(Arc<std::io::Error>),
3655}
3656impl PartialEq for FontLoadingError {
3657    fn eq(&self, other: &Self) -> bool {
3658        match (self, other) {
3659            (Self::Io(l0), Self::Io(r0)) => Arc::ptr_eq(l0, r0),
3660            _ => core::mem::discriminant(self) == core::mem::discriminant(other),
3661        }
3662    }
3663}
3664impl From<std::io::Error> for FontLoadingError {
3665    fn from(error: std::io::Error) -> FontLoadingError {
3666        Self::Io(Arc::new(error))
3667    }
3668}
3669impl fmt::Display for FontLoadingError {
3670    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3671        match self {
3672            Self::UnknownFormat => write!(f, "unknown format"),
3673            Self::NoSuchFontInCollection => write!(f, "no such font in the collection"),
3674            Self::NoFilesystem => write!(f, "no filesystem present"),
3675            Self::Parse(e) => fmt::Display::fmt(e, f),
3676            Self::Io(e) => fmt::Display::fmt(e, f),
3677        }
3678    }
3679}
3680impl std::error::Error for FontLoadingError {
3681    fn cause(&self) -> Option<&dyn std::error::Error> {
3682        match self {
3683            FontLoadingError::Parse(e) => Some(e),
3684            FontLoadingError::Io(e) => Some(e),
3685            _ => None,
3686        }
3687    }
3688}
3689
3690#[cfg(test)]
3691mod tests {
3692    use zng_app::APP;
3693
3694    use super::*;
3695
3696    #[test]
3697    fn generic_fonts_default() {
3698        let _app = APP.minimal().run_headless(false);
3699
3700        assert_eq!(FontName::sans_serif(), GenericFonts {}.sans_serif(&lang!(und)))
3701    }
3702
3703    #[test]
3704    fn generic_fonts_fallback() {
3705        let _app = APP.minimal().run_headless(false);
3706
3707        assert_eq!(FontName::sans_serif(), GenericFonts {}.sans_serif(&lang!(en_US)));
3708        assert_eq!(FontName::sans_serif(), GenericFonts {}.sans_serif(&lang!(es)));
3709    }
3710
3711    #[test]
3712    fn generic_fonts_get1() {
3713        let mut app = APP.minimal().run_headless(false);
3714        GenericFonts {}.set_sans_serif(lang!(en_US), "Test Value");
3715        app.update(false).assert_wait();
3716
3717        assert_eq!(&GenericFonts {}.sans_serif(&lang!("en-US")), "Test Value");
3718        assert_eq!(&GenericFonts {}.sans_serif(&lang!("en")), "Test Value");
3719    }
3720
3721    #[test]
3722    fn generic_fonts_get2() {
3723        let mut app = APP.minimal().run_headless(false);
3724        GenericFonts {}.set_sans_serif(lang!(en), "Test Value");
3725        app.update(false).assert_wait();
3726
3727        assert_eq!(&GenericFonts {}.sans_serif(&lang!("en-US")), "Test Value");
3728        assert_eq!(&GenericFonts {}.sans_serif(&lang!("en")), "Test Value");
3729    }
3730
3731    #[test]
3732    fn generic_fonts_get_best() {
3733        let mut app = APP.minimal().run_headless(false);
3734        GenericFonts {}.set_sans_serif(lang!(en), "Test Value");
3735        GenericFonts {}.set_sans_serif(lang!(en_US), "Best");
3736        app.update(false).assert_wait();
3737
3738        assert_eq!(&GenericFonts {}.sans_serif(&lang!("en-US")), "Best");
3739        assert_eq!(&GenericFonts {}.sans_serif(&lang!("en")), "Test Value");
3740        assert_eq!(&GenericFonts {}.sans_serif(&lang!("und")), "sans-serif");
3741    }
3742
3743    #[test]
3744    fn generic_fonts_get_no_lang_match() {
3745        let mut app = APP.minimal().run_headless(false);
3746        GenericFonts {}.set_sans_serif(lang!(es_US), "Test Value");
3747        app.update(false).assert_wait();
3748
3749        assert_eq!(&GenericFonts {}.sans_serif(&lang!("en-US")), "sans-serif");
3750        assert_eq!(&GenericFonts {}.sans_serif(&lang!("es")), "Test Value");
3751    }
3752
3753    #[test]
3754    fn white_space_merge() {
3755        macro_rules! test {
3756            ($input:tt, $output:tt) => {
3757                let input = Txt::from($input);
3758                let output = WhiteSpace::Merge.transform(&input);
3759                assert_eq!($output, output.as_str());
3760
3761                let input = input.replace('\n', "\r\n");
3762                let output = WhiteSpace::Merge.transform(&Txt::from(input)).replace("\r\n", "\n");
3763                assert_eq!($output, output.as_str());
3764            };
3765        }
3766        test!("a  b\n\nc", "a b\nc");
3767        test!("a b\nc", "a b\nc");
3768        test!(" a b\nc\n  \n", "a b\nc");
3769        test!(" \n a b\nc", "a b\nc");
3770        test!("a\n \nb", "a\nb");
3771    }
3772
3773    #[test]
3774    fn white_space_merge_paragraph() {
3775        macro_rules! test {
3776            ($input:tt, $output:tt) => {
3777                let input = Txt::from($input);
3778                let output = WhiteSpace::MergeParagraph.transform(&input);
3779                assert_eq!($output, output.as_str());
3780
3781                let input = input.replace('\n', "\r\n");
3782                let output = WhiteSpace::MergeParagraph.transform(&Txt::from(input)).replace("\r\n", "\n");
3783                assert_eq!($output, output.as_str());
3784            };
3785        }
3786        test!("a  b\n\nc", "a b\nc");
3787        test!("a b\nc", "a b c");
3788        test!(" a b\nc\n  \n", "a b c");
3789        test!(" \n a b\nc", "a b c");
3790        test!("a\n \nb", "a\nb");
3791    }
3792
3793    #[test]
3794    fn white_space_merge_all() {
3795        macro_rules! test {
3796            ($input:tt, $output:tt) => {
3797                let input = Txt::from($input);
3798                let output = WhiteSpace::MergeAll.transform(&input);
3799                assert_eq!($output, output.as_str());
3800            };
3801        }
3802        test!("a  b\n\nc", "a b c");
3803        test!("a b\nc", "a b c");
3804        test!(" a b\nc\n  \n", "a b c");
3805        test!(" \n a b\nc", "a b c");
3806        test!("a\n \nb", "a b");
3807    }
3808}