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