zng_ext_font/
lib.rs

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