Skip to main content

zng_ext_font/
lib.rs

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