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