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